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 "Viewport.h"
11 
12 #include "../Context.h"
13 #include "../Game.h"
14 #include "../Input.h"
15 #include "../OpenRCT2.h"
16 #include "../config/Config.h"
17 #include "../core/Guard.hpp"
18 #include "../core/JobPool.h"
19 #include "../drawing/Drawing.h"
20 #include "../drawing/IDrawingEngine.h"
21 #include "../paint/Paint.h"
22 #include "../peep/Guest.h"
23 #include "../peep/Staff.h"
24 #include "../ride/Ride.h"
25 #include "../ride/TrackDesign.h"
26 #include "../ride/Vehicle.h"
27 #include "../ui/UiContext.h"
28 #include "../ui/WindowManager.h"
29 #include "../util/Math.hpp"
30 #include "../world/Climate.h"
31 #include "../world/EntityList.h"
32 #include "../world/Map.h"
33 #include "Colour.h"
34 #include "Window.h"
35 #include "Window_internal.h"
36 
37 #include <algorithm>
38 #include <cstring>
39 #include <list>
40 #include <unordered_map>
41 
42 using namespace OpenRCT2;
43 
44 //#define DEBUG_SHOW_DIRTY_BOX
45 uint8_t gShowGridLinesRefCount;
46 uint8_t gShowLandRightsRefCount;
47 uint8_t gShowConstuctionRightsRefCount;
48 
49 static std::list<rct_viewport> _viewports;
50 rct_viewport* g_music_tracking_viewport;
51 
52 static std::unique_ptr<JobPool> _paintJobs;
53 static std::vector<paint_session*> _paintColumns;
54 
55 ScreenCoordsXY gSavedView;
56 ZoomLevel gSavedViewZoom;
57 uint8_t gSavedViewRotation;
58 
59 paint_entry* gNextFreePaintStruct;
60 uint8_t gCurrentRotation;
61 
62 static uint32_t _currentImageType;
InteractionInfo(const paint_struct * ps)63 InteractionInfo::InteractionInfo(const paint_struct* ps)
64     : Loc(ps->map_x, ps->map_y)
65     , Element(ps->tileElement)
66     , SpriteType(ps->sprite_type)
67 {
68 }
69 static void viewport_paint_weather_gloom(rct_drawpixelinfo* dpi);
70 
71 /**
72  * This is not a viewport function. It is used to setup many variables for
73  * multiple things.
74  *  rct2: 0x006E6EAC
75  */
viewport_init_all()76 void viewport_init_all()
77 {
78     if (!gOpenRCT2NoGraphics)
79     {
80         colours_init_maps();
81     }
82 
83     window_init_all();
84 
85     // ?
86     input_reset_flags();
87     input_set_state(InputState::Reset);
88     gPressedWidget.window_classification = 255;
89     gPickupPeepImage = UINT32_MAX;
90     reset_tooltip_not_shown();
91     gMapSelectFlags = 0;
92     gStaffDrawPatrolAreas = 0xFFFF;
93     textinput_cancel();
94 }
95 
96 /**
97  * Converts between 3d point of a sprite to 2d coordinates for centring on that
98  * sprite
99  *  rct2: 0x006EB0C1
100  * x : ax
101  * y : bx
102  * z : cx
103  * out_x : ax
104  * out_y : bx
105  */
centre_2d_coordinates(const CoordsXYZ & loc,rct_viewport * viewport)106 std::optional<ScreenCoordsXY> centre_2d_coordinates(const CoordsXYZ& loc, rct_viewport* viewport)
107 {
108     // If the start location was invalid
109     // propagate the invalid location to the output.
110     // This fixes a bug that caused the game to enter an infinite loop.
111     if (loc.IsNull())
112     {
113         return std::nullopt;
114     }
115 
116     auto screenCoord = translate_3d_to_2d_with_z(get_current_rotation(), loc);
117     screenCoord.x -= viewport->view_width / 2;
118     screenCoord.y -= viewport->view_height / 2;
119     return { screenCoord };
120 }
121 
GetPos() const122 CoordsXYZ Focus::GetPos() const
123 {
124     return std::visit(
125         [](auto&& arg) {
126             using T = std::decay_t<decltype(arg)>;
127             if constexpr (std::is_same_v<T, Focus::CoordinateFocus>)
128                 return arg;
129             else if constexpr (std::is_same_v<T, Focus::EntityFocus>)
130             {
131                 auto* centreEntity = GetEntity(arg);
132                 if (centreEntity != nullptr)
133                 {
134                     return CoordsXYZ{ centreEntity->x, centreEntity->y, centreEntity->z };
135                 }
136                 else
137                 {
138                     log_error("Invalid entity for focus.");
139                     return CoordsXYZ{};
140                 }
141             }
142         },
143         data);
144 }
145 
146 /**
147  * Viewport will look at sprite or at coordinates as specified in flags 0b_1X
148  * for sprite 0b_0X for coordinates
149  *
150  *  rct2: 0x006EB009
151  *  x:      ax
152  *  y:      eax (top 16)
153  *  width:  bx
154  *  height: ebx (top 16)
155  *  zoom:   cl (8 bits)
156  *  centre_x: edx lower 16 bits
157  *  centre_y: edx upper 16 bits
158  *  centre_z: ecx upper 16 bits
159  *  sprite: edx lower 16 bits
160  *  flags:  edx top most 2 bits 0b_X1 for zoom clear see below for 2nd bit.
161  *  w:      esi
162  */
viewport_create(rct_window * w,const ScreenCoordsXY & screenCoords,int32_t width,int32_t height,const Focus & focus)163 void viewport_create(rct_window* w, const ScreenCoordsXY& screenCoords, int32_t width, int32_t height, const Focus& focus)
164 {
165     rct_viewport* viewport = nullptr;
166     if (_viewports.size() >= MAX_VIEWPORT_COUNT)
167     {
168         log_error("No more viewport slots left to allocate.");
169         return;
170     }
171 
172     auto itViewport = _viewports.insert(_viewports.end(), rct_viewport{});
173 
174     viewport = &*itViewport;
175     viewport->pos = screenCoords;
176     viewport->width = width;
177     viewport->height = height;
178     const auto zoom = focus.zoom;
179 
180     viewport->view_width = width << zoom;
181     viewport->view_height = height << zoom;
182     viewport->zoom = zoom;
183     viewport->flags = 0;
184 
185     if (gConfigGeneral.always_show_gridlines)
186         viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
187     w->viewport = viewport;
188 
189     CoordsXYZ centrePos = focus.GetPos();
190     w->viewport_target_sprite = std::visit(
191         [](auto&& arg) {
192             using T = std::decay_t<decltype(arg)>;
193             if constexpr (std::is_same_v<T, Focus::CoordinateFocus>)
194                 return SPRITE_INDEX_NULL;
195             else if constexpr (std::is_same_v<T, Focus::EntityFocus>)
196                 return arg;
197         },
198         focus.data);
199 
200     auto centreLoc = centre_2d_coordinates(centrePos, viewport);
201     if (!centreLoc.has_value())
202     {
203         log_error("Invalid location for viewport.");
204         return;
205     }
206     w->savedViewPos = *centreLoc;
207     viewport->viewPos = *centreLoc;
208 }
209 
viewport_remove(rct_viewport * viewport)210 void viewport_remove(rct_viewport* viewport)
211 {
212     auto it = std::find_if(_viewports.begin(), _viewports.end(), [viewport](const auto& vp) { return &vp == viewport; });
213     if (it == _viewports.end())
214     {
215         log_error("Unable to remove viewport: %p", viewport);
216         return;
217     }
218     _viewports.erase(it);
219 }
220 
viewports_invalidate(const ScreenRect & screenRect,int32_t maxZoom)221 void viewports_invalidate(const ScreenRect& screenRect, int32_t maxZoom)
222 {
223     for (auto& vp : _viewports)
224     {
225         if (maxZoom == -1 || vp.zoom <= maxZoom)
226         {
227             viewport_invalidate(&vp, screenRect);
228         }
229     }
230 }
231 
232 /**
233  *
234  *  rct2: 0x00689174
235  * edx is assumed to be (and always is) the current rotation, so it is not
236  * needed as parameter.
237  */
viewport_adjust_for_map_height(const ScreenCoordsXY & startCoords)238 CoordsXYZ viewport_adjust_for_map_height(const ScreenCoordsXY& startCoords)
239 {
240     int32_t height = 0;
241 
242     uint32_t rotation = get_current_rotation();
243     CoordsXY pos{};
244     for (int32_t i = 0; i < 6; i++)
245     {
246         pos = viewport_coord_to_map_coord(startCoords, height);
247         height = tile_element_height(pos);
248 
249         // HACK: This is to prevent the x and y values being set to values outside
250         // of the map. This can happen when the height is larger than the map size.
251         auto max = GetMapSizeMinus2();
252         if (pos.x > max && pos.y > max)
253         {
254             static constexpr CoordsXY corr[] = {
255                 { -1, -1 },
256                 { 1, -1 },
257                 { 1, 1 },
258                 { -1, 1 },
259             };
260             pos.x += corr[rotation].x * height;
261             pos.y += corr[rotation].y * height;
262         }
263     }
264 
265     return { pos, height };
266 }
267 
268 /*
269  *  rct2: 0x006E7FF3
270  */
viewport_redraw_after_shift(rct_drawpixelinfo * dpi,rct_window * window,rct_viewport * viewport,const ScreenCoordsXY & coords)271 static void viewport_redraw_after_shift(
272     rct_drawpixelinfo* dpi, rct_window* window, rct_viewport* viewport, const ScreenCoordsXY& coords)
273 {
274     // sub-divide by intersecting windows
275     if (window != nullptr)
276     {
277         // skip current window and non-intersecting windows
278         if (viewport == window->viewport || viewport->pos.x + viewport->width <= window->windowPos.x
279             || viewport->pos.x >= window->windowPos.x + window->width
280             || viewport->pos.y + viewport->height <= window->windowPos.y
281             || viewport->pos.y >= window->windowPos.y + window->height)
282         {
283             auto itWindowPos = window_get_iterator(window);
284             auto itNextWindow = itWindowPos != g_window_list.end() ? std::next(itWindowPos) : g_window_list.end();
285             viewport_redraw_after_shift(
286                 dpi, itNextWindow == g_window_list.end() ? nullptr : itNextWindow->get(), viewport, coords);
287             return;
288         }
289 
290         // save viewport
291         rct_viewport view_copy = *viewport;
292 
293         if (viewport->pos.x < window->windowPos.x)
294         {
295             viewport->width = window->windowPos.x - viewport->pos.x;
296             viewport->view_width = viewport->width * viewport->zoom;
297             viewport_redraw_after_shift(dpi, window, viewport, coords);
298 
299             viewport->pos.x += viewport->width;
300             viewport->viewPos.x += viewport->width * viewport->zoom;
301             viewport->width = view_copy.width - viewport->width;
302             viewport->view_width = viewport->width * viewport->zoom;
303             viewport_redraw_after_shift(dpi, window, viewport, coords);
304         }
305         else if (viewport->pos.x + viewport->width > window->windowPos.x + window->width)
306         {
307             viewport->width = window->windowPos.x + window->width - viewport->pos.x;
308             viewport->view_width = viewport->width * viewport->zoom;
309             viewport_redraw_after_shift(dpi, window, viewport, coords);
310 
311             viewport->pos.x += viewport->width;
312             viewport->viewPos.x += viewport->width * viewport->zoom;
313             viewport->width = view_copy.width - viewport->width;
314             viewport->view_width = viewport->width * viewport->zoom;
315             viewport_redraw_after_shift(dpi, window, viewport, coords);
316         }
317         else if (viewport->pos.y < window->windowPos.y)
318         {
319             viewport->height = window->windowPos.y - viewport->pos.y;
320             viewport->view_width = viewport->width * viewport->zoom;
321             viewport_redraw_after_shift(dpi, window, viewport, coords);
322 
323             viewport->pos.y += viewport->height;
324             viewport->viewPos.y += viewport->height * viewport->zoom;
325             viewport->height = view_copy.height - viewport->height;
326             viewport->view_width = viewport->width * viewport->zoom;
327             viewport_redraw_after_shift(dpi, window, viewport, coords);
328         }
329         else if (viewport->pos.y + viewport->height > window->windowPos.y + window->height)
330         {
331             viewport->height = window->windowPos.y + window->height - viewport->pos.y;
332             viewport->view_width = viewport->width * viewport->zoom;
333             viewport_redraw_after_shift(dpi, window, viewport, coords);
334 
335             viewport->pos.y += viewport->height;
336             viewport->viewPos.y += viewport->height * viewport->zoom;
337             viewport->height = view_copy.height - viewport->height;
338             viewport->view_width = viewport->width * viewport->zoom;
339             viewport_redraw_after_shift(dpi, window, viewport, coords);
340         }
341 
342         // restore viewport
343         *viewport = view_copy;
344     }
345     else
346     {
347         auto left = viewport->pos.x;
348         auto right = viewport->pos.x + viewport->width;
349         auto top = viewport->pos.y;
350         auto bottom = viewport->pos.y + viewport->height;
351 
352         // if moved more than the viewport size
353         if (abs(coords.x) < viewport->width && abs(coords.y) < viewport->height)
354         {
355             // update whole block ?
356             drawing_engine_copy_rect(viewport->pos.x, viewport->pos.y, viewport->width, viewport->height, coords.x, coords.y);
357 
358             if (coords.x > 0)
359             {
360                 // draw left
361                 auto _right = viewport->pos.x + coords.x;
362                 window_draw_all(dpi, left, top, _right, bottom);
363                 left += coords.x;
364             }
365             else if (coords.x < 0)
366             {
367                 // draw right
368                 auto _left = viewport->pos.x + viewport->width + coords.x;
369                 window_draw_all(dpi, _left, top, right, bottom);
370                 right += coords.x;
371             }
372 
373             if (coords.y > 0)
374             {
375                 // draw top
376                 bottom = viewport->pos.y + coords.y;
377                 window_draw_all(dpi, left, top, right, bottom);
378             }
379             else if (coords.y < 0)
380             {
381                 // draw bottom
382                 top = viewport->pos.y + viewport->height + coords.y;
383                 window_draw_all(dpi, left, top, right, bottom);
384             }
385         }
386         else
387         {
388             // redraw whole viewport
389             window_draw_all(dpi, left, top, right, bottom);
390         }
391     }
392 }
393 
viewport_shift_pixels(rct_drawpixelinfo * dpi,rct_window * window,rct_viewport * viewport,int32_t x_diff,int32_t y_diff)394 static void viewport_shift_pixels(
395     rct_drawpixelinfo* dpi, rct_window* window, rct_viewport* viewport, int32_t x_diff, int32_t y_diff)
396 {
397     auto it = window_get_iterator(window);
398     for (; it != g_window_list.end(); it++)
399     {
400         auto w = it->get();
401         if (!(w->flags & WF_TRANSPARENT))
402             continue;
403         if (w->viewport == viewport)
404             continue;
405 
406         if (viewport->pos.x + viewport->width <= w->windowPos.x)
407             continue;
408         if (w->windowPos.x + w->width <= viewport->pos.x)
409             continue;
410 
411         if (viewport->pos.y + viewport->height <= w->windowPos.y)
412             continue;
413         if (w->windowPos.y + w->height <= viewport->pos.y)
414             continue;
415 
416         auto left = w->windowPos.x;
417         auto right = w->windowPos.x + w->width;
418         auto top = w->windowPos.y;
419         auto bottom = w->windowPos.y + w->height;
420 
421         if (left < viewport->pos.x)
422             left = viewport->pos.x;
423         if (right > viewport->pos.x + viewport->width)
424             right = viewport->pos.x + viewport->width;
425 
426         if (top < viewport->pos.y)
427             top = viewport->pos.y;
428         if (bottom > viewport->pos.y + viewport->height)
429             bottom = viewport->pos.y + viewport->height;
430 
431         if (left >= right)
432             continue;
433         if (top >= bottom)
434             continue;
435 
436         window_draw_all(dpi, left, top, right, bottom);
437     }
438 
439     viewport_redraw_after_shift(dpi, window, viewport, { x_diff, y_diff });
440 }
441 
viewport_move(const ScreenCoordsXY & coords,rct_window * w,rct_viewport * viewport)442 static void viewport_move(const ScreenCoordsXY& coords, rct_window* w, rct_viewport* viewport)
443 {
444     auto zoom = viewport->zoom;
445 
446     // Note: do not do the subtraction and then divide!
447     // Note: Due to arithmetic shift != /zoom a shift will have to be used
448     // hopefully when 0x006E7FF3 is finished this can be converted to /zoom.
449     auto x_diff = (viewport->viewPos.x / viewport->zoom) - (coords.x / viewport->zoom);
450     auto y_diff = (viewport->viewPos.y / viewport->zoom) - (coords.y / viewport->zoom);
451 
452     viewport->viewPos = coords;
453 
454     // If no change in viewing area
455     if ((!x_diff) && (!y_diff))
456         return;
457 
458     if (w->flags & WF_7)
459     {
460         int32_t left = std::max<int32_t>(viewport->pos.x, 0);
461         int32_t top = std::max<int32_t>(viewport->pos.y, 0);
462         int32_t right = std::min<int32_t>(viewport->pos.x + viewport->width, context_get_width());
463         int32_t bottom = std::min<int32_t>(viewport->pos.y + viewport->height, context_get_height());
464 
465         if (left >= right)
466             return;
467         if (top >= bottom)
468             return;
469 
470         if (drawing_engine_has_dirty_optimisations())
471         {
472             rct_drawpixelinfo* dpi = drawing_engine_get_dpi();
473             window_draw_all(dpi, left, top, right, bottom);
474             return;
475         }
476     }
477 
478     rct_viewport view_copy = *viewport;
479 
480     if (viewport->pos.x < 0)
481     {
482         viewport->width += viewport->pos.x;
483         viewport->view_width += viewport->pos.x * zoom;
484         viewport->viewPos.x -= viewport->pos.x * zoom;
485         viewport->pos.x = 0;
486     }
487 
488     int32_t eax = viewport->pos.x + viewport->width - context_get_width();
489     if (eax > 0)
490     {
491         viewport->width -= eax;
492         viewport->view_width -= eax * zoom;
493     }
494 
495     if (viewport->width <= 0)
496     {
497         *viewport = view_copy;
498         return;
499     }
500 
501     if (viewport->pos.y < 0)
502     {
503         viewport->height += viewport->pos.y;
504         viewport->view_height += viewport->pos.y * zoom;
505         viewport->viewPos.y -= viewport->pos.y * zoom;
506         viewport->pos.y = 0;
507     }
508 
509     eax = viewport->pos.y + viewport->height - context_get_height();
510     if (eax > 0)
511     {
512         viewport->height -= eax;
513         viewport->view_height -= eax * zoom;
514     }
515 
516     if (viewport->height <= 0)
517     {
518         *viewport = view_copy;
519         return;
520     }
521 
522     if (drawing_engine_has_dirty_optimisations())
523     {
524         rct_drawpixelinfo* dpi = drawing_engine_get_dpi();
525         viewport_shift_pixels(dpi, w, viewport, x_diff, y_diff);
526     }
527 
528     *viewport = view_copy;
529 }
530 
531 // rct2: 0x006E7A15
viewport_set_underground_flag(int32_t underground,rct_window * window,rct_viewport * viewport)532 static void viewport_set_underground_flag(int32_t underground, rct_window* window, rct_viewport* viewport)
533 {
534     if (window->classification != WC_MAIN_WINDOW
535         || (window->classification == WC_MAIN_WINDOW && window->viewport_smart_follow_sprite != SPRITE_INDEX_NULL))
536     {
537         if (!underground)
538         {
539             int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
540             viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE;
541             if (!bit)
542                 return;
543         }
544         else
545         {
546             int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
547             viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
548             if (bit)
549                 return;
550         }
551         window->Invalidate();
552     }
553 }
554 
555 /**
556  *
557  *  rct2: 0x006E7A3A
558  */
viewport_update_position(rct_window * window)559 void viewport_update_position(rct_window* window)
560 {
561     window_event_resize_call(window);
562 
563     rct_viewport* viewport = window->viewport;
564     if (viewport == nullptr)
565         return;
566 
567     if (window->viewport_smart_follow_sprite != SPRITE_INDEX_NULL)
568     {
569         viewport_update_smart_sprite_follow(window);
570     }
571 
572     if (window->viewport_target_sprite != SPRITE_INDEX_NULL)
573     {
574         viewport_update_sprite_follow(window);
575         return;
576     }
577 
578     viewport_set_underground_flag(0, window, viewport);
579 
580     auto viewportMidPoint = ScreenCoordsXY{ window->savedViewPos.x + viewport->view_width / 2,
581                                             window->savedViewPos.y + viewport->view_height / 2 };
582 
583     auto mapCoord = viewport_coord_to_map_coord(viewportMidPoint, 0);
584 
585     // Clamp to the map minimum value
586     int32_t at_map_edge = 0;
587     if (mapCoord.x < MAP_MINIMUM_X_Y)
588     {
589         mapCoord.x = MAP_MINIMUM_X_Y;
590         at_map_edge = 1;
591     }
592     if (mapCoord.y < MAP_MINIMUM_X_Y)
593     {
594         mapCoord.y = MAP_MINIMUM_X_Y;
595         at_map_edge = 1;
596     }
597 
598     // Clamp to the map maximum value (scenario specific)
599     if (mapCoord.x > GetMapSizeMinus2())
600     {
601         mapCoord.x = GetMapSizeMinus2();
602         at_map_edge = 1;
603     }
604     if (mapCoord.y > GetMapSizeMinus2())
605     {
606         mapCoord.y = GetMapSizeMinus2();
607         at_map_edge = 1;
608     }
609 
610     if (at_map_edge)
611     {
612         auto centreLoc = centre_2d_coordinates({ mapCoord, 0 }, viewport);
613         if (centreLoc.has_value())
614         {
615             window->savedViewPos = centreLoc.value();
616         }
617     }
618 
619     auto windowCoords = window->savedViewPos;
620     if (window->flags & WF_SCROLLING_TO_LOCATION)
621     {
622         // Moves the viewport if focusing in on an item
623         uint8_t flags = 0;
624         windowCoords.x -= viewport->viewPos.x;
625         if (windowCoords.x < 0)
626         {
627             windowCoords.x = -windowCoords.x;
628             flags |= 1;
629         }
630         windowCoords.y -= viewport->viewPos.y;
631         if (windowCoords.y < 0)
632         {
633             windowCoords.y = -windowCoords.y;
634             flags |= 2;
635         }
636         windowCoords.x = (windowCoords.x + 7) / 8;
637         windowCoords.y = (windowCoords.y + 7) / 8;
638 
639         // If we are at the final zoom position
640         if (!windowCoords.x && !windowCoords.y)
641         {
642             window->flags &= ~WF_SCROLLING_TO_LOCATION;
643         }
644         if (flags & 1)
645         {
646             windowCoords.x = -windowCoords.x;
647         }
648         if (flags & 2)
649         {
650             windowCoords.y = -windowCoords.y;
651         }
652         windowCoords.x += viewport->viewPos.x;
653         windowCoords.y += viewport->viewPos.y;
654     }
655 
656     viewport_move(windowCoords, window, viewport);
657 }
658 
viewport_update_sprite_follow(rct_window * window)659 void viewport_update_sprite_follow(rct_window* window)
660 {
661     if (window->viewport_target_sprite != SPRITE_INDEX_NULL && window->viewport != nullptr)
662     {
663         auto* sprite = GetEntity(window->viewport_target_sprite);
664         if (sprite == nullptr)
665         {
666             return;
667         }
668         int32_t height = (tile_element_height({ sprite->x, sprite->y })) - 16;
669         int32_t underground = sprite->z < height;
670 
671         viewport_set_underground_flag(underground, window, window->viewport);
672 
673         auto centreLoc = centre_2d_coordinates(sprite->GetLocation(), window->viewport);
674         if (centreLoc.has_value())
675         {
676             window->savedViewPos = *centreLoc;
677             viewport_move(*centreLoc, window, window->viewport);
678         }
679     }
680 }
681 
viewport_update_smart_sprite_follow(rct_window * window)682 void viewport_update_smart_sprite_follow(rct_window* window)
683 {
684     auto entity = TryGetEntity(window->viewport_smart_follow_sprite);
685     if (entity == nullptr || entity->Type == EntityType::Null)
686     {
687         window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
688         window->viewport_target_sprite = SPRITE_INDEX_NULL;
689         return;
690     }
691 
692     switch (entity->Type)
693     {
694         case EntityType::Vehicle:
695             viewport_update_smart_vehicle_follow(window);
696             break;
697 
698         case EntityType::Guest:
699             viewport_update_smart_guest_follow(window, entity->As<Guest>());
700             break;
701 
702         case EntityType::Staff:
703             viewport_update_smart_staff_follow(window, entity->As<Staff>());
704             break;
705 
706         default: // All other types don't need any "smart" following; steam particle, duck, money effect, etc.
707             window->focus = Focus(window->viewport_smart_follow_sprite);
708             window->viewport_target_sprite = window->viewport_smart_follow_sprite;
709             break;
710     }
711 }
712 
viewport_update_smart_guest_follow(rct_window * window,const Guest * peep)713 void viewport_update_smart_guest_follow(rct_window* window, const Guest* peep)
714 {
715     Focus focus = Focus(peep->sprite_index);
716     window->viewport_target_sprite = peep->sprite_index;
717 
718     if (peep->State == PeepState::Picked)
719     {
720         window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
721         window->viewport_target_sprite = SPRITE_INDEX_NULL;
722         window->focus = std::nullopt; // No focus
723         return;
724     }
725 
726     bool overallFocus = true;
727     if (peep->State == PeepState::OnRide || peep->State == PeepState::EnteringRide
728         || (peep->State == PeepState::LeavingRide && peep->x == LOCATION_NULL))
729     {
730         auto ride = get_ride(peep->CurrentRide);
731         if (ride != nullptr && (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
732         {
733             auto train = GetEntity<Vehicle>(ride->vehicles[peep->CurrentTrain]);
734             if (train != nullptr)
735             {
736                 const auto car = train->GetCar(peep->CurrentCar);
737                 if (car != nullptr)
738                 {
739                     focus = Focus(car->sprite_index);
740                     overallFocus = false;
741                     window->viewport_target_sprite = car->sprite_index;
742                 }
743             }
744         }
745     }
746 
747     if (peep->x == LOCATION_NULL && overallFocus)
748     {
749         auto ride = get_ride(peep->CurrentRide);
750         if (ride != nullptr)
751         {
752             auto xy = ride->overall_view.ToTileCentre();
753             CoordsXYZ coordFocus;
754             coordFocus.x = xy.x;
755             coordFocus.y = xy.y;
756             coordFocus.z = tile_element_height(xy) + (4 * COORDS_Z_STEP);
757             focus = Focus(coordFocus);
758             window->viewport_target_sprite = SPRITE_INDEX_NULL;
759         }
760     }
761 
762     window->focus = focus;
763 }
764 
viewport_update_smart_staff_follow(rct_window * window,const Staff * peep)765 void viewport_update_smart_staff_follow(rct_window* window, const Staff* peep)
766 {
767     if (peep->State == PeepState::Picked)
768     {
769         window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
770         window->viewport_target_sprite = SPRITE_INDEX_NULL;
771         window->focus = std::nullopt;
772         return;
773     }
774 
775     window->focus = Focus(window->viewport_smart_follow_sprite);
776     window->viewport_target_sprite = window->viewport_smart_follow_sprite;
777 }
778 
viewport_update_smart_vehicle_follow(rct_window * window)779 void viewport_update_smart_vehicle_follow(rct_window* window)
780 {
781     window->focus = Focus(window->viewport_smart_follow_sprite);
782     window->viewport_target_sprite = window->viewport_smart_follow_sprite;
783 }
784 
785 /**
786  *
787  *  rct2: 0x00685C02
788  *  ax: left
789  *  bx: top
790  *  dx: right
791  *  esi: viewport
792  *  edi: dpi
793  *  ebp: bottom
794  */
viewport_render(rct_drawpixelinfo * dpi,const rct_viewport * viewport,const ScreenRect & screenRect,std::vector<RecordedPaintSession> * sessions)795 void viewport_render(
796     rct_drawpixelinfo* dpi, const rct_viewport* viewport, const ScreenRect& screenRect,
797     std::vector<RecordedPaintSession>* sessions)
798 {
799     auto [topLeft, bottomRight] = screenRect;
800 
801     if (bottomRight.x <= viewport->pos.x)
802         return;
803     if (bottomRight.y <= viewport->pos.y)
804         return;
805     if (topLeft.x >= viewport->pos.x + viewport->width)
806         return;
807     if (topLeft.y >= viewport->pos.y + viewport->height)
808         return;
809 
810 #ifdef DEBUG_SHOW_DIRTY_BOX
811     const auto dirtyBoxTopLeft = topLeft;
812     const auto dirtyBoxTopRight = bottomRight - ScreenCoordsXY{ 1, 1 };
813 #endif
814 
815     topLeft -= viewport->pos;
816     topLeft = ScreenCoordsXY{
817         std::max(topLeft.x, 0) * viewport->zoom,
818         std::max(topLeft.y, 0) * viewport->zoom,
819     } + viewport->viewPos;
820 
821     bottomRight -= viewport->pos;
822     bottomRight = ScreenCoordsXY{
823         std::min(bottomRight.x, viewport->width) * viewport->zoom,
824         std::min(bottomRight.y, viewport->height) * viewport->zoom,
825     } + viewport->viewPos;
826 
827     viewport_paint(viewport, dpi, { topLeft, bottomRight }, sessions);
828 
829 #ifdef DEBUG_SHOW_DIRTY_BOX
830     // FIXME g_viewport_list doesn't exist anymore
831     if (viewport != g_viewport_list)
832         gfx_fill_rect_inset(dpi, { dirtyBoxTopLeft, dirtyBoxTopRight }, 0x2, INSET_RECT_F_30);
833 #endif
834 }
835 
record_session(const paint_session * session,std::vector<RecordedPaintSession> * recorded_sessions,size_t record_index)836 static void record_session(
837     const paint_session* session, std::vector<RecordedPaintSession>* recorded_sessions, size_t record_index)
838 {
839     // Perform a deep copy of the paint session, use relative offsets.
840     // This is done to extract the session for benchmark.
841     // Place the copied session at provided record_index, so the caller can decide which columns/paint sessions to copy;
842     // there is no column information embedded in the session itself.
843     auto& recordedSession = recorded_sessions->at(record_index);
844     recordedSession.Session = *session;
845     recordedSession.Entries.resize(session->PaintEntryChain.GetCount());
846 
847     // Mind the offset needs to be calculated against the original `session`, not `session_copy`
848     std::unordered_map<paint_struct*, paint_struct*> entryRemap;
849 
850     // Copy all entries
851     auto paintIndex = 0;
852     auto chain = session->PaintEntryChain.Head;
853     while (chain != nullptr)
854     {
855         for (size_t i = 0; i < chain->Count; i++)
856         {
857             auto& src = chain->PaintStructs[i];
858             auto& dst = recordedSession.Entries[paintIndex++];
859             dst = src;
860             entryRemap[&src.basic] = reinterpret_cast<paint_struct*>(i * sizeof(paint_entry));
861         }
862         chain = chain->Next;
863     }
864     entryRemap[nullptr] = reinterpret_cast<paint_struct*>(-1);
865 
866     // Remap all entries
867     for (auto& ps : recordedSession.Entries)
868     {
869         auto& ptr = ps.basic.next_quadrant_ps;
870         auto it = entryRemap.find(ptr);
871         if (it == entryRemap.end())
872         {
873             assert(false);
874             ptr = nullptr;
875         }
876         else
877         {
878             ptr = it->second;
879         }
880     }
881     for (auto& ptr : recordedSession.Session.Quadrants)
882     {
883         auto it = entryRemap.find(ptr);
884         if (it == entryRemap.end())
885         {
886             assert(false);
887             ptr = nullptr;
888         }
889         else
890         {
891             ptr = it->second;
892         }
893     }
894 }
895 
viewport_fill_column(paint_session * session,std::vector<RecordedPaintSession> * recorded_sessions,size_t record_index)896 static void viewport_fill_column(
897     paint_session* session, std::vector<RecordedPaintSession>* recorded_sessions, size_t record_index)
898 {
899     PaintSessionGenerate(session);
900     if (recorded_sessions != nullptr)
901     {
902         record_session(session, recorded_sessions, record_index);
903     }
904     PaintSessionArrange(session);
905 }
906 
viewport_paint_column(paint_session * session)907 static void viewport_paint_column(paint_session* session)
908 {
909     if (session->ViewFlags
910             & (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_UNDERGROUND_INSIDE
911                | VIEWPORT_FLAG_CLIP_VIEW)
912         && (~session->ViewFlags & VIEWPORT_FLAG_TRANSPARENT_BACKGROUND))
913     {
914         uint8_t colour = COLOUR_AQUAMARINE;
915         if (session->ViewFlags & VIEWPORT_FLAG_INVISIBLE_SPRITES)
916         {
917             colour = COLOUR_BLACK;
918         }
919         gfx_clear(&session->DPI, colour);
920     }
921 
922     PaintDrawStructs(session);
923 
924     if (gConfigGeneral.render_weather_gloom && !gTrackDesignSaveMode && !(session->ViewFlags & VIEWPORT_FLAG_INVISIBLE_SPRITES)
925         && !(session->ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES))
926     {
927         viewport_paint_weather_gloom(&session->DPI);
928     }
929 
930     if (session->PSStringHead != nullptr)
931     {
932         PaintDrawMoneyStructs(&session->DPI, session->PSStringHead);
933     }
934 }
935 
936 /**
937  *
938  *  rct2: 0x00685CBF
939  *  eax: left
940  *  ebx: top
941  *  edx: right
942  *  esi: viewport
943  *  edi: dpi
944  *  ebp: bottom
945  */
viewport_paint(const rct_viewport * viewport,rct_drawpixelinfo * dpi,const ScreenRect & screenRect,std::vector<RecordedPaintSession> * recorded_sessions)946 void viewport_paint(
947     const rct_viewport* viewport, rct_drawpixelinfo* dpi, const ScreenRect& screenRect,
948     std::vector<RecordedPaintSession>* recorded_sessions)
949 {
950     uint32_t viewFlags = viewport->flags;
951     uint32_t width = screenRect.GetWidth();
952     uint32_t height = screenRect.GetHeight();
953     uint32_t bitmask = viewport->zoom >= 0 ? 0xFFFFFFFF & (0xFFFFFFFF * viewport->zoom) : 0xFFFFFFFF;
954     ScreenCoordsXY topLeft = screenRect.Point1;
955 
956     width &= bitmask;
957     height &= bitmask;
958     topLeft.x &= bitmask;
959     topLeft.y &= bitmask;
960 
961     auto x = topLeft.x - static_cast<int32_t>(viewport->viewPos.x & bitmask);
962     x = x / viewport->zoom;
963     x += viewport->pos.x;
964 
965     auto y = topLeft.y - static_cast<int32_t>(viewport->viewPos.y & bitmask);
966     y = y / viewport->zoom;
967     y += viewport->pos.y;
968 
969     rct_drawpixelinfo dpi1;
970     dpi1.DrawingEngine = dpi->DrawingEngine;
971     dpi1.bits = dpi->bits + (x - dpi->x) + ((y - dpi->y) * (dpi->width + dpi->pitch));
972     dpi1.x = topLeft.x;
973     dpi1.y = topLeft.y;
974     dpi1.width = width;
975     dpi1.height = height;
976     dpi1.pitch = (dpi->width + dpi->pitch) - (width / viewport->zoom);
977     dpi1.zoom_level = viewport->zoom;
978     dpi1.remX = std::max(0, dpi->x - x);
979     dpi1.remY = std::max(0, dpi->y - y);
980 
981     // make sure, the compare operation is done in int32_t to avoid the loop becoming an infiniteloop.
982     // this as well as the [x += 32] in the loop causes signed integer overflow -> undefined behaviour.
983     auto rightBorder = dpi1.x + dpi1.width;
984     auto alignedX = floor2(dpi1.x, 32);
985 
986     _paintColumns.clear();
987 
988     bool useMultithreading = gConfigGeneral.multithreading;
989     if (useMultithreading && _paintJobs == nullptr)
990     {
991         _paintJobs = std::make_unique<JobPool>();
992     }
993     else if (useMultithreading == false && _paintJobs != nullptr)
994     {
995         _paintJobs.reset();
996     }
997 
998     bool useParallelDrawing = false;
999     if (useMultithreading && (dpi->DrawingEngine->GetFlags() & DEF_PARALLEL_DRAWING))
1000     {
1001         useParallelDrawing = true;
1002     }
1003 
1004     // Create space to record sessions and keep track which index is being drawn
1005     size_t index = 0;
1006     if (recorded_sessions != nullptr)
1007     {
1008         auto columnSize = rightBorder - alignedX;
1009         auto columnCount = (columnSize + 31) / 32;
1010         recorded_sessions->resize(columnCount);
1011     }
1012 
1013     // Generate and sort columns.
1014     for (x = alignedX; x < rightBorder; x += 32, index++)
1015     {
1016         paint_session* session = PaintSessionAlloc(&dpi1, viewFlags);
1017         _paintColumns.push_back(session);
1018 
1019         rct_drawpixelinfo& dpi2 = session->DPI;
1020         if (x >= dpi2.x)
1021         {
1022             auto leftPitch = x - dpi2.x;
1023             dpi2.width -= leftPitch;
1024             dpi2.bits += leftPitch / dpi2.zoom_level;
1025             dpi2.pitch += leftPitch / dpi2.zoom_level;
1026             dpi2.x = x;
1027         }
1028 
1029         auto paintRight = dpi2.x + dpi2.width;
1030         if (paintRight >= x + 32)
1031         {
1032             auto rightPitch = paintRight - x - 32;
1033             paintRight -= rightPitch;
1034             dpi2.pitch += rightPitch / dpi2.zoom_level;
1035         }
1036         dpi2.width = paintRight - dpi2.x;
1037 
1038         if (useMultithreading)
1039         {
1040             _paintJobs->AddTask(
1041                 [session, recorded_sessions, index]() -> void { viewport_fill_column(session, recorded_sessions, index); });
1042         }
1043         else
1044         {
1045             viewport_fill_column(session, recorded_sessions, index);
1046         }
1047     }
1048 
1049     if (useMultithreading)
1050     {
1051         _paintJobs->Join();
1052     }
1053 
1054     // Paint columns.
1055     for (auto* session : _paintColumns)
1056     {
1057         if (useParallelDrawing)
1058         {
1059             _paintJobs->AddTask([session]() -> void { viewport_paint_column(session); });
1060         }
1061         else
1062         {
1063             viewport_paint_column(session);
1064         }
1065     }
1066     if (useParallelDrawing)
1067     {
1068         _paintJobs->Join();
1069     }
1070 
1071     // Release resources.
1072     for (auto* session : _paintColumns)
1073     {
1074         PaintSessionFree(session);
1075     }
1076 }
1077 
viewport_paint_weather_gloom(rct_drawpixelinfo * dpi)1078 static void viewport_paint_weather_gloom(rct_drawpixelinfo* dpi)
1079 {
1080     auto paletteId = climate_get_weather_gloom_palette_id(gClimateCurrent);
1081     if (paletteId != FilterPaletteID::PaletteNull)
1082     {
1083         // Only scale width if zoomed in more than 1:1
1084         auto zoomLevel = dpi->zoom_level < 0 ? dpi->zoom_level : 0;
1085         auto x = dpi->x;
1086         auto y = dpi->y;
1087         auto w = (dpi->width / zoomLevel) - 1;
1088         auto h = (dpi->height / zoomLevel) - 1;
1089         gfx_filter_rect(dpi, ScreenRect(x, y, x + w, y + h), paletteId);
1090     }
1091 }
1092 
1093 /**
1094  *
1095  *  rct2: 0x0068958D
1096  */
screen_pos_to_map_pos(const ScreenCoordsXY & screenCoords,int32_t * direction)1097 std::optional<CoordsXY> screen_pos_to_map_pos(const ScreenCoordsXY& screenCoords, int32_t* direction)
1098 {
1099     auto mapCoords = screen_get_map_xy(screenCoords, nullptr);
1100     if (!mapCoords.has_value())
1101         return std::nullopt;
1102 
1103     int32_t my_direction;
1104     int32_t dist_from_centre_x = abs(mapCoords->x % 32);
1105     int32_t dist_from_centre_y = abs(mapCoords->y % 32);
1106     if (dist_from_centre_x > 8 && dist_from_centre_x < 24 && dist_from_centre_y > 8 && dist_from_centre_y < 24)
1107     {
1108         my_direction = 4;
1109     }
1110     else
1111     {
1112         auto mod_x = mapCoords->x & 0x1F;
1113         auto mod_y = mapCoords->y & 0x1F;
1114         if (mod_x <= 16)
1115         {
1116             if (mod_y < 16)
1117             {
1118                 my_direction = 2;
1119             }
1120             else
1121             {
1122                 my_direction = 3;
1123             }
1124         }
1125         else
1126         {
1127             if (mod_y < 16)
1128             {
1129                 my_direction = 1;
1130             }
1131             else
1132             {
1133                 my_direction = 0;
1134             }
1135         }
1136     }
1137 
1138     if (direction != nullptr)
1139         *direction = my_direction;
1140     return { mapCoords->ToTileStart() };
1141 }
1142 
ScreenToViewportCoord(const ScreenCoordsXY & screenCoords) const1143 [[nodiscard]] ScreenCoordsXY rct_viewport::ScreenToViewportCoord(const ScreenCoordsXY& screenCoords) const
1144 {
1145     ScreenCoordsXY ret;
1146     ret.x = ((screenCoords.x - pos.x) * zoom) + viewPos.x;
1147     ret.y = ((screenCoords.y - pos.y) * zoom) + viewPos.y;
1148     return ret;
1149 }
1150 
Invalidate() const1151 void rct_viewport::Invalidate() const
1152 {
1153     viewport_invalidate(this, { viewPos, viewPos + ScreenCoordsXY{ view_width, view_height } });
1154 }
1155 
viewport_coord_to_map_coord(const ScreenCoordsXY & coords,int32_t z)1156 CoordsXY viewport_coord_to_map_coord(const ScreenCoordsXY& coords, int32_t z)
1157 {
1158     // Reverse of translate_3d_to_2d_with_z
1159     CoordsXY ret = { coords.y - coords.x / 2 + z, coords.y + coords.x / 2 + z };
1160     auto inverseRotation = DirectionFlipXAxis(get_current_rotation());
1161     return ret.Rotate(inverseRotation);
1162 }
1163 
1164 /**
1165  *
1166  *  rct2: 0x00664689
1167  */
show_gridlines()1168 void show_gridlines()
1169 {
1170     if (gShowGridLinesRefCount == 0)
1171     {
1172         rct_window* mainWindow = window_get_main();
1173         if (mainWindow != nullptr)
1174         {
1175             if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_GRIDLINES))
1176             {
1177                 mainWindow->viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
1178                 mainWindow->Invalidate();
1179             }
1180         }
1181     }
1182     gShowGridLinesRefCount++;
1183 }
1184 
1185 /**
1186  *
1187  *  rct2: 0x006646B4
1188  */
hide_gridlines()1189 void hide_gridlines()
1190 {
1191     gShowGridLinesRefCount--;
1192     if (gShowGridLinesRefCount == 0)
1193     {
1194         rct_window* mainWindow = window_get_main();
1195         if (mainWindow != nullptr)
1196         {
1197             if (!gConfigGeneral.always_show_gridlines)
1198             {
1199                 mainWindow->viewport->flags &= ~VIEWPORT_FLAG_GRIDLINES;
1200                 mainWindow->Invalidate();
1201             }
1202         }
1203     }
1204 }
1205 
1206 /**
1207  *
1208  *  rct2: 0x00664E8E
1209  */
show_land_rights()1210 void show_land_rights()
1211 {
1212     if (gShowLandRightsRefCount == 0)
1213     {
1214         rct_window* mainWindow = window_get_main();
1215         if (mainWindow != nullptr)
1216         {
1217             if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP))
1218             {
1219                 mainWindow->viewport->flags |= VIEWPORT_FLAG_LAND_OWNERSHIP;
1220                 mainWindow->Invalidate();
1221             }
1222         }
1223     }
1224     gShowLandRightsRefCount++;
1225 }
1226 
1227 /**
1228  *
1229  *  rct2: 0x00664EB9
1230  */
hide_land_rights()1231 void hide_land_rights()
1232 {
1233     gShowLandRightsRefCount--;
1234     if (gShowLandRightsRefCount == 0)
1235     {
1236         rct_window* mainWindow = window_get_main();
1237         if (mainWindow != nullptr)
1238         {
1239             if (mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP)
1240             {
1241                 mainWindow->viewport->flags &= ~VIEWPORT_FLAG_LAND_OWNERSHIP;
1242                 mainWindow->Invalidate();
1243             }
1244         }
1245     }
1246 }
1247 
1248 /**
1249  *
1250  *  rct2: 0x00664EDD
1251  */
show_construction_rights()1252 void show_construction_rights()
1253 {
1254     if (gShowConstuctionRightsRefCount == 0)
1255     {
1256         rct_window* mainWindow = window_get_main();
1257         if (mainWindow != nullptr)
1258         {
1259             if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS))
1260             {
1261                 mainWindow->viewport->flags |= VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
1262                 mainWindow->Invalidate();
1263             }
1264         }
1265     }
1266     gShowConstuctionRightsRefCount++;
1267 }
1268 
1269 /**
1270  *
1271  *  rct2: 0x00664F08
1272  */
hide_construction_rights()1273 void hide_construction_rights()
1274 {
1275     gShowConstuctionRightsRefCount--;
1276     if (gShowConstuctionRightsRefCount == 0)
1277     {
1278         rct_window* mainWindow = window_get_main();
1279         if (mainWindow != nullptr)
1280         {
1281             if (mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS)
1282             {
1283                 mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
1284                 mainWindow->Invalidate();
1285             }
1286         }
1287     }
1288 }
1289 
1290 /**
1291  *
1292  *  rct2: 0x006CB70A
1293  */
viewport_set_visibility(uint8_t mode)1294 void viewport_set_visibility(uint8_t mode)
1295 {
1296     rct_window* window = window_get_main();
1297 
1298     if (window != nullptr)
1299     {
1300         rct_viewport* vp = window->viewport;
1301         uint32_t invalidate = 0;
1302 
1303         switch (mode)
1304         {
1305             case 0:
1306             { // Set all these flags to 0, and invalidate if any were active
1307                 uint32_t mask = VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_SEETHROUGH_RIDES
1308                     | VIEWPORT_FLAG_SEETHROUGH_SCENERY | VIEWPORT_FLAG_SEETHROUGH_PATHS | VIEWPORT_FLAG_INVISIBLE_SUPPORTS
1309                     | VIEWPORT_FLAG_LAND_HEIGHTS | VIEWPORT_FLAG_TRACK_HEIGHTS | VIEWPORT_FLAG_PATH_HEIGHTS
1310                     | VIEWPORT_FLAG_INVISIBLE_PEEPS | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL;
1311 
1312                 invalidate += vp->flags & mask;
1313                 vp->flags &= ~mask;
1314                 break;
1315             }
1316             case 1: // 6CB79D
1317             case 4: // 6CB7C4
1318                 // Set underground on, invalidate if it was off
1319                 invalidate += !(vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE);
1320                 vp->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
1321                 break;
1322             case 2: // 6CB7EB
1323                 // Set track heights on, invalidate if off
1324                 invalidate += !(vp->flags & VIEWPORT_FLAG_TRACK_HEIGHTS);
1325                 vp->flags |= VIEWPORT_FLAG_TRACK_HEIGHTS;
1326                 break;
1327             case 3: // 6CB7B1
1328             case 5: // 6CB7D8
1329                 // Set underground off, invalidate if it was on
1330                 invalidate += vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
1331                 vp->flags &= ~(static_cast<uint16_t>(VIEWPORT_FLAG_UNDERGROUND_INSIDE));
1332                 break;
1333         }
1334         if (invalidate != 0)
1335             window->Invalidate();
1336     }
1337 }
1338 
1339 /**
1340  * Checks if a paint_struct sprite type is in the filter mask.
1341  */
PSSpriteTypeIsInFilter(paint_struct * ps,uint16_t filter)1342 static bool PSSpriteTypeIsInFilter(paint_struct* ps, uint16_t filter)
1343 {
1344     if (ps->sprite_type != ViewportInteractionItem::None && ps->sprite_type != ViewportInteractionItem::Label
1345         && ps->sprite_type <= ViewportInteractionItem::Banner)
1346     {
1347         auto mask = EnumToFlag(ps->sprite_type);
1348         if (filter & mask)
1349         {
1350             return true;
1351         }
1352     }
1353     return false;
1354 }
1355 
1356 /**
1357  * rct2: 0x00679236, 0x00679662, 0x00679B0D, 0x00679FF1
1358  */
is_pixel_present_bmp(uint32_t imageType,const rct_g1_element * g1,const uint8_t * index,const PaletteMap & paletteMap)1359 static bool is_pixel_present_bmp(
1360     uint32_t imageType, const rct_g1_element* g1, const uint8_t* index, const PaletteMap& paletteMap)
1361 {
1362     // Probably used to check for corruption
1363     if (!(g1->flags & G1_FLAG_BMP))
1364     {
1365         return false;
1366     }
1367 
1368     if (imageType & IMAGE_TYPE_REMAP)
1369     {
1370         return paletteMap[*index] != 0;
1371     }
1372 
1373     if (imageType & IMAGE_TYPE_TRANSPARENT)
1374     {
1375         return false;
1376     }
1377 
1378     return (*index != 0);
1379 }
1380 
1381 /**
1382  * rct2: 0x0067933B, 0x00679788, 0x00679C4A, 0x0067A117
1383  */
is_pixel_present_rle(const uint8_t * esi,int32_t x_start_point,int32_t y_start_point,int32_t round)1384 static bool is_pixel_present_rle(const uint8_t* esi, int32_t x_start_point, int32_t y_start_point, int32_t round)
1385 {
1386     uint32_t start_offset = esi[y_start_point * 2] | (esi[y_start_point * 2 + 1] << 8);
1387     const uint8_t* ebx = esi + start_offset;
1388 
1389     uint8_t last_data_line = 0;
1390     while (!last_data_line)
1391     {
1392         int32_t no_pixels = *ebx++;
1393         uint8_t gap_size = *ebx++;
1394 
1395         last_data_line = no_pixels & 0x80;
1396 
1397         no_pixels &= 0x7F;
1398 
1399         ebx += no_pixels;
1400 
1401         if (round > 1)
1402         {
1403             if (gap_size % 2)
1404             {
1405                 gap_size++;
1406                 no_pixels--;
1407                 if (no_pixels == 0)
1408                 {
1409                     continue;
1410                 }
1411             }
1412         }
1413 
1414         if (round == 4)
1415         {
1416             if (gap_size % 4)
1417             {
1418                 gap_size += 2;
1419                 no_pixels -= 2;
1420                 if (no_pixels <= 0)
1421                 {
1422                     continue;
1423                 }
1424             }
1425         }
1426 
1427         int32_t x_start = gap_size - x_start_point;
1428         if (x_start <= 0)
1429         {
1430             no_pixels += x_start;
1431             if (no_pixels <= 0)
1432             {
1433                 continue;
1434             }
1435 
1436             x_start = 0;
1437         }
1438         else
1439         {
1440             // Do nothing?
1441         }
1442 
1443         x_start += no_pixels;
1444         x_start--;
1445         if (x_start > 0)
1446         {
1447             no_pixels -= x_start;
1448             if (no_pixels <= 0)
1449             {
1450                 continue;
1451             }
1452         }
1453 
1454         if (round > 1)
1455         {
1456             // This matches the original implementation, but allows empty lines to cause false positives on zoom 0
1457             if (ceil2(no_pixels, round) == 0)
1458                 continue;
1459         }
1460 
1461         return true;
1462     }
1463 
1464     return false;
1465 }
1466 
1467 /**
1468  * rct2: 0x00679074
1469  *
1470  * @param dpi (edi)
1471  * @param imageId (ebx)
1472  * @param x (cx)
1473  * @param y (dx)
1474  * @return value originally stored in 0x00141F569
1475  */
is_sprite_interacted_with_palette_set(rct_drawpixelinfo * dpi,int32_t imageId,const ScreenCoordsXY & coords,const PaletteMap & paletteMap)1476 static bool is_sprite_interacted_with_palette_set(
1477     rct_drawpixelinfo* dpi, int32_t imageId, const ScreenCoordsXY& coords, const PaletteMap& paletteMap)
1478 {
1479     const rct_g1_element* g1 = gfx_get_g1_element(imageId & 0x7FFFF);
1480     if (g1 == nullptr)
1481     {
1482         return false;
1483     }
1484 
1485     if (dpi->zoom_level > 0)
1486     {
1487         if (g1->flags & G1_FLAG_NO_ZOOM_DRAW)
1488         {
1489             return false;
1490         }
1491 
1492         if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE)
1493         {
1494             // TODO: SAR in dpi done with `>> 1`, in coordinates with `/ 2`
1495             rct_drawpixelinfo zoomed_dpi = {
1496                 /* .bits = */ dpi->bits,
1497                 /* .x = */ dpi->x >> 1,
1498                 /* .y = */ dpi->y >> 1,
1499                 /* .height = */ dpi->height,
1500                 /* .width = */ dpi->width,
1501                 /* .pitch = */ dpi->pitch,
1502                 /* .zoom_level = */ dpi->zoom_level - 1,
1503             };
1504 
1505             return is_sprite_interacted_with_palette_set(
1506                 &zoomed_dpi, imageId - g1->zoomed_offset, { coords.x / 2, coords.y / 2 }, paletteMap);
1507         }
1508     }
1509 
1510     int32_t round = std::max(1, 1 * dpi->zoom_level);
1511 
1512     auto origin = coords;
1513     if (g1->flags & G1_FLAG_RLE_COMPRESSION)
1514     {
1515         origin.y -= (round - 1);
1516     }
1517 
1518     origin.y += g1->y_offset;
1519     int32_t yStartPoint = 0;
1520     int32_t height = g1->height;
1521     if (dpi->zoom_level != 0)
1522     {
1523         if (height % 2)
1524         {
1525             height--;
1526             yStartPoint++;
1527         }
1528 
1529         if (dpi->zoom_level == 2)
1530         {
1531             if (height % 4)
1532             {
1533                 height -= 2;
1534                 yStartPoint += 2;
1535             }
1536         }
1537 
1538         if (height == 0)
1539         {
1540             return false;
1541         }
1542     }
1543 
1544     origin.y = floor2(origin.y, round);
1545     int32_t yEndPoint = height;
1546     origin.y -= dpi->y;
1547     if (origin.y < 0)
1548     {
1549         yEndPoint += origin.y;
1550         if (yEndPoint <= 0)
1551         {
1552             return false;
1553         }
1554 
1555         yStartPoint -= origin.y;
1556         origin.y = 0;
1557     }
1558 
1559     origin.y += yEndPoint;
1560     origin.y--;
1561     if (origin.y > 0)
1562     {
1563         yEndPoint -= origin.y;
1564         if (yEndPoint <= 0)
1565         {
1566             return false;
1567         }
1568     }
1569 
1570     int32_t xStartPoint = 0;
1571     int32_t xEndPoint = g1->width;
1572 
1573     origin.x += g1->x_offset;
1574     origin.x = floor2(origin.x, round);
1575     origin.x -= dpi->x;
1576     if (origin.x < 0)
1577     {
1578         xEndPoint += origin.x;
1579         if (xEndPoint <= 0)
1580         {
1581             return false;
1582         }
1583 
1584         xStartPoint -= origin.x;
1585         origin.x = 0;
1586     }
1587 
1588     origin.x += xEndPoint;
1589     origin.x--;
1590     if (origin.x > 0)
1591     {
1592         xEndPoint -= origin.x;
1593         if (xEndPoint <= 0)
1594         {
1595             return false;
1596         }
1597     }
1598 
1599     if (g1->flags & G1_FLAG_RLE_COMPRESSION)
1600     {
1601         return is_pixel_present_rle(g1->offset, xStartPoint, yStartPoint, round);
1602     }
1603 
1604     uint8_t* offset = g1->offset + (yStartPoint * g1->width) + xStartPoint;
1605     uint32_t imageType = _currentImageType;
1606 
1607     if (!(g1->flags & G1_FLAG_1))
1608     {
1609         return is_pixel_present_bmp(imageType, g1, offset, paletteMap);
1610     }
1611 
1612     Guard::Assert(false, "Invalid image type encountered.");
1613     return false;
1614 }
1615 
1616 /**
1617  *
1618  *  rct2: 0x00679023
1619  */
1620 
is_sprite_interacted_with(rct_drawpixelinfo * dpi,int32_t imageId,const ScreenCoordsXY & coords)1621 static bool is_sprite_interacted_with(rct_drawpixelinfo* dpi, int32_t imageId, const ScreenCoordsXY& coords)
1622 {
1623     auto paletteMap = PaletteMap::GetDefault();
1624     imageId &= ~IMAGE_TYPE_TRANSPARENT;
1625     if (imageId & IMAGE_TYPE_REMAP)
1626     {
1627         _currentImageType = IMAGE_TYPE_REMAP;
1628         int32_t index = (imageId >> 19) & 0x7F;
1629         if (imageId & IMAGE_TYPE_REMAP_2_PLUS)
1630         {
1631             index &= 0x1F;
1632         }
1633         if (auto pm = GetPaletteMapForColour(index); pm.has_value())
1634         {
1635             paletteMap = pm.value();
1636         }
1637     }
1638     else
1639     {
1640         _currentImageType = 0;
1641     }
1642     return is_sprite_interacted_with_palette_set(dpi, imageId, coords, paletteMap);
1643 }
1644 
1645 /**
1646  *
1647  *  rct2: 0x0068862C
1648  */
set_interaction_info_from_paint_session(paint_session * session,uint16_t filter)1649 InteractionInfo set_interaction_info_from_paint_session(paint_session* session, uint16_t filter)
1650 {
1651     paint_struct* ps = &session->PaintHead;
1652     rct_drawpixelinfo* dpi = &session->DPI;
1653     InteractionInfo info{};
1654 
1655     while ((ps = ps->next_quadrant_ps) != nullptr)
1656     {
1657         paint_struct* old_ps = ps;
1658         paint_struct* next_ps = ps;
1659         while (next_ps != nullptr)
1660         {
1661             ps = next_ps;
1662             if (is_sprite_interacted_with(dpi, ps->image_id, { ps->x, ps->y }))
1663             {
1664                 if (PSSpriteTypeIsInFilter(ps, filter))
1665                 {
1666                     info = { ps };
1667                 }
1668             }
1669             next_ps = ps->children;
1670         }
1671 
1672         for (attached_paint_struct* attached_ps = ps->attached_ps; attached_ps != nullptr; attached_ps = attached_ps->next)
1673         {
1674             if (is_sprite_interacted_with(dpi, attached_ps->image_id, { (attached_ps->x + ps->x), (attached_ps->y + ps->y) }))
1675             {
1676                 if (PSSpriteTypeIsInFilter(ps, filter))
1677                 {
1678                     info = { ps };
1679                 }
1680             }
1681         }
1682 
1683         ps = old_ps;
1684     }
1685     return info;
1686 }
1687 
1688 /**
1689  *
1690  *  rct2: 0x00685ADC
1691  * screenX: eax
1692  * screenY: ebx
1693  * flags: edx
1694  * x: ax
1695  * y: cx
1696  * interactionType: bl
1697  * tileElement: edx
1698  * viewport: edi
1699  */
get_map_coordinates_from_pos(const ScreenCoordsXY & screenCoords,int32_t flags)1700 InteractionInfo get_map_coordinates_from_pos(const ScreenCoordsXY& screenCoords, int32_t flags)
1701 {
1702     rct_window* window = window_find_from_point(screenCoords);
1703     return get_map_coordinates_from_pos_window(window, screenCoords, flags);
1704 }
1705 
get_map_coordinates_from_pos_window(rct_window * window,const ScreenCoordsXY & screenCoords,int32_t flags)1706 InteractionInfo get_map_coordinates_from_pos_window(rct_window* window, const ScreenCoordsXY& screenCoords, int32_t flags)
1707 {
1708     InteractionInfo info{};
1709     if (window == nullptr || window->viewport == nullptr)
1710     {
1711         return info;
1712     }
1713 
1714     rct_viewport* myviewport = window->viewport;
1715     auto viewLoc = screenCoords;
1716     viewLoc -= myviewport->pos;
1717     if (viewLoc.x >= 0 && viewLoc.x < static_cast<int32_t>(myviewport->width) && viewLoc.y >= 0
1718         && viewLoc.y < static_cast<int32_t>(myviewport->height))
1719     {
1720         viewLoc.x = viewLoc.x * myviewport->zoom;
1721         viewLoc.y = viewLoc.y * myviewport->zoom;
1722         viewLoc += myviewport->viewPos;
1723         if (myviewport->zoom > 0)
1724         {
1725             viewLoc.x &= (0xFFFFFFFF * myviewport->zoom) & 0xFFFFFFFF;
1726             viewLoc.y &= (0xFFFFFFFF * myviewport->zoom) & 0xFFFFFFFF;
1727         }
1728         rct_drawpixelinfo dpi;
1729         dpi.x = viewLoc.x;
1730         dpi.y = viewLoc.y;
1731         dpi.height = 1;
1732         dpi.zoom_level = myviewport->zoom;
1733         dpi.width = 1;
1734 
1735         paint_session* session = PaintSessionAlloc(&dpi, myviewport->flags);
1736         PaintSessionGenerate(session);
1737         PaintSessionArrange(session);
1738         info = set_interaction_info_from_paint_session(session, flags & 0xFFFF);
1739         PaintSessionFree(session);
1740     }
1741     return info;
1742 }
1743 
1744 /**
1745  * screenRect represents 2D map coordinates at zoom 0.
1746  */
viewport_invalidate(const rct_viewport * viewport,const ScreenRect & screenRect)1747 void viewport_invalidate(const rct_viewport* viewport, const ScreenRect& screenRect)
1748 {
1749     // if unknown viewport visibility, use the containing window to discover the status
1750     if (viewport->visibility == VisibilityCache::Unknown)
1751     {
1752         auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
1753         auto owner = windowManager->GetOwner(viewport);
1754         if (owner != nullptr && owner->classification != WC_MAIN_WINDOW)
1755         {
1756             // note, window_is_visible will update viewport->visibility, so this should have a low hit count
1757             if (!window_is_visible(owner))
1758             {
1759                 return;
1760             }
1761         }
1762     }
1763 
1764     if (viewport->visibility == VisibilityCache::Covered)
1765         return;
1766 
1767     auto [topLeft, bottomRight] = screenRect;
1768     const auto [viewportRight, viewportBottom] = viewport->viewPos
1769         + ScreenCoordsXY{ viewport->view_width, viewport->view_height };
1770 
1771     if (bottomRight.x > viewport->viewPos.x && bottomRight.y > viewport->viewPos.y)
1772     {
1773         topLeft = { std::max(topLeft.x, viewport->viewPos.x), std::max(topLeft.y, viewport->viewPos.y) };
1774         topLeft -= viewport->viewPos;
1775         topLeft = { topLeft.x / viewport->zoom, topLeft.y / viewport->zoom };
1776         topLeft += viewport->pos;
1777 
1778         bottomRight = { std::max(bottomRight.x, viewportRight), std::max(bottomRight.y, viewportBottom) };
1779         bottomRight -= viewport->viewPos;
1780         bottomRight = { bottomRight.x / viewport->zoom, bottomRight.y / viewport->zoom };
1781         bottomRight += viewport->pos;
1782 
1783         gfx_set_dirty_blocks({ topLeft, bottomRight });
1784     }
1785 }
1786 
viewport_find_from_point(const ScreenCoordsXY & screenCoords)1787 static rct_viewport* viewport_find_from_point(const ScreenCoordsXY& screenCoords)
1788 {
1789     rct_window* w = window_find_from_point(screenCoords);
1790     if (w == nullptr)
1791         return nullptr;
1792 
1793     rct_viewport* viewport = w->viewport;
1794     if (viewport == nullptr)
1795         return nullptr;
1796 
1797     if (viewport->ContainsScreen(screenCoords))
1798         return viewport;
1799 
1800     return nullptr;
1801 }
1802 
1803 /**
1804  *
1805  *  rct2: 0x00688972
1806  * In:
1807  *      screen_x: eax
1808  *      screen_y: ebx
1809  * Out:
1810  *      x: ax
1811  *      y: bx
1812  *      tile_element: edx ?
1813  *      viewport: edi
1814  */
screen_get_map_xy(const ScreenCoordsXY & screenCoords,rct_viewport ** viewport)1815 std::optional<CoordsXY> screen_get_map_xy(const ScreenCoordsXY& screenCoords, rct_viewport** viewport)
1816 {
1817     // This will get the tile location but we will need the more accuracy
1818     rct_window* window = window_find_from_point(screenCoords);
1819     if (window == nullptr || window->viewport == nullptr)
1820     {
1821         return std::nullopt;
1822     }
1823     auto myViewport = window->viewport;
1824     auto info = get_map_coordinates_from_pos_window(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain));
1825     if (info.SpriteType == ViewportInteractionItem::None)
1826     {
1827         return std::nullopt;
1828     }
1829 
1830     auto start_vp_pos = myViewport->ScreenToViewportCoord(screenCoords);
1831     CoordsXY cursorMapPos = info.Loc.ToTileCentre();
1832 
1833     // Iterates the cursor location to work out exactly where on the tile it is
1834     for (int32_t i = 0; i < 5; i++)
1835     {
1836         int32_t z = tile_element_height(cursorMapPos);
1837         cursorMapPos = viewport_coord_to_map_coord(start_vp_pos, z);
1838         cursorMapPos.x = std::clamp(cursorMapPos.x, info.Loc.x, info.Loc.x + 31);
1839         cursorMapPos.y = std::clamp(cursorMapPos.y, info.Loc.y, info.Loc.y + 31);
1840     }
1841 
1842     if (viewport != nullptr)
1843         *viewport = myViewport;
1844 
1845     return cursorMapPos;
1846 }
1847 
1848 /**
1849  *
1850  *  rct2: 0x006894D4
1851  */
screen_get_map_xy_with_z(const ScreenCoordsXY & screenCoords,int32_t z)1852 std::optional<CoordsXY> screen_get_map_xy_with_z(const ScreenCoordsXY& screenCoords, int32_t z)
1853 {
1854     rct_viewport* viewport = viewport_find_from_point(screenCoords);
1855     if (viewport == nullptr)
1856     {
1857         return std::nullopt;
1858     }
1859 
1860     auto vpCoords = viewport->ScreenToViewportCoord(screenCoords);
1861     auto mapPosition = viewport_coord_to_map_coord(vpCoords, z);
1862     if (!map_is_location_valid(mapPosition))
1863     {
1864         return std::nullopt;
1865     }
1866 
1867     return mapPosition;
1868 }
1869 
1870 /**
1871  *
1872  *  rct2: 0x00689604
1873  */
screen_get_map_xy_quadrant(const ScreenCoordsXY & screenCoords,uint8_t * quadrant)1874 std::optional<CoordsXY> screen_get_map_xy_quadrant(const ScreenCoordsXY& screenCoords, uint8_t* quadrant)
1875 {
1876     auto mapCoords = screen_get_map_xy(screenCoords, nullptr);
1877     if (!mapCoords.has_value())
1878         return std::nullopt;
1879 
1880     *quadrant = map_get_tile_quadrant(*mapCoords);
1881     return mapCoords->ToTileStart();
1882 }
1883 
1884 /**
1885  *
1886  *  rct2: 0x0068964B
1887  */
screen_get_map_xy_quadrant_with_z(const ScreenCoordsXY & screenCoords,int32_t z,uint8_t * quadrant)1888 std::optional<CoordsXY> screen_get_map_xy_quadrant_with_z(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant)
1889 {
1890     auto mapCoords = screen_get_map_xy_with_z(screenCoords, z);
1891     if (!mapCoords.has_value())
1892         return std::nullopt;
1893 
1894     *quadrant = map_get_tile_quadrant(*mapCoords);
1895     return mapCoords->ToTileStart();
1896 }
1897 
1898 /**
1899  *
1900  *  rct2: 0x00689692
1901  */
screen_get_map_xy_side(const ScreenCoordsXY & screenCoords,uint8_t * side)1902 std::optional<CoordsXY> screen_get_map_xy_side(const ScreenCoordsXY& screenCoords, uint8_t* side)
1903 {
1904     auto mapCoords = screen_get_map_xy(screenCoords, nullptr);
1905     if (!mapCoords.has_value())
1906         return std::nullopt;
1907 
1908     *side = map_get_tile_side(*mapCoords);
1909     return mapCoords->ToTileStart();
1910 }
1911 
1912 /**
1913  *
1914  *  rct2: 0x006896DC
1915  */
screen_get_map_xy_side_with_z(const ScreenCoordsXY & screenCoords,int32_t z,uint8_t * side)1916 std::optional<CoordsXY> screen_get_map_xy_side_with_z(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side)
1917 {
1918     auto mapCoords = screen_get_map_xy_with_z(screenCoords, z);
1919     if (!mapCoords.has_value())
1920         return std::nullopt;
1921 
1922     *side = map_get_tile_side(*mapCoords);
1923     return mapCoords->ToTileStart();
1924 }
1925 
1926 /**
1927  * Get current viewport rotation.
1928  *
1929  * If an invalid rotation is detected and DEBUG_LEVEL_1 is enabled, an error
1930  * will be reported.
1931  *
1932  * @returns rotation in range 0-3 (inclusive)
1933  */
get_current_rotation()1934 uint8_t get_current_rotation()
1935 {
1936     uint8_t rotation = gCurrentRotation;
1937     uint8_t rotation_masked = rotation & 3;
1938 #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
1939     if (rotation != rotation_masked)
1940     {
1941         log_error(
1942             "Found wrong rotation %d! Will return %d instead.", static_cast<uint32_t>(rotation),
1943             static_cast<uint32_t>(rotation_masked));
1944     }
1945 #endif // DEBUG_LEVEL_1
1946     return rotation_masked;
1947 }
1948 
get_height_marker_offset()1949 int32_t get_height_marker_offset()
1950 {
1951     // Height labels in units
1952     if (gConfigGeneral.show_height_as_units)
1953         return 0;
1954 
1955     // Height labels in feet
1956     if (gConfigGeneral.measurement_format == MeasurementFormat::Imperial)
1957         return 1 * 256;
1958 
1959     // Height labels in metres
1960     return 2 * 256;
1961 }
1962 
viewport_set_saved_view()1963 void viewport_set_saved_view()
1964 {
1965     rct_window* w = window_get_main();
1966     if (w != nullptr)
1967     {
1968         rct_viewport* viewport = w->viewport;
1969 
1970         gSavedView = ScreenCoordsXY{ viewport->view_width / 2, viewport->view_height / 2 } + viewport->viewPos;
1971 
1972         gSavedViewZoom = viewport->zoom;
1973         gSavedViewRotation = get_current_rotation();
1974     }
1975 }
1976 
min()1977 ZoomLevel ZoomLevel::min()
1978 {
1979     if (drawing_engine_get_type() == DrawingEngine::OpenGL)
1980     {
1981         return -2;
1982     }
1983 
1984     return 0;
1985 }
1986