1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +02:00

Implemented explicitly visitable town buildings, e.g. hota mana vortex

Added flag `manualHeroVisit` flag to town building. If this flag is set,
then building will only be activated on click and will not give its
effect on hero recrutiment, hero visit, or new day.

This allows implementing changes to Mana Vortex from HotA
This commit is contained in:
Ivan Savenko 2024-09-03 16:31:07 +00:00
parent ee64928454
commit 327ff01471
17 changed files with 104 additions and 57 deletions

View File

@ -217,12 +217,12 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
return true; return true;
} }
bool CCallback::triggerTownSpecialBuildingAction(const CGTownInstance *town, BuildingSubID::EBuildingSubID subBuildingID) bool CCallback::visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)
{ {
if(town->tempOwner!=player) if(town->tempOwner!=player)
return false; return false;
TriggerTownSpecialBuildingAction pack(town->id, subBuildingID); VisitTownBuilding pack(town->id, buildingID);
sendRequest(&pack); sendRequest(&pack);
return true; return true;
} }

View File

@ -75,7 +75,7 @@ public:
//town //town
virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE)=0; 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 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 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 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; virtual void swapGarrisonHero(const CGTownInstance *town)=0;
@ -182,7 +182,7 @@ public:
void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override; void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;
void eraseArtifactByClient(const ArtifactLocation & al) override; void eraseArtifactByClient(const ArtifactLocation & al) override;
bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) 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; 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 dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;

View File

@ -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) if (buildingToTest >= BuildingID::DWELL_FIRST)
{ {
enterDwelling((BuildingID::getLevelFromDwelling(buildingToTest))); enterDwelling((BuildingID::getLevelFromDwelling(buildingToTest)));
@ -820,7 +826,7 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
return true; return true;
case BuildingSubID::BANK: case BuildingSubID::BANK:
enterBank(); enterBank(buildingTarget);
return true; return true;
} }
} }
@ -837,6 +843,11 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
return false; return false;
} }
void CCastleBuildings::enterRewardable(BuildingID building)
{
LOCPLINT->cb->visitTownBuilding(town, building);
}
void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactID) void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactID)
{ {
const CGHeroInstance *hero = town->visitingHero; const CGHeroInstance *hero = town->visitingHero;
@ -1053,7 +1064,7 @@ void CCastleBuildings::enterAnyThievesGuild()
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern")); LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
} }
void CCastleBuildings::enterBank() void CCastleBuildings::enterBank(BuildingID building)
{ {
std::vector<std::shared_ptr<CComponent>> components; std::vector<std::shared_ptr<CComponent>> components;
if(town->bonusValue.second > 0) if(town->bonusValue.second > 0)
@ -1064,7 +1075,7 @@ void CCastleBuildings::enterBank()
else{ else{
components.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, GameResID(GameResID::GOLD), 2500)); components.push_back(std::make_shared<CComponent>(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);
} }
} }

View File

@ -167,10 +167,11 @@ public:
void enterDwelling(int level); void enterDwelling(int level);
void enterTownHall(); void enterTownHall();
void enterRewardable(BuildingID building);
void enterMagesGuild(); void enterMagesGuild();
void enterAnyMarket(); void enterAnyMarket();
void enterAnyThievesGuild(); void enterAnyThievesGuild();
void enterBank(); void enterBank(BuildingID building);
void enterToTheQuickRecruitmentWindow(); void enterToTheQuickRecruitmentWindow();
bool buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget); bool buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget);

View File

