/**
 * 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));
			}
		}
	}
}
Beispiel #2
0
/**
 * 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);
		}
	}
}
Beispiel #4
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);
	}
}
Beispiel #8
0
/**
 * 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);
		}
	}
}