浏览代码

Imposed time limits on AIs.
Simple action validation.

Michał W. Urbańczyk 14 年之前
父节点
当前提交
c8115d41a8

+ 6 - 1
AI/MadAI/main.cpp

@@ -1,5 +1,6 @@
 #include <cstring>
 #include <cstring>
 #include "../../AI_Base.h"
 #include "../../AI_Base.h"
+#include "../../lib/CBattleCallback.h"
 
 
 
 
 const char *g_cszAiName = "Mad AI";
 const char *g_cszAiName = "Mad AI";
@@ -16,13 +17,17 @@ class CMadAI : public CBattleGameInterface
 
 
 	virtual BattleAction activeStack(const CStack * stack) 
 	virtual BattleAction activeStack(const CStack * stack) 
 	{
 	{
+// 		int *g = 0;
+// 		*g = 4;
+//		while(1);
+
 		srand(time(NULL));
 		srand(time(NULL));
 		BattleAction ba;
 		BattleAction ba;
 		ba.actionType = rand() % 14;
 		ba.actionType = rand() % 14;
 		ba.additionalInfo = rand() % BFIELD_SIZE + 5;
 		ba.additionalInfo = rand() % BFIELD_SIZE + 5;
 		ba.side = rand() % 7;
 		ba.side = rand() % 7;
 		ba.destinationTile = rand() % BFIELD_SIZE + 5;
 		ba.destinationTile = rand() % BFIELD_SIZE + 5;
-		ba.stackNumber = rand() % 500;
+		ba.stackNumber = rand() % cb->battleGetAllStacks().size() + 1;
 		return ba;
 		return ba;
 	}
 	}
 };
 };

+ 2 - 2
Odpalarka/main.cpp

@@ -2,7 +2,7 @@
 #include <boost/thread.hpp>
 #include <boost/thread.hpp>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
 
 
