From baa865d85711cb15e1bb856847da2952ecdee7f6 Mon Sep 17 00:00:00 2001
From: Ivan Savenko <saven.ivan@gmail.com>
Date: Wed, 12 Jul 2023 21:13:17 +0300
Subject: [PATCH] Extracted message-related functionality of CGameHandler to
 separate file

---
 lib/GameConstants.cpp             |   1 +
 lib/GameConstants.h               |   2 +
 server/CGameHandler.cpp           | 393 +----------------------
 server/CGameHandler.h             |  19 +-
 server/CMakeLists.txt             |   2 +
 server/CVCMIServer.cpp            |   5 +-
 server/NetPacksServer.cpp         |   3 +-
 server/PlayerMessageProcessor.cpp | 508 ++++++++++++++++++++++++++++++
 server/PlayerMessageProcessor.h   |  63 ++++
 9 files changed, 606 insertions(+), 390 deletions(-)
 create mode 100644 server/PlayerMessageProcessor.cpp
 create mode 100644 server/PlayerMessageProcessor.h

diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp
index 76dcc54ed..c440277de 100644
--- a/lib/GameConstants.cpp
+++ b/lib/GameConstants.cpp
@@ -40,6 +40,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 const HeroTypeID HeroTypeID::NONE = HeroTypeID(-1);
+const ObjectInstanceID ObjectInstanceID::NONE = ObjectInstanceID(-1);
 
 const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
 const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
diff --git a/lib/GameConstants.h b/lib/GameConstants.h
index 9cc315f6b..93a3085a3 100644
--- a/lib/GameConstants.h
+++ b/lib/GameConstants.h
@@ -313,6 +313,8 @@ class ObjectInstanceID : public BaseForID<ObjectInstanceID, si32>
 {
 	INSTID_LIKE_CLASS_COMMON(ObjectInstanceID, si32)
 
+	DLL_LINKAGE static const ObjectInstanceID NONE;
+
 	friend class CGameInfoCallback;
 	friend class CNonConstInfoCallback;
 };
diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp
index c96f8d808..00412911a 100644
--- a/server/CGameHandler.cpp
+++ b/server/CGameHandler.cpp
@@ -15,6 +15,8 @@
 #include "ServerSpellCastEnvironment.h"
 #include "CVCMIServer.h"
 
+#include "PlayerMessageProcessor.h"
+
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/FileInfo.h"
 #include "../lib/int3.h"
@@ -296,6 +298,11 @@ events::EventBus * CGameHandler::eventBus() const
 	return serverEventBus.get();
 }
 
+CVCMIServer * CGameHandler::gameLobby() const
+{
+	return lobby;
+}
+
 void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill)
 {
 	changeSecSkill(hero, skill, 1, 0);
@@ -1217,7 +1224,7 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 		if(playerConnection != playerConnections.second.end())
 		{
 			std::string messageText = boost::str(boost::format("%s (cid %d) was disconnected") % playerSettings->name % c->connectionID);
-			playerMessage(playerId, messageText, ObjectInstanceID{});
+			playerMessages->broadcastMessage(playerId, messageText);
 		}
 	}
 }
@@ -1554,6 +1561,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 CGameHandler::CGameHandler(CVCMIServer * lobby)
 	: lobby(lobby)
 	, heroPool(std::make_unique<HeroPoolProcessor>(this))
+	, playerMessages(std::make_unique<PlayerMessageProcessor>(this))
 	, complainNoCreatures("No creatures to split")
 	, complainNotEnoughCreatures("Cannot split that stack, not enough creatures!")
 	, complainInvalidSlot("Invalid slot accessed!")
@@ -2644,14 +2652,6 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st
 	sendAndApply(&cs);
 }
 
-void CGameHandler::sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message)
-{
-	SystemMessage sm;
-	sm.text = message;
-	boost::unique_lock<boost::mutex> lock(*c->mutexWrite);
-	*(c.get()) << &sm;
-}
-
 void CGameHandler::giveHeroBonus(GiveBonus * bonus)
 {
 	sendAndApply(bonus);
@@ -2862,10 +2862,8 @@ bool CGameHandler::isPlayerOwns(CPackForServer * pack, ObjectInstanceID id)
 void CGameHandler::throwNotAllowedAction(CPackForServer * pack)
 {
 	if(pack->c)
-	{
-		SystemMessage temp_message("You are not allowed to perform this action!");
-		pack->c->sendPack(&temp_message);
-	}
+		playerMessages->sendSystemMessage(pack->c, "You are not allowed to perform this action!");
+
 	logNetwork->error("Player is not allowed to perform this action!");
 	throw ExceptionNotAllowedAction();
 }
@@ -2875,11 +2873,9 @@ void CGameHandler::wrongPlayerMessage(CPackForServer * pack, PlayerColor expecte
 	std::ostringstream oss;
 	oss << "You were identified as player " << getPlayerAt(pack->c) << " while expecting " << expectedplayer;
 	logNetwork->error(oss.str());
+
 	if(pack->c)
-	{
-		SystemMessage temp_message(oss.str());
-		pack->c->sendPack(&temp_message);
-	}
+		playerMessages->sendSystemMessage(pack->c, oss.str());
 }
 
 void CGameHandler::throwOnWrongOwner(CPackForServer * pack, ObjectInstanceID id)
@@ -3594,13 +3590,6 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
 	return true;
 }
 
-void CGameHandler::sendMessageToAll(const std::string &message)
-{
-	SystemMessage sm;
-	sm.text = message;
-	sendToAllClients(&sm);
-}
-
 bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl)
 {
 	const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid));
@@ -4825,140 +4814,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 	return ok;
 }
 
