diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index e66722d09..ba91a62d6 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1491,7 +1491,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade //TODO trade only as much as needed if (toGive) //don't try to sell 0 resources { - cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive); + cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive); acquiredResources = static_cast(toGet * (it->resVal / toGive)); logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName()); } diff --git a/AI/VCAI/BuildingManager.cpp b/AI/VCAI/BuildingManager.cpp index 365b83b23..202661c33 100644 --- a/AI/VCAI/BuildingManager.cpp +++ b/AI/VCAI/BuildingManager.cpp @@ -196,7 +196,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t) return true; //workaround for mantis #2696 - build capitol with separate algorithm if it is available - if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL) + if(t->hasBuilt(BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL) { if(tryBuildNextStructure(t, capitolAndRequirements)) return true; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index e00a119c9..4372fb951 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2130,7 +2130,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade //TODO trade only as much as needed if (toGive) //don't try to sell 0 resources { - cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive); + cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive); acquiredResources = static_cast(toGet * (it->resVal / toGive)); logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName()); } diff --git a/CCallback.cpp b/CCallback.cpp index 1be655bc5..d8a8fac99 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -266,15 +266,15 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid) sendRequest(&pack); } -void CCallback::trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero) +void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero) { - trade(market, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero); + trade(marketId, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero); } -void CCallback::trade(const IMarket * market, EMarketMode mode, const std::vector & id1, const std::vector & id2, const std::vector & val1, const CGHeroInstance * hero) +void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector & id1, const std::vector & id2, const std::vector & val1, const CGHeroInstance * hero) { TradeOnMarketplace pack; - pack.marketId = dynamic_cast(market)->id; + pack.marketId = marketId; pack.heroId = hero ? hero->id : ObjectInstanceID(); pack.mode = mode; pack.r1 = id1; diff --git a/CCallback.h b/CCallback.h index 7da23128d..0f406dc2c 100644 --- a/CCallback.h +++ b/CCallback.h @@ -34,7 +34,6 @@ class IBattleEventsReceiver; class IGameEventsReceiver; struct ArtifactLocation; class BattleStateInfoForRetreat; -class IMarket; VCMI_LIB_NAMESPACE_END @@ -81,8 +80,8 @@ public: 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 trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce - virtual void trade(const IMarket * market, EMarketMode mode, const std::vector & id1, const std::vector & id2, const std::vector & val1, const CGHeroInstance * hero = nullptr)=0; + virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce + virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector & id1, const std::vector & id2, const std::vector & val1, const CGHeroInstance * hero)=0; virtual int selectionMade(int selection, QueryID queryID) =0; virtual int sendQueryReply(std::optional reply, QueryID queryID) =0; @@ -190,8 +189,8 @@ public: void endTurn() override; void swapGarrisonHero(const CGTownInstance *town) override; void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; - void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; - void trade(const IMarket * market, EMarketMode mode, const std::vector & id1, const std::vector & id2, const std::vector & val1, const CGHeroInstance * hero = nullptr) override; + void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; + void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector & id1, const std::vector & id2, const std::vector & val1, const CGHeroInstance * hero = nullptr) override; void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override; void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override; void save(const std::string &fname) override; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index bc89cc5b5..36c41a338 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1633,14 +1633,14 @@ void CPlayerInterface::battleNewRoundFirst(const BattleID & battleID) battleInt->newRoundFirst(); } -void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) +void CPlayerInterface::showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) { EVENT_HANDLER_CALLED_BY_CLIENT; auto onWindowClosed = [this, queryID](){ cb->selectionMade(0, queryID); }; - if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && dynamic_cast(market) == nullptr) + if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && market->getArtifactsStorage() == nullptr) { // compatibility check, safe to remove for 1.6 // 1.4 saves loaded in 1.5 will not be able to visit Altar of Sacrifice due to Altar now requiring different map object class @@ -1655,8 +1655,17 @@ void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInsta GH.windows().createAndPushWindow(market, visitor, onWindowClosed, EMarketMode::CREATURE_EXP); else if(market->allowsTrade(EMarketMode::CREATURE_UNDEAD)) GH.windows().createAndPushWindow(market, visitor, onWindowClosed); - else if(!market->availableModes().empty()) - GH.windows().createAndPushWindow(market, visitor, onWindowClosed, market->availableModes().front()); + else if (!market->availableModes().empty()) + for(auto mode = EMarketMode::RESOURCE_RESOURCE; mode != EMarketMode::MARKET_AFTER_LAST_PLACEHOLDER; mode = vstd::next(mode, 1)) + { + if(vstd::contains(market->availableModes(), mode)) + { + GH.windows().createAndPushWindow(market, visitor, onWindowClosed, mode); + break; + } + } + else + onWindowClosed(); } void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 0ae02576c..a0f57d271 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -120,7 +120,7 @@ protected: // Call-ins from server, should not be called directly, but only via void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector & objects) override; - void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override; + void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) override; void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override; void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override; void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 05f6fe038..460517e4d 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -999,7 +999,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack) case EOpenWindowMode::UNIVERSITY_WINDOW: { //displays University window (when hero enters University on adventure map) - const auto * market = dynamic_cast(cl.getObj(ObjectInstanceID(pack.object))); + const auto * market = cl.getMarket(ObjectInstanceID(pack.object)); const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor)); callInterfaceIfPresent(cl, hero->tempOwner, &IGameEventsReceiver::showUniversityWindow, market, hero, pack.queryID); } @@ -1009,7 +1009,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack) //displays Thieves' Guild window (when hero enters Den of Thieves) const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object)); const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor)); - const auto *market = dynamic_cast(obj); + const auto market = cl.getMarket(pack.object); callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID); } break; diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index e77b9a9b4..3fc06b8e9 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -464,7 +464,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town) std::vector towns = LOCPLINT->cb->getTownsInfo(true); for(auto & town : towns) { - if(town->id == townId && town->builtBuildings.count(BuildingID::TAVERN)) + if(town->id == townId && town->hasBuilt(BuildingID::TAVERN)) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); } }, [town]{ @@ -476,7 +476,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town) std::vector towns = LOCPLINT->cb->getTownsInfo(true); for(auto & town : towns) { - if(town->builtBuildings.count(BuildingID::MARKETPLACE)) + if(town->hasBuilt(BuildingID::MARKETPLACE)) { GH.windows().createAndPushWindow(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE); return; diff --git a/client/widgets/markets/CAltarArtifacts.cpp b/client/widgets/markets/CAltarArtifacts.cpp index 0c61c1073..78c4ff87a 100644 --- a/client/widgets/markets/CAltarArtifacts.cpp +++ b/client/widgets/markets/CAltarArtifacts.cpp @@ -24,16 +24,15 @@ #include "../../../lib/networkPacks/ArtifactLocation.h" #include "../../../lib/texts/CGeneralTextHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h" -#include "../../../lib/mapObjects/CGMarket.h" +#include "../../../lib/mapObjects/IMarket.h" CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero) : CMarketBase(market, hero) { OBJECT_CONSTRUCTION; - assert(dynamic_cast(market)); - auto altarObj = dynamic_cast(market); - altarArtifacts = altarObj; + assert(market->getArtifactsStorage()); + altarArtifactsStorage = market->getArtifactsStorage(); deal = std::make_shared(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"), CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); }, EShortcut::MARKET_DEAL); @@ -51,7 +50,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * // Hero's artifacts heroArts = std::make_shared(Point(-365, -11)); heroArts->setHero(hero); - heroArts->altarId = altarObj->id; + heroArts->altarId = market->getObjInstanceID(); // Altar offerTradePanel = std::make_shared([this](const std::shared_ptr & altarSlot) @@ -104,7 +103,7 @@ void CAltarArtifacts::makeDeal() { positions.push_back(artInst->getId()); } - LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_EXP, positions, std::vector(), std::vector(), hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_EXP, positions, std::vector(), std::vector(), hero); deselect(); } @@ -125,7 +124,7 @@ std::shared_ptr CAltarArtifacts::getAOHset() const void CAltarArtifacts::updateAltarSlots() { - assert(altarArtifacts->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS); + assert(altarArtifactsStorage->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS); assert(tradeSlotsMap.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS); auto tradeSlotsMapNewArts = tradeSlotsMap; @@ -146,12 +145,12 @@ void CAltarArtifacts::updateAltarSlots() for(auto & tradeSlot : tradeSlotsMapNewArts) { assert(tradeSlot.first->id == -1); - assert(altarArtifacts->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST); + assert(altarArtifactsStorage->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST); tradeSlot.first->setID(tradeSlot.second->getTypeId().num); tradeSlot.first->subtitle->setText(std::to_string(calcExpCost(tradeSlot.second->getTypeId()))); } - auto newArtsFromBulkMove = altarArtifacts->artifactsInBackpack; + auto newArtsFromBulkMove = altarArtifactsStorage->artifactsInBackpack; for(const auto & [altarSlot, art] : tradeSlotsMap) { newArtsFromBulkMove.erase(std::remove_if(newArtsFromBulkMove.begin(), newArtsFromBulkMove.end(), [artForRemove = art](auto & slotInfo) @@ -179,7 +178,7 @@ void CAltarArtifacts::putBackArtifacts() { // TODO: If the backpack capacity limit is enabled, artifacts may remain on the altar. // Perhaps should be erased in CGameHandler::objectVisitEnded if id of visited object will be available - if(!altarArtifacts->artifactsInBackpack.empty()) + if(!altarArtifactsStorage->artifactsInBackpack.empty()) LOCPLINT->cb->bulkMoveArtifacts(heroArts->altarId, heroArts->getHero()->id, false, true, true); } @@ -200,7 +199,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr & if(const auto pickedArtInst = heroArts->getPickedArtifact()) { - if(pickedArtInst->canBePutAt(altarArtifacts)) + if(pickedArtInst->canBePutAt(altarArtifactsStorage.get())) { if(pickedArtInst->artType->isTradable()) { @@ -221,7 +220,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr & else if(altarSlot->id != -1) { assert(tradeSlotsMap.at(altarSlot)); - const auto slot = altarArtifacts->getArtPos(tradeSlotsMap.at(altarSlot)); + const auto slot = altarArtifactsStorage->getArtPos(tradeSlotsMap.at(altarSlot)); assert(slot != ArtifactPosition::PRE_FIRST); LOCPLINT->cb->swapArtifacts(ArtifactLocation(heroArts->altarId, slot), ArtifactLocation(hero->id, GH.isKeyboardCtrlDown() ? ArtifactPosition::FIRST_AVAILABLE : ArtifactPosition::TRANSITION_POS)); diff --git a/client/widgets/markets/CAltarArtifacts.h b/client/widgets/markets/CAltarArtifacts.h index 6f714f70d..5487729b5 100644 --- a/client/widgets/markets/CAltarArtifacts.h +++ b/client/widgets/markets/CAltarArtifacts.h @@ -26,7 +26,7 @@ public: void putBackArtifacts(); private: - const CArtifactSet * altarArtifacts; + std::shared_ptr altarArtifactsStorage; std::shared_ptr sacrificeBackpackButton; std::shared_ptr heroArts; std::map, const CArtifactInstance*> tradeSlotsMap; diff --git a/client/widgets/markets/CAltarCreatures.cpp b/client/widgets/markets/CAltarCreatures.cpp index e168335d0..896e0644b 100644 --- a/client/widgets/markets/CAltarCreatures.cpp +++ b/client/widgets/markets/CAltarCreatures.cpp @@ -23,7 +23,7 @@ #include "../../../lib/texts/CGeneralTextHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h" -#include "../../../lib/mapObjects/CGMarket.h" +#include "../../../lib/mapObjects/IMarket.h" CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance * hero) : CMarketBase(market, hero) @@ -157,7 +157,7 @@ void CAltarCreatures::makeDeal() } } - LOCPLINT->cb->trade(market, EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero); for(int & units : unitsOnAltar) units = 0; diff --git a/client/widgets/markets/CArtifactsBuying.cpp b/client/widgets/markets/CArtifactsBuying.cpp index 576c4f55d..853bc848b 100644 --- a/client/widgets/markets/CArtifactsBuying.cpp +++ b/client/widgets/markets/CArtifactsBuying.cpp @@ -11,7 +11,6 @@ #include "StdInc.h" #include "CArtifactsBuying.h" -#include "../../gui/CGuiHandler.h" #include "../../gui/Shortcut.h" #include "../../widgets/Buttons.h" #include "../../widgets/TextControls.h" @@ -21,24 +20,16 @@ #include "../../../CCallback.h" -#include "../../../lib/entities/building/CBuilding.h" -#include "../../../lib/entities/faction/CTownHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h" -#include "../../../lib/mapObjects/CGMarket.h" -#include "../../../lib/mapObjects/CGTownInstance.h" +#include "../../../lib/mapObjects/IMarket.h" #include "../../../lib/texts/CGeneralTextHandler.h" -CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero) +CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title) : CMarketBase(market, hero) , CResourcesSelling([this](const std::shared_ptr & heroSlot){CArtifactsBuying::onSlotClickPressed(heroSlot, bidTradePanel);}) { OBJECT_CONSTRUCTION; - std::string title; - if(auto townMarket = dynamic_cast(market)) - title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated(); - else - title = CGI->generaltexth->allTexts[349]; labels.emplace_back(std::make_shared(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title)); deal = std::make_shared(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"), CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();}, EShortcut::MARKET_DEAL); @@ -77,7 +68,7 @@ void CArtifactsBuying::makeDeal() { if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero)) { - LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()), + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()), ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero); CMarketTraderText::makeDeal(); deselect(); diff --git a/client/widgets/markets/CArtifactsBuying.h b/client/widgets/markets/CArtifactsBuying.h index 8393e8c07..11e114387 100644 --- a/client/widgets/markets/CArtifactsBuying.h +++ b/client/widgets/markets/CArtifactsBuying.h @@ -14,7 +14,7 @@ class CArtifactsBuying : public CResourcesSelling, public CMarketTraderText { public: - CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero); + CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title); void deselect() override; void makeDeal() override; diff --git a/client/widgets/markets/CArtifactsSelling.cpp b/client/widgets/markets/CArtifactsSelling.cpp index a79708556..dbbe41278 100644 --- a/client/widgets/markets/CArtifactsSelling.cpp +++ b/client/widgets/markets/CArtifactsSelling.cpp @@ -22,14 +22,11 @@ #include "../../../CCallback.h" #include "../../../lib/CArtifactInstance.h" -#include "../../../lib/entities/building/CBuilding.h" -#include "../../../lib/entities/faction/CTownHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h" -#include "../../../lib/mapObjects/CGMarket.h" -#include "../../../lib/mapObjects/CGTownInstance.h" +#include "../../../lib/mapObjects/IMarket.h" #include "../../../lib/texts/CGeneralTextHandler.h" -CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero) +CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title) : CMarketBase(market, hero) , CResourcesBuying( [this](const std::shared_ptr & resSlot){CArtifactsSelling::onSlotClickPressed(resSlot, offerTradePanel);}, @@ -37,12 +34,6 @@ CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstanc { OBJECT_CONSTRUCTION; - std::string title; - if(const auto townMarket = dynamic_cast(market)) - title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated(); - else if(const auto mapMarket = dynamic_cast(market)) - title = mapMarket->title; - labels.emplace_back(std::make_shared(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title)); labels.push_back(std::make_shared(155, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated()))); deal = std::make_shared(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"), @@ -87,7 +78,8 @@ void CArtifactsSelling::makeDeal() { const auto art = hero->getArt(selectedHeroSlot); assert(art); - LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_RESOURCE, art->getId(), GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE, art->getId(), + GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero); CMarketTraderText::makeDeal(); } diff --git a/client/widgets/markets/CArtifactsSelling.h b/client/widgets/markets/CArtifactsSelling.h index 581209a30..74a67e1d8 100644 --- a/client/widgets/markets/CArtifactsSelling.h +++ b/client/widgets/markets/CArtifactsSelling.h @@ -15,7 +15,7 @@ class CArtifactsSelling : public CResourcesBuying, public CMarketTraderText { public: - CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero); + CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title); void deselect() override; void makeDeal() override; void updateShowcases() override; diff --git a/client/widgets/markets/CFreelancerGuild.cpp b/client/widgets/markets/CFreelancerGuild.cpp index 1a2ab31b7..19c138d1b 100644 --- a/client/widgets/markets/CFreelancerGuild.cpp +++ b/client/widgets/markets/CFreelancerGuild.cpp @@ -23,7 +23,7 @@ #include "../../../lib/texts/CGeneralTextHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h" -#include "../../../lib/mapObjects/CGMarket.h" +#include "../../../lib/mapObjects/IMarket.h" CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance * hero) : CMarketBase(market, hero) @@ -69,7 +69,7 @@ void CFreelancerGuild::makeDeal() { if(auto toTrade = offerSlider->getValue(); toTrade != 0) { - LOCPLINT->cb->trade(market, EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero); CMarketTraderText::makeDeal(); deselect(); } diff --git a/client/widgets/markets/CMarketResources.cpp b/client/widgets/markets/CMarketResources.cpp index be8e2a92a..4233d2f2a 100644 --- a/client/widgets/markets/CMarketResources.cpp +++ b/client/widgets/markets/CMarketResources.cpp @@ -22,7 +22,7 @@ #include "../../../CCallback.h" #include "../../../lib/texts/CGeneralTextHandler.h" -#include "../../../lib/mapObjects/CGMarket.h" +#include "../../../lib/mapObjects/IMarket.h" CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance * hero) : CMarketBase(market, hero) @@ -60,7 +60,7 @@ void CMarketResources::makeDeal() { if(auto toTrade = offerSlider->getValue(); toTrade != 0) { - LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()), + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()), GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero); CMarketTraderText::makeDeal(); deselect(); diff --git a/client/widgets/markets/CTransferResources.cpp b/client/widgets/markets/CTransferResources.cpp index 04e3cf545..f2871c869 100644 --- a/client/widgets/markets/CTransferResources.cpp +++ b/client/widgets/markets/CTransferResources.cpp @@ -22,6 +22,7 @@ #include "../../../CCallback.h" #include "../../../lib/texts/CGeneralTextHandler.h" +#include "../../../lib/mapObjects/IMarket.h" #include "../../../lib/texts/MetaString.h" CTransferResources::CTransferResources(const IMarket * market, const CGHeroInstance * hero) @@ -63,7 +64,7 @@ void CTransferResources::makeDeal() { if(auto toTrade = offerSlider->getValue(); toTrade != 0) { - LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()), + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()), PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero); CMarketTraderText::makeDeal(); deselect(); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 0bdd0cd95..9d3c7dae7 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -586,9 +586,9 @@ void CCastleBuildings::recreate() //Generate buildings list - auto buildingsCopy = town->builtBuildings;// a bit modified copy of built buildings + auto buildingsCopy = town->getBuildings();// a bit modified copy of built buildings - if(vstd::contains(town->builtBuildings, BuildingID::SHIPYARD)) + if(town->hasBuilt(BuildingID::SHIPYARD)) { auto bayPos = town->bestLocation(); if(!bayPos.valid()) @@ -996,7 +996,7 @@ void CCastleBuildings::enterMagesGuild() void CCastleBuildings::enterTownHall() { if(town->visitingHero && town->visitingHero->hasArt(ArtifactID::GRAIL) && - !vstd::contains(town->builtBuildings, BuildingID::GRAIL)) //hero has grail, but town does not have it + !town->hasBuilt(BuildingID::GRAIL)) //hero has grail, but town does not have it { if(!vstd::contains(town->forbiddenBuildings, BuildingID::GRAIL)) { @@ -1033,7 +1033,7 @@ void CCastleBuildings::enterAnyThievesGuild() std::vector towns = LOCPLINT->cb->getTownsInfo(true); for(auto & ownedTown : towns) { - if(ownedTown->builtBuildings.count(BuildingID::TAVERN)) + if(ownedTown->hasBuilt(BuildingID::TAVERN)) { LOCPLINT->showThievesGuildWindow(ownedTown); return; @@ -1059,7 +1059,7 @@ void CCastleBuildings::enterBank() void CCastleBuildings::enterAnyMarket() { - if(town->builtBuildings.count(BuildingID::MARKETPLACE)) + if(town->hasBuilt(BuildingID::MARKETPLACE)) { GH.windows().createAndPushWindow(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE); return; @@ -1068,7 +1068,7 @@ void CCastleBuildings::enterAnyMarket() std::vector towns = LOCPLINT->cb->getTownsInfo(true); for(auto & town : towns) { - if(town->builtBuildings.count(BuildingID::MARKETPLACE)) + if(town->hasBuilt(BuildingID::MARKETPLACE)) { GH.windows().createAndPushWindow(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE); return; @@ -1385,7 +1385,7 @@ void CCastleInterface::recreateIcons() fastMarket = std::make_shared(Rect(163, 410, 64, 42), [this]() { builds->enterAnyMarket(); }); fastTavern = std::make_shared(Rect(15, 387, 58, 64), [&]() { - if(town->builtBuildings.count(BuildingID::TAVERN)) + if(town->hasBuilt(BuildingID::TAVERN)) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); }, [this]{ if(!town->town->faction->getDescriptionTranslated().empty()) @@ -1563,7 +1563,7 @@ CHallInterface::CHallInterface(const CGTownInstance * Town): } const CBuilding * current = town->town->buildings.at(buildingID); - if(vstd::contains(town->builtBuildings, buildingID)) + if(town->hasBuilt(buildingID)) { building = current; } @@ -1776,7 +1776,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town): { BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1); - if(vstd::contains(town->builtBuildings, dwelling)) + if(town->hasBuilt(dwelling)) buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 1)); else buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 0)); @@ -1841,7 +1841,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance * buildingIcon = std::make_shared(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21); buildingName = std::make_shared(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152); - if(vstd::contains(town->builtBuildings, getMyBuilding()->bid)) + if(town->hasBuilt(getMyBuilding()->bid)) { ui32 available = town->creatures[level].first; std::string availableText = CGI->generaltexth->allTexts[217]+ std::to_string(available); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 42da761bb..23f48ca6d 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -822,7 +822,7 @@ CTownItem::CTownItem(const CGTownInstance * Town) fastTavern = std::make_shared(Rect(5, 6, 58, 64), [&]() { - if(town->builtBuildings.count(BuildingID::TAVERN)) + if(town->hasBuilt(BuildingID::TAVERN)) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); }, [&]{ if(!town->town->faction->getDescriptionTranslated().empty()) @@ -833,7 +833,7 @@ CTownItem::CTownItem(const CGTownInstance * Town) std::vector towns = LOCPLINT->cb->getTownsInfo(true); for(auto & town : towns) { - if(town->builtBuildings.count(BuildingID::MARKETPLACE)) + if(town->hasBuilt(BuildingID::MARKETPLACE)) { GH.windows().createAndPushWindow(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE); return; diff --git a/client/windows/CMarketWindow.cpp b/client/windows/CMarketWindow.cpp index 6a8e2e2ce..bf7661696 100644 --- a/client/windows/CMarketWindow.cpp +++ b/client/windows/CMarketWindow.cpp @@ -27,11 +27,14 @@ #include "../CGameInfo.h" #include "../CPlayerInterface.h" +#include "../../lib/entities/building/CBuilding.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGMarket.h" #include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../CCallback.h" + CMarketWindow::CMarketWindow(const IMarket * market, const CGHeroInstance * hero, const std::function & onWindowClosed, EMarketMode mode) : CWindowObject(PLAYER_COLORED) , windowClosedCallback(onWindowClosed) @@ -111,6 +114,11 @@ void CMarketWindow::createChangeModeButtons(EMarketMode currentMode, const IMark if(!market->allowsTrade(modeButton)) return false; + if(currentMode == EMarketMode::ARTIFACT_EXP && modeButton != EMarketMode::CREATURE_EXP) + return false; + if(currentMode == EMarketMode::CREATURE_EXP && modeButton != EMarketMode::ARTIFACT_EXP) + return false; + if(modeButton == EMarketMode::RESOURCE_RESOURCE || modeButton == EMarketMode::RESOURCE_PLAYER) { if(const auto town = dynamic_cast(market)) @@ -175,12 +183,28 @@ void CMarketWindow::initWidgetInternals(const EMarketMode mode, const std::pair< redraw(); } +std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const +{ + assert(LOCPLINT->cb->getMarket(marketId)); + assert(vstd::contains(LOCPLINT->cb->getMarket(marketId)->availableModes(), mode)); + + if(const auto town = LOCPLINT->cb->getTown(marketId)) + { + for(const auto & buildingId : town->getBuildings()) + { + if(const auto building = town->town->buildings.at(buildingId); vstd::contains(building->marketModes, mode)) + return building->getNameTranslated(); + } + } + return LOCPLINT->cb->getObj(marketId)->getObjectName(); +} + void CMarketWindow::createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero) { OBJECT_CONSTRUCTION; background = createBg(ImagePath::builtin("TPMRKABS.bmp"), PLAYER_COLORED); - marketWidget = std::make_shared(market, hero); + marketWidget = std::make_shared(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT)); initWidgetInternals(EMarketMode::RESOURCE_ARTIFACT, CGI->generaltexth->zelp[600]); } @@ -192,13 +216,13 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI // Create image that copies part of background containing slot MISC_1 into position of slot MISC_5 artSlotBack = std::make_shared(background->getSurface(), Rect(20, 187, 47, 47), 0, 0); artSlotBack->moveTo(pos.topLeft() + Point(18, 339)); - auto artsSellingMarket = std::make_shared(market, hero); + auto artsSellingMarket = std::make_shared(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE)); artSets.clear(); const auto heroArts = artsSellingMarket->getAOHset(); heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition){showArifactInfo(*heroArts, artPlace, cursorPosition);}; addSet(heroArts); marketWidget = artsSellingMarket; - initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]); + initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]); } void CMarketWindow::createMarketResources(const IMarket * market, const CGHeroInstance * hero) @@ -233,10 +257,10 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns OBJECT_CONSTRUCTION; background = createBg(ImagePath::builtin("ALTRART2.bmp"), PLAYER_COLORED); - auto altarArtifacts = std::make_shared(market, hero); - marketWidget = altarArtifacts; + auto altarArtifactsStorage = std::make_shared(market, hero); + marketWidget = altarArtifactsStorage; artSets.clear(); - const auto heroArts = altarArtifacts->getAOHset(); + const auto heroArts = altarArtifactsStorage->getAOHset(); heroArts->clickPressedCallback = [this, heroArts](const CArtPlace & artPlace, const Point & cursorPosition) { clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false); @@ -252,7 +276,7 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns addSet(heroArts); initWidgetInternals(EMarketMode::ARTIFACT_EXP, CGI->generaltexth->zelp[568]); updateExperience(); - quitButton->addCallback([altarArtifacts](){altarArtifacts->putBackArtifacts();}); + quitButton->addCallback([altarArtifactsStorage](){altarArtifactsStorage->putBackArtifacts();}); } void CMarketWindow::createAltarCreatures(const IMarket * market, const CGHeroInstance * hero) diff --git a/client/windows/CMarketWindow.h b/client/windows/CMarketWindow.h index 1f7d2179c..27b1026bc 100644 --- a/client/windows/CMarketWindow.h +++ b/client/windows/CMarketWindow.h @@ -27,6 +27,7 @@ public: private: void createChangeModeButtons(EMarketMode currentMode, const IMarket * market, const CGHeroInstance * hero); void initWidgetInternals(const EMarketMode mode, const std::pair & quitButtonHelpContainer); + std::string getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const; void createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero); void createArtifactsSelling(const IMarket * market, const CGHeroInstance * hero); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index b5994d914..bc11501e7 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -819,7 +819,7 @@ void CTransformerWindow::makeDeal() for(auto & elem : items) { if(!elem->left) - LOCPLINT->cb->trade(market, EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero); } } @@ -1005,7 +1005,7 @@ void CUniversityWindow::updateSecondarySkills() void CUniversityWindow::makeDeal(SecondarySkill skill) { - LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero); } CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, SecondarySkill SKILL, bool available) diff --git a/config/factions/castle.json b/config/factions/castle.json index 66336c156..690b78fd9 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -166,7 +166,7 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "ore": 1, "wood": 1 } }, "blacksmith": { }, diff --git a/config/factions/conflux.json b/config/factions/conflux.json index d760cf80f..6a7d14713 100644 --- a/config/factions/conflux.json +++ b/config/factions/conflux.json @@ -171,11 +171,11 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "mercury": 1 } }, "blacksmith": { }, - "special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] }, + "special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] }, "horde1": { "id" : 18, "upgrades" : "dwellingLvl1" }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, "ship": { "id" : 20, "upgrades" : "shipyard" }, diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 9d5c2dacd..ccec28b38 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -167,11 +167,11 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "sulfur": 1 } }, "blacksmith": { }, - "special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] }, + "special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] }, "horde1": { "id" : 18, "upgrades" : "dwellingLvl1" }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, "special2": { diff --git a/config/factions/fortress.json b/config/factions/fortress.json index d66054168..9cc7e41f4 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -165,7 +165,7 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "wood": 1, "ore": 1 } }, "blacksmith": { }, diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 3764c938e..9b657b16d 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -167,7 +167,7 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "mercury": 1 } }, "blacksmith": { }, diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 08dd7aed8..291fa134c 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -172,7 +172,7 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "ore": 1, "wood": 1 } }, "blacksmith": { }, @@ -182,7 +182,7 @@ "ship": { "id" : 20, "upgrades" : "shipyard" }, "special2": { "requires" : [ "mageGuild1" ], "bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] }, - "special3": { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] }, + "special3": { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ], "marketModes" : ["creature-undead"] }, "grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] }, diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 01e86fb0c..eec3b2c73 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -170,7 +170,7 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "crystal": 1 } }, "blacksmith": { }, diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index 73bc9ff52..c2ca9715f 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -162,14 +162,14 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce": { "ore": 1, "wood": 1 } }, "blacksmith": { }, "special1": { "type" : "escapeTunnel", "requires" : [ "fort" ] }, "horde1": { "id" : 18, "upgrades" : "dwellingLvl1" }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" }, - "special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ] }, + "special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ], "marketModes" : ["creature-resource"] }, "special3": { "type" : "ballistaYard", "requires" : [ "blacksmith" ] }, "special4": { "requires" : [ "fort" ], diff --git a/config/factions/tower.json b/config/factions/tower.json index 452f884a8..977372509 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -165,11 +165,11 @@ "townHall": { }, "cityHall": { }, "capitol": { }, - "marketplace": { }, + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, "resourceSilo": { "produce" : { "gems": 1 } }, "blacksmith": { }, - "special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] }, + "special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] }, "horde1": { "id" : 18, "upgrades" : "dwellingLvl2" }, "horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" }, "special2": { "height" : "high", "requires" : [ "fort" ] }, diff --git a/config/schemas/townBuilding.json b/config/schemas/townBuilding.json index b273e66a9..53a9aa570 100644 --- a/config/schemas/townBuilding.json +++ b/config/schemas/townBuilding.json @@ -97,6 +97,11 @@ "type" : "array", "description" : "Bonuses that are provided by this building in any town where this building has been built. Only affects town itself (including siege), to propagate effect to player or team please use bonus propagators", "items" : { "$ref" : "bonus.json" } + }, + "marketModes" : { + "type" : "array", + "enum" : [ "resource-resource", "resource-player", "creature-resource", "resource-artifact", "artifact-resource", "artifact-experience", "creature-experience", "creature-undead", "resource-skill"], + "description" : "List of modes available in this market" } } } diff --git a/docs/modders/Entities_Format/Town_Building_Format.md b/docs/modders/Entities_Format/Town_Building_Format.md index 964b52b56..91d8288e2 100644 --- a/docs/modders/Entities_Format/Town_Building_Format.md +++ b/docs/modders/Entities_Format/Town_Building_Format.md @@ -171,6 +171,9 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config // 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" ], } ``` @@ -217,7 +220,6 @@ Following HotA buildings can be used as unique building for a town. Functionalit #### Custom buildings In addition to above, it is possible to use same format as [Rewardable](../Map_Objects/Rewardable.md) map objects for town buildings. In order to do that, configuration of a rewardable object must be placed into `configuration` json node in building config. -``` ### Town Structure node @@ -248,3 +250,17 @@ In addition to above, it is possible to use same format as [Rewardable](../Map_O "hidden" : false } ``` + + +#### Markets in towns +Market buildings require list of available [modes](../Map_Objects/Market.md) + +##### Marketplace +```jsonc + "marketplace": { "marketModes" : ["resource-resource", "resource-player"] }, +``` + +##### Artifact merchant +```jsonc + "special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] }, +``` \ No newline at end of file diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 156c3fde5..76a7f6f29 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -174,6 +174,15 @@ const CGTownInstance* CGameInfoCallback::getTown(ObjectInstanceID objid) const return nullptr; } +const IMarket * CGameInfoCallback::getMarket(ObjectInstanceID objid) const +{ + const CGObjectInstance * obj = getObj(objid, false); + if(obj) + return dynamic_cast(obj); + else + return nullptr; +} + void CGameInfoCallback::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const { //boost::shared_lock lock(*gs->mx); diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 5cca4276d..ff9867249 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -48,6 +48,7 @@ class CGHeroInstance; class CGDwelling; class CGTeleport; class CGTownInstance; +class IMarket; class DLL_LINKAGE IGameInfoCallback : boost::noncopyable { @@ -189,6 +190,7 @@ public: virtual std::vector getFlaggableObjects(int3 pos) const; virtual const CGObjectInstance * getTopObj (int3 pos) const; virtual PlayerColor getOwner(ObjectInstanceID heroID) const; + virtual const IMarket * getMarket(ObjectInstanceID objid) const; //map virtual int3 guardingCreaturePosition (int3 pos) const; diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 3a70daa64..aeaa569f6 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -287,18 +287,16 @@ CArtifactSet * CNonConstInfoCallback::getArtSet(const ArtifactLocation & loc) return hero; } } + else if(auto market = getMarket(loc.artHolder)) + { + if(auto artSet = market->getArtifactsStorage()) + return artSet.get(); + } else if(auto army = getArmyInstance(loc.artHolder)) { return army->getStackPtr(loc.creature.value()); } - else if(auto market = dynamic_cast(getObjInstance(loc.artHolder))) - { - return market; - } - else - { - return nullptr; - } + return nullptr; } bool IGameCallback::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index 5f6c5540a..ca7d5e86f 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -109,7 +109,7 @@ public: virtual void showPuzzleMap(){}; virtual void viewWorldMap(){}; - virtual void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){}; + virtual void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID){}; virtual void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){}; virtual void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor){}; virtual void showThievesGuildWindow (const CGObjectInstance * obj){}; diff --git a/lib/entities/building/CBuilding.h b/lib/entities/building/CBuilding.h index fff475922..fae69474e 100644 --- a/lib/entities/building/CBuilding.h +++ b/lib/entities/building/CBuilding.h @@ -34,6 +34,7 @@ public: TResources resources; TResources produce; TRequired requirements; + std::set marketModes; BuildingID bid; //structure ID BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty @@ -85,7 +86,7 @@ public: STRONG_INLINE bool IsTradeBuilding() const { - return bid == BuildingID::MARKETPLACE || subId == BuildingSubID::ARTIFACT_MERCHANT || subId == BuildingSubID::FREELANCERS_GUILD; + return !marketModes.empty(); } void addNewBonus(const std::shared_ptr & b, BonusList & bonusList) const; diff --git a/lib/entities/faction/CTownHandler.cpp b/lib/entities/faction/CTownHandler.cpp index 9bdf8a9be..94739eaf2 100644 --- a/lib/entities/faction/CTownHandler.cpp +++ b/lib/entities/faction/CTownHandler.cpp @@ -342,6 +342,11 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->upgrade = BuildingID::NONE; ret->town->buildings[ret->bid] = ret; + for(const auto & element : source["marketModes"].Vector()) + { + if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String())) + ret->marketModes.insert(MappedKeys::MARKET_NAMES_TO_TYPES.at(element.String())); + } registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum()); } diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 742901d9d..f3de1126f 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -808,12 +808,12 @@ void CGameState::initTowns() constexpr std::array hordes = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7, BuildingID::HORDE_PLACEHOLDER8 }; //init buildings - if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings + if(vti->hasBuilt(BuildingID::DEFAULT)) //give standard set of buildings { - vti->builtBuildings.erase(BuildingID::DEFAULT); - vti->builtBuildings.insert(BuildingID::VILLAGE_HALL); + vti->removeBuilding(BuildingID::DEFAULT); + vti->addBuilding(BuildingID::VILLAGE_HALL); if(vti->tempOwner != PlayerColor::NEUTRAL) - vti->builtBuildings.insert(BuildingID::TAVERN); + vti->addBuilding(BuildingID::TAVERN); auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES); @@ -821,48 +821,49 @@ void CGameState::initTowns() { if((getRandomGenerator().nextInt(1,100) <= definesBuildingsChances[i])) { - vti->builtBuildings.insert(basicDwellings[i]); + vti->addBuilding(basicDwellings[i]); } } } // village hall must always exist - vti->builtBuildings.insert(BuildingID::VILLAGE_HALL); + vti->addBuilding(BuildingID::VILLAGE_HALL); //init hordes for (int i = 0; i < vti->town->creatures.size(); i++) { - if (vstd::contains(vti->builtBuildings, hordes[i])) //if we have horde for this level + if(vti->hasBuilt(hordes[i])) //if we have horde for this level { - vti->builtBuildings.erase(hordes[i]);//remove old ID + vti->removeBuilding(hordes[i]);//remove old ID if (vti->getTown()->hordeLvl.at(0) == i)//if town first horde is this one { - vti->builtBuildings.insert(BuildingID::HORDE_1);//add it + vti->addBuilding(BuildingID::HORDE_1);//add it //if we have upgraded dwelling as well - if (vstd::contains(vti->builtBuildings, upgradedDwellings[i])) - vti->builtBuildings.insert(BuildingID::HORDE_1_UPGR);//add it as well + if(vti->hasBuilt(upgradedDwellings[i])) + vti->addBuilding(BuildingID::HORDE_1_UPGR);//add it as well } if (vti->getTown()->hordeLvl.at(1) == i)//if town second horde is this one { - vti->builtBuildings.insert(BuildingID::HORDE_2); - if (vstd::contains(vti->builtBuildings, upgradedDwellings[i])) - vti->builtBuildings.insert(BuildingID::HORDE_2_UPGR); + vti->addBuilding(BuildingID::HORDE_2); + if(vti->hasBuilt(upgradedDwellings[i])) + vti->addBuilding(BuildingID::HORDE_2_UPGR); } } } //#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings) //But DO NOT remove horde placeholders before they are replaced - vstd::erase_if(vti->builtBuildings, [vti](const BuildingID & bid) - { - return !vti->getTown()->buildings.count(bid) || !vti->getTown()->buildings.at(bid); - }); + for(const auto & building : vti->getBuildings()) + { + if(!vti->getTown()->buildings.count(building) || !vti->getTown()->buildings.at(building)) + vti->removeBuilding(building); + } - if (vstd::contains(vti->builtBuildings, BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED) - vti->builtBuildings.erase(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour) + if(vti->hasBuilt(BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED) + vti->removeBuilding(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour) //Early check for #1444-like problems - for([[maybe_unused]] const auto & building : vti->builtBuildings) + for([[maybe_unused]] const auto & building : vti->getBuildings()) { assert(vti->getTown()->buildings.at(building) != nullptr); } diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index b5a30ceff..4f66f525f 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -656,7 +656,7 @@ void CGameStateCampaign::initTowns() if(gameState->scenarioOps->campState->formatVCMI()) newBuilding = BuildingID(chosenBonus->info1); else - newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->builtBuildings); + newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->getBuildings()); // Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2 while(true) @@ -664,10 +664,10 @@ void CGameStateCampaign::initTowns() if (newBuilding == BuildingID::NONE) break; - if (town->builtBuildings.count(newBuilding) != 0) + if(town->hasBuilt(newBuilding)) break; - town->builtBuildings.insert(newBuilding); + town->addBuilding(newBuilding); auto building = town->town->buildings.at(newBuilding); newBuilding = building->upgrade; diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 8551e18de..9fcc422d7 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -372,7 +372,7 @@ float Statistic::getTownBuiltRatio(const PlayerState * ps) for(const auto & t : ps->towns) { - built += t->builtBuildings.size(); + built += t->getBuildings().size(); for(const auto & b : t->town->buildings) if(!t->forbiddenBuildings.count(b.first)) total += 1; diff --git a/lib/gameState/HighScore.cpp b/lib/gameState/HighScore.cpp index a43fe7978..55f615445 100644 --- a/lib/gameState/HighScore.cpp +++ b/lib/gameState/HighScore.cpp @@ -33,7 +33,7 @@ HighScoreParameter HighScore::prepareHighScores(const CGameState * gs, PlayerCol if(h->hasArt(ArtifactID::GRAIL)) param.hasGrail = true; for(const CGTownInstance * t : playerState->towns) - if(t->builtBuildings.count(BuildingID::GRAIL)) + if(t->hasBuilt(BuildingID::GRAIL)) param.hasGrail = true; param.allEnemiesDefeated = true; for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer) diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index b7f828dd9..8678ee73b 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -238,25 +238,23 @@ CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const return new CGUniversity(cb); } } - else if(marketModes.size() == 2) - { - if(vstd::contains(marketModes, EMarketMode::ARTIFACT_EXP)) - return new CGArtifactsAltar(cb); - } return new CGMarket(cb); } void MarketInstanceConstructor::initializeObject(CGMarket * market) const { - market->marketModes = marketModes; + market->addMarketMode(marketModes); market->marketEfficiency = marketEfficiency; - market->title = market->getObjectName(); - if(!title.empty()) - market->title = VLC->generaltexth->translate(title); - - if (!speech.empty()) - market->speech = VLC->generaltexth->translate(speech); + if(auto university = dynamic_cast(market)) + { + university->title = market->getObjectName(); + if(!title.empty()) + university->title = VLC->generaltexth->translate(title); + + if(!speech.empty()) + university->speech = VLC->generaltexth->translate(speech); + } } void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & rng) const diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index 8b6cf877e..a160f7803 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -23,6 +23,11 @@ VCMI_LIB_NAMESPACE_BEGIN +ObjectInstanceID CGMarket::getObjInstanceID() const +{ + return id; +} + void CGMarket::initObj(vstd::RNG & rand) { getObjectHandler()->configureObject(this, rand); @@ -38,23 +43,11 @@ int CGMarket::getMarketEfficiency() const return marketEfficiency; } -bool CGMarket::allowsTrade(EMarketMode mode) const -{ - return marketModes.count(mode); -} - int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const { return -1; } -std::vector CGMarket::availableItemsIds(EMarketMode mode) const -{ - if(allowsTrade(mode)) - return IMarket::availableItemsIds(mode); - return std::vector(); -} - CGMarket::CGMarket(IGameCallback *cb): CGObjectInstance(cb) {} @@ -63,8 +56,6 @@ std::vector CGBlackMarket::availableItemsIds(EMarketMode mode) con { switch(mode) { - case EMarketMode::ARTIFACT_RESOURCE: - return IMarket::availableItemsIds(mode); case EMarketMode::RESOURCE_ARTIFACT: { std::vector ret; @@ -113,9 +104,4 @@ void CGUniversity::onHeroVisit(const CGHeroInstance * h) const cb->showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true); } -ArtBearer::ArtBearer CGArtifactsAltar::bearerType() const -{ - return ArtBearer::ALTAR; -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index 956d15daa..19cb235c7 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -18,32 +18,23 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket { public: - - std::set marketModes; int marketEfficiency; - //window variables - std::string title; - std::string speech; //currently shown only in university - CGMarket(IGameCallback *cb); ///IObjectInterface void onHeroVisit(const CGHeroInstance * h) const override; //open trading window void initObj(vstd::RNG & rand) override;//set skills for trade ///IMarket + ObjectInstanceID getObjInstanceID() const override; int getMarketEfficiency() const override; - bool allowsTrade(EMarketMode mode) const override; int availableUnits(EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited - std::vector availableItemsIds(EMarketMode mode) const override; template void serialize(Handler &h) { h & static_cast(*this); - h & marketModes; + h & static_cast(*this); h & marketEfficiency; - h & title; - h & speech; } }; @@ -68,6 +59,8 @@ class DLL_LINKAGE CGUniversity : public CGMarket { public: using CGMarket::CGMarket; + std::string speech; //currently shown only in university + std::string title; std::vector skills; //available skills @@ -78,20 +71,8 @@ public: { h & static_cast(*this); h & skills; - } -}; - -class DLL_LINKAGE CGArtifactsAltar : public CGMarket, public CArtifactSet -{ -public: - using CGMarket::CGMarket; - - ArtBearer::ArtBearer bearerType() const override; - - template void serialize(Handler & h) - { - h & static_cast(*this); - h & static_cast(*this); + h & speech; + h & title; } }; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 64eddc418..1b8a60b28 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -690,35 +690,6 @@ int CGTownInstance::getMarketEfficiency() const return marketCount; } -bool CGTownInstance::allowsTrade(EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - return hasBuilt(BuildingID::MARKETPLACE); - - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::RESOURCE_ARTIFACT: - return hasBuilt(BuildingSubID::ARTIFACT_MERCHANT); - - case EMarketMode::CREATURE_RESOURCE: - return hasBuilt(BuildingSubID::FREELANCERS_GUILD); - - case EMarketMode::CREATURE_UNDEAD: - return hasBuilt(BuildingSubID::CREATURE_TRANSFORMER); - - case EMarketMode::RESOURCE_SKILL: - return hasBuilt(BuildingSubID::MAGIC_UNIVERSITY); - case EMarketMode::CREATURE_EXP: - case EMarketMode::ARTIFACT_EXP: - return false; - default: - assert(0); - return false; - } -} - std::vector CGTownInstance::availableItemsIds(EMarketMode mode) const { if(mode == EMarketMode::RESOURCE_ARTIFACT) @@ -739,6 +710,11 @@ std::vector CGTownInstance::availableItemsIds(EMarketMode mode) co return IMarket::availableItemsIds(mode); } +ObjectInstanceID CGTownInstance::getObjInstanceID() const +{ + return id; +} + void CGTownInstance::updateAppearance() { auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); @@ -932,12 +908,7 @@ const CArmedInstance * CGTownInstance::getUpperArmy() const bool CGTownInstance::hasBuiltSomeTradeBuilding() const { - for(const auto & bid : builtBuildings) - { - if(town->buildings.at(bid)->IsTradeBuilding()) - return true; - } - return false; + return availableModes().empty() ? false : true; } bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const @@ -962,6 +933,42 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) c return false; } +void CGTownInstance::addBuilding(const BuildingID & buildingID) +{ + if(buildingID == BuildingID::NONE) + return; + + const auto townType = (*VLC->townh)[getFaction()]->town; + if(const auto & building = townType->buildings.find(buildingID); building != townType->buildings.end()) + { + builtBuildings.insert(buildingID); + addMarketMode(building->second->marketModes); + } +} + +void CGTownInstance::removeBuilding(const BuildingID & buildingID) +{ + if(!vstd::contains(builtBuildings, buildingID)) + return; + + if(const auto & building = town->buildings.find(buildingID); building != town->buildings.end()) + { + builtBuildings.erase(buildingID); + removeMarketMode(building->second->marketModes); + } +} + +void CGTownInstance::removeAllBuildings() +{ + builtBuildings.clear(); + removeAllMarketModes(); +} + +std::set CGTownInstance::getBuildings() const +{ + return builtBuildings; +} + TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const { if (vstd::contains(town->buildings, buildingID)) @@ -1132,23 +1139,23 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) { handler.serializeLIC("buildings", buildingsLIC); - builtBuildings.insert(BuildingID::VILLAGE_HALL); + addBuilding(BuildingID::VILLAGE_HALL); if(buildingsLIC.none.empty() && buildingsLIC.all.empty()) { - builtBuildings.insert(BuildingID::DEFAULT); + addBuilding(BuildingID::DEFAULT); bool hasFort = false; handler.serializeBool("hasFort",hasFort); if(hasFort) - builtBuildings.insert(BuildingID::FORT); + addBuilding(BuildingID::FORT); } else { for(const si32 item : buildingsLIC.none) forbiddenBuildings.insert(BuildingID(item)); for(const si32 item : buildingsLIC.all) - builtBuildings.insert(BuildingID(item)); + addBuilding(BuildingID(item)); } } } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fc0c5a7d4..137ebb7fd 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -52,6 +52,8 @@ class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public I std::string nameTextId; // name of town std::map convertOldBuildings(std::vector oldVector); + std::set builtBuildings; + public: using CGDwelling::getPosition; @@ -65,7 +67,6 @@ public: ui32 identifier; //special identifier from h3m (only > RoE maps) PlayerColor alignmentToPlayer; // if set to non-neutral, random town will have same faction as specified player std::set forbiddenBuildings; - std::set builtBuildings; std::map rewardableBuildings; std::vector possibleSpells, obligatorySpells; std::vector > spells; //spells[level] -> vector of spells, first will be available in guild @@ -76,6 +77,7 @@ public: template void serialize(Handler &h) { h & static_cast(*this); + h & static_cast(*this); h & nameTextId; h & built; h & destroyed; @@ -152,9 +154,8 @@ public: EGeneratorState shipyardStatus() const override; const IObjectInterface * getObject() const override; int getMarketEfficiency() const override; //=market count - bool allowsTrade(EMarketMode mode) const override; std::vector availableItemsIds(EMarketMode mode) const override; - + ObjectInstanceID getObjInstanceID() const override; void updateAppearance(); ////////////////////////////////////////////////////////////////////////// @@ -174,6 +175,10 @@ public: //checks if building is constructed and town has same subID bool hasBuilt(const BuildingID & buildingID) const; bool hasBuilt(const BuildingID & buildingID, FactionID townID) const; + void addBuilding(const BuildingID & buildingID); + void removeBuilding(const BuildingID & buildingID); + void removeAllBuildings(); + std::set getBuildings() const; TResources getBuildingCost(const BuildingID & buildingID) const; TResources dailyIncome() const; //calculates daily income of this town diff --git a/lib/mapObjects/IMarket.cpp b/lib/mapObjects/IMarket.cpp index 7f7840408..3a698d8c9 100644 --- a/lib/mapObjects/IMarket.cpp +++ b/lib/mapObjects/IMarket.cpp @@ -20,6 +20,11 @@ VCMI_LIB_NAMESPACE_BEGIN +bool IMarket::allowsTrade(const EMarketMode mode) const +{ + return vstd::contains(marketModes, mode); +} + bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const { switch(mode) @@ -122,12 +127,7 @@ bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) return true; } -bool IMarket::allowsTrade(EMarketMode mode) const -{ - return false; -} - -int IMarket::availableUnits(EMarketMode mode, int marketItemSerial) const +int IMarket::availableUnits(const EMarketMode mode, const int marketItemSerial) const { switch(mode) { @@ -140,7 +140,45 @@ int IMarket::availableUnits(EMarketMode mode, int marketItemSerial) const } } -std::vector IMarket::availableItemsIds(EMarketMode mode) const +void IMarket::addMarketMode(const EMarketMode mode) +{ + marketModes.insert(mode); + + if(mode == EMarketMode::ARTIFACT_EXP) + altarArtifactsStorage = std::make_shared(); +} + +void IMarket::addMarketMode(const std::set & modes) +{ + for(const auto & mode : modes) + addMarketMode(mode); +} + +void IMarket::removeMarketMode(const EMarketMode mode) +{ + marketModes.erase(mode); + + if(mode == EMarketMode::ARTIFACT_EXP) + altarArtifactsStorage.reset(); +} + +void IMarket::removeMarketMode(const std::set & modes) +{ + for(const auto & mode : modes) + removeMarketMode(mode); +} + +void IMarket::removeAllMarketModes() +{ + marketModes.clear(); +} + +std::shared_ptr IMarket::getArtifactsStorage() const +{ + return altarArtifactsStorage; +} + +std::vector IMarket::availableItemsIds(const EMarketMode mode) const { std::vector ret; switch(mode) @@ -148,24 +186,15 @@ std::vector IMarket::availableItemsIds(EMarketMode mode) const case EMarketMode::RESOURCE_RESOURCE: case EMarketMode::ARTIFACT_RESOURCE: case EMarketMode::CREATURE_RESOURCE: - for (auto res : GameResID::ALL_RESOURCES()) + for(const auto & res : GameResID::ALL_RESOURCES()) ret.push_back(res); } return ret; } -IMarket::IMarket() +std::set IMarket::availableModes() const { -} - -std::vector IMarket::availableModes() const -{ - std::vector ret; - for (EMarketMode i = static_cast(0); i < EMarketMode::MARKET_AFTER_LAST_PLACEHOLDER; i = vstd::next(i, 1)) - if(allowsTrade(i)) - ret.push_back(i); - - return ret; + return marketModes; } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/IMarket.h b/lib/mapObjects/IMarket.h index caf3e4cb4..3145a010b 100644 --- a/lib/mapObjects/IMarket.h +++ b/lib/mapObjects/IMarket.h @@ -11,24 +11,44 @@ #include "../networkPacks/TradeItem.h" #include "../constants/Enumerations.h" +#include "../CArtHandler.h" VCMI_LIB_NAMESPACE_BEGIN -class CGObjectInstance; - -class DLL_LINKAGE IMarket +class DLL_LINKAGE IMarket : public virtual Serializeable { public: - IMarket(); - virtual ~IMarket() {} + class CArtifactSetAltar : public CArtifactSet + { + public: + ArtBearer::ArtBearer bearerType() const override {return ArtBearer::ALTAR;}; + }; + virtual ObjectInstanceID getObjInstanceID() const = 0; // The market is always an object on the map virtual int getMarketEfficiency() const = 0; - virtual bool allowsTrade(EMarketMode mode) const; - virtual int availableUnits(EMarketMode mode, int marketItemSerial) const; //-1 if unlimited - virtual std::vector availableItemsIds(EMarketMode mode) const; - + virtual bool allowsTrade(const EMarketMode mode) const; + virtual int availableUnits(const EMarketMode mode, const int marketItemSerial) const; //-1 if unlimited + virtual std::vector availableItemsIds(const EMarketMode mode) const; + void addMarketMode(const EMarketMode mode); + void addMarketMode(const std::set & modes); + void removeMarketMode(const EMarketMode mode); + void removeMarketMode(const std::set & modes); + void removeAllMarketModes(); + std::set availableModes() const; + std::shared_ptr getArtifactsStorage() const; bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units - std::vector availableModes() const; + + template void serialize(Handler & h) + { + h & marketModes; + + if(h.loadingGamestate && vstd::contains(marketModes, EMarketMode::ARTIFACT_EXP)) + altarArtifactsStorage = std::make_shared(); + } + +private: + std::shared_ptr altarArtifactsStorage; + std::set marketModes; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 18b51e1b6..2de87996c 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2198,7 +2198,11 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt bool hasCustomBuildings = reader->readBool(); if(hasCustomBuildings) { - reader->readBitmaskBuildings(object->builtBuildings, faction); + object->subID = faction.value(); + std::set builtBuildings; + reader->readBitmaskBuildings(builtBuildings, faction); + for(const auto & building : builtBuildings) + object->addBuilding(building); reader->readBitmaskBuildings(object->forbiddenBuildings, faction); } // Standard buildings @@ -2206,10 +2210,10 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt { bool hasFort = reader->readBool(); if(hasFort) - object->builtBuildings.insert(BuildingID::FORT); + object->addBuilding(BuildingID::FORT); //means that set of standard building should be included - object->builtBuildings.insert(BuildingID::DEFAULT); + object->addBuilding(BuildingID::DEFAULT); } if(features.levelAB) diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 6946b11ab..8bddb478e 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1332,7 +1332,7 @@ void NewStructures::applyGs(CGameState *gs) for(const auto & id : bid) { assert(t->town->buildings.at(id) != nullptr); - t->builtBuildings.insert(id); + t->addBuilding(id); } t->updateAppearance(); t->built = built; @@ -1344,7 +1344,7 @@ void RazeStructures::applyGs(CGameState *gs) CGTownInstance *t = gs->getTown(tid); for(const auto & id : bid) { - t->builtBuildings.erase(id); + t->removeBuilding(id); t->updateAppearance(); } diff --git a/lib/registerTypes/RegisterTypesMapObjects.h b/lib/registerTypes/RegisterTypesMapObjects.h index 4c2528030..8338edd68 100644 --- a/lib/registerTypes/RegisterTypesMapObjects.h +++ b/lib/registerTypes/RegisterTypesMapObjects.h @@ -132,8 +132,7 @@ void registerTypesMapObjects(Serializer &s) //s.template registerType(); s.template registerType(); - - s.template registerType(); + s.template registerType(); } VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/modificators/TownPlacer.cpp b/lib/rmg/modificators/TownPlacer.cpp index 035536d29..0a3e412c4 100644 --- a/lib/rmg/modificators/TownPlacer.cpp +++ b/lib/rmg/modificators/TownPlacer.cpp @@ -79,8 +79,8 @@ void TownPlacer::placeTowns(ObjectManager & manager) CGTownInstance * town = dynamic_cast(townFactory->create(map.mapInstance->cb, nullptr)); town->tempOwner = player; - town->builtBuildings.insert(BuildingID::FORT); - town->builtBuildings.insert(BuildingID::DEFAULT); + town->addBuilding(BuildingID::FORT); + town->addBuilding(BuildingID::DEFAULT); for(auto spellID : VLC->spellh->getDefaultAllowed()) //add all regular spells to town @@ -203,8 +203,8 @@ void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player town->tempOwner = player; if (hasFort) - town->builtBuildings.insert(BuildingID::FORT); - town->builtBuildings.insert(BuildingID::DEFAULT); + town->addBuilding(BuildingID::FORT); + town->addBuilding(BuildingID::DEFAULT); for(auto spellID : VLC->spellh->getDefaultAllowed()) //add all regular spells to town town->possibleSpells.push_back(spellID); diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 971c6676d..4aae886a1 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -166,12 +166,12 @@ void Initializer::initialize(CGTownInstance * o) const std::vector castleLevels{"village", "fort", "citadel", "castle", "capitol"}; int lvl = vstd::find_pos(castleLevels, o->appearance->stringID); - o->builtBuildings.insert(BuildingID::DEFAULT); - if(lvl > -1) o->builtBuildings.insert(BuildingID::TAVERN); - if(lvl > 0) o->builtBuildings.insert(BuildingID::FORT); - if(lvl > 1) o->builtBuildings.insert(BuildingID::CITADEL); - if(lvl > 2) o->builtBuildings.insert(BuildingID::CASTLE); - if(lvl > 3) o->builtBuildings.insert(BuildingID::CAPITOL); + o->addBuilding(BuildingID::DEFAULT); + if(lvl > -1) o->addBuilding(BuildingID::TAVERN); + if(lvl > 0) o->addBuilding(BuildingID::FORT); + if(lvl > 1) o->addBuilding(BuildingID::CITADEL); + if(lvl > 2) o->addBuilding(BuildingID::CASTLE); + if(lvl > 3) o->addBuilding(BuildingID::CAPITOL); for(auto const & spell : VLC->spellh->objects) //add all regular spells to town { diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index c3bff0216..a90bd654a 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -166,7 +166,7 @@ QStandardItem * TownBuildingsWidget::addBuilding(const CTown & ctown, int bId, s checks << new QStandardItem; checks.back()->setCheckable(true); - checks.back()->setCheckState(town.builtBuildings.count(buildingId) ? Qt::Checked : Qt::Unchecked); + checks.back()->setCheckState(town.hasBuilt(buildingId) ? Qt::Checked : Qt::Unchecked); checks.back()->setData(bId, MapEditorRoles::BuildingIDRole); if(building->getBase() == buildingId) @@ -350,7 +350,9 @@ void TownBuildingsDelegate::setModelData(QWidget *editor, QAbstractItemModel *mo if(auto * ed = qobject_cast(editor)) { town.forbiddenBuildings = ed->getForbiddenBuildings(); - town.builtBuildings = ed->getBuiltBuildings(); + town.removeAllBuildings(); + for(const auto & building : ed->getBuiltBuildings()) + town.addBuilding(building); } else { diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 34b4270dd..8b7fcc352 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -174,10 +174,11 @@ void MapController::repairMap(CMap * map) const { if(tnh->getTown()) { - vstd::erase_if(tnh->builtBuildings, [tnh](BuildingID bid) + for(const auto & building : tnh->getBuildings()) { - return !tnh->getTown()->buildings.count(bid); - }); + if(!tnh->getTown()->buildings.count(building)) + tnh->removeBuilding(building); + } vstd::erase_if(tnh->forbiddenBuildings, [tnh](BuildingID bid) { return !tnh->getTown()->buildings.count(bid); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index d269b3d0f..357e44d87 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2525,7 +2525,7 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) { ///incomplete, simply erases target building const CGTownInstance * t = getTown(tid); - if (!vstd::contains(t->builtBuildings, bid)) + if(!t->hasBuilt(bid)) return false; RazeStructures rs; rs.tid = tid; @@ -3565,9 +3565,9 @@ bool CGameHandler::isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2) return true; } - auto market = dynamic_cast(o1); + auto market = getMarket(id1); if(market == nullptr) - market = dynamic_cast(o2); + market = getMarket(id2); if(market) return market->allowsTrade(EMarketMode::ARTIFACT_EXP); @@ -3916,15 +3916,15 @@ bool CGameHandler::sacrificeCreatures(const IMarket * market, const CGHeroInstan return true; } -bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector & arts) +bool CGameHandler::sacrificeArtifact(const IMarket * market, const CGHeroInstance * hero, const std::vector & arts) { if (!hero) COMPLAIN_RET("You need hero to sacrifice artifact!"); if(hero->getAlignment() == EAlignment::EVIL) COMPLAIN_RET("Evil hero can't sacrifice artifact!"); - assert(m); - auto altarObj = dynamic_cast(m); + assert(market); + const auto artSet = market->getArtifactsStorage(); int expSum = 0; auto finish = [this, &hero, &expSum]() @@ -3934,15 +3934,15 @@ bool CGameHandler::sacrificeArtifact(const IMarket * m, const CGHeroInstance * h for(const auto & artInstId : arts) { - if(auto art = altarObj->getArtByInstanceId(artInstId)) + if(auto art = artSet->getArtByInstanceId(artInstId)) { if(art->artType->isTradable()) { int dmp; int expToGive; - m->getOffer(art->getTypeId(), 0, dmp, expToGive, EMarketMode::ARTIFACT_EXP); + market->getOffer(art->getTypeId(), 0, dmp, expToGive, EMarketMode::ARTIFACT_EXP); expSum += expToGive; - removeArtifact(ArtifactLocation(altarObj->id, altarObj->getArtPos(art))); + removeArtifact(ArtifactLocation(market->getObjInstanceID(), artSet->getArtPos(art))); } else { diff --git a/server/CGameHandler.h b/server/CGameHandler.h index f0f6bfbf5..6042a512c 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -282,7 +282,7 @@ public: void start(bool resume); void tick(int millisecondsPassed); - bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector & arts); + bool sacrificeArtifact(const IMarket * market, const CGHeroInstance * hero, const std::vector & arts); void spawnWanderingMonsters(CreatureID creatureID); // Check for victory and loss conditions diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 17217dac6..d1ebbf895 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -184,7 +184,7 @@ void ApplyGhNetPackVisitor::visitExchangeArtifacts(ExchangeArtifacts & pack) void ApplyGhNetPackVisitor::visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) { - if(dynamic_cast(gh.getObj(pack.srcHero)) == nullptr) + if(gh.getMarket(pack.srcHero) == nullptr) gh.throwIfWrongOwner(&pack, pack.srcHero); if(pack.swap) gh.throwIfWrongOwner(&pack, pack.dstHero); @@ -250,7 +250,7 @@ void ApplyGhNetPackVisitor::visitTradeOnMarketplace(TradeOnMarketplace & pack) { const CGObjectInstance * object = gh.getObj(pack.marketId); const CGHeroInstance * hero = gh.getHero(pack.heroId); - const auto * market = dynamic_cast(object); + const auto * market = gh.getMarket(pack.marketId); gh.throwIfWrongPlayer(&pack); gh.throwIfPlayerNotActive(&pack); diff --git a/test/spells/effects/CatapultTest.cpp b/test/spells/effects/CatapultTest.cpp index a67e9204a..9743f9378 100644 --- a/test/spells/effects/CatapultTest.cpp +++ b/test/spells/effects/CatapultTest.cpp @@ -64,7 +64,7 @@ TEST_F(CatapultTest, NotApplicableInVillage) TEST_F(CatapultTest, NotApplicableForDefenderIfSmart) { auto fakeTown = std::make_shared(nullptr); - fakeTown->builtBuildings.insert(BuildingID::FORT); + fakeTown->addBuilding(BuildingID::FORT); mechanicsMock.casterSide = BattleSide::DEFENDER; EXPECT_CALL(*battleFake, getDefendedTown()).WillRepeatedly(Return(fakeTown.get())); @@ -78,7 +78,7 @@ TEST_F(CatapultTest, NotApplicableForDefenderIfSmart) TEST_F(CatapultTest, DISABLED_ApplicableInTown) { auto fakeTown = std::make_shared(nullptr); - fakeTown->builtBuildings.insert(BuildingID::FORT); + fakeTown->addBuilding(BuildingID::FORT); EXPECT_CALL(*battleFake, getDefendedTown()).WillRepeatedly(Return(fakeTown.get())); EXPECT_CALL(mechanicsMock, adaptProblem(_, _)).Times(0); @@ -108,7 +108,7 @@ protected: { EffectFixture::setUp(); fakeTown = std::make_shared(nullptr); - fakeTown->builtBuildings.insert(BuildingID::FORT); + fakeTown->addBuilding(BuildingID::FORT); } private: std::shared_ptr fakeTown;