From 82a9f82e1c07798bfba98c76a1bbf36ca9402854 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Fri, 14 Oct 2022 11:24:29 +0300 Subject: [PATCH] BattleAI: retreat --- AI/BattleAI/BattleAI.cpp | 25 ++++++++++-- AI/Nullkiller/AIGateway.cpp | 18 ++++++++ AI/Nullkiller/AIGateway.h | 1 + CCallback.cpp | 6 +++ CCallback.h | 3 ++ cmake_modules/VCMI_lib.cmake | 2 + lib/CGameInterface.h | 9 +++- lib/battle/BattleAction.cpp | 16 ++++++++ lib/battle/BattleAction.h | 2 + lib/battle/BattleStateInfoForRetreat.cpp | 52 ++++++++++++++++++++++++ lib/battle/BattleStateInfoForRetreat.h | 38 +++++++++++++++++ 11 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 lib/battle/BattleStateInfoForRetreat.cpp create mode 100644 lib/battle/BattleStateInfoForRetreat.h diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 820988a18..61327e548 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -17,6 +17,7 @@ #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/ISpellMechanics.h" +#include "../../lib/battle/BattleStateInfoForRetreat.h" #include "../../lib/CStack.h" // TODO: remove // Eventually only IBattleInfoCallback and battle::Unit should be used, // CUnitState should be private and CStack should be removed completely @@ -728,13 +729,31 @@ void CBattleAI::print(const std::string &text) const boost::optional CBattleAI::considerFleeingOrSurrendering() { - if(cb->battleCanSurrender(playerID)) + BattleStateInfoForRetreat bs; + + bs.canFlee = cb->battleCanFlee(); + bs.canSurrender = cb->battleCanSurrender(playerID); + bs.ourSide = cb->battleGetMySide(); + bs.ourHero = cb->battleGetMyHero(); + bs.enemyHero = cb->battleGetFightingHero(!bs.ourSide); + + for(auto stack : cb->battleGetAllStacks(false)) { + if(stack->alive()) + { + if(stack->side == bs.ourSide) + bs.ourStacks.push_back(stack); + else + bs.enemyStacks.push_back(stack); + } } - if(cb->battleCanFlee()) + + if(!bs.canFlee || !bs.canSurrender) { + return boost::none; } - return boost::none; + + return cb->makeSurrenderRetreatDecision(bs); } diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 1b7313d4e..d8bfdfa44 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -19,6 +19,7 @@ #include "../../lib/serializer/CTypeList.h" #include "../../lib/serializer/BinarySerializer.h" #include "../../lib/serializer/BinaryDeserializer.h" +#include "../../lib/battle/BattleStateInfoForRetreat.h" #include "AIGateway.h" #include "Goals/Goals.h" @@ -488,6 +489,23 @@ void AIGateway::showWorldViewEx(const std::vector & objectPositio NET_EVENT_HANDLER; } +boost::optional AIGateway::makeSurrenderRetreatDecision( + const BattleStateInfoForRetreat & battleState) +{ + LOG_TRACE(logAi); + NET_EVENT_HANDLER; + + double fightRatio = battleState.getOurStrength() / (double)battleState.getEnemyStrength(); + + if(fightRatio < 0.3 && battleState.canFlee) + { + return BattleAction::makeRetreat(battleState.ourSide); + } + + return boost::none; +} + + void AIGateway::init(std::shared_ptr env, std::shared_ptr CB) { LOG_TRACE(logAi); diff --git a/AI/Nullkiller/AIGateway.h b/AI/Nullkiller/AIGateway.h index f3a4d8454..0df0359d1 100644 --- a/AI/Nullkiller/AIGateway.h +++ b/AI/Nullkiller/AIGateway.h @@ -166,6 +166,7 @@ public: void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override; void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override; void showWorldViewEx(const std::vector & objectPositions) override; + boost::optional makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override; void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override; void battleEnd(const BattleResult * br) override; diff --git a/CCallback.cpp b/CCallback.cpp index a4c2de397..09d6ff49f 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -387,3 +387,9 @@ bool CBattleCallback::battleMakeTacticAction( BattleAction * action ) sendRequest(&ma); return true; } + +boost::optional CBattleCallback::makeSurrenderRetreatDecision( + const BattleStateInfoForRetreat & battleState) +{ + return cl->playerint[getPlayerID().get()]->makeSurrenderRetreatDecision(battleState); +} \ No newline at end of file diff --git a/CCallback.h b/CCallback.h index 6986153f1..49e1a9970 100644 --- a/CCallback.h +++ b/CCallback.h @@ -32,6 +32,7 @@ struct CPackForServer; class IBattleEventsReceiver; class IGameEventsReceiver; struct ArtifactLocation; +class BattleStateInfoForRetreat; VCMI_LIB_NAMESPACE_END @@ -48,6 +49,7 @@ public: //battle virtual int battleMakeAction(const BattleAction * action) = 0;//for casting spells by hero - DO NOT use it for moving active stack virtual bool battleMakeTacticAction(BattleAction * action) = 0; // performs tactic phase actions + virtual boost::optional makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) = 0; }; class IGameActionCallback @@ -104,6 +106,7 @@ public: CBattleCallback(boost::optional Player, CClient *C); int battleMakeAction(const BattleAction * action) override;//for casting spells by hero - DO NOT use it for moving active stack bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions + boost::optional makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override; #if SCRIPTING_ENABLED scripting::Pool * getContextPool() const override; diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 9ba3074c2..e915a69aa 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -12,6 +12,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/battle/BattleHex.cpp ${MAIN_LIB_DIR}/battle/BattleInfo.cpp ${MAIN_LIB_DIR}/battle/BattleProxy.cpp + ${MAIN_LIB_DIR}/battle/BattleStateInfoForRetreat.cpp ${MAIN_LIB_DIR}/battle/CBattleInfoCallback.cpp ${MAIN_LIB_DIR}/battle/CBattleInfoEssentials.cpp ${MAIN_LIB_DIR}/battle/CCallbackBase.cpp @@ -241,6 +242,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/battle/BattleAttackInfo.h ${MAIN_LIB_DIR}/battle/BattleHex.h ${MAIN_LIB_DIR}/battle/BattleInfo.h + ${MAIN_LIB_DIR}/battle/BattleStateInfoForRetreat.h ${MAIN_LIB_DIR}/battle/BattleProxy.h ${MAIN_LIB_DIR}/battle/CBattleInfoCallback.h ${MAIN_LIB_DIR}/battle/CBattleInfoEssentials.h diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 2228a86e3..99c166c3f 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -58,7 +58,9 @@ class CLoadFile; class CSaveFile; class BinaryDeserializer; class BinarySerializer; +class BattleStateInfo; struct ArtifactLocation; +class BattleStateInfoForRetreat; #if SCRIPTING_ENABLED namespace scripting @@ -93,7 +95,7 @@ public: //pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id virtual void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, QueryID queryID)=0; - virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID)=0; + virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID)=0; // Show a dialog, player must take decision. If selection then he has to choose between one of given components, // if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called @@ -108,6 +110,11 @@ public: virtual void showWorldViewEx(const std::vector & objectPositions){}; + virtual boost::optional makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) + { + return boost::none; + } + virtual void saveGame(BinarySerializer & h, const int version) = 0; virtual void loadGame(BinaryDeserializer & h, const int version) = 0; }; diff --git a/lib/battle/BattleAction.cpp b/lib/battle/BattleAction.cpp index 9567ffbb1..a03bf8a95 100644 --- a/lib/battle/BattleAction.cpp +++ b/lib/battle/BattleAction.cpp @@ -105,6 +105,22 @@ BattleAction BattleAction::makeEndOFTacticPhase(ui8 side) return ba; } +BattleAction BattleAction::makeSurrender(ui8 side) +{ + BattleAction ba; + ba.side = side; + ba.actionType = EActionType::SURRENDER; + return ba; +} + +BattleAction BattleAction::makeRetreat(ui8 side) +{ + BattleAction ba; + ba.side = side; + ba.actionType = EActionType::RETREAT; + return ba; +} + std::string BattleAction::toString() const { std::stringstream actionTypeStream; diff --git a/lib/battle/BattleAction.h b/lib/battle/BattleAction.h index ecce56ccb..5df753eb8 100644 --- a/lib/battle/BattleAction.h +++ b/lib/battle/BattleAction.h @@ -40,6 +40,8 @@ public: static BattleAction makeCreatureSpellcast(const battle::Unit * stack, const battle::Target & target, SpellID spellID); static BattleAction makeMove(const battle::Unit * stack, BattleHex dest); static BattleAction makeEndOFTacticPhase(ui8 side); + static BattleAction makeRetreat(ui8 side); + static BattleAction makeSurrender(ui8 side); std::string toString() const; diff --git a/lib/battle/BattleStateInfoForRetreat.cpp b/lib/battle/BattleStateInfoForRetreat.cpp new file mode 100644 index 000000000..8366f76fc --- /dev/null +++ b/lib/battle/BattleStateInfoForRetreat.cpp @@ -0,0 +1,52 @@ +/* + * BattleStateInfoForRetreat.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 "BattleStateInfoForRetreat.h" +#include "Unit.h" +#include "CBattleInfoCallback.h" +#include "../CCreatureSet.h" +#include "../mapObjects/CGHeroInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN + +BattleStateInfoForRetreat::BattleStateInfoForRetreat() +: canFlee(false), canSurrender(false), isLastTurnBeforeDie(false), ourStacks(), enemyStacks(), ourHero(nullptr), enemyHero(nullptr), ourSide(-1) +{ +} + +uint64_t getFightingStrength(std::vector stacks, const CGHeroInstance * hero = nullptr) +{ + uint64_t result = 0; + + for(const battle::Unit * stack : stacks) + { + result += stack->creatureId().toCreature()->AIValue * stack->getCount(); + } + + if(hero) + { + result = (uint64_t)(result * hero->getFightingStrength()); + } + + return result; +} + +uint64_t BattleStateInfoForRetreat::getOurStrength() const +{ + return getFightingStrength(ourStacks, ourHero); +} + +uint64_t BattleStateInfoForRetreat::getEnemyStrength() const +{ + return getFightingStrength(enemyStacks, enemyHero); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleStateInfoForRetreat.h b/lib/battle/BattleStateInfoForRetreat.h new file mode 100644 index 000000000..4dc713e41 --- /dev/null +++ b/lib/battle/BattleStateInfoForRetreat.h @@ -0,0 +1,38 @@ +/* + * BattleStateInfoForRetreat.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 + +VCMI_LIB_NAMESPACE_BEGIN + +namespace battle +{ + class Unit; +} + +class CGHeroInstance; + +class DLL_LINKAGE BattleStateInfoForRetreat +{ +public: + bool canFlee; + bool canSurrender; + bool isLastTurnBeforeDie; + ui8 ourSide; + std::vector ourStacks; + std::vector enemyStacks; + const CGHeroInstance * ourHero; + const CGHeroInstance * enemyHero; + + BattleStateInfoForRetreat(); + uint64_t getOurStrength() const; + uint64_t getEnemyStrength() const; +}; + +VCMI_LIB_NAMESPACE_END