-int main(int argc, const char **)
+int main(int argc, const char **argv)
 {
 {
 	std::string runnername = 
 	std::string runnername = 
 #ifdef _WIN32
 #ifdef _WIN32
@@ -19,7 +19,7 @@ int main(int argc, const char **)
 #endif
 #endif
 	;
 	;
 
 
-	std::string serverCommand = servername + " b1.json StupidAI StupidAI";
+	std::string serverCommand = servername + " b1.json StupidAI MadAI"; // StupidAI MadAI
 	boost::thread t(boost::bind(std::system, serverCommand.c_str()));
 	boost::thread t(boost::bind(std::system, serverCommand.c_str()));
 	boost::thread tt(boost::bind(std::system, runnername.c_str()));
 	boost::thread tt(boost::bind(std::system, runnername.c_str()));
 	boost::thread ttt(boost::bind(std::system, runnername.c_str()));
 	boost::thread ttt(boost::bind(std::system, runnername.c_str()));

+ 76 - 25
VCMI_BattleAiHost/CheckTime.h

@@ -1,43 +1,94 @@
 #pragma once
 #pragma once
 
 
 #include "../global.h"
 #include "../global.h"
-#include <ctime>
-#ifndef _WIN32
-#include <sys/time.h>                // for gettimeofday()
-#endif
 
 
 
 
-struct CheckTime
-{
 #ifdef _WIN32
 #ifdef _WIN32
-	time_t t0;
+	#include <ctime>
+	typedef time_t TTime;
+	#define GET_TIME(var) (var = clock())
 #else
 #else
-	timeval t1, t2;
+	#include <sys/time.h>                // for gettimeofday()
+	typedef timeval TTime;
+	#define GET_TIME(var) (gettimeofday(&var, NULL))
 #endif
 #endif
+
+
+struct CheckTime
+{
+	TTime start;
+
 	std::string msg;
 	std::string msg;
 
 
-	CheckTime(const std::string & Msg) : msg(Msg)
-#ifdef _WIN32
-		, t0(clock())
-#endif
+	CheckTime(const std::string & Msg = "") : msg(Msg)
 	{
 	{
-
-#ifndef _WIN32
-		gettimeofday(&t1, NULL);
-#endif
+		GET_TIME(start);
 	}
 	}
-	~CheckTime()
-	{
-		float liczyloSie = 0;
 
 
+	int timeDiff(const TTime &t1, const TTime &t2)
+	{
+		int ret = 0;
 #ifdef _WIN32
 #ifdef _WIN32
-		liczyloSie = (float)(clock() - t0) / (CLOCKS_PER_SEC / 1000);
+		ret = (float)(t2 - t1) / (CLOCKS_PER_SEC / 1000);
 #else
 #else
-		// stop timer
-		gettimeofday(&t2, NULL);
-		liczyloSie = (t2.tv_sec - t1.tv_sec) * 1000.0;      // sec to ms
-		liczyloSie += (t2.tv_usec - t1.tv_usec) / 1000.0;   // us to ms
+		ret += (t2.tv_sec - t1.tv_sec) * 1000.0;   // sec to ms
+		ret += (t2.tv_usec - t1.tv_usec) / 1000.0; // us to ms
 #endif
 #endif
-		tlog0 << msg << ": " << liczyloSie << "ms" << std::endl;;
+
+		//TODO abs?
+		return ret;
+	}
+
+	int timeSinceStart()
+	{
+		TTime now;
+		GET_TIME(now);
+		return timeDiff(start, now);
+	}
+
+	~CheckTime()
+	{
+		if(msg.size())
+		{
+			float liczyloSie = timeSinceStart();
+			tlog0 << msg << ": " << liczyloSie << "ms" << std::endl;
+		}
 	}
 	}
 };
 };
+
+//all ms
+const int PROCESS_INFO_TIME = 5; 
+const int MAKE_DECIDION_TIME = 75; 
+const int MEASURE_MARGIN = 1;
+const int HANGUP_TIME = 50;
+
+void postInfoCall(int timeUsed);
+void postDecisionCall(int timeUsed);
+
+struct Bomb
+{
+	int armed;
+
+	void run(int time)
+	{
+		boost::this_thread::sleep(boost::posix_time::milliseconds(time));
+		if(armed)
+		{
+			tlog1 << "BOOOM! The bomb exploded! AI was thinking for too long!\n";
+			exit(1);
+		}
+
+		delete this;
+	}
+
+	Bomb(int timer)
+	{
+		boost::thread t(&Bomb::run, this, timer);
+		t.detach();
+	}
+
+	void disarm()
+	{
+		armed = 0;
+	}
+};

+ 35 - 3
VCMI_BattleAiHost/Client.cpp

@@ -5,8 +5,9 @@
 #include "../lib/BattleAction.h"
 #include "../lib/BattleAction.h"
 #include "../lib/CGameInterface.h"
 #include "../lib/CGameInterface.h"
 #include "CheckTime.h"
 #include "CheckTime.h"
+#include "../lib/BattleState.h"
 
 
-#define NOT_LIB
+//#define NOT_LIB
 #include "../lib/RegisterTypes.cpp"
 #include "../lib/RegisterTypes.cpp"
 
 
 template <typename T> class CApplyOnCL;
 template <typename T> class CApplyOnCL;
@@ -104,14 +105,44 @@ void CClient::requestMoveFromAIWorker(const CStack *s)
 
 
 	try
 	try
 	{
 	{
-		CheckTime timer("AI was thinking for ");
+		Bomb *b = new Bomb(MAKE_DECIDION_TIME + HANGUP_TIME);
+		CheckTime timer;
 		ba = ai->activeStack(s);
 		ba = ai->activeStack(s);
+		postDecisionCall(timer.timeSinceStart());
+		b->disarm();
+	}
+	catch(...)
+	{
+		tlog0 << "AI thrown an exception!\n";
+		//TODO: disqualify?
+		ba = BattleAction::makeDefend(s);
+	}
+
+	try
+	{
 		MakeAction temp_action(ba);
 		MakeAction temp_action(ba);
+		tlog0 << "Checking if action looks valid... ";
+		bool valid = gs->isValidAction(temp_action, true);
+
+		if(gs->curB->sides[temp_action.ba.side] != color)
+		{
+			tlog1 << "Wrong side set!\n";
+			valid = false;
+		}
+
+		if(!valid)
+		{
+			tlog1 << "Warning: action seems to be invalid! Stack will defend\n";
+			temp_action.ba = BattleAction::makeDefend(s);
+		}
+		else
+			tlog1 << "Doesn't look suspicious.\n";
+
 		*serv << &temp_action;
 		*serv << &temp_action;
 	}
 	}
 	catch(...)
 	catch(...)
 	{
 	{
-		tlog0 << "AI thrown an exception!\n";
+		tlog1 << "Failed sending action!\n";
 	}
 	}
 }
 }
 
 
