瀏覽代碼

* added support for archery, offence and armorer secondary abilities
* hero's primary skills account for damage dealt by creatures in battle
* magical hero are given spellbook at the beginning
* added initial secondary skills for heroes
* minor fixes and improvements

mateuszb 17 年之前
父節點
當前提交
538ffb1579
共有 13 個文件被更改,包括 290 次插入29 次删除
  1. 68 6
      CGameState.cpp
  2. 1 1
      CGameState.h
  3. 7 7
      CHeroWindow.cpp
  4. 2 2
      CPlayerInterface.cpp
  5. 1 1
      client/Graphics.cpp
  6. 158 0
      config/heroes_sec_skills.txt
  7. 32 3
      hch/CHeroHandler.cpp
  8. 2 1
      hch/CHeroHandler.h
  9. 4 0
      hch/CObjectHandler.cpp
  10. 2 1
      hch/CObjectHandler.h
  11. 4 0
      map.cpp
  12. 7 7
      server/CGameHandler.cpp
  13. 2 0
      server/CGameHandler.h

+ 68 - 6
CGameState.cpp

@@ -56,13 +56,18 @@ CGObjectInstance * createObject(int id, int subid, int3 pos, int owner)
 			nobj->defInfo->blockMap[5] = 253;
 			nobj->defInfo->visitMap[5] = 2;
 			nobj->artifWorn[16] = 3;
+			if(nobj->type->heroType % 2 == 1) //it's a magical hero
+			{
+				nobj->artifWorn[17] = 0; //give him spellbook
+			}
 			nobj->portrait = subid;
 			nobj->primSkills.resize(4);
 			nobj->primSkills[0] = nobj->type->heroClass->initialAttack;
 			nobj->primSkills[1] = nobj->type->heroClass->initialDefence;
 			nobj->primSkills[2] = nobj->type->heroClass->initialPower;
 			nobj->primSkills[3] = nobj->type->heroClass->initialKnowledge;
-			nobj->mana = 10 * nobj->primSkills[3];
+			nobj->secSkills = nobj->type->secSkillsInit; //copying initial secondary skills
+			nobj->mana = 10 * nobj->getPrimSkillLevel(3);
 			return nobj;
 		}
 	case 98: //town
@@ -1000,7 +1005,10 @@ void CGameState::init(StartInfo * si, Mapa * map, int Seed)
 			vhi->exp=40+ran()%50;
 			vhi->level = 1;
 		}
-		if (vhi->level>1) ;//TODO dodac um dr, ale potrzebne los
+		if(vhi->secSkills.size() == 1 && vhi->secSkills[0] == std::make_pair(-1, -1)) //set secondary skills to default
+		{
+			vhi->secSkills = vhi->type->secSkillsInit;
+		}
 		if ((!vhi->primSkills.size()) || (vhi->primSkills[0]<0))
 		{
 			if (vhi->primSkills.size()<PRIMARY_SKILLS)
@@ -1010,7 +1018,7 @@ void CGameState::init(StartInfo * si, Mapa * map, int Seed)
 			vhi->primSkills[2] = vhi->type->heroClass->initialPower;
 			vhi->primSkills[3] = vhi->type->heroClass->initialKnowledge;
 		}
-		vhi->mana = vhi->primSkills[3]*10;
+		vhi->mana = vhi->getPrimSkillLevel(3)*10;
 		if (!vhi->name.length())
 		{
 			vhi->name = vhi->type->name;
@@ -1023,6 +1031,10 @@ void CGameState::init(StartInfo * si, Mapa * map, int Seed)
 			vhi->portrait = vhi->type->ID;
 
 		vhi->artifWorn[16] = 3;
+		if(vhi->type->heroType % 2 == 1) //it's a magical hero
+		{
+			vhi->artifWorn[17] = 0; //give him spellbook
+		}
 
 		//initial army
 		if (!vhi->army.slots.size()) //standard army
@@ -1050,6 +1062,7 @@ void CGameState::init(StartInfo * si, Mapa * map, int Seed)
 					vhi->army.slots[x-pom2].second = (ran()%pom)+vhi->type->lowStack[x];
 				else 
 					vhi->army.slots[x-pom2].second = +vhi->type->lowStack[x];
+				vhi->army.formation = false;
 			}
 		}
 
