Răsfoiți Sursa

First part of support for victory & loss conditions.
Implemented and tested are
victory:
Defeat hero
Capture town
Defeat monster
Flag dwellings
Flag mines

Lose:
Loss hero
Time expire

**

Some others may work but not has been tested yet.
I've added a new page in VCMI Status spreadsheet with status of various victory/loss conditions.

Michał W. Urbańczyk 15 ani în urmă
părinte
comite
5279e2e9fc

+ 1 - 0
CGameInterface.h

@@ -103,6 +103,7 @@ public:
 	virtual void objectPropertyChanged(const SetObjectProperty * sop){}; //eg. mine has been flagged
 	virtual void objectRemoved(const CGObjectInstance *obj){}; //eg. collected resource, picked artifact, beaten hero
 	virtual void playerBlocked(int reason){}; //reason: 0 - upcoming battle
+	virtual void gameOver(ui8 player, bool victory){}; //player lost or won the game
 	virtual void serialize(COSer<CSaveFile> &h, const int version){}; //saving
 	virtual void serialize(CISer<CLoadFile> &h, const int version){}; //loading
 

+ 4 - 0
client/CMT.cpp

@@ -169,6 +169,7 @@ int _tmain(int argc, _TCHAR* argv[])
 int main(int argc, char** argv)
 #endif
 {
+	//Set environment vars to make window centered. Sometimes work, sometimes not. :/
 	putenv("SDL_VIDEO_WINDOW_POS");
 	putenv("SDL_VIDEO_CENTERED=1");
 
@@ -504,6 +505,9 @@ static void listenForEvents()
 		else if (ev->type == SDL_USEREVENT && ev->user.code == 2) //something want to quit to main menu
 		{
 			client->stop();
+			delete client;
+			client = NULL;
+
 			delete ev;
 
 			GH.curInt = CGP;

+ 21 - 1
client/CPlayerInterface.cpp

@@ -746,7 +746,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
 	pom.push_back(std::pair<std::string,CFunctionList<void()> >("IOKAY.DEF",0));
 	CInfoWindow * temp = new CInfoWindow(text,playerID,0,components,pom,false);
 
-	if(makingTurn && GH.listInt.size())
+	if(/*makingTurn && */GH.listInt.size())
 	{
 		CGI->soundh->playSound(static_cast<soundBase::soundID>(soundID));
 		showingDialog->set(true);
@@ -1590,6 +1590,26 @@ void CPlayerInterface::finishMovement( const TryMoveHero &details, const int3 &h
 	std::stable_sort(CGI->mh->ttiles[details.end.x-1][details.end.y][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x-1][details.end.y][details.end.z].objects.end(), ocmptwo_cgin);
 	std::stable_sort(CGI->mh->ttiles[details.end.x][details.end.y][details.end.z].objects.begin(), CGI->mh->ttiles[details.end.x][details.end.y][details.end.z].objects.end(), ocmptwo_cgin);
 }
+
+void CPlayerInterface::gameOver(ui8 player, bool victory )
+{
+	if(player == playerID)
+	{
+		if(!victory)
+			showInfoDialog(CGI->generaltexth->allTexts[95]);
+// 		else
+// 			showInfoDialog("Placeholder message: you won!");
+
+		waitWhileDialog();
+
+ 		//return to main menu
+ 		SDL_Event event;
+ 		event.type = SDL_USEREVENT;
+ 		event.user.code = 2;
+ 		SDL_PushEvent(&event);
+	}
+}
+
 void SystemOptions::setMusicVolume( int newVolume )
 {
 	musicVolume = newVolume;

+ 1 - 0
client/CPlayerInterface.h

@@ -170,6 +170,7 @@ public:
 	void centerView (int3 pos, int focusTime);
 	void objectPropertyChanged(const SetObjectProperty * sop);
 	void objectRemoved(const CGObjectInstance *obj);
+	void gameOver(ui8 player, bool victory);
 	void serialize(COSer<CSaveFile> &h, const int version); //saving
 	void serialize(CISer<CLoadFile> &h, const int version); //loading
 

+ 1 - 0
client/CPreGame.cpp

@@ -14,6 +14,7 @@
 #include "../hch/CLodHandler.h"
 #include "../hch/CTownHandler.h"
 #include "../hch/CHeroHandler.h"
+#include "../hch/CObjectHandler.h"
 #include <cmath>
 #include "Graphics.h"
 //#include <boost/thread.hpp>

+ 11 - 1
client/Client.cpp

@@ -153,7 +153,17 @@ void CClient::run()
 			handlePack(pack);
 			pack = NULL;
 		}
-	} HANDLE_EXCEPTION(tlog1 << "Lost connection to server, ending listening thread!\n");
+	} 
+	catch (const std::exception& e)
+	{	
+		tlog3 << "Lost connection to server, ending listening thread!\n";					
+		tlog1 << e.what() << std::endl;
+		if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected
+		{
+			tlog1 << "Something wrong, lost connection while game is still ongoing...\n";
+			throw;
+		}
+	}
 }
 
 void CClient::stop()

+ 1 - 0
client/GUIClasses.cpp

@@ -628,6 +628,7 @@ void CGarrisonInt::deactivate()
 
 CInfoWindow::CInfoWindow(std::string text, int player, int charperline, const std::vector<SComponent*> &comps, std::vector<std::pair<std::string,CFunctionList<void()> > > &Buttons, bool delComps)
 {
+	slider = NULL;
 	ID = -1;
 	this->delComps = delComps;
 	for(int i=0;i<Buttons.size();i++)

+ 6 - 0
client/NetPacksClient.cpp

@@ -127,6 +127,12 @@ void ChangeObjPos::applyCl( CClient *cl )
 		CGI->mh->printObject(obj);
 }
 
