浏览代码

* redone stack queue algorithm
* recalculating paths only after finished movement / switching selection
* moving hero uses "new" pathfinder
* moving hero by arrow keys / numpad
* VCMI window should start centered
* fixed pairing Subterranean Gates
* fixed issues with creatures sounds after loading
* several minor changes and improvements

Michał W. Urbańczyk 16 年之前
父节点
当前提交
956a87f264

+ 3 - 1
AI/GeniusAI/CGeniusAI.cpp

@@ -1110,6 +1110,8 @@ BattleAction CGeniusAI::activeStack(int stackID)
 	message += ")";
 	DbgBox(message.c_str());
 
-	return m_battleLogic->MakeDecision(stackID);
+	BattleAction bact = m_battleLogic->MakeDecision(stackID);
+	assert(m_cb->battleGetStackByID(bact.stackNumber));
+	return bact;
 };
 

+ 20 - 4
CCallback.cpp

@@ -500,14 +500,14 @@ std::map<int, CStack> CCallback::battleGetStacks()
 	return ret;
 }
 
-std::vector<CStack> CCallback::battleGetStackQueue()
+void CCallback::getStackQueue( std::vector<const CStack *> &out, int howMany )
 {
 	if(!gs->curB)
 	{
-		tlog2<<"battleGetStackQueue called when there is not battle!"<<std::endl;
-		return std::vector<CStack>();
+		tlog2 << "battleGetStackQueue called when there is not battle!" << std::endl;
+		return;
 	}
-	return gs->curB->getStackQueue();
+	gs->curB->getStackQueue(out, howMany);
 }
 
 CCreature CCallback::battleGetCreature(int number)
@@ -834,9 +834,25 @@ const CGPathNode * CCallback::getPathInfo( int3 tile )
 
 bool CCallback::getPath2( int3 dest, CGPath &ret )
 {
+	const CGHeroInstance *h = cl->IGameCallback::getSelectedHero(player);
+	assert(cl->pathInfo->hero == h);
+	if(cl->pathInfo->hpos != h->getPosition(false)) //hero position changed, must update paths
+	{ 
+		recalculatePaths();
+	}
 	return cl->pathInfo->getPath(dest, ret);
 }
 
+void CCallback::recalculatePaths()
+{
+	gs->calculatePaths(cl->IGameCallback::getSelectedHero(player), *cl->pathInfo);
+}
+
+void CCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out, int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/ )
+{
+	gs->calculatePaths(hero, out, src, movement);
+}
+
 InfoAboutHero::InfoAboutHero()
 {
 	details = NULL;

+ 8 - 4
CCallback.h

@@ -137,8 +137,10 @@ public:
 	virtual std::vector < const CGHeroInstance *> getHeroesInfo(bool onlyOur=true)const =0;
 	virtual bool getHeroInfo(const CGObjectInstance *hero, InfoAboutHero &dest) const = 0;
 	virtual bool getPath(int3 src, int3 dest, const CGHeroInstance * hero, CPath &ret)=0;
-	virtual const CGPathNode *getPathInfo(int3 tile)=0;
-	virtual bool getPath2(int3 dest, CGPath &ret)=0;
+	virtual const CGPathNode *getPathInfo(int3 tile)=0; //uses main, client pathfinder info
+	virtual bool getPath2(int3 dest, CGPath &ret)=0; //uses main, client pathfinder info
+	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1) =0;
+	virtual void recalculatePaths()=0; //updates main, client pathfinder info (should be called when moving hero is over)
 	
 	//map
 	virtual std::vector < const CGObjectInstance * > getBlockingObjs(int3 pos)const =0;
@@ -172,7 +174,7 @@ public:
 	virtual int battleGetPos(int stack)=0; //returns position (tile ID) of stack
 	virtual int battleMakeAction(BattleAction* action)=0;//for casting spells by hero - DO NOT use it for moving active stack
 	virtual std::map<int, CStack> battleGetStacks()=0; //returns stacks on battlefield
-	virtual std::vector<CStack> battleGetStackQueue()=0; //returns vector of stack in order of their move sequence
+	virtual void getStackQueue( std::vector<const CStack *> &out, int howMany )=0; //returns vector of stack in order of their move sequence
 	virtual CCreature battleGetCreature(int number)=0; //returns type of creature by given number of stack
 	//virtual bool battleMoveCreature(int ID, int dest)=0; //moves creature with id ID to dest if possible
 	virtual std::vector<int> battleGetAvailableHexes(int ID, bool addOccupiable)=0; //reutrns numbers of hexes reachable by creature with id ID
@@ -269,6 +271,8 @@ public:
 	bool getPath(int3 src, int3 dest, const CGHeroInstance * hero, CPath &ret);
 	const CGPathNode *getPathInfo(int3 tile);
 	bool getPath2(int3 dest, CGPath &ret);
+	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;
+	void recalculatePaths(); //updates pathfinder info (should be called when moving hero is over)
 	bool getHeroInfo(const CGObjectInstance *hero, InfoAboutHero &dest) const;
 	bool getTownInfo(const CGObjectInstance *town, InfoAboutTown &dest) const;
 
@@ -282,7 +286,7 @@ public:
 	int battleGetPos(int stack); //returns position (tile ID) of stack
 	int battleMakeAction(BattleAction* action);//for casting spells by hero - DO NOT use it for moving active stack
 	std::map<int, CStack> battleGetStacks(); //returns stacks on battlefield
-	std::vector<CStack> battleGetStackQueue(); //returns vector of stack in order of their move sequence
+	void getStackQueue( std::vector<const CStack *> &out, int howMany ); //returns vector of stack in order of their move sequence
 	CCreature battleGetCreature(int number); //returns type of creature by given number of stack
 	std::vector<int> battleGetAvailableHexes(int ID, bool addOccupiable); //reutrns numbers of hexes reachable by creature with id ID
 	bool battleIsStackMine(int ID); //returns true if stack with id ID belongs to caller

+ 1 - 1
client/AdventureMapButton.cpp

@@ -632,5 +632,5 @@ void CSlider::setAmount( int to )
 {
 	amount = to;
 	positions = to - capacity;
-	amax(positions, 1);
+	amax(positions, 0);
 }

+ 73 - 21
client/CAdvmapInterface.cpp

@@ -545,9 +545,9 @@ void CTerrainRect::clickLeft(tribool down, bool previousState)
 		else if(mp.z == currentHero->pos.z) //remove old path and find a new one if we clicked on the map level on which hero is present
 		{
 			int3 bufpos = currentHero->getPosition(false);
-			CPath &path = LOCPLINT->adventureInt->paths[currentHero];
+			CGPath &path = LOCPLINT->adventureInt->paths[currentHero];
 			currentPath = &path;
-			if(!LOCPLINT->cb->getPath(bufpos, mp, currentHero, path))
+			if(!LOCPLINT->cb->getPath2(mp, path))
 			{
 				LOCPLINT->adventureInt->paths.erase(currentHero);
 				currentPath = NULL;
@@ -799,7 +799,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect)
 			 * 7 8 9
 			 * ie. 157 means an arrow from left upper tile to left bottom tile through 5 (all arrows go through 5 in this notation)
 			*/
-			std::vector<CPathNode> & cv = currentPath->nodes;
+			std::vector<CGPathNode> & cv = currentPath->nodes;
 			if (cv[i+1].coord.x == cv[i].coord.x-1 && cv[i+1].coord.y == cv[i].coord.y-1) //15x
 			{
 				if(cv[i-1].coord.x == cv[i].coord.x+1 && cv[i-1].coord.y == cv[i].coord.y) //156
@@ -1042,7 +1042,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect)
 			}
 
 		}
