Browse Source

Merge pull request #3861 from Laserlicht/pvp_options

Pvp options
Ivan Savenko 1 year ago
parent
commit
6ac67775f5

+ 7 - 0
Mods/vcmi/config/vcmi/english.json

@@ -125,6 +125,13 @@
 	"vcmi.lobby.mod.state.version" : "Version mismatch",
 	"vcmi.lobby.mod.state.excessive" : "Must be disabled",
 	"vcmi.lobby.mod.state.missing" : "Not installed",
+	"vcmi.lobby.pvp.coin.hover" : "Coin",
+	"vcmi.lobby.pvp.coin.help" : "Flips a coin",
+	"vcmi.lobby.pvp.randomTown.hover" : "Random town",
+	"vcmi.lobby.pvp.randomTown.help" : "Write a random town in the chat",
+	"vcmi.lobby.pvp.randomTownVs.hover" : "Random town vs.",
+	"vcmi.lobby.pvp.randomTownVs.help" : "Write two random towns in the chat",
+	"vcmi.lobby.pvp.versus" : "vs.",
 
 	"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",

+ 5 - 2
client/GameChatHandler.cpp

@@ -25,6 +25,8 @@
 #include "../lib/mapObjects/CArmedInstance.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/MetaString.h"
+#include "../lib/VCMI_Lib.h"
+#include "../lib/CGeneralTextHandler.h"
 
 const std::vector<GameChatMessage> & GameChatHandler::getChatHistory() const
 {
@@ -45,7 +47,9 @@ void GameChatHandler::sendMessageGameplay(const std::string & messageText)
 void GameChatHandler::sendMessageLobby(const std::string & senderName, const std::string & messageText)
 {
 	LobbyChatMessage lcm;
-	lcm.message = messageText;
+	MetaString txt;
+	txt.appendRawString(messageText);
+	lcm.message = txt;
 	lcm.playerName = senderName;
 	CSH->sendLobbyPack(lcm);
 	CSH->getGlobalLobby().sendMatchChatMessage(messageText);
@@ -104,4 +108,3 @@ void GameChatHandler::onNewSystemMessageReceived(const std::string & messageText
 	if(LOCPLINT && !settings["session"]["hideSystemMessages"].Bool())
 		LOCPLINT->cingconsole->addMessage(TextOperations::getCurrentFormattedTimeLocal(), "System", messageText);
 }
-

+ 2 - 2
client/NetPacksClient.cpp

@@ -915,9 +915,9 @@ void ApplyClientNetPackVisitor::visitPackageApplied(PackageApplied & pack)
 void ApplyClientNetPackVisitor::visitSystemMessage(SystemMessage & pack)
 {
 	// usually used to receive error messages from server
-	logNetwork->error("System message: %s", pack.text);
+	logNetwork->error("System message: %s", pack.text.toString());
 
-	CSH->getGameChat().onNewSystemMessageReceived(pack.text);
+	CSH->getGameChat().onNewSystemMessageReceived(pack.text.toString());
 }
 
 void ApplyClientNetPackVisitor::visitPlayerBlocked(PlayerBlocked & pack)

+ 2 - 2
client/NetPacksLobbyClient.cpp

@@ -99,7 +99,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientD
 
 void ApplyOnLobbyScreenNetPackVisitor::visitLobbyChatMessage(LobbyChatMessage & pack)
 {
-	handler.getGameChat().onNewLobbyMessageReceived(pack.playerName, pack.message);
+	handler.getGameChat().onNewLobbyMessageReceived(pack.playerName, pack.message.toString());
 }
 
 void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack)
@@ -220,5 +220,5 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyShowMessage(LobbyShowMessage &
 		return;
 	
 	lobby->buttonStart->block(false);
-	handler.showServerError(pack.message);
+	handler.showServerError(pack.message.toString());
 }

+ 134 - 0
client/lobby/CSelectionBase.cpp

@@ -31,6 +31,7 @@
 #include "../widgets/Buttons.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/GraphicalPrimitiveCanvas.h"
+#include "../widgets/Images.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
@@ -43,12 +44,14 @@
 
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
+#include "../../lib/CTownHandler.h"
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/CThreadHelper.h"
 #include "../../lib/MetaString.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/mapping/CMapHeader.h"
 #include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/networkPacks/PacksForLobby.h"
 
 ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
 	: screenType(ScreenType)
@@ -137,6 +140,7 @@ InfoCard::InfoCard()
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
 	chat = std::make_shared<CChatBox>(Rect(18, 126, 335, 143));
+	pvpBox = std::make_shared<PvPBox>(Rect(17, 396, 338, 105));
 
 	buttonInvitePlayers = std::make_shared<CButton>(Point(20, 365), AnimationPath::builtin("pregameInvitePlayers"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateRoomInviteInterface(); } );
 	buttonOpenGlobalLobby = std::make_shared<CButton>(Point(188, 365), AnimationPath::builtin("pregameReturnToLobby"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateInterface(); });
@@ -298,8 +302,16 @@ void InfoCard::setChat(bool activateChat)
 			buttonInvitePlayers->enable();
 			buttonOpenGlobalLobby->enable();
 		}