+void PlayerEndsGame::applyCl( CClient *cl )
+{
+	for(std::map<ui8, CGameInterface*>::iterator i=cl->playerint.begin();i!=cl->playerint.end();i++)
+		i->second->gameOver(player,	victory);
+}
+
 void RemoveObject::applyFirstCl( CClient *cl )
 {
 	const CGObjectInstance *o = cl->getObj(id);

+ 8 - 0
client/VCMI_client.vcproj

@@ -325,6 +325,10 @@
 				RelativePath=".\CHeroWindow.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\CKingdomInterface.cpp"
+				>
+			</File>
 			<File
 				RelativePath=".\Client.cpp"
 				>
@@ -471,6 +475,10 @@
 				RelativePath=".\CHeroWindow.h"
 				>
 			</File>
+			<File
+				RelativePath=".\CKingdomInterface.h"
+				>
+			</File>
 			<File
 				RelativePath=".\Client.h"
 				>

+ 1 - 1
global.h

@@ -20,7 +20,7 @@ typedef boost::int8_t si8; //signed int 8 bits (1 byte)
 #define THC
 #endif
 
-#define NAME_VER ("VCMI 0.75")
+#define NAME_VER ("VCMI 0.75b")
 extern std::string NAME; //full name
 extern std::string NAME_AFFIX; //client / server
 #define CONSOLE_LOGGING_LEVEL 5

+ 11 - 0
hch/CObjectHandler.cpp

@@ -1234,6 +1234,17 @@ void CGHeroInstance::recreateArtBonuses()
 	}
 }
 
+bool CGHeroInstance::hasArt( ui32 aid ) const
+{
+	if(vstd::contains(artifacts, aid))
+		return true;
+	for(std::map<ui16,ui32>::const_iterator i = artifWorn.begin(); i != artifWorn.end(); i++)
+		if(i->second == aid)
+			return true;
+
+	return true;
+}
+
 void CGDwelling::initObj()
 {
 	switch(ID)

+ 6 - 2
hch/CObjectHandler.h

@@ -298,10 +298,12 @@ public:
 	int getPrimSkillLevel(int id) const;
 	ui8 getSecSkillLevel(const int & ID) const; //0 - no skill
 	int maxMovePoints(bool onLand) const;
+
 	ui32 getArtAtPos(ui16 pos) const; //-1 - no artifact
 	const CArtifact * getArt(int pos) const;
 	si32 getArtPos(int aid) const; //looks for equipped artifact with given ID and returns its slot ID or -1 if none(if more than one such artifact lower ID is returned)
-	void giveArtifact (ui32 aid);
+	bool hasArt(ui32 aid) const; //checks if hero possess artifact of given id (either in backack or worn)
+
 	int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error
 	static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
 	double getHeroStrength() const;
@@ -316,7 +318,9 @@ public:
 	void initHero(); 
 	void initHero(int SUBID); 
 	void recreateArtBonuses();
+	void giveArtifact (ui32 aid);
 	void initHeroDefInfo();
+
 	CGHeroInstance();
 	virtual ~CGHeroInstance();
 
@@ -605,7 +609,7 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CGObjectInstance&>(*this) & static_cast<CQuest&>(*this);
-		h & rewardType & rID & rVal & textOption & seerName;
+		h & rewardType & rID & rVal & textOption;
 	}
 };
 

+ 204 - 1
lib/CGameState.cpp

@@ -966,12 +966,24 @@ CGHeroInstance *CGameState::getHero(int objid)
 		return NULL;
 	return static_cast<CGHeroInstance *>(map->objects[objid]);
 }
+
+const CGHeroInstance * CGameState::getHero( int objid ) const
+{
+	return (const_cast<CGameState *>(this))->getHero(objid);
+}
+
 CGTownInstance *CGameState::getTown(int objid)
 {
 	if(objid<0 || objid>=map->objects.size())
 		return NULL;
 	return static_cast<CGTownInstance *>(map->objects[objid]);
 }
+
+const CGTownInstance * CGameState::getTown( int objid ) const
+{
+	return (const_cast<CGameState *>(this))->getTown(objid);
+}
+
 std::pair<int,int> CGameState::pickObject (CGObjectInstance *obj)
 {
 	switch(obj->ID)
@@ -1162,6 +1174,7 @@ void CGameState::randomizeObject(CGObjectInstance *cur)
 		if(cur->ID==TOWNI_TYPE) //town - set def
 		{
 			CGTownInstance *t = dynamic_cast<CGTownInstance*>(cur);
+			t->town = &VLC->townh->towns[t->subID];
 			if(t->hasCapitol())
 				t->defInfo = capitols[t->subID];
 			else if(t->hasFort())
@@ -1937,6 +1950,11 @@ PlayerState * CGameState::getPlayer( ui8 color )
 	}
 }
 
+const PlayerState * CGameState::getPlayer( ui8 color ) const
+{
+	return (const_cast<CGameState *>(this))->getPlayer(color);
+}
+
 bool CGameState::getPath(int3 src, int3 dest, const CGHeroInstance * hero, CPath &ret)
 {
 	if(!map->isInTheMap(src) || !map->isInTheMap(dest)) //check input
@@ -2916,6 +2934,191 @@ bool CGameState::battleCanShoot(int ID, int dest)
 	return false;
 }
 
