Quellcode durchsuchen

Various changes, including exprank limiter and development towards Tactics secondary skill support.

mateuszb vor 14 Jahren
Ursprung
Commit
fcdc3f0bdd

+ 34 - 0
CCallback.cpp

@@ -1111,3 +1111,37 @@ CBattleCallback::ESpellCastProblem CBattleCallback::battleCanCastThisSpell( cons
 
 	return OK;
 }
+
+si8 CBattleCallback::battleGetTacticDist()
+{
+	if (!gs->curB)
+	{
+		tlog1 << "battleGetTacticDist called when no battle!\n";
+		return 0;
+	}
+
+	if (gs->curB->sides[gs->curB->tacticsSide] == player)
+	{
+		return gs->curB->tacticDistance;
+	}
+	return 0;
+}
+
+ui8 CBattleCallback::battleGetMySide()
+{
+	if (!gs->curB)
+	{
+		tlog1 << "battleGetMySide called when no battle!\n";
+		return 0;
+	}
+
+	return gs->curB->sides[1] == player;
+}
+
+bool CBattleCallback::battleMakeTacticAction( BattleAction * action )
+{
+	MakeAction ma;
+	ma.ba = *action;
+	sendRequest(&ma);
+	return true;
+}

+ 6 - 0
CCallback.h

@@ -105,6 +105,9 @@ public:
 	virtual si8 battleHasDistancePenalty(const CStack * stack, THex destHex) =0; //checks if given stack has distance penalty
 	virtual si8 battleHasWallPenalty(const CStack * stack, THex destHex) =0; //checks if given stack has wall penalty
 	virtual si8 battleCanTeleportTo(const CStack * stack, THex destHex, int telportLevel) =0; //checks if teleportation of given stack to given position can take place
+	virtual si8 battleGetTacticDist() =0; //returns tactic distance for calling player or 0 if player is not in tactic phase
+	virtual ui8 battleGetMySide() =0; //return side of player in battle (attacker/defender)
+	virtual bool battleMakeTacticAction(BattleAction * action) =0; // performs tactic phase actions
 };
 
 class ICallback : public virtual IBattleCallback
@@ -234,6 +237,9 @@ public:
 	si8 battleHasDistancePenalty(const CStack * stack, THex destHex) OVERRIDE; //checks if given stack has distance penalty
 	si8 battleHasWallPenalty(const CStack * stack, THex destHex) OVERRIDE; //checks if given stack has wall penalty
 	si8 battleCanTeleportTo(const CStack * stack, THex destHex, int telportLevel) OVERRIDE; //checks if teleportation of given stack to given position can take place
+	si8 battleGetTacticDist() OVERRIDE; //returns tactic distance for calling player or 0 if player is not in tactic phase
+	ui8 battleGetMySide() OVERRIDE; //return side of player in battle (attacker/defender)
+	bool battleMakeTacticAction(BattleAction * action) OVERRIDE; // performs tactic phase actions
 
 	friend class CCallback;
 	friend class CClient;

+ 34 - 1
client/CBattleInterface.cpp

@@ -104,7 +104,7 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency)
 		if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
 			continue;
 
-		if(sen && thSen && perStackConcurrency)
+		if(sen && thSen && sen != thSen && perStackConcurrency)
 			continue;
 
 		CReverseAnim * revAnim = dynamic_cast<CReverseAnim *>(stAnim);
@@ -1214,6 +1214,13 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	console->pos.y = 560 + pos.y;
 	console->pos.w = 406;
 	console->pos.h = 38;
+	if(curInt->cb->battleGetTacticDist())
+	{
+		btactNext = new AdventureMapButton(std::string(), std::string(), boost::bind(&CBattleInterface::bTacticNextStack,this), 213 + pos.x, 560 + pos.y, "icm011.def", SDLK_SPACE);
+		btactEnd = new AdventureMapButton(std::string(), std::string(), boost::bind(&CBattleInterface::bEndTacticPhase,this), 419 + pos.x, 560 + pos.y, "icm012.def", SDLK_RETURN);
+		bDefence->block(true);
+		bWait->block(true);
+	}
 
 	//loading hero animations
 	if(hero1) // attacking hero
@@ -1331,6 +1338,20 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	{
 		children.push_back(&bfield[i]);
 	}
+
+	if(curInt->cb->battleGetTacticDist())
+	{
+		BOOST_FOREACH(const CStack *s, curInt->cb->battleGetStacks())
+		{
+			if(s->owner == curInt->playerID)
+			{
+				active = 1;
+				stackActivated(s);
+				active = 0;
+				break;
+			}
+		}
+	}
 }
 
 CBattleInterface::~CBattleInterface()
@@ -3387,6 +3408,18 @@ void CBattleInterface::waitForAnims()
 	LOCPLINT->pim->lock();
 }
 