@@ -1347,9 +1360,9 @@ std::set<int3> CGameState::tilesToReveal(int3 pos, int radious, int player)
 	return ret;
 }
 
-int BattleInfo::calculateDmg(const CStack* attacker, const CStack* defender)
+int BattleInfo::calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting)
 {
-	int attackDefenseBonus = attacker->creature->attack - defender->creature->defence;
+	int attackDefenseBonus = attacker->creature->attack + (attackerHero ? attackerHero->getPrimSkillLevel(0) : 0) - (defender->creature->defence + (defendingHero ? defendingHero->getPrimSkillLevel(1) : 0));
 	int damageBase = 0;
 	if(attacker->creature->damageMax == attacker->creature->damageMin) //constant damage
 	{
@@ -1360,7 +1373,7 @@ int BattleInfo::calculateDmg(const CStack* attacker, const CStack* defender)
 		damageBase = rand()%(attacker->creature->damageMax - attacker->creature->damageMin) + attacker->creature->damageMin + 1;
 	}
 
-	float dmgBonusMultiplier = 1.0;
+	float dmgBonusMultiplier = 1.0f;
 	if(attackDefenseBonus < 0) //decreasing dmg
 	{
 		if(0.02f * (-attackDefenseBonus) > 0.3f)
@@ -1383,6 +1396,55 @@ int BattleInfo::calculateDmg(const CStack* attacker, const CStack* defender)
 			dmgBonusMultiplier += 0.05f * attackDefenseBonus;
 		}
 	}
+	//handling secondary abilities
+	if(attackerHero)
+	{
+		if(shooting)
+		{
+			switch(attackerHero->getSecSkillLevel(1)) //archery
+			{
+			case 1: //basic
+				dmgBonusMultiplier *= 1.1f;
+				break;
+			case 2: //advanced
+				dmgBonusMultiplier *= 1.25f;
+				break;
+			case 3: //expert
+				dmgBonusMultiplier *= 1.5f;
+				break;
+			}
+		}
+		else
+		{
+			switch(attackerHero->getSecSkillLevel(22)) //offence
+			{
+			case 1: //basic
+				dmgBonusMultiplier *= 1.1f;
+				break;
+			case 2: //advanced
+				dmgBonusMultiplier *= 1.2f;
+				break;
+			case 3: //expert
+				dmgBonusMultiplier *= 1.3f;
+				break;
+			}
+		}
+	}
+	if(defendingHero)
+	{
+		switch(defendingHero->getSecSkillLevel(23)) //armourer
+		{
+		case 1: //basic
+			dmgBonusMultiplier *= 0.95f;
+			break;
+		case 2: //advanced
+			dmgBonusMultiplier *= 0.9f;
+			break;
+		case 3: //expert
+			dmgBonusMultiplier *= 0.85f;
+			break;
+		}
+	}
 
 	return (float)damageBase * (float)attacker->amount * dmgBonusMultiplier;
 }

+ 1 - 1
CGameState.h

@@ -73,7 +73,7 @@ struct DLL_EXPORT BattleInfo
 
 	static signed char mutualPosition(int hex1, int hex2); //returns info about mutual position of given hexes (-1 - they're distant, 0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left)
 	static std::vector<int> neighbouringTiles(int hex);
-	static int calculateDmg(const CStack* attacker, const CStack* defender); //TODO: add additional conditions and require necessary data
+	static int calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting); //TODO: add additional conditions and require necessary data
 	void calculateCasualties(std::set<std::pair<ui32,si32> > *casualties);
 };
 

