sin-2015/g_main.cpp
1999-04-22 00:00:00 +00:00

3177 lines
68 KiB
C++

// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
//
//### upped savegame version for the add-on pack
//#define SAVEGAME_VERSION 13
#define SAVEGAME_VERSION 15
#include <setjmp.h>
#include "g_local.h"
#include "g_utils.h"
#include "Entity.h"
#include "vector.h"
#include "scriptmaster.h"
#include "navigate.h"
#include "viewthing.h"
#include "console.h"
#include "player.h"
#include "surface.h"
#include "gravpath.h"
#include "deadbody.h"
#include "spritegun.h" //### added for sprite gun
#ifdef SIN_ARCADE
#include "arcade_comm.h"
#endif
Vector vec_origin = "0 0 0";
Vector vec_zero = "0 0 0";
qboolean LoadingSavegame = false;
qboolean LoadingServer = false;
game_locals_t game;
level_locals_t level;
game_import_t gi;
game_export_t globals;
edict_t *g_edicts = NULL;
edict_t active_edicts;
edict_t free_edicts;
netconsole_t *g_consoles;
netconbuffer_t *g_conbuffers;
netsurface_t *g_surfaces;
cvar_t *developer;
cvar_t *deathmatch;
cvar_t *coop;
cvar_t *dmflags;
cvar_t *skill;
cvar_t *fraglimit;
cvar_t *timelimit;
cvar_t *password;
cvar_t *filterban;
cvar_t *flood_msgs;
cvar_t *flood_persecond;
cvar_t *flood_waitdelay;
cvar_t *maxclients;
cvar_t *maxentities;
cvar_t *maxconsoles;
cvar_t *maxsurfaces;
cvar_t *g_select_empty;
cvar_t *g_unlimited_ammo;
cvar_t *nomonsters;
cvar_t *dm_respawn;
cvar_t *dialog;
cvar_t *precache;
cvar_t *g_showmem;
cvar_t *g_timeents;
cvar_t *sv_maxvelocity;
cvar_t *sv_gravity;
cvar_t *dedicated;
cvar_t *sv_rollspeed;
cvar_t *sv_rollangle;
cvar_t *gun_x;
cvar_t *gun_y;
cvar_t *gun_z;
cvar_t *run_pitch;
cvar_t *run_roll;
cvar_t *bob_up;
cvar_t *bob_pitch;
cvar_t *bob_roll;
cvar_t *sv_cheats;
cvar_t *sv_showbboxes;
cvar_t *sv_showentnums;
cvar_t *sv_rocketspeed;
cvar_t *sv_rocketrate;
cvar_t *sv_stopspeed;
cvar_t *sv_friction;
cvar_t *sv_waterfriction;
cvar_t *sv_waterspeed;
cvar_t *sv_maxbulletholes;
cvar_t *sv_maxbloodsplats;
cvar_t *sv_gore;
cvar_t *sv_gibs;
cvar_t *sv_showdamage;
cvar_t *sv_showdamagelocation;
cvar_t *sv_traceinfo;
cvar_t *sv_drawtrace;
cvar_t *sv_maplist;
cvar_t *sv_footsteps;
cvar_t *sv_fatrockets;
cvar_t *csys_posx;
cvar_t *csys_posy;
cvar_t *csys_posz;
cvar_t *csys_x;
cvar_t *csys_y;
cvar_t *csys_z;
cvar_t *csys_draw;
cvar_t *parentmode;
//###
cvar_t *informermodel;
cvar_t *informerskin;
cvar_t *mobmodel;
cvar_t *mobskin;
float reset_timeofs;
//###
int sv_numtraces;
usercmd_t *current_ucmd;
void G_AllocDebugLines( void );
void G_ClientDrawBoundingBoxes( void );
void ( *ServerError )( const char *fmt, ... );
char G_ErrorMessage[ 1024 ];
jmp_buf G_AbortGame;
/*
===============
G_Error
Abort the server with a game error
===============
*/
void G_Error
(
const char *fmt,
...
)
{
va_list argptr;
va_start( argptr, fmt );
vsprintf( G_ErrorMessage, fmt, argptr );
va_end( argptr );
// assert( 0 );
longjmp( G_AbortGame, -1 );
}
/*
===============
G_ExitWithError
Calls the server's error function with the last error that occurred.
Should only be called after a setjmp( G_AbortGame ) call
===============
*/
void G_ExitWithError
(
void
)
{
ServerError( G_ErrorMessage );
}
/*
=================
G_ShutdownGame
Frees up any resources
=================
*/
void G_ShutdownGame
(
void
)
{
gi.dprintf ("==== ShutdownGame ====\n");
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
G_LevelShutdown();
gi.FreeTags (TAG_GAME);
#ifdef SIN_ARCADE
ARCADE_CloseCommunications();
#endif
}
/*
============
G_InitGame
This will be called when the dll is first loaded, which
only happens when a new game is begun
============
*/
void G_InitGame
(
void
)
{
gi.dprintf ("==== InitGame ====\n");
// Install our own error handler, since we can't
// call the EXE's handler from within a C++ class
ServerError = gi.error;
gi.error = G_Error;
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
// initialize the game variables
gameVars.ClearList();
//### set the game2015 cvar
gi.cvar_forceset("game2015", "1");
//###
gun_x = gi.cvar ("gun_x", "0", 0);
gun_y = gi.cvar ("gun_y", "0", 0);
gun_z = gi.cvar ("gun_z", "0", 0);
developer = gi.cvar( "developer", "0", 0 );
precache = gi.cvar( "sv_precache", "1", 0 );
//FIXME: sv_ prefix is wrong for these
sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
sv_gravity = gi.cvar ("sv_gravity", "800", 0);
sv_maxbulletholes = gi.cvar ("sv_maxbulletholes", "32", 0);
sv_maxbloodsplats = gi.cvar ("sv_maxbloodspats", "5", 0);
sv_gore = gi.cvar ("sv_gore", "1", 0);
sv_gibs = gi.cvar ("sv_gibs", "1", 0);
sv_showdamage = gi.cvar ("sv_showdetaildamage", "0", 0);
sv_showdamagelocation = gi.cvar ("sv_showdamagelocation", "0", 0);
// noset vars
dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
// latched vars
sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
deathmatch = gi.cvar ("deathmatch", "0", CVAR_SERVERINFO|CVAR_LATCH);
coop = gi.cvar ("coop", "0", CVAR_SERVERINFO|CVAR_LATCH);
skill = gi.cvar ("skill", "1", CVAR_SERVERINFO|CVAR_LATCH);
#ifdef SIN
maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE );
#else
maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
#endif
maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
maxconsoles = gi.cvar ("maxconsoles", "32", CVAR_LATCH);
maxsurfaces = gi.cvar ("maxsurfaces", "1024", CVAR_LATCH);
// flood control
flood_msgs = gi.cvar ("flood_msgs", "4", 0);
flood_persecond = gi.cvar ("flood_persecond", "4", 0);
flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
// change anytime vars
password = gi.cvar ("password", "", CVAR_USERINFO);
filterban = gi.cvar ("filterban", "1", 0);
dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
g_unlimited_ammo = gi.cvar ("g_unlimited_ammo", "0", CVAR_SERVERINFO);
g_showmem = gi.cvar ("g_showmem", "0", 0 );
g_timeents = gi.cvar ("g_timeents", "0", 0 );
dm_respawn = gi.cvar ("dm_respawn", "2", CVAR_SERVERINFO);
nomonsters = gi.cvar ("nomonsters", "0", CVAR_SERVERINFO);
dialog = gi.cvar ("dialog", "2", CVAR_SERVERINFO | CVAR_ARCHIVE );
run_pitch = gi.cvar ("run_pitch", "0.002", 0);
run_roll = gi.cvar ("run_roll", "0.005", 0);
bob_up = gi.cvar ("bob_up", "0.005", 0);
bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
bob_roll = gi.cvar ("bob_roll", "0.002", 0);
bob_roll = gi.cvar ("bob_roll", "0.002", 0);
csys_posx = gi.cvar ("csys_posx", "0", 0);
csys_posy = gi.cvar ("csys_posy", "0", 0);
csys_posz = gi.cvar ("csys_posz", "0", 0);
csys_x = gi.cvar ("csys_x", "0", 0);
csys_y = gi.cvar ("csys_y", "0", 0);
csys_z = gi.cvar ("csys_z", "0", 0);
csys_draw = gi.cvar ("csys_draw", "0", 0);
sv_traceinfo = gi.cvar ("sv_traceinfo", "0", 0);
sv_drawtrace = gi.cvar ("sv_drawtrace", "0", 0);
// debug stuff
sv_showbboxes = gi.cvar ("sv_showbboxes", "0", 0);
sv_showentnums = gi.cvar ("sv_showentnums", "0", 0);
sv_rocketspeed = gi.cvar ("sv_rocketspeed", "300", 0);
sv_rocketrate = gi.cvar ("sv_rocketrate", "1.2", 0);
sv_friction = gi.cvar ("sv_friction", "4", CVAR_SERVERINFO);
sv_stopspeed = gi.cvar ("sv_stopspeed", "100", CVAR_SERVERINFO);
sv_waterfriction = gi.cvar ("sv_waterfriction", "1", CVAR_SERVERINFO);
sv_waterspeed = gi.cvar ("sv_waterspeed", "400", CVAR_SERVERINFO);
sv_maplist = gi.cvar ("sv_maplist", "", CVAR_SERVERINFO|CVAR_ARCHIVE);
sv_footsteps = gi.cvar ("sv_footsteps", "1", CVAR_SERVERINFO|CVAR_ARCHIVE);
if ( deathmatch->value )
{
sv_fatrockets = gi.cvar ("sv_fatrockets", "1", CVAR_SERVERINFO);
}
else
{
sv_fatrockets = gi.cvar ("sv_fatrockets", "1", CVAR_SERVERINFO);
}
parentmode = gi.cvar ("parentmode", "0", CVAR_USERINFO|CVAR_SERVERINFO|CVAR_ARCHIVE);
//###
informermodel = gi.cvar("informermodel", MFD_INFORMERMODEL, CVAR_SERVERINFO|CVAR_ARCHIVE);
informerskin = gi.cvar("informerskin", MFD_INFORMERSKIN, CVAR_SERVERINFO|CVAR_ARCHIVE);
mobmodel = gi.cvar("mobmodel", MFD_MOBMODEL, CVAR_SERVERINFO|CVAR_ARCHIVE);
mobskin = gi.cvar("mobskin", MFD_MOBSKIN, CVAR_SERVERINFO|CVAR_ARCHIVE);
//###
G_InitEvents();
sv_numtraces = 0;
game.maxentities = maxentities->value;
if (maxclients->value * 8 > game.maxentities)
{
game.maxentities = maxclients->value * 8;
}
//### added a few edicts per client to allow for the extra hoverbike entities
game.maxentities += maxclients->value * 5;
//###
game.maxclients = maxclients->value;
game.maxconsoles = maxconsoles->value;
game.maxsurfaces = maxsurfaces->value;
G_AllocGameData();
#ifdef SIN_ARCADE
ARCADE_SetupCommunications();
#endif
}
void G_AllocGameData
(
void
)
{
int i;
gi.FreeTags( TAG_GAME );
// Initialize debug lines
G_AllocDebugLines();
// initialize all entities for this game
g_edicts = ( edict_t * )gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
globals.edicts = g_edicts;
globals.max_edicts = game.maxentities;
// Add all the edicts to the free list
LL_Reset( &free_edicts, next, prev );
LL_Reset( &active_edicts, next, prev );
for( i = 0; i < game.maxentities; i++ )
{
LL_Add( &free_edicts, &g_edicts[ i ], next, prev );
}
// initialize all clients for this game
game.clients = ( gclient_t * )gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
memset( game.clients, 0, game.maxclients * sizeof( game.clients[ 0 ] ) );
for (i=0 ; i<game.maxclients ; i++)
{
// set client fields on player ents
g_edicts[ i + 1 ].client = game.clients + i;
G_InitClientPersistant (&game.clients[i]);
G_InitClientResp (&game.clients[i]);
}
globals.num_edicts = game.maxclients+1;
// initialize all the consoles
g_consoles = (netconsole_t *)gi.TagMalloc (game.maxconsoles * sizeof(g_consoles[0]), TAG_GAME);
globals.consoles = g_consoles;
globals.max_consoles = game.maxconsoles;
globals.num_consoles = 0;
g_conbuffers = (netconbuffer_t *)gi.TagMalloc (game.maxconsoles * sizeof(g_conbuffers[0]), TAG_GAME);
globals.conbuffers = g_conbuffers;
// initialize the surfaces
g_surfaces = (netsurface_t *)gi.TagMalloc (game.maxsurfaces * sizeof(g_surfaces[0]), TAG_GAME);
globals.surfaces = g_surfaces;
globals.max_surfaces = game.maxsurfaces;
globals.num_surfaces = 0;
}
void ReadGame
(
const char *name
)
{
str mapname;
str spawnpoint;
Archiver arc;
int version;
int savegame_version;
LoadingServer = true;
// Get rid of anything left over from the last level
G_LevelShutdown();
arc.Read( name );
arc.ReadInteger( &version );
if ( version < GAME_API_VERSION )
{
gi.error( "Savegame from an older version (%d) of Sin.\n", version );
}
else if ( version > GAME_API_VERSION )
{
gi.error( "Savegame from version %d of Sin.\n", version );
}
arc.ReadInteger( &savegame_version );
if ( savegame_version < SAVEGAME_VERSION )
{
gi.error( "Savegame from an older version (%d) of Sin.\n", version );
}
else if ( savegame_version > SAVEGAME_VERSION )
{
gi.error( "Savegame from version %d of Sin.\n", version );
}
// Read the map name (needed by G_MapInit)
arc.ReadString( &mapname );
// Set up for a new map
G_MapInit( mapname.c_str() );
arc.ReadObject( &PersistantData );
arc.ReadObject( &game );
arc.ReadObject( &gameVars );
arc.Close();
}
void G_ReadGame
(
const char *name
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
ReadGame( name );
}
/*
============
WriteGame
This will be called whenever the game goes to a new level,
and when the user explicitly saves the game.
Game information include cross level data, like multi level
triggers, help computer info, and all client states.
A single player death will automatically restore from the
last save position.
============
*/
void WriteGame
(
const char *name,
qboolean autosave
)
{
Archiver arc;
game.autosaved = autosave;
arc.Create( name );
arc.WriteInteger( GAME_API_VERSION );
arc.WriteInteger( SAVEGAME_VERSION );
arc.WriteString( level.mapname );
arc.WriteObject( &PersistantData );
arc.WriteObject( &game );
arc.WriteObject( &gameVars );
arc.Close();
game.autosaved = false;
}
void G_WriteGame
(
const char *name,
qboolean autosave
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
WriteGame( name, autosave );
}
/*
==============
G_WriteClient
==============
*/
void G_WriteClient
(
Archiver &arc,
gclient_t *client
)
{
arc.WriteRaw( client, sizeof( gclient_t ) );
}
/*
==============
G_ReadClient
==============
*/
void G_ReadClient
(
Archiver &arc,
gclient_t *client
)
{
arc.ReadRaw( client, sizeof( gclient_t ) );
}
/*
==================
G_SaveClientData
Some information that should be persistant, like health,
is stored in the Entity structure, so it needs to be mirrored
out to the client structure before all the edicts are wiped.
==================
*/
void G_SaveClientData
(
void
)
{
int i;
edict_t *ent;
PersistantData.Reset();
if ( deathmatch->value )
{
return;
}
for( i = 0; i < game.maxclients; i++ )
{
ent = &g_edicts[ 1 + i ];
if ( !ent->inuse || !ent->entity )
{
continue;
}
PersistantData.AddEnt( ent->entity );
}
}
/*
=================
WriteLevel
=================
*/
void WriteLevel
(
const char *filename,
qboolean autosave
)
{
int i;
int num;
edict_t *edict;
Archiver arc;
if ( autosave )
{
for( i = 0; i < game.maxclients; i++ )
{
edict = &g_edicts[ 1 + i ];
if ( !edict->inuse && !edict->entity )
{
continue;
}
delete edict->entity;
}
}
arc.Create( filename );
// write out the version number
arc.WriteInteger( GAME_API_VERSION );
arc.WriteInteger( SAVEGAME_VERSION );
// Write out the pending events. These are written first in case
// later objects need to post events when reading the archive.
G_ArchiveEvents( arc );
// write out level_locals_t
arc.WriteObject( &level );
// write out consoles
arc.WriteObject( &consoleManager );
// write out script librarian
arc.WriteObject( &ScriptLib );
// write out gravity paths
arc.WriteObject( &gravPathManager );
// write out paths
arc.WriteObject( &PathManager );
// write out script controller
arc.WriteObject( &Director );
// write out surface manager
arc.WriteObject( &surfaceManager );
// write out Viewmodel manager (for debugging only)
arc.WriteObject( &Viewmodel );
// count the entities
num = 0;
for( i = 0; i < globals.num_edicts; i++ )
{
edict = &g_edicts[ i ];
if ( edict->inuse && edict->entity && !( edict->entity->flags & FL_DONTSAVE ) )
{
num++;
}
}
// write out all the entities
arc.WriteInteger( globals.num_edicts );
arc.WriteInteger( num );
for( i = 0; i < globals.num_edicts; i++ )
{
edict = &g_edicts[ i ];
if ( !edict->inuse || !edict->entity || ( edict->entity->flags & FL_DONTSAVE ) )
{
continue;
}
arc.WriteObject( edict->entity );
}
arc.Close();
}
void G_WriteLevel
(
const char *filename,
qboolean autosave
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
WriteLevel( filename, autosave );
}
/*
=================
ReadLevel
SpawnEntities will already have been called on the
level the same way it was when the level was saved.
That is necessary to get the baselines set up identically.
The server will have cleared all of the world links before
calling ReadLevel.
No clients are connected yet.
=================
*/
void ReadLevel
(
const char *filename
)
{
int i;
int num;
Archiver arc;
int version;
int savegame_version;
LoadingSavegame = true;
// Get rid of anything left over from the last level
G_LevelShutdown();
G_ResetEdicts();
arc.Read( filename );
// read the version number
arc.ReadInteger( &version );
if ( version < GAME_API_VERSION )
{
gi.error( "Savegame from an older version (%d) of Sin.\n", version );
}
else if ( version > GAME_API_VERSION )
{
gi.error( "Savegame from version %d of Sin.\n", version );
}
arc.ReadInteger( &savegame_version );
if ( savegame_version < SAVEGAME_VERSION )
{
gi.error( "Savegame from an older version (%d) of Sin.\n", version );
}
else if ( savegame_version > SAVEGAME_VERSION )
{
gi.error( "Savegame from version %d of Sin.\n", version );
}
// Read in the pending events. These are read in first in case
// later objects need to post events.
G_UnarchiveEvents( arc );
// read level_locals_t
arc.ReadObject( &level );
// read consoles
arc.ReadObject( &consoleManager );
// read script librarian
arc.ReadObject( &ScriptLib );
// read gravity paths
arc.ReadObject( &gravPathManager );
// read paths
arc.ReadObject( &PathManager );
// read script controller
arc.ReadObject( &Director );
// read surface manager
arc.ReadObject( &surfaceManager );
// read Viewmodel manager (for debugging only)
arc.ReadObject( &Viewmodel );
// read all the entities
arc.ReadInteger( &globals.num_edicts );
arc.ReadInteger( &num );
for( i = 0; i < num; i++ )
{
arc.ReadObject();
}
arc.Close();
// call the precache scripts
G_Precache();
LoadingSavegame = false;
}
void G_ReadLevel
(
const char *filename
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
ReadLevel( filename );
}
/*
=================
GetGameAPI
Returns a pointer to the structure with all entry points
and global variables
=================
*/
game_export_t *GetGameAPI
(
game_import_t *import
)
{
gi = *import;
globals.apiversion = GAME_API_VERSION;
globals.Init = G_InitGame;
globals.Shutdown = G_ShutdownGame;
globals.SpawnEntities = G_SpawnEntities;
globals.CreateSurfaces = CreateSurfaces;
globals.WriteGame = G_WriteGame;
globals.ReadGame = G_ReadGame;
globals.WriteLevel = G_WriteLevel;
globals.ReadLevel = G_ReadLevel;
globals.ClientThink = G_ClientThink;
globals.ClientConnect = G_ClientConnect;
globals.ClientUserinfoChanged = G_ClientUserinfoChanged;
globals.ClientDisconnect = G_ClientDisconnect;
globals.ClientBegin = G_ClientBegin;
globals.ClientCommand = G_ClientCommand;
globals.RunFrame = G_RunFrame;
globals.ServerCommand = G_ServerCommand;
globals.edict_size = sizeof(edict_t);
globals.console_size = sizeof(netconsole_t);
globals.conbuffer_size = sizeof(netconbuffer_t);
globals.surface_size = sizeof(netsurface_t);
return &globals;
}
#ifndef GAME_HARD_LINKED
// this is only here so the functions in q_shared.c and q_shwin.c can link
void Sys_Error
(
const char *error,
...
)
{
va_list argptr;
char text[1024];
va_start (argptr, error);
vsprintf (text, error, argptr);
va_end (argptr);
gi.error (ERR_FATAL, "%s", text);
}
void Com_Printf
(
const char *msg,
...
)
{
va_list argptr;
char text[1024];
va_start (argptr, msg);
vsprintf (text, msg, argptr);
va_end (argptr);
gi.dprintf ("%s", text);
}
#endif
//======================================================================
/*
=================
G_ClientEndServerFrames
=================
*/
void G_ClientEndServerFrames
(
void
)
{
int i;
edict_t *ent;
// calc the player views now that all pushing
// and damage has been added
for( i = 0; i < maxclients->value; i++ )
{
ent = g_edicts + 1 + i;
if ( !ent->inuse || !ent->client || !ent->entity )
{
continue;
}
ent->entity->ProcessEvent( EV_ClientEndFrame );
}
}
/*
=================
G_EndDMLevel
The timelimit or fraglimit has been exceeded
=================
*/
void G_EndDMLevel
(
void
)
{
int num;
TriggerChangeLevel *ent;
char *s, *t, *f;
static const char *seps = " ,\n\r";
// stay on same level flag
if ( DM_FLAG( DF_SAME_LEVEL ) )
{
G_BeginIntermission( level.mapname.c_str() );
return;
}
// see if it's in the map list
if ( *sv_maplist->string )
{
s = strdup(sv_maplist->string);
f = NULL;
t = strtok( s, seps );
while ( t != NULL )
{
if ( !stricmp( t, level.mapname.c_str() ) )
{
// it's in the list, go to the next one
t = strtok( NULL, seps );
if ( t == NULL )
{ // end of list, go to first one
if ( f == NULL ) // there isn't a first one, same level
G_BeginIntermission( level.mapname.c_str() );
else
G_BeginIntermission( f );
}
else
{
G_BeginIntermission( t );
}
free(s);
return;
}
if (!f)
{
f = t;
}
t = strtok(NULL, seps);
}
free(s);
}
if ( !level.nextmap.length() )
{
// search for a changelevel
num = G_FindClass( 0, "target_changelevel" );
if ( !num )
{
// the map designer didn't include a changelevel,
// so go back to the same level
G_BeginIntermission( level.mapname.c_str() );
}
else
{
ent = ( TriggerChangeLevel * )G_GetEntity( num );
G_BeginIntermission( ent->Map() );
}
}
}
/*
=================
G_CheckDMRules
=================
*/
void G_CheckDMRules
(
void
)
{
int i;
gclient_t *cl;
if ( level.intermissiontime )
{
return;
}
if ( !deathmatch->value )
{
return;
}
if ( timelimit->value )
{
//### added time reset thinggy
// if ( level.time >= timelimit->value * 60 )
if ( level.time >= (timelimit->value - reset_timeofs) * 60 )
{
gi.bprintf( PRINT_HIGH, "Timelimit hit.\n" );
G_EndDMLevel();
return;
}
}
if ( fraglimit->value )
{
for( i = 0; i < maxclients->value; i++ )
{
cl = game.clients + i;
if ( !g_edicts[ i + 1 ].inuse )
{
continue;
}
if ( cl->resp.score >= fraglimit->value )
{
gi.bprintf( PRINT_HIGH, "Fraglimit hit.\n" );
G_EndDMLevel();
return;
}
}
}
//### check that an informer is set in MFD
if (
((int)dmflags->value & DF_AUTO_INFORMER) &&
(deathmatch->value == DEATHMATCH_MFD || deathmatch->value == DEATHMATCH_MOB)
)
{
int i, num = 0;
qboolean foundinformer;
edict_t *tmpent, *newinf;
// check for there being an informer
foundinformer = false;
for(i = 1; i <= maxclients->value; i++)
{
if(!g_edicts[i].inuse || !g_edicts[i].entity || !g_edicts[i].client->pers.netname)
{
continue;
}
tmpent = &g_edicts[i];
if(tmpent->client->resp.informer)
{
foundinformer = true;
}
// counts up the clients
num++;
}
// need to set someone as the informer
if(!foundinformer)
{
/*
for(i = 1; i <= maxclients->value; i++)
{
if(!g_edicts[i].inuse || !g_edicts[i].entity || !g_edicts[i].client->pers.netname)
{
continue;
}
// count up all the connected clients
num++;
}
*/
// randomly pick a new informer
if(num > 0)
{
num = rand()%num + 1;
for(i = 1; i <= maxclients->value; i++)
{
if(!g_edicts[i].inuse || !g_edicts[i].entity || !g_edicts[i].client->pers.netname)
{
continue;
}
// find the picked player and make him the informer
num--;
if(!num) // found 'em
{
char userinfo[MAX_INFO_STRING];
tmpent = &g_edicts[i];
tmpent->client->resp.informer = true;
foundinformer = false;
// correct the player's model and skin
memcpy(userinfo, tmpent->client->pers.userinfo, sizeof(userinfo));
G_ClientUserinfoChanged(tmpent, userinfo);
newinf = tmpent;
}
}
}
if(!foundinformer)
{
// broadcast a messages about who the new informer is
for(i = 1; i <= maxclients->value; i++)
{
if(!g_edicts[i].inuse || !g_edicts[i].entity || !g_edicts[i].client->pers.netname)
{
continue;
}
tmpent = &g_edicts[i];
}
}
}
}
//###
}
void G_MoveClientToIntermission
(
Entity *ent
)
{
// Display the scores for the client
if ( deathmatch->value || coop->value )
{
ent->client->showinfo = true;
G_DeathmatchScoreboardMessage( ent, NULL );
gi.unicast( ent->edict, true );
}
}
void G_BeginIntermission
(
const char *map
)
{
edict_t *client;
Entity *ent;
Entity *path;
int i,num;
Event *event, event2;
assert( map );
if ( !map )
{
gi.dprintf( "G_BeginIntermission : Null map name\n" );
return;
}
if ( level.missionfailed )
{
// don't allow map changes when a mission has failed
return;
}
if ( level.intermissiontime )
{
// already activated
return;
}
level.intermissiontime = level.time;
if ( level.clearsavegames && ( map[ 0 ] != '*' ) )
{
level.nextmap = str( "*" ) + map;
}
else
{
level.nextmap = map;
}
level.clearsavegames = false;
level.exitintermission = !( deathmatch->value || coop->value );
// find an intermission spot
num = G_FindClass( 0, "info_player_intermission" );
// Only do the camera stuff if the node exists.
if ( num )
{
ent = G_GetEntity( num );
SetCamera( ent );
event = new Event( EV_Camera_Orbit );
// Find the end node
num = G_FindTarget( 0, "endnode1" );
if ( num )
{
path = G_GetEntity( num );
event->AddEntity( path );
ent->ProcessEvent( event );
event = new Event( EV_Camera_JumpCut );
ent->ProcessEvent( event );
}
}
// Display scores for all the clients
for( i = 0; i < maxclients->value; i++ )
{
client = g_edicts + 1 + i;
if (!client->inuse)
continue;
ent = G_GetEntity( client->s.number );
G_MoveClientToIntermission( ent );
}
// tell the script that the player's not ready so that if we return to this map,
// we can do something about it.
Director.PlayerNotReady();
}
/*
=============
G_ExitLevel
=============
*/
void G_ExitLevel
(
void
)
{
char command[ 256 ];
int j;
edict_t *ent;
// kill the sounds
Com_sprintf( command, sizeof( command ), "stopsound\n" );
gi.AddCommandString( command );
Com_sprintf( command, sizeof( command ), "gamemap \"%s\"\n", level.nextmap.c_str() );
gi.AddCommandString( command );
level.nextmap = "";
level.exitintermission = 0;
level.intermissiontime = 0;
G_SaveClientData();
// Tell all the client that the level is done
for( j = 1; j <= game.maxclients; j++ )
{
ent = &g_edicts[ j ];
if ( !ent->inuse || !ent->entity )
{
continue;
}
ent->entity->ProcessEvent( EV_Player_EndLevel );
}
G_ClientEndServerFrames();
// tell the script that the player's not ready so that if we return to this map,
// we can do something about it.
Director.PlayerNotReady();
}
void G_DrawCSystem
(
void
)
{
Vector pos;
Vector ang;
Vector f;
Vector r;
Vector u;
Vector v;
pos.x = csys_posx->value;
pos.y = csys_posy->value;
pos.z = csys_posz->value;
ang.x = csys_x->value;
ang.y = csys_y->value;
ang.z = csys_z->value;
ang.AngleVectors( &f, &r, &u );
G_DebugLine( pos, pos + f * 48, 1.0, 0, 0, 1 );
G_DebugLine( pos, pos + r * 48, 0, 1.0, 0, 1 );
G_DebugLine( pos, pos + u * 48, 0, 0, 1.0, 1 );
}
/*
================
G_RunFrame
Advances the world by 0.1 seconds
================
*/
void G_RunFrame
(
void
)
{
edict_t *edict;
Entity *ent;
int num;
qboolean showentnums;
int start;
int end;
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
level.framenum++;
level.time = level.framenum * FRAMETIME;
if ( g_showmem->value )
{
DisplayMemoryUsage();
}
// exit intermissions
if ( level.exitintermission )
{
G_ExitLevel();
return;
}
// if the player in the server and the mission has failed, show the loadmenu
if ( g_edicts[ 1 ].inuse && level.missionfailed && ( level.missionfailedtime < level.time ) )
{
// restart the entire server
gi.AddCommandString( "con_clearfade\n" );
gi.AddCommandString( "menu_loadgame\n" );
return;
}
path_checksthisframe = 0;
// Reset debug lines
G_InitDebugLines();
// testing coordinate system
if ( csys_draw->value )
{
G_DrawCSystem();
}
PathManager.ShowNodes();
// don't show entnums during deathmatch
showentnums = ( sv_showentnums->value && ( !deathmatch->value || sv_cheats->value ) );
// Process most of the events before the physics are run
// so that we can affect the physics immediately
G_ProcessPendingEvents();
//
// treat each object in turn
//
for( edict = active_edicts.next, num = 0; edict != &active_edicts; edict = level.next_edict, num++ )
{
assert( edict );
assert( edict->inuse );
assert( edict->entity );
level.next_edict = edict->next;
// Paranoia - It's a way of life
assert( num <= MAX_EDICTS );
if ( num > MAX_EDICTS )
{
gi.dprintf( "Possible infinite loop in G_RunFrame.\n");
break;
}
ent = edict->entity;
level.current_entity = ent;
if ( g_timeents->value )
{
start = G_Milliseconds();
G_RunEntity( ent );
end = G_Milliseconds();
if ( g_timeents->value <= ( end - start ) )
{
G_DebugPrintf( "%d: '%s'(%d) : %d\n", level.framenum, ent->targetname.c_str(), ent->entnum, end - start );
}
}
else
{
G_RunEntity( ent );
}
if ( showentnums )
{
G_DrawDebugNumber( ent->worldorigin + Vector( 0, 0, ent->maxs.z + 2 ), ent->entnum, 2, 1, 1, 0 );
}
}
// Process any pending events that got posted during the physics code.
G_ProcessPendingEvents();
// see if it is time to end a deathmatch
G_CheckDMRules();
// build the playerstate_t structures for all players
G_ClientEndServerFrames();
// see if we should draw the bounding boxes
G_ClientDrawBoundingBoxes();
// show how many traces the game code is doing
if ( sv_traceinfo->value )
{
if ( sv_traceinfo->value == 3 )
{
G_DebugPrintf( "%0.1f : Total traces %d\n", level.time, sv_numtraces );
}
else
{
gi.dprintf( "%0.1f : Total traces %d\n", level.time, sv_numtraces );
}
}
// reset out count of the number of game traces
sv_numtraces = 0;
}
void G_ClientThink
(
edict_t *ent,
usercmd_t *ucmd
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
if ( ent->entity )
{
current_ucmd = ucmd;
level.current_entity = ent->entity;
ent->entity->ProcessEvent( EV_ClientMove );
current_ucmd = NULL;
}
}
/*
===========
G_PutClientInServer
Called when a player connects to a server
============
*/
void G_PutClientInServer
(
edict_t *ent
)
{
if ( !ent->entity )
{
G_InitSpawnArguments();
G_SetSpawnArg( "classname", "player" );
game.force_entnum = true;
game.spawn_entnum = ent->s.number;
G_CallSpawn();
game.force_entnum = false;
if ( ent->entity && ent->entity->isSubclassOf( Player ) )
{
( ( Player * )ent->entity )->Init();
}
}
}
/*
===========
G_ClientBegin
called when a client has finished connecting, and is ready
to be placed into the game. This will happen every level load.
============
*/
void G_ClientBegin
(
edict_t *ent,
qboolean loadgame
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
if ( ent->inuse && ent->entity )
{
// the client has cleared the client side viewangles upon
// connecting to the server, which is different than the
// state when the game is saved, so we need to compensate
// with deltaangles
ent->entity->SetDeltaAngles();
}
else
{
// a spawn point will completely reinitialize the entity
G_InitEdict( ent );
G_InitClientResp( ent->client );
G_PutClientInServer( ent );
}
if ( level.intermissiontime && ent->entity )
{
G_MoveClientToIntermission( ent->entity );
}
else
{
// send effect if in a multiplayer game
if ( game.maxclients > 1 )
{
gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
}
}
// make sure all view stuff is valid
if ( ent->entity )
{
ent->entity->ProcessEvent( EV_ClientEndFrame );
}
}
void FixDeadBodiesForPlayer
(
edict_t *ent
)
{
int i,playernum;
edict_t *body;
if ( !deathmatch->value )
return;
playernum = ent-g_edicts-1;
for ( i=0; i<BODY_QUEUE_SIZE; i++ )
{
body = &g_edicts[ ( int )maxclients->value + 1 + i ];
if ( ( body->s.skinnum == playernum ) && ( body->s.modelindex != ent->s.modelindex ) )
{
body->s.renderfx |= RF_DONTDRAW;
body->s.skinnum = -1;
}
}
}
/*
===========
G_ClientUserInfoChanged
called whenever the player updates a userinfo variable.
The game can override any of the settings in place
(forcing skins or names, etc) before copying it off.
============
*/
void G_ClientUserinfoChanged
(
edict_t *ent,
const char *userinfo
)
{
const char *s;
int playernum;
Player *player;
str model;
float fov;
Event *ev;
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
player = ( Player * )ent->entity;
ent->client->ps.pmove.pm_flags &= ~PMF_OLDNOCLIP;
s = Info_ValueForKey( userinfo, "cl_oldnoclip" );
if (strlen(s))
{
if ( atoi(s) )
{
ent->client->ps.pmove.pm_flags |= PMF_OLDNOCLIP;
}
}
// set name
s = Info_ValueForKey( userinfo, "name" );
strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1);
// Don't allow zero length names
if ( !strlen( ent->client->pers.netname ) )
strcpy( ent->client->pers.netname, "Blade" );
//### added MFD dm modes
if(deathmatch->value == DEATHMATCH_MFD || deathmatch->value == DEATHMATCH_MOB)
{
if(ent->client->resp.informer)
s = informerskin->string;
else
s = mobskin->string;
strncpy(ent->client->pers.skin, s, sizeof( ent->client->pers.skin) - 1);
// Don't allow zero length skins
if(!strlen(ent->client->pers.skin))
{
if(ent->client->resp.informer)
strcpy( ent->client->pers.skin, MFD_INFORMERSKIN );
else
strcpy( ent->client->pers.skin, MFD_MOBSKIN );
}
// set model only if player not a mutant
if ( !( player && ( player->flags & (FL_MUTANT|FL_SP_MUTANT) ) ) )
{
if(ent->client->resp.informer)
s = informermodel->string;
else
s = mobmodel->string;
COM_StripExtension(s, ent->client->pers.model);
strcat(ent->client->pers.model, ".def");
// Don't allow zero length models
if(!strlen(ent->client->pers.model))
{
if(ent->client->resp.informer)
strcpy(ent->client->pers.model, MFD_INFORMERMODEL);
else
strcpy(ent->client->pers.model, MFD_MOBMODEL);
}
// Only allow models that the server sets up in the players script file
model = ent->client->pers.model;
if(!game.ValidPlayerModels.ObjectInList(model))
{
// Fall back to default
if(ent->client->resp.informer)
strcpy(ent->client->pers.model, MFD_INFORMERMODEL);
else
strcpy(ent->client->pers.model, MFD_MOBMODEL);
}
// Call the player's setModel function if he exists
// Prepend 'models/' to make things easier
if ( !strchr( ent->client->pers.model, '*' ) && !strchr( ent->client->pers.model, '\\' ) && !strchr( ent->client->pers.model, '/' ) )
{
model = "models/";
model += ent->client->pers.model;
}
else
{
model = ent->client->pers.model;
}
if ( player && !player->deadflag && ( player->model != model ) )
{
player->setModel( model );
if(player->client->resp.informer)
player->TempAnim("pain", NULL);
else
player->TempAnim("pain", NULL);
}
}
}
else
{
//###
if ( deathmatch->value )
{
// set skin
s = Info_ValueForKey( userinfo, "skin" );
strncpy( ent->client->pers.skin, s, sizeof( ent->client->pers.skin ) - 1 );
}
// Don't allow zero length skins
if ( !strlen( ent->client->pers.skin ) )
{
strcpy( ent->client->pers.skin, "blade_base" );
}
// set model only if player not a mutant
if ( !( player && ( player->flags & (FL_MUTANT|FL_SP_MUTANT) ) ) )
{
s = Info_ValueForKey( userinfo, "model" );
COM_StripExtension( s, ent->client->pers.model );
strcat( ent->client->pers.model, ".def" );
// Don't allow zero length models
if ( !strlen( ent->client->pers.model ) )
{
strcpy( ent->client->pers.model, "pl_blade.def" );
}
// Only allow models that the server sets up in the players script file
model = ent->client->pers.model;
if ( !game.ValidPlayerModels.ObjectInList( model ) )
{
// Fall back to blade
strcpy( ent->client->pers.model, "pl_blade.def" );
}
#ifdef SIN_DEMO
if ( 1 )
#else
// Always be blade in single player
if ( !deathmatch->value )
#endif
{
strcpy( ent->client->pers.model, "pl_blade.def" );
}
// Call the player's setModel function if he exists
// Prepend 'models/' to make things easier
if ( !strchr( ent->client->pers.model, '*' ) && !strchr( ent->client->pers.model, '\\' ) && !strchr( ent->client->pers.model, '/' ) )
{
model = "models/";
model += ent->client->pers.model;
}
else
{
model = ent->client->pers.model;
}
if ( player && !player->deadflag && ( player->model != model ) )
{
player->setModel( model );
player->RandomAnimate( "idle", NULL );
}
}
} //###
//### hoverbike player config stuff
// always use the hoverbike prototype model in single player
if(!deathmatch->value)
{
strcpy( ent->client->pers.bikemodel, "bike_prototype.def" );
strcpy( ent->client->pers.bikeskin, "hover_02a_r" );
}
else
{
// set skin
s = Info_ValueForKey( userinfo, "bikeskin" );
strncpy( ent->client->pers.bikeskin, s, sizeof( ent->client->pers.bikeskin ) - 1 );
// Don't allow zero length skins
if ( !strlen( ent->client->pers.bikeskin ) )
{
strcpy( ent->client->pers.bikeskin, "hover_02a_r" );
}
// set model
s = Info_ValueForKey( userinfo, "bikemodel" );
COM_StripExtension( s, ent->client->pers.bikemodel );
strcat( ent->client->pers.bikemodel, ".def" );
// Don't allow zero length models
if ( !strlen( ent->client->pers.bikemodel ) )
{
strcpy( ent->client->pers.bikemodel, "bike_prototype.def" );
}
// Only allow models that the server sets up in the hoverbikes script file
model = ent->client->pers.bikemodel;
if ( !game.ValidBikeModels.ObjectInList( model ) )
{
// Fall back to default
strcpy( ent->client->pers.bikemodel, "bike_prototype.def" );
}
}
//###
// Fov
if ( player )
{
fov = atof( Info_ValueForKey( userinfo, "fov" ) );
if ( fov < 1 )
{
fov = 90;
}
else if ( fov > 160 )
{
fov = 160;
}
ev = new Event( EV_Player_Fov );
ev->AddFloat( fov );
player->ProcessEvent( ev );
}
// Player number
playernum = ent - g_edicts - 1;
// combine name, skin and model into a configstring
gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s\\%s",
ent->client->pers.netname,
ent->client->pers.model,
ent->client->pers.skin));
//###
// combine hoverbike skin and model into a configstring
gi.configstring (CS_BIKESKINS+playernum, va("%s\\%s",
ent->client->pers.bikemodel,
ent->client->pers.bikeskin));
//###
// handedness
s = Info_ValueForKey( userinfo, "hand" );
if ( strlen( s ) )
{
ent->client->pers.hand = atoi( s );
}
//###
// call the hoverbike's setModel function if it exists
if(player && !player->deadflag)
{
Hoverbike *bike;
bike = player->GetHoverbike();
if(bike && !bike->deadflag)
{
if(!strchr(ent->client->pers.bikemodel, '*') && !strchr(ent->client->pers.bikemodel, '\\') && !strchr(ent->client->pers.bikemodel, '/'))
{
model = "models/";
model += ent->client->pers.bikemodel;
}
else
{
model = ent->client->pers.bikemodel;
}
if(bike->model != model)
{
bike->setModel(model);
bike->RandomAnimate("idle", NULL);
}
// make sure the player is in the riding pose
player->RandomAnimate( "ride", NULL );
}
}
if(player)
{
// auto weapon switching toggle
fov = atof(Info_ValueForKey(userinfo, "weaponswitch"));
ev = new Event(EV_Player_WeaponSwitch);
ev->AddFloat(fov);
player->ProcessEvent(ev);
// weapon select override
fov = atof(Info_ValueForKey(userinfo, "weaponoverride"));
ev = new Event(EV_Player_WeaponOverride);
ev->AddFloat(fov);
player->ProcessEvent(ev);
}
//###
// save off the userinfo in case we want to check something later
strncpy( ent->client->pers.userinfo, userinfo, sizeof( ent->client->pers.userinfo )-1 );
// Hide the bodies that are associated with this player so that
// no weird animations show up on the client
if ( ( !LoadingSavegame ) && ( deathmatch->value || coop->value ) )
FixDeadBodiesForPlayer( ent );
}
/*
===========
G_ClientConnect
Called when a player begins connecting to the server.
The game can refuse entrance to a client by returning false.
If the client is allowed, the connection process will continue
and eventually get to ClientBegin()
Changing levels will NOT cause this to be called again.
============
*/
qboolean G_ClientConnect
(
edict_t *ent,
const char *userinfo
)
{
const char *value;
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
// check to see if they are on the banned IP list
value = Info_ValueForKey( userinfo, "ip" );
if ( SV_FilterPacket( value ) )
{
return false;
}
// check for a password
value = Info_ValueForKey( userinfo, "password" );
if ( strcmp( password->string, value ) != 0 )
{
return false;
}
// if there is already a body waiting for us (a loadgame), just
// take it, otherwise spawn one from scratch
if ( ent->inuse == false )
{
// clear the respawning variables
G_InitClientResp( ent->client );
if ( !game.autosaved )//|| !ent->client->pers.weapon)
{
G_InitClientPersistant( ent->client );
}
}
G_ClientUserinfoChanged( ent, userinfo );
if ( game.maxclients > 1 )
{
gi.printf( "%s connected\n", ent->client->pers.netname );
}
LoadingServer = false;
return true;
}
/*
===========
G_ClientDisconnect
called when a player drops from the server
============
*/
void G_ClientDisconnect
(
edict_t *ent
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
if ( ( !ent->client ) || ( !ent->entity ) )
{
return;
}
delete ent->entity;
ent->entity = NULL;
}
/*
==================
Cmd_Say_f
==================
*/
void G_Say
(
edict_t *ent,
qboolean team,
qboolean arg0
)
{
int j;
edict_t *other;
const char *p;
char text[ 2048 ];
if ( gi.argc() < 2 && !arg0 )
{
return;
}
if ( !DM_FLAG( DF_MODELTEAMS | DF_SKINTEAMS ) )
{
team = false;
}
if ( team )
{
Com_sprintf( text, sizeof( text ), "(%s): ", ent->client->pers.netname );
}
else
{
Com_sprintf( text, sizeof( text ), "%s: ", ent->client->pers.netname );
}
if ( arg0 )
{
strcat( text, gi.argv( 0 ) );
strcat( text, " " );
strcat( text, gi.args() );
}
else
{
p = gi.args();
if ( *p == '"' )
{
p++;
strcat( text, p );
text[ strlen( text ) - 1 ] = 0;
}
else
{
strcat( text, p );
}
}
// don't let text be too long for malicious reasons
if ( strlen( text ) > 150 )
{
text[ 150 ] = 0;
}
strcat( text, "\n" );
if ( dedicated->value )
{
gi.cprintf( NULL, PRINT_CHAT, "%s", text );
}
for( j = 1; j <= game.maxclients; j++ )
{
other = &g_edicts[ j ];
if ( !other->inuse || !other->client )
{
continue;
}
#if 0
if ( team )
{
if ( !OnSameTeam( ent, other ) )
{
continue;
}
}
#endif
gi.cprintf( other, PRINT_CHAT, "%s", text );
}
}
void ClientCommand
(
edict_t *ent
)
{
const char *cmd;
int i;
int n;
Event *ev;
qboolean found;
cvar_t *cvar;
float t;
if ( !ent->client || !ent->entity )
{
// not fully in game yet
return;
}
cmd = gi.argv( 0 );
n = gi.argc();
if ( !Q_strcasecmp( cmd, "say" ) )
{
G_Say( ent, false, false );
return;
}
else if ( game.maxclients == 1 )
{
// only allow these commands when we only have one client (most likely only a local game)
if ( !Q_strcasecmp( cmd, "add" ) )
{
if ( n < 3 )
{
gi.cprintf( ent, PRINT_HIGH, "Syntax: add [var name] [amount].\n" );
return;
}
cvar = gi.cvar( gi.argv( 1 ), "0", 0 );
t = cvar->value + atof( gi.argv( 2 ) );
gi.cvar_set( gi.argv( 1 ), va( "%f", t ) );
gi.dprintf( "%s = %f\n", gi.argv( 1 ), cvar->value );
return;
}
else if ( !Q_strcasecmp( cmd, "eventlist" ) )
{
const char *mask;
mask = NULL;
if ( n > 1 )
{
mask = gi.argv( 1 );
}
Event::ListCommands( mask );
return;
}
else if ( !Q_strcasecmp( cmd, "classlist" ) )
{
listAllClasses();
return;
}
else if ( !Q_strcasecmp( cmd, "classtree" ) )
{
if ( n > 1 )
{
listInheritanceOrder( gi.argv( 1 ) );
}
else
{
gi.cprintf( ent, PRINT_HIGH, "Syntax: classtree [classname].\n" );
}
return;
}
else if ( !Q_strcasecmp( cmd, "showvar" ) )
{
ScriptVariable *var;
var = Director.GetExistingVariable( gi.argv( 1 ) );
if ( var )
{
gi.cprintf( ent, PRINT_HIGH, "%s = '%s'\n", gi.argv( 1 ), var->stringValue() );
}
else
{
gi.cprintf( ent, PRINT_HIGH, "Variable '%s' does not exist.\n", gi.argv( 1 ) );
}
return;
}
}
found = false;
if ( Event::Exists( cmd ) )
{
ev = new Event( cmd );
ev->SetSource( EV_FROM_CONSOLE );
ev->SetConsoleEdict( ent );
for( i = 1; i < n; i++ )
{
ev->AddToken( gi.argv( i ) );
}
if ( !Q_strncasecmp( cmd, "view", 4 ) )
{
found = Viewmodel.ProcessEvent( ev );
}
else if ( !Q_strncasecmp( cmd, "ai_", 2 ) )
{
found = PathManager.ProcessEvent( ev );
}
else if ( !Q_strncasecmp( cmd, "console", 7 ) )
{
found = consoleManager.ProcessEvent( ev );
}
//### added for sprite gun
else if (!Q_strncasecmp(cmd, "spg_", 4))
{
found = Spritecontrol.ProcessEvent(ev);
}
//###
else
{
found = ent->entity->ProcessEvent( ev );
}
}
if ( !found )
{
// anything that doesn't match a command will be a chat
G_Say( ent, false, true );
}
}
void G_ClientCommand
(
edict_t *ent
)
{
// If we get an error, call the server's error function
if ( setjmp( G_AbortGame ) )
{
G_ExitWithError();
}
//FIXME
// setjmp doesn't seem to like to work inside the above function, so I've broken it out,
// which makes it happy. Wierd.
ClientCommand( ent );
}
/*
==================
G_DeathmatchScoreboardMessage
==================
*/
void G_DeathmatchScoreboardMessage
(
Entity *ent,
Entity *killer
)
{
char entry[ 1024 ];
char string[ 1400 ];
int stringlength;
int i, j, k;
int sorted[ MAX_CLIENTS ];
int sortedscores[ MAX_CLIENTS ];
int score, total;
int x,y;
gclient_t *cl;
edict_t *cl_ent, *killeredict, *entedict;
const char *tag;
killeredict = NULL;
entedict = NULL;
if ( killer )
{
killeredict = killer->edict;
}
if ( ent )
{
entedict = ent->edict;
}
// sort the clients by score
total = 0;
for( i = 0; i < game.maxclients; i++ )
{
cl_ent = g_edicts + 1 + i;
if ( !cl_ent->inuse )
{
continue;
}
score = game.clients[ i ].resp.score;
for( j = 0; j < total; j++ )
{
if ( score > sortedscores[ j ] )
break;
}
for( k = total; k > j; k-- )
{
sorted[ k ] = sorted[ k - 1 ];
sortedscores[ k ] = sortedscores[ k - 1 ];
}
sorted[ j ] = i;
sortedscores[ j ] = score;
total++;
}
// print level name and exit rules
string[ 0 ] = 0;
stringlength = strlen( string );
// add the clients in sorted order
if ( total > 12 )
{
total = 12;
}
for( i = 0; i < total; i++ )
{
cl = &game.clients[ sorted[ i ] ];
cl_ent = g_edicts + 1 + sorted[ i ];
x = (i>=6) ? 160 : 0;
y = 32 + 32 * (i%6);
// Add a tag to the player and the killer
if (cl_ent == entedict)
tag = "tag1";
else if (cl_ent == killeredict)
tag = "tag2";
else
tag = NULL;
// send the layout
Com_sprintf( entry, sizeof( entry ),
"client %i %i %i %i %i %i ",
x, y, sorted[ i ], cl->resp.score, cl->ping, ( level.framenum - cl->resp.enterframe ) / 600 );
// Put the tag on the end of the client command
if ( tag )
strcat( entry, va( "1 %s ",tag ) );
else
strcat( entry, va( "0 " ) );
j = strlen( entry );
if ( stringlength + j > 1024 )
{
break;
}
strcpy( string + stringlength, entry );
stringlength += j;
}
gi.WriteByte( svc_layout );
gi.WriteString( string );
}
/*
==================
G_DeathmatchScoreboard
Draw instead of help message.
Note that it isn't that hard to overflow the 1400 byte message limit!
==================
*/
void G_DeathmatchScoreboard
(
Entity *ent
)
{
G_DeathmatchScoreboardMessage( ent, ent->enemy );
gi.unicast( ent->edict, true );
}
/*
=================
G_ClientDrawBoundingBoxes
=================
*/
void G_ClientDrawBoundingBoxes
(
void
)
{
edict_t *edict;
Entity *ent;
Vector eye;
// don't show bboxes during deathmatch
if ( !sv_showbboxes->value || ( deathmatch->value && !sv_cheats->value ) )
{
return;
}
edict = g_edicts + 1 + 0;
ent = edict->entity;
if ( ent )
{
eye = ent->worldorigin;
ent = findradius( NULL, eye, 1000 );
while( ent )
{
switch ((int)sv_showbboxes->value)
{
case 1:
if ( ent->edict != edict && ent->edict->s.solid)
{
if (ent->bindmaster)
G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 0, 1, 0, 1 );
else
G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 1, 1, 0, 1 );
}
break;
case 2:
if ( ent->edict != edict && ent->edict->s.solid)
{
if (ent->bindmaster)
G_DebugBBox( "0 0 0", ent->edict->absmin, ent->edict->absmax, 0, 0, 1, 1 );
else
G_DebugBBox( "0 0 0", ent->edict->absmin, ent->edict->absmax, 1, 0, 1, 1 );
}
break;
case 3:
if ( ent->edict->s.modelindex && !(ent->edict->s.renderfx & RF_DONTDRAW) )
G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 1, 1, 0, 1 );
break;
case 4:
default:
G_DebugBBox( ent->worldorigin, ent->mins, ent->maxs, 1, 1, 0, 1 );
break;
case 5:
if ( ent->edict->s.solid )
{
G_DebugBBox( ent->worldorigin, ent->edict->fullmins, ent->edict->fullmaxs, 1, 1, 1, 1 );
}
break;
}
ent = findradius( ent, eye, 1000 );
}
}
}
CLASS_DECLARATION( Class, game_locals_t, NULL );
ResponseDef game_locals_t::Responses[] =
{
{ NULL, NULL }
};
game_locals_t::game_locals_t()
{
clients = NULL;
autosaved = false;
spawnpoint = "";
maxentities = 0;
maxclients = 0;
maxconsoles = 0;
maxsurfaces = 0;
force_entnum = false;
spawn_entnum = 0;
ValidPlayerModels.FreeObjectList();
ValidBikeModels.FreeObjectList(); //###
}
EXPORT_FROM_DLL void game_locals_t::Archive
(
Archiver &arc
)
{
int i;
int num;
Class::Archive( arc );
arc.WriteBoolean( autosaved );
arc.WriteString( spawnpoint );
arc.WriteBoolean( force_entnum );
arc.WriteInteger( spawn_entnum );
// List of valid player models loaded from players global scriptfile
num = ValidPlayerModels.NumObjects();
arc.WriteInteger( num );
for( i = 1; i <= num; i++ )
{
arc.WriteString( ValidPlayerModels.ObjectAt( i ) );
}
arc.WriteInteger( maxentities );
arc.WriteInteger( maxclients );
arc.WriteInteger( maxconsoles );
arc.WriteInteger( maxsurfaces );
for( i = 0; i < maxclients; i++ )
{
G_WriteClient( arc, &clients[ i ] );
}
//### List of valid hoverbikes models loaded from hoverbikes global scriptfile
num = ValidBikeModels.NumObjects();
arc.WriteInteger(num);
for(i = 1; i <= num; i++)
{
arc.WriteString(ValidBikeModels.ObjectAt(i));
}
//###
}
EXPORT_FROM_DLL void game_locals_t::Unarchive
(
Archiver &arc
)
{
int i;
int num;
str modelname;
Class::Unarchive( arc );
arc.ReadBoolean( &autosaved );
arc.ReadString( &spawnpoint );
arc.ReadBoolean( &force_entnum );
arc.ReadInteger( &spawn_entnum );
// Load list of valid player models
arc.ReadInteger( &num );
for( i = 1; i <= num; i++ )
{
arc.ReadString( &modelname );
ValidPlayerModels.AddObject( modelname );
}
arc.ReadInteger( &maxentities );
arc.ReadInteger( &maxclients );
arc.ReadInteger( &maxconsoles );
arc.ReadInteger( &maxsurfaces );
G_AllocGameData();
for( i = 0; i < maxclients; i++ )
{
G_ReadClient( arc, &clients[ i ] );
}
//### Load list of valid player models
arc.ReadInteger(&num);
for(i = 1; i <= num; i++)
{
arc.ReadString(&modelname);
ValidBikeModels.AddObject(modelname);
}
//###
}
CLASS_DECLARATION( Class, level_locals_t, NULL );
ResponseDef level_locals_t::Responses[] =
{
{ NULL, NULL }
};
level_locals_t::level_locals_t()
{
framenum = 0;
time = 0;
level_name = "";
mapname = "";
nextmap = "";
playerfrozen = false;
intermissiontime = 0;
exitintermission = 0;
next_edict = NULL;
total_secrets = 0;
found_secrets = 0;
current_entity = NULL;
memset( &impact_trace, 0, sizeof( impact_trace ) );
body_queue = 0;
earthquake = 0;
clearsavegames = false;
cinematic = false;
no_jc = false;
water_color = vec_zero;
lightvolume_color = vec_zero;
lava_color = vec_zero;
water_alpha = lightvolume_alpha = lava_alpha = 0;
training = false;
airclamp = true;
missionfailed = false;
missionfailedtime = 0;
}
EXPORT_FROM_DLL void level_locals_t::Archive
(
Archiver &arc
)
{
Class::Archive( arc );
arc.WriteInteger( framenum );
arc.WriteFloat( time );
arc.WriteString( level_name );
arc.WriteString( mapname );
arc.WriteString( nextmap );
arc.WriteBoolean( playerfrozen );
arc.WriteFloat( intermissiontime );
arc.WriteInteger( exitintermission );
arc.WriteInteger( total_secrets );
arc.WriteInteger( found_secrets );
arc.WriteInteger( body_queue );
arc.WriteFloat( earthquake );
arc.WriteBoolean( clearsavegames );
arc.WriteBoolean( cinematic );
arc.WriteBoolean( no_jc );
arc.WriteVector( water_color );
arc.WriteVector( lightvolume_color );
arc.WriteVector( lava_color );
arc.WriteFloat( water_alpha );
arc.WriteFloat( lightvolume_alpha );
arc.WriteFloat( lava_alpha );
arc.WriteBoolean( airclamp );
arc.WriteBoolean( training );
arc.WriteBoolean( missionfailed );
arc.WriteFloat( missionfailedtime );
//### added checkpoint stuff
arc.WriteFloat(fastest_lap);
arc.WriteString(cp_sounds_script);
arc.WriteFloat(cp_num);
arc.WriteBoolean(cp_anyorder);
arc.WriteBoolean(bikesonly);
arc.WriteBoolean(bodiesfade);
arc.WriteInteger(lavadamage);
//###
}
EXPORT_FROM_DLL void level_locals_t::Unarchive
(
Archiver &arc
)
{
Class::Unarchive( arc );
arc.ReadInteger( &framenum );
arc.ReadFloat( &time );
arc.ReadString( &level_name );
arc.ReadString( &mapname );
arc.ReadString( &nextmap );
arc.ReadBoolean( &playerfrozen );
arc.ReadFloat( &intermissiontime );
arc.ReadInteger( &exitintermission );
// not archived since we can't save mid-frame
next_edict = NULL;
arc.ReadInteger( &total_secrets );
arc.ReadInteger( &found_secrets );
// not archived since we can't save mid-frame
current_entity = NULL;
memset( &impact_trace, 0, sizeof( impact_trace ) );
arc.ReadInteger( &body_queue );
arc.ReadFloat( &earthquake );
arc.ReadBoolean( &clearsavegames );
arc.ReadBoolean( &cinematic );
arc.ReadBoolean( &no_jc );
arc.ReadVector( &water_color );
arc.ReadVector( &lightvolume_color );
arc.ReadVector( &lava_color );
arc.ReadFloat( &water_alpha );
arc.ReadFloat( &lightvolume_alpha );
arc.ReadFloat( &lava_alpha );
arc.ReadBoolean( &airclamp );
arc.ReadBoolean( &training );
arc.ReadBoolean( &missionfailed );
arc.ReadFloat( &missionfailedtime );
//### added checkpoint stuff
arc.ReadFloat(&fastest_lap);
arc.ReadString(&cp_sounds_script);
arc.ReadFloat(&cp_num);
arc.ReadBoolean(&cp_anyorder);
arc.ReadBoolean(&bikesonly);
arc.ReadBoolean(&bodiesfade);
arc.ReadInteger(&lavadamage);
//###
}
/*
==============================================================================
PACKET FILTERING
You can add or remove addresses from the filter list with:
addip <ip>
removeip <ip>
The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40".
Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host.
listip
Prints the current list of filters.
writeip
Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion.
filterban <0 or 1>
If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting.
If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network.
==============================================================================
*/
typedef struct
{
unsigned mask;
unsigned compare;
} ipfilter_t;
#define MAX_IPFILTERS 1024
ipfilter_t ipfilters[ MAX_IPFILTERS ];
int numipfilters;
/*
=================
StringToFilter
=================
*/
static qboolean StringToFilter
(
const char *s,
ipfilter_t *f
)
{
char num[ 128 ];
int i;
int j;
byte b[ 4 ];
byte m[ 4 ];
for( i = 0; i < 4; i++ )
{
b[ i ] = 0;
m[ i ] = 0;
}
for( i = 0; i < 4; i++ )
{
if ( *s < '0' || *s > '9' )
{
gi.cprintf( NULL, PRINT_HIGH, "Bad filter address: %s\n", s );
return false;
}
j = 0;
while( *s >= '0' && *s <= '9' )
{
num[ j++ ] = *s++;
}
num[ j ] = 0;
b[ i ] = atoi( num );
if ( b[ i ] != 0 )
{
m[ i ] = 255;
}
if ( !*s )
{
break;
}
s++;
}
f->mask = *( unsigned * )m;
f->compare = *( unsigned * )b;
return true;
}
/*
=================
SV_FilterPacket
=================
*/
qboolean SV_FilterPacket
(
const char *from
)
{
int i;
unsigned in;
byte m[ 4 ];
const char *p;
i = 0;
p = from;
while( *p && i < 4 )
{
m[ i ] = 0;
while( *p >= '0' && *p <= '9' )
{
m[ i ] = m[ i ] * 10 + ( *p - '0' );
p++;
}
if ( !*p || *p == ':' )
{
break;
}
i++;
p++;
}
in = *( unsigned * )m;
for( i = 0; i < numipfilters; i++ )
{
if ( ( in & ipfilters[ i ].mask ) == ipfilters[ i ].compare )
{
return ( int )filterban->value;
}
}
return !( int )filterban->value;
}
/*
=================
SV_AddIP_f
=================
*/
void SVCmd_AddIP_f
(
void
)
{
int i;
if ( gi.argc() < 3 )
{
gi.cprintf( NULL, PRINT_HIGH, "Usage: addip <ip-mask>\n" );
return;
}
for( i = 0; i < numipfilters; i++ )
{
if ( ipfilters[ i ].compare == 0xffffffff )
{
// free spot
break;
}
}
if ( i == numipfilters )
{
if ( numipfilters == MAX_IPFILTERS )
{
gi.cprintf( NULL, PRINT_HIGH, "IP filter list is full\n" );
return;
}
numipfilters++;
}
if ( !StringToFilter( gi.argv( 2 ), &ipfilters[ i ] ) )
{
ipfilters[ i ].compare = 0xffffffff;
}
}
/*
=================
SV_RemoveIP_f
=================
*/
void SVCmd_RemoveIP_f
(
void
)
{
ipfilter_t f;
int i;
int j;
if ( gi.argc() < 3 )
{
gi.cprintf( NULL, PRINT_HIGH, "Usage: sv removeip <ip-mask>\n" );
return;
}
if ( !StringToFilter( gi.argv( 2 ), &f ) )
{
return;
}
for( i = 0; i < numipfilters; i++ )
{
if ( ( ipfilters[ i ].mask == f.mask ) && ( ipfilters[ i ].compare == f.compare ) )
{
for ( j = i + 1; j < numipfilters; j++ )
{
ipfilters[ j - 1 ] = ipfilters[ j ];
}
numipfilters--;
gi.cprintf( NULL, PRINT_HIGH, "Removed.\n" );
return;
}
}
gi.cprintf( NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv( 2 ) );
}
/*
=================
SV_ListIP_f
=================
*/
void SVCmd_ListIP_f
(
void
)
{
int i;
byte b[ 4 ];
gi.cprintf( NULL, PRINT_HIGH, "Filter list:\n" );
for( i = 0; i < numipfilters; i++ )
{
*( unsigned * )b = ipfilters[ i ].compare;
gi.cprintf( NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ] );
}
}
/*
=================
SV_WriteIP_f
=================
*/
void SVCmd_WriteIP_f
(
void
)
{
FILE *f;
char name[ MAX_OSPATH ];
byte b[ 4 ];
int i;
cvar_t *game;
game = gi.cvar( "game", "", 0 );
if ( !*game->string )
{
sprintf( name, "%s/listip.cfg", GAMEVERSION );
}
else
{
sprintf( name, "%s/listip.cfg", game->string );
}
gi.cprintf( NULL, PRINT_HIGH, "Writing %s.\n", name );
f = fopen( name, "wb" );
if ( !f )
{
gi.cprintf( NULL, PRINT_HIGH, "Couldn't open %s\n", name );
return;
}
fprintf( f, "set filterban %d\n", ( int )filterban->value );
for( i = 0; i < numipfilters; i++ )
{
*( unsigned * )b = ipfilters[ i ].compare;
fprintf( f, "sv addip %i.%i.%i.%i\n", b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ] );
}
fclose( f );
}
//### command to reset the time limit and scores
void SVCmd_Reset_f (void)
{
int i;
edict_t *ent;
Event *event;
// only reset if in DM
if(!deathmatch->value)
{
gi.cprintf(NULL, PRINT_HIGH, "Can't reset a single player game\n");
return;
}
for(i = 1; i <= maxclients->value; i++)
{
if(!g_edicts[i].inuse || !g_edicts[i].entity)
{
continue;
}
ent = &g_edicts[i];
// first kill the player
event = new Event(EV_Killed);
event->AddEntity(world);
event->AddInteger(1000);
event->AddEntity(world);
event->AddString("all");
event->AddInteger(MOD_CRUSH);
ent->entity->health = -100;
ent->entity->ProcessEvent(event);
// now reset their score
ent->client->resp.score = 0;
}
// reset the time limit
reset_timeofs = level.time;
}
//###
/*
=================
G_ServerCommand
G_ServerCommand will be called when an "sv" command is issued.
The game can issue gi.argc() / gi.argv() commands to get the rest
of the parameters
=================
*/
void G_ServerCommand
(
void
)
{
const char *cmd;
cmd = gi.argv(1);
if ( Q_stricmp( cmd, "addip" ) == 0 )
{
SVCmd_AddIP_f();
}
else if ( Q_stricmp( cmd, "removeip" ) == 0 )
{
SVCmd_RemoveIP_f();
}
else if ( Q_stricmp( cmd, "listip" ) == 0 )
{
SVCmd_ListIP_f();
}
else if ( Q_stricmp( cmd, "writeip" ) == 0 )
{
SVCmd_WriteIP_f();
}
//### added reset command to allow resetting the timmer and scores
else if(Q_stricmp(cmd, "reset") == 0)
{
SVCmd_Reset_f();
}
//###
else
{
gi.cprintf( NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd );
}
}