1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-21 21:17:49 +02:00

vcmi: setup moats using MoatAbility

Setup moats using moat ability, need playtest for now.
-3 to defence not added for now.
This commit is contained in:
Konstantin 2023-03-20 14:02:09 +03:00
parent 6c5f5dba75
commit aab5b47038
23 changed files with 784 additions and 136 deletions

@ -147,8 +147,7 @@
"horde" : [ 2, -1 ],
"mageGuild" : 4,
"warMachine" : "ballista",
"moatDamage" : 70,
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.castleMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
"buildings" :

@ -152,8 +152,7 @@
"mageGuild" : 5,
"primaryResource" : "mercury",
"warMachine" : "ballista",
"moatDamage" : 70,
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.castleMoat",
"buildings" :
{

@ -148,8 +148,8 @@
"mageGuild" : 5,
"primaryResource" : "sulfur",
"warMachine" : "ballista",
"moatDamage" : 90,
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.dungeonMoat",
"buildings" :
{

@ -147,8 +147,7 @@
"horde" : [ 0, -1 ],
"mageGuild" : 3,
"warMachine" : "firstAidTent",
"moatDamage" : 90,
"moatHexes" : [ 10, 11, 27, 28, 43, 44, 60, 61, 76, 77, 94, 110, 111, 128, 129, 145, 146, 163, 164, 180, 181 ],
"moatAbility" : "core:spell.fortressMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
"buildings" :

@ -149,8 +149,7 @@
"mageGuild" : 5,
"primaryResource" : "mercury",
"warMachine" : "ammoCart",
"moatDamage" : 90,
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.infernoMoat",
"buildings" :
{

@ -152,8 +152,7 @@
"horde" : [ 0, -1 ],
"mageGuild" : 5,
"warMachine" : "firstAidTent",
"moatDamage" : 70,
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.necropolisMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
"buildings" :

@ -152,8 +152,7 @@
"mageGuild" : 5,
"primaryResource" : "crystal",
"warMachine" : "firstAidTent",
"moatDamage" : 70,
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.rampartMoat",
"buildings" :
{

@ -145,8 +145,7 @@
"horde" : [ 0, -1 ],
"mageGuild" : 3,
"warMachine" : "ammoCart",
"moatDamage" : 70,
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.strongholdMoat",
// primaryResource not specified so town get both Wood and Ore for resource bonus
"buildings" :

@ -147,8 +147,7 @@
"primaryResource" : "gems",
"mageGuild" : 5,
"warMachine" : "ammoCart",
"moatDamage" : 0, //TODO: minefield
"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
"moatAbility" : "core:spell.towerMoat",
"buildings" :
{

@ -80,6 +80,7 @@
"config/spells/timed.json",
"config/spells/ability.json",
"config/spells/vcmiAbility.json"
"config/spells/moats.json"
],
"skills" :
[

@ -109,7 +109,7 @@
"additionalProperties" : false,
"required" : [
"mapObject", "buildingsIcons", "buildings", "creatures", "guildWindow", "names",
"hallBackground", "hallSlots", "horde", "mageGuild", "moatDamage", "defaultTavern", "tavernVideo", "guildBackground", "musicTheme", "siege", "structures", "townBackground", "warMachine"
"hallBackground", "hallSlots", "horde", "mageGuild", "moatAbility", "defaultTavern", "tavernVideo", "guildBackground", "musicTheme", "siege", "structures", "townBackground", "warMachine"
],
"description": "town",
"properties":{
@ -230,14 +230,9 @@
"type":"number",
"description": "Maximal level of mage guild"
},
"moatDamage": {
"type":"number",
"description": "Damage dealt to creature that entered town moat during siege"
},
"moatHexes": {
"type" : "array",
"description" : "Numbers of battlefield hexes affected by moat during siege",
"items" : { "type" : "number" }
"moatAbility": {
"type":"string",
"description": "Identifier of ability to use as town moat during siege"
},
"musicTheme": {
"type":"string",

677
config/spells/moats.json Normal file

@ -0,0 +1,677 @@
{
"castleMoatTrigger" :
{
"targetType" : "CREATURE",
"type": "ability",
"name": "Moat",
"school": {},
"level": 0,
"power": 0,
"gainChance": {},
"levels" : {
"base": {
"power" : 0,
"range" : "0",
"description" : "", //For validation
"cost" : 0, //For validation
"aiValue" : 0, //For validation
"battleEffects" : {
"directDamage" : {
"type":"core:damage"
}
},
"targetModifier":{"smart":false}
},
"none" : {
},
"basic" : {
},
"advanced" : {
},
"expert" : {
}
},
"flags" : {
"damage": true,
"negative": true,
"special": true
},
"targetCondition" : {
}
},
"castleMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Moat",
"school" : {},
"level": 0,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : false,
"trap" : true,
"trigger" : true,
"triggerAbility" : "core:castleMoatTrigger",
"dispellable" : false,
"removeOnTrigger" : false,
"moatDamage" : 70,
"moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]],
"defender" :{
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
},
"rampartMoatTrigger" :
{
"targetType" : "CREATURE",
"type": "ability",
"name": "Brambles",
"school": {},
"level": 0,
"power": 0,
"gainChance": {},
"levels" : {
"base": {
"power" : 0,
"range" : "0",
"description" : "", //For validation
"cost" : 0, //For validation
"aiValue" : 0, //For validation
"battleEffects" : {
"directDamage" : {
"type":"core:damage"
}
},
"targetModifier":{"smart":false}
},
"none" : {
},
"basic" : {
},
"advanced" : {
},
"expert" : {
}
},
"flags" : {
"damage": true,
"negative": true,
"special": true
},
"targetCondition" : {
}
},
"rampartMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Brambles",
"school" : {},
"level": 0,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : false,
"trap" : true,
"trigger" : true,
"triggerAbility" : "core:rampartMoatTrigger",
"dispellable" : false,
"removeOnTrigger" : false,
"moatDamage" : 70,
"moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]],
"defender" :{
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
},
"towerMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Land Mine",
"school" : {},
"level": 3,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : true,
"trap" : false,
"trigger" : true,
"triggerAbility" : "core:landMineTrigger",
"dispellable" : true,
"removeOnTrigger" : true,
"moatDamage" : 150,
"moatHexes" : [[11], [28], [44], [61], [77], [111], [129], [146], [164], [181]],
"defender" :{
"animation" : "C09SPF1",
"appearAnimation" : "C09SPF0",
"appearSound" : "LANDMINE"
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
},
"infernoMoatTrigger" :
{
"targetType" : "CREATURE",
"type": "ability",
"name": "Lava",
"school": {},
"level": 0,
"power": 0,
"gainChance": {},
"levels" : {
"base": {
"power" : 0,
"range" : "0",
"description" : "", //For validation
"cost" : 0, //For validation
"aiValue" : 0, //For validation
"battleEffects" : {
"directDamage" : {
"type":"core:damage"
}
},
"targetModifier":{"smart":false}
},
"none" : {
},
"basic" : {
},
"advanced" : {
},
"expert" : {
}
},
"flags" : {
"damage": true,
"negative": true,
"special": true
},
"targetCondition" : {
}
},
"infernoMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Lava",
"school" : {},
"level": 0,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : false,
"trap" : true,
"trigger" : true,
"triggerAbility" : "core:infernoMoatTrigger",
"dispellable" : false,
"removeOnTrigger" : false,
"moatDamage" : 90,
"moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]],
"defender" :{
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
},
"necropolisMoatTrigger" :
{
"targetType" : "CREATURE",
"type": "ability",
"name": "Boneyard",
"school": {},
"level": 0,
"power": 0,
"gainChance": {},
"levels" : {
"base": {
"power" : 0,
"range" : "0",
"description" : "", //For validation
"cost" : 0, //For validation
"aiValue" : 0, //For validation
"battleEffects" : {
"directDamage" : {
"type":"core:damage"
}
},
"targetModifier":{"smart":false}
},
"none" : {
},
"basic" : {
},
"advanced" : {
},
"expert" : {
}
},
"flags" : {
"damage": true,
"negative": true,
"special": true
},
"targetCondition" : {
}
},
"necropolisMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Boneyard",
"school" : {},
"level": 0,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : true,
"trap" : false,
"trigger" : true,
"triggerAbility" : "core:necropolisMoatTrigger",
"dispellable" : false,
"removeOnTrigger" : false,
"moatDamage" : 70,
"moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]],
"defender" :{
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
},
"dungeonMoatTrigger" :
{
"targetType" : "CREATURE",
"type": "ability",
"name": "Boiling Oil",
"school": {},
"level": 0,
"power": 0,
"gainChance": {},
"levels" : {
"base": {
"power" : 0,
"range" : "0",
"description" : "", //For validation
"cost" : 0, //For validation
"aiValue" : 0, //For validation
"battleEffects" : {
"directDamage" : {
"type":"core:damage"
}
},
"targetModifier":{"smart":false}
},
"none" : {
},
"basic" : {
},
"advanced" : {
},
"expert" : {
}
},
"flags" : {
"damage": true,
"negative": true,
"special": true
},
"targetCondition" : {
}
},
"dungeonMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Boiling Oil",
"school" : {},
"level": 0,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : false,
"trap" : true,
"trigger" : true,
"triggerAbility" : "core:dungeonMoatTrigger",
"dispellable" : false,
"removeOnTrigger" : false,
"moatDamage" : 90,
"moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]],
"defender" :{
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
},
"strongholdMoatTrigger" :
{
"targetType" : "CREATURE",
"type": "ability",
"name": "Wooden Spikes",
"school": {},
"level": 0,
"power": 0,
"gainChance": {},
"levels" : {
"base": {
"power" : 0,
"range" : "0",
"description" : "", //For validation
"cost" : 0, //For validation
"aiValue" : 0, //For validation
"battleEffects" : {
"directDamage" : {
"type":"core:damage"
}
},
"targetModifier":{"smart":false}
},
"none" : {
},
"basic" : {
},
"advanced" : {
},
"expert" : {
}
},
"flags" : {
"damage": true,
"negative": true,
"special": true
},
"targetCondition" : {
}
},
"strongholdMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Wooden Spikes",
"school" : {},
"level": 0,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : false,
"trap" : true,
"trigger" : true,
"triggerAbility" : "core:strongholdMoatTrigger",
"dispellable" : false,
"removeOnTrigger" : false,
"moatDamage" : 70,
"moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]],
"defender" :{
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
},
"fortressMoatTrigger" :
{
"targetType" : "CREATURE",
"type": "ability",
"name": "Boiling Tar",
"school": {},
"level": 0,
"power": 0,
"gainChance": {},
"levels" : {
"base": {
"power" : 0,
"range" : "0",
"description" : "", //For validation
"cost" : 0, //For validation
"aiValue" : 0, //For validation
"battleEffects" : {
"directDamage" : {
"type":"core:damage"
}
},
"targetModifier":{"smart":false}
},
"none" : {
},
"basic" : {
},
"advanced" : {
},
"expert" : {
}
},
"flags" : {
"damage": true,
"negative": true,
"special": true
},
"targetCondition" : {
}
},
"fortressMoat": {
"targetType" : "NO_TARGET",
"type": "ability",
"name": "Boiling Tar",
"school" : {},
"level": 0,
"power": 0,
"defaultGainChance": 0,
"gainChance": {},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 0,
"cost" : 0,
"targetModifier":{"smart":false},
"battleEffects":{
"moat":{
"type":"core:moat",
"hidden" : false,
"trap" : true,
"trigger" : true,
"triggerAbility" : "core:fortressMoatTrigger",
"dispellable" : false,
"removeOnTrigger" : false,
"moatDamage" : 90,
"moatHexes" : [[10, 11, 27, 28, 43, 44, 60, 61, 76, 77, 94, 110, 111, 128, 129, 145, 146, 163, 164, 180, 181]],
"defender" :{
}
}
},
"range" : "X"
},
"none" :{
},
"basic" :{
},
"advanced" :{
},
"expert" :{
}
},
"flags" : {
"indifferent": true
},
"targetCondition" : {
"nonMagical" : true
}
}
}

@ -173,7 +173,7 @@ void CFaction::serializeJson(JsonSerializeFormat & handler)
CTown::CTown()
: faction(nullptr), mageLevel(0), primaryRes(0), moatDamage(0), defaultTavernChance(0)
: faction(nullptr), mageLevel(0), primaryRes(0), moatAbility(SpellID::NONE), defaultTavernChance(0)
{
}
@ -882,9 +882,6 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
warMachinesToLoad[town] = source["warMachine"];
town->moatDamage = static_cast<si32>(source["moatDamage"].Float());
town->moatHexes = source["moatHexes"].convertTo<std::vector<BattleHex> >();
town->mageLevel = static_cast<ui32>(source["mageGuild"].Float());
town->namesCount = 0;
@ -894,6 +891,11 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
town->namesCount += 1;
}
VLC->modh->identifiers.requestIdentifier(source["moatAbility"], [=](si32 ability)
{
town->moatAbility = SpellID(ability);
});
// Horde building creature level
for(const JsonNode &node : source["horde"].Vector())
town->hordeLvl[static_cast<int>(town->hordeLvl.size())] = static_cast<int>(node.Float());

@ -271,8 +271,7 @@ public:
ui32 mageLevel; //max available mage guild level
ui16 primaryRes;
ArtifactID warMachine;
si32 moatDamage;
std::vector<BattleHex> moatHexes;
SpellID moatAbility;
// default chance for hero of specific class to appear in tavern, if field "tavern" was not set
// resulting chance = sqrt(town.chance * heroClass.chance)
ui32 defaultTavernChance;
@ -339,8 +338,7 @@ public:
h & primaryRes;
h & warMachine;
h & clientInfo;
h & moatDamage;
h & moatHexes;
h & moatAbility;
h & defaultTavernChance;
}

@ -451,12 +451,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER);
}
//moat
auto moat = std::make_shared<MoatObstacle>();
moat->ID = curB->town->subID;
moat->obstacleType = CObstacleInstance::MOAT;
moat->uniqueID = static_cast<si32>(curB->obstacles.size());
curB->obstacles.push_back(moat);
//Moat generating is done on server
}
std::stable_sort(stacks.begin(),stacks.end(),cmpst);

