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