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

656 lines
21 KiB
C++
Raw Normal View History

2016-01-20 23:45:09 +01:00
/*****************************************************************************
* 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.
*****************************************************************************/
2016-01-20 23:45:09 +01:00
2018-06-22 23:21:44 +02:00
#include <openrct2-ui/interface/Dropdown.h>
#include <openrct2-ui/interface/Widget.h>
2017-09-06 15:09:18 +02:00
#include <openrct2-ui/windows/Window.h>
2017-12-12 14:52:57 +01:00
#include <openrct2/Input.h>
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include <openrct2/actions/PlayerKickAction.h>
#include <openrct2/actions/PlayerSetGroupAction.h>
2018-06-22 23:21:44 +02:00
#include <openrct2/drawing/Drawing.h>
2021-12-12 00:06:06 +01:00
#include <openrct2/localisation/Formatter.h>
#include <openrct2/network/NetworkAction.h>
2018-06-22 23:21:44 +02:00
#include <openrct2/network/network.h>
2017-09-06 15:09:18 +02:00
#include <openrct2/sprites.h>
2017-12-13 13:02:24 +01:00
#include <openrct2/util/Util.h>
#include <utility>
2016-01-20 23:45:09 +01:00
namespace OpenRCT2::Ui::Windows
{
// clang-format off
enum WindowPlayerPage {
WINDOW_PLAYER_PAGE_OVERVIEW,
WINDOW_PLAYER_PAGE_STATISTICS,
2016-01-20 23:45:09 +01:00
};
#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,
2016-01-20 23:45:09 +01:00
};
#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 */
2022-12-24 16:50:29 +01:00
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,
};
2022-12-24 16:50:29 +01:00
static Widget window_player_statistics_widgets[] = {
WINDOW_PLAYER_COMMON_WIDGETS,
kWidgetsEnd,
2016-01-20 23:45:09 +01:00
};
2022-12-24 16:50:29 +01:00
static Widget *window_player_page_widgets[] = {
window_player_overview_widgets,
window_player_statistics_widgets,
2016-01-20 23:45:09 +01:00
};
#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;
2017-09-06 15:09:18 +02:00
case WINDOW_PLAYER_PAGE_STATISTICS:
OnResizeStatistics();
break;
}
}
2016-01-20 23:45:09 +01:00
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;
}
}
2016-01-20 23:45:09 +01:00
#pragma endregion
2016-01-20 23:45:09 +01:00
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)
2018-06-22 23:21:44 +02:00
{
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);
}
}
2016-01-20 23:45:09 +01:00
void UpdateViewport(bool scroll)
{
int32_t playerIndex = NetworkGetPlayerIndex(static_cast<uint8_t>(number));
if (playerIndex == -1)
{
return;
}
2016-01-20 23:45:09 +01:00
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);
}
2016-01-20 23:45:09 +01:00
void OnUpdateOverview()
{
frame_no++;
InvalidateWidget(WIDX_TAB_1 + page);
if (NetworkGetPlayerIndex(static_cast<uint8_t>(number)) == -1)
{
Close();
return;
}
// Update viewport
bool scroll = true;
2020-10-16 01:13:52 +02:00
// 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();
}
2016-01-20 23:45:09 +01:00
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
2016-11-13 20:17:49 +01:00
#pragma region Statistics
void OnResizeStatistics()
2018-06-22 23:21:44 +02:00
{
WindowSetResize(*this, 210, 80, 210, 80);
}
2016-01-20 23:45:09 +01:00
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)
2018-06-22 23:21:44 +02:00
{
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)
2018-06-22 23:21:44 +02:00
{
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