/** * @brief Run base defences. * @param[in] dt Time elapsed since last call of this function. */ void AIRFIGHT_CampaignRunBaseDefence (int dt) { installation_t *installation; base_t *base; base = NULL; while ((base = B_GetNext(base)) != NULL) { int idx; if (B_IsUnderAttack(base)) continue; for (idx = 0; idx < base->numBatteries; idx++) { baseWeapon_t *battery = &base->batteries[idx]; aircraftSlot_t *slot = &battery->slot; if (slot->delayNextShot > 0) slot->delayNextShot -= dt; if (slot->ammoLeft <= 0) AII_ReloadWeapon(slot); } for (idx = 0; idx < base->numLasers; idx++) { baseWeapon_t *battery = &base->lasers[idx]; aircraftSlot_t *slot = &battery->slot; if (slot->delayNextShot > 0) slot->delayNextShot -= dt; if (slot->ammoLeft <= 0) AII_ReloadWeapon(slot); } if (AII_BaseCanShoot(base)) { if (B_GetBuildingStatus(base, B_DEFENCE_MISSILE)) AIRFIGHT_BaseShoot(base, base->batteries, base->numBatteries); if (B_GetBuildingStatus(base, B_DEFENCE_LASER)) AIRFIGHT_BaseShoot(base, base->lasers, base->numLasers); } } INS_Foreach(installation) { int idx; if (installation->installationTemplate->maxBatteries <= 0) continue; for (idx = 0; idx < installation->installationTemplate->maxBatteries; idx++) { baseWeapon_t *battery = &installation->batteries[idx]; aircraftSlot_t *slot = &battery->slot; if (slot->delayNextShot > 0) slot->delayNextShot -= dt; if (slot->ammoLeft <= 0) AII_ReloadWeapon(slot); } if (AII_InstallationCanShoot(installation)) { AIRFIGHT_InstallationShoot(installation, installation->batteries, installation->installationTemplate->maxBatteries); } } }
/** * @brief Returns the base pointer the production belongs to * @param[in] production pointer to the production entry * @return base_t pointer to the base */ base_t* PR_ProductionBase (const production_t* production) { base_t* base = nullptr; while ((base = B_GetNext(base)) != nullptr) { const ptrdiff_t diff = ((ptrdiff_t)((production) - PR_GetProductionForBase(base)->items)); if (diff >= 0 && diff < MAX_PRODUCTIONS) return base; } return nullptr; }
/** * @brief Display the popup_homebase * @param[in] aircraft Pointer to aircraft we want to change homebase. * @param[in] alwaysDisplay False if popup should be displayed only if at least one base is available. * @return true if popup is displayed. */ qboolean CL_DisplayHomebasePopup (aircraft_t *aircraft, qboolean alwaysDisplay) { int homebase; int numAvailableBases = 0; baseCapacities_t capacity; linkedList_t* popupListText = NULL; base_t *base; assert(aircraft); capacity = AIR_GetCapacityByAircraftWeight(aircraft); LIST_Delete(&popupListData); popupNum = 0; homebase = -1; base = NULL; while ((base = B_GetNext(base)) != NULL) { char text[MAX_VAR]; char const* msg; if (base == aircraft->homebase) { msg = _("current homebase of aircraft"); LIST_Add(&popupListData, (byte *)&INVALID_BASE, sizeof(int)); homebase = popupNum; } else { msg = AIR_CheckMoveIntoNewHomebase(aircraft, base, capacity); if (!msg) { msg = _("base can hold aircraft"); LIST_Add(&popupListData, (byte *)&base->idx, sizeof(int)); numAvailableBases++; } else { LIST_Add(&popupListData, (byte *)&INVALID_BASE, sizeof(int)); } } Com_sprintf(text, sizeof(text), "%s\t%s", base->name, msg); LIST_AddString(&popupListText, text); popupNum++; } if (alwaysDisplay || numAvailableBases > 0) { CL_GameTimeStop(); popupListNode = UI_PopupList(_("Change homebase of aircraft"), _("Base\tStatus"), popupListText, "change_homebase <lineselected>;"); VectorSet(popupListNode->selectedColor, 0.0, 0.78, 0.0); /**< Set color for selected entry. */ popupListNode->selectedColor[3] = 1.0; UI_TextNodeSelectLine(popupListNode, homebase); MAP_SelectAircraft(aircraft); return qtrue; } return qfalse; }
/** * @brief Counts live aliens in all bases. */ int AL_CountAll (void) { int amount = 0; base_t* base = nullptr; while ((base = B_GetNext(base)) != nullptr) { if (base->alienContainment) amount += base->alienContainment->getAlive(); } return amount; }
/** * @brief Set percentDone values after loading the campaign * @note it need to be done after B_PostLoadInitCapacity * @sa PR_PostLoadInit */ static bool PR_PostLoadInitProgress (void) { base_t* base = nullptr; while ((base = B_GetNext(base)) != nullptr) { production_queue_t* pq = PR_GetProductionForBase(base); for (int j = 0; j < pq->numItems; j++) { production_t* prod = &pq->items[j]; prod->totalFrames = PR_CalculateTotalFrames(base, &prod->data); } } return true; }
/** * @brief Counts live aliens in all bases. * @note This should be called whenever you add or remove * @note aliens from alien containment. * @return amount of all live aliens stored in containments * @sa B_AircraftReturnedToHomeBase * @sa AC_Init_f */ int AL_CountAll (void) { int amount = 0; base_t *base = NULL; while ((base = B_GetNext(base)) != NULL) { int j; if (!B_GetBuildingStatus(base, B_ALIEN_CONTAINMENT)) continue; for (j = 0; j < ccs.numAliensTD; j++) { const aliensCont_t *ac = &base->alienscont[j]; if (ac->teamDef) amount += ac->amountAlive; } } return amount; }
/** * @brief Checks capacity overflows on bases * @sa CP_CampaignRun */ void CAP_CheckOverflow (void) { base_t *base = nullptr; while ((base = B_GetNext(base)) != nullptr) { int i; for (i = CAP_ALIENS; i < MAX_CAP; i++) { baseCapacities_t capacityType = (baseCapacities_t)i; capacities_t *cap = CAP_Get(base, capacityType); if (cap->cur <= cap->max) continue; switch (capacityType) { case CAP_WORKSPACE: PR_UpdateProductionCap(base); break; case CAP_ITEMS: CAP_RemoveItemsExceedingCapacity(base); break; case CAP_ALIENS: AL_RemoveAliensExceedingCapacity(base); break; case CAP_LABSPACE: RS_RemoveScientistsExceedingCapacity(base); break; case CAP_AIRCRAFT_SMALL: case CAP_AIRCRAFT_BIG: CAP_RemoveAircraftExceedingCapacity(base, capacityType); break; case CAP_EMPLOYEES: E_DeleteEmployeesExceedingCapacity(base); break; case CAP_ANTIMATTER: CAP_RemoveAntimatterExceedingCapacity(base); break; default: /* nothing to do */ break; } } } }
/** * @brief Checks capacity overflows on bases * @sa CP_CampaignRun */ void CAP_CheckOverflow (void) { base_t* base = nullptr; while ((base = B_GetNext(base)) != nullptr) { for (int i = CAP_ALIENS; i < MAX_CAP; i++) { baseCapacities_t capacityType = (baseCapacities_t)i; capacities_t* cap = CAP_Get(base, capacityType); if (cap->cur <= cap->max) continue; switch (capacityType) { case CAP_ANTIMATTER: CAP_RemoveAntimatterExceedingCapacity(base); break; case CAP_WORKSPACE: PR_UpdateProductionCap(base); break; case CAP_LABSPACE: RS_RemoveScientistsExceedingCapacity(base); break; case CAP_AIRCRAFT_SMALL: case CAP_AIRCRAFT_BIG: case CAP_ALIENS: case CAP_EMPLOYEES: case CAP_ITEMS: if (base->baseStatus != BASE_DESTROYED) { const buildingType_t bldgType = B_GetBuildingTypeByCapacity((baseCapacities_t)i); const building_t* bldg = B_GetBuildingTemplateByType(bldgType); CP_GameTimeStop(); cgi->Cmd_ExecuteString("ui_push popup_cap_overload base %d \"%s\" \"%s\" %d %d", base->idx, base->name, _(bldg->name), cap->max - cap->cur, cap->max); } break; default: /* nothing to do */ break; } } } }
/** * @brief Update alien interest for all PHALANX bases. * @note called every @c DETECTION_INTERVAL * @sa UFO_UpdateAlienInterestForOneBase * @sa CP_CampaignRun */ void UFO_UpdateAlienInterestForAllBasesAndInstallations (void) { aircraft_t *ufo; ufo = NULL; while ((ufo = UFO_GetNext(ufo)) != NULL) { base_t *base; /* landed UFO can't detect any phalanx base or installation */ if (ufo->landed) continue; base = NULL; while ((base = B_GetNext(base)) != NULL) UFO_UpdateAlienInterestForOneBase(ufo, base); INS_Foreach(installation) UFO_UpdateAlienInterestForOneInstallation(ufo, installation); } }
/** * @brief Counts killed or captured aliens of given type in all bases. * @param[in] alienidx * @param[in] alive boolean whether the alien is alive or already dead * @return amount of killed aliens of given type */ static int AL_CountForMenu (int alienidx, bool alive) { int amount = 0; base_t *base; assert(alienidx >= 0); assert(alienidx < MAX_ALIENCONT_CAP); base = NULL; while ((base = B_GetNext(base)) != NULL) { aliensCont_t *ac = &base->alienscont[alienidx]; if (!B_GetBuildingStatus(base, B_ALIEN_CONTAINMENT)) continue; if (ac->teamDef) { if (!alive) amount += ac->amountDead; else amount += ac->amountAlive; } } return amount; }
/** * @brief Shows the current stats from stats_t stats * @todo This is very redundant with NAT_HandleBudget Ivestigate and clean up. */ void CP_StatsUpdate_f (void) { char *pos; static char statsBuffer[MAX_STATS_BUFFER]; int hired[MAX_EMPL]; int i, costs = 0, sum = 0, totalfunds = 0; base_t *base; const campaign_t *campaign = ccs.curCampaign; const salary_t *salary = &campaign->salaries; const rank_t *rank; /* delete buffer */ OBJZERO(statsBuffer); OBJZERO(hired); pos = statsBuffer; /* missions */ cgi->UI_RegisterText(TEXT_STATS_MISSION, pos); Com_sprintf(pos, MAX_STATS_BUFFER, _("Won:\t%i\nLost:\t%i\n\n"), ccs.campaignStats.missionsWon, ccs.campaignStats.missionsLost); /* bases */ pos += (strlen(pos) + 1); cgi->UI_RegisterText(TEXT_STATS_BASES, pos); Com_sprintf(pos, (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos), _("Built:\t%i\nActive:\t%i\nAttacked:\t%i\n"), ccs.campaignStats.basesBuilt, B_GetCount(), ccs.campaignStats.basesAttacked), /* installations */ pos += (strlen(pos) + 1); cgi->UI_RegisterText(TEXT_STATS_INSTALLATIONS, pos); INS_Foreach(inst) { Q_strcat(pos, va("%s\n", inst->name), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); } /* nations */ pos += (strlen(pos) + 1); cgi->UI_RegisterText(TEXT_STATS_NATIONS, pos); for (i = 0; i < ccs.numNations; i++) { const nation_t *nation = NAT_GetNationByIDX(i); Q_strcat(pos, va(_("%s\t%s\n"), _(nation->name), NAT_GetHappinessString(nation)), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); totalfunds += NAT_GetFunding(nation, 0); } Q_strcat(pos, va(_("\nFunding this month:\t%d"), totalfunds), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); /* costs */ for (i = 0; i < MAX_EMPL; i++) { E_Foreach(i, employee) { const employeeType_t type = (employeeType_t)i; if (!E_IsHired(employee)) continue; rank = CL_GetRankByIdx(employee->chr.score.rank); costs += CP_GetSalaryBaseEmployee(salary, type) + rank->level * CP_GetSalaryRankBonusEmployee(salary, type); hired[employee->type]++; } } /* employees - this is between the two costs parts to count the hired employees */ pos += (strlen(pos) + 1); cgi->UI_RegisterText(TEXT_STATS_EMPLOYEES, pos); for (i = 0; i < MAX_EMPL; i++) { const employeeType_t type = (employeeType_t)i; Q_strcat(pos, va(_("%s\t%i\n"), E_GetEmployeeString(type, hired[i]), hired[i]), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); } /* costs - second part */ pos += (strlen(pos) + 1); cgi->UI_RegisterText(TEXT_STATS_COSTS, pos); Q_strcat(pos, va(_("Employees:\t%i c\n"), costs), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); sum += costs; costs = 0; AIR_Foreach(aircraft) { if (aircraft->status == AIR_CRASHED) continue; costs += aircraft->price * salary->aircraftFactor / salary->aircraftDivisor; } Q_strcat(pos, va(_("Aircraft:\t%i c\n"), costs), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); sum += costs; base = NULL; while ((base = B_GetNext(base)) != NULL) { costs = CP_GetSalaryUpKeepBase(salary, base); Q_strcat(pos, va(_("Base (%s):\t%i c\n"), base->name, costs), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); sum += costs; } costs = CP_GetSalaryAdministrative(salary); Q_strcat(pos, va(_("Administrative costs:\t%i c\n"), costs), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); sum += costs; if (ccs.credits < 0) { const float interest = ccs.credits * campaign->salaries.debtInterest; costs = (int)ceil(interest); Q_strcat(pos, va(_("Debt:\t%i c\n"), costs), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); sum += costs; } Q_strcat(pos, va(_("\n\t-------\nSum:\t%i c\n"), sum), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); /* campaign */ pos += (strlen(pos) + 1); cgi->UI_RegisterText(TEXT_GENERIC, pos); Q_strcat(pos, va(_("Max. allowed debts: %ic\n"), campaign->negativeCreditsUntilLost), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); /* only show the xvi spread data when it's available */ if (CP_IsXVIVisible()) { Q_strcat(pos, va(_("Max. allowed eXtraterrestial Viral Infection: %i%%\n" "Current eXtraterrestial Viral Infection: %i%%"), campaign->maxAllowedXVIRateUntilLost, CP_GetAverageXVIRate()), (ptrdiff_t)(&statsBuffer[MAX_STATS_BUFFER] - pos)); } }
/** * @brief Check events for UFOs: Appears or disappears on radars * @return qtrue if any new ufo was detected during this iteration, qfalse otherwise */ qboolean UFO_CampaignCheckEvents (void) { qboolean newDetection; aircraft_t *ufo; newDetection = qfalse; /* For each ufo in geoscape */ ufo = NULL; while ((ufo = UFO_GetNext(ufo)) != NULL) { char detectedBy[MAX_VAR] = ""; float minDistance = -1; /* detected tells us whether or not a UFO is detected NOW, whereas ufo->detected tells * us whether or not the UFO was detected PREVIOUSLY. */ qboolean detected = qfalse; base_t *base; /* don't update UFO status id UFO is landed or crashed */ if (ufo->landed) continue; /* note: We can't exit these loops as soon as we found the UFO detected * RADAR_CheckUFOSensored registers the UFO in every radars' detection list * which detect it */ /* Check if UFO is detected by an aircraft */ AIR_Foreach(aircraft) { if (!AIR_IsAircraftOnGeoscape(aircraft)) continue; /* maybe the ufo is already detected, don't reset it */ if (RADAR_CheckUFOSensored(&aircraft->radar, aircraft->pos, ufo, detected | ufo->detected)) { const int distance = GetDistanceOnGlobe(aircraft->pos, ufo->pos); detected = qtrue; if (minDistance < 0 || minDistance > distance) { minDistance = distance; Q_strncpyz(detectedBy, aircraft->name, sizeof(detectedBy)); } } } /* Check if UFO is detected by a base */ base = NULL; while ((base = B_GetNext(base)) != NULL) { if (!B_GetBuildingStatus(base, B_POWER)) continue; /* maybe the ufo is already detected, don't reset it */ if (RADAR_CheckUFOSensored(&base->radar, base->pos, ufo, detected | ufo->detected)) { const int distance = GetDistanceOnGlobe(base->pos, ufo->pos); detected = qtrue; if (minDistance < 0 || minDistance > distance) { minDistance = distance; Q_strncpyz(detectedBy, base->name, sizeof(detectedBy)); } } } /* Check if UFO is detected by a radartower */ INS_Foreach(installation) { /* maybe the ufo is already detected, don't reset it */ if (RADAR_CheckUFOSensored(&installation->radar, installation->pos, ufo, detected | ufo->detected)) { const int distance = GetDistanceOnGlobe(installation->pos, ufo->pos); detected = qtrue; if (minDistance < 0 || minDistance > distance) { minDistance = distance; Q_strncpyz(detectedBy, installation->name, sizeof(detectedBy)); } } } /* Check if ufo appears or disappears on radar */ if (detected != ufo->detected) { if (detected) { /* if UFO is aiming a PHALANX aircraft, warn player */ if (ufo->aircraftTarget) { /* stop time and notify */ MSO_CheckAddNewMessage(NT_UFO_ATTACKING, _("Notice"), va(_("A UFO is flying toward %s"), ufo->aircraftTarget->name), qfalse, MSG_STANDARD, NULL); /** @todo present a popup with possible orders like: return to base, attack the ufo, try to flee the rockets * @sa UFO_SearchAircraftTarget */ } else { MSO_CheckAddNewMessage(NT_UFO_SPOTTED, _("Notice"), va(_("Our radar detected a new UFO near %s"), detectedBy), qfalse, MSG_UFOSPOTTED, NULL); } newDetection = qtrue; UFO_DetectNewUFO(ufo); } else if (!detected) { MSO_CheckAddNewMessage(NT_UFO_SIGNAL_LOST, _("Notice"), _("Our radar has lost the tracking on a UFO"), qfalse, MSG_UFOLOST, NULL); /* Make this UFO undetected */ ufo->detected = qfalse; /* Notify that ufo disappeared */ AIR_AircraftsUFODisappear(ufo); MAP_NotifyUFODisappear(ufo); /* Deactivate Radar overlay */ RADAR_DeactivateRadarOverlay(); } } } return newDetection; }