/***************************************************************************** * Copyright (c) 2014-2023 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static constexpr const StringId WINDOW_TITLE = STR_STRINGID; static constexpr const int32_t WH = 157; static constexpr const int32_t WW = 192; enum WindowGuestPage { WINDOW_GUEST_OVERVIEW, WINDOW_GUEST_STATS, WINDOW_GUEST_RIDES, WINDOW_GUEST_FINANCE, WINDOW_GUEST_THOUGHTS, WINDOW_GUEST_INVENTORY, WINDOW_GUEST_DEBUG, WINDOW_GUEST_PAGE_COUNT, }; enum WindowGuestWidgetIdx { WIDX_BACKGROUND, WIDX_TITLE, WIDX_CLOSE, WIDX_PAGE_BACKGROUND, WIDX_TAB_1, WIDX_TAB_2, WIDX_TAB_3, WIDX_TAB_4, WIDX_TAB_5, WIDX_TAB_6, WIDX_TAB_7, WIDX_MARQUEE = 11, WIDX_VIEWPORT, WIDX_ACTION_LBL, WIDX_PICKUP, WIDX_RENAME, WIDX_LOCATE, WIDX_TRACK, WIDX_RIDE_SCROLL = 11 }; validate_global_widx(WC_PEEP, WIDX_PICKUP); static constexpr int32_t TabWidth = 30; #define MAIN_GUEST_WIDGETS \ WINDOW_SHIM(WINDOW_TITLE, WW, WH), \ MakeWidget({ 0, 43 }, { 192, 114 }, WindowWidgetType::Resize, WindowColour::Secondary), /* Resize */ \ MakeTab({ 3, 17 }, STR_SHOW_GUEST_VIEW_TIP), /* Tab 1 */ \ MakeTab({ 34, 17 }, STR_SHOW_GUEST_NEEDS_TIP), /* Tab 2 */ \ MakeTab({ 65, 17 }, STR_SHOW_GUEST_VISITED_RIDES_TIP), /* Tab 3 */ \ MakeTab({ 96, 17 }, STR_SHOW_GUEST_FINANCE_TIP), /* Tab 4 */ \ MakeTab({ 127, 17 }, STR_SHOW_GUEST_THOUGHTS_TIP), /* Tab 5 */ \ MakeTab({ 158, 17 }, STR_SHOW_GUEST_ITEMS_TIP), /* Tab 6 */ \ MakeTab({ 189, 17 }, STR_DEBUG_TIP) /* Tab 7 */ // clang-format off static Widget _guestWindowWidgetsOverview[] = { MAIN_GUEST_WIDGETS, MakeWidget({ 3, 45}, {164, 12}, WindowWidgetType::LabelCentred, WindowColour::Secondary ), // Label Thought marquee MakeWidget({ 3, 57}, {164, 87}, WindowWidgetType::Viewport, WindowColour::Secondary ), // Viewport MakeWidget({ 3, 144}, {164, 11}, WindowWidgetType::LabelCentred, WindowColour::Secondary ), // Label Action MakeWidget({167, 45}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_PICKUP_BTN), STR_PICKUP_TIP ), // Pickup Button MakeWidget({167, 69}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RENAME), STR_NAME_GUEST_TIP ), // Rename Button MakeWidget({167, 93}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_LOCATE), STR_LOCATE_SUBJECT_TIP ), // Locate Button MakeWidget({167, 117}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_TRACK_PEEP), STR_TOGGLE_GUEST_TRACKING_TIP), // Track Button WIDGETS_END, }; // clang-format on static Widget _guestWindowWidgetsStats[] = { MAIN_GUEST_WIDGETS, WIDGETS_END, }; static Widget _guestWindowWidgetsRides[] = { MAIN_GUEST_WIDGETS, MakeWidget({ 3, 57 }, { 186, 87 }, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL), WIDGETS_END, }; static Widget _guestWindowWidgetsFinance[] = { MAIN_GUEST_WIDGETS, WIDGETS_END, }; static Widget _guestWindowWidgetsThoughts[] = { MAIN_GUEST_WIDGETS, WIDGETS_END, }; static Widget _guestWindowWidgetsInventory[] = { MAIN_GUEST_WIDGETS, WIDGETS_END, }; static Widget _guestWindowWidgetsDebug[] = { MAIN_GUEST_WIDGETS, WIDGETS_END, }; // clang-format off static constexpr std::array _guestWindowPageWidgets = { _guestWindowWidgetsOverview, _guestWindowWidgetsStats, _guestWindowWidgetsRides, _guestWindowWidgetsFinance, _guestWindowWidgetsThoughts, _guestWindowWidgetsInventory, _guestWindowWidgetsDebug, }; static_assert(_guestWindowPageWidgets.size() == WINDOW_GUEST_PAGE_COUNT); // clang-format on static constexpr const std::array _guestWindowPageSizes = { std::array{ ScreenSize{ 192, 159 }, ScreenSize{ 500, 450 } }, // WINDOW_GUEST_OVERVIEW std::array{ ScreenSize{ 192, 180 }, ScreenSize{ 192, 180 } }, // WINDOW_GUEST_STATS std::array{ ScreenSize{ 192, 180 }, ScreenSize{ 500, 400 } }, // WINDOW_GUEST_RIDES std::array{ ScreenSize{ 210, 148 }, ScreenSize{ 210, 148 } }, // WINDOW_GUEST_FINANCE std::array{ ScreenSize{ 192, 159 }, ScreenSize{ 500, 450 } }, // WINDOW_GUEST_THOUGHTS std::array{ ScreenSize{ 192, 159 }, ScreenSize{ 500, 450 } }, // WINDOW_GUEST_INVENTORY std::array{ ScreenSize{ 192, 171 }, ScreenSize{ 192, 171 } }, // WINDOW_GUEST_DEBUG }; static_assert(_guestWindowPageSizes.size() == WINDOW_GUEST_PAGE_COUNT); class GuestWindow final : public Window { private: uint16_t _marqueePosition = 0; uint16_t _beingWatchedTimer = 0; uint16_t _guestAnimationFrame = 0; int16_t _pickedPeepX = LOCATION_NULL; // entity->x gets set to 0x8000 on pickup, this is the old value public: void OnOpen() override { widgets = _guestWindowWidgetsOverview; page = WINDOW_GUEST_OVERVIEW; frame_no = 0; _marqueePosition = 0; picked_peep_frame = 0; min_width = width; min_height = 157; max_width = 500; max_height = 450; no_list_items = 0; selected_list_item = -1; } void Init(EntityId id) { number = id.ToUnderlying(); page = -1; // Set Page to something invalid so that SetPage doesn't set audio on viewport SetPage(WINDOW_GUEST_OVERVIEW); } void OnClose() override { if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)) { if (classification == gCurrentToolWidget.window_classification && number == gCurrentToolWidget.window_number) ToolCancel(); } } void OnMouseUp(WidgetIndex widx) override { switch (widx) { case WIDX_CLOSE: Close(); return; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: SetPage(widx - WIDX_TAB_1); return; } switch (page) { case WINDOW_GUEST_OVERVIEW: OnMouseUpOverview(widx); break; } } void OnMouseDown(WidgetIndex widx) override { if (page == WINDOW_GUEST_OVERVIEW) { OnMouseDownOverview(widx); } } void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override { if (page == WINDOW_GUEST_OVERVIEW) { OnDropdownOverview(widgetIndex, selectedIndex); } } void OnResize() override { switch (page) { case WINDOW_GUEST_OVERVIEW: OnResizeOverview(); break; case WINDOW_GUEST_STATS: case WINDOW_GUEST_RIDES: case WINDOW_GUEST_FINANCE: case WINDOW_GUEST_THOUGHTS: case WINDOW_GUEST_INVENTORY: case WINDOW_GUEST_DEBUG: OnResizeCommon(); break; } } void OnUpdate() override { switch (page) { case WINDOW_GUEST_OVERVIEW: OnUpdateOverview(); break; case WINDOW_GUEST_STATS: OnUpdateStats(); break; case WINDOW_GUEST_RIDES: OnUpdateRides(); break; case WINDOW_GUEST_FINANCE: OnUpdateFinance(); break; case WINDOW_GUEST_THOUGHTS: OnUpdateThoughts(); break; case WINDOW_GUEST_INVENTORY: OnUpdateInventory(); break; case WINDOW_GUEST_DEBUG: OnUpdateDebug(); break; } } void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { if (page == WINDOW_GUEST_OVERVIEW) { OnToolUpdateOverview(widgetIndex, screenCoords); } } void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { if (page == WINDOW_GUEST_OVERVIEW) { OnToolDownOverview(widgetIndex, screenCoords); } } void OnToolAbort(WidgetIndex widgetIndex) override { if (page == WINDOW_GUEST_OVERVIEW) { OnToolAbortOverview(widgetIndex); } } void OnTextInput(WidgetIndex widgetIndex, std::string_view text) override { if (page == WINDOW_GUEST_OVERVIEW) { OnTextInputOverview(widgetIndex, text); } } void OnViewportRotate() override { if (page == WINDOW_GUEST_OVERVIEW) { OnViewportRotateOverview(); } } void OnDraw(DrawPixelInfo& dpi) override { switch (page) { case WINDOW_GUEST_OVERVIEW: OnDrawOverview(dpi); break; case WINDOW_GUEST_STATS: OnDrawStats(dpi); break; case WINDOW_GUEST_RIDES: OnDrawRides(dpi); break; case WINDOW_GUEST_FINANCE: OnDrawFinance(dpi); break; case WINDOW_GUEST_THOUGHTS: OnDrawThoughts(dpi); break; case WINDOW_GUEST_INVENTORY: OnDrawInventory(dpi); break; case WINDOW_GUEST_DEBUG: OnDrawDebug(dpi); break; } } void OnPrepareDraw() override { OnPrepareDrawCommon(); switch (page) { case WINDOW_GUEST_OVERVIEW: OnPrepareDrawOverview(); break; case WINDOW_GUEST_RIDES: OnPrepareDrawRides(); break; } } ScreenSize OnScrollGetSize(int32_t scrollIndex) override { if (page == WINDOW_GUEST_RIDES) { return OnScrollGetSizeRides(scrollIndex); } return {}; } void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override { if (page == WINDOW_GUEST_RIDES) { OnScrollMouseOverRides(scrollIndex, screenCoords); } } void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override { if (page == WINDOW_GUEST_RIDES) { OnScrollMouseDownRides(scrollIndex, screenCoords); } } void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) override { if (page == WINDOW_GUEST_RIDES) { OnScrollDrawRides(scrollIndex, dpi); } } private: Guest* GetGuest() { auto guest = GetEntity(EntityId::FromUnderlying(number)); if (guest == nullptr) { Close(); return nullptr; } return guest; } void OnResizeCommon() { // Get page specific min and max size int32_t minWidth = _guestWindowPageSizes[page][0].width; int32_t minHeight = _guestWindowPageSizes[page][0].height; int32_t maxWidth = _guestWindowPageSizes[page][1].width; int32_t maxHeight = _guestWindowPageSizes[page][1].height; // Ensure min size is large enough for all tabs to fit for (int32_t i = WIDX_TAB_1; i <= WIDX_TAB_7; i++) { if (!WidgetIsDisabled(*this, i)) { minWidth = std::max(minWidth, widgets[i].right + 3); } } maxWidth = std::max(minWidth, maxWidth); WindowSetResize(*this, minWidth, minHeight, maxWidth, maxHeight); } void OnPrepareDrawCommon() { if (_guestWindowPageWidgets[page] != widgets) { widgets = _guestWindowPageWidgets[page]; InitScrollWidgets(); } pressed_widgets |= 1uLL << (page + WIDX_TAB_1); const auto peep = GetGuest(); if (peep == nullptr) { return; } auto ft = Formatter::Common(); peep->FormatNameTo(ft); ResizeFrameWithPage(); WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7); } void DisableWidgets() { const auto peep = GetGuest(); if (peep == nullptr) { return; } uint64_t newDisabledWidgets = 0; if (peep->CanBePickedUp()) { if (WidgetIsDisabled(*this, WIDX_PICKUP)) Invalidate(); } else { newDisabledWidgets = (1uLL << WIDX_PICKUP); if (!WidgetIsDisabled(*this, WIDX_PICKUP)) Invalidate(); } if (gParkFlags & PARK_FLAGS_NO_MONEY) { newDisabledWidgets |= (1uLL << WIDX_TAB_4); // Disable finance tab if no money } if (!gConfigGeneral.DebuggingTools) { newDisabledWidgets |= (1uLL << WIDX_TAB_7); // Disable debug tab when debug tools not turned on } disabled_widgets = newDisabledWidgets; } void SetPage(int32_t newPage) { if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)) { if (number == gCurrentToolWidget.window_number && classification == gCurrentToolWidget.window_classification) ToolCancel(); } int32_t listen = 0; if (newPage == WINDOW_GUEST_OVERVIEW && page == WINDOW_GUEST_OVERVIEW && viewport != nullptr) { if (!(viewport->flags & VIEWPORT_FLAG_SOUND_ON)) listen = 1; } page = newPage; frame_no = 0; no_list_items = 0; selected_list_item = -1; RemoveViewport(); hold_down_widgets = 0; pressed_widgets = 0; widgets = _guestWindowPageWidgets[page]; DisableWidgets(); Invalidate(); OnResize(); OnPrepareDraw(); InitScrollWidgets(); Invalidate(); if (listen && viewport != nullptr) viewport->flags |= VIEWPORT_FLAG_SOUND_ON; } #pragma region Overview void OverviewTabDraw(DrawPixelInfo& dpi) { if (WidgetIsDisabled(*this, WIDX_TAB_1)) return; const auto& widget = widgets[WIDX_TAB_1]; int32_t widgWidth = widget.width() - 1; int32_t widgHeight = widget.height() - 1; auto screenCoords = windowPos + ScreenCoordsXY{ widget.left + 1, widget.top + 1 }; if (page == WINDOW_GUEST_OVERVIEW) widgHeight++; DrawPixelInfo clipDpi; if (!ClipDrawPixelInfo(&clipDpi, &dpi, screenCoords, widgWidth, widgHeight)) { return; } screenCoords = ScreenCoordsXY{ 14, 20 }; const auto* peep = GetGuest(); if (peep == nullptr) { return; } int32_t animationFrame = GetPeepAnimation(peep->SpriteType).base_image + 1; int32_t animationFrameOffset = 0; if (page == WINDOW_GUEST_OVERVIEW) { animationFrameOffset = _guestAnimationFrame; animationFrameOffset &= 0xFFFC; } animationFrame += animationFrameOffset; auto spriteId = ImageId(animationFrame, peep->TshirtColour, peep->TrousersColour); GfxDrawSprite(&clipDpi, spriteId, screenCoords); auto* guest = peep->As(); if (guest != nullptr) { // If holding a balloon if (animationFrame >= 0x2A1D && animationFrame < 0x2A3D) { GfxDrawSprite(&clipDpi, ImageId(animationFrame + 32, guest->BalloonColour), screenCoords); } // If holding umbrella if (animationFrame >= 0x2BBD && animationFrame < 0x2BDD) { GfxDrawSprite(&clipDpi, ImageId(animationFrame + 32, guest->UmbrellaColour), screenCoords); } // If wearing hat if (animationFrame >= 0x29DD && animationFrame < 0x29FD) { GfxDrawSprite(&clipDpi, ImageId(animationFrame + 32, guest->HatColour), screenCoords); } } } void OnResizeOverview() { DisableWidgets(); OnPrepareDraw(); WidgetInvalidate(*this, WIDX_MARQUEE); OnResizeCommon(); if (viewport != nullptr) { auto reqViewportWidth = width - 30; auto reqViewportHeight = height - 72; if (viewport->width != reqViewportWidth || viewport->height != reqViewportHeight) { viewport->width = reqViewportWidth; viewport->height = reqViewportHeight; viewport->view_width = viewport->zoom.ApplyInversedTo(viewport->width); viewport->view_height = viewport->zoom.ApplyInversedTo(viewport->height); } } OnViewportRotate(); } void OnMouseUpOverview(WidgetIndex widgetIndex) { const auto peep = GetGuest(); if (peep == nullptr) { return; } switch (widgetIndex) { case WIDX_PICKUP: { if (!peep->CanBePickedUp()) { return; } _pickedPeepX = peep->x; CoordsXYZ nullLoc{}; nullLoc.SetNull(); PeepPickupAction pickupAction{ PeepPickupType::Pickup, EntityId::FromUnderlying(number), nullLoc, NetworkGetCurrentPlayerId() }; pickupAction.SetCallback([peepnum = number](const GameAction* ga, const GameActions::Result* result) { if (result->Error != GameActions::Status::Ok) return; WindowBase* wind = WindowFindByNumber(WindowClass::Peep, peepnum); if (wind != nullptr) { ToolSet(*wind, WC_PEEP__WIDX_PICKUP, Tool::Picker); } }); GameActions::Execute(&pickupAction); } break; case WIDX_RENAME: { auto peepName = peep->GetName(); WindowTextInputRawOpen( this, widgetIndex, STR_GUEST_RENAME_TITLE, STR_GUEST_RENAME_PROMPT, {}, peepName.c_str(), 32); break; } case WIDX_TRACK: { uint32_t guestFlags = peep->PeepFlags ^ PEEP_FLAGS_TRACKING; auto guestSetFlagsAction = GuestSetFlagsAction(EntityId::FromUnderlying(number), guestFlags); GameActions::Execute(&guestSetFlagsAction); } break; } } void OnMouseDownOverview(WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_LOCATE: ShowLocateDropdown(widgets[widgetIndex]); break; } } void OnDropdownOverview(WidgetIndex widgetIndex, int32_t dropdownIndex) { switch (widgetIndex) { case WIDX_LOCATE: { if (dropdownIndex == 0) { ScrollToViewport(); } else if (dropdownIndex == 1) { GuestFollow(); } break; } } } void ShowLocateDropdown(Widget& widget) { gDropdownItems[0].Format = STR_LOCATE_SUBJECT_TIP; gDropdownItems[1].Format = STR_FOLLOW_SUBJECT_TIP; WindowDropdownShowText({ windowPos.x + widget.left, windowPos.y + widget.top }, widget.height() + 1, colours[1], 0, 2); gDropdownDefaultIndex = 0; } void GuestFollow() { WindowBase* main = WindowGetMain(); WindowFollowSprite(*main, EntityId::FromUnderlying(number)); } void OnViewportRotateOverview() { const auto peep = GetGuest(); if (peep == nullptr) { return; } ViewportUpdateSmartFollowGuest(this, peep); bool reCreateViewport = false; uint16_t origViewportFlags{}; if (viewport != nullptr) { if (focus.has_value()) return; origViewportFlags = viewport->flags; reCreateViewport = true; RemoveViewport(); } OnPrepareDraw(); if (peep->State != PeepState::Picked && viewport == nullptr) { const auto& viewWidget = widgets[WIDX_VIEWPORT]; auto screenPos = ScreenCoordsXY{ viewWidget.left + 1 + windowPos.x, viewWidget.top + 1 + windowPos.y }; int32_t widgWidth = viewWidget.width() - 1; int32_t widgHeight = viewWidget.height() - 1; ViewportCreate(this, screenPos, widgWidth, widgHeight, focus.value()); if (viewport != nullptr && reCreateViewport) { viewport->flags = origViewportFlags; } flags |= WF_NO_SCROLLING; Invalidate(); } Invalidate(); } void OnDrawOverview(DrawPixelInfo& dpi) { DrawWidgets(dpi); OverviewTabDraw(dpi); StatsTabDraw(dpi); RidesTabDraw(dpi); FinanceTabDraw(dpi); ThoughtsTabDraw(dpi); InventoryTabDraw(dpi); DebugTabDraw(dpi); // Draw the viewport no sound sprite if (viewport != nullptr) { WindowDrawViewport(&dpi, *this); if (viewport->flags & VIEWPORT_FLAG_SOUND_ON) { GfxDrawSprite(&dpi, ImageId(SPR_HEARING_VIEWPORT), windowPos + ScreenCoordsXY{ 2, 2 }); } } // Draw the centred label const auto* peep = GetGuest(); if (peep == nullptr) { return; } const auto& actionLabelWidget = widgets[WIDX_ACTION_LBL]; auto screenPos = windowPos + ScreenCoordsXY{ actionLabelWidget.midX(), actionLabelWidget.top - 1 }; { auto ft = Formatter(); peep->FormatActionTo(ft); int32_t textWidth = actionLabelWidget.width(); DrawTextEllipsised(dpi, screenPos, textWidth, STR_BLACK_STRING, ft, { TextAlignment::CENTRE }); } // Draw the marquee thought const auto& marqueeWidget = widgets[WIDX_MARQUEE]; auto marqWidth = marqueeWidget.width() - 3; int32_t left = marqueeWidget.left + 2 + windowPos.x; int32_t top = marqueeWidget.top + windowPos.y; int32_t marqHeight = marqueeWidget.height(); DrawPixelInfo dpiMarquee; if (!ClipDrawPixelInfo(&dpiMarquee, &dpi, { left, top }, marqWidth, marqHeight)) { return; } int32_t i = 0; for (; i < PEEP_MAX_THOUGHTS; ++i) { if (peep->Thoughts[i].type == PeepThoughtType::None) { _marqueePosition = 0; return; } if (peep->Thoughts[i].freshness == 1) { // If a fresh thought break; } } if (i == PEEP_MAX_THOUGHTS) { _marqueePosition = 0; return; } screenPos.x = marqueeWidget.width() - _marqueePosition; { auto ft = Formatter(); PeepThoughtSetFormatArgs(&peep->Thoughts[i], ft); DrawTextBasic(dpiMarquee, { screenPos.x, 0 }, STR_WINDOW_COLOUR_2_STRINGID, ft, { FontStyle::Small }); } } void OnPrepareDrawOverview() { const auto peep = GetGuest(); if (peep == nullptr) { return; } pressed_widgets &= ~(1uLL << WIDX_TRACK); if (peep->PeepFlags & PEEP_FLAGS_TRACKING) { pressed_widgets |= (1uLL << WIDX_TRACK); } widgets[WIDX_VIEWPORT].right = width - 26; widgets[WIDX_VIEWPORT].bottom = height - 14; widgets[WIDX_ACTION_LBL].top = height - 12; widgets[WIDX_ACTION_LBL].bottom = height - 3; widgets[WIDX_ACTION_LBL].right = width - 24; widgets[WIDX_MARQUEE].right = width - 24; widgets[WIDX_PICKUP].right = width - 2; widgets[WIDX_RENAME].right = width - 2; widgets[WIDX_LOCATE].right = width - 2; widgets[WIDX_TRACK].right = width - 2; widgets[WIDX_PICKUP].left = width - 25; widgets[WIDX_RENAME].left = width - 25; widgets[WIDX_LOCATE].left = width - 25; widgets[WIDX_TRACK].left = width - 25; } void OnUpdateOverview() { int32_t newAnimationFrame = _guestAnimationFrame; newAnimationFrame++; newAnimationFrame %= 24; _guestAnimationFrame = newAnimationFrame; WidgetInvalidate(*this, WIDX_TAB_1); WidgetInvalidate(*this, WIDX_TAB_2); const auto peep = GetGuest(); if (peep == nullptr) { return; } if (peep->WindowInvalidateFlags & PEEP_INVALIDATE_PEEP_ACTION) { peep->WindowInvalidateFlags &= ~PEEP_INVALIDATE_PEEP_ACTION; WidgetInvalidate(*this, WIDX_ACTION_LBL); } _marqueePosition += 2; _beingWatchedTimer++; // Disable peep watching thought for multiplayer as it's client specific if (NetworkGetMode() == NETWORK_MODE_NONE) { // Create the "I have the strangest feeling I am being watched thought" if (_beingWatchedTimer >= 3840) { if (!(_beingWatchedTimer & 0x3FF)) { int32_t random = UtilRand() & 0xFFFF; if (random <= 0x2AAA) { peep->InsertNewThought(PeepThoughtType::Watched); } } } } } void OnTextInputOverview(WidgetIndex widgetIndex, std::string_view text) { if (widgetIndex != WIDX_RENAME) return; if (text.empty()) return; std::string sText(text); auto gameAction = GuestSetNameAction(EntityId::FromUnderlying(number), sText); GameActions::Execute(&gameAction); } void OnToolUpdateOverview(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { if (widgetIndex != WIDX_PICKUP) return; MapInvalidateSelectionRect(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; auto mapCoords = FootpathGetCoordinatesFromPos({ screenCoords.x, screenCoords.y + 16 }, nullptr, nullptr); if (!mapCoords.IsNull()) { gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; gMapSelectType = MAP_SELECT_TYPE_FULL; gMapSelectPositionA = mapCoords; gMapSelectPositionB = mapCoords; MapInvalidateSelectionRect(); } gPickupPeepImage = ImageId(); auto info = GetMapCoordinatesFromPos(screenCoords, ViewportInteractionItemAll); if (info.SpriteType == ViewportInteractionItem::None) return; gPickupPeepX = screenCoords.x - 1; gPickupPeepY = screenCoords.y + 16; picked_peep_frame++; if (picked_peep_frame >= 48) { picked_peep_frame = 0; } const auto peep = GetGuest(); if (peep == nullptr) { return; } auto baseImageId = GetPeepAnimation(peep->SpriteType, PeepActionSpriteType::Ui).base_image; baseImageId += picked_peep_frame >> 2; gPickupPeepImage = ImageId(baseImageId, peep->TshirtColour, peep->TrousersColour); } void OnToolDownOverview(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { if (widgetIndex != WIDX_PICKUP) return; TileElement* tileElement; auto destCoords = FootpathGetCoordinatesFromPos({ screenCoords.x, screenCoords.y + 16 }, nullptr, &tileElement); if (destCoords.IsNull()) return; PeepPickupAction pickupAction{ PeepPickupType::Place, EntityId::FromUnderlying(number), { destCoords, tileElement->GetBaseZ() }, NetworkGetCurrentPlayerId() }; pickupAction.SetCallback([](const GameAction* ga, const GameActions::Result* result) { if (result->Error != GameActions::Status::Ok) return; ToolCancel(); gPickupPeepImage = ImageId(); }); GameActions::Execute(&pickupAction); } void OnToolAbortOverview(WidgetIndex widgetIndex) { if (widgetIndex != WIDX_PICKUP) return; PeepPickupAction pickupAction{ PeepPickupType::Cancel, EntityId::FromUnderlying(number), { _pickedPeepX, 0, 0 }, NetworkGetCurrentPlayerId() }; GameActions::Execute(&pickupAction); } #pragma endregion #pragma region Stats void StatsTabDraw(DrawPixelInfo& dpi) { if (WidgetIsDisabled(*this, WIDX_TAB_2)) return; const auto& widget = widgets[WIDX_TAB_2]; auto screenCoords = windowPos + ScreenCoordsXY{ widget.left, widget.top }; const auto peep = GetGuest(); if (peep == nullptr) { return; } int32_t imageId = GetPeepFaceSpriteLarge(peep); if (page == WINDOW_GUEST_STATS) { // If currently viewing this tab animate tab // if it is very sick or angry. switch (imageId) { case SPR_PEEP_LARGE_FACE_VERY_VERY_SICK_0: imageId += (frame_no / 4) & 0xF; break; case SPR_PEEP_LARGE_FACE_VERY_SICK_0: imageId += (frame_no / 8) & 0x3; break; case SPR_PEEP_LARGE_FACE_ANGRY_0: imageId += (frame_no / 8) & 0x3; break; } } GfxDrawSprite(&dpi, ImageId(imageId), screenCoords); } void OnUpdateStats() { frame_no++; auto peep = GetGuest(); if (peep == nullptr) { return; } peep->WindowInvalidateFlags &= ~PEEP_INVALIDATE_PEEP_STATS; Invalidate(); } void StatsBarsDraw(int32_t value, const ScreenCoordsXY& origCoords, DrawPixelInfo& dpi, int32_t colour, bool blinkFlag) { auto coords = origCoords; if (FontGetLineHeight(FontStyle::Medium) > 10) { coords.y += 1; } GfxFillRectInset( &dpi, { coords + ScreenCoordsXY{ 61, 1 }, coords + ScreenCoordsXY{ 61 + 121, 9 } }, colours[1], INSET_RECT_F_30); if (!blinkFlag || GameIsPaused() || (gCurrentRealTimeTicks & 8) == 0) { value *= 118; value >>= 8; if (value <= 2) return; GfxFillRectInset( &dpi, { coords + ScreenCoordsXY{ 63, 2 }, coords + ScreenCoordsXY{ 63 + value - 1, 8 } }, colour, 0); } } /** * Takes a guest stat value (scaled to currMax) and adjusts it to be scaled out of 255. * Then clamp the value to between newMin and 255. */ int32_t NormalizeGuestStatValue(int32_t value, int32_t currMax, int32_t newMin) const { int32_t newValue = (value * 255) / currMax; return std::clamp(newValue, newMin, 255); } void OnDrawStats(DrawPixelInfo& dpi) { DrawWidgets(dpi); OverviewTabDraw(dpi); StatsTabDraw(dpi); RidesTabDraw(dpi); FinanceTabDraw(dpi); ThoughtsTabDraw(dpi); InventoryTabDraw(dpi); DebugTabDraw(dpi); // ebx const auto peep = GetGuest(); if (peep == nullptr) { return; } // Not sure why this is not stats widgets // cx dx auto screenCoords = windowPos + ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 }; // Happiness DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_HAPPINESS_LABEL); int32_t happiness = NormalizeGuestStatValue(peep->Happiness, PEEP_MAX_HAPPINESS, 10); int32_t barColour = COLOUR_BRIGHT_GREEN; bool barBlink = happiness < 50; StatsBarsDraw(happiness, screenCoords, dpi, barColour, barBlink); // Energy screenCoords.y += LIST_ROW_HEIGHT; DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_ENERGY_LABEL); int32_t energy = NormalizeGuestStatValue(peep->Energy - PEEP_MIN_ENERGY, PEEP_MAX_ENERGY - PEEP_MIN_ENERGY, 10); barColour = COLOUR_BRIGHT_GREEN; barBlink = energy < 50; StatsBarsDraw(energy, screenCoords, dpi, barColour, barBlink); // Hunger screenCoords.y += LIST_ROW_HEIGHT; DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_HUNGER_LABEL); int32_t hunger = NormalizeGuestStatValue(peep->Hunger - 32, 158, 0); hunger = 255 - hunger; // the bar should be longer when peep->Hunger is low barColour = COLOUR_BRIGHT_RED; barBlink = hunger > 170; StatsBarsDraw(hunger, screenCoords, dpi, barColour, barBlink); // Thirst screenCoords.y += LIST_ROW_HEIGHT; DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_THIRST_LABEL); int32_t thirst = NormalizeGuestStatValue(peep->Thirst - 32, 158, 0); thirst = 255 - thirst; // the bar should be longer when peep->Thirst is low barColour = COLOUR_BRIGHT_RED; barBlink = thirst > 170; StatsBarsDraw(thirst, screenCoords, dpi, barColour, barBlink); // Nausea screenCoords.y += LIST_ROW_HEIGHT; DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_NAUSEA_LABEL); int32_t nausea = NormalizeGuestStatValue(peep->Nausea - 32, 223, 0); barColour = COLOUR_BRIGHT_RED; barBlink = nausea > 120; StatsBarsDraw(nausea, screenCoords, dpi, barColour, barBlink); // Toilet screenCoords.y += LIST_ROW_HEIGHT; DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_TOILET_LABEL); int32_t toilet = NormalizeGuestStatValue(peep->Toilet - 64, 178, 0); barColour = COLOUR_BRIGHT_RED; barBlink = toilet > 160; StatsBarsDraw(toilet, screenCoords, dpi, barColour, barBlink); // Time in park screenCoords.y += LIST_ROW_HEIGHT + 1; int32_t guestEntryTime = peep->GetParkEntryTime(); if (guestEntryTime != -1) { int32_t timeInPark = (gCurrentTicks - guestEntryTime) >> 11; auto ft = Formatter(); ft.Add(timeInPark & 0xFFFF); DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_TIME_IN_PARK, ft); } screenCoords.y += LIST_ROW_HEIGHT + 9; GfxFillRectInset( &dpi, { screenCoords - ScreenCoordsXY{ 0, 6 }, screenCoords + ScreenCoordsXY{ 179, -5 } }, colours[1], INSET_RECT_FLAG_BORDER_INSET); // Preferred Ride DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_PREFERRED_RIDE); screenCoords.y += LIST_ROW_HEIGHT; // Intensity { auto ft = Formatter(); auto maxIntensity = peep->Intensity.GetMaximum(); int32_t string_id = STR_GUEST_STAT_PREFERRED_INTESITY_BELOW; if (peep->Intensity.GetMinimum() != 0) { ft.Add(peep->Intensity.GetMinimum()); ft.Add(maxIntensity); string_id = STR_GUEST_STAT_PREFERRED_INTESITY_BETWEEN; if (maxIntensity == 15) string_id = STR_GUEST_STAT_PREFERRED_INTESITY_ABOVE; } else { ft.Add(maxIntensity); } DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ 4, 0 }, string_id, ft); } // Nausea tolerance { static constexpr const StringId _nauseaTolerances[] = { STR_PEEP_STAT_NAUSEA_TOLERANCE_NONE, STR_PEEP_STAT_NAUSEA_TOLERANCE_LOW, STR_PEEP_STAT_NAUSEA_TOLERANCE_AVERAGE, STR_PEEP_STAT_NAUSEA_TOLERANCE_HIGH, }; screenCoords.y += LIST_ROW_HEIGHT; auto nausea_tolerance = EnumValue(peep->NauseaTolerance) & 0x3; auto ft = Formatter(); ft.Add(_nauseaTolerances[nausea_tolerance]); DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_NAUSEA_TOLERANCE, ft); } } #pragma endregion #pragma region Rides void RidesTabDraw(DrawPixelInfo& dpi) { if (WidgetIsDisabled(*this, WIDX_TAB_3)) return; const auto& widget = widgets[WIDX_TAB_3]; auto screenCoords = windowPos + ScreenCoordsXY{ widget.left, widget.top }; int32_t imageId = SPR_TAB_RIDE_0; if (page == WINDOW_GUEST_RIDES) { imageId += (frame_no / 4) & 0xF; } GfxDrawSprite(&dpi, ImageId(imageId), screenCoords); } void OnUpdateRides() { frame_no++; WidgetInvalidate(*this, WIDX_TAB_2); WidgetInvalidate(*this, WIDX_TAB_3); const auto guest = GetGuest(); if (guest == nullptr) { return; } // Every 2048 ticks do a full window_invalidate int32_t numTicks = gCurrentTicks - guest->GetParkEntryTime(); if (!(numTicks & 0x7FF)) Invalidate(); uint8_t currListPosition = 0; for (const auto& r : GetRideManager()) { if (r.IsRide() && guest->HasRidden(r)) { list_item_positions[currListPosition] = r.id.ToUnderlying(); currListPosition++; } } // If there are new items if (no_list_items != currListPosition) { no_list_items = currListPosition; Invalidate(); } } ScreenSize OnScrollGetSizeRides(int32_t scrollIndex) { ScreenSize newSize; newSize.height = no_list_items * 10; if (selected_list_item != -1) { selected_list_item = -1; Invalidate(); } int32_t visableHeight = newSize.height - widgets[WIDX_RIDE_SCROLL].bottom + widgets[WIDX_RIDE_SCROLL].top + 21; if (visableHeight < 0) visableHeight = 0; if (visableHeight < scrolls[0].v_top) { scrolls[0].v_top = visableHeight; Invalidate(); } return newSize; } void OnScrollMouseDownRides(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) { auto index = screenCoords.y / 10; if (index >= no_list_items) return; auto intent = Intent(WindowClass::Ride); intent.PutExtra(INTENT_EXTRA_RIDE_ID, list_item_positions[index]); ContextOpenIntent(&intent); } void OnScrollMouseOverRides(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) { auto index = screenCoords.y / 10; if (index >= no_list_items) return; if (index == selected_list_item) return; selected_list_item = index; Invalidate(); } void OnPrepareDrawRides() { widgets[WIDX_RIDE_SCROLL].right = width - 4; widgets[WIDX_RIDE_SCROLL].bottom = height - 15; } void OnDrawRides(DrawPixelInfo& dpi) { DrawWidgets(dpi); OverviewTabDraw(dpi); StatsTabDraw(dpi); RidesTabDraw(dpi); FinanceTabDraw(dpi); ThoughtsTabDraw(dpi); InventoryTabDraw(dpi); DebugTabDraw(dpi); const auto peep = GetGuest(); if (peep == nullptr) { return; } // cx dx auto screenCoords = windowPos + ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 2, widgets[WIDX_PAGE_BACKGROUND].top + 2 }; DrawTextBasic(dpi, screenCoords, STR_GUEST_LABEL_RIDES_BEEN_ON); screenCoords.y = windowPos.y + widgets[WIDX_PAGE_BACKGROUND].bottom - 12; auto ft = Formatter(); auto* r = GetRide(peep->FavouriteRide); if (r != nullptr) { r->FormatNameTo(ft); } else { ft.Add(STR_PEEP_FAVOURITE_RIDE_NOT_AVAILABLE); } DrawTextEllipsised(dpi, screenCoords, width - 14, STR_FAVOURITE_RIDE, ft); } void OnScrollDrawRides(int32_t scrollIndex, DrawPixelInfo& dpi) { auto colour = ColourMapA[colours[1]].mid_light; GfxFillRect(&dpi, { { dpi.x, dpi.y }, { dpi.x + dpi.width - 1, dpi.y + dpi.height - 1 } }, colour); for (int32_t listIndex = 0; listIndex < no_list_items; listIndex++) { auto y = listIndex * 10; StringId stringId = STR_BLACK_STRING; if (listIndex == selected_list_item) { GfxFilterRect(&dpi, { 0, y, 800, y + 9 }, FilterPaletteID::PaletteDarken1); stringId = STR_WINDOW_COLOUR_2_STRINGID; } const auto rId = RideId::FromUnderlying(list_item_positions[listIndex]); auto* r = GetRide(rId); if (r != nullptr) { auto ft = Formatter(); r->FormatNameTo(ft); DrawTextBasic(dpi, { 0, y - 1 }, stringId, ft); } } } #pragma endregion #pragma region Finance void FinanceTabDraw(DrawPixelInfo& dpi) { if (WidgetIsDisabled(*this, WIDX_TAB_4)) return; const auto& widget = widgets[WIDX_TAB_4]; auto screenCoords = windowPos + ScreenCoordsXY{ widget.left, widget.top }; int32_t imageId = SPR_TAB_FINANCES_SUMMARY_0; if (page == WINDOW_GUEST_FINANCE) { imageId += (frame_no / 2) & 0x7; } GfxDrawSprite(&dpi, ImageId(imageId), screenCoords); } void OnUpdateFinance() { frame_no++; WidgetInvalidate(*this, WIDX_TAB_2); WidgetInvalidate(*this, WIDX_TAB_4); } void OnDrawFinance(DrawPixelInfo& dpi) { DrawWidgets(dpi); OverviewTabDraw(dpi); StatsTabDraw(dpi); RidesTabDraw(dpi); FinanceTabDraw(dpi); ThoughtsTabDraw(dpi); InventoryTabDraw(dpi); DebugTabDraw(dpi); const auto peep = GetGuest(); if (peep == nullptr) { return; } // cx dx auto screenCoords = windowPos + ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 }; // Cash in pocket { auto ft = Formatter(); ft.Add(peep->CashInPocket); DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_CASH_IN_POCKET, ft); screenCoords.y += LIST_ROW_HEIGHT; } // Cash spent { auto ft = Formatter(); ft.Add(peep->CashSpent); DrawTextBasic(dpi, screenCoords, STR_GUEST_STAT_CASH_SPENT, ft); screenCoords.y += LIST_ROW_HEIGHT * 2; } GfxFillRectInset( &dpi, { screenCoords - ScreenCoordsXY{ 0, 6 }, screenCoords + ScreenCoordsXY{ 179, -5 } }, colours[1], INSET_RECT_FLAG_BORDER_INSET); // Paid to enter { auto ft = Formatter(); ft.Add(peep->PaidToEnter); DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_ENTRANCE_FEE, ft); screenCoords.y += LIST_ROW_HEIGHT; } // Paid on rides { auto ft = Formatter(); ft.Add(peep->PaidOnRides); ft.Add(peep->GuestNumRides); if (peep->GuestNumRides != 1) { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_RIDE_PLURAL, ft); } else { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_RIDE, ft); } screenCoords.y += LIST_ROW_HEIGHT; } // Paid on food { auto ft = Formatter(); ft.Add(peep->PaidOnFood); ft.Add(peep->AmountOfFood); if (peep->AmountOfFood != 1) { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_FOOD_PLURAL, ft); } else { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_FOOD, ft); } screenCoords.y += LIST_ROW_HEIGHT; } // Paid on drinks { auto ft = Formatter(); ft.Add(peep->PaidOnDrink); ft.Add(peep->AmountOfDrinks); if (peep->AmountOfDrinks != 1) { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_DRINK_PLURAL, ft); } else { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_DRINK, ft); } screenCoords.y += LIST_ROW_HEIGHT; } // Paid on souvenirs { auto ft = Formatter(); ft.Add(peep->PaidOnSouvenirs); ft.Add(peep->AmountOfSouvenirs); if (peep->AmountOfSouvenirs != 1) { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_SOUVENIR_PLURAL, ft); } else { DrawTextBasic(dpi, screenCoords, STR_GUEST_EXPENSES_SOUVENIR, ft); } } } #pragma endregion #pragma region Thoughts void ThoughtsTabDraw(DrawPixelInfo& dpi) { if (WidgetIsDisabled(*this, WIDX_TAB_5)) return; const auto& widget = widgets[WIDX_TAB_5]; auto screenCoords = windowPos + ScreenCoordsXY{ widget.left, widget.top }; int32_t imageId = SPR_TAB_THOUGHTS_0; if (page == WINDOW_GUEST_THOUGHTS) { imageId += (frame_no / 2) & 0x7; } GfxDrawSprite(&dpi, ImageId(imageId), screenCoords); } void OnUpdateThoughts() { frame_no++; WidgetInvalidate(*this, WIDX_TAB_2); WidgetInvalidate(*this, WIDX_TAB_5); auto peep = GetGuest(); if (peep == nullptr) { return; } if (peep->WindowInvalidateFlags & PEEP_INVALIDATE_PEEP_THOUGHTS) { peep->WindowInvalidateFlags &= ~PEEP_INVALIDATE_PEEP_THOUGHTS; Invalidate(); } } void OnDrawThoughts(DrawPixelInfo& dpi) { DrawWidgets(dpi); OverviewTabDraw(dpi); StatsTabDraw(dpi); RidesTabDraw(dpi); FinanceTabDraw(dpi); ThoughtsTabDraw(dpi); InventoryTabDraw(dpi); DebugTabDraw(dpi); const auto peep = GetGuest(); if (peep == nullptr) { return; } // cx dx auto screenCoords = windowPos + ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 }; DrawTextBasic(dpi, screenCoords, STR_GUEST_RECENT_THOUGHTS_LABEL); screenCoords.y += 10; for (const auto& thought : peep->Thoughts) { if (thought.type == PeepThoughtType::None) return; if (thought.freshness == 0) continue; int32_t widgWidth = widgets[WIDX_PAGE_BACKGROUND].right - widgets[WIDX_PAGE_BACKGROUND].left - 8; auto ft = Formatter(); PeepThoughtSetFormatArgs(&thought, ft); screenCoords.y += DrawTextWrapped(dpi, screenCoords, widgWidth, STR_BLACK_STRING, ft, { FontStyle::Small }); // If this is the last visible line end drawing. if (screenCoords.y > windowPos.y + widgets[WIDX_PAGE_BACKGROUND].bottom - 32) return; } } #pragma endregion #pragma region Inventory void InventoryTabDraw(DrawPixelInfo& dpi) { if (WidgetIsDisabled(*this, WIDX_TAB_6)) return; const auto& widget = widgets[WIDX_TAB_6]; auto screenCoords = windowPos + ScreenCoordsXY{ widget.left, widget.top }; GfxDrawSprite(&dpi, ImageId(SPR_TAB_GUEST_INVENTORY), screenCoords); } void OnUpdateInventory() { frame_no++; WidgetInvalidate(*this, WIDX_TAB_2); WidgetInvalidate(*this, WIDX_TAB_6); auto peep = GetGuest(); if (peep == nullptr) { return; } if (peep->WindowInvalidateFlags & PEEP_INVALIDATE_PEEP_INVENTORY) { peep->WindowInvalidateFlags &= ~PEEP_INVALIDATE_PEEP_INVENTORY; Invalidate(); } } std::pair InventoryFormatItem(Guest& guest, ShopItem item) const { auto& park = OpenRCT2::GetContext()->GetGameState()->GetPark(); auto parkName = park.Name.c_str(); // Default arguments auto ft = Formatter(); ft.Add(GetShopItemDescriptor(item).Image); ft.Add(GetShopItemDescriptor(item).Naming.Display); ft.Add(STR_STRING); ft.Add(parkName); // Special overrides Ride* invRide{}; switch (item) { case ShopItem::Balloon: ft.Rewind(); ft.Add(ImageId(GetShopItemDescriptor(item).Image, (guest.BalloonColour)).ToUInt32()); break; case ShopItem::Photo: invRide = GetRide(guest.Photo1RideRef); if (invRide != nullptr) { ft.Rewind(); ft.Increment(6); invRide->FormatNameTo(ft); } break; case ShopItem::Umbrella: ft.Rewind(); ft.Add(ImageId(GetShopItemDescriptor(item).Image, (guest.UmbrellaColour)).ToUInt32()); break; case ShopItem::Voucher: switch (guest.VoucherType) { case VOUCHER_TYPE_PARK_ENTRY_FREE: ft.Rewind(); ft.Increment(6); ft.Add(STR_PEEP_INVENTORY_VOUCHER_PARK_ENTRY_FREE); ft.Add(STR_STRING); ft.Add(parkName); break; case VOUCHER_TYPE_RIDE_FREE: invRide = GetRide(guest.VoucherRideId); if (invRide != nullptr) { ft.Rewind(); ft.Increment(6); ft.Add(STR_PEEP_INVENTORY_VOUCHER_RIDE_FREE); invRide->FormatNameTo(ft); } break; case VOUCHER_TYPE_PARK_ENTRY_HALF_PRICE: ft.Rewind(); ft.Increment(6); ft.Add(STR_PEEP_INVENTORY_VOUCHER_PARK_ENTRY_HALF_PRICE); ft.Add(STR_STRING); ft.Add(parkName); break; case VOUCHER_TYPE_FOOD_OR_DRINK_FREE: ft.Rewind(); ft.Increment(6); ft.Add(STR_PEEP_INVENTORY_VOUCHER_FOOD_OR_DRINK_FREE); ft.Add(GetShopItemDescriptor(guest.VoucherShopItem).Naming.Singular); break; } break; case ShopItem::Hat: ft.Rewind(); ft.Add(ImageId(GetShopItemDescriptor(item).Image, (guest.HatColour)).ToUInt32()); break; case ShopItem::TShirt: ft.Rewind(); ft.Add(ImageId(GetShopItemDescriptor(item).Image, (guest.TshirtColour)).ToUInt32()); break; case ShopItem::Photo2: invRide = GetRide(guest.Photo2RideRef); if (invRide != nullptr) { ft.Rewind(); ft.Increment(6); invRide->FormatNameTo(ft); } break; case ShopItem::Photo3: invRide = GetRide(guest.Photo3RideRef); if (invRide != nullptr) { ft.Rewind(); ft.Increment(6); invRide->FormatNameTo(ft); } break; case ShopItem::Photo4: invRide = GetRide(guest.Photo4RideRef); if (invRide != nullptr) { ft.Rewind(); ft.Increment(6); invRide->FormatNameTo(ft); } break; default: // This switch handles only items visible in peep window break; } return std::make_pair(STR_GUEST_ITEM_FORMAT, ft); } void OnDrawInventory(DrawPixelInfo& dpi) { DrawWidgets(dpi); OverviewTabDraw(dpi); StatsTabDraw(dpi); RidesTabDraw(dpi); FinanceTabDraw(dpi); ThoughtsTabDraw(dpi); InventoryTabDraw(dpi); DebugTabDraw(dpi); const auto guest = GetGuest(); if (guest == nullptr) { return; } Widget* pageBackgroundWidget = &widgets[WIDX_PAGE_BACKGROUND]; auto screenCoords = windowPos + ScreenCoordsXY{ pageBackgroundWidget->left + 4, pageBackgroundWidget->top + 2 }; int32_t itemNameWidth = pageBackgroundWidget->width() - 8; int32_t maxY = windowPos.y + height - 22; int32_t numItems = 0; DrawTextBasic(dpi, screenCoords, STR_CARRYING); screenCoords.y += 10; for (ShopItem item = ShopItem::Balloon; item < ShopItem::Count; item++) { if (screenCoords.y >= maxY) break; if (!guest->HasItem(item)) continue; auto [stringId, ft] = InventoryFormatItem(*guest, item); screenCoords.y += DrawTextWrapped(dpi, screenCoords, itemNameWidth, stringId, ft); numItems++; } if (numItems == 0) { DrawTextBasic(dpi, screenCoords, STR_NOTHING); } } #pragma endregion #pragma region Debug void DebugTabDraw(DrawPixelInfo& dpi) { if (WidgetIsDisabled(*this, WIDX_TAB_7)) return; const auto& widget = widgets[WIDX_TAB_7]; auto screenCoords = windowPos + ScreenCoordsXY{ widget.left, widget.top }; int32_t imageId = SPR_TAB_GEARS_0; if (page == WINDOW_GUEST_DEBUG) { imageId += (frame_no / 2) & 0x3; } GfxDrawSprite(&dpi, ImageId(imageId), screenCoords); } void OnUpdateDebug() { frame_no++; Invalidate(); } void OnDrawDebug(DrawPixelInfo& dpi) { char buffer[512]{}; char buffer2[512]{}; DrawWidgets(dpi); OverviewTabDraw(dpi); StatsTabDraw(dpi); RidesTabDraw(dpi); FinanceTabDraw(dpi); ThoughtsTabDraw(dpi); InventoryTabDraw(dpi); DebugTabDraw(dpi); const auto peep = GetGuest(); if (peep == nullptr) { return; } auto screenCoords = windowPos + ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 }; { auto ft = Formatter(); ft.Add(peep->Id); DrawTextBasic(dpi, screenCoords, STR_PEEP_DEBUG_SPRITE_INDEX, ft); } screenCoords.y += LIST_ROW_HEIGHT; { auto ft = Formatter(); ft.Add(peep->x); ft.Add(peep->y); ft.Add(peep->z); DrawTextBasic(dpi, screenCoords, STR_PEEP_DEBUG_POSITION, ft); } screenCoords.y += LIST_ROW_HEIGHT; { auto ft = Formatter(); ft.Add(peep->NextLoc.x); ft.Add(peep->NextLoc.y); ft.Add(peep->NextLoc.z); OpenRCT2::FormatStringLegacy(buffer, sizeof(buffer), STR_PEEP_DEBUG_NEXT, ft.Data()); if (peep->GetNextIsSurface()) { OpenRCT2::FormatStringLegacy(buffer2, sizeof(buffer2), STR_PEEP_DEBUG_NEXT_SURFACE, nullptr); SafeStrCat(buffer, buffer2, sizeof(buffer)); } if (peep->GetNextIsSloped()) { auto ft2 = Formatter(); ft2.Add(peep->GetNextDirection()); OpenRCT2::FormatStringLegacy(buffer2, sizeof(buffer2), STR_PEEP_DEBUG_NEXT_SLOPE, ft2.Data()); SafeStrCat(buffer, buffer2, sizeof(buffer)); } GfxDrawString(dpi, screenCoords, buffer, {}); } screenCoords.y += LIST_ROW_HEIGHT; { auto ft = Formatter(); ft.Add(peep->DestinationX); ft.Add(peep->DestinationY); ft.Add(peep->DestinationTolerance); DrawTextBasic(dpi, screenCoords, STR_PEEP_DEBUG_DEST, ft); } screenCoords.y += LIST_ROW_HEIGHT; { auto ft = Formatter(); ft.Add(peep->PathfindGoal.x); ft.Add(peep->PathfindGoal.y); ft.Add(peep->PathfindGoal.z); ft.Add(peep->PathfindGoal.direction); DrawTextBasic(dpi, screenCoords, STR_PEEP_DEBUG_PATHFIND_GOAL, ft); } screenCoords.y += LIST_ROW_HEIGHT; DrawTextBasic(dpi, screenCoords, STR_PEEP_DEBUG_PATHFIND_HISTORY); screenCoords.y += LIST_ROW_HEIGHT; screenCoords.x += 10; for (auto& point : peep->PathfindHistory) { auto ft = Formatter(); ft.Add(point.x); ft.Add(point.y); ft.Add(point.z); ft.Add(point.direction); DrawTextBasic(dpi, screenCoords, STR_PEEP_DEBUG_PATHFIND_HISTORY_ITEM, ft); screenCoords.y += LIST_ROW_HEIGHT; } screenCoords.x -= 10; } #pragma endregion }; /** * * rct2: 0x006989E9 * */ WindowBase* WindowGuestOpen(Peep* peep) { if (peep == nullptr) { return nullptr; } if (peep->Is()) { return WindowStaffOpen(peep); } auto* window = static_cast(WindowBringToFrontByNumber(WindowClass::Peep, peep->Id.ToUnderlying())); if (window == nullptr) { int32_t windowWidth = 192; if (gConfigGeneral.DebuggingTools) windowWidth += TabWidth; window = WindowCreate(WindowClass::Peep, windowWidth, 157, WF_RESIZABLE); if (window == nullptr) { return nullptr; } } window->Init(peep->Id); return window; }