From efcac3b9337e19e639b000f47b1efd2633c18031 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:16:41 +0200 Subject: [PATCH 01/18] settings --- config/gameConfig.json | 4 +++- config/schemas/gameSettings.json | 3 ++- lib/GameSettings.cpp | 1 + lib/IGameSettings.h | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/config/gameConfig.json b/config/gameConfig.json index 714309b2b..3f439f223 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -311,7 +311,9 @@ // How many new building can be built in a town per day "buildingsPerTurnCap" : 1, // Chances for a town with default buildings to receive corresponding dwelling level built in start - "startingDwellingChances": [100, 50] + "startingDwellingChances": [100, 50], + // Enable spell research in mage guild + "spellResearch": false }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 0eec56189..67ef54701 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -52,7 +52,8 @@ "additionalProperties" : false, "properties" : { "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" } + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 780c79f43..4525b89c7 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -101,6 +101,7 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, + {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 9f6a8a78b..c75726a49 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -79,6 +79,7 @@ enum class EGameSettings TEXTS_TERRAIN, TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, + TOWNS_SPELL_RESEARCH, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL From a9327b3fa3fe94e743f2a704db366df24d7416a4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 27 Sep 2024 22:47:22 +0200 Subject: [PATCH 02/18] netpacks --- CCallback.cpp | 6 ++++++ CCallback.h | 2 ++ client/Client.h | 1 + client/windows/CCastleInterface.cpp | 13 +++++++----- client/windows/CCastleInterface.h | 5 ++++- config/gameConfig.json | 2 +- lib/IGameCallback.h | 1 + lib/gameState/CGameState.cpp | 2 +- lib/networkPacks/NetPackVisitor.h | 2 ++ lib/networkPacks/NetPacksLib.cpp | 16 +++++++++++++++ lib/networkPacks/PacksForClient.h | 18 ++++++++++++++++ lib/networkPacks/PacksForServer.h | 18 ++++++++++++++++ lib/serializer/RegisterTypes.h | 2 ++ server/CGameHandler.cpp | 32 +++++++++++++++++++++++++++++ server/CGameHandler.h | 3 +++ server/NetPacksServer.cpp | 5 +++++ server/ServerNetPackVisitors.h | 1 + 17 files changed, 121 insertions(+), 8 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index 8d2709f3d..e5953394e 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -249,6 +249,12 @@ int CBattleCallback::sendRequest(const CPackForServer * request) return requestID; } +void CCallback::spellResearch( const CGTownInstance *town ) +{ + SpellResearch pack(town->id); + sendRequest(&pack); +} + void CCallback::swapGarrisonHero( const CGTownInstance *town ) { if(town->tempOwner == *player || (town->garrisonHero && town->garrisonHero->tempOwner == *player )) diff --git a/CCallback.h b/CCallback.h index a934113ce..3e9778387 100644 --- a/CCallback.h +++ b/CCallback.h @@ -78,6 +78,7 @@ public: virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0; virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0; virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made + virtual void spellResearch(const CGTownInstance *town)=0; virtual void swapGarrisonHero(const CGTownInstance *town)=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 @@ -187,6 +188,7 @@ public: bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; void endTurn() override; + void spellResearch(const CGTownInstance *town) override; void swapGarrisonHero(const CGTownInstance *town) override; void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; diff --git a/client/Client.h b/client/Client.h index d63f70578..cfb00e26c 100644 --- a/client/Client.h +++ b/client/Client.h @@ -159,6 +159,7 @@ public: friend class CBattleCallback; //handling players actions void changeSpells(const CGHeroInstance * hero, bool give, const std::set & spells) override {}; + void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {}; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}; void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {}; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {}; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index e8086f830..56211c500 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1966,7 +1966,7 @@ void CFortScreen::RecruitArea::showPopupWindow(const Point & cursorPosition) } CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & imagename) - : CWindowObject(BORDERED, imagename) + : CWindowObject(BORDERED, imagename), town(owner->town) { OBJECT_CONSTRUCTION; @@ -1997,15 +1997,15 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i for(size_t j=0; jtown->mageGuildLevel() && owner->town->spells[i].size()>j) - spells.push_back(std::make_shared(positions[i][j], owner->town->spells[i][j].toSpell())); + spells.push_back(std::make_shared(positions[i][j], owner->town->spells[i][j].toSpell(), town)); else emptyScrolls.push_back(std::make_shared(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y)); } } } -CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell) - : spell(Spell) +CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, const CGTownInstance *town) + : spell(Spell), town(town) { OBJECT_CONSTRUCTION; @@ -2017,7 +2017,10 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell) void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { - LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); + if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) + LOCPLINT->cb->spellResearch(town); + else + LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); } void CMageGuildScreen::Scroll::showPopupWindow(const Point & cursorPosition) diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 3b8fb6037..3091d48ab 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -379,9 +379,10 @@ class CMageGuildScreen : public CStatusbarWindow { const CSpell * spell; std::shared_ptr image; + const CGTownInstance *town; public: - Scroll(Point position, const CSpell *Spell); + Scroll(Point position, const CSpell *Spell, const CGTownInstance *town); void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; void hover(bool on) override; @@ -393,6 +394,8 @@ class CMageGuildScreen : public CStatusbarWindow std::shared_ptr resdatabar; + const CGTownInstance *town; + public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); }; diff --git a/config/gameConfig.json b/config/gameConfig.json index 3f439f223..9e4e5a4a3 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -313,7 +313,7 @@ // Chances for a town with default buildings to receive corresponding dwelling level built in start "startingDwellingChances": [100, 50], // Enable spell research in mage guild - "spellResearch": false + "spellResearch": true }, "combat": diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index a9cc7b655..9e1e8c1ca 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -94,6 +94,7 @@ public: virtual void showInfoDialog(InfoWindow * iw) = 0; virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells)=0; + virtual void setTownSpells(const CGTownInstance * town, int level, const std::vector spells)=0; virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0; diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 31bd4e2de..fed910807 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -915,7 +915,7 @@ void CGameState::initTowns() vti->spells[s->getLevel()-1].push_back(s->id); vti->possibleSpells -= s->id; } - vti->possibleSpells.clear(); + vti->possibleSpells.clear(); //SR } } diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 475b9b5db..4cfb0e402 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -42,6 +42,7 @@ public: virtual void visitSetSecSkill(SetSecSkill & pack) {} virtual void visitHeroVisitCastle(HeroVisitCastle & pack) {} virtual void visitChangeSpells(ChangeSpells & pack) {} + virtual void visitSetTownSpells(SetTownSpells & pack) {} virtual void visitSetMana(SetMana & pack) {} virtual void visitSetMovePoints(SetMovePoints & pack) {} virtual void visitFoWChange(FoWChange & pack) {} @@ -128,6 +129,7 @@ public: virtual void visitBuildStructure(BuildStructure & pack) {} virtual void visitVisitTownBuilding(VisitTownBuilding & pack) {} virtual void visitRazeStructure(RazeStructure & pack) {} + virtual void visitSpellResearch(SpellResearch & pack) {} virtual void visitRecruitCreatures(RecruitCreatures & pack) {} virtual void visitUpgradeCreature(UpgradeCreature & pack) {} virtual void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 43309a8c5..a2cb60448 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -162,6 +162,10 @@ void ChangeSpells::visitTyped(ICPackVisitor & visitor) visitor.visitChangeSpells(*this); } +void SetTownSpells::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSetTownSpells(*this); +} void SetMana::visitTyped(ICPackVisitor & visitor) { visitor.visitSetMana(*this); @@ -592,6 +596,11 @@ void RazeStructure::visitTyped(ICPackVisitor & visitor) visitor.visitRazeStructure(*this); } +void SpellResearch::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSpellResearch(*this); +} + void RecruitCreatures::visitTyped(ICPackVisitor & visitor) { visitor.visitRecruitCreatures(*this); @@ -930,6 +939,13 @@ void ChangeSpells::applyGs(CGameState *gs) hero->removeSpellFromSpellbook(sid); } +void SetTownSpells::applyGs(CGameState *gs) +{ + CGTownInstance *town = gs->getTown(tid); + + town->spells[level] = spells; +} + void SetMana::applyGs(CGameState *gs) { CGHeroInstance * hero = gs->getHero(hid); diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 5a81ec806..ae1d23167 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -288,6 +288,24 @@ struct DLL_LINKAGE ChangeSpells : public CPackForClient } }; +struct DLL_LINKAGE SetTownSpells : public CPackForClient +{ + void applyGs(CGameState * gs) override; + + void visitTyped(ICPackVisitor & visitor) override; + + ui8 level = 0; + ObjectInstanceID tid; + std::vector spells; + + template void serialize(Handler & h) + { + h & level; + h & tid; + h & spells; + } +}; + struct DLL_LINKAGE SetMana : public CPackForClient { void applyGs(CGameState * gs) override; diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index a909cf652..b35254f23 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -306,6 +306,24 @@ struct DLL_LINKAGE RazeStructure : public BuildStructure void visitTyped(ICPackVisitor & visitor) override; }; +struct DLL_LINKAGE SpellResearch : public CPackForServer +{ + SpellResearch() = default; + SpellResearch(const ObjectInstanceID & TID) + : tid(TID) + { + } + ObjectInstanceID tid; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & static_cast(*this); + h & tid; + } +}; + struct DLL_LINKAGE RecruitCreatures : public CPackForServer { RecruitCreatures() = default; diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 94663357e..6fcbc5e20 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -288,6 +288,8 @@ void registerTypes(Serializer &s) s.template registerType(238); s.template registerType(239); s.template registerType(240); + s.template registerType(241); + s.template registerType(242); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 35dde1615..c14248b54 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1235,6 +1235,15 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st sendAndApply(&cs); } +void CGameHandler::setTownSpells(const CGTownInstance * town, int level, const std::vector spells) +{ + SetTownSpells cs; + cs.tid = town->id; + cs.spells = spells; + cs.level = level; + sendAndApply(&cs); +} + void CGameHandler::giveHeroBonus(GiveBonus * bonus) { sendAndApply(bonus); @@ -2233,6 +2242,29 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) return true; } +bool CGameHandler::spellResearch(ObjectInstanceID tid) +{ + if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) + return false; + + CGTownInstance *t = gs->getTown(tid); + auto spells = t->spells.at(1); + auto spell = SpellID(SpellID::FLY); + spells.at(0) = spell; + setTownSpells(t, 1, spells); + spellResearchFinished(tid); + return true; +} + +void CGameHandler::spellResearchFinished(ObjectInstanceID tid) +{ + const CGTownInstance * t = getTown(tid); + if(t->visitingHero) + giveSpells(t, t->visitingHero); + if(t->garrisonHero) + giveSpells(t, t->garrisonHero); +} + bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl, PlayerColor player) { const CGDwelling * dwelling = dynamic_cast(getObj(objid)); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index cdb194377..b1540b636 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -107,6 +107,7 @@ public: //from IGameCallback //do sth void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override; + void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override; void giveExperience(const CGHeroInstance * hero, TExpType val) override; @@ -218,6 +219,8 @@ public: bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); + bool spellResearch(ObjectInstanceID tid); + void spellResearchFinished(ObjectInstanceID tid); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 2a7b493d4..90e8f2062 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -138,6 +138,11 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack) result = gh.buildStructure(pack.tid, pack.bid); } +void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) +{ + result = gh.spellResearch(pack.tid); +} + void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack) { gh.throwIfWrongOwner(&pack, pack.tid); diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index 34593c1da..ba94157cb 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -41,6 +41,7 @@ public: void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) override; void visitDisbandCreature(DisbandCreature & pack) override; void visitBuildStructure(BuildStructure & pack) override; + void visitSpellResearch(SpellResearch & pack) override; void visitVisitTownBuilding(VisitTownBuilding & pack) override; void visitRecruitCreatures(RecruitCreatures & pack) override; void visitUpgradeCreature(UpgradeCreature & pack) override; From 857b2e9a352ae8a4ce54213a03c9762d19e92987 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:52:33 +0200 Subject: [PATCH 03/18] spell replacement works --- client/ClientNetPackVisitors.h | 1 + client/NetPacksClient.cpp | 7 +++++++ client/windows/CCastleInterface.cpp | 28 +++++++++++++++++++++------- client/windows/CCastleInterface.h | 7 ++++--- server/CGameHandler.cpp | 12 ++++-------- server/CGameHandler.h | 1 - 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/client/ClientNetPackVisitors.h b/client/ClientNetPackVisitors.h index 82cb72d53..7b67d4b72 100644 --- a/client/ClientNetPackVisitors.h +++ b/client/ClientNetPackVisitors.h @@ -37,6 +37,7 @@ public: void visitHeroVisitCastle(HeroVisitCastle & pack) override; void visitSetMana(SetMana & pack) override; void visitSetMovePoints(SetMovePoints & pack) override; + void visitSetTownSpells(SetTownSpells & pack) override; void visitFoWChange(FoWChange & pack) override; void visitChangeStackCount(ChangeStackCount & pack) override; void visitSetStackType(SetStackType & pack) override; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index cda16c4b3..e26516078 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -14,6 +14,7 @@ #include "CPlayerInterface.h" #include "CGameInfo.h" #include "windows/GUIClasses.h" +#include "windows/CCastleInterface.h" #include "mapView/mapHandler.h" #include "adventureMap/AdventureMapInterface.h" #include "adventureMap/CInGameConsole.h" @@ -172,6 +173,12 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h); } +void ApplyClientNetPackVisitor::visitSetTownSpells(SetTownSpells & pack) +{ + for(const auto & win : GH.windows().findWindows()) + win->update(); +} + void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack) { for(auto &i : cl.playerint) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 56211c500..7ee8dfeb1 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1966,7 +1966,7 @@ void CFortScreen::RecruitArea::showPopupWindow(const Point & cursorPosition) } CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & imagename) - : CWindowObject(BORDERED, imagename), town(owner->town) + : CWindowObject(BORDERED, imagename), townId(owner->town->id) { OBJECT_CONSTRUCTION; @@ -1982,6 +1982,12 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN); + update(); +} + +void CMageGuildScreen::update() +{ + OBJECT_CONSTRUCTION; static const std::vector > positions = { {Point(222,445), Point(312,445), Point(402,445), Point(520,445), Point(610,445), Point(700,445)}, @@ -1991,21 +1997,28 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i {Point(491,325), Point(591,325)} }; - for(size_t i=0; itown->town->mageLevel; i++) + spells.clear(); + emptyScrolls.clear(); + + const CGTownInstance * town = LOCPLINT->cb->getTown(townId); + + for(size_t i=0; itown->mageLevel; i++) { - size_t spellCount = owner->town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? + size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? for(size_t j=0; jtown->mageGuildLevel() && owner->town->spells[i].size()>j) - spells.push_back(std::make_shared(positions[i][j], owner->town->spells[i][j].toSpell(), town)); + if(imageGuildLevel() && town->spells[i].size()>j) + spells.push_back(std::make_shared(positions[i][j], town->spells[i][j].toSpell(), townId)); else emptyScrolls.push_back(std::make_shared(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y)); } } + + redraw(); } -CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, const CGTownInstance *town) - : spell(Spell), town(town) +CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId) + : spell(Spell), townId(townId) { OBJECT_CONSTRUCTION; @@ -2017,6 +2030,7 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, const CGTo void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { + const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) LOCPLINT->cb->spellResearch(town); else diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 3091d48ab..a0d338555 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -379,10 +379,10 @@ class CMageGuildScreen : public CStatusbarWindow { const CSpell * spell; std::shared_ptr image; - const CGTownInstance *town; + ObjectInstanceID townId; public: - Scroll(Point position, const CSpell *Spell, const CGTownInstance *town); + Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId); void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; void hover(bool on) override; @@ -394,10 +394,11 @@ class CMageGuildScreen : public CStatusbarWindow std::shared_ptr resdatabar; - const CGTownInstance *town; + ObjectInstanceID townId; public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); + void update(); }; /// The blacksmith window where you can buy available in town war machine diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c14248b54..4c28225a9 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2248,21 +2248,17 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid) return false; CGTownInstance *t = gs->getTown(tid); - auto spells = t->spells.at(1); + auto spells = t->spells.at(0); auto spell = SpellID(SpellID::FLY); spells.at(0) = spell; - setTownSpells(t, 1, spells); - spellResearchFinished(tid); - return true; -} + setTownSpells(t, 0, spells); -void CGameHandler::spellResearchFinished(ObjectInstanceID tid) -{ - const CGTownInstance * t = getTown(tid); if(t->visitingHero) giveSpells(t, t->visitingHero); if(t->garrisonHero) giveSpells(t, t->garrisonHero); + + return true; } bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl, PlayerColor player) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index b1540b636..e446aec7c 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -220,7 +220,6 @@ public: bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); bool spellResearch(ObjectInstanceID tid); - void spellResearchFinished(ObjectInstanceID tid); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); From 5b2aa4dc717b2a530f65d99989c9997884c9f5aa Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 00:34:25 +0200 Subject: [PATCH 04/18] swapping spells --- CCallback.cpp | 4 ++-- CCallback.h | 4 ++-- client/windows/CCastleInterface.cpp | 2 +- lib/networkPacks/PacksForServer.h | 6 ++++-- server/CGameHandler.cpp | 22 +++++++++++++++++----- server/CGameHandler.h | 2 +- server/NetPacksServer.cpp | 2 +- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index e5953394e..a126475d3 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -249,9 +249,9 @@ int CBattleCallback::sendRequest(const CPackForServer * request) return requestID; } -void CCallback::spellResearch( const CGTownInstance *town ) +void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot ) { - SpellResearch pack(town->id); + SpellResearch pack(town->id, spellAtSlot); sendRequest(&pack); } diff --git a/CCallback.h b/CCallback.h index 3e9778387..74f9b0fda 100644 --- a/CCallback.h +++ b/CCallback.h @@ -78,7 +78,7 @@ public: virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0; virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0; virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made - virtual void spellResearch(const CGTownInstance *town)=0; + virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot)=0; virtual void swapGarrisonHero(const CGTownInstance *town)=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 @@ -188,7 +188,7 @@ public: bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; void endTurn() override; - void spellResearch(const CGTownInstance *town) override; + void spellResearch(const CGTownInstance *town, SpellID spellAtSlot) override; void swapGarrisonHero(const CGTownInstance *town) override; void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 7ee8dfeb1..457871a3a 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2032,7 +2032,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) - LOCPLINT->cb->spellResearch(town); + LOCPLINT->cb->spellResearch(town, spell->id); else LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); } diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index b35254f23..059205973 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -309,11 +309,12 @@ struct DLL_LINKAGE RazeStructure : public BuildStructure struct DLL_LINKAGE SpellResearch : public CPackForServer { SpellResearch() = default; - SpellResearch(const ObjectInstanceID & TID) - : tid(TID) + SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot) + : tid(TID), spellAtSlot(spellAtSlot) { } ObjectInstanceID tid; + SpellID spellAtSlot; void visitTyped(ICPackVisitor & visitor) override; @@ -321,6 +322,7 @@ struct DLL_LINKAGE SpellResearch : public CPackForServer { h & static_cast(*this); h & tid; + h & spellAtSlot; } }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4c28225a9..607fdf620 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2242,16 +2242,28 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) return true; } -bool CGameHandler::spellResearch(ObjectInstanceID tid) +bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) { if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) return false; CGTownInstance *t = gs->getTown(tid); - auto spells = t->spells.at(0); - auto spell = SpellID(SpellID::FLY); - spells.at(0) = spell; - setTownSpells(t, 0, spells); + + int level = -1; + for(int i = 0; i < t->spells.size(); i++) + if(vstd::find_pos(t->spells[i], spellAtSlot) != -1) + level = i; + + if(level == -1 && complain("Spell for replacement not found!")) + return false; + + auto spells = t->spells.at(level); + + std::swap(spells.at(t->spellsAtLevel(level, false)), spells.at(vstd::find_pos(spells, spellAtSlot))); + auto it = spells.begin() + t->spellsAtLevel(level, false); + std::rotate(it, it + 1, spells.end()); // move to end + + setTownSpells(t, level, spells); if(t->visitingHero) giveSpells(t, t->visitingHero); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index e446aec7c..fabba227e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -219,7 +219,7 @@ public: bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); - bool spellResearch(ObjectInstanceID tid); + bool spellResearch(ObjectInstanceID tid, SpellID spellAtSlot); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 90e8f2062..b8f71167b 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -140,7 +140,7 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack) void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) { - result = gh.spellResearch(pack.tid); + result = gh.spellResearch(pack.tid, pack.spellAtSlot); } void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack) From 7707adc44f29c802b7a62e36d461261b75ef5fb2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:18:10 +0200 Subject: [PATCH 05/18] checks on server --- lib/mapObjects/CGTownInstance.cpp | 3 ++- lib/mapObjects/CGTownInstance.h | 4 ++++ lib/serializer/ESerializationVersion.h | 3 ++- server/CGameHandler.cpp | 18 ++++++++++++++++++ server/NetPacksServer.cpp | 3 +++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 2c040da6b..500ce19b0 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -268,7 +268,8 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): built(0), destroyed(0), identifier(0), - alignmentToPlayer(PlayerColor::NEUTRAL) + alignmentToPlayer(PlayerColor::NEUTRAL), + lastSpellResearchDay(0) { this->setNodeType(CBonusSystemNode::TOWN); } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fab98714e..e14802fd4 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -73,6 +73,7 @@ public: std::vector > spells; //spells[level] -> vector of spells, first will be available in guild std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); + int lastSpellResearchDay; ////////////////////////////////////////////////////////////////////////// template void serialize(Handler &h) @@ -93,6 +94,9 @@ public: h & spells; h & events; + if (h.version >= Handler::Version::SPELL_RESEARCH) + h & lastSpellResearchDay; + if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS) { h & rewardableBuildings; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index dd0deb6b0..b4be54223 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -61,6 +61,7 @@ enum class ESerializationVersion : int32_t CAMPAIGN_OUTRO_SUPPORT, // 862 - support for campaign outro video REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects REGION_LABEL, // 864 - labels for campaign regions + SPELL_RESEARCH, // 865 - spell research - CURRENT = REGION_LABEL + CURRENT = SPELL_RESEARCH }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 607fdf620..fb9625012 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2256,6 +2256,24 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) if(level == -1 && complain("Spell for replacement not found!")) return false; + + int daysSinceLastResearch = gs->getDate(Date::DAY) - t->lastSpellResearchDay; + if(!daysSinceLastResearch && complain("Already researched today!")) + return false; + + TResources cost; + cost[EGameResID::GOLD] = 1000; + cost[EGameResID::MERCURY] = (level + 1) * 2; + cost[EGameResID::SULFUR] = (level + 1) * 2; + cost[EGameResID::CRYSTAL] = (level + 1) * 2; + cost[EGameResID::GEMS] = (level + 1) * 2; + + if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) + return false; + + giveResources(t->getOwner(), -cost); + + t->lastSpellResearchDay = gs->getDate(Date::DAY); auto spells = t->spells.at(level); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index b8f71167b..b4227f1ea 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -140,6 +140,9 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack) void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) { + gh.throwIfWrongOwner(&pack, pack.tid); + gh.throwIfPlayerNotActive(&pack); + result = gh.spellResearch(pack.tid, pack.spellAtSlot); } From 3559f9f9236c54b54973b5ba9873a015852dfa2f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:47:32 +0200 Subject: [PATCH 06/18] HMI for spell research --- Mods/vcmi/config/vcmi/english.json | 4 ++++ Mods/vcmi/config/vcmi/german.json | 4 ++++ client/windows/CCastleInterface.cpp | 32 ++++++++++++++++++++++++++++- config/gameConfig.json | 2 +- lib/gameState/CGameState.cpp | 2 +- lib/networkPacks/NetPacksLib.cpp | 1 + server/CGameHandler.cpp | 2 -- 7 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b62ee25d2..692b19b46 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -59,6 +59,10 @@ "vcmi.spellBook.search" : "search...", + "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", + "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", + "vcmi.spellResearch.pay" : "Would you like to research a new spell and replace this?", + "vcmi.mainMenu.serverConnecting" : "Connecting...", "vcmi.mainMenu.serverAddressEnter" : "Enter address:", "vcmi.mainMenu.serverConnectionFailed" : "Failed to connect", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index efe786220..6ef4f4353 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -58,6 +58,10 @@ "vcmi.spellBook.search" : "suchen...", + "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", + "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", + "vcmi.spellResearch.pay" : "Möchtet Ihr einen neuen Zauberspruch erforschen und diesen ersetzen?", + "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", "vcmi.mainMenu.serverConnectionFailed" : "Verbindung fehlgeschlagen", diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 457871a3a..045e4fb3a 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2032,7 +2032,37 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) - LOCPLINT->cb->spellResearch(town, spell->id); + { + int daysSinceLastResearch = LOCPLINT->cb->getDate(Date::DAY) - town->lastSpellResearchDay; + if(!daysSinceLastResearch) + { + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); + return; + } + + int level = -1; + for(int i = 0; i < town->spells.size(); i++) + if(vstd::find_pos(town->spells[i], spell->id) != -1) + level = i; + + TResources cost; + cost[EGameResID::GOLD] = 1000; + cost[EGameResID::MERCURY] = (level + 1) * 2; + cost[EGameResID::SULFUR] = (level + 1) * 2; + cost[EGameResID::CRYSTAL] = (level + 1) * 2; + cost[EGameResID::GEMS] = (level + 1) * 2; + + std::vector> resComps; + for(TResources::nziterator i(cost); i.valid(); i++) + { + resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal)); + } + + if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) + LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id); }, nullptr, resComps); + else + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); + } else LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); } diff --git a/config/gameConfig.json b/config/gameConfig.json index 9e4e5a4a3..3f439f223 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -313,7 +313,7 @@ // Chances for a town with default buildings to receive corresponding dwelling level built in start "startingDwellingChances": [100, 50], // Enable spell research in mage guild - "spellResearch": true + "spellResearch": false }, "combat": diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index fed910807..31bd4e2de 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -915,7 +915,7 @@ void CGameState::initTowns() vti->spells[s->getLevel()-1].push_back(s->id); vti->possibleSpells -= s->id; } - vti->possibleSpells.clear(); //SR + vti->possibleSpells.clear(); } } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index a2cb60448..89207fa1c 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -944,6 +944,7 @@ void SetTownSpells::applyGs(CGameState *gs) CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; + town->lastSpellResearchDay = gs->getDate(Date::DAY); } void SetMana::applyGs(CGameState *gs) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index fb9625012..6620f0c74 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2273,8 +2273,6 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) giveResources(t->getOwner(), -cost); - t->lastSpellResearchDay = gs->getDate(Date::DAY); - auto spells = t->spells.at(level); std::swap(spells.at(t->spellsAtLevel(level, false)), spells.at(vstd::find_pos(spells, spellAtSlot))); From afb90c076d4a60decd2ac67bea592374b132509f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 02:34:18 +0200 Subject: [PATCH 07/18] better UI --- Mods/vcmi/config/vcmi/english.json | 2 +- Mods/vcmi/config/vcmi/german.json | 2 +- client/widgets/CComponent.cpp | 4 +++- client/widgets/CComponent.h | 1 + client/windows/CCastleInterface.cpp | 4 +++- test/mock/mock_IGameCallback.h | 1 + 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 692b19b46..cc936dbf9 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -61,7 +61,7 @@ "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", - "vcmi.spellResearch.pay" : "Would you like to research a new spell and replace this?", + "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one?", "vcmi.mainMenu.serverConnecting" : "Connecting...", "vcmi.mainMenu.serverAddressEnter" : "Enter address:", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 6ef4f4353..26deaf5c0 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -60,7 +60,7 @@ "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", - "vcmi.spellResearch.pay" : "Möchtet Ihr einen neuen Zauberspruch erforschen und diesen ersetzen?", + "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen?", "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 2ece8d515..f02ba67b9 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -70,6 +70,7 @@ void CComponent::init(ComponentType Type, ComponentSubType Subtype, std::optiona customSubtitle = ValText; size = imageSize; font = fnt; + newLine = false; assert(size < sizeInvalid); @@ -471,7 +472,8 @@ void CComponentBox::placeComponents(bool selectable) //start next row if ((pos.w != 0 && rows.back().width + comp->pos.w + distance > pos.w) // row is full - || rows.back().comps >= componentsInRow) + || rows.back().comps >= componentsInRow + || (prevComp && prevComp->newLine)) { prevComp = nullptr; rows.push_back (RowData (0,0,0)); diff --git a/client/widgets/CComponent.h b/client/widgets/CComponent.h index f4d360460..d4c1acc3f 100644 --- a/client/widgets/CComponent.h +++ b/client/widgets/CComponent.h @@ -52,6 +52,7 @@ public: std::string customSubtitle; ESize size; //component size. EFonts font; //Font size of label + bool newLine; //Line break after component std::string getDescription() const; std::string getSubtitle() const; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 045e4fb3a..6a5ce592f 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2053,9 +2053,11 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) cost[EGameResID::GEMS] = (level + 1) * 2; std::vector> resComps; + resComps.push_back(std::make_shared(ComponentType::SPELL_SCROLL, town->spells[level].at(town->spellsAtLevel(level, false)))); + resComps.back()->newLine = true; for(TResources::nziterator i(cost); i.valid(); i++) { - resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal)); + resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium)); } if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 1f2456a2f..1cec76e76 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -44,6 +44,7 @@ public: void showInfoDialog(InfoWindow * iw) override {} void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override {} + void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {} bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;} void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {} void setOwner(const CGObjectInstance * objid, PlayerColor owner) override {} From 5bb29732d0e2a39175de0ee2b19b9ffbe8103cad Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:53:57 +0200 Subject: [PATCH 08/18] spell description, not spell roll --- client/windows/CCastleInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 6a5ce592f..8fd68dbe2 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2053,7 +2053,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) cost[EGameResID::GEMS] = (level + 1) * 2; std::vector> resComps; - resComps.push_back(std::make_shared(ComponentType::SPELL_SCROLL, town->spells[level].at(town->spellsAtLevel(level, false)))); + resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); resComps.back()->newLine = true; for(TResources::nziterator i(cost); i.valid(); i++) { From 2052a2603175951477b586745433a0b5b4b17981 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 14:25:11 +0200 Subject: [PATCH 09/18] code review --- client/NetPacksClient.cpp | 2 +- client/windows/CCastleInterface.cpp | 15 ++++++--------- client/windows/CCastleInterface.h | 2 +- config/gameConfig.json | 6 +++++- config/schemas/gameSettings.json | 8 +++++--- lib/GameSettings.cpp | 2 ++ lib/IGameSettings.h | 2 ++ lib/mapObjects/CGTownInstance.cpp | 3 ++- lib/mapObjects/CGTownInstance.h | 1 + lib/mapping/MapFormatH3M.cpp | 5 +---- server/CGameHandler.cpp | 15 ++++++--------- 11 files changed, 32 insertions(+), 29 deletions(-) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index e26516078..1ae453d63 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -176,7 +176,7 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) void ApplyClientNetPackVisitor::visitSetTownSpells(SetTownSpells & pack) { for(const auto & win : GH.windows().findWindows()) - win->update(); + win->updateSpells(); } void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 8fd68dbe2..b0751e5a9 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1982,10 +1982,10 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN); - update(); + updateSpells(); } -void CMageGuildScreen::update() +void CMageGuildScreen::updateSpells() { OBJECT_CONSTRUCTION; static const std::vector > positions = @@ -2031,7 +2031,7 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInst void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { const CGTownInstance * town = LOCPLINT->cb->getTown(townId); - if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) + if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && town->spellResearchAllowed) { int daysSinceLastResearch = LOCPLINT->cb->getDate(Date::DAY) - town->lastSpellResearchDay; if(!daysSinceLastResearch) @@ -2045,12 +2045,9 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) if(vstd::find_pos(town->spells[i], spell->id) != -1) level = i; - TResources cost; - cost[EGameResID::GOLD] = 1000; - cost[EGameResID::MERCURY] = (level + 1) * 2; - cost[EGameResID::SULFUR] = (level + 1) * 2; - cost[EGameResID::CRYSTAL] = (level + 1) * 2; - cost[EGameResID::GEMS] = (level + 1) * 2; + auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); + auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); + auto cost = costBase + costPerLevel * (level + 1); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index a0d338555..b5fd3d7ab 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -398,7 +398,7 @@ class CMageGuildScreen : public CStatusbarWindow public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); - void update(); + void updateSpells(); }; /// The blacksmith window where you can buy available in town war machine diff --git a/config/gameConfig.json b/config/gameConfig.json index 3f439f223..ae79b22ba 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -313,7 +313,11 @@ // Chances for a town with default buildings to receive corresponding dwelling level built in start "startingDwellingChances": [100, 50], // Enable spell research in mage guild - "spellResearch": false + "spellResearch": false, + // Base cost for an spell research + "spellResearchCostBase": { "gold": 1000 }, + // Costs depends on level for an spell research + "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 } }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 67ef54701..e98bf8a0e 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -51,9 +51,11 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" }, - "spellResearch" : { "type" : "boolean" } + "buildingsPerTurnCap" : { "type" : "number" }, + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" }, + "spellResearchCostBase" : { "type" : "object" }, + "spellResearchCostPerLevel" : { "type" : "object" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 4525b89c7..2a6077928 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -102,6 +102,8 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index c75726a49..5b3b81a9d 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -80,6 +80,8 @@ enum class EGameSettings TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, TOWNS_SPELL_RESEARCH, + TOWNS_SPELL_RESEARCH_COST_BASE, + TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 500ce19b0..c8ccb57f2 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -269,7 +269,8 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): destroyed(0), identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), - lastSpellResearchDay(0) + lastSpellResearchDay(0), + spellResearchAllowed(true) { this->setNodeType(CBonusSystemNode::TOWN); } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index e14802fd4..204bdb51e 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -74,6 +74,7 @@ public: std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); int lastSpellResearchDay; + bool spellResearchAllowed; ////////////////////////////////////////////////////////////////////////// template void serialize(Handler &h) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 32e0057ac..4d46aba86 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2235,10 +2235,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt } if(features.levelHOTA1) - { - // TODO: HOTA support - [[maybe_unused]] bool spellResearchAvailable = reader->readBool(); - } + object->spellResearchAllowed = reader->readBool(); // Read castle events uint32_t eventsCount = reader->readUInt32(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 6620f0c74..47233d034 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2244,11 +2244,11 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) { - if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) - return false; - CGTownInstance *t = gs->getTown(tid); + if(!(getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && t->spellResearchAllowed) && complain("Spell research not allowed!")) + return false; + int level = -1; for(int i = 0; i < t->spells.size(); i++) if(vstd::find_pos(t->spells[i], spellAtSlot) != -1) @@ -2261,12 +2261,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) if(!daysSinceLastResearch && complain("Already researched today!")) return false; - TResources cost; - cost[EGameResID::GOLD] = 1000; - cost[EGameResID::MERCURY] = (level + 1) * 2; - cost[EGameResID::SULFUR] = (level + 1) * 2; - cost[EGameResID::CRYSTAL] = (level + 1) * 2; - cost[EGameResID::GEMS] = (level + 1) * 2; + auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); + auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); + auto cost = costBase + costPerLevel * (level + 1); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; From d929bfb9d1fa09e457d7622c3327eba7962f03a5 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 15:05:13 +0200 Subject: [PATCH 10/18] rename; introduce factor --- CCallback.cpp | 4 +- CCallback.h | 4 +- client/Client.h | 2 +- client/ClientNetPackVisitors.h | 2 +- client/NetPacksClient.cpp | 2 +- client/windows/CCastleInterface.cpp | 4 +- config/gameConfig.json | 6 +- config/schemas/gameSettings.json | 11 +-- lib/GameSettings.cpp | 135 ++++++++++++++-------------- lib/IGameCallback.h | 2 +- lib/IGameSettings.h | 1 + lib/mapObjects/CGTownInstance.cpp | 1 + lib/mapObjects/CGTownInstance.h | 5 ++ lib/networkPacks/NetPackVisitor.h | 2 +- lib/networkPacks/NetPacksLib.cpp | 8 +- lib/networkPacks/PacksForClient.h | 4 +- lib/networkPacks/PacksForServer.h | 4 +- lib/serializer/RegisterTypes.h | 2 +- server/CGameHandler.cpp | 11 +-- server/CGameHandler.h | 4 +- server/NetPacksServer.cpp | 2 +- test/mock/mock_IGameCallback.h | 2 +- 22 files changed, 118 insertions(+), 100 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index a126475d3..7a4e2a3f5 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -249,9 +249,9 @@ int CBattleCallback::sendRequest(const CPackForServer * request) return requestID; } -void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot ) +void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot, bool accepted ) { - SpellResearch pack(town->id, spellAtSlot); + SpellResearch pack(town->id, spellAtSlot, accepted); sendRequest(&pack); } diff --git a/CCallback.h b/CCallback.h index 74f9b0fda..1bf7d1d7d 100644 --- a/CCallback.h +++ b/CCallback.h @@ -78,7 +78,7 @@ public: virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0; virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0; virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made - virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot)=0; + virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted)=0; virtual void swapGarrisonHero(const CGTownInstance *town)=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 @@ -188,7 +188,7 @@ public: bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; void endTurn() override; - void spellResearch(const CGTownInstance *town, SpellID spellAtSlot) override; + void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override; void swapGarrisonHero(const CGTownInstance *town) override; void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; diff --git a/client/Client.h b/client/Client.h index cfb00e26c..5e5723338 100644 --- a/client/Client.h +++ b/client/Client.h @@ -159,7 +159,7 @@ public: friend class CBattleCallback; //handling players actions void changeSpells(const CGHeroInstance * hero, bool give, const std::set & spells) override {}; - void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {}; + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override {}; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}; void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {}; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {}; diff --git a/client/ClientNetPackVisitors.h b/client/ClientNetPackVisitors.h index 7b67d4b72..3198f74fe 100644 --- a/client/ClientNetPackVisitors.h +++ b/client/ClientNetPackVisitors.h @@ -37,7 +37,7 @@ public: void visitHeroVisitCastle(HeroVisitCastle & pack) override; void visitSetMana(SetMana & pack) override; void visitSetMovePoints(SetMovePoints & pack) override; - void visitSetTownSpells(SetTownSpells & pack) override; + void visitSetResearchedSpells(SetResearchedSpells & pack) override; void visitFoWChange(FoWChange & pack) override; void visitChangeStackCount(ChangeStackCount & pack) override; void visitSetStackType(SetStackType & pack) override; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 1ae453d63..75618089f 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -173,7 +173,7 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h); } -void ApplyClientNetPackVisitor::visitSetTownSpells(SetTownSpells & pack) +void ApplyClientNetPackVisitor::visitSetResearchedSpells(SetResearchedSpells & pack) { for(const auto & win : GH.windows().findWindows()) win->updateSpells(); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index b0751e5a9..776192009 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2047,7 +2047,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = costBase + costPerLevel * (level + 1); + auto cost = (costBase + costPerLevel * (level + 1)) * (town->spellResearchCounter + 1); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); @@ -2058,7 +2058,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) } if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) - LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id); }, nullptr, resComps); + LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }, nullptr, resComps); else LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); } diff --git a/config/gameConfig.json b/config/gameConfig.json index ae79b22ba..1848e15af 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -302,7 +302,7 @@ "backpackSize" : -1, // if heroes are invitable in tavern "tavernInvite" : false, - // minimai primary skills for heroes + // minimal primary skills for heroes "minimalPrimarySkills": [ 0, 0, 1, 1] }, @@ -317,7 +317,9 @@ // Base cost for an spell research "spellResearchCostBase": { "gold": 1000 }, // Costs depends on level for an spell research - "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 } + "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, + // Factor for increasing cost for each research + "spellResearchCostFactorPerResearch": 2.0 }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index e98bf8a0e..166b27961 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -51,11 +51,12 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" }, - "spellResearch" : { "type" : "boolean" }, - "spellResearchCostBase" : { "type" : "object" }, - "spellResearchCostPerLevel" : { "type" : "object" } + "buildingsPerTurnCap" : { "type" : "number" }, + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" }, + "spellResearchCostBase" : { "type" : "object" }, + "spellResearchCostPerLevel" : { "type" : "object" }, + "spellResearchCostFactorPerResearch" : { "type" : "number" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 2a6077928..3fe782a58 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -37,73 +37,74 @@ GameSettings::GameSettings() = default; GameSettings::~GameSettings() = default; const std::vector GameSettings::settingProperties = { - {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, - {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, - {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, - {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, - {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, - {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, - {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, - {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, - {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, - {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, - {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, - {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, - {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, - {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, - {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, - {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles"}, - {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit"}, - {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, - {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, - {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, - {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, - {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, - {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, - {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, - {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, - {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, - {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, - {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, - {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, - {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, - {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, - {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, - {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, - {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, - {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, - {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, - {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, - {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, - {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, - {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, - {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, - {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, - {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, - {EGameSettings::TEXTS_FACTION, "textData", "faction" }, - {EGameSettings::TEXTS_HERO, "textData", "hero" }, - {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, - {EGameSettings::TEXTS_OBJECT, "textData", "object" }, - {EGameSettings::TEXTS_RIVER, "textData", "river" }, - {EGameSettings::TEXTS_ROAD, "textData", "road" }, - {EGameSettings::TEXTS_SPELL, "textData", "spell" }, - {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, - {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, - {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, - {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, + {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, + {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, + {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, + {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, + {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, + {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, + {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, + {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, + {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, + {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, + {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, + {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, + {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, + {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, + {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles" }, + {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit" }, + {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, + {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, + {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, + {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, + {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, + {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, + {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, + {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, + {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, + {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, + {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, + {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, + {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, + {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, + {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, + {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, + {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, + {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, + {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, + {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, + {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, + {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, + {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, + {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, + {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, + {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, + {EGameSettings::TEXTS_FACTION, "textData", "faction" }, + {EGameSettings::TEXTS_HERO, "textData", "hero" }, + {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, + {EGameSettings::TEXTS_OBJECT, "textData", "object" }, + {EGameSettings::TEXTS_RIVER, "textData", "river" }, + {EGameSettings::TEXTS_ROAD, "textData", "road" }, + {EGameSettings::TEXTS_SPELL, "textData", "spell" }, + {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, + {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, + {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, + {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, "towns", "spellResearchCostFactorPerResearch" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 9e1e8c1ca..3d44b451e 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -94,7 +94,7 @@ public: virtual void showInfoDialog(InfoWindow * iw) = 0; virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells)=0; - virtual void setTownSpells(const CGTownInstance * town, int level, const std::vector spells)=0; + virtual void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted)=0; virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0; diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 5b3b81a9d..11683ed20 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -82,6 +82,7 @@ enum class EGameSettings TOWNS_SPELL_RESEARCH, TOWNS_SPELL_RESEARCH_COST_BASE, TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, + TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index c8ccb57f2..fbd2a76c6 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -270,6 +270,7 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), lastSpellResearchDay(0), + spellResearchCounter(0), spellResearchAllowed(true) { this->setNodeType(CBonusSystemNode::TOWN); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 204bdb51e..fb5eb0af7 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -74,6 +74,7 @@ public: std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); int lastSpellResearchDay; + int spellResearchCounter; bool spellResearchAllowed; ////////////////////////////////////////////////////////////////////////// @@ -96,7 +97,11 @@ public: h & events; if (h.version >= Handler::Version::SPELL_RESEARCH) + { h & lastSpellResearchDay; + h & spellResearchCounter; + h & spellResearchAllowed; + } if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS) { diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 4cfb0e402..007f30fa6 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -42,7 +42,7 @@ public: virtual void visitSetSecSkill(SetSecSkill & pack) {} virtual void visitHeroVisitCastle(HeroVisitCastle & pack) {} virtual void visitChangeSpells(ChangeSpells & pack) {} - virtual void visitSetTownSpells(SetTownSpells & pack) {} + virtual void visitSetResearchedSpells(SetResearchedSpells & pack) {} virtual void visitSetMana(SetMana & pack) {} virtual void visitSetMovePoints(SetMovePoints & pack) {} virtual void visitFoWChange(FoWChange & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 89207fa1c..785052430 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -162,9 +162,9 @@ void ChangeSpells::visitTyped(ICPackVisitor & visitor) visitor.visitChangeSpells(*this); } -void SetTownSpells::visitTyped(ICPackVisitor & visitor) +void SetResearchedSpells::visitTyped(ICPackVisitor & visitor) { - visitor.visitSetTownSpells(*this); + visitor.visitSetResearchedSpells(*this); } void SetMana::visitTyped(ICPackVisitor & visitor) { @@ -939,12 +939,14 @@ void ChangeSpells::applyGs(CGameState *gs) hero->removeSpellFromSpellbook(sid); } -void SetTownSpells::applyGs(CGameState *gs) +void SetResearchedSpells::applyGs(CGameState *gs) { CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; town->lastSpellResearchDay = gs->getDate(Date::DAY); + if(accepted) + town->spellResearchCounter++; } void SetMana::applyGs(CGameState *gs) diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index ae1d23167..e992e4e21 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -288,7 +288,7 @@ struct DLL_LINKAGE ChangeSpells : public CPackForClient } }; -struct DLL_LINKAGE SetTownSpells : public CPackForClient +struct DLL_LINKAGE SetResearchedSpells : public CPackForClient { void applyGs(CGameState * gs) override; @@ -297,12 +297,14 @@ struct DLL_LINKAGE SetTownSpells : public CPackForClient ui8 level = 0; ObjectInstanceID tid; std::vector spells; + bool accepted; template void serialize(Handler & h) { h & level; h & tid; h & spells; + h & accepted; } }; diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index 059205973..202d1bb7b 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -309,12 +309,13 @@ struct DLL_LINKAGE RazeStructure : public BuildStructure struct DLL_LINKAGE SpellResearch : public CPackForServer { SpellResearch() = default; - SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot) + SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot, bool accepted) : tid(TID), spellAtSlot(spellAtSlot) { } ObjectInstanceID tid; SpellID spellAtSlot; + bool accepted; void visitTyped(ICPackVisitor & visitor) override; @@ -323,6 +324,7 @@ struct DLL_LINKAGE SpellResearch : public CPackForServer h & static_cast(*this); h & tid; h & spellAtSlot; + h & accepted; } }; diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 6fcbc5e20..f716c7be4 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -289,7 +289,7 @@ void registerTypes(Serializer &s) s.template registerType(239); s.template registerType(240); s.template registerType(241); - s.template registerType(242); + s.template registerType(242); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 47233d034..9518078b7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1235,12 +1235,13 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st sendAndApply(&cs); } -void CGameHandler::setTownSpells(const CGTownInstance * town, int level, const std::vector spells) +void CGameHandler::setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) { - SetTownSpells cs; + SetResearchedSpells cs; cs.tid = town->id; cs.spells = spells; cs.level = level; + cs.accepted = accepted; sendAndApply(&cs); } @@ -2242,7 +2243,7 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) return true; } -bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) +bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool accepted) { CGTownInstance *t = gs->getTown(tid); @@ -2263,7 +2264,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = costBase + costPerLevel * (level + 1); + auto cost = (costBase + costPerLevel * (level + 1)) * (t->spellResearchCounter + 1); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; @@ -2276,7 +2277,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) auto it = spells.begin() + t->spellsAtLevel(level, false); std::rotate(it, it + 1, spells.end()); // move to end - setTownSpells(t, level, spells); + setResearchedSpells(t, level, spells, accepted); if(t->visitingHero) giveSpells(t, t->visitingHero); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index fabba227e..69a83928c 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -107,7 +107,7 @@ public: //from IGameCallback //do sth void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override; - void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override; + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override; void giveExperience(const CGHeroInstance * hero, TExpType val) override; @@ -219,7 +219,7 @@ public: bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); - bool spellResearch(ObjectInstanceID tid, SpellID spellAtSlot); + bool spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool accepted); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index b4227f1ea..9713810fe 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -143,7 +143,7 @@ void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) gh.throwIfWrongOwner(&pack, pack.tid); gh.throwIfPlayerNotActive(&pack); - result = gh.spellResearch(pack.tid, pack.spellAtSlot); + result = gh.spellResearch(pack.tid, pack.spellAtSlot, pack.accepted); } void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack) diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 1cec76e76..8f67f41a6 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -44,7 +44,7 @@ public: void showInfoDialog(InfoWindow * iw) override {} void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override {} - void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {} + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override {} bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;} void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {} void setOwner(const CGObjectInstance * objid, PlayerColor owner) override {} From f94f0a3274f330a900d4cbf2b190973b49986689 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 15:51:53 +0200 Subject: [PATCH 11/18] new dialog --- Mods/vcmi/config/vcmi/english.json | 5 +- Mods/vcmi/config/vcmi/german.json | 5 +- client/windows/CCastleInterface.cpp | 29 +++++- config/gameConfig.json | 4 +- config/schemas/gameSettings.json | 12 +-- lib/GameSettings.cpp | 136 ++++++++++++++-------------- lib/IGameSettings.h | 2 +- lib/networkPacks/PacksForServer.h | 2 +- server/CGameHandler.cpp | 16 +++- 9 files changed, 123 insertions(+), 88 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index cc936dbf9..11ed66eaa 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -61,7 +61,10 @@ "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", - "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one?", + "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one or skip this spell?", + "vcmi.spellResearch.research" : "Research this Spell", + "vcmi.spellResearch.skip" : "Skip this Spell", + "vcmi.spellResearch.abort" : "Abort", "vcmi.mainMenu.serverConnecting" : "Connecting...", "vcmi.mainMenu.serverAddressEnter" : "Enter address:", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 26deaf5c0..38f5428e8 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -60,7 +60,10 @@ "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", - "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen?", + "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen oder diesen überspringen?", + "vcmi.spellResearch.research" : "Erforsche diesen Zauberspruch", + "vcmi.spellResearch.skip" : "Überspringe diesen Zauberspruch", + "vcmi.spellResearch.abort" : "Abbruch", "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 776192009..8bc898ddc 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2047,7 +2047,8 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = (costBase + costPerLevel * (level + 1)) * (town->spellResearchCounter + 1); + auto costExponent = LOCPLINT->cb->getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); + auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(town->spellResearchCounter + 1, costExponent); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); @@ -2057,10 +2058,28 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium)); } - if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) - LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }, nullptr, resComps); - else - LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); + auto showSpellResearchDialog = [this, resComps, town, cost](){ + std::vector>> pom; + pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); + pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); + pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); + auto temp = std::make_shared(CGI->generaltexth->translate("vcmi.spellResearch.pay"), LOCPLINT->playerID, resComps, pom); + + temp->buttons[0]->addCallback([this, resComps, town, cost](){ + if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) + LOCPLINT->cb->spellResearch(town, spell->id, true); + else + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); + }); + temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); + temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); + temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); + temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); + + GH.windows().pushWindow(temp); + }; + + showSpellResearchDialog(); } else LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); diff --git a/config/gameConfig.json b/config/gameConfig.json index 1848e15af..08f2ce659 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -318,8 +318,8 @@ "spellResearchCostBase": { "gold": 1000 }, // Costs depends on level for an spell research "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, - // Factor for increasing cost for each research - "spellResearchCostFactorPerResearch": 2.0 + // Exponent for increasing cost for each research + "spellResearchCostExponentPerResearch": 1.5 }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 166b27961..da113f7f1 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -51,12 +51,12 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" }, - "spellResearch" : { "type" : "boolean" }, - "spellResearchCostBase" : { "type" : "object" }, - "spellResearchCostPerLevel" : { "type" : "object" }, - "spellResearchCostFactorPerResearch" : { "type" : "number" } + "buildingsPerTurnCap" : { "type" : "number" }, + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" }, + "spellResearchCostBase" : { "type" : "object" }, + "spellResearchCostPerLevel" : { "type" : "object" }, + "spellResearchCostExponentPerResearch" : { "type" : "number" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 3fe782a58..fee70455e 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -37,74 +37,74 @@ GameSettings::GameSettings() = default; GameSettings::~GameSettings() = default; const std::vector GameSettings::settingProperties = { - {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, - {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, - {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, - {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, - {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, - {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, - {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, - {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, - {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, - {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, - {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, - {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, - {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, - {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, - {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, - {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles" }, - {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit" }, - {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, - {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, - {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, - {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, - {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, - {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, - {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, - {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, - {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, - {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, - {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, - {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, - {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, - {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, - {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, - {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, - {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, - {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, - {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, - {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, - {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, - {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, - {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, - {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, - {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, - {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, - {EGameSettings::TEXTS_FACTION, "textData", "faction" }, - {EGameSettings::TEXTS_HERO, "textData", "hero" }, - {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, - {EGameSettings::TEXTS_OBJECT, "textData", "object" }, - {EGameSettings::TEXTS_RIVER, "textData", "river" }, - {EGameSettings::TEXTS_ROAD, "textData", "road" }, - {EGameSettings::TEXTS_SPELL, "textData", "spell" }, - {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, - {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, - {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, - {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, "towns", "spellResearchCostFactorPerResearch" }, + {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, + {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, + {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, + {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, + {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, + {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, + {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, + {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, + {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, + {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, + {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, + {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, + {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, + {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, + {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, + {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles" }, + {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit" }, + {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, + {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, + {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, + {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, + {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, + {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, + {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, + {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, + {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, + {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, + {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, + {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, + {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, + {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, + {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, + {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, + {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, + {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, + {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, + {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, + {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, + {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, + {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, + {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, + {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, + {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, + {EGameSettings::TEXTS_FACTION, "textData", "faction" }, + {EGameSettings::TEXTS_HERO, "textData", "hero" }, + {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, + {EGameSettings::TEXTS_OBJECT, "textData", "object" }, + {EGameSettings::TEXTS_RIVER, "textData", "river" }, + {EGameSettings::TEXTS_ROAD, "textData", "road" }, + {EGameSettings::TEXTS_SPELL, "textData", "spell" }, + {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, + {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, + {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, + {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, "towns", "spellResearchCostExponentPerResearch" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 11683ed20..12dc351c7 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -82,7 +82,7 @@ enum class EGameSettings TOWNS_SPELL_RESEARCH, TOWNS_SPELL_RESEARCH_COST_BASE, TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, - TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, + TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index 202d1bb7b..d72be5265 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -310,7 +310,7 @@ struct DLL_LINKAGE SpellResearch : public CPackForServer { SpellResearch() = default; SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot, bool accepted) - : tid(TID), spellAtSlot(spellAtSlot) + : tid(TID), spellAtSlot(spellAtSlot), accepted(accepted) { } ObjectInstanceID tid; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9518078b7..b295a897d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2257,22 +2257,32 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool if(level == -1 && complain("Spell for replacement not found!")) return false; + + auto spells = t->spells.at(level); int daysSinceLastResearch = gs->getDate(Date::DAY) - t->lastSpellResearchDay; if(!daysSinceLastResearch && complain("Already researched today!")) return false; + if(!accepted) + { + auto it = spells.begin() + t->spellsAtLevel(level, false); + std::rotate(it, it + 1, spells.end()); // move to end + setResearchedSpells(t, level, spells, accepted); + return true; + } + auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = (costBase + costPerLevel * (level + 1)) * (t->spellResearchCounter + 1); + auto costExponent = getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); + + auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(t->spellResearchCounter + 1, costExponent); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; giveResources(t->getOwner(), -cost); - auto spells = t->spells.at(level); - std::swap(spells.at(t->spellsAtLevel(level, false)), spells.at(vstd::find_pos(spells, spellAtSlot))); auto it = spells.begin() + t->spellsAtLevel(level, false); std::rotate(it, it + 1, spells.end()); // move to end From 8461189e9501971288dcc645c27b8832312b5d10 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:10:03 +0200 Subject: [PATCH 12/18] code review + text --- client/NetPacksClient.cpp | 2 +- client/windows/CCastleInterface.cpp | 18 +++++++++--------- client/windows/CCastleInterface.h | 2 +- config/gameConfig.json | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 75618089f..d403120a7 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -176,7 +176,7 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) void ApplyClientNetPackVisitor::visitSetResearchedSpells(SetResearchedSpells & pack) { for(const auto & win : GH.windows().findWindows()) - win->updateSpells(); + win->updateSpells(pack.tid); } void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 8bc898ddc..bd129ea4a 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1982,11 +1982,14 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN); - updateSpells(); + updateSpells(townId); } -void CMageGuildScreen::updateSpells() +void CMageGuildScreen::updateSpells(ObjectInstanceID tID) { + if(tID != townId) + return; + OBJECT_CONSTRUCTION; static const std::vector > positions = { @@ -2063,15 +2066,12 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); - auto temp = std::make_shared(CGI->generaltexth->translate("vcmi.spellResearch.pay"), LOCPLINT->playerID, resComps, pom); + auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); + auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); - temp->buttons[0]->addCallback([this, resComps, town, cost](){ - if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) - LOCPLINT->cb->spellResearch(town, spell->id, true); - else - LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); - }); + temp->buttons[0]->addCallback([this, resComps, town, cost](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); + temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index b5fd3d7ab..717c1a748 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -398,7 +398,7 @@ class CMageGuildScreen : public CStatusbarWindow public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); - void updateSpells(); + void updateSpells(ObjectInstanceID tID); }; /// The blacksmith window where you can buy available in town war machine diff --git a/config/gameConfig.json b/config/gameConfig.json index 08f2ce659..a2330ec2b 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -319,7 +319,7 @@ // Costs depends on level for an spell research "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, // Exponent for increasing cost for each research - "spellResearchCostExponentPerResearch": 1.5 + "spellResearchCostExponentPerResearch": 1.25 }, "combat": From 3813db83abe133e222d60557691613e7cd309750 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:16:34 +0200 Subject: [PATCH 13/18] make ci happy --- client/windows/CCastleInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index bd129ea4a..53705a146 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2069,7 +2069,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); - temp->buttons[0]->addCallback([this, resComps, town, cost](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); + temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); From 713fcd65436e634a2b06e547975d1ba027dc125e Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:40:28 +0200 Subject: [PATCH 14/18] research per day & seperate config --- client/windows/CCastleInterface.cpp | 21 ++++++++++----------- config/gameConfig.json | 18 ++++++++++++------ config/schemas/gameSettings.json | 6 +++--- lib/GameSettings.cpp | 4 ++-- lib/IGameSettings.h | 4 ++-- lib/mapObjects/CGTownInstance.cpp | 1 - lib/mapObjects/CGTownInstance.h | 4 ++-- lib/networkPacks/NetPacksLib.cpp | 2 +- server/CGameHandler.cpp | 13 ++++++------- 9 files changed, 38 insertions(+), 35 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 53705a146..95c01ce58 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2036,22 +2036,21 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && town->spellResearchAllowed) { - int daysSinceLastResearch = LOCPLINT->cb->getDate(Date::DAY) - town->lastSpellResearchDay; - if(!daysSinceLastResearch) + int level = -1; + for(int i = 0; i < town->spells.size(); i++) + if(vstd::find_pos(town->spells[i], spell->id) != -1) + level = i; + + int today = LOCPLINT->cb->getDate(Date::DAY); + if(town->spellResearchActionsPerDay.find(today) == town->spellResearchActionsPerDay.end() || town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) { LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); return; } - int level = -1; - for(int i = 0; i < town->spells.size(); i++) - if(vstd::find_pos(town->spells[i], spell->id) != -1) - level = i; - - auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); - auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto costExponent = LOCPLINT->cb->getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); - auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(town->spellResearchCounter + 1, costExponent); + auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); + auto costExponent = LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); + auto cost = costBase * std::pow(town->spellResearchCounter + 1, costExponent); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); diff --git a/config/gameConfig.json b/config/gameConfig.json index a2330ec2b..a7a80d2a0 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -314,12 +314,18 @@ "startingDwellingChances": [100, 50], // Enable spell research in mage guild "spellResearch": false, - // Base cost for an spell research - "spellResearchCostBase": { "gold": 1000 }, - // Costs depends on level for an spell research - "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, - // Exponent for increasing cost for each research - "spellResearchCostExponentPerResearch": 1.25 + // Cost for an spell research (array index is spell tier) + "spellResearchCost": [ + { "gold": 1000, "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, + { "gold": 1000, "wood" : 4, "mercury": 4, "ore": 4, "sulfur": 4, "crystal": 4, "gems": 4 }, + { "gold": 1000, "wood" : 6, "mercury": 6, "ore": 6, "sulfur": 6, "crystal": 6, "gems": 6 }, + { "gold": 1000, "wood" : 8, "mercury": 8, "ore": 8, "sulfur": 8, "crystal": 8, "gems": 8 }, + { "gold": 1000, "wood" : 10, "mercury": 10, "ore": 10, "sulfur": 10, "crystal": 10, "gems": 10 } + ], + // How much researchs/skips per day are possible? (array index is spell tier) + "spellResearchPerDay": [ 2, 2, 2, 2, 1 ], + // Exponent for increasing cost for each research (factor 1 disables this; array index is spell tier) + "spellResearchCostExponentPerResearch": [ 1.25, 1.25, 1.25, 1.25, 1.25 ] }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index da113f7f1..6d0fd4670 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -54,9 +54,9 @@ "buildingsPerTurnCap" : { "type" : "number" }, "startingDwellingChances" : { "type" : "array" }, "spellResearch" : { "type" : "boolean" }, - "spellResearchCostBase" : { "type" : "object" }, - "spellResearchCostPerLevel" : { "type" : "object" }, - "spellResearchCostExponentPerResearch" : { "type" : "number" } + "spellResearchCost" : { "type" : "array" }, + "spellResearchPerDay" : { "type" : "array" }, + "spellResearchCostExponentPerResearch" : { "type" : "array" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index fee70455e..8ea0228b1 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -102,8 +102,8 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST, "towns", "spellResearchCost" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY, "towns", "spellResearchPerDay" }, {EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, "towns", "spellResearchCostExponentPerResearch" }, }; diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 12dc351c7..fdde0da27 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -80,8 +80,8 @@ enum class EGameSettings TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, TOWNS_SPELL_RESEARCH, - TOWNS_SPELL_RESEARCH_COST_BASE, - TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, + TOWNS_SPELL_RESEARCH_COST, + TOWNS_SPELL_RESEARCH_PER_DAY, TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, OPTIONS_COUNT, diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index fbd2a76c6..76dc2a254 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -269,7 +269,6 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): destroyed(0), identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), - lastSpellResearchDay(0), spellResearchCounter(0), spellResearchAllowed(true) { diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fb5eb0af7..6e4bc7af3 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -73,7 +73,7 @@ public: std::vector > spells; //spells[level] -> vector of spells, first will be available in guild std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); - int lastSpellResearchDay; + std::map spellResearchActionsPerDay; int spellResearchCounter; bool spellResearchAllowed; @@ -98,7 +98,7 @@ public: if (h.version >= Handler::Version::SPELL_RESEARCH) { - h & lastSpellResearchDay; + h & spellResearchActionsPerDay; h & spellResearchCounter; h & spellResearchAllowed; } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 785052430..fb37de483 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -944,7 +944,7 @@ void SetResearchedSpells::applyGs(CGameState *gs) CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; - town->lastSpellResearchDay = gs->getDate(Date::DAY); + town->spellResearchActionsPerDay[gs->getDate(Date::DAY)]++; if(accepted) town->spellResearchCounter++; } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b295a897d..44fe7e06d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2260,8 +2260,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto spells = t->spells.at(level); - int daysSinceLastResearch = gs->getDate(Date::DAY) - t->lastSpellResearchDay; - if(!daysSinceLastResearch && complain("Already researched today!")) + int today = getDate(Date::DAY); + bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) == t->spellResearchActionsPerDay.end() || t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); + if(researchLimitExceeded && complain("Already researched today!")) return false; if(!accepted) @@ -2272,11 +2273,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool return true; } - auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); - auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto costExponent = getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); - - auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(t->spellResearchCounter + 1, costExponent); + auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); + auto costExponent = getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); + auto cost = costBase * std::pow(t->spellResearchCounter + 1, costExponent); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; From e2b49bbf79f9557dc7900402b12e02a167db973d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:46:45 +0200 Subject: [PATCH 15/18] fix condition --- client/windows/CCastleInterface.cpp | 2 +- server/CGameHandler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 95c01ce58..abb4fa036 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2042,7 +2042,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) level = i; int today = LOCPLINT->cb->getDate(Date::DAY); - if(town->spellResearchActionsPerDay.find(today) == town->spellResearchActionsPerDay.end() || town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) + if(town->spellResearchActionsPerDay.find(today) != town->spellResearchActionsPerDay.end() && town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) { LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); return; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 44fe7e06d..8d1046398 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2261,7 +2261,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto spells = t->spells.at(level); int today = getDate(Date::DAY); - bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) == t->spellResearchActionsPerDay.end() || t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); + bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) != t->spellResearchActionsPerDay.end() && t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); if(researchLimitExceeded && complain("Already researched today!")) return false; From 31f87cb6eddcc265d365090998ce6e724db42677 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:15:13 +0200 Subject: [PATCH 16/18] improve ui --- Mods/vcmi/config/vcmi/english.json | 4 ++-- Mods/vcmi/config/vcmi/german.json | 4 ++-- client/windows/CCastleInterface.cpp | 9 +++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 11ed66eaa..d6c27e5b0 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -59,9 +59,9 @@ "vcmi.spellBook.search" : "search...", - "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", + "vcmi.spellResearch.canNotAfford" : "You can't afford to replace {%SPELL1} with {%SPELL2}. But you can still discard this spell and continue spell research.", "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", - "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one or skip this spell?", + "vcmi.spellResearch.pay" : "Would you like to replace {%SPELL1} with {%SPELL2}? Or discard this spell and continue spell research?", "vcmi.spellResearch.research" : "Research this Spell", "vcmi.spellResearch.skip" : "Skip this Spell", "vcmi.spellResearch.abort" : "Abort", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 38f5428e8..e1934f370 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -58,9 +58,9 @@ "vcmi.spellBook.search" : "suchen...", - "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", + "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, {%SPELL1} durch {%SPELL2} zu ersetzen. Aber Ihr könnt diesen Zauberspruch trotzdem verwerfen und die Zauberspruchforschung fortsetzen.", "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", - "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen oder diesen überspringen?", + "vcmi.spellResearch.pay" : "Möchtet Ihr {%SPELL1} durch {%SPELL2} ersetzen? Oder diesen Zauberspruch verwerfen und die Zauberspruchforschung fortsetzen?", "vcmi.spellResearch.research" : "Erforsche diesen Zauberspruch", "vcmi.spellResearch.skip" : "Überspringe diesen Zauberspruch", "vcmi.spellResearch.abort" : "Abbruch", diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index abb4fa036..9bc5e1ae6 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2053,19 +2053,24 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto cost = costBase * std::pow(town->spellResearchCounter + 1, costExponent); std::vector> resComps; - resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); + auto newSpell = town->spells[level].at(town->spellsAtLevel(level, false)); + resComps.push_back(std::make_shared(ComponentType::SPELL, spell->id)); + resComps.push_back(std::make_shared(ComponentType::SPELL, newSpell)); resComps.back()->newLine = true; for(TResources::nziterator i(cost); i.valid(); i++) { resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium)); } - auto showSpellResearchDialog = [this, resComps, town, cost](){ + auto showSpellResearchDialog = [this, resComps, town, cost, newSpell](){ std::vector>> pom; pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); + auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); + boost::replace_first(text, "%SPELL1", spell->id.toSpell()->getNameTranslated()); + boost::replace_first(text, "%SPELL2", newSpell.toSpell()->getNameTranslated()); auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); From 9c6bd201594eac73d67877fad231dcba0161c24a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:32:28 +0200 Subject: [PATCH 17/18] code review --- client/windows/CCastleInterface.cpp | 5 ++--- lib/mapObjects/CGTownInstance.cpp | 3 ++- lib/mapObjects/CGTownInstance.h | 8 ++++---- lib/networkPacks/NetPacksLib.cpp | 7 +++++-- server/CGameHandler.cpp | 9 +++++---- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 9bc5e1ae6..d8f069e56 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2041,8 +2041,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) if(vstd::find_pos(town->spells[i], spell->id) != -1) level = i; - int today = LOCPLINT->cb->getDate(Date::DAY); - if(town->spellResearchActionsPerDay.find(today) != town->spellResearchActionsPerDay.end() && town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) + if(town->spellResearchCounterDay >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) { LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); return; @@ -2050,7 +2049,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); auto costExponent = LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); - auto cost = costBase * std::pow(town->spellResearchCounter + 1, costExponent); + auto cost = costBase * std::pow(town->spellResearchAcceptedCounter + 1, costExponent); std::vector> resComps; auto newSpell = town->spells[level].at(town->spellsAtLevel(level, false)); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 76dc2a254..fc968ffb5 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -269,7 +269,8 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): destroyed(0), identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), - spellResearchCounter(0), + spellResearchCounterDay(0), + spellResearchAcceptedCounter(0), spellResearchAllowed(true) { this->setNodeType(CBonusSystemNode::TOWN); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 6e4bc7af3..fe143c6fd 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -73,8 +73,8 @@ public: std::vector > spells; //spells[level] -> vector of spells, first will be available in guild std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); - std::map spellResearchActionsPerDay; - int spellResearchCounter; + int spellResearchCounterDay; + int spellResearchAcceptedCounter; bool spellResearchAllowed; ////////////////////////////////////////////////////////////////////////// @@ -98,8 +98,8 @@ public: if (h.version >= Handler::Version::SPELL_RESEARCH) { - h & spellResearchActionsPerDay; - h & spellResearchCounter; + h & spellResearchCounterDay; + h & spellResearchAcceptedCounter; h & spellResearchAllowed; } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index fb37de483..623437b03 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -944,9 +944,9 @@ void SetResearchedSpells::applyGs(CGameState *gs) CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; - town->spellResearchActionsPerDay[gs->getDate(Date::DAY)]++; + town->spellResearchCounterDay++; if(accepted) - town->spellResearchCounter++; + town->spellResearchAcceptedCounter++; } void SetMana::applyGs(CGameState *gs) @@ -1941,7 +1941,10 @@ void NewTurn::applyGs(CGameState *gs) creatureSet.applyGs(gs); for(CGTownInstance* t : gs->map->towns) + { t->built = 0; + t->spellResearchCounterDay = 0; + } if(newRumor) gs->currentRumor = *newRumor; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 8d1046398..8448ca6fc 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2247,7 +2247,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool { CGTownInstance *t = gs->getTown(tid); - if(!(getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && t->spellResearchAllowed) && complain("Spell research not allowed!")) + if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) + return false; + if (!t->spellResearchAllowed && complain("Spell research not allowed in this town!")) return false; int level = -1; @@ -2260,8 +2262,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto spells = t->spells.at(level); - int today = getDate(Date::DAY); - bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) != t->spellResearchActionsPerDay.end() && t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); + bool researchLimitExceeded = t->spellResearchCounterDay >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); if(researchLimitExceeded && complain("Already researched today!")) return false; @@ -2275,7 +2276,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); auto costExponent = getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); - auto cost = costBase * std::pow(t->spellResearchCounter + 1, costExponent); + auto cost = costBase * std::pow(t->spellResearchAcceptedCounter + 1, costExponent); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; From b885fd9d3bd6ab0dd8ce2a25b5a3119635ff29ab Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:26:43 +0200 Subject: [PATCH 18/18] added custom icons for buttons made by Ivan --- Mods/vcmi/Data/spellResearch/accept.png | Bin 0 -> 931 bytes Mods/vcmi/Data/spellResearch/close.png | Bin 0 -> 675 bytes Mods/vcmi/Data/spellResearch/reroll.png | Bin 0 -> 1047 bytes client/windows/CCastleInterface.cpp | 8 +++++--- 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Mods/vcmi/Data/spellResearch/accept.png create mode 100644 Mods/vcmi/Data/spellResearch/close.png create mode 100644 Mods/vcmi/Data/spellResearch/reroll.png diff --git a/Mods/vcmi/Data/spellResearch/accept.png b/Mods/vcmi/Data/spellResearch/accept.png new file mode 100644 index 0000000000000000000000000000000000000000..d3958902c9161ea81941a2dacb02be1c9c384598 GIT binary patch literal 931 zcmV;U16=%xP)X1^@s6S>%lC000AONkl_!k0a(5xI|!`_DAS1=&IMfG^!9rhZZ9XvjZDXv?%+4R)W)}UIbR$-s1e25Usgf`K_ta?`?*|} zISp!+wve2}4iq=qb^|wiLQ9JAN|q^Aq0C9F@Yu?o+S!lxJAj5XUkXC`Cvmq9H6k%J zkmgH72xFar2^a$^6*7q{m!yz+`p>aV{rc*Zl^CkTp2Wo?bX-1#Z}(>=uV2`yhxAh! z(pa=zK1p0iAp^m0r!b&L-Rwfu03T?&Cc98QRBjLY@>I92;)_d@FQek0l_ejYoj~gy zDT!HR=Fus{Ic!Wumj~4{&qLIazU1+_i7uJP&0fv#5bwAtQF;zEx#Z2-a*Ty5h4G5VyME$aOGk2 z=VxLOSd1Jxij$(pS9oxG{ItwhT;;VbhGaUSB&3^!@&<#QUem3-mcJ zV(mAYchv^Ii|B5E8J?`KvZJYnUllBuI+22V5nbFWy*th6x{jy(wNf- zB>3&%@)449W1elW*&1`{^-q{Pu~qF%VY^E|_v9kg8)|YDjj&!HeRy+k5&5B_enH?C ziJ$MxxP}u7rM}bGr9KE(9xykO*^+)$x5RB<*-cD#*^);6!jk5`Rs48s`nE*p&8gHk zelhQ-HZo&?n)BJ7yk}hxG1Ovh;bk%rWwV~se>!IYsABhu*kyw|Hg}xM>c(m z6H3Mnq^oYoBJ>~qbxO}|0%}SSP000>X1^@s6#OZ}&0007NNklha8+qT{Q?YpS9Z8NBC+qP}n=61ex=d#C#Nxo!nF~8fLc{3Er)IDz@ zR$tZ*)??N$NRM~uwL;{=;Jg%T0ZZA@m#fJeaK;J1%X(rLAo_Ma^~;mvF=Oli2C)8` zhp+AKpcYN6q0&0#S9!B%hA_q))`DO+_H^82bps!O|CO3LsG=ewXbmfhm7A3g(&MPb zlWHEme>lnybZSFufP!NX_(X5cC8)Lyvz;cQ24rFL$5oHJxwqppIs55KvSPUZ)0shj zdp?2RH$`sqW#7`KdB}zxSkbK7%hPc_(B&QNzwmS^GfkCf`jAR$?#QZ-KV44xgWu0i z#=f%)dT=-@&h#~^N0|)*>1zfVQ}wE3X?|jTt3iD2-%ddvE+rinf7|Cbg1TRIOg@%Lq^8ayasd)ZZt=%Y4u3l^`Aa784d~P)7a)P8 z6Muj1c;N4GC`dYj*#%iceg!|5K8oo(VL} zi|m5FT>TF+3uJ$9aR3&OX=pg+Mie{LA}i3(TWQU|-$=b{o?SDmE&xwh!QB!KM>dLd zEsD~qnNADiC<(Hu1IwEM0C_2#q2b`VPTMce=o>(z$;!}h&<|_^5M+tL6S;w1D}{c5 zT*pU+uNP$k!WeT{(`hO}E=4Y_kk0iTtjDZhT95TyU%e-+{R3n9=V)3aNfiJ9002ov JPDHLkV1g1@I(Gm7 literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Data/spellResearch/reroll.png b/Mods/vcmi/Data/spellResearch/reroll.png new file mode 100644 index 0000000000000000000000000000000000000000..f5603dde099dc28a4366a9f0ab60169a666acf72 GIT binary patch literal 1047 zcmV+y1nB#TP)X1^@s6S>%lC000BtNklp5r9$(K~s>8|rntNOa; z8q4y(A4Gtxb`S?1GmQ1p>`ov^%f)L%Xt7S$wYQ=3eM*L3l$`FTz-ILcUQ(S7@+pqr z)$&L;!O8Z;Bn*2@8{1#W^h?M&zwij`+S+)})ti19w>i+MbDYv{2Z>Sp97`}( zsEzYnfj+mkSJ!>{7oBV9kNbDOkHOCaxx}De59Coi^UMN@b09`1$ObS75w`s?VH-lV zAPkHF&w=N`*mu3UFV8*iB;$i}j<277PkU$Ts3u5-d%G@vJ5V)2M-5|a)J#7fv&gDq zJVY1!7pJ$B|LeEkBg3 zxMc4|=eOAkFgyJ(1R>X$nmErAm_b$*gjfK$(S)EE)l7d8W`wl`@nN3~`T}N$MA1i- zN4e8D-r9m9f%+&!>*G`oa05%BpA34wmE5<3Bv1qz zfK6Ls`ht4YB<@4so-{jA&GhLlrTHqj)YzV-lit?FZ>4fTH!v=}ms+<#;AA6iJEv);PD-CL-^G(?zZNSI->#)ZK>f>;!^91mFqH4$_qhFL1w6 zPzYefS6$2A_3X|t-2(Y-fp-(zzhcTF%uoUOu?J_TBi;3uo+vNC8WO!Kedl*#b@5(} z6SjBRWFIU|1NmpS*c`P)%m^S#>ArQZxpZL#mz*^*9i#M`t8{YX3_C8_N5-tt z$qOEt%yH7+ZlV0;Vz=VfQE@bLA+FpK?iN(95ERPZ>LECKIm>EaGMuFFez(hLOyQ|%TI0#ZPh6-WW0THX$|zW}5bu(L|z R(BA+6002ovPDHLkV1k~N{)Yen literal 0 HcmV?d00001 diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index d8f069e56..90f2b1dca 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2063,20 +2063,22 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto showSpellResearchDialog = [this, resComps, town, cost, newSpell](){ std::vector>> pom; - pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); - pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); - pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); + for(int i = 0; i < 3; i++) + pom.emplace_back(AnimationPath::builtin("settingsWindow/button80"), nullptr); auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); boost::replace_first(text, "%SPELL1", spell->id.toSpell()->getNameTranslated()); boost::replace_first(text, "%SPELL2", newSpell.toSpell()->getNameTranslated()); auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); + temp->buttons[0]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/accept"))); temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); + temp->buttons[1]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/reroll"))); temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); + temp->buttons[2]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/close"))); temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); GH.windows().pushWindow(temp);