static GameLoopResult DogfightScoresUpdate(GameLoopData *data, LoopRunner *l) { PlayerList *pl = data->Data; const GameLoopResult result = MenuUpdate(&pl->ms); if (result == UPDATE_RESULT_OK) { // Calculate PVP rounds won int maxScore = 0; CA_FOREACH(PlayerData, p, gPlayerDatas) if (IsPlayerAlive(p)) { p->Totals.Score++; maxScore = MAX(maxScore, p->Totals.Score); } CA_FOREACH_END() gCampaign.IsComplete = maxScore == ModeMaxRoundsWon(gCampaign.Entry.Mode); CASSERT(maxScore <= ModeMaxRoundsWon(gCampaign.Entry.Mode), "score exceeds max rounds won"); if (gCampaign.IsComplete) { LoopRunnerChange(l, ScreenDogfightFinalScores()); } else { LoopRunnerChange( l, HighScoresScreen(&gCampaign, &gGraphicsDevice)); } }
static bool CanSeeAPlayer(const TActor *a) { const Vec2i realPos = Vec2iFull2Real(a->Pos); CA_FOREACH(const PlayerData, p, gPlayerDatas) if (!IsPlayerAlive(p)) { continue; } const TActor *player = ActorGetByUID(p->ActorUID); const Vec2i playerRealPos = Vec2iFull2Real(player->Pos); // Can see player if: // - Clear line of sight, and // - If they are close, or if facing and they are not too far if (!AIHasClearShot(realPos, playerRealPos)) { continue; } const int distance = CHEBYSHEV_DISTANCE( realPos.x, realPos.y, playerRealPos.x, playerRealPos.y); const bool isClose = distance < 16 * 4; const bool isNotTooFar = distance < 16 * 30; if (isClose || (isNotTooFar && AIIsFacing(a, player->Pos, a->direction))) { return true; } CA_FOREACH_END() return false; }
static bool CanSeeAPlayer(const TActor *a) { const Vec2i realPos = Vec2iFull2Real(a->Pos); for (int i = 0; i < MAX_PLAYERS; i++) { if (!IsPlayerAlive(i)) { continue; } const TActor *player = CArrayGet(&gActors, gPlayerIds[i]); const Vec2i playerRealPos = Vec2iFull2Real(player->Pos); // Can see player if: // - Clear line of sight, and // - If they are close, or if facing and they are not too far if (!AIHasClearShot(realPos, playerRealPos)) { continue; } const int distance = CHEBYSHEV_DISTANCE( realPos.x, realPos.y, playerRealPos.x, playerRealPos.y); const bool isClose = distance < 16 * 4; const bool isNotTooFar = distance < 16 * 30; if (isClose || (isNotTooFar && IsFacing(realPos, playerRealPos, a->direction))) { return true; } } return false; }
static void DrawHealthUpdate(const HUDNumUpdate *u, const int flags) { const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight * 2; const TActor *a = ActorGetByUID(p->ActorUID); DrawNumUpdate(u, "%d", a->health, Vec2iNew(5, y), flags); }
static void DrawScoreUpdate(const HUDNumUpdate *u, const int flags) { if (!IsScoreNeeded(gCampaign.Entry.Mode)) { return; } const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight; DrawNumUpdate(u, "Score: %d", p->score, Vec2iNew(5, y), flags); }
static bool DidPlayerShoot(void) { for (int i = 0; i < MAX_PLAYERS; i++) { if (IsPlayerAlive(i)) { TActor *player = CArrayGet(&gActors, gPlayerIds[i]); return player->lastCmd & CMD_BUTTON1; } } return 0; }
static void DrawAmmoUpdate(const HUDNumUpdate *u, const int flags) { const PlayerData *p = PlayerDataGetByUID(u->u.PlayerUID); if (!IsPlayerAlive(p)) return; const int rowHeight = 1 + FontH(); const int y = 5 + rowHeight * 4 + LIVES_ROW_EXTRA_Y; const TActor *a = ActorGetByUID(p->ActorUID); const Weapon *w = ActorGetGun(a); char gunNameBuf[256]; sprintf(gunNameBuf, "%s %%d", w->Gun->name); const int ammo = ActorGunGetAmmo(a, w); DrawNumUpdate(u, gunNameBuf, ammo, Vec2iNew(5 + GUN_ICON_PAD, y), flags); }
static bool IsFacingPlayer(TActor *actor, direction_e d) { CA_FOREACH(const PlayerData, p, gPlayerDatas) if (!IsPlayerAlive(p)) { continue; } const TActor *player = ActorGetByUID(p->ActorUID); if (AIIsFacing(actor, player->Pos, d)) { return true; } CA_FOREACH_END() return false; }
static bool DidPlayerShoot(void) { CA_FOREACH(const PlayerData, p, gPlayerDatas) if (!IsPlayerAlive(p)) { continue; } const TActor *player = ActorGetByUID(p->ActorUID); if (player->lastCmd & CMD_BUTTON1) { return true; } CA_FOREACH_END() return false; }
void HealthPickupsUpdate(HealthPickups *h, int ticks) { // Don't spawn pickups if not allowed if (!AreHealthPickupsAllowed(gCampaign.Entry.Mode) || !gConfig.Game.HealthPickups) { return; } double scalar = 1.0; // Update time until next spawn based on: // Damage taken (find player with lowest health) int minHealth = ModeMaxHealth(gCampaign.Entry.Mode); int maxHealth = minHealth; for (int i = 0; i < (int)gPlayerDatas.size; i++) { const PlayerData *p = CArrayGet(&gPlayerDatas, i); if (!IsPlayerAlive(p)) { continue; } const TActor *player = CArrayGet(&gActors, p->Id); minHealth = MIN(minHealth, player->health); } // Double spawn rate if near 0 health scalar *= (minHealth + maxHealth) / (maxHealth * 2.0); // Scale down over time scalar *= pow(TIME_DECAY_EXPONENT, h->pickupsSpawned); h->timeUntilNextSpawn = (int)floor(scalar * SPAWN_TIME); // Update time h->timer += ticks; // Attempt to add health if time reached, and we haven't placed too many if (h->timer >= h->timeUntilNextSpawn && h->map->NumExplorableTiles / MAX_TILES_PER_PICKUP + 1 > h->numPickups) { h->timer = 0; if (TryPlacePickup(h)) { h->pickupsSpawned++; h->numPickups++; } } }
static bool IsFacingPlayer(TActor *actor, direction_e d) { for (int i = 0; i < MAX_PLAYERS; i++) { if (gPlayerIds[i] < 0) { continue; } const TActor *player = CArrayGet(&gActors, gPlayerIds[i]); if (IsPlayerAlive(i) && IsFacing(actor->Pos, player->Pos, d)) { return true; } } return false; }
TActor *AIGetClosestPlayer(Vec2i fullpos) { int i; int minDistance = -1; TActor *closestPlayer = NULL; for (i = 0; i < gOptions.numPlayers; i++) { if (IsPlayerAlive(i)) { TActor *p = CArrayGet(&gActors, gPlayerIds[i]); Vec2i pPos = Vec2iFull2Real(p->Pos); int distance = CHEBYSHEV_DISTANCE( fullpos.x, fullpos.y, pPos.x, pPos.y); if (!closestPlayer || distance < minDistance) { closestPlayer = p; minDistance = distance; } } } return closestPlayer; }
static void HandleGameEvent( GameEvent *e, HUD *hud, ScreenShake *shake, HealthPickups *hp, EventHandlers *eventHandlers) { switch (e->Type) { case GAME_EVENT_SCORE: Score(&gPlayerDatas[e->u.Score.PlayerIndex], e->u.Score.Score); HUDAddScoreUpdate(hud, e->u.Score.PlayerIndex, e->u.Score.Score); break; case GAME_EVENT_SOUND_AT: if (e->u.SoundAt.Sound) { SoundPlayAt( &gSoundDevice, e->u.SoundAt.Sound, e->u.SoundAt.Pos); } break; case GAME_EVENT_SCREEN_SHAKE: *shake = ScreenShakeAdd( *shake, e->u.ShakeAmount, gConfig.Graphics.ShakeMultiplier); break; case GAME_EVENT_SET_MESSAGE: HUDDisplayMessage( hud, e->u.SetMessage.Message, e->u.SetMessage.Ticks); break; case GAME_EVENT_GAME_START: if (eventHandlers->netInput.channel.state == CHANNEL_STATE_CONNECTED) { NetInputSendMsg( &eventHandlers->netInput, SERVER_MSG_GAME_START); } break; case GAME_EVENT_ADD_HEALTH_PICKUP: MapPlaceHealth(e->u.AddPos); break; case GAME_EVENT_TAKE_HEALTH_PICKUP: if (IsPlayerAlive(e->u.PickupPlayer)) { TActor *player = CArrayGet( &gActors, gPlayerIds[e->u.PickupPlayer]); ActorHeal(player, HEALTH_PICKUP_HEAL_AMOUNT); HealthPickupsRemoveOne(hp); HUDAddHealthUpdate( hud, e->u.PickupPlayer, HEALTH_PICKUP_HEAL_AMOUNT); } break; case GAME_EVENT_MOBILE_OBJECT_REMOVE: MobObjDestroy(e->u.MobileObjectRemoveId); break; case GAME_EVENT_ADD_BULLET: BulletAdd( e->u.AddBullet.Bullet, e->u.AddBullet.MuzzlePos, e->u.AddBullet.MuzzleHeight, e->u.AddBullet.Angle, e->u.AddBullet.Direction, e->u.AddBullet.Flags, e->u.AddBullet.PlayerIndex); break; case GAME_EVENT_ADD_MUZZLE_FLASH: AddMuzzleFlash( e->u.AddMuzzleFlash.FullPos, e->u.AddMuzzleFlash.MuzzleHeight, e->u.AddMuzzleFlash.Sprites, e->u.AddMuzzleFlash.Direction, e->u.AddMuzzleFlash.Color, e->u.AddMuzzleFlash.Duration); break; case GAME_EVENT_ADD_FIREBALL: AddFireball(e->u.AddFireball); break; case GAME_EVENT_HIT_CHARACTER: HitCharacter( e->u.HitCharacter.Flags, e->u.HitCharacter.PlayerIndex, CArrayGet(&gActors, e->u.HitCharacter.TargetId), e->u.HitCharacter.Special, e->u.HitCharacter.HasHitSound); break; case GAME_EVENT_ACTOR_IMPULSE: { TActor *a = CArrayGet(&gActors, e->u.ActorImpulse.Id); a->Vel = Vec2iAdd(a->Vel, e->u.ActorImpulse.Vel); } break; case GAME_EVENT_DAMAGE_CHARACTER: DamageCharacter( e->u.DamageCharacter.Power, e->u.DamageCharacter.PlayerIndex, CArrayGet(&gActors, e->u.DamageCharacter.TargetId)); if (e->u.DamageCharacter.Power != 0) { HUDAddHealthUpdate( hud, e->u.DamageCharacter.TargetPlayerIndex, -e->u.DamageCharacter.Power); } break; case GAME_EVENT_TRIGGER: { const Tile *t = MapGetTile(&gMap, e->u.Trigger.TilePos); for (int i = 0; i < (int)t->triggers.size; i++) { Trigger **tp = CArrayGet(&t->triggers, i); if ((*tp)->id == e->u.Trigger.Id) { TriggerActivate(*tp, &gMap.triggers); break; } } } break; case GAME_EVENT_UPDATE_OBJECTIVE: { struct Objective *o = CArrayGet( &gMission.Objectives, e->u.UpdateObjective.ObjectiveIndex); o->done += e->u.UpdateObjective.Update; MissionObjective *mo = CArrayGet( &gMission.missionData->Objectives, e->u.UpdateObjective.ObjectiveIndex); switch (mo->Type) { case OBJECTIVE_COLLECT: { GameEvent e1; e1.Type = GAME_EVENT_SCORE; e1.u.Score.PlayerIndex = e->u.UpdateObjective.PlayerIndex; e1.u.Score.Score = PICKUP_SCORE; HandleGameEvent(&e1, hud, shake, hp, eventHandlers); } break; case OBJECTIVE_DESTROY: { GameEvent e1; e1.Type = GAME_EVENT_SCORE; e1.u.Score.PlayerIndex = e->u.UpdateObjective.PlayerIndex; e1.u.Score.Score = OBJECT_SCORE; HandleGameEvent(&e1, hud, shake, hp, eventHandlers); } break; default: // No other special objective handling break; } // Display a text update effect for the objective HUDAddObjectiveUpdate( hud, e->u.UpdateObjective.ObjectiveIndex, e->u.UpdateObjective.Update); MissionSetMessageIfComplete(&gMission); } break; case GAME_EVENT_MISSION_COMPLETE: HUDDisplayMessage(hud, "Mission complete", -1); hud->showExit = true; MapShowExitArea(&gMap); break; case GAME_EVENT_MISSION_INCOMPLETE: gMission.state = MISSION_STATE_PLAY; break; case GAME_EVENT_MISSION_PICKUP: gMission.state = MISSION_STATE_PICKUP; gMission.pickupTime = gMission.time; SoundPlay(&gSoundDevice, StrSound("whistle")); break; case GAME_EVENT_MISSION_END: gMission.isDone = true; break; default: assert(0 && "unknown game event"); break; } }
static void Campaign(GraphicsDevice *graphics, CampaignOptions *co) { if (co->IsClient) { // If connecting to a server, we've already received the mission index // Do nothing } else if (IsPasswordAllowed(co->Entry.Mode)) { MissionSave m; AutosaveLoadMission(&gAutosave, &m, co->Entry.Path); co->MissionIndex = EnterPassword(graphics, &m); } else { co->MissionIndex = 0; } bool run = false; bool gameOver = true; do { // Unready all the players for (int i = 0; i < (int)gPlayerDatas.size; i++) { PlayerData *p = CArrayGet(&gPlayerDatas, i); p->Ready = false; } CampaignAndMissionSetup(1, co, &gMission); if (IsGameOptionsNeeded(co->Entry.Mode)) { debug(D_NORMAL, ">> Game options\n"); if (!GameOptions(co->Entry.Mode)) { run = false; goto bail; } co->OptionsSet = true; // If enabled, start net server if (!co->IsClient && ConfigGetBool(&gConfig, "StartServer")) { NetServerOpen(&gNetServer); } } // Mission briefing if (IsMissionBriefingNeeded(co->Entry.Mode)) { if (!ScreenMissionBriefing(&gMission)) { run = false; goto bail; } } // Equip guns if (!PlayerEquip()) { run = false; goto bail; } if (co->IsClient) { if (!ScreenWaitForGameStart()) { run = false; goto bail; } } run = RunGame(co, &gMission, &gMap); // Don't quit if all players died, that's normal for PVP modes if (IsPVP(co->Entry.Mode) && GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false) == 0) { run = true; } const int survivingPlayers = GetNumPlayers(PLAYER_ALIVE, false, false); // In co-op (non-PVP) modes, at least one player must survive if (!IsPVP(co->Entry.Mode)) { gameOver = survivingPlayers == 0 || co->MissionIndex == (int)gCampaign.Setting.Missions.size - 1; } int maxScore = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++) { PlayerData *p = CArrayGet(&gPlayerDatas, i); p->survived = IsPlayerAlive(p); if (IsPlayerAlive(p)) { TActor *player = ActorGetByUID(p->ActorUID); p->hp = player->health; p->RoundsWon++; maxScore = MAX(maxScore, p->RoundsWon); } } if (IsPVP(co->Entry.Mode)) { gameOver = maxScore == ModeMaxRoundsWon(co->Entry.Mode); CASSERT(maxScore <= ModeMaxRoundsWon(co->Entry.Mode), "score exceeds max rounds won"); } MissionEnd(); MusicPlayMenu(&gSoundDevice); if (run) { switch (co->Entry.Mode) { case GAME_MODE_DOGFIGHT: ScreenDogfightScores(); break; case GAME_MODE_DEATHMATCH: ScreenDeathmatchFinalScores(); break; default: ScreenMissionSummary(&gCampaign, &gMission); // Note: must use cached value because players get cleaned up // in CleanupMission() if (gameOver && survivingPlayers > 0) { ScreenVictory(&gCampaign); } break; } } // Check if any scores exceeded high scores, if we're not a PVP mode if (!IsPVP(co->Entry.Mode)) { bool allTime = false; bool todays = false; for (int i = 0; i < (int)gPlayerDatas.size; i++) { PlayerData *p = CArrayGet(&gPlayerDatas, i); if (((run && !p->survived) || gameOver) && p->IsLocal) { EnterHighScore(p); allTime |= p->allTime >= 0; todays |= p->today >= 0; } if (!p->survived) { p->totalScore = 0; p->missions = 0; } else { p->missions++; } p->lastMission = co->MissionIndex; } if (allTime) { DisplayAllTimeHighScores(graphics); } if (todays) { DisplayTodaysHighScores(graphics); } } if (!HasRounds(co->Entry.Mode)) { co->MissionIndex++; } bail: // Need to terminate the mission later as it is used in calculating scores MissionOptionsTerminate(&gMission); } while (run && !gameOver); // Final screen if (run) { switch (co->Entry.Mode) { case GAME_MODE_DOGFIGHT: ScreenDogfightFinalScores(); break; default: // no end screen break; } } NetServerClose(&gNetServer); }
static void Campaign(GraphicsDevice *graphics, CampaignOptions *co) { if (IsPasswordAllowed(co->Entry.Mode)) { MissionSave m; AutosaveLoadMission( &gAutosave, &m, co->Entry.Path, co->Entry.BuiltinIndex); co->MissionIndex = EnterPassword(graphics, &m); } else { co->MissionIndex = 0; } bool run = false; bool gameOver = true; do { CampaignAndMissionSetup(1, co, &gMission); if (IsGameOptionsNeeded(gCampaign.Entry.Mode)) { debug(D_NORMAL, ">> Game options\n"); if (!GameOptions(gCampaign.Entry.Mode)) { run = false; goto bail; } gCampaign.OptionsSet = true; } // Mission briefing if (IsMissionBriefingNeeded(co->Entry.Mode)) { if (!ScreenMissionBriefing(&gMission)) { run = false; goto bail; } } // Equip guns if (!PlayerEquip()) { run = false; goto bail; } // Initialise before waiting for game start; // server will send us messages GameEventsInit(&gGameEvents); if (gCampaign.IsClient) { if (!ScreenWaitForGameStart()) { run = false; goto bail; } } MapLoad(&gMap, &gMission, co); // Seed random if PVP mode (otherwise players will always spawn in same // position) if (IsPVP(co->Entry.Mode)) { srand((unsigned int)time(NULL)); } if (!gCampaign.IsClient) { MapLoadDynamic(&gMap, &gMission, &co->Setting.characters); // Note: place players first, // as bad guys are placed away from players StartPlayers(ModeMaxHealth(co->Entry.Mode), co->MissionIndex); AddAndPlacePlayers(); if (!IsPVP(co->Entry.Mode)) { InitializeBadGuys(); CreateEnemies(); } } MusicPlayGame( &gSoundDevice, gCampaign.Entry.Path, gMission.missionData->Song); run = RunGame(&gMission, &gMap); // Don't quit if all players died, that's normal for PVP modes if (IsPVP(co->Entry.Mode) && GetNumPlayers(true, false, false) == 0) { run = true; } GameEventsTerminate(&gGameEvents); const int survivingPlayers = GetNumPlayers(true, false, false); // In co-op (non-PVP) modes, at least one player must survive if (!IsPVP(co->Entry.Mode)) { gameOver = survivingPlayers == 0 || co->MissionIndex == (int)gCampaign.Setting.Missions.size - 1; } int maxScore = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++) { PlayerData *p = CArrayGet(&gPlayerDatas, i); p->survived = IsPlayerAlive(p); if (IsPlayerAlive(p)) { TActor *player = CArrayGet(&gActors, p->Id); p->hp = player->health; p->RoundsWon++; maxScore = MAX(maxScore, p->RoundsWon); } } if (IsPVP(co->Entry.Mode)) { gameOver = maxScore == ModeMaxRoundsWon(co->Entry.Mode); CASSERT(maxScore <= ModeMaxRoundsWon(co->Entry.Mode), "score exceeds max rounds won"); } MissionEnd(); MusicPlayMenu(&gSoundDevice); if (run) { switch (co->Entry.Mode) { case GAME_MODE_DOGFIGHT: ScreenDogfightScores(); break; case GAME_MODE_DEATHMATCH: ScreenDeathmatchFinalScores(); break; default: ScreenMissionSummary(&gCampaign, &gMission); // Note: must use cached value because players get cleaned up // in CleanupMission() if (gameOver && survivingPlayers > 0) { ScreenVictory(&gCampaign); } break; } } // Check if any scores exceeded high scores, if we're not a PVP mode if (!IsPVP(co->Entry.Mode)) { bool allTime = false; bool todays = false; for (int i = 0; i < (int)gPlayerDatas.size; i++) { PlayerData *p = CArrayGet(&gPlayerDatas, i); if (((run && !p->survived) || gameOver) && p->IsLocal) { EnterHighScore(p); allTime |= p->allTime >= 0; todays |= p->today >= 0; } if (!p->survived) { p->totalScore = 0; p->missions = 0; } else { p->missions++; } p->lastMission = co->MissionIndex; } if (allTime) { DisplayAllTimeHighScores(graphics); } if (todays) { DisplayTodaysHighScores(graphics); } } if (!HasRounds(co->Entry.Mode)) { co->MissionIndex++; } bail: // Need to terminate the mission later as it is used in calculating scores MissionOptionsTerminate(&gMission); } while (run && !gameOver); // Final screen if (run) { switch (co->Entry.Mode) { case GAME_MODE_DOGFIGHT: ScreenDogfightFinalScores(); break; default: // no end screen break; } } }
void HUDDraw(HUD *hud, const input_device_e pausingDevice) { char s[50]; int flags = 0; const int numPlayersAlive = GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false); const int numLocalPlayers = GetNumPlayers(PLAYER_ANY, false, true); const int numLocalPlayersAlive = GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, true); Rect2i r; r.Size = Vec2iNew( hud->device->cachedConfig.Res.x, hud->device->cachedConfig.Res.y); if (numLocalPlayersAlive <= 1) { flags = 0; } else if ( ConfigGetEnum(&gConfig, "Interface.Splitscreen") == SPLITSCREEN_NEVER) { flags |= HUDFLAGS_SHARE_SCREEN; } else if (numLocalPlayers == 2) { r.Size.x /= 2; flags |= HUDFLAGS_HALF_SCREEN; } else if (numLocalPlayers == 3 || numLocalPlayers == 4) { r.Size.x /= 2; r.Size.y /= 2; flags |= HUDFLAGS_QUARTER_SCREEN; } else { assert(0 && "not implemented"); } int idx = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++, idx++) { const PlayerData *p = CArrayGet(&gPlayerDatas, i); if (!p->IsLocal) { idx--; continue; } int drawFlags = flags; r.Pos = Vec2iZero(); if (idx & 1) { r.Pos.x = r.Size.x; drawFlags |= HUDFLAGS_PLACE_RIGHT; } if (idx >= 2) { r.Pos.y = r.Size.y; drawFlags |= HUDFLAGS_PLACE_BOTTOM; } TActor *player = NULL; if (IsPlayerAlive(p)) { player = ActorGetByUID(p->ActorUID); } DrawPlayerStatus(hud, p, player, drawFlags, r); DrawScoreUpdate(&hud->scoreUpdates[idx], drawFlags); DrawHealthUpdate(&hud->healthUpdates[idx], drawFlags); DrawAmmoUpdate(&hud->ammoUpdates[idx], drawFlags); } // Only draw radar once if shared if (ConfigGetBool(&gConfig, "Interface.ShowHUDMap") && (flags & HUDFLAGS_SHARE_SCREEN) && IsAutoMapEnabled(gCampaign.Entry.Mode)) { DrawSharedRadar(hud->device, RADAR_SCALE, hud->showExit); } if (numPlayersAlive == 0) { if (AreAllPlayersDeadAndNoLives()) { if (!IsPVP(gCampaign.Entry.Mode)) { FontStrCenter("Game Over!"); } else { FontStrCenter("All Kill!"); } } } else if (hud->mission->state == MISSION_STATE_PICKUP) { int timeLeft = gMission.pickupTime + PICKUP_LIMIT - gMission.time; sprintf(s, "Pickup in %d seconds\n", (timeLeft + (FPS_FRAMELIMIT - 1)) / FPS_FRAMELIMIT); FontStrCenter(s); } if (pausingDevice != INPUT_DEVICE_UNSET) { Vec2i pos = Vec2iScaleDiv(Vec2iMinus( gGraphicsDevice.cachedConfig.Res, FontStrSize("Foo\nPress foo or bar to unpause\nBaz")), 2); const int x = pos.x; FontStr("<Paused>", pos); pos.y += FontH(); pos = FontStr("Press ", pos); color_t c = colorWhite; const char *buttonName = InputGetButtonNameColor(pausingDevice, 0, CMD_ESC, &c); pos = FontStrMask(buttonName, pos, c); FontStr(" again to quit", pos); pos.x = x; pos.y += FontH(); pos = FontStr("Press ", pos); buttonName = InputGetButtonNameColor( pausingDevice, 0, CMD_BUTTON1, &c); pos = FontStrMask(buttonName, pos, c); pos = FontStr(" or ", pos); buttonName = InputGetButtonNameColor( pausingDevice, 0, CMD_BUTTON2, &c); pos = FontStrMask(buttonName, pos, c); FontStr(" to unpause", pos); } if (hud->messageTicks > 0 || hud->messageTicks == -1) { // Draw the message centered, and just below the automap Vec2i pos = Vec2iNew( (hud->device->cachedConfig.Res.x - FontStrW(hud->message)) / 2, AUTOMAP_SIZE + AUTOMAP_PADDING + AUTOMAP_PADDING); FontStrMask(hud->message, pos, colorCyan); } if (ConfigGetBool(&gConfig, "Interface.ShowFPS")) { FPSCounterDraw(&hud->fpsCounter); } if (ConfigGetBool(&gConfig, "Interface.ShowTime")) { WallClockDraw(&hud->clock); } DrawKeycards(hud); // Draw elapsed mission time as MM:SS int missionTimeSeconds = gMission.time / FPS_FRAMELIMIT; sprintf(s, "%d:%02d", missionTimeSeconds / 60, missionTimeSeconds % 60); FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_CENTER; opts.Area = hud->device->cachedConfig.Res; opts.Pad.y = 5; FontStrOpt(s, Vec2iZero(), opts); if (HasObjectives(gCampaign.Entry.Mode)) { DrawObjectiveCounts(hud); } }
int IsMissionComplete(struct MissionOptions *options) { int rescuesRequired = 0; int i; if (!CanCompleteMission(options)) { return 0; } // Check if dogfight is complete if (gCampaign.Entry.Mode == CAMPAIGN_MODE_DOGFIGHT && GetNumPlayersAlive() <= 1) { return 1; } // Check that all surviving players are in exit zone for (i = 0; i < MAX_PLAYERS; i++) { if (!IsPlayerAlive(i)) { continue; } TActor *player = CArrayGet(&gActors, gPlayerIds[i]); if (!MapIsTileInExit(&gMap, &player->tileItem)) { return 0; } } // Find number of rescues required // TODO: support multiple rescue objectives for (i = 0; i < (int)options->missionData->Objectives.size; i++) { MissionObjective *mobj = CArrayGet(&options->missionData->Objectives, i); if (mobj->Type == OBJECTIVE_RESCUE) { rescuesRequired = mobj->Required; break; } } // Check that enough prisoners are in exit zone if (rescuesRequired > 0) { int prisonersRescued = 0; for (i = 0; i < (int)gActors.size; i++) { TActor *a = CArrayGet(&gActors, i); if (!a->isInUse) { continue; } if (a->character == CharacterStoreGetPrisoner( &gCampaign.Setting.characters, 0) && MapIsTileInExit(&gMap, &a->tileItem)) { prisonersRescued++; } } if (prisonersRescued < rescuesRequired) { return 0; } } return 1; }