1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Merge pull request #4530 from IvanSavenko/town_fortifications

Support for configurable town fortifications
This commit is contained in:
Ivan Savenko 2024-08-31 13:59:40 +03:00 committed by GitHub
commit 39e5ba32f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 284 additions and 98 deletions

View File

@ -229,7 +229,7 @@ BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * st
{ {
auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart); auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart);
if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED) if(wallState != EWallState::NONE && wallState != EWallState::DESTROYED)
{ {
targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart); targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart);
break; break;

View File

@ -17,6 +17,7 @@
#include "../../lib/CStopWatch.h" #include "../../lib/CStopWatch.h"
#include "../../lib/CThreadHelper.h" #include "../../lib/CThreadHelper.h"
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/entities/building/TownFortifications.h"
#include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/CSpellHandler.h"
#include "../../lib/spells/ISpellMechanics.h" #include "../../lib/spells/ISpellMechanics.h"
#include "../../lib/battle/BattleStateInfoForRetreat.h" #include "../../lib/battle/BattleStateInfoForRetreat.h"
@ -265,7 +266,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
if(score <= EvaluationResult::INEFFECTIVE_SCORE if(score <= EvaluationResult::INEFFECTIVE_SCORE
&& !stack->hasBonusOfType(BonusType::FLYING) && !stack->hasBonusOfType(BonusType::FLYING)
&& stack->unitSide() == BattleSide::ATTACKER && stack->unitSide() == BattleSide::ATTACKER
&& cb->getBattle(battleID)->battleGetSiegeLevel() >= CGTownInstance::CITADEL) && cb->getBattle(battleID)->battleGetFortifications().hasMoat)
{ {
auto brokenWallMoat = getBrokenWallMoatHexes(); auto brokenWallMoat = getBrokenWallMoatHexes();

View File

@ -161,7 +161,7 @@ ECreatureAnimType AttackAnimation::findValidGroup( const std::vector<ECreatureAn
const CCreature * AttackAnimation::getCreature() const const CCreature * AttackAnimation::getCreature() const
{ {
if (attackingStack->unitType()->getId() == CreatureID::ARROW_TOWERS) if (attackingStack->unitType()->getId() == CreatureID::ARROW_TOWERS)
return owner.siegeController->getTurretCreature(); return owner.siegeController->getTurretCreature(attackingStack->initialPosition);
else else
return attackingStack->unitType(); return attackingStack->unitType();
} }

View File

@ -85,7 +85,7 @@ BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet *
this->army2 = army2; this->army2 = army2;
const CGTownInstance *town = getBattle()->battleGetDefendedTown(); const CGTownInstance *town = getBattle()->battleGetDefendedTown();
if(town && town->hasFort()) if(town && town->fortificationsLevel().wallsHealth > 0)
siegeController.reset(new BattleSiegeController(*this, town)); siegeController.reset(new BattleSiegeController(*this, town));
windowObject = std::make_shared<BattleWindow>(*this); windowObject = std::make_shared<BattleWindow>(*this);

View File

@ -160,7 +160,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
const CCreature * creature = stack->unitType(); const CCreature * creature = stack->unitType();
if(creature->getId() == CreatureID::ARROW_TOWERS) if(creature->getId() == CreatureID::ARROW_TOWERS)
creature = owner.siegeController->getTurretCreature(); creature = owner.siegeController->getTurretCreature(stack->initialPosition);
if(creature->animation.missileFrameAngles.empty()) if(creature->animation.missileFrameAngles.empty())
{ {

View File

@ -27,6 +27,7 @@
#include "../../CCallback.h" #include "../../CCallback.h"
#include "../../lib/CStack.h" #include "../../lib/CStack.h"
#include "../../lib/entities/building/TownFortifications.h"
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/networkPacks/PacksForClientBattle.h"
@ -34,30 +35,27 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual
{ {
auto getImageIndex = [&]() -> int auto getImageIndex = [&]() -> int
{ {
bool isTower = (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER); int health = static_cast<int>(state);
switch (state) switch (what)
{ {
case EWallState::REINFORCED : case EWallVisual::KEEP:
return 1; case EWallVisual::BOTTOM_TOWER:
case EWallState::INTACT : case EWallVisual::UPPER_TOWER:
if (town->hasBuilt(BuildingID::CASTLE)) if (health > 0)
return 2; // reinforced walls were damaged return 1;
else else
return 1; return 2;
case EWallState::DAMAGED : default:
// towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2 {
if (isTower) int healthTotal = town->fortificationsLevel().wallsHealth;
return 1; if (healthTotal == health)
else return 1;
return 2; if (health > 0)
case EWallState::DESTROYED : return 2;
if (isTower)
return 2;
else
return 3; return 3;
} }
return 1; };
}; };
const std::string & prefix = town->town->clientInfo.siegePrefix; const std::string & prefix = town->town->clientInfo.siegePrefix;
@ -128,16 +126,15 @@ ImagePath BattleSiegeController::getBattleBackgroundName() const
bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what) const bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what) const
{ {
//FIXME: use this instead of buildings test? const auto & fortifications = town->fortificationsLevel();
//ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();
switch (what) switch (what)
{ {
case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid(); case EWallVisual::MOAT: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid(); case EWallVisual::MOAT_BANK: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED; case EWallVisual::KEEP_BATTLEMENT: return fortifications.citadelHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED; case EWallVisual::UPPER_BATTLEMENT: return fortifications.upperTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED; case EWallVisual::BOTTOM_BATTLEMENT: return fortifications.lowerTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
default: return true; default: return true;
} }
} }
@ -186,9 +183,19 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
} }
} }
const CCreature *BattleSiegeController::getTurretCreature() const const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) const
{ {
return town->town->clientInfo.siegeShooter.toCreature(); switch (position)
{
case BattleHex::CASTLE_CENTRAL_TOWER:
return town->fortificationsLevel().citadelShooter.toCreature();
case BattleHex::CASTLE_UPPER_TOWER:
return town->fortificationsLevel().upperTowerShooter.toCreature();
case BattleHex::CASTLE_BOTTOM_TOWER:
return town->fortificationsLevel().lowerTowerShooter.toCreature();
}
throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.hex));
} }
Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const

