From dd3adb7e169a886dd1d103344d340bee567741fa Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 12 Jan 2023 23:52:03 +0200 Subject: [PATCH 1/9] Arrow towers damage algorithm should now match H3 --- lib/battle/CBattleInfoCallback.cpp | 41 +++++++++++++----------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 808249246..1e6358d7c 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -24,39 +24,32 @@ VCMI_LIB_NAMESPACE_BEGIN namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO { -/* - *Here are 2 explanations how below algorithm should work in H3, looks like they are not 100% accurate as it results in one damage number, not min/max range: - * - *1. http://heroes.thelazy.net/wiki/Arrow_tower - * - *2. All towns' turrets do the same damage. If Fort, Citadel or Castle is built damage of the Middle turret is 15, and 7,5 for others. - *Buildings increase turrets' damage, but only those buildings that are new in town view, not upgrades to the existing. So, every building save: - *- dwellings' upgrades - *- Mage Guild upgrades - *- Horde buildings - *- income upgrades - *- some special ones - *increases middle Turret damage by 3, and 1,5 for the other two. - *Damage is almost always the maximum one (right click on the Turret), sometimes +1/2 points, and it does not depend on the target. Nothing can influence it, except the mentioned above (but it will be roughly double if the defender has Armorer or Air Shield). - *Maximum damage for Castle, Conflux is 120, Necropolis, Inferno, Fortress 125, Stronghold, Turret, and Dungeon 130 (for all three Turrets). - *Artillery allows the player to control the Turrets. - */ + static void retrieveTurretDamageRange(const CGTownInstance * town, const battle::Unit * turret, double & outMinDmg, double & outMaxDmg)//does not match OH3 yet, but damage is somewhat close { + // http://heroes.thelazy.net/wiki/Arrow_tower assert(turret->creatureIndex() == CreatureID::ARROW_TOWERS); assert(town); assert(turret->getPosition() >= -4 && turret->getPosition() <= -2); - const float multiplier = (turret->getPosition() == -2) ? 1.0f : 0.5f; + // base damage, irregardless of town level + static const int baseDamageKeep = 10; + static const int baseDamageTower = 6; - //Revised - Where do below values come from? - /*int baseMin = 6; - int baseMax = 10;*/ + // extra damage, for each building in town + static const int extraDamage = 2; - const int baseDamage = 15; + const int townLevel = town->getTownLevel(); - outMinDmg = multiplier * (baseDamage + town->getTownLevel() * 3); - outMaxDmg = outMinDmg; + int minDamage; + + if (turret->getPosition() == BattleHex::CASTLE_CENTRAL_TOWER) + minDamage = baseDamageKeep + townLevel * extraDamage; + else + minDamage = baseDamageTower + townLevel / 2 * extraDamage; + + outMinDmg = minDamage; + outMaxDmg = minDamage * 2; } static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate) From b86704bece692692f52594afd0171b0c20411add Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 12 Jan 2023 23:53:29 +0200 Subject: [PATCH 2/9] Ballistics mechanics should now match H3 --- server/CGameHandler.cpp | 179 ++++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 98 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ddbf5fd92..fc1b94cd3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4790,7 +4790,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) case EActionType::CATAPULT: { //TODO: unify with spells::effects:Catapult - auto getCatapultHitChance = [&](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int + auto getCatapultHitChance = [](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int { switch(part) { @@ -4811,115 +4811,105 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) } }; - auto wrapper = wrapAction(ba); - - if(target.size() < 1) + auto getBallisticsInfo = [this, &ba] (const CStack * actor) { - complain("Destination required for catapult action."); - ok = false; - break; - } - auto destination = target.at(0).hexValue; + const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side); - const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side); - - CHeroHandler::SBallisticsLevelInfo stackBallisticsParameters; - if(stack->getCreature()->idNumber == CreatureID::CATAPULT) - stackBallisticsParameters = VLC->heroh->ballistics.at(attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::BALLISTICS)); - else - { - if(stack->hasBonusOfType(Bonus::CATAPULT_EXTRA_SHOTS)) //by design use advanced ballistics parameters with this bonus present, upg. cyclops use advanced ballistics, nonupg. use basic in OH3 + if(actor->getCreature()->idNumber == CreatureID::CATAPULT) + return VLC->heroh->ballistics.at(attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::BALLISTICS)); + else { - stackBallisticsParameters = VLC->heroh->ballistics.at(2); - stackBallisticsParameters.shots = 1; //skip default "2 shots" from adv. ballistics + //by design use advanced ballistics parameters with this bonus present, upg. cyclops use advanced ballistics, nonupg. use basic in OH3 + int ballisticsLevel = actor->hasBonusOfType(Bonus::CATAPULT_EXTRA_SHOTS) ? 2 : 1; + + auto parameters = VLC->heroh->ballistics.at(ballisticsLevel); + parameters.shots = 1 + std::max(actor->valOfBonuses(Bonus::CATAPULT_EXTRA_SHOTS), 0); + + return parameters; + } + }; + + auto isWallPartAttackable = [this] (EWallPart::EWallPart part) + { + return (gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); + }; + + CHeroHandler::SBallisticsLevelInfo stackBallisticsParameters = getBallisticsInfo(stack); + + auto wrapper = wrapAction(ba); + auto destination = target.empty() ? BattleHex(BattleHex::INVALID) : target.at(0).hexValue; + auto desiredTarget = gs->curB->battleHexToWallPart(destination); + + for (int shotNumber=0; shotNumberheroh->ballistics.at(1); - - stackBallisticsParameters.shots += std::max(stack->valOfBonuses(Bonus::CATAPULT_EXTRA_SHOTS), 0); //0 is allowed minimum to let modders force advanced ballistics for "oneshotting creatures" - } - - auto wallPart = gs->curB->battleHexToWallPart(destination); - if (!gs->curB->isWallPartPotentiallyAttackable(wallPart)) - { - complain("catapult tried to attack non-catapultable hex!"); - break; - } - - //in successive iterations damage is dealt but not yet subtracted from wall's HPs - auto ¤tHP = gs->curB->si.wallState; - - if (currentHP.at(wallPart) == EWallState::DESTROYED || currentHP.at(wallPart) == EWallState::NONE) - { - complain("catapult tried to attack already destroyed wall part!"); - break; - } - - for (int g=0; g allowedTargets; - for (size_t i=0; i< currentHP.size(); i++) - { - if(currentHP.at(i) != EWallState::DESTROYED && - currentHP.at(i) != EWallState::NONE) - allowedTargets.push_back(EWallPart::EWallPart(i)); - } - if (allowedTargets.empty()) - break; - attackedPart = *RandomGeneratorUtil::nextItem(allowedTargets, getRandomGenerator()); - } + static const std::array walls = { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }; + static const std::array towers= { EWallPart::BOTTOM_TOWER, EWallPart::KEEP, EWallPart::UPPER_TOWER }; + static const EWallPart::EWallPart gates = EWallPart::GATE; + + // in H3, catapult under automatic control will attack objects in following order: + // walls, gates, towers + std::vector potentialTargets; + for (auto & part : walls ) + if (isWallPartAttackable(part)) + potentialTargets.push_back(part); + + if (potentialTargets.empty() && isWallPartAttackable(gates)) + potentialTargets.push_back(gates); + + if (potentialTargets.empty()) + for (auto & part : towers ) + if (isWallPartAttackable(part)) + potentialTargets.push_back(part); + + if (potentialTargets.empty()) + break; // everything is gone, can't attack anymore + + actualTarget = *RandomGeneratorUtil::nextItem(potentialTargets, getRandomGenerator()); } - while (!hitSuccessfull); + assert(actualTarget != EWallPart::INVALID); - if (!hitSuccessfull) // break triggered - no target to shoot at - break; + std::array damageChances = { stackBallisticsParameters.noDmg, stackBallisticsParameters.oneDmg, stackBallisticsParameters.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful + int totalChance = std::accumulate(damageChances.begin(), damageChances.end(), 0); + int damageRandom = getRandomGenerator().nextInt(totalChance - 1); + int dealtDamage = 0; - CatapultAttack ca; //package for clients - CatapultAttack::AttackInfo attack; - attack.attackedPart = attackedPart; - attack.destinationTile = destination; - attack.damageDealt = 0; - BattleUnitsChanged removeUnits; - - int dmgChance[] = { stackBallisticsParameters.noDmg, stackBallisticsParameters.oneDmg, stackBallisticsParameters.twoDmg }; //dmgChance[i] - chance for doing i dmg when hit is successful - - int dmgRand = getRandomGenerator().nextInt(99); - //accumulating dmgChance - dmgChance[1] += dmgChance[0]; - dmgChance[2] += dmgChance[1]; //calculating dealt damage - for (int damage = 0; damage < ARRAY_COUNT(dmgChance); ++damage) + for (int damage = 0; damage < damageChances.size(); ++damage) { - if (dmgRand <= dmgChance[damage]) + if (damageRandom <= damageChances[damage]) { - attack.damageDealt = damage; + dealtDamage = damage; break; } + damageRandom -= damageChances[damage]; } - // attacked tile may have changed - update destination - attack.destinationTile = gs->curB->wallPartToBattleHex(EWallPart::EWallPart(attack.attackedPart)); + + CatapultAttack::AttackInfo attack; + attack.attackedPart = actualTarget; + attack.destinationTile = gs->curB->wallPartToBattleHex(actualTarget); + attack.damageDealt = dealtDamage; + + CatapultAttack ca; //package for clients + ca.attacker = ba.stackNumber; + ca.attackedParts.push_back(attack); + sendAndApply(&ca); logGlobal->trace("Catapult attacks %d dealing %d damage", (int)attack.attackedPart, (int)attack.damageDealt); //removing creatures in turrets / keep if one is destroyed - if (currentHP.at(attackedPart) - attack.damageDealt <= 0 && (attackedPart == EWallPart::KEEP || //HP enum subtraction not intuitive, consider using SiegeInfo::applyDamage - attackedPart == EWallPart::BOTTOM_TOWER || attackedPart == EWallPart::UPPER_TOWER)) + if (gs->curB->si.wallState[actualTarget] <= 0 && (actualTarget == EWallPart::KEEP || actualTarget == EWallPart::BOTTOM_TOWER || actualTarget == EWallPart::UPPER_TOWER)) { int posRemove = -1; - switch(attackedPart) + switch(actualTarget) { case EWallPart::KEEP: posRemove = BattleHex::CASTLE_CENTRAL_TOWER; @@ -4936,18 +4926,13 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) { if(elem->initialPosition == posRemove) { + BattleUnitsChanged removeUnits; removeUnits.changedStacks.emplace_back(elem->unitId(), UnitChanges::EOperation::REMOVE); + sendAndApply(&removeUnits); break; } } } - ca.attacker = ba.stackNumber; - ca.attackedParts.push_back(attack); - - sendAndApply(&ca); - - if(!removeUnits.changedStacks.empty()) - sendAndApply(&removeUnits); } //finish by scope guard break; @@ -6769,8 +6754,6 @@ void CGameHandler::runBattle() if (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, CreatureID::CATAPULT)) { BattleAction attack; - auto destination = *RandomGeneratorUtil::nextItem(attackableBattleHexes, getRandomGenerator()); - attack.aimToHex(destination); attack.actionType = EActionType::CATAPULT; attack.side = next->side; attack.stackNumber = next->ID; From 500cf7f15d62a8e6acdf467d30c47bed598bc9de Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 00:35:58 +0200 Subject: [PATCH 3/9] EWallPart & EWallState are now enum class --- AI/BattleAI/BattleAI.cpp | 6 ++-- AI/BattleAI/StackWithBonuses.cpp | 2 +- AI/BattleAI/StackWithBonuses.h | 2 +- client/battle/BattleSiegeController.cpp | 14 ++++---- client/battle/BattleSiegeController.h | 2 +- lib/GameConstants.h | 30 +++++++---------- lib/NetPacks.h | 2 +- lib/NetPacksLib.cpp | 2 +- lib/battle/BattleInfo.cpp | 8 ++--- lib/battle/BattleInfo.h | 4 +-- lib/battle/BattleProxy.cpp | 2 +- lib/battle/BattleProxy.h | 2 +- lib/battle/CBattleInfoCallback.cpp | 24 ++++++------- lib/battle/CBattleInfoCallback.h | 6 ++-- lib/battle/CBattleInfoEssentials.cpp | 2 +- lib/battle/CBattleInfoEssentials.h | 2 +- lib/battle/IBattleState.h | 4 +-- lib/battle/SiegeInfo.cpp | 6 ++-- lib/battle/SiegeInfo.h | 4 +-- lib/spells/effects/Catapult.cpp | 45 ++++++++++--------------- lib/spells/effects/Obstacle.cpp | 2 +- server/CGameHandler.cpp | 14 ++++---- 22 files changed, 84 insertions(+), 101 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 1bbc2f09e..0ae8a183d 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -46,14 +46,14 @@ std::vector CBattleAI::getBrokenWallMoatHexes() const { std::vector result; - for(int wallPart = EWallPart::BOTTOM_WALL; wallPart < EWallPart::UPPER_WALL; wallPart++) + for(EWallPart wallPart : { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }) { auto state = cb->battleGetWallState(wallPart); if(state != EWallState::DESTROYED) continue; - auto wallHex = cb->wallPartToBattleHex((EWallPart::EWallPart)wallPart); + auto wallHex = cb->wallPartToBattleHex((EWallPart)wallPart); auto moatHex = wallHex.cloneInDirection(BattleHex::LEFT); result.push_back(moatHex); @@ -364,7 +364,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack) } else { - EWallPart::EWallPart wallParts[] = { + EWallPart wallParts[] = { EWallPart::KEEP, EWallPart::BOTTOM_TOWER, EWallPart::UPPER_TOWER, diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index b843b7b4e..e698c3014 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -403,7 +403,7 @@ void HypotheticBattle::removeUnitBonus(uint32_t id, const std::vector & b bonusTreeVersion++; } -void HypotheticBattle::setWallState(int partOfWall, si8 state) +void HypotheticBattle::setWallState(EWallPart partOfWall, EWallState state) { //TODO:HypotheticBattle::setWallState } diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index a2ebafd70..48070ca40 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -130,7 +130,7 @@ public: void updateUnitBonus(uint32_t id, const std::vector & bonus) override; void removeUnitBonus(uint32_t id, const std::vector & bonus) override; - void setWallState(int partOfWall, si8 state) override; + void setWallState(EWallPart partOfWall, EWallState state) override; void addObstacle(const ObstacleChanges & changes) override; void updateObstacle(const ObstacleChanges& changes) override; diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 0d6b76895..0f6a76f7f 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -28,7 +28,7 @@ #include "../../lib/CStack.h" #include "../../lib/mapObjects/CGTownInstance.h" -std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const +std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const { auto getImageIndex = [&]() -> int { @@ -130,9 +130,9 @@ bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) { case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER; case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS; - case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED; - case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED; - case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED; + case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED; + case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED; + case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED; default: return true; } } @@ -321,7 +321,7 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const if (!owner.curInt->cb->isWallPartPotentiallyAttackable(wallPart)) return false; - auto state = owner.curInt->cb->battleGetWallState(static_cast(wallPart)); + auto state = owner.curInt->cb->battleGetWallState(wallPart); return state != EWallState::DESTROYED && state != EWallState::NONE; } @@ -354,12 +354,12 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) for (auto attackInfo : ca.attackedParts) { - int wallId = attackInfo.attackedPart + EWallVisual::DESTRUCTIBLE_FIRST; + int wallId = static_cast(attackInfo.attackedPart) + EWallVisual::DESTRUCTIBLE_FIRST; //gate state changing handled separately if (wallId == EWallVisual::GATE) continue; - auto wallState = EWallState::EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart)); + auto wallState = EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart)); wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState)); } diff --git a/client/battle/BattleSiegeController.h b/client/battle/BattleSiegeController.h index 90e304c46..dd4795afc 100644 --- a/client/battle/BattleSiegeController.h +++ b/client/battle/BattleSiegeController.h @@ -76,7 +76,7 @@ class BattleSiegeController std::array, EWallVisual::WALL_LAST + 1> wallPieceImages; /// return URI for image for a wall piece - std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const; + std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const; /// returns BattleHex to which chosen wall piece is bound BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index b995bc089..5d50073bc 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -615,28 +615,22 @@ namespace ECommander const int MAX_SKILL_LEVEL = 5; } -namespace EWallPart +enum class EWallPart : int8_t { - enum EWallPart - { - INDESTRUCTIBLE_PART_OF_GATE = -3, INDESTRUCTIBLE_PART = -2, INVALID = -1, - KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WALL, UPPER_TOWER, GATE, - PARTS_COUNT /* This constant SHOULD always stay as the last item in the enum. */ - }; -} + INDESTRUCTIBLE_PART_OF_GATE = -3, INDESTRUCTIBLE_PART = -2, INVALID = -1, + KEEP = 0, BOTTOM_TOWER, BOTTOM_WALL, BELOW_GATE, OVER_GATE, UPPER_WALL, UPPER_TOWER, GATE, + PARTS_COUNT /* This constant SHOULD always stay as the last item in the enum. */ +}; -namespace EWallState +enum class EWallState : int8_t { - enum EWallState - { - NONE = -1, //no wall - DESTROYED, - DAMAGED, - INTACT - }; -} + NONE = -1, //no wall + DESTROYED, + DAMAGED, + INTACT +}; -enum class EGateState : ui8 +enum class EGateState : uint8_t { NONE, CLOSED, diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 82df6ab91..76a93c497 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1827,7 +1827,7 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient struct AttackInfo { si16 destinationTile; - ui8 attackedPart; + EWallPart attackedPart; ui8 damageDealt; template void serialize(Handler & h, const int version) diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 5e4e869ea..0be775aa8 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1707,7 +1707,7 @@ DLL_LINKAGE void CatapultAttack::applyBattle(IBattleState * battleState) for(const auto & part : attackedParts) { - auto newWallState = SiegeInfo::applyDamage(EWallState::EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt); + auto newWallState = SiegeInfo::applyDamage(EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt); battleState->setWallState(part.attackedPart, newWallState); } } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index f1687ccef..74024b8a4 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -225,7 +225,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const for (int b = 0; b < curB->si.wallState.size(); ++b) { - curB->si.wallState[b] = EWallState::INTACT; + curB->si.wallState[EWallPart(b)] = EWallState::INTACT; } if (!town->hasBuilt(BuildingID::CITADEL)) @@ -610,7 +610,7 @@ const CGTownInstance * BattleInfo::getDefendedTown() const return town; } -si8 BattleInfo::getWallState(int partOfWall) const +EWallState BattleInfo::getWallState(EWallPart partOfWall) const { return si.wallState.at(partOfWall); } @@ -913,9 +913,9 @@ void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool fo } } -void BattleInfo::setWallState(int partOfWall, si8 state) +void BattleInfo::setWallState(EWallPart partOfWall, EWallState state) { - si.wallState.at(partOfWall) = state; + si.wallState[partOfWall] = state; } void BattleInfo::addObstacle(const ObstacleChanges & changes) diff --git a/lib/battle/BattleInfo.h b/lib/battle/BattleInfo.h index 76d3a9db2..1bafc74dd 100644 --- a/lib/battle/BattleInfo.h +++ b/lib/battle/BattleInfo.h @@ -87,7 +87,7 @@ public: ui8 getTacticsSide() const override; const CGTownInstance * getDefendedTown() const override; - si8 getWallState(int partOfWall) const override; + EWallState getWallState(EWallPart partOfWall) const override; EGateState getGateState() const override; uint32_t getCastSpells(ui8 side) const override; @@ -115,7 +115,7 @@ public: void updateUnitBonus(uint32_t id, const std::vector & bonus) override; void removeUnitBonus(uint32_t id, const std::vector & bonus) override; - void setWallState(int partOfWall, si8 state) override; + void setWallState(EWallPart partOfWall, EWallState state) override; void addObstacle(const ObstacleChanges & changes) override; void updateObstacle(const ObstacleChanges& changes) override; diff --git a/lib/battle/BattleProxy.cpp b/lib/battle/BattleProxy.cpp index 0ebb475ba..67e4aae16 100644 --- a/lib/battle/BattleProxy.cpp +++ b/lib/battle/BattleProxy.cpp @@ -89,7 +89,7 @@ const CGTownInstance * BattleProxy::getDefendedTown() const return subject->battleGetDefendedTown(); } -si8 BattleProxy::getWallState(int partOfWall) const +EWallState BattleProxy::getWallState(EWallPart partOfWall) const { return subject->battleGetWallState(partOfWall); } diff --git a/lib/battle/BattleProxy.h b/lib/battle/BattleProxy.h index 8a7b0e13d..0e07e707c 100644 --- a/lib/battle/BattleProxy.h +++ b/lib/battle/BattleProxy.h @@ -44,7 +44,7 @@ public: ui8 getTacticsSide() const override; const CGTownInstance * getDefendedTown() const override; - si8 getWallState(int partOfWall) const override; + EWallState getWallState(EWallPart partOfWall) const override; EGateState getGateState() const override; uint32_t getCastSpells(ui8 side) const override; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 1e6358d7c..05ce607bb 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -71,7 +71,7 @@ static bool sameSideOfWall(BattleHex pos1, BattleHex pos2) } // parts of wall -static const std::pair wallParts[] = +static const std::pair wallParts[] = { std::make_pair(50, EWallPart::KEEP), std::make_pair(183, EWallPart::BOTTOM_TOWER), @@ -89,7 +89,7 @@ static const std::pair wallParts[] = std::make_pair(165, EWallPart::INDESTRUCTIBLE_PART) }; -static EWallPart::EWallPart hexToWallPart(BattleHex hex) +static EWallPart hexToWallPart(BattleHex hex) { for(auto & elem : wallParts) { @@ -100,7 +100,7 @@ static EWallPart::EWallPart hexToWallPart(BattleHex hex) return EWallPart::INVALID; //not found! } -static BattleHex WallPartToHex(EWallPart::EWallPart part) +static BattleHex WallPartToHex(EWallPart part) { for(auto & elem : wallParts) { @@ -1109,13 +1109,13 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const ret[hex] = EAccessibility::UNAVAILABLE; //TODO likely duplicated logic - static const std::pair lockedIfNotDestroyed[] = + static const std::pair lockedIfNotDestroyed[] = { //which part of wall, which hex is blocked if this part of wall is not destroyed - std::make_pair(2, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)), - std::make_pair(3, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)), - std::make_pair(4, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)), - std::make_pair(5, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1)) + std::make_pair(EWallPart::BOTTOM_WALL, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)), + std::make_pair(EWallPart::BELOW_GATE, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)), + std::make_pair(EWallPart::OVER_GATE, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)), + std::make_pair(EWallPart::UPPER_WALL, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1)) }; for(auto & elem : lockedIfNotDestroyed) @@ -1604,19 +1604,19 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter, return true; } -BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart::EWallPart part) const +BattleHex CBattleInfoCallback::wallPartToBattleHex(EWallPart part) const { RETURN_IF_NOT_BATTLE(BattleHex::INVALID); return WallPartToHex(part); } -EWallPart::EWallPart CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const +EWallPart CBattleInfoCallback::battleHexToWallPart(BattleHex hex) const { RETURN_IF_NOT_BATTLE(EWallPart::INVALID); return hexToWallPart(hex); } -bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const +bool CBattleInfoCallback::isWallPartPotentiallyAttackable(EWallPart wallPart) const { RETURN_IF_NOT_BATTLE(false); return wallPart != EWallPart::INDESTRUCTIBLE_PART && wallPart != EWallPart::INDESTRUCTIBLE_PART_OF_GATE && @@ -1632,7 +1632,7 @@ std::vector CBattleInfoCallback::getAttackableBattleHexes() const { if(isWallPartPotentiallyAttackable(wallPartPair.second)) { - auto wallState = static_cast(battleGetWallState(static_cast(wallPartPair.second))); + auto wallState = static_cast(battleGetWallState(wallPartPair.second)); if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) { attackableBattleHexes.push_back(BattleHex(wallPartPair.first)); diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 75fc6bf81..259313fd8 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -110,9 +110,9 @@ public: bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; bool battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const; - BattleHex wallPartToBattleHex(EWallPart::EWallPart part) const; - EWallPart::EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found - bool isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not + BattleHex wallPartToBattleHex(EWallPart part) const; + EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found + bool isWallPartPotentiallyAttackable(EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not std::vector getAttackableBattleHexes() const; si8 battleMinSpellLevel(ui8 side) const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned diff --git a/lib/battle/CBattleInfoEssentials.cpp b/lib/battle/CBattleInfoEssentials.cpp index 53fcd6dd8..e7e0062aa 100644 --- a/lib/battle/CBattleInfoEssentials.cpp +++ b/lib/battle/CBattleInfoEssentials.cpp @@ -364,7 +364,7 @@ bool CBattleInfoEssentials::battleHasHero(ui8 side) const return getBattle()->getSideHero(side) != nullptr; } -si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const +EWallState CBattleInfoEssentials::battleGetWallState(EWallPart partOfWall) const { RETURN_IF_NOT_BATTLE(EWallState::NONE); if(battleGetSiegeLevel() == CGTownInstance::NONE) diff --git a/lib/battle/CBattleInfoEssentials.h b/lib/battle/CBattleInfoEssentials.h index 6903e66bc..117e352b3 100644 --- a/lib/battle/CBattleInfoEssentials.h +++ b/lib/battle/CBattleInfoEssentials.h @@ -94,7 +94,7 @@ public: // for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, // [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle - si8 battleGetWallState(int partOfWall) const; + EWallState battleGetWallState(EWallPart partOfWall) const; EGateState battleGetGateState() const; //helpers diff --git a/lib/battle/IBattleState.h b/lib/battle/IBattleState.h index 62536f4c1..7165ed5b2 100644 --- a/lib/battle/IBattleState.h +++ b/lib/battle/IBattleState.h @@ -49,7 +49,7 @@ public: virtual ObstacleCList getAllObstacles() const = 0; virtual const CGTownInstance * getDefendedTown() const = 0; - virtual si8 getWallState(int partOfWall) const = 0; + virtual EWallState getWallState(EWallPart partOfWall) const = 0; virtual EGateState getGateState() const = 0; virtual PlayerColor getSidePlayer(ui8 side) const = 0; @@ -87,7 +87,7 @@ public: virtual void updateUnitBonus(uint32_t id, const std::vector & bonus) = 0; virtual void removeUnitBonus(uint32_t id, const std::vector & bonus) = 0; - virtual void setWallState(int partOfWall, si8 state) = 0; + virtual void setWallState(EWallPart partOfWall, EWallState state) = 0; virtual void addObstacle(const ObstacleChanges & changes) = 0; virtual void updateObstacle(const ObstacleChanges & changes) = 0; diff --git a/lib/battle/SiegeInfo.cpp b/lib/battle/SiegeInfo.cpp index 13c2dc468..c11a5a6d9 100644 --- a/lib/battle/SiegeInfo.cpp +++ b/lib/battle/SiegeInfo.cpp @@ -15,14 +15,14 @@ VCMI_LIB_NAMESPACE_BEGIN SiegeInfo::SiegeInfo() { - for(int i = 0; i < wallState.size(); ++i) + for(int i = 0; i < int(EWallPart::PARTS_COUNT); ++i) { - wallState[i] = EWallState::NONE; + wallState[EWallPart(i)] = EWallState::NONE; } gateState = EGateState::NONE; } -EWallState::EWallState SiegeInfo::applyDamage(EWallState::EWallState state, unsigned int value) +EWallState SiegeInfo::applyDamage(EWallState state, unsigned int value) { if(value == 0) return state; diff --git a/lib/battle/SiegeInfo.h b/lib/battle/SiegeInfo.h index f508c1f14..680888201 100644 --- a/lib/battle/SiegeInfo.h +++ b/lib/battle/SiegeInfo.h @@ -15,13 +15,13 @@ VCMI_LIB_NAMESPACE_BEGIN //only for use in BattleInfo struct DLL_LINKAGE SiegeInfo { - std::array wallState; + std::map wallState; EGateState gateState; SiegeInfo(); // return EWallState decreased by value of damage points - static EWallState::EWallState applyDamage(EWallState::EWallState state, unsigned int value); + static EWallState applyDamage(EWallState state, unsigned int value); template void serialize(Handler &h, const int version) { diff --git a/lib/spells/effects/Catapult.cpp b/lib/spells/effects/Catapult.cpp index 62c9363f3..8f98f2a53 100644 --- a/lib/spells/effects/Catapult.cpp +++ b/lib/spells/effects/Catapult.cpp @@ -68,7 +68,7 @@ bool Catapult::applicable(Problem & problem, const Mechanics * m) const void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & /* eTarget */) const { //start with all destructible parts - static const std::set potentialTargets = + static const std::set potentialTargets = { EWallPart::KEEP, EWallPart::BOTTOM_TOWER, @@ -80,9 +80,9 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT EWallPart::GATE }; - assert(potentialTargets.size() == EWallPart::PARTS_COUNT); + assert(potentialTargets.size() == size_t(EWallPart::PARTS_COUNT)); - std::set allowedTargets; + std::set allowedTargets; for (auto const & target : potentialTargets) { @@ -98,16 +98,13 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT CatapultAttack ca; ca.attacker = -1; - BattleUnitsChanged removeUnits; - for(int i = 0; i < targetsToAttack; i++) { // Hit on any existing, not destroyed targets are allowed // Multiple hit on same target are allowed. // Potential overshots (more hits on same targets than remaining HP) are allowed - EWallPart::EWallPart target = *RandomGeneratorUtil::nextItem(allowedTargets, *server->getRNG()); + EWallPart target = *RandomGeneratorUtil::nextItem(allowedTargets, *server->getRNG()); - auto state = m->battle()->battleGetWallState(target); auto attackInfo = ca.attackedParts.begin(); for ( ; attackInfo != ca.attackedParts.end(); ++attackInfo) @@ -127,11 +124,19 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT { attackInfo->damageDealt += 1; } + } + server->apply(&ca); + + BattleUnitsChanged removeUnits; + + for (auto const wallPart : { EWallPart::KEEP, EWallPart::BOTTOM_TOWER, EWallPart::UPPER_TOWER }) + { //removing creatures in turrets / keep if one is destroyed BattleHex posRemove; + auto state = m->battle()->battleGetWallState(wallPart); - switch(target) + switch(wallPart) { case EWallPart::KEEP: posRemove = BattleHex::CASTLE_CENTRAL_TOWER; @@ -144,35 +149,19 @@ void Catapult::apply(ServerCallback * server, const Mechanics * m, const EffectT break; } - if(posRemove != BattleHex::INVALID && state - attackInfo->damageDealt <= 0) //HP enum subtraction not intuitive, consider using SiegeInfo::applyDamage + if(state == EWallState::DESTROYED) //HP enum subtraction not intuitive, consider using SiegeInfo::applyDamage { auto all = m->battle()->battleGetUnitsIf([=](const battle::Unit * unit) { - return !unit->isGhost(); + return !unit->isGhost() && unit->getPosition() == posRemove; }); + assert(all.size() == 0 || all.size() == 1); for(auto & elem : all) - { - if(elem->getPosition() != posRemove) - continue; - - // if tower was hit multiple times, it may have been destroyed already - bool stackWasRemovedBefore = false; - for(auto & removed : removeUnits.changedStacks) - { - if (removed.id == elem->unitId()) - stackWasRemovedBefore = true; - } - - if (!stackWasRemovedBefore) - removeUnits.changedStacks.emplace_back(elem->unitId(), UnitChanges::EOperation::REMOVE); - break; - } + removeUnits.changedStacks.emplace_back(elem->unitId(), UnitChanges::EOperation::REMOVE); } } - server->apply(&ca); - if(!removeUnits.changedStacks.empty()) server->apply(&removeUnits); } diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 4c381ce9c..402ccc155 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -256,7 +256,7 @@ bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & if(cb->battleGetSiegeLevel() != 0) { - EWallPart::EWallPart part = cb->battleHexToWallPart(hex); + EWallPart part = cb->battleHexToWallPart(hex); if(part == EWallPart::INVALID || part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) return true;//no fortification here diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index fc1b94cd3..985f7bbd7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4790,7 +4790,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) case EActionType::CATAPULT: { //TODO: unify with spells::effects:Catapult - auto getCatapultHitChance = [](EWallPart::EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int + auto getCatapultHitChance = [](EWallPart part, const CHeroHandler::SBallisticsLevelInfo & sbi) -> int { switch(part) { @@ -4829,7 +4829,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) } }; - auto isWallPartAttackable = [this] (EWallPart::EWallPart part) + auto isWallPartAttackable = [this] (EWallPart part) { return (gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); }; @@ -4851,13 +4851,13 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) } else { - static const std::array walls = { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }; - static const std::array towers= { EWallPart::BOTTOM_TOWER, EWallPart::KEEP, EWallPart::UPPER_TOWER }; - static const EWallPart::EWallPart gates = EWallPart::GATE; + static const std::array walls = { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL }; + static const std::array towers= { EWallPart::BOTTOM_TOWER, EWallPart::KEEP, EWallPart::UPPER_TOWER }; + static const EWallPart gates = EWallPart::GATE; // in H3, catapult under automatic control will attack objects in following order: // walls, gates, towers - std::vector potentialTargets; + std::vector potentialTargets; for (auto & part : walls ) if (isWallPartAttackable(part)) potentialTargets.push_back(part); @@ -4906,7 +4906,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) logGlobal->trace("Catapult attacks %d dealing %d damage", (int)attack.attackedPart, (int)attack.damageDealt); //removing creatures in turrets / keep if one is destroyed - if (gs->curB->si.wallState[actualTarget] <= 0 && (actualTarget == EWallPart::KEEP || actualTarget == EWallPart::BOTTOM_TOWER || actualTarget == EWallPart::UPPER_TOWER)) + if (gs->curB->si.wallState[actualTarget] == EWallState::DESTROYED && (actualTarget == EWallPart::KEEP || actualTarget == EWallPart::BOTTOM_TOWER || actualTarget == EWallPart::UPPER_TOWER)) { int posRemove = -1; switch(actualTarget) From 1d7f004658bfe28da1282e49cbd86b99aa94bcfa Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 01:09:24 +0200 Subject: [PATCH 4/9] Implemented reinforced walls in towns with Castle --- AI/BattleAI/BattleAI.cpp | 3 +-- client/battle/BattleSiegeController.cpp | 21 ++++++++++++++------- lib/GameConstants.h | 3 ++- lib/NetPacksLib.cpp | 2 +- lib/battle/BattleInfo.cpp | 21 ++++++++++++--------- lib/battle/CBattleInfoCallback.cpp | 4 ++-- lib/battle/SiegeInfo.cpp | 2 ++ server/CGameHandler.cpp | 2 +- 8 files changed, 35 insertions(+), 23 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 0ae8a183d..a0ccfb6f9 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -378,10 +378,9 @@ BattleAction CBattleAI::useCatapult(const CStack * stack) { auto wallState = cb->battleGetWallState(wallPart); - if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) + if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) { targetHex = cb->wallPartToBattleHex(wallPart); - break; } } diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 0f6a76f7f..0b0acf36c 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -32,18 +32,25 @@ std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisua { auto getImageIndex = [&]() -> int { + bool isTower = (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER); + switch (state) { - case EWallState::INTACT : + case EWallState::REINFORCED : return 1; + case EWallState::INTACT : + if (town->hasBuilt(BuildingID::CASTLE)) + return 2; // reinforced walls were damaged + else + return 1; case EWallState::DAMAGED : // towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2 - if(what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER) + if (isTower) return 1; else return 2; case EWallState::DESTROYED : - if (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER) + if (isTower) return 2; else return 3; @@ -130,9 +137,9 @@ bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) { case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER; case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS; - case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED; - case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED; - case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED; + case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && owner.curInt->cb->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED; + case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED; + case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED; default: return true; } } @@ -177,7 +184,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) ) continue; - wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::INTACT)); + wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED)); } } diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 5d50073bc..1574619de 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -627,7 +627,8 @@ enum class EWallState : int8_t NONE = -1, //no wall DESTROYED, DAMAGED, - INTACT + INTACT, + REINFORCED, // walls in towns with castle }; enum class EGateState : uint8_t diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 0be775aa8..665304f99 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1707,7 +1707,7 @@ DLL_LINKAGE void CatapultAttack::applyBattle(IBattleState * battleState) for(const auto & part : attackedParts) { - auto newWallState = SiegeInfo::applyDamage(EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt); + auto newWallState = SiegeInfo::applyDamage(battleState->getWallState(part.attackedPart), part.damageDealt); battleState->setWallState(part.attackedPart, newWallState); } } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 74024b8a4..10ee785da 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -223,20 +223,23 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const { curB->si.gateState = EGateState::CLOSED; - for (int b = 0; b < curB->si.wallState.size(); ++b) + curB->si.wallState[EWallPart::GATE] = EWallState::INTACT; + + for (auto const wall : {EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL} ) { - curB->si.wallState[EWallPart(b)] = EWallState::INTACT; + if (town->hasBuilt(BuildingID::CASTLE)) + curB->si.wallState[wall] = EWallState::REINFORCED; + else + curB->si.wallState[wall] = EWallState::INTACT; } - if (!town->hasBuilt(BuildingID::CITADEL)) - { - curB->si.wallState[EWallPart::KEEP] = EWallState::NONE; - } + if (town->hasBuilt(BuildingID::CITADEL)) + curB->si.wallState[EWallPart::KEEP] = EWallState::INTACT; - if (!town->hasBuilt(BuildingID::CASTLE)) + if (town->hasBuilt(BuildingID::CASTLE)) { - curB->si.wallState[EWallPart::UPPER_TOWER] = EWallState::NONE; - curB->si.wallState[EWallPart::BOTTOM_TOWER] = EWallState::NONE; + curB->si.wallState[EWallPart::UPPER_TOWER] = EWallState::INTACT; + curB->si.wallState[EWallPart::BOTTOM_TOWER] = EWallState::INTACT; } } diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 05ce607bb..e0ffd90d3 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1632,8 +1632,8 @@ std::vector CBattleInfoCallback::getAttackableBattleHexes() const { if(isWallPartPotentiallyAttackable(wallPartPair.second)) { - auto wallState = static_cast(battleGetWallState(wallPartPair.second)); - if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) + auto wallState = battleGetWallState(wallPartPair.second); + if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) { attackableBattleHexes.push_back(BattleHex(wallPartPair.first)); } diff --git a/lib/battle/SiegeInfo.cpp b/lib/battle/SiegeInfo.cpp index c11a5a6d9..4666671e3 100644 --- a/lib/battle/SiegeInfo.cpp +++ b/lib/battle/SiegeInfo.cpp @@ -29,6 +29,8 @@ EWallState SiegeInfo::applyDamage(EWallState state, unsigned int value) switch(applyDamage(state, value - 1)) { + case EWallState::REINFORCED: + return EWallState::INTACT; case EWallState::INTACT: return EWallState::DAMAGED; case EWallState::DAMAGED: diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 985f7bbd7..4934e3315 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4831,7 +4831,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) auto isWallPartAttackable = [this] (EWallPart part) { - return (gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); + return (gs->curB->si.wallState[part] == EWallState::REINFORCED || gs->curB->si.wallState[part] == EWallState::INTACT || gs->curB->si.wallState[part] == EWallState::DAMAGED); }; CHeroHandler::SBallisticsLevelInfo stackBallisticsParameters = getBallisticsInfo(stack); From 3be3c871fb0da889dbf8c3f6aa87775bb5e133bb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 01:59:09 +0200 Subject: [PATCH 5/9] Destroyed walls will now remove wall penalty --- lib/battle/CBattleInfoCallback.cpp | 99 +++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index e0ffd90d3..ab9ddd892 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -54,7 +54,7 @@ static void retrieveTurretDamageRange(const CGTownInstance * town, const battle: static BattleHex lineToWallHex(int line) //returns hex with wall in given line (y coordinate) { - static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 95, 112, 130, 147, 165, 182}; + static const BattleHex lineToHex[] = {12, 29, 45, 62, 78, 96, 112, 130, 147, 165, 182}; return lineToHex[line]; } @@ -157,8 +157,89 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con return ESpellCastProblem::OK; } +struct Point +{ + int x,y; +}; + +/// Algorithm to test whether line segment between points line1-line2 will intersect with +/// AABB (Axis-Aligned Bounding Box) defined by points aabb1 & aabb2 +/// Note that in order to avoid floating point rounding errors algorithm uses integers with no divisions +static bool intersectionTestSegmentAABB(Point line1, Point line2, Point aabb1, Point aabb2) +{ + // check whether segment is located to the left of our AABB + if (line1.x < aabb1.x && line2.x < aabb1.x) + return false; + + // check whether segment is located to the right of our AABB + if (line1.x > aabb2.x && line2.x > aabb2.x) + return false; + + // check whether segment is located on top of our AABB + if (line1.y < aabb1.y && line2.y < aabb1.y) + return false; + + // check whether segment is located below of our AABB + if (line1.y > aabb2.y && line2.y > aabb2.y) + return false; + + Point vector { line2.x - line1.x, line2.y - line1.y}; + + // compute position of AABB corners relative to our line + int tlTest = vector.y*aabb1.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); + int trTest = vector.y*aabb2.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); + int blTest = vector.y*aabb1.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); + int brTest = vector.y*aabb2.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); + + // if all points are on the left of our line then there is no intersection + if ( tlTest > 0 && trTest > 0 && blTest > 0 && brTest > 0 ) + return false; + + // if all points are on the right of our line then there is no intersection + if ( tlTest < 0 && trTest < 0 && blTest < 0 && brTest < 0 ) + return false; + + // if all previous checks failed, this means that there is an intersection between line and AABB + return true; +} + bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const { + auto isTileBlocked = [&](BattleHex tile) + { + EWallPart wallPart = battleHexToWallPart(tile); + if (wallPart == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) + return false; // does not blocks ranged attacks + if (wallPart == EWallPart::INDESTRUCTIBLE_PART) + return true; // always blocks ranged attacks + + assert(isWallPartPotentiallyAttackable(wallPart)); + + EWallState state = battleGetWallState(wallPart); + + return state != EWallState::DESTROYED; + }; + + auto needWallPenalty = [&](BattleHex from, BattleHex dest) + { + Point line1{ from.getX()*10+5, from.getY()*10+5}; + Point line2{ dest.getX()*10+5, dest.getY()*10+5}; + + for (int y = 0; y < GameConstants::BFIELD_HEIGHT; ++y) + { + BattleHex obstacle = lineToWallHex(y); + if (!isTileBlocked(obstacle)) + continue; + + Point aabb1{ obstacle.getX()*10, obstacle.getY()*10 }; + Point aabb2{ aabb1.x + 10, aabb1.y + 10 }; + + if ( intersectionTestSegmentAABB(line1, line2, aabb1, aabb2)) + return true; + } + return false; + }; + RETURN_IF_NOT_BATTLE(false); if(!battleGetSiegeLevel()) return false; @@ -170,21 +251,9 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat return false; const int wallInStackLine = lineToWallHex(shooterPosition.getY()); - const int wallInDestLine = lineToWallHex(destHex.getY()); + const bool shooterOutsideWalls = shooterPosition < wallInStackLine; - const bool stackLeft = shooterPosition < wallInStackLine; - const bool destRight = destHex > wallInDestLine; - - if (stackLeft && destRight) //shooting from outside to inside - { - int row = (shooterPosition + destHex) / (2 * GameConstants::BFIELD_WIDTH); - if (shooterPosition > destHex && ((destHex % GameConstants::BFIELD_WIDTH - shooterPosition % GameConstants::BFIELD_WIDTH) < 2)) //shooting up high - row -= 2; - const int wallPos = lineToWallHex(row); - if (!isWallPartPotentiallyAttackable(battleHexToWallPart(wallPos))) return true; - } - - return false; + return shooterOutsideWalls && needWallPenalty(shooterPosition, destHex); } si8 CBattleInfoCallback::battleCanTeleportTo(const battle::Unit * stack, BattleHex destHex, int telportLevel) const From 526de47ca4949bb6fa71071eeb654f9123f37e2a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 13:00:07 +0200 Subject: [PATCH 6/9] Removed 0.98 compatibility --- lib/CTownHandler.cpp | 13 +------------ lib/CTownHandler.h | 2 -- lib/spells/effects/Obstacle.cpp | 6 ++++-- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 6cda5f1e9..9b1777c5d 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -177,12 +177,6 @@ CTown::~CTown() str.dellNull(); } -std::vector CTown::defaultMoatHexes() -{ - static const std::vector moatHexes = {11, 28, 44, 61, 77, 111, 129, 146, 164, 181}; - return moatHexes; -} - std::string CTown::getLocalizedFactionName() const { if(faction == nullptr) @@ -855,12 +849,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) warMachinesToLoad[town] = source["warMachine"]; town->moatDamage = static_cast(source["moatDamage"].Float()); - - // Compatibility for <= 0.98f mods - if(source["moatHexes"].isNull()) - town->moatHexes = CTown::defaultMoatHexes(); - else - town->moatHexes = source["moatHexes"].convertTo >(); + town->moatHexes = source["moatHexes"].convertTo >(); town->mageLevel = static_cast(source["mageGuild"].Float()); town->names = source["names"].convertTo >(); diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index d85f1465b..f27940a09 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -232,8 +232,6 @@ class DLL_LINKAGE CTown public: CTown(); ~CTown(); - // TODO: remove once save and mod compatability not needed - static std::vector defaultMoatHexes(); std::string getLocalizedFactionName() const; std::string getBuildingScope() const; diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 402ccc155..77bcc6300 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -258,9 +258,11 @@ bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & { EWallPart part = cb->battleHexToWallPart(hex); - if(part == EWallPart::INVALID || part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) + if(part == EWallPart::INVALID) return true;//no fortification here - else if(static_cast(part) < 0) + else if(part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) + return false; // location accessible to units, but not targetable by spells + else if(part == EWallPart::INDESTRUCTIBLE_PART) return false;//indestructible part (cant be checked by battleGetWallState) else if(part == EWallPart::BOTTOM_TOWER || part == EWallPart::UPPER_TOWER) return false;//destructible, but should not be available From ccc6ebee0d907aedea6e5363d8f61f033efe1f88 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 13 Jan 2023 15:44:42 +0200 Subject: [PATCH 7/9] Cleared up gate blocking, Force Field can now block gates --- lib/GameConstants.h | 2 +- lib/spells/effects/Obstacle.cpp | 2 +- server/CGameHandler.cpp | 53 ++++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 1574619de..a4c5eee11 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -635,7 +635,7 @@ enum class EGateState : uint8_t { NONE, CLOSED, - BLOCKED, //dead or alive stack blocking from outside + BLOCKED, // gate is blocked in closed state, e.g. by creature OPENED, DESTROYED }; diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 77bcc6300..0b628b994 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -261,7 +261,7 @@ bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & if(part == EWallPart::INVALID) return true;//no fortification here else if(part == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) - return false; // location accessible to units, but not targetable by spells + return true; // location accessible else if(part == EWallPart::INDESTRUCTIBLE_PART) return false;//indestructible part (cant be checked by battleGetWallState) else if(part == EWallPart::BOTTOM_TOWER || part == EWallPart::UPPER_TOWER) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4934e3315..01436631e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4426,6 +4426,26 @@ static EndAction end_action; void CGameHandler::updateGateState() { + // GATE_BRIDGE - leftmost tile, located over moat + // GATE_OUTER - central tile, mostly covered by gate image + // GATE_INNER - rightmost tile, inside the walls + + // GATE_OUTER or GATE_INNER: + // - if defender moves unit on these tiles, bridge will open + // - if there is a creature (dead or alive) on these tiles, bridge will always remain open + // - blocked to attacker if bridge is closed + + // GATE_BRIDGE + // - if there is a unit or corpse here, bridge can't open (and can't close in fortress) + // - if Force Field is cast here, bridge can't open (but can close, in any town) + // - deals moat damage to attacker if bridge is closed (fortress only) + + bool hasForceFieldOnBridge = !battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty(); + bool hasStackAtGateInner = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr; + bool hasStackAtGateOuter = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; + bool hasStackAtGateBridge = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; + bool hasLongBridge = gs->curB->town->subID == ETownType::FORTRESS; + BattleUpdateGateState db; db.state = gs->curB->si.gateState; if (gs->curB->si.wallState[EWallPart::GATE] == EWallState::DESTROYED) @@ -4434,24 +4454,23 @@ void CGameHandler::updateGateState() } else if (db.state == EGateState::OPENED) { - if (!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) && - !gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false)) - { - if (gs->curB->town->subID == ETownType::FORTRESS) - { - if (!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false)) - db.state = EGateState::CLOSED; - } - else if (gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE))) - db.state = EGateState::BLOCKED; - else - db.state = EGateState::CLOSED; - } + bool hasStackOnLongBridge = hasStackAtGateBridge && hasLongBridge; + bool gateCanClose = !hasStackAtGateInner && !hasStackAtGateOuter && !hasStackOnLongBridge; + + if (gateCanClose) + db.state = EGateState::CLOSED; + else + db.state = EGateState::OPENED; + } + else // CLOSED or BLOCKED + { + bool gateBlocked = hasForceFieldOnBridge || hasStackAtGateBridge; + + if (gateBlocked) + db.state = EGateState::BLOCKED; + else + db.state = EGateState::CLOSED; } - else if (gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false)) - db.state = EGateState::BLOCKED; - else - db.state = EGateState::CLOSED; if (db.state != gs->curB->si.gateState) sendAndApply(&db); From a46ab835ceb6c4269ae44b27e015c8a269fcb126 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jan 2023 23:01:33 +0200 Subject: [PATCH 8/9] Applied review suggestions --- lib/battle/CBattleInfoCallback.cpp | 39 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index ab9ddd892..5dae6f485 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -163,33 +163,36 @@ struct Point }; /// Algorithm to test whether line segment between points line1-line2 will intersect with -/// AABB (Axis-Aligned Bounding Box) defined by points aabb1 & aabb2 +/// rectangle specified by top-left and bottom-right points /// Note that in order to avoid floating point rounding errors algorithm uses integers with no divisions -static bool intersectionTestSegmentAABB(Point line1, Point line2, Point aabb1, Point aabb2) +static bool intersectionSegmentRect(Point line1, Point line2, Point rectTL, Point rectBR) { + assert(rectTL.x < rectBR.x); + assert(rectTL.y < rectBR.y); + // check whether segment is located to the left of our AABB - if (line1.x < aabb1.x && line2.x < aabb1.x) + if (line1.x < rectTL.x && line2.x < rectTL.x) return false; // check whether segment is located to the right of our AABB - if (line1.x > aabb2.x && line2.x > aabb2.x) + if (line1.x > rectBR.x && line2.x > rectBR.x) return false; // check whether segment is located on top of our AABB - if (line1.y < aabb1.y && line2.y < aabb1.y) + if (line1.y < rectTL.y && line2.y < rectTL.y) return false; // check whether segment is located below of our AABB - if (line1.y > aabb2.y && line2.y > aabb2.y) + if (line1.y > rectBR.y && line2.y > rectBR.y) return false; Point vector { line2.x - line1.x, line2.y - line1.y}; // compute position of AABB corners relative to our line - int tlTest = vector.y*aabb1.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); - int trTest = vector.y*aabb2.x - vector.x*aabb1.y + (line2.x*line1.y-line1.x*line2.y); - int blTest = vector.y*aabb1.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); - int brTest = vector.y*aabb2.x - vector.x*aabb2.y + (line2.x*line1.y-line1.x*line2.y); + int tlTest = vector.y*rectTL.x - vector.x*rectTL.y + (line2.x*line1.y-line1.x*line2.y); + int trTest = vector.y*rectBR.x - vector.x*rectTL.y + (line2.x*line1.y-line1.x*line2.y); + int blTest = vector.y*rectTL.x - vector.x*rectBR.y + (line2.x*line1.y-line1.x*line2.y); + int brTest = vector.y*rectBR.x - vector.x*rectBR.y + (line2.x*line1.y-line1.x*line2.y); // if all points are on the left of our line then there is no intersection if ( tlTest > 0 && trTest > 0 && blTest > 0 && brTest > 0 ) @@ -222,8 +225,13 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat auto needWallPenalty = [&](BattleHex from, BattleHex dest) { - Point line1{ from.getX()*10+5, from.getY()*10+5}; - Point line2{ dest.getX()*10+5, dest.getY()*10+5}; + // arbitrary selected cell size for virtual grid + // any even number can be selected (for division by two) + static const int cellSize = 10; + + // create line that goes from center of shooter cell to center of target cell + Point line1{ from.getX()*cellSize+cellSize/2, from.getY()*cellSize+cellSize/2}; + Point line2{ dest.getX()*cellSize+cellSize/2, dest.getY()*cellSize+cellSize/2}; for (int y = 0; y < GameConstants::BFIELD_HEIGHT; ++y) { @@ -231,10 +239,11 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat if (!isTileBlocked(obstacle)) continue; - Point aabb1{ obstacle.getX()*10, obstacle.getY()*10 }; - Point aabb2{ aabb1.x + 10, aabb1.y + 10 }; + // create rect around cell with an obstacle + Point rectTL{ obstacle.getX()*cellSize, obstacle.getY()*cellSize }; + Point recrBR{ obstacle.getX()*(cellSize+1), obstacle.getY()*(cellSize+1) }; - if ( intersectionTestSegmentAABB(line1, line2, aabb1, aabb2)) + if ( intersectionSegmentRect(line1, line2, rectTL, recrBR)) return true; } return false; From e53e515aa788f5b1ff0d2bb9c1f5fa00ba98c1e9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jan 2023 01:04:50 +0200 Subject: [PATCH 9/9] Fix msvc compile --- lib/battle/BattleHex.cpp | 2 +- lib/battle/BattleHex.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index a8a026af8..94d8657e2 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -159,7 +159,7 @@ BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) return NONE; } -char BattleHex::getDistance(BattleHex hex1, BattleHex hex2) +uint8_t BattleHex::getDistance(BattleHex hex1, BattleHex hex2) { int y1 = hex1.getY(), y2 = hex2.getY(); diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index f3c92b97c..af0f51c64 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -88,7 +88,7 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f std::vector allNeighbouringTiles() const; static EDir mutualPosition(BattleHex hex1, BattleHex hex2); - static char getDistance(BattleHex hex1, BattleHex hex2); + static uint8_t getDistance(BattleHex hex1, BattleHex hex2); static void checkAndPush(BattleHex tile, std::vector & ret); static BattleHex getClosestTile(ui8 side, BattleHex initialPos, std::set & possibilities); //TODO: vector or set? copying one to another is bad