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 
20 #include "ExplosionBState.h"
21 #include "BattlescapeState.h"
22 #include "Explosion.h"
23 #include "TileEngine.h"
24 #include "UnitDieBState.h"
25 #include "Map.h"
26 #include "Camera.h"
27 #include "../Engine/Game.h"
28 #include "../Savegame/BattleUnit.h"
29 #include "../Savegame/BattleItem.h"
30 #include "../Savegame/SavedGame.h"
31 #include "../Savegame/SavedBattleGame.h"
32 #include "../Savegame/Tile.h"
33 #include "../Resource/ResourcePack.h"
34 #include "../Engine/Sound.h"
35 #include "../Ruleset/RuleItem.h"
36 #include "../Ruleset/Ruleset.h"
37 #include "../Ruleset/Armor.h"
38 #include "../Engine/RNG.h"
39 
40 namespace OpenXcom
41 {
42 
43 /**
44  * Sets up an ExplosionBState.
45  * @param parent Pointer to the BattleScape.
46  * @param center Center position in voxelspace.
47  * @param item Item involved in the explosion (eg grenade).
48  * @param unit Unit involved in the explosion (eg unit throwing the grenade).
49  * @param tile Tile the explosion is on.
50  * @param lowerWeapon Whether the unit causing this explosion should now lower their weapon.
51  */
ExplosionBState(BattlescapeGame * parent,Position center,BattleItem * item,BattleUnit * unit,Tile * tile,bool lowerWeapon)52 ExplosionBState::ExplosionBState(BattlescapeGame *parent, Position center, BattleItem *item, BattleUnit *unit, Tile *tile, bool lowerWeapon) : BattleState(parent), _unit(unit), _center(center), _item(item), _tile(tile), _power(0), _areaOfEffect(false), _lowerWeapon(lowerWeapon), _pistolWhip(false)
53 {
54 
55 }
56 
57 /**
58  * Deletes the ExplosionBState.
59  */
~ExplosionBState()60 ExplosionBState::~ExplosionBState()
61 {
62 
63 }
64 
65 /**
66  * Initializes the explosion.
67  * The animation and sound starts here.
68  * If the animation is finished, the actual effect takes place.
69  */
init()70 void ExplosionBState::init()
71 {
72 	if (_item)
73 	{
74 		_power = _item->getRules()->getPower();
75 		_pistolWhip = (_item->getRules()->getBattleType() != BT_MELEE &&
76 			_parent->getCurrentAction()->type == BA_HIT);
77 		if (_pistolWhip)
78 		{
79 			_power = _item->getRules()->getMeleePower();
80 		}
81 		// since melee aliens don't use a conventional weapon type, we use their strength instead.
82 		if (_item->getRules()->isStrengthApplied())
83 		{
84 			_power += _unit->getStats()->strength;
85 		}
86 
87 		_areaOfEffect = _item->getRules()->getBattleType() != BT_MELEE &&
88 						_item->getRules()->getExplosionRadius() != 0 &&
89 						!_pistolWhip;
90 	}
91 	else if (_tile)
92 	{
93 		_power = _tile->getExplosive();
94 		_areaOfEffect = true;
95 	}
96 	else if (_unit && _unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH)
97 	{
98 		_power = _parent->getRuleset()->getItem(_unit->getArmor()->getCorpseGeoscape())->getPower();
99 		_areaOfEffect = true;
100 	}
101 	else
102 	{
103 		_power = 120;
104 		_areaOfEffect = true;
105 	}
106 
107 	Tile *t = _parent->getSave()->getTile(Position(_center.x/16, _center.y/16, _center.z/24));
108 	if (_areaOfEffect)
109 	{
110 		if (_power)
111 		{
112 			int frame = 0;
113 			int counter = std::max(1, (_power/5) / 5);
114 			for (int i = 0; i < _power/5; i++)
115 			{
116 				int X = RNG::generate(-_power/2,_power/2);
117 				int Y = RNG::generate(-_power/2,_power/2);
118 				Position p = _center;
119 				p.x += X; p.y += Y;
120 				Explosion *explosion = new Explosion(p, frame, true);
121 				// add the explosion on the map
122 				_parent->getMap()->getExplosions()->push_back(explosion);
123 				if (i > 0 && i % counter == 0)
124 				{
125 					--frame;
126 				}
127 			}
128 			_parent->setStateInterval(BattlescapeState::DEFAULT_ANIM_SPEED/2);
129 			// explosion sound
130 			if (_power <= 80)
131 				_parent->getResourcePack()->getSound("BATTLE.CAT", 2)->play();
132 			else
133 				_parent->getResourcePack()->getSound("BATTLE.CAT", 5)->play();
134 
135 			_parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false);
136 		}
137 		else
138 		{
139 			_parent->popState();
140 		}
141 	}
142 	else
143 	// create a bullet hit
144 	{
145 		_parent->setStateInterval(std::max(1, ((BattlescapeState::DEFAULT_ANIM_SPEED/2) - (10 * _item->getRules()->getExplosionSpeed()))));
146 		bool hit = _pistolWhip || _item->getRules()->getBattleType() == BT_MELEE || _item->getRules()->getBattleType() == BT_PSIAMP;
147 		int anim = _item->getRules()->getHitAnimation();
148 		int sound = _item->getRules()->getHitSound();
149 		if (hit)
150 		{
151 			anim = _item->getRules()->getMeleeAnimation();
152 		}
153 		if (sound != -1)
154 		{
155 			// bullet hit sound
156 			_parent->getResourcePack()->getSound("BATTLE.CAT", sound)->play();
157 		}
158 		Explosion *explosion = new Explosion(_center, anim, false, hit);
159 		_parent->getMap()->getExplosions()->push_back(explosion);
160 		_parent->getMap()->getCamera()->setViewLevel(_center.z / 24);
161 
162 		BattleUnit *target = t->getUnit();
163 		if (hit && _parent->getSave()->getSide() == FACTION_HOSTILE && target && target->getFaction() == FACTION_PLAYER)
164 		{
165 			_parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false);
166 		}
167 	}
168 }
169 
170 /**
171  * Animates explosion sprites. If their animation is finished remove them from the list.
172  * If the list is empty, this state is finished and the actual calculations take place.
173  */
think()174 void ExplosionBState::think()
175 {
176 	for (std::list<Explosion*>::iterator i = _parent->getMap()->getExplosions()->begin(); i != _parent->getMap()->getExplosions()->end();)
177 	{
178 		if(!(*i)->animate())
179 		{
180 			delete (*i);
181 			i = _parent->getMap()->getExplosions()->erase(i);
182 			if (_parent->getMap()->getExplosions()->empty())
183 			{
184 				explode();
185 				return;
186 			}
187 		}
188 		else
189 		{
190 			++i;
191 		}
192 	}
193 }
194 
195 /**
196  * Explosions cannot be cancelled.
197  */
cancel()198 void ExplosionBState::cancel()
199 {
200 }
201 
202 /**
203  * Calculates the effects of the explosion.
204  */
explode()205 void ExplosionBState::explode()
206 {
207 	bool terrainExplosion = false;
208 	SavedBattleGame *save = _parent->getSave();
209 	// last minute adjustment: determine if we actually
210 	if (_parent->getCurrentAction()->type == BA_HIT || _parent->getCurrentAction()->type == BA_STUN)
211 	{
212 		save->getBattleGame()->getCurrentAction()->type = BA_NONE;
213 		BattleUnit *targetUnit = save->getTile(_center / Position(16, 16, 24))->getUnit();
214 		if (_unit && !_unit->isOut())
215 		{
216 			_unit->aim(false);
217 			_unit->setCache(0);
218 		}
219 		if (!RNG::percent(_unit->getFiringAccuracy(BA_HIT, _item)))
220 		{
221 			_parent->getMap()->cacheUnits();
222 			_parent->popState();
223 			return;
224 		}
225 		else if (targetUnit && targetUnit->getOriginalFaction() == FACTION_HOSTILE &&
226 				_unit->getOriginalFaction() == FACTION_PLAYER)
227 		{
228 			_unit->addMeleeExp();
229 		}
230 		if (_item->getRules()->getMeleeHitSound() != -1)
231 		{
232 			_parent->getResourcePack()->getSound("BATTLE.CAT", _item->getRules()->getMeleeHitSound())->play();
233 		}
234 	}
235 	// after the animation is done, the real explosion/hit takes place
236 	if (_item)
237 	{
238 		if (!_unit && _item->getPreviousOwner())
239 		{
240 			_unit = _item->getPreviousOwner();
241 		}
242 
243 		if (_areaOfEffect)
244 		{
245 			save->getTileEngine()->explode(_center, _power, _item->getRules()->getDamageType(), _item->getRules()->getExplosionRadius(), _unit);
246 		}
247 		else
248 		{
249 			ItemDamageType type = _item->getRules()->getDamageType();
250 			if (_pistolWhip)
251 			{
252 				type = DT_STUN;
253 			}
254 			BattleUnit *victim = save->getTileEngine()->hit(_center, _power, type, _unit);
255 			// check if this unit turns others into zombies
256 			if (!_item->getRules()->getZombieUnit().empty()
257 				&& victim
258 				&& victim->getArmor()->getSize() == 1
259 				&& victim->getSpawnUnit().empty()
260 				&& victim->getOriginalFaction() != FACTION_HOSTILE)
261 			{
262 				// converts the victim to a zombie on death
263 				victim->setSpecialAbility(SPECAB_RESPAWN);
264 				victim->setSpawnUnit(_item->getRules()->getZombieUnit());
265 			}
266 		}
267 	}
268 	if (_tile)
269 	{
270 		save->getTileEngine()->explode(_center, _power, DT_HE, _power/10);
271 		terrainExplosion = true;
272 	}
273 	if (!_tile && !_item)
274 	{
275 		int radius = 6;
276 		// explosion not caused by terrain or an item, must be by a unit (cyberdisc)
277 		if (_unit && _unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH)
278 		{
279 			radius = _parent->getRuleset()->getItem(_unit->getArmor()->getCorpseGeoscape())->getExplosionRadius();
280 		}
281 		save->getTileEngine()->explode(_center, _power, DT_HE, radius);
282 		terrainExplosion = true;
283 	}
284 
285 	// now check for new casualties
286 	_parent->checkForCasualties(_item, _unit, false, terrainExplosion);
287 
288 	// if this explosion was caused by a unit shooting, now it's the time to put the gun down
289 	if (_unit && !_unit->isOut() && _lowerWeapon)
290 	{
291 		_unit->aim(false);
292 		_unit->setCache(0);
293 	}
294 	_parent->getMap()->cacheUnits();
295 	_parent->popState();
296 
297 	// check for terrain explosions
298 	Tile *t = save->getTileEngine()->checkForTerrainExplosions();
299 	if (t)
300 	{
301 		Position p = Position(t->getPosition().x * 16, t->getPosition().y * 16, t->getPosition().z * 24);
302 		p += Position(8,8,0);
303 		_parent->statePushFront(new ExplosionBState(_parent, p, 0, _unit, t));
304 	}
305 
306 	if (_item && (_item->getRules()->getBattleType() == BT_GRENADE || _item->getRules()->getBattleType() == BT_PROXIMITYGRENADE))
307 	{
308 		for (std::vector<BattleItem*>::iterator j = _parent->getSave()->getItems()->begin(); j != _parent->getSave()->getItems()->end(); ++j)
309 		{
310 			if (_item->getId() == (*j)->getId())
311 			{
312 				delete *j;
313 				_parent->getSave()->getItems()->erase(j);
314 				break;
315 			}
316 		}
317 	}
318 }
319 
320 }
321