Przeglądaj źródła

* fixed crash related to cammander's SPELL_AFTER_ATTACK spell id not initialized properly (text id was resolved on copy of bonus)
* fixed duels, added no-GUI mode for automatic AI testing
* minor things

Michał W. Urbańczyk 12 lat temu
rodzic
commit
6a88604937

+ 17 - 2
AI/BattleAI/BattleAI.cpp

@@ -354,15 +354,16 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		if(cb->battleCanCastSpell())
 			attemptCastingSpell();
 
+		if(auto action = considerFleeingOrSurrendering())
+			return *action;
+
 		if(cb->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY).empty())
 		{
 			//We apparently won battle by casting spell, return defend... (accessing cb may cause trouble)
 			return BattleAction::makeDefend(stack);
 		}
 
-		ThreatMap threatsToUs(stack);
 		PotentialTargets targets(stack);
-
 		if(targets.possibleAttacks.size())
 		{
 			auto hlp = targets.bestAction();
@@ -375,6 +376,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		{
 			if(stack->waited())
 			{
+				ThreatMap threatsToUs(stack);
 				auto dists = cbc->battleGetDistances(stack);
 				const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, boost::bind(isCloser, _1, _2, boost::ref(dists)));
 				if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
@@ -749,3 +751,16 @@ std::vector<BattleHex> CBattleAI::getTargetsToConsider( const CSpell *spell ) co
 		return cbc->battleGetPossibleTargets(playerID, spell);
 	}
 }
+
+boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
+{
+	if(cb->battleCanSurrender(playerID))
+	{
+
+	}
+	if(cb->battleCanFlee())
+	{
+
+	}
+	return boost::none;
+}

+ 2 - 0
AI/BattleAI/BattleAI.h

@@ -42,6 +42,8 @@ public:
 	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;
 };

+ 2 - 2
client/CCreatureWindow.cpp

@@ -239,8 +239,8 @@ void CCreatureWindow::init(const CStackInstance *Stack, const CBonusSystemNode *
 				else
 				{
 					selectableSkill->pos = Rect (95, 256, 55, 55); //TODO: scroll
-					Bonus b = CGI->creh->skillRequirements[option-100].first; 
-					bonusItems.push_back (new CBonusItem (genRect(0, 0, 251, 57), stack->bonusToString(&b, false), stack->bonusToString(&b, true), stack->bonusToGraphics(&b)));
+					const Bonus *b = CGI->creh->skillRequirements[option-100].first; 
+					bonusItems.push_back (new CBonusItem (genRect(0, 0, 251, 57), stack->bonusToString(b, false), stack->bonusToString(b, true), stack->bonusToGraphics(b)));
 					selectableBonuses.push_back (selectableSkill); //insert these before other bonuses
 				}
 			}

+ 79 - 44
client/CMT.cpp

@@ -47,6 +47,7 @@
 #endif
 #include "../lib/CDefObjInfoHandler.h"
 #include "../lib/UnlockGuard.h"
+#include "CMT.h"
 
 #if __MINGW32__
 #undef main
@@ -76,6 +77,7 @@ static boost::thread *mainGUIThread;
 std::queue<SDL_Event> events;
 boost::mutex eventsM;
 
+bool gNoGUI = false;
 static bool gOnlyAI = false;
 //static bool setResolution = false; //set by event handling thread after resolution is adjusted
 
@@ -146,20 +148,23 @@ void init()
     logGlobal->infoStream()<<"Initializing VCMI_Lib: "<<tmh.getDiff();
 
 	pomtime.getDiff();
-	CCS->curh = new CCursorHandler;
-	graphics = new Graphics(); // should be before curh->init()
+	if(!gNoGUI)
+	{
+		CCS->curh = new CCursorHandler;
+		graphics = new Graphics(); // should be before curh->init()
 
-	CCS->curh->initCursor();
-	CCS->curh->show();
-    logGlobal->infoStream()<<"Screen handler: "<<pomtime.getDiff();
-	pomtime.getDiff();
+		CCS->curh->initCursor();
+		CCS->curh->show();
+		logGlobal->infoStream()<<"Screen handler: "<<pomtime.getDiff();
+		pomtime.getDiff();
 
-	graphics->loadHeroAnims();
-    logGlobal->infoStream()<<"\tMain graphics: "<<tmh.getDiff();
-    logGlobal->infoStream()<<"Initializing game graphics: "<<tmh.getDiff();
+		graphics->loadHeroAnims();
+		logGlobal->infoStream()<<"\tMain graphics: "<<tmh.getDiff();
+		logGlobal->infoStream()<<"Initializing game graphics: "<<tmh.getDiff();
 
-	CMessage::init();
-    logGlobal->infoStream()<<"Message handler: "<<tmh.getDiff();
+		CMessage::init();
+		logGlobal->infoStream()<<"Message handler: "<<tmh.getDiff();
+	}
 }
 
 static void prog_version(void)
