2
0
Эх сурвалжийг харах

Refactored player-specific data into single struct BattleState.

Michał W. Urbańczyk 12 жил өмнө
parent
commit
d8a27d8f3c

+ 7 - 6
Global.h

@@ -329,13 +329,14 @@ namespace vstd
 		return -1;
 	}
 
-	//Func(T1,T2) must say if these elements matches
-	template <typename T1, typename T2, typename Func>
-	int find_pos(const std::vector<T1> & c, const T2 &s, const Func &f)
+	//Func f tells if element matches
+	template <typename Container, typename Func>
+	int find_pos_if(const Container & c, const Func &f)
 	{
-		for(size_t i=0; i < c.size(); ++i)
-			if(f(c[i],s))
-				return i;
+		auto ret = boost::range::find_if(c, f);
+		if(ret != std::end(c))
+			return std::distance(std::begin(c), ret);
+
 		return -1;
 	}
 

+ 24 - 17
client/Client.cpp

@@ -570,51 +570,58 @@ void CClient::battleStarted(const BattleInfo * info)
 {
 	for(auto &battleCb : battleCallbacks)
 	{
-		if(vstd::contains(info->sides, battleCb.first)  ||  battleCb.first >= PlayerColor::PLAYER_LIMIT)
+		if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })  
+			||  battleCb.first >= PlayerColor::PLAYER_LIMIT)
+		{
 			battleCb.second->setBattle(info);
+		}
 	}
 // 	for(ui8 side : info->sides)
 // 		if(battleCallbacks.count(side))
 // 			battleCallbacks[side]->setBattle(info);
 
 	shared_ptr<CPlayerInterface> att, def;
+	auto &leftSide = info->sides[0], &rightSide = info->sides[1];
+
 
 	//If quick combat is not, do not prepare interfaces for battleint
 	if(!settings["adventure"]["quickCombat"].Bool())
 	{
-		if(vstd::contains(playerint, info->sides[0]) && playerint[info->sides[0]]->human)
-			att = std::dynamic_pointer_cast<CPlayerInterface>( playerint[info->sides[0]] );
+		if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
+			att = std::dynamic_pointer_cast<CPlayerInterface>( playerint[leftSide.color] );
 
-		if(vstd::contains(playerint, info->sides[1]) && playerint[info->sides[1]]->human)
-			def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[info->sides[1]] );
+		if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
+			def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[rightSide.color] );
 	}
 
 	if(!gNoGUI && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL))
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
-		new CBattleInterface(info->belligerents[0], info->belligerents[1], info->heroes[0], info->heroes[1],
+		new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
 			Rect((screen->w - 800)/2, 
 			     (screen->h - 600)/2, 800, 600), att, def);
 	}
 
-	if(vstd::contains(battleints,info->sides[0]))
-		battleints[info->sides[0]]->battleStart(info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], 0);
-	if(vstd::contains(battleints,info->sides[1]))
-		battleints[info->sides[1]]->battleStart(info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], 1);
-	if(vstd::contains(battleints,PlayerColor::UNFLAGGABLE))
-		battleints[PlayerColor::UNFLAGGABLE]->battleStart(info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], 1);
+	auto callBattleStart = [&](PlayerColor color, ui8 side){
+		if(vstd::contains(battleints, color))
+			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);
+	};
+
+	callBattleStart(leftSide.color, 0);
+	callBattleStart(leftSide.color, 1);
+	callBattleStart(PlayerColor::UNFLAGGABLE, 1);
 
-	if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide]))
+	if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color))
 	{
-		boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide]]);
+		boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide].color]);
 	}
 }
 
 void CClient::battleFinished()
 {
-	for(PlayerColor side : gs->curB->sides)
-		if(battleCallbacks.count(side))
-			battleCallbacks[side]->setBattle(nullptr);
+	for(auto & side : gs->curB->sides)
+		if(battleCallbacks.count(side.color))
+			battleCallbacks[side.color]->setBattle(nullptr);
 }
 
 void CClient::loadNeutralBattleAI()

+ 5 - 3
client/NetPacksClient.cpp

@@ -90,8 +90,8 @@
 
 
 #define BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(function,...) 				\
