From 1fb2a026ec0d75f306e9a54f0a88b10beb4a6ccf Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 19 Jan 2024 23:31:21 +0100 Subject: [PATCH 01/12] enable doubleclick --- client/windows/GUIClasses.cpp | 18 +++++++++++++----- client/windows/GUIClasses.h | 5 ++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 94c11a178..8fed1bd40 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -459,8 +459,8 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func oldSelected = -1; - h1 = std::make_shared(selected, 0, 72, 299, h[0]); - h2 = std::make_shared(selected, 1, 162, 299, h[1]); + h1 = std::make_shared(selected, 0, 72, 299, h[0], [&]() { if(!recruit->isBlocked()) recruitb(); }); + h2 = std::make_shared(selected, 1, 162, 299, h[1], [&]() { if(!recruit->isBlocked()) recruitb(); }); title = std::make_shared(197, 32, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]); cost = std::make_shared(320, 328, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, std::to_string(GameConstants::HERO_GOLD_COST)); @@ -569,15 +569,23 @@ void CTavernWindow::HeroPortrait::clickPressed(const Point & cursorPosition) *_sel = _id; } +void CTavernWindow::HeroPortrait::clickDouble(const Point & cursorPosition) +{ + clickPressed(cursorPosition); + + if(onChoose) + onChoose(); +} + void CTavernWindow::HeroPortrait::showPopupWindow(const Point & cursorPosition) { if(h) GH.windows().createAndPushWindow(std::make_shared(h)); } -CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H) - : CIntObject(LCLICK | SHOW_POPUP | HOVER), - h(H), _sel(&sel), _id(id) +CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H, std::function OnChoose) + : CIntObject(LCLICK | DOUBLECLICK | SHOW_POPUP | HOVER), + h(H), _sel(&sel), _id(id), onChoose(OnChoose) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); h = H; diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 1d57d7454..281d30e16 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -206,10 +206,13 @@ public: std::string description; // "XXX is a level Y ZZZ with N artifacts" const CGHeroInstance * h; + std::function onChoose; + void clickPressed(const Point & cursorPosition) override; + void clickDouble(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; void hover (bool on) override; - HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H); + HeroPortrait(int & sel, int id, int x, int y, const CGHeroInstance * H, std::function OnChoose = nullptr); private: int *_sel; From 346beea49ca36aa4c9e420adb7fbb4f95dfdecb9 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 20 Jan 2024 01:09:55 +0100 Subject: [PATCH 02/12] invite hero backend --- CCallback.cpp | 4 ++-- CCallback.h | 4 ++-- client/windows/GUIClasses.cpp | 15 ++++++++++++++- lib/networkPacks/PacksForServer.h | 5 ++++- server/NetPacksServer.cpp | 2 +- server/processors/HeroPoolProcessor.cpp | 12 ++++++++---- server/processors/HeroPoolProcessor.h | 6 ++++-- 7 files changed, 35 insertions(+), 13 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index 85cf0471b..4d932b117 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -260,12 +260,12 @@ void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode) sendRequest(&pack); } -void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) +void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero) { assert(townOrTavern); assert(hero); - HireHero pack(hero->getHeroType(), townOrTavern->id); + HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero); pack.player = *player; sendRequest(&pack); } diff --git a/CCallback.h b/CCallback.h index 068ac547b..5e9f1cf1a 100644 --- a/CCallback.h +++ b/CCallback.h @@ -73,7 +73,7 @@ public: virtual void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1))=0; //cast adventure map spell //town - virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero)=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 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 @@ -185,7 +185,7 @@ public: 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 setFormation(const CGHeroInstance * hero, EArmyFormation mode) override; - void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) override; + void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override; void save(const std::string &fname) override; void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) override; void gamePause(bool pause) override; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 8fed1bd40..c433451fb 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -17,6 +17,8 @@ #include "InfoWindows.h" #include "../CGameInfo.h" +#include "../CServerHandler.h" +#include "../Client.h" #include "../CMusicHandler.h" #include "../CPlayerInterface.h" #include "../CVideoHandler.h" @@ -48,6 +50,7 @@ #include "../lib/mapObjects/ObjectTemplate.h" #include "../lib/gameState/CGameState.h" #include "../lib/gameState/SThievesGuildInfo.h" +#include "../lib/gameState/TavernHeroesPool.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/GameSettings.h" @@ -514,7 +517,17 @@ void CTavernWindow::recruitb() const CGHeroInstance *toBuy = (selected ? h2 : h1)->h; const CGObjectInstance *obj = tavernObj; - LOCPLINT->cb->recruitHero(obj, toBuy); + const auto & heroesPool = CSH->client->gameState()->heroesPool; + HeroTypeID nextHero = HeroTypeID::NONE; + + for(auto & elem : heroesPool->unusedHeroesFromPool()) + { + bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, tavernObj->getOwner()); + if(heroAvailable && elem.second->getNameTranslated() == "Sir Mullich") + nextHero = elem.first; + } + + LOCPLINT->cb->recruitHero(obj, toBuy, nextHero); close(); } diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index de1d7d613..3b3df13c6 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -516,12 +516,14 @@ struct DLL_LINKAGE SetFormation : public CPackForServer struct DLL_LINKAGE HireHero : public CPackForServer { HireHero() = default; - HireHero(HeroTypeID HID, const ObjectInstanceID & TID) + HireHero(HeroTypeID HID, const ObjectInstanceID & TID, const HeroTypeID & NHID) : hid(HID) , tid(TID) + , nhid(NHID) { } HeroTypeID hid; //available hero serial + HeroTypeID nhid; //next hero ObjectInstanceID tid; //town (tavern) id PlayerColor player; @@ -531,6 +533,7 @@ struct DLL_LINKAGE HireHero : public CPackForServer { h & static_cast(*this); h & hid; + h & nhid; h & tid; h & player; } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index a3d77db31..2d064316b 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -282,7 +282,7 @@ void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack) { gh.throwIfWrongPlayer(&pack); - result = gh.heroPool->hireHero(pack.tid, pack.hid, pack.player); + result = gh.heroPool->hireHero(pack.tid, pack.hid, pack.player, pack.nhid); } void ApplyGhNetPackVisitor::visitBuildBoat(BuildBoat & pack) diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 623340e6c..40ce321f3 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -104,7 +104,7 @@ void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroS gameHandler->sendAndApply(&sah); } -void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveArmy) +void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveArmy, const HeroTypeID & nextHero) { SetAvailableHero sah; sah.player = color; @@ -113,6 +113,10 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe CGHeroInstance *newHero = pickHeroFor(needNativeHero, color); + const auto & heroesPool = gameHandler->gameState()->heroesPool; + if(gameHandler->getStartInfo()->extraOptionsInfo.unlimitedReplay && heroesPool->unusedHeroesFromPool().count(nextHero) && heroesPool->isHeroAvailableFor(nextHero, color)) + newHero = heroesPool->unusedHeroesFromPool()[nextHero]; + if (newHero) { sah.hid = newHero->getHeroType(); @@ -145,7 +149,7 @@ void HeroPoolProcessor::onNewWeek(const PlayerColor & color) selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true); } -bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTypeID & heroToRecruit, const PlayerColor & player) +bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTypeID & heroToRecruit, const PlayerColor & player, const HeroTypeID & nextHero) { const PlayerState * playerState = gameHandler->getPlayerState(player); const CGObjectInstance * mapObject = gameHandler->getObj(objectID); @@ -226,9 +230,9 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy gameHandler->sendAndApply(&hr); if(recruitableHeroes[0] == recruitedHero) - selectNewHeroForSlot(player, TavernHeroSlot::NATIVE, false, false); + selectNewHeroForSlot(player, TavernHeroSlot::NATIVE, false, false, nextHero); else - selectNewHeroForSlot(player, TavernHeroSlot::RANDOM, false, false); + selectNewHeroForSlot(player, TavernHeroSlot::RANDOM, false, false, nextHero); gameHandler->giveResource(player, EGameResID::GOLD, -GameConstants::HERO_GOLD_COST); diff --git a/server/processors/HeroPoolProcessor.h b/server/processors/HeroPoolProcessor.h index eed300a79..c69255496 100644 --- a/server/processors/HeroPoolProcessor.h +++ b/server/processors/HeroPoolProcessor.h @@ -9,6 +9,8 @@ */ #pragma once +#include "../../lib/constants/EntityIdentifiers.h" + VCMI_LIB_NAMESPACE_BEGIN enum class TavernHeroSlot : int8_t; @@ -33,7 +35,7 @@ class HeroPoolProcessor : boost::noncopyable std::map> heroSeed; void clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot); - void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy); + void selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveStartingArmy, const HeroTypeID & nextHero = HeroTypeID::NONE); std::vector findAvailableClassesFor(const PlayerColor & player) const; std::vector findAvailableHeroesFor(const PlayerColor & player, const CHeroClass * heroClass) const; @@ -60,7 +62,7 @@ public: CRandomGenerator & getHeroSkillsRandomGenerator(const HeroTypeID & hero); /// Incoming net pack handling - bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player); + bool hireHero(const ObjectInstanceID & objectID, const HeroTypeID & hid, const PlayerColor & player, const HeroTypeID & nextHero); template void serialize(Handler &h, const int version) { From 4813179abb51f211f24d4657abb134104d0db7e5 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 20 Jan 2024 01:24:34 +0100 Subject: [PATCH 03/12] Invite hero setting --- Mods/vcmi/config/vcmi/english.json | 2 ++ Mods/vcmi/config/vcmi/german.json | 2 ++ client/lobby/OptionsTabBase.cpp | 9 +++++++++ config/widgets/extraOptionsTab.json | 16 ++++++++++++++++ lib/ExtraOptionsInfo.cpp | 3 ++- lib/ExtraOptionsInfo.h | 2 ++ server/processors/HeroPoolProcessor.cpp | 2 +- 7 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 81215b65c..aefc46864 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -317,8 +317,10 @@ "vcmi.optionsTab.cheatAllowed.hover" : "Allow cheats", "vcmi.optionsTab.unlimitedReplay.hover" : "Unlimited battle replay", + "vcmi.optionsTab.inviteHero.hover" : "Invite hero", "vcmi.optionsTab.cheatAllowed.help" : "{Allow cheats}\nAllows the inputs of cheats during the game.", "vcmi.optionsTab.unlimitedReplay.help" : "{Unlimited battle replay}\nNo limit of replaying battles.", + "vcmi.optionsTab.inviteHero.help" : "{Invite hero}\nPossibility to invite hero in the tavern.", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index ffbe4bf94..344acd9b2 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -317,8 +317,10 @@ "vcmi.optionsTab.cheatAllowed.hover" : "Cheats erlauben", "vcmi.optionsTab.unlimitedReplay.hover" : "Unbegrenzte Kampfwiederholung", + "vcmi.optionsTab.inviteHero.hover" : "Helden einladen", "vcmi.optionsTab.cheatAllowed.help" : "{Cheats erlauben}\nErlaubt die Eingabe von Cheats während des Spiels.", "vcmi.optionsTab.unlimitedReplay.help" : "{Unbegrenzte Kampfwiederholung}\nKämpfe lassen sich unbegrenzt wiederholen.", + "vcmi.optionsTab.inviteHero.help" : "{Helden einladen}\nMöglichkeit Helden in der Taverne einzuladen.", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Der Feind hat es geschafft, bis zum heutigen Tag zu überleben. Der Sieg gehört ihm!", diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 42bfab108..218b1ccb4 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -110,6 +110,12 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) CSH->setExtraOptionsInfo(info); }); + addCallback("setInviteHero", [&](int index){ + ExtraOptionsInfo info = SEL->getStartInfo()->extraOptionsInfo; + info.inviteHero = index; + CSH->setExtraOptionsInfo(info); + }); + addCallback("setTurnTimerAccumulate", [&](int index){ TurnTimerInfo info = SEL->getStartInfo()->turnTimerInfo; info.accumulatingTurnTimer = index; @@ -411,4 +417,7 @@ void OptionsTabBase::recreate() if(auto buttonUnlimitedReplay = widget("buttonUnlimitedReplay")) buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay); + + if(auto buttonInviteHero = widget("buttonInviteHero")) + buttonInviteHero->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.inviteHero); } diff --git a/config/widgets/extraOptionsTab.json b/config/widgets/extraOptionsTab.json index 1c77dfeab..5f6635254 100644 --- a/config/widgets/extraOptionsTab.json +++ b/config/widgets/extraOptionsTab.json @@ -64,12 +64,21 @@ "name": "buttonCheatAllowed", "image": "lobby/checkbox", "callback" : "setCheatAllowed", + "help" : "vcmi.optionsTab.cheatAllowed", "selected" : true }, { "name": "buttonUnlimitedReplay", "image": "lobby/checkbox", "callback" : "setUnlimitedReplay", + "help" : "vcmi.optionsTab.unlimitedReplay", + "selected" : true + }, + { + "name": "buttonInviteHero", + "image": "lobby/checkbox", + "callback" : "setInviteHero", + "help" : "vcmi.optionsTab.inviteHero", "selected" : true } ] @@ -94,6 +103,13 @@ "alignment": "left", "color": "yellow", "text": "vcmi.optionsTab.unlimitedReplay.hover" + }, + { + "name": "labelInviteHero", + "font": "small", + "alignment": "left", + "color": "yellow", + "text": "vcmi.optionsTab.inviteHero.hover" } ] } diff --git a/lib/ExtraOptionsInfo.cpp b/lib/ExtraOptionsInfo.cpp index 2411608b7..bd0efb124 100644 --- a/lib/ExtraOptionsInfo.cpp +++ b/lib/ExtraOptionsInfo.cpp @@ -15,7 +15,8 @@ VCMI_LIB_NAMESPACE_BEGIN bool ExtraOptionsInfo::operator == (const ExtraOptionsInfo & other) const { return cheatsAllowed == other.cheatsAllowed && - unlimitedReplay == other.unlimitedReplay; + unlimitedReplay == other.unlimitedReplay && + inviteHero == other.inviteHero; } VCMI_LIB_NAMESPACE_END diff --git a/lib/ExtraOptionsInfo.h b/lib/ExtraOptionsInfo.h index 3b1e65d1b..f433008c2 100644 --- a/lib/ExtraOptionsInfo.h +++ b/lib/ExtraOptionsInfo.h @@ -16,6 +16,7 @@ struct DLL_LINKAGE ExtraOptionsInfo { bool cheatsAllowed = true; bool unlimitedReplay = false; + bool inviteHero = false; bool operator == (const ExtraOptionsInfo & other) const; @@ -24,6 +25,7 @@ struct DLL_LINKAGE ExtraOptionsInfo { h & cheatsAllowed; h & unlimitedReplay; + h & inviteHero; } }; diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 40ce321f3..ebf4b4edc 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -114,7 +114,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe CGHeroInstance *newHero = pickHeroFor(needNativeHero, color); const auto & heroesPool = gameHandler->gameState()->heroesPool; - if(gameHandler->getStartInfo()->extraOptionsInfo.unlimitedReplay && heroesPool->unusedHeroesFromPool().count(nextHero) && heroesPool->isHeroAvailableFor(nextHero, color)) + if(gameHandler->getStartInfo()->extraOptionsInfo.inviteHero && heroesPool->unusedHeroesFromPool().count(nextHero) && heroesPool->isHeroAvailableFor(nextHero, color)) newHero = heroesPool->unusedHeroesFromPool()[nextHero]; if (newHero) From a0098eb065e1729b999ed241e7ecb562eeb40c6d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 20 Jan 2024 04:32:59 +0100 Subject: [PATCH 04/12] Gui for hero invite --- Mods/vcmi/config/vcmi/english.json | 2 + Mods/vcmi/config/vcmi/german.json | 2 + client/windows/GUIClasses.cpp | 69 ++++++++++++++++++++++++------ client/windows/GUIClasses.h | 24 +++++++++++ 4 files changed, 85 insertions(+), 12 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index aefc46864..482bd67c4 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -232,6 +232,8 @@ "vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window", "vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management.", + "vcmi.tavernWindow.inviteHero" : "Invite hero", + "vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?", "vcmi.creatureWindow.showBonuses.hover" : "Switch to bonuses view", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 344acd9b2..268caf197 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -232,6 +232,8 @@ "vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen", "vcmi.heroWindow.openBackpack.help" : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert", + "vcmi.tavernWindow.inviteHero" : "Helden einladen", + "vcmi.commanderWindow.artifactMessage" : "Möchtet Ihr diesen Artefakt dem Helden zurückgeben?", "vcmi.creatureWindow.showBonuses.hover" : "Wechsle zur Bonus-Ansicht", diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index c433451fb..9c9b12d8d 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -446,7 +446,8 @@ CLevelWindow::~CLevelWindow() CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::function & onWindowClosed) : CStatusbarWindow(PLAYER_COLORED, ImagePath::builtin("TPTAVERN")), onWindowClosed(onWindowClosed), - tavernObj(TavernObj) + tavernObj(TavernObj), + heroToInvite(nullptr) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); @@ -510,6 +511,34 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func CCS->videoh->open(townObj->town->clientInfo.tavernVideo); else CCS->videoh->open(VideoPath::builtin("TAVERN.BIK")); + + addInvite(); +} + +void CTavernWindow::addInvite() +{ + OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + + if(CSH->client->getStartInfo()->extraOptionsInfo.inviteHero) + { + const auto & heroesPool = CSH->client->gameState()->heroesPool; + for(auto & elem : heroesPool->unusedHeroesFromPool()) + { + bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, tavernObj->getOwner()); + if(heroAvailable) + inviteableHeroes[elem.first] = elem.second; + } + + if(inviteableHeroes.size() > 0) + { + if(!heroToInvite) + heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; + + inviteHero = std::make_shared(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero")); + inviteHeroImage = std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428); + inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [&](){ GH.windows().createAndPushWindow(inviteableHeroes, [&](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [&](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); + } + } } void CTavernWindow::recruitb() @@ -517,17 +546,7 @@ void CTavernWindow::recruitb() const CGHeroInstance *toBuy = (selected ? h2 : h1)->h; const CGObjectInstance *obj = tavernObj; - const auto & heroesPool = CSH->client->gameState()->heroesPool; - HeroTypeID nextHero = HeroTypeID::NONE; - - for(auto & elem : heroesPool->unusedHeroesFromPool()) - { - bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, tavernObj->getOwner()); - if(heroAvailable && elem.second->getNameTranslated() == "Sir Mullich") - nextHero = elem.first; - } - - LOCPLINT->cb->recruitHero(obj, toBuy, nextHero); + LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroType() : HeroTypeID::NONE); close(); } @@ -636,6 +655,32 @@ void CTavernWindow::HeroPortrait::hover(bool on) GH.statusbar()->clear(); } +CTavernWindow::HeroSelector::HeroSelector(std::map InviteableHeroes, std::function OnChoose) + : CWindowObject(BORDERED), inviteableHeroes(InviteableHeroes), onChoose(OnChoose) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + pos = Rect(0, 0, 16 * 48, (inviteableHeroes.size() / 16 + (inviteableHeroes.size() % 16 != 0)) * 32); + background = std::make_shared(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h)); + + int x = 0, y = 0; + for(auto & h : inviteableHeroes) + { + portraits.push_back(std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32)); + portraitAreas.push_back(std::make_shared(Rect(x * 48, y * 32, 48, 32), [&](){ close(); onChoose(inviteableHeroes[h.first]); }, [&](){ GH.windows().createAndPushWindow(std::make_shared(inviteableHeroes[h.first])); })); + + if(x > 0 && x % 15 == 0) + { + x = 0; + y++; + } + else + x++; + } + + center(); +} + static const std::string QUICK_EXCHANGE_MOD_PREFIX = "quick-exchange"; static const std::string QUICK_EXCHANGE_BG = QUICK_EXCHANGE_MOD_PREFIX + "/TRADEQE"; diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 281d30e16..e37c510ee 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -36,6 +36,8 @@ class CTextBox; class CGarrisonInt; class CGarrisonSlot; class CHeroArea; +class CAnimImage; +class CFilledTexture; enum class EUserEvent; @@ -221,6 +223,21 @@ public: std::shared_ptr portrait; }; + class HeroSelector : public CWindowObject + { + public: + std::shared_ptr background; + + HeroSelector(std::map InviteableHeroes, std::function OnChoose); + + private: + std::map inviteableHeroes; + std::function onChoose; + + std::vector> portraits; + std::vector> portraitAreas; + }; + //recruitable heroes std::shared_ptr h1; std::shared_ptr h2; //recruitable heroes @@ -240,6 +257,13 @@ public: std::shared_ptr heroDescription; std::shared_ptr rumor; + + std::shared_ptr inviteHero; + std::shared_ptr inviteHeroImage; + std::shared_ptr inviteHeroImageArea; + std::map inviteableHeroes; + CGHeroInstance* heroToInvite; + void addInvite(); CTavernWindow(const CGObjectInstance * TavernObj, const std::function & onWindowClosed); ~CTavernWindow(); From d6172a9c4c2fae17481533260d83c2690c21ea34 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 20 Jan 2024 13:42:37 +0100 Subject: [PATCH 05/12] disable buttons while loading (not changable) --- client/lobby/OptionsTabBase.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 218b1ccb4..19bc145b4 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -413,11 +413,20 @@ void OptionsTabBase::recreate() } if(auto buttonCheatAllowed = widget("buttonCheatAllowed")) + { buttonCheatAllowed->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.cheatsAllowed); + buttonCheatAllowed->setInputEnabled(SEL->screenType != ESelectionScreen::loadGame); + } if(auto buttonUnlimitedReplay = widget("buttonUnlimitedReplay")) + { buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay); + buttonUnlimitedReplay->setInputEnabled(SEL->screenType != ESelectionScreen::loadGame); + } if(auto buttonInviteHero = widget("buttonInviteHero")) + { buttonInviteHero->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.inviteHero); + buttonInviteHero->setInputEnabled(SEL->screenType != ESelectionScreen::loadGame); + } } From 883ea2349540c478f972881be35d14175625bc4b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 20 Jan 2024 17:54:15 +0100 Subject: [PATCH 06/12] moved to gameconfig --- client/lobby/OptionsTabBase.cpp | 12 ------------ client/windows/GUIClasses.cpp | 2 +- config/gameConfig.json | 4 +++- config/widgets/extraOptionsTab.json | 14 -------------- lib/ExtraOptionsInfo.cpp | 3 +-- lib/ExtraOptionsInfo.h | 2 -- lib/GameSettings.cpp | 1 + lib/GameSettings.h | 1 + server/processors/HeroPoolProcessor.cpp | 3 ++- 9 files changed, 9 insertions(+), 33 deletions(-) diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 19bc145b4..aa1b97c9a 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -110,12 +110,6 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) CSH->setExtraOptionsInfo(info); }); - addCallback("setInviteHero", [&](int index){ - ExtraOptionsInfo info = SEL->getStartInfo()->extraOptionsInfo; - info.inviteHero = index; - CSH->setExtraOptionsInfo(info); - }); - addCallback("setTurnTimerAccumulate", [&](int index){ TurnTimerInfo info = SEL->getStartInfo()->turnTimerInfo; info.accumulatingTurnTimer = index; @@ -423,10 +417,4 @@ void OptionsTabBase::recreate() buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay); buttonUnlimitedReplay->setInputEnabled(SEL->screenType != ESelectionScreen::loadGame); } - - if(auto buttonInviteHero = widget("buttonInviteHero")) - { - buttonInviteHero->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.inviteHero); - buttonInviteHero->setInputEnabled(SEL->screenType != ESelectionScreen::loadGame); - } } diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 9c9b12d8d..3a10c2870 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -519,7 +519,7 @@ void CTavernWindow::addInvite() { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - if(CSH->client->getStartInfo()->extraOptionsInfo.inviteHero) + if(VLC->settings()->getBoolean(EGameSettings::HEROES_INVITE)) { const auto & heroesPool = CSH->client->gameState()->heroesPool; for(auto & elem : heroesPool->unusedHeroesFromPool()) diff --git a/config/gameConfig.json b/config/gameConfig.json index 56260f4e2..24e3c4580 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -289,7 +289,9 @@ // Chances for a hero with default army to receive corresponding stack out of his predefined starting troops "startingStackChances": [ 100, 88, 25], // number of artifacts that can fit in a backpack. -1 is unlimited. - "backpackSize" : -1 + "backpackSize" : -1, + // if heroes are invitable in tavern + "invite" : false }, "towns": diff --git a/config/widgets/extraOptionsTab.json b/config/widgets/extraOptionsTab.json index 5f6635254..d8a333b9c 100644 --- a/config/widgets/extraOptionsTab.json +++ b/config/widgets/extraOptionsTab.json @@ -73,13 +73,6 @@ "callback" : "setUnlimitedReplay", "help" : "vcmi.optionsTab.unlimitedReplay", "selected" : true - }, - { - "name": "buttonInviteHero", - "image": "lobby/checkbox", - "callback" : "setInviteHero", - "help" : "vcmi.optionsTab.inviteHero", - "selected" : true } ] }, @@ -103,13 +96,6 @@ "alignment": "left", "color": "yellow", "text": "vcmi.optionsTab.unlimitedReplay.hover" - }, - { - "name": "labelInviteHero", - "font": "small", - "alignment": "left", - "color": "yellow", - "text": "vcmi.optionsTab.inviteHero.hover" } ] } diff --git a/lib/ExtraOptionsInfo.cpp b/lib/ExtraOptionsInfo.cpp index bd0efb124..2411608b7 100644 --- a/lib/ExtraOptionsInfo.cpp +++ b/lib/ExtraOptionsInfo.cpp @@ -15,8 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN bool ExtraOptionsInfo::operator == (const ExtraOptionsInfo & other) const { return cheatsAllowed == other.cheatsAllowed && - unlimitedReplay == other.unlimitedReplay && - inviteHero == other.inviteHero; + unlimitedReplay == other.unlimitedReplay; } VCMI_LIB_NAMESPACE_END diff --git a/lib/ExtraOptionsInfo.h b/lib/ExtraOptionsInfo.h index f433008c2..3b1e65d1b 100644 --- a/lib/ExtraOptionsInfo.h +++ b/lib/ExtraOptionsInfo.h @@ -16,7 +16,6 @@ struct DLL_LINKAGE ExtraOptionsInfo { bool cheatsAllowed = true; bool unlimitedReplay = false; - bool inviteHero = false; bool operator == (const ExtraOptionsInfo & other) const; @@ -25,7 +24,6 @@ struct DLL_LINKAGE ExtraOptionsInfo { h & cheatsAllowed; h & unlimitedReplay; - h & inviteHero; } }; diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index e3ef38929..248300fea 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -74,6 +74,7 @@ void GameSettings::load(const JsonNode & input) {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, + {EGameSettings::HEROES_INVITE, "heroes", "invite" }, {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, diff --git a/lib/GameSettings.h b/lib/GameSettings.h index 7241e9b92..a33581320 100644 --- a/lib/GameSettings.h +++ b/lib/GameSettings.h @@ -38,6 +38,7 @@ enum class EGameSettings HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, HEROES_STARTING_STACKS_CHANCES, HEROES_BACKPACK_CAP, + HEROES_INVITE, MARKETS_BLACK_MARKET_RESTOCK_PERIOD, BANKS_SHOW_GUARDS_COMPOSITION, MODULE_COMMANDERS, diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index ebf4b4edc..789775804 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -23,6 +23,7 @@ #include "../../lib/gameState/CGameState.h" #include "../../lib/gameState/TavernHeroesPool.h" #include "../../lib/gameState/TavernSlot.h" +#include "../../lib/GameSettings.h" HeroPoolProcessor::HeroPoolProcessor() : gameHandler(nullptr) @@ -114,7 +115,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe CGHeroInstance *newHero = pickHeroFor(needNativeHero, color); const auto & heroesPool = gameHandler->gameState()->heroesPool; - if(gameHandler->getStartInfo()->extraOptionsInfo.inviteHero && heroesPool->unusedHeroesFromPool().count(nextHero) && heroesPool->isHeroAvailableFor(nextHero, color)) + if(VLC->settings()->getBoolean(EGameSettings::HEROES_INVITE) && heroesPool->unusedHeroesFromPool().count(nextHero) && heroesPool->isHeroAvailableFor(nextHero, color)) newHero = heroesPool->unusedHeroesFromPool()[nextHero]; if (newHero) From b82a51d184aee4a6d93f40ce655e49bf49abc65a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 20 Jan 2024 17:58:54 +0100 Subject: [PATCH 07/12] remove unused translation --- Mods/vcmi/config/vcmi/english.json | 2 -- Mods/vcmi/config/vcmi/german.json | 2 -- 2 files changed, 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 482bd67c4..c9a383fba 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -319,10 +319,8 @@ "vcmi.optionsTab.cheatAllowed.hover" : "Allow cheats", "vcmi.optionsTab.unlimitedReplay.hover" : "Unlimited battle replay", - "vcmi.optionsTab.inviteHero.hover" : "Invite hero", "vcmi.optionsTab.cheatAllowed.help" : "{Allow cheats}\nAllows the inputs of cheats during the game.", "vcmi.optionsTab.unlimitedReplay.help" : "{Unlimited battle replay}\nNo limit of replaying battles.", - "vcmi.optionsTab.inviteHero.help" : "{Invite hero}\nPossibility to invite hero in the tavern.", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 268caf197..8a50c204b 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -319,10 +319,8 @@ "vcmi.optionsTab.cheatAllowed.hover" : "Cheats erlauben", "vcmi.optionsTab.unlimitedReplay.hover" : "Unbegrenzte Kampfwiederholung", - "vcmi.optionsTab.inviteHero.hover" : "Helden einladen", "vcmi.optionsTab.cheatAllowed.help" : "{Cheats erlauben}\nErlaubt die Eingabe von Cheats während des Spiels.", "vcmi.optionsTab.unlimitedReplay.help" : "{Unbegrenzte Kampfwiederholung}\nKämpfe lassen sich unbegrenzt wiederholen.", - "vcmi.optionsTab.inviteHero.help" : "{Helden einladen}\nMöglichkeit Helden in der Taverne einzuladen.", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Der Feind hat es geschafft, bis zum heutigen Tag zu überleben. Der Sieg gehört ihm!", From 0a4b5dbe100ac56db9073526d9ed48bcab8e9e51 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 21 Jan 2024 18:12:46 +0100 Subject: [PATCH 08/12] code review --- client/lobby/OptionsTabBase.cpp | 4 ++-- client/windows/GUIClasses.cpp | 32 ++++++++++++------------- config/gameConfig.json | 2 +- lib/GameSettings.cpp | 2 +- lib/GameSettings.h | 2 +- server/processors/HeroPoolProcessor.cpp | 13 +++++----- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index aa1b97c9a..a89943dc8 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -409,12 +409,12 @@ void OptionsTabBase::recreate() if(auto buttonCheatAllowed = widget("buttonCheatAllowed")) { buttonCheatAllowed->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.cheatsAllowed); - buttonCheatAllowed->setInputEnabled(SEL->screenType != ESelectionScreen::loadGame); + buttonCheatAllowed->block(SEL->screenType == ESelectionScreen::loadGame); } if(auto buttonUnlimitedReplay = widget("buttonUnlimitedReplay")) { buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay); - buttonUnlimitedReplay->setInputEnabled(SEL->screenType != ESelectionScreen::loadGame); + buttonUnlimitedReplay->block(SEL->screenType == ESelectionScreen::loadGame); } } diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 3a10c2870..962a1828b 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -519,25 +519,25 @@ void CTavernWindow::addInvite() { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - if(VLC->settings()->getBoolean(EGameSettings::HEROES_INVITE)) + if(!VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE)) + return; + + const auto & heroesPool = CSH->client->gameState()->heroesPool; + for(auto & elem : heroesPool->unusedHeroesFromPool()) { - const auto & heroesPool = CSH->client->gameState()->heroesPool; - for(auto & elem : heroesPool->unusedHeroesFromPool()) - { - bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, tavernObj->getOwner()); - if(heroAvailable) - inviteableHeroes[elem.first] = elem.second; - } + bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, tavernObj->getOwner()); + if(heroAvailable) + inviteableHeroes[elem.first] = elem.second; + } - if(inviteableHeroes.size() > 0) - { - if(!heroToInvite) - heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; + if(inviteableHeroes.size() > 0) + { + if(!heroToInvite) + heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; - inviteHero = std::make_shared(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero")); - inviteHeroImage = std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428); - inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [&](){ GH.windows().createAndPushWindow(inviteableHeroes, [&](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [&](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); - } + inviteHero = std::make_shared(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero")); + inviteHeroImage = std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428); + inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [&](){ GH.windows().createAndPushWindow(inviteableHeroes, [&](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [&](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); } } diff --git a/config/gameConfig.json b/config/gameConfig.json index 24e3c4580..b7d7bcd91 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -291,7 +291,7 @@ // number of artifacts that can fit in a backpack. -1 is unlimited. "backpackSize" : -1, // if heroes are invitable in tavern - "invite" : false + "tavernInvite" : false }, "towns": diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 248300fea..1996fb62b 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -74,7 +74,7 @@ void GameSettings::load(const JsonNode & input) {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, - {EGameSettings::HEROES_INVITE, "heroes", "invite" }, + {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, diff --git a/lib/GameSettings.h b/lib/GameSettings.h index a33581320..e1b76ef54 100644 --- a/lib/GameSettings.h +++ b/lib/GameSettings.h @@ -38,7 +38,7 @@ enum class EGameSettings HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, HEROES_STARTING_STACKS_CHANCES, HEROES_BACKPACK_CAP, - HEROES_INVITE, + HEROES_TAVERN_INVITE, MARKETS_BLACK_MARKET_RESTOCK_PERIOD, BANKS_SHOW_GUARDS_COMPOSITION, MODULE_COMMANDERS, diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 789775804..bdc2b21c1 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -112,11 +112,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe sah.slotID = slot; sah.replenishPoints = true; - CGHeroInstance *newHero = pickHeroFor(needNativeHero, color); - - const auto & heroesPool = gameHandler->gameState()->heroesPool; - if(VLC->settings()->getBoolean(EGameSettings::HEROES_INVITE) && heroesPool->unusedHeroesFromPool().count(nextHero) && heroesPool->isHeroAvailableFor(nextHero, color)) - newHero = heroesPool->unusedHeroesFromPool()[nextHero]; + CGHeroInstance *newHero = (nextHero == HeroTypeID::NONE) ? pickHeroFor(needNativeHero, color) : gameHandler->gameState()->heroesPool->unusedHeroesFromPool()[nextHero]; if (newHero) { @@ -155,6 +151,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy const PlayerState * playerState = gameHandler->getPlayerState(player); const CGObjectInstance * mapObject = gameHandler->getObj(objectID); const CGTownInstance * town = gameHandler->getTown(objectID); + const auto & heroesPool = gameHandler->gameState()->heroesPool; if (!mapObject && gameHandler->complain("Invalid map object!")) return false; @@ -171,6 +168,10 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy if (gameHandler->getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!")) return false; + if(VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE) && nextHero != HeroTypeID::NONE) + if(!heroesPool->unusedHeroesFromPool().count(nextHero) && !heroesPool->isHeroAvailableFor(nextHero, player) && gameHandler->complain("Cannot set next hero!")) + return false; + if(town) //tavern in town { if(gameHandler->getPlayerRelations(mapObject->tempOwner, player) == PlayerRelations::ENEMIES && gameHandler->complain("Can't buy hero in enemy town!")) @@ -197,7 +198,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy return false; } - auto recruitableHeroes = gameHandler->gameState()->heroesPool->getHeroesFor(player); + auto recruitableHeroes = heroesPool->getHeroesFor(player); const CGHeroInstance * recruitedHero = nullptr; From 9b2908d46457761443258c05de12143527a56994 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:19:49 +0100 Subject: [PATCH 09/12] Apply suggestions from code review Co-authored-by: Ivan Savenko --- client/windows/GUIClasses.cpp | 10 +++++----- server/processors/HeroPoolProcessor.cpp | 11 +++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 962a1828b..153edd8b4 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -463,8 +463,8 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func oldSelected = -1; - h1 = std::make_shared(selected, 0, 72, 299, h[0], [&]() { if(!recruit->isBlocked()) recruitb(); }); - h2 = std::make_shared(selected, 1, 162, 299, h[1], [&]() { if(!recruit->isBlocked()) recruitb(); }); + h1 = std::make_shared(selected, 0, 72, 299, h[0], [this]() { if(!recruit->isBlocked()) recruitb(); }); + h2 = std::make_shared(selected, 1, 162, 299, h[1], [this]() { if(!recruit->isBlocked()) recruitb(); }); title = std::make_shared(197, 32, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]); cost = std::make_shared(320, 328, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, std::to_string(GameConstants::HERO_GOLD_COST)); @@ -530,14 +530,14 @@ void CTavernWindow::addInvite() inviteableHeroes[elem.first] = elem.second; } - if(inviteableHeroes.size() > 0) + if(!inviteableHeroes.empty()) { if(!heroToInvite) heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; inviteHero = std::make_shared(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero")); inviteHeroImage = std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428); - inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [&](){ GH.windows().createAndPushWindow(inviteableHeroes, [&](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [&](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); + inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [&](){ GH.windows().createAndPushWindow(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); } } @@ -667,7 +667,7 @@ CTavernWindow::HeroSelector::HeroSelector(std::map for(auto & h : inviteableHeroes) { portraits.push_back(std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32)); - portraitAreas.push_back(std::make_shared(Rect(x * 48, y * 32, 48, 32), [&](){ close(); onChoose(inviteableHeroes[h.first]); }, [&](){ GH.windows().createAndPushWindow(std::make_shared(inviteableHeroes[h.first])); })); + portraitAreas.push_back(std::make_shared(Rect(x * 48, y * 32, 48, 32), [this](){ close(); onChoose(inviteableHeroes[h.first]); }, [this](){ GH.windows().createAndPushWindow(std::make_shared(inviteableHeroes[h.first])); })); if(x > 0 && x % 15 == 0) { diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index bdc2b21c1..65b7acd46 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -168,8 +168,15 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy if (gameHandler->getHeroCount(player, true) >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP) && gameHandler->complain("Cannot hire hero, too many heroes garrizoned and wandering already!")) return false; - if(VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE) && nextHero != HeroTypeID::NONE) - if(!heroesPool->unusedHeroesFromPool().count(nextHero) && !heroesPool->isHeroAvailableFor(nextHero, player) && gameHandler->complain("Cannot set next hero!")) + if (nextHero != HeroTypeID::NONE) // player attempts to invite next hero + { + if(!VLC->settings()->getBoolean(EGameSettings::HEROES_TAVERN_INVITE) && gameHandler->complain("Inviting heroes not allowed!")) + return false; + + if(!heroesPool->unusedHeroesFromPool().count(nextHero) && gameHandler->complain("Cannot invite specified hero!")) + return false; + + if(!heroesPool->isHeroAvailableFor(nextHero, player) && gameHandler->complain("Cannot invite specified hero!")) return false; if(town) //tavern in town From 7857673ca93feb67ba94a2e4dbda760369ebc895 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:22:15 +0100 Subject: [PATCH 10/12] missing bracket --- server/processors/HeroPoolProcessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 65b7acd46..d6bb4e104 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -178,6 +178,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy if(!heroesPool->isHeroAvailableFor(nextHero, player) && gameHandler->complain("Cannot invite specified hero!")) return false; + } if(town) //tavern in town { From 7d1553bc298ce87147faeac3166c9411962bf7b2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:56:35 +0100 Subject: [PATCH 11/12] fix build --- client/windows/GUIClasses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 153edd8b4..4298719cb 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -667,7 +667,7 @@ CTavernWindow::HeroSelector::HeroSelector(std::map for(auto & h : inviteableHeroes) { portraits.push_back(std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32)); - portraitAreas.push_back(std::make_shared(Rect(x * 48, y * 32, 48, 32), [this](){ close(); onChoose(inviteableHeroes[h.first]); }, [this](){ GH.windows().createAndPushWindow(std::make_shared(inviteableHeroes[h.first])); })); + portraitAreas.push_back(std::make_shared(Rect(x * 48, y * 32, 48, 32), [this, h](){ close(); onChoose(inviteableHeroes[h.first]); }, [this, h](){ GH.windows().createAndPushWindow(std::make_shared(inviteableHeroes[h.first])); })); if(x > 0 && x % 15 == 0) { From e434d5c0b7270c53f1f2a464b0887fd6f8545a3c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:35:30 +0100 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Ivan Savenko --- client/windows/GUIClasses.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 4298719cb..e77d1dcc7 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -537,7 +537,7 @@ void CTavernWindow::addInvite() inviteHero = std::make_shared(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero")); inviteHeroImage = std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428); - inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [&](){ GH.windows().createAndPushWindow(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); + inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [this](){ GH.windows().createAndPushWindow(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); } } @@ -663,7 +663,8 @@ CTavernWindow::HeroSelector::HeroSelector(std::map pos = Rect(0, 0, 16 * 48, (inviteableHeroes.size() / 16 + (inviteableHeroes.size() % 16 != 0)) * 32); background = std::make_shared(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h)); - int x = 0, y = 0; + int x = 0; + int y = 0; for(auto & h : inviteableHeroes) { portraits.push_back(std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[h.first]->imageIndex, 0, x * 48, y * 32));