Jelajahi Sumber

vcmi: setup moats using MoatAbility

Setup moats using moat ability, need playtest for now.
-3 to defence not added for now.
Konstantin 2 tahun lalu
induk
melakukan
aab5b47038

+ 1 - 2
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" :

+ 1 - 2
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" :
 			{

+ 2 - 2
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" :
 			{

+ 1 - 2
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" :

+ 1 - 2
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" :
 			{

+ 1 - 2
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" :

+ 1 - 2
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" :
 			{

+ 1 - 2
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" :

+ 1 - 2
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" :
 			{

+ 1 - 0
config/gameConfig.json

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

+ 4 - 9
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",

+ 677 - 0
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
+        }
+    }
+}

+ 6 - 4
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<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());

+ 2 - 4
lib/CTownHandler.h

@@ -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;
 	}
 	

+ 1 - 6
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<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);

+ 2 - 1
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;
 	}

+ 0 - 5
lib/battle/CObstacleInstance.h

@@ -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;

+ 0 - 1
lib/registerTypes/RegisterTypes.h

@@ -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>

+ 20 - 27
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<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_))
 {
-	return owner;
 }
 
-void ObstacleCasterProxy::getCasterName(MetaString & text) const
+void SilentCaster::getCasterName(MetaString & text) const
 {
-	logGlobal->error("Unexpected call to ObstacleCasterProxy::getCasterName");
+	logGlobal->error("Unexpected call to SilentCaster::getCasterName");
 }
 
-void ObstacleCasterProxy::getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const
+void SilentCaster::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
+void SilentCaster::spendMana(ServerCallback * server, const int spellCost) const
 {
 		//do nothing
 }
 
+PlayerColor SilentCaster::getCasterOwner() const
+{
+	if(actualCaster)
+		return actualCaster->getCasterOwner();
+
+	return owner;
+}
+
+
 }
 VCMI_LIB_NAMESPACE_END

+ 15 - 9
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);
+
+	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 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;
 
 private:
-	const PlayerColor owner;
 	const SpellCreatedObstacle & obs;
 };
 

+ 21 - 14
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<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());

+ 1 - 1
lib/spells/effects/Moat.h

@@ -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:

+ 12 - 25
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<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())