//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/g_main.cpp                       $
// $Revision:: 170                                                            $
//   $Author:: Jimdose                                                        $
//     $Date:: 2/16/99 8:37p                                                  $
//
// 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.
//
// $Log:: /Quake 2 Engine/Sin/code/game/g_main.cpp                            $
// 
// 170   2/16/99 8:37p Jimdose
// moved sin arcade comm stuff to client
// made dmflags, timelimit, and fraglimit archived under SIN_ARCADE
// 
// 169   1/22/99 8:00p Jimdose
// added "showvar" command for inspecting script variables
// 
// 168   12/08/98 7:04p Aldie
// Added setup and shutdown calls to comm for arcade
// 
// 167   11/17/98 6:21p Aldie
// Put a check for DM in the fixbodies
// 
// 166   11/17/98 4:07a Markd
// put in stopsound when changing levels 
// 
// 165   11/15/98 11:31p Markd
// changed default fat rocket setting
// 
// 164   11/15/98 9:02p Jimdose
// added sv_fatrockets
// 
// 163   11/14/98 2:55a Aldie
// Don't update skin in single player in the clientuserinfo.  The player Init
// function will take care of it.
// 
// 162   11/13/98 11:02p Jimdose
// made fov persistant across levels and after zooming in and out
// 
// 161   11/13/98 10:05p Jimdose
// G_SaveClientData is now called when exiting the level, instead of when the
// level is saved.  This should fix the problem of the player keeping objects
// from the previous level when he crosses an episode (when savegames are
// cleared out).
// 
// 160   11/13/98 2:38a Aldie
// Fixed userinfo changed so manumit is preserved in loadgames
// 
// 159   11/11/98 2:56p Aldie
// Fix for deadbody code
// 
// 158   11/10/98 8:05p Aldie
// Fix dead bodies for players changing models
// 
// 157   11/10/98 4:34p Jimdose
// added clearsavegames to level
// 
// 156   11/09/98 1:04a Markd
// made parentmode not LATCHED
// 
// 155   11/09/98 12:55a Jimdose
// added sv_footsteps cvar so that server admins can turn footsteps off
// completely
// added CVAR_USERINFO to parentmode
// 
// 154   11/08/98 10:48p Jimdose
// moved earthquake to level struct
// upped savegame version
// 
// 153   11/08/98 8:09p Aldie
// Upped the savegame version
// 
// 152   11/06/98 10:04p Jimdose
// Added G_AllocDebugLines
// 
// 151   11/06/98 5:32p Jimdose
// added missionfailed and missionfailedtime to level vars
// when a mission has failed or the player is dead in single player, the game
// code immediately shows the loadmenu, preventing them from letting the game
// continue running if they exit the menu
// 
// 150   10/27/98 9:46p Aldie
// Changed training cvar to level.training
// 
// 149   10/27/98 6:52p Jimdose
// changed savegame version
// 
// 148   10/27/98 5:41a Jimdose
// upped the savegame version
// 
// 147   10/27/98 3:43a Aldie
// Removed the "loading" command string
// 
// 146   10/26/98 5:14p Jimdose
// change recalcpaths to ai_recalcpaths
// 
// 145   10/26/98 4:42p Jimdose
// added recalcpaths
// 
// 144   10/26/98 2:17p Aldie
// Added AirClamp
// 
// 143   10/25/98 11:56p Jimdose
// moved playerfrozen from game to level
// 
// 142   10/25/98 10:11p Markd
// made default dialog be mode 2 instead of mode 3
// 
// 141   10/25/98 9:10p Markd
// made dialog variable archived
// 
// 140   10/25/98 6:26p Markd
// Added in no_jc abililty
// 
// 139   10/25/98 6:09p Aldie
// Added training cvar
// 
// 138   10/25/98 4:53a Jimdose
// Increased the savegame version
// 
// 137   10/25/98 12:48a Markd
// changed manumit.def to manumit_pl.def
// 
// 136   10/24/98 6:05p Jimdose
// made waitForPlayer work in change level threads
// upped the savegame version number
// 
// 135   10/24/98 3:34p Markd
// Added loading command, fixed typo
// 
// 134   10/24/98 2:17p Markd
// Put in a loading in exit level
// 
// 133   10/24/98 12:42a Markd
// changed origins to worldorigins where appropriate
// 
// 132   10/23/98 6:46p Aldie
// Archive maxclients
// 
// 131   10/22/98 4:57p Aldie
// Removed blastscale_z value
// 
// 130   10/22/98 3:27a Jimdose
// changed the savegame version
// 
// 129   10/21/98 6:42p Markd
// Added sv_drawtrace
// 
// 128   10/20/98 10:30p Jimdose
// added savegame version
// 
// 127   10/20/98 2:21a Aldie
// Added sv_maplist for map rotation
// 
// 126   10/19/98 9:54p Jimdose
// changed slime variables to lightvolume
// 
// 125   10/19/98 12:07a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
// 
// 124   10/18/98 8:42p Jimdose
// Cleaned unnecessary or slow stuff from G_RunFrame 
// 
// 123   10/18/98 3:23a Jimdose
// Added code for timing entities
// 
// 122   10/17/98 12:20a Jimdose
// Save games now archive paths
// Fixed bug due to ReadLevel not freeing the spawned entities
// G_SaveClientData doesn't save data during deathmatch
// 
// 121   10/16/98 1:50a Jimdose
// Added FL_DONTSAVE flag to mark entities that shouldn't be saved to savegames
// Send end level event to clients on level exit
// Added autosave variable to WriteLevel
// 
// 120   10/14/98 11:55p Markd
// put in assert( 0 ) in G_Error
// 
// 119   10/14/98 10:55p Jimdose
// More work on persitant data
// 
// 118   10/14/98 1:17a Jimdose
// Got cross-level persistant info working
// 
// 117   10/11/98 8:51p Jimdose
// Added LoadingServer variable
// 
// 116   10/10/98 1:26a Jimdose
// added G_Precache
// 
// 115   10/08/98 7:18p Jimdose
// Added IP filtering
// Removed noexit and samelevel cvars since they are dmflags
// fixed bug with restarting level
// 
// 114   10/08/98 12:38a Jimdose
// Made savegames work
// 
// 113   9/30/98 5:39p Aldie
// Added showinfo command
// 
// 112   9/26/98 4:44p Aldie
// Added mutant mode
// 
// 111   9/25/98 4:36p Markd
// replaced Event::Find with Event::Exists
// 
// 110   9/24/98 1:49a Jimdose
// Added g_showmem
// 
// 109   9/22/98 3:21p Markd
// put in parentmode lockout for blood and gibs
// 
// 108   9/19/98 5:01p Markd
// took out current_music and fallback_music
// 
// 107   9/19/98 4:33p Jimdose
// added eventlist client command
// 
// 106   9/18/98 5:37p Aldie
// Removed dead code
// 
// 105   9/11/98 2:49p Aldie
// Cleaned up intermission stuff
// 
// 104   9/10/98 8:38p Aldie
// Electrical beam effects
// 
// 103   9/09/98 3:03p Markd
// Fixed deathmatch camera
// 
// 102   9/07/98 8:29p Markd
// Added fulltrace bboxes
// 
// 101   9/03/98 2:49p Aldie
// Changed default of sv_showdamagelocation to 0
// 
// 100   9/02/98 7:46p Aldie
// Added ValidPlayerModels list
// 
// 99    8/31/98 4:44p Markd
// fixed setting on non-blade in demo and non-deathmatch
// 
// 98    8/29/98 9:41p Jimdose
// Made all function names consistantly begin with G_
// Added call info to G_Trace
// 
// 97    8/21/98 5:26p Markd
// Added sv_precache and cl_precache 
// 
// 96    8/19/98 8:49p Aldie
// Added sv_showdamagelocation for kicks
// 
// 95    8/13/98 8:10p Aldie
// Deathmatch score bug
// 
// 94    8/13/98 7:30p Aldie
// New deathmatch scoreboard
// 
// 93    8/08/98 7:29p Aldie
// Added intermissions for deathmatch
// 
// 92    8/07/98 4:20p Aldie
// Fixed say command when command is not known
// 
// 91    8/07/98 2:28p Aldie
// Fixed a bug where dead person could go into idle animation
// 
// 90    8/03/98 7:54p Jimdose
// Added sv_showentnums to show the entity number above any entity
// 
// 89    8/02/98 9:00p Markd
// Merged code 3.17
// 
// 88    7/31/98 8:08p Jimdose
// Script commands now include flags to indicate cheats and console commands
// 
// 87    7/31/98 4:19p Jimdose
// Fixed deathmatch cheats
// 
// 86    7/26/98 9:29a Aldie
// Fixed parameter error on skin checking
// 
// 85    7/26/98 9:12a Aldie
// Fix multiplayer skin lookups for invalid models
// 
// 84    7/26/98 5:32a Markd
// put in rudimentary savegame
// 
// 83    7/25/98 3:12p Aldie
// Initialize showdamage to 0
// 
// 82    7/24/98 10:03p Aldie
// Changed the gibs layout
// 
// 81    7/24/98 6:17p Aldie
// Dialog checking
// 
// 80    7/24/98 4:51p Jimdose
// Bounding boxes no longer show up in deathmatch
// 
// 79    7/24/98 3:47p Aldie
// Don't allow invalid models, skins, etc...
// 
// 78    7/23/98 6:17p Aldie
// Updated damage system and fixed some damage related bugs.  Also put tracers
// back to the way they were, and added gib event to funcscriptmodels
// 
// 77    7/23/98 2:32p Aldie
// Made speed of rocket a tweak cvar
// 
// 76    7/21/98 9:04p Markd
// Added current_mood and fallback mood for music
// 
// 75    7/18/98 5:04p Aldie
// Added showdamage
// 
// 74    7/13/98 4:59p Aldie
// Added dead player bodies with gibbing
// 
// 73    7/11/98 8:58p Markd
// Removed testthread command
// 
// 72    7/11/98 8:42p Markd
// Added testthread command
// 
// 71    7/10/98 6:20a Jimdose
// Added "add" command to add a value to a cvar
// Added G_DrawCSystem for use in debugging orientations
// 
// 70    7/09/98 10:38p Aldie
// Put bodyparts into game init
// 
// 69    7/08/98 12:54p Jimdose
// Made developer c_var global
// 
// 68    7/08/98 12:23p Aldie
// Updated deathmatch scoreboard
// 
// 67    7/03/98 12:01p Aldie
// New userinfo stuff
// 
// 66    7/01/98 7:02p Aldie
// Removed zoom crosshair stuff
// 
// 65    6/23/98 9:54p Jimdose
// Fixed infinite loop bug in G_RunFrame
// 
// 64    6/21/98 6:11p Jimdose
// set level.current_entity in G_ClientThink
// 
// 63    6/19/98 6:37p Aldie
// Removed one of the stat #defines
// 
// 62    6/18/98 8:46p Jimdose
// Added better event error handling
// Added source info to events
// 
// 61    6/15/98 8:04p Markd
// changed max_entities
// 
// 60    6/10/98 9:31p Markd
// Put in infiite loop checking into main loop
// 
// 59    6/09/98 4:19p Jimdose
// Fixed infinite loop bug in G_RunFrame
// 
// 58    5/27/98 5:27a Aldie
// added sv_gibs flag
// 
// 57    5/27/98 5:02a Aldie
// Added gibs and gore
// 
// 56    5/25/98 6:51a Aldie
// Added sv_maxbulletholes
// 
// 55    5/24/98 9:10p Markd
// added another mode to sv_showbboxes
// 
// 54    5/24/98 8:34p Markd
// changed max_entities
// 
// 53    5/24/98 4:48p Jimdose
// Made char *'s const
// 
// 52    5/20/98 7:16p Markd
// Added ClientDrawBoundingBoxes
// 
// 51    5/18/98 8:13p Jimdose
// Renamed Navigator back to PathManager
// 
// 50    5/14/98 10:11p Jimdose
// g_edicts is now initialized to NULL
// 
// 49    5/09/98 8:02p Jimdose
// Added ai commands to Clientcommand.
// Disables Cmd_Say_f
// Added path saving
// 
// 48    5/08/98 2:54p Jimdose
// working on Cmd_Say_f
// 
// 47    5/07/98 10:57p Jimdose
// Fixed gi.error crashing in ClientCommand
// 
// 46    5/05/98 2:43p Aldie
// Added server side surface states
// 
// 45    5/03/98 4:31p Jimdose
// Increased MAX_DEBUG_LINES
// 
// 44    5/02/98 8:37p Aldie
// More console stuff for demos
// 
// 43    4/29/98 5:04p Jimdose
// Fixed gi.error so that it doesn't crash when called from C++
// 
// 42    4/28/98 8:16p Jimdose
// Added checks to ensure that SOLID_BSP objects have models
// 
// 41    4/28/98 6:59p Aldie
// Added server side console buffer
// 
// 40    4/27/98 6:09p Jimdose
// Added debug lines
// 
// 39    4/27/98 5:28p Aldie
// Added server side console states.
// 
// 38    4/23/98 5:00p Jimdose
// Added ai_debugpath
// 
// 37    4/20/98 2:45p Jimdose
// working on ai
// 
// 36    4/18/98 3:01p Jimdose
// Added ai_createnodes and ai_showpath	
// 
// 35    4/16/98 2:03p Jimdose
// edict->s.prevframe is set to -1 (cleared) at the beginning of each frame
// Removed pathmanager
// 
// 34    4/08/98 12:20a Jimdose
// Made viewcommands unavailble without cheats enabled.
// 
// 33    4/07/98 3:48p Aldie
// Added zooming crosshair
// 
// 32    4/06/98 7:10p Aldie
// Added zooming for SniperRifle
// 
// 31    4/06/98 5:42p Jimdose
// G_RunFrame now clears RF_FRAMELERP in renderfx and sets the oldorigin on
// entities
// 
// 30    4/05/98 11:02p Jimdose
// Took out that lastorigin bullshit
// 
// 29    4/05/98 10:34p Jimdose
// Added lastorigin to entity so that we can properly track oldorigin since
// worldorigin may be changed prior to setting oldorigin
// 
// 28    4/05/98 2:56a Jimdose
// fixed bug where deathmatch scores never went away
// 
// 27    4/05/98 1:56a Jimdose
// Fixed bug where oldorigin was being updated with origin instead of
// worldorigin
// 
// 26    4/04/98 6:03p Jimdose
// Got rid of unreferenced variable
// 
// 25    4/03/98 3:36p Jimdose
// Defined draw flags for STAT_LAYOUTS
// 
// 24    4/03/98 1:10p Aldie
// Added consolevar
// 
// 23    4/02/98 4:49p Jimdose
// Added stats and scoreboard
// 
// 22    3/30/98 2:43p Jimdose
// Started on status bar and dm scores
// 
// 21    3/27/98 5:36p Jimdose
// Enabled level changing
// 
// 20    3/27/98 12:05p Aldie
// Added consolecmd
// 
// 19    3/26/98 8:23p Jimdose
// Added coop variable
// made deathmatch work
// changed groundentity to an edict_t *
// Changed assertions when traversing active entities so that it doesn't crash
// out
// 
// 18    3/24/98 4:54p Jimdose
// G_RunFrame now does pre and post physics checks for pending events
// 
// 17    3/23/98 1:31p Jimdose
// Revamped event and command system
// 
// 16    3/15/98 5:06p Aldie
// fixed bug with viewspan and appending models
// 
// 15    3/13/98 10:52a Markd
// fixed bug
// 
// 14    3/13/98 10:48a Markd
// Prepended "models/" to the beginning of viewmodel and viewspawn
// 
// 13    3/12/98 9:49a Markd
// Re-worked viewthing commands
// 
// 12    3/11/98 11:31a Markd
// Re-worked viewthing commands a bunch
// 
// 11    3/07/98 2:04p Markd
// Fixed View* commands
// 
// 10    3/05/98 6:43p Markd
// Added viewthing support
// 
// 9     3/02/98 5:47p Jimdose
// ShutdownGame now frees all entities and paths before exiting
// 
// 8     2/21/98 7:32p Jimdose
// Added checks for cl_oldladders and cl_oldnoclip in ClientUserinfoChanged
// 
// 7     2/21/98 1:14p Jimdose
// Temporarily bumped up maxentities
// Made player commands act as script commands
// Fixed physics loop so that old_origin is only set when RF_BEAM is not set.
// 
// 6     2/17/98 7:01p Jimdose
// gameVars are cleared upon game startup
// 
// 5     2/16/98 2:22p Jimdose
// Added active_edicts and free_edicts for faster entity processing and for
// operations that depend on the order that physics is processed on entities
// (such as binding).
// 
// 4     2/09/98 2:35p Aldie
// Removed const of vec_origin and vec_zero
// 
// 3     2/09/98 11:54a Jimdose
// Made vec_zero and vec_origin const
// 
// 2     2/03/98 11:05a Jimdose
// In process of converting to work with Sin progs
// 
// 1     1/23/98 5:53p Jimdose
// 
// 3     12/30/97 6:04p Jimdose
// Added header text
//
// DESCRIPTION:
// 

