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:
commit
39e5ba32f6
@ -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;
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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())
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
49
lib/entities/building/TownFortifications.h
Normal file
49
lib/entities/building/TownFortifications.h
Normal 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
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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));
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user