-	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[0], function, __VA_ARGS__)	\
-	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[1], function, __VA_ARGS__)	\
+	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[0].color, function, __VA_ARGS__)	\
+	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[1].color, function, __VA_ARGS__)	\
 	BATTLE_INTERFACE_CALL_RECEIVERS(function, __VA_ARGS__)
 /*
  * NetPacksClient.cpp, part of VCMI engine
@@ -624,7 +624,9 @@ void BattleSetActiveStack::applyCl( CClient *cl )
 	PlayerColor playerToCall; //player that will move activated stack
 	if( activated->hasBonusOfType(Bonus::HYPNOTIZED) )
 	{
-		playerToCall = ( GS(cl)->curB->sides[0] == activated->owner ? GS(cl)->curB->sides[1] : GS(cl)->curB->sides[0] );
+		playerToCall = ( GS(cl)->curB->sides[0].color == activated->owner 
+			? GS(cl)->curB->sides[1].color 
+			: GS(cl)->curB->sides[0].color );
 	}
 	else
 	{

+ 1 - 1
client/battle/CBattleInterface.cpp

@@ -101,7 +101,7 @@ void CBattleInterface::addNewAnim(CBattleAnimation * anim)
 }
 
 CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSet * army2, 
-								   CGHeroInstance *hero1, CGHeroInstance *hero2, 
+								   const CGHeroInstance *hero1, const CGHeroInstance *hero2, 
 								   const SDL_Rect & myRect, 
 								   shared_ptr<CPlayerInterface> att, shared_ptr<CPlayerInterface> defen)
 	: background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),

+ 1 - 1
client/battle/CBattleInterface.h

@@ -243,7 +243,7 @@ public:
 	ui32 animIDhelper; //for giving IDs for animations
 	static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
 
-	CBattleInterface(const CCreatureSet * army1, const CCreatureSet * army2, CGHeroInstance *hero1, CGHeroInstance *hero2, const SDL_Rect & myRect, shared_ptr<CPlayerInterface> att, shared_ptr<CPlayerInterface> defen); //c-tor
+	CBattleInterface(const CCreatureSet * army1, const CCreatureSet * army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, shared_ptr<CPlayerInterface> att, shared_ptr<CPlayerInterface> defen); //c-tor
 	~CBattleInterface(); //d-tor
 
 	//std::vector<TimeInterested*> timeinterested; //animation handling

+ 54 - 29
lib/BattleState.cpp

@@ -140,7 +140,7 @@ int BattleInfo::calculateSpellDuration( const CSpell * spell, const CGHeroInstan
 CStack * BattleInfo::generateNewStack(const CStackInstance &base, bool attackerOwned, SlotID slot, BattleHex position) const
 {
 	int stackID = getIdForNewStack();
-	PlayerColor owner = attackerOwned ? sides[0] : sides[1];
+	PlayerColor owner = sides[attackerOwned ? 0 : 1].color;
 	assert((owner >= PlayerColor::PLAYER_LIMIT)  ||
 		   (base.armyObj && base.armyObj->tempOwner == owner));
 
@@ -153,7 +153,7 @@ CStack * BattleInfo::generateNewStack(const CStackInstance &base, bool attackerO
 CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor &base, bool attackerOwned, SlotID slot, BattleHex position) const
 {
 	int stackID = getIdForNewStack();
-	PlayerColor owner = attackerOwned ? sides[0] : sides[1];
+	PlayerColor owner = sides[attackerOwned ? 0 : 1].color;
 	auto  ret = new CStack(&base, owner, stackID, attackerOwned, slot);
 	ret->position = position;
 	ret->state.insert(EBattleStackState::ALIVE);  //alive state indication
@@ -202,7 +202,7 @@ const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive)
 			) )
 		{
 			if (elem->alive())
-				return elem; //we prefer living stacks - there cna be only one stack on te tile, so return it imediately
+				return elem; //we prefer living stacks - there can be only one stack on the tile, so return it immediately
 			else if (!onlyAlive)
 				stack = elem; //dead stacks are only accessible when there's no alive stack on this tile
 		}
@@ -212,15 +212,17 @@ const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive)
 
 const CGHeroInstance * BattleInfo::battleGetOwner(const CStack * stack) const
 {
-	return heroes[!stack->attackerOwned];
+	return sides[!stack->attackerOwned].hero;
 }
 
 void BattleInfo::localInit()
 {
-	belligerents[0]->battle = belligerents[1]->battle = this;
-
-	for(CArmedInstance *b : belligerents)
-		b->attachTo(this);
+	for(int i = 0; i < 2; i++)
+	{
+		auto armyObj = battleGetArmyObject(i);
+		armyObj->battle = this;
+		armyObj->attachTo(this);
+	}
 
 	for(CStack *s : stacks)
 		localInitStack(s);
@@ -237,7 +239,7 @@ void BattleInfo::localInitStack(CStack * s)
 	}
 	else //attach directly to obj to which stack belongs and creature type
 	{
-		CArmedInstance *army = belligerents[!s->attackerOwned];
+		CArmedInstance *army = battleGetArmyObject(!s->attackerOwned);
 		s->attachTo(army);
 		assert(s->type);
 		s->attachTo(const_cast<CCreature*>(s->type));
@@ -349,23 +351,17 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 {
 	CMP_stack cmpst;
 	auto curB = new BattleInfo;
-	curB->castSpells[0] = curB->castSpells[1] = 0;
-	curB->sides[0] = armies[0]->tempOwner;
-	curB->sides[1] = armies[1]->tempOwner;
-	if(curB->sides[1] == PlayerColor::UNFLAGGABLE)
-		curB->sides[1] = PlayerColor::NEUTRAL;
+
+	for(auto i = 0u; i < curB->sides.size(); i++)
+		curB->sides[i].init(heroes[i], armies[i]);
+
 
 	std::vector<CStack*> & stacks = (curB->stacks);
 
 	curB->tile = tile;
 	curB->battlefieldType = battlefieldType;
-	curB->belligerents[0] = const_cast<CArmedInstance*>(armies[0]);
-	curB->belligerents[1] = const_cast<CArmedInstance*>(armies[1]);
-	curB->heroes[0] = const_cast<CGHeroInstance*>(heroes[0]);
-	curB->heroes[1] = const_cast<CGHeroInstance*>(heroes[1]);
 	curB->round = -2;
 	curB->activeStack = -1;
-	curB->enchanterCounter[0] = curB->enchanterCounter[1] = 0; //ready to cast
 
 	if(town)
 	{
@@ -664,7 +660,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 	//////////////////////////////////////////////////////////////////////////
 
 	//tactics
-	bool isTacticsAllowed = !creatureBank; //no tactics in crebanks
+	bool isTacticsAllowed = !creatureBank; //no tactics in creature banks
 
 	int tacticLvls[2] = {0};
 	for(int i = 0; i < ARRAY_COUNT(tacticLvls); i++)
@@ -687,7 +683,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 	for(int i = 0; i < 2; i++)
 	{
 		TNodes nodes;
-		curB->belligerents[i]->getRedAncestors(nodes);
+		curB->battleGetArmyObject(i)->getRedAncestors(nodes);
 		for(CBonusSystemNode *n : nodes)
 		{
 			for(Bonus *b : n->getExportedBonusList())
@@ -697,7 +693,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 					auto bCopy = new Bonus(*b);
 					bCopy->effectRange = Bonus::NO_LIMIT;
 					bCopy->propagator.reset();
-					bCopy->limiter.reset(new StackOwnerLimiter(curB->sides[!i]));
+					bCopy->limiter.reset(new StackOwnerLimiter(curB->sides[!i].color));
 					curB->addNewBonus(bCopy);
 				}
 			}
@@ -709,11 +705,12 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 
 const CGHeroInstance * BattleInfo::getHero( PlayerColor player ) const
 {
-	assert(sides[0] == player || sides[1] == player);
-	if(heroes[0] && heroes[0]->getOwner() == player)
-		return heroes[0];
+	for(int i = 0; i < sides.size(); i++)
+		if(sides[i].color == player)
+			return sides[i].hero;
 
-	return heroes[1];
+	logGlobal->errorStream() << "Player " << player << " is not in battle!";
+	return nullptr;
 }
 
 std::vector<ui32> BattleInfo::calculateResistedStacks(const CSpell * sp, const CGHeroInstance * caster, const CGHeroInstance * hero2, const std::vector<const CStack*> affectedCreatures, PlayerColor casterSideOwner, ECastingMode::ECastingMode mode, int usedSpellPower, int spellLevel) const
@@ -767,13 +764,13 @@ std::vector<ui32> BattleInfo::calculateResistedStacks(const CSpell * sp, const C
 
 PlayerColor BattleInfo::theOtherPlayer(PlayerColor player) const
 {
-	return sides[!whatSide(player)];
+	return sides[!whatSide(player)].color;
 }
 
 ui8 BattleInfo::whatSide(PlayerColor player) const
 {
-	for(int i = 0; i < ARRAY_COUNT(sides); i++)
-		if(sides[i] == player)
+	for(int i = 0; i < sides.size(); i++)
+		if(sides[i].color == player)
 			return i;
 
     logGlobal->warnStream() << "BattleInfo::whatSide: Player " << player << " is not in battle!";
@@ -841,6 +838,16 @@ BattleInfo::BattleInfo()
 	setNodeType(BATTLE);
 }
 
+CArmedInstance * BattleInfo::battleGetArmyObject(ui8 side) const
+{
+	return const_cast<CArmedInstance*>(CBattleInfoEssentials::battleGetArmyObject(side));
+}
+
+CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
+{
+	return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side));
+}
+
 CStack::CStack(const CStackInstance *Base, PlayerColor O, int I, bool AO, SlotID S)
 	: base(Base), ID(I), owner(O), slot(S), attackerOwned(AO),
 	counterAttacks(1)
@@ -1287,3 +1294,21 @@ CMP_stack::CMP_stack( int Phase /*= 1*/, int Turn )
 	turn = Turn;
 }
 