@@ -219,7 +224,8 @@ int main(int argc, char** argv)
 		("version,v", "display version information and exit")
 		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only")
 		("start", po::value<std::string>(), "starts game from saved StartInfo file")
-		("onlyAI", "runs without GUI, all players will be default AI")
+		("onlyAI", "runs without human player, all players will be default AI")
+		("noGUI", "runs without GUI, implies --onlyAI")
 		("oneGoodAI", "puts one default AI and the rest will be EmptyAI")
 		("autoSkip", "automatically skip turns in GUI")
 		("disable-video", "disable video player")
@@ -249,6 +255,11 @@ int main(int argc, char** argv)
 		prog_version();
 		return 0;
 	}
+	if(vm.count("noGUI"))
+	{
+		gNoGUI = true;
+		vm["onlyAI"];
+	}
 
 	//Set environment vars to make window centered. Sometimes work, sometimes not. :/
 	putenv((char*)"SDL_VIDEO_WINDOW_POS");
@@ -303,16 +314,7 @@ int main(int argc, char** argv)
     logGlobal->infoStream() << NAME;
 
 	srand ( time(NULL) );
-
-	CCS = new CClientState;
-	CGI = new CGameInfo; //contains all global informations about game (texts, lodHandlers, map handler etc.)
-
-	if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO))
-	{
-        logGlobal->errorStream()<<"Something was wrong: "<< SDL_GetError();
-		exit(-1);
-	}
-	atexit(SDL_Quit);
+	
 
 	const JsonNode& video = settings["video"];
 	const JsonNode& res = video["screenRes"];
@@ -328,15 +330,26 @@ int main(int argc, char** argv)
 		exit(EXIT_FAILURE);
 	}
 
-	setScreenRes(res["width"].Float(), res["height"].Float(), video["bitsPerPixel"].Float(), video["fullscreen"].Bool());
+	if(!gNoGUI)
+	{
+		if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO))
+		{
+			logGlobal->errorStream()<<"Something was wrong: "<< SDL_GetError();
+			exit(-1);
+		}
+		atexit(SDL_Quit);
+		setScreenRes(res["width"].Float(), res["height"].Float(), video["bitsPerPixel"].Float(), video["fullscreen"].Bool());
+		logGlobal->infoStream() <<"\tInitializing screen: "<<pomtime.getDiff();
+	}
 
-    logGlobal->infoStream() <<"\tInitializing screen: "<<pomtime.getDiff();
 
+	CCS = new CClientState;
+	CGI = new CGameInfo; //contains all global informations about game (texts, lodHandlers, map handler etc.)
 	// Initialize video
 #if DISABLE_VIDEO
 	CCS->videoh = new CEmptyVideoPlayer;
 #else
-	if (!vm.count("disable-video"))
+	if (!gNoGUI && !vm.count("disable-video"))
 		CCS->videoh = new CVideoPlayer;
 	else
 		CCS->videoh = new CEmptyVideoPlayer;
@@ -344,13 +357,18 @@ int main(int argc, char** argv)
 
     logGlobal->infoStream()<<"\tInitializing video: "<<pomtime.getDiff();
 
+
+
 	//we can properly play intro only in the main thread, so we have to move loading to the separate thread
 	boost::thread loading(init);
 
-	if(!vm.count("battle") && !vm.count("nointro"))
-		playIntro();
+	if(!gNoGUI )
+	{
+		if(!vm.count("battle") && !vm.count("nointro"))
+			playIntro();
+		SDL_FillRect(screen,NULL,0);
+	}
 
-	SDL_FillRect(screen,NULL,0);
 	CSDL_Ext::update(screen);
 	loading.join();
     logGlobal->infoStream()<<"Initialization of VCMI (together): "<<total.getDiff();
@@ -387,8 +405,17 @@ int main(int argc, char** argv)
 		si->playerInfos[PlayerColor(1)].color = PlayerColor(1);
 		startGame(si);
 	}
