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 <algorithm>
11 #include <openrct2-ui/interface/Viewport.h>
12 #include <openrct2-ui/interface/Widget.h>
13 #include <openrct2-ui/windows/Window.h>
14 #include <openrct2/Cheats.h>
15 #include <openrct2/Context.h>
16 #include <openrct2/Game.h>
17 #include <openrct2/Input.h>
18 #include <openrct2/actions/TrackDesignAction.h>
19 #include <openrct2/audio/audio.h>
20 #include <openrct2/localisation/Localisation.h>
21 #include <openrct2/ride/RideData.h>
22 #include <openrct2/ride/Track.h>
23 #include <openrct2/ride/TrackData.h>
24 #include <openrct2/ride/TrackDesign.h>
25 #include <openrct2/ride/TrackDesignRepository.h>
26 #include <openrct2/sprites.h>
27 #include <openrct2/ui/UiContext.h>
28 #include <openrct2/ui/WindowManager.h>
29 #include <openrct2/windows/Intent.h>
30 #include <openrct2/world/Park.h>
31 #include <openrct2/world/Surface.h>
32 #include <vector>
33 
34 using namespace OpenRCT2;
35 using namespace OpenRCT2::TrackMetaData;
36 
37 static constexpr const rct_string_id WINDOW_TITLE = STR_STRING;
38 static constexpr const int32_t WH = 124;
39 static constexpr const int32_t WW = 200;
40 constexpr int16_t TRACK_MINI_PREVIEW_WIDTH = 168;
41 constexpr int16_t TRACK_MINI_PREVIEW_HEIGHT = 78;
42 constexpr uint16_t TRACK_MINI_PREVIEW_SIZE = TRACK_MINI_PREVIEW_WIDTH * TRACK_MINI_PREVIEW_HEIGHT;
43 
44 struct rct_track_td6;
45 
46 static constexpr uint8_t _PaletteIndexColourEntrance = PALETTE_INDEX_20; // White
47 static constexpr uint8_t _PaletteIndexColourExit = PALETTE_INDEX_10;     // Black
48 static constexpr uint8_t _PaletteIndexColourTrack = PALETTE_INDEX_248;   // Grey (dark)
49 static constexpr uint8_t _PaletteIndexColourStation = PALETTE_INDEX_252; // Grey (light)
50 
51 // clang-format off
52 enum {
53     WIDX_BACKGROUND,
54     WIDX_TITLE,
55     WIDX_CLOSE,
56     WIDX_ROTATE,
57     WIDX_MIRROR,
58     WIDX_SELECT_DIFFERENT_DESIGN,
59     WIDX_PRICE
60 };
61 
62 validate_global_widx(WC_TRACK_DESIGN_PLACE, WIDX_ROTATE);
63 
64 static rct_widget window_track_place_widgets[] = {
65     WINDOW_SHIM(WINDOW_TITLE, WW, WH),
66     MakeWidget({173,  83}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Primary, SPR_ROTATE_ARROW,              STR_ROTATE_90_TIP                         ),
67     MakeWidget({173,  59}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Primary, SPR_MIRROR_ARROW,              STR_MIRROR_IMAGE_TIP                      ),
68     MakeWidget({  4, 109}, {192, 12}, WindowWidgetType::Button,  WindowColour::Primary, STR_SELECT_A_DIFFERENT_DESIGN, STR_GO_BACK_TO_DESIGN_SELECTION_WINDOW_TIP),
69     MakeWidget({  0,   0}, {  1,  1}, WindowWidgetType::Empty,   WindowColour::Primary),
70     WIDGETS_END,
71 };
72 
73 static void window_track_place_close(rct_window *w);
74 static void window_track_place_mouseup(rct_window *w, rct_widgetindex widgetIndex);
75 static void window_track_place_update(rct_window *w);
76 static void window_track_place_toolupdate(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
77 static void window_track_place_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
78 static void window_track_place_toolabort(rct_window *w, rct_widgetindex widgetIndex);
79 static void window_track_place_unknown14(rct_window *w);
80 static void window_track_place_invalidate(rct_window *w);
81 static void window_track_place_paint(rct_window *w, rct_drawpixelinfo *dpi);
82 
83 static rct_window_event_list window_track_place_events([](auto& events)
__anoncae132bb0202(auto& events) 84 {
85     events.close = &window_track_place_close;
86     events.mouse_up = &window_track_place_mouseup;
87     events.update = &window_track_place_update;
88     events.tool_update = &window_track_place_toolupdate;
89     events.tool_down = &window_track_place_tooldown;
90     events.tool_abort = &window_track_place_toolabort;
91     events.viewport_rotate = &window_track_place_unknown14;
92     events.invalidate = &window_track_place_invalidate;
93     events.paint = &window_track_place_paint;
94 });
95 // clang-format on
96 
97 static std::vector<uint8_t> _window_track_place_mini_preview;
98 static CoordsXY _windowTrackPlaceLast;
99 
100 static ride_id_t _window_track_place_ride_index;
101 static bool _window_track_place_last_was_valid;
102 static CoordsXYZ _windowTrackPlaceLastValid;
103 static money32 _window_track_place_last_cost;
104 
105 static std::unique_ptr<TrackDesign> _trackDesign;
106 
107 static void window_track_place_clear_provisional();
108 static int32_t window_track_place_get_base_z(const CoordsXY& loc);
109 
110 static void window_track_place_clear_mini_preview();
111 static void window_track_place_draw_mini_preview(TrackDesign* td6);
112 static void window_track_place_draw_mini_preview_track(
113     TrackDesign* td6, int32_t pass, const CoordsXY& origin, CoordsXY min, CoordsXY max);
114 static void window_track_place_draw_mini_preview_maze(
115     TrackDesign* td6, int32_t pass, const CoordsXY& origin, CoordsXY min, CoordsXY max);
116 static ScreenCoordsXY draw_mini_preview_get_pixel_position(const CoordsXY& location);
117 static bool draw_mini_preview_is_pixel_in_bounds(const ScreenCoordsXY& pixel);
118 static uint8_t* draw_mini_preview_get_pixel_ptr(const ScreenCoordsXY& pixel);
119 
120 /**
121  *
122  *  rct2: 0x006D182E
123  */
window_track_place_clear_mini_preview()124 static void window_track_place_clear_mini_preview()
125 {
126     // Fill with transparent colour.
127     std::fill(_window_track_place_mini_preview.begin(), _window_track_place_mini_preview.end(), PALETTE_INDEX_0);
128 }
129 
130 /**
131  *
132  *  rct2: 0x006CFCA0
133  */
window_track_place_open(const track_design_file_ref * tdFileRef)134 rct_window* window_track_place_open(const track_design_file_ref* tdFileRef)
135 {
136     _trackDesign = TrackDesignImport(tdFileRef->path);
137     if (_trackDesign == nullptr)
138     {
139         return nullptr;
140     }
141 
142     window_close_construction_windows();
143 
144     _window_track_place_mini_preview.resize(TRACK_MINI_PREVIEW_SIZE);
145 
146     rct_window* w = WindowCreate(ScreenCoordsXY(0, 29), 200, 124, &window_track_place_events, WC_TRACK_DESIGN_PLACE, 0);
147     w->widgets = window_track_place_widgets;
148     w->enabled_widgets = 1ULL << WIDX_CLOSE | 1ULL << WIDX_ROTATE | 1ULL << WIDX_MIRROR | 1ULL << WIDX_SELECT_DIFFERENT_DESIGN;
149     WindowInitScrollWidgets(w);
150     tool_set(w, WIDX_PRICE, Tool::Crosshair);
151     input_set_flag(INPUT_FLAG_6, true);
152     window_push_others_right(w);
153     show_gridlines();
154     _window_track_place_last_cost = MONEY32_UNDEFINED;
155     _windowTrackPlaceLast.SetNull();
156     _currentTrackPieceDirection = (2 - get_current_rotation()) & 3;
157 
158     window_track_place_clear_mini_preview();
159     window_track_place_draw_mini_preview(_trackDesign.get());
160 
161     return w;
162 }
163 
164 /**
165  *
166  *  rct2: 0x006D0119
167  */
window_track_place_close(rct_window * w)168 static void window_track_place_close(rct_window* w)
169 {
170     window_track_place_clear_provisional();
171     viewport_set_visibility(0);
172     map_invalidate_map_selection_tiles();
173     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
174     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
175     hide_gridlines();
176     _window_track_place_mini_preview.clear();
177     _window_track_place_mini_preview.shrink_to_fit();
178     _trackDesign = nullptr;
179 }
180 
181 /**
182  *
183  *  rct2: 0x006CFEAC
184  */
window_track_place_mouseup(rct_window * w,rct_widgetindex widgetIndex)185 static void window_track_place_mouseup(rct_window* w, rct_widgetindex widgetIndex)
186 {
187     switch (widgetIndex)
188     {
189         case WIDX_CLOSE:
190             window_close(w);
191             break;
192         case WIDX_ROTATE:
193             window_track_place_clear_provisional();
194             _currentTrackPieceDirection = (_currentTrackPieceDirection + 1) & 3;
195             w->Invalidate();
196             _windowTrackPlaceLast.SetNull();
197             window_track_place_draw_mini_preview(_trackDesign.get());
198             break;
199         case WIDX_MIRROR:
200             TrackDesignMirror(_trackDesign.get());
201             _currentTrackPieceDirection = (0 - _currentTrackPieceDirection) & 3;
202             w->Invalidate();
203             _windowTrackPlaceLast.SetNull();
204             window_track_place_draw_mini_preview(_trackDesign.get());
205             break;
206         case WIDX_SELECT_DIFFERENT_DESIGN:
207             window_close(w);
208 
209             auto intent = Intent(WC_TRACK_DESIGN_LIST);
210             intent.putExtra(INTENT_EXTRA_RIDE_TYPE, _window_track_list_item.Type);
211             intent.putExtra(INTENT_EXTRA_RIDE_ENTRY_INDEX, _window_track_list_item.EntryIndex);
212             context_open_intent(&intent);
213             break;
214     }
215 }
216 
217 /**
218  *
219  *  rct2: 0x006CFCA0
220  */
window_track_place_update(rct_window * w)221 static void window_track_place_update(rct_window* w)
222 {
223     if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE)))
224         if (gCurrentToolWidget.window_classification != WC_TRACK_DESIGN_PLACE)
225             window_close(w);
226 }
227 
FindValidTrackDesignPlaceHeight(CoordsXYZ & loc,uint32_t flags)228 static GameActions::Result::Ptr FindValidTrackDesignPlaceHeight(CoordsXYZ& loc, uint32_t flags)
229 {
230     GameActions::Result::Ptr res;
231     for (int32_t i = 0; i < 7; i++, loc.z += 8)
232     {
233         auto tdAction = TrackDesignAction(CoordsXYZD{ loc.x, loc.y, loc.z, _currentTrackPieceDirection }, *_trackDesign);
234         tdAction.SetFlags(flags);
235         res = GameActions::Query(&tdAction);
236 
237         // If successful don't keep trying.
238         // If failure due to no money then increasing height only makes problem worse
239         if (res->Error == GameActions::Status::Ok || res->Error == GameActions::Status::InsufficientFunds)
240         {
241             return res;
242         }
243     }
244     return res;
245 }
246 
247 /**
248  *
249  *  rct2: 0x006CFF2D
250  */
window_track_place_toolupdate(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)251 static void window_track_place_toolupdate(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
252 {
253     int16_t mapZ;
254 
255     map_invalidate_map_selection_tiles();
256     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE;
257     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
258     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
259 
260     // Get the tool map position
261     CoordsXY mapCoords = ViewportInteractionGetTileStartAtCursor(screenCoords);
262     if (mapCoords.IsNull())
263     {
264         window_track_place_clear_provisional();
265         return;
266     }
267 
268     // Check if tool map position has changed since last update
269     if (mapCoords == _windowTrackPlaceLast)
270     {
271         TrackDesignPreviewDrawOutlines(_trackDesign.get(), GetOrAllocateRide(PreviewRideId), { mapCoords, 0 });
272         return;
273     }
274 
275     money32 cost = MONEY32_UNDEFINED;
276 
277     // Get base Z position
278     mapZ = window_track_place_get_base_z(mapCoords);
279     CoordsXYZ trackLoc = { mapCoords, mapZ };
280 
281     if (game_is_not_paused() || gCheatsBuildInPauseMode)
282     {
283         window_track_place_clear_provisional();
284         auto res = FindValidTrackDesignPlaceHeight(trackLoc, GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
285 
286         if (res->Error == GameActions::Status::Ok)
287         {
288             // Valid location found. Place the ghost at the location.
289             auto tdAction = TrackDesignAction({ trackLoc, _currentTrackPieceDirection }, *_trackDesign);
290             tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
291             tdAction.SetCallback([trackLoc](const GameAction*, const GameActions::Result* result) {
292                 if (result->Error == GameActions::Status::Ok)
293                 {
294                     _window_track_place_ride_index = result->GetData<ride_id_t>();
295                     _windowTrackPlaceLastValid = trackLoc;
296                     _window_track_place_last_was_valid = true;
297                 }
298             });
299             res = GameActions::Execute(&tdAction);
300             cost = res->Error == GameActions::Status::Ok ? res->Cost : MONEY32_UNDEFINED;
301         }
302     }
303 
304     _windowTrackPlaceLast = trackLoc;
305     if (cost != _window_track_place_last_cost)
306     {
307         _window_track_place_last_cost = cost;
308         widget_invalidate(w, WIDX_PRICE);
309     }
310 
311     TrackDesignPreviewDrawOutlines(_trackDesign.get(), GetOrAllocateRide(PreviewRideId), trackLoc);
312 }
313 
314 /**
315  *
316  *  rct2: 0x006CFF34
317  */
window_track_place_tooldown(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)318 static void window_track_place_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
319 {
320     window_track_place_clear_provisional();
321     map_invalidate_map_selection_tiles();
322     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE;
323     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
324     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
325 
326     const CoordsXY mapCoords = ViewportInteractionGetTileStartAtCursor(screenCoords);
327     if (mapCoords.IsNull())
328         return;
329 
330     // Try increasing Z until a feasible placement is found
331     int16_t mapZ = window_track_place_get_base_z(mapCoords);
332     CoordsXYZ trackLoc = { mapCoords, mapZ };
333 
334     auto res = FindValidTrackDesignPlaceHeight(trackLoc, 0);
335     if (res->Error == GameActions::Status::Ok)
336     {
337         auto tdAction = TrackDesignAction({ trackLoc, _currentTrackPieceDirection }, *_trackDesign);
338         tdAction.SetCallback([trackLoc](const GameAction*, const GameActions::Result* result) {
339             if (result->Error == GameActions::Status::Ok)
340             {
341                 const auto rideId = result->GetData<ride_id_t>();
342                 auto ride = get_ride(rideId);
343                 if (ride != nullptr)
344                 {
345                     window_close_by_class(WC_ERROR);
346                     OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, trackLoc);
347 
348                     _currentRideIndex = rideId;
349                     if (track_design_are_entrance_and_exit_placed())
350                     {
351                         auto intent = Intent(WC_RIDE);
352                         intent.putExtra(INTENT_EXTRA_RIDE_ID, static_cast<int32_t>(rideId));
353                         context_open_intent(&intent);
354                         auto wnd = window_find_by_class(WC_TRACK_DESIGN_PLACE);
355                         window_close(wnd);
356                     }
357                     else
358                     {
359                         ride_initialise_construction_window(ride);
360                         auto wnd = window_find_by_class(WC_RIDE_CONSTRUCTION);
361                         window_event_mouse_up_call(wnd, WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE);
362                     }
363                 }
364             }
365             else
366             {
367                 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Error, result->Position);
368             }
369         });
370         GameActions::Execute(&tdAction);
371         return;
372     }
373 
374     // Unable to build track
375     OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Error, trackLoc);
376 
377     auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
378     windowManager->ShowError(res->GetErrorTitle(), res->GetErrorMessage());
379 }
380 
381 /**
382  *
383  *  rct2: 0x006D015C
384  */
window_track_place_toolabort(rct_window * w,rct_widgetindex widgetIndex)385 static void window_track_place_toolabort(rct_window* w, rct_widgetindex widgetIndex)
386 {
387     window_track_place_clear_provisional();
388 }
389 
390 /**
391  *
392  *  rct2: 0x006CFF01
393  */
window_track_place_unknown14(rct_window * w)394 static void window_track_place_unknown14(rct_window* w)
395 {
396     window_track_place_draw_mini_preview(_trackDesign.get());
397 }
398 
window_track_place_invalidate(rct_window * w)399 static void window_track_place_invalidate(rct_window* w)
400 {
401     window_track_place_draw_mini_preview(_trackDesign.get());
402 }
403 
404 /**
405  *
406  *  rct2: 0x006D017F
407  */
window_track_place_clear_provisional()408 static void window_track_place_clear_provisional()
409 {
410     if (_window_track_place_last_was_valid)
411     {
412         auto ride = get_ride(_window_track_place_ride_index);
413         if (ride != nullptr)
414         {
415             TrackDesignPreviewRemoveGhosts(_trackDesign.get(), ride, _windowTrackPlaceLastValid);
416             _window_track_place_last_was_valid = false;
417         }
418     }
419 }
420 
TrackPlaceClearProvisionalTemporarily()421 void TrackPlaceClearProvisionalTemporarily()
422 {
423     if (_window_track_place_last_was_valid)
424     {
425         auto ride = get_ride(_window_track_place_ride_index);
426         if (ride != nullptr)
427         {
428             TrackDesignPreviewRemoveGhosts(_trackDesign.get(), ride, _windowTrackPlaceLastValid);
429         }
430     }
431 }
432 
TrackPlaceRestoreProvisional()433 void TrackPlaceRestoreProvisional()
434 {
435     if (_window_track_place_last_was_valid)
436     {
437         auto tdAction = TrackDesignAction({ _windowTrackPlaceLastValid, _currentTrackPieceDirection }, *_trackDesign);
438         tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
439         auto res = GameActions::Execute(&tdAction);
440         if (res->Error != GameActions::Status::Ok)
441         {
442             _window_track_place_last_was_valid = false;
443         }
444     }
445 }
446 
447 /**
448  *
449  *  rct2: 0x006D17C6
450  */
window_track_place_get_base_z(const CoordsXY & loc)451 static int32_t window_track_place_get_base_z(const CoordsXY& loc)
452 {
453     auto surfaceElement = map_get_surface_element_at(loc);
454     if (surfaceElement == nullptr)
455         return 0;
456 
457     auto z = surfaceElement->GetBaseZ();
458 
459     // Increase Z above slope
460     if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
461     {
462         z += 16;
463 
464         // Increase Z above double slope
465         if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
466             z += 16;
467     }
468 
469     // Increase Z above water
470     if (surfaceElement->GetWaterHeight() > 0)
471         z = std::max(z, surfaceElement->GetWaterHeight());
472 
473     return z + TrackDesignGetZPlacement(_trackDesign.get(), GetOrAllocateRide(PreviewRideId), { loc, z });
474 }
475 
476 /**
477  *
478  *  rct2: 0x006CFD9D
479  */
window_track_place_paint(rct_window * w,rct_drawpixelinfo * dpi)480 static void window_track_place_paint(rct_window* w, rct_drawpixelinfo* dpi)
481 {
482     auto ft = Formatter::Common();
483     ft.Add<char*>(_trackDesign->name.c_str());
484     WindowDrawWidgets(w, dpi);
485 
486     // Draw mini tile preview
487     rct_drawpixelinfo clippedDpi;
488     if (clip_drawpixelinfo(&clippedDpi, dpi, w->windowPos + ScreenCoordsXY{ 4, 18 }, 168, 78))
489     {
490         rct_g1_element g1temp = {};
491         g1temp.offset = _window_track_place_mini_preview.data();
492         g1temp.width = TRACK_MINI_PREVIEW_WIDTH;
493         g1temp.height = TRACK_MINI_PREVIEW_HEIGHT;
494         gfx_set_g1_element(SPR_TEMP, &g1temp);
495         drawing_engine_invalidate_image(SPR_TEMP);
496         gfx_draw_sprite(&clippedDpi, ImageId(SPR_TEMP, NOT_TRANSLUCENT(w->colours[0])), { 0, 0 });
497     }
498 
499     // Price
500     if (_window_track_place_last_cost != MONEY32_UNDEFINED && !(gParkFlags & PARK_FLAGS_NO_MONEY))
501     {
502         ft = Formatter();
503         ft.Add<money64>(_window_track_place_last_cost);
504         DrawTextBasic(dpi, w->windowPos + ScreenCoordsXY{ 88, 94 }, STR_COST_LABEL, ft, { TextAlignment::CENTRE });
505     }
506 }
507 
508 /**
509  *
510  *  rct2: 0x006D1845
511  */
window_track_place_draw_mini_preview(TrackDesign * td6)512 static void window_track_place_draw_mini_preview(TrackDesign* td6)
513 {
514     window_track_place_clear_mini_preview();
515 
516     // First pass is used to determine the width and height of the image so it can centre it
517     CoordsXY min = { 0, 0 };
518     CoordsXY max = { 0, 0 };
519     for (int32_t pass = 0; pass < 2; pass++)
520     {
521         CoordsXY origin = { 0, 0 };
522         if (pass == 1)
523         {
524             origin.x -= ((max.x + min.x) >> 6) * COORDS_XY_STEP;
525             origin.y -= ((max.y + min.y) >> 6) * COORDS_XY_STEP;
526         }
527 
528         if (td6->type == RIDE_TYPE_MAZE)
529         {
530             window_track_place_draw_mini_preview_maze(td6, pass, origin, min, max);
531         }
532         else
533         {
534             window_track_place_draw_mini_preview_track(td6, pass, origin, min, max);
535         }
536     }
537 }
538 
window_track_place_draw_mini_preview_track(TrackDesign * td6,int32_t pass,const CoordsXY & origin,CoordsXY min,CoordsXY max)539 static void window_track_place_draw_mini_preview_track(
540     TrackDesign* td6, int32_t pass, const CoordsXY& origin, CoordsXY min, CoordsXY max)
541 {
542     const uint8_t rotation = (_currentTrackPieceDirection + get_current_rotation()) & 3;
543 
544     CoordsXY curTrackStart = origin;
545     uint8_t curTrackRotation = rotation;
546     for (const auto& trackElement : td6->track_elements)
547     {
548         int32_t trackType = trackElement.type;
549         if (trackType == TrackElemType::InvertedUp90ToFlatQuarterLoopAlias)
550         {
551             trackType = TrackElemType::MultiDimInvertedUp90ToFlatQuarterLoop;
552         }
553 
554         // Follow a single track piece shape
555         const auto& ted = GetTrackElementDescriptor(trackType);
556         const rct_preview_track* trackBlock = ted.Block;
557         while (trackBlock->index != 255)
558         {
559             auto rotatedAndOffsetTrackBlock = curTrackStart + CoordsXY{ trackBlock->x, trackBlock->y }.Rotate(curTrackRotation);
560 
561             if (pass == 0)
562             {
563                 min.x = std::min(min.x, rotatedAndOffsetTrackBlock.x);
564                 max.x = std::max(max.x, rotatedAndOffsetTrackBlock.x);
565                 min.y = std::min(min.y, rotatedAndOffsetTrackBlock.y);
566                 max.y = std::max(max.y, rotatedAndOffsetTrackBlock.y);
567             }
568             else
569             {
570                 auto pixelPosition = draw_mini_preview_get_pixel_position(rotatedAndOffsetTrackBlock);
571                 if (draw_mini_preview_is_pixel_in_bounds(pixelPosition))
572                 {
573                     uint8_t* pixel = draw_mini_preview_get_pixel_ptr(pixelPosition);
574 
575                     auto bits = trackBlock->var_08.Rotate(curTrackRotation & 3).GetBaseQuarterOccupied();
576 
577                     // Station track is a lighter colour
578                     uint8_t colour = (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN) ? _PaletteIndexColourStation
579                                                                                               : _PaletteIndexColourTrack;
580 
581                     for (int32_t i = 0; i < 4; i++)
582                     {
583                         if (bits & 1)
584                             pixel[338 + i] = colour; // x + 2, y + 2
585                         if (bits & 2)
586                             pixel[168 + i] = colour; //        y + 1
587                         if (bits & 4)
588                             pixel[2 + i] = colour; // x + 2
589                         if (bits & 8)
590                             pixel[172 + i] = colour; // x + 4, y + 1
591                     }
592                 }
593             }
594             trackBlock++;
595         }
596 
597         // Change rotation and next position based on track curvature
598         curTrackRotation &= 3;
599 
600         const rct_track_coordinates* track_coordinate = &ted.Coordinates;
601 
602         curTrackStart += CoordsXY{ track_coordinate->x, track_coordinate->y }.Rotate(curTrackRotation);
603         curTrackRotation += track_coordinate->rotation_end - track_coordinate->rotation_begin;
604         curTrackRotation &= 3;
605         if (track_coordinate->rotation_end & 4)
606         {
607             curTrackRotation |= 4;
608         }
609         if (!(curTrackRotation & 4))
610         {
611             curTrackStart += CoordsDirectionDelta[curTrackRotation];
612         }
613     }
614 
615     // Draw entrance and exit preview.
616     for (const auto& entrance : td6->entrance_elements)
617     {
618         auto rotatedAndOffsetEntrance = origin + CoordsXY{ entrance.x, entrance.y }.Rotate(rotation);
619 
620         if (pass == 0)
621         {
622             min.x = std::min(min.x, rotatedAndOffsetEntrance.x);
623             max.x = std::max(max.x, rotatedAndOffsetEntrance.x);
624             min.y = std::min(min.y, rotatedAndOffsetEntrance.y);
625             max.y = std::max(max.y, rotatedAndOffsetEntrance.y);
626         }
627         else
628         {
629             auto pixelPosition = draw_mini_preview_get_pixel_position(rotatedAndOffsetEntrance);
630             if (draw_mini_preview_is_pixel_in_bounds(pixelPosition))
631             {
632                 uint8_t* pixel = draw_mini_preview_get_pixel_ptr(pixelPosition);
633                 uint8_t colour = entrance.isExit ? _PaletteIndexColourExit : _PaletteIndexColourEntrance;
634                 for (int32_t i = 0; i < 4; i++)
635                 {
636                     pixel[338 + i] = colour; // x + 2, y + 2
637                     pixel[168 + i] = colour; //        y + 1
638                     pixel[2 + i] = colour;   // x + 2
639                     pixel[172 + i] = colour; // x + 4, y + 1
640                 }
641             }
642         }
643     }
644 }
645 
window_track_place_draw_mini_preview_maze(TrackDesign * td6,int32_t pass,const CoordsXY & origin,CoordsXY min,CoordsXY max)646 static void window_track_place_draw_mini_preview_maze(
647     TrackDesign* td6, int32_t pass, const CoordsXY& origin, CoordsXY min, CoordsXY max)
648 {
649     uint8_t rotation = (_currentTrackPieceDirection + get_current_rotation()) & 3;
650     for (const auto& mazeElement : td6->maze_elements)
651     {
652         auto rotatedMazeCoords = origin + TileCoordsXY{ mazeElement.x, mazeElement.y }.ToCoordsXY().Rotate(rotation);
653 
654         if (pass == 0)
655         {
656             min.x = std::min(min.x, rotatedMazeCoords.x);
657             max.x = std::max(max.x, rotatedMazeCoords.x);
658             min.y = std::min(min.y, rotatedMazeCoords.y);
659             max.y = std::max(max.y, rotatedMazeCoords.y);
660         }
661         else
662         {
663             auto pixelPosition = draw_mini_preview_get_pixel_position(rotatedMazeCoords);
664             if (draw_mini_preview_is_pixel_in_bounds(pixelPosition))
665             {
666                 uint8_t* pixel = draw_mini_preview_get_pixel_ptr(pixelPosition);
667 
668                 uint8_t colour = _PaletteIndexColourTrack;
669 
670                 // Draw entrance and exit with different colours.
671                 if (mazeElement.type == MAZE_ELEMENT_TYPE_ENTRANCE)
672                     colour = _PaletteIndexColourEntrance;
673                 else if (mazeElement.type == MAZE_ELEMENT_TYPE_EXIT)
674                     colour = _PaletteIndexColourExit;
675 
676                 for (int32_t i = 0; i < 4; i++)
677                 {
678                     pixel[338 + i] = colour; // x + 2, y + 2
679                     pixel[168 + i] = colour; //        y + 1
680                     pixel[2 + i] = colour;   // x + 2
681                     pixel[172 + i] = colour; // x + 4, y + 1
682                 }
683             }
684         }
685     }
686 }
687 
draw_mini_preview_get_pixel_position(const CoordsXY & location)688 static ScreenCoordsXY draw_mini_preview_get_pixel_position(const CoordsXY& location)
689 {
690     auto tilePos = TileCoordsXY(location);
691     return { (80 + (tilePos.y - tilePos.x) * 4), (38 + (tilePos.y + tilePos.x) * 2) };
692 }
693 
draw_mini_preview_is_pixel_in_bounds(const ScreenCoordsXY & pixel)694 static bool draw_mini_preview_is_pixel_in_bounds(const ScreenCoordsXY& pixel)
695 {
696     return pixel.x >= 0 && pixel.y >= 0 && pixel.x <= 160 && pixel.y <= 75;
697 }
698 
draw_mini_preview_get_pixel_ptr(const ScreenCoordsXY & pixel)699 static uint8_t* draw_mini_preview_get_pixel_ptr(const ScreenCoordsXY& pixel)
700 {
701     return &_window_track_place_mini_preview[pixel.y * TRACK_MINI_PREVIEW_WIDTH + pixel.x];
702 }
703