From aab5b47038f4c8a31eec7d4e66a8e7a9ad32ce3f Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 20 Mar 2023 14:02:09 +0300 Subject: [PATCH] vcmi: setup moats using MoatAbility Setup moats using moat ability, need playtest for now. -3 to defence not added for now. --- config/factions/castle.json | 3 +- config/factions/conflux.json | 3 +- config/factions/dungeon.json | 4 +- config/factions/fortress.json | 3 +- config/factions/inferno.json | 3 +- config/factions/necropolis.json | 3 +- config/factions/rampart.json | 3 +- config/factions/stronghold.json | 3 +- config/factions/tower.json | 3 +- config/gameConfig.json | 1 + config/schemas/faction.json | 13 +- config/spells/moats.json | 677 +++++++++++++++++++++++++++++ lib/CTownHandler.cpp | 10 +- lib/CTownHandler.h | 6 +- lib/battle/BattleInfo.cpp | 7 +- lib/battle/CObstacleInstance.cpp | 3 +- lib/battle/CObstacleInstance.h | 5 - lib/registerTypes/RegisterTypes.h | 1 - lib/spells/ObstacleCasterProxy.cpp | 65 ++- lib/spells/ObstacleCasterProxy.h | 30 +- lib/spells/effects/Moat.cpp | 35 +- lib/spells/effects/Moat.h | 2 +- server/CGameHandler.cpp | 37 +- 23 files changed, 784 insertions(+), 136 deletions(-) create mode 100644 config/spells/moats.json diff --git a/config/factions/castle.json b/config/factions/castle.json index 7d48b39c3..a18c6db98 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -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" : diff --git a/config/factions/conflux.json b/config/factions/conflux.json index e63a770ad..456f67375 100644 --- a/config/factions/conflux.json +++ b/config/factions/conflux.json @@ -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" : { diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index d03e436e6..b0b78fdb0 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -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" : { diff --git a/config/factions/fortress.json b/config/factions/fortress.json index cdfd9fb8f..d65e3ddf9 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -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" : diff --git a/config/factions/inferno.json b/config/factions/inferno.json index bacb77cfa..72b20dcc7 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -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" : { diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 06b6ea00e..673bf1039 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -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" : diff --git a/config/factions/rampart.json b/config/factions/rampart.json index c25deaec6..40d767fa7 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -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" : { diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index d4c7fba71..b7b9ff5b7 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -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" : diff --git a/config/factions/tower.json b/config/factions/tower.json index 89e93b34a..7a9c80c69 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -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" : { diff --git a/config/gameConfig.json b/config/gameConfig.json index 26194789d..337b212cc 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -80,6 +80,7 @@ "config/spells/timed.json", "config/spells/ability.json", "config/spells/vcmiAbility.json" + "config/spells/moats.json" ], "skills" : [ diff --git a/config/schemas/faction.json b/config/schemas/faction.json index f67ba6386..4f21c1652 100644 --- a/config/schemas/faction.json +++ b/config/schemas/faction.json @@ -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", diff --git a/config/spells/moats.json b/config/spells/moats.json new file mode 100644 index 000000000..673de9dd3 --- /dev/null +++ b/config/spells/moats.json @@ -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 + } + } +} \ No newline at end of file diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index e76ee47cf..549474118 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -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(source["moatDamage"].Float()); - town->moatHexes = source["moatHexes"].convertTo >(); - town->mageLevel = static_cast(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(town->hordeLvl.size())] = static_cast(node.Float()); diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 54ef3adec..d95ff05e8 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -271,8 +271,7 @@ public: ui32 mageLevel; //max available mage guild level ui16 primaryRes; ArtifactID warMachine; - si32 moatDamage; - std::vector 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; } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 85658adc7..e6de1ba0a 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -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(); - moat->ID = curB->town->subID; - moat->obstacleType = CObstacleInstance::MOAT; - moat->uniqueID = static_cast(curB->obstacles.size()); - curB->obstacles.push_back(moat); + //Moat generating is done on server } std::stable_sort(stacks.begin(),stacks.end(),cmpst); diff --git a/lib/battle/CObstacleInstance.cpp b/lib/battle/CObstacleInstance.cpp index 7ea0f5eb7..6b04beb9c 100644 --- a/lib/battle/CObstacleInstance.cpp +++ b/lib/battle/CObstacleInstance.cpp @@ -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; } diff --git a/lib/battle/CObstacleInstance.h b/lib/battle/CObstacleInstance.h index f4a5ccb96..018b5b552 100644 --- a/lib/battle/CObstacleInstance.h +++ b/lib/battle/CObstacleInstance.h @@ -58,11 +58,6 @@ struct DLL_LINKAGE CObstacleInstance } }; -struct DLL_LINKAGE MoatObstacle : CObstacleInstance -{ - std::vector getAffectedTiles() const override; //for special effects (not blocking) -}; - struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance { int32_t turnsRemaining; diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index e05e49f02..58347a5b6 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -203,7 +203,6 @@ void registerTypesMapObjects2(Serializer &s) s.template registerType(); //s.template registerType(); - s.template registerType(); s.template registerType(); } template diff --git a/lib/spells/ObstacleCasterProxy.cpp b/lib/spells/ObstacleCasterProxy.cpp index 8d1a74b12..437ffd445 100644 --- a/lib/spells/ObstacleCasterProxy.cpp +++ b/lib/spells/ObstacleCasterProxy.cpp @@ -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(actualCaster->getSpellBonus(spell, base, affectedStack), obs.minimalDamage); - else - return std::max(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(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 & 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 & attacked, MetaString & text) const -{ - //do nothing -} - -void ObstacleCasterProxy::spendMana(ServerCallback * server, const int spellCost) const -{ - //do nothing -} } VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/spells/ObstacleCasterProxy.h b/lib/spells/ObstacleCasterProxy.h index 4e941701c..eacdb0755 100644 --- a/lib/spells/ObstacleCasterProxy.h +++ b/lib/spells/ObstacleCasterProxy.h @@ -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 & 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; }; diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index 1fba587c7..fa8ed948d 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -31,6 +31,23 @@ namespace effects VCMI_REGISTER_SPELL_EFFECT(Moat, EFFECT_NAME); +static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string & fieldName, std::vector> & 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()); diff --git a/lib/spells/effects/Moat.h b/lib/spells/effects/Moat.h index 53fa949a2..c4e45668c 100644 --- a/lib/spells/effects/Moat.h +++ b/lib/spells/effects/Moat.h @@ -23,7 +23,7 @@ class Moat : public Obstacle { private: ObstacleSideOptions sideOptions; //Defender only - std::vector moatHexes; + std::vector> moatHexes; bool dispellable; //For Tower landmines int moatDamage; // Minimal moat damage public: diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c14d2b180..6e1b8971b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -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(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(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())