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 <sstream>
22 #include <typeinfo>
23 #include "BattlescapeGame.h"
24 #include "BattlescapeState.h"
25 #include "Map.h"
26 #include "Camera.h"
27 #include "NextTurnState.h"
28 #include "AbortMissionState.h"
29 #include "BattleState.h"
30 #include "UnitTurnBState.h"
31 #include "UnitWalkBState.h"
32 #include "ProjectileFlyBState.h"
33 #include "ExplosionBState.h"
34 #include "TileEngine.h"
35 #include "UnitInfoState.h"
36 #include "UnitDieBState.h"
37 #include "UnitPanicBState.h"
38 #include "AlienBAIState.h"
39 #include "CivilianBAIState.h"
40 #include "Pathfinding.h"
41 #include "../Engine/Game.h"
42 #include "../Engine/Language.h"
43 #include "../Engine/Sound.h"
44 #include "../Resource/ResourcePack.h"
45 #include "../Interface/Cursor.h"
46 #include "../Savegame/SavedGame.h"
47 #include "../Savegame/SavedBattleGame.h"
48 #include "../Savegame/Tile.h"
49 #include "../Savegame/BattleUnit.h"
50 #include "../Savegame/BattleItem.h"
51 #include "../Ruleset/Ruleset.h"
52 #include "../Ruleset/RuleItem.h"
53 #include "../Ruleset/RuleInventory.h"
54 #include "../Ruleset/Armor.h"
55 #include "../Engine/Options.h"
56 #include "../Engine/RNG.h"
57 #include "InfoboxState.h"
58 #include "InfoboxOKState.h"
59 #include "UnitFallBState.h"
60 #include "../Engine/Logger.h"
61
62 namespace OpenXcom
63 {
64
65 bool BattlescapeGame::_debugPlay = false;
66
67 /**
68 * Initializes all the elements in the Battlescape screen.
69 * @param save Pointer to the save game.
70 * @param parentState Pointer to the parent battlescape state.
71 */
BattlescapeGame(SavedBattleGame * save,BattlescapeState * parentState)72 BattlescapeGame::BattlescapeGame(SavedBattleGame *save, BattlescapeState *parentState) : _save(save), _parentState(parentState), _playedAggroSound(false), _endTurnRequested(false), _kneelReserved(false)
73 {
74 _tuReserved = BA_NONE;
75 _playerTUReserved = BA_NONE;
76 _debugPlay = false;
77 _playerPanicHandled = true;
78 _AIActionCounter = 0;
79 _AISecondMove = false;
80 _currentAction.actor = 0;
81
82 checkForCasualties(0, 0, true);
83 cancelCurrentAction();
84 _currentAction.targeting = false;
85 _currentAction.type = BA_NONE;
86 }
87
88
89 /**
90 * Delete BattlescapeGame.
91 */
~BattlescapeGame()92 BattlescapeGame::~BattlescapeGame()
93 {
94 for (std::list<BattleState*>::iterator i = _states.begin(); i != _states.end(); ++i)
95 {
96 delete *i;
97 }
98 cleanupDeleted();
99 }
100
101 /**
102 * Checks for units panicking or falling and so on.
103 */
think()104 void BattlescapeGame::think()
105 {
106 // nothing is happening - see if we need some alien AI or units panicking or what have you
107 if (_states.empty())
108 {
109 // it's a non player side (ALIENS or CIVILIANS)
110 if (_save->getSide() != FACTION_PLAYER)
111 {
112 if (!_debugPlay)
113 {
114 if (_save->getSelectedUnit())
115 {
116 if (!handlePanickingUnit(_save->getSelectedUnit()))
117 handleAI(_save->getSelectedUnit());
118 }
119 else
120 {
121 if (_save->selectNextPlayerUnit(true, _AISecondMove) == 0)
122 {
123 if (!_save->getDebugMode())
124 {
125 _endTurnRequested = true;
126 statePushBack(0); // end AI turn
127 }
128 else
129 {
130 _save->selectNextPlayerUnit();
131 _debugPlay = true;
132 }
133 }
134 }
135 }
136 }
137 else
138 {
139 // it's a player side && we have not handled all panicking units
140 if (!_playerPanicHandled)
141 {
142 _playerPanicHandled = handlePanickingPlayer();
143 _save->getBattleState()->updateSoldierInfo();
144 }
145 }
146 if (_save->getUnitsFalling())
147 {
148 statePushFront(new UnitFallBState(this));
149 _save->setUnitsFalling(false);
150 }
151 }
152 }
153
154 /**
155 * Initializes the Battlescape game.
156 */
init()157 void BattlescapeGame::init()
158 {
159 if (_save->getSide() == FACTION_PLAYER && _save->getTurn() > 1)
160 {
161 _playerPanicHandled = false;
162 }
163 }
164
165
166 /**
167 * Handles the processing of the AI states of a unit.
168 * @param unit Pointer to a unit.
169 */
handleAI(BattleUnit * unit)170 void BattlescapeGame::handleAI(BattleUnit *unit)
171 {
172 std::wostringstream ss;
173
174 _tuReserved = BA_NONE;
175 if (unit->getTimeUnits() <= 5)
176 {
177 unit->dontReselect();
178 }
179 if (unit->getTimeUnits() <= 5 || _AIActionCounter >= 2 || !unit->reselectAllowed())
180 {
181 if (_save->selectNextPlayerUnit(true, _AISecondMove) == 0)
182 {
183 if (!_save->getDebugMode())
184 {
185 _endTurnRequested = true;
186 statePushBack(0); // end AI turn
187 }
188 else
189 {
190 _save->selectNextPlayerUnit();
191 _debugPlay = true;
192 }
193 }
194 if (_save->getSelectedUnit())
195 {
196 _parentState->updateSoldierInfo();
197 getMap()->getCamera()->centerOnPosition(_save->getSelectedUnit()->getPosition());
198 if (_save->getSelectedUnit()->getId() <= unit->getId())
199 {
200 _AISecondMove = true;
201 }
202 }
203 _AIActionCounter = 0;
204 return;
205 }
206
207 unit->setVisible(false);
208
209 _save->getTileEngine()->calculateFOV(unit->getPosition()); // might need this populate _visibleUnit for a newly-created alien
210 // it might also help chryssalids realize they've zombified someone and need to move on
211 // it should also hide units when they've killed the guy spotting them
212 // it's also for good luck
213
214 BattleAIState *ai = unit->getCurrentAIState();
215 if (!ai)
216 {
217 // for some reason the unit had no AI routine assigned..
218 if (unit->getFaction() == FACTION_HOSTILE)
219 unit->setAIState(new AlienBAIState(_save, unit, 0));
220 else
221 unit->setAIState(new CivilianBAIState(_save, unit, 0));
222 ai = unit->getCurrentAIState();
223 }
224 _AIActionCounter++;
225 if(_AIActionCounter == 1)
226 {
227 _playedAggroSound = false;
228 unit->_hidingForTurn = false;
229 if (Options::traceAI) { Log(LOG_INFO) << "#" << unit->getId() << "--" << unit->getType(); }
230 }
231 //AlienBAIState *aggro = dynamic_cast<AlienBAIState*>(ai); // this cast only works when ai was already AlienBAIState at heart
232
233 BattleAction action;
234 action.actor = unit;
235 action.number = _AIActionCounter;
236 unit->think(&action);
237
238 if (action.type == BA_RETHINK)
239 {
240 _parentState->debug(L"Rethink");
241 unit->think(&action);
242 }
243
244 _AIActionCounter = action.number;
245
246 if (!unit->getMainHandWeapon() || !unit->getMainHandWeapon()->getAmmoItem())
247 {
248 if (unit->getOriginalFaction() == FACTION_HOSTILE && unit->getVisibleUnits()->empty())
249 {
250 findItem(&action);
251 }
252 }
253
254 if (unit->getCharging() != 0)
255 {
256 if (unit->getAggroSound() != -1 && !_playedAggroSound)
257 {
258 getResourcePack()->getSound("BATTLE.CAT", unit->getAggroSound())->play();
259 _playedAggroSound = true;
260 }
261 }
262 if (action.type == BA_WALK)
263 {
264 ss << L"Walking to " << action.target;
265 _parentState->debug(ss.str());
266
267 if (_save->getTile(action.target))
268 {
269 _save->getPathfinding()->calculate(action.actor, action.target);//, _save->getTile(action.target)->getUnit());
270 }
271 if (_save->getPathfinding()->getStartDirection() != -1)
272 {
273 statePushBack(new UnitWalkBState(this, action));
274 }
275 }
276
277 if (action.type == BA_SNAPSHOT || action.type == BA_AUTOSHOT || action.type == BA_AIMEDSHOT || action.type == BA_THROW || action.type == BA_HIT || action.type == BA_MINDCONTROL || action.type == BA_PANIC || action.type == BA_LAUNCH)
278 {
279 if (action.type == BA_MINDCONTROL || action.type == BA_PANIC)
280 {
281 action.weapon = new BattleItem(_parentState->getGame()->getRuleset()->getItem("ALIEN_PSI_WEAPON"), _save->getCurrentItemId());
282 action.TU = action.weapon->getRules()->getTUUse();
283 }
284 else
285 {
286 statePushBack(new UnitTurnBState(this, action));
287 }
288
289 ss.clear();
290 ss << L"Attack type=" << action.type << " target="<< action.target << " weapon=" << action.weapon->getRules()->getName().c_str();
291 _parentState->debug(ss.str());
292
293 statePushBack(new ProjectileFlyBState(this, action));
294 if (action.type == BA_MINDCONTROL || action.type == BA_PANIC)
295 {
296 bool success = _save->getTileEngine()->psiAttack(&action);
297 if (success && action.type == BA_MINDCONTROL)
298 {
299 // show a little infobox with the name of the unit and "... is under alien control"
300 Game *game = _parentState->getGame();
301 BattleUnit *unit = _save->getTile(action.target)->getUnit();
302 game->pushState(new InfoboxState(game, game->getLanguage()->getString("STR_IS_UNDER_ALIEN_CONTROL", unit->getGender()).arg(unit->getName(game->getLanguage()))));
303 }
304 _save->removeItem(action.weapon);
305 }
306 }
307
308 if (action.type == BA_NONE)
309 {
310 _parentState->debug(L"Idle");
311 _AIActionCounter = 0;
312 if (_save->selectNextPlayerUnit(true, _AISecondMove) == 0)
313 {
314 if (!_save->getDebugMode())
315 {
316 _endTurnRequested = true;
317 statePushBack(0); // end AI turn
318 }
319 else
320 {
321 _save->selectNextPlayerUnit();
322 _debugPlay = true;
323 }
324 }
325 if (_save->getSelectedUnit())
326 {
327 _parentState->updateSoldierInfo();
328 getMap()->getCamera()->centerOnPosition(_save->getSelectedUnit()->getPosition());
329 if (_save->getSelectedUnit()->getId() <= unit->getId())
330 {
331 _AISecondMove = true;
332 }
333 }
334 }
335 }
336
337 /**
338 * Toggles the Kneel/Standup status of the unit.
339 * @param bu Pointer to a unit.
340 * @return If the action succeeded.
341 */
kneel(BattleUnit * bu)342 bool BattlescapeGame::kneel(BattleUnit *bu)
343 {
344 int tu = bu->isKneeled()?8:4;
345 if (bu->getType() == "SOLDIER" && !bu->isFloating() && ((!bu->isKneeled() && _kneelReserved) || checkReservedTU(bu, tu)))
346 {
347 if (bu->spendTimeUnits(tu))
348 {
349 bu->kneel(!bu->isKneeled());
350 // kneeling or standing up can reveal new terrain or units. I guess.
351 getTileEngine()->calculateFOV(bu);
352 getMap()->cacheUnits();
353 _parentState->updateSoldierInfo();
354 getTileEngine()->checkReactionFire(bu);
355 return true;
356 }
357 else
358 {
359 _parentState->warning("STR_NOT_ENOUGH_TIME_UNITS");
360 }
361 }
362 return false;
363 }
364
365 /**
366 * Ends the turn.
367 */
endTurn()368 void BattlescapeGame::endTurn()
369 {
370
371 Position p;
372
373 _tuReserved = _playerTUReserved;
374 _debugPlay = false;
375 _currentAction.type = BA_NONE;
376 getMap()->getWaypoints()->clear();
377 _currentAction.waypoints.clear();
378 _parentState->showLaunchButton(false);
379 _currentAction.targeting = false;
380 _AISecondMove = false;
381
382 if (_save->getTileEngine()->closeUfoDoors())
383 {
384 getResourcePack()->getSound("BATTLE.CAT", 21)->play(); // ufo door closed
385 }
386
387 // check for hot grenades on the ground
388 for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
389 {
390 for (std::vector<BattleItem*>::iterator it = _save->getTiles()[i]->getInventory()->begin(); it != _save->getTiles()[i]->getInventory()->end(); )
391 {
392 if ((*it)->getRules()->getBattleType() == BT_GRENADE && (*it)->getFuseTimer() == 0) // it's a grenade to explode now
393 {
394 p.x = _save->getTiles()[i]->getPosition().x*16 + 8;
395 p.y = _save->getTiles()[i]->getPosition().y*16 + 8;
396 p.z = _save->getTiles()[i]->getPosition().z*24 - _save->getTiles()[i]->getTerrainLevel();
397 statePushNext(new ExplosionBState(this, p, (*it), (*it)->getPreviousOwner()));
398 _save->removeItem((*it));
399 statePushBack(0);
400 return;
401 }
402 ++it;
403 }
404 }
405 // check for terrain explosions
406 Tile *t = _save->getTileEngine()->checkForTerrainExplosions();
407 if (t)
408 {
409 Position p = Position(t->getPosition().x * 16, t->getPosition().y * 16, t->getPosition().z * 24);
410 statePushNext(new ExplosionBState(this, p, 0, 0, t));
411 t = _save->getTileEngine()->checkForTerrainExplosions();
412 statePushBack(0);
413 return;
414 }
415
416 if (_save->getSide() != FACTION_NEUTRAL)
417 {
418 for (std::vector<BattleItem*>::iterator it = _save->getItems()->begin(); it != _save->getItems()->end(); ++it)
419 {
420 if (((*it)->getRules()->getBattleType() == BT_GRENADE || (*it)->getRules()->getBattleType() == BT_PROXIMITYGRENADE) && (*it)->getFuseTimer() > 0)
421 {
422 (*it)->setFuseTimer((*it)->getFuseTimer() - 1);
423 }
424 }
425 }
426
427 // if all units from either faction are killed - the mission is over.
428 int liveAliens = 0;
429 int liveSoldiers = 0;
430 // we'll tally them NOW, so that any infected units will... change
431 tallyUnits(liveAliens, liveSoldiers, true);
432
433 _save->endTurn();
434
435 if (_save->getSide() == FACTION_PLAYER)
436 {
437 setupCursor();
438 }
439 else
440 {
441 getMap()->setCursorType(CT_NONE);
442 }
443
444 checkForCasualties(0, 0, false, false);
445
446 // turn off MCed alien lighting.
447 _save->getTileEngine()->calculateUnitLighting();
448
449 if (_save->isObjectiveDestroyed())
450 {
451 _parentState->finishBattle(false,liveSoldiers);
452 return;
453 }
454
455
456 if (liveAliens > 0 && liveSoldiers > 0)
457 {
458 showInfoBoxQueue();
459
460 _parentState->updateSoldierInfo();
461
462 if (playableUnitSelected())
463 {
464 getMap()->getCamera()->centerOnPosition(_save->getSelectedUnit()->getPosition());
465 setupCursor();
466 }
467 }
468
469 bool battleComplete = liveAliens == 0 || liveSoldiers == 0;
470
471 if ((_save->getSide() != FACTION_NEUTRAL || battleComplete)
472 && _endTurnRequested)
473 {
474 _parentState->getGame()->pushState(new NextTurnState(_parentState->getGame(), _save, _parentState));
475 }
476 _endTurnRequested = false;
477 }
478
479
480 /**
481 * Checks for casualties and adjusts morale accordingly.
482 * @param murderweapon Need to know this, for a HE explosion there is an instant death.
483 * @param murderer This is needed for credits for the kill.
484 * @param hiddenExplosion Set to true for the explosions of UFO Power sources at start of battlescape.
485 * @param terrainExplosion Set to true for the explosions of terrain.
486 */
checkForCasualties(BattleItem * murderweapon,BattleUnit * murderer,bool hiddenExplosion,bool terrainExplosion)487 void BattlescapeGame::checkForCasualties(BattleItem *murderweapon, BattleUnit *murderer, bool hiddenExplosion, bool terrainExplosion)
488 {
489 for (std::vector<BattleUnit*>::iterator j = _save->getUnits()->begin(); j != _save->getUnits()->end(); ++j)
490 {
491 if ((*j)->getHealth() == 0 && (*j)->getStatus() != STATUS_DEAD && (*j)->getStatus() != STATUS_COLLAPSING)
492 {
493 BattleUnit *victim = (*j);
494
495 if (murderer)
496 {
497 murderer->addKillCount();
498 victim->killedBy(murderer->getFaction());
499 int modifier = murderer->getFaction() == FACTION_PLAYER ? _save->getMoraleModifier() : 100;
500
501 // if there is a known murderer, he will get a morale bonus if he is of a different faction (what with neutral?)
502 if ((victim->getOriginalFaction() == FACTION_PLAYER && murderer->getFaction() == FACTION_HOSTILE) ||
503 (victim->getOriginalFaction() == FACTION_HOSTILE && murderer->getFaction() == FACTION_PLAYER))
504 {
505 murderer->moraleChange(20 * modifier / 100);
506 }
507 // murderer will get a penalty with friendly fire
508 if (victim->getOriginalFaction() == murderer->getOriginalFaction())
509 {
510 murderer->moraleChange(-(2000 / modifier));
511 }
512 if (victim->getOriginalFaction() == FACTION_NEUTRAL)
513 {
514 if (murderer->getOriginalFaction() == FACTION_PLAYER)
515 {
516 murderer->moraleChange(-(1000 / modifier));
517 }
518 else
519 {
520 murderer->moraleChange(10);
521 }
522 }
523 }
524
525 if (victim->getFaction() != FACTION_NEUTRAL)
526 {
527 int modifier = _save->getMoraleModifier(victim);
528 int loserMod = victim->getFaction() == FACTION_HOSTILE ? 100 : _save->getMoraleModifier();
529 int winnerMod = victim->getFaction() == FACTION_HOSTILE ? _save->getMoraleModifier() : 100;
530 for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
531 {
532 if (!(*i)->isOut() && (*i)->getArmor()->getSize() == 1)
533 {
534 // the losing squad all get a morale loss
535 if ((*i)->getOriginalFaction() == victim->getOriginalFaction())
536 {
537 int bravery = (110 - (*i)->getStats()->bravery) / 10;
538 (*i)->moraleChange(-(modifier * 200 * bravery / loserMod / 100));
539
540 if (victim->getFaction() == FACTION_HOSTILE && murderer)
541 {
542 murderer->setTurnsSinceSpotted(0);
543 }
544 }
545 // the winning squad all get a morale increase
546 else
547 {
548 (*i)->moraleChange(10 * winnerMod / 100);
549 }
550 }
551 }
552 }
553 if (murderweapon)
554 {
555 statePushNext(new UnitDieBState(this, (*j), murderweapon->getRules()->getDamageType(), false));
556 }
557 else
558 {
559 if (hiddenExplosion)
560 {
561 // this is instant death from UFO powersources, without screaming sounds
562 statePushNext(new UnitDieBState(this, (*j), DT_HE, true));
563 }
564 else
565 {
566 if (terrainExplosion)
567 {
568 // terrain explosion
569 statePushNext(new UnitDieBState(this, (*j), DT_HE, false));
570 }
571 else
572 {
573 // no murderer, and no terrain explosion, must be fatal wounds
574 statePushNext(new UnitDieBState(this, (*j), DT_NONE, false)); // DT_NONE = STR_HAS_DIED_FROM_A_FATAL_WOUND
575 }
576 }
577 }
578 }
579 else if ((*j)->getStunlevel() >= (*j)->getHealth() && (*j)->getStatus() != STATUS_DEAD && (*j)->getStatus() != STATUS_UNCONSCIOUS && (*j)->getStatus() != STATUS_COLLAPSING && (*j)->getStatus() != STATUS_TURNING)
580 {
581 statePushNext(new UnitDieBState(this, (*j), DT_STUN, true));
582 }
583 }
584 BattleUnit *bu = _save->getSelectedUnit();
585 if (_save->getSide() == FACTION_PLAYER)
586 {
587 _parentState->showPsiButton(bu && bu->getOriginalFaction() == FACTION_HOSTILE && bu->getStats()->psiSkill > 0 && !bu->isOut());
588 }
589 }
590
591 /**
592 * Shows the infoboxes in the queue (if any).
593 */
showInfoBoxQueue()594 void BattlescapeGame::showInfoBoxQueue()
595 {
596 for (std::vector<InfoboxOKState*>::iterator i = _infoboxQueue.begin(); i != _infoboxQueue.end(); ++i)
597 {
598 _parentState->getGame()->pushState(*i);
599 }
600
601 _infoboxQueue.clear();
602 }
603
604 /**
605 * Handles the result of non target actions, like priming a grenade.
606 */
handleNonTargetAction()607 void BattlescapeGame::handleNonTargetAction()
608 {
609 if (!_currentAction.targeting)
610 {
611 _currentAction.cameraPosition = Position(0,0,-1);
612 if (_currentAction.type == BA_PRIME && _currentAction.value > -1)
613 {
614 if (_currentAction.actor->spendTimeUnits(_currentAction.TU))
615 {
616 _parentState->warning("STR_GRENADE_IS_ACTIVATED");
617 _currentAction.weapon->setFuseTimer(_currentAction.value);
618 }
619 else
620 {
621 _parentState->warning("STR_NOT_ENOUGH_TIME_UNITS");
622 }
623 }
624 if (_currentAction.type == BA_USE || _currentAction.type == BA_LAUNCH)
625 {
626 if (_currentAction.result.length() > 0)
627 {
628 _parentState->warning(_currentAction.result);
629 _currentAction.result = "";
630 }
631 _save->reviveUnconsciousUnits();
632 }
633 if (_currentAction.type == BA_HIT)
634 {
635 if (_currentAction.result.length() > 0)
636 {
637 _parentState->warning(_currentAction.result);
638 _currentAction.result = "";
639 }
640 else
641 {
642 if (_currentAction.actor->spendTimeUnits(_currentAction.TU))
643 {
644 statePushBack(new ProjectileFlyBState(this, _currentAction));
645 return;
646 }
647 else
648 {
649 _parentState->warning("STR_NOT_ENOUGH_TIME_UNITS");
650 }
651 }
652 }
653 _currentAction.type = BA_NONE;
654 _parentState->updateSoldierInfo();
655 }
656
657 setupCursor();
658 }
659
660 /**
661 * Sets the cursor according to the selected action.
662 */
setupCursor()663 void BattlescapeGame::setupCursor()
664 {
665 if (_currentAction.targeting)
666 {
667 if (_currentAction.type == BA_THROW)
668 {
669 getMap()->setCursorType(CT_THROW);
670 }
671 else if (_currentAction.type == BA_MINDCONTROL || _currentAction.type == BA_PANIC || _currentAction.type == BA_USE)
672 {
673 getMap()->setCursorType(CT_PSI);
674 }
675 else if (_currentAction.type == BA_LAUNCH)
676 {
677 getMap()->setCursorType(CT_WAYPOINT);
678 }
679 else
680 {
681 getMap()->setCursorType(CT_AIM);
682 }
683 }
684 else
685 {
686 _currentAction.actor = _save->getSelectedUnit();
687 if (_currentAction.actor)
688 {
689 getMap()->setCursorType(CT_NORMAL, _currentAction.actor->getArmor()->getSize());
690 }
691 }
692 }
693
694 /**
695 * Determines whether a playable unit is selected. Normally only player side units can be selected, but in debug mode one can play with aliens too :)
696 * Is used to see if stats can be displayed and action buttons will work.
697 * @return Whether a playable unit is selected.
698 */
playableUnitSelected()699 bool BattlescapeGame::playableUnitSelected()
700 {
701 return _save->getSelectedUnit() != 0 && (_save->getSide() == FACTION_PLAYER || _save->getDebugMode());
702 }
703
704 /**
705 * Gives time slice to the front state.
706 */
handleState()707 void BattlescapeGame::handleState()
708 {
709 if (!_states.empty())
710 {
711 // end turn request?
712 if (_states.front() == 0)
713 {
714 _states.pop_front();
715 endTurn();
716 return;
717 }
718 else
719 {
720 _states.front()->think();
721 }
722 getMap()->invalidate(); // redraw map
723 }
724 }
725
726 /**
727 * Pushes a state to the front of the queue and starts it.
728 * @param bs Battlestate.
729 */
statePushFront(BattleState * bs)730 void BattlescapeGame::statePushFront(BattleState *bs)
731 {
732 _states.push_front(bs);
733 bs->init();
734 }
735
736 /**
737 * Pushes a state as the next state after the current one.
738 * @param bs Battlestate.
739 */
statePushNext(BattleState * bs)740 void BattlescapeGame::statePushNext(BattleState *bs)
741 {
742 if (_states.empty())
743 {
744 _states.push_front(bs);
745 bs->init();
746 }
747 else
748 {
749 _states.insert(++_states.begin(), bs);
750 }
751
752 }
753
754 /**
755 * Pushes a state to the back.
756 * @param bs Battlestate.
757 */
statePushBack(BattleState * bs)758 void BattlescapeGame::statePushBack(BattleState *bs)
759 {
760 if (_states.empty())
761 {
762 _states.push_front(bs);
763 // end turn request?
764 if (_states.front() == 0)
765 {
766 _states.pop_front();
767 endTurn();
768 return;
769 }
770 else
771 {
772 bs->init();
773 }
774 }
775 else
776 {
777 _states.push_back(bs);
778 }
779 }
780
781 /**
782 * Removes the current state.
783 *
784 * This is a very important function. It is called by a BattleState (walking, projectile is flying, explosions,...) at the moment this state has finished its action.
785 * Here we check the result of that action and do all the aftermath.
786 * The state is popped off the list.
787 */
popState()788 void BattlescapeGame::popState()
789 {
790 if (Options::traceAI)
791 {
792 Log(LOG_INFO) << "BattlescapeGame::popState() #" << _AIActionCounter << " with " << (_save->getSelectedUnit() ? _save->getSelectedUnit()->getTimeUnits() : -9999) << " TU";
793 }
794 bool actionFailed = false;
795
796 if (_states.empty()) return;
797
798 BattleAction action = _states.front()->getAction();
799
800 if (action.actor && action.result.length() > 0 && action.actor->getFaction() == FACTION_PLAYER
801 && _playerPanicHandled && (_save->getSide() == FACTION_PLAYER || _debugPlay))
802 {
803 _parentState->warning(action.result);
804 actionFailed = true;
805 }
806 _deleted.push_back(_states.front());
807 _states.pop_front();
808
809 // handle the end of this unit's actions
810 if (action.actor && noActionsPending(action.actor))
811 {
812 if (action.actor->getFaction() == FACTION_PLAYER)
813 {
814 // spend TUs of "target triggered actions" (shooting, throwing) only
815 // the other actions' TUs (healing,scanning,..) are already take care of
816 if (action.targeting && _save->getSelectedUnit() && !actionFailed)
817 {
818 action.actor->spendTimeUnits(action.TU);
819 }
820 if (_save->getSide() == FACTION_PLAYER)
821 {
822 // after throwing the cursor returns to default cursor, after shooting it stays in targeting mode and the player can shoot again in the same mode (autoshot,snap,aimed)
823 if ((action.type == BA_THROW || action.type == BA_LAUNCH) && !actionFailed)
824 {
825 // clean up the waypoints
826 if (action.type == BA_LAUNCH)
827 {
828 _currentAction.waypoints.clear();
829 }
830
831 cancelCurrentAction(true);
832 }
833 _parentState->getGame()->getCursor()->setVisible(true);
834 setupCursor();
835 }
836 }
837 else
838 {
839 // spend TUs
840 action.actor->spendTimeUnits(action.TU);
841 if (_save->getSide() != FACTION_PLAYER && !_debugPlay)
842 {
843 // AI does three things per unit, before switching to the next, or it got killed before doing the second thing
844 if (_AIActionCounter > 2 || _save->getSelectedUnit() == 0 || _save->getSelectedUnit()->isOut())
845 {
846 if (_save->getSelectedUnit())
847 {
848 _save->getSelectedUnit()->setCache(0);
849 getMap()->cacheUnit(_save->getSelectedUnit());
850 }
851 _AIActionCounter = 0;
852 if (_states.empty() && _save->selectNextPlayerUnit(true) == 0)
853 {
854 if (!_save->getDebugMode())
855 {
856 _endTurnRequested = true;
857 statePushBack(0); // end AI turn
858 }
859 else
860 {
861 _save->selectNextPlayerUnit();
862 _debugPlay = true;
863 }
864 }
865 if (_save->getSelectedUnit())
866 {
867 getMap()->getCamera()->centerOnPosition(_save->getSelectedUnit()->getPosition());
868 }
869 }
870 }
871 else if (_debugPlay)
872 {
873 _parentState->getGame()->getCursor()->setVisible(true);
874 setupCursor();
875 }
876 }
877 }
878
879 if (!_states.empty())
880 {
881 // end turn request?
882 if (_states.front() == 0)
883 {
884 while (!_states.empty())
885 {
886 if (_states.front() == 0)
887 _states.pop_front();
888 else
889 break;
890 }
891 if (_states.empty())
892 {
893 endTurn();
894 return;
895 }
896 else
897 {
898 _states.push_back(0);
899 }
900 }
901 // init the next state in queue
902 _states.front()->init();
903 }
904
905 // the currently selected unit died or became unconscious or disappeared inexplicably
906 if (_save->getSelectedUnit() == 0 || _save->getSelectedUnit()->isOut())
907 {
908 cancelCurrentAction();
909 getMap()->setCursorType(CT_NORMAL, 1);
910 _parentState->getGame()->getCursor()->setVisible(true);
911 _save->setSelectedUnit(0);
912 }
913 _parentState->updateSoldierInfo();
914 }
915
916 /**
917 * Determines whether there are any actions pending for the given unit.
918 * @param bu BattleUnit.
919 * @return True if there are no actions pending.
920 */
noActionsPending(BattleUnit * bu)921 bool BattlescapeGame::noActionsPending(BattleUnit *bu)
922 {
923 if (_states.empty()) return true;
924
925 for (std::list<BattleState*>::iterator i = _states.begin(); i != _states.end(); ++i)
926 {
927 if ((*i) != 0 && (*i)->getAction().actor == bu)
928 return false;
929 }
930
931 return true;
932 }
933 /**
934 * Sets the timer interval for think() calls of the state.
935 * @param interval An interval in ms.
936 */
setStateInterval(Uint32 interval)937 void BattlescapeGame::setStateInterval(Uint32 interval)
938 {
939 _parentState->setStateInterval(interval);
940 }
941
942
943 /**
944 * Checks against reserved time units.
945 * @param bu Pointer to the unit.
946 * @param tu Number of time units to check.
947 * @param justChecking True to suppress error messages, false otherwise.
948 * @return bool Whether or not we got enough time units.
949 */
checkReservedTU(BattleUnit * bu,int tu,bool justChecking)950 bool BattlescapeGame::checkReservedTU(BattleUnit *bu, int tu, bool justChecking)
951 {
952 BattleActionType effectiveTuReserved = _tuReserved; // avoid changing _tuReserved in this method
953
954 if (_save->getSide() != FACTION_PLAYER) // aliens reserve TUs as a percentage rather than just enough for a single action.
955 {
956 if (_save->getSide() == FACTION_NEUTRAL)
957 {
958 return tu <= bu->getTimeUnits();
959 }
960 switch (effectiveTuReserved)
961 {
962 case BA_SNAPSHOT: return tu + (bu->getStats()->tu / 3) <= bu->getTimeUnits(); break; // 33%
963 case BA_AUTOSHOT: return tu + ((bu->getStats()->tu / 5)*2) <= bu->getTimeUnits(); break; // 40%
964 case BA_AIMEDSHOT: return tu + (bu->getStats()->tu / 2) <= bu->getTimeUnits(); break; // 50%
965 default: return tu <= bu->getTimeUnits(); break;
966 }
967 }
968
969 // check TUs against slowest weapon if we have two weapons
970 BattleItem *slowestWeapon = bu->getMainHandWeapon(false);
971 // if the weapon has no autoshot, reserve TUs for snapshot
972 if (bu->getActionTUs(_tuReserved, slowestWeapon) == 0 && _tuReserved == BA_AUTOSHOT)
973 {
974 effectiveTuReserved = BA_SNAPSHOT;
975 }
976 // likewise, if we don't have a snap shot available, try aimed.
977 if (bu->getActionTUs(effectiveTuReserved, slowestWeapon) == 0 && effectiveTuReserved == BA_SNAPSHOT)
978 {
979 effectiveTuReserved = BA_AIMEDSHOT;
980 }
981 const int tuKneel = (_kneelReserved && bu->getType() == "SOLDIER") ? 4 : 0;
982 if ((effectiveTuReserved != BA_NONE || _kneelReserved) &&
983 tu + tuKneel + bu->getActionTUs(effectiveTuReserved, slowestWeapon) > bu->getTimeUnits() &&
984 (tuKneel + bu->getActionTUs(effectiveTuReserved, slowestWeapon) <= bu->getTimeUnits() || justChecking))
985 {
986 if (!justChecking)
987 {
988 if (tuKneel)
989 {
990 switch (effectiveTuReserved)
991 {
992 case BA_NONE: _parentState->warning("STR_TIME_UNITS_RESERVED_FOR_KNEELING"); break;
993 default: _parentState->warning("STR_TIME_UNITS_RESERVED_FOR_KNEELING_AND_FIRING");
994 }
995 }
996 else
997 {
998 switch (effectiveTuReserved)
999 {
1000 case BA_SNAPSHOT: _parentState->warning("STR_TIME_UNITS_RESERVED_FOR_SNAP_SHOT"); break;
1001 case BA_AUTOSHOT: _parentState->warning("STR_TIME_UNITS_RESERVED_FOR_AUTO_SHOT"); break;
1002 case BA_AIMEDSHOT: _parentState->warning("STR_TIME_UNITS_RESERVED_FOR_AIMED_SHOT"); break;
1003 default: ;
1004 }
1005 }
1006 }
1007 return false;
1008 }
1009
1010 return true;
1011 }
1012
1013
1014
1015 /**
1016 * Picks the first soldier that is panicking.
1017 * @return True when all panicking is over.
1018 */
handlePanickingPlayer()1019 bool BattlescapeGame::handlePanickingPlayer()
1020 {
1021 for (std::vector<BattleUnit*>::iterator j = _save->getUnits()->begin(); j != _save->getUnits()->end(); ++j)
1022 {
1023 if ((*j)->getFaction() == FACTION_PLAYER && (*j)->getOriginalFaction() == FACTION_PLAYER && handlePanickingUnit(*j))
1024 return false;
1025 }
1026 return true;
1027 }
1028
1029 /**
1030 * Common function for hanlding panicking units.
1031 * @return False when unit not in panicking mode.
1032 */
handlePanickingUnit(BattleUnit * unit)1033 bool BattlescapeGame::handlePanickingUnit(BattleUnit *unit)
1034 {
1035 UnitStatus status = unit->getStatus();
1036 if (status != STATUS_PANICKING && status != STATUS_BERSERK) return false;
1037 _save->setSelectedUnit(unit);
1038 _parentState->getMap()->setCursorType(CT_NONE);
1039
1040 // show a little infobox with the name of the unit and "... is panicking"
1041 Game *game = _parentState->getGame();
1042 if (unit->getVisible() || !Options::noAlienPanicMessages)
1043 {
1044 getMap()->getCamera()->centerOnPosition(unit->getPosition());
1045 if (status == STATUS_PANICKING)
1046 {
1047 game->pushState(new InfoboxState(game, game->getLanguage()->getString("STR_HAS_PANICKED", unit->getGender()).arg(unit->getName(game->getLanguage()))));
1048 }
1049 else
1050 {
1051 game->pushState(new InfoboxState(game, game->getLanguage()->getString("STR_HAS_GONE_BERSERK", unit->getGender()).arg(unit->getName(game->getLanguage()))));
1052 }
1053 }
1054
1055 unit->abortTurn(); //makes the unit go to status STANDING :p
1056
1057 int flee = RNG::generate(0,100);
1058 BattleAction ba;
1059 ba.actor = unit;
1060 switch (status)
1061 {
1062 case STATUS_PANICKING: // 1/2 chance to freeze and 1/2 chance try to flee
1063 if (flee <= 50)
1064 {
1065 BattleItem *item = unit->getItem("STR_RIGHT_HAND");
1066 if (item)
1067 {
1068 dropItem(unit->getPosition(), item, false, true);
1069 }
1070 item = unit->getItem("STR_LEFT_HAND");
1071 if (item)
1072 {
1073 dropItem(unit->getPosition(), item, false, true);
1074 }
1075 unit->setCache(0);
1076 ba.target = Position(unit->getPosition().x + RNG::generate(-5,5), unit->getPosition().y + RNG::generate(-5,5), unit->getPosition().z);
1077 if (_save->getTile(ba.target)) // only walk towards it when the place exists
1078 {
1079 _save->getPathfinding()->calculate(ba.actor, ba.target);
1080 statePushBack(new UnitWalkBState(this, ba));
1081 }
1082 }
1083 break;
1084 case STATUS_BERSERK: // berserk - do some weird turning around and then aggro towards an enemy unit or shoot towards random place
1085 ba.type = BA_TURN;
1086 for (int i= 0; i < 4; i++)
1087 {
1088 ba.target = Position(unit->getPosition().x + RNG::generate(-5,5), unit->getPosition().y + RNG::generate(-5,5), unit->getPosition().z);
1089 statePushBack(new UnitTurnBState(this, ba));
1090 }
1091 for (std::vector<BattleUnit*>::iterator j = unit->getVisibleUnits()->begin(); j != unit->getVisibleUnits()->end(); ++j)
1092 {
1093 ba.target = (*j)->getPosition();
1094 statePushBack(new UnitTurnBState(this, ba));
1095 }
1096 if (_save->getTile(ba.target) != 0)
1097 {
1098 ba.weapon = unit->getMainHandWeapon();
1099 if(ba.weapon)
1100 {
1101 if (ba.weapon->getRules()->getBattleType() == BT_FIREARM)
1102 {
1103 ba.type = BA_SNAPSHOT;
1104 int tu = ba.actor->getActionTUs(ba.type, ba.weapon);
1105 for (int i= 0; i < 10; i++)
1106 {
1107 // fire shots until unit runs out of TUs
1108 if (!ba.actor->spendTimeUnits(tu))
1109 break;
1110 statePushBack(new ProjectileFlyBState(this, ba));
1111 }
1112 }
1113 else if (ba.weapon->getRules()->getBattleType() == BT_GRENADE)
1114 {
1115 if (ba.weapon->getFuseTimer() == -1)
1116 {
1117 ba.weapon->setFuseTimer(0);
1118 }
1119 ba.type = BA_THROW;
1120 statePushBack(new ProjectileFlyBState(this, ba));
1121 }
1122 }
1123 }
1124 // replace the TUs from shooting
1125 unit->setTimeUnits(unit->getStats()->tu);
1126 ba.type = BA_NONE;
1127 break;
1128 default: break;
1129 }
1130 // Time units can only be reset after everything else occurs
1131 statePushBack(new UnitPanicBState(this, ba.actor));
1132 unit->moraleChange(+15);
1133
1134 return true;
1135 }
1136
1137 /**
1138 * Cancels the current action the user had selected (firing, throwing,..)
1139 * @param bForce Force the action to be cancelled.
1140 * @return Whether an action was cancelled or not.
1141 */
cancelCurrentAction(bool bForce)1142 bool BattlescapeGame::cancelCurrentAction(bool bForce)
1143 {
1144 bool bPreviewed = Options::battleNewPreviewPath != PATH_NONE;
1145
1146 if (_save->getPathfinding()->removePreview() && bPreviewed) return true;
1147
1148 if (_states.empty() || bForce)
1149 {
1150 if (_currentAction.targeting)
1151 {
1152 if (_currentAction.type == BA_LAUNCH && !_currentAction.waypoints.empty())
1153 {
1154 _currentAction.waypoints.pop_back();
1155 if (!getMap()->getWaypoints()->empty())
1156 {
1157 getMap()->getWaypoints()->pop_back();
1158 }
1159 if (_currentAction.waypoints.empty())
1160 {
1161 _parentState->showLaunchButton(false);
1162 }
1163 return true;
1164 }
1165 else
1166 {
1167 if (Options::battleConfirmFireMode && !_currentAction.waypoints.empty())
1168 {
1169 _currentAction.waypoints.pop_back();
1170 getMap()->getWaypoints()->pop_back();
1171 return true;
1172 }
1173 _currentAction.targeting = false;
1174 _currentAction.type = BA_NONE;
1175 setupCursor();
1176 _parentState->getGame()->getCursor()->setVisible(true);
1177 return true;
1178 }
1179 }
1180 }
1181 else if (!_states.empty() && _states.front() != 0)
1182 {
1183 _states.front()->cancel();
1184 return true;
1185 }
1186
1187 return false;
1188 }
1189 /**
1190 * Gets a pointer to access action members directly.
1191 * @return Pointer to action.
1192 */
getCurrentAction()1193 BattleAction *BattlescapeGame::getCurrentAction()
1194 {
1195 return &_currentAction;
1196 }
1197
1198 /**
1199 * Determines whether an action is currently going on?
1200 * @return true or false.
1201 */
isBusy()1202 bool BattlescapeGame::isBusy()
1203 {
1204 return !_states.empty();
1205 }
1206
1207 /**
1208 * Activates primary action (left click).
1209 * @param pos Position on the map.
1210 */
primaryAction(const Position & pos)1211 void BattlescapeGame::primaryAction(const Position &pos)
1212 {
1213 bool bPreviewed = Options::battleNewPreviewPath != PATH_NONE;
1214
1215 if (_currentAction.targeting && _save->getSelectedUnit())
1216 {
1217 if (_currentAction.type == BA_LAUNCH)
1218 {
1219 _parentState->showLaunchButton(true);
1220 _currentAction.waypoints.push_back(pos);
1221 getMap()->getWaypoints()->push_back(pos);
1222 }
1223 else if (_currentAction.type == BA_USE && _currentAction.weapon->getRules()->getBattleType() == BT_MINDPROBE)
1224 {
1225 if (_save->selectUnit(pos) && _save->selectUnit(pos)->getFaction() != _save->getSelectedUnit()->getFaction() && _save->selectUnit(pos)->getVisible())
1226 {
1227 if (!_currentAction.weapon->getRules()->isLOSRequired() ||
1228 std::find(_currentAction.actor->getVisibleUnits()->begin(), _currentAction.actor->getVisibleUnits()->end(), _save->selectUnit(pos)) != _currentAction.actor->getVisibleUnits()->end())
1229 {
1230 if (_currentAction.actor->spendTimeUnits(_currentAction.TU))
1231 {
1232 _parentState->getGame()->getResourcePack()->getSound("BATTLE.CAT", _currentAction.weapon->getRules()->getHitSound())->play();
1233 _parentState->getGame()->pushState (new UnitInfoState(_parentState->getGame(), _save->selectUnit(pos), _parentState, false, true));
1234 cancelCurrentAction();
1235 }
1236 else
1237 {
1238 _parentState->warning("STR_NOT_ENOUGH_TIME_UNITS");
1239 }
1240 }
1241 else
1242 {
1243 _parentState->warning("STR_NO_LINE_OF_FIRE");
1244 }
1245 }
1246 }
1247 else if (_currentAction.type == BA_PANIC || _currentAction.type == BA_MINDCONTROL)
1248 {
1249 if (_save->selectUnit(pos) && _save->selectUnit(pos)->getFaction() != _save->getSelectedUnit()->getFaction() && _save->selectUnit(pos)->getVisible())
1250 {
1251 bool builtinpsi = !_currentAction.weapon;
1252 if (builtinpsi)
1253 {
1254 _currentAction.weapon = new BattleItem(_parentState->getGame()->getRuleset()->getItem("ALIEN_PSI_WEAPON"), _save->getCurrentItemId());
1255 }
1256 _currentAction.TU = _currentAction.actor->getActionTUs(_currentAction.type, _currentAction.weapon);
1257 _currentAction.target = pos;
1258 if (!_currentAction.weapon->getRules()->isLOSRequired() ||
1259 std::find(_currentAction.actor->getVisibleUnits()->begin(), _currentAction.actor->getVisibleUnits()->end(), _save->selectUnit(pos)) != _currentAction.actor->getVisibleUnits()->end())
1260 {
1261 // get the sound/animation started
1262 getMap()->setCursorType(CT_NONE);
1263 _parentState->getGame()->getCursor()->setVisible(false);
1264 _currentAction.cameraPosition = getMap()->getCamera()->getMapOffset();
1265 statePushBack(new ProjectileFlyBState(this, _currentAction));
1266 if (_currentAction.TU <= _currentAction.actor->getTimeUnits())
1267 {
1268 if (getTileEngine()->psiAttack(&_currentAction))
1269 {
1270 // show a little infobox if it's successful
1271 Game *game = _parentState->getGame();
1272 if (_currentAction.type == BA_PANIC)
1273 game->pushState(new InfoboxState(game, game->getLanguage()->getString("STR_MORALE_ATTACK_SUCCESSFUL")));
1274 else if (_currentAction.type == BA_MINDCONTROL)
1275 game->pushState(new InfoboxState(game, game->getLanguage()->getString("STR_MIND_CONTROL_SUCCESSFUL")));
1276 _parentState->updateSoldierInfo();
1277 }
1278 }
1279 }
1280 else
1281 {
1282 _parentState->warning("STR_NO_LINE_OF_FIRE");
1283 }
1284 if (builtinpsi)
1285 {
1286 _save->removeItem(_currentAction.weapon);
1287 _currentAction.weapon = 0;
1288 }
1289 }
1290 }
1291 else if (Options::battleConfirmFireMode && (_currentAction.waypoints.empty() || pos != _currentAction.waypoints.front()))
1292 {
1293 _currentAction.waypoints.clear();
1294 _currentAction.waypoints.push_back(pos);
1295 getMap()->getWaypoints()->clear();
1296 getMap()->getWaypoints()->push_back(pos);
1297 }
1298 else
1299 {
1300 _currentAction.target = pos;
1301 getMap()->setCursorType(CT_NONE);
1302
1303 if (Options::battleConfirmFireMode)
1304 {
1305 _currentAction.waypoints.clear();
1306 getMap()->getWaypoints()->clear();
1307 }
1308
1309 _parentState->getGame()->getCursor()->setVisible(false);
1310 _currentAction.cameraPosition = getMap()->getCamera()->getMapOffset();
1311 _states.push_back(new ProjectileFlyBState(this, _currentAction));
1312 statePushFront(new UnitTurnBState(this, _currentAction)); // first of all turn towards the target
1313 }
1314 }
1315 else
1316 {
1317 _currentAction.actor = _save->getSelectedUnit();
1318 BattleUnit *unit = _save->selectUnit(pos);
1319 if (unit && unit != _save->getSelectedUnit() && (unit->getVisible() || _debugPlay))
1320 {
1321 // -= select unit =-
1322 if (unit->getFaction() == _save->getSide())
1323 {
1324 _save->setSelectedUnit(unit);
1325 _parentState->updateSoldierInfo();
1326 cancelCurrentAction();
1327 setupCursor();
1328 _currentAction.actor = unit;
1329 }
1330 }
1331 else if (playableUnitSelected())
1332 {
1333 bool modifierPressed = (SDL_GetModState() & KMOD_CTRL) != 0;
1334 if (bPreviewed &&
1335 (_currentAction.target != pos || (_save->getPathfinding()->isModifierUsed() != modifierPressed)))
1336 {
1337 _save->getPathfinding()->removePreview();
1338 }
1339 _currentAction.run = false;
1340 _currentAction.strafe = Options::strafe && modifierPressed && _save->getSelectedUnit()->getTurretType() == -1;
1341 if (_currentAction.strafe && _save->getTileEngine()->distance(_currentAction.actor->getPosition(), pos) > 1)
1342 {
1343 _currentAction.run = true;
1344 _currentAction.strafe = false;
1345 }
1346 _currentAction.target = pos;
1347 _save->getPathfinding()->calculate(_currentAction.actor, _currentAction.target);
1348 if (bPreviewed && !_save->getPathfinding()->previewPath() && _save->getPathfinding()->getStartDirection() != -1)
1349 {
1350 _save->getPathfinding()->removePreview();
1351 bPreviewed = false;
1352 }
1353
1354 if (!bPreviewed && _save->getPathfinding()->getStartDirection() != -1)
1355 {
1356 // -= start walking =-
1357 getMap()->setCursorType(CT_NONE);
1358 _parentState->getGame()->getCursor()->setVisible(false);
1359 statePushBack(new UnitWalkBState(this, _currentAction));
1360 }
1361 }
1362 }
1363 }
1364
1365 /**
1366 * Activates secondary action (right click).
1367 * @param pos Position on the map.
1368 */
secondaryAction(const Position & pos)1369 void BattlescapeGame::secondaryAction(const Position &pos)
1370 {
1371 // -= turn to or open door =-
1372 _currentAction.target = pos;
1373 _currentAction.actor = _save->getSelectedUnit();
1374 _currentAction.strafe = Options::strafe && (SDL_GetModState() & KMOD_CTRL) != 0 && _save->getSelectedUnit()->getTurretType() > -1;
1375 statePushBack(new UnitTurnBState(this, _currentAction));
1376 }
1377
1378 /**
1379 * Handler for the blaster launcher button.
1380 */
launchAction()1381 void BattlescapeGame::launchAction()
1382 {
1383 _parentState->showLaunchButton(false);
1384 getMap()->getWaypoints()->clear();
1385 _currentAction.target = _currentAction.waypoints.front();
1386 getMap()->setCursorType(CT_NONE);
1387 _parentState->getGame()->getCursor()->setVisible(false);
1388 _currentAction.cameraPosition = getMap()->getCamera()->getMapOffset();
1389 _states.push_back(new ProjectileFlyBState(this, _currentAction));
1390 statePushFront(new UnitTurnBState(this, _currentAction)); // first of all turn towards the target
1391 }
1392
1393 /**
1394 * Handler for the psi button.
1395 */
psiButtonAction()1396 void BattlescapeGame::psiButtonAction()
1397 {
1398 _currentAction.weapon = 0;
1399 _currentAction.targeting = true;
1400 _currentAction.type = BA_PANIC;
1401 _currentAction.TU = 25;
1402 setupCursor();
1403 }
1404
1405
1406 /**
1407 * Moves a unit up or down.
1408 * @param unit The unit.
1409 * @param dir Direction DIR_UP or DIR_DOWN.
1410 */
moveUpDown(BattleUnit * unit,int dir)1411 void BattlescapeGame::moveUpDown(BattleUnit *unit, int dir)
1412 {
1413 _currentAction.target = unit->getPosition();
1414 if (dir == Pathfinding::DIR_UP)
1415 {
1416 _currentAction.target.z++;
1417 }
1418 else
1419 {
1420 _currentAction.target.z--;
1421 }
1422 getMap()->setCursorType(CT_NONE);
1423 _parentState->getGame()->getCursor()->setVisible(false);
1424 if (_save->getSelectedUnit()->isKneeled())
1425 {
1426 kneel(_save->getSelectedUnit());
1427 }
1428 _save->getPathfinding()->calculate(_currentAction.actor, _currentAction.target);
1429 statePushBack(new UnitWalkBState(this, _currentAction));
1430 }
1431
1432 /**
1433 * Requests the end of the turn (waits for explosions etc to really end the turn).
1434 */
requestEndTurn()1435 void BattlescapeGame::requestEndTurn()
1436 {
1437 cancelCurrentAction();
1438 if (!_endTurnRequested)
1439 {
1440 _endTurnRequested = true;
1441 statePushBack(0);
1442 }
1443 }
1444
1445 /**
1446 * Sets the TU reserved type.
1447 * @param tur A battleactiontype.
1448 * @param player is this requested by the player?
1449 */
setTUReserved(BattleActionType tur,bool player)1450 void BattlescapeGame::setTUReserved(BattleActionType tur, bool player)
1451 {
1452 _tuReserved = tur;
1453 if (player)
1454 {
1455 _playerTUReserved = tur;
1456 _save->setTUReserved(tur);
1457 }
1458 }
1459
1460 /**
1461 * Drops an item to the floor and affects it with gravity.
1462 * @param position Position to spawn the item.
1463 * @param item Pointer to the item.
1464 * @param newItem Bool whether this is a new item.
1465 * @param removeItem Bool whether to remove the item from the owner.
1466 */
dropItem(const Position & position,BattleItem * item,bool newItem,bool removeItem)1467 void BattlescapeGame::dropItem(const Position &position, BattleItem *item, bool newItem, bool removeItem)
1468 {
1469 Position p = position;
1470
1471 // don't spawn anything outside of bounds
1472 if (_save->getTile(p) == 0)
1473 return;
1474
1475 // don't ever drop fixed items
1476 if (item->getRules()->isFixed())
1477 return;
1478
1479 _save->getTile(p)->addItem(item, getRuleset()->getInventory("STR_GROUND"));
1480
1481 if (item->getUnit())
1482 {
1483 item->getUnit()->setPosition(p);
1484 }
1485
1486 if(newItem)
1487 {
1488 _save->getItems()->push_back(item);
1489 }
1490 else if (_save->getSide() != FACTION_PLAYER)
1491 {
1492 item->setTurnFlag(true);
1493 }
1494
1495 if (removeItem)
1496 {
1497 item->moveToOwner(0);
1498 }
1499 else if (item->getRules()->getBattleType() != BT_GRENADE && item->getRules()->getBattleType() != BT_PROXIMITYGRENADE)
1500 {
1501 item->setOwner(0);
1502 }
1503
1504 getTileEngine()->applyGravity(_save->getTile(p));
1505
1506 if (item->getRules()->getBattleType() == BT_FLARE)
1507 {
1508 getTileEngine()->calculateTerrainLighting();
1509 getTileEngine()->calculateFOV(position);
1510 }
1511
1512 }
1513
1514 /**
1515 * Converts a unit into a unit of another type.
1516 * @param unit The unit to convert.
1517 * @param newType The type of unit to convert to.
1518 * @return Pointer to the new unit.
1519 */
convertUnit(BattleUnit * unit,std::string newType)1520 BattleUnit *BattlescapeGame::convertUnit(BattleUnit *unit, std::string newType)
1521 {
1522 getSave()->getBattleState()->showPsiButton(false);
1523 // in case the unit was unconscious
1524 getSave()->removeUnconsciousBodyItem(unit);
1525
1526 unit->instaKill();
1527
1528 if (Options::battleNotifyDeath && unit->getFaction() == FACTION_PLAYER && unit->getOriginalFaction() == FACTION_PLAYER)
1529 {
1530 _parentState->getGame()->pushState(new InfoboxState(_parentState->getGame(), _parentState->getGame()->getLanguage()->getString("STR_HAS_BEEN_KILLED", unit->getGender()).arg(unit->getName(_parentState->getGame()->getLanguage()))));
1531 }
1532
1533 for (std::vector<BattleItem*>::iterator i = unit->getInventory()->begin(); i != unit->getInventory()->end(); ++i)
1534 {
1535 dropItem(unit->getPosition(), (*i));
1536 (*i)->setOwner(0);
1537 }
1538
1539 unit->getInventory()->clear();
1540
1541 // remove unit-tile link
1542 unit->setTile(0);
1543
1544 getSave()->getTile(unit->getPosition())->setUnit(0);
1545 std::ostringstream newArmor;
1546 newArmor << getRuleset()->getUnit(newType)->getArmor();
1547 std::string terroristWeapon = getRuleset()->getUnit(newType)->getRace().substr(4);
1548 terroristWeapon += "_WEAPON";
1549 RuleItem *newItem = getRuleset()->getItem(terroristWeapon);
1550 int difficulty = (int)(_parentState->getGame()->getSavedGame()->getDifficulty());
1551
1552 BattleUnit *newUnit = new BattleUnit(getRuleset()->getUnit(newType), FACTION_HOSTILE, _save->getUnits()->back()->getId() + 1, getRuleset()->getArmor(newArmor.str()), difficulty);
1553
1554 if (!difficulty)
1555 {
1556 newUnit->halveArmor();
1557 }
1558
1559 getSave()->getTile(unit->getPosition())->setUnit(newUnit, _save->getTile(unit->getPosition() + Position(0,0,-1)));
1560 newUnit->setPosition(unit->getPosition());
1561 newUnit->setDirection(3);
1562 newUnit->setCache(0);
1563 newUnit->setTimeUnits(0);
1564 getSave()->getUnits()->push_back(newUnit);
1565 getMap()->cacheUnit(newUnit);
1566 newUnit->setAIState(new AlienBAIState(getSave(), newUnit, 0));
1567 BattleItem *bi = new BattleItem(newItem, getSave()->getCurrentItemId());
1568 bi->moveToOwner(newUnit);
1569 bi->setSlot(getRuleset()->getInventory("STR_RIGHT_HAND"));
1570 getSave()->getItems()->push_back(bi);
1571 getTileEngine()->calculateFOV(newUnit->getPosition());
1572 getTileEngine()->applyGravity(newUnit->getTile());
1573 //newUnit->getCurrentAIState()->think();
1574 return newUnit;
1575
1576 }
1577
1578 /**
1579 * Gets the map.
1580 * @return map.
1581 */
getMap()1582 Map *BattlescapeGame::getMap()
1583 {
1584 return _parentState->getMap();
1585 }
1586 /**
1587 * Gets the save.
1588 * @return save.
1589 */
getSave()1590 SavedBattleGame *BattlescapeGame::getSave()
1591 {
1592 return _save;
1593 }
1594 /**
1595 * Gets the tilengine.
1596 * @return tilengine.
1597 */
getTileEngine()1598 TileEngine *BattlescapeGame::getTileEngine()
1599 {
1600 return _save->getTileEngine();
1601 }
1602 /**
1603 * Gets the pathfinding.
1604 * @return pathfinding.
1605 */
getPathfinding()1606 Pathfinding *BattlescapeGame::getPathfinding()
1607 {
1608 return _save->getPathfinding();
1609 }
1610 /**
1611 * Gets the resourcepack.
1612 * @return resourcepack.
1613 */
getResourcePack()1614 ResourcePack *BattlescapeGame::getResourcePack()
1615 {
1616 return _parentState->getGame()->getResourcePack();
1617 }
1618 /**
1619 * Gets the ruleset.
1620 * @return ruleset.
1621 */
getRuleset() const1622 const Ruleset *BattlescapeGame::getRuleset() const
1623 {
1624 return _parentState->getGame()->getRuleset();
1625 }
1626
1627
1628 /**
1629 * Tries to find an item and pick it up if possible.
1630 */
findItem(BattleAction * action)1631 void BattlescapeGame::findItem(BattleAction *action)
1632 {
1633 // terrorists don't have hands.
1634 if (action->actor->getRankString() != "STR_LIVE_TERRORIST")
1635 {
1636 // pick the best available item
1637 BattleItem *targetItem = surveyItems(action);
1638 // make sure it's worth taking
1639 if (targetItem && worthTaking(targetItem, action))
1640 {
1641 // if we're already standing on it...
1642 if (targetItem->getTile()->getPosition() == action->actor->getPosition())
1643 {
1644 // try to pick it up
1645 if (takeItemFromGround(targetItem, action) == 0)
1646 {
1647 // if it isn't loaded or it is ammo
1648 if (!targetItem->getAmmoItem())
1649 {
1650 // try to load our weapon
1651 action->actor->checkAmmo();
1652 }
1653 }
1654 }
1655 else if (!targetItem->getTile()->getUnit() || targetItem->getTile()->getUnit()->isOut())
1656 {
1657 // if we're not standing on it, we should try to get to it.
1658 action->target = targetItem->getTile()->getPosition();
1659 action->type = BA_WALK;
1660 }
1661 }
1662 }
1663 }
1664
1665
1666 /**
1667 * Searches through items on the map that were dropped on an alien turn, then picks the most "attractive" one.
1668 * @param action A pointer to the action being performed.
1669 * @return The item to attempt to take.
1670 */
surveyItems(BattleAction * action)1671 BattleItem *BattlescapeGame::surveyItems(BattleAction *action)
1672 {
1673 std::vector<BattleItem*> droppedItems;
1674
1675 // first fill a vector with items on the ground that were dropped on the alien turn, and have an attraction value.
1676 for (std::vector<BattleItem*>::iterator i = _save->getItems()->begin(); i != _save->getItems()->end(); ++i)
1677 {
1678 if ((*i)->getSlot() && (*i)->getSlot()->getId() == "STR_GROUND" && (*i)->getTile() && (*i)->getTurnFlag() && (*i)->getRules()->getAttraction())
1679 {
1680 droppedItems.push_back(*i);
1681 }
1682 }
1683
1684 BattleItem *targetItem = 0;
1685 int maxWorth = 0;
1686
1687 // now select the most suitable candidate depending on attractiveness and distance
1688 // (are we still talking about items?)
1689 for (std::vector<BattleItem*>::iterator i = droppedItems.begin(); i != droppedItems.end(); ++i)
1690 {
1691 int currentWorth = (*i)->getRules()->getAttraction() / ((_save->getTileEngine()->distance(action->actor->getPosition(), (*i)->getTile()->getPosition()) * 2)+1);
1692 if (currentWorth > maxWorth)
1693 {
1694 maxWorth = currentWorth;
1695 targetItem = *i;
1696 }
1697 }
1698
1699 return targetItem;
1700 }
1701
1702
1703 /**
1704 * Assesses whether this item is worth trying to pick up, taking into account how many units we see,
1705 * whether or not the Weapon has ammo, and if we have ammo FOR it,
1706 * or, if it's ammo, checks if we have the weapon to go with it,
1707 * assesses the attraction value of the item and compares it with the distance to the object,
1708 * then returns false anyway.
1709 * @param item The item to attempt to take.
1710 * @param action A pointer to the action being performed.
1711 * @return false.
1712 */
worthTaking(BattleItem * item,BattleAction * action)1713 bool BattlescapeGame::worthTaking(BattleItem* item, BattleAction *action)
1714 {
1715 int worthToTake = 0;
1716
1717 // don't even think about making a move for that gun if you can see a target, for some reason
1718 // (maybe this should check for enemies spotting the tile the item is on?)
1719 if (action->actor->getVisibleUnits()->empty())
1720 {
1721 // retrieve an insignificantly low value from the ruleset.
1722 worthToTake = item->getRules()->getAttraction();
1723
1724 // it's always going to be worth while to try and take a blaster launcher, apparently
1725 if (!item->getRules()->isWaypoint() && item->getRules()->getBattleType() != BT_AMMO)
1726 {
1727 // we only want weapons that HAVE ammo, or weapons that we have ammo FOR
1728 bool ammoFound = true;
1729 if (!item->getAmmoItem())
1730 {
1731 ammoFound = false;
1732 for (std::vector<BattleItem*>::iterator i = action->actor->getInventory()->begin(); i != action->actor->getInventory()->end() && !ammoFound; ++i)
1733 {
1734 if ((*i)->getRules()->getBattleType() == BT_AMMO)
1735 {
1736 for (std::vector<std::string>::iterator j = item->getRules()->getCompatibleAmmo()->begin(); j != item->getRules()->getCompatibleAmmo()->end() && !ammoFound; ++j)
1737 {
1738 if ((*i)->getRules()->getName() == *j)
1739 {
1740 ammoFound = true;
1741 break;
1742 }
1743 }
1744 }
1745 }
1746 }
1747 if (!ammoFound)
1748 {
1749 return false;
1750 }
1751 }
1752
1753 if ( item->getRules()->getBattleType() == BT_AMMO)
1754 {
1755 // similar to the above, but this time we're checking if the ammo is suitable for a weapon we have.
1756 bool weaponFound = false;
1757 for (std::vector<BattleItem*>::iterator i = action->actor->getInventory()->begin(); i != action->actor->getInventory()->end() && !weaponFound; ++i)
1758 {
1759 if ((*i)->getRules()->getBattleType() == BT_FIREARM)
1760 {
1761 for (std::vector<std::string>::iterator j = (*i)->getRules()->getCompatibleAmmo()->begin(); j != (*i)->getRules()->getCompatibleAmmo()->end() && !weaponFound; ++j)
1762 {
1763 if ((*i)->getRules()->getName() == *j)
1764 {
1765 weaponFound = true;
1766 break;
1767 }
1768 }
1769 }
1770 }
1771 if (!weaponFound)
1772 {
1773 return false;
1774 }
1775 }
1776 }
1777
1778 if (worthToTake)
1779 {
1780 // use bad logic to determine if we'll have room for the item
1781 int freeSlots = 25;
1782 for (std::vector<BattleItem*>::iterator i = action->actor->getInventory()->begin(); i != action->actor->getInventory()->end(); ++i)
1783 {
1784 freeSlots -= (*i)->getRules()->getInventoryHeight() * (*i)->getRules()->getInventoryWidth();
1785 }
1786 int size = item->getRules()->getInventoryHeight() * item->getRules()->getInventoryWidth();
1787 if (freeSlots < size)
1788 {
1789 return false;
1790 }
1791 }
1792
1793 // return false for any item that we aren't standing directly on top of with an attraction value less than 6 (aka always)
1794 return (worthToTake - (_save->getTileEngine()->distance(action->actor->getPosition(), item->getTile()->getPosition())*2)) > 5;
1795 }
1796
1797
1798 /**
1799 * Picks the item up from the ground.
1800 *
1801 * At this point we've decided it's worth our while to grab this item, so we try to do just that.
1802 * First we check to make sure we have time units, then that we have space (using horrifying logic)
1803 * then we attempt to actually recover the item.
1804 * @param item The item to attempt to take.
1805 * @param action A pointer to the action being performed.
1806 * @return 0 if successful, 1 for no TUs, 2 for not enough room, 3 for "won't fit" and -1 for "something went horribly wrong".
1807 */
takeItemFromGround(BattleItem * item,BattleAction * action)1808 int BattlescapeGame::takeItemFromGround(BattleItem* item, BattleAction *action)
1809 {
1810 const int unhandledError = -1;
1811 const int success = 0;
1812 const int notEnoughTimeUnits = 1;
1813 const int notEnoughSpace = 2;
1814 const int couldNotFit = 3;
1815 int freeSlots = 25;
1816
1817 // make sure we have time units
1818 if (action->actor->getTimeUnits() < 6)
1819 {
1820 return notEnoughTimeUnits;
1821 }
1822 else
1823 {
1824 // check to make sure we have enough space by checking all the sizes of items in our inventory
1825 for (std::vector<BattleItem*>::iterator i = action->actor->getInventory()->begin(); i != action->actor->getInventory()->end(); ++i)
1826 {
1827 freeSlots -= (*i)->getRules()->getInventoryHeight() * (*i)->getRules()->getInventoryWidth();
1828 }
1829 if (freeSlots < item->getRules()->getInventoryHeight() * item->getRules()->getInventoryWidth())
1830 {
1831 return notEnoughSpace;
1832 }
1833 else
1834 {
1835 // check that the item will fit in our inventory, and if so, take it
1836 if (takeItem(item, action))
1837 {
1838 action->actor->spendTimeUnits(6);
1839 item->getTile()->removeItem(item);
1840 return success;
1841 }
1842 else
1843 {
1844 return couldNotFit;
1845 }
1846 }
1847 }
1848 // shouldn't ever end up here
1849 return unhandledError;
1850 }
1851
1852
1853 /**
1854 * Tries to fit an item into the unit's inventory, return false if you can't.
1855 * @param item The item to attempt to take.
1856 * @param action A pointer to the action being performed.
1857 * @return Whether or not the item was successfully retrieved.
1858 */
takeItem(BattleItem * item,BattleAction * action)1859 bool BattlescapeGame::takeItem(BattleItem* item, BattleAction *action)
1860 {
1861 bool placed = false;
1862 Ruleset* rules = _parentState->getGame()->getRuleset();
1863 switch (item->getRules()->getBattleType())
1864 {
1865 case BT_AMMO:
1866 // find equipped weapons that can be loaded with this ammo
1867 if (action->actor->getItem("STR_RIGHT_HAND") && action->actor->getItem("STR_RIGHT_HAND")->getAmmoItem() == 0)
1868 {
1869 if (action->actor->getItem("STR_RIGHT_HAND")->setAmmoItem(item) == 0)
1870 {
1871 placed = true;
1872 }
1873 }
1874 else
1875 {
1876 for (int i = 0; i != 4; ++i)
1877 {
1878 if (!action->actor->getItem("STR_BELT", i))
1879 {
1880 item->moveToOwner(action->actor);
1881 item->setSlot(rules->getInventory("STR_BELT"));
1882 item->setSlotX(i);
1883 placed = true;
1884 break;
1885 }
1886 }
1887 }
1888 break;
1889 case BT_GRENADE:
1890 case BT_PROXIMITYGRENADE:
1891 for (int i = 0; i != 4; ++i)
1892 {
1893 if (!action->actor->getItem("STR_BELT", i))
1894 {
1895 item->moveToOwner(action->actor);
1896 item->setSlot(rules->getInventory("STR_BELT"));
1897 item->setSlotX(i);
1898 placed = true;
1899 break;
1900 }
1901 }
1902 break;
1903 case BT_FIREARM:
1904 case BT_MELEE:
1905 if (!action->actor->getItem("STR_RIGHT_HAND"))
1906 {
1907 item->moveToOwner(action->actor);
1908 item->setSlot(rules->getInventory("STR_RIGHT_HAND"));
1909 placed = true;
1910 }
1911 break;
1912 case BT_MEDIKIT:
1913 case BT_SCANNER:
1914 if (!action->actor->getItem("STR_BACK_PACK"))
1915 {
1916 item->moveToOwner(action->actor);
1917 item->setSlot(rules->getInventory("STR_BACK_PACK"));
1918 placed = true;
1919 }
1920 break;
1921 case BT_MINDPROBE:
1922 if (!action->actor->getItem("STR_LEFT_HAND"))
1923 {
1924 item->moveToOwner(action->actor);
1925 item->setSlot(rules->getInventory("STR_LEFT_HAND"));
1926 placed = true;
1927 }
1928 break;
1929 default: break;
1930 }
1931 return placed;
1932 }
1933
1934 /**
1935 * Returns the action type that is reserved.
1936 * @return The type of action that is reserved.
1937 */
getReservedAction()1938 BattleActionType BattlescapeGame::getReservedAction()
1939 {
1940 return _tuReserved;
1941 }
1942
1943 /**
1944 * Tallies the living units in the game and, if required, converts units into their spawn unit.
1945 * @param &liveAliens The integer in which to store the live alien tally.
1946 * @param &liveSoldiers The integer in which to store the live XCom tally.
1947 * @param convert Should we convert infected units?
1948 */
tallyUnits(int & liveAliens,int & liveSoldiers,bool convert)1949 void BattlescapeGame::tallyUnits(int &liveAliens, int &liveSoldiers, bool convert)
1950 {
1951 liveSoldiers = 0;
1952 liveAliens = 0;
1953
1954 if (convert)
1955 {
1956 for (std::vector<BattleUnit*>::iterator j = _save->getUnits()->begin(); j != _save->getUnits()->end(); ++j)
1957 {
1958 if ((*j)->getHealth() > 0 && (*j)->getSpecialAbility() == SPECAB_RESPAWN)
1959 {
1960 (*j)->setSpecialAbility(SPECAB_NONE);
1961 convertUnit((*j), (*j)->getSpawnUnit());
1962 j = _save->getUnits()->begin();
1963 }
1964 }
1965 }
1966
1967 for (std::vector<BattleUnit*>::iterator j = _save->getUnits()->begin(); j != _save->getUnits()->end(); ++j)
1968 {
1969 if (!(*j)->isOut())
1970 {
1971 if ((*j)->getOriginalFaction() == FACTION_HOSTILE)
1972 {
1973 if (!Options::allowPsionicCapture || (*j)->getFaction() != FACTION_PLAYER)
1974 {
1975 liveAliens++;
1976 }
1977 }
1978 else if ((*j)->getOriginalFaction() == FACTION_PLAYER)
1979 {
1980 if ((*j)->getFaction() == FACTION_PLAYER)
1981 {
1982 liveSoldiers++;
1983 }
1984 else
1985 {
1986 liveAliens++;
1987 }
1988 }
1989 }
1990 }
1991 }
1992
1993 /**
1994 * Sets the kneel reservation setting.
1995 * @param reserved Should we reserve an extra 4 TUs to kneel?
1996 */
setKneelReserved(bool reserved)1997 void BattlescapeGame::setKneelReserved(bool reserved)
1998 {
1999 _kneelReserved = reserved;
2000 _save->setKneelReserved(reserved);
2001 }
2002
2003 /**
2004 * Gets the kneel reservation setting.
2005 * @return Kneel reservation setting.
2006 */
getKneelReserved()2007 bool BattlescapeGame::getKneelReserved()
2008 {
2009 if (_save->getSelectedUnit() && _save->getSelectedUnit()->getGeoscapeSoldier())
2010 {
2011 return _kneelReserved;
2012 }
2013 return false;
2014 }
2015
2016 /**
2017 * Checks if a unit has moved next to a proximity grenade.
2018 * Checks one tile around the unit in every direction.
2019 * For a large unit we check every tile it occupies.
2020 * @param unit Pointer to a unit.
2021 * @return True if a proximity grenade was triggered.
2022 */
checkForProximityGrenades(BattleUnit * unit)2023 bool BattlescapeGame::checkForProximityGrenades(BattleUnit *unit)
2024 {
2025 int size = unit->getArmor()->getSize() - 1;
2026 for (int x = size; x >= 0; x--)
2027 {
2028 for (int y = size; y >= 0; y--)
2029 {
2030 for (int tx = -1; tx < 2; tx++)
2031 {
2032 for (int ty = -1; ty < 2; ty++)
2033 {
2034 Tile *t = _save->getTile(unit->getPosition() + Position(x,y,0) + Position(tx,ty,0));
2035 if (t)
2036 {
2037 for (std::vector<BattleItem*>::iterator i = t->getInventory()->begin(); i != t->getInventory()->end(); ++i)
2038 {
2039 if ((*i)->getRules()->getBattleType() == BT_PROXIMITYGRENADE && (*i)->getFuseTimer() == 0)
2040 {
2041 Position p;
2042 p.x = t->getPosition().x*16 + 8;
2043 p.y = t->getPosition().y*16 + 8;
2044 p.z = t->getPosition().z*24 + t->getTerrainLevel();
2045 statePushNext(new ExplosionBState(this, p, (*i), (*i)->getPreviousOwner()));
2046 getSave()->removeItem(*i);
2047 unit->setCache(0);
2048 getMap()->cacheUnit(unit);
2049 return true;
2050 }
2051 }
2052 }
2053 }
2054 }
2055 }
2056 }
2057 return false;
2058 }
2059
cleanupDeleted()2060 void BattlescapeGame::cleanupDeleted()
2061 {
2062 for (std::list<BattleState*>::iterator i = _deleted.begin(); i != _deleted.end(); ++i)
2063 {
2064 delete *i;
2065 }
2066 _deleted.clear();
2067 }
2068
2069 }
2070