+ 7 - 7
CHeroWindow.cpp

@@ -220,7 +220,7 @@ void CHeroWindow::setHero(const CGHeroInstance *Hero)
 
 	for(int g=0; g<primSkillAreas.size(); ++g)
 	{
-		primSkillAreas[g]->bonus = hero->primSkills[g];
+		primSkillAreas[g]->bonus = hero->getPrimSkillLevel(g);
 	}
 	for(int g=0; g<hero->secSkills.size(); ++g)
 	{
@@ -236,7 +236,7 @@ void CHeroWindow::setHero(const CGHeroInstance *Hero)
 	sprintf(bufor, CGI->generaltexth->allTexts[2].substr(1, CGI->generaltexth->allTexts[2].size()-2).c_str(), hero->level, CGI->heroh->reqExp(hero->level+1), hero->exp);
 	expArea->text = std::string(bufor);
 
-	sprintf(bufor, CGI->generaltexth->allTexts[205].substr(1, CGI->generaltexth->allTexts[205].size()-2).c_str(), hero->name.c_str(), hero->mana, hero->primSkills[3]*10);
+	sprintf(bufor, CGI->generaltexth->allTexts[205].substr(1, CGI->generaltexth->allTexts[205].size()-2).c_str(), hero->name.c_str(), hero->mana, hero->getPrimSkillLevel(3)*10);
 	spellPointsArea->text = std::string(bufor);
 
 	for(int g=0; g<artWorn.size(); ++g)
@@ -551,19 +551,19 @@ void CHeroWindow::redrawCurBack()
 
 	//printing primary skills' amounts
 	std::stringstream primarySkill1;
-	primarySkill1<<curHero->primSkills[0];
+	primarySkill1<<curHero->getPrimSkillLevel(0);
 	CSDL_Ext::printAtMiddle(primarySkill1.str(), 53, 165, TNRB16, zwykly, curBack);
 
 	std::stringstream primarySkill2;
-	primarySkill2<<curHero->primSkills[1];
+	primarySkill2<<curHero->getPrimSkillLevel(1);
 	CSDL_Ext::printAtMiddle(primarySkill2.str(), 123, 165, TNRB16, zwykly, curBack);
 
 	std::stringstream primarySkill3;
-	primarySkill3<<curHero->primSkills[2];
+	primarySkill3<<curHero->getPrimSkillLevel(2);
 	CSDL_Ext::printAtMiddle(primarySkill3.str(), 193, 165, TNRB16, zwykly, curBack);
 
 	std::stringstream primarySkill4;
-	primarySkill4<<curHero->primSkills[3];
+	primarySkill4<<curHero->getPrimSkillLevel(3);
 	CSDL_Ext::printAtMiddle(primarySkill4.str(), 263, 165, TNRB16, zwykly, curBack);
 
 	blitAt(graphics->luck42->ourImages[curHero->getCurrentLuck()+3].bitmap, 239, 182, curBack);
@@ -650,7 +650,7 @@ void CHeroWindow::redrawCurBack()
 	CSDL_Ext::printAt(expstr.str(), 69, 247, GEOR16, zwykly, curBack);
 	CSDL_Ext::printAt(CGI->generaltexth->jktexts[7].substr(1, CGI->generaltexth->jktexts[7].size()-2), 212, 231, GEOR13, tytulowy, curBack);
 	std::stringstream manastr;
-	manastr<<curHero->mana<<'/'<<curHero->primSkills[3]*10;
+	manastr<<curHero->mana<<'/'<<curHero->getPrimSkillLevel(3)*10;
 	CSDL_Ext::printAt(manastr.str(), 212, 247, GEOR16, zwykly, curBack);
 }
 

+ 2 - 2
CPlayerInterface.cpp

