/* * G_RunEntities * treat each object in turn * even the world and clients get a chance to think */ static void G_RunEntities( void ) { edict_t *ent; for( ent = &game.edicts[0]; ENTNUM( ent ) < game.numentities; ent++ ) { if( !ent->r.inuse ) continue; if( ISEVENTENTITY( &ent->s ) ) continue; // events do not think level.current_entity = ent; // backup oldstate ( for world frame ). ent->olds = ent->s; // if the ground entity moved, make sure we are still on it if( !ent->r.client ) { if( ( ent->groundentity ) && ( ent->groundentity->linkcount != ent->groundentity_linkcount ) ) G_CheckGround( ent ); } G_RunEntity( ent ); if( ent->takedamage ) ent->s.effects |= EF_TAKEDAMAGE; else ent->s.effects &= ~EF_TAKEDAMAGE; } }
/* * G_RunEntity * */ void G_RunEntity( edict_t *ent ) { edict_t *part; if( !level.canSpawnEntities ) { // don't try to think before map entities are spawned return; } if( ISEVENTENTITY( &ent->s ) ) { // events do not think return; } if( ent->timeDelta && !( ent->r.svflags & SVF_PROJECTILE ) ) { G_Printf( "Warning: G_RunEntity 'Fixing timeDelta on non projectile entity\n" ); ent->timeDelta = 0; } // only team captains decide the think, and they make think their team members when they do if( !( ent->flags & FL_TEAMSLAVE ) ) { for( part = ent; part; part = part->teamchain ) { SV_RunThink( part ); } } switch( (int)ent->movetype ) { case MOVETYPE_NONE: case MOVETYPE_NOCLIP: // only used for clients, that use pmove SV_Physics_None( ent ); break; case MOVETYPE_PLAYER: SV_Physics_None( ent ); break; case MOVETYPE_PUSH: case MOVETYPE_STOP: SV_Physics_Pusher( ent ); break; case MOVETYPE_BOUNCE: case MOVETYPE_BOUNCEGRENADE: SV_Physics_Toss( ent ); break; case MOVETYPE_TOSS: SV_Physics_Toss( ent ); break; case MOVETYPE_FLY: SV_Physics_Toss( ent ); break; case MOVETYPE_LINEARPROJECTILE: SV_Physics_LinearProjectile( ent ); break; case MOVETYPE_TOSSSLIDE: G_BoxSlideMove( ent, ent->r.clipmask ? ent->r.clipmask : MASK_PLAYERSOLID, 1.01f, 10 ); break; case MOVETYPE_STEP: SV_Physics_Step( ent ); break; default: G_Error( "SV_Physics: bad movetype %i", (int)ent->movetype ); } }
/* * G_ClearSnap * We just run G_SnapFrame, the server just sent the snap to the clients, * it's now time to clean up snap specific data to start the next snap from clean. */ void G_ClearSnap( void ) { edict_t *ent; game.realtime = trap_Milliseconds(); // level.time etc. might not be real time // clear gametype's clock override gs.gameState.longstats[GAMELONG_CLOCKOVERRIDE] = 0; // clear all events in the snap for( ent = &game.edicts[0]; ENTNUM( ent ) < game.numentities; ent++ ) { if( ISEVENTENTITY( &ent->s ) ) // events do not persist after a snapshot { G_FreeEdict( ent ); continue; } // events only last for a single message ent->s.events[0] = ent->s.events[1] = 0; ent->s.eventParms[0] = ent->s.eventParms[1] = 0; ent->numEvents = 0; ent->eventPriority[0] = ent->eventPriority[1] = false; ent->s.teleported = qfalse; // remove teleported bit. // remove effect bits that are (most likely) added from gametypes ent->s.effects = ( ent->s.effects & (EF_TAKEDAMAGE|EF_CARRIER|EF_FLAG_TRAIL|EF_ROTATE_AND_BOB|EF_STRONG_WEAPON|EF_GHOST) ); } // recover some info, let players respawn and finally clear the snap structures for( ent = &game.edicts[0]; ENTNUM( ent ) < game.numentities; ent++ ) { if( !GS_MatchPaused() ) { // copy origin to old origin ( this old_origin is for snaps ) if( !( ent->r.svflags & SVF_TRANSMITORIGIN2 ) ) VectorCopy( ent->s.origin, ent->s.old_origin ); G_CheckClientRespawnClick( ent ); } if( GS_MatchPaused() ) ent->s.sound = entity_sound_backup[ENTNUM( ent )]; // clear the snap temp info memset( &ent->snap, 0, sizeof( ent->snap ) ); if( ent->r.client && trap_GetClientState( PLAYERNUM( ent ) ) >= CS_SPAWNED ) { memset( &ent->r.client->resp.snap, 0, sizeof( ent->r.client->resp.snap ) ); // set race stats to invisible RS_clearHUDStats( ent->r.client ); // racesow - clear with our function } } g_snapStarted = false; }
/* * SV_RunThink * * Runs thinking code for this frame if necessary */ static void SV_RunThink( edict_t *ent ) { int64_t thinktime; thinktime = ent->nextThink; if( thinktime <= 0 ) { return; } if( thinktime > level.time ) { return; } ent->nextThink = 0; if( ISEVENTENTITY( &ent->s ) ) { // events do not think return; } G_CallThink( ent ); }
/* * G_SnapFrame * It's time to send a new snap, so set the world up for sending */ void G_SnapFrame( void ) { edict_t *ent; game.realtime = trap_Milliseconds(); // level.time etc. might not be real time //others G_UpdateServerInfo(); // exit level if( level.exitNow ) { G_ExitLevel(); return; } AITools_Frame(); //MbotGame //give think time to AI debug tools // finish snap G_SnapClients(); // build the playerstate_t structures for all players G_SnapEntities(); // add effects based on accumulated info along the frame // set entity bits (prepare entities for being sent in the snap) for( ent = &game.edicts[0]; ENTNUM( ent ) < game.numentities; ent++ ) { if( ent->s.number != ENTNUM( ent ) ) { if( developer->integer ) G_Printf( "fixing ent->s.number (etype:%i, classname:%s)\n", ent->s.type, ent->classname ? ent->classname : "noclassname" ); ent->s.number = ENTNUM( ent ); } // temporary filter (Q2 system to ensure reliability) // ignore ents without visible models unless they have an effect if( !ent->r.inuse ) { ent->r.svflags |= SVF_NOCLIENT; continue; } else if( ent->s.type >= ET_TOTAL_TYPES || ent->s.type < 0 ) { if( developer->integer ) G_Printf( "'G_SnapFrame': Inhibiting invalid entity type %i\n", ent->s.type ); ent->r.svflags |= SVF_NOCLIENT; continue; } else if( !( ent->r.svflags & SVF_NOCLIENT ) && !ent->s.modelindex && !ent->s.effects && !ent->s.sound && !ISEVENTENTITY( &ent->s ) && !ent->s.light && !ent->r.client ) { if( developer->integer ) G_Printf( "'G_SnapFrame': fixing missing SVF_NOCLIENT flag (no effect)\n" ); ent->r.svflags |= SVF_NOCLIENT; continue; } ent->s.effects &= ~EF_TAKEDAMAGE; if( ent->takedamage ) ent->s.effects |= EF_TAKEDAMAGE; if( GS_MatchPaused() ) { // when in timeout, we don't send entity sounds entity_sound_backup[ENTNUM( ent )] = ent->s.sound; ent->s.sound = 0; } } }
/* * G_EdictsAddSnapEffects * add effects based on accumulated info along the server frame */ static void G_SnapEntities( void ) { 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 || ( ent->r.svflags & SVF_NOCLIENT ) ) continue; if( ent->s.type == ET_PARTICLES ) // particles use a special configuration { ent->s.frame = ent->particlesInfo.speed; ent->s.modelindex = ent->particlesInfo.shaderIndex; ent->s.modelindex2 = ent->particlesInfo.spread; ent->s.counterNum = ent->particlesInfo.time; ent->s.weapon = ent->particlesInfo.frequency; ent->s.effects = ent->particlesInfo.size & 0xFF; if( ent->particlesInfo.spherical ) ent->s.effects |= ( 1<<8 ); if( ent->particlesInfo.bounce ) ent->s.effects |= ( 1<<9 ); if( ent->particlesInfo.gravity ) ent->s.effects |= ( 1<<10 ); if( ent->particlesInfo.expandEffect ) ent->s.effects |= ( 1<<11 ); if( ent->particlesInfo.shrinkEffect ) ent->s.effects |= ( 1<<11 ); GClip_LinkEntity( ent ); continue; } if( ent->s.type == ET_PLAYER || ent->s.type == ET_CORPSE ) { // this is pretty hackish. We exploit the fact that 0.5 servers *do not* transmit // origin2/old_origin for ET_PLAYER/ET_CORPSE entities, and we use it for sending the player velocity if( !G_ISGHOSTING( ent ) ) { ent->r.svflags |= SVF_TRANSMITORIGIN2; VectorCopy( ent->velocity, ent->s.origin2 ); } else ent->r.svflags &= ~SVF_TRANSMITORIGIN2; } if( ISEVENTENTITY( ent ) || G_ISGHOSTING( ent ) || !ent->takedamage ) continue; // types which can have accumulated damage effects if( ( ent->s.type == ET_GENERIC || ent->s.type == ET_PLAYER || ent->s.type == ET_CORPSE ) ) // doors don't bleed { // 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->snap.damage_taken += ent->snap.damage_saved; //ent->snap.damage_saved = 0; //spawn accumulated damage if( ent->snap.damage_taken && !( ent->flags & FL_GODMODE ) && HEALTH_TO_INT( ent->health ) > 0 ) { edict_t *event; float damage = ent->snap.damage_taken; if( damage > 120 ) damage = 120; VectorCopy( ent->snap.damage_dir, dir ); VectorNormalize( dir ); VectorAdd( ent->s.origin, ent->snap.damage_at, origin ); if( ent->s.type == ET_PLAYER || ent->s.type == ET_CORPSE ) { event = G_SpawnEvent( EV_BLOOD, DirToByte( dir ), origin ); event->s.damage = HEALTH_TO_INT( damage ); event->s.ownerNum = i; // set owner // ET_PLAYERS can also spawn sound events if( ent->s.type == ET_PLAYER ) { // play an apropriate pain sound if( level.time >= ent->pain_debounce_time ) { // see if it should pain for a FALL or for damage received if( ent->snap.damage_fall ) { ent->pain_debounce_time = level.time + 200; } else if( !G_IsDead( ent ) ) { if( ent->r.client->ps.inventory[POWERUP_SHELL] > 0 ) G_AddEvent( ent, EV_PAIN, PAIN_WARSHELL, true ); else if( ent->health <= 20 ) G_AddEvent( ent, EV_PAIN, PAIN_20, true ); else if( ent->health <= 35 ) G_AddEvent( ent, EV_PAIN, PAIN_30, true ); else if( ent->health <= 60 ) G_AddEvent( ent, EV_PAIN, PAIN_60, true ); else G_AddEvent( ent, EV_PAIN, PAIN_100, true ); ent->pain_debounce_time = level.time + 400; } } } } else { event = G_SpawnEvent( EV_SPARKS, DirToByte( dir ), origin ); event->s.damage = HEALTH_TO_INT( damage ); } } } } }