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 <cmath>
21 #include <climits>
22 #include <algorithm>
23 #include "AlienBAIState.h"
24 #include "ProjectileFlyBState.h"
25 #include "../Savegame/BattleUnit.h"
26 #include "../Savegame/BattleItem.h"
27 #include "../Savegame/Node.h"
28 #include "../Savegame/SavedBattleGame.h"
29 #include "../Savegame/SavedGame.h"
30 #include "../Battlescape/TileEngine.h"
31 #include "../Battlescape/Map.h"
32 #include "../Battlescape/BattlescapeState.h"
33 #include "../Savegame/Tile.h"
34 #include "../Battlescape/Pathfinding.h"
35 #include "../Engine/RNG.h"
36 #include "../Engine/Logger.h"
37 #include "../Engine/Game.h"
38 #include "../Ruleset/Armor.h"
39 #include "../Resource/ResourcePack.h"
40 #include "../Ruleset/Ruleset.h"
41 #include "../Ruleset/RuleItem.h"
42 
43 namespace OpenXcom
44 {
45 
46 
47 /**
48  * Sets up a BattleAIState.
49  * @param save Pointer to the battle game.
50  * @param unit Pointer to the unit.
51  * @param node Pointer to the node the unit originates from.
52  */
AlienBAIState(SavedBattleGame * save,BattleUnit * unit,Node * node)53 AlienBAIState::AlienBAIState(SavedBattleGame *save, BattleUnit *unit, Node *node) : BattleAIState(save, unit), _aggroTarget(0), _knownEnemies(0), _visibleEnemies(0), _spottingEnemies(0),
54 																				_escapeTUs(0), _ambushTUs(0), _reserveTUs(0), _rifle(false), _melee(false), _blaster(false),
55 																				_wasHit(false), _didPsi(false), _AIMode(AI_PATROL), _closestDist(100), _fromNode(node), _toNode(0)
56 {
57 	_traceAI = Options::traceAI;
58 
59 	_intelligence = _unit->getIntelligence();
60 	_escapeAction = new BattleAction();
61 	_ambushAction = new BattleAction();
62 	_attackAction = new BattleAction();
63 	_patrolAction = new BattleAction();
64 	_psiAction = new BattleAction();
65 }
66 
67 /**
68  * Deletes the BattleAIState.
69  */
~AlienBAIState()70 AlienBAIState::~AlienBAIState()
71 {
72 	delete _escapeAction;
73 	delete _ambushAction;
74 	delete _attackAction;
75 	delete _patrolAction;
76 	delete _psiAction;
77 }
78 
79 /**
80  * Loads the AI state from a YAML file.
81  * @param node YAML node.
82  */
load(const YAML::Node & node)83 void AlienBAIState::load(const YAML::Node &node)
84 {
85 	int fromNodeID, toNodeID;
86 	fromNodeID = node["fromNode"].as<int>(-1);
87 	toNodeID = node["toNode"].as<int>(-1);
88 	_AIMode = node["AIMode"].as<int>(0);
89 	_wasHit = node["wasHit"].as<bool>(false);
90 	if (fromNodeID != -1)
91 	{
92 		_fromNode = _save->getNodes()->at(fromNodeID);
93 	}
94 	if (toNodeID != -1)
95 	{
96 		_toNode = _save->getNodes()->at(toNodeID);
97 	}
98 }
99 
100 /**
101  * Saves the AI state to a YAML file.
102  * @return YAML node.
103  */
save() const104 YAML::Node AlienBAIState::save() const
105 {
106 	int fromNodeID = -1, toNodeID = -1;
107 	if (_fromNode)
108 		fromNodeID = _fromNode->getID();
109 	if (_toNode)
110 		toNodeID = _toNode->getID();
111 
112 	YAML::Node node;
113 	node["fromNode"] = fromNodeID;
114 	node["toNode"] = toNodeID;
115 	node["AIMode"] = _AIMode;
116 	node["wasHit"] = _wasHit;
117 	return node;
118 }
119 
120 /**
121  * Enters the current AI state.
122  */
enter()123 void AlienBAIState::enter()
124 {
125 	// ROOOAARR !
126 
127 }
128 
129 
130 /**
131  * Exits the current AI state.
132  */
exit()133 void AlienBAIState::exit()
134 {
135 
136 }
137 
138 /**
139  * Runs any code the state needs to keep updating every AI cycle.
140  * @param action (possible) AI action to execute after thinking is done.
141  */
think(BattleAction * action)142 void AlienBAIState::think(BattleAction *action)
143 {
144  	action->type = BA_RETHINK;
145 	action->actor = _unit;
146 	action->weapon = _unit->getMainHandWeapon();
147 	_attackAction->diff = (int)(_save->getBattleState()->getGame()->getSavedGame()->getDifficulty());
148 	_attackAction->actor = _unit;
149 	_attackAction->weapon = action->weapon;
150 	_attackAction->number = action->number;
151 	_escapeAction->number = action->number;
152 	_knownEnemies = countKnownTargets();
153 	_visibleEnemies = selectNearestTarget();
154 	_spottingEnemies = getSpottingUnits(_unit->getPosition());
155 	_melee = false;
156 	_rifle = false;
157 	_blaster = false;
158 	_reachable = _save->getPathfinding()->findReachable(_unit, _unit->getTimeUnits());
159 	if(_unit->getCharging() && _unit->getCharging()->isOut())
160 	{
161 		_unit->setCharging(0);
162 	}
163 
164 	if (_traceAI)
165 	{
166 		Log(LOG_INFO) << "Unit has " << _visibleEnemies << "/" << _knownEnemies << " known enemies visible, " << _spottingEnemies << " of whom are spotting him. ";
167 		std::string AIMode;
168 		switch (_AIMode)
169 		{
170 		case 0:
171 			AIMode = "Patrol";
172 			break;
173 		case 1:
174 			AIMode = "Ambush";
175 			break;
176 		case 2:
177 			AIMode = "Combat";
178 			break;
179 		case 3:
180 			AIMode = "Escape";
181 			break;
182 		}
183 		Log(LOG_INFO) << "Currently using " << AIMode << " behaviour";
184 	}
185 
186 	if (action->weapon)
187 	{
188 		RuleItem *rule = action->weapon->getRules();
189 		if (rule->getBattleType() == BT_FIREARM)
190 		{
191 			if (!rule->isWaypoint())
192 			{
193 				_rifle = true;
194 				_reachableWithAttack = _save->getPathfinding()->findReachable(_unit, _unit->getTimeUnits() - _unit->getActionTUs(BA_SNAPSHOT, action->weapon));
195 			}
196 			else
197 			{
198 				_blaster = true;
199 				_reachableWithAttack = _save->getPathfinding()->findReachable(_unit, _unit->getTimeUnits() - _unit->getActionTUs(BA_AIMEDSHOT, action->weapon));
200 			}
201 		}
202 		else if (rule->getBattleType() == BT_MELEE)
203 		{
204 			_melee = true;
205 			_reachableWithAttack = _save->getPathfinding()->findReachable(_unit, _unit->getTimeUnits() - _unit->getActionTUs(BA_HIT, action->weapon));
206 		}
207 	}
208 
209 	if (_spottingEnemies && !_escapeTUs)
210 	{
211 		setupEscape();
212 	}
213 
214 	if (_knownEnemies && !_melee && !_ambushTUs)
215 	{
216 		setupAmbush();
217 	}
218 
219 	setupAttack();
220 	setupPatrol();
221 
222 	if (_psiAction->type != BA_NONE && !_didPsi)
223 	{
224 		_didPsi = true;
225 		action->type = _psiAction->type;
226 		action->target = _psiAction->target;
227 		action->number -= 1;
228 		return;
229 	}
230 	else
231 	{
232 		_didPsi = false;
233 	}
234 
235 	bool evaluate = false;
236 
237 	if (_AIMode == AI_ESCAPE)
238 	{
239 		if (!_spottingEnemies || !_knownEnemies)
240 		{
241 			evaluate = true;
242 		}
243 	}
244 	else if (_AIMode == AI_PATROL)
245 	{
246 		if (_spottingEnemies || _visibleEnemies || _knownEnemies || RNG::percent(10))
247 		{
248 			evaluate = true;
249 		}
250 	}
251 	else if (_AIMode == AI_AMBUSH)
252 	{
253 		if (!_rifle || !_ambushTUs || _visibleEnemies)
254 		{
255 			evaluate = true;
256 		}
257 	}
258 
259 	if (_AIMode == AI_COMBAT)
260 	{
261 		if (_attackAction->type == BA_RETHINK)
262 		{
263 			evaluate = true;
264 		}
265 	}
266 	if (_spottingEnemies > 2
267 		|| _unit->getHealth() < 2 * _unit->getStats()->health / 3
268 		|| (_aggroTarget && _aggroTarget->getTurnsSinceSpotted() > _intelligence))
269 	{
270 		evaluate = true;
271 	}
272 
273 
274 	if (_save->isCheating() && _AIMode != AI_COMBAT)
275 	{
276 		evaluate = true;
277 	}
278 
279 	if (evaluate)
280 	{
281 		evaluateAIMode();
282 		if (_traceAI)
283 		{
284 			std::string AIMode;
285 			switch (_AIMode)
286 			{
287 			case 0:
288 				AIMode = "Patrol";
289 				break;
290 			case 1:
291 				AIMode = "Ambush";
292 				break;
293 			case 2:
294 				AIMode = "Combat";
295 				break;
296 			case 3:
297 				AIMode = "Escape";
298 				break;
299 			}
300 			Log(LOG_INFO) << "Re-Evaluated, now using " << AIMode << " behaviour";
301 		}
302 	}
303 
304 	switch (_AIMode)
305 	{
306 	case AI_ESCAPE:
307 		_unit->setCharging(0);
308 		action->type = _escapeAction->type;
309 		action->target = _escapeAction->target;
310 		// end this unit's turn.
311 		action->finalAction = true;
312 		// ignore new targets.
313 		action->desperate = true;
314 		// spin 180 at the end of your route.
315 		_unit->_hidingForTurn = true;
316 		break;
317 	case AI_PATROL:
318 		_unit->setCharging(0);
319 		if (action->weapon && action->weapon->getRules()->getBattleType() == BT_FIREARM)
320 		{
321 			switch (_unit->getAggression())
322 			{
323 			case 0:
324 				_save->getBattleGame()->setTUReserved(BA_AIMEDSHOT, false);
325 				break;
326 			case 1:
327 				_save->getBattleGame()->setTUReserved(BA_AUTOSHOT, false);
328 				break;
329 			case 2:
330 				_save->getBattleGame()->setTUReserved(BA_SNAPSHOT, false);
331 			default:
332 				break;
333 			}
334 		}
335 		action->type = _patrolAction->type;
336 		action->target = _patrolAction->target;
337 		break;
338 	case AI_COMBAT:
339 		action->type = _attackAction->type;
340 		action->target = _attackAction->target;
341 		// this may have changed to a grenade.
342 		action->weapon = _attackAction->weapon;
343 		if (action->weapon && action->type == BA_THROW && action->weapon->getRules()->getBattleType() == BT_GRENADE)
344 		{
345 			_unit->spendTimeUnits(_unit->getActionTUs(BA_PRIME, action->weapon));
346 		}
347 		// if this is a firepoint action, set our facing.
348 		action->finalFacing = _attackAction->finalFacing;
349 		action->TU = _unit->getActionTUs(_attackAction->type, _attackAction->weapon);
350 		// don't worry about reserving TUs, we've factored that in already.
351 		_save->getBattleGame()->setTUReserved(BA_NONE, false);
352 		// if this is a "find fire point" action, don't increment the AI counter.
353 		if (action->type == BA_WALK && _rifle
354 			// so long as we can take a shot afterwards.
355 			&& _unit->getTimeUnits() > _unit->getActionTUs(BA_SNAPSHOT, action->weapon))
356 		{
357 			action->number -= 1;
358 		}
359 		else if (action->type == BA_LAUNCH)
360 		{
361 			action->waypoints = _attackAction->waypoints;
362 		}
363 		break;
364 	case AI_AMBUSH:
365 		_unit->setCharging(0);
366 		action->type = _ambushAction->type;
367 		action->target = _ambushAction->target;
368 		// face where we think our target will appear.
369 		action->finalFacing = _ambushAction->finalFacing;
370 		// end this unit's turn.
371 		action->finalAction = true;
372 		break;
373 	default:
374 		break;
375 	}
376 
377 	if (action->type == BA_WALK)
378 	{
379 		// if we're moving, we'll have to re-evaluate our escape/ambush position.
380 		if (action->target != _unit->getPosition())
381 		{
382 			_escapeTUs = 0;
383 			_ambushTUs = 0;
384 		}
385 		else
386 		{
387 			action->type = BA_NONE;
388 		}
389 	}
390 }
391 
392 /*
393  * sets the "was hit" flag to true.
394  */
setWasHit()395 void AlienBAIState::setWasHit()
396 {
397 	_wasHit = true;
398 }
399 
400 /*
401  * Gets whether the unit was hit.
402  * @return if it was hit.
403  */
getWasHit() const404 bool AlienBAIState::getWasHit() const
405 {
406 	return _wasHit;
407 }
408 /*
409  * Sets up a patrol action.
410  * this is mainly going from node to node, moving about the map.
411  * handles node selection, and fills out the _patrolAction with useful data.
412  */
setupPatrol()413 void AlienBAIState::setupPatrol()
414 {
415 	Node *node;
416 	_patrolAction->TU = 0;
417 	if (_toNode != 0 && _unit->getPosition() == _toNode->getPosition())
418 	{
419 		if (_traceAI)
420 		{
421 			Log(LOG_INFO) << "Patrol destination reached!";
422 		}
423 		// destination reached
424 		// head off to next patrol node
425 		_fromNode = _toNode;
426 		_toNode->freeNode();
427 		_toNode = 0;
428 		// take a peek through window before walking to the next node
429 		int dir = _save->getTileEngine()->faceWindow(_unit->getPosition());
430 		if (dir != -1 && dir != _unit->getDirection())
431 		{
432 			_unit->lookAt(dir);
433 			while (_unit->getStatus() == STATUS_TURNING)
434 			{
435 				_unit->turn();
436 			}
437 		}
438 	}
439 
440 	if (_fromNode == 0)
441 	{
442 		// assume closest node as "from node"
443 		// on same level to avoid strange things, and the node has to match unit size or it will freeze
444 		int closest = 1000000;
445 		for (std::vector<Node*>::iterator i = _save->getNodes()->begin(); i != _save->getNodes()->end(); ++i)
446 		{
447 			node = *i;
448 			int d = _save->getTileEngine()->distanceSq(_unit->getPosition(), node->getPosition());
449 			if (_unit->getPosition().z == node->getPosition().z
450 				&& d < closest
451 				&& (!(node->getType() & Node::TYPE_SMALL) || _unit->getArmor()->getSize() == 1))
452 			{
453 				_fromNode = node;
454 				closest = d;
455 			}
456 		}
457 	}
458 	int triesLeft = 5;
459 
460 	while (_toNode == 0 && triesLeft)
461 	{
462 		triesLeft--;
463 		// look for a new node to walk towards
464 		bool scout = true;
465 		if (_save->getMissionType() != "STR_BASE_DEFENSE")
466 		{
467 			// after turn 20 or if the morale is low, everyone moves out the UFO and scout
468 			// also anyone standing in fire should also probably move
469 			if (_save->isCheating() || !_fromNode || _fromNode->getRank() == 0 ||
470 				(_save->getTile(_unit->getPosition()) && _save->getTile(_unit->getPosition())->getFire()))
471 			{
472 				scout = true;
473 			}
474 			else
475 			{
476 				scout = false;
477 			}
478 		}
479 
480 		// in base defense missions, the smaller aliens walk towards target nodes - or if there, shoot objects around them
481 		else if (_unit->getArmor()->getSize() == 1)
482 		{
483 			// can i shoot an object?
484 			if (_fromNode->isTarget() &&
485 				_unit->getMainHandWeapon() &&
486 				_unit->getMainHandWeapon()->getAmmoItem()->getRules()->getDamageType() != DT_HE &&
487 				_save->getModuleMap()[_fromNode->getPosition().x / 10][_fromNode->getPosition().y / 10].second > 0)
488 			{
489 				// scan this room for objects to destroy
490 				int x = (_unit->getPosition().x/10)*10;
491 				int y = (_unit->getPosition().y/10)*10;
492 				for (int i = x; i < x+9; i++)
493 				for (int j = y; j < y+9; j++)
494 				{
495 					MapData *md = _save->getTile(Position(i, j, 1))->getMapData(MapData::O_OBJECT);
496 					if (md && md->isBaseModule())
497 					{
498 						_patrolAction->actor = _unit;
499 						_patrolAction->target = Position(i, j, 1);
500 						_patrolAction->weapon = _patrolAction->actor->getMainHandWeapon();
501 						_patrolAction->type = BA_SNAPSHOT;
502 						_patrolAction->TU = _patrolAction->actor->getActionTUs(_patrolAction->type, _patrolAction->weapon);
503 						return;
504 					}
505 				}
506 			}
507 			else
508 			{
509 				// find closest high value target which is not already allocated
510 				int closest = 1000000;
511 				for (std::vector<Node*>::iterator i = _save->getNodes()->begin(); i != _save->getNodes()->end(); ++i)
512 				{
513 					if ((*i)->isTarget() && !(*i)->isAllocated())
514 					{
515 						node = *i;
516 						int d = _save->getTileEngine()->distanceSq(_unit->getPosition(), node->getPosition());
517 						if (!_toNode ||  (d < closest && node != _fromNode))
518 						{
519 							_toNode = node;
520 							closest = d;
521 						}
522 					}
523 				}
524 			}
525 		}
526 
527 		if (_toNode == 0)
528 		{
529 			_toNode = _save->getPatrolNode(scout, _unit, _fromNode);
530 			if (_toNode == 0)
531 			{
532 				_toNode = _save->getPatrolNode(!scout, _unit, _fromNode);
533 			}
534 		}
535 
536 		if (_toNode != 0)
537 		{
538 			_save->getPathfinding()->calculate(_unit, _toNode->getPosition());
539 			if (_save->getPathfinding()->getStartDirection() == -1)
540 			{
541 				_toNode = 0;
542 			}
543 			_save->getPathfinding()->abortPath();
544 		}
545 	}
546 
547 	if (_toNode != 0)
548 	{
549 		_toNode->allocateNode();
550 		_patrolAction->actor = _unit;
551 		_patrolAction->type = BA_WALK;
552 		_patrolAction->target = _toNode->getPosition();
553 	}
554 	else
555 	{
556 		_patrolAction->type = BA_RETHINK;
557 	}
558 }
559 
560 /**
561  * Try to set up an ambush action
562  * The idea is to check within a 11x11 tile square for a tile which is not seen by our aggroTarget,
563  * but that can be reached by him. we then intuit where we will see the target first from our covered
564  * position, and set that as our final facing.
565  * Fills out the _ambushAction with useful data.
566  */
setupAmbush()567 void AlienBAIState::setupAmbush()
568 {
569 	_ambushAction->type = BA_RETHINK;
570 	int bestScore = 0;
571 	_ambushTUs = 0;
572 	std::vector<int> path;
573 
574 	if (selectClosestKnownEnemy())
575 	{
576 		Position target;
577 		const int BASE_SYSTEMATIC_SUCCESS = 100;
578 		const int COVER_BONUS = 25;
579 		const int FAST_PASS_THRESHOLD = 80;
580 		Position origin = _save->getTileEngine()->getSightOriginVoxel(_aggroTarget);
581 
582 		// we'll use node positions for this, as it gives map makers a good degree of control over how the units will use the environment.
583 		for (std::vector<Node*>::const_iterator i = _save->getNodes()->begin(); i != _save->getNodes()->end(); ++i)
584 		{
585 			Position pos = (*i)->getPosition();
586 			Tile *tile = _save->getTile(pos);
587 			if (tile == 0 || _save->getTileEngine()->distance(pos, _unit->getPosition()) > 10 || pos.z != _unit->getPosition().z || tile->getDangerous() ||
588 				std::find(_reachableWithAttack.begin(), _reachableWithAttack.end(), _save->getTileIndex(pos))  == _reachableWithAttack.end())
589 				continue; // just ignore unreachable tiles
590 
591 			if (_traceAI)
592 			{
593 				// colour all the nodes in range purple.
594 				tile->setPreview(10);
595 				tile->setMarkerColor(13);
596 			}
597 
598 			// make sure we can't be seen here.
599 			if (!_save->getTileEngine()->canTargetUnit(&origin, tile, &target, _aggroTarget, _unit) && !getSpottingUnits(pos))
600 			{
601 				_save->getPathfinding()->calculate(_unit, pos);
602 				int ambushTUs = _save->getPathfinding()->getTotalTUCost();
603 				// make sure we can move here
604 				if (_save->getPathfinding()->getStartDirection() != -1)
605 				{
606 					int score = BASE_SYSTEMATIC_SUCCESS;
607 					score -= ambushTUs;
608 
609 					// make sure our enemy can reach here too.
610 					_save->getPathfinding()->calculate(_aggroTarget, pos);
611 
612 					if (_save->getPathfinding()->getStartDirection() != -1)
613 					{
614 						// ideally we'd like to be behind some cover, like say a window or a low wall.
615 						if (_save->getTileEngine()->faceWindow(pos) != -1)
616 						{
617 							score += COVER_BONUS;
618 						}
619 						if (score > bestScore)
620 						{
621 							path = _save->getPathfinding()->copyPath();
622 							bestScore = score;
623 							_ambushTUs = (pos == _unit->getPosition()) ? 1 : ambushTUs;
624 							_ambushAction->target = pos;
625 							if (bestScore > FAST_PASS_THRESHOLD)
626 							{
627 								break;
628 							}
629 						}
630 					}
631 				}
632 			}
633 		}
634 
635 		if (bestScore > 0)
636 		{
637 			_ambushAction->type = BA_WALK;
638 			// i should really make a function for this
639 			origin = (_ambushAction->target * Position(16,16,24)) +
640 				// 4 because -2 is eyes and 2 below that is the rifle (or at least that's my understanding)
641 				Position(8,8, _unit->getHeight() + _unit->getFloatHeight() - _save->getTile(_ambushAction->target)->getTerrainLevel() - 4);
642 			Position currentPos = _aggroTarget->getPosition();
643 			_save->getPathfinding()->setUnit(_aggroTarget);
644 			Position nextPos;
645 			size_t tries = path.size();
646 			// hypothetically walk the target through the path.
647 			while (tries > 0)
648 			{
649 				_save->getPathfinding()->getTUCost(currentPos, path.back(), &nextPos, _aggroTarget, 0, false);
650 				path.pop_back();
651 				currentPos = nextPos;
652 				Tile *tile = _save->getTile(currentPos);
653 				Position target;
654 				// do a virtual fire calculation
655 				if (_save->getTileEngine()->canTargetUnit(&origin, tile, &target, _unit, _aggroTarget))
656 				{
657 					// if we can virtually fire at the hypothetical target, we know which way to face.
658 					_ambushAction->finalFacing = _save->getTileEngine()->getDirectionTo(_ambushAction->target, currentPos);
659 					break;
660 				}
661 				--tries;
662 			}
663 			if (_traceAI)
664 			{
665 				Log(LOG_INFO) << "Ambush estimation will move to " << _ambushAction->target;
666 			}
667 			return;
668 		}
669 	}
670 	if (_traceAI)
671 	{
672 		Log(LOG_INFO) << "Ambush estimation failed";
673 	}
674 }
675 
676 /**
677  * Try to set up a combat action
678  * This will either be a psionic, grenade, or weapon attack,
679  * or potentially just moving to get a line of sight to a target.
680  * Fills out the _attackAction with useful data.
681  */
setupAttack()682 void AlienBAIState::setupAttack()
683 {
684 	_attackAction->type = BA_RETHINK;
685 	_psiAction->type = BA_NONE;
686 
687 	// if enemies are known to us but not necessarily visible, we can attack them with a blaster launcher or psi.
688 	if (_knownEnemies)
689 	{
690 		if (_unit->getStats()->psiSkill && psiAction())
691 		{
692 			// at this point we can save some time with other calculations - the unit WILL make a psionic attack this turn.
693 			return;
694 		}
695 		if (_blaster)
696 		{
697 			wayPointAction();
698 		}
699 	}
700 
701 	// if we CAN see someone, that makes them a viable target for "regular" attacks.
702 	if (selectNearestTarget())
703 	{
704 		if (_unit->getGrenadeFromBelt())
705 		{
706 			grenadeAction();
707 		}
708 		if (_melee)
709 		{
710 			meleeAction();
711 		}
712 		if (_rifle)
713 		{
714 			projectileAction();
715 		}
716 	}
717 
718 	if (_attackAction->type != BA_RETHINK)
719 	{
720 		if (_traceAI)
721 		{
722 			if (_attackAction->type != BA_WALK)
723 			{
724 				Log(LOG_INFO) << "Attack estimation desires to shoot at " << _attackAction->target;
725 			}
726 			else
727 			{
728 				Log(LOG_INFO) << "Attack estimation desires to move to " << _attackAction->target;
729 			}
730 		}
731 		return;
732 	}
733 	else if (_spottingEnemies || _unit->getAggression() < RNG::generate(0, 3))
734 	{
735 		// if enemies can see us, or if we're feeling lucky, we can try to spot the enemy.
736 		if (findFirePoint())
737 		{
738 			if (_traceAI)
739 			{
740 				Log(LOG_INFO) << "Attack estimation desires to move to " << _attackAction->target;
741 			}
742 			return;
743 		}
744 	}
745 	if (_traceAI)
746 	{
747 		Log(LOG_INFO) << "Attack estimation failed";
748 	}
749 }
750 
751 /**
752  * Attempts to find cover, and move toward it.
753  * The idea is to check within a 11x11 tile square for a tile which is not seen by our aggroTarget.
754  * If there is no such tile, we run away from the target.
755  * Fills out the _escapeAction with useful data.
756  */
setupEscape()757 void AlienBAIState::setupEscape()
758 {
759 	int unitsSpottingMe = getSpottingUnits(_unit->getPosition());
760 	int currentTilePreference = 15;
761 	int tries = -1;
762 	bool coverFound = false;
763 	selectNearestTarget();
764 	_escapeTUs = 0;
765 
766 	int dist = _aggroTarget ? _save->getTileEngine()->distance(_unit->getPosition(), _aggroTarget->getPosition()) : 0;
767 
768 	int bestTileScore = -100000;
769 	int score = -100000;
770 	Position bestTile(0, 0, 0);
771 
772 	Tile *tile = 0;
773 
774 	// weights of various factors in choosing a tile to which to withdraw
775 	const int EXPOSURE_PENALTY = 10;
776 	const int FIRE_PENALTY = 40;
777 	const int BASE_SYSTEMATIC_SUCCESS = 100;
778 	const int BASE_DESPERATE_SUCCESS = 110;
779 	const int FAST_PASS_THRESHOLD = 100; // a score that's good engouh to quit the while loop early; it's subjective, hand-tuned and may need tweaking
780 
781 	std::vector<Position> randomTileSearch = _save->getTileSearch();
782 	RNG::shuffle(randomTileSearch);
783 
784 	while (tries < 150 && !coverFound)
785 	{
786 		_escapeAction->target = _unit->getPosition(); // start looking in a direction away from the enemy
787 
788 		if (!_save->getTile(_escapeAction->target))
789 		{
790 			_escapeAction->target = _unit->getPosition(); // cornered at the edge of the map perhaps?
791 		}
792 
793 		score = 0;
794 
795 		if (tries == -1)
796 		{
797 			// you know, maybe we should just stay where we are and not risk reaction fire...
798 			// or maybe continue to wherever we were running to and not risk looking stupid
799 			if (_save->getTile(_unit->lastCover) != 0)
800 			{
801 				_escapeAction->target = _unit->lastCover;
802 			}
803 		}
804 		else if (tries < 121)
805 		{
806 			// looking for cover
807 			_escapeAction->target.x += randomTileSearch[tries].x;
808 			_escapeAction->target.y += randomTileSearch[tries].y;
809 			score = BASE_SYSTEMATIC_SUCCESS;
810 			if (_escapeAction->target == _unit->getPosition())
811 			{
812 				if (unitsSpottingMe > 0)
813 				{
814 					// maybe don't stay in the same spot? move or something if there's any point to it?
815 					_escapeAction->target.x += RNG::generate(-20,20);
816 					_escapeAction->target.y += RNG::generate(-20,20);
817 				}
818 				else
819 				{
820 					score += currentTilePreference;
821 				}
822 			}
823 		}
824 		else
825 		{
826 
827 			if (tries == 121)
828 			{
829 				if (_traceAI)
830 				{
831 					Log(LOG_INFO) << "best score after systematic search was: " << bestTileScore;
832 				}
833 			}
834 
835 			score = BASE_DESPERATE_SUCCESS; // ruuuuuuun
836 			_escapeAction->target = _unit->getPosition();
837 			_escapeAction->target.x += RNG::generate(-10,10);
838 			_escapeAction->target.y += RNG::generate(-10,10);
839 			_escapeAction->target.z = _unit->getPosition().z + RNG::generate(-1,1);
840 			if (_escapeAction->target.z < 0)
841 			{
842 				_escapeAction->target.z = 0;
843 			}
844 			else if (_escapeAction->target.z >= _save->getMapSizeZ())
845 			{
846 				_escapeAction->target.z = _unit->getPosition().z;
847 			}
848 		}
849 
850 		tries++;
851 
852 		// THINK, DAMN YOU
853 		tile = _save->getTile(_escapeAction->target);
854 		int distanceFromTarget = _aggroTarget ? _save->getTileEngine()->distance(_aggroTarget->getPosition(), _escapeAction->target) : 0;
855 		if (dist >= distanceFromTarget)
856 		{
857 			score -= (distanceFromTarget - dist) * 10;
858 		}
859 		else
860 		{
861 			score += (distanceFromTarget - dist) * 10;
862 		}
863 		int spotters = 0;
864 		if (!tile)
865 		{
866 			score = -100001; // no you can't quit the battlefield by running off the map.
867 		}
868 		else
869 		{
870 			spotters = getSpottingUnits(_escapeAction->target);
871 			if (std::find(_reachable.begin(), _reachable.end(), _save->getTileIndex(_escapeAction->target))  == _reachable.end())
872 				continue; // just ignore unreachable tiles
873 
874 			if (_spottingEnemies || spotters)
875 			{
876 				if (_spottingEnemies <= spotters)
877 				{
878 					score -= (1 + spotters - _spottingEnemies) * EXPOSURE_PENALTY; // that's for giving away our position
879 				}
880 				else
881 				{
882 					score += (_spottingEnemies - spotters) * EXPOSURE_PENALTY;
883 				}
884 			}
885 			if (tile->getFire())
886 			{
887 				score -= FIRE_PENALTY;
888 			}
889 			if (tile->getDangerous())
890 			{
891 				score -= BASE_SYSTEMATIC_SUCCESS;
892 			}
893 
894 			if (_traceAI)
895 			{
896 				tile->setMarkerColor(score < 0 ? 3 : (score < FAST_PASS_THRESHOLD/2 ? 8 : (score < FAST_PASS_THRESHOLD ? 9 : 5)));
897 				tile->setPreview(10);
898 				tile->setTUMarker(score);
899 			}
900 
901 		}
902 
903 		if (tile && score > bestTileScore)
904 		{
905 			// calculate TUs to tile; we could be getting this from findReachable() somehow but that would break something for sure...
906 			_save->getPathfinding()->calculate(_unit, _escapeAction->target);
907 			if (_escapeAction->target == _unit->getPosition() || _save->getPathfinding()->getStartDirection() != -1)
908 			{
909 				bestTileScore = score;
910 				bestTile = _escapeAction->target;
911 				_escapeTUs = _save->getPathfinding()->getTotalTUCost();
912 				if (_escapeAction->target == _unit->getPosition())
913 				{
914 					_escapeTUs = 1;
915 				}
916 				if (_traceAI)
917 				{
918 					tile->setMarkerColor(score < 0 ? 7 : (score < FAST_PASS_THRESHOLD/2 ? 10 : (score < FAST_PASS_THRESHOLD ? 4 : 5)));
919 					tile->setPreview(10);
920 					tile->setTUMarker(score);
921 				}
922 			}
923 			_save->getPathfinding()->abortPath();
924 			if (bestTileScore > FAST_PASS_THRESHOLD) coverFound = true; // good enough, gogogo
925 		}
926 	}
927 	_escapeAction->target = bestTile;
928 	if (_traceAI)
929 	{
930 		_save->getTile(_escapeAction->target)->setMarkerColor(13);
931 	}
932 
933 	if (bestTileScore <= -100000)
934 	{
935 		if (_traceAI)
936 		{
937 			Log(LOG_INFO) << "Escape estimation failed.";
938 		}
939 		_escapeAction->type = BA_RETHINK; // do something, just don't look dumbstruck :P
940 		return;
941 	}
942 	else
943 	{
944 		if (_traceAI)
945 		{
946 			Log(LOG_INFO) << "Escape estimation completed after " << tries << " tries, " << _save->getTileEngine()->distance(_unit->getPosition(), bestTile) << " squares or so away.";
947 		}
948 		_escapeAction->type = BA_WALK;
949 	}
950 }
951 
952 /**
953  * Counts how many targets, both xcom and civilian are known to this unit
954  * @return how many targets are known to us.
955  */
countKnownTargets() const956 int AlienBAIState::countKnownTargets() const
957 {
958 	int knownEnemies = 0;
959 	for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
960 	{
961 		if (validTarget(*i, true, true))
962 		{
963 			++knownEnemies;
964 		}
965 	}
966 	return knownEnemies;
967 }
968 
969 /*
970  * counts how many enemies (xcom only) are spotting any given position.
971  * @param pos the Position to check for spotters.
972  * @return spotters.
973  */
getSpottingUnits(Position pos) const974 int AlienBAIState::getSpottingUnits(Position pos) const
975 {
976 	// if we don't actually occupy the position being checked, we need to do a virtual LOF check.
977 	bool checking = pos != _unit->getPosition();
978 	int tally = 0;
979 	for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
980 	{
981 		if (validTarget(*i, false, false))
982 		{
983 			int dist = _save->getTileEngine()->distance(pos, (*i)->getPosition());
984 			if (dist > 20) continue;
985 			Position originVoxel = _save->getTileEngine()->getSightOriginVoxel(*i);
986 			originVoxel.z -= 2;
987 			Position targetVoxel;
988 			if (checking)
989 			{
990 				if (_save->getTileEngine()->canTargetUnit(&originVoxel, _save->getTile(pos), &targetVoxel, *i, _unit))
991 				{
992 					tally++;
993 				}
994 			}
995 			else
996 			{
997 				if (_save->getTileEngine()->canTargetUnit(&originVoxel, _save->getTile(pos), &targetVoxel, *i))
998 				{
999 					tally++;
1000 				}
1001 			}
1002 		}
1003 	}
1004 	return tally;
1005 }
1006 
1007 /**
1008  * Selects the nearest known living target we can see/reach and returns the number of visible enemies.
1009  * This function includes civilians as viable targets.
1010  * @return viable targets.
1011  */
selectNearestTarget()1012 int AlienBAIState::selectNearestTarget()
1013 {
1014 	int tally = 0;
1015 	_closestDist= 100;
1016 	_aggroTarget = 0;
1017 	Position origin = _save->getTileEngine()->getSightOriginVoxel(_unit);
1018 	origin.z -= 2;
1019 	Position target;
1020 	for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
1021 	{
1022 		if (validTarget(*i, true, true) &&
1023 			_save->getTileEngine()->visible(_unit, (*i)->getTile()))
1024 		{
1025 			tally++;
1026 			int dist = _save->getTileEngine()->distance(_unit->getPosition(), (*i)->getPosition());
1027 			if (dist < _closestDist)
1028 			{
1029 				bool valid = false;
1030 				if (_rifle || !_melee)
1031 				{
1032 					valid = _save->getTileEngine()->canTargetUnit(&origin, (*i)->getTile(), &target, _unit);
1033 				}
1034 				else
1035 				{
1036 					if (selectPointNearTarget(*i, _unit->getTimeUnits()))
1037 					{
1038 						int dir = _save->getTileEngine()->getDirectionTo(_attackAction->target, (*i)->getPosition());
1039 						valid = _save->getTileEngine()->validMeleeRange(_attackAction->target, dir, _unit, *i, 0);
1040 					}
1041 				}
1042 				if (valid)
1043 				{
1044 					_closestDist = dist;
1045 					_aggroTarget = *i;
1046 				}
1047 			}
1048 		}
1049 	}
1050 	if (_aggroTarget)
1051 	{
1052 		return tally;
1053 	}
1054 
1055 	return 0;
1056 }
1057 
1058 /**
1059  * Selects the nearest known living Xcom unit.
1060  * used for ambush calculations
1061  * @return if we found one.
1062  */
selectClosestKnownEnemy()1063 bool AlienBAIState::selectClosestKnownEnemy()
1064 {
1065 	_aggroTarget = 0;
1066 	int minDist = 255;
1067 	for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
1068 	{
1069 		if (validTarget(*i, true, false))
1070 		{
1071 			int dist = _save->getTileEngine()->distance((*i)->getPosition(), _unit->getPosition());
1072 			if (dist < minDist)
1073 			{
1074 				minDist = dist;
1075 				_aggroTarget = *i;
1076 			}
1077 		}
1078 	}
1079 	return _aggroTarget != 0;
1080 }
1081 
1082 /**
1083  * Selects a random known living Xcom or civilian unit.
1084  * @return if we found one.
1085  */
selectRandomTarget()1086 bool AlienBAIState::selectRandomTarget()
1087 {
1088 	int farthest = -100;
1089 	_aggroTarget = 0;
1090 
1091 	for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
1092 	{
1093 		if (validTarget(*i, true, true))
1094 		{
1095 			int dist = RNG::generate(0,20) - _save->getTileEngine()->distance(_unit->getPosition(), (*i)->getPosition());
1096 			if (dist > farthest)
1097 			{
1098 				farthest = dist;
1099 				_aggroTarget = *i;
1100 			}
1101 		}
1102 	}
1103 	return _aggroTarget != 0;
1104 }
1105 
1106 /**
1107  * Selects a point near enough to our target to perform a melee attack.
1108  * @param target Pointer to a target.
1109  * @param maxTUs Maximum time units the path to the target can cost.
1110  * @return True if a point was found.
1111  */
selectPointNearTarget(BattleUnit * target,int maxTUs) const1112 bool AlienBAIState::selectPointNearTarget(BattleUnit *target, int maxTUs) const
1113 {
1114 	int size = _unit->getArmor()->getSize();
1115 	int targetsize = target->getArmor()->getSize();
1116 	bool returnValue = false;
1117 	int distance = 1000;
1118 	for (int z = -1; z <= 1; ++z)
1119 	{
1120 		for (int x = -size; x <= targetsize; ++x)
1121 		{
1122 			for (int y = -size; y <= targetsize; ++y)
1123 			{
1124 				if (x || y) // skip the unit itself
1125 				{
1126 					Position checkPath = target->getPosition() + Position (x, y, z);
1127 					if (_save->getTile(checkPath) == 0 || std::find(_reachable.begin(), _reachable.end(), _save->getTileIndex(checkPath))  == _reachable.end())
1128 						continue;
1129 					int dir = _save->getTileEngine()->getDirectionTo(checkPath, target->getPosition());
1130 					bool valid = _save->getTileEngine()->validMeleeRange(checkPath, dir, _unit, target, 0);
1131 					bool fitHere = _save->setUnitPosition(_unit, checkPath, true);
1132 
1133 					if (valid && fitHere && !_save->getTile(checkPath)->getDangerous())
1134 					{
1135 						_save->getPathfinding()->calculate(_unit, checkPath, 0, maxTUs);
1136 						if (_save->getPathfinding()->getStartDirection() != -1 && _save->getPathfinding()->getPath().size() < distance)
1137 						{
1138 							_attackAction->target = checkPath;
1139 							returnValue = true;
1140 							distance = _save->getPathfinding()->getPath().size();
1141 						}
1142 						_save->getPathfinding()->abortPath();
1143 					}
1144 				}
1145 			}
1146 		}
1147 	}
1148 	return returnValue;
1149 }
1150 
1151 /**
1152  * Selects an AI mode based on a number of factors, some RNG and the results of the rest of the determinations.
1153  */
evaluateAIMode()1154 void AlienBAIState::evaluateAIMode()
1155 {
1156 	if (_unit->getCharging() && _attackAction->type != BA_RETHINK)
1157 	{
1158 		_AIMode = AI_COMBAT;
1159 		return;
1160 	}
1161 	// don't try to run away as often if we're a melee type, and really don't try to run away if we have a viable melee target, or we still have 50% or more TUs remaining.
1162 	int escapeOdds = 15;
1163 	if (_melee)
1164 	{
1165 		escapeOdds = 12;
1166 	}
1167 	if (_unit->getTimeUnits() > _unit->getStats()->tu / 2 || _unit->getCharging())
1168 	{
1169 		escapeOdds = 5;
1170 	}
1171 	int ambushOdds = 12;
1172 	int combatOdds = 20;
1173 	// we're less likely to patrol if we see enemies.
1174 	int patrolOdds = _visibleEnemies ? 15 : 30;
1175 
1176 	// the enemy sees us, we should take retreat into consideration, and forget about patrolling for now.
1177 	if (_spottingEnemies)
1178 	{
1179 		patrolOdds = 0;
1180 		if (_escapeTUs == 0)
1181 		{
1182 			setupEscape();
1183 		}
1184 	}
1185 
1186 	// melee/blaster units shouldn't consider ambush
1187 	if (!_rifle || _ambushTUs == 0)
1188 	{
1189 		ambushOdds = 0;
1190 		if (_melee)
1191 		{
1192 			combatOdds *= 1.3;
1193 		}
1194 	}
1195 
1196 	// if we KNOW there are enemies around...
1197 	if (_knownEnemies)
1198 	{
1199 		if (_knownEnemies == 1)
1200 		{
1201 			combatOdds *= 1.2;
1202 		}
1203 
1204 		if (_escapeTUs == 0)
1205 		{
1206 			if (selectClosestKnownEnemy())
1207 			{
1208 				setupEscape();
1209 			}
1210 			else
1211 			{
1212 				escapeOdds = 0;
1213 			}
1214 		}
1215 	}
1216 	else
1217 	{
1218 		combatOdds = 0;
1219 		escapeOdds = 0;
1220 	}
1221 
1222 	// take our current mode into consideration
1223 	switch (_AIMode)
1224 	{
1225 	case AI_PATROL:
1226 		patrolOdds *= 1.1;
1227 		break;
1228 	case AI_AMBUSH:
1229 		ambushOdds *= 1.1;
1230 		break;
1231 	case AI_COMBAT:
1232 		combatOdds *= 1.1;
1233 		break;
1234 	case AI_ESCAPE:
1235 		escapeOdds *= 1.1;
1236 		break;
1237 	}
1238 
1239 	// take our overall health into consideration
1240 	if (_unit->getHealth() < _unit->getStats()->health / 3)
1241 	{
1242 		escapeOdds *= 1.7;
1243 		combatOdds *= 0.6;
1244 		ambushOdds *= 0.75;
1245 	}
1246 	else if (_unit->getHealth() < 2 * (_unit->getStats()->health / 3))
1247 	{
1248 		escapeOdds *= 1.4;
1249 		combatOdds *= 0.8;
1250 		ambushOdds *= 0.8;
1251 	}
1252 	else if (_unit->getHealth() < _unit->getStats()->health)
1253 	{
1254 		escapeOdds *= 1.1;
1255 	}
1256 
1257 	// take our aggression into consideration
1258 	switch (_unit->getAggression())
1259 	{
1260 	case 0:
1261 		escapeOdds *= 1.4;
1262 		combatOdds *= 0.7;
1263 		break;
1264 	case 1:
1265 		ambushOdds *= 1.1;
1266 		break;
1267 	case 2:
1268 		combatOdds *= 1.4;
1269 		escapeOdds *= 0.7;
1270 		break;
1271 	default:
1272 		combatOdds *= std::max(0.1, std::min(2.0, 1.2 + (_unit->getAggression() / 10)));
1273 		escapeOdds *= std::min(2.0, std::max(0.1, 0.9 - (_unit->getAggression() / 10)));
1274 		break;
1275 	}
1276 
1277 	if (_AIMode == AI_COMBAT)
1278 	{
1279 		ambushOdds *= 1.5;
1280 	}
1281 
1282 	// factor in the spotters.
1283 	if (_spottingEnemies)
1284 	{
1285 		escapeOdds = 10 * escapeOdds * (_spottingEnemies + 10) / 100;
1286 		combatOdds = 5 * combatOdds * (_spottingEnemies + 20) / 100;
1287 	}
1288 	else
1289 	{
1290 		escapeOdds /= 2;
1291 	}
1292 
1293 	// factor in visible enemies.
1294 	if (_visibleEnemies)
1295 	{
1296 		combatOdds = 10 * combatOdds * (_visibleEnemies + 10) /100;
1297 		if (_closestDist < 5)
1298 		{
1299 			ambushOdds = 0;
1300 		}
1301 	}
1302 	// make sure we have an ambush lined up, or don't even consider it.
1303 	if (_ambushTUs)
1304 	{
1305 		ambushOdds *= 1.7;
1306 	}
1307 	else
1308 	{
1309 		ambushOdds = 0;
1310 	}
1311 
1312 	// factor in mission type
1313 	if (_save->getMissionType() == "STR_BASE_DEFENSE")
1314 	{
1315 		escapeOdds *= 0.75;
1316 		ambushOdds *= 0.6;
1317 	}
1318 
1319 	// generate a random number to represent our decision.
1320 	int decision = RNG::generate(1, patrolOdds + ambushOdds + escapeOdds + combatOdds);
1321 
1322 	if (decision > escapeOdds)
1323 	{
1324 		if (decision > escapeOdds + ambushOdds)
1325 		{
1326 			if (decision > escapeOdds + ambushOdds + combatOdds)
1327 			{
1328 				_AIMode = AI_PATROL;
1329 			}
1330 			else
1331 			{
1332 				_AIMode = AI_COMBAT;
1333 			}
1334 		}
1335 		else
1336 		{
1337 			_AIMode = AI_AMBUSH;
1338 		}
1339 	}
1340 	else
1341 	{
1342 		_AIMode = AI_ESCAPE;
1343 	}
1344 
1345 	// if the aliens are cheating, or the unit is charging, enforce combat as a priority.
1346 	if (_save->isCheating() || _unit->getCharging() != 0)
1347 	{
1348 		_AIMode = AI_COMBAT;
1349 	}
1350 
1351 
1352 	// enforce the validity of our decision, and try fallback behaviour according to priority.
1353 	if (_AIMode == AI_COMBAT)
1354 	{
1355 		if (_save->getTile(_attackAction->target) && _save->getTile(_attackAction->target)->getUnit())
1356 		{
1357 			if (_attackAction->type != BA_RETHINK)
1358 			{
1359 				return;
1360 			}
1361 			if (findFirePoint())
1362 			{
1363 				return;
1364 			}
1365 		}
1366 		else if (selectRandomTarget() && findFirePoint())
1367 		{
1368 			return;
1369 		}
1370 		_AIMode = AI_PATROL;
1371 	}
1372 
1373 	if (_AIMode == AI_PATROL)
1374 	{
1375 		if (_toNode)
1376 		{
1377 			return;
1378 		}
1379 		_AIMode = AI_AMBUSH;
1380 	}
1381 
1382 	if (_AIMode == AI_AMBUSH)
1383 	{
1384 		if (_ambushTUs != 0)
1385 		{
1386 			return;
1387 		}
1388 		_AIMode = AI_ESCAPE;
1389 	}
1390 }
1391 
1392 /**
1393  * Find a position where we can see our target, and move there.
1394  * check the 11x11 grid for a position nearby where we can potentially target him.
1395  * @return True if a possible position was found.
1396  */
findFirePoint()1397 bool AlienBAIState::findFirePoint()
1398 {
1399 	if (!selectClosestKnownEnemy())
1400 		return false;
1401 	std::vector<Position> randomTileSearch = _save->getTileSearch();
1402 	RNG::shuffle(randomTileSearch);
1403 	Position target;
1404 	const int BASE_SYSTEMATIC_SUCCESS = 100;
1405 	const int FAST_PASS_THRESHOLD = 125;
1406 	int bestScore = 0;
1407 	_attackAction->type = BA_RETHINK;
1408 	for (std::vector<Position>::const_iterator i = randomTileSearch.begin(); i != randomTileSearch.end(); ++i)
1409 	{
1410 		Position pos = _unit->getPosition() + *i;
1411 		Tile *tile = _save->getTile(pos);
1412 		if (tile == 0  ||
1413 			std::find(_reachableWithAttack.begin(), _reachableWithAttack.end(), _save->getTileIndex(pos))  == _reachableWithAttack.end())
1414 			continue;
1415 		int score = 0;
1416 		// i should really make a function for this
1417 		Position origin = (pos * Position(16,16,24)) +
1418 			// 4 because -2 is eyes and 2 below that is the rifle (or at least that's my understanding)
1419 			Position(8,8, _unit->getHeight() + _unit->getFloatHeight() - tile->getTerrainLevel() - 4);
1420 
1421 		if (_save->getTileEngine()->canTargetUnit(&origin, _aggroTarget->getTile(), &target, _unit))
1422 		{
1423 			_save->getPathfinding()->calculate(_unit, pos);
1424 			// can move here
1425 			if (_save->getPathfinding()->getStartDirection() != -1)
1426 			{
1427 				score = BASE_SYSTEMATIC_SUCCESS - getSpottingUnits(pos) * 10;
1428 				score += _unit->getTimeUnits() - _save->getPathfinding()->getTotalTUCost();
1429 				if (!_aggroTarget->checkViewSector(pos))
1430 				{
1431 					score += 10;
1432 				}
1433 				if (score > bestScore)
1434 				{
1435 					bestScore = score;
1436 					_attackAction->target = pos;
1437 					_attackAction->finalFacing = _save->getTileEngine()->getDirectionTo(pos, _aggroTarget->getPosition());
1438 					if (score > FAST_PASS_THRESHOLD)
1439 					{
1440 						break;
1441 					}
1442 				}
1443 			}
1444 		}
1445 	}
1446 
1447 	if (bestScore > 70)
1448 	{
1449 		_attackAction->type = BA_WALK;
1450 		if (_traceAI)
1451 		{
1452 			Log(LOG_INFO) << "Firepoint found at " << _attackAction->target << ", with a score of: " << bestScore;
1453 		}
1454 		return true;
1455 	}
1456 	if (_traceAI)
1457 	{
1458 		Log(LOG_INFO) << "Firepoint failed, best estimation was: " << _attackAction->target << ", with a score of: " << bestScore;
1459 	}
1460 
1461 	return false;
1462 }
1463 
1464 /**
1465  * Decides if it worth our while to create an explosion here.
1466  * @param targetPos The target's position.
1467  * @param attackingUnit The attacking unit.
1468  * @param radius How big the explosion will be.
1469  * @param diff Game difficulty.
1470  * @param grenade Is the explosion coming from a grenade?
1471  * @return True if it is worthwhile creating an explosion in the target position.
1472  */
explosiveEfficacy(Position targetPos,BattleUnit * attackingUnit,int radius,int diff,bool grenade) const1473 bool AlienBAIState::explosiveEfficacy(Position targetPos, BattleUnit *attackingUnit, int radius, int diff, bool grenade) const
1474 {
1475 	// i hate the player and i want him dead, but i don't want to piss him off.
1476 	if (_save->getTurn() < 3)
1477 		return false;
1478 	if (diff == -1)
1479 	{
1480 		diff = (int)(_save->getBattleState()->getGame()->getSavedGame()->getDifficulty());
1481 	}
1482 	int distance = _save->getTileEngine()->distance(attackingUnit->getPosition(), targetPos);
1483 	int injurylevel = attackingUnit->getStats()->health - attackingUnit->getHealth();
1484 	int desperation = (100 - attackingUnit->getMorale()) / 10;
1485 	int enemiesAffected = 0;
1486 	// if we're below 1/3 health, let's assume things are dire, and increase desperation.
1487 	if (injurylevel > (attackingUnit->getStats()->health / 3) * 2)
1488 		desperation += 3;
1489 
1490 	int efficacy = desperation;
1491 
1492 	// don't go kamikaze unless we're already doomed.
1493 	if (abs(attackingUnit->getPosition().z - targetPos.z) <= Options::battleExplosionHeight && distance <= radius)
1494 	{
1495 		efficacy -= 4;
1496 	}
1497 
1498 	// we don't want to ruin our own base, but we do want to ruin XCom's day.
1499 	if (_save->getMissionType() == "STR_ALIEN_BASE_ASSAULT") efficacy -= 3;
1500 	else if (_save->getMissionType() == "STR_BASE_DEFENSE" || _save->getMissionType() == "STR_TERROR_MISSION") efficacy += 3;
1501 
1502 	// allow difficulty to have its influence
1503 	efficacy += diff/2;
1504 
1505 	// account for the unit we're targetting
1506 	BattleUnit *target = _save->getTile(targetPos)->getUnit();
1507 	if (target)
1508 	{
1509 		++enemiesAffected;
1510 		++efficacy;
1511 	}
1512 
1513 	for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
1514 	{
1515 			// don't grenade dead guys
1516 		if (!(*i)->isOut() &&
1517 			// don't count ourself twice
1518 			(*i) != attackingUnit &&
1519 			// don't count the target twice
1520 			(*i) != target &&
1521 			// don't count units that probably won't be affected cause they're out of range
1522 			abs((*i)->getPosition().z - targetPos.z) <= Options::battleExplosionHeight &&
1523 			_save->getTileEngine()->distance((*i)->getPosition(), targetPos) <= radius)
1524 		{
1525 				// don't count people who were already grenaded this turn
1526 			if ((*i)->getTile()->getDangerous() ||
1527 				// don't count units we don't know about
1528 				((*i)->getFaction() == FACTION_PLAYER && (*i)->getTurnsSinceSpotted() > _intelligence))
1529 				continue;
1530 
1531 			// trace a line from the grenade origin to the unit we're checking against
1532 			Position voxelPosA = Position ((targetPos.x * 16)+8, (targetPos.y * 16)+8, (targetPos.z * 24)+12);
1533 			Position voxelPosB = Position (((*i)->getPosition().x * 16)+8, ((*i)->getPosition().y * 16)+8, ((*i)->getPosition().z * 24)+12);
1534 			std::vector<Position> traj;
1535 			int collidesWith = _save->getTileEngine()->calculateLine(voxelPosA, voxelPosB, false, &traj, target, true, false, *i);
1536 
1537 			if (collidesWith == V_UNIT && traj.front() / Position(16,16,24) == (*i)->getPosition())
1538 			{
1539 				if ((*i)->getFaction() == FACTION_PLAYER)
1540 				{
1541 					++enemiesAffected;
1542 					++efficacy;
1543 				}
1544 				else if ((*i)->getFaction() == attackingUnit->getFaction())
1545 					efficacy -= 2; // friendlies count double
1546 			}
1547 		}
1548 	}
1549 	// don't throw grenades at single targets, unless morale is in the danger zone
1550 	// or we're halfway towards panicking while bleeding to death.
1551 	if (grenade && desperation < 6 && enemiesAffected < 2)
1552 	{
1553 		return false;
1554 	}
1555 	return (efficacy > 0 || enemiesAffected >= 10);
1556 }
1557 
1558 /**
1559  * Attempts to take a melee attack/charge an enemy we can see.
1560  * Melee targetting: we can see an enemy, we can move to it so we're charging blindly toward an enemy.
1561  */
meleeAction()1562 void AlienBAIState::meleeAction()
1563 {
1564 	if (_aggroTarget != 0 && !_aggroTarget->isOut())
1565 	{
1566 		if (_save->getTileEngine()->validMeleeRange(_unit, _aggroTarget, _save->getTileEngine()->getDirectionTo(_unit->getPosition(), _aggroTarget->getPosition())))
1567 		{
1568 			meleeAttack();
1569 			return;
1570 		}
1571 	}
1572 	int attackCost = _unit->getActionTUs(BA_HIT, _unit->getMainHandWeapon());
1573 	int chargeReserve = _unit->getTimeUnits() - attackCost;
1574 	int distance = (chargeReserve / 4) + 1;
1575 	_aggroTarget = 0;
1576 	for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
1577 	{
1578 		int newDistance = _save->getTileEngine()->distance(_unit->getPosition(), (*i)->getPosition());
1579 		if (newDistance > 20 ||
1580 			!validTarget(*i, true, true))
1581 			continue;
1582 		//pick closest living unit that we can move to
1583 		if ((newDistance < distance || newDistance == 1) && !(*i)->isOut())
1584 		{
1585 			if (newDistance == 1 || selectPointNearTarget(*i, chargeReserve))
1586 			{
1587 				_aggroTarget = (*i);
1588 				_attackAction->type = BA_WALK;
1589 				_unit->setCharging(_aggroTarget);
1590 				distance = newDistance;
1591 			}
1592 
1593 		}
1594 	}
1595 	if (_aggroTarget != 0)
1596 	{
1597 		if (_save->getTileEngine()->validMeleeRange(_unit, _aggroTarget, _save->getTileEngine()->getDirectionTo(_unit->getPosition(), _aggroTarget->getPosition())))
1598 		{
1599 			meleeAttack();
1600 		}
1601 	}
1602 	if (_traceAI && _aggroTarget) { Log(LOG_INFO) << "AlienBAIState::meleeAction:" << " [target]: " << (_aggroTarget->getId()) << " at: "  << _attackAction->target; }
1603 	if (_traceAI && _aggroTarget) { Log(LOG_INFO) << "CHARGE!"; }
1604 }
1605 
1606 /**
1607  * Attempts to fire a waypoint projectile at an enemy we, or one of our teammates sees.
1608  *
1609  * Waypoint targeting: pick from any units currently spotted by our allies.
1610  */
wayPointAction()1611 void AlienBAIState::wayPointAction()
1612 {
1613 	_aggroTarget = 0;
1614 	for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end() && _aggroTarget == 0; ++i)
1615 	{
1616 		if (!validTarget(*i, true, true))
1617 			continue;
1618 		_save->getPathfinding()->calculate(_unit, (*i)->getPosition(), *i, -1);
1619 		if (_save->getPathfinding()->getStartDirection() != -1 &&
1620 			explosiveEfficacy((*i)->getPosition(), _unit, (_unit->getMainHandWeapon()->getAmmoItem()->getRules()->getPower()/20)+1, _attackAction->diff))
1621 		{
1622 			_aggroTarget = *i;
1623 		}
1624 		_save->getPathfinding()->abortPath();
1625 	}
1626 
1627 	if (_aggroTarget != 0)
1628 	{
1629 		_attackAction->type = BA_LAUNCH;
1630 		_attackAction->TU = _unit->getActionTUs(BA_LAUNCH, _attackAction->weapon);
1631 		if (_attackAction->TU > _unit->getTimeUnits())
1632 		{
1633 			_attackAction->type = BA_RETHINK;
1634 			return;
1635 		}
1636 		_attackAction->waypoints.clear();
1637 
1638 		int PathDirection;
1639 		int CollidesWith;
1640 		Position LastWayPoint = _unit->getPosition();
1641 		Position LastPosition = _unit->getPosition();
1642 		Position CurrentPosition = _unit->getPosition();
1643 		Position DirectionVector;
1644 
1645 		_save->getPathfinding()->calculate(_unit, _aggroTarget->getPosition(), _aggroTarget, -1);
1646 		PathDirection = _save->getPathfinding()->dequeuePath();
1647 		while (PathDirection != -1)
1648 		{
1649 			LastPosition = CurrentPosition;
1650 			_save->getPathfinding()->directionToVector(PathDirection, &DirectionVector);
1651 			CurrentPosition = CurrentPosition + DirectionVector;
1652 			Position voxelPosA ((CurrentPosition.x * 16)+8, (CurrentPosition.y * 16)+8, (CurrentPosition.z * 24)+16);
1653 			Position voxelPosb ((LastWayPoint.x * 16)+8, (LastWayPoint.y * 16)+8, (LastWayPoint.z * 24)+16);
1654 			CollidesWith = _save->getTileEngine()->calculateLine(voxelPosA, voxelPosb, false, 0, _unit, true);
1655 			if (CollidesWith > V_EMPTY && CollidesWith < V_UNIT)
1656 			{
1657 				_attackAction->waypoints.push_back(LastPosition);
1658 				LastWayPoint = LastPosition;
1659 			}
1660 			else if (CollidesWith == V_UNIT)
1661 			{
1662 				BattleUnit* target = _save->getTile(CurrentPosition)->getUnit();
1663 				if (target == _aggroTarget)
1664 				{
1665 					_attackAction->waypoints.push_back(CurrentPosition);
1666 					LastWayPoint = CurrentPosition;
1667 				}
1668 			}
1669 
1670 			PathDirection = _save->getPathfinding()->dequeuePath();
1671 		}
1672 		_attackAction->target = _attackAction->waypoints.front();
1673 		if ((int) _attackAction->waypoints.size() > 6 + (_attackAction->diff * 2) || LastWayPoint != _aggroTarget->getPosition())
1674 		{
1675 			_attackAction->type = BA_RETHINK;
1676 		}
1677 	}
1678 }
1679 
1680 /**
1681  * Attempts to fire at an enemy we can see.
1682  *
1683  * Regular targeting: we can see an enemy, we have a gun, let's try to shoot.
1684  */
projectileAction()1685 void AlienBAIState::projectileAction()
1686 {
1687 	_attackAction->target = _aggroTarget->getPosition();
1688 	if (!_attackAction->weapon->getAmmoItem()->getRules()->getExplosionRadius() ||
1689 		explosiveEfficacy(_aggroTarget->getPosition(), _unit, _attackAction->weapon->getAmmoItem()->getRules()->getExplosionRadius(), _attackAction->diff))
1690 	{
1691 		selectFireMethod();
1692 	}
1693 }
1694 
1695 /**
1696  * Selects a fire method based on range, time units, and time units reserved for cover.
1697  */
selectFireMethod()1698 void AlienBAIState::selectFireMethod()
1699 {
1700 	int distance = _save->getTileEngine()->distance(_unit->getPosition(), _attackAction->target);
1701 	_attackAction->type = BA_RETHINK;
1702 	int tuAuto = _attackAction->weapon->getRules()->getTUAuto();
1703 	int tuSnap = _attackAction->weapon->getRules()->getTUSnap();
1704 	int tuAimed = _attackAction->weapon->getRules()->getTUAimed();
1705 	int currentTU = _unit->getTimeUnits();
1706 
1707 	if (distance < 4)
1708 	{
1709 		if ( tuAuto && currentTU >= _unit->getActionTUs(BA_AUTOSHOT, _attackAction->weapon) )
1710 		{
1711 			_attackAction->type = BA_AUTOSHOT;
1712 			return;
1713 		}
1714 		if ( !tuSnap || currentTU < _unit->getActionTUs(BA_SNAPSHOT, _attackAction->weapon) )
1715 		{
1716 			if ( tuAimed && currentTU >= _unit->getActionTUs(BA_AIMEDSHOT, _attackAction->weapon) )
1717 			{
1718 				_attackAction->type = BA_AIMEDSHOT;
1719 			}
1720 			return;
1721 		}
1722 		_attackAction->type = BA_SNAPSHOT;
1723 		return;
1724 	}
1725 
1726 
1727 	if ( distance > 12 )
1728 	{
1729 		if ( tuAimed && currentTU >= _unit->getActionTUs(BA_AIMEDSHOT, _attackAction->weapon) )
1730 		{
1731 			_attackAction->type = BA_AIMEDSHOT;
1732 			return;
1733 		}
1734 		if ( distance < 20
1735 			&& tuSnap
1736 			&& currentTU >= _unit->getActionTUs(BA_SNAPSHOT, _attackAction->weapon) )
1737 		{
1738 			_attackAction->type = BA_SNAPSHOT;
1739 			return;
1740 		}
1741 	}
1742 
1743 	if ( tuSnap && currentTU >= _unit->getActionTUs(BA_SNAPSHOT, _attackAction->weapon) )
1744 	{
1745 			_attackAction->type = BA_SNAPSHOT;
1746 			return;
1747 	}
1748 	if ( tuAimed && currentTU >= _unit->getActionTUs(BA_AIMEDSHOT, _attackAction->weapon) )
1749 	{
1750 			_attackAction->type = BA_AIMEDSHOT;
1751 			return;
1752 	}
1753 	if ( tuAuto && currentTU >= _unit->getActionTUs(BA_AUTOSHOT, _attackAction->weapon) )
1754 	{
1755 			_attackAction->type = BA_AUTOSHOT;
1756 	}
1757 }
1758 
1759 /**
1760  * Evaluates whether to throw a grenade at an enemy (or group of enemies) we can see.
1761  */
grenadeAction()1762 void AlienBAIState::grenadeAction()
1763 {
1764 	// do we have a grenade on our belt?
1765 	BattleItem *grenade = _unit->getGrenadeFromBelt();
1766 	// distance must be more than X tiles, otherwise it's too dangerous to play with explosives
1767 	if (explosiveEfficacy(_aggroTarget->getPosition(), _unit, grenade->getRules()->getExplosionRadius(), _attackAction->diff, true))
1768 	{
1769 		int tu = 4; // 4TUs for picking up the grenade
1770 		tu += _unit->getActionTUs(BA_PRIME, grenade);
1771 		tu += _unit->getActionTUs(BA_THROW, grenade);
1772 		// do we have enough TUs to prime and throw the grenade?
1773 		if (tu <= _unit->getStats()->tu)
1774 		{
1775 			BattleAction action;
1776 			action.weapon = grenade;
1777 			action.target = _aggroTarget->getPosition();
1778 			action.type = BA_THROW;
1779 			action.actor = _unit;
1780 			Position originVoxel = _save->getTileEngine()->getOriginVoxel(action, 0);
1781 			Position targetVoxel = action.target * Position (16,16,24) + Position (8,8, (2 + -_save->getTile(action.target)->getTerrainLevel()));
1782 			// are we within range?
1783 			if (_save->getTileEngine()->validateThrow(action, originVoxel, targetVoxel))
1784 			{
1785 				_attackAction->weapon = grenade;
1786 				_attackAction->target = action.target;
1787 				_attackAction->type = BA_THROW;
1788 				_rifle = false;
1789 				_melee = false;
1790 			}
1791 		}
1792 	}
1793 }
1794 
1795 /**
1796  * Attempts a psionic attack on an enemy we "know of".
1797  *
1798  * Psionic targetting: pick from any of the "exposed" units.
1799  * Exposed means they have been previously spotted, and are therefore "known" to the AI,
1800  * regardless of whether we can see them or not, because we're psychic.
1801  * @return True if a psionic attack is performed.
1802  */
psiAction()1803 bool AlienBAIState::psiAction()
1804 {
1805 	RuleItem *psiWeaponRules = _save->getBattleGame()->getRuleset()->getItem("ALIEN_PSI_WEAPON");
1806 	int cost = psiWeaponRules->getTUUse();
1807 	if (!psiWeaponRules->getFlatRate())
1808 	{
1809 		cost = (int)floor(_unit->getStats()->tu * cost / 100.0f);
1810 	}
1811 	bool LOSRequired = psiWeaponRules->isLOSRequired();
1812 
1813 	_aggroTarget = 0;
1814 		// don't let mind controlled soldiers mind control other soldiers.
1815 	if (_unit->getOriginalFaction() != FACTION_PLAYER
1816 		// and we have the required 25 TUs and can still make it to cover
1817 		&& _unit->getTimeUnits() > _escapeTUs + cost
1818 		// and we didn't already do a psi action this round
1819 		&& !_didPsi)
1820 	{
1821 		int psiAttackStrength = _unit->getStats()->psiSkill * _unit->getStats()->psiStrength / 50;
1822 		int chanceToAttack = 0;
1823 
1824 		for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
1825 		{
1826 				// don't target tanks
1827 			if ((*i)->getArmor()->getSize() == 1 &&
1828 				validTarget(*i, true, false) &&
1829 				// they must be player units
1830 				(*i)->getOriginalFaction() == FACTION_PLAYER &&
1831 				(!LOSRequired ||
1832 				std::find(_unit->getVisibleUnits()->begin(), _unit->getVisibleUnits()->end(), *i) != _unit->getVisibleUnits()->end()))
1833 			{
1834 				int chanceToAttackMe = psiAttackStrength
1835 					+ (((*i)->getStats()->psiSkill > 0) ? (*i)->getStats()->psiSkill * -0.4 : 0)
1836 					- _save->getTileEngine()->distance((*i)->getPosition(), _unit->getPosition())
1837 					- ((*i)->getStats()->psiStrength)
1838 					+ RNG::generate(55, 105);
1839 
1840 				if (chanceToAttackMe > chanceToAttack)
1841 				{
1842 					chanceToAttack = chanceToAttackMe;
1843 					_aggroTarget = *i;
1844 				}
1845 			}
1846 		}
1847 
1848 		if (!_aggroTarget || !chanceToAttack) return false;
1849 
1850 		if (_visibleEnemies && _attackAction->weapon && _attackAction->weapon->getAmmoItem())
1851 		{
1852 			if (_attackAction->weapon->getAmmoItem()->getRules()->getPower() >= chanceToAttack)
1853 			{
1854 				return false;
1855 			}
1856 		}
1857 		else if (RNG::generate(35, 155) >= chanceToAttack)
1858 		{
1859 			return false;
1860 		}
1861 
1862 		if (_traceAI)
1863 		{
1864 			Log(LOG_INFO) << "making a psionic attack this turn";
1865 		}
1866 
1867 		if (chanceToAttack >= 30)
1868 		{
1869 			int controlOdds = 40;
1870 			int morale = _aggroTarget->getMorale();
1871 			int bravery = (110 - _aggroTarget->getStats()->bravery) / 10;
1872 			if (bravery > 6)
1873 				controlOdds -= 15;
1874 			if (bravery < 4)
1875 				controlOdds += 15;
1876 			if (morale >= 40)
1877 			{
1878 				if (morale - 10 * bravery < 50)
1879 					controlOdds -= 15;
1880 			}
1881 			else
1882 			{
1883 				controlOdds += 15;
1884 			}
1885 			if (!morale)
1886 			{
1887 				controlOdds = 100;
1888 			}
1889 			if (RNG::percent(controlOdds))
1890 			{
1891 				_psiAction->type = BA_MINDCONTROL;
1892 				_psiAction->target = _aggroTarget->getPosition();
1893 				return true;
1894 			}
1895 		}
1896 		_psiAction->type = BA_PANIC;
1897 		_psiAction->target = _aggroTarget->getPosition();
1898 		return true;
1899 	}
1900 	return false;
1901 }
1902 
1903 /**
1904  * Performs a melee attack action.
1905  */
meleeAttack()1906 void AlienBAIState::meleeAttack()
1907 {
1908 	_unit->lookAt(_aggroTarget->getPosition() + Position(_unit->getArmor()->getSize()-1, _unit->getArmor()->getSize()-1, 0), false);
1909 	while (_unit->getStatus() == STATUS_TURNING)
1910 		_unit->turn();
1911 	if (_traceAI) { Log(LOG_INFO) << "Attack unit: " << _aggroTarget->getId(); }
1912 	_attackAction->target = _aggroTarget->getPosition();
1913 	_attackAction->type = BA_HIT;
1914 }
1915 
validTarget(BattleUnit * unit,bool assessDanger,bool includeCivs) const1916 bool AlienBAIState::validTarget(BattleUnit *unit, bool assessDanger, bool includeCivs) const
1917 {
1918 		// ignore units that are dead/unconscious
1919 	if (unit->isOut() ||
1920 		// they must be units that we "know" about
1921 		_intelligence < unit->getTurnsSinceSpotted() ||
1922 		// they haven't been grenaded
1923 		(assessDanger && unit->getTile()->getDangerous()) ||
1924 		// and they mustn't be on our side
1925 		unit->getFaction() == FACTION_HOSTILE)
1926 	{
1927 		return false;
1928 	}
1929 
1930 	if (includeCivs)
1931 	{
1932 		return true;
1933 	}
1934 
1935 	return unit->getFaction() == FACTION_PLAYER;
1936 }
1937 }
1938