@@ -122,6 +153,7 @@ CClient::CClient()
 	ai = NULL;
 	ai = NULL;
 	curbaction = NULL;
 	curbaction = NULL;
 	terminate = false;
 	terminate = false;
+	color = 250;
 
 
 	applier = new CApplier<CBaseForCLApply>;
 	applier = new CApplier<CBaseForCLApply>;
 	registerTypes2(*applier);
 	registerTypes2(*applier);

+ 1 - 0
VCMI_BattleAiHost/Client.h

@@ -15,6 +15,7 @@ public:
 	BattleAction *curbaction;
 	BattleAction *curbaction;
 	CGameState *gs;
 	CGameState *gs;
 	CBattleGameInterface *ai;
 	CBattleGameInterface *ai;
+	ui8 color;
 
 
 	CClient();
 	CClient();
 
 

+ 33 - 12
VCMI_BattleAiHost/NetPacksRunner.cpp

@@ -12,19 +12,40 @@
 #include "CheckTime.h"
 #include "CheckTime.h"
 
 
 
 
+void postInfoCall(int timeUsed)
+{
+	tlog0 << "AI was processing info for " << timeUsed << " ms.\n";
+	if(timeUsed > PROCESS_INFO_TIME + MEASURE_MARGIN)
+	{
+		tlog1 << "That's too long! AI is disqualified!\n";
+		exit(1);
+	}
+}
+
+void postDecisionCall(int timeUsed)
+{
+	tlog0 << "AI was thinking over an action for " << timeUsed << " ms.\n";
+	if(timeUsed > MAKE_DECIDION_TIME + MEASURE_MARGIN)
+	{
+		tlog1 << "That's too long! AI is disqualified!\n";
+		exit(1);
+	}
+}
 
 
 //macros to avoid code duplication - calls given method with given arguments if interface for specific player is present
 //macros to avoid code duplication - calls given method with given arguments if interface for specific player is present
 //awaiting variadic templates...
 //awaiting variadic templates...
-
-
-#define BATTLE_INTERFACE_CALL_IF_PRESENT(function,...) 	\
-	do													\
-	{													\
-		if(cl->ai)										\
-		{												\
-			CheckTime("AI was processing info for ");	\
-			cl->ai->function(__VA_ARGS__);				\
-		}												\
+#define BATTLE_INTERFACE_CALL_IF_PRESENT(function,...) 		\
+	do														\
+	{														\
+		int timeUsed = 0;									\
+		if(cl->ai)											\
+		{													\
+			Bomb *b = new Bomb(PROCESS_INFO_TIME + HANGUP_TIME);\
+			CheckTime pr;									\
+			cl->ai->function(__VA_ARGS__);					\
+			postInfoCall(pr.timeSinceStart());				\
+			b->disarm();									\
+		}													\
 	} while(0)
 	} while(0)
 
 
 #define UNEXPECTED_PACK assert(0)
 #define UNEXPECTED_PACK assert(0)