+
+SideInBattle::SideInBattle()
+{
+	hero = nullptr;
+	armyObject = nullptr;
+	castSpellsCount = 0;
+	enchanterCounter = 0;
+}
+
+void SideInBattle::init(const CGHeroInstance *Hero, const CArmedInstance *Army)
+{
+	hero = Hero;
+	armyObject = Army;
+	color = armyObject->getOwner();
+
+	if(color == PlayerColor::UNFLAGGABLE)
+		color = PlayerColor::NEUTRAL;
+}

+ 28 - 11
lib/BattleState.h

@@ -41,22 +41,36 @@ struct DLL_LINKAGE SiegeInfo
 	}
 };
 
+struct DLL_LINKAGE SideInBattle
+{
+	PlayerColor color;
+	const CGHeroInstance *hero; //may be NULL if army is not commanded by hero
+	const CArmedInstance *armyObject; //adv. map object with army that participates in battle; may be same as hero
+
+	ui8 castSpellsCount; //how many spells each side has cast this turn
+	std::vector<const CSpell *> usedSpellsHistory; //every time hero casts spell, it's inserted here -> eagle eye skill
+	si16 enchanterCounter; //tends to pass through 0, so sign is needed
+
+	SideInBattle();
+	void init(const CGHeroInstance *Hero, const CArmedInstance *Army);
+
 
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & color & hero & armyObject;
+		h & castSpellsCount & usedSpellsHistory & enchanterCounter;
+	}
+};
 
 struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback
 {
-	PlayerColor sides[2]; //sides[0] - attacker, sides[1] - defender
+	std::array<SideInBattle, 2> sides; //sides[0] - attacker, sides[1] - defender
 	si32 round, activeStack, selectedStack;
 	CGTownInstance::EFortLevel siege;
 	const CGTownInstance * town; //used during town siege - id of attacked town; -1 if not town defence
 	int3 tile; //for background and bonuses
-	CGHeroInstance* heroes[2];
-	CArmedInstance *belligerents[2]; //may be same as heroes
 	std::vector<CStack*> stacks;
 	std::vector<shared_ptr<CObstacleInstance> > obstacles;
-	ui8 castSpells[2]; //how many spells each side has cast this turn [0] - attacker, [1] - defender
-	std::vector<const CSpell *> usedSpellsHistory[2]; //each time hero casts spell, it's inserted here -> eagle eye skill
-	si16 enchanterCounter[2]; //tends to pass through 0, so sign is needed
 	SiegeInfo si;
 
 	BFieldType battlefieldType; //like !!BA:B
@@ -67,10 +81,9 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & sides & round & activeStack & selectedStack & siege & town & tile & stacks & belligerents & obstacles
-			& castSpells & si & battlefieldType & terrainType;
-		h & heroes;
-		h & usedSpellsHistory & enchanterCounter;
+		h & sides;
+		h & round & activeStack & selectedStack & siege & town & tile & stacks & obstacles
+			& si & battlefieldType & terrainType;
 		h & tacticsSide & tacticDistance;
 		h & static_cast<CBonusSystemNode&>(*this);
 	}
