mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Merge pull request #3776 from IvanSavenko/lobby_room_compatibility_check
Check for game version and mods compatibility in lobby
This commit is contained in:
commit
428c88d8c1
Binary file not shown.
Before Width: | Height: | Size: 145 B |
@ -99,6 +99,20 @@
|
||||
"vcmi.lobby.room.description.limit" : "Up to %d players can enter your room, including you.",
|
||||
"vcmi.lobby.invite.header" : "Invite Players",
|
||||
"vcmi.lobby.invite.notification" : "Player has invited you to their game room. You can now join their private room.",
|
||||
"vcmi.lobby.preview.title" : "Join Game Room",
|
||||
"vcmi.lobby.preview.subtitle" : "Game on %s, hosted by %s", //TL Note: 1) name of map or RMG template 2) nickname of game host
|
||||
"vcmi.lobby.preview.version" : "Game version:",
|
||||
"vcmi.lobby.preview.players" : "Players:",
|
||||
"vcmi.lobby.preview.mods" : "Used mods:",
|
||||
"vcmi.lobby.preview.title" : "Join Game Room",
|
||||
"vcmi.lobby.preview.allowed" : "Join the game room?",
|
||||
"vcmi.lobby.preview.error.header" : "Unable to join this room.",
|
||||
"vcmi.lobby.preview.error.playing" : "You need to leave your current game first.",
|
||||
"vcmi.lobby.preview.error.full" : "The room is already full.",
|
||||
"vcmi.lobby.preview.error.busy" : "The room no longer accepts new players.",
|
||||
"vcmi.lobby.preview.error.invite" : "You were not invited to this room.",
|
||||
"vcmi.lobby.preview.error.mods" : "You are using different set of mods.",
|
||||
"vcmi.lobby.preview.error.version" : "You are using different version of VCMI.",
|
||||
"vcmi.lobby.room.new" : "New Game",
|
||||
"vcmi.lobby.room.load" : "Load Game",
|
||||
"vcmi.lobby.room.type" : "Room Type",
|
||||
@ -107,6 +121,11 @@
|
||||
"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" : "Must be enabled",
|
||||
"vcmi.lobby.mod.state.version" : "Version mismatch",
|
||||
"vcmi.lobby.mod.state.excessive" : "Must be disabled",
|
||||
"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.",
|
||||
|
@ -99,6 +99,20 @@
|
||||
"vcmi.lobby.room.description.limit" : "До %d гравців можуть зайти у вашу кімнату, включаючи вас.",
|
||||
"vcmi.lobby.invite.header" : "Запросити гравців",
|
||||
"vcmi.lobby.invite.notification" : "Гравець запросив вас до своєї ігрової кімнати. Тепер ви можете приєднатися до його приватної кімнати.",
|
||||
"vcmi.lobby.preview.title" : "Join Game Room",
|
||||
"vcmi.lobby.preview.subtitle" : "Гра на %s, яку проводить %s", //TL Note: 1) name of map or RMG template 2) nickname of game host
|
||||
"vcmi.lobby.preview.version" : "Версія гри:",
|
||||
"vcmi.lobby.preview.players" : "Гравці:",
|
||||
"vcmi.lobby.preview.mods" : "Активні модифікації:",
|
||||
"vcmi.lobby.preview.title" : "Приєднатися до кімнати",
|
||||
"vcmi.lobby.preview.allowed" : "Приєднатися до цієї ігрової кімнати?",
|
||||
"vcmi.lobby.preview.error.header" : "Неможливо приєднатися до цієї кімнати.",
|
||||
"vcmi.lobby.preview.error.playing" : "Ви повинні спочатку вийти з поточної гри.",
|
||||
"vcmi.lobby.preview.error.full" : "Ця кімната вже повна.",
|
||||
"vcmi.lobby.preview.error.busy" : "Кімната більше не приймає нових гравців.",
|
||||
"vcmi.lobby.preview.error.invite" : "Ви не були запрошені до цієї кімнати.",
|
||||
"vcmi.lobby.preview.error.mods" : "Ви використовуєте інший набір модифікацій.",
|
||||
"vcmi.lobby.preview.error.version" : "Ви використовуєте іншу версію VCMI.",
|
||||
"vcmi.lobby.room.new" : "Нова гра",
|
||||
"vcmi.lobby.room.load" : "Завантажити гру",
|
||||
"vcmi.lobby.room.type" : "Тип кімнати",
|
||||
@ -107,6 +121,11 @@
|
||||
"vcmi.lobby.room.state.private" : "Приватна",
|
||||
"vcmi.lobby.room.state.busy" : "У грі",
|
||||
"vcmi.lobby.room.state.invited" : "Запрошено",
|
||||
"vcmi.lobby.mod.state.compatible" : "Сумісна",
|
||||
"vcmi.lobby.mod.state.disabled" : "Має бути увімкнена",
|
||||
"vcmi.lobby.mod.state.version" : "Розбіжність версій",
|
||||
"vcmi.lobby.mod.state.excessive" : "Має бути вимкнена",
|
||||
"vcmi.lobby.mod.state.missing" : "Не встановлена",
|
||||
|
||||
"vcmi.client.errors.invalidMap" : "{Пошкоджена карта або кампанія}\n\nНе вдалося запустити гру! Вибрана карта або кампанія може бути невірною або пошкодженою. Причина:\n%s",
|
||||
"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
|
||||
|
@ -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
|
||||
|
@ -201,6 +201,8 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
|
||||
room.hostAccountDisplayName = jsonEntry["hostAccountDisplayName"].String();
|
||||
room.description = jsonEntry["description"].String();
|
||||
room.statusID = jsonEntry["status"].String();
|
||||
room.gameVersion = jsonEntry["version"].String();
|
||||
room.modList = ModVerificationInfo::jsonDeserializeList(jsonEntry["mods"]);
|
||||
std::chrono::seconds ageSeconds (jsonEntry["ageSeconds"].Integer());
|
||||
room.startDateFormatted = TextOperations::getCurrentFormattedDateTimeLocal(-ageSeconds);
|
||||
|
||||
@ -277,7 +279,7 @@ void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json)
|
||||
{
|
||||
if (json["proxyMode"].Bool())
|
||||
{
|
||||
CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_GUEST, {});
|
||||
CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_GUEST, { CSH->getGlobalLobby().getAccountDisplayName() });
|
||||
CSH->loadMode = ELoadMode::MULTI;
|
||||
|
||||
std::string hostname = getServerHost();
|
||||
@ -430,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;
|
||||
|
@ -9,6 +9,8 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/modding/ModVerificationInfo.h"
|
||||
|
||||
struct GlobalLobbyAccount
|
||||
{
|
||||
std::string accountID;
|
||||
@ -22,8 +24,10 @@ struct GlobalLobbyRoom
|
||||
std::string hostAccountID;
|
||||
std::string hostAccountDisplayName;
|
||||
std::string description;
|
||||
std::string gameVersion;
|
||||
std::string statusID;
|
||||
std::string startDateFormatted;
|
||||
ModCompatibilityInfo modList;
|
||||
std::vector<GlobalLobbyAccount> participants;
|
||||
int playerLimit;
|
||||
};
|
||||
|
200
client/globalLobby/GlobalLobbyRoomWindow.cpp
Normal file
200
client/globalLobby/GlobalLobbyRoomWindow.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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)));
|
||||
}
|
||||
|
||||
static const std::string getJoinRoomErrorMessage(const GlobalLobbyRoom & roomDescription, const std::vector<GlobalLobbyRoomModInfo> & modVerificationList)
|
||||
{
|
||||
bool publicRoom = roomDescription.statusID == "public";
|
||||
bool privateRoom = roomDescription.statusID == "private";
|
||||
bool gameStarted = !publicRoom && !privateRoom;
|
||||
bool hasInvite = CSH->getGlobalLobby().isInvitedToRoom(roomDescription.gameRoomID);
|
||||
bool alreadyInRoom = CSH->inGame();
|
||||
|
||||
if (alreadyInRoom)
|
||||
return "vcmi.lobby.preview.error.playing";
|
||||
|
||||
if (gameStarted)
|
||||
return "vcmi.lobby.preview.error.busy";
|
||||
|
||||
if (VCMI_VERSION_STRING != roomDescription.gameVersion)
|
||||
return "vcmi.lobby.preview.error.version";
|
||||
|
||||
if (roomDescription.playerLimit == roomDescription.participants.size())
|
||||
return "vcmi.lobby.preview.error.full";
|
||||
|
||||
if (privateRoom && !hasInvite)
|
||||
return "vcmi.lobby.preview.error.invite";
|
||||
|
||||
for(const auto & mod : modVerificationList)
|
||||
{
|
||||
switch (mod.status)
|
||||
{
|
||||
case ModVerificationStatus::NOT_INSTALLED:
|
||||
case ModVerificationStatus::DISABLED:
|
||||
case ModVerificationStatus::EXCESSIVE:
|
||||
return "vcmi.lobby.preview.error.mods";
|
||||
break;
|
||||
case ModVerificationStatus::VERSION_MISMATCH:
|
||||
case ModVerificationStatus::FULL_MATCH:
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
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(const auto & modEntry : ModVerificationInfo::verifyListAgainstLocalMods(roomDescription.modList))
|
||||
{
|
||||
GlobalLobbyRoomModInfo modInfo;
|
||||
modInfo.status = modEntry.second;
|
||||
if (modEntry.second == ModVerificationStatus::EXCESSIVE)
|
||||
modInfo.version = CGI->modh->getModInfo(modEntry.first).getVerificationInfo().version.toString();
|
||||
else
|
||||
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);
|
||||
}
|
||||
|
||||
MetaString subtitleText;
|
||||
subtitleText.appendTextID("vcmi.lobby.preview.subtitle");
|
||||
subtitleText.replaceRawString(roomDescription.description);
|
||||
subtitleText.replaceRawString(roomDescription.hostAccountDisplayName);
|
||||
|
||||
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, MetaString::createFromTextID("vcmi.lobby.preview.title").toString());
|
||||
labelSubtitle = std::make_shared<CLabel>( pos.w / 2, 40, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, subtitleText.toString());
|
||||
|
||||
labelVersionTitle = std::make_shared<CLabel>( 10, 60, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.version").toString());
|
||||
labelVersionValue = std::make_shared<CLabel>( 10, 80, FONT_MEDIUM, ETextAlignment::CENTERLEFT, Colors::WHITE, 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(); });
|
||||
|
||||
MetaString joinStatusText;
|
||||
std::string errorMessage = getJoinRoomErrorMessage(roomDescription, modVerificationList);
|
||||
if (!errorMessage.empty())
|
||||
{
|
||||
joinStatusText.appendTextID("vcmi.lobby.preview.error.header");
|
||||
joinStatusText.appendRawString("\n");
|
||||
joinStatusText.appendTextID(errorMessage);
|
||||
}
|
||||
else
|
||||
joinStatusText.appendTextID("vcmi.lobby.preview.allowed");
|
||||
|
||||
labelJoinStatus = std::make_shared<CTextBox>(joinStatusText.toString(), Rect(10, 280, 150, 70), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
||||
|
||||
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, MetaString::createFromTextID("vcmi.lobby.preview.players").toString());
|
||||
|
||||
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, MetaString::createFromTextID("vcmi.lobby.preview.mods").toString());
|
||||
|
||||
buttonJoin->block(!errorMessage.empty());
|
||||
filledBackground->playerColored(PlayerColor(1));
|
||||
center();
|
||||
}
|
||||
|
||||
void GlobalLobbyRoomWindow::onJoin()
|
||||
{
|
||||
window->doJoinRoom(roomUUID);
|
||||
}
|
||||
|
||||
void GlobalLobbyRoomWindow::onClose()
|
||||
{
|
||||
close();
|
||||
}
|
89
client/globalLobby/GlobalLobbyRoomWindow.h
Normal file
89
client/globalLobby/GlobalLobbyRoomWindow.h
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
struct ModVerificationInfo;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
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> labelVersionTitle;
|
||||
std::shared_ptr<CLabel> labelVersionValue;
|
||||
std::shared_ptr<CTextBox> 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);
|
||||
};
|
@ -11,6 +11,8 @@
|
||||
#include "StdInc.h"
|
||||
#include "GlobalLobbyServerSetup.h"
|
||||
|
||||
#include "GlobalLobbyClient.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
@ -125,9 +127,9 @@ void GlobalLobbyServerSetup::onGameModeChanged(int value)
|
||||
void GlobalLobbyServerSetup::onCreate()
|
||||
{
|
||||
if(toggleGameMode->getSelected() == 0)
|
||||
CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_HOST, {});
|
||||
CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_HOST, { CSH->getGlobalLobby().getAccountDisplayName() });
|
||||
else
|
||||
CSH->resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOBBY_HOST, {});
|
||||
CSH->resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOBBY_HOST, { CSH->getGlobalLobby().getAccountDisplayName() });
|
||||
|
||||
CSH->loadMode = ELoadMode::MULTI;
|
||||
CSH->startLocalServerAndConnect(true);
|
||||
|
@ -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());
|
||||
@ -241,15 +238,14 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
|
||||
|
||||
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName);
|
||||
labelDescription = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description);
|
||||
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));
|
||||
labelRoomSize = std::make_shared<CLabel>(212, 10, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomSizeText.toString());
|
||||
labelRoomStatus = std::make_shared<CLabel>(225, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomStatusText.toString());
|
||||
iconRoomSize = std::make_shared<CPicture>(ImagePath::builtin("lobby/iconPlayer"), Point(214, 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);
|
||||
};
|
||||
|
@ -20,7 +20,7 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : [ "gameRoomID", "hostAccountID", "hostAccountDisplayName", "description", "participants", "playerLimit", "status", "ageSeconds" ],
|
||||
"required" : [ "gameRoomID", "hostAccountID", "hostAccountDisplayName", "description", "participants", "playerLimit", "status", "ageSeconds", "mods", "version" ],
|
||||
"properties" : {
|
||||
"gameRoomID" :
|
||||
{
|
||||
@ -65,6 +65,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mods" :
|
||||
{
|
||||
"type" : "array",
|
||||
"description" : "List of gameplay-affecting mods active on server",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : [ "modId", "name", "version" ],
|
||||
"properties" : {
|
||||
"modId" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Unique identifier of the mod"
|
||||
},
|
||||
"name" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Human-readable name of the mod"
|
||||
},
|
||||
"parent" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Unique ID of parent mod, only for submods"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Version of mod, as specified in mod config"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"status" :
|
||||
{
|
||||
"type" : "string",
|
||||
@ -78,6 +110,11 @@
|
||||
"maximum" : 8,
|
||||
"description" : "Maximum number of players that can join this room, including host"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Version of match server, e.g. 1.5.0"
|
||||
},
|
||||
"ageSeconds" :
|
||||
{
|
||||
"type" : "number",
|
||||
|
@ -32,6 +32,38 @@
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Version of match server, e.g. 1.5.0"
|
||||
},
|
||||
"mods" :
|
||||
{
|
||||
"type" : "array",
|
||||
"description" : "List of gameplay-affecting mods active on server",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : [ "modId", "name", "version" ],
|
||||
"properties" : {
|
||||
"modId" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Unique identifier of the mod"
|
||||
},
|
||||
"name" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Human-readable name of the mod"
|
||||
},
|
||||
"parent" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Unique ID of parent mod, only for submods"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Version of mod, as specified in mod config"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,7 @@ set(lib_MAIN_SRCS
|
||||
modding/ContentTypeHandler.cpp
|
||||
modding/IdentifierStorage.cpp
|
||||
modding/ModUtility.cpp
|
||||
modding/ModVerificationInfo.cpp
|
||||
|
||||
networkPacks/NetPacksLib.cpp
|
||||
|
||||
|
@ -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);
|
||||
|
@ -23,7 +23,6 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CGObjectInstance;
|
||||
enum class EMapFormat : uint8_t;
|
||||
using ModCompatibilityInfo = std::map<std::string, ModVerificationInfo>;
|
||||
|
||||
/// The hero name struct consists of the hero id and the hero name.
|
||||
struct DLL_LINKAGE SHeroName
|
||||
|
@ -24,8 +24,6 @@ class IMapLoader;
|
||||
class IMapPatcher;
|
||||
class IGameCallback;
|
||||
|
||||
using ModCompatibilityInfo = std::map<std::string, ModVerificationInfo>;
|
||||
|
||||
/**
|
||||
* The map service provides loading of VCMI/H3 map files. It can
|
||||
* be extended to save maps later as well.
|
||||
|
@ -850,23 +850,7 @@ void CMapLoaderJson::readHeader(const bool complete)
|
||||
mapHeader->version = EMapFormat::VCMI;//todo: new version field
|
||||
|
||||
//loading mods
|
||||
if(!header["mods"].isNull())
|
||||
{
|
||||
for(auto & mod : header["mods"].Vector())
|
||||
{
|
||||
ModVerificationInfo info;
|
||||
info.version = CModVersion::fromString(mod["version"].String());
|
||||
info.checksum = mod["checksum"].Integer();
|
||||
info.name = mod["name"].String();
|
||||
info.parent = mod["parent"].String();
|
||||
info.impactsGameplay = true;
|
||||
|
||||
if(!mod["modId"].isNull())
|
||||
mapHeader->mods[mod["modId"].String()] = info;
|
||||
else
|
||||
mapHeader->mods[mod["name"].String()] = info;
|
||||
}
|
||||
}
|
||||
mapHeader->mods = ModVerificationInfo::jsonDeserializeList(header["mods"]);
|
||||
|
||||
//todo: multilevel map load support
|
||||
{
|
||||
@ -1231,17 +1215,7 @@ void CMapSaverJson::writeHeader()
|
||||
header["versionMinor"].Float() = VERSION_MINOR;
|
||||
|
||||
//write mods
|
||||
JsonNode & mods = header["mods"];
|
||||
for(const auto & mod : mapHeader->mods)
|
||||
{
|
||||
JsonNode modWriter;
|
||||
modWriter["modId"].String() = mod.first;
|
||||
modWriter["name"].String() = mod.second.name;
|
||||
modWriter["parent"].String() = mod.second.parent;
|
||||
modWriter["version"].String() = mod.second.version.toString();
|
||||
modWriter["checksum"].Integer() = mod.second.checksum;
|
||||
mods.Vector().push_back(modWriter);
|
||||
}
|
||||
header["mods"] = ModVerificationInfo::jsonSerializeList(mapHeader->mods);
|
||||
|
||||
//todo: multilevel map save support
|
||||
JsonNode & levels = header["mapLevels"];
|
||||
|
@ -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;
|
||||
|
116
lib/modding/ModVerificationInfo.cpp
Normal file
116
lib/modding/ModVerificationInfo.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* ModVerificationInfo.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 "ModVerificationInfo.h"
|
||||
|
||||
#include "CModInfo.h"
|
||||
#include "CModHandler.h"
|
||||
#include "ModIncompatibility.h"
|
||||
|
||||
#include "../json/JsonNode.h"
|
||||
#include "../VCMI_Lib.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
JsonNode ModVerificationInfo::jsonSerializeList(const ModCompatibilityInfo & input)
|
||||
{
|
||||
JsonNode output;
|
||||
|
||||
for(const auto & mod : input)
|
||||
{
|
||||
JsonNode modWriter;
|
||||
modWriter["modId"].String() = mod.first;
|
||||
modWriter["name"].String() = mod.second.name;
|
||||
if (!mod.second.parent.empty())
|
||||
modWriter["parent"].String() = mod.second.parent;
|
||||
modWriter["version"].String() = mod.second.version.toString();
|
||||
output.Vector().push_back(modWriter);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
ModCompatibilityInfo ModVerificationInfo::jsonDeserializeList(const JsonNode & input)
|
||||
{
|
||||
ModCompatibilityInfo output;
|
||||
|
||||
for(const auto & mod : input.Vector())
|
||||
{
|
||||
ModVerificationInfo info;
|
||||
info.version = CModVersion::fromString(mod["version"].String());
|
||||
info.name = mod["name"].String();
|
||||
info.parent = mod["parent"].String();
|
||||
info.checksum = 0;
|
||||
info.impactsGameplay = true;
|
||||
|
||||
if(!mod["modId"].isNull())
|
||||
output[mod["modId"].String()] = info;
|
||||
else
|
||||
output[mod["name"].String()] = info;
|
||||
}
|
||||
|
||||
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 (!modAffectsGameplay)
|
||||
continue; // skip it
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -13,7 +13,22 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct ModVerificationInfo
|
||||
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
|
||||
std::string name;
|
||||
@ -30,6 +45,10 @@ struct ModVerificationInfo
|
||||
/// for serialization purposes
|
||||
bool impactsGameplay = true;
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, cons
|
||||
, listener(listener)
|
||||
{
|
||||
socket->set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
socket->set_option(boost::asio::socket_base::keep_alive(true));
|
||||
|
||||
// iOS throws exception on attempt to set buffer size
|
||||
constexpr auto bufferSize = 4 * 1024 * 1024;
|
||||
|
@ -81,6 +81,38 @@ void LobbyDatabase::createTables()
|
||||
database->prepare(createTableGameRoomInvites)->execute();
|
||||
}
|
||||
|
||||
void LobbyDatabase::upgradeDatabase()
|
||||
{
|
||||
auto getDatabaseVersionStatement = database->prepare(R"(
|
||||
PRAGMA user_version
|
||||
)");
|
||||
|
||||
auto upgradeDatabaseVersionStatement = database->prepare(R"(
|
||||
PRAGMA user_version = 10501
|
||||
)");
|
||||
|
||||
int databaseVersion;
|
||||
getDatabaseVersionStatement->execute();
|
||||
getDatabaseVersionStatement->getColumns(databaseVersion);
|
||||
getDatabaseVersionStatement->reset();
|
||||
|
||||
if (databaseVersion < 10501)
|
||||
{
|
||||
database->prepare(R"(
|
||||
ALTER TABLE gameRooms
|
||||
ADD COLUMN mods TEXT NOT NULL DEFAULT '{}'
|
||||
)")->execute();
|
||||
|
||||
database->prepare(R"(
|
||||
ALTER TABLE gameRooms
|
||||
ADD COLUMN version TEXT NOT NULL DEFAULT ''
|
||||
)")->execute();
|
||||
|
||||
upgradeDatabaseVersionStatement->execute();
|
||||
upgradeDatabaseVersionStatement->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void LobbyDatabase::clearOldData()
|
||||
{
|
||||
static const std::string removeActiveAccounts = R"(
|
||||
@ -122,7 +154,7 @@ void LobbyDatabase::prepareStatements()
|
||||
)");
|
||||
|
||||
insertGameRoomStatement = database->prepare(R"(
|
||||
INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 0, 8);
|
||||
INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit, version, mods) VALUES(?, ?, 0, 8, ?, ?);
|
||||
)");
|
||||
|
||||
insertGameRoomPlayersStatement = database->prepare(R"(
|
||||
@ -233,20 +265,26 @@ void LobbyDatabase::prepareStatements()
|
||||
)");
|
||||
|
||||
getActiveGameRoomsStatement = database->prepare(R"(
|
||||
SELECT roomID, hostAccountID, displayName, description, status, playerLimit, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',gr.creationTime) AS secondsElapsed
|
||||
SELECT roomID, hostAccountID, displayName, description, status, playerLimit, version, mods, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',gr.creationTime) AS secondsElapsed
|
||||
FROM gameRooms gr
|
||||
LEFT JOIN accounts a ON gr.hostAccountID = a.accountID
|
||||
WHERE status IN (1, 2, 3)
|
||||
ORDER BY secondsElapsed ASC
|
||||
)");
|
||||
|
||||
countRoomUsedSlotsStatement = database->prepare(R"(
|
||||
getGameRoomPlayersStatement = database->prepare(R"(
|
||||
SELECT a.accountID, a.displayName
|
||||
FROM gameRoomPlayers grp
|
||||
LEFT JOIN accounts a ON a.accountID = grp.accountID
|
||||
WHERE roomID = ?
|
||||
)");
|
||||
|
||||
countRoomUsedSlotsStatement = database->prepare(R"(
|
||||
SELECT COUNT(grp.accountID)
|
||||
FROM gameRoomPlayers grp
|
||||
WHERE roomID = ?
|
||||
)");
|
||||
|
||||
countRoomTotalSlotsStatement = database->prepare(R"(
|
||||
SELECT playerLimit
|
||||
FROM gameRooms
|
||||
@ -298,6 +336,7 @@ LobbyDatabase::LobbyDatabase(const boost::filesystem::path & databasePath)
|
||||
{
|
||||
database = SQLiteInstance::open(databasePath, true);
|
||||
createTables();
|
||||
upgradeDatabase();
|
||||
clearOldData();
|
||||
prepareStatements();
|
||||
}
|
||||
@ -393,9 +432,9 @@ void LobbyDatabase::insertGameRoomInvite(const std::string & targetAccountID, co
|
||||
insertGameRoomInvitesStatement->executeOnce(roomID, targetAccountID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertGameRoom(const std::string & roomID, const std::string & hostAccountID)
|
||||
void LobbyDatabase::insertGameRoom(const std::string & roomID, const std::string & hostAccountID, const std::string & serverVersion, const std::string & modListJson)
|
||||
{
|
||||
insertGameRoomStatement->executeOnce(roomID, hostAccountID);
|
||||
insertGameRoomStatement->executeOnce(roomID, hostAccountID, serverVersion, modListJson);
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertAccount(const std::string & accountID, const std::string & displayName)
|
||||
@ -526,21 +565,21 @@ std::vector<LobbyGameRoom> LobbyDatabase::getActiveGameRooms()
|
||||
while(getActiveGameRoomsStatement->execute())
|
||||
{
|
||||
LobbyGameRoom entry;
|
||||
getActiveGameRoomsStatement->getColumns(entry.roomID, entry.hostAccountID, entry.hostAccountDisplayName, entry.description, entry.roomState, entry.playerLimit, entry.age);
|
||||
getActiveGameRoomsStatement->getColumns(entry.roomID, entry.hostAccountID, entry.hostAccountDisplayName, entry.description, entry.roomState, entry.playerLimit, entry.version, entry.modsJson, entry.age);
|
||||
result.push_back(entry);
|
||||
}
|
||||
getActiveGameRoomsStatement->reset();
|
||||
|
||||
for (auto & room : result)
|
||||
{
|
||||
countRoomUsedSlotsStatement->setBinds(room.roomID);
|
||||
while(countRoomUsedSlotsStatement->execute())
|
||||
getGameRoomPlayersStatement->setBinds(room.roomID);
|
||||
while(getGameRoomPlayersStatement->execute())
|
||||
{
|
||||
LobbyAccount account;
|
||||
countRoomUsedSlotsStatement->getColumns(account.accountID, account.displayName);
|
||||
getGameRoomPlayersStatement->getColumns(account.accountID, account.displayName);
|
||||
room.participants.push_back(account);
|
||||
}
|
||||
countRoomUsedSlotsStatement->reset();
|
||||
getGameRoomPlayersStatement->reset();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -560,14 +599,14 @@ std::vector<LobbyGameRoom> LobbyDatabase::getAccountGameHistory(const std::strin
|
||||
|
||||
for (auto & room : result)
|
||||
{
|
||||
countRoomUsedSlotsStatement->setBinds(room.roomID);
|
||||
while(countRoomUsedSlotsStatement->execute())
|
||||
getGameRoomPlayersStatement->setBinds(room.roomID);
|
||||
while(getGameRoomPlayersStatement->execute())
|
||||
{
|
||||
LobbyAccount account;
|
||||
countRoomUsedSlotsStatement->getColumns(account.accountID, account.displayName);
|
||||
getGameRoomPlayersStatement->getColumns(account.accountID, account.displayName);
|
||||
room.participants.push_back(account);
|
||||
}
|
||||
countRoomUsedSlotsStatement->reset();
|
||||
getGameRoomPlayersStatement->reset();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ class LobbyDatabase
|
||||
SQLiteStatementPtr getAccountInviteStatusStatement;
|
||||
SQLiteStatementPtr getAccountGameRoomStatement;
|
||||
SQLiteStatementPtr getAccountDisplayNameStatement;
|
||||
SQLiteStatementPtr getGameRoomPlayersStatement;
|
||||
SQLiteStatementPtr countRoomUsedSlotsStatement;
|
||||
SQLiteStatementPtr countRoomTotalSlotsStatement;
|
||||
|
||||
@ -59,6 +60,7 @@ class LobbyDatabase
|
||||
|
||||
void prepareStatements();
|
||||
void createTables();
|
||||
void upgradeDatabase();
|
||||
void clearOldData();
|
||||
|
||||
public:
|
||||
@ -74,7 +76,7 @@ public:
|
||||
void deleteGameRoomInvite(const std::string & targetAccountID, const std::string & roomID);
|
||||
void insertGameRoomInvite(const std::string & targetAccountID, const std::string & roomID);
|
||||
|
||||
void insertGameRoom(const std::string & roomID, const std::string & hostAccountID);
|
||||
void insertGameRoom(const std::string & roomID, const std::string & hostAccountID, const std::string & serverVersion, const std::string & modListJson);
|
||||
void insertAccount(const std::string & accountID, const std::string & displayName);
|
||||
void insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID);
|
||||
void insertChatMessage(const std::string & sender, const std::string & channelType, const std::string & roomID, const std::string & messageText);
|
||||
|
@ -44,6 +44,8 @@ struct LobbyGameRoom
|
||||
std::string hostAccountID;
|
||||
std::string hostAccountDisplayName;
|
||||
std::string description;
|
||||
std::string version;
|
||||
std::string modsJson;
|
||||
std::vector<LobbyAccount> participants;
|
||||
LobbyRoomState roomState;
|
||||
uint32_t playerLimit;
|
||||
|
@ -208,9 +208,11 @@ static JsonNode loadLobbyGameRoomToJson(const LobbyGameRoom & gameRoom)
|
||||
jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
|
||||
jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
|
||||
jsonEntry["description"].String() = gameRoom.description;
|
||||
jsonEntry["version"].String() = gameRoom.version;
|
||||
jsonEntry["status"].String() = LOBBY_ROOM_STATE_NAMES[vstd::to_underlying(gameRoom.roomState)];
|
||||
jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit;
|
||||
jsonEntry["ageSeconds"].Integer() = gameRoom.age.count();
|
||||
jsonEntry["mods"] = JsonNode(reinterpret_cast<const std::byte *>(gameRoom.modsJson.data()), gameRoom.modsJson.size());
|
||||
|
||||
for(const auto & account : gameRoom.participants)
|
||||
jsonEntry["participants"].Vector().push_back(loadLobbyAccountToJson(account));
|
||||
@ -625,7 +627,8 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co
|
||||
}
|
||||
else
|
||||
{
|
||||
database->insertGameRoom(gameRoomID, accountID);
|
||||
std::string modListString = json["mods"].isNull() ? "[]" : json["mods"].toCompactString();
|
||||
database->insertGameRoom(gameRoomID, accountID, version, modListString);
|
||||
activeGameRooms[connection] = gameRoomID;
|
||||
sendServerLoginSuccess(connection, accountCookie);
|
||||
broadcastActiveGameRooms();
|
||||
|
@ -340,9 +340,9 @@ std::unique_ptr<CMap> MainWindow::openMapInternal(const QString & filenameSelect
|
||||
if(auto header = mapService.loadMapHeader(resId))
|
||||
{
|
||||
auto missingMods = CMapService::verifyMapHeaderMods(*header);
|
||||
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);
|
||||
|
@ -13,6 +13,9 @@
|
||||
#include "CVCMIServer.h"
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/json/JsonUtils.h"
|
||||
#include "../lib/VCMI_Lib.h"
|
||||
#include "../lib/modding/CModHandler.h"
|
||||
#include "../lib/modding/CModInfo.h"
|
||||
|
||||
GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner)
|
||||
: owner(owner)
|
||||
@ -125,6 +128,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
|
||||
toSend["accountID"].String() = getHostAccountID();
|
||||
toSend["accountCookie"].String() = getHostAccountCookie();
|
||||
toSend["version"].String() = VCMI_VERSION_STRING;
|
||||
toSend["mods"] = getHostModList();
|
||||
|
||||
sendMessage(connection, toSend);
|
||||
}
|
||||
@ -149,6 +153,19 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode GlobalLobbyProcessor::getHostModList() const
|
||||
{
|
||||
ModCompatibilityInfo info;
|
||||
|
||||
for (auto const & modName : VLC->modh->getActiveMods())
|
||||
{
|
||||
if(VLC->modh->getModInfo(modName).checkModGameplayAffecting())
|
||||
info[modName] = VLC->modh->getModInfo(modName).getVerificationInfo();
|
||||
}
|
||||
|
||||
return ModVerificationInfo::jsonSerializeList(info);
|
||||
}
|
||||
|
||||
void GlobalLobbyProcessor::sendGameStarted()
|
||||
{
|
||||
JsonNode toSend;
|
||||
|
@ -36,6 +36,7 @@ class GlobalLobbyProcessor : public INetworkClientListener
|
||||
void establishNewConnection();
|
||||
void sendMessage(const NetworkConnectionPtr & targetConnection, const JsonNode & payload);
|
||||
|
||||
JsonNode getHostModList() const;
|
||||
const std::string & getHostAccountID() const;
|
||||
const std::string & getHostAccountCookie() const;
|
||||
const std::string & getHostAccountDisplayName() const;
|
||||
|
Loading…
Reference in New Issue
Block a user