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/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..9002b44a3 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -180,6 +180,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/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/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 4ce58097d..a9cbd36f8 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/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 58fe4d444..eab97a9db 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1181,7 +1181,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 +2151,39 @@ 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) + { + // FIXME: query might produce unintended side effects, double check + auto visitQuery = std::make_shared(this, t, hero, t->visitablePos()); + queries->addQuery(visitQuery); + building->onHeroVisit(hero); + queries->popIfTop(visitQuery); + return true; + } } return true; diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 889339e82..e2ceecd85 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -214,7 +214,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); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index d1ebbf895..d3938deeb 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -139,12 +139,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;