/* sv_game.c - gamedll interaction Copyright (C) 2008 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 "common.h" #include "server.h" #include "net_encode.h" #include "event_flags.h" #include "library.h" #include "pm_defs.h" #include "studio.h" #include "const.h" #include "render_api.h" // modelstate_t #include "ref_common.h" // decals #define ENTVARS_COUNT ARRAYSIZE( gEntvarsDescription ) // GameAPI functions declarations static int GAME_EXPORT pfnModelIndex( const char *m ); // fatpvs stuff static byte fatpvs[MAX_MAP_LEAFS/8]; static byte fatphs[MAX_MAP_LEAFS/8]; static byte clientpvs[MAX_MAP_LEAFS/8]; // for find client in PVS static vec3_t viewPoint[MAX_CLIENTS]; // exports typedef void (__cdecl *LINK_ENTITY_FUNC)( entvars_t *pev ); typedef void (__stdcall *GIVEFNPTRSTODLL)( enginefuncs_t* engfuncs, globalvars_t *pGlobals ); #ifndef NDEBUG qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) { int n; if( !e ) return false; // may be NULL n = ((int)((edict_t *)(e) - svgame.edicts)); if(( n >= 0 ) && ( n < GI->max_edicts )) return !e->free; Con_Printf( "bad entity %i (called at %s:%i)\n", n, file, line ); return false; } #endif static edict_t *SV_PEntityOfEntIndex( const int iEntIndex, const qboolean allentities ) { if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) { edict_t *pEdict = EDICT_NUM( iEntIndex ); qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) return pEdict; // just get access to array if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) return pEdict; // g-cont: world and clients can be accessed even without private data if( SV_IsValidEdict( pEdict ) && player ) return pEdict; } return NULL; } /* ============= EntvarsDescription entavrs table for FindEntityByString ============= */ static TYPEDESCRIPTION gEntvarsDescription[] = { DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), DEFINE_ENTITY_FIELD( globalname, FIELD_STRING ), DEFINE_ENTITY_FIELD( model, FIELD_MODELNAME ), DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), DEFINE_ENTITY_FIELD( target, FIELD_STRING ), DEFINE_ENTITY_FIELD( targetname, FIELD_STRING ), DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), DEFINE_ENTITY_FIELD( message, FIELD_STRING ), DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), }; /* ============= SV_GetEntvarsDescription entavrs table for FindEntityByString ============= */ static TYPEDESCRIPTION *SV_GetEntvarsDescirption( int number ) { if( number < 0 || number >= ENTVARS_COUNT ) return NULL; return &gEntvarsDescription[number]; } /* ============= SV_SysError tell the game.dll about system error ============= */ void SV_SysError( const char *error_string ) { Log_Printf( "FATAL ERROR (shutting down): %s\n", error_string ); if( svgame.hInstance != NULL ) svgame.dllFuncs.pfnSys_Error( error_string ); } /* ============= SV_Serverinfo get server infostring ============= */ char *SV_Serverinfo( void ) { return svs.serverinfo; } /* ============= SV_AngleMod do modulo on entity angles ============= */ static float SV_AngleMod( float ideal, float current, float speed ) { float move; current = anglemod( current ); if( current == ideal ) // already there? return current; move = ideal - current; if( ideal > current ) { if( move >= 180 ) move = move - 360; } else { if( move <= -180 ) move = move + 360; } if( move > 0 ) { if( move > speed ) move = speed; } else { if( move < -speed ) move = -speed; } return anglemod( current + move ); } /* ============= SV_SetMinMaxSize update entity bounds, relink into world ============= */ void SV_SetMinMaxSize( edict_t *e, const float *mins, const float *maxs, qboolean relink ) { int i; if( !SV_IsValidEdict( e )) return; for( i = 0; i < 3; i++ ) { if( mins[i] > maxs[i] ) { Con_Printf( S_ERROR "%s[%i] has backwards mins/maxs\n", SV_ClassName( e ), NUM_FOR_EDICT( e )); if( relink ) SV_LinkEdict( e, false ); // just relink edict and exit return; } } VectorCopy( mins, e->v.mins ); VectorCopy( maxs, e->v.maxs ); VectorSubtract( maxs, mins, e->v.size ); if( relink ) SV_LinkEdict( e, false ); } /* ============= SV_CopyTraceToGlobal each trace will share their result into global state ============= */ void SV_CopyTraceToGlobal( trace_t *trace ) { svgame.globals->trace_allsolid = trace->allsolid; svgame.globals->trace_startsolid = trace->startsolid; svgame.globals->trace_fraction = trace->fraction; svgame.globals->trace_plane_dist = trace->plane.dist; svgame.globals->trace_inopen = trace->inopen; svgame.globals->trace_inwater = trace->inwater; VectorCopy( trace->endpos, svgame.globals->trace_endpos ); VectorCopy( trace->plane.normal, svgame.globals->trace_plane_normal ); svgame.globals->trace_hitgroup = trace->hitgroup; svgame.globals->trace_flags = 0; // g-cont: always reset config flags when trace is finished if( SV_IsValidEdict( trace->ent )) svgame.globals->trace_ent = trace->ent; else svgame.globals->trace_ent = svgame.edicts; } /* ============== SV_SetModel ============== */ void GAME_EXPORT SV_SetModel( edict_t *ent, const char *modelname ) { char name[MAX_QPATH]; model_t *mod; int i = 1; if( !SV_IsValidEdict( ent )) { Con_Printf( S_WARN "SV_SetModel: invalid entity %s\n", SV_ClassName( ent )); return; } if( !modelname || modelname[0] <= ' ' ) { Con_Printf( S_WARN "SV_SetModel: null name\n" ); return; } if( *modelname == '\\' || *modelname == '/' ) modelname++; Q_strncpy( name, modelname, sizeof( name )); COM_FixSlashes( name ); i = SV_ModelIndex( name ); if( i == 0 ) { if( sv.state == ss_active ) Con_Printf( S_ERROR "SV_SetModel: failed to set model %s: world model cannot be changed\n", name ); return; } if( COM_CheckString( name )) { ent->v.model = MAKE_STRING( sv.model_precache[i] ); ent->v.modelindex = i; mod = sv.models[i]; } else { // model will be cleared ent->v.model = ent->v.modelindex = 0; mod = NULL; } // set the model size if( mod && mod->type != mod_studio ) SV_SetMinMaxSize( ent, mod->mins, mod->maxs, true ); else SV_SetMinMaxSize( ent, vec3_origin, vec3_origin, true ); } /* ============= SV_ConvertTrace convert trace_t to TraceResult ============= */ static void SV_ConvertTrace( TraceResult *dst, trace_t *src ) { if( !src || !dst ) return; dst->fAllSolid = src->allsolid; dst->fStartSolid = src->startsolid; dst->fInOpen = src->inopen; dst->fInWater = src->inwater; dst->flFraction = src->fraction; VectorCopy( src->endpos, dst->vecEndPos ); dst->flPlaneDist = src->plane.dist; VectorCopy( src->plane.normal, dst->vecPlaneNormal ); dst->pHit = src->ent; dst->iHitgroup = src->hitgroup; // g-cont: always reset config flags when trace is finished svgame.globals->trace_flags = 0; } /* ============= SV_CheckClientVisiblity Check visibility through client camera, portal camera, etc ============= */ static qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask ) { int i, clientnum; vec3_t vieworg; mleaf_t *leaf; if( !mask ) return true; // GoldSrc rules clientnum = cl - svs.clients; VectorCopy( viewPoint[clientnum], vieworg ); // Invasion issues: wrong camera position received in ENGINE_SET_PVS if( cl->pViewEntity && !VectorCompare( vieworg, cl->pViewEntity->v.origin )) VectorCopy( cl->pViewEntity->v.origin, vieworg ); leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); if( CHECKVISBIT( mask, leaf->cluster )) return true; // visible from player view or camera view // now check all the portal cameras for( i = 0; i < cl->num_viewents; i++ ) { edict_t *view = cl->viewentity[i]; if( !SV_IsValidEdict( view )) continue; VectorAdd( view->v.origin, view->v.view_ofs, vieworg ); leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); if( CHECKVISBIT( mask, leaf->cluster )) return true; // visible from portal camera view } // not visible from any viewpoint return false; } /* ================= SV_Multicast Sends the contents of sv.multicast to a subset of the clients, then clears sv.multicast. MSG_INIT write message into signon buffer MSG_ONE send to one client (ent can't be NULL) MSG_ALL same as broadcast (origin can be NULL) MSG_PVS send to clients potentially visible from org MSG_PHS send to clients potentially audible from org ================= */ static int SV_Multicast( int dest, const vec3_t origin, const edict_t *ent, qboolean usermessage, qboolean filter ) { byte *mask = NULL; int j, numclients = svs.maxclients; sv_client_t *cl, *current = svs.clients; qboolean reliable = false; qboolean specproxy = false; int numsends = 0; // some mods trying to send messages after SV_FinalMessage if( !svs.initialized || sv.state == ss_dead ) { MSG_Clear( &sv.multicast ); return 0; } switch( dest ) { case MSG_INIT: if( sv.state == ss_loading ) { // copy to signon buffer MSG_WriteBits( &sv.signon, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); MSG_Clear( &sv.multicast ); return 1; } // intentional fallthrough (in-game MSG_INIT it's a MSG_ALL reliable) case MSG_ALL: reliable = true; // intentional fallthrough case MSG_BROADCAST: // nothing to sort break; case MSG_PAS_R: reliable = true; // intentional fallthrough case MSG_PAS: if( origin == NULL ) return false; // NOTE: GoldSource not using PHS for singleplayer Mod_FatPVS( origin, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); mask = fatphs; // using the FatPVS like a PHS break; case MSG_PVS_R: reliable = true; // intentional fallthrough case MSG_PVS: if( origin == NULL ) return 0; mask = Mod_GetPVSForPoint( origin ); break; case MSG_ONE: reliable = true; // intentional fallthrough case MSG_ONE_UNRELIABLE: if( !SV_IsValidEdict( ent )) return 0; j = NUM_FOR_EDICT( ent ); if( j < 1 || j > numclients ) return 0; current = svs.clients + (j - 1); numclients = 1; // send to one break; case MSG_SPEC: specproxy = reliable = true; break; default: Host_Error( "SV_Multicast: bad dest: %i\n", dest ); return 0; } // send the data to all relevent clients (or once only) for( j = 0, cl = current; j < numclients; j++, cl++ ) { if( cl->state == cs_free || cl->state == cs_zombie ) continue; if( cl->state != cs_spawned && ( !reliable || usermessage )) continue; if( specproxy && !FBitSet( cl->flags, FCL_HLTV_PROXY )) continue; if( !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT )) continue; // reject step sounds while predicting is enabled // FIXME: make sure what this code doesn't cutoff something important!!! if( filter && cl == sv.current_client && FBitSet( sv.current_client->flags, FCL_PREDICT_MOVEMENT )) continue; if( SV_IsValidEdict( ent ) && ent->v.groupinfo && cl->edict->v.groupinfo ) { if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo )) continue; if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo )) continue; } if( !SV_CheckClientVisiblity( cl, mask )) continue; if( specproxy ) MSG_WriteBits( &sv.spec_datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); else if( reliable ) MSG_WriteBits( &cl->netchan.message, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); else MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); numsends++; } MSG_Clear( &sv.multicast ); return numsends; // just for debug } /* ======================= SV_GetReliableDatagram Get shared reliable buffer ======================= */ static sizebuf_t *SV_GetReliableDatagram( void ) { return &sv.reliable_datagram; } /* ======================= SV_RestoreCustomDecal Let the user spawn decal in game code ======================= */ qboolean SV_RestoreCustomDecal( decallist_t *entry, edict_t *pEdict, qboolean adjacent ) { if( svgame.physFuncs.pfnRestoreDecal != NULL ) { if( !pEdict ) pEdict = EDICT_NUM( entry->entityIndex ); // true if decal was sucessfully restored at the game-side return svgame.physFuncs.pfnRestoreDecal( entry, pEdict, adjacent ); } return false; } /* ======================= SV_CreateDecal NOTE: static decals only accepted when game is loading ======================= */ void SV_CreateDecal( sizebuf_t *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale ) { if( msg == &sv.signon && sv.state != ss_loading ) return; // this can happens if serialized map contain 4096 static decals... if( MSG_GetNumBytesLeft( msg ) < 20 ) { sv.ignored_world_decals++; return; } // static decals are posters, it's always reliable MSG_BeginServerCmd( msg, svc_bspdecal ); MSG_WriteVec3Coord( msg, origin ); MSG_WriteWord( msg, decalIndex ); MSG_WriteShort( msg, entityIndex ); if( entityIndex > 0 ) MSG_WriteWord( msg, modelIndex ); MSG_WriteByte( msg, flags ); MSG_WriteWord( msg, scale * 4096 ); } /* ======================= SV_CreateStaticEntity NOTE: static entities only accepted when game is loading ======================= */ qboolean SV_CreateStaticEntity( sizebuf_t *msg, int index ) { entity_state_t nullstate, *baseline; entity_state_t *state; int offset; if( index >= ( MAX_STATIC_ENTITIES - 1 )) { if( !sv.static_ents_overflow ) { Con_Printf( S_WARN "MAX_STATIC_ENTITIES limit exceeded (%d)\n", MAX_STATIC_ENTITIES ); sv.static_ents_overflow = true; } sv.ignored_static_ents++; // continue overflowed entities return false; } // this can happens if serialized map contain too many static entities... if( MSG_GetNumBytesLeft( msg ) < 50 ) { sv.ignored_static_ents++; return false; } state = &svs.static_entities[index]; // allocate a new one memset( &nullstate, 0, sizeof( nullstate )); baseline = &nullstate; // restore modelindex from modelname (already precached) state->modelindex = pfnModelIndex( STRING( state->messagenum )); state->entityType = ENTITY_NORMAL; // select delta-encode state->number = 0; // trying to compress with previous delta's offset = SV_FindBestBaselineForStatic( index, &baseline, state ); MSG_BeginServerCmd( msg, svc_spawnstatic ); MSG_WriteDeltaEntity( baseline, state, msg, true, DELTA_STATIC, sv.time, offset ); return true; } /* ================= SV_RestartStaticEnts Write all the static ents into demo ================= */ void SV_RestartStaticEnts( void ) { int i; // remove all the static entities on the client CL_ClearStaticEntities(); // resend them again for( i = 0; i < sv.num_static_entities; i++ ) SV_CreateStaticEntity( &sv.reliable_datagram, i ); } /* ================= SV_StartMusic ================= */ static void SV_StartMusic( const char *curtrack, const char *looptrack, int position ) { MSG_BeginServerCmd( &sv.multicast, svc_stufftext ); MSG_WriteStringf( &sv.multicast, "music \"%s\" \"%s\" %d\n", curtrack, looptrack, position ); SV_Multicast( MSG_ALL, NULL, NULL, false, false ); } /* ================= SV_RestartAmbientSounds Write ambient sounds into demo ================= */ void SV_RestartAmbientSounds( void ) { soundlist_t soundInfo[256]; string curtrack, looptrack; int i, nSounds; int position; if( !SV_Active( )) return; nSounds = S_GetCurrentStaticSounds( soundInfo, 256 ); for( i = 0; i < nSounds; i++ ) { soundlist_t *si = &soundInfo[i]; if( !si->looping || si->entnum == -1 ) continue; S_StopSound( si->entnum, si->channel, si->name ); SV_StartSound( SV_PEntityOfEntIndex( si->entnum, true ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); } #if !XASH_DEDICATED // TODO: ??? // restart soundtrack if( S_StreamGetCurrentState( curtrack, looptrack, &position )) { SV_StartMusic( curtrack, looptrack, position ); } #endif } /* ================= SV_RestartDecals Write all the decals into demo ================= */ void SV_RestartDecals( void ) { decallist_t *entry; int decalIndex; int modelIndex; sizebuf_t *msg; int i; if( !SV_Active( )) return; // g-cont. add space for studiodecals if present host.decalList = (decallist_t *)Z_Calloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); #if !XASH_DEDICATED if( !Host_IsDedicated() ) { host.numdecals = ref.dllFuncs.R_CreateDecalList( host.decalList ); // remove decals from map ref.dllFuncs.R_ClearAllDecals(); } else #endif // XASH_DEDICATED { // we probably running a dedicated server host.numdecals = 0; } // write decals into reliable datagram msg = SV_GetReliableDatagram(); // restore decals and write them into network message for( i = 0; i < host.numdecals; i++ ) { entry = &host.decalList[i]; modelIndex = SV_PEntityOfEntIndex( entry->entityIndex, true )->v.modelindex; // game override if( SV_RestoreCustomDecal( entry, SV_PEntityOfEntIndex( entry->entityIndex, true ), false )) continue; decalIndex = pfnDecalIndex( entry->name ); // studiodecals will be restored at game-side if( !FBitSet( entry->flags, FDECAL_STUDIO )) SV_CreateDecal( msg, entry->position, decalIndex, entry->entityIndex, modelIndex, entry->flags, entry->scale ); } Z_Free( host.decalList ); host.decalList = NULL; host.numdecals = 0; } /* ============== SV_BoxInPVS check brush boxes in fat pvs ============== */ qboolean GAME_EXPORT SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax ) { if( !Mod_BoxVisible( absmin, absmax, Mod_GetPVSForPoint( org ))) return false; return true; } /* ============= SV_ChangeLevel Issue changing level ============= */ void SV_QueueChangeLevel( const char *level, const char *landname ) { uint flags, smooth = false; char mapname[MAX_QPATH]; // hold mapname to other place Q_strncpy( mapname, level, sizeof( mapname )); COM_StripExtension( mapname ); if( COM_CheckString( landname )) smooth = true; flags = SV_MapIsValid( mapname, landname ); if( FBitSet( flags, MAP_INVALID_VERSION )) { Con_Printf( S_ERROR "changelevel: %s is invalid or not supported\n", mapname ); return; } if( !FBitSet( flags, MAP_IS_EXIST )) { Con_Printf( S_ERROR "changelevel: map %s doesn't exist\n", mapname ); return; } if( smooth && !FBitSet( flags, MAP_HAS_LANDMARK )) { if( sv_validate_changelevel.value ) { // NOTE: we find valid map but specified landmark it's doesn't exist // run simple changelevel like in q1, throw warning Con_Printf( S_WARN "changelevel: %s doesn't contain landmark [%s]. smooth transition was disabled\n", mapname, landname ); smooth = false; } } if( svs.maxclients > 1 ) smooth = false; // multiplayer doesn't support smooth transition if( smooth && !Q_stricmp( sv.name, level )) { Con_Printf( S_ERROR "can't changelevel with same map. Ignored.\n" ); return; } // bad changelevel position invoke enables in one-way transition if( sv.framecount < 15 ) { if( sv_validate_changelevel.value ) { Con_Printf( S_WARN "an infinite changelevel was detected and will be disabled until a next save\\restore\n" ); return; // lock with svs.spawncount here } } SV_SkipUpdates (); // changelevel will be executed on a next frame if( smooth ) COM_ChangeLevel( mapname, landname, sv.background ); // Smoothed Half-Life changelevel else COM_ChangeLevel( mapname, NULL, sv.background ); // Classic Quake changlevel } /* ============== SV_WriteEntityPatch Create entity patch for selected map ============== */ void SV_WriteEntityPatch( const char *filename ) { int lumpofs = 0, lumplen = 0; byte buf[MAX_TOKEN]; // 1 kb string bspfilename; dlump_t entities; file_t *f; Q_snprintf( bspfilename, sizeof( bspfilename ), "maps/%s.bsp", filename ); f = FS_Open( bspfilename, "rb", false ); if( !f ) return; memset( buf, 0, MAX_TOKEN ); FS_Read( f, buf, MAX_TOKEN ); // check all the lumps and some other errors if( !Mod_TestBmodelLumps( f, bspfilename, buf, true, &entities )) { FS_Close( f ); return; } lumpofs = entities.fileofs; lumplen = entities.filelen; if( lumplen >= 10 ) { char *entities = NULL; FS_Seek( f, lumpofs, SEEK_SET ); entities = (char *)Z_Calloc( lumplen + 1 ); FS_Read( f, entities, lumplen ); FS_WriteFile( va( "maps/%s.ent", filename ), entities, lumplen ); Con_Printf( "Write 'maps/%s.ent'\n", filename ); Mem_Free( entities ); } FS_Close( f ); } /* ============== SV_ReadEntityScript pfnMapIsValid use this ============== */ static char *SV_ReadEntityScript( const char *filename, int *flags ) { string bspfilename, entfilename; int lumpofs = 0, lumplen = 0; byte buf[MAX_TOKEN]; char *ents = NULL; dlump_t entities; size_t ft1, ft2; file_t *f; *flags = 0; Q_snprintf( bspfilename, sizeof( bspfilename ), "maps/%s.bsp", filename ); f = FS_Open( bspfilename, "rb", false ); if( !f ) return NULL; SetBits( *flags, MAP_IS_EXIST ); memset( buf, 0, MAX_TOKEN ); FS_Read( f, buf, MAX_TOKEN ); // check all the lumps and some other errors if( !Mod_TestBmodelLumps( f, bspfilename, buf, (host_developer.value) ? false : true, &entities )) { SetBits( *flags, MAP_INVALID_VERSION ); FS_Close( f ); return NULL; } // after call Mod_TestBmodelLumps we gurantee what map is valid lumpofs = entities.fileofs; lumplen = entities.filelen; // check for entfile too Q_snprintf( entfilename, sizeof( entfilename ), "maps/%s.ent", filename ); // make sure what entity patch is newer than bsp ft1 = FS_FileTime( bspfilename, false ); ft2 = FS_FileTime( entfilename, true ); if( ft2 != -1 && ft1 < ft2 ) { // grab .ent files only from gamedir ents = (char *)FS_LoadFile( entfilename, NULL, true ); } // at least entities should contain "{ "classname" "worldspawn" }\0" // for correct spawn the level if( !ents && lumplen >= 32 ) { FS_Seek( f, lumpofs, SEEK_SET ); ents = Z_Calloc( lumplen + 1 ); FS_Read( f, ents, lumplen ); } FS_Close( f ); // all done return ents; } /* ============== SV_MapIsValid Validate map ============== */ uint SV_MapIsValid( const char *filename, const char *landmark_name ) { uint flags = 0; char *pfile; char *ents; ents = SV_ReadEntityScript( filename, &flags ); if( ents ) { qboolean need_landmark; char token[MAX_TOKEN]; string check_name; need_landmark = COM_CheckString( landmark_name ); if( !need_landmark ) { Mem_Free( ents ); return flags; } pfile = ents; while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { if( !Q_strcmp( token, "targetname" )) { // check targetname for landmark entity pfile = COM_ParseFile( pfile, check_name, sizeof( check_name )); if( !Q_strcmp( landmark_name, check_name )) { // we found landmark, stop the parsing SetBits( flags, MAP_HAS_LANDMARK ); break; } } } Mem_Free( ents ); } return flags; } /* ============== SV_FreePrivateData release private edict memory ============== */ static void GAME_EXPORT SV_FreePrivateData( edict_t *pEdict ) { if( !pEdict || !pEdict->pvPrivateData ) return; // NOTE: new interface can be missing if( svgame.dllFuncs2.pfnOnFreeEntPrivateData != NULL ) svgame.dllFuncs2.pfnOnFreeEntPrivateData( pEdict ); if( Mem_IsAllocatedExt( svgame.mempool, pEdict->pvPrivateData )) Mem_Free( pEdict->pvPrivateData ); pEdict->pvPrivateData = NULL; } /* ============== SV_InitEdict clear edict for reuse ============== */ void SV_InitEdict( edict_t *pEdict ) { Assert( pEdict != NULL ); SV_FreePrivateData( pEdict ); memset( &pEdict->v, 0, sizeof( entvars_t )); pEdict->v.pContainingEntity = pEdict; pEdict->v.controller[0] = 0x7F; pEdict->v.controller[1] = 0x7F; pEdict->v.controller[2] = 0x7F; pEdict->v.controller[3] = 0x7F; pEdict->free = false; } /* ============== SV_FreeEdict unlink edict from world and free it ============== */ void SV_FreeEdict( edict_t *pEdict ) { Assert( pEdict != NULL ); if( pEdict->free ) return; // unlink from world SV_UnlinkEdict( pEdict ); SV_FreePrivateData( pEdict ); // mark edict as freed pEdict->freetime = sv.time; pEdict->serialnumber++; // invalidate EHANDLE's pEdict->v.solid = SOLID_NOT; pEdict->v.flags = 0; pEdict->v.model = 0; pEdict->v.takedamage = 0; pEdict->v.modelindex = 0; pEdict->v.nextthink = -1; pEdict->v.colormap = 0; pEdict->v.frame = 0; pEdict->v.scale = 0; pEdict->v.gravity = 0; pEdict->v.skin = 0; VectorClear( pEdict->v.angles ); VectorClear( pEdict->v.origin ); pEdict->free = true; } /* ============== SV_AllocEdict allocate new or reuse existing ============== */ edict_t *GAME_EXPORT SV_AllocEdict( void ) { edict_t *e; int i; for( i = svs.maxclients + 1; i < svgame.numEntities; i++ ) { e = EDICT_NUM( i ); // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy if( e->free && ( e->freetime < 2.0f || ( sv.time - e->freetime ) > 0.5f )) { SV_InitEdict( e ); return e; } } if( i >= GI->max_edicts ) Host_Error( "ED_AllocEdict: no free edicts (max is %d)\n", GI->max_edicts ); svgame.numEntities++; e = EDICT_NUM( i ); SV_InitEdict( e ); return e; } /* ============== SV_GetEntityClass get pointer for entity class ============== */ static LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName ) { // allocate edict private memory (passed by dlls) return (LINK_ENTITY_FUNC)COM_GetProcAddress( svgame.hInstance, pszClassName ); } /* ============== SV_AllocPrivateData allocate private data for a given edict if customentity is NULL, no "custom" entity EXPORT is being done if customentity is not NULL, will be set to true if "custom" export was used to create this entity ============== */ static edict_t* SV_AllocPrivateData( edict_t *ent, string_t className, qboolean *customentity ) { const char *pszClassName; LINK_ENTITY_FUNC SpawnEdict; pszClassName = STRING( className ); if( customentity ) *customentity = false; if( !ent ) { // allocate a new one ent = SV_AllocEdict(); } else if( ent->free ) { SV_InitEdict( ent ); // re-init edict } ent->v.classname = className; ent->v.pContainingEntity = ent; // re-link // allocate edict private memory (passed by dlls) SpawnEdict = SV_GetEntityClass( pszClassName ); if( !SpawnEdict ) { // attempt to create custom entity (Xash3D extension) if( svgame.physFuncs.SV_CreateEntity && svgame.physFuncs.SV_CreateEntity( ent, pszClassName ) != -1 ) return ent; if( customentity ) { SpawnEdict = SV_GetEntityClass( "custom" ); *customentity = SpawnEdict != NULL; } if( !SpawnEdict ) { Con_Printf( S_ERROR "No spawn function for \"%s\"\n", pszClassName ); // free entity immediately SV_FreeEdict( ent ); return NULL; } } SpawnEdict( &ent->v ); return ent; } /* ============== SV_CreateNamedEntity create specified entity, alloc private data ============== */ edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className ) { return SV_AllocPrivateData( ent, className, NULL ); } /* ============== SV_FreeEdicts release all the edicts from server ============== */ void SV_FreeEdicts( void ) { int i = 0; edict_t *ent; for( i = 0; i < svgame.numEntities; i++ ) { ent = EDICT_NUM( i ); if( ent->free ) continue; SV_FreeEdict( ent ); } } /* ============== SV_PlaybackReliableEvent reliable event is must be delivered always ============== */ static void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args ) { event_args_t nullargs; memset( &nullargs, 0, sizeof( nullargs )); MSG_BeginServerCmd( msg, svc_event_reliable ); // send event index MSG_WriteUBitLong( msg, eventindex, MAX_EVENT_BITS ); if( delay ) { // send event delay MSG_WriteOneBit( msg, 1 ); MSG_WriteWord( msg, ( delay * 100.0f )); } else MSG_WriteOneBit( msg, 0 ); // reliable events not use delta-compression just null-compression MSG_WriteDeltaEvent( msg, &nullargs, args ); } /* ============== SV_ClassName template to get edict classname ============== */ const char *SV_ClassName( const edict_t *e ) { if( !e ) return "(null)"; if( e->free ) return "freed"; return STRING( e->v.classname ); } /* ============== SV_IsValidCmd command validation ============== */ static qboolean SV_IsValidCmd( const char *pCmd ) { size_t len = Q_strlen( pCmd ); // valid commands all have a ';' or newline '\n' as their last character if( len && ( pCmd[len-1] == '\n' || pCmd[len-1] == ';' )) return true; return false; } /* ============== SV_ClientFromEdict get edict that attached to the client structure ============== */ sv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only ) { int i; if( !SV_IsValidEdict( pEdict )) return NULL; i = NUM_FOR_EDICT( pEdict ) - 1; if( i < 0 || i >= svs.maxclients ) return NULL; if( spawned_only ) { if( svs.clients[i].state != cs_spawned ) return NULL; } return (svs.clients + i); } /* =============================================================================== Game Builtin Functions =============================================================================== */ /* ========= pfnPrecacheModel ========= */ static int GAME_EXPORT pfnPrecacheModel( const char *s ) { qboolean optional = false; int i; if( *s == '!' ) { optional = true; s++; } if(( i = SV_ModelIndex( s )) == 0 ) return 0; sv.models[i] = Mod_ForName( sv.model_precache[i], false, true ); if( !optional ) SetBits( sv.model_precache_flags[i], RES_FATALIFMISSING ); return i; } /* ================= pfnModelIndex ================= */ static int GAME_EXPORT pfnModelIndex( const char *m ) { char name[MAX_QPATH]; int i; if( !COM_CheckString( m )) return 0; if( *m == '\\' || *m == '/' ) m++; Q_strncpy( name, m, sizeof( name )); COM_FixSlashes( name ); for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) { if( !Q_stricmp( sv.model_precache[i], name )) return i; } Con_Printf( S_ERROR "Cannot get index for model %s: not precached\n", name ); return 0; } /* ================= pfnModelFrames ================= */ static int GAME_EXPORT pfnModelFrames( int modelIndex ) { model_t *pmodel = SV_ModelHandle( modelIndex ); if( pmodel != NULL ) return pmodel->numframes; return 1; } /* ================= pfnSetSize ================= */ static void GAME_EXPORT pfnSetSize( edict_t *e, const float *rgflMin, const float *rgflMax ) { if( !SV_IsValidEdict( e )) return; SV_SetMinMaxSize( e, rgflMin, rgflMax, true ); } /* ================= pfnChangeLevel ================= */ static void GAME_EXPORT pfnChangeLevel( const char *level, const char *landmark ) { static uint last_spawncount = 0; char landname[MAX_QPATH]; char *text; if( !COM_CheckString( level ) || sv.state != ss_active ) return; // ??? // make sure we don't issue two changelevels if( svs.spawncount == last_spawncount ) return; last_spawncount = svs.spawncount; landname[0] ='\0'; #ifdef HACKS_RELATED_HLMODS // g-cont. some level-designers wrote landmark name with space // and Cmd_TokenizeString separating all the after space as next argument // emulate this bug for compatibility if( COM_CheckString( landmark )) { text = (char *)landname; while( *landmark && ((byte)*landmark) != ' ' ) *text++ = *landmark++; *text = '\0'; } #else Q_strncpy( landname, landmark, sizeof( landname )); #endif SV_QueueChangeLevel( level, landname ); } /* ================= pfnGetSpawnParms OBSOLETE, UNUSED ================= */ static void GAME_EXPORT pfnGetSpawnParms( edict_t *ent ) { } /* ================= pfnSaveSpawnParms OBSOLETE, UNUSED ================= */ static void GAME_EXPORT pfnSaveSpawnParms( edict_t *ent ) { } /* ================= pfnVecToYaw ================= */ static float GAME_EXPORT pfnVecToYaw( const float *rgflVector ) { return SV_VecToYaw( rgflVector ); } /* ================= pfnMoveToOrigin ================= */ static void GAME_EXPORT pfnMoveToOrigin( edict_t *ent, const float *pflGoal, float dist, int iMoveType ) { if( !pflGoal || !SV_IsValidEdict( ent )) return; SV_MoveToOrigin( ent, pflGoal, dist, iMoveType ); } /* ============== pfnChangeYaw ============== */ static void GAME_EXPORT pfnChangeYaw( edict_t* ent ) { if( !SV_IsValidEdict( ent )) return; ent->v.angles[YAW] = SV_AngleMod( ent->v.ideal_yaw, ent->v.angles[YAW], ent->v.yaw_speed ); } /* ============== pfnChangePitch ============== */ static void GAME_EXPORT pfnChangePitch( edict_t* ent ) { if( !SV_IsValidEdict( ent )) return; ent->v.angles[PITCH] = SV_AngleMod( ent->v.idealpitch, ent->v.angles[PITCH], ent->v.pitch_speed ); } /* ========= SV_FindEntityByString ========= */ static edict_t *GAME_EXPORT SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue ) { int index = 0, e = 0; TYPEDESCRIPTION *desc = NULL; edict_t *ed; const char *t; if( !COM_CheckString( pszValue )) return svgame.edicts; if( pStartEdict ) e = NUM_FOR_EDICT( pStartEdict ); while(( desc = SV_GetEntvarsDescirption( index++ )) != NULL ) { if( !Q_strcmp( pszField, desc->fieldName )) break; } if( desc == NULL ) { Con_Printf( S_ERROR "FindEntityByString: field %s not a string\n", pszField ); return svgame.edicts; } for( e++; e < svgame.numEntities; e++ ) { ed = EDICT_NUM( e ); if( !SV_IsValidEdict( ed )) continue; if( e <= svs.maxclients && !SV_ClientFromEdict( ed, ( svs.maxclients != 1 ))) continue; switch( desc->fieldType ) { case FIELD_STRING: case FIELD_MODELNAME: case FIELD_SOUNDNAME: t = STRING( *(string_t *)&((byte *)&ed->v)[desc->fieldOffset] ); if( t != NULL && t != svgame.globals->pStringBase ) { if( !Q_strcmp( t, pszValue )) return ed; } break; default: ASSERT( 0 ); break; } } return svgame.edicts; } /* ========= SV_FindGlobalEntity ripped out from the hl.dll ========= */ edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ) { edict_t *pent = SV_FindEntityByString( NULL, "globalname", STRING( globalname )); if( SV_IsValidEdict( pent )) { // don't spam about error - game code already tell us if( Q_strcmp( SV_ClassName( pent ), STRING( classname ))) pent = NULL; } return pent; } /* ============== pfnGetEntityIllum returns averaged lightvalue for entity ============== */ static int GAME_EXPORT pfnGetEntityIllum( edict_t* pEnt ) { if( !SV_IsValidEdict( pEnt )) return -1; return SV_LightForEntity( pEnt ); } /* ================= pfnFindEntityInSphere find the entity in sphere ================= */ static edict_t *GAME_EXPORT pfnFindEntityInSphere( edict_t *pStartEdict, const float *org, float flRadius ) { float distSquared; int j, e = 0; float eorg; edict_t *ent; flRadius *= flRadius; if( SV_IsValidEdict( pStartEdict )) e = NUM_FOR_EDICT( pStartEdict ); for( e++; e < svgame.numEntities; e++ ) { ent = EDICT_NUM( e ); if( !SV_IsValidEdict( ent )) continue; // ignore clients that not in a game if( e <= svs.maxclients && !SV_ClientFromEdict( ent, true )) continue; distSquared = 0.0f; for( j = 0; j < 3 && distSquared <= flRadius; j++ ) { if( org[j] < ent->v.absmin[j] ) eorg = org[j] - ent->v.absmin[j]; else if( org[j] > ent->v.absmax[j] ) eorg = org[j] - ent->v.absmax[j]; else eorg = 0.0f; distSquared += eorg * eorg; } if( distSquared < flRadius ) return ent; } return svgame.edicts; } /* ================= SV_CheckClientPVS build the new client PVS ================= */ static int SV_CheckClientPVS( int check, qboolean bMergePVS ) { byte *pvs; vec3_t vieworg; sv_client_t *cl; int i, j, k; edict_t *ent = NULL; // cycle to the next one check = bound( 1, check, svs.maxclients ); if( check == svs.maxclients ) i = 1; // reset cycle else i = check + 1; for( ;; i++ ) { if( i == ( svs.maxclients + 1 )) i = 1; ent = EDICT_NUM( i ); if( i == check ) break; // didn't find anything else if( ent->free || !ent->pvPrivateData || FBitSet( ent->v.flags, FL_NOTARGET )) continue; // anything that is a client, or has a client as an enemy break; } cl = SV_ClientFromEdict( ent, true ); memset( clientpvs, 0xFF, world.visbytes ); // get the PVS for the entity VectorAdd( ent->v.origin, ent->v.view_ofs, vieworg ); pvs = Mod_GetPVSForPoint( vieworg ); if( pvs ) memcpy( clientpvs, pvs, world.visbytes ); // transition in progress if( !cl ) return i; // now merge PVS with all the portal cameras for( k = 0; k < cl->num_viewents && bMergePVS; k++ ) { edict_t *view = cl->viewentity[k]; if( !SV_IsValidEdict( view )) continue; VectorAdd( view->v.origin, view->v.view_ofs, vieworg ); pvs = Mod_GetPVSForPoint( vieworg ); for( j = 0; j < world.visbytes && pvs; j++ ) SetBits( clientpvs[j], pvs[j] ); } return i; } /* ================= pfnFindClientInPVS ================= */ static edict_t* GAME_EXPORT pfnFindClientInPVS( edict_t *pEdict ) { edict_t *pClient; vec3_t view; float delta; model_t *mod; qboolean bMergePVS; mleaf_t *leaf; if( !SV_IsValidEdict( pEdict )) return svgame.edicts; delta = ( sv.time - sv.lastchecktime ); // don't merge visibility for portal entity, only for monsters bMergePVS = FBitSet( pEdict->v.flags, FL_MONSTER ) ? true : false; // find a new check if on a new frame if( delta < 0.0f || delta >= 0.1f ) { sv.lastcheck = SV_CheckClientPVS( sv.lastcheck, bMergePVS ); sv.lastchecktime = sv.time; } // return check if it might be visible pClient = EDICT_NUM( sv.lastcheck ); if( !SV_ClientFromEdict( pClient, true )) return svgame.edicts; mod = SV_ModelHandle( pEdict->v.modelindex ); // portals & monitors // NOTE: this specific break "radiaton tick" in normal half-life. use only as feature if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ) && mod && mod->type == mod_brush && !FBitSet( mod->flags, MODEL_HAS_ORIGIN )) { // handle PVS origin for bmodels VectorAverage( pEdict->v.mins, pEdict->v.maxs, view ); VectorAdd( view, pEdict->v.origin, view ); } else { VectorAdd( pEdict->v.origin, pEdict->v.view_ofs, view ); } leaf = Mod_PointInLeaf( view, sv.worldmodel->nodes ); if( CHECKVISBIT( clientpvs, leaf->cluster )) return pClient; // client which currently in PVS return svgame.edicts; } /* ================= pfnEntitiesInPVS ================= */ static edict_t *pfnEntitiesInPVS( edict_t *pview ) { edict_t *pchain, *ptest; vec3_t viewpoint; edict_t *pent; int i; if( !SV_IsValidEdict( pview )) return NULL; VectorAdd( pview->v.origin, pview->v.view_ofs, viewpoint ); pchain = EDICT_NUM( 0 ); for( i = 1; i < svgame.numEntities; i++ ) { pent = EDICT_NUM( i ); if( !SV_IsValidEdict( pent )) continue; if( pent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( pent->v.aiment )) ptest = pent->v.aiment; else ptest = pent; if( SV_BoxInPVS( viewpoint, ptest->v.absmin, ptest->v.absmax )) { pent->v.chain = pchain; pchain = pent; } } return pchain; } /* ============== pfnMakeVectors ============== */ static void GAME_EXPORT pfnMakeVectors( const float *rgflVector ) { AngleVectors( rgflVector, svgame.globals->v_forward, svgame.globals->v_right, svgame.globals->v_up ); } /* ============== pfnRemoveEntity free edict private mem, unlink physics etc ============== */ static void GAME_EXPORT pfnRemoveEntity( edict_t *e ) { if( !SV_IsValidEdict( e )) return; // never free client or world entity if( NUM_FOR_EDICT( e ) < ( svs.maxclients + 1 )) { Con_Printf( S_ERROR "can't delete %s\n", ( e == EDICT_NUM( 0 )) ? "world" : "client" ); return; } SV_FreeEdict( e ); } /* ============== pfnCreateNamedEntity ============== */ static edict_t *GAME_EXPORT pfnCreateNamedEntity( string_t className ) { return SV_CreateNamedEntity( NULL, className ); } /* ============= pfnMakeStatic move entity to client ============= */ static void GAME_EXPORT pfnMakeStatic( edict_t *ent ) { entity_state_t *state; if( !SV_IsValidEdict( ent )) return; // fill the entity state state = &svs.static_entities[sv.num_static_entities]; // allocate a new one svgame.dllFuncs.pfnCreateBaseline( false, NUM_FOR_EDICT( ent ), state, ent, 0, vec3_origin, vec3_origin ); state->messagenum = ent->v.model; // member modelname if( SV_CreateStaticEntity( &sv.signon, sv.num_static_entities )) sv.num_static_entities++; // remove at end of the frame SetBits( ent->v.flags, FL_KILLME ); } /* ============= pfnEntIsOnFloor legacy builtin ============= */ static int GAME_EXPORT pfnEntIsOnFloor( edict_t *e ) { if( !SV_IsValidEdict( e )) return 0; return SV_CheckBottom( e, MOVE_NORMAL ); } /* =============== pfnDropToFloor =============== */ int GAME_EXPORT pfnDropToFloor( edict_t *e ) { qboolean monsterClip; trace_t trace; vec3_t end; if( !SV_IsValidEdict( e )) return 0; monsterClip = FBitSet( e->v.flags, FL_MONSTERCLIP ) ? true : false; VectorCopy( e->v.origin, end ); end[2] -= 256.0f; trace = SV_Move( e->v.origin, e->v.mins, e->v.maxs, end, MOVE_NORMAL, e, monsterClip ); if( trace.allsolid ) return -1; if( trace.fraction == 1.0f ) return 0; VectorCopy( trace.endpos, e->v.origin ); SV_LinkEdict( e, false ); SetBits( e->v.flags, FL_ONGROUND ); e->v.groundentity = trace.ent; return 1; } /* =============== pfnWalkMove =============== */ static int GAME_EXPORT pfnWalkMove( edict_t *ent, float yaw, float dist, int iMode ) { vec3_t move; if( !SV_IsValidEdict( ent )) return 0; if( !FBitSet( ent->v.flags, FL_FLY|FL_SWIM|FL_ONGROUND )) return 0; yaw = DEG2RAD( yaw ); VectorSet( move, cos( yaw ) * dist, sin( yaw ) * dist, 0.0f ); switch( iMode ) { case WALKMOVE_NORMAL: return SV_MoveStep( ent, move, true ); case WALKMOVE_WORLDONLY: return SV_MoveTest( ent, move, true ); case WALKMOVE_CHECKONLY: return SV_MoveStep( ent, move, false); } return 0; } /* ================= pfnSetOrigin ================= */ static void GAME_EXPORT pfnSetOrigin( edict_t *e, const float *rgflOrigin ) { if( !SV_IsValidEdict( e )) return; VectorCopy( rgflOrigin, e->v.origin ); SV_LinkEdict( e, false ); } /* ================= SV_BuildSoundMsg ================= */ int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample, int vol, float attn, int flags, int pitch, const vec3_t pos ) { int entityIndex; int sound_idx; qboolean spawn; if( vol < 0 || vol > 255 ) { Con_Reportf( S_ERROR "SV_StartSound: volume = %i\n", vol ); vol = bound( 0, vol, 255 ); } if( attn < 0.0f || attn > 4.0f ) { Con_Reportf( S_ERROR "SV_StartSound: attenuation %g must be in range 0-4\n", attn ); attn = bound( 0.0f, attn, 4.0f ); } if( chan < 0 || chan > 7 ) { Con_Reportf( S_ERROR "SV_StartSound: channel must be in range 0-7\n" ); chan = bound( 0, chan, 7 ); } if( pitch < 0 || pitch > 255 ) { Con_Reportf( S_ERROR "SV_StartSound: pitch = %i\n", pitch ); pitch = bound( 0, pitch, 255 ); } if( !COM_CheckString( sample )) { Con_Reportf( S_ERROR "SV_StartSound: passed NULL sample\n" ); return 0; } if( sample[0] == '!' && Q_isdigit( sample + 1 )) { sound_idx = Q_atoi( sample + 1 ); if( sound_idx >= MAX_SOUNDS_NONSENTENCE ) { SetBits( flags, SND_SENTENCE|SND_SEQUENCE ); sound_idx -= MAX_SOUNDS_NONSENTENCE; } else SetBits( flags, SND_SENTENCE ); } else if( sample[0] == '#' && Q_isdigit( sample + 1 )) { SetBits( flags, SND_SENTENCE|SND_SEQUENCE ); sound_idx = Q_atoi( sample + 1 ); } else { // '*' is special symbol to handle stream sounds // (CHAN_VOICE but cannot be overriden) // originally handled on client side if( *sample == '*' ) chan = CHAN_STREAM; // precache_sound can be used twice: cache sounds when loading // and return sound index when server is active sound_idx = SV_SoundIndex( sample ); if( !sound_idx ) { Con_Printf( S_ERROR "SV_StartSound: %s not precached (%d)\n", sample, sound_idx ); return 0; } } spawn = FBitSet( flags, SND_RESTORE_POSITION ) ? false : true; if( SV_IsValidEdict( ent ) && SV_IsValidEdict( ent->v.aiment )) entityIndex = NUM_FOR_EDICT( ent->v.aiment ); else if( SV_IsValidEdict( ent )) entityIndex = NUM_FOR_EDICT( ent ); else entityIndex = 0; // assume world if( vol != 255 ) SetBits( flags, SND_VOLUME ); if( attn != ATTN_NONE ) SetBits( flags, SND_ATTENUATION ); if( pitch != PITCH_NORM ) SetBits( flags, SND_PITCH ); // not sending (because this is out of range) ClearBits( flags, SND_RESTORE_POSITION ); ClearBits( flags, SND_FILTER_CLIENT ); ClearBits( flags, SND_SPAWNING ); if( spawn ) MSG_BeginServerCmd( msg, svc_sound ); else MSG_BeginServerCmd( msg, svc_restoresound ); MSG_WriteUBitLong( msg, flags, MAX_SND_FLAGS_BITS ); MSG_WriteUBitLong( msg, sound_idx, MAX_SOUND_BITS ); MSG_WriteUBitLong( msg, chan, MAX_SND_CHAN_BITS ); if( FBitSet( flags, SND_VOLUME )) MSG_WriteByte( msg, vol ); if( FBitSet( flags, SND_ATTENUATION )) MSG_WriteByte( msg, attn * 64 ); if( FBitSet( flags, SND_PITCH )) MSG_WriteByte( msg, pitch ); MSG_WriteUBitLong( msg, entityIndex, MAX_ENTITY_BITS ); MSG_WriteVec3Coord( msg, pos ); return 1; } /* ================= SV_StartSound ================= */ void GAME_EXPORT SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ) { qboolean filter = false; int msg_dest; vec3_t origin; if( !SV_IsValidEdict( ent )) return; VectorAverage( ent->v.mins, ent->v.maxs, origin ); VectorAdd( origin, ent->v.origin, origin ); if( FBitSet( flags, SND_SPAWNING )) msg_dest = MSG_INIT; else if( chan == CHAN_STATIC ) msg_dest = MSG_ALL; else if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) msg_dest = MSG_ALL; else msg_dest = (svs.maxclients <= 1 ) ? MSG_ALL : MSG_PAS_R; // always sending stop sound command if( FBitSet( flags, SND_STOP )) msg_dest = MSG_ALL; if( FBitSet( flags, SND_FILTER_CLIENT )) filter = true; if( SV_BuildSoundMsg( &sv.multicast, ent, chan, sample, vol * 255, attn, flags, pitch, origin )) SV_Multicast( msg_dest, origin, NULL, false, filter ); } /* ================= pfnEmitAmbientSound ================= */ static void GAME_EXPORT pfnEmitAmbientSound( edict_t *ent, float *pos, const char *sample, float vol, float attn, int flags, int pitch ) { int msg_dest; if( sv.state == ss_loading ) SetBits( flags, SND_SPAWNING ); if( FBitSet( flags, SND_SPAWNING )) msg_dest = MSG_INIT; else msg_dest = MSG_ALL; // always sending stop sound command if( FBitSet( flags, SND_STOP )) msg_dest = MSG_ALL; if( SV_BuildSoundMsg( &sv.multicast, ent, CHAN_STATIC, sample, vol * 255, attn, flags, pitch, pos )) SV_Multicast( msg_dest, pos, NULL, false, false ); } /* ================= pfnTraceLine ================= */ static void GAME_EXPORT pfnTraceLine( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ) { trace_t trace; trace = SV_Move( v1, vec3_origin, vec3_origin, v2, fNoMonsters, pentToSkip, false ); if( !SV_IsValidEdict( trace.ent )) trace.ent = svgame.edicts; SV_ConvertTrace( ptr, &trace ); } /* ================= pfnTraceToss ================= */ static void GAME_EXPORT pfnTraceToss( edict_t *pent, edict_t *pentToIgnore, TraceResult *ptr ) { trace_t trace; if( !SV_IsValidEdict( pent )) return; trace = SV_MoveToss( pent, pentToIgnore ); SV_ConvertTrace( ptr, &trace ); } /* ================= pfnTraceHull ================= */ static void GAME_EXPORT pfnTraceHull( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr ) { trace_t trace; if( hullNumber < 0 || hullNumber > 3 ) hullNumber = 0; trace = SV_Move( v1, sv.worldmodel->hulls[hullNumber].clip_mins, sv.worldmodel->hulls[hullNumber].clip_maxs, v2, fNoMonsters, pentToSkip, false ); SV_ConvertTrace( ptr, &trace ); } /* ============= pfnTraceMonsterHull ============= */ static int GAME_EXPORT pfnTraceMonsterHull( edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ) { qboolean monsterClip; trace_t trace; if( !SV_IsValidEdict( pEdict )) return 0; monsterClip = FBitSet( pEdict->v.flags, FL_MONSTERCLIP ) ? true : false; trace = SV_Move( v1, pEdict->v.mins, pEdict->v.maxs, v2, fNoMonsters, pentToSkip, monsterClip ); SV_ConvertTrace( ptr, &trace ); if( trace.allsolid || trace.fraction != 1.0f ) return true; return false; } /* ============= pfnTraceModel ============= */ static void GAME_EXPORT pfnTraceModel( const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr ) { float *mins, *maxs; model_t *model; trace_t trace; if( !SV_IsValidEdict( pent )) return; if( hullNumber < 0 || hullNumber > 3 ) hullNumber = 0; mins = sv.worldmodel->hulls[hullNumber].clip_mins; maxs = sv.worldmodel->hulls[hullNumber].clip_maxs; model = SV_ModelHandle( pent->v.modelindex ); if( pent->v.solid == SOLID_CUSTOM ) { // NOTE: always goes through custom clipping move // even if our callbacks is not initialized SV_CustomClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); } else if( model && model->type == mod_brush ) { int oldmovetype = pent->v.movetype; int oldsolid = pent->v.solid; pent->v.movetype = MOVETYPE_PUSH; pent->v.solid = SOLID_BSP; SV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); pent->v.movetype = oldmovetype; pent->v.solid = oldsolid; } else { SV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); } SV_ConvertTrace( ptr, &trace ); } /* ============= pfnTraceTexture returns texture basename ============= */ static const char *pfnTraceTexture( edict_t *pTextureEntity, const float *v1, const float *v2 ) { if( !SV_IsValidEdict( pTextureEntity )) return NULL; return SV_TraceTexture( pTextureEntity, v1, v2 ); } /* ============= pfnTraceSphere OBSOLETE, UNUSED ============= */ static void GAME_EXPORT pfnTraceSphere( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr ) { } /* ============= pfnGetAimVector NOTE: speed is unused ============= */ static void GAME_EXPORT pfnGetAimVector( edict_t* ent, float speed, float *rgflReturn ) { edict_t *check; vec3_t start, dir, end, bestdir; float dist, bestdist; int i, j; trace_t tr; VectorCopy( svgame.globals->v_forward, rgflReturn ); // assume failure if it returns early if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_FAKECLIENT )) return; VectorCopy( ent->v.origin, start ); VectorAdd( start, ent->v.view_ofs, start ); // try sending a trace straight VectorCopy( svgame.globals->v_forward, dir ); VectorMA( start, 2048, dir, end ); tr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false ); // don't aim at teammate if( tr.ent && ( tr.ent->v.takedamage == DAMAGE_AIM || ent->v.team <= 0 || ent->v.team != tr.ent->v.team )) return; // try all possible entities VectorCopy( svgame.globals->v_forward, bestdir ); if( sv_allow_autoaim.value ) bestdist = sv_aim.value; else bestdist = 0; check = EDICT_NUM( 1 ); // start at first client for( i = 1; i < svgame.numEntities; i++, check++ ) { if( check->v.takedamage != DAMAGE_AIM ) continue; if( FBitSet( check->v.flags, FL_FAKECLIENT )) continue; if( ent->v.team > 0 && ent->v.team == check->v.team ) continue; if( check == ent ) continue; for( j = 0; j < 3; j++ ) end[j] = check->v.origin[j] + 0.5f * (check->v.mins[j] + check->v.maxs[j]); VectorSubtract( end, start, dir ); VectorNormalize( dir ); dist = DotProduct( dir, svgame.globals->v_forward ); if( dist < bestdist ) continue; // to far to turn tr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false ); if( tr.ent == check ) { // can shoot at this one VectorCopy( dir, bestdir ); bestdist = dist; } } VectorCopy( bestdir, rgflReturn ); } /* ========= pfnServerCommand ========= */ static void GAME_EXPORT pfnServerCommand( const char* str ) { if( !SV_IsValidCmd( str )) Con_Printf( S_ERROR "bad server command %s\n", str ); else Cbuf_AddText( str ); } /* ========= pfnServerExecute ========= */ static void GAME_EXPORT pfnServerExecute( void ) { Cbuf_Execute(); } /* ========= pfnClientCommand ========= */ void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) _format( 2 ); void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) { sv_client_t *cl; string buffer; va_list args; if( sv.state != ss_active ) return; // early out if(( cl = SV_ClientFromEdict( pEdict, true )) == NULL ) { Con_Printf( S_ERROR "stuffcmd: client is not spawned!\n" ); return; } if( FBitSet( cl->flags, FCL_FAKECLIENT )) return; va_start( args, szFmt ); Q_vsnprintf( buffer, MAX_STRING, szFmt, args ); va_end( args ); if( SV_IsValidCmd( buffer )) { MSG_BeginServerCmd( &cl->netchan.message, svc_stufftext ); MSG_WriteString( &cl->netchan.message, buffer ); } else Con_Printf( S_ERROR "Tried to stuff bad command %s\n", buffer ); } /* ================= pfnParticleEffect Make sure the event gets sent to all clients ================= */ static void GAME_EXPORT pfnParticleEffect( const float *org, const float *dir, float color, float count ) { int v; if( MSG_GetNumBytesLeft( &sv.datagram ) < 16 ) return; MSG_BeginServerCmd( &sv.datagram, svc_particle ); MSG_WriteVec3Coord( &sv.datagram, org ); v = bound( -128, dir[0] * 16.0f, 127 ); MSG_WriteChar( &sv.datagram, v ); v = bound( -128, dir[1] * 16.0f, 127 ); MSG_WriteChar( &sv.datagram, v ); v = bound( -128, dir[2] * 16.0f, 127 ); MSG_WriteChar( &sv.datagram, v ); MSG_WriteByte( &sv.datagram, count ); MSG_WriteByte( &sv.datagram, color ); MSG_WriteByte( &sv.datagram, 0 ); // z-vel } /* =============== pfnLightStyle =============== */ static void GAME_EXPORT pfnLightStyle( int style, const char* val ) { if( style < 0 ) style = 0; if( style >= MAX_LIGHTSTYLES ) Host_Error( "SV_LightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES ); if( sv.loadgame ) return; // don't let the world overwrite our restored styles SV_SetLightStyle( style, val, 0.0f ); // set correct style } /* ================= pfnDecalIndex register decal name on client ================= */ int GAME_EXPORT pfnDecalIndex( const char *m ) { int i; if( !COM_CheckString( m )) return -1; for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) { if( !Q_stricmp( host.draw_decals[i], m )) return i; } return -1; } /* ============= pfnMessageBegin ============= */ static void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigin, edict_t *ed ) { int i, iSize; if( svgame.msg_started ) Host_Error( "MessageBegin: New message started when msg '%s' has not been sent yet\n", svgame.msg_name ); svgame.msg_started = true; // check range msg_num = bound( svc_bad, msg_num, 255 ); if( msg_num <= svc_lastmsg ) { svgame.msg_index = -msg_num; // this is a system message svgame.msg_name = svc_strings[msg_num]; if( msg_num == svc_temp_entity ) iSize = -1; // temp entity have variable size else iSize = 0; } else { // check for existing for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ ) { if( svgame.msg[i].number == msg_num ) break; // found } if( i == MAX_USER_MESSAGES ) { Host_Error( "MessageBegin: tried to send unregistered message %i\n", msg_num ); return; } svgame.msg_name = svgame.msg[i].name; iSize = svgame.msg[i].size; svgame.msg_index = i; } MSG_WriteCmdExt( &sv.multicast, msg_num, NS_SERVER, svgame.msg_name ); // save message destination if( pOrigin ) VectorCopy( pOrigin, svgame.msg_org ); else VectorClear( svgame.msg_org ); if( iSize == -1 ) { // variable sized messages sent size as first short svgame.msg_size_index = MSG_GetNumBytesWritten( &sv.multicast ); MSG_WriteWord( &sv.multicast, 0 ); // reserve space for now } else svgame.msg_size_index = -1; // message has constant size svgame.msg_realsize = 0; svgame.msg_dest = msg_dest; svgame.msg_ent = ed; // enable message tracing svgame.msg_trace = sv_trace_messages.value != 0 && msg_num > svc_lastmsg && Q_strcmp( svgame.msg_name, "ReqState" ); if( svgame.msg_trace ) Con_Printf( "^3%s( %i, %s )\n", __FUNCTION__, msg_dest, svgame.msg_name ); } /* ============= pfnMessageEnd ============= */ static void GAME_EXPORT pfnMessageEnd( void ) { const char *name = "Unknown"; float *org = NULL; word realsize; if( svgame.msg_name ) name = svgame.msg_name; if( !svgame.msg_started ) Host_Error( "MessageEnd: called with no active message\n" ); svgame.msg_started = false; if( MSG_CheckOverflow( &sv.multicast )) { Con_Printf( S_ERROR "MessageEnd: %s has overflow multicast buffer\n", name ); MSG_Clear( &sv.multicast ); return; } // check for system message if( svgame.msg_index < 0 ) { if( svgame.msg_size_index != -1 ) { // variable sized message if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) { Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); MSG_Clear( &sv.multicast ); return; } else if( svgame.msg_realsize < 0 ) { Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); MSG_Clear( &sv.multicast ); return; } realsize = svgame.msg_realsize; memcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize )); } } else if( svgame.msg[svgame.msg_index].size != -1 ) { int expsize = svgame.msg[svgame.msg_index].size; int realsize = svgame.msg_realsize; // compare sizes if( expsize != realsize ) { Con_Printf( S_ERROR "SV_Multicast: %s expected %i bytes, it written %i. Ignored.\n", name, expsize, realsize ); MSG_Clear( &sv.multicast ); return; } } else if( svgame.msg_size_index != -1 ) { // variable sized message if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) { Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); MSG_Clear( &sv.multicast ); return; } else if( svgame.msg_realsize < 0 ) { Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); MSG_Clear( &sv.multicast ); return; } realsize = svgame.msg_realsize; memcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize )); } else { // this should never happen Con_Printf( S_ERROR "SV_Multicast: %s have encountered error\n", name ); MSG_Clear( &sv.multicast ); return; } // update some messages in case their was format was changed and we want to keep backward compatibility if( svgame.msg_index < 0 ) { int svc_msg = abs( svgame.msg_index ); if(( svc_msg == svc_finale || svc_msg == svc_cutscene ) && svgame.msg_realsize == 0 ) MSG_WriteChar( &sv.multicast, 0 ); // write null string } if( !VectorIsNull( svgame.msg_org )) org = svgame.msg_org; svgame.msg_dest = bound( MSG_BROADCAST, svgame.msg_dest, MSG_SPEC ); SV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false ); if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __FUNCTION__ ); } /* ============= pfnWriteByte ============= */ static void GAME_EXPORT pfnWriteByte( int iValue ) { if( iValue == -1 ) iValue = 0xFF; // convert char to byte MSG_WriteByte( &sv.multicast, (byte)iValue ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize++; } /* ============= pfnWriteChar ============= */ static void GAME_EXPORT pfnWriteChar( int iValue ) { MSG_WriteChar( &sv.multicast, (signed char)iValue ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize++; } /* ============= pfnWriteShort ============= */ static void GAME_EXPORT pfnWriteShort( int iValue ) { MSG_WriteShort( &sv.multicast, (short)iValue ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } /* ============= pfnWriteLong ============= */ static void GAME_EXPORT pfnWriteLong( int iValue ) { MSG_WriteLong( &sv.multicast, iValue ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 4; } /* ============= pfnWriteAngle this is low-res angle ============= */ static void GAME_EXPORT pfnWriteAngle( float flValue ) { int iAngle = ((int)(( flValue ) * 256 / 360) & 255); MSG_WriteChar( &sv.multicast, iAngle ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); svgame.msg_realsize += 1; } /* ============= pfnWriteCoord ============= */ static void GAME_EXPORT pfnWriteCoord( float flValue ) { MSG_WriteCoord( &sv.multicast, flValue ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); svgame.msg_realsize += 2; } /* ============= pfnWriteString ============= */ static void GAME_EXPORT pfnWriteString( const char *src ) { MSG_WriteString( &sv.multicast, src ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )\n", __FUNCTION__, src ); // NOTE: some messages with constant string length can be marked as known sized svgame.msg_realsize += Q_strlen( src ) + 1; } /* ============= pfnWriteEntity ============= */ static void GAME_EXPORT pfnWriteEntity( int iValue ) { if( iValue < 0 || iValue >= svgame.numEntities ) Host_Error( "MSG_WriteEntity: invalid entnumber %i\n", iValue ); MSG_WriteShort( &sv.multicast, (short)iValue ); if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } /* ============= pfnCvar_RegisterServerVariable standard path to register game variable ============= */ static void GAME_EXPORT pfnCvar_RegisterServerVariable( cvar_t *variable ) { if( variable != NULL ) { SetBits( variable->flags, FCVAR_EXTDLL ); Cvar_RegisterVariable( (convar_t *)variable ); } } /* ============= pfnAlertMessage ============= */ static void pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) _format( 2 ); static void GAME_EXPORT pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) { char buffer[2048]; va_list args; if( type == at_logged && svs.maxclients > 1 ) { va_start( args, szFmt ); Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); va_end( args ); Log_Printf( "%s", buffer ); return; } if( host_developer.value <= DEV_NONE ) return; // g-cont: some mods have wrong aiconsole messages that crash the engine if( type == at_aiconsole && host_developer.value < DEV_EXTENDED ) return; va_start( args, szFmt ); Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); va_end( args ); // check message for pass switch( type ) { case at_notice: Con_Printf( S_NOTE "%s", buffer ); break; case at_console: Con_Printf( "%s", buffer ); break; case at_aiconsole: Con_DPrintf( "%s", buffer ); break; case at_warning: Con_Printf( S_WARN "%s", buffer ); break; case at_error: Con_Printf( S_ERROR "%s", buffer ); break; } } /* ============= pfnEngineFprintf OBSOLETE, UNUSED ============= */ static void GAME_EXPORT pfnEngineFprintf( FILE *pfile, char *szFmt, ... ) { } /* ============= pfnBuildSoundMsg Customizable sound message ============= */ static void GAME_EXPORT pfnBuildSoundMsg( edict_t *pSource, int chan, const char *samp, float fvol, float attn, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *pSend ) { pfnMessageBegin( msg_dest, msg_type, pOrigin, pSend ); SV_BuildSoundMsg( &sv.multicast, pSource, chan, samp, fvol * 255, attn, fFlags, pitch, pOrigin ); pfnMessageEnd(); } /* ============= pfnPvAllocEntPrivateData ============= */ static void *GAME_EXPORT pfnPvAllocEntPrivateData( edict_t *pEdict, long cb ) { Assert( pEdict != NULL ); SV_FreePrivateData( pEdict ); if( cb > 0 ) { // a poke646 have memory corrupt in somewhere - this is trashed last sixteen bytes :( pEdict->pvPrivateData = Mem_Calloc( svgame.mempool, (cb + 15) & ~15 ); } return pEdict->pvPrivateData; } /* ============= pfnPvEntPrivateData we already have copy of this function in 'enginecallback.h' :-) ============= */ static void *GAME_EXPORT pfnPvEntPrivateData( edict_t *pEdict ) { if( pEdict ) return pEdict->pvPrivateData; return NULL; } #ifdef XASH_64BIT static struct str64_s { size_t maxstringarray; qboolean allowdup; char *staticstringarray; char *pstringarray; char *pstringarraystatic; char *pstringbase; char *poldstringbase; char *plast; qboolean dynamic; size_t maxalloc; size_t numdups; size_t numoverflows; size_t totalalloc; } str64; #endif /* ================== SV_EmptyStringPool Free strings on server stop. Reset string pointer on 64 bits ================== */ void SV_EmptyStringPool( void ) { #ifdef XASH_64BIT if( str64.dynamic ) // switch only after array fill (more space for multiplayer games) str64.pstringbase = str64.pstringarray; else { str64.pstringbase = str64.poldstringbase = str64.pstringarraystatic; str64.plast = str64.pstringbase + 1; } #else Mem_EmptyPool( svgame.stringspool ); #endif } /* =============== SV_SetStringArrayMode use different arrays on 64 bit platforms set dynamic after complete server spawn this helps not to lose strings that belongs to static game part =============== */ void SV_SetStringArrayMode( qboolean dynamic ) { #ifdef XASH_64BIT Con_Reportf( "SV_SetStringArrayMode(%d) %d\n", dynamic, str64.dynamic ); if( dynamic == str64.dynamic ) return; str64.dynamic = dynamic; SV_EmptyStringPool(); #endif } #if XASH_64BIT && !XASH_WIN32 && !XASH_APPLE && !XASH_NSWITCH && !XASH_ANDROID #define USE_MMAP #include #endif /* ================== SV_AllocStringPool alloc string pool on 32bit platforms alloc string array near the server library on 64bit platforms if possible alloc string array somewhere if not (MAKE_STRING will not work. Always call ALLOC_STRING instead, or crash) this case need patched game dll with MAKE_STRING checking ptrdiff size ================== */ static void SV_AllocStringPool( void ) { #ifdef XASH_64BIT void *ptr = NULL; string lenstr; Con_Reportf( "SV_AllocStringPool()\n" ); if( Sys_GetParmFromCmdLine( "-str64alloc", lenstr ) ) { str64.maxstringarray = Q_atoi( lenstr ); if( str64.maxstringarray < 1024 || str64.maxstringarray >= INT_MAX ) str64.maxstringarray = 65536; } else str64.maxstringarray = 65536; if( Sys_CheckParm( "-str64dup" ) ) str64.allowdup = true; #ifdef USE_MMAP { uint flags; size_t pagesize = sysconf( _SC_PAGESIZE ); int arrlen = (str64.maxstringarray * 2) & ~(pagesize - 1); void *base = svgame.dllFuncs.pfnGameInit; void *start = svgame.hInstance - arrlen; #if defined(MAP_ANON) flags = MAP_ANON | MAP_PRIVATE; #elif defined(MAP_ANONYMOUS) flags = MAP_ANONYMOUS | MAP_PRIVATE; #endif while( start - base > INT_MIN ) { void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 ); if( mapptr && mapptr != (void*)-1 && mapptr - base > INT_MIN && mapptr - base < INT_MAX ) { ptr = mapptr; break; } if( mapptr ) munmap( mapptr, arrlen ); start -= arrlen; } if( !ptr ) { start = base; while( start - base < INT_MAX ) { void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 ); if( mapptr && mapptr != (void*)-1 && mapptr - base > INT_MIN && mapptr - base < INT_MAX ) { ptr = mapptr; break; } if( mapptr ) munmap( mapptr, arrlen ); start += arrlen; } } if( ptr ) { Con_Reportf( "SV_AllocStringPool: Allocated string array near the server library: %p %p\n", base, ptr ); } else { Con_Reportf( "SV_AllocStringPool: Failed to allocate string array near the server library!\n" ); ptr = str64.staticstringarray = Mem_Calloc(host.mempool, str64.maxstringarray * 2); } } #else ptr = str64.staticstringarray = Mem_Calloc(host.mempool, str64.maxstringarray * 2); #endif str64.pstringarray = ptr; str64.pstringarraystatic = (byte*)ptr + str64.maxstringarray; str64.pstringbase = str64.poldstringbase = ptr; str64.plast = (byte*)ptr + 1; svgame.globals->pStringBase = ptr; #else svgame.stringspool = Mem_AllocPool( "Server Strings" ); svgame.globals->pStringBase = ""; #endif } static void SV_FreeStringPool( void ) { #ifdef XASH_64BIT Con_Reportf( "SV_FreeStringPool()\n" ); #ifdef USE_MMAP if( str64.pstringarray != str64.staticstringarray ) munmap( str64.pstringarray, (str64.maxstringarray * 2) & ~(sysconf( _SC_PAGESIZE ) - 1) ); else #endif Mem_Free( str64.staticstringarray ); #else Mem_FreePool( &svgame.stringspool ); #endif } /* ============ SV_ProcessString Process newly allocated string pass NULL pointer to dst to get required length incl. null terminator ============ */ static uint SV_ProcessString( char *dst, const char *src ) { const char *p; uint i = 0; p = src; while( *p ) { if( *p == '\\' ) { char replace = 0; switch( p[1] ) { case 'n': replace = '\n'; break; // GoldSrc doesn't replace these symbols // but old hack in pfnWriteString did case 'r': replace = '\r'; break; case 't': replace = '\t'; break; } if( replace ) { if( dst ) dst[i] = replace; i++; p += 2; continue; } } if( dst ) dst[i] = *p; i++; p++; } // null terminator if( dst ) dst[i] = '\0'; i++; return i; } /* ============= SV_AllocString allocate new engine string on 64bit platforms find in array string if deduplication enabled (default) if not found, add to array use -str64dup to disable deduplication, -str64alloc to set array size ============= */ string_t GAME_EXPORT SV_AllocString( const char *szValue ) { char *newString = NULL; uint len; #ifdef XASH_64BIT int cmp; #endif if( svgame.physFuncs.pfnAllocString != NULL ) { string_t i; newString = Mem_Malloc( host.mempool, SV_ProcessString( NULL, szValue )); SV_ProcessString( newString, szValue ); i = svgame.physFuncs.pfnAllocString( newString ); Mem_Free( newString ); return i; } #ifdef XASH_64BIT cmp = 1; if( !str64.allowdup ) { for( newString = str64.poldstringbase + 1; newString < str64.plast && ( cmp = Q_strcmp( newString, szValue ) ); newString += Q_strlen( newString ) + 1 ); } if( cmp ) { uint len = SV_ProcessString( NULL, szValue ); if( str64.plast - str64.poldstringbase + len + 1 > str64.maxstringarray ) { str64.plast = str64.pstringbase + 1; str64.poldstringbase = str64.pstringbase; str64.numoverflows++; } //MsgDev( D_NOTE, "SV_AllocString: %ld %s\n", str64.plast - svgame.globals->pStringBase, szValue ); SV_ProcessString( str64.plast, szValue ); str64.totalalloc += len; newString = str64.plast; str64.plast += len; } else { str64.numdups++; //MsgDev( D_NOTE, "SV_AllocString: dup %ld %s\n", newString - svgame.globals->pStringBase, szValue ); } if( newString - str64.pstringarray > str64.maxalloc ) str64.maxalloc = newString - str64.pstringarray; return newString - svgame.globals->pStringBase; #else len = SV_ProcessString( NULL, szValue ); newString = Mem_Malloc( svgame.stringspool, len ); SV_ProcessString( newString, szValue ); return newString - svgame.globals->pStringBase; #endif } void SV_PrintStr64Stats_f( void ) { #ifdef XASH_64BIT Con_Printf( "====================\n" ); Con_Printf( "64 bit string pool statistics\n" ); Con_Printf( "====================\n" ); Con_Printf( "string array size: %lu\n", str64.maxstringarray ); Con_Printf( "total alloc %lu\n", str64.totalalloc ); Con_Printf( "maximum array usage: %lu\n", str64.maxalloc ); Con_Printf( "overflow counter: %lu\n", str64.numoverflows ); Con_Printf( "dup string counter: %lu\n", str64.numdups ); #else Con_Printf( "Not implemented\n" ); #endif } /* ============= SV_MakeString make constant string ============= */ string_t SV_MakeString( const char *szValue ) { if( svgame.physFuncs.pfnMakeString != NULL ) return svgame.physFuncs.pfnMakeString( szValue ); #ifdef XASH_64BIT { long long ptrdiff = szValue - svgame.globals->pStringBase; if( ptrdiff > INT_MAX || ptrdiff < INT_MIN ) return SV_AllocString(szValue); else return (int)ptrdiff; } #else return szValue - svgame.globals->pStringBase; #endif } /* ============= SV_GetString ============= */ const char *GAME_EXPORT SV_GetString( string_t iString ) { if( svgame.physFuncs.pfnGetString != NULL ) return svgame.physFuncs.pfnGetString( iString ); return (svgame.globals->pStringBase + iString); } /* ============= pfnGetVarsOfEnt ============= */ static entvars_t *GAME_EXPORT pfnGetVarsOfEnt( edict_t *pEdict ) { if( pEdict ) return &pEdict->v; return NULL; } /* ============= pfnPEntityOfEntOffset ============= */ static edict_t *GAME_EXPORT pfnPEntityOfEntOffset( int iEntOffset ) { return (edict_t *)((byte *)svgame.edicts + iEntOffset); } /* ============= pfnEntOffsetOfPEntity ============= */ static int GAME_EXPORT pfnEntOffsetOfPEntity( const edict_t *pEdict ) { return (byte *)pEdict - (byte *)svgame.edicts; } /* ============= pfnIndexOfEdict ============= */ int GAME_EXPORT pfnIndexOfEdict( const edict_t *pEdict ) { int number; if( !pEdict ) return 0; // world ? number = NUM_FOR_EDICT( pEdict ); if( number < 0 || number > GI->max_edicts ) Host_Error( "bad entity number %d\n", number ); return number; } /* ============= pfnPEntityOfEntIndex ============= */ static edict_t *pfnPEntityOfEntIndex( int iEntIndex ) { // have to be bug-compatible with GoldSrc in this function if( host.bugcomp == BUGCOMP_GOLDSRC ) return SV_PEntityOfEntIndex( iEntIndex, false ); return SV_PEntityOfEntIndex( iEntIndex, true ); } /* ============= pfnPEntityOfEntIndexAllEntities ============= */ static edict_t *GAME_EXPORT pfnPEntityOfEntIndexAllEntities( int iEntIndex ) { return SV_PEntityOfEntIndex( iEntIndex, true ); } /* ============= pfnFindEntityByVars debug thing ============= */ static edict_t *GAME_EXPORT pfnFindEntityByVars( entvars_t *pvars ) { edict_t *pEdict; int i; // don't pass invalid arguments if( !pvars ) return NULL; for( i = 0; i < GI->max_edicts; i++ ) { pEdict = EDICT_NUM( i ); // g-cont: we should compare pointers if( &pEdict->v == pvars ) return pEdict; // found it } return NULL; } /* ============= pfnGetModelPtr returns pointer to a studiomodel ============= */ static void *pfnGetModelPtr( edict_t *pEdict ) { model_t *mod; if( !SV_IsValidEdict( pEdict )) return NULL; mod = SV_ModelHandle( pEdict->v.modelindex ); return Mod_StudioExtradata( mod ); } /* ============= SV_SendUserReg ============= */ void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ) { MSG_BeginServerCmd( msg, svc_usermessage ); MSG_WriteByte( msg, user->number ); MSG_WriteWord( msg, (word)user->size ); MSG_WriteString( msg, user->name ); } /* ============= pfnRegUserMsg ============= */ static int GAME_EXPORT pfnRegUserMsg( const char *pszName, int iSize ) { int i; if( !COM_CheckString( pszName )) return svc_bad; if( Q_strlen( pszName ) >= sizeof( svgame.msg[0].name )) { Con_Printf( S_ERROR "REG_USER_MSG: too long name %s\n", pszName ); return svc_bad; // force error } if( iSize > MAX_USERMSG_LENGTH ) { Con_Printf( S_ERROR "REG_USER_MSG: %s has too big size %i\n", pszName, iSize ); return svc_bad; // force error } // make sure what size inrange iSize = bound( -1, iSize, MAX_USERMSG_LENGTH ); // message 0 is reserved for svc_bad for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ ) { // see if already registered if( !Q_strcmp( svgame.msg[i].name, pszName )) return svc_lastmsg + i; // offset } if( i == MAX_USER_MESSAGES ) { Con_Printf( S_ERROR "REG_USER_MSG: user messages limit exceeded\n" ); return svc_bad; } // register new message Q_strncpy( svgame.msg[i].name, pszName, sizeof( svgame.msg[i].name )); svgame.msg[i].number = svc_lastmsg + i; svgame.msg[i].size = iSize; if( sv.state == ss_active ) { // tell the client about new user message SV_SendUserReg( &sv.multicast, &svgame.msg[i] ); SV_Multicast( MSG_ALL, NULL, NULL, false, false ); } return svgame.msg[i].number; } /* ============= pfnAnimationAutomove OBSOLETE, UNUSED ============= */ static void GAME_EXPORT pfnAnimationAutomove( const edict_t* pEdict, float flTime ) { } /* ============= pfnGetBonePosition ============= */ static void GAME_EXPORT pfnGetBonePosition( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ) { if( !SV_IsValidEdict( pEdict )) return; Mod_GetBonePosition( pEdict, iBone, rgflOrigin, rgflAngles ); } /* ============= pfnFunctionFromName ============= */ static void *GAME_EXPORT pfnFunctionFromName( const char *pName ) { return COM_FunctionFromName_SR( svgame.hInstance, pName ); } /* ============= pfnNameForFunction ============= */ static const char *GAME_EXPORT pfnNameForFunction( void *function ) { return COM_NameForFunction( svgame.hInstance, function ); } /* ============= pfnClientPrintf ============= */ static void GAME_EXPORT pfnClientPrintf( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ) { sv_client_t *client; if(( client = SV_ClientFromEdict( pEdict, false )) == NULL ) { Con_Printf( "tried to sprint to a non-client\n" ); return; } if( FBitSet( client->flags, FCL_FAKECLIENT )) return; switch( ptype ) { case print_console: case print_chat: SV_ClientPrintf( client, "%s", szMsg ); break; case print_center: MSG_BeginServerCmd( &client->netchan.message, svc_centerprint ); MSG_WriteString( &client->netchan.message, szMsg ); break; } } /* ============= pfnServerPrint print to the server console ============= */ static void GAME_EXPORT pfnServerPrint( const char *szMsg ) { if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) SV_BroadcastPrintf( NULL, "%s", szMsg ); else Con_Printf( "%s", szMsg ); } /* ============= pfnGetAttachment ============= */ static void GAME_EXPORT pfnGetAttachment( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ) { if( !SV_IsValidEdict( pEdict )) return; Mod_StudioGetAttachment( pEdict, iAttachment, rgflOrigin, rgflAngles ); } /* ============= pfnCrosshairAngle ============= */ static void GAME_EXPORT pfnCrosshairAngle( const edict_t *pClient, float pitch, float yaw ) { sv_client_t *client; if(( client = SV_ClientFromEdict( pClient, true )) == NULL ) return; // fakeclients ignores it silently if( FBitSet( client->flags, FCL_FAKECLIENT )) return; if( pitch > 180.0f ) pitch -= 360; if( pitch < -180.0f ) pitch += 360; if( yaw > 180.0f ) yaw -= 360; if( yaw < -180.0f ) yaw += 360; MSG_BeginServerCmd( &client->netchan.message, svc_crosshairangle ); MSG_WriteChar( &client->netchan.message, pitch * 5 ); MSG_WriteChar( &client->netchan.message, yaw * 5 ); } /* ============= pfnSetView ============= */ static void GAME_EXPORT pfnSetView( const edict_t *pClient, const edict_t *pViewent ) { sv_client_t *client; int viewEnt; if( !SV_IsValidEdict( pClient )) return; if(( client = SV_ClientFromEdict( pClient, false )) == NULL ) { Con_Printf( S_ERROR "PF_SetView_I: not a client!\n" ); return; } if( !SV_IsValidEdict( pViewent ) || pClient == pViewent ) client->pViewEntity = NULL; // just reset viewentity else client->pViewEntity = (edict_t *)pViewent; // fakeclients ignore to send client message (but can see into the trigger_camera through the PVS) if( FBitSet( client->flags, FCL_FAKECLIENT )) return; if( client->pViewEntity ) viewEnt = NUM_FOR_EDICT( client->pViewEntity ); else viewEnt = NUM_FOR_EDICT( client->edict ); MSG_BeginServerCmd( &client->netchan.message, svc_setview ); MSG_WriteWord( &client->netchan.message, viewEnt ); } /* ============= pfnStaticDecal ============= */ static void GAME_EXPORT pfnStaticDecal( const float *origin, int decalIndex, int entityIndex, int modelIndex ) { SV_CreateDecal( &sv.signon, origin, decalIndex, entityIndex, modelIndex, FDECAL_PERMANENT, 1.0f ); } /* ============= pfnIsDedicatedServer ============= */ static int GAME_EXPORT pfnIsDedicatedServer( void ) { return Host_IsDedicated(); } /* ============= pfnGetPlayerWONId OBSOLETE, UNUSED ============= */ static uint GAME_EXPORT pfnGetPlayerWONId( edict_t *e ) { return (uint)-1; } /* ============= pfnIsMapValid vaild map must contain one info_player_deatchmatch ============= */ int GAME_EXPORT pfnIsMapValid( char *filename ) { uint flags = SV_MapIsValid( filename, NULL ); if( FBitSet( flags, MAP_IS_EXIST )) return true; return false; } /* ============= pfnCvar_RegisterEngineVariable use with precaution: this cvar will NOT unlinked after game.dll is unloaded ============= */ static void GAME_EXPORT pfnCvar_RegisterEngineVariable( cvar_t *variable ) { Cvar_RegisterVariable( (convar_t *)variable ); } /* ============= pfnFadeClientVolume ============= */ static void GAME_EXPORT pfnFadeClientVolume( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds ) { sv_client_t *cl; if(( cl = SV_ClientFromEdict( pEdict, true )) == NULL ) return; if( FBitSet( cl->flags, FCL_FAKECLIENT )) return; MSG_BeginServerCmd( &cl->netchan.message, svc_soundfade ); MSG_WriteByte( &cl->netchan.message, fadePercent ); MSG_WriteByte( &cl->netchan.message, holdTime ); MSG_WriteByte( &cl->netchan.message, fadeOutSeconds ); MSG_WriteByte( &cl->netchan.message, fadeInSeconds ); } /* ============= pfnSetClientMaxspeed fakeclients can be changed speed to ============= */ static void GAME_EXPORT pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed ) { sv_client_t *cl; // not spawned clients allowed if(( cl = SV_ClientFromEdict( pEdict, false )) == NULL ) return; fNewMaxspeed = bound( -svgame.movevars.maxspeed, fNewMaxspeed, svgame.movevars.maxspeed ); Info_SetValueForKeyf( cl->physinfo, "maxspd", MAX_INFO_STRING, "%.f", fNewMaxspeed ); cl->edict->v.maxspeed = fNewMaxspeed; } /* ============= pfnRunPlayerMove ============= */ static void GAME_EXPORT pfnRunPlayerMove( edict_t *pClient, const float *viewangles, float fmove, float smove, float upmove, word buttons, byte impulse, byte msec ) { sv_client_t *cl, *oldcl; usercmd_t cmd; uint seed; if(( cl = SV_ClientFromEdict( pClient, true )) == NULL ) return; if( !FBitSet( cl->flags, FCL_FAKECLIENT )) return; // only fakeclients allows oldcl = sv.current_client; sv.current_client = SV_ClientFromEdict( pClient, true ); sv.current_client->timebase = (sv.time + sv.frametime) - ((double)msec / 1000.0); memset( &cmd, 0, sizeof( cmd )); VectorCopy( viewangles, cmd.viewangles ); cmd.forwardmove = fmove; cmd.sidemove = smove; cmd.upmove = upmove; cmd.buttons = buttons; cmd.impulse = impulse; cmd.msec = msec; seed = COM_RandomLong( 0, 0x7fffffff ); // full range SV_RunCmd( cl, &cmd, seed ); cl->lastcmd = cmd; sv.current_client = oldcl; } /* ============= pfnNumberOfEntities returns actual entity count ============= */ int GAME_EXPORT pfnNumberOfEntities( void ) { int i, total = 0; for( i = 0; i < svgame.numEntities; i++ ) { if( svgame.edicts[i].free ) continue; total++; } return total; } /* ============= pfnGetInfoKeyBuffer ============= */ static char *GAME_EXPORT pfnGetInfoKeyBuffer( edict_t *e ) { sv_client_t *cl; // NULL passes localinfo if( !SV_IsValidEdict( e )) return svs.localinfo; // world passes serverinfo if( e == svgame.edicts ) return svs.serverinfo; // userinfo for specified edict if(( cl = SV_ClientFromEdict( e, false )) != NULL ) return cl->userinfo; return (char*)""; // assume error } /* ============= pfnSetValueForKey ============= */ static void GAME_EXPORT pfnSetValueForKey( char *infobuffer, char *key, char *value ) { if( infobuffer == svs.localinfo ) Info_SetValueForStarKey( infobuffer, key, value, MAX_LOCALINFO_STRING ); else if( infobuffer == svs.serverinfo ) Info_SetValueForStarKey( infobuffer, key, value, MAX_SERVERINFO_STRING ); else Con_Printf( S_ERROR "can't set client keys with SetValueForKey\n" ); } /* ============= pfnSetClientKeyValue ============= */ static void GAME_EXPORT pfnSetClientKeyValue( int clientIndex, char *infobuffer, char *key, char *value ) { sv_client_t *cl; if( infobuffer == svs.localinfo || infobuffer == svs.serverinfo ) return; clientIndex -= 1; if( !svs.clients || clientIndex < 0 || clientIndex >= svs.maxclients ) return; // value not changed? if( !Q_strcmp( Info_ValueForKey( infobuffer, key ), value )) return; cl = &svs.clients[clientIndex]; Info_SetValueForStarKey( infobuffer, key, value, MAX_INFO_STRING ); SetBits( cl->flags, FCL_RESEND_USERINFO ); cl->next_sendinfotime = 0.0; // send immediately } /* ============= pfnGetPhysicsKeyValue ============= */ static const char *GAME_EXPORT pfnGetPhysicsKeyValue( const edict_t *pClient, const char *key ) { sv_client_t *cl; // pfnUserInfoChanged passed if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) { Con_Printf( S_ERROR "GetPhysicsKeyValue: tried to a non-client!\n" ); return ""; } return Info_ValueForKey( cl->physinfo, key ); } /* ============= pfnSetPhysicsKeyValue ============= */ static void GAME_EXPORT pfnSetPhysicsKeyValue( const edict_t *pClient, const char *key, const char *value ) { sv_client_t *cl; // pfnUserInfoChanged passed if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) { Con_Printf( S_ERROR "SetPhysicsKeyValue: tried to a non-client!\n" ); return; } Info_SetValueForKey( cl->physinfo, key, value, MAX_INFO_STRING ); } /* ============= pfnGetPhysicsInfoString ============= */ static const char *GAME_EXPORT pfnGetPhysicsInfoString( const edict_t *pClient ) { sv_client_t *cl; // pfnUserInfoChanged passed if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) { Con_Printf( S_ERROR "GetPhysicsInfoString: tried to a non-client!\n" ); return ""; } return cl->physinfo; } /* ============= pfnPrecacheEvent register or returns already registered event id a type of event is ignored at this moment ============= */ static word GAME_EXPORT pfnPrecacheEvent( int type, const char *psz ) { return (word)SV_EventIndex( psz ); } /* ============= pfnPlaybackEvent ============= */ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) { sv_client_t *cl; event_state_t *es; event_args_t args; event_info_t *ei = NULL; int j, slot, bestslot; int invokerIndex; byte *mask = NULL; vec3_t pvspoint; if( FBitSet( flags, FEV_CLIENT )) return; // someone stupid joke // first check event for out of bounds if( eventindex < 1 || eventindex >= MAX_EVENTS ) { Con_Printf( S_ERROR "EV_Playback: invalid eventindex %i\n", eventindex ); return; } // check event for precached if( !COM_CheckString( sv.event_precache[eventindex] )) { Con_Printf( S_ERROR "EV_Playback: event %i was not precached\n", eventindex ); return; } memset( &args, 0, sizeof( args )); if( origin && !VectorIsNull( origin )) { VectorCopy( origin, args.origin ); args.flags |= FEVENT_ORIGIN; } if( angles && !VectorIsNull( angles )) { VectorCopy( angles, args.angles ); args.flags |= FEVENT_ANGLES; } // copy other parms args.fparam1 = fparam1; args.fparam2 = fparam2; args.iparam1 = iparam1; args.iparam2 = iparam2; args.bparam1 = bparam1; args.bparam2 = bparam2; VectorClear( pvspoint ); if( SV_IsValidEdict( pInvoker )) { // add the view_ofs to avoid problems with crossed contents line VectorAdd( pInvoker->v.origin, pInvoker->v.view_ofs, pvspoint ); args.entindex = invokerIndex = NUM_FOR_EDICT( pInvoker ); // g-cont. allow 'ducking' param for all entities args.ducking = FBitSet( pInvoker->v.flags, FL_DUCKING ) ? true : false; // this will be send only for reliable event if( !FBitSet( args.flags, FEVENT_ORIGIN )) VectorCopy( pInvoker->v.origin, args.origin ); // this will be send only for reliable event if( !FBitSet( args.flags, FEVENT_ANGLES )) VectorCopy( pInvoker->v.angles, args.angles ); } else { VectorCopy( args.origin, pvspoint ); args.entindex = 0; invokerIndex = -1; } if( !FBitSet( flags, FEV_GLOBAL ) && VectorIsNull( pvspoint )) { Con_DPrintf( S_ERROR "%s: not a FEV_GLOBAL event missing origin. Ignored.\n", sv.event_precache[eventindex] ); return; } // check event for some user errors if( FBitSet( flags, FEV_NOTHOST|FEV_HOSTONLY )) { if( !SV_ClientFromEdict( pInvoker, true )) { const char *ev_name = sv.event_precache[eventindex]; if( FBitSet( flags, FEV_NOTHOST )) { Con_DPrintf( S_WARN "%s: specified FEV_NOTHOST when invoker not a client\n", ev_name ); ClearBits( flags, FEV_NOTHOST ); } if( FBitSet( flags, FEV_HOSTONLY )) { Con_DPrintf( S_WARN "%s: specified FEV_HOSTONLY when invoker not a client\n", ev_name ); ClearBits( flags, FEV_HOSTONLY ); } } } SetBits( flags, FEV_SERVER ); // it's a server event! if( delay < 0.0f ) delay = 0.0f; // fixup negative delays // setup pvs cluster for invoker if( !FBitSet( flags, FEV_GLOBAL )) { Mod_FatPVS( pvspoint, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); mask = fatphs; // using the FatPVS like a PHS } // process all the clients for( slot = 0, cl = svs.clients; slot < svs.maxclients; slot++, cl++ ) { if( cl->state != cs_spawned || !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT )) continue; if( SV_IsValidEdict( pInvoker ) && pInvoker->v.groupinfo && cl->edict->v.groupinfo ) { if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo )) continue; if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo )) continue; } if( SV_IsValidEdict( pInvoker )) { if( !SV_CheckClientVisiblity( cl, mask )) continue; } if( FBitSet( flags, FEV_NOTHOST ) && cl == sv.current_client && FBitSet( cl->flags, FCL_LOCAL_WEAPONS )) continue; // will be played on client side if( FBitSet( flags, FEV_HOSTONLY ) && cl->edict != pInvoker ) continue; // sending only to invoker // all checks passed, send the event // reliable event if( FBitSet( flags, FEV_RELIABLE )) { // skipping queue, write direct into reliable datagram SV_PlaybackReliableEvent( &cl->netchan.message, eventindex, delay, &args ); continue; } // unreliable event (stores in queue) es = &cl->events; bestslot = -1; if( FBitSet( flags, FEV_UPDATE )) { for( j = 0; j < MAX_EVENT_QUEUE; j++ ) { ei = &es->ei[j]; if( ei->index == eventindex && invokerIndex != -1 && invokerIndex == ei->entity_index ) { bestslot = j; break; } } } if( bestslot == -1 ) { for( j = 0; j < MAX_EVENT_QUEUE; j++ ) { ei = &es->ei[j]; if( ei->index == 0 ) { // found an empty slot bestslot = j; break; } } } // no slot found for this player, oh well if( bestslot == -1 ) continue; // add event to queue ei->index = eventindex; ei->fire_time = delay; ei->entity_index = invokerIndex; ei->packet_index = -1; ei->flags = flags; ei->args = args; } } /* ============= pfnGetCurrentPlayer ============= */ static int GAME_EXPORT pfnGetCurrentPlayer( void ) { int idx = sv.current_client - svs.clients; if( idx < 0 || idx >= svs.maxclients ) return -1; return idx; } /* ============= pfnSetFatPVS The client will interpolate the view position, so we can't use a single PVS point ============= */ static byte *GAME_EXPORT pfnSetFatPVS( const float *org ) { qboolean fullvis = false; if( !sv.worldmodel->visdata || sv_novis.value || !org || CL_DisableVisibility( )) fullvis = true; // portals can't change viewpoint! if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) { vec3_t viewPos, offset; qboolean client_active = pfnGetCurrentPlayer() != -1; // see code from client.cpp for understanding: // org = pView->v.origin + pView->v.view_ofs; // if ( pView->v.flags & FL_DUCKING ) // { // org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); // } // so we have unneeded duck calculations who have affect when player // is ducked into water. Remove offset to restore right PVS position if( client_active && FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) { VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); VectorSubtract( org, offset, viewPos ); } else VectorCopy( org, viewPos ); // build a new PVS frame Mod_FatPVS( viewPos, FATPVS_RADIUS, fatpvs, world.fatbytes, false, fullvis ); if( client_active ) VectorCopy( viewPos, viewPoint[pfnGetCurrentPlayer()] ); } else { // merge PVS Mod_FatPVS( org, FATPVS_RADIUS, fatpvs, world.fatbytes, true, fullvis ); } return fatpvs; } /* ============= pfnSetFatPHS The client will interpolate the hear position, so we can't use a single PHS point ============= */ static byte *GAME_EXPORT pfnSetFatPAS( const float *org ) { qboolean fullvis = false; if( !sv.worldmodel->visdata || sv_novis.value || !org || CL_DisableVisibility( )) fullvis = true; // portals can't change viewpoint! if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) { vec3_t viewPos, offset; qboolean client_active = pfnGetCurrentPlayer() != -1; // see code from client.cpp for understanding: // org = pView->v.origin + pView->v.view_ofs; // if ( pView->v.flags & FL_DUCKING ) // { // org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); // } // so we have unneeded duck calculations who have affect when player // is ducked into water. Remove offset to restore right PVS position if( client_active && FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) { VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); VectorSubtract( org, offset, viewPos ); } else VectorCopy( org, viewPos ); // build a new PHS frame Mod_FatPVS( viewPos, FATPHS_RADIUS, fatphs, world.fatbytes, false, fullvis ); } else { // merge PHS Mod_FatPVS( org, FATPHS_RADIUS, fatphs, world.fatbytes, true, fullvis ); } return fatphs; } /* ============= pfnCheckVisibility ============= */ static int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset ) { int i, leafnum; if( !SV_IsValidEdict( ent )) return 0; // vis not set - fullvis enabled if( !pset ) return 1; if( FBitSet( ent->v.flags, FL_CUSTOMENTITY ) && ent->v.owner && FBitSet( ent->v.owner->v.flags, FL_CLIENT )) ent = ent->v.owner; // upcast beams to my owner if( ent->headnode < 0 ) { // check individual leafs for( i = 0; i < ent->num_leafs; i++ ) { if( CHECKVISBIT( pset, ent->leafnums[i] )) return 1; // visible passed by leaf } return 0; } else { for( i = 0; i < MAX_ENT_LEAFS; i++ ) { leafnum = ent->leafnums[i]; if( leafnum == -1 ) break; if( CHECKVISBIT( pset, leafnum )) return 1; // visible passed by leaf } // too many leafs for individual check, go by headnode if( !Mod_HeadnodeVisible( &sv.worldmodel->nodes[ent->headnode], pset, &leafnum )) return 0; ((edict_t *)ent)->leafnums[ent->num_leafs] = leafnum; ((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS; return 2; // visible passed by headnode } } /* ============= pfnCanSkipPlayer ============= */ static int GAME_EXPORT pfnCanSkipPlayer( const edict_t *player ) { sv_client_t *cl; if(( cl = SV_ClientFromEdict( player, false )) == NULL ) return false; return FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) ? true : false; } /* ============= pfnSetGroupMask ============= */ static void GAME_EXPORT pfnSetGroupMask( int mask, int op ) { svs.groupmask = mask; svs.groupop = op; } /* ============= pfnCreateInstancedBaseline ============= */ static int GAME_EXPORT pfnCreateInstancedBaseline( int classname, struct entity_state_s *baseline ) { if( !baseline || sv.num_instanced >= MAX_CUSTOM_BASELINES ) return 0; // g-cont. must sure that classname is really allocated sv.instanced[sv.num_instanced].classname = SV_CopyString( STRING( classname )); sv.instanced[sv.num_instanced].baseline = *baseline; sv.num_instanced++; return sv.num_instanced; } /* ============= pfnEndSection ============= */ static void GAME_EXPORT pfnEndSection( const char *pszSection ) { if( !Q_stricmp( "oem_end_credits", pszSection )) Host_Credits (); else Cbuf_AddText( "\ndisconnect\n" ); } /* ============= pfnGetPlayerUserId ============= */ static int GAME_EXPORT pfnGetPlayerUserId( edict_t *e ) { sv_client_t *cl; if(( cl = SV_ClientFromEdict( e, false )) == NULL ) return -1; return cl->userid; } /* ============= pfnGetPlayerStats ============= */ static void GAME_EXPORT pfnGetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss ) { sv_client_t *cl; if( packet_loss ) *packet_loss = 0; if( ping ) *ping = 0; if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) return; if( packet_loss ) *packet_loss = cl->packet_loss; if( ping ) *ping = cl->latency * 1000; } /* ============= pfnForceUnmodified ============= */ static void GAME_EXPORT pfnForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char *filename ) { consistency_t *pc; int i; if( !COM_CheckString( filename )) return; if( sv.state == ss_loading ) { for( i = 0; i < MAX_MODELS; i++ ) { pc = &sv.consistency_list[i]; if( !pc->filename ) { if( mins ) VectorCopy( mins, pc->mins ); if( maxs ) VectorCopy( maxs, pc->maxs ); pc->filename = SV_CopyString( filename ); pc->check_type = type; return; } else if( !Q_strcmp( filename, pc->filename )) return; } Host_Error( "MAX_MODELS limit exceeded (%d)\n", MAX_MODELS ); } else { for( i = 0; i < MAX_MODELS; i++ ) { pc = &sv.consistency_list[i]; if( !pc->filename ) continue; if( !Q_strcmp( filename, pc->filename )) return; } Con_Printf( S_ERROR "Failed to enforce consistency for %s: was not precached\n", filename ); } } /* ============= pfnVoice_GetClientListening ============= */ static qboolean GAME_EXPORT pfnVoice_GetClientListening( int iReceiver, int iSender ) { iReceiver -= 1; iSender -= 1; if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender >= svs.maxclients ) return false; return (FBitSet( svs.clients[iSender].listeners, BIT( iReceiver )) != 0 ); } /* ============= pfnVoice_SetClientListening ============= */ static qboolean GAME_EXPORT pfnVoice_SetClientListening( int iReceiver, int iSender, qboolean bListen ) { iReceiver -= 1; iSender -= 1; if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender >= svs.maxclients ) return false; if( bListen ) SetBits( svs.clients[iSender].listeners, BIT( iReceiver )); else ClearBits( svs.clients[iSender].listeners, BIT( iReceiver )); return true; } /* ============= pfnGetPlayerAuthId These function must returns cd-key hashed value but Xash3D currently doesn't have any security checks return nullstring for now ============= */ static const char *GAME_EXPORT pfnGetPlayerAuthId( edict_t *e ) { return SV_GetClientIDString( SV_ClientFromEdict( e, false )); } /* ============= pfnQueryClientCvarValue request client cvar value ============= */ static void GAME_EXPORT pfnQueryClientCvarValue( const edict_t *player, const char *cvarName ) { sv_client_t *cl; if( !COM_CheckString( cvarName )) return; if(( cl = SV_ClientFromEdict( player, true )) != NULL ) { MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue ); MSG_WriteString( &cl->netchan.message, cvarName ); } else { if( svgame.dllFuncs2.pfnCvarValue ) svgame.dllFuncs2.pfnCvarValue( player, "Bad Player" ); Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); } } /* ============= pfnQueryClientCvarValue2 request client cvar value (bugfixed) ============= */ static void GAME_EXPORT pfnQueryClientCvarValue2( const edict_t *player, const char *cvarName, int requestID ) { sv_client_t *cl; if( !COM_CheckString( cvarName )) return; if(( cl = SV_ClientFromEdict( player, true )) != NULL ) { MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue2 ); MSG_WriteLong( &cl->netchan.message, requestID ); MSG_WriteString( &cl->netchan.message, cvarName ); } else { if( svgame.dllFuncs2.pfnCvarValue2 ) svgame.dllFuncs2.pfnCvarValue2( player, requestID, cvarName, "Bad Player" ); Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); } } /* ============= pfnEngineStub extended iface stubs ============= */ static int GAME_EXPORT pfnGetLocalizedStringLength( const char *label ) { return 0; } /* ============= pfnRegisterTutorMessageShown only exists in PlayStation version ============= */ static void GAME_EXPORT pfnRegisterTutorMessageShown( int mid ) { } /* ============= pfnGetTimesTutorMessageShown only exists in PlayStation version ============= */ static int GAME_EXPORT pfnGetTimesTutorMessageShown( int mid ) { return 0; } // engine callbacks static enginefuncs_t gEngfuncs = { pfnPrecacheModel, SV_SoundIndex, SV_SetModel, pfnModelIndex, pfnModelFrames, pfnSetSize, pfnChangeLevel, pfnGetSpawnParms, pfnSaveSpawnParms, pfnVecToYaw, VectorAngles, pfnMoveToOrigin, pfnChangeYaw, pfnChangePitch, SV_FindEntityByString, pfnGetEntityIllum, pfnFindEntityInSphere, pfnFindClientInPVS, pfnEntitiesInPVS, pfnMakeVectors, AngleVectors, SV_AllocEdict, pfnRemoveEntity, pfnCreateNamedEntity, pfnMakeStatic, pfnEntIsOnFloor, pfnDropToFloor, pfnWalkMove, pfnSetOrigin, SV_StartSound, pfnEmitAmbientSound, pfnTraceLine, pfnTraceToss, pfnTraceMonsterHull, pfnTraceHull, pfnTraceModel, pfnTraceTexture, pfnTraceSphere, pfnGetAimVector, pfnServerCommand, pfnServerExecute, pfnClientCommand, pfnParticleEffect, pfnLightStyle, pfnDecalIndex, SV_PointContents, pfnMessageBegin, pfnMessageEnd, pfnWriteByte, pfnWriteChar, pfnWriteShort, pfnWriteLong, pfnWriteAngle, pfnWriteCoord, pfnWriteString, pfnWriteEntity, pfnCvar_RegisterServerVariable, Cvar_VariableValue, Cvar_VariableString, Cvar_SetValue, Cvar_Set, pfnAlertMessage, pfnEngineFprintf, pfnPvAllocEntPrivateData, pfnPvEntPrivateData, SV_FreePrivateData, SV_GetString, SV_AllocString, pfnGetVarsOfEnt, pfnPEntityOfEntOffset, pfnEntOffsetOfPEntity, pfnIndexOfEdict, pfnPEntityOfEntIndex, pfnFindEntityByVars, pfnGetModelPtr, pfnRegUserMsg, pfnAnimationAutomove, pfnGetBonePosition, (void*)pfnFunctionFromName, (void*)pfnNameForFunction, pfnClientPrintf, pfnServerPrint, Cmd_Args, Cmd_Argv, (void*)Cmd_Argc, pfnGetAttachment, CRC32_Init, CRC32_ProcessBuffer, CRC32_ProcessByte, CRC32_Final, COM_RandomLong, COM_RandomFloat, pfnSetView, pfnTime, pfnCrosshairAngle, COM_LoadFileForMe, COM_FreeFile, pfnEndSection, COM_CompareFileTime, pfnGetGameDir, pfnCvar_RegisterEngineVariable, pfnFadeClientVolume, pfnSetClientMaxspeed, SV_FakeConnect, pfnRunPlayerMove, pfnNumberOfEntities, pfnGetInfoKeyBuffer, Info_ValueForKey, pfnSetValueForKey, pfnSetClientKeyValue, pfnIsMapValid, pfnStaticDecal, SV_GenericIndex, pfnGetPlayerUserId, pfnBuildSoundMsg, pfnIsDedicatedServer, pfnCVarGetPointer, pfnGetPlayerWONId, (void*)Info_RemoveKey, pfnGetPhysicsKeyValue, pfnSetPhysicsKeyValue, pfnGetPhysicsInfoString, pfnPrecacheEvent, SV_PlaybackEventFull, pfnSetFatPVS, pfnSetFatPAS, pfnCheckVisibility, Delta_SetField, Delta_UnsetField, Delta_AddEncoder, pfnGetCurrentPlayer, pfnCanSkipPlayer, Delta_FindField, Delta_SetFieldByIndex, Delta_UnsetFieldByIndex, pfnSetGroupMask, pfnCreateInstancedBaseline, (void*)Cvar_DirectSet, pfnForceUnmodified, pfnGetPlayerStats, Cmd_AddServerCommand, pfnVoice_GetClientListening, pfnVoice_SetClientListening, pfnGetPlayerAuthId, pfnSequenceGet, pfnSequencePickSentence, COM_FileSize, Sound_GetApproxWavePlayLen, pfnIsCareerMatch, pfnGetLocalizedStringLength, pfnRegisterTutorMessageShown, pfnGetTimesTutorMessageShown, pfnProcessTutorMessageDecayBuffer, pfnConstructTutorMessageDecayBuffer, pfnResetTutorMessageDecayData, pfnQueryClientCvarValue, pfnQueryClientCvarValue2, COM_CheckParm, pfnPEntityOfEntIndexAllEntities, }; static void SV_FreeKeyValueStrings( KeyValueData *kvd, int numpairs ) { for( int i = 0; i < numpairs; i++ ) { Mem_Free( kvd[i].szKeyName ); Mem_Free( kvd[i].szValue ); } } /* ==================== SV_ParseEdict Parses an edict out of the given string, returning the new position ed should be a properly initialized empty edict. ==================== */ static qboolean SV_ParseEdict( char **pfile, edict_t *ent ) { KeyValueData pkvd[256]; // per one entity qboolean adjust_origin = false, customentity; int i, numpairs = 0; const char *classname = NULL; // go through all the dictionary pairs while( 1 ) { string keyname; char value[2048]; int len; // parse key if(( *pfile = COM_ParseFile( *pfile, keyname, sizeof( keyname ))) == NULL ) Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); if( keyname[0] == '}' ) break; // end of desc // parse value if(( *pfile = COM_ParseFile( *pfile, value, sizeof( value ))) == NULL ) Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); if( value[0] == '}' ) Host_Error( "ED_ParseEdict: closing brace without data\n" ); // ignore attempts to set empty key or value // "wad" field is already handled if( !keyname[0] || !value[0] || !Q_strcmp( keyname, "wad" )) continue; // keynames with a leading underscore are used for // utility comments and are immediately discarded by engine if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && keyname[0] == '_' ) continue; // classname must be first if( !Q_strcmp( keyname, "classname" )) { KeyValueData kvd = { .szClassName = NULL, .szKeyName = keyname, .szValue = value, .fHandled = false }; // don't allow double classnames if( classname != NULL ) continue; svgame.dllFuncs.pfnKeyValue( ent, &kvd ); // ideally, all game dlls should handle classname. // throw an error for now, improve the logic if it causes // compatibility issues with Xash-based games if( !kvd.fHandled ) Host_Error( "%s: game didn't handled \"%s\" classname\n", __func__, value ); // this lets game dll override custom entity classname // to something bogus that's exported in game dll classname = STRING( ent->v.classname ); continue; } // GoldSrc removes trailing spaces // but does this after sucking out classname // which doesn't have similar check for( len = Q_strlen( keyname ); len > 0 && keyname[len - 1] == ' '; len-- ) keyname[len - 1] = '\0'; // create keyvalue strings pkvd[numpairs].szClassName = (char*)""; // unknown at this moment pkvd[numpairs].szKeyName = copystring( keyname ); pkvd[numpairs].szValue = copystring( value ); pkvd[numpairs].fHandled = false; numpairs++; if( numpairs > ARRAYSIZE( pkvd )) { if( classname ) Con_Printf( S_ERROR "%s: too many keyvalue pairs for %s!\n", __func__, classname ); else Con_Printf( S_ERROR "%s: too many keyvalue pairs!\n", __func__ ); break; } } if( classname == NULL ) { // release allocated strings SV_FreeKeyValueStrings( pkvd, numpairs ); return false; } ent = SV_AllocPrivateData( ent, ent->v.classname, &customentity ); if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME )) { // release allocated strings SV_FreeKeyValueStrings( pkvd, numpairs ); return false; } if( customentity ) { KeyValueData kvd = { .szClassName = (char *)"custom", .szKeyName = (char *)"customclass", .szValue = (char *)classname, .fHandled = false }; svgame.dllFuncs.pfnKeyValue( ent, &kvd ); // no fHandled check, GoldSrc behavior } #ifdef HACKS_RELATED_HLMODS // chemical existence have broked changelevels if( !Q_stricmp( GI->gamefolder, "ce" )) { if( !Q_stricmp( sv.name, "ce08_02" ) && !Q_stricmp( classname, "info_player_start_force" )) adjust_origin = true; } #endif for( i = 0; i < numpairs; i++ ) { char *keyname, *value; char temp[MAX_VA_STRING]; #if 0 // this is stupid bug in GoldSrc, disable if( !Q_strcmp( pkvd[i].szValue, classname )) continue; #endif if( !Q_strcmp( pkvd[i].szKeyName, "angle" )) { float flYawAngle = Q_atof( pkvd[i].szValue ); Mem_Free( pkvd[i].szKeyName ); // will be replace with 'angles' Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these pkvd[i].szKeyName = copystring( "angles" ); if( flYawAngle >= 0.0f ) { Q_snprintf( temp, sizeof( temp ), "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] ); pkvd[i].szValue = copystring( temp ); } else if( flYawAngle == -1.0f ) pkvd[i].szValue = copystring( "-90 0 0" ); else if( flYawAngle == -2.0f ) pkvd[i].szValue = copystring( "90 0 0" ); else pkvd[i].szValue = copystring( "0 0 0" ); // technically an error } if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" )) { char *pstart = pkvd[i].szValue; vec3_t origin; COM_ParseVector( &pstart, origin, 3 ); Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these Q_snprintf( temp, sizeof( temp ), "%g %g %g", origin[0], origin[1], origin[2] - 16.0f ); pkvd[i].szValue = copystring( temp ); } // do not leak memory if game overwritten these pointers keyname = pkvd[i].szKeyName; value = pkvd[i].szValue; pkvd[i].szClassName = (char *)classname; svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] ); Mem_Free( keyname ); Mem_Free( value ); } return true; } /* ================ SV_LoadFromFile The entities are directly placed in the array, rather than allocated with ED_Alloc, because otherwise an error loading the map would have entity number references out of order. Creates a server's entity / program execution context by parsing textual entity definitions out of an ent file. ================ */ static void SV_LoadFromFile( const char *mapname, char *entities ) { char token[2048]; qboolean create_world = true; int inhibited; edict_t *ent; Assert( entities != NULL ); // user dll can override spawn entities function (Xash3D extension) if( !svgame.physFuncs.SV_LoadEntities || !svgame.physFuncs.SV_LoadEntities( mapname, entities )) { inhibited = 0; // parse ents while(( entities = COM_ParseFile( entities, token, sizeof( token ))) != NULL ) { if( token[0] != '{' ) Host_Error( "ED_LoadFromFile: found %s when expecting {\n", token ); if( create_world ) { create_world = false; ent = EDICT_NUM( 0 ); // already initialized } else ent = SV_AllocEdict(); if( !SV_ParseEdict( &entities, ent )) continue; if( svgame.dllFuncs.pfnSpawn( ent ) == -1 ) { // game rejected the spawn if( !FBitSet( ent->v.flags, FL_KILLME )) { SV_FreeEdict( ent ); inhibited++; } } } Con_DPrintf( "\n%i entities inhibited\n", inhibited ); } // reset world origin and angles for some reason VectorClear( svgame.edicts->v.origin ); VectorClear( svgame.edicts->v.angles ); } /* ============== SpawnEntities Creates a server's entity / program execution context by parsing textual entity definitions out of an ent file. ============== */ void SV_SpawnEntities( const char *mapname ) { edict_t *ent; // reset misc parms Cvar_Reset( "sv_zmax" ); Cvar_Reset( "sv_wateramp" ); Cvar_Reset( "sv_wateralpha" ); // reset sky parms Cvar_Reset( "sv_skycolor_r" ); Cvar_Reset( "sv_skycolor_g" ); Cvar_Reset( "sv_skycolor_b" ); Cvar_Reset( "sv_skyvec_x" ); Cvar_Reset( "sv_skyvec_y" ); Cvar_Reset( "sv_skyvec_z" ); Cvar_Reset( "sv_skyname" ); ent = EDICT_NUM( 0 ); if( ent->free ) SV_InitEdict( ent ); ent->v.model = MAKE_STRING( sv.model_precache[1] ); ent->v.modelindex = WORLD_INDEX; // world model ent->v.solid = SOLID_BSP; ent->v.movetype = MOVETYPE_PUSH; svgame.movevars.fog_settings = 0; svgame.globals->maxEntities = GI->max_edicts; svgame.globals->mapname = MAKE_STRING( sv.name ); svgame.globals->startspot = MAKE_STRING( sv.startspot ); svgame.globals->time = sv.time; // spawn the rest of the entities on the map SV_LoadFromFile( mapname, sv.worldmodel->entities ); } void SV_UnloadProgs( void ) { if( !svgame.hInstance ) return; SV_DeactivateServer (); Delta_Shutdown (); /// TODO: reenable this when /// SV_UnloadProgs will be disabled //Mod_ClearUserData (); if( svgame.dllFuncs2.pfnGameShutdown != NULL ) svgame.dllFuncs2.pfnGameShutdown (); // now we can unload cvars Cvar_FullSet( "host_gameloaded", "0", FCVAR_READ_ONLY ); Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY ); // free entity baselines Z_Free( svs.static_entities ); Z_Free( svs.baselines ); svs.baselines = NULL; // remove server cmds SV_KillOperatorCommands(); // must unlink all game cvars, // before pointers on them will be lost... Cvar_Unlink( FCVAR_EXTDLL ); Cmd_Unlink( CMD_SERVERDLL ); SV_FreeStringPool(); Mod_ResetStudioAPI (); COM_FreeLibrary( svgame.hInstance ); Mem_FreePool( &svgame.mempool ); memset( &svgame, 0, sizeof( svgame )); } qboolean SV_LoadProgs( const char *name ) { int i, version; static APIFUNCTION GetEntityAPI; static APIFUNCTION2 GetEntityAPI2; static GIVEFNPTRSTODLL GiveFnptrsToDll; static NEW_DLL_FUNCTIONS_FN GiveNewDllFuncs; static enginefuncs_t gpEngfuncs; static globalvars_t gpGlobals; static playermove_t gpMove; edict_t *e; if( svgame.hInstance ) { #if XASH_WIN32 SV_UnloadProgs(); #else // XASH_WIN32 return true; #endif // XASH_WIN32 } // fill it in svgame.pmove = &gpMove; svgame.globals = &gpGlobals; svgame.mempool = Mem_AllocPool( "Server Edicts Zone" ); svgame.hInstance = COM_LoadLibrary( name, true, false ); if( !svgame.hInstance ) { Mem_FreePool(&svgame.mempool); return false; } // make sure what new dll functions is cleared memset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 )); // make sure what physic functions is cleared memset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs )); // make local copy of engfuncs to prevent overwrite it with bots.dll memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs )); GetEntityAPI = (APIFUNCTION)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI" ); GetEntityAPI2 = (APIFUNCTION2)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI2" ); GiveNewDllFuncs = (NEW_DLL_FUNCTIONS_FN)COM_GetProcAddress( svgame.hInstance, "GetNewDLLFunctions" ); if( !GetEntityAPI && !GetEntityAPI2 ) { COM_FreeLibrary( svgame.hInstance ); Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GetEntityAPI proc\n" ); svgame.hInstance = NULL; Mem_FreePool(&svgame.mempool); return false; } GiveFnptrsToDll = (GIVEFNPTRSTODLL)COM_GetProcAddress( svgame.hInstance, "GiveFnptrsToDll" ); if( !GiveFnptrsToDll ) { COM_FreeLibrary( svgame.hInstance ); Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GiveFnptrsToDll proc\n" ); svgame.hInstance = NULL; Mem_FreePool(&svgame.mempool); return false; } GiveFnptrsToDll( &gpEngfuncs, svgame.globals ); // get extended callbacks if( GiveNewDllFuncs ) { version = NEW_DLL_FUNCTIONS_VERSION; if( !GiveNewDllFuncs( &svgame.dllFuncs2, &version )) { if( version != NEW_DLL_FUNCTIONS_VERSION ) Con_Printf( S_WARN "SV_LoadProgs: new interface version %i should be %i\n", NEW_DLL_FUNCTIONS_VERSION, version ); memset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 )); } } version = INTERFACE_VERSION; if( GetEntityAPI2 ) { if( !GetEntityAPI2( &svgame.dllFuncs, &version )) { Con_Printf( S_WARN "SV_LoadProgs: interface version %i should be %i\n", INTERFACE_VERSION, version ); // fallback to old API if( !GetEntityAPI( &svgame.dllFuncs, version )) { COM_FreeLibrary( svgame.hInstance ); Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); svgame.hInstance = NULL; Mem_FreePool(&svgame.mempool); return false; } } else Con_Reportf( "SV_LoadProgs: ^2initailized extended EntityAPI ^7ver. %i\n", version ); } else if( !GetEntityAPI( &svgame.dllFuncs, version )) { COM_FreeLibrary( svgame.hInstance ); Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); svgame.hInstance = NULL; Mem_FreePool(&svgame.mempool); return false; } SV_InitOperatorCommands(); Mod_InitStudioAPI(); if( !SV_InitPhysicsAPI( )) { Con_Printf( S_WARN "SV_LoadProgs: couldn't get physics API\n" ); } // grab function SV_SaveGameComment SV_InitSaveRestore (); svgame.globals->pStringBase = ""; // setup string base svgame.globals->maxEntities = GI->max_edicts; svgame.globals->maxClients = svs.maxclients; svgame.edicts = Mem_Calloc( svgame.mempool, sizeof( edict_t ) * GI->max_edicts ); svs.static_entities = Z_Calloc( sizeof( entity_state_t ) * MAX_STATIC_ENTITIES ); svs.baselines = Z_Calloc( sizeof( entity_state_t ) * GI->max_edicts ); svgame.numEntities = svs.maxclients + 1; // clients + world for( i = 0, e = svgame.edicts; i < GI->max_edicts; i++, e++ ) e->free = true; // mark all edicts as freed Cvar_FullSet( "host_gameloaded", "1", FCVAR_READ_ONLY ); SV_AllocStringPool(); // fire once Con_Printf( "Dll loaded for game ^2\"%s\"\n", svgame.dllFuncs.pfnGetGameDescription( )); // all done, initialize game svgame.dllFuncs.pfnGameInit(); // initialize pm_shared SV_InitClientMove(); Delta_Init (); // register custom encoders svgame.dllFuncs.pfnRegisterEncoders(); return true; }