diff --git a/CCallback.cpp b/CCallback.cpp index d8a8fac99..8d2709f3d 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -217,12 +217,12 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID) return true; } -bool CCallback::triggerTownSpecialBuildingAction(const CGTownInstance *town, BuildingSubID::EBuildingSubID subBuildingID) +bool CCallback::visitTownBuilding(const CGTownInstance *town, BuildingID buildingID) { if(town->tempOwner!=player) return false; - TriggerTownSpecialBuildingAction pack(town->id, subBuildingID); + VisitTownBuilding pack(town->id, buildingID); sendRequest(&pack); return true; } diff --git a/CCallback.h b/CCallback.h index 0f406dc2c..a934113ce 100644 --- a/CCallback.h +++ b/CCallback.h @@ -75,7 +75,7 @@ public: //town virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE)=0; virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID)=0; - virtual bool triggerTownSpecialBuildingAction(const CGTownInstance *town, BuildingSubID::EBuildingSubID subBuildingID)=0; + virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0; virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0; virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made virtual void swapGarrisonHero(const CGTownInstance *town)=0; @@ -182,7 +182,7 @@ public: void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override; void eraseArtifactByClient(const ArtifactLocation & al) override; bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override; - bool triggerTownSpecialBuildingAction(const CGTownInstance *town, BuildingSubID::EBuildingSubID subBuildingID) override; + bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID) override; void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override; bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 2361608d0..744c51f2b 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -51,7 +51,6 @@ #include #include #include -#include "../lib/serializer/Cast.h" #include "LobbyClientNetPackVisitors.h" #include diff --git a/client/Client.h b/client/Client.h index 3fa2be4f3..600528188 100644 --- a/client/Client.h +++ b/client/Client.h @@ -164,7 +164,7 @@ public: void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs = false) override {}; void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs = false) override {}; - void showBlockingDialog(BlockingDialog * iw) override {}; + void showBlockingDialog(const IObjectInterface * caller, BlockingDialog * iw) override {}; void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {}; void showTeleportDialog(TeleportDialog * iw) override {}; void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) override {}; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 79bb69330..bd582296f 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -743,6 +743,12 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu } } + if (town->rewardableBuildings.count(buildingToTest) && town->town->buildings.at(buildingToTest)->manualHeroVisit) + { + enterRewardable(buildingToTest); + return true; + } + if (buildingToTest >= BuildingID::DWELL_FIRST) { enterDwelling((BuildingID::getLevelFromDwelling(buildingToTest))); @@ -820,7 +826,7 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu return true; case BuildingSubID::BANK: - enterBank(); + enterBank(buildingTarget); return true; } } @@ -837,6 +843,11 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu return false; } +void CCastleBuildings::enterRewardable(BuildingID building) +{ + LOCPLINT->cb->visitTownBuilding(town, building); +} + void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactID) { const CGHeroInstance *hero = town->visitingHero; @@ -1053,7 +1064,7 @@ void CCastleBuildings::enterAnyThievesGuild() LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern")); } -void CCastleBuildings::enterBank() +void CCastleBuildings::enterBank(BuildingID building) { std::vector> components; if(town->bonusValue.second > 0) @@ -1064,7 +1075,7 @@ void CCastleBuildings::enterBank() else{ components.push_back(std::make_shared(ComponentType::RESOURCE, GameResID(GameResID::GOLD), 2500)); - LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.townStructure.bank.borrow"), [this](){ LOCPLINT->cb->triggerTownSpecialBuildingAction(town, BuildingSubID::BANK); }, nullptr, components); + LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.townStructure.bank.borrow"), [this, building](){ LOCPLINT->cb->visitTownBuilding(town, building); }, nullptr, components); } } diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index c756c1236..3b8fb6037 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -167,10 +167,11 @@ public: void enterDwelling(int level); void enterTownHall(); + void enterRewardable(BuildingID building); void enterMagesGuild(); void enterAnyMarket(); void enterAnyThievesGuild(); - void enterBank(); + void enterBank(BuildingID building); void enterToTheQuickRecruitmentWindow(); bool buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget); diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index ef8d1b7e5..54cafa27b 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -179,7 +179,7 @@ "period" : 7, "visitors" : true }, - "visitMode" : "hero", // Should be 'once' to match (somewhat buggy) H3 logic + "visitMode" : "once", "rewards" : [ { "limiter" : { diff --git a/config/schemas/townBuilding.json b/config/schemas/townBuilding.json index 9fdf00e66..8a7635c55 100644 --- a/config/schemas/townBuilding.json +++ b/config/schemas/townBuilding.json @@ -61,11 +61,15 @@ "description" : "If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building", "type" : "boolean" }, + "manualHeroVisit" : { + "description" : "If set to true, this building will not automatically activate on new day or on entering town and needs to be activated manually on click", + "type" : "boolean" + }, "configuration" : { "description" : "Optional, configuration of building that can be activated by visiting hero", "$ref" : "rewardable.json" }, - "firtufications" : { + "fortifications" : { "type" : "object", "additionalProperties" : false, "description" : "Fortifications provided by this buildings, if any", diff --git a/docs/modders/Entities_Format/Town_Building_Format.md b/docs/modders/Entities_Format/Town_Building_Format.md index 3f8339fab..9cc14dd7d 100644 --- a/docs/modders/Entities_Format/Town_Building_Format.md +++ b/docs/modders/Entities_Format/Town_Building_Format.md @@ -16,18 +16,18 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config ##### Order of Fire from Inferno: ```jsonc "special4": { - "requires" : [ "mageGuild1" ], + "requires" : [ "mageGuild1" ], "name" : "Order of Fire", "description" : "Increases spellpower of visiting hero", "cost" : { - "mercury" : 5, + "mercury" : 5, "gold" : 1000 }, "configuration" : { - "visitMode" : "hero", + "visitMode" : "hero", "rewards" : [ - { - // NOTE: this forces vcmi to load string from H3 text file. In order to define own string simply write your own message without '@' symbol + { + // NOTE: this forces vcmi to load string from H3 text file. In order to define own string simply write your own message without '@' symbol "message" : "@core.genrltxt.582", "primary" : { "spellpower" : 1 } } @@ -39,22 +39,22 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config ##### Mana Vortex from Dungeon ```jsonc "special2": { - "requires" : [ "mageGuild1" ], + "requires" : [ "mageGuild1" ], "name" : "Mana Vortex", "description" : "Doubles mana points of the first visiting hero each week", "cost" : { - "gold" : 5000 + "gold" : 5000 }, "configuration" : { - "resetParameters" : { - "period" : 7, + "resetParameters" : { + "period" : 7, "visitors" : true }, "visitMode" : "once", "rewards" : [ - { - "limiter" : { - "noneOf" : [ { "manaPercentage" : 200 } ] + { + "limiter" : { + "noneOf" : [ { "manaPercentage" : 200 } ] }, "message" : "As you near the mana vortex your body is filled with new energy. You have doubled your normal spell points.", "manaPercentage" : 200 @@ -67,14 +67,14 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config #### Resource Silo with custom production ```jsonc "resourceSilo": { - "name" : "Wood Resource Silo", + "name" : "Wood Resource Silo", "description" : "Produces 2 wood every day", "cost" : { - "wood" : 10, + "wood" : 10, "gold" : 5000 }, "produce" : { - "wood": 2 + "wood": 2 } }, ``` @@ -193,11 +193,15 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config // Buildings which bonuses should be overridden with bonuses of the current building "overrides" : [ "anotherBuilding ] - // Bonuses provided by this special building if this building or any of its upgrades are constructed in town + // Bonuses provided by this special building if this building or any of its upgrades are constructed in town + "bonuses" : [ BONUS_FORMAT ] + + // If set to true, this building will not automatically activate on new day or on entering town and needs to be activated manually on click + "manualHeroVisit" : false, + + // Bonuses provided by this special building if this building or any of its upgrades are constructed in town "bonuses" : [ BONUS_FORMAT ] - // If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building" - "upgradeReplacesBonuses" : false, // If the building is a market, it requires market mode. "marketModes" : [ "resource-resource", "resource-player" ], @@ -209,18 +213,18 @@ Building requirements can be described using logical expressions: ```jsonc "requires" : [ - "allOf", // Normal H3 "build all" mode - [ "mageGuild1" ], - [ - "noneOf", // available only when none of these building are built - [ "dwelling5A" ], - [ "dwelling5AUpgrade" ] - ], - [ - "anyOf", // any non-zero number of these buildings must be built - [ "tavern" ], - [ "blacksmith" ] - ] + "allOf", // Normal H3 "build all" mode + [ "mageGuild1" ], + [ + "noneOf", // available only when none of these building are built + [ "dwelling5A" ], + [ "dwelling5AUpgrade" ] + ], + [ + "anyOf", // any non-zero number of these buildings must be built + [ "tavern" ], + [ "blacksmith" ] + ] ] ``` ### List of unique town buildings diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e5b9f8892..f859bb058 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -620,7 +620,6 @@ set(lib_MAIN_HEADERS serializer/JsonSerializeFormat.h serializer/JsonSerializer.h serializer/JsonUpdater.h - serializer/Cast.h serializer/ESerializationVersion.h serializer/RegisterTypes.h serializer/Serializeable.h diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index a4fad1070..a53691f4e 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -33,6 +33,7 @@ class CStackBasicDescriptor; class CGCreature; class CSaveFile; class CLoadFile; +class IObjectInterface; enum class EOpenWindowMode : uint8_t; namespace spells @@ -98,7 +99,7 @@ public: virtual void giveExperience(const CGHeroInstance * hero, TExpType val) =0; virtual void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false)=0; virtual void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false)=0; - virtual void showBlockingDialog(BlockingDialog *iw) =0; + virtual void showBlockingDialog(const IObjectInterface * caller, BlockingDialog *iw) =0; virtual void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) =0; //cb will be called when player closes garrison window virtual void showTeleportDialog(TeleportDialog *iw) =0; virtual void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) = 0; diff --git a/lib/entities/building/CBuilding.h b/lib/entities/building/CBuilding.h index 4f4531a85..9c35fd14c 100644 --- a/lib/entities/building/CBuilding.h +++ b/lib/entities/building/CBuilding.h @@ -44,6 +44,7 @@ public: BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty BuildingSubID::EBuildingSubID subId; /// subtype for special buildings, -1 = the building is not special bool upgradeReplacesBonuses = false; + bool manualHeroVisit = false; BonusList buildingBonuses; Rewardable::Info rewardableObjectInfo; ///configurable rewards for special buildings diff --git a/lib/entities/faction/CTownHandler.cpp b/lib/entities/faction/CTownHandler.cpp index 82f1311b3..355de1f2b 100644 --- a/lib/entities/faction/CTownHandler.cpp +++ b/lib/entities/faction/CTownHandler.cpp @@ -299,6 +299,9 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->resources = TResources(source["cost"]); ret->produce = TResources(source["produce"]); + ret->manualHeroVisit = source["manualHeroVisit"].Bool(); + ret->upgradeReplacesBonuses = source["upgradeReplacesBonuses"].Bool(); + const JsonNode & fortifications = source["fortifications"]; if (!fortifications.isNull()) { diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index b12ad46b4..9a6b975c8 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -178,7 +178,7 @@ void CBank::onHeroVisit(const CGHeroInstance * h) const if (banktext == 32) bd.text.replaceRawString(getObjectName()); - cb->showBlockingDialog(&bd); + cb->showBlockingDialog(this, &bd); } void CBank::doVisit(const CGHeroInstance * hero) const diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index 9924562e9..7dd4cf595 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -164,7 +164,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const ynd.player = h->tempOwner; ynd.text.appendLocalString(EMetaText::ADVOB_TXT, 86); ynd.text.replaceName(getCreature(), getStackCount(SlotID(0))); - cb->showBlockingDialog(&ynd); + cb->showBlockingDialog(this, &ynd); break; } default: //join for gold @@ -180,7 +180,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const boost::algorithm::replace_first(tmp, "%d", std::to_string(action)); boost::algorithm::replace_first(tmp,"%s",VLC->creatures()->getById(getCreature())->getNamePluralTranslated()); ynd.text.appendRawString(tmp); - cb->showBlockingDialog(&ynd); + cb->showBlockingDialog(this, &ynd); break; } } @@ -475,7 +475,7 @@ void CGCreature::flee( const CGHeroInstance * h ) const ynd.player = h->tempOwner; ynd.text.appendLocalString(EMetaText::ADVOB_TXT,91); ynd.text.replaceName(getCreature(), getStackCount(SlotID(0))); - cb->showBlockingDialog(&ynd); + cb->showBlockingDialog(this, &ynd); } void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 983194342..0a417837c 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -244,7 +244,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const else bd.text.replaceLocalString(EMetaText::ARRAY_TXT, 173 + (int)Slots().begin()->second->getQuantityID()*3); bd.text.replaceName(*Slots().begin()->second); - cb->showBlockingDialog(&bd); + cb->showBlockingDialog(this, &bd); return; } @@ -280,7 +280,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const bd.flags |= BlockingDialog::SAFE_TO_AUTOACCEPT; } - cb->showBlockingDialog(&bd); + cb->showBlockingDialog(this, &bd); } void CGDwelling::newTurn(vstd::RNG & rand) const diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 8e931cf90..eaf4d8473 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -175,7 +175,7 @@ void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const BlockingDialog bd (true, false); bd.player = h->getOwner(); bd.text.appendLocalString(EMetaText::ADVOB_TXT, 14); - cb->showBlockingDialog(&bd); + cb->showBlockingDialog(this, &bd); } void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index e19d80cee..078e2ce0c 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -301,12 +301,6 @@ void CGTownInstance::setOwner(const PlayerColor & player) const cb->setOwner(this, player); } -void CGTownInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const -{ - for (auto building : rewardableBuildings) - building.second->blockingDialogAnswered(hero, answer); // FIXME: why call for every building? -} - void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const { if(cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ) == PlayerRelations::ENEMIES) diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index f2cb73c5c..fab98714e 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -239,7 +239,6 @@ public: protected: void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override; void serializeJsonOptions(JsonSerializeFormat & handler) override; - void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; private: FactionID randomizeFaction(vstd::RNG & rand); diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 3ce677ed0..15a9153e8 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -851,7 +851,7 @@ void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const BlockingDialog bd (true, false); bd.player = h->getOwner(); bd.text.appendLocalString (EMetaText::ADVOB_TXT, 17); - cb->showBlockingDialog (&bd); + cb->showBlockingDialog (this, &bd); } else { diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 4fedb7f09..e0ebe2d8f 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -52,7 +52,7 @@ void CRewardableObject::selectRewardWithMessage(const CGHeroInstance * contextHe sd.player = contextHero->tempOwner; sd.text = dialog; sd.components = loadComponents(contextHero, rewardIndices); - cb->showBlockingDialog(&sd); + cb->showBlockingDialog(this, &sd); } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 4a9b589d4..ca0714235 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -88,7 +88,7 @@ void CGMine::onHeroVisit( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->tempOwner; ynd.text.appendLocalString(EMetaText::ADVOB_TXT, isAbandoned() ? 84 : 187); - cb->showBlockingDialog(&ynd); + cb->showBlockingDialog(this, &ynd); return; } @@ -310,7 +310,7 @@ void CGResource::onHeroVisit( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->getOwner(); ynd.text = message; - cb->showBlockingDialog(&ynd); + cb->showBlockingDialog(this, &ynd); } else { @@ -879,7 +879,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const ynd.text.replaceRawString(getArmyDescription()); ynd.text.replaceLocalString(EMetaText::GENERAL_TXT, 43); // creatures } - cb->showBlockingDialog(&ynd); + cb->showBlockingDialog(this, &ynd); } break; case Obj::SPELL_SCROLL: @@ -889,7 +889,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const BlockingDialog ynd(true,false); ynd.player = h->getOwner(); ynd.text = message; - cb->showBlockingDialog(&ynd); + cb->showBlockingDialog(this, &ynd); } else blockingDialogAnswered(h, true); diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index 3bded0bce..dd8b59b6d 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -221,7 +221,7 @@ void TownRewardableBuildingInstance::onHeroVisit(const CGHeroInstance *h) const if (rewards.size() == 1) configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h); - cb->showBlockingDialog(&sd); + cb->showBlockingDialog(this, &sd); }; if(!town->hasBuilt(getBuildingType())) diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 9060bb494..475b9b5db 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -126,7 +126,7 @@ public: virtual void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) {} virtual void visitDisbandCreature(DisbandCreature & pack) {} virtual void visitBuildStructure(BuildStructure & pack) {} - virtual void visitTriggerTownSpecialBuildingAction(TriggerTownSpecialBuildingAction & pack) {} + virtual void visitVisitTownBuilding(VisitTownBuilding & pack) {} virtual void visitRazeStructure(RazeStructure & pack) {} virtual void visitRecruitCreatures(RecruitCreatures & pack) {} virtual void visitUpgradeCreature(UpgradeCreature & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 2e123aa79..44e54bb16 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -582,9 +582,9 @@ void BuildStructure::visitTyped(ICPackVisitor & visitor) visitor.visitBuildStructure(*this); } -void TriggerTownSpecialBuildingAction::visitTyped(ICPackVisitor & visitor) +void VisitTownBuilding::visitTyped(ICPackVisitor & visitor) { - visitor.visitTriggerTownSpecialBuildingAction(*this); + visitor.visitVisitTownBuilding(*this); } void RazeStructure::visitTyped(ICPackVisitor & visitor) diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index c0e40b4c0..a909cf652 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -280,16 +280,16 @@ struct DLL_LINKAGE BuildStructure : public CPackForServer } }; -struct DLL_LINKAGE TriggerTownSpecialBuildingAction : public CPackForServer +struct DLL_LINKAGE VisitTownBuilding : public CPackForServer { - TriggerTownSpecialBuildingAction() = default; - TriggerTownSpecialBuildingAction(const ObjectInstanceID & TID, const BuildingSubID::EBuildingSubID SID) + VisitTownBuilding() = default; + VisitTownBuilding(const ObjectInstanceID & TID, const BuildingID BID) : tid(TID) - , sid(SID) + , bid(BID) { } ObjectInstanceID tid; - BuildingSubID::EBuildingSubID sid; + BuildingID bid; void visitTyped(ICPackVisitor & visitor) override; @@ -297,7 +297,7 @@ struct DLL_LINKAGE TriggerTownSpecialBuildingAction : public CPackForServer { h & static_cast(*this); h & tid; - h & sid; + h & bid; } }; diff --git a/lib/serializer/Cast.h b/lib/serializer/Cast.h deleted file mode 100644 index 7525f6d85..000000000 --- a/lib/serializer/Cast.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Cast.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 - -VCMI_LIB_NAMESPACE_BEGIN - -template -inline const T * dynamic_ptr_cast(const F * ptr) -{ - return dynamic_cast(ptr); -} - -template -inline T * dynamic_ptr_cast(F * ptr) -{ - return dynamic_cast(ptr); -} - -VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 070c76f8e..463e5f75f 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -239,7 +239,7 @@ void registerTypes(Serializer &s) s.template registerType(183); s.template registerType(184); s.template registerType(185); - s.template registerType(186); + s.template registerType(186); s.template registerType(187); s.template registerType(188); s.template registerType(189); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 46b9e92dc..362336458 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -21,6 +21,7 @@ #include "processors/TurnOrderProcessor.h" #include "queries/QueriesProcessor.h" #include "queries/MapQueries.h" +#include "queries/VisitQueries.h" #include "../lib/ArtifactUtils.h" #include "../lib/CArtHandler.h" @@ -1077,9 +1078,9 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, const PlayerColor owne } } -void CGameHandler::showBlockingDialog(BlockingDialog *iw) +void CGameHandler::showBlockingDialog(const IObjectInterface * caller, BlockingDialog *iw) { - auto dialogQuery = std::make_shared(this, *iw); + auto dialogQuery = std::make_shared(this, caller, *iw); queries->addQuery(dialogQuery); iw->queryID = dialogQuery->queryID; sendToAllClients(iw); @@ -1181,7 +1182,10 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta void CGameHandler::visitCastleObjects(const CGTownInstance * t, const CGHeroInstance * h) { for (auto & building : t->rewardableBuildings) - building.second->onHeroVisit(h); + { + if (!t->town->buildings.at(building.first)->manualHeroVisit) + building.second->onHeroVisit(h); + } } void CGameHandler::stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) @@ -2148,20 +2152,38 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, return true; } -bool CGameHandler::triggerTownSpecialBuildingAction(ObjectInstanceID tid, BuildingSubID::EBuildingSubID sid) +bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid) { const CGTownInstance * t = getTown(tid); - if(t->town->getBuildingType(sid) == BuildingID::NONE) + if(!t->hasBuilt(bid)) return false; - if(sid == BuildingSubID::EBuildingSubID::BANK) + auto subID = t->town->buildings.at(bid)->subId; + + if(subID == BuildingSubID::EBuildingSubID::BANK) { TResources res; res[EGameResID::GOLD] = 2500; giveResources(t->getOwner(), res); setObjPropertyValue(t->id, ObjProperty::BONUS_VALUE_SECOND, 2500); + return true; + } + + if (t->rewardableBuildings.count(bid)) + { + auto & hero = t->garrisonHero ? t->garrisonHero : t->visitingHero; + auto * building = t->rewardableBuildings.at(bid); + + if (hero && t->town->buildings.at(bid)->manualHeroVisit) + { + auto visitQuery = std::make_shared(this, t, hero, bid); + queries->addQuery(visitQuery); + building->onHeroVisit(hero); + queries->popIfTop(visitQuery); + return true; + } } return true; @@ -3185,7 +3207,7 @@ void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInsta throw std::runtime_error("Can not visit object that is being visited"); } - std::shared_ptr visitQuery; + std::shared_ptr visitQuery; auto startVisit = [&](ObjectVisitStarted & event) { @@ -3204,7 +3226,7 @@ void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInsta visitedObject = visitedTown; } } - visitQuery = std::make_shared(this, visitedObject, h, visitedObject->visitablePos()); + visitQuery = std::make_shared(this, visitedObject, h); queries->addQuery(visitQuery); //TODO real visit pos HeroVisit hv; @@ -3223,11 +3245,11 @@ void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInsta queries->popIfTop(visitQuery); //visit ends here if no queries were created } -void CGameHandler::objectVisitEnded(const CObjectVisitQuery & query) +void CGameHandler::objectVisitEnded(const CGHeroInstance *h, PlayerColor player) { using events::ObjectVisitEnded; - logGlobal->debug("%s visit ends.\n", query.visitingHero->nodeName()); + logGlobal->debug("%s visit ends.\n", h->nodeName()); auto endVisit = [&](ObjectVisitEnded & event) { @@ -3240,7 +3262,7 @@ void CGameHandler::objectVisitEnded(const CObjectVisitQuery & query) //TODO: ObjectVisitEnded should also have id of visited object, //but this requires object being deleted only by `removeAfterVisit()` but not `removeObject()` - ObjectVisitEnded::defaultExecute(serverEventBus.get(), endVisit, query.players.front(), query.visitingHero->id); + ObjectVisitEnded::defaultExecute(serverEventBus.get(), endVisit, player, h->id); } bool CGameHandler::buildBoat(ObjectInstanceID objid, PlayerColor playerID) @@ -3837,7 +3859,7 @@ bool CGameHandler::isValidObject(const CGObjectInstance *obj) const bool CGameHandler::isBlockedByQueries(const CPack *pack, PlayerColor player) { - if (!strcmp(typeid(*pack).name(), typeid(PlayerMessage).name())) + if (dynamic_cast(pack) != nullptr) return false; auto query = queries->topQuery(player); @@ -3859,7 +3881,7 @@ void CGameHandler::removeAfterVisit(const CGObjectInstance *object) //If the object is being visited, there must be a matching query for (const auto &query : queries->allQueries()) { - if (auto someVistQuery = std::dynamic_pointer_cast(query)) + if (auto someVistQuery = std::dynamic_pointer_cast(query)) { if (someVistQuery->visitedObject == object) { @@ -3923,7 +3945,7 @@ const CGHeroInstance * CGameHandler::getVisitingHero(const CGObjectInstance *obj for(const auto & query : queries->allQueries()) { - auto visit = std::dynamic_pointer_cast(query); + auto visit = std::dynamic_pointer_cast(query); if (visit && visit->visitedObject == obj) return visit->visitingHero; } @@ -3936,7 +3958,7 @@ const CGObjectInstance * CGameHandler::getVisitingObject(const CGHeroInstance *h for(const auto & query : queries->allQueries()) { - auto visit = std::dynamic_pointer_cast(query); + auto visit = std::dynamic_pointer_cast(query); if (visit && visit->visitingHero == hero) return visit->visitedObject; } @@ -3953,7 +3975,7 @@ bool CGameHandler::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, con // visitation query is covered by other query that must be answered first if (auto topQuery = queries->topQuery(hero->getOwner())) - if (auto visit = std::dynamic_pointer_cast(topQuery)) + if (auto visit = std::dynamic_pointer_cast(topQuery)) return !(visit->visitedObject == obj && visit->visitingHero == hero); return true; diff --git a/server/CGameHandler.h b/server/CGameHandler.h index d3a8f65fb..2a12c6a9b 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -113,7 +113,7 @@ public: void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false) override; void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) override; - void showBlockingDialog(BlockingDialog *iw) override; + void showBlockingDialog(const IObjectInterface * caller, BlockingDialog *iw) override; void showTeleportDialog(TeleportDialog *iw) override; void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override; void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) override; @@ -216,7 +216,7 @@ public: bool upgradeCreature( ObjectInstanceID objid, SlotID pos, CreatureID upgID ); bool recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst, CreatureID crid, ui32 cram, si32 level, PlayerColor player); bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings - bool triggerTownSpecialBuildingAction(ObjectInstanceID tid, BuildingSubID::EBuildingSubID sid); + bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); @@ -234,7 +234,7 @@ public: bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true void objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h ); - void objectVisitEnded(const CObjectVisitQuery &query); + void objectVisitEnded(const CGHeroInstance *h, PlayerColor player); bool dig(const CGHeroInstance *h); void moveArmy(const CArmedInstance *src, const CArmedInstance *dst, bool allowMerging); diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index bd98d94ac..a65c3ee0f 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -9,6 +9,7 @@ set(vcmiservercommon_SRCS queries/BattleQueries.cpp queries/CQuery.cpp queries/MapQueries.cpp + queries/VisitQueries.cpp queries/QueriesProcessor.cpp processors/HeroPoolProcessor.cpp @@ -36,6 +37,7 @@ set(vcmiservercommon_HEADERS queries/BattleQueries.h queries/CQuery.h queries/MapQueries.h + queries/VisitQueries.h queries/QueriesProcessor.h processors/HeroPoolProcessor.h diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index d1ebbf895..2a7b493d4 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -27,7 +27,6 @@ #include "../lib/battle/Unit.h" #include "../lib/spells/CSpellHandler.h" #include "../lib/spells/ISpellMechanics.h" -#include "../lib/serializer/Cast.h" void ApplyGhNetPackVisitor::visitSaveGame(SaveGame & pack) { @@ -139,12 +138,12 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack) result = gh.buildStructure(pack.tid, pack.bid); } -void ApplyGhNetPackVisitor::visitTriggerTownSpecialBuildingAction(TriggerTownSpecialBuildingAction & pack) +void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack) { gh.throwIfWrongOwner(&pack, pack.tid); gh.throwIfPlayerNotActive(&pack); - result = gh.triggerTownSpecialBuildingAction(pack.tid, pack.sid); + result = gh.visitTownBuilding(pack.tid, pack.bid); } void ApplyGhNetPackVisitor::visitRecruitCreatures(RecruitCreatures & pack) diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index 68601e929..34593c1da 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -41,7 +41,7 @@ public: void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) override; void visitDisbandCreature(DisbandCreature & pack) override; void visitBuildStructure(BuildStructure & pack) override; - void visitTriggerTownSpecialBuildingAction(TriggerTownSpecialBuildingAction & pack) override; + void visitVisitTownBuilding(VisitTownBuilding & pack) override; void visitRecruitCreatures(RecruitCreatures & pack) override; void visitUpgradeCreature(UpgradeCreature & pack) override; void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) override; diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 45e08c59b..614f9e59b 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -27,7 +27,6 @@ #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/networkPacks/PacksForClient.h" -#include "../../lib/serializer/Cast.h" #include "../../lib/spells/CSpellHandler.h" #include @@ -81,7 +80,7 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CBattleInfoCallback & battle, else if(warMachine != ArtifactID::CATAPULT && st->getCount() <= 0) { logGlobal->debug("War machine has been destroyed"); - auto hero = dynamic_ptr_cast (army); + auto hero = dynamic_cast (army); if (hero) removedWarMachines.push_back (ArtifactLocation(hero->id, hero->getArtPos(warMachine, true))); else diff --git a/server/queries/BattleQueries.cpp b/server/queries/BattleQueries.cpp index 4fc0c25f0..80ac9029c 100644 --- a/server/queries/BattleQueries.cpp +++ b/server/queries/BattleQueries.cpp @@ -21,14 +21,13 @@ #include "../../lib/mapObjects/CGObjectInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForServer.h" -#include "../../lib/serializer/Cast.h" -void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const +void CBattleQuery::notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const { assert(result); if(result) - objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result); + visitedObject->battleFinished(visitingHero, *result); } CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi): @@ -51,10 +50,10 @@ CBattleQuery::CBattleQuery(CGameHandler * owner): bool CBattleQuery::blocksPack(const CPack * pack) const { - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; return true; diff --git a/server/queries/BattleQueries.h b/server/queries/BattleQueries.h index 6916cb7f5..ae32b08b3 100644 --- a/server/queries/BattleQueries.h +++ b/server/queries/BattleQueries.h @@ -29,7 +29,7 @@ public: CBattleQuery(CGameHandler * owner); CBattleQuery(CGameHandler * owner, const IBattleInfo * Bi); //TODO - void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; bool blocksPack(const CPack *pack) const override; void onRemoval(PlayerColor color) override; void onExposure(QueryPtr topQuery) override; diff --git a/server/queries/CQuery.cpp b/server/queries/CQuery.cpp index 02eb14762..3b9d8dd84 100644 --- a/server/queries/CQuery.cpp +++ b/server/queries/CQuery.cpp @@ -14,7 +14,6 @@ #include "../CGameHandler.h" -#include "../../lib/serializer/Cast.h" #include "../../lib/networkPacks/PacksForServer.h" std::ostream & operator<<(std::ostream & out, const CQuery & query) @@ -87,7 +86,7 @@ bool CQuery::blocksPack(const CPack * pack) const return false; } -void CQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const +void CQuery::notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const { } @@ -116,7 +115,7 @@ void CQuery::setReply(std::optional reply) bool CQuery::blockAllButReply(const CPack * pack) const { //We accept only query replies from correct player - if(auto reply = dynamic_ptr_cast(pack)) + if(auto reply = dynamic_cast(pack)) return !vstd::contains(players, reply->player); return true; diff --git a/server/queries/CQuery.h b/server/queries/CQuery.h index 5773ecadd..2fde16295 100644 --- a/server/queries/CQuery.h +++ b/server/queries/CQuery.h @@ -14,6 +14,8 @@ VCMI_LIB_NAMESPACE_BEGIN struct CPack; +class CGObjectInstance; +class CGHeroInstance; VCMI_LIB_NAMESPACE_END @@ -40,18 +42,29 @@ public: CQuery(CGameHandler * gh); - virtual bool blocksPack(const CPack *pack) const; //query can block attempting actions by player. Eg. he can't move hero during the battle. + /// query can block attempting actions by player. Eg. he can't move hero during the battle. + virtual bool blocksPack(const CPack *pack) const; - virtual bool endsByPlayerAnswer() const; //query is removed after player gives answer (like dialogs) - virtual void onAdding(PlayerColor color); //called just before query is pushed on stack - virtual void onAdded(PlayerColor color); //called right after query is pushed on stack - virtual void onRemoval(PlayerColor color); //called after query is removed from stack - virtual void onExposure(QueryPtr topQuery);//called when query immediately above is removed and this is exposed (becomes top) - virtual std::string toString() const; + /// query is removed after player gives answer (like dialogs) + virtual bool endsByPlayerAnswer() const; - virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const; + /// called just before query is pushed on stack + virtual void onAdding(PlayerColor color); + + /// called right after query is pushed on stack + virtual void onAdded(PlayerColor color); + + /// called after query is removed from stack + virtual void onRemoval(PlayerColor color); + + /// called when query immediately above is removed and this is exposed (becomes top) + virtual void onExposure(QueryPtr topQuery); + + /// called when this query is being removed and must report its result to currently visited object + virtual void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const; virtual void setReply(std::optional reply); + virtual std::string toString() const; virtual ~CQuery(); protected: diff --git a/server/queries/MapQueries.cpp b/server/queries/MapQueries.cpp index 72f9bfbce..2881368d4 100644 --- a/server/queries/MapQueries.cpp +++ b/server/queries/MapQueries.cpp @@ -13,10 +13,9 @@ #include "QueriesProcessor.h" #include "../CGameHandler.h" #include "../TurnTimerHandler.h" -#include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/networkPacks/PacksForServer.h" -#include "../../lib/serializer/Cast.h" TimerPauseQuery::TimerPauseQuery(CGameHandler * owner, PlayerColor player): CQuery(owner) @@ -44,41 +43,9 @@ bool TimerPauseQuery::endsByPlayerAnswer() const return true; } -CObjectVisitQuery::CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, int3 Tile): - CQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false) +void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const { - addPlayer(Hero->tempOwner); -} - -bool CObjectVisitQuery::blocksPack(const CPack *pack) const -{ - //During the visit itself ALL actions are blocked. - //(However, the visit may trigger a query above that'll pass some.) - return true; -} - -void CObjectVisitQuery::onRemoval(PlayerColor color) -{ - gh->objectVisitEnded(*this); - - //TODO or should it be destructor? - //Can object visit affect 2 players and what would be desired behavior? - if(removeObjectAfterVisit) - gh->removeObject(visitedObject, color); -} - -void CObjectVisitQuery::onExposure(QueryPtr topQuery) -{ - //Object may have been removed and deleted. - if(gh->isValidObject(visitedObject)) - topQuery->notifyObjectAboutRemoval(*this); - - owner->popIfTop(*this); -} - -void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const -{ - objectVisit.visitedObject->garrisonDialogClosed(objectVisit.visitingHero); + visitedObject->garrisonDialogClosed(visitingHero); } CGarrisonDialogQuery::CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance * up, const CArmedInstance * down): @@ -97,22 +64,22 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const ourIds.insert(this->exchangingArmies[0]->id); ourIds.insert(this->exchangingArmies[1]->id); - if(auto stacks = dynamic_ptr_cast(pack)) + if(auto stacks = dynamic_cast(pack)) return !vstd::contains(ourIds, stacks->id1) || !vstd::contains(ourIds, stacks->id2); - if(auto stacks = dynamic_ptr_cast(pack)) + if(auto stacks = dynamic_cast(pack)) return !vstd::contains(ourIds, stacks->srcOwner); - if(auto stacks = dynamic_ptr_cast(pack)) + if(auto stacks = dynamic_cast(pack)) return !vstd::contains(ourIds, stacks->srcOwner); - if(auto stacks = dynamic_ptr_cast(pack)) + if(auto stacks = dynamic_cast(pack)) return !vstd::contains(ourIds, stacks->srcOwner); - if(auto stacks = dynamic_ptr_cast(pack)) + if(auto stacks = dynamic_cast(pack)) return !vstd::contains(ourIds, stacks->srcArmy) || !vstd::contains(ourIds, stacks->destArmy); - if(auto arts = dynamic_ptr_cast(pack)) + if(auto arts = dynamic_cast(pack)) { if(auto id1 = arts->src.artHolder) if(!vstd::contains(ourIds, id1)) @@ -123,41 +90,42 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const return true; return false; } - if(auto dismiss = dynamic_ptr_cast(pack)) + if(auto dismiss = dynamic_cast(pack)) return !vstd::contains(ourIds, dismiss->id); - if(auto arts = dynamic_ptr_cast(pack)) + if(auto arts = dynamic_cast(pack)) return !vstd::contains(ourIds, arts->srcHero) || !vstd::contains(ourIds, arts->dstHero); - if(auto arts = dynamic_ptr_cast(pack)) + if(auto arts = dynamic_cast(pack)) return !vstd::contains(ourIds, arts->artHolder); - if(auto art = dynamic_ptr_cast(pack)) + if(auto art = dynamic_cast(pack)) { if(auto id = art->al.artHolder) return !vstd::contains(ourIds, id); } - if(auto dismiss = dynamic_ptr_cast(pack)) + if(auto dismiss = dynamic_cast(pack)) return !vstd::contains(ourIds, dismiss->heroID); - if(auto upgrade = dynamic_ptr_cast(pack)) + if(auto upgrade = dynamic_cast(pack)) return !vstd::contains(ourIds, upgrade->id); - if(auto formation = dynamic_ptr_cast(pack)) + if(auto formation = dynamic_cast(pack)) return !vstd::contains(ourIds, formation->hid); return CDialogQuery::blocksPack(pack); } -void CBlockingDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const +void CBlockingDialogQuery::notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const { assert(answer); - objectVisit.visitedObject->blockingDialogAnswered(objectVisit.visitingHero, *answer); + caller->blockingDialogAnswered(visitingHero, *answer); } -CBlockingDialogQuery::CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog & bd): - CDialogQuery(owner) +CBlockingDialogQuery::CBlockingDialogQuery(CGameHandler * owner, const IObjectInterface * caller, const BlockingDialog & bd): + CDialogQuery(owner), + caller(caller) { this->bd = bd; addPlayer(bd.player); @@ -179,56 +147,55 @@ bool OpenWindowQuery::blocksPack(const CPack *pack) const { if (mode == EOpenWindowMode::RECRUITMENT_FIRST || mode == EOpenWindowMode::RECRUITMENT_ALL) { - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; // If hero has no free slots, he might get some stacks merged automatically - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; } if (mode == EOpenWindowMode::TAVERN_WINDOW) { - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; } if (mode == EOpenWindowMode::UNIVERSITY_WINDOW) { - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; } if (mode == EOpenWindowMode::MARKET_WINDOW) { - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; - if(dynamic_ptr_cast(pack)) + if(dynamic_cast(pack)) return false; - if(dynamic_ptr_cast(pack)) + if(dynamic_cast(pack)) return false; - if(dynamic_ptr_cast(pack) != nullptr) + if(dynamic_cast(pack) != nullptr) return false; } return CDialogQuery::blocksPack(pack); } -void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const +void CTeleportDialogQuery::notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const { - // do not change to dynamic_ptr_cast - SIGSEGV! - auto obj = dynamic_cast(objectVisit.visitedObject); + auto obj = dynamic_cast(visitedObject); if(obj) - obj->teleportDialogAnswered(objectVisit.visitingHero, *answer, td.exits); + obj->teleportDialogAnswered(visitingHero, *answer, td.exits); else logGlobal->error("Invalid instance in teleport query"); } @@ -254,9 +221,9 @@ void CHeroLevelUpDialogQuery::onRemoval(PlayerColor color) gh->levelUpHero(hero, hlu.skills[*answer]); } -void CHeroLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const +void CHeroLevelUpDialogQuery::notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const { - objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero); + visitedObject->heroLevelUpDone(visitingHero); } CCommanderLevelUpDialogQuery::CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp & Clu, const CGHeroInstance * Hero): @@ -273,9 +240,9 @@ void CCommanderLevelUpDialogQuery::onRemoval(PlayerColor color) gh->levelUpCommander(hero->commander, clu.skills[*answer]); } -void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const +void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const { - objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero); + visitedObject->heroLevelUpDone(visitingHero); } CHeroMovementQuery::CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory): diff --git a/server/queries/MapQueries.h b/server/queries/MapQueries.h index 7a6cabf3c..41b06d3b7 100644 --- a/server/queries/MapQueries.h +++ b/server/queries/MapQueries.h @@ -15,12 +15,9 @@ VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; class CGObjectInstance; -class int3; +class IObjectInterface; VCMI_LIB_NAMESPACE_END - -class TurnTimerHandler; - //Created when player starts turn or when player puts game on [ause //Removed when player accepts a turn or continur play class TimerPauseQuery : public CQuery @@ -34,23 +31,6 @@ public: bool endsByPlayerAnswer() const override; }; -//Created when hero visits object. -//Removed when query above is resolved (or immediately after visit if no queries were created) -class CObjectVisitQuery : public CQuery -{ -public: - const CGObjectInstance *visitedObject; - const CGHeroInstance *visitingHero; - int3 tile; //may be different than hero pos -> eg. visit via teleport - bool removeObjectAfterVisit; - - CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile); - - bool blocksPack(const CPack *pack) const override; - void onRemoval(PlayerColor color) override; - void onExposure(QueryPtr topQuery) override; -}; - //Created when hero attempts move and something happens //(not necessarily position change, could be just an object interaction). class CHeroMovementQuery : public CQuery @@ -73,7 +53,7 @@ public: std::array exchangingArmies; CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance *up, const CArmedInstance *down); - void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; bool blocksPack(const CPack *pack) const override; }; @@ -81,11 +61,12 @@ public: class CBlockingDialogQuery : public CDialogQuery { public: + const IObjectInterface * caller; BlockingDialog bd; //copy of pack... debug purposes - CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog &bd); + CBlockingDialogQuery(CGameHandler * owner, const IObjectInterface * caller, const BlockingDialog &bd); - void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; }; class OpenWindowQuery : public CDialogQuery @@ -105,7 +86,7 @@ public: CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog &td); - void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; }; class CHeroLevelUpDialogQuery : public CDialogQuery @@ -114,7 +95,7 @@ public: CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp &Hlu, const CGHeroInstance * Hero); void onRemoval(PlayerColor color) override; - void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; HeroLevelUp hlu; const CGHeroInstance * hero; @@ -126,7 +107,7 @@ public: CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp &Clu, const CGHeroInstance * Hero); void onRemoval(PlayerColor color) override; - void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; CommanderLevelUp clu; const CGHeroInstance * hero; diff --git a/server/queries/VisitQueries.cpp b/server/queries/VisitQueries.cpp new file mode 100644 index 000000000..38beccbe6 --- /dev/null +++ b/server/queries/VisitQueries.cpp @@ -0,0 +1,66 @@ +/* + * VisitQueries.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 "VisitQueries.h" + +#include "../../lib/mapObjects/CGHeroInstance.h" +#include "../CGameHandler.h" +#include "QueriesProcessor.h" + +VisitQuery::VisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero) + : CQuery(owner) + , visitedObject(Obj) + , visitingHero(Hero) +{ + addPlayer(Hero->tempOwner); +} + +bool VisitQuery::blocksPack(const CPack * pack) const +{ + //During the visit itself ALL actions are blocked. + //(However, the visit may trigger a query above that'll pass some.) + return true; +} + +void VisitQuery::onExposure(QueryPtr topQuery) +{ + //Object may have been removed and deleted. + if(gh->isValidObject(visitedObject)) + topQuery->notifyObjectAboutRemoval(visitedObject, visitingHero); + + owner->popIfTop(*this); +} + +MapObjectVisitQuery::MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero) + : VisitQuery(owner, Obj, Hero) + , removeObjectAfterVisit(false) +{ +} + +void MapObjectVisitQuery::onRemoval(PlayerColor color) +{ + gh->objectVisitEnded(visitingHero, players.front()); + + //TODO or should it be destructor? + //Can object visit affect 2 players and what would be desired behavior? + if(removeObjectAfterVisit) + gh->removeObject(visitedObject, color); +} + +TownBuildingVisitQuery::TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, BuildingID buildingToVisit) + : VisitQuery(owner, Obj, Hero) + , visitedBuilding(buildingToVisit) +{ +} + +void TownBuildingVisitQuery::onRemoval(PlayerColor color) +{ + +} diff --git a/server/queries/VisitQueries.h b/server/queries/VisitQueries.h new file mode 100644 index 000000000..0d7ce5cca --- /dev/null +++ b/server/queries/VisitQueries.h @@ -0,0 +1,47 @@ +/* + * VisitQueries.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 "CQuery.h" + +//Created when hero visits object. +//Removed when query above is resolved (or immediately after visit if no queries were created) +class VisitQuery : public CQuery +{ +protected: + VisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero); + +public: + const CGObjectInstance *visitedObject; + const CGHeroInstance *visitingHero; + + bool blocksPack(const CPack *pack) const final; + void onExposure(QueryPtr topQuery) final; +}; + +class MapObjectVisitQuery final : public VisitQuery +{ +public: + bool removeObjectAfterVisit; + + MapObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero); + + void onRemoval(PlayerColor color) final; +}; + +class TownBuildingVisitQuery final : public VisitQuery +{ +public: + BuildingID visitedBuilding; + + TownBuildingVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, BuildingID buildingToVisit); + + void onRemoval(PlayerColor color) final; +}; diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 9c6ebe7d4..c1b094366 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -50,7 +50,7 @@ public: void giveExperience(const CGHeroInstance * hero, TExpType val) override {} void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs=false) override {} void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) override {} - void showBlockingDialog(BlockingDialog *iw) override {} + void showBlockingDialog(const IObjectInterface * caller, BlockingDialog *iw) override {} void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {} //cb will be called when player closes garrison window void showTeleportDialog(TeleportDialog *iw) override {} void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) override {};