| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 | 
							- /*
 
-  * BattleAI.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 "BattleAI.h"
 
- #include "BattleEvaluator.h"
 
- #include "BattleExchangeVariant.h"
 
- #include "StackWithBonuses.h"
 
- #include "EnemyInfo.h"
 
- #include "tbb/parallel_for.h"
 
- #include "../../lib/CStopWatch.h"
 
- #include "../../lib/CThreadHelper.h"
 
- #include "../../lib/mapObjects/CGTownInstance.h"
 
- #include "../../lib/spells/CSpellHandler.h"
 
- #include "../../lib/spells/ISpellMechanics.h"
 
- #include "../../lib/battle/BattleAction.h"
 
- #include "../../lib/battle/BattleStateInfoForRetreat.h"
 
- #include "../../lib/battle/CObstacleInstance.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
 
- #define LOGL(text) print(text)
 
- #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
 
- CBattleAI::CBattleAI()
 
- 	: side(-1),
 
- 	wasWaitingForRealize(false),
 
- 	wasUnlockingGs(false)
 
- {
 
- }
 
- CBattleAI::~CBattleAI()
 
- {
 
- 	if(cb)
 
- 	{
 
- 		//Restore previous state of CB - it may be shared with the main AI (like VCAI)
 
- 		cb->waitTillRealize = wasWaitingForRealize;
 
- 		cb->unlockGsWhenWaiting = wasUnlockingGs;
 
- 	}
 
- }
 
- void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
 
- {
 
- 	setCbc(CB);
 
- 	env = ENV;
 
- 	cb = CB;
 
- 	playerID = *CB->getPlayerID();
 
- 	wasWaitingForRealize = CB->waitTillRealize;
 
- 	wasUnlockingGs = CB->unlockGsWhenWaiting;
 
- 	CB->waitTillRealize = false;
 
- 	CB->unlockGsWhenWaiting = false;
 
- 	movesSkippedByDefense = 0;
 
- }
 
- void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences)
 
- {
 
- 	initBattleInterface(ENV, CB);
 
- 	autobattlePreferences = autocombatPreferences;
 
- }
 
- BattleAction CBattleAI::useHealingTent(const BattleID & battleID, const CStack *stack)
 
- {
 
- 	auto healingTargets = cb->getBattle(battleID)->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
 
- 	std::map<int, const CStack*> woundHpToStack;
 
- 	for(const auto * stack : healingTargets)
 
- 	{
 
- 		if(auto woundHp = stack->getMaxHealth() - stack->getFirstHPleft())
 
- 			woundHpToStack[woundHp] = stack;
 
- 	}
 
- 	if(woundHpToStack.empty())
 
- 		return BattleAction::makeDefend(stack);
 
- 	else
 
- 		return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
 
- }
 
- void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
 
- {
 
- 	cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
 
- }
 
- static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
 
- {
 
- 	auto stacks = cb->battleGetAllStacks();
 
- 	auto our = 0, enemy = 0;
 
- 	for(auto stack : stacks)
 
- 	{
 
- 		auto creature = stack->creatureId().toCreature();
 
- 		if(!creature)
 
- 			continue;
 
- 		if(stack->unitSide() == side)
 
- 			our += stack->getCount() * creature->getAIValue();
 
- 		else
 
- 			enemy += stack->getCount() * creature->getAIValue();
 
- 	}
 
- 	return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
 
- }
 
- void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 
- {
 
- 	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
 
- 	auto timeElapsed = [](std::chrono::time_point<std::chrono::high_resolution_clock> start) -> uint64_t
 
- 	{
 
- 		auto end = std::chrono::high_resolution_clock::now();
 
- 		return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
 
- 	};
 
- 	BattleAction result = BattleAction::makeDefend(stack);
 
- 	setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
 
- 	auto start = std::chrono::high_resolution_clock::now();
 
- 	try
 
- 	{
 
- 		if(stack->creatureId() == CreatureID::CATAPULT)
 
- 		{
 
- 			cb->battleMakeUnitAction(battleID, useCatapult(battleID, stack));
 
- 			return;
 
- 		}
 
- 		if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
 
- 		{
 
- 			cb->battleMakeUnitAction(battleID, useHealingTent(battleID, stack));
 
- 			return;
 
- 		}
 
- #if BATTLE_TRACE_LEVEL>=1
 
- 		logAi->trace("Build evaluator and targets");
 
- #endif
 
- 		BattleEvaluator evaluator(env, cb, stack, playerID, battleID, side, getStrengthRatio(cb->getBattle(battleID), side));
 
- 		result = evaluator.selectStackAction(stack);
 
- 		if(!skipCastUntilNextBattle && evaluator.canCastSpell())
 
- 		{
 
- 			auto spelCasted = evaluator.attemptCastingSpell(stack);
 
- 			if(spelCasted)
 
- 				return;
 
- 			
 
- 			skipCastUntilNextBattle = true;
 
- 		}
 
- 		logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
 
- 		if(auto action = considerFleeingOrSurrendering(battleID))
 
- 		{
 
- 			cb->battleMakeUnitAction(battleID, *action);
 
- 			return;
 
- 		}
 
- 	}
 
- 	catch(boost::thread_interrupted &)
 
- 	{
 
- 		throw;
 
- 	}
 
- 	catch(std::exception &e)
 
- 	{
 
- 		logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what());
 
- 	}
 
- 	if(result.actionType == EActionType::DEFEND)
 
- 	{
 
- 		movesSkippedByDefense++;
 
- 	}
 
- 	else if(result.actionType != EActionType::WAIT)
 
- 	{
 
- 		movesSkippedByDefense = 0;
 
- 	}
 
- 	logAi->trace("BattleAI decission made in %lld", timeElapsed(start));
 
- 	cb->battleMakeUnitAction(battleID, result);
 
- }
 
- BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * stack)
 
- {
 
- 	BattleAction attack;
 
- 	BattleHex targetHex = BattleHex::INVALID;
 
- 	if(cb->getBattle(battleID)->battleGetGateState() == EGateState::CLOSED)
 
- 	{
 
- 		targetHex = cb->getBattle(battleID)->wallPartToBattleHex(EWallPart::GATE);
 
- 	}
 
- 	else
 
- 	{
 
- 		EWallPart wallParts[] = {
 
- 			EWallPart::KEEP,
 
- 			EWallPart::BOTTOM_TOWER,
 
- 			EWallPart::UPPER_TOWER,
 
- 			EWallPart::BELOW_GATE,
 
- 			EWallPart::OVER_GATE,
 
- 			EWallPart::BOTTOM_WALL,
 
- 			EWallPart::UPPER_WALL
 
- 		};
 
- 		for(auto wallPart : wallParts)
 
- 		{
 
- 			auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart);
 
- 			if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
 
- 			{
 
- 				targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart);
 
- 				break;
 
- 			}
 
- 		}
 
- 	}
 
- 	if(!targetHex.isValid())
 
- 	{
 
- 		return BattleAction::makeDefend(stack);
 
- 	}
 
- 	attack.aimToHex(targetHex);
 
- 	attack.actionType = EActionType::CATAPULT;
 
- 	attack.side = side;
 
- 	attack.stackNumber = stack->unitId();
 
- 	movesSkippedByDefense = 0;
 
- 	return attack;
 
- }
 
- void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
 
- {
 
- 	LOG_TRACE(logAi);
 
- 	side = Side;
 
- 	skipCastUntilNextBattle = false;
 
- }
 
- void CBattleAI::print(const std::string &text) const
 
- {
 
- 	logAi->trace("%s Battle AI[%p]: %s", playerID.toString(), this, text);
 
- }
 
- std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering(const BattleID & battleID)
 
- {
 
- 	BattleStateInfoForRetreat bs;
 
- 	bs.canFlee = cb->getBattle(battleID)->battleCanFlee();
 
- 	bs.canSurrender = cb->getBattle(battleID)->battleCanSurrender(playerID);
 
- 	bs.ourSide = cb->getBattle(battleID)->battleGetMySide();
 
- 	bs.ourHero = cb->getBattle(battleID)->battleGetMyHero();
 
- 	bs.enemyHero = nullptr;
 
- 	for(auto stack : cb->getBattle(battleID)->battleGetAllStacks(false))
 
- 	{
 
- 		if(stack->alive())
 
- 		{
 
- 			if(stack->unitSide() == bs.ourSide)
 
- 				bs.ourStacks.push_back(stack);
 
- 			else
 
- 			{
 
- 				bs.enemyStacks.push_back(stack);
 
- 				bs.enemyHero = cb->getBattle(battleID)->battleGetOwnerHero(stack);
 
- 			}
 
- 		}
 
- 	}
 
- 	bs.turnsSkippedByDefense = movesSkippedByDefense / bs.ourStacks.size();
 
- 	if(!bs.canFlee && !bs.canSurrender)
 
- 	{
 
- 		return std::nullopt;
 
- 	}
 
- 	auto result = cb->makeSurrenderRetreatDecision(battleID, bs);
 
- 	if(!result && bs.canFlee && bs.turnsSkippedByDefense > 30)
 
- 	{
 
- 		return BattleAction::makeRetreat(bs.ourSide);
 
- 	}
 
- 	return result;
 
- }
 
 
  |