1 /**
2  * @file
3  * @brief Alien base related functions
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 "cp_campaign.h"
27 #include "cp_alienbase.h"
28 #include "cp_geoscape.h"
29 #include "cp_missions.h"
30 #include "save/save_alienbase.h"
31 
32 #define MAPDEF_ALIENBASE "alienbase"
33 
34 /**
35  * @brief Set new base position
36  * @param[out] pos Position of the new base.
37  * @note This function generates @c maxLoopPosition random positions, and select among those the one that
38  * is the farthest from every other alien bases. This is intended to get a rather uniform distribution
39  * of alien bases, while still keeping a random base localisation.
40  */
AB_SetAlienBasePosition(vec2_t pos)41 void AB_SetAlienBasePosition (vec2_t pos)
42 {
43 	int counter;
44 	vec2_t randomPos;
45 	float minDistance = 0.0f;			/**< distance between current selected alien base */
46 	const int maxLoopPosition = 6;		/**< Number of random position among which the final one will be selected */
47 
48 	counter = 0;
49 	while (counter < maxLoopPosition) {
50 		float distance = 0.0f;
51 
52 		/* Get a random position */
53 		CP_GetRandomPosOnGeoscape(randomPos, true);
54 
55 		/* Alien base must not be too close from phalanx base */
56 		if (GEO_PositionCloseToBase(randomPos))
57 			continue;
58 
59 		/* If this is the first alien base, there's no further condition: select this pos and quit */
60 		if (AB_Exists()) {
61 			Vector2Copy(randomPos, pos);
62 			return;
63 		}
64 
65 		/* Calculate minimim distance between THIS position (pos) and all alien bases */
66 		AB_Foreach(base) {
67 			const float currentDistance = GetDistanceOnGlobe(base->pos, randomPos);
68 			if (distance < currentDistance) {
69 				distance = currentDistance;
70 			}
71 		}
72 
73 		/* If this position is farther than previous ones, select it */
74 		if (minDistance < distance) {
75 			Vector2Copy(randomPos, pos);
76 			minDistance = distance;
77 		}
78 
79 		counter++;
80 	}
81 	if (counter == maxLoopPosition)
82 		Vector2Copy(randomPos, pos);
83 }
84 
85 /**
86  * @brief Build a new alien base
87  * @param[in] pos Position of the new base.
88  * @return Pointer to the base that has been built.
89  */
AB_BuildBase(const vec2_t pos)90 alienBase_t* AB_BuildBase (const vec2_t pos)
91 {
92 	alienBase_t base;
93 	const float initialStealthValue = 50.0f;				/**< How hard PHALANX will find the base */
94 
95 	OBJZERO(base);
96 	Vector2Copy(pos, base.pos);
97 	base.stealth = initialStealthValue;
98 	base.idx = ccs.campaignStats.alienBasesBuilt++;
99 
100 	return &LIST_Add(&ccs.alienBases, base);
101 }
102 
103 /**
104  * @brief Destroy an alien base.
105  * @param[in] base Pointer to the alien base.
106  */
AB_DestroyBase(alienBase_t * base)107 void AB_DestroyBase (alienBase_t* base)
108 {
109 	assert(base);
110 
111 	cgi->LIST_Remove(&ccs.alienBases, (void*)base);
112 
113 	/* Alien loose all their interest in supply if there's no base to send the supply */
114 	if (!AB_Exists())
115 		ccs.interest[INTERESTCATEGORY_SUPPLY] = 0;
116 }
117 
118 /**
119  * @brief Get Alien Base per Idx.
120  * @param[in] baseIDX The unique IDX of the alien Base.
121  * @return Pointer to the base.
122  */
AB_GetByIDX(int baseIDX)123 alienBase_t* AB_GetByIDX (int baseIDX)
124 {
125 	AB_Foreach(base) {
126 		if (base->idx == baseIDX)
127 			return base;
128 	}
129 	return nullptr;
130 }
131 
132 /**
133  * @brief Spawn a new alien base mission after it has been discovered.
134  */
CP_SpawnAlienBaseMission(alienBase_t * alienBase)135 void CP_SpawnAlienBaseMission (alienBase_t* alienBase)
136 {
137 	mission_t* mission;
138 
139 	mission = CP_CreateNewMission(INTERESTCATEGORY_ALIENBASE, true);
140 	if (!mission) {
141 		Com_Printf("CP_SpawnAlienBaseMission: Could not add mission, abort\n");
142 		return;
143 	}
144 
145 	mission->stage = STAGE_BASE_DISCOVERED;
146 	mission->data.alienBase = alienBase;
147 
148 	mission->mapDef = cgi->Com_GetMapDefinitionByID(MAPDEF_ALIENBASE);
149 	if (!mission->mapDef)
150 		cgi->Com_Error(ERR_FATAL, "Could not find mapdef " MAPDEF_ALIENBASE);
151 
152 	Vector2Copy(alienBase->pos, mission->pos);
153 	mission->posAssigned = true;
154 
155 	/* Alien base stay until it's destroyed */
156 	CP_MissionDisableTimeLimit(mission);
157 	/* mission appears on geoscape, player can go there */
158 	CP_MissionAddToGeoscape(mission, false);
159 
160 	CP_TriggerEvent(ALIENBASE_DISCOVERED, nullptr);
161 }
162 
163 /**
164  * @brief Update stealth value of one alien base due to one aircraft.
165  * @param[in] aircraft Pointer to the aircraft_t.
166  * @param[in] base Pointer to the alien base.
167  * @note base stealth decreases if it is inside an aircraft radar range, and even more if it's
168  * inside @c radarratio times radar range.
169  * @sa UFO_UpdateAlienInterestForOneBase
170  */
AB_UpdateStealthForOneBase(const aircraft_t * aircraft,alienBase_t * base)171 static void AB_UpdateStealthForOneBase (const aircraft_t* aircraft, alienBase_t* base)
172 {
173 	float distance;
174 	float probability = 0.0001f;			/**< base probability, will be modified below */
175 	const float radarratio = 0.4f;			/**< stealth decreases faster if base is inside radarratio times radar range */
176 	const float decreasingFactor = 5.0f;	/**< factor applied when outside @c radarratio times radar range */
177 
178 	/* base is already discovered */
179 	if (base->stealth < 0)
180 		return;
181 
182 	/* aircraft can't find base if it's too far */
183 	distance = GetDistanceOnGlobe(aircraft->pos, base->pos);
184 	if (distance > aircraft->radar.range)
185 		return;
186 
187 	/* the bigger the base, the higher the probability to find it */
188 	probability *= base->supply;
189 
190 	/* decrease probability if the base is far from aircraft */
191 	if (distance > aircraft->radar.range * radarratio)
192 		probability /= decreasingFactor;
193 
194 	/* probability must depend on DETECTION_INTERVAL (in case we change the value) */
195 	probability *= DETECTION_INTERVAL;
196 
197 	base->stealth -= probability;
198 
199 	/* base discovered ? */
200 	if (base->stealth < 0) {
201 		base->stealth = -10.0f;		/* just to avoid rounding errors */
202 		CP_SpawnAlienBaseMission(base);
203 	}
204 }
205 
206 /**
207  * @brief Update stealth value of every base for every aircraft.
208  * @note Called every @c DETECTION_INTERVAL
209  * @sa CP_CampaignRun
210  * @sa UFO_UpdateAlienInterestForOneBase
211  */
AB_UpdateStealthForAllBase(void)212 void AB_UpdateStealthForAllBase (void)
213 {
214 	base_t* base = nullptr;
215 	while ((base = B_GetNext(base)) != nullptr) {
216 		AIR_ForeachFromBase(aircraft, base) {
217 			/* Only aircraft on geoscape can detect alien bases */
218 			if (!AIR_IsAircraftOnGeoscape(aircraft))
219 				continue;
220 
221 			AB_Foreach(alienBase)
222 				AB_UpdateStealthForOneBase(aircraft, alienBase);
223 		}
224 	}
225 }
226 
227 /**
228  * @brief Nations help in searching alien base.
229  * @note called once per day, but will update stealth only every @c daysPerWeek day
230  * @sa CP_CampaignRun
231  */
AB_BaseSearchedByNations(void)232 void AB_BaseSearchedByNations (void)
233 {
234 	const int daysPerWeek = 7;				/**< delay (in days) between base stealth update */
235 	float probability = 1.0f;				/**< base probability, will be modified below */
236 	const float xviLevel = 20.0f;			/**< xviInfection value of nation that will divide probability to
237 											 * find alien base by 2*/
238 
239 	/* Stealth is updated only once a week */
240 	if (ccs.date.day % daysPerWeek)
241 		return;
242 
243 	AB_Foreach(base) {
244 		const nation_t* nation = GEO_GetNation(base->pos);
245 
246 		/* If nation is a lot infected, it won't help in finding base (government infected) */
247 		if (nation) {
248 			const nationInfo_t* stats = NAT_GetCurrentMonthInfo(nation);
249 			if (stats->xviInfection)
250 				probability /= 1.0f + stats->xviInfection / xviLevel;
251 		}
252 
253 		/* the bigger the base, the higher the probability to find it */
254 		probability *= base->supply;
255 
256 		base->stealth -= probability;
257 	}
258 }
259 
260 /**
261  * @brief Check if a supply mission is possible.
262  * @return True if there is at least one base to supply.
263  */
AB_CheckSupplyMissionPossible(void)264 bool AB_CheckSupplyMissionPossible (void)
265 {
266 	return AB_Exists();
267 }
268 
269 /**
270  * @brief Choose Alien Base that should be supplied.
271  * @return Pointer to the base.
272  */
AB_ChooseBaseToSupply(void)273 alienBase_t* AB_ChooseBaseToSupply (void)
274 {
275 	const int baseCount = AB_GetAlienBaseNumber();
276 
277 	if (baseCount <= 0) {
278 		Com_Printf("AB_ChooseBaseToSupply: no bases exists (basecount: %d)\n", baseCount);
279 		return nullptr;
280 	}
281 
282 	const int selected = rand() % baseCount;
283 
284 	int i = 0;
285 	AB_Foreach(alienBase) {
286 		if (i == selected)
287 			return alienBase;
288 		i++;
289 	}
290 	return nullptr;
291 }
292 
293 /**
294  * @brief Supply a base.
295  * @param[in] base Pointer to the supplied base.
296  * @param[in] decreaseStealth If the stealth level of the base should be decreased.
297  */
AB_SupplyBase(alienBase_t * base,bool decreaseStealth)298 void AB_SupplyBase (alienBase_t* base, bool decreaseStealth)
299 {
300 	const float decreasedStealthValue = 5.0f;				/**< How much stealth is reduced because Supply UFO was seen */
301 
302 	assert(base);
303 
304 	base->supply++;
305 	if (decreaseStealth && base->stealth >= 0.0f)
306 		base->stealth -= decreasedStealthValue;
307 }
308 
309 /**
310  * @brief Check number of alien bases.
311  * @return number of alien bases.
312  */
AB_GetAlienBaseNumber(void)313 int AB_GetAlienBaseNumber (void)
314 {
315 	return cgi->LIST_Count(ccs.alienBases);
316 }
317 
318 #ifdef DEBUG
319 /**
320  * @brief Print Alien Bases information to game console
321  */
AB_AlienBaseDiscovered_f(void)322 static void AB_AlienBaseDiscovered_f (void)
323 {
324 	AB_Foreach(base) {
325 		base->stealth = -10.0f;
326 		CP_SpawnAlienBaseMission(base);
327 	}
328 }
329 
330 /**
331  * @brief Print Alien Bases information to game console
332  * @note called with debug_listalienbase
333  */
AB_AlienBaseList_f(void)334 static void AB_AlienBaseList_f (void)
335 {
336 	AB_Foreach(base) {
337 		Com_Printf("Alien Base: %i\n", base->idx);
338 		Com_Printf("...pos: (%f, %f)\n", base->pos[0], base->pos[1]);
339 		Com_Printf("...supply: %i\n", base->supply);
340 		if (base->stealth < 0)
341 			Com_Printf("...base discovered\n");
342 		else
343 			Com_Printf("...stealth: %f\n", base->stealth);
344 	}
345 }
346 #endif
347 
348 /**
349  * @brief Load callback for alien base data
350  * @param[in] p XML Node structure, where we get the information from
351  * @sa AB_SaveXML
352  */
AB_LoadXML(xmlNode_t * p)353 bool AB_LoadXML (xmlNode_t* p)
354 {
355 	int i; /**< @todo this is for old saves now only */
356 	xmlNode_t* n, *s;
357 
358 	n = cgi->XML_GetNode(p, SAVE_ALIENBASE_ALIENBASES);
359 	if (!n)
360 		return false;
361 
362 	for (i = 0, s = cgi->XML_GetNode(n, SAVE_ALIENBASE_BASE); s; i++, s = cgi->XML_GetNextNode(s, n, SAVE_ALIENBASE_BASE)) {
363 		alienBase_t base;
364 
365 		base.idx = cgi->XML_GetInt(s, SAVE_ALIENBASE_IDX, -1);
366 		if (base.idx < 0) {
367 			Com_Printf("Invalid or no IDX defined for Alienbase %d.\n", i);
368 			return false;
369 		}
370 		if (!cgi->XML_GetPos2(s, SAVE_ALIENBASE_POS, base.pos)) {
371 			Com_Printf("Position is invalid for Alienbase (idx %d)\n", base.idx);
372 			return false;
373 		}
374 		base.supply = cgi->XML_GetInt(s, SAVE_ALIENBASE_SUPPLY, 0);
375 		base.stealth = cgi->XML_GetFloat(s, SAVE_ALIENBASE_STEALTH, 0.0);
376 		LIST_Add(&ccs.alienBases, base);
377 	}
378 
379 	return true;
380 }
381 
382 /**
383  * @brief Save callback for alien base data
384  * @param[out] p XML Node structure, where we write the information to
385  * @sa AB_LoadXML
386  */
AB_SaveXML(xmlNode_t * p)387 bool AB_SaveXML (xmlNode_t* p)
388 {
389 	xmlNode_t* n = cgi->XML_AddNode(p, SAVE_ALIENBASE_ALIENBASES);
390 
391 	AB_Foreach(base) {
392 		xmlNode_t* s = cgi->XML_AddNode(n, SAVE_ALIENBASE_BASE);
393 		cgi->XML_AddInt(s, SAVE_ALIENBASE_IDX, base->idx);
394 		cgi->XML_AddPos2(s, SAVE_ALIENBASE_POS, base->pos);
395 		cgi->XML_AddIntValue(s, SAVE_ALIENBASE_SUPPLY, base->supply);
396 		cgi->XML_AddFloatValue(s, SAVE_ALIENBASE_STEALTH, base->stealth);
397 	}
398 
399 	return true;
400 }
401 
402 /**
403  * @brief Init actions for alienbase-subsystem
404  * @sa UI_InitStartup
405  */
AB_InitStartup(void)406 void AB_InitStartup (void)
407 {
408 #ifdef DEBUG
409 	cgi->Cmd_AddCommand("debug_listalienbase", AB_AlienBaseList_f, "Print Alien Bases information to game console");
410 	cgi->Cmd_AddCommand("debug_alienbasevisible", AB_AlienBaseDiscovered_f, "Set all alien bases to discovered");
411 #endif
412 }
413 
414 /**
415  * @brief Closing actions for alienbase-subsystem
416  */
AB_Shutdown(void)417 void AB_Shutdown (void)
418 {
419 	cgi->LIST_Delete(&ccs.alienBases);
420 
421 #ifdef DEBUG
422 	cgi->Cmd_RemoveCommand("debug_listalienbase");
423 	cgi->Cmd_RemoveCommand("debug_alienbasevisible");
424 #endif
425 }
426