@@ -134,8 +134,8 @@ void CGarrisonSlot::clickRight (tribool down)
 			pom = new StackState();
 			const CGHeroInstance *h = static_cast<const CGHeroInstance *>(getObj());
 			pom->currentHealth = 0;
-			pom->attackBonus = h->primSkills[0];
-			pom->defenseBonus = h->primSkills[1];
+			pom->attackBonus = h->getPrimSkillLevel(0);
+			pom->defenseBonus = h->getPrimSkillLevel(1);
 			pom->luck = h->getCurrentLuck();
 			pom->morale = h->getCurrentMorale();
 		}

+ 1 - 1
client/Graphics.cpp

@@ -27,7 +27,7 @@ SDL_Surface * Graphics::drawPrimarySkill(const CGHeroInstance *curh, SDL_Surface
 	char * buf = new char[10];
 	for (int i=from;i<to;i++)
 	{
-		SDL_itoa(curh->primSkills[i],buf,10);
+		SDL_itoa(curh->getPrimSkillLevel(i),buf,10);
 		printAtMiddle(buf,84+28*i,68,GEOR13,zwykly,ret);
 	}
 	delete[] buf;

+ 158 - 0
config/heroes_sec_skills.txt

@@ -0,0 +1,158 @@
+//heroes'_inintial_set_of_secondary_abilities_format:_heroID_numberOfInitialSecSkills_(Skill_ID,_skill_lvl_for_every_spell)
+0 2 6 1 1 1
+1 2 6 1 1 1
+2 2 6 1 23 1
+3 2 6 1 5 1
+4 2 6 1 13 1
+5 2 6 1 22 1
+6 2 6 1 19 1
+7 2 6 1 19 1
+8 2 7 1 27 1
+9 2 7 1 4 1
+10 2 7 1 13 1
+11 1 7 2
+12 2 7 1 8 1
+13 2 7 1 11 1
+14 2 7 1 21 1
+15 2 7 1 24 1
+16 2 6 1 23 1
+17 2 9 1 26 1
+18 1 1 2
+19 2 4 1 6 1
+20 1 26 2
+21 2 1 1 22 1
+22 2 0 1 26 1
+23 2 1 1 2 1
+24 2 7 1 18 1
+25 2 7 2 10 1
+26 2 7 1 24 1
+27 2 7 1 27 1
+28 2 7 1 11 1
+29 2 7 1 9 1
+30 2 7 1 25 1
+31 2 7 1 3 1
+32 2 3 1 8 1
+33 1 18 2
+34 2 8 1 25 1
+35 2 18 1 23 1
+36 2 8 1 19 1
+37 2 18 1 26 1
+38 2 8 1 22 1
+39 2 18 1 24 1
+40 1 7 2
+41 2 7 1 8 1
+42 2 7 1 11 1
+43 2 7 1 24 1
+44 2 7 1 10 1
+45 2 7 1 25 1
+46 2 7 1 4 1
+47 2 7 1 18 1
+48 1 3 2
+49 2 7 1 18 1
+50 1 23 2
+51 2 19 1 26 1
+52 2 18 1 22 1
+53 2 1 1 3 1
+54 2 1 1 2 1
+55 1 22 2
+56 2 7 1 24 1
+57 2 7 1 18 1
+58 2 7 1 8 1
+59 2 7 1 10 1
+60 2 7 1 21 1
+61 2 7 1 11 1
+62 2 7 1 25 1
+63 2 7 1 6 1
+64 2 12 1 26 1
+65 2 12 1 20 1
+66 2 12 1 21 1
+67 2 12 1 19 1
+68 2 12 1 22 1
+69 1 12 2
+70 2 12 1 22 1
+71 2 12 1 23 1
+72 2 12 1 18 1
+73 2 12 1 7 1
+74 2 12 1 25 1
+75 2 12 1 11 1
+76 2 12 1 8 1
+77 2 12 1 21 1
+78 1 12 2
+79 2 12 1 24 1
+80 2 3 1 6 1
+81 2 20 1 22 1
+82 2 19 1 22 1
+83 2 6 1 26 1
+84 1 22 2
+85 2 2 1 19 1
+86 2 6 1 18 1
+87 2 19 1 22 1
+88 2 7 1 18 1
+89 2 7 1 8 1
+90 2 7 1 25 1
+91 1 7 2
+92 2 7 1 11 1
+93 2 7 1 3 2
+94 2 7 1 24 1
+95 2 7 1 21 1
+96 2 22 1 10 1
+97 2 22 1 20 1
+98 2 22 1 1 1
+99 2 22 1 3 1
+100 2 22 1 0 1
+101 2 22 1 26 1
+102 1 22 2
+103 2 22 1 19 1
+104 2 7 1 25 1
+105 2 7 1 6 1
+106 2 7 1 2 1
+107 2 7 1 19 1
+108 2 7 1 20 1
+109 2 7 1 22 1
+110 2 7 1 11 1
+111 2 7 1 26 1
+112 2 23 1 26 1
+113 2 23 1 6 1
+114 2 23 1 1 1
+115 1 23 2
+116 2 23 1 22 1
+117 2 23 1 0 1
+118 2 23 1 20 1
+119 2 23 1 3 1
+120 1 7 2
+121 2 7 1 8 1
+122 2 7 1 5 1
+123 2 7 1 27 1
+124 2 7 1 21 1
+125 2 7 1 25 1
+126 2 7 1 24 1
+127 2 7 1 11 1
+128 2 20 1 22 1
+129 2 13 1 19 1
+130 2 20 1 22 1
+131 1 19 2
+132 2 2 1 22 1
+133 2 13 1 19 1
+134 1 22 2
+135 2 19 1 21 1
+136 2 7 1 14 1
+137 2 7 1 15 1
+138 2 7 1 16 1
+139 2 7 1 17 1
+140 2 7 1 14 1
+141 2 7 1 15 1
+142 2 7 1 16 1
+143 2 7 1 17 1
+144 1 6 2
+145 2 7 1 14 3
+146 2 6 1 22 1
+147 1 7 2
+148 2 1 1 6 1
+149 1 22 2
+150 1 12 2
+151 2 13 1 19 1
+152 2 6 1 23 1
+153 2 13 1 19 1
+154 2 19 1 22 1
+155 2 6 1 19 1
+-1

+ 32 - 3
hch/CHeroHandler.cpp

@@ -14,17 +14,17 @@ CHeroClass::CHeroClass()
 CHeroClass::~CHeroClass()
 {
 }
-int CHeroClass::chooseSecSkill(std::set<int> possibles) //picks secondary skill out from given possibilities
+int CHeroClass::chooseSecSkill(const std::set<int> & possibles) const //picks secondary skill out from given possibilities
 {
 	if(possibles.size()==1)
 		return *possibles.begin();
 	int totalProb = 0;
-	for(std::set<int>::iterator i=possibles.begin(); i!=possibles.end(); i++)
+	for(std::set<int>::const_iterator i=possibles.begin(); i!=possibles.end(); i++)
 	{
 		totalProb += proSec[*i];
 	}
 	int ran = rand()%totalProb;
-	for(std::set<int>::iterator i=possibles.begin(); i!=possibles.end(); i++)
+	for(std::set<int>::const_iterator i=possibles.begin(); i!=possibles.end(); i++)
 	{
 		ran -= proSec[*i];
 		if(ran<0)
@@ -111,6 +111,35 @@ void CHeroHandler::loadHeroes()
 		nher->ID = heroes.size();
 		heroes.push_back(nher);
 	}
+	//loading initial secondary skills
+	std::ifstream inp;
+	inp.open("config" PATHSEPARATOR "heroes_sec_skills.txt", std::ios_base::in|std::ios_base::binary);
+	if(!inp.is_open())
+	{
+		tlog1<<"missing file: config/heroes_sec_skills.txt"<<std::endl;
+	}
+	else
+	{
+		inp>>dump;
+		int hid; //ID of currently read hero
+		int secQ; //number of secondary abilities
+		while(true)
+		{
+			inp>>hid;
+			if(hid == -1)
+				break;
+			inp>>secQ;
+			for(int g=0; g<secQ; ++g)
+			{
+				int a, b;
+				inp>>a; inp>>b;
+				heroes[hid]->secSkillsInit.push_back(std::make_pair(a, b));
+			}
+		}
+		inp.close();
+	}
+	//initial skills loaded
+
 	loadSpecialAbilities();
 	loadBiographies();
 	loadHeroClasses();

+ 2 - 1
hch/CHeroHandler.h

@@ -20,6 +20,7 @@ public:
 	bool isAllowed; //true if we can play with this hero (depends on map)
 	CHeroClass * heroClass;
 	EHeroClasses heroType; //hero class
+	std::vector<std::pair<int,int> > secSkillsInit; //initial secondaryskills; first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert)
 	//bool operator<(CHero& drugi){if (ID < drugi.ID) return true; else return false;}
 };
 
@@ -35,7 +36,7 @@ public:
 	int selectionProbability[9]; //probability of selection in towns
 	std::vector<int> terrCosts; //default costs of going through terrains: dirt, sand, grass, snow, swamp, rough, subterrain, lava, water, rock; -1 means terrain is imapassable
 	CDefHandler * moveAnim; //added group 10: up - left, 11 - left and 12 - left down // 13 - up-left standing; 14 - left standing; 15 - left down standing
-	int chooseSecSkill(std::set<int> possibles); //picks secondary skill out from given possibilities
+	int chooseSecSkill(const std::set<int> & possibles) const; //picks secondary skill out from given possibilities
 	CHeroClass();
 	~CHeroClass();
 };

