فهرست منبع

Remove CPack::applyGs method in favor of GameStatePackVisitor class

Ivan Savenko 5 ماه پیش
والد
کامیت
86b832be67

+ 15 - 7
AI/BattleAI/StackWithBonuses.cpp

@@ -15,6 +15,7 @@
 #include "../../lib/battle/BattleLayout.h"
 #include "../../lib/CStack.h"
 #include "../../lib/ScriptHandler.h"
+#include "../../lib/gameState/GameStatePackVisitor.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/networkPacks/SetStackEffect.h"
 
@@ -538,37 +539,44 @@ void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient & pack)
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack & pack)
 {
-	pack.applyBattle(owner);
+	BattleStatePackVisitor visitor(*owner);
+	pack.visit(visitor);
 }
 
 HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment)

+ 1 - 1
client/ClientNetPackVisitors.h

@@ -47,7 +47,7 @@ public:
 	void visitRebalanceStacks(RebalanceStacks & pack) override;
 	void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) override;
 	void visitPutArtifact(PutArtifact & pack) override;
-	void visitEraseArtifact(BulkEraseArtifacts & pack) override;
+	void visitBulkEraseArtifacts(BulkEraseArtifacts & pack) override;
 	void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
 	void visitAssembledArtifact(AssembledArtifact & pack) override;
 	void visitDisassembledArtifact(DisassembledArtifact & pack) override;

+ 1 - 1
client/NetPacksClient.cpp

@@ -270,7 +270,7 @@ void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
 		callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::askToAssembleArtifact, pack.al);
 }
 
