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