View File

@ -104,7 +104,7 @@ public:
/// queries from other battle controllers /// queries from other battle controllers
bool isAttackableByCatapult(BattleHex hex) const; bool isAttackableByCatapult(BattleHex hex) const;
ImagePath getBattleBackgroundName() const; ImagePath getBattleBackgroundName() const;
const CCreature *getTurretCreature() const; const CCreature *getTurretCreature(BattleHex turretPosition) const;
Point getTurretCreaturePosition( BattleHex position ) const; Point getTurretCreaturePosition( BattleHex position ) const;
const CGTownInstance *getSiegedTown() const; const CGTownInstance *getSiegedTown() const;

View File

@ -191,7 +191,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
{ {
assert(owner.siegeController); assert(owner.siegeController);
const CCreature *turretCreature = owner.siegeController->getTurretCreature(); const CCreature *turretCreature = owner.siegeController->getTurretCreature(stack->initialPosition);
stackAnimation[stack->unitId()] = AnimationControls::getAnimation(turretCreature); stackAnimation[stack->unitId()] = AnimationControls::getAnimation(turretCreature);
stackAnimation[stack->unitId()]->pos.h = turretCreatureAnimationHeight; stackAnimation[stack->unitId()]->pos.h = turretCreatureAnimationHeight;

View File

@ -19,9 +19,31 @@
] ]
}, },
"shipyard": { "id" : 6 }, "shipyard": { "id" : 6 },
"fort": { "id" : 7 }, "fort": {
"citadel": { "id" : 8, "upgrades" : "fort" }, "id" : 7,
"castle": { "id" : 9, "upgrades" : "citadel" }, "fortifications" : {
"wallsHealth" : 2
}
},
"citadel": {
"id" : 8,
"upgrades" : "fort",
"fortifications" : {
"citadelHealth" : 2,
"hasMoat" : true
}
},
"castle": {
"id" : 9,
"upgrades" : "citadel",
"fortifications" : {
"wallsHealth" : 3,
"upperTowerHealth" : 2,
"lowerTowerHealth" : 2
}
},
"villageHall": { "villageHall": {
"id" : 10, "id" : 10,

View File

@ -65,6 +65,22 @@
"description" : "Optional, configuration of building that can be activated by visiting hero", "description" : "Optional, configuration of building that can be activated by visiting hero",
"$ref" : "rewardable.json" "$ref" : "rewardable.json"
}, },
"firtufications" : {
"type" : "object",
"additionalProperties" : false,
"description" : "Fortifications provided by this buildings, if any",
"properties" : {
"citadelShooter" : { "type" : "string", "description" : "Creature ID of shooter located in central keep (citadel). Used only if citadel is present." },
"upperTowerShooter" : { "type" : "string", "description" : "Creature ID of shooter located in upper tower. Used only if upper tower is present." },
"lowerTowerShooter" : { "type" : "string", "description" : "Creature ID of shooter located in lower tower. Used only if lower tower is present." },
"wallsHealth" : { "type" : "number", "description" : "Maximum health of destructible walls. Walls are only present if their health is above zero" },
"citadelHealth" : { "type" : "number", "description" : "Maximum health of central tower or 0 if not present. Requires walls presence." },
"upperTowerHealth" : { "type" : "number", "description" : "Maximum health of upper tower or 0 if not present. Requires walls presence." },
"lowerTowerHealth" : { "type" : "number", "description" : "Maximum health of lower tower or 0 if not present. Requires walls presence." },
"hasMoat" : { "type" : "boolean","description" : "If set to true, moat will be placed in front of the walls. Requires walls presence." }
}
},
"cost" : { "cost" : {
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,

View File

@ -157,9 +157,33 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
"produce" : { "produce" : {
"sulfur" : 1, "sulfur" : 1,
"gold" : 2000 "gold" : 2000
}, },
// Optional, allows this building to add fortifications during siege
"fortifications" : {
// Maximum health of destructible walls. Walls are only present if their health is above zero".
// Presence of walls is required for all other fortification types
"wallsHealth" : 3,
//determine how this building can be built. Possible values are: // If set to true, moat will be placed in front of the walls. Requires walls presence.
"hasMoat" : true
// Maximum health of central tower or 0 if not present. Requires walls presence.
"citadelHealth" : 2,
// Maximum health of upper tower or 0 if not present. Requires walls presence.
"upperTowerHealth" : 2,
// Maximum health of lower tower or 0 if not present. Requires walls presence.
"lowerTowerHealth" : 2,
// Creature ID of shooter located in central keep (citadel). Used only if citadel is present.
"citadelShooter" : "archer",
// Creature ID of shooter located in upper tower. Used only if upper tower is present.
"upperTowerShooter" : "archer",
// Creature ID of shooter located in lower tower. Used only if lower tower is present.
"lowerTowerShooter" : "archer",
},
//determine how this building can be built. Possible values are:
// normal - default value. Fulfill requirements, use resources, spend one day // normal - default value. Fulfill requirements, use resources, spend one day
// auto - building appears when all requirements are built // auto - building appears when all requirements are built
// special - building can not be built manually // special - building can not be built manually

View File

@ -450,6 +450,7 @@ set(lib_MAIN_HEADERS
entities/building/CBuilding.h entities/building/CBuilding.h
entities/building/CBuildingHandler.h entities/building/CBuildingHandler.h
entities/building/TownFortifications.h
entities/faction/CFaction.h entities/faction/CFaction.h
entities/faction/CTown.h entities/faction/CTown.h
entities/faction/CTownHandler.h entities/faction/CTownHandler.h

View File

@ -14,6 +14,7 @@
#include "bonuses/Updaters.h" #include "bonuses/Updaters.h"
#include "../CStack.h" #include "../CStack.h"
#include "../CHeroHandler.h" #include "../CHeroHandler.h"
#include "../entities/building/TownFortifications.h"
#include "../filesystem/Filesystem.h" #include "../filesystem/Filesystem.h"
#include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CGTownInstance.h"
#include "../texts/CGeneralTextHandler.h" #include "../texts/CGeneralTextHandler.h"
@ -202,28 +203,25 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
} }
//setting up siege obstacles //setting up siege obstacles
if (town && town->hasFort()) if (town && town->fortificationsLevel().wallsHealth != 0)
{ {
auto fortification = town->fortificationsLevel();
curB->si.gateState = EGateState::CLOSED; curB->si.gateState = EGateState::CLOSED;
curB->si.wallState[EWallPart::GATE] = EWallState::INTACT; curB->si.wallState[EWallPart::GATE] = EWallState::INTACT;
for(const auto wall : {EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL}) for(const auto wall : {EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL})
{ curB->si.wallState[wall] = static_cast<EWallState>(fortification.wallsHealth);
if (town->hasBuilt(BuildingID::CASTLE))
curB->si.wallState[wall] = EWallState::REINFORCED;
else
curB->si.wallState[wall] = EWallState::INTACT;
}
if (town->hasBuilt(BuildingID::CITADEL)) if (fortification.citadelHealth != 0)
curB->si.wallState[EWallPart::KEEP] = EWallState::INTACT; curB->si.wallState[EWallPart::KEEP] = static_cast<EWallState>(fortification.citadelHealth);
if (town->hasBuilt(BuildingID::CASTLE)) if (fortification.upperTowerHealth != 0)
{ curB->si.wallState[EWallPart::UPPER_TOWER] = static_cast<EWallState>(fortification.upperTowerHealth);
curB->si.wallState[EWallPart::UPPER_TOWER] = EWallState::INTACT;
curB->si.wallState[EWallPart::BOTTOM_TOWER] = EWallState::INTACT; if (fortification.lowerTowerHealth != 0)
} curB->si.wallState[EWallPart::BOTTOM_TOWER] = static_cast<EWallState>(fortification.lowerTowerHealth);
} }
//randomize obstacles //randomize obstacles
@ -369,7 +367,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH1, 52); handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH1, 52);
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH2, 18); handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH2, 18);
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH3, 154); handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH3, 154);
if(town && town->hasFort()) if(town && town->fortificationsLevel().wallsHealth > 0)
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH4, 120); handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH4, 120);
} }
@ -419,18 +417,16 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
} }
if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL) if (curB->town)
{ {
// keep tower if (curB->town->fortificationsLevel().citadelHealth != 0)
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER); curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER);
if (curB->town->fortLevel() >= CGTownInstance::CASTLE) if (curB->town->fortificationsLevel().upperTowerHealth != 0)
{
// lower tower + upper tower
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER); curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER);
if (curB->town->fortificationsLevel().lowerTowerHealth != 0)
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER); curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER);
}
//Moat generating is done on server //Moat generating is done on server
} }