@@ -82,6 +95,10 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 	//////////////////////////////////////////////////////////////////////////
 	CStack * getStackT(BattleHex tileID, bool onlyAlive = true);
 	CStack * getStack(int stackID, bool onlyAlive = true);
+	using CBattleInfoEssentials::battleGetArmyObject;
+	CArmedInstance * battleGetArmyObject(ui8 side) const; 
+	using CBattleInfoEssentials::battleGetFightingHero;
+	CGHeroInstance * battleGetFightingHero(ui8 side) const; 
 
 	const CStack * getNextStack() const; //which stack will have turn after current one
 	//void getStackQueue(std::vector<const CStack *> &out, int howMany, int turn = 0, int lastMoved = -1) const; //returns stack in order of their movement action
@@ -170,7 +187,7 @@ public:
 	bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
 	ui32 Speed(int turn = 0, bool useBind = false) const; //get speed of creature with all modificators
 	ui32 level() const;
-	si32 magicResistance() const; //include aura of resistance
+	si32 magicResistance() const override; //include aura of resistance
 	static void stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse);
 	std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
 	const CGHeroInstance *getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise

+ 25 - 7
lib/CBattleCallback.cpp

@@ -221,9 +221,9 @@ BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() co
 	RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID);
 	if(!player)
 		return BattlePerspective::ALL_KNOWING;
-	if(*player == getBattle()->sides[0])
+	if(*player == getBattle()->sides[0].color)
 		return BattlePerspective::LEFT_SIDE;
-	if(*player == getBattle()->sides[1])
+	if(*player == getBattle()->sides[1].color)
 		return BattlePerspective::RIGHT_SIDE;
 
     logGlobal->errorStream() << "Cannot find player " << *player << " in battle!";
@@ -281,12 +281,30 @@ const CGHeroInstance * CBattleInfoEssentials::battleGetFightingHero(ui8 side) co
 		return nullptr;
 	}
 
