forked from FWGS/Paranoia2
543 lines
12 KiB
C++
543 lines
12 KiB
C++
/*
|
|
sv_materials.cpp - materials handling and processing at the server-side
|
|
Copyright (C) 2016 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "extdll.h"
|
|
#include "util.h"
|
|
#include "com_model.h"
|
|
#include "material.h"
|
|
#include "triangleapi.h"
|
|
|
|
sv_matdesc_t *sv_materials;
|
|
int sv_matcount;
|
|
|
|
terrain_t *sv_terrains;
|
|
int sv_numterrains;
|
|
|
|
/*
|
|
==================
|
|
SV_LoadMaterials
|
|
|
|
parse material from a given file
|
|
==================
|
|
*/
|
|
void SV_LoadMaterials( const char *path )
|
|
{
|
|
ALERT( at_aiconsole, "loading %s\n", path );
|
|
|
|
char *afile = (char *)LOAD_FILE( (char *)path, NULL );
|
|
|
|
if( !afile )
|
|
{
|
|
ALERT( at_error, "Cannot open file \"%s\"\n", path );
|
|
return;
|
|
}
|
|
|
|
sv_matdesc_t *oldmaterials = sv_materials;
|
|
int oldcount = sv_matcount;
|
|
char *pfile = afile;
|
|
char token[256];
|
|
int depth = 0;
|
|
|
|
// count materials
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( Q_strlen( token ) > 1 )
|
|
continue;
|
|
|
|
if( token[0] == '{' )
|
|
{
|
|
depth++;
|
|
}
|
|
else if( token[0] == '}' )
|
|
{
|
|
sv_matcount++;
|
|
depth--;
|
|
}
|
|
}
|
|
|
|
if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", path );
|
|
if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", path );
|
|
|
|
sv_materials = (sv_matdesc_t *)Mem_Alloc( sizeof( sv_matdesc_t ) * sv_matcount );
|
|
memcpy( sv_materials, oldmaterials, oldcount * sizeof( sv_matdesc_t ));
|
|
Mem_Free( oldmaterials );
|
|
pfile = afile; // start real parsing
|
|
|
|
int current = oldcount; // starts from
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( current >= sv_matcount )
|
|
{
|
|
ALERT ( at_error, "material parse is overrun %d > %d\n", current, sv_matcount );
|
|
break;
|
|
}
|
|
|
|
sv_matdesc_t *mat = &sv_materials[current];
|
|
|
|
// read the material name
|
|
Q_strncpy( mat->name, token, sizeof( mat->name ));
|
|
COM_StripExtension( mat->name );
|
|
|
|
// read opening brace
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( token[0] != '{' )
|
|
{
|
|
ALERT( at_error, "found %s when expecting {\n", token );
|
|
break;
|
|
}
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "EOF without closing brace\n" );
|
|
goto getout;
|
|
}
|
|
|
|
// description end goto next material
|
|
if( token[0] == '}' )
|
|
{
|
|
current++;
|
|
break;
|
|
}
|
|
else if( !Q_stricmp( token, "material" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'material'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
mat->effects = COM_FindMatdef( token );
|
|
}
|
|
else continue; // skip all other tokens on server-side
|
|
}
|
|
|
|
// apply default values
|
|
if( !mat->effects ) mat->effects = COM_DefaultMatdef();
|
|
}
|
|
getout:
|
|
FREE_FILE( afile );
|
|
ALERT( at_aiconsole, "%d materials parsed\n", current );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_InitMaterials
|
|
|
|
parse global material settings
|
|
==================
|
|
*/
|
|
void SV_InitMaterials( void )
|
|
{
|
|
int count = 0;
|
|
char **filenames = GET_FILES_LIST( "scripts/*.mat", &count, 0 );
|
|
|
|
sv_matcount = 0;
|
|
|
|
// sequentially load materials
|
|
for( int i = 0; i < count; i++ )
|
|
SV_LoadMaterials( filenames[i] );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_FindMaterial
|
|
|
|
This function never failed
|
|
==================
|
|
*/
|
|
sv_matdesc_t *SV_FindMaterial( const char *name )
|
|
{
|
|
static sv_matdesc_t defmat;
|
|
|
|
if( !defmat.name[0] )
|
|
{
|
|
// initialize default material
|
|
Q_strncpy( defmat.name, "*default", sizeof( defmat.name ));
|
|
defmat.effects = COM_DefaultMatdef();
|
|
}
|
|
|
|
for( int i = 0; i < sv_matcount; i++ )
|
|
{
|
|
if( !Q_stricmp( name, sv_materials[i].name ))
|
|
return &sv_materials[i];
|
|
}
|
|
|
|
return &defmat;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LoadHeightMap
|
|
|
|
parse heightmap pixels and remap it
|
|
to real layer count
|
|
========================
|
|
*/
|
|
static bool LoadHeightMap( sv_indexMap_t *im, int numLayers )
|
|
{
|
|
unsigned int *src;
|
|
byte *buffer;
|
|
int i, width, height;
|
|
int depth = 1;
|
|
|
|
if( numLayers <= 0 ) return false;
|
|
|
|
// loading heightmap and keep the source pixels
|
|
if(( buffer = (byte *)LOAD_IMAGE_PIXELS( im->name, &width, &height )) == NULL )
|
|
{
|
|
ALERT( at_error, "LoadHeightMap: couldn't get source pixels for %s\n", im->name );
|
|
return false;
|
|
}
|
|
|
|
im->pixels = (byte *)Mem_Alloc( width * height );
|
|
im->numLayers = bound( 1, numLayers, 255 );
|
|
im->height = height;
|
|
im->width = width;
|
|
|
|
src = (unsigned int *)buffer;
|
|
|
|
for( i = 0; i < ( im->width * im->height ); i++ )
|
|
{
|
|
byte rawHeight = ( src[i] & 0xFF );
|
|
im->maxHeight = Q_max(( 16 * (int)ceil( rawHeight / 16 )), im->maxHeight );
|
|
}
|
|
|
|
// merge layers count
|
|
im->numLayers = (im->maxHeight / 16) + 1;
|
|
depth = Q_max((int)Q_ceil((float)im->numLayers / 4.0f ), 1 );
|
|
|
|
// clamp to layers count
|
|
for( i = 0; i < ( im->width * im->height ); i++ )
|
|
im->pixels[i] = (( src[i] & 0xFF ) * ( im->numLayers - 1 )) / im->maxHeight;
|
|
|
|
Mem_Free( buffer ); // no reason to keep this data
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LoadTerrainLayers
|
|
|
|
loading all the landscape layers
|
|
into texture arrays
|
|
========================
|
|
*/
|
|
static bool LoadTerrainLayers( sv_layerMap_t *lm, int numLayers )
|
|
{
|
|
// setup materials
|
|
for( int i = 0; i < numLayers; i++ )
|
|
lm->effects[i] = SV_FindMaterial( lm->names[i] )->effects;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
SV_FreeLandscapes
|
|
|
|
free the landscape definitions
|
|
========================
|
|
*/
|
|
void SV_FreeLandscapes( void )
|
|
{
|
|
for( int i = 0; i < sv_numterrains; i++ )
|
|
{
|
|
terrain_t *terra = &sv_terrains[i];
|
|
sv_indexMap_t *im = &terra->indexmap;
|
|
if( im->pixels ) Mem_Free( im->pixels );
|
|
}
|
|
|
|
if( sv_terrains )
|
|
Mem_Free( sv_terrains );
|
|
sv_numterrains = 0;
|
|
sv_terrains = NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
SV_LoadLandscapes
|
|
|
|
load the landscape definitions
|
|
========================
|
|
*/
|
|
void SV_LoadLandscapes( const char *filename )
|
|
{
|
|
char filepath[256];
|
|
|
|
Q_snprintf( filepath, sizeof( filepath ), "maps/%s_land.txt", filename );
|
|
|
|
char *afile = (char *)LOAD_FILE( filepath, NULL );
|
|
if( !afile ) return;
|
|
|
|
ALERT( at_aiconsole, "loading %s\n", filepath );
|
|
|
|
char *pfile = afile;
|
|
char token[256];
|
|
int depth = 0;
|
|
|
|
// count materials
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( Q_strlen( token ) > 1 )
|
|
continue;
|
|
|
|
if( token[0] == '{' )
|
|
{
|
|
depth++;
|
|
}
|
|
else if( token[0] == '}' )
|
|
{
|
|
sv_numterrains++;
|
|
depth--;
|
|
}
|
|
}
|
|
|
|
if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", filepath );
|
|
if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", filepath );
|
|
|
|
sv_terrains = (terrain_t *)Mem_Alloc( sizeof( terrain_t ) * sv_numterrains );
|
|
pfile = afile; // start real parsing
|
|
|
|
int current = 0;
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( current >= sv_numterrains )
|
|
{
|
|
ALERT ( at_error, "landscape parse is overrun %d > %d\n", current, sv_numterrains );
|
|
break;
|
|
}
|
|
|
|
terrain_t *terra = &sv_terrains[current];
|
|
|
|
// read the landscape name
|
|
Q_strncpy( terra->name, token, sizeof( terra->name ));
|
|
|
|
// read opening brace
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( token[0] != '{' )
|
|
{
|
|
ALERT( at_error, "found %s when expecting {\n", token );
|
|
break;
|
|
}
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "EOF without closing brace\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
// description end goto next material
|
|
if( token[0] == '}' )
|
|
{
|
|
current++;
|
|
break;
|
|
}
|
|
else if( !Q_stricmp( token, "indexMap" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'indexMap'\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
Q_strncpy( terra->indexmap.name, token, sizeof( terra->indexmap.name ));
|
|
}
|
|
else if( !Q_strnicmp( token, "layer", 5 ))
|
|
{
|
|
int layerNum = Q_atoi( token + 5 );
|
|
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'layer'\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
if( layerNum < 0 || layerNum > ( MAX_LANDSCAPE_LAYERS - 1 ))
|
|
{
|
|
ALERT( at_error, "%s is out of range. Ignored\n", token );
|
|
}
|
|
else
|
|
{
|
|
COM_FileBase( token, terra->layermap.names[layerNum] );
|
|
}
|
|
|
|
terra->numLayers = Q_max( terra->numLayers, layerNum + 1 );
|
|
}
|
|
else continue; // skip all other tokens on server-side
|
|
}
|
|
|
|
if( LoadHeightMap( &terra->indexmap, terra->numLayers ))
|
|
{
|
|
if( LoadTerrainLayers( &terra->layermap, terra->numLayers ))
|
|
terra->valid = true; // all done
|
|
}
|
|
}
|
|
|
|
land_getout:
|
|
FREE_FILE( afile );
|
|
ALERT( at_console, "server: %d landscapes parsed\n", current );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
SV_FindTerrain
|
|
|
|
find the terrain description
|
|
========================
|
|
*/
|
|
terrain_t *SV_FindTerrain( const char *texname )
|
|
{
|
|
for( int i = 0; i < sv_numterrains; i++ )
|
|
{
|
|
if( !Q_stricmp( texname, sv_terrains[i].name ) && sv_terrains[i].valid )
|
|
return &sv_terrains[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_ProcessLandscapes
|
|
|
|
handle all the landscapes per level
|
|
=================
|
|
*/
|
|
static void Mod_ProcessLandscapes( msurface_t *surf, mextrasurf_t *esrf )
|
|
{
|
|
mtexinfo_t *tx = surf->texinfo;
|
|
mfaceinfo_t *land = tx->faceinfo;
|
|
|
|
if( !land || land->groupid == 0 || !land->landname[0] )
|
|
return; // no landscape specified, just lightmap resolution
|
|
|
|
if( !land->terrain )
|
|
{
|
|
land->terrain = SV_FindTerrain( land->landname );
|
|
|
|
if( !land->terrain )
|
|
{
|
|
// land name was specified in bsp but not declared in script file
|
|
ALERT( at_error, "Mod_ProcessLandscapes: %s missing description\n", land->landname );
|
|
land->landname[0] = '\0'; // clear name to avoid trying to find invalid terrain
|
|
return;
|
|
}
|
|
|
|
// prepare new landscape params
|
|
ClearBounds( land->mins, land->maxs );
|
|
|
|
// setup shared pointers
|
|
for( int i = 0; i < land->terrain->numLayers; i++ )
|
|
land->effects[i] = land->terrain->layermap.effects[i];
|
|
|
|
land->heightmap = land->terrain->indexmap.pixels;
|
|
land->heightmap_width = land->terrain->indexmap.width;
|
|
land->heightmap_height = land->terrain->indexmap.height;
|
|
}
|
|
|
|
// update terrain bounds
|
|
AddPointToBounds( esrf->mins, land->mins, land->maxs );
|
|
AddPointToBounds( esrf->maxs, land->mins, land->maxs );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_LoadWorld
|
|
|
|
|
|
=================
|
|
*/
|
|
void Mod_LoadWorld( model_t *mod, const byte *buf )
|
|
{
|
|
dheader_t *header;
|
|
dextrahdr_t *extrahdr;
|
|
char barename[64];
|
|
int i;
|
|
|
|
header = (dheader_t *)buf;
|
|
extrahdr = (dextrahdr_t *)((byte *)buf + sizeof( dheader_t ));
|
|
|
|
COM_FileBase( mod->name, barename );
|
|
|
|
// process landscapes first
|
|
SV_LoadLandscapes( barename );
|
|
|
|
// apply materials to right get them on dedicated server
|
|
for( i = 0; i < mod->numtextures; i++ )
|
|
{
|
|
texture_t *tx = mod->textures[i];
|
|
|
|
// bad texture?
|
|
if( !tx || !tx->name[0] ) continue;
|
|
|
|
sv_matdesc_t *desc = SV_FindMaterial( tx->name );
|
|
|
|
tx->effects = desc->effects;
|
|
}
|
|
|
|
// mark surfaces for world features
|
|
for( i = 0; i < mod->numsurfaces; i++ )
|
|
{
|
|
msurface_t *surf = &mod->surfaces[i];
|
|
Mod_ProcessLandscapes( surf, surf->info );
|
|
}
|
|
}
|
|
|
|
void Mod_FreeWorld( model_t *mod )
|
|
{
|
|
// free landscapes
|
|
SV_FreeLandscapes();
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_ProcessWorldData
|
|
|
|
resource management
|
|
==================
|
|
*/
|
|
void SV_ProcessWorldData( model_t *mod, qboolean create, const byte *buffer )
|
|
{
|
|
if( create ) Mod_LoadWorld( mod, buffer );
|
|
else Mod_FreeWorld( mod );
|
|
} |