/* 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 ); edict_t *SV_EdictNum( int n ) { if(( n >= 0 ) && ( n < GI->max_edicts )) return svgame.edicts + n; return NULL; } #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_LocalInfo get local infostring ============= */ static char *SV_Localinfo( void ) { return svs.localinfo; } /* ============= 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]; qboolean found = false; 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]; char *spawn_entity; // hold mapname to other place Q_strncpy( mapname, level, sizeof( mapname )); COM_StripExtension( mapname ); if( COM_CheckString( landname )) smooth = true; // determine spawn entity classname if( svs.maxclients == 1 ) spawn_entity = GI->sp_entity; else spawn_entity = GI->mp_entity; flags = SV_MapIsValid( mapname, spawn_entity, 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; } if( !smooth && !FBitSet( flags, MAP_HAS_SPAWNPOINT )) { if( sv_validate_changelevel.value ) { Con_Printf( S_ERROR "changelevel: %s doesn't have a valid spawnpoint. Ignored.\n", mapname ); 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; dheader_t *header; 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 ); header = (dheader_t *)buf; // 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; dheader_t *header; 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 ); header = (dheader_t *)buf; // 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 *spawn_entity, 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 ); // g-cont. in-dev mode we can entering on map even without "info_player_start" if( !need_landmark && host_developer.value ) { // not transition Mem_Free( ents ); // skip spawnpoint checks in devmode return (flags|MAP_HAS_SPAWNPOINT); } pfile = ents; while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { if( !Q_strcmp( token, "classname" )) { // check classname for spawn entity pfile = COM_ParseFile( pfile, check_name, sizeof( check_name )); if( !Q_strcmp( spawn_entity, check_name )) { SetBits( flags, MAP_HAS_SPAWNPOINT ); // we already find landmark, stop the parsing if( need_landmark && FBitSet( flags, MAP_HAS_LANDMARK )) break; } } else if( need_landmark && !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 )) { SetBits( flags, MAP_HAS_LANDMARK ); // we already find spawnpoint, stop the parsing if( FBitSet( flags, MAP_HAS_SPAWNPOINT )) 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 ============== */ static edict_t* SV_AllocPrivateData( edict_t *ent, string_t className ) { const char *pszClassName; LINK_ENTITY_FUNC SpawnEdict; pszClassName = STRING( className ); 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; SpawnEdict = SV_GetEntityClass( "custom" ); if( !SpawnEdict ) { Con_Printf( S_ERROR "No spawn function for %s\n", STRING( className )); // free entity immediately SV_FreeEdict( ent ); return NULL; } SetBits( ent->v.flags, FL_CUSTOMENTITY ); // it's a custom entity but not a beam! } SpawnEdict( &ent->v ); return ent; } /* ============== SV_CreateNamedEntity create specified entity, alloc private data ============== */ edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className ) { edict_t *ed = SV_AllocPrivateData( ent, className ); // for some reasons this flag should be immediately cleared if( ed ) ClearBits( ed->v.flags, FL_CUSTOMENTITY ); return ed; } /* ============== 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; int cmp; 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 } #ifdef XASH_64BIT void SV_PrintStr64Stats_f( void ) { Msg( "====================\n" ); Msg( "64 bit string pool statistics\n" ); Msg( "====================\n" ); Msg( "string array size: %lu\n", str64.maxstringarray ); Msg( "total alloc %lu\n", str64.totalalloc ); Msg( "maximum array usage: %lu\n", str64.maxalloc ); Msg( "overflow counter: %lu\n", str64.numoverflows ); Msg( "dup string counter: %lu\n", str64.numdups ); } #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, GI->mp_entity, NULL ); if( FBitSet( flags, MAP_IS_EXIST ) && FBitSet( flags, MAP_HAS_SPAWNPOINT )) 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 SV_Localinfo(); // world passes serverinfo if( e == svgame.edicts ) return SV_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, pfnCVarDirectSet, 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, }; /* ==================== 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; int i, numpairs = 0; char *classname = NULL; char token[2048]; vec3_t origin; // go through all the dictionary pairs while( 1 ) { string keyname; // parse key if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL ) Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); if( token[0] == '}' ) break; // end of desc Q_strncpy( keyname, token, sizeof( keyname )); // parse value if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL ) Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); if( token[0] == '}' ) Host_Error( "ED_ParseEdict: closing brace without data\n" ); // ignore attempts to set key "" if( !keyname[0] ) continue; // "wad" field is already handled if( !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; // ignore attempts to set value "" if( !token[0] ) continue; // create keyvalue strings pkvd[numpairs].szClassName = (char*)""; // unknown at this moment pkvd[numpairs].szKeyName = copystring( keyname ); pkvd[numpairs].szValue = copystring( token ); pkvd[numpairs].fHandled = false; if( !Q_strcmp( keyname, "classname" ) && classname == NULL ) classname = copystring( pkvd[numpairs].szValue ); if( ++numpairs >= 256 ) break; } if( classname == NULL ) { // release allocated strings for( i = 0; i < numpairs; i++ ) { Mem_Free( pkvd[i].szKeyName ); Mem_Free( pkvd[i].szValue ); } return false; } ent = SV_AllocPrivateData( ent, ALLOC_STRING( classname )); if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME )) { // release allocated strings for( i = 0; i < numpairs; i++ ) { Mem_Free( pkvd[i].szKeyName ); Mem_Free( pkvd[i].szValue ); } return false; } if( FBitSet( ent->v.flags, FL_CUSTOMENTITY )) { if( numpairs < 256 ) { pkvd[numpairs].szClassName = (char*)"custom"; pkvd[numpairs].szKeyName = (char*)"customclass"; pkvd[numpairs].szValue = classname; pkvd[numpairs].fHandled = false; numpairs++; } // clear it now - no longer used ClearBits( ent->v.flags, FL_CUSTOMENTITY ); } #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++ ) { 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 ) { char temp[MAX_VA_STRING]; 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 } #ifdef HACKS_RELATED_HLMODS if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" )) { char temp[MAX_VA_STRING]; char *pstart = pkvd[i].szValue; 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 ); } #endif if( !Q_strcmp( pkvd[i].szKeyName, "light" )) { Mem_Free( pkvd[i].szKeyName ); pkvd[i].szKeyName = copystring( "light_level" ); } if( !pkvd[i].fHandled ) { pkvd[i].szClassName = classname; svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] ); } // no reason to keep this data if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szKeyName )) Mem_Free( pkvd[i].szKeyName ); if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szValue )) Mem_Free( pkvd[i].szValue ); } if( Mem_IsAllocatedExt( host.mempool, classname )) Mem_Free( classname ); 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 (); SV_FreeStringPool(); 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 ); 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; }