-void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
-{
-	bool cheated = false;
-	
-	std::vector<std::string> words;
-	boost::split(words, message, boost::is_any_of(" "));
-	
-	bool isHost = false;
-	for(auto & c : connections[player])
-		if(lobby->isClientHost(c->connectionID))
-			isHost = true;
-	
-	if(isHost && words.size() >= 2 && words[0] == "game")
-	{
-		if(words[1] == "exit" || words[1] == "quit" || words[1] == "end")
-		{
-			SystemMessage temp_message("game was terminated");
-			sendAndApply(&temp_message);
-			lobby->state = EServerState::SHUTDOWN;
-			return;
-		}
-		if(words.size() == 3 && words[1] == "save")
-		{
-			save("Saves/" + words[2]);
-			SystemMessage temp_message("game saved as " + words[2]);
-			sendAndApply(&temp_message);
-			return;
-		}
-		if(words.size() == 3 && words[1] == "kick")
-		{
-			auto playername = words[2];
-			PlayerColor playerToKick(PlayerColor::CANNOT_DETERMINE);
-			if(std::all_of(playername.begin(), playername.end(), ::isdigit))
-				playerToKick = PlayerColor(std::stoi(playername));
-			else
-			{
-				for(auto & c : connections)
-				{
-					if(c.first.getStr(false) == playername)
-						playerToKick = c.first;
-				}
-			}
-			
-			if(playerToKick != PlayerColor::CANNOT_DETERMINE)
-			{
-				PlayerCheated pc;
-				pc.player = playerToKick;
-				pc.losingCheatCode = true;
-				sendAndApply(&pc);
-				checkVictoryLossConditionsForPlayer(playerToKick);
-			}
-			return;
-		}
-	}
-	
-	int obj = 0;
-	if (words.size() == 2 && words[0] != "vcmiexp" && words[0] != "vcmiolorin")
-	{
-		obj = std::atoi(words[1].c_str());
-		if (obj)
-			currObj = ObjectInstanceID(obj);
-	}
-
-	const CGHeroInstance * hero = getHero(currObj);
-	const CGTownInstance * town = getTown(currObj);
-	if (!town && hero)
-		town = hero->visitedTown;
-
-	if(words.size() > 1 && (words[0] == "vcmiarmy" || words[0] == "vcminissi" || words[0] == "vcmiexp" || words[0] == "vcmiolorin"))
-	{
-		std::string cheatCodeWithOneParameter = std::string(words[0]) + " " + words[1];
-		handleCheatCode(cheatCodeWithOneParameter, player, hero, town, cheated);
-	}
-	else if (words.size() == 1 || obj)
-	{
-		handleCheatCode(words[0], player, hero, town, cheated);
-	}
-	else
-	{
-		for (const auto & i : gs->players)
-		{
-			if (i.first == PlayerColor::NEUTRAL)
-				continue;
-			if (words[1] == "ai")
-			{
-				if (i.second.human)
-					continue;
-			}
-			else if (words[1] != "all" && words[1] != i.first.getStr())
-				continue;
-
-			if (words[0] == "vcmiformenos" || words[0] == "vcmieagles" || words[0] == "vcmiungoliant"
-				|| words[0] == "vcmiresources" || words[0] == "vcmimap" || words[0] == "vcmihidemap")
-			{
-				handleCheatCode(words[0], i.first, nullptr, nullptr, cheated);
-			}
-			else if (words[0] == "vcmiarmenelos" || words[0] == "vcmibuild")
-			{
-				for (const auto & t : i.second.towns)
-				{
-					handleCheatCode(words[0], i.first, nullptr, t, cheated);
-				}
-			}
-			else
-			{
-				for (const auto & h : i.second.heroes)
-				{
-					handleCheatCode(words[0], i.first, h, nullptr, cheated);
-				}
-			}
-		}
-	}
-
-	if (cheated)
-	{
-		if(!getPlayerSettings(player)->isControlledByAI())
-		{
-			SystemMessage temp_message(VLC->generaltexth->allTexts[260]);
-			sendAndApply(&temp_message);
-		}
-
-		if(!player.isSpectator())
-			checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
-	}
-	else
-	{
-		if(!getPlayerSettings(player)->isControlledByAI())
-		{
-			PlayerMessageClient temp_message(player, message);
-			sendAndApply(&temp_message);
-		}
-	}
-}
-
 bool CGameHandler::makeCustomAction(BattleAction & ba)
 {
 	switch(ba.actionType)
@@ -5321,7 +5176,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
 
 bool CGameHandler::complain(const std::string &problem)
 {
-	sendMessageToAll("Server encountered a problem: " + problem);
+	playerMessages->broadcastSystemMessage("Server encountered a problem: " + problem);
 	logGlobal->error(problem);
 	return true;
 }
@@ -6713,225 +6568,6 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID)
 	}
 }
 