-		if (  ((currentPath->nodes[i].dist)-(*(currentPath->nodes.end()-1)).dist) > (static_cast<const CGHeroInstance*>(LOCPLINT->adventureInt->selection))->movement)
+		if (currentPath->nodes[i].turns)
 			pn+=25;
 		if (pn>=0)
 		{
@@ -1712,8 +1712,9 @@ void CAdvMapInt::centerOn(int3 on)
 }
 void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 {
-	ui8 Dir;
-	switch(key.keysym.sym)
+	ui8 Dir = 0;
+	int k = key.keysym.sym;
+	switch(k)
 	{
 	case SDLK_i: 
 		if(active)
@@ -1723,18 +1724,6 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		if(active)
 			GH.pushInt(new CSelectionScreen(saveGame));
 		return;
-	case SDLK_UP: 
-		Dir = UP;
-		break;
-	case SDLK_LEFT: 
-		Dir = LEFT;
-		break;
-	case SDLK_RIGHT: 
-		Dir = RIGHT;
-		break;
-	case SDLK_DOWN: 
-		Dir = DOWN;
-		break;
 	case SDLK_SPACE: //space - try to revisit current object with selected hero
 		{
 			if(!active) 
@@ -1777,9 +1766,70 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 			return;
 		}
 	default: 
+		{
+			static const int3 directions[] = {  int3(-1, +1, 0), int3(0, +1, 0), int3(+1, +1, 0),
+												int3(-1, 0, 0),  int3(0, 0, 0),  int3(+1, 0, 0),
+												int3(-1, -1, 0), int3(0, -1, 0), int3(+1, -1, 0) };
+
+			//numpad arrow
+			if(isArrowKey(SDLKey(k)))
+			{
+				switch(k)
+				{
+				case SDLK_UP: 
+					Dir = UP;
+					break;
+				case SDLK_LEFT: 
+					Dir = LEFT;
+					break;
+				case SDLK_RIGHT: 
+					Dir = RIGHT;
+					break;
+				case SDLK_DOWN: 
+					Dir = DOWN;
+					break;
+				}
+
+				k = arrowToNum(SDLKey(k));
+			}
+
+			if(!active)
+				break;
+
+			k -= SDLK_KP0 + 1;
+			if(k < 0 || k > 8 || key.state != SDL_PRESSED)
+				return;
+
+
+			const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(selection);
+			if(!h) break;
+			if(k == 4)
+			{
+				centerOn(h->getPosition(false));
+				return;
+			}
+
+			int3 dir = directions[k];
+
+			CGPath &path = paths[h];
+			terrain.currentPath = &path;
+			if(!LOCPLINT->cb->getPath2(h->getPosition(false) + dir, path))
+			{
+				terrain.currentPath = NULL;
+				return;
+			}
+
+			if(!path.nodes[0].turns)
+			{
+				LOCPLINT->pim->unlock();
+				LOCPLINT->moveHero(h, path);
+				LOCPLINT->pim->lock();
+			}
+		}
+
 		return;
 	}
-	if(key.state == SDL_PRESSED //arrow is pressed
+	if(Dir && key.state == SDL_PRESSED //arrow is pressed
 		&& (SDL_GetKeyState(NULL)[SDLK_LCTRL] 
 			|| SDL_GetKeyState(NULL)[SDLK_RCTRL])
 	)
@@ -1816,6 +1866,7 @@ int3 CAdvMapInt::verifyPos(int3 ver)
 void CAdvMapInt::select(const CArmedInstance *sel )
 {
 	LOCPLINT->cb->setSelection(sel);
+
 	centerOn(sel->pos);
 	selection = sel;
 
@@ -1834,9 +1885,10 @@ void CAdvMapInt::select(const CArmedInstance *sel )
 
 		if(vstd::contains(paths,h)) //hero has assigned path
 		{
-			CPath &path = paths[h];
+			CGPath &path = paths[h];
+			assert(h->getPosition(false) == path.startPos()); 
 			//update the hero path in case of something has changed on map
-			if(LOCPLINT->cb->getPath(path.startPos(), path.endPos(), h, path))
+			if(LOCPLINT->cb->getPath2(path.endPos(), path))
 				terrain.currentPath = &path;
 			else
 				paths.erase(h);

+ 3 - 3
client/CAdvmapInterface.h

@@ -8,7 +8,7 @@
 #include "GUIClasses.h"
 class CDefHandler;
 class CCallback;
-struct CPath;
+struct CGPath;
 class CAdvMapInt;
 class CGHeroInstance;
 class CGTownInstance;
@@ -80,7 +80,7 @@ public:
 	CDefHandler * arrows;
 	CTerrainRect();
 	~CTerrainRect();
-	CPath * currentPath;
+	CGPath * currentPath;
 	void activate();
 	void deactivate();
 	void clickLeft(tribool down, bool previousState);
@@ -173,7 +173,7 @@ public:
 	CHeroWindow * heroWindow;
 
 	const CArmedInstance *selection; //currently selected town/hero
-	std::map<const CGHeroInstance *, CPath> paths; //maps hero => selected path in adventure map
+	std::map<const CGHeroInstance *, CGPath> paths; //maps hero => selected path in adventure map
 
 	//functions bound to buttons
 	void fshowOverview();

+ 33 - 37
client/CBattleInterface.cpp

@@ -124,7 +124,7 @@ CBattleInterface::CBattleInterface(CCreatureSet * army1, CCreatureSet * army2, C
 		background = BitmapHandler::loadBitmap(backref[ rand() % backref.size()] );
 	}
 	
-	//preparign menu background
+	//preparing menu background
 	menu = BitmapHandler::loadBitmap("CBAR.BMP");
 	graphics->blueToPlayersAdv(menu, hero1->tempOwner);
 
@@ -560,49 +560,46 @@ void CBattleInterface::show(SDL_Surface * to)
 	//showing queue of stacks
 	if(showStackQueue)
 	{
+		const int QUEUE_SIZE = 10;
+
 		int xPos = screen->w/2 - ( stacks.size() * 37 )/2;
 		int yPos = (screen->h - 600)/2 + 10;
 
-		std::vector<CStack> stacksSorted;
-		stacksSorted = LOCPLINT->cb->battleGetStackQueue();
-		int startFrom = -1;
-		for(size_t n=0; n<stacksSorted.size(); ++n)
-		{
-			if(stacksSorted[n].ID == activeStack)
-			{
-				startFrom = n;
-				break;
-			}
-		}
-		if(startFrom != -1)
+		std::vector<const CStack *> stacksSorted;
+// 		const CStack *curStack = LOCPLINT->cb->battleGetStackByID(activeStack);
+// 		if(curStack)
+// 			stacksSorted.push_back(curStack);
+		LOCPLINT->cb->getStackQueue(stacksSorted, QUEUE_SIZE);
+
+
+		for(size_t b=0; b < stacksSorted.size(); ++b)
 		{
-			for(size_t b=startFrom; b<stacksSorted.size()+startFrom; ++b)
+			const CStack * s = stacksSorted[b];
+			SDL_BlitSurface(graphics->smallImgs[-2], NULL, to, &genRect(32, 32, xPos, yPos));
+
+			//printing colored border
+			for(int xFrom = xPos-1; xFrom<xPos+33; ++xFrom)
 			{
-				SDL_BlitSurface(graphics->smallImgs[-2], NULL, to, &genRect(32, 32, xPos, yPos));
-				//printing colored border
-				for(int xFrom = xPos-1; xFrom<xPos+33; ++xFrom)
+				for(int yFrom = yPos-1; yFrom<yPos+33; ++yFrom)
 				{
-					for(int yFrom = yPos-1; yFrom<yPos+33; ++yFrom)
+					if(xFrom == xPos-1 || xFrom == xPos+32 || yFrom == yPos-1 || yFrom == yPos+32)
 					{
-						if(xFrom == xPos-1 || xFrom == xPos+32 || yFrom == yPos-1 || yFrom == yPos+32)
+						SDL_Color pc;
+						if(s->owner != 255)
 						{
-							SDL_Color pc;
-							if(stacksSorted[b % stacksSorted.size()].owner != 255)
-							{
-								pc = graphics->playerColors[stacksSorted[b % stacksSorted.size()].owner];
-							}
-							else
-							{
-								pc = *graphics->neutralColor;
-							}
-							CSDL_Ext::SDL_PutPixelWithoutRefresh(to, xFrom, yFrom, pc.r, pc.g, pc.b);
+							pc = graphics->playerColors[s->owner];
+						}
+						else
+						{
+							pc = *graphics->neutralColor;
 						}
+						CSDL_Ext::SDL_PutPixelWithoutRefresh(to, xFrom, yFrom, pc.r, pc.g, pc.b);
 					}
 				}
-				//colored border printed
-				SDL_BlitSurface(graphics->smallImgs[stacksSorted[b % stacksSorted.size()].creature->idNumber], NULL, to, &genRect(32, 32, xPos, yPos));
-				xPos += 37;
 			}
+			//colored border printed
+			SDL_BlitSurface(graphics->smallImgs[s->creature->idNumber], NULL, to, &genRect(32, 32, xPos, yPos));
+			xPos += 37;
 		}
 	}
 
@@ -2478,12 +2475,11 @@ void CBattleInterface::showAliveStack(int ID, const std::map<int, CStack> & stac
 		}
 		SDL_BlitSurface(amountBG, NULL, to, &genRect(amountNormal->h, amountNormal->w, creAnims[ID]->pos.x + xAdd + pos.x, creAnims[ID]->pos.y + 260 + pos.y));
 		//blitting amount
-		CSDL_Ext::printAtMiddleWB(
+		CSDL_Ext::printAtMiddle(
 			makeNumberShort(curStack.amount),
-			creAnims[ID]->pos.x + xAdd + 14 + pos.x,
-			creAnims[ID]->pos.y + 260 + 4 + pos.y,
-			GEOR13,
-			20,
+			creAnims[ID]->pos.x + xAdd + 15 + pos.x,
+			creAnims[ID]->pos.y + 260 + 5 + pos.y,
+			FONT_TINY,
 			zwykly,
 			to
         );

+ 1 - 1
client/CBattleInterface.h

@@ -150,7 +150,7 @@ struct BattleSettings
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & printCellBorders & printStackRange & printMouseShadow;
+		h & printCellBorders & printStackRange & animSpeed & printMouseShadow;
 	}
 };
 

+ 1 - 1
client/CCastleInterface.cpp

@@ -992,7 +992,7 @@ void CCastleInterface::enterTavern()
 
 void CCastleInterface::keyPressed( const SDL_KeyboardEvent & key )
 {
-	if(key.state != SDL_RELEASED) return;
+	if(key.state != SDL_PRESSED) return;
 
 	switch(key.keysym.sym)
 	{

+ 3 - 0
client/CMT.cpp

@@ -163,6 +163,9 @@ int _tmain(int argc, _TCHAR* argv[])
 int main(int argc, char** argv)
 #endif
 {
+	putenv("SDL_VIDEO_WINDOW_POS");
+	putenv("SDL_VIDEO_CENTERED=1");
+
 	tlog0 << "Starting... " << std::endl;
 	timeHandler total, pomtime;
 	std::cout.flags(std::ios::unitbuf);

+ 2 - 5
client/CPlayerInterface.cpp

@@ -1415,7 +1415,7 @@ void CPlayerInterface::redrawHeroWin(const CGHeroInstance * hero)
 		adventureInt->infoBar.draw(screen);
 }
 
-bool CPlayerInterface::moveHero( const CGHeroInstance *h, CPath path )
+bool CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 {
 	if (!h)
 		return false; //can't find hero
@@ -1442,10 +1442,6 @@ bool CPlayerInterface::moveHero( const CGHeroInstance *h, CPath path )
 		// TODO
 		if (hero is flying && sh == -1)
 			sh = CGI->soundh->playSound(soundBase::horseFlying, -1);
-		} 
-		else if (hero is in a boat && sh = -1) {
-			sh = CGI->soundh->playSound(soundBase::sound_todo, -1);
-		} else
 #endif
 		{
 			newTerrain = cb->getTileInfo(CGHeroInstance::convertPosition(path.nodes[i].coord, false))->tertype;
@@ -1468,6 +1464,7 @@ bool CPlayerInterface::moveHero( const CGHeroInstance *h, CPath path )
 	CGI->soundh->stopSound(sh);
 
 	//stillMoveHero = false;
+	cb->recalculatePaths();
 	return result;
 }
 

+ 2 - 2
client/CPlayerInterface.h

@@ -42,7 +42,7 @@ class CStack;
 class SComponent;
 class CCreature;
 struct SDL_Surface;
-struct CPath;
+struct CGPath;
 class CCreatureAnimation;
 class CSelectableComponent;
 class CCreatureSet;
@@ -198,7 +198,7 @@ public:
 	int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
 	void showInfoDialog(const std::string &text, const std::vector<SComponent*> & components = std::vector<SComponent*>(), int soundID = 0);
 	void showYesNoDialog(const std::string &text, const std::vector<SComponent*> & components, CFunctionList<void()> onYes, CFunctionList<void()> onNo, bool DelComps); //deactivateCur - whether current main interface should be deactivated; delComps - if components will be deleted on window close
-	bool moveHero(const CGHeroInstance *h, CPath path);
+	bool moveHero(const CGHeroInstance *h, CGPath path);
 
 	CPlayerInterface(int Player, int serial);//c-tor
 	~CPlayerInterface();//d-tor

+ 5 - 2
client/CPreGame.cpp

@@ -977,7 +977,7 @@ void InfoCard::showAll( SDL_Surface * to )
 
 void InfoCard::changeSelection( const CMapInfo *to )
 {
-	if(to/* && type != newGame*/)
+	if(to && type != newGame)
 		difficulty->select(curOpts->difficulty, 0);
 	GH.totalRedraw();
 }
@@ -1257,6 +1257,9 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSet
 		btns[4] = new AdventureMapButton(CGI->generaltexth->zelp[164], bind(&OptionsTab::nextBonus, owner, s.serial, -1), 259, 5, "ADOPLFA.DEF");
 		btns[5] = new AdventureMapButton(CGI->generaltexth->zelp[165], bind(&OptionsTab::nextBonus, owner, s.serial, +1), 320, 5, "ADOPRTA.DEF");
 	}
+	else
+		for(int i = 0; i < 6; i++)
+			btns[i] = NULL;
 
 	fixedHero = s.hero != -1; //if we doesn't start with "random hero" it must be fixed or none
 	selectButtons(false);
@@ -1287,7 +1290,7 @@ void OptionsTab::PlayerOptionsEntry::showAll( SDL_Surface * to )
 
 void OptionsTab::PlayerOptionsEntry::selectButtons(bool onlyHero)
 {
-	if(type != newGame)
+	if(!btns[0])
 		return;
 
 	if(!onlyHero  &&  s.castle != -1)

+ 37 - 5
client/GUIBase.cpp

@@ -150,11 +150,7 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
 		SDL_KeyboardEvent key = sEvent->key;
 
 		//translate numpad keys
-		if (key.keysym.sym >= SDLK_KP0  && key.keysym.sym <= SDLK_KP9)
-		{
-			key.keysym.sym = (SDLKey) (key.keysym.sym - SDLK_KP0 + SDLK_0);
-		}
-		else if(key.keysym.sym == SDLK_KP_ENTER)
+		if(key.keysym.sym == SDLK_KP_ENTER)
 		{
 			key.keysym.sym = (SDLKey)SDLK_RETURN;
 		}
@@ -700,4 +696,40 @@ void IShowable::redraw()
 	showAll(screenBuf);
 	if(screenBuf != screen)
 		showAll(screen);
+}
+
+SDLKey arrowToNum( SDLKey key )
+{
+	switch(key)
+	{
+	case SDLK_DOWN:
+		return SDLK_KP2;
+	case SDLK_UP:
+		return SDLK_KP8;
+	case SDLK_LEFT:
+		return SDLK_KP4;
+	case SDLK_RIGHT:
+		return SDLK_KP6;
+	default:
+		assert(0);
+	}
+	throw std::string("Wrong key!");
+}
+
+SDLKey numToDigit( SDLKey key )
+{
+	return SDLKey(key - SDLK_KP0 + SDLK_0);
+}
+
+bool isNumKey( SDLKey key, bool number )
+{
+	if(number)
+		return key >= SDLK_KP0 && key <= SDLK_KP_EQUALS;
+	else
+		return key >= SDLK_KP0 && key <= SDLK_KP9;
+}
+
+bool isArrowKey( SDLKey key )
+{
+	return key >= SDLK_UP && key <= SDLK_LEFT;
 }

+ 5 - 0
client/GUIBase.h

@@ -442,6 +442,11 @@ public:
 	std::list<CIntObject *> createdObj; //stack of objs being created
 };
 
+SDLKey arrowToNum(SDLKey key); //converts arrow key to according numpad key
+SDLKey numToDigit(SDLKey key);//converts numpad digit key to normal digit key
+bool isNumKey(SDLKey key, bool number = true); //checks if key is on numpad (numbers - check only for numpad digits)
+bool isArrowKey(SDLKey key); 
+
 extern CGuiHandler GH; //global gui handler
 
 struct ObjectConstruction

+ 19 - 10
client/GUIClasses.cpp

@@ -41,6 +41,7 @@
 #include "CHeroWindow.h"
 #include "../hch/CVideoHandler.h"
 #include "../StartInfo.h"
+#include "CPreGame.h"
 
 /*
  * GUIClasses.cpp, part of VCMI engine
@@ -1308,11 +1309,13 @@ void CHeroList::updateHList(const CGHeroInstance *toRemove)
 	if(selected >= heroes.size())
 		select(heroes.size()-1);
 
-
-	if(heroes.size() == 0)
-		LOCPLINT->adventureInt->townList.select(0);
-	else
-		select(selected);
+	if(toRemove)
+	{
+		if(heroes.size() == 0)
+			LOCPLINT->adventureInt->townList.select(0);
+		else
+			select(selected);
+	}
 }
 
 void CHeroList::updateMove(const CGHeroInstance* which) //draws move points bar
@@ -1990,23 +1993,27 @@ void CSplitWindow::show(SDL_Surface * to)
 
 void CSplitWindow::keyPressed (const SDL_KeyboardEvent & key)
 {
+	SDLKey k = key.keysym.sym;
+	if (isNumKey(k)) //convert numpad number to normal digit
+		k = numToDigit(k); 
+
 	if(key.state != SDL_PRESSED)
 		return;
 
 	int &cur = (which ? a2 : a1), 
 		&sec = (which ? a1 : a2), 
 		ncur = cur;
-	if (key.keysym.sym == SDLK_BACKSPACE)
+	if (k == SDLK_BACKSPACE)
 	{
 		ncur /= 10;
 	}
-	else if(key.keysym.sym == SDLK_TAB)
+	else if(k == SDLK_TAB)
 	{
 		which = !which;
 	}
 	else
 	{
-		int number = key.keysym.sym - SDLK_0;
+		int number = k - SDLK_0;
 		if (number < 0   ||   number > 9) //not a number pressed
 		{
 			return;
@@ -2811,13 +2818,15 @@ void CSystemOptionsWindow::breturnf()
 
 void CSystemOptionsWindow::bsavef()
 {
-	using namespace boost::posix_time;
+	GH.popIntTotally(this);
+	GH.pushInt(new CSelectionScreen(saveGame));
+	/*using namespace boost::posix_time;
 	std::ostringstream fnameStream;
 	fnameStream << second_clock::local_time();
 	std::string fname = fnameStream.str();
 	boost::algorithm::replace_all(fname,":","");
 	boost::algorithm::replace_all(fname," ","-");
-	LOCPLINT->showYesNoDialog("Do you want to save current game as " + fname, std::vector<SComponent*>(), boost::bind(&CCallback::save, LOCPLINT->cb, fname), boost::bind(&CSystemOptionsWindow::activate, this), false);
+	LOCPLINT->showYesNoDialog("Do you want to save current game as " + fname, std::vector<SComponent*>(), boost::bind(&CCallback::save, LOCPLINT->cb, fname), boost::bind(&CSystemOptionsWindow::activate, this), false);*/
 }
 
 void CSystemOptionsWindow::activate()

+ 0 - 9
client/NetPacksClient.cpp

@@ -500,15 +500,6 @@ void EndAction::applyCl( CClient *cl )
 void PackageApplied::applyCl( CClient *cl )
 {
 	ui8 player = GS(cl)->currentPlayer;
-
-	if(packType == typeList.getTypeID((MoveHero*)NULL))
-	{
-		//we've finished moving hero - paths info must be updated
-		const CGHeroInstance *h = cl->IGameCallback::getSelectedHero(player);
-		if(h)
-			GS(cl)->calculatePaths(h, *cl->pathInfo);
-	}
-
 	INTERFACE_CALL_IF_PRESENT(player, requestRealized, this);
 	if(cl->waitingRequest.get())
 		cl->waitingRequest.setn(false);

+ 2 - 1
global.h

@@ -301,7 +301,8 @@ extern DLL_EXPORT CLogger<5> tlog5; //gray - minor log info
 //XXX pls dont - 'debug macros' are usually more trubble then its worth
 #define HANDLE_EXCEPTION  \
 	catch (const std::exception& e) {	\
-	tlog1 << e.what() << std::endl;	\
+	tlog1 << e.what() << std::endl;		\
+	throw;								\
 	}									\
 	catch (const std::exception * e)	\
 	{									\

+ 7 - 0
hch/CCreatureHandler.h

@@ -57,6 +57,11 @@ public:
 		soundBase::soundID ext2;  // creature specific extension
 		soundBase::soundID startMoving; // usually same as ext1
 		soundBase::soundID endMoving;	// usually same as ext2
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & attack & defend & killed & move & shoot & wince & ext1 & ext2 & startMoving & endMoving;
+		}
 	} sounds;
 
 	bool isDoubleWide() const; //returns true if unit is double wide on battlefield
@@ -89,6 +94,8 @@ public:
 			& timeBetweenFidgets & walkAnimationTime & attackAnimationTime & flightAnimationDistance
 			& upperRightMissleOffsetX & rightMissleOffsetX & lowerRightMissleOffsetX & upperRightMissleOffsetY & rightMissleOffsetY & lowerRightMissleOffsetY
 			& missleFrameAngles & troopCountLocationOffset & attackClimaxFrame;
+
+		h & sounds;
 	}
 };
 