+		labelMapDiff->disable();
+		labelPlayerDifficulty->disable();
+		labelRating->disable();
+		labelDifficulty->disable();
+		labelDifficultyPercent->disable();
+		flagbox->disable();
+		iconDifficulty->disable();
 		mapDescription->disable();
 		chat->enable();
+		pvpBox->enable();
 		playerListBg->enable();
 	}
 	else
@@ -308,6 +320,7 @@ void InfoCard::setChat(bool activateChat)
 		buttonOpenGlobalLobby->disable();
 		mapDescription->enable();
 		chat->disable();
+		pvpBox->disable();
 		playerListBg->disable();
 
 		if(SEL->screenType == ESelectionScreen::campaignList)
@@ -325,6 +338,13 @@ void InfoCard::setChat(bool activateChat)
 			labelLossConditionText->enable();
 			labelGroupPlayers->disable();
 		}
+		labelMapDiff->enable();
+		labelPlayerDifficulty->enable();
+		labelRating->enable();
+		labelDifficulty->enable();
+		labelDifficultyPercent->enable();
+		flagbox->enable();
+		iconDifficulty->enable();
 	}
 
 	showChat = activateChat;
@@ -373,6 +393,120 @@ void CChatBox::addNewMessage(const std::string & text)
 		chatHistory->slider->scrollToMax();
 }
 