-	return getBattle()->heroes[side];
+	return getBattle()->sides[side].hero;
+}
+
+const CArmedInstance * CBattleInfoEssentials::battleGetArmyObject(ui8 side) const
+{
+	RETURN_IF_NOT_BATTLE(nullptr);
+	if(side > 1)
+	{
+		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " wrong argument!";
+		return nullptr;
+	}
+
+	if(!battleDoWeKnowAbout(side))
+	{
+		logGlobal->errorStream() << "FIXME: " <<  __FUNCTION__ << " access check ";
+		return nullptr;
+	}
+
+	return getBattle()->sides[side].armyObject;
 }
 
 InfoAboutHero CBattleInfoEssentials::battleGetHeroInfo( ui8 side ) const
 {
-	auto hero = getBattle()->heroes[side];
+	auto hero = getBattle()->sides[side].hero;
 	if(!hero)
 	{
         logGlobal->warnStream() << __FUNCTION__ << ": side " << (int)side << " does not have hero!";
@@ -299,7 +317,7 @@ InfoAboutHero CBattleInfoEssentials::battleGetHeroInfo( ui8 side ) const
 int CBattleInfoEssentials::battleCastSpells(ui8 side) const
 {
 	RETURN_IF_NOT_BATTLE(-1);
-	return getBattle()->castSpells[side];
+	return getBattle()->sides[side].castSpellsCount;
 }
 
 ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const
@@ -366,7 +384,7 @@ bool CBattleInfoEssentials::battleCanFlee(PlayerColor player) const
 ui8 CBattleInfoEssentials::playerToSide(PlayerColor player) const
 {
 	RETURN_IF_NOT_BATTLE(-1);
-	int ret = vstd::find_pos(getBattle()->sides, player);
+	int ret = vstd::find_pos_if(getBattle()->sides, [=](const SideInBattle &side){ return side.color == player; });
 	if(ret < 0)
         logGlobal->warnStream() << "Cannot find side for player " << player;
 
@@ -390,7 +408,7 @@ bool CBattleInfoEssentials::battleHasHero(ui8 side) const
 {
 	RETURN_IF_NOT_BATTLE(false);
 	assert(side < 2);
-	return getBattle()->heroes[side];
+	return getBattle()->sides[side].hero;
 }
 
 ui8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const

+ 2 - 0
lib/CBattleCallback.h

@@ -20,6 +20,7 @@ struct BattleInfo;
 struct CObstacleInstance;
 class IBonusBearer;
 struct InfoAboutHero;
+class CArmedInstance;
 
 namespace boost
 {class shared_mutex;}
@@ -182,6 +183,7 @@ public:
 	bool battleHasHero(ui8 side) const;
 	int battleCastSpells(ui8 side) const; //how many spells has given side casted
 	const CGHeroInstance * battleGetFightingHero(ui8 side) const; //depracated for players callback, easy to get wrong
+	const CArmedInstance * battleGetArmyObject(ui8 side) const; 
 	InfoAboutHero battleGetHeroInfo(ui8 side) const;
 
 	//helpers

+ 21 - 21
lib/NetPacksLib.cpp

@@ -1015,10 +1015,10 @@ DLL_LINKAGE void BattleStart::applyGs( CGameState *gs )
 
 DLL_LINKAGE void BattleNextRound::applyGs( CGameState *gs )
 {
-	gs->curB->castSpells[0] = gs->curB->castSpells[1] = 0;
 	for (int i = 0; i < 2; ++i)
 	{
-		vstd::amax(--gs->curB->enchanterCounter[i], 0);
+		gs->curB->sides[i].castSpellsCount = 0;
+		vstd::amax(--gs->curB->sides[i].enchanterCounter, 0);
 	}
 
 	gs->curB->round = round;
@@ -1106,11 +1106,10 @@ void BattleResult::applyGs( CGameState *gs )
 	for (auto & elem : gs->curB->stacks)
 		delete elem;
 
-	CGHeroInstance *h;
-	for (int i = 0; i < 2; ++i)
+
+	for(int i = 0; i < 2; ++i)
 	{
-		h = gs->curB->heroes[i];
-		if (h)
+		if(auto h = gs->curB->battleGetFightingHero(i))
 		{
 			h->getBonusList().remove_if(Bonus::OneBattle); 	//remove any "until next battle" bonuses
 			if (h->commander && h->commander->alive)
@@ -1123,17 +1122,18 @@ void BattleResult::applyGs( CGameState *gs )
 		}
 	}
 
-	if (VLC->modh->modules.STACK_EXP)
+	if(VLC->modh->modules.STACK_EXP)
 	{
-		if (exp[0]) //checking local array is easier than dereferencing this crap twice
-			gs->curB->belligerents[0]->giveStackExp(exp[0]);
-		if (exp[1])
-			gs->curB->belligerents[1]->giveStackExp(exp[1]);
-
+		for(int i = 0; i < 2; i++)
+			if(exp[i])
+				gs->curB->battleGetArmyObject(i)->giveStackExp(exp[i]);
+		
 		CBonusSystemNode::treeHasChanged();
 	}
 
-	gs->curB->belligerents[0]->battle = gs->curB->belligerents[1]->battle = nullptr;
+	for(int i = 0; i < 2; i++)
+		gs->curB->battleGetArmyObject(i)->battle = nullptr;
+
 	gs->curB.dellNull();
 }
 
@@ -1246,7 +1246,7 @@ DLL_LINKAGE void StartAction::applyGs( CGameState *gs )
 	}
 	else
 	{
-		gs->curB->usedSpellsHistory[ba.side].push_back(SpellID(ba.additionalInfo).toSpell());
+		gs->curB->sides[ba.side].usedSpellsHistory.push_back(SpellID(ba.additionalInfo).toSpell());
 	}
 
 	switch(ba.actionType)
@@ -1273,8 +1273,8 @@ DLL_LINKAGE void BattleSpellCast::applyGs( CGameState *gs )
 	assert(gs->curB);
 	if (castedByHero)
 	{
-		CGHeroInstance * h = gs->curB->heroes[side];
-		CGHeroInstance * enemy = gs->curB->heroes[1-side];
+		CGHeroInstance * h = gs->curB->battleGetFightingHero(side);
+		CGHeroInstance * enemy = gs->curB->battleGetFightingHero(!side);
 
 		h->mana -= spellCost;
 			vstd::amax(h->mana, 0);
@@ -1282,7 +1282,7 @@ DLL_LINKAGE void BattleSpellCast::applyGs( CGameState *gs )
 			enemy->mana += manaGained;
 		if (side < 2)
 		{
-			gs->curB->castSpells[side]++;
+			gs->curB->sides[side].castSpellsCount++;
 		}
 	}
 
@@ -1532,12 +1532,12 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
 		}
 		case ENCHANTER_COUNTER:
 		{
-			int side = gs->curB->whatSide(stack->owner);
+			auto & counter = gs->curB->sides[gs->curB->whatSide(stack->owner)].enchanterCounter;
 			if (absolute)
-				gs->curB->enchanterCounter[side] = val;
+				counter = val;
 			else
-				gs->curB->enchanterCounter[side] += val;
-			vstd::amax(gs->curB->enchanterCounter[side], 0);
+				counter += val;
+			vstd::amax(counter, 0);
 			break;
 		}
 		case UNBIND:

+ 39 - 36
server/CGameHandler.cpp

@@ -428,8 +428,8 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 	if (hero2)
 		battleResult.data->exp[1] = hero2->calculateXp(battleResult.data->exp[1]);
 
-	const CArmedInstance *bEndArmy1 = gs->curB->belligerents[0];
-	const CArmedInstance *bEndArmy2 = gs->curB->belligerents[1];
+	const CArmedInstance *bEndArmy1 = gs->curB->sides[0].armyObject;
+	const CArmedInstance *bEndArmy2 = gs->curB->sides[1].armyObject;
 	const BattleResult::EResult result = battleResult.get()->result;
 
 	auto findBattleQuery = [this] () -> shared_ptr<CBattleQuery>
@@ -452,8 +452,8 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 			battleQuery = make_shared<CBattleQuery>(gs->curB);
 		}
 	}