View File

@ -18,6 +18,7 @@
#include "CObstacleInstance.h" #include "CObstacleInstance.h"
#include "DamageCalculator.h" #include "DamageCalculator.h"
#include "PossiblePlayerBattleAction.h" #include "PossiblePlayerBattleAction.h"
#include "../entities/building/TownFortifications.h"
#include "../spells/ObstacleCasterProxy.h" #include "../spells/ObstacleCasterProxy.h"
#include "../spells/ISpellMechanics.h" #include "../spells/ISpellMechanics.h"
#include "../spells/Problem.h" #include "../spells/Problem.h"
@ -237,7 +238,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest,
bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const
{ {
RETURN_IF_NOT_BATTLE(false); RETURN_IF_NOT_BATTLE(false);
if(!battleGetSiegeLevel()) if(battleGetFortifications().wallsHealth == 0)
return false; return false;
const std::string cachingStrNoWallPenalty = "type_NO_WALL_PENALTY"; const std::string cachingStrNoWallPenalty = "type_NO_WALL_PENALTY";
@ -288,7 +289,7 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK); allowedActionList.push_back(PossiblePlayerBattleAction::MOVE_STACK);
const auto * siegedTown = battleGetDefendedTown(); const auto * siegedTown = battleGetDefendedTown();
if(siegedTown && siegedTown->hasFort() && stack->hasBonusOfType(BonusType::CATAPULT)) //TODO: check shots if(siegedTown && siegedTown->fortificationsLevel().wallsHealth > 0 && stack->hasBonusOfType(BonusType::CATAPULT)) //TODO: check shots
allowedActionList.push_back(PossiblePlayerBattleAction::CATAPULT); allowedActionList.push_back(PossiblePlayerBattleAction::CATAPULT);
if(stack->hasBonusOfType(BonusType::HEALER)) if(stack->hasBonusOfType(BonusType::HEALER))
allowedActionList.push_back(PossiblePlayerBattleAction::HEAL); allowedActionList.push_back(PossiblePlayerBattleAction::HEAL);
@ -943,7 +944,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
} }
//gate -> should be before stacks //gate -> should be before stacks
if(battleGetSiegeLevel() > 0) if(battleGetFortifications().wallsHealth > 0)
{ {
EAccessibility accessibility = EAccessibility::ACCESSIBLE; EAccessibility accessibility = EAccessibility::ACCESSIBLE;
switch(battleGetGateState()) switch(battleGetGateState())
@ -975,7 +976,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const
} }
//walls //walls
if(battleGetSiegeLevel() > 0) if(battleGetFortifications().wallsHealth > 0)
{ {
static const int permanentlyLocked[] = {12, 45, 62, 112, 147, 165}; static const int permanentlyLocked[] = {12, 45, 62, 112, 147, 165};
for(auto hex : permanentlyLocked) for(auto hex : permanentlyLocked)
@ -1612,7 +1613,7 @@ bool CBattleInfoCallback::isWallPartAttackable(EWallPart wallPart) const
if(isWallPartPotentiallyAttackable(wallPart)) if(isWallPartPotentiallyAttackable(wallPart))
{ {
auto wallState = battleGetWallState(wallPart); auto wallState = battleGetWallState(wallPart);
return (wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED); return (wallState != EWallState::NONE && wallState != EWallState::DESTROYED);
} }
return false; return false;
} }

