# HG changeset patch # User S.D. # Date 1664279943 -10800 # Node ID fc3cb23fd26febe2d0bbf34a528d904aaf59fe48 # Parent 6cb7330113d8960151c1f5db396859c539079e34 Allow to see rooms of incompatible versions in the lobby For the new clients the room version is shown in a separate column. There is also a hack for previous versions clients: the room vesion specifier is prepended to the room names for rooms of incompatible versions, and the server shows 'incompatible version' error if the client tries to join them. diff -r 6cb7330113d8 -r fc3cb23fd26f QTfrontend/model/roomslistmodel.cpp --- a/QTfrontend/model/roomslistmodel.cpp Fri Sep 23 12:47:47 2022 -0400 +++ b/QTfrontend/model/roomslistmodel.cpp Tue Sep 27 14:59:03 2022 +0300 @@ -27,10 +27,11 @@ #include "roomslistmodel.h" #include "MapModel.h" +#include "hwconsts.h" RoomsListModel::RoomsListModel(QObject *parent) : QAbstractTableModel(parent), - c_nColumns(9) + c_nColumns(10) { m_headerData = QStringList(); m_headerData << tr("In progress"); @@ -44,6 +45,7 @@ m_headerData << tr("Script"); m_headerData << tr("Rules"); m_headerData << tr("Weapons"); + m_headerData << tr("Version"); m_staticMapModel = DataManager::instance().staticMapModel(); m_missionMapModel = DataManager::instance().missionMapModel(); @@ -77,6 +79,59 @@ } +QString RoomsListModel::protoToVersion(const QString & proto) +{ + bool ok; + uint protoNum = proto.toUInt(&ok); + if (!ok) + return "Unknown"; + switch (protoNum) { + case 17: return "0.9.7-dev"; + case 19: return "0.9.7"; + case 20: return "0.9.8-dev"; + case 21: return "0.9.8"; + case 22: return "0.9.9-dev"; + case 23: return "0.9.9"; + case 24: return "0.9.10-dev"; + case 25: return "0.9.10"; + case 26: return "0.9.11-dev"; + case 27: return "0.9.11"; + case 28: return "0.9.12-dev"; + case 29: return "0.9.12"; + case 30: return "0.9.13-dev"; + case 31: return "0.9.13"; + case 32: return "0.9.14-dev"; + case 33: return "0.9.14"; + case 34: return "0.9.15-dev"; + case 35: return "0.9.14.1"; + case 37: return "0.9.15"; + case 38: return "0.9.16-dev"; + case 39: return "0.9.16"; + case 40: return "0.9.17-dev"; + case 41: return "0.9.17"; + case 42: return "0.9.18-dev"; + case 43: return "0.9.18"; + case 44: return "0.9.19-dev"; + case 45: return "0.9.19"; + case 46: return "0.9.20-dev"; + case 47: return "0.9.20"; + case 48: return "0.9.21-dev"; + case 49: return "0.9.21"; + case 50: return "0.9.22-dev"; + case 51: return "0.9.22"; + case 52: return "0.9.23-dev"; + case 53: return "0.9.23"; + case 54: return "0.9.24-dev"; + case 55: return "0.9.24"; + case 56: return "0.9.25-dev"; + case 57: return "0.9.25"; + case 58: return "1.0.0-dev"; + case 59: return "1.0.0"; + case 60: return "1.0.1-dev"; + default: return "Unknown"; + } +} + QVariant RoomsListModel::data(const QModelIndex &index, int role) const { int column = index.column(); @@ -101,9 +156,10 @@ || ((column != PlayerCountColumn) && (column != TeamCountColumn))) // only decorate name column if ((role != Qt::DecorationRole) || (column != NameColumn)) - // only dye map column - if ((role != Qt::ForegroundRole) || (column != MapColumn)) - return QVariant(); + if ((role != Qt::ForegroundRole)) + // UserRole is used for version column filtering + if ((role != Qt::UserRole)) + return QVariant(); // decorate room name based on room state if (role == Qt::DecorationRole) @@ -159,6 +215,10 @@ !m_missionMapModel->mapExists(content)) return QString ("? %1").arg(content); } + else if (column == VersionColumn) + { + return protoToVersion(content); + } return content; } @@ -166,16 +226,23 @@ // dye map names red if map not available if (role == Qt::ForegroundRole) { - if (content == "+rnd+" || - content == "+maze+" || - content == "+perlin+" || - content == "+drawn+" || - content == "+forts+" || - m_staticMapModel->mapExists(content) || - m_missionMapModel->mapExists(content)) - return QVariant(); - else - return QBrush(QColor("darkred")); + if (m_data[row][VersionColumn] != *cProtoVer) + return QBrush(QColor("darkgrey")); + + if (column == MapColumn) + { + if (content == "+rnd+" || + content == "+maze+" || + content == "+perlin+" || + content == "+drawn+" || + content == "+forts+" || + m_staticMapModel->mapExists(content) || + m_missionMapModel->mapExists(content)) + return QVariant(); + else + return QBrush(QColor("darkred")); + } + return QVariant(); } if (role == Qt::TextAlignmentRole) @@ -183,6 +250,9 @@ return (int)(Qt::AlignHCenter | Qt::AlignVCenter); } + if (role == Qt::UserRole && column == VersionColumn) + return content; + Q_ASSERT(false); return QVariant(); } diff -r 6cb7330113d8 -r fc3cb23fd26f QTfrontend/model/roomslistmodel.h --- a/QTfrontend/model/roomslistmodel.h Fri Sep 23 12:47:47 2022 -0400 +++ b/QTfrontend/model/roomslistmodel.h Tue Sep 27 14:59:03 2022 +0300 @@ -42,8 +42,10 @@ TeamCountColumn, OwnerColumn, MapColumn, + ScriptColumn, SchemeColumn, - WeaponsColumn + WeaponsColumn, + VersionColumn, }; explicit RoomsListModel(QObject *parent = 0); @@ -51,6 +53,7 @@ QVariant headerData(int section, Qt::Orientation orientation, int role) const; int rowCount(const QModelIndex & parent) const; int columnCount(const QModelIndex & parent) const; + int columnCountSupported() const { return c_nColumns; }; QVariant data(const QModelIndex &index, int role) const; public slots: @@ -66,6 +69,7 @@ QStringList m_headerData; MapModel * m_staticMapModel; MapModel * m_missionMapModel; + static QString protoToVersion(const QString & proto); }; #endif // HEDGEWARS_ROOMSLISTMODEL_H diff -r 6cb7330113d8 -r fc3cb23fd26f QTfrontend/net/newnetclient.cpp --- a/QTfrontend/net/newnetclient.cpp Fri Sep 23 12:47:47 2022 -0400 +++ b/QTfrontend/net/newnetclient.cpp Tue Sep 27 14:59:03 2022 +0300 @@ -394,7 +394,7 @@ if (lst[0] == "ROOMS") { - if(lst.size() % 9 != 1) + if(lst.size() % m_roomsListModel->columnCountSupported() != 1) { qWarning("Net: Malformed ROOMS message"); return; @@ -644,7 +644,7 @@ return; } - if(lst[0] == "ROOM" && lst.size() == 11 && lst[1] == "ADD") + if(lst[0] == "ROOM" && lst.size() == m_roomsListModel->columnCountSupported() + 2 && lst[1] == "ADD") { QStringList tmp = lst; tmp.removeFirst(); @@ -654,7 +654,7 @@ return; } - if(lst[0] == "ROOM" && lst.size() == 12 && lst[1] == "UPD") + if(lst[0] == "ROOM" && lst.size() == m_roomsListModel->columnCountSupported() + 3 && lst[1] == "UPD") { QStringList tmp = lst; tmp.removeFirst(); diff -r 6cb7330113d8 -r fc3cb23fd26f QTfrontend/ui/page/pageroomslist.cpp --- a/QTfrontend/ui/page/pageroomslist.cpp Fri Sep 23 12:47:47 2022 -0400 +++ b/QTfrontend/ui/page/pageroomslist.cpp Tue Sep 27 14:59:03 2022 +0300 @@ -84,10 +84,14 @@ showJoinRestricted = new QAction(QAction::tr("Show join restricted"), stateMenu); showJoinRestricted->setCheckable(true); showJoinRestricted->setChecked(true); + showIncompatible = new QAction(QAction::tr("Show incompatible"), stateMenu); + showIncompatible->setCheckable(true); + showIncompatible->setChecked(true); stateMenu->addAction(showGamesInLobby); stateMenu->addAction(showGamesInProgress); stateMenu->addAction(showPassword); stateMenu->addAction(showJoinRestricted); + stateMenu->addAction(showIncompatible); btnState->setMenu(stateMenu); // Help/prompt message at top @@ -199,6 +203,7 @@ connect(showGamesInProgress, SIGNAL(triggered()), this, SLOT(onFilterChanged())); connect(showPassword, SIGNAL(triggered()), this, SLOT(onFilterChanged())); connect(showJoinRestricted, SIGNAL(triggered()), this, SLOT(onFilterChanged())); + connect(showIncompatible, SIGNAL(triggered()), this, SLOT(onFilterChanged())); connect(searchText, SIGNAL(textChanged (const QString &)), this, SLOT(onFilterChanged())); connect(this, SIGNAL(askJoinConfirmation (const QString &)), this, SLOT(onJoinConfirmation(const QString &)), Qt::QueuedConnection); @@ -232,6 +237,7 @@ { roomsModel = NULL; stateFilteredModel = NULL; + versionFilteredModel = NULL; initPage(); } @@ -554,7 +560,7 @@ void PageRoomsList::setModel(RoomsListModel * model) { // filter chain: - // model -> stateFilteredModel -> schemeFilteredModel -> + // model -> versionFilteredModel -> stateFilteredModel -> schemeFilteredModel -> // -> weaponsFilteredModel -> roomsModel (search filter+sorting) if (roomsModel == NULL) @@ -564,12 +570,17 @@ roomsModel->setSortCaseSensitivity(Qt::CaseInsensitive); roomsModel->sort(RoomsListModel::StateColumn, Qt::AscendingOrder); - stateFilteredModel = new QSortFilterProxyModel(this); + versionFilteredModel = new QSortFilterProxyModel(this); + versionFilteredModel->setDynamicSortFilter(true); + versionFilteredModel->setFilterKeyColumn(RoomsListModel::VersionColumn); + versionFilteredModel->setFilterRole(Qt::UserRole); + stateFilteredModel = new QSortFilterProxyModel(this); stateFilteredModel->setDynamicSortFilter(true); + stateFilteredModel->setFilterKeyColumn(RoomsListModel::StateColumn); + stateFilteredModel->setSourceModel(versionFilteredModel); roomsModel->setFilterKeyColumn(-1); // search in all columns - stateFilteredModel->setFilterKeyColumn(RoomsListModel::StateColumn); roomsModel->setFilterCaseSensitivity(Qt::CaseInsensitive); @@ -585,7 +596,7 @@ connect(roomsList->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(roomSelectionChanged(const QModelIndex &, const QModelIndex &))); } - stateFilteredModel->setSourceModel(model); + versionFilteredModel->setSourceModel(model); QHeaderView * h = roomsList->horizontalHeader(); @@ -638,6 +649,12 @@ bool stateProgress = showGamesInProgress->isChecked(); bool statePassword = showPassword->isChecked(); bool stateJoinRestricted = showJoinRestricted->isChecked(); + bool stateIncompatible = showIncompatible->isChecked(); + + if (!stateIncompatible) + versionFilteredModel->setFilterFixedString(*cProtoVer); + else + versionFilteredModel->setFilterFixedString(""); QString filter; if (!stateLobby && !stateProgress) diff -r 6cb7330113d8 -r fc3cb23fd26f QTfrontend/ui/page/pageroomslist.h --- a/QTfrontend/ui/page/pageroomslist.h Fri Sep 23 12:47:47 2022 -0400 +++ b/QTfrontend/ui/page/pageroomslist.h Tue Sep 27 14:59:03 2022 +0300 @@ -93,10 +93,12 @@ QSettings * m_gameSettings; QSortFilterProxyModel * roomsModel; QSortFilterProxyModel * stateFilteredModel; + QSortFilterProxyModel * versionFilteredModel; QAction * showGamesInLobby; QAction * showGamesInProgress; QAction * showPassword; QAction * showJoinRestricted; + QAction * showIncompatible; QSplitter * m_splitter; GameSchemeModel * gameSchemeModel; diff -r 6cb7330113d8 -r fc3cb23fd26f gameServer/Actions.hs --- a/gameServer/Actions.hs Fri Sep 23 12:47:47 2022 -0400 +++ b/gameServer/Actions.hs Tue Sep 27 14:59:03 2022 +0300 @@ -24,6 +24,7 @@ import qualified Data.Set as Set import qualified Data.Map as Map import qualified Data.List as L +import Data.Word import qualified Control.Exception as Exception import System.Log.Logger import Control.Monad @@ -65,6 +66,12 @@ ri <- clientRoomA liftM (map sendChan . filter (/= cl)) $ roomClientsS ri +othersChansProto :: StateT ServerState IO [(ClientChan, Word16)] +othersChansProto = do + cl <- client's id + ri <- clientRoomA + map (\ci -> (sendChan ci, clientProto ci)) . filter (/= cl) <$> roomClientsS ri + processAction :: Action -> StateT ServerState IO () @@ -72,6 +79,10 @@ io $ mapM_ (`writeChan` (msg `deepseq` msg)) (chans `deepseq` chans) +processAction (AnswerClientsByProto chansProto msgFunc) = + io $ mapM_ (\(chan, proto) -> writeChan chan (msgFunc proto)) chansProto + + processAction SendServerMessage = do chan <- client's sendChan protonum <- client's clientProto @@ -279,8 +290,9 @@ ) newRoom' <- io $ room'sM rnc id ri - chans <- liftM (map sendChan) $! sameProtoClientsS proto - processAction $ AnswerClients chans ("ROOM" : "UPD" : oldRoomName : roomInfo proto (maybeNick newMaster) newRoom') + chansProto <- fmap (map (\c -> (sendChan c, clientProto c))) $! allClientsS + let oldRoomNameByProto = roomNameByProto oldRoomName (roomProto newRoom') + processAction $ AnswerClientsByProto chansProto (\p -> "ROOM" : "UPD" : oldRoomNameByProto p : roomInfo p (maybeNick newMaster) newRoom') processAction (AddRoom roomName roomPassword) = do @@ -300,10 +312,10 @@ processAction $ MoveToRoom rId - chans <- liftM (map sendChan) $! sameProtoClientsS proto + chansProto <- fmap (map (\c -> (sendChan c, clientProto c))) $! allClientsS mapM_ processAction [ - AnswerClients chans ("ROOM" : "ADD" : roomInfo proto n rm{playersIn = 1}) + AnswerClientsByProto chansProto (\p -> "ROOM" : "ADD" : roomInfo p n rm{playersIn = 1}) ] @@ -312,13 +324,13 @@ rnc <- gets roomsClients ri <- io $ clientRoomM rnc clId roomName <- io $ room'sM rnc name ri - others <- othersChans - proto <- client's clientProto - chans <- liftM (map sendChan) $! sameProtoClientsS proto + roomProto <- io $ room'sM rnc roomProto ri + others <- othersChansProto + chansProto <- fmap (map (\c -> (sendChan c, clientProto c))) $! allClientsS mapM_ processAction [ - AnswerClients chans ["ROOM", "DEL", roomName], - AnswerClients others ["ROOMABANDONED", roomName] + AnswerClientsByProto chansProto (\p -> ["ROOM", "DEL", roomNameByProto roomName roomProto p]), + AnswerClientsByProto others (\p -> ["ROOMABANDONED", roomNameByProto roomName roomProto p]) ] io $ removeRoom rnc ri @@ -331,8 +343,9 @@ ri <- io $ clientRoomM rnc clId rm <- io $ room'sM rnc id ri masterCl <- io $ client'sM rnc id `DT.mapM` (masterID rm) - chans <- liftM (map sendChan) $! sameProtoClientsS proto - processAction $ AnswerClients chans ("ROOM" : "UPD" : name rm : roomInfo proto (maybeNick masterCl) rm) + chansProto <- fmap (map (\c -> (sendChan c, clientProto c))) $! allClientsS + let thisRoomNameByProto = roomNameByProto (name rm) (roomProto rm) + processAction $ AnswerClientsByProto chansProto (\p -> "ROOM" : "UPD" : thisRoomNameByProto p : roomInfo p (maybeNick masterCl) rm) processAction UnreadyRoomClients = do @@ -536,7 +549,7 @@ rooms <- roomsM rnc mapM (\r -> (mapM (client'sM rnc id) $ masterID r) >>= \cn -> return $ roomInfo clProto (maybeNick cn) r) - $ filter (\r -> (roomProto r == clProto)) rooms + $ filter ((/=) 0 . roomProto) rooms mapM_ processAction . concat $ [ [AnswerClients clientsChans ["LOBBY:JOINED", clientNick]] diff -r 6cb7330113d8 -r fc3cb23fd26f gameServer/CoreTypes.hs --- a/gameServer/CoreTypes.hs Fri Sep 23 12:47:47 2022 -0400 +++ b/gameServer/CoreTypes.hs Tue Sep 27 14:59:03 2022 +0300 @@ -46,6 +46,7 @@ data Action = AnswerClients ![ClientChan] ![B.ByteString] + | AnswerClientsByProto ![(ClientChan, Word16)] !(Word16 -> [B.ByteString]) | SendServerMessage | SendServerVars | MoveToRoom RoomIndex diff -r 6cb7330113d8 -r fc3cb23fd26f gameServer/HWProtoInRoomState.hs --- a/gameServer/HWProtoInRoomState.hs Fri Sep 23 12:47:47 2022 -0400 +++ b/gameServer/HWProtoInRoomState.hs Tue Sep 27 14:59:03 2022 +0300 @@ -313,7 +313,8 @@ cl <- thisClient rs <- allRoomInfos rm <- thisRoom - chans <- sameProtoChans + chansProto <- allChansProto + let thisRoomNameByProto = roomNameByProto (name rm) (roomProto rm) return $ if illegalName newName then @@ -326,7 +327,7 @@ [Warning $ loc "A room with the same name already exists."] else [ModifyRoom roomUpdate, - AnswerClients chans ("ROOM" : "UPD" : name rm : roomInfo (clientProto cl) (nick cl) (roomUpdate rm)), + AnswerClientsByProto chansProto (\p -> "ROOM" : "UPD" : thisRoomNameByProto p : roomInfo p (nick cl) (roomUpdate rm)), RegisterEvent RoomNameUpdate] where roomUpdate r = r{name = newName} diff -r 6cb7330113d8 -r fc3cb23fd26f gameServer/HWProtoLobbyState.hs --- a/gameServer/HWProtoLobbyState.hs Fri Sep 23 12:47:47 2022 -0400 +++ b/gameServer/HWProtoLobbyState.hs Tue Sep 27 14:59:03 2022 +0300 @@ -40,7 +40,7 @@ (ci, irnc) <- ask let cl = irnc `client` ci rooms <- allRoomInfos - let roomsInfoList = concatMap (\r -> roomInfo (clientProto cl) (maybeNick . liftM (client irnc) $ masterID r) r) . filter (\r -> (roomProto r == clientProto cl)) + let roomsInfoList = concatMap (\r -> roomInfo (clientProto cl) (maybeNick . liftM (client irnc) $ masterID r) r) . filter ((/=) 0 . roomProto) return $ if hasAskedList cl then [] else [ ModifyClient (\c -> c{hasAskedList = True}) , AnswerClients [sendChan cl] ("ROOMS" : roomsInfoList rooms)] @@ -91,7 +91,9 @@ [] let clTeamsNames = map teamname clTeams return $ - if isNothing maybeRI then + if isNothing maybeRI && clientProto cl < 60 && B.isPrefixOf "[v" roomName then + [Warning $ loc "Room version incompatible to your Hedgewars version!"] + else if isNothing maybeRI then [Warning $ loc "No such room."] else if (not sameProto) && (not $ isAdministrator cl) then [Warning $ loc "Room version incompatible to your Hedgewars version!"] diff -r 6cb7330113d8 -r fc3cb23fd26f gameServer/HandlerUtils.hs --- a/gameServer/HandlerUtils.hs Fri Sep 23 12:47:47 2022 -0400 +++ b/gameServer/HandlerUtils.hs Tue Sep 27 14:59:03 2022 +0300 @@ -21,6 +21,7 @@ import Control.Monad.Reader import qualified Data.ByteString.Char8 as B import Data.List +import Data.Word import RoomsAndClients import CoreTypes @@ -74,6 +75,11 @@ let p = clientProto (rnc `client` ci) return . map sendChan . filter (\c -> clientProto c == p) . map (client rnc) $ allClients rnc +allChansProto :: Reader (ClientIndex, IRnC) [(ClientChan, Word16)] +allChansProto = do + (ci, rnc) <- ask + return . map ((\c -> (sendChan c, clientProto c)) . client rnc) $ allClients rnc + answerClient :: [B.ByteString] -> Reader (ClientIndex, IRnC) [Action] answerClient msg = liftM ((: []) . flip AnswerClients msg) thisClientChans diff -r 6cb7330113d8 -r fc3cb23fd26f gameServer/Utils.hs --- a/gameServer/Utils.hs Fri Sep 23 12:47:47 2022 -0400 +++ b/gameServer/Utils.hs Tue Sep 27 14:59:03 2022 +0300 @@ -158,11 +158,16 @@ upperCase :: B.ByteString -> B.ByteString upperCase = UTF8.fromString . map Char.toUpper . UTF8.toString +roomNameByProto :: B.ByteString -> Word16 -> Word16 -> B.ByteString +roomNameByProto roomName roomProto clientProto + | clientProto < 60 && roomProto /= clientProto = B.concat [B.pack "[v", protoNumber2ver roomProto, B.pack "] ", roomName] + | otherwise = roomName + roomInfo :: Word16 -> B.ByteString -> RoomInfo -> [B.ByteString] roomInfo p n r | p < 46 = [ showB $ isJust $ gameInfo r, - name r, + roomNameByProto (name r) (roomProto r) p, showB $ playersIn r, showB $ length $ teams r, n, @@ -172,7 +177,18 @@ ] | p < 48 = [ showB $ isJust $ gameInfo r, - name r, + roomNameByProto (name r) (roomProto r) p, + showB $ playersIn r, + showB $ length $ teams r, + n, + Map.findWithDefault "+rnd+" "MAP" (mapParams r), + head (Map.findWithDefault ["Normal"] "SCRIPT" (params r)), + head (Map.findWithDefault ["Default"] "SCHEME" (params r)), + head (Map.findWithDefault ["Default"] "AMMO" (params r)) + ] + | p < 60 = [ + B.pack roomFlags, + roomNameByProto (name r) (roomProto r) p, showB $ playersIn r, showB $ length $ teams r, n, @@ -190,7 +206,8 @@ Map.findWithDefault "+rnd+" "MAP" (mapParams r), head (Map.findWithDefault ["Normal"] "SCRIPT" (params r)), head (Map.findWithDefault ["Default"] "SCHEME" (params r)), - head (Map.findWithDefault ["Default"] "AMMO" (params r)) + head (Map.findWithDefault ["Default"] "AMMO" (params r)), + showB $ roomProto r ] where roomFlags = concat [