+int CGameState::victoryCheck( ui8 player ) const
+{
+	const PlayerState *p = getPlayer(player);
+	if(map->victoryCondition.condition == winStandard  ||  map->victoryCondition.allowNormalVictory)
+		if(player == checkForStandardWin())
+			return -1;
+
+	if(p->human || map->victoryCondition.appliesToAI)
+	{
+ 		switch(map->victoryCondition.condition)
+		{
+		case artifact:
+			//check if any hero has winning artifact
+			for(size_t i = 0; i < p->heroes.size(); i++)
+				if(p->heroes[i]->hasArt(map->victoryCondition.ID))
+					return 1;
+
+			break;
+
+		case gatherTroop:
+			{
+				//check if in players armies there is enough creatures
+				int total = 0; //creature counter
+				for(size_t i = 0; i < map->objects.size(); i++)
+				{
+					const CArmedInstance *ai = NULL;
+					if(map->objects[i] 
+						&& map->objects[i]->tempOwner  //object controlled by player
+						&&  (ai = dynamic_cast<const CArmedInstance*>(map->objects[i]))) //contains army
+					{
+						for(TSlots::const_iterator i=ai->army.slots.begin(); i!=ai->army.slots.end(); ++i) //iterate through army
+							if(i->second.first == map->victoryCondition.ID) //it's searched creature
+								total += i->second.second;
+					}
+				}
+
+				if(total >= map->victoryCondition.count)
+					return 1;
+			}
+			break;
+
+		case gatherResource:
+			if(p->resources[map->victoryCondition.ID] >= map->victoryCondition.count)
+				return 1;
+
+			break;
+
+		case buildCity:
+			for(size_t i = 0; i < map->towns.size(); i++)
+				if(map->towns[i]->pos == map->victoryCondition.pos
+					&& map->towns[i]->tempOwner == player 
+					&& map->towns[i]->hallLevel() >= map->victoryCondition.ID)
+					return 1;
+			break;
+
+		case buildGrail:
+			for(size_t i = 0; i < map->towns.size(); i++)
+				if(map->towns[i]->pos == map->victoryCondition.pos
+					&& map->towns[i]->tempOwner == player 
+					&& vstd::contains(map->towns[i]->builtBuildings, 26))
+					return 1;
+			break;
+
+		case beatHero:
+			if(map->victoryCondition.obj->tempOwner >= PLAYER_LIMIT) //target hero not present on map
+				return 1;
+			break;
+		case captureCity:
+			{
+				if(map->victoryCondition.obj->tempOwner == player)
+					return 1;
+			}
+			break;
+		case beatMonster:
+			if(!map->objects[map->victoryCondition.obj->id]) //target monster not present on map
+				return 1;
+			break;
+		case takeDwellings:
+			for(size_t i = 0; i < map->objects.size(); i++)
+			{
+				if(map->objects[i] && map->objects[i]->tempOwner != player) //check not flagged objs
+				{
+					switch(map->objects[i]->ID)
+					{
+					case 17: case 18: case 19: case 20: //dwellings
+					case 216: case 217: case 218:
+						return 0; //found not flagged dwelling - player not won
+					}
+				}
+			}
+			return 1;
+			break;
+		case takeMines:
+			for(size_t i = 0; i < map->objects.size(); i++)
+			{
+				if(map->objects[i] && map->objects[i]->tempOwner != player) //check not flagged objs
+				{
+					switch(map->objects[i]->ID)
+					{
+					case 53: case 220:
+						return 0; //found not flagged mine - player not won
+					}
+				}
+			}
+			return 1;
+			break;
+		case transportItem:
+			//TODO
+			break;
+ 		}
+	}
+
+	return 0;
+}
+
+ui8 CGameState::checkForStandardWin() const
+{
+	//std victory condition is:
+	//all enemies lost
+	ui8 supposedWinner = 255, winnerTeam = 255;
+	for(std::map<ui8,PlayerState>::const_iterator i = players.begin(); i != players.end(); i++)
+	{
+		if(i->second.status == PlayerState::INGAME)
+		{
+			if(supposedWinner == 255)		
+			{
+				//first player remaining ingame - candidate for victory
+				supposedWinner = i->second.color;
+				winnerTeam = map->players[supposedWinner].team;
+			}
+			else if(winnerTeam != map->players[i->second.color].team)
+			{
+				//current candidate has enemy remaining in game -> no vicotry
+				return 255;
+			}
+		}
+	}
+
+	return supposedWinner;
+}
+
+bool CGameState::checkForStandardLoss( ui8 player ) const
+{
+	//std loss condition is: player lost all towns and heroes
+	const PlayerState &p = *getPlayer(player);
+	return p.heroes.size() || p.towns.size();
+}
+
+int CGameState::lossCheck( ui8 player ) const
+{
+	const PlayerState *p = getPlayer(player);
+	if(map->lossCondition.typeOfLossCon == lossStandard)
+		if(checkForStandardLoss(player))
+			return -1;
+
+	if(p->human) //special loss condition applies only to human player
+	{
+		switch(map->lossCondition.typeOfLossCon)
+		{
+		case lossCastle:
+			{
+				const CGTownInstance *t = dynamic_cast<const CGTownInstance *>(map->lossCondition.obj);
+				assert(t);
+				if(t->tempOwner != player)
+					return 1;
+			}
+			break;
+		case lossHero:
+			{
+				const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(map->lossCondition.obj);
+				assert(h);
+				if(h->tempOwner != player)
+					return 1;
+			}
+			break;
+		case timeExpires:
+			if(map->lossCondition.timeLimit < day)
+				return 1;
+			break;
+		}
+	}
+
+	return false;
+}
+
 const CStack * BattleInfo::getNextStack() const
 {
 	std::vector<const CStack *> hlp;
@@ -3205,7 +3408,7 @@ CMP_stack::CMP_stack( int Phase /*= 1*/, int Turn )
 }
 
 PlayerState::PlayerState() 
- : color(-1), currentSelection(0xffffffff)
+ : color(-1), currentSelection(0xffffffff), status(INGAME), daysWithoutCastle(0)
 {
 
 }

+ 70 - 58
lib/CGameState.h