View File

@ -9,12 +9,15 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "CBattleInfoEssentials.h" #include "CBattleInfoEssentials.h"
#include "../CStack.h" #include "../CStack.h"
#include "BattleInfo.h" #include "BattleInfo.h"
#include "CObstacleInstance.h" #include "CObstacleInstance.h"
#include "../mapObjects/CGTownInstance.h"
#include "../gameState/InfoAboutArmy.h"
#include "../constants/EntityIdentifiers.h" #include "../constants/EntityIdentifiers.h"
#include "../entities/building/TownFortifications.h"
#include "../gameState/InfoAboutArmy.h"
#include "../mapObjects/CGTownInstance.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -345,10 +348,10 @@ bool CBattleInfoEssentials::playerHasAccessToHeroInfo(const PlayerColor & player
return false; return false;
} }
ui8 CBattleInfoEssentials::battleGetSiegeLevel() const TownFortifications CBattleInfoEssentials::battleGetFortifications() const
{ {
RETURN_IF_NOT_BATTLE(CGTownInstance::NONE); RETURN_IF_NOT_BATTLE(TownFortifications());
return getBattle()->getDefendedTown() ? getBattle()->getDefendedTown()->fortLevel() : CGTownInstance::NONE; return getBattle()->getDefendedTown() ? getBattle()->getDefendedTown()->fortificationsLevel() : TownFortifications();
} }
bool CBattleInfoEssentials::battleCanSurrender(const PlayerColor & player) const bool CBattleInfoEssentials::battleCanSurrender(const PlayerColor & player) const
@ -371,7 +374,7 @@ bool CBattleInfoEssentials::battleHasHero(BattleSide side) const
EWallState CBattleInfoEssentials::battleGetWallState(EWallPart partOfWall) const EWallState CBattleInfoEssentials::battleGetWallState(EWallPart partOfWall) const
{ {
RETURN_IF_NOT_BATTLE(EWallState::NONE); RETURN_IF_NOT_BATTLE(EWallState::NONE);
if(battleGetSiegeLevel() == CGTownInstance::NONE) if(battleGetFortifications().wallsHealth == 0)
return EWallState::NONE; return EWallState::NONE;
return getBattle()->getWallState(partOfWall); return getBattle()->getWallState(partOfWall);
@ -380,7 +383,7 @@ EWallState CBattleInfoEssentials::battleGetWallState(EWallPart partOfWall) const
EGateState CBattleInfoEssentials::battleGetGateState() const EGateState CBattleInfoEssentials::battleGetGateState() const
{ {
RETURN_IF_NOT_BATTLE(EGateState::NONE); RETURN_IF_NOT_BATTLE(EGateState::NONE);
if(battleGetSiegeLevel() == CGTownInstance::NONE) if(battleGetFortifications().wallsHealth == 0)
return EGateState::NONE; return EGateState::NONE;
return getBattle()->getGateState(); return getBattle()->getGateState();
@ -389,7 +392,7 @@ EGateState CBattleInfoEssentials::battleGetGateState() const
bool CBattleInfoEssentials::battleIsGatePassable() const bool CBattleInfoEssentials::battleIsGatePassable() const
{ {
RETURN_IF_NOT_BATTLE(true); RETURN_IF_NOT_BATTLE(true);
if(battleGetSiegeLevel() == CGTownInstance::NONE) if(battleGetFortifications().wallsHealth == 0)
return true; return true;
return battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED; return battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED;

View File

@ -18,6 +18,7 @@ class CGHeroInstance;
class CStack; class CStack;
class IBonusBearer; class IBonusBearer;
struct InfoAboutHero; struct InfoAboutHero;
struct TownFortifications;
class CArmedInstance; class CArmedInstance;
using TStacks = std::vector<const CStack *>; using TStacks = std::vector<const CStack *>;
@ -75,7 +76,7 @@ public:
BattleSide playerToSide(const PlayerColor & player) const; BattleSide playerToSide(const PlayerColor & player) const;
PlayerColor sideToPlayer(BattleSide side) const; PlayerColor sideToPlayer(BattleSide side) const;
bool playerHasAccessToHeroInfo(const PlayerColor & player, const CGHeroInstance * h) const; bool playerHasAccessToHeroInfo(const PlayerColor & player, const CGHeroInstance * h) const;
ui8 battleGetSiegeLevel() const; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle TownFortifications battleGetFortifications() const;
bool battleHasHero(BattleSide side) const; bool battleHasHero(BattleSide side) const;
uint32_t battleCastSpells(BattleSide side) const; //how many spells has given side cast uint32_t battleCastSpells(BattleSide side) const; //how many spells has given side cast
const CGHeroInstance * battleGetFightingHero(BattleSide side) const; //deprecated for players callback, easy to get wrong const CGHeroInstance * battleGetFightingHero(BattleSide side) const; //deprecated for players callback, easy to get wrong

View File

@ -9,6 +9,8 @@
*/ */
#pragma once #pragma once
#include "TownFortifications.h"
#include "../../constants/EntityIdentifiers.h" #include "../../constants/EntityIdentifiers.h"
#include "../../LogicalExpression.h" #include "../../LogicalExpression.h"
#include "../../ResourceSet.h" #include "../../ResourceSet.h"
@ -35,6 +37,7 @@ public:
TResources produce; TResources produce;
TRequired requirements; TRequired requirements;
ArtifactID warMachine; ArtifactID warMachine;
TownFortifications fortifications;
std::set<EMarketMode> marketModes; std::set<EMarketMode> marketModes;
BuildingID bid; //structure ID BuildingID bid; //structure ID

View File

@ -0,0 +1,49 @@
/*
* TownFortifications.h, 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
*
*/
#pragma once
#include "../../constants/EntityIdentifiers.h"
VCMI_LIB_NAMESPACE_BEGIN
struct TownFortifications
{
CreatureID citadelShooter;
CreatureID upperTowerShooter;
CreatureID lowerTowerShooter;
SpellID moatSpell;
int8_t wallsHealth = 0;
int8_t citadelHealth = 0;
int8_t upperTowerHealth = 0;
int8_t lowerTowerHealth = 0;
bool hasMoat = false;
const TownFortifications & operator +=(const TownFortifications & other)
{
if (other.citadelShooter.hasValue())
citadelShooter = other.citadelShooter;
if (other.upperTowerShooter.hasValue())
upperTowerShooter = other.upperTowerShooter;
if (other.lowerTowerShooter.hasValue())
lowerTowerShooter = other.lowerTowerShooter;
if (other.moatSpell.hasValue())
moatSpell = other.moatSpell;
wallsHealth = std::max(wallsHealth, other.wallsHealth);
citadelHealth = std::max(citadelHealth, other.citadelHealth);
upperTowerHealth = std::max(upperTowerHealth, other.upperTowerHealth);
lowerTowerHealth = std::max(lowerTowerHealth, other.lowerTowerHealth);
hasMoat = hasMoat || other.hasMoat;
return *this;
}
};
VCMI_LIB_NAMESPACE_END

View File

@ -18,7 +18,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
CTown::CTown() CTown::CTown()
: faction(nullptr), mageLevel(0), primaryRes(0), moatAbility(SpellID::NONE), defaultTavernChance(0) : faction(nullptr), mageLevel(0), primaryRes(0), defaultTavernChance(0)
{ {
} }

View File

@ -9,6 +9,7 @@
*/ */
#pragma once #pragma once
#include "../building/TownFortifications.h"
#include "../../ConstTransitivePtr.h" #include "../../ConstTransitivePtr.h"
#include "../../Point.h" #include "../../Point.h"
#include "../../constants/EntityIdentifiers.h" #include "../../constants/EntityIdentifiers.h"
@ -70,7 +71,10 @@ public:
ui32 mageLevel; //max available mage guild level ui32 mageLevel; //max available mage guild level
GameResID primaryRes; GameResID primaryRes;
CreatureID warMachineDeprecated; CreatureID warMachineDeprecated;
SpellID moatAbility;
/// Base state of fortifications for empty town.
/// Used to define shooter units and moat spell ID
TownFortifications fortifications;
// default chance for hero of specific class to appear in tavern, if field "tavern" was not set // default chance for hero of specific class to appear in tavern, if field "tavern" was not set
// resulting chance = sqrt(town.chance * heroClass.chance) // resulting chance = sqrt(town.chance * heroClass.chance)
@ -99,7 +103,6 @@ public:
std::string siegePrefix; std::string siegePrefix;
std::vector<Point> siegePositions; std::vector<Point> siegePositions;
CreatureID siegeShooter; // shooter creature ID
std::string towerIconSmall; std::string towerIconSmall;
std::string towerIconLarge; std::string towerIconLarge;

View File

@ -299,6 +299,31 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
ret->resources = TResources(source["cost"]); ret->resources = TResources(source["cost"]);
ret->produce = TResources(source["produce"]); ret->produce = TResources(source["produce"]);
const JsonNode & fortifications = source["fortifications"];
if (!fortifications.isNull())
{
VLC->identifiers()->requestIdentifierOptional("creature", fortifications["citadelShooter"], [=](si32 identifier)
{
ret->fortifications.citadelShooter = CreatureID(identifier);
});
VLC->identifiers()->requestIdentifierOptional("creature", fortifications["upperTowerShooter"], [=](si32 identifier)
{
ret->fortifications.upperTowerShooter = CreatureID(identifier);
});
VLC->identifiers()->requestIdentifierOptional("creature", fortifications["lowerTowerShooter"], [=](si32 identifier)
{
ret->fortifications.lowerTowerShooter = CreatureID(identifier);
});
ret->fortifications.wallsHealth = fortifications["wallsHealth"].Integer();
ret->fortifications.citadelHealth = fortifications["citadelHealth"].Integer();
ret->fortifications.upperTowerHealth = fortifications["upperTowerHealth"].Integer();
ret->fortifications.lowerTowerHealth = fortifications["lowerTowerHealth"].Integer();
ret->fortifications.hasMoat = fortifications["hasMoat"].Bool();
}
loadBuildingBonuses(source["bonuses"], ret->buildingBonuses, ret); loadBuildingBonuses(source["bonuses"], ret->buildingBonuses, ret);
if(!source["configuration"].isNull()) if(!source["configuration"].isNull())
@ -477,7 +502,9 @@ void CTownHandler::loadSiegeScreen(CTown &town, const JsonNode & source) const
, town.faction->getNameTranslated() , town.faction->getNameTranslated()
, (*VLC->creh)[crId]->getNameSingularTranslated()); , (*VLC->creh)[crId]->getNameSingularTranslated());
town.clientInfo.siegeShooter = crId; town.fortifications.citadelShooter = crId;
town.fortifications.upperTowerShooter = crId;
town.fortifications.lowerTowerShooter = crId;
}); });
auto & pos = town.clientInfo.siegePositions; auto & pos = town.clientInfo.siegePositions;
@ -581,14 +608,14 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
{ {
VLC->identifiers()->requestIdentifier( "spell", source["moatAbility"], [=](si32 ability) VLC->identifiers()->requestIdentifier( "spell", source["moatAbility"], [=](si32 ability)
{ {
town->moatAbility = SpellID(ability); town->fortifications.moatSpell = SpellID(ability);
}); });
} }
else else
{ {
VLC->identifiers()->requestIdentifier( source.getModScope(), "spell", "castleMoat", [=](si32 ability) VLC->identifiers()->requestIdentifier( source.getModScope(), "spell", "castleMoat", [=](si32 ability)
{ {
town->moatAbility = SpellID(ability); town->fortifications.moatSpell = SpellID(ability);
}); });
} }

