1 /*
2  * Copyright 2010-2014 OpenXcom Developers.
3  *
4  * This file is part of OpenXcom.
5  *
6  * OpenXcom is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * OpenXcom is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with OpenXcom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #define _USE_MATH_DEFINES
20 #include "AlienMission.h"
21 #include "AlienBase.h"
22 #include "Base.h"
23 #include "../fmath.h"
24 #include "../Engine/Exception.h"
25 #include "../Engine/Game.h"
26 #include "../Engine/Logger.h"
27 #include "../Engine/RNG.h"
28 #include "../Geoscape/Globe.h"
29 #include "../Ruleset/RuleAlienMission.h"
30 #include "../Ruleset/RuleRegion.h"
31 #include "../Ruleset/RuleCountry.h"
32 #include "../Ruleset/Ruleset.h"
33 #include "../Ruleset/RuleUfo.h"
34 #include "../Ruleset/City.h"
35 #include "../Ruleset/UfoTrajectory.h"
36 #include "SavedGame.h"
37 #include "TerrorSite.h"
38 #include "Ufo.h"
39 #include "Craft.h"
40 #include "Region.h"
41 #include "Country.h"
42 #include "Waypoint.h"
43 #include <assert.h>
44 #include <algorithm>
45 #include <functional>
46 #include <math.h>
47 
48 namespace OpenXcom
49 {
50 
AlienMission(const RuleAlienMission & rule)51 AlienMission::AlienMission(const RuleAlienMission &rule) : _rule(rule), _nextWave(0), _nextUfoCounter(0), _spawnCountdown(0), _liveUfos(0), _uniqueID(0), _base(0)
52 {
53 	// Empty by design.
54 }
55 
~AlienMission()56 AlienMission::~AlienMission()
57 {
58 	// Empty by design.
59 }
60 
61 class matchById: public std::unary_function<const AlienBase *, bool>
62 {
63 public:
64 	/// Remember ID.
matchById(int id)65 	matchById(int id) : _id(id) { /* Empty by design. */ }
66 	/// Match with stored ID.
operator ()(const AlienBase * ab) const67 	bool operator()(const AlienBase *ab) const { return ab->getId() == _id; }
68 private:
69 	int _id;
70 };
71 
72 /**
73  * @param node The YAML node containing the data.
74  * @param game The game data, required to locate the alien base.
75  */
load(const YAML::Node & node,SavedGame & game)76 void AlienMission::load(const YAML::Node& node, SavedGame &game)
77 {
78 	_region = node["region"].as<std::string>(_region);
79 	_race = node["race"].as<std::string>(_race);
80 	_nextWave = node["nextWave"].as<size_t>(_nextWave);
81 	_nextUfoCounter = node["nextUfoCounter"].as<size_t>(_nextUfoCounter);
82 	_spawnCountdown = node["spawnCountdown"].as<size_t>(_spawnCountdown);
83 	_liveUfos = node["liveUfos"].as<size_t>(_liveUfos);
84 	_uniqueID = node["uniqueID"].as<int>(_uniqueID);
85 	if (const YAML::Node &base = node["alienBase"])
86 	{
87 		int id = base.as<int>();
88 		std::vector<AlienBase*>::const_iterator found = std::find_if(game.getAlienBases()->begin(), game.getAlienBases()->end(), matchById(id));
89 		if (found == game.getAlienBases()->end())
90 		{
91 			throw Exception("Corrupted save: Invalid base for mission.");
92 		}
93 		_base = *found;
94 	}
95 
96 }
97 
save() const98 YAML::Node AlienMission::save() const
99 {
100 	YAML::Node node;
101 	node["type"] = _rule.getType();
102 	node["region"] = _region;
103 	node["race"] = _race;
104 	node["nextWave"] = _nextWave;
105 	node["nextUfoCounter"] = _nextUfoCounter;
106 	node["spawnCountdown"] = _spawnCountdown;
107 	node["liveUfos"] = _liveUfos;
108 	node["uniqueID"] = _uniqueID;
109 	if (_base)
110 	{
111 		node["alienBase"] = _base->getId();
112 	}
113 	return node;
114 }
115 
getType() const116 const std::string &AlienMission::getType() const
117 {
118 	return _rule.getType();
119 }
120 
121 /**
122  * Check if a mission is over and can be safely removed from the game.
123  * A mission is over if it will spawn no more UFOs and it has no UFOs still in
124  * the game.
125  * @return If the mission can be safely removed from game.
126  */
isOver() const127 bool AlienMission::isOver() const
128 {
129 	if (_rule.getType() == "STR_ALIEN_INFILTRATION")
130 	{
131 		//Infiltrations continue for ever.
132 		return false;
133 	}
134 	if (_nextWave == _rule.getWaveCount() && !_liveUfos)
135 	{
136 		return true;
137 	}
138 	return false;
139 }
140 
141 /**
142  * Find an XCOM base in this region that is marked for retaliation.
143  */
144 class FindMarkedXCOMBase: public std::unary_function<const Base *, bool>
145 {
146 public:
FindMarkedXCOMBase(const RuleRegion & region)147 	FindMarkedXCOMBase(const RuleRegion &region) : _region(region) { /* Empty by design. */ }
operator ()(const Base * base) const148 	bool operator()(const Base *base) const { return (_region.insideRegion(base->getLongitude(), base->getLatitude()) && base->getRetaliationTarget()); }
149 private:
150 	const RuleRegion &_region;
151 };
152 
think(Game & engine,const Globe & globe)153 void AlienMission::think(Game &engine, const Globe &globe)
154 {
155 	const Ruleset &ruleset = *engine.getRuleset();
156 	SavedGame &game = *engine.getSavedGame();
157 	if (_nextWave >= _rule.getWaveCount())
158 		return;
159 	if (_spawnCountdown > 30)
160 	{
161 		_spawnCountdown -= 30;
162 		return;
163 	}
164 	const MissionWave &wave = _rule.getWave(_nextWave);
165 	RuleUfo &ufoRule = *ruleset.getUfo(wave.ufoType);
166 	const UfoTrajectory &trajectory = *ruleset.getUfoTrajectory(wave.trajectory);
167 	Ufo *ufo = spawnUfo(game, ruleset, globe, ufoRule, trajectory);
168 	if (ufo)
169 	{
170 		//Some missions may not spawn a UFO!
171 		game.getUfos()->push_back(ufo);
172 	}
173 	++_nextUfoCounter;
174 	if (_nextUfoCounter == wave.ufoCount)
175 	{
176 		_nextUfoCounter = 0;
177 		++_nextWave;
178 	}
179 	if (_rule.getType() == "STR_ALIEN_INFILTRATION" && _nextWave == _rule.getWaveCount())
180 	{
181 		for (std::vector<Country*>::iterator c = game.getCountries()->begin(); c != game.getCountries()->end(); ++c)
182 		{
183 			if (!(*c)->getPact() && !(*c)->getNewPact() && ruleset.getRegion(_region)->insideRegion((*c)->getRules()->getLabelLongitude(), (*c)->getRules()->getLabelLatitude()))
184 			{
185 				(*c)->setNewPact();
186 				spawnAlienBase(globe, engine);
187 				break;
188 			}
189 		}
190 
191 
192 		// Infiltrations loop for ever.
193 		_nextWave = 0;
194 	}
195 	if (_rule.getType() == "STR_ALIEN_BASE" && _nextWave == _rule.getWaveCount())
196 	{
197 		spawnAlienBase(globe, engine);
198 	}
199 	if (_nextWave != _rule.getWaveCount())
200 	{
201 		size_t spawnTimer = _rule.getWave(_nextWave).spawnTimer / 30;
202 		_spawnCountdown = (spawnTimer/2 + RNG::generate(0, spawnTimer)) * 30;
203 	}
204 }
205 
206 /**
207  * This function will spawn a UFO according the the mission rules.
208  * Some code is duplicated between cases, that's ok for now. It's on different
209  * code paths and the function is MUCH easier to read written this way.
210  * @param game The saved game information.
211  * @param ruleset The ruleset.
212  * @param globe The globe, for land checks.
213  * @param ufoRule The rule for the desired UFO.
214  * @param trajectory The rule for the desired trajectory.
215  * @return Pointer to the spawned UFO. If the mission does not desire to spawn a UFO, 0 is returned.
216  */
spawnUfo(const SavedGame & game,const Ruleset & ruleset,const Globe & globe,const RuleUfo & ufoRule,const UfoTrajectory & trajectory)217 Ufo *AlienMission::spawnUfo(const SavedGame &game, const Ruleset &ruleset, const Globe &globe, const RuleUfo &ufoRule, const UfoTrajectory &trajectory)
218 {
219 	if (_rule.getType() == "STR_ALIEN_RETALIATION")
220 	{
221 		const RuleRegion &regionRules = *ruleset.getRegion(_region);
222 		std::vector<Base *>::const_iterator found =
223 		    std::find_if(game.getBases()->begin(), game.getBases()->end(),
224 				 FindMarkedXCOMBase(regionRules));
225 		if (found != game.getBases()->end())
226 		{
227 			// Spawn a battleship straight for the XCOM base.
228 			const RuleUfo &battleshipRule = *ruleset.getUfo("STR_BATTLESHIP");
229 			const UfoTrajectory &assaultTrajectory = *ruleset.getUfoTrajectory("__RETALIATION_ASSAULT_RUN");
230 			Ufo *ufo = new Ufo(const_cast<RuleUfo*>(&battleshipRule));
231 			ufo->setMissionInfo(this, &assaultTrajectory);
232 			std::pair<double, double> pos;
233 			if (trajectory.getAltitude(0) == "STR_GROUND")
234 			{
235 				pos = getLandPoint(globe, regionRules, trajectory.getZone(0));
236 			}
237 			else
238 			{
239 				pos = regionRules.getRandomPoint(trajectory.getZone(0));
240 			}
241 			ufo->setAltitude(assaultTrajectory.getAltitude(0));
242 			ufo->setSpeed(assaultTrajectory.getSpeedPercentage(0) * ufoRule.getMaxSpeed());
243 			ufo->setLongitude(pos.first);
244 			ufo->setLatitude(pos.second);
245 			Waypoint *wp = new Waypoint();
246 			wp->setLongitude((*found)->getLongitude());
247 			wp->setLatitude((*found)->getLatitude());
248 			ufo->setDestination(wp);
249 			return ufo;
250 		}
251 	}
252 	else if (_rule.getType() == "STR_ALIEN_SUPPLY")
253 	{
254 		if (ufoRule.getType() == "STR_SUPPLY_SHIP" && !_base)
255 		{
256 			// No base to supply!
257 			return 0;
258 		}
259 		// Our destination is always an alien base.
260 		Ufo *ufo = new Ufo(const_cast<RuleUfo*>(&ufoRule));
261 		ufo->setMissionInfo(this, &trajectory);
262 		const RuleRegion &regionRules = *ruleset.getRegion(_region);
263 		std::pair<double, double> pos;
264 		if (trajectory.getAltitude(0) == "STR_GROUND")
265 		{
266 			pos = getLandPoint(globe, regionRules, trajectory.getZone(0));
267 		}
268 		else
269 		{
270 			pos = regionRules.getRandomPoint(trajectory.getZone(0));
271 		}
272 		ufo->setAltitude(trajectory.getAltitude(0));
273 		ufo->setSpeed(trajectory.getSpeedPercentage(0) * ufoRule.getMaxSpeed());
274 		ufo->setLongitude(pos.first);
275 		ufo->setLatitude(pos.second);
276 		Waypoint *wp = new Waypoint();
277 		if (trajectory.getAltitude(1) == "STR_GROUND")
278 		{
279 			if (ufoRule.getType() == "STR_SUPPLY_SHIP")
280 			{
281 				// Supply ships on supply missions land on bases, ignore trajectory zone.
282 				pos.first = _base->getLongitude();
283 				pos.second = _base->getLatitude();
284 			}
285 			else
286 			{
287 				// Other ships can land where they want.
288 				pos = getLandPoint(globe, regionRules, trajectory.getZone(1));
289 			}
290 		}
291 		else
292 		{
293 			pos = regionRules.getRandomPoint(trajectory.getZone(1));
294 		}
295 		wp->setLongitude(pos.first);
296 		wp->setLatitude(pos.second);
297 		ufo->setDestination(wp);
298 		return ufo;
299 	}
300 	// Spawn according to sequence.
301 	Ufo *ufo = new Ufo(const_cast<RuleUfo*>(&ufoRule));
302 	ufo->setMissionInfo(this, &trajectory);
303 	const RuleRegion &regionRules = *ruleset.getRegion(_region);
304 	std::pair<double, double> pos = getWaypoint(trajectory, 0, globe, regionRules);
305 	ufo->setAltitude(trajectory.getAltitude(0));
306 	if (trajectory.getAltitude(0) == "STR_GROUND")
307 	{
308 		ufo->setSecondsRemaining(trajectory.groundTimer());
309 	}
310 	ufo->setSpeed(trajectory.getSpeedPercentage(0) * ufoRule.getMaxSpeed());
311 	ufo->setLongitude(pos.first);
312 	ufo->setLatitude(pos.second);
313 	Waypoint *wp = new Waypoint();
314 	pos = getWaypoint(trajectory, 1, globe, regionRules);
315 	wp->setLongitude(pos.first);
316 	wp->setLatitude(pos.second);
317 		ufo->setDestination(wp);
318 	return ufo;
319 }
320 
start(size_t initialCount)321 void AlienMission::start(size_t initialCount)
322 {
323 	_nextWave = 0;
324 	_nextUfoCounter = 0;
325 	_liveUfos = 0;
326 	if (initialCount == 0)
327 	{
328 		size_t spawnTimer = _rule.getWave(0).spawnTimer / 30;
329 		_spawnCountdown = (spawnTimer / 2 + RNG::generate(0, spawnTimer)) * 30;
330 	}
331 	else
332 	{
333 		_spawnCountdown = initialCount;
334 	}
335 }
336 
337 /** @brief Match a base from it's coordinates.
338  * This function object uses coordinates to match a base.
339  */
340 class MatchBaseCoordinates: public std::unary_function<const Base *, bool>
341 {
342 public:
343 	/// Remember the query coordinates.
MatchBaseCoordinates(double lon,double lat)344 	MatchBaseCoordinates(double lon, double lat) : _lon(lon), _lat(lat) { /* Empty by design. */ }
345 	/// Match with base's coordinates.
operator ()(const Base * base) const346 	bool operator()(const Base *base) const { return AreSame(base->getLongitude(), _lon) && AreSame(base->getLatitude(), _lat); }
347 private:
348 	double _lon, _lat;
349 };
350 
351 /**
352  * This function is called when one of the mission's UFOs arrives at it's current destination.
353  * It takes care of sending the UFO to the next waypoint, landing UFOs and
354  * marking them for removal as required. It must set the game data in a way that the rest of the code
355  * understands what to do.
356  * @param ufo The UFO that reached it's waypoint.
357  * @param engine The game engine, required to get access to game data and game rules.
358  * @param globe The earth globe, required to get access to land checks.
359  */
ufoReachedWaypoint(Ufo & ufo,Game & engine,const Globe & globe)360 void AlienMission::ufoReachedWaypoint(Ufo &ufo, Game &engine, const Globe &globe)
361 {
362 	const Ruleset &rules = *engine.getRuleset();
363 	SavedGame &game = *engine.getSavedGame();
364 	const size_t curWaypoint = ufo.getTrajectoryPoint();
365 	const size_t nextWaypoint = curWaypoint + 1;
366 	const UfoTrajectory &trajectory = ufo.getTrajectory();
367 	if (nextWaypoint >= trajectory.getWaypointCount())
368 	{
369 		ufo.setDetected(false);
370 		ufo.setStatus(Ufo::DESTROYED);
371 		return;
372 	}
373 	ufo.setAltitude(trajectory.getAltitude(nextWaypoint));
374 	ufo.setTrajectoryPoint(nextWaypoint);
375 	std::pair<double, double> pos = getWaypoint(trajectory, nextWaypoint, globe, *rules.getRegion(_region));
376 
377 	// screw it, we're not taking any chances, use the city's lon/lat info instead of the region's
378 	// TODO: find out why there is a discrepency between generated city mission zones and the cities that generated them.
379 	// honolulu: 3.6141230952747376, -0.37332941766009109
380 	// UFO: lon: 3.61412 lat: -0.373329
381 	// Zone: Longitudes: 3.61412 to 3.61412 Lattitudes: -0.373329 to -0.373329
382 	// http://openxcom.org/bugs/openxcom/issues/615#comment_3292
383 	if (ufo.getRules()->getType() == "STR_TERROR_SHIP" && _rule.getType() == "STR_ALIEN_TERROR" && trajectory.getZone(nextWaypoint) == RuleRegion::CITY_MISSION_ZONE)
384 	{
385 		while(!rules.locateCity(pos.first, pos.second))
386 		{
387 			Log(LOG_DEBUG) << "Longitude: " << pos.first << "Lattitude: " << pos.second << " invalid";
388 			size_t city = RNG::generate(0, rules.getRegion(_region)->getCities()->size() - 1);
389 			pos.first = rules.getRegion(_region)->getCities()->at(city)->getLongitude();
390 			pos.second = rules.getRegion(_region)->getCities()->at(city)->getLatitude();
391 		}
392 	}
393 
394 	Waypoint *wp = new Waypoint();
395 	wp->setLongitude(pos.first);
396 	wp->setLatitude(pos.second);
397 	ufo.setDestination(wp);
398 	if (ufo.getAltitude() != "STR_GROUND")
399 	{
400 		if (ufo.getLandId() != 0)
401 		{
402 			ufo.setLandId(0);
403 		}
404 		// Set next waypoint.
405 		ufo.setSpeed((int)(ufo.getRules()->getMaxSpeed() * trajectory.getSpeedPercentage(nextWaypoint)));
406 	}
407 	else
408 	{
409 		// UFO landed.
410 
411 		if (ufo.getRules()->getType() == "STR_TERROR_SHIP" && _rule.getType() == "STR_ALIEN_TERROR" && trajectory.getZone(curWaypoint) == RuleRegion::CITY_MISSION_ZONE)
412 		{
413 			// Specialized: STR_ALIEN_TERROR
414 			// Remove UFO, replace with TerrorSite.
415 			addScore(ufo.getLongitude(), ufo.getLatitude(), engine);
416 			ufo.setStatus(Ufo::DESTROYED);
417 			TerrorSite *terrorSite = new TerrorSite();
418 			terrorSite->setLongitude(ufo.getLongitude());
419 			terrorSite->setLatitude(ufo.getLatitude());
420 			terrorSite->setId(game.getId("STR_TERROR_SITE"));
421 			terrorSite->setSecondsRemaining(4 * 3600 + RNG::generate(0, 6) * 3600);
422 			terrorSite->setAlienRace(_race);
423 			if (!rules.locateCity(ufo.getLongitude(), ufo.getLatitude()))
424 			{
425 				std::ostringstream error;
426 				error << "Mission number: " << getId() << " in region: " << getRegion() << " trying to land at lon: " << ufo.getLongitude() << " lat: " << ufo.getLatitude() << " ufo is on flightpath: " << ufo.getTrajectory().getID() << " at point: " << ufo.getTrajectoryPoint() << ", no city found.";
427 				Log(LOG_FATAL) << error.str();
428 				std::vector<MissionArea> cityZones = rules.getRegion(getRegion())->getMissionZones().at(RuleRegion::CITY_MISSION_ZONE).areas;
429 				for (int i = 0; i != cityZones.size(); ++i)
430 				{
431 					error.str("");
432 					error << "Zone " << i  << ": Longitudes: " << cityZones.at(i).lonMin * M_PI / 180 << " to " << cityZones.at(i).lonMax * M_PI / 180 << " Latitudes: " << cityZones.at(i).latMin * M_PI / 180 << " to " << cityZones.at(i).latMax * M_PI / 180;
433 					Log(LOG_INFO) << error.str();
434 				}
435 				for (std::vector<City*>::const_iterator i = rules.getRegion(getRegion())->getCities()->begin(); i != rules.getRegion(getRegion())->getCities()->end(); ++i)
436 				{
437 					error.str("");
438 					error << "City: " << (*i)->getName()  << " Longitude: " << (*i)->getLongitude() << " Latitude: " << (*i)->getLatitude();
439 					Log(LOG_INFO) << error.str();
440 				}
441 				assert(0 && "Terror Mission failed to find a city, please check your log file for more details");
442 			}
443 			game.getTerrorSites()->push_back(terrorSite);
444 			for (std::vector<Target*>::iterator t = ufo.getFollowers()->begin(); t != ufo.getFollowers()->end();)
445 			{
446 				Craft* c = dynamic_cast<Craft*>(*t);
447 				if (c && c->getNumSoldiers() != 0)
448 				{
449 					c->setDestination(terrorSite);
450 					t = ufo.getFollowers()->begin();
451 				}
452 				else
453 				{
454 					++t;
455 				}
456 			}
457 		}
458 		else if (_rule.getType() == "STR_ALIEN_RETALIATION" && trajectory.getID() == "__RETALIATION_ASSAULT_RUN")
459 		{
460 			// Ignore what the trajectory might say, this is a base assault.
461 			// Remove UFO, replace with Base defense.
462 			ufo.setDetected(false);
463 			std::vector<Base *>::const_iterator found =
464 			    std::find_if(game.getBases()->begin(), game.getBases()->end(),
465 					 MatchBaseCoordinates(ufo.getLongitude(), ufo.getLatitude()));
466 			if (found == game.getBases()->end())
467 			{
468 				ufo.setStatus(Ufo::DESTROYED);
469 				// Only spawn mission if the base is still there.
470 				return;
471 			}
472 			ufo.setDestination(*found);
473 		}
474 		else
475 		{
476 			// Set timer for UFO on the ground.
477 			ufo.setSecondsRemaining(trajectory.groundTimer());
478 			if (ufo.getDetected() && ufo.getLandId() == 0)
479 			{
480 				ufo.setLandId(engine.getSavedGame()->getId("STR_LANDING_SITE"));
481 			}
482 		}
483 	}
484 }
485 
486 /**
487  * This function is called when one of the mission's UFOs is shot down (crashed or destroyed).
488  * Currently the only thing that happens is delaying the next UFO in the mission sequence.
489  * @param ufo The UFO that was shot down.
490  * @param engine The game engine, unused for now.
491  * @param globe The earth globe, unused for now.
492  */
ufoShotDown(Ufo & ufo,Game &,const Globe &)493 void AlienMission::ufoShotDown(Ufo &ufo, Game &, const Globe &)
494 {
495 	switch (ufo.getStatus())
496 	{
497 	case Ufo::FLYING:
498 	case Ufo::LANDED:
499 		assert(0 && "Ufo seems ok!");
500 		break;
501 	case Ufo::CRASHED:
502 	case Ufo::DESTROYED:
503 		if (_nextWave != _rule.getWaveCount())
504 		{
505 			// Delay next wave
506 			_spawnCountdown += 30 * (RNG::generate(0, 48) + 400);
507 		}
508 		break;
509 	}
510 }
511 
512 /**
513  * This function is called when one of the mission's UFOs has finished it's time on the ground.
514  * It takes care of sending the UFO to the next waypoint and marking them for removal as required.
515  * It must set the game data in a way that the rest of the code understands what to do.
516  * @param ufo The UFO that reached it's waypoint.
517  * @param engine The game engine, required to get access to game data and game rules.
518  * @param globe The earth globe, required to get access to land checks.
519  */
ufoLifting(Ufo & ufo,Game & engine,const Globe & globe)520 void AlienMission::ufoLifting(Ufo &ufo, Game &engine, const Globe &globe)
521 {
522 	switch (ufo.getStatus())
523 	{
524 	case Ufo::FLYING:
525 		assert(0 && "Ufo is already on the air!");
526 		break;
527 	case Ufo::LANDED:
528 		{
529 			// base missions only get points when they are completed.
530 			if (_rule.getPoints() > 0 && _rule.getType() != "STR_ALIEN_BASE")
531 			{
532 				addScore(ufo.getLongitude(), ufo.getLatitude(), engine);
533 			}
534 			ufo.setAltitude("STR_VERY_LOW");
535 			ufo.setSpeed((int)(ufo.getRules()->getMaxSpeed() * ufo.getTrajectory().getSpeedPercentage(ufo.getTrajectoryPoint())));
536 		}
537 		break;
538 	case Ufo::CRASHED:
539 		// Mission expired
540 		ufo.setDetected(false);
541 		ufo.setStatus(Ufo::DESTROYED);
542 		break;
543 	case Ufo::DESTROYED:
544 		assert(0 && "UFO can't fly!");
545 		break;
546 	}
547 }
548 
549 /**
550  * The new time must be a multiple of 30 minutes, and more than 0.
551  * Calling this on a finished mission has no effect.
552  * @param minutes The minutes until the next UFO wave will spawn.
553  */
setWaveCountdown(size_t minutes)554 void AlienMission::setWaveCountdown(size_t minutes)
555 {
556 	assert(minutes != 0 && minutes % 30 == 0);
557 	if (isOver())
558 	{
559 		return;
560 	}
561 	_spawnCountdown = minutes;
562 }
563 
564 /**
565  * Assigns a unique ID to this mission.
566  * It is an error to assign two IDs to the same mission.
567  * @param id The UD to assign.
568  */
setId(int id)569 void AlienMission::setId(int id)
570 {
571 	assert(_uniqueID == 0 && "Reassigning ID!");
572 	_uniqueID = id;
573 }
574 
575 /**
576  * @return The unique ID assigned to this mission.
577  */
getId() const578 int AlienMission::getId() const
579 {
580 	assert(_uniqueID != 0 && "Uninitalized mission!");
581 	return _uniqueID;
582 }
583 
584 /**
585  * Sets the alien base associated with this mission.
586  * Only the alien supply missions care about this.
587  * @param base A pointer to an alien base.
588  */
setAlienBase(const AlienBase * base)589 void AlienMission::setAlienBase(const AlienBase *base)
590 {
591 	_base = base;
592 }
593 
594 /**
595  * Only alien supply missions ever have a valid pointer.
596  * @return A pointer (possibly 0) of the AlienBase for this mission.
597  */
getAlienBase() const598 const AlienBase *AlienMission::getAlienBase() const
599 {
600 	return _base;
601 }
602 
603 /**
604  * Add alien points to the country and region at the coordinates given.
605  * @param lon Longitudinal coordinates to check.
606  * @param lat Latitudinal coordinates to check.
607  * @param engine The game engine, required to get access to game data and game rules.
608  */
addScore(const double lon,const double lat,Game & engine)609 void AlienMission::addScore(const double lon, const double lat, Game &engine)
610 {
611 	for (std::vector<Region *>::iterator region = engine.getSavedGame()->getRegions()->begin(); region != engine.getSavedGame()->getRegions()->end(); ++region)
612 	{
613 		if ((*region)->getRules()->insideRegion(lon, lat))
614 		{
615 			(*region)->addActivityAlien(_rule.getPoints());
616 			break;
617 		}
618 	}
619 	for (std::vector<Country *>::iterator country = engine.getSavedGame()->getCountries()->begin(); country != engine.getSavedGame()->getCountries()->end(); ++country)
620 	{
621 		if ((*country)->getRules()->insideCountry(lon, lat))
622 		{
623 			(*country)->addActivityAlien(_rule.getPoints());
624 			break;
625 		}
626 	}
627 }
628 
629 /**
630  * Spawn an alien base.
631  * @param globe The earth globe, required to get access to land checks.
632  * @param engine The game engine, required to get access to game data and game rules.
633  */
spawnAlienBase(const Globe & globe,Game & engine)634 void AlienMission::spawnAlienBase(const Globe &globe, Game &engine)
635 {
636 	SavedGame &game = *engine.getSavedGame();
637 	if (game.getAlienBases()->size() >= 8)
638 	{
639 		return;
640 	}
641 	const Ruleset &ruleset = *engine.getRuleset();
642 	// Once the last UFO is spawned, the aliens build their base.
643 	const RuleRegion &regionRules = *ruleset.getRegion(_region);
644 	std::pair<double, double> pos = getLandPoint(globe, regionRules, RuleRegion::ALIEN_BASE_ZONE);
645 	AlienBase *ab = new AlienBase();
646 	ab->setAlienRace(_race);
647 	ab->setId(game.getId("STR_ALIEN_BASE"));
648 	ab->setLongitude(pos.first);
649 	ab->setLatitude(pos.second);
650 	game.getAlienBases()->push_back(ab);
651 	addScore(pos.first, pos.second, engine);
652 }
653 
654 /*
655  * Sets the mission's region. if the region is incompatible with
656  * actually carrying out an attack, use the "fallback" region as
657  * defined in the ruleset.
658  * (this is a slight difference from the original, which just
659  * defaulted them to zone[0], North America)
660  * @param region the region we want to try to set the mission to.
661  * @param rules the ruleset, in case we need to swap out the region.
662  */
setRegion(const std::string & region,const Ruleset & rules)663 void AlienMission::setRegion(const std::string &region, const Ruleset &rules)
664 {
665 	if (rules.getRegion(region)->getMissionRegion() != "")
666 	{
667 		_region = rules.getRegion(region)->getMissionRegion();
668 	}
669 	else
670 	{
671 		_region = region;
672 	}
673 }
674 
675 /**
676  * Select a destination based on the criteria of our trajectory and desired waypoint.
677  * @param trajectory the trajectory in question.
678  * @param nextWaypoint the next logical waypoint in sequence (0 for newly spawned UFOs)
679  * @param globe The earth globe, required to get access to land checks.
680  * @param region the ruleset for the region of our mission.
681  * @return a set of lon and lat coordinates based on the criteria of the trajectory.
682  */
getWaypoint(const UfoTrajectory & trajectory,const size_t nextWaypoint,const Globe & globe,const RuleRegion & region)683 std::pair<double, double> AlienMission::getWaypoint(const UfoTrajectory &trajectory, const size_t nextWaypoint, const Globe &globe, const RuleRegion &region)
684 {
685 	/* LOOK MA! NO HANDS!
686 	if (trajectory.getAltitude(nextWaypoint) == "STR_GROUND")
687 	{
688 		return getLandPoint(globe, region, trajectory.getZone(nextWaypoint));
689 	}
690 	else
691 	*/
692 		return region.getRandomPoint(trajectory.getZone(nextWaypoint));
693 }
694 
695 /**
696  * Get a random point inside the given region zone.
697  * The point will be used to land a UFO, so it HAS to be on land.
698  */
getLandPoint(const Globe & globe,const RuleRegion & region,size_t zone)699 std::pair<double, double> AlienMission::getLandPoint(const Globe &globe, const RuleRegion &region, size_t zone)
700 {
701 	int tries = 0;
702 	std::pair<double, double> pos;
703 	do
704 	{
705 		pos = region.getRandomPoint(zone);
706 		++tries;
707 	}
708 	while (!(globe.insideLand(pos.first, pos.second)
709 		&& region.insideRegion(pos.first, pos.second))
710 		&& tries < 100);
711 	if (tries == 100)
712 	{
713 		Log(LOG_DEBUG) << "Region: " << region.getType() << " Longitude: " << pos.first << " Lattitude: " << pos.second << " invalid zone: " << zone << " ufo forced to land on water!";
714 	}
715 	return pos;
716 
717 }
718 
719 }
720