-void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, const CGHeroInstance * hero, const CGTownInstance * town, bool & cheated)
-{
-	//Make cheat case-insensitive
-	std::transform(cheat.begin(), cheat.end(), cheat.begin(), [](unsigned char c){ return std::tolower(c); });
-	
-	if (cheat == "vcmiistari" || cheat == "vcmispells")
-	{
-		cheated = true;
-		if (!hero) return;
-		///Give hero spellbook
-		if (!hero->hasSpellbook())
-			giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
-
-		///Give all spells with bonus (to allow banned spells)
-		GiveBonus giveBonus(GiveBonus::ETarget::HERO);
-		giveBonus.id = hero->id.getNum();
-		giveBonus.bonus = Bonus(BonusDuration::PERMANENT, BonusType::SPELLS_OF_LEVEL, BonusSource::OTHER, 0, 0);
-		//start with level 0 to skip abilities
-		for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++)
-		{
-			giveBonus.bonus.subtype = level;
-			sendAndApply(&giveBonus);
-		}
-
-		///Give mana
-		SetMana sm;
-		sm.hid = hero->id;
-		sm.val = 999;
-		sm.absolute = true;
-		sendAndApply(&sm);
-	}
-	else if (cheat == "vcmiarmenelos" || cheat == "vcmibuild")
-	{
-		cheated = true;
-		if (!town) return;
-		///Build all buildings in selected town
-		for (auto & build : town->town->buildings)
-		{
-			if (!town->hasBuilt(build.first)
-				&& !build.second->getNameTranslated().empty()
-				&& build.first != BuildingID::SHIP)
-			{
-				buildStructure(town->id, build.first, true);
-			}
-		}
-	}
-	else if (cheat == "vcmiainur" || cheat == "vcmiangband" || cheat == "vcmiglaurung" || cheat == "vcmiarchangel"
-		|| cheat == "vcmiblackknight" || cheat == "vcmicrystal" || cheat == "vcmiazure" || cheat == "vcmifaerie")
-	{
-		cheated = true;
-		if (!hero) return;
-		///Gives N creatures into each slot
-		std::map<std::string, std::pair<std::string, int>> creatures;
-		creatures.insert(std::make_pair("vcmiainur", std::make_pair("archangel", 5))); //5 archangels
-		creatures.insert(std::make_pair("vcmiangband", std::make_pair("blackKnight", 10))); //10 black knights
-		creatures.insert(std::make_pair("vcmiglaurung", std::make_pair("crystalDragon", 5000))); //5000 crystal dragons
-		creatures.insert(std::make_pair("vcmiarchangel", std::make_pair("archangel", 5))); //5 archangels
-		creatures.insert(std::make_pair("vcmiblackknight", std::make_pair("blackKnight", 10))); //10 black knights
-		creatures.insert(std::make_pair("vcmicrystal", std::make_pair("crystalDragon", 5000))); //5000 crystal dragons
-		creatures.insert(std::make_pair("vcmiazure", std::make_pair("azureDragon", 5000))); //5000 azure dragons
-		creatures.insert(std::make_pair("vcmifaerie", std::make_pair("fairieDragon", 5000))); //5000 faerie dragons
-
-		const int32_t creatureIdentifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatures[cheat].first, false).value();
-		const CCreature * creature = VLC->creh->objects.at(creatureIdentifier);
-		for (int i = 0; i < GameConstants::ARMY_SIZE; i++)
-			if (!hero->hasStackAtSlot(SlotID(i)))
-				insertNewStack(StackLocation(hero, SlotID(i)), creature, creatures[cheat].second);
-	}
-	else if (boost::starts_with(cheat, "vcmiarmy") || boost::starts_with(cheat, "vcminissi"))
-	{
-		cheated = true;
-		if (!hero) return;
-
-		std::vector<std::string> words;
-		boost::split(words, cheat, boost::is_any_of(" "));
-
-		if(words.size() < 2)
-			return;
-
-		std::string creatureIdentifier = words[1];
-
-		std::optional<int32_t> creatureId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatureIdentifier, false);
-
-		if(creatureId.has_value())
-		{
-			const auto * creature = CreatureID(creatureId.value()).toCreature();
-
-			for (int i = 0; i < GameConstants::ARMY_SIZE; i++)
-				if (!hero->hasStackAtSlot(SlotID(i)))
-					insertNewStack(StackLocation(hero, SlotID(i)), creature, 5 * std::pow(10, i));
-		}
-	}
-	else if (cheat == "vcminoldor" || cheat == "vcmimachines")
-	{
-		cheated = true;
-		if (!hero) return;
-		///Give all war machines to hero
-		if (!hero->getArt(ArtifactPosition::MACH1))
-			giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
-		if (!hero->getArt(ArtifactPosition::MACH2))
-			giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
-		if (!hero->getArt(ArtifactPosition::MACH3))
-			giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
-	}
-	else if (cheat == "vcmiforgeofnoldorking" || cheat == "vcmiartifacts")
-	{
-		cheated = true;
-		if (!hero) return;
-		///Give hero all artifacts except war machines, spell scrolls and spell book
-		for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
-		{
-			if(VLC->arth->objects[g]->canBePutAt(hero))
-				giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
-		}
-	}
-	else if (cheat == "vcmiglorfindel" || cheat == "vcmilevel")
-	{
-		cheated = true;
-		if (!hero) return;
-		///selected hero gains a new level
-		changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + 1) - VLC->heroh->reqExp(hero->level));
-	}
-	else if (boost::starts_with(cheat, "vcmiexp") || boost::starts_with(cheat, "vcmiolorin"))
-	{
-		cheated = true;
-		if (!hero) return;
-
-		std::vector<std::string> words;
-		boost::split(words, cheat, boost::is_any_of(" "));
-
-		if(words.size() < 2)
-			return;
-
-		std::string expAmount = words[1];
-		long expAmountProcessed = 0;
-
-		try
-		{
-			expAmountProcessed = std::stol(expAmount);
-		}
-		catch(std::exception&)
-		{
-			logGlobal->error("Could not parse experience amount for vcmiexp cheat");
-		}
-
-		if(expAmountProcessed > 1)
-		{
-			changePrimSkill(hero, PrimarySkill::EXPERIENCE, expAmountProcessed);
-		}
-	}
-	else if (cheat == "vcminahar" || cheat == "vcmimove")
-	{
-		cheated = true;
-		if (!hero) return;
-		///Give 1000000 movement points to hero
-		SetMovePoints smp;
-		smp.hid = hero->id;
-		smp.val = 1000000;
-		sendAndApply(&smp);
-
-		GiveBonus gb(GiveBonus::ETarget::HERO);
-		gb.bonus.type = BonusType::FREE_SHIP_BOARDING;
-		gb.bonus.duration = BonusDuration::ONE_DAY;
-		gb.bonus.source = BonusSource::OTHER;
-		gb.id = hero->id.getNum();
-		giveHeroBonus(&gb);
-	}
-	else if (cheat == "vcmiformenos" || cheat == "vcmiresources")
-	{
-		cheated = true;
-		///Give resources to player
-		TResources resources;
-		resources[EGameResID::GOLD] = 100000;
-		for (GameResID i = EGameResID::WOOD; i < EGameResID::GOLD; ++i)
-			resources[i] = 100;
-
-		giveResources(player, resources);
-	}
-	else if (cheat == "vcmisilmaril" || cheat == "vcmiwin")
-	{
-		cheated = true;
-		///Player wins
-		PlayerCheated pc;
-		pc.player = player;
-		pc.winningCheatCode = true;
-		sendAndApply(&pc);
-	}
-	else if (cheat == "vcmimelkor" || cheat == "vcmilose")
-	{
-		cheated = true;
-		///Player looses
-		PlayerCheated pc;
-		pc.player = player;
-		pc.losingCheatCode = true;
-		sendAndApply(&pc);
-	}
-	else if (cheat == "vcmieagles" || cheat == "vcmiungoliant" || cheat == "vcmimap" || cheat == "vcmihidemap")
-	{
-		cheated = true;
-		///Reveal or conceal FoW
-		FoWChange fc;
-		fc.mode = ((cheat == "vcmieagles" || cheat == "vcmimap") ? 1 : 0);
-		fc.player = player;
-		const auto & fowMap = gs->getPlayerTeam(player)->fogOfWarMap;
-		auto hlp_tab = new int3[gs->map->width * gs->map->height * (gs->map->levels())];
-		int lastUnc = 0;
-
-		for(int z = 0; z < gs->map->levels(); z++)
-			for(int x = 0; x < gs->map->width; x++)
-				for(int y = 0; y < gs->map->height; y++)
-					if(!(*fowMap)[z][x][y] || !fc.mode)
-						hlp_tab[lastUnc++] = int3(x, y, z);
-
-		fc.tiles.insert(hlp_tab, hlp_tab + lastUnc);
-		delete [] hlp_tab;
-		sendAndApply(&fc);
-	}
-}
-
 void CGameHandler::removeObstacle(const CObstacleInstance & obstacle)
 {
 	BattleObstaclesChanged obsRem;
@@ -7248,4 +6884,5 @@ void CGameHandler::deserializationFix()
 	//FIXME: pointer to GameHandler itself can't be deserialized at the moment since GameHandler is top-level entity in serialization
 	// restore any places that requires such pointer manually
 	heroPool->gameHandler = this;
+	playerMessages->gameHandler = this;
 }
