diff --git a/config/commanders.json b/config/commanders.json index fee89569a..59837b8b4 100644 --- a/config/commanders.json +++ b/config/commanders.json @@ -3,9 +3,9 @@ //Commander receives these bonuses on level-up "bonusPerLevel": [ - ["CREATURE_DAMAGE", 2, "creatureDamageMin", 0 ], //+2 minimum damage - ["CREATURE_DAMAGE", 4, "creatureDamageMax", 0 ], //+4 maximum damage - ["STACK_HEALTH", 20, null, 0 ] //+20 hp + { "type" : "CREATURE_DAMAGE", "val" : 2, "subtype" : "creatureDamageMin" }, //+2 minimum damage + { "type" : "CREATURE_DAMAGE", "val" : 4, "subtype" : "creatureDamageMax" }, //+4 maximum damage + { "type" : "STACK_HEALTH", "val" : 20 } //+20 hp ], //Value of bonuses given by each skill level "skillLevels": @@ -22,20 +22,71 @@ "abilityRequirements": //Two secondary skills needed for each special ability [ - {"ability": ["ENEMY_DEFENCE_REDUCTION", 50, null, 0 ], "skills": [0, 1]}, - {"ability": ["FEAR", 0, null, 0 ], "skills": [0, 2]}, - {"ability": ["ALWAYS_MAXIMUM_DAMAGE", 0, null, 0 ], "skills": [0, 3]}, - {"ability": [["SHOOTER", 0, null, 0 ], ["NO_MELEE_PENALTY", 0, null, 0 ]], "skills": [0, 4]}, - {"ability": ["BLOCKS_RETALIATION", 0, null, 0 ], "skills": [0,5]}, - {"ability": ["UNLIMITED_RETALIATIONS", 0, null, 0 ], "skills": [1, 2]}, - {"ability": ["ATTACKS_ALL_ADJACENT", 0, null, 0 ], "skills": [1, 3]}, - {"ability": ["NONE", 30, null, 0 ], "skills": [1, 4]}, // TODO: Implement bonus that gives chance to completely block one enemy attack per turn - {"ability": ["FIRE_SHIELD", 1, null, 0 ], "skills": [1, 5]}, - {"ability": ["ADDITIONAL_ATTACK", 1, null, 0 ], "skills": [2, 3]}, - {"ability": ["HP_REGENERATION", 50, null, 0 ], "skills": [2, 4]}, - {"ability": ["SPELL_AFTER_ATTACK", 30, "spell.paralyze", 0 ], "skills": [2, 5]}, - {"ability": ["JOUSTING", 5, null, 0 ], "skills": [3, 4]}, - {"ability": ["DEATH_STARE", 1, "deathStareCommander", 0 ], "skills": [3,5]}, - {"ability": ["FLYING", 0, "movementFlying", 0 ], "skills": [4,5]} + { + "ability": [{ "type" : "ENEMY_DEFENCE_REDUCTION", "val": 50 }], + "skills": [0, 1] + }, + { + "ability": [{ "type" : "FEAR", "val": 0 }], + "skills": [0, 2] + }, + { + "ability": [{ "type" : "ALWAYS_MAXIMUM_DAMAGE", "val": 0 }], + "skills": [0, 3] + }, + { + "ability": [ + { "type" : "SHOOTER", "val": 0 }, + { "type" : "NO_MELEE_PENALTY", "val": 0 } + ], + "skills": [0, 4] + }, + { + "ability": [{ "type" : "BLOCKS_RETALIATION", "val": 0 }], + "skills": [0,5] + }, + { + "ability": [{ "type" : "UNLIMITED_RETALIATIONS", "val": 0 }], + "skills": [1, 2] + }, + { + "ability": [{ "type" : "ATTACKS_ALL_ADJACENT", "val": 0 }], + "skills": [1, 3] + }, + { + // TODO: Implement bonus that gives chance to completely block one enemy attack per turn + "ability": [{ "type" : "NONE", "val": 30 }], + "skills": [1, 4] + }, + { + "ability": [{ "type" : "FIRE_SHIELD", "val": 1 }], + "skills": [1, 5] + }, + { + "ability": [{ "type" : "ADDITIONAL_ATTACK", "val": 1 }], + "skills": [2, 3] + }, + { + "ability": [{ "type" : "HP_REGENERATION", "val": 50 }], + "skills": [2, 4] + }, + { + "ability": [{ "type" : "SPELL_AFTER_ATTACK", "val": 30, "subtype" : "spell.paralyze" }], + "skills": [2, 5] + }, + { + "ability": [{ "type" : "JOUSTING", "val": 5 }], + "skills": [3, 4] + }, + { + "ability": [{ "type" : "DEATH_STARE", "val": 1, "subtype" : "deathStareCommander" }], + "skills": [3,5] + }, + { + "ability": [{ "type" : "FLYING", "val": 0, "subtype" : "movementFlying" }], + "skills": [4,5] + } ] + + } diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index 9dcd9f10c..8f16b2e76 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -110,7 +110,7 @@ "fearless" : { "type" : "FEARFUL", - "valueType" : "INDEPENDENT_MAX", + "valueType" : "INDEPENDENT_MIN", "description" : "PLACEHOLDER", "val" : 0 diff --git a/config/factions/tower.json b/config/factions/tower.json index 5ce5bc6a7..e3194218e 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -202,6 +202,7 @@ "produce" : { "gold": 5000 }, "bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primarySkill.knowledge", "val": 15 }, + { "type": "COMBAT_MANA_BONUS", "val": 150 }, { "type": "FULL_MAP_SCOUTING" } ] }, diff --git a/docs/images/Bonus_System_Nodes.gv b/docs/images/Bonus_System_Nodes.gv index c72e6aea5..fdcea94b1 100644 --- a/docs/images/Bonus_System_Nodes.gv +++ b/docs/images/Bonus_System_Nodes.gv @@ -23,7 +23,7 @@ digraph mygraph { "Team" [ label =< - +
Team
Propagator: TEAM_PROPAGATOR
Propagator: TEAM
C++ Class: TeamState
Per-team node.
Game will put players without team
into a team with a single player
> @@ -31,7 +31,7 @@ digraph mygraph { "Player" [ label =< - +
Player
Propagator: PLAYER_PROPAGATOR
Propagator: PLAYER
C++ Class: CPlayerState
Per-player team.
All objects owned by a player
belong to such node
> @@ -50,13 +50,6 @@ digraph mygraph { Contains per-hero global bonuses, specialty bonuses,
primary and secondary skill bonuses, campaign primary skill bonus > ] - "Combat" [ - label =< - - - -
Combat
Propagator: BATTLE_WIDE
Node that contains both sides of a combat
Anything propagated to this node will affect both sides in combat
> - ] }; subgraph rankedHeroes { @@ -68,11 +61,11 @@ digraph mygraph { Hero that is currently
visiting owned or allied town > ] - "Garrisoned Hero" [ + "Hero defending town" [ fillcolor="#80808080" label =< - - + +
Garrisoned Hero
Hero that is currently
placed in a garrison of owned town
Hero defending town
Hero that is currently
fighting on a defending side in a siege
> ] "Wandering Hero" [ @@ -111,6 +104,7 @@ digraph mygraph { fillcolor="#80808080" label =< + @@ -140,7 +134,7 @@ digraph mygraph { "Town and visiting hero" [ label =<
Town
Propagator: TOWN
C++ Class: CGTownInstance
Represents a town on map.
Contains town building bonuses
- + @@ -190,6 +184,7 @@ digraph mygraph { "Army" [ label =<
Town and Visiting Hero
Propagator: VISITED_TOWN_AND_VISITOR
Propagator: TOWN_AND_VISITOR
C++ Class: CTownAndVisitingHero
Helper node that exists solely
to propagate bonuses to both town and visiting hero
Note: Neutral towns are attached to global node instead
+ @@ -253,13 +248,13 @@ digraph mygraph { "Creature Type" -> "Summon in Combat" "Creature Type" -> "Unit in Army" - "Town" -> "Garrisoned Hero" + "Town" -> "Hero defending town" "Town" -> "Army" "Neutral Army" -> "Army" "Owned Army" -> "Army" "Visiting Hero" -> "Hero" - "Garrisoned Hero" -> "Hero" + "Hero defending town" -> "Hero" "Wandering Hero" -> "Hero" "Hero" -> "Army" } diff --git a/docs/images/Bonus_System_Nodes.svg b/docs/images/Bonus_System_Nodes.svg index 9b1f71d6f..0e8c903b0 100644 --- a/docs/images/Bonus_System_Nodes.svg +++ b/docs/images/Bonus_System_Nodes.svg @@ -4,536 +4,542 @@ - - + + mygraph - + Global - - -Global - -Propagator: -GLOBAL_EFFECT - -C++ Class: -CGameState - -Global node to which -all map entities are connected - -Note: Not recruited heroes (such as in tavern) -are not attached to any node - -Contains global bonuses, global stack experience and difficulty bonuses - + + +Global + +Propagator: +GLOBAL_EFFECT + +C++ Class: +CGameState + +Global node to which +all map entities are connected + +Note: Not recruited heroes (such as in tavern) +are not attached to any node + +Contains global bonuses, global stack experience and difficulty bonuses + Team - - -Team - -Propagator: -TEAM_PROPAGATOR - -C++ Class: -TeamState - -Per-team node. -Game will put players without team -into a team with a single player - + + +Team + +Propagator: +TEAM + +C++ Class: +TeamState + +Per-team node. +Game will put players without team +into a team with a single player + Global->Team - - + + - + Neutral Army - - -Neutral Army - -Any army that is not owned by a player -Wandering monsters, Banks, Events, etc - + + +Neutral Army + +Any army that is not owned by a player +Wandering monsters, Banks, Events, etc + Global->Neutral Army - - + + Player - - -Player - -Propagator: -PLAYER_PROPAGATOR - -C++ Class: -CPlayerState - -Per-player team. -All objects owned by a player -belong to such node - + + +Player + +Propagator: +PLAYER + +C++ Class: +CPlayerState + +Per-player team. +All objects owned by a player +belong to such node + Team->Player - - + + - + Wandering Hero - - -Wandering Hero - -Hero that is currently -moving on map, outside of towns - + + +Wandering Hero + +Hero that is currently +moving on map, outside of towns + Player->Wandering Hero - - + + - + Owned Army - - -Owned Army - -Army owned by a player. -Mines, Garrisons, Dwellings - + + +Owned Army + +Army owned by a player. +Mines, Garrisons, Dwellings + Player->Owned Army - - + + - + Owned Object - + + +Owned Object -Owned Object +Other objects owned by a player, like Lighthouse -Other objects owned by a player, like Lighthouse - -Contains Flaggable Objects bonuses - +Contains Flaggable Objects bonuses + Player->Owned Object - - + + - + Town and visiting hero - - -Town and Visiting Hero - -Propagator: -VISITED_TOWN_AND_VISITOR - -C++ Class: -CTownAndVisitingHero - -Helper node that exists solely -to propagate bonuses to both town and visiting hero - -Note: Neutral towns are attached to global node instead - + + +Town and Visiting Hero + +Propagator: +TOWN_AND_VISITOR + +C++ Class: +CTownAndVisitingHero + +Helper node that exists solely +to propagate bonuses to both town and visiting hero + +Note: Neutral towns are attached to global node instead + Player->Town and visiting hero - - + + Hero - - -Hero - -Propagator: -HERO - -C++ Class: -CGHeroInstance - -Represents a hero, either owned by player or in prison. -Bonuses from specialty and secondary skills -are attached directly to this node - -Contains per-hero global bonuses, specialty bonuses, -primary and secondary skill bonuses, campaign primary skill bonus - + + +Hero + +Propagator: +HERO + +C++ Class: +CGHeroInstance + +Represents a hero, either owned by player or in prison. +Bonuses from specialty and secondary skills +are attached directly to this node + +Contains per-hero global bonuses, specialty bonuses, +primary and secondary skill bonuses, campaign primary skill bonus + Army - - -Army - -C++ Class: -CArmedInstance - -Represents any object that can hold army, -such as town, hero, mines, garrisons, wandering monsters - -Contain anti-magic garrison bonus, faction mixing morale bonus - + + +Army + +Propagator: +ARMY + +C++ Class: +CArmedInstance + +Represents any object that can hold army, +such as town, hero, mines, garrisons, wandering monsters + +Contain anti-magic garrison bonus, faction mixing morale bonus + Hero->Army - - - - - -Combat - - -Combat - -Propagator: -BATTLE_WIDE - -C++ Class: -BattleInfo - -Node that contains both sides of a combat -Anything propagated to this node will affect both sides in combat - -Contains battlefield and native terrain bonuses - - - - -Combat->Army - - + + - + Visiting Hero - - -Visiting Hero - -Hero that is currently -visiting owned or allied town - + + +Visiting Hero + +Hero that is currently +visiting owned or allied town + Visiting Hero->Hero - - + + - - -Garrisoned Hero - - -Garrisoned Hero - -Hero that is currently -placed in a garrison of owned town - + + +Hero defending town + + +Hero defending town + +Hero that is currently +fighting on a defending side in a siege + - + -Garrisoned Hero->Hero - - +Hero defending town->Hero + + Wandering Hero->Hero - - + + Neutral Army->Army - - + + Owned Army->Army - - + + - + Town - - -Town - -C++ Class: -CGTownInstance - -Represents a town on map. - -Contains town building bonuses - + + +Town + +Propagator: +TOWN + +C++ Class: +CGTownInstance + +Represents a town on map. + +Contains town building bonuses + - + -Town->Garrisoned Hero - - +Town->Hero defending town + + Town->Army - - + + - + Artifact Instance - - -Artifact Instance - -C++ Class: -CArtifactInstance - -Represents a particular instance of an artifact - that hero can equip or trade - -Contains bonuses of spell scrolls and growing artifacts - + + +Artifact Instance + +C++ Class: +CArtifactInstance + +Represents a particular instance of an artifact + that hero can equip or trade + +Contains bonuses of spell scrolls and growing artifacts + Artifact Instance->Hero - - + + - + Boat - - -Boat - -C++ Class: -CGBoat - -Represents a boat or other type of transport. - -Contains bonuses provided to boarded hero - + + +Boat + +C++ Class: +CGBoat + +Represents a boat or other type of transport. + +Contains bonuses provided to boarded hero + Boat->Hero - - + + Town and visiting hero->Visiting Hero - - + + Town and visiting hero->Town - - + + + + + +Combat + + +Combat + +Propagator: +BATTLE_WIDE + +C++ Class: +BattleInfo + +Node that contains both sides of a combat +Anything propagated to this node will affect both sides in combat + +Contains battlefield and native terrain bonuses + + + + +Combat->Army + + Creature Type - - -Creature Type - -C++ Class: -CCreature - -Represents a creature type, such as Pikeman or Archer - -Contains creature abilities bonuses, stack experience bonuses - + + +Creature Type + +C++ Class: +CCreature + +Represents a creature type, such as Pikeman or Archer + +Contains creature abilities bonuses, stack experience bonuses + Unit in Army - - -Unit in Army - -C++ Class: -CStackInstance - -Represents a unit that is part of a army -A unit always has a creature type, -belongs to an army and has stack size - + + +Unit in Army + +C++ Class: +CStackInstance + +Represents a unit that is part of a army +A unit always has a creature type, +belongs to an army and has stack size + Creature Type->Unit in Army - - + + Commander - - -Commander - -C++ Class: -CCommanderInstance - -Represents a hero commander, WoG feature - + + +Commander + +C++ Class: +CCommanderInstance + +Represents a hero commander, WoG feature + Creature Type->Commander - - + + Summon in Combat - - -Summon in Combat - -C++ Class: -CStack - -Represents any unit that was added in combat, -and may not remain after combat - + + +Summon in Combat + +C++ Class: +CStack + +Represents any unit that was added in combat, +and may not remain after combat + Creature Type->Summon in Combat - - + + Artifact Type - - -Artifact Type - -C++ Class: -CArtifact - -Represents an artifact type, for example Ring of Life - -Contains fixed bonuses of artifacts - + + +Artifact Type + +C++ Class: +CArtifact + +Represents an artifact type, for example Ring of Life + +Contains fixed bonuses of artifacts + Artifact Type->Artifact Instance - - + + Artifact Component - - -Artifact Component - -C++ Class: -CArtifactInstance - -For combined, non-fused artifacts, -instances of components are attached to instance of combined artifact - + + +Artifact Component + +C++ Class: +CArtifactInstance + +For combined, non-fused artifacts, +instances of components are attached to instance of combined artifact + Artifact Component->Artifact Instance - - + + Army->Unit in Army - - + + Army->Commander - - + + Army->Summon in Combat - - + + Unit in Combat - - -Unit in Combat - -C++ Class: -CStack - -Represents current state of a unit during combat, -can be affected by spells or receive damage - + + +Unit in Combat + +C++ Class: +CStack + +Represents current state of a unit during combat, +can be affected by spells or receive damage + Unit in Army->Unit in Combat - - + + Commander->Unit in Combat - - + + diff --git a/docs/modders/Bonus/Bonus_Types.md b/docs/modders/Bonus/Bonus_Types.md index c57663eef..8f6ca055d 100644 --- a/docs/modders/Bonus/Bonus_Types.md +++ b/docs/modders/Bonus/Bonus_Types.md @@ -264,6 +264,12 @@ Defines maximum level of spells than hero can learn from any source (Wisdom) - val: maximal level to learn +### COMBAT_MANA_BONUS + +Grants affected hero additional mana for the duration of combat. Bonus may give total mana above mana limit. Any additional mana not spent during combat will be lost. + +- val: amount of additional mana + ## Hero specialties ### SPECIAL_SPELL_LEV diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 6d8c3c73d..086fc936c 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -470,6 +470,16 @@ CCreatureHandler::CCreatureHandler() void CCreatureHandler::loadCommanders() { + const auto & parseBonusWithCompatibility = [](const JsonNode & node) + { + // MOD COMPATIBILITY: 1.6 mods use old, vector format. + // NOTE: please also remove parseBonus itself - commanders is the last place that uses it + if (node.isVector()) + return JsonUtils::parseBonus(node.Vector()); + else + return JsonUtils::parseBonus(node); + }; + auto configResource = JsonPath::builtin("config/commanders.json"); std::string modSource = LIBRARY->modh->findResourceOrigin(configResource); @@ -480,7 +490,7 @@ void CCreatureHandler::loadCommanders() for (auto bonus : config["bonusPerLevel"].Vector()) { - commanderLevelPremy.push_back(JsonUtils::parseBonus(bonus.Vector())); + commanderLevelPremy.push_back(parseBonusWithCompatibility(bonus)); } int level = 0; @@ -497,15 +507,20 @@ void CCreatureHandler::loadCommanders() for (auto ability : config["abilityRequirements"].Vector()) { std::pair >, std::pair > a; - JsonVector & abilities = ability["ability"].Vector(); - a.first = std::vector >(); - if (abilities[0].isVector()) - for (int i = 0; i < abilities.size(); i++) - a.first.push_back(JsonUtils::parseBonus(abilities[i].Vector())); - else - a.first.push_back(JsonUtils::parseBonus(ability["ability"].Vector())); - a.second.first = static_cast(ability["skills"].Vector()[0].Float()); - a.second.second = static_cast(ability["skills"].Vector()[1].Float()); + JsonNode & abilities = ability["ability"]; + + if (abilities[0].isString()) // old format with single bonus + { + a.first.push_back(parseBonusWithCompatibility(abilities)); + } + else + { + for (const auto & ability : abilities.Vector()) + a.first.push_back(parseBonusWithCompatibility(ability)); + } + + a.second.first = static_cast(ability["skills"][0].Float()); + a.second.second = static_cast(ability["skills"][1].Float()); skillRequirements.push_back (a); } } diff --git a/lib/battle/SideInBattle.cpp b/lib/battle/SideInBattle.cpp index b9c3ca8be..3b4b7d79c 100644 --- a/lib/battle/SideInBattle.cpp +++ b/lib/battle/SideInBattle.cpp @@ -19,7 +19,11 @@ void SideInBattle::init(const CGHeroInstance * Hero, const CArmedInstance * Army { armyObjectID = Army->id; if (Hero) + { heroID = Hero->id; + initialMana = Hero->mana; + additionalMana = Hero->valOfBonuses(BonusType::COMBAT_MANA_BONUS); + } switch(Army->ID.toEnum()) { diff --git a/lib/battle/SideInBattle.h b/lib/battle/SideInBattle.h index 3a345a244..752fddb3f 100644 --- a/lib/battle/SideInBattle.h +++ b/lib/battle/SideInBattle.h @@ -28,6 +28,8 @@ struct DLL_LINKAGE SideInBattle : public GameCallbackHolder uint32_t castSpellsCount = 0; //how many spells each side has been cast this turn std::vector usedSpellsHistory; //every time hero casts spell, it's inserted here -> eagle eye skill int32_t enchanterCounter = 0; //tends to pass through 0, so sign is needed + int32_t initialMana = 0; + int32_t additionalMana = 0; void init(const CGHeroInstance * Hero, const CArmedInstance * Army); const CArmedInstance * getArmy() const; @@ -41,6 +43,8 @@ struct DLL_LINKAGE SideInBattle : public GameCallbackHolder h & castSpellsCount; h & usedSpellsHistory; h & enchanterCounter; + h & initialMana; + h & additionalMana; } }; diff --git a/lib/bonuses/BonusEnum.h b/lib/bonuses/BonusEnum.h index 4430bc1f7..5182e5e10 100644 --- a/lib/bonuses/BonusEnum.h +++ b/lib/bonuses/BonusEnum.h @@ -192,6 +192,7 @@ class JsonNode; BONUS_NAME(FULL_MAP_SCOUTING) /*Skyship*/\ BONUS_NAME(FULL_MAP_DARKNESS) /*opposite to Skyship*/\ BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\ + BONUS_NAME(COMBAT_MANA_BONUS) /* Additional mana per combat */ \ /* end of list */ diff --git a/lib/gameState/GameStatePackVisitor.cpp b/lib/gameState/GameStatePackVisitor.cpp index 64a3e3a11..cb2a6fa95 100644 --- a/lib/gameState/GameStatePackVisitor.cpp +++ b/lib/gameState/GameStatePackVisitor.cpp @@ -1176,18 +1176,27 @@ void GameStatePackVisitor::visitBattleStart(BattleStart & pack) pack.info->battleID = gs.nextBattleID; pack.info->localInit(); - if (pack.info->getDefendedTown() && pack.info->getSideHero(BattleSide::DEFENDER)) + if (pack.info->getDefendedTown() && pack.info->getSide(BattleSide::DEFENDER).heroID.hasValue()) { CGTownInstance * town = gs.getTown(pack.info->townID); CGHeroInstance * hero = gs.getHero(pack.info->getSideHero(BattleSide::DEFENDER)->id); - if (town->getVisitingHero() == hero) + if (hero) { hero->detachFrom(town->townAndVis); hero->attachTo(*town); } } + for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER}) + { + if (pack.info->getSide(i).heroID.hasValue()) + { + CGHeroInstance * hero = gs.getHero(pack.info->getSideHero(i)->id); + hero->mana = pack.info->getSide(i).initialMana + pack.info->getSide(i).additionalMana; + } + } + gs.currentBattles.push_back(std::move(pack.info)); gs.nextBattleID = BattleID(gs.nextBattleID.getNum() + 1); } @@ -1366,7 +1375,7 @@ void GameStatePackVisitor::restorePreBattleState(BattleID battleID) CGTownInstance * town = gs.getTown(currentBattle.townID); CGHeroInstance * hero = gs.getHero(currentBattle.getSideHero(BattleSide::DEFENDER)->id); - if (town->getVisitingHero() == hero) + if (hero) { hero->detachFrom(*town); hero->attachTo(town->townAndVis); @@ -1383,6 +1392,17 @@ void GameStatePackVisitor::visitBattleCancelled(BattleCancelled & pack) return battle->battleID == pack.battleID; }); + const auto & currentBattle = **battleIter; + + for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER}) + { + if (currentBattle.getSide(i).heroID.hasValue()) + { + CGHeroInstance * hero = gs.getHero(currentBattle.getSideHero(i)->id); + hero->mana = currentBattle.getSide(i).initialMana; + } + } + assert(battleIter != gs.currentBattles.end()); gs.currentBattles.erase(battleIter); } @@ -1401,14 +1421,23 @@ void GameStatePackVisitor::visitBattleResultsApplied(BattleResultsApplied & pack for(auto & movingPack : pack.movingArtifacts) movingPack.visit(*this); - const auto currentBattle = std::find_if(gs.currentBattles.begin(), gs.currentBattles.end(), - [&](const auto & battle) - { - return battle->battleID == pack.battleID; - }); + auto battleIter = boost::range::find_if(gs.currentBattles, [&](const auto & battle) + { + return battle->battleID == pack.battleID; + }); + const auto & currentBattle = **battleIter; - assert(currentBattle != gs.currentBattles.end()); - gs.currentBattles.erase(currentBattle); + for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER}) + { + if (currentBattle.getSide(i).heroID.hasValue()) + { + CGHeroInstance * hero = gs.getHero(currentBattle.getSideHero(i)->id); + hero->mana = std::min(hero->mana, currentBattle.getSide(i).initialMana); + } + } + + assert(battleIter != gs.currentBattles.end()); + gs.currentBattles.erase(battleIter); } void GameStatePackVisitor::visitBattleObstaclesChanged(BattleObstaclesChanged & pack) diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 5eaffa95c..bd199a1d0 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -8,6 +8,7 @@ * */ #include "StdInc.h" +#include "CConfigHandler.h" #include "CObjectClassesHandler.h" #include "../filesystem/Filesystem.h" @@ -205,9 +206,20 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject, size_t index) { - assert(identifier.find(':') == std::string::npos); assert(!scope.empty()); + if (settings["mods"]["validation"].String() != "off") + { + size_t separator = identifier.find(':'); + + if (separator != std::string::npos) + { + std::string modName = identifier.substr(0, separator); + std::string objectName = identifier.substr(separator + 1); + logMod->warn("Mod %s: Map object type with format '%s' will add new map object, not modify it! Please use '%s' form and add dependency on mod '%s' instead!", scope, identifier, modName, identifier ); + } + } + std::string handler = baseObject->handlerName; if(!handlerConstructors.count(handler)) { diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 191fb1b87..8ba22b442 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1355,10 +1355,7 @@ CBonusSystemNode & CGHeroInstance::whereShouldBeAttached(CGameState & gs) if(visitedTown.hasValue()) { auto town = gs.getTown(visitedTown); - if(isGarrisoned()) - return *town; - else - return town->townAndVis; + return town->townAndVis; } else return CArmedInstance::whereShouldBeAttached(gs); diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index 42956382f..7823aa3b3 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -76,7 +76,7 @@ void BattleProcessor::restartBattle(const BattleID & battleID, const CArmedInsta if(heroes[i]) { SetMana restoreInitialMana; - restoreInitialMana.val = lastBattleQuery->initialHeroMana[i]; + restoreInitialMana.val = battle->getSide(i).initialMana; restoreInitialMana.hid = heroes[i]->id; restoreInitialMana.mode = ChangeValueMode::ABSOLUTE; gameHandler->sendAndApply(restoreInitialMana); @@ -134,12 +134,6 @@ void BattleProcessor::startBattle(const CArmedInstance *army1, const CArmedInsta else { auto newBattleQuery = std::make_shared(gameHandler, battle); - - // store initial mana to reset if battle has been restarted - for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER}) - if(heroes[i]) - newBattleQuery->initialHeroMana[i] = heroes[i]->mana; - gameHandler->queries->addQuery(newBattleQuery); } diff --git a/server/queries/BattleQueries.h b/server/queries/BattleQueries.h index 1c09fd9dc..8df9d09c2 100644 --- a/server/queries/BattleQueries.h +++ b/server/queries/BattleQueries.h @@ -22,7 +22,6 @@ class CBattleQuery : public CQuery { public: BattleSideArray belligerents; - BattleSideArray initialHeroMana; BattleID battleID; std::optional result;
Army
Propagator: ARMY
C++ Class: CArmedInstance
Represents any object that can hold army,
such as town, hero, mines, garrisons, wandering monsters
Contain anti-magic garrison bonus, faction mixing morale bonus