//----------------------------------------------------------------------------- // // $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 #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_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; ivalue + 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 removeip 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 " 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 \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 \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 ); } }