@@ -257,7 +278,7 @@ void GarrisonDialog::applyCl(CClient *cl)
 void BattleStart::applyCl( CClient *cl )
 void BattleStart::applyCl( CClient *cl )
 {
 {
 	//TODO!!!!
 	//TODO!!!!
-	BATTLE_INTERFACE_CALL_IF_PRESENT(battleStart, info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], cl->ai->playerID);
+	BATTLE_INTERFACE_CALL_IF_PRESENT(battleStart, info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], cl->color);
 }
 }
 
 
 void BattleNextRound::applyFirstCl(CClient *cl)
 void BattleNextRound::applyFirstCl(CClient *cl)
@@ -279,7 +300,7 @@ void BattleSetActiveStack::applyCl( CClient *cl )
 	else
 	else
 		playerToCall = activated->owner;
 		playerToCall = activated->owner;
 
 
-	if(cl->ai && cl->ai->playerID == playerToCall)
+	if(cl->ai && cl->color == playerToCall)
 		cl->requestMoveFromAI(activated);
 		cl->requestMoveFromAI(activated);
 }
 }
 
 

+ 1 - 1
VCMI_BattleAiHost/main.cpp

@@ -84,7 +84,7 @@ int main(int argc, char** argv)
 		if(battleAIName.size())
 		if(battleAIName.size())
 		{
 		{
 			cl.ai = CDynLibHandler::getNewBattleAI(battleAIName);
 			cl.ai = CDynLibHandler::getNewBattleAI(battleAIName);
-			cl.ai->playerID = color;
+			cl.color = color;
 			tlog0 << "AI created\n";
 			tlog0 << "AI created\n";
 			cl.ai->init(cbc);
 			cl.ai->init(cbc);
 		}
 		}

+ 70 - 0
lib/CGameState.cpp

@@ -32,6 +32,7 @@
 #include "BattleState.h"
 #include "BattleState.h"
 #include "../lib/JsonNode.h"
 #include "../lib/JsonNode.h"
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/predicate.hpp>
+#include "BattleAction.h"
 
 
 boost::rand48 ran;
 boost::rand48 ran;
 class CGObjectInstance;
 class CGObjectInstance;
@@ -2465,6 +2466,75 @@ void CGameState::attachArmedObjects()
 	}
 	}
 }
 }
 
 