@@ -60,6 +60,7 @@ namespace boost
 struct DLL_EXPORT PlayerState
 {
 public:
+	enum EStatus {INGAME, LOSER, WINNER};
 	ui8 color, serial;
 	ui8 human; //true if human controlled player, false for AI
 	ui32 currentSelection; //id of hero/town, 0xffffffff if none
@@ -70,42 +71,46 @@ public:
 	std::vector<CGHeroInstance *> availableHeroes; //heroes available in taverns
 	std::vector<CGDwelling *> dwellings; //used for town growth
 
+	ui8 status; //0 - in game, 1 - loser, 2 - winner <- uses EStatus enum
+	ui8 daysWithoutCastle;
+
 	PlayerState();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & color & serial & human & currentSelection & fogOfWarMap & resources;
-
-		ui32 size;
-		if(h.saving) //write subids of available heroes
-		{
-			size = availableHeroes.size();
-			h & size;
-			for(size_t i=0; i < size; i++)
-			{
-				if(availableHeroes[i])
-				{
-					h & availableHeroes[i]->subID;
-				}
-				else
-				{
-					ui32 none = 0xffffffff;
-					h & none;
-				}
-			}
-		}
-		else
-		{
-			ui32 hid; 
-			h & size;
-			for(size_t i=0; i < size; i++)
-			{
-				//fill availableHeroes with dummy hero instances, holding subids
-				h & hid;
-				availableHeroes.push_back(new CGHeroInstance);
-				availableHeroes[availableHeroes.size()-1]->subID = hid;
-			}
-		}
+		h & color & serial & human & currentSelection & fogOfWarMap & resources & status;
+		h & heroes & towns & availableHeroes & dwellings & status & daysWithoutCastle;
+
+// 		ui32 size;
+// 		if(h.saving) //write subids of available heroes
+// 		{
+// 			size = availableHeroes.size();
+// 			h & size;
+// 			for(size_t i=0; i < size; i++)
+// 			{
+// 				if(availableHeroes[i])
+// 				{
+// 					h & availableHeroes[i]->subID;
+// 				}
+// 				else
+// 				{
+// 					ui32 none = 0xffffffff;
+// 					h & none;
+// 				}
+// 			}
+// 		}
+// 		else
+// 		{
+// 			ui32 hid; 
+// 			h & size;
+// 			for(size_t i=0; i < size; i++)
+// 			{
+// 				//fill availableHeroes with dummy hero instances, holding subids
+// 				h & hid;
+// 				availableHeroes.push_back(new CGHeroInstance);
+// 				availableHeroes[availableHeroes.size()-1]->subID = hid;
+// 			}
+// 		}
 	}
 };
 
@@ -334,7 +339,7 @@ public:
 	BattleInfo *curB; //current battle
 	ui32 day; //total number of days in game
 	Mapa * map;
-	std::map<ui8,PlayerState> players; //ID <-> player state
+	std::map<ui8, PlayerState> players; //ID <-> player state
 	std::map<int, CGDefInfo*> villages, forts, capitols; //def-info for town graphics
 	std::vector<ui32> resVals; //default values of resources in gold
 
@@ -354,6 +359,7 @@ public:
 	boost::shared_mutex *mx;
 
 	PlayerState *getPlayer(ui8 color);
+	const PlayerState *getPlayer(ui8 color) const;
 	void init(StartInfo * si, Mapa * map, int Seed);
 	void loadTownDInfos();
 	void randomizeObject(CGObjectInstance *cur);
@@ -362,6 +368,8 @@ public:
 	void apply(CPack *pack);
 	CGHeroInstance *getHero(int objid);
 	CGTownInstance *getTown(int objid);
+	const CGHeroInstance *getHero(int objid) const;
+	const CGTownInstance *getTown(int objid) const;
 	bool battleMoveCreatureStack(int ID, int dest);
 	bool battleAttackCreatureStack(int ID, int dest);
 	bool battleShootCreatureStack(int ID, int dest);
@@ -379,6 +387,10 @@ public:
 	bool checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst) const; //check if src tile is visitable from dst tile
 	bool getPath(int3 src, int3 dest, const CGHeroInstance * hero, CPath &ret); //calculates path between src and dest; returns pointer to newly allocated CPath or NULL if path does not exists
 	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or NULL if path does not exists
+	int victoryCheck(ui8 player) const; //checks if given player is winner; -1 if std victory, 1 if special victory, 0 else
+	int lossCheck(ui8 player) const; //checks if given player is loser;  -1 if std loss, 1 if special, 0 else
+	ui8 checkForStandardWin() const; //returns color of player that accomplished standard victory conditions or 255 if no winner
+	bool checkForStandardLoss(ui8 player) const; //checks if given player lost the game
 
 	bool isVisible(int3 pos, int player);
 	bool isVisible(const CGObjectInstance *obj, int player);
@@ -395,31 +407,31 @@ public:
 		{
 			loadTownDInfos();
 
-			//recreating towns/heroes vectors in players entries
-			for(int i=0; i<map->towns.size(); i++)
-				if(map->towns[i]->tempOwner < PLAYER_LIMIT)
-					getPlayer(map->towns[i]->tempOwner)->towns.push_back(map->towns[i]);
-			for(int i=0; i<map->heroes.size(); i++)
-				if(map->heroes[i]->tempOwner < PLAYER_LIMIT)
-					getPlayer(map->heroes[i]->tempOwner)->heroes.push_back(map->heroes[i]);
-			//recreating available heroes
-			for(std::map<ui8,PlayerState>::iterator i=players.begin(); i!=players.end(); i++)
-			{
-				for(size_t j=0; j < i->second.availableHeroes.size(); j++)
-				{
-					ui32 hlp = i->second.availableHeroes[j]->subID;
-					delete i->second.availableHeroes[j];
-					if(hlp != 0xffffffff)
-					{
-						assert(vstd::contains(hpool.heroesPool, hlp));
-						i->second.availableHeroes[j] = hpool.heroesPool[hlp];
-					}
-					else
-					{
-						i->second.availableHeroes[j] = NULL;
-					}
-				}
-			}
+// 			//recreating towns/heroes vectors in players entries
+// 			for(int i=0; i<map->towns.size(); i++)
+// 				if(map->towns[i]->tempOwner < PLAYER_LIMIT)
+// 					getPlayer(map->towns[i]->tempOwner)->towns.push_back(map->towns[i]);
+// 			for(int i=0; i<map->heroes.size(); i++)
+// 				if(map->heroes[i]->tempOwner < PLAYER_LIMIT)
+// 					getPlayer(map->heroes[i]->tempOwner)->heroes.push_back(map->heroes[i]);
+// 			//recreating available heroes
+// 			for(std::map<ui8,PlayerState>::iterator i=players.begin(); i!=players.end(); i++)
+// 			{
+// 				for(size_t j=0; j < i->second.availableHeroes.size(); j++)
+// 				{
+// 					ui32 hlp = i->second.availableHeroes[j]->subID;
+// 					delete i->second.availableHeroes[j];
+// 					if(hlp != 0xffffffff)
+// 					{
+// 						assert(vstd::contains(hpool.heroesPool, hlp));
+// 						i->second.availableHeroes[j] = hpool.heroesPool[hlp];
+// 					}
+// 					else
+// 					{
+// 						i->second.availableHeroes[j] = NULL;
+// 					}
+// 				}
+// 			}
 		}
 	}
 

