/** * Select next soldier. * @param action Pointer to an action. */ void BattlescapeState::btnNextSoldierClick(Action *action) { if (_popup) return; BattleUnit *unit = _battleGame->selectNextPlayerUnit(); updateSoldierInfo(unit); if (unit) _map->centerOnPosition(unit->getPosition()); }
void InventoryState::onAutoequip(Action *) { // don't act when moving items if (_inv->getSelectedItem() != 0) { return; } BattleUnit *unit = _battleGame->getSelectedUnit(); Tile *groundTile = unit->getTile(); std::vector<BattleItem*> *groundInv = groundTile->getInventory(); Mod *mod = _game->getMod(); RuleInventory *groundRuleInv = mod->getInventory("STR_GROUND", true); int worldShade = _battleGame->getGlobalShade(); std::vector<BattleUnit*> units; units.push_back(unit); BattlescapeGenerator::autoEquip(units, mod, NULL, groundInv, groundRuleInv, worldShade, true, true); // refresh ui _inv->arrangeGround(false); updateStats(); _refreshMouse(); // give audio feedback _game->getMod()->getSoundByDepth(_battleGame->getDepth(), Mod::ITEM_DROP)->play(); }
/** * Calculates the trajectory for a curved path. * @param accuracy The unit's accuracy. * @param doTestTrajectory * @return True when a trajectory is possible. */ int Projectile::calculateThrow(double accuracy, bool doTestTrajectory) { Tile *targetTile = _save->getTile(_action.target); Position originVoxel = _save->getTileEngine()->getOriginVoxel(_action, 0); Position targetVoxel = _action.target * Position(16,16,24) + Position(8,8, (2 + -targetTile->getTerrainLevel())); if (_action.type != BA_THROW) { BattleUnit *tu = targetTile->getUnit(); if (!tu && _action.target.z > 0 && targetTile->hasNoFloor(0)) tu = _save->getTile(_action.target - Position(0, 0, 1))->getUnit(); if (tu) { targetVoxel.z += ((tu->getHeight()/2) + tu->getFloatHeight()) - 2; } } double curvature = 0.0; int retVal = V_OUTOFBOUNDS; if (_save->getTileEngine()->validateThrow(_action, originVoxel, targetVoxel, &curvature, &retVal)) { if (doTestTrajectory) { return V_FLOOR; // retVal; } int test = V_OUTOFBOUNDS; // finally do a line calculation and store this trajectory, make sure it's valid. while (test == V_OUTOFBOUNDS) { Position deltas = targetVoxel; // apply some accuracy modifiers applyAccuracy(originVoxel, &deltas, accuracy, true, _save->getTile(_action.target), false); //calling for best flavor deltas -= targetVoxel; _trajectory.clear(); test = _save->getTileEngine()->calculateParabola(originVoxel, targetVoxel, true, &_trajectory, _action.actor, curvature, deltas); Position endPoint = _trajectory.back(); endPoint.x /= 16; endPoint.y /= 16; endPoint.z /= 24; Tile *endTile = _save->getTile(endPoint); // check if the item would land on a tile with a blocking object if (_action.type == BA_THROW && endTile && endTile->getMapData(MapData::O_OBJECT) && endTile->getMapData(MapData::O_OBJECT)->getTUCost(MT_WALK) == 255) { test = V_OUTOFBOUNDS; } } return retVal; } return V_OUTOFBOUNDS; }
/* * Think! */ void UnitDieBState::think() { if (_unit->getStatus() == STATUS_TURNING) { _unit->turn(); } else if (_unit->getStatus() == STATUS_STANDING) { _unit->startFalling(); } else if (_unit->getStatus() == STATUS_COLLAPSING) { _unit->keepFalling(); } if (_unit->getStatus() == STATUS_DEAD || _unit->getStatus() == STATUS_UNCONSCIOUS) { _parent->getMap()->setUnitDying(false); if (!_unit->getVisibleUnits()->empty()) { _unit->clearVisibleUnits(); } if (_unit->getTurnsExposed()) { _unit->setTurnsExposed(0); _parent->getSave()->updateExposedUnits(); } if (!_unit->getSpawnUnit().empty()) { // converts the dead zombie to a chryssalid BattleUnit *newUnit = _parent->convertUnit(_unit, _unit->getSpawnUnit()); newUnit->lookAt(_originalDir); } else { convertUnitToCorpse(); } _parent->getTileEngine()->calculateUnitLighting(); _parent->popState(); if (_unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH) { _unit->instaKill(); if (_damageType != DT_STUN && _damageType != DT_HE) { Position p = Position(_unit->getPosition().x * 16, _unit->getPosition().y * 16, _unit->getPosition().z * 24); _parent->statePushNext(new ExplosionBState(_parent, p, 0, _unit, 0)); } } } _parent->getMap()->cacheUnit(_unit); }
/** * Kneel/Standup. * @param action Pointer to an action. */ void BattlescapeState::btnKneelClick(Action *action) { if (_popup) return; // TODO: check for timeunits... check for FOV... BattleUnit *bu = _battleGame->getSelectedUnit(); if (bu) { if (bu->spendTimeUnits(bu->isKneeled()?8:4, _battleGame->getDebugMode())) { bu->kneel(!bu->isKneeled()); _map->cacheUnits(); updateSoldierInfo(bu); } } }
/** * Update soldier stats when the soldier changes. */ void InventoryState::init() { BattleUnit *unit = _battleGame->getSelectedUnit(); unit->setCache(0); _soldier->clear(); _btnRank->clear(); _txtName->setText(unit->getName(_game->getLanguage())); _inv->setSelectedUnit(unit); Soldier *s = _game->getSavedGame()->getSoldier(unit->getId()); if (s) { SurfaceSet *texture = _game->getResourcePack()->getSurfaceSet("BASEBITS.PCK"); texture->getFrame(s->getRankSprite())->setX(0); texture->getFrame(s->getRankSprite())->setY(0); texture->getFrame(s->getRankSprite())->blit(_btnRank); std::string look = s->getArmor()->getSpriteInventory(); if (s->getGender() == GENDER_MALE) look += "M"; else look += "F"; if (s->getLook() == LOOK_BLONDE) look += "0"; if (s->getLook() == LOOK_BROWNHAIR) look += "1"; if (s->getLook() == LOOK_ORIENTAL) look += "2"; if (s->getLook() == LOOK_AFRICAN) look += "3"; look += ".SPK"; if (!CrossPlatform::fileExists(CrossPlatform::getDataFile("UFOGRAPH/" + look))) { look = s->getArmor()->getSpriteInventory() + ".SPK"; } _game->getResourcePack()->getSurface(look)->blit(_soldier); } if (_tu) { std::wstringstream ss; ss << _game->getLanguage()->getString("STR_TUS") << L'\x01' << unit->getTimeUnits(); _txtTus->setText(ss.str()); } }
/** * Updates the soldier stats (Weight, TU). */ void InventoryState::updateStats() { BattleUnit *unit = _battleGame->getSelectedUnit(); if (_showMoreStatsInInventoryView) { int Weight = unit->getCarriedWeight(_inv->getSelectedItem()); std::wstringstream ss; ss << _game->getLanguage()->getString("STR_WEIGHT") << L'\x01' << Weight << " /" << unit->getStats()->strength; _txtWeight->setText(ss.str()); if (Weight > unit->getStats()->strength) _txtWeight->setSecondaryColor(Palette::blockOffset(2)); else _txtWeight->setSecondaryColor(Palette::blockOffset(1)); } if (_tu) { std::wstringstream ss; ss << _game->getLanguage()->getString("STR_TUS") << L'\x01' << unit->getTimeUnits(); _txtTus->setText(ss.str()); } }
/** * Update soldier stats when the soldier changes. */ void InventoryState::init() { BattleUnit *unit = _battleGame->getSelectedUnit(); unit->setCache(0); _soldier->clear(); _btnRank->clear(); _txtName->setText(unit->getUnit()->getName(_game->getLanguage())); _inv->setSelectedUnit(unit); Soldier *s = dynamic_cast<Soldier*>(unit->getUnit()); if (s) { SurfaceSet *texture = _game->getResourcePack()->getSurfaceSet("BASEBITS.PCK"); texture->getFrame(s->getRankSprite())->setX(0); texture->getFrame(s->getRankSprite())->setY(0); texture->getFrame(s->getRankSprite())->blit(_btnRank); std::string look = "MAN_0"; if (s->getGender() == GENDER_MALE) look += "M"; else look += "F"; if (s->getLook() == LOOK_BLONDE) look += "0"; if (s->getLook() == LOOK_BROWNHAIR) look += "1"; if (s->getLook() == LOOK_ORIENTAL) look += "2"; if (s->getLook() == LOOK_AFRICAN) look += "3"; look += ".SPK"; _game->getResourcePack()->getSurface(look)->blit(_soldier); } if (_tu) { std::wstringstream ss; ss << _game->getLanguage()->getString("STR_TUS") << L'\x01' << unit->getTimeUnits(); _txtTus->setText(ss.str()); } }
void InventoryState::onClearInventory(Action *) { // don't accept clicks when moving items if (_inv->getSelectedItem() != 0) { return; } BattleUnit *unit = _battleGame->getSelectedUnit(); std::vector<BattleItem*> *unitInv = unit->getInventory(); Tile *groundTile = unit->getTile(); _clearInventory(_game, unitInv, groundTile); // refresh ui _inv->arrangeGround(false); updateStats(); _refreshMouse(); // give audio feedback _game->getMod()->getSoundByDepth(_battleGame->getDepth(), Mod::ITEM_DROP)->play(); }
/* * This function popups a context sensitive list of actions the user can choose from. * Some actions result in a change of gamestate. * @param item Item the user clicked on (righthand/lefthand) */ void BattlescapeState::handleItemClick(BattleItem *item) { // make sure there is an item, and the battlescape is in an idle state if (item && _states.empty()) { BattleUnit *bu = _battleGame->getSelectedUnit(); // Build up the popup menu int id = 0; std::wstring strAcc = _game->getLanguage()->getString("STR_ACC"); std::wstring strTU = _game->getLanguage()->getString("STR_TUS"); std::wstringstream ss1, ss2; ss1 << strAcc.c_str() << (int)floor(bu->getThrowingAccuracy() * 100) << "%"; ss2 << strTU.c_str() << (int)floor(bu->getUnit()->getTimeUnits() * 0.25); _actionMenu[id]->setAction(BA_THROW, _game->getLanguage()->getString("STR_THROW"), ss1.str(), ss2.str()); _actionMenu[id]->setVisible(true); id++; ss1.str(L""); ss2.str(L""); if (item->getRules()->getAccuracyAuto() != 0) { ss1 << strAcc.c_str() << (int)floor(bu->getFiringAccuracy(item->getRules()->getAccuracyAuto()) * 100) << "%"; ss2 << strTU.c_str() << 0; _actionMenu[id]->setAction(BA_AUTOSHOT, _game->getLanguage()->getString("STR_AUTO_SHOT"), ss1.str(), ss2.str()); _actionMenu[id]->setVisible(true); id++; ss1.str(L""); ss2.str(L""); } if (item->getRules()->getAccuracySnap() != 0) { ss1 << strAcc.c_str() << (int)floor(bu->getFiringAccuracy(item->getRules()->getAccuracySnap()) * 100) << "%"; ss2 << strTU.c_str() << 0; _actionMenu[id]->setAction(BA_SNAPSHOT, _game->getLanguage()->getString("STR_SNAP_SHOT"), ss1.str(), ss2.str()); _actionMenu[id]->setVisible(true); id++; ss1.str(L""); ss2.str(L""); } if (item->getRules()->getAccuracyAimed() != 0) { ss1 << strAcc.c_str() << (int)floor(bu->getFiringAccuracy(item->getRules()->getAccuracyAimed()) * 100) << "%"; ss2 << strTU.c_str() << 0; _actionMenu[id]->setAction(BA_AIMEDSHOT, _game->getLanguage()->getString("STR_AIMED_SHOT"), ss1.str(), ss2.str()); _actionMenu[id]->setVisible(true); id++; ss1.str(L""); ss2.str(L""); } _map->setCursorType(CT_NONE); _popup = true; // TODO other gamestates: scanner/medikit // this should be returned by the popup menu, but is now hardcoded to test without popup menu _selectedItem = item; } }
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(); }
/** * Updates all soldier stats when the soldier changes. */ void InventoryState::init() { State::init(); BattleUnit *unit = _battleGame->getSelectedUnit(); // no selected unit, close inventory if (unit == 0) { btnOkClick(0); return; } // skip to the first unit with inventory if (!unit->hasInventory()) { if (_parent) { _parent->selectNextPlayerUnit(false, false, true); } else { _battleGame->selectNextPlayerUnit(false, false, true); } // no available unit, close inventory if (_battleGame->getSelectedUnit() == 0 || !_battleGame->getSelectedUnit()->hasInventory()) { // starting a mission with just vehicles btnOkClick(0); return; } else { unit = _battleGame->getSelectedUnit(); } } unit->setCache(0); _soldier->clear(); _rank->clear(); _txtName->setBig(); _txtName->setText(unit->getName(_game->getLanguage())); _inv->setSelectedUnit(unit); Soldier *s = unit->getGeoscapeSoldier(); if (s) { SurfaceSet *texture = _game->getMod()->getSurfaceSet("SMOKE.PCK"); texture->getFrame(20 + s->getRank())->setX(0); texture->getFrame(20 + s->getRank())->setY(0); texture->getFrame(20 + s->getRank())->blit(_rank); std::string look = s->getArmor()->getSpriteInventory(); if (s->getGender() == GENDER_MALE) look += "M"; else look += "F"; if (s->getLook() == LOOK_BLONDE) look += "0"; if (s->getLook() == LOOK_BROWNHAIR) look += "1"; if (s->getLook() == LOOK_ORIENTAL) look += "2"; if (s->getLook() == LOOK_AFRICAN) look += "3"; look += ".SPK"; const std::set<std::string> &ufographContents = FileMap::getVFolderContents("UFOGRAPH"); std::string lcaseLook = look; std::transform(lcaseLook.begin(), lcaseLook.end(), lcaseLook.begin(), tolower); if (ufographContents.find("lcaseLook") == ufographContents.end() && !_game->getMod()->getSurface(look)) { look = s->getArmor()->getSpriteInventory() + ".SPK"; } _game->getMod()->getSurface(look)->blit(_soldier); } else { Surface *armorSurface = _game->getMod()->getSurface(unit->getArmor()->getSpriteInventory()); if (armorSurface) { armorSurface->blit(_soldier); } } updateStats(); _refreshMouse(); }
/** * 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))); } } }
/** * Calculates the effects of the explosion. */ void ExplosionBState::explode() { bool terrainExplosion = false; SavedBattleGame *save = _parent->getSave(); // after the animation is done, the real explosion/hit takes place if (_item) { if (!_unit && _item->getPreviousOwner()) { _unit = _item->getPreviousOwner(); } BattleUnit *victim = 0; if (_areaOfEffect) { save->getTileEngine()->explode(_center, _power, _item->getRules()->getDamageType(), _item->getRules()->getExplosionRadius(), _unit); } else if (!_cosmetic) { ItemDamageType type = _item->getRules()->getDamageType(); victim = save->getTileEngine()->hit(_center, _power, type, _unit); } // check if this unit turns others into zombies if (!_item->getRules()->getZombieUnit().empty() && victim && victim->getArmor()->getSize() == 1 && (victim->getGeoscapeSoldier() || victim->getUnitRules()->getRace() == "STR_CIVILIAN") && victim->getSpawnUnit().empty()) { // converts the victim to a zombie on death victim->setRespawn(true); victim->setSpawnUnit(_item->getRules()->getZombieUnit()); } } 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; } if (DT != DT_HE) { _tile->setExplosive(0,0,true); } save->getTileEngine()->explode(_center, _power, DT, _power/10); terrainExplosion = true; } if (!_tile && !_item) { int radius = 6; // explosion not caused by terrain or an item, must be by a unit (cyberdisc) if (_unit && (_unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH || _unit->getSpecialAbility() == SPECAB_BURN_AND_EXPLODE)) { radius = _parent->getMod()->getItem(_unit->getArmor()->getCorpseGeoscape())->getExplosionRadius(); } save->getTileEngine()->explode(_center, _power, DT_HE, radius); terrainExplosion = true; } if (!_cosmetic) { // now check for new casualties _parent->checkForCasualties(_item, _unit, false, terrainExplosion); } // 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); _unit->setCache(0); } _parent->getMap()->cacheUnits(); _parent->popState(); // check for terrain explosions Tile *t = save->getTileEngine()->checkForTerrainExplosions(); if (t) { Position p = Position(t->getPosition().x * 16, t->getPosition().y * 16, t->getPosition().z * 24); p += Position(8,8,0); _parent->statePushFront(new ExplosionBState(_parent, p, 0, _unit, t)); } if (_item && (_item->getRules()->getBattleType() == BT_GRENADE || _item->getRules()->getBattleType() == BT_PROXIMITYGRENADE)) { for (std::vector<BattleItem*>::iterator j = _parent->getSave()->getItems()->begin(); j != _parent->getSave()->getItems()->end(); ++j) { if (_item->getId() == (*j)->getId()) { _parent->getSave()->removeItem(_item); break; } } } }
/** * 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); }
/** * 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)); } } } }
/** * Calculates the effects of the explosion. */ void ExplosionBState::explode() { bool terrainExplosion = false; SavedBattleGame *save = _parent->getSave(); // after the animation is done, the real explosion/hit takes place if (_item) { if (!_unit && _item->getPreviousOwner()) { _unit = _item->getPreviousOwner(); } if (_areaOfEffect) { save->getTileEngine()->explode(_center, _power, _item->getRules()->getDamageType(), _item->getRules()->getExplosionRadius(), _unit); } else { BattleUnit *victim = save->getTileEngine()->hit(_center, _power, _item->getRules()->getDamageType(), _unit); // check if this unit turns others into zombies if (!_unit->getZombieUnit().empty() && victim && victim->getArmor()->getSize() == 1 && victim->getSpawnUnit().empty() && victim->getOriginalFaction() != FACTION_HOSTILE) { // converts the victim to a zombie on death victim->setSpecialAbility(SPECAB_RESPAWN); victim->setSpawnUnit(_unit->getZombieUnit()); } } } if (_tile) { save->getTileEngine()->explode(_center, _power, DT_HE, _power/10); terrainExplosion = true; } if (!_tile && !_item) { // explosion not caused by terrain or an item, must be by a unit (cyberdisc) save->getTileEngine()->explode(_center, _power, DT_HE, 6); terrainExplosion = true; } // now check for new casualties _parent->checkForCasualties(_item, _unit, false, terrainExplosion); // 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); } _parent->getMap()->cacheUnits(); _parent->popState(); // check for terrain explosions Tile *t = save->getTileEngine()->checkForTerrainExplosions(); if (t) { Position p = Position(t->getPosition().x * 16, t->getPosition().y * 16, t->getPosition().z * 24); _parent->statePushFront(new ExplosionBState(_parent, p, 0, _unit, t)); } if (_item && (_item->getRules()->getBattleType() == BT_GRENADE || _item->getRules()->getBattleType() == BT_PROXIMITYGRENADE)) { for (std::vector<BattleItem*>::iterator j = _parent->getSave()->getItems()->begin(); j != _parent->getSave()->getItems()->end(); ++j) { if (_item->getId() == (*j)->getId()) { delete *j; _parent->getSave()->getItems()->erase(j); break; } } } }
/** * calculateTrajectory. * @return true when a trajectory is possible. */ bool Projectile::calculateThrow(double accuracy) { Position originVoxel, targetVoxel; bool foundCurve = false; // object blocking - can't throw here if (_action.type == BA_THROW &&_save->getTile(_action.target) && _save->getTile(_action.target)->getMapData(MapData::O_OBJECT) && _save->getTile(_action.target)->getMapData(MapData::O_OBJECT)->getTUCost(MT_WALK) == 255) { return false; } originVoxel = Position(_origin.x*16 + 8, _origin.y*16 + 8, _origin.z*24); originVoxel.z += -_save->getTile(_origin)->getTerrainLevel(); BattleUnit *bu = _save->getTile(_origin)->getUnit(); if(!bu) bu = _save->getTile(Position(_origin.x, _origin.y, _origin.z-1))->getUnit(); originVoxel.z += bu->getHeight() + bu->getFloatHeight(); originVoxel.z -= 3; if (originVoxel.z >= (_origin.z + 1)*24) { _origin.z++; } // determine the target voxel. // aim at the center of the floor targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24 + 2); // we try 4 different curvatures to try and reach our goal. double curvature = 1.0; while (!foundCurve && curvature < 5.0) { _save->getTileEngine()->calculateParabola(originVoxel, targetVoxel, false, &_trajectory, bu, curvature, 1.0); if ((int)_trajectory.at(0).x/16 == (int)targetVoxel.x/16 && (int)_trajectory.at(0).y/16 == (int)targetVoxel.y/16 && (int)_trajectory.at(0).z/24 == (int)targetVoxel.z/24) { foundCurve = true; } else { curvature += 1.0; } _trajectory.clear(); } if ( AreSame(curvature, 5.0) ) { return false; } // apply some accuracy modifiers if (accuracy > 1) accuracy = 1; static const double maxDeviation = 0.08; static const double minDeviation = 0; double baseDeviation = (maxDeviation - (maxDeviation * accuracy)) + minDeviation; double deviation = RNG::boxMuller(0, baseDeviation); _trajectory.clear(); // finally do a line calculation and store this trajectory. _save->getTileEngine()->calculateParabola(originVoxel, targetVoxel, true, &_trajectory, bu, curvature, 1.0 + deviation); Position endPoint = _trajectory.at(_trajectory.size() - 1); endPoint.x /= 16; endPoint.y /= 16; endPoint.z /= 24; // check if the item would land on a tile with a blocking object, if so then we let it fly without deviation, it must land on a valid tile in that case if (_save->getTile(endPoint) && _save->getTile(endPoint)->getMapData(MapData::O_OBJECT) && _save->getTile(endPoint)->getMapData(MapData::O_OBJECT)->getTUCost(MT_WALK) == 255) { _trajectory.clear(); // finally do a line calculation and store this trajectory. _save->getTileEngine()->calculateParabola(originVoxel, targetVoxel, true, &_trajectory, bu, curvature, 1.0); } return true; }
/** * 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); }
/** * Updates the soldier stats (Weight, TU). */ void InventoryState::updateStats() { BattleUnit *unit = _battleGame->getSelectedUnit(); _txtTus->setText(tr("STR_TIME_UNITS_SHORT").arg(unit->getTimeUnits())); int weight = unit->getCarriedWeight(_inv->getSelectedItem()); _txtWeight->setText(tr("STR_WEIGHT").arg(weight).arg(unit->getBaseStats()->strength)); if (weight > unit->getBaseStats()->strength) { _txtWeight->setSecondaryColor(_game->getMod()->getInterface("inventory")->getElement("weight")->color2); } else { _txtWeight->setSecondaryColor(_game->getMod()->getInterface("inventory")->getElement("weight")->color); } _txtFAcc->setText(tr("STR_ACCURACY_SHORT").arg((int)(unit->getBaseStats()->firing * unit->getHealth()) / unit->getBaseStats()->health)); _txtReact->setText(tr("STR_REACTIONS_SHORT").arg(unit->getBaseStats()->reactions)); if (unit->getBaseStats()->psiSkill > 0) { _txtPSkill->setText(tr("STR_PSIONIC_SKILL_SHORT").arg(unit->getBaseStats()->psiSkill)); } else { _txtPSkill->setText(L""); } if (unit->getBaseStats()->psiSkill > 0 || (Options::psiStrengthEval && _game->getSavedGame()->isResearched(_game->getMod()->getPsiRequirements()))) { _txtPStr->setText(tr("STR_PSIONIC_STRENGTH_SHORT").arg(unit->getBaseStats()->psiStrength)); } else { _txtPStr->setText(L""); } }
/** * Updates all soldier stats when the soldier changes. */ void InventoryState::init() { State::init(); BattleUnit *unit = _battleGame->getSelectedUnit(); // no selected unit, close inventory if (unit == 0) { btnOkClick(0); return; } // skip to the first unit with inventory if (!unit->hasInventory()) { if (_parent) { _parent->selectNextPlayerUnit(false, false, true); } else { _battleGame->selectNextPlayerUnit(false, false, true); } // no available unit, close inventory if (_battleGame->getSelectedUnit() == 0 || !_battleGame->getSelectedUnit()->hasInventory()) { // starting a mission with just vehicles btnOkClick(0); return; } else { unit = _battleGame->getSelectedUnit(); } } if (_parent) _parent->getMap()->getCamera()->centerOnPosition(unit->getPosition(), false); unit->setCache(0); _soldier->clear(); _btnRank->clear(); _txtName->setBig(); _txtName->setText(unit->getName(_game->getLanguage())); _inv->setSelectedUnit(unit); Soldier *s = _game->getSavedGame()->getSoldier(unit->getId()); if (s) { SurfaceSet *texture = _game->getResourcePack()->getSurfaceSet("BASEBITS.PCK"); texture->getFrame(s->getRankSprite())->setX(0); texture->getFrame(s->getRankSprite())->setY(0); texture->getFrame(s->getRankSprite())->blit(_btnRank); std::string look = s->getArmor()->getSpriteInventory(); if (s->getGender() == GENDER_MALE) look += "M"; else look += "F"; if (s->getLook() == LOOK_BLONDE) look += "0"; if (s->getLook() == LOOK_BROWNHAIR) look += "1"; if (s->getLook() == LOOK_ORIENTAL) look += "2"; if (s->getLook() == LOOK_AFRICAN) look += "3"; look += ".SPK"; if (!CrossPlatform::fileExists(CrossPlatform::getDataFile("UFOGRAPH/" + look)) && !_game->getResourcePack()->getSurface(look)) { look = s->getArmor()->getSpriteInventory() + ".SPK"; } _game->getResourcePack()->getSurface(look)->blit(_soldier); } else { Surface *armorSurface = _game->getResourcePack()->getSurface(unit->getArmor()->getSpriteInventory()); if (armorSurface) { armorSurface->blit(_soldier); } } updateStats(); }
/** * Updates all soldier stats when the soldier changes. */ void InventoryState::init() { BattleUnit *unit = _battleGame->getSelectedUnit(); // no selected unit, close inventory if (unit == 0) { btnOkClick(0); return; } // skip to the first unit with inventory if (!unit->hasInventory()) { if (_parent) { _parent->selectNextPlayerUnit(false, false, true); } else { _battleGame->selectNextPlayerUnit(false, false, true); } // no available unit, close inventory if (_battleGame->getSelectedUnit() == 0) { // starting a mission with just vehicles btnOkClick(0); return; } else { unit = _battleGame->getSelectedUnit(); } } if (_parent) _parent->getMap()->getCamera()->centerOnPosition(unit->getPosition()); unit->setCache(0); _soldier->clear(); _btnRank->clear(); _txtName->setBig(); _txtName->setText(unit->getName(_game->getLanguage())); _inv->setSelectedUnit(unit); Soldier *s = _game->getSavedGame()->getSoldier(unit->getId()); if (s) { SurfaceSet *texture = _game->getResourcePack()->getSurfaceSet("BASEBITS.PCK"); texture->getFrame(s->getRankSprite())->setX(0); texture->getFrame(s->getRankSprite())->setY(0); texture->getFrame(s->getRankSprite())->blit(_btnRank); std::string look = s->getArmor()->getSpriteInventory(); if (s->getGender() == GENDER_MALE) look += "M"; else look += "F"; if (s->getLook() == LOOK_BLONDE) look += "0"; if (s->getLook() == LOOK_BROWNHAIR) look += "1"; if (s->getLook() == LOOK_ORIENTAL) look += "2"; if (s->getLook() == LOOK_AFRICAN) look += "3"; look += ".SPK"; if (!CrossPlatform::fileExists(CrossPlatform::getDataFile("UFOGRAPH/" + look)) && !_game->getResourcePack()->getSurface(look)) { look = s->getArmor()->getSpriteInventory() + ".SPK"; } _game->getResourcePack()->getSurface(look)->blit(_soldier); } else { Surface *armorSurface = _game->getResourcePack()->getSurface(unit->getArmor()->getSpriteInventory()); if (armorSurface) { armorSurface->blit(_soldier); } } if (_showMoreStatsInInventoryView && !_tu) { std::wstringstream ss2; ss2 << tr("STR_FACCURACY") << L'\x01' << (int)(unit->getStats()->firing * unit->getAccuracyModifier()); _txtFAcc->setText(ss2.str()); std::wstringstream ss3; ss3 << tr("STR_REACT") << L'\x01' << unit->getStats()->reactions; _txtReact->setText(ss3.str()); if (unit->getStats()->psiSkill > 0) { std::wstringstream ss4; ss4 << tr("STR_PSKILL") << L'\x01' << unit->getStats()->psiSkill; _txtPSkill->setText(ss4.str()); std::wstringstream ss5; ss5 << tr("STR_PSTRENGTH") << L'\x01' << unit->getStats()->psiStrength; _txtPStr->setText(ss5.str()); } else { _txtPSkill->setText(L""); _txtPStr->setText(L""); } } updateStats(); }
/** * 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["missionType"] >> _missionType; node["globalshade"] >> _globalShade; node["turn"] >> _turn; 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; BattleUnit *b; if (a < BattleUnit::MAX_SOLDIER_ID) // Unit is linked to a geoscape soldier { // look up the matching soldier b = new BattleUnit(savedGame->getSoldier(a), faction); } else { std::string type, armor; (*i)["genUnitType"] >> type; (*i)["genUnitArmor"] >> armor; // create a new Unit. b = new BattleUnit(rule->getUnit(type), faction, a, rule->getArmor(armor)); } b->load(*i); _units.push_back(b); if (faction == FACTION_PLAYER) { if (b->getId() == selectedUnit) _selectedUnit = b; } else if (b->getStatus() != STATUS_DEAD) { 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); } }
/** * calculateTrajectory. * @return true when a trajectory is possible. */ bool Projectile::calculateTrajectory(double accuracy) { Position originVoxel, targetVoxel; int direction; int dirYshift[8] = {1, 1, 8, 15, 15, 15, 8, 1 }; int dirXshift[8] = {8, 14, 15, 15, 8, 1, 1, 1 }; // large units : x2 originVoxel = Position(_origin.x*16, _origin.y*16, _origin.z*24); originVoxel.z += -_save->getTile(_origin)->getTerrainLevel(); BattleUnit *bu = _save->getTile(_origin)->getUnit(); originVoxel.z += bu->isKneeled()?bu->getUnit()->getKneelHeight():bu->getUnit()->getStandHeight(); originVoxel.z -= 3; if (originVoxel.z >= (_origin.z + 1)*24) { _origin.z++; } direction = bu->getDirection(); originVoxel.x += dirXshift[direction]; originVoxel.y += 15-dirYshift[direction]; // determine the target voxel. // aim at the center of the unit, the object, the walls or the floor (in that priority) // if there is no LOF to the center, try elsewhere (more outward). // Store this target voxel. Tile *tile = _save->getTile(_target); if (tile->getUnit() != 0) { if (_origin == _target) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24); } else { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24 + tile->getUnit()->getUnit()->getStandHeight()/2); } } else if (tile->getMapData(O_OBJECT) != 0) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24 + 10); } else if (tile->getMapData(O_NORTHWALL) != 0) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 16, _target.z*24 + 10); } else if (tile->getMapData(O_WESTWALL) != 0) { targetVoxel = Position(_target.x*16, _target.y*16 + 8, _target.z*24 + 10); } else if (tile->getMapData(O_FLOOR) != 0) { targetVoxel = Position(_target.x*16 + 8, _target.y*16 + 8, _target.z*24); } else { return false; // no line of fire } // apply some accuracy modifiers (todo: calculate this) // This will results in a new target voxel applyAccuracy(originVoxel, &targetVoxel, accuracy); // finally do a line calculation and store this trajectory. _save->getTerrainModifier()->calculateLine(originVoxel, targetVoxel, true, &_trajectory, bu); return true; }
/** * 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); } } }
/** * calculateTrajectory. * @return the objectnumber(0-3) or unit(4) or out of map (5) or -1(no line of fire) */ int Projectile::calculateTrajectory(double accuracy) { Position originVoxel, targetVoxel; int direction; int dirYshift[8] = {1, 1, 8, 15, 15, 15, 8, 1 }; int dirXshift[8] = {8, 14, 15, 15, 8, 1, 1, 1 }; // large units : x2 originVoxel = Position(_origin.x*16, _origin.y*16, _origin.z*24); originVoxel.z += -_save->getTile(_origin)->getTerrainLevel(); BattleUnit *bu = _save->getTile(_origin)->getUnit(); originVoxel.z += bu->getHeight(); originVoxel.z -= 3; if (originVoxel.z >= (_origin.z + 1)*24) { _origin.z++; } direction = bu->getDirection(); originVoxel.x += dirXshift[direction]; originVoxel.y += dirYshift[direction]; // determine the target voxel. // aim at the center of the unit, the object, the walls or the floor (in that priority) // if there is no LOF to the center, try elsewhere (more outward). // Store this target voxel. Tile *tile = _save->getTile(_action.target); if (tile->getUnit() != 0) { if (_origin == _action.target) { // don't shoot at yourself but shoot at the floor targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24); } else { // first try is at half the unit height targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24 + tile->getUnit()->getUnit()->getStandHeight()/2); int test = _save->getTileEngine()->calculateLine(originVoxel, targetVoxel, false, &_trajectory, bu); _trajectory.clear(); if (test != 4) { // did not hit a unit, try at different heights (for ex: unit behind a window can only be hit in the head) targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24 + (tile->getUnit()->getUnit()->getStandHeight()*3)/4); test = _save->getTileEngine()->calculateLine(originVoxel, targetVoxel, false, &_trajectory, bu); _trajectory.clear(); } } } else if (tile->getMapData(MapData::O_OBJECT) != 0) { targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24 + 10); } else if (tile->getMapData(MapData::O_NORTHWALL) != 0) { targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16, _action.target.z*24 + 10); } else if (tile->getMapData(MapData::O_WESTWALL) != 0) { targetVoxel = Position(_action.target.x*16, _action.target.y*16 + 8, _action.target.z*24 + 10); } else if (tile->getMapData(MapData::O_FLOOR) != 0) { targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24); } else { return -1; // no line of fire } // apply some accuracy modifiers (todo: calculate this) // This will results in a new target voxel applyAccuracy(originVoxel, &targetVoxel, accuracy); // finally do a line calculation and store this trajectory. return _save->getTileEngine()->calculateLine(originVoxel, targetVoxel, true, &_trajectory, bu); }
/** * Updates all soldier stats when the soldier changes. */ void InventoryState::init() { BattleUnit *unit = _battleGame->getSelectedUnit(); unit->setCache(0); _soldier->clear(); _btnRank->clear(); _txtName->setText(unit->getName(_game->getLanguage())); _inv->setSelectedUnit(unit); Soldier *s = _game->getSavedGame()->getSoldier(unit->getId()); if (s) { SurfaceSet *texture = _game->getResourcePack()->getSurfaceSet("BASEBITS.PCK"); texture->getFrame(s->getRankSprite())->setX(0); texture->getFrame(s->getRankSprite())->setY(0); texture->getFrame(s->getRankSprite())->blit(_btnRank); std::string look = s->getArmor()->getSpriteInventory(); if (s->getGender() == GENDER_MALE) look += "M"; else look += "F"; if (s->getLook() == LOOK_BLONDE) look += "0"; if (s->getLook() == LOOK_BROWNHAIR) look += "1"; if (s->getLook() == LOOK_ORIENTAL) look += "2"; if (s->getLook() == LOOK_AFRICAN) look += "3"; look += ".SPK"; if (!CrossPlatform::fileExists(CrossPlatform::getDataFile("UFOGRAPH/" + look))) { look = s->getArmor()->getSpriteInventory() + ".SPK"; } _game->getResourcePack()->getSurface(look)->blit(_soldier); } if (_showMoreStatsInInventoryView && !_tu) { std::wstringstream ss2; ss2 << _game->getLanguage()->getString("STR_FACCURACY") << L'\x01' << (int)(unit->getStats()->firing * unit->getAccuracyModifier()); _txtFAcc->setText(ss2.str()); std::wstringstream ss3; ss3 << _game->getLanguage()->getString("STR_REACT") << L'\x01' << unit->getStats()->reactions; _txtReact->setText(ss3.str()); if (unit->getStats()->psiSkill > 0) { std::wstringstream ss4; ss4 << _game->getLanguage()->getString("STR_PSKILL") << L'\x01' << unit->getStats()->psiSkill; _txtPSkill->setText(ss4.str()); std::wstringstream ss5; ss5 << _game->getLanguage()->getString("STR_PSTRENGTH") << L'\x01' << unit->getStats()->psiStrength; _txtPStr->setText(ss5.str()); } else { _txtPSkill->setText(L""); _txtPStr->setText(L""); } } updateStats(); }
/** * calculateTrajectory. * @return the objectnumber(0-3) or unit(4) or out of map (5) or -1(no line of fire) */ int Projectile::calculateTrajectory(double accuracy) { Position originVoxel, targetVoxel; Tile *targetTile = 0; int direction; int dirYshift[24] = {1, 3, 9, 15, 15, 13, 7, 1, 1, 1, 7, 13, 15, 15, 9, 3, 1, 2, 8, 14, 15, 14, 8, 2}; int dirXshift[24] = {9, 15, 15, 13, 8, 1, 1, 3, 7, 13, 15, 15, 9, 3, 1, 1, 8, 14, 15, 14, 8, 2, 1, 2}; int offset = 0; originVoxel = Position(_origin.x*16, _origin.y*16, _origin.z*24); BattleUnit *bu = _action.actor; if (bu->getArmor()->getSize() > 1) { offset = 16; } else if(_action.weapon == _action.weapon->getOwner()->getItem("STR_LEFT_HAND") && !_action.weapon->getRules()->isTwoHanded()) { offset = 8; } // take into account soldier height and terrain level if the projectile is launched from a soldier if (_action.actor->getPosition() == _origin) { // calculate offset of the starting point of the projectile originVoxel.z += -_save->getTile(_origin)->getTerrainLevel(); originVoxel.z += bu->getHeight() + bu->getFloatHeight(); originVoxel.z -= 4; if (originVoxel.z >= (_origin.z + 1)*24) { _origin.z++; } direction = bu->getDirection(); if (bu->getTurretType() != -1) direction = bu->getTurretDirection(); originVoxel.x += dirXshift[direction+offset]*bu->getArmor()->getSize(); originVoxel.y += dirYshift[direction+offset]*bu->getArmor()->getSize(); } else { // don't take into account soldier height and terrain level if the projectile is not launched from a soldier(from a waypoint) originVoxel.x += 8; originVoxel.y += 8; originVoxel.z += 12; } if (_action.type == BA_LAUNCH || (SDL_GetModState() & KMOD_CTRL) != 0) { // target nothing, targets the middle of the tile targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24 + 12); } else { // determine the target voxel. // aim at the center of the unit, the object, the walls or the floor (in that priority) // if there is no LOF to the center, try elsewhere (more outward). // Store this target voxel. targetTile = _save->getTile(_action.target); Position hitPos; int test = -1; if (targetTile->getUnit() != 0) { if (_origin == _action.target || targetTile->getUnit() == _action.actor) { // don't shoot at yourself but shoot at the floor targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24); } else { _save->getTileEngine()->canTargetUnit(&originVoxel, targetTile, &targetVoxel, bu); } } else if (targetTile->getMapData(MapData::O_OBJECT) != 0) { if (!_save->getTileEngine()->canTargetTile(&originVoxel, targetTile, MapData::O_OBJECT, &targetVoxel, bu)) { targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24 + 10); } } else if (targetTile->getMapData(MapData::O_NORTHWALL) != 0) { if (!_save->getTileEngine()->canTargetTile(&originVoxel, targetTile, MapData::O_NORTHWALL, &targetVoxel, bu)) { targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16, _action.target.z*24 + 9); } } else if (targetTile->getMapData(MapData::O_WESTWALL) != 0) { if (!_save->getTileEngine()->canTargetTile(&originVoxel, targetTile, MapData::O_WESTWALL, &targetVoxel, bu)) { targetVoxel = Position(_action.target.x*16, _action.target.y*16 + 8, _action.target.z*24 + 9); } } else if (targetTile->getMapData(MapData::O_FLOOR) != 0) { if (!_save->getTileEngine()->canTargetTile(&originVoxel, targetTile, MapData::O_FLOOR, &targetVoxel, bu)) { targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24); } } else { // target nothing, targets the middle of the tile targetVoxel = Position(_action.target.x*16 + 8, _action.target.y*16 + 8, _action.target.z*24 + 10); } test = _save->getTileEngine()->calculateLine(originVoxel, targetVoxel, false, &_trajectory, bu); if (test == 4 && !_trajectory.empty()) { hitPos = Position(_trajectory.at(0).x/16, _trajectory.at(0).y/16, _trajectory.at(0).z/24); if (_save->getTile(hitPos) && _save->getTile(hitPos)->getUnit() == 0) //no unit? must be lower { hitPos = Position(hitPos.x, hitPos.y, hitPos.z-1); } } if (test != -1 && !_trajectory.empty() && _action.actor->getFaction() == FACTION_PLAYER && _action.autoShotCounter == 1) { //skip already estimated hitPos if (test != 4) { hitPos = Position(_trajectory.at(0).x/16, _trajectory.at(0).y/16, _trajectory.at(0).z/24); } if (hitPos != _action.target && _action.result == "") { if (test == 2) { if (hitPos.y - 1 == _action.target.y) { _trajectory.clear(); return _save->getTileEngine()->calculateLine(originVoxel, targetVoxel, true, &_trajectory, bu); } } if (test == 1) { if (hitPos.x - 1 == _action.target.x) { _trajectory.clear(); return _save->getTileEngine()->calculateLine(originVoxel, targetVoxel, true, &_trajectory, bu); } } _trajectory.clear(); return -1; } } _trajectory.clear(); } // apply some accuracy modifiers (todo: calculate this) // This will results in a new target voxel if (_action.type != BA_LAUNCH) applyAccuracy(originVoxel, &targetVoxel, accuracy, false, targetTile); // finally do a line calculation and store this trajectory. return _save->getTileEngine()->calculateLine(originVoxel, targetVoxel, true, &_trajectory, bu); }
/* * Think! */ void UnitDieBState::think() { if (_unit->getStatus() == STATUS_TURNING) { _unit->turn(); } else if (_unit->getStatus() == STATUS_STANDING) { _unit->startFalling(); if (!_noSound) { playDeathSound(); } } else if (_unit->getStatus() == STATUS_COLLAPSING) { _unit->keepFalling(); } if (_unit->getStatus() == STATUS_DEAD || _unit->getStatus() == STATUS_UNCONSCIOUS) { if (_unit->getStatus() == STATUS_UNCONSCIOUS && _unit->getSpecialAbility() == SPECAB_EXPLODEONDEATH) { _unit->instaKill(); } _parent->getMap()->setUnitDying(false); if (_unit->getTurnsExposed()) { _unit->setTurnsExposed(0); _parent->getSave()->updateExposedUnits(); } if (!_unit->getSpawnUnit().empty()) { // converts the dead zombie to a chryssalid BattleUnit *newUnit = _parent->convertUnit(_unit, _unit->getSpawnUnit()); newUnit->lookAt(_originalDir); } else { convertUnitToCorpse(); } _parent->getTileEngine()->calculateUnitLighting(); _parent->popState(); if (_unit->getOriginalFaction() == FACTION_PLAYER && _unit->getSpawnUnit().empty()) { Game *game = _parent->getSave()->getBattleState()->getGame(); if (_unit->getStatus() == STATUS_DEAD) { if (_damageType == DT_NONE) { std::wstringstream ss; ss << _unit->getName(game->getLanguage()) << L'\n'; ss << game->getLanguage()->getString("STR_HAS_DIED_FROM_A_FATAL_WOUND", _unit->getGender()); game->pushState(new InfoboxOKState(game, ss.str())); } else if (Options::getBool("battleNotifyDeath")) { std::wstringstream ss; ss << _unit->getName(game->getLanguage()) << L'\n'; ss << game->getLanguage()->getString("STR_HAS_BEEN_KILLED", _unit->getGender()); game->pushState(new InfoboxState(game, ss.str())); } } else { std::wstringstream ss; ss << _unit->getName(game->getLanguage()) << L'\n'; ss << game->getLanguage()->getString("STR_HAS_BECOME_UNCONSCIOUS", _unit->getGender()); game->pushState(new InfoboxOKState(game, ss.str())); } } } // if all units from either faction are killed - auto-end the mission. if (Options::getBool("battleAutoEnd")) { int liveAliens = 0; int liveSoldiers = 0; _parent->tallyUnits(liveAliens, liveSoldiers, false); if (liveAliens == 0 || liveSoldiers == 0) { _parent->getSave()->getBattleState()->getBattleGame()->requestEndTurn(); } } _parent->getMap()->cacheUnit(_unit); }
/** * Runs state functionality every cycle. */ void UnitWalkBState::think() { bool unitSpotted = false; int size = _unit->getArmor()->getSize() - 1; bool onScreen = (_unit->getVisible() && _parent->getMap()->getCamera()->isOnScreen(_unit->getPosition(), true, size, false)); if (_unit->isKneeled()) { if (_parent->kneel(_unit)) { _unit->setCache(0); _terrain->calculateFOV(_unit); _parent->getMap()->cacheUnit(_unit); return; } else { _action.result = "STR_NOT_ENOUGH_TIME_UNITS"; _pf->abortPath(); _parent->popState(); return; } } Tile *tileBelow = _parent->getSave()->getTile(_unit->getPosition() + Position(0,0,-1)); if (_unit->isOut()) { _pf->abortPath(); _parent->popState(); return; } if (_unit->getStatus() == STATUS_WALKING || _unit->getStatus() == STATUS_FLYING) { if ((_parent->getSave()->getTile(_unit->getDestination())->getUnit() == 0) || // next tile must be not occupied (_parent->getSave()->getTile(_unit->getDestination())->getUnit() == _unit)) { bool onScreenBoundary = (_unit->getVisible() && _parent->getMap()->getCamera()->isOnScreen(_unit->getPosition(), true, size, true)); _unit->keepWalking(tileBelow, onScreenBoundary); // advances the phase playMovementSound(); } else if (!_falling) { _unit->lookAt(_unit->getDestination(), (_unit->getTurretType() != -1)); // turn to undiscovered unit _pf->abortPath(); } // unit moved from one tile to the other, update the tiles if (_unit->getPosition() != _unit->getLastPosition()) { bool largeCheck = true; for (int x = size; x >= 0; x--) { for (int y = size; y >= 0; y--) { Tile *otherTileBelow = _parent->getSave()->getTile(_unit->getPosition() + Position(x,y,-1)); if (!_parent->getSave()->getTile(_unit->getPosition() + Position(x,y,0))->hasNoFloor(otherTileBelow) || _unit->getMovementType() == MT_FLY) largeCheck = false; _parent->getSave()->getTile(_unit->getLastPosition() + Position(x,y,0))->setUnit(0); } } for (int x = size; x >= 0; x--) { for (int y = size; y >= 0; y--) { _parent->getSave()->getTile(_unit->getPosition() + Position(x,y,0))->setUnit(_unit, _parent->getSave()->getTile(_unit->getPosition() + Position(x,y,-1))); } } _falling = largeCheck && _unit->getPosition().z != 0 && _unit->getTile()->hasNoFloor(tileBelow) && _unit->getMovementType() != MT_FLY && _unit->getWalkingPhase() == 0; if (_falling) { for (int x = size; x >= 0; --x) { for (int y = size; y >= 0; --y) { Tile *otherTileBelow = _parent->getSave()->getTile(_unit->getPosition() + Position(x,y,-1)); if (otherTileBelow && otherTileBelow->getUnit()) { _falling = false; _pf->dequeuePath(); _parent->getSave()->addFallingUnit(_unit); _parent->statePushFront(new UnitFallBState(_parent)); return; } } } } if (!_parent->getMap()->getCamera()->isOnScreen(_unit->getPosition(), true, size, false) && _unit->getFaction() != FACTION_PLAYER && _unit->getVisible()) _parent->getMap()->getCamera()->centerOnPosition(_unit->getPosition()); // if the unit changed level, camera changes level with _parent->getMap()->getCamera()->setViewLevel(_unit->getPosition().z); } // is the step finished? if (_unit->getStatus() == STATUS_STANDING) { // update the TU display _parent->getSave()->getBattleState()->updateSoldierInfo(); // if the unit burns floortiles, burn floortiles as long as we're not falling if (!_falling && (_unit->getSpecialAbility() == SPECAB_BURNFLOOR || _unit->getSpecialAbility() == SPECAB_BURN_AND_EXPLODE)) { _unit->getTile()->ignite(1); Position posHere = _unit->getPosition(); Position voxelHere = (posHere * Position(16,16,24)) + Position(8,8,-(_unit->getTile()->getTerrainLevel())); _parent->getTileEngine()->hit(voxelHere, _unit->getBaseStats()->strength, DT_IN, _unit); if (_unit->getStatus() != STATUS_STANDING) // ie: we burned a hole in the floor and fell through it { _pf->abortPath(); return; } } // move our personal lighting with us _terrain->calculateUnitLighting(); if (_unit->getFaction() != FACTION_PLAYER) { _unit->setVisible(false); } _terrain->calculateFOV(_unit->getPosition()); unitSpotted = (!_falling && !_action.desperate && _parent->getPanicHandled() && _numUnitsSpotted != _unit->getUnitsSpottedThisTurn().size()); if (_parent->checkForProximityGrenades(_unit)) { _parent->popState(); return; } if (unitSpotted) { _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); _pf->abortPath(); _parent->popState(); return; } // check for reaction fire if (!_falling) { if (_terrain->checkReactionFire(_unit)) { // unit got fired upon - stop walking _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); _pf->abortPath(); _parent->popState(); return; } } } else if (onScreen) { // make sure the unit sprites are up to date if (_pf->getStrafeMove()) { // This is where we fake out the strafe movement direction so the unit "moonwalks" int dirTemp = _unit->getDirection(); _unit->setDirection(_unit->getFaceDirection()); _parent->getMap()->cacheUnit(_unit); _unit->setDirection(dirTemp); } else { _parent->getMap()->cacheUnit(_unit); } } } // we are just standing around, shouldn't we be walking? if (_unit->getStatus() == STATUS_STANDING || _unit->getStatus() == STATUS_PANICKING) { // check if we did spot new units if (unitSpotted && !_action.desperate && _unit->getCharging() == 0 && !_falling) { if (Options::traceAI) { Log(LOG_INFO) << "Uh-oh! Company!"; } _unit->setHiding(false); // clearly we're not hidden now _parent->getMap()->cacheUnit(_unit); postPathProcedures(); return; } if (onScreen || _parent->getSave()->getDebugMode()) { setNormalWalkSpeed(); } else { _parent->setStateInterval(0); } int dir = _pf->getStartDirection(); if (_falling) { dir = Pathfinding::DIR_DOWN; } if (dir != -1) { if (_pf->getStrafeMove()) { _unit->setFaceDirection(_unit->getDirection()); } Position destination; int tu = _pf->getTUCost(_unit->getPosition(), dir, &destination, _unit, 0, false); // gets tu cost, but also gets the destination position. if (_unit->getFaction() != FACTION_PLAYER && _unit->getSpecialAbility() < SPECAB_BURNFLOOR && _parent->getSave()->getTile(destination) && _parent->getSave()->getTile(destination)->getFire() > 0) { tu -= 32; // we artificially inflate the TU cost by 32 points in getTUCost under these conditions, so we have to deflate it here. } if (_falling) { tu = 0; } int energy = tu; if (dir >= Pathfinding::DIR_UP) { energy = 0; } else if (_action.run) { tu *= 0.75; energy *= 1.5; } if (tu > _unit->getTimeUnits()) { if (_parent->getPanicHandled() && tu < 255) { _action.result = "STR_NOT_ENOUGH_TIME_UNITS"; } _pf->abortPath(); _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); _parent->popState(); return; } if (energy / 2 > _unit->getEnergy()) { if (_parent->getPanicHandled()) { _action.result = "STR_NOT_ENOUGH_ENERGY"; } _pf->abortPath(); _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); _parent->popState(); return; } if (_parent->getPanicHandled() && _parent->checkReservedTU(_unit, tu) == false) { _pf->abortPath(); _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); return; } // we are looking in the wrong way, turn first (unless strafing) // we are not using the turn state, because turning during walking costs no tu if (dir != _unit->getDirection() && dir < Pathfinding::DIR_UP && !_pf->getStrafeMove()) { _unit->lookAt(dir); _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); return; } // now open doors (if any) if (dir < Pathfinding::DIR_UP) { int door = _terrain->unitOpensDoor(_unit, false, dir); if (door == 3) { return; // don't start walking yet, wait for the ufo door to open } if (door == 0) { _parent->getMod()->getSoundByDepth(_parent->getDepth(), Mod::DOOR_OPEN)->play(-1, _parent->getMap()->getSoundAngle(_unit->getPosition())); // normal door } if (door == 1) { _parent->getMod()->getSoundByDepth(_parent->getDepth(), Mod::SLIDING_DOOR_OPEN)->play(-1, _parent->getMap()->getSoundAngle(_unit->getPosition())); // ufo door return; // don't start walking yet, wait for the ufo door to open } } for (int x = size; x >= 0; --x) { for (int y = size; y >= 0; --y) { BattleUnit* unitInMyWay = _parent->getSave()->getTile(destination + Position(x,y,0))->getUnit(); BattleUnit* unitBelowMyWay = 0; Tile* belowDest = _parent->getSave()->getTile(destination + Position(x,y,-1)); if (belowDest) { unitBelowMyWay = belowDest->getUnit(); } // can't walk into units in this tile, or on top of other units sticking their head into this tile if (!_falling && ((unitInMyWay && unitInMyWay != _unit) || (belowDest && unitBelowMyWay && unitBelowMyWay != _unit && (-belowDest->getTerrainLevel() + unitBelowMyWay->getFloatHeight() + unitBelowMyWay->getHeight()) >= 28))) // 4+ voxels poking into the tile above, we don't kick people in the head here at XCom. { _action.TU = 0; _pf->abortPath(); _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); _parent->popState(); return; } } } // now start moving dir = _pf->dequeuePath(); if (_falling) { dir = Pathfinding::DIR_DOWN; } if (_unit->spendTimeUnits(tu)) { if (_unit->spendEnergy(energy)) { Tile *tileBelow = _parent->getSave()->getTile(_unit->getPosition() + Position(0,0,-1)); _unit->startWalking(dir, destination, tileBelow, onScreen); _beforeFirstStep = false; } } // make sure the unit sprites are up to date if (onScreen) { if (_pf->getStrafeMove()) { // This is where we fake out the strafe movement direction so the unit "moonwalks" int dirTemp = _unit->getDirection(); _unit->setDirection(_unit->getFaceDirection()); _parent->getMap()->cacheUnit(_unit); _unit->setDirection(dirTemp); } else { _parent->getMap()->cacheUnit(_unit); } } } else { postPathProcedures(); return; } } // turning during walking costs no tu if (_unit->getStatus() == STATUS_TURNING) { // except before the first step. if (_beforeFirstStep) { _preMovementCost++; } _unit->turn(); // calculateFOV is unreliable for setting the unitSpotted bool, as it can be called from various other places // in the code, ie: doors opening, and this messes up the result. _terrain->calculateFOV(_unit); unitSpotted = (!_falling && !_action.desperate && _parent->getPanicHandled() && _numUnitsSpotted != _unit->getUnitsSpottedThisTurn().size()); // make sure the unit sprites are up to date _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); if (unitSpotted && !_action.desperate && !_unit->getCharging() && !_falling) { if (_beforeFirstStep) { _unit->spendTimeUnits(_preMovementCost); } if (Options::traceAI) { Log(LOG_INFO) << "Egads! A turn reveals new units! I must pause!"; } _unit->setHiding(false); // not hidden, are we... _pf->abortPath(); _unit->abortTurn(); //revert to a standing state. _unit->setCache(0); _parent->getMap()->cacheUnit(_unit); _parent->popState(); } } }