-	if(battleQuery != queries.topQuery(gs->curB->sides[0]))
-		complain("Player " + boost::lexical_cast<std::string>(gs->curB->sides[0]) + " although in battle has no battle query at the top!");
+	if(battleQuery != queries.topQuery(gs->curB->sides[0].color))
+		complain("Player " + boost::lexical_cast<std::string>(gs->curB->sides[0].color) + " although in battle has no battle query at the top!");
 		
 	battleQuery->result = *battleResult.data;
 
@@ -480,7 +480,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 		{
 			int maxLevel = eagleEyeLevel + 1;
 			double eagleEyeChance = finishingBattle->winnerHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::EAGLE_EYE);
-			for(const CSpell *sp : gs->curB->usedSpellsHistory[!battleResult.data->winner])
+			for(const CSpell *sp : gs->curB->sides[!battleResult.data->winner].usedSpellsHistory)
 				if(sp->level <= maxLevel && !vstd::contains(finishingBattle->winnerHero->spells, sp->id) && rand() % 100 < eagleEyeChance)
 					cs.spells.insert(sp->id);
 		}
@@ -534,7 +534,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 				}
 			}
 		}
-		for (auto armySlot : gs->curB->belligerents[!battleResult.data->winner]->stacks)
+		for (auto armySlot : gs->curB->battleGetArmyObject(!battleResult.data->winner)->stacks)
 		{
 			auto artifactsWorn = armySlot.second->artifactsWorn;
 			for (auto artSlot : artifactsWorn)
@@ -647,7 +647,7 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result )
 	logGlobal->traceStream() << "Decremented queries count to " << finishingBattle->remainingBattleQueriesCount;
 
 	if(finishingBattle->remainingBattleQueriesCount > 0)
-		//Battle results will be hndled when all battle queries are closed
+		//Battle results will be handled when all battle queries are closed
 		return;
 
 	//TODO consider if we really want it to work like above. ATM each player as unblocked as soon as possible
@@ -657,7 +657,7 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result )
 	// Necromancy if applicable.
 	const CStackBasicDescriptor raisedStack = finishingBattle->winnerHero ? finishingBattle->winnerHero->calculateNecromancy(*battleResult.data) : CStackBasicDescriptor();
 	// Give raised units to winner and show dialog, if any were raised,
-	// units will be given after casualities are taken
+	// units will be given after casualties are taken
 	const SlotID necroSlot = raisedStack.type ? finishingBattle->winnerHero->getSlotFor(raisedStack.type) : SlotID();
 
 	if (necroSlot != SlotID())