+ 31 - 8
lib/Connection.cpp

@@ -147,22 +147,40 @@ CConnection::CConnection(boost::asio::basic_socket_acceptor<boost::asio::ip::tcp
 int CConnection::write(const void * data, unsigned size)
 {
 	//LOG("Sending " << size << " byte(s) of data" <<std::endl);
-	int ret;
-	ret = asio::write(*socket,asio::const_buffers_1(asio::const_buffer(data,size)));
-	return ret;
+	try
+	{
+		int ret;
+		ret = asio::write(*socket,asio::const_buffers_1(asio::const_buffer(data,size)));
+		return ret;
+	}
+	catch(...)
+	{
+		//connection has been lost
+		connected = false;
+		throw;
+	}
 }
 int CConnection::read(void * data, unsigned size)
 {
 	//LOG("Receiving " << size << " byte(s) of data" <<std::endl);
-	int ret = asio::read(*socket,asio::mutable_buffers_1(asio::mutable_buffer(data,size)));
-	return ret;
+	try
+	{
+		int ret = asio::read(*socket,asio::mutable_buffers_1(asio::mutable_buffer(data,size)));
+		return ret;
+	}
+	catch(...)
+	{
+		//connection has been lost
+		connected = false;
+		throw;
+	}
 }
 CConnection::~CConnection(void)
 {
 	close();
-	delete io_service;
-	delete wmx;
-	delete rmx;
+ 	delete io_service;
+ 	delete wmx;
+ 	delete rmx;
 }
 
 template<class T>
@@ -205,6 +223,11 @@ void CConnection::setGS( CGameState *state )
 	gs = state;
 }
 
+bool CConnection::isOpen() const
+{
+	return socket && connected;
+}
+
 CSaveFile::CSaveFile( const std::string &fname )
 	:sfile(new std::ofstream(fname.c_str(),std::ios::binary))
 {

+ 13 - 11
lib/Connection.h

@@ -11,16 +11,17 @@
 #include <boost/type_traits/is_enum.hpp>
 #include <boost/type_traits/is_pointer.hpp> 
 #include <boost/type_traits/is_class.hpp> 
-#include <boost/type_traits/remove_pointer.hpp>
 #include <boost/type_traits/is_base_of.hpp>
+#include <boost/type_traits/is_array.hpp>
+#include <boost/type_traits/remove_pointer.hpp>
+#include <boost/type_traits/remove_const.hpp>
 
 #include <boost/mpl/eval_if.hpp>
 #include <boost/mpl/equal_to.hpp>
 #include <boost/mpl/int.hpp>
 #include <boost/mpl/identity.hpp>
 
-#include <boost/type_traits/is_array.hpp>
-const ui32 version = 714;
+const ui32 version = 716;
 class CConnection;
 class CGObjectInstance;
 class CGameState;
@@ -542,11 +543,11 @@ public:
 	template <typename T>
 	void loadSerializable(T &data)
 	{
-		////that const cast would be evil because it allows to implicitly overwrite const objects when deserializing
-		//typedef typename boost::remove_const<T>::type nonConstT;
-		//nonConstT &hlp = const_cast<nonConstT&>(data);
-		//hlp.serialize(*this,myVersion);
-		data.serialize(*this,myVersion);
+		////that const cast is evil because it allows to implicitly overwrite const objects when deserializing
+		typedef typename boost::remove_const<T>::type nonConstT;
+		nonConstT &hlp = const_cast<nonConstT&>(data);
+		hlp.serialize(*this,myVersion);
+		//data.serialize(*this,myVersion);
 	}	
 	template <typename T>
 	void loadArray(T &data)
@@ -587,7 +588,7 @@ public:
 		This()->loadPointerHlp(tid, data);
 
 		if(smartPointerSerialization && i == loadedPointers.end())
-			loadedPointers[pid] = data; //add loaded pointer to our lookup map
+			loadedPointers[pid] = (void*)data; //add loaded pointer to our lookup map; cast is to avoid errors with const T* pt
 	}
 
 	//that part of ptr deserialization was extracted to allow customization of its behavior in derived classes
@@ -597,7 +598,8 @@ public:
 		if(!tid)
 		{
 			typedef typename boost::remove_pointer<T>::type npT;
-			data = new npT;
+			typedef typename boost::remove_const<npT>::type ncpT;
+			data = new ncpT;
 			*this >> *data;
 		}
 		else
@@ -723,8 +725,8 @@ public:
 		std::string Name); //use immediately after accepting connection into socket
 	int write(const void * data, unsigned size);
 	int read(void * data, unsigned size);
-	int readLine(void * data, unsigned maxSize);
 	void close();
+	bool isOpen() const;
     template<class T>
     CConnection &operator&(const T&);
 	~CConnection(void);

+ 20 - 0
lib/NetPacks.h

@@ -388,6 +388,26 @@ struct ChangeObjPos : public CPackForClient //116
 	}
 };
 
+struct PlayerEndsGame : public CPackForClient //117
+{
+	PlayerEndsGame()
+	{
+		type = 117;
+	}
+
+	void applyCl(CClient *cl);
+	DLL_EXPORT void applyGs(CGameState *gs);
+
+	ui8 player;
+	ui8 victory;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & player & victory;
+	}
+};
+
+
 struct RemoveObject : public CPackForClient //500
 {
 	RemoveObject(){type = 500;};

+ 8 - 0
lib/NetPacksLib.cpp

@@ -219,6 +219,12 @@ DLL_EXPORT void ChangeObjPos::applyGs( CGameState *gs )
 	gs->map->addBlockVisTiles(obj);
 }
 