+bool CGameState::isValidAction(const MakeAction &ma, bool verbose) const
+{
+#define PROBLEM(txt) do{if(verbose) tlog1 << "Action invalid: " << txt << std::endl; return false;} while(0);
+
+	const CStack *stack = curB->getStack(ma.ba.stackNumber);
+	if(ma.ba.actionType != BattleAction::RETREAT && ma.ba.actionType != BattleAction::SURRENDER)
+	{
+		if(!stack)
+			PROBLEM("There is no such stack!");
+		if(stack->ID != curB->activeStack)
+			PROBLEM("Action has to be about the active stack!");
+	}
+
+	switch(ma.ba.actionType)
+	{
+		
+	case BattleAction::NO_ACTION:
+		PROBLEM("No action is not a valid action. Use DEFEND to do nothing!");
+	case BattleAction::HERO_SPELL:
+		PROBLEM("Casting spells by hero must be done as a custom action!");
+	case BattleAction::WALK:
+		if(!vstd::contains(curB->getAccessibility(stack, true), ma.ba.destinationTile))
+			PROBLEM("Destination tile is not accessible!");
+
+		return true;
+	case BattleAction::DEFEND:
+		return true;
+	case BattleAction::RETREAT:
+		return true;
+	case BattleAction::SURRENDER:
+		PROBLEM("SURRENDER is not considered to be a valid action. Use RETREAT instead!");
+	case BattleAction::WALK_AND_ATTACK:
+		{
+			std::vector<THex> attackable;
+			if(!vstd::contains(curB->getAccessibility(stack, true, &attackable), ma.ba.destinationTile))
+				PROBLEM("Destination tile is not accessible!");
+			if(!vstd::contains(attackable, ma.ba.additionalInfo))
+				PROBLEM("Target tile is not attackable!");
+		}
+
+		return true;
+	case BattleAction::SHOOT:
+		if(!curB->battleCanShoot(stack, ma.ba.destinationTile))
+			PROBLEM("Stack cannot make shot!");
+
+		return true;
+	case BattleAction::WAIT:
+		if(vstd::contains(stack->state, WAITING))
+			PROBLEM("Stack can be ordered to wait only once in a turn!");
+
+		return true;
+	case BattleAction::CATAPULT:
+		//TODO czy aktywna jest katapulta
+		//czy bohater posiada balistyke
+		// czy celuje w mur
+		// czy segment w ktory celuje nie jest juz zniszczony
+		return true;
+	case BattleAction::MONSTER_SPELL:
+		PROBLEM("Monster spells are not supported!");
+	case BattleAction::BAD_MORALE:
+		PROBLEM("Player can't decide when stack has a bad morale!");
+	case BattleAction::STACK_HEAL:
+		//TODO namiot
+		return true;
+	default:
+		PROBLEM("Action of invalid type!");
+	}
+}
+
 int3 CPath::startPos() const
 int3 CPath::startPos() const
 {
 {
 	return nodes[nodes.size()-1].coord;
 	return nodes[nodes.size()-1].coord;

+ 3 - 0
lib/CGameState.h

@@ -67,6 +67,7 @@ class CCampaign;
 class CCampaignState;
 class CCampaignState;
 class IModableArt;
 class IModableArt;
 class CGGarrison;
 class CGGarrison;
+struct MakeAction;
 
 
 namespace boost
 namespace boost
 {
 {
@@ -405,6 +406,8 @@ public:
 	bmap<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
 	bmap<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
 	BattleInfo * setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town);
 	BattleInfo * setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town);
 
 
+	bool isValidAction(const MakeAction &ma, bool verbose) const;
+
 	void buildBonusSystemTree();
 	void buildBonusSystemTree();
 	void attachArmedObjects();
 	void attachArmedObjects();
 	void buildGlobalTeamPlayerTree();
 	void buildGlobalTeamPlayerTree();

+ 35 - 6
server/CGameHandler.cpp

@@ -473,8 +473,11 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 		tlog0 << boost::format("Total casualties points: %d\n") % casualtiesPoints;
 		tlog0 << boost::format("Total casualties points: %d\n") % casualtiesPoints;
 
 
 		//battle ai1 ai2 winner_side winner_casualties
 		//battle ai1 ai2 winner_side winner_casualties
+		time_t czas;
+		time(&czas);
+		std::string resultTypes[] = {"SIDE_DEFEATED", "SIDE_RETREATED", "SIDE_SURRENDERED", "SIDE_DISQUALIFIED"};
 		std::ofstream resultsList("results.txt", std::fstream::out | std::fstream::app);
 		std::ofstream resultsList("results.txt", std::fstream::out | std::fstream::app);
-		resultsList << boost::format("\n%s\t%s\t%s\t%d\t%d") % gs->scenarioOps->mapname % ais[0] % ais[1] % (int)battleResult.data->winner % casualtiesPoints;
+		resultsList << boost::format("%s\t%s\t%s\t%d\t%d\t%s\t%s") % gs->scenarioOps->mapname % ais[0] % ais[1] % (int)battleResult.data->winner % casualtiesPoints % resultTypes[battleResult.data->result] % asctime(localtime(&czas));
 	}
 	}
 
 
 	sendAndApply(&resultsApplied);
 	sendAndApply(&resultsApplied);
@@ -634,13 +637,30 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
 		bat.bsa.push_back(bsa);
 		bat.bsa.push_back(bsa);
 	}
 	}
 }
 }