+PvPBox::PvPBox(const Rect & rect)
+{
+	OBJ_CONSTRUCTION;
+	pos += rect.topLeft();
+	setRedrawParent(true);
+
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, rect.w, rect.h));
+	backgroundTexture->playerColored(PlayerColor(1));
+	backgroundBorder = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, rect.w, rect.h), ColorRGBA(0, 0, 0, 64), ColorRGBA(96, 96, 96, 255), 1);
+
+	townSelector = std::make_shared<TownSelector>(Point(5, 3));
+
+	auto getBannedTowns = [this](){
+		std::vector<FactionID> bannedTowns;
+		for(auto & town : townSelector->townsEnabled)
+			if(!town.second)
+				bannedTowns.push_back(town.first);
+		return bannedTowns;
+	};
+
+	buttonFlipCoin = std::make_shared<CButton>(Point(190, 6), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.coin.help")), [](){
+		LobbyPvPAction lpa;
+		lpa.action = LobbyPvPAction::COIN;
+		CSH->sendLobbyPack(lpa);
+	}, EShortcut::NONE);
+	buttonFlipCoin->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.coin.hover"), EFonts::FONT_SMALL, Colors::WHITE);
+
+	buttonRandomTown = std::make_shared<CButton>(Point(190, 31), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.randomTown.help")), [getBannedTowns](){
+		LobbyPvPAction lpa;
+		lpa.action = LobbyPvPAction::RANDOM_TOWN;
+		lpa.bannedTowns = getBannedTowns();
+		CSH->sendLobbyPack(lpa);
+	}, EShortcut::NONE);
+	buttonRandomTown->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.randomTown.hover"), EFonts::FONT_SMALL, Colors::WHITE);
+
+	buttonRandomTownVs = std::make_shared<CButton>(Point(190, 56), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.randomTownVs.help")), [getBannedTowns](){
+		LobbyPvPAction lpa;
+		lpa.action = LobbyPvPAction::RANDOM_TOWN_VS;
+		lpa.bannedTowns = getBannedTowns();
+		CSH->sendLobbyPack(lpa);
+	}, EShortcut::NONE);
+	buttonRandomTownVs->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.randomTownVs.hover"), EFonts::FONT_SMALL, Colors::WHITE);
+}
+
+TownSelector::TownSelector(const Point & loc)
+{
+	OBJ_CONSTRUCTION;
+	pos += loc;
+	setRedrawParent(true);
+
+	int count = 0;
+	for(auto const & factionID : VLC->townh->getDefaultAllowed())
+	{
+		townsEnabled[factionID] = true;
+		count++;
+	};
+
+	auto divisionRoundUp = [](int x, int y){ return (x + (y - 1)) / y; };
+
+	if(count > 9)
+	{
+		slider = std::make_shared<CSlider>(Point(144, 0), 96, std::bind(&TownSelector::sliderMove, this, _1), 3, divisionRoundUp(count, 3), 0, Orientation::VERTICAL, CSlider::BLUE);
+		slider->setPanningStep(24);
+		slider->setScrollBounds(Rect(-144, 0, slider->pos.x - pos.x + slider->pos.w, slider->pos.h));
+	}
+
+	updateListItems();
+}
+
+void TownSelector::updateListItems()
+{
+	OBJ_CONSTRUCTION;
+	int line = slider ? slider->getValue() : 0;
+	int x_offset = slider ? 0 : 8;
+	
+	towns.clear();
+	townsArea.clear();
+
+	int x = 0;
+	int y = 0;
+	CGI->factions()->forEach([this, &x, &y, line, x_offset](const Faction *entity, bool &stop){
+		if(!entity->hasTown())
+			return;
+
+		if(y >= line && (y - line) < 3)
+		{
+			FactionID factionID = entity->getFaction();
+			auto getImageIndex = [](FactionID factionID, bool enabled){ return (*CGI->townh)[factionID]->town->clientInfo.icons[true][!enabled] + 2; }; 
+			towns[factionID] = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), getImageIndex(factionID, townsEnabled[factionID]), 0, x_offset + 48 * x, 32 * (y - line));
+			townsArea[factionID] = std::make_shared<LRClickableArea>(Rect(x_offset + 48 * x, 32 * (y - line), 48, 32), [this, getImageIndex, factionID](){
+				townsEnabled[factionID] = !townsEnabled[factionID];
+				towns[factionID]->setFrame(getImageIndex(factionID, townsEnabled[factionID]));
+				redraw();
+			}, [factionID](){ CRClickPopup::createAndPush((*CGI->townh)[factionID]->town->faction->getNameTranslated()); });
+		}
+
+		if (x < 2)
+			x++;
+		else
+		{
+			x = 0;
+			y++;
+		}
+	});
+}
+
+void TownSelector::sliderMove(int slidPos)
+{
+	if(!slider)
+		return; // ignore spurious call when slider is being created
+	updateListItems();
+	redraw();
+}
+
 CFlagBox::CFlagBox(const Rect & rect)
 	: CIntObject(SHOW_POPUP)
 {

+ 36 - 0
client/lobby/CSelectionBase.h

@@ -31,10 +31,15 @@ class ExtraOptionsTab;
 class SelectionTab;
 class InfoCard;
 class CChatBox;
+class PvPBox;
+class TownSelector;
 class CLabel;
+class CSlider;
 class CFlagBox;
 class CLabelGroup;
 class TransparentFilledRectangle;
+class FilledTexturePlayerColored;
+class LRClickableArea;
 
 class ISelectionScreenInfo
 {
@@ -107,6 +112,8 @@ class InfoCard : public CIntObject
 	std::shared_ptr<CLabelGroup> labelGroupPlayers;
 	std::shared_ptr<CButton> buttonInvitePlayers;
 	std::shared_ptr<CButton> buttonOpenGlobalLobby;
+
+	std::shared_ptr<PvPBox> pvpBox;
 public:
 
 	bool showChat;
@@ -136,6 +143,35 @@ public:
 	void addNewMessage(const std::string & text);
 };
 
+class PvPBox : public CIntObject
+{
+	std::shared_ptr<FilledTexturePlayerColored> backgroundTexture;
+	std::shared_ptr<TransparentFilledRectangle> backgroundBorder;
+	
+	std::shared_ptr<TownSelector> townSelector;
+
+	std::shared_ptr<CButton> buttonFlipCoin;
+	std::shared_ptr<CButton> buttonRandomTown;
+	std::shared_ptr<CButton> buttonRandomTownVs;
+public:
+	PvPBox(const Rect & rect);
+};
+
+class TownSelector : public CIntObject
+{
+	std::map<FactionID, std::shared_ptr<CAnimImage>> towns;
+	std::map<FactionID, std::shared_ptr<LRClickableArea>> townsArea;
+	std::shared_ptr<CSlider> slider;
+
+	void sliderMove(int slidPos);
+	void updateListItems();
+
+public:
+	std::map<FactionID, bool> townsEnabled;
+
+	TownSelector(const Point & loc);
+};
+
 class CFlagBox : public CIntObject
 {
 	std::shared_ptr<CAnimation> iconsTeamFlags;

+ 1 - 0
lib/networkPacks/NetPackVisitor.h

@@ -172,6 +172,7 @@ public:
 	virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) {}
 	virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {}
 	virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {}
+	virtual void visitLobbyPvPAction(LobbyPvPAction & pack) {}
 };
 
 VCMI_LIB_NAMESPACE_END

