OpenRCT2/src/openrct2-ui/windows/Player.cpp

656 lines
21 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2024 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.
*****************************************************************************/
#include <openrct2-ui/interface/Dropdown.h>
#include <openrct2-ui/interface/Widget.h>
#include <openrct2-ui/windows/Window.h>
#include <openrct2/Input.h>
#include <openrct2/actions/PlayerKickAction.h>
#include <openrct2/actions/PlayerSetGroupAction.h>
#include <openrct2/drawing/Drawing.h>
#include <openrct2/localisation/Formatter.h>
#include <openrct2/network/NetworkAction.h>
#include <openrct2/network/network.h>
#include <openrct2/sprites.h>
#include <openrct2/util/Util.h>
#include <utility>
namespace OpenRCT2::Ui::Windows
{
// clang-format off
enum WindowPlayerPage {
WINDOW_PLAYER_PAGE_OVERVIEW,
WINDOW_PLAYER_PAGE_STATISTICS,
};
#pragma region Widgets
enum WindowPlayerWidgetIdx {
WIDX_BACKGROUND,
WIDX_TITLE,
WIDX_CLOSE,
WIDX_PAGE_BACKGROUND,
WIDX_TAB_1,
WIDX_TAB_2,
WIDX_GROUP = 6,
WIDX_GROUP_DROPDOWN,
WIDX_LOCATE,
WIDX_KICK,
WIDX_VIEWPORT,
};
#define WINDOW_PLAYER_COMMON_WIDGETS \
MakeWidget({ 0, 0}, {192, 157}, WindowWidgetType::Frame, WindowColour::Primary ), /* Panel / Background */ \
MakeWidget({ 1, 1}, {190, 14}, WindowWidgetType::Caption, WindowColour::Primary , STR_STRING, STR_WINDOW_TITLE_TIP), /* Title */ \
MakeWidget({179, 2}, { 11, 12}, WindowWidgetType::CloseBox, WindowColour::Primary , STR_CLOSE_X, STR_CLOSE_WINDOW_TIP), /* Close x button */ \
MakeWidget({ 0, 43}, {192, 114}, WindowWidgetType::Resize, WindowColour::Secondary ), /* Resize */ \
MakeTab ({ 3, 17} ), /* Tab 1 */ \
MakeTab ({ 34, 17} ) /* Tab 2 */
static Widget window_player_overview_widgets[] = {
WINDOW_PLAYER_COMMON_WIDGETS,
MakeWidget({ 3, 46}, {175, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ), // Permission group
MakeWidget({167, 47}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ),
MakeWidget({179, 45}, { 12, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_LOCATE), STR_LOCATE_PLAYER_TIP), // Locate button
MakeWidget({179, 69}, { 12, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_DEMOLISH), STR_KICK_PLAYER_TIP ), // Kick button
MakeWidget({ 3, 60}, {175, 61}, WindowWidgetType::Viewport, WindowColour::Secondary ), // Viewport
kWidgetsEnd,
};
static Widget window_player_statistics_widgets[] = {
WINDOW_PLAYER_COMMON_WIDGETS,
kWidgetsEnd,
};
static Widget *window_player_page_widgets[] = {
window_player_overview_widgets,
window_player_statistics_widgets,
};
#pragma endregion
// clang-format on
class PlayerWindow final : public Window
{
int16_t _previousRotation = -1;
bool _drawViewport = true;
public:
void Init(const uint8_t id)
{
number = id;
InitScrollWidgets();
SetPage(WINDOW_PLAYER_PAGE_OVERVIEW);
}
#pragma region Events
void OnOpen() override
{
page = 0;
frame_no = 0;
list_information_type = 0;
min_width = 210;
min_height = 134;
max_width = 500;
max_height = 450;
Invalidate();
widgets = window_player_page_widgets[WINDOW_PLAYER_PAGE_OVERVIEW];
hold_down_widgets = 0;
pressed_widgets = 0;
}
void OnResize() override
{
switch (page)
{
case WINDOW_PLAYER_PAGE_OVERVIEW:
OnResizeOverview();
break;
case WINDOW_PLAYER_PAGE_STATISTICS:
OnResizeStatistics();
break;
}
}
void OnUpdate() override
{
switch (page)
{
case WINDOW_PLAYER_PAGE_OVERVIEW:
OnUpdateOverview();
break;
case WINDOW_PLAYER_PAGE_STATISTICS:
OnUpdateStatistics();
break;
}
}
void OnPrepareDraw() override
{
switch (page)
{
case WINDOW_PLAYER_PAGE_OVERVIEW:
OnPrepareDrawOverview();
break;
case WINDOW_PLAYER_PAGE_STATISTICS:
OnPrepareDrawStatistics();
break;
}
}
void OnDraw(DrawPixelInfo& dpi) override
{
switch (page)
{
case WINDOW_PLAYER_PAGE_OVERVIEW:
OnDrawOverview(dpi);
break;
case WINDOW_PLAYER_PAGE_STATISTICS:
OnDrawStatistics(dpi);
break;
}
}
void OnMouseDown(WidgetIndex widgetIndex) override
{
switch (page)
{
case WINDOW_PLAYER_PAGE_OVERVIEW:
OnMouseDownOverview(widgetIndex);
break;
}
}
void OnMouseUp(WidgetIndex widgetIndex) override
{
switch (widgetIndex)
{
case WIDX_CLOSE:
Close();
return;
case WIDX_TAB_1:
case WIDX_TAB_2:
SetPage(widgetIndex - WIDX_TAB_1);
return;
}
switch (page)
{
case WINDOW_PLAYER_PAGE_OVERVIEW:
OnMouseUpOverview(widgetIndex);
break;
}
}
void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override
{
switch (page)
{
case WINDOW_PLAYER_PAGE_OVERVIEW:
OnDropdownOverview(widgetIndex, selectedIndex);
break;
}
}
#pragma endregion
private:
void SetPage(int32_t newPage)
{
int32_t originalPage = page;
page = newPage;
frame_no = 0;
hold_down_widgets = 0;
pressed_widgets = 0;
widgets = window_player_page_widgets[newPage];
Invalidate();
OnResize();
OnPrepareDraw();
InitScrollWidgets();
Invalidate();
if (page == WINDOW_PLAYER_PAGE_OVERVIEW)
{
if (viewport == nullptr)
{
const auto viewportFocus = Focus(TileCoordsXYZ(128, 128, 0).ToCoordsXYZ());
ViewportCreate(this, windowPos, width, height, viewportFocus);
flags |= WF_NO_SCROLLING;
OnPrepareDraw();
UpdateViewport(false);
}
else if (originalPage != page)
{
OnPrepareDraw();
UpdateViewport(false);
}
}
else
{
RemoveViewport();
}
}
void DrawTabImages(DrawPixelInfo& dpi)
{
Widget* widget;
// Tab 1
if (!IsWidgetDisabled(WIDX_TAB_1))
{
widget = &this->widgets[WIDX_TAB_1];
auto screenCoords = windowPos + ScreenCoordsXY{ widget->left, widget->top };
GfxDrawSprite(dpi, ImageId(SPR_PEEP_LARGE_FACE_NORMAL), screenCoords);
}
// Tab 2
if (!IsWidgetDisabled(WIDX_TAB_2))
{
widget = &this->widgets[WIDX_TAB_2];
auto screenCoords = windowPos + ScreenCoordsXY{ widget->left, widget->top };
int32_t imageId = SPR_TAB_FINANCES_SUMMARY_0;
if (page == WINDOW_PLAYER_PAGE_STATISTICS)
{
imageId += (frame_no / 2) & 7;
}
GfxDrawSprite(dpi, ImageId(imageId), screenCoords);
}
}
void UpdateViewport(bool scroll)
{
int32_t playerIndex = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (playerIndex == -1)
{
return;
}
if (viewport != nullptr)
{
auto coord = NetworkGetPlayerLastActionCoord(playerIndex);
if (coord.x != 0 || coord.y != 0 || coord.z != 0)
{
auto centreLoc = centre_2d_coordinates(coord, viewport);
if (!centreLoc.has_value())
{
return;
}
// Don't scroll if the view was originally undefined
if (!_drawViewport)
{
scroll = false;
}
if (!scroll || savedViewPos != centreLoc.value())
{
flags |= WF_SCROLLING_TO_LOCATION;
savedViewPos = centreLoc.value();
if (!scroll)
{
viewport->viewPos = centreLoc.value();
}
InvalidateWidget(WIDX_VIEWPORT);
}
// Draw the viewport
_drawViewport = true;
}
else
{
// Don't draw the viewport
_drawViewport = false;
}
}
}
void UpdateTitle()
{
auto ft = Formatter::Common();
int32_t player = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (player != -1)
{
ft.Add<const char*>(NetworkGetPlayerName(player)); // set title caption to player name
}
else
{
ft.Add<const char*>("");
}
}
#pragma region Overview
void OnResizeOverview()
{
WindowSetResize(*this, 240, 170, 500, 300);
}
void OnUpdateOverview()
{
frame_no++;
InvalidateWidget(WIDX_TAB_1 + page);
if (NetworkGetPlayerIndex(static_cast<uint8_t>(number)) == -1)
{
Close();
return;
}
// Update viewport
bool scroll = true;
// Use this spare window field for rotation check
if (_previousRotation != GetCurrentRotation())
{
_previousRotation = GetCurrentRotation();
scroll = false;
}
UpdateViewport(scroll);
}
void OnPrepareDrawOverview()
{
int32_t playerIndex = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (playerIndex == -1)
{
return;
}
if (window_player_page_widgets[page] != widgets)
{
widgets = window_player_page_widgets[page];
InitScrollWidgets();
}
pressed_widgets &= ~(WIDX_TAB_1);
pressed_widgets &= ~(WIDX_TAB_2);
pressed_widgets |= 1uLL << (page + WIDX_TAB_1);
UpdateTitle();
ResizeFrameWithPage();
widgets[WIDX_LOCATE].right = width - 2;
widgets[WIDX_LOCATE].left = width - 25;
widgets[WIDX_KICK].right = width - 2;
widgets[WIDX_KICK].left = width - 25;
widgets[WIDX_VIEWPORT].right = width - 26;
widgets[WIDX_VIEWPORT].bottom = height - 14;
int32_t groupDropdownWidth = widgets[WIDX_GROUP].width();
widgets[WIDX_GROUP].left = (width - groupDropdownWidth) / 2;
widgets[WIDX_GROUP].right = widgets[WIDX_GROUP].left + groupDropdownWidth;
widgets[WIDX_GROUP_DROPDOWN].left = widgets[WIDX_GROUP].right - 10;
widgets[WIDX_GROUP_DROPDOWN].right = widgets[WIDX_GROUP].right;
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_2);
if (viewport != nullptr)
{
Widget* viewportWidget = &window_player_overview_widgets[WIDX_VIEWPORT];
viewport->pos = windowPos + ScreenCoordsXY{ viewportWidget->left, viewportWidget->top };
viewport->width = viewportWidget->width();
viewport->height = viewportWidget->height();
viewport->view_width = viewport->zoom.ApplyTo(viewport->width);
viewport->view_height = viewport->zoom.ApplyTo(viewport->height);
}
// Only enable kick button for other players
const bool canKick = NetworkCanPerformAction(NetworkGetCurrentPlayerGroupIndex(), NetworkPermission::KickPlayer);
const bool isServer = NetworkGetPlayerFlags(playerIndex) & NETWORK_PLAYER_FLAG_ISSERVER;
const bool isOwnWindow = (NetworkGetCurrentPlayerId() == number);
WidgetSetEnabled(*this, WIDX_KICK, canKick && !isOwnWindow && !isServer);
}
void OnDrawOverview(DrawPixelInfo& dpi)
{
DrawWidgets(dpi);
DrawTabImages(dpi);
int32_t player = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (player == -1)
{
return;
}
// Draw current group
int32_t groupindex = NetworkGetGroupIndex(NetworkGetPlayerGroup(player));
if (groupindex != -1)
{
Widget* widget = &window_player_overview_widgets[WIDX_GROUP];
thread_local std::string _buffer;
_buffer.assign("{WINDOW_COLOUR_2}");
_buffer += NetworkGetGroupName(groupindex);
auto ft = Formatter();
ft.Add<const char*>(_buffer.c_str());
DrawTextEllipsised(
dpi, windowPos + ScreenCoordsXY{ widget->midX() - 5, widget->top }, widget->width() - 8, STR_STRING, ft,
{ TextAlignment::CENTRE });
}
// Draw ping
auto screenCoords = windowPos + ScreenCoordsXY{ 90, 24 };
auto ft = Formatter();
ft.Add<StringId>(STR_PING);
DrawTextBasic(dpi, screenCoords, STR_WINDOW_COLOUR_2_STRINGID, ft);
char ping[64];
snprintf(ping, 64, "%d ms", NetworkGetPlayerPing(player));
GfxDrawString(dpi, screenCoords + ScreenCoordsXY(30, 0), ping, { colours[2] });
// Draw last action
screenCoords = windowPos + ScreenCoordsXY{ width / 2, height - 13 };
int32_t updatedWidth = this->width - 8;
int32_t lastaction = NetworkGetPlayerLastAction(player, 0);
ft = Formatter();
if (lastaction != -999)
{
ft.Add<StringId>(NetworkGetActionNameStringID(lastaction));
}
else
{
ft.Add<StringId>(STR_ACTION_NA);
}
DrawTextEllipsised(dpi, screenCoords, updatedWidth, STR_LAST_ACTION_RAN, ft, { TextAlignment::CENTRE });
if (viewport != nullptr && _drawViewport)
{
WindowDrawViewport(dpi, *this);
}
}
void OnMouseDownOverview(WidgetIndex widgetIndex)
{
auto* widget = &widgets[widgetIndex];
switch (widgetIndex)
{
case WIDX_GROUP_DROPDOWN:
ShowGroupDropdownOverview(widget);
break;
}
}
void OnMouseUpOverview(WidgetIndex widgetIndex)
{
switch (widgetIndex)
{
case WIDX_LOCATE:
{
WindowBase* mainWindow = WindowGetMain();
if (mainWindow != nullptr)
{
int32_t player = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (player == -1)
{
return;
}
auto coord = NetworkGetPlayerLastActionCoord(player);
if (coord.x || coord.y || coord.z)
{
WindowScrollToLocation(*mainWindow, coord);
}
}
}
break;
case WIDX_KICK:
{
auto kickPlayerAction = PlayerKickAction(number);
GameActions::Execute(&kickPlayerAction);
}
break;
}
}
void OnDropdownOverview(WidgetIndex widgetIndex, int32_t dropdownIndex)
{
const auto playerId = static_cast<uint8_t>(number);
const auto playerIdx = NetworkGetPlayerIndex(playerId);
if (playerIdx == -1)
{
return;
}
if (dropdownIndex == -1)
{
return;
}
const auto groupId = NetworkGetGroupID(dropdownIndex);
const auto windowHandle = std::make_pair(classification, number);
auto playerSetGroupAction = PlayerSetGroupAction(playerId, groupId);
playerSetGroupAction.SetCallback([windowHandle](const GameAction* ga, const GameActions::Result* result) {
if (result->Error == GameActions::Status::Ok)
{
WindowInvalidateByNumber(windowHandle.first, windowHandle.second);
}
});
GameActions::Execute(&playerSetGroupAction);
}
void ShowGroupDropdownOverview(Widget* widget)
{
Widget* dropdownWidget;
int32_t numItems, i;
int32_t player = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (player == -1)
{
return;
}
dropdownWidget = widget - 1;
numItems = NetworkGetNumGroups();
WindowDropdownShowTextCustomWidth(
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
colours[1], 0, 0, numItems, widget->right - dropdownWidget->left);
for (i = 0; i < NetworkGetNumGroups(); i++)
{
gDropdownItems[i].Format = STR_OPTIONS_DROPDOWN_ITEM;
gDropdownItems[i].Args = reinterpret_cast<uintptr_t>(NetworkGetGroupName(i));
}
Dropdown::SetChecked(NetworkGetGroupIndex(NetworkGetPlayerGroup(player)), true);
}
#pragma endregion
#pragma region Statistics
void OnResizeStatistics()
{
WindowSetResize(*this, 210, 80, 210, 80);
}
void OnUpdateStatistics()
{
frame_no++;
InvalidateWidget(WIDX_TAB_1 + page);
if (NetworkGetPlayerIndex(static_cast<uint8_t>(number)) == -1)
{
Close();
}
}
void OnPrepareDrawStatistics()
{
if (window_player_page_widgets[page] != widgets)
{
widgets = window_player_page_widgets[page];
InitScrollWidgets();
}
pressed_widgets &= ~(WIDX_TAB_1);
pressed_widgets &= ~(WIDX_TAB_2);
pressed_widgets |= 1uLL << (page + WIDX_TAB_1);
UpdateTitle();
ResizeFrameWithPage();
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_2);
}
void OnDrawStatistics(DrawPixelInfo& dpi)
{
DrawWidgets(dpi);
DrawTabImages(dpi);
int32_t player = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (player == -1)
{
return;
}
auto screenCoords = windowPos
+ ScreenCoordsXY{ window_player_overview_widgets[WIDX_PAGE_BACKGROUND].left + 4,
window_player_overview_widgets[WIDX_PAGE_BACKGROUND].top + 4 };
auto ft = Formatter();
ft.Add<uint32_t>(NetworkGetPlayerCommandsRan(player));
DrawTextBasic(dpi, screenCoords, STR_COMMANDS_RAN, ft);
screenCoords.y += kListRowHeight;
ft = Formatter();
ft.Add<uint32_t>(NetworkGetPlayerMoneySpent(player));
DrawTextBasic(dpi, screenCoords, STR_MONEY_SPENT, ft);
}
#pragma endregion
};
WindowBase* PlayerOpen(uint8_t id)
{
auto* window = static_cast<PlayerWindow*>(WindowBringToFrontByNumber(WindowClass::Player, id));
if (window == nullptr)
{
window = WindowCreate<PlayerWindow>(WindowClass::Player, 240, 170, WF_RESIZABLE);
}
window->Init(id);
return window;
}
} // namespace OpenRCT2::Ui::Windows