@ -180,6 +180,7 @@
"visitors" : true "visitors" : true
}, },
"visitMode" : "hero", // Should be 'once' to match (somewhat buggy) H3 logic "visitMode" : "hero", // Should be 'once' to match (somewhat buggy) H3 logic
"visitMode" : "once",
"rewards" : [ "rewards" : [
{ {
"limiter" : { "limiter" : {

View File

@ -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", "description" : "If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building",
"type" : "boolean" "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" : { "configuration" : {
"description" : "Optional, configuration of building that can be activated by visiting hero", "description" : "Optional, configuration of building that can be activated by visiting hero",
"$ref" : "rewardable.json" "$ref" : "rewardable.json"
}, },
"firtufications" : { "fortifications" : {
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,
"description" : "Fortifications provided by this buildings, if any", "description" : "Fortifications provided by this buildings, if any",

View File

@ -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: ##### Order of Fire from Inferno:
```jsonc ```jsonc
"special4": { "special4": {
"requires" : [ "mageGuild1" ], "requires" : [ "mageGuild1" ],
"name" : "Order of Fire", "name" : "Order of Fire",
"description" : "Increases spellpower of visiting hero", "description" : "Increases spellpower of visiting hero",
"cost" : { "cost" : {
"mercury" : 5, "mercury" : 5,
"gold" : 1000 "gold" : 1000
}, },
"configuration" : { "configuration" : {
"visitMode" : "hero", "visitMode" : "hero",
"rewards" : [ "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", "message" : "@core.genrltxt.582",
"primary" : { "spellpower" : 1 } "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 ##### Mana Vortex from Dungeon
```jsonc ```jsonc
"special2": { "special2": {
"requires" : [ "mageGuild1" ], "requires" : [ "mageGuild1" ],
"name" : "Mana Vortex", "name" : "Mana Vortex",
"description" : "Doubles mana points of the first visiting hero each week", "description" : "Doubles mana points of the first visiting hero each week",
"cost" : { "cost" : {
"gold" : 5000 "gold" : 5000
}, },
"configuration" : { "configuration" : {
"resetParameters" : { "resetParameters" : {
"period" : 7, "period" : 7,
"visitors" : true "visitors" : true
}, },
"visitMode" : "once", "visitMode" : "once",
"rewards" : [ "rewards" : [
{ {
"limiter" : { "limiter" : {
"noneOf" : [ { "manaPercentage" : 200 } ] "noneOf" : [ { "manaPercentage" : 200 } ]
}, },
"message" : "As you near the mana vortex your body is filled with new energy. You have doubled your normal spell points.", "message" : "As you near the mana vortex your body is filled with new energy. You have doubled your normal spell points.",
"manaPercentage" : 200 "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 #### Resource Silo with custom production
```jsonc ```jsonc
"resourceSilo": { "resourceSilo": {
"name" : "Wood Resource Silo", "name" : "Wood Resource Silo",
"description" : "Produces 2 wood every day", "description" : "Produces 2 wood every day",
"cost" : { "cost" : {
"wood" : 10, "wood" : 10,
"gold" : 5000 "gold" : 5000
}, },
"produce" : { "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 // Buildings which bonuses should be overridden with bonuses of the current building
"overrides" : [ "anotherBuilding ] "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 ] "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. // If the building is a market, it requires market mode.
"marketModes" : [ "resource-resource", "resource-player" ], "marketModes" : [ "resource-resource", "resource-player" ],
@ -209,18 +213,18 @@ Building requirements can be described using logical expressions:
```jsonc ```jsonc
"requires" : "requires" :
[ [
"allOf", // Normal H3 "build all" mode "allOf", // Normal H3 "build all" mode
[ "mageGuild1" ], [ "mageGuild1" ],
[ [
"noneOf", // available only when none of these building are built "noneOf", // available only when none of these building are built
[ "dwelling5A" ], [ "dwelling5A" ],
[ "dwelling5AUpgrade" ] [ "dwelling5AUpgrade" ]
], ],
[ [
"anyOf", // any non-zero number of these buildings must be built "anyOf", // any non-zero number of these buildings must be built
[ "tavern" ], [ "tavern" ],
[ "blacksmith" ] [ "blacksmith" ]
] ]
] ]
``` ```
### List of unique town buildings ### List of unique town buildings

View File

@ -44,6 +44,7 @@ public:
BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty 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 BuildingSubID::EBuildingSubID subId; /// subtype for special buildings, -1 = the building is not special
bool upgradeReplacesBonuses = false; bool upgradeReplacesBonuses = false;
bool manualHeroVisit = false;
BonusList buildingBonuses; BonusList buildingBonuses;
Rewardable::Info rewardableObjectInfo; ///configurable rewards for special buildings Rewardable::Info rewardableObjectInfo; ///configurable rewards for special buildings

View File

@ -299,6 +299,9 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
ret->resources = TResources(source["cost"]); ret->resources = TResources(source["cost"]);
ret->produce = TResources(source["produce"]); ret->produce = TResources(source["produce"]);
ret->manualHeroVisit = source["manualHeroVisit"].Bool();
ret->upgradeReplacesBonuses = source["upgradeReplacesBonuses"].Bool();
const JsonNode & fortifications = source["fortifications"]; const JsonNode & fortifications = source["fortifications"];
if (!fortifications.isNull()) if (!fortifications.isNull())
{ {

View File

@ -126,7 +126,7 @@ public:
virtual void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) {} virtual void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) {}
virtual void visitDisbandCreature(DisbandCreature & pack) {} virtual void visitDisbandCreature(DisbandCreature & pack) {}
virtual void visitBuildStructure(BuildStructure & pack) {} virtual void visitBuildStructure(BuildStructure & pack) {}
virtual void visitTriggerTownSpecialBuildingAction(TriggerTownSpecialBuildingAction & pack) {} virtual void visitVisitTownBuilding(VisitTownBuilding & pack) {}
virtual void visitRazeStructure(RazeStructure & pack) {} virtual void visitRazeStructure(RazeStructure & pack) {}
virtual void visitRecruitCreatures(RecruitCreatures & pack) {} virtual void visitRecruitCreatures(RecruitCreatures & pack) {}
virtual void visitUpgradeCreature(UpgradeCreature & pack) {} virtual void visitUpgradeCreature(UpgradeCreature & pack) {}

View File

@ -582,9 +582,9 @@ void BuildStructure::visitTyped(ICPackVisitor & visitor)
visitor.visitBuildStructure(*this); visitor.visitBuildStructure(*this);
} }
void TriggerTownSpecialBuildingAction::visitTyped(ICPackVisitor & visitor) void VisitTownBuilding::visitTyped(ICPackVisitor & visitor)
{ {
visitor.visitTriggerTownSpecialBuildingAction(*this); visitor.visitVisitTownBuilding(*this);
} }
void RazeStructure::visitTyped(ICPackVisitor & visitor) void RazeStructure::visitTyped(ICPackVisitor & visitor)

View File

@ -280,16 +280,16 @@ struct DLL_LINKAGE BuildStructure : public CPackForServer
} }
}; };
struct DLL_LINKAGE TriggerTownSpecialBuildingAction : public CPackForServer struct DLL_LINKAGE VisitTownBuilding : public CPackForServer
{ {
TriggerTownSpecialBuildingAction() = default; VisitTownBuilding() = default;
TriggerTownSpecialBuildingAction(const ObjectInstanceID & TID, const BuildingSubID::EBuildingSubID SID) VisitTownBuilding(const ObjectInstanceID & TID, const BuildingID BID)
: tid(TID) : tid(TID)
, sid(SID) , bid(BID)
{ {
} }
ObjectInstanceID tid; ObjectInstanceID tid;
BuildingSubID::EBuildingSubID sid; BuildingID bid;
void visitTyped(ICPackVisitor & visitor) override; void visitTyped(ICPackVisitor & visitor) override;
@ -297,7 +297,7 @@ struct DLL_LINKAGE TriggerTownSpecialBuildingAction : public CPackForServer
{ {
h & static_cast<CPackForServer &>(*this); h & static_cast<CPackForServer &>(*this);
h & tid; h & tid;
h & sid; h & bid;
} }
}; };

View File

@ -239,7 +239,7 @@ void registerTypes(Serializer &s)
s.template registerType<ArrangeStacks>(183); s.template registerType<ArrangeStacks>(183);
s.template registerType<DisbandCreature>(184); s.template registerType<DisbandCreature>(184);
s.template registerType<BuildStructure>(185); s.template registerType<BuildStructure>(185);
s.template registerType<TriggerTownSpecialBuildingAction>(186); s.template registerType<VisitTownBuilding>(186);
s.template registerType<RecruitCreatures>(187); s.template registerType<RecruitCreatures>(187);
s.template registerType<UpgradeCreature>(188); s.template registerType<UpgradeCreature>(188);
s.template registerType<GarrisonHeroSwap>(189); s.template registerType<GarrisonHeroSwap>(189);

View File

@ -1181,7 +1181,10 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
void CGameHandler::visitCastleObjects(const CGTownInstance * t, const CGHeroInstance * h) void CGameHandler::visitCastleObjects(const CGTownInstance * t, const CGHeroInstance * h)
{ {
for (auto & building : t->rewardableBuildings) 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) void CGameHandler::stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)
@ -2148,20 +2151,39 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
return true; return true;
} }
bool CGameHandler::triggerTownSpecialBuildingAction(ObjectInstanceID tid, BuildingSubID::EBuildingSubID sid) bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid)
{ {
const CGTownInstance * t = getTown(tid); const CGTownInstance * t = getTown(tid);
if(t->town->getBuildingType(sid) == BuildingID::NONE) if(!t->hasBuilt(bid))
return false; return false;
if(sid == BuildingSubID::EBuildingSubID::BANK) auto subID = t->town->buildings.at(bid)->subId;
if(subID == BuildingSubID::EBuildingSubID::BANK)
{ {
TResources res; TResources res;
res[EGameResID::GOLD] = 2500; res[EGameResID::GOLD] = 2500;
giveResources(t->getOwner(), res); giveResources(t->getOwner(), res);
setObjPropertyValue(t->id, ObjProperty::BONUS_VALUE_SECOND, 2500); 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<CObjectVisitQuery>(this, t, hero, t->visitablePos());
queries->addQuery(visitQuery);
building->onHeroVisit(hero);
queries->popIfTop(visitQuery);
return true;
}
} }
return true; return true;

View File

@ -214,7 +214,7 @@ public:
bool upgradeCreature( ObjectInstanceID objid, SlotID pos, CreatureID upgID ); bool upgradeCreature( ObjectInstanceID objid, SlotID pos, CreatureID upgID );
bool recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst, CreatureID crid, ui32 cram, si32 level, PlayerColor player); 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 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 razeStructure(ObjectInstanceID tid, BuildingID bid);
bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool disbandCreature( ObjectInstanceID id, SlotID pos );
bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player);

View File

@ -139,12 +139,12 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack)
result = gh.buildStructure(pack.tid, pack.bid); result = gh.buildStructure(pack.tid, pack.bid);
} }
void ApplyGhNetPackVisitor::visitTriggerTownSpecialBuildingAction(TriggerTownSpecialBuildingAction & pack) void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack)
{ {
gh.throwIfWrongOwner(&pack, pack.tid); gh.throwIfWrongOwner(&pack, pack.tid);
gh.throwIfPlayerNotActive(&pack); gh.throwIfPlayerNotActive(&pack);
result = gh.triggerTownSpecialBuildingAction(pack.tid, pack.sid); result = gh.visitTownBuilding(pack.tid, pack.bid);
} }
void ApplyGhNetPackVisitor::visitRecruitCreatures(RecruitCreatures & pack) void ApplyGhNetPackVisitor::visitRecruitCreatures(RecruitCreatures & pack)

View File

@ -41,7 +41,7 @@ public:
void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) override; void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) override;
void visitDisbandCreature(DisbandCreature & pack) override; void visitDisbandCreature(DisbandCreature & pack) override;
void visitBuildStructure(BuildStructure & pack) override; void visitBuildStructure(BuildStructure & pack) override;
void visitTriggerTownSpecialBuildingAction(TriggerTownSpecialBuildingAction & pack) override; void visitVisitTownBuilding(VisitTownBuilding & pack) override;
void visitRecruitCreatures(RecruitCreatures & pack) override; void visitRecruitCreatures(RecruitCreatures & pack) override;
void visitUpgradeCreature(UpgradeCreature & pack) override; void visitUpgradeCreature(UpgradeCreature & pack) override;
void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) override; void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) override;