diff --git a/server/CGameHandler.h b/server/CGameHandler.h
index af01e43c1..771ba90f1 100644
--- a/server/CGameHandler.h
+++ b/server/CGameHandler.h
@@ -50,6 +50,7 @@ class HeroPoolProcessor;
 class CGameHandler;
 class CVCMIServer;
 class CBaseForGHApply;
+class PlayerMessageProcessor;
 
 struct PlayerStatus
 {
@@ -108,6 +109,8 @@ public:
 	enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
 	enum ELEaveTile {LEAVING_TILE, REMAINING_ON_TILE};
 
+	std::unique_ptr<PlayerMessageProcessor> playerMessages;
+
 	std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player
 	PlayerStatuses states; //player color -> player state
 
@@ -123,6 +126,7 @@ public:
 	const GameCb * game() const override;
 	vstd::CLoggerBase * logger() const override;
 	events::EventBus * eventBus() const override;
+	CVCMIServer * gameLobby() const;
 
 	bool isValidObject(const CGObjectInstance *obj) const;
 	bool isBlockedByQueries(const CPack *pack, PlayerColor player);
@@ -235,7 +239,6 @@ public:
 	PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;
 	bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const;
 
-	void playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj);
 	void updateGateState();
 	bool makeBattleAction(BattleAction &ba);
 	bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
@@ -289,6 +292,7 @@ public:
 		h & finishingBattle;
 		h & heroPool;
 		h & getRandomGenerator();
+		h & playerMessages;
 
 		if (!h.saving)
 			deserializationFix();
@@ -303,8 +307,6 @@ public:
 #endif
 	}
 
-	void sendMessageToAll(const std::string &message);
-	void sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message);
 	void sendToAllClients(CPackForClient * pack);
 	void sendAndApply(CPackForClient * pack) override;
 	void applyAndSend(CPackForClient * pack);
@@ -354,7 +356,11 @@ public:
 	void attackCasting(bool ranged, BonusType attackMode, const battle::Unit * attacker, const battle::Unit * defender);
 	bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot);
 	void spawnWanderingMonsters(CreatureID creatureID);
-	void handleCheatCode(std::string & cheat, PlayerColor player, const CGHeroInstance * hero, const CGTownInstance * town, bool & cheated);
+
+	// Check for victory and loss conditions
+	void checkVictoryLossConditionsForPlayer(PlayerColor player);
+	void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors);
+	void checkVictoryLossConditionsForAll();
 
 	CRandomGenerator & getRandomGenerator();
 
@@ -379,11 +385,6 @@ private:
 	void makeStackDoNothing(const CStack * next);
 	void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
 
