From b37a3dc63c395678015bf9a1c46586d77077de5b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 May 2025 17:07:58 +0300 Subject: [PATCH] Lighthouse / flaggable objects now has dedicated bonus system node this alows to remove multiple hacks from lighthouse logic --- config/objects/lighthouse.json | 5 +- docs/modders/Map_Objects/Flaggable.md | 3 +- lib/gameState/CGameState.cpp | 4 +- lib/gameState/GameStatePackVisitor.cpp | 6 +-- lib/mapObjects/FlaggableMapObject.cpp | 73 +++++++++----------------- lib/mapObjects/FlaggableMapObject.h | 27 ++++++++-- lib/serializer/ESerializationVersion.h | 3 +- 7 files changed, 58 insertions(+), 63 deletions(-) diff --git a/config/objects/lighthouse.json b/config/objects/lighthouse.json index cecabfc0b..e3949e829 100644 --- a/config/objects/lighthouse.json +++ b/config/objects/lighthouse.json @@ -20,10 +20,11 @@ "seaMovement" : { "type" : "MOVEMENT", "subtype" : "heroMovementSea", - "val" : 500 + "val" : 500, + "propagator": "PLAYER_PROPAGATOR" } } } } } -} \ No newline at end of file +} diff --git a/docs/modders/Map_Objects/Flaggable.md b/docs/modders/Map_Objects/Flaggable.md index ea8d21f94..d506f6a79 100644 --- a/docs/modders/Map_Objects/Flaggable.md +++ b/docs/modders/Map_Objects/Flaggable.md @@ -21,7 +21,8 @@ Currently, it is possible to make flaggable objects that provide player with: // Alternatively, it is possible to reuse existing string from H3 using form '@core.advevent.69' "onVisit" : "{Object Name}\r\n\r\nText of messages that player will see on visit.", - // List of bonuses that will be granted to player that owns this object + // List of bonuses that player that owns this object may receive + // Make sure to use required propagator, such as PLAYER_PROPAGATOR "bonuses" : { "firstBonus" : { BONUS FORMAT }, "secondBonus" : { BONUS FORMAT }, diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 5824800bb..ce495bbd2 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1571,7 +1571,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) void CGameState::buildBonusSystemTree() { buildGlobalTeamPlayerTree(); - for(auto & armed : map->getObjects()) + for(auto & armed : map->getObjects()) armed->attachToBonusSystem(*this); } @@ -1580,7 +1580,7 @@ void CGameState::restoreBonusSystemTree() heroesPool->setGameState(this); buildGlobalTeamPlayerTree(); - for(auto & armed : map->getObjects()) + for(auto & armed : map->getObjects()) armed->restoreBonusSystem(*this); for(auto & art : map->getArtifacts()) diff --git a/lib/gameState/GameStatePackVisitor.cpp b/lib/gameState/GameStatePackVisitor.cpp index 9bdb96482..3d64f73aa 100644 --- a/lib/gameState/GameStatePackVisitor.cpp +++ b/lib/gameState/GameStatePackVisitor.cpp @@ -1093,7 +1093,7 @@ void GameStatePackVisitor::visitSetObjectProperty(SetObjectProperty & pack) gs.getPlayerState(newOwner)->addOwnedObject(obj); } - if(pack.what == ObjProperty::OWNER && cai) + if(pack.what == ObjProperty::OWNER) { if(obj->ID == Obj::TOWN) { @@ -1116,9 +1116,9 @@ void GameStatePackVisitor::visitSetObjectProperty(SetObjectProperty & pack) } } - cai->detachFromBonusSystem(gs); + obj->detachFromBonusSystem(gs); obj->setProperty(pack.what, pack.identifier); - cai->attachToBonusSystem(gs); + obj->attachToBonusSystem(gs); } else //not an armed instance { diff --git a/lib/mapObjects/FlaggableMapObject.cpp b/lib/mapObjects/FlaggableMapObject.cpp index 1314cab6f..628282228 100644 --- a/lib/mapObjects/FlaggableMapObject.cpp +++ b/lib/mapObjects/FlaggableMapObject.cpp @@ -12,11 +12,13 @@ #include "FlaggableMapObject.h" #include "CGHeroInstance.h" + +#include "../CPlayerState.h" #include "../callback/CPrivilegedInfoCallback.h" #include "../callback/IGameEventCallback.h" -#include "../networkPacks/PacksForClient.h" +#include "../gameState/CGameState.h" #include "../mapObjectConstructors/FlaggableInstanceConstructor.h" -#include "../gameState/GameStatePackVisitor.h" +#include "../networkPacks/PacksForClient.h" VCMI_LIB_NAMESPACE_BEGIN @@ -40,32 +42,17 @@ void FlaggableMapObject::onHeroVisit(IGameEventCallback & gameEvents, const CGHe if (cb->getPlayerRelations(h->getOwner(), getOwner()) != PlayerRelations::ENEMIES) return; // H3 behavior - revisiting owned Lighthouse is a no-op - if (getOwner().isValidPlayer()) - takeBonusFrom(gameEvents, getOwner()); - gameEvents.setOwner(this, h->getOwner()); //not ours? flag it! InfoWindow iw; iw.player = h->getOwner(); iw.text.appendTextID(getFlaggableHandler()->getVisitMessageTextID()); gameEvents.showInfoDialog(&iw); - - giveBonusTo(gameEvents, h->getOwner()); -} - -void FlaggableMapObject::markAsDeleted() const -{ -// if(getOwner().isValidPlayer()) -// takeBonusFrom(gameEvents, getOwner()); } void FlaggableMapObject::initObj(vstd::RNG & rand) { -// if(getOwner().isValidPlayer()) -// { -// // FIXME: This is dirty hack -// giveBonusTo(gameEvents, getOwner(), true); -// } + initBonuses(); } std::shared_ptr FlaggableMapObject::getFlaggableHandler() const @@ -73,39 +60,10 @@ std::shared_ptr FlaggableMapObject::getFlaggableHa return std::dynamic_pointer_cast(getObjectHandler()); } -void FlaggableMapObject::giveBonusTo(IGameEventCallback & gameEvents, const PlayerColor & player, bool onInit) const +void FlaggableMapObject::initBonuses() { for (auto const & bonus : getFlaggableHandler()->getProvidedBonuses()) - { - GiveBonus gb(GiveBonus::ETarget::PLAYER); - gb.id = player; - gb.bonus = *bonus; - - // FIXME: better place for this code? - gb.bonus.duration = BonusDuration::PERMANENT; - gb.bonus.source = BonusSource::OBJECT_INSTANCE; - gb.bonus.sid = BonusSourceID(id); - - // FIXME: This is really dirty hack - // Proper fix would be to make FlaggableMapObject into bonus system node - // Unfortunately this will cause saves breakage - if(onInit) - { - GameStatePackVisitor visitor(cb->gameState()); - gb.visit(visitor); - } - else - gameEvents.sendAndApply(gb); - } -} - -void FlaggableMapObject::takeBonusFrom(IGameEventCallback & gameEvents, const PlayerColor & player) const -{ - RemoveBonus rb(GiveBonus::ETarget::PLAYER); - rb.whoID = player; - rb.source = BonusSource::OBJECT_INSTANCE; - rb.id = BonusSourceID(id); - gameEvents.sendAndApply(rb); + addNewBonus(bonus); } void FlaggableMapObject::serializeJsonOptions(JsonSerializeFormat& handler) @@ -113,4 +71,21 @@ void FlaggableMapObject::serializeJsonOptions(JsonSerializeFormat& handler) serializeJsonOwner(handler); } +void FlaggableMapObject::attachToBonusSystem(CGameState & gs) +{ + if (getOwner().isValidPlayer()) + attachTo(*gs.getPlayerState(getOwner())); +} + +void FlaggableMapObject::detachFromBonusSystem(CGameState & gs) +{ + if (getOwner().isValidPlayer()) + detachFrom(*gs.getPlayerState(getOwner())); +} + +void FlaggableMapObject::restoreBonusSystem(CGameState & gs) +{ + attachToBonusSystem(gs); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/FlaggableMapObject.h b/lib/mapObjects/FlaggableMapObject.h index db822c9fe..75b8737ed 100644 --- a/lib/mapObjects/FlaggableMapObject.h +++ b/lib/mapObjects/FlaggableMapObject.h @@ -11,32 +11,49 @@ #include "CGObjectInstance.h" #include "IOwnableObject.h" +#include "../bonuses/CBonusSystemNode.h" VCMI_LIB_NAMESPACE_BEGIN struct Bonus; class FlaggableInstanceConstructor; -class DLL_LINKAGE FlaggableMapObject : public CGObjectInstance, public IOwnableObject +class DLL_LINKAGE FlaggableMapObject final : public CGObjectInstance, public IOwnableObject, public CBonusSystemNode { std::shared_ptr getFlaggableHandler() const; - void giveBonusTo(IGameEventCallback & gameEvents, const PlayerColor & player, bool onInit = false) const; - void takeBonusFrom(IGameEventCallback & gameEvents, const PlayerColor & player) const; + void initBonuses(); public: using CGObjectInstance::CGObjectInstance; void onHeroVisit(IGameEventCallback & gameEvents, const CGHeroInstance * h) const override; - void markAsDeleted() const; void initObj(vstd::RNG & rand) override; const IOwnableObject * asOwnable() const final; ResourceSet dailyIncome() const override; std::vector providedCreatures() const override; -protected: + void attachToBonusSystem(CGameState & gs) override; + void detachFromBonusSystem(CGameState & gs) override; + void restoreBonusSystem(CGameState & gs) override; + + PlayerColor getOwner() const override + { + return CGObjectInstance::getOwner(); + } + void serializeJsonOptions(JsonSerializeFormat & handler) override; + + template void serialize(Handler &h) + { + h & static_cast(*this); + + if (h.version >= Handler::Version::FLAGGABLE_BONUS_SYSTEM_NODE) + h & static_cast(*this); + else + initBonuses(); + } }; VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 7124a92ff..d4afc4f37 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -40,8 +40,9 @@ enum class ESerializationVersion : int32_t STACK_INSTANCE_ARMY_FIX, // remove serialization of army that owns stack instance STORE_UID_COUNTER_IN_CMAP, // fix crash caused by conflicting instanceName after loading game REWARDABLE_EXTENSIONS, // new functionality for rewardable objects + FLAGGABLE_BONUS_SYSTEM_NODE, // flaggable objects now contain bonus system node - CURRENT = REWARDABLE_EXTENSIONS, + CURRENT = FLAGGABLE_BONUS_SYSTEM_NODE, }; static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");