@ -162,6 +162,7 @@ void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler)
handler.serializeInt("spellLevel", spellLevel);
handler.serializeInt("casterSide", casterSide);
handler.serializeInt("minimalDamage", minimalDamage);
handler.serializeInt("type", obstacleType);
handler.serializeBool("hidden", hidden);
handler.serializeBool("revealed", revealed);
@ -201,7 +202,7 @@ int SpellCreatedObstacle::getAnimationYOffset(int imageHeight) const
{
int offset = imageHeight % 42;
if(obstacleType == CObstacleInstance::SPELL_CREATED)
if(obstacleType == CObstacleInstance::SPELL_CREATED || obstacleType == CObstacleInstance::MOAT)
{
offset += animationYOffset;
}

@ -58,11 +58,6 @@ struct DLL_LINKAGE CObstacleInstance
}
};
struct DLL_LINKAGE MoatObstacle : CObstacleInstance
{
std::vector<BattleHex> getAffectedTiles() const override; //for special effects (not blocking)
};
struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance
{
int32_t turnsRemaining;

@ -203,7 +203,6 @@ void registerTypesMapObjects2(Serializer &s)
s.template registerType<CArtifactInstance, CCombinedArtifactInstance>();
//s.template registerType<CObstacleInstance>();
s.template registerType<CObstacleInstance, MoatObstacle>();
s.template registerType<CObstacleInstance, SpellCreatedObstacle>();
}
template<typename Serializer>