-	// Check for victory and loss conditions
-	void checkVictoryLossConditionsForPlayer(PlayerColor player);
-	void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors);
-	void checkVictoryLossConditionsForAll();
-
 	const std::string complainNoCreatures;
 	const std::string complainNotEnoughCreatures;
 	const std::string complainInvalidSlot;
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
index 76030afd0..4239d07ba 100644
--- a/server/CMakeLists.txt
+++ b/server/CMakeLists.txt
@@ -3,6 +3,7 @@ set(server_SRCS
 
 		CGameHandler.cpp
 		HeroPoolProcessor.cpp
+		PlayerMessageProcessor.cpp
 		ServerSpellCastEnvironment.cpp
 		CQuery.cpp
 		CVCMIServer.cpp
@@ -15,6 +16,7 @@ set(server_HEADERS
 
 		CGameHandler.h
 		HeroPoolProcessor.h
+		PlayerMessageProcessor.h
 		ServerSpellCastEnvironment.h
 		CQuery.h
 		CVCMIServer.h
diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp
index 892869efd..d8ce1fc34 100644
--- a/server/CVCMIServer.cpp
+++ b/server/CVCMIServer.cpp
@@ -39,6 +39,7 @@
 #include "../lib/VCMI_Lib.h"
 #include "../lib/VCMIDirs.h"
 #include "CGameHandler.h"
+#include "PlayerMessageProcessor.h"
 #include "../lib/mapping/CMapInfo.h"
 #include "../lib/GameConstants.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
@@ -605,7 +606,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
 		
 		if(gh && si && state == EServerState::GAMEPLAY)
 		{
-			gh->playerMessage(playerSettings->color, playerLeftMsgText, ObjectInstanceID{});
+			gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText);
 			gh->connections[playerSettings->color].insert(hostClient);
 			startAiPack.players.push_back(playerSettings->color);
 		}
@@ -633,7 +634,7 @@ void CVCMIServer::reconnectPlayer(int connId)
 				continue;
 			
 			std::string messageText = boost::str(boost::format("%s (cid %d) is connected") % playerSettings->name % connId);
-			gh->playerMessage(playerSettings->color, messageText, ObjectInstanceID{});
+			gh->playerMessages->broadcastMessage(playerSettings->color, messageText);
 			
 			startAiPack.players.push_back(playerSettings->color);
 		}
diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp
index dc3fd7ef1..d6812f1ef 100644
--- a/server/NetPacksServer.cpp
+++ b/server/NetPacksServer.cpp
@@ -12,6 +12,7 @@
 
 #include "CGameHandler.h"
 #include "HeroPoolProcessor.h"
+#include "PlayerMessageProcessor.h"
 
 #include "../lib/IGameCallback.h"
 #include "../lib/mapObjects/CGTownInstance.h"
@@ -352,6 +353,6 @@ void ApplyGhNetPackVisitor::visitPlayerMessage(PlayerMessage & pack)
 	if(!pack.player.isSpectator()) // TODO: clearly not a great way to verify permissions
 		gh.throwOnWrongPlayer(&pack, pack.player);
 	
-	gh.playerMessage(pack.player, pack.text, pack.currObj);
+	gh.playerMessages->playerMessage(pack.player, pack.text, pack.currObj);
 	result = true;
 }