+ 4 - 0
hch/CObjectHandler.cpp

@@ -275,6 +275,10 @@ int CGHeroInstance::getCurrentMorale() const
 	//TODO: write it
 	return 0;
 }
+int CGHeroInstance::getPrimSkillLevel(int id) const
+{
+	return primSkills[id];
+}
 int CGHeroInstance::getSecSkillLevel(const int & ID) const
 {
 	for(int i=0;i<secSkills.size();i++)

+ 2 - 1
hch/CObjectHandler.h

@@ -92,7 +92,7 @@ public:
 	int portrait; //may be custom
 	int mana; // remaining spell points
 	std::vector<int> primSkills; //0-attack, 1-defence, 2-spell power, 3-knowledge
-	std::vector<std::pair<int,int> > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert)
+	std::vector<std::pair<int,int> > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities
 	int movement; //remaining movement points
 	int identifier; //from the map file
 	bool sex;
@@ -125,6 +125,7 @@ public:
 	bool canWalkOnSea() const;
 	int getCurrentLuck() const;
 	int getCurrentMorale() const;
+	int getPrimSkillLevel(int id) const;
 	int getSecSkillLevel(const int & ID) const; //0 - no skill
 	ui32 getArtAtPos(ui16 pos) const; //-1 - no artifact
 	void setArtAtPos(ui16 pos, int art);