+ 5 - 0
lib/networkPacks/NetPacksLib.cpp

@@ -813,6 +813,11 @@ void LobbyShowMessage::visitTyped(ICPackVisitor & visitor)
 	visitor.visitLobbyShowMessage(*this);
 }
 
+void LobbyPvPAction::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitLobbyPvPAction(*this);
+}
+
 void SetResources::applyGs(CGameState * gs) const
 {
 	assert(player.isValidPlayer());

+ 2 - 2
lib/networkPacks/PacksForClient.h

@@ -73,7 +73,7 @@ struct DLL_LINKAGE PackageApplied : public CPackForClient
 
 struct DLL_LINKAGE SystemMessage : public CPackForClient
 {
-	explicit SystemMessage(std::string Text)
+	explicit SystemMessage(MetaString Text)
 		: text(std::move(Text))
 	{
 	}
@@ -81,7 +81,7 @@ struct DLL_LINKAGE SystemMessage : public CPackForClient
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
-	std::string text;
+	MetaString text;
 
 	template <typename Handler> void serialize(Handler & h)
 	{

+ 20 - 2
lib/networkPacks/PacksForLobby.h

@@ -11,6 +11,7 @@
 
 #include "StartInfo.h"
 #include "NetPacksBase.h"
+#include "../MetaString.h"
 
 class CServerHandler;
 class CVCMIServer;
@@ -73,7 +74,7 @@ struct DLL_LINKAGE LobbyClientDisconnected : public CLobbyPackToPropagate
 struct DLL_LINKAGE LobbyChatMessage : public CLobbyPackToPropagate
 {
 	std::string playerName;
-	std::string message;
+	MetaString message;
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -333,7 +334,7 @@ struct DLL_LINKAGE LobbyForceSetPlayer : public CLobbyPackToServer
 
 struct DLL_LINKAGE LobbyShowMessage : public CLobbyPackToPropagate
 {
-	std::string message;
+	MetaString message;
 	
 	void visitTyped(ICPackVisitor & visitor) override;
 	
@@ -343,4 +344,21 @@ struct DLL_LINKAGE LobbyShowMessage : public CLobbyPackToPropagate
 	}
 };
 
+struct DLL_LINKAGE LobbyPvPAction : public CLobbyPackToServer
+{
+	enum EAction : ui8 {
+		NONE, COIN, RANDOM_TOWN, RANDOM_TOWN_VS
+	} action = NONE;
+	std::vector<FactionID> bannedTowns;
+
+
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & action;
+		h & bannedTowns;
+	}
+};
+
 VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/registerTypes/RegisterTypesLobbyPacks.h

@@ -34,6 +34,7 @@ void registerTypesLobbyPacks(Serializer &s)
 	s.template registerType<CLobbyPackToPropagate, LobbyClientConnected>();
 	s.template registerType<CLobbyPackToPropagate, LobbyClientDisconnected>();
 	s.template registerType<CLobbyPackToPropagate, LobbyChatMessage>();
+	s.template registerType<CLobbyPackToServer, LobbyPvPAction>();
 	// Only host client send
 	s.template registerType<CLobbyPackToPropagate, LobbyGuiAction>();
 	s.template registerType<CLobbyPackToPropagate, LobbyLoadProgress>();

+ 19 - 4
server/CVCMIServer.cpp

@@ -16,6 +16,7 @@
 #include "processors/PlayerMessageProcessor.h"
 
 #include "../lib/CHeroHandler.h"
+#include "../lib/MetaString.h"
 #include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
 #include "../lib/serializer/CMemorySerializer.h"
 #include "../lib/serializer/Connection.h"
@@ -378,23 +379,37 @@ void CVCMIServer::announcePack(std::unique_ptr<CPackForLobby> pack)
 	applier->getApplier(CTypeList::getInstance().getTypeID(pack.get()))->applyOnServerAfter(this, pack.get());
 }
 
-void CVCMIServer::announceMessage(const std::string & txt)
+void CVCMIServer::announceMessage(MetaString txt)
 {
-	logNetwork->info("Show message: %s", txt);
+	logNetwork->info("Show message: %s", txt.toString());
 	auto cm = std::make_unique<LobbyShowMessage>();
 	cm->message = txt;
 	announcePack(std::move(cm));
 }
 
-void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName)
+void CVCMIServer::announceMessage(const std::string & txt)
+{
+	MetaString str;
+	str.appendRawString(txt);
+	announceMessage(str);
+}
+
+void CVCMIServer::announceTxt(MetaString txt, const std::string & playerName)
 {
-	logNetwork->info("%s says: %s", playerName, txt);
+	logNetwork->info("%s says: %s", playerName, txt.toString());
 	auto cm = std::make_unique<LobbyChatMessage>();
 	cm->playerName = playerName;
 	cm->message = txt;
 	announcePack(std::move(cm));
 }
 
