/* * Convert unit to corpse(item). */ void UnitDieBState::convertUnitToCorpse() { // in case the unit was unconscious _parent->getSave()->removeUnconsciousBodyItem(_unit); int size = _unit->getArmor()->getSize() - 1; // move inventory from unit to the ground for non-large units if (size == 0) { for (std::vector<BattleItem*>::iterator i = _unit->getInventory()->begin(); i != _unit->getInventory()->end(); ++i) { _parent->dropItem(_unit->getPosition(), (*i)); } } _unit->getInventory()->clear(); // remove unit-tile link _unit->setTile(0); if (size == 0) { BattleItem *corpse = new BattleItem(_parent->getRuleset()->getItem(_unit->getArmor()->getCorpseItem()),_parent->getSave()->getCurrentItemId()); corpse->setUnit(_unit); _parent->dropItem(_unit->getPosition(), corpse, true); _parent->getSave()->getTile(_unit->getPosition())->setUnit(0); } else { Position p = _unit->getPosition(); int i = 1; for (int y = 0; y <= size; y++) { for (int x = 0; x <= size; x++) { std::stringstream ss; ss << _unit->getArmor()->getCorpseItem() << i; BattleItem *corpse = new BattleItem(_parent->getRuleset()->getItem(ss.str()),_parent->getSave()->getCurrentItemId()); corpse->setUnit(_unit); // no need for this, because large units never can be revived as they don't go unconscious // yes there freaking is because yes they freaking do, nerf their consciousness elswhere, // because we need to recover live reapers and i need this kept track of for corpse recovery. also i hate reapers. _parent->dropItem(p + Position(x,y,0), corpse, true); _parent->getSave()->getTile(p + Position(x,y,0))->setUnit(0); i++; } } } }
/* * Convert unit to corpse(item). * @param unit * @param terrain */ void UnitDieBState::convertUnitToCorpse(BattleUnit *unit, TileEngine *terrain) { int size = _unit->getUnit()->getArmor()->getSize() - 1; // move inventory from unit to the ground for non-large units if (size == 0) { for (std::vector<BattleItem*>::iterator i = _unit->getInventory()->begin(); i != _unit->getInventory()->end(); ++i) { _parent->dropItem(_unit->getPosition(), (*i)); } } _unit->getInventory()->clear(); // remove unit-tile link _unit->setTile(0); if (size == 0) { _parent->getGame()->getSavedGame()->getBattleGame()->getTile(_unit->getPosition())->setUnit(0); BattleItem *corpse = new BattleItem(_parent->getGame()->getRuleset()->getItem(_unit->getUnit()->getArmor()->getCorpseItem()),_parent->getGame()->getSavedGame()->getBattleGame()->getCurrentItemId()); corpse->setUnit(unit); _parent->dropItem(_unit->getPosition(), corpse, true); } else { int i = 1; for (int y = 0; y <= size; y++) { for (int x = 0; x <= size; x++) { _parent->getGame()->getSavedGame()->getBattleGame()->getTile(_unit->getPosition() + Position(x,y,0))->setUnit(0); std::stringstream ss; ss << _unit->getUnit()->getArmor()->getCorpseItem() << i; BattleItem *corpse = new BattleItem(_parent->getGame()->getRuleset()->getItem(ss.str()),_parent->getGame()->getSavedGame()->getBattleGame()->getCurrentItemId()); //corpse->setUnit(unit); // no need for this, because large units never can be revived as they don't go unconscious _parent->dropItem(_unit->getPosition() + Position(x,y,0), corpse, true); i++; } } } }
/** * Get the "main hand weapon" from the unit. * @return Pointer to item. */ BattleItem *BattleUnit::getMainHandWeapon() const { BattleItem *weaponRightHand = getItem("STR_RIGHT_HAND"); BattleItem *weaponLeftHand = getItem("STR_LEFT_HAND"); // if there is only one weapon, or only one weapon loaded (rules out grenades) it's easy: if (!weaponRightHand || !weaponRightHand->getAmmoItem() || !weaponRightHand->getAmmoItem()->getAmmoQuantity()) return weaponLeftHand; if (!weaponLeftHand || !weaponLeftHand->getAmmoItem() || !weaponLeftHand->getAmmoItem()->getAmmoQuantity()) return weaponRightHand; // otherwise pick the one with the least snapshot TUs int tuRightHand = weaponRightHand->getRules()->getTUSnap(); int tuLeftHand = weaponRightHand->getRules()->getTUSnap(); if (tuLeftHand >= tuRightHand) { return weaponRightHand; } else { return weaponLeftHand; } }
/** * Updates item info. * @param action Pointer to an action. */ void InventoryState::invClick(Action *action) { BattleItem *item = _inv->getSelectedItem(); _txtItem->setText(L""); _txtAmmo->setText(L""); _selAmmo->clear(); if (item != 0) { _txtItem->setText(_game->getLanguage()->getString(item->getRules()->getType())); std::wstringstream ss; if (item->getAmmoItem() != 0) { ss << _game->getLanguage()->getString("STR_AMMO_ROUNDS_LEFT") << L'\x01' << item->getAmmoItem()->getAmmoQuantity(); SDL_Rect r; r.x = 0; r.y = 0; r.w = RuleInventory::HAND_W * RuleInventory::SLOT_W; r.h = RuleInventory::HAND_H * RuleInventory::SLOT_H; _selAmmo->drawRect(&r, Palette::blockOffset(0)+8); r.x++; r.y++; r.w -= 2; r.h -= 2; _selAmmo->drawRect(&r, 0); item->getAmmoItem()->getRules()->drawHandSprite(_game->getResourcePack()->getSurfaceSet("BIGOBS.PCK"), _selAmmo); } else if (item->getAmmoQuantity() != 0) { ss << _game->getLanguage()->getString("STR_AMMO_ROUNDS_LEFT") << L'\x01' << item->getAmmoQuantity(); } _txtAmmo->setText(ss.str()); } if (_tu) { std::wstringstream ss; ss << _game->getLanguage()->getString("STR_TUS") << L'\x01' << _battleGame->getSelectedUnit()->getTimeUnits(); _txtTus->setText(ss.str()); } }
/** * 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)); } } } }
/** * Converts unit to a corpse (item). */ void UnitDieBState::convertUnitToCorpse() { _parent->getSave()->getBattleState()->showPsiButton(false); Position lastPosition = _unit->getPosition(); // remove the unconscious body item corresponding to this unit, and if it was being carried, keep track of what slot it was in if (lastPosition != Position(-1,-1,-1)) { _parent->getSave()->removeUnconsciousBodyItem(_unit); } int size = _unit->getArmor()->getSize(); BattleItem *itemToKeep = 0; bool dropItems = !Options::weaponSelfDestruction || (_unit->getOriginalFaction() != FACTION_HOSTILE || _unit->getStatus() == STATUS_UNCONSCIOUS); // move inventory from unit to the ground for non-large units if (size == 1 && dropItems) { for (std::vector<BattleItem*>::iterator i = _unit->getInventory()->begin(); i != _unit->getInventory()->end(); ++i) { _parent->dropItem(lastPosition, (*i)); if (!(*i)->getRules()->isFixed()) { (*i)->setOwner(0); } else { itemToKeep = *i; } } } _unit->getInventory()->clear(); if (itemToKeep != 0) { _unit->getInventory()->push_back(itemToKeep); } // remove unit-tile link _unit->setTile(0); if (lastPosition == Position(-1,-1,-1)) // we're being carried { // replace the unconscious body item with a corpse in the carrying unit's inventory for (std::vector<BattleItem*>::iterator it = _parent->getSave()->getItems()->begin(); it != _parent->getSave()->getItems()->end(); ) { if ((*it)->getUnit() == _unit) { RuleItem *corpseRules = _parent->getRuleset()->getItem(_unit->getArmor()->getCorpseBattlescape()[0]); // we're in an inventory, so we must be a 1x1 unit (*it)->convertToCorpse(corpseRules); break; } ++it; } } else { int i = 0; for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { BattleItem *corpse = new BattleItem(_parent->getRuleset()->getItem(_unit->getArmor()->getCorpseBattlescape()[i]), _parent->getSave()->getCurrentItemId()); corpse->setUnit(_unit); if (_parent->getSave()->getTile(lastPosition + Position(x,y,0))->getUnit() == _unit) // check in case unit was displaced by another unit { _parent->getSave()->getTile(lastPosition + Position(x,y,0))->setUnit(0); } _parent->dropItem(lastPosition + Position(x,y,0), corpse, true); i++; } } } }
/** * 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 && _autoshotCounter < 3 && !_action.actor->isOut() && _ammo->getAmmoQuantity() != 0) { createNewProjectile(); } else { _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()->getSoundSet("BATTLE.CAT")->getSound(38)->play(); if (Options::getBool("battleAltGrenade") && item->getRules()->getBattleType() == BT_GRENADE && item->getExplodeTurn() > 0) { // 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 (_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 one step back if (_ammo && ( _ammo->getRules()->getDamageType() == DT_HE || _ammo->getRules()->getDamageType() == DT_IN)) { offset = -1; } _parent->statePushFront(new ExplosionBState(_parent, _parent->getMap()->getProjectile()->getPosition(offset), _ammo, _action.actor)); } else { _unit->aim(false); _parent->getMap()->cacheUnits(); } } delete _parent->getMap()->getProjectile(); _parent->getMap()->setProjectile(0); } } }
/** * 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); } } }
/** * init sequence: * - check if shot is valid * - calculate base accuracy */ void ProjectileFlyBState::init() { if (_initialized) return; _initialized = true; BattleItem *weapon = _action.weapon; _projectileItem = 0; if (!weapon) // can't shoot without weapon { _parent->popState(); return; } if (!_parent->getSave()->getTile(_action.target)) // invalid target position { _parent->popState(); return; } if (_parent->getPanicHandled() && _action.actor->getTimeUnits() < _action.TU) { _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; } // reaction fire if (_unit->getFaction() != _parent->getSave()->getSide()) { // no ammo or target is dead: give the time units back and cancel the shot. if (_ammo == 0 || !_parent->getSave()->getTile(_action.target)->getUnit() || _parent->getSave()->getTile(_action.target)->getUnit()->isOut()) { _unit->setTimeUnits(_unit->getTimeUnits() + _unit->getActionTUs(_action.type, _action.weapon)); _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 (!_parent->getTileEngine()->validMeleeRange(_action.actor->getPosition(), _action.actor->getDirection(), _action.actor->getArmor()->getSize(), 0)) { _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; } createNewProjectile(); }
/** * Updates soldier name/rank/tu/energy/health/morale. * @param battleUnit Pointer to current unit. */ void BattlescapeState::updateSoldierInfo(BattleUnit *battleUnit) { if (battleUnit == 0) { _txtName->setText(L""); _rank->clear(); _numTimeUnits->clear(); _barTimeUnits->clear(); _barTimeUnits->clear(); _numEnergy->clear(); _barEnergy->clear(); _barEnergy->clear(); _numHealth->clear(); _barHealth->clear(); _barHealth->clear(); _numMorale->clear(); _barMorale->clear(); _barMorale->clear(); _btnLeftHandItem->clear(); _btnRightHandItem->clear(); return; } _txtName->setText(battleUnit->getUnit()->getName()); Soldier *soldier = dynamic_cast<Soldier*>(battleUnit->getUnit()); if (soldier != 0) { SurfaceSet *texture = _game->getResourcePack()->getSurfaceSet("BASEBITS.PCK"); texture->getFrame(soldier->getRankSprite())->blit(_rank); } _numTimeUnits->setValue(battleUnit->getTimeUnits()); _barTimeUnits->setMax(battleUnit->getUnit()->getTimeUnits()); _barTimeUnits->setValue(battleUnit->getTimeUnits()); _numEnergy->setValue(battleUnit->getEnergy()); _barEnergy->setMax(battleUnit->getUnit()->getStamina()); _barEnergy->setValue(battleUnit->getEnergy()); _numHealth->setValue(battleUnit->getHealth()); _barHealth->setMax(battleUnit->getUnit()->getHealth()); _barHealth->setValue(battleUnit->getHealth()); _numMorale->setValue(battleUnit->getMorale()); _barMorale->setMax(100); _barMorale->setValue(battleUnit->getMorale()); BattleItem *leftHandItem = _battleGame->getItemFromUnit(battleUnit, LEFT_HAND); _btnLeftHandItem->clear(); _numAmmoLeft->clear(); if (leftHandItem) { drawItemSprite(leftHandItem, _btnLeftHandItem); _numAmmoLeft->setValue(leftHandItem->getAmmoQuantity()); } BattleItem *rightHandItem = _battleGame->getItemFromUnit(battleUnit, RIGHT_HAND); _btnRightHandItem->clear(); _numAmmoRight->clear(); if (rightHandItem) { drawItemSprite(rightHandItem, _btnRightHandItem); _numAmmoRight->setValue(rightHandItem->getAmmoQuantity()); } _battleGame->getTerrainModifier()->calculateFOV(_battleGame->getSelectedUnit()); for (int i = 0; i < 10; i++) { _btnVisibleUnit[i]->hide(); _numVisibleUnit[i]->hide(); _visibleUnit[i] = 0; } int j = 0; for (std::vector<BattleUnit*>::iterator i = battleUnit->getVisibleUnits()->begin(); i != battleUnit->getVisibleUnits()->end(); i++) { _btnVisibleUnit[j]->show(); _numVisibleUnit[j]->show(); _visibleUnit[j] = (*i); j++; } }
/** * 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); } }
/** * Calculates the effects of the explosion. */ void ExplosionBState::explode() { bool terrainExplosion = false; SavedBattleGame *save = _parent->getSave(); // last minute adjustment: determine if we actually if (_hit) { if (_unit && !_unit->isOut()) { _unit->aim(false); } if (_power <= 0) { _parent->popState(); return; } int sound = _item->getRules()->getMeleeHitSound(); if (!_pistolWhip) { // melee weapon with ammo BattleItem *ammo = _item->getAmmoItem(); if (ammo) { optValue(sound, ammo->getRules()->getMeleeHitSound()); } } _parent->playSound(sound, _action.target); } // after the animation is done, the real explosion/hit takes place if (_item) { if (!_unit && _item->getPreviousOwner()) { _unit = _item->getPreviousOwner(); } } bool range = !(_hit || (_item && _item->getRules()->getBattleType() == BT_PSIAMP)); if (_areaOfEffect) { save->getTileEngine()->explode(_center, _power, _damageType, _radius, _unit, _item, range); } else { BattleUnit *victim = save->getTileEngine()->hit(_center, _power, _damageType, _unit, _item, range); // check if this unit turns others into zombies if (!_item->getRules()->getZombieUnit().empty() && RNG::percent(_item->getRules()->getSpecialChance()) && victim && victim->getArmor()->getZombiImmune() == false && victim->getSpawnUnit().empty() && victim->getOriginalFaction() != FACTION_HOSTILE) { // converts the victim to a zombie on death victim->setRespawn(true); victim->setSpawnUnit(_item->getRules()->getZombieUnit()); } } if (_tile) { terrainExplosion = true; } if (!_tile && !_item) { terrainExplosion = true; } // now check for new casualties _parent->checkForCasualties(_item ? _damageType : 0, _item, _unit, false, terrainExplosion); // revive units if damage could give hp or reduce stun _parent->getSave()->reviveUnconsciousUnits(true); // if this explosion was caused by a unit shooting, now it's the time to put the gun down if (_unit && !_unit->isOut() && _lowerWeapon) { _unit->aim(false); } if (_item && (_item->getRules()->getBattleType() == BT_GRENADE || _item->getRules()->getBattleType() == BT_PROXIMITYGRENADE)) { _parent->getSave()->removeItem(_item); } _parent->popState(); // check for terrain explosions Tile *t = save->getTileEngine()->checkForTerrainExplosions(); if (t) { Position p = t->getPosition().toVexel(); p += Position(8,8,0); _parent->statePushFront(new ExplosionBState(_parent, p, BA_NONE, 0, _unit, t)); } }
void InventoryState::btnApplyTemplateClick(Action *) { // don't accept clicks when moving items // it's ok if the template is empty -- it will just result in clearing the // unit's inventory if (_inv->getSelectedItem() != 0) { return; } BattleUnit *unit = _battleGame->getSelectedUnit(); std::vector<BattleItem*> *unitInv = unit->getInventory(); Tile *groundTile = unit->getTile(); std::vector<BattleItem*> *groundInv = groundTile->getInventory(); RuleInventory *groundRuleInv = _game->getMod()->getInventory("STR_GROUND"); _clearInventory(_game, unitInv, groundTile); // attempt to replicate inventory template by grabbing corresponding items // from the ground. if any item is not found on the ground, display warning // message, but continue attempting to fulfill the template as best we can bool itemMissing = false; std::vector<EquipmentLayoutItem*>::iterator templateIt; for (templateIt = _curInventoryTemplate.begin(); templateIt != _curInventoryTemplate.end(); ++templateIt) { // search for template item in ground inventory std::vector<BattleItem*>::iterator groundItem; const bool needsAmmo = !_game->getMod()->getItem((*templateIt)->getItemType())->getCompatibleAmmo()->empty(); bool found = false; bool rescan = true; while (rescan) { rescan = false; const std::string targetAmmo = (*templateIt)->getAmmoItem(); BattleItem *matchedWeapon = NULL; BattleItem *matchedAmmo = NULL; for (groundItem = groundInv->begin(); groundItem != groundInv->end(); ++groundItem) { // if we find the appropriate ammo, remember it for later for if we find // the right weapon but with the wrong ammo const std::string groundItemName = (*groundItem)->getRules()->getType(); if (needsAmmo && targetAmmo == groundItemName) { matchedAmmo = *groundItem; } if ((*templateIt)->getItemType() == groundItemName) { // if the loaded ammo doesn't match the template item's, // remember the weapon for later and continue scanning BattleItem *loadedAmmo = (*groundItem)->getAmmoItem(); if ((needsAmmo && loadedAmmo && targetAmmo != loadedAmmo->getRules()->getType()) || (needsAmmo && !loadedAmmo)) { // remember the last matched weapon for simplicity (but prefer empty weapons if any are found) if (!matchedWeapon || matchedWeapon->getAmmoItem()) { matchedWeapon = *groundItem; } continue; } // move matched item from ground to the appropriate inv slot (*groundItem)->setOwner(unit); (*groundItem)->setSlot(_game->getMod()->getInventory((*templateIt)->getSlot())); (*groundItem)->setSlotX((*templateIt)->getSlotX()); (*groundItem)->setSlotY((*templateIt)->getSlotY()); (*groundItem)->setFuseTimer((*templateIt)->getFuseTimer()); unitInv->push_back(*groundItem); groundInv->erase(groundItem); found = true; break; } } // if we failed to find an exact match, but found unloaded ammo and // the right weapon, unload the target weapon, load the right ammo, and use it if (!found && matchedWeapon && (!needsAmmo || matchedAmmo)) { // unload the existing ammo (if any) from the weapon BattleItem *loadedAmmo = matchedWeapon->getAmmoItem(); if (loadedAmmo) { groundTile->addItem(loadedAmmo, groundRuleInv); matchedWeapon->setAmmoItem(NULL); } // load the correct ammo into the weapon if (matchedAmmo) { matchedWeapon->setAmmoItem(matchedAmmo); groundTile->removeItem(matchedAmmo); } // rescan and pick up the newly-loaded/unloaded weapon rescan = true; } } if (!found) { itemMissing = true; } } if (itemMissing) { _inv->showWarning(tr("STR_NOT_ENOUGH_ITEMS_FOR_TEMPLATE")); } // refresh ui _inv->arrangeGround(false); updateStats(); _refreshMouse(); // give audio feedback _game->getMod()->getSoundByDepth(_battleGame->getDepth(), Mod::ITEM_DROP)->play(); }
/** * Loads the saved battle game from a YAML file. * @param node YAML node. */ void SavedBattleGame::load(const YAML::Node &node, Ruleset *rule, SavedGame* savedGame) { int a; int selectedUnit = 0; node["width"] >> _width; node["length"] >> _length; node["height"] >> _height; node["globalshade"] >> _globalShade; node["selectedUnit"] >> selectedUnit; for (YAML::Iterator i = node["mapdatasets"].begin(); i != node["mapdatasets"].end(); ++i) { std::string name; *i >> name; MapDataSet *mds = new MapDataSet(name); _mapDataSets.push_back(mds); } initMap(_width, _length, _height); for (YAML::Iterator i = node["tiles"].begin(); i != node["tiles"].end(); ++i) { Position pos; (*i)["position"][0] >> pos.x; (*i)["position"][1] >> pos.y; (*i)["position"][2] >> pos.z; getTile(pos)->load((*i)); } for (YAML::Iterator i = node["nodes"].begin(); i != node["nodes"].end(); ++i) { Node *n = new Node(); n->load(*i); _nodes.push_back(n); } for (YAML::Iterator i = node["units"].begin(); i != node["units"].end(); ++i) { UnitFaction faction; (*i)["faction"] >> a; faction = (UnitFaction)a; (*i)["soldierId"] >> a; Unit *unit; if (a != -1) // Unit is linked to a geoscape soldier { // look up the matching soldier unit = savedGame->getSoldier(a); } else { // create a new Unit. unit = new GenUnit(rule->getGenUnit("SECTOID_SOLDIER"), rule->getArmor("SECTOID_ARMOR0")); } BattleUnit *b = new BattleUnit(unit, faction); b->load(*i); _units.push_back(b); if (faction == FACTION_PLAYER) { if (b->getId() == selectedUnit) _selectedUnit = b; } else { std::string state; BattleAIState *aiState; (*i)["AI"]["state"] >> state; if (state == "PATROL") { aiState = new PatrolBAIState(this, b, 0); } else if (state == "AGGRO") { aiState = new AggroBAIState(this, b); } else { continue; } aiState->load((*i)["AI"]); b->setAIState(aiState); } } // matches up tiles and units resetUnitTiles(); for (YAML::Iterator i = node["items"].begin(); i != node["items"].end(); ++i) { std::string type; (*i)["type"] >> type; if (type != "0") { BattleItem *item = new BattleItem(rule->getItem(type), &_itemId); item->load(*i); (*i)["inventoryslot"] >> type; if (type != "NULL") item->setSlot(rule->getInventory(type)); (*i)["owner"] >> a; // match up items and units for (std::vector<BattleUnit*>::iterator bu = _units.begin(); bu != _units.end(); ++bu) { if ((*bu)->getId() == a) { item->moveToOwner(*bu); break; } } // match up items and tiles if (item->getSlot() && item->getSlot()->getType() == INV_GROUND) { Position pos; (*i)["position"][0] >> pos.x; (*i)["position"][1] >> pos.y; (*i)["position"][2] >> pos.z; if (pos.x != -1) getTile(pos)->addItem(item); } _items.push_back(item); }
/** * 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() { if (_parent->getMap()->getProjectile() == 0) { if (_action.type == BA_AUTOSHOT && _autoshotCounter < 3 && !_action.actor->isOut()) { createNewProjectile(); } else { _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->getGame()->getResourcePack()->getSoundSet("BATTLE.CAT")->getSound(38)->play(); if (Options::getBool("battleAltGrenade") && item->getRules()->getBattleType() == BT_GRENADE) { // it's a hot grenade to explode immediatly _parent->statePushFront(new ExplosionBState(_parent, _parent->getMap()->getProjectile()->getPosition(-1), item, _action.actor)); } else { _parent->dropItem(pos, item); } } else { if (_projectileImpact != 5) // out of map { int offset = 0; // explosions impact not inside the voxel but one step back if (_ammo && ( _ammo->getRules()->getDamageType() == DT_HE || _ammo->getRules()->getDamageType() == DT_IN)) { offset = -1; } _parent->statePushFront(new ExplosionBState(_parent, _parent->getMap()->getProjectile()->getPosition(offset), _ammo, _action.actor)); } else { _unit->aim(false); _parent->getMap()->cacheUnits(); if (_parent->getMap()->didCameraFollow() && _parent->getGame()->getSavedGame()->getBattleGame()->getSide() == FACTION_PLAYER) { _parent->getMap()->centerOnPosition(_parent->getGame()->getSavedGame()->getBattleGame()->getSelectedUnit()->getPosition()); } } } delete _parent->getMap()->getProjectile(); _parent->getMap()->setProjectile(0); } } }
/** * Runs any code the state needs to keep updating every * AI cycle. * @param action (possible) AI action to execute after thinking is done. */ void AggroBAIState::think(BattleAction *action) { if (Options::getBool("traceAI")) { Log(LOG_INFO) << "AggroBAIState::think() #" << action->number << (charge ? " [charging]": " "); } action->type = BA_RETHINK; action->actor = _unit; /* Aggro is mainly either shooting a target or running towards it (melee). If we do no action here - we assume we lost aggro and will go back to patrol state. */ int aggression = _unit->getAggression(); if (!charge || (_aggroTarget && (_aggroTarget->isOut() || _aggroTarget->getFaction() == _unit->getFaction()))) { _aggroTarget = 0; } else { action->type = BA_WALK; // just in case action->target = _lastKnownPosition; } int unitsSpottingMe = _game->getSpottingUnits(_unit); /* psionic targetting: pick from any of the "exposed" units. exposed means they have been previously spotted, and are therefore "known" to the AI, regardless of whether we can see them or not, because we're psychic. */ if (_unit->getStats()->psiSkill && _unit->getType() != "SOLDIER" && _game->getExposedUnits()->size() > 0 && RNG::generate(0, 100) > 66) { int psiAttackStrength = _unit->getStats()->psiSkill * _unit->getStats()->psiStrength / 50; int chanceToAttack = 0; int tries = 0; for (std::vector<BattleUnit*>::const_iterator i = _game->getExposedUnits()->begin(); i != _game->getExposedUnits()->end() && tries < 80; ++i) { // don't target tanks if ((*i)->getArmor()->getSize() != 2) { int chanceToAttackMe = psiAttackStrength + ((*i)->getStats()->psiSkill * -0.4) - (_game->getTileEngine()->distance(_unit->getPosition(), (*i)->getPosition()) / 2) - ((*i)->getStats()->psiStrength) + (RNG::generate(0, 50)) + 55; if (chanceToAttackMe > chanceToAttack) { chanceToAttack = chanceToAttackMe; _aggroTarget = *i; } } ++tries; } if (!_aggroTarget) chanceToAttack = 0; if (chanceToAttack) { if (_unit->getMainHandWeapon() && _unit->getMainHandWeapon()->getAmmoItem()) { if (_unit->getMainHandWeapon()->getAmmoItem()->getRules()->getPower() >= chanceToAttack) { chanceToAttack = 0; _aggroTarget = 0; } } else { if (RNG::generate(35, 155) >= chanceToAttack) { chanceToAttack = 0; _aggroTarget = 0; } } if (chanceToAttack >= 30) { int controlOrPanic = 60; int morale = _aggroTarget->getMorale(); int bravery = _aggroTarget->getStats()->bravery; if (bravery > 60) controlOrPanic += 15; if ( bravery < 40) controlOrPanic -= 15; if (morale >= 40) { if (morale - 10 * bravery < 50) controlOrPanic += 15; } else { controlOrPanic -= 15; } if (!morale) { controlOrPanic = 0; } if (RNG::generate(0, 100) >= controlOrPanic) { action->type = BA_MINDCONTROL; action->target = _aggroTarget->getPosition(); } else { action->type = BA_PANIC; action->target = _aggroTarget->getPosition(); } } else if (chanceToAttack) { action->type = BA_PANIC; action->target = _aggroTarget->getPosition(); } } } /* * waypoint targetting: pick from any units currently spotted by our allies. */ if (_unit->getMainHandWeapon() && _unit->getMainHandWeapon()->getAmmoItem() && _unit->getMainHandWeapon()->getRules()->isWaypoint() && _unit->getType() != "SOLDIER") { for (std::vector<BattleUnit*>::const_iterator i = _game->getUnits()->begin(); i != _game->getUnits()->end() && _aggroTarget == 0; ++i) { if ((*i)->getFaction() == _unit->getFaction()) { for (std::vector<BattleUnit*>::const_iterator j = (*i)->getVisibleUnits()->begin(); j != (*i)->getVisibleUnits()->end() && _aggroTarget == 0; ++j) { _game->getPathfinding()->calculate(_unit, (*j)->getPosition(), *j); if (_game->getPathfinding()->getStartDirection() != -1 && explosiveEfficacy((*j)->getPosition(), _unit, (_unit->getMainHandWeapon()->getAmmoItem()->getRules()->getPower()/20)+1, action->diff)) { _aggroTarget = *j; } _game->getPathfinding()->abortPath(); } } } if (_aggroTarget != 0) { action->weapon = _unit->getMainHandWeapon(); action->type = BA_LAUNCH; action->TU = action->actor->getActionTUs(action->type, action->weapon); action->waypoints.clear(); int PathDirection; int CollidesWith; Position LastWayPoint = _unit->getPosition(); Position LastPosition = _unit->getPosition(); Position CurrentPosition = _unit->getPosition(); Position DirectionVector; _game->getPathfinding()->calculate(_unit, _aggroTarget->getPosition(), _aggroTarget); PathDirection = _game->getPathfinding()->dequeuePath(); while (PathDirection != -1) { LastPosition = CurrentPosition; _game->getPathfinding()->directionToVector(PathDirection, &DirectionVector); CurrentPosition = CurrentPosition + DirectionVector; Position voxelPosA ((CurrentPosition.x * 16)+8, (CurrentPosition.y * 16)+8, (CurrentPosition.z * 24)+12); Position voxelPosb ((LastWayPoint.x * 16)+8, (LastWayPoint.y * 16)+8, (LastWayPoint.z * 24)+12); CollidesWith = _game->getTileEngine()->calculateLine(voxelPosA, voxelPosb, false, 0, _unit, true); if (CollidesWith > -1 && CollidesWith < 4) { action->waypoints.push_back(LastPosition); LastWayPoint = LastPosition; } else if (CollidesWith == 4) { BattleUnit* target = _game->getTile(CurrentPosition)->getUnit(); if (target == _aggroTarget) { action->waypoints.push_back(CurrentPosition); LastWayPoint = CurrentPosition; } } PathDirection = _game->getPathfinding()->dequeuePath(); } action->target = action->waypoints.front(); if( action->waypoints.size() > 6 + (action->diff * 2) || LastWayPoint != _aggroTarget->getPosition()) { action->type = BA_RETHINK; } } } /* * Regular targetting: we can see an enemy, or an enemy can see us (in case we need to take cover), or we're charging blindly toward an enemy we're pretty sure is there */ if (_unit->getVisibleUnits()->size() > 0 || unitsSpottingMe || charge) { for (std::vector<BattleUnit*>::iterator j = _unit->getVisibleUnits()->begin(); j != _unit->getVisibleUnits()->end(); ++j) { //pick closest living unit if (!_aggroTarget || _game->getTileEngine()->distance(_unit->getPosition(), (*j)->getPosition()) < _game->getTileEngine()->distance(_unit->getPosition(), _aggroTarget->getPosition())) { if(!(*j)->isOut()) _aggroTarget = (*j); } } // if we currently see no target, we either can move to it's last seen position or lose aggro if (_aggroTarget == 0 && _lastKnownTarget != 0) { _timesNotSeen++; if (_timesNotSeen > _unit->getIntelligence() || aggression == 0) { // we lost aggro - going back to patrol state charge = 0; _unit->setCharging(0); return; } // lets go looking where we've last seen him action->type = BA_WALK; action->target = _lastKnownPosition; } else if (_aggroTarget != 0) { // if we see the target, we either can shoot him, or take cover. bool takeCover = true; if (!charge) { _unit->setCharging(0); } else { _unit->setCharging(_aggroTarget); } int number = RNG::generate(0,100); // extra 5% chance per unit that sees us number += unitsSpottingMe * 5; // lost health, chances to take cover get bigger if (_unit->getHealth() < _unit->getStats()->health) number += 10; // aggrotarget has no weapon - chances of take cover get smaller if (!_aggroTarget->getMainHandWeapon()) number -= 50; if (aggression == 0 && number < 10) takeCover = false; if (aggression == 1 && number < 50) takeCover = false; if (aggression == 2 && number < 90) takeCover = false; // we're using melee, so CHAAAAAAAARGE!!!!! if (_unit->getMainHandWeapon() && _unit->getMainHandWeapon()->getRules()->getBattleType() == BT_MELEE) { _unit->lookAt(_aggroTarget->getPosition() + Position(_unit->getArmor()->getSize()-1, _unit->getArmor()->getSize()-1, 0), false); while (_unit->getStatus() == STATUS_TURNING) _unit->turn(); if (_game->getTileEngine()->validMeleeRange(_unit, _aggroTarget)) { if (Options::getBool("traceAI")) { Log(LOG_INFO) << "Rawr!"; } action->target = _aggroTarget->getPosition(); action->weapon = action->actor->getMainHandWeapon(); action->type = BA_HIT; charge = true; } else { takeCover = true; bool targetFound = false; int distance = 200; int size = action->actor->getArmor()->getSize(); //-1; int targetsize = _aggroTarget->getArmor()->getSize(); //-1; for (int x = 0 - size; x <= targetsize; ++x) { for (int y = 0 - size; y <= targetsize; ++y) { if (!(x == 0 && y == 0)) { Position checkPath = _aggroTarget->getPosition() + Position (x, y, 0); _game->getPathfinding()->calculate(action->actor, checkPath, 0); int newDistance = _game->getTileEngine()->distance(action->actor->getPosition(), checkPath); bool valid = _game->getTileEngine()->validMeleeRange(checkPath, -1, action->actor->getArmor()->getSize(), action->actor->getHeight(), _aggroTarget); if (_game->getPathfinding()->getStartDirection() != -1 && valid && newDistance < distance) { // CHAAAAAAARGE! if (Options::getBool("traceAI")) { Log(LOG_INFO) << "CHAAAAAAARGE!"; } action->target = checkPath; action->type = BA_WALK; charge = true; _unit->setCharging(_aggroTarget); distance = newDistance; } _game->getPathfinding()->abortPath(); } } } } } if (action->number >= 3 && !charge) { if (_unit->_hidingForTurn) { // already tried to get to cover; stay still action->target = _unit->getPosition(); action->TU = 0; return; } takeCover = true; // always seek cover as last action (unless melee... charge, stupid reapers!) action->reckless = true; } if (!takeCover && !charge) { _timesNotSeen = 0; _lastKnownPosition = _aggroTarget->getPosition(); action->target = _aggroTarget->getPosition(); action->type = BA_NONE; // lets' evaluate if we could throw a grenade int tu = 4; // 4TUs for picking up the grenade // do we have a grenade on our belt? BattleItem *grenade = _unit->getGrenadeFromBelt(); // distance must be more than X tiles, otherwise it's too dangerous to play with explosives if (grenade && explosiveEfficacy(_aggroTarget->getPosition(), _unit, (grenade->getRules()->getPower()/10)+1, action->diff)) { if((_unit->getFaction() == FACTION_NEUTRAL && _aggroTarget->getFaction() == FACTION_HOSTILE) || _unit->getFaction() == FACTION_HOSTILE) { action->weapon = grenade; tu += _unit->getActionTUs(BA_PRIME, grenade); tu += _unit->getActionTUs(BA_THROW, grenade); // do we have enough TUs to prime and throw the grenade? if (tu <= _unit->getStats()->tu) { // are we within range? if (ProjectileFlyBState::validThrowRange(action)) { grenade->setExplodeTurn(_game->getTurn()); _unit->spendTimeUnits(_unit->getActionTUs(BA_PRIME, grenade)); action->type = BA_THROW; } } } } if (action->type == BA_NONE) { action->weapon = action->actor->getMainHandWeapon(); // out of ammo or no weapon or ammo at all, we have to take cover if (!action->weapon) { takeCover = true; } else { if(action->weapon->getAmmoItem() && ((_unit->getFaction() == FACTION_NEUTRAL && _aggroTarget->getFaction() == FACTION_HOSTILE) || _unit->getFaction() == FACTION_HOSTILE)) { if (action->weapon->getAmmoItem()->getRules()->getDamageType() != DT_HE || explosiveEfficacy(_aggroTarget->getPosition(), _unit, (action->weapon->getAmmoItem()->getRules()->getPower() / 10) +1, action->diff)) { if (RNG::generate(1,10) < 5 && action->weapon->getAmmoQuantity() > 2) { action->type = BA_AUTOSHOT; } else { action->type = BA_SNAPSHOT; } } tu = action->actor->getActionTUs(action->type, action->weapon); // enough time units to shoot? if (tu > _unit->getTimeUnits()) { takeCover = true; } } else takeCover = true; } } } if (takeCover && !charge) { // the idea is to check within a 11x11 tile square for a tile which is not seen by our aggroTarget // if there is no such tile, we run away from the target. action->type = BA_WALK; _unit->_hidingForTurn = true; int tries = 0; bool coverFound = false; int x_search_sign = RNG::generate(0, 1) ? 1 : -1; // randomize the direction of the search for lack of a better heuristic int y_search_sign = RNG::generate(0, 1) ? 1 : -1; int dx = _unit->getPosition().x - _aggroTarget->getPosition().x; // 2d vector in the direction away from the aggro target int dy = _unit->getPosition().y - _aggroTarget->getPosition().y; int dist = _game->getTileEngine()->distance(_unit->getPosition(), _aggroTarget->getPosition()); Vector3i run; run.x = (dx * 5) / dist; run.y = (dy * 5) / dist; run.z = 0; int bestTileScore = -100000; int score = -100000; Position bestTile(0, 0, 0); ++_randomTileSearchAge; if (action->number > 1) action->desperate = true; Tile *tile = 0; bool traceSpammed = false; // weights of various factors in choosing a tile to which to withdraw const int EXPOSURE_PENALTY = 20; const int WINDOW_PENALTY = 30; const int WALL_BONUS = 1; const int FIRE_PENALTY = 40; const int FRIEND_BONUS = 10; const int SMOKE_PENALTY = 5; const int OVERREACH_PENALTY = EXPOSURE_PENALTY*3; const int MELEE_TUNNELVISION_BONUS = 1000; const int DIRECT_PATH_PENALTY = 10; const int DIRECT_PATH_TO_TARGET_PENALTY = 30; const int BASE_SYSTEMATIC_SUCCESS = 100; const int BASE_DESPERATE_SUCCESS = 110; const int FAST_PASS_THRESHOLD = 120; // a score that's good engouh to quit the while loop early; it's subjective, hand-tuned and may need tweaking const int MAX_ALLY_DISTANCE = 25; // distance^2 actually const int MIN_ALLY_DISTANCE = 4; // don't clump up too much and get grenaded, OK? const int ALLY_BONUS = 4; while (tries < 150 && !coverFound) { tries++; action->target = _unit->getPosition() + run; // start looking in a direction away from the enemy if (!_game->getTile(action->target)) { action->target = _unit->getPosition(); // cornered at the edge of the map perhaps? } if (tries < 121) { // looking for cover action->target.x += _randomTileSearch[tries].x; action->target.y += _randomTileSearch[tries].y; if (action->target == _unit->getPosition() && action->number == 1 && unitsSpottingMe > 0) { // don't even think about staying in the same spot. Move! action->target.x += RNG::generate(-20,20); action->target.y += RNG::generate(-20,20); } //score = _game->getTileEngine()->visible(_aggroTarget, _game->getTile(action->target)) ? 0 : 100; score = BASE_SYSTEMATIC_SUCCESS; // no need for visible here, the TileEngine code will take care of it } else { if (tries == 121) { action->reckless = true; if (Options::getBool("traceAI")) { Log(LOG_INFO) << _unit->getId() << " best score after systematic search was: " << bestTileScore; } } score = BASE_DESPERATE_SUCCESS; // ruuuuuuun action->target = _unit->getPosition() + run*3; action->target.x += RNG::generate(-10,10); action->target.y += RNG::generate(-10,10); action->target.z = _unit->getPosition().z + RNG::generate(-1,1); if (action->target.z < 0) { action->target.z = 0; } else if (action->target.z >= _game->getMapSizeZ()) { action->target.z = _unit->getPosition().z; } } // THINK, DAMN YOU tile = _game->getTile(action->target); if (!tile) { score = -100000; // no you can't quit the battlefield by running off the map. } else { _game->getTileEngine()->surveyXComThreatToTile(tile, action->target, _unit); if (tile->soldiersVisible == -1) continue; // you can't go there. if (tile->soldiersVisible && tile->closestSoldierDSqr <= 100 && tile->closestSoldierDSqr > 0) { score -= (100/tile->closestSoldierDSqr); } if (tile->soldiersVisible && tile->meanSoldierDSqr <= 200 && tile->meanSoldierDSqr > 0) { score -= (50/tile->meanSoldierDSqr); // less important than above } //if (!tile->_soldiersVisible) { Log(LOG_WARNING) << "No soldiers visible? Really?"; } if (!tile->soldiersVisible) { score += dist*4; // hooray! (4 because it's about 4 TUs to walk a tile?) } else { score -= tile->soldiersVisible * EXPOSURE_PENALTY; score += (dist > 9) ? 4 : dist; } if (_unit->getMainHandWeapon() && _unit->getMainHandWeapon()->getRules()->getBattleType() == BT_MELEE && _unit->getUnitRules() && _unit->getHealth() > _unit->getStats()->health/2) { // did you say "not charge?" KOMPRESSOR BREAK YOUR GLOWSTICK AND KOMPRESSOR EAT YOUR CANDY score -= (dist-1) * MELEE_TUNNELVISION_BONUS; if (score < -90000) score = -90000; charge = true; if (Options::getBool("traceAI") && !traceSpammed) { Log(LOG_INFO) << "Trying to get melee unit to do something."; traceSpammed = true; } } // strength in numbers but not in "grenade us!" huddles: if (tile->closestAlienDSqr < MAX_ALLY_DISTANCE && tile->closestAlienDSqr > MIN_ALLY_DISTANCE) score += ALLY_BONUS; if (tile->closestAlienDSqr <= MIN_ALLY_DISTANCE) score -= ALLY_BONUS; _game->getPathfinding()->setUnit(_unit); // because we can't just pass this around as a paramater, can we... no, that would be too simple if (tile->soldiersVisible && _game->getPathfinding()->bresenhamPath(tile->_closestSoldierPos, action->target, 0, false)) { score -= DIRECT_PATH_TO_TARGET_PENALTY; // not even partial cover? } _game->getPathfinding()->abortPath(); // clean up hypothetical path data if (_game->getPathfinding()->bresenhamPath(_aggroTarget->getPosition(), action->target, 0, false)) score -= DIRECT_PATH_PENALTY; // come on partial cover? _game->getPathfinding()->abortPath(); if (tile->getFire()) score -= FIRE_PENALTY; // maybe stop, drop, and roll? if (tile->getSmoke()) score -= SMOKE_PENALTY; // *cough* *cough* if (tile->getMapData(MapData::O_NORTHWALL) || tile->getMapData(MapData::O_WESTWALL)) score += WALL_BONUS; // hug the walls if (_game->getTileEngine()->faceWindow(action->target) != -1) score -= WINDOW_PENALTY; // a window is not cover. } if (score > bestTileScore) { // check if we can reach this tile _game->getPathfinding()->calculate(_unit, action->target); if (_game->getPathfinding()->getTotalTUCost() > _unit->getTimeUnits()) { score -= OVERREACH_PENALTY; // not gonna make it } else { if (tile->soldiersVisible == 0) score += _unit->getTimeUnits() - _game->getPathfinding()->getTotalTUCost(); // conserve TU if possible } if (score > bestTileScore && _game->getPathfinding()->getStartDirection() != -1) { // yay, we can get there and the overreach penalty didn't kill the score bestTileScore = score; bestTile = action->target; } _game->getPathfinding()->abortPath(); if (bestTileScore > FAST_PASS_THRESHOLD) coverFound = true; // good enough, gogogo } } if (Options::getBool("traceAI")) { Log(LOG_INFO) << _unit->getId() << " Taking cover with score " << bestTileScore << " after " << tries << " tries, at a tile spotted by " << ((tile=_game->getTile(bestTile)) ? tile->soldiersVisible : -666) << ". Action #" << action->number; //Log(LOG_INFO) << "Walking " << _game->getTileEngine()->distance(_unit->getPosition(), bestTile) << " squares or so."; } action->target = bestTile; if (_aggroTarget != 0) setAggroTarget(_aggroTarget); if (score <= -100000) { coverFound = false; action->type = BA_NONE; action->TU = 0; return; } } } if (action->type != BA_RETHINK && action->type != BA_WALK) action->TU = action->actor->getActionTUs(action->type, action->weapon); } if (_aggroTarget != 0) setAggroTarget(_aggroTarget); if (charge) action->desperate = true; }
/** * Runs any code the state needs to keep updating every * AI cycle. * @param action (possible) AI action to execute after thinking is done. */ void AggroBAIState::think(BattleAction *action) { action->type = BA_NONE; action->actor = _unit; /* Aggro is mainly either shooting a target or running towards it (melee). If we do no action here - we assume we lost aggro and will go back to patrol state. */ int aggression = _unit->getUnit()->getAggression(); _aggroTarget = 0; for (std::vector<BattleUnit*>::iterator j = _unit->getVisibleUnits()->begin(); j != _unit->getVisibleUnits()->end(); ++j) { _aggroTarget = (*j); } // if we currently see no target, we either can move to it's last seen position or loose aggro if (_aggroTarget == 0 || _aggroTarget->isOut()) { _timesNotSeen++; if (_timesNotSeen > _unit->getUnit()->getIntelligence() || aggression == 0) { // we lost aggro - going back to patrol state return; } // lets go looking where we've last seen him action->type = BA_WALK; action->target = _lastKnownPosition; } else { // if we see the target, we either can shoot him, or take cover. bool takeCover = true; int number = RNG::generate(0,100); // lost health, chances to take cover get bigger if (_unit->getHealth() < _unit->getUnit()->getHealth()) number += 10; if (aggression == 0 && number < 10) takeCover = false; if (aggression == 1 && number < 50) takeCover = false; if (aggression == 2 && number < 90) takeCover = false; if (!takeCover) { _timesNotSeen = 0; _lastKnownPosition = _aggroTarget->getPosition(); action->target = _aggroTarget->getPosition(); action->type = BA_NONE; // lets' evaluate if we could throw a grenade int tu = 4; // 4TUs for picking up the grenade // distance must be more than 6 tiles, otherwise it's too dangerous to play with explosives if (_game->getTileEngine()->distance(_unit->getPosition(), _aggroTarget->getPosition()) > 6) { // do we have a grenade on our belt? BattleItem *grenade = _unit->getGrenadeFromBelt(); // do we have enough TUs to prime and throw the grenade? if (grenade) { action->weapon = grenade; tu += _unit->getActionTUs(BA_PRIME, grenade); tu += _unit->getActionTUs(BA_THROW, grenade); if (tu <= _unit->getTimeUnits()) { // are we within range? if (ProjectileFlyBState::validThrowRange(action)) { grenade->setExplodeTurn(_game->getTurn()); _unit->spendTimeUnits(_unit->getActionTUs(BA_PRIME, grenade), false); action->type = BA_THROW; } } } } if (action->type == BA_NONE) { action->weapon = action->actor->getMainHandWeapon(); // out of ammo or no weapon or ammo at all, we have to take cover if (!action->weapon || !action->weapon->getAmmoItem() || !action->weapon->getAmmoItem()->getAmmoQuantity()) { takeCover = true; } else { if (RNG::generate(1,10) < 5) action->type = BA_SNAPSHOT; else action->type = BA_AUTOSHOT; tu = action->actor->getActionTUs(action->type, action->weapon); // enough time units to shoot? if (tu > _unit->getTimeUnits()) { takeCover = true; } } } } if (takeCover) { // the idea is to check within a 5 tile radius for a tile which is not seen by our aggroTarget // if there is no such tile, we run away from the target. action->type = BA_WALK; int tries = 0; bool coverFound = false; while (tries < 30 && !coverFound) { tries++; action->target = _unit->getPosition(); action->target.x += RNG::generate(-5,5); action->target.y += RNG::generate(-5,5); if (tries < 20) coverFound = !_game->getTileEngine()->visible(_aggroTarget, _game->getTile(action->target)); else coverFound = true; if (coverFound) { // check if we can reach this tile _game->getPathfinding()->calculate(_unit, action->target); if (_game->getPathfinding()->getStartDirection() == -1) { coverFound = false; } _game->getPathfinding()->abortPath(); } } } } action->TU = action->actor->getActionTUs(action->type, action->weapon); }
/** * Converts unit to a corpse (item). */ void UnitDieBState::convertUnitToCorpse() { _parent->getSave()->getBattleState()->showPsiButton(false); // in case the unit was unconscious _parent->getSave()->removeUnconsciousBodyItem(_unit); Position lastPosition = _unit->getPosition(); int size = _unit->getArmor()->getSize() - 1; BattleItem *itemToKeep = 0; bool dropItems = !Options::getBool("weaponSelfDestruction") || (_unit->getOriginalFaction() != FACTION_HOSTILE || _unit->getStatus() == STATUS_UNCONSCIOUS); // move inventory from unit to the ground for non-large units if (size == 0 && dropItems) { for (std::vector<BattleItem*>::iterator i = _unit->getInventory()->begin(); i != _unit->getInventory()->end(); ++i) { _parent->dropItem(_unit->getPosition(), (*i)); if (!(*i)->getRules()->isFixed()) { (*i)->setOwner(0); } else { itemToKeep = *i; } } } _unit->getInventory()->clear(); if (itemToKeep != 0) { _unit->getInventory()->push_back(itemToKeep); } // remove unit-tile link _unit->setTile(0); if (size == 0) { BattleItem *corpse = new BattleItem(_parent->getRuleset()->getItem(_unit->getArmor()->getCorpseItem()),_parent->getSave()->getCurrentItemId()); corpse->setUnit(_unit); _parent->dropItem(_unit->getPosition(), corpse, true); if (_parent->getSave()->getTile(lastPosition)->getUnit() == _unit) // check in case unit was displaced by another unit { _parent->getSave()->getTile(lastPosition)->setUnit(0); } } else { int i = 1; for (int y = 0; y <= size; y++) { for (int x = 0; x <= size; x++) { std::ostringstream ss; ss << _unit->getArmor()->getCorpseItem() << i; BattleItem *corpse = new BattleItem(_parent->getRuleset()->getItem(ss.str()),_parent->getSave()->getCurrentItemId()); corpse->setUnit(_unit); if (_parent->getSave()->getTile(lastPosition + Position(x,y,0))->getUnit() == _unit) // check in case unit was displaced by another unit { _parent->getSave()->getTile(lastPosition + Position(x,y,0))->setUnit(0); } _parent->dropItem(lastPosition + Position(x,y,0), corpse, true); i++; } } } }
/** * Updates item info. * @param action Pointer to an action. */ void InventoryState::invClick(Action *) { BattleItem *item = _inv->getSelectedItem(); _txtItem->setText(L""); _txtAmmo->setText(L""); _selAmmo->clear(); if (item != 0) { if (item->getUnit() && item->getUnit()->getStatus() == STATUS_UNCONSCIOUS) { _txtItem->setText(item->getUnit()->getName(_game->getLanguage())); } else { if (_game->getSavedGame()->isResearched(item->getRules()->getRequirements())) { _txtItem->setText(_game->getLanguage()->getString(item->getRules()->getName())); } else { _txtItem->setText(_game->getLanguage()->getString("STR_ALIEN_ARTIFACT")); } } std::wstringstream ss; if (item->getAmmoItem() != 0 && item->needsAmmo()) { ss << _game->getLanguage()->getString("STR_AMMO_ROUNDS_LEFT") << L'\x01' << item->getAmmoItem()->getAmmoQuantity(); SDL_Rect r; r.x = 0; r.y = 0; r.w = RuleInventory::HAND_W * RuleInventory::SLOT_W; r.h = RuleInventory::HAND_H * RuleInventory::SLOT_H; _selAmmo->drawRect(&r, Palette::blockOffset(0)+8); r.x++; r.y++; r.w -= 2; r.h -= 2; _selAmmo->drawRect(&r, 0); item->getAmmoItem()->getRules()->drawHandSprite(_game->getResourcePack()->getSurfaceSet("BIGOBS.PCK"), _selAmmo); } else if (item->getAmmoQuantity() != 0 && item->needsAmmo()) { ss << _game->getLanguage()->getString("STR_AMMO_ROUNDS_LEFT") << L'\x01' << item->getAmmoQuantity(); } _txtAmmo->setText(ss.str()); } updateStats(); }
/** * Shows item info. * @param action Pointer to an action. */ void InventoryState::invMouseOver(Action *) { if (_inv->getSelectedItem() != 0) { return; } BattleItem *item = _inv->getMouseOverItem(); if (item != 0) { if (item->getUnit() && item->getUnit()->getStatus() == STATUS_UNCONSCIOUS) { _txtItem->setText(item->getUnit()->getName(_game->getLanguage())); } else { if (_game->getSavedGame()->isResearched(item->getRules()->getRequirements())) { _txtItem->setText(tr(item->getRules()->getName())); } else { _txtItem->setText(tr("STR_ALIEN_ARTIFACT")); } } std::wstring s; if (item->getAmmoItem() != 0 && item->needsAmmo()) { s = tr("STR_AMMO_ROUNDS_LEFT").arg(item->getAmmoItem()->getAmmoQuantity()); SDL_Rect r; r.x = 0; r.y = 0; r.w = RuleInventory::HAND_W * RuleInventory::SLOT_W; r.h = RuleInventory::HAND_H * RuleInventory::SLOT_H; _selAmmo->drawRect(&r, _game->getMod()->getInterface("inventory")->getElement("grid")->color); r.x++; r.y++; r.w -= 2; r.h -= 2; _selAmmo->drawRect(&r, Palette::blockOffset(0)+15); item->getAmmoItem()->getRules()->drawHandSprite(_game->getMod()->getSurfaceSet("BIGOBS.PCK"), _selAmmo); _updateTemplateButtons(false); } else { _selAmmo->clear(); _updateTemplateButtons(!_tu); } if (item->getAmmoQuantity() != 0 && item->needsAmmo()) { s = tr("STR_AMMO_ROUNDS_LEFT").arg(item->getAmmoQuantity()); } else if (item->getRules()->getBattleType() == BT_MEDIKIT) { s = tr("STR_MEDI_KIT_QUANTITIES_LEFT").arg(item->getPainKillerQuantity()).arg(item->getStimulantQuantity()).arg(item->getHealQuantity()); } _txtAmmo->setText(s); } else { if (_currentTooltip.empty()) { _txtItem->setText(L""); } _txtAmmo->setText(L""); _selAmmo->clear(); _updateTemplateButtons(!_tu); } }
/** * Runs any code the state needs to keep updating every * AI cycle. * @param action (possible) AI action to execute after thinking is done. */ void AggroBAIState::think(BattleAction *action) { action->type = BA_RETHINK; action->actor = _unit; /* Aggro is mainly either shooting a target or running towards it (melee). If we do no action here - we assume we lost aggro and will go back to patrol state. */ int aggression = _unit->getAggression(); _aggroTarget = 0; int unitsSpottingMe = _game->getSpottingUnits(_unit); /* psionic targetting: pick from any of the "exposed" units. exposed means they have been previously spotted, and are therefore "known" to the AI, regardless of whether we can see them or not, because we're psychic. */ if (_unit->getStats()->psiSkill && _unit->getType() != "SOLDIER" && _game->getExposedUnits()->size() > 0 && RNG::generate(0, 100) > 66) { int psiAttackStrength = _unit->getStats()->psiSkill * _unit->getStats()->psiStrength / 50; int chanceToAttack = 0; int tries = 0; for (std::vector<BattleUnit*>::const_iterator i = _game->getExposedUnits()->begin(); i != _game->getExposedUnits()->end() && tries < 80; ++i) { // don't target tanks if ((*i)->getArmor()->getSize() != 2) { int chanceToAttackMe = psiAttackStrength + ((*i)->getStats()->psiSkill * -0.4) - (_game->getTileEngine()->distance(_unit->getPosition(), (*i)->getPosition()) / 2) - ((*i)->getStats()->psiStrength) + (RNG::generate(0, 50)) + 55; if (chanceToAttackMe > chanceToAttack) { chanceToAttack = chanceToAttackMe; _aggroTarget = *i; } } ++tries; } if (!_aggroTarget) chanceToAttack = 0; if (chanceToAttack) { if (_unit->getMainHandWeapon() && _unit->getMainHandWeapon()->getAmmoItem()) { if (_unit->getMainHandWeapon()->getAmmoItem()->getRules()->getPower() >= chanceToAttack) { chanceToAttack = 0; _aggroTarget = 0; } } else { if (RNG::generate(35, 155) >= chanceToAttack) { chanceToAttack = 0; _aggroTarget = 0; } } if (chanceToAttack >= 30) { int controlOrPanic = 60; int morale = _aggroTarget->getMorale(); int bravery = _aggroTarget->getStats()->bravery; if (bravery > 60) controlOrPanic += 15; if ( bravery < 40) controlOrPanic -= 15; if (morale >= 40) { if (morale - 10 * bravery < 50) controlOrPanic += 15; } else { controlOrPanic -= 15; } if (!morale) { controlOrPanic = 0; } if (RNG::generate(0, 100) >= controlOrPanic) { action->type = BA_MINDCONTROL; action->target = _aggroTarget->getPosition(); } else { action->type = BA_PANIC; action->target = _aggroTarget->getPosition(); } } else if (chanceToAttack) { action->type = BA_PANIC; action->target = _aggroTarget->getPosition(); } } } /* * waypoint targetting: pick from any units currently spotted by our allies. */ if (_unit->getMainHandWeapon() && _unit->getMainHandWeapon()->getAmmoItem() && _unit->getMainHandWeapon()->getRules()->isWaypoint() && _unit->getType() != "SOLDIER") { for (std::vector<BattleUnit*>::const_iterator i = _game->getUnits()->begin(); i != _game->getUnits()->end() && _aggroTarget == 0; ++i) { if ((*i)->getFaction() == _unit->getFaction()) { for (std::vector<BattleUnit*>::const_iterator j = (*i)->getVisibleUnits()->begin(); j != (*i)->getVisibleUnits()->end() && _aggroTarget == 0; ++j) { _game->getPathfinding()->calculate(_unit, (*j)->getPosition(), *j); if (_game->getPathfinding()->getStartDirection() != -1 && explosiveEfficacy((*j)->getPosition(), _unit, (_unit->getMainHandWeapon()->getAmmoItem()->getRules()->getPower()/20)+1, action->diff)) { _aggroTarget = *j; } _game->getPathfinding()->abortPath(); } } } if (_aggroTarget != 0) { action->weapon = _unit->getMainHandWeapon(); action->type = BA_LAUNCH; action->TU = action->actor->getActionTUs(action->type, action->weapon); action->waypoints.clear(); int PathDirection; int CollidesWith; Position LastWayPoint = _unit->getPosition(); Position LastPosition = _unit->getPosition(); Position CurrentPosition = _unit->getPosition(); Position DirectionVector; _game->getPathfinding()->calculate(_unit, _aggroTarget->getPosition(), _aggroTarget); PathDirection = _game->getPathfinding()->dequeuePath(); while (PathDirection != -1) { LastPosition = CurrentPosition; _game->getPathfinding()->directionToVector(PathDirection, &DirectionVector); CurrentPosition = CurrentPosition + DirectionVector; Position voxelPosA ((CurrentPosition.x * 16)+8, (CurrentPosition.y * 16)+8, (CurrentPosition.z * 24)+12); Position voxelPosb ((LastWayPoint.x * 16)+8, (LastWayPoint.y * 16)+8, (LastWayPoint.z * 24)+12); CollidesWith = _game->getTileEngine()->calculateLine(voxelPosA, voxelPosb, false, 0, _unit, true); if (CollidesWith > -1 && CollidesWith < 4) { action->waypoints.push_back(LastPosition); LastWayPoint = LastPosition; } else if (CollidesWith == 4) { BattleUnit* target = _game->getTile(CurrentPosition)->getUnit(); if (target == _aggroTarget) { action->waypoints.push_back(CurrentPosition); LastWayPoint = CurrentPosition; } } PathDirection = _game->getPathfinding()->dequeuePath(); } action->target = action->waypoints.front(); if( action->waypoints.size() > 6 + (action->diff * 2) || LastWayPoint != _aggroTarget->getPosition()) { action->type = BA_RETHINK; } } } /* * Regular targetting: we can see an enemy, or an enemy can see us (in case we need to take cover) */ if (_unit->getVisibleUnits()->size() > 0 || unitsSpottingMe) { for (std::vector<BattleUnit*>::iterator j = _unit->getVisibleUnits()->begin(); j != _unit->getVisibleUnits()->end(); ++j) { //pick closest living unit if (!_aggroTarget || _game->getTileEngine()->distance(_unit->getPosition(), (*j)->getPosition()) < _game->getTileEngine()->distance(_unit->getPosition(), _aggroTarget->getPosition())) { if(!(*j)->isOut()) _aggroTarget = (*j); } } // if we currently see no target, we either can move to it's last seen position or lose aggro if (_aggroTarget == 0 && _lastKnownTarget != 0) { _timesNotSeen++; if (_timesNotSeen > _unit->getIntelligence() || aggression == 0) { // we lost aggro - going back to patrol state return; } // lets go looking where we've last seen him action->type = BA_WALK; action->target = _lastKnownPosition; } else if (_aggroTarget != 0) { // if we see the target, we either can shoot him, or take cover. bool takeCover = true; bool charge = false; _unit->setCharging(0); int number = RNG::generate(0,100); // extra 5% chance per unit that sees us number += unitsSpottingMe * 5; // lost health, chances to take cover get bigger if (_unit->getHealth() < _unit->getStats()->health) number += 10; // aggrotarget has no weapon - chances of take cover get smaller if (!_aggroTarget->getMainHandWeapon()) number -= 50; if (aggression == 0 && number < 10) takeCover = false; if (aggression == 1 && number < 50) takeCover = false; if (aggression == 2 && number < 90) takeCover = false; // we're using melee, so CHAAAAAAAARGE!!!!! if (_unit->getMainHandWeapon() && _unit->getMainHandWeapon()->getRules()->getBattleType() == BT_MELEE) { _unit->lookAt(_aggroTarget->getPosition() + Position(_unit->getArmor()->getSize()-1, _unit->getArmor()->getSize()-1, 0), false); while (_unit->getStatus() == STATUS_TURNING) _unit->turn(); if (_game->getTileEngine()->validMeleeRange(_unit, _aggroTarget)) { action->target = _aggroTarget->getPosition(); action->weapon = action->actor->getMainHandWeapon(); action->type = BA_HIT; charge = true; } else { takeCover = true; bool targetFound = false; int distance = 200; int size = action->actor->getArmor()->getSize()-1; int targetsize = _aggroTarget->getArmor()->getSize()-1; for (int x = 0 - size; x <= targetsize; ++x) { for (int y = 0 - size; y <= targetsize; ++y) { if (!(x == 0 && y == 0)) { Position checkPath = _aggroTarget->getPosition() + Position (x, y, 0); _game->getPathfinding()->calculate(action->actor, checkPath, 0); int newDistance = _game->getTileEngine()->distance(action->actor->getPosition(), checkPath); bool valid = _game->getTileEngine()->validMeleeRange(checkPath, -1, action->actor->getArmor()->getSize(), action->actor->getHeight(), _aggroTarget); if (_game->getPathfinding()->getStartDirection() != -1 && valid && newDistance < distance) { // CHAAAAAAARGE! action->target = checkPath; action->type = BA_WALK; charge = true; _unit->setCharging(_aggroTarget); distance = newDistance; } _game->getPathfinding()->abortPath(); } } } } } if (!takeCover && !charge) { _timesNotSeen = 0; _lastKnownPosition = _aggroTarget->getPosition(); action->target = _aggroTarget->getPosition(); action->type = BA_NONE; // lets' evaluate if we could throw a grenade int tu = 4; // 4TUs for picking up the grenade // do we have a grenade on our belt? BattleItem *grenade = _unit->getGrenadeFromBelt(); // distance must be more than X tiles, otherwise it's too dangerous to play with explosives if (grenade && explosiveEfficacy(_aggroTarget->getPosition(), _unit, (grenade->getRules()->getPower()/10)+1, action->diff)) { if((_unit->getFaction() == FACTION_NEUTRAL && _aggroTarget->getFaction() == FACTION_HOSTILE) || _unit->getFaction() == FACTION_HOSTILE) { action->weapon = grenade; tu += _unit->getActionTUs(BA_PRIME, grenade); tu += _unit->getActionTUs(BA_THROW, grenade); // do we have enough TUs to prime and throw the grenade? if (tu <= _unit->getStats()->tu) { // are we within range? if (ProjectileFlyBState::validThrowRange(action)) { grenade->setExplodeTurn(_game->getTurn()); _unit->spendTimeUnits(_unit->getActionTUs(BA_PRIME, grenade)); action->type = BA_THROW; } } } } if (action->type == BA_NONE) { action->weapon = action->actor->getMainHandWeapon(); // out of ammo or no weapon or ammo at all, we have to take cover if (!action->weapon) { takeCover = true; } else { if(action->weapon->getAmmoItem() && ((_unit->getFaction() == FACTION_NEUTRAL && _aggroTarget->getFaction() == FACTION_HOSTILE) || _unit->getFaction() == FACTION_HOSTILE)) { if (action->weapon->getAmmoItem()->getRules()->getDamageType() != DT_HE || explosiveEfficacy(_aggroTarget->getPosition(), _unit, (action->weapon->getAmmoItem()->getRules()->getPower() / 10) +1, action->diff)) { if (RNG::generate(1,10) < 5 && action->weapon->getAmmoQuantity() > 2) { action->type = BA_AUTOSHOT; } else { action->type = BA_SNAPSHOT; } } tu = action->actor->getActionTUs(action->type, action->weapon); // enough time units to shoot? if (tu > _unit->getTimeUnits()) { takeCover = true; } } else takeCover = true; } } } if (takeCover && !charge) { // the idea is to check within a 11x11 tile squarefor a tile which is not seen by our aggroTarget // if there is no such tile, we run away from the target. action->type = BA_WALK; int tries = 0; bool coverFound = false; int x_search_sign = RNG::generate(0, 1) ? 1 : -1; // randomize the direction of the search for lack of a better heuristic int y_search_sign = RNG::generate(0, 1) ? 1 : -1; int dx = _unit->getPosition().x - _aggroTarget->getPosition().x; // 2d vector in the direction away from the aggro target int dy = _unit->getPosition().y - _aggroTarget->getPosition().y; int dsqr = dx*dx + dy*dy; int runx = _unit->getPosition().x + (dx * 7 * 7) / dsqr; int runy = _unit->getPosition().y + (dy * 7 * 7) / dsqr; while (tries < 150 && !coverFound) { tries++; action->target = _unit->getPosition(); if (tries < 121) { // looking for cover action->target.x += x_search_sign * ((tries%11) - 5); action->target.y += y_search_sign * ((tries/11) - 5); coverFound = !_game->getTileEngine()->visible(_aggroTarget, _game->getTile(action->target)); } else { // trying to run the hell away action->target.x = runx + RNG::generate(-5,5); action->target.y = runy + RNG::generate(-5,5); coverFound = true; } if (coverFound) { // check if we can reach this tile _game->getPathfinding()->calculate(_unit, action->target); if (_game->getPathfinding()->getStartDirection() == -1) { coverFound = false; } _game->getPathfinding()->abortPath(); } } } } if (action->type != BA_RETHINK && action->type != BA_WALK) action->TU = action->actor->getActionTUs(action->type, action->weapon); } if (_aggroTarget != 0) setAggroTarget(_aggroTarget); }
/** * 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)); } } }