1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include <limits>
11 #include <openrct2-ui/interface/Dropdown.h>
12 #include <openrct2-ui/interface/Viewport.h>
13 #include <openrct2-ui/interface/Widget.h>
14 #include <openrct2-ui/windows/Window.h>
15 #include <openrct2/Context.h>
16 #include <openrct2/Game.h>
17 #include <openrct2/Input.h>
18 #include <openrct2/actions/StaffFireAction.h>
19 #include <openrct2/actions/StaffSetColourAction.h>
20 #include <openrct2/config/Config.h>
21 #include <openrct2/drawing/Drawing.h>
22 #include <openrct2/localisation/Localisation.h>
23 #include <openrct2/management/Finance.h>
24 #include <openrct2/peep/Staff.h>
25 #include <openrct2/sprites.h>
26 #include <openrct2/util/Util.h>
27 #include <openrct2/windows/Intent.h>
28 #include <openrct2/world/EntityList.h>
29 #include <openrct2/world/Footpath.h>
30 #include <openrct2/world/Park.h>
31 #include <openrct2/world/Sprite.h>
32 #include <vector>
33 
34 enum
35 {
36     WINDOW_STAFF_LIST_TAB_HANDYMEN,
37     WINDOW_STAFF_LIST_TAB_MECHANICS,
38     WINDOW_STAFF_LIST_TAB_SECURITY,
39     WINDOW_STAFF_LIST_TAB_ENTERTAINERS
40 };
41 
42 enum WINDOW_STAFF_LIST_WIDGET_IDX
43 {
44     WIDX_STAFF_LIST_BACKGROUND,
45     WIDX_STAFF_LIST_TITLE,
46     WIDX_STAFF_LIST_CLOSE,
47     WIDX_STAFF_LIST_TAB_CONTENT_PANEL,
48     WIDX_STAFF_LIST_HANDYMEN_TAB,
49     WIDX_STAFF_LIST_MECHANICS_TAB,
50     WIDX_STAFF_LIST_SECURITY_TAB,
51     WIDX_STAFF_LIST_ENTERTAINERS_TAB,
52     WIDX_STAFF_LIST_LIST,
53     WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER,
54     WIDX_STAFF_LIST_HIRE_BUTTON,
55     WIDX_STAFF_LIST_QUICK_FIRE,
56     WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON,
57     WIDX_STAFF_LIST_MAP,
58 };
59 
60 static constexpr const rct_string_id WINDOW_TITLE = STR_STAFF;
61 static constexpr const int32_t WW = 320;
62 static constexpr const int32_t WH = 270;
63 constexpr int32_t MAX_WW = 500;
64 constexpr int32_t MAX_WH = 450;
65 
66 // clang-format off
67 static rct_widget window_staff_list_widgets[] = {
68     WINDOW_SHIM(WINDOW_TITLE, WW, WH),
69     MakeWidget({  0, 43}, {    WW, WH - 43}, WindowWidgetType::Resize,    WindowColour::Secondary                                                 ), // tab content panel
70     MakeTab   ({  3, 17},                                                                             STR_STAFF_HANDYMEN_TAB_TIP    ), // handymen tab
71     MakeTab   ({ 34, 17},                                                                             STR_STAFF_MECHANICS_TAB_TIP   ), // mechanics tab
72     MakeTab   ({ 65, 17},                                                                             STR_STAFF_SECURITY_TAB_TIP    ), // security guards tab
73     MakeTab   ({ 96, 17},                                                                             STR_STAFF_ENTERTAINERS_TAB_TIP), // entertainers tab
74     MakeWidget({  3, 72}, {WW - 6,     195}, WindowWidgetType::Scroll,    WindowColour::Secondary, SCROLL_VERTICAL                                ), // staff list
75     MakeWidget({130, 58}, {    12,      12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, STR_NONE,        STR_UNIFORM_COLOUR_TIP        ), // uniform colour picker
76     MakeWidget({165, 17}, {   145,      13}, WindowWidgetType::Button,    WindowColour::Primary  , STR_NONE,        STR_HIRE_STAFF_TIP            ), // hire button
77     MakeWidget({243, 46}, {    24,      24}, WindowWidgetType::FlatBtn,   WindowColour::Secondary, SPR_DEMOLISH,    STR_QUICK_FIRE_STAFF          ), // quick fire staff
78     MakeWidget({267, 46}, {    24,      24}, WindowWidgetType::FlatBtn,   WindowColour::Secondary, SPR_PATROL_BTN,  STR_SHOW_PATROL_AREA_TIP      ), // show staff patrol area tool
79     MakeWidget({291, 46}, {    24,      24}, WindowWidgetType::FlatBtn,   WindowColour::Secondary, SPR_MAP,         STR_SHOW_STAFF_ON_MAP_TIP     ), // show staff on map button
80     WIDGETS_END,
81 };
82 // clang-format on
83 
84 class StaffListWindow final : public Window
85 {
86 private:
87     struct StaffNamingConvention
88     {
89         rct_string_id Plural;
90         rct_string_id Singular;
91         rct_string_id ActionHire;
92     };
93 
94     std::vector<uint16_t> _staffList;
95     bool _quickFireMode{};
96     std::optional<size_t> _highlightedIndex{};
97     int32_t _selectedTab{};
98     uint32_t _tabAnimationIndex{};
99 
100 public:
OnOpen()101     void OnOpen() override
102     {
103         widgets = window_staff_list_widgets;
104         enabled_widgets = (1ULL << WIDX_STAFF_LIST_CLOSE) | (1ULL << WIDX_STAFF_LIST_HANDYMEN_TAB)
105             | (1ULL << WIDX_STAFF_LIST_MECHANICS_TAB) | (1ULL << WIDX_STAFF_LIST_SECURITY_TAB)
106             | (1ULL << WIDX_STAFF_LIST_ENTERTAINERS_TAB) | (1ULL << WIDX_STAFF_LIST_HIRE_BUTTON)
107             | (1ULL << WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER) | (1ULL << WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON)
108             | (1ULL << WIDX_STAFF_LIST_MAP) | (1ULL << WIDX_STAFF_LIST_QUICK_FIRE);
109         WindowInitScrollWidgets(this);
110 
111         widgets[WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER].type = WindowWidgetType::Empty;
112         min_width = WW;
113         min_height = WH;
114         max_width = MAX_WW;
115         max_height = MAX_WH;
116 
117         RefreshList();
118     }
119 
OnClose()120     void OnClose() override
121     {
122         CancelTools();
123     }
124 
OnMouseUp(rct_widgetindex widgetIndex)125     void OnMouseUp(rct_widgetindex widgetIndex) override
126     {
127         switch (widgetIndex)
128         {
129             case WIDX_STAFF_LIST_CLOSE:
130                 Close();
131                 break;
132             case WIDX_STAFF_LIST_HIRE_BUTTON:
133             {
134                 auto staffType = GetSelectedStaffType();
135                 auto costume = EntertainerCostume::Count;
136                 if (staffType == StaffType::Entertainer)
137                 {
138                     costume = GetRandomEntertainerCostume();
139                 }
140                 staff_hire_new_member(staffType, costume);
141                 break;
142             }
143             case WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON:
144                 if (!tool_set(this, WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON, Tool::Crosshair))
145                 {
146                     show_gridlines();
147                     gStaffDrawPatrolAreas = _selectedTab | 0x8000;
148                     gfx_invalidate_screen();
149                 }
150                 break;
151             case WIDX_STAFF_LIST_MAP:
152                 context_open_window(WC_MAP);
153                 break;
154             case WIDX_STAFF_LIST_QUICK_FIRE:
155                 _quickFireMode = !_quickFireMode;
156                 Invalidate();
157                 break;
158         }
159     }
160 
OnResize()161     void OnResize() override
162     {
163         min_width = WW;
164         min_height = WH;
165         if (width < min_width)
166         {
167             width = min_width;
168             Invalidate();
169         }
170         if (height < min_height)
171         {
172             height = min_height;
173             Invalidate();
174         }
175     }
176 
OnUpdate()177     void OnUpdate() override
178     {
179         _tabAnimationIndex++;
180         if (_tabAnimationIndex >= 24)
181         {
182             _tabAnimationIndex = 0;
183         }
184         else
185         {
186             InvalidateWidget(WIDX_STAFF_LIST_HANDYMEN_TAB + _selectedTab);
187 
188             // Enable highlighting of these staff members in map window
189             if (window_find_by_class(WC_MAP) != nullptr)
190             {
191                 gWindowMapFlashingFlags |= MapFlashingFlags::StaffListOpen;
192                 for (auto peep : EntityList<Staff>())
193                 {
194                     sprite_set_flashing(peep, false);
195                     if (peep->AssignedStaffType == GetSelectedStaffType())
196                     {
197                         sprite_set_flashing(peep, true);
198                     }
199                 }
200             }
201         }
202 
203         // Note this may be slow if number of staff increases a large amount.
204         // See GuestList for fix (more intents) if required.
205         RefreshList();
206     }
207 
OnMouseDown(rct_widgetindex widgetIndex)208     void OnMouseDown(rct_widgetindex widgetIndex) override
209     {
210         switch (widgetIndex)
211         {
212             case WIDX_STAFF_LIST_HANDYMEN_TAB:
213             case WIDX_STAFF_LIST_MECHANICS_TAB:
214             case WIDX_STAFF_LIST_SECURITY_TAB:
215             case WIDX_STAFF_LIST_ENTERTAINERS_TAB:
216             {
217                 auto newSelectedTab = widgetIndex - WIDX_STAFF_LIST_HANDYMEN_TAB;
218                 if (_selectedTab != newSelectedTab)
219                 {
220                     _selectedTab = static_cast<uint8_t>(newSelectedTab);
221                     Invalidate();
222                     scrolls[0].v_top = 0;
223                     CancelTools();
224                 }
225                 break;
226             }
227             case WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER:
228                 WindowDropdownShowColour(this, &widgets[widgetIndex], colours[1], staff_get_colour(GetSelectedStaffType()));
229                 break;
230         }
231     }
232 
OnDropdown(rct_widgetindex widgetIndex,int32_t dropdownIndex)233     void OnDropdown(rct_widgetindex widgetIndex, int32_t dropdownIndex) override
234     {
235         if (widgetIndex == WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER)
236         {
237             auto action = StaffSetColourAction(GetSelectedStaffType(), dropdownIndex);
238             GameActions::Execute(&action);
239         }
240     }
241 
OnPrepareDraw()242     void OnPrepareDraw() override
243     {
244         // Set selected tab
245         SetWidgetPressed(WIDX_STAFF_LIST_HANDYMEN_TAB, false);
246         SetWidgetPressed(WIDX_STAFF_LIST_MECHANICS_TAB, false);
247         SetWidgetPressed(WIDX_STAFF_LIST_SECURITY_TAB, false);
248         SetWidgetPressed(WIDX_STAFF_LIST_ENTERTAINERS_TAB, false);
249         SetWidgetPressed(_selectedTab + WIDX_STAFF_LIST_HANDYMEN_TAB, true);
250 
251         widgets[WIDX_STAFF_LIST_HIRE_BUTTON].text = GetStaffNamingConvention(GetSelectedStaffType()).ActionHire;
252         widgets[WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER].type = WindowWidgetType::Empty;
253 
254         if (GetSelectedStaffType() != StaffType::Entertainer)
255         {
256             widgets[WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER].type = WindowWidgetType::ColourBtn;
257             auto spriteIdPalette = SPRITE_ID_PALETTE_COLOUR_1(static_cast<uint32_t>(staff_get_colour(GetSelectedStaffType())));
258             widgets[WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER].image = spriteIdPalette | IMAGE_TYPE_TRANSPARENT | SPR_PALETTE_BTN;
259         }
260         SetWidgetPressed(WIDX_STAFF_LIST_QUICK_FIRE, _quickFireMode);
261 
262         widgets[WIDX_STAFF_LIST_BACKGROUND].right = width - 1;
263         widgets[WIDX_STAFF_LIST_BACKGROUND].bottom = height - 1;
264         widgets[WIDX_STAFF_LIST_TAB_CONTENT_PANEL].right = width - 1;
265         widgets[WIDX_STAFF_LIST_TAB_CONTENT_PANEL].bottom = height - 1;
266         widgets[WIDX_STAFF_LIST_TITLE].right = width - 2;
267         widgets[WIDX_STAFF_LIST_CLOSE].left = width - 2 - 11;
268         widgets[WIDX_STAFF_LIST_CLOSE].right = width - 2 - 11 + 10;
269         widgets[WIDX_STAFF_LIST_LIST].right = width - 4;
270         widgets[WIDX_STAFF_LIST_LIST].bottom = height - 15;
271         widgets[WIDX_STAFF_LIST_QUICK_FIRE].left = width - 77;
272         widgets[WIDX_STAFF_LIST_QUICK_FIRE].right = width - 54;
273         widgets[WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON].left = width - 53;
274         widgets[WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON].right = width - 30;
275         widgets[WIDX_STAFF_LIST_MAP].left = width - 29;
276         widgets[WIDX_STAFF_LIST_MAP].right = width - 6;
277         widgets[WIDX_STAFF_LIST_HIRE_BUTTON].left = width - 155;
278         widgets[WIDX_STAFF_LIST_HIRE_BUTTON].right = width - 11;
279     }
280 
OnDraw(rct_drawpixelinfo & dpi)281     void OnDraw(rct_drawpixelinfo& dpi) override
282     {
283         DrawWidgets(dpi);
284         DrawTabImages(dpi);
285 
286         if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
287         {
288             auto ft = Formatter();
289             ft.Add<money64>(GetStaffWage(GetSelectedStaffType()));
290             DrawTextBasic(&dpi, windowPos + ScreenCoordsXY{ width - 155, 32 }, STR_COST_PER_MONTH, ft);
291         }
292 
293         if (GetSelectedStaffType() != StaffType::Entertainer)
294         {
295             DrawTextBasic(
296                 &dpi, windowPos + ScreenCoordsXY{ 6, widgets[WIDX_STAFF_LIST_UNIFORM_COLOUR_PICKER].top + 1 },
297                 STR_UNIFORM_COLOUR);
298         }
299 
300         auto namingConvention = GetStaffNamingConvention(GetSelectedStaffType());
301         auto staffTypeStringId = _staffList.size() == 1 ? namingConvention.Singular : namingConvention.Plural;
302 
303         auto ft = Formatter();
304         ft.Add<uint16_t>(_staffList.size());
305         ft.Add<rct_string_id>(staffTypeStringId);
306 
307         DrawTextBasic(
308             &dpi, windowPos + ScreenCoordsXY{ 4, widgets[WIDX_STAFF_LIST_LIST].bottom + 2 }, STR_STAFF_LIST_COUNTER, ft);
309     }
310 
OnScrollGetSize(int32_t scrollIndex)311     ScreenSize OnScrollGetSize(int32_t scrollIndex) override
312     {
313         if (_highlightedIndex)
314         {
315             _highlightedIndex = {};
316             Invalidate();
317         }
318 
319         auto scrollHeight = static_cast<int16_t>(_staffList.size()) * SCROLLABLE_ROW_HEIGHT;
320         auto i = scrollHeight - widgets[WIDX_STAFF_LIST_LIST].bottom + widgets[WIDX_STAFF_LIST_LIST].top + 21;
321         if (i < 0)
322             i = 0;
323         if (i < scrolls[0].v_top)
324         {
325             scrolls[0].v_top = i;
326             Invalidate();
327         }
328 
329         auto scrollWidth = widgets[WIDX_STAFF_LIST_LIST].width() - 15;
330         return { scrollWidth, scrollHeight };
331     }
332 
OnScrollMouseOver(int32_t scrollIndex,const ScreenCoordsXY & screenCoords)333     void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
334     {
335         auto i = static_cast<size_t>(screenCoords.y / SCROLLABLE_ROW_HEIGHT);
336         if (i != _highlightedIndex)
337         {
338             _highlightedIndex = static_cast<size_t>(i);
339             Invalidate();
340         }
341     }
342 
OnScrollMouseDown(int32_t scrollIndex,const ScreenCoordsXY & screenCoords)343     void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
344     {
345         int32_t i = screenCoords.y / SCROLLABLE_ROW_HEIGHT;
346         for (auto spriteIndex : _staffList)
347         {
348             if (i == 0)
349             {
350                 if (_quickFireMode)
351                 {
352                     auto staffFireAction = StaffFireAction(spriteIndex);
353                     GameActions::Execute(&staffFireAction);
354                 }
355                 else
356                 {
357                     auto peep = GetEntity<Staff>(spriteIndex);
358                     if (peep != nullptr)
359                     {
360                         auto intent = Intent(WC_PEEP);
361                         intent.putExtra(INTENT_EXTRA_PEEP, peep);
362                         context_open_intent(&intent);
363                     }
364                 }
365                 break;
366             }
367 
368             i--;
369         }
370     }
371 
OnScrollDraw(int32_t scrollIndex,rct_drawpixelinfo & dpi)372     void OnScrollDraw(int32_t scrollIndex, rct_drawpixelinfo& dpi) override
373     {
374         auto dpiCoords = ScreenCoordsXY{ dpi.x, dpi.y };
375         gfx_fill_rect(
376             &dpi, { dpiCoords, dpiCoords + ScreenCoordsXY{ dpi.width - 1, dpi.height - 1 } }, ColourMapA[colours[1]].mid_light);
377 
378         // How much space do we have for the name and action columns? (Discount scroll area and icons.)
379         const int32_t nonIconSpace = widgets[WIDX_STAFF_LIST_LIST].width() - 15 - 68;
380         const int32_t nameColumnSize = nonIconSpace * 0.42;
381         const int32_t actionColumnSize = nonIconSpace * 0.58;
382         const int32_t actionOffset = widgets[WIDX_STAFF_LIST_LIST].right - actionColumnSize - 15;
383 
384         auto y = 0;
385         size_t i = 0;
386         for (auto spriteIndex : _staffList)
387         {
388             if (y > dpi.y + dpi.height)
389             {
390                 break;
391             }
392 
393             if (y + 11 >= dpi.y)
394             {
395                 auto peep = GetEntity<Staff>(spriteIndex);
396                 if (peep == nullptr)
397                 {
398                     continue;
399                 }
400                 int32_t format = (_quickFireMode ? STR_RED_STRINGID : STR_BLACK_STRING);
401 
402                 if (i == _highlightedIndex)
403                 {
404                     gfx_filter_rect(&dpi, { 0, y, 800, y + (SCROLLABLE_ROW_HEIGHT - 1) }, FilterPaletteID::PaletteDarken1);
405                     format = (_quickFireMode ? STR_LIGHTPINK_STRINGID : STR_WINDOW_COLOUR_2_STRINGID);
406                 }
407 
408                 auto ft = Formatter();
409                 peep->FormatNameTo(ft);
410                 DrawTextEllipsised(&dpi, { 0, y }, nameColumnSize, format, ft);
411 
412                 ft = Formatter();
413                 peep->FormatActionTo(ft);
414                 DrawTextEllipsised(&dpi, { actionOffset, y }, actionColumnSize, format, ft);
415 
416                 // True if a patrol path is set for the worker
417                 if (peep->HasPatrolArea())
418                 {
419                     gfx_draw_sprite(&dpi, ImageId(SPR_STAFF_PATROL_PATH), { nameColumnSize + 5, y });
420                 }
421 
422                 auto staffOrderIcon_x = nameColumnSize + 20;
423                 if (peep->AssignedStaffType != StaffType::Entertainer)
424                 {
425                     auto staffOrders = peep->StaffOrders;
426                     auto staffOrderSprite = GetStaffOrderBaseSprite(GetSelectedStaffType());
427 
428                     while (staffOrders != 0)
429                     {
430                         if (staffOrders & 1)
431                         {
432                             gfx_draw_sprite(&dpi, ImageId(staffOrderSprite), { staffOrderIcon_x, y });
433                         }
434                         staffOrders = staffOrders >> 1;
435                         staffOrderIcon_x += 9;
436                         // TODO: Remove sprite ID addition
437                         staffOrderSprite++;
438                     }
439                 }
440                 else
441                 {
442                     gfx_draw_sprite(&dpi, ImageId(GetEntertainerCostumeSprite(peep->SpriteType)), { staffOrderIcon_x, y });
443                 }
444             }
445 
446             y += SCROLLABLE_ROW_HEIGHT;
447             i++;
448         }
449     }
450 
OnToolDown(rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)451     void OnToolDown(rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) override
452     {
453         if (widgetIndex == WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON)
454         {
455             auto closestStaffMember = GetClosestStaffMemberTo(screenCoords);
456             if (closestStaffMember != nullptr)
457             {
458                 tool_cancel();
459                 auto* staffWindow = window_staff_open(closestStaffMember);
460                 window_event_dropdown_call(staffWindow, WC_PEEP__WIDX_PATROL, 0);
461             }
462             else
463             {
464                 auto ft = Formatter();
465                 ft.Add<rct_string_id>(GetStaffNamingConvention(GetSelectedStaffType()).Plural);
466                 context_show_error(STR_NO_THING_IN_PARK_YET, STR_NONE, ft);
467             }
468         }
469     }
470 
OnToolAbort(rct_widgetindex widgetIndex)471     void OnToolAbort(rct_widgetindex widgetIndex) override
472     {
473         if (widgetIndex == WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON)
474         {
475             hide_gridlines();
476             tool_cancel();
477             gStaffDrawPatrolAreas = 0xFFFF;
478             gfx_invalidate_screen();
479         }
480     }
481 
RefreshList()482     void RefreshList()
483     {
484         _staffList.clear();
485 
486         for (auto peep : EntityList<Staff>())
487         {
488             sprite_set_flashing(peep, false);
489             if (peep->AssignedStaffType == GetSelectedStaffType())
490             {
491                 sprite_set_flashing(peep, true);
492                 _staffList.push_back(peep->sprite_index);
493             }
494         }
495 
496         std::sort(
497             _staffList.begin(), _staffList.end(), [](const uint16_t a, const uint16_t b) { return peep_compare(a, b) < 0; });
498     }
499 
500 private:
GetSelectedStaffType() const501     StaffType GetSelectedStaffType() const
502     {
503         return static_cast<StaffType>(_selectedTab);
504     }
505 
DrawTabImages(rct_drawpixelinfo & dpi) const506     void DrawTabImages(rct_drawpixelinfo& dpi) const
507     {
508         DrawTabImage(dpi, WINDOW_STAFF_LIST_TAB_HANDYMEN, PeepSpriteType::Handyman, gStaffHandymanColour);
509         DrawTabImage(dpi, WINDOW_STAFF_LIST_TAB_MECHANICS, PeepSpriteType::Mechanic, gStaffMechanicColour);
510         DrawTabImage(dpi, WINDOW_STAFF_LIST_TAB_SECURITY, PeepSpriteType::Security, gStaffSecurityColour);
511         DrawTabImage(dpi, WINDOW_STAFF_LIST_TAB_ENTERTAINERS, PeepSpriteType::EntertainerElephant);
512     }
513 
DrawTabImage(rct_drawpixelinfo & dpi,int32_t tabIndex,PeepSpriteType type,colour_t colour) const514     void DrawTabImage(rct_drawpixelinfo& dpi, int32_t tabIndex, PeepSpriteType type, colour_t colour) const
515     {
516         auto widgetIndex = WIDX_STAFF_LIST_HANDYMEN_TAB + tabIndex;
517         const auto& widget = widgets[widgetIndex];
518         auto imageId = (_selectedTab == tabIndex ? (_tabAnimationIndex & ~3) : 0);
519         imageId += GetPeepAnimation(type).base_image + 1;
520         gfx_draw_sprite(
521             &dpi, ImageId(imageId, colour), windowPos + ScreenCoordsXY{ (widget.left + widget.right) / 2, widget.bottom - 6 });
522     }
523 
DrawTabImage(rct_drawpixelinfo & dpi,int32_t tabIndex,PeepSpriteType type) const524     void DrawTabImage(rct_drawpixelinfo& dpi, int32_t tabIndex, PeepSpriteType type) const
525     {
526         auto widgetIndex = WIDX_STAFF_LIST_HANDYMEN_TAB + tabIndex;
527         const auto& widget = widgets[widgetIndex];
528         rct_drawpixelinfo clippedDpi;
529         if (clip_drawpixelinfo(
530                 &clippedDpi, &dpi, windowPos + ScreenCoordsXY{ widget.left + 1, widget.top + 1 },
531                 widget.right - widget.left - 1, widget.bottom - widget.top - 1))
532         {
533             auto imageId = (_selectedTab == 3 ? (_tabAnimationIndex & ~3) : 0);
534             imageId += GetPeepAnimation(type).base_image + 1;
535             gfx_draw_sprite(&clippedDpi, ImageId(imageId), { 15, 23 });
536         }
537     }
538 
CancelTools()539     void CancelTools()
540     {
541         if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
542         {
543             if (classification == gCurrentToolWidget.window_classification && number == gCurrentToolWidget.window_number)
544             {
545                 tool_cancel();
546             }
547         }
548     }
549 
GetClosestStaffMemberTo(const ScreenCoordsXY & screenCoords)550     Peep* GetClosestStaffMemberTo(const ScreenCoordsXY& screenCoords)
551     {
552         int32_t direction{};
553         TileElement* tileElement{};
554         auto footpathCoords = footpath_get_coordinates_from_pos(screenCoords, &direction, &tileElement);
555         if (footpathCoords.IsNull())
556             return nullptr;
557 
558         auto isPatrolAreaSet = staff_is_patrol_area_set_for_type(GetSelectedStaffType(), footpathCoords);
559 
560         Peep* closestPeep = nullptr;
561         auto closestPeepDistance = std::numeric_limits<int32_t>::max();
562         for (auto peep : EntityList<Staff>())
563         {
564             if (peep->AssignedStaffType != GetSelectedStaffType())
565                 continue;
566 
567             if (isPatrolAreaSet)
568             {
569                 if (!peep->HasPatrolArea())
570                 {
571                     continue;
572                 }
573                 if (!peep->IsLocationInPatrol(footpathCoords))
574                 {
575                     continue;
576                 }
577             }
578 
579             if (peep->x == LOCATION_NULL)
580             {
581                 continue;
582             }
583 
584             auto distance = std::abs(footpathCoords.x - peep->x) + std::abs(footpathCoords.y - peep->y);
585             if (distance < closestPeepDistance)
586             {
587                 closestPeepDistance = distance;
588                 closestPeep = peep;
589             }
590         }
591         return closestPeep;
592     }
593 
GetRandomEntertainerCostume()594     static EntertainerCostume GetRandomEntertainerCostume()
595     {
596         auto result = EntertainerCostume::Panda;
597         EntertainerCostume costumeList[static_cast<uint8_t>(EntertainerCostume::Count)];
598         int32_t numCostumes = staff_get_available_entertainer_costume_list(costumeList);
599         if (numCostumes > 0)
600         {
601             int32_t index = util_rand() % numCostumes;
602             result = costumeList[index];
603         }
604         return result;
605     }
606 
GetStaffNamingConvention(StaffType type)607     static constexpr StaffNamingConvention GetStaffNamingConvention(StaffType type)
608     {
609         switch (type)
610         {
611             default:
612             case StaffType::Handyman:
613                 return { STR_HANDYMAN_PLURAL, STR_HANDYMAN_SINGULAR, STR_HIRE_HANDYMAN };
614             case StaffType::Mechanic:
615                 return { STR_MECHANIC_PLURAL, STR_MECHANIC_SINGULAR, STR_HIRE_MECHANIC };
616             case StaffType::Security:
617                 return { STR_SECURITY_GUARD_PLURAL, STR_SECURITY_GUARD_SINGULAR, STR_HIRE_SECURITY_GUARD };
618             case StaffType::Entertainer:
619                 return { STR_ENTERTAINER_PLURAL, STR_ENTERTAINER_SINGULAR, STR_HIRE_ENTERTAINER };
620         }
621     }
622 
GetStaffOrderBaseSprite(StaffType type)623     static uint32_t GetStaffOrderBaseSprite(StaffType type)
624     {
625         switch (type)
626         {
627             case StaffType::Handyman:
628                 return SPR_STAFF_ORDERS_SWEEPING;
629             case StaffType::Mechanic:
630                 return SPR_STAFF_ORDERS_INSPECT_RIDES;
631             default:
632                 return 0;
633         }
634     }
635 
GetEntertainerCostumeSprite(PeepSpriteType type)636     static uint32_t GetEntertainerCostumeSprite(PeepSpriteType type)
637     {
638         switch (type)
639         {
640             default:
641             case PeepSpriteType::EntertainerPanda:
642                 return SPR_STAFF_COSTUME_PANDA;
643             case PeepSpriteType::EntertainerTiger:
644                 return SPR_STAFF_COSTUME_TIGER;
645             case PeepSpriteType::EntertainerElephant:
646                 return SPR_STAFF_COSTUME_ELEPHANT;
647             case PeepSpriteType::EntertainerRoman:
648                 return SPR_STAFF_COSTUME_ROMAN;
649             case PeepSpriteType::EntertainerGorilla:
650                 return SPR_STAFF_COSTUME_GORILLA;
651             case PeepSpriteType::EntertainerSnowman:
652                 return SPR_STAFF_COSTUME_SNOWMAN;
653             case PeepSpriteType::EntertainerKnight:
654                 return SPR_STAFF_COSTUME_KNIGHT;
655             case PeepSpriteType::EntertainerAstronaut:
656                 return SPR_STAFF_COSTUME_ASTRONAUT;
657             case PeepSpriteType::EntertainerBandit:
658                 return SPR_STAFF_COSTUME_BANDIT;
659             case PeepSpriteType::EntertainerSheriff:
660                 return SPR_STAFF_COSTUME_SHERIFF;
661             case PeepSpriteType::EntertainerPirate:
662                 return SPR_STAFF_COSTUME_PIRATE;
663         }
664     }
665 };
666 
window_staff_list_open()667 rct_window* window_staff_list_open()
668 {
669     return WindowFocusOrCreate<StaffListWindow>(WC_STAFF_LIST, WW, WH, WF_10 | WF_RESIZABLE);
670 }
671 
WindowStaffListRefresh()672 void WindowStaffListRefresh()
673 {
674     auto* window = window_find_by_class(WC_STAFF_LIST);
675     if (window != nullptr)
676     {
677         static_cast<StaffListWindow*>(window)->RefreshList();
678     }
679 }
680