+void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName)
+{
+	MetaString str;
+	str.appendRawString(txt);
+	announceTxt(str, playerName);
+}
+
 bool CVCMIServer::passHost(int toConnectionId)
 {
 	for(auto activeConnection : activeConnections)

+ 3 - 0
server/CVCMIServer.h

@@ -23,6 +23,7 @@ struct StartInfo;
 struct LobbyInfo;
 struct PlayerSettings;
 class PlayerColor;
+class MetaString;
 
 template<typename T> class CApplier;
 
@@ -89,6 +90,7 @@ public:
 	void announcePack(std::unique_ptr<CPackForLobby> pack);
 	bool passHost(int toConnectionId);
 
+	void announceTxt(MetaString txt, const std::string & playerName = "system");
 	void announceTxt(const std::string & txt, const std::string & playerName = "system");
 
 	void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const;
@@ -98,6 +100,7 @@ public:
 	void clientDisconnected(std::shared_ptr<CConnection> c);
 	void reconnectPlayer(int connId);
 
+	void announceMessage(MetaString txt);
 	void announceMessage(const std::string & txt);
 
 	void handleReceivedPack(std::unique_ptr<CPackForLobby> pack);

+ 2 - 0
server/LobbyNetPackVisitors.h

@@ -38,6 +38,7 @@ public:
 	void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override;
 	void visitLobbyChatMessage(LobbyChatMessage & pack) override;
 	void visitLobbyGuiAction(LobbyGuiAction & pack) override;
+	void visitLobbyPvPAction(LobbyPvPAction & pack) override;
 };
 
 class ApplyOnServerAfterAnnounceNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
@@ -93,4 +94,5 @@ public:
 	void visitLobbySetSimturns(LobbySetSimturns & pack) override;
 	void visitLobbySetDifficulty(LobbySetDifficulty & pack) override;
 	void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override;
+	void visitLobbyPvPAction(LobbyPvPAction & pack) override;
 };

+ 54 - 0
server/NetPacksLobbyServer.cpp

@@ -18,6 +18,9 @@
 
 #include "../lib/campaign/CampaignState.h"
 #include "../lib/serializer/Connection.h"
