summaryrefslogtreecommitdiff
path: root/source/sv_game.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/sv_game.c')
-rw-r--r--source/sv_game.c924
1 files changed, 924 insertions, 0 deletions
diff --git a/source/sv_game.c b/source/sv_game.c
new file mode 100644
index 0000000..50a5b5a
--- /dev/null
+++ b/source/sv_game.c
@@ -0,0 +1,924 @@
+/*
+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 "sv_local.h"
+#include "mvd_local.h"
+
+//void PF_error (const char *fmt, ...) q_noreturn;
+
+game_export_t *ge;
+
+
+/*
+===============
+PF_Unicast
+
+Sends the contents of the mutlicast buffer to a single client.
+Archived in MVD stream.
+===============
+*/
+static void PF_Unicast( edict_t *ent, qboolean reliable ) {
+ client_t *client;
+ int flags, clientNum;
+ svc_ops_t op;
+
+ if( !ent )
+ return;
+
+ clientNum = NUM_FOR_EDICT( ent ) - 1;
+ if( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
+ Com_WPrintf( "unicast to a non-client %d\n", clientNum );
+ return;
+ }
+
+ client = svs.clientpool + clientNum;
+ if( client->state == cs_free ) {
+ Com_WPrintf( "unicast to a free client %d\n", clientNum );
+ return;
+ }
+
+#if 0
+ // HACK: fixes 'anti-votekick' exploit
+ if( msg_write.data[0] == svc_disconnect ) {
+ SV_RemoveClient( client );
+ }
+#endif
+
+ flags = 0;
+ op = mvd_unicast;
+ if( reliable ) {
+ flags |= MSG_RELIABLE;
+ op = mvd_unicast_r;
+ }
+
+ if( client == svs.mvdummy ) {
+ if( msg_write.data[0] == svc_stufftext && reliable ) {
+ /* probably some Q2Admin crap,
+ * let MVD client process this internally */
+ SV_ClientAddMessage( client, flags );
+ } else if( sv.mvdpaused < PAUSED_FRAMES ) {
+ /* otherwise, MVD client will send
+ * this to everyone in freefloat mode */
+ SV_MvdUnicast( clientNum, op );
+ }
+ } else {
+ SV_ClientAddMessage( client, flags );
+ if( sv_mvd_enable->integer && sv.mvdpaused < PAUSED_FRAMES &&
+ SV_MvdPlayerIsActive( ent ) )
+ {
+ SV_MvdUnicast( clientNum, op );
+ }
+ }
+
+ SZ_Clear( &msg_write );
+}
+
+/*
+=================
+PF_bprintf
+
+Sends text to all active clients.
+Archived in MVD stream.
+=================
+*/
+static void PF_bprintf( int level, const char *fmt, ... ) {
+ va_list argptr;
+ char string[MAX_STRING_CHARS];
+ client_t *client;
+ int i;
+
+ va_start( argptr, fmt );
+ Q_vsnprintf( string, sizeof( string ), fmt, argptr );
+ va_end( argptr );
+
+ MSG_WriteByte( svc_print );
+ MSG_WriteByte( level );
+ MSG_WriteString( string );
+
+ // echo to console
+ if( dedicated->integer ) {
+ // mask off high bits
+ for( i = 0; string[i]; i++ )
+ string[i] &= 127;
+ Com_Printf( "%s", string );
+ }
+
+ FOR_EACH_CLIENT( client ) {
+ if( client->state != cs_spawned )
+ continue;
+ if( level >= client->messagelevel ) {
+ SV_ClientAddMessage( client, MSG_RELIABLE );
+ }
+ }
+ if( sv_mvd_enable->integer ) {
+ SV_MvdMulticast( -1, mvd_multicast_all_r );
+ }
+
+ SZ_Clear( &msg_write );
+}
+
+
+/*
+===============
+PF_dprintf
+
+Debug print to server console.
+===============
+*/
+static void PF_dprintf( const char *fmt, ... ) {
+ char msg[MAXPRINTMSG];
+ va_list argptr;
+
+ va_start( argptr, fmt );
+ Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
+ va_end( argptr );
+
+ Com_Printf( "%s", msg );
+}
+
+
+/*
+===============
+PF_cprintf
+
+Print to a single client if the level passes.
+Archived in MVD stream.
+===============
+*/
+static void PF_cprintf( edict_t *ent, int level, const char *fmt, ... ) {
+ char msg[MAX_STRING_CHARS];
+ va_list argptr;
+ int clientNum;
+ client_t *client;
+
+ va_start( argptr, fmt );
+ Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
+ va_end( argptr );
+
+ if( !ent ) {
+ Com_Printf( "%s", msg );
+ return;
+ }
+
+ clientNum = NUM_FOR_EDICT( ent ) - 1;
+ if( clientNum < 0 || clientNum >= sv_maxclients->integer ) {
+ Com_Error( ERR_DROP, "cprintf to a non-client %d", clientNum );
+ }
+
+ client = svs.clientpool + clientNum;
+ if( client->state == cs_free ) {
+ Com_Error( ERR_DROP, "cprintf to a free client %d", clientNum );
+ }
+
+ MSG_WriteByte( svc_print );
+ MSG_WriteByte( level );
+ MSG_WriteString( msg );
+
+ if( level >= client->messagelevel ) {
+ SV_ClientAddMessage( client, MSG_RELIABLE );
+ }
+
+ if( sv_mvd_enable->integer &&
+ ( client == svs.mvdummy || SV_MvdPlayerIsActive( ent ) ) )
+ {
+ SV_MvdUnicast( clientNum, mvd_unicast_r );
+ }
+
+ SZ_Clear( &msg_write );
+}
+
+
+/*
+===============
+PF_centerprintf
+
+Centerprint to a single client.
+Archived in MVD stream.
+===============
+*/
+static void PF_centerprintf (edict_t *ent, const char *fmt, ...) {
+ char msg[MAX_STRING_CHARS];
+ va_list argptr;
+ int n;
+
+ if( !ent ) {
+ return;
+ }
+
+ n = NUM_FOR_EDICT(ent);
+ if (n < 1 || n > sv_maxclients->integer) {
+ Com_WPrintf( "centerprintf to a non-client\n" );
+ return;
+ }
+
+ va_start (argptr,fmt);
+ Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
+ va_end (argptr);
+
+ MSG_WriteByte (svc_centerprint);
+ MSG_WriteString (msg);
+
+ PF_Unicast (ent, qtrue);
+}
+
+
+/*
+===============
+PF_error
+
+Abort the server with a game error
+===============
+*/
+static q_noreturn void PF_error (const char *fmt, ...) {
+ char msg[MAXPRINTMSG];
+ va_list argptr;
+
+ va_start (argptr,fmt);
+ Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
+ va_end (argptr);
+
+ Com_Error (ERR_DROP, "Game Error: %s", msg);
+}
+
+
+/*
+=================
+PF_setmodel
+
+Also sets mins and maxs for inline bmodels
+=================
+*/
+static void PF_setmodel (edict_t *ent, const char *name) {
+ int i;
+ cmodel_t *mod;
+
+ if (!name)
+ Com_Error (ERR_DROP, "PF_setmodel: NULL");
+
+ i = SV_ModelIndex (name);
+
+ ent->s.modelindex = i;
+
+// if it is an inline model, get the size information for it
+ if( name[0] == '*' ) {
+ mod = CM_InlineModel (&sv.cm, name);
+ VectorCopy (mod->mins, ent->mins);
+ VectorCopy (mod->maxs, ent->maxs);
+ SV_LinkEdict (ent);
+ }
+
+}
+
+/*
+===============
+PF_Configstring
+
+If game is actively running, broadcasts configstring change.
+Archived in MVD stream.
+===============
+*/
+void PF_Configstring( int index, const char *val ) {
+ int length, maxlength;
+ client_t *client;
+
+ if( index < 0 || index >= MAX_CONFIGSTRINGS )
+ Com_Error( ERR_DROP, "PF_Configstring: bad index %i\n", index );
+
+ if( !val )
+ val = "";
+
+ length = strlen( val );
+ maxlength = sizeof( sv.configstrings ) - MAX_QPATH * index;
+ if( length >= maxlength ) {
+ Com_Error( ERR_DROP, "PF_Configstring: index %d overflowed: %d > %d\n",
+ index, length, maxlength - 1 );
+ }
+
+ if( !strcmp( sv.configstrings[index], val ) ) {
+ return;
+ }
+
+ // change the string in sv
+ strcpy( sv.configstrings[index], val );
+
+ if( sv.state == ss_loading ) {
+ return;
+ }
+
+ if( sv_mvd_enable->integer ) {
+ SV_MvdConfigstring( index, val );
+ }
+
+ // send the update to everyone
+ MSG_WriteByte( svc_configstring );
+ MSG_WriteShort( index );
+ MSG_WriteString( val );
+
+ FOR_EACH_CLIENT( client ) {
+ if( client->state < cs_primed ) {
+ continue;
+ }
+ SV_ClientAddMessage( client, MSG_RELIABLE );
+ }
+
+ SZ_Clear( &msg_write );
+}
+
+static void PF_WriteFloat( float f ) {
+ Com_Error( ERR_DROP, "PF_WriteFloat not implemented" );
+}
+
+
+
+/*
+=================
+PF_inPVS
+
+Also checks portalareas so that doors block sight
+=================
+*/
+static qboolean PF_inPVS (vec3_t p1, vec3_t p2) {
+ cleaf_t *leaf;
+ int cluster;
+ int area1, area2;
+ byte *mask;
+
+ if( !sv.cm.cache ) {
+ Com_Error( ERR_DROP, "PF_inPVS: no map loaded" );
+ }
+
+ leaf = CM_PointLeaf (&sv.cm, p1);
+ cluster = CM_LeafCluster (leaf);
+ area1 = CM_LeafArea (leaf);
+ mask = CM_ClusterPVS (&sv.cm, cluster);
+
+ leaf = CM_PointLeaf (&sv.cm, p2);
+ cluster = CM_LeafCluster (leaf);
+ area2 = CM_LeafArea (leaf);
+ if ( !Q_IsBitSet( mask, cluster ) )
+ return qfalse;
+ if (!CM_AreasConnected (&sv.cm, 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) {
+ cleaf_t *leaf;
+ int cluster;
+ int area1, area2;
+ byte *mask;
+
+ if( !sv.cm.cache ) {
+ Com_Error( ERR_DROP, "PF_inPHS: no map loaded" );
+ }
+
+ leaf = CM_PointLeaf (&sv.cm, p1);
+ cluster = CM_LeafCluster (leaf);
+ area1 = CM_LeafArea (leaf);
+ mask = CM_ClusterPHS (&sv.cm, cluster);
+
+ leaf = CM_PointLeaf (&sv.cm, p2);
+ cluster = CM_LeafCluster (leaf);
+ area2 = CM_LeafArea (leaf);
+ if( !Q_IsBitSet( mask, cluster ) )
+ return qfalse; // more than one bounce away
+ if (!CM_AreasConnected (&sv.cm, area1, area2))
+ return qfalse; // a door blocks hearing
+
+ return qtrue;
+}
+
+/*
+==================
+PF_StartSound
+
+Each entity can have eight independant sound sources, like voice,
+weapon, feet, etc.
+
+If channel & 8, the sound will be sent to everyone, not just
+things in the PHS.
+
+FIXME: if entity isn't in PHS, they must be forced to be sent or
+have the origin explicitly sent.
+
+Channel 0 is an auto-allocate channel, the others override anything
+already running on that entity/channel pair.
+
+An attenuation of 0 will play full volume everywhere in the level.
+Larger attenuations will drop off. (max 4 attenuation)
+
+Timeofs can range from 0.0 to 0.1 to cause sounds to be started
+later in the frame than they normally would.
+
+If origin is NULL, the origin is determined from the entity origin
+or the midpoint of the entity box for bmodels.
+==================
+*/
+static void PF_StartSound( edict_t *entity, int channel,
+ int soundindex, float volume,
+ float attenuation, float timeofs )
+{
+ int sendchan;
+ int flags;
+ int ent;
+ vec3_t origin, clientorg;
+ client_t *client;
+ byte *mask;
+ cleaf_t *leaf;
+ int area, cluster;
+ player_state_t *ps;
+
+ if( !entity )
+ return;
+
+ if( volume < 0 || volume > 1.0 )
+ Com_Error( ERR_DROP, "PF_StartSound: volume = %f", volume );
+ if( attenuation < 0 || attenuation > 4 )
+ Com_Error( ERR_DROP, "PF_StartSound: attenuation = %f", attenuation );
+ if( timeofs < 0 || timeofs > 0.255 )
+ Com_Error( ERR_DROP, "PF_StartSound: timeofs = %f", timeofs );
+ if( soundindex < 0 || soundindex >= MAX_SOUNDS )
+ Com_Error( ERR_DROP, "PF_StartSound: soundindex = %d", soundindex );
+
+ ent = NUM_FOR_EDICT( entity );
+
+ sendchan = ( ent << 3 ) | ( channel & 7 );
+
+ // always send the entity number for channel overrides
+ flags = SND_ENT;
+ if( volume != DEFAULT_SOUND_PACKET_VOLUME )
+ flags |= SND_VOLUME;
+ if( attenuation != DEFAULT_SOUND_PACKET_ATTENUATION )
+ flags |= SND_ATTENUATION;
+ if( timeofs )
+ flags |= SND_OFFSET;
+
+ // use the entity origin unless it is a bmodel
+ if( entity->solid == SOLID_BSP ) {
+ VectorAvg( entity->mins, entity->maxs, origin );
+ VectorAdd( entity->s.origin, origin, origin );
+ } else {
+ VectorCopy( entity->s.origin, origin );
+ }
+
+ // if the sound doesn't attenuate,send it to everyone
+ // (global radio chatter, voiceovers, etc)
+ if( attenuation == ATTN_NONE ) {
+ channel |= CHAN_NO_PHS_ADD;
+ }
+
+ FOR_EACH_CLIENT( client ) {
+ // do not send sounds to connecting clients
+ if( client->state != cs_spawned || client->download || client->nodata ) {
+ continue;
+ }
+
+ // send origin for invisible entities
+ if( entity->svflags & SVF_NOCLIENT ) {
+ flags |= SND_POS;
+ }
+
+ // default client doesn't know that bmodels have weird origins
+ if( entity->solid == SOLID_BSP && client->protocol == PROTOCOL_VERSION_DEFAULT ) {
+ flags |= SND_POS;
+ }
+
+ if( entity != client->edict ) {
+ // get client viewpos
+ ps = &client->edict->client->ps;
+ VectorMA( ps->viewoffset, 0.125f, ps->pmove.origin, clientorg );
+
+ // PHS cull this sound
+ if( ( channel & CHAN_NO_PHS_ADD ) == 0 ) {
+ leaf = CM_PointLeaf( &sv.cm, clientorg );
+ area = CM_LeafArea( leaf );
+ if( !CM_AreasConnected( &sv.cm, area, entity->areanum ) ) {
+ // doors can legally straddle two areas, so
+ // we may need to check another one
+ if( !entity->areanum2 || !CM_AreasConnected( &sv.cm, area, entity->areanum2 ) ) {
+ continue; // blocked by a door
+ }
+ }
+ cluster = CM_LeafCluster( leaf );
+ mask = CM_ClusterPHS( &sv.cm, cluster );
+ if( !SV_EdictPV( entity, mask ) ) {
+ continue; // not in PHS
+ }
+ }
+
+ // check if position needs to be explicitly sent
+ if( ( flags & SND_POS ) == 0 ) {
+ mask = CM_FatPVS( &sv.cm, clientorg );
+ if( !SV_EdictPV( entity, mask ) ) {
+ flags |= SND_POS; // not in PVS
+ }
+ }
+ }
+
+ MSG_WriteByte( svc_sound );
+ MSG_WriteByte( flags );
+ MSG_WriteByte( soundindex );
+
+ if( flags & SND_VOLUME )
+ MSG_WriteByte( volume * 255 );
+ if( flags & SND_ATTENUATION )
+ MSG_WriteByte( attenuation * 64 );
+ if( flags & SND_OFFSET )
+ MSG_WriteByte( timeofs * 1000 );
+
+ MSG_WriteShort( sendchan );
+
+ if( flags & SND_POS )
+ MSG_WritePos( origin );
+
+ flags &= ~SND_POS;
+
+ if( channel & CHAN_RELIABLE ) {
+ SV_ClientAddMessage( client, MSG_RELIABLE|MSG_CLEAR );
+ } else {
+ SV_ClientAddMessage( client, MSG_CLEAR );
+ }
+ }
+
+ if( svs.mvdummy && sv.mvdpaused < PAUSED_FRAMES ) {
+ int extrabits = 0;
+
+ if( channel & CHAN_NO_PHS_ADD ) {
+ extrabits |= 1;
+ }
+ if( channel & CHAN_RELIABLE ) {
+ extrabits |= 2;
+ }
+
+ SZ_WriteByte( &sv.multicast, mvd_sound | ( extrabits << SVCMD_BITS ) );
+ SZ_WriteByte( &sv.multicast, flags );
+ SZ_WriteByte( &sv.multicast, soundindex );
+
+ if( flags & SND_VOLUME )
+ SZ_WriteByte( &sv.multicast, volume * 255 );
+ if( flags & SND_ATTENUATION )
+ SZ_WriteByte( &sv.multicast, attenuation * 64 );
+ if( flags & SND_OFFSET )
+ SZ_WriteByte( &sv.multicast, timeofs * 1000 );
+
+ SZ_WriteShort( &sv.multicast, sendchan );
+ }
+}
+
+static void PF_PositionedSound( vec3_t origin, edict_t *entity, int channel,
+ int soundindex, float volume,
+ float attenuation, float timeofs )
+{
+ int sendchan;
+ int flags;
+ int ent;
+
+ if( !origin )
+ Com_Error( ERR_DROP, "PF_PositionedSound: NULL origin" );
+ if( volume < 0 || volume > 1.0 )
+ Com_Error( ERR_DROP, "PF_PositionedSound: volume = %f", volume );
+ if( attenuation < 0 || attenuation > 4 )
+ Com_Error( ERR_DROP, "PF_PositionedSound: attenuation = %f", attenuation );
+ if( timeofs < 0 || timeofs > 0.255 )
+ Com_Error( ERR_DROP, "PF_PositionedSound: timeofs = %f", timeofs );
+ if( soundindex < 0 || soundindex >= MAX_SOUNDS )
+ Com_Error( ERR_DROP, "PF_PositionedSound: soundindex = %d", soundindex );
+
+ ent = NUM_FOR_EDICT( entity );
+
+ sendchan = ( ent << 3 ) | ( channel & 7 );
+
+ // always send the entity number for channel overrides
+ flags = SND_ENT|SND_POS;
+ if( volume != DEFAULT_SOUND_PACKET_VOLUME )
+ flags |= SND_VOLUME;
+ if( attenuation != DEFAULT_SOUND_PACKET_ATTENUATION )
+ flags |= SND_ATTENUATION;
+ if( timeofs )
+ flags |= SND_OFFSET;
+
+ MSG_WriteByte( svc_sound );
+ MSG_WriteByte( flags );
+ MSG_WriteByte( soundindex );
+
+ if( flags & SND_VOLUME )
+ MSG_WriteByte( volume * 255 );
+ if( flags & SND_ATTENUATION )
+ MSG_WriteByte( attenuation * 64 );
+ if( flags & SND_OFFSET )
+ MSG_WriteByte( timeofs * 1000 );
+
+ MSG_WriteShort( sendchan );
+ MSG_WritePos( origin );
+
+ // if the sound doesn't attenuate,send it to everyone
+ // (global radio chatter, voiceovers, etc)
+ if( attenuation == ATTN_NONE || ( channel & CHAN_NO_PHS_ADD ) ) {
+ if( channel & CHAN_RELIABLE ) {
+ SV_Multicast( NULL, MULTICAST_ALL_R );
+ } else {
+ SV_Multicast( NULL, MULTICAST_ALL );
+ }
+ } else {
+ if( channel & CHAN_RELIABLE ) {
+ SV_Multicast( origin, MULTICAST_PHS_R );
+ } else {
+ SV_Multicast( origin, MULTICAST_PHS );
+ }
+ }
+}
+
+
+void PF_Pmove( pmove_t *pm ) {
+ if( !sv_client ) {
+ //Pmove( pm ); // TODO
+ return;
+ }
+
+ Pmove( pm, &sv_client->pmp );
+}
+
+static cvar_t *PF_cvar( const char *name, const char *value, int flags ) {
+ cvar_t *var;
+
+ if( flags & CVAR_EXTENDED_MASK ) {
+ Com_WPrintf( "Game DLL attemped to set extended flags on variable '%s', cleared.\n", name );
+ flags &= ~CVAR_EXTENDED_MASK;
+ }
+
+ var = Cvar_Get( name, value, flags );
+ if( !var->subsystem ) {
+ var->subsystem = CVAR_SYSTEM_GAME;
+ }
+
+ return var;
+}
+
+static void PF_AddCommandString( const char *string ) {
+ Cbuf_AddTextEx( &cmd_buffer, string );
+}
+
+static void PF_SetAreaPortalState( int portalnum, qboolean open ) {
+ if( !sv.cm.cache ) {
+ Com_Error( ERR_DROP, "PF_SetAreaPortalState: no map loaded" );
+ }
+ CM_SetAreaPortalState( &sv.cm, portalnum, open );
+}
+
+static qboolean PF_AreasConnected( int area1, int area2 ) {
+ if( !sv.cm.cache ) {
+ Com_Error( ERR_DROP, "PF_AreasConnected: no map loaded" );
+ }
+ return CM_AreasConnected( &sv.cm, area1, area2 );
+}
+
+static void *PF_TagMalloc( int size, memtag_t tag ) {
+ void *ptr;
+
+ if( !size ) {
+ return NULL;
+ }
+
+ ptr = Z_TagMalloc( size, tag );
+ memset( ptr, 0, size );
+
+ return ptr;
+}
+
+//==============================================
+
+static void *game_library;
+
+/*
+===============
+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 ();
+ ge = NULL;
+ if( game_library ) {
+ Sys_FreeLibrary( game_library );
+ game_library = NULL;
+ }
+}
+
+#if 0
+static qboolean SV_GameSetupCallback( api_type_t type, void *api ) {
+ switch( type ) {
+ case API_CMD:
+ Cmd_FillAPI( ( cmdAPI_t * )api );
+ break;
+ case API_CVAR:
+ Cvar_FillAPI( ( cvarAPI_t * )api );
+ break;
+ case API_FS:
+ FS_FillAPI( ( fsAPI_t * )api );
+ break;
+ case API_COMMON:
+ Com_FillAPI( ( commonAPI_t * )api );
+ break;
+ case API_SYSTEM:
+ Sys_FillAPI( ( sysAPI_t * )api );
+ break;
+ default:
+ Com_Error( ERR_FATAL, "SV_GameSetupCallback: bad api type" );
+ }
+
+ return qtrue;
+}
+#endif
+
+/*
+===============
+SV_InitGameProgs
+
+Init the game subsystem for a new map
+===============
+*/
+void SCR_DebugGraph (float value, int color);
+
+void SV_InitGameProgs ( void )
+{
+ game_import_t import;
+ char path[MAX_OSPATH];
+ game_export_t *(*entry)( game_import_t * ) = NULL;
+ /*moduleEntry_t moduleEntry;
+ moduleInfo_t info;
+ moduleCapability_t caps;
+ APISetupCallback_t callback;*/
+
+ // unload anything we have now
+ SV_ShutdownGameProgs ();
+
+#ifdef _WIN32
+ // FIXME: check current debug directory first for
+ // e.g. running legacy stuff like Q2Admin
+ Com_sprintf( path, sizeof( path ), "%s" PATH_SEP_STRING "release"
+ PATH_SEP_STRING GAMELIB, Sys_GetCurrentDirectory() );
+ entry = Sys_LoadLibrary( path, "GetGameAPI", &game_library );
+ if( !entry )
+#endif
+ {
+ // try refdir first for development purposes
+ Com_sprintf( path, sizeof( path ), "%s" PATH_SEP_STRING GAMELIB,
+ sys_refdir->string );
+ entry = Sys_LoadLibrary( path, "GetGameAPI", &game_library );
+ if( !entry ) {
+ // try gamedir
+ if( fs_game->string[0] ) {
+ Com_sprintf( path, sizeof( path ), "%s" PATH_SEP_STRING "%s"
+ PATH_SEP_STRING GAMELIB, sys_libdir->string, fs_game->string );
+ entry = Sys_LoadLibrary( path, "GetGameAPI", &game_library );
+ }
+
+ if( !entry ) {
+ // try baseq2
+ Com_sprintf( path, sizeof( path ), "%s" PATH_SEP_STRING BASEGAME
+ PATH_SEP_STRING GAMELIB, sys_libdir->string );
+ entry = Sys_LoadLibrary( path, "GetGameAPI", &game_library );
+ if( !entry ) {
+ Com_Error( ERR_DROP, "Failed to load game DLL" );
+ }
+ }
+ }
+ }
+
+ // load a new game dll
+ import.multicast = SV_Multicast;
+ import.unicast = PF_Unicast;
+ import.bprintf = PF_bprintf;
+ import.dprintf = PF_dprintf;
+ import.cprintf = PF_cprintf;
+ import.centerprintf = PF_centerprintf;
+ import.error = PF_error;
+
+ import.linkentity = SV_LinkEdict;
+ import.unlinkentity = SV_UnlinkEdict;
+ import.BoxEdicts = SV_AreaEdicts;
+#ifdef _WIN32
+#ifdef __GNUC__
+ import.trace = ( sv_trace_t )SV_Trace_Old;
+#else
+ import.trace = SV_Trace;
+#endif
+#else /* _WIN32 */
+ if( sv_oldgame_hack->integer ) {
+ import.trace = ( sv_trace_t )SV_Trace_Old;
+ } else {
+ import.trace = SV_Trace;
+ }
+#endif /* !_WIN32 */
+ import.pointcontents = SV_PointContents;
+ import.setmodel = PF_setmodel;
+ import.inPVS = PF_inPVS;
+ import.inPHS = PF_inPHS;
+ import.Pmove = PF_Pmove;
+
+ import.modelindex = SV_ModelIndex;
+ import.soundindex = SV_SoundIndex;
+ import.imageindex = SV_ImageIndex;
+
+ import.configstring = PF_Configstring;
+ import.sound = PF_StartSound;
+ import.positioned_sound = PF_PositionedSound;
+
+ import.WriteChar = MSG_WriteChar;
+ import.WriteByte = MSG_WriteByte;
+ import.WriteShort = MSG_WriteShort;
+ import.WriteLong = MSG_WriteLong;
+ import.WriteFloat = PF_WriteFloat;
+ import.WriteString = MSG_WriteString;
+ import.WritePosition = MSG_WritePos;
+ import.WriteDir = MSG_WriteDir;
+ import.WriteAngle = MSG_WriteAngle;
+
+ import.TagMalloc = PF_TagMalloc;
+ import.TagFree = Z_Free;
+ import.FreeTags = Z_FreeTags;
+
+ import.cvar = PF_cvar;
+ import.cvar_set = Cvar_UserSet;
+ import.cvar_forceset = Cvar_Set;
+
+ import.argc = Cmd_Argc;
+ import.argv = Cmd_Argv;
+ import.args = Cmd_Args;
+ import.AddCommandString = PF_AddCommandString;
+
+ import.DebugGraph = SCR_DebugGraph;
+ import.SetAreaPortalState = PF_SetAreaPortalState;
+ import.AreasConnected = PF_AreasConnected;
+
+ ge = entry( &import );
+ if (!ge) {
+ Com_Error (ERR_DROP, "Game DLL returned NULL exports");
+ }
+ if (ge->apiversion != GAME_API_VERSION) {
+ Com_Error (ERR_DROP, "Game DLL is version %d, expected %d",
+ ge->apiversion, GAME_API_VERSION);
+ }
+
+#if 0
+ // get extended API
+ moduleEntry = Sys_GetProcAddress( game_library, "moduleEntry" );
+ if( moduleEntry ) {
+ moduleEntry( MQ_GETINFO, &info );
+ if( info.api_version != MODULES_APIVERSION ) {
+ Com_DPrintf( "Game DLL has incompatible extended "
+ "api_version: %d, should be %d\n",
+ info.api_version, MODULES_APIVERSION );
+ } else {
+ caps = ( moduleCapability_t )moduleEntry( MQ_GETCAPS, NULL );
+ if( !( caps & MCP_GAME ) ) {
+ Com_DPrintf( "Game DLL doesn't have GAME capability\n" );
+ } else {
+ callback = ( APISetupCallback_t )moduleEntry( MQ_SETUPAPI,
+ ( void * )SV_GameSetupCallback );
+ if( !callback ) {
+ Com_DPrintf( "Game DLL returned NULL callback\n" );
+ } else {
+ callback( API_GAME, &game_api );
+ Com_DPrintf( "Extended game API initialized\n" );
+ }
+ }
+ }
+ }
+#endif
+
+ // initialize
+ ge->Init ();
+}
+