/* Copyright (C) 1997-2001 Id Software, Inc. 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sv_game.c -- interface to the game dll #include "server.h" game_export_t *ge; mempool_t *sv_gameprogspool; /* =============== PF_Layout Sends the layout to a single client =============== */ static void PF_Layout( edict_t *ent, char *string ) { int p; client_t *client; if( !ent || !string || !string[0] ) return; if( ent->r.svflags & SVF_FAKECLIENT ) return; p = NUM_FOR_EDICT(ent); if( p < 1 || p > sv_maxclients->integer ) return; client = svs.clients + (p-1); SV_SendServerCommand( client, "layout \"%s\"", string ); } /* =============== PF_DropClient =============== */ void PF_DropClient( edict_t *ent, int type, char *message ) { int p; client_t *drop; if (!ent) return; p = NUM_FOR_EDICT(ent); if( p < 1 || p > sv_maxclients->integer ) return; drop = svs.clients + (p-1); SV_DropClient( drop, type, "%s", message ); } //=============== //PF_ServerCmd // Game code asks for the state of this client //=============== static int PF_GetClientState( int numClient ) { if( numClient < 0 || numClient >= sv_maxclients->integer ) return -1; return svs.clients[numClient].state; } /* =============== PF_ServerCmd Sends the server command to clients. if ent is NULL the command will be sent to all connected clients =============== */ static void PF_ServerCmd( edict_t *ent, char *cmd ) { if( !cmd || !cmd[0] ) return; if( !ent ) { SV_SendServerCommand( NULL, "%s", cmd ); } else { int p; client_t *client; if( ent->r.svflags & SVF_FAKECLIENT ) return; p = NUM_FOR_EDICT(ent); if( p < 1 || p > sv_maxclients->integer ) return; client = svs.clients + (p-1); SV_SendServerCommand( client, "%s", cmd ); } } /* =============== PF_dprint Debug print to server console =============== */ static void PF_dprint( char *msg ) { int i; char copy[MAX_PRINTMSG]; if( !msg ) return; // mask off high bits and colored strings for( i = 0; i < sizeof(copy)-1 && msg[i]; i++ ) copy[i] = msg[i]&127; copy[i] = 0; Com_Printf( "%s", copy ); } /* =============== PF_error Abort the server with a game error =============== */ static void PF_error( char *msg ) { int i; char copy[MAX_PRINTMSG]; if( !msg ) { Com_Error (ERR_DROP, "Game Error: unknown error"); return; } // mask off high bits and colored strings for( i = 0; ir.svflags & SVF_FAKECLIENT ) return; p = NUM_FOR_EDICT(ent); if( p < 1 || p > sv_maxclients->integer ) return; client = svs.clients + (p-1); SV_SendServerCommand( client, "stufftext \"%s\"", string ); } /* ================= PF_SetBrushModel Also sets mins and maxs for inline bmodels ================= */ static void PF_SetBrushModel( edict_t *ent, char *name ) { struct cmodel_s *cmodel; if( !name ) Com_Error( ERR_DROP, "PF_setmodel: NULL" ); if( !name[0] ) { ent->s.modelindex = 0; return; } // if it is an inline model, get the size information for it if (name[0] != '*') { ent->s.modelindex = SV_ModelIndex( name ); return; } if( !strcmp(name, "*0") ) { ent->s.modelindex = 1; cmodel = CM_InlineModel(0); CM_InlineModelBounds( cmodel, ent->r.mins, ent->r.maxs ); return; } ent->s.modelindex = atoi( name+1 ); cmodel = CM_InlineModel( ent->s.modelindex ); CM_InlineModelBounds( cmodel, ent->r.mins, ent->r.maxs ); SV_LinkEdict(ent); } /* =============== PF_EntityContact =============== */ static qboolean PF_EntityContact( vec3_t mins, vec3_t maxs, edict_t *ent ) { trace_t tr; struct cmodel_s *model; if( !mins ) mins = vec3_origin; if( !maxs ) maxs = vec3_origin; if( !ent->s.modelindex ) { if( ent->r.solid == SOLID_TRIGGER ) return( BoundsIntersect( mins, maxs, ent->r.absmin, ent->r.absmax ) ); return qfalse; } if( ent->r.solid == SOLID_TRIGGER || ent->r.solid == SOLID_BSP ) { model = CM_InlineModel( ent->s.modelindex ); if( !model ) Com_Error( ERR_FATAL, "MOVETYPE_PUSH with a non bsp model" ); CM_TransformedBoxTrace( &tr, vec3_origin, vec3_origin, mins, maxs, model, MASK_ALL, ent->s.origin, ent->s.angles ); return tr.startsolid || tr.allsolid; } return( BoundsIntersect( mins, maxs, ent->r.absmin, ent->r.absmax ) ); } /* =============== PF_Configstring =============== */ static void PF_Configstring( int index, char *val ) { if( index < 0 || index >= MAX_CONFIGSTRINGS ) Com_Error( ERR_DROP, "configstring: bad index %i", index ); if( !val ) { val = ""; } // verify parity if(1) { char *p; int parity = 0; qboolean opened = qfalse; p = val; while( *p ) { if( *p == '\"' ) { if( opened ) { parity--; opened = qfalse; } else { parity++; opened = qtrue; } } p++; } if( parity < 0 ) { Com_Printf( "WARNING!: 'PF_Configstring', unfixable configstring quotes mismatch: %s\n", val ); return; } else if( parity > 0 ) { Com_Printf( "WARNING!: 'PF_Configstring', configstring quotes mismatch: fixing\n" ); while( parity ) { strcat( val, "\n" ); parity--; } } } if( strlen(val) >= sizeof(sv.configstrings[index]) ) { Com_Printf( "WARNING!: 'PF_Configstring', configstring overflow\n" ); return; } if( !strcmp( sv.configstrings[index], val ) ) // don't send if the same return; // change the string in sv Q_strncpyz( sv.configstrings[index], val, sizeof(sv.configstrings[index]) ); if( sv.state != ss_loading ) { // We have to manually broadcast this one. client_t *client; int i; for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ ) { if( client->state < CS_CONNECTED ) continue; SV_SendServerCommand( client, "cs %i \"%s\"", index, val ); } } } /* ================= PF_inPVS Also checks portalareas so that doors block sight ================= */ static qboolean PF_inPVS( vec3_t p1, vec3_t p2 ) { int leafnum; int cluster; int area1, area2; qbyte *mask; leafnum = CM_PointLeafnum( p1 ); cluster = CM_LeafCluster( leafnum ); area1 = CM_LeafArea( leafnum ); mask = CM_ClusterPVS( cluster ); leafnum = CM_PointLeafnum( p2 ); cluster = CM_LeafCluster( leafnum ); area2 = CM_LeafArea( leafnum ); if( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) return qfalse; if( !CM_AreasConnected(area1, area2) ) return qfalse; // a door blocks sight return qtrue; } /* ================= PF_inPHS Also checks portalareas so that doors block sound ================= */ static qboolean PF_inPHS( vec3_t p1, vec3_t p2 ) { int leafnum; int cluster; int area1, area2; qbyte *mask; leafnum = CM_PointLeafnum( p1 ); cluster = CM_LeafCluster( leafnum ); area1 = CM_LeafArea( leafnum ); mask = CM_ClusterPHS( cluster ); leafnum = CM_PointLeafnum( p2 ); cluster = CM_LeafCluster( leafnum ); area2 = CM_LeafArea( leafnum ); if( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) return qfalse; // more than one bounce away if( !CM_AreasConnected(area1, area2) ) return qfalse; // a door blocks hearing return qtrue; } /* =============== PF_MemAlloc =============== */ static void *PF_MemAlloc( mempool_t *pool, int size, const char *filename, int fileline ) { return _Mem_Alloc( pool, size, MEMPOOL_GAMEPROGS, 0, filename, fileline ); } /* =============== PF_MemFree =============== */ static void PF_MemFree( void *data, const char *filename, int fileline ) { _Mem_Free( data, MEMPOOL_GAMEPROGS, 0, filename, fileline ); } /* =============== PF_MemAllocPool =============== */ static mempool_t *PF_MemAllocPool( const char *name, const char *filename, int fileline ) { return _Mem_AllocPool( sv_gameprogspool, name, MEMPOOL_GAMEPROGS, filename, fileline ); } /* =============== PF_MemFreePool =============== */ static void PF_MemFreePool( mempool_t **pool, const char *filename, int fileline ) { _Mem_FreePool( pool, MEMPOOL_GAMEPROGS, 0, filename, fileline ); } /* =============== PF_MemEmptyPool =============== */ static void PF_MemEmptyPool( mempool_t *pool, const char *filename, int fileline ) { _Mem_EmptyPool( pool, MEMPOOL_GAMEPROGS, 0, filename, fileline ); } //============================================== /* =============== SV_ShutdownGameProgs Called when either the entire server is being killed, or it is changing to a different game directory. =============== */ void SV_ShutdownGameProgs( void ) { if( !ge ) return; ge->Shutdown(); Mem_FreePool( &sv_gameprogspool ); Sys_UnloadGameLibrary( LIB_GAME ); ge = NULL; } /* ================= SV_LocateEntities ================= */ void SV_LocateEntities( struct edict_s *edicts, int edict_size, int num_edicts, int max_edicts ) { if( !edicts || edict_size < sizeof(entity_shared_t) ) { Com_Error( ERR_DROP, "SV_LocateEntities: bad edicts" ); } sv.edicts = edicts; sv.edict_size = edict_size; sv.num_edicts = num_edicts; sv.max_edicts = max_edicts; } /* =============== SV_InitGameProgs Init the game subsystem for a new map =============== */ void SV_InitGameProgs( void ) { int apiversion; game_import_t import; // unload anything we have now if( ge ) SV_ShutdownGameProgs(); sv_gameprogspool = Mem_AllocPool( NULL, "Game Progs" ); // load a new game dll import.Print = PF_dprint; import.Error = PF_error; import.ServerCmd = PF_ServerCmd; import.LinkEntity = SV_LinkEdict; import.UnlinkEntity = SV_UnlinkEdict; import.BoxEdicts = SV_AreaEdicts; import.Trace = SV_Trace; import.PointContents = SV_PointContents; import.SetBrushModel = PF_SetBrushModel; import.inPVS = PF_inPVS; import.inPHS = PF_inPHS; import.EntityContact = PF_EntityContact; import.Milliseconds = Sys_Milliseconds; import.ModelIndex = SV_ModelIndex; import.SoundIndex = SV_SoundIndex; import.ImageIndex = SV_ImageIndex; import.SkinIndex = SV_SkinIndex; import.ConfigString = PF_Configstring; import.Layout = PF_Layout; import.StuffCmd = PF_StuffCmd; import.Sound = SV_StartSound; import.FS_FOpenFile = FS_FOpenFile; import.FS_Read = FS_Read; import.FS_Write = FS_Write; import.FS_Tell = FS_Tell; import.FS_Seek = FS_Seek; import.FS_Eof = FS_Eof; import.FS_Flush = FS_Flush; import.FS_FCloseFile = FS_FCloseFile; import.FS_GetFileList = FS_GetFileList; import.FS_Gamedir = FS_Gamedir; import.Mem_Alloc = PF_MemAlloc; import.Mem_Free = PF_MemFree; import.Mem_AllocPool = PF_MemAllocPool; import.Mem_FreePool = PF_MemFreePool; import.Mem_EmptyPool = PF_MemEmptyPool; import.Cvar_Get = Cvar_Get; import.Cvar_Set = Cvar_Set; import.Cvar_SetValue = Cvar_SetValue; import.Cvar_ForceSet = Cvar_ForceSet; import.Cvar_VariableValue = Cvar_VariableValue; import.Cvar_VariableString = Cvar_VariableString; import.Cmd_Argc = Cmd_Argc; import.Cmd_Argv = Cmd_Argv; import.Cmd_Args = Cmd_Args; import.AddCommandString = Cbuf_AddText; import.SetAreaPortalState = SV_SetAreaPortalState; import.AreasConnected = CM_AreasConnected; import.FakeClientConnect = SVC_FakeConnect; import.DropClient = PF_DropClient; import.GetClientState = PF_GetClientState; import.LocateEntities = SV_LocateEntities; ge = (game_export_t *)Sys_LoadGameLibrary(LIB_GAME, &import); if( !ge ) Com_Error( ERR_DROP, "failed to load game DLL" ); apiversion = ge->API(); if( apiversion != GAME_API_VERSION ) Com_Error( ERR_DROP, "game is version %i, not %i", apiversion, GAME_API_VERSION ); // wsw : jal : send maxclients and frametime to game progs ge->Init( time(NULL), sv_maxclients->integer, svc.frametime ); }