/** * @brief Unloads transfer cargo when finishing the transfer or destroys it when no buildings/base. * @param[in,out] destination The destination base - might be NULL in case the base * is already destroyed * @param[in] transfer Pointer to transfer in ccs.transfers. * @param[in] success True if the transfer reaches dest base, false if the base got destroyed. * @sa TR_TransferEnd */ static void TR_EmptyTransferCargo (base_t *destination, transfer_t *transfer, qboolean success) { assert(transfer); if (transfer->hasItems && success) { /* Items. */ const objDef_t *od = INVSH_GetItemByID(ANTIMATTER_TECH_ID); int i; /* antimatter */ if (transfer->itemAmount[od->idx] > 0) { if (B_GetBuildingStatus(destination, B_ANTIMATTER)) { B_ManageAntimatter(destination, transfer->itemAmount[od->idx], qtrue); } else { Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s does not have Antimatter Storage, antimatter are removed!"), destination->name); MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), cp_messageBuffer, qfalse, MSG_TRANSFERFINISHED, NULL); } } /* items */ for (i = 0; i < csi.numODs; i++) { od = INVSH_GetItemByIDX(i); if (transfer->itemAmount[od->idx] <= 0) continue; if (!B_ItemIsStoredInBaseStorage(od)) continue; if (!B_GetBuildingStatus(destination, B_STORAGE)) { Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s does not have Storage, items are removed!"), destination->name); MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), cp_messageBuffer, qfalse, MSG_TRANSFERFINISHED, NULL); break; } B_UpdateStorageAndCapacity(destination, od, transfer->itemAmount[od->idx], qfalse, qtrue); } } if (transfer->hasEmployees && transfer->srcBase) { /* Employees. (cannot come from a mission) */ if (!success || !B_GetBuildingStatus(destination, B_QUARTERS)) { /* Employees will be unhired. */ employeeType_t i; if (success) { Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s does not have Living Quarters, employees got unhired!"), destination->name); MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), cp_messageBuffer, qfalse, MSG_TRANSFERFINISHED, NULL); } for (i = EMPL_SOLDIER; i < MAX_EMPL; i++) { employee_t *employee; TR_ForeachEmployee(employee, transfer, i) { employee->baseHired = transfer->srcBase; /* Restore back the original baseid. */ employee->transfer = qfalse; E_UnhireEmployee(employee); } } } else {
/** * @brief Run actions on finishing disassembling of a ufo * @param base The base to produce in * @param prod The production that is running */ static void PR_FinishDisassembly (base_t* base, production_t* prod) { storedUFO_t* ufo = prod->data.data.ufo; assert(ufo); for (int i = 0; i < ufo->comp->numItemtypes; i++) { const objDef_t* compOd = ufo->comp->items[i]; const int amount = (ufo->condition < 1 && ufo->comp->itemAmount2[i] != COMP_ITEMCOUNT_SCALED) ? ufo->comp->itemAmount2[i] : round(ufo->comp->itemAmount[i] * ufo->condition); assert(compOd); if (amount <= 0) continue; if (Q_streq(compOd->id, ANTIMATTER_ITEM_ID)) { B_AddAntimatter(base, amount); } else { technology_t* tech = RS_GetTechForItem(compOd); B_AddToStorage(base, compOd, amount); RS_MarkCollected(tech); } } Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("The disassembling of %s at %s has finished."), UFO_TypeToName(ufo->ufoTemplate->getUfoType()), base->name); MSO_CheckAddNewMessage(NT_PRODUCTION_FINISHED, _("Production finished"), cp_messageBuffer, MSG_PRODUCTION, ufo->ufoTemplate->tech); /* Removing UFO will remove the production too */ US_RemoveStoredUFO(ufo); }
/** * @brief Run actions on finishing production of one item/aircraft/UGV.. * @param base The base to produce in * @param prod The production that is running */ static void PR_FinishProduction (base_t* base, production_t* prod) { const char* name = PR_GetName(&prod->data); technology_t* tech = PR_GetTech(&prod->data); prod->frame = 0; prod->amount--; if (PR_IsItem(prod)) { CP_UpdateCredits(ccs.credits - PR_GetPrice(prod->data.data.item)); /* Now add it to equipment and update capacity. */ B_AddToStorage(base, prod->data.data.item, 1); } else if (PR_IsAircraft(prod)) { CP_UpdateCredits(ccs.credits - PR_GetPrice(prod->data.data.aircraft)); /* Now add new aircraft. */ AIR_NewAircraft(base, prod->data.data.aircraft); } if (prod->amount > 0) return; Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Work on %s at %s has finished."), name, base->name); MSO_CheckAddNewMessage(NT_PRODUCTION_FINISHED, _("Production finished"), cp_messageBuffer, MSG_PRODUCTION, tech); /* queue the next production */ PR_QueueNext(base); }
/** * @brief Checks whether production can continue * @param base The base the production is running in * @param prod The production * @return @c false if production is stopped, @c true if production can be continued. */ static bool PR_CheckFrame (base_t* base, production_t* prod) { int price = 0; if (PR_IsItem(prod)) { const objDef_t* od = prod->data.data.item; price = PR_GetPrice(od); } else if (PR_IsAircraft(prod)) { const aircraft_t* aircraft = prod->data.data.aircraft; price = PR_GetPrice(aircraft); } /* Not enough money */ if (price > 0 && price > ccs.credits) { if (!prod->creditMessage) { Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Not enough credits to finish production in %s."), base->name); MSO_CheckAddNewMessage(NT_PRODUCTION_FAILED, _("Notice"), cp_messageBuffer); prod->creditMessage = true; } PR_ProductionRollBottom(base); return false; } return true; }
/** * @brief Decide what an attacking aircraft can do. * @param[in] campaign The campaign data structure * @param[in] shooter The aircraft we attack with. * @param[in] target The ufo we are going to attack. * @todo Implement me and display an attack popup. */ void AIRFIGHT_ExecuteActions (const campaign_t* campaign, aircraft_t* shooter, aircraft_t* target) { /* some asserts */ assert(shooter); assert(target); /* Check if the attacking aircraft can shoot */ const int slotIdx = AIRFIGHT_ChooseWeapon(shooter->weapons, shooter->maxWeapons, shooter->pos, target->pos); /* if weapon found that can shoot */ if (slotIdx >= AIRFIGHT_WEAPON_CAN_SHOOT) { aircraftSlot_t* weaponSlot = &shooter->weapons[slotIdx]; const objDef_t* ammo = weaponSlot->ammo; /* shoot */ if (AIRFIGHT_AddProjectile(nullptr, nullptr, shooter, target, weaponSlot)) { /* will we miss the target ? */ const float probability = frand(); Com_DPrintf(DEBUG_CLIENT, "AIRFIGHT_ExecuteActions: %s - Random probability to hit: %f\n", shooter->name, probability); weaponSlot->delayNextShot = ammo->craftitem.weaponDelay; const float calculatedProbability = AIRFIGHT_ProbabilityToHit(shooter, target, shooter->weapons + slotIdx); Com_DPrintf(DEBUG_CLIENT, "AIRFIGHT_ExecuteActions: %s - Calculated probability to hit: %f\n", shooter->name, calculatedProbability); if (probability > calculatedProbability) AIRFIGHT_MissTarget(&ccs.projectiles[ccs.numProjectiles - 1]); if (shooter->type != AIRCRAFT_UFO) { /* Maybe UFO is going to shoot back ? */ UFO_CheckShootBack(campaign, target, shooter); } else { /* an undetected UFO within radar range and firing should become detected */ if (!shooter->detected && RADAR_CheckRadarSensored(shooter->pos)) { /* stop time and notify */ MSO_CheckAddNewMessage(NT_UFO_ATTACKING, _("Notice"), va(_("A UFO is shooting at %s"), target->name)); RADAR_AddDetectedUFOToEveryRadar(shooter); UFO_DetectNewUFO(shooter); } } } } else if (slotIdx == AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT) { /* no ammo to fire atm (too far or reloading), pursue target */ if (shooter->type == AIRCRAFT_UFO) { /** @todo This should be calculated only when target destination changes, or when aircraft speed changes. * @sa AIR_GetDestination */ UFO_SendPursuingAircraft(shooter, target); } else AIR_SendAircraftPursuingUFO(shooter, target); } else { /* no ammo left, or no weapon, proceed with mission */ if (shooter->type == AIRCRAFT_UFO) { shooter->aircraftTarget = nullptr; /* reset target */ CP_UFOProceedMission(campaign, shooter); } else { MS_AddNewMessage(_("Notice"), _("Our aircraft has no more ammo left - returning to home base now.")); AIR_AircraftReturnToBase(shooter); } } }
/** * @brief Queues the next production in the queue. * @param[in] base Pointer to the base. */ void PR_QueueNext (base_t* base) { production_queue_t* queue = PR_GetProductionForBase(base); PR_QueueDelete(base, queue, 0); if (queue->numItems == 0) { Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Production queue for %s is empty"), base->name); MSO_CheckAddNewMessage(NT_PRODUCTION_QUEUE_EMPTY, _("Production queue empty"), cp_messageBuffer, MSG_PRODUCTION); } }
/** * @brief Unloads transfer cargo when finishing the transfer or destroys it when no buildings/base. * @param[in,out] destination The destination base - might be nullptr in case the base * is already destroyed * @param[in] transfer Pointer to transfer in ccs.transfers. * @param[in] success True if the transfer reaches dest base, false if the base got destroyed. * @sa TR_TransferEnd */ static void TR_EmptyTransferCargo (base_t* destination, transfer_t* transfer, bool success) { assert(transfer); /* antimatter */ if (transfer->antimatter > 0 && success) { if (B_GetBuildingStatus(destination, B_ANTIMATTER)) { B_AddAntimatter(destination, transfer->antimatter); } else { Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s does not have Antimatter Storage, antimatter are removed!"), destination->name); MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), cp_messageBuffer, MSG_TRANSFERFINISHED); } } /* items */ if (transfer->itemCargo != nullptr) { if (success) { linkedList_t* cargo = transfer->itemCargo->list(); LIST_Foreach(cargo, itemCargo_t, item) { if (item->amount <= 0) continue; if (!B_ItemIsStoredInBaseStorage(item->objDef)) continue; B_AddToStorage(destination, item->objDef, item->amount); } cgi->LIST_Delete(&cargo); } delete transfer->alienCargo; transfer->alienCargo = nullptr; } /* Employee */ if (transfer->hasEmployees && transfer->srcBase) { /* Employees. (cannot come from a mission) */ for (int i = EMPL_SOLDIER; i < MAX_EMPL; i++) { const employeeType_t type = (employeeType_t)i; TR_ForeachEmployee(employee, transfer, type) { employee->transfer = false; if (!success) { E_DeleteEmployee(employee); continue; } switch (type) { case EMPL_WORKER: PR_UpdateProductionCap(destination, 0); break; case EMPL_PILOT: AIR_AutoAddPilotToAircraft(destination, employee); break; default: break; } } } }
/** * @brief Check if some installation are build. * @note Daily called. */ void INS_UpdateInstallationData (void) { installation_t *installation; INS_Foreach(installation) { if (installation->installationStatus == INSTALLATION_UNDER_CONSTRUCTION && installation->buildStart && installation->buildStart + installation->installationTemplate->buildTime <= ccs.date.day) { INS_FinishInstallation(installation); Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer), _("Construction of installation %s finished."), installation->name); MSO_CheckAddNewMessage(NT_INSTALLATION_BUILDFINISH, _("Installation finished"), cp_messageBuffer, qfalse, MSG_CONSTRUCTION, NULL); } } }
/** * @brief Constructs a new installation. */ static void INS_BuildInstallation_f (void) { const installationTemplate_t* installationTemplate; if (cgi->Cmd_Argc() < 1) { Com_Printf("Usage: %s <installationType>\n", cgi->Cmd_Argv(0)); return; } /* We shouldn't build more installations than the actual limit */ if (B_GetInstallationLimit() <= INS_GetCount()) return; installationTemplate = INS_GetInstallationTemplateByID(cgi->Cmd_Argv(1)); if (!installationTemplate) { Com_Printf("The installation type %s passed for %s is not valid.\n", cgi->Cmd_Argv(1), cgi->Cmd_Argv(0)); return; } assert(installationTemplate->cost >= 0); if (ccs.credits - installationTemplate->cost > 0) { /* set up the installation */ installation_t* installation = INS_Build(installationTemplate, ccs.newBasePos, cgi->Cvar_GetString("mn_installation_title")); CP_UpdateCredits(ccs.credits - installationTemplate->cost); /* this cvar is used for disabling the installation build button on geoscape if MAX_INSTALLATIONS was reached */ cgi->Cvar_SetValue("mn_installation_count", INS_GetCount()); const nation_t* nation = GEO_GetNation(installation->pos); if (nation) Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new installation has been built: %s (nation: %s)"), installation->name, _(nation->name)); else Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new installation has been built: %s"), installation->name); MSO_CheckAddNewMessage(NT_INSTALLATION_BUILDSTART, _("Installation building"), cp_messageBuffer, MSG_CONSTRUCTION); } else { if (installationTemplate->type == INSTALLATION_RADAR) { if (GEO_IsRadarOverlayActivated()) GEO_SetOverlay("radar"); } if (ccs.mapAction == MA_NEWINSTALLATION) ccs.mapAction = MA_NONE; CP_Popup(_("Notice"), _("Not enough credits to set up a new installation.")); } ccs.mapAction = MA_NONE; }
/** * @brief Start Base Attack. * @param[in] mission Pointer to the baseattack mission */ void CP_BaseAttackStartMission (mission_t* mission) { base_t* base = mission->data.base; int soldiers; assert(base); mission->stage = STAGE_BASE_ATTACK; CP_MissionDisableTimeLimit(mission); if (mission->ufo) { /* ufo becomes invisible on geoscape, but don't remove it from ufo global array (may reappear)*/ CP_UFORemoveFromGeoscape(mission, false); } /* we always need at least one command centre in the base - because the * phalanx soldiers have their starting positions here. * There should also always be an entrance - the aliens start there * but we don't need to check that as entrance can't be destroyed */ if (!B_GetNumberOfBuildingsInBaseByBuildingType(base, B_COMMAND)) { /** @todo handle command centre properly */ cgi->Com_DPrintf(DEBUG_CLIENT, "CP_BaseAttackStartMission: Base '%s' has no Command Center: it can't defend itself. Destroy base.\n", base->name); CP_BaseAttackMissionDestroyBase(mission); return; } MSO_CheckAddNewMessage(NT_BASE_ATTACK, _("Base attack"), va(_("Base '%s' is under attack!"), base->name), MSG_BASEATTACK); base->baseStatus = BASE_UNDER_ATTACK; ccs.campaignStats.basesAttacked++; soldiers = 0; E_Foreach(EMPL_SOLDIER, employee) { if (!employee->isHiredInBase(base)) continue; if (employee->isAwayFromBase()) continue; soldiers++; } if (soldiers == 0) { cgi->Com_DPrintf(DEBUG_CLIENT, "CP_BaseAttackStartMission: Base '%s' has no soldiers at home: it can't defend itself. Destroy base.\n", base->name); CP_BaseAttackMissionDestroyBase(mission); return; } CP_BaseAttackPrepareBattle(mission); }
/** * @brief Check if the ufo can shoot at a PHALANX aircraft */ static void UFO_SearchAircraftTarget (const campaign_t* campaign, aircraft_t *ufo) { float distance = 999999.; /* UFO never try to attack a PHALANX aircraft except if they came on earth in that aim */ if (ufo->mission->stage != STAGE_INTERCEPT) { /* Check if UFO is defending itself */ if (ufo->aircraftTarget) UFO_CheckShootBack(campaign, ufo, ufo->aircraftTarget); return; } /* check if the ufo is already attacking an aircraft */ if (ufo->aircraftTarget) { /* check if the target disappeared from geoscape (fled in a base) */ if (AIR_IsAircraftOnGeoscape(ufo->aircraftTarget)) AIRFIGHT_ExecuteActions(campaign, ufo, ufo->aircraftTarget); else ufo->aircraftTarget = NULL; return; } ufo->status = AIR_TRANSIT; AIR_Foreach(phalanxAircraft) { /* check that aircraft is flying */ if (AIR_IsAircraftOnGeoscape(phalanxAircraft)) { /* get the distance from ufo to aircraft */ const float dist = GetDistanceOnGlobe(ufo->pos, phalanxAircraft->pos); /* check out of reach */ if (dist > MAX_DETECTING_RANGE) continue; /* choose the nearest target */ if (dist < distance) { distance = dist; if (UFO_SendPursuingAircraft(ufo, phalanxAircraft) && UFO_IsUFOSeenOnGeoscape(ufo)) { /* stop time and notify */ MSO_CheckAddNewMessage(NT_UFO_ATTACKING, _("Notice"), va(_("A UFO is flying toward %s"), phalanxAircraft->name), qfalse, MSG_STANDARD, NULL); /** @todo present a popup with possible orders like: return to base, attack the ufo, try to flee the rockets */ } } } } }
/** * @brief Destroys an installation * @param[in,out] installation Pointer to the installation to be destroyed */ void INS_DestroyInstallation (installation_t *installation) { if (!installation) return; /* Disable radar */ RADAR_UpdateInstallationRadarCoverage(installation, 0, 0); /* Destroy stored UFOs */ if (installation->ufoCapacity.max > 0) { installation->ufoCapacity.max = 0; US_RemoveUFOsExceedingCapacity(installation); } CP_MissionNotifyInstallationDestroyed(installation); Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Installation %s was destroyed."), installation->name); MSO_CheckAddNewMessage(NT_INSTALLATION_DESTROY, _("Installation destroyed"), cp_messageBuffer, qfalse, MSG_CONSTRUCTION, NULL); LIST_Remove(&ccs.installations, installation); Cvar_Set("mn_installation_count", va("%i", INS_GetCount())); }
/** * @brief Unloads transfer cargo when finishing the transfer or destroys it when no buildings/base. * @param[in,out] destination The destination base - might be nullptr in case the base * is already destroyed * @param[in] transfer Pointer to transfer in ccs.transfers. * @param[in] success True if the transfer reaches dest base, false if the base got destroyed. * @sa TR_TransferEnd */ static void TR_EmptyTransferCargo (base_t* destination, transfer_t* transfer, bool success) { assert(transfer); if (transfer->hasItems && success) { /* Items. */ const objDef_t* od = INVSH_GetItemByID(ANTIMATTER_ITEM_ID); int i; /* antimatter */ if (transfer->itemAmount[od->idx] > 0) { if (B_GetBuildingStatus(destination, B_ANTIMATTER)) { B_ManageAntimatter(destination, transfer->itemAmount[od->idx], true); } else { Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("%s does not have Antimatter Storage, antimatter are removed!"), destination->name); MSO_CheckAddNewMessage(NT_TRANSFER_LOST, _("Transport mission"), cp_messageBuffer, MSG_TRANSFERFINISHED); } } /* items */ for (i = 0; i < cgi->csi->numODs; i++) { od = INVSH_GetItemByIDX(i); if (transfer->itemAmount[od->idx] <= 0) continue; if (!B_ItemIsStoredInBaseStorage(od)) continue; B_AddToStorage(destination, od, transfer->itemAmount[od->idx]); } } if (transfer->hasEmployees && transfer->srcBase) { /* Employees. (cannot come from a mission) */ if (!success) { /* Employees will be unhired. */ for (int i = EMPL_SOLDIER; i < MAX_EMPL; i++) { const employeeType_t type = (employeeType_t)i; TR_ForeachEmployee(employee, transfer, type) { employee->baseHired = transfer->srcBase; /* Restore back the original baseid. */ employee->transfer = false; employee->unhire(); } } } else { for (int i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
/** * @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; }
/** * @brief Check if the ufo can shoot at a PHALANX aircraft and whether it should follow another ufo */ static void UFO_SearchAircraftTarget (const campaign_t* campaign, aircraft_t* ufo, float maxDetectionRange = MAX_DETECTING_RANGE) { float distance = 999999.; /* UFO never try to attack a PHALANX aircraft except if they came on earth in that aim */ if (ufo->mission->stage != STAGE_INTERCEPT) { /* Check if UFO is defending itself */ if (ufo->aircraftTarget) UFO_CheckShootBack(campaign, ufo, ufo->aircraftTarget); return; } /* check if the ufo is already attacking an aircraft */ if (ufo->aircraftTarget) { /* check if the target disappeared from geoscape (fled in a base) */ if (AIR_IsAircraftOnGeoscape(ufo->aircraftTarget)) AIRFIGHT_ExecuteActions(campaign, ufo, ufo->aircraftTarget); else ufo->aircraftTarget = nullptr; return; } ufo->status = AIR_TRANSIT; AIR_Foreach(phalanxAircraft) { /* check that aircraft is flying */ if (AIR_IsAircraftOnGeoscape(phalanxAircraft)) { /* get the distance from ufo to aircraft */ const float dist = GetDistanceOnGlobe(ufo->pos, phalanxAircraft->pos); /* check out of reach */ if (dist > maxDetectionRange) continue; /* choose the nearest target */ if (dist < distance) { distance = dist; if (UFO_SendPursuingAircraft(ufo, phalanxAircraft) && UFO_IsUFOSeenOnGeoscape(ufo)) { /* stop time and notify */ MSO_CheckAddNewMessage(NT_UFO_ATTACKING, _("Notice"), va(_("A UFO is flying toward %s"), phalanxAircraft->name)); /** @todo present a popup with possible orders like: return to base, attack the ufo, try to flee the rockets */ return; } } } } /* if this ufo is a leader, it does not try to search another one */ if (ufo->leader) return; aircraft_t* otherUFO = nullptr; const float polarCoordinatesOffset = 1.0f; while ((otherUFO = UFO_GetNextOnGeoscape(otherUFO)) != nullptr) { if (otherUFO == ufo) continue; if (otherUFO->leader) { vec2_t dest; AIR_GetDestinationWhilePursuing(ufo, otherUFO, dest); dest[0] += polarCoordinatesOffset; dest[1] += polarCoordinatesOffset; GEO_CalcLine(ufo->pos, dest, &ufo->route); ufo->time = 0; ufo->point = 0; break; } } }
/** * @brief Start Base Attack. * @note Base attack mission -- Stage 2 */ void CP_BaseAttackStartMission (mission_t *mission) { base_t *base = mission->data.base; linkedList_t *hiredSoldiersInBase = NULL; int soldiers; assert(base); mission->stage = STAGE_BASE_ATTACK; CP_MissionDisableTimeLimit(mission); if (mission->ufo) { /* ufo becomes invisible on geoscape, but don't remove it from ufo global array (may reappear)*/ CP_UFORemoveFromGeoscape(mission, qfalse); } /* we always need at least one command centre in the base - because the * phalanx soldiers have their starting positions here. * There should also always be an entrance - the aliens start there * but we don't need to check that as entrance can't be destroyed */ if (!B_GetNumberOfBuildingsInBaseByBuildingType(base, B_COMMAND)) { /** @todo handle command centre properly */ Com_DPrintf(DEBUG_CLIENT, "CP_BaseAttackStartMission: Base '%s' has no Command Center: it can't defend itself. Destroy base.\n", base->name); CP_BaseAttackMissionDestroyBase(mission); return; } MSO_CheckAddNewMessage(NT_BASE_ATTACK, _("Base attack"), va(_("Base '%s' is under attack!"), base->name), qfalse, MSG_BASEATTACK, NULL); base->baseStatus = BASE_UNDER_ATTACK; ccs.campaignStats.basesAttacked++; MAP_SelectMission(mission); mission->active = qtrue; ccs.mapAction = MA_BASEATTACK; Com_DPrintf(DEBUG_CLIENT, "Base attack: %s at %.0f:%.0f\n", mission->id, mission->pos[0], mission->pos[1]); /** @todo EMPL_ROBOT */ E_GetHiredEmployees(base, EMPL_SOLDIER, &hiredSoldiersInBase); /* Fill the fake aircraft */ LIST_Delete(&baseAttackFakeAircraft.acTeam); OBJZERO(baseAttackFakeAircraft); baseAttackFakeAircraft.homebase = base; /* needed for transfer of alien corpses */ VectorCopy(base->pos, baseAttackFakeAircraft.pos); #if 0 /** @todo active this once more than 8 soldiers are working */ /* needed to spawn soldiers on map */ baseAttackFakeAircraft.maxTeamSize = LIST_Count(hiredSoldiersInBase); #else baseAttackFakeAircraft.maxTeamSize = MAX_ACTIVETEAM; #endif if (!hiredSoldiersInBase) { Com_DPrintf(DEBUG_CLIENT, "CP_BaseAttackStartMission: Base '%s' has no soldiers: it can't defend itself. Destroy base.\n", base->name); CP_BaseAttackMissionDestroyBase(mission); return; } UI_ExecuteConfunc("soldierlist_clear"); soldiers = 0; LIST_Foreach(hiredSoldiersInBase, employee_t, employee) { const rank_t *rank = CL_GetRankByIdx(employee->chr.score.rank); if (E_IsAwayFromBase(employee)) continue; UI_ExecuteConfunc("soldierlist_add %d \"%s %s\"", employee->chr.ucn, (rank) ? _(rank->shortname) : "", employee->chr.name); if (soldiers < 1) UI_ExecuteConfunc("team_select_ucn %d", employee->chr.ucn); soldiers++; } if (soldiers == 0) { Com_DPrintf(DEBUG_CLIENT, "CP_BaseAttackStartMission: Base '%s' has no soldiers at home: it can't defend itself. Destroy base.\n", base->name); CP_BaseAttackMissionDestroyBase(mission); return; } #if 0 /** @todo active this once more than 8 soldiers are working */ /* all soldiers in the base should get used */ baseAttackFakeAircraft.maxTeamSize = AIR_GetTeamSize(&baseAttackFakeAircraft); #endif LIST_Delete(&hiredSoldiersInBase); base->aircraftCurrent = &baseAttackFakeAircraft; MAP_SetMissionAircraft(&baseAttackFakeAircraft); /** @todo remove me - this is not needed because we are using the base->aircraftCurrent * pointer for resolving the aircraft - only CP_GameAutoGo needs this */ MAP_SetInterceptorAircraft(&baseAttackFakeAircraft); /* needed for updating soldier stats sa CP_UpdateCharacterStats*/ B_SetCurrentSelectedBase(base); /* needed for equipment menu */ Com_sprintf(popupText, sizeof(popupText), _("Base '%s' is under attack! What to do ?"), base->name); UI_RegisterText(TEXT_POPUP, popupText); CP_GameTimeStop(); UI_PushWindow("popup_baseattack", NULL, NULL); }