+void CBattleInterface::bEndTacticPhase()
+{
+	BattleAction endt = BattleAction::makeEndOFTacticPhase(curInt->cb->battleGetMySide());
+	curInt->cb->battleMakeTacticAction(&endt);
+
+}
+
+void CBattleInterface::bTacticNextStack()
+{
+
+}
+
 void CBattleHero::show(SDL_Surface *to)
 {
 	//animation of flag

+ 3 - 1
client/CBattleInterface.h

@@ -379,7 +379,7 @@ class CBattleInterface : public CIntObject
 private:
 	SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes;
 	AdventureMapButton * bOptions, * bSurrender, * bFlee, * bAutofight, * bSpell,
-		* bWait, * bDefence, * bConsoleUp, * bConsoleDown;
+		* bWait, * bDefence, * bConsoleUp, * bConsoleDown, *btactNext, *btactEnd;
 	CBattleConsole * console;
 	CBattleHero * attackingHero, * defendingHero; //fighting heroes
 	CStackQueue *queue;
@@ -479,6 +479,8 @@ public:
 	void bDefencef();
 	void bConsoleUpf();
 	void bConsoleDownf();
+	void bTacticNextStack();
+	void bEndTacticPhase();
 	//end of button handle funcs
 	//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
 	void activate();

+ 60 - 105
client/GUIClasses.cpp

@@ -1705,14 +1705,14 @@ void CRecruitmentWindow::Cancel()
 void CRecruitmentWindow::sliderMoved(int to)
 {
 	buy->block(!to);
+	redraw();
 }
 void CRecruitmentWindow::clickLeft(tribool down, bool previousState)
 {
-	int curx = 192 + 51 - (CREATURE_WIDTH*creatures.size()/2) - (SPACE_BETWEEN*(creatures.size()-1)/2);
 	for(int i=0;i<creatures.size();i++)
 	{
 		const int sCREATURE_WIDTH = CREATURE_WIDTH; // gcc -O0 workaround
-		if(isItIn(&genRect(132,sCREATURE_WIDTH,pos.x+curx,pos.y+64),GH.current->motion.x,GH.current->motion.y))
+		if(isItIn(&(Rect(creatures[i].pos) + pos),GH.current->motion.x,GH.current->motion.y))
 		{
 			which = i;
 			int newAmount = std::min(amounts[i],creatures[i].amount);
@@ -1725,18 +1725,9 @@ void CRecruitmentWindow::clickLeft(tribool down, bool previousState)
 				slider->moveTo(newAmount);
 			else
 				slider->moveTo(slider->value);
-			curx = 192 + 51 - (CREATURE_WIDTH*creatures.size()/2) - (SPACE_BETWEEN*(creatures.size()-1)/2);
-			for(int j=0;j<creatures.size();j++)
-			{
-				if(which==j)
-					drawBorder(bitmap,curx,64,CREATURE_WIDTH,132,int3(255,0,0));
-				else
-					drawBorder(bitmap,curx,64,CREATURE_WIDTH,132,int3(239,215,123));
-				curx += TOTAL_CREATURE_WIDTH;
-			}
+			redraw();
 			break;
 		}
-		curx += TOTAL_CREATURE_WIDTH;
 	}
 }
 void CRecruitmentWindow::clickRight(tribool down, bool previousState)
@@ -1758,106 +1749,85 @@ void CRecruitmentWindow::clickRight(tribool down, bool previousState)
 	}
 }
 