View File

@ -248,6 +248,19 @@ bool CGTownInstance::hasCapitol() const
return hasBuilt(BuildingID::CAPITOL); return hasBuilt(BuildingID::CAPITOL);
} }
TownFortifications CGTownInstance::fortificationsLevel() const
{
auto result = town->fortifications;
for (auto const & buildingID : builtBuildings)
result += town->buildings.at(buildingID)->fortifications;
if (result.wallsHealth == 0)
return TownFortifications();
return result;
}
CGTownInstance::CGTownInstance(IGameCallback *cb): CGTownInstance::CGTownInstance(IGameCallback *cb):
CGDwelling(cb), CGDwelling(cb),
town(nullptr), town(nullptr),
@ -387,8 +400,6 @@ void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
DamageRange CGTownInstance::getTowerDamageRange() const DamageRange CGTownInstance::getTowerDamageRange() const
{ {
assert(hasBuilt(BuildingID::CASTLE));
// http://heroes.thelazy.net/wiki/Arrow_tower // http://heroes.thelazy.net/wiki/Arrow_tower
// base damage, irregardless of town level // base damage, irregardless of town level
static constexpr int baseDamage = 6; static constexpr int baseDamage = 6;
@ -405,8 +416,6 @@ DamageRange CGTownInstance::getTowerDamageRange() const
DamageRange CGTownInstance::getKeepDamageRange() const DamageRange CGTownInstance::getKeepDamageRange() const
{ {
assert(hasBuilt(BuildingID::CITADEL));
// http://heroes.thelazy.net/wiki/Arrow_tower // http://heroes.thelazy.net/wiki/Arrow_tower
// base damage, irregardless of town level // base damage, irregardless of town level
static constexpr int baseDamage = 10; static constexpr int baseDamage = 10;

View File

@ -19,6 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN
class CCastleEvent; class CCastleEvent;
class CTown; class CTown;
class TownBuildingInstance; class TownBuildingInstance;
struct TownFortifications;
class TownRewardableBuildingInstance; class TownRewardableBuildingInstance;
struct DamageRange; struct DamageRange;
@ -162,6 +163,7 @@ public:
bool needsLastStack() const override; bool needsLastStack() const override;
CGTownInstance::EFortLevel fortLevel() const; CGTownInstance::EFortLevel fortLevel() const;
TownFortifications fortificationsLevel() const;
int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present

View File

@ -190,6 +190,12 @@ void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::fun
requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false)); requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false));
} }
void CIdentifierStorage::requestIdentifierOptional(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
{
if (!name.isNull())
requestIdentifier(type, name, callback);
}
void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const
{ {
requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true)); requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true));

