From 39a2c29c97aeba396de47f9c1dfe7d10861cf7db Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 31 Aug 2024 21:04:32 +0000 Subject: [PATCH] Removed creatureBank flag from battle, battles now receive BattleLayout struct that defines how units are placed in combat --- AI/BattleAI/StackWithBonuses.cpp | 6 +- AI/BattleAI/StackWithBonuses.h | 2 +- client/Client.h | 5 +- config/battleStartpos.json | 87 ---------------- config/gameConfig.json | 73 ++++++++++++- config/objects/creatureBanks.json | 12 +++ config/schemas/rewardable.json | 4 + lib/CMakeLists.txt | 2 + lib/GameSettings.cpp | 1 + lib/IGameCallback.h | 6 +- lib/IGameSettings.h | 1 + lib/battle/BattleInfo.cpp | 147 ++++++++++----------------- lib/battle/BattleInfo.h | 8 +- lib/battle/BattleLayout.cpp | 81 +++++++++++++++ lib/battle/BattleLayout.h | 39 +++++++ lib/battle/IBattleState.h | 3 +- lib/mapObjects/CBank.cpp | 2 +- lib/mapObjects/CGCreature.cpp | 2 +- lib/mapObjects/CGDwelling.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/mapObjects/CGPandoraBox.cpp | 4 +- lib/mapObjects/CGTownInstance.cpp | 3 +- lib/mapObjects/CRewardableObject.cpp | 10 +- lib/mapObjects/MiscObjects.cpp | 8 +- lib/rewardable/Configuration.h | 7 +- lib/rewardable/Info.cpp | 1 + server/CGameHandler.cpp | 13 +-- server/CGameHandler.h | 5 +- server/battles/BattleProcessor.cpp | 35 +++---- server/battles/BattleProcessor.h | 11 +- server/queries/BattleQueries.cpp | 5 +- test/game/CGameStateTest.cpp | 4 +- test/mock/mock_IGameCallback.h | 5 +- test/mock/mock_battle_IBattleState.h | 3 +- 34 files changed, 341 insertions(+), 258 deletions(-) delete mode 100644 config/battleStartpos.json create mode 100644 lib/battle/BattleLayout.cpp create mode 100644 lib/battle/BattleLayout.h diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index 9ded2518f..fbecaf541 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -12,6 +12,7 @@ #include +#include "../../lib/battle/BattleLayout.h" #include "../../lib/CStack.h" #include "../../lib/ScriptHandler.h" #include "../../lib/networkPacks/PacksForClientBattle.h" @@ -479,10 +480,9 @@ int3 HypotheticBattle::getLocation() const return int3(-1, -1, -1); } -bool HypotheticBattle::isCreatureBank() const +BattleLayout HypotheticBattle::getLayout() const { - // TODO - return false; + return subject->getBattle()->getLayout(); } int64_t HypotheticBattle::getTreeVersion() const diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index 3d249d428..f5b4fac23 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -160,7 +160,7 @@ public: int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override; std::vector getUsedSpells(BattleSide side) const override; int3 getLocation() const override; - bool isCreatureBank() const override; + BattleLayout getLayout() const override; int64_t getTreeVersion() const; diff --git a/client/Client.h b/client/Client.h index 600528188..c5b763a49 100644 --- a/client/Client.h +++ b/client/Client.h @@ -193,9 +193,8 @@ public: void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {}; void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override {}; void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {}; - void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero - void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used - void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle + void startBattle(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, const BattleLayout & layout, const CGTownInstance * town) override {}; //use hero=nullptr for no hero + void startBattle(const CArmedInstance * army1, const CArmedInstance * army2) override {}; //if any of armies is hero, hero will be used bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;}; void giveHeroBonus(GiveBonus * bonus) override {}; void setMovePoints(SetMovePoints * smp) override {}; diff --git a/config/battleStartpos.json b/config/battleStartpos.json deleted file mode 100644 index 5921cc071..000000000 --- a/config/battleStartpos.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "battle_positions": - [ - { - "name" : "attackerLoose", // loose formation, attacker - "levels": [ - [ 86 ], - [ 35, 137 ], - [ 35, 86, 137 ], - [ 1, 69, 103, 171 ], - [ 1, 35, 86, 137, 171 ], - [ 1, 35, 69, 103, 137, 171 ], - [ 1, 35, 69, 86, 103, 137, 171 ] - ] - }, - - { - "name" : "defenderLoose", // loose formation, defender - "levels": [ - [ 100 ], - [ 49, 151 ], - [ 49, 100, 151 ], - [ 15, 83, 117, 185 ], - [ 15, 49, 100, 151, 185 ], - [ 15, 49, 83, 117, 151, 185 ], - [ 15, 49, 83, 100, 117, 151, 185 ] - ] - }, - - { - "name" : "attackerTight", // tight formation, attacker - "levels": [ - [ 86 ], - [ 69, 103 ], - [ 69, 86, 103 ], - [ 35, 69, 103, 137 ], - [ 35, 69, 86, 103, 137 ], - [ 1, 35, 69, 103, 137, 171 ], - [ 1, 35, 69, 86, 103, 137, 171 ] - ] - }, - - { - "name" : "defenderTight", // tight formation, defender - "levels": [ - [ 100 ], - [ 83, 117 ], - [ 83, 100, 117 ], - [ 49, 83, 117, 151 ], - [ 49, 83, 100, 117, 151 ], - [ 15, 49, 83, 117, 151, 185 ], - [ 15, 49, 83, 100, 117, 151, 185 ] - ] - }, - - { - "name" : "attackerCreBank", // creature bank, attacker - "levels": [ - [ 57 ], - [ 57, 61 ], - [ 57, 61, 90 ], - [ 57, 61, 90, 93 ], - [ 57, 61, 90, 93, 96 ], - [ 57, 61, 90, 93, 96, 125 ], - [ 57, 61, 90, 93, 96, 125, 129 ] - ] - }, - - { - "name" : "defenderCreBank", // creature bank, defender - "levels": [ - [ 15 ], - [ 15, 185 ], - [ 15, 185, 172 ], - [ 15, 185, 172, 2 ], - [ 15, 185, 172, 2, 100 ], - [ 15, 185, 172, 2, 100, 86 ], - [ 15, 185, 172, 2, 100, 86, 8 ] - ] - } - ], - "commanderPositions": - { - "field" : [88, 98], //attacker/defender - "creBank" : [95, 8] //not expecting defendig hero at bank, but hell knows - } -} diff --git a/config/gameConfig.json b/config/gameConfig.json index 802c92f02..5ceae7dcc 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -342,8 +342,77 @@ // limit of damage reduction that can be achieved by overpowering defense points "defensePointDamageFactorCap": 0.7, // If set to true, double-wide creatures will trigger obstacle effect when moving one tile forward or backwards - "oneHexTriggersObstacles": false - }, + "oneHexTriggersObstacles": false, + + // Positions of units on start of the combat + // If battle does not defines specific configuration, 'default' configuration will be used + // Configuration must define either 'attackerUnits' list of 7 elements or both 'attackerUnitsLoose' and 'attackerUnitsTight' lists of 7 elements, 1..7 elements each + // Similarly, for defender configuration must have either 'defenderUnits' or both 'defenderUnitsLoose' and 'defenderUnitsTight' + "layouts" : { + "default" : { + "tacticsAllowed" : true, + "obstaclesAllowed" : true, + "attackerCommander" : 88, + "defenderCommander" : 98, + "attackerWarMachines" : [ 52, 18, 154, 120 ], + "defenderWarMachines" : [ 66, 32, 168, 134 ], + "attackerUnitsLoose": [ + [ 86 ], + [ 35, 137 ], + [ 35, 86, 137 ], + [ 1, 69, 103, 171 ], + [ 1, 35, 86, 137, 171 ], + [ 1, 35, 69, 103, 137, 171 ], + [ 1, 35, 69, 86, 103, 137, 171 ] + ], + "defenderUnitsLoose": [ + [ 100 ], + [ 49, 151 ], + [ 49, 100, 151 ], + [ 15, 83, 117, 185 ], + [ 15, 49, 100, 151, 185 ], + [ 15, 49, 83, 117, 151, 185 ], + [ 15, 49, 83, 100, 117, 151, 185 ] + ], + "attackerUnitsTight": [ + [ 86 ], + [ 69, 103 ], + [ 69, 86, 103 ], + [ 35, 69, 103, 137 ], + [ 35, 69, 86, 103, 137 ], + [ 1, 35, 69, 103, 137, 171 ], + [ 1, 35, 69, 86, 103, 137, 171 ] + ], + "defenderUnitsTight": [ + [ 100 ], + [ 83, 117 ], + [ 83, 100, 117 ], + [ 49, 83, 117, 151 ], + [ 49, 83, 100, 117, 151 ], + [ 15, 49, 83, 117, 151, 185 ], + [ 15, 49, 83, 100, 117, 151, 185 ] + ] + }, + // Configuration for creature banks with single-tile enemies + "creatureBankNarrow" : { + "tacticsAllowed" : false, + "obstaclesAllowed" : false, + "attackerCommander" : 95, + "defenderCommander" : 8, + "attackerUnits": [ 57, 61, 90, 93, 96, 125, 129 ], + "defenderUnits": [ 15, 185, 172, 2, 100, 87, 8 ] + }, + // Configuration for creature banks with double-wide enemies + "creatureBankWide" : { + "tacticsAllowed" : false, + "obstaclesAllowed" : false, + "attackerCommander" : 95, + "defenderCommander" : 8, + "attackerUnits": [ 57, 61, 90, 93, 96, 125, 129 ], + "defenderUnits": [ 15, 185, 171, 1, 100, 86, 8 ] + } + } + }, "creatures": { diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json index f05744014..b1d84cebb 100644 --- a/config/objects/creatureBanks.json +++ b/config/objects/creatureBanks.json @@ -25,6 +25,7 @@ "onVisitedMessage" : 33, "visitMode" : "once", "selectMode" : "selectFirst", + "guardsLayout" : "creatureBankNarrow", "rewards" : [ { "message" : 34, @@ -125,6 +126,7 @@ "onVisitedMessage" : 33, "visitMode" : "once", "selectMode" : "selectFirst", + "guardsLayout" : "creatureBankNarrow", "rewards" : [ { "message" : 34, @@ -207,6 +209,7 @@ "onVisitedMessage" : 33, "visitMode" : "once", "selectMode" : "selectFirst", + "guardsLayout" : "creatureBankWide", "rewards" : [ { "message" : 34, @@ -273,6 +276,7 @@ "onVisitedMessage" : 33, "visitMode" : "once", "selectMode" : "selectFirst", + "guardsLayout" : "creatureBankNarrow", "rewards" : [ { "message" : 34, @@ -354,6 +358,7 @@ "onVisitedMessage" : 33, "visitMode" : "once", "selectMode" : "selectFirst", + "guardsLayout" : "creatureBankWide", "rewards" : [ { "message" : 34, @@ -436,6 +441,7 @@ "onVisitedMessage" : 33, "visitMode" : "once", "selectMode" : "selectFirst", + "guardsLayout" : "creatureBankWide", "rewards" : [ { "message" : 34, @@ -518,6 +524,7 @@ "onVisitedMessage" : 33, "visitMode" : "once", "selectMode" : "selectFirst", + "guardsLayout" : "creatureBankNarrow", "rewards" : [ { "message" : 34, @@ -600,6 +607,7 @@ "bonuses" : [ { "type" : "MORALE", "val" : -1, "duration" : "ONE_BATTLE", "description" : 99 } ] } ], + "guardsLayout" : "creatureBankNarrow", "rewards" : [ { "message" : 124, @@ -695,6 +703,7 @@ "bonuses" : [ { "type" : "MORALE", "val" : -1, "duration" : "ONE_BATTLE", "description" : 101 } ] } ], + "guardsLayout" : "creatureBankWide", "rewards" : [ { "message" : 43, @@ -789,6 +798,7 @@ "bonuses" : [ { "type" : "MORALE", "val" : -1, "duration" : "ONE_BATTLE" } ] } ], + "guardsLayout" : "creatureBankNarrow", "rewards" : [ { "appearChance" : { "min" : 0, "max" : 30 }, @@ -878,6 +888,7 @@ "selectMode" : "selectFirst", "onGuardedMessage" : 47, "onVisitedMessage" : 33, + "guardsLayout" : "creatureBankWide", "rewards" : [ { "message" : 34, @@ -991,6 +1002,7 @@ "bonuses" : [ { "type" : "LUCK", "val" : -2, "duration" : "ONE_BATTLE", "description" : 70 } ] } ], + "guardsLayout" : "default", "rewards" : [ { "message" : 106, diff --git a/config/schemas/rewardable.json b/config/schemas/rewardable.json index 150349a92..afa4119e0 100644 --- a/config/schemas/rewardable.json +++ b/config/schemas/rewardable.json @@ -302,6 +302,10 @@ "type" : "string" }, + "guardsLayout": { + "type" : "string" + }, + "visitLimiter": { "$ref" : "#/definitions/limiter" }, "selectMode": { diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 002b83951..d691daaa3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -48,6 +48,7 @@ set(lib_MAIN_SRCS battle/BattleAttackInfo.cpp battle/BattleHex.cpp battle/BattleInfo.cpp + battle/BattleLayout.cpp battle/BattleProxy.cpp battle/BattleStateInfoForRetreat.cpp battle/CBattleInfoCallback.cpp @@ -404,6 +405,7 @@ set(lib_MAIN_HEADERS battle/BattleAttackInfo.h battle/BattleHex.h battle/BattleInfo.h + battle/BattleLayout.h battle/BattleSide.h battle/BattleStateInfoForRetreat.h battle/BattleProxy.h diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 3e2e2b8ef..4a34df857 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -47,6 +47,7 @@ const std::vector GameSettings::settingProperties = {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" }, diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index a53691f4e..a9cc7b655 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -28,6 +28,7 @@ struct TeleportDialog; struct StackLocation; struct ArtifactLocation; struct BankConfig; +struct BattleLayout; class CCreatureSet; class CStackBasicDescriptor; class CGCreature; @@ -128,9 +129,8 @@ public: virtual void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0; virtual void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero)=0; virtual void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0; - virtual void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr)=0; //use hero=nullptr for no hero - virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false)=0; //if any of armies is hero, hero will be used - virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle + virtual void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town)=0; //use hero=nullptr for no hero + virtual void startBattle(const CArmedInstance *army1, const CArmedInstance *army2)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveMove, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL)=0; virtual bool swapGarrisonOnSiege(ObjectInstanceID tid)=0; virtual void giveHeroBonus(GiveBonus * bonus)=0; diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 85ffeefc0..c32f71785 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -25,6 +25,7 @@ enum class EGameSettings COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, COMBAT_GOOD_LUCK_DICE, COMBAT_GOOD_MORALE_DICE, + COMBAT_LAYOUTS, CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, CREATURES_DAILY_STACK_EXPERIENCE, diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index c098f5298..1a8a89e8f 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -9,6 +9,8 @@ */ #include "StdInc.h" #include "BattleInfo.h" + +#include "BattleLayout.h" #include "CObstacleInstance.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" @@ -74,22 +76,6 @@ void BattleInfo::localInit() exportBonuses(); } -namespace CGH -{ - static void readBattlePositions(const JsonNode &node, std::vector< std::vector > & dest) - { - for(const JsonNode &level : node.Vector()) - { - std::vector pom; - for(const JsonNode &value : level.Vector()) - { - pom.push_back(static_cast(value.Float())); - } - - dest.push_back(pom); - } - } -} //RNG that works like H3 one struct RandGen @@ -173,22 +159,20 @@ struct RangeGenerator std::function myRand; }; -BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const BattleField & battlefieldType, BattleSideArray armies, BattleSideArray heroes, bool creatureBank, const CGTownInstance * town) +BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const BattleField & battlefieldType, BattleSideArray armies, BattleSideArray heroes, const BattleLayout & layout, const CGTownInstance * town) { CMP_stack cmpst; - auto * curB = new BattleInfo(); + auto * curB = new BattleInfo(layout); for(auto i : { BattleSide::LEFT_SIDE, BattleSide::RIGHT_SIDE}) curB->sides[i].init(heroes[i], armies[i]); - std::vector & stacks = (curB->stacks); curB->tile = tile; curB->battlefieldType = battlefieldType; curB->round = -2; curB->activeStack = -1; - curB->creatureBank = creatureBank; curB->replayAllowed = false; if(town) @@ -225,7 +209,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const } //randomize obstacles - if (town == nullptr && !creatureBank) //do it only when it's not siege and not creature bank + if (layout.obstaclesAllowed) { RandGen r{}; auto ourRand = [&](){ return r.rand(); }; @@ -321,63 +305,40 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const } } - //reading battleStartpos - add creatures AFTER random obstacles are generated - //TODO: parse once to some structure - BattleSideArray>> looseFormations; - BattleSideArray>> tightFormations; - BattleSideArray>> creBankFormations; - BattleSideArray commanderField; - BattleSideArray commanderBank; - const JsonNode config(JsonPath::builtin("config/battleStartpos.json")); - const JsonVector &positions = config["battle_positions"].Vector(); - - CGH::readBattlePositions(positions[0]["levels"], looseFormations[BattleSide::ATTACKER]); - CGH::readBattlePositions(positions[1]["levels"], looseFormations[BattleSide::DEFENDER]); - CGH::readBattlePositions(positions[2]["levels"], tightFormations[BattleSide::ATTACKER]); - CGH::readBattlePositions(positions[3]["levels"], tightFormations[BattleSide::DEFENDER]); - CGH::readBattlePositions(positions[4]["levels"], creBankFormations[BattleSide::ATTACKER]); - CGH::readBattlePositions(positions[5]["levels"], creBankFormations[BattleSide::DEFENDER]); - - commanderField[BattleSide::ATTACKER] = config["commanderPositions"]["field"][0].Integer(); - commanderField[BattleSide::DEFENDER] = config["commanderPositions"]["field"][1].Integer(); - - commanderBank[BattleSide::ATTACKER] = config["commanderPositions"]["creBank"][0].Integer(); - commanderBank[BattleSide::DEFENDER] = config["commanderPositions"]["creBank"][1].Integer(); - //adding war machines - if(!creatureBank) + //Checks if hero has artifact and create appropriate stack + auto handleWarMachine = [&](BattleSide side, const ArtifactPosition & artslot, BattleHex hex) { - //Checks if hero has artifact and create appropriate stack - auto handleWarMachine = [&](BattleSide side, const ArtifactPosition & artslot, BattleHex hex) + const CArtifactInstance * warMachineArt = heroes[side]->getArt(artslot); + + if(nullptr != warMachineArt && hex.isValid()) { - const CArtifactInstance * warMachineArt = heroes[side]->getArt(artslot); + CreatureID cre = warMachineArt->artType->getWarMachine(); - if(nullptr != warMachineArt) - { - CreatureID cre = warMachineArt->artType->getWarMachine(); - - if(cre != CreatureID::NONE) - curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex); - } - }; - - if(heroes[BattleSide::ATTACKER]) - { - - handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH1, 52); - handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH2, 18); - handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH3, 154); - if(town && town->fortificationsLevel().wallsHealth > 0) - handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH4, 120); + if(cre != CreatureID::NONE) + curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex); } + }; - if(heroes[BattleSide::DEFENDER]) - { - if(!town) //defending hero shouldn't receive ballista (bug #551) - handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH1, 66); - handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH2, 32); - handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH3, 168); - } + if(heroes[BattleSide::ATTACKER]) + { + auto warMachineHexes = layout.warMachines.at(BattleSide::ATTACKER); + + handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH1, warMachineHexes.at(0)); + handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH2, warMachineHexes.at(1)); + handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH3, warMachineHexes.at(2)); + if(town && town->fortificationsLevel().wallsHealth > 0) + handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH4, warMachineHexes.at(3)); + } + + if(heroes[BattleSide::DEFENDER]) + { + auto warMachineHexes = layout.warMachines.at(BattleSide::DEFENDER); + + if(!town) //defending hero shouldn't receive ballista (bug #551) + handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH1, warMachineHexes.at(0)); + handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH2, warMachineHexes.at(1)); + handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH3, warMachineHexes.at(2)); } //war machines added @@ -390,20 +351,13 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const int k = 0; //stack serial for(auto i = armies[side]->Slots().begin(); i != armies[side]->Slots().end(); i++, k++) { - std::vector *formationVector = nullptr; - if(armies[side]->formation == EArmyFormation::TIGHT ) - formationVector = &tightFormations[side][formationNo]; - else - formationVector = &looseFormations[side][formationNo]; + const BattleHex & pos = layout.units.at(side).at(k); - if(creatureBank) - formationVector = &creBankFormations[side][formationNo]; + //if(creatureBank && i->second->type->isDoubleWide()) + // pos += side == BattleSide::RIGHT_SIDE ? BattleHex::LEFT : BattleHex::RIGHT; - BattleHex pos = (k < formationVector->size() ? formationVector->at(k) : 0); - if(creatureBank && i->second->type->isDoubleWide()) - pos += side == BattleSide::RIGHT_SIDE ? BattleHex::LEFT : BattleHex::RIGHT; - - curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos); + if (pos.isValid()) + curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos); } } @@ -412,9 +366,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const { if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive) { - curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, creatureBank ? commanderBank[i] : commanderField[i]); + curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, layout.commanders.at(i)); } - } if (curB->town) @@ -453,8 +406,6 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const ////////////////////////////////////////////////////////////////////////// //tactics - bool isTacticsAllowed = !creatureBank; //no tactics in creature banks - BattleSideArray battleRepositionHex = {}; BattleSideArray battleRepositionHexBlock = {}; for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER}) @@ -475,7 +426,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const double tactics will be implemented. */ - if(isTacticsAllowed) + if(layout.tacticsAllowed) { if(tacticsSkillDiffAttacker > 0 && tacticsSkillDiffDefender > 0) logGlobal->warn("Double tactics is not implemented, only attacker will have tactics!"); @@ -523,11 +474,18 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive) return const_cast(battleGetStackByID(stackID, onlyAlive)); } +BattleInfo::BattleInfo(const BattleLayout & layout): + BattleInfo() +{ + *this->layout = layout; +} + BattleInfo::BattleInfo(): round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1), + layout(std::make_unique()), battlefieldType(BattleField::NONE), tacticsSide(BattleSide::NONE), tacticDistance(0) @@ -535,6 +493,11 @@ BattleInfo::BattleInfo(): setNodeType(BATTLE); } +BattleLayout BattleInfo::getLayout() const +{ + return *layout; +} + BattleID BattleInfo::getBattleID() const { return battleID; @@ -679,12 +642,6 @@ int3 BattleInfo::getLocation() const return tile; } -bool BattleInfo::isCreatureBank() const -{ - return creatureBank; -} - - std::vector BattleInfo::getUsedSpells(BattleSide side) const { return getSide(side).usedSpellsHistory; diff --git a/lib/battle/BattleInfo.h b/lib/battle/BattleInfo.h index a95c154b5..3c3dd4502 100644 --- a/lib/battle/BattleInfo.h +++ b/lib/battle/BattleInfo.h @@ -22,10 +22,12 @@ class CStack; class CStackInstance; class CStackBasicDescriptor; class BattleField; +struct BattleLayout; class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState { BattleSideArray sides; //sides[0] - attacker, sides[1] - defender + std::unique_ptr layout; public: BattleID battleID = BattleID(0); @@ -33,7 +35,6 @@ public: si32 activeStack; const CGTownInstance * town; //used during town siege, nullptr if this is not a siege (note that fortless town IS also a siege) int3 tile; //for background and bonuses - bool creatureBank; //auxiliary field, do not serialize bool replayAllowed; std::vector stacks; std::vector > obstacles; @@ -65,6 +66,7 @@ public: } ////////////////////////////////////////////////////////////////////////// + BattleInfo(const BattleLayout & layout); BattleInfo(); virtual ~BattleInfo(); @@ -108,7 +110,7 @@ public: int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override; int3 getLocation() const override; - bool isCreatureBank() const override; + BattleLayout getLayout() const override; std::vector getUsedSpells(BattleSide side) const override; @@ -152,7 +154,7 @@ public: const CGHeroInstance * getHero(const PlayerColor & player) const; //returns fighting hero that belongs to given player void localInit(); - static BattleInfo * setupBattle(const int3 & tile, TerrainId, const BattleField & battlefieldType, BattleSideArray armies, BattleSideArray heroes, bool creatureBank, const CGTownInstance * town); + static BattleInfo * setupBattle(const int3 & tile, TerrainId, const BattleField & battlefieldType, BattleSideArray armies, BattleSideArray heroes, const BattleLayout & layout, const CGTownInstance * town); BattleSide whatSide(const PlayerColor & player) const; diff --git a/lib/battle/BattleLayout.cpp b/lib/battle/BattleLayout.cpp new file mode 100644 index 000000000..272b14aa7 --- /dev/null +++ b/lib/battle/BattleLayout.cpp @@ -0,0 +1,81 @@ +/* + * BattleLayout.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "BattleLayout.h" + +#include "../GameSettings.h" +#include "../IGameCallback.h" +#include "../VCMI_Lib.h" +#include "../json/JsonNode.h" +#include "../mapObjects/CArmedInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN + +BattleLayout BattleLayout::createDefaultLayout(IGameCallback * cb, const CArmedInstance * attacker, const CArmedInstance * defender) +{ + return createLayout(cb, "default", attacker, defender); +} + +BattleLayout BattleLayout::createLayout(IGameCallback * cb, const std::string & layoutName, const CArmedInstance * attacker, const CArmedInstance * defender) +{ + const auto & loadHex = [](const JsonNode & node) + { + if (node.isNull()) + return BattleHex(); + else + return BattleHex(node.Integer()); + }; + + const auto & loadUnits = [](const JsonNode & node) + { + UnitsArrayType::value_type result; + for (size_t i = 0; i < GameConstants::ARMY_SIZE; ++i) + { + if (!node[i].isNull()) + result[i] = BattleHex(node[i].Integer()); + } + return result; + }; + + const JsonNode & configRoot = cb->getSettings().getValue(EGameSettings::COMBAT_LAYOUTS); + const JsonNode & config = configRoot[layoutName]; + + BattleLayout result; + + result.commanders[BattleSide::ATTACKER] = loadHex(config["attackerCommander"]); + result.commanders[BattleSide::DEFENDER] = loadHex(config["defenderCommander"]); + + for (size_t i = 0; i < 4; ++i) + result.warMachines[BattleSide::ATTACKER][i] = loadHex(config["attackerWarMachines"][i]); + + for (size_t i = 0; i < 4; ++i) + result.warMachines[BattleSide::DEFENDER][i] = loadHex(config["attackerWarMachines"][i]); + + if (attacker->formation == EArmyFormation::LOOSE && !config["attackerUnitsLoose"].isNull()) + result.units[BattleSide::ATTACKER] = loadUnits(config["attackerUnitsLoose"][attacker->stacksCount()]); + else if (attacker->formation == EArmyFormation::TIGHT && !config["attackerUnitsTight"].isNull()) + result.units[BattleSide::ATTACKER] = loadUnits(config["attackerUnitsTight"][attacker->stacksCount()]); + else + result.units[BattleSide::ATTACKER] = loadUnits(config["attackerUnits"]); + + if (attacker->formation == EArmyFormation::LOOSE && !config["defenderUnitsLoose"].isNull()) + result.units[BattleSide::DEFENDER] = loadUnits(config["defenderUnitsLoose"][attacker->stacksCount()]); + else if (attacker->formation == EArmyFormation::TIGHT && !config["defenderUnitsTight"].isNull()) + result.units[BattleSide::DEFENDER] = loadUnits(config["defenderUnitsTight"][attacker->stacksCount()]); + else + result.units[BattleSide::DEFENDER] = loadUnits(config["defenderUnits"]); + + result.obstaclesAllowed = config["obstaclesAllowed"].Bool(); + result.tacticsAllowed = config["tacticsAllowed"].Bool(); + + return result; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleLayout.h b/lib/battle/BattleLayout.h new file mode 100644 index 000000000..a6a6948de --- /dev/null +++ b/lib/battle/BattleLayout.h @@ -0,0 +1,39 @@ +/* + * BattleLayout.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "BattleHex.h" +#include "BattleSide.h" +#include "../constants/NumericConstants.h" +#include "../constants/Enumerations.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CArmedInstance; +class IGameCallback; + +struct DLL_EXPORT BattleLayout +{ + using UnitsArrayType = BattleSideArray>; + using MachinesArrayType = BattleSideArray>; + using CommanderArrayType = BattleSideArray; + + UnitsArrayType units; + MachinesArrayType warMachines; + CommanderArrayType commanders; + + bool tacticsAllowed = false; + bool obstaclesAllowed = false; + + static BattleLayout createDefaultLayout(IGameCallback * cb, const CArmedInstance * attacker, const CArmedInstance * defender); + static BattleLayout createLayout(IGameCallback * cb, const std::string & layoutName, const CArmedInstance * attacker, const CArmedInstance * defender); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/IBattleState.h b/lib/battle/IBattleState.h index 6c866d449..a2ae55576 100644 --- a/lib/battle/IBattleState.h +++ b/lib/battle/IBattleState.h @@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN class ObstacleChanges; class UnitChanges; struct Bonus; +struct BattleLayout; class JsonNode; class JsonSerializeFormat; class BattleField; @@ -72,7 +73,7 @@ public: virtual int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const = 0; virtual int3 getLocation() const = 0; - virtual bool isCreatureBank() const = 0; + virtual BattleLayout getLayout() const = 0; }; class DLL_LINKAGE IBattleState : public IBattleInfo diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index d0f371195..ac65f7fa8 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -283,7 +283,7 @@ void CBank::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) c if (answer) { if (bankConfig) // not looted bank - cb->startBattleI(hero, this, !regularUnitPlacement); + cb->startBattle(hero, this); else doVisit(hero); } diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index f6ae1eaac..4d2495c82 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -465,7 +465,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const } } - cb->startBattleI(h, this); + cb->startBattle(h, this); } diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index f376ca98e..1ec3e492c 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -505,7 +505,7 @@ void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answ if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present { if(answer) - cb->startBattleI(hero, this); + cb->startBattle(hero, this); } else if(answer) { diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index afe95892a..35782bfdb 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -512,7 +512,7 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const if(visitedTown) //we're in town visitedTown->onHeroVisit(h); //town will handle attacking else - cb->startBattleI(h, this); + cb->startBattle(h, this); } } else if(ID == Obj::PRISON) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index eaf4d8473..c75f04dab 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -193,7 +193,7 @@ void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, int32_t an if(stacksCount() > 0) //if pandora's box is protected by army { hero->showInfoDialog(16, 0, EInfoWindowMode::MODAL); - cb->startBattleI(hero, this); //grants things after battle + cb->startBattle(hero, this); //grants things after battle } else if(getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT).empty()) { @@ -332,7 +332,7 @@ void CGEvent::activated( const CGHeroInstance * h ) const else iw.text.appendLocalString(EMetaText::ADVOB_TXT, 16); cb->showInfoDialog(&iw); - cb->startBattleI(h, this); + cb->startBattle(h, this); } else { diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 078e2ce0c..2c040da6b 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -15,6 +15,7 @@ #include "../spells/CSpellHandler.h" #include "../bonuses/Bonus.h" #include "../battle/IBattleInfoCallback.h" +#include "../battle/BattleLayout.h" #include "../CConfigHandler.h" #include "../texts/CGeneralTextHandler.h" #include "../IGameCallback.h" @@ -321,7 +322,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const const_cast(defendingHero)->inTownGarrison = false; //hack to return visitor from garrison after battle } - cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (isBattleOutside ? nullptr : this)); + cb->startBattle(h, defendingArmy, getSightCenter(), h, defendingHero, BattleLayout::createDefaultLayout(cb, h, defendingArmy), (isBattleOutside ? nullptr : this)); } else { diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 3a26738f5..461a0d661 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -14,6 +14,7 @@ #include "../CPlayerState.h" #include "../GameSettings.h" #include "../IGameCallback.h" +#include "../battle/BattleLayout.h" #include "../gameState/CGameState.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" @@ -127,7 +128,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const bd.text = guardedReward.message; bd.components = getPopupComponents(hero->getOwner()); - cb->showBlockingDialog(&bd); + cb->showBlockingDialog(this, &bd); } else { @@ -238,7 +239,10 @@ void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int3 if(guardedPresently()) { if (answer) - cb->startBattleI(hero, this, true); + { + auto layout = BattleLayout::createLayout(cb, configuration.guardsLayout, hero, this); + cb->startBattle(hero, this, visitablePos(), hero, nullptr, layout, nullptr); + } } else { @@ -405,7 +409,7 @@ std::vector CRewardableObject::getPopupComponentsImpl(PlayerColor pla if (guardedPresently()) { - if (!VLC->settings()->getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION)) + if (!cb->getSettings().getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION)) return {}; std::map guardsAmounts; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index ca0714235..ab5fc3d50 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -219,7 +219,7 @@ void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &resu void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer) - cb->startBattleI(hero, this); + cb->startBattle(hero, this); } void CGMine::serializeJsonOptions(JsonSerializeFormat & handler) @@ -352,7 +352,7 @@ void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult & void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer) - cb->startBattleI(hero, this); + cb->startBattle(hero, this); } void CGResource::serializeJsonOptions(JsonSerializeFormat & handler) @@ -919,7 +919,7 @@ void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult & void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { if(answer) - cb->startBattleI(hero, this); + cb->startBattle(hero, this); } void CGArtifact::afterAddToMap(CMap * map) @@ -999,7 +999,7 @@ void CGGarrison::onHeroVisit (const CGHeroInstance *h) const auto relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); if (relations == PlayerRelations::ENEMIES && stacksCount() > 0) { //TODO: Find a way to apply magic garrison effects in battle. - cb->startBattleI(h, this); + cb->startBattle(h, this); return; } diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index d4ed53db0..e9e9f832f 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -156,6 +156,8 @@ struct DLL_LINKAGE Configuration /// Limiter that will be used to determine that object is visited. Only if visit mode is set to "limiter" Rewardable::Limiter visitLimiter; + std::string guardsLayout; + /// if true - player can refuse visiting an object (e.g. Tomb) bool canRefuse = false; @@ -192,8 +194,11 @@ struct DLL_LINKAGE Configuration h & canRefuse; h & showScoutedPreview; h & infoWindowType; - if (h.version >= Handler::Version::BANK_UNIT_PLACEMENT) + if (h.version >= Handler::Version::REWARDABLE_BANKS) + { h & coastVisitable; + h & guardsLayout; + } else coastVisitable = false; } diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index abf8fc56c..bc01b41c1 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -394,6 +394,7 @@ void Rewardable::Info::configureObject(Rewardable::Configuration & object, vstd: object.canRefuse = parameters["canRefuse"].Bool(); object.showScoutedPreview = parameters["showScoutedPreview"].Bool(); + object.guardsLayout = parameters["guardsLayout"].String(); object.coastVisitable = parameters["coastVisitable"].Bool(); if(parameters["showInInfobox"].isNull()) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 13cf68c43..4b93943b1 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4114,17 +4114,12 @@ void CGameHandler::newObject(CGObjectInstance * object, PlayerColor initiator) sendAndApply(&no); } -void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, const CGTownInstance *town) +void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) { - battles->startBattlePrimary(army1, army2, tile, hero1, hero2, creatureBank, town); + battles->startBattle(army1, army2, tile, hero1, hero2, layout, town); } -void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank ) +void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance *army2 ) { - battles->startBattleI(army1, army2, tile, creatureBank); -} - -void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank ) -{ - battles->startBattleI(army1, army2, creatureBank); + battles->startBattle(army1, army2); } diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 2a12c6a9b..b7df98e05 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -149,9 +149,8 @@ public: void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override; void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override; - void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override; //use hero=nullptr for no hero - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override; //if any of armies is hero, hero will be used - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle + void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) override; //use hero=nullptr for no hero + void startBattle(const CArmedInstance *army1, const CArmedInstance *army2) override; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override; void giveHeroBonus(GiveBonus * bonus) override; void setMovePoints(SetMovePoints * smp) override; diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index 2b9a8e103..57d0e32d1 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -23,6 +23,7 @@ #include "../../lib/battle/CBattleInfoCallback.h" #include "../../lib/battle/CObstacleInstance.h" #include "../../lib/battle/BattleInfo.h" +#include "../../lib/battle/BattleLayout.h" #include "../../lib/entities/building/TownFortifications.h" #include "../../lib/gameState/CGameState.h" #include "../../lib/mapping/CMap.h" @@ -52,9 +53,8 @@ void BattleProcessor::engageIntoBattle(PlayerColor player) gameHandler->sendAndApply(&pb); } -void BattleProcessor::restartBattlePrimary(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, - const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, - const CGTownInstance *town) +void BattleProcessor::restartBattle(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, + const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) { auto battle = gameHandler->gameState()->getBattle(battleID); @@ -90,12 +90,11 @@ void BattleProcessor::restartBattlePrimary(const BattleID & battleID, const CArm bc.battleID = battleID; gameHandler->sendAndApply(&bc); - startBattlePrimary(army1, army2, tile, hero1, hero2, creatureBank, town); + startBattle(army1, army2, tile, hero1, hero2, layout, town); } -void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, - const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, - const CGTownInstance *town) +void BattleProcessor::startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, + const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) { assert(gameHandler->gameState()->getBattle(army1->getOwner()) == nullptr); assert(gameHandler->gameState()->getBattle(army2->getOwner()) == nullptr); @@ -103,7 +102,7 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm BattleSideArray armies{army1, army2}; BattleSideArrayheroes{hero1, hero2}; - auto battleID = setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces + auto battleID = setupBattle(tile, armies, heroes, layout, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces const auto * battle = gameHandler->gameState()->getBattle(battleID); assert(battle); @@ -144,20 +143,16 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm flowProcessor->onBattleStarted(*battle); } -void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank) +void BattleProcessor::startBattle(const CArmedInstance *army1, const CArmedInstance *army2) { - startBattlePrimary(army1, army2, tile, - army1->ID == Obj::HERO ? static_cast(army1) : nullptr, - army2->ID == Obj::HERO ? static_cast(army2) : nullptr, - creatureBank); + startBattle(army1, army2, army2->visitablePos(), + army1->ID == Obj::HERO ? dynamic_cast(army1) : nullptr, + army2->ID == Obj::HERO ? dynamic_cast(army2) : nullptr, + BattleLayout::createDefaultLayout(gameHandler, army1, army2), + nullptr); } -void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank) -{ - startBattleI(army1, army2, army2->visitablePos(), creatureBank); -} - -BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray armies, BattleSideArray heroes, bool creatureBank, const CGTownInstance *town) +BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray armies, BattleSideArray heroes, const BattleLayout & layout, const CGTownInstance *town) { const auto & t = *gameHandler->getTile(tile); TerrainId terrain = t.terType->getId(); @@ -170,7 +165,7 @@ BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArraygameState()->nextBattleID; engageIntoBattle(bs.info->getSide(BattleSide::ATTACKER).color); diff --git a/server/battles/BattleProcessor.h b/server/battles/BattleProcessor.h index c21dd6a66..480627b21 100644 --- a/server/battles/BattleProcessor.h +++ b/server/battles/BattleProcessor.h @@ -20,6 +20,7 @@ class BattleAction; class int3; class CBattleInfoCallback; struct BattleResult; +struct BattleLayout; class BattleID; VCMI_LIB_NAMESPACE_END @@ -45,7 +46,7 @@ class BattleProcessor : boost::noncopyable void engageIntoBattle(PlayerColor player); bool checkBattleStateChanges(const CBattleInfoCallback & battle); - BattleID setupBattle(int3 tile, BattleSideArray armies, BattleSideArray heroes, bool creatureBank, const CGTownInstance *town); + BattleID setupBattle(int3 tile, BattleSideArray armies, BattleSideArray heroes, const BattleLayout & layout, const CGTownInstance *town); bool makeAutomaticBattleAction(const CBattleInfoCallback & battle, const BattleAction & ba); @@ -56,13 +57,11 @@ public: ~BattleProcessor(); /// Starts battle with specified parameters - void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr); - /// Starts battle between two armies (which can also be heroes) at specified tile - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false); + void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town); /// Starts battle between two armies (which can also be heroes) at position of 2nd object - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false); + void startBattle(const CArmedInstance *army1, const CArmedInstance *army2); /// Restart ongoing battle and end previous battle - void restartBattlePrimary(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr); + void restartBattle(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town); /// Processing of incoming battle action netpack bool makePlayerBattleAction(const BattleID & battleID, PlayerColor player, const BattleAction & ba); diff --git a/server/queries/BattleQueries.cpp b/server/queries/BattleQueries.cpp index 80ac9029c..30b06db91 100644 --- a/server/queries/BattleQueries.cpp +++ b/server/queries/BattleQueries.cpp @@ -17,6 +17,7 @@ #include "../../lib/battle/IBattleState.h" #include "../../lib/battle/SideInBattle.h" +#include "../../lib/battle/BattleLayout.h" #include "../../lib/CPlayerState.h" #include "../../lib/mapObjects/CGObjectInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" @@ -95,14 +96,14 @@ void CBattleDialogQuery::onRemoval(PlayerColor color) assert(answer); if(*answer == 1) { - gh->battles->restartBattlePrimary( + gh->battles->restartBattle( bi->getBattleID(), bi->getSideArmy(BattleSide::ATTACKER), bi->getSideArmy(BattleSide::DEFENDER), bi->getLocation(), bi->getSideHero(BattleSide::ATTACKER), bi->getSideHero(BattleSide::DEFENDER), - bi->isCreatureBank(), + bi->getLayout(), bi->getDefendedTown() ); } diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index a897732dc..be71314fc 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -24,6 +24,7 @@ #include "../../lib/TerrainHandler.h" #include "../../lib/battle/BattleInfo.h" +#include "../../lib/battle/BattleLayout.h" #include "../../lib/CStack.h" #include "../../lib/filesystem/ResourcePath.h" @@ -197,10 +198,11 @@ public: auto terrain = t.terType->getId(); BattleField terType(0); + BattleLayout layout = BattleLayout::createDefaultLayout(gameState->callback, attacker, defender); //send info about battles - BattleInfo * battle = BattleInfo::setupBattle(tile, terrain, terType, armedInstancies, heroes, false, nullptr); + BattleInfo * battle = BattleInfo::setupBattle(tile, terrain, terType, armedInstancies, heroes, layout, nullptr); BattleStart bs; bs.info = battle; diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index c1b094366..1f2456a2f 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -79,9 +79,8 @@ public: void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {} void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {} void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override {} - void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override {} //use hero=nullptr for no hero - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override {} //if any of armies is hero, hero will be used - void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override {} //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle + void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) override {} //use hero=nullptr for no hero + void startBattle(const CArmedInstance *army1, const CArmedInstance *army2) override {} bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;} bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;} void giveHeroBonus(GiveBonus * bonus) override {} diff --git a/test/mock/mock_battle_IBattleState.h b/test/mock/mock_battle_IBattleState.h index 0893fd6cc..02389dc42 100644 --- a/test/mock/mock_battle_IBattleState.h +++ b/test/mock/mock_battle_IBattleState.h @@ -11,6 +11,7 @@ #pragma once #include "../../lib/battle/IBattleState.h" +#include "../../lib/battle/BattleLayout.h" #include "../../lib/int3.h" class BattleStateMock : public IBattleState @@ -37,7 +38,7 @@ public: MOCK_CONST_METHOD3(getActualDamage, int64_t(const DamageRange &, int32_t, vstd::RNG &)); MOCK_CONST_METHOD0(getBattleID, BattleID()); MOCK_CONST_METHOD0(getLocation, int3()); - MOCK_CONST_METHOD0(isCreatureBank, bool()); + MOCK_CONST_METHOD0(getLayout, BattleLayout()); MOCK_CONST_METHOD1(getUsedSpells, std::vector(BattleSide)); MOCK_METHOD0(nextRound, void());