+ 63 - 14
hch/CObjectHandler.cpp

@@ -35,6 +35,7 @@ using namespace boost::assign;
  */
 
 std::map<int,std::map<int, std::vector<int> > > CGTeleport::objs;
+std::vector<std::pair<int, int> > CGTeleport::gates;
 IGameCallback * IObjectInterface::cb = NULL;
 DLL_EXPORT void loadToIt(std::string &dest, std::string &src, int &iter, int mode);
 extern CLodHandler * bitmaph;
@@ -64,6 +65,12 @@ void IObjectInterface::initObj()
 void IObjectInterface::setProperty( ui8 what, ui32 val )
 {}
 
+void IObjectInterface::postInit()
+{}
+
+void IObjectInterface::preInit()
+{}
+
 void CPlayersVisited::setPropertyDer( ui8 what, ui32 val )
 {
 	if(what == 10)
@@ -2476,27 +2483,25 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const
 		break;
 	case 103: //find nearest subterranean gate on the other level
 		{
-			std::pair<int,double> best(-1,150000); //pair<id,dist>
-			for(int i=0; i<objs[103][0].size(); i++)
+			for(int i=0; i < gates.size(); i++)
 			{
-				if(cb->getObj(objs[103][0][i])->pos.z == pos.z) continue; //gates on our level are not interesting
-				double hlp = cb->getObj(objs[103][0][i])->pos.dist2d(pos);
-				if(hlp<best.second)
+				if(gates[i].first == id)
+				{
+					destinationid = gates[i].second;
+					break;
+				}
+				else if(gates[i].second == id)
 				{
-					best.first = objs[103][0][i];
-					best.second = hlp;
+					destinationid = gates[i].first;
+					break;
 				}
 			}
-			if(best.first<0)
-				return;
-			else 
-				destinationid = best.first;
 			break;
 		}
 	}
 	if(destinationid < 0)
 	{
-		tlog2 << "Cannot find exit... :( \n";
+		tlog2 << "Cannot find exit... (obj at " << pos << ") :( \n";
 		return;
 	}
 	cb->moveHero(h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(),
@@ -2505,7 +2510,51 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const
 
 void CGTeleport::initObj()
 {
-	objs[ID][subID].push_back(id);
+	int si = subID;
+	if(ID == 103) //ignore subterranean gates subid
+		si = 0;
+
+	objs[ID][si].push_back(id);
+}
+
+void CGTeleport::postInit() //matches subterranean gates into pairs
+{
+	//split on underground and surface gates
+	std::vector<const CGObjectInstance *> gatesSplit[2]; //surface and underground gates
+	for(size_t i = 0; i < objs[103][0].size(); i++)
+	{
+		const CGObjectInstance *hlp = cb->getObj(objs[103][0][i]);
+		gatesSplit[hlp->pos.z].push_back(hlp);
+	}
+
+	//sort by position
+	std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), boost::bind(&CGObjectInstance::pos, _1) < boost::bind(&CGObjectInstance::pos, _2));
+
+	for(size_t i = 0; i < gatesSplit[0].size(); i++)
+	{
+		const CGObjectInstance *cur = gatesSplit[0][i];
+
+		//find nearest underground exit
+		std::pair<int,double> best(-1,150000); //pair<pos_in_vector, distance>
+		for(int j = 0; j < gatesSplit[1].size(); j++)
+		{
+			const CGObjectInstance *checked = gatesSplit[1][j];
+			if(!checked)
+				continue;
+			double hlp = checked->pos.dist2d(cur->pos);
+			if(hlp < best.second)
+			{
+				best.first = j;
+				best.second = hlp;
+			}
+		}
+
+		gates.push_back(std::pair<int, int>(cur->id, gatesSplit[1][best.first]->id));
+		gatesSplit[1][best.first] = NULL;
+	}
+
+
+	objs.erase(103);
 }
 
 void CGArtifact::initObj()