-	mainGUIThread = new boost::thread(&CGuiHandler::run, boost::ref(GH));
-	listenForEvents();
+
+	if(!gNoGUI)
+	{
+		mainGUIThread = new boost::thread(&CGuiHandler::run, boost::ref(GH));
+		listenForEvents();
+	}
+	else
+	{
+		while(true)
+			boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
+	}
 
 	return 0;
 }
@@ -822,20 +849,7 @@ static void listenForEvents()
 		if (ret == 0 || (ev.type==SDL_QUIT) ||
 			(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
 		{
-			if (client)
-				client->endGame();
-			if (mainGUIThread) 
-			{
-				GH.terminate = true;
-				mainGUIThread->join();
-				delete mainGUIThread;
-				mainGUIThread = NULL;
-			}
-			delete console;
-			console = NULL;
-			SDL_Delay(750);
-			SDL_Quit();
-            std::cout << "Ending...";
+			handleQuit();
 			break;
 		}
 		else if(LOCPLINT && ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4)
@@ -929,3 +943,24 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
 
 		client->connectionHandler = new boost::thread(&CClient::run, client);
 }
+
+void handleQuit()
+{
+	if (client)
+		client->endGame();
+	if (mainGUIThread) 
+	{
+		GH.terminate = true;
+		mainGUIThread->join();
+		delete mainGUIThread;
+		mainGUIThread = NULL;
+	}
+	delete console;
+	console = NULL;
+	boost::this_thread::sleep(boost::posix_time::milliseconds(750));
+	if(!gNoGUI)
+		SDL_Quit();
+
+	std::cout << "Ending...";
+	exit(0);
+}

+ 5 - 1
client/CMT.h

@@ -2,4 +2,8 @@
 
 extern SDL_Surface *screen;      // main screen surface
 extern SDL_Surface *screen2;     // and hlp surface (used to store not-active interfaces layer)
-extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
+extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
+
+extern bool gNoGUI; //if true there is no client window and game is silently played between AIs
+
+void handleQuit();

+ 14 - 10
client/Client.cpp

@@ -34,6 +34,7 @@
 #include "../lib/CScriptingModule.h"
 #include "../lib/RegisterTypes.h"
 #include "gui/CGuiHandler.h"
+#include "CMT.h"
 
 extern std::string NAME;
 namespace intpr = boost::interprocess;
@@ -419,15 +420,18 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 
 	if(si->mode == StartInfo::DUEL)
 	{
-		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
-		CPlayerInterface *p = new CPlayerInterface(PlayerColor::NEUTRAL); //TODO: check if neutral really works -- was -1, but CPlayerInterface seems to cooperate with this value not too well
-		p->observerInDuelMode = true;
-		battleints[PlayerColor::UNFLAGGABLE] = playerint[PlayerColor::UNFLAGGABLE] = p;
-		privilagedBattleEventReceivers.push_back(p);
-		GH.curInt = p;
-		auto cb = make_shared<CCallback>(gs, boost::optional<PlayerColor>(), this);
-		battleCallbacks[PlayerColor::NEUTRAL] = callbacks[PlayerColor::NEUTRAL] = cb;
-		p->init(cb.get());
+		if(!gNoGUI)
+		{
+			boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
+			CPlayerInterface *p = new CPlayerInterface(PlayerColor::NEUTRAL);
+			p->observerInDuelMode = true;
+			battleints[PlayerColor::UNFLAGGABLE] = playerint[PlayerColor::UNFLAGGABLE] = p;
+			privilagedBattleEventReceivers.push_back(p);
+			GH.curInt = p;
+			auto cb = make_shared<CCallback>(gs, boost::optional<PlayerColor>(), this);
+			battleCallbacks[PlayerColor::NEUTRAL] = callbacks[PlayerColor::NEUTRAL] = cb;
+			p->init(cb.get());
+		}
 		battleStarted(gs->curB);
 	}
 	else
@@ -616,7 +620,7 @@ void CClient::battleStarted(const BattleInfo * info)
 	else
 		def = NULL;
 
-	if(att || def || gs->scenarioOps->mode == StartInfo::DUEL)
+	if(!gNoGUI && (att || def || gs->scenarioOps->mode == StartInfo::DUEL))
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
 		new CBattleInterface(info->belligerents[0], info->belligerents[1], info->heroes[0], info->heroes[1],

+ 2 - 3
client/NetPacksClient.cpp

@@ -27,6 +27,7 @@
 #include "../lib/BattleState.h"
 #include "../lib/GameConstants.h"
 #include "gui/CGuiHandler.h"
+#include "CMT.h"
 
 //macros to avoid code duplication - calls given method with given arguments if interface for specific player is present
 //awaiting variadic templates...
@@ -705,9 +706,7 @@ void BattleResultsApplied::applyCl( CClient *cl )
 	INTERFACE_CALL_IF_PRESENT(PlayerColor::UNFLAGGABLE, battleResultsApplied);
 	if(GS(cl)->initialOpts->mode == StartInfo::DUEL)
 	{
-		cl->terminate = true;
-		CloseServer cs;
-		*cl->serv << &cs;
+		handleQuit();
 	}
 }
 

+ 2 - 2
client/battle/CBattleInterface.cpp

@@ -204,8 +204,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	bOptions = new CAdventureMapButton (CGI->generaltexth->zelp[381].first, CGI->generaltexth->zelp[381].second, boost::bind(&CBattleInterface::bOptionsf,this), 3, 561, "icm003.def", SDLK_o);
 	bSurrender = new CAdventureMapButton (CGI->generaltexth->zelp[379].first, CGI->generaltexth->zelp[379].second, boost::bind(&CBattleInterface::bSurrenderf,this), 54, 561, "icm001.def", SDLK_s);
 	bFlee = new CAdventureMapButton (CGI->generaltexth->zelp[380].first, CGI->generaltexth->zelp[380].second, boost::bind(&CBattleInterface::bFleef,this), 105, 561, "icm002.def", SDLK_r);
-	bFlee->block(!curInt->cb->battleCanFlee());
-	bSurrender->block(curInt->cb->battleGetSurrenderCost() < 0);
+	bFlee->block(!curInt->cb->getMyColor() || !curInt->cb->battleCanFlee());
+	bSurrender->block(!curInt->cb->getMyColor() || curInt->cb->battleGetSurrenderCost() < 0);
 	bAutofight  = new CAdventureMapButton (CGI->generaltexth->zelp[382].first, CGI->generaltexth->zelp[382].second, boost::bind(&CBattleInterface::bAutofightf,this), 157, 561, "icm004.def", SDLK_a);
 	bSpell = new CAdventureMapButton (CGI->generaltexth->zelp[385].first, CGI->generaltexth->zelp[385].second, boost::bind(&CBattleInterface::bSpellf,this), 645, 561, "icm005.def", SDLK_c);
 	bSpell->block(true);

+ 9 - 1
lib/CBattleCallback.cpp

@@ -942,6 +942,11 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 		multBonus *= 0.5;
 	}
 