-void CRecruitmentWindow::activate()
+void CRecruitmentWindow::showAll( SDL_Surface * to )
 {
-	activateLClick();
-	activateRClick();
-	buy->activate();
-	max->activate();
-	cancel->activate();
-	slider->activate();
-	GH.statusbar = bar;
-}
-
-void CRecruitmentWindow::deactivate()
-{
-	deactivateLClick();
-	deactivateRClick();
-	buy->deactivate();
-	max->deactivate();
-	cancel->deactivate();
-	slider->deactivate();
-}
-
-void CRecruitmentWindow::show(SDL_Surface * to)
-{
-	blitAt(bitmap,pos.x,pos.y,to);
-	buy->show(to);
-	max->show(to);
-	cancel->show(to);
-	slider->show(to);
-
+	CIntObject::showAll(to);
+	
 	char pom[15];
 	SDL_itoa(creatures[which].amount-slider->value,pom,10); //available
-	printAtMiddle(pom,pos.x+205,pos.y+253,FONT_SMALL,zwykly,to);
+	printAtMiddleLoc(pom,205,253,FONT_SMALL,zwykly,to);
 	SDL_itoa(slider->value,pom,10); //recruit
-	printAtMiddle(pom,pos.x+279,pos.y+253,FONT_SMALL,zwykly,to);
-	printAtMiddle(CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures[creatures[which].ID]->namePl,pos.x+243,pos.y+32,FONT_BIG,tytulowy,to); //eg "Recruit Dragon flies"
+	printAtMiddleLoc(pom,279,253,FONT_SMALL,zwykly,to);
+	printAtMiddleLoc(CGI->generaltexth->allTexts[16] + " " + CGI->creh->creatures[creatures[which].ID]->namePl,243,32,FONT_BIG,tytulowy,to); //eg "Recruit Dragon flies"
 
-	int curx = pos.x+122-creatures[which].res.size()*24;
+	int curx = 122-creatures[which].res.size()*24;
 	for(int i=creatures[which].res.size()-1; i>=0; i--)// decrement used to make gold displayed as first res
 	{
-		blitAt(graphics->resources32->ourImages[creatures[which].res[i].first].bitmap,curx,pos.y+243,to);
-		blitAt(graphics->resources32->ourImages[creatures[which].res[i].first].bitmap,curx+258,pos.y+243,to);
+		blitAtLoc(graphics->resources32->ourImages[creatures[which].res[i].first].bitmap,curx,243,to);
+		blitAtLoc(graphics->resources32->ourImages[creatures[which].res[i].first].bitmap,curx+258,243,to);
 		SDL_itoa(creatures[which].res[i].second,pom,10);
-		printAtMiddle(pom,curx+15,pos.y+287,FONT_SMALL,zwykly,to);
+		printAtMiddleLoc(pom,curx+15,287,FONT_SMALL,zwykly,to);
 		SDL_itoa(creatures[which].res[i].second * slider->value,pom,10);
-		printAtMiddle(pom,curx+15+258,pos.y+287,FONT_SMALL,zwykly,to);
+		printAtMiddleLoc(pom,curx+15+258,287,FONT_SMALL,zwykly,to);
 		curx+=32+16;//size of bitmap + distance between them
 	}
 
-	for(int i=0; i<creatures.size(); ++i)
-		creatures[i].pic->show(to);
-
-	bar->show(to);
+	for(int j=0;j<creatures.size();j++)
+	{
+		if(which==j)
+			drawBorder(*bitmap,creatures[j].pos,int3(255,0,0));
+		else
+			drawBorder(*bitmap,creatures[j].pos,int3(239,215,123));
+	}
 }
 
 CRecruitmentWindow::CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const boost::function<void(int,int)> &Recruit, int y_offset)
 :recruit(Recruit), dwelling(Dwelling), level(Level), dst(Dst)
 {
+	used = LCLICK | RCLICK;
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+
 	which = 0;
-	SDL_Surface *hhlp = BitmapHandler::loadBitmap("TPRCRT.bmp");
-	graphics->blueToPlayersAdv(hhlp,LOCPLINT->playerID);
-	bitmap = SDL_ConvertSurface(hhlp,screen->format,0);
-	SDL_SetColorKey(bitmap,SDL_SRCCOLORKEY,SDL_MapRGB(bitmap->format,0,255,255));
-	SDL_FreeSurface(hhlp);
-	pos.x = screen->w/2 - bitmap->w/2;
-	pos.y = screen->h/2 - bitmap->h/2+y_offset;
-	pos.w = bitmap->w;
-	pos.h = bitmap->h;
-	bar = new CStatusBar(pos.x+8, pos.y+370, "APHLFTRT.bmp", 471);
-	max = new AdventureMapButton(CGI->generaltexth->zelp[553],boost::bind(&CRecruitmentWindow::Max,this),pos.x+134,pos.y+313,"IRCBTNS.DEF",SDLK_m);
-	buy = new AdventureMapButton(CGI->generaltexth->zelp[554],boost::bind(&CRecruitmentWindow::Buy,this),pos.x+212,pos.y+313,"IBY6432.DEF",SDLK_RETURN);
-	cancel = new AdventureMapButton(CGI->generaltexth->zelp[555],boost::bind(&CRecruitmentWindow::Cancel,this),pos.x+290,pos.y+313,"ICN6432.DEF",SDLK_ESCAPE);
-	slider = new CSlider(pos.x+176,pos.y+279,135,boost::bind(&CRecruitmentWindow::sliderMoved,this, _1),0,0,0,true);
+
+	bitmap = new CPicture("TPRCRT.bmp");
+	bitmap->colorizeAndConvert(LOCPLINT->playerID);
+	bitmap->center();
+	pos = (bitmap->pos += Point(0, y_offset));
+
+	bar = new CGStatusBar(8, 370, "APHLFTRT.bmp", 471);
+	max = new AdventureMapButton(CGI->generaltexth->zelp[553],boost::bind(&CRecruitmentWindow::Max,this),134,313,"IRCBTNS.DEF",SDLK_m);
+	buy = new AdventureMapButton(CGI->generaltexth->zelp[554],boost::bind(&CRecruitmentWindow::Buy,this),212,313,"IBY6432.DEF",SDLK_RETURN);
+	cancel = new AdventureMapButton(CGI->generaltexth->zelp[555],boost::bind(&CRecruitmentWindow::Cancel,this),290,313,"ICN6432.DEF",SDLK_ESCAPE);
+	slider = new CSlider(176,279,135,0,0,0,0,true);
+	slider->moved = boost::bind(&CRecruitmentWindow::sliderMoved,this, _1);
 
 	initCres();
 
-	printAtMiddle(CGI->generaltexth->allTexts[346],113,232,FONT_SMALL,zwykly,bitmap); //cost per troop t
-	printAtMiddle(CGI->generaltexth->allTexts[465],205,233,FONT_SMALL,zwykly,bitmap); //available t
-	printAtMiddle(CGI->generaltexth->allTexts[16],279,233,FONT_SMALL,zwykly,bitmap); //recruit t
-	printAtMiddle(CGI->generaltexth->allTexts[466],371,232,FONT_SMALL,zwykly,bitmap); //total cost t
-	drawBorder(bitmap,172,222,67,42,int3(239,215,123));
-	drawBorder(bitmap,246,222,67,42,int3(239,215,123));
-	drawBorder(bitmap,64,222,99,76,int3(239,215,123));
-	drawBorder(bitmap,322,222,99,76,int3(239,215,123));
-	drawBorder(bitmap,133,312,66,34,int3(173,142,66));
-	drawBorder(bitmap,211,312,66,34,int3(173,142,66));
-	drawBorder(bitmap,289,312,66,34,int3(173,142,66));
+	printAtMiddle(CGI->generaltexth->allTexts[346],113,232,FONT_SMALL,zwykly,*bitmap); //cost per troop t
+	printAtMiddle(CGI->generaltexth->allTexts[465],205,233,FONT_SMALL,zwykly,*bitmap); //available t
+	printAtMiddle(CGI->generaltexth->allTexts[16],279,233,FONT_SMALL,zwykly,*bitmap); //recruit t
+	printAtMiddle(CGI->generaltexth->allTexts[466],371,232,FONT_SMALL,zwykly,*bitmap); //total cost t
+	drawBorder(*bitmap,172,222,67,42,int3(239,215,123));
+	drawBorder(*bitmap,246,222,67,42,int3(239,215,123));
+	drawBorder(*bitmap,64,222,99,76,int3(239,215,123));
+	drawBorder(*bitmap,322,222,99,76,int3(239,215,123));
+	drawBorder(*bitmap,133,312,66,34,int3(173,142,66));
+	drawBorder(*bitmap,211,312,66,34,int3(173,142,66));
+	drawBorder(*bitmap,289,312,66,34,int3(173,142,66));
 
 	//border for creatures
 	int curx = 192 + 50 - (CREATURE_WIDTH*creatures.size()/2) - (SPACE_BETWEEN*(creatures.size()-1)/2);
 	for(int i=0;i<creatures.size();i++)
 	{
-		creatures[i].pos.x = curx;
-		creatures[i].pos.y = 65;
-		creatures[i].pos.w = 100;
-		creatures[i].pos.h = 130;
-		if(which==i)
-			drawBorder(bitmap,curx-1,64,CREATURE_WIDTH,132,int3(255,0,0));
-		else
-			drawBorder(bitmap,curx-1,64,CREATURE_WIDTH,132,int3(239,215,123));
-		creatures[i].pic = new CCreaturePic(pos.x+curx, pos.y+65, CGI->creh->creatures[creatures[i].ID]);
+		creatures[i].pos.x = curx-1;
+		creatures[i].pos.y = 65 - 1;
+		creatures[i].pos.w = 100 + 2;
+		creatures[i].pos.h = 130 + 2;
+// 		if(which==i)
+// 			drawBorder(*bitmap,curx-1,64,CREATURE_WIDTH,132,int3(255,0,0));
+// 		else
+// 			drawBorder(*bitmap,curx-1,64,CREATURE_WIDTH,132,int3(239,215,123));
+		creatures[i].pic = new CCreaturePic(curx, 65, CGI->creh->creatures[creatures[i].ID]);
 		curx += TOTAL_CREATURE_WIDTH;
 	}
 
@@ -1871,18 +1841,13 @@ CRecruitmentWindow::CRecruitmentWindow(const CGDwelling *Dwelling, int Level, co
 
 CRecruitmentWindow::~CRecruitmentWindow()
 {
-	cleanCres();
-	delete max;
-	delete buy;
-	delete cancel;
-	SDL_FreeSurface(bitmap);
-	delete slider;
-	delete bar;
 }
 
 void CRecruitmentWindow::initCres()
 {
-	cleanCres();
+	creatures.clear();
+	amounts.clear();
+
 	for(int i=0; i<dwelling->creatures.size(); i++)
 	{
 		if(level >= 0 && i != level) 
@@ -1907,16 +1872,6 @@ void CRecruitmentWindow::initCres()
 	slider->setAmount(std::min(amounts[which],creatures[which].amount));
 }
 
-void CRecruitmentWindow::cleanCres()
-{
-	for(int i=0;i<creatures.size();i++)
-	{
-		delete creatures[i].pic;
-	}
-	creatures.clear();
-	amounts.clear();
-}
-
 CSplitWindow::CSplitWindow(int cid, int max, CGarrisonInt *Owner, int Last, int val)
 {
 	last = Last;
@@ -6556,7 +6511,7 @@ void MoraleLuckBox::set(const CBonusSystemNode *node)
 		text += CGI->generaltexth->arraytxt[noneTxtId];
 	else
 	{
-		if (node->nodeType == CBonusSystemNode::STACK &&
+		if (node->nodeType == CBonusSystemNode::STACK_INSTANCE &&
 			(node->hasBonusOfType (Bonus::UNDEAD) || node->hasBonusOfType(Bonus::BLOCK_MORALE) || node->hasBonusOfType(Bonus::NON_LIVING))) //it's a creature window
 		{
 			text += CGI->generaltexth->arraytxt[113]; //unaffected by morale

+ 3 - 7
client/GUIClasses.h

@@ -471,8 +471,8 @@ public:
 	boost::function<void(int,int)> recruit; //void (int ID, int amount) <-- call to recruit creatures
 	CSlider *slider; //for selecting amount
 	AdventureMapButton *max, *buy, *cancel;
-	SDL_Surface *bitmap; //background
-	CStatusBar *bar;
+	CPicture *bitmap; //background
+	CGStatusBar *bar;
 	int which; //which creature is active
 
 	const CGDwelling *dwelling;
@@ -486,11 +486,7 @@ public:
 	void sliderMoved(int to);
 	void clickLeft(tribool down, bool previousState);
 	void clickRight(tribool down, bool previousState);
-	void activate();
-	void deactivate();
-	void show(SDL_Surface * to);
-	void showAll(SDL_Surface * to){show(to);};
-	void cleanCres();
+	void showAll(SDL_Surface * to);
 	void initCres();
 	CRecruitmentWindow(const CGDwelling *Dwelling, int Level, const CArmedInstance *Dst, const boost::function<void(int,int)> & Recruit, int y_offset = 0); //creatures - pairs<creature_ID,amount> //c-tor
 	~CRecruitmentWindow(); //d-tor

+ 9 - 1
lib/BattleAction.cpp

@@ -69,4 +69,12 @@ BattleAction BattleAction::makeMove(const CStack *stack, THex dest)
 	ba.stackNumber = stack->ID;
 	ba.destinationTile = dest;
 	return ba;
-}
+}
+
+BattleAction BattleAction::makeEndOFTacticPhase(ui8 side)
+{
+	BattleAction ba;
+	ba.side = side;
+	ba.actionType = END_TACTIC_PHASE;
+	return ba;
+}

+ 2 - 1
lib/BattleAction.h

@@ -21,7 +21,7 @@ struct DLL_EXPORT BattleAction
 	ui32 stackNumber;//stack ID, -1 left hero, -2 right hero,
 	enum ActionType
 	{
-		INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE, STACK_HEAL
+		END_TACTIC_PHASE = -2, INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE, STACK_HEAL
 	};
 	ui8 actionType; //use ActionType enum for values
 		//10 = Monster casts a spell (i.e. Faerie Dragons)	11 - Bad morale freeze	12 - stacks heals another stack
@@ -39,5 +39,6 @@ struct DLL_EXPORT BattleAction
 	static BattleAction makeMeleeAttack(const CStack *stack, const CStack * attacked, THex attackFrom = THex::INVALID);
 	static BattleAction makeShotAttack(const CStack *shooter, const CStack *target);
 	static BattleAction makeMove(const CStack *stack, THex dest);
+	static BattleAction makeEndOFTacticPhase(ui8 side);
 };
 #endif // __BATTLEACTION_H__

+ 34 - 11
lib/BattleState.cpp

@@ -315,11 +315,15 @@ std::vector<THex> BattleInfo::getAccessibility(const CStack * stack, bool addOcc
 		}
 	}
 	
-	for (int i=0; i < BFIELD_SIZE ; ++i) {
-		if(
-			( ( !addOccupiable && dist[i] <= stack->Speed() && ac[i] ) || ( addOccupiable && dist[i] <= stack->Speed() && isAccessible(i, ac, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), true) ) )//we can reach it
-			|| (vstd::contains(occupyable, i) && ( dist[ i + (stack->attackerOwned ? 1 : -1 ) ] <= stack->Speed() ) &&
-				ac[i + (stack->attackerOwned ? 1 : -1 )] ) //it's occupyable and we can reach adjacent hex
+	for (int i=0; i < BFIELD_SIZE ; ++i)
+	{
+		bool rangeFits = tacticDistance 
+						? isInTacticRange(i)
+						: dist[i] <= stack->Speed();
+
+		if(	( !addOccupiable && rangeFits && ac[i] ) 
+			|| ( addOccupiable && rangeFits && isAccessible(i, ac, stack->doubleWide(), stack->attackerOwned, stack->hasBonusOfType(Bonus::FLYING), true) )//we can reach it
+			|| (vstd::contains(occupyable, i) && (!tacticDistance && dist[ i + (stack->attackerOwned ? 1 : -1 ) ] <= stack->Speed() ) && ac[i + (stack->attackerOwned ? 1 : -1 )] ) //it's occupyable and we can reach adjacent hex
 			)
 		{
 			ret.push_back(i);
@@ -501,17 +505,17 @@ TDmgRange BattleInfo::calculateDmgRange( const CStack* attacker, const CStack* d
 	{
 		if(shooting)
 		{
-			additiveBonus += attackerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 1) / 100.0f;
+			additiveBonus += attackerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARCHERY) / 100.0f;
 		}
 		else
 		{
-			additiveBonus += attackerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 22) / 100.0f;
+			additiveBonus += attackerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::OFFENCE) / 100.0f;
 		}
 	}
 
 	if(defendingHero)
 	{
-		multBonus *= (std::max(0, 100-defendingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 23))) / 100.0f;
+		multBonus *= (std::max(0, 100-defendingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::ARMORER))) / 100.0f;
 	}
 
 	//handling hate effect
@@ -838,7 +842,7 @@ ui32 BattleInfo::calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const C
 	//applying sorcery secondary skill
 	if(caster)
 	{
-		ret *= (100.f + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 25)) / 100.0f; //sorcery
+		ret *= (100.f + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::SORCERY)) / 100.0f;
 		ret *= (100.f + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, sp->id)) / 100.0f;
 
 		if(sp->air)