@@ -4042,7 +4091,7 @@ void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) cons
 			int slot = ourArmy.getSlotFor (it->second);
 			ourArmy.slots[slot] = *it; //assuming we're not going to add multiple stacks of same creature
 		}
-		cb->giveCreatures (id, cb->getHero (h->getOwner()), &ourArmy);
+		cb->giveCreatures (id, h, &ourArmy);
 		cb->setObjProperty (id, 15, 0); //bc = NULL
 	}
 	else

+ 7 - 12
hch/CObjectHandler.h

@@ -103,6 +103,9 @@ public:
 	virtual void newTurn() const;
 	virtual void initObj(); //synchr
 	virtual void setProperty(ui8 what, ui32 val);//synchr
+
+	static void preInit(); //called before objs receive their initObj
+	static void postInit();//caleed after objs receive their initObj
 };
 
 class DLL_EXPORT IShipyard
@@ -121,7 +124,7 @@ public:
 	static IShipyard *castFrom(CGObjectInstance *obj);
 };
 
-class DLL_EXPORT CGObjectInstance : protected IObjectInterface
+class DLL_EXPORT CGObjectInstance : public IObjectInterface
 {
 protected:
 	void getNameVis(std::string &hname) const;
@@ -388,16 +391,6 @@ public:
 	std::vector<CGTownBuilding*> bonusingBuildings;
 	std::vector<ui32> possibleSpells, obligatorySpells;
 	std::vector<std::vector<ui32> > spells; //spells[level] -> vector of spells, first will be available in guild
-
-	//struct StrInfo
-	//{
-	//	std::map<si32,ui32> creatures; //level - available amount
-
-	//	template <typename Handler> void serialize(Handler &h, const int version)
-	//	{
-	//		h & creatures;
-	//	}
-	//} strInfo;
 	std::set<CCastleEvent> events;
 
 	//////////////////////////////////////////////////////////////////////////
@@ -723,9 +716,11 @@ public:
 class DLL_EXPORT CGTeleport : public CGObjectInstance //teleports and subterranean gates
 {
 public:
-	static std::map<int,std::map<int, std::vector<int> > > objs; //map[ID][subID] => vector of ids
+	static std::map<int,std::map<int, std::vector<int> > > objs; //teleports: map[ID][subID] => vector of ids
+	static std::vector<std::pair<int, int> > gates; //subterranean gates: pairs of ids
 	void onHeroVisit(const CGHeroInstance * h) const;
 	void initObj();	
+	static void postInit();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 272 - 93
lib/CGameState.cpp

@@ -90,6 +90,61 @@ public:
 
 } *applierGs = NULL;
 
+class IObjectCaller
+{
+public:
+	virtual void preInit()=0;
+	virtual void postInit()=0;
+};
+
+template <typename T>
+class CObjectCaller : public IObjectCaller
+{
+public:
+	void preInit()
+	{
+		T::preInit();
+	}
+	void postInit()
+	{
+		T::postInit();
+	}
+};
+
+class CObjectCallersHandler
+{
+public:
+	std::vector<IObjectCaller*> apps; 
+
+	template<typename T> void registerType(const T * t=NULL)
+	{
+		apps.push_back(new CObjectCaller<T>);
+	}
+
+	CObjectCallersHandler()
+	{
+		registerTypes1(*this);
+	}
+
+	~CObjectCallersHandler()
+	{
+		for (size_t i = 0; i < apps.size(); i++)
+			delete apps[i];
+	}
+
+	void preInit()
+	{
+		for (size_t i = 0; i < apps.size(); i++)
+			apps[i]->preInit();
+	}
+
+	void postInit()
+	{
+		for (size_t i = 0; i < apps.size(); i++)
+			apps[i]->postInit();
+	}
+} *objCaller = NULL;
+
 void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst) const
 {
 	int type = txt.first, ser = txt.second;
@@ -252,12 +307,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
 
 const CStack * BattleInfo::getStack(int stackID, bool onlyAlive) const
 {
-	for(unsigned int g=0; g<stacks.size(); ++g)
-	{
-		if(stacks[g]->ID == stackID && (!onlyAlive || stacks[g]->alive()))
-			return stacks[g];
-	}
-	return NULL;
+	return const_cast<BattleInfo * const>(this)->getStack(stackID, onlyAlive);
 }
 
 CStack * BattleInfo::getStackT(int tileID, bool onlyAlive)
@@ -279,19 +329,7 @@ CStack * BattleInfo::getStackT(int tileID, bool onlyAlive)
 
 const CStack * BattleInfo::getStackT(int tileID, bool onlyAlive) const
 {
-	for(unsigned int g=0; g<stacks.size(); ++g)
-	{
-		if(stacks[g]->position == tileID 
-			|| (stacks[g]->hasFeatureOfType(StackFeature::DOUBLE_WIDE) && stacks[g]->attackerOwned && stacks[g]->position-1 == tileID)
-			|| (stacks[g]->hasFeatureOfType(StackFeature::DOUBLE_WIDE) && !stacks[g]->attackerOwned && stacks[g]->position+1 == tileID))
-		{
-			if(!onlyAlive || stacks[g]->alive())
-			{
-				return stacks[g];
-			}
-		}
-	}
-	return NULL;
+	return const_cast<BattleInfo * const>(this)->getStackT(tileID, onlyAlive);
 }
 
 void BattleInfo::getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set<int> & occupyable, bool flying, int stackToOmmit) const
