/* 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 ); }