diff --git a/server/PlayerMessageProcessor.cpp b/server/PlayerMessageProcessor.cpp
new file mode 100644
index 000000000..0a161d64f
--- /dev/null
+++ b/server/PlayerMessageProcessor.cpp
@@ -0,0 +1,508 @@
+/*
+ * CGameHandler.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 "PlayerMessageProcessor.h"
+
+#include "CGameHandler.h"
+#include "CVCMIServer.h"
+
+#include "../lib/serializer/Connection.h"
+#include "../lib/CGeneralTextHandler.h"
+#include "../lib/CHeroHandler.h"
+#include "../lib/CModHandler.h"
+#include "../lib/CPlayerState.h"
+#include "../lib/GameConstants.h"
+#include "../lib/NetPacks.h"
+#include "../lib/StartInfo.h"
+#include "../lib/gameState/CGameState.h"
+#include "../lib/mapObjects/CGTownInstance.h"
+
+PlayerMessageProcessor::PlayerMessageProcessor()
+	:gameHandler(nullptr)
+{
+}
+
+PlayerMessageProcessor::PlayerMessageProcessor(CGameHandler * gameHandler)
+	:gameHandler(gameHandler)
+{
+}
+
+void PlayerMessageProcessor::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
+{
+	if (handleHostCommand(player, message))
+		return;
+
+	if (handleCheatCode(message, player, currObj))
+	{
+		if(!gameHandler->getPlayerSettings(player)->isControlledByAI())
+			broadcastSystemMessage(VLC->generaltexth->allTexts[260]);
+
+		if(!player.isSpectator())
+			gameHandler->checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
+
+		return;
+	}
+
+	broadcastMessage(player, message);
+}
+
+bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::string &message)
+{
+	std::vector<std::string> words;
+	boost::split(words, message, boost::is_any_of(" "));
+
+	bool isHost = false;
+	for(auto & c : gameHandler->connections[player])
+		if(gameHandler->gameLobby()->isClientHost(c->connectionID))
+			isHost = true;
+
+	if(!isHost || words.size() < 2 || words[0] != "game")
+		return false;
+
+	if(words[1] == "exit" || words[1] == "quit" || words[1] == "end")
+	{
+		broadcastSystemMessage("game was terminated");
+		gameHandler->gameLobby()->state = EServerState::SHUTDOWN;
+
+		return true;
+	}
+	if(words.size() == 3 && words[1] == "save")
+	{
+		gameHandler->save("Saves/" + words[2]);
+		broadcastSystemMessage("game saved as " + words[2]);
+
+		return true;
+	}
+	if(words.size() == 3 && words[1] == "kick")
+	{
+		auto playername = words[2];
+		PlayerColor playerToKick(PlayerColor::CANNOT_DETERMINE);
+		if(std::all_of(playername.begin(), playername.end(), ::isdigit))
+			playerToKick = PlayerColor(std::stoi(playername));
+		else
+		{
+			for(auto & c : gameHandler->connections)
+			{
+				if(c.first.getStr(false) == playername)
+					playerToKick = c.first;
+			}
+		}
+
+		if(playerToKick != PlayerColor::CANNOT_DETERMINE)
+		{
+			PlayerCheated pc;
+			pc.player = playerToKick;
+			pc.losingCheatCode = true;
+			gameHandler->sendAndApply(&pc);
+			gameHandler->checkVictoryLossConditionsForPlayer(playerToKick);
+		}
+		return true;
+	}
+
+	return false;
+}
+
+void PlayerMessageProcessor::cheatGiveSpells(PlayerColor player, const CGHeroInstance * hero)
+{
+	if (!hero)
+		return;
+
+	///Give hero spellbook
+	if (!hero->hasSpellbook())
+		gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK);
+
+	///Give all spells with bonus (to allow banned spells)
+	GiveBonus giveBonus(GiveBonus::ETarget::HERO);
+	giveBonus.id = hero->id.getNum();
+	giveBonus.bonus = Bonus(BonusDuration::PERMANENT, BonusType::SPELLS_OF_LEVEL, BonusSource::OTHER, 0, 0);
+	//start with level 0 to skip abilities
+	for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++)
+	{
+		giveBonus.bonus.subtype = level;
+		gameHandler->sendAndApply(&giveBonus);
+	}
+
+	///Give mana
+	SetMana sm;
+	sm.hid = hero->id;
+	sm.val = 999;
+	sm.absolute = true;
+	gameHandler->sendAndApply(&sm);
+}
+
+void PlayerMessageProcessor::cheatBuildTown(PlayerColor player, const CGTownInstance * town)
+{
+	if (!town)
+		return;
+
+	for (auto & build : town->town->buildings)
+	{
+		if (!town->hasBuilt(build.first)
+			&& !build.second->getNameTranslated().empty()
+			&& build.first != BuildingID::SHIP)
+		{
+			gameHandler->buildStructure(town->id, build.first, true);
+		}
+	}
+}
+
+void PlayerMessageProcessor::cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
+{
+	if (!hero)
+		return;
+
+	std::string creatureIdentifier = words.empty() ? "archangel" : words[0];
+	std::optional<int> amountPerSlot;
+
+	try
+	{
+		amountPerSlot = std::stol(words.at(1));
+	}
+	catch(std::exception&)
+	{
+	}
+
+	std::optional<int32_t> creatureId = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "creature", creatureIdentifier, false);
+
+	if(creatureId.has_value())
+	{
+		const auto * creature = CreatureID(creatureId.value()).toCreature();
+
+		for (int i = 0; i < GameConstants::ARMY_SIZE; i++)
+		{
+			if (!hero->hasStackAtSlot(SlotID(i)))
+			{
+				if (amountPerSlot.has_value())
+					gameHandler->insertNewStack(StackLocation(hero, SlotID(i)), creature, *amountPerSlot);
+				else
+					gameHandler->insertNewStack(StackLocation(hero, SlotID(i)), creature, 5 * std::pow(10, i));
+			}
+		}
+	}
+}
+
+void PlayerMessageProcessor::cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero)
+{
+	if (!hero)
+		return;
+
+	if (!hero->getArt(ArtifactPosition::MACH1))
+		gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
+	if (!hero->getArt(ArtifactPosition::MACH2))
+		gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
+	if (!hero->getArt(ArtifactPosition::MACH3))
+		gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
+}
+
+void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero)
+{
+	if (!hero)
+		return;
+
+	for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
+	{
+		if(VLC->arth->objects[g]->canBePutAt(hero))
+			gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE);
+	}
+}
+
+void PlayerMessageProcessor::cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
+{
+	if (!hero)
+		return;
+
+	int levelsToGain;
+	try
+	{
+		levelsToGain = std::stol(words.at(0));
+	}
+	catch(std::exception&)
+	{
+		levelsToGain = 1;
+	}
+
+	gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + levelsToGain) - VLC->heroh->reqExp(hero->level));
+}
+
+void PlayerMessageProcessor::cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
+{
+	if (!hero)
+		return;
+
+	int expAmountProcessed;
+
+	try
+	{
+		expAmountProcessed = std::stol(words.at(0));
+	}
+	catch(std::exception&)
+	{
+		expAmountProcessed = 10000;
+	}
+
+	gameHandler->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expAmountProcessed);
+}
+
+void PlayerMessageProcessor::cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words)
+{
+	if (!hero)
+		return;
+
+	SetMovePoints smp;
+	smp.hid = hero->id;
+	try
+	{
+		smp.val = std::stol(words.at(0));;
+	}
+	catch(std::exception&)
+	{
+		smp.val = 1000000;
+	}
+
+	gameHandler->sendAndApply(&smp);
+
+	GiveBonus gb(GiveBonus::ETarget::HERO);
+	gb.bonus.type = BonusType::FREE_SHIP_BOARDING;
+	gb.bonus.duration = BonusDuration::ONE_DAY;
+	gb.bonus.source = BonusSource::OTHER;
+	gb.id = hero->id.getNum();
+	gameHandler->giveHeroBonus(&gb);
+}
+
+void PlayerMessageProcessor::cheatResources(PlayerColor player, std::vector<std::string> words)
+{
+	int baseResourceAmount;
+	try
+	{
+		baseResourceAmount = std::stol(words.at(0));;
+	}
+	catch(std::exception&)
+	{
+		baseResourceAmount = 100;
+	}
+
+	TResources resources;
+	resources[EGameResID::GOLD] = baseResourceAmount * 100;
+	for (GameResID i = EGameResID::WOOD; i < EGameResID::GOLD; ++i)
+		resources[i] = baseResourceAmount;
+
+	gameHandler->giveResources(player, resources);
+}
+
+void PlayerMessageProcessor::cheatVictory(PlayerColor player)
+{
+	PlayerCheated pc;
+	pc.player = player;
+	pc.winningCheatCode = true;
+	gameHandler->sendAndApply(&pc);
+}
+
+void PlayerMessageProcessor::cheatDefeat(PlayerColor player)
+{
+	PlayerCheated pc;
+	pc.player = player;
+	pc.losingCheatCode = true;
+	gameHandler->sendAndApply(&pc);
+}
+
+void PlayerMessageProcessor::cheatMapReveal(PlayerColor player, bool reveal)
+{
+	FoWChange fc;
+	fc.mode = reveal;
+	fc.player = player;
+	const auto & fowMap = gameHandler->gameState()->getPlayerTeam(player)->fogOfWarMap;
+	const auto & mapSize = gameHandler->gameState()->getMapSize();
+	auto hlp_tab = new int3[mapSize.x * mapSize.y * mapSize.z];
+	int lastUnc = 0;
+
+	for(int z = 0; z < mapSize.z; z++)
+		for(int x = 0; x < mapSize.x; x++)
+			for(int y = 0; y < mapSize.y; y++)
+				if(!(*fowMap)[z][x][y] || !fc.mode)
+					hlp_tab[lastUnc++] = int3(x, y, z);
+
+	fc.tiles.insert(hlp_tab, hlp_tab + lastUnc);
+	delete [] hlp_tab;
+	gameHandler->sendAndApply(&fc);
+}
+
+bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerColor player, ObjectInstanceID currObj)
+{
+	std::vector<std::string> words;
+	boost::split(words, cheat, boost::is_any_of("\t\r\n "));
+
+	if (words.empty())
+		return false;
+
+	//Make cheat name case-insensitive, but keep words/parameters (e.g. creature name) as it
+	std::string cheatName = boost::to_lower_copy(words[0]);
+	words.erase(words.begin());
+
+	std::vector<std::string> townTargetedCheats = { "vcmiarmenelos", "vcmibuild", "nwczion" };
+	std::vector<std::string> playerTargetedCheats = {
+		"vcmiformenos",  "vcmiresources", "nwctheconstruct",
+		"vcmimelkor",    "vcmilose",      "nwcbluepill",
+		"vcmisilmaril",  "vcmiwin",       "nwcredpill",
+		"vcmieagles",    "vcmimap",       "nwcwhatisthematrix",
+		"vcmiungoliant", "vcmihidemap",   "nwcignoranceisbliss"
+	};
+	std::vector<std::string> heroTargetedCheats = {
+		"vcmiainur",             "vcmiarchangel",   "nwctrinity",
+		"vcmiangband",           "vcmiblackknight", "nwcagents",
+		"vcmiglaurung",          "vcmicrystal",     "vcmiazure",
+		"vcmifaerie",            "vcmiarmy",        "vcminissi",
+		"vcmiistari",            "vcmispells",      "nwcthereisnospoon",
+		"vcminoldor",            "vcmimachines",     "nwclotsofguns",
+		"vcmiglorfindel",        "vcmilevel",       "nwcneo",
+		"vcminahar",             "vcmimove",        "nwcnebuchadnezzar",
+		"vcmiforgeofnoldorking", "vcmiartifacts",
+		"vcmiolorin",            "vcmiexp",
+	};
+
+	if (!vstd::contains(townTargetedCheats, cheatName) && !vstd::contains(playerTargetedCheats, cheatName) && !vstd::contains(heroTargetedCheats, cheatName))
+		return false;
+
+	bool playerTargetedCheat = false;
+
+	for (const auto & i : gameHandler->gameState()->players)
+	{
+		if (i.first == PlayerColor::NEUTRAL)
+			continue;
+
+		if (words.front() == "ai" && i.second.human)
+			continue;
+
+		if (words.front() != "all" && words.front() != i.first.getStr())
+			continue;
+
+		std::vector<std::string> parameters = words;
+
+		playerTargetedCheat = true;
+		parameters.erase(parameters.begin());
+
+		if (vstd::contains(playerTargetedCheats, cheatName))
+			executeCheatCode(cheatName, i.first, ObjectInstanceID::NONE, parameters);
+
+		if (vstd::contains(townTargetedCheats, cheatName))
+			for (const auto & t : i.second.towns)
+				executeCheatCode(cheatName, i.first, t->id, parameters);
+
+		if (vstd::contains(heroTargetedCheats, cheatName))
+			for (const auto & h : i.second.heroes)
+				executeCheatCode(cheatName, i.first, h->id, parameters);
+	}
+
+	if (!playerTargetedCheat)
+		executeCheatCode(cheatName, player, currObj, words);
+
+	return true;
+}
+
+void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector<std::string> & words)
+{
+	const CGHeroInstance * hero = gameHandler->getHero(currObj);
+	const CGTownInstance * town = gameHandler->getTown(currObj);
+	if (!town && hero)
+		town = hero->visitedTown;
+
+	const auto & doCheatGiveSpells = [&]() { cheatGiveSpells(player, hero); };
+	const auto & doCheatBuildTown = [&]() { cheatBuildTown(player, town); };
+	const auto & doCheatGiveArmyCustom = [&]() { cheatGiveArmy(player, hero, words); };
+	const auto & doCheatGiveArmyFixed = [&](std::vector<std::string> customWords) { cheatGiveArmy(player, hero, customWords); };
+	const auto & doCheatGiveMachines = [&]() { cheatGiveMachines(player, hero); };
+	const auto & doCheatGiveArtifacts = [&]() { cheatGiveArtifacts(player, hero); };
+	const auto & doCheatLevelup = [&]() { cheatLevelup(player, hero, words); };
+	const auto & doCheatExperience = [&]() { cheatExperience(player, hero, words); };
+	const auto & doCheatMovement = [&]() { cheatMovement(player, hero, words); };
+	const auto & doCheatResources = [&]() { cheatResources(player, words); };
+	const auto & doCheatVictory = [&]() { cheatVictory(player); };
+	const auto & doCheatDefeat = [&]() { cheatDefeat(player); };
+	const auto & doCheatMapReveal = [&]() { cheatMapReveal(player, true); };
+	const auto & doCheatMapHide = [&]() { cheatMapReveal(player, false); };
+
+	// Unimplemented H3 cheats:
+	// nwcfollowthewhiterabbit - The currently selected hero permanently gains maximum luck.
+	// nwcmorpheus - The currently selected hero permanently gains maximum morale.
+	// nwcoracle - The puzzle map is permanently revealed.
+	// nwcphisherprice - Changes and brightens the game colors.
+
+	std::map<std::string, std::function<void()>> callbacks = {
+		{"vcmiainur",            [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} },
+		{"nwctrinity",           [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} },
+		{"vcmiangband",          [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} },
+		{"vcmiglaurung",         [&] () {doCheatGiveArmyFixed({ "crystalDragon", "5000" });} },
+		{"vcmiarchangel",        [&] () {doCheatGiveArmyFixed({ "archangel", "5" });} },
+		{"nwcagents",            [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} },
+		{"vcmiblackknight",      [&] () {doCheatGiveArmyFixed({ "blackKnight", "10" });} },
+		{"vcmicrystal",          [&] () {doCheatGiveArmyFixed({ "crystalDragon", "5000" });} },
+		{"vcmiazure",            [&] () {doCheatGiveArmyFixed({ "azureDragon", "5000" });} },
+		{"vcmifaerie",           [&] () {doCheatGiveArmyFixed({ "fairieDragon", "5000" });} },
+		{"vcmiarmy",              doCheatGiveArmyCustom },
+		{"vcminissi",             doCheatGiveArmyCustom },
+		{"vcmiistari",            doCheatGiveSpells     },
+		{"vcmispells",            doCheatGiveSpells     },
+		{"nwcthereisnospoon",     doCheatGiveSpells     },
+		{"vcmiarmenelos",         doCheatBuildTown      },
+		{"vcmibuild",             doCheatBuildTown      },
+		{"nwczion",               doCheatBuildTown      },
+		{"vcminoldor",            doCheatGiveMachines   },
+		{"vcmimachines",          doCheatGiveMachines   },
+		{"nwclotsofguns",         doCheatGiveMachines   },
+		{"vcmiforgeofnoldorking", doCheatGiveArtifacts  },
+		{"vcmiartifacts",         doCheatGiveArtifacts  },
+		{"vcmiglorfindel",        doCheatLevelup        },
+		{"vcmilevel",             doCheatLevelup        },
+		{"nwcneo",                doCheatLevelup        },
+		{"vcmiolorin",            doCheatExperience     },
+		{"vcmiexp",               doCheatExperience     },
+		{"vcminahar",             doCheatMovement       },
+		{"vcmimove",              doCheatMovement       },
+		{"nwcnebuchadnezzar",     doCheatMovement       },
+		{"vcmiformenos",          doCheatResources      },
+		{"vcmiresources",         doCheatResources      },
+		{"nwctheconstruct",       doCheatResources      },
+		{"nwcbluepill",           doCheatDefeat         },
+		{"vcmimelkor",            doCheatDefeat         },
+		{"vcmilose",              doCheatDefeat         },
+		{"nwcredpill",            doCheatVictory        },
+		{"vcmisilmaril",          doCheatVictory        },
+		{"vcmiwin",               doCheatVictory        },
+		{"nwcwhatisthematrix",    doCheatMapReveal      },
+		{"vcmieagles",            doCheatMapReveal      },
+		{"vcmimap",               doCheatMapReveal      },
+		{"vcmiungoliant",         doCheatMapHide        },
+		{"vcmihidemap",           doCheatMapHide        },
+		{"nwcignoranceisbliss",   doCheatMapHide        },
+	};
+
+	assert(callbacks.count(cheatName));
+	if (callbacks.count(cheatName))
+		callbacks.at(cheatName)();
+}
+
+void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message)
+{
+	SystemMessage sm;
+	sm.text = message;
+	connection->sendPack(&sm);
+}
+
+void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message)
+{
+	SystemMessage sm;
+	sm.text = message;
+	gameHandler->sendToAllClients(&sm);
+}
+
+void PlayerMessageProcessor::broadcastMessage(PlayerColor playerSender, const std::string & message)
+{
+	PlayerMessageClient temp_message(playerSender, message);
+	gameHandler->sendAndApply(&temp_message);
+}
diff --git a/server/PlayerMessageProcessor.h b/server/PlayerMessageProcessor.h
new file mode 100644
index 000000000..357bd675c
--- /dev/null
+++ b/server/PlayerMessageProcessor.h
@@ -0,0 +1,63 @@
+/*
+ * CGameHandler.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+class PlayerColor;
+class ObjectInstanceID;
+class CGHeroInstance;
+class CGTownInstance;
+class CConnection;
+VCMI_LIB_NAMESPACE_END
+
+class CGameHandler;
+
+class PlayerMessageProcessor
+{
+	void executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector<std::string> & arguments );
+	bool handleCheatCode(const std::string & cheatFullCommand, PlayerColor player, ObjectInstanceID currObj);
+	bool handleHostCommand(PlayerColor player, const std::string & message);
+
+	void cheatGiveSpells(PlayerColor player, const CGHeroInstance * hero);
+	void cheatBuildTown(PlayerColor player, const CGTownInstance * town);
+	void cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
+	void cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero);
+	void cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero);
+	void cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
+	void cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
+	void cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector<std::string> words);
+	void cheatResources(PlayerColor player, std::vector<std::string> words);
+	void cheatVictory(PlayerColor player);
+	void cheatDefeat(PlayerColor player);
+	void cheatMapReveal(PlayerColor player, bool reveal);
+
+public:
+	CGameHandler * gameHandler;
+
+	PlayerMessageProcessor();
+	PlayerMessageProcessor(CGameHandler * gameHandler);
+
+	/// incoming NetPack handling
+	void playerMessage(PlayerColor player, const std::string & message, ObjectInstanceID currObj);
+
+	/// Send message to specific client with "System" as sender
+	void sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message);
+
+	/// Send message to all players with "System" as sender
+	void broadcastSystemMessage(const std::string & message);
+
+	/// Send message from specific player to all other players
+	void broadcastMessage(PlayerColor playerSender, const std::string & message);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+
+	}
+};