/** * @sa SV_RunGameFrame * @sa G_MatchEndTrigger * @sa AI_Run * @return true if game reaches its end - false otherwise */ static bool G_RunFrame (void) { level.framenum++; /* server is running at 10 fps */ level.time = level.framenum * SERVER_FRAME_SECONDS; /* this doesn't belong here, but it works */ if (!level.routed) { level.routed = true; G_CompleteRecalcRouting(); } /* still waiting for other players */ if (!G_MatchIsRunning()) { if (sv_maxteams->modified) { /* inform the client */ gi.ConfigString(CS_MAXTEAMS, "%i", sv_maxteams->integer); sv_maxteams->modified = false; } } if (G_IsMultiPlayer()) { if (sv_roundtimelimit->modified) { /* some played around here - restart the count down */ level.roundstartTime = level.time; /* don't allow smaller values here */ if (sv_roundtimelimit->integer < 30 && sv_roundtimelimit->integer > 0) { gi.DPrintf("The minimum value for sv_roundtimelimit is 30\n"); gi.Cvar_Set("sv_roundtimelimit", "30"); } sv_roundtimelimit->modified = false; } G_CheckForceEndRound(); } /* end this game? */ if (G_MatchDoEnd()) return true; CheckNeedPass(); /* run ai */ AI_Run(); /* not all teams are spawned or game has already ended */ if (G_MatchIsRunning()) G_EdictsThink(); G_SendBoundingBoxes(); return false; }
/** * @brief Checks whether the actor is allowed to activate reaction fire and will informs the player about * the reason if this would not work. * @param[in] ent The actor to check * @return @c true if the actor is allowed to activate it, @c false otherwise */ static bool G_ReactionFireCanBeEnabled (const edict_t *ent) { /* check ent is a suitable shooter */ if (!ent->inuse || !G_IsLivingActor(ent)) return false; if (G_MatchIsRunning() && ent->team != level.activeTeam) return false; /* actor may not carry weapons at all - so no further checking is needed */ if (!ent->chr.teamDef->weapons) return false; if (!G_ActorHasReactionFireEnabledWeapon(ent)) { G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("No reaction fire enabled weapon.")); return false; } if (!G_ActorHasWorkingFireModeSet(ent)) { G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("No fire mode selected for reaction fire.")); return false; } if (!G_ActorHasEnoughTUsReactionFire(ent)) { G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("Not enough TUs left for activating reaction fire.")); return false; } return true; }
/** * @brief called whenever the player updates a userinfo variable. * @note The game can override any of the settings in place (forcing skins or names, etc) before copying it off. */ void G_ClientUserinfoChanged (player_t * player, const char *userinfo) { const bool alreadyReady = player->isReady; const int oldTeamnum = Info_IntegerForKey(player->pers.userinfo, "cl_teamnum"); /* check for malformed or illegal info strings */ if (!Info_Validate(userinfo)) userinfo = "\\cl_name\\badinfo"; /* set name */ Q_strncpyz(player->pers.netname, Info_ValueForKey(userinfo, "cl_name"), sizeof(player->pers.netname)); Q_strncpyz(player->pers.userinfo, userinfo, sizeof(player->pers.userinfo)); player->autostand = Info_IntegerForKey(userinfo, "cl_autostand"); player->reactionLeftover = Info_IntegerForKey(userinfo, "cl_reactionleftover"); player->isReady = Info_IntegerForKey(userinfo, "cl_ready"); /* send the updated config string */ gi.ConfigString(CS_PLAYERNAMES + player->num, "%s", player->pers.netname); /* try to update to the preferred team */ if (!G_MatchIsRunning() && oldTeamnum != Info_IntegerForKey(userinfo, "cl_teamnum")) { /* if the player is marked as ready he can't change his team */ if (!alreadyReady || !player->isReady) { player->pers.team = TEAM_NO_ACTIVE; G_GetTeam(player); } else { Com_DPrintf(DEBUG_GAME, "G_ClientUserinfoChanged: player %s is already marked as being ready\n", player->pers.netname); } } }
/** * @brief Check whether a forced turn end should be executed */ void G_CheckForceEndRound (void) { /* check for roundlimits in multiplayer only */ if (!sv_roundtimelimit->integer || G_IsSinglePlayer()) return; if (!G_MatchIsRunning()) return; if (level.time != ceil(level.time)) return; const int diff = level.roundstartTime + sv_roundtimelimit->integer - level.time; switch (diff) { case 240: gi.BroadcastPrintf(PRINT_HUD, _("4 minutes left until forced turn end.")); return; case 180: gi.BroadcastPrintf(PRINT_HUD, _("3 minutes left until forced turn end.")); return; case 120: gi.BroadcastPrintf(PRINT_HUD, _("2 minutes left until forced turn end.")); return; case 60: gi.BroadcastPrintf(PRINT_HUD, _("1 minute left until forced turn end.")); return; case 30: gi.BroadcastPrintf(PRINT_HUD, _("30 seconds left until forced turn end.")); return; case 15: gi.BroadcastPrintf(PRINT_HUD, _("15 seconds left until forced turn end.")); return; } /* active team still has time left */ if (level.time < level.roundstartTime + sv_roundtimelimit->integer) return; gi.BroadcastPrintf(PRINT_HUD, _("Current active team hit the max round time.")); /* store this in a local variable, as the global variable is changed in G_ClientEndRound */ const int activeTeam = level.activeTeam; /* set all team members to ready (only human players) */ Player* p = nullptr; while ((p = G_PlayerGetNextActiveHuman(p))) { if (p->getTeam() == activeTeam) { G_ClientEndRound(*p); level.nextEndRound = level.framenum; } } level.roundstartTime = level.time; }
/** * @brief Chose a team that should start the match * @param[in] player In singleplayer mode the team of this player will get the first turn * @sa SVCmd_StartGame_f */ static void G_GetStartingTeam (const player_t* player) { int teamCount; int playerCount; int knownTeams[MAX_TEAMS]; player_t *p; /* return with no action if activeTeam already assigned or if are in single-player mode */ if (G_MatchIsRunning()) return; if (sv_maxclients->integer == 1) { level.activeTeam = player->pers.team; level.teamOfs = MAX_TEAMS - level.activeTeam; return; } /* count number of currently connected unique teams and players (human controlled players only) */ p = NULL; teamCount = 0; playerCount = 0; while ((p = G_PlayerGetNextActiveHuman(p))) { int j; playerCount++; for (j = 0; j < teamCount; j++) { if (p->pers.team == knownTeams[j]) break; } if (j == teamCount) knownTeams[teamCount++] = p->pers.team; } if (teamCount) { const int teamIndex = (int) (frand() * (teamCount - 1) + 0.5); G_PrintStats("Starting new game: %s with %i teams", level.mapname, teamCount); level.activeTeam = knownTeams[teamIndex]; level.teamOfs = MAX_TEAMS - level.activeTeam; p = NULL; while ((p = G_PlayerGetNextActiveHuman(p))) if (p->pers.team != level.activeTeam) p->roundDone = true; } }
/** * @brief Handles door and other objects * @sa G_RunFrame */ void G_PhysicsRun (void) { edict_t *ent = NULL; /* not all teams are spawned or game has already ended */ if (!G_MatchIsRunning()) return; #if 0 /* taken out - otherwise footstep sounds are too slow */ /* don't run this too often to prevent overflows */ if (level.framenum % 10) return; #endif /* treat each object in turn */ /* even the world gets a chance to think */ while ((ent = G_EdictsGetNextInUse(ent))) { if (ent->think) G_PhysicsThink(ent); } }
/** * @note Think functions are only executed when the match is running * or in other word, the game has started */ void G_MissionThink (Edict* self) { if (!G_MatchIsRunning()) return; /* when every player has joined the match - spawn the mission target * particle (if given) to mark the trigger */ if (self->particle) { self->link = G_SpawnParticle(self->origin, self->spawnflags, self->particle); /* This is automatically freed on map shutdown */ self->particle = nullptr; } Edict* chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { if (chain->item) { const Item* ic; G_GetFloorItems(chain); ic = chain->getFloor(); if (!ic) { /* reset the counter if there is no item */ chain->count = 0; return; } for (; ic; ic = ic->getNext()) { const objDef_t* od = ic->def(); assert(od); /* not the item we are looking for */ if (Q_streq(od->id, chain->item)) break; } if (!ic) { /* reset the counter if it's not the searched item */ chain->count = 0; return; } } if (chain->time) { /* Check that the target zone is still occupied (last defender might have died) */ if (!chain->item && !G_MissionIsTouched(chain)) { chain->count = 0; } const int endTime = level.actualRound - chain->count; const int spawnIndex = (chain->getTeam() + level.teamOfs) % MAX_TEAMS; const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS; /* not every edict in the group chain has * been occupied long enough */ if (!chain->count || endTime < chain->time || (endTime == chain->time && spawnIndex < currentIndex)) return; } if (chain->target && !chain->time && !chain->item) { if (!G_MissionIsTouched(chain)) return; } } chain = chain->groupChain; } const bool endMission = self->target == nullptr; /* store team before the edict is released */ const int team = self->getTeam(); chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { G_UseEdict(chain, nullptr); if (chain->item != nullptr) { Edict* item = G_GetEdictFromPos(chain->pos, ET_ITEM); if (item != nullptr) { if (!G_InventoryRemoveItemByID(chain->item, item, CID_FLOOR)) { Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->getIdNum()); } else if (!item->getFloor()) { G_EventPerish(*item); G_FreeEdict(item); } } } if (chain->link != nullptr) { Edict* particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE); if (particle != nullptr) { G_AppearPerishEvent(G_VisToPM(particle->visflags), false, *particle, nullptr); G_FreeEdict(particle); } chain->link = nullptr; } /* Display mission message */ if (G_ValidMessage(chain)) { const char* msg = chain->message; if (msg[0] == '_') ++msg; gi.BroadcastPrintf(PRINT_HUD, "%s", msg); } } Edict* ent = chain->groupChain; /* free the group chain */ G_FreeEdict(chain); chain = ent; } if (endMission) G_MatchEndTrigger(team, level.activeTeam == TEAM_ALIEN ? 10 : 3); }
/** * @brief Opens/closes a door * @note Use function for func_door * @todo Check if the door can be opened or closed - there should not be * anything in the way (e.g. an actor) */ static bool Door_Use (edict_t *door, edict_t *activator) { if (door->doorState == STATE_CLOSED) { door->doorState = STATE_OPENED; /* change rotation/origin and relink */ if (door->type == ET_DOOR) { if (door->dir & DOOR_OPEN_REVERSE) door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE; else door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE; } else if (door->type == ET_DOOR_SLIDING) { Door_SlidingUse(door); } gi.LinkEdict(door); /* maybe the server called this because the door starts opened */ if (G_MatchIsRunning()) { /* let everybody know, that the door opens */ G_EventDoorOpen(door); if (door->noise[0] != '\0') G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise); } } else if (door->doorState == STATE_OPENED) { door->doorState = STATE_CLOSED; /* change rotation and relink */ if (door->type == ET_DOOR) { if (door->dir & DOOR_OPEN_REVERSE) door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE; else door->angles[door->dir & 3] -= DOOR_ROTATION_ANGLE; } else if (door->type == ET_DOOR_SLIDING) { Door_SlidingUse(door); } gi.LinkEdict(door); /* closed is the standard, opened is handled above - we need an active * team here already */ if (G_MatchIsRunning()) { /* let everybody know, that the door closes */ G_EventDoorClose(door); if (door->noise[0] != '\0') G_EventSpawnSound(PM_ALL, false, door, door->origin, door->noise); } } else return false; /* Update model orientation */ gi.SetInlineModelOrientation(door->model, door->origin, door->angles); Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n"); /* Update path finding table */ G_RecalcRouting(door->model); if (activator && G_IsLivingActor(activator)) { /* Check if the player appears/perishes, seen from other teams. */ G_CheckVis(activator, true); /* Calc new vis for the activator. */ G_CheckVisTeamAll(activator->team, false, activator); } return true; }
/** * @sa G_PlayerSoldiersCount */ void G_ClientEndRound (Player& player) { Player* p; const int lastTeamIndex = (G_GetActiveTeam() + level.teamOfs) % MAX_TEAMS; if (!G_IsAIPlayer(&player)) { /* inactive players can't end their inactive turn :) */ if (level.activeTeam != player.getTeam()) return; /* check for "team oszillation" */ if (level.framenum < level.nextEndRound) return; level.nextEndRound = level.framenum + 20; } /* only use this for teamplay matches like coopX or fight2on2 and above * also skip this for ai players, this is only called when all ai actors * have finished their 'thinking' */ if (!G_IsAIPlayer(&player) && sv_teamplay->integer) { /* check if all team members are ready */ if (!player.roundDone) { player.roundDone = true; G_EventEndRoundAnnounce(player); G_EventEnd(); } p = nullptr; while ((p = G_PlayerGetNextActiveHuman(p))) if (p->getTeam() == level.activeTeam && !p->roundDone && G_PlayerSoldiersCount(*p) > 0) return; p = nullptr; while ((p = G_PlayerGetNextActiveAI(p))) if (p->getTeam() == level.activeTeam && !p->roundDone && G_PlayerSoldiersCount(*p) > 0) return; } else { player.roundDone = true; } /* clear any remaining reaction fire */ G_ReactionFireOnEndTurn(); if (!G_IsAIPlayer(&player)) { if (g_lastseen->integer > 0) { Edict* ent = nullptr; while ((ent = G_EdictsGetNextActor(ent))) { if (G_IsAI(ent) && G_IsVisibleForTeam(ent, level.activeTeam)) { player.lastSeen = level.actualRound; break; } } if (level.actualRound - player.lastSeen > g_lastseen->integer) { Com_Printf("round end triggered by g_lastseen (player %i (team %i) last seen in round %i of %i rounds)\n", player.getNum(), level.activeTeam, player.lastSeen, level.actualRound); G_MatchEndTrigger(-1, 0); } } } /* let all the invisible players perish now */ G_CheckVisTeamAll(level.activeTeam, VIS_APPEAR, nullptr); G_GetNextActiveTeam(); AI_CheckRespawn(TEAM_ALIEN); /* no other team left? */ if (!G_MatchIsRunning()) return; if (lastTeamIndex > (level.activeTeam + level.teamOfs) % MAX_TEAMS) level.actualRound++; /* communicate next player in row to clients */ G_EventEndRound(); /* store the round start time to be able to abort the round after a give time */ level.roundstartTime = level.time; /* Wounded team members bleed */ G_BleedWounds(level.activeTeam); /* Update the state of stuned team-members. The actual statistics are sent below! */ G_UpdateStunState(level.activeTeam); /* Give the actors of the now active team their TUs. */ G_GiveTimeUnits(level.activeTeam); /* apply morale behaviour, reset reaction fire */ G_ReactionFireReset(level.activeTeam); if (mor_panic->integer) G_MoraleBehaviour(level.activeTeam); G_UpdateCarriedWeight(level.activeTeam); /* start ai - there is only one player for ai teams, and the last pointer must only * be updated for ai players */ p = G_GetPlayerForTeam(level.activeTeam); if (p == nullptr) gi.Error("Could not find player for team %i", level.activeTeam); /* finish off events */ G_EventEnd(); /* reset ready flag for every player on the current team (even ai players) */ p = nullptr; while ((p = G_PlayerGetNextActiveHuman(p))) { if (p->getTeam() == level.activeTeam) { p->roundDone = false; } } p = nullptr; while ((p = G_PlayerGetNextActiveAI(p))) { if (p->getTeam() == level.activeTeam) { p->roundDone = false; } } }
/** * @note Think functions are only executed when the match is running * or in other word, the game has started */ void G_MissionThink (Edict* self) { if (!G_MatchIsRunning()) return; /* when every player has joined the match - spawn the mission target * particle (if given) to mark the trigger */ if (self->particle) { self->link = G_SpawnParticle(self->origin, self->spawnflags, self->particle); /* This is automatically freed on map shutdown */ self->particle = nullptr; } Edict* chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { if (chain->item) { const Item* ic; G_GetFloorItems(chain); ic = chain->getFloor(); if (!ic) { /* reset the counter if there is no item */ chain->count = 0; return; } for (; ic; ic = ic->getNext()) { const objDef_t* od = ic->def(); assert(od); /* not the item we are looking for */ if (Q_streq(od->id, chain->item)) break; } if (!ic) { /* reset the counter if it's not the searched item */ chain->count = 0; return; } } if (chain->time) { const int endTime = level.actualRound - chain->count; const int spawnIndex = (self->getTeam() + level.teamOfs) % MAX_TEAMS; const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS; /* not every edict in the group chain has * been occupied long enough */ if (!chain->count || endTime < chain->time || (endTime == level.actualRound && spawnIndex <= currentIndex)) return; } /* not destroyed yet */ if ((chain->flags & FL_DESTROYABLE) && chain->HP) return; } chain = chain->groupChain; } if (self->use) self->use(self, nullptr); /* store team before the edict is released */ const int team = self->getTeam(); chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->item != nullptr) { Edict* item = G_GetEdictFromPos(chain->pos, ET_ITEM); if (item != nullptr) { if (!G_InventoryRemoveItemByID(chain->item, item, CID_FLOOR)) { Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->getIdNum()); } else if (!item->getFloor()) { G_EventPerish(*item); G_FreeEdict(item); } } } if (chain->link != nullptr) { Edict* particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE); if (particle != nullptr) { G_AppearPerishEvent(PM_ALL, false, *particle, nullptr); G_FreeEdict(particle); } chain->link = nullptr; } Edict* ent = chain->groupChain; /* free the trigger */ if (chain->child()) G_FreeEdict(chain->child()); /* free the group chain */ G_FreeEdict(chain); chain = ent; } self = nullptr; /* still active mission edicts left */ Edict* ent = nullptr; while ((ent = G_EdictsGetNextInUse(ent))) if (ent->type == ET_MISSION && ent->getTeam() == team) return; G_MatchEndTrigger(team, 10); }
/** * @brief Opens/closes a door * @note Use function for func_door * @todo Check if the door can be opened or closed - there should not be * anything in the way (e.g. an actor) */ static bool Door_Use (edict_t *door, edict_t *activator) { int opening = 1; if (door->doorState == STATE_CLOSED) { door->doorState = STATE_OPENED; opening = 1; } else if (door->doorState == STATE_OPENED) { door->doorState = STATE_CLOSED; opening = -1; } else return false; /* remember the old location */ AABB oldAABB; gi.GetInlineModelAABB(door->model, oldAABB); GridBox rerouteOldBox(oldAABB); /* change rotation and relink */ if (door->type == ET_DOOR) { if (door->dir & DOOR_OPEN_REVERSE) opening *= -1; door->angles[door->dir & 3] += DOOR_ROTATION_ANGLE * opening; } else if (door->type == ET_DOOR_SLIDING) { Door_SlidingUse(door); } gi.LinkEdict(door); /* maybe the server called this because the door starts opened */ if (G_MatchIsRunning()) { /* let everybody know, that the door moves */ if (door->doorState == STATE_OPENED) G_EventDoorOpen(door); else G_EventDoorClose(door); if (door->noise[0] != '\0') { const playermask_t playerMask = G_GetClosePlayerMask(door->origin, UNIT_SIZE * 10); G_EventSpawnSound(playerMask, false, door, door->origin, door->noise); } } /* Update model orientation */ gi.SetInlineModelOrientation(door->model, door->origin, door->angles); AABB newAabb; gi.GetInlineModelAABB(door->model, newAabb); GridBox rerouteNewBox(newAabb); Com_DPrintf(DEBUG_GAME, "Server processed door movement.\n"); /* Update path finding table for the new location of the model */ G_RecalcRouting(door->model, rerouteOldBox); /* Update path finding table */ G_RecalcRouting(door->model, rerouteNewBox); if (activator && G_IsLivingActor(activator)) { /* Check if the player appears/perishes, seen from other teams. */ G_CheckVis(activator); /* Calc new vis for the activator. */ G_CheckVisTeamAll(activator->team, 0, activator); } return true; }
/** * @note Think functions are only executed when the match is running * or in other word, the game has started */ void G_MissionThink (edict_t *self) { edict_t *chain = self->groupMaster; edict_t *ent; int team; if (!G_MatchIsRunning()) return; /* when every player has joined the match - spawn the mission target * particle (if given) to mark the trigger */ if (self->particle) { G_SpawnParticle(self->origin, self->spawnflags, self->particle); /* This is automatically freed on map shutdown */ self->particle = NULL; } if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { if (chain->item) { const invList_t *ic; G_GetFloorItems(chain); ic = FLOOR(chain); if (!ic) { /* reset the counter if there is no item */ chain->count = 0; return; } for (; ic; ic = ic->next) { const objDef_t *od = ic->item.t; assert(od); /* not the item we are looking for */ if (Q_streq(od->id, chain->item)) break; } if (!ic) { /* reset the counter if it's not the searched item */ chain->count = 0; return; } } if (chain->time) { /* not every edict in the group chain has * been occupied long enough */ if (!chain->count || level.actualRound - chain->count < chain->time) return; } /* not destroyed yet */ if ((chain->flags & FL_DESTROYABLE) && chain->HP) return; } chain = chain->groupChain; } if (self->use) self->use(self, NULL); /* store team before the edict is released */ team = self->team; chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->item != NULL) { edict_t *item = G_GetEdictFromPos(chain->pos, ET_ITEM); if (item != NULL) { if (!G_InventoryRemoveItemByID(chain->item, item, gi.csi->idFloor)) { Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->number); } else { G_AppearPerishEvent(G_VisToPM(item->visflags), false, item, NULL); } } } if (chain->particle != NULL) { /** @todo not yet working - particle stays active */ edict_t *particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE); if (particle != NULL) { G_AppearPerishEvent(PM_ALL, false, particle, NULL); G_FreeEdict(particle); } } ent = chain->groupChain; /* free the trigger */ if (chain->child) G_FreeEdict(chain->child); /* free the group chain */ G_FreeEdict(chain); chain = ent; } self = NULL; /* still active mission edicts left */ ent = NULL; while ((ent = G_EdictsGetNextInUse(ent))) if (ent->type == ET_MISSION && ent->team == team) return; G_MatchEndTrigger(team, 10); }