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