+
+	// TODO attack on petrified unit 50%
+	// psychic elementals versus mind immune units 50%
+	// blinded unit retaliates
+
 	minDmg *= additiveBonus * multBonus;
 	maxDmg *= additiveBonus * multBonus;
 
@@ -2268,7 +2273,10 @@ TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AN
 {
 	TStacks ret;
 	RETURN_IF_NOT_BATTLE(ret);
-	ASSERT_IF_CALLED_WITH_PLAYER
+	if(whose != MINE_AND_ENEMY)
+	{
+		ASSERT_IF_CALLED_WITH_PLAYER
+	}
 	vstd::copy_if(battleGetAllStacks(), std::back_inserter(ret), [=](const CStack *s) -> bool
 	{
 		const bool ownerMatches = (whose == MINE_AND_ENEMY)

+ 2 - 2
lib/CBonusTypeHandler.cpp

@@ -128,7 +128,7 @@ CBonusTypeHandler::~CBonusTypeHandler()
 	//dtor
 }
 
-std::string CBonusTypeHandler::bonusToString(Bonus *bonus, const IBonusBearer *bearer, bool description) const
+std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBearer *bearer, bool description) const
 {
 	auto getValue = [=](const std::string &name) -> std::string
 	{
@@ -171,7 +171,7 @@ std::string CBonusTypeHandler::bonusToString(Bonus *bonus, const IBonusBearer *b
 	return text;	
 }
 
-std::string CBonusTypeHandler::bonusToGraphics(Bonus* bonus) const
+std::string CBonusTypeHandler::bonusToGraphics(const Bonus* bonus) const
 {
 	std::string fileName;
 	bool fullPath = false;

+ 2 - 2
lib/CBonusTypeHandler.h

@@ -75,8 +75,8 @@ public:
 	CBonusTypeHandler();
 	virtual ~CBonusTypeHandler();
 	
-	std::string bonusToString(Bonus *bonus, const IBonusBearer *bearer, bool description) const override;
-	std::string bonusToGraphics(Bonus *bonus) const override;
+	std::string bonusToString(const Bonus *bonus, const IBonusBearer *bearer, bool description) const override;
+	std::string bonusToGraphics(const Bonus *bonus) const override;
 	
 	void load();
 	void load(const JsonNode& config);

+ 2 - 2
lib/CCreatureHandler.cpp

@@ -212,8 +212,8 @@ void CCreatureHandler::loadCommanders()
 
 	BOOST_FOREACH (auto ability, config["abilityRequirements"].Vector())
 	{
-		std::pair <Bonus, std::pair <ui8, ui8> > a;
-		a.first = *JsonUtils::parseBonus (ability["ability"].Vector());
+		std::pair <Bonus*, std::pair <ui8, ui8> > a;
+		a.first = JsonUtils::parseBonus (ability["ability"].Vector());
 		a.second.first = ability["skills"].Vector()[0].Float();
 		a.second.second = ability["skills"].Vector()[1].Float();
 		skillRequirements.push_back (a);

+ 1 - 1
lib/CCreatureHandler.h

@@ -181,7 +181,7 @@ public:
 	//Commanders
 	BonusList commanderLevelPremy; //bonus values added with each level-up
 	std::vector< std::vector <ui8> > skillLevels; //how much of a bonus will be given to commander with every level. SPELL_POWER also gives CASTS and RESISTANCE
-	std::vector <std::pair <Bonus, std::pair <ui8, ui8> > > skillRequirements; // first - Bonus, second - which two skills are needed to use it
+	std::vector <std::pair <Bonus*, std::pair <ui8, ui8> > > skillRequirements; // first - Bonus, second - which two skills are needed to use it
 
 	void deserializationFix();
 	CreatureID pickRandomMonster(const boost::function<int()> &randGen = 0, int tier = -1) const; //tier <1 - CREATURES_PER_TOWN> or -1 for any

+ 2 - 2
lib/CCreatureSet.cpp

@@ -565,7 +565,7 @@ void CStackInstance::setType(const CCreature *c)
 	if(type)
 		attachTo(const_cast<CCreature*>(type));
 }
-std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const
+std::string CStackInstance::bonusToString(const Bonus *bonus, bool description) const
 {
 	if(Bonus::MAGIC_RESISTANCE == bonus->type)
 	{
@@ -578,7 +578,7 @@ std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const
 	
 }
 
-std::string CStackInstance::bonusToGraphics(Bonus *bonus) const
+std::string CStackInstance::bonusToGraphics(const Bonus *bonus) const
 {
 	return VLC->getBth()->bonusToGraphics(bonus);
 }

+ 2 - 2
lib/CCreatureSet.h

@@ -56,8 +56,8 @@ public:
 	}
 
 	//overrides CBonusSystemNode
-	std::string bonusToString(Bonus *bonus, bool description) const override; // how would bonus description look for this particular type of node
-	std::string bonusToGraphics(Bonus *bonus) const; //file name of graphics from StackSkills , in future possibly others
+	std::string bonusToString(const Bonus *bonus, bool description) const override; // how would bonus description look for this particular type of node
+	std::string bonusToGraphics(const Bonus *bonus) const; //file name of graphics from StackSkills , in future possibly others
 
 	virtual ui64 getPower() const;
 	int getQuantityID() const;

+ 1 - 1
lib/HeroBonus.h

@@ -619,7 +619,7 @@ public:
 	//bool isLimitedOnUs(Bonus *b) const; //if bonus should be removed from list acquired from this node
 
 	void popBonuses(const CSelector &s);
-	virtual std::string bonusToString(Bonus *bonus, bool description) const {return "";}; //description or bonus name
+	virtual std::string bonusToString(const Bonus *bonus, bool description) const {return "";}; //description or bonus name
 	virtual std::string nodeName() const;
 
 	void deserializationFix();

+ 2 - 2
lib/IBonusTypeHandler.h

@@ -20,6 +20,6 @@ class IBonusTypeHandler
 public:
 	virtual ~IBonusTypeHandler(){};
 
-	virtual std::string bonusToString(Bonus *bonus, const IBonusBearer *bearer, bool description) const = 0;
-	virtual std::string bonusToGraphics(Bonus *bonus) const = 0;
+	virtual std::string bonusToString(const Bonus *bonus, const IBonusBearer *bearer, bool description) const = 0;
+	virtual std::string bonusToGraphics(const Bonus *bonus) const = 0;
 };

+ 30 - 5
server/CGameHandler.cpp

@@ -293,7 +293,7 @@ void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill)
 	else if (skill >= 100)
 	{
 		scp.which = SetCommanderProperty::SPECIAL_SKILL;
-		scp.accumulatedBonus = VLC->creh->skillRequirements[skill-100].first;
+		scp.accumulatedBonus = *VLC->creh->skillRequirements[skill-100].first;
 		scp.additionalInfo = skill; //unnormalized
 		sendAndApply (&scp);
 	}
@@ -435,17 +435,22 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 		return nullptr;
 	};
 
-	const auto battleQuery = findBattleQuery();
+	auto battleQuery = findBattleQuery();
 	if(!battleQuery)
+	{
 		logGlobal->errorStream() << "Cannot find battle query!";
+		if(gs->initialOpts->mode == StartInfo::DUEL)
+		{
+			battleQuery = make_shared<CBattleQuery>(gs->curB);
+		}
+	}
 	if(battleQuery != queries.topQuery(gs->curB->sides[0]))
 		complain("Player " + boost::lexical_cast<std::string>(gs->curB->sides[0]) + " although in battle has no battle query at the top!");
-
+		
 	battleQuery->result = *battleResult.data;
 
 	//Check how many battle queries were created (number of players blocked by battle)
-	const int queriedPlayers = boost::count(queries.allQueries(), battleQuery); 
-
+	const int queriedPlayers = battleQuery ? boost::count(queries.allQueries(), battleQuery) : 0; 
 	finishingBattle = make_unique<FinishingBattleHelper>(battleQuery, gs->initialOpts->mode == StartInfo::DUEL, queriedPlayers);
 
 
@@ -592,7 +597,13 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 	}
 
 	if(finishingBattle->duel)