+ 4 - 0
map.cpp

@@ -1052,6 +1052,10 @@ void Mapa::loadHero( CGObjectInstance * &nobj, unsigned char * bufor, int &i )
 			nhi->secSkills[yy].second = readNormalNr(bufor,i, 1); ++i;
 		}
 	}
+	else //set default secondary skils
+	{
+		nhi->secSkills.push_back(std::make_pair(-1, -1));
+	}
 	if(readChar(bufor,i))//true if hero has nonstandard garrison
 		nhi->army = readCreatureSet(bufor,i,7,(version>RoE));
 	nhi->army.formation =bufor[i]; ++i; //formation

+ 7 - 7
server/CGameHandler.cpp

@@ -349,11 +349,11 @@ void CGameHandler::startBattle(CCreatureSet army1, CCreatureSet army2, int3 tile
 	delete battleResult.data;
 
 }
-void prepareAttack(BattleAttack &bat, CStack *att, CStack *def)
+void CGameHandler::prepareAttack(BattleAttack &bat, CStack *att, CStack *def, bool shooting)
 {
 	bat.stackAttacking = att->ID;
 	bat.bsa.stackAttacked = def->ID;
-	bat.bsa.damageAmount = BattleInfo::calculateDmg(att, def);//counting dealt damage
+	bat.bsa.damageAmount = BattleInfo::calculateDmg(att, def, gs->getHero(att->attackerOwned ? gs->curB->hero1 : gs->curB->hero2), gs->getHero(def->attackerOwned ? gs->curB->hero1 : gs->curB->hero2), shooting);//counting dealt damage
 
 	//applying damages
 	bat.bsa.killedAmount = bat.bsa.damageAmount / def->creature->hitPoints;
@@ -948,14 +948,14 @@ upgend:
 								return;
 
 							BattleAttack bat;
-							prepareAttack(bat,curStack,stackAtEnd);
+							prepareAttack(bat,curStack,stackAtEnd,false);
 							sendAndApply(&bat);
 							//counterattack
 							if(!vstd::contains(curStack->abilities,NO_ENEMY_RETALIATION)
 								&& stackAtEnd->alive()
 								&& stackAtEnd->counterAttacks	) //TODO: support for multiple retaliatons per turn
 							{
-								prepareAttack(bat,stackAtEnd,curStack);
+								prepareAttack(bat,stackAtEnd,curStack,false);
 								bat.flags |= 2;
 								sendAndApply(&bat);
 							}
@@ -965,7 +965,7 @@ upgend:
 								&& stackAtEnd->alive()  )
 							{
 								bat.flags = 0;
-								prepareAttack(bat,curStack,stackAtEnd);
+								prepareAttack(bat,curStack,stackAtEnd,false);
 								sendAndApply(&bat);
 							}
 							sendDataToClients(ui16(3008)); //end movement and attack