@@ -749,14 +787,24 @@ ui16 CStack::MaxHealth() const
 	return creature->hitPoints + valOfFeatures(StackFeature::HP_BONUS);
 }
 
-bool CStack::willMove()
+bool CStack::willMove() const
 {
 	return !vstd::contains(state, DEFENDING)
-		&& !vstd::contains(state, MOVED)
-		&& alive()
+		&& !moved()
+		&& canMove();
+}
+
+bool CStack::canMove() const
+{
+	return alive()
 		&& ! hasFeatureOfType(StackFeature::NOT_ACTIVE); //eg. Ammo Cart
 }
 
+bool CStack::moved() const
+{
+	return vstd::contains(state, MOVED);
+}
+
 CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, int player, const CTown *town, std::map<ui32,CGHeroInstance *> &available) const
 {
 	CGHeroInstance *ret = NULL;
@@ -1105,6 +1153,7 @@ CGameState::CGameState()
 	curB = NULL;
 	scenarioOps = NULL;
 	applierGs = new CGSApplier;
+	objCaller = new CObjectCallersHandler;
 }
 CGameState::~CGameState()
 {
@@ -1113,6 +1162,7 @@ CGameState::~CGameState()
 	delete curB;
 	delete scenarioOps;
 	delete applierGs;
+	delete objCaller;
 }
 void CGameState::init(StartInfo * si, Mapa * map, int Seed)
 {
@@ -1456,12 +1506,14 @@ void CGameState::init(StartInfo * si, Mapa * map, int Seed)
 		map->defy[i]->serial = i;
 	}
 
