public game browsing: show difficulty, speed, players and possible incompatibility
This commit is contained in:
parent
50cca7bf94
commit
1f7b0607a6
8 changed files with 149 additions and 39 deletions
|
|
@ -13,6 +13,7 @@
|
|||
#include "options.h"
|
||||
#include "storm/storm_net.hpp"
|
||||
#include "utils/language.h"
|
||||
#include "utils/utf8.hpp"
|
||||
|
||||
namespace devilution {
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ const char *title = "";
|
|||
|
||||
std::vector<std::unique_ptr<UiListItem>> vecSelGameDlgItems;
|
||||
std::vector<std::unique_ptr<UiItemBase>> vecSelGameDialog;
|
||||
std::vector<std::string> Gamelist;
|
||||
std::vector<GameInfo> Gamelist;
|
||||
uint32_t firstPublicGameInfoRequestSend = 0;
|
||||
int HighlightedItem;
|
||||
|
||||
|
|
@ -63,6 +64,41 @@ void selgame_Free()
|
|||
selgame_FreeVectors();
|
||||
}
|
||||
|
||||
bool IsGameCompatible(const GameData &data)
|
||||
{
|
||||
return (data.versionMajor == PROJECT_VERSION_MAJOR
|
||||
&& data.versionMinor == PROJECT_VERSION_MINOR
|
||||
&& data.versionPatch == PROJECT_VERSION_PATCH
|
||||
&& data.programid == GAME_ID);
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string GetErrorMessageIncompatibility(const GameData &data)
|
||||
{
|
||||
if (data.programid != GAME_ID) {
|
||||
std::string gameMode;
|
||||
switch (data.programid) {
|
||||
case GameIdDiabloFull:
|
||||
gameMode = _("Diablo");
|
||||
break;
|
||||
case GameIdDiabloSpawn:
|
||||
gameMode = _("Diablo Shareware");
|
||||
break;
|
||||
case GameIdHellfireFull:
|
||||
gameMode = _("Hellfire");
|
||||
break;
|
||||
case GameIdHellfireSpawn:
|
||||
gameMode = _("Hellfire Shareware");
|
||||
break;
|
||||
default:
|
||||
return _("The host is running a different game than you.");
|
||||
}
|
||||
return fmt::format(_("The host is running a different game mode ({:s}) than you."), gameMode);
|
||||
} else {
|
||||
return fmt::format(_(/* TRANSLATORS: Error message when somebody tries to join a game running another version. */ "Your version {:s} does not match the host {:d}.{:d}.{:d}."), PROJECT_VERSION, data.versionMajor, data.versionMinor, data.versionPatch).c_str();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void selgame_GameSelection_Init()
|
||||
|
|
@ -116,7 +152,7 @@ void selgame_GameSelection_Init()
|
|||
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(_("None"), -1, UiFlags::ElementDisabled | UiFlags::ColorUiSilver));
|
||||
} else {
|
||||
for (unsigned i = 0; i < Gamelist.size(); i++) {
|
||||
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(Gamelist[i].c_str(), i + 3, UiFlags::ColorUiGold));
|
||||
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(Gamelist[i].name.c_str(), i + 3, UiFlags::ColorUiGold));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +188,52 @@ void selgame_GameSelection_Focus(int value)
|
|||
strcpy(selgame_Description, _("Enter an IP or a hostname and join a game already in progress at that address."));
|
||||
break;
|
||||
default:
|
||||
strcpy(selgame_Description, _("Join the public game already in progress at this address."));
|
||||
const auto &gameInfo = Gamelist[vecSelGameDlgItems[value]->m_value - 3];
|
||||
std::string infoString = _("Join the public game already in progress at this address.");
|
||||
infoString.append("\n\n");
|
||||
if (IsGameCompatible(gameInfo.gameData)) {
|
||||
string_view difficulty;
|
||||
switch (gameInfo.gameData.nDifficulty) {
|
||||
case DIFF_NORMAL:
|
||||
difficulty = _("Normal");
|
||||
break;
|
||||
case DIFF_NIGHTMARE:
|
||||
difficulty = _("Nightmare");
|
||||
break;
|
||||
case DIFF_HELL:
|
||||
difficulty = _("Hell");
|
||||
break;
|
||||
}
|
||||
infoString.append(fmt::format(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}"), difficulty));
|
||||
infoString.append("\n");
|
||||
switch (gameInfo.gameData.nTickRate) {
|
||||
case 20:
|
||||
infoString.append(_("Speed: Normal"));
|
||||
break;
|
||||
case 30:
|
||||
infoString.append(_("Speed: Fast"));
|
||||
break;
|
||||
case 40:
|
||||
infoString.append(_("Speed: Faster"));
|
||||
break;
|
||||
case 50:
|
||||
infoString.append(_("Speed: Fastest"));
|
||||
break;
|
||||
default:
|
||||
// This should not occure, so no translations is needed
|
||||
infoString.append(fmt::format("Speed: {}", gameInfo.gameData.nTickRate));
|
||||
break;
|
||||
}
|
||||
infoString.append("\n");
|
||||
infoString.append(_("Players: "));
|
||||
for (auto &playerName : gameInfo.players) {
|
||||
infoString.append(playerName);
|
||||
infoString.append(" ");
|
||||
}
|
||||
} else {
|
||||
infoString.append(GetErrorMessageIncompatibility(gameInfo.gameData));
|
||||
}
|
||||
CopyUtf8(selgame_Description, infoString, sizeof(selgame_Description));
|
||||
break;
|
||||
}
|
||||
strcpy(selgame_Description, WordWrapString(selgame_Description, DESCRIPTION_WIDTH).c_str());
|
||||
|
|
@ -177,7 +258,7 @@ void selgame_GameSelection_Select(int value)
|
|||
selgame_selectedGame = value;
|
||||
|
||||
if (value > 2) {
|
||||
strcpy(selgame_Ip, Gamelist[value - 3].c_str());
|
||||
strcpy(selgame_Ip, Gamelist[value - 3].name.c_str());
|
||||
selgame_Password_Select(value);
|
||||
return;
|
||||
}
|
||||
|
|
@ -454,25 +535,15 @@ void selgame_Password_Init(int /*value*/)
|
|||
UiInitList(nullptr, selgame_Password_Select, selgame_Password_Esc, vecSelGameDialog);
|
||||
}
|
||||
|
||||
static bool IsGameCompatible(const GameData &data)
|
||||
static bool IsGameCompatibleWithErrorMessage(const GameData &data)
|
||||
{
|
||||
if (data.versionMajor == PROJECT_VERSION_MAJOR
|
||||
&& data.versionMinor == PROJECT_VERSION_MINOR
|
||||
&& data.versionPatch == PROJECT_VERSION_PATCH
|
||||
&& data.programid == GAME_ID) {
|
||||
if (IsGameCompatible(data))
|
||||
return IsDifficultyAllowed(data.nDifficulty);
|
||||
}
|
||||
|
||||
selgame_Free();
|
||||
|
||||
if (data.programid != GAME_ID) {
|
||||
UiSelOkDialog(title, _("The host is running a different game than you."), false);
|
||||
} else {
|
||||
char msg[128];
|
||||
strcpy(msg, fmt::format(_(/* TRANSLATORS: Error message when somebody tries to join a game running another version. */ "Your version {:s} does not match the host {:d}.{:d}.{:d}."), PROJECT_VERSION, data.versionMajor, data.versionMinor, data.versionPatch).c_str());
|
||||
|
||||
UiSelOkDialog(title, msg, false);
|
||||
}
|
||||
std::string errorMessage = GetErrorMessageIncompatibility(data);
|
||||
UiSelOkDialog(title, errorMessage.c_str(), false);
|
||||
|
||||
selgame_Init();
|
||||
|
||||
|
|
@ -495,7 +566,7 @@ void selgame_Password_Select(int /*value*/)
|
|||
if (selgame_selectedGame > 1) {
|
||||
strcpy(sgOptions.Network.szPreviousHost, selgame_Ip);
|
||||
if (SNetJoinGame(selgame_Ip, gamePassword, gdwPlayerId)) {
|
||||
if (!IsGameCompatible(*m_game_data)) {
|
||||
if (!IsGameCompatibleWithErrorMessage(*m_game_data)) {
|
||||
InitGameInfo();
|
||||
selgame_GameSelection_Select(1);
|
||||
return;
|
||||
|
|
@ -562,7 +633,7 @@ void RefreshGameList()
|
|||
}
|
||||
|
||||
if (lastUpdate == 0 || currentTime - lastUpdate > 5000) {
|
||||
std::vector<std::string> gamelist = DvlNet_GetGamelist();
|
||||
std::vector<GameInfo> gamelist = DvlNet_GetGamelist();
|
||||
Gamelist.clear();
|
||||
for (unsigned i = 0; i < gamelist.size(); i++) {
|
||||
Gamelist.push_back(gamelist[i]);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,11 @@
|
|||
|
||||
namespace devilution {
|
||||
|
||||
#define GAME_ID (gbIsHellfire ? (gbIsSpawn ? LoadBE32("HSHR") : LoadBE32("HRTL")) : (gbIsSpawn ? LoadBE32("DSHR") : LoadBE32("DRTL")))
|
||||
constexpr uint32_t GameIdDiabloFull = LoadBE32("DRTL");
|
||||
constexpr uint32_t GameIdDiabloSpawn = LoadBE32("DSHR");
|
||||
constexpr uint32_t GameIdHellfireFull = LoadBE32("HRTL");
|
||||
constexpr uint32_t GameIdHellfireSpawn = LoadBE32("HSHR");
|
||||
#define GAME_ID (gbIsHellfire ? (gbIsSpawn ? GameIdHellfireSpawn : GameIdHellfireFull) : (gbIsSpawn ? GameIdDiabloSpawn : GameIdDiabloFull))
|
||||
|
||||
#define NUMLEVELS 25
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "multi.h"
|
||||
#include "storm/storm_net.hpp"
|
||||
|
||||
namespace devilution {
|
||||
|
|
@ -57,9 +58,9 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual std::vector<std::string> get_gamelist()
|
||||
virtual std::vector<GameInfo> get_gamelist()
|
||||
{
|
||||
return std::vector<std::string>();
|
||||
return std::vector<GameInfo>();
|
||||
}
|
||||
|
||||
static std::unique_ptr<abstract_net> MakeNet(provider_t provider);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public:
|
|||
virtual std::string make_default_gamename();
|
||||
virtual bool send_info_request();
|
||||
virtual void clear_gamelist();
|
||||
virtual std::vector<std::string> get_gamelist();
|
||||
virtual std::vector<GameInfo> get_gamelist();
|
||||
|
||||
virtual ~base_protocol() = default;
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ private:
|
|||
|
||||
endpoint firstpeer;
|
||||
std::string gamename;
|
||||
std::map<std::string, endpoint> game_list;
|
||||
std::map<std::string, std::tuple<GameData, std::vector<std::string>, endpoint>> game_list;
|
||||
std::array<endpoint, MAX_PLRS> peers;
|
||||
|
||||
plr_t get_master();
|
||||
|
|
@ -86,7 +86,7 @@ bool base_protocol<P>::wait_firstpeer()
|
|||
// wait for peer for 5 seconds
|
||||
for (auto i = 0; i < 500; ++i) {
|
||||
if (game_list.count(gamename)) {
|
||||
firstpeer = game_list[gamename];
|
||||
firstpeer = std::get<2>(game_list[gamename]);
|
||||
break;
|
||||
}
|
||||
send_info_request();
|
||||
|
|
@ -229,10 +229,24 @@ template <class P>
|
|||
void base_protocol<P>::recv_decrypted(packet &pkt, endpoint sender)
|
||||
{
|
||||
if (pkt.Source() == PLR_BROADCAST && pkt.Destination() == PLR_MASTER && pkt.Type() == PT_INFO_REPLY) {
|
||||
std::string pname;
|
||||
pname.resize(pkt.Info().size());
|
||||
std::memcpy(&pname[0], pkt.Info().data(), pkt.Info().size());
|
||||
game_list[pname] = sender;
|
||||
constexpr size_t sizePlayerName = (sizeof(char) * PLR_NAME_LEN);
|
||||
size_t neededSize = sizeof(GameData) + (sizePlayerName * MAX_PLRS);
|
||||
if (pkt.Info().size() < neededSize)
|
||||
return;
|
||||
const GameData *gameData = (const GameData *)pkt.Info().data();
|
||||
std::vector<std::string> playerNames;
|
||||
for (size_t i = 0; i < MAX_PLRS; i++) {
|
||||
std::string playerName;
|
||||
const char *playerNamePointer = (const char *)(pkt.Info().data() + sizeof(GameData) + (i * sizePlayerName));
|
||||
playerName.append(playerNamePointer, strnlen(playerNamePointer, PLR_NAME_LEN));
|
||||
if (!playerName.empty())
|
||||
playerNames.push_back(playerName);
|
||||
}
|
||||
std::string gameName;
|
||||
size_t gameNameSize = pkt.Info().size() - neededSize;
|
||||
gameName.resize(gameNameSize);
|
||||
std::memcpy(&gameName[0], pkt.Info().data() + neededSize, gameNameSize);
|
||||
game_list[gameName] = std::make_tuple(*gameData, playerNames, sender);
|
||||
return;
|
||||
}
|
||||
recv_ingame(pkt, sender);
|
||||
|
|
@ -247,8 +261,17 @@ void base_protocol<P>::recv_ingame(packet &pkt, endpoint sender)
|
|||
} else if (pkt.Type() == PT_INFO_REQUEST) {
|
||||
if ((plr_self != PLR_BROADCAST) && (get_master() == plr_self)) {
|
||||
buffer_t buf;
|
||||
buf.resize(gamename.size());
|
||||
std::memcpy(buf.data(), &gamename[0], gamename.size());
|
||||
constexpr size_t sizePlayerName = (sizeof(char) * PLR_NAME_LEN);
|
||||
buf.resize(game_init_info.size() + (sizePlayerName * MAX_PLRS) + gamename.size());
|
||||
std::memcpy(buf.data(), &game_init_info[0], game_init_info.size());
|
||||
for (size_t i = 0; i < MAX_PLRS; i++) {
|
||||
if (Players[i].plractive) {
|
||||
std::memcpy(buf.data() + game_init_info.size() + (i * sizePlayerName), &Players[i]._pName, sizePlayerName);
|
||||
} else {
|
||||
std::memset(buf.data() + game_init_info.size() + (i * sizePlayerName), '\0', sizePlayerName);
|
||||
}
|
||||
}
|
||||
std::memcpy(buf.data() + game_init_info.size() + (sizePlayerName * MAX_PLRS), &gamename[0], gamename.size());
|
||||
auto reply = pktfty->make_packet<PT_INFO_REPLY>(PLR_BROADCAST,
|
||||
PLR_MASTER,
|
||||
buf);
|
||||
|
|
@ -280,12 +303,12 @@ void base_protocol<P>::clear_gamelist()
|
|||
}
|
||||
|
||||
template <class P>
|
||||
std::vector<std::string> base_protocol<P>::get_gamelist()
|
||||
std::vector<GameInfo> base_protocol<P>::get_gamelist()
|
||||
{
|
||||
recv();
|
||||
std::vector<std::string> ret;
|
||||
std::vector<GameInfo> ret;
|
||||
for (auto &s : game_list) {
|
||||
ret.push_back(s.first);
|
||||
ret.push_back({ s.first, std::get<0>(s.second), std::get<1>(s.second) });
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public:
|
|||
virtual std::string make_default_gamename();
|
||||
virtual bool send_info_request();
|
||||
virtual void clear_gamelist();
|
||||
virtual std::vector<std::string> get_gamelist();
|
||||
virtual std::vector<GameInfo> get_gamelist();
|
||||
virtual void setup_password(std::string pw);
|
||||
virtual void clear_password();
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ void cdwrap<T>::clear_gamelist()
|
|||
}
|
||||
|
||||
template <class T>
|
||||
std::vector<std::string> cdwrap<T>::get_gamelist()
|
||||
std::vector<GameInfo> cdwrap<T>::get_gamelist()
|
||||
{
|
||||
return dvlnet_wrap->get_gamelist();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "msg.h"
|
||||
#include "utils/attributes.h"
|
||||
|
|
@ -31,6 +33,13 @@ struct GameData {
|
|||
uint8_t bFriendlyFire;
|
||||
};
|
||||
|
||||
/* @brief Contains info of running public game (for game list browsing) */
|
||||
struct GameInfo {
|
||||
std::string name;
|
||||
GameData gameData;
|
||||
std::vector<std::string> players;
|
||||
};
|
||||
|
||||
extern bool gbSomebodyWonGameKludge;
|
||||
extern char szPlayerDescript[128];
|
||||
extern uint16_t sgwPackPlrOffsetTbl[MAX_PLRS];
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ void DvlNet_ClearGamelist()
|
|||
return dvlnet_inst->clear_gamelist();
|
||||
}
|
||||
|
||||
std::vector<std::string> DvlNet_GetGamelist()
|
||||
std::vector<GameInfo> DvlNet_GetGamelist()
|
||||
{
|
||||
return dvlnet_inst->get_gamelist();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "multi.h"
|
||||
|
||||
namespace devilution {
|
||||
|
||||
enum game_info : uint8_t {
|
||||
|
|
@ -167,7 +169,7 @@ void SNetGetProviderCaps(struct _SNETCAPS *);
|
|||
|
||||
bool DvlNet_SendInfoRequest();
|
||||
void DvlNet_ClearGamelist();
|
||||
std::vector<std::string> DvlNet_GetGamelist();
|
||||
std::vector<GameInfo> DvlNet_GetGamelist();
|
||||
void DvlNet_SetPassword(std::string pw);
|
||||
void DvlNet_ClearPassword();
|
||||
bool DvlNet_IsPublicGame();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue