/* ================ G_FindTeams Chain together all entities with a matching team field. Entity teams are used for item groups and multi-entity mover groups. All but the first will have the FL_TEAMSLAVE flag set and teammaster field set All but the last will have the teamchain field set to the next one ================ */ void G_FindTeams( void ) { gentity_t *e, *e2; int i, j; int c, c2; c = 0; c2 = 0; // for ( i=1, e=g_entities,i ; i < globals.num_entities ; i++,e++ ) for ( i=1 ; i < globals.num_entities ; i++ ) { // if (!e->inuse) // continue; if(!PInUse(i)) continue; e=&g_entities[i]; if (!e->team) continue; if (e->flags & FL_TEAMSLAVE) continue; e->teammaster = e; c++; c2++; // for (j=i+1, e2=e+1 ; j < globals.num_entities ; j++,e2++) for (j=i+1; j < globals.num_entities ; j++) { // if (!e2->inuse) // continue; if(!PInUse(j)) continue; e2=&g_entities[j]; if (!e2->team) continue; if (e2->flags & FL_TEAMSLAVE) continue; if (!strcmp(e->team, e2->team)) { c2++; e2->teamchain = e->teamchain; e->teamchain = e2; e2->teammaster = e; e2->flags |= FL_TEAMSLAVE; // make sure that targets only point at the master if ( e2->targetname ) { e->targetname = e2->targetname; e2->targetname = NULL; } } } } gi.Printf ("%i teams with %i entities\n", c, c2); }
void ValidateInUseBits(void) { for(int i=0;i<MAX_GENTITIES;i++) { assert(g_entities[i].inuse==PInUse(i)); } }
/* ============= G_Find Searches all active entities for the next one that holds the matching string at fieldofs (use the FOFS() macro) in the structure. Searches beginning at the entity after from, or the beginning if NULL NULL will be returned if the end of the list is reached. ============= */ gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match) { char *s; if(!match || !match[0]) { return NULL; } if (!from) from = g_entities; else from++; // for ( ; from < &g_entities[globals.num_entities] ; from++) int i=from-g_entities; for ( ; i < globals.num_entities ; i++) { // if (!from->inuse) if(!PInUse(i)) continue; from=&g_entities[i]; s = *(char **) ((byte *)from + fieldofs); if (!s) continue; if (!Q_stricmp (s, match)) return from; } return NULL; }
void ReadInUseBits(void) { gi.ReadFromSaveGame('INUS', &g_entityInUseBits, sizeof(g_entityInUseBits)); // This is only temporary. Once I have converted all the ent->inuse refs, // it won;t be needed -MW. for(int i=0;i<MAX_GENTITIES;i++) { g_entities[i].inuse=PInUse(i); } }
void ReadInUseBits() { ojk::SavedGameHelper saved_game( ::gi.saved_game); saved_game.read_chunk<uint32_t>( INT_ID('I', 'N', 'U', 'S'), ::g_entityInUseBits); // This is only temporary. Once I have converted all the ent->inuse refs, // it won;t be needed -MW. for(int i=0;i<MAX_GENTITIES;i++) { g_entities[i].inuse=PInUse(i); } }
/* ================== Cmd_Where_f ================== */ void Cmd_Where_f( gentity_t *ent ) { const char *s = gi.argv(1); const int len = strlen(s); gentity_t *check; if ( gi.argc () < 2 ) { gi.Printf("usage: where classname\n"); return; } for (int i = 0; i < globals.num_entities; i++) { if(!PInUse(i)) continue; // if(!check || !check->inuse) { // continue; // } check = &g_entities[i]; if (!Q_stricmpn(s, check->classname, len) ) { gi.SendServerCommand( ent-g_entities, "print \"%s %s\n\"", check->classname, vtos( check->s.pos.trBase ) ); } } }
//#define MAX_WAITERS 128 void AI_GetGroup( gentity_t *self ) { int i; gentity_t *member;//, *waiter; //int waiters[MAX_WAITERS]; if ( !self || !self->NPC ) { return; } if ( d_noGroupAI->integer ) { self->NPC->group = NULL; return; } if ( !self->client ) { self->NPC->group = NULL; return; } if ( self->NPC->scriptFlags&SCF_NO_GROUPS ) { self->NPC->group = NULL; return; } if ( self->enemy && (!self->enemy->client || (level.time - self->NPC->enemyLastSeenTime > 7000 ))) { self->NPC->group = NULL; return; } if ( !AI_GetNextEmptyGroup( self ) ) {//either no more groups left or we're already in a group built earlier return; } //create a new one memset( self->NPC->group, 0, sizeof( AIGroupInfo_t ) ); self->NPC->group->enemy = self->enemy; self->NPC->group->team = self->client->playerTeam; self->NPC->group->processed = qfalse; self->NPC->group->commander = self; self->NPC->group->memberValidateTime = level.time + 2000; self->NPC->group->activeMemberNum = 0; if ( self->NPC->group->enemy ) { self->NPC->group->lastSeenEnemyTime = level.time; self->NPC->group->lastClearShotTime = level.time; VectorCopy( self->NPC->group->enemy->currentOrigin, self->NPC->group->enemyLastSeenPos ); } // for ( i = 0, member = &g_entities[0]; i < globals.num_entities ; i++, member++) for ( i = 0; i < globals.num_entities ; i++) { if(!PInUse(i)) continue; member = &g_entities[i]; if ( !AI_ValidateGroupMember( self->NPC->group, member ) ) {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group continue; } //store it AI_InsertGroupMember( self->NPC->group, member ); if ( self->NPC->group->numGroup >= (MAX_GROUP_MEMBERS - 1) ) {//full break; } } /* //now go through waiters and see if any should join the group //NOTE: Some should hang back and probably not attack, so we can ambush //NOTE: only do this if calling for reinforcements? for ( i = 0; i < numWaiters; i++ ) { waiter = &g_entities[waiters[i]]; for ( j = 0; j < self->NPC->group->numGroup; j++ ) { member = &g_entities[self->NPC->group->member[j]; if ( gi.inPVS( waiter->currentOrigin, member->currentOrigin ) ) {//this waiter is within PVS of a current member } } } */ if ( self->NPC->group->numGroup <= 0 ) {//none in group self->NPC->group = NULL; return; } AI_SortGroupByPathCostToEnemy( self->NPC->group ); AI_SetClosestBuddy( self->NPC->group ); }
void G_RunFrame( int levelTime ) { int i; gentity_t *ent; int msec; int ents_inuse=0; // someone's gonna be pissed I put this here... #if AI_TIMERS AITime = 0; navTime = 0; #endif// AI_TIMERS level.framenum++; level.previousTime = level.time; level.time = levelTime; msec = level.time - level.previousTime; NAV_CheckCalcPaths(); //ResetTeamCounters(); AI_UpdateGroups(); if ( d_altRoutes->integer ) { navigator.CheckAllFailedEdges(); } navigator.ClearCheckedNodes(); //remember last waypoint, clear current one // for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++) for ( i = 0; i < globals.num_entities ; i++) { // if ( !ent->inuse ) // continue; if(!PInUse(i)) continue; ent = &g_entities[i]; if ( ent->waypoint != WAYPOINT_NONE && ent->noWaypointTime < level.time ) { ent->lastWaypoint = ent->waypoint; ent->waypoint = WAYPOINT_NONE; } if ( d_altRoutes->integer ) { navigator.CheckFailedNodes( ent ); } } //Look to clear out old events ClearPlayerAlertEvents(); //Run the frame for all entities // for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++) for ( i = 0; i < globals.num_entities ; i++) { // if ( !ent->inuse ) // continue; if(!PInUse(i)) continue; ents_inuse++; ent = &g_entities[i]; // clear events that are too old if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) { if ( ent->s.event ) { ent->s.event = 0; // &= EV_EVENT_BITS; if ( ent->client ) { ent->client->ps.externalEvent = 0; } } if ( ent->freeAfterEvent ) { // tempEntities or dropped items completely go away after their event G_FreeEntity( ent ); continue; } else if ( ent->unlinkAfterEvent ) { // items that will respawn will hide themselves after their pickup event ent->unlinkAfterEvent = qfalse; gi.unlinkentity( ent ); } } // temporary entities don't think if ( ent->freeAfterEvent ) continue; G_CheckTasksCompleted(ent); G_Roff( ent ); if( !ent->client ) { if ( !(ent->svFlags & SVF_SELF_ANIMATING) ) {//FIXME: make sure this is done only for models with frames? //Or just flag as animating? if ( ent->s.eFlags & EF_ANIM_ONCE ) { ent->s.frame++; } else if ( !(ent->s.eFlags & EF_ANIM_ALLFAST) ) { G_Animate( ent ); } } } G_CheckSpecialPersistentEvents( ent ); if ( ent->s.eType == ET_MISSILE ) { G_RunMissile( ent ); continue; } if ( ent->s.eType == ET_ITEM ) { G_RunItem( ent ); continue; } if ( ent->s.eType == ET_MOVER ) { if ( ent->model && Q_stricmp( "models/test/mikeg/tie_fighter.md3", ent->model ) == 0 ) { TieFighterThink( ent ); } G_RunMover( ent ); continue; } //The player if ( i == 0 ) { // decay batteries if the goggles are active if ( cg.zoomMode == 1 && ent->client->ps.batteryCharge > 0 ) { ent->client->ps.batteryCharge--; } else if ( cg.zoomMode == 3 && ent->client->ps.batteryCharge > 0 ) { ent->client->ps.batteryCharge -= 2; if ( ent->client->ps.batteryCharge < 0 ) { ent->client->ps.batteryCharge = 0; } } G_CheckEndLevelTimers( ent ); //Recalculate the nearest waypoint for the coming NPC updates NAV_FindPlayerWaypoint(); if( ent->taskManager && !stop_icarus ) { ent->taskManager->Update(); } //dead if ( ent->health <= 0 ) { if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//on the ground pitch_roll_for_slope( ent, NULL ); } } continue; // players are ucmd driven } G_RunThink( ent ); // be aware that ent may be free after returning from here, at least one func frees them ClearNPCGlobals(); // but these 2 funcs are ok //UpdateTeamCounters( ent ); // to call anyway on a freed ent. } // perform final fixups on the player ent = &g_entities[0]; if ( ent->inuse ) { ClientEndFrame( ent ); } if( g_numEntities->integer ) { gi.Printf( S_COLOR_WHITE"Number of Entities in use : %d\n", ents_inuse ); } //DEBUG STUFF NAV_ShowDebugInfo(); NPC_ShowDebugInfo(); G_DynamicMusicUpdate(); #if AI_TIMERS AITime -= navTime; if ( AITime > 20 ) { gi.Printf( S_COLOR_RED"ERROR: total AI time: %d\n", AITime ); } else if ( AITime > 10 ) { gi.Printf( S_COLOR_YELLOW"WARNING: total AI time: %d\n", AITime ); } else if ( AITime > 2 ) { gi.Printf( S_COLOR_GREEN"total AI time: %d\n", AITime ); } if ( navTime > 20 ) { gi.Printf( S_COLOR_RED"ERROR: total nav time: %d\n", navTime ); } else if ( navTime > 10 ) { gi.Printf( S_COLOR_YELLOW"WARNING: total nav time: %d\n", navTime ); } else if ( navTime > 2 ) { gi.Printf( S_COLOR_GREEN"total nav time: %d\n", navTime ); } #endif// AI_TIMERS #ifndef FINAL_BUILD if ( delayedShutDown != 0 && delayedShutDown < level.time ) { G_Error( "Game Errors. Scroll up the console to read them.\n" ); } #endif #ifdef _DEBUG if(!(level.framenum&0xff)) { ValidateInUseBits(); } #endif }
/* ================= G_Spawn Either finds a free entity, or allocates a new one. The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will never be used by anything else. Try to avoid reusing an entity that was recently freed, because it can cause the client to think the entity morphed into something else instead of being removed and recreated, which can cause interpolated angles and bad trails. ================= */ gentity_t *G_Spawn( void ) { int i, force; gentity_t *e; e = NULL; // shut up warning i = 0; // shut up warning for ( force = 0 ; force < 2 ; force++ ) { // if we go through all entities and can't find one to free, // override the normal minimum times before use e = &g_entities[MAX_CLIENTS]; // for ( i = MAX_CLIENTS ; i<globals.num_entities ; i++, e++) // { // if ( e->inuse ) // { // continue; // } for ( i = MAX_CLIENTS ; i<globals.num_entities ; i++) { if(PInUse(i)) { continue; } e=&g_entities[i]; // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy if ( !force && e->freetime > 2000 && level.time - e->freetime < 1000 ) { continue; } // reuse this slot G_InitGentity( e ); return e; } e=&g_entities[i]; if ( i != ENTITYNUM_MAX_NORMAL ) { break; } } if ( i == ENTITYNUM_MAX_NORMAL ) { /* e = &g_entities[0]; //--------------Use this to dump directly to a file char buff[256]; FILE *fp; fp = fopen( "c:/dump.txt", "w" ); for ( i = 0 ; i<globals.num_entities ; i++, e++) { if ( e->classname ) { sprintf( buff, "%d: %s\n", i, e->classname ); } fputs( buff, fp ); } fclose( fp ); //---------------Or use this to dump to the console -- beware though, the console will fill quickly and you probably won't see the full list for ( i = 0 ; i<globals.num_entities ; i++, e++) { if ( e->classname ) { Com_Printf( "%d: %s\n", i, e->classname ); } } */ G_Error( "G_Spawn: no free entities" ); } // open up a new slot globals.num_entities++; G_InitGentity( e ); return e; }