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.
--- 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();
}
--- 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
--- 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();
--- 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)
--- 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;
--- 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]]
--- 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
--- 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}
--- 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!"]
--- 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
--- 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 [