Browse Source

WIP: Moat placer

Konstantin 2 years ago
parent
commit
57c35f39ca

+ 1 - 0
cmake_modules/VCMI_lib.cmake

@@ -147,6 +147,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/spells/effects/Effects.cpp
 		${MAIN_LIB_DIR}/spells/effects/Heal.cpp
 		${MAIN_LIB_DIR}/spells/effects/LocationEffect.cpp
+		${MAIN_LIB_DIR}/spells/effects/Moat.cpp
 		${MAIN_LIB_DIR}/spells/effects/Obstacle.cpp
 		${MAIN_LIB_DIR}/spells/effects/Registry.cpp
 		${MAIN_LIB_DIR}/spells/effects/UnitEffect.cpp

+ 0 - 2
config/spells/other.json

@@ -139,7 +139,6 @@
 						"passable" : false,
 						"trap" : false,
 						"trigger" : false,
-						"patchCount" : 1,
 						"turnsRemaining" : 2,
 						"attacker" :{
 							"range" : [[""]],
@@ -213,7 +212,6 @@
 						"passable" : true,
 						"trap" : false,
 						"trigger" : true,
-						"patchCount" : 1,
 						"turnsRemaining" : 2,
 						"attacker" :{
 							"shape" : [[""]],

+ 7 - 7
lib/battle/CObstacleInstance.cpp

@@ -97,7 +97,8 @@ SpellCreatedObstacle::SpellCreatedObstacle()
 	trap(false),
 	removeOnTrigger(false),
 	revealed(false),
-	animationYOffset(0)
+	animationYOffset(0),
+	nativeVisible(true)
 {
 	obstacleType = SPELL_CREATED;
 }
@@ -107,8 +108,11 @@ bool SpellCreatedObstacle::visibleForSide(ui8 side, bool hasNativeStack) const
 	//we hide mines and not discovered quicksands
 	//quicksands are visible to the caster or if owned unit stepped into that particular patch
 	//additionally if side has a native unit, mines/quicksands will be visible
+	//but it is not a case for a moat, so, hasNativeStack should not work for moats
 
-	return casterSide == side || !hidden || revealed || hasNativeStack;
+	auto nativeVis = hasNativeStack && nativeVisible;
+
+	return casterSide == side || !hidden || revealed || nativeVis;
 }
 
 bool SpellCreatedObstacle::blocksTiles() const
@@ -163,6 +167,7 @@ void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeBool("trigger", trigger);
 	handler.serializeBool("trap", trap);
 	handler.serializeBool("removeOnTrigger", removeOnTrigger);
+	handler.serializeBool("nativeVisible", nativeVisible);
 
 	handler.serializeString("appearSound", appearSound);
 	handler.serializeString("appearAnimation", appearAnimation);
@@ -204,9 +209,4 @@ int SpellCreatedObstacle::getAnimationYOffset(int imageHeight) const
 	return offset;
 }
 
-std::vector<BattleHex> MoatObstacle::getAffectedTiles() const
-{
-	return (*VLC->townh)[ID]->town->moatHexes;
-}
-
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/battle/CObstacleInstance.h

@@ -77,6 +77,7 @@ struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance
 	bool removeOnTrigger;
 
 	bool revealed;
+	bool nativeVisible; //Should native terrain creatures reveal obstacle
 
 	std::string appearSound;
 	std::string appearAnimation;
@@ -115,6 +116,7 @@ struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance
 		h & casterSide;
 
 		h & hidden;
+		h & nativeVisible;
 		h & passable;
 		h & trigger;
 		h & trap;

+ 119 - 0
lib/spells/effects/Moat.cpp

