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] 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 {}