1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-20 21:43:06 +01:00
Files
OpenRCT2/src/openrct2-ui/windows/ServerList.cpp
Duncan d2aca03ff6 Fix #15271. Use formatter to pass description args to text input (#15272)
* Fix #15271. Use formatter to pass description args to text input

Originally passed the variables via global vars which were not updated to 32bit during recent refactors. This removes the global and makes the interface cleaner and corrects the type

* Fix size of arguments
2021-08-24 19:12:05 +01:00

632 lines
22 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#ifndef DISABLE_NETWORK
# include <algorithm>
# include <chrono>
# include <openrct2-ui/interface/Dropdown.h>
# include <openrct2-ui/interface/Widget.h>
# include <openrct2-ui/windows/Window.h>
# include <openrct2/Context.h>
# include <openrct2/config/Config.h>
# include <openrct2/core/Json.hpp>
# include <openrct2/core/String.hpp>
# include <openrct2/drawing/Drawing.h>
# include <openrct2/interface/Colour.h>
# include <openrct2/localisation/Localisation.h>
# include <openrct2/network/ServerList.h>
# include <openrct2/network/network.h>
# include <openrct2/platform/platform.h>
# include <openrct2/sprites.h>
# include <openrct2/util/Util.h>
# include <tuple>
# define WWIDTH_MIN 500
# define WHEIGHT_MIN 300
# define WWIDTH_MAX 1200
# define WHEIGHT_MAX 800
# define ITEM_HEIGHT (3 + 9 + 3)
static char _playerName[32 + 1];
static ServerList _serverList;
static std::future<std::tuple<std::vector<ServerListEntry>, rct_string_id>> _fetchFuture;
static uint32_t _numPlayersOnline = 0;
static rct_string_id _statusText = STR_SERVER_LIST_CONNECTING;
// clang-format off
enum {
WIDX_BACKGROUND,
WIDX_TITLE,
WIDX_CLOSE,
WIDX_PLAYER_NAME_INPUT,
WIDX_LIST,
WIDX_FETCH_SERVERS,
WIDX_ADD_SERVER,
WIDX_START_SERVER
};
enum {
WIDX_LIST_REMOVE,
WIDX_LIST_SPECTATE
};
static rct_widget window_server_list_widgets[] = {
MakeWidget({ 0, 0}, {341, 91}, WindowWidgetType::Frame, WindowColour::Primary ), // panel / background
MakeWidget({ 1, 1}, {338, 14}, WindowWidgetType::Caption, WindowColour::Primary , STR_SERVER_LIST, STR_WINDOW_TITLE_TIP), // title bar
MakeWidget({327, 2}, { 11, 12}, WindowWidgetType::CloseBox, WindowColour::Primary , STR_CLOSE_X, STR_CLOSE_WINDOW_TIP), // close x button
MakeWidget({100, 20}, {245, 12}, WindowWidgetType::TextBox, WindowColour::Secondary ), // player name text box
MakeWidget({ 6, 37}, {332, 14}, WindowWidgetType::Scroll, WindowColour::Secondary ), // server list
MakeWidget({ 6, 53}, {101, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_FETCH_SERVERS ), // fetch servers button
MakeWidget({112, 53}, {101, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_ADD_SERVER ), // add server button
MakeWidget({218, 53}, {101, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_START_SERVER ), // start server button
{ WIDGETS_END },
};
static void window_server_list_close(rct_window *w);
static void window_server_list_mouseup(rct_window *w, rct_widgetindex widgetIndex);
static void window_server_list_resize(rct_window *w);
static void window_server_list_dropdown(rct_window *w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
static void window_server_list_update(rct_window *w);
static void window_server_list_scroll_getsize(rct_window *w, int32_t scrollIndex, int32_t *width, int32_t *height);
static void window_server_list_scroll_mousedown(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
static void window_server_list_scroll_mouseover(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
static void window_server_list_textinput(rct_window *w, rct_widgetindex widgetIndex, char *text);
static OpenRCT2String window_server_list_tooltip(rct_window* const w, const rct_widgetindex widgetIndex, rct_string_id fallback);
static void window_server_list_invalidate(rct_window *w);
static void window_server_list_paint(rct_window *w, rct_drawpixelinfo *dpi);
static void window_server_list_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int32_t scrollIndex);
static rct_window_event_list window_server_list_events([](auto& events)
{
events.close = &window_server_list_close;
events.mouse_up = &window_server_list_mouseup;
events.resize = &window_server_list_resize;
events.dropdown = &window_server_list_dropdown;
events.update = &window_server_list_update;
events.get_scroll_size = &window_server_list_scroll_getsize;
events.scroll_mousedown = &window_server_list_scroll_mousedown;
events.scroll_mouseover = &window_server_list_scroll_mouseover;
events.text_input = &window_server_list_textinput;
events.tooltip = &window_server_list_tooltip;
events.invalidate = &window_server_list_invalidate;
events.paint = &window_server_list_paint;
events.scroll_paint = &window_server_list_scrollpaint;
});
// clang-format on
enum
{
DDIDX_JOIN,
DDIDX_FAVOURITE
};
static int32_t _hoverButtonIndex = -1;
static std::string _version;
static void server_list_get_item_button(int32_t buttonIndex, int32_t x, int32_t y, int32_t width, int32_t* outX, int32_t* outY);
static void join_server(std::string address);
static void server_list_fetch_servers_begin();
static void server_list_fetch_servers_check(rct_window* w);
rct_window* window_server_list_open()
{
rct_window* window;
// Check if window is already open
window = window_bring_to_front_by_class(WC_SERVER_LIST);
if (window != nullptr)
return window;
window = WindowCreateCentred(WWIDTH_MIN, WHEIGHT_MIN, &window_server_list_events, WC_SERVER_LIST, WF_10 | WF_RESIZABLE);
window_server_list_widgets[WIDX_PLAYER_NAME_INPUT].string = _playerName;
window->widgets = window_server_list_widgets;
window->enabled_widgets
= ((1ULL << WIDX_CLOSE) | (1ULL << WIDX_PLAYER_NAME_INPUT) | (1ULL << WIDX_FETCH_SERVERS) | (1ULL << WIDX_ADD_SERVER)
| (1ULL << WIDX_START_SERVER));
WindowInitScrollWidgets(window);
window->no_list_items = 0;
window->selected_list_item = -1;
window->frame_no = 0;
window->min_width = 320;
window->min_height = 90;
window->max_width = window->min_width;
window->max_height = window->min_height;
window->page = 0;
window->list_information_type = 0;
window_set_resize(window, WWIDTH_MIN, WHEIGHT_MIN, WWIDTH_MAX, WHEIGHT_MAX);
safe_strcpy(_playerName, gConfigNetwork.player_name.c_str(), sizeof(_playerName));
_serverList.ReadAndAddFavourites();
window->no_list_items = static_cast<uint16_t>(_serverList.GetCount());
server_list_fetch_servers_begin();
return window;
}
static void window_server_list_close(rct_window* w)
{
_serverList = {};
_fetchFuture = {};
}
static void window_server_list_mouseup(rct_window* w, rct_widgetindex widgetIndex)
{
switch (widgetIndex)
{
case WIDX_CLOSE:
window_close(w);
break;
case WIDX_PLAYER_NAME_INPUT:
window_start_textbox(w, widgetIndex, STR_STRING, _playerName, 63);
break;
case WIDX_LIST:
{
int32_t serverIndex = w->selected_list_item;
if (serverIndex >= 0 && serverIndex < static_cast<int32_t>(_serverList.GetCount()))
{
const auto& server = _serverList.GetServer(serverIndex);
if (server.IsVersionValid())
{
join_server(server.Address);
}
else
{
Formatter ft;
ft.Add<const char*>(server.Version.c_str());
context_show_error(STR_UNABLE_TO_CONNECT_TO_SERVER, STR_MULTIPLAYER_INCORRECT_SOFTWARE_VERSION, ft);
}
}
break;
}
case WIDX_FETCH_SERVERS:
server_list_fetch_servers_begin();
break;
case WIDX_ADD_SERVER:
window_text_input_open(w, widgetIndex, STR_ADD_SERVER, STR_ENTER_HOSTNAME_OR_IP_ADDRESS, {}, STR_NONE, 0, 128);
break;
case WIDX_START_SERVER:
context_open_window(WC_SERVER_START);
break;
}
}
static void window_server_list_resize(rct_window* w)
{
window_set_resize(w, WWIDTH_MIN, WHEIGHT_MIN, WWIDTH_MAX, WHEIGHT_MAX);
}
static void window_server_list_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
{
auto serverIndex = w->selected_list_item;
if (serverIndex >= 0 && serverIndex < static_cast<int32_t>(_serverList.GetCount()))
{
auto& server = _serverList.GetServer(serverIndex);
switch (dropdownIndex)
{
case DDIDX_JOIN:
if (server.IsVersionValid())
{
join_server(server.Address);
}
else
{
Formatter ft;
ft.Add<const char*>(server.Version.c_str());
context_show_error(STR_UNABLE_TO_CONNECT_TO_SERVER, STR_MULTIPLAYER_INCORRECT_SOFTWARE_VERSION, ft);
}
break;
case DDIDX_FAVOURITE:
{
server.Favourite = !server.Favourite;
_serverList.WriteFavourites();
}
break;
}
}
}
static void window_server_list_update(rct_window* w)
{
if (gCurrentTextBox.window.classification == w->classification && gCurrentTextBox.window.number == w->number)
{
window_update_textbox_caret();
widget_invalidate(w, WIDX_PLAYER_NAME_INPUT);
}
server_list_fetch_servers_check(w);
}
static void window_server_list_scroll_getsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
{
*width = 0;
*height = w->no_list_items * ITEM_HEIGHT;
}
static void window_server_list_scroll_mousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
int32_t serverIndex = w->selected_list_item;
if (serverIndex >= 0 && serverIndex < static_cast<int32_t>(_serverList.GetCount()))
{
const auto& server = _serverList.GetServer(serverIndex);
auto listWidget = &w->widgets[WIDX_LIST];
gDropdownItemsFormat[0] = STR_JOIN_GAME;
if (server.Favourite)
{
gDropdownItemsFormat[1] = STR_REMOVE_FROM_FAVOURITES;
}
else
{
gDropdownItemsFormat[1] = STR_ADD_TO_FAVOURITES;
}
auto dropdownPos = ScreenCoordsXY{ w->windowPos.x + listWidget->left + screenCoords.x + 2 - w->scrolls[0].h_left,
w->windowPos.y + listWidget->top + screenCoords.y + 2 - w->scrolls[0].v_top };
WindowDropdownShowText(dropdownPos, 0, COLOUR_GREY, 0, 2);
}
}
static void window_server_list_scroll_mouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
// Item
int32_t index = screenCoords.y / ITEM_HEIGHT;
if (index < 0 || index >= w->no_list_items)
{
index = -1;
}
int32_t hoverButtonIndex = -1;
if (index != -1)
{
int32_t width = w->widgets[WIDX_LIST].width();
int32_t sy = index * ITEM_HEIGHT;
for (int32_t i = 0; i < 2; i++)
{
int32_t bx, by;
server_list_get_item_button(i, 0, sy, width, &bx, &by);
if (screenCoords.x >= bx && screenCoords.y >= by && screenCoords.x < bx + 24 && screenCoords.y < by + 24)
{
hoverButtonIndex = i;
break;
}
}
}
int32_t width = w->widgets[WIDX_LIST].width();
int32_t right = width - 3 - 14 - 10;
if (screenCoords.x < right)
{
w->widgets[WIDX_LIST].tooltip = STR_NONE;
window_tooltip_close();
}
if (w->selected_list_item != index || _hoverButtonIndex != hoverButtonIndex)
{
w->selected_list_item = index;
_hoverButtonIndex = hoverButtonIndex;
window_tooltip_close();
w->Invalidate();
}
}
static void window_server_list_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text)
{
if (text == nullptr || text[0] == 0)
return;
switch (widgetIndex)
{
case WIDX_PLAYER_NAME_INPUT:
if (strcmp(_playerName, text) == 0)
return;
std::fill_n(_playerName, sizeof(_playerName), 0x00);
if (strlen(text) > 0)
{
safe_strcpy(_playerName, text, sizeof(_playerName));
}
if (strlen(_playerName) > 0)
{
gConfigNetwork.player_name = _playerName;
config_save_default();
}
widget_invalidate(w, WIDX_PLAYER_NAME_INPUT);
break;
case WIDX_ADD_SERVER:
{
ServerListEntry entry;
entry.Address = text;
entry.Name = text;
entry.Favourite = true;
_serverList.Add(entry);
_serverList.WriteFavourites();
w->Invalidate();
break;
}
}
}
static OpenRCT2String window_server_list_tooltip(rct_window* const w, const rct_widgetindex widgetIndex, rct_string_id fallback)
{
auto ft = Formatter();
ft.Add<char*>(_version.c_str());
return { fallback, ft };
}
static void window_server_list_invalidate(rct_window* w)
{
window_server_list_widgets[WIDX_BACKGROUND].right = w->width - 1;
window_server_list_widgets[WIDX_BACKGROUND].bottom = w->height - 1;
window_server_list_widgets[WIDX_TITLE].right = w->width - 2;
window_server_list_widgets[WIDX_CLOSE].left = w->width - 2 - 11;
window_server_list_widgets[WIDX_CLOSE].right = w->width - 2 - 11 + 10;
int32_t margin = 6;
int32_t buttonHeight = 13;
int32_t buttonTop = w->height - margin - buttonHeight - 13;
int32_t buttonBottom = buttonTop + buttonHeight;
int32_t listBottom = buttonTop - margin;
window_server_list_widgets[WIDX_PLAYER_NAME_INPUT].right = w->width - 6;
window_server_list_widgets[WIDX_LIST].left = 6;
window_server_list_widgets[WIDX_LIST].right = w->width - 6;
window_server_list_widgets[WIDX_LIST].bottom = listBottom;
window_server_list_widgets[WIDX_FETCH_SERVERS].top = buttonTop;
window_server_list_widgets[WIDX_FETCH_SERVERS].bottom = buttonBottom;
window_server_list_widgets[WIDX_ADD_SERVER].top = buttonTop;
window_server_list_widgets[WIDX_ADD_SERVER].bottom = buttonBottom;
window_server_list_widgets[WIDX_START_SERVER].top = buttonTop;
window_server_list_widgets[WIDX_START_SERVER].bottom = buttonBottom;
w->no_list_items = static_cast<uint16_t>(_serverList.GetCount());
}
static void window_server_list_paint(rct_window* w, rct_drawpixelinfo* dpi)
{
WindowDrawWidgets(w, dpi);
DrawTextBasic(
dpi, w->windowPos + ScreenCoordsXY{ 6, w->widgets[WIDX_PLAYER_NAME_INPUT].top }, STR_PLAYER_NAME, {}, { COLOUR_WHITE });
// Draw version number
std::string version = network_get_version();
auto ft = Formatter();
ft.Add<const char*>(version.c_str());
DrawTextBasic(
dpi, w->windowPos + ScreenCoordsXY{ 324, w->widgets[WIDX_START_SERVER].top + 1 }, STR_NETWORK_VERSION, ft,
{ COLOUR_WHITE });
ft = Formatter();
ft.Add<uint32_t>(_numPlayersOnline);
DrawTextBasic(dpi, w->windowPos + ScreenCoordsXY{ 8, w->height - 15 }, _statusText, ft, { COLOUR_WHITE });
}
static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
{
uint8_t paletteIndex = ColourMapA[w->colours[1]].mid_light;
gfx_clear(dpi, paletteIndex);
int32_t width = w->widgets[WIDX_LIST].width();
ScreenCoordsXY screenCoords;
screenCoords.y = 0;
w->widgets[WIDX_LIST].tooltip = STR_NONE;
for (int32_t i = 0; i < w->no_list_items; i++)
{
if (screenCoords.y >= dpi->y + dpi->height)
continue;
const auto& serverDetails = _serverList.GetServer(i);
bool highlighted = i == w->selected_list_item;
// Draw hover highlight
if (highlighted)
{
gfx_filter_rect(dpi, 0, screenCoords.y, width, screenCoords.y + ITEM_HEIGHT, FilterPaletteID::PaletteDarken1);
_version = serverDetails.Version;
w->widgets[WIDX_LIST].tooltip = STR_NETWORK_VERSION_TIP;
}
colour_t colour = w->colours[1];
if (serverDetails.Favourite)
{
colour = COLOUR_YELLOW;
}
else if (serverDetails.Local)
{
colour = COLOUR_MOSS_GREEN;
}
screenCoords.x = 3;
// Before we draw the server info, we need to know how much room we'll need for player info.
char players[32] = { 0 };
if (serverDetails.MaxPlayers > 0)
{
snprintf(players, sizeof(players), "%d/%d", serverDetails.Players, serverDetails.MaxPlayers);
}
const int16_t numPlayersStringWidth = gfx_get_string_width(players, FontSpriteBase::MEDIUM);
// How much space we have for the server info depends on the size of everything rendered after.
const int16_t spaceAvailableForInfo = width - numPlayersStringWidth - SCROLLBAR_WIDTH - 35;
// Are we showing the server's name or description?
const char* serverInfoToShow = serverDetails.Name.c_str();
if (highlighted && !serverDetails.Description.empty())
{
serverInfoToShow = serverDetails.Description.c_str();
}
// Finally, draw the server information.
auto ft = Formatter();
ft.Add<const char*>(serverInfoToShow);
DrawTextEllipsised(dpi, screenCoords + ScreenCoordsXY{ 0, 3 }, spaceAvailableForInfo, STR_STRING, ft, { colour });
int32_t right = width - 7 - SCROLLBAR_WIDTH;
// Draw compatibility icon
right -= 10;
int32_t compatibilitySpriteId;
if (serverDetails.Version.empty())
{
// Server not online...
compatibilitySpriteId = SPR_G2_RCT1_CLOSE_BUTTON_0;
}
else
{
// Server online... check version
bool correctVersion = serverDetails.Version == network_get_version();
compatibilitySpriteId = correctVersion ? SPR_G2_RCT1_OPEN_BUTTON_2 : SPR_G2_RCT1_CLOSE_BUTTON_2;
}
gfx_draw_sprite(dpi, ImageId(compatibilitySpriteId), { right, screenCoords.y + 1 });
right -= 4;
// Draw lock icon
right -= 8;
if (serverDetails.RequiresPassword)
{
gfx_draw_sprite(dpi, ImageId(SPR_G2_LOCKED), { right, screenCoords.y + 4 });
}
right -= 6;
// Draw number of players
screenCoords.x = right - numPlayersStringWidth;
gfx_draw_string(dpi, screenCoords + ScreenCoordsXY{ 0, 3 }, players, { w->colours[1] });
screenCoords.y += ITEM_HEIGHT;
}
}
static void server_list_get_item_button(int32_t buttonIndex, int32_t x, int32_t y, int32_t width, int32_t* outX, int32_t* outY)
{
*outX = width - 3 - 36 - (30 * buttonIndex);
*outY = y + 2;
}
static void join_server(std::string address)
{
int32_t port = NETWORK_DEFAULT_PORT;
auto beginBracketIndex = address.find('[');
auto endBracketIndex = address.find(']');
auto dotIndex = address.find('.');
auto colonIndex = address.find_last_of(':');
if (colonIndex != std::string::npos)
{
if (endBracketIndex != std::string::npos || dotIndex != std::string::npos)
{
auto ret = std::sscanf(&address[colonIndex + 1], "%d", &port);
assert(ret);
if (ret > 0)
{
address = address.substr(0, colonIndex);
}
}
}
if (beginBracketIndex != std::string::npos && endBracketIndex != std::string::npos)
{
address = address.substr(beginBracketIndex + 1, endBracketIndex - beginBracketIndex - 1);
}
if (!network_begin_client(address.c_str(), port))
{
context_show_error(STR_UNABLE_TO_CONNECT_TO_SERVER, STR_NONE, {});
}
}
static void server_list_fetch_servers_begin()
{
if (_fetchFuture.valid())
{
// A fetch is already in progress
return;
}
_serverList.Clear();
_serverList.ReadAndAddFavourites();
_statusText = STR_SERVER_LIST_CONNECTING;
_fetchFuture = std::async(std::launch::async, [] {
// Spin off background fetches
auto lanF = _serverList.FetchLocalServerListAsync();
auto wanF = _serverList.FetchOnlineServerListAsync();
// Merge or deal with errors
std::vector<ServerListEntry> allEntries;
try
{
auto entries = lanF.get();
allEntries.insert(allEntries.end(), entries.begin(), entries.end());
}
catch (...)
{
}
auto status = STR_NONE;
try
{
auto entries = wanF.get();
allEntries.insert(allEntries.end(), entries.begin(), entries.end());
}
catch (const MasterServerException& e)
{
status = e.StatusText;
}
catch (...)
{
status = STR_SERVER_LIST_NO_CONNECTION;
}
return std::make_tuple(allEntries, status);
});
}
static void server_list_fetch_servers_check(rct_window* w)
{
if (_fetchFuture.valid())
{
auto status = _fetchFuture.wait_for(std::chrono::seconds::zero());
if (status == std::future_status::ready)
{
try
{
auto [entries, statusText] = _fetchFuture.get();
_serverList.AddRange(entries);
_numPlayersOnline = _serverList.GetTotalPlayerCount();
_statusText = STR_X_PLAYERS_ONLINE;
if (statusText != STR_NONE)
{
_statusText = statusText;
}
}
catch (const MasterServerException& e)
{
_statusText = e.StatusText;
}
catch (const std::exception& e)
{
_statusText = STR_SERVER_LIST_NO_CONNECTION;
log_warning("Unable to connect to master server: %s", e.what());
}
_fetchFuture = {};
w->Invalidate();
}
}
}
#endif