From 57c35f39cadc7846afbcaedc5a4e04365f386c32 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Thu, 16 Mar 2023 01:49:40 +0300 Subject: [PATCH] WIP: Moat placer --- cmake_modules/VCMI_lib.cmake | 1 + config/spells/other.json | 2 - lib/battle/CObstacleInstance.cpp | 14 ++-- lib/battle/CObstacleInstance.h | 2 + lib/spells/effects/Moat.cpp | 119 +++++++++++++++++++++++++++++++ lib/spells/effects/Moat.h | 38 ++++++++++ lib/spells/effects/Obstacle.cpp | 30 ++++---- lib/spells/effects/Obstacle.h | 8 +-- 8 files changed, 188 insertions(+), 26 deletions(-) create mode 100644 lib/spells/effects/Moat.cpp create mode 100644 lib/spells/effects/Moat.h diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 602278338..3e6c2cf2f 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/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 diff --git a/config/spells/other.json b/config/spells/other.json index a0d9c7620..6970f40d0 100644 --- a/config/spells/other.json +++ b/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" : [[""]], diff --git a/lib/battle/CObstacleInstance.cpp b/lib/battle/CObstacleInstance.cpp index 715412e62..dbfe10a39 100644 --- a/lib/battle/CObstacleInstance.cpp +++ b/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 MoatObstacle::getAffectedTiles() const -{ - return (*VLC->townh)[ID]->town->moatHexes; -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/CObstacleInstance.h b/lib/battle/CObstacleInstance.h index 7b4b52a05..7345cbf51 100644 --- a/lib/battle/CObstacleInstance.h +++ b/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; diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp new file mode 100644 index 000000000..20e45dc38 --- /dev/null +++ b/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 diff --git a/lib/spells/effects/Moat.h b/lib/spells/effects/Moat.h new file mode 100644 index 000000000..2112d8cd2 --- /dev/null +++ b/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 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 diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 36b60d908..9172982de 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/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 availableTiles; - for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) + auto insertAvailable = [&m](const BattleHex & hex, std::vector & availableTiles) { - BattleHex hex = i; if(isHexAvailable(m->battle(), hex, true)) availableTiles.push_back(hex); - } + }; + + 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(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); } - } } diff --git a/lib/spells/effects/Obstacle.h b/lib/spells/effects/Obstacle.h index 4f53e0855..64b5ecaa1 100644 --- a/lib/spells/effects/Obstacle.h +++ b/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 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; }; }