+	objCaller->preInit();
 	for(unsigned int i=0; i<map->objects.size(); i++)
 	{
 		map->objects[i]->initObj();
 		if(map->objects[i]->ID == 62) //prison also needs to initialize hero
 			static_cast<CGHeroInstance*>(map->objects[i])->initHero();
 	}
+	objCaller->postInit();
 }
 
 bool CGameState::battleShootCreatureStack(int ID, int dest)
@@ -1915,11 +1967,15 @@ bool CGameState::getPath(int3 src, int3 dest, const CGHeroInstance * hero, CPath
 
 void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src, int movement)
 {
+	assert(hero);
 	if(src.x < 0)
 		src = hero->getPosition(false);
 	if(movement < 0)
 		movement = hero->movement;
 
+	out.hero = hero;
+	out.hpos = src;
+
 	if(!map->isInTheMap(src)/* || !map->isInTheMap(dest)*/) //check input
 	{
 		tlog1 << "CGameState::calculatePaths: Hero outside the map? How dare you...\n";
@@ -1953,6 +2009,7 @@ void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int
 				node.coord.y = j;
 				node.coord.z = k;
 				node.land = tinfo->tertype != TerrainTile::water;
+				node.theNodeBefore = NULL;
 
 				if ( tinfo->tertype == TerrainTile::rock//it's rock
 					|| onLand  && !node.land		//it's sea and we cannot walk on sea
@@ -1988,7 +2045,7 @@ void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int
 				else if(!onLand && tinfo->tertype != TerrainTile::water) //hero is moving by water
 				{
 					if((tinfo->siodmyTajemniczyBajt & 64) && !tinfo->blocked)
-						node.accessible = CGPathNode::ACCESSIBLE; //tile is accessible if it's coastal and not blocked
+						node.accessible = CGPathNode::VISITABLE; //tile is accessible if it's coastal and not blocked
 				}
 			}
 		}
@@ -2213,7 +2270,7 @@ std::pair<ui32, ui32> BattleInfo::calculateDmgRange(const CStack* attacker, cons
 				affectedIds.push_back(151); //diamond dragon
 				affectedIds.push_back(154); //blood dragon
 				affectedIds.push_back(155); //darkness dragon
-				affectedIds.push_back(156); //ghost behemot
+				affectedIds.push_back(156); //ghost behemoth
 				affectedIds.push_back(157); //hell hydra
 				break;
 			}
@@ -2412,17 +2469,17 @@ si8 CGameState::battleMaxSpellLevel()
 std::set<CStack*> BattleInfo::getAttackedCreatures(const CSpell * s, const CGHeroInstance * caster, int destinationTile)
 {
 	std::set<ui16> attackedHexes = s->rangeInHexes(destinationTile, caster->getSpellSchoolLevel(s));
-	std::set<CStack*> attackedCres; /*std::set to exclude multiple occurences of two hex creatures*/
+	std::set<CStack*> attackedCres; /*std::set to exclude multiple occurrences of two hex creatures*/
 
 	bool onlyAlive = s->id != 38 && s->id != 39; //when casting resurrection or animate dead we should be allow to select dead stack
 
-	if(s->id == 24 || s->id == 25 || s->id == 26) //death ripple, destroy undead and armageddon
+	if(s->id == 24 || s->id == 25 || s->id == 26) //death ripple, destroy undead and Armageddon
 	{
 		for(int it=0; it<stacks.size(); ++it)
 		{
 			if((s->id == 24 && !stacks[it]->creature->isUndead()) //death ripple
 				|| (s->id == 25 && stacks[it]->creature->isUndead()) //destroy undead
-				|| (s->id == 26) //armageddon
+				|| (s->id == 26) //Armageddon
 				)
 			{
 				attackedCres.insert(stacks[it]);
@@ -2641,90 +2698,148 @@ bool CGameState::battleCanShoot(int ID, int dest)
 	return false;
 }
 
-CStack * BattleInfo::getNextStack()
+const CStack * BattleInfo::getNextStack() const
 {
-	CStack *current = getStack(activeStack);
-	for (unsigned int i = 0; i <  stacks.size(); i++)  //find fastest not moved/waited stack (stacks vector is sorted by speed)
+	std::vector<const CStack *> hlp;
+	getStackQueue(hlp, 1, 2);
+
+	if(hlp.size())
+		return hlp[0];
+	else
+		return NULL;
+}
+
+static const CStack *takeStack(std::vector<const CStack *> &st, int &curside)
+{
+	const CStack *ret = NULL;
+	unsigned i, //fastest stack
+		j; //fastest stack of the other side
+	for(i = 0; i < st.size(); i++)
+		if(st[i])
+			break;
+
+	//no stacks left
+	if(i == st.size())
+		return NULL;
+
+	const CStack *fastest = st[i], *other = NULL;
+	int bestSpeed = fastest->Speed();
+
+	if(fastest->attackerOwned != curside)
 	{
-		if(stacks[i]->willMove()  &&  !vstd::contains(stacks[i]->state,WAITING))
-			return stacks[i];
+		ret = fastest;
 	}
-	for (int i = stacks.size() - 1; i >= 0 ; i--) //find slowest waiting stack
+	else
 	{
-		if(stacks[i]->willMove())
-			return stacks[i];
+		for(j = i + 1; j < st.size(); j++)
+		{
+			if(!st[j]) continue;
+			if(st[j]->attackerOwned != curside || st[j]->Speed() != bestSpeed)
+				break;
+		}
+
+		if(j >= st.size())
+		{
+			ret = fastest;
+		}
+		else
+		{
+			other = st[j];
+			if(other->Speed() != bestSpeed)
+				ret = fastest;
+			else
+				ret = other;
+		}
 	}
-	return NULL; //all stacks moved or defending!
+
+	assert(ret);
+	if(ret == fastest)
+		st[i] = NULL;
+	else
+		st[j] = NULL;
+
+	curside = ret->attackerOwned;
+	return ret;
 }
 
-std::vector<CStack> BattleInfo::getStackQueue()
+void BattleInfo::getStackQueue( std::vector<const CStack *> &out, int howMany, int mode, int lastMoved ) const 
 {
-	std::vector<CStack> ret;
-	std::vector<int> taken; //if non-zero value, corresponding stack has been placed in ret
-	taken.resize(stacks.size());
-	for(unsigned int g=0; g<taken.size(); ++g)
-	{
-		taken[g] = 0;
-	}
+	//we'll split creatures with remaining movement to 4 parts
+	std::vector<const CStack *> phase[4]; //0 - turrets/catapult, 1 - normal (unmoved) creatures, other war machines, 2 - waited cres that had morale, 3 - rest of waited cres
+	int toMove = 0; //how many stacks still has move
 
-	for(int moved=0; moved<2; ++moved) //in first cycle we add stacks that can act in current turn, in second one the rest of them
+	for(unsigned int i=0; i<stacks.size(); ++i)
 	{
-		for(unsigned int gc=0; gc<stacks.size(); ++gc)
+		const CStack * const s = stacks[i];
+		if(mode != 1  &&  s->willMove()
+			|| mode == 1  &&  s->canMove())
 		{
-			int id = -1, speed = -1;
-			for(unsigned int i=0; i<stacks.size(); ++i) //find not waited stacks only
+			int p = -1; //in which phase this tack will move?
+			if(!mode && vstd::contains(s->state, WAITING))
 			{
-				if((moved == 1 ||!vstd::contains(stacks[i]->state, DEFENDING))
-					&& stacks[i]->alive()
-					&& (moved == 1 || !vstd::contains(stacks[i]->state, MOVED))
-					&& !vstd::contains(stacks[i]->state,WAITING)
-					&& taken[i]==0
-					&& !stacks[i]->hasFeatureOfType(StackFeature::NOT_ACTIVE)) //eg. Ammo Cart
-				{
-					if(speed == -1 || stacks[i]->Speed() > speed)
-					{
-						id = i;
-						speed = stacks[i]->Speed();
-					}
-				}
+				if(vstd::contains(s->state, HAD_MORALE))
+					p = 2;
+				else
+					p = 3;
 			}
-			if(id != -1)
+			else if(vstd::contains(s->state, StackFeature::SIEGE_WEAPON)	 //catapult and turrets are first
+				&& (s->creature->idNumber == 145  ||  s->creature->idNumber == 149))
 			{
-				ret.push_back(*stacks[id]);
-				taken[id] = 1;
+				p = 0;
 			}
-			else //choose something from not moved stacks
+			else
 			{
-				int id = -1, speed = 10000; //infinite speed
-				for(unsigned int i=0; i<stacks.size(); ++i) //find waited stacks only
-				{
-					if((moved == 1 ||!vstd::contains(stacks[i]->state, DEFENDING))
-						&& stacks[i]->alive()
-						&& (moved == 1 || !vstd::contains(stacks[i]->state, MOVED))
-						&& vstd::contains(stacks[i]->state,WAITING)
-						&& taken[i]==0
-						&& !stacks[i]->hasFeatureOfType(StackFeature::NOT_ACTIVE)) //eg. Ammo Cart
-					{
-						if(stacks[i]->Speed() < speed) //slowest one
-						{
-							id = i;
-							speed = stacks[i]->Speed();
-						}
-					}
-				}
-				if(id != -1)
-				{
-					ret.push_back(*stacks[id]);
-					taken[id] = 1;
-				}
-				else
-				{
-					break; //no stacks have been found, so none of them will be found in next iterations
-				}
+				p = 1;
 			}
+
+			phase[p].push_back(s);
+			toMove++;
+		}
+	}
+
+	for(int i = 0; i < 4; i++)
+		std::sort(phase[i].begin(), phase[i].end(), CMP_stack(i));
+
+	for(size_t i = 0; i < phase[0].size() && i < howMany; i++)
+		out.push_back(phase[0][i]);
+
+	if(out.size() == howMany)
+		return;
+
+	if(lastMoved == -1)
+	{
+		const CStack *current = getStack(activeStack);
+		if(current)
+		{
+			lastMoved = !current->attackerOwned;
+			if(!current->willMove() || mode == 2)
+				lastMoved = !lastMoved;
+		}
+		else
+		{
+			lastMoved = 0;
+		}
+	}
+
+	int pi = 1;
+	while(out.size() < howMany)
+	{
+		const CStack *hlp = takeStack(phase[pi], lastMoved);
+		if(!hlp)
+		{
+			pi++;
+			if(pi > 3)
+			{
+				if(mode != 2)
+					getStackQueue(out, howMany, 1, lastMoved);
+				return;
+			}
+		}
+		else
+		{
+			out.push_back(hlp);
 		}
 	}
-	return ret;
 }
 
 int3 CPath::startPos() const
@@ -2764,7 +2879,7 @@ bool CPathsInfo::getPath( const int3 &dst, CGPath &out )
 	if(!curnode->theNodeBefore)
 		return false;
 
-	while(curnode->theNodeBefore)
+	while(curnode)
 	{
 		out.nodes.push_back(*curnode);
 		curnode = curnode->theNodeBefore;
@@ -2797,4 +2912,68 @@ CPathsInfo::~CPathsInfo()
 		delete [] nodes[i];
 	}
 	delete [] nodes;
+}
+
+int3 CGPath::startPos() const
+{
+	return nodes[nodes.size()-1].coord;
+}
+
+int3 CGPath::endPos() const
+{
+	return nodes[0].coord;
+}
+
+void CGPath::convert( ui8 mode )
+{
+	if(mode==0)
+	{
+		for(unsigned int i=0;i<nodes.size();i++)
+		{
+			nodes[i].coord = CGHeroInstance::convertPosition(nodes[i].coord,true);
+		}
+	}
+}
+
+bool CMP_stack::operator()( const CStack* a, const CStack* b )
+{
+	switch(phase)
+	{
+	case 0: //catapult moves after turrets
+		return a->creature->idNumber < b->creature->idNumber; //catapult is 145 and turrets are 149
+		//TODO? turrets order
+	case 1: //fastest first, upper slot first
+		{
+			int as = a->Speed(), bs = b->Speed();
+			if(as != bs)
+				return as > bs;
+			else
+				return a->slot < b->slot;
+		}
+	case 2: //fastest last, upper slot first
+		//TODO: should be replaced with order of receiving morale!
+	case 3: //fastest last, upper slot first
+		{
+			int as = a->Speed(), bs = b->Speed();
+			if(as != bs)
+				return as < bs;
+			else
+				return a->slot < b->slot;
+		}
+	default:
+		assert(0);
+		return false;
+	}
+
+}
+
+CMP_stack::CMP_stack( int Phase /*= 1*/ )
+{
+	phase = Phase;
+}
+
+PlayerState::PlayerState() 
+ : color(-1), currentSelection(0xffffffff)
+{
+
 }

+ 19 - 4
lib/CGameState.h

@@ -66,7 +66,9 @@ public:
 	std::vector<CGHeroInstance *> heroes;
 	std::vector<CGTownInstance *> towns;
 	std::vector<CGHeroInstance *> availableHeroes; //heroes available in taverns
-	PlayerState():color(-1),currentSelection(0xffffffff){};
+
+	PlayerState();
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & color & serial & human & currentSelection & fogOfWarMap & resources;
@@ -135,8 +137,8 @@ struct DLL_EXPORT BattleInfo
 		h & side1 & side2 & round & activeStack & siege & tid & tile & stacks & army1 & army2 & hero1 & hero2 & obstacles
 			& castSpells & si;
 	}
-	CStack * getNextStack(); //which stack will have turn after current one
-	std::vector<CStack> getStackQueue(); //returns stack in order of their movement action
+	const CStack * getNextStack() const; //which stack will have turn after current one
+	void getStackQueue(std::vector<const CStack *> &out, int howMany, int mode = 0, int lastMoved = -1) const; //returns stack in order of their movement action
 	CStack * getStack(int stackID, bool onlyAlive = true);
 	const CStack * getStack(int stackID, bool onlyAlive = true) const;
 	CStack * getStackT(int tileID, bool onlyAlive = true);
@@ -196,7 +198,9 @@ public:
 	CStack() : ID(-1), creature(NULL), amount(-1), baseAmount(-1), firstHPleft(-1), owner(255), slot(255), attackerOwned(true), position(-1), counterAttacks(1) {} //c-tor
 	const StackEffect * getEffect(ui16 id) const; //effect id (SP)
 	ui8 howManyEffectsSet(ui16 id) const; //returns amount of effects with given id set for this stack
-	bool willMove(); //if stack has remaining move this turn
+	bool willMove() const; //if stack has remaining move this turn
+	bool moved() const; //if stack was already moved this turn
+	bool canMove() const; //if stack can move
 	ui32 Speed() const; //get speed of creature with all modificators
 	si8 Morale() const; //get morale of stack with all modificators
 	si8 Luck() const; //get luck of stack with all modificators
@@ -229,6 +233,15 @@ public:
 	}
 };
 