@@ -1588,12 +1592,28 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const
 	curB->addNewBonus(makeFeature(Bonus::PRIMARY_SKILL, Bonus::ONE_BATTLE, PrimarySkill::DEFENSE, 1, Bonus::TERRAIN_NATIVE)->addLimiter(nativeTerrain));
 	//////////////////////////////////////////////////////////////////////////
 
-	return curB;
-}
+	int tacticLvls[2] = {0};
+	for(int i = 0; i < ARRAY_COUNT(tacticLvls); i++)
+	{
+		if(heroes[i])
+			tacticLvls[i] += heroes[i]->getSecSkillLevel(CGHeroInstance::TACTICS);
+	}
 
+	if(int diff = tacticLvls[0] - tacticLvls[1])
+	{
+		curB->tacticsSide = diff < 0;
+		curB->tacticDistance = std::abs(diff)*2 + 1;
+	}
 
+	return curB;
+}
 
+bool BattleInfo::isInTacticRange( THex dest ) const
+{
 
+	return ((tacticsSide && dest.getX() > 0 && dest.getX() <= tacticDistance)
+		|| (!tacticsSide && dest.getX() < BFIELD_WIDTH - 1 && dest.getX() >= BFIELD_WIDTH - tacticDistance - 1));
+}
 
 CStack::CStack(const CStackInstance *Base, int O, int I, bool AO, int S)
 	: base(Base), ID(I), owner(O), slot(S), attackerOwned(AO),   