#define SAVEGAME_VERSION 13

#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"

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;

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);
	}

/*
============
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();

	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);
#ifdef SIN_ARCADE
	dmflags				= gi.cvar ("dmflags", "0", CVAR_SERVERINFO|CVAR_ARCHIVE);
	fraglimit			= gi.cvar ("fraglimit", "0", CVAR_SERVERINFO|CVAR_ARCHIVE);
	timelimit			= gi.cvar ("timelimit", "0", CVAR_SERVERINFO|CVAR_ARCHIVE);
#else
	dmflags				= gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
	fraglimit			= gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
	timelimit			= gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
#endif
	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);

	G_InitEvents();
	sv_numtraces = 0;

	game.maxentities = maxentities->value;
	if (maxclients->value * 8 > game.maxentities)
		{
		game.maxentities = maxclients->value * 8;
		}
	game.maxclients = maxclients->value;
   game.maxconsoles = maxconsoles->value;
   game.maxsurfaces = maxsurfaces->value;
   G_AllocGameData();
   }

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 )
		{
		if ( level.time >= timelimit->value * 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;
				}
			}
		}
	}

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" );

   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 );
         }      
      }

   // 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));

	// handedness
	s = Info_ValueForKey( userinfo, "hand" );
	if ( strlen( s ) )
	   {
		ent->client->pers.hand = atoi( s );
	   }

	// 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 );
			}
		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();
   }

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 ] );
      }
   }

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 ] );
      }
   }

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 );
   }

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 );
   }

/*
==============================================================================

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 );
   }

/*
=================
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();
      }
	else
      {
		gi.cprintf( NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd );
      }
   }