+	{
+		BattleResultsApplied resultsApplied;
+		resultsApplied.player1 = finishingBattle->victor;
+		resultsApplied.player2 = finishingBattle->loser;
+		sendAndApply(&resultsApplied);
 		return;
+	}
 	
 	cab1.takeFromArmy(this); cab2.takeFromArmy(this); //take casualties after battle is deleted
 
@@ -826,6 +837,12 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
 			{
 				boost::unique_lock<boost::mutex> lock(*c.rmx);
 				c >> player >> requestID >> pack; //get the package
+
+				if(!pack)
+				{
+					logGlobal ->errorStream() << boost::format("Received a null package marked as request %d from player %d") % requestID % player;
+				}
+
 				packType = typeList.getTypeID(pack); //get the id of type
 
                 logGlobal->traceStream() << boost::format("Received client message (request %d by player %d) of type with ID=%d (%s).\n")
@@ -1427,6 +1444,8 @@ void CGameHandler::newTurn()
 }
 void CGameHandler::run(bool resume)
 {
+	LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
+
 	using namespace boost::posix_time;
 	BOOST_FOREACH(CConnection *cc, conns)
 	{
@@ -1468,6 +1487,12 @@ void CGameHandler::run(bool resume)
 	if(gs->scenarioOps->mode == StartInfo::DUEL)
 	{
 		runBattle();
+		end2 = true;
+
+
+		while(conns.size() && (*conns.begin())->isOpen())
+			boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
+
 		return;
 	}
 

+ 2 - 2
server/CGameHandler.h

@@ -265,7 +265,7 @@ public:
 		FinishingBattleHelper();
 		FinishingBattleHelper(shared_ptr<const CBattleQuery> Query, bool Duel, int RemainingBattleQueriesCount);
 
-		shared_ptr<const CBattleQuery> query;
+		//shared_ptr<const CBattleQuery> query;
 		const CGHeroInstance *winnerHero, *loserHero;
 		PlayerColor victor, loser;
 		bool duel;
@@ -274,7 +274,7 @@ public:
 
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
-			h & query & winnerHero & loserHero & victor & loser & duel & remainingBattleQueriesCount;
+			h & /*query & */winnerHero & loserHero & victor & loser & duel & remainingBattleQueriesCount;
 		}
 	};
 

+ 4 - 0
server/CQuery.cpp

@@ -179,6 +179,10 @@ QueryPtr Queries::topQuery(PlayerColor player)
 
 void Queries::popIfTop(QueryPtr query)
 {
+	LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
+	if(!query)
+		logGlobal->errorStream() << "The query is nullptr! Ignoring.";
+
 	popIfTop(*query);
 }