@@ -706,12 +706,11 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 {
 	bat.bsa.clear();
 	bat.stackAttacking = att->ID;
-	int attackerLuck = att->LuckVal();
-	const CGHeroInstance * h0 = gs->curB->heroes[0],
-		* h1 = gs->curB->heroes[1];
+	const int attackerLuck = att->LuckVal();
+	
+	auto sideHeroBlocksLuck = [](const SideInBattle &side){ return NBonus::hasOfType(side.hero, Bonus::BLOCK_LUCK); };
 
-	if(!(h0 && NBonus::hasOfType(h0, Bonus::BLOCK_LUCK)) &&
-	   !(h1 && NBonus::hasOfType(h1, Bonus::BLOCK_LUCK)))
+	if(!vstd::contains_if(gs->curB->sides, sideHeroBlocksLuck))
 	{
 		if(attackerLuck > 0  && rand()%24 < attackerLuck)
 		{
@@ -3339,7 +3338,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 		}
 	case Battle::RETREAT: //retreat/flee
 		{
-			if(!gs->curB->battleCanFlee(gs->curB->sides[ba.side]))
+			if(!gs->curB->battleCanFlee(gs->curB->sides[ba.side].color))
 				complain("Cannot retreat!");
 			else
 				setBattleResult(BattleResult::ESCAPE, !ba.side); //surrendering side loses
@@ -3347,7 +3346,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 		}
 	case Battle::SURRENDER:
 		{
-			PlayerColor player = gs->curB->sides[ba.side];
+			PlayerColor player = gs->curB->sides[ba.side].color;
 			int cost = gs->curB->battleGetSurrenderCost(player);
 			if(cost < 0)
 				complain("Cannot surrender!");
@@ -3477,7 +3476,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 			//second shot for ballista, only if hero has advanced artillery
 
-			const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side];
+			const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
 
 			if( destStack->alive()
 			    && (stack->getCreature()->idNumber == CreatureID::BALLISTA)
@@ -3516,7 +3515,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 		{
 			StartAction start_action(ba);
 			sendAndApply(&start_action);
-			const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side];
+			const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
 			CHeroHandler::SBallisticsLevelInfo sbi = VLC->heroh->ballistics[attackingHero->getSecSkillLevel(SecondarySkill::BALLISTICS)];
 
 			EWallParts::EWallParts attackedPart = gs->curB->battleHexToWallPart(ba.destinationTile);
@@ -3620,7 +3619,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 		{
 			StartAction start_action(ba);
 			sendAndApply(&start_action);
-			const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side];
+			const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
 			const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber),
 				*destStack = gs->curB->battleGetStackByPos(ba.destinationTile);
 
@@ -4385,7 +4384,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 				std::vector<CStack *> & battleStacks = gs->curB->stacks;
 				for (auto & battleStack : battleStacks)
 				{
-					if(battleStack->owner == gs->curB->sides[casterSide]) //get enemy stacks which can be affected by this spell
+					if(battleStack->owner == gs->curB->sides[casterSide].color) //get enemy stacks which can be affected by this spell
 					{
 						if (!gs->curB->battleIsImmune(nullptr, spell, ECastingMode::MAGIC_MIRROR, battleStack->position))
 							mirrorTargets.push_back(battleStack);
@@ -4410,8 +4409,8 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 			COMPLAIN_RET_FALSE_IF(ba.side > 1, "Side must be 0 or 1!");
 				
 
-			const CGHeroInstance *h = gs->curB->heroes[ba.side];
-			const CGHeroInstance *secondHero = gs->curB->heroes[!ba.side];
+			const CGHeroInstance *h = gs->curB->battleGetFightingHero(ba.side);
+			const CGHeroInstance *secondHero = gs->curB->battleGetFightingHero(!ba.side);
 			if(!h)
 			{
                 logGlobal->warnStream() << "Wrong caster!";
@@ -4534,10 +4533,11 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 		if (st->hasBonusOfType(Bonus::MANA_DRAIN) && !vstd::contains(st->state, EBattleStackState::DRAINED_MANA))
 		{
 			const CGHeroInstance * enemy = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
+			const CGHeroInstance * owner = gs->curB->getHero(st->owner);
 			if (enemy)
 			{
 				ui32 manaDrained = st->valOfBonuses(Bonus::MANA_DRAIN);
-				vstd::amin (manaDrained, gs->curB->heroes[0]->mana);
+				vstd::amin(manaDrained, gs->curB->battleGetFightingHero(0)->mana);
 				if (manaDrained)
 				{
 					bte.effect = Bonus::MANA_DRAIN;
@@ -4569,7 +4569,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 		}
 		BonusList bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTER)));
 		int side = gs->curB->whatSide(st->owner);
-		if (bl.size() && st->casts && !gs->curB->enchanterCounter[side])
+		if (bl.size() && st->casts && !gs->curB->sides[side].enchanterCounter)
 		{
 			int index = rand() % bl.size();
 			SpellID spellID = SpellID(bl[index]->subtype);
@@ -4624,7 +4624,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
 	//helper info
 	const SpellCreatedObstacle *spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(&obstacle); //not nice but we may need spell params
 	const ui8 side = curStack->attackerOwned; //if enemy is defending (false = 0), side of our hero is also 0 (attacker)
-	const CGHeroInstance *hero = gs->curB->heroes[side];
+	const CGHeroInstance *hero = gs->curB->battleGetFightingHero(side);
 
 	if(obstacle.obstacleType == CObstacleInstance::MOAT)
 	{
@@ -5819,14 +5819,15 @@ void CGameHandler::runBattle()
 	}
 
 	//spells opening battle
-	for(int i=0; i<ARRAY_COUNT(gs->curB->heroes); ++i)
+	for(int i = 0; i < 2; ++i)
 	{
-		if(gs->curB->heroes[i] && gs->curB->heroes[i]->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
+		auto h = gs->curB->battleGetFightingHero(i);
+		if(h && h->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
 		{
-			TBonusListPtr bl = gs->curB->heroes[i]->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
+			TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
 			for (Bonus *b : *bl)
 			{
-				handleSpellCasting(SpellID(b->subtype), 3, -1, 0, gs->curB->heroes[i]->tempOwner, nullptr, gs->curB->heroes[1-i], b->val, ECastingMode::HERO_CASTING, nullptr);
+				handleSpellCasting(SpellID(b->subtype), 3, -1, 0, h->tempOwner, nullptr, gs->curB->battleGetFightingHero(1-i), b->val, ECastingMode::HERO_CASTING, nullptr);
 			}
 		}
 	}
@@ -5853,7 +5854,8 @@ void CGameHandler::runBattle()
 			//check for bad morale => freeze
 			int nextStackMorale = next->MoraleVal();
 			if( nextStackMorale < 0 &&
-				!(NBonus::hasOfType(gs->curB->heroes[0], Bonus::BLOCK_MORALE) || NBonus::hasOfType(gs->curB->heroes[1], Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses)
+				!(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
+				   || NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses)
 				)
 			{
 				if( rand()%24   <   -2 * nextStackMorale)
@@ -6005,7 +6007,8 @@ void CGameHandler::runBattle()
 					&& !vstd::contains(next->state, EBattleStackState::FEAR)
 					&&  next->alive()
 					&&  nextStackMorale > 0
-					&& !(NBonus::hasOfType(gs->curB->heroes[0], Bonus::BLOCK_MORALE) || NBonus::hasOfType(gs->curB->heroes[1], Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
+					&& !(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
+						|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
 					)
 				{
 					if(rand()%24 < nextStackMorale) //this stack hasn't got morale this turn
@@ -6033,7 +6036,7 @@ void CGameHandler::runBattle()
 		}
 	}
 
-	endBattle(gs->curB->tile, gs->curB->heroes[0], gs->curB->heroes[1]);
+	endBattle(gs->curB->tile, gs->curB->battleGetFightingHero(0), gs->curB->battleGetFightingHero(1));
 }
 
 bool CGameHandler::makeAutomaticAction(const CStack *stack, BattleAction &ba)
@@ -6210,7 +6213,7 @@ bool CGameHandler::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, con
 void CGameHandler::duelFinished()
 {
 	auto si = getStartInfo();
-	auto getName = [&](int i){ return si->getIthPlayersSettings(gs->curB->sides[i]).name; };
+	auto getName = [&](int i){ return si->getIthPlayersSettings(gs->curB->sides[i].color).name; };
 
 	int casualtiesPoints = 0;
 	logGlobal->debugStream() << boost::format("Winner side %d\nWinner casualties:") 
@@ -6310,10 +6313,10 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper(shared_ptr<const CBat
 	auto &result = *Query->result;
 	auto &info = *Query->bi;
 
-	winnerHero = result.winner != 0 ? info.heroes[1] : info.heroes[0];
-	loserHero = result.winner != 0 ? info.heroes[0] : info.heroes[1];
-	victor = info.sides[result.winner];
-	loser = info.sides[!result.winner];
+	winnerHero = result.winner != 0 ? info.sides[1].hero : info.sides[0].hero;
+	loserHero = result.winner != 0 ? info.sides[0].hero : info.sides[1].hero;
+	victor = info.sides[result.winner].color;
+	loser = info.sides[!result.winner].color;
 	duel = Duel;
 	remainingBattleQueriesCount = RemainingBattleQueriesCount;
 }

+ 4 - 4
server/CQuery.cpp

@@ -222,13 +222,13 @@ void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit
 
 CBattleQuery::CBattleQuery(const BattleInfo *Bi)
 {
-	belligerents[0] = Bi->belligerents[0];
-	belligerents[1] = Bi->belligerents[1];
+	belligerents[0] = Bi->sides[0].armyObject;
+	belligerents[1] = Bi->sides[1].armyObject;
 
 	bi = Bi;
 
-	for(PlayerColor side : bi->sides)
-		addPlayer(side);
+	for(auto &side : bi->sides)
+		addPlayer(side.color);
 }
 
 CBattleQuery::CBattleQuery()

+ 1 - 1
server/NetPacksServer.cpp

@@ -249,7 +249,7 @@ bool MakeAction::applyGh( CGameHandler *gh )
 		if(ba.actionType != Battle::WALK  &&  ba.actionType != Battle::END_TACTIC_PHASE  
 			&& ba.actionType != Battle::RETREAT && ba.actionType != Battle::SURRENDER)
 			ERROR_AND_RETURN;
-		if(gh->connections[b->sides[b->tacticsSide]] != c) 
+		if(gh->connections[b->sides[b->tacticsSide].color] != c) 
 			ERROR_AND_RETURN;
 	}
 	else if(gh->connections[b->battleGetStackByID(b->activeStack)->owner] != c)