@@ -1602,11 +1622,13 @@ CStack::CStack(const CStackInstance *Base, int O, int I, bool AO, int S)
 	assert(base);
 	type = base->type;
 	count = baseAmount = base->count;
+	nodeType = STACK_BATTLE;
 }
 
 CStack::CStack()
 {
 	init();
+	nodeType = STACK_BATTLE;
 }
 
 CStack::CStack(const CStackBasicDescriptor *stack, int O, int I, bool AO, int S)
@@ -1614,6 +1636,7 @@ CStack::CStack(const CStackBasicDescriptor *stack, int O, int I, bool AO, int S)
 {
 	type = stack->type;
 	count = baseAmount = stack->count;
+	nodeType = STACK_BATTLE;
 }
 
 void CStack::init()

+ 5 - 0
lib/BattleState.h

@@ -63,12 +63,16 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode
 	SiegeInfo si;
 	si32 battlefieldType;
 
+	ui8 tacticsSide; //which side is requested to play tactics phase
+	ui8 tacticDistance; //how many hexes we can go forward (1 = only hexes adjacent to margin line)
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & sides & round & activeStack & siege & town & tile & stacks & belligerents & obstacles
 			& castSpells & si & battlefieldType;
 		h & heroes;
 		h & usedSpellsHistory;
+		h & tacticsSide & tacticDistance;
 		h & static_cast<CBonusSystemNode&>(*this);
 	}
 
@@ -117,6 +121,7 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode
 	si8 battleMaxSpellLevel() const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, SPELL_LEVELS is returned
 	void localInit();
 	static BattleInfo * setupBattle( int3 tile, int terrain, int terType, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town );
+	bool isInTacticRange( THex dest ) const;
 };
 
 class DLL_EXPORT CStack : public CBonusSystemNode, public CStackBasicDescriptor

+ 9 - 9
lib/CArtHandler.cpp

@@ -585,23 +585,23 @@ void CArtHandler::addBonuses()
 	giveArtBonus(53,Bonus::SIGHT_RADIOUS,+1);//Spyglass
 
 	//necromancy bonus
-	giveArtBonus(54,Bonus::SECONDARY_SKILL_PREMY,+5,12, Bonus::ADDITIVE_VALUE);//Amulet of the Undertaker
-	giveArtBonus(55,Bonus::SECONDARY_SKILL_PREMY,+10,12, Bonus::ADDITIVE_VALUE);//Vampire's Cowl
-	giveArtBonus(56,Bonus::SECONDARY_SKILL_PREMY,+15,12, Bonus::ADDITIVE_VALUE);//Dead Man's Boots
+	giveArtBonus(54,Bonus::SECONDARY_SKILL_PREMY,+5, CGHeroInstance::NECROMANCY, Bonus::ADDITIVE_VALUE);//Amulet of the Undertaker
+	giveArtBonus(55,Bonus::SECONDARY_SKILL_PREMY,+10, CGHeroInstance::NECROMANCY, Bonus::ADDITIVE_VALUE);//Vampire's Cowl
+	giveArtBonus(56,Bonus::SECONDARY_SKILL_PREMY,+15, CGHeroInstance::NECROMANCY, Bonus::ADDITIVE_VALUE);//Dead Man's Boots
 
 	giveArtBonus(57,Bonus::MAGIC_RESISTANCE,+5);//Garniture of Interference
 	giveArtBonus(58,Bonus::MAGIC_RESISTANCE,+10);//Surcoat of Counterpoise
 	giveArtBonus(59,Bonus::MAGIC_RESISTANCE,+15);//Boots of Polarity
 
 	//archery bonus
-	giveArtBonus(60,Bonus::SECONDARY_SKILL_PREMY,+5,1, Bonus::ADDITIVE_VALUE);//Bow of Elven Cherrywood
-	giveArtBonus(61,Bonus::SECONDARY_SKILL_PREMY,+10,1, Bonus::ADDITIVE_VALUE);//Bowstring of the Unicorn's Mane
-	giveArtBonus(62,Bonus::SECONDARY_SKILL_PREMY,+15,1, Bonus::ADDITIVE_VALUE);//Angel Feather Arrows
+	giveArtBonus(60,Bonus::SECONDARY_SKILL_PREMY,+5, CGHeroInstance::ARCHERY, Bonus::ADDITIVE_VALUE);//Bow of Elven Cherrywood
+	giveArtBonus(61,Bonus::SECONDARY_SKILL_PREMY,+10,CGHeroInstance::ARCHERY, Bonus::ADDITIVE_VALUE);//Bowstring of the Unicorn's Mane
+	giveArtBonus(62,Bonus::SECONDARY_SKILL_PREMY,+15,CGHeroInstance::ARCHERY, Bonus::ADDITIVE_VALUE);//Angel Feather Arrows
 
 	//eagle eye bonus
