1 /**
2 * @file
3 * @brief Radars / sensor stuff, to detect and track ufos
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_overlay.h"
28 #include "cp_geoscape.h"
29 #include "cp_ufo.h"
30
31 /**
32 * used to store the previous configuration of overlay before radar
33 * is automatically turned on (e.g when creating base or when UFO appears)
34 */
35 bool radarOverlayWasSet;
36
37 /* Define base radar range (can be modified by level of the radar) */
38 const float RADAR_BASERANGE = 24.0f;
39 const float RADAR_BASETRACKINGRANGE = 34.0f;
40 const float RADAR_INSTALLATIONLEVEL = 1.0f;
41 /** @brief this is the multiplier applied to the radar range when the radar levels up */
42 static const float RADAR_UPGRADE_MULTIPLIER = 0.4f;
43
44 /**
45 * @brief Update every static radar drawing (radar that don't move: base and installation radar).
46 * @note This is only called when radar range of bases change.
47 */
RADAR_UpdateStaticRadarCoverage(void)48 void RADAR_UpdateStaticRadarCoverage (void)
49 {
50 base_t* base;
51
52 /* Initialise radar range (will be filled below) */
53 CP_InitializeRadarOverlay(true);
54
55 /* Add base radar coverage */
56 base = nullptr;
57 while ((base = B_GetNext(base)) != nullptr) {
58 if (base->radar.range) {
59 CP_AddRadarCoverage(base->pos, base->radar.range, base->radar.trackingRange, true);
60 }
61 }
62
63 /* Add installation coverage */
64 INS_Foreach(installation) {
65 if (installation->installationStatus == INSTALLATION_WORKING && installation->radar.range)
66 CP_AddRadarCoverage(installation->pos, installation->radar.range, installation->radar.trackingRange, true);
67 }
68
69 /* Smooth and bind radar overlay without aircraft (in case no aircraft is on geoscape:
70 * RADAR_UpdateWholeRadarOverlay won't be called) */
71 CP_InitializeRadarOverlay(false);
72 CP_UploadRadarCoverage();
73 }
74
75 /**
76 * @brief Update map radar coverage with moving radar
77 * @sa RADAR_UpdateWholeRadarOverlay
78 */
RADAR_DrawCoverage(const radar_t * radar,const vec2_t pos)79 static inline void RADAR_DrawCoverage (const radar_t* radar, const vec2_t pos)
80 {
81 if (radar && radar->range)
82 CP_AddRadarCoverage(pos, radar->range, radar->trackingRange, false);
83 }
84
85 /**
86 * @brief Update radar overlay of base, installation and aircraft range.
87 */
RADAR_UpdateWholeRadarOverlay(void)88 void RADAR_UpdateWholeRadarOverlay (void)
89 {
90 /* Copy Base and installation radar overlay*/
91 CP_InitializeRadarOverlay(false);
92
93 /* Add aircraft radar coverage */
94 AIR_Foreach(aircraft) {
95 if (AIR_IsAircraftOnGeoscape(aircraft))
96 RADAR_DrawCoverage(&aircraft->radar, aircraft->pos);
97 }
98
99 CP_UploadRadarCoverage();
100 }
101
102 /**
103 * @brief Deactivate Radar overlay if there is no more UFO on geoscape
104 */
RADAR_DeactivateRadarOverlay(void)105 void RADAR_DeactivateRadarOverlay (void)
106 {
107 base_t* base;
108
109 /* never deactivate radar if player wants it to be always turned on */
110 if (radarOverlayWasSet)
111 return;
112
113 AIR_Foreach(aircraft) {
114 /** @todo Is aircraft->radar cleared for crashed aircraft? */
115 if (aircraft->radar.numUFOs)
116 return;
117 }
118
119 base = nullptr;
120 while ((base = B_GetNext(base)) != nullptr) {
121 if (base->radar.numUFOs)
122 return;
123 }
124
125 INS_Foreach(installation) {
126 if (installation->radar.numUFOs)
127 return;
128 }
129
130 if (GEO_IsRadarOverlayActivated())
131 GEO_SetOverlay("radar");
132 }
133
134 /**
135 * @brief Check if UFO is in the sensored list
136 */
RADAR_IsUFOSensored(const radar_t * radar,const aircraft_t * ufo)137 static bool RADAR_IsUFOSensored (const radar_t* radar, const aircraft_t* ufo)
138 {
139 int i;
140
141 for (i = 0; i < radar->numUFOs; i++)
142 if (radar->ufos[i] == ufo)
143 return true;
144
145 return false;
146 }
147
148 /**
149 * @brief Add a UFO in the list of sensored UFOs
150 */
RADAR_AddUFO(radar_t * radar,const aircraft_t * ufo)151 static bool RADAR_AddUFO (radar_t* radar, const aircraft_t* ufo)
152 {
153 #ifdef DEBUG
154 if (RADAR_IsUFOSensored(radar, ufo)) {
155 Com_Printf("RADAR_AddUFO: Aircraft already in radar range\n");
156 return false;
157 }
158 #endif
159
160 if (radar->numUFOs >= MAX_UFOONGEOSCAPE)
161 return false;
162
163 radar->ufos[radar->numUFOs] = ufo;
164 radar->numUFOs++;
165
166 return true;
167 }
168
169 /**
170 * @brief UFO will no more be referenced by radar
171 */
RADAR_RemoveUFO(radar_t * radar,const aircraft_t * ufo)172 static void RADAR_RemoveUFO (radar_t* radar, const aircraft_t* ufo)
173 {
174 int i;
175
176 assert(radar->numUFOs < MAX_UFOONGEOSCAPE && radar->numUFOs > 0);
177
178 for (i = 0; i < radar->numUFOs; i++)
179 if (radar->ufos[i] == ufo)
180 break;
181
182 if (i == radar->numUFOs)
183 return;
184
185 REMOVE_ELEM(radar->ufos, i, radar->numUFOs);
186
187 RADAR_DeactivateRadarOverlay();
188 }
189
190 /**
191 * @brief Notify that the specified ufo has been removed from geoscape to one radar.
192 * @param[in] radar Pointer to the radar where ufo should be removed.
193 * @param[in] ufo Pointer to UFO to remove.
194 * @param[in] destroyed True if the UFO has been destroyed, false if it's been only set invisible (landed)
195 */
RADAR_NotifyUFORemovedFromOneRadar(radar_t * radar,const aircraft_t * ufo,bool destroyed)196 static void RADAR_NotifyUFORemovedFromOneRadar (radar_t* radar, const aircraft_t* ufo, bool destroyed)
197 {
198 int i;
199
200 for (i = 0; i < radar->numUFOs; i++)
201 if (radar->ufos[i] == ufo) {
202 radar->numUFOs--;
203 radar->ufos[i] = radar->ufos[radar->numUFOs];
204 i--; /* Allow the moved value to be checked */
205 } else if (destroyed && (radar->ufos[i] > ufo))
206 radar->ufos[i]--;
207
208 RADAR_DeactivateRadarOverlay();
209 }
210
211 /**
212 * @brief Notify to every radar that the specified ufo has been removed from geoscape
213 * @param[in] ufo Pointer to UFO to remove.
214 * @param[in] destroyed True if the UFO has been destroyed, false if it's only landed.
215 */
RADAR_NotifyUFORemoved(const aircraft_t * ufo,bool destroyed)216 void RADAR_NotifyUFORemoved (const aircraft_t* ufo, bool destroyed)
217 {
218 base_t* base;
219
220 base = nullptr;
221 while ((base = B_GetNext(base)) != nullptr) {
222 RADAR_NotifyUFORemovedFromOneRadar(&base->radar, ufo, destroyed);
223
224 AIR_ForeachFromBase(aircraft, base)
225 RADAR_NotifyUFORemovedFromOneRadar(&aircraft->radar, ufo, destroyed);
226 }
227
228 INS_Foreach(installation) {
229 if (installation->installationStatus == INSTALLATION_WORKING)
230 RADAR_NotifyUFORemovedFromOneRadar(&installation->radar, ufo, destroyed);
231 }
232 }
233
234 /**
235 * @brief Set radar range to new value
236 * @param[in,out] radar The radar to update/initialize
237 * @param[in] range New range of the radar
238 * @param[in] trackingRange New tracking range of the radar
239 * @param[in] level The tech level of the radar
240 * @param[in] updateSourceRadarMap if the radar overlay should be updated.
241 */
RADAR_Initialise(radar_t * radar,float range,float trackingRange,float level,bool updateSourceRadarMap)242 void RADAR_Initialise (radar_t* radar, float range, float trackingRange, float level, bool updateSourceRadarMap)
243 {
244 const int oldrange = radar->range;
245
246 if (EQUAL(level, 0.0)) {
247 radar->range = 0.0f;
248 radar->trackingRange = 0.0f;
249 } else {
250 radar->range = range * (1 + (level - 1) * RADAR_UPGRADE_MULTIPLIER);
251 radar->trackingRange = trackingRange * (1 + (level - 1) * RADAR_UPGRADE_MULTIPLIER);
252 }
253
254 radar->ufoDetectionProbability = 0.000125f * DETECTION_INTERVAL;
255
256 assert(radar->numUFOs >= 0);
257
258 if (updateSourceRadarMap && !EQUAL(radar->range, oldrange)) {
259 RADAR_UpdateStaticRadarCoverage();
260 RADAR_UpdateWholeRadarOverlay();
261 }
262 }
263
264 /**
265 * @brief Reset UFO sensored on radar.
266 * @param[out] radar The radar to initialize.
267 */
RADAR_InitialiseUFOs(radar_t * radar)268 void RADAR_InitialiseUFOs (radar_t* radar)
269 {
270 radar->numUFOs = 0;
271 OBJZERO(radar->ufos);
272 }
273
274 /**
275 * @brief Update radar coverage when building/destroying new radar
276 * @note This must be called on each radar build/destruction because radar facilities may have different level.
277 * @note This must also be called when radar installation become inactive or active (due to dependencies)
278 * @note called with update_base_radar_coverage
279 */
RADAR_UpdateBaseRadarCoverage_f(void)280 void RADAR_UpdateBaseRadarCoverage_f (void)
281 {
282 int baseIdx;
283 base_t* base;
284 float level;
285
286 if (cgi->Cmd_Argc() < 2) {
287 Com_Printf("Usage: %s <baseIdx> <buildingType>\n", cgi->Cmd_Argv(0));
288 return;
289 }
290
291 baseIdx = atoi(cgi->Cmd_Argv(1));
292
293 if (baseIdx < 0 || baseIdx >= MAX_BASES) {
294 Com_Printf("RADAR_UpdateBaseRadarCoverage_f: %i is outside bounds\n", baseIdx);
295 return;
296 }
297
298 base = B_GetFoundedBaseByIDX(baseIdx);
299
300 if (!base)
301 return;
302
303 level = B_GetMaxBuildingLevel(base, B_RADAR);
304 RADAR_Initialise(&base->radar, RADAR_BASERANGE, RADAR_BASETRACKINGRANGE, level, true);
305 CP_UpdateMissionVisibleOnGeoscape();
306 }
307
308 /**
309 * @brief Update radar coverage when building/destroying new radar
310 * @param[in,out] installation The radartower to update
311 * @param[in] radarRange New range of the radar
312 * @param[in] trackingRadarRange New tracking range of the radar
313 */
RADAR_UpdateInstallationRadarCoverage(installation_t * installation,const float radarRange,const float trackingRadarRange)314 void RADAR_UpdateInstallationRadarCoverage (installation_t* installation, const float radarRange, const float trackingRadarRange)
315 {
316 /* Sanity check */
317 if (!installation || !installation->installationTemplate)
318 cgi->Com_Error(ERR_DROP, "RADAR_UpdateInstallationRadarCoverage: No installation or no template!\n");
319
320 /* Do nothing if installation not finished */
321 if (installation->installationStatus != INSTALLATION_WORKING)
322 return;
323 /* Do nothing if this isn't a RadarTower */
324 if (installation->installationTemplate->radarRange <= 0
325 || installation->installationTemplate->trackingRange <= 0)
326 return;
327
328 RADAR_Initialise(&installation->radar, radarRange, trackingRadarRange, RADAR_INSTALLATIONLEVEL, true);
329 CP_UpdateMissionVisibleOnGeoscape();
330 }
331
332 /**
333 * @brief Adds detected UFO to any radar in range (if not already detected).
334 * @param[in] ufo Pointer to the UFO to check.
335 */
RADAR_AddDetectedUFOToEveryRadar(const aircraft_t * ufo)336 void RADAR_AddDetectedUFOToEveryRadar (const aircraft_t* ufo)
337 {
338 base_t* base = nullptr;
339
340 AIR_Foreach(aircraft) {
341 if (!AIR_IsAircraftOnGeoscape(aircraft))
342 continue;
343
344 if (!RADAR_IsUFOSensored(&aircraft->radar, ufo)) {
345 /* Distance from radar to UFO */
346 const float dist = GetDistanceOnGlobe(ufo->pos, aircraft->pos);
347 if (dist <= aircraft->radar.trackingRange)
348 RADAR_AddUFO(&aircraft->radar, ufo);
349 }
350 }
351
352 while ((base = B_GetNext(base)) != nullptr) {
353 if (!RADAR_IsUFOSensored(&base->radar, ufo)) {
354 /* Distance from radar to UFO */
355 const float dist = GetDistanceOnGlobe(ufo->pos, base->pos);
356 if (dist <= base->radar.trackingRange)
357 RADAR_AddUFO(&base->radar, ufo);
358 }
359 }
360
361 INS_Foreach(installation) {
362 /* No need to check installations without radar */
363 if (!installation->radar.trackingRange)
364 continue;
365
366 if (!RADAR_IsUFOSensored(&installation->radar, ufo)) {
367 /* Distance from radar to UFO */
368 const float dist = GetDistanceOnGlobe(ufo->pos, installation->pos);
369 if (dist <= ufo->radar.trackingRange)
370 RADAR_AddUFO(&installation->radar, ufo);
371 }
372 }
373 }
374
375 /**
376 * @brief Check if the specified position is within base radar range
377 * @note aircraft radars are not checked (and this is intended)
378 * @return true if the position is inside one of the base radar range
379 */
RADAR_CheckRadarSensored(const vec2_t pos)380 bool RADAR_CheckRadarSensored (const vec2_t pos)
381 {
382 base_t* base = nullptr;
383
384 while ((base = B_GetNext(base)) != nullptr) {
385 const float dist = GetDistanceOnGlobe(pos, base->pos); /* Distance from base to position */
386 if (dist <= base->radar.range)
387 return true;
388 }
389
390 INS_Foreach(installation) {
391 float dist;
392
393 dist = GetDistanceOnGlobe(pos, installation->pos); /* Distance from base to position */
394 if (dist <= installation->radar.range)
395 return true;
396 }
397
398 return false;
399 }
400
401 /**
402 * @brief Check if the specified UFO is inside the sensor range of the given radar
403 * @param[in,out] radar radar that may detect the UFO.
404 * @param[in] posRadar Position of @c radar
405 * @param[in,out] ufo aircraft that should be checked.
406 * @param[in] detected Is the UFO already detected by another radar? (Beware: this is not the same as ufo->detected)
407 * @return true if the aircraft is inside sensor and was sensored
408 * @sa UFO_CampaignCheckEvents
409 * @sa CP_CheckNewMissionDetectedOnGeoscape
410 */
RADAR_CheckUFOSensored(radar_t * radar,const vec2_t posRadar,const aircraft_t * ufo,bool detected)411 bool RADAR_CheckUFOSensored (radar_t* radar, const vec2_t posRadar,
412 const aircraft_t* ufo, bool detected)
413 {
414 int dist;
415 bool ufoIsSensored;
416
417 /* indice of ufo in radar list */
418 ufoIsSensored = RADAR_IsUFOSensored(radar, ufo);
419 /* Distance from radar to ufo */
420 dist = GetDistanceOnGlobe(posRadar, ufo->pos);
421
422 if ((detected ? radar->trackingRange : radar->range) > dist) {
423 if (detected) {
424 if (!ufoIsSensored) {
425 /* UFO was not sensored by this radar, but by another one
426 * (it just entered this radar zone) */
427 RADAR_AddUFO(radar, ufo);
428 }
429 return true;
430 } else {
431 /* UFO is sensored by no radar, so it shouldn't be sensored
432 * by this radar */
433 assert(ufoIsSensored == false);
434 /* Check if UFO is detected */
435 if (frand() <= radar->ufoDetectionProbability) {
436 RADAR_AddDetectedUFOToEveryRadar(ufo);
437 return true;
438 }
439 return false;
440 }
441 }
442
443 /* UFO is not in this sensor range any more (but maybe
444 * in the range of another radar) */
445 if (ufoIsSensored)
446 RADAR_RemoveUFO(radar, ufo);
447
448 return false;
449 }
450
451 /**
452 * @brief Set radar to proper values after loading
453 * @note numUFOs is not saved, so we must calculate it.
454 * @note should be called after loading.
455 */
RADAR_SetRadarAfterLoading(void)456 void RADAR_SetRadarAfterLoading (void)
457 {
458 aircraft_t* ufo;
459
460 ufo = nullptr;
461 while ((ufo = UFO_GetNext(ufo)) != nullptr) {
462 if (!ufo->detected)
463 continue;
464
465 RADAR_AddDetectedUFOToEveryRadar(ufo);
466 }
467
468 GEO_UpdateGeoscapeDock();
469 }
470