View File

@ -84,6 +84,8 @@ public:
void requestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const; void requestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback) const; void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback) const;
void requestIdentifierOptional(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;
/// try to request ID. If ID with such name won't be loaded, callback function will not be called /// try to request ID. If ID with such name won't be loaded, callback function will not be called
void tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const; void tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const;
void tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const; void tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const;

View File

@ -33,6 +33,7 @@
#include "CPlayerState.h" #include "CPlayerState.h"
#include "TerrainHandler.h" #include "TerrainHandler.h"
#include "entities/building/CBuilding.h" #include "entities/building/CBuilding.h"
#include "entities/building/TownFortifications.h"
#include "mapObjects/CBank.h" #include "mapObjects/CBank.h"
#include "mapObjects/CGCreature.h" #include "mapObjects/CGCreature.h"
#include "mapObjects/CGMarket.h" #include "mapObjects/CGMarket.h"
@ -2338,7 +2339,7 @@ void CatapultAttack::applyBattle(IBattleState * battleState)
if(!town) if(!town)
return; return;
if(town->fortLevel() == CGTownInstance::NONE) if(town->fortificationsLevel().wallsHealth == 0)
return; return;
for(const auto & part : attackedParts) for(const auto & part : attackedParts)

View File

@ -18,6 +18,7 @@
#include "../../battle/CBattleInfoCallback.h" #include "../../battle/CBattleInfoCallback.h"
#include "../../battle/Unit.h" #include "../../battle/Unit.h"
#include "../../mapObjects/CGTownInstance.h" #include "../../mapObjects/CGTownInstance.h"
#include "../../entities/building/TownFortifications.h"
#include "../../networkPacks/PacksForClientBattle.h" #include "../../networkPacks/PacksForClientBattle.h"
#include "../../serializer/JsonSerializeFormat.h" #include "../../serializer/JsonSerializeFormat.h"
@ -39,7 +40,7 @@ bool Catapult::applicable(Problem & problem, const Mechanics * m) const
return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem); return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
} }
if(CGTownInstance::NONE == town->fortLevel()) if(town->fortificationsLevel().wallsHealth == 0)
{ {
return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem); return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem);
} }