-	giveArtBonus(63,Bonus::SECONDARY_SKILL_PREMY,+5,11, Bonus::ADDITIVE_VALUE);//Bird of Perception
-	giveArtBonus(64,Bonus::SECONDARY_SKILL_PREMY,+10,11, Bonus::ADDITIVE_VALUE);//Stoic Watchman
-	giveArtBonus(65,Bonus::SECONDARY_SKILL_PREMY,+15,11, Bonus::ADDITIVE_VALUE);//Emblem of Cognizance
+	giveArtBonus(63,Bonus::SECONDARY_SKILL_PREMY,+5, CGHeroInstance::EAGLE_EYE, Bonus::ADDITIVE_VALUE);//Bird of Perception
+	giveArtBonus(64,Bonus::SECONDARY_SKILL_PREMY,+10, CGHeroInstance::EAGLE_EYE, Bonus::ADDITIVE_VALUE);//Stoic Watchman
+	giveArtBonus(65,Bonus::SECONDARY_SKILL_PREMY,+15, CGHeroInstance::EAGLE_EYE, Bonus::ADDITIVE_VALUE);//Emblem of Cognizance
 
 	//reducing cost of surrendering
 	giveArtBonus(66,Bonus::SURRENDER_DISCOUNT,+10);//Statesman's Medal

+ 2 - 2
lib/CCreatureHandler.cpp

@@ -939,7 +939,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, std::string & src
 			loadToIt (curVal, src, it, 4);
 			if (curVal == 1)
 			{
-				b.limiter.reset (new ExpRankLimiter(i));
+				b.limiter.reset (new RankRangeLimiter(i));
 				bl.push_back(new Bonus(b));
 				break; //never turned off it seems
 			}
@@ -954,7 +954,7 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, std::string & src
 			if (curVal > lastVal) //threshold, add last bonus
 			{
 				b.val = lastVal;
-				b.limiter.reset (new ExpRankLimiter(i));
+				b.limiter.reset (new RankRangeLimiter(i));
 				bl.push_back(new Bonus(b));
 				lastLev = i; //start new range from here, i = previous rank
 			}

+ 1 - 1
lib/CCreatureSet.cpp

@@ -422,7 +422,7 @@ void CStackInstance::init()
 	type = NULL;
 	idRand = -1;
 	_armyObj = NULL;
-	nodeType = STACK;
+	nodeType = STACK_INSTANCE;
 }
 
 int CStackInstance::getQuantityID() const 

+ 16 - 17
lib/CObjectHandler.cpp

@@ -690,13 +690,11 @@ int CGHeroInstance::maxMovePoints(bool onLand) const
 	double modifier = 0;
 	if(onLand)
 	{
-		//logistics:
-		modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 2) / 100.0f;
+		modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, LOGISTICS) / 100.0f;
 	}
 	else
 	{
-		//navigation:
-		modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 5) / 100.0f;
+		modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, NAVIGATION) / 100.0f;
 	}
 	return int(base + base*modifier) + bonus;
 }
@@ -1215,20 +1213,21 @@ void CGHeroInstance::updateSkill(int which, int val)
 		case 27: //First Aid
 			skillVal = 25 + 25*val; break;
 	}
-	if (skillVal) //we don't need bonuses of other types here
+	
+
+	int skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN;
+	if(Bonus * b = bonuses.getFirst(Selector::typeSybtype(Bonus::SECONDARY_SKILL_PREMY, which) && Selector::sourceType(Bonus::SECONDARY_SKILL))) //only local hero bonus
 	{
-		Bonus * b = bonuses.getFirst(Selector::typeSybtype(Bonus::SECONDARY_SKILL_PREMY, which) && Selector::sourceType(Bonus::SECONDARY_SKILL));
-		if (b) //only local hero bonus
-		{
-			b->val = skillVal;
-		}
-		else
-		{
-			Bonus *bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, id, skillVal, ID, which, Bonus::BASE_NUMBER);
-			bonus->source = Bonus::SECONDARY_SKILL;
-			addNewBonus(bonus);
-		}
+		b->val = skillVal;
+		b->valType = skillValType;
 	}
+	else
+	{
+		Bonus *bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, id, skillVal, ID, which, skillValType);
+		bonus->source = Bonus::SECONDARY_SKILL;
+		addNewBonus(bonus);
+	}
+	
 }
 void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
 {
@@ -1312,7 +1311,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
 	// Hero knows necromancy.
 	if (necromancyLevel > 0) 
 	{
-		double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 12)/100.0;
+		double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, NECROMANCY)/100.0;
 		amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all...
 		const std::map<ui32,si32> &casualties = battleResult.casualties[!battleResult.winner];
 		ui32 raisedUnits = 0;

+ 50 - 26
lib/HeroBonus.cpp

@@ -11,6 +11,7 @@
 #include <boost/bind.hpp>
 #include "CHeroHandler.h"
 #include "CGeneralTextHandler.h"
+#include "BattleState.h"
 
 #define FOREACH_CONST_PARENT(pname) 	TCNodes parents; getParents(parents); BOOST_FOREACH(const CBonusSystemNode *pname, parents)
 #define FOREACH_PARENT(pname) 	TNodes parents; getParents(parents); BOOST_FOREACH(CBonusSystemNode *pname, parents)
@@ -29,6 +30,8 @@ int DLL_EXPORT BonusList::totalValue() const
 	int additive = 0;
 	int indepMax = 0;
 	bool hasIndepMax = false;
+	int indepMin = 0;
+	bool hasIndepMin = false;
 
 	BOOST_FOREACH(Bonus *i, *this)
 	{
@@ -57,16 +60,33 @@ int DLL_EXPORT BonusList::totalValue() const
 				amax(indepMax, i->val);
 			}
 
+			break;
+		case Bonus::INDEPENDENT_MIN:
+			if (!indepMin)
+			{
+				indepMin = i->val;
+				hasIndepMin = true;
+			}
+			else
+			{
+				amax(indepMin, i->val);
+			}
+
 			break;
 		}
 	}
 	int modifiedBase = base + (base * percentToBase) / 100;
 	modifiedBase += additive;
 	int valFirst = (modifiedBase * (100 + percentToAll)) / 100;
