mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Refactoring Battle AI.
Divide BattleAI on the smaller files.
This commit is contained in:
		
							
								
								
									
										66
									
								
								AI/BattleAI/AttackPossibility.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								AI/BattleAI/AttackPossibility.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| /* | ||||
|  * AttackPossibility.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 "AttackPossibility.h" | ||||
|  | ||||
| int AttackPossibility::damageDiff() const | ||||
| { | ||||
| 	if (!priorities) | ||||
| 		priorities = new Priorities; | ||||
| 	const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt; | ||||
| 	const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived; | ||||
| 	return dealtDmgValue - receivedDmgValue; | ||||
| } | ||||
|  | ||||
| int AttackPossibility::attackValue() const | ||||
| { | ||||
| 	return damageDiff() + tacticImpact; | ||||
| } | ||||
|  | ||||
| AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex) | ||||
| { | ||||
| 	auto attacker = AttackInfo.attacker; | ||||
| 	auto enemy = AttackInfo.defender; | ||||
|  | ||||
| 	const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining()); | ||||
| 	const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION); | ||||
| 	const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue(); | ||||
|  | ||||
| 	AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0}; | ||||
|  | ||||
| 	auto curBai = AttackInfo; //we'll modify here the stack counts | ||||
| 	for(int i  = 0; i < totalAttacks; i++) | ||||
| 	{ | ||||
| 		std::pair<ui32, ui32> retaliation(0,0); | ||||
| 		auto attackDmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation); | ||||
| 		ap.damageDealt = (attackDmg.first + attackDmg.second) / 2; | ||||
| 		ap.damageReceived = (retaliation.first + retaliation.second) / 2; | ||||
|  | ||||
| 		if(remainingCounterAttacks <= i || counterAttacksBlocked) | ||||
| 			ap.damageReceived = 0; | ||||
|  | ||||
| 		curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first; | ||||
| 		curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first; | ||||
| 		if(!curBai.attackerCount) | ||||
| 			break; | ||||
| 		//TODO what about defender? should we break? but in pessimistic scenario defender might be alive | ||||
| 	} | ||||
|  | ||||
| 	//TODO other damage related to attack (eg. fire shield and other abilities) | ||||
|  | ||||
| 	//Limit damages by total stack health | ||||
| 	vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft)); | ||||
| 	vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft)); | ||||
|  | ||||
| 	return ap; | ||||
| } | ||||
|  | ||||
|  | ||||
| Priorities* AttackPossibility::priorities = nullptr; | ||||
							
								
								
									
										50
									
								
								AI/BattleAI/AttackPossibility.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								AI/BattleAI/AttackPossibility.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| /* | ||||
|  * AttackPossibility.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 "../../lib/BattleState.h" | ||||
| #include "CCallback.h" | ||||
| #include "common.h" | ||||
|  | ||||
|  | ||||
| struct HypotheticChangesToBattleState | ||||
| { | ||||
| 	std::map<const CStack *, const IBonusBearer *> bonusesOfStacks; | ||||
| 	std::map<const CStack *, int> counterAttacksLeft; | ||||
| }; | ||||
|  | ||||
| class Priorities | ||||
| { | ||||
| public: | ||||
| 	std::vector<double> resourceTypeBaseValues; | ||||
| 	std::function<double(const CStack *)> stackEvaluator; | ||||
| 	Priorities() | ||||
| 	{ | ||||
| 		//        range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues)); | ||||
| 		stackEvaluator = [](const CStack*){ return 1.0; }; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| class AttackPossibility | ||||
| { | ||||
| public: | ||||
| 	const CStack *enemy; //redundant (to attack.defender) but looks nice | ||||
| 	BattleHex tile; //tile from which we attack | ||||
| 	BattleAttackInfo attack; | ||||
|  | ||||
| 	int damageDealt; | ||||
| 	int damageReceived; //usually by counter-attack | ||||
| 	int tacticImpact; | ||||
|  | ||||
| 	int damageDiff() const; | ||||
| 	int attackValue() const; | ||||
|  | ||||
| 	static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex); | ||||
| 	static Priorities * priorities; | ||||
| }; | ||||
| @@ -1,88 +1,47 @@ | ||||
| /* | ||||
| 	 * 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 "../../lib/AI_Base.h" | ||||
| #include "BattleAI.h" | ||||
| #include "../../lib/BattleState.h" | ||||
| #include "../../CCallback.h" | ||||
| #include "../../lib/CCreatureHandler.h" | ||||
| #include "StackWithBonuses.h" | ||||
| #include "EnemyInfo.h" | ||||
| #include "../../lib/spells/CSpellHandler.h" | ||||
| #include "../../lib/VCMI_Lib.h" | ||||
|  | ||||
| using boost::optional; | ||||
| static std::shared_ptr<CBattleCallback> cbc; | ||||
|  | ||||
|  | ||||
| /* | ||||
| 	// | ||||
| 	// //set has its own order, so remove_if won't work. TODO - reuse for map | ||||
| 	// template<typename Elem, typename Predicate> | ||||
| 	// void erase_if(std::set<Elem> &setContainer, Predicate pred) | ||||
| 	// { | ||||
| 	// 	auto itr = setContainer.begin(); | ||||
| 	// 	auto endItr = setContainer.end(); | ||||
| 	// 	while(itr != endItr) | ||||
| 	// 	{ | ||||
| 	// 		auto tmpItr = itr++; | ||||
| 	// 		if(pred(*tmpItr)) | ||||
| 	// 			setContainer.erase(tmpItr); | ||||
| 	// 	} | ||||
| 	// } | ||||
| 	*/ | ||||
|  | ||||
| #define LOGL(text) print(text) | ||||
| #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl)) | ||||
|  | ||||
| struct Priorities | ||||
| { | ||||
| 	double manaValue; | ||||
| 	double generalResourceValueModifier; | ||||
| 	std::vector<double> resourceTypeBaseValues; | ||||
| 	std::function<double(const CStack *)> stackEvaluator; | ||||
|  | ||||
|  | ||||
| 	Priorities() | ||||
| 	{ | ||||
| 		manaValue = 0.; | ||||
| 		generalResourceValueModifier = 1.; | ||||
| 		range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues)); | ||||
| 		stackEvaluator = [](const CStack*){ return 1.0; }; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| Priorities *priorities = nullptr; | ||||
|  | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr) | ||||
| { | ||||
| 	int ret = 1000000; | ||||
| 	for(BattleHex n : hex.neighbouringTiles()) | ||||
| 	{ | ||||
| 		if(dists[n] >= 0 && dists[n] < ret) | ||||
| 		{ | ||||
| 			ret = dists[n]; | ||||
| 			if(chosenHex) | ||||
| 				*chosenHex = n; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists) | ||||
| { | ||||
| 	return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
| template <typename Container, typename Pred> | ||||
| auto sum(const Container & c, Pred p) -> decltype(p(*std::begin(c))) | ||||
| { | ||||
| 	double ret = 0; | ||||
| 	for(const auto &element : c) | ||||
| 	{ | ||||
| 		ret += p(element); | ||||
| 	} | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| CBattleAI::CBattleAI(void) | ||||
| 	: side(-1) | ||||
| { | ||||
| 	print("created"); | ||||
| } | ||||
|  | ||||
|  | ||||
| CBattleAI::~CBattleAI(void) | ||||
| { | ||||
| 	print("destroyed"); | ||||
|  | ||||
| 	if(cb) | ||||
| 	{ | ||||
| 		//Restore previous state of CB - it may be shared with the main AI (like VCAI) | ||||
| @@ -93,10 +52,9 @@ CBattleAI::~CBattleAI(void) | ||||
|  | ||||
| void CBattleAI::init(std::shared_ptr<CBattleCallback> CB) | ||||
| { | ||||
| 	print("init called, saving ptr to IBattleCallback"); | ||||
| 	cbc = cb = CB; | ||||
| 	setCbc(CB); | ||||
| 	cb = CB; | ||||
| 	playerID = *CB->getPlayerID();; //TODO should be sth in callback | ||||
|  | ||||
| 	wasWaitingForRealize = cb->waitTillRealize; | ||||
| 	wasUnlockingGs = CB->unlockGsWhenWaiting; | ||||
| 	CB->waitTillRealize = true; | ||||
| @@ -106,14 +64,11 @@ void CBattleAI::init(std::shared_ptr<CBattleCallback> CB) | ||||
| BattleAction CBattleAI::activeStack( const CStack * stack ) | ||||
| { | ||||
| 	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName())	; | ||||
|  | ||||
| 	cbc = cb; //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too) | ||||
| 	setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too) | ||||
| 	try | ||||
| 	{ | ||||
| 		print("activeStack called for " + stack->nodeName()); | ||||
| 		if(stack->type->idNumber == CreatureID::CATAPULT) | ||||
| 			return useCatapult(stack); | ||||
|  | ||||
| 		if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER)) | ||||
| 		{ | ||||
| 			auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE); | ||||
| @@ -121,7 +76,6 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) | ||||
| 			for(auto stack : healingTargets) | ||||
| 				if(auto woundHp = stack->MaxHealth() - stack->firstHPleft) | ||||
| 					woundHpToStack[woundHp] = stack; | ||||
|  | ||||
| 			if(woundHpToStack.empty()) | ||||
| 				return BattleAction::makeDefend(stack); | ||||
| 			else | ||||
| @@ -130,8 +84,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) | ||||
|  | ||||
| 		if(cb->battleCanCastSpell()) | ||||
| 			attemptCastingSpell(); | ||||
|  | ||||
| 		if(auto ret = cbc->battleIsFinished()) | ||||
| 		if(auto ret = getCbc()->battleIsFinished()) | ||||
| 		{ | ||||
| 			//spellcast may finish battle | ||||
| 			//send special preudo-action | ||||
| @@ -142,9 +95,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) | ||||
|  | ||||
| 		if(auto action = considerFleeingOrSurrendering()) | ||||
| 			return *action; | ||||
|  | ||||
| 		PotentialTargets targets(stack); | ||||
|  | ||||
| 		if(targets.possibleAttacks.size()) | ||||
| 		{ | ||||
| 			auto hlp = targets.bestAction(); | ||||
| @@ -157,8 +108,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) | ||||
| 		{ | ||||
| 			if(stack->waited()) | ||||
| 			{ | ||||
| 				ThreatMap threatsToUs(stack); | ||||
| 				auto dists = cbc->battleGetDistances(stack); | ||||
| 				//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code. | ||||
| 				auto dists = getCbc()->battleGetDistances(stack); | ||||
| 				const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists))); | ||||
| 				if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE) | ||||
| 				{ | ||||
| @@ -175,105 +126,16 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) | ||||
| 	{ | ||||
| 		logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what()); | ||||
| 	} | ||||
|  | ||||
| 	return BattleAction::makeDefend(stack); | ||||
| } | ||||
|  | ||||
| void CBattleAI::actionFinished(const BattleAction &action) | ||||
| { | ||||
| 	print("actionFinished called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::actionStarted(const BattleAction &action) | ||||
| { | ||||
| 	print("actionStarted called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleAttack(const BattleAttack *ba) | ||||
| { | ||||
| 	print("battleAttack called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) | ||||
| { | ||||
| 	print("battleStacksAttacked called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleEnd(const BattleResult *br) | ||||
| { | ||||
| 	print("battleEnd called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleNewRoundFirst(int round) | ||||
| { | ||||
| 	print("battleNewRoundFirst called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleNewRound(int round) | ||||
| { | ||||
| 	print("battleNewRound called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) | ||||
| { | ||||
| 	print("battleStackMoved called");; | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleSpellCast(const BattleSpellCast *sc) | ||||
| { | ||||
| 	print("battleSpellCast called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleStacksEffectsSet(const SetStackEffect & sse) | ||||
| { | ||||
| 	print("battleStacksEffectsSet called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side) | ||||
| { | ||||
| 	print("battleStart called"); | ||||
| 	side = Side; | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) | ||||
| { | ||||
| 	print("battleStacksHealedRes called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleNewStackAppeared(const CStack * stack) | ||||
| { | ||||
| 	print("battleNewStackAppeared called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleObstaclesRemoved(const std::set<si32> & removedObstacles) | ||||
| { | ||||
| 	print("battleObstaclesRemoved called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleCatapultAttacked(const CatapultAttack & ca) | ||||
| { | ||||
| 	print("battleCatapultAttacked called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleStacksRemoved(const BattleStacksRemoved & bsr) | ||||
| { | ||||
| 	print("battleStacksRemoved called"); | ||||
| } | ||||
|  | ||||
| void CBattleAI::print(const std::string &text) const | ||||
| { | ||||
| 	logAi->trace("CBattleAI [%p]: %s", this, text); | ||||
| } | ||||
|  | ||||
| BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) | ||||
| { | ||||
| 	assert(destination.isValid()); | ||||
| 	auto avHexes = cb->battleGetAvailableHexes(stack, false); | ||||
| 	auto reachability = cb->getReachability(stack); | ||||
|  | ||||
| 	if(vstd::contains(avHexes, destination)) | ||||
| 		return BattleAction::makeMove(stack, destination); | ||||
|  | ||||
| 	auto destNeighbours = destination.neighbouringTiles(); | ||||
| 	if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); })) | ||||
| 	{ | ||||
| @@ -281,15 +143,11 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) | ||||
| 		//We shouldn't even be here... | ||||
| 		return BattleAction::makeDefend(stack); | ||||
| 	} | ||||
|  | ||||
| 	vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); }); | ||||
|  | ||||
| 	if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked | ||||
| 	{ | ||||
| 		print("goTowards: Stack cannot move! That's " + stack->nodeName()); | ||||
| 		return BattleAction::makeDefend(stack); | ||||
| 	} | ||||
|  | ||||
| 	if(stack->hasBonusOfType(Bonus::FLYING)) | ||||
| 	{ | ||||
| 		// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. | ||||
| @@ -297,13 +155,9 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) | ||||
| 		auto distToDestNeighbour = [&](BattleHex hex) -> int | ||||
| 		{ | ||||
| 			auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a) | ||||
| 			{ | ||||
| 				return BattleHex::getDistance(a, hex); | ||||
| 			}); | ||||
|  | ||||
| 			{return BattleHex::getDistance(a, hex);}); | ||||
| 			return BattleHex::getDistance(*nearestNeighbourToHex, hex); | ||||
| 		}; | ||||
|  | ||||
| 		auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour); | ||||
| 		return BattleAction::makeMove(stack, *nearestAvailableHex); | ||||
| 	} | ||||
| @@ -312,17 +166,14 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) | ||||
| 		BattleHex bestNeighbor = destination; | ||||
| 		if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE) | ||||
| 		{ | ||||
| 			print("goTowards: Cannot reach"); | ||||
| 			return BattleAction::makeDefend(stack); | ||||
| 		} | ||||
|  | ||||
| 		BattleHex currentDest = bestNeighbor; | ||||
| 		while(1) | ||||
| 		{ | ||||
| 			assert(currentDest.isValid()); | ||||
| 			if(vstd::contains(avHexes, currentDest)) | ||||
| 				return BattleAction::makeMove(stack, currentDest); | ||||
|  | ||||
| 			currentDest = reachability.predecessors[currentDest]; | ||||
| 		} | ||||
| 	} | ||||
| @@ -333,6 +184,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack) | ||||
| 	throw std::runtime_error("The method or operation is not implemented."); | ||||
| } | ||||
|  | ||||
|  | ||||
| enum SpellTypes | ||||
| { | ||||
| 	OFFENSIVE_SPELL, TIMED_EFFECT, OTHER | ||||
| @@ -345,74 +197,17 @@ SpellTypes spellType(const CSpell *spell) | ||||
| 	if (spell->hasEffects()) | ||||
| 		return TIMED_EFFECT; | ||||
| 	return OTHER; | ||||
|  | ||||
| } | ||||
|  | ||||
| struct PossibleSpellcast | ||||
| { | ||||
| 	const CSpell *spell; | ||||
| 	BattleHex dest; | ||||
| 	si32 value; | ||||
| }; | ||||
|  | ||||
| struct CurrentOffensivePotential | ||||
| { | ||||
| 	std::map<const CStack *, PotentialTargets> ourAttacks; | ||||
| 	std::map<const CStack *, PotentialTargets> enemyAttacks; | ||||
|  | ||||
| 	CurrentOffensivePotential(ui8 side) | ||||
| 	{ | ||||
| 		for(auto stack : cbc->battleGetStacks()) | ||||
| 		{ | ||||
| 			if(stack->attackerOwned == !side) | ||||
| 				ourAttacks[stack] = PotentialTargets(stack); | ||||
| 			else | ||||
| 				enemyAttacks[stack] = PotentialTargets(stack); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	int potentialValue() | ||||
| 	{ | ||||
| 		int ourPotential = 0, enemyPotential = 0; | ||||
| 		for(auto &p : ourAttacks) | ||||
| 			ourPotential += p.second.bestAction().attackValue(); | ||||
|  | ||||
| 		for(auto &p : enemyAttacks) | ||||
| 			enemyPotential += p.second.bestAction().attackValue(); | ||||
|  | ||||
| 		return ourPotential - enemyPotential; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|  | ||||
| // | ||||
| // //set has its own order, so remove_if won't work. TODO - reuse for map | ||||
| // template<typename Elem, typename Predicate> | ||||
| // void erase_if(std::set<Elem> &setContainer, Predicate pred) | ||||
| // { | ||||
| // 	auto itr = setContainer.begin(); | ||||
| // 	auto endItr = setContainer.end(); | ||||
| // 	while(itr != endItr) | ||||
| // 	{ | ||||
| // 		auto tmpItr = itr++; | ||||
| // 		if(pred(*tmpItr)) | ||||
| // 			setContainer.erase(tmpItr); | ||||
| // 	} | ||||
| // } | ||||
|  | ||||
| void CBattleAI::attemptCastingSpell() | ||||
| { | ||||
| 	LOGL("Casting spells sounds like fun. Let's see..."); | ||||
|  | ||||
| 	auto hero = cb->battleGetMyHero(); | ||||
|  | ||||
| 	//auto known = cb->battleGetFightingHero(side); | ||||
|  | ||||
| 	auto hero = cb->battleGetMyHero(); //auto known = cb->battleGetFightingHero(side); | ||||
| 	//Get all spells we can cast | ||||
| 	std::vector<const CSpell*> possibleSpells; | ||||
| 	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool | ||||
| 	{ | ||||
| 		auto problem = cbc->battleCanCastThisSpell(s); | ||||
| 		auto problem = getCbc()->battleCanCastThisSpell(s); | ||||
| 		return problem == ESpellCastProblem::OK; | ||||
| 	}); | ||||
| 	LOGFL("I can cast %d spells.", possibleSpells.size()); | ||||
| @@ -446,74 +241,59 @@ void CBattleAI::attemptCastingSpell() | ||||
| 	{ | ||||
| 		const int skillLevel = hero->getSpellSchoolLevel(ps.spell); | ||||
| 		const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER); | ||||
|  | ||||
| 		switch(spellType(ps.spell)) | ||||
| 		{ | ||||
| 		case OFFENSIVE_SPELL: | ||||
| 		{ | ||||
| 			int damageDealt = 0, damageReceived = 0; | ||||
| 			auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); | ||||
| 			if(stacksSuffering.empty()) | ||||
| 				return -1; | ||||
| 			for(auto stack : stacksSuffering) | ||||
| 			{ | ||||
| 				int damageDealt = 0, damageReceived = 0; | ||||
|  | ||||
| 				auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); | ||||
|  | ||||
| 				if(stacksSuffering.empty()) | ||||
| 					return -1; | ||||
|  | ||||
| 				for(auto stack : stacksSuffering) | ||||
| 				{ | ||||
| 					const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower); | ||||
| 					if(stack->owner == playerID) | ||||
| 						damageReceived += dmg; | ||||
| 					else | ||||
| 						damageDealt += dmg; | ||||
| 				} | ||||
|  | ||||
| 				const int damageDiff = damageDealt - damageReceived * 10; | ||||
|  | ||||
| 				LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.", | ||||
| 					ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size()); | ||||
| 				//TODO tactic effect too | ||||
| 				return damageDiff; | ||||
| 				const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower); | ||||
| 				if(stack->owner == playerID) | ||||
| 					damageReceived += dmg; | ||||
| 				else | ||||
| 					damageDealt += dmg; | ||||
| 			} | ||||
| 			const int damageDiff = damageDealt - damageReceived * 10; | ||||
| 			LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.", | ||||
| 				  ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size()); | ||||
| 			//TODO tactic effect too | ||||
| 			return damageDiff; | ||||
| 		} | ||||
| 		case TIMED_EFFECT: | ||||
| 		{ | ||||
| 			auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); | ||||
| 			if(stacksAffected.empty()) | ||||
| 				return -1; | ||||
| 			int totalGain = 0; | ||||
| 			for(const CStack * sta : stacksAffected) | ||||
| 			{ | ||||
| 				auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); | ||||
|  | ||||
| 				if(stacksAffected.empty()) | ||||
| 					return -1; | ||||
|  | ||||
| 				int totalGain = 0; | ||||
|  | ||||
| 				for(const CStack * sta : stacksAffected) | ||||
| 				{ | ||||
| 					StackWithBonuses swb; | ||||
| 					swb.stack = sta; | ||||
|  | ||||
| 					Bonus pseudoBonus; | ||||
| 					pseudoBonus.sid = ps.spell->id; | ||||
| 					pseudoBonus.val = skillLevel; | ||||
| 					pseudoBonus.turnsRemain = 1; //TODO | ||||
| 					CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus); | ||||
|  | ||||
| 					HypotheticChangesToBattleState state; | ||||
| 					state.bonusesOfStacks[swb.stack] = &swb; | ||||
|  | ||||
| 					PotentialTargets pt(swb.stack, state); | ||||
| 					auto newValue = pt.bestActionValue(); | ||||
| 					auto oldValue = valueOfStack[swb.stack]; | ||||
| 					auto gain = newValue - oldValue; | ||||
| 					if(swb.stack->owner != playerID) //enemy | ||||
| 						gain = -gain; | ||||
|  | ||||
| 					LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)", | ||||
| 						ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue)); | ||||
|  | ||||
| 					totalGain += gain; | ||||
| 				} | ||||
|  | ||||
| 				LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain)); | ||||
|  | ||||
| 				return totalGain; | ||||
| 				StackWithBonuses swb; | ||||
| 				swb.stack = sta; | ||||
| 				Bonus pseudoBonus; | ||||
| 				pseudoBonus.sid = ps.spell->id; | ||||
| 				pseudoBonus.val = skillLevel; | ||||
| 				pseudoBonus.turnsRemain = 1; //TODO | ||||
| 				CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus); | ||||
| 				HypotheticChangesToBattleState state; | ||||
| 				state.bonusesOfStacks[swb.stack] = &swb; | ||||
| 				PotentialTargets pt(swb.stack, state); | ||||
| 				auto newValue = pt.bestActionValue(); | ||||
| 				auto oldValue = valueOfStack[swb.stack]; | ||||
| 				auto gain = newValue - oldValue; | ||||
| 				if(swb.stack->owner != playerID) //enemy | ||||
| 					gain = -gain; | ||||
| 				LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)", | ||||
| 					  ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue)); | ||||
| 				totalGain += gain; | ||||
| 			} | ||||
|  | ||||
| 			LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain)); | ||||
| 			return totalGain; | ||||
| 		} | ||||
| 		default: | ||||
| 			assert(0); | ||||
| 			return 0; | ||||
| @@ -522,22 +302,18 @@ void CBattleAI::attemptCastingSpell() | ||||
|  | ||||
| 	for(PossibleSpellcast & psc : possibleCasts) | ||||
| 		psc.value = evaluateSpellcast(psc); | ||||
|  | ||||
| 	auto pscValue = [] (const PossibleSpellcast &ps) -> int | ||||
| 	{ | ||||
| 		return ps.value; | ||||
| 	}; | ||||
|  | ||||
| 	auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue); | ||||
| 	LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name); | ||||
|  | ||||
| 	BattleAction spellcast; | ||||
| 	spellcast.actionType = Battle::HERO_SPELL; | ||||
| 	spellcast.additionalInfo = castToPerform.spell->id; | ||||
| 	spellcast.destinationTile = castToPerform.dest; | ||||
| 	spellcast.side = side; | ||||
| 	spellcast.stackNumber = (!side) ? -1 : -2; | ||||
|  | ||||
| 	cb->battleMakeAction(&spellcast); | ||||
| } | ||||
|  | ||||
| @@ -545,7 +321,6 @@ std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, con | ||||
| { | ||||
| 	const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell)); | ||||
| 	std::vector<BattleHex> ret; | ||||
|  | ||||
| 	if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET) | ||||
| 	{ | ||||
| 		ret.push_back(BattleHex()); | ||||
| @@ -555,240 +330,86 @@ std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, con | ||||
| 		switch(targetInfo.type) | ||||
| 		{ | ||||
| 		case CSpell::CREATURE: | ||||
| 		{ | ||||
| 			for(const CStack * stack : getCbc()->battleAliveStacks()) | ||||
| 			{ | ||||
| 				for(const CStack * stack : cbc->battleAliveStacks()) | ||||
| 				{ | ||||
| 					bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack); | ||||
| 					bool casterStack = stack->owner == caster->getOwner(); | ||||
| 				bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack); | ||||
| 				bool casterStack = stack->owner == caster->getOwner(); | ||||
|  | ||||
| 					if(!immune) | ||||
| 						switch (spell->positiveness) | ||||
| 						{ | ||||
| 						case CSpell::POSITIVE: | ||||
| 							if(casterStack || targetInfo.smart) | ||||
| 								ret.push_back(stack->position); | ||||
| 							break; | ||||
|  | ||||
| 						case CSpell::NEUTRAL: | ||||
| 				if(!immune) | ||||
| 					switch (spell->positiveness) | ||||
| 					{ | ||||
| 					case CSpell::POSITIVE: | ||||
| 						if(casterStack || targetInfo.smart) | ||||
| 							ret.push_back(stack->position); | ||||
| 							break; | ||||
|  | ||||
| 						case CSpell::NEGATIVE: | ||||
| 							if(!casterStack || targetInfo.smart) | ||||
| 								ret.push_back(stack->position); | ||||
| 							break; | ||||
| 						} | ||||
| 				} | ||||
| 						break; | ||||
| 					case CSpell::NEUTRAL: | ||||
| 						ret.push_back(stack->position); | ||||
| 						break; | ||||
| 					case CSpell::NEGATIVE: | ||||
| 						if(!casterStack || targetInfo.smart) | ||||
| 							ret.push_back(stack->position); | ||||
| 						break; | ||||
| 					} | ||||
| 			} | ||||
| 		} | ||||
| 			break; | ||||
| 		case CSpell::LOCATION: | ||||
| 			{ | ||||
| 				for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 					if(BattleHex(i).isAvailable()) | ||||
| 						ret.push_back(i); | ||||
| 			} | ||||
| 		{ | ||||
| 			for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 				if(BattleHex(i).isAvailable()) | ||||
| 					ret.push_back(i); | ||||
| 		} | ||||
| 			break; | ||||
|  | ||||
| 		default: | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex) | ||||
| { | ||||
| 	int ret = 1000000; | ||||
| 	for(BattleHex n : hex.neighbouringTiles()) | ||||
| 	{ | ||||
| 		if(dists[n] >= 0 && dists[n] < ret) | ||||
| 		{ | ||||
| 			ret = dists[n]; | ||||
| 			if(chosenHex) | ||||
| 				*chosenHex = n; | ||||
| 		} | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side) | ||||
| { | ||||
| 	print("battleStart called"); | ||||
| 	side = Side; | ||||
| } | ||||
|  | ||||
| bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists) | ||||
| { | ||||
| 	return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); | ||||
| } | ||||
|  | ||||
| void CBattleAI::print(const std::string &text) const | ||||
| { | ||||
| 	logAi->trace("CBattleAI [%p]: %s", this, text); | ||||
| } | ||||
|  | ||||
| boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering() | ||||
| { | ||||
| 	if(cb->battleCanSurrender(playerID)) | ||||
| 	{ | ||||
|  | ||||
| 	} | ||||
| 	if(cb->battleCanFlee()) | ||||
| 	{ | ||||
|  | ||||
| 	} | ||||
| 	return boost::none; | ||||
| } | ||||
|  | ||||
| ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered) | ||||
| { | ||||
| 	sufferedDamage.fill(0); | ||||
|  | ||||
| 	for(const CStack *enemy : cbc->battleGetStacks()) | ||||
| 	{ | ||||
| 		//Consider only stacks of different owner | ||||
| 		if(enemy->attackerOwned == endangered->attackerOwned) | ||||
| 			continue; | ||||
|  | ||||
| 		//Look-up which tiles can be melee-attacked | ||||
| 		std::array<bool, GameConstants::BFIELD_SIZE> meleeAttackable; | ||||
| 		meleeAttackable.fill(false); | ||||
| 		auto enemyReachability = cbc->getReachability(enemy); | ||||
| 		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 		{ | ||||
| 			if(enemyReachability.isReachable(i)) | ||||
| 			{ | ||||
| 				meleeAttackable[i] = true; | ||||
| 				for(auto n : BattleHex(i).neighbouringTiles()) | ||||
| 					meleeAttackable[n] = true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//Gather possible assaults | ||||
| 		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 		{ | ||||
| 			if(cbc->battleCanShoot(enemy, i)) | ||||
| 				threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true)); | ||||
| 			else if(meleeAttackable[i]) | ||||
| 			{ | ||||
| 				BattleAttackInfo bai(enemy, endangered, false); | ||||
| 				bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric | ||||
| 				threatMap[i].push_back(BattleAttackInfo(bai)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 	{ | ||||
| 		sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int | ||||
| 		{ | ||||
| 			auto dmg = cbc->calculateDmgRange(bai); | ||||
| 			return (dmg.first + dmg.second)/2; | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root /*= nullptr*/, const std::string &cachingStr /*= ""*/) const | ||||
| { | ||||
| 	TBonusListPtr ret = std::make_shared<BonusList>(); | ||||
| 	const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr); | ||||
| 	range::copy(*originalList, std::back_inserter(*ret)); | ||||
| 	for(auto &bonus : bonusesToAdd) | ||||
| 	{ | ||||
| 		auto b = std::make_shared<Bonus>(bonus); | ||||
| 		if(selector(b.get())  &&  (!limit || !limit(b.get()))) | ||||
| 			ret->push_back(b); | ||||
| 	} | ||||
|  | ||||
| 	//TODO limiters? | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| int AttackPossibility::damageDiff() const | ||||
| { | ||||
| 	if (!priorities) | ||||
| 		priorities = new Priorities; | ||||
| 	const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt; | ||||
| 	const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived; | ||||
| 	return dealtDmgValue - receivedDmgValue; | ||||
| } | ||||
|  | ||||
| int AttackPossibility::attackValue() const | ||||
| { | ||||
| 	return damageDiff() + tacticImpact; | ||||
| } | ||||
|  | ||||
| AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex) | ||||
| { | ||||
| 	auto attacker = AttackInfo.attacker; | ||||
| 	auto enemy = AttackInfo.defender; | ||||
|  | ||||
| 	const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacksRemaining()); | ||||
| 	const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION); | ||||
| 	const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue(); | ||||
|  | ||||
| 	AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0}; | ||||
|  | ||||
| 	auto curBai = AttackInfo; //we'll modify here the stack counts | ||||
| 	for(int i  = 0; i < totalAttacks; i++) | ||||
| 	{ | ||||
| 		std::pair<ui32, ui32> retaliation(0,0); | ||||
| 		auto attackDmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation); | ||||
| 		ap.damageDealt = (attackDmg.first + attackDmg.second) / 2; | ||||
| 		ap.damageReceived = (retaliation.first + retaliation.second) / 2; | ||||
|  | ||||
| 		if(remainingCounterAttacks <= i || counterAttacksBlocked) | ||||
| 			ap.damageReceived = 0; | ||||
|  | ||||
| 		curBai.attackerCount = attacker->count - attacker->countKilledByAttack(ap.damageReceived).first; | ||||
| 		curBai.defenderCount = enemy->count - enemy->countKilledByAttack(ap.damageDealt).first; | ||||
| 		if(!curBai.attackerCount) | ||||
| 			break; | ||||
| 		//TODO what about defender? should we break? but in pessimistic scenario defender might be alive | ||||
| 	} | ||||
|  | ||||
| 	//TODO other damage related to attack (eg. fire shield and other abilities) | ||||
|  | ||||
| 	//Limit damages by total stack health | ||||
| 	vstd::amin(ap.damageDealt, enemy->count * enemy->MaxHealth() - (enemy->MaxHealth() - enemy->firstHPleft)); | ||||
| 	vstd::amin(ap.damageReceived, attacker->count * attacker->MaxHealth() - (attacker->MaxHealth() - attacker->firstHPleft)); | ||||
|  | ||||
| 	return ap; | ||||
| } | ||||
|  | ||||
|  | ||||
| PotentialTargets::PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/) | ||||
| { | ||||
| 	auto dists = cbc->battleGetDistances(attacker); | ||||
| 	auto avHexes = cbc->battleGetAvailableHexes(attacker, false); | ||||
|  | ||||
| 	for(const CStack *enemy : cbc->battleGetStacks()) | ||||
| 	{ | ||||
| 		//Consider only stacks of different owner | ||||
| 		if(enemy->attackerOwned == attacker->attackerOwned) | ||||
| 			continue; | ||||
|  | ||||
| 		auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility | ||||
| 		{ | ||||
| 			auto bai = BattleAttackInfo(attacker, enemy, shooting); | ||||
| 			bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker); | ||||
| 			bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender); | ||||
|  | ||||
| 			if(hex.isValid()) | ||||
| 			{ | ||||
| 				assert(dists[hex] <= attacker->Speed()); | ||||
| 				bai.chargedFields = dists[hex]; | ||||
| 			} | ||||
|  | ||||
| 			return AttackPossibility::evaluate(bai, state, hex); | ||||
| 		}; | ||||
|  | ||||
| 		if(cbc->battleCanShoot(attacker, enemy->position)) | ||||
| 		{ | ||||
| 			possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			for(BattleHex hex : avHexes) | ||||
| 				if(CStack::isMeleeAttackPossible(attacker, enemy, hex)) | ||||
| 					possibleAttacks.push_back(GenerateAttackInfo(false, hex)); | ||||
|  | ||||
| 			if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; })) | ||||
| 				unreachableEnemies.push_back(enemy); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| AttackPossibility PotentialTargets::bestAction() const | ||||
| { | ||||
| 	if(possibleAttacks.empty()) | ||||
| 		throw std::runtime_error("No best action, since we don't have any actions"); | ||||
|  | ||||
| 	return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } ); | ||||
| } | ||||
|  | ||||
| int PotentialTargets::bestActionValue() const | ||||
| { | ||||
| 	if(possibleAttacks.empty()) | ||||
| 		return 0; | ||||
|  | ||||
| 	return bestAction().attackValue(); | ||||
| } | ||||
|  | ||||
| void EnemyInfo::calcDmg(const CStack * ourStack) | ||||
| { | ||||
| 	TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); | ||||
| 	adi = (dmg.first + dmg.second) / 2; | ||||
| 	adr = (retal.first + retal.second) / 2; | ||||
| } | ||||
|   | ||||
| @@ -1,110 +1,55 @@ | ||||
| /* | ||||
|  * BattleAI.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 "../../lib/BattleHex.h" | ||||
| #include "../../lib/HeroBonus.h" | ||||
| #include "../../lib/CBattleCallback.h" | ||||
| #include "../../lib/AI_Base.h" | ||||
| #include "PotentialTargets.h" | ||||
|  | ||||
| class CSpell; | ||||
| class EnemyInfo; | ||||
|  | ||||
|  | ||||
| class StackWithBonuses : public IBonusBearer | ||||
| /* | ||||
| struct CurrentOffensivePotential | ||||
| { | ||||
| public: | ||||
| 	const CStack *stack; | ||||
| 	mutable std::vector<Bonus> bonusesToAdd; | ||||
| 	std::map<const CStack *, PotentialTargets> ourAttacks; | ||||
| 	std::map<const CStack *, PotentialTargets> enemyAttacks; | ||||
|  | ||||
| 	virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; | ||||
| }; | ||||
|  | ||||
| struct EnemyInfo | ||||
| { | ||||
| 	const CStack * s; | ||||
| 	int adi, adr; | ||||
| 	std::vector<BattleHex> attackFrom; //for melee fight | ||||
| 	EnemyInfo(const CStack * _s) : s(_s) | ||||
| 	{} | ||||
| 	void calcDmg(const CStack * ourStack); | ||||
|  | ||||
| 	bool operator==(const EnemyInfo& ei) const | ||||
| 	CurrentOffensivePotential(ui8 side) | ||||
| 	{ | ||||
| 		return s == ei.s; | ||||
| 		for(auto stack : cbc->battleGetStacks()) | ||||
| 		{ | ||||
| 			if(stack->attackerOwned == !side) | ||||
| 				ourAttacks[stack] = PotentialTargets(stack); | ||||
| 			else | ||||
| 				enemyAttacks[stack] = PotentialTargets(stack); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	int potentialValue() | ||||
| 	{ | ||||
| 		int ourPotential = 0, enemyPotential = 0; | ||||
| 		for(auto &p : ourAttacks) | ||||
| 			ourPotential += p.second.bestAction().attackValue(); | ||||
|  | ||||
| 		for(auto &p : enemyAttacks) | ||||
| 			enemyPotential += p.second.bestAction().attackValue(); | ||||
|  | ||||
| 		return ourPotential - enemyPotential; | ||||
| 	} | ||||
| }; | ||||
| */ // These lines may be usefull but they are't used in the code. | ||||
|  | ||||
|  | ||||
| //FIXME: unused function | ||||
| /* | ||||
| static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2) | ||||
| struct PossibleSpellcast | ||||
| { | ||||
| 	int shooters[2] = {0}; //count of shooters on hexes | ||||
|  | ||||
| 	for(int i = 0; i < 2; i++) | ||||
| 		BOOST_FOREACH(BattleHex neighbour, (i ? h2 : h1).neighbouringTiles()) | ||||
| 			if(const CStack *s = cbc->battleGetStackByPos(neighbour)) | ||||
| 				if(s->getCreature()->isShooting()) | ||||
| 						shooters[i]++; | ||||
|  | ||||
| 	return shooters[0] < shooters[1]; | ||||
| } | ||||
| */ | ||||
|  | ||||
|  | ||||
|  | ||||
| struct ThreatMap | ||||
| { | ||||
| 	std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike | ||||
|  | ||||
| 	const CStack *endangered; | ||||
| 	std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage; | ||||
|  | ||||
| 	ThreatMap(const CStack *Endangered); | ||||
| }; | ||||
|  | ||||
| struct HypotheticChangesToBattleState | ||||
| { | ||||
| 	std::map<const CStack *, const IBonusBearer *> bonusesOfStacks; | ||||
| 	std::map<const CStack *, int> counterAttacksLeft; | ||||
| }; | ||||
|  | ||||
| struct AttackPossibility | ||||
| { | ||||
| 	const CStack *enemy; //redundant (to attack.defender) but looks nice | ||||
| 	BattleHex tile; //tile from which we attack | ||||
| 	BattleAttackInfo attack; | ||||
|  | ||||
| 	int damageDealt; | ||||
| 	int damageReceived; //usually by counter-attack | ||||
| 	int tacticImpact; | ||||
|  | ||||
| 	int damageDiff() const; | ||||
| 	int attackValue() const; | ||||
|  | ||||
| 	static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex); | ||||
| }; | ||||
|  | ||||
| template<typename Key, typename Val, typename Val2> | ||||
| const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 defaultValue) | ||||
| { | ||||
| 	//returning references here won't work: defaultValue must be converted into Val, creating temporary | ||||
| 	auto i = Map.find(key); | ||||
| 	if(i != Map.end()) | ||||
| 		return i->second; | ||||
| 	else | ||||
| 		return defaultValue; | ||||
| } | ||||
|  | ||||
| struct PotentialTargets | ||||
| { | ||||
| 	std::vector<AttackPossibility> possibleAttacks; | ||||
| 	std::vector<const CStack *> unreachableEnemies; | ||||
|  | ||||
| 	//std::function<AttackPossibility(bool,BattleHex)>  GenerateAttackInfo; //args: shooting, destHex | ||||
|  | ||||
| 	PotentialTargets(){}; | ||||
| 	PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState()); | ||||
|  | ||||
| 	AttackPossibility bestAction() const; | ||||
| 	int bestActionValue() const; | ||||
| 	const CSpell *spell; | ||||
| 	BattleHex dest; | ||||
| 	si32 value; | ||||
| }; | ||||
|  | ||||
| class CBattleAI : public CBattleGameInterface | ||||
| @@ -115,39 +60,41 @@ class CBattleAI : public CBattleGameInterface | ||||
| 	//Previous setting of cb | ||||
| 	bool wasWaitingForRealize, wasUnlockingGs; | ||||
|  | ||||
| 	void print(const std::string &text) const; | ||||
| public: | ||||
| 	CBattleAI(void); | ||||
| 	~CBattleAI(void); | ||||
|  | ||||
| 	void init(std::shared_ptr<CBattleCallback> CB) override; | ||||
| 	void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero | ||||
| 	void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero | ||||
| 	void attemptCastingSpell(); | ||||
|  | ||||
| 	BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack | ||||
|  | ||||
| 	void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack | ||||
| 	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack()) | ||||
| 	void battleEnd(const BattleResult *br) override; | ||||
| 	//void battleResultsApplied() override; //called when all effects of last battle are applied | ||||
| 	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; | ||||
| 	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn | ||||
| 	void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override; | ||||
| 	void battleSpellCast(const BattleSpellCast *sc) override; | ||||
| 	void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks | ||||
| 	//void battleTriggerEffect(const BattleTriggerEffect & bte) override; | ||||
| 	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right | ||||
| 	void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp | ||||
| 	void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned | ||||
| 	void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given | ||||
| 	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack | ||||
| 	void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield | ||||
|  | ||||
| 	BattleAction goTowards(const CStack * stack, BattleHex hex ); | ||||
| 	BattleAction useCatapult(const CStack * stack); | ||||
|  | ||||
| 	boost::optional<BattleAction> considerFleeingOrSurrendering(); | ||||
|  | ||||
| 	void attemptCastingSpell(); | ||||
| 	std::vector<BattleHex> getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const; | ||||
| }; | ||||
| 	static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr); | ||||
| 	static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists); | ||||
|  | ||||
| 	void print(const std::string &text) const; | ||||
| 	BattleAction useCatapult(const CStack *stack); | ||||
| 	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side); | ||||
| 	//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero | ||||
| 	//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero | ||||
| 	//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack | ||||
| 	//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack()) | ||||
| 	//void battleEnd(const BattleResult *br) override; | ||||
| 	//void battleResultsApplied() override; //called when all effects of last battle are applied | ||||
| 	//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; | ||||
| 	//void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn | ||||
| 	//void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override; | ||||
| 	//void battleSpellCast(const BattleSpellCast *sc) override; | ||||
| 	//void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks | ||||
| 	//void battleTriggerEffect(const BattleTriggerEffect & bte) override; | ||||
| 	//void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right | ||||
| 	//void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp | ||||
| 	//void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned | ||||
| 	//void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given | ||||
| 	//void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack | ||||
| 	//void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield | ||||
| }; | ||||
|   | ||||
| @@ -6,7 +6,13 @@ include_directories(${Boost_INCLUDE_DIRS} ${CMAKE_HOME_DIRECTORY} ${CMAKE_HOME_D | ||||
| set(battleAI_SRCS | ||||
| 		StdInc.cpp | ||||
| 		BattleAI.cpp | ||||
| 		StackWithBonuses.cpp | ||||
| 		EnemyInfo.cpp | ||||
| 		AttackPossibility.cpp | ||||
| 		PotentialTargets.cpp | ||||
| 		main.cpp | ||||
| 		common.cpp | ||||
| 		ThreatMap.cpp | ||||
| ) | ||||
|  | ||||
| add_library(BattleAI SHARED ${battleAI_SRCS}) | ||||
| @@ -16,6 +22,6 @@ set_target_properties(BattleAI PROPERTIES ${PCH_PROPERTIES}) | ||||
| cotire(BattleAI) | ||||
|  | ||||
| if (NOT APPLE) # Already inside vcmiclient bundle | ||||
|     install(TARGETS BattleAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR}) | ||||
| 	install(TARGETS BattleAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR}) | ||||
| endif() | ||||
|  | ||||
|   | ||||
							
								
								
									
										21
									
								
								AI/BattleAI/EnemyInfo.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								AI/BattleAI/EnemyInfo.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| /* | ||||
|  * EnemyInfo.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 "EnemyInfo.h" | ||||
| #include "CRandomGenerator.h" | ||||
| #include "CCallback.h" | ||||
| #include "common.h" | ||||
|  | ||||
| void EnemyInfo::calcDmg(const CStack * ourStack) | ||||
| { | ||||
| 	TDmgRange retal, dmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); | ||||
| 	adi = (dmg.first + dmg.second) / 2; | ||||
| 	adr = (retal.first + retal.second) / 2; | ||||
| } | ||||
							
								
								
									
										28
									
								
								AI/BattleAI/EnemyInfo.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								AI/BattleAI/EnemyInfo.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| /* | ||||
|  * EnemyInfo.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" | ||||
|  | ||||
| class CStack; | ||||
|  | ||||
| class EnemyInfo | ||||
| { | ||||
| public: | ||||
| 	const CStack * s; | ||||
| 	int adi, adr; | ||||
| 	std::vector<BattleHex> attackFrom; //for melee fight | ||||
| 	EnemyInfo(const CStack * _s) : s(_s) | ||||
| 	{} | ||||
| 	void calcDmg(const CStack * ourStack); | ||||
| 	bool operator==(const EnemyInfo& ei) const | ||||
| 	{ | ||||
| 		return s == ei.s; | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										71
									
								
								AI/BattleAI/PotentialTargets.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								AI/BattleAI/PotentialTargets.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| /* | ||||
|  * PotentialTargets.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 "PotentialTargets.h" | ||||
|  | ||||
| PotentialTargets::PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state /*= HypotheticChangesToBattleState()*/) | ||||
| { | ||||
| 	auto dists = getCbc()->battleGetDistances(attacker); | ||||
| 	auto avHexes = getCbc()->battleGetAvailableHexes(attacker, false); | ||||
|  | ||||
| 	for(const CStack *enemy : getCbc()->battleGetStacks()) | ||||
| 	{ | ||||
| 		//Consider only stacks of different owner | ||||
| 		if(enemy->attackerOwned == attacker->attackerOwned) | ||||
| 			continue; | ||||
|  | ||||
| 		auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility | ||||
| 		{ | ||||
| 			auto bai = BattleAttackInfo(attacker, enemy, shooting); | ||||
| 			bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker); | ||||
| 			bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender); | ||||
|  | ||||
| 			if(hex.isValid()) | ||||
| 			{ | ||||
| 				assert(dists[hex] <= attacker->Speed()); | ||||
| 				bai.chargedFields = dists[hex]; | ||||
| 			} | ||||
|  | ||||
| 			return AttackPossibility::evaluate(bai, state, hex); | ||||
| 		}; | ||||
|  | ||||
| 		if(getCbc()->battleCanShoot(attacker, enemy->position)) | ||||
| 		{ | ||||
| 			possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			for(BattleHex hex : avHexes) | ||||
| 				if(CStack::isMeleeAttackPossible(attacker, enemy, hex)) | ||||
| 					possibleAttacks.push_back(GenerateAttackInfo(false, hex)); | ||||
|  | ||||
| 			if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; })) | ||||
| 				unreachableEnemies.push_back(enemy); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| int PotentialTargets::bestActionValue() const | ||||
| { | ||||
| 	if(possibleAttacks.empty()) | ||||
| 		return 0; | ||||
|  | ||||
| 	return bestAction().attackValue(); | ||||
| } | ||||
|  | ||||
| AttackPossibility PotentialTargets::bestAction() const | ||||
| { | ||||
| 	if(possibleAttacks.empty()) | ||||
| 		throw std::runtime_error("No best action, since we don't have any actions"); | ||||
|  | ||||
| 	return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } ); | ||||
| } | ||||
							
								
								
									
										26
									
								
								AI/BattleAI/PotentialTargets.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								AI/BattleAI/PotentialTargets.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| /* | ||||
|  * PotentialTargets.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 "AttackPossibility.h" | ||||
|  | ||||
| class PotentialTargets | ||||
| { | ||||
| public: | ||||
| 	std::vector<AttackPossibility> possibleAttacks; | ||||
| 	std::vector<const CStack *> unreachableEnemies; | ||||
|  | ||||
| 	//std::function<AttackPossibility(bool,BattleHex)>  GenerateAttackInfo; //args: shooting, destHex | ||||
|  | ||||
| 	PotentialTargets(){}; | ||||
| 	PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState()); | ||||
|  | ||||
| 	AttackPossibility bestAction() const; | ||||
| 	int bestActionValue() const; | ||||
| }; | ||||
							
								
								
									
										28
									
								
								AI/BattleAI/StackWithBonuses.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								AI/BattleAI/StackWithBonuses.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| /* | ||||
|  * StackWithBonuses.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 "StackWithBonuses.h" | ||||
| #include "../../lib/BattleState.h" | ||||
|  | ||||
| const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, | ||||
| 						    const CBonusSystemNode *root /*= nullptr*/, const std::string &cachingStr /*= ""*/) const | ||||
| { | ||||
| 	TBonusListPtr ret = std::make_shared<BonusList>(); | ||||
| 	const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr); | ||||
| 	range::copy(*originalList, std::back_inserter(*ret)); | ||||
| 	for(auto &bonus : bonusesToAdd) | ||||
| 	{ | ||||
| 		auto b = std::make_shared<Bonus>(bonus); | ||||
| 		if(selector(b.get())  &&  (!limit || !limit(b.get()))) | ||||
| 			ret->push_back(b); | ||||
| 	} | ||||
| 	//TODO limiters? | ||||
| 	return ret; | ||||
| } | ||||
							
								
								
									
										23
									
								
								AI/BattleAI/StackWithBonuses.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								AI/BattleAI/StackWithBonuses.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| /* | ||||
|  * StackWithBonuses.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 "../../lib/HeroBonus.h" | ||||
|  | ||||
| class CStack; | ||||
|  | ||||
| class StackWithBonuses : public IBonusBearer | ||||
| { | ||||
| public: | ||||
| 	const CStack *stack; | ||||
| 	mutable std::vector<Bonus> bonusesToAdd; | ||||
|  | ||||
| 	virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, | ||||
| 						  const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; | ||||
| }; | ||||
| @@ -1,2 +1,11 @@ | ||||
| /* | ||||
|  * StdInc.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 | ||||
|  * | ||||
|  */ | ||||
| // Creates the precompiled header | ||||
| #include "StdInc.h" | ||||
| #include "StdInc.h" | ||||
|   | ||||
| @@ -1,7 +1,15 @@ | ||||
| /* | ||||
|  * StdInc.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 "../../Global.h" | ||||
|  | ||||
| // This header should be treated as a pre compiled header file(PCH) in the compiler building settings. | ||||
|  | ||||
| // Here you can add specific libraries and macros which are specific to this project. | ||||
| // Here you can add specific libraries and macros which are specific to this project. | ||||
|   | ||||
							
								
								
									
										72
									
								
								AI/BattleAI/ThreatMap.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								AI/BattleAI/ThreatMap.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| /* | ||||
|  * ThreatMap.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 "ThreatMap.h" | ||||
| #include "StdInc.h" | ||||
|  | ||||
| template <typename Container, typename Pred> | ||||
| auto sum(const Container & c, Pred p) -> decltype(p(*std::begin(c))) | ||||
| { | ||||
| 	double ret = 0; | ||||
| 	for(const auto &element : c) | ||||
| 	{ | ||||
| 		ret += p(element); | ||||
| 	} | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
| ThreatMap::ThreatMap(const CStack *Endangered) : endangered(Endangered) | ||||
| { | ||||
| 	sufferedDamage.fill(0); | ||||
|  | ||||
| 	for(const CStack *enemy : getCbc()->battleGetStacks()) | ||||
| 	{ | ||||
| 		//Consider only stacks of different owner | ||||
| 		if(enemy->attackerOwned == endangered->attackerOwned) | ||||
| 			continue; | ||||
|  | ||||
| 		//Look-up which tiles can be melee-attacked | ||||
| 		std::array<bool, GameConstants::BFIELD_SIZE> meleeAttackable; | ||||
| 		meleeAttackable.fill(false); | ||||
| 		auto enemyReachability = getCbc()->getReachability(enemy); | ||||
| 		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 		{ | ||||
| 			if(enemyReachability.isReachable(i)) | ||||
| 			{ | ||||
| 				meleeAttackable[i] = true; | ||||
| 				for(auto n : BattleHex(i).neighbouringTiles()) | ||||
| 					meleeAttackable[n] = true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//Gather possible assaults | ||||
| 		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 		{ | ||||
| 			if(getCbc()->battleCanShoot(enemy, i)) | ||||
| 				threatMap[i].push_back(BattleAttackInfo(enemy, endangered, true)); | ||||
| 			else if(meleeAttackable[i]) | ||||
| 			{ | ||||
| 				BattleAttackInfo bai(enemy, endangered, false); | ||||
| 				bai.chargedFields = std::max(BattleHex::getDistance(enemy->position, i) - 1, 0); //TODO check real distance (BFS), not just metric | ||||
| 				threatMap[i].push_back(BattleAttackInfo(bai)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) | ||||
| 	{ | ||||
| 		sufferedDamage[i] = sum(threatMap[i], [](const BattleAttackInfo &bai) -> int | ||||
| 		{ | ||||
| 			auto dmg = getCbc()->calculateDmgRange(bai); | ||||
| 			return (dmg.first + dmg.second)/2; | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| */ // These lines may be usefull but they are't used in the code. | ||||
							
								
								
									
										26
									
								
								AI/BattleAI/ThreatMap.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								AI/BattleAI/ThreatMap.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| /* | ||||
|  * ThreatMap.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 "common.h" | ||||
| #include "../../lib/BattleState.h" | ||||
| #include "CCallback.h" | ||||
| /* | ||||
| class ThreatMap | ||||
| { | ||||
| public: | ||||
| 	std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike | ||||
|  | ||||
| 	const CStack *endangered; | ||||
| 	std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage; | ||||
|  | ||||
| 	ThreatMap(const CStack *Endangered); | ||||
| };*/ // These lines may be usefull but they are't used in the code. | ||||
							
								
								
									
										23
									
								
								AI/BattleAI/common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								AI/BattleAI/common.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| /* | ||||
|  * common.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 "common.h" | ||||
|  | ||||
| std::shared_ptr<CBattleCallback> cbc; | ||||
|  | ||||
| void setCbc(std::shared_ptr<CBattleCallback> cb) | ||||
| { | ||||
| 	cbc = cb; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<CBattleCallback> getCbc() | ||||
| { | ||||
| 	return cbc; | ||||
| } | ||||
							
								
								
									
										26
									
								
								AI/BattleAI/common.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								AI/BattleAI/common.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| /* | ||||
|  * common.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 | ||||
|  | ||||
| class CBattleCallback; | ||||
|  | ||||
| template<typename Key, typename Val, typename Val2> | ||||
| const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 defaultValue) | ||||
| { | ||||
| 	//returning references here won't work: defaultValue must be converted into Val, creating temporary | ||||
| 	auto i = Map.find(key); | ||||
| 	if(i != Map.end()) | ||||
| 		return i->second; | ||||
| 	else | ||||
| 		return defaultValue; | ||||
| } | ||||
|  | ||||
| void setCbc(std::shared_ptr<CBattleCallback> cb); | ||||
| std::shared_ptr<CBattleCallback> getCbc(); | ||||
| @@ -1,5 +1,13 @@ | ||||
| /* | ||||
|  * main.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 "../../lib/AI_Base.h" | ||||
| #include "BattleAI.h" | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user