@@ -981,13 +981,13 @@ upgend:
 								*destStack= gs->curB->getStackT(ba.destinationTile);
 
 							BattleAttack bat;
-							prepareAttack(bat,curStack,destStack);
+							prepareAttack(bat,curStack,destStack,true);
 							bat.flags |= 1;
 
 							if(vstd::contains(curStack->abilities,TWICE_ATTACK)
 								&& curStack->alive())
 							{
-								prepareAttack(bat,curStack,destStack);
+								prepareAttack(bat,curStack,destStack,true);
 								sendAndApply(&bat);
 							}
 

+ 2 - 0
server/CGameHandler.h

@@ -12,6 +12,7 @@ struct StartInfo;
 class CCPPObjectScript;
 class CScriptCallback;
 struct BattleResult;
+struct BattleAttack;
 template <typename T> struct CPack;
 template <typename T> struct Query;
 class CGHeroInstance;
@@ -56,6 +57,7 @@ class CGameHandler
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
 	void moveStack(int stack, int dest);
 	void startBattle(CCreatureSet army1, CCreatureSet army2, int3 tile, CGHeroInstance *hero1, CGHeroInstance *hero2, boost::function<void(BattleResult*)> cb); //use hero=NULL for no hero
+	void prepareAttack(BattleAttack &bat, CStack *att, CStack *def, bool shooting); //if last parameter is true, attack is by shooting, if false it's a melee attack
 
 	void checkForBattleEnd( std::vector<CStack*> &stacks );
 	void setupBattle( BattleInfo * curB, int3 tile, CCreatureSet &army1, CCreatureSet &army2, CGHeroInstance * hero1, CGHeroInstance * hero2 );