+
+void CGameHandler::disqualifyPlayer(int side)
+{
+	tlog0 << "The side " << (int)side << " will be disqualified!\n";
+	boost::unique_lock<boost::shared_mutex> lock(*gs->mx);
+	setBattleResult(3, !side);
+	battleMadeAction.setn(true);
+}
+
 void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 {
 {
 	setThreadName(-1, "CGameHandler::handleConnection");
 	setThreadName(-1, "CGameHandler::handleConnection");
 	srand(time(NULL));
 	srand(time(NULL));
 	CPack *pack = NULL;
 	CPack *pack = NULL;
+
+	boost::function<void()> onException;
+
 	try
 	try
 	{
 	{
+		if(gs->curB && gs->initialOpts->mode == StartInfo::DUEL)
+		{
+			onException = boost::bind(&CGameHandler::disqualifyPlayer, this, *players.begin());
+		}
+
 		while(1)//server should never shut connection first //was: while(!end2)
 		while(1)//server should never shut connection first //was: while(!end2)
 		{
 		{
 			pack = c.retreivePack();
 			pack = c.retreivePack();
@@ -668,6 +688,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 			}
 			}
 			else if(apply)
 			else if(apply)
 			{
 			{
+				boost::unique_lock<boost::recursive_mutex> lock(gsm);
 				bool result = apply->applyOnGH(this,&c,pack);
 				bool result = apply->applyOnGH(this,&c,pack);
 				tlog5 << "Message successfully applied (result=" << result << ")!\n";
 				tlog5 << "Message successfully applied (result=" << result << ")!\n";
 
 
@@ -690,11 +711,16 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 	}
 	}
 	catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
 	catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
 	{
 	{
-		assert(!c.connected); //make sure that connection has been marked as broken
+		boost::unique_lock<boost::recursive_mutex> lock(gsm);
+		if(gs->scenarioOps->mode != StartInfo::DUEL)
+		{
+			assert(!c.connected); //make sure that connection has been marked as broken
+		}
 		tlog1 << e.what() << std::endl;
 		tlog1 << e.what() << std::endl;
 		end2 = true;
 		end2 = true;
+		if(onException) onException();
 	}
 	}
-	HANDLE_EXCEPTION(end2 = true);
+	HANDLE_EXCEPTIONC(boost::unique_lock<boost::recursive_mutex> lock(gsm);end2 = true; if(onException) {onException();return;});
 
 
 	tlog1 << "Ended handling connection\n";
 	tlog1 << "Ended handling connection\n";
 }
 }
@@ -1938,8 +1964,11 @@ void CGameHandler::sendToAllClients( CPackForClient * info )
 	tlog5 << "Sending to all clients a package of type " << typeid(*info).name() << std::endl;
 	tlog5 << "Sending to all clients a package of type " << typeid(*info).name() << std::endl;
 	for(std::set<CConnection*>::iterator i=conns.begin(); i!=conns.end();i++)
 	for(std::set<CConnection*>::iterator i=conns.begin(); i!=conns.end();i++)
 	{
 	{
-		boost::unique_lock<boost::mutex> lock(*(*i)->wmx);
-		**i << info;
+		if((**i).connected)
+		{
+			boost::unique_lock<boost::mutex> lock(*(*i)->wmx);
+			**i << info;
+		}
 	}
 	}
 }
 }
 
 
@@ -2955,7 +2984,7 @@ static EndAction end_action;
 
 
 bool CGameHandler::makeBattleAction( BattleAction &ba )
 bool CGameHandler::makeBattleAction( BattleAction &ba )
 {
 {
-	tlog1 << "\tMaking action of type " << ba.actionType << std::endl;
+	tlog1 << "\tMaking action of type " << (int)ba.actionType << std::endl;
 	bool ok = true;
 	bool ok = true;
 
 
 	switch(ba.actionType)
 	switch(ba.actionType)

+ 2 - 0
server/CGameHandler.h

@@ -123,6 +123,8 @@ public:
 	//const CArmedInstance * bEndArmy1, * bEndArmy2;
 	//const CArmedInstance * bEndArmy1, * bEndArmy2;
 	bool visitObjectAfterVictory;
 	bool visitObjectAfterVictory;
 	//
 	//
+
+	void disqualifyPlayer(int side);
 	void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle
 	void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle
 	void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex); //distance - number of hexes travelled before attacking
 	void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex); //distance - number of hexes travelled before attacking
 	void applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary); //damage, drain life & fire shield
 	void applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary); //damage, drain life & fire shield