Browse Source

public game browsing: show difficulty, speed, players and possible incompatibility

pull/4077/head
obligaron 4 years ago committed by Anders Jenbo
parent
commit
1f7b0607a6
  1. 111
      Source/DiabloUI/selgame.cpp
  2. 6
      Source/diablo.h
  3. 5
      Source/dvlnet/abstract_net.h
  4. 47
      Source/dvlnet/base_protocol.h
  5. 4
      Source/dvlnet/cdwrap.h
  6. 9
      Source/multi.h
  7. 2
      Source/storm/storm_net.cpp
  8. 4
      Source/storm/storm_net.hpp

111
Source/DiabloUI/selgame.cpp

@ -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]);

6
Source/diablo.h

@ -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
Source/dvlnet/abstract_net.h

@ -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);

47
Source/dvlnet/base_protocol.h

@ -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;
}

4
Source/dvlnet/cdwrap.h

@ -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();
}

9
Source/multi.h

@ -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];

2
Source/storm/storm_net.cpp

@ -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();
}

4
Source/storm/storm_net.hpp

@ -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…
Cancel
Save