diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index 261a2ef64..e36e98e96 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -24,39 +24,71 @@ BattleHexArray::BattleHexArray(std::initializer_list initList) noexce BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) const { + if(this->empty()) + return BattleHex(); + BattleHex initialHex = BattleHex(initialPos); - auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool + int closestDistance = std::numeric_limits::max(); + BattleHexArray closestTiles; + + for(auto hex : internalStorage) { - return initialHex.getDistance(initialHex, left) < initialHex.getDistance(initialHex, right); - }; - BattleHexArray sortedTiles(*this); - boost::sort(sortedTiles, compareDistance); //closest tiles at front - int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away - auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool - { - return closestDistance < here.getDistance(initialPos, here); - }; - vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting - auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool + int distance = initialHex.getDistance(initialHex, hex); + if(distance < closestDistance) + { + closestDistance = distance; + closestTiles.clear(); + closestTiles.insert(hex); + } + else if(distance == closestDistance) + closestTiles.insert(hex); + } + + auto compareHorizontal = [side, initialPos](const BattleHex & left, const BattleHex & right) { if(left.getX() != right.getX()) { - if(side == BattleSide::ATTACKER) - return left.getX() > right.getX(); //find furthest right - else - return left.getX() < right.getX(); //find furthest left - } - else - { - //Prefer tiles in the same row. - return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); + return (side == BattleSide::ATTACKER) ? (left.getX() > right.getX()) : (left.getX() < right.getX()); } + return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); }; - boost::sort(sortedTiles, compareHorizontal); - return sortedTiles.front(); -} - -BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTiles() + + auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal); + return (bestTile != closestTiles.end()) ? *bestTile : BattleHex(); + + //BattleHex initialHex = BattleHex(initialPos); + //auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool + //{ + // return initialHex.getDistance(initialHex, left) < initialHex.getDistance(initialHex, right); + //}; + //BattleHexArray sortedTiles(*this); + //boost::sort(sortedTiles, compareDistance); //closest tiles at front + //int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away + //auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool + //{ + // return closestDistance < here.getDistance(initialPos, here); + //}; + //vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting + //auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool + //{ + // if(left.getX() != right.getX()) + // { + // if(side == BattleSide::ATTACKER) + // return left.getX() > right.getX(); //find furthest right + // else + // return left.getX() < right.getX(); //find furthest left + // } + // else + // { + // //Prefer tiles in the same row. + // return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); + // } + //}; + //boost::sort(sortedTiles, compareHorizontal); + //return sortedTiles.front(); +} + +BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTiles() { BattleHexArray::NeighbouringTilesCache ret; @@ -71,59 +103,59 @@ BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTile } return ret; -} - -BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) +} + +BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) { BattleHexArray ret; for(auto dir : BattleHex::hexagonalDirections()) ret.checkAndPush(hex.cloneInDirection(dir, false)); return ret; -} - -BattleHexArray BattleHexArray::generateAttackerClosestTilesCache() -{ - assert(!neighbouringTilesCache.empty()); - - BattleHexArray ret; - - ret.resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); - } - - return ret; -} - -BattleHexArray BattleHexArray::generateDefenderClosestTilesCache() -{ - assert(!neighbouringTilesCache.empty()); - - BattleHexArray ret; - - ret.resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::DEFENDER, hex)); - } - - return ret; } -BattleHex BattleHexArray::getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos) -{ - if(side == BattleSide::ATTACKER) - return closestTilesCacheForAttacker[pos.hex]; - else if(side == BattleSide::DEFENDER) - return closestTilesCacheForDefender[pos.hex]; - else - assert(false); // we should never be here -} - +BattleHexArray BattleHexArray::generateAttackerClosestTilesCache() +{ + assert(!neighbouringTilesCache.empty()); + + BattleHexArray ret; + + ret.resize(GameConstants::BFIELD_SIZE); + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); + } + + return ret; +} + +BattleHexArray BattleHexArray::generateDefenderClosestTilesCache() +{ + assert(!neighbouringTilesCache.empty()); + + BattleHexArray ret; + + ret.resize(GameConstants::BFIELD_SIZE); + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::DEFENDER, hex)); + } + + return ret; +} + +BattleHex BattleHexArray::getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos) +{ + if(side == BattleSide::ATTACKER) + return closestTilesCacheForAttacker[pos.hex]; + else if(side == BattleSide::DEFENDER) + return closestTilesCacheForDefender[pos.hex]; + else + assert(false); // we should never be here +} + void BattleHexArray::merge(const BattleHexArray & other) noexcept { for(auto hex : other) diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index a8b438ced..021173901 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -1,240 +1,243 @@ -/* - * Unit.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include "Unit.h" - -#include "../VCMI_Lib.h" -#include "../texts/CGeneralTextHandler.h" - -#include "../serializer/JsonDeserializer.h" -#include "../serializer/JsonSerializer.h" - -#include -#include - -VCMI_LIB_NAMESPACE_BEGIN - -namespace battle -{ - -///Unit -Unit::~Unit() = default; - -bool Unit::isDead() const -{ - return !alive() && !isGhost(); -} - -bool Unit::isTurret() const -{ - return creatureIndex() == CreatureID::ARROW_TOWERS; -} - -std::string Unit::getDescription() const -{ - boost::format fmt("Unit %d of side %d"); - fmt % unitId() % static_cast(unitSide()); - return fmt.str(); -} - -//TODO: deduplicate these functions -const IBonusBearer* Unit::getBonusBearer() const -{ - return this; -} - -BattleHexArray Unit::getSurroundingHexes(BattleHex assumedPosition) const -{ - BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position - - return getSurroundingHexes(hex, doubleWide(), unitSide()); -} - -BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) -{ - BattleHexArray hexes; - if(twoHex) - { - const BattleHex otherHex = occupiedHex(position, twoHex, side); - - if(side == BattleSide::ATTACKER) - { - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(position.cloneInDirection(dir, false)); - - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - } - else - { - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); - - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false)); - } - return hexes; - } - else - { - return BattleHexArray::neighbouringTilesCache[position]; - } -} - -BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const -{ - auto defenderHexes = battle::Unit::getHexes( - getPosition(), - doubleWide(), - unitSide()); - - BattleHexArray targetableHexes; - - for(auto defenderHex : defenderHexes) - { - auto hexes = battle::Unit::getHexes( - defenderHex, - attacker->doubleWide(), - unitSide()); - - if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1) - hexes.pop_back(); - - for(auto hex : hexes) - targetableHexes.merge(BattleHexArray::neighbouringTilesCache[hex]); - } - - return targetableHexes; -} - -bool Unit::coversPos(BattleHex pos) const -{ - return getPosition() == pos || (doubleWide() && (occupiedHex() == pos)); -} - -BattleHexArray Unit::getHexes() const -{ - return getHexes(getPosition(), doubleWide(), unitSide()); -} - -BattleHexArray Unit::getHexes(BattleHex assumedPos) const -{ - return getHexes(assumedPos, doubleWide(), unitSide()); -} - -BattleHexArray Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) -{ - BattleHexArray hexes; - hexes.insert(assumedPos); - - if(twoHex) - hexes.insert(occupiedHex(assumedPos, twoHex, side)); - - return hexes; -} - -BattleHex Unit::occupiedHex() const -{ - return occupiedHex(getPosition(), doubleWide(), unitSide()); -} - -BattleHex Unit::occupiedHex(BattleHex assumedPos) const -{ - return occupiedHex(assumedPos, doubleWide(), unitSide()); -} - -BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side) -{ - if(twoHex) - { - if(side == BattleSide::ATTACKER) - return assumedPos - 1; - else - return assumedPos + 1; - } - else - { - return BattleHex::INVALID; - } -} - -void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const -{ - if(boost::logic::indeterminate(plural)) - serial = VLC->generaltexth->pluralText(serial, getCount()); - else if(plural) - serial = VLC->generaltexth->pluralText(serial, 2); - else - serial = VLC->generaltexth->pluralText(serial, 1); - - text.appendLocalString(type, serial); -} - -void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const -{ - if(boost::logic::indeterminate(plural)) - text.replaceName(creatureId(), getCount()); - else if(plural) - text.replaceNamePlural(creatureIndex()); - else - text.replaceNameSingular(creatureIndex()); -} - -std::string Unit::formatGeneralMessage(const int32_t baseTextId) const -{ - const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount()); - - MetaString text; - text.appendLocalString(EMetaText::GENERAL_TXT, textId); - text.replaceName(creatureId(), getCount()); - - return text.toString(); -} - -int Unit::getRawSurrenderCost() const -{ - //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines - if(unitSlot().validSlot()) - return creatureCost() * getCount(); - else - return 0; -} - -///UnitInfo -void UnitInfo::serializeJson(JsonSerializeFormat & handler) -{ - handler.serializeInt("count", count); - handler.serializeId("type", type, CreatureID(CreatureID::NONE)); - handler.serializeInt("side", side); - handler.serializeInt("position", position); - handler.serializeBool("summoned", summoned); -} - -void UnitInfo::save(JsonNode & data) -{ - data.clear(); - JsonSerializer ser(nullptr, data); - ser.serializeStruct("newUnitInfo", *this); -} - -void UnitInfo::load(uint32_t id_, const JsonNode & data) -{ - id = id_; - JsonDeserializer deser(nullptr, data); - deser.serializeStruct("newUnitInfo", *this); -} - -} - -VCMI_LIB_NAMESPACE_END +/* + * Unit.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "Unit.h" + +#include "../VCMI_Lib.h" +#include "../texts/CGeneralTextHandler.h" + +#include "../serializer/JsonDeserializer.h" +#include "../serializer/JsonSerializer.h" + +#include +#include + +VCMI_LIB_NAMESPACE_BEGIN + +namespace battle +{ + +///Unit +Unit::~Unit() = default; + +bool Unit::isDead() const +{ + return !alive() && !isGhost(); +} + +bool Unit::isTurret() const +{ + return creatureIndex() == CreatureID::ARROW_TOWERS; +} + +std::string Unit::getDescription() const +{ + boost::format fmt("Unit %d of side %d"); + fmt % unitId() % static_cast(unitSide()); + return fmt.str(); +} + +//TODO: deduplicate these functions +const IBonusBearer* Unit::getBonusBearer() const +{ + return this; +} + +BattleHexArray Unit::getSurroundingHexes(BattleHex assumedPosition) const +{ + BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position + + return getSurroundingHexes(hex, doubleWide(), unitSide()); +} + +BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) +{ + if(!position.isValid()) + return { }; + + BattleHexArray hexes; + if(twoHex) + { + const BattleHex otherHex = occupiedHex(position, twoHex, side); + + if(side == BattleSide::ATTACKER) + { + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(position.cloneInDirection(dir, false)); + + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + } + else + { + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); + + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false)); + } + return hexes; + } + else + { + return BattleHexArray::neighbouringTilesCache[position]; + } +} + +BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const +{ + auto defenderHexes = battle::Unit::getHexes( + getPosition(), + doubleWide(), + unitSide()); + + BattleHexArray targetableHexes; + + for(auto defenderHex : defenderHexes) + { + auto hexes = battle::Unit::getHexes( + defenderHex, + attacker->doubleWide(), + unitSide()); + + if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1) + hexes.pop_back(); + + for(auto hex : hexes) + targetableHexes.merge(BattleHexArray::neighbouringTilesCache[hex]); + } + + return targetableHexes; +} + +bool Unit::coversPos(BattleHex pos) const +{ + return getPosition() == pos || (doubleWide() && (occupiedHex() == pos)); +} + +BattleHexArray Unit::getHexes() const +{ + return getHexes(getPosition(), doubleWide(), unitSide()); +} + +BattleHexArray Unit::getHexes(BattleHex assumedPos) const +{ + return getHexes(assumedPos, doubleWide(), unitSide()); +} + +BattleHexArray Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) +{ + BattleHexArray hexes; + hexes.insert(assumedPos); + + if(twoHex) + hexes.insert(occupiedHex(assumedPos, twoHex, side)); + + return hexes; +} + +BattleHex Unit::occupiedHex() const +{ + return occupiedHex(getPosition(), doubleWide(), unitSide()); +} + +BattleHex Unit::occupiedHex(BattleHex assumedPos) const +{ + return occupiedHex(assumedPos, doubleWide(), unitSide()); +} + +BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side) +{ + if(twoHex) + { + if(side == BattleSide::ATTACKER) + return assumedPos - 1; + else + return assumedPos + 1; + } + else + { + return BattleHex::INVALID; + } +} + +void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const +{ + if(boost::logic::indeterminate(plural)) + serial = VLC->generaltexth->pluralText(serial, getCount()); + else if(plural) + serial = VLC->generaltexth->pluralText(serial, 2); + else + serial = VLC->generaltexth->pluralText(serial, 1); + + text.appendLocalString(type, serial); +} + +void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const +{ + if(boost::logic::indeterminate(plural)) + text.replaceName(creatureId(), getCount()); + else if(plural) + text.replaceNamePlural(creatureIndex()); + else + text.replaceNameSingular(creatureIndex()); +} + +std::string Unit::formatGeneralMessage(const int32_t baseTextId) const +{ + const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount()); + + MetaString text; + text.appendLocalString(EMetaText::GENERAL_TXT, textId); + text.replaceName(creatureId(), getCount()); + + return text.toString(); +} + +int Unit::getRawSurrenderCost() const +{ + //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines + if(unitSlot().validSlot()) + return creatureCost() * getCount(); + else + return 0; +} + +///UnitInfo +void UnitInfo::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeInt("count", count); + handler.serializeId("type", type, CreatureID(CreatureID::NONE)); + handler.serializeInt("side", side); + handler.serializeInt("position", position); + handler.serializeBool("summoned", summoned); +} + +void UnitInfo::save(JsonNode & data) +{ + data.clear(); + JsonSerializer ser(nullptr, data); + ser.serializeStruct("newUnitInfo", *this); +} + +void UnitInfo::load(uint32_t id_, const JsonNode & data) +{ + id = id_; + JsonDeserializer deser(nullptr, data); + deser.serializeStruct("newUnitInfo", *this); +} + +} + +VCMI_LIB_NAMESPACE_END