+
+	if(hasIndepMin && hasIndepMax)
+		assert(indepMin < indepMax);
 	if (hasIndepMax)
-		return std::max(valFirst, indepMax);
-	else
-		return valFirst;
+		amax(valFirst, indepMax);
+	if (hasIndepMax)
+		amin(valFirst, indepMin);
+
+	return valFirst;
 }
 const DLL_EXPORT Bonus * BonusList::getFirst(const CSelector &selector) const
 {
@@ -600,15 +620,29 @@ namespace Selector
 	}
 }
 
+const CStackInstance * retreiveStackInstance(const CBonusSystemNode *node)
+{
+	switch(node->nodeType)
+	{
+	case CBonusSystemNode::STACK_INSTANCE:
+		return (static_cast<const CStackInstance *>(node));
+	case CBonusSystemNode::STACK_BATTLE:
+		return (static_cast<const CStack*>(node))->base;
+	default:
+		return NULL;
+	}
+}
+
 const CCreature * retrieveCreature(const CBonusSystemNode *node)
 {
 	switch(node->nodeType)
 	{
 	case CBonusSystemNode::CREATURE:
 		return (static_cast<const CCreature *>(node));
-	case CBonusSystemNode::STACK:
-		return (static_cast<const CStackInstance *>(node))->type;
 	default:
+		const CStackInstance *csi = retreiveStackInstance(node);
+		if(csi)
+			return csi->type;
 		return NULL;
 	}
 }
@@ -655,24 +689,11 @@ bool ILimiter::limit(const Bonus *b, const CBonusSystemNode &node) const /*retur
 
 bool CCreatureTypeLimiter::limit(const Bonus *b, const CBonusSystemNode &node) const
 {
-	switch (node.nodeType)
-	{	
-		case CBonusSystemNode::STACK:
-		{
-			const CCreature *c = (static_cast<const CStackInstance *>(&node))->type;
-			return c != creature   &&   (!includeUpgrades || !creature->isMyUpgrade(c));
-		}	//drop bonus if it's not our creature and (we dont check upgrades or its not our upgrade)
-			break;
-		case CBonusSystemNode::CREATURE:
-		{
-			const CCreature *c = (static_cast<const CCreature *>(&node));
-			return c != creature   &&   (!includeUpgrades || !creature->isMyUpgrade(c));
-		}
-			break;
-		default:
-			return true;
-	}
+	const CCreature *c = retrieveCreature(&node);
+	return c != creature   &&   (!includeUpgrades || !creature->isMyUpgrade(c));
+	//drop bonus if it's not our creature and (we dont check upgrades or its not our upgrade)
 }
+
 CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature &Creature, ui8 IncludeUpgrades /*= true*/)
 	:creature(&Creature), includeUpgrades(IncludeUpgrades)
 {
@@ -773,12 +794,15 @@ bool CreatureAlignmentLimiter::limit(const Bonus *b, const CBonusSystemNode &nod
 	}
 }
 
-ExpRankLimiter::ExpRankLimiter(ui8 Rank)
-	:rank(Rank)
+RankRangeLimiter::RankRangeLimiter(ui8 Min, ui8 Max)
+	:minRank(Min), maxRank(Max)
 {
 }
 
-RankRangeLimiter::RankRangeLimiter(ui8 Min, ui8 Max)
-	:min(Min), max(Max)
+bool RankRangeLimiter::limit( const Bonus *b, const CBonusSystemNode &node ) const
 {
+	const CStackInstance *csi = retreiveStackInstance(&node);
+	if(csi)
+		return csi->getExpRank() < minRank || csi->getExpRank() > maxRank;
+	return true;
 }

+ 7 - 18
lib/HeroBonus.h

@@ -213,7 +213,8 @@ struct DLL_EXPORT Bonus
 		BASE_NUMBER,
 		PERCENT_TO_ALL,
 		PERCENT_TO_BASE,
-		INDEPENDENT_MAX //used for SPELL bonus
+		INDEPENDENT_MAX, //used for SPELL bonus
+		INDEPENDENT_MIN //used for SECONDARY_SKILL_PREMY bonus
 	};
 
 	ui16 duration; //uses BonusDuration values
@@ -433,7 +434,7 @@ public:
 
 	enum ENodeTypes
 	{
-		UNKNOWN, STACK, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO
+		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO
 	};
 };
 
@@ -618,29 +619,17 @@ public:
 	}
 };
 
-class DLL_EXPORT ExpRankLimiter : public ILimiter //applies to creatures with Rank >= rank
-{
-public:
-	ui8 rank;
-
-	ExpRankLimiter(ui8 Rank);
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & rank;
-	}
-};
-
 class DLL_EXPORT RankRangeLimiter : public ILimiter //applies to creatures with min <= Rank <= max
 {
 public:
-	ui8 min, max;
+	ui8 minRank, maxRank;
 
-	RankRangeLimiter(ui8 Min, ui8 Max);
+	RankRangeLimiter(ui8 Min, ui8 Max = 255);
+	bool limit(const Bonus *b, const CBonusSystemNode &node) const OVERRIDE;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & min & max;
+		h & minRank & maxRank;
 	}
 };
 

+ 11 - 1
lib/NetPacksLib.cpp