@@ -0,0 +1,119 @@
+/*
+ * Moat.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+
+#include "Moat.h"
+
+#include "Registry.h"
+#include "../ISpellMechanics.h"
+
+#include "../../NetPacks.h"
+#include "../../mapObjects/CGTownInstance.h"
+#include "../../battle/IBattleState.h"
+#include "../../battle/CBattleInfoCallback.h"
+#include "../../serializer/JsonSerializeFormat.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+static const std::string EFFECT_NAME = "core:moat";
+
+namespace spells
+{
+namespace effects
+{
+
+VCMI_REGISTER_SPELL_EFFECT(Moat, EFFECT_NAME);
+
+void Moat::serializeJsonEffect(JsonSerializeFormat & handler)
+{
+	handler.serializeBool("hidden", hidden);
+	handler.serializeBool("passable", passable);
+	handler.serializeBool("trigger", trigger);
+	handler.serializeBool("trap", trap);
+	handler.serializeBool("removeOnTrigger", removeOnTrigger);
+	handler.serializeBool("dispellable", dispellable);
+	{
+		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
+}
+
+void Moat::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
+{
+	assert(m->isMassive());
+	assert(m->battle()->battleGetDefendedTown());
+	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);
+	}
+}
+
+void Moat::placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
+{
+	assert(m->battle()->battleGetDefendedTown());
+	assert(m->casterSide == BattleSide::DEFENDER); // Moats are always cast by defender
+	assert(turnsRemaining < 0); // Moats should lasts infininte number of turns
+
+	BattleObstaclesChanged pack;
+
+	auto all = m->battle()->battleGetAllObstacles(BattlePerspective::ALL_KNOWING);
+
+	int obstacleIdToGive = 1;
+	for(auto & one : all)
+		if(one->uniqueID >= obstacleIdToGive)
+			obstacleIdToGive = one->uniqueID + 1;
+
+	for(const Destination & destination : target)
+	{
+		SpellCreatedObstacle obstacle;
+		obstacle.uniqueID = obstacleIdToGive++;
+		obstacle.pos = destination.hexValue;
+		obstacle.obstacleType = dispellable ? CObstacleInstance::SPELL_CREATED : CObstacleInstance::MOAT;
+		obstacle.ID = m->battle()->battleGetDefendedTown()->subID;
+
+		obstacle.turnsRemaining = -1; //Moat cannot be expired
+		obstacle.casterSpellPower = m->getEffectPower();
+		obstacle.spellLevel = m->getEffectLevel(); //todo: level of indirect effect should be also configurable
+		obstacle.casterSide = BattleSide::DEFENDER; // Moats are always cast by defender
+		obstacle.hidden = hidden;
+		obstacle.passable = true; //Moats always passable
+		obstacle.trigger = trigger;
+		obstacle.trap = trap;
+		obstacle.removeOnTrigger = removeOnTrigger;
+		obstacle.nativeVisible = false; //Moats is invisible for native terrain
+
+		// Moats should not have appear sound and appear animation (they are always exists)
+		// Only trigger animation may exists
+		obstacle.triggerSound = sideOptions.triggerSound;
+		obstacle.triggerAnimation = sideOptions.triggerAnimation;
+		obstacle.animation = sideOptions.animation;
+
+		obstacle.animationYOffset = sideOptions.offsetY;
+		pack.changes.emplace_back();
+		obstacle.toInfo(pack.changes.back());
+	}
+
+	if(!pack.changes.empty())
+		server->apply(&pack);
+}
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 38 - 0
lib/spells/effects/Moat.h

@@ -0,0 +1,38 @@
+/*
+ * Moat.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "Obstacle.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace spells
+{
+namespace effects
+{
+
+class Moat : public Obstacle
+{
+private:
+	ObstacleSideOptions sideOptions; //Defender only
+	std::vector<BattleHex> moatHexes;
+	bool dispellable; //For Tower landmines
+public:
+	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
+protected:
+	void serializeJsonEffect(JsonSerializeFormat & handler) override;
+	void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
+};
+
+}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 17 - 13
lib/spells/effects/Obstacle.cpp

@@ -181,30 +181,33 @@ EffectTarget Obstacle::transformTarget(const Mechanics * m, const Target & aimPo
 
 void Obstacle::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
 {
-	if(m->isMassive())
+	if(patchCount > 0)
 	{
 		std::vector<BattleHex> availableTiles;
-		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
+		auto insertAvailable = [&m](const BattleHex & hex, std::vector<BattleHex> & availableTiles)
 		{
-			BattleHex hex = i;
 			if(isHexAvailable(m->battle(), hex, true))
 				availableTiles.push_back(hex);
-		}
-		RandomGeneratorUtil::randomShuffle(availableTiles, *server->getRNG());
+		};
 
-		const int patchesToPut = std::min(patchCount, static_cast<int>(availableTiles.size()));
+		if(m->isMassive())
+			for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
+				insertAvailable(BattleHex(i), availableTiles);
+		else
+			for(const auto & destination : target)
+				insertAvailable(destination.hexValue, availableTiles);
 
+		RandomGeneratorUtil::randomShuffle(availableTiles, *server->getRNG());
+		const int patchesToPut = std::min(patchCount, static_cast<int>(availableTiles.size()));
 		EffectTarget randomTarget;
 		randomTarget.reserve(patchesToPut);
 		for(int i = 0; i < patchesToPut; i++)
 			randomTarget.emplace_back(availableTiles.at(i));
-
 		placeObstacles(server, m, randomTarget);
+		return;
 	}
-	else
-	{
-		placeObstacles(server, m, target);
-	}
+
+	placeObstacles(server, m, target);
 }
 
 void Obstacle::serializeJsonEffect(JsonSerializeFormat & handler)
@@ -213,7 +216,8 @@ void Obstacle::serializeJsonEffect(JsonSerializeFormat & handler)
 	handler.serializeBool("passable", passable);
 	handler.serializeBool("trigger", trigger);
 	handler.serializeBool("trap", trap);
-    handler.serializeBool("removeOnTrigger", removeOnTrigger);
+	handler.serializeBool("removeOnTrigger", removeOnTrigger);
+	handler.serializeBool("hideNative", hideNative);
 
 	handler.serializeInt("patchCount", patchCount);
 	handler.serializeInt("turnsRemaining", turnsRemaining, -1);
@@ -293,6 +297,7 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons
 		obstacle.spellLevel = m->getEffectLevel();//todo: level of indirect effect should be also configurable
 		obstacle.casterSide = m->casterSide;
 
+		obstacle.nativeVisible = !hideNative;
 		obstacle.hidden = hidden;
 		obstacle.passable = passable;
 		obstacle.trigger = trigger;
@@ -328,7 +333,6 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons
 		server->apply(&pack);
 }
 
-
 }
 }
 

+ 4 - 4
lib/spells/effects/Obstacle.h

@@ -54,22 +54,22 @@ public:
 
 protected:
 	void serializeJsonEffect(JsonSerializeFormat & handler) override;
+	virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const;
 
-private:
 	bool hidden = false;
 	bool passable = false;
 	bool trigger = false;
 	bool trap = false;
 	bool removeOnTrigger = false;
-	int32_t patchCount = 1;//random patches to place, only for massive spells
+	bool hideNative = false;
+private:
+	int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine)
 	int32_t turnsRemaining = -1;
 
 	std::array<ObstacleSideOptions, 2> sideOptions;
 
 	static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear);
 	static bool noRoomToPlace(Problem & problem, const Mechanics * m);
-
-	void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const;
 };
 
 }