/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "g_local.h" #include "g_callvotes.h" #include "g_gametypes.h" game_locals_t game; level_locals_t level; spawn_temp_t st; timeout_t gtimeout; struct mempool_s *gamepool; struct mempool_s *levelpool; int meansOfDeath; cvar_t *dmflags; cvar_t *password; cvar_t *g_select_empty; cvar_t *dedicated; cvar_t *developer; cvar_t *filterban; cvar_t *g_maxvelocity; cvar_t *g_gravity; cvar_t *sv_cheats; #ifdef BATTLEYE cvar_t *sv_battleye; #endif cvar_t *g_maplist; cvar_t *g_maprotation; cvar_t *flood_msgs; cvar_t *flood_persecond; cvar_t *flood_waitdelay; //MBotGame [start] cvar_t *bot_showpath; cvar_t *bot_showcombat; cvar_t *bot_showsrgoal; cvar_t *bot_showlrgoal; cvar_t *bot_debugmonster; #ifndef WSW_RELEASE cvar_t *bot_dummy; #endif //[end] cvar_t *g_projectile_touch_owner; cvar_t *g_projectile_prestep; cvar_t *g_numbots; cvar_t *g_maxtimeouts; cvar_t *g_respawn_delay_min; cvar_t *g_respawn_delay_max; cvar_t *g_deadbody_filter; cvar_t *g_deadbody_followkiller; cvar_t *g_challengers_queue; cvar_t *g_tctf; cvar_t *g_instagib; cvar_t *g_disable_vote_gametype; //=================================================================== //================= //G_API //================= int G_API (void) { return GAME_API_VERSION; } //============ //G_Error // //Abort the server with a game error //============ void G_Error ( char *fmt, ... ) { char msg[1024]; va_list argptr; va_start( argptr, fmt ); vsnprintf( msg, sizeof(msg), fmt, argptr ); va_end( argptr ); msg[sizeof(msg)-1] = 0; trap_Error ( msg ); } //============ //G_Printf // //Debug print to server console //============ void G_Printf ( char *fmt, ... ) { char msg[1024]; va_list argptr; va_start( argptr, fmt ); vsnprintf( msg, sizeof(msg), fmt, argptr ); va_end( argptr ); msg[sizeof(msg)-1] = 0; trap_Print ( msg ); } //============ //G_Init // //This will be called when the dll is first loaded, which //only happens when a new game is started or a save game is loaded. //============ void G_Init( unsigned int seed, unsigned int maxclients, unsigned int framemsec ) { cvar_t *g_maxentities; G_Printf( "==== G_Init ====\n" ); srand( seed ); gamepool = G_MemAllocPool( "Game" ); levelpool = G_MemAllocPool( "Level" ); game.framemsec = framemsec; game.frametime = game.framemsec * 0.001; G_Printf( "Server running at %i pps\n", (int)(1000 / game.framemsec) ); g_maxvelocity = trap_Cvar_Get( "g_maxvelocity", "10000", 0 ); g_gravity = trap_Cvar_Get( "g_gravity", "800", 0 ); developer = trap_Cvar_Get( "developer", "0", 0 ); // noset vars dedicated = trap_Cvar_Get("dedicated", "0", CVAR_NOSET); // latched vars sv_cheats = trap_Cvar_Get("sv_cheats", "0", CVAR_SERVERINFO|CVAR_LATCH ); #ifdef BATTLEYE if( dedicated->integer ) sv_battleye = trap_Cvar_Get( "sv_battleye", "1", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_LATCH ); else sv_battleye = trap_Cvar_Get( "sv_battleye", "0", CVAR_SERVERINFO | CVAR_READONLY ); #endif trap_Cvar_Get( "gamename", GAMENAME , CVAR_SERVERINFO | CVAR_LATCH ); trap_Cvar_Get( "gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH ); // change anytime vars dmflags = trap_Cvar_Get( "dmflags", va("%i", DF_INSTANT_ITEMS), CVAR_SERVERINFO ); password = trap_Cvar_Get( "password", "", CVAR_USERINFO ); filterban = trap_Cvar_Get( "filterban", "1", 0 ); // wsw g_select_empty = trap_Cvar_Get( "g_select_empty", "0", CVAR_DEVELOPER ); g_projectile_touch_owner = trap_Cvar_Get( "g_projectile_touch_owner", "0", CVAR_DEVELOPER ); g_projectile_prestep = trap_Cvar_Get( "g_projectile_prestep", "32", CVAR_DEVELOPER ); g_respawn_delay_min = trap_Cvar_Get( "g_respawn_delay_min", "600", CVAR_DEVELOPER ); g_respawn_delay_max = trap_Cvar_Get( "g_respawn_delay_max", "6000", CVAR_DEVELOPER ); g_numbots = trap_Cvar_Get( "g_numbots", "0", CVAR_ARCHIVE ); g_deadbody_followkiller = trap_Cvar_Get( "g_deadbody_followkiller", "1", CVAR_DEVELOPER ); g_deadbody_filter = trap_Cvar_Get( "g_deadbody_filter", "1", CVAR_DEVELOPER ); g_challengers_queue = trap_Cvar_Get( "g_challengers_queue", "1", CVAR_ARCHIVE ); g_maxtimeouts = trap_Cvar_Get( "g_maxtimeouts", "2", CVAR_ARCHIVE ); // flood control flood_msgs = trap_Cvar_Get( "flood_msgs", "4", 0 ); flood_persecond = trap_Cvar_Get( "flood_persecond", "4", 0 ); flood_waitdelay = trap_Cvar_Get( "flood_waitdelay", "10", 0 ); // map list g_maplist = trap_Cvar_Get( "g_maplist", "", CVAR_ARCHIVE ); g_maprotation = trap_Cvar_Get( "g_maprotation", "1", CVAR_ARCHIVE ); //game switches g_tctf = trap_Cvar_Get( "g_tctf", "10", CVAR_SERVERINFO|CVAR_ARCHIVE ); g_instagib = trap_Cvar_Get( "g_instagib", "0", CVAR_SERVERINFO|CVAR_ARCHIVE|CVAR_LATCH ); // helper cvars to show current status in serverinfo reply trap_Cvar_Get( "g_match_time", "", CVAR_SERVERINFO|CVAR_READONLY ); trap_Cvar_Get( "g_match_score", "", CVAR_SERVERINFO|CVAR_READONLY ); trap_Cvar_Get( "g_needpass", "", CVAR_SERVERINFO|CVAR_READONLY ); trap_Cvar_Get( "g_gametypes_available", "", CVAR_SERVERINFO|CVAR_READONLY ); // define this one here so we can see when it's modified g_disable_vote_gametype = trap_Cvar_Get( "g_disable_vote_gametype", "0", CVAR_ARCHIVE ); // items InitItems (); // initialize all entities for this game g_maxentities = trap_Cvar_Get( "sv_maxentities", "1024", CVAR_LATCH ); game.maxentities = g_maxentities->integer; game.edicts = G_GameMalloc( game.maxentities * sizeof(game.edicts[0]) ); // initialize all clients for this game game.maxclients = maxclients; game.clients = G_GameMalloc( game.maxclients * sizeof(game.clients[0]) ); game.numentities = game.maxclients + 1; trap_LocateEntities( game.edicts, sizeof(game.edicts[0]), game.numentities, game.maxentities ); G_Gametype_Init();//newgametypes G_CallVotes_Init(); AI_Init();//MbotGame } //================= //G_Shutdown //================= void G_Shutdown( void ) { BOT_RemoveBot( "all" ); G_Printf( "==== G_Shutdown ====\n" ); G_MemFreePool( &gamepool ); G_MemFreePool( &levelpool ); } //====================================================================== //================ //G_SetEntityBits // //Set misc bits EF_CORPSE, etc //================ void G_SetEntityBits( edict_t *ent ) { // inverse entity type if it can take damage ent->s.takedamage = (ent->takedamage != 0); // set EF_CORPSE for client side prediction if( ent->r.svflags & SVF_CORPSE ) ent->s.effects |= EF_CORPSE; } //================= //ClientEndServerFrames //================= void ClientEndServerFrames( void ) { int i; edict_t *ent; // calc the player views now that all pushing // and damage has been added for( i = 0; i < game.maxclients; i++ ) { ent = game.edicts + 1 + i; if( !ent->r.inuse || !ent->r.client ) continue; ClientEndServerFrame(ent); G_SetEntityBits(ent); } ClientEndServerFrames_UpdateScoreBoardMessages(); //newgametypes ClientEndServerFrames_UpdateWeaponListMessages(); //newgametypes G_EndServerFrames_UpdateChaseCam(); //splitmodels } //================= //CreateTargetChangeLevel // //Returns the created target changelevel //================= edict_t *CreateTargetChangeLevel(char *map) { edict_t *ent; ent = G_Spawn (); ent->classname = "target_changelevel"; Q_strncpyz (level.nextmap, map, sizeof(level.nextmap)); ent->map = level.nextmap; return ent; } //================= //G_ChooseNextMap //================= edict_t *G_ChooseNextMap( void ) { edict_t *ent; char *s, *t, *f; static const char *seps = " ,\n\r"; if( *level.forcemap ) { return CreateTargetChangeLevel( level.forcemap ); } if( !(*g_maplist->string) || strlen(g_maplist->string) == 0 || g_maprotation->integer == 0 ) { // same map again return CreateTargetChangeLevel( level.mapname ); } else if (g_maprotation->integer == 1) { // next map in list s = G_CopyString( g_maplist->string ); f = NULL; t = strtok( s, seps ); while( t != NULL ) { if( !Q_stricmp(t, level.mapname) ) { // 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 ent = CreateTargetChangeLevel( level.mapname ); else ent = CreateTargetChangeLevel(f); } else ent = CreateTargetChangeLevel(t); G_Free(s); return ent; } if( !f ) f = t; t = strtok( NULL, seps ); } // not in the list, we go for the first one ent = CreateTargetChangeLevel(f); G_Free(s); return ent; } else { // random from the list, but not the same int count = 0; s = G_CopyString( g_maplist->string ); t = strtok( s, seps ); while( t != NULL ) { if( Q_stricmp(t, level.mapname) ) count++; t = strtok( NULL, seps ); } G_Free(s); s = G_CopyString( g_maplist->string ); if( count < 1 ) { // no other maps found, restart ent = CreateTargetChangeLevel( level.mapname ); } else { int seed = game.realtime; count -= (int)Q_brandom(&seed, 0, count); // this should give random integer from 0 to count-1 ent = NULL; // shutup compiler warning; t = strtok( s, seps ); while( t != NULL ) { if( Q_stricmp(t, level.mapname) ) { count--; if( count == 0 ) { ent = CreateTargetChangeLevel(t); break; } } t = strtok( NULL, seps ); } } G_Free(s); return ent; } #if 0 // wsw : mdr : this never gets called now, but would be usefull for SP if( level.nextmap[0] ) // go to a specific map return CreateTargetChangeLevel( level.nextmap ); // search for a changelevel ent = G_Find( NULL, FOFS(classname), "target_changelevel" ); if(!ent) { // the map designer didn't include a changelevel, // so create a fake ent that goes back to the same level return CreateTargetChangeLevel( level.mapname ); } return ent; #endif } //================= //G_SelectNextMapName //================= char *G_SelectNextMapName( void ) { edict_t *changelevel; changelevel = G_ChooseNextMap(); return changelevel->map; } //============= //G_ExitLevel //============= void G_ExitLevel( void ) { int i; edict_t *ent; char command [256]; char *nextmapname; qboolean loadmap = qtrue; level.exitnow = 0; game.autosaved = qfalse; nextmapname = G_SelectNextMapName(); // if it's the same map see if we can restart without loading if( !Q_stricmp(nextmapname, level.mapname) ) { if( G_Match_RestartLevel() ) { loadmap = qfalse; } } if( loadmap ) { BOT_RemoveBot( "all" ); // MbotGame (Disconnect all bots before changing map) Q_snprintfz( command, sizeof(command), "gamemap \"%s\"\n", nextmapname ); trap_AddCommandString( command ); } ClientEndServerFrames(); level.changemap = NULL; // clear some things before going to next level for( i = 0; i < game.maxclients; i++ ) { ent = game.edicts + 1 + i; if( !ent->r.inuse ) continue; ent->r.client->showscores = qfalse; ent->r.client->showinventory = qfalse; if( ent->health > ent->max_health ) ent->health = ent->max_health; // some things are only cleared when there's a new map load if( loadmap ) { ent->r.client->pers.connecting = qtrue; // set all connected players as "reconnecting" ent->s.team = TEAM_SPECTATOR; } } } //============= // G_Edicts_EndServerFrames // finish entities, add effects based on accumulated info along the server frame //============= void G_Edicts_EndServerFrames( void ) { #define JALFIXME_DAMAGESAVE edict_t *ent; int i; vec3_t dir, origin; for( i = 0, ent = &game.edicts[0]; i < game.numentities; i++, ent++ ) { if( !ent->r.inuse ) continue; // types which can have accumulated damage effects if( (ent->s.type == ET_GENERIC || ent->s.type == ET_PLAYER) && ent->movetype != MOVETYPE_PUSH ) // doors don't bleed { #ifdef JALFIXME_DAMAGESAVE // Until we get a proper damage saved effect, we accumulate both into the blood fx // so, at least, we don't send 2 entities where we can send one ent->frame_damage_taken += ent->frame_damage_saved; ent->frame_damage_saved = 0; #endif //spawn accumulated damage if( ent->frame_damage_taken ) { edict_t *event; float damage = ent->frame_damage_taken; if( damage > 120 ) damage = 120; //VectorSubtract( ent->frame_damage_from, ent->s.origin, dir ); VectorCopy( ent->frame_damage_dir, dir ); VectorNormalize( dir ); VectorAdd( ent->s.origin, ent->frame_damage_at, origin ); event = G_SpawnEvent( EV_BLOOD2, DirToByte(dir), origin ); event->s.damage = HEALTH_TO_INT(damage); event->r.svflags = SVF_NOOLDORIGIN; event->s.ownerNum=i; // set owner } ent->frame_damage_taken = 0; #ifndef JALFIXME_DAMAGESAVE // This should not be a blood effect, but a armor sparks one // or maybe we should just not send any damage saved effect //---------------------------------------------------------- //spawn accumulated save if( ent->frame_damage_saved ) { edict_t *event; float save = ent->frame_damage_saved; if( save > 120 ) save = 120; VectorCopy( ent->frame_damage_dir, dir ); VectorNormalize( dir ); VectorAdd( ent->s.origin, ent->frame_damage_at, origin ); event = G_SpawnEvent( EV_BLOOD_SAVED, DirToByte(dir), origin ); event->s.damage = HEALTH_TO_INT(save); event->r.svflags = SVF_NOOLDORIGIN; } ent->frame_damage_saved = 0; #endif } G_SetEntityBits( ent ); } } //================ //G_Timeout_Reset //================ void G_Timeout_Reset( void ) { int i; gtimeout.active = qfalse; gtimeout.time = 0; gtimeout.endtime = 0; gtimeout.caller = 0; for( i = 0; i < MAX_CLIENTS; i++ ) gtimeout.used[i] = 0; } //================ //G_Timeout_Update // //Updates the timeout struct and informs clients about the status of the pause //================ static void G_Timeout_Update( unsigned int msec ) { static int timeout_printtime = 0; static int timeout_last_endtime = 0; static int countdown_set = 1; if( !gtimeout.active ) return; if( timeout_last_endtime != gtimeout.endtime ) // force print when endtime is changed { timeout_printtime = 0; timeout_last_endtime = gtimeout.endtime; } gtimeout.time += msec; if( gtimeout.endtime && gtimeout.time >= gtimeout.endtime ) { gtimeout.time = 0; gtimeout.caller = -1; gtimeout.active = qfalse; timeout_printtime = 0; timeout_last_endtime = -1; G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_TIMEOUT_MATCH_RESUMED_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue ); G_CenterPrintMsg( NULL, "Match resumed" ); G_PrintMsg( NULL, "Match resumed\n" ); } else if( timeout_printtime == 0 || gtimeout.time - timeout_printtime >= 1000 ) { if( gtimeout.endtime ) { int seconds_left = (int)((gtimeout.endtime - gtimeout.time) / 1000.0 + 0.5); if( seconds_left == (TIMEIN_TIME * 2) / 1000 ) { G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_COUNTDOWN_READY_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qfalse ); countdown_set = (rand()&1)+1; } else if( seconds_left >= 1 && seconds_left <= 3 ) { G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, seconds_left, countdown_set)), GS_MAX_TEAMS, qfalse ); } if( seconds_left > 1 ) G_CenterPrintMsg( NULL, "Match will resume in %i seconds", seconds_left ); else G_CenterPrintMsg( NULL, "Match will resume in 1 second" ); } else { G_CenterPrintMsg( NULL, "Match paused" ); } timeout_printtime = gtimeout.time; } } void G_UpdateServerInfo( void ) { // g_match_time if( match.state <= MATCH_STATE_WARMUP ) { trap_Cvar_ForceSet( "g_match_time", "Warmup" ); } else if( match.state == MATCH_STATE_COUNTDOWN ) { trap_Cvar_ForceSet( "g_match_time", "Countdown" ); } else if( match.state == MATCH_STATE_PLAYTIME ) { // partly from G_GetMatchState char extra[MAX_INFO_STRING]; int clocktime, timelimit, mins, secs; if( match.endtime ) timelimit = (match.endtime - match.starttime) / 60; else timelimit = 0; clocktime = level.time - match.starttime; if( clocktime <= 0 ) { mins = 0; secs = 0; } else { mins = clocktime / 60; secs = clocktime - mins * 60; } extra[0] = 0; if( match.extendedTime > 0 ) { if( timelimit ) Q_strncatz( extra, " overtime", sizeof(extra) ); else Q_strncatz( extra, " suddendeath", sizeof(extra) ); } if( gtimeout.active ) Q_strncatz( extra, " (in timeout)", sizeof(extra) ); if( timelimit ) trap_Cvar_ForceSet( "g_match_time", va("%02i:%02i / %02i:00%s", mins, secs, timelimit, extra) ); else trap_Cvar_ForceSet( "g_match_time", va("%02i:%02i%s", mins, secs, extra) ); } else { trap_Cvar_ForceSet( "g_match_time", "Finished" ); } // g_match_score if( match.state >= MATCH_STATE_PLAYTIME && GS_Gametype_IsTeamBased( game.gametype ) ) { char score[MAX_INFO_STRING]; score[0] = 0; Q_strncatz( score, va("Red: %i", teamlist[TEAM_RED].teamscore), sizeof(score) ); if( TEAM_BLUE < TEAM_RED + g_maxteams->integer ) Q_strncatz( score, va(" Blue: %i", teamlist[TEAM_BLUE].teamscore), sizeof(score) ); if( TEAM_GREEN < TEAM_RED + g_maxteams->integer ) Q_strncatz( score, va(" Green: %i", teamlist[TEAM_GREEN].teamscore), sizeof(score) ); if( TEAM_YELLOW < TEAM_RED + g_maxteams->integer ) Q_strncatz( score, va(" Yellow: %i", teamlist[TEAM_YELLOW].teamscore), sizeof(score) ); trap_Cvar_ForceSet( "g_match_score", score ); } else { trap_Cvar_ForceSet( "g_match_score", "" ); } // g_needpass if( password->modified ) { if( password->string && strlen(password->string) ) { trap_Cvar_ForceSet( "g_needpass", "1" ); } else { trap_Cvar_ForceSet( "g_needpass", "0" ); } password->modified = qfalse; } // g_gametypes_available if( g_votable_gametypes->modified || g_disable_vote_gametype->modified ) { if( g_disable_vote_gametype->integer || !g_votable_gametypes->string || !strlen(g_votable_gametypes->string) ) { trap_Cvar_ForceSet( "g_gametypes_available", "" ); } else { int type; char votable[MAX_INFO_VALUE]; votable[0] = 0; for( type = GAMETYPE_DM; type < GAMETYPE_TOTAL; type++ ) { if( G_Gametype_IsVotable(type) ) { Q_strncatz( votable, GS_Gametype_ShortName(type), sizeof(votable) ); Q_strncatz( votable, " ", sizeof(votable) ); } } votable[strlen(votable)-2] = 0; // remove the last space trap_Cvar_ForceSet( "g_gametypes_available", votable ); } g_votable_gametypes->modified = qfalse; g_disable_vote_gametype->modified = qfalse; } } void G_CheckCvars( void ) { if( g_teams_lock->modified ) { // if we are inside a match, update the teams state if( match.state > MATCH_STATE_WARMUP && match.state <= MATCH_STATE_PLAYTIME ) { int team; if( g_teams_lock->integer ) { for( team = 0; team < GS_MAX_TEAMS; team++ ) G_Teams_LockTeam( team ); G_PrintMsg( NULL, "Teams locked.\n"); } else { for( team = 0; team < GS_MAX_TEAMS; team++ ) G_Teams_UnLockTeam( team ); G_PrintMsg( NULL, "Teams unlocked.\n"); } } g_teams_lock->modified = qfalse; } if( g_warmup_enabled->modified ) { // if we are inside warmup period, finish it if( !g_warmup_enabled->integer && (match.state == MATCH_STATE_WARMUP || match.state == MATCH_STATE_COUNTDOWN) ) G_Match_SetUpNextState(); g_warmup_enabled->modified = qfalse; } if( g_warmup_timelimit->modified ) { // if we are inside timelimit period, update the endtime if( match.state == MATCH_STATE_WARMUP ) { if( g_warmup_timelimit->integer ) match.endtime = match.starttime + fabs(60 * g_warmup_timelimit->integer); else match.endtime = 0; } g_warmup_timelimit->modified = qfalse; } if( g_timelimit->modified ) { // if we are inside timelimit period, update the endtime if( match.state == MATCH_STATE_PLAYTIME && match.extendedTime == 0 && game.gametype != GAMETYPE_RACE ) { if( g_timelimit->value ) match.endtime = match.starttime + fabs(60 * g_timelimit->value); else match.endtime = 0; } g_timelimit->modified = qfalse; } if( g_match_extendedtime->modified ) { // if we are inside extended_time period, update the endtime if( match.state == MATCH_STATE_PLAYTIME && match.extendedTime > 0 ) { if( g_match_extendedtime->integer ) { // wsw: Medar: this is little bit stupid float oldendtime = match.endtime; match.endtime = match.starttime + fabs(60 * g_timelimit->value); while( match.endtime < oldendtime || match.endtime < level.time ) match.endtime += fabs(g_match_extendedtime->integer * 60); } else { match.endtime = 0; G_Match_SetUpNextState(); } } g_match_extendedtime->modified = qfalse; } } //================ //G_RunFrame // //Advances the world //================ void G_RunFrame( unsigned int msec ) { int i; edict_t *ent; G_Timeout_Update( msec ); if( !gtimeout.active ) { level.framenum++; level.maptime += game.framemsec; } game.framemsec = msec; game.frametime = (float)msec * 0.001; level.timemsec = level.framenum * game.framemsec; level.time = (float)level.timemsec * 0.001; game.realtime = trap_Milliseconds(); // level.time etc. might not be real time // // clear all events to free entity spots // ent = &game.edicts[0]; for( i = 0; i < game.numentities; i++, ent++ ) { if( !ent->r.inuse ) continue; // free if no events if( !ent->s.events[0] ) { ent->numEvents = 0; ent->eventPriority[0] = ent->eventPriority[1] = qfalse; if( ent->freeAfterEvent ) G_FreeEdict(ent); } // wsw : jal : clear player state events if( ent->r.client ) ent->r.client->ps.event = PSEV_NONE; } // exit intermissions if( level.exitnow ) { G_ExitLevel(); return; } // // treat each object in turn // even the world gets a chance to think // ent = &game.edicts[0]; for( i = 0; i < game.numentities; i++, ent++ ) { if( !ent->r.inuse ) continue; if( ent->freeAfterEvent ) continue; // events do not think ent->s.effects &= ~EF_CORPSE; // remove EF_CORPSE bit level.current_entity = ent; if( !gtimeout.active ) { if( !(ent->r.svflags & SVF_FORCEOLDORIGIN) ) VectorCopy( ent->s.origin, ent->s.old_origin ); // if the ground entity moved, make sure we are still on it if( (ent->groundentity) && (ent->groundentity->r.linkcount != ent->groundentity_linkcount) ) { ent->groundentity = NULL; if( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->r.svflags & SVF_MONSTER) ) G_CheckGround( ent ); } } if( i > 0 && i <= game.maxclients ) { ClientBeginServerFrame(ent); //MbotGame[start] if( !ent->ai.type ) //[end] continue; } if( !gtimeout.active ) G_RunEntity( ent ); } G_GametypeCheckRules();//newgametypes // finish entities, add effects based on accumulated info along the frame G_Edicts_EndServerFrames(); // build the playerstate_t structures for all players ClientEndServerFrames(); //MbotGame[start] AITools_Frame(); //give think time to AI debug tools //[end] G_CheckCvars(); G_UpdateServerInfo(); } //====================================================================== #ifndef GAME_HARD_LINKED // this is only here so the functions in q_shared.c and q_math.c can link void Sys_Error(char *error, ...) { va_list argptr; char text[1024]; va_start( argptr, error ); vsnprintf( text, sizeof(text), error, argptr ); va_end( argptr ); text[sizeof(text)-1] = 0; G_Error( "%s", text ); } void Com_Printf( char *msg, ... ) { va_list argptr; char text[1024]; va_start( argptr, msg ); vsnprintf( text, sizeof(text), msg, argptr ); va_end( argptr ); text[sizeof(text)-1] = 0; G_Printf( "%s", text ); } #endif