diff --git a/cockatrice/src/abstractclient.cpp b/cockatrice/src/abstractclient.cpp index 6faf6b33..a91ea784 100644 --- a/cockatrice/src/abstractclient.cpp +++ b/cockatrice/src/abstractclient.cpp @@ -19,6 +19,7 @@ #include "get_pb_extension.h" #include #include "client_metatypes.h" +#include "featureset.h" AbstractClient::AbstractClient(QObject *parent) : QObject(parent), nextCmdId(0), status(StatusDisconnected) @@ -45,6 +46,10 @@ AbstractClient::AbstractClient(QObject *parent) qRegisterMetaType("ServerInfo_User"); qRegisterMetaType >("QList"); qRegisterMetaType("Event_ReplayAdded"); + qRegisterMetaType >("missingFeatures"); + + FeatureSet features; + features.initalizeFeatureList(clientFeatures); connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *))); } diff --git a/cockatrice/src/abstractclient.h b/cockatrice/src/abstractclient.h index 939066bc..9c7606a2 100644 --- a/cockatrice/src/abstractclient.h +++ b/cockatrice/src/abstractclient.h @@ -25,6 +25,7 @@ class Event_NotifyUser; class Event_ConnectionClosed; class Event_ServerShutdown; class Event_ReplayAdded; +class FeatureSet; enum ClientStatus { StatusDisconnected, @@ -96,6 +97,8 @@ public: static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId); static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd); static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd); + + QMap clientFeatures; }; #endif diff --git a/cockatrice/src/client_metatypes.h b/cockatrice/src/client_metatypes.h index db1c90f7..d0ece007 100644 --- a/cockatrice/src/client_metatypes.h +++ b/cockatrice/src/client_metatypes.h @@ -24,6 +24,6 @@ Q_DECLARE_METATYPE(Event_UserMessage) Q_DECLARE_METATYPE(ServerInfo_User) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(Event_ReplayAdded) - +Q_DECLARE_METATYPE(QList) #endif diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 36733d1f..05e748dd 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -45,6 +45,7 @@ GeneralSettingsPage::GeneralSettingsPage() picDownloadCheckBox.setChecked(settingsCache->getPicDownload()); picDownloadHqCheckBox.setChecked(settingsCache->getPicDownloadHq()); + updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates()); pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); // 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size) @@ -64,19 +65,21 @@ GeneralSettingsPage::GeneralSettingsPage() connect(&picDownloadHqCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownloadHq(int))); connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int))); connect(&picDownloadHqCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool))); + connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int))); connect(highQualityURLEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlHq(QString))); QGridLayout *personalGrid = new QGridLayout; personalGrid->addWidget(&languageLabel, 0, 0); personalGrid->addWidget(&languageBox, 0, 1); - personalGrid->addWidget(&pixmapCacheLabel, 1, 0, 1, 1); - personalGrid->addWidget(&pixmapCacheEdit, 1, 1, 1, 1); - personalGrid->addWidget(&picDownloadCheckBox, 2, 0, 1, 2); - personalGrid->addWidget(&picDownloadHqCheckBox, 3, 0, 1, 2); - personalGrid->addWidget(&clearDownloadedPicsButton, 4, 0, 1, 1); - personalGrid->addWidget(&highQualityURLLabel, 5, 0, 1, 1); - personalGrid->addWidget(highQualityURLEdit, 5, 1, 1, 1); - personalGrid->addWidget(&highQualityURLLinkLabel, 6, 1, 1, 1); + personalGrid->addWidget(&pixmapCacheLabel, 1, 0); + personalGrid->addWidget(&pixmapCacheEdit, 1, 1); + personalGrid->addWidget(&updateNotificationCheckBox, 2, 0); + personalGrid->addWidget(&picDownloadCheckBox, 3, 0); + personalGrid->addWidget(&picDownloadHqCheckBox, 4, 0); + personalGrid->addWidget(&highQualityURLLabel, 5, 0); + personalGrid->addWidget(highQualityURLEdit, 5, 1); + personalGrid->addWidget(&highQualityURLLinkLabel, 6, 1); + personalGrid->addWidget(&clearDownloadedPicsButton, 6, 0); highQualityURLLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); highQualityURLLinkLabel.setOpenExternalLinks(true); @@ -246,6 +249,7 @@ void GeneralSettingsPage::retranslateUi() highQualityURLLabel.setText(tr("Custom Card Download URL:")); highQualityURLLinkLabel.setText(QString("%2").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ"))); clearDownloadedPicsButton.setText(tr("Reset/Clear Downloaded Pictures")); + updateNotificationCheckBox.setText(tr("Notify when new client features are available")); } void GeneralSettingsPage::setEnabledStatus(bool status) @@ -914,3 +918,4 @@ void DlgSettings::retranslateUi() for (int i = 0; i < pagesWidget->count(); i++) dynamic_cast(pagesWidget->widget(i))->retranslateUi(); } + diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index 2bceda71..45443c0b 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -60,6 +60,7 @@ private: QComboBox languageBox; QCheckBox picDownloadCheckBox; QCheckBox picDownloadHqCheckBox; + QCheckBox updateNotificationCheckBox; QLabel languageLabel; QLabel pixmapCacheLabel; QLabel deckPathLabel; diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index b8216163..c6f303b6 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -33,7 +33,6 @@ #include #include "QtNetwork/QNetworkInterface" #include - #include "main.h" #include "window_main.h" #include "dlg_settings.h" @@ -43,6 +42,7 @@ #include "pixmapgenerator.h" #include "rng_sfmt.h" #include "soundengine.h" +#include "featureset.h" //Q_IMPORT_PLUGIN(qjpeg) diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index e149082b..b652726d 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "remoteclient.h" @@ -81,7 +82,6 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdRegister.set_country(country.toStdString()); cmdRegister.set_real_name(realName.toStdString()); cmdRegister.set_clientid(settingsCache->getClientID().toStdString()); - PendingCommand *pend = prepareSessionCommand(cmdRegister); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response))); sendCommand(pend); @@ -105,8 +105,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica doLogin(); } -void RemoteClient::doLogin() -{ +void RemoteClient::doLogin() { setStatus(StatusLoggingIn); Command_Login cmdLogin; @@ -114,6 +113,13 @@ void RemoteClient::doLogin() cmdLogin.set_password(password.toStdString()); cmdLogin.set_clientid(settingsCache->getClientID().toStdString()); cmdLogin.set_clientver(VERSION_STRING); + + if (!clientFeatures.isEmpty()) { + QMap::iterator i; + for (i = clientFeatures.begin(); i != clientFeatures.end(); ++i) + cmdLogin.add_clientfeatures(i.key().toStdString().c_str()); + } + PendingCommand *pend = prepareSessionCommand(cmdLogin); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response))); sendCommand(pend); @@ -140,8 +146,17 @@ void RemoteClient::loginResponse(const Response &response) for (int i = resp.ignore_list_size() - 1; i >= 0; --i) ignoreList.append(resp.ignore_list(i)); emit ignoreListReceived(ignoreList); + + if (resp.missing_features_size() > 0 && settingsCache->getNotifyAboutUpdates()) + emit notifyUserAboutUpdate(); + } else { - emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); + QList missingFeatures; + if (resp.missing_features_size() > 0) { + for (int i = 0; i < resp.missing_features_size(); ++i) + missingFeatures << QString::fromStdString(resp.missing_features(i)); + } + emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time(), missingFeatures); setStatus(StatusDisconnecting); } } diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index c1478732..3e0ae971 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -11,7 +11,7 @@ class RemoteClient : public AbstractClient { signals: void maxPingTime(int seconds, int maxSeconds); void serverTimeout(); - void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); + void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime, QList missingFeatures); void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); void activateError(); void socketError(const QString &errorString); @@ -21,6 +21,7 @@ signals: void sigRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); void sigActivateToServer(const QString &_token); void sigDisconnectFromServer(); + void notifyUserAboutUpdate(); private slots: void slotConnected(); void readData(); diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 131653ad..3a1b7b5c 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -130,6 +130,7 @@ SettingsCache::SettingsCache() if(!QFile(settingsPath+"global.ini").exists()) translateLegacySettings(); + notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); lang = settings->value("personal/lang").toString(); keepalive = settings->value("personal/keepalive", 5).toInt(); deckPath = settings->value("paths/decks").toString(); @@ -620,4 +621,10 @@ void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) { rememberGameSettings = _rememberGameSettings; settings->setValue("game/remembergamesettings", rememberGameSettings); +} + +void SettingsCache::setNotifyAboutUpdate(int _notifyaboutupdate) +{ + notifyAboutUpdates = _notifyaboutupdate; + settings->setValue("personal/updatenotification", notifyAboutUpdates); } \ No newline at end of file diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index 775a53d4..1cfe4d42 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -58,6 +58,7 @@ private: QByteArray mainWindowGeometry; QString lang; QString deckPath, replaysPath, picsPath, cardDatabasePath, tokenDatabasePath, themeName; + bool notifyAboutUpdates; bool picDownload; bool picDownloadHq; bool notificationsEnabled; @@ -129,6 +130,7 @@ public: bool getPicDownloadHq() const { return picDownloadHq; } bool getNotificationsEnabled() const { return notificationsEnabled; } bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; } + bool getNotifyAboutUpdates() const { return notifyAboutUpdates; } bool getDoubleClickToPlay() const { return doubleClickToPlay; } bool getPlayToStack() const { return playToStack; } @@ -251,6 +253,7 @@ public slots: void setSpectatorsCanTalk(const bool _spectatorsCanTalk); void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything); void setRememberGameSettings(const bool _rememberGameSettings); + void setNotifyAboutUpdate(int _notifyaboutupdate); }; extern SettingsCache *settingsCache; diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 812fc1df..6ff7a56e 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -30,6 +30,7 @@ #include #include #include + #if QT_VERSION < 0x050000 #include // for Qt::escape() #endif @@ -293,9 +294,23 @@ void MainWindow::serverTimeout() actConnect(); } -void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime) +void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime, QList missingFeatures) { switch (r) { + case Response::RespClientUpdateRequired: { + QString formatedMissingFeatures; + formatedMissingFeatures = "Missing Features: "; + for (int i = 0; i < missingFeatures.size(); ++i) + formatedMissingFeatures.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i) ); + + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Critical); + msgBox.setWindowTitle(tr("Failed Login")); + msgBox.setText(tr("Your client does not support features that the server requires, please update your client and try again.")); + msgBox.setDetailedText(formatedMissingFeatures); + msgBox.exec(); + break; + } case Response::RespWrongPassword: QMessageBox::critical(this, tr("Error"), tr("Incorrect username or password. Please check your authentication information and try again.")); break; @@ -561,13 +576,13 @@ MainWindow::MainWindow(QWidget *parent) client = new RemoteClient; connect(client, SIGNAL(connectionClosedEventReceived(const Event_ConnectionClosed &)), this, SLOT(processConnectionClosedEvent(const Event_ConnectionClosed &))); connect(client, SIGNAL(serverShutdownEventReceived(const Event_ServerShutdown &)), this, SLOT(processServerShutdownEvent(const Event_ServerShutdown &))); - connect(client, SIGNAL(loginError(Response::ResponseCode, QString, quint32)), this, SLOT(loginError(Response::ResponseCode, QString, quint32))); + connect(client, SIGNAL(loginError(Response::ResponseCode, QString, quint32, QList)), this, SLOT(loginError(Response::ResponseCode, QString, quint32, QList))); connect(client, SIGNAL(socketError(const QString &)), this, SLOT(socketError(const QString &))); connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout())); connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int))); connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection); - + connect(client, SIGNAL(notifyUserAboutUpdate()), this, SLOT(notifyUserAboutUpdate())); connect(client, SIGNAL(registerAccepted()), this, SLOT(registerAccepted())); connect(client, SIGNAL(registerAcceptedNeedsActivate()), this, SLOT(registerAcceptedNeedsActivate())); connect(client, SIGNAL(registerError(Response::ResponseCode, QString, quint32)), this, SLOT(registerError(Response::ResponseCode, QString, quint32))); @@ -795,3 +810,8 @@ void MainWindow::refreshShortcuts() aExit->setShortcuts(settingsCache->shortcuts().getShortcut("MainWindow/aExit")); aCheckCardUpdates->setShortcuts(settingsCache->shortcuts().getShortcut("MainWindow/aCheckCardUpdates")); } + +void MainWindow::notifyUserAboutUpdate() +{ + QMessageBox::information(this, tr("Information"), tr("Your client appears to be missing features that the server supports.\nThis usually means that your client version is out of date,pleae check to see if there is a new client available for download.")); +} diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 5a12676e..149f7197 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -20,6 +20,7 @@ #ifndef WINDOW_H #define WINDOW_H +#include #include #include #include @@ -42,7 +43,7 @@ private slots: void processConnectionClosedEvent(const Event_ConnectionClosed &event); void processServerShutdownEvent(const Event_ServerShutdown &event); void serverTimeout(); - void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime); + void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime, QList missingFeatures); void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime); void activateError(); void socketError(const QString &errorStr); @@ -53,7 +54,7 @@ private slots: void activateAccepted(); void localGameEnded(); void pixmapCacheSizeChanged(int newSizeInMBs); - + void notifyUserAboutUpdate(); void actConnect(); void actDisconnect(); void actSinglePlayer(); diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 47dab1f5..d73dc6f8 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(pb) SET(common_SOURCES decklist.cpp + featureset.cpp get_pb_extension.cpp rng_abstract.cpp rng_sfmt.cpp diff --git a/common/featureset.cpp b/common/featureset.cpp new file mode 100644 index 00000000..f940a461 --- /dev/null +++ b/common/featureset.cpp @@ -0,0 +1,55 @@ +#include "featureset.h" +#include +#include + +FeatureSet::FeatureSet() +{ + +} + +QMap FeatureSet::getDefaultFeatureList() { + initalizeFeatureList(featureList); + return featureList; +} + +void FeatureSet::initalizeFeatureList(QMap &featureList){ + featureList.insert("client_id", false); + featureList.insert("client_ver", false); + featureList.insert("feature_set", false); +} + +void FeatureSet::enableRequiredFeature(QMap &featureList, QString featureName){ + if (featureList.contains(featureName)) + featureList.insert(featureName,true); +} + +void FeatureSet::disableRequiredFeature(QMap &featureList, QString featureName){ + if (featureList.contains(featureName)) + featureList.insert(featureName,false); +} + +QMap FeatureSet::addFeature(QMap &featureList, QString featureName, bool isFeatureRequired){ + featureList.insert(featureName,isFeatureRequired); + return featureList; +} + +QMap FeatureSet::identifyMissingFeatures(QMap suppliedFeatures, QMap requiredFeatures){ + QMap missingList; + QMap::iterator i; + for (i = requiredFeatures.begin(); i != requiredFeatures.end(); ++i) { + if (!suppliedFeatures.contains(i.key())) { + missingList.insert(i.key(), i.value()); + } + } + return missingList; +} + +bool FeatureSet::isRequiredFeaturesMissing(QMap suppliedFeatures, QMap requiredFeatures) { + QMap::iterator i; + for (i = requiredFeatures.begin(); i != requiredFeatures.end(); ++i) { + if (i.value() && suppliedFeatures.contains(i.key())) { + return true; + } + } + return false; +} diff --git a/common/featureset.h b/common/featureset.h new file mode 100644 index 00000000..620ba96a --- /dev/null +++ b/common/featureset.h @@ -0,0 +1,24 @@ +#ifndef FEATURESET_H +#define FEATURESET_H + +#include +#include +#include + +class FeatureSet +{ +public: + FeatureSet(); + QMap getDefaultFeatureList(); + void initalizeFeatureList(QMap &featureList); + void enableRequiredFeature(QMap &featureList, QString featureName); + void disableRequiredFeature(QMap &featureList, QString featureName); + QMap addFeature(QMap &featureList, QString featureName, bool isFeatureRequired); + QMap identifyMissingFeatures(QMap featureListToCheck, QMap featureListToCompareTo); + bool isRequiredFeaturesMissing(QMap featureListToCheck, QMap featureListToCompareTo); +private: + QMap featureList; +}; + + +#endif // FEEATURESET_H diff --git a/common/pb/response.proto b/common/pb/response.proto index ba3ccc6a..ce2aec00 100644 --- a/common/pb/response.proto +++ b/common/pb/response.proto @@ -37,6 +37,7 @@ message Response { RespActivationFailed = 32; // Server didn't accept a reg user activation token RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation RespClientIdRequired = 34; // Server requires client to generate and send its client id before allowing access + RespClientUpdateRequired = 35; // Client is missing features that the server is requiring } enum ResponseType { JOIN_ROOM = 1000; diff --git a/common/pb/response_login.proto b/common/pb/response_login.proto index fecd9d83..673eaa46 100644 --- a/common/pb/response_login.proto +++ b/common/pb/response_login.proto @@ -11,4 +11,5 @@ message Response_Login { repeated ServerInfo_User ignore_list = 3; optional string denied_reason_str = 4; optional uint64 denied_end_time = 5; + repeated string missing_features = 6; } diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto index 0d5b12bd..afd85a39 100644 --- a/common/pb/session_commands.proto +++ b/common/pb/session_commands.proto @@ -46,6 +46,7 @@ message Command_Login { optional string password = 2; optional string clientid = 3; optional string clientver = 4; + repeated string clientfeatures = 5; } message Command_Message { diff --git a/common/server.cpp b/common/server.cpp index 88d52909..e0a015ec 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -32,6 +32,7 @@ #include "pb/session_event.pb.h" #include "pb/event_connection_closed.pb.h" #include "pb/isl_message.pb.h" +#include "featureset.h" #include #include #include diff --git a/common/server.h b/common/server.h index 494ec534..e4b635e0 100644 --- a/common/server.h +++ b/common/server.h @@ -51,10 +51,10 @@ public: Server_AbstractUserInterface *findUser(const QString &userName) const; const QMap &getUsers() const { return users; } const QMap &getUsersBySessionId() const { return usersBySessionId; } + virtual QMap getServerRequiredFeatureList() const { return QMap(); } void addClient(Server_ProtocolHandler *player); void removeClient(Server_ProtocolHandler *player); virtual QString getLoginMessage() const { return QString(); } - virtual bool permitUnregisteredUsers() const { return true; } virtual bool getGameShouldPing() const { return false; } virtual bool getClientIdRequired() const { return false; } @@ -94,6 +94,7 @@ private: mutable QReadWriteLock persistentPlayersLock; int nextLocalGameId; QMutex nextLocalGameIdMutex; + protected slots: void externalUserJoined(const ServerInfo_User &userInfo); void externalUserLeft(const QString &userName); diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index c4ed0f51..775b204f 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -19,6 +19,8 @@ #include "pb/event_game_joined.pb.h" #include "pb/event_room_say.pb.h" #include +#include "featureset.h" + Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent) : QObject(parent), @@ -382,10 +384,32 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd { QString userName = QString::fromStdString(cmd.user_name()).simplified(); QString clientId = QString::fromStdString(cmd.clientid()).simplified(); - + if (userName.isEmpty() || (userInfo != 0)) return Response::RespContextError; + // check client feature set against server feature set + FeatureSet features; + QMap receivedClientFeatures; + QMap missingClientFeatures; + + for (int i = 0; i < cmd.clientfeatures().size(); ++i) + receivedClientFeatures.insert(QString::fromStdString(cmd.clientfeatures(i)).simplified(), false); + + missingClientFeatures = features.identifyMissingFeatures(receivedClientFeatures, server->getServerRequiredFeatureList()); + + if (!missingClientFeatures.isEmpty()) { + if (features.isRequiredFeaturesMissing(missingClientFeatures, server->getServerRequiredFeatureList())) { + Response_Login *re = new Response_Login; + re->set_denied_reason_str("Client upgrade required"); + QMap::iterator i; + for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i) + re->add_missing_features(i.key().toStdString().c_str()); + rc.setResponseExtension(re); + return Response::RespClientUpdateRequired; + } + } + QString reasonStr; int banSecondsLeft = 0; AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft, clientId); @@ -430,6 +454,13 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd re->add_ignore_list()->CopyFrom(ignoreIterator.next().value()); } + // return to client any missing features the server has that the client does not + if (!missingClientFeatures.isEmpty()) { + QMap::iterator i; + for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i) + re->add_missing_features(i.key().toStdString().c_str()); + } + joinPersistentGames(rc); rc.setResponseExtension(re); diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index bf9d1940..5c9aef9a 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -8,11 +8,13 @@ #include "pb/response.pb.h" #include "pb/server_message.pb.h" +class Features; class Server_DatabaseInterface; class Server_Player; class ServerInfo_User; class Server_Room; class QTimer; +class FeatureSet; class ServerMessage; class Response; diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index ce57df53..842fb032 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -50,6 +50,12 @@ max_player_inactivity_time=15 ' require that clients report the client ID in order to log into the server. Default is false requireclientid=false +; You can limit the types of clients that connect to the server by requiring different features be available +; on the client. This setting can contain a comma-seperated list of features. if any of the features +; listed in this line are not available on the client the client will be denied access to the server upon +; attempting to log in. Example: "client_id,client_ver" +requiredfeatures="" + [authentication] ; Servatrice can authenticate users connecting. It currently supports 3 different authentication methods: diff --git a/servatrice/src/main.cpp b/servatrice/src/main.cpp index 499b468d..d19ebb66 100644 --- a/servatrice/src/main.cpp +++ b/servatrice/src/main.cpp @@ -198,6 +198,7 @@ int main(int argc, char *argv[]) #else qInstallMessageHandler(myMessageOutput); #endif + retval = app.exec(); std::cerr << "Server quit." << std::endl; diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 701902f8..f1f29191 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -38,6 +38,7 @@ #include "pb/event_server_message.pb.h" #include "pb/event_server_shutdown.pb.h" #include "pb/event_connection_closed.pb.h" +#include "featureset.h" Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent) : QTcpServer(parent), @@ -179,6 +180,16 @@ bool Servatrice::initServer() if (registrationEnabled) qDebug() << "Require email address to register: " << requireEmailForRegistration; + FeatureSet features; + features.initalizeFeatureList(serverRequiredFeatureList); + requiredFeatures = settingsCache->value("server/requiredfeatures","").toString(); + QStringList listReqFeatures = requiredFeatures.split(",", QString::SkipEmptyParts); + if (!listReqFeatures.isEmpty()) + foreach(QString reqFeature, listReqFeatures) + features.enableRequiredFeature(serverRequiredFeatureList,reqFeature); + + qDebug() << "Required client features: " << serverRequiredFeatureList; + QString dbTypeStr = settingsCache->value("database/type").toString(); if (dbTypeStr == "mysql") databaseType = DatabaseMySql; diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 4e4a74b2..ad42497b 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -30,6 +30,7 @@ #include #include "server.h" + Q_DECLARE_METATYPE(QSqlDatabase) class QSqlQuery; @@ -41,6 +42,7 @@ class Servatrice_ConnectionPool; class Servatrice_DatabaseInterface; class ServerSocketInterface; class IslInterface; +class FeatureSet; class Servatrice_GameServer : public QTcpServer { Q_OBJECT @@ -109,6 +111,8 @@ private: mutable QMutex loginMessageMutex; QString loginMessage; QString dbPrefix; + QString requiredFeatures; + QMap serverRequiredFeatureList; Servatrice_DatabaseInterface *servatriceDatabaseInterface; int serverId; int uptime; @@ -134,8 +138,10 @@ public: Servatrice(QObject *parent = 0); ~Servatrice(); bool initServer(); + QMap getServerRequiredFeatureList() const { return serverRequiredFeatureList; } QString getServerName() const { return serverName; } QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; } + QString getRequiredFeatures() const { return requiredFeatures; } bool permitUnregisteredUsers() const { return authenticationMethod != AuthenticationNone; } bool getGameShouldPing() const { return true; } bool getClientIdRequired() const { return clientIdRequired; }