/** * init sequence: * - check if shot is valid * - calculate base accuracy */ void ProjectileFlyBState::init() { if (_initialized) return; _initialized = true; BattleItem *weapon = _action.weapon; _projectileItem = 0; _autoshotCounter = 0; if (!weapon) // can't shoot without weapon return; if (!_parent->getSave()->getTile(_action.target)) // invalid target position return; if (_action.actor->getTimeUnits() < _action.TU && !_parent->dontSpendTUs()) { _action.result = "STR_NOT_ENOUGH_TIME_UNITS"; _parent->popState(); return; } _unit = _action.actor; _ammo = weapon->getAmmoItem(); if (_unit->isOut()) { // something went wrong - we can't shoot when dead or unconscious _parent->popState(); return; } // autoshot will default back to snapshot if it's not possible if (weapon->getRules()->getAccuracyAuto() == 0 && _action.type == BA_AUTOSHOT) _action.type = BA_SNAPSHOT; // snapshot defaults to "hit" if it's a melee weapon // (in case of reaction "shots" with a melee weapon) if (weapon->getRules()->getBattleType() == BT_MELEE && _action.type == BA_SNAPSHOT) _action.type = BA_HIT; switch (_action.type) { case BA_SNAPSHOT: case BA_AIMEDSHOT: case BA_AUTOSHOT: case BA_LAUNCH: if (_ammo == 0) { _action.result = "STR_NO_AMMUNITION_LOADED"; _parent->popState(); return; } if (_ammo->getAmmoQuantity() == 0) { _action.result = "STR_NO_ROUNDS_LEFT"; _parent->popState(); return; } break; case BA_THROW: if (!validThrowRange(&_action)) { // out of range _action.result = "STR_OUT_OF_RANGE"; _parent->popState(); return; } _projectileItem = weapon; break; case BA_HIT: if (!validMeleeRange(&_action)) { _action.result = "STR_THERE_IS_NO_ONE_THERE"; _parent->popState(); return; } break; case BA_PANIC: case BA_MINDCONTROL: _parent->statePushFront(new ExplosionBState(_parent, Position((_action.target.x*16)+8,(_action.target.y*16)+8,(_action.target.z*24)+10), weapon, _action.actor)); return; default: _parent->popState(); return; } if (createNewProjectile() == true) { BattleAction action; BattleUnit *potentialVictim = _parent->getSave()->getTile(_action.target)->getUnit(); if (potentialVictim && potentialVictim->getFaction() != _unit->getFaction()) { if (_parent->getSave()->getTileEngine()->checkReactionFire(_unit, &action, potentialVictim, false)) { _parent->statePushBack(new ProjectileFlyBState(_parent, action)); } } } }
/** * Initializes the explosion. * The animation and sound starts here. * If the animation is finished, the actual effect takes place. */ void ExplosionBState::init() { if (_item) { _power = _item->getRules()->getPower(); // this usually only applies to melee, but as a concession for modders i'll leave it here in case they wanna make bows or something. if (_item->getRules()->isStrengthApplied() && _unit) { _power += _unit->getBaseStats()->strength; } _areaOfEffect = _item->getRules()->getBattleType() != BT_MELEE && _item->getRules()->getExplosionRadius() != 0 && !_cosmetic; } else if (_tile) { _power = _tile->getExplosive(); _areaOfEffect = true; } else if (_unit && (_unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH || _unit->getSpecialAbility() == SPECAB_BURN_AND_EXPLODE)) { _power = _parent->getMod()->getItem(_unit->getArmor()->getCorpseGeoscape())->getPower(); _areaOfEffect = true; } else { _power = 120; _areaOfEffect = true; } Tile *t = _parent->getSave()->getTile(Position(_center.x/16, _center.y/16, _center.z/24)); if (_areaOfEffect) { if (_power) { int frame = Mod::EXPLOSION_OFFSET; if (_item) { frame = _item->getRules()->getHitAnimation(); } if (_parent->getDepth() > 0) { frame -= Explosion::EXPLODE_FRAMES; } int frameDelay = 0; int counter = std::max(1, (_power/5) / 5); _parent->getMap()->setBlastFlash(true); for (int i = 0; i < _power/5; i++) { int X = RNG::generate(-_power/2,_power/2); int Y = RNG::generate(-_power/2,_power/2); Position p = _center; p.x += X; p.y += Y; Explosion *explosion = new Explosion(p, frame, frameDelay, true); // add the explosion on the map _parent->getMap()->getExplosions()->push_back(explosion); if (i > 0 && i % counter == 0) { frameDelay++; } } _parent->setStateInterval(BattlescapeState::DEFAULT_ANIM_SPEED/2); // explosion sound if (_power <= 80) _parent->getMod()->getSoundByDepth(_parent->getDepth(), Mod::SMALL_EXPLOSION)->play(); else _parent->getMod()->getSoundByDepth(_parent->getDepth(), Mod::LARGE_EXPLOSION)->play(); _parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false); } else { _parent->popState(); } } else // create a bullet hit { _parent->setStateInterval(std::max(1, ((BattlescapeState::DEFAULT_ANIM_SPEED/2) - (10 * _item->getRules()->getExplosionSpeed())))); int anim = _item->getRules()->getHitAnimation(); int sound = _item->getRules()->getHitSound(); if (_cosmetic) // Play melee animation on hitting and psiing { anim = _item->getRules()->getMeleeAnimation(); } if (anim != -1) { Explosion *explosion = new Explosion(_center, anim, 0, false, _cosmetic); _parent->getMap()->getExplosions()->push_back(explosion); } _parent->getMap()->getCamera()->setViewLevel(_center.z / 24); BattleUnit *target = t->getUnit(); if (_cosmetic && _parent->getSave()->getSide() == FACTION_HOSTILE && target && target->getFaction() == FACTION_PLAYER) { _parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false); } if (sound != -1 && !_cosmetic) { // bullet hit sound _parent->getMod()->getSoundByDepth(_parent->getDepth(), sound)->play(-1, _parent->getMap()->getSoundAngle(_center / Position(16,16,24))); } } }
/** * Animates the projectile (move to the next point in it's trajectory). * If the animation is finished the projectile sprite is removed from the map. * And this state is finished. */ void ProjectileFlyBState::think() { /* TODO refactoring : store the projectile in this state, instead of getting it from the map each time? */ if (_parent->getMap()->getProjectile() == 0) { if (_action.type == BA_AUTOSHOT && _action.autoShotCounter < 3 && !_action.actor->isOut() && _ammo->getAmmoQuantity() != 0) { createNewProjectile(); } else { if (_action.cameraPosition.z != -1) { _parent->getMap()->getCamera()->setMapOffset(_action.cameraPosition); } if (_action.type != BA_PANIC && _action.type != BA_MINDCONTROL) { _parent->getTileEngine()->checkReactionFire(_unit); } _unit->abortTurn(); _parent->popState(); } } else { if(!_parent->getMap()->getProjectile()->move()) { // impact ! if (_action.type == BA_THROW) { Position pos = _parent->getMap()->getProjectile()->getPosition(-1); pos.x /= 16; pos.y /= 16; pos.z /= 24; BattleItem *item = _parent->getMap()->getProjectile()->getItem(); _parent->getResourcePack()->getSound("BATTLE.CAT", 38)->play(); if (Options::getBool("battleInstantGrenade") && item->getRules()->getBattleType() == BT_GRENADE && item->getExplodeTurn() != 0 && item->getExplodeTurn() <= _parent->getSave()->getTurn()) { // it's a hot grenade to explode immediately _parent->statePushFront(new ExplosionBState(_parent, _parent->getMap()->getProjectile()->getPosition(-1), item, _action.actor)); } else { _parent->dropItem(pos, item); } } else if (_action.type == BA_LAUNCH && _action.waypoints.size() > 1 && _projectileImpact == -1) { _origin = _action.waypoints.front(); _action.waypoints.pop_front(); _action.target = _action.waypoints.front(); // launch the next projectile in the waypoint cascade _parent->statePushBack(new ProjectileFlyBState(_parent, _action, _origin)); } else { if (_ammo && _action.type == BA_LAUNCH && _ammo->spendBullet() == false) { _parent->getSave()->removeItem(_ammo); _action.weapon->setAmmoItem(0); } if (_projectileImpact != 5) // out of map { int offset = 0; // explosions impact not inside the voxel but two steps back (projectiles generally move 2 voxels at a time) if (_ammo && ( _ammo->getRules()->getDamageType() == DT_HE || _ammo->getRules()->getDamageType() == DT_IN)) { offset = -2; } _parent->statePushFront(new ExplosionBState(_parent, _parent->getMap()->getProjectile()->getPosition(offset), _ammo, _action.actor, 0, (_action.type != BA_AUTOSHOT || _action.autoShotCounter == 3|| !_action.weapon->getAmmoItem()))); if (_projectileImpact == 4) { BattleUnit *victim = _parent->getSave()->getTile(_parent->getMap()->getProjectile()->getPosition(offset) / Position(16,16,24))->getUnit(); if (victim && !victim->isOut() && victim->getFaction() == FACTION_HOSTILE) { AggroBAIState *aggro = dynamic_cast<AggroBAIState*>(victim->getCurrentAIState()); if (aggro == 0) { aggro = new AggroBAIState(_parent->getSave(), victim); victim->setAIState(aggro); } aggro->setAggroTarget(_action.actor); } } } else if (_action.type != BA_AUTOSHOT || _action.autoShotCounter == 3 || !_action.weapon->getAmmoItem()) { _unit->aim(false); _parent->getMap()->cacheUnits(); } } delete _parent->getMap()->getProjectile(); _parent->getMap()->setProjectile(0); } } }
/** * applyAccuracy calculates the new target in voxel space, based on the given accuracy modifier. * @param origin Startposition of the trajectory. * @param target Endpoint of the trajectory. * @param accuracy Accuracy modifier. * @param targetTile Tile of target. Default = 0. */ void Projectile::applyAccuracy(const Position& origin, Position *target, double accuracy, bool keepRange, Tile *targetTile) { int xdiff = origin.x - target->x; int ydiff = origin.y - target->y; double realDistance = sqrt((double)(xdiff*xdiff)+(double)(ydiff*ydiff)); // maxRange is the maximum range a projectile shall ever travel in voxel space double maxRange = keepRange?realDistance:16*1000; // 1000 tiles maxRange = _action.type == BA_HIT?46:maxRange; // up to 2 tiles diagonally (as in the case of reaper v reaper) if (Options::getBool("battleRangeBasedAccuracy")) { double baseDeviation, accuracyPenalty; if (targetTile) { BattleUnit* targetUnit = targetTile->getUnit(); if (targetUnit && (targetUnit->getFaction() == FACTION_HOSTILE)) accuracyPenalty = 0.01 * targetTile->getShade(); // Shade can be from 0 to 15 else accuracyPenalty = 0.0; // Enemy units can see in the dark. // If unit is kneeled, then chance to hit them reduced on 5%. This is a compromise, because vertical deviation in 2 times less. if (targetUnit && targetUnit->isKneeled()) accuracyPenalty += 0.05; } else accuracyPenalty = 0.01 * _save->getGlobalShade(); // Shade can be from 0 (day) to 15 (night). baseDeviation = -0.15 + (_action.type == BA_AUTOSHOT? 0.28 : 0.26) / (accuracy - accuracyPenalty + 0.25); // 0.02 is the min angle deviation for best accuracy (+-3s = 0.02 radian). if (baseDeviation < 0.02) baseDeviation = 0.02; // the angle deviations are spread using a normal distribution for baseDeviation (+-3s with precision 99,7%) double dH = RNG::boxMuller(0.0, baseDeviation / 6.0); // horizontal miss in radian double dV = RNG::boxMuller(0.0, baseDeviation /(6.0 * 2)); double te = atan2(double(target->y - origin.y), double(target->x - origin.x)) + dH; double fi = atan2(double(target->z - origin.z), realDistance) + dV; double cos_fi = cos(fi); // It is a simple task - to hit in target width of 5-7 voxels. Good luck! target->x = (int)(origin.x + maxRange * cos(te) * cos_fi); target->y = (int)(origin.y + maxRange * sin(te) * cos_fi); target->z = (int)(origin.z + maxRange * sin(fi)); return; } // maxDeviation is the max angle deviation for accuracy 0% in degrees double maxDeviation = 2.5; // minDeviation is the min angle deviation for accuracy 100% in degrees double minDeviation = 0.4; double dRot, dTilt; double rotation, tilt; double baseDeviation = (maxDeviation - (maxDeviation * accuracy)) + minDeviation; // the angle deviations are spread using a normal distribution between 0 and baseDeviation // check if we hit if (RNG::generate(0.0, 1.0) < accuracy) { // we hit, so no deviation dRot = 0; dTilt = 0; } else { dRot = RNG::boxMuller(0, baseDeviation); dTilt = RNG::boxMuller(0, baseDeviation / 2.0); // tilt deviation is halved } rotation = atan2(double(target->y - origin.y), double(target->x - origin.x)) * 180 / M_PI; tilt = atan2(double(target->z - origin.z), sqrt(double(target->x - origin.x)*double(target->x - origin.x)+double(target->y - origin.y)*double(target->y - origin.y))) * 180 / M_PI; // add deviations rotation += dRot; tilt += dTilt; // calculate new target // this new target can be very far out of the map, but we don't care about that right now double cos_fi = cos(tilt * M_PI / 180.0); double sin_fi = sin(tilt * M_PI / 180.0); double cos_te = cos(rotation * M_PI / 180.0); double sin_te = sin(rotation * M_PI / 180.0); target->x = (int)(origin.x + maxRange * cos_te * cos_fi); target->y = (int)(origin.y + maxRange * sin_te * cos_fi); target->z = (int)(origin.z + maxRange * sin_fi); }
/** * Processes any clicks on the map to * command units. * @param action Pointer to an action. */ void BattlescapeState::mapClick(Action *action) { // right-click aborts walking state if (action->getDetails()->button.button == SDL_BUTTON_RIGHT) { if (_popup) { hidePopup(); return; } if (_states.empty()) { if (_targeting) { _targeting = false; _map->setCursorType(CT_NORMAL); _game->getCursor()->setVisible(true); _selectedAction = BA_NONE; return; } } else { _states.front()->cancel(); return; } } // don't handle mouseclicks below 140, because they are in the buttons area (it overlaps with map surface) if (action->getYMouse() / action->getYScale() > BUTTONS_AREA) return; // don't accept leftclicks if there is no cursor or there is an action busy if (_map->getCursorType() == CT_NONE || !_states.empty()) return; Position pos; _map->getSelectorPosition(&pos); if (action->getDetails()->button.button == SDL_BUTTON_LEFT) { if (_targeting && _battleGame->getSelectedUnit()) { // -= fire weapon =- _target = pos; _map->setCursorType(CT_NONE); _game->getCursor()->setVisible(false); statePushBack(new UnitTurnBState(this)); statePushBack(new ProjectileFlyBState(this)); } else { BattleUnit *unit = _battleGame->selectUnit(pos); if (unit && !unit->isOut()) { // -= select unit =- if (unit->getFaction() == _battleGame->getSide()) { _battleGame->setSelectedUnit(unit); updateSoldierInfo(unit); } } else if (_battleGame->getSelectedUnit()) { // -= start walking =- _target = pos; _map->setCursorType(CT_NONE); _game->getCursor()->setVisible(false); statePushBack(new UnitWalkBState(this)); } } } else if (action->getDetails()->button.button == SDL_BUTTON_RIGHT && _battleGame->getSelectedUnit()) { // -= turn to or open door =- _target = pos; statePushBack(new UnitTurnBState(this)); } }
/** * init sequence: * - check if shot is valid * - calculate base accuracy */ void ProjectileFlyBState::init() { if (_initialized) return; _initialized = true; BattleItem *weapon = _action.weapon; _projectileItem = 0; _autoshotCounter = 0; if (!weapon) // can't shoot without weapon return; if (_action.actor->getTimeUnits() < _action.TU && !_parent->dontSpendTUs()) { _result = "STR_NOT_ENOUGH_TIME_UNITS"; _parent->popState(); return; } _unit = _action.actor; _ammo = weapon->getAmmoItem(); if (_unit->isOut()) { // something went wrong _parent->popState(); return; } if (_action.type != BA_THROW) { if (_ammo == 0) { _result = "STR_NO_AMMUNITION_LOADED"; _parent->popState(); return; } if (_ammo->getAmmoQuantity() == 0) { _result = "STR_NO_ROUNDS_LEFT"; _parent->popState(); return; } } // action specific initialisation switch (_action.type) { case BA_AUTOSHOT: _baseAcc = weapon->getRules()->getAccuracyAuto(); break; case BA_SNAPSHOT: _baseAcc = weapon->getRules()->getAccuracySnap(); break; case BA_AIMEDSHOT: _baseAcc = weapon->getRules()->getAccuracyAimed(); break; case BA_THROW: if (!validThrowRange()) { // out of range _result = "STR_OUT_OF_RANGE"; _parent->popState(); return; } _baseAcc = (int)(_unit->getThrowingAccuracy()*100.0); _projectileItem = weapon; break; default: _baseAcc = 0; } createNewProjectile(); BattleAction action; BattleUnit *potentialVictim = _parent->getGame()->getSavedGame()->getBattleGame()->getTile(_action.target)->getUnit(); if (potentialVictim && potentialVictim->getFaction() != _unit->getFaction()) { if (_parent->getGame()->getSavedGame()->getBattleGame()->getTileEngine()->checkReactionFire(_unit, &action, potentialVictim, false)) { _parent->statePushBack(new ProjectileFlyBState(_parent, action)); } } }
/** * Initializes the explosion. * The animation and sound starts here. * If the animation is finished, the actual effect takes place. */ void ExplosionBState::init() { BattleType type = BT_NONE; BattleActionType action = _action.type; const RuleItem* itemRule = 0; bool miss = false; if (_item) { itemRule = _item->getRules(); type = itemRule->getBattleType(); _power = 0; _pistolWhip = (type != BT_MELEE && action == BA_HIT); if (_pistolWhip) { _power += itemRule->getMeleeBonus(_unit); _radius = 0; _damageType = itemRule->getMeleeType(); } else { _power += itemRule->getPowerBonus(_unit); _power -= itemRule->getPowerRangeReduction(_range); _radius = itemRule->getExplosionRadius(_unit); _damageType = itemRule->getDamageType(); } //testing if we hit target if (type == BT_PSIAMP && !_pistolWhip) { if (action != BA_USE) { _power = 0; } if (!_parent->psiAttack(&_action)) { _power = 0; miss = true; } } else if (type == BT_MELEE || _pistolWhip) { if (!_parent->getTileEngine()->meleeAttack(&_action)) { _power = 0; miss = true; } } else if (type == BT_FIREARM) { if (_power <= 0) { miss = true; } } _areaOfEffect = type != BT_MELEE && _radius != 0 && (type != BT_PSIAMP || action == BA_USE) && !_pistolWhip && !miss; } else if (_tile) { ItemDamageType DT; switch (_tile->getExplosiveType()) { case 0: DT = DT_HE; break; case 5: DT = DT_IN; break; case 6: DT = DT_STUN; break; default: DT = DT_SMOKE; break; } _power = _tile->getExplosive(); _tile->setExplosive(0, 0, true); _damageType = _parent->getMod()->getDamageType(DT); _radius = _power /10; _areaOfEffect = true; } else if (_unit && (_unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH || _unit->getSpecialAbility() == SPECAB_BURN_AND_EXPLODE)) { RuleItem* corpse = _parent->getMod()->getItem(_unit->getArmor()->getCorpseGeoscape()); _power = corpse->getPowerBonus(_unit); _damageType = corpse->getDamageType(); _radius = corpse->getExplosionRadius(_unit); _areaOfEffect = true; if (!RNG::percent(corpse->getSpecialChance())) { _power = 0; } } else { _power = 120; _damageType = _parent->getMod()->getDamageType(DT_HE); _areaOfEffect = true; } Tile *t = _parent->getSave()->getTile(_action.target); if (_areaOfEffect) { if (_power > 0) { int frame = Mod::EXPLOSION_OFFSET; if (_item) { frame = itemRule->getHitAnimation(); } if (_parent->getDepth() > 0) { frame -= Explosion::EXPLODE_FRAMES; } int frameDelay = 0; int counter = std::max(1, (_power/5) / 5); _parent->getMap()->setBlastFlash(true); for (int i = 0; i < _power/5; i++) { int X = RNG::generate(-_power/2,_power/2); int Y = RNG::generate(-_power/2,_power/2); Position p = _center; p.x += X; p.y += Y; Explosion *explosion = new Explosion(p, frame, frameDelay, true); // add the explosion on the map _parent->getMap()->getExplosions()->push_back(explosion); if (i > 0 && i % counter == 0) { frameDelay++; } } _parent->setStateInterval(BattlescapeState::DEFAULT_ANIM_SPEED/2); // explosion sound int sound = _power <= 80 ? Mod::SMALL_EXPLOSION : Mod::LARGE_EXPLOSION; if (_item) optValue(sound, itemRule->getExplosionHitSound()); _parent->playSound(sound); _parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false); } else { _parent->popState(); } } else // create a bullet hit { _parent->setStateInterval(std::max(1, ((BattlescapeState::DEFAULT_ANIM_SPEED/2) - (10 * itemRule->getExplosionSpeed())))); _hit = _pistolWhip || type == BT_MELEE; bool psi = type == BT_PSIAMP && action != BA_USE && !_pistolWhip; int anim = -1; int sound = -1; // melee weapon with ammo BattleItem *ammo = !_pistolWhip && _hit ? _item->getAmmoItem() : 0; if (_hit || psi) { anim = itemRule->getMeleeAnimation(); if (psi) { // psi attack sound is based weapon hit sound sound = itemRule->getHitSound(); optValue(anim, itemRule->getPsiAnimation()); optValue(sound, itemRule->getPsiSound()); } else { sound = itemRule->getMeleeSound(); if (ammo) { optValue(anim, ammo->getRules()->getMeleeAnimation()); optValue(sound, ammo->getRules()->getMeleeSound()); } } } else { anim = itemRule->getHitAnimation(); sound = itemRule->getHitSound(); } if (miss) { if (_hit || psi) { optValue(anim, itemRule->getMeleeMissAnimation()); if (psi) { // psi attack sound is based weapon hit sound optValue(sound, itemRule->getHitMissSound()); optValue(anim, itemRule->getPsiMissAnimation()); optValue(sound, itemRule->getPsiMissSound()); } else { optValue(sound, itemRule->getMeleeMissSound()); if (ammo) { optValue(anim, ammo->getRules()->getMeleeMissAnimation()); optValue(sound, ammo->getRules()->getMeleeMissSound()); } } } else { optValue(anim, itemRule->getHitMissAnimation()); optValue(sound, itemRule->getHitMissSound()); } } if (anim != -1) { Explosion *explosion = new Explosion(_center, anim, 0, false, (_hit || psi)); // Don't burn the tile _parent->getMap()->getExplosions()->push_back(explosion); } _parent->getMap()->getCamera()->setViewLevel(_center.z / 24); BattleUnit *target = t->getUnit(); if ((_hit || psi) && _parent->getSave()->getSide() == FACTION_HOSTILE && target && target->getFaction() == FACTION_PLAYER) { _parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false); } // bullet hit sound _parent->playSound(sound, _action.target); } }
/** * Initializes the explosion. * The animation and sound starts here. * If the animation is finished, the actual effect takes place. */ void ExplosionBState::init() { if (_item) { _power = _item->getRules()->getPower(); // getCurrentAction() only works for player actions: aliens cannot melee attack with rifle butts. _pistolWhip = (_item->getRules()->getBattleType() != BT_MELEE && _parent->getCurrentAction()->type == BA_HIT); if (_pistolWhip) { _power = _item->getRules()->getMeleePower(); } // since melee aliens don't use a conventional weapon type, we use their strength instead. if (_item->getRules()->isStrengthApplied()) { _power += _unit->getStats()->strength; } _areaOfEffect = _item->getRules()->getBattleType() != BT_MELEE && _item->getRules()->getExplosionRadius() != 0 && !_pistolWhip; } else if (_tile) { _power = _tile->getExplosive(); _areaOfEffect = true; } else if (_unit && _unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH) { _power = _parent->getRuleset()->getItem(_unit->getArmor()->getCorpseGeoscape())->getPower(); _areaOfEffect = true; } else { _power = 120; _areaOfEffect = true; } Tile *t = _parent->getSave()->getTile(Position(_center.x/16, _center.y/16, _center.z/24)); if (_areaOfEffect) { if (_power) { int frame = ResourcePack::EXPLOSION_OFFSET; if (_item) { frame = _item->getRules()->getHitAnimation(); } if (_parent->getDepth() > 0) { frame -= Explosion::EXPLODE_FRAMES; } int frameDelay = 0; int counter = std::max(1, (_power/5) / 5); for (int i = 0; i < _power/5; i++) { int X = RNG::generate(-_power/2,_power/2); int Y = RNG::generate(-_power/2,_power/2); Position p = _center; p.x += X; p.y += Y; Explosion *explosion = new Explosion(p, frame, frameDelay, true); // add the explosion on the map _parent->getMap()->getExplosions()->push_back(explosion); if (i > 0 && i % counter == 0) { frameDelay++; } } _parent->setStateInterval(BattlescapeState::DEFAULT_ANIM_SPEED/2); // explosion sound if (_power <= 80) _parent->getResourcePack()->getSoundByDepth(_parent->getDepth(), ResourcePack::SMALL_EXPLOSION)->play(); else _parent->getResourcePack()->getSoundByDepth(_parent->getDepth(), ResourcePack::LARGE_EXPLOSION)->play(); _parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false); } else { _parent->popState(); } } else // create a bullet hit { _parent->setStateInterval(std::max(1, ((BattlescapeState::DEFAULT_ANIM_SPEED/2) - (10 * _item->getRules()->getExplosionSpeed())))); _hit = _pistolWhip || _item->getRules()->getBattleType() == BT_MELEE; bool psi = _item->getRules()->getBattleType() == BT_PSIAMP; int anim = _item->getRules()->getHitAnimation(); int sound = _item->getRules()->getHitSound(); if (_hit || psi) // Play melee animation on hitting and psiing { anim = _item->getRules()->getMeleeAnimation(); } if (sound != -1) { // bullet hit sound _parent->getResourcePack()->getSoundByDepth(_parent->getDepth(), sound)->play(); } Explosion *explosion = new Explosion(_center, anim, 0, false, (_hit || psi)); // Don't burn the tile _parent->getMap()->getExplosions()->push_back(explosion); _parent->getMap()->getCamera()->setViewLevel(_center.z / 24); BattleUnit *target = t->getUnit(); if ((_hit || psi) && _parent->getSave()->getSide() == FACTION_HOSTILE && target && target->getFaction() == FACTION_PLAYER) { _parent->getMap()->getCamera()->centerOnPosition(t->getPosition(), false); } } }