+DLL_EXPORT void PlayerEndsGame::applyGs( CGameState *gs )
+{
+	PlayerState *p = gs->getPlayer(player);
+	p->status = victory ? 2 : 1;
+}
+
 DLL_EXPORT void RemoveObject::applyGs( CGameState *gs )
 {
 	CGObjectInstance *obj = gs->map->objects[id];
@@ -230,6 +236,8 @@ DLL_EXPORT void RemoveObject::applyGs( CGameState *gs )
 		int player = h->tempOwner;
 		nitr = std::find(gs->getPlayer(player)->heroes.begin(), gs->getPlayer(player)->heroes.end(), h);
 		gs->getPlayer(player)->heroes.erase(nitr);
+		h->tempOwner = 255; //no one owns beaten hero
+
 		if(h->visitedTown)
 		{
 			if(h->inTownGarrison)

+ 1 - 0
lib/RegisterTypes.cpp

@@ -79,6 +79,7 @@ void registerTypes2(Serializer &s)
 	s.template registerType<SetAvailableHeroes>();
 	s.template registerType<GiveBonus>();
 	s.template registerType<ChangeObjPos>();
+	s.template registerType<PlayerEndsGame>();
 	s.template registerType<RemoveObject>();
 	s.template registerType<TryMoveHero>();
 	s.template registerType<SetGarrisons>();

+ 42 - 11
lib/map.cpp

@@ -8,6 +8,7 @@
 #include <boost/crc.hpp>
 #include "../hch/CLodHandler.h"
 #include <boost/bind.hpp>
+#include <assert.h>
 
 /*
  * map.cpp, part of VCMI engine
@@ -353,6 +354,7 @@ void CMapHeader::loadPlayerInfo( int &pom, const unsigned char * bufor, int &i )
 
 void CMapHeader::loadViCLossConditions( const unsigned char * bufor, int &i)
 {
+	victoryCondition.obj = NULL;
 	victoryCondition.condition = (EvictoryConditions)bufor[i++];
 	if (victoryCondition.condition != winStandard) //specific victory conditions
 	{
@@ -367,8 +369,8 @@ void CMapHeader::loadViCLossConditions( const unsigned char * bufor, int &i)
 			}
 		case gatherTroop:
 			{
-				int temp1 = bufor[i+2];
-				int temp2 = bufor[i+3];
+// 				int temp1 = bufor[i+2];
+// 				int temp2 = bufor[i+3];
 				victoryCondition.ID = bufor[i+2];
 				victoryCondition.count = readNormalNr(bufor, i+(version==RoE ? 3 : 4));
 				nr=(version==RoE ? 5 : 6);
@@ -438,17 +440,11 @@ void CMapHeader::loadViCLossConditions( const unsigned char * bufor, int &i)
 	switch (lossCondition.typeOfLossCon) //read loss conditions
 	{
 	case lossCastle:
-		{
-			lossCondition.castlePos.x=bufor[i++];
-			lossCondition.castlePos.y=bufor[i++];
-			lossCondition.castlePos.z=bufor[i++];
-			break;
-		}
 	case lossHero:
 		{
-			lossCondition.heroPos.x=bufor[i++];
-			lossCondition.heroPos.y=bufor[i++];
-			lossCondition.heroPos.z=bufor[i++];
+			lossCondition.pos.x=bufor[i++];
+			lossCondition.pos.y=bufor[i++];
+			lossCondition.pos.z=bufor[i++];
 			break;
 		}
 	case timeExpires:
@@ -500,6 +496,9 @@ void Mapa::initFromBytes(const unsigned char * bufor)
 		addBlockVisTiles(objects[f]);
 	}
 	tlog0<<"\tCalculating blocked/visitable tiles: "<<th.getDif()<<std::endl;
+
+	checkForObjectives();
+	tlog0 << "\tMap initialization done!" << std::endl;
 }	
 void Mapa::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 {
@@ -1402,6 +1401,7 @@ void Mapa::readObjects( const unsigned char * bufor, int &i)
 		pos.x = bufor[i++];
 		pos.y = bufor[i++];
 		pos.z = bufor[i++];
+
 		int defnum = readNormalNr(bufor,i, 4); i+=4;
 
 		CGDefInfo * defInfo = defy[defnum];
@@ -2109,6 +2109,23 @@ bool Mapa::isWaterTile(const int3 &pos) const
 	return isInTheMap(pos) && getTile(pos).tertype == TerrainTile::water;
 }
 
+void Mapa::checkForObjectives()
+{
+	if(isInTheMap(victoryCondition.pos))
+	{
+		const std::vector <CGObjectInstance*> &objs = getTile(victoryCondition.pos).visitableObjects;
+		assert(objs.size());
+		victoryCondition.obj = objs.front();
+	}
+
+	if(isInTheMap(lossCondition.pos))
+	{
+		const std::vector <CGObjectInstance*> &objs = getTile(lossCondition.pos).visitableObjects;
+		assert(objs.size());
+		lossCondition.obj = objs.front();
+	}
+}
+
 void CMapInfo::countPlayers()
 {
 	playerAmnt = humenPlayers = 0;
@@ -2143,3 +2160,17 @@ void CMapInfo::init(const std::string &fname, const unsigned char *map )
 	initFromMemory(map, i);
 	countPlayers();
 }
+
+LossCondition::LossCondition()
+{
+	obj = NULL;
+	timeLimit = -1;
+	pos = int3(-1,-1,-1);
+}
+
+CVictoryCondition::CVictoryCondition()
+{
+	pos = int3(-1,-1,-1);
+	obj = NULL;
+	ID = allowNormalVictory = appliesToAI = count = 0;
+}

+ 14 - 5
lib/map.h

@@ -137,14 +137,19 @@ struct DLL_EXPORT PlayerInfo
 struct DLL_EXPORT LossCondition
 {
 	ElossCon typeOfLossCon;
-	int3 castlePos;
-	int3 heroPos;
-	int timeLimit; // in days
+
+	int3 pos;
+
+	si32 timeLimit; // in days; -1 if not used
+	const CGObjectInstance *obj; //set during map parsing: hero/town (depending on typeOfLossCon); NULL if not used
+
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & typeOfLossCon & castlePos & heroPos & timeLimit;
+		h & typeOfLossCon & pos & timeLimit & obj;
 	}
+
+	LossCondition();
 };
 struct DLL_EXPORT CVictoryCondition
 {
@@ -155,10 +160,13 @@ struct DLL_EXPORT CVictoryCondition
 	ui32 ID; //artifact ID (0); monster ID (1); resource ID (2); needed fort level in upgraded town (3); artifact ID (8)
 	ui32 count; //needed count for creatures (1) / resource (2); upgraded town hall level (3); 
 
+	const CGObjectInstance *obj; //object of specific monster / city / hero instance (NULL if not used); set during map parsing
+
+	CVictoryCondition();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & condition & allowNormalVictory & appliesToAI & pos & ID & count;
+		h & condition & allowNormalVictory & appliesToAI & pos & ID & count & obj;
 	}
 };
 
@@ -329,6 +337,7 @@ struct DLL_EXPORT Mapa : public CMapHeader
 	void loadTown( CGObjectInstance * &nobj, const unsigned char * bufor, int &i, int subid);
 	int loadSeerHut( const unsigned char * bufor, int i, CGObjectInstance *& nobj);
 
+	void checkForObjectives();
 
 	void addBlockVisTiles(CGObjectInstance * obj);
 	void removeBlockVisTiles(CGObjectInstance * obj, bool total=false);

+ 165 - 6
server/CGameHandler.cpp

@@ -491,6 +491,10 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 	sg.garrs[bEndArmy2->id] = takeCasualties(bEndArmy2->tempOwner, bEndArmy2->army, gs->curB);
 	sendAndApply(&sg);
 
+	ui8 sides[2];
+	sides[0] = gs->curB->side1;
+	sides[1] = gs->curB->side2;
+
 	//end battle, remove all info, free memory
 	giveExp(*battleResult.data);
 	sendAndApply(battleResult.data);
@@ -524,14 +528,17 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 
 	// Necromancy if applicable.
 	const CGHeroInstance *winnerHero = battleResult.data->winner != 0 ? hero2 : hero1;
-	if (winnerHero) {
+	if (winnerHero) 
+	{
 		std::pair<ui32, si32> raisedStack = winnerHero->calculateNecromancy(*battleResult.data);
 
 		// Give raised units to winner and show dialog, if any were raised.
-		if (raisedStack.first != -1) {
+		if (raisedStack.first != -1) 
+		{
 			int slot = winnerHero->army.getSlotFor(raisedStack.first);
 
-			if (slot != -1) {
+			if (slot != -1) 
+			{
 				SetGarrisons sg;
 
 				sg.garrs[winnerHero->id] = winnerHero->army;
@@ -545,6 +552,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 		}
 	}
 
+	winLoseHandle(1<<sides[0] | 1<<sides[1]); //handle victory/loss of engaged players
 	delete battleResult.data;
 }
 
@@ -602,7 +610,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 	CPack *pack = NULL;
 	try
 	{
-		while(!end2)
+		while(1)//server should never shut connection first //was: while(!end2)
 		{
 			{
 				boost::unique_lock<boost::mutex> lock(*c.rmx);
@@ -640,11 +648,12 @@ 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
 	{
+		assert(!c.connected); //make sure that connection has been marked as broken
 		tlog1 << e.what() << std::endl;
 		end2 = true;
 	}
 	HANDLE_EXCEPTION(end2 = true);
-handleConEnd:
+
 	tlog1 << "Ended handling connection\n";
 }
 
@@ -901,6 +910,8 @@ void CGameHandler::newTurn()
 	for(size_t i = 0; i<gs->map->objects.size(); i++)
 		if(gs->map->objects[i])
 			gs->map->objects[i]->newTurn();
+
+	winLoseHandle(0xff);
 }
 void CGameHandler::run(bool resume)
 {	
@@ -948,7 +959,13 @@ void CGameHandler::run(bool resume)
 		for(; i != gs->players.end(); i++)
 		{
 
-			if((i->second.towns.size()==0 && i->second.heroes.size()==0)  || i->second.color<0 || i->first>=PLAYER_LIMIT  ) continue; //players has not towns/castle - loser
+			if(i->second.towns.size()==0 && i->second.heroes.size()==0
+				|| i->second.color<0 
+				|| i->first>=PLAYER_LIMIT  
+				|| i->second.status) 
+			{
+				continue; 
+			}
 			states.setFlag(i->first,&PlayerStatus::makingTurn,true);
 			gs->currentPlayer = i->first;
 			{
@@ -968,6 +985,9 @@ void CGameHandler::run(bool resume)
 			}
 		}
 	}
+
+	while(conns.size() && (*conns.begin())->isOpen())
+		boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
 }
 
 namespace CGH
@@ -1385,6 +1405,8 @@ bool CGameHandler::removeObject( int objid )
 	RemoveObject ro;
 	ro.id = objid;
 	sendAndApply(&ro);
+
+	winLoseHandle(255); //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function)
 	return true;
 }
 
@@ -1556,6 +1578,8 @@ void CGameHandler::setOwner(int objid, ui8 owner)
 {
 	SetObjectProperty sop(objid,1,owner);
 	sendAndApply(&sop);
+
+	winLoseHandle(1<<owner);
 }
 void CGameHandler::setHoverName(int objid, MetaString* name)
 {
@@ -3433,3 +3457,138 @@ void CGameHandler::engageIntoBattle( ui8 player )
 	pb.reason = PlayerBlocked::UPCOMING_BATTLE;
 	sendAndApply(&pb);
 }
+
+void CGameHandler::winLoseHandle(ui8 players )
+{
+	for(size_t i = 0; i < PLAYER_LIMIT; i++)
+	{
+		if(players & 1<<i  &&  gs->getPlayer(i))
+		{
+			checkLossVictory(i);
+		}
+	}
+}
+
+void CGameHandler::checkLossVictory( ui8 player )
+{
+	int loss = gs->lossCheck(player);
+	int vic = gs->victoryCheck(player);
+
+	if(!loss && !vic)
+		return;
+
+	InfoWindow iw;
+	getLossVicMessage(player, vic ? vic < 0 : loss < 0, vic, iw);
+	sendAndApply(&iw);
+
+	PlayerEndsGame peg;
+	peg.player = player;
+	peg.victory = vic;
+	sendAndApply(&peg);
+
+	if(vic)
+		end2 = true;
+}
+
+void CGameHandler::getLossVicMessage( ui8 player, bool standard, bool victory, InfoWindow &out ) const
+{
+	const PlayerState *p = gs->getPlayer(player);
+	if(!p->human)
+		return; //AI doesn't need text info of loss
+
+	out.player = player;
+
+	if(victory)
+	{
+		if(!standard) //not std loss
+		{
+			switch(gs->map->victoryCondition.condition)
+			{
+			case artifact:
+				out.text.addTxt(MetaString::GENERAL_TXT, 280); //Congratulations! You have found the %s, and can claim victory!
+				out.text.addReplacement(MetaString::ART_NAMES,gs->map->victoryCondition.obj->subID); //artifact name
+				break;
+			case gatherTroop:
+				out.text.addTxt(MetaString::GENERAL_TXT, 276); //Congratulations! You have over %d %s in your armies. Your enemies have no choice but to bow down before your power!
+				out.text.addReplacement(gs->map->victoryCondition.count);
+				out.text.addReplacement(MetaString::CRE_PL_NAMES, gs->map->victoryCondition.ID);
+				break;
+			case gatherResource:
+				out.text.addTxt(MetaString::GENERAL_TXT, 278); //Congratulations! You have collected over %d %s in your treasury. Victory is yours!
+				out.text.addReplacement(gs->map->victoryCondition.count);
+				out.text.addReplacement(MetaString::RES_NAMES, gs->map->victoryCondition.ID);
+				break;
+			case buildCity:
+				out.text.addTxt(MetaString::GENERAL_TXT, 282); //Congratulations! You have successfully upgraded your town, and can claim victory!
+				break;
+			case buildGrail:
+				out.text.addTxt(MetaString::GENERAL_TXT, 284); //Congratulations! You have constructed a permanent home for the Grail, and can claim victory!
+				break;
+			case beatHero:
+				{
+					out.text.addTxt(MetaString::GENERAL_TXT, 252); //Congratulations! You have completed your quest to defeat the enemy hero %s. Victory is yours!
+					const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->victoryCondition.obj);
+					assert(h);
+					out.text.addReplacement(h->name);
+				}
+				break;
+			case captureCity:
+				{
+					out.text.addTxt(MetaString::GENERAL_TXT, 249); //Congratulations! You captured %s, and are victorious!
+					const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->victoryCondition.obj);
+					assert(t);
+					out.text.addReplacement(t->name);
+				}
+				break;
+			case beatMonster:
+				out.text.addTxt(MetaString::GENERAL_TXT, 286); //Congratulations! You have completed your quest to kill the fearsome beast, and can claim victory!
+				break;
+			case takeDwellings:
+				out.text.addTxt(MetaString::GENERAL_TXT, 288); //Congratulations! Your flag flies on the dwelling of every creature. Victory is yours!
+				break;
+			case takeMines:
+				out.text.addTxt(MetaString::GENERAL_TXT, 290); //Congratulations! Your flag flies on every mine. Victory is yours!
+				break;
+			case transportItem:
+				out.text.addTxt(MetaString::GENERAL_TXT, 292); //Congratulations! You have reached your destination, precious cargo intact, and can claim victory!
+				break;
+			}
+		}
+		else
+		{
+
+		}
+	}
+	else
+	{
+		if(!standard) //not std loss
+		{
+			switch(gs->map->lossCondition.typeOfLossCon)
+			{
+			case lossCastle:
+				{
+					out.text.addTxt(MetaString::GENERAL_TXT, 251); //The town of %s has fallen - all is lost!
+					const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->lossCondition.obj);
+					assert(t);
+					out.text.addReplacement(t->name);
+				}
+				break;
+			case lossHero:
+				{
+					out.text.addTxt(MetaString::GENERAL_TXT, 253); //The hero, %s, has suffered defeat - your quest is over!
+					const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->lossCondition.obj);
+					assert(h);
+					out.text.addReplacement(h->name);
+				}
+				break;
+			case timeExpires:
+				out.text.addTxt(MetaString::GENERAL_TXT, 254); //Alas, time has run out on your quest. All is lost.
+				break;
+			}
+		}
+		else //lost all towns and heroes
+		{
+			out.text.addReplacement(MetaString::GENERAL_TXT, 660); //All your forces have been defeated, and you are banished from this land!
+		}
+	}
+}

+ 4 - 1
server/CGameHandler.h

@@ -85,6 +85,10 @@ public:
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
 	int moveStack(int stack, int dest); //returned value - travelled distance
 	void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, boost::function<void(BattleResult*)> cb, const CGTownInstance *town = NULL); //use hero=NULL for no hero
+	void checkLossVictory(ui8 player);
+	void winLoseHandle(ui8 players=255); //players: bit field - colours of players to be checked; default: all
+	void getLossVicMessage(ui8 player, bool standard, bool victory, InfoWindow &out) const;
+
 	////used only in endBattle - don't touch elsewhere
 	boost::function<void(BattleResult*)> * battleEndCallback;
 	const CArmedInstance * bEndArmy1, * bEndArmy2;
@@ -95,7 +99,6 @@ public:
 	void checkForBattleEnd( std::vector<CStack*> &stacks );
 	void setupBattle( BattleInfo * curB, int3 tile, const CCreatureSet &army1, const CCreatureSet &army2, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank, const CGTownInstance *town);
 
-
 	CGameHandler(void);
 	~CGameHandler(void);