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

1262 lines
42 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 "../interface/Theme.h"
#include <openrct2-ui/interface/Dropdown.h>
#include <openrct2-ui/interface/Viewport.h>
#include <openrct2-ui/interface/Widget.h>
#include <openrct2-ui/windows/Window.h>
#include <openrct2/Context.h>
#include <openrct2/Game.h>
#include <openrct2/GameState.h>
#include <openrct2/Input.h>
#include <openrct2/actions/PeepPickupAction.h>
#include <openrct2/actions/StaffSetCostumeAction.h>
#include <openrct2/actions/StaffSetNameAction.h>
#include <openrct2/actions/StaffSetOrdersAction.h>
#include <openrct2/actions/StaffSetPatrolAreaAction.h>
#include <openrct2/config/Config.h>
#include <openrct2/entity/EntityRegistry.h>
#include <openrct2/entity/PatrolArea.h>
#include <openrct2/entity/Staff.h>
#include <openrct2/localisation/Formatter.h>
#include <openrct2/localisation/Localisation.h>
#include <openrct2/management/Finance.h>
#include <openrct2/network/network.h>
#include <openrct2/sprites.h>
#include <openrct2/windows/Intent.h>
#include <openrct2/world/Footpath.h>
#include <openrct2/world/Park.h>
namespace OpenRCT2::Ui::Windows
{
static constexpr StringId WINDOW_TITLE = STR_STRINGID;
static constexpr int32_t WW = 190;
static constexpr int32_t WH = 180;
enum WindowStaffPage
{
WINDOW_STAFF_OVERVIEW,
WINDOW_STAFF_OPTIONS,
WINDOW_STAFF_STATISTICS,
WINDOW_STAFF_PAGE_COUNT,
};
enum WindowStaffWidgetIdx
{
WIDX_BACKGROUND,
WIDX_TITLE,
WIDX_CLOSE,
WIDX_RESIZE,
WIDX_TAB_1,
WIDX_TAB_2,
WIDX_TAB_3,
WIDX_VIEWPORT = 7,
WIDX_BTM_LABEL,
WIDX_PICKUP,
WIDX_PATROL,
WIDX_RENAME,
WIDX_LOCATE,
WIDX_FIRE,
WIDX_CHECKBOX_1 = 7,
WIDX_CHECKBOX_2,
WIDX_CHECKBOX_3,
WIDX_CHECKBOX_4,
WIDX_COSTUME_BOX,
WIDX_COSTUME_BTN,
};
validate_global_widx(WC_PEEP, WIDX_PATROL);
validate_global_widx(WC_STAFF, WIDX_PICKUP);
// clang-format off
#define MAIN_STAFF_WIDGETS \
WINDOW_SHIM(WINDOW_TITLE, WW, WH), \
MakeWidget({ 0, 43}, {190, 137}, WindowWidgetType::Resize, WindowColour::Secondary), /* Resize */ \
MakeTab ({ 3, 17}, STR_STAFF_OVERVIEW_TIP ), /* Tab 1 */ \
MakeTab ({34, 17}, STR_STAFF_OPTIONS_TIP ), /* Tab 2 */ \
MakeTab ({65, 17}, STR_STAFF_STATS_TIP ) /* Tab 3 */
static Widget _staffOverviewWidgets[] = {
MAIN_STAFF_WIDGETS,
MakeWidget ({ 3, 47}, {162, 120}, WindowWidgetType::Viewport, WindowColour::Secondary ), // Viewport
MakeWidget ({ 3, WH - 13}, {162, 11}, WindowWidgetType::LabelCentred, WindowColour::Secondary ), // Label at bottom of viewport
MakeWidget ({WW - 25, 45}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_PICKUP_BTN), STR_PICKUP_TIP ), // Pickup Button
MakeWidget ({WW - 25, 69}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_PATROL_BTN), STR_SET_PATROL_TIP ), // Patrol Button
MakeWidget ({WW - 25, 93}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RENAME), STR_NAME_STAFF_TIP ), // Rename Button
MakeWidget ({WW - 25, 117}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_LOCATE), STR_LOCATE_SUBJECT_TIP), // Locate Button
MakeWidget ({WW - 25, 141}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_DEMOLISH), STR_FIRE_STAFF_TIP ), // Fire Button
kWidgetsEnd,
};
//0x9AF910
static Widget _staffOptionsWidgets[] = {
MAIN_STAFF_WIDGETS,
MakeWidget ({ 5, 50}, {180, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary ), // Checkbox 1
MakeWidget ({ 5, 67}, {180, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary ), // Checkbox 2
MakeWidget ({ 5, 84}, {180, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary ), // Checkbox 3
MakeWidget ({ 5, 101}, {180, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary ), // Checkbox 4
MakeWidget ({ 5, 50}, {180, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ), // Costume Dropdown
MakeWidget ({WW - 17, 51}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_COSTUME_TIP), // Costume Dropdown Button
kWidgetsEnd,
};
// clang-format on
// 0x9AF9F4
static Widget _staffStatsWidgets[] = {
MAIN_STAFF_WIDGETS,
kWidgetsEnd,
};
static Widget* window_staff_page_widgets[] = {
_staffOverviewWidgets,
_staffOptionsWidgets,
_staffStatsWidgets,
};
class StaffWindow final : public Window
{
private:
EntertainerCostume _availableCostumes[EnumValue(EntertainerCostume::Count)]{};
uint16_t _tabAnimationOffset = 0;
int32_t _pickedPeepOldX = LOCATION_NULL;
public:
void Initialise(EntityId entityId)
{
number = entityId.ToUnderlying();
}
void OnOpen() override
{
SetPage(WINDOW_STAFF_OVERVIEW);
}
void OnClose() override
{
CancelTools();
}
void OnMouseUp(WidgetIndex widgetIndex) override
{
if (widgetIndex <= WIDX_TAB_3)
CommonMouseUp(widgetIndex);
else
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewMouseUp(widgetIndex);
break;
case WINDOW_STAFF_OPTIONS:
OptionsMouseUp(widgetIndex);
}
}
}
void OnMouseDown(WidgetIndex widgetIndex) override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewOnMouseDown(widgetIndex);
break;
case WINDOW_STAFF_OPTIONS:
OptionsOnMouseDown(widgetIndex);
break;
}
}
void OnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex) override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewOnDropdown(widgetIndex, dropdownIndex);
break;
case WINDOW_STAFF_OPTIONS:
OptionsOnDropdown(widgetIndex, dropdownIndex);
break;
}
}
void OnPrepareDraw() override
{
CommonPrepareDrawBefore();
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewPrepareDraw();
break;
case WINDOW_STAFF_OPTIONS:
OptionsPrepareDraw();
break;
}
CommonPrepareDrawAfter();
}
void OnDraw(DrawPixelInfo& dpi) override
{
DrawWidgets(dpi);
DrawTabImages(dpi);
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewDraw(dpi);
break;
case WINDOW_STAFF_STATISTICS:
StatsDraw(dpi);
break;
}
}
void OnResize() override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewResize();
break;
case WINDOW_STAFF_OPTIONS:
OptionsResize();
break;
case WINDOW_STAFF_STATISTICS:
StatsResize();
break;
}
}
void OnUpdate() override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewUpdate();
break;
case WINDOW_STAFF_OPTIONS:
OptionsUpdate();
break;
case WINDOW_STAFF_STATISTICS:
StatsUpdate();
break;
}
}
void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewToolUpdate(widgetIndex, screenCoords);
break;
}
}
void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewToolDown(widgetIndex, screenCoords);
break;
}
}
void OnToolAbort(WidgetIndex widgetIndex) override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewToolAbort(widgetIndex);
break;
}
}
void OnViewportRotate() override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewViewportRotate();
break;
}
}
void OnTextInput(WidgetIndex widgetIndex, std::string_view text) override
{
switch (page)
{
case WINDOW_STAFF_OVERVIEW:
OverviewTextInput(widgetIndex, text);
break;
}
}
private:
#pragma region Common events
void CommonMouseUp(WidgetIndex widgetIndex)
{
switch (widgetIndex)
{
case WIDX_CLOSE:
Close();
break;
case WIDX_TAB_1:
case WIDX_TAB_2:
case WIDX_TAB_3:
SetPage(widgetIndex - WIDX_TAB_1);
break;
}
}
void CommonPrepareDrawBefore()
{
ColourSchemeUpdateByClass(this, static_cast<WindowClass>(WindowClass::Staff));
if (window_staff_page_widgets[page] != widgets)
{
widgets = window_staff_page_widgets[page];
InitScrollWidgets();
}
SetPressedTab();
DisableWidgets();
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
auto ft = Formatter::Common();
staff->FormatNameTo(ft);
ResizeFrameWithPage();
}
void CommonPrepareDrawAfter()
{
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_3);
}
#pragma endregion
#pragma region Overview tab events
void OverviewMouseUp(WidgetIndex widgetIndex)
{
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
switch (widgetIndex)
{
case WIDX_PICKUP:
{
_pickedPeepOldX = staff->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_STAFF__WIDX_PICKUP, Tool::Picker);
}
});
GameActions::Execute(&pickupAction);
}
break;
case WIDX_FIRE:
{
auto intent = Intent(WindowClass::FirePrompt);
intent.PutExtra(INTENT_EXTRA_PEEP, staff);
ContextOpenIntent(&intent);
break;
}
case WIDX_RENAME:
{
auto peepName = staff->GetName();
WindowTextInputRawOpen(
this, widgetIndex, STR_STAFF_TITLE_STAFF_MEMBER_NAME, STR_STAFF_PROMPT_ENTER_NAME, {}, peepName.c_str(),
32);
break;
}
}
}
void OverviewOnMouseDown(WidgetIndex widgetIndex)
{
Widget* widget = &widgets[widgetIndex];
switch (widgetIndex)
{
case WIDX_LOCATE:
ShowLocateDropdown(widget);
break;
case WIDX_PATROL:
{
// Dropdown names
gDropdownItems[0].Format = STR_SET_PATROL_AREA;
gDropdownItems[1].Format = STR_CLEAR_PATROL_AREA;
auto ddPos = ScreenCoordsXY{ widget->left + windowPos.x, widget->top + windowPos.y };
int32_t extraHeight = widget->height() + 1;
WindowDropdownShowText(ddPos, extraHeight, colours[1], 0, 2);
gDropdownDefaultIndex = 0;
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
// Disable clear patrol area if no area is set.
if (!staff->HasPatrolArea())
{
Dropdown::SetDisabled(1, true);
}
}
}
}
void OverviewOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
{
switch (widgetIndex)
{
case WIDX_LOCATE:
{
if (dropdownIndex == 0)
{
ScrollToViewport();
}
else if (dropdownIndex == 1)
{
FollowPeep();
}
break;
}
case WIDX_PATROL:
{
// Clear patrol
if (dropdownIndex == 1)
{
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
WindowCloseByClass(WindowClass::PatrolArea);
auto staffSetPatrolAreaAction = StaffSetPatrolAreaAction(
staff->Id, {}, StaffSetPatrolAreaMode::ClearAll);
GameActions::Execute(&staffSetPatrolAreaAction);
}
else
{
auto staffId = EntityId::FromUnderlying(number);
if (WindowPatrolAreaGetCurrentStaffId() == staffId)
{
WindowCloseByClass(WindowClass::PatrolArea);
}
else
{
PatrolAreaOpen(staffId);
}
}
break;
}
}
}
void OverviewPrepareDraw()
{
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
widgets[WIDX_VIEWPORT].right = width - 26;
widgets[WIDX_VIEWPORT].bottom = height - 14;
widgets[WIDX_BTM_LABEL].right = width - 26;
widgets[WIDX_BTM_LABEL].top = height - 13;
widgets[WIDX_BTM_LABEL].bottom = height - 3;
widgets[WIDX_PICKUP].left = width - 25;
widgets[WIDX_PICKUP].right = width - 2;
SetWidgetPressed(WIDX_PATROL, WindowPatrolAreaGetCurrentStaffId() == staff->Id);
widgets[WIDX_PATROL].left = width - 25;
widgets[WIDX_PATROL].right = width - 2;
widgets[WIDX_RENAME].left = width - 25;
widgets[WIDX_RENAME].right = width - 2;
widgets[WIDX_LOCATE].left = width - 25;
widgets[WIDX_LOCATE].right = width - 2;
widgets[WIDX_FIRE].left = width - 25;
widgets[WIDX_FIRE].right = width - 2;
}
void OverviewDraw(DrawPixelInfo& 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), WindowGetViewportSoundIconPos(*this));
}
}
// Draw the centred label
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
auto ft = Formatter();
staff->FormatActionTo(ft);
const auto& widget = widgets[WIDX_BTM_LABEL];
auto screenPos = windowPos + ScreenCoordsXY{ widget.midX(), widget.top };
int32_t widgetWidth = widget.width();
DrawTextEllipsised(dpi, screenPos, widgetWidth, STR_BLACK_STRING, ft, { TextAlignment::CENTRE });
}
void DrawOverviewTabImage(DrawPixelInfo& dpi)
{
if (IsWidgetDisabled(WIDX_TAB_1))
return;
const auto& widget = widgets[WIDX_TAB_1];
int32_t widgetWidth = widget.width() - 1;
int32_t widgetHeight = widget.height() - 1;
auto screenCoords = windowPos + ScreenCoordsXY{ widget.left + 1, widget.top + 1 };
if (page == WINDOW_STAFF_OVERVIEW)
widgetHeight++;
DrawPixelInfo clip_dpi;
if (!ClipDrawPixelInfo(clip_dpi, dpi, screenCoords, widgetWidth, widgetHeight))
{
return;
}
screenCoords = ScreenCoordsXY{ 14, 20 };
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
if (staff->Is<Staff>() && staff->AssignedStaffType == StaffType::Entertainer)
screenCoords.y++;
int32_t imageIndex = GetPeepAnimation(staff->SpriteType).base_image + 1;
int32_t offset = 0;
if (page == WINDOW_STAFF_OVERVIEW)
{
offset = _tabAnimationOffset;
offset = Floor2(offset, 4);
}
imageIndex += offset;
GfxDrawSprite(clip_dpi, ImageId(imageIndex, staff->TshirtColour, staff->TrousersColour), screenCoords);
}
void OverviewResize()
{
min_width = WW;
max_width = 500;
min_height = WH;
max_height = 450;
if (width < min_width)
{
width = min_width;
Invalidate();
}
if (width > max_width)
{
Invalidate();
width = max_width;
}
if (height < min_height)
{
height = min_height;
Invalidate();
}
if (height > max_height)
{
Invalidate();
height = max_height;
}
if (viewport != nullptr)
{
int32_t newWidth = width - 30;
int32_t newHeight = height - 62;
// Update the viewport size
if (viewport->width != newWidth || viewport->height != newHeight)
{
viewport->width = newWidth;
viewport->height = newHeight;
viewport->view_width = viewport->zoom.ApplyTo(newWidth);
viewport->view_height = viewport->zoom.ApplyTo(newHeight);
}
}
ViewportInit();
}
void OverviewUpdate()
{
_tabAnimationOffset++;
_tabAnimationOffset %= 24;
// Update pickup animation, can only happen in this tab.
picked_peep_frame++;
picked_peep_frame %= 48;
InvalidateWidget(WIDX_TAB_1);
}
void OverviewToolUpdate(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;
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
auto baseImageId = GetPeepAnimation(staff->SpriteType, PeepActionSpriteType::Ui).base_image;
baseImageId += picked_peep_frame >> 2;
gPickupPeepImage = ImageId(baseImageId, staff->TshirtColour, staff->TrousersColour);
}
void OverviewToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords)
{
if (widgetIndex != WIDX_PICKUP)
return;
const auto staffEntityId = EntityId::FromUnderlying(number);
TileElement* tileElement;
auto destCoords = FootpathGetCoordinatesFromPos({ screenCoords.x, screenCoords.y + 16 }, nullptr, &tileElement);
if (destCoords.IsNull())
return;
PeepPickupAction pickupAction{
PeepPickupType::Place, staffEntityId, { 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 OverviewToolAbort(WidgetIndex widgetIndex)
{
if (widgetIndex != WIDX_PICKUP)
return;
PeepPickupAction pickupAction{
PeepPickupType::Cancel, EntityId::FromUnderlying(number), { _pickedPeepOldX, 0, 0 }, NetworkGetCurrentPlayerId()
};
GameActions::Execute(&pickupAction);
}
void OverviewViewportRotate()
{
ViewportInit();
}
void OverviewTextInput(WidgetIndex widgetIndex, std::string_view text)
{
if (widgetIndex != WIDX_RENAME)
return;
if (text.empty())
return;
auto gameAction = StaffSetNameAction(EntityId::FromUnderlying(number), std::string{ text });
GameActions::Execute(&gameAction);
}
#pragma endregion
#pragma region Options tab events
void OptionsMouseUp(WidgetIndex widgetIndex)
{
switch (widgetIndex)
{
case WIDX_CHECKBOX_1:
case WIDX_CHECKBOX_2:
case WIDX_CHECKBOX_3:
case WIDX_CHECKBOX_4:
SetOrder(widgetIndex - WIDX_CHECKBOX_1);
break;
}
}
void OptionsOnMouseDown(WidgetIndex widgetIndex)
{
if (widgetIndex != WIDX_COSTUME_BTN)
{
return;
}
auto* ddWidget = &widgets[WIDX_COSTUME_BOX];
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
int32_t checkedIndex = -1;
// This will be moved below where Items Checked is when all
// of dropdown related functions are finished. This prevents
// the dropdown from not working on first click.
int32_t numCostumes = StaffGetAvailableEntertainerCostumeList(_availableCostumes);
for (int32_t i = 0; i < numCostumes; i++)
{
EntertainerCostume costume = _availableCostumes[i];
if (staff->SpriteType == EntertainerCostumeToSprite(costume))
{
checkedIndex = i;
}
gDropdownItems[i].Args = StaffCostumeNames[EnumValue(costume)];
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
}
auto ddPos = ScreenCoordsXY{ ddWidget->left + windowPos.x, ddWidget->top + windowPos.y };
int32_t ddHeight = ddWidget->height() + 1;
int32_t ddWidth = ddWidget->width() - 3;
WindowDropdownShowTextCustomWidth(ddPos, ddHeight, colours[1], 0, Dropdown::Flag::StayOpen, numCostumes, ddWidth);
// See above note.
if (checkedIndex != -1)
{
Dropdown::SetChecked(checkedIndex, true);
}
}
void OptionsOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
{
if (widgetIndex != WIDX_COSTUME_BTN)
{
return;
}
if (dropdownIndex == -1)
return;
EntertainerCostume costume = _availableCostumes[dropdownIndex];
auto staffSetCostumeAction = StaffSetCostumeAction(EntityId::FromUnderlying(number), costume);
GameActions::Execute(&staffSetCostumeAction);
}
void OptionsPrepareDraw()
{
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
switch (staff->AssignedStaffType)
{
case StaffType::Entertainer:
widgets[WIDX_CHECKBOX_1].type = WindowWidgetType::Empty;
widgets[WIDX_CHECKBOX_2].type = WindowWidgetType::Empty;
widgets[WIDX_CHECKBOX_3].type = WindowWidgetType::Empty;
widgets[WIDX_CHECKBOX_4].type = WindowWidgetType::Empty;
widgets[WIDX_COSTUME_BOX].type = WindowWidgetType::DropdownMenu;
widgets[WIDX_COSTUME_BTN].type = WindowWidgetType::Button;
widgets[WIDX_COSTUME_BOX].text = StaffCostumeNames[EnumValue(staff->SpriteType) - 4];
break;
case StaffType::Handyman:
widgets[WIDX_CHECKBOX_1].type = WindowWidgetType::Checkbox;
widgets[WIDX_CHECKBOX_1].text = STR_STAFF_OPTION_SWEEP_FOOTPATHS;
widgets[WIDX_CHECKBOX_2].type = WindowWidgetType::Checkbox;
widgets[WIDX_CHECKBOX_2].text = STR_STAFF_OPTION_WATER_GARDENS;
widgets[WIDX_CHECKBOX_3].type = WindowWidgetType::Checkbox;
widgets[WIDX_CHECKBOX_3].text = STR_STAFF_OPTION_EMPTY_LITTER;
widgets[WIDX_CHECKBOX_4].type = WindowWidgetType::Checkbox;
widgets[WIDX_CHECKBOX_4].text = STR_STAFF_OPTION_MOW_GRASS;
widgets[WIDX_COSTUME_BOX].type = WindowWidgetType::Empty;
widgets[WIDX_COSTUME_BTN].type = WindowWidgetType::Empty;
OptionsSetCheckboxValues();
break;
case StaffType::Mechanic:
widgets[WIDX_CHECKBOX_1].type = WindowWidgetType::Checkbox;
widgets[WIDX_CHECKBOX_1].text = STR_INSPECT_RIDES;
widgets[WIDX_CHECKBOX_2].type = WindowWidgetType::Checkbox;
widgets[WIDX_CHECKBOX_2].text = STR_FIX_RIDES;
widgets[WIDX_CHECKBOX_3].type = WindowWidgetType::Empty;
widgets[WIDX_CHECKBOX_4].type = WindowWidgetType::Empty;
widgets[WIDX_COSTUME_BOX].type = WindowWidgetType::Empty;
widgets[WIDX_COSTUME_BTN].type = WindowWidgetType::Empty;
widgets[WIDX_COSTUME_BTN].type = WindowWidgetType::Empty;
OptionsSetCheckboxValues();
break;
case StaffType::Security:
// Security guards don't have an options screen.
break;
case StaffType::Count:
break;
}
}
void OptionsSetCheckboxValues()
{
SetCheckboxValue(WIDX_CHECKBOX_1, false);
SetCheckboxValue(WIDX_CHECKBOX_2, false);
SetCheckboxValue(WIDX_CHECKBOX_3, false);
SetCheckboxValue(WIDX_CHECKBOX_4, false);
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
auto staffOrders = staff->StaffOrders;
for (auto index = UtilBitScanForward(staffOrders); index != -1; index = UtilBitScanForward(staffOrders))
{
staffOrders &= ~(1 << index);
SetCheckboxValue(WIDX_CHECKBOX_1 + index, true);
}
}
void OptionsResize()
{
min_width = 190;
max_width = 190;
min_height = 126;
max_height = 126;
if (width < min_width)
{
width = min_width;
Invalidate();
}
if (width > max_width)
{
Invalidate();
width = max_width;
}
if (height < min_height)
{
height = min_height;
Invalidate();
}
if (height > max_height)
{
Invalidate();
height = max_height;
}
}
void OptionsUpdate()
{
frame_no++;
InvalidateWidget(WIDX_TAB_2);
}
#pragma endregion
#pragma region Statistics tab events
void StatsDraw(DrawPixelInfo& dpi)
{
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
auto screenCoords = windowPos + ScreenCoordsXY{ widgets[WIDX_RESIZE].left + 4, widgets[WIDX_RESIZE].top + 4 };
if (!(GetGameState().Park.Flags & PARK_FLAGS_NO_MONEY))
{
auto ft = Formatter();
ft.Add<money64>(GetStaffWage(staff->AssignedStaffType));
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_WAGES, ft);
screenCoords.y += LIST_ROW_HEIGHT;
}
auto ft = Formatter();
ft.Add<int32_t>(staff->GetHireDate());
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_EMPLOYED_FOR, ft);
screenCoords.y += LIST_ROW_HEIGHT;
switch (staff->AssignedStaffType)
{
case StaffType::Handyman:
ft = Formatter();
ft.Add<uint32_t>(staff->StaffLawnsMown);
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_LAWNS_MOWN, ft);
screenCoords.y += LIST_ROW_HEIGHT;
ft = Formatter();
ft.Add<uint32_t>(staff->StaffGardensWatered);
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_GARDENS_WATERED, ft);
screenCoords.y += LIST_ROW_HEIGHT;
ft = Formatter();
ft.Add<uint32_t>(staff->StaffLitterSwept);
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_LITTER_SWEPT, ft);
screenCoords.y += LIST_ROW_HEIGHT;
ft = Formatter();
ft.Add<uint32_t>(staff->StaffBinsEmptied);
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_BINS_EMPTIED, ft);
break;
case StaffType::Mechanic:
ft = Formatter();
ft.Add<uint32_t>(staff->StaffRidesInspected);
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_RIDES_INSPECTED, ft);
screenCoords.y += LIST_ROW_HEIGHT;
ft = Formatter();
ft.Add<uint32_t>(staff->StaffRidesFixed);
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_RIDES_FIXED, ft);
break;
case StaffType::Security:
ft = Formatter();
ft.Add<uint32_t>(staff->StaffVandalsStopped);
DrawTextBasic(dpi, screenCoords, STR_STAFF_STAT_VANDALS_STOPPED, ft);
break;
case StaffType::Entertainer:
case StaffType::Count:
break;
}
}
void StatsResize()
{
min_width = 190;
max_width = 190;
min_height = 126;
max_height = 126;
if (width < min_width)
{
width = min_width;
Invalidate();
}
if (width > max_width)
{
Invalidate();
width = max_width;
}
if (height < min_height)
{
height = min_height;
Invalidate();
}
if (height > max_height)
{
Invalidate();
height = max_height;
}
}
void StatsUpdate()
{
frame_no++;
InvalidateWidget(WIDX_TAB_3);
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
if (staff->WindowInvalidateFlags & PEEP_INVALIDATE_STAFF_STATS)
{
staff->WindowInvalidateFlags &= ~PEEP_INVALIDATE_STAFF_STATS;
Invalidate();
}
}
#pragma endregion
void DisableWidgets()
{
const auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
for (WidgetIndex widgetIndex = WIDX_TAB_1; widgets[widgetIndex].type != kWidgetsEnd.type; widgetIndex++)
{
SetWidgetDisabled(widgetIndex, false);
}
if (staff->AssignedStaffType == StaffType::Security)
{
SetWidgetDisabled(WIDX_TAB_2, true);
}
if (page == WINDOW_STAFF_OVERVIEW)
{
if (staff->CanBePickedUp())
{
SetWidgetDisabled(WIDX_PICKUP, false);
}
else
{
SetWidgetDisabled(WIDX_PICKUP, true);
}
SetWidgetDisabled(WIDX_FIRE, staff->State == PeepState::Fixing || staff->State == PeepState::Inspecting);
}
}
void CancelTools()
{
if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE))
{
if (number == gCurrentToolWidget.window_number && classification == gCurrentToolWidget.window_classification)
ToolCancel();
}
}
void SetPage(int32_t pageNum)
{
CancelTools();
int32_t listen = 0;
if (page == WINDOW_STAFF_OVERVIEW && viewport != nullptr)
{
if (!(viewport->flags & VIEWPORT_FLAG_SOUND_ON))
listen = 1;
}
page = pageNum;
frame_no = 0;
pressed_widgets = 0;
hold_down_widgets = 0;
widgets = window_staff_page_widgets[page];
RemoveViewport();
Invalidate();
OnResize();
OnPrepareDraw();
InitScrollWidgets();
ViewportInit();
Invalidate();
if (listen && viewport != nullptr)
viewport->flags |= VIEWPORT_FLAG_SOUND_ON;
}
void SetPressedTab()
{
for (int32_t i = 0; i < WINDOW_STAFF_PAGE_COUNT; i++)
SetWidgetPressed((WIDX_TAB_1 + i), false);
SetWidgetPressed(WIDX_TAB_1 + page, true);
}
void SetOrder(int32_t orderId)
{
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
uint8_t newOrders = staff->StaffOrders ^ (1 << orderId);
auto staffSetOrdersAction = StaffSetOrdersAction(EntityId::FromUnderlying(number), newOrders);
GameActions::Execute(&staffSetOrdersAction);
}
void ViewportInit()
{
if (page != WINDOW_STAFF_OVERVIEW)
return;
auto staff = GetStaff();
if (staff == nullptr)
{
return;
}
std::optional<Focus> tempFocus;
if (staff->State != PeepState::Picked)
{
tempFocus = Focus(staff->Id);
}
uint16_t viewport_flags;
if (viewport != nullptr)
{
if (tempFocus == focus)
return;
viewport_flags = viewport->flags;
RemoveViewport();
}
else
{
viewport_flags = 0;
if (gConfigGeneral.AlwaysShowGridlines)
viewport_flags |= VIEWPORT_FLAG_GRIDLINES;
}
OnPrepareDraw();
focus = tempFocus;
if (staff->State != PeepState::Picked)
{
if (viewport == nullptr)
{
const auto& viewWidget = widgets[WIDX_VIEWPORT];
auto screenPos = ScreenCoordsXY{ viewWidget.left + 1 + windowPos.x, viewWidget.top + 1 + windowPos.y };
int32_t viewportWidth = viewWidget.width() - 1;
int32_t viewportHeight = viewWidget.height() - 1;
ViewportCreate(this, screenPos, viewportWidth, viewportHeight, focus.value());
flags |= WF_NO_SCROLLING;
Invalidate();
}
}
if (viewport != nullptr)
viewport->flags = viewport_flags;
Invalidate();
}
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 FollowPeep()
{
WindowBase* main = WindowGetMain();
WindowFollowSprite(*main, EntityId::FromUnderlying(number));
}
void DrawTabImages(DrawPixelInfo& dpi)
{
DrawOverviewTabImage(dpi);
DrawTabImage(dpi, WINDOW_STAFF_OPTIONS, SPR_TAB_STAFF_OPTIONS_0);
DrawTabImage(dpi, WINDOW_STAFF_STATISTICS, SPR_TAB_STATS_0);
}
void DrawTabImage(DrawPixelInfo& dpi, int32_t p, int32_t baseImageId)
{
WidgetIndex widgetIndex = WIDX_TAB_1 + p;
Widget* widget = &widgets[widgetIndex];
auto screenCoords = windowPos + ScreenCoordsXY{ widget->left, widget->top };
if (!IsWidgetDisabled(widgetIndex))
{
if (page == p)
{
int32_t frame = frame_no / TabAnimationDivisor[page - 1];
baseImageId += (frame % TabAnimationFrames);
}
// Draw normal, enabled sprite.
GfxDrawSprite(dpi, ImageId(baseImageId), screenCoords);
}
}
Staff* GetStaff()
{
return GetEntity<Staff>(EntityId::FromUnderlying(number));
}
static constexpr int32_t TabAnimationDivisor[] = {
2, // WINDOW_STAFF_OPTIONS,
4, // WINDOW_STAFF_STATISTICS,
};
static constexpr int32_t TabAnimationFrames = 7;
};
WindowBase* StaffOpen(Peep* peep)
{
auto w = static_cast<StaffWindow*>(WindowBringToFrontByNumber(WindowClass::Peep, peep->Id.ToUnderlying()));
if (w != nullptr)
return w;
w = WindowCreate<StaffWindow>(WindowClass::Peep, WW, WH, WF_10 | WF_RESIZABLE);
if (w == nullptr)
return nullptr;
if (w != nullptr)
w->Initialise(peep->Id);
return w;
}
} // namespace OpenRCT2::Ui::Windows