View File

@ -19,6 +19,7 @@
#include "../../bonuses/Limiters.h" #include "../../bonuses/Limiters.h"
#include "../../battle/IBattleState.h" #include "../../battle/IBattleState.h"
#include "../../battle/CBattleInfoCallback.h" #include "../../battle/CBattleInfoCallback.h"
#include "../../entities/building/TownFortifications.h"
#include "../../json/JsonBonus.h" #include "../../json/JsonBonus.h"
#include "../../serializer/JsonSerializeFormat.h" #include "../../serializer/JsonSerializeFormat.h"
#include "../../networkPacks/PacksForClient.h" #include "../../networkPacks/PacksForClient.h"
@ -85,7 +86,7 @@ void Moat::convertBonus(const Mechanics * m, std::vector<Bonus> & converted) con
//Moat battlefield effect is always permanent //Moat battlefield effect is always permanent
nb.duration = BonusDuration::ONE_BATTLE; nb.duration = BonusDuration::ONE_BATTLE;
if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetSiegeLevel() >= CGTownInstance::CITADEL) if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetFortifications().hasMoat)
{ {
nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->town->buildings.at(BuildingID::CITADEL)->getUniqueTypeID()); nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->town->buildings.at(BuildingID::CITADEL)->getUniqueTypeID());
nb.source = BonusSource::TOWN_STRUCTURE; nb.source = BonusSource::TOWN_STRUCTURE;
@ -109,7 +110,7 @@ void Moat::apply(ServerCallback * server, const Mechanics * m, const EffectTarge
{ {
assert(m->isMassive()); assert(m->isMassive());
assert(m->battle()->battleGetDefendedTown()); assert(m->battle()->battleGetDefendedTown());
if(m->isMassive() && m->battle()->battleGetSiegeLevel() >= CGTownInstance::CITADEL) if(m->isMassive() && m->battle()->battleGetFortifications().hasMoat)
{ {
EffectTarget moat; EffectTarget moat;
placeObstacles(server, m, moat); placeObstacles(server, m, moat);

View File

@ -16,6 +16,7 @@
#include "../../battle/IBattleState.h" #include "../../battle/IBattleState.h"
#include "../../battle/CBattleInfoCallback.h" #include "../../battle/CBattleInfoCallback.h"
#include "../../entities/building/TownFortifications.h"
#include "../../networkPacks/PacksForClientBattle.h" #include "../../networkPacks/PacksForClientBattle.h"
#include "../../serializer/JsonSerializeFormat.h" #include "../../serializer/JsonSerializeFormat.h"
@ -239,7 +240,7 @@ bool Obstacle::isHexAvailable(const CBattleInfoCallback * cb, const BattleHex &
if(i->obstacleType != CObstacleInstance::MOAT) if(i->obstacleType != CObstacleInstance::MOAT)
return false; return false;
if(cb->battleGetSiegeLevel() != 0) if(cb->battleGetFortifications().wallsHealth != 0)
{ {
EWallPart part = cb->battleHexToWallPart(hex); EWallPart part = cb->battleHexToWallPart(hex);

View File

@ -15,6 +15,7 @@
#include "../../battle/IBattleState.h" #include "../../battle/IBattleState.h"
#include "../../battle/CBattleInfoCallback.h" #include "../../battle/CBattleInfoCallback.h"
#include "../../battle/Unit.h" #include "../../battle/Unit.h"
#include "../../entities/building/TownFortifications.h"
#include "../../networkPacks/PacksForClientBattle.h" #include "../../networkPacks/PacksForClientBattle.h"
#include "../../serializer/JsonSerializeFormat.h" #include "../../serializer/JsonSerializeFormat.h"
@ -64,7 +65,7 @@ bool Teleport::applicable(Problem & problem, const Mechanics * m, const EffectTa
if(!targetHex.isValid() || !m->battle()->getAccessibility(targetUnit).accessible(targetHex, targetUnit)) if(!targetHex.isValid() || !m->battle()->getAccessibility(targetUnit).accessible(targetHex, targetUnit))
return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem); return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem);
if(m->battle()->battleGetSiegeLevel() && !(isWallPassable && isMoatPassable)) if(m->battle()->battleGetFortifications().wallsHealth > 0 && !(isWallPassable && isMoatPassable))
{ {
return !m->battle()->battleHasPenaltyOnLine(target[0].hexValue, target[1].hexValue, !isWallPassable, !isMoatPassable); return !m->battle()->battleHasPenaltyOnLine(target[0].hexValue, target[1].hexValue, !isWallPassable, !isMoatPassable);
} }

View File

@ -21,6 +21,7 @@
#include "../../lib/battle/CObstacleInstance.h" #include "../../lib/battle/CObstacleInstance.h"
#include "../../lib/battle/IBattleState.h" #include "../../lib/battle/IBattleState.h"
#include "../../lib/battle/BattleAction.h" #include "../../lib/battle/BattleAction.h"
#include "../../lib/entities/building/TownFortifications.h"
#include "../../lib/gameState/CGameState.h" #include "../../lib/gameState/CGameState.h"
#include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/networkPacks/SetStackEffect.h" #include "../../lib/networkPacks/SetStackEffect.h"
@ -651,7 +652,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta
bool canUseGate = false; bool canUseGate = false;
auto dbState = battle.battleGetGateState(); auto dbState = battle.battleGetGateState();
if(battle.battleGetSiegeLevel() > 0 && curStack->unitSide() == BattleSide::DEFENDER && if(battle.battleGetFortifications().wallsHealth > 0 && curStack->unitSide() == BattleSide::DEFENDER &&
dbState != EGateState::DESTROYED && dbState != EGateState::DESTROYED &&
dbState != EGateState::BLOCKED) dbState != EGateState::BLOCKED)
{ {

View File

@ -19,6 +19,7 @@
#include "../../lib/GameSettings.h" #include "../../lib/GameSettings.h"
#include "../../lib/battle/CBattleInfoCallback.h" #include "../../lib/battle/CBattleInfoCallback.h"
#include "../../lib/battle/IBattleState.h" #include "../../lib/battle/IBattleState.h"
#include "../../lib/entities/building/TownFortifications.h"
#include "../../lib/gameState/CGameState.h" #include "../../lib/gameState/CGameState.h"
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/networkPacks/PacksForClientBattle.h"
@ -114,13 +115,18 @@ void BattleFlowProcessor::tryPlaceMoats(const CBattleInfoCallback & battle)
{ {
const auto * town = battle.battleGetDefendedTown(); const auto * town = battle.battleGetDefendedTown();
if (!town)
return;
const auto & fortifications = town->fortificationsLevel();
//Moat should be initialized here, because only here we can use spellcasting //Moat should be initialized here, because only here we can use spellcasting
if (town && town->fortLevel() >= CGTownInstance::CITADEL) if (fortifications.hasMoat)
{ {
const auto * h = battle.battleGetFightingHero(BattleSide::DEFENDER); const auto * h = battle.battleGetFightingHero(BattleSide::DEFENDER);
const auto * actualCaster = h ? static_cast<const spells::Caster*>(h) : nullptr; const auto * actualCaster = h ? static_cast<const spells::Caster*>(h) : nullptr;
auto moatCaster = spells::SilentCaster(battle.sideToPlayer(BattleSide::DEFENDER), actualCaster); auto moatCaster = spells::SilentCaster(battle.sideToPlayer(BattleSide::DEFENDER), actualCaster);
auto cast = spells::BattleCast(&battle, &moatCaster, spells::Mode::PASSIVE, town->town->moatAbility.toSpell()); auto cast = spells::BattleCast(&battle, &moatCaster, spells::Mode::PASSIVE, fortifications.moatSpell.toSpell());
auto target = spells::Target(); auto target = spells::Target();
cast.cast(gameHandler->spellEnv, target); cast.cast(gameHandler->spellEnv, target);
} }

View File

@ -23,6 +23,7 @@
#include "../../lib/battle/CBattleInfoCallback.h" #include "../../lib/battle/CBattleInfoCallback.h"
#include "../../lib/battle/CObstacleInstance.h" #include "../../lib/battle/CObstacleInstance.h"
#include "../../lib/battle/BattleInfo.h" #include "../../lib/battle/BattleInfo.h"
#include "../../lib/entities/building/TownFortifications.h"
#include "../../lib/gameState/CGameState.h" #include "../../lib/gameState/CGameState.h"
#include "../../lib/mapping/CMap.h" #include "../../lib/mapping/CMap.h"
#include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h"
@ -192,7 +193,7 @@ BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray<const CArmedIns
bool BattleProcessor::checkBattleStateChanges(const CBattleInfoCallback & battle) bool BattleProcessor::checkBattleStateChanges(const CBattleInfoCallback & battle)
{ {
//check if drawbridge state need to be changes //check if drawbridge state need to be changes
if (battle.battleGetSiegeLevel() > 0) if (battle.battleGetFortifications().wallsHealth > 0)
updateGateState(battle); updateGateState(battle);
if (resultProcessor->battleIsEnding(battle)) if (resultProcessor->battleIsEnding(battle))