mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Implemented join room dialog
This commit is contained in:
		| @@ -103,10 +103,16 @@ | ||||
| 	"vcmi.lobby.room.load" : "Load Game", | ||||
| 	"vcmi.lobby.room.type" : "Room Type", | ||||
| 	"vcmi.lobby.room.mode" : "Game Mode", | ||||
| 	"vcmi.lobby.room.join" : "Join Game Room", | ||||
| 	"vcmi.lobby.room.state.public" : "Public", | ||||
| 	"vcmi.lobby.room.state.private" : "Private", | ||||
| 	"vcmi.lobby.room.state.busy" : "In Game", | ||||
| 	"vcmi.lobby.room.state.invited" : "Invited", | ||||
| 	"vcmi.lobby.mod.state.compatible" : "Compatible", | ||||
| 	"vcmi.lobby.mod.state.disabled" : "Disabled", | ||||
| 	"vcmi.lobby.mod.state.version" : "Version mismatch", | ||||
| 	"vcmi.lobby.mod.state.excessive" : "Excessive", | ||||
| 	"vcmi.lobby.mod.state.missing" : "Not installed", | ||||
|  | ||||
| 	"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.", | ||||
|   | ||||
| @@ -98,6 +98,7 @@ set(client_SRCS | ||||
| 	globalLobby/GlobalLobbyClient.cpp | ||||
| 	globalLobby/GlobalLobbyInviteWindow.cpp | ||||
| 	globalLobby/GlobalLobbyLoginWindow.cpp | ||||
| 	globalLobby/GlobalLobbyRoomWindow.cpp | ||||
| 	globalLobby/GlobalLobbyServerSetup.cpp | ||||
| 	globalLobby/GlobalLobbyWidget.cpp | ||||
| 	globalLobby/GlobalLobbyWindow.cpp | ||||
| @@ -288,6 +289,7 @@ set(client_HEADERS | ||||
| 	globalLobby/GlobalLobbyDefines.h | ||||
| 	globalLobby/GlobalLobbyInviteWindow.h | ||||
| 	globalLobby/GlobalLobbyLoginWindow.h | ||||
| 	globalLobby/GlobalLobbyRoomWindow.h | ||||
| 	globalLobby/GlobalLobbyServerSetup.h | ||||
| 	globalLobby/GlobalLobbyWidget.h | ||||
| 	globalLobby/GlobalLobbyWindow.h | ||||
|   | ||||
| @@ -432,6 +432,17 @@ const std::vector<GlobalLobbyRoom> & GlobalLobbyClient::getMatchesHistory() cons | ||||
| 	return matchesHistory; | ||||
| } | ||||
|  | ||||
| const GlobalLobbyRoom & GlobalLobbyClient::getActiveRoomByName(const std::string & roomUUID) const | ||||
| { | ||||
| 	for (auto const & room : activeRooms) | ||||
| 	{ | ||||
| 		if (room.gameRoomID == roomUUID) | ||||
| 			return room; | ||||
| 	} | ||||
|  | ||||
| 	throw std::out_of_range("Failed to find room with UUID of " + roomUUID); | ||||
| } | ||||
|  | ||||
| const std::vector<GlobalLobbyChannelMessage> & GlobalLobbyClient::getChannelHistory(const std::string & channelType, const std::string & channelName) const | ||||
| { | ||||
| 	static const std::vector<GlobalLobbyChannelMessage> emptyVector; | ||||
|   | ||||
| @@ -73,6 +73,9 @@ public: | ||||
| 	const std::vector<GlobalLobbyRoom> & getMatchesHistory() const; | ||||
| 	const std::vector<GlobalLobbyChannelMessage> & getChannelHistory(const std::string & channelType, const std::string & channelName) const; | ||||
|  | ||||
| 	/// Returns active room by ID. Throws out-of-range on failure | ||||
| 	const GlobalLobbyRoom & getActiveRoomByName(const std::string & roomUUID) const; | ||||
|  | ||||
| 	const std::string & getAccountID() const; | ||||
| 	const std::string & getAccountCookie() const; | ||||
| 	const std::string & getAccountDisplayName() const; | ||||
|   | ||||
							
								
								
									
										147
									
								
								client/globalLobby/GlobalLobbyRoomWindow.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								client/globalLobby/GlobalLobbyRoomWindow.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| /* | ||||
|  * GlobalLobbyRoomWindow.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 "GlobalLobbyRoomWindow.h" | ||||
|  | ||||
| #include "GlobalLobbyClient.h" | ||||
| #include "GlobalLobbyDefines.h" | ||||
| #include "GlobalLobbyWindow.h" | ||||
|  | ||||
| #include "../CGameInfo.h" | ||||
| #include "../CServerHandler.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../mainmenu/CMainMenu.h" | ||||
| #include "../widgets/Buttons.h" | ||||
| #include "../widgets/Images.h" | ||||
| #include "../widgets/TextControls.h" | ||||
| #include "../widgets/GraphicalPrimitiveCanvas.h" | ||||
| #include "../widgets/ObjectLists.h" | ||||
|  | ||||
| #include "../../lib/CConfigHandler.h" | ||||
| #include "../../lib/CGeneralTextHandler.h" | ||||
| #include "../../lib/MetaString.h" | ||||
| #include "../../lib/VCMI_Lib.h" | ||||
| #include "../../lib/modding/CModHandler.h" | ||||
| #include "../../lib/modding/CModInfo.h" | ||||
|  | ||||
| GlobalLobbyRoomAccountCard::GlobalLobbyRoomAccountCard(const GlobalLobbyAccount & accountDescription) | ||||
| { | ||||
| 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; | ||||
| 	pos.w = 130; | ||||
| 	pos.h = 40; | ||||
| 	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1); | ||||
| 	labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName); | ||||
| 	labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status); | ||||
| } | ||||
|  | ||||
| GlobalLobbyRoomModCard::GlobalLobbyRoomModCard(const GlobalLobbyRoomModInfo & modInfo) | ||||
| { | ||||
| 	const std::map<ModVerificationStatus, std::string> statusToString = { | ||||
| 		{ ModVerificationStatus::NOT_INSTALLED, "missing" }, | ||||
| 		{ ModVerificationStatus::DISABLED, "disabled" }, | ||||
| 		{ ModVerificationStatus::EXCESSIVE, "excessive" }, | ||||
| 		{ ModVerificationStatus::VERSION_MISMATCH, "version" }, | ||||
| 		{ ModVerificationStatus::FULL_MATCH, "compatible" } | ||||
| 	}; | ||||
|  | ||||
| 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; | ||||
| 	pos.w = 200; | ||||
| 	pos.h = 40; | ||||
| 	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1); | ||||
|  | ||||
| 	labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, modInfo.modName); | ||||
| 	labelVersion = std::make_shared<CLabel>(195, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, modInfo.version); | ||||
| 	labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.mod.state." + statusToString.at(modInfo.status))); | ||||
| } | ||||
|  | ||||
| GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const std::string & roomUUID) | ||||
| 	: CWindowObject(BORDERED) | ||||
| 	, roomUUID(roomUUID) | ||||
| 	, window(window) | ||||
| { | ||||
| 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; | ||||
|  | ||||
| 	pos.w = 400; | ||||
| 	pos.h = 400; | ||||
|  | ||||
| 	GlobalLobbyRoom roomDescription = CSH->getGlobalLobby().getActiveRoomByName(roomUUID); | ||||
| 	for (auto const & modEntry : ModVerificationInfo::verifyListAgainstLocalMods(roomDescription.modList)) | ||||
| 	{ | ||||
| 		GlobalLobbyRoomModInfo modInfo; | ||||
| 		modInfo.status = modEntry.second; | ||||
| 		modInfo.version = roomDescription.modList.at(modEntry.first).version.toString(); | ||||
| 		if (modEntry.second == ModVerificationStatus::NOT_INSTALLED) | ||||
| 			modInfo.modName = roomDescription.modList.at(modEntry.first).name; | ||||
| 		else | ||||
| 			modInfo.modName = CGI->modh->getModInfo(modEntry.first).getVerificationInfo().name; | ||||
|  | ||||
| 		modVerificationList.push_back(modInfo); | ||||
| 	} | ||||
|  | ||||
| 	bool gameStarted = roomDescription.statusID != "public" && roomDescription.statusID != "private"; | ||||
| 	bool publicRoom = roomDescription.statusID == "public"; | ||||
| 	bool hasInvite = CSH->getGlobalLobby().isInvitedToRoom(roomDescription.gameRoomID); | ||||
| 	bool canJoinRoom = (publicRoom || hasInvite) && !gameStarted && !CSH->inGame(); | ||||
|  | ||||
| 	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); | ||||
| 	labelTitle = std::make_shared<CLabel>( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.join")); | ||||
| 	labelSubtitle = std::make_shared<CLabel>( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, "Game room hosted by AccountName"); | ||||
|  | ||||
| 	labelDescription = std::make_shared<CLabel>( 20, 60, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description); | ||||
| 	labelVersion = std::make_shared<CLabel>( 20, 80, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, "Game version: " + roomDescription.gameVersion); | ||||
|  | ||||
| 	buttonJoin = std::make_shared<CButton>(Point(10, 360), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onJoin(); }); | ||||
| 	buttonClose = std::make_shared<CButton>(Point(100, 360), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); | ||||
|  | ||||
| 	if (canJoinRoom) | ||||
| 		labelJoinStatus = std::make_shared<CLabel>( 20, 300, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, "Join Room?"); | ||||
| 	else | ||||
| 		labelJoinStatus = std::make_shared<CLabel>( 20, 300, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, "Unable to join room!"); | ||||
|  | ||||
|  | ||||
| 	const auto & createAccountCardCallback = [participants = roomDescription.participants](size_t index) -> std::shared_ptr<CIntObject> | ||||
| 	{ | ||||
| 		if(index < participants.size()) | ||||
| 			return std::make_shared<GlobalLobbyRoomAccountCard>(participants[index]); | ||||
| 		return std::make_shared<CIntObject>(); | ||||
| 	}; | ||||
|  | ||||
| 	accountListBackground = std::make_shared<TransparentFilledRectangle>(Rect(8, 98, 150, 180), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1); | ||||
| 	accountList = std::make_shared<CListBox>(createAccountCardCallback, Point(10, 116), Point(0, 40), 4, roomDescription.participants.size(), 0, 1 | 4, Rect(130, 0, 160, 160)); | ||||
| 	accountList->setRedrawParent(true); | ||||
| 	accountListTitle = std::make_shared<CLabel>( 12, 109, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, "Players in room"); | ||||
|  | ||||
| 	const auto & createModCardCallback = [this](size_t index) -> std::shared_ptr<CIntObject> | ||||
| 	{ | ||||
| 		if(index < modVerificationList.size()) | ||||
| 			return std::make_shared<GlobalLobbyRoomModCard>(modVerificationList[index]); | ||||
| 		return std::make_shared<CIntObject>(); | ||||
| 	}; | ||||
|  | ||||
| 	modListBackground = std::make_shared<TransparentFilledRectangle>(Rect(178, 48, 220, 340), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1); | ||||
| 	modList = std::make_shared<CListBox>(createModCardCallback, Point(180, 66), Point(0, 40), 8, modVerificationList.size(), 0, 1 | 4, Rect(200, 0, 320, 320)); | ||||
| 	modList->setRedrawParent(true); | ||||
| 	modListTitle = std::make_shared<CLabel>( 182, 59, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, "Used mods"); | ||||
|  | ||||
| 	buttonJoin->block(!canJoinRoom); | ||||
| 	filledBackground->playerColored(PlayerColor(1)); | ||||
| 	center(); | ||||
| } | ||||
|  | ||||
| void GlobalLobbyRoomWindow::onJoin() | ||||
| { | ||||
| 	window->doJoinRoom(roomUUID); | ||||
| } | ||||
|  | ||||
| void GlobalLobbyRoomWindow::onClose() | ||||
| { | ||||
| 	close(); | ||||
| } | ||||
							
								
								
									
										86
									
								
								client/globalLobby/GlobalLobbyRoomWindow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								client/globalLobby/GlobalLobbyRoomWindow.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| /* | ||||
|  * GlobalLobbyRoomWindow.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 "../windows/CWindowObject.h" | ||||
| #include "../../lib/modding/ModVerificationInfo.h" | ||||
|  | ||||
| class CLabel; | ||||
| class CTextBox; | ||||
| class FilledTexturePlayerColored; | ||||
| class CButton; | ||||
| class CToggleGroup; | ||||
| class GlobalLobbyWindow; | ||||
| class TransparentFilledRectangle; | ||||
| class CListBox; | ||||
|  | ||||
| struct GlobalLobbyAccount; | ||||
| struct GlobalLobbyRoom; | ||||
| struct ModVerificationInfo; | ||||
|  | ||||
| struct GlobalLobbyRoomModInfo | ||||
| { | ||||
| 	std::string modName; | ||||
| 	std::string version; | ||||
| 	ModVerificationStatus status; | ||||
| }; | ||||
|  | ||||
| class GlobalLobbyRoomAccountCard : public CIntObject | ||||
| { | ||||
| 	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay; | ||||
| 	std::shared_ptr<CLabel> labelName; | ||||
| 	std::shared_ptr<CLabel> labelStatus; | ||||
|  | ||||
| public: | ||||
| 	GlobalLobbyRoomAccountCard(const GlobalLobbyAccount & accountDescription); | ||||
| }; | ||||
|  | ||||
| class GlobalLobbyRoomModCard : public CIntObject | ||||
| { | ||||
| 	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay; | ||||
| 	std::shared_ptr<CLabel> labelName; | ||||
| 	std::shared_ptr<CLabel> labelStatus; | ||||
| 	std::shared_ptr<CLabel> labelVersion; | ||||
|  | ||||
| public: | ||||
| 	GlobalLobbyRoomModCard(const GlobalLobbyRoomModInfo & modInfo); | ||||
| }; | ||||
|  | ||||
| class GlobalLobbyRoomWindow : public CWindowObject | ||||
| { | ||||
| 	std::vector<GlobalLobbyRoomModInfo> modVerificationList; | ||||
| 	GlobalLobbyWindow * window; | ||||
| 	std::string roomUUID; | ||||
|  | ||||
| 	std::shared_ptr<FilledTexturePlayerColored> filledBackground; | ||||
| 	std::shared_ptr<CLabel> labelTitle; | ||||
| 	std::shared_ptr<CLabel> labelSubtitle; | ||||
| 	std::shared_ptr<CLabel> labelDescription; | ||||
| 	std::shared_ptr<CLabel> labelVersion; | ||||
| 	std::shared_ptr<CLabel> labelJoinStatus; | ||||
|  | ||||
| 	std::shared_ptr<CLabel> accountListTitle; | ||||
| 	std::shared_ptr<CLabel> modListTitle; | ||||
|  | ||||
| 	std::shared_ptr<TransparentFilledRectangle> accountListBackground; | ||||
| 	std::shared_ptr<TransparentFilledRectangle> modListBackground; | ||||
|  | ||||
| 	std::shared_ptr<CListBox> accountList; | ||||
| 	std::shared_ptr<CListBox> modList; | ||||
|  | ||||
| 	std::shared_ptr<CButton> buttonJoin; | ||||
| 	std::shared_ptr<CButton> buttonClose; | ||||
|  | ||||
| 	void onJoin(); | ||||
| 	void onClose(); | ||||
|  | ||||
| public: | ||||
| 	GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const std::string & roomUUID); | ||||
| }; | ||||
| @@ -13,6 +13,7 @@ | ||||
|  | ||||
| #include "GlobalLobbyClient.h" | ||||
| #include "GlobalLobbyWindow.h" | ||||
| #include "GlobalLobbyRoomWindow.h" | ||||
|  | ||||
| #include "../CGameInfo.h" | ||||
| #include "../CMusicHandler.h" | ||||
| @@ -209,17 +210,13 @@ GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const | ||||
| } | ||||
|  | ||||
| GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription) | ||||
| 	: roomUUID(roomDescription.gameRoomID) | ||||
| 	, window(window) | ||||
| { | ||||
| 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; | ||||
| 	addUsedEvents(LCLICK); | ||||
|  | ||||
| 	const auto & onJoinClicked = [window, roomID = roomDescription.gameRoomID]() | ||||
| 	{ | ||||
| 		window->doJoinRoom(roomID); | ||||
| 	}; | ||||
|  | ||||
| 	bool publicRoom = roomDescription.statusID == "public"; | ||||
| 	bool hasInvite = CSH->getGlobalLobby().isInvitedToRoom(roomDescription.gameRoomID); | ||||
| 	bool canJoin = publicRoom || hasInvite; | ||||
|  | ||||
| 	auto roomSizeText = MetaString::createFromRawString("%d/%d"); | ||||
| 	roomSizeText.replaceNumber(roomDescription.participants.size()); | ||||
| @@ -244,12 +241,11 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa | ||||
| 	labelRoomSize = std::make_shared<CLabel>(178, 10, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomSizeText.toString()); | ||||
| 	labelRoomStatus = std::make_shared<CLabel>(190, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomStatusText.toString()); | ||||
| 	iconRoomSize = std::make_shared<CPicture>(ImagePath::builtin("lobby/iconPlayer"), Point(180, 5)); | ||||
| } | ||||
|  | ||||
| 	if(!CSH->inGame() && canJoin) | ||||
| 	{ | ||||
| 		buttonJoin = std::make_shared<CButton>(Point(194, 4), AnimationPath::builtin("lobbyJoinRoom"), CButton::tooltip(), onJoinClicked); | ||||
| 		buttonJoin->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/iconEnter"))); | ||||
| 	} | ||||
| void GlobalLobbyRoomCard::clickPressed(const Point & cursorPosition) | ||||
| { | ||||
| 	GH.windows().createAndPushWindow<GlobalLobbyRoomWindow>(window, roomUUID); | ||||
| } | ||||
|  | ||||
| GlobalLobbyChannelCard::GlobalLobbyChannelCard(GlobalLobbyWindow * window, const std::string & channelName) | ||||
|   | ||||
| @@ -69,6 +69,9 @@ public: | ||||
|  | ||||
| class GlobalLobbyRoomCard : public CIntObject | ||||
| { | ||||
| 	GlobalLobbyWindow * window; | ||||
| 	std::string roomUUID; | ||||
|  | ||||
| 	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay; | ||||
| 	std::shared_ptr<CLabel> labelName; | ||||
| 	std::shared_ptr<CLabel> labelRoomSize; | ||||
| @@ -77,6 +80,7 @@ class GlobalLobbyRoomCard : public CIntObject | ||||
| 	std::shared_ptr<CButton> buttonJoin; | ||||
| 	std::shared_ptr<CPicture> iconRoomSize; | ||||
|  | ||||
| 	void clickPressed(const Point & cursorPosition) override; | ||||
| public: | ||||
| 	GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription); | ||||
| }; | ||||
|   | ||||
| @@ -96,7 +96,7 @@ | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 					}, | ||||
| 					"status" : | ||||
| 					{ | ||||
| 						"type" : "string", | ||||
|   | ||||
| @@ -109,9 +109,9 @@ void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const | ||||
| 		throw std::domain_error(VLC->generaltexth->translate("core.genrltxt.529")); | ||||
| 	 | ||||
| 	auto missingMods = CMapService::verifyMapHeaderMods(*mi->mapHeader); | ||||
| 	ModIncompatibility::ModListWithVersion modList; | ||||
| 	ModIncompatibility::ModList modList; | ||||
| 	for(const auto & m : missingMods) | ||||
| 		modList.push_back({m.second.name, m.second.version.toString()}); | ||||
| 		modList.push_back(m.second.name); | ||||
| 	 | ||||
| 	if(!modList.empty()) | ||||
| 		throw ModIncompatibility(modList); | ||||
|   | ||||
| @@ -32,85 +32,26 @@ const ModVerificationInfo & ActiveModsInSaveList::getVerificationInfo(TModID mod | ||||
| 	return VLC->modh->getModInfo(mod).getVerificationInfo(); | ||||
| } | ||||
|  | ||||
| void ActiveModsInSaveList::verifyActiveMods(const std::vector<std::pair<TModID, ModVerificationInfo>> & modList) | ||||
| void ActiveModsInSaveList::verifyActiveMods(const std::map<TModID, ModVerificationInfo> & modList) | ||||
| { | ||||
| 	auto searchVerificationInfo = [&modList](const TModID & m) -> const ModVerificationInfo* | ||||
| 	auto comparison = ModVerificationInfo::verifyListAgainstLocalMods(modList); | ||||
| 	std::vector<TModID> missingMods; | ||||
| 	std::vector<TModID> excessiveMods; | ||||
|  | ||||
| 	for (auto const & compared : comparison) | ||||
| 	{ | ||||
| 		for(auto & i : modList) | ||||
| 			if(i.first == m) | ||||
| 				return &i.second; | ||||
| 		return nullptr; | ||||
| 	}; | ||||
| 		if (compared.second == ModVerificationStatus::NOT_INSTALLED) | ||||
| 			missingMods.push_back(modList.at(compared.first).name); | ||||
|  | ||||
| 	std::vector<TModID> missingMods, excessiveMods; | ||||
| 	ModIncompatibility::ModListWithVersion missingModsResult; | ||||
| 	ModIncompatibility::ModList excessiveModsResult; | ||||
| 		if (compared.second == ModVerificationStatus::DISABLED) | ||||
| 			missingMods.push_back(VLC->modh->getModInfo(compared.first).getVerificationInfo().name); | ||||
|  | ||||
| 	for(const auto & m : VLC->modh->getActiveMods()) | ||||
| 	{ | ||||
| 		if(searchVerificationInfo(m)) | ||||
| 			continue; | ||||
|  | ||||
| 		//TODO: support actual disabling of these mods | ||||
| 		if(VLC->modh->getModInfo(m).checkModGameplayAffecting()) | ||||
| 			excessiveMods.push_back(m); | ||||
| 		if (compared.second == ModVerificationStatus::EXCESSIVE) | ||||
| 			excessiveMods.push_back(modList.at(compared.first).name); | ||||
| 	} | ||||
|  | ||||
| 	for(const auto & infoPair : modList) | ||||
| 	{ | ||||
| 		auto & remoteModId = infoPair.first; | ||||
| 		auto & remoteModInfo = infoPair.second; | ||||
|  | ||||
| 		bool modAffectsGameplay = remoteModInfo.impactsGameplay; | ||||
| 		//parent mod affects gameplay if child affects too | ||||
| 		for(const auto & subInfoPair : modList) | ||||
| 			modAffectsGameplay |= (subInfoPair.second.impactsGameplay && subInfoPair.second.parent == remoteModId); | ||||
|  | ||||
| 		if(!vstd::contains(VLC->modh->getAllMods(), remoteModId)) | ||||
| 		{ | ||||
| 			if(modAffectsGameplay) | ||||
| 				missingMods.push_back(remoteModId); //mod is not installed | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		auto & localModInfo = VLC->modh->getModInfo(remoteModId).getVerificationInfo(); | ||||
| 		modAffectsGameplay |= VLC->modh->getModInfo(remoteModId).checkModGameplayAffecting(); | ||||
| 		bool modVersionCompatible = localModInfo.version.isNull() | ||||
| 			|| remoteModInfo.version.isNull() | ||||
| 			|| localModInfo.version.compatible(remoteModInfo.version); | ||||
| 		bool modLocalyEnabled = vstd::contains(VLC->modh->getActiveMods(), remoteModId); | ||||
|  | ||||
| 		if(modVersionCompatible && modAffectsGameplay && modLocalyEnabled) | ||||
| 			continue; | ||||
|  | ||||
| 		if(modAffectsGameplay) | ||||
| 			missingMods.push_back(remoteModId); //incompatible mod impacts gameplay | ||||
| 	} | ||||
|  | ||||
| 	//filter mods | ||||
| 	for(auto & m : missingMods) | ||||
| 	{ | ||||
| 		if(auto * vInfo = searchVerificationInfo(m)) | ||||
| 		{ | ||||
| 			assert(vInfo->parent != m); | ||||
| 			if(!vInfo->parent.empty() && vstd::contains(missingMods, vInfo->parent)) | ||||
| 				continue; | ||||
| 			missingModsResult.push_back({vInfo->name, vInfo->version.toString()}); | ||||
| 		} | ||||
| 	} | ||||
| 	for(auto & m : excessiveMods) | ||||
| 	{ | ||||
| 		auto & vInfo = VLC->modh->getModInfo(m).getVerificationInfo(); | ||||
| 		assert(vInfo.parent != m); | ||||
| 		if(!vInfo.parent.empty() && vstd::contains(excessiveMods, vInfo.parent)) | ||||
| 			continue; | ||||
| 		excessiveModsResult.push_back(vInfo.name); | ||||
| 	} | ||||
|  | ||||
| 	if(!missingModsResult.empty() || !excessiveModsResult.empty()) | ||||
| 		throw ModIncompatibility(missingModsResult, excessiveModsResult); | ||||
|  | ||||
| 	//TODO: support actual enabling of required mods | ||||
| 	if(!missingMods.empty() || !excessiveMods.empty()) | ||||
| 		throw ModIncompatibility(missingMods, excessiveMods); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class ActiveModsInSaveList | ||||
| 	const ModVerificationInfo & getVerificationInfo(TModID mod); | ||||
|  | ||||
| 	/// Checks whether provided mod list is compatible with current VLC and throws on failure | ||||
| 	void verifyActiveMods(const std::vector<std::pair<TModID, ModVerificationInfo>> & modList); | ||||
| 	void verifyActiveMods(const std::map<TModID, ModVerificationInfo> & modList); | ||||
| public: | ||||
| 	template <typename Handler> void serialize(Handler &h) | ||||
| 	{ | ||||
| @@ -36,11 +36,11 @@ public: | ||||
| 			std::vector<TModID> saveActiveMods; | ||||
| 			h & saveActiveMods; | ||||
|  | ||||
| 			std::vector<std::pair<TModID, ModVerificationInfo>> saveModInfos(saveActiveMods.size()); | ||||
| 			std::map<TModID, ModVerificationInfo> saveModInfos; | ||||
| 			for(int i = 0; i < saveActiveMods.size(); ++i) | ||||
| 			{ | ||||
| 				saveModInfos[i].first = saveActiveMods[i]; | ||||
| 				h & saveModInfos[i].second; | ||||
| 				ModVerificationInfo data; | ||||
| 				h & saveModInfos[saveActiveMods[i]]; | ||||
| 			} | ||||
|  | ||||
| 			verifyActiveMods(saveModInfos); | ||||
|   | ||||
| @@ -59,6 +59,16 @@ std::string CModVersion::toString() const | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| bool CModVersion::operator ==(const CModVersion & other) const | ||||
| { | ||||
| 	return major == other.major && minor == other.minor && patch == other.patch; | ||||
| } | ||||
|  | ||||
| bool CModVersion::operator !=(const CModVersion & other) const | ||||
| { | ||||
| 	return major != other.major || minor != other.minor || patch != other.patch; | ||||
| } | ||||
|  | ||||
| bool CModVersion::compatible(const CModVersion & other, bool checkMinor, bool checkPatch) const | ||||
| { | ||||
| 	bool doCheckMinor = checkMinor && minor != Any && other.minor != Any; | ||||
|   | ||||
| @@ -35,6 +35,8 @@ struct DLL_LINKAGE CModVersion | ||||
| 	static CModVersion fromString(std::string from); | ||||
| 	std::string toString() const; | ||||
|  | ||||
| 	bool operator !=(const CModVersion & other) const; | ||||
| 	bool operator ==(const CModVersion & other) const; | ||||
| 	bool compatible(const CModVersion & other, bool checkMinor = false, bool checkPatch = false) const; | ||||
| 	bool isNull() const; | ||||
|  | ||||
|   | ||||
| @@ -14,18 +14,17 @@ VCMI_LIB_NAMESPACE_BEGIN | ||||
| class DLL_LINKAGE ModIncompatibility: public std::exception | ||||
| { | ||||
| public: | ||||
| 	using ModListWithVersion = std::vector<std::pair<const std::string, const std::string>>; | ||||
| 	using ModList = std::vector<std::string>; | ||||
|  | ||||
| 	ModIncompatibility(const ModListWithVersion & _missingMods) | ||||
| 	ModIncompatibility(const ModList & _missingMods) | ||||
| 	{ | ||||
| 		std::ostringstream _ss; | ||||
| 		for(const auto & m : _missingMods) | ||||
| 			_ss << m.first << ' ' << m.second << std::endl; | ||||
| 			_ss << m << std::endl; | ||||
| 		messageMissingMods = _ss.str(); | ||||
| 	} | ||||
| 	 | ||||
| 	ModIncompatibility(const ModListWithVersion & _missingMods, ModList & _excessiveMods) | ||||
| 	ModIncompatibility(const ModList & _missingMods, ModList & _excessiveMods) | ||||
| 		: ModIncompatibility(_missingMods) | ||||
| 	{ | ||||
| 		std::ostringstream _ss; | ||||
|   | ||||
| @@ -10,7 +10,12 @@ | ||||
| #include "StdInc.h" | ||||
| #include "ModVerificationInfo.h" | ||||
|  | ||||
| #include "CModInfo.h" | ||||
| #include "CModHandler.h" | ||||
| #include "ModIncompatibility.h" | ||||
|  | ||||
| #include "../json/JsonNode.h" | ||||
| #include "../VCMI_Lib.h" | ||||
|  | ||||
| JsonNode ModVerificationInfo::jsonSerializeList(const ModCompatibilityInfo & input) | ||||
| { | ||||
| @@ -51,3 +56,55 @@ ModCompatibilityInfo ModVerificationInfo::jsonDeserializeList(const JsonNode & i | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| ModListVerificationStatus ModVerificationInfo::verifyListAgainstLocalMods(const ModCompatibilityInfo & modList) | ||||
| { | ||||
| 	ModListVerificationStatus result; | ||||
|  | ||||
| 	for(const auto & m : VLC->modh->getActiveMods()) | ||||
| 	{ | ||||
| 		if(modList.count(m)) | ||||
| 			continue; | ||||
|  | ||||
| 		if(VLC->modh->getModInfo(m).checkModGameplayAffecting()) | ||||
| 			result[m] = ModVerificationStatus::EXCESSIVE; | ||||
| 	} | ||||
|  | ||||
| 	for(const auto & infoPair : modList) | ||||
| 	{ | ||||
| 		auto & remoteModId = infoPair.first; | ||||
| 		auto & remoteModInfo = infoPair.second; | ||||
|  | ||||
| 		bool modAffectsGameplay = remoteModInfo.impactsGameplay; | ||||
| 		//parent mod affects gameplay if child affects too | ||||
| 		for(const auto & subInfoPair : modList) | ||||
| 			modAffectsGameplay |= (subInfoPair.second.impactsGameplay && subInfoPair.second.parent == remoteModId); | ||||
|  | ||||
| 		if(!vstd::contains(VLC->modh->getAllMods(), remoteModId)) | ||||
| 		{ | ||||
| 			result[remoteModId] = ModVerificationStatus::NOT_INSTALLED; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		auto & localModInfo = VLC->modh->getModInfo(remoteModId).getVerificationInfo(); | ||||
| 		modAffectsGameplay |= VLC->modh->getModInfo(remoteModId).checkModGameplayAffecting(); | ||||
|  | ||||
| 		assert(modAffectsGameplay); // such mods should not be in the list to begin with | ||||
|  | ||||
| 		if (!vstd::contains(VLC->modh->getActiveMods(), remoteModId)) | ||||
| 		{ | ||||
| 			result[remoteModId] = ModVerificationStatus::DISABLED; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if(remoteModInfo.version != localModInfo.version) | ||||
| 		{ | ||||
| 			result[remoteModId] = ModVerificationStatus::VERSION_MISMATCH; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		result[remoteModId] = ModVerificationStatus::FULL_MATCH; | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,17 @@ class JsonNode; | ||||
| struct ModVerificationInfo; | ||||
| using ModCompatibilityInfo = std::map<std::string, ModVerificationInfo>; | ||||
|  | ||||
| enum class ModVerificationStatus | ||||
| { | ||||
| 	NOT_INSTALLED, /// Mod is not installed locally | ||||
| 	DISABLED, /// Mod is installed locally but not enabled | ||||
| 	EXCESSIVE, /// Mod is enabled locally but must be disabled | ||||
| 	VERSION_MISMATCH, /// Mod is present on both sides, but has different version | ||||
| 	FULL_MATCH, /// No issues detected, everything matches | ||||
| }; | ||||
|  | ||||
| using ModListVerificationStatus = std::map<std::string, ModVerificationStatus>; | ||||
|  | ||||
| struct DLL_LINKAGE ModVerificationInfo | ||||
| { | ||||
| 	/// human-readable mod name | ||||
| @@ -36,6 +47,7 @@ struct DLL_LINKAGE ModVerificationInfo | ||||
|  | ||||
| 	static JsonNode jsonSerializeList(const ModCompatibilityInfo & input); | ||||
| 	static ModCompatibilityInfo jsonDeserializeList(const JsonNode & input); | ||||
| 	static ModListVerificationStatus verifyListAgainstLocalMods(const ModCompatibilityInfo & input); | ||||
|  | ||||
| 	template <typename Handler> | ||||
| 	void serialize(Handler & h) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user