/** * @brief Prepare things for baseattack battle * @param[in] mission Mission to prepare battle for */ static void CP_BaseAttackPrepareBattle (mission_t* mission) { if (!mission) return; base_t* base = mission->data.base; GEO_SelectMission(mission); mission->active = true; cgi->Com_DPrintf(DEBUG_CLIENT, "Base attack: %s at %.0f:%.0f\n", mission->id, mission->pos[0], mission->pos[1]); /* Fill the fake aircraft */ OBJZERO(baseAttackFakeAircraft); baseAttackFakeAircraft.homebase = base; /* needed for transfer of alien corpses */ VectorCopy(base->pos, baseAttackFakeAircraft.pos); /* needed to spawn soldiers on map */ baseAttackFakeAircraft.maxTeamSize = std::min(MAX_ACTIVETEAM, E_CountByType(EMPL_SOLDIER) + E_CountByType(EMPL_ROBOT)); baseAttackFakeAircraft.mission = mission; base->aircraftCurrent = &baseAttackFakeAircraft; GEO_SetMissionAircraft(&baseAttackFakeAircraft); /** @todo remove me - this is not needed because we are using the base->aircraftCurrent * pointer for resolving the aircraft - only Autocombat needs this */ GEO_SetInterceptorAircraft(&baseAttackFakeAircraft); /* needed for updating soldier stats sa CHAR_UpdateStats */ B_SetCurrentSelectedBase(base); /* needed for equipment menu */ static char popupText[1024]; Com_sprintf(popupText, sizeof(popupText), _("Base '%s' is under attack! What to do?"), base->name); cgi->UI_RegisterText(TEXT_POPUP, popupText); CP_GameTimeStop(); cgi->UI_PushWindow("popup_baseattack"); }
/** * @brief Base attack mission ends: UFO leave earth. * @note Base attack mission -- Stage 3 * @note UFO attacking this base will be redirected when notify function will be called, don't set new destination here. */ void CP_BaseAttackMissionDestroyBase (mission_t *mission) { base_t *base = mission->data.base; assert(base); /* Base attack is over, alien won */ Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Your base: %s has been destroyed! All employees killed and all equipment destroyed."), base->name); MS_AddNewMessage(_("Notice"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL); B_Destroy(base); CP_GameTimeStop(); /* we really don't want to use the fake aircraft anywhere */ MAP_SetMissionAircraft(NULL); /* HACK This hack is only needed until base will be really destroyed * we must recalculate items in storage because of the items we collected on battlefield */ CAP_UpdateStorageCap(base); base->aircraftCurrent = NULL; base->baseStatus = BASE_WORKING; }
/** * @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 Adds a new message to message stack * @note These are the messages that are displayed at geoscape * @param[in] title Already translated message/mail title * @param[in] text Already translated message/mail body * @param[in] popup Show this as a popup, too? * @param[in] type The message type * @param[in] pedia Pointer to technology (only if needed) * @param[in] playSound Whether the sound associated with the message type should be played * @return message_t pointer * @sa UP_OpenMail_f * @sa CL_EventAddMail_f * @note this method forwards to @c MS_AddNewMessageSound with @code playSound = true @endcode */ uiMessageListNodeMessage_t* MS_AddNewMessage (const char* title, const char* text, messageType_t type, technology_t* pedia, bool popup, bool playSound) { assert(type < MSG_MAX); /* allocate memory for new message - delete this with every new game */ uiMessageListNodeMessage_t* const mess = Mem_PoolAllocType(uiMessageListNodeMessage_t, cp_campaignPool); switch (type) { case MSG_DEBUG: /**< only save them in debug mode */ mess->iconName = "icons/message_debug"; break; case MSG_INFO: /**< don't save these messages */ mess->iconName = "icons/message_info"; break; case MSG_STANDARD: mess->iconName = "icons/message_info"; break; case MSG_RESEARCH_PROPOSAL: mess->iconName = "icons/message_research"; break; case MSG_RESEARCH_HALTED: mess->iconName = "icons/message_research"; break; case MSG_RESEARCH_FINISHED: mess->iconName = "icons/message_research"; break; case MSG_CONSTRUCTION: mess->iconName = "icons/message_construction"; break; case MSG_UFOSPOTTED: mess->iconName = "icons/message_ufo"; break; case MSG_TERRORSITE: mess->iconName = "icons/message_ufo"; break; case MSG_BASEATTACK: mess->iconName = "icons/message_ufo"; break; case MSG_TRANSFERFINISHED: mess->iconName = "icons/message_transfer"; break; case MSG_PROMOTION: mess->iconName = "icons/message_promotion"; break; case MSG_PRODUCTION: mess->iconName = "icons/message_production"; break; case MSG_DEATH: mess->iconName = "icons/message_death"; break; case MSG_CRASHSITE: mess->iconName = "icons/message_ufo"; break; case MSG_EVENT: mess->iconName = "icons/message_info"; break; default: mess->iconName = "icons/message_info"; break; } /* push the new message at the beginning of the stack */ cgi->UI_MessageAddStack(mess); mess->type = type; mess->pedia = pedia; /* pointer to UFOpaedia entry */ mess->date = ccs.date; Q_strncpyz(mess->title, title, sizeof(mess->title)); mess->text = cgi->PoolStrDup(text, cp_campaignPool, 0); /* get formatted date text */ MS_TimestampedText(mess->timestamp, mess, sizeof(mess->timestamp)); /* they need to be translated already */ if (popup) { CP_GameTimeStop(); CP_Popup(mess->title, "%s", mess->text); } if (playSound) { const char* sound = nullptr; switch (type) { case MSG_DEBUG: break; case MSG_STANDARD: sound = "geoscape/standard"; break; case MSG_INFO: case MSG_TRANSFERFINISHED: case MSG_DEATH: case MSG_CONSTRUCTION: case MSG_PRODUCTION: sound = "geoscape/info"; break; case MSG_RESEARCH_PROPOSAL: case MSG_RESEARCH_FINISHED: assert(pedia); case MSG_RESEARCH_HALTED: case MSG_EVENT: case MSG_NEWS: /* reread the new mails in UP_GetUnreadMails */ ccs.numUnreadMails = -1; sound = "geoscape/mail"; break; case MSG_UFOLOST: sound = "geoscape/ufolost"; break; case MSG_UFOSPOTTED: sound = "geoscape/ufospotted"; break; case MSG_BASEATTACK: sound = "geoscape/basealert"; break; case MSG_TERRORSITE: sound = "geoscape/alien-activity"; break; case MSG_CRASHSITE: sound = "geoscape/newmission"; break; case MSG_PROMOTION: sound = "geoscape/promotion"; break; case MSG_MAX: break; } cgi->S_StartLocalSample(sound, 1.0f); } return mess; }
/** * @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); }