/***************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include 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(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(number)); if (player != -1) { ft.Add(NetworkGetPlayerName(player)); // set title caption to player name } else { ft.Add(""); } } #pragma region Overview void OnResizeOverview() { WindowSetResize(*this, 240, 170, 500, 300); } void OnUpdateOverview() { frame_no++; InvalidateWidget(WIDX_TAB_1 + page); if (NetworkGetPlayerIndex(static_cast(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(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(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(_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(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(NetworkGetActionNameStringID(lastaction)); } else { ft.Add(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(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(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(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(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(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(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(NetworkGetPlayerCommandsRan(player)); DrawTextBasic(dpi, screenCoords, STR_COMMANDS_RAN, ft); screenCoords.y += kListRowHeight; ft = Formatter(); ft.Add(NetworkGetPlayerMoneySpent(player)); DrawTextBasic(dpi, screenCoords, STR_MONEY_SPENT, ft); } #pragma endregion }; WindowBase* PlayerOpen(uint8_t id) { auto* window = static_cast(WindowBringToFrontByNumber(WindowClass::Player, id)); if (window == nullptr) { window = WindowCreate(WindowClass::Player, 240, 170, WF_RESIZABLE); } window->Init(id); return window; } } // namespace OpenRCT2::Ui::Windows