@@ -243,8 +243,10 @@ DLL_EXPORT void RemoveObject::applyGs( CGameState *gs )
 	if(obj->ID==HEROI_TYPE)
 	{
 		CGHeroInstance *h = static_cast<CGHeroInstance*>(obj);
+		PlayerState *p = gs->getPlayer(h->tempOwner);
 		gs->map->heroes -= h;
-		gs->getPlayer(h->tempOwner)->heroes -= h;
+		p->heroes -= h;
+		h->detachFrom(p);
 		h->tempOwner = 255; //no one owns beaten hero
 
 		if(CGTownInstance *t = const_cast<CGTownInstance *>(h->visitedTown))
@@ -972,6 +974,12 @@ DLL_EXPORT void StartAction::applyGs( CGameState *gs )
 {
 	CStack *st = gs->curB->getStack(ba.stackNumber);
 
+	if(ba.actionType == BattleAction::END_TACTIC_PHASE)
+	{
+		gs->curB->tacticDistance = 0;
+		return;
+	}
+
 	if(ba.actionType != BattleAction::HERO_SPELL) //don't check for stack if it's custom action by hero
 	{
 		assert(st);
@@ -1100,6 +1108,8 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs )
 		csi->setArmyObj(h);
 		CStack * summonedStack = gs->curB->generateNewStack(*csi, gs->curB->stacks.size(), !side, 255, pos);
 		summonedStack->state.insert(SUMMONED);
+		summonedStack->attachTo(csi);
+		summonedStack->postInit();
 		//summonedStack->addNewBonus( makeFeature(HeroBonus::SUMMONED, HeroBonus::ONE_BATTLE, 0, 0, HeroBonus::BONUS_FROM_HERO) );
 		gs->curB->stacks.push_back(summonedStack);
 	}

+ 25 - 13
server/CGameHandler.cpp

@@ -352,10 +352,12 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 	ui8 loser = sides[!battleResult.data->winner];
 
 	CasualtiesAfterBattle cab1(bEndArmy1, gs->curB), cab2(bEndArmy2, gs->curB); //calculate casualties before deleting battle
+
+
 	sendAndApply(battleResult.data);
 
 	//Eagle Eye secondary skill handling
-	const CGHeroInstance *vistoriousHero = gs->curB->heroes[battleResult.data->winner];
+	/*const CGHeroInstance *vistoriousHero = gs->curB->heroes[battleResult.data->winner];
 	if(0 && vistoriousHero)
 	{
 		if(int eagleEyeLevel = vistoriousHero->getSecSkillLevel(CGHeroInstance::EAGLE_EYE))
@@ -403,7 +405,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 			}
 		}
 	}
-
+	*/
 
 	if(!duel)
 	{
@@ -617,7 +619,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 	tlog1 << "Ended handling connection\n";
 }
 
-int CGameHandler::moveStack(int stack, int dest)
+int CGameHandler::moveStack(int stack, THex dest)
 {
 	int ret = 0;
 
@@ -627,6 +629,11 @@ int CGameHandler::moveStack(int stack, int dest)
 	assert(curStack);
 	assert(dest < BFIELD_SIZE);
 
+	if (gs->curB->tacticDistance)
+	{
+		assert(gs->curB->isInTacticRange(dest));
+	}
+
 	//initing necessary tables
 	bool accessibility[BFIELD_SIZE];
 	std::vector<THex> accessible = gs->curB->getAccessibility(curStack, false);
@@ -645,12 +652,12 @@ int CGameHandler::moveStack(int stack, int dest)
 		if(curStack->attackerOwned)
 		{
 			if(accessibility[dest+1])
-				dest+=1;
+				dest += THex::RIGHT;
 		}
 		else
 		{
 			if(accessibility[dest-1])
-				dest-=1;
+				dest += THex::LEFT;
 		}
 	}
 
@@ -675,9 +682,11 @@ int CGameHandler::moveStack(int stack, int dest)
 
 	ret = path.second;
 
+	int creSpeed = gs->curB->tacticDistance ? BFIELD_SIZE : curStack->Speed();
+
 	if(curStack->hasBonusOfType(Bonus::FLYING))
 	{
-		if(path.second <= curStack->Speed() && path.first.size() > 0)
+		if(path.second <= creSpeed && path.first.size() > 0)
 		{
 			//inform clients about move
 			BattleStackMoved sm;
@@ -691,7 +700,7 @@ int CGameHandler::moveStack(int stack, int dest)
 	}
 	else //for non-flying creatures
 	{
-		int tilesToMove = std::max((int)(path.first.size() - curStack->Speed()), 0);
+		int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
 		for(int v=path.first.size()-1; v>=tilesToMove; --v)
 		{
 			//inform clients about move
@@ -3099,8 +3108,15 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 {
 	tlog1 << "\tMaking action of type " << ba.actionType << std::endl;
 	bool ok = true;
+
 	switch(ba.actionType)
 	{
+	case BattleAction::END_TACTIC_PHASE: //wait
+		{
+			sendAndApply(&StartAction(ba));
+			sendAndApply(&EndAction());
+			break;
+		}
 	case BattleAction::WALK: //walk
 		{
 			sendAndApply(&StartAction(ba)); //start movement
@@ -4844,12 +4860,8 @@ void CGameHandler::runBattle()
 
 	//tactic round
 	{
-		if( (gs->curB->heroes[0] && gs->curB->heroes[0]->getSecSkillLevel(CGHeroInstance::TACTICS)>0) || 
-			( gs->curB->heroes[1] && gs->curB->heroes[1]->getSecSkillLevel(CGHeroInstance::TACTICS)>0)  )//someone has tactics
-		{
-			//TODO: tactic round (round -1)
-			NEW_ROUND;
-		}
+		while(gs->curB->tacticDistance)
+			boost::this_thread::sleep(boost::posix_time::milliseconds(50));
 	}
 
 	//spells opening battle

+ 1 - 1
server/CGameHandler.h

@@ -104,7 +104,7 @@ public:
 
 	bool isAllowedExchange(int id1, int id2);
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
-	int moveStack(int stack, int dest); //returned value - travelled distance
+	int moveStack(int stack, THex dest); //returned value - travelled distance
 	void startBattle(const CArmedInstance *armies[2], int3 tile, const CGHeroInstance *heroes[2], bool creatureBank, boost::function<void(BattleResult*)> cb, const CGTownInstance *town = NULL); //use hero=NULL for no hero
 	void runBattle();
 	void checkLossVictory(ui8 player);

+ 10 - 0
server/NetPacksServer.cpp

@@ -6,6 +6,7 @@
 #include "../lib/map.h"
 #include "../lib/CGameState.h"
 #include "../lib/BattleState.h"
+#include "../lib/BattleAction.h"
 
 
 #define PLAYER_OWNS(id) (gh->getPlayerAt(c)==gh->getOwner(id))
@@ -209,6 +210,15 @@ bool MakeAction::applyGh( CGameHandler *gh )
 {
 	if(!GS(gh)->curB) ERROR_AND_RETURN;
 	if(gh->connections[GS(gh)->curB->getStack(GS(gh)->curB->activeStack)->owner] != c) ERROR_AND_RETURN;
+
+	if(GS(gh)->curB->tacticDistance)
+	{
+		if(ba.actionType != BattleAction::WALK  &&  ba.actionType != BattleAction::END_TACTIC_PHASE)
+			ERROR_AND_RETURN;
+		if(gh->connections[GS(gh)->curB->sides[GS(gh)->curB->tacticsSide]] != c) 
+			ERROR_AND_RETURN;
+	}
+
 	return gh->makeBattleAction(ba);
 }