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