+class DLL_EXPORT CMP_stack
+{
+	int phase; //rules of which phase will be used
+public:
+
+	bool operator ()(const CStack* a, const CStack* b);
+	CMP_stack(int Phase = 1);
+};
+
 struct UpgradeInfo
 {
 	int oldID; //creature to be upgraded
@@ -279,6 +292,8 @@ struct DLL_EXPORT CGPath
 
 struct DLL_EXPORT CPathsInfo
 {
+	const CGHeroInstance *hero;
+	int3 hpos;
 	int3 sizes;
 	CGPathNode ***nodes; //[w][h][level]
 

+ 2 - 0
lib/NetPacksLib.cpp

@@ -646,6 +646,7 @@ DLL_EXPORT void BattleSetActiveStack::applyGs( CGameState *gs )
 {
 	gs->curB->activeStack = stack;
 	CStack *st = gs->curB->getStack(stack);
+	st->state -= WAITING; //if stack was waiting it'll now make move, so it won't be "waiting" anymore
 	if(vstd::contains(st->state,MOVED)) //if stack is moving second time this turn it must had a high morale bonus
 		st->state.insert(HAD_MORALE);
 }
@@ -719,6 +720,7 @@ DLL_EXPORT void BattleAttack::applyGs( CGameState *gs )
 DLL_EXPORT void StartAction::applyGs( CGameState *gs )
 {
 	CStack *st = gs->curB->getStack(ba.stackNumber);
+	assert(st);
 	switch(ba.actionType)
 	{
 	case 3:

+ 1 - 0
lib/map.h

@@ -407,6 +407,7 @@ struct DLL_EXPORT Mapa : public CMapHeader
 		}
 
 		h & CGTeleport::objs;
+		h & CGTeleport::gates;
 		h & CGKeys::playerKeyMap;
 		h & CGMagi::eyelist;
 

+ 15 - 19
server/CGameHandler.cpp

@@ -84,14 +84,7 @@ public:
 
 } *applier = NULL;
 
-class CMP_stack
-{
-public:
-	inline bool operator ()(const CStack* a, const CStack* b)
-	{
-		return (a->Speed())>(b->Speed());
-	}
-} cmpst ;
+CMP_stack cmpst ;
 
 static inline double distance(int3 a, int3 b)
 {
@@ -324,11 +317,15 @@ static CCreatureSet takeCasualties(int color, const CCreatureSet &set, BattleInf
 
 void CGameHandler::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)
 {
-	BattleInfo *curB = new BattleInfo;
-	curB->side1 = army1->tempOwner;
-	curB->side2 = army2->tempOwner;
-	if(curB->side2 == 254) curB->side2 = 255;
-	setupBattle(curB, tile, army1->army, army2->army, hero1, hero2, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
+	{
+		BattleInfo *curB = new BattleInfo;
+		curB->side1 = army1->tempOwner;
+		curB->side2 = army2->tempOwner;
+		if(curB->side2 == 254) 
+			curB->side2 = 255;
+		setupBattle(curB, tile, army1->army, army2->army, hero1, hero2, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
+	}
+
 	NEW_ROUND;
 	//TODO: pre-tactic stuff, call scripts etc.
 
@@ -351,10 +348,9 @@ void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance
 		const BattleInfo & curB = *gs->curB;
 
 		//stack loop
-		CStack *next;
-		while(!battleResult.get() && (next=gs->curB->getNextStack()))
+		const CStack *next;
+		while(!battleResult.get() && (next = curB.getNextStack()) && next->willMove())
 		{
-			next->state -= WAITING; //if stack was waiting it'll now make move, so it won't be "waiting" anymore
 
 			//check for bad morale => freeze
 			if(next->Morale() < 0 &&
@@ -1369,8 +1365,6 @@ bool CGameHandler::moveHero( si32 hid, int3 dst, ui8 instant, ui8 asker /*= 255*
 		return false;
 	}
 
-	if(states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle) && complain("Cannot move hero during the battle"))
-		return false;
 
 	tlog5 << "Player " <<int(asker) << " wants to move hero "<< hid << " from "<< h->pos << " to " << dst << std::endl;
 	int3 hmpos = dst + int3(-1,0,0);
@@ -1403,7 +1397,9 @@ bool CGameHandler::moveHero( si32 hid, int3 dst, ui8 instant, ui8 asker /*= 255*
 		|| (h->boat && t.tertype != TerrainTile::water && t.blocked)
 			&& complain("Cannot disembark hero, tile is blocked!")
 		|| (!h->movement && dst != h->pos)
-			&& complain("Hero don't have any movement points left!"))
+			&& complain("Hero don't have any movement points left!")
+		|| states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle)
+			&& complain("Cannot move hero during the battle"))
 	{
 		//send info about movement failure
 		sendAndApply(&tmh);