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