+#include "../lib/mapping/CMapInfo.h"
+#include "../lib/mapping/CMapHeader.h"
+#include "../lib/CTownHandler.h"
 
 void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack)
 {
@@ -366,3 +369,54 @@ void ApplyOnServerNetPackVisitor::visitLobbyForceSetPlayer(LobbyForceSetPlayer &
 	srv.si->playerInfos[pack.targetPlayerColor].connectedPlayerIDs.insert(pack.targetConnectedPlayer);
 	result = true;
 }
+
+
+void ClientPermissionsCheckerNetPackVisitor::visitLobbyPvPAction(LobbyPvPAction & pack)
+{
+	result = true;
+}
+
+void ApplyOnServerNetPackVisitor::visitLobbyPvPAction(LobbyPvPAction & pack)
+{
+	std::vector<FactionID> allowedTowns;
+
+	for (auto const & factionID : VLC->townh->getDefaultAllowed())
+		if(std::find(pack.bannedTowns.begin(), pack.bannedTowns.end(), factionID) == pack.bannedTowns.end())
+			allowedTowns.push_back(factionID);
+
+	std::vector<FactionID> randomFaction1;
+	std::sample(allowedTowns.begin(), allowedTowns.end(), std::back_inserter(randomFaction1), 1, std::mt19937{std::random_device{}()});
+	std::vector<FactionID> randomFaction2;
+	std::sample(allowedTowns.begin(), allowedTowns.end(), std::back_inserter(randomFaction2), 1, std::mt19937{std::random_device{}()});
+
+	MetaString txt;
+
+	switch(pack.action) {
+		case LobbyPvPAction::COIN:
+			txt.appendTextID("vcmi.lobby.pvp.coin.hover");
+			txt.appendRawString(" - " + std::to_string(std::rand()%2));
+			srv.announceTxt(txt);
+			break;
+		case LobbyPvPAction::RANDOM_TOWN:
+			if(!allowedTowns.size())
+				break;
+			txt.appendTextID("core.overview.3");
+			txt.appendRawString(" - ");
+			txt.appendTextID(VLC->townh->getById(randomFaction1[0])->getNameTextID());
+			srv.announceTxt(txt);
+			break;
+		case LobbyPvPAction::RANDOM_TOWN_VS:
+			if(!allowedTowns.size())
+				break;
+			txt.appendTextID("core.overview.3");
+			txt.appendRawString(" - ");
+			txt.appendTextID(VLC->townh->getById(randomFaction1[0])->getNameTextID());
+			txt.appendRawString(" ");
+			txt.appendTextID("vcmi.lobby.pvp.versus");
+			txt.appendRawString(" ");
+			txt.appendTextID(VLC->townh->getById(randomFaction2[0])->getNameTextID());
+			srv.announceTxt(txt);
+			break;
+	}
+	result = true;
+}

+ 21 - 3
server/processors/PlayerMessageProcessor.cpp

@@ -42,7 +42,11 @@ void PlayerMessageProcessor::playerMessage(PlayerColor player, const std::string
 	if (handleCheatCode(message, player, currObj))
 	{
 		if(!gameHandler->getPlayerSettings(player)->isControlledByAI())
-			broadcastSystemMessage(VLC->generaltexth->allTexts[260]);
+		{
+			MetaString txt;
+			txt.appendLocalString(EMetaText::GENERAL_TXT, 260);
+			broadcastSystemMessage(txt);
+		}			
 
 		if(!player.isSpectator())
 			gameHandler->checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
@@ -614,20 +618,34 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla
 		callbacks.at(cheatName)();
 }
 
-void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message)
+void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, MetaString message)
 {
 	SystemMessage sm;
 	sm.text = message;
 	connection->sendPack(&sm);
 }
 
-void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message)
+void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message)
+{
+	MetaString str;
+	str.appendRawString(message);
+	sendSystemMessage(connection, str);
+}
+
+void PlayerMessageProcessor::broadcastSystemMessage(MetaString message)
 {
 	SystemMessage sm;
 	sm.text = message;
 	gameHandler->sendToAllClients(&sm);
 }
 
+void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message)
+{
+	MetaString str;
+	str.appendRawString(message);
+	broadcastSystemMessage(str);
+}
+
 void PlayerMessageProcessor::broadcastMessage(PlayerColor playerSender, const std::string & message)
 {
 	PlayerMessageClient temp_message(playerSender, message);

+ 3 - 0
server/processors/PlayerMessageProcessor.h

@@ -15,6 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CGHeroInstance;
 class CGTownInstance;
 class CConnection;
+class MetaString;
 VCMI_LIB_NAMESPACE_END
 
 class CGameHandler;
@@ -51,9 +52,11 @@ public:
 	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, MetaString message);
 	void sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message);
 
 	/// Send message to all players with "System" as sender
+	void broadcastSystemMessage(MetaString message);
 	void broadcastSystemMessage(const std::string & message);
 
 	/// Send message from specific player to all other players