-void ApplyClientNetPackVisitor::visitEraseArtifact(BulkEraseArtifacts & pack)
+void ApplyClientNetPackVisitor::visitBulkEraseArtifacts(BulkEraseArtifacts & pack)
 {
 	for(const auto & slotErase : pack.posPack)
 		callInterfaceIfPresent(cl, cl.getOwner(pack.artHolder), &IGameEventsReceiver::artifactRemoved, ArtifactLocation(pack.artHolder, slotErase));

+ 2 - 0
lib/CMakeLists.txt

@@ -112,6 +112,7 @@ set(lib_MAIN_SRCS
 
 	gameState/CGameState.cpp
 	gameState/CGameStateCampaign.cpp
+	gameState/GameStatePackVisitor.cpp
 	gameState/HighScore.cpp
 	gameState/InfoAboutArmy.cpp
 	gameState/QuestInfo.cpp
@@ -498,6 +499,7 @@ set(lib_MAIN_HEADERS
 	gameState/CGameState.h
 	gameState/CGameStateCampaign.h
 	gameState/EVictoryLossCheckResult.h
+	gameState/GameStatePackVisitor.h
 	gameState/HighScore.h
 	gameState/InfoAboutArmy.h
 	gameState/RumorState.h

+ 3 - 1
lib/gameState/CGameState.cpp

@@ -14,6 +14,7 @@
 #include "InfoAboutArmy.h"
 #include "TavernHeroesPool.h"
 #include "CGameStateCampaign.h"
+#include "GameStatePackVisitor.h"
 #include "SThievesGuildInfo.h"
 #include "QuestInfo.h"
 
@@ -1119,7 +1120,8 @@ PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor
 
 void CGameState::apply(CPackForClient & pack)
 {
-	pack.applyGs(this);
+	GameStatePackVisitor visitor(*this);
+	pack.visit(visitor);
 }
 
 void CGameState::calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const

+ 0 - 5
lib/gameState/CGameState.h

@@ -17,11 +17,6 @@
 #include "RumorState.h"
 #include "GameStatistics.h"
 
-namespace boost
-{
-class shared_mutex;
-}
-
 VCMI_LIB_NAMESPACE_BEGIN
 
 class EVictoryLossCheckResult;

+ 1575 - 0
lib/gameState/GameStatePackVisitor.cpp

@@ -0,0 +1,1575 @@
+/*
+ * GameStatePackVisitor.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "GameStatePackVisitor.h"
+
+#include "CGameState.h"
+#include "TavernHeroesPool.h"
+
+#include "../CPlayerState.h"
+#include "../CStack.h"
+#include "../IGameSettings.h"
+
+#include "../campaign/CampaignState.h"
+#include "../entities/artifact/ArtifactUtils.h"
+#include "../entities/artifact/CArtifact.h"
+#include "../entities/artifact/CArtifactFittingSet.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../mapObjects/CGMarket.h"
+#include "../mapObjects/CGTownInstance.h"
+#include "../mapObjects/CQuest.h"
+#include "../mapObjects/FlaggableMapObject.h"
+#include "../mapObjects/MiscObjects.h"
+#include "../mapObjects/TownBuildingInstance.h"
+#include "../mapping/CMap.h"
+#include "../networkPacks/StackLocation.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void GameStatePackVisitor::visitSetResources(SetResources & pack)
+{
+	assert(pack.player.isValidPlayer());
+	if(pack.abs)
+		gs.getPlayerState(pack.player)->resources = pack.res;
+	else
+		gs.getPlayerState(pack.player)->resources += pack.res;
+	gs.getPlayerState(pack.player)->resources.amin(GameConstants::PLAYER_RESOURCES_CAP);
+
+	//just ensure that player resources are not negative
+	//server is responsible to check if player can afford deal
+	//but events on server side are allowed to take more than player have
+	gs.getPlayerState(pack.player)->resources.positive();
+}
+
+void GameStatePackVisitor::visitSetPrimSkill(SetPrimSkill & pack)
+{
+	CGHeroInstance * hero = gs.getHero(pack.id);
+	assert(hero);
+	hero->setPrimarySkill(pack.which, pack.val, pack.abs);
+}
+
+void GameStatePackVisitor::visitSetSecSkill(SetSecSkill & pack)
+{
+	CGHeroInstance *hero = gs.getHero(pack.id);
+	hero->setSecSkillLevel(pack.which, pack.val, pack.abs);
+}
+
+void GameStatePackVisitor::visitSetCommanderProperty(SetCommanderProperty & pack)
+{
+	const auto & commander = gs.getHero(pack.heroid)->getCommander();
+	assert (commander);
+
+	switch (pack.which)
+	{
+		case SetCommanderProperty::BONUS:
+			commander->accumulateBonus (std::make_shared<Bonus>(pack.accumulatedBonus));
+			break;
+		case SetCommanderProperty::SPECIAL_SKILL:
+			commander->accumulateBonus (std::make_shared<Bonus>(pack.accumulatedBonus));
+			commander->specialSkills.insert (pack.additionalInfo);
+			break;
+		case SetCommanderProperty::SECONDARY_SKILL:
+			commander->secondarySkills[pack.additionalInfo] = static_cast<ui8>(pack.amount);
+			break;
+		case SetCommanderProperty::ALIVE:
+			if (pack.amount)
+				commander->setAlive(true);
+			else
+				commander->setAlive(false);
+			break;
+		case SetCommanderProperty::EXPERIENCE:
+			commander->giveTotalStackExperience(pack.amount);
+			commander->nodeHasChanged();
+			break;
+	}
+}
+
+void GameStatePackVisitor::visitAddQuest(AddQuest & pack)
+{
+	assert(vstd::contains(gs.players, pack.player));
+	auto * vec = &gs.players.at(pack.player).quests;
+	if (!vstd::contains(*vec, pack.quest))
+		vec->push_back(pack.quest);
+	else
+		logNetwork->warn("Warning! Attempt to add duplicated quest");
+}
+
+void GameStatePackVisitor::visitUpdateArtHandlerLists(UpdateArtHandlerLists & pack)
+{
+	gs.allocatedArtifacts = pack.allocatedArtifacts;
+}
+
+void GameStatePackVisitor::visitChangeFormation(ChangeFormation & pack)
+{
+	gs.getHero(pack.hid)->setFormation(pack.formation);
+}
+
+void GameStatePackVisitor::visitHeroVisitCastle(HeroVisitCastle & pack)
+{
+	CGHeroInstance *h = gs.getHero(pack.hid);
+	CGTownInstance *t = gs.getTown(pack.tid);
+
+	assert(h);
+	assert(t);
+
+	if(pack.start())
+		t->setVisitingHero(h);
+	else
+		t->setVisitingHero(nullptr);
+}
+
+void GameStatePackVisitor::visitChangeSpells(ChangeSpells & pack)
+{
+	CGHeroInstance *hero = gs.getHero(pack.hid);
+
+	if(pack.learn)
+		for(const auto & sid : pack.spells)
+			hero->addSpellToSpellbook(sid);
+	else
+		for(const auto & sid : pack.spells)
+			hero->removeSpellFromSpellbook(sid);
+}
+
+void GameStatePackVisitor::visitSetResearchedSpells(SetResearchedSpells & pack)
+{
+	CGTownInstance *town = gs.getTown(pack.tid);
+
+	town->spells[pack.level] = pack.spells;
+	town->spellResearchCounterDay++;
+	if(pack.accepted)
+		town->spellResearchAcceptedCounter++;
+}
+
+void GameStatePackVisitor::visitSetMana(SetMana & pack)
+{
+	CGHeroInstance * hero = gs.getHero(pack.hid);
+
+	assert(hero);
+
+	if(pack.absolute)
+		hero->mana = pack.val;
+	else
+		hero->mana += pack.val;
+
+	vstd::amax(hero->mana, 0); //not less than 0
+}
+
+void GameStatePackVisitor::visitSetMovePoints(SetMovePoints & pack)
+{
+	CGHeroInstance *hero = gs.getHero(pack.hid);
+
+	assert(hero);
+
+	if(pack.absolute)
+		hero->setMovementPoints(pack.val);
+	else
+		hero->setMovementPoints(hero->movementPointsRemaining() + pack.val);
+}
+
+void GameStatePackVisitor::visitFoWChange(FoWChange & pack)
+{
+	TeamState * team = gs.getPlayerTeam(pack.player);
+	auto & fogOfWarMap = team->fogOfWarMap;
+	for(const int3 & t : pack.tiles)
+		fogOfWarMap[t.z][t.x][t.y] = pack.mode != ETileVisibility::HIDDEN;
+
+	if (pack.mode == ETileVisibility::HIDDEN) //do not hide too much
+	{
+		std::unordered_set<int3> tilesRevealed;
+		for (auto & o : gs.getMap().getObjects())
+		{
+			if (o->asOwnable())
+			{
+				if(vstd::contains(team->players, o->getOwner())) //check owned observators
+					gs.getTilesInRange(tilesRevealed, o->getSightCenter(), o->getSightRadius(), ETileVisibility::HIDDEN, o->tempOwner);
+			}
+		}
+		for(const int3 & t : tilesRevealed) //probably not the most optimal solution ever
+			fogOfWarMap[t.z][t.x][t.y] = 1;
+	}
+}
+
+void GameStatePackVisitor::visitSetAvailableHero(SetAvailableHero & pack)
+{
+	gs.heroesPool->setHeroForPlayer(pack.player, pack.slotID, pack.hid, pack.army, pack.roleID, pack.replenishPoints);
+}
+
+void GameStatePackVisitor::visitGiveBonus(GiveBonus & pack)
+{
+	CBonusSystemNode *cbsn = nullptr;
+	switch(pack.who)
+	{
+		case GiveBonus::ETarget::OBJECT:
+			cbsn = dynamic_cast<CBonusSystemNode*>(gs.getObjInstance(pack.id.as<ObjectInstanceID>()));
+			break;
+		case GiveBonus::ETarget::HERO_COMMANDER:
+			cbsn = gs.getHero(pack.id.as<ObjectInstanceID>())->getCommander();
+			break;
+		case GiveBonus::ETarget::PLAYER:
+			cbsn = gs.getPlayerState(pack.id.as<PlayerColor>());
+			break;
+		case GiveBonus::ETarget::BATTLE:
+			assert(Bonus::OneBattle(&pack.bonus));
+			cbsn = dynamic_cast<CBonusSystemNode*>(gs.getBattle(pack.id.as<BattleID>()));
+			break;
+	}
+
+	assert(cbsn);
+
+	if(Bonus::OneWeek(&pack.bonus))
+		pack.bonus.turnsRemain = 8 - gs.getDate(Date::DAY_OF_WEEK); // set correct number of days before adding bonus
+
+	auto b = std::make_shared<Bonus>(pack.bonus);
+	cbsn->addNewBonus(b);
+}
+
+void GameStatePackVisitor::visitChangeObjPos(ChangeObjPos & pack)
+{
+	CGObjectInstance *obj = gs.getObjInstance(pack.objid);
+	if(!obj)
+	{
+		logNetwork->error("Wrong ChangeObjPos: object %d doesn't exist!", pack.objid.getNum());
+		return;
+	}
+	gs.getMap().moveObject(pack.objid, pack.nPos + obj->getVisitableOffset());
+}
+
+void GameStatePackVisitor::visitChangeObjectVisitors(ChangeObjectVisitors & pack)
+{
+	auto objectPtr = gs.getObjInstance(pack.object);
+
+	switch (pack.mode)
+	{
+		case ChangeObjectVisitors::VISITOR_ADD_HERO:
+			gs.getHero(pack.hero)->visitedObjects.insert(pack.object);
+			[[fallthrough]];
+		case ChangeObjectVisitors::VISITOR_ADD_PLAYER:
+			gs.getPlayerTeam(gs.getHero(pack.hero)->tempOwner)->scoutedObjects.insert(pack.object);
+			gs.getPlayerState(gs.getHero(pack.hero)->tempOwner)->visitedObjects.insert(pack.object);
+			gs.getPlayerState(gs.getHero(pack.hero)->tempOwner)->visitedObjectsGlobal.insert({objectPtr->ID, objectPtr->subID});
+			break;
+
+		case ChangeObjectVisitors::VISITOR_CLEAR:
+			// remove visit info from all heroes, including those that are not present on map
+			for (auto heroID : gs.getMap().getHeroesOnMap())
+				gs.getHero(heroID)->visitedObjects.erase(pack.object);
+
+			for (auto heroID : gs.getMap().getHeroesInPool())
+				gs.getMap().tryGetFromHeroPool(heroID)->visitedObjects.erase(pack.object);
+
+			for(auto &elem : gs.players)
+				elem.second.visitedObjects.erase(pack.object);
+
+			for(auto &elem : gs.teams)
+				elem.second.scoutedObjects.erase(pack.object);
+
+			break;
+		case ChangeObjectVisitors::VISITOR_SCOUTED:
+			gs.getPlayerTeam(gs.getHero(pack.hero)->tempOwner)->scoutedObjects.insert(pack.object);
+			break;
+	}
+}
+
+void GameStatePackVisitor::visitChangeArtifactsCostume(ChangeArtifactsCostume & pack)
+{
+	auto & allCostumes = gs.getPlayerState(pack.player)->costumesArtifacts;
+	if(const auto & costume = allCostumes.find(pack.costumeIdx); costume != allCostumes.end())
+		costume->second = pack.costumeSet;
+	else
+		allCostumes.try_emplace(pack.costumeIdx, pack.costumeSet);
+}
+
+void GameStatePackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
+{
+	PlayerState *p = gs.getPlayerState(pack.player);
+	if(pack.victoryLossCheckResult.victory())
+	{
+		p->status = EPlayerStatus::WINNER;
+
+		// TODO: Campaign-specific code might as well go somewhere else
+		// keep all heroes from the winning player
+		if(p->human && gs.getStartInfo()->campState)
+		{
+			std::vector<CGHeroInstance *> crossoverHeroes;
+			for (auto hero : p->getHeroes())
+				if (hero->tempOwner == pack.player)
+					crossoverHeroes.push_back(hero);
+
+			gs.getStartInfo()->campState->setCurrentMapAsConquered(crossoverHeroes);
+		}
+	}
+	else
+	{
+		p->status = EPlayerStatus::LOSER;
+	}
+
+	// defeated player may be making turn right now
+	gs.actingPlayers.erase(pack.player);
+}
+
+void GameStatePackVisitor::visitPlayerReinitInterface(PlayerReinitInterface & pack)
+{
+	if(!gs.getStartInfo())
+		return;
+
+	//TODO: what does mean if more that one player connected?
+	if(pack.playerConnectionId == PlayerSettings::PLAYER_AI)
+	{
+		for(const auto & player : pack.players)
+			gs.getStartInfo()->getIthPlayersSettings(player).connectedPlayerIDs.clear();
+	}
+}
+
+void GameStatePackVisitor::visitRemoveBonus(RemoveBonus & pack)
+{
+	CBonusSystemNode *node = nullptr;
+	switch(pack.who)
+	{
+		case GiveBonus::ETarget::OBJECT:
+			node = dynamic_cast<CBonusSystemNode*>(gs.getObjInstance(pack.whoID.as<ObjectInstanceID>()));
+			break;
+		case GiveBonus::ETarget::PLAYER:
+			node = gs.getPlayerState(pack.whoID.as<PlayerColor>());
+			break;
+		case GiveBonus::ETarget::BATTLE:
+			assert(Bonus::OneBattle(&pack.bonus));
+			node = dynamic_cast<CBonusSystemNode*>(gs.getBattle(pack.whoID.as<BattleID>()));
+			break;
+	}
+
+	BonusList &bonuses = node->getExportedBonusList();
+
+	for(const auto & b : bonuses)
+	{
+		if(b->source == pack.source && b->sid == pack.id)
+		{
+			pack.bonus = *b; //backup bonus (to show to interfaces later)
+			node->removeBonus(b);
+			break;
+		}
+	}
+}
+
+void GameStatePackVisitor::visitRemoveObject(RemoveObject & pack)
+{
+	CGObjectInstance *obj = gs.getObjInstance(pack.objectID);
+	logGlobal->debug("removing object id=%d; address=%x; name=%s", pack.objectID, (intptr_t)obj, obj->getObjectName());
+
+	if (pack.initiator.isValidPlayer())
+		gs.getPlayerState(pack.initiator)->destroyedObjects.insert(pack.objectID);
+
+	if(obj->getOwner().isValidPlayer())
+	{
+		gs.getPlayerState(obj->getOwner())->removeOwnedObject(obj); //object removed via map event or hero got beaten
+
+		FlaggableMapObject* flaggableObject = dynamic_cast<FlaggableMapObject*>(obj);
+		if(flaggableObject)
+		{
+			flaggableObject->markAsDeleted();
+		}
+	}
+
+	if(obj->ID == Obj::HERO) //remove beaten hero
+	{
+		auto beatenHero = dynamic_cast<CGHeroInstance*>(obj);
+		assert(beatenHero);
+
+		auto * siegeNode = beatenHero->whereShouldBeAttachedOnSiege(gs);
+		vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi)
+		{
+			return asi.getArt()->getTypeId() == ArtifactID::GRAIL;
+		});
+
+		if(beatenHero->getVisitedTown())
+		{
+			if(beatenHero->getVisitedTown()->getGarrisonHero() == beatenHero)
+				beatenHero->getVisitedTown()->setGarrisonedHero(nullptr);
+			else
+				beatenHero->getVisitedTown()->setVisitingHero(nullptr);
+
+			beatenHero->setVisitedTown(nullptr, false);
+		}
+		beatenHero->detachFromBonusSystem(gs);
+		beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero
+
+		// FIXME: workaround:
+		// hero should be attached to siegeNode after battle
+		// however this code might also be called on dismissing hero while in town
+		if (siegeNode && vstd::contains(beatenHero->getParentNodes(), siegeNode))
+			beatenHero->detachFrom(*siegeNode);
+
+		//If hero on Boat is removed, the Boat disappears
+		if(beatenHero->inBoat())
+		{
+			auto boat = beatenHero->getBoat();
+			beatenHero->setBoat(nullptr);
+			gs.getMap().eraseObject(boat->id);
+		}
+
+		auto beatenObject = gs.getMap().eraseObject(obj->id);
+
+		//return hero to the pool, so he may reappear in tavern
+		gs.heroesPool->addHeroToPool(beatenHero->getHeroTypeID());
+		gs.getMap().addToHeroPool(std::dynamic_pointer_cast<CGHeroInstance>(beatenObject));
+
+		return;
+	}
+
+	const auto * quest = dynamic_cast<const IQuestObject *>(obj);
+	if (quest)
+	{
+		for (auto &player : gs.players)
+		{
+			vstd::erase_if(player.second.quests, [obj](const QuestInfo & q){
+				return q.obj == obj->id;
+			});
+		}
+	}
+
+	gs.getMap().eraseObject(pack.objectID);
+	gs.getMap().calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
+}
+
+static int getDir(const int3 & src, const int3 & dst)
+{
+	int ret = -1;
+	if(dst.x+1 == src.x && dst.y+1 == src.y) //tl
+	{
+		ret = 1;
+	}
+	else if(dst.x == src.x && dst.y+1 == src.y) //t
+	{
+		ret = 2;
+	}
+	else if(dst.x-1 == src.x && dst.y+1 == src.y) //tr
+	{
+		ret = 3;
+	}
+	else if(dst.x-1 == src.x && dst.y == src.y) //r
+	{
+		ret = 4;
+	}
+	else if(dst.x-1 == src.x && dst.y-1 == src.y) //br
+	{
+		ret = 5;
+	}
+	else if(dst.x == src.x && dst.y-1 == src.y) //b
+	{
+		ret = 6;
+	}
+	else if(dst.x+1 == src.x && dst.y-1 == src.y) //bl
+	{
+		ret = 7;
+	}
+	else if(dst.x+1 == src.x && dst.y == src.y) //l
+	{
+		ret = 8;
+	}
+	return ret;
+}
+
+void GameStatePackVisitor::visitTryMoveHero(TryMoveHero & pack)
+{
+	CGHeroInstance *h = gs.getHero(pack.id);
+	if (!h)
+	{
+		logGlobal->error("Attempt ot move unavailable hero %d", pack.id.getNum());
+		return;
+	}
+
+	const TerrainTile & fromTile = gs.getMap().getTile(h->convertToVisitablePos(pack.start));
+	const TerrainTile & destTile = gs.getMap().getTile(h->convertToVisitablePos(pack.end));
+
+	h->setMovementPoints(pack.movePoints);
+
+	if((pack.result == TryMoveHero::SUCCESS || pack.result == TryMoveHero::BLOCKING_VISIT || pack.result == TryMoveHero::EMBARK || pack.result == TryMoveHero::DISEMBARK) && pack.start != pack.end)
+	{
+		auto dir = getDir(pack.start, pack.end);
+		if(dir > 0  &&  dir <= 8)
+			h->moveDir = dir;
+		//else don`t change move direction - hero might have traversed the subterranean gate, direction should be kept
+	}
+
+	if(pack.result == TryMoveHero::EMBARK) //hero enters boat at destination tile
+	{
+		const TerrainTile &tt = gs.getMap().getTile(h->convertToVisitablePos(pack.end));
+		ObjectInstanceID topObjectID = tt.visitableObjects.back();
+		CGObjectInstance * topObject = gs.getObjInstance(topObjectID);
+		assert(tt.visitableObjects.size() >= 1 && topObject->ID == Obj::BOAT); //the only visitable object at destination is Boat
+		auto * boat = dynamic_cast<CGBoat *>(topObject);
+		assert(boat);
+
+		gs.getMap().hideObject(boat); //hero blockvis mask will be used, we don't need to duplicate it with boat
+		h->setBoat(boat);
+	}
+	else if(pack.result == TryMoveHero::DISEMBARK) //hero leaves boat to destination tile
+	{
+		auto * b = h->getBoat();
+		b->direction = h->moveDir;
+		b->pos = pack.start;
+		gs.getMap().showObject(b);
+		h->setBoat(nullptr);
+	}
+
+	if(pack.start != pack.end && (pack.result == TryMoveHero::SUCCESS || pack.result == TryMoveHero::TELEPORTATION || pack.result == TryMoveHero::EMBARK || pack.result == TryMoveHero::DISEMBARK))
+	{
+		gs.getMap().hideObject(h);
+		h->setAnchorPos(pack.end);
+		if(auto * b = h->getBoat())
+			b->setAnchorPos(pack.end);
+		gs.getMap().showObject(h);
+	}
+
+	auto & fogOfWarMap = gs.getPlayerTeam(h->getOwner())->fogOfWarMap;
+	for(const int3 & t : pack.fowRevealed)
+		fogOfWarMap[t.z][t.x][t.y] = 1;
+
+	if (fromTile.getTerrainID() != destTile.getTerrainID())
+		h->nodeHasChanged(); // update bonuses with terrain limiter
+}
+
+void GameStatePackVisitor::visitNewStructures(NewStructures & pack)
+{
+	CGTownInstance *t = gs.getTown(pack.tid);
+
+	for(const auto & id : pack.bid)
+	{
+		assert(t->getTown()->buildings.at(id) != nullptr);
+		t->addBuilding(id);
+	}
+	t->updateAppearance();
+	t->built = pack.built;
+	t->recreateBuildingsBonuses();
+}
+
+void GameStatePackVisitor::visitRazeStructures(RazeStructures & pack)
+{
+	CGTownInstance *t = gs.getTown(pack.tid);
+	for(const auto & id : pack.bid)
+	{
+		t->removeBuilding(id);
+
+		t->updateAppearance();
+	}
+	t->destroyed = pack.destroyed; //yeaha
+	t->recreateBuildingsBonuses();
+}
+
+void GameStatePackVisitor::visitSetAvailableCreatures(SetAvailableCreatures & pack)
+{
+	auto * dw = dynamic_cast<CGDwelling *>(gs.getObjInstance(pack.tid));
+	assert(dw);
+	dw->creatures = pack.creatures;
+}
+
+void GameStatePackVisitor::visitSetHeroesInTown(SetHeroesInTown & pack)
+{
+	CGTownInstance *t = gs.getTown(pack.tid);
+
+	CGHeroInstance * v = gs.getHero(pack.visiting);
+	CGHeroInstance * g = gs.getHero(pack.garrison);
+
+	bool newVisitorComesFromGarrison = v && v == t->getGarrisonHero();
+	bool newGarrisonComesFromVisiting = g && g == t->getVisitingHero();
+
+	if(newVisitorComesFromGarrison)
+		t->setGarrisonedHero(nullptr);
+	if(newGarrisonComesFromVisiting)
+		t->setVisitingHero(nullptr);
+	if(!newGarrisonComesFromVisiting || v)
+		t->setVisitingHero(v);
+	if(!newVisitorComesFromGarrison || g)
+		t->setGarrisonedHero(g);
+
+	if(v)
+		gs.getMap().showObject(v);
+
+	if(g)
+		gs.getMap().hideObject(g);
+}
+
+void GameStatePackVisitor::visitHeroRecruited(HeroRecruited & pack)
+{
+	auto h = gs.heroesPool->takeHeroFromPool(pack.hid);
+	CGTownInstance *t = gs.getTown(pack.tid);
+	PlayerState *p = gs.getPlayerState(pack.player);
+
+	if (pack.boatId.hasValue())
+	{
+		CGObjectInstance *obj = gs.getObjInstance(pack.boatId);
+		auto * boat = dynamic_cast<CGBoat *>(obj);
+		if (boat)
+		{
+			gs.getMap().hideObject(boat);
+			h->setBoat(boat);
+		}
+	}
+
+	h->setOwner(pack.player);
+	h->pos = pack.tile;
+	h->updateAppearance();
+
+	assert(h->id.hasValue());
+	gs.getMap().addNewObject(h);
+
+	p->addOwnedObject(h.get());
+	h->attachToBonusSystem(gs);
+
+	if(t)
+		t->setVisitingHero(h.get());
+}
+
+void GameStatePackVisitor::visitGiveHero(GiveHero & pack)
+{
+	CGHeroInstance *h = gs.getHero(pack.id);
+
+	if (pack.boatId.hasValue())
+	{
+		CGObjectInstance *obj = gs.getObjInstance(pack.boatId);
+		auto * boat = dynamic_cast<CGBoat *>(obj);
+		if (boat)
+		{
+			gs.getMap().hideObject(boat);
+			h->setBoat(boat);
+		}
+	}
+
+	//bonus system
+	h->detachFrom(gs.globalEffects);
+	h->attachTo(*gs.getPlayerState(pack.player));
+
+	auto oldVisitablePos = h->visitablePos();
+	gs.getMap().hideObject(h);
+	h->updateAppearance();
+
+	h->setOwner(pack.player);
+	h->setMovementPoints(h->movementPointsLimit(true));
+	h->setAnchorPos(h->convertFromVisitablePos(oldVisitablePos));
+	gs.getMap().heroAddedToMap(h);
+	gs.getPlayerState(h->getOwner())->addOwnedObject(h);
+
+	gs.getMap().showObject(h);
+	h->setVisitedTown(nullptr, false);
+}
+
+void GameStatePackVisitor::visitNewObject(NewObject & pack)
+{
+	gs.getMap().addNewObject(pack.newObject);
+	gs.getMap().calculateGuardingGreaturePositions();
+
+	// attach newly spawned wandering monster to global bonus system node
+	auto newArmy = std::dynamic_pointer_cast<CArmedInstance>(pack.newObject);
+	if (newArmy)
+		newArmy->attachToBonusSystem(gs);
+
+	logGlobal->debug("Added object id=%d; name=%s", pack.newObject->id, pack.newObject->getObjectName());
+}
+
+void GameStatePackVisitor::visitNewArtifact(NewArtifact & pack)
+{
+	auto art = gs.createArtifact(pack.artId, pack.spellId);
+	PutArtifact pa(art->getId(), ArtifactLocation(pack.artHolder, pack.pos), false);
+	pa.visit(*this);
+}
+
+void GameStatePackVisitor::visitChangeStackCount(ChangeStackCount & pack)
+{
+	auto * srcObj = gs.getArmyInstance(pack.army);
+	if(!srcObj)
+		throw std::runtime_error("ChangeStackCount: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption.");
+
+	if(pack.absoluteValue)
+		srcObj->setStackCount(pack.slot, pack.count);
+	else
+		srcObj->changeStackCount(pack.slot, pack.count);
+}
+
+void GameStatePackVisitor::visitSetStackType(SetStackType & pack)
+{
+	auto * srcObj = gs.getArmyInstance(pack.army);
+	if(!srcObj)
+		throw std::runtime_error("SetStackType: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption.");
+
+	srcObj->setStackType(pack.slot, pack.type);
+}
+
+void GameStatePackVisitor::visitEraseStack(EraseStack & pack)
+{
+	auto * srcObj = gs.getArmyInstance(pack.army);
+	if(!srcObj)
+		throw std::runtime_error("EraseStack: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption.");
+
+	srcObj->eraseStack(pack.slot);
+}
+
+void GameStatePackVisitor::visitSwapStacks(SwapStacks & pack)
+{
+	auto * srcObj = gs.getArmyInstance(pack.srcArmy);
+	if(!srcObj)
+		throw std::runtime_error("SwapStacks: invalid army object " + std::to_string(pack.srcArmy.getNum()) + ", possible game state corruption.");
+
+	auto * dstObj = gs.getArmyInstance(pack.dstArmy);
+	if(!dstObj)
+		throw std::runtime_error("SwapStacks: invalid army object " + std::to_string(pack.dstArmy.getNum()) + ", possible game state corruption.");
+
+	auto s1 = srcObj->detachStack(pack.srcSlot);
+	auto s2 = dstObj->detachStack(pack.dstSlot);
+
+	srcObj->putStack(pack.srcSlot, std::move(s2));
+	dstObj->putStack(pack.dstSlot, std::move(s1));
+}
+
+void GameStatePackVisitor::visitInsertNewStack(InsertNewStack & pack)
+{
+	if(auto * obj = gs.getArmyInstance(pack.army))
+		obj->putStack(pack.slot, std::make_unique<CStackInstance>(gs.cb, pack.type, pack.count));
+	else
+		throw std::runtime_error("InsertNewStack: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption.");
+}
+
+void GameStatePackVisitor::visitRebalanceStacks(RebalanceStacks & pack)
+{
+	auto * srcObj = gs.getArmyInstance(pack.srcArmy);
+	if(!srcObj)
+		throw std::runtime_error("RebalanceStacks: invalid army object " + std::to_string(pack.srcArmy.getNum()) + ", possible game state corruption.");
+
+	auto * dstObj = gs.getArmyInstance(pack.dstArmy);
+	if(!dstObj)
+		throw std::runtime_error("RebalanceStacks: invalid army object " + std::to_string(pack.dstArmy.getNum()) + ", possible game state corruption.");
+
+	StackLocation src(srcObj->id, pack.srcSlot);
+	StackLocation dst(dstObj->id, pack.dstSlot);
+
+	[[maybe_unused]] const CCreature * srcType = srcObj->getCreature(src.slot);
+	const CCreature * dstType = dstObj->getCreature(dst.slot);
+	TQuantity srcCount = srcObj->getStackCount(src.slot);
+
+	if(srcCount == pack.count) //moving whole stack
+	{
+		if(dstType) //stack at dest -> merge
+		{
+			assert(dstType == srcType);
+			const auto srcHero = dynamic_cast<CGHeroInstance*>(srcObj);
+			const auto dstHero = dynamic_cast<CGHeroInstance*>(dstObj);
+			auto srcStack = const_cast<CStackInstance*>(srcObj->getStackPtr(src.slot));
+			auto dstStack = const_cast<CStackInstance*>(dstObj->getStackPtr(dst.slot));
+			if(srcStack->getArt(ArtifactPosition::CREATURE_SLOT))
+			{
+				if(auto dstArt = dstStack->getArt(ArtifactPosition::CREATURE_SLOT))
+				{
+					bool artifactIsLost = true;
+
+					if(srcHero)
+					{
+						auto dstSlot = ArtifactUtils::getArtBackpackPosition(srcHero, dstArt->getTypeId());
+						if (dstSlot != ArtifactPosition::PRE_FIRST)
+						{
+							gs.getMap().moveArtifactInstance(*dstStack, ArtifactPosition::CREATURE_SLOT, *srcHero, dstSlot);
+							artifactIsLost = false;
+						}
+					}
+
+					if (artifactIsLost)
+					{
+						BulkEraseArtifacts ea;
+						ea.artHolder = dstHero->id;
+						ea.posPack.emplace_back(ArtifactPosition::CREATURE_SLOT);
+						ea.creature = dst.slot;
+						ea.visit(*this);
+						logNetwork->warn("Cannot move artifact! No free slots");
+					}
+					gs.getMap().moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
+					//TODO: choose from dialog
+				}
+				else //just move to the other slot before stack gets erased
+				{
+					gs.getMap().moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
+				}
+			}
+
+			auto movedStack = srcObj->detachStack(src.slot);
+			dstObj->joinStack(dst.slot, std::move(movedStack));
+		}
+		else
+		{
+			auto movedStack = srcObj->detachStack(src.slot);
+			dstObj->putStack(dst.slot, std::move(movedStack));
+		}
+	}
+	else
+	{
+		auto movedStack = srcObj->splitStack(src.slot, pack.count);
+		if(dstType) //stack at dest -> rebalance
+		{
+			assert(dstType == srcType);
+			dstObj->joinStack(dst.slot, std::move(movedStack));
+		}
+		else //move new stack to an empty slot
+		{
+			dstObj->putStack(dst.slot, std::move(movedStack));
+		}
+	}
+
+	srcObj->nodeHasChanged();
+	if (srcObj != dstObj)
+		dstObj->nodeHasChanged();
+}
+
+void GameStatePackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
+{
+	for(auto & move : pack.moves)
+		move.visit(*this);
+}
+
+void GameStatePackVisitor::visitPutArtifact(PutArtifact & pack)
+{
+	auto art = gs.getArtInstance(pack.id);
+	assert(!art->getParentNodes().empty());
+	auto hero = gs.getHero(pack.al.artHolder);
+	assert(hero);
+	assert(art && art->canBePutAt(hero, pack.al.slot));
+	assert(ArtifactUtils::checkIfSlotValid(*hero, pack.al.slot));
+	gs.getMap().putArtifactInstance(*hero, art->getId(), pack.al.slot);
+}
+
+void GameStatePackVisitor::visitBulkEraseArtifacts(BulkEraseArtifacts & pack)
+{
+	const auto artSet = gs.getArtSet(pack.artHolder);
+	assert(artSet);
+
+	std::sort(pack.posPack.begin(), pack.posPack.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool
+	{
+		return slot0.num > slot1.num;
+	});
+
+	for(const auto & slot : pack.posPack)
+	{
+		const auto slotInfo = artSet->getSlot(slot);
+		const ArtifactInstanceID artifactID = slotInfo->artifactID;
+		const CArtifactInstance * artifact = gs.getArtInstance(artifactID);
+		if(slotInfo->locked)
+		{
+			logGlobal->debug("Erasing locked artifact: %s", artifact->getType()->getNameTranslated());
+			DisassembledArtifact dis;
+			dis.al.artHolder = pack.artHolder;
+
+			for(auto & slotInfoWorn : artSet->artifactsWorn)
+			{
+				auto art = slotInfoWorn.second.getArt();
+				if(art->isCombined() && art->isPart(artifact))
+				{
+					dis.al.slot = artSet->getArtPos(art);
+					break;
+				}
+			}
+			assert((dis.al.slot != ArtifactPosition::PRE_FIRST) && "Failed to determine the assembly this locked artifact belongs to");
+			logGlobal->debug("Found the corresponding assembly: %s", artSet->getArt(dis.al.slot)->getType()->getNameTranslated());
+			dis.visit(*this);
+		}
+		else
+		{
+			logGlobal->debug("Erasing artifact %s", artifact->getType()->getNameTranslated());
+		}
+		gs.getMap().removeArtifactInstance(*artSet, slot);
+	}
+}
+
+void GameStatePackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
+{
+	const auto bulkArtsRemove = [this](std::vector<MoveArtifactInfo> & artsPack, CArtifactSet & artSet)
+	{
+		std::vector<ArtifactPosition> packToRemove;
+		for(const auto & slotsPair : artsPack)
+			packToRemove.push_back(slotsPair.srcPos);
+		std::sort(packToRemove.begin(), packToRemove.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool
+		{
+			return slot0.num > slot1.num;
+		});
+
+		for(const auto & slot : packToRemove)
+			gs.getMap().removeArtifactInstance(artSet, slot);
+	};
+
+	const auto bulkArtsPut = [this](std::vector<MoveArtifactInfo> & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet)
+	{
+		for(const auto & slotsPair : artsPack)
+		{
+			auto * art = initArtSet.getArt(slotsPair.srcPos);
+			assert(art);
+			gs.getMap().putArtifactInstance(dstArtSet, art->getId(), slotsPair.dstPos);
+		}
+	};
+
+	auto * leftSet = gs.getArtSet(ArtifactLocation(pack.srcArtHolder, pack.srcCreature));
+	assert(leftSet);
+	auto * rightSet = gs.getArtSet(ArtifactLocation(pack.dstArtHolder, pack.dstCreature));
+	assert(rightSet);
+	CArtifactFittingSet artInitialSetLeft(*leftSet);
+	bulkArtsRemove(pack.artsPack0, *leftSet);
+	if(!pack.artsPack1.empty())
+	{
+		CArtifactFittingSet artInitialSetRight(*rightSet);
+		bulkArtsRemove(pack.artsPack1, *rightSet);
+		bulkArtsPut(pack.artsPack1, artInitialSetRight, *leftSet);
+	}
+	bulkArtsPut(pack.artsPack0, artInitialSetLeft, *rightSet);
+}
+
+void GameStatePackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
+{
+	auto artSet = gs.getArtSet(pack.al.artHolder);
+	assert(artSet);
+	const auto transformedArt = artSet->getArt(pack.al.slot);
+	assert(transformedArt);
+	const auto builtArt = pack.artId.toArtifact();
+	assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(artSet, transformedArt->getTypeId()), [=](const CArtifact * art)->bool
+	{
+		return art->getId() == builtArt->getId();
+	}));
+
+	auto * combinedArt = gs.getMap().createArtifactComponent(pack.artId);
+
+	// Find slots for all involved artifacts
+	std::set<ArtifactPosition, std::greater<>> slotsInvolved = { pack.al.slot };
+	CArtifactFittingSet fittingSet(*artSet);
+	auto parts = builtArt->getConstituents();
+	parts.erase(std::find(parts.begin(), parts.end(), transformedArt->getType()));
+	for(const auto constituent : parts)
+	{
+		const auto slot = fittingSet.getArtPos(constituent->getId(), false, false);
+		fittingSet.lockSlot(slot);
+		assert(slot != ArtifactPosition::PRE_FIRST);
+		slotsInvolved.insert(slot);
+	}
+
+	// Find a slot for combined artifact
+	if(ArtifactUtils::isSlotEquipment(pack.al.slot) && ArtifactUtils::isSlotBackpack(*slotsInvolved.begin()))
+	{
+		pack.al.slot = ArtifactPosition::BACKPACK_START;
+	}
+	else if(ArtifactUtils::isSlotBackpack(pack.al.slot))
+	{
+		for(const auto & slot : slotsInvolved)
+			if(ArtifactUtils::isSlotBackpack(slot))
+				pack.al.slot = slot;
+	}
+	else
+	{
+		for(const auto & slot : slotsInvolved)
+			if(!vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), pack.al.slot)
+			   && vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), slot))
+			{
+				pack.al.slot = slot;
+				break;
+			}
+	}
+
+	// Delete parts from hero
+	for(const auto & slot : slotsInvolved)
+	{
+		const auto constituentInstance = artSet->getArt(slot);
+		gs.getMap().removeArtifactInstance(*artSet, slot);
+
+		if(!combinedArt->getType()->isFused())
+		{
+			if(ArtifactUtils::isSlotEquipment(pack.al.slot) && slot != pack.al.slot)
+				combinedArt->addPart(constituentInstance, slot);
+			else
+				combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST);
+		}
+	}
+
+	// Put new combined artifacts
+	gs.getMap().putArtifactInstance(*artSet, combinedArt->getId(), pack.al.slot);
+}
+
+void GameStatePackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
+{
+	auto hero = gs.getHero(pack.al.artHolder);
+	assert(hero);
+	auto disassembledArtID = hero->getArtID(pack.al.slot);
+	auto disassembledArt = gs.getArtInstance(disassembledArtID);
+	assert(disassembledArt);
+
+	const auto parts = disassembledArt->getPartsInfo();
+	gs.getMap().removeArtifactInstance(*hero, pack.al.slot);
+	for(auto & part : parts)
+	{
+		// ArtifactPosition::PRE_FIRST is value of main part slot -> it'll replace combined artifact in its pos
+		auto slot = (ArtifactUtils::isSlotEquipment(part.slot) ? part.slot : pack.al.slot);
+		disassembledArt->detachFromSource(*part.getArtifact());
+		gs.getMap().putArtifactInstance(*hero, part.getArtifact()->getId(), slot);
+	}
+	gs.getMap().eraseArtifactInstance(disassembledArt->getId());
+}
+
+void GameStatePackVisitor::visitHeroVisit(HeroVisit & pack)
+{
+}
+
+void GameStatePackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts & pack)
+{
+	if(pack.id != ObjectInstanceID::NONE)
+	{
+		if(auto * bm = dynamic_cast<CGBlackMarket *>(gs.getObjInstance(pack.id)))
+		{
+			bm->artifacts = pack.arts;
+		}
+		else
+		{
+			logNetwork->error("Wrong black market id!");
+		}
+	}
+	else
+	{
+		gs.getMap().townMerchantArtifacts = pack.arts;
+	}
+}
+
+void GameStatePackVisitor::visitNewTurn(NewTurn & pack)
+{
+	gs.day = pack.day;
+
+	// Update bonuses before doing anything else so hero don't get more MP than needed
+	gs.globalEffects.removeBonusesRecursive(Bonus::OneDay); //works for children -> all game objs
+	gs.globalEffects.reduceBonusDurations(Bonus::NDays);
+	gs.globalEffects.reduceBonusDurations(Bonus::OneWeek);
+	//TODO not really a single root hierarchy, what about bonuses placed elsewhere? [not an issue with H3 mechanics but in the future...]
+
+	for(auto & manaPack : pack.heroesMana)
+		manaPack.visit(*this);
+
+	for(auto & movePack : pack.heroesMovement)
+		movePack.visit(*this);
+
+	gs.heroesPool->onNewDay();
+
+	for(auto & entry : pack.playerIncome)
+	{
+		gs.getPlayerState(entry.first)->resources += entry.second;
+		gs.getPlayerState(entry.first)->resources.amin(GameConstants::PLAYER_RESOURCES_CAP);
+	}
+
+	for(auto & creatureSet : pack.availableCreatures) //set available creatures in towns
+		creatureSet.visit(*this);
+
+	for (const auto & townID : gs.getMap().getAllTowns())
+	{
+		auto t = gs.getTown(townID);
+		t->built = 0;
+		t->spellResearchCounterDay = 0;
+	}
+
+	if(pack.newRumor)
+		gs.currentRumor = *pack.newRumor;
+}
+
+void GameStatePackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
+{
+	CGObjectInstance *obj = gs.getObjInstance(pack.id);
+	if(!obj)
+	{
+		logNetwork->error("Wrong object ID - property cannot be set!");
+		return;
+	}
+
+	auto * cai = dynamic_cast<CArmedInstance *>(obj);
+
+	if(pack.what == ObjProperty::OWNER && obj->asOwnable())
+	{
+		PlayerColor oldOwner = obj->getOwner();
+		PlayerColor newOwner = pack.identifier.as<PlayerColor>();
+		if(oldOwner.isValidPlayer())
+			gs.getPlayerState(oldOwner)->removeOwnedObject(obj);
+
+		if(newOwner.isValidPlayer())
+			gs.getPlayerState(newOwner)->addOwnedObject(obj);
+	}
+
+	if(pack.what == ObjProperty::OWNER && cai)
+	{
+		if(obj->ID == Obj::TOWN)
+		{
+			auto * t = dynamic_cast<CGTownInstance *>(obj);
+			assert(t);
+
+			PlayerColor oldOwner = t->tempOwner;
+			if(oldOwner.isValidPlayer())
+			{
+				auto * state = gs.getPlayerState(oldOwner);
+				if(state->getTowns().empty())
+					state->daysWithoutCastle = 0;
+			}
+			if(pack.identifier.as<PlayerColor>().isValidPlayer())
+			{
+				//reset counter before NewTurn to avoid no town message if game loaded at turn when one already captured
+				PlayerState * p = gs.getPlayerState(pack.identifier.as<PlayerColor>());
+				if(p->daysWithoutCastle)
+					p->daysWithoutCastle = std::nullopt;
+			}
+		}
+
+		cai->detachFromBonusSystem(gs);
+		obj->setProperty(pack.what, pack.identifier);
+		cai->attachToBonusSystem(gs);
+	}
+	else //not an armed instance
+	{
+		obj->setProperty(pack.what, pack.identifier);
+	}
+}
+
+void GameStatePackVisitor::visitHeroLevelUp(HeroLevelUp & pack)
+{
+	auto * hero = gs.getHero(pack.heroId);
+	assert(hero);
+	hero->levelUp(pack.skills);
+}
+
+void GameStatePackVisitor::visitCommanderLevelUp(CommanderLevelUp & pack)
+{
+	auto * hero = gs.getHero(pack.heroId);
+	assert(hero);
+	const auto & commander = hero->getCommander();
+	assert(commander);
+	commander->levelUp();
+}
+
+void GameStatePackVisitor::visitBattleStart(BattleStart & pack)
+{
+	assert(pack.battleID == gs.nextBattleID);
+
+	pack.info->battleID = gs.nextBattleID;
+	pack.info->localInit();
+
+	gs.currentBattles.push_back(std::move(pack.info));
+	gs.nextBattleID = BattleID(gs.nextBattleID.getNum() + 1);
+}
+
+void GameStatePackVisitor::visitBattleNextRound(BattleNextRound & pack)
+{
+	gs.getBattle(pack.battleID)->nextRound();
+}
+
+void GameStatePackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & pack)
+{
+	gs.getBattle(pack.battleID)->nextTurn(pack.stack, pack.reason);
+}
+
+void GameStatePackVisitor::visitBattleTriggerEffect(BattleTriggerEffect & pack)
+{
+	CStack * st = gs.getBattle(pack.battleID)->getStack(pack.stackID);
+	assert(st);
+	switch(static_cast<BonusType>(pack.effect))
+	{
+		case BonusType::HP_REGENERATION:
+		{
+			int64_t toHeal = pack.val;
+			st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
+			break;
+		}
+		case BonusType::MANA_DRAIN:
+		{
+			CGHeroInstance * h = gs.getHero(ObjectInstanceID(pack.additionalInfo));
+			st->drainedMana = true;
+			h->mana -= pack.val;
+			vstd::amax(h->mana, 0);
+			break;
+		}
+		case BonusType::POISON:
+		{
+			auto b = st->getLocalBonus(Selector::source(BonusSource::SPELL_EFFECT, SpellID(SpellID::POISON))
+										   .And(Selector::type()(BonusType::STACK_HEALTH)));
+			if (b)
+				b->val = pack.val;
+			break;
+		}
+		case BonusType::ENCHANTER:
+		case BonusType::MORALE:
+			break;
+		case BonusType::FEAR:
+			st->fear = true;
+			break;
+		default:
+			logNetwork->error("Unrecognized trigger effect type %d", pack.effect);
+	}
+}
+
+void GameStatePackVisitor::visitBattleUpdateGateState(BattleUpdateGateState & pack)
+{
+	if(gs.getBattle(pack.battleID))
+		gs.getBattle(pack.battleID)->si.gateState = pack.state;
+}
+
+void GameStatePackVisitor::visitBattleCancelled(BattleCancelled & pack)
+{
+	auto currentBattle = boost::range::find_if(gs.currentBattles, [&](const auto & battle)
+	{
+		return battle->battleID == pack.battleID;
+	});
+
+	assert(currentBattle != gs.currentBattles.end());
+	gs.currentBattles.erase(currentBattle);
+}
+
+void GameStatePackVisitor::visitBattleResultAccepted(BattleResultAccepted & pack)
+{
+	// Remove any "until next battle" bonuses
+	if(const auto attackerHero = gs.getHero(pack.heroResult[BattleSide::ATTACKER].heroID))
+		attackerHero->removeBonusesRecursive(Bonus::OneBattle);
+	if(const auto defenderHero = gs.getHero(pack.heroResult[BattleSide::DEFENDER].heroID))
+		defenderHero->removeBonusesRecursive(Bonus::OneBattle);
+
+	if(pack.winnerSide != BattleSide::NONE)
+	{
+		// Grow up growing artifacts
+		if(const auto winnerHero = gs.getHero(pack.heroResult[pack.winnerSide].heroID))
+		{
+			if(winnerHero->getCommander() && winnerHero->getCommander()->alive)
+
+			{
+				for(auto & art : winnerHero->getCommander()->artifactsWorn)
+					gs.getArtInstance(art.second.getID())->growingUp();
+			}
+			for(auto & art : winnerHero->artifactsWorn)
+				gs.getArtInstance(art.second.getID())->growingUp();
+		}
+	}
+
+	if(gs.getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+	{
+		if(const auto attackerArmy = gs.getArmyInstance(pack.heroResult[BattleSide::ATTACKER].armyID))
+			attackerArmy->giveAverageStackExperience(pack.heroResult[BattleSide::ATTACKER].exp);
+
+		if(const auto defenderArmy = gs.getArmyInstance(pack.heroResult[BattleSide::DEFENDER].armyID))
+			defenderArmy->giveAverageStackExperience(pack.heroResult[BattleSide::DEFENDER].exp);
+	}
+}
+
+void GameStatePackVisitor::visitBattleStackMoved(BattleStackMoved & pack)
+{
+	BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID));
+	pack.visitTyped(battleVisitor);
+}
+
+void GameStatePackVisitor::visitBattleAttack(BattleAttack & pack)
+{
+	CStack * attacker = gs.getBattle(pack.battleID)->getStack(pack.stackAttacking);
+	assert(attacker);
+
+	pack.attackerChanges.visit(*this);
+
+	for(BattleStackAttacked & stack : pack.bsa)
+		gs.getBattle(pack.battleID)->setUnitState(stack.newState.id, stack.newState.data, stack.newState.healthDelta);
+
+	attacker->removeBonusesRecursive(Bonus::UntilAttack);
+
+	if(!pack.counter())
+		attacker->removeBonusesRecursive(Bonus::UntilOwnAttack);
+}
+
+void GameStatePackVisitor::visitStartAction(StartAction & pack)
+{
+	CStack *st = gs.getBattle(pack.battleID)->getStack(pack.ba.stackNumber);
+
+	if(pack.ba.actionType == EActionType::END_TACTIC_PHASE)
+	{
+		gs.getBattle(pack.battleID)->tacticDistance = 0;
+		return;
+	}
+
+	if(gs.getBattle(pack.battleID)->tacticDistance)
+	{
+		// moves in tactics phase do not affect creature status
+		// (tactics stack queue is managed by client)
+		return;
+	}
+
+	if (pack.ba.isUnitAction())
+	{
+		assert(st); // stack must exists for all non-hero actions
+
+		switch(pack.ba.actionType)
+		{
+			case EActionType::DEFEND:
+				st->waiting = false;
+				st->defending = true;
+				st->defendingAnim = true;
+				break;
+			case EActionType::WAIT:
+				st->defendingAnim = false;
+				st->waiting = true;
+				st->waitedThisTurn = true;
+				break;
+			case EActionType::HERO_SPELL: //no change in current stack state
+				break;
+			default: //any active stack action - attack, catapult, heal, spell...
+				st->waiting = false;
+				st->defendingAnim = false;
+				st->movedThisRound = true;
+				st->castSpellThisTurn = pack.ba.actionType == EActionType::MONSTER_SPELL;
+				break;
+		}
+	}
+	else
+	{
+		if(pack.ba.actionType == EActionType::HERO_SPELL)
+			gs.getBattle(pack.battleID)->getSide(pack.ba.side).usedSpellsHistory.push_back(pack.ba.spell);
+	}
+}
+
+void GameStatePackVisitor::visitBattleSpellCast(BattleSpellCast & pack)
+{
+	if(pack.castByHero && pack.side != BattleSide::NONE)
+		gs.getBattle(pack.battleID)->getSide(pack.side).castSpellsCount++;
+}
+
+void GameStatePackVisitor::visitSetStackEffect(SetStackEffect & pack)
+{
+	BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID));
+	pack.visitTyped(battleVisitor);
+}
+
+void GameStatePackVisitor::visitStacksInjured(StacksInjured & pack)
+{
+	BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID));
+	pack.visitTyped(battleVisitor);
+}
+
+void GameStatePackVisitor::visitBattleUnitsChanged(BattleUnitsChanged & pack)
+{
+	BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID));
+	pack.visitTyped(battleVisitor);
+}
+
+void GameStatePackVisitor::visitBattleResultsApplied(BattleResultsApplied & pack)
+{
+	pack.learnedSpells.visit(*this);
+
+	for(auto & artPack : pack.artifacts)
+		artPack.visit(*this);
+
+	const auto currentBattle = std::find_if(gs.currentBattles.begin(), gs.currentBattles.end(),
+											[&](const auto & battle)
+											{
+												return battle->battleID == pack.battleID;
+											});
+
+	assert(currentBattle != gs.currentBattles.end());
+	gs.currentBattles.erase(currentBattle);
+}
+
+void GameStatePackVisitor::visitBattleObstaclesChanged(BattleObstaclesChanged & pack)
+{
+	BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID));
+	pack.visitTyped(battleVisitor);
+}
+
+void GameStatePackVisitor::visitCatapultAttack(CatapultAttack & pack)
+{
+	BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID));
+	pack.visitTyped(battleVisitor);
+}
+
+void GameStatePackVisitor::visitBattleSetStackProperty(BattleSetStackProperty & pack)
+{
+	CStack * stack = gs.getBattle(pack.battleID)->getStack(pack.stackID, false);
+	switch(pack.which)
+	{
+		case BattleSetStackProperty::CASTS:
+		{
+			if(pack.absolute)
+				logNetwork->error("Can not change casts in absolute mode");
+			else
+				stack->casts.use(-pack.val);
+			break;
+		}
+		case BattleSetStackProperty::ENCHANTER_COUNTER:
+		{
+			auto & counter = gs.getBattle(pack.battleID)->getSide(gs.getBattle(pack.battleID)->whatSide(stack->unitOwner())).enchanterCounter;
+			if(pack.absolute)
+				counter = pack.val;
+			else
+				counter += pack.val;
+			vstd::amax(counter, 0);
+			break;
+		}
+		case BattleSetStackProperty::UNBIND:
+		{
+			stack->removeBonusesRecursive(Selector::type()(BonusType::BIND_EFFECT));
+			break;
+		}
+		case BattleSetStackProperty::CLONED:
+		{
+			stack->cloned = true;
+			break;
+		}
+		case BattleSetStackProperty::HAS_CLONE:
+		{
+			stack->cloneID = pack.val;
+			break;
+		}
+	}
+}
+
+void GameStatePackVisitor::visitPlayerCheated(PlayerCheated & pack)
+{
+	assert(pack.player.isValidPlayer());
+
+	gs.getPlayerState(pack.player)->enteredLosingCheatCode = pack.losingCheatCode;
+	gs.getPlayerState(pack.player)->enteredWinningCheatCode = pack.winningCheatCode;
+	gs.getPlayerState(pack.player)->cheated = true;
+}
+
+void GameStatePackVisitor::visitPlayerStartsTurn(PlayerStartsTurn & pack)
+{
+	//assert(gs.actingPlayers.count(player) == 0);//Legal - may happen after loading of deserialized map state
+	gs.actingPlayers.insert(pack.player);
+}
+
+void GameStatePackVisitor::visitPlayerEndsTurn(PlayerEndsTurn & pack)
+{
+	assert(gs.actingPlayers.count(pack.player) == 1);
+	gs.actingPlayers.erase(pack.player);
+}
+
+void GameStatePackVisitor::visitDaysWithoutTown(DaysWithoutTown & pack)
+{
+	auto & playerState = gs.players.at(pack.player);
+	playerState.daysWithoutCastle = pack.daysWithoutCastle;
+}
+
+void GameStatePackVisitor::visitTurnTimeUpdate(TurnTimeUpdate & pack)
+{
+	auto & playerState = gs.players.at(pack.player);
+	playerState.turnTimer = pack.turnTimer;
+}
+
+void GameStatePackVisitor::visitEntitiesChanged(EntitiesChanged & pack)
+{
+	for(const auto & change : pack.changes)
+		gs.updateEntity(change.metatype, change.entityIndex, change.data);
+}
+
+void GameStatePackVisitor::visitSetRewardableConfiguration(SetRewardableConfiguration & pack)
+{
+	auto * objectPtr = gs.getObjInstance(pack.objectID);
+
+	if (!pack.buildingID.hasValue())
+	{
+		auto * rewardablePtr = dynamic_cast<CRewardableObject *>(objectPtr);
+		assert(rewardablePtr);
+		rewardablePtr->configuration = pack.configuration;
+		rewardablePtr->initializeGuards();
+	}
+	else
+	{
+		auto * townPtr = dynamic_cast<CGTownInstance*>(objectPtr);
+		TownBuildingInstance * buildingPtr = nullptr;
+
+		for (auto & building : townPtr->rewardableBuildings)
+			if (building.second->getBuildingType() == pack.buildingID)
+				buildingPtr = building.second.get();
+
+		auto * rewardablePtr = dynamic_cast<TownRewardableBuildingInstance *>(buildingPtr);
+		assert(rewardablePtr);
+		rewardablePtr->configuration = pack.configuration;
+	}
+}
+
+void BattleStatePackVisitor::visitBattleStackMoved(BattleStackMoved & pack)
+{
+	battleState.moveUnit(pack.stack, pack.tilesToMove.back());
+}
+
+void BattleStatePackVisitor::visitCatapultAttack(CatapultAttack & pack)
+{
+	const auto * town = battleState.getDefendedTown();
+	if(!town)
+		throw std::runtime_error("CatapultAttack without town!");
+
+	if(town->fortificationsLevel().wallsHealth == 0)
+		throw std::runtime_error("CatapultAttack without walls!");
+
+	for(const auto & part : pack.attackedParts)
+	{
+		auto newWallState = SiegeInfo::applyDamage(battleState.getWallState(part.attackedPart), part.damageDealt);
+		battleState.setWallState(part.attackedPart, newWallState);
+	}
+}
+
+void BattleStatePackVisitor::visitBattleObstaclesChanged(BattleObstaclesChanged & pack)
+{
+	for(const auto & change : pack.changes)
+	{
+		switch(change.operation)
+		{
+			case BattleChanges::EOperation::REMOVE:
+				battleState.removeObstacle(change.id);
+				break;
+			case BattleChanges::EOperation::ADD:
+				battleState.addObstacle(change);
+				break;
+			case BattleChanges::EOperation::UPDATE:
+				battleState.updateObstacle(change);
+				break;
+			default:
+				throw std::runtime_error("Unknown obstacle operation");
+				break;
+		}
+	}
+}
+
+void BattleStatePackVisitor::visitSetStackEffect(SetStackEffect & pack)
+{
+	for(const auto & stackData : pack.toRemove)
+		battleState.removeUnitBonus(stackData.first, stackData.second);
+
+	for(const auto & stackData : pack.toUpdate)
+		battleState.updateUnitBonus(stackData.first, stackData.second);
+
+	for(const auto & stackData : pack.toAdd)
+		battleState.addUnitBonus(stackData.first, stackData.second);
+}
+
+void BattleStatePackVisitor::visitStacksInjured(StacksInjured & pack)
+{
+	for(const BattleStackAttacked & stack : pack.stacks)
+	{
+		battleState.setUnitState(stack.newState.id, stack.newState.data, stack.newState.healthDelta);
+	}
+}
+
+void BattleStatePackVisitor::visitBattleUnitsChanged(BattleUnitsChanged & pack)
+{
+	for(auto & elem : pack.changedStacks)
+	{
+		switch(elem.operation)
+		{
+			case BattleChanges::EOperation::RESET_STATE:
+				battleState.setUnitState(elem.id, elem.data, elem.healthDelta);
+				break;
+			case BattleChanges::EOperation::REMOVE:
+				battleState.removeUnit(elem.id);
+				break;
+			case BattleChanges::EOperation::ADD:
+				battleState.addUnit(elem.id, elem.data);
+				break;
+			case BattleChanges::EOperation::UPDATE:
+				battleState.updateUnit(elem.id, elem.data);
+				break;
+			default:
+				throw std::runtime_error("Unknown unit operation");
+				break;
+		}
+	}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 121 - 0
lib/gameState/GameStatePackVisitor.h

@@ -0,0 +1,121 @@
+/*
+ * GameStatePackVisitor.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../networkPacks/NetPackVisitor.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGameState;
+
+class GameStatePackVisitor final : public ICPackVisitor
+{
+private:
+	CGameState & gs;
+
+public:
+	GameStatePackVisitor(CGameState & gs)
+		: gs(gs)
+	{
+	}
+
+	void visitSetResources(SetResources & pack) override;
+	void visitSetPrimSkill(SetPrimSkill & pack) override;
+	void visitSetSecSkill(SetSecSkill & pack) override;
+	void visitHeroVisitCastle(HeroVisitCastle & pack) override;
+	void visitSetMana(SetMana & pack) override;
+	void visitSetMovePoints(SetMovePoints & pack) override;
+	void visitSetResearchedSpells(SetResearchedSpells & pack) override;
+	void visitFoWChange(FoWChange & pack) override;
+	void visitChangeStackCount(ChangeStackCount & pack) override;
+	void visitSetStackType(SetStackType & pack) override;
+	void visitEraseStack(EraseStack & pack) override;
+	void visitSwapStacks(SwapStacks & pack) override;
+	void visitInsertNewStack(InsertNewStack & pack) override;
+	void visitRebalanceStacks(RebalanceStacks & pack) override;
+	void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) override;
+	void visitPutArtifact(PutArtifact & pack) override;
+	void visitBulkEraseArtifacts(BulkEraseArtifacts & pack) override;
+	void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
+	void visitAssembledArtifact(AssembledArtifact & pack) override;
+	void visitDisassembledArtifact(DisassembledArtifact & pack) override;
+	void visitHeroVisit(HeroVisit & pack) override;
+	void visitNewTurn(NewTurn & pack) override;
+	void visitGiveBonus(GiveBonus & pack) override;
+	void visitChangeObjPos(ChangeObjPos & pack) override;
+	void visitPlayerEndsTurn(PlayerEndsTurn & pack) override;
+	void visitPlayerEndsGame(PlayerEndsGame & pack) override;
+	void visitPlayerReinitInterface(PlayerReinitInterface & pack) override;
+	void visitRemoveBonus(RemoveBonus & pack) override;
+	void visitRemoveObject(RemoveObject & pack) override;
+	void visitTryMoveHero(TryMoveHero & pack) override;
+	void visitNewStructures(NewStructures & pack) override;
+	void visitRazeStructures(RazeStructures & pack) override;
+	void visitSetAvailableCreatures(SetAvailableCreatures & pack) override;
+	void visitSetHeroesInTown(SetHeroesInTown & pack) override;
+	void visitHeroRecruited(HeroRecruited & pack) override;
+	void visitGiveHero(GiveHero & pack) override;
+	void visitSetObjectProperty(SetObjectProperty & pack) override;
+	void visitHeroLevelUp(HeroLevelUp & pack) override;
+	void visitCommanderLevelUp(CommanderLevelUp & pack) override;
+	void visitBattleStart(BattleStart & pack) override;
+	void visitBattleSetActiveStack(BattleSetActiveStack & pack) override;
+	void visitBattleTriggerEffect(BattleTriggerEffect & pack) override;
+	void visitBattleAttack(BattleAttack & pack) override;
+	void visitBattleSpellCast(BattleSpellCast & pack) override;
+	void visitSetStackEffect(SetStackEffect & pack) override;
+	void visitStacksInjured(StacksInjured & pack) override;
+	void visitBattleUnitsChanged(BattleUnitsChanged & pack) override;
+	void visitBattleObstaclesChanged(BattleObstaclesChanged & pack) override;
+	void visitBattleStackMoved(BattleStackMoved & pack) override;
+	void visitCatapultAttack(CatapultAttack & pack) override;
+	void visitPlayerStartsTurn(PlayerStartsTurn & pack) override;
+	void visitNewObject(NewObject & pack) override;
+	void visitSetAvailableArtifacts(SetAvailableArtifacts & pack) override;
+	void visitEntitiesChanged(EntitiesChanged & pack) override;
+	void visitSetCommanderProperty(SetCommanderProperty & pack) override;
+	void visitAddQuest(AddQuest & pack) override;
+	void visitUpdateArtHandlerLists(UpdateArtHandlerLists & pack) override;
+	void visitChangeFormation(ChangeFormation & pack) override;
+	void visitChangeSpells(ChangeSpells & pack) override;
+	void visitSetAvailableHero(SetAvailableHero & pack) override;
+	void visitChangeObjectVisitors(ChangeObjectVisitors & pack) override;
+	void visitChangeArtifactsCostume(ChangeArtifactsCostume & pack) override;
+	void visitNewArtifact(NewArtifact & pack) override;
+	void visitBattleUpdateGateState(BattleUpdateGateState & pack) override;
+	void visitPlayerCheated(PlayerCheated & pack) override;
+	void visitDaysWithoutTown(DaysWithoutTown & pack) override;
+	void visitStartAction(StartAction & pack) override;
+	void visitSetRewardableConfiguration(SetRewardableConfiguration & pack) override;
+	void visitBattleSetStackProperty(BattleSetStackProperty & pack) override;
+	void visitBattleNextRound(BattleNextRound & pack) override;
+	void visitBattleCancelled(BattleCancelled & pack) override;
+	void visitBattleResultsApplied(BattleResultsApplied & pack) override;
+	void visitBattleResultAccepted(BattleResultAccepted & pack) override;
+	void visitTurnTimeUpdate(TurnTimeUpdate & pack) override;
+};
+
+class DLL_LINKAGE BattleStatePackVisitor final : public ICPackVisitor
+{
+	IBattleState & battleState;
+public:
+	BattleStatePackVisitor(IBattleState & battleState)
+		:battleState(battleState)
+	{}
+
+	void visitSetStackEffect(SetStackEffect & pack) override;
+	void visitStacksInjured(StacksInjured & pack) override;
+	void visitBattleUnitsChanged(BattleUnitsChanged & pack) override;
+	void visitBattleObstaclesChanged(BattleObstaclesChanged & pack) override;
+	void visitCatapultAttack(CatapultAttack & pack) override;
+	void visitBattleStackMoved(BattleStackMoved & pack) override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 5 - 1
lib/mapObjects/FlaggableMapObject.cpp

@@ -15,6 +15,7 @@
 #include "CGHeroInstance.h"
 #include "../networkPacks/PacksForClient.h"
 #include "../mapObjectConstructors/FlaggableInstanceConstructor.h"
+#include "../gameState/GameStatePackVisitor.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -88,7 +89,10 @@ void FlaggableMapObject::giveBonusTo(const PlayerColor & player, bool onInit) co
 		// Proper fix would be to make FlaggableMapObject into bonus system node
 		// Unfortunately this will cause saves breakage
 		if(onInit)
-			gb.applyGs(&cb->gameState());
+		{
+			GameStatePackVisitor visitor(cb->gameState());
+			gb.visit(visitor);
+		}
 		else
 			cb->sendAndApply(gb);
 	}

+ 6 - 3
lib/networkPacks/NetPackVisitor.h

@@ -46,7 +46,7 @@ public:
 	virtual void visitSetMana(SetMana & pack) {}
 	virtual void visitSetMovePoints(SetMovePoints & pack) {}
 	virtual void visitFoWChange(FoWChange & pack) {}
-	virtual void visitSetAvailableHeroes(SetAvailableHero & pack) {}
+	virtual void visitSetAvailableHero(SetAvailableHero & pack) {}
 	virtual void visitGiveBonus(GiveBonus & pack) {}
 	virtual void visitChangeObjPos(ChangeObjPos & pack) {}
 	virtual void visitPlayerEndsTurn(PlayerEndsTurn & pack) {};
@@ -78,7 +78,7 @@ public:
 	virtual void visitRebalanceStacks(RebalanceStacks & pack) {}
 	virtual void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) {}
 	virtual void visitPutArtifact(PutArtifact & pack) {}
-	virtual void visitEraseArtifact(BulkEraseArtifacts & pack) {}
+	virtual void visitBulkEraseArtifacts(BulkEraseArtifacts & pack) {}
 	virtual void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) {}
 	virtual void visitAssembledArtifact(AssembledArtifact & pack) {}
 	virtual void visitDisassembledArtifact(DisassembledArtifact & pack) {}
@@ -123,7 +123,7 @@ public:
 	virtual void visitBulkMoveArmy(BulkMoveArmy & pack) {}
 	virtual void visitBulkSplitStack(BulkSplitStack & pack) {}
 	virtual void visitBulkMergeStacks(BulkMergeStacks & pack) {}
-	virtual void visitBulkSmartSplitStack(BulkSplitAndRebalanceStack & pack) {}
+	virtual void visitBulkSplitAndRebalanceStack(BulkSplitAndRebalanceStack & pack) {}
 	virtual void visitDisbandCreature(DisbandCreature & pack) {}
 	virtual void visitBuildStructure(BuildStructure & pack) {}
 	virtual void visitVisitTownBuilding(VisitTownBuilding & pack) {}
@@ -178,6 +178,9 @@ public:
 	virtual void visitLobbyPvPAction(LobbyPvPAction & pack) {}
 	virtual void visitLobbyDelete(LobbyDelete & pack) {}
 	virtual void visitSaveLocalState(SaveLocalState & pack) {}
+	virtual void visitBattleCancelled(BattleCancelled & pack) {}
+	virtual void visitBattleResultAccepted(BattleResultAccepted & pack) {}
+	virtual void visitBattleStackMoved(BattleLogMessage & pack) {}
 };
 
 VCMI_LIB_NAMESPACE_END

+ 7 - 6
lib/networkPacks/NetPacksBase.h

@@ -33,21 +33,15 @@ struct DLL_LINKAGE CPack : public Serializeable
 	void visit(ICPackVisitor & cpackVisitor);
 
 protected:
-	/// <summary>
 	/// For basic types of netpacks hierarchy like CPackForClient. Called first.
-	/// </summary>
 	virtual void visitBasic(ICPackVisitor & cpackVisitor);
 
-	/// <summary>
 	/// For leaf types of netpacks hierarchy. Called after visitBasic.
-	/// </summary>
 	virtual void visitTyped(ICPackVisitor & cpackVisitor);
 };
 
 struct DLL_LINKAGE CPackForClient : public CPack
 {
-	virtual void applyGs(CGameState * gs) = 0;
-
 protected:
 	void visitBasic(ICPackVisitor & cpackVisitor) override;
 };
@@ -57,6 +51,13 @@ struct DLL_LINKAGE Query : public CPackForClient
 	QueryID queryID; // equals to -1 if it is not an actual query (and should not be answered)
 };
 
+struct PackForClientBattle : public CPackForClient
+{
+	BattleID battleID;
+
+	void visitTyped(ICPackVisitor & visitor) override;
+};
+
 struct DLL_LINKAGE CPackForServer : public CPack
 {
 	mutable PlayerColor player = PlayerColor::NEUTRAL;

+ 3 - 1593
lib/networkPacks/NetPacksLib.cpp

@@ -13,39 +13,9 @@
 #include "PacksForServer.h"
 #include "SaveLocalState.h"
 #include "SetRewardableConfiguration.h"
-#include "StackLocation.h"
 #include "PacksForLobby.h"
 #include "SetStackEffect.h"
 #include "NetPackVisitor.h"
-#include "texts/CGeneralTextHandler.h"
-#include "GameLibrary.h"
-#include "mapping/CMap.h"
-#include "spells/CSpellHandler.h"
-#include "CCreatureHandler.h"
-#include "gameState/CGameState.h"
-#include "gameState/TavernHeroesPool.h"
-#include "CStack.h"
-#include "battle/BattleInfo.h"
-#include "mapping/CMapInfo.h"
-#include "StartInfo.h"
-#include "CPlayerState.h"
-#include "TerrainHandler.h"
-#include "entities/artifact/ArtifactUtils.h"
-#include "entities/artifact/CArtifact.h"
-#include "entities/artifact/CArtifactFittingSet.h"
-#include "entities/building/CBuilding.h"
-#include "entities/building/TownFortifications.h"
-#include "mapObjects/CGCreature.h"
-#include "mapObjects/CGMarket.h"
-#include "mapObjects/TownBuildingInstance.h"
-#include "mapObjects/CGTownInstance.h"
-#include "mapObjects/CQuest.h"
-#include "mapObjects/MiscObjects.h"
-#include "mapObjectConstructors/AObjectTypeHandler.h"
-#include "mapObjectConstructors/CObjectClassesHandler.h"
-#include "campaign/CampaignState.h"
-#include "IGameSettings.h"
-#include "mapObjects/FlaggableMapObject.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -98,7 +68,6 @@ void SaveLocalState::visitTyped(ICPackVisitor & visitor)
 	visitor.visitSaveLocalState(*this);
 }
 
-
 void PackageApplied::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitPackageApplied(*this);
@@ -185,7 +154,7 @@ void FoWChange::visitTyped(ICPackVisitor & visitor)
 
 void SetAvailableHero::visitTyped(ICPackVisitor & visitor)
 {
-	visitor.visitSetAvailableHeroes(*this);
+	visitor.visitSetAvailableHero(*this);
 }
 
 void GiveBonus::visitTyped(ICPackVisitor & visitor)
@@ -340,7 +309,7 @@ void PutArtifact::visitTyped(ICPackVisitor & visitor)
 
 void BulkEraseArtifacts::visitTyped(ICPackVisitor & visitor)
 {
-	visitor.visitEraseArtifact(*this);
+	visitor.visitBulkEraseArtifacts(*this);
 }
 
 void BulkMoveArtifacts::visitTyped(ICPackVisitor & visitor)
@@ -570,7 +539,7 @@ void BulkMergeStacks::visitTyped(ICPackVisitor & visitor)
 
 void BulkSplitAndRebalanceStack::visitTyped(ICPackVisitor & visitor)
 {
-	visitor.visitBulkSmartSplitStack(*this);
+	visitor.visitBulkSplitAndRebalanceStack(*this);
 }
 
 void DisbandCreature::visitTyped(ICPackVisitor & visitor)
@@ -838,1568 +807,9 @@ void LobbyDelete::visitTyped(ICPackVisitor & visitor)
 	visitor.visitLobbyDelete(*this);
 }
 
-void SetResources::applyGs(CGameState *gs)
-{
-	assert(player.isValidPlayer());
-	if(abs)
-		gs->getPlayerState(player)->resources = res;
-	else
-		gs->getPlayerState(player)->resources += res;
-	gs->getPlayerState(player)->resources.amin(GameConstants::PLAYER_RESOURCES_CAP);
-
-	//just ensure that player resources are not negative
-	//server is responsible to check if player can afford deal
-	//but events on server side are allowed to take more than player have
-	gs->getPlayerState(player)->resources.positive();
-}
-
-void SetPrimSkill::applyGs(CGameState *gs)
-{
-	CGHeroInstance * hero = gs->getHero(id);
-	assert(hero);
-	hero->setPrimarySkill(which, val, abs);
-}
-
-void SetSecSkill::applyGs(CGameState *gs)
-{
-	CGHeroInstance *hero = gs->getHero(id);
-	hero->setSecSkillLevel(which, val, abs);
-}
-
-void SetCommanderProperty::applyGs(CGameState *gs)
-{
-	const auto & commander = gs->getHero(heroid)->getCommander();
-	assert (commander);
-
-	switch (which)
-	{
-		case BONUS:
-			commander->accumulateBonus (std::make_shared<Bonus>(accumulatedBonus));
-			break;
-		case SPECIAL_SKILL:
-			commander->accumulateBonus (std::make_shared<Bonus>(accumulatedBonus));
-			commander->specialSkills.insert (additionalInfo);
-			break;
-		case SECONDARY_SKILL:
-			commander->secondarySkills[additionalInfo] = static_cast<ui8>(amount);
-			break;
-		case ALIVE:
-			if (amount)
-				commander->setAlive(true);
-			else
-				commander->setAlive(false);
-			break;
-		case EXPERIENCE:
-			commander->giveTotalStackExperience(amount);
-			commander->nodeHasChanged();
-			break;
-	}
-}
-
-void AddQuest::applyGs(CGameState *gs)
-{
-	assert (vstd::contains(gs->players, player));
-	auto * vec = &gs->players.at(player).quests;
-	if (!vstd::contains(*vec, quest))
-		vec->push_back (quest);
-	else
-		logNetwork->warn("Warning! Attempt to add duplicated quest");
-}
-
-void UpdateArtHandlerLists::applyGs(CGameState *gs)
-{
-	gs->allocatedArtifacts = allocatedArtifacts;
-}
-
-void ChangeFormation::applyGs(CGameState *gs)
-{
-	gs->getHero(hid)->setFormation(formation);
-}
-
-void HeroVisitCastle::applyGs(CGameState *gs)
-{
-	CGHeroInstance *h = gs->getHero(hid);
-	CGTownInstance *t = gs->getTown(tid);
-
-	assert(h);
-	assert(t);
-
-	if(start())
-		t->setVisitingHero(h);
-	else
-		t->setVisitingHero(nullptr);
-}
-
-void ChangeSpells::applyGs(CGameState *gs)
-{
-	CGHeroInstance *hero = gs->getHero(hid);
-
-	if(learn)
-		for(const auto & sid : spells)
-			hero->addSpellToSpellbook(sid);
-	else
-		for(const auto & sid : spells)
-			hero->removeSpellFromSpellbook(sid);
-}
-
-void SetResearchedSpells::applyGs(CGameState *gs)
-{
-	CGTownInstance *town = gs->getTown(tid);
-
-	town->spells[level] = spells;
-	town->spellResearchCounterDay++;
-	if(accepted)
-		town->spellResearchAcceptedCounter++;
-}
-
-void SetMana::applyGs(CGameState *gs)
-{
-	CGHeroInstance * hero = gs->getHero(hid);
-
-	assert(hero);
-
-	if(absolute)
-		hero->mana = val;
-	else
-		hero->mana += val;
-
-	vstd::amax(hero->mana, 0); //not less than 0
-}
-
-void SetMovePoints::applyGs(CGameState *gs)
-{
-	CGHeroInstance *hero = gs->getHero(hid);
-
-	assert(hero);
-
-	if(absolute)
-		hero->setMovementPoints(val);
-	else
-		hero->setMovementPoints(hero->movementPointsRemaining() + val);
-}
-
-void FoWChange::applyGs(CGameState *gs)
-{
-	TeamState * team = gs->getPlayerTeam(player);
-	auto & fogOfWarMap = team->fogOfWarMap;
-	for(const int3 & t : tiles)
-		fogOfWarMap[t.z][t.x][t.y] = mode != ETileVisibility::HIDDEN;
-
-	if (mode == ETileVisibility::HIDDEN) //do not hide too much
-	{
-		std::unordered_set<int3> tilesRevealed;
-		for (auto & o : gs->getMap().getObjects())
-		{
-			if (o->asOwnable())
-			{
-				if(vstd::contains(team->players, o->getOwner())) //check owned observators
-					gs->getTilesInRange(tilesRevealed, o->getSightCenter(), o->getSightRadius(), ETileVisibility::HIDDEN, o->tempOwner);
-			}
-		}
-		for(const int3 & t : tilesRevealed) //probably not the most optimal solution ever
-			fogOfWarMap[t.z][t.x][t.y] = 1;
-	}
-}
-
-void SetAvailableHero::applyGs(CGameState *gs)
-{
-	gs->heroesPool->setHeroForPlayer(player, slotID, hid, army, roleID, replenishPoints);
-}
-
-void GiveBonus::applyGs(CGameState *gs)
-{
-	CBonusSystemNode *cbsn = nullptr;
-	switch(who)
-	{
-	case ETarget::OBJECT:
-		cbsn = dynamic_cast<CBonusSystemNode*>(gs->getObjInstance(id.as<ObjectInstanceID>()));
-		break;
-	case ETarget::HERO_COMMANDER:
-		cbsn = gs->getHero(id.as<ObjectInstanceID>())->getCommander();
-		break;
-	case ETarget::PLAYER:
-		cbsn = gs->getPlayerState(id.as<PlayerColor>());
-		break;
-	case ETarget::BATTLE:
-		assert(Bonus::OneBattle(&bonus));
-		cbsn = dynamic_cast<CBonusSystemNode*>(gs->getBattle(id.as<BattleID>()));
-		break;
-	}
-
-	assert(cbsn);
-
-	if(Bonus::OneWeek(&bonus))
-		bonus.turnsRemain = 8 - gs->getDate(Date::DAY_OF_WEEK); // set correct number of days before adding bonus
-
-	auto b = std::make_shared<Bonus>(bonus);
-	cbsn->addNewBonus(b);
-}
-
-void ChangeObjPos::applyGs(CGameState *gs)
-{
-	CGObjectInstance *obj = gs->getObjInstance(objid);
-	if(!obj)
-	{
-		logNetwork->error("Wrong ChangeObjPos: object %d doesn't exist!", objid.getNum());
-		return;
-	}
-	gs->getMap().moveObject(objid, nPos + obj->getVisitableOffset());
-}
-
-void ChangeObjectVisitors::applyGs(CGameState *gs)
-{
-	auto objectPtr = gs->getObjInstance(object);
-
-	switch (mode) {
-		case VISITOR_ADD_HERO:
-			gs->getHero(hero)->visitedObjects.insert(object);
-			[[fallthrough]];
-		case VISITOR_ADD_PLAYER:
-			gs->getPlayerTeam(gs->getHero(hero)->tempOwner)->scoutedObjects.insert(object);
-			gs->getPlayerState(gs->getHero(hero)->tempOwner)->visitedObjects.insert(object);
-			gs->getPlayerState(gs->getHero(hero)->tempOwner)->visitedObjectsGlobal.insert({objectPtr->ID, objectPtr->subID});
-
-			break;
-		case VISITOR_CLEAR:
-			// remove visit info from all heroes, including those that are not present on map
-			for (auto heroID : gs->getMap().getHeroesOnMap())
-				gs->getHero(heroID)->visitedObjects.erase(object);
-
-			for (auto heroID : gs->getMap().getHeroesInPool())
-				gs->getMap().tryGetFromHeroPool(heroID)->visitedObjects.erase(object);
-
-			for(auto &elem : gs->players)
-				elem.second.visitedObjects.erase(object);
-
-			for(auto &elem : gs->teams)
-				elem.second.scoutedObjects.erase(object);
-
-			break;
-		case VISITOR_SCOUTED:
-			gs->getPlayerTeam(gs->getHero(hero)->tempOwner)->scoutedObjects.insert(object);
-
-			break;
-	}
-}
-
-void ChangeArtifactsCostume::applyGs(CGameState *gs)
-{
-	auto & allCostumes = gs->getPlayerState(player)->costumesArtifacts;
-	if(const auto & costume = allCostumes.find(costumeIdx); costume != allCostumes.end())
-		costume->second = costumeSet;
-	else
-		allCostumes.try_emplace(costumeIdx, costumeSet);
-}
-
-void PlayerEndsGame::applyGs(CGameState *gs)
-{
-	PlayerState *p = gs->getPlayerState(player);
-	if(victoryLossCheckResult.victory())
-	{
-		p->status = EPlayerStatus::WINNER;
-
-		// TODO: Campaign-specific code might as well go somewhere else
-		// keep all heroes from the winning player
-		if(p->human && gs->getStartInfo()->campState)
-		{
-			std::vector<CGHeroInstance *> crossoverHeroes;
-			for (auto hero : p->getHeroes())
-				if (hero->tempOwner == player)
-					crossoverHeroes.push_back(hero);
-
-			gs->getStartInfo()->campState->setCurrentMapAsConquered(crossoverHeroes);
-		}
-	}
-	else
-	{
-		p->status = EPlayerStatus::LOSER;
-	}
-
-	// defeated player may be making turn right now
-	gs->actingPlayers.erase(player);
-}
-
-void PlayerReinitInterface::applyGs(CGameState *gs)
-{
-	if(!gs || !gs->getStartInfo())
-		return;
-	
-	//TODO: what does mean if more that one player connected?
-	if(playerConnectionId == PlayerSettings::PLAYER_AI)
-	{
-		for(const auto & player : players)
-			gs->getStartInfo()->getIthPlayersSettings(player).connectedPlayerIDs.clear();
-	}
-}
-
-void RemoveBonus::applyGs(CGameState *gs)
-{
-	CBonusSystemNode *node = nullptr;
-	switch(who)
-	{
-	case GiveBonus::ETarget::OBJECT:
-		node = dynamic_cast<CBonusSystemNode*>(gs->getObjInstance(whoID.as<ObjectInstanceID>()));
-		break;
-	case GiveBonus::ETarget::PLAYER:
-		node = gs->getPlayerState(whoID.as<PlayerColor>());
-		break;
-	case GiveBonus::ETarget::BATTLE:
-		assert(Bonus::OneBattle(&bonus));
-		node = dynamic_cast<CBonusSystemNode*>(gs->getBattle(whoID.as<BattleID>()));
-		break;
-	}
-
-	BonusList &bonuses = node->getExportedBonusList();
-
-	for(const auto & b : bonuses)
-	{
-		if(b->source == source && b->sid == id)
-		{
-			bonus = *b; //backup bonus (to show to interfaces later)
-			node->removeBonus(b);
-			break;
-		}
-	}
-}
-
-void RemoveObject::applyGs(CGameState *gs)
-{
-	CGObjectInstance *obj = gs->getObjInstance(objectID);
-	logGlobal->debug("removing object id=%d; address=%x; name=%s", objectID, (intptr_t)obj, obj->getObjectName());
-
-	if (initiator.isValidPlayer())
-		gs->getPlayerState(initiator)->destroyedObjects.insert(objectID);
-
-	if(obj->getOwner().isValidPlayer())
-	{
-		gs->getPlayerState(obj->getOwner())->removeOwnedObject(obj); //object removed via map event or hero got beaten
-
-		FlaggableMapObject* flaggableObject = dynamic_cast<FlaggableMapObject*>(obj);
-		if(flaggableObject)
-		{
-			flaggableObject->markAsDeleted();
-		}
-	}
-
-	if(obj->ID == Obj::HERO) //remove beaten hero
-	{
-		auto beatenHero = dynamic_cast<CGHeroInstance*>(obj);
-		assert(beatenHero);
-
-		auto * siegeNode = beatenHero->whereShouldBeAttachedOnSiege(gs);
-		vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi)
-		{
-			return asi.getArt()->getTypeId() == ArtifactID::GRAIL;
-		});
-
-		if(beatenHero->getVisitedTown())
-		{
-			if(beatenHero->getVisitedTown()->getGarrisonHero() == beatenHero)
-				beatenHero->getVisitedTown()->setGarrisonedHero(nullptr);
-			else
-				beatenHero->getVisitedTown()->setVisitingHero(nullptr);
-
-			beatenHero->setVisitedTown(nullptr, false);
-		}
-		beatenHero->detachFromBonusSystem(*gs);
-		beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero
-
-		// FIXME: workaround:
-		// hero should be attached to siegeNode after battle
-		// however this code might also be called on dismissing hero while in town
-		if (siegeNode && vstd::contains(beatenHero->getParentNodes(), siegeNode))
-			beatenHero->detachFrom(*siegeNode);
-
-		//If hero on Boat is removed, the Boat disappears
-		if(beatenHero->inBoat())
-		{
-			auto boat = beatenHero->getBoat();
-			beatenHero->setBoat(nullptr);
-			gs->getMap().eraseObject(boat->id);
-		}
-
-		auto beatenObject = gs->getMap().eraseObject(obj->id);
-
-		//return hero to the pool, so he may reappear in tavern
-		gs->heroesPool->addHeroToPool(beatenHero->getHeroTypeID());
-		gs->getMap().addToHeroPool(std::dynamic_pointer_cast<CGHeroInstance>(beatenObject));
-
-		return;
-	}
-
-	const auto * quest = dynamic_cast<const IQuestObject *>(obj);
-	if (quest)
-	{
-		for (auto &player : gs->players)
-		{
-			vstd::erase_if(player.second.quests, [obj](const QuestInfo & q){
-				return q.obj == obj->id;
-			});
-		}
-	}
-
-	gs->getMap().eraseObject(objectID);
-	gs->getMap().calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
-}
-
-static int getDir(const int3 & src, const int3 & dst)
-{
-	int ret = -1;
-	if(dst.x+1 == src.x && dst.y+1 == src.y) //tl
-	{
-		ret = 1;
-	}
-	else if(dst.x == src.x && dst.y+1 == src.y) //t
-	{
-		ret = 2;
-	}
-	else if(dst.x-1 == src.x && dst.y+1 == src.y) //tr
-	{
-		ret = 3;
-	}
-	else if(dst.x-1 == src.x && dst.y == src.y) //r
-	{
-		ret = 4;
-	}
-	else if(dst.x-1 == src.x && dst.y-1 == src.y) //br
-	{
-		ret = 5;
-	}
-	else if(dst.x == src.x && dst.y-1 == src.y) //b
-	{
-		ret = 6;
-	}
-	else if(dst.x+1 == src.x && dst.y-1 == src.y) //bl
-	{
-		ret = 7;
-	}
-	else if(dst.x+1 == src.x && dst.y == src.y) //l
-	{
-		ret = 8;
-	}
-	return ret;
-}
-
-void TryMoveHero::applyGs(CGameState *gs)
-{
-	CGHeroInstance *h = gs->getHero(id);
-	if (!h)
-	{
-		logGlobal->error("Attempt ot move unavailable hero %d", id.getNum());
-		return;
-	}
-
-	const TerrainTile & fromTile = gs->getMap().getTile(h->convertToVisitablePos(start));
-	const TerrainTile & destTile = gs->getMap().getTile(h->convertToVisitablePos(end));
-
-	h->setMovementPoints(movePoints);
-
-	if((result == SUCCESS || result == BLOCKING_VISIT || result == EMBARK || result == DISEMBARK) && start != end)
-	{
-		auto dir = getDir(start,end);
-		if(dir > 0  &&  dir <= 8)
-			h->moveDir = dir;
-		//else don`t change move direction - hero might have traversed the subterranean gate, direction should be kept
-	}
-
-	if(result == EMBARK) //hero enters boat at destination tile
-	{
-		const TerrainTile &tt = gs->getMap().getTile(h->convertToVisitablePos(end));
-		ObjectInstanceID topObjectID = tt.visitableObjects.back();
-		CGObjectInstance * topObject = gs->getObjInstance(topObjectID);
-		assert(tt.visitableObjects.size() >= 1 && topObject->ID == Obj::BOAT); //the only visitable object at destination is Boat
-		auto * boat = dynamic_cast<CGBoat *>(topObject);
-		assert(boat);
-
-		gs->getMap().hideObject(boat); //hero blockvis mask will be used, we don't need to duplicate it with boat
-		h->setBoat(boat);
-	}
-	else if(result == DISEMBARK) //hero leaves boat to destination tile
-	{
-		auto * b = h->getBoat();
-		b->direction = h->moveDir;
-		b->pos = start;
-		gs->getMap().showObject(b);
-		h->setBoat(nullptr);
-	}
-
-	if(start!=end && (result == SUCCESS || result == TELEPORTATION || result == EMBARK || result == DISEMBARK))
-	{
-		gs->getMap().hideObject(h);
-		h->setAnchorPos(end);
-		if(auto * b = h->getBoat())
-			b->setAnchorPos(end);
-		gs->getMap().showObject(h);
-	}
-
-	auto & fogOfWarMap = gs->getPlayerTeam(h->getOwner())->fogOfWarMap;
-	for(const int3 & t : fowRevealed)
-		fogOfWarMap[t.z][t.x][t.y] = 1;
-
-	if (fromTile.getTerrainID() != destTile.getTerrainID())
-		h->nodeHasChanged(); // update bonuses with terrain limiter
-}
-
-void NewStructures::applyGs(CGameState *gs)
-{
-	CGTownInstance *t = gs->getTown(tid);
-
-	for(const auto & id : bid)
-	{
-		assert(t->getTown()->buildings.at(id) != nullptr);
-		t->addBuilding(id);
-	}
-	t->updateAppearance();
-	t->built = built;
-	t->recreateBuildingsBonuses();
-}
-
-void RazeStructures::applyGs(CGameState *gs)
-{
-	CGTownInstance *t = gs->getTown(tid);
-	for(const auto & id : bid)
-	{
-		t->removeBuilding(id);
-
-		t->updateAppearance();
-	}
-	t->destroyed = destroyed; //yeaha
-	t->recreateBuildingsBonuses();
-}
-
-void SetAvailableCreatures::applyGs(CGameState *gs)
-{
-	auto * dw = dynamic_cast<CGDwelling *>(gs->getObjInstance(tid));
-	assert(dw);
-	dw->creatures = creatures;
-}
-
-void SetHeroesInTown::applyGs(CGameState *gs)
-{
-	CGTownInstance *t = gs->getTown(tid);
-
-	CGHeroInstance * v = gs->getHero(visiting);
-	CGHeroInstance * g = gs->getHero(garrison);
-
-	bool newVisitorComesFromGarrison = v && v == t->getGarrisonHero();
-	bool newGarrisonComesFromVisiting = g && g == t->getVisitingHero();
-
-	if(newVisitorComesFromGarrison)
-		t->setGarrisonedHero(nullptr);
-	if(newGarrisonComesFromVisiting)
-		t->setVisitingHero(nullptr);
-	if(!newGarrisonComesFromVisiting || v)
-		t->setVisitingHero(v);
-	if(!newVisitorComesFromGarrison || g)
-		t->setGarrisonedHero(g);
-
-	if(v)
-		gs->getMap().showObject(v);
-
-	if(g)
-		gs->getMap().hideObject(g);
-}
-
-void HeroRecruited::applyGs(CGameState *gs)
-{
-	auto h = gs->heroesPool->takeHeroFromPool(hid);
-	CGTownInstance *t = gs->getTown(tid);
-	PlayerState *p = gs->getPlayerState(player);
-
-	if (boatId != ObjectInstanceID::NONE)
-	{
-		CGObjectInstance *obj = gs->getObjInstance(boatId);
-		auto * boat = dynamic_cast<CGBoat *>(obj);
-		if (boat)
-		{
-			gs->getMap().hideObject(boat);
-			h->setBoat(boat);
-		}
-	}
-
-	h->setOwner(player);
-	h->pos = tile;
-	h->updateAppearance();
-
-	assert(h->id.hasValue());
-	gs->getMap().addNewObject(h);
-
-	p->addOwnedObject(h.get());
-	h->attachToBonusSystem(*gs);
-
-	if(t)
-		t->setVisitingHero(h.get());
-}
-
-void GiveHero::applyGs(CGameState *gs)
-{
-	CGHeroInstance *h = gs->getHero(id);
-
-	if (boatId != ObjectInstanceID::NONE)
-	{
-		CGObjectInstance *obj = gs->getObjInstance(boatId);
-		auto * boat = dynamic_cast<CGBoat *>(obj);
-		if (boat)
-		{
-			gs->getMap().hideObject(boat);
-			h->setBoat(boat);
-		}
-	}
-
-	//bonus system
-	h->detachFrom(gs->globalEffects);
-	h->attachTo(*gs->getPlayerState(player));
-
-	auto oldVisitablePos = h->visitablePos();
-	gs->getMap().hideObject(h);
-	h->updateAppearance();
-
-	h->setOwner(player);
-	h->setMovementPoints(h->movementPointsLimit(true));
-	h->setAnchorPos(h->convertFromVisitablePos(oldVisitablePos));
-	gs->getMap().heroAddedToMap(h);
-	gs->getPlayerState(h->getOwner())->addOwnedObject(h);
-
-	gs->getMap().showObject(h);
-	h->setVisitedTown(nullptr, false);
-}
-
-void NewObject::applyGs(CGameState *gs)
-{
-	gs->getMap().addNewObject(newObject);
-	gs->getMap().calculateGuardingGreaturePositions();
-
-	// attach newly spawned wandering monster to global bonus system node
-	auto newArmy = std::dynamic_pointer_cast<CArmedInstance>(newObject);
-	if (newArmy)
-		newArmy->attachToBonusSystem(*gs);
-
-	logGlobal->debug("Added object id=%d; name=%s", newObject->id, newObject->getObjectName());
-}
-
-void NewArtifact::applyGs(CGameState *gs)
-{
-	auto art = gs->createArtifact(artId, spellId);
-	PutArtifact pa(art->getId(), ArtifactLocation(artHolder, pos), false);
-	pa.applyGs(gs);
-}
-
-void ChangeStackCount::applyGs(CGameState *gs)
-{
-	auto * srcObj = gs->getArmyInstance(army);
-	if(!srcObj)
-		throw std::runtime_error("ChangeStackCount: invalid army object " + std::to_string(army.getNum()) + ", possible game state corruption.");
-
-	if(absoluteValue)
-		srcObj->setStackCount(slot, count);
-	else
-		srcObj->changeStackCount(slot, count);
-}
-
-void SetStackType::applyGs(CGameState *gs)
-{
-	auto * srcObj = gs->getArmyInstance(army);
-	if(!srcObj)
-		throw std::runtime_error("SetStackType: invalid army object " + std::to_string(army.getNum()) + ", possible game state corruption.");
-
-	srcObj->setStackType(slot, type);
-}
-
-void EraseStack::applyGs(CGameState *gs)
-{
-	auto * srcObj = gs->getArmyInstance(army);
-	if(!srcObj)
-		throw std::runtime_error("EraseStack: invalid army object " + std::to_string(army.getNum()) + ", possible game state corruption.");
-
-	srcObj->eraseStack(slot);
-}
-
-void SwapStacks::applyGs(CGameState *gs)
-{
-	auto * srcObj = gs->getArmyInstance(srcArmy);
-	if(!srcObj)
-		throw std::runtime_error("SwapStacks: invalid army object " + std::to_string(srcArmy.getNum()) + ", possible game state corruption.");
-
-	auto * dstObj = gs->getArmyInstance(dstArmy);
-	if(!dstObj)
-		throw std::runtime_error("SwapStacks: invalid army object " + std::to_string(dstArmy.getNum()) + ", possible game state corruption.");
-
-	auto s1 = srcObj->detachStack(srcSlot);
-	auto s2 = dstObj->detachStack(dstSlot);
-
-	srcObj->putStack(srcSlot, std::move(s2));
-	dstObj->putStack(dstSlot, std::move(s1));
-}
-
-void InsertNewStack::applyGs(CGameState *gs)
-{
-	if(auto * obj = gs->getArmyInstance(army))
-		obj->putStack(slot, std::make_unique<CStackInstance>(gs->cb, type, count));
-	else
-		throw std::runtime_error("InsertNewStack: invalid army object " + std::to_string(army.getNum()) + ", possible game state corruption.");
-}
-
-void RebalanceStacks::applyGs(CGameState *gs)
-{
-	auto * srcObj = gs->getArmyInstance(srcArmy);
-	if(!srcObj)
-		throw std::runtime_error("RebalanceStacks: invalid army object " + std::to_string(srcArmy.getNum()) + ", possible game state corruption.");
-
-	auto * dstObj = gs->getArmyInstance(dstArmy);
-	if(!dstObj)
-		throw std::runtime_error("RebalanceStacks: invalid army object " + std::to_string(dstArmy.getNum()) + ", possible game state corruption.");
-
-	StackLocation src(srcObj->id, srcSlot);
-	StackLocation dst(dstObj->id, dstSlot);
-
-	[[maybe_unused]] const CCreature * srcType = srcObj->getCreature(src.slot);
-	const CCreature * dstType = dstObj->getCreature(dst.slot);
-	TQuantity srcCount = srcObj->getStackCount(src.slot);
-
-	if(srcCount == count) //moving whole stack
-	{
-		if(dstType) //stack at dest -> merge
-		{
-			assert(dstType == srcType);
-			
-			const auto srcHero = dynamic_cast<CGHeroInstance*>(srcObj);
-			const auto dstHero = dynamic_cast<CGHeroInstance*>(dstObj);
-			auto srcStack = const_cast<CStackInstance*>(srcObj->getStackPtr(src.slot));
-			auto dstStack = const_cast<CStackInstance*>(dstObj->getStackPtr(dst.slot));
-			if(srcStack->getArt(ArtifactPosition::CREATURE_SLOT))
-			{
-				if(auto dstArt = dstStack->getArt(ArtifactPosition::CREATURE_SLOT))
-				{
-					bool artifactIsLost = true;
-
-					if(srcHero)
-					{
-						auto dstSlot = ArtifactUtils::getArtBackpackPosition(srcHero, dstArt->getTypeId());
-						if (dstSlot != ArtifactPosition::PRE_FIRST)
-						{
-							gs->getMap().moveArtifactInstance(*dstStack, ArtifactPosition::CREATURE_SLOT, *srcHero, dstSlot);
-							artifactIsLost = false;
-						}
-					}
-
-					if (artifactIsLost)
-					{
-						BulkEraseArtifacts ea;
-						ea.artHolder = dstHero->id;
-						ea.posPack.emplace_back(ArtifactPosition::CREATURE_SLOT);
-						ea.creature = dst.slot;
-						ea.applyGs(gs);
-						logNetwork->warn("Cannot move artifact! No free slots");
-					}
-					gs->getMap().moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
-					//TODO: choose from dialog
-				}
-				else //just move to the other slot before stack gets erased
-				{
-					gs->getMap().moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
-				}
-			}
-
-			auto movedStack = srcObj->detachStack(src.slot);
-			dstObj->joinStack(dst.slot, std::move(movedStack));
-		}
-		else
-		{
-			auto movedStack = srcObj->detachStack(src.slot);
-			dstObj->putStack(dst.slot, std::move(movedStack));
-		}
-	}
-	else
-	{
-		auto movedStack = srcObj->splitStack(src.slot, count);
-
-		if(dstType) //stack at dest -> rebalance
-		{
-			assert(dstType == srcType);
-			dstObj->joinStack(dst.slot, std::move(movedStack));
-		}
-		else //move new stack to an empty slot
-		{
-			dstObj->putStack(dst.slot, std::move(movedStack));
-		}
-	}
-
-	srcObj->nodeHasChanged();
-	if (srcObj != dstObj)
-		dstObj->nodeHasChanged();
-}
-
-void BulkRebalanceStacks::applyGs(CGameState *gs)
-{
-	for(auto & move : moves)
-		move.applyGs(gs);
-}
-
-void PutArtifact::applyGs(CGameState *gs)
-{
-	auto art = gs->getArtInstance(id);
-	assert(!art->getParentNodes().empty());
-	auto hero = gs->getHero(al.artHolder);
-	assert(hero);
-	assert(art && art->canBePutAt(hero, al.slot));
-	assert(ArtifactUtils::checkIfSlotValid(*hero, al.slot));
-	gs->getMap().putArtifactInstance(*hero, art->getId(), al.slot);
-}
-
-void BulkEraseArtifacts::applyGs(CGameState *gs)
-{
-	const auto artSet = gs->getArtSet(artHolder);
-	assert(artSet);
-
-	std::sort(posPack.begin(), posPack.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool
-		{
-			return slot0.num > slot1.num;
-		});
-
-	for(const auto & slot : posPack)
-	{
-		const auto slotInfo = artSet->getSlot(slot);
-		const ArtifactInstanceID artifactID = slotInfo->artifactID;
-		const CArtifactInstance * artifact = gs->getArtInstance(artifactID);
-		if(slotInfo->locked)
-		{
-			logGlobal->debug("Erasing locked artifact: %s", artifact->getType()->getNameTranslated());
-			DisassembledArtifact dis;
-			dis.al.artHolder = artHolder;
-
-			for(auto & slotInfoWorn : artSet->artifactsWorn)
-			{
-				auto art = slotInfoWorn.second.getArt();
-				if(art->isCombined() && art->isPart(artifact))
-				{
-					dis.al.slot = artSet->getArtPos(art);
-					break;
-				}
-			}
-			assert((dis.al.slot != ArtifactPosition::PRE_FIRST) && "Failed to determine the assembly this locked artifact belongs to");
-			logGlobal->debug("Found the corresponding assembly: %s", artSet->getArt(dis.al.slot)->getType()->getNameTranslated());
-			dis.applyGs(gs);
-		}
-		else
-		{
-			logGlobal->debug("Erasing artifact %s", artifact->getType()->getNameTranslated());
-		}
-		gs->getMap().removeArtifactInstance(*artSet, slot);
-	}
-}
-
-void BulkMoveArtifacts::applyGs(CGameState *gs)
-{
-	const auto bulkArtsRemove = [gs](std::vector<MoveArtifactInfo> & artsPack, CArtifactSet & artSet)
-	{
-		std::vector<ArtifactPosition> packToRemove;
-		for(const auto & slotsPair : artsPack)
-			packToRemove.push_back(slotsPair.srcPos);
-		std::sort(packToRemove.begin(), packToRemove.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool
-			{
-				return slot0.num > slot1.num;
-			});
-
-		for(const auto & slot : packToRemove)
-			gs->getMap().removeArtifactInstance(artSet, slot);
-	};
-
-	const auto bulkArtsPut = [gs](std::vector<MoveArtifactInfo> & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet)
-	{
-		for(const auto & slotsPair : artsPack)
-		{
-			auto * art = initArtSet.getArt(slotsPair.srcPos);
-			assert(art);
-			gs->getMap().putArtifactInstance(dstArtSet, art->getId(), slotsPair.dstPos);
-		}
-	};
-	
-	auto * leftSet = gs->getArtSet(ArtifactLocation(srcArtHolder, srcCreature));
-	assert(leftSet);
-	auto * rightSet = gs->getArtSet(ArtifactLocation(dstArtHolder, dstCreature));
-	assert(rightSet);
-	CArtifactFittingSet artInitialSetLeft(*leftSet);
-	bulkArtsRemove(artsPack0, *leftSet);
-	if(!artsPack1.empty())
-	{
-		CArtifactFittingSet artInitialSetRight(*rightSet);
-		bulkArtsRemove(artsPack1, *rightSet);
-		bulkArtsPut(artsPack1, artInitialSetRight, *leftSet);
-	}
-	bulkArtsPut(artsPack0, artInitialSetLeft, *rightSet);
-}
-
-void AssembledArtifact::applyGs(CGameState *gs)
-{
-	auto artSet = gs->getArtSet(al.artHolder);
-	assert(artSet);
-	const auto transformedArt = artSet->getArt(al.slot);
-	assert(transformedArt);
-	const auto builtArt = artId.toArtifact();
-	assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(artSet, transformedArt->getTypeId()), [=](const CArtifact * art)->bool
-		{
-			return art->getId() == builtArt->getId();
-		}));
-
-	auto * combinedArt = gs->getMap().createArtifactComponent(artId);
-
-	// Find slots for all involved artifacts
-	std::set<ArtifactPosition, std::greater<>> slotsInvolved = { al.slot };
-	CArtifactFittingSet fittingSet(*artSet);
-	auto parts = builtArt->getConstituents();
-	parts.erase(std::find(parts.begin(), parts.end(), transformedArt->getType()));
-	for(const auto constituent : parts)
-	{
-		const auto slot = fittingSet.getArtPos(constituent->getId(), false, false);
-		fittingSet.lockSlot(slot);
-		assert(slot != ArtifactPosition::PRE_FIRST);
-		slotsInvolved.insert(slot);
-	}
-
-	// Find a slot for combined artifact
-	if(ArtifactUtils::isSlotEquipment(al.slot) && ArtifactUtils::isSlotBackpack(*slotsInvolved.begin()))
-	{
-		al.slot = ArtifactPosition::BACKPACK_START;
-	}
-	else if(ArtifactUtils::isSlotBackpack(al.slot))
-	{
-		for(const auto & slot : slotsInvolved)
-			if(ArtifactUtils::isSlotBackpack(slot))
-				al.slot = slot;
-	}
-	else
-	{
-		for(const auto & slot : slotsInvolved)
-			if(!vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), al.slot)
-				&& vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), slot))
-			{
-				al.slot = slot;
-				break;
-			}
-	}
-
-	// Delete parts from hero
-	for(const auto & slot : slotsInvolved)
-	{
-		const auto constituentInstance = artSet->getArt(slot);
-		gs->getMap().removeArtifactInstance(*artSet, slot);
-
-		if(!combinedArt->getType()->isFused())
-		{
-			if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot)
-				combinedArt->addPart(constituentInstance, slot);
-			else
-				combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST);
-		}
-	}
-
-	// Put new combined artifacts
-	gs->getMap().putArtifactInstance(*artSet, combinedArt->getId(), al.slot);
-}
-
-void DisassembledArtifact::applyGs(CGameState *gs)
-{
-	auto hero = gs->getHero(al.artHolder);
-	assert(hero);
-	auto disassembledArtID = hero->getArtID(al.slot);
-	auto disassembledArt = gs->getArtInstance(disassembledArtID);
-	assert(disassembledArt);
-
-	const auto parts = disassembledArt->getPartsInfo();
-	gs->getMap().removeArtifactInstance(*hero, al.slot);
-	for(auto & part : parts)
-	{
-		// ArtifactPosition::PRE_FIRST is value of main part slot -> it'll replace combined artifact in its pos
-		auto slot = (ArtifactUtils::isSlotEquipment(part.slot) ? part.slot : al.slot);
-		disassembledArt->detachFromSource(*part.getArtifact());
-		gs->getMap().putArtifactInstance(*hero, part.getArtifact()->getId(), slot);
-	}
-	gs->getMap().eraseArtifactInstance(disassembledArt->getId());
-}
-
-void HeroVisit::applyGs(CGameState *gs)
-{
-}
-
-void SetAvailableArtifacts::applyGs(CGameState *gs)
-{
-	if(id != ObjectInstanceID::NONE)
-	{
-		if(auto * bm = dynamic_cast<CGBlackMarket *>(gs->getObjInstance(id)))
-		{
-			bm->artifacts = arts;
-		}
-		else
-		{
-			logNetwork->error("Wrong black market id!");
-		}
-	}
-	else
-	{
-		gs->getMap().townMerchantArtifacts = arts;
-	}
-}
-
-void NewTurn::applyGs(CGameState *gs)
-{
-	gs->day = day;
-
-	// Update bonuses before doing anything else so hero don't get more MP than needed
-	gs->globalEffects.removeBonusesRecursive(Bonus::OneDay); //works for children -> all game objs
-	gs->globalEffects.reduceBonusDurations(Bonus::NDays);
-	gs->globalEffects.reduceBonusDurations(Bonus::OneWeek);
-	//TODO not really a single root hierarchy, what about bonuses placed elsewhere? [not an issue with H3 mechanics but in the future...]
-
-	for(auto & manaPack : heroesMana)
-		manaPack.applyGs(gs);
-
-	for(auto & movePack : heroesMovement)
-		movePack.applyGs(gs);
-
-	gs->heroesPool->onNewDay();
-
-	for(auto & entry : playerIncome)
-	{
-		gs->getPlayerState(entry.first)->resources += entry.second;
-		gs->getPlayerState(entry.first)->resources.amin(GameConstants::PLAYER_RESOURCES_CAP);
-	}
-
-	for(auto & creatureSet : availableCreatures) //set available creatures in towns
-		creatureSet.applyGs(gs);
-
-	for (const auto & townID : gs->getMap().getAllTowns())
-	{
-		auto t = gs->getTown(townID);
-		t->built = 0;
-		t->spellResearchCounterDay = 0;
-	}
-
-	if(newRumor)
-		gs->currentRumor = *newRumor;
-}
-
-void SetObjectProperty::applyGs(CGameState *gs)
-{
-	CGObjectInstance *obj = gs->getObjInstance(id);
-	if(!obj)
-	{
-		logNetwork->error("Wrong object ID - property cannot be set!");
-		return;
-	}
-
-	auto * cai = dynamic_cast<CArmedInstance *>(obj);
-
-	if(what == ObjProperty::OWNER && obj->asOwnable())
-	{
-		PlayerColor oldOwner = obj->getOwner();
-		PlayerColor newOwner = identifier.as<PlayerColor>();
-		if(oldOwner.isValidPlayer())
-			gs->getPlayerState(oldOwner)->removeOwnedObject(obj);
-
-		if(newOwner.isValidPlayer())
-			gs->getPlayerState(newOwner)->addOwnedObject(obj);
-	}
-
-	if(what == ObjProperty::OWNER && cai)
-	{
-		if(obj->ID == Obj::TOWN)
-		{
-			auto * t = dynamic_cast<CGTownInstance *>(obj);
-			assert(t);
-
-			PlayerColor oldOwner = t->tempOwner;
-			if(oldOwner.isValidPlayer())
-			{
-				auto * state = gs->getPlayerState(oldOwner);
-				if(state->getTowns().empty())
-					state->daysWithoutCastle = 0;
-			}
-			if(identifier.as<PlayerColor>().isValidPlayer())
-			{
-				//reset counter before NewTurn to avoid no town message if game loaded at turn when one already captured
-				PlayerState * p = gs->getPlayerState(identifier.as<PlayerColor>());
-				if(p->daysWithoutCastle)
-					p->daysWithoutCastle = std::nullopt;
-			}
-		}
-
-		cai->detachFromBonusSystem(*gs);
-		obj->setProperty(what, identifier);
-		cai->attachToBonusSystem(*gs);
-	}
-	else //not an armed instance
-	{
-		obj->setProperty(what, identifier);
-	}
-}
-
-void HeroLevelUp::applyGs(CGameState *gs)
-{
-	auto * hero = gs->getHero(heroId);
-	assert(hero);
-	hero->levelUp(skills);
-}
-
-void CommanderLevelUp::applyGs(CGameState *gs)
-{
-	auto * hero = gs->getHero(heroId);
-	assert(hero);
-	const auto & commander = hero->getCommander();
-	assert(commander);
-	commander->levelUp();
-}
-
-void BattleStart::applyGs(CGameState *gs)
-{
-	assert(battleID == gs->nextBattleID);
-
-	info->battleID = gs->nextBattleID;
-	info->localInit();
-
-	gs->currentBattles.push_back(std::move(info));
-	gs->nextBattleID = BattleID(gs->nextBattleID.getNum() + 1);
-}
-
-void BattleNextRound::applyGs(CGameState *gs)
-{
-	gs->getBattle(battleID)->nextRound();
-}
-
-void BattleSetActiveStack::applyGs(CGameState *gs)
-{
-	gs->getBattle(battleID)->nextTurn(stack, reason);
-}
-
-void BattleTriggerEffect::applyGs(CGameState *gs)
-{
-	CStack * st = gs->getBattle(battleID)->getStack(stackID);
-	assert(st);
-	switch(static_cast<BonusType>(effect))
-	{
-	case BonusType::HP_REGENERATION:
-	{
-		int64_t toHeal = val;
-		st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
-		break;
-	}
-	case BonusType::MANA_DRAIN:
-	{
-		CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
-		st->drainedMana = true;
-		h->mana -= val;
-		vstd::amax(h->mana, 0);
-		break;
-	}
-	case BonusType::POISON:
-	{
-		auto b = st->getLocalBonus(Selector::source(BonusSource::SPELL_EFFECT, SpellID(SpellID::POISON))
-				.And(Selector::type()(BonusType::STACK_HEALTH)));
-		if (b)
-			b->val = val;
-		break;
-	}
-	case BonusType::ENCHANTER:
-	case BonusType::MORALE:
-		break;
-	case BonusType::FEAR:
-		st->fear = true;
-		break;
-	default:
-		logNetwork->error("Unrecognized trigger effect type %d", effect);
-	}
-}
-
-void BattleUpdateGateState::applyGs(CGameState *gs)
-{
-	if(gs->getBattle(battleID))
-		gs->getBattle(battleID)->si.gateState = state;
-}
-
-void BattleCancelled::applyGs(CGameState *gs)
-{
-	auto currentBattle = boost::range::find_if(gs->currentBattles, [&](const auto & battle)
-	{
-		return battle->battleID == battleID;
-	});
-
-	assert(currentBattle != gs->currentBattles.end());
-	gs->currentBattles.erase(currentBattle);
-}
-
-void BattleResultAccepted::applyGs(CGameState *gs)
-{
-	// Remove any "until next battle" bonuses
-	if(const auto attackerHero = gs->getHero(heroResult[BattleSide::ATTACKER].heroID))
-		attackerHero->removeBonusesRecursive(Bonus::OneBattle);
-	if(const auto defenderHero = gs->getHero(heroResult[BattleSide::DEFENDER].heroID))
-		defenderHero->removeBonusesRecursive(Bonus::OneBattle);
-
-	if(winnerSide != BattleSide::NONE)
-	{
-		// Grow up growing artifacts
-		if(const auto winnerHero = gs->getHero(heroResult[winnerSide].heroID))
-		{
-			if(winnerHero->getCommander() && winnerHero->getCommander()->alive)
-
-			{
-				for(auto & art : winnerHero->getCommander()->artifactsWorn)
-					gs->getArtInstance(art.second.getID())->growingUp();
-			}
-			for(auto & art : winnerHero->artifactsWorn)
-				gs->getArtInstance(art.second.getID())->growingUp();
-		}
-	}
-
-	if(gs->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
-	{
-		if(const auto attackerArmy = gs->getArmyInstance(heroResult[BattleSide::ATTACKER].armyID))
-			attackerArmy->giveAverageStackExperience(heroResult[BattleSide::ATTACKER].exp);
-
-		if(const auto defenderArmy = gs->getArmyInstance(heroResult[BattleSide::DEFENDER].armyID))
-			defenderArmy->giveAverageStackExperience(heroResult[BattleSide::DEFENDER].exp);
-	}
-}
-
-void BattleLogMessage::applyGs(CGameState *gs)
-{
-	//nothing
-}
-
-void BattleLogMessage::applyBattle(IBattleState * battleState)
-{
-	//nothing
-}
-
-void BattleStackMoved::applyGs(CGameState *gs)
-{
-	applyBattle(gs->getBattle(battleID));
-}
-
-void BattleStackMoved::applyBattle(IBattleState * battleState)
-{
-	battleState->moveUnit(stack, tilesToMove.back());
-}
-
-void BattleStackAttacked::applyGs(CGameState *gs)
-{
-	applyBattle(gs->getBattle(battleID));
-}
-
-void BattleStackAttacked::applyBattle(IBattleState * battleState)
-{
-	battleState->setUnitState(newState.id, newState.data, newState.healthDelta);
-}
-
-void BattleAttack::applyGs(CGameState *gs)
-{
-	CStack * attacker = gs->getBattle(battleID)->getStack(stackAttacking);
-	assert(attacker);
-
-	attackerChanges.applyGs(gs);
-
-	for(BattleStackAttacked & stackAttacked : bsa)
-		stackAttacked.applyGs(gs);
-
-	attacker->removeBonusesRecursive(Bonus::UntilAttack);
-
-	if(!this->counter())
-		attacker->removeBonusesRecursive(Bonus::UntilOwnAttack);
-}
-
-void StartAction::applyGs(CGameState *gs)
-{
-	CStack *st = gs->getBattle(battleID)->getStack(ba.stackNumber);
-
-	if(ba.actionType == EActionType::END_TACTIC_PHASE)
-	{
-		gs->getBattle(battleID)->tacticDistance = 0;
-		return;
-	}
-
-	if(gs->getBattle(battleID)->tacticDistance)
-	{
-		// moves in tactics phase do not affect creature status
-		// (tactics stack queue is managed by client)
-		return;
-	}
-
-	if (ba.isUnitAction())
-	{
-		assert(st); // stack must exists for all non-hero actions
-
-		switch(ba.actionType)
-		{
-			case EActionType::DEFEND:
-				st->waiting = false;
-				st->defending = true;
-				st->defendingAnim = true;
-				break;
-			case EActionType::WAIT:
-				st->defendingAnim = false;
-				st->waiting = true;
-				st->waitedThisTurn = true;
-				break;
-			case EActionType::HERO_SPELL: //no change in current stack state
-				break;
-			default: //any active stack action - attack, catapult, heal, spell...
-				st->waiting = false;
-				st->defendingAnim = false;
-				st->movedThisRound = true;
-				st->castSpellThisTurn = ba.actionType == EActionType::MONSTER_SPELL;
-				break;
-		}
-	}
-	else
-	{
-		if(ba.actionType == EActionType::HERO_SPELL)
-			gs->getBattle(battleID)->getSide(ba.side).usedSpellsHistory.push_back(ba.spell);
-	}
-}
-
-void BattleSpellCast::applyGs(CGameState *gs)
-{
-	if(castByHero && side != BattleSide::NONE)
-		gs->getBattle(battleID)->getSide(side).castSpellsCount++;
-}
-
-void SetStackEffect::applyGs(CGameState *gs)
-{
-	applyBattle(gs->getBattle(battleID));
-}
-
-void SetStackEffect::applyBattle(IBattleState * battleState)
-{
-	for(const auto & stackData : toRemove)
-		battleState->removeUnitBonus(stackData.first, stackData.second);
-
-	for(const auto & stackData : toUpdate)
-		battleState->updateUnitBonus(stackData.first, stackData.second);
-
-	for(const auto & stackData : toAdd)
-		battleState->addUnitBonus(stackData.first, stackData.second);
-}
-
-
-void StacksInjured::applyGs(CGameState *gs)
-{
-	applyBattle(gs->getBattle(battleID));
-}
-
-void StacksInjured::applyBattle(IBattleState * battleState)
-{
-	for(BattleStackAttacked stackAttacked : stacks)
-		stackAttacked.applyBattle(battleState);
-}
-
-void BattleUnitsChanged::applyGs(CGameState *gs)
-{
-	applyBattle(gs->getBattle(battleID));
-}
-
-void BattleUnitsChanged::applyBattle(IBattleState * battleState)
-{
-	for(auto & elem : changedStacks)
-	{
-		switch(elem.operation)
-		{
-		case BattleChanges::EOperation::RESET_STATE:
-			battleState->setUnitState(elem.id, elem.data, elem.healthDelta);
-			break;
-		case BattleChanges::EOperation::REMOVE:
-			battleState->removeUnit(elem.id);
-			break;
-		case BattleChanges::EOperation::ADD:
-			battleState->addUnit(elem.id, elem.data);
-			break;
-		case BattleChanges::EOperation::UPDATE:
-			battleState->updateUnit(elem.id, elem.data);
-			break;
-		default:
-			logNetwork->error("Unknown unit operation %d", static_cast<int>(elem.operation));
-			break;
-		}
-	}
-}
-
-void BattleResultsApplied::applyGs(CGameState * gs)
-{
-	learnedSpells.applyGs(gs);
-
-	for(auto & artPack : artifacts)
-		artPack.applyGs(gs);
-
-	const auto currentBattle = std::find_if(gs->currentBattles.begin(), gs->currentBattles.end(),
-		[this](const auto & battle)
-		{
-			return battle->battleID == battleID;
-		});
-
-	assert(currentBattle != gs->currentBattles.end());
-	gs->currentBattles.erase(currentBattle);
-}
-
-void BattleObstaclesChanged::applyGs(CGameState *gs)
-{
-	applyBattle(gs->getBattle(battleID));
-}
-
-void BattleObstaclesChanged::applyBattle(IBattleState * battleState)
-{
-	for(const auto & change : changes)
-	{
-		switch(change.operation)
-		{
-		case BattleChanges::EOperation::REMOVE:
-			battleState->removeObstacle(change.id);
-			break;
-		case BattleChanges::EOperation::ADD:
-			battleState->addObstacle(change);
-			break;
-		case BattleChanges::EOperation::UPDATE:
-			battleState->updateObstacle(change);
-			break;
-		default:
-			logNetwork->error("Unknown obstacle operation %d", static_cast<int>(change.operation));
-			break;
-		}
-	}
-}
-
-CatapultAttack::CatapultAttack() = default;
-
-CatapultAttack::~CatapultAttack() = default;
-
-void CatapultAttack::applyGs(CGameState *gs)
-{
-	applyBattle(gs->getBattle(battleID));
-}
-
 void CatapultAttack::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitCatapultAttack(*this);
 }
 
-void CatapultAttack::applyBattle(IBattleState * battleState)
-{
-	const auto * town = battleState->getDefendedTown();
-	if(!town)
-		return;
-
-	if(town->fortificationsLevel().wallsHealth == 0)
-		return;
-
-	for(const auto & part : attackedParts)
-	{
-		auto newWallState = SiegeInfo::applyDamage(battleState->getWallState(part.attackedPart), part.damageDealt);
-		battleState->setWallState(part.attackedPart, newWallState);
-	}
-}
-
-void BattleSetStackProperty::applyGs(CGameState *gs)
-{
-	CStack * stack = gs->getBattle(battleID)->getStack(stackID, false);
-	switch(which)
-	{
-		case CASTS:
-		{
-			if(absolute)
-				logNetwork->error("Can not change casts in absolute mode");
-			else
-				stack->casts.use(-val);
-			break;
-		}
-		case ENCHANTER_COUNTER:
-		{
-			auto & counter = gs->getBattle(battleID)->getSide(gs->getBattle(battleID)->whatSide(stack->unitOwner())).enchanterCounter;
-			if(absolute)
-				counter = val;
-			else
-				counter += val;
-			vstd::amax(counter, 0);
-			break;
-		}
-		case UNBIND:
-		{
-			stack->removeBonusesRecursive(Selector::type()(BonusType::BIND_EFFECT));
-			break;
-		}
-		case CLONED:
-		{
-			stack->cloned = true;
-			break;
-		}
-		case HAS_CLONE:
-		{
-			stack->cloneID = val;
-			break;
-		}
-	}
-}
-
-void PlayerCheated::applyGs(CGameState *gs)
-{
-	if(!player.isValidPlayer())
-		return;
-
-	gs->getPlayerState(player)->enteredLosingCheatCode = losingCheatCode;
-	gs->getPlayerState(player)->enteredWinningCheatCode = winningCheatCode;
-	gs->getPlayerState(player)->cheated = true;
-}
-
-void PlayerStartsTurn::applyGs(CGameState *gs)
-{
-	//assert(gs->actingPlayers.count(player) == 0);//Legal - may happen after loading of deserialized map state
-	gs->actingPlayers.insert(player);
-}
-
-void PlayerEndsTurn::applyGs(CGameState *gs)
-{
-	assert(gs->actingPlayers.count(player) == 1);
-	gs->actingPlayers.erase(player);
-}
-
-void DaysWithoutTown::applyGs(CGameState *gs)
-{
-	auto & playerState = gs->players.at(player);
-	playerState.daysWithoutCastle = daysWithoutCastle;
-}
-
-void TurnTimeUpdate::applyGs(CGameState *gs)
-{
-	auto & playerState = gs->players.at(player);
-	playerState.turnTimer = turnTimer;
-}
-
-void EntitiesChanged::applyGs(CGameState *gs)
-{
-	for(const auto & change : changes)
-		gs->updateEntity(change.metatype, change.entityIndex, change.data);
-}
-
-void SetRewardableConfiguration::applyGs(CGameState *gs)
-{
-	auto * objectPtr = gs->getObjInstance(objectID);
-
-	if (!buildingID.hasValue())
-	{
-		auto * rewardablePtr = dynamic_cast<CRewardableObject *>(objectPtr);
-		assert(rewardablePtr);
-		rewardablePtr->configuration = configuration;
-		rewardablePtr->initializeGuards();
-	}
-	else
-	{
-		auto * townPtr = dynamic_cast<CGTownInstance*>(objectPtr);
-		TownBuildingInstance * buildingPtr = nullptr;
-
-		for (auto & building : townPtr->rewardableBuildings)
-			if (building.second->getBuildingType() == buildingID)
-				buildingPtr = building.second.get();
-
-		auto * rewardablePtr = dynamic_cast<TownRewardableBuildingInstance *>(buildingPtr);
-		assert(rewardablePtr);
-		rewardablePtr->configuration = configuration;
-	}
-}
-
 VCMI_LIB_NAMESPACE_END

+ 0 - 111
lib/networkPacks/PacksForClient.h

@@ -57,7 +57,6 @@ struct DLL_LINKAGE PackageApplied : public CPackForClient
 	{
 	}
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState *gs) override {}
 
 	ui8 result = 0; //0 - something went wrong, request hasn't been realized; 1 - OK
 	ui32 packType = 0; //type id of applied package
@@ -82,7 +81,6 @@ struct DLL_LINKAGE SystemMessage : public CPackForClient
 	SystemMessage() = default;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState *gs) override {}
 
 	MetaString text;
 
@@ -102,7 +100,6 @@ struct DLL_LINKAGE PlayerBlocked : public CPackForClient
 	PlayerColor player;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState *gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -114,8 +111,6 @@ struct DLL_LINKAGE PlayerBlocked : public CPackForClient
 
 struct DLL_LINKAGE PlayerCheated : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	PlayerColor player;
 	bool losingCheatCode = false;
 	bool winningCheatCode = false;
@@ -132,8 +127,6 @@ struct DLL_LINKAGE PlayerCheated : public CPackForClient
 
 struct DLL_LINKAGE TurnTimeUpdate : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-	
 	PlayerColor player;
 	TurnTimerInfo turnTimer;
 		
@@ -146,8 +139,6 @@ struct DLL_LINKAGE TurnTimeUpdate : public CPackForClient
 
 struct DLL_LINKAGE PlayerStartsTurn : public Query
 {
-	void applyGs(CGameState * gs) override;
-
 	PlayerColor player;
 
 	void visitTyped(ICPackVisitor & visitor) override;
@@ -161,8 +152,6 @@ struct DLL_LINKAGE PlayerStartsTurn : public Query
 
 struct DLL_LINKAGE DaysWithoutTown : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	PlayerColor player;
 	std::optional<int32_t> daysWithoutCastle;
 
@@ -179,8 +168,6 @@ struct DLL_LINKAGE EntitiesChanged : public CPackForClient
 {
 	std::vector<EntityChanges> changes;
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -191,8 +178,6 @@ struct DLL_LINKAGE EntitiesChanged : public CPackForClient
 
 struct DLL_LINKAGE SetResources : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	bool abs = true; //false - changes by value; 1 - sets to value
@@ -209,8 +194,6 @@ struct DLL_LINKAGE SetResources : public CPackForClient
 
 struct DLL_LINKAGE SetPrimSkill : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ui8 abs = 0; //0 - changes by value; 1 - sets to value
@@ -229,8 +212,6 @@ struct DLL_LINKAGE SetPrimSkill : public CPackForClient
 
 struct DLL_LINKAGE SetSecSkill : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ui8 abs = 0; //0 - changes by value; 1 - sets to value
@@ -249,8 +230,6 @@ struct DLL_LINKAGE SetSecSkill : public CPackForClient
 
 struct DLL_LINKAGE HeroVisitCastle : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ui8 flags = 0; //1 - start
@@ -272,8 +251,6 @@ struct DLL_LINKAGE HeroVisitCastle : public CPackForClient
 
 struct DLL_LINKAGE ChangeSpells : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ui8 learn = 1; //1 - gives spell, 0 - takes
@@ -290,8 +267,6 @@ struct DLL_LINKAGE ChangeSpells : public CPackForClient
 
 struct DLL_LINKAGE SetResearchedSpells : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ui8 level = 0;
@@ -310,8 +285,6 @@ struct DLL_LINKAGE SetResearchedSpells : public CPackForClient
 
 struct DLL_LINKAGE SetMana : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	SetMana() = default;
@@ -335,8 +308,6 @@ struct DLL_LINKAGE SetMana : public CPackForClient
 
 struct DLL_LINKAGE SetMovePoints : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	SetMovePoints() = default;
 	SetMovePoints(ObjectInstanceID hid, si32 val, bool absolute)
 		: hid(hid)
@@ -360,8 +331,6 @@ struct DLL_LINKAGE SetMovePoints : public CPackForClient
 
 struct DLL_LINKAGE FoWChange : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	std::unordered_set<int3> tiles;
 	PlayerColor player;
 	ETileVisibility mode;
@@ -384,7 +353,6 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient
 	{
 		army.clearSlots();
 	}
-	void applyGs(CGameState * gs) override;
 
 	TavernHeroSlot slotID;
 	TavernSlotRole roleID;
@@ -429,8 +397,6 @@ struct DLL_LINKAGE GiveBonus : public CPackForClient
 	{
 	}
 
-	void applyGs(CGameState * gs) override;
-
 	ETarget who = ETarget::OBJECT;
 	VariantType id;
 	Bonus bonus;
@@ -448,8 +414,6 @@ struct DLL_LINKAGE GiveBonus : public CPackForClient
 
 struct DLL_LINKAGE ChangeObjPos : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	/// Object to move
 	ObjectInstanceID objid;
 	/// New position of visitable tile of an object
@@ -469,8 +433,6 @@ struct DLL_LINKAGE ChangeObjPos : public CPackForClient
 
 struct DLL_LINKAGE PlayerEndsTurn : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	PlayerColor player;
 
 	void visitTyped(ICPackVisitor & visitor) override;
@@ -483,8 +445,6 @@ struct DLL_LINKAGE PlayerEndsTurn : public CPackForClient
 
 struct DLL_LINKAGE PlayerEndsGame : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	PlayerColor player;
 	EVictoryLossCheckResult victoryLossCheckResult;
 	StatisticDataSet statistic;
@@ -501,8 +461,6 @@ struct DLL_LINKAGE PlayerEndsGame : public CPackForClient
 
 struct DLL_LINKAGE PlayerReinitInterface : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	std::vector<PlayerColor> players;
 	ui8 playerConnectionId; //PLAYER_AI for AI player
 
@@ -522,8 +480,6 @@ struct DLL_LINKAGE RemoveBonus : public CPackForClient
 	{
 	}
 
-	void applyGs(CGameState * gs) override;
-
 	GiveBonus::ETarget who; //who receives bonus
 	VariantIdentifier<HeroTypeID, PlayerColor, BattleID, ObjectInstanceID> whoID;
 
@@ -549,8 +505,6 @@ struct DLL_LINKAGE SetCommanderProperty : public CPackForClient
 {
 	enum ECommanderProperty { ALIVE, BONUS, SECONDARY_SKILL, EXPERIENCE, SPECIAL_SKILL };
 
-	void applyGs(CGameState * gs) override;
-
 	ObjectInstanceID heroid;
 
 	ECommanderProperty which = ALIVE;
@@ -572,8 +526,6 @@ struct DLL_LINKAGE SetCommanderProperty : public CPackForClient
 
 struct DLL_LINKAGE AddQuest : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	PlayerColor player;
 	QuestInfo quest;
 
@@ -590,7 +542,6 @@ struct DLL_LINKAGE UpdateArtHandlerLists : public CPackForClient
 {
 	std::map<ArtifactID, int> allocatedArtifacts;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -604,7 +555,6 @@ struct DLL_LINKAGE ChangeFormation : public CPackForClient
 	ObjectInstanceID hid;
 	EArmyFormation formation{};
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -623,7 +573,6 @@ struct DLL_LINKAGE RemoveObject : public CPackForClient
 	{
 	}
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	/// ID of removed object
@@ -641,8 +590,6 @@ struct DLL_LINKAGE RemoveObject : public CPackForClient
 
 struct DLL_LINKAGE TryMoveHero : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	enum EResult
 	{
 		FAILED,
@@ -689,8 +636,6 @@ struct DLL_LINKAGE TryMoveHero : public CPackForClient
 
 struct DLL_LINKAGE NewStructures : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	ObjectInstanceID tid;
 	std::set<BuildingID> bid;
 	si16 built = 0;
@@ -707,8 +652,6 @@ struct DLL_LINKAGE NewStructures : public CPackForClient
 
 struct DLL_LINKAGE RazeStructures : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	ObjectInstanceID tid;
 	std::set<BuildingID> bid;
 	si16 destroyed = 0;
@@ -725,8 +668,6 @@ struct DLL_LINKAGE RazeStructures : public CPackForClient
 
 struct DLL_LINKAGE SetAvailableCreatures : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	ObjectInstanceID tid;
 	std::vector<std::pair<ui32, std::vector<CreatureID> > > creatures;
 
@@ -741,8 +682,6 @@ struct DLL_LINKAGE SetAvailableCreatures : public CPackForClient
 
 struct DLL_LINKAGE SetHeroesInTown : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	ObjectInstanceID tid; //id of town
 	ObjectInstanceID visiting; //id of visiting hero
 	ObjectInstanceID garrison; //id of hero in garrison
@@ -759,8 +698,6 @@ struct DLL_LINKAGE SetHeroesInTown : public CPackForClient
 
 struct DLL_LINKAGE HeroRecruited : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	HeroTypeID hid; //subID of hero
 	ObjectInstanceID tid;
 	ObjectInstanceID boatId;
@@ -781,8 +718,6 @@ struct DLL_LINKAGE HeroRecruited : public CPackForClient
 
 struct DLL_LINKAGE GiveHero : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	ObjectInstanceID id; //object id
 	ObjectInstanceID boatId;
 	PlayerColor player;
@@ -804,7 +739,6 @@ struct DLL_LINKAGE OpenWindow : public Query
 	ObjectInstanceID visitor;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState *gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -817,8 +751,6 @@ struct DLL_LINKAGE OpenWindow : public Query
 
 struct DLL_LINKAGE NewObject : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	/// Object ID to create
 	std::shared_ptr<CGObjectInstance> newObject;
 	/// Which player initiated creation of this object
@@ -835,8 +767,6 @@ struct DLL_LINKAGE NewObject : public CPackForClient
 
 struct DLL_LINKAGE SetAvailableArtifacts : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	//two variants: id < 0: set artifact pool for Artifact Merchants in towns; id >= 0: set pool for adv. map Black Market (id is the id of Black Market instance then)
 	ObjectInstanceID id;
 	std::vector<ArtifactID> arts;
@@ -861,8 +791,6 @@ struct DLL_LINKAGE ChangeStackCount : CGarrisonOperationPack
 	TQuantity count;
 	bool absoluteValue; //if not -> count will be added (or subtracted if negative)
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -880,8 +808,6 @@ struct DLL_LINKAGE SetStackType : CGarrisonOperationPack
 	SlotID slot;
 	CreatureID type;
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -897,7 +823,6 @@ struct DLL_LINKAGE EraseStack : CGarrisonOperationPack
 	ObjectInstanceID army;
 	SlotID slot;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -914,7 +839,6 @@ struct DLL_LINKAGE SwapStacks : CGarrisonOperationPack
 	SlotID srcSlot;
 	SlotID dstSlot;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -933,7 +857,6 @@ struct DLL_LINKAGE InsertNewStack : CGarrisonOperationPack
 	CreatureID type;
 	TQuantity count = 0;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -955,7 +878,6 @@ struct DLL_LINKAGE RebalanceStacks : CGarrisonOperationPack
 
 	TQuantity count;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -972,7 +894,6 @@ struct DLL_LINKAGE BulkRebalanceStacks : CGarrisonOperationPack
 {
 	std::vector<RebalanceStacks> moves;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler>
@@ -998,7 +919,6 @@ struct DLL_LINKAGE PutArtifact : CArtifactOperationPack
 	bool askAssemble;
 	ArtifactInstanceID id;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1016,7 +936,6 @@ struct DLL_LINKAGE NewArtifact : public CArtifactOperationPack
 	SpellID spellId;
 	ArtifactPosition pos;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1034,7 +953,6 @@ struct DLL_LINKAGE BulkEraseArtifacts : CArtifactOperationPack
 	std::vector<ArtifactPosition> posPack;
 	std::optional<SlotID> creature;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1070,8 +988,6 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack
 	{
 	}
 
-	void applyGs(CGameState * gs) override;
-
 	std::vector<MoveArtifactInfo> artsPack0;
 	std::vector<MoveArtifactInfo> artsPack1;
 
@@ -1094,8 +1010,6 @@ struct DLL_LINKAGE AssembledArtifact : CArtifactOperationPack
 	ArtifactLocation al;
 	ArtifactID artId;
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1109,8 +1023,6 @@ struct DLL_LINKAGE DisassembledArtifact : CArtifactOperationPack
 {
 	ArtifactLocation al;
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1127,8 +1039,6 @@ struct DLL_LINKAGE HeroVisit : public CPackForClient
 
 	bool starting; //false -> ending
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1149,7 +1059,6 @@ struct DLL_LINKAGE InfoWindow : public CPackForClient //103  - displays simple i
 	ui16 soundID = 0;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -1164,8 +1073,6 @@ struct DLL_LINKAGE InfoWindow : public CPackForClient //103  - displays simple i
 
 struct DLL_LINKAGE NewTurn : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ui32 day = 0;
@@ -1197,7 +1104,6 @@ struct DLL_LINKAGE NewTurn : public CPackForClient
 
 struct DLL_LINKAGE SetObjectProperty : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
 	ObjectInstanceID id;
 	ObjProperty what{};
 
@@ -1228,8 +1134,6 @@ struct DLL_LINKAGE ChangeObjectVisitors : public CPackForClient
 	ObjectInstanceID object;
 	ObjectInstanceID hero; // note: hero owner will be also marked as "visited" this object
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ChangeObjectVisitors() = default;
@@ -1255,7 +1159,6 @@ struct DLL_LINKAGE ChangeArtifactsCostume : public CPackForClient
 	uint32_t costumeIdx = 0;
 	const PlayerColor player = PlayerColor::NEUTRAL;
 
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ChangeArtifactsCostume() = default;
@@ -1281,8 +1184,6 @@ struct DLL_LINKAGE HeroLevelUp : public Query
 	PrimarySkill primskill = PrimarySkill::ATTACK;
 	std::vector<SecondarySkill> skills;
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1302,8 +1203,6 @@ struct DLL_LINKAGE CommanderLevelUp : public Query
 
 	std::vector<ui32> skills; //0-5 - secondary skills, val-100 - special skill
 
-	void applyGs(CGameState * gs) override;
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -1349,7 +1248,6 @@ struct DLL_LINKAGE BlockingDialog : public Query
 	BlockingDialog() = default;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -1369,7 +1267,6 @@ struct DLL_LINKAGE GarrisonDialog : public Query
 	bool removableUnits = false;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -1388,7 +1285,6 @@ struct DLL_LINKAGE ExchangeDialog : public Query
 	ObjectInstanceID hero2;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -1414,7 +1310,6 @@ struct DLL_LINKAGE TeleportDialog : public Query
 	bool impassable = false;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -1435,7 +1330,6 @@ struct DLL_LINKAGE MapObjectSelectDialog : public Query
 	std::vector<ObjectInstanceID> objects;
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -1460,7 +1354,6 @@ struct DLL_LINKAGE AdvmapSpellCast : public CPackForClient
 
 protected:
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 };
 
 struct DLL_LINKAGE ShowWorldViewEx : public CPackForClient
@@ -1470,8 +1363,6 @@ struct DLL_LINKAGE ShowWorldViewEx : public CPackForClient
 
 	std::vector<ObjectPosInfo> objectPositions;
 
-	void applyGs(CGameState * gs) override {}
-
 	template <typename Handler> void serialize(Handler & h)
 	{
 		h & player;
@@ -1492,7 +1383,6 @@ struct DLL_LINKAGE PlayerMessageClient : public CPackForClient
 	{
 	}
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	PlayerColor player;
 	std::string text;
@@ -1511,7 +1401,6 @@ struct DLL_LINKAGE CenterView : public CPackForClient
 	ui32 focusTime = 0; //ms
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState * gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{

+ 0 - 53
lib/networkPacks/PacksForClientBattle.h

@@ -29,8 +29,6 @@ class BattleInfo;
 
 struct DLL_LINKAGE BattleStart : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	BattleID battleID = BattleID::NONE;
 	std::unique_ptr<BattleInfo> info;
 
@@ -46,8 +44,6 @@ struct DLL_LINKAGE BattleStart : public CPackForClient
 
 struct DLL_LINKAGE BattleNextRound : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	BattleID battleID = BattleID::NONE;
 
 	void visitTyped(ICPackVisitor & visitor) override;
@@ -61,8 +57,6 @@ struct DLL_LINKAGE BattleNextRound : public CPackForClient
 
 struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	BattleID battleID = BattleID::NONE;
 	uint32_t stack = 0;
 	BattleUnitTurnReason reason;
@@ -80,8 +74,6 @@ struct DLL_LINKAGE BattleSetActiveStack : public CPackForClient
 
 struct DLL_LINKAGE BattleCancelled: public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	BattleID battleID = BattleID::NONE;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -93,8 +85,6 @@ struct DLL_LINKAGE BattleCancelled: public CPackForClient
 
 struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	struct HeroBattleResults
 	{
 		ObjectInstanceID heroID;
@@ -126,8 +116,6 @@ struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
 
 struct DLL_LINKAGE BattleResult : public Query
 {
-	void applyFirstCl(CClient * cl);
-
 	BattleID battleID = BattleID::NONE;
 	EBattleResult result = EBattleResult::NORMAL;
 	BattleSide winner = BattleSide::NONE; //0 - attacker, 1 - defender, [2 - draw (should be possible?)]
@@ -135,7 +123,6 @@ struct DLL_LINKAGE BattleResult : public Query
 	BattleSideArray<TExpType> exp{0,0}; //exp for attacker and defender
 
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState *gs) override {}
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -154,9 +141,6 @@ struct DLL_LINKAGE BattleLogMessage : public CPackForClient
 	BattleID battleID = BattleID::NONE;
 	std::vector<MetaString> lines;
 
-	void applyGs(CGameState * gs) override;
-	void applyBattle(IBattleState * battleState);
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -175,9 +159,6 @@ struct DLL_LINKAGE BattleStackMoved : public CPackForClient
 	int distance = 0;
 	bool teleporting = false;
 	
-	void applyGs(CGameState * gs) override;
-	void applyBattle(IBattleState * battleState);
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -193,9 +174,6 @@ struct DLL_LINKAGE BattleStackMoved : public CPackForClient
 
 struct DLL_LINKAGE BattleUnitsChanged : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-	void applyBattle(IBattleState * battleState);
-
 	BattleID battleID = BattleID::NONE;
 	std::vector<UnitChanges> changedStacks;
 
@@ -211,10 +189,6 @@ struct DLL_LINKAGE BattleUnitsChanged : public CPackForClient
 
 struct BattleStackAttacked
 {
-	DLL_LINKAGE void applyGs(CGameState * gs);
-	DLL_LINKAGE void applyBattle(IBattleState * battleState);
-
-	BattleID battleID = BattleID::NONE;
 	ui32 stackAttacked = 0, attackerID = 0;
 	ui32 killedAmount = 0;
 	int64_t damageAmount = 0;
@@ -251,7 +225,6 @@ struct BattleStackAttacked
 
 	template <typename Handler> void serialize(Handler & h)
 	{
-		h & battleID;
 		h & stackAttacked;
 		h & attackerID;
 		h & newState;
@@ -259,7 +232,6 @@ struct BattleStackAttacked
 		h & killedAmount;
 		h & damageAmount;
 		h & spellID;
-		assert(battleID != BattleID::NONE);
 	}
 	bool operator<(const BattleStackAttacked & b) const
 	{
@@ -269,7 +241,6 @@ struct BattleStackAttacked
 
 struct DLL_LINKAGE BattleAttack : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
 	BattleUnitsChanged attackerChanges;
 
 	BattleID battleID = BattleID::NONE;
@@ -336,8 +307,6 @@ struct DLL_LINKAGE StartAction : public CPackForClient
 		: ba(std::move(act))
 	{
 	}
-	void applyFirstCl(CClient * cl);
-	void applyGs(CGameState * gs) override;
 
 	BattleID battleID = BattleID::NONE;
 	BattleAction ba;
@@ -355,7 +324,6 @@ struct DLL_LINKAGE StartAction : public CPackForClient
 struct DLL_LINKAGE EndAction : public CPackForClient
 {
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState *gs) override {}
 
 	BattleID battleID = BattleID::NONE;
 
@@ -367,8 +335,6 @@ struct DLL_LINKAGE EndAction : public CPackForClient
 
 struct DLL_LINKAGE BattleSpellCast : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	BattleID battleID = BattleID::NONE;
 	bool activeCast = true;
 	BattleSide side = BattleSide::NONE; //which hero did cast spell
@@ -402,9 +368,6 @@ struct DLL_LINKAGE BattleSpellCast : public CPackForClient
 
 struct DLL_LINKAGE StacksInjured : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-	void applyBattle(IBattleState * battleState);
-
 	BattleID battleID = BattleID::NONE;
 	std::vector<BattleStackAttacked> stacks;
 
@@ -427,7 +390,6 @@ struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 	std::vector<BulkMoveArtifacts> artifacts;
 	CStackBasicDescriptor raisedStack;
 	void visitTyped(ICPackVisitor & visitor) override;
-	void applyGs(CGameState *gs) override;
 
 	template <typename Handler> void serialize(Handler & h)
 	{
@@ -443,9 +405,6 @@ struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 
 struct DLL_LINKAGE BattleObstaclesChanged : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-	void applyBattle(IBattleState * battleState);
-
 	BattleID battleID = BattleID::NONE;
 	std::vector<ObstacleChanges> changes;
 
@@ -475,12 +434,6 @@ struct DLL_LINKAGE CatapultAttack : public CPackForClient
 		}
 	};
 
-	CatapultAttack();
-	~CatapultAttack() override;
-
-	void applyGs(CGameState * gs) override;
-	void applyBattle(IBattleState * battleState);
-
 	BattleID battleID = BattleID::NONE;
 	std::vector< AttackInfo > attackedParts;
 	int attacker = -1; //if -1, then a spell caused this
@@ -500,8 +453,6 @@ struct DLL_LINKAGE BattleSetStackProperty : public CPackForClient
 {
 	enum BattleStackProperty { CASTS, ENCHANTER_COUNTER, UNBIND, CLONED, HAS_CLONE };
 
-	void applyGs(CGameState * gs) override;
-
 	BattleID battleID = BattleID::NONE;
 	int stackID = 0;
 	BattleStackProperty which = CASTS;
@@ -525,8 +476,6 @@ protected:
 ///activated at the beginning of turn
 struct DLL_LINKAGE BattleTriggerEffect : public CPackForClient
 {
-	void applyGs(CGameState * gs) override; //effect
-
 	BattleID battleID = BattleID::NONE;
 	int stackID = 0;
 	int effect = 0; //use corresponding Bonus type
@@ -549,8 +498,6 @@ protected:
 
 struct DLL_LINKAGE BattleUpdateGateState : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-
 	BattleID battleID = BattleID::NONE;
 	EGateState state = EGateState::NONE;
 	template <typename Handler> void serialize(Handler & h)

+ 0 - 4
lib/networkPacks/PacksForServer.h

@@ -732,8 +732,6 @@ struct DLL_LINKAGE SaveGame : public CPackForServer
 	}
 	std::string fname;
 
-	void applyGs(CGameState * gs) {};
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler & h)
@@ -752,8 +750,6 @@ struct DLL_LINKAGE PlayerMessage : public CPackForServer
 	{
 	}
 
-	void applyGs(CGameState * gs) {};
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	std::string text;

+ 0 - 1
lib/networkPacks/SetRewardableConfiguration.h

@@ -17,7 +17,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 struct DLL_LINKAGE SetRewardableConfiguration : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	ObjectInstanceID objectID;

+ 0 - 3
lib/networkPacks/SetStackEffect.h

@@ -19,9 +19,6 @@ class IBattleState;
 
 struct DLL_LINKAGE SetStackEffect : public CPackForClient
 {
-	void applyGs(CGameState * gs) override;
-	void applyBattle(IBattleState * battleState);
-
 	BattleID battleID = BattleID::NONE;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toAdd;
 	std::vector<std::pair<ui32, std::vector<Bonus>>> toUpdate;

+ 4 - 4
lib/serializer/RegisterTypes.h

@@ -59,10 +59,10 @@ void registerTypes(Serializer &s)
 	static_assert(std::is_abstract_v<CGTeleport>, "If this type is no longer abstract consider registering it for serialization with ID 3");
 	static_assert(std::is_abstract_v<IQuestObject>, "If this type is no longer abstract consider registering it for serialization with ID 11");
 	static_assert(std::is_abstract_v<CArtifactSet>, "If this type is no longer abstract consider registering it for serialization with ID 29");
-	static_assert(std::is_abstract_v<CPackForClient>, "If this type is no longer abstract consider registering it for serialization with ID 83");
-	static_assert(std::is_abstract_v<Query>, "If this type is no longer abstract consider registering it for serialization with ID 153");
-	static_assert(std::is_abstract_v<CGarrisonOperationPack>, "If this type is no longer abstract consider registering it for serialization with ID 161");
-	static_assert(std::is_abstract_v<CArtifactOperationPack>, "If this type is no longer abstract consider registering it for serialization with ID 168");
+//	static_assert(std::is_abstract_v<CPackForClient>, "If this type is no longer abstract consider registering it for serialization with ID 83");
+//	static_assert(std::is_abstract_v<Query>, "If this type is no longer abstract consider registering it for serialization with ID 153");
+//	static_assert(std::is_abstract_v<CGarrisonOperationPack>, "If this type is no longer abstract consider registering it for serialization with ID 161");
+//	static_assert(std::is_abstract_v<CArtifactOperationPack>, "If this type is no longer abstract consider registering it for serialization with ID 168");
 
 	s.template registerType<CGObjectInstance>(2);
 	s.template registerType<CGMonolith>(4);

+ 0 - 1
lib/spells/effects/Damage.cpp

@@ -53,7 +53,6 @@ void Damage::apply(ServerCallback * server, const Mechanics * m, const EffectTar
 		if(unit && unit->alive())
 		{
 			BattleStackAttacked bsa;
-			bsa.battleID = m->battle()->getBattle()->getBattleID();
 			bsa.damageAmount = damageForTarget(targetIndex, m, unit);
 			bsa.stackAttacked = unit->unitId();
 			bsa.attackerID = -1;

+ 1 - 1
server/NetPacksServer.cpp

@@ -124,7 +124,7 @@ void ApplyGhNetPackVisitor::visitBulkMergeStacks(BulkMergeStacks & pack)
 	result = gh.bulkMergeStacks(pack.src, pack.srcOwner);
 }
 
-void ApplyGhNetPackVisitor::visitBulkSmartSplitStack(BulkSplitAndRebalanceStack & pack)
+void ApplyGhNetPackVisitor::visitBulkSplitAndRebalanceStack(BulkSplitAndRebalanceStack & pack)
 {
 	gh.throwIfWrongPlayer(connection, &pack);
 	gh.throwIfPlayerNotActive(connection, &pack);

+ 1 - 1
server/ServerNetPackVisitors.h

@@ -41,7 +41,7 @@ public:
 	void visitBulkMoveArmy(BulkMoveArmy & pack) override;
 	void visitBulkSplitStack(BulkSplitStack & pack) override;
 	void visitBulkMergeStacks(BulkMergeStacks & pack) override;
-	void visitBulkSmartSplitStack(BulkSplitAndRebalanceStack & pack) override;
+	void visitBulkSplitAndRebalanceStack(BulkSplitAndRebalanceStack & pack) override;
 	void visitDisbandCreature(DisbandCreature & pack) override;
 	void visitBuildStructure(BuildStructure & pack) override;
 	void visitSpellResearch(SpellResearch & pack) override;

+ 0 - 4
server/battles/BattleActionProcessor.cpp

@@ -1037,9 +1037,6 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 	if (healInfo.healedHealthPoints > 0)
 		bat.flags |= BattleAttack::LIFE_DRAIN;
 
-	for (BattleStackAttacked & bsa : bat.bsa)
-		bsa.battleID = battle.getBattle()->getBattleID();
-
 	gameHandler->sendAndApply(bat);
 
 	{
@@ -1097,7 +1094,6 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
 		{
 			BattleStackAttacked bsa;
 
-			bsa.battleID = battle.getBattle()->getBattleID();
 			bsa.flags |= BattleStackAttacked::FIRE_SHIELD;
 			bsa.stackAttacked = attacker->unitId(); //invert
 			bsa.attackerID = defender->unitId();

+ 3 - 1
test/mock/BattleFake.h

@@ -20,6 +20,7 @@
 #endif
 
 #include "../../lib/battle/CBattleInfoCallback.h"
+#include "../../lib/gameState/GameStatePackVisitor.h"
 
 namespace test
 {
@@ -83,7 +84,8 @@ public:
 	template <typename T>
 	void accept(T & pack)
 	{
-		pack.applyBattle(this);
+		BattleStatePackVisitor visitor(*this);
+		pack.visit(visitor);
 	}
 
 	const IBattleInfo * getBattle() const override