@ -15,21 +15,12 @@ VCMI_LIB_NAMESPACE_BEGIN
namespace spells
{
ObstacleCasterProxy::ObstacleCasterProxy(PlayerColor owner_, const Caster * hero_, const SpellCreatedObstacle * obs_):
ProxyCaster(hero_),
owner(std::move(owner_)),
obs(*obs_)
ObstacleCasterProxy::ObstacleCasterProxy(PlayerColor owner_, const Caster * hero_, const SpellCreatedObstacle & obs_):
SilentCaster(owner_, hero_),
obs(obs_)
{
}
int32_t ObstacleCasterProxy::getCasterUnitId() const
{
if(actualCaster)
return actualCaster->getCasterUnitId();
else
return -1;
}
int32_t ObstacleCasterProxy::getSpellSchoolLevel(const Spell * spell, int32_t * outSelectedSchool) const
{
return obs.spellLevel;
@ -44,16 +35,8 @@ int64_t ObstacleCasterProxy::getSpellBonus(const Spell * spell, int64_t base, co
{
if(actualCaster)
return std::max<int64_t>(actualCaster->getSpellBonus(spell, base, affectedStack), obs.minimalDamage);
else
return std::max<int64_t>(base, obs.minimalDamage);
}
int64_t ObstacleCasterProxy::getSpecificSpellBonus(const Spell * spell, int64_t base) const
{
if(actualCaster)
return actualCaster->getSpecificSpellBonus(spell, base);
else
return base;
return std::max<int64_t>(base, obs.minimalDamage);
}
int32_t ObstacleCasterProxy::getEffectPower(const Spell * spell) const
@ -74,25 +57,35 @@ int64_t ObstacleCasterProxy::getEffectValue(const Spell * spell) const
return obs.minimalDamage;
}
PlayerColor ObstacleCasterProxy::getCasterOwner() const
SilentCaster::SilentCaster(PlayerColor owner_, const Caster * hero_):
ProxyCaster(hero_),
owner(std::move(owner_))
{
}
void SilentCaster::getCasterName(MetaString & text) const
{
logGlobal->error("Unexpected call to SilentCaster::getCasterName");
}
void SilentCaster::getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const
{
//do nothing
}
void SilentCaster::spendMana(ServerCallback * server, const int spellCost) const
{
//do nothing
}
PlayerColor SilentCaster::getCasterOwner() const
{
if(actualCaster)
return actualCaster->getCasterOwner();
return owner;
}
void ObstacleCasterProxy::getCasterName(MetaString & text) const
{
logGlobal->error("Unexpected call to ObstacleCasterProxy::getCasterName");
}
void ObstacleCasterProxy::getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const
{
//do nothing
}
void ObstacleCasterProxy::spendMana(ServerCallback * server, const int spellCost) const
{
//do nothing
}
}
VCMI_LIB_NAMESPACE_END

@ -17,26 +17,32 @@ VCMI_LIB_NAMESPACE_BEGIN
namespace spells
{
class DLL_LINKAGE ObstacleCasterProxy : public ProxyCaster
class DLL_LINKAGE SilentCaster : public ProxyCaster
{
protected:
const PlayerColor owner;
public:
ObstacleCasterProxy(PlayerColor owner_, const Caster * hero_, const SpellCreatedObstacle * obs_);
SilentCaster(PlayerColor owner_, const Caster * caster);
int32_t getCasterUnitId() const override;
int32_t getSpellSchoolLevel(const Spell * spell, int32_t * outSelectedSchool = nullptr) const override;
int32_t getEffectLevel(const Spell * spell) const override;
int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override;
int64_t getSpecificSpellBonus(const Spell * spell, int64_t base) const override;
int32_t getEffectPower(const Spell * spell) const override;
int32_t getEnchantPower(const Spell * spell) const override;
int64_t getEffectValue(const Spell * spell) const override;
PlayerColor getCasterOwner() const override;
void getCasterName(MetaString & text) const override;
void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override;
void spendMana(ServerCallback * server, const int spellCost) const override;
PlayerColor getCasterOwner() const override;
};
class DLL_LINKAGE ObstacleCasterProxy : public SilentCaster
{
public:
ObstacleCasterProxy(PlayerColor owner_, const Caster * hero_, const SpellCreatedObstacle & obs_);
int32_t getSpellSchoolLevel(const Spell * spell, int32_t * outSelectedSchool = nullptr) const override;
int32_t getEffectLevel(const Spell * spell) const override;
int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override;
int32_t getEffectPower(const Spell * spell) const override;
int32_t getEnchantPower(const Spell * spell) const override;
int64_t getEffectValue(const Spell * spell) const override;
private:
const PlayerColor owner;
const SpellCreatedObstacle & obs;
};

@ -31,6 +31,23 @@ namespace effects
VCMI_REGISTER_SPELL_EFFECT(Moat, EFFECT_NAME);
static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string & fieldName, std::vector<std::vector<BattleHex>> & moatHexes)
{
{
JsonArraySerializer outer = handler.enterArray(fieldName);
outer.syncSize(moatHexes, JsonNode::JsonType::DATA_VECTOR);
for(size_t outerIndex = 0; outerIndex < outer.size(); outerIndex++)
{
JsonArraySerializer inner = outer.enterArray(outerIndex);
inner.syncSize(moatHexes.at(outerIndex), JsonNode::JsonType::DATA_INTEGER);
for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++)
inner.serializeInt(innerIndex, moatHexes.at(outerIndex).at(innerIndex));
}
}
}
void Moat::serializeJsonEffect(JsonSerializeFormat & handler)
{
handler.serializeBool("hidden", hidden);
@ -39,14 +56,8 @@ void Moat::serializeJsonEffect(JsonSerializeFormat & handler)
handler.serializeBool("removeOnTrigger", removeOnTrigger);
handler.serializeBool("dispellable", dispellable);
handler.serializeInt("moatDamage", moatDamage);
serializeMoatHexes(handler, "moatHexes", moatHexes);
handler.serializeId("triggerAbility", triggerAbility, SpellID::NONE);
{
JsonArraySerializer customSizeJson = handler.enterArray("moatHexes");
customSizeJson.syncSize(moatHexes, JsonNode::JsonType::DATA_INTEGER);
for(size_t index = 0; index < customSizeJson.size(); index++)
customSizeJson.serializeInt(index, moatHexes.at(index));
}
handler.serializeStruct("defender", sideOptions); //Moats are defender only
}
@ -57,10 +68,6 @@ void Moat::apply(ServerCallback * server, const Mechanics * m, const EffectTarge
if(m->isMassive() && m->battle()->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
{
EffectTarget moat;
moat.reserve(moatHexes.size());
for(const auto & tile : moatHexes)
moat.emplace_back(tile);
placeObstacles(server, m, moat);
}
}
@ -80,11 +87,11 @@ void Moat::placeObstacles(ServerCallback * server, const Mechanics * m, const Ef
if(one->uniqueID >= obstacleIdToGive)
obstacleIdToGive = one->uniqueID + 1;
for(const Destination & destination : target)
for(const auto & destination : moatHexes) //Moat hexes can be different obstacles
{
SpellCreatedObstacle obstacle;
obstacle.uniqueID = obstacleIdToGive++;
obstacle.pos = destination.hexValue;
obstacle.pos = destination.at(0);
obstacle.obstacleType = dispellable ? CObstacleInstance::SPELL_CREATED : CObstacleInstance::MOAT;
obstacle.ID = triggerAbility;
@ -102,7 +109,7 @@ void Moat::placeObstacles(ServerCallback * server, const Mechanics * m, const Ef
obstacle.appearSound = sideOptions.appearSound; //For dispellable moats
obstacle.appearAnimation = sideOptions.appearAnimation; //For dispellable moats
obstacle.animation = sideOptions.animation;
obstacle.customSize.emplace_back(obstacle.pos); //All moat hexes are different obstacles
obstacle.customSize.insert(obstacle.customSize.end(),destination.cbegin(), destination.cend());
obstacle.animationYOffset = sideOptions.offsetY;
pack.changes.emplace_back();
obstacle.toInfo(pack.changes.back());

@ -23,7 +23,7 @@ class Moat : public Obstacle
{
private:
ObstacleSideOptions sideOptions; //Defender only
std::vector<BattleHex> moatHexes;
std::vector<std::vector<BattleHex>> moatHexes;
bool dispellable; //For Tower landmines
int moatDamage; // Minimal moat damage
public:

@ -5222,12 +5222,9 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI
{
if(!curStack->alive())
return false;
bool containDamageFromMoat = false;
bool movementStopped = false;
for(auto & obstacle : getAllAffectedObstaclesByStack(curStack, passed))
{
if(obstacle->obstacleType == CObstacleInstance::SPELL_CREATED)
{
//helper info
const SpellCreatedObstacle * spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(obstacle.get());
const ui8 side = curStack->side;
@ -5262,7 +5259,7 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI
};
auto shouldReveal = !spellObstacle->hidden || !gs->curB->battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side);
const auto * hero = gs->curB->battleGetFightingHero(spellObstacle->casterSide);
auto caster = spells::ObstacleCasterProxy(gs->curB->sides.at(spellObstacle->casterSide).color, hero, spellObstacle);
auto caster = spells::ObstacleCasterProxy(gs->curB->sides.at(spellObstacle->casterSide).color, hero, *spellObstacle);
const auto * sp = SpellID(spellObstacle->ID).toSpell();
if(sp)
{
@ -5279,27 +5276,6 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI
}
else if(shouldReveal)
revealObstacles(*spellObstacle);
}
}
else if(obstacle->obstacleType == CObstacleInstance::MOAT)
{
auto town = gs->curB->town;
int damage = (town == nullptr) ? 0 : town->town->moatDamage;
if(!containDamageFromMoat)
{
containDamageFromMoat = true;
BattleStackAttacked bsa;
bsa.damageAmount = damage;
bsa.stackAttacked = curStack->ID;
bsa.attackerID = -1;
curStack->prepareAttacked(bsa, getRandomGenerator());
StacksInjured si;
si.stacks.push_back(bsa);
sendAndApply(&si);
sendGenericKilledLog(curStack, bsa.killedAmount, false);
}
}
if(!curStack->alive())
@ -6347,6 +6323,17 @@ void CGameHandler::runBattle()
assert(gs->curB);
//TODO: pre-tactic stuff, call scripts etc.
//Moat should be initialized here, because only here we can use spellcasting
if (gs->curB->town && gs->curB->town->fortLevel() >= CGTownInstance::CITADEL)
{
const auto * h = gs->curB->battleGetFightingHero(BattleSide::DEFENDER);
const auto * actualCaster = h ? static_cast<const spells::Caster*>(h) : nullptr;
auto moatCaster = spells::SilentCaster(gs->curB->getSidePlayer(BattleSide::DEFENDER), actualCaster);
auto cast = spells::BattleCast(gs->curB, &moatCaster, spells::Mode::PASSIVE, gs->curB->town->town->moatAbility.toSpell());
auto target = spells::Target();
cast.cast(spellEnv, target);
}
//tactic round
{
while (gs->curB->tacticDistance && !battleResult.get())