1 /**
2 * @file
3 * @brief Campaign mission code
4 */
5
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 */
24
25 #include "../../../cl_shared.h"
26 #include "../../../ui/ui_dataids.h"
27 #include "../cp_campaign.h"
28 #include "../cp_capacity.h"
29 #include "../cp_geoscape.h"
30 #include "../cp_ufo.h"
31 #include "../cp_missions.h"
32 #include "../cp_time.h"
33 #include "../cp_popup.h"
34 #include "../cp_alien_interest.h"
35
36 /**
37 * @brief This fake aircraft is used to assign soldiers for a base attack mission
38 * @sa CP_BaseAttackStartMission
39 * @sa AIR_AddToAircraftTeam
40 */
41 static aircraft_t baseAttackFakeAircraft;
42
43 /**
44 * @brief Base attack mission is over and is a success (from an alien point of view): change interest values.
45 * @note Base attack mission
46 * @sa CP_BaseAttackMissionStart
47 */
CP_BaseAttackMissionIsSuccess(mission_t * mission)48 void CP_BaseAttackMissionIsSuccess (mission_t* mission)
49 {
50 INT_ChangeIndividualInterest(0.3f, INTERESTCATEGORY_RECON);
51 INT_ChangeIndividualInterest(0.1f, INTERESTCATEGORY_HARVEST);
52 INT_ChangeIndividualInterest(-0.5f, INTERESTCATEGORY_TERROR_ATTACK);
53 INT_ChangeIndividualInterest(-0.5f, INTERESTCATEGORY_INTERCEPT);
54
55 CP_MissionRemove(mission);
56 }
57
58 /**
59 * @brief Base attack mission is over and is a failure (from an alien point of view): change interest values.
60 */
CP_BaseAttackMissionIsFailure(mission_t * mission)61 void CP_BaseAttackMissionIsFailure (mission_t* mission)
62 {
63 base_t* base = mission->data.base;
64
65 if (base) {
66 base->baseStatus = BASE_WORKING;
67
68 /* clean up the fakeAircraft */
69 cgi->LIST_Delete(&baseAttackFakeAircraft.acTeam);
70
71 base->aircraftCurrent = AIR_GetFirstFromBase(base);
72 }
73 ccs.mapAction = MA_NONE;
74
75 GEO_SetMissionAircraft(nullptr);
76
77 INT_ChangeIndividualInterest(0.05f, INTERESTCATEGORY_BUILDING);
78 INT_ChangeIndividualInterest(0.1f, INTERESTCATEGORY_BASE_ATTACK);
79
80 /* reset current selected mission */
81 GEO_NotifyMissionRemoved(mission);
82
83 CP_MissionRemove(mission);
84 }
85
86 /**
87 * @brief Run when the mission is spawned.
88 */
CP_BaseAttackMissionOnSpawn(void)89 void CP_BaseAttackMissionOnSpawn (void)
90 {
91 /* Prevent multiple bases from being attacked. by resetting interest. */
92 INT_ChangeIndividualInterest(-1.0f, INTERESTCATEGORY_BASE_ATTACK);
93 }
94
95 /**
96 * @brief Base attack mission ends: UFO leave earth.
97 * @note Base attack mission -- Stage 3
98 */
CP_BaseAttackMissionLeave(mission_t * mission)99 void CP_BaseAttackMissionLeave (mission_t* mission)
100 {
101 mission->stage = STAGE_RETURN_TO_ORBIT;
102
103 if (mission->ufo) {
104 CP_MissionDisableTimeLimit(mission);
105 UFO_SetRandomDest(mission->ufo);
106 /* Display UFO on geoscape if it is detected */
107 mission->ufo->landed = false;
108 } else {
109 /* Go to next stage on next frame */
110 mission->finalDate = ccs.date;
111 }
112 }
113
114 /**
115 * @brief Base attack mission ends: UFO leave earth.
116 * @note Base attack mission -- Stage 3
117 * @note UFO attacking this base will be redirected when notify function will be called, don't set new destination here.
118 */
CP_BaseAttackMissionDestroyBase(mission_t * mission)119 void CP_BaseAttackMissionDestroyBase (mission_t* mission)
120 {
121 base_t* base = mission->data.base;
122 assert(base);
123 /* Base attack is over, alien won */
124 Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Your base: %s has been destroyed! All employees killed and all equipment destroyed."), base->name);
125 MS_AddNewMessage(_("Notice"), cp_messageBuffer);
126
127 cgi->LIST_Delete(&baseAttackFakeAircraft.acTeam);
128 B_Destroy(base);
129 CP_GameTimeStop();
130
131 /* we really don't want to use the fake aircraft anywhere */
132 GEO_SetMissionAircraft(nullptr);
133
134 /* HACK This hack is only needed until base will be really destroyed
135 * we must recalculate items in storage because of the items we collected on battlefield */
136 CAP_UpdateStorageCap(base);
137 base->aircraftCurrent = nullptr;
138 base->baseStatus = BASE_WORKING;
139 }
140
141 /**
142 * @brief Prepare things for baseattack battle
143 * @param[in] mission Mission to prepare battle for
144 */
CP_BaseAttackPrepareBattle(mission_t * mission)145 static void CP_BaseAttackPrepareBattle (mission_t* mission)
146 {
147 if (!mission)
148 return;
149
150 base_t* base = mission->data.base;
151
152 GEO_SelectMission(mission);
153 mission->active = true;
154 ccs.mapAction = MA_BASEATTACK;
155 Com_DPrintf(DEBUG_CLIENT, "Base attack: %s at %.0f:%.0f\n", mission->id, mission->pos[0], mission->pos[1]);
156
157 /* Fill the fake aircraft */
158 OBJZERO(baseAttackFakeAircraft);
159 baseAttackFakeAircraft.homebase = base;
160 /* needed for transfer of alien corpses */
161 VectorCopy(base->pos, baseAttackFakeAircraft.pos);
162
163 /* needed to spawn soldiers on map */
164 baseAttackFakeAircraft.maxTeamSize = std::min(MAX_ACTIVETEAM, E_CountByType(EMPL_SOLDIER) + E_CountByType(EMPL_ROBOT));
165
166
167 base->aircraftCurrent = &baseAttackFakeAircraft;
168 GEO_SetMissionAircraft(&baseAttackFakeAircraft);
169 /** @todo remove me - this is not needed because we are using the base->aircraftCurrent
170 * pointer for resolving the aircraft - only CP_GameAutoGo needs this */
171 GEO_SetInterceptorAircraft(&baseAttackFakeAircraft); /* needed for updating soldier stats sa CP_UpdateCharacterStats */
172 B_SetCurrentSelectedBase(base); /* needed for equipment menu */
173
174 static char popupText[1024];
175 Com_sprintf(popupText, sizeof(popupText), _("Base '%s' is under attack! What to do?"), base->name);
176 cgi->UI_RegisterText(TEXT_POPUP, popupText);
177
178 CP_GameTimeStop();
179 cgi->UI_PushWindow("popup_baseattack");
180 }
181
182 /**
183 * @brief Start Base Attack.
184 * @param[in] mission Pointer to the baseattack mission
185 */
CP_BaseAttackStartMission(mission_t * mission)186 void CP_BaseAttackStartMission (mission_t* mission)
187 {
188 base_t* base = mission->data.base;
189 int soldiers;
190
191 assert(base);
192 mission->stage = STAGE_BASE_ATTACK;
193 CP_MissionDisableTimeLimit(mission);
194 if (mission->ufo) {
195 /* ufo becomes invisible on geoscape, but don't remove it from ufo global array (may reappear)*/
196 CP_UFORemoveFromGeoscape(mission, false);
197 }
198
199 /* we always need at least one command centre in the base - because the
200 * phalanx soldiers have their starting positions here.
201 * There should also always be an entrance - the aliens start there
202 * but we don't need to check that as entrance can't be destroyed */
203 if (!B_GetNumberOfBuildingsInBaseByBuildingType(base, B_COMMAND)) {
204 /** @todo handle command centre properly */
205 Com_DPrintf(DEBUG_CLIENT, "CP_BaseAttackStartMission: Base '%s' has no Command Center: it can't defend itself. Destroy base.\n", base->name);
206 CP_BaseAttackMissionDestroyBase(mission);
207 return;
208 }
209
210 MSO_CheckAddNewMessage(NT_BASE_ATTACK, _("Base attack"), va(_("Base '%s' is under attack!"), base->name), MSG_BASEATTACK);
211
212 base->baseStatus = BASE_UNDER_ATTACK;
213 ccs.campaignStats.basesAttacked++;
214
215 soldiers = 0;
216 E_Foreach(EMPL_SOLDIER, employee) {
217 if (!employee->isHiredInBase(base))
218 continue;
219 if (employee->isAwayFromBase())
220 continue;
221 soldiers++;
222 }
223 if (soldiers == 0) {
224 Com_DPrintf(DEBUG_CLIENT, "CP_BaseAttackStartMission: Base '%s' has no soldiers at home: it can't defend itself. Destroy base.\n", base->name);
225 CP_BaseAttackMissionDestroyBase(mission);
226 return;
227 }
228 CP_BaseAttackPrepareBattle(mission);
229 }
230
231
232 /**
233 * @brief Check and start baseattack missions
234 * @sa CP_BaseAttackStartMission
235 */
CP_CheckBaseAttacks(void)236 void CP_CheckBaseAttacks (void)
237 {
238 MIS_Foreach(mission) {
239 if (mission->category == INTERESTCATEGORY_BASE_ATTACK && mission->stage == STAGE_BASE_ATTACK)
240 CP_BaseAttackPrepareBattle(mission);
241 }
242 }
243
244 /**
245 * @brief Choose Base that will be attacked
246 * @return Pointer to the base, nullptr if no base set
247 * @note Base attack mission -- Stage 1
248 */
CP_BaseAttackChooseBase(void)249 static base_t* CP_BaseAttackChooseBase (void)
250 {
251 float randomNumber;
252 float sum = 0.0f;
253 base_t* base = nullptr;
254
255 /* Choose randomly a base depending on alienInterest values for those bases */
256 while ((base = B_GetNext(base)) != nullptr)
257 sum += base->alienInterest;
258 randomNumber = frand() * sum;
259 while ((base = B_GetNext(base)) != nullptr) {
260 randomNumber -= base->alienInterest;
261 if (randomNumber < 0)
262 break;
263 }
264
265 /* Make sure we have a base */
266 assert(base && (randomNumber < 0));
267
268 /* base is already under attack */
269 if (B_IsUnderAttack(base))
270 return nullptr;
271 /* base not (yet) working */
272 if (!B_GetBuildingStatus(base, B_COMMAND))
273 return nullptr;
274
275 return base;
276 }
277
278 /**
279 * @brief Set base attack mission, and go to base position.
280 * @note Base attack mission -- Stage 1
281 */
CP_BaseAttackGoToBase(mission_t * mission)282 static void CP_BaseAttackGoToBase (mission_t* mission)
283 {
284 base_t* base;
285
286 mission->stage = STAGE_MISSION_GOTO;
287
288 base = CP_BaseAttackChooseBase();
289 if (!base) {
290 Com_Printf("CP_BaseAttackGoToBase: no base found\n");
291 CP_MissionRemove(mission);
292 return;
293 }
294 mission->data.base = base;
295
296 mission->mapDef = cgi->Com_GetMapDefinitionByID("baseattack");
297 if (!mission->mapDef) {
298 CP_MissionRemove(mission);
299 cgi->Com_Error(ERR_DROP, "Could not find mapdef baseattack");
300 return;
301 }
302
303 Vector2Copy(base->pos, mission->pos);
304 mission->posAssigned = true;
305
306 if (mission->ufo) {
307 CP_MissionDisableTimeLimit(mission);
308 UFO_SendToDestination(mission->ufo, mission->pos);
309 } else {
310 /* Go to next stage on next frame */
311 mission->finalDate = ccs.date;
312 }
313 }
314
315 /**
316 * @brief Fill an array with available UFOs for Base Attack mission type.
317 * @param[in] mission Pointer to the mission we are currently creating.
318 * @param[out] ufoTypes Array of ufoType_t that may be used for this mission.
319 * @note Base Attack mission -- Stage 0
320 * @return number of elements written in @c ufoTypes
321 */
CP_BaseAttackMissionAvailableUFOs(const mission_t * mission,ufoType_t * ufoTypes)322 int CP_BaseAttackMissionAvailableUFOs (const mission_t* mission, ufoType_t* ufoTypes)
323 {
324 int num = 0;
325 if (UFO_ShouldAppearOnGeoscape(UFO_HARVESTER))
326 ufoTypes[num++] = UFO_HARVESTER;
327 if (UFO_ShouldAppearOnGeoscape(UFO_CORRUPTER))
328 ufoTypes[num++] = UFO_CORRUPTER;
329 if (UFO_ShouldAppearOnGeoscape(UFO_GUNBOAT))
330 ufoTypes[num++] = UFO_GUNBOAT;
331 if (UFO_ShouldAppearOnGeoscape(UFO_BOMBER))
332 ufoTypes[num++] = UFO_BOMBER;
333
334 return num;
335 }
336
337 /**
338 * @brief Determine what action should be performed when a Base Attack mission stage ends.
339 * @param[in] mission Pointer to the mission which stage ended.
340 */
CP_BaseAttackMissionNextStage(mission_t * mission)341 void CP_BaseAttackMissionNextStage (mission_t* mission)
342 {
343 switch (mission->stage) {
344 case STAGE_NOT_ACTIVE:
345 /* Create mission */
346 CP_MissionBegin(mission);
347 break;
348 case STAGE_COME_FROM_ORBIT:
349 /* Choose a base to attack and go to this base */
350 CP_BaseAttackGoToBase(mission);
351 break;
352 case STAGE_MISSION_GOTO:
353 /* just arrived on base location: attack it */
354 CP_BaseAttackStartMission(mission);
355 break;
356 case STAGE_BASE_ATTACK:
357 /* Leave earth */
358 CP_BaseAttackMissionDestroyBase(mission);
359 break;
360 case STAGE_RETURN_TO_ORBIT:
361 /* mission is over, remove mission */
362 CP_BaseAttackMissionIsSuccess(mission);
363 break;
364 default:
365 Com_Printf("CP_BaseAttackMissionNextStage: Unknown stage: %i, removing mission.\n", mission->stage);
366 CP_MissionRemove(mission);
367 break;
368 }
369 }
370