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 "../interface/Theme.h"
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <iterator>
15 #include <limits>
16 #include <openrct2-ui/interface/Dropdown.h>
17 #include <openrct2-ui/interface/Viewport.h>
18 #include <openrct2-ui/interface/Widget.h>
19 #include <openrct2-ui/windows/Window.h>
20 #include <openrct2/Cheats.h>
21 #include <openrct2/Context.h>
22 #include <openrct2/Game.h>
23 #include <openrct2/Input.h>
24 #include <openrct2/OpenRCT2.h>
25 #include <openrct2/actions/GameAction.h>
26 #include <openrct2/actions/ParkSetParameterAction.h>
27 #include <openrct2/actions/RideSetAppearanceAction.h>
28 #include <openrct2/actions/RideSetColourSchemeAction.h>
29 #include <openrct2/actions/RideSetPriceAction.h>
30 #include <openrct2/actions/RideSetSettingAction.h>
31 #include <openrct2/audio/audio.h>
32 #include <openrct2/config/Config.h>
33 #include <openrct2/core/String.hpp>
34 #include <openrct2/localisation/Date.h>
35 #include <openrct2/localisation/Localisation.h>
36 #include <openrct2/localisation/LocalisationService.h>
37 #include <openrct2/localisation/StringIds.h>
38 #include <openrct2/network/network.h>
39 #include <openrct2/object/MusicObject.h>
40 #include <openrct2/object/ObjectManager.h>
41 #include <openrct2/object/ObjectRepository.h>
42 #include <openrct2/object/StationObject.h>
43 #include <openrct2/peep/Staff.h>
44 #include <openrct2/rct1/RCT1.h>
45 #include <openrct2/rct2/T6Exporter.h>
46 #include <openrct2/ride/RideData.h>
47 #include <openrct2/ride/ShopItem.h>
48 #include <openrct2/ride/Station.h>
49 #include <openrct2/ride/Track.h>
50 #include <openrct2/ride/TrackData.h>
51 #include <openrct2/ride/TrackDesign.h>
52 #include <openrct2/ride/TrackDesignRepository.h>
53 #include <openrct2/ride/Vehicle.h>
54 #include <openrct2/sprites.h>
55 #include <openrct2/windows/Intent.h>
56 #include <openrct2/world/EntityList.h>
57 #include <openrct2/world/Park.h>
58 #include <optional>
59 #include <vector>
60 
61 using namespace OpenRCT2;
62 using namespace OpenRCT2::TrackMetaData;
63 
64 static constexpr const rct_string_id WINDOW_TITLE = STR_RIDE_WINDOW_TITLE;
65 static constexpr const int32_t WH = 207;
66 static constexpr const int32_t WW = 316;
67 
68 static void populate_vehicle_type_dropdown(Ride* ride, bool forceRefresh = false);
69 
70 enum
71 {
72     WINDOW_RIDE_PAGE_MAIN,
73     WINDOW_RIDE_PAGE_VEHICLE,
74     WINDOW_RIDE_PAGE_OPERATING,
75     WINDOW_RIDE_PAGE_MAINTENANCE,
76     WINDOW_RIDE_PAGE_COLOUR,
77     WINDOW_RIDE_PAGE_MUSIC,
78     WINDOW_RIDE_PAGE_MEASUREMENTS,
79     WINDOW_RIDE_PAGE_GRAPHS,
80     WINDOW_RIDE_PAGE_INCOME,
81     WINDOW_RIDE_PAGE_CUSTOMER,
82     WINDOW_RIDE_PAGE_COUNT
83 };
84 
85 #pragma region Widgets
86 
87 // clang-format off
88 enum {
89     WIDX_BACKGROUND,
90     WIDX_TITLE,
91     WIDX_CLOSE,
92     WIDX_PAGE_BACKGROUND,
93     WIDX_TAB_1,
94     WIDX_TAB_2,
95     WIDX_TAB_3,
96     WIDX_TAB_4,
97     WIDX_TAB_5,
98     WIDX_TAB_6,
99     WIDX_TAB_7,
100     WIDX_TAB_8,
101     WIDX_TAB_9,
102     WIDX_TAB_10,
103 
104     WIDX_VIEWPORT = 14,
105     WIDX_VIEW,
106     WIDX_VIEW_DROPDOWN,
107     WIDX_STATUS,
108     WIDX_OPEN,
109     WIDX_CONSTRUCTION,
110     WIDX_RENAME,
111     WIDX_LOCATE,
112     WIDX_DEMOLISH,
113     WIDX_CLOSE_LIGHT,
114     WIDX_SIMULATE_LIGHT,
115     WIDX_TEST_LIGHT,
116     WIDX_OPEN_LIGHT,
117     WIDX_RIDE_TYPE,
118     WIDX_RIDE_TYPE_DROPDOWN,
119 
120     WIDX_VEHICLE_TYPE = 14,
121     WIDX_VEHICLE_TYPE_DROPDOWN,
122     WIDX_VEHICLE_TRAINS_PREVIEW,
123     WIDX_VEHICLE_TRAINS,
124     WIDX_VEHICLE_TRAINS_INCREASE,
125     WIDX_VEHICLE_TRAINS_DECREASE,
126     WIDX_VEHICLE_CARS_PER_TRAIN,
127     WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE,
128     WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE,
129 
130     WIDX_MODE_TWEAK = 14,
131     WIDX_MODE_TWEAK_INCREASE,
132     WIDX_MODE_TWEAK_DECREASE,
133     WIDX_LIFT_HILL_SPEED,
134     WIDX_LIFT_HILL_SPEED_INCREASE,
135     WIDX_LIFT_HILL_SPEED_DECREASE,
136     WIDX_LOAD_CHECKBOX,
137     WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX,
138     WIDX_MINIMUM_LENGTH_CHECKBOX,
139     WIDX_MINIMUM_LENGTH,
140     WIDX_MINIMUM_LENGTH_INCREASE,
141     WIDX_MINIMUM_LENGTH_DECREASE,
142     WIDX_MAXIMUM_LENGTH_CHECKBOX,
143     WIDX_MAXIMUM_LENGTH,
144     WIDX_MAXIMUM_LENGTH_INCREASE,
145     WIDX_MAXIMUM_LENGTH_DECREASE,
146     WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX,
147     WIDX_MODE_TWEAK_LABEL,
148     WIDX_LIFT_HILL_SPEED_LABEL,
149     WIDX_MODE,
150     WIDX_MODE_DROPDOWN,
151     WIDX_LOAD,
152     WIDX_LOAD_DROPDOWN,
153     WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL,
154     WIDX_OPERATE_NUMBER_OF_CIRCUITS,
155     WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE,
156     WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE,
157 
158     WIDX_INSPECTION_INTERVAL = 14,
159     WIDX_INSPECTION_INTERVAL_DROPDOWN,
160     WIDX_LOCATE_MECHANIC,
161     WIDX_REFURBISH_RIDE,
162     WIDX_FORCE_BREAKDOWN,
163 
164     WIDX_TRACK_PREVIEW = 14,
165     WIDX_TRACK_COLOUR_SCHEME,
166     WIDX_TRACK_COLOUR_SCHEME_DROPDOWN,
167     WIDX_TRACK_MAIN_COLOUR,
168     WIDX_TRACK_ADDITIONAL_COLOUR,
169     WIDX_TRACK_SUPPORT_COLOUR,
170     WIDX_MAZE_STYLE,
171     WIDX_MAZE_STYLE_DROPDOWN,
172     WIDX_PAINT_INDIVIDUAL_AREA,
173     WIDX_ENTRANCE_PREVIEW,
174     WIDX_ENTRANCE_STYLE,
175     WIDX_ENTRANCE_STYLE_DROPDOWN,
176     WIDX_VEHICLE_PREVIEW,
177     WIDX_VEHICLE_COLOUR_SCHEME,
178     WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN,
179     WIDX_VEHICLE_COLOUR_INDEX,
180     WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN,
181     WIDX_VEHICLE_MAIN_COLOUR,
182     WIDX_VEHICLE_ADDITIONAL_COLOUR_1,
183     WIDX_VEHICLE_ADDITIONAL_COLOUR_2,
184 
185     WIDX_PLAY_MUSIC = 14,
186     WIDX_MUSIC,
187     WIDX_MUSIC_DROPDOWN,
188 
189     WIDX_SAVE_TRACK_DESIGN = 14,
190     WIDX_SELECT_NEARBY_SCENERY,
191     WIDX_RESET_SELECTION,
192     WIDX_SAVE_DESIGN,
193     WIDX_CANCEL_DESIGN,
194 
195     WIDX_GRAPH = 14,
196     WIDX_GRAPH_VELOCITY,
197     WIDX_GRAPH_ALTITUDE,
198     WIDX_GRAPH_VERTICAL,
199     WIDX_GRAPH_LATERAL,
200 
201     WIDX_PRIMARY_PRICE_LABEL = 14,
202     WIDX_PRIMARY_PRICE,
203     WIDX_PRIMARY_PRICE_INCREASE,
204     WIDX_PRIMARY_PRICE_DECREASE,
205     WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK,
206     WIDX_SECONDARY_PRICE_LABEL,
207     WIDX_SECONDARY_PRICE,
208     WIDX_SECONDARY_PRICE_INCREASE,
209     WIDX_SECONDARY_PRICE_DECREASE,
210     WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK,
211 
212     WIDX_SHOW_GUESTS_THOUGHTS = 14,
213     WIDX_SHOW_GUESTS_ON_RIDE,
214     WIDX_SHOW_GUESTS_QUEUING
215 };
216 
217 constexpr int32_t RCT1_LIGHT_OFFSET = 4;
218 
219 #define MAIN_RIDE_WIDGETS \
220     WINDOW_SHIM(WINDOW_TITLE, WW, WH), \
221     MakeWidget({  0, 43}, {316, 137}, WindowWidgetType::Resize, WindowColour::Secondary), \
222     MakeTab   ({  3, 17}, STR_VIEW_OF_RIDE_ATTRACTION_TIP                ), \
223     MakeTab   ({ 34, 17}, STR_VEHICLE_DETAILS_AND_OPTIONS_TIP            ), \
224     MakeTab   ({ 65, 17}, STR_OPERATING_OPTIONS_TIP                      ), \
225     MakeTab   ({ 96, 17}, STR_MAINTENANCE_OPTIONS_TIP                    ), \
226     MakeTab   ({127, 17}, STR_COLOUR_SCHEME_OPTIONS_TIP                  ), \
227     MakeTab   ({158, 17}, STR_SOUND_AND_MUSIC_OPTIONS_TIP                ), \
228     MakeTab   ({189, 17}, STR_MEASUREMENTS_AND_TEST_DATA_TIP             ), \
229     MakeTab   ({220, 17}, STR_GRAPHS_TIP                                 ), \
230     MakeTab   ({251, 17}, STR_INCOME_AND_COSTS_TIP                       ), \
231     MakeTab   ({282, 17}, STR_CUSTOMER_INFORMATION_TIP                   )
232 
233 // 0x009ADC34
234 static rct_widget window_ride_main_widgets[] = {
235     MAIN_RIDE_WIDGETS,
236     MakeWidget({  3,  60}, {288, 107}, WindowWidgetType::Viewport,      WindowColour::Secondary, STR_VIEWPORT                                           ),
237     MakeWidget({ 35,  46}, {222,  12}, WindowWidgetType::DropdownMenu,      WindowColour::Secondary, 0xFFFFFFFF,                 STR_VIEW_SELECTION         ),
238     MakeWidget({245,  47}, { 11,  10}, WindowWidgetType::Button,        WindowColour::Secondary, STR_DROPDOWN_GLYPH,         STR_VIEW_SELECTION         ),
239     MakeWidget({  3, 167}, {288,  11}, WindowWidgetType::LabelCentred, WindowColour::Secondary                                                         ),
240     MakeWidget({291,  46}, { 24,  24}, WindowWidgetType::FlatBtn,       WindowColour::Secondary, 0xFFFFFFFF,                 STR_OPEN_CLOSE_OR_TEST_RIDE),
241     MakeWidget({291,  70}, { 24,  24}, WindowWidgetType::FlatBtn,       WindowColour::Secondary, SPR_CONSTRUCTION,           STR_CONSTRUCTION           ),
242     MakeWidget({291,  94}, { 24,  24}, WindowWidgetType::FlatBtn,       WindowColour::Secondary, SPR_RENAME,                 STR_NAME_RIDE_TIP          ),
243     MakeWidget({291, 118}, { 24,  24}, WindowWidgetType::FlatBtn,       WindowColour::Secondary, SPR_LOCATE,                 STR_LOCATE_SUBJECT_TIP     ),
244     MakeWidget({291, 142}, { 24,  24}, WindowWidgetType::FlatBtn,       WindowColour::Secondary, SPR_DEMOLISH,               STR_DEMOLISH_RIDE_TIP      ),
245     MakeWidget({296,  48}, { 14,  14}, WindowWidgetType::ImgBtn,        WindowColour::Secondary, SPR_G2_RCT1_CLOSE_BUTTON_0, STR_CLOSE_RIDE_TIP         ),
246     MakeWidget({296,  62}, { 14,  14}, WindowWidgetType::ImgBtn,        WindowColour::Secondary, SPR_G2_RCT1_TEST_BUTTON_0,  STR_SIMULATE_RIDE_TIP      ),
247     MakeWidget({296,  62}, { 14,  14}, WindowWidgetType::ImgBtn,        WindowColour::Secondary, SPR_G2_RCT1_TEST_BUTTON_0,  STR_TEST_RIDE_TIP          ),
248     MakeWidget({296,  76}, { 14,  14}, WindowWidgetType::ImgBtn,        WindowColour::Secondary, SPR_G2_RCT1_OPEN_BUTTON_0,  STR_OPEN_RIDE_TIP          ),
249     MakeWidget({  3, 180}, {305,  12}, WindowWidgetType::DropdownMenu,      WindowColour::Secondary, STR_ARG_6_STRINGID                                     ),
250     MakeWidget({297, 180}, { 11,  12}, WindowWidgetType::Button,        WindowColour::Secondary, STR_DROPDOWN_GLYPH                                     ),
251     WIDGETS_END,
252 };
253 
254 // 0x009ADDA8
255 static rct_widget window_ride_vehicle_widgets[] = {
256     MAIN_RIDE_WIDGETS,
257     MakeWidget        ({  7,  50}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary                                                    ),
258     MakeWidget        ({297,  51}, { 11, 10}, WindowWidgetType::Button,   WindowColour::Secondary, STR_DROPDOWN_GLYPH                                ),
259     MakeWidget        ({  7, 147}, {302, 43}, WindowWidgetType::Scroll,   WindowColour::Secondary, STR_EMPTY                                         ),
260     MakeSpinnerWidgets({  7, 196}, {145, 12}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_RIDE_VEHICLE_COUNT, STR_MAX_VEHICLES_TIP      ),
261     MakeSpinnerWidgets({164, 196}, {145, 12}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_1_CAR_PER_TRAIN,    STR_MAX_CARS_PER_TRAIN_TIP),
262     WIDGETS_END,
263 };
264 
265 // 0x009ADEFC
266 static rct_widget window_ride_operating_widgets[] = {
267     MAIN_RIDE_WIDGETS,
268     MakeSpinnerWidgets({157,  61}, {152, 12}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_ARG_18_STRINGID                                                                 ), // NB: 3 widgets
269     MakeSpinnerWidgets({157,  75}, {152, 12}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_LIFT_HILL_CHAIN_SPEED_VALUE                                                     ), // NB: 3 widgets
270     MakeWidget        ({  7, 109}, { 80, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_WAIT_FOR,                           STR_WAIT_FOR_PASSENGERS_BEFORE_DEPARTING_TIP),
271     MakeWidget        ({  7, 124}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary                                                                                      ),
272     MakeWidget        ({  7, 139}, {150, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_MINIMUM_WAITING_TIME,               STR_MINIMUM_LENGTH_BEFORE_DEPARTING_TIP     ),
273     MakeSpinnerWidgets({157, 139}, {152, 12}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_ARG_10_STRINGID                                                                 ), // NB: 3 widgets
274     MakeWidget        ({  7, 154}, {150, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_MAXIMUM_WAITING_TIME,               STR_MAXIMUM_LENGTH_BEFORE_DEPARTING_TIP     ),
275     MakeSpinnerWidgets({157, 154}, {152, 12}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_ARG_14_STRINGID                                                                 ), // NB: 3 widgets
276     MakeWidget        ({  7, 169}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SYNCHRONISE_WITH_ADJACENT_STATIONS, STR_SYNCHRONISE_WITH_ADJACENT_STATIONS_TIP  ),
277     MakeWidget        ({ 21,  61}, {129, 12}, WindowWidgetType::Label,    WindowColour::Secondary                                                                                      ),
278     MakeWidget        ({ 21,  75}, {129, 12}, WindowWidgetType::Label,    WindowColour::Secondary, STR_LIFT_HILL_CHAIN_SPEED                                                           ),
279     MakeWidget        ({  7,  47}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, 0xFFFFFFFF,                             STR_SELECT_OPERATING_MODE                   ),
280     MakeWidget        ({297,  48}, { 11, 10}, WindowWidgetType::Button,   WindowColour::Secondary, STR_DROPDOWN_GLYPH,                     STR_SELECT_OPERATING_MODE                   ),
281     MakeWidget        ({ 87, 109}, {222, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary                                                                                      ),
282     MakeWidget        ({297, 110}, { 11, 10}, WindowWidgetType::Button,   WindowColour::Secondary, STR_DROPDOWN_GLYPH                                                                  ),
283     MakeWidget        ({ 21,  89}, {129, 12}, WindowWidgetType::Label,    WindowColour::Secondary, STR_NUMBER_OF_CIRCUITS,                 STR_NUMBER_OF_CIRCUITS_TIP                  ),
284     MakeSpinnerWidgets({157,  89}, {152, 12}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_NUMBER_OF_CIRCUITS_VALUE                                                        ), // NB: 3 widgets
285     WIDGETS_END,
286 };
287 
288 // 0x009AE190
289 static rct_widget window_ride_maintenance_widgets[] = {
290     MAIN_RIDE_WIDGETS,
291     MakeWidget({107,  71}, {202, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY,          STR_SELECT_HOW_OFTEN_A_MECHANIC_SHOULD_CHECK_THIS_RIDE),
292     MakeWidget({297,  72}, { 11, 10}, WindowWidgetType::Button,   WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_HOW_OFTEN_A_MECHANIC_SHOULD_CHECK_THIS_RIDE),
293     MakeWidget({289, 108}, { 24, 24}, WindowWidgetType::FlatBtn,  WindowColour::Secondary, 0xFFFFFFFF,         STR_LOCATE_NEAREST_AVAILABLE_MECHANIC_TIP             ),
294     MakeWidget({265, 108}, { 24, 24}, WindowWidgetType::FlatBtn,  WindowColour::Secondary, SPR_CONSTRUCTION,   STR_REFURBISH_RIDE_TIP                                ),
295     MakeWidget({241, 108}, { 24, 24}, WindowWidgetType::FlatBtn,  WindowColour::Secondary, SPR_NO_ENTRY,       STR_DEBUG_FORCE_BREAKDOWN_TIP                         ),
296     WIDGETS_END,
297 };
298 
299 // 0x009AE2A4
300 static rct_widget window_ride_colour_widgets[] = {
301     MAIN_RIDE_WIDGETS,
302     MakeWidget({  3,  47}, { 68, 47}, WindowWidgetType::Spinner,   WindowColour::Secondary                                                                    ),
303     MakeWidget({ 74,  49}, {239, 12}, WindowWidgetType::DropdownMenu,  WindowColour::Secondary, STR_ARG_14_STRINGID                                               ),
304     MakeWidget({301,  50}, { 11, 10}, WindowWidgetType::Button,    WindowColour::Secondary, STR_DROPDOWN_GLYPH,  STR_COLOUR_SCHEME_TO_CHANGE_TIP              ),
305     MakeWidget({ 79,  74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF,          STR_SELECT_MAIN_COLOUR_TIP                   ),
306     MakeWidget({ 99,  74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF,          STR_SELECT_ADDITIONAL_COLOUR_1_TIP           ),
307     MakeWidget({119,  74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF,          STR_SELECT_SUPPORT_STRUCTURE_COLOUR_TIP      ),
308     MakeWidget({ 74,  49}, {239, 12}, WindowWidgetType::DropdownMenu,  WindowColour::Secondary                                                                    ),
309     MakeWidget({301,  50}, { 11, 10}, WindowWidgetType::Button,    WindowColour::Secondary, STR_DROPDOWN_GLYPH                                                ),
310     MakeWidget({289,  68}, { 24, 24}, WindowWidgetType::FlatBtn,   WindowColour::Secondary, SPR_PAINTBRUSH,      STR_PAINT_INDIVIDUAL_AREA_TIP                ),
311     MakeWidget({245, 101}, { 68, 47}, WindowWidgetType::Spinner,   WindowColour::Secondary                                                                    ),
312     MakeWidget({103, 103}, {139, 12}, WindowWidgetType::DropdownMenu,  WindowColour::Secondary, STR_EMPTY                                                         ),
313     MakeWidget({230, 104}, { 11, 10}, WindowWidgetType::Button,    WindowColour::Secondary, STR_DROPDOWN_GLYPH,  STR_SELECT_STYLE_OF_ENTRANCE_EXIT_STATION_TIP),
314     MakeWidget({  3, 157}, { 68, 47}, WindowWidgetType::Scroll,    WindowColour::Secondary, STR_EMPTY                                                         ),
315     MakeWidget({ 74, 157}, {239, 12}, WindowWidgetType::DropdownMenu,  WindowColour::Secondary, STR_ARG_6_STRINGID                                                ),
316     MakeWidget({301, 158}, { 11, 10}, WindowWidgetType::Button,    WindowColour::Secondary, STR_DROPDOWN_GLYPH,  STR_SELECT_VEHICLE_COLOUR_SCHEME_TIP         ),
317     MakeWidget({ 74, 173}, {239, 12}, WindowWidgetType::DropdownMenu,  WindowColour::Secondary                                                                    ),
318     MakeWidget({301, 174}, { 11, 10}, WindowWidgetType::Button,    WindowColour::Secondary, STR_DROPDOWN_GLYPH,  STR_SELECT_VEHICLE_TO_MODIFY_TIP             ),
319     MakeWidget({ 79, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF,          STR_SELECT_MAIN_COLOUR_TIP                   ),
320     MakeWidget({ 99, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF,          STR_SELECT_ADDITIONAL_COLOUR_1_TIP           ),
321     MakeWidget({119, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF,          STR_SELECT_ADDITIONAL_COLOUR_2_TIP           ),
322     WIDGETS_END,
323 };
324 
325 // 0x009AE4C8
326 static rct_widget window_ride_music_widgets[] = {
327     MAIN_RIDE_WIDGETS,
328     MakeWidget({  7, 47}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_PLAY_MUSIC,     STR_SELECT_MUSIC_TIP      ),
329     MakeWidget({  7, 62}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY                                     ),
330     MakeWidget({297, 63}, { 11, 10}, WindowWidgetType::Button,   WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_MUSIC_STYLE_TIP),
331     WIDGETS_END,
332 };
333 
334 // 0x009AE5DC
335 static rct_widget window_ride_measurements_widgets[] = {
336     MAIN_RIDE_WIDGETS,
337     MakeWidget({288, 194}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_FLOPPY,                STR_SAVE_TRACK_DESIGN),
338     MakeWidget({  4, 127}, {154, 14}, WindowWidgetType::Button,  WindowColour::Secondary, STR_SELECT_NEARBY_SCENERY                       ),
339     MakeWidget({158, 127}, {154, 14}, WindowWidgetType::Button,  WindowColour::Secondary, STR_RESET_SELECTION                             ),
340     MakeWidget({  4, 177}, {154, 14}, WindowWidgetType::Button,  WindowColour::Secondary, STR_DESIGN_SAVE                                 ),
341     MakeWidget({158, 177}, {154, 14}, WindowWidgetType::Button,  WindowColour::Secondary, STR_DESIGN_CANCEL                               ),
342     WIDGETS_END,
343 };
344 
345 // 0x009AE710
346 static rct_widget window_ride_graphs_widgets[] = {
347     MAIN_RIDE_WIDGETS,
348     MakeWidget({  3,  46}, {306, 112}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_HORIZONTAL,       STR_LOGGING_DATA_FROM_TIP                               ),
349     MakeWidget({  3, 163}, { 73,  14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_VELOCITY, STR_SHOW_GRAPH_OF_VELOCITY_AGAINST_TIME_TIP             ),
350     MakeWidget({ 76, 163}, { 73,  14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_ALTITUDE, STR_SHOW_GRAPH_OF_ALTITUDE_AGAINST_TIME_TIP             ),
351     MakeWidget({149, 163}, { 73,  14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_VERT_G,   STR_SHOW_GRAPH_OF_VERTICAL_ACCELERATION_AGAINST_TIME_TIP),
352     MakeWidget({222, 163}, { 73,  14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_LAT_G,    STR_SHOW_GRAPH_OF_LATERAL_ACCELERATION_AGAINST_TIME_TIP ),
353     WIDGETS_END,
354 };
355 
356 // 0x009AE844
357 static rct_widget window_ride_income_widgets[] = {
358     MAIN_RIDE_WIDGETS,
359     MakeWidget        ({ 19,  50}, {126, 14}, WindowWidgetType::Label,    WindowColour::Secondary                                                                    ),
360     MakeSpinnerWidgets({147,  50}, {162, 14}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_ARG_6_CURRENCY2DP                                             ), // NB: 3 widgets
361     MakeWidget        ({  5,  62}, {306, 13}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SAME_PRICE_THROUGHOUT_PARK, STR_SAME_PRICE_THROUGHOUT_PARK_TIP),
362     MakeWidget        ({ 19,  94}, {126, 14}, WindowWidgetType::Label,    WindowColour::Secondary                                                                    ),
363     MakeSpinnerWidgets({147,  94}, {162, 14}, WindowWidgetType::Spinner,  WindowColour::Secondary, STR_RIDE_SECONDARY_PRICE_VALUE                                    ), // NB: 3 widgets
364     MakeWidget        ({  5, 106}, {306, 13}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SAME_PRICE_THROUGHOUT_PARK, STR_SAME_PRICE_THROUGHOUT_PARK_TIP),
365     WIDGETS_END,
366 };
367 
368 // 0x009AE9C8
369 static rct_widget window_ride_customer_widgets[] = {
370     MAIN_RIDE_WIDGETS,
371     MakeWidget({289,  54}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_SHOW_GUESTS_THOUGHTS_ABOUT_THIS_RIDE_ATTRACTION, STR_SHOW_GUESTS_THOUGHTS_ABOUT_THIS_RIDE_ATTRACTION_TIP),
372     MakeWidget({289,  78}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_SHOW_GUESTS_ON_THIS_RIDE_ATTRACTION,             STR_SHOW_GUESTS_ON_THIS_RIDE_ATTRACTION_TIP            ),
373     MakeWidget({289, 102}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_SHOW_GUESTS_QUEUING_FOR_THIS_RIDE_ATTRACTION,    STR_SHOW_GUESTS_QUEUING_FOR_THIS_RIDE_ATTRACTION_TIP   ),
374     WIDGETS_END,
375 };
376 
377 static rct_widget *window_ride_page_widgets[] = {
378     window_ride_main_widgets,
379     window_ride_vehicle_widgets,
380     window_ride_operating_widgets,
381     window_ride_maintenance_widgets,
382     window_ride_colour_widgets,
383     window_ride_music_widgets,
384     window_ride_measurements_widgets,
385     window_ride_graphs_widgets,
386     window_ride_income_widgets,
387     window_ride_customer_widgets,
388 };
389 
390 #define MAIN_RIDE_ENABLED_WIDGETS \
391     (1ULL << WIDX_CLOSE) | \
392     (1ULL << WIDX_TAB_1) | \
393     (1ULL << WIDX_TAB_2) | \
394     (1ULL << WIDX_TAB_3) | \
395     (1ULL << WIDX_TAB_4) | \
396     (1ULL << WIDX_TAB_5) | \
397     (1ULL << WIDX_TAB_6) | \
398     (1ULL << WIDX_TAB_7) | \
399     (1ULL << WIDX_TAB_8) | \
400     (1ULL << WIDX_TAB_9) | \
401     (1ULL << WIDX_TAB_10)
402 
403 static constexpr const uint64_t window_ride_page_enabled_widgets[] = {
404     MAIN_RIDE_ENABLED_WIDGETS |
405         (1ULL << WIDX_VIEW) |
406         (1ULL << WIDX_VIEW_DROPDOWN) |
407         (1ULL << WIDX_OPEN) |
408         (1ULL << WIDX_CONSTRUCTION) |
409         (1ULL << WIDX_RENAME) |
410         (1ULL << WIDX_LOCATE) |
411         (1ULL << WIDX_DEMOLISH) |
412         (1ULL << WIDX_CLOSE_LIGHT) |
413         (1ULL << WIDX_SIMULATE_LIGHT) |
414         (1ULL << WIDX_TEST_LIGHT) |
415         (1ULL << WIDX_OPEN_LIGHT) |
416         (1ULL << WIDX_RIDE_TYPE) |
417         (1ULL << WIDX_RIDE_TYPE_DROPDOWN),
418     MAIN_RIDE_ENABLED_WIDGETS |
419         (1ULL << WIDX_VEHICLE_TYPE) |
420         (1ULL << WIDX_VEHICLE_TYPE_DROPDOWN) |
421         (1ULL << WIDX_VEHICLE_TRAINS) |
422         (1ULL << WIDX_VEHICLE_TRAINS_INCREASE) |
423         (1ULL << WIDX_VEHICLE_TRAINS_DECREASE) |
424         (1ULL << WIDX_VEHICLE_CARS_PER_TRAIN) |
425         (1ULL << WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE) |
426         (1ULL << WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE),
427     MAIN_RIDE_ENABLED_WIDGETS |
428         (1ULL << WIDX_MODE_TWEAK_INCREASE) |
429         (1ULL << WIDX_MODE_TWEAK_DECREASE) |
430         (1ULL << WIDX_LIFT_HILL_SPEED_INCREASE) |
431         (1ULL << WIDX_LIFT_HILL_SPEED_DECREASE) |
432         (1ULL << WIDX_LOAD_CHECKBOX) |
433         (1ULL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX) |
434         (1ULL << WIDX_MINIMUM_LENGTH_CHECKBOX) |
435         (1ULL << WIDX_MINIMUM_LENGTH_INCREASE) |
436         (1ULL << WIDX_MINIMUM_LENGTH_DECREASE) |
437         (1ULL << WIDX_MAXIMUM_LENGTH_CHECKBOX) |
438         (1ULL << WIDX_MAXIMUM_LENGTH_INCREASE) |
439         (1ULL << WIDX_MAXIMUM_LENGTH_DECREASE) |
440         (1ULL << WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX) |
441         (1ULL << WIDX_MODE) |
442         (1ULL << WIDX_MODE_DROPDOWN) |
443         (1ULL << WIDX_LOAD) |
444         (1ULL << WIDX_LOAD_DROPDOWN) |
445         (1ULL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE) |
446         (1ULL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE),
447     MAIN_RIDE_ENABLED_WIDGETS |
448         (1ULL << WIDX_INSPECTION_INTERVAL) |
449         (1ULL << WIDX_INSPECTION_INTERVAL_DROPDOWN) |
450         (1ULL << WIDX_LOCATE_MECHANIC) |
451         (1ULL << WIDX_REFURBISH_RIDE) |
452         (1ULL << WIDX_FORCE_BREAKDOWN),
453     MAIN_RIDE_ENABLED_WIDGETS |
454         (1ULL << WIDX_TRACK_COLOUR_SCHEME_DROPDOWN) |
455         (1ULL << WIDX_TRACK_MAIN_COLOUR) |
456         (1ULL << WIDX_TRACK_ADDITIONAL_COLOUR) |
457         (1ULL << WIDX_TRACK_SUPPORT_COLOUR) |
458         (1ULL << WIDX_MAZE_STYLE) |
459         (1ULL << WIDX_MAZE_STYLE_DROPDOWN) |
460         (1ULL << WIDX_PAINT_INDIVIDUAL_AREA) |
461         (1ULL << WIDX_ENTRANCE_STYLE) |
462         (1ULL << WIDX_ENTRANCE_STYLE_DROPDOWN) |
463         (1ULL << WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN) |
464         (1ULL << WIDX_VEHICLE_COLOUR_INDEX) |
465         (1ULL << WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN) |
466         (1ULL << WIDX_VEHICLE_MAIN_COLOUR) |
467         (1ULL << WIDX_VEHICLE_ADDITIONAL_COLOUR_1) |
468         (1ULL << WIDX_VEHICLE_ADDITIONAL_COLOUR_2),
469     MAIN_RIDE_ENABLED_WIDGETS |
470         (1ULL << WIDX_PLAY_MUSIC) |
471         (1ULL << WIDX_MUSIC) |
472         (1ULL << WIDX_MUSIC_DROPDOWN),
473     MAIN_RIDE_ENABLED_WIDGETS |
474         (1ULL << WIDX_SAVE_TRACK_DESIGN) |
475         (1ULL << WIDX_SELECT_NEARBY_SCENERY) |
476         (1ULL << WIDX_RESET_SELECTION) |
477         (1ULL << WIDX_SAVE_DESIGN) |
478         (1ULL << WIDX_CANCEL_DESIGN),
479     MAIN_RIDE_ENABLED_WIDGETS |
480         (1ULL << WIDX_GRAPH_VELOCITY) |
481         (1ULL << WIDX_GRAPH_ALTITUDE) |
482         (1ULL << WIDX_GRAPH_VERTICAL) |
483         (1ULL << WIDX_GRAPH_LATERAL),
484     MAIN_RIDE_ENABLED_WIDGETS |
485         (1ULL << WIDX_PRIMARY_PRICE) |
486         (1ULL << WIDX_PRIMARY_PRICE_INCREASE) |
487         (1ULL << WIDX_PRIMARY_PRICE_DECREASE) |
488         (1ULL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK) |
489         (1ULL << WIDX_SECONDARY_PRICE) |
490         (1ULL << WIDX_SECONDARY_PRICE_INCREASE) |
491         (1ULL << WIDX_SECONDARY_PRICE_DECREASE) |
492         (1ULL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK),
493     MAIN_RIDE_ENABLED_WIDGETS |
494         (1ULL << WIDX_SHOW_GUESTS_THOUGHTS) |
495         (1ULL << WIDX_SHOW_GUESTS_ON_RIDE) |
496         (1ULL << WIDX_SHOW_GUESTS_QUEUING),
497 };
498 
499 static constexpr const uint64_t window_ride_page_hold_down_widgets[] = {
500     0,
501     (1ULL << WIDX_VEHICLE_TRAINS_INCREASE) |
502         (1ULL << WIDX_VEHICLE_TRAINS_DECREASE) |
503         (1ULL << WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE) |
504         (1ULL << WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE),
505     (1ULL << WIDX_MODE_TWEAK_INCREASE) |
506         (1ULL << WIDX_MODE_TWEAK_DECREASE) |
507         (1ULL << WIDX_LIFT_HILL_SPEED_INCREASE) |
508         (1ULL << WIDX_LIFT_HILL_SPEED_DECREASE) |
509         (1ULL << WIDX_MINIMUM_LENGTH_INCREASE) |
510         (1ULL << WIDX_MINIMUM_LENGTH_DECREASE) |
511         (1ULL << WIDX_MAXIMUM_LENGTH_INCREASE) |
512         (1ULL << WIDX_MAXIMUM_LENGTH_DECREASE) |
513         (1ULL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE) |
514         (1ULL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE),
515     0,
516     0,
517     0,
518     0,
519     0,
520     (1ULL << WIDX_PRIMARY_PRICE_INCREASE) |
521         (1ULL << WIDX_PRIMARY_PRICE_DECREASE) |
522         (1ULL << WIDX_SECONDARY_PRICE_INCREASE) |
523         (1ULL << WIDX_SECONDARY_PRICE_DECREASE),
524     0,
525 };
526 // clang-format on
527 
528 #pragma endregion
529 
530 #pragma region Events
531 
532 static void window_ride_init_viewport(rct_window* w);
533 
534 static void window_ride_main_mouseup(rct_window* w, rct_widgetindex widgetIndex);
535 static void window_ride_main_resize(rct_window* w);
536 static void window_ride_main_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
537 static void window_ride_main_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
538 static void window_ride_main_update(rct_window* w);
539 static void window_ride_main_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text);
540 static void window_ride_main_viewport_rotate(rct_window* w);
541 static void window_ride_main_invalidate(rct_window* w);
542 static void window_ride_main_paint(rct_window* w, rct_drawpixelinfo* dpi);
543 static void window_ride_main_follow_ride(rct_window* w);
544 
545 static void window_ride_vehicle_mouseup(rct_window* w, rct_widgetindex widgetIndex);
546 static void window_ride_vehicle_resize(rct_window* w);
547 static void window_ride_vehicle_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
548 static void window_ride_vehicle_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
549 static void window_ride_vehicle_update(rct_window* w);
550 static OpenRCT2String window_ride_vehicle_tooltip(
551     rct_window* const w, const rct_widgetindex widgetIndex, rct_string_id fallback);
552 static void window_ride_vehicle_invalidate(rct_window* w);
553 static void window_ride_vehicle_paint(rct_window* w, rct_drawpixelinfo* dpi);
554 static void window_ride_vehicle_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex);
555 
556 static void window_ride_operating_mouseup(rct_window* w, rct_widgetindex widgetIndex);
557 static void window_ride_operating_resize(rct_window* w);
558 static void window_ride_operating_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
559 static void window_ride_operating_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
560 static void window_ride_operating_update(rct_window* w);
561 static void window_ride_operating_invalidate(rct_window* w);
562 static void window_ride_operating_paint(rct_window* w, rct_drawpixelinfo* dpi);
563 
564 static void window_ride_maintenance_mouseup(rct_window* w, rct_widgetindex widgetIndex);
565 static void window_ride_maintenance_resize(rct_window* w);
566 static void window_ride_maintenance_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
567 static void window_ride_maintenance_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
568 static void window_ride_maintenance_update(rct_window* w);
569 static void window_ride_maintenance_invalidate(rct_window* w);
570 static void window_ride_maintenance_paint(rct_window* w, rct_drawpixelinfo* dpi);
571 
572 static void window_ride_colour_close(rct_window* w);
573 static void window_ride_colour_mouseup(rct_window* w, rct_widgetindex widgetIndex);
574 static void window_ride_colour_resize(rct_window* w);
575 static void window_ride_colour_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
576 static void window_ride_colour_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
577 static void window_ride_colour_update(rct_window* w);
578 static void window_ride_colour_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
579 static void window_ride_colour_tooldrag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
580 static void window_ride_colour_invalidate(rct_window* w);
581 static void window_ride_colour_paint(rct_window* w, rct_drawpixelinfo* dpi);
582 static void window_ride_colour_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex);
583 
584 static void window_ride_music_mouseup(rct_window* w, rct_widgetindex widgetIndex);
585 static void window_ride_music_resize(rct_window* w);
586 static void window_ride_music_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
587 static void window_ride_music_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
588 static void window_ride_music_update(rct_window* w);
589 static void window_ride_music_invalidate(rct_window* w);
590 static void window_ride_music_paint(rct_window* w, rct_drawpixelinfo* dpi);
591 
592 static void window_ride_measurements_close(rct_window* w);
593 static void window_ride_measurements_mouseup(rct_window* w, rct_widgetindex widgetIndex);
594 static void window_ride_measurements_resize(rct_window* w);
595 static void window_ride_measurements_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
596 static void window_ride_measurements_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
597 static void window_ride_measurements_update(rct_window* w);
598 static void window_ride_measurements_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
599 static void window_ride_measurements_tooldrag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
600 static void window_ride_measurements_toolabort(rct_window* w, rct_widgetindex widgetIndex);
601 static void window_ride_measurements_invalidate(rct_window* w);
602 static void window_ride_measurements_paint(rct_window* w, rct_drawpixelinfo* dpi);
603 
604 static void window_ride_graphs_mouseup(rct_window* w, rct_widgetindex widgetIndex);
605 static void window_ride_graphs_resize(rct_window* w);
606 static void window_ride_graphs_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
607 static void window_ride_graphs_update(rct_window* w);
608 static void window_ride_graphs_scrollgetheight(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height);
609 static void window_ride_graphs_15(rct_window* w, int32_t scrollIndex, int32_t scrollAreaType);
610 static OpenRCT2String window_ride_graphs_tooltip(
611     rct_window* w, const rct_widgetindex widgetIndex, const rct_string_id fallback);
612 static void window_ride_graphs_invalidate(rct_window* w);
613 static void window_ride_graphs_paint(rct_window* w, rct_drawpixelinfo* dpi);
614 static void window_ride_graphs_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex);
615 
616 static void window_ride_income_mouseup(rct_window* w, rct_widgetindex widgetIndex);
617 static void window_ride_income_resize(rct_window* w);
618 static void window_ride_income_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
619 static void window_ride_income_update(rct_window* w);
620 static void window_ride_income_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text);
621 static void window_ride_income_invalidate(rct_window* w);
622 static void window_ride_income_paint(rct_window* w, rct_drawpixelinfo* dpi);
623 static bool window_ride_income_can_modify_primary_price(rct_window* w);
624 
625 static void window_ride_customer_mouseup(rct_window* w, rct_widgetindex widgetIndex);
626 static void window_ride_customer_resize(rct_window* w);
627 static void window_ride_customer_update(rct_window* w);
628 static void window_ride_customer_invalidate(rct_window* w);
629 static void window_ride_customer_paint(rct_window* w, rct_drawpixelinfo* dpi);
630 
631 static void window_ride_set_page(rct_window* w, int32_t page);
632 
633 // 0x0098DFD4
__anonabbf27ab0302(auto& events) 634 static rct_window_event_list window_ride_main_events([](auto& events) {
635     events.mouse_up = &window_ride_main_mouseup;
636     events.resize = &window_ride_main_resize;
637     events.mouse_down = &window_ride_main_mousedown;
638     events.dropdown = &window_ride_main_dropdown;
639     events.update = &window_ride_main_update;
640     events.text_input = &window_ride_main_textinput;
641     events.viewport_rotate = &window_ride_main_viewport_rotate;
642     events.invalidate = &window_ride_main_invalidate;
643     events.paint = &window_ride_main_paint;
644 });
645 
646 // 0x0098E204
__anonabbf27ab0402(auto& events) 647 static rct_window_event_list window_ride_vehicle_events([](auto& events) {
648     events.mouse_up = &window_ride_vehicle_mouseup;
649     events.resize = &window_ride_vehicle_resize;
650     events.mouse_down = &window_ride_vehicle_mousedown;
651     events.dropdown = &window_ride_vehicle_dropdown;
652     events.update = &window_ride_vehicle_update;
653     events.tooltip = &window_ride_vehicle_tooltip;
654     events.invalidate = &window_ride_vehicle_invalidate;
655     events.paint = &window_ride_vehicle_paint;
656     events.scroll_paint = &window_ride_vehicle_scrollpaint;
657 });
658 
659 // 0x0098E0B4
__anonabbf27ab0502(auto& events) 660 static rct_window_event_list window_ride_operating_events([](auto& events) {
661     events.mouse_up = &window_ride_operating_mouseup;
662     events.resize = &window_ride_operating_resize;
663     events.mouse_down = &window_ride_operating_mousedown;
664     events.dropdown = &window_ride_operating_dropdown;
665     events.update = &window_ride_operating_update;
666     events.invalidate = &window_ride_operating_invalidate;
667     events.paint = &window_ride_operating_paint;
668 });
669 
670 // 0x0098E124
__anonabbf27ab0602(auto& events) 671 static rct_window_event_list window_ride_maintenance_events([](auto& events) {
672     events.mouse_up = &window_ride_maintenance_mouseup;
673     events.resize = &window_ride_maintenance_resize;
674     events.mouse_down = &window_ride_maintenance_mousedown;
675     events.dropdown = &window_ride_maintenance_dropdown;
676     events.update = &window_ride_maintenance_update;
677     events.invalidate = &window_ride_maintenance_invalidate;
678     events.paint = &window_ride_maintenance_paint;
679 });
680 
681 // 0x0098E044
__anonabbf27ab0702(auto& events) 682 static rct_window_event_list window_ride_colour_events([](auto& events) {
683     events.close = &window_ride_colour_close;
684     events.mouse_up = &window_ride_colour_mouseup;
685     events.resize = &window_ride_colour_resize;
686     events.mouse_down = &window_ride_colour_mousedown;
687     events.dropdown = &window_ride_colour_dropdown;
688     events.update = &window_ride_colour_update;
689     events.tool_down = &window_ride_colour_tooldown;
690     events.tool_drag = &window_ride_colour_tooldrag;
691     events.invalidate = &window_ride_colour_invalidate;
692     events.paint = &window_ride_colour_paint;
693     events.scroll_paint = &window_ride_colour_scrollpaint;
694 });
695 
696 // 0x0098E194
__anonabbf27ab0802(auto& events) 697 static rct_window_event_list window_ride_music_events([](auto& events) {
698     events.mouse_up = &window_ride_music_mouseup;
699     events.resize = &window_ride_music_resize;
700     events.mouse_down = &window_ride_music_mousedown;
701     events.dropdown = &window_ride_music_dropdown;
702     events.update = &window_ride_music_update;
703     events.invalidate = &window_ride_music_invalidate;
704     events.paint = &window_ride_music_paint;
705 });
706 
707 // 0x0098DE14
__anonabbf27ab0902(auto& events) 708 static rct_window_event_list window_ride_measurements_events([](auto& events) {
709     events.close = &window_ride_measurements_close;
710     events.mouse_up = &window_ride_measurements_mouseup;
711     events.resize = &window_ride_measurements_resize;
712     events.mouse_down = &window_ride_measurements_mousedown;
713     events.dropdown = &window_ride_measurements_dropdown;
714     events.update = &window_ride_measurements_update;
715     events.tool_down = &window_ride_measurements_tooldown;
716     events.tool_drag = &window_ride_measurements_tooldrag;
717     events.tool_abort = &window_ride_measurements_toolabort;
718     events.invalidate = &window_ride_measurements_invalidate;
719     events.paint = &window_ride_measurements_paint;
720 });
721 
722 // 0x0098DF64
__anonabbf27ab0a02(auto& events) 723 static rct_window_event_list window_ride_graphs_events([](auto& events) {
724     events.mouse_up = &window_ride_graphs_mouseup;
725     events.resize = &window_ride_graphs_resize;
726     events.mouse_down = &window_ride_graphs_mousedown;
727     events.update = &window_ride_graphs_update;
728     events.get_scroll_size = &window_ride_graphs_scrollgetheight;
729     events.unknown_15 = &window_ride_graphs_15;
730     events.tooltip = &window_ride_graphs_tooltip;
731     events.invalidate = &window_ride_graphs_invalidate;
732     events.paint = &window_ride_graphs_paint;
733     events.scroll_paint = &window_ride_graphs_scrollpaint;
734 });
735 
736 // 0x0098DEF4
__anonabbf27ab0b02(auto& events) 737 static rct_window_event_list window_ride_income_events([](auto& events) {
738     events.mouse_up = &window_ride_income_mouseup;
739     events.resize = &window_ride_income_resize;
740     events.mouse_down = &window_ride_income_mousedown;
741     events.update = &window_ride_income_update;
742     events.text_input = &window_ride_income_textinput;
743     events.invalidate = &window_ride_income_invalidate;
744     events.paint = &window_ride_income_paint;
745 });
746 
747 // 0x0098DE84
__anonabbf27ab0c02(auto& events) 748 static rct_window_event_list window_ride_customer_events([](auto& events) {
749     events.mouse_up = &window_ride_customer_mouseup;
750     events.resize = &window_ride_customer_resize;
751     events.update = &window_ride_customer_update;
752     events.invalidate = &window_ride_customer_invalidate;
753     events.paint = &window_ride_customer_paint;
754 });
755 
756 // clang-format off
757 static rct_window_event_list *window_ride_page_events[] = {
758     &window_ride_main_events,
759     &window_ride_vehicle_events,
760     &window_ride_operating_events,
761     &window_ride_maintenance_events,
762     &window_ride_colour_events,
763     &window_ride_music_events,
764     &window_ride_measurements_events,
765     &window_ride_graphs_events,
766     &window_ride_income_events,
767     &window_ride_customer_events,
768 };
769 // clang-format on
770 
771 #pragma endregion
772 
773 static bool _collectTrackDesignScenery = false;
774 static int32_t _lastSceneryX = 0;
775 static int32_t _lastSceneryY = 0;
776 static std::unique_ptr<TrackDesign> _trackDesign;
777 
778 // Cached overall view for each ride
779 // (Re)calculated when the ride window is opened
780 struct ride_overall_view
781 {
782     CoordsXYZ loc;
783     uint8_t zoom;
784 };
785 
786 static std::vector<ride_overall_view> ride_overall_views = {};
787 
788 static constexpr const int32_t window_ride_tab_animation_divisor[] = {
789     0, 0, 2, 2, 4, 2, 8, 8, 2, 0,
790 };
791 static constexpr const int32_t window_ride_tab_animation_frames[] = {
792     0, 0, 4, 16, 8, 16, 8, 8, 8, 0,
793 };
794 
795 // clang-format off
796 static constexpr const rct_string_id RatingNames[] = {
797     STR_RATING_LOW,
798     STR_RATING_MEDIUM,
799     STR_RATING_HIGH,
800     STR_RATING_VERY_HIGH,
801     STR_RATING_EXTREME,
802     STR_RATING_ULTRA_EXTREME,
803 };
804 
805 static constexpr const rct_string_id RideBreakdownReasonNames[] = {
806     STR_RIDE_BREAKDOWN_SAFETY_CUT_OUT ,
807     STR_RIDE_BREAKDOWN_RESTRAINTS_STUCK_CLOSED,
808     STR_RIDE_BREAKDOWN_RESTRAINTS_STUCK_OPEN,
809     STR_RIDE_BREAKDOWN_DOORS_STUCK_CLOSED,
810     STR_RIDE_BREAKDOWN_DOORS_STUCK_OPEN,
811     STR_RIDE_BREAKDOWN_VEHICLE_MALFUNCTION,
812     STR_RIDE_BREAKDOWN_BRAKES_FAILURE,
813     STR_RIDE_BREAKDOWN_CONTROL_FAILURE,
814 };
815 
816 const rct_string_id ColourSchemeNames[4] = {
817     STR_MAIN_COLOUR_SCHEME,
818     STR_ALTERNATIVE_COLOUR_SCHEME_1,
819     STR_ALTERNATIVE_COLOUR_SCHEME_2,
820     STR_ALTERNATIVE_COLOUR_SCHEME_3,
821 };
822 
823 static constexpr const rct_string_id VehicleLoadNames[] = {
824     STR_QUARTER_LOAD,
825     STR_HALF_LOAD,
826     STR_THREE_QUARTER_LOAD,
827     STR_FULL_LOAD,
828     STR_ANY_LOAD,
829 };
830 
831 static constexpr const rct_string_id VehicleColourSchemeNames[] = {
832     STR_ALL_VEHICLES_IN_SAME_COLOURS ,
833     STR_DIFFERENT_COLOURS_PER ,
834     STR_DIFFERENT_COLOURS_PER_VEHICLE ,
835 };
836 
837 static constexpr const rct_string_id VehicleStatusNames[] = {
838     STR_MOVING_TO_END_OF,           // Vehicle::Status::MovingToEndOfStation
839     STR_WAITING_FOR_PASSENGERS_AT,  // Vehicle::Status::WaitingForPassengers
840     STR_WAITING_TO_DEPART,          // Vehicle::Status::WaitingToDepart
841     STR_DEPARTING,                  // Vehicle::Status::Departing
842     STR_TRAVELLING_AT_0,            // Vehicle::Status::Travelling
843     STR_ARRIVING_AT,                // Vehicle::Status::Arriving
844     STR_UNLOADING_PASSENGERS_AT,    // Vehicle::Status::UnloadingPassengers
845     STR_TRAVELLING_AT_1,            // Vehicle::Status::TravellingBoat
846     STR_CRASHING,                   // Vehicle::Status::Crashing
847     STR_CRASHED_0,                  // Vehicle::Status::Crashed
848     STR_TRAVELLING_AT_2,            // Vehicle::Status::TravellingDodgems
849     STR_SWINGING,                   // Vehicle::Status::Swinging
850     STR_ROTATING_0,                 // Vehicle::Status::Rotating
851     STR_ROTATING_1,                 // Vehicle::Status::FerrisWheelRotating
852     STR_OPERATING_0,                // Vehicle::Status::SimulatorOperating
853     STR_SHOWING_FILM,               // Vehicle::Status::ShowingFilm
854     STR_ROTATING_2,                 // Vehicle::Status::SpaceRingsOperating
855     STR_OPERATING_1,                // Vehicle::Status::TopSpinOperating
856     STR_OPERATING_2,                // Vehicle::Status::HauntedHouseOperating
857     STR_DOING_CIRCUS_SHOW,          // Vehicle::Status::DoingCircusShow
858     STR_OPERATING_3,                // Vehicle::Status::CrookedHouseOperating
859     STR_WAITING_FOR_CABLE_LIFT,     // Vehicle::Status::WaitingForCableLift
860     STR_TRAVELLING_AT_3,            // Vehicle::Status::TravellingCableLift
861     STR_STOPPING_0,                 // Vehicle::Status::Stopping
862     STR_WAITING_FOR_PASSENGERS,     // Vehicle::Status::WaitingForPassengers17
863     STR_WAITING_TO_START,           // Vehicle::Status::WaitingToStart
864     STR_STARTING,                   // Vehicle::Status::Starting
865     STR_OPERATING,                  // Vehicle::Status::Operating1A
866     STR_STOPPING_1,                 // Vehicle::Status::Stopping1B
867     STR_UNLOADING_PASSENGERS,       // Vehicle::Status::UnloadingPassengers1C
868     STR_STOPPED_BY_BLOCK_BRAKES,    // Vehicle::Status::StoppedByBlockBrakes
869 };
870 
871 static constexpr const rct_string_id SingleSessionVehicleStatusNames[] = {
872     STR_STOPPING_0,                 // Vehicle::Status::MovingToEndOfStation
873     STR_WAITING_FOR_PASSENGERS,     // Vehicle::Status::WaitingForPassengers
874     STR_WAITING_TO_START,           // Vehicle::Status::WaitingToDepart
875     STR_STARTING,                   // Vehicle::Status::Departing
876     STR_OPERATING,                  // Vehicle::Status::Travelling
877     STR_STOPPING_1,                 // Vehicle::Status::Arriving
878     STR_UNLOADING_PASSENGERS,       // Vehicle::Status::UnloadingPassengers
879 };
880 // clang-format on
881 
882 struct window_ride_maze_design_option
883 {
884     rct_string_id text;
885     uint32_t sprite;
886 };
887 
888 static constexpr const window_ride_maze_design_option MazeOptions[] = {
889     { STR_RIDE_DESIGN_MAZE_BRICK_WALLS, SPR_RIDE_DESIGN_PREVIEW_MAZE_BRICK_WALLS },
890     { STR_RIDE_DESIGN_MAZE_HEDGES, SPR_RIDE_DESIGN_PREVIEW_MAZE_HEDGES },
891     { STR_RIDE_DESIGN_MAZE_ICE_BLOCKS, SPR_RIDE_DESIGN_PREVIEW_MAZE_ICE_BLOCKS },
892     { STR_RIDE_DESIGN_MAZE_WOODEN_FENCES, SPR_RIDE_DESIGN_PREVIEW_MAZE_WOODEN_FENCES },
893 };
894 
895 struct rct_window_graphs_y_axis
896 {
897     uint8_t interval;
898     int8_t unit;
899     int8_t unit_interval;
900     rct_string_id label;
901 };
902 
903 /** rct2: 0x0098DD98 */
904 static constexpr const rct_window_graphs_y_axis window_graphs_y_axi[] = {
905     { 11, 0, 10, STR_RIDE_STATS_VELOCITY_FORMAT }, // GRAPH_VELOCITY
906     { 10, 0, 15, STR_RIDE_STATS_ALTITUDE_FORMAT }, // GRAPH_ALTITUDE
907     { 13, -3, 1, STR_RIDE_STATS_G_FORCE_FORMAT },  // GRAPH_VERTICAL
908     { 13, -4, 1, STR_RIDE_STATS_G_FORCE_FORMAT },  // GRAPH_LATERAL
909 };
910 
911 static constexpr auto RIDE_G_FORCES_RED_POS_VERTICAL = FIXED_2DP(5, 00);
912 static constexpr auto RIDE_G_FORCES_RED_NEG_VERTICAL = -FIXED_2DP(2, 00);
913 static constexpr auto RIDE_G_FORCES_RED_LATERAL = FIXED_2DP(2, 80);
914 
915 // Used for sorting the ride type cheat dropdown.
916 struct RideTypeLabel
917 {
918     uint8_t ride_type_id;
919     rct_string_id label_id;
920     const char* label_string;
921 };
922 
923 static int32_t RideDropdownDataLanguage = LANGUAGE_UNDEFINED;
924 static std::vector<RideTypeLabel> RideDropdownData;
925 
926 // Used for sorting the vehicle type dropdown.
927 struct VehicleTypeLabel
928 {
929     int32_t subtype_id;
930     rct_string_id label_id;
931     const char* label_string;
932 };
933 
934 static int32_t VehicleDropdownDataLanguage = LANGUAGE_UNDEFINED;
935 static rct_ride_entry* VehicleDropdownRideType = nullptr;
936 static bool VehicleDropdownExpanded = false;
937 static std::vector<VehicleTypeLabel> VehicleDropdownData;
938 
window_ride_draw_tab_image(rct_drawpixelinfo * dpi,rct_window * w,int32_t page,int32_t spriteIndex)939 static void window_ride_draw_tab_image(rct_drawpixelinfo* dpi, rct_window* w, int32_t page, int32_t spriteIndex)
940 {
941     rct_widgetindex widgetIndex = WIDX_TAB_1 + page;
942 
943     if (!(w->disabled_widgets & (1LL << widgetIndex)))
944     {
945         if (w->page == page)
946         {
947             int32_t frame = w->frame_no / window_ride_tab_animation_divisor[w->page];
948             spriteIndex += (frame % window_ride_tab_animation_frames[w->page]);
949         }
950 
951         const auto& widget = w->widgets[widgetIndex];
952         gfx_draw_sprite(dpi, ImageId(spriteIndex), w->windowPos + ScreenCoordsXY{ widget.left, widget.top });
953     }
954 }
955 
956 /**
957  *
958  *  rct2: 0x006B2E88
959  */
window_ride_draw_tab_main(rct_drawpixelinfo * dpi,rct_window * w)960 static void window_ride_draw_tab_main(rct_drawpixelinfo* dpi, rct_window* w)
961 {
962     rct_widgetindex widgetIndex = WIDX_TAB_1 + WINDOW_RIDE_PAGE_MAIN;
963     if (!(w->disabled_widgets & (1LL << widgetIndex)))
964     {
965         auto ride = get_ride(w->rideId);
966         if (ride != nullptr)
967         {
968             int32_t spriteIndex = 0;
969             switch (ride->GetClassification())
970             {
971                 case RideClassification::Ride:
972                     spriteIndex = SPR_TAB_RIDE_0;
973                     if (w->page == WINDOW_RIDE_PAGE_MAIN)
974                         spriteIndex += (w->frame_no / 4) % 16;
975                     break;
976                 case RideClassification::ShopOrStall:
977                     spriteIndex = SPR_TAB_SHOPS_AND_STALLS_0;
978                     if (w->page == WINDOW_RIDE_PAGE_MAIN)
979                         spriteIndex += (w->frame_no / 4) % 16;
980                     break;
981                 case RideClassification::KioskOrFacility:
982                     spriteIndex = SPR_TAB_KIOSKS_AND_FACILITIES_0;
983                     if (w->page == WINDOW_RIDE_PAGE_MAIN)
984                         spriteIndex += (w->frame_no / 4) % 8;
985                     break;
986             }
987 
988             const auto& widget = w->widgets[widgetIndex];
989             gfx_draw_sprite(dpi, ImageId(spriteIndex), w->windowPos + ScreenCoordsXY{ widget.left, widget.top });
990         }
991     }
992 }
993 
994 /**
995  *
996  *  rct2: 0x006B2B68
997  */
window_ride_draw_tab_vehicle(rct_drawpixelinfo * dpi,rct_window * w)998 static void window_ride_draw_tab_vehicle(rct_drawpixelinfo* dpi, rct_window* w)
999 {
1000     rct_widgetindex widgetIndex = WIDX_TAB_1 + WINDOW_RIDE_PAGE_VEHICLE;
1001     const auto& widget = w->widgets[widgetIndex];
1002 
1003     if (!(w->disabled_widgets & (1LL << widgetIndex)))
1004     {
1005         auto screenCoords = ScreenCoordsXY{ widget.left + 1, widget.top + 1 };
1006         int32_t width = widget.right - screenCoords.x;
1007         int32_t height = widget.bottom - 3 - screenCoords.y;
1008         if (w->page == WINDOW_RIDE_PAGE_VEHICLE)
1009             height += 4;
1010 
1011         screenCoords += w->windowPos;
1012 
1013         rct_drawpixelinfo clipDPI;
1014         if (!clip_drawpixelinfo(&clipDPI, dpi, screenCoords, width, height))
1015         {
1016             return;
1017         }
1018 
1019         screenCoords = ScreenCoordsXY{ widget.width() / 2, widget.height() - 12 };
1020 
1021         auto ride = get_ride(w->rideId);
1022         if (ride == nullptr)
1023             return;
1024 
1025         auto rideEntry = ride->GetRideEntry();
1026         if (rideEntry == nullptr)
1027             return;
1028 
1029         if (rideEntry->flags & RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF)
1030         {
1031             clipDPI.zoom_level = 1;
1032             clipDPI.width *= 2;
1033             clipDPI.height *= 2;
1034             screenCoords.x *= 2;
1035             screenCoords.y *= 2;
1036             clipDPI.x *= 2;
1037             clipDPI.y *= 2;
1038         }
1039 
1040         // For any suspended rides, move image higher in the vehicle tab on the rides window
1041         if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SUSPENDED))
1042         {
1043             screenCoords.y /= 4;
1044         }
1045 
1046         const uint8_t vehicle = ride_entry_get_vehicle_at_position(
1047             ride->subtype, ride->num_cars_per_train, rideEntry->tab_vehicle);
1048         rct_ride_entry_vehicle* rideVehicleEntry = &rideEntry->vehicles[vehicle];
1049 
1050         auto vehicleId = ((ride->colour_scheme_type & 3) == VEHICLE_COLOUR_SCHEME_PER_VEHICLE) ? rideEntry->tab_vehicle : 0;
1051         vehicle_colour vehicleColour = ride_get_vehicle_colour(ride, vehicleId);
1052         int32_t spriteIndex = 32;
1053         if (w->page == WINDOW_RIDE_PAGE_VEHICLE)
1054             spriteIndex += w->frame_no;
1055         spriteIndex /= (rideVehicleEntry->flags & VEHICLE_ENTRY_FLAG_USE_16_ROTATION_FRAMES) ? 4 : 2;
1056         spriteIndex &= rideVehicleEntry->rotation_frame_mask;
1057         spriteIndex *= rideVehicleEntry->base_num_frames;
1058         spriteIndex += rideVehicleEntry->base_image_id;
1059         spriteIndex |= (vehicleColour.additional_1 << 24) | (vehicleColour.main << 19);
1060         spriteIndex |= IMAGE_TYPE_REMAP_2_PLUS;
1061 
1062         gfx_draw_sprite(&clipDPI, ImageId::FromUInt32(spriteIndex, vehicleColour.additional_2), screenCoords);
1063     }
1064 }
1065 
1066 /**
1067  *
1068  *  rct2: 0x006B2F42
1069  */
window_ride_draw_tab_customer(rct_drawpixelinfo * dpi,rct_window * w)1070 static void window_ride_draw_tab_customer(rct_drawpixelinfo* dpi, rct_window* w)
1071 {
1072     rct_widgetindex widgetIndex = WIDX_TAB_1 + WINDOW_RIDE_PAGE_CUSTOMER;
1073 
1074     if (!(w->disabled_widgets & (1LL << widgetIndex)))
1075     {
1076         const auto& widget = w->widgets[widgetIndex];
1077         int32_t spriteIndex = 0;
1078         if (w->page == WINDOW_RIDE_PAGE_CUSTOMER)
1079             spriteIndex = w->picked_peep_frame & ~3;
1080 
1081         spriteIndex += GetPeepAnimation(PeepSpriteType::Normal).base_image;
1082         spriteIndex += 1;
1083         spriteIndex |= 0xA9E00000;
1084 
1085         gfx_draw_sprite(
1086             dpi, ImageId::FromUInt32(spriteIndex), w->windowPos + ScreenCoordsXY{ widget.midX(), widget.bottom - 6 });
1087     }
1088 }
1089 
1090 /**
1091  *
1092  *  rct2: 0x006B2B35
1093  */
window_ride_draw_tab_images(rct_drawpixelinfo * dpi,rct_window * w)1094 static void window_ride_draw_tab_images(rct_drawpixelinfo* dpi, rct_window* w)
1095 {
1096     window_ride_draw_tab_vehicle(dpi, w);
1097     window_ride_draw_tab_image(dpi, w, WINDOW_RIDE_PAGE_OPERATING, SPR_TAB_GEARS_0);
1098     window_ride_draw_tab_image(dpi, w, WINDOW_RIDE_PAGE_MAINTENANCE, SPR_TAB_WRENCH_0);
1099     window_ride_draw_tab_image(dpi, w, WINDOW_RIDE_PAGE_INCOME, SPR_TAB_ADMISSION_0);
1100     window_ride_draw_tab_main(dpi, w);
1101     window_ride_draw_tab_image(dpi, w, WINDOW_RIDE_PAGE_MEASUREMENTS, SPR_TAB_TIMER_0);
1102     window_ride_draw_tab_image(dpi, w, WINDOW_RIDE_PAGE_COLOUR, SPR_TAB_PAINT_0);
1103     window_ride_draw_tab_image(dpi, w, WINDOW_RIDE_PAGE_GRAPHS, SPR_TAB_GRAPH_A_0);
1104     window_ride_draw_tab_customer(dpi, w);
1105     window_ride_draw_tab_image(dpi, w, WINDOW_RIDE_PAGE_MUSIC, SPR_TAB_MUSIC_0);
1106 }
1107 
1108 /**
1109  *
1110  * rct2: 0x006AEB9F
1111  */
window_ride_disable_tabs(rct_window * w)1112 static void window_ride_disable_tabs(rct_window* w)
1113 {
1114     uint32_t disabled_tabs = 0;
1115     auto ride = get_ride(w->rideId);
1116     if (ride == nullptr)
1117         return;
1118 
1119     const auto& rtd = ride->GetRideTypeDescriptor();
1120 
1121     if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_DATA_LOGGING))
1122         disabled_tabs |= (1ULL << WIDX_TAB_8); // 0x800
1123 
1124     if (ride->type == RIDE_TYPE_MINI_GOLF)
1125         disabled_tabs |= (1ULL << WIDX_TAB_2 | 1ULL << WIDX_TAB_3 | 1ULL << WIDX_TAB_4); // 0xE0
1126 
1127     if (rtd.HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
1128         disabled_tabs |= (1ULL << WIDX_TAB_2); // 0x20
1129 
1130     if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL)
1131         && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_VEHICLE_COLOURS)
1132         && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT))
1133     {
1134         disabled_tabs |= (1ULL << WIDX_TAB_5); // 0x100
1135     }
1136 
1137     if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
1138         disabled_tabs |= (1ULL << WIDX_TAB_3 | 1ULL << WIDX_TAB_4 | 1ULL << WIDX_TAB_7); // 0x4C0
1139 
1140     if (!rtd.HasFlag(RIDE_TYPE_FLAG_ALLOW_MUSIC))
1141     {
1142         disabled_tabs |= (1ULL << WIDX_TAB_6); // 0x200
1143     }
1144 
1145     if (ride->type == RIDE_TYPE_CASH_MACHINE || ride->type == RIDE_TYPE_FIRST_AID || (gParkFlags & PARK_FLAGS_NO_MONEY) != 0)
1146         disabled_tabs |= (1ULL << WIDX_TAB_9); // 0x1000
1147 
1148     if ((gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) != 0)
1149         disabled_tabs |= (1ULL << WIDX_TAB_4 | 1ULL << WIDX_TAB_6 | 1ULL << WIDX_TAB_9 | 1ULL << WIDX_TAB_10); // 0x3280
1150 
1151     rct_ride_entry* rideEntry = get_ride_entry(ride->subtype);
1152 
1153     if (rideEntry == nullptr)
1154     {
1155         disabled_tabs |= 1ULL << WIDX_TAB_2 | 1ULL << WIDX_TAB_3 | 1ULL << WIDX_TAB_4 | 1ULL << WIDX_TAB_5 | 1ULL << WIDX_TAB_6
1156             | 1ULL << WIDX_TAB_7 | 1ULL << WIDX_TAB_8 | 1ULL << WIDX_TAB_9 | 1ULL << WIDX_TAB_10;
1157     }
1158     else if ((rideEntry->flags & RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB) != 0)
1159     {
1160         disabled_tabs |= (1ULL << WIDX_TAB_5);
1161     }
1162 
1163     w->disabled_widgets = disabled_tabs;
1164 }
1165 
window_ride_update_overall_view(Ride * ride)1166 static void window_ride_update_overall_view(Ride* ride)
1167 {
1168     // Calculate x, y, z bounds of the entire ride using its track elements
1169     tile_element_iterator it;
1170 
1171     tile_element_iterator_begin(&it);
1172 
1173     CoordsXYZ min = { std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max(),
1174                       std::numeric_limits<int32_t>::max() };
1175     CoordsXYZ max = { std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min(),
1176                       std::numeric_limits<int32_t>::min() };
1177 
1178     while (tile_element_iterator_next(&it))
1179     {
1180         if (it.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
1181             continue;
1182 
1183         if (it.element->AsTrack()->GetRideIndex() != ride->id)
1184             continue;
1185 
1186         auto location = TileCoordsXY(it.x, it.y).ToCoordsXY();
1187         int32_t baseZ = it.element->GetBaseZ();
1188         int32_t clearZ = it.element->GetClearanceZ();
1189 
1190         min.x = std::min(min.x, location.x);
1191         min.y = std::min(min.y, location.y);
1192         min.z = std::min(min.z, baseZ);
1193 
1194         max.x = std::max(max.x, location.x);
1195         max.y = std::max(max.y, location.y);
1196         max.z = std::max(max.z, clearZ);
1197     }
1198 
1199     const auto rideIndex = EnumValue(ride->id);
1200     if (rideIndex >= ride_overall_views.size())
1201     {
1202         ride_overall_views.resize(rideIndex + 1);
1203     }
1204 
1205     auto& view = ride_overall_views[rideIndex];
1206     view.loc = CoordsXYZ{ (min.x + max.x) / 2, (min.y + max.y) / 2, (min.z + max.z) / 2 } + CoordsXYZ{ 16, 16, -8 };
1207 
1208     // Calculate size to determine from how far away to view the ride
1209     const auto diff = max - min;
1210 
1211     const int32_t size = static_cast<int32_t>(std::sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z));
1212 
1213     if (size >= 80)
1214     {
1215         // Each farther zoom level shows twice as many tiles (log)
1216         // Appropriate zoom is lowered by one to fill the entire view with the ride
1217         view.zoom = std::clamp<int32_t>(std::ceil(std::log(size / 80)) - 1, 0, 3);
1218     }
1219     else
1220     {
1221         // Small rides or stalls are zoomed in all the way.
1222         view.zoom = 0;
1223     }
1224 }
1225 
1226 /**
1227  *
1228  *  rct2: 0x006AEAB4
1229  */
window_ride_open(Ride * ride)1230 static rct_window* window_ride_open(Ride* ride)
1231 {
1232     rct_window* w;
1233 
1234     w = WindowCreateAutoPos(316, 207, window_ride_page_events[0], WC_RIDE, WF_10 | WF_RESIZABLE);
1235     w->widgets = window_ride_page_widgets[WINDOW_RIDE_PAGE_MAIN];
1236     w->enabled_widgets = window_ride_page_enabled_widgets[WINDOW_RIDE_PAGE_MAIN];
1237     w->hold_down_widgets = window_ride_page_hold_down_widgets[WINDOW_RIDE_PAGE_MAIN];
1238     w->rideId = ride->id;
1239 
1240     w->page = WINDOW_RIDE_PAGE_MAIN;
1241     w->vehicleIndex = 0;
1242     w->frame_no = 0;
1243     w->list_information_type = 0;
1244     w->picked_peep_frame = 0;
1245     w->ride_colour = 0;
1246     window_ride_disable_tabs(w);
1247     w->min_width = 316;
1248     w->min_height = 180;
1249     w->max_width = 500;
1250     w->max_height = 450;
1251 
1252     window_ride_update_overall_view(ride);
1253 
1254     populate_vehicle_type_dropdown(ride, true);
1255 
1256     return w;
1257 }
1258 
1259 /**
1260  *
1261  *  rct2: 0x006ACC28
1262  */
window_ride_main_open(Ride * ride)1263 rct_window* window_ride_main_open(Ride* ride)
1264 {
1265     if (ride->type >= RIDE_TYPE_COUNT)
1266     {
1267         return nullptr;
1268     }
1269 
1270     rct_window* w = window_bring_to_front_by_number(WC_RIDE, EnumValue(ride->id));
1271     if (w == nullptr)
1272     {
1273         w = window_ride_open(ride);
1274         w->ride.var_482 = -1;
1275         w->ride.view = 0;
1276     }
1277     else if (w->ride.view >= (1 + ride->num_vehicles + ride->num_stations))
1278     {
1279         w->ride.view = 0;
1280     }
1281 
1282     if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
1283     {
1284         if (w->classification == gCurrentToolWidget.window_classification && w->number == gCurrentToolWidget.window_number)
1285         {
1286             tool_cancel();
1287         }
1288     }
1289 
1290     if (w->page != WINDOW_RIDE_PAGE_MAIN)
1291     {
1292         window_ride_set_page(w, WINDOW_RIDE_PAGE_MAIN);
1293     }
1294 
1295     window_ride_init_viewport(w);
1296     return w;
1297 }
1298 
1299 /**
1300  *
1301  *  rct2: 0x006ACCCE
1302  */
window_ride_open_station(Ride * ride,StationIndex stationIndex)1303 static rct_window* window_ride_open_station(Ride* ride, StationIndex stationIndex)
1304 {
1305     if (ride->type >= RIDE_TYPE_COUNT)
1306         return nullptr;
1307 
1308     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
1309         return window_ride_main_open(ride);
1310 
1311     auto w = window_bring_to_front_by_number(WC_RIDE, EnumValue(ride->id));
1312     if (w == nullptr)
1313     {
1314         w = window_ride_open(ride);
1315         w->ride.var_482 = -1;
1316     }
1317 
1318     if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == w->classification
1319         && gCurrentToolWidget.window_number == w->number)
1320     {
1321         tool_cancel();
1322     }
1323 
1324     w->page = WINDOW_RIDE_PAGE_MAIN;
1325     w->width = 316;
1326     w->height = 180;
1327     w->Invalidate();
1328 
1329     w->widgets = window_ride_page_widgets[w->page];
1330     w->enabled_widgets = window_ride_page_enabled_widgets[w->page];
1331     w->hold_down_widgets = window_ride_page_hold_down_widgets[w->page];
1332     w->event_handlers = window_ride_page_events[w->page];
1333     w->pressed_widgets = 0;
1334     window_ride_disable_tabs(w);
1335     WindowInitScrollWidgets(w);
1336 
1337     // View
1338     for (int32_t i = stationIndex; i >= 0; i--)
1339     {
1340         if (ride->stations[i].Start.IsNull())
1341         {
1342             stationIndex--;
1343         }
1344     }
1345 
1346     w->ride.view = 1 + ride->num_vehicles + stationIndex;
1347     window_ride_init_viewport(w);
1348 
1349     return w;
1350 }
1351 
window_ride_open_track(TileElement * tileElement)1352 rct_window* window_ride_open_track(TileElement* tileElement)
1353 {
1354     assert(tileElement != nullptr);
1355     auto rideIndex = tileElement->GetRideIndex();
1356     if (rideIndex != RIDE_ID_NULL)
1357     {
1358         auto ride = get_ride(rideIndex);
1359         if (ride != nullptr)
1360         {
1361             switch (tileElement->GetType())
1362             {
1363                 case TILE_ELEMENT_TYPE_ENTRANCE:
1364                 {
1365                     // Open ride window in station view
1366                     auto entranceElement = tileElement->AsEntrance();
1367                     auto stationIndex = entranceElement->GetStationIndex();
1368                     return window_ride_open_station(ride, stationIndex);
1369                 }
1370                 case TILE_ELEMENT_TYPE_TRACK:
1371                 {
1372                     // Open ride window in station view
1373                     auto trackElement = tileElement->AsTrack();
1374                     auto trackType = trackElement->GetTrackType();
1375                     const auto& ted = GetTrackElementDescriptor(trackType);
1376                     if (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN)
1377                     {
1378                         auto stationIndex = trackElement->GetStationIndex();
1379                         return window_ride_open_station(ride, stationIndex);
1380                     }
1381                 }
1382             }
1383 
1384             // Open ride window in overview mode
1385             return window_ride_main_open(ride);
1386         }
1387     }
1388     return nullptr;
1389 }
1390 
1391 /**
1392  *
1393  *  rct2: 0x006ACAC2
1394  */
window_ride_open_vehicle(Vehicle * vehicle)1395 rct_window* window_ride_open_vehicle(Vehicle* vehicle)
1396 {
1397     if (vehicle == nullptr)
1398         return nullptr;
1399 
1400     Vehicle* headVehicle = vehicle->TrainHead();
1401     if (headVehicle == nullptr)
1402         return nullptr;
1403 
1404     uint16_t headVehicleSpriteIndex = headVehicle->sprite_index;
1405     auto ride = headVehicle->GetRide();
1406     if (ride == nullptr)
1407         return nullptr;
1408 
1409     // Get view index
1410     int32_t view = 1;
1411     for (int32_t i = 0; i <= MAX_VEHICLES_PER_RIDE; i++)
1412     {
1413         if (ride->vehicles[i] == headVehicleSpriteIndex)
1414             break;
1415 
1416         view++;
1417     }
1418 
1419     rct_window* w = window_find_by_number(WC_RIDE, EnumValue(ride->id));
1420     if (w != nullptr)
1421     {
1422         w->Invalidate();
1423 
1424         if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == w->classification
1425             && gCurrentToolWidget.window_number == w->number)
1426         {
1427             tool_cancel();
1428         }
1429 
1430         int32_t openedPeepWindow = 0;
1431         if (w->ride.view == view)
1432         {
1433             int32_t numPeepsLeft = vehicle->num_peeps;
1434             for (int32_t i = 0; i < 32 && numPeepsLeft > 0; i++)
1435             {
1436                 Peep* peep = GetEntity<Guest>(vehicle->peep[i]);
1437                 if (peep == nullptr)
1438                     continue;
1439 
1440                 numPeepsLeft--;
1441                 rct_window* w2 = window_find_by_number(WC_PEEP, vehicle->peep[i]);
1442                 if (w2 == nullptr)
1443                 {
1444                     auto intent = Intent(WC_PEEP);
1445                     intent.putExtra(INTENT_EXTRA_PEEP, peep);
1446                     context_open_intent(&intent);
1447                     openedPeepWindow = 1;
1448 
1449                     break;
1450                 }
1451             }
1452         }
1453 
1454         w = openedPeepWindow ? window_find_by_number(WC_RIDE, EnumValue(ride->id))
1455                              : window_bring_to_front_by_number(WC_RIDE, EnumValue(ride->id));
1456     }
1457 
1458     if (w == nullptr)
1459     {
1460         w = window_ride_open(ride);
1461         w->ride.var_482 = -1;
1462     }
1463 
1464     w->page = WINDOW_RIDE_PAGE_MAIN;
1465     w->width = 316;
1466     w->height = 180;
1467     w->Invalidate();
1468 
1469     w->widgets = window_ride_page_widgets[w->page];
1470     w->enabled_widgets = window_ride_page_enabled_widgets[w->page];
1471     w->hold_down_widgets = window_ride_page_hold_down_widgets[w->page];
1472     w->event_handlers = window_ride_page_events[w->page];
1473     w->pressed_widgets = 0;
1474     window_ride_disable_tabs(w);
1475     WindowInitScrollWidgets(w);
1476 
1477     w->ride.view = view;
1478     window_ride_init_viewport(w);
1479     w->Invalidate();
1480 
1481     return w;
1482 }
1483 
1484 /**
1485  *
1486  *  rct2: 0x006AF1D2
1487  */
window_ride_set_page(rct_window * w,int32_t page)1488 static void window_ride_set_page(rct_window* w, int32_t page)
1489 {
1490     int32_t listen;
1491 
1492     if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
1493         if (w->classification == gCurrentToolWidget.window_classification && w->number == gCurrentToolWidget.window_number)
1494             tool_cancel();
1495 
1496     if (page == WINDOW_RIDE_PAGE_VEHICLE)
1497     {
1498         auto constructionWindow = window_find_by_class(WC_RIDE_CONSTRUCTION);
1499         if (constructionWindow != nullptr && constructionWindow->number == w->number)
1500         {
1501             window_close_by_class(WC_RIDE_CONSTRUCTION);
1502             // Closing the construction window sets the tab to the first page, which we don't want here,
1503             // as user just clicked the Vehicle page
1504             window_ride_set_page(w, WINDOW_RIDE_PAGE_VEHICLE);
1505         }
1506     }
1507 
1508     // Set listen only to viewport
1509     listen = 0;
1510     if (page == WINDOW_RIDE_PAGE_MAIN && w->page == WINDOW_RIDE_PAGE_MAIN && w->viewport != nullptr
1511         && !(w->viewport->flags & VIEWPORT_FLAG_SOUND_ON))
1512         listen++;
1513 
1514     w->page = page;
1515     w->frame_no = 0;
1516     w->picked_peep_frame = 0;
1517 
1518     // There doesn't seem to be any need for this call, and it can sometimes modify the reported number of cars per train, so
1519     // I've removed it if (page == WINDOW_RIDE_PAGE_VEHICLE) { ride_update_max_vehicles(ride);
1520     //}
1521 
1522     w->RemoveViewport();
1523 
1524     w->enabled_widgets = window_ride_page_enabled_widgets[page];
1525     w->hold_down_widgets = window_ride_page_hold_down_widgets[page];
1526     w->event_handlers = window_ride_page_events[page];
1527     w->pressed_widgets = 0;
1528     w->widgets = window_ride_page_widgets[page];
1529     window_ride_disable_tabs(w);
1530     w->Invalidate();
1531 
1532     window_event_resize_call(w);
1533     window_event_invalidate_call(w);
1534     WindowInitScrollWidgets(w);
1535     w->Invalidate();
1536 
1537     if (listen != 0 && w->viewport != nullptr)
1538         w->viewport->flags |= VIEWPORT_FLAG_SOUND_ON;
1539 }
1540 
window_ride_set_pressed_tab(rct_window * w)1541 static void window_ride_set_pressed_tab(rct_window* w)
1542 {
1543     int32_t i;
1544     for (i = 0; i < WINDOW_RIDE_PAGE_COUNT; i++)
1545         w->pressed_widgets &= ~(1 << (WIDX_TAB_1 + i));
1546     w->pressed_widgets |= 1LL << (WIDX_TAB_1 + w->page);
1547 }
1548 
window_ride_anchor_border_widgets(rct_window * w)1549 static void window_ride_anchor_border_widgets(rct_window* w)
1550 {
1551     w->widgets[WIDX_BACKGROUND].right = w->width - 1;
1552     w->widgets[WIDX_BACKGROUND].bottom = w->height - 1;
1553     w->widgets[WIDX_PAGE_BACKGROUND].right = w->width - 1;
1554     w->widgets[WIDX_PAGE_BACKGROUND].bottom = w->height - 1;
1555     w->widgets[WIDX_TITLE].right = w->width - 2;
1556     w->widgets[WIDX_CLOSE].left = w->width - 13;
1557     w->widgets[WIDX_CLOSE].right = w->width - 3;
1558 }
1559 
1560 #pragma region Main
1561 
GetStationIndexFromViewSelection(const rct_window & w)1562 static std::optional<StationIndex> GetStationIndexFromViewSelection(const rct_window& w)
1563 {
1564     const auto* ride = get_ride(static_cast<ride_id_t>(w.number));
1565     if (ride == nullptr)
1566         return std::nullopt;
1567 
1568     int32_t viewSelectionIndex = w.ride.view - 1 - ride->num_vehicles;
1569     if (viewSelectionIndex < 0)
1570     {
1571         return std::nullopt;
1572     }
1573 
1574     for (StationIndex index = 0; index < std::size(ride->stations); ++index)
1575     {
1576         const auto& station = ride->stations[index];
1577         if (!station.Start.IsNull())
1578         {
1579             if (viewSelectionIndex-- == 0)
1580             {
1581                 return { index };
1582             }
1583         }
1584     }
1585     return std::nullopt;
1586 }
1587 
1588 /**
1589  *
1590  *  rct2: 0x006AF994
1591  */
window_ride_init_viewport(rct_window * w)1592 static void window_ride_init_viewport(rct_window* w)
1593 {
1594     if (w->page != WINDOW_RIDE_PAGE_MAIN)
1595         return;
1596 
1597     auto ride = get_ride(w->rideId);
1598     if (ride == nullptr)
1599         return;
1600 
1601     int32_t viewSelectionIndex = w->ride.view - 1;
1602 
1603     std::optional<Focus> focus;
1604 
1605     if (viewSelectionIndex >= 0 && viewSelectionIndex < ride->num_vehicles && ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)
1606     {
1607         uint16_t vehId = ride->vehicles[viewSelectionIndex];
1608         rct_ride_entry* ride_entry = ride->GetRideEntry();
1609         if (ride_entry != nullptr && ride_entry->tab_vehicle != 0)
1610         {
1611             Vehicle* vehicle = GetEntity<Vehicle>(vehId);
1612             if (vehicle == nullptr)
1613             {
1614                 vehId = SPRITE_INDEX_NULL;
1615             }
1616             else if (vehicle->next_vehicle_on_train != SPRITE_INDEX_NULL)
1617             {
1618                 vehId = vehicle->next_vehicle_on_train;
1619             }
1620         }
1621         if (vehId != SPRITE_INDEX_NULL)
1622         {
1623             focus = Focus(vehId);
1624         }
1625     }
1626     else if (viewSelectionIndex >= ride->num_vehicles && viewSelectionIndex < (ride->num_vehicles + ride->num_stations))
1627     {
1628         auto stationIndex = GetStationIndexFromViewSelection(*w);
1629         if (stationIndex)
1630         {
1631             const auto location = ride->stations[*stationIndex].GetStart();
1632             focus = Focus(location);
1633         }
1634     }
1635     else
1636     {
1637         if (viewSelectionIndex > 0)
1638         {
1639             w->ride.view = 0;
1640         }
1641         if (w->number < ride_overall_views.size())
1642         {
1643             const auto& view = ride_overall_views[w->number];
1644             focus = Focus(view.loc, view.zoom);
1645         }
1646     }
1647 
1648     uint16_t viewport_flags = 0;
1649     if (w->viewport != nullptr)
1650     {
1651         if (focus == w->focus)
1652         {
1653             return;
1654         }
1655         viewport_flags = w->viewport->flags;
1656         w->RemoveViewport();
1657     }
1658     else if (gConfigGeneral.always_show_gridlines)
1659     {
1660         viewport_flags |= VIEWPORT_FLAG_GRIDLINES;
1661     }
1662 
1663     window_event_invalidate_call(w);
1664 
1665     w->focus = focus;
1666 
1667     // rct2: 0x006aec9c only used here so brought it into the function
1668     if (w->viewport == nullptr && !ride->overall_view.IsNull())
1669     {
1670         const auto& view_widget = w->widgets[WIDX_VIEWPORT];
1671 
1672         auto screenPos = w->windowPos + ScreenCoordsXY{ view_widget.left + 1, view_widget.top + 1 };
1673         int32_t width = view_widget.width() - 1;
1674         int32_t height = view_widget.height() - 1;
1675 
1676         viewport_create(w, screenPos, width, height, w->focus.value());
1677 
1678         w->flags |= WF_NO_SCROLLING;
1679         w->Invalidate();
1680     }
1681     if (w->viewport != nullptr)
1682     {
1683         w->viewport->flags = viewport_flags;
1684         w->Invalidate();
1685     }
1686 }
1687 
1688 /**
1689  *
1690  *  rct2: 0x006AF315
1691  */
window_ride_rename(rct_window * w)1692 static void window_ride_rename(rct_window* w)
1693 {
1694     auto ride = get_ride(w->rideId);
1695     if (ride != nullptr)
1696     {
1697         auto rideName = ride->GetName();
1698         window_text_input_raw_open(
1699             w, WIDX_RENAME, STR_RIDE_ATTRACTION_NAME, STR_ENTER_NEW_NAME_FOR_THIS_RIDE_ATTRACTION, {}, rideName.c_str(), 32);
1700     }
1701 }
1702 
1703 /**
1704  *
1705  *  rct2: 0x006AF17E
1706  */
window_ride_main_mouseup(rct_window * w,rct_widgetindex widgetIndex)1707 static void window_ride_main_mouseup(rct_window* w, rct_widgetindex widgetIndex)
1708 {
1709     switch (widgetIndex)
1710     {
1711         case WIDX_CLOSE:
1712             window_close(w);
1713             break;
1714         case WIDX_TAB_1:
1715         case WIDX_TAB_2:
1716         case WIDX_TAB_3:
1717         case WIDX_TAB_4:
1718         case WIDX_TAB_5:
1719         case WIDX_TAB_6:
1720         case WIDX_TAB_7:
1721         case WIDX_TAB_8:
1722         case WIDX_TAB_9:
1723         case WIDX_TAB_10:
1724             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
1725             break;
1726         case WIDX_CONSTRUCTION:
1727         {
1728             auto ride = get_ride(w->rideId);
1729             if (ride != nullptr)
1730             {
1731                 ride_construct(ride);
1732                 if (window_find_by_number(WC_RIDE_CONSTRUCTION, EnumValue(ride->id)) != nullptr)
1733                 {
1734                     window_close(w);
1735                 }
1736             }
1737             break;
1738         }
1739         case WIDX_RENAME:
1740             window_ride_rename(w);
1741             break;
1742         case WIDX_DEMOLISH:
1743             context_open_detail_window(WD_DEMOLISH_RIDE, w->number);
1744             break;
1745         case WIDX_CLOSE_LIGHT:
1746         case WIDX_SIMULATE_LIGHT:
1747         case WIDX_TEST_LIGHT:
1748         case WIDX_OPEN_LIGHT:
1749         {
1750             auto ride = get_ride(w->rideId);
1751             if (ride != nullptr)
1752             {
1753                 RideStatus status;
1754                 switch (widgetIndex)
1755                 {
1756                     default:
1757                     case WIDX_CLOSE_LIGHT:
1758                         status = RideStatus::Closed;
1759                         break;
1760                     case WIDX_SIMULATE_LIGHT:
1761                         status = RideStatus::Simulating;
1762                         break;
1763                     case WIDX_TEST_LIGHT:
1764                         status = RideStatus::Testing;
1765                         break;
1766                     case WIDX_OPEN_LIGHT:
1767                         status = RideStatus::Open;
1768                         break;
1769                 }
1770                 ride_set_status(ride, status);
1771             }
1772             break;
1773         }
1774     }
1775 }
1776 
1777 /**
1778  *
1779  *  rct2: 0x006AF4A2
1780  */
window_ride_main_resize(rct_window * w)1781 static void window_ride_main_resize(rct_window* w)
1782 {
1783     int32_t minHeight = 180;
1784     if (ThemeGetFlags() & UITHEME_FLAG_USE_LIGHTS_RIDE)
1785     {
1786         minHeight += 20 + RCT1_LIGHT_OFFSET;
1787 
1788         auto ride = get_ride(w->rideId);
1789         if (ride != nullptr)
1790         {
1791 #ifdef __SIMULATE_IN_RIDE_WINDOW__
1792             if (ride->SupportsStatus(RideStatus::Simulating))
1793             {
1794                 minHeight += 14;
1795             }
1796 #endif
1797             if (ride->SupportsStatus(RideStatus::Testing))
1798             {
1799                 minHeight += 14;
1800             }
1801         }
1802     }
1803     if (gCheatsAllowArbitraryRideTypeChanges)
1804     {
1805         minHeight += 15;
1806     }
1807 
1808     w->flags |= WF_RESIZABLE;
1809     window_set_resize(w, 316, minHeight, 500, 450);
1810     // Unlike with other windows, the focus needs to be recentred so it’s best to just reset it.
1811     w->focus = std::nullopt;
1812     window_ride_init_viewport(w);
1813 }
1814 
1815 /**
1816  *
1817  *  rct2: 0x006AF825
1818  */
window_ride_show_view_dropdown(rct_window * w,rct_widget * widget)1819 static void window_ride_show_view_dropdown(rct_window* w, rct_widget* widget)
1820 {
1821     rct_widget* dropdownWidget = widget - 1;
1822     auto ride = get_ride(w->rideId);
1823     if (ride == nullptr)
1824         return;
1825 
1826     int32_t numItems = 1;
1827     if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
1828     {
1829         numItems += ride->num_stations;
1830         numItems += ride->num_vehicles;
1831     }
1832 
1833     WindowDropdownShowTextCustomWidth(
1834         { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
1835         w->colours[1], 0, 0, numItems, widget->right - dropdownWidget->left);
1836 
1837     // First item
1838     gDropdownItemsFormat[0] = STR_DROPDOWN_MENU_LABEL;
1839     gDropdownItemsArgs[0] = STR_OVERALL_VIEW;
1840     int32_t currentItem = 1;
1841 
1842     const auto& rtd = ride->GetRideTypeDescriptor();
1843 
1844     // Vehicles
1845     int32_t name = GetRideComponentName(rtd.NameConvention.vehicle).number;
1846     for (int32_t i = 1; i <= ride->num_vehicles; i++)
1847     {
1848         gDropdownItemsFormat[currentItem] = STR_DROPDOWN_MENU_LABEL;
1849         gDropdownItemsArgs[currentItem] = name | (currentItem << 16);
1850         currentItem++;
1851     }
1852 
1853     // Stations
1854     name = GetRideComponentName(rtd.NameConvention.station).number;
1855     for (int32_t i = 1; i <= ride->num_stations; i++)
1856     {
1857         gDropdownItemsFormat[currentItem] = STR_DROPDOWN_MENU_LABEL;
1858         gDropdownItemsArgs[currentItem] = name | (i << 16);
1859         currentItem++;
1860     }
1861 
1862     // Set highlighted item
1863     if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
1864     {
1865         for (int32_t i = 0; i < ride->num_vehicles; i++)
1866         {
1867             // The +1 is to skip 'Overall view'
1868             Dropdown::SetDisabled(i + 1, true);
1869             ;
1870         }
1871     }
1872 
1873     // Set checked item
1874     Dropdown::SetChecked(w->ride.view, true);
1875 }
1876 
window_ride_get_next_default_status(const Ride * ride)1877 static RideStatus window_ride_get_next_default_status(const Ride* ride)
1878 {
1879     switch (ride->status)
1880     {
1881         default:
1882         case RideStatus::Closed:
1883             if ((ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED)
1884                 || (ride->lifecycle_flags & RIDE_LIFECYCLE_HAS_STALLED_VEHICLE))
1885             {
1886                 return RideStatus::Closed;
1887             }
1888             if (ride->SupportsStatus(RideStatus::Testing) && !(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED))
1889             {
1890                 return RideStatus::Testing;
1891             }
1892             return RideStatus::Open;
1893         case RideStatus::Simulating:
1894             return RideStatus::Testing;
1895         case RideStatus::Testing:
1896             return (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED) ? RideStatus::Open : RideStatus::Closed;
1897         case RideStatus::Open:
1898             return RideStatus::Closed;
1899     }
1900 }
1901 
1902 struct RideStatusDropdownInfo
1903 {
1904     struct Ride* Ride{};
1905     RideStatus CurrentStatus{};
1906     RideStatus DefaultStatus{};
1907 
1908     int32_t NumItems{};
1909     int32_t CheckedIndex = -1;
1910     int32_t DefaultIndex = -1;
1911 };
1912 
window_ride_set_dropdown(RideStatusDropdownInfo & info,RideStatus status,rct_string_id text)1913 static void window_ride_set_dropdown(RideStatusDropdownInfo& info, RideStatus status, rct_string_id text)
1914 {
1915     if (info.Ride->SupportsStatus(status))
1916     {
1917         auto index = info.NumItems;
1918         gDropdownItemsFormat[index] = STR_DROPDOWN_MENU_LABEL;
1919         gDropdownItemsArgs[index] = text;
1920         if (info.CurrentStatus == status)
1921         {
1922             info.CheckedIndex = index;
1923         }
1924         if (info.DefaultStatus == status)
1925         {
1926             info.DefaultIndex = index;
1927         }
1928         info.NumItems++;
1929     }
1930 }
1931 
window_ride_show_open_dropdown(rct_window * w,rct_widget * widget)1932 static void window_ride_show_open_dropdown(rct_window* w, rct_widget* widget)
1933 {
1934     RideStatusDropdownInfo info;
1935     info.Ride = get_ride(w->rideId);
1936     if (info.Ride == nullptr)
1937         return;
1938 
1939     info.CurrentStatus = info.Ride->status;
1940     info.DefaultStatus = window_ride_get_next_default_status(info.Ride);
1941     window_ride_set_dropdown(info, RideStatus::Closed, STR_CLOSE_RIDE);
1942 #ifdef __SIMULATE_IN_RIDE_WINDOW__
1943     window_ride_set_dropdown(info, RideStatus::Simulating, STR_SIMULATE_RIDE);
1944 #endif
1945     window_ride_set_dropdown(info, RideStatus::Testing, STR_TEST_RIDE);
1946     window_ride_set_dropdown(info, RideStatus::Open, STR_OPEN_RIDE);
1947     WindowDropdownShowText(
1948         { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1, w->colours[1], 0, info.NumItems);
1949     Dropdown::SetChecked(info.CheckedIndex, true);
1950     gDropdownDefaultIndex = info.DefaultIndex;
1951 }
1952 
get_ride_type_name_for_dropdown(uint8_t rideType)1953 static rct_string_id get_ride_type_name_for_dropdown(uint8_t rideType)
1954 {
1955     switch (rideType)
1956     {
1957         case RIDE_TYPE_1D:
1958             return STR_RIDE_NAME_1D;
1959         case RIDE_TYPE_1F:
1960             return STR_RIDE_NAME_1F;
1961         case RIDE_TYPE_22:
1962             return STR_RIDE_NAME_22;
1963         case RIDE_TYPE_50:
1964             return STR_RIDE_NAME_50;
1965         case RIDE_TYPE_52:
1966             return STR_RIDE_NAME_52;
1967         case RIDE_TYPE_53:
1968             return STR_RIDE_NAME_53;
1969         case RIDE_TYPE_54:
1970             return STR_RIDE_NAME_54;
1971         case RIDE_TYPE_55:
1972             return STR_RIDE_NAME_55;
1973         case RIDE_TYPE_59:
1974             return STR_RIDE_NAME_59;
1975         default:
1976             return GetRideTypeDescriptor(rideType).Naming.Name;
1977     }
1978 }
1979 
populate_ride_type_dropdown()1980 static void populate_ride_type_dropdown()
1981 {
1982     auto& ls = OpenRCT2::GetContext()->GetLocalisationService();
1983     if (RideDropdownDataLanguage == ls.GetCurrentLanguage())
1984         return;
1985 
1986     RideDropdownData.clear();
1987 
1988     for (uint8_t i = 0; i < RIDE_TYPE_COUNT; i++)
1989     {
1990         auto name = get_ride_type_name_for_dropdown(i);
1991         RideDropdownData.push_back({ i, name, ls.GetString(name) });
1992     }
1993 
1994     std::sort(RideDropdownData.begin(), RideDropdownData.end(), [](auto& a, auto& b) {
1995         return String::Compare(a.label_string, b.label_string, true) < 0;
1996     });
1997 
1998     RideDropdownDataLanguage = ls.GetCurrentLanguage();
1999 }
2000 
window_ride_show_ride_type_dropdown(rct_window * w,rct_widget * widget)2001 static void window_ride_show_ride_type_dropdown(rct_window* w, rct_widget* widget)
2002 {
2003     auto ride = get_ride(w->rideId);
2004     if (ride == nullptr)
2005         return;
2006 
2007     populate_ride_type_dropdown();
2008 
2009     for (size_t i = 0; i < RideDropdownData.size(); i++)
2010     {
2011         gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
2012         gDropdownItemsArgs[i] = RideDropdownData[i].label_id;
2013     }
2014 
2015     rct_widget* dropdownWidget = widget - 1;
2016     WindowDropdownShowText(
2017         { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
2018         w->colours[1], Dropdown::Flag::StayOpen, RIDE_TYPE_COUNT);
2019 
2020     // Find the current ride type in the ordered list.
2021     uint8_t pos = 0;
2022     for (uint8_t i = 0; i < RIDE_TYPE_COUNT; i++)
2023     {
2024         if (RideDropdownData[i].ride_type_id == ride->type)
2025         {
2026             pos = i;
2027             break;
2028         }
2029     }
2030 
2031     gDropdownHighlightedIndex = pos;
2032     gDropdownDefaultIndex = pos;
2033     Dropdown::SetChecked(pos, true);
2034 }
2035 
window_ride_show_locate_dropdown(rct_window * w,rct_widget * widget)2036 static void window_ride_show_locate_dropdown(rct_window* w, rct_widget* widget)
2037 {
2038     auto ride = get_ride(w->rideId);
2039     if (ride == nullptr)
2040         return;
2041 
2042     gDropdownItemsFormat[0] = STR_LOCATE_SUBJECT_TIP;
2043     gDropdownItemsFormat[1] = STR_FOLLOW_SUBJECT_TIP;
2044 
2045     WindowDropdownShowText(
2046         { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1, w->colours[1], 0, 2);
2047     gDropdownDefaultIndex = 0;
2048     if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
2049     {
2050         // Disable if we're a flat ride
2051         Dropdown::SetDisabled(1, true);
2052     }
2053 }
2054 
window_ride_main_follow_ride(rct_window * w)2055 static void window_ride_main_follow_ride(rct_window* w)
2056 {
2057     auto ride = get_ride(w->rideId);
2058     if (ride != nullptr)
2059     {
2060         if (!(ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAIN))
2061         {
2062             if (w->ride.view > 0)
2063             {
2064                 if (w->ride.view <= ride->num_vehicles)
2065                 {
2066                     Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[w->ride.view - 1]);
2067                     if (vehicle != nullptr)
2068                     {
2069                         uint16_t headVehicleSpriteIndex = vehicle->sprite_index;
2070                         rct_window* w_main = window_get_main();
2071                         window_follow_sprite(w_main, headVehicleSpriteIndex);
2072                     }
2073                 }
2074             }
2075         }
2076     }
2077 }
2078 
populate_vehicle_type_dropdown(Ride * ride,bool forceRefresh)2079 static void populate_vehicle_type_dropdown(Ride* ride, bool forceRefresh)
2080 {
2081     auto& objManager = GetContext()->GetObjectManager();
2082     rct_ride_entry* rideEntry = ride->GetRideEntry();
2083 
2084     bool selectionShouldBeExpanded;
2085     int32_t rideTypeIterator, rideTypeIteratorMax;
2086     if (gCheatsShowVehiclesFromOtherTrackTypes
2087         && !(
2088             ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE) || ride->type == RIDE_TYPE_MAZE
2089             || ride->type == RIDE_TYPE_MINI_GOLF))
2090     {
2091         selectionShouldBeExpanded = true;
2092         rideTypeIterator = 0;
2093         rideTypeIteratorMax = RIDE_TYPE_COUNT - 1;
2094     }
2095     else
2096     {
2097         selectionShouldBeExpanded = false;
2098         rideTypeIterator = ride->type;
2099         rideTypeIteratorMax = ride->type;
2100     }
2101 
2102     // Don't repopulate the list if we just did.
2103     auto& ls = OpenRCT2::GetContext()->GetLocalisationService();
2104     if (!forceRefresh && VehicleDropdownExpanded == selectionShouldBeExpanded && VehicleDropdownRideType == rideEntry
2105         && VehicleDropdownDataLanguage == ls.GetCurrentLanguage())
2106         return;
2107 
2108     VehicleDropdownData.clear();
2109 
2110     for (; rideTypeIterator <= rideTypeIteratorMax; rideTypeIterator++)
2111     {
2112         if (selectionShouldBeExpanded && GetRideTypeDescriptor(rideTypeIterator).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
2113             continue;
2114         if (selectionShouldBeExpanded && (rideTypeIterator == RIDE_TYPE_MAZE || rideTypeIterator == RIDE_TYPE_MINI_GOLF))
2115             continue;
2116 
2117         auto& rideEntries = objManager.GetAllRideEntries(rideTypeIterator);
2118         for (auto rideEntryIndex : rideEntries)
2119         {
2120             auto currentRideEntry = get_ride_entry(rideEntryIndex);
2121 
2122             // Skip if vehicle type has not been invented yet
2123             if (!ride_entry_is_invented(rideEntryIndex) && !gCheatsIgnoreResearchStatus)
2124                 continue;
2125 
2126             VehicleDropdownData.push_back(
2127                 { rideEntryIndex, currentRideEntry->naming.Name, ls.GetString(currentRideEntry->naming.Name) });
2128         }
2129     }
2130 
2131     std::sort(VehicleDropdownData.begin(), VehicleDropdownData.end(), [](auto& a, auto& b) {
2132         return String::Compare(a.label_string, b.label_string, true) < 0;
2133     });
2134 
2135     VehicleDropdownExpanded = selectionShouldBeExpanded;
2136     VehicleDropdownRideType = rideEntry;
2137     VehicleDropdownDataLanguage = ls.GetCurrentLanguage();
2138 }
2139 
window_ride_show_vehicle_type_dropdown(rct_window * w,rct_widget * widget)2140 static void window_ride_show_vehicle_type_dropdown(rct_window* w, rct_widget* widget)
2141 {
2142     auto ride = get_ride(w->rideId);
2143     if (ride == nullptr)
2144         return;
2145 
2146     populate_vehicle_type_dropdown(ride);
2147 
2148     size_t numItems = std::min<size_t>(VehicleDropdownData.size(), Dropdown::ItemsMaxSize);
2149 
2150     for (size_t i = 0; i < numItems; i++)
2151     {
2152         gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
2153         gDropdownItemsArgs[i] = VehicleDropdownData[i].label_id;
2154     }
2155 
2156     rct_widget* dropdownWidget = widget - 1;
2157     WindowDropdownShowTextCustomWidth(
2158         { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
2159         w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left);
2160 
2161     // Find the current vehicle type in the ordered list.
2162     uint8_t pos = 0;
2163     for (uint8_t i = 0; i < VehicleDropdownData.size(); i++)
2164     {
2165         if (VehicleDropdownData[i].subtype_id == ride->subtype)
2166         {
2167             pos = i;
2168             break;
2169         }
2170     }
2171 
2172     gDropdownHighlightedIndex = pos;
2173     gDropdownDefaultIndex = pos;
2174     Dropdown::SetChecked(pos, true);
2175 }
2176 
2177 /**
2178  *
2179  *  rct2: 0x006AF1BD
2180  */
window_ride_main_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)2181 static void window_ride_main_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
2182 {
2183     switch (widgetIndex)
2184     {
2185         case WIDX_VIEW_DROPDOWN:
2186             window_ride_show_view_dropdown(w, widget);
2187             break;
2188         case WIDX_OPEN:
2189             window_ride_show_open_dropdown(w, widget);
2190             break;
2191         case WIDX_RIDE_TYPE_DROPDOWN:
2192             window_ride_show_ride_type_dropdown(w, widget);
2193             break;
2194         case WIDX_LOCATE:
2195             window_ride_show_locate_dropdown(w, widget);
2196             break;
2197     }
2198 }
2199 
2200 /**
2201  *
2202  *  rct2: 0x006AF300
2203  */
window_ride_main_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)2204 static void window_ride_main_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
2205 {
2206     switch (widgetIndex)
2207     {
2208         case WIDX_VIEW_DROPDOWN:
2209             if (dropdownIndex == -1)
2210             {
2211                 dropdownIndex = w->ride.view + 1;
2212                 auto ride = get_ride(w->rideId);
2213                 if (ride != nullptr)
2214                 {
2215                     if (dropdownIndex != 0 && dropdownIndex <= ride->num_vehicles
2216                         && !(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
2217                     {
2218                         dropdownIndex = ride->num_vehicles + 1;
2219                     }
2220                     if (dropdownIndex >= gDropdownNumItems)
2221                     {
2222                         dropdownIndex = 0;
2223                     }
2224                 }
2225             }
2226 
2227             w->ride.view = dropdownIndex;
2228             window_ride_init_viewport(w);
2229             w->Invalidate();
2230             break;
2231         case WIDX_OPEN:
2232         {
2233             auto ride = get_ride(w->rideId);
2234             if (ride != nullptr)
2235             {
2236                 auto status = RideStatus::Closed;
2237                 if (dropdownIndex < 0)
2238                 {
2239                     dropdownIndex = gDropdownHighlightedIndex;
2240                 }
2241                 if (dropdownIndex < static_cast<int32_t>(std::size(gDropdownItemsArgs)))
2242                 {
2243                     switch (gDropdownItemsArgs[dropdownIndex])
2244                     {
2245                         case STR_CLOSE_RIDE:
2246                             status = RideStatus::Closed;
2247                             break;
2248                         case STR_SIMULATE_RIDE:
2249                             status = RideStatus::Simulating;
2250                             break;
2251                         case STR_TEST_RIDE:
2252                             status = RideStatus::Testing;
2253                             break;
2254                         case STR_OPEN_RIDE:
2255                             status = RideStatus::Open;
2256                             break;
2257                     }
2258                 }
2259                 ride_set_status(ride, status);
2260             }
2261             break;
2262         }
2263         case WIDX_RIDE_TYPE_DROPDOWN:
2264             if (dropdownIndex != -1 && dropdownIndex < RIDE_TYPE_COUNT)
2265             {
2266                 uint8_t rideLabelId = std::clamp(dropdownIndex, 0, RIDE_TYPE_COUNT - 1);
2267                 uint8_t rideType = RideDropdownData[rideLabelId].ride_type_id;
2268                 if (rideType < RIDE_TYPE_COUNT)
2269                 {
2270                     auto rideSetSetting = RideSetSettingAction(w->rideId, RideSetSetting::RideType, rideType);
2271                     rideSetSetting.SetCallback([](const GameAction* ga, const GameActions::Result* result) {
2272                         // Reset ghost track if ride construction window is open, prevents a crash
2273                         // Will get set to the correct Alternative variable during set_default_next_piece.
2274                         // TODO: Rework construction window to prevent the need for this.
2275                         _currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES;
2276                         ride_construction_set_default_next_piece();
2277                     });
2278                     GameActions::Execute(&rideSetSetting);
2279                 }
2280             }
2281             break;
2282         case WIDX_LOCATE:
2283         {
2284             if (dropdownIndex == 0)
2285             {
2286                 w->ScrollToViewport();
2287             }
2288             else if (dropdownIndex == 1)
2289             {
2290                 window_ride_main_follow_ride(w);
2291             }
2292             break;
2293         }
2294     }
2295 }
2296 
2297 /**
2298  *
2299  *  rct2: 0x006AF40F
2300  */
window_ride_main_update(rct_window * w)2301 static void window_ride_main_update(rct_window* w)
2302 {
2303     // Update tab animation
2304     w->frame_no++;
2305     window_event_invalidate_call(w);
2306     widget_invalidate(w, WIDX_TAB_1);
2307 
2308     // Update status
2309     auto ride = get_ride(w->rideId);
2310     if (ride != nullptr)
2311     {
2312         if (!(ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAIN))
2313         {
2314             if (w->ride.view == 0)
2315                 return;
2316 
2317             if (w->ride.view <= ride->num_vehicles)
2318             {
2319                 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[w->ride.view - 1]);
2320                 if (vehicle == nullptr
2321                     || (vehicle->status != Vehicle::Status::Travelling
2322                         && vehicle->status != Vehicle::Status::TravellingCableLift
2323                         && vehicle->status != Vehicle::Status::TravellingDodgems
2324                         && vehicle->status != Vehicle::Status::TravellingBoat))
2325                 {
2326                     return;
2327                 }
2328             }
2329         }
2330         ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_MAIN;
2331     }
2332     widget_invalidate(w, WIDX_STATUS);
2333 }
2334 
2335 /**
2336  *
2337  *  rct2: 0x006AF2F9
2338  */
window_ride_main_textinput(rct_window * w,rct_widgetindex widgetIndex,char * text)2339 static void window_ride_main_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text)
2340 {
2341     if (widgetIndex != WIDX_RENAME || text == nullptr)
2342         return;
2343 
2344     auto ride = get_ride(w->rideId);
2345     if (ride != nullptr)
2346     {
2347         ride_set_name(ride, text, 0);
2348     }
2349 }
2350 
2351 /**
2352  *
2353  *  rct2: 0x006AF55A
2354  */
window_ride_main_viewport_rotate(rct_window * w)2355 static void window_ride_main_viewport_rotate(rct_window* w)
2356 {
2357     window_ride_init_viewport(w);
2358 }
2359 
2360 /**
2361  *
2362  *  rct2: 0x006AECF6
2363  */
window_ride_main_invalidate(rct_window * w)2364 static void window_ride_main_invalidate(rct_window* w)
2365 {
2366     rct_widget* widgets;
2367     int32_t i, height;
2368 
2369     widgets = window_ride_page_widgets[w->page];
2370     if (w->widgets != widgets)
2371     {
2372         w->widgets = widgets;
2373         WindowInitScrollWidgets(w);
2374     }
2375 
2376     window_ride_set_pressed_tab(w);
2377 
2378     auto ride = get_ride(w->rideId);
2379     if (ride == nullptr)
2380         return;
2381 
2382     w->disabled_widgets &= ~((1ULL << WIDX_DEMOLISH) | (1ULL << WIDX_CONSTRUCTION));
2383     if (ride->lifecycle_flags & (RIDE_LIFECYCLE_INDESTRUCTIBLE | RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK))
2384         w->disabled_widgets |= (1ULL << WIDX_DEMOLISH);
2385 
2386     auto ft = Formatter::Common();
2387     ride->FormatNameTo(ft);
2388 
2389     uint32_t spriteIds[] = {
2390         SPR_CLOSED,
2391         SPR_OPEN,
2392         SPR_TESTING,
2393         SPR_G2_SIMULATE,
2394     };
2395     window_ride_main_widgets[WIDX_OPEN].image = spriteIds[EnumValue(ride->status)];
2396 
2397 #ifdef __SIMULATE_IN_RIDE_WINDOW__
2398     window_ride_main_widgets[WIDX_CLOSE_LIGHT].image = SPR_G2_RCT1_CLOSE_BUTTON_0 + (ride->status == RideStatus::Closed) * 2
2399         + WidgetIsPressed(w, WIDX_CLOSE_LIGHT);
2400     window_ride_main_widgets[WIDX_SIMULATE_LIGHT].image = SPR_G2_RCT1_SIMULATE_BUTTON_0
2401         + (ride->status == RideStatus::Simulating) * 2 + WidgetIsPressed(w, WIDX_SIMULATE_LIGHT);
2402     window_ride_main_widgets[WIDX_TEST_LIGHT].image = SPR_G2_RCT1_TEST_BUTTON_0 + (ride->status == RideStatus::Testing) * 2
2403         + WidgetIsPressed(w, WIDX_TEST_LIGHT);
2404 #else
2405     window_ride_main_widgets[WIDX_CLOSE_LIGHT].image = SPR_G2_RCT1_CLOSE_BUTTON_0 + (ride->status == RideStatus::Closed) * 2
2406         + WidgetIsPressed(w, WIDX_CLOSE_LIGHT);
2407 
2408     auto baseSprite = ride->status == RideStatus::Simulating ? SPR_G2_RCT1_SIMULATE_BUTTON_0 : SPR_G2_RCT1_TEST_BUTTON_0;
2409     window_ride_main_widgets[WIDX_TEST_LIGHT].image = baseSprite
2410         + (ride->status == RideStatus::Testing || ride->status == RideStatus::Simulating) * 2
2411         + WidgetIsPressed(w, WIDX_TEST_LIGHT);
2412 #endif
2413     window_ride_main_widgets[WIDX_OPEN_LIGHT].image = SPR_G2_RCT1_OPEN_BUTTON_0 + (ride->status == RideStatus::Open) * 2
2414         + WidgetIsPressed(w, WIDX_OPEN_LIGHT);
2415 
2416     window_ride_anchor_border_widgets(w);
2417 
2418     const int32_t offset = gCheatsAllowArbitraryRideTypeChanges ? 15 : 0;
2419     // Anchor main page specific widgets
2420     window_ride_main_widgets[WIDX_VIEWPORT].right = w->width - 26;
2421     window_ride_main_widgets[WIDX_VIEWPORT].bottom = w->height - (14 + offset);
2422     window_ride_main_widgets[WIDX_STATUS].right = w->width - 26;
2423     window_ride_main_widgets[WIDX_STATUS].top = w->height - (13 + offset);
2424     window_ride_main_widgets[WIDX_STATUS].bottom = w->height - (3 + offset);
2425     window_ride_main_widgets[WIDX_VIEW].right = w->width - 60;
2426     window_ride_main_widgets[WIDX_VIEW_DROPDOWN].right = w->width - 61;
2427     window_ride_main_widgets[WIDX_VIEW_DROPDOWN].left = w->width - 71;
2428     window_ride_main_widgets[WIDX_RIDE_TYPE].right = w->width - 26;
2429     window_ride_main_widgets[WIDX_RIDE_TYPE].top = w->height - 17;
2430     window_ride_main_widgets[WIDX_RIDE_TYPE].bottom = w->height - 4;
2431     window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].left = w->width - 37;
2432     window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].right = w->width - 27;
2433     window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].top = w->height - 16;
2434     window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].bottom = w->height - 5;
2435 
2436     if (!gCheatsAllowArbitraryRideTypeChanges)
2437     {
2438         window_ride_main_widgets[WIDX_RIDE_TYPE].type = WindowWidgetType::Empty;
2439         window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].type = WindowWidgetType::Empty;
2440     }
2441     else
2442     {
2443         window_ride_main_widgets[WIDX_RIDE_TYPE].type = WindowWidgetType::DropdownMenu;
2444         window_ride_main_widgets[WIDX_RIDE_TYPE].text = ride->GetRideTypeDescriptor().Naming.Name;
2445         window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].type = WindowWidgetType::Button;
2446     }
2447 
2448     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
2449 
2450     if (ThemeGetFlags() & UITHEME_FLAG_USE_LIGHTS_RIDE)
2451     {
2452         window_ride_main_widgets[WIDX_OPEN].type = WindowWidgetType::Empty;
2453         window_ride_main_widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::ImgBtn;
2454         window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::Empty;
2455 #ifdef __SIMULATE_IN_RIDE_WINDOW__
2456         if (ride->SupportsStatus(RideStatus::Simulating))
2457             window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::ImgBtn;
2458 #endif
2459         window_ride_main_widgets[WIDX_TEST_LIGHT].type = ride->SupportsStatus(RideStatus::Testing) ? WindowWidgetType::ImgBtn
2460                                                                                                    : WindowWidgetType::Empty;
2461         window_ride_main_widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::ImgBtn;
2462 
2463         height = 62;
2464         if (window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type != WindowWidgetType::Empty)
2465         {
2466             window_ride_main_widgets[WIDX_SIMULATE_LIGHT].top = height;
2467             window_ride_main_widgets[WIDX_SIMULATE_LIGHT].bottom = height + 13;
2468             height += 14;
2469         }
2470         if (window_ride_main_widgets[WIDX_TEST_LIGHT].type != WindowWidgetType::Empty)
2471         {
2472             window_ride_main_widgets[WIDX_TEST_LIGHT].top = height;
2473             window_ride_main_widgets[WIDX_TEST_LIGHT].bottom = height + 13;
2474             height += 14;
2475         }
2476         window_ride_main_widgets[WIDX_OPEN_LIGHT].top = height;
2477         window_ride_main_widgets[WIDX_OPEN_LIGHT].bottom = height + 13;
2478         height += 14 - 24 + RCT1_LIGHT_OFFSET;
2479     }
2480     else
2481     {
2482         window_ride_main_widgets[WIDX_OPEN].type = WindowWidgetType::FlatBtn;
2483         window_ride_main_widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::Empty;
2484         window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::Empty;
2485         window_ride_main_widgets[WIDX_TEST_LIGHT].type = WindowWidgetType::Empty;
2486         window_ride_main_widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::Empty;
2487         height = 46;
2488     }
2489     for (i = WIDX_CLOSE_LIGHT; i <= WIDX_OPEN_LIGHT; i++)
2490     {
2491         window_ride_main_widgets[i].left = w->width - 20;
2492         window_ride_main_widgets[i].right = w->width - 7;
2493     }
2494     for (i = WIDX_OPEN; i <= WIDX_DEMOLISH; i++, height += 24)
2495     {
2496         window_ride_main_widgets[i].left = w->width - 25;
2497         window_ride_main_widgets[i].right = w->width - 2;
2498         window_ride_main_widgets[i].top = height;
2499         window_ride_main_widgets[i].bottom = height + 23;
2500     }
2501 }
2502 
2503 /**
2504  *
2505  *  rct2: 0x006AF10A
2506  */
window_ride_get_status_overall_view(rct_window * w,Formatter & ft)2507 static rct_string_id window_ride_get_status_overall_view(rct_window* w, Formatter& ft)
2508 {
2509     auto stringId = STR_NONE;
2510     auto ride = get_ride(w->rideId);
2511     if (ride != nullptr)
2512     {
2513         ride->FormatStatusTo(ft);
2514         stringId = STR_BLACK_STRING;
2515         if ((ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) || (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED))
2516         {
2517             stringId = STR_RED_OUTLINED_STRING;
2518         }
2519     }
2520     return stringId;
2521 }
2522 
2523 /**
2524  *
2525  *  rct2: 0x006AEFEF
2526  */
window_ride_get_status_vehicle(rct_window * w,Formatter & ft)2527 static rct_string_id window_ride_get_status_vehicle(rct_window* w, Formatter& ft)
2528 {
2529     auto ride = get_ride(w->rideId);
2530     if (ride == nullptr)
2531         return STR_EMPTY;
2532 
2533     auto vehicle = GetEntity<Vehicle>(ride->vehicles[w->ride.view - 1]);
2534     if (vehicle == nullptr)
2535         return STR_EMPTY;
2536 
2537     if (vehicle->status != Vehicle::Status::Crashing && vehicle->status != Vehicle::Status::Crashed)
2538     {
2539         auto trackType = vehicle->GetTrackType();
2540         if (trackType == TrackElemType::BlockBrakes || trackType == TrackElemType::CableLiftHill
2541             || trackType == TrackElemType::Up25ToFlat || trackType == TrackElemType::Up60ToFlat
2542             || trackType == TrackElemType::DiagUp25ToFlat || trackType == TrackElemType::DiagUp60ToFlat)
2543         {
2544             if (ride->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_BLOCK_BRAKES) && vehicle->velocity == 0)
2545             {
2546                 ft.Add<rct_string_id>(STR_STOPPED_BY_BLOCK_BRAKES);
2547                 return STR_BLACK_STRING;
2548             }
2549         }
2550     }
2551 
2552     if (ride->type == RIDE_TYPE_MINI_GOLF)
2553         return STR_EMPTY;
2554 
2555     auto stringId = VehicleStatusNames[static_cast<size_t>(vehicle->status)];
2556     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SINGLE_SESSION)
2557         && vehicle->status <= Vehicle::Status::UnloadingPassengers)
2558     {
2559         stringId = SingleSessionVehicleStatusNames[static_cast<size_t>(vehicle->status)];
2560     }
2561 
2562     ft.Add<rct_string_id>(stringId);
2563     uint16_t speedInMph = (abs(vehicle->velocity) * 9) >> 18;
2564     ft.Add<uint16_t>(speedInMph);
2565     const RideComponentName stationName = GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.station);
2566     ft.Add<rct_string_id>(ride->num_stations > 1 ? stationName.number : stationName.singular);
2567     ft.Add<uint16_t>(vehicle->current_station + 1);
2568     return stringId != STR_CRASHING && stringId != STR_CRASHED_0 ? STR_BLACK_STRING : STR_RED_OUTLINED_STRING;
2569 }
2570 
2571 /**
2572  *
2573  *  rct2: 0x006AEF65
2574  */
window_ride_get_status_station(rct_window * w,Formatter & ft)2575 static rct_string_id window_ride_get_status_station(rct_window* w, Formatter& ft)
2576 {
2577     auto ride = get_ride(w->rideId);
2578     if (ride == nullptr)
2579         return STR_NONE;
2580 
2581     const auto stationIndex = GetStationIndexFromViewSelection(*w);
2582     if (!stationIndex)
2583     {
2584         return STR_NONE;
2585     }
2586 
2587     rct_string_id stringId = STR_EMPTY;
2588     // Entrance / exit
2589     if (ride->status == RideStatus::Closed)
2590     {
2591         if (ride_get_entrance_location(ride, static_cast<uint8_t>(*stationIndex)).IsNull())
2592             stringId = STR_NO_ENTRANCE;
2593         else if (ride_get_exit_location(ride, static_cast<uint8_t>(*stationIndex)).IsNull())
2594             stringId = STR_NO_EXIT;
2595     }
2596     else
2597     {
2598         if (ride_get_entrance_location(ride, static_cast<uint8_t>(*stationIndex)).IsNull())
2599             stringId = STR_EXIT_ONLY;
2600     }
2601     // Queue length
2602     if (stringId == STR_EMPTY)
2603     {
2604         stringId = STR_QUEUE_EMPTY;
2605         uint16_t queueLength = ride->stations[*stationIndex].QueueLength;
2606         if (queueLength == 1)
2607             stringId = STR_QUEUE_ONE_PERSON;
2608         else if (queueLength > 1)
2609             stringId = STR_QUEUE_PEOPLE;
2610 
2611         ft.Add<rct_string_id>(stringId);
2612         ft.Add<uint16_t>(queueLength);
2613     }
2614     else
2615     {
2616         ft.Add<rct_string_id>(stringId);
2617     }
2618 
2619     return STR_BLACK_STRING;
2620 }
2621 
2622 /**
2623  *
2624  *  rct2: 0x006AEE73
2625  */
window_ride_get_status(rct_window * w,Formatter & ft)2626 static rct_string_id window_ride_get_status(rct_window* w, Formatter& ft)
2627 {
2628     auto ride = get_ride(w->rideId);
2629     if (w->ride.view == 0)
2630         return window_ride_get_status_overall_view(w, ft);
2631     if (ride != nullptr && w->ride.view <= ride->num_vehicles)
2632         return window_ride_get_status_vehicle(w, ft);
2633     if (ride != nullptr && ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
2634         return window_ride_get_status_overall_view(w, ft);
2635     return window_ride_get_status_station(w, ft);
2636 }
2637 
2638 /**
2639  *
2640  *  rct2: 0x006AEE73
2641  */
window_ride_main_paint(rct_window * w,rct_drawpixelinfo * dpi)2642 static void window_ride_main_paint(rct_window* w, rct_drawpixelinfo* dpi)
2643 {
2644     rct_widget* widget;
2645 
2646     WindowDrawWidgets(w, dpi);
2647     window_ride_draw_tab_images(dpi, w);
2648 
2649     // Viewport and ear icon
2650     if (w->viewport != nullptr)
2651     {
2652         window_draw_viewport(dpi, w);
2653         if (w->viewport->flags & VIEWPORT_FLAG_SOUND_ON)
2654             gfx_draw_sprite(dpi, ImageId(SPR_HEARING_VIEWPORT), w->windowPos + ScreenCoordsXY{ 2, 2 });
2655     }
2656 
2657     // View dropdown
2658     auto ride = get_ride(w->rideId);
2659     if (ride == nullptr)
2660         return;
2661 
2662     auto ft = Formatter();
2663     if (w->ride.view != 0)
2664     {
2665         if (w->ride.view > ride->num_vehicles)
2666         {
2667             ft.Add<rct_string_id>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.station).number);
2668             ft.Add<uint16_t>(w->ride.view - ride->num_vehicles);
2669         }
2670         else
2671         {
2672             ft.Add<rct_string_id>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).number);
2673             ft.Add<uint16_t>(w->ride.view);
2674         }
2675     }
2676     else
2677     {
2678         ft.Add<rct_string_id>(STR_OVERALL_VIEW);
2679     }
2680 
2681     widget = &window_ride_main_widgets[WIDX_VIEW];
2682     DrawTextBasic(
2683         dpi, { w->windowPos.x + (widget->left + widget->right - 11) / 2, w->windowPos.y + widget->top },
2684         STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE });
2685 
2686     // Status
2687     ft = Formatter();
2688     widget = &window_ride_main_widgets[WIDX_STATUS];
2689     rct_string_id rideStatus = window_ride_get_status(w, ft);
2690     DrawTextEllipsised(
2691         dpi, w->windowPos + ScreenCoordsXY{ (widget->left + widget->right) / 2, widget->top }, widget->width(), rideStatus, ft,
2692         { TextAlignment::CENTRE });
2693 }
2694 
2695 #pragma endregion
2696 
2697 #pragma region Vehicle
2698 
2699 /**
2700  *
2701  *  rct2: 0x006B272D
2702  */
window_ride_vehicle_mouseup(rct_window * w,rct_widgetindex widgetIndex)2703 static void window_ride_vehicle_mouseup(rct_window* w, rct_widgetindex widgetIndex)
2704 {
2705     switch (widgetIndex)
2706     {
2707         case WIDX_CLOSE:
2708             window_close(w);
2709             break;
2710         case WIDX_TAB_1:
2711         case WIDX_TAB_2:
2712         case WIDX_TAB_3:
2713         case WIDX_TAB_4:
2714         case WIDX_TAB_5:
2715         case WIDX_TAB_6:
2716         case WIDX_TAB_7:
2717         case WIDX_TAB_8:
2718         case WIDX_TAB_9:
2719         case WIDX_TAB_10:
2720             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
2721             break;
2722     }
2723 }
2724 
2725 /**
2726  *
2727  *  rct2: 0x006B2ABB
2728  */
window_ride_vehicle_resize(rct_window * w)2729 static void window_ride_vehicle_resize(rct_window* w)
2730 {
2731     window_set_resize(w, 316, 214, 316, 214);
2732 }
2733 
2734 /**
2735  *
2736  *  rct2: 0x006B2748
2737  */
window_ride_vehicle_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)2738 static void window_ride_vehicle_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
2739 {
2740     auto ride = get_ride(w->rideId);
2741     if (ride == nullptr)
2742         return;
2743 
2744     switch (widgetIndex)
2745     {
2746         case WIDX_VEHICLE_TYPE_DROPDOWN:
2747             window_ride_show_vehicle_type_dropdown(w, &w->widgets[widgetIndex]);
2748             break;
2749         case WIDX_VEHICLE_TRAINS_INCREASE:
2750             if (ride->num_vehicles < MAX_VEHICLES_PER_RIDE)
2751                 ride->SetNumVehicles(ride->num_vehicles + 1);
2752             break;
2753         case WIDX_VEHICLE_TRAINS_DECREASE:
2754             if (ride->num_vehicles > 1)
2755                 ride->SetNumVehicles(ride->num_vehicles - 1);
2756             break;
2757         case WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE:
2758             if (ride->num_cars_per_train < MAX_CARS_PER_TRAIN)
2759                 ride->SetNumCarsPerVehicle(ride->num_cars_per_train + 1);
2760             break;
2761         case WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE:
2762             rct_ride_entry* rideEntry = ride->GetRideEntry();
2763             if (ride->num_cars_per_train > rideEntry->zero_cars + 1)
2764                 ride->SetNumCarsPerVehicle(ride->num_cars_per_train - 1);
2765             break;
2766     }
2767 }
2768 
2769 /**
2770  *
2771  *  rct2: 0x006B2767
2772  */
window_ride_vehicle_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)2773 static void window_ride_vehicle_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
2774 {
2775     if (dropdownIndex == -1)
2776         return;
2777 
2778     switch (widgetIndex)
2779     {
2780         case WIDX_VEHICLE_TYPE_DROPDOWN:
2781             if (dropdownIndex >= 0 && static_cast<std::size_t>(dropdownIndex) < VehicleDropdownData.size())
2782             {
2783                 auto ride = get_ride(w->rideId);
2784                 if (ride != nullptr)
2785                 {
2786                     auto newRideType = VehicleDropdownData[dropdownIndex].subtype_id;
2787                     ride->SetRideEntry(newRideType);
2788                 }
2789             }
2790             break;
2791     }
2792 }
2793 
2794 /**
2795  *
2796  *  rct2: 0x006B2AA1
2797  */
window_ride_vehicle_update(rct_window * w)2798 static void window_ride_vehicle_update(rct_window* w)
2799 {
2800     w->frame_no++;
2801     window_event_invalidate_call(w);
2802     widget_invalidate(w, WIDX_TAB_2);
2803 }
2804 
window_ride_vehicle_tooltip(rct_window * const w,const rct_widgetindex widgetIndex,rct_string_id fallback)2805 static OpenRCT2String window_ride_vehicle_tooltip(
2806     rct_window* const w, const rct_widgetindex widgetIndex, rct_string_id fallback)
2807 {
2808     auto ride = get_ride(w->rideId);
2809     if (ride == nullptr)
2810         return { STR_NONE, {} };
2811 
2812     switch (widgetIndex)
2813     {
2814         case WIDX_VEHICLE_TRAINS:
2815         case WIDX_VEHICLE_TRAINS_DECREASE:
2816         case WIDX_VEHICLE_TRAINS_INCREASE:
2817         {
2818             auto ft = Formatter();
2819             ft.Increment(12);
2820 
2821             RideComponentType vehicleType = ride->GetRideTypeDescriptor().NameConvention.vehicle;
2822             rct_string_id stringId = GetRideComponentName(vehicleType).count;
2823             if (ride->max_trains > 1)
2824             {
2825                 stringId = GetRideComponentName(vehicleType).count_plural;
2826             }
2827             ft.Add<rct_string_id>(stringId);
2828             ft.Add<uint16_t>(ride->max_trains);
2829             return { fallback, ft };
2830         }
2831         case WIDX_VEHICLE_CARS_PER_TRAIN:
2832         case WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE:
2833         case WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE:
2834         {
2835             auto rideEntry = ride->GetRideEntry();
2836             if (rideEntry == nullptr)
2837                 return { STR_NONE, {} };
2838 
2839             auto ft = Formatter();
2840             ft.Increment(16);
2841             ft.Add<uint16_t>(std::max(uint8_t(1), ride->MaxCarsPerTrain) - rideEntry->zero_cars);
2842 
2843             rct_string_id stringId = GetRideComponentName(RideComponentType::Car).singular;
2844             if (ride->MaxCarsPerTrain - rideEntry->zero_cars > 1)
2845             {
2846                 stringId = GetRideComponentName(RideComponentType::Car).plural;
2847             }
2848             ft.Add<rct_string_id>(stringId);
2849             return { fallback, ft };
2850         }
2851     }
2852     return { fallback, {} };
2853 }
2854 
2855 /**
2856  *
2857  *  rct2: 0x006B222C
2858  */
window_ride_vehicle_invalidate(rct_window * w)2859 static void window_ride_vehicle_invalidate(rct_window* w)
2860 {
2861     rct_widget* widgets;
2862     rct_ride_entry* rideEntry;
2863     rct_string_id stringId;
2864     int32_t carsPerTrain;
2865 
2866     widgets = window_ride_page_widgets[w->page];
2867     if (w->widgets != widgets)
2868     {
2869         w->widgets = widgets;
2870         WindowInitScrollWidgets(w);
2871     }
2872 
2873     window_ride_set_pressed_tab(w);
2874 
2875     auto ride = get_ride(w->rideId);
2876     if (ride == nullptr)
2877         return;
2878 
2879     rideEntry = ride->GetRideEntry();
2880 
2881     w->widgets[WIDX_TITLE].text = STR_ARG_20_STRINGID;
2882 
2883     // Widget setup
2884     carsPerTrain = ride->num_cars_per_train - rideEntry->zero_cars;
2885 
2886     // Vehicle type
2887     window_ride_vehicle_widgets[WIDX_VEHICLE_TYPE].text = rideEntry->naming.Name;
2888 
2889     // Trains
2890     if (rideEntry->cars_per_flat_ride > 1 || gCheatsDisableTrainLengthLimit)
2891     {
2892         window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS].type = WindowWidgetType::Spinner;
2893         window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_INCREASE].type = WindowWidgetType::Button;
2894         window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_DECREASE].type = WindowWidgetType::Button;
2895     }
2896     else
2897     {
2898         window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS].type = WindowWidgetType::Empty;
2899         window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_INCREASE].type = WindowWidgetType::Empty;
2900         window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_DECREASE].type = WindowWidgetType::Empty;
2901     }
2902 
2903     // Cars per train
2904     if (rideEntry->zero_cars + 1 < rideEntry->max_cars_in_train || gCheatsDisableTrainLengthLimit)
2905     {
2906         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].type = WindowWidgetType::Spinner;
2907         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE].type = WindowWidgetType::Button;
2908         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE].type = WindowWidgetType::Button;
2909     }
2910     else
2911     {
2912         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].type = WindowWidgetType::Empty;
2913         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE].type = WindowWidgetType::Empty;
2914         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE].type = WindowWidgetType::Empty;
2915     }
2916 
2917     auto ft = Formatter::Common();
2918     ft.Increment(6);
2919     ft.Add<uint16_t>(carsPerTrain);
2920     RideComponentType vehicleType = ride->GetRideTypeDescriptor().NameConvention.vehicle;
2921     stringId = GetRideComponentName(vehicleType).count;
2922     if (ride->num_vehicles > 1)
2923     {
2924         stringId = GetRideComponentName(vehicleType).count_plural;
2925     }
2926     ft.Add<rct_string_id>(stringId);
2927     ft.Add<uint16_t>(ride->num_vehicles);
2928 
2929     ft.Increment(8);
2930 
2931     ride->FormatNameTo(ft);
2932 
2933     window_ride_anchor_border_widgets(w);
2934     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
2935 
2936     if (ride->num_cars_per_train > (rideEntry->zero_cars + 1))
2937     {
2938         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].text = STR_X_CARS_PER_TRAIN;
2939     }
2940     else
2941     {
2942         window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].text = STR_1_CAR_PER_TRAIN;
2943     }
2944 }
2945 
2946 /**
2947  *
2948  *  rct2: 0x006B23DC
2949  */
window_ride_vehicle_paint(rct_window * w,rct_drawpixelinfo * dpi)2950 static void window_ride_vehicle_paint(rct_window* w, rct_drawpixelinfo* dpi)
2951 {
2952     WindowDrawWidgets(w, dpi);
2953     window_ride_draw_tab_images(dpi, w);
2954 
2955     auto ride = get_ride(w->rideId);
2956     if (ride == nullptr)
2957         return;
2958 
2959     auto rideEntry = ride->GetRideEntry();
2960     if (rideEntry == nullptr)
2961         return;
2962 
2963     auto screenCoords = w->windowPos + ScreenCoordsXY{ 8, 64 };
2964 
2965     // Description
2966     auto ft = Formatter();
2967     ft.Add<rct_string_id>(rideEntry->naming.Description);
2968     screenCoords.y += DrawTextWrapped(dpi, screenCoords, 300, STR_BLACK_STRING, ft, { TextAlignment::LEFT });
2969     screenCoords.y += 2;
2970 
2971     // Capacity
2972     ft = Formatter();
2973     ft.Add<rct_string_id>(rideEntry->capacity);
2974     DrawTextBasic(dpi, screenCoords, STR_CAPACITY, ft);
2975 
2976     // Excitement Factor
2977     if (rideEntry->excitement_multiplier > 0)
2978     {
2979         screenCoords.y += LIST_ROW_HEIGHT;
2980 
2981         ft = Formatter();
2982         ft.Add<int16_t>(rideEntry->excitement_multiplier);
2983         DrawTextBasic(dpi, screenCoords, STR_EXCITEMENT_FACTOR, ft);
2984     }
2985 
2986     // Intensity Factor
2987     if (rideEntry->intensity_multiplier > 0)
2988     {
2989         int32_t lineHeight = font_get_line_height(FontSpriteBase::MEDIUM);
2990         if (lineHeight != 10)
2991             screenCoords.x += 150;
2992         else
2993             screenCoords.y += LIST_ROW_HEIGHT;
2994 
2995         ft = Formatter();
2996         ft.Add<int16_t>(rideEntry->intensity_multiplier);
2997         DrawTextBasic(dpi, screenCoords, STR_INTENSITY_FACTOR, ft);
2998 
2999         if (lineHeight != 10)
3000             screenCoords.x -= 150;
3001     }
3002 
3003     // Nausea Factor
3004     if (rideEntry->nausea_multiplier > 0)
3005     {
3006         screenCoords.y += LIST_ROW_HEIGHT;
3007 
3008         ft = Formatter();
3009         ft.Add<int16_t>(rideEntry->nausea_multiplier);
3010         DrawTextBasic(dpi, screenCoords, STR_NAUSEA_FACTOR, ft);
3011     }
3012 }
3013 
3014 struct rct_vehicle_paintinfo
3015 {
3016     int16_t x;
3017     int16_t y;
3018     int32_t sprite_index;
3019     int32_t tertiary_colour;
3020 };
3021 
3022 static rct_vehicle_paintinfo _sprites_to_draw[144];
3023 
3024 /**
3025  *
3026  *  rct2: 0x006B2502
3027  */
window_ride_vehicle_scrollpaint(rct_window * w,rct_drawpixelinfo * dpi,int32_t scrollIndex)3028 static void window_ride_vehicle_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
3029 {
3030     auto ride = get_ride(w->rideId);
3031     if (ride == nullptr)
3032         return;
3033 
3034     rct_ride_entry* rideEntry = ride->GetRideEntry();
3035 
3036     // Background
3037     gfx_fill_rect(dpi, { { dpi->x, dpi->y }, { dpi->x + dpi->width, dpi->y + dpi->height } }, PALETTE_INDEX_12);
3038 
3039     rct_widget* widget = &window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_PREVIEW];
3040     int32_t startX = std::max(2, (widget->width() - ((ride->num_vehicles - 1) * 36)) / 2 - 25);
3041     int32_t startY = widget->height() - 4;
3042 
3043     rct_ride_entry_vehicle* rideVehicleEntry = &rideEntry->vehicles[ride_entry_get_vehicle_at_position(
3044         ride->subtype, ride->num_cars_per_train, 0)];
3045     startY += rideVehicleEntry->tab_height;
3046 
3047     // For each train
3048     for (int32_t i = 0; i < ride->num_vehicles; i++)
3049     {
3050         rct_vehicle_paintinfo* nextSpriteToDraw = _sprites_to_draw;
3051         int32_t x = startX;
3052         int32_t y = startY;
3053 
3054         // For each car in train
3055         for (int32_t j = 0; j < ride->num_cars_per_train; j++)
3056         {
3057             rideVehicleEntry = &rideEntry
3058                                     ->vehicles[ride_entry_get_vehicle_at_position(ride->subtype, ride->num_cars_per_train, j)];
3059             x += rideVehicleEntry->spacing / 17432;
3060             y -= (rideVehicleEntry->spacing / 2) / 17432;
3061 
3062             // Get colour of vehicle
3063             int32_t vehicleColourIndex = 0;
3064             switch (ride->colour_scheme_type & 3)
3065             {
3066                 case VEHICLE_COLOUR_SCHEME_SAME:
3067                     vehicleColourIndex = 0;
3068                     break;
3069                 case VEHICLE_COLOUR_SCHEME_PER_TRAIN:
3070                     vehicleColourIndex = i;
3071                     break;
3072                 case VEHICLE_COLOUR_SCHEME_PER_VEHICLE:
3073                     vehicleColourIndex = j;
3074                     break;
3075             }
3076             vehicle_colour vehicleColour = ride_get_vehicle_colour(ride, vehicleColourIndex);
3077 
3078             int32_t spriteIndex = 16;
3079             if (rideVehicleEntry->flags & VEHICLE_ENTRY_FLAG_USE_16_ROTATION_FRAMES)
3080                 spriteIndex /= 2;
3081 
3082             spriteIndex &= rideVehicleEntry->rotation_frame_mask;
3083             spriteIndex *= rideVehicleEntry->base_num_frames;
3084             spriteIndex += rideVehicleEntry->base_image_id;
3085             spriteIndex |= (vehicleColour.additional_1 << 24) | (vehicleColour.main << 19);
3086             spriteIndex |= IMAGE_TYPE_REMAP_2_PLUS;
3087 
3088             nextSpriteToDraw->x = x;
3089             nextSpriteToDraw->y = y;
3090             nextSpriteToDraw->sprite_index = spriteIndex;
3091             nextSpriteToDraw->tertiary_colour = vehicleColour.additional_2;
3092             nextSpriteToDraw++;
3093 
3094             x += rideVehicleEntry->spacing / 17432;
3095             y -= (rideVehicleEntry->spacing / 2) / 17432;
3096         }
3097 
3098         if (ride->type == RIDE_TYPE_REVERSER_ROLLER_COASTER)
3099         {
3100             rct_vehicle_paintinfo tmp = *(nextSpriteToDraw - 1);
3101             *(nextSpriteToDraw - 1) = *(nextSpriteToDraw - 2);
3102             *(nextSpriteToDraw - 2) = tmp;
3103         }
3104 
3105         rct_vehicle_paintinfo* current = nextSpriteToDraw;
3106         while (--current >= _sprites_to_draw)
3107             gfx_draw_sprite(dpi, current->sprite_index, { current->x, current->y }, current->tertiary_colour);
3108 
3109         startX += 36;
3110     }
3111 }
3112 
3113 #pragma endregion
3114 
3115 #pragma region Operating
3116 
3117 /**
3118  *
3119  *  rct2: 0x006B11D5
3120  */
window_ride_mode_tweak_increase(rct_window * w)3121 static void window_ride_mode_tweak_increase(rct_window* w)
3122 {
3123     auto ride = get_ride(w->rideId);
3124     if (ride == nullptr)
3125         return;
3126 
3127     const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings;
3128     uint8_t maxValue = operatingSettings.MaxValue;
3129     uint8_t minValue = gCheatsUnlockOperatingLimits ? 0 : operatingSettings.MinValue;
3130 
3131     if (gCheatsUnlockOperatingLimits)
3132     {
3133         maxValue = 255;
3134     }
3135 
3136     uint8_t increment = ride->mode == RideMode::Dodgems ? 10 : 1;
3137 
3138     set_operating_setting(
3139         w->rideId, RideSetSetting::Operation, std::clamp<int16_t>(ride->operation_option + increment, minValue, maxValue));
3140 }
3141 
3142 /**
3143  *
3144  *  rct2: 0x006B120A
3145  */
window_ride_mode_tweak_decrease(rct_window * w)3146 static void window_ride_mode_tweak_decrease(rct_window* w)
3147 {
3148     auto ride = get_ride(w->rideId);
3149     if (ride == nullptr)
3150         return;
3151 
3152     const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings;
3153     uint8_t maxValue = operatingSettings.MaxValue;
3154     uint8_t minValue = gCheatsUnlockOperatingLimits ? 0 : operatingSettings.MinValue;
3155     if (gCheatsUnlockOperatingLimits)
3156     {
3157         maxValue = 255;
3158     }
3159 
3160     uint8_t decrement = ride->mode == RideMode::Dodgems ? 10 : 1;
3161 
3162     set_operating_setting(
3163         w->rideId, RideSetSetting::Operation, std::clamp<int16_t>(ride->operation_option - decrement, minValue, maxValue));
3164 }
3165 
3166 /**
3167  *
3168  *  rct2: 0x006B1631
3169  */
window_ride_mode_dropdown(rct_window * w,rct_widget * widget)3170 static void window_ride_mode_dropdown(rct_window* w, rct_widget* widget)
3171 {
3172     rct_widget* dropdownWidget;
3173 
3174     dropdownWidget = widget - 1;
3175     auto ride = get_ride(w->rideId);
3176     if (ride == nullptr)
3177         return;
3178 
3179     auto availableModes = ride->GetAvailableModes();
3180 
3181     // Create dropdown list
3182     auto numAvailableModes = 0;
3183     auto checkedIndex = -1;
3184     for (auto i = 0; i < static_cast<uint8_t>(RideMode::Count); i++)
3185     {
3186         if (availableModes & (1ULL << i))
3187         {
3188             gDropdownItemsFormat[numAvailableModes] = STR_DROPDOWN_MENU_LABEL;
3189             gDropdownItemsArgs[numAvailableModes] = RideModeNames[i];
3190 
3191             if (ride->mode == static_cast<RideMode>(i))
3192                 checkedIndex = numAvailableModes;
3193 
3194             numAvailableModes++;
3195         }
3196     }
3197 
3198     WindowDropdownShowTextCustomWidth(
3199         { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
3200         w->colours[1], 0, Dropdown::Flag::StayOpen, numAvailableModes, widget->right - dropdownWidget->left);
3201 
3202     if (checkedIndex != -1)
3203     {
3204         Dropdown::SetChecked(checkedIndex, true);
3205     }
3206 }
3207 
3208 /**
3209  *
3210  *  rct2: 0x006B15C0
3211  */
window_ride_load_dropdown(rct_window * w,rct_widget * widget)3212 static void window_ride_load_dropdown(rct_window* w, rct_widget* widget)
3213 {
3214     auto ride = get_ride(w->rideId);
3215     if (ride == nullptr)
3216         return;
3217 
3218     auto dropdownWidget = widget - 1;
3219     for (auto i = 0; i < 5; i++)
3220     {
3221         gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
3222         gDropdownItemsArgs[i] = VehicleLoadNames[i];
3223     }
3224     WindowDropdownShowTextCustomWidth(
3225         { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
3226         w->colours[1], 0, Dropdown::Flag::StayOpen, 5, widget->right - dropdownWidget->left);
3227 
3228     Dropdown::SetChecked(ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD_MASK, true);
3229 }
3230 
3231 /**
3232  *
3233  *  rct2: 0x006B10A7
3234  */
window_ride_operating_mouseup(rct_window * w,rct_widgetindex widgetIndex)3235 static void window_ride_operating_mouseup(rct_window* w, rct_widgetindex widgetIndex)
3236 {
3237     const auto rideId = w->rideId;
3238     auto ride = get_ride(rideId);
3239     if (ride == nullptr)
3240         return;
3241 
3242     switch (widgetIndex)
3243     {
3244         case WIDX_CLOSE:
3245             window_close(w);
3246             break;
3247         case WIDX_TAB_1:
3248         case WIDX_TAB_2:
3249         case WIDX_TAB_3:
3250         case WIDX_TAB_4:
3251         case WIDX_TAB_5:
3252         case WIDX_TAB_6:
3253         case WIDX_TAB_7:
3254         case WIDX_TAB_8:
3255         case WIDX_TAB_9:
3256         case WIDX_TAB_10:
3257             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
3258             break;
3259         case WIDX_LOAD_CHECKBOX:
3260             set_operating_setting(rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_LOAD);
3261             break;
3262         case WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX:
3263             set_operating_setting(
3264                 rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_LEAVE_WHEN_ANOTHER_ARRIVES);
3265             break;
3266         case WIDX_MINIMUM_LENGTH_CHECKBOX:
3267             set_operating_setting(rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH);
3268             break;
3269         case WIDX_MAXIMUM_LENGTH_CHECKBOX:
3270             set_operating_setting(rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_MAXIMUM_LENGTH);
3271             break;
3272         case WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX:
3273             set_operating_setting(
3274                 rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS);
3275             break;
3276     }
3277 }
3278 
3279 /**
3280  *
3281  *  rct2: 0x006B1715
3282  */
window_ride_operating_resize(rct_window * w)3283 static void window_ride_operating_resize(rct_window* w)
3284 {
3285     window_set_resize(w, 316, 186, 316, 186);
3286 }
3287 
3288 /**
3289  *
3290  *  rct2: 0x006B10F4
3291  */
window_ride_operating_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)3292 static void window_ride_operating_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
3293 {
3294     const auto rideId = w->rideId;
3295     auto ride = get_ride(rideId);
3296     if (ride == nullptr)
3297         return;
3298 
3299     uint8_t upper_bound, lower_bound;
3300     switch (widgetIndex)
3301     {
3302         case WIDX_MODE_TWEAK_INCREASE:
3303             window_ride_mode_tweak_increase(w);
3304             break;
3305         case WIDX_MODE_TWEAK_DECREASE:
3306             window_ride_mode_tweak_decrease(w);
3307             break;
3308         case WIDX_LIFT_HILL_SPEED_INCREASE:
3309             upper_bound = gCheatsUnlockOperatingLimits ? 255 : ride->GetRideTypeDescriptor().LiftData.maximum_speed;
3310             lower_bound = gCheatsUnlockOperatingLimits ? 0 : ride->GetRideTypeDescriptor().LiftData.minimum_speed;
3311             set_operating_setting(
3312                 rideId, RideSetSetting::LiftHillSpeed,
3313                 std::clamp<int16_t>(ride->lift_hill_speed + 1, lower_bound, upper_bound));
3314             break;
3315         case WIDX_LIFT_HILL_SPEED_DECREASE:
3316             upper_bound = gCheatsUnlockOperatingLimits ? 255 : ride->GetRideTypeDescriptor().LiftData.maximum_speed;
3317             lower_bound = gCheatsUnlockOperatingLimits ? 0 : ride->GetRideTypeDescriptor().LiftData.minimum_speed;
3318             set_operating_setting(
3319                 rideId, RideSetSetting::LiftHillSpeed,
3320                 std::clamp<int16_t>(ride->lift_hill_speed - 1, lower_bound, upper_bound));
3321             break;
3322         case WIDX_MINIMUM_LENGTH_INCREASE:
3323             upper_bound = 250;
3324             lower_bound = 0;
3325             set_operating_setting(
3326                 rideId, RideSetSetting::MinWaitingTime,
3327                 std::clamp<int16_t>(ride->min_waiting_time + 1, lower_bound, upper_bound));
3328             break;
3329         case WIDX_MINIMUM_LENGTH_DECREASE:
3330             upper_bound = 250;
3331             lower_bound = 0;
3332             set_operating_setting(
3333                 rideId, RideSetSetting::MinWaitingTime,
3334                 std::clamp<int16_t>(ride->min_waiting_time - 1, lower_bound, upper_bound));
3335             break;
3336         case WIDX_MAXIMUM_LENGTH_INCREASE:
3337             upper_bound = 250;
3338             lower_bound = 0;
3339             set_operating_setting(
3340                 rideId, RideSetSetting::MaxWaitingTime,
3341                 std::clamp<int16_t>(ride->max_waiting_time + 1, lower_bound, upper_bound));
3342             break;
3343         case WIDX_MAXIMUM_LENGTH_DECREASE:
3344             upper_bound = 250;
3345             lower_bound = 0;
3346             set_operating_setting(
3347                 rideId, RideSetSetting::MaxWaitingTime,
3348                 std::clamp<int16_t>(ride->max_waiting_time - 1, lower_bound, upper_bound));
3349             break;
3350         case WIDX_MODE_DROPDOWN:
3351             window_ride_mode_dropdown(w, widget);
3352             break;
3353         case WIDX_LOAD_DROPDOWN:
3354             window_ride_load_dropdown(w, widget);
3355             break;
3356         case WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE:
3357             upper_bound = gCheatsUnlockOperatingLimits ? 255 : MAX_CIRCUITS_PER_RIDE;
3358             lower_bound = 1;
3359             set_operating_setting(
3360                 rideId, RideSetSetting::NumCircuits, std::clamp<int16_t>(ride->num_circuits + 1, lower_bound, upper_bound));
3361             break;
3362         case WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE:
3363             upper_bound = gCheatsUnlockOperatingLimits ? 255 : MAX_CIRCUITS_PER_RIDE;
3364             lower_bound = 1;
3365             set_operating_setting(
3366                 rideId, RideSetSetting::NumCircuits, std::clamp<int16_t>(ride->num_circuits - 1, lower_bound, upper_bound));
3367             break;
3368     }
3369 }
3370 
3371 /**
3372  *
3373  *  rct2: 0x006B1165
3374  */
window_ride_operating_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)3375 static void window_ride_operating_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
3376 {
3377     if (dropdownIndex == -1)
3378         return;
3379 
3380     const auto rideId = w->rideId;
3381     auto ride = get_ride(rideId);
3382     if (ride == nullptr)
3383         return;
3384 
3385     switch (widgetIndex)
3386     {
3387         case WIDX_MODE_DROPDOWN:
3388         {
3389             RideMode rideMode = RideMode::NullMode;
3390             auto availableModes = ride->GetAvailableModes();
3391             auto modeInDropdownIndex = -1;
3392             for (RideMode rideModeIndex = RideMode::Normal; rideModeIndex < RideMode::Count; rideModeIndex++)
3393             {
3394                 if (availableModes & EnumToFlag(rideModeIndex))
3395                 {
3396                     modeInDropdownIndex++;
3397                     if (modeInDropdownIndex == dropdownIndex)
3398                     {
3399                         rideMode = rideModeIndex;
3400                         break;
3401                     }
3402                 }
3403             }
3404             if (rideMode != RideMode::NullMode)
3405                 set_operating_setting(rideId, RideSetSetting::Mode, static_cast<uint8_t>(rideMode));
3406             break;
3407         }
3408         case WIDX_LOAD_DROPDOWN:
3409             set_operating_setting(
3410                 rideId, RideSetSetting::Departure, (ride->depart_flags & ~RIDE_DEPART_WAIT_FOR_LOAD_MASK) | dropdownIndex);
3411             break;
3412     }
3413 }
3414 
3415 /**
3416  *
3417  *  rct2: 0x006B178E
3418  */
window_ride_operating_update(rct_window * w)3419 static void window_ride_operating_update(rct_window* w)
3420 {
3421     w->frame_no++;
3422     window_event_invalidate_call(w);
3423     widget_invalidate(w, WIDX_TAB_3);
3424 
3425     auto ride = get_ride(w->rideId);
3426     if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_OPERATING)
3427     {
3428         ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_OPERATING;
3429         w->Invalidate();
3430     }
3431 }
3432 
3433 /**
3434  *
3435  *  rct2: 0x006B0B30
3436  */
window_ride_operating_invalidate(rct_window * w)3437 static void window_ride_operating_invalidate(rct_window* w)
3438 {
3439     rct_widget* widgets;
3440     rct_string_id format, caption, tooltip;
3441 
3442     widgets = window_ride_page_widgets[w->page];
3443     if (w->widgets != widgets)
3444     {
3445         w->widgets = widgets;
3446         WindowInitScrollWidgets(w);
3447     }
3448 
3449     window_ride_set_pressed_tab(w);
3450 
3451     auto ride = get_ride(w->rideId);
3452     if (ride == nullptr)
3453         return;
3454 
3455     auto ft = Formatter::Common();
3456     ride->FormatNameTo(ft);
3457 
3458     // Widget setup
3459     w->pressed_widgets &= ~(
3460         (1ULL << WIDX_LOAD_CHECKBOX) | (1ULL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX)
3461         | (1ULL << WIDX_MINIMUM_LENGTH_CHECKBOX) | (1ULL << WIDX_MAXIMUM_LENGTH_CHECKBOX)
3462         | (1ULL << WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX));
3463 
3464     // Sometimes, only one of the alternatives support lift hill pieces. Make sure to check both.
3465     bool hasAlternativeType = ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE);
3466     if (ride->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_LIFT_HILL)
3467         || (hasAlternativeType
3468             && GetRideTypeDescriptor(ride->GetRideTypeDescriptor().AlternateType).SupportsTrackPiece(TRACK_LIFT_HILL)))
3469     {
3470         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_LABEL].type = WindowWidgetType::Label;
3471         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED].type = WindowWidgetType::Spinner;
3472         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_INCREASE].type = WindowWidgetType::Button;
3473         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_DECREASE].type = WindowWidgetType::Button;
3474         ft.Rewind();
3475         ft.Increment(20);
3476         ft.Add<uint16_t>(ride->lift_hill_speed);
3477     }
3478     else
3479     {
3480         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_LABEL].type = WindowWidgetType::Empty;
3481         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED].type = WindowWidgetType::Empty;
3482         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_INCREASE].type = WindowWidgetType::Empty;
3483         window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_DECREASE].type = WindowWidgetType::Empty;
3484     }
3485 
3486     // Number of circuits
3487     if (ride->CanHaveMultipleCircuits())
3488     {
3489         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL].type = WindowWidgetType::Label;
3490         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS].type = WindowWidgetType::Spinner;
3491         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE].type = WindowWidgetType::Button;
3492         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE].type = WindowWidgetType::Button;
3493         ft.Rewind();
3494         ft.Increment(22);
3495         ft.Add<uint16_t>(ride->num_circuits);
3496     }
3497     else
3498     {
3499         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL].type = WindowWidgetType::Empty;
3500         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS].type = WindowWidgetType::Empty;
3501         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE].type = WindowWidgetType::Empty;
3502         window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE].type = WindowWidgetType::Empty;
3503     }
3504 
3505     // Leave if another vehicle arrives at station
3506     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LEAVE_WHEN_ANOTHER_VEHICLE_ARRIVES_AT_STATION)
3507         && ride->num_vehicles > 1 && !ride->IsBlockSectioned())
3508     {
3509         window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].type = WindowWidgetType::Checkbox;
3510         window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].tooltip
3511             = STR_LEAVE_IF_ANOTHER_VEHICLE_ARRIVES_TIP;
3512         window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].text = ride->GetRideTypeDescriptor()
3513                                                                                            .NameConvention.vehicle
3514                 == RideComponentType::Boat
3515             ? STR_LEAVE_IF_ANOTHER_BOAT_ARRIVES
3516             : STR_LEAVE_IF_ANOTHER_TRAIN_ARRIVES;
3517     }
3518     else
3519     {
3520         window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].type = WindowWidgetType::Empty;
3521     }
3522 
3523     // Synchronise with adjacent stations
3524     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_CAN_SYNCHRONISE_ADJACENT_STATIONS))
3525     {
3526         window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].type = WindowWidgetType::Checkbox;
3527         window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].image
3528             = STR_SYNCHRONISE_WITH_ADJACENT_STATIONS;
3529         window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].tooltip
3530             = STR_SYNCHRONISE_WITH_ADJACENT_STATIONS_TIP;
3531     }
3532     else
3533     {
3534         window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].type = WindowWidgetType::Empty;
3535     }
3536 
3537     // Mode
3538     window_ride_operating_widgets[WIDX_MODE].text = RideModeNames[static_cast<int>(ride->mode)];
3539 
3540     // Waiting
3541     window_ride_operating_widgets[WIDX_LOAD].text = VehicleLoadNames[(ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD_MASK)];
3542     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LOAD_OPTIONS))
3543     {
3544         window_ride_operating_widgets[WIDX_LOAD_CHECKBOX].type = WindowWidgetType::Checkbox;
3545         window_ride_operating_widgets[WIDX_LOAD].type = WindowWidgetType::DropdownMenu;
3546         window_ride_operating_widgets[WIDX_LOAD_DROPDOWN].type = WindowWidgetType::Button;
3547 
3548         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Checkbox;
3549         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH].type = WindowWidgetType::Spinner;
3550         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_INCREASE].type = WindowWidgetType::Button;
3551         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_DECREASE].type = WindowWidgetType::Button;
3552 
3553         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Checkbox;
3554         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH].type = WindowWidgetType::Spinner;
3555         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_INCREASE].type = WindowWidgetType::Button;
3556         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_DECREASE].type = WindowWidgetType::Button;
3557 
3558         ft.Rewind();
3559         ft.Increment(10);
3560         ft.Add<rct_string_id>(STR_FORMAT_SECONDS);
3561         ft.Add<uint16_t>(ride->min_waiting_time);
3562         ft.Add<rct_string_id>(STR_FORMAT_SECONDS);
3563         ft.Add<uint16_t>(ride->max_waiting_time);
3564 
3565         if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD)
3566             w->pressed_widgets |= (1ULL << WIDX_LOAD_CHECKBOX);
3567     }
3568     else
3569     {
3570         window_ride_operating_widgets[WIDX_LOAD_CHECKBOX].type = WindowWidgetType::Empty;
3571         window_ride_operating_widgets[WIDX_LOAD].type = WindowWidgetType::Empty;
3572         window_ride_operating_widgets[WIDX_LOAD_DROPDOWN].type = WindowWidgetType::Empty;
3573 
3574         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Empty;
3575         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH].type = WindowWidgetType::Empty;
3576         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_INCREASE].type = WindowWidgetType::Empty;
3577         window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_DECREASE].type = WindowWidgetType::Empty;
3578 
3579         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Empty;
3580         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH].type = WindowWidgetType::Empty;
3581         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_INCREASE].type = WindowWidgetType::Empty;
3582         window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_DECREASE].type = WindowWidgetType::Empty;
3583     }
3584 
3585     if (ride->depart_flags & RIDE_DEPART_LEAVE_WHEN_ANOTHER_ARRIVES)
3586         w->pressed_widgets |= (1ULL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX);
3587     if (ride->depart_flags & RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS)
3588         w->pressed_widgets |= (1ULL << WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX);
3589     if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH)
3590         w->pressed_widgets |= (1ULL << WIDX_MINIMUM_LENGTH_CHECKBOX);
3591     if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_MAXIMUM_LENGTH)
3592         w->pressed_widgets |= (1ULL << WIDX_MAXIMUM_LENGTH_CHECKBOX);
3593 
3594     // Mode specific functionality
3595     ft.Rewind();
3596     ft.Increment(18);
3597     ft.Add<uint16_t>(ride->operation_option);
3598     switch (ride->mode)
3599     {
3600         case RideMode::PoweredLaunchPasstrough:
3601         case RideMode::PoweredLaunch:
3602         case RideMode::UpwardLaunch:
3603         case RideMode::PoweredLaunchBlockSectioned:
3604             ft.Rewind();
3605             ft.Increment(18);
3606             ft.Add<uint16_t>((ride->launch_speed * 9) / 4);
3607             format = STR_RIDE_MODE_SPEED_VALUE;
3608             caption = STR_LAUNCH_SPEED;
3609             tooltip = STR_LAUNCH_SPEED_TIP;
3610             break;
3611         case RideMode::StationToStation:
3612             ft.Rewind();
3613             ft.Increment(18);
3614             ft.Add<uint16_t>((ride->speed * 9) / 4);
3615             format = STR_RIDE_MODE_SPEED_VALUE;
3616             caption = STR_SPEED;
3617             tooltip = STR_SPEED_TIP;
3618             break;
3619         case RideMode::Race:
3620             ft.Rewind();
3621             ft.Increment(18);
3622             ft.Add<uint16_t>(ride->num_laps);
3623             format = STR_NUMBER_OF_LAPS_VALUE;
3624             caption = STR_NUMBER_OF_LAPS;
3625             tooltip = STR_NUMBER_OF_LAPS_TIP;
3626             break;
3627         case RideMode::Dodgems:
3628             format = STR_RIDE_MODE_TIME_LIMIT_VALUE;
3629             caption = STR_TIME_LIMIT;
3630             tooltip = STR_TIME_LIMIT_TIP;
3631             break;
3632         case RideMode::Swing:
3633             format = STR_RIDE_MODE_NUMBER_OF_SWINGS_VALUE;
3634             caption = STR_NUMBER_OF_SWINGS;
3635             tooltip = STR_NUMBER_OF_SWINGS_TIP;
3636             break;
3637         case RideMode::Rotation:
3638         case RideMode::ForwardRotation:
3639         case RideMode::BackwardRotation:
3640             format = STR_NUMBER_OF_ROTATIONS_VALUE;
3641             caption = STR_NUMBER_OF_ROTATIONS;
3642             tooltip = STR_NUMBER_OF_ROTATIONS_TIP;
3643             break;
3644         default:
3645             format = STR_MAX_PEOPLE_ON_RIDE_VALUE;
3646             caption = STR_MAX_PEOPLE_ON_RIDE;
3647             tooltip = STR_MAX_PEOPLE_ON_RIDE_TIP;
3648             if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
3649                 format = 0;
3650             break;
3651     }
3652 
3653     if (format != 0)
3654     {
3655         if (ride->type == RIDE_TYPE_TWIST)
3656         {
3657             ft = Formatter::Common();
3658             ft.Increment(18);
3659             ft.Add<uint16_t>(ride->operation_option * 3);
3660         }
3661 
3662         window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].type = WindowWidgetType::Label;
3663         window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].text = caption;
3664         window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].tooltip = tooltip;
3665         window_ride_operating_widgets[WIDX_MODE_TWEAK].type = WindowWidgetType::Spinner;
3666         window_ride_operating_widgets[WIDX_MODE_TWEAK].text = format;
3667         window_ride_operating_widgets[WIDX_MODE_TWEAK_INCREASE].type = WindowWidgetType::Button;
3668         window_ride_operating_widgets[WIDX_MODE_TWEAK_DECREASE].type = WindowWidgetType::Button;
3669         w->pressed_widgets &= ~(1ULL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX);
3670     }
3671     else
3672     {
3673         window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].type = WindowWidgetType::Empty;
3674         window_ride_operating_widgets[WIDX_MODE_TWEAK].type = WindowWidgetType::Empty;
3675         window_ride_operating_widgets[WIDX_MODE_TWEAK_INCREASE].type = WindowWidgetType::Empty;
3676         window_ride_operating_widgets[WIDX_MODE_TWEAK_DECREASE].type = WindowWidgetType::Empty;
3677     }
3678 
3679     window_ride_anchor_border_widgets(w);
3680     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
3681 }
3682 
3683 /**
3684  *
3685  *  rct2: 0x006B1001
3686  */
window_ride_operating_paint(rct_window * w,rct_drawpixelinfo * dpi)3687 static void window_ride_operating_paint(rct_window* w, rct_drawpixelinfo* dpi)
3688 {
3689     WindowDrawWidgets(w, dpi);
3690     window_ride_draw_tab_images(dpi, w);
3691 
3692     auto ride = get_ride(w->rideId);
3693     if (ride == nullptr)
3694         return;
3695 
3696     // Horizontal rule between mode settings and depart settings
3697     gfx_fill_rect_inset(
3698         dpi,
3699         { w->windowPos + ScreenCoordsXY{ window_ride_operating_widgets[WIDX_PAGE_BACKGROUND].left + 4, 103 },
3700           w->windowPos + ScreenCoordsXY{ window_ride_operating_widgets[WIDX_PAGE_BACKGROUND].right - 5, 104 } },
3701         w->colours[1], INSET_RECT_FLAG_BORDER_INSET);
3702 
3703     // Number of block sections
3704     if (ride->IsBlockSectioned())
3705     {
3706         auto ft = Formatter();
3707         ft.Add<uint16_t>(ride->num_block_brakes + ride->num_stations);
3708         DrawTextBasic(
3709             dpi, w->windowPos + ScreenCoordsXY{ 21, ride->mode == RideMode::PoweredLaunchBlockSectioned ? 89 : 61 },
3710             STR_BLOCK_SECTIONS, ft, COLOUR_BLACK);
3711     }
3712 }
3713 
3714 #pragma endregion
3715 
3716 #pragma region Maintenance
3717 
3718 /**
3719  *
3720  *  rct2: 0x006B1AE4
3721  */
window_ride_locate_mechanic(rct_window * w)3722 static void window_ride_locate_mechanic(rct_window* w)
3723 {
3724     auto ride = get_ride(w->rideId);
3725     if (ride == nullptr)
3726         return;
3727 
3728     // First check if there is a mechanic assigned
3729     Peep* mechanic = ride_get_assigned_mechanic(ride);
3730 
3731     // Otherwise find the closest mechanic
3732     if (mechanic == nullptr)
3733         mechanic = ride_find_closest_mechanic(ride, 1);
3734 
3735     if (mechanic == nullptr)
3736         context_show_error(STR_UNABLE_TO_LOCATE_MECHANIC, STR_NONE, {});
3737     else
3738     {
3739         auto intent = Intent(WC_PEEP);
3740         intent.putExtra(INTENT_EXTRA_PEEP, mechanic);
3741         context_open_intent(&intent);
3742     }
3743 }
3744 
3745 /**
3746  *
3747  *  rct2: 0x006B7D08
3748  */
window_ride_maintenance_draw_bar(rct_window * w,rct_drawpixelinfo * dpi,const ScreenCoordsXY & coords,int32_t value,int32_t colour)3749 static void window_ride_maintenance_draw_bar(
3750     rct_window* w, rct_drawpixelinfo* dpi, const ScreenCoordsXY& coords, int32_t value, int32_t colour)
3751 {
3752     gfx_fill_rect_inset(dpi, { coords, coords + ScreenCoordsXY{ 149, 8 } }, w->colours[1], INSET_RECT_F_30);
3753     if (colour & BAR_BLINK)
3754     {
3755         colour &= ~BAR_BLINK;
3756         if (game_is_not_paused() && (gCurrentRealTimeTicks & 8))
3757             return;
3758     }
3759 
3760     value = ((186 * ((value * 2) & 0xFF)) >> 8) & 0xFF;
3761     if (value > 2)
3762     {
3763         gfx_fill_rect_inset(dpi, { coords + ScreenCoordsXY{ 2, 1 }, coords + ScreenCoordsXY{ value + 1, 7 } }, colour, 0);
3764     }
3765 }
3766 
3767 /**
3768  *
3769  *  rct2: 0x006B1AAD
3770  */
window_ride_maintenance_mouseup(rct_window * w,rct_widgetindex widgetIndex)3771 static void window_ride_maintenance_mouseup(rct_window* w, rct_widgetindex widgetIndex)
3772 {
3773     switch (widgetIndex)
3774     {
3775         case WIDX_CLOSE:
3776             window_close(w);
3777             break;
3778         case WIDX_TAB_1:
3779         case WIDX_TAB_2:
3780         case WIDX_TAB_3:
3781         case WIDX_TAB_4:
3782         case WIDX_TAB_5:
3783         case WIDX_TAB_6:
3784         case WIDX_TAB_7:
3785         case WIDX_TAB_8:
3786         case WIDX_TAB_9:
3787         case WIDX_TAB_10:
3788             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
3789             break;
3790         case WIDX_LOCATE_MECHANIC:
3791             window_ride_locate_mechanic(w);
3792             break;
3793         case WIDX_REFURBISH_RIDE:
3794             context_open_detail_window(WD_REFURBISH_RIDE, w->number);
3795             break;
3796     }
3797 }
3798 
3799 /**
3800  *
3801  *  rct2: 0x006B1D70
3802  */
window_ride_maintenance_resize(rct_window * w)3803 static void window_ride_maintenance_resize(rct_window* w)
3804 {
3805     window_set_resize(w, 316, 135, 316, 135);
3806 }
3807 
3808 /**
3809  *
3810  *  rct2: 0x006B1ACE
3811  */
window_ride_maintenance_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)3812 static void window_ride_maintenance_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
3813 {
3814     auto ride = get_ride(w->rideId);
3815     if (ride == nullptr)
3816         return;
3817 
3818     auto rideEntry = ride->GetRideEntry();
3819     if (rideEntry == nullptr)
3820         return;
3821 
3822     rct_widget* dropdownWidget = widget;
3823     int32_t j, num_items;
3824 
3825     switch (widgetIndex)
3826     {
3827         case WIDX_INSPECTION_INTERVAL_DROPDOWN:
3828             dropdownWidget--;
3829             for (int32_t i = 0; i < 7; i++)
3830             {
3831                 gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
3832                 gDropdownItemsArgs[i] = RideInspectionIntervalNames[i];
3833             }
3834             WindowDropdownShowTextCustomWidth(
3835                 { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
3836                 w->colours[1], 0, Dropdown::Flag::StayOpen, 7, widget->right - dropdownWidget->left);
3837 
3838             Dropdown::SetChecked(ride->inspection_interval, true);
3839             break;
3840 
3841         case WIDX_FORCE_BREAKDOWN:
3842             num_items = 1;
3843             for (j = 0; j < MAX_RIDE_TYPES_PER_RIDE_ENTRY; j++)
3844             {
3845                 if (rideEntry->ride_type[j] != RIDE_TYPE_NULL)
3846                     break;
3847             }
3848             gDropdownItemsFormat[0] = STR_DROPDOWN_MENU_LABEL;
3849             gDropdownItemsArgs[0] = STR_DEBUG_FIX_RIDE;
3850             for (int32_t i = 0; i < 8; i++)
3851             {
3852                 assert(j < static_cast<int32_t>(std::size(rideEntry->ride_type)));
3853                 if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns & static_cast<uint8_t>(1 << i))
3854                 {
3855                     if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned())
3856                     {
3857                         if (ride->num_vehicles != 1)
3858                             continue;
3859                     }
3860                     gDropdownItemsFormat[num_items] = STR_DROPDOWN_MENU_LABEL;
3861                     gDropdownItemsArgs[num_items] = RideBreakdownReasonNames[i];
3862                     num_items++;
3863                 }
3864             }
3865             if (num_items == 1)
3866             {
3867                 context_show_error(STR_DEBUG_NO_BREAKDOWNS_AVAILABLE, STR_NONE, {});
3868             }
3869             else
3870             {
3871                 WindowDropdownShowText(
3872                     { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top },
3873                     dropdownWidget->height() + 1, w->colours[1], Dropdown::Flag::StayOpen, num_items);
3874 
3875                 num_items = 1;
3876                 int32_t breakdownReason = ride->breakdown_reason_pending;
3877                 if (breakdownReason != BREAKDOWN_NONE && (ride->lifecycle_flags & RIDE_LIFECYCLE_BREAKDOWN_PENDING))
3878                 {
3879                     for (int32_t i = 0; i < 8; i++)
3880                     {
3881                         if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns & static_cast<uint8_t>(1 << i))
3882                         {
3883                             if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned())
3884                             {
3885                                 if (ride->num_vehicles != 1)
3886                                     continue;
3887                             }
3888                             if (i == breakdownReason)
3889                             {
3890                                 Dropdown::SetChecked(num_items, true);
3891                                 break;
3892                             }
3893                             gDropdownItemsFormat[num_items] = STR_DROPDOWN_MENU_LABEL;
3894                             gDropdownItemsArgs[num_items] = RideBreakdownReasonNames[i];
3895                             num_items++;
3896                         }
3897                     }
3898                 }
3899 
3900                 if ((ride->lifecycle_flags & RIDE_LIFECYCLE_BREAKDOWN_PENDING) == 0)
3901                 {
3902                     Dropdown::SetDisabled(0, true);
3903                 }
3904             }
3905             break;
3906     }
3907 }
3908 
3909 /**
3910  *
3911  *  rct2: 0x006B1AD9
3912  */
window_ride_maintenance_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)3913 static void window_ride_maintenance_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
3914 {
3915     if (dropdownIndex == -1)
3916         return;
3917 
3918     auto ride = get_ride(w->rideId);
3919     if (ride == nullptr)
3920         return;
3921 
3922     auto rideEntry = ride->GetRideEntry();
3923     if (rideEntry == nullptr)
3924         return;
3925 
3926     switch (widgetIndex)
3927     {
3928         case WIDX_INSPECTION_INTERVAL_DROPDOWN:
3929             set_operating_setting(w->rideId, RideSetSetting::InspectionInterval, dropdownIndex);
3930             break;
3931 
3932         case WIDX_FORCE_BREAKDOWN:
3933             if (dropdownIndex == 0)
3934             {
3935                 Vehicle* vehicle;
3936                 switch (ride->breakdown_reason_pending)
3937                 {
3938                     case BREAKDOWN_SAFETY_CUT_OUT:
3939                         if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
3940                             break;
3941                         for (int32_t i = 0; i < ride->num_vehicles; ++i)
3942                         {
3943                             for (vehicle = GetEntity<Vehicle>(ride->vehicles[i]); vehicle != nullptr;
3944                                  vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
3945                             {
3946                                 vehicle->ClearUpdateFlag(
3947                                     VEHICLE_UPDATE_FLAG_BROKEN_CAR | VEHICLE_UPDATE_FLAG_ZERO_VELOCITY
3948                                     | VEHICLE_UPDATE_FLAG_BROKEN_TRAIN);
3949                             }
3950                         }
3951                         break;
3952                     case BREAKDOWN_RESTRAINTS_STUCK_CLOSED:
3953                     case BREAKDOWN_RESTRAINTS_STUCK_OPEN:
3954                     case BREAKDOWN_DOORS_STUCK_CLOSED:
3955                     case BREAKDOWN_DOORS_STUCK_OPEN:
3956                         vehicle = GetEntity<Vehicle>(ride->vehicles[ride->broken_vehicle]);
3957                         if (vehicle != nullptr)
3958                         {
3959                             vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR);
3960                         }
3961                         break;
3962                     case BREAKDOWN_VEHICLE_MALFUNCTION:
3963                         vehicle = GetEntity<Vehicle>(ride->vehicles[ride->broken_vehicle]);
3964                         if (vehicle != nullptr)
3965                         {
3966                             vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN);
3967                         }
3968                         break;
3969                 }
3970                 ride->lifecycle_flags &= ~(RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN);
3971                 window_invalidate_by_number(WC_RIDE, w->number);
3972                 break;
3973             }
3974             if (ride->lifecycle_flags
3975                 & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
3976             {
3977                 context_show_error(STR_DEBUG_CANT_FORCE_BREAKDOWN, STR_DEBUG_RIDE_ALREADY_BROKEN, {});
3978             }
3979             else if (ride->status == RideStatus::Closed)
3980             {
3981                 context_show_error(STR_DEBUG_CANT_FORCE_BREAKDOWN, STR_DEBUG_RIDE_IS_CLOSED, {});
3982             }
3983             else
3984             {
3985                 int32_t j;
3986                 for (j = 0; j < MAX_RIDE_TYPES_PER_RIDE_ENTRY; j++)
3987                 {
3988                     if (rideEntry->ride_type[j] != RIDE_TYPE_NULL)
3989                         break;
3990                 }
3991                 int32_t i;
3992                 int32_t num_items = 1;
3993                 for (i = 0; i < BREAKDOWN_COUNT; i++)
3994                 {
3995                     assert(j < static_cast<int32_t>(std::size(rideEntry->ride_type)));
3996                     if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns & static_cast<uint8_t>(1 << i))
3997                     {
3998                         if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned())
3999                         {
4000                             if (ride->num_vehicles != 1)
4001                                 continue;
4002                         }
4003                         if (num_items == dropdownIndex)
4004                             break;
4005                         num_items++;
4006                     }
4007                 }
4008                 ride_prepare_breakdown(ride, i);
4009             }
4010             break;
4011     }
4012 }
4013 
4014 /**
4015  *
4016  *  rct2: 0x006B1D37
4017  */
window_ride_maintenance_update(rct_window * w)4018 static void window_ride_maintenance_update(rct_window* w)
4019 {
4020     w->frame_no++;
4021     window_event_invalidate_call(w);
4022     widget_invalidate(w, WIDX_TAB_4);
4023 
4024     auto ride = get_ride(w->rideId);
4025     if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAINTENANCE)
4026     {
4027         ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_MAINTENANCE;
4028         w->Invalidate();
4029     }
4030 }
4031 
4032 /**
4033  *
4034  *  rct2: 0x006B17C8
4035  */
window_ride_maintenance_invalidate(rct_window * w)4036 static void window_ride_maintenance_invalidate(rct_window* w)
4037 {
4038     auto widgets = window_ride_page_widgets[w->page];
4039     if (w->widgets != widgets)
4040     {
4041         w->widgets = widgets;
4042         WindowInitScrollWidgets(w);
4043     }
4044 
4045     window_ride_set_pressed_tab(w);
4046 
4047     auto ride = get_ride(w->rideId);
4048     if (ride == nullptr)
4049         return;
4050 
4051     auto ft = Formatter::Common();
4052     ride->FormatNameTo(ft);
4053 
4054     window_ride_maintenance_widgets[WIDX_INSPECTION_INTERVAL].text = RideInspectionIntervalNames[ride->inspection_interval];
4055 
4056     window_ride_anchor_border_widgets(w);
4057     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
4058 
4059     if (gConfigGeneral.debugging_tools && network_get_mode() == NETWORK_MODE_NONE)
4060     {
4061         window_ride_maintenance_widgets[WIDX_FORCE_BREAKDOWN].type = WindowWidgetType::FlatBtn;
4062     }
4063     else
4064     {
4065         window_ride_maintenance_widgets[WIDX_FORCE_BREAKDOWN].type = WindowWidgetType::Empty;
4066     }
4067 
4068     if (ride->GetRideTypeDescriptor().AvailableBreakdowns == 0 || !(ride->lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED))
4069     {
4070         w->disabled_widgets |= (1ULL << WIDX_REFURBISH_RIDE);
4071         window_ride_maintenance_widgets[WIDX_REFURBISH_RIDE].tooltip = STR_CANT_REFURBISH_NOT_NEEDED;
4072     }
4073     else
4074     {
4075         w->disabled_widgets &= ~(1ULL << WIDX_REFURBISH_RIDE);
4076         window_ride_maintenance_widgets[WIDX_REFURBISH_RIDE].tooltip = STR_REFURBISH_RIDE_TIP;
4077     }
4078 }
4079 
4080 /**
4081  *
4082  *  rct2: 0x006B1877
4083  */
window_ride_maintenance_paint(rct_window * w,rct_drawpixelinfo * dpi)4084 static void window_ride_maintenance_paint(rct_window* w, rct_drawpixelinfo* dpi)
4085 {
4086     WindowDrawWidgets(w, dpi);
4087     window_ride_draw_tab_images(dpi, w);
4088 
4089     auto ride = get_ride(w->rideId);
4090     if (ride == nullptr)
4091         return;
4092 
4093     // Locate mechanic button image
4094     rct_widget* widget = &window_ride_maintenance_widgets[WIDX_LOCATE_MECHANIC];
4095     auto screenCoords = w->windowPos + ScreenCoordsXY{ widget->left, widget->top };
4096     gfx_draw_sprite(
4097         dpi, (gStaffMechanicColour << 24) | IMAGE_TYPE_REMAP | IMAGE_TYPE_REMAP_2_PLUS | SPR_MECHANIC, screenCoords, 0);
4098 
4099     // Inspection label
4100     widget = &window_ride_maintenance_widgets[WIDX_INSPECTION_INTERVAL];
4101     screenCoords = w->windowPos + ScreenCoordsXY{ 4, widget->top + 1 };
4102     DrawTextBasic(dpi, screenCoords, STR_INSPECTION);
4103 
4104     // Reliability
4105     widget = &window_ride_maintenance_widgets[WIDX_PAGE_BACKGROUND];
4106     screenCoords = w->windowPos + ScreenCoordsXY{ widget->left + 4, widget->top + 4 };
4107 
4108     uint16_t reliability = ride->reliability_percentage;
4109     auto ft = Formatter();
4110     ft.Add<uint16_t>(reliability);
4111     DrawTextBasic(dpi, screenCoords, STR_RELIABILITY_LABEL_1757, ft);
4112     window_ride_maintenance_draw_bar(
4113         w, dpi, screenCoords + ScreenCoordsXY{ 103, 0 }, std::max<int32_t>(10, reliability), COLOUR_BRIGHT_GREEN);
4114     screenCoords.y += 11;
4115 
4116     uint16_t downTime = ride->downtime;
4117     ft = Formatter();
4118     ft.Add<uint16_t>(downTime);
4119     DrawTextBasic(dpi, screenCoords, STR_DOWN_TIME_LABEL_1889, ft);
4120     window_ride_maintenance_draw_bar(w, dpi, screenCoords + ScreenCoordsXY{ 103, 0 }, downTime, COLOUR_BRIGHT_RED);
4121     screenCoords.y += 26;
4122 
4123     // Last inspection
4124     rct_string_id stringId;
4125     if (ride->last_inspection <= 1)
4126         stringId = STR_TIME_SINCE_LAST_INSPECTION_MINUTE;
4127     else if (ride->last_inspection <= 240)
4128         stringId = STR_TIME_SINCE_LAST_INSPECTION_MINUTES;
4129     else
4130         stringId = STR_TIME_SINCE_LAST_INSPECTION_MORE_THAN_4_HOURS;
4131 
4132     ft = Formatter();
4133     ft.Add<uint16_t>(ride->last_inspection);
4134     DrawTextBasic(dpi, screenCoords, stringId, ft);
4135     screenCoords.y += 12;
4136 
4137     // Last / current breakdown
4138     if (ride->breakdown_reason == BREAKDOWN_NONE)
4139         return;
4140 
4141     stringId = (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) ? STR_CURRENT_BREAKDOWN : STR_LAST_BREAKDOWN;
4142     ft = Formatter();
4143     ft.Add<rct_string_id>(RideBreakdownReasonNames[ride->breakdown_reason]);
4144     DrawTextBasic(dpi, screenCoords, stringId, ft);
4145     screenCoords.y += 12;
4146 
4147     // Mechanic status
4148     if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
4149     {
4150         switch (ride->mechanic_status)
4151         {
4152             case RIDE_MECHANIC_STATUS_CALLING:
4153             {
4154                 stringId = STR_NO_MECHANICS_ARE_HIRED_MESSAGE;
4155 
4156                 for (auto peep : EntityList<Staff>())
4157                 {
4158                     if (peep->IsMechanic())
4159                     {
4160                         stringId = STR_CALLING_MECHANIC;
4161                         break;
4162                     }
4163                 }
4164                 break;
4165             }
4166             case RIDE_MECHANIC_STATUS_HEADING:
4167                 stringId = STR_MEHCANIC_IS_HEADING_FOR_THE_RIDE;
4168                 break;
4169             case RIDE_MECHANIC_STATUS_FIXING:
4170             case RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES:
4171                 stringId = STR_MEHCANIC_IS_FIXING_THE_RIDE;
4172                 break;
4173             default:
4174                 stringId = STR_EMPTY;
4175                 break;
4176         }
4177 
4178         if (stringId != STR_EMPTY)
4179         {
4180             if (stringId == STR_CALLING_MECHANIC || stringId == STR_NO_MECHANICS_ARE_HIRED_MESSAGE)
4181             {
4182                 DrawTextWrapped(dpi, screenCoords, 280, stringId, {}, { TextAlignment::LEFT });
4183             }
4184             else
4185             {
4186                 auto staff = GetEntity<Staff>(ride->mechanic);
4187                 if (staff != nullptr && staff->IsMechanic())
4188                 {
4189                     ft = Formatter();
4190                     staff->FormatNameTo(ft);
4191                     DrawTextWrapped(dpi, screenCoords, 280, stringId, ft, { TextAlignment::LEFT });
4192                 }
4193             }
4194         }
4195     }
4196 }
4197 
4198 #pragma endregion
4199 
4200 #pragma region Colour
4201 
window_ride_get_colour_button_image(int32_t colour)4202 static uint32_t window_ride_get_colour_button_image(int32_t colour)
4203 {
4204     return IMAGE_TYPE_TRANSPARENT | SPRITE_ID_PALETTE_COLOUR_1(colour) | SPR_PALETTE_BTN;
4205 }
4206 
window_ride_has_track_colour(Ride * ride,int32_t trackColour)4207 static int32_t window_ride_has_track_colour(Ride* ride, int32_t trackColour)
4208 {
4209     // Get station flags (shops don't have them)
4210     auto stationObjFlags = 0;
4211     if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
4212     {
4213         auto stationObj = ride_get_station_object(ride);
4214         if (stationObj != nullptr)
4215         {
4216             stationObjFlags = stationObj->Flags;
4217         }
4218     }
4219 
4220     switch (trackColour)
4221     {
4222         case 0:
4223             return (stationObjFlags & STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR)
4224                 || ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN);
4225         case 1:
4226             return (stationObjFlags & STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR)
4227                 || ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL);
4228         case 2:
4229             return ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS);
4230         default:
4231             return 0;
4232     }
4233 }
4234 
window_ride_set_track_colour_scheme(rct_window * w,const ScreenCoordsXY & screenPos)4235 static void window_ride_set_track_colour_scheme(rct_window* w, const ScreenCoordsXY& screenPos)
4236 {
4237     auto newColourScheme = static_cast<uint8_t>(w->ride_colour);
4238     auto info = get_map_coordinates_from_pos(screenPos, EnumsToFlags(ViewportInteractionItem::Ride));
4239 
4240     if (info.SpriteType != ViewportInteractionItem::Ride)
4241         return;
4242     if (info.Element->GetType() != TILE_ELEMENT_TYPE_TRACK)
4243         return;
4244     if (info.Element->AsTrack()->GetRideIndex() != w->rideId)
4245         return;
4246     if (info.Element->AsTrack()->GetColourScheme() == newColourScheme)
4247         return;
4248 
4249     auto z = info.Element->GetBaseZ();
4250     auto direction = info.Element->GetDirection();
4251     auto gameAction = RideSetColourSchemeAction(
4252         CoordsXYZD{ info.Loc, z, static_cast<Direction>(direction) }, info.Element->AsTrack()->GetTrackType(), newColourScheme);
4253     GameActions::Execute(&gameAction);
4254 }
4255 
4256 /**
4257  *
4258  *  rct2: 0x006B04FA
4259  */
window_ride_colour_close(rct_window * w)4260 static void window_ride_colour_close(rct_window* w)
4261 {
4262     if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE)))
4263         return;
4264 
4265     if (gCurrentToolWidget.window_classification != w->classification)
4266         return;
4267 
4268     if (gCurrentToolWidget.window_number != w->number)
4269         return;
4270 
4271     tool_cancel();
4272 }
4273 
4274 /**
4275  *
4276  *  rct2: 0x006B02A1
4277  */
window_ride_colour_mouseup(rct_window * w,rct_widgetindex widgetIndex)4278 static void window_ride_colour_mouseup(rct_window* w, rct_widgetindex widgetIndex)
4279 {
4280     switch (widgetIndex)
4281     {
4282         case WIDX_CLOSE:
4283             window_close(w);
4284             break;
4285         case WIDX_TAB_1:
4286         case WIDX_TAB_2:
4287         case WIDX_TAB_3:
4288         case WIDX_TAB_4:
4289         case WIDX_TAB_5:
4290         case WIDX_TAB_6:
4291         case WIDX_TAB_7:
4292         case WIDX_TAB_8:
4293         case WIDX_TAB_9:
4294         case WIDX_TAB_10:
4295             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
4296             break;
4297         case WIDX_PAINT_INDIVIDUAL_AREA:
4298             tool_set(w, WIDX_PAINT_INDIVIDUAL_AREA, Tool::PaintDown);
4299             break;
4300     }
4301 }
4302 
4303 /**
4304  *
4305  *  rct2: 0x006B0AB6
4306  */
window_ride_colour_resize(rct_window * w)4307 static void window_ride_colour_resize(rct_window* w)
4308 {
4309     window_set_resize(w, 316, 207, 316, 207);
4310 }
4311 
4312 /**
4313  *
4314  *  rct2: 0x006B02C6
4315  */
window_ride_colour_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)4316 static void window_ride_colour_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
4317 {
4318     vehicle_colour vehicleColour;
4319     int32_t i, numItems;
4320     rct_string_id stringId;
4321 
4322     auto ride = get_ride(w->rideId);
4323     if (ride == nullptr)
4324         return;
4325 
4326     auto rideEntry = ride->GetRideEntry();
4327     if (rideEntry == nullptr)
4328         return;
4329 
4330     auto colourSchemeIndex = w->ride_colour;
4331     auto dropdownWidget = widget - 1;
4332 
4333     switch (widgetIndex)
4334     {
4335         case WIDX_TRACK_COLOUR_SCHEME_DROPDOWN:
4336             for (i = 0; i < NUM_COLOUR_SCHEMES; i++)
4337             {
4338                 gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
4339                 gDropdownItemsArgs[i] = ColourSchemeNames[i];
4340             }
4341 
4342             WindowDropdownShowTextCustomWidth(
4343                 { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
4344                 w->colours[1], 0, Dropdown::Flag::StayOpen, 4, widget->right - dropdownWidget->left);
4345 
4346             Dropdown::SetChecked(colourSchemeIndex, true);
4347             break;
4348         case WIDX_TRACK_MAIN_COLOUR:
4349             WindowDropdownShowColour(w, widget, w->colours[1], ride->track_colour[colourSchemeIndex].main);
4350             break;
4351         case WIDX_TRACK_ADDITIONAL_COLOUR:
4352             WindowDropdownShowColour(w, widget, w->colours[1], ride->track_colour[colourSchemeIndex].additional);
4353             break;
4354         case WIDX_TRACK_SUPPORT_COLOUR:
4355             WindowDropdownShowColour(w, widget, w->colours[1], ride->track_colour[colourSchemeIndex].supports);
4356             break;
4357         case WIDX_MAZE_STYLE_DROPDOWN:
4358             for (i = 0; i < 4; i++)
4359             {
4360                 gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
4361                 gDropdownItemsArgs[i] = MazeOptions[i].text;
4362             }
4363 
4364             WindowDropdownShowTextCustomWidth(
4365                 { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
4366                 w->colours[1], 0, Dropdown::Flag::StayOpen, 4, widget->right - dropdownWidget->left);
4367 
4368             Dropdown::SetChecked(ride->track_colour[colourSchemeIndex].supports, true);
4369             break;
4370         case WIDX_ENTRANCE_STYLE_DROPDOWN:
4371         {
4372             auto ddIndex = 0;
4373             auto& objManager = GetContext()->GetObjectManager();
4374             for (i = 0; i < MAX_STATION_OBJECTS; i++)
4375             {
4376                 auto stationObj = static_cast<StationObject*>(objManager.GetLoadedObject(ObjectType::Station, i));
4377                 if (stationObj != nullptr)
4378                 {
4379                     gDropdownItemsFormat[ddIndex] = STR_DROPDOWN_MENU_LABEL;
4380                     gDropdownItemsArgs[ddIndex] = stationObj->NameStringId;
4381                     if (ride->entrance_style == i)
4382                     {
4383                         gDropdownItemsFormat[ddIndex] = STR_DROPDOWN_MENU_LABEL_SELECTED;
4384                     }
4385                     ddIndex++;
4386                 }
4387             }
4388 
4389             WindowDropdownShowTextCustomWidth(
4390                 { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
4391                 w->colours[1], 0, Dropdown::Flag::StayOpen, ddIndex, widget->right - dropdownWidget->left);
4392             break;
4393         }
4394         case WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN:
4395             for (i = 0; i < 3; i++)
4396             {
4397                 gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
4398                 gDropdownItemsArgs[i] = (GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).singular
4399                                          << 16)
4400                     | VehicleColourSchemeNames[i];
4401             }
4402 
4403             WindowDropdownShowTextCustomWidth(
4404                 { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
4405                 w->colours[1], 0, Dropdown::Flag::StayOpen, rideEntry->max_cars_in_train > 1 ? 3 : 2,
4406                 widget->right - dropdownWidget->left);
4407 
4408             Dropdown::SetChecked(ride->colour_scheme_type & 3, true);
4409             break;
4410         case WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN:
4411             numItems = ride->num_vehicles;
4412             if ((ride->colour_scheme_type & 3) != VEHICLE_COLOUR_SCHEME_PER_TRAIN)
4413                 numItems = ride->num_cars_per_train;
4414 
4415             stringId = (ride->colour_scheme_type & 3) == VEHICLE_COLOUR_SCHEME_PER_TRAIN ? STR_RIDE_COLOUR_TRAIN_OPTION
4416                                                                                          : STR_RIDE_COLOUR_VEHICLE_OPTION;
4417             for (i = 0; i < std::min(numItems, Dropdown::ItemsMaxSize); i++)
4418             {
4419                 gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
4420                 gDropdownItemsArgs[i] = (static_cast<int64_t>(i + 1) << 32)
4421                     | ((GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).capitalised) << 16)
4422                     | stringId;
4423             }
4424 
4425             WindowDropdownShowTextCustomWidth(
4426                 { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
4427                 w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left);
4428 
4429             Dropdown::SetChecked(w->vehicleIndex, true);
4430             break;
4431         case WIDX_VEHICLE_MAIN_COLOUR:
4432             vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex);
4433             WindowDropdownShowColour(w, widget, w->colours[1], vehicleColour.main);
4434             break;
4435         case WIDX_VEHICLE_ADDITIONAL_COLOUR_1:
4436             vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex);
4437             WindowDropdownShowColour(w, widget, w->colours[1], vehicleColour.additional_1);
4438             break;
4439         case WIDX_VEHICLE_ADDITIONAL_COLOUR_2:
4440             vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex);
4441             WindowDropdownShowColour(w, widget, w->colours[1], vehicleColour.additional_2);
4442             break;
4443     }
4444 }
4445 
4446 /**
4447  *
4448  *  rct2: 0x006B0331
4449  */
window_ride_colour_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)4450 static void window_ride_colour_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
4451 {
4452     if (dropdownIndex == -1)
4453         return;
4454 
4455     const auto rideId = w->rideId;
4456     switch (widgetIndex)
4457     {
4458         case WIDX_TRACK_COLOUR_SCHEME_DROPDOWN:
4459             w->ride_colour = static_cast<uint16_t>(dropdownIndex);
4460             w->Invalidate();
4461             break;
4462         case WIDX_TRACK_MAIN_COLOUR:
4463         {
4464             auto rideSetAppearanceAction = RideSetAppearanceAction(
4465                 rideId, RideSetAppearanceType::TrackColourMain, dropdownIndex, w->ride_colour);
4466             GameActions::Execute(&rideSetAppearanceAction);
4467         }
4468         break;
4469         case WIDX_TRACK_ADDITIONAL_COLOUR:
4470         {
4471             auto rideSetAppearanceAction = RideSetAppearanceAction(
4472                 rideId, RideSetAppearanceType::TrackColourAdditional, dropdownIndex, w->ride_colour);
4473             GameActions::Execute(&rideSetAppearanceAction);
4474         }
4475         break;
4476         case WIDX_TRACK_SUPPORT_COLOUR:
4477         {
4478             auto rideSetAppearanceAction = RideSetAppearanceAction(
4479                 rideId, RideSetAppearanceType::TrackColourSupports, dropdownIndex, w->ride_colour);
4480             GameActions::Execute(&rideSetAppearanceAction);
4481         }
4482         break;
4483         case WIDX_MAZE_STYLE_DROPDOWN:
4484         {
4485             auto rideSetAppearanceAction = RideSetAppearanceAction(
4486                 rideId, RideSetAppearanceType::MazeStyle, dropdownIndex, w->ride_colour);
4487             GameActions::Execute(&rideSetAppearanceAction);
4488         }
4489         break;
4490         case WIDX_ENTRANCE_STYLE_DROPDOWN:
4491         {
4492             auto ddIndex = 0;
4493             auto& objManager = GetContext()->GetObjectManager();
4494             for (auto i = 0; i < MAX_STATION_OBJECTS; i++)
4495             {
4496                 auto stationObj = static_cast<StationObject*>(objManager.GetLoadedObject(ObjectType::Station, i));
4497                 if (stationObj != nullptr)
4498                 {
4499                     if (ddIndex == dropdownIndex)
4500                     {
4501                         auto rideSetAppearanceAction = RideSetAppearanceAction(
4502                             rideId, RideSetAppearanceType::EntranceStyle, ddIndex, 0);
4503                         GameActions::Execute(&rideSetAppearanceAction);
4504                         break;
4505                     }
4506                     ddIndex++;
4507                 }
4508             }
4509             break;
4510         }
4511         case WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN:
4512         {
4513             auto rideSetAppearanceAction = RideSetAppearanceAction(
4514                 rideId, RideSetAppearanceType::VehicleColourScheme, dropdownIndex, 0);
4515             GameActions::Execute(&rideSetAppearanceAction);
4516             w->vehicleIndex = 0;
4517         }
4518         break;
4519         case WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN:
4520             w->vehicleIndex = dropdownIndex;
4521             w->Invalidate();
4522             break;
4523         case WIDX_VEHICLE_MAIN_COLOUR:
4524         {
4525             auto rideSetAppearanceAction = RideSetAppearanceAction(
4526                 rideId, RideSetAppearanceType::VehicleColourBody, dropdownIndex, w->vehicleIndex);
4527             GameActions::Execute(&rideSetAppearanceAction);
4528         }
4529         break;
4530         case WIDX_VEHICLE_ADDITIONAL_COLOUR_1:
4531         {
4532             auto rideSetAppearanceAction = RideSetAppearanceAction(
4533                 rideId, RideSetAppearanceType::VehicleColourTrim, dropdownIndex, w->vehicleIndex);
4534             GameActions::Execute(&rideSetAppearanceAction);
4535         }
4536         break;
4537         case WIDX_VEHICLE_ADDITIONAL_COLOUR_2:
4538         {
4539             auto rideSetAppearanceAction = RideSetAppearanceAction(
4540                 rideId, RideSetAppearanceType::VehicleColourTernary, dropdownIndex, w->vehicleIndex);
4541             GameActions::Execute(&rideSetAppearanceAction);
4542         }
4543         break;
4544     }
4545 }
4546 
4547 /**
4548  *
4549  *  rct2: 0x006B0A8F
4550  */
window_ride_colour_update(rct_window * w)4551 static void window_ride_colour_update(rct_window* w)
4552 {
4553     w->frame_no++;
4554     window_event_invalidate_call(w);
4555     widget_invalidate(w, WIDX_TAB_5);
4556     widget_invalidate(w, WIDX_VEHICLE_PREVIEW);
4557 }
4558 
4559 /**
4560  *
4561  *  rct2: 0x006B04EC
4562  */
window_ride_colour_tooldown(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)4563 static void window_ride_colour_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
4564 {
4565     if (widgetIndex == WIDX_PAINT_INDIVIDUAL_AREA)
4566         window_ride_set_track_colour_scheme(w, screenCoords);
4567 }
4568 
4569 /**
4570  *
4571  *  rct2: 0x006B04F3
4572  */
window_ride_colour_tooldrag(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)4573 static void window_ride_colour_tooldrag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
4574 {
4575     if (widgetIndex == WIDX_PAINT_INDIVIDUAL_AREA)
4576         window_ride_set_track_colour_scheme(w, screenCoords);
4577 }
4578 
4579 /**
4580  *
4581  *  rct2: 0x006AFB36
4582  */
window_ride_colour_invalidate(rct_window * w)4583 static void window_ride_colour_invalidate(rct_window* w)
4584 {
4585     TrackColour trackColour;
4586     vehicle_colour vehicleColour;
4587 
4588     auto widgets = window_ride_page_widgets[w->page];
4589     if (w->widgets != widgets)
4590     {
4591         w->widgets = widgets;
4592         WindowInitScrollWidgets(w);
4593     }
4594 
4595     window_ride_set_pressed_tab(w);
4596 
4597     auto ride = get_ride(w->rideId);
4598     if (ride == nullptr)
4599         return;
4600 
4601     auto rideEntry = ride->GetRideEntry();
4602     if (rideEntry == nullptr)
4603         return;
4604 
4605     w->widgets[WIDX_TITLE].text = STR_ARG_16_STRINGID;
4606     auto ft = Formatter::Common();
4607     ft.Increment(16);
4608     ride->FormatNameTo(ft);
4609 
4610     // Track colours
4611     int32_t colourScheme = w->ride_colour;
4612     trackColour = ride_get_track_colour(ride, colourScheme);
4613 
4614     // Maze style
4615     if (ride->type == RIDE_TYPE_MAZE)
4616     {
4617         window_ride_colour_widgets[WIDX_MAZE_STYLE].type = WindowWidgetType::DropdownMenu;
4618         window_ride_colour_widgets[WIDX_MAZE_STYLE_DROPDOWN].type = WindowWidgetType::Button;
4619         window_ride_colour_widgets[WIDX_MAZE_STYLE].text = MazeOptions[trackColour.supports].text;
4620     }
4621     else
4622     {
4623         window_ride_colour_widgets[WIDX_MAZE_STYLE].type = WindowWidgetType::Empty;
4624         window_ride_colour_widgets[WIDX_MAZE_STYLE_DROPDOWN].type = WindowWidgetType::Empty;
4625     }
4626 
4627     // Track, multiple colour schemes
4628     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SUPPORTS_MULTIPLE_TRACK_COLOUR))
4629     {
4630         window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME].type = WindowWidgetType::DropdownMenu;
4631         window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Button;
4632         window_ride_colour_widgets[WIDX_PAINT_INDIVIDUAL_AREA].type = WindowWidgetType::FlatBtn;
4633     }
4634     else
4635     {
4636         window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME].type = WindowWidgetType::Empty;
4637         window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty;
4638         window_ride_colour_widgets[WIDX_PAINT_INDIVIDUAL_AREA].type = WindowWidgetType::Empty;
4639     }
4640 
4641     // Track main colour
4642     if (window_ride_has_track_colour(ride, 0))
4643     {
4644         window_ride_colour_widgets[WIDX_TRACK_MAIN_COLOUR].type = WindowWidgetType::ColourBtn;
4645         window_ride_colour_widgets[WIDX_TRACK_MAIN_COLOUR].image = window_ride_get_colour_button_image(trackColour.main);
4646     }
4647     else
4648     {
4649         window_ride_colour_widgets[WIDX_TRACK_MAIN_COLOUR].type = WindowWidgetType::Empty;
4650     }
4651 
4652     // Track additional colour
4653     if (window_ride_has_track_colour(ride, 1))
4654     {
4655         window_ride_colour_widgets[WIDX_TRACK_ADDITIONAL_COLOUR].type = WindowWidgetType::ColourBtn;
4656         window_ride_colour_widgets[WIDX_TRACK_ADDITIONAL_COLOUR].image = window_ride_get_colour_button_image(
4657             trackColour.additional);
4658     }
4659     else
4660     {
4661         window_ride_colour_widgets[WIDX_TRACK_ADDITIONAL_COLOUR].type = WindowWidgetType::Empty;
4662     }
4663 
4664     // Track supports colour
4665     if (window_ride_has_track_colour(ride, 2) && ride->type != RIDE_TYPE_MAZE)
4666     {
4667         window_ride_colour_widgets[WIDX_TRACK_SUPPORT_COLOUR].type = WindowWidgetType::ColourBtn;
4668         window_ride_colour_widgets[WIDX_TRACK_SUPPORT_COLOUR].image = window_ride_get_colour_button_image(trackColour.supports);
4669     }
4670     else
4671     {
4672         window_ride_colour_widgets[WIDX_TRACK_SUPPORT_COLOUR].type = WindowWidgetType::Empty;
4673     }
4674 
4675     // Track preview
4676     if (ride->GetRideTypeDescriptor().HasFlag(
4677             RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN | RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL
4678             | RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS))
4679         window_ride_colour_widgets[WIDX_TRACK_PREVIEW].type = WindowWidgetType::Spinner;
4680     else
4681         window_ride_colour_widgets[WIDX_TRACK_PREVIEW].type = WindowWidgetType::Empty;
4682 
4683     // Entrance style
4684     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT))
4685     {
4686         window_ride_colour_widgets[WIDX_ENTRANCE_PREVIEW].type = WindowWidgetType::Spinner;
4687         window_ride_colour_widgets[WIDX_ENTRANCE_STYLE].type = WindowWidgetType::DropdownMenu;
4688         window_ride_colour_widgets[WIDX_ENTRANCE_STYLE_DROPDOWN].type = WindowWidgetType::Button;
4689 
4690         auto stringId = STR_NONE;
4691         auto stationObj = ride_get_station_object(ride);
4692         if (stationObj != nullptr)
4693         {
4694             stringId = stationObj->NameStringId;
4695         }
4696         window_ride_colour_widgets[WIDX_ENTRANCE_STYLE].text = stringId;
4697     }
4698     else
4699     {
4700         window_ride_colour_widgets[WIDX_ENTRANCE_PREVIEW].type = WindowWidgetType::Empty;
4701         window_ride_colour_widgets[WIDX_ENTRANCE_STYLE].type = WindowWidgetType::Empty;
4702         window_ride_colour_widgets[WIDX_ENTRANCE_STYLE_DROPDOWN].type = WindowWidgetType::Empty;
4703     }
4704 
4705     // Vehicle colours
4706     if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES)
4707         && ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_VEHICLE_COLOURS))
4708     {
4709         int32_t vehicleColourSchemeType = ride->colour_scheme_type & 3;
4710         if (vehicleColourSchemeType == 0)
4711             w->vehicleIndex = 0;
4712 
4713         vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex);
4714 
4715         window_ride_colour_widgets[WIDX_VEHICLE_PREVIEW].type = WindowWidgetType::Scroll;
4716         window_ride_colour_widgets[WIDX_VEHICLE_MAIN_COLOUR].type = WindowWidgetType::ColourBtn;
4717         window_ride_colour_widgets[WIDX_VEHICLE_MAIN_COLOUR].image = window_ride_get_colour_button_image(vehicleColour.main);
4718 
4719         bool allowChangingAdditionalColour1 = false;
4720         bool allowChangingAdditionalColour2 = false;
4721 
4722         for (int32_t i = 0; i < ride->num_cars_per_train; i++)
4723         {
4724             uint8_t vehicleTypeIndex = ride_entry_get_vehicle_at_position(ride->subtype, ride->num_cars_per_train, i);
4725 
4726             if (rideEntry->vehicles[vehicleTypeIndex].flags & VEHICLE_ENTRY_FLAG_ENABLE_ADDITIONAL_COLOUR_1)
4727             {
4728                 allowChangingAdditionalColour1 = true;
4729             }
4730             if (rideEntry->vehicles[vehicleTypeIndex].flags & VEHICLE_ENTRY_FLAG_ENABLE_ADDITIONAL_COLOUR_2)
4731             {
4732                 allowChangingAdditionalColour2 = true;
4733             }
4734         }
4735 
4736         // Additional colours
4737         if (allowChangingAdditionalColour1)
4738         {
4739             window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_1].type = WindowWidgetType::ColourBtn;
4740             window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_1].image = window_ride_get_colour_button_image(
4741                 vehicleColour.additional_1);
4742             if (allowChangingAdditionalColour2)
4743             {
4744                 window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_2].type = WindowWidgetType::ColourBtn;
4745                 window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_2].image = window_ride_get_colour_button_image(
4746                     vehicleColour.additional_2);
4747             }
4748             else
4749             {
4750                 window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_2].type = WindowWidgetType::Empty;
4751             }
4752         }
4753         else
4754         {
4755             window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_1].type = WindowWidgetType::Empty;
4756             window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_2].type = WindowWidgetType::Empty;
4757         }
4758 
4759         // Vehicle colour scheme type
4760         if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_VEHICLE_IS_INTEGRAL)
4761             && (ride->num_cars_per_train | ride->num_vehicles) > 1)
4762         {
4763             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::DropdownMenu;
4764             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Button;
4765         }
4766         else
4767         {
4768             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::Empty;
4769             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty;
4770         }
4771         ft.Rewind();
4772         ft.Increment(6);
4773         ft.Add<rct_string_id>(VehicleColourSchemeNames[vehicleColourSchemeType]);
4774         ft.Add<rct_string_id>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).singular);
4775         ft.Add<rct_string_id>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).capitalised);
4776         ft.Add<uint16_t>(w->vehicleIndex + 1);
4777 
4778         // Vehicle index
4779         if (vehicleColourSchemeType != 0)
4780         {
4781             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::DropdownMenu;
4782             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Button;
4783             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].text = vehicleColourSchemeType == 1
4784                 ? STR_RIDE_COLOUR_TRAIN_VALUE
4785                 : STR_RIDE_COLOUR_VEHICLE_VALUE;
4786         }
4787         else
4788         {
4789             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::Empty;
4790             window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Empty;
4791         }
4792     }
4793     else
4794     {
4795         window_ride_colour_widgets[WIDX_VEHICLE_PREVIEW].type = WindowWidgetType::Empty;
4796         window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::Empty;
4797         window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty;
4798         window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::Empty;
4799         window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Empty;
4800         window_ride_colour_widgets[WIDX_VEHICLE_MAIN_COLOUR].type = WindowWidgetType::Empty;
4801         window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_1].type = WindowWidgetType::Empty;
4802         window_ride_colour_widgets[WIDX_VEHICLE_ADDITIONAL_COLOUR_2].type = WindowWidgetType::Empty;
4803     }
4804 
4805     ft.Rewind();
4806     ft.Increment(14);
4807     ft.Add<rct_string_id>(ColourSchemeNames[colourScheme]);
4808 
4809     window_ride_anchor_border_widgets(w);
4810     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
4811 }
4812 
4813 /**
4814  *
4815  *  rct2: 0x006AFF3E
4816  */
window_ride_colour_paint(rct_window * w,rct_drawpixelinfo * dpi)4817 static void window_ride_colour_paint(rct_window* w, rct_drawpixelinfo* dpi)
4818 {
4819     // TODO: This should use lists and identified sprites
4820     rct_drawpixelinfo clippedDpi;
4821 
4822     auto ride = get_ride(w->rideId);
4823     if (ride == nullptr)
4824         return;
4825 
4826     WindowDrawWidgets(w, dpi);
4827     window_ride_draw_tab_images(dpi, w);
4828 
4829     // Track / shop item preview
4830     const auto& trackPreviewWidget = window_ride_colour_widgets[WIDX_TRACK_PREVIEW];
4831     if (trackPreviewWidget.type != WindowWidgetType::Empty)
4832         gfx_fill_rect(
4833             dpi,
4834             { { w->windowPos + ScreenCoordsXY{ trackPreviewWidget.left + 1, trackPreviewWidget.top + 1 } },
4835               { w->windowPos + ScreenCoordsXY{ trackPreviewWidget.right - 1, trackPreviewWidget.bottom - 1 } } },
4836             PALETTE_INDEX_12);
4837 
4838     auto trackColour = ride_get_track_colour(ride, w->ride_colour);
4839 
4840     //
4841     auto rideEntry = ride->GetRideEntry();
4842     if (rideEntry == nullptr || rideEntry->shop_item[0] == ShopItem::None)
4843     {
4844         auto screenCoords = w->windowPos + ScreenCoordsXY{ trackPreviewWidget.left, trackPreviewWidget.top };
4845 
4846         // Track
4847         if (ride->type == RIDE_TYPE_MAZE)
4848         {
4849             gfx_draw_sprite(dpi, ImageId(MazeOptions[trackColour.supports].sprite), screenCoords);
4850         }
4851         else
4852         {
4853             auto typeDescriptor = ride->GetRideTypeDescriptor();
4854             int32_t spriteIndex = typeDescriptor.ColourPreview.Track;
4855             if (spriteIndex != 0)
4856             {
4857                 gfx_draw_sprite(dpi, ImageId(spriteIndex, trackColour.main, trackColour.additional), screenCoords);
4858             }
4859 
4860             // Supports
4861             spriteIndex = typeDescriptor.ColourPreview.Supports;
4862             if (spriteIndex != 0)
4863             {
4864                 gfx_draw_sprite(dpi, ImageId(spriteIndex, trackColour.supports), screenCoords);
4865             }
4866         }
4867     }
4868     else
4869     {
4870         auto screenCoords = w->windowPos
4871             + ScreenCoordsXY{ (trackPreviewWidget.left + trackPreviewWidget.right) / 2 - 8,
4872                               (trackPreviewWidget.bottom + trackPreviewWidget.top) / 2 - 6 };
4873 
4874         ShopItem shopItem = rideEntry->shop_item[1] == ShopItem::None ? rideEntry->shop_item[0] : rideEntry->shop_item[1];
4875         gfx_draw_sprite(dpi, ImageId(GetShopItemDescriptor(shopItem).Image, ride->track_colour[0].main), screenCoords);
4876     }
4877 
4878     // Entrance preview
4879     trackColour = ride_get_track_colour(ride, 0);
4880     const auto& entrancePreviewWidget = w->widgets[WIDX_ENTRANCE_PREVIEW];
4881     if (entrancePreviewWidget.type != WindowWidgetType::Empty)
4882     {
4883         if (clip_drawpixelinfo(
4884                 &clippedDpi, dpi,
4885                 w->windowPos + ScreenCoordsXY{ entrancePreviewWidget.left + 1, entrancePreviewWidget.top + 1 },
4886                 entrancePreviewWidget.width(), entrancePreviewWidget.height()))
4887         {
4888             gfx_clear(&clippedDpi, PALETTE_INDEX_12);
4889 
4890             auto stationObj = ride_get_station_object(ride);
4891             if (stationObj != nullptr && stationObj->BaseImageId != 0)
4892             {
4893                 int32_t terniaryColour = 0;
4894                 if (stationObj->Flags & STATION_OBJECT_FLAGS::IS_TRANSPARENT)
4895                 {
4896                     terniaryColour = IMAGE_TYPE_TRANSPARENT | (EnumValue(GlassPaletteIds[trackColour.main]) << 19);
4897                 }
4898 
4899                 int32_t spriteIndex = SPRITE_ID_PALETTE_COLOUR_2(trackColour.main, trackColour.additional);
4900                 spriteIndex += stationObj->BaseImageId;
4901 
4902                 // Back
4903                 gfx_draw_sprite(&clippedDpi, spriteIndex, { 34, 20 }, terniaryColour);
4904 
4905                 // Front
4906                 gfx_draw_sprite(&clippedDpi, spriteIndex + 4, { 34, 20 }, terniaryColour);
4907 
4908                 // Glass
4909                 if (terniaryColour != 0)
4910                     gfx_draw_sprite(&clippedDpi, ((spriteIndex + 20) & 0x7FFFF) + terniaryColour, { 34, 20 }, terniaryColour);
4911             }
4912         }
4913 
4914         DrawTextEllipsised(dpi, { w->windowPos.x + 3, w->windowPos.y + 103 }, 97, STR_STATION_STYLE, {});
4915     }
4916 }
4917 
4918 /**
4919  *
4920  *  rct2: 0x006B0192
4921  */
window_ride_colour_scrollpaint(rct_window * w,rct_drawpixelinfo * dpi,int32_t scrollIndex)4922 static void window_ride_colour_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
4923 {
4924     auto ride = get_ride(w->rideId);
4925     if (ride == nullptr)
4926         return;
4927 
4928     auto rideEntry = ride->GetRideEntry();
4929     if (rideEntry == nullptr)
4930         return;
4931 
4932     auto vehiclePreviewWidget = &window_ride_colour_widgets[WIDX_VEHICLE_PREVIEW];
4933     auto vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex);
4934 
4935     // Background colour
4936     gfx_fill_rect(dpi, { { dpi->x, dpi->y }, { dpi->x + dpi->width - 1, dpi->y + dpi->height - 1 } }, PALETTE_INDEX_12);
4937 
4938     // ?
4939     auto screenCoords = ScreenCoordsXY{ vehiclePreviewWidget->width() / 2, vehiclePreviewWidget->height() - 15 };
4940 
4941     // ?
4942     auto trainCarIndex = (ride->colour_scheme_type & 3) == RIDE_COLOUR_SCHEME_DIFFERENT_PER_CAR ? w->vehicleIndex
4943                                                                                                 : rideEntry->tab_vehicle;
4944 
4945     rct_ride_entry_vehicle* rideVehicleEntry = &rideEntry->vehicles[ride_entry_get_vehicle_at_position(
4946         ride->subtype, ride->num_cars_per_train, trainCarIndex)];
4947 
4948     screenCoords.y += rideVehicleEntry->tab_height;
4949 
4950     // Draw the coloured spinning vehicle
4951     uint32_t spriteIndex = (rideVehicleEntry->flags & VEHICLE_ENTRY_FLAG_USE_16_ROTATION_FRAMES) ? w->frame_no / 4
4952                                                                                                  : w->frame_no / 2;
4953     spriteIndex &= rideVehicleEntry->rotation_frame_mask;
4954     spriteIndex *= rideVehicleEntry->base_num_frames;
4955     spriteIndex += rideVehicleEntry->base_image_id;
4956     spriteIndex |= (vehicleColour.additional_1 << 24) | (vehicleColour.main << 19);
4957     spriteIndex |= IMAGE_TYPE_REMAP_2_PLUS;
4958     gfx_draw_sprite(dpi, ImageId::FromUInt32(spriteIndex, vehicleColour.additional_2), screenCoords);
4959 }
4960 
4961 #pragma endregion
4962 
4963 #pragma region Music
4964 
4965 static constexpr const uint8_t MusicStyleOrder[] = {
4966     MUSIC_STYLE_GENTLE,         MUSIC_STYLE_SUMMER,        MUSIC_STYLE_WATER,
4967     MUSIC_STYLE_RAGTIME,        MUSIC_STYLE_TECHNO,        MUSIC_STYLE_MECHANICAL,
4968     MUSIC_STYLE_MODERN,         MUSIC_STYLE_WILD_WEST,     MUSIC_STYLE_PIRATES,
4969     MUSIC_STYLE_ROCK,           MUSIC_STYLE_ROCK_STYLE_2,  MUSIC_STYLE_ROCK_STYLE_3,
4970     MUSIC_STYLE_FANTASY,        MUSIC_STYLE_HORROR,        MUSIC_STYLE_TOYLAND,
4971     MUSIC_STYLE_CANDY_STYLE,    MUSIC_STYLE_ROMAN_FANFARE, MUSIC_STYLE_ORIENTAL,
4972     MUSIC_STYLE_MARTIAN,        MUSIC_STYLE_SPACE,         MUSIC_STYLE_JUNGLE_DRUMS,
4973     MUSIC_STYLE_JURASSIC,       MUSIC_STYLE_EGYPTIAN,      MUSIC_STYLE_DODGEMS_BEAT,
4974     MUSIC_STYLE_SNOW,           MUSIC_STYLE_ICE,           MUSIC_STYLE_MEDIEVAL,
4975     MUSIC_STYLE_URBAN,          MUSIC_STYLE_ORGAN,         MUSIC_STYLE_CUSTOM_MUSIC_1,
4976     MUSIC_STYLE_CUSTOM_MUSIC_2,
4977 };
4978 
4979 static std::vector<ObjectEntryIndex> window_ride_current_music_style_order;
4980 
4981 /**
4982  *
4983  *  rct2: 0x006B215D
4984  */
window_ride_toggle_music(rct_window * w)4985 static void window_ride_toggle_music(rct_window* w)
4986 {
4987     auto ride = get_ride(w->rideId);
4988     if (ride != nullptr)
4989     {
4990         int32_t activateMusic = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) ? 0 : 1;
4991         set_operating_setting(w->rideId, RideSetSetting::Music, activateMusic);
4992     }
4993 }
4994 
4995 /**
4996  *
4997  *  rct2: 0x006B1ED7
4998  */
window_ride_music_mouseup(rct_window * w,rct_widgetindex widgetIndex)4999 static void window_ride_music_mouseup(rct_window* w, rct_widgetindex widgetIndex)
5000 {
5001     switch (widgetIndex)
5002     {
5003         case WIDX_CLOSE:
5004             window_close(w);
5005             break;
5006         case WIDX_TAB_1:
5007         case WIDX_TAB_2:
5008         case WIDX_TAB_3:
5009         case WIDX_TAB_4:
5010         case WIDX_TAB_5:
5011         case WIDX_TAB_6:
5012         case WIDX_TAB_7:
5013         case WIDX_TAB_8:
5014         case WIDX_TAB_9:
5015         case WIDX_TAB_10:
5016             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
5017             break;
5018         case WIDX_PLAY_MUSIC:
5019             window_ride_toggle_music(w);
5020             break;
5021     }
5022 }
5023 
5024 /**
5025  *
5026  *  rct2: 0x006AF4A2
5027  */
window_ride_music_resize(rct_window * w)5028 static void window_ride_music_resize(rct_window* w)
5029 {
5030     w->flags |= WF_RESIZABLE;
5031     window_set_resize(w, 316, 81, 316, 81);
5032 }
5033 
GetMusicStyleOrder(ObjectEntryIndex musicObjectIndex)5034 static std::optional<size_t> GetMusicStyleOrder(ObjectEntryIndex musicObjectIndex)
5035 {
5036     auto& objManager = GetContext()->GetObjectManager();
5037     auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, musicObjectIndex));
5038 
5039     // Get the index in the order list
5040     auto originalStyleId = musicObj->GetOriginalStyleId();
5041     if (originalStyleId.has_value())
5042     {
5043         auto it = std::find(std::begin(MusicStyleOrder), std::end(MusicStyleOrder), originalStyleId.value());
5044         if (it != std::end(MusicStyleOrder))
5045         {
5046             return std::distance(std::begin(MusicStyleOrder), it);
5047         }
5048     }
5049 
5050     return std::nullopt;
5051 }
5052 
5053 /**
5054  *
5055  *  rct2: 0x006B1EFC
5056  */
window_ride_music_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)5057 static void window_ride_music_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
5058 {
5059     if (widgetIndex != WIDX_MUSIC_DROPDOWN)
5060         return;
5061 
5062     auto dropdownWidget = widget - 1;
5063     auto ride = get_ride(w->rideId);
5064     if (ride == nullptr)
5065         return;
5066 
5067     // Construct list of available music
5068     auto& musicOrder = window_ride_current_music_style_order;
5069     musicOrder.clear();
5070     auto& objManager = GetContext()->GetObjectManager();
5071     for (ObjectEntryIndex i = 0; i < MAX_MUSIC_OBJECTS; i++)
5072     {
5073         auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, i));
5074         if (musicObj != nullptr)
5075         {
5076             // Hide custom music if the WAV file does not exist
5077             auto originalStyleId = musicObj->GetOriginalStyleId();
5078             if (originalStyleId.has_value()
5079                 && (originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_1 || originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_2))
5080             {
5081                 auto numTracks = musicObj->GetTrackCount();
5082                 if (numTracks != 0)
5083                 {
5084                     auto track0 = musicObj->GetTrack(0);
5085                     if (!track0->Asset.IsAvailable())
5086                     {
5087                         continue;
5088                     }
5089                 }
5090                 else
5091                 {
5092                     continue;
5093                 }
5094             }
5095 
5096             if (gCheatsUnlockOperatingLimits || musicObj->SupportsRideType(ride->type))
5097             {
5098                 musicOrder.push_back(i);
5099             }
5100         }
5101     }
5102 
5103     // Sort available music by the original RCT2 list order
5104     std::stable_sort(musicOrder.begin(), musicOrder.end(), [](const ObjectEntryIndex& a, const ObjectEntryIndex& b) {
5105         auto orderA = GetMusicStyleOrder(a);
5106         auto orderB = GetMusicStyleOrder(b);
5107         return orderA < orderB;
5108     });
5109 
5110     // Setup dropdown list
5111     auto numItems = musicOrder.size();
5112     for (size_t i = 0; i < numItems; i++)
5113     {
5114         auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, musicOrder[i]));
5115         gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
5116         gDropdownItemsArgs[i] = musicObj->NameStringId;
5117     }
5118 
5119     WindowDropdownShowTextCustomWidth(
5120         { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
5121         w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left);
5122 
5123     // Set currently checked item
5124     for (size_t i = 0; i < numItems; i++)
5125     {
5126         if (musicOrder[i] == ride->music)
5127         {
5128             Dropdown::SetChecked(static_cast<int32_t>(i), true);
5129         }
5130     }
5131 }
5132 
5133 /**
5134  *
5135  *  rct2: 0x006B1F03
5136  */
window_ride_music_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)5137 static void window_ride_music_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
5138 {
5139     if (widgetIndex == WIDX_MUSIC_DROPDOWN && dropdownIndex >= 0
5140         && static_cast<size_t>(dropdownIndex) < window_ride_current_music_style_order.size())
5141     {
5142         auto musicStyle = window_ride_current_music_style_order[dropdownIndex];
5143         set_operating_setting(w->rideId, RideSetSetting::MusicType, musicStyle);
5144     }
5145 }
5146 
5147 /**
5148  *
5149  *  rct2: 0x006B2198
5150  */
window_ride_music_update(rct_window * w)5151 static void window_ride_music_update(rct_window* w)
5152 {
5153     w->frame_no++;
5154     window_event_invalidate_call(w);
5155     widget_invalidate(w, WIDX_TAB_6);
5156 }
5157 
5158 /**
5159  *
5160  *  rct2: 0x006B1DEA
5161  */
window_ride_music_invalidate(rct_window * w)5162 static void window_ride_music_invalidate(rct_window* w)
5163 {
5164     auto widgets = window_ride_page_widgets[w->page];
5165     if (w->widgets != widgets)
5166     {
5167         w->widgets = widgets;
5168         WindowInitScrollWidgets(w);
5169     }
5170 
5171     window_ride_set_pressed_tab(w);
5172 
5173     auto ride = get_ride(w->rideId);
5174     if (ride == nullptr)
5175         return;
5176 
5177     auto ft = Formatter::Common();
5178     ride->FormatNameTo(ft);
5179 
5180     // Set selected music
5181     rct_string_id musicName = STR_NONE;
5182     auto& objManager = GetContext()->GetObjectManager();
5183     auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music));
5184     if (musicObj != nullptr)
5185     {
5186         musicName = musicObj->NameStringId;
5187     }
5188     window_ride_music_widgets[WIDX_MUSIC].text = musicName;
5189 
5190     // Set music activated
5191     auto isMusicActivated = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) != 0;
5192     if (isMusicActivated)
5193     {
5194         w->pressed_widgets |= (1ULL << WIDX_PLAY_MUSIC);
5195         w->disabled_widgets &= ~(1ULL << WIDX_MUSIC);
5196         w->disabled_widgets &= ~(1ULL << WIDX_MUSIC_DROPDOWN);
5197     }
5198     else
5199     {
5200         w->pressed_widgets &= ~(1ULL << WIDX_PLAY_MUSIC);
5201         w->disabled_widgets |= (1ULL << WIDX_MUSIC);
5202         w->disabled_widgets |= (1ULL << WIDX_MUSIC_DROPDOWN);
5203     }
5204 
5205     window_ride_anchor_border_widgets(w);
5206     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
5207 }
5208 
5209 /**
5210  *
5211  *  rct2: 0x006B1ECC
5212  */
window_ride_music_paint(rct_window * w,rct_drawpixelinfo * dpi)5213 static void window_ride_music_paint(rct_window* w, rct_drawpixelinfo* dpi)
5214 {
5215     WindowDrawWidgets(w, dpi);
5216     window_ride_draw_tab_images(dpi, w);
5217 }
5218 
5219 #pragma endregion
5220 
5221 #pragma region Measurements
5222 
get_rating_name(ride_rating rating)5223 static rct_string_id get_rating_name(ride_rating rating)
5224 {
5225     int32_t index = std::clamp<int32_t>(rating >> 8, 0, static_cast<int32_t>(std::size(RatingNames)) - 1);
5226     return RatingNames[index];
5227 }
5228 
5229 /**
5230  *
5231  *  rct2: 0x006D2804
5232   when al == 0*/
cancel_scenery_selection()5233 static void cancel_scenery_selection()
5234 {
5235     gGamePaused &= ~GAME_PAUSED_SAVING_TRACK;
5236     gTrackDesignSaveMode = false;
5237     OpenRCT2::Audio::Resume();
5238 
5239     rct_window* main_w = window_get_main();
5240     if (main_w != nullptr)
5241     {
5242         main_w->viewport->flags &= ~(VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE);
5243     }
5244 
5245     gfx_invalidate_screen();
5246     tool_cancel();
5247 }
5248 
5249 /**
5250  *
5251  *  rct2: 0x006D27A3
5252  */
setup_scenery_selection(rct_window * w)5253 static void setup_scenery_selection(rct_window* w)
5254 {
5255     if (gTrackDesignSaveMode)
5256     {
5257         cancel_scenery_selection();
5258     }
5259 
5260     while (tool_set(w, WIDX_BACKGROUND, Tool::Crosshair))
5261         ;
5262 
5263     gTrackDesignSaveRideIndex = w->rideId;
5264 
5265     track_design_save_init();
5266     gGamePaused |= GAME_PAUSED_SAVING_TRACK;
5267     gTrackDesignSaveMode = true;
5268 
5269     OpenRCT2::Audio::StopAll();
5270 
5271     rct_window* w_main = window_get_main();
5272     if (w_main != nullptr)
5273     {
5274         w_main->viewport->flags |= (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE);
5275     }
5276 
5277     gfx_invalidate_screen();
5278 }
5279 
5280 /**
5281  *
5282  *  rct2: 0x006D3026
5283  */
window_ride_measurements_design_reset()5284 static void window_ride_measurements_design_reset()
5285 {
5286     track_design_save_reset_scenery();
5287 }
5288 
5289 /**
5290  *
5291  *  rct2: 0x006D303D
5292  */
window_ride_measurements_design_select_nearby_scenery()5293 static void window_ride_measurements_design_select_nearby_scenery()
5294 {
5295     track_design_save_select_nearby_scenery(gTrackDesignSaveRideIndex);
5296 }
5297 
5298 /**
5299  *
5300  *  rct2: 0x006AD4DA
5301  */
window_ride_measurements_design_cancel()5302 void window_ride_measurements_design_cancel()
5303 {
5304     if (gTrackDesignSaveMode)
5305     {
5306         cancel_scenery_selection();
5307     }
5308 }
5309 
TrackDesignCallback(int32_t result,const utf8 * path)5310 static void TrackDesignCallback(int32_t result, [[maybe_unused]] const utf8* path)
5311 {
5312     if (result == MODAL_RESULT_OK)
5313     {
5314         track_repository_scan();
5315     }
5316     gfx_invalidate_screen();
5317 };
5318 
5319 /**
5320  *
5321  *  rct2: 0x006AD4CD
5322  */
window_ride_measurements_design_save(rct_window * w)5323 static void window_ride_measurements_design_save(rct_window* w)
5324 {
5325     Ride* ride = get_ride(w->rideId);
5326     _trackDesign = ride->SaveToTrackDesign();
5327     if (!_trackDesign)
5328     {
5329         return;
5330     }
5331 
5332     if (gTrackDesignSaveMode)
5333     {
5334         TrackDesignState tds{};
5335         auto errMessage = _trackDesign->CreateTrackDesignScenery(tds);
5336         if (errMessage != STR_NONE)
5337         {
5338             context_show_error(STR_CANT_SAVE_TRACK_DESIGN, errMessage, {});
5339             return;
5340         }
5341     }
5342 
5343     auto trackName = ride->GetName();
5344     auto intent = Intent(WC_LOADSAVE);
5345     intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_TRACK);
5346     intent.putExtra(INTENT_EXTRA_TRACK_DESIGN, _trackDesign.get());
5347     intent.putExtra(INTENT_EXTRA_PATH, trackName);
5348     intent.putExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast<void*>(&TrackDesignCallback));
5349 
5350     context_open_intent(&intent);
5351 }
5352 
5353 /**
5354  *
5355  *  rct2: 0x006AD4DA
5356  */
window_ride_measurements_close(rct_window * w)5357 static void window_ride_measurements_close(rct_window* w)
5358 {
5359     window_ride_measurements_design_cancel();
5360 }
5361 
5362 /**
5363  *
5364  *  rct2: 0x006AD478
5365  */
window_ride_measurements_mouseup(rct_window * w,rct_widgetindex widgetIndex)5366 static void window_ride_measurements_mouseup(rct_window* w, rct_widgetindex widgetIndex)
5367 {
5368     switch (widgetIndex)
5369     {
5370         case WIDX_CLOSE:
5371             window_close(w);
5372             break;
5373         case WIDX_TAB_1:
5374         case WIDX_TAB_2:
5375         case WIDX_TAB_3:
5376         case WIDX_TAB_4:
5377         case WIDX_TAB_5:
5378         case WIDX_TAB_6:
5379         case WIDX_TAB_7:
5380         case WIDX_TAB_8:
5381         case WIDX_TAB_9:
5382         case WIDX_TAB_10:
5383             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
5384             break;
5385         case WIDX_SELECT_NEARBY_SCENERY:
5386             window_ride_measurements_design_select_nearby_scenery();
5387             break;
5388         case WIDX_RESET_SELECTION:
5389             window_ride_measurements_design_reset();
5390             break;
5391         case WIDX_SAVE_DESIGN:
5392             window_ride_measurements_design_save(w);
5393             break;
5394         case WIDX_CANCEL_DESIGN:
5395             window_ride_measurements_design_cancel();
5396             break;
5397     }
5398 }
5399 
5400 /**
5401  *
5402  *  rct2: 0x006AD564
5403  */
window_ride_measurements_resize(rct_window * w)5404 static void window_ride_measurements_resize(rct_window* w)
5405 {
5406     window_set_resize(w, 316, 234, 316, 234);
5407 }
5408 
5409 /**
5410  *
5411  *  rct2: 0x006AD4AB
5412  */
window_ride_measurements_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)5413 static void window_ride_measurements_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
5414 {
5415     if (widgetIndex != WIDX_SAVE_TRACK_DESIGN)
5416         return;
5417 
5418     auto ride = get_ride(w->rideId);
5419     if (ride == nullptr)
5420         return;
5421 
5422     gDropdownItemsFormat[0] = STR_SAVE_TRACK_DESIGN_ITEM;
5423     gDropdownItemsFormat[1] = STR_SAVE_TRACK_DESIGN_WITH_SCENERY_ITEM;
5424 
5425     WindowDropdownShowText(
5426         { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1, w->colours[1],
5427         Dropdown::Flag::StayOpen, 2);
5428     gDropdownDefaultIndex = 0;
5429     if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
5430     {
5431         // Disable saving without scenery if we're a flat ride
5432         Dropdown::SetDisabled(0, true);
5433         gDropdownDefaultIndex = 1;
5434     }
5435 }
5436 
5437 /**
5438  *
5439  *  rct2: 0x006AD4B2
5440  */
window_ride_measurements_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)5441 static void window_ride_measurements_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
5442 {
5443     if (widgetIndex != WIDX_SAVE_TRACK_DESIGN)
5444         return;
5445 
5446     if (dropdownIndex == -1)
5447         dropdownIndex = gDropdownHighlightedIndex;
5448 
5449     if (dropdownIndex == 0)
5450     {
5451         window_ride_measurements_design_save(w);
5452     }
5453     else
5454         setup_scenery_selection(w);
5455 }
5456 
5457 /**
5458  *
5459  *  rct2: 0x006AD5DD
5460  */
window_ride_measurements_update(rct_window * w)5461 static void window_ride_measurements_update(rct_window* w)
5462 {
5463     w->frame_no++;
5464     window_event_invalidate_call(w);
5465     widget_invalidate(w, WIDX_TAB_7);
5466 }
5467 
5468 /**
5469  *
5470  *  rct2: 0x006D2AE7
5471  */
window_ride_measurements_tooldown(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)5472 static void window_ride_measurements_tooldown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
5473 {
5474     _lastSceneryX = screenCoords.x;
5475     _lastSceneryY = screenCoords.y;
5476     _collectTrackDesignScenery = true; // Default to true in case user does not select anything valid
5477 
5478     constexpr auto flags = EnumsToFlags(
5479         ViewportInteractionItem::Scenery, ViewportInteractionItem::Footpath, ViewportInteractionItem::Wall,
5480         ViewportInteractionItem::LargeScenery);
5481     auto info = get_map_coordinates_from_pos(screenCoords, flags);
5482     switch (info.SpriteType)
5483     {
5484         case ViewportInteractionItem::Scenery:
5485         case ViewportInteractionItem::LargeScenery:
5486         case ViewportInteractionItem::Wall:
5487         case ViewportInteractionItem::Footpath:
5488             _collectTrackDesignScenery = !track_design_save_contains_tile_element(info.Element);
5489             track_design_save_select_tile_element(info.SpriteType, info.Loc, info.Element, _collectTrackDesignScenery);
5490             break;
5491         default:
5492             break;
5493     }
5494 }
5495 
window_ride_measurements_tooldrag(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)5496 static void window_ride_measurements_tooldrag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
5497 {
5498     if (screenCoords.x == _lastSceneryX && screenCoords.y == _lastSceneryY)
5499         return;
5500     _lastSceneryX = screenCoords.x;
5501     _lastSceneryY = screenCoords.y;
5502 
5503     auto flags = EnumsToFlags(
5504         ViewportInteractionItem::Scenery, ViewportInteractionItem::Footpath, ViewportInteractionItem::Wall,
5505         ViewportInteractionItem::LargeScenery);
5506     auto info = get_map_coordinates_from_pos(screenCoords, flags);
5507     switch (info.SpriteType)
5508     {
5509         case ViewportInteractionItem::Scenery:
5510         case ViewportInteractionItem::LargeScenery:
5511         case ViewportInteractionItem::Wall:
5512         case ViewportInteractionItem::Footpath:
5513             track_design_save_select_tile_element(info.SpriteType, info.Loc, info.Element, _collectTrackDesignScenery);
5514             break;
5515         default:
5516             break;
5517     }
5518 }
5519 
5520 /**
5521  *
5522  *  rct2: 0x006AD4DA
5523  */
window_ride_measurements_toolabort(rct_window * w,rct_widgetindex widgetIndex)5524 static void window_ride_measurements_toolabort(rct_window* w, rct_widgetindex widgetIndex)
5525 {
5526     window_ride_measurements_design_cancel();
5527 }
5528 
5529 /**
5530  *
5531  *  rct2: 0x006ACDBC
5532  */
window_ride_measurements_invalidate(rct_window * w)5533 static void window_ride_measurements_invalidate(rct_window* w)
5534 {
5535     auto widgets = window_ride_page_widgets[w->page];
5536     if (w->widgets != widgets)
5537     {
5538         w->widgets = widgets;
5539         WindowInitScrollWidgets(w);
5540     }
5541 
5542     window_ride_set_pressed_tab(w);
5543 
5544     const auto rideId = w->rideId;
5545     auto ride = get_ride(rideId);
5546     if (ride == nullptr)
5547         return;
5548 
5549     auto ft = Formatter::Common();
5550     ride->FormatNameTo(ft);
5551 
5552     window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].tooltip = STR_SAVE_TRACK_DESIGN_NOT_POSSIBLE;
5553     window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].type = WindowWidgetType::Empty;
5554     if (gTrackDesignSaveMode && gTrackDesignSaveRideIndex == rideId)
5555     {
5556         window_ride_measurements_widgets[WIDX_SELECT_NEARBY_SCENERY].type = WindowWidgetType::Button;
5557         window_ride_measurements_widgets[WIDX_RESET_SELECTION].type = WindowWidgetType::Button;
5558         window_ride_measurements_widgets[WIDX_SAVE_DESIGN].type = WindowWidgetType::Button;
5559         window_ride_measurements_widgets[WIDX_CANCEL_DESIGN].type = WindowWidgetType::Button;
5560     }
5561     else
5562     {
5563         window_ride_measurements_widgets[WIDX_SELECT_NEARBY_SCENERY].type = WindowWidgetType::Empty;
5564         window_ride_measurements_widgets[WIDX_RESET_SELECTION].type = WindowWidgetType::Empty;
5565         window_ride_measurements_widgets[WIDX_SAVE_DESIGN].type = WindowWidgetType::Empty;
5566         window_ride_measurements_widgets[WIDX_CANCEL_DESIGN].type = WindowWidgetType::Empty;
5567 
5568         window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].type = WindowWidgetType::FlatBtn;
5569         w->disabled_widgets |= (1ULL << WIDX_SAVE_TRACK_DESIGN);
5570         if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)
5571         {
5572             if (ride->excitement != RIDE_RATING_UNDEFINED)
5573             {
5574                 w->disabled_widgets &= ~(1ULL << WIDX_SAVE_TRACK_DESIGN);
5575                 window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].tooltip = STR_SAVE_TRACK_DESIGN;
5576             }
5577         }
5578     }
5579 
5580     window_ride_anchor_border_widgets(w);
5581     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
5582 }
5583 
5584 /**
5585  *
5586  *  rct2: 0x006ACF07
5587  */
window_ride_measurements_paint(rct_window * w,rct_drawpixelinfo * dpi)5588 static void window_ride_measurements_paint(rct_window* w, rct_drawpixelinfo* dpi)
5589 {
5590     WindowDrawWidgets(w, dpi);
5591     window_ride_draw_tab_images(dpi, w);
5592 
5593     if (window_ride_measurements_widgets[WIDX_SAVE_DESIGN].type == WindowWidgetType::Button)
5594     {
5595         rct_widget* widget = &window_ride_measurements_widgets[WIDX_PAGE_BACKGROUND];
5596 
5597         ScreenCoordsXY widgetCoords(w->windowPos.x + widget->width() / 2, w->windowPos.y + widget->top + 40);
5598         DrawTextWrapped(dpi, widgetCoords, w->width - 8, STR_CLICK_ITEMS_OF_SCENERY_TO_SELECT, {}, { TextAlignment::CENTRE });
5599 
5600         widgetCoords.x = w->windowPos.x + 4;
5601         widgetCoords.y = w->windowPos.y + window_ride_measurements_widgets[WIDX_SELECT_NEARBY_SCENERY].bottom + 17;
5602         gfx_fill_rect_inset(
5603             dpi, { widgetCoords, { w->windowPos.x + 312, widgetCoords.y + 1 } }, w->colours[1], INSET_RECT_FLAG_BORDER_INSET);
5604     }
5605     else
5606     {
5607         auto ride = get_ride(w->rideId);
5608         if (ride == nullptr)
5609             return;
5610 
5611         auto screenCoords = w->windowPos
5612             + ScreenCoordsXY{ window_ride_measurements_widgets[WIDX_PAGE_BACKGROUND].left + 4,
5613                               window_ride_measurements_widgets[WIDX_PAGE_BACKGROUND].top + 4 };
5614 
5615         if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)
5616         {
5617             // Excitement
5618             rct_string_id ratingName = get_rating_name(ride->excitement);
5619             auto ft = Formatter();
5620             ft.Add<uint32_t>(ride->excitement);
5621             ft.Add<rct_string_id>(ratingName);
5622             rct_string_id stringId = ride->excitement == RIDE_RATING_UNDEFINED ? STR_EXCITEMENT_RATING_NOT_YET_AVAILABLE
5623                                                                                : STR_EXCITEMENT_RATING;
5624             DrawTextBasic(dpi, screenCoords, stringId, ft);
5625             screenCoords.y += LIST_ROW_HEIGHT;
5626 
5627             // Intensity
5628             ratingName = get_rating_name(ride->intensity);
5629             ft = Formatter();
5630             ft.Add<uint32_t>(ride->intensity);
5631             ft.Add<rct_string_id>(ratingName);
5632 
5633             stringId = STR_INTENSITY_RATING;
5634             if (ride->excitement == RIDE_RATING_UNDEFINED)
5635                 stringId = STR_INTENSITY_RATING_NOT_YET_AVAILABLE;
5636             else if (ride->intensity >= RIDE_RATING(10, 00))
5637                 stringId = STR_INTENSITY_RATING_RED;
5638 
5639             DrawTextBasic(dpi, screenCoords, stringId, ft);
5640             screenCoords.y += LIST_ROW_HEIGHT;
5641 
5642             // Nausea
5643             ratingName = get_rating_name(ride->nausea);
5644             ft = Formatter();
5645             ft.Add<uint32_t>(ride->nausea);
5646             ft.Add<rct_string_id>(ratingName);
5647             stringId = ride->excitement == RIDE_RATING_UNDEFINED ? STR_NAUSEA_RATING_NOT_YET_AVAILABLE : STR_NAUSEA_RATING;
5648             DrawTextBasic(dpi, screenCoords, stringId, ft);
5649             screenCoords.y += 2 * LIST_ROW_HEIGHT;
5650 
5651             // Horizontal rule
5652             gfx_fill_rect_inset(
5653                 dpi, { screenCoords - ScreenCoordsXY{ 0, 6 }, screenCoords + ScreenCoordsXY{ 303, -5 } }, w->colours[1],
5654                 INSET_RECT_FLAG_BORDER_INSET);
5655 
5656             if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_NO_RAW_STATS))
5657             {
5658                 if (ride->type == RIDE_TYPE_MINI_GOLF)
5659                 {
5660                     // Holes
5661                     ft = Formatter();
5662                     ft.Add<uint16_t>(ride->holes);
5663                     DrawTextBasic(dpi, screenCoords, STR_HOLES, ft);
5664                     screenCoords.y += LIST_ROW_HEIGHT;
5665                 }
5666                 else
5667                 {
5668                     // Max speed
5669                     ft = Formatter();
5670                     ft.Add<int32_t>((ride->max_speed * 9) >> 18);
5671                     DrawTextBasic(dpi, screenCoords, STR_MAX_SPEED, ft);
5672                     screenCoords.y += LIST_ROW_HEIGHT;
5673 
5674                     // Average speed
5675                     ft = Formatter();
5676                     ft.Add<int32_t>((ride->average_speed * 9) >> 18);
5677                     DrawTextBasic(dpi, screenCoords, STR_AVERAGE_SPEED, ft);
5678                     screenCoords.y += LIST_ROW_HEIGHT;
5679 
5680                     // Ride time
5681                     ft = Formatter();
5682                     int32_t numTimes = 0;
5683                     for (int32_t i = 0; i < ride->num_stations; i++)
5684                     {
5685                         auto time = ride->stations[numTimes].SegmentTime;
5686                         if (time != 0)
5687                         {
5688                             ft.Add<uint16_t>(STR_RIDE_TIME_ENTRY_WITH_SEPARATOR);
5689                             ft.Add<uint16_t>(time);
5690                             numTimes++;
5691                         }
5692                     }
5693                     if (numTimes == 0)
5694                     {
5695                         ft.Add<uint16_t>(STR_RIDE_TIME_ENTRY);
5696                         ft.Add<uint16_t>(0);
5697                         numTimes++;
5698                     }
5699                     else
5700                     {
5701                         // sadly, STR_RIDE_TIME_ENTRY_WITH_SEPARATOR are defined with the separator AFTER an entry
5702                         // therefore we set the last entry to use the no-separator format now, post-format
5703                         ft.Rewind();
5704                         ft.Increment((numTimes - 1) * 4);
5705                         ft.Add<uint16_t>(STR_RIDE_TIME_ENTRY);
5706                     }
5707                     ft.Rewind();
5708                     ft.Increment(numTimes * 4);
5709                     ft.Add<uint16_t>(0);
5710                     ft.Add<uint16_t>(0);
5711                     ft.Add<uint16_t>(0);
5712                     ft.Add<uint16_t>(0);
5713                     DrawTextEllipsised(dpi, screenCoords, 308, STR_RIDE_TIME, ft);
5714                     screenCoords.y += LIST_ROW_HEIGHT;
5715                 }
5716 
5717                 // Ride length
5718                 ft = Formatter();
5719                 int32_t numLengths = 0;
5720                 for (int32_t i = 0; i < ride->num_stations; i++)
5721                 {
5722                     auto length = ride->stations[i].SegmentLength;
5723                     if (length != 0)
5724                     {
5725                         length >>= 16;
5726                         ft.Add<rct_string_id>(STR_RIDE_LENGTH_ENTRY_WITH_SEPARATOR);
5727                         ft.Add<uint16_t>(length & 0xFFFF);
5728                         numLengths++;
5729                     }
5730                 }
5731                 if (numLengths == 0)
5732                 {
5733                     ft.Add<rct_string_id>(STR_RIDE_LENGTH_ENTRY);
5734                     ft.Add<uint16_t>(0);
5735                     numLengths++;
5736                 }
5737                 else
5738                 {
5739                     // sadly, STR_RIDE_LENGTH_ENTRY_WITH_SEPARATOR are defined with the separator AFTER an entry
5740                     // therefore we set the last entry to use the no-separator format now, post-format
5741                     ft.Rewind();
5742                     ft.Increment((numLengths - 1) * 4);
5743                     ft.Add<rct_string_id>(STR_RIDE_LENGTH_ENTRY);
5744                 }
5745                 ft.Rewind();
5746                 ft.Increment(numLengths * 4);
5747                 ft.Add<uint16_t>(0);
5748                 ft.Add<uint16_t>(0);
5749                 ft.Add<uint16_t>(0);
5750                 ft.Add<uint16_t>(0);
5751                 DrawTextEllipsised(dpi, screenCoords, 308, STR_RIDE_LENGTH, ft);
5752 
5753                 screenCoords.y += LIST_ROW_HEIGHT;
5754 
5755                 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES))
5756                 {
5757                     // Max. positive vertical G's
5758                     stringId = ride->max_positive_vertical_g >= RIDE_G_FORCES_RED_POS_VERTICAL ? STR_MAX_POSITIVE_VERTICAL_G_RED
5759                                                                                                : STR_MAX_POSITIVE_VERTICAL_G;
5760                     ft = Formatter();
5761                     ft.Add<fixed16_2dp>(ride->max_positive_vertical_g);
5762                     DrawTextBasic(dpi, screenCoords, stringId, ft);
5763                     screenCoords.y += LIST_ROW_HEIGHT;
5764 
5765                     // Max. negative vertical G's
5766                     stringId = ride->max_negative_vertical_g <= RIDE_G_FORCES_RED_NEG_VERTICAL ? STR_MAX_NEGATIVE_VERTICAL_G_RED
5767                                                                                                : STR_MAX_NEGATIVE_VERTICAL_G;
5768                     ft = Formatter();
5769                     ft.Add<int32_t>(ride->max_negative_vertical_g);
5770                     DrawTextBasic(dpi, screenCoords, stringId, ft);
5771                     screenCoords.y += LIST_ROW_HEIGHT;
5772 
5773                     // Max lateral G's
5774                     stringId = ride->max_lateral_g >= RIDE_G_FORCES_RED_LATERAL ? STR_MAX_LATERAL_G_RED : STR_MAX_LATERAL_G;
5775                     ft = Formatter();
5776                     ft.Add<fixed16_2dp>(ride->max_lateral_g);
5777                     DrawTextBasic(dpi, screenCoords, stringId, ft);
5778                     screenCoords.y += LIST_ROW_HEIGHT;
5779 
5780                     // Total 'air' time
5781                     ft = Formatter();
5782                     ft.Add<fixed32_2dp>(ride->total_air_time * 3);
5783                     DrawTextBasic(dpi, screenCoords, STR_TOTAL_AIR_TIME, ft);
5784                     screenCoords.y += LIST_ROW_HEIGHT;
5785                 }
5786 
5787                 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_DROPS))
5788                 {
5789                     // Drops
5790                     auto drops = ride->drops & 0x3F;
5791                     ft = Formatter();
5792                     ft.Add<uint16_t>(drops);
5793                     DrawTextBasic(dpi, screenCoords, STR_DROPS, ft);
5794                     screenCoords.y += LIST_ROW_HEIGHT;
5795 
5796                     // Highest drop height
5797                     auto highestDropHeight = (ride->highest_drop_height * 3) / 4;
5798                     ft = Formatter();
5799                     ft.Add<int32_t>(highestDropHeight);
5800                     DrawTextBasic(dpi, screenCoords, STR_HIGHEST_DROP_HEIGHT, ft);
5801                     screenCoords.y += LIST_ROW_HEIGHT;
5802                 }
5803 
5804                 if (ride->type != RIDE_TYPE_MINI_GOLF)
5805                 {
5806                     // Inversions
5807                     if (ride->inversions != 0)
5808                     {
5809                         ft = Formatter();
5810                         ft.Add<uint16_t>(ride->inversions);
5811                         DrawTextBasic(dpi, screenCoords, STR_INVERSIONS, ft);
5812                         screenCoords.y += LIST_ROW_HEIGHT;
5813                     }
5814                 }
5815             }
5816         }
5817         else
5818         {
5819             DrawTextBasic(dpi, screenCoords, STR_NO_TEST_RESULTS_YET);
5820         }
5821     }
5822 }
5823 
5824 #pragma endregion
5825 
5826 #pragma region Graphs
5827 
5828 enum
5829 {
5830     GRAPH_VELOCITY,
5831     GRAPH_ALTITUDE,
5832     GRAPH_VERTICAL,
5833     GRAPH_LATERAL
5834 };
5835 
5836 /**
5837  *
5838  *  rct2: 0x006AE8A6
5839  */
window_ride_set_graph(rct_window * w,int32_t type)5840 static void window_ride_set_graph(rct_window* w, int32_t type)
5841 {
5842     if ((w->list_information_type & 0xFF) == type)
5843     {
5844         w->list_information_type ^= 0x8000;
5845     }
5846     else
5847     {
5848         w->list_information_type &= 0xFF00;
5849         w->list_information_type |= type;
5850     }
5851     w->Invalidate();
5852 }
5853 
5854 /**
5855  *
5856  *  rct2: 0x006AE85D
5857  */
window_ride_graphs_mouseup(rct_window * w,rct_widgetindex widgetIndex)5858 static void window_ride_graphs_mouseup(rct_window* w, rct_widgetindex widgetIndex)
5859 {
5860     switch (widgetIndex)
5861     {
5862         case WIDX_CLOSE:
5863             window_close(w);
5864             break;
5865         case WIDX_TAB_1:
5866         case WIDX_TAB_2:
5867         case WIDX_TAB_3:
5868         case WIDX_TAB_4:
5869         case WIDX_TAB_5:
5870         case WIDX_TAB_6:
5871         case WIDX_TAB_7:
5872         case WIDX_TAB_8:
5873         case WIDX_TAB_9:
5874         case WIDX_TAB_10:
5875             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
5876             break;
5877     }
5878 }
5879 
5880 /**
5881  *
5882  *  rct2: 0x006AE8DA
5883  */
window_ride_graphs_resize(rct_window * w)5884 static void window_ride_graphs_resize(rct_window* w)
5885 {
5886     window_set_resize(w, 316, 182, 500, 450);
5887 }
5888 
5889 /**
5890  *
5891  *  rct2: 0x006AE878
5892  */
window_ride_graphs_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)5893 static void window_ride_graphs_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
5894 {
5895     switch (widgetIndex)
5896     {
5897         case WIDX_GRAPH_VELOCITY:
5898             window_ride_set_graph(w, GRAPH_VELOCITY);
5899             break;
5900         case WIDX_GRAPH_ALTITUDE:
5901             window_ride_set_graph(w, GRAPH_ALTITUDE);
5902             break;
5903         case WIDX_GRAPH_VERTICAL:
5904             window_ride_set_graph(w, GRAPH_VERTICAL);
5905             break;
5906         case WIDX_GRAPH_LATERAL:
5907             window_ride_set_graph(w, GRAPH_LATERAL);
5908             break;
5909     }
5910 }
5911 
5912 /**
5913  *
5914  *  rct2: 0x006AE95D
5915  */
window_ride_graphs_update(rct_window * w)5916 static void window_ride_graphs_update(rct_window* w)
5917 {
5918     rct_widget* widget;
5919     int32_t x;
5920 
5921     w->frame_no++;
5922     window_event_invalidate_call(w);
5923     widget_invalidate(w, WIDX_TAB_8);
5924     window_event_invalidate_call(w);
5925     widget_invalidate(w, WIDX_GRAPH);
5926 
5927     widget = &window_ride_graphs_widgets[WIDX_GRAPH];
5928     x = w->scrolls[0].h_left;
5929     if (!(w->list_information_type & 0x8000))
5930     {
5931         auto ride = get_ride(w->rideId);
5932         if (ride != nullptr)
5933         {
5934             RideMeasurement* measurement{};
5935             std::tie(measurement, std::ignore) = ride->GetMeasurement();
5936             x = measurement == nullptr ? 0 : measurement->current_item - ((widget->width() / 4) * 3);
5937         }
5938     }
5939 
5940     w->scrolls[0].h_left = std::clamp(x, 0, w->scrolls[0].h_right - (widget->width() - 2));
5941     WidgetScrollUpdateThumbs(w, WIDX_GRAPH);
5942 }
5943 
5944 /**
5945  *
5946  *  rct2: 0x006AEA75
5947  */
window_ride_graphs_scrollgetheight(rct_window * w,int32_t scrollIndex,int32_t * width,int32_t * height)5948 static void window_ride_graphs_scrollgetheight(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
5949 {
5950     window_event_invalidate_call(w);
5951 
5952     // Set minimum size
5953     *width = window_ride_graphs_widgets[WIDX_GRAPH].width() - 2;
5954 
5955     // Get measurement size
5956     auto ride = get_ride(w->rideId);
5957     if (ride != nullptr)
5958     {
5959         RideMeasurement* measurement{};
5960         std::tie(measurement, std::ignore) = ride->GetMeasurement();
5961         if (measurement != nullptr)
5962         {
5963             *width = std::max<int32_t>(*width, measurement->num_items);
5964         }
5965     }
5966 }
5967 
5968 /**
5969  *
5970  *  rct2: 0x006AE953
5971  */
window_ride_graphs_15(rct_window * w,int32_t scrollIndex,int32_t scrollAreaType)5972 static void window_ride_graphs_15(rct_window* w, int32_t scrollIndex, int32_t scrollAreaType)
5973 {
5974     w->list_information_type |= 0x8000;
5975 }
5976 
5977 /**
5978  *
5979  *  rct2: 0x006AEA05
5980  */
window_ride_graphs_tooltip(rct_window * w,const rct_widgetindex widgetIndex,const rct_string_id fallback)5981 static OpenRCT2String window_ride_graphs_tooltip(rct_window* w, const rct_widgetindex widgetIndex, const rct_string_id fallback)
5982 {
5983     if (widgetIndex == WIDX_GRAPH)
5984     {
5985         auto ride = get_ride(w->rideId);
5986         if (ride != nullptr)
5987         {
5988             auto [measurement, message] = ride->GetMeasurement();
5989             if (measurement != nullptr && (measurement->flags & RIDE_MEASUREMENT_FLAG_RUNNING))
5990             {
5991                 auto ft = Formatter();
5992                 ft.Increment(2);
5993                 ft.Add<rct_string_id>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).number);
5994                 ft.Add<uint16_t>(measurement->vehicle_index + 1);
5995                 return { fallback, ft };
5996             }
5997 
5998             return message;
5999         }
6000     }
6001     else
6002     {
6003         return { STR_NONE, {} };
6004     }
6005     return { fallback, {} };
6006 }
6007 
6008 /**
6009  *
6010  *  rct2: 0x006AE372
6011  */
window_ride_graphs_invalidate(rct_window * w)6012 static void window_ride_graphs_invalidate(rct_window* w)
6013 {
6014     auto widgets = window_ride_page_widgets[w->page];
6015     if (w->widgets != widgets)
6016     {
6017         w->widgets = widgets;
6018         WindowInitScrollWidgets(w);
6019     }
6020 
6021     window_ride_set_pressed_tab(w);
6022 
6023     auto ride = get_ride(w->rideId);
6024     if (ride == nullptr)
6025         return;
6026 
6027     auto ft = Formatter::Common();
6028     ride->FormatNameTo(ft);
6029 
6030     // Set pressed graph button type
6031     w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_VELOCITY);
6032     w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_ALTITUDE);
6033     w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_VERTICAL);
6034     w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_LATERAL);
6035     w->pressed_widgets |= (1LL << (WIDX_GRAPH_VELOCITY + (w->list_information_type & 0xFF)));
6036 
6037     // Hide graph buttons that are not applicable
6038     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES))
6039     {
6040         window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].type = WindowWidgetType::Button;
6041         window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].type = WindowWidgetType::Button;
6042     }
6043     else
6044     {
6045         window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].type = WindowWidgetType::Empty;
6046         window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].type = WindowWidgetType::Empty;
6047     }
6048 
6049     // Anchor graph widget
6050     auto x = w->width - 4;
6051     auto y = w->height - BUTTON_FACE_HEIGHT - 8;
6052 
6053     window_ride_graphs_widgets[WIDX_GRAPH].right = x;
6054     window_ride_graphs_widgets[WIDX_GRAPH].bottom = y;
6055     y += 3;
6056     window_ride_graphs_widgets[WIDX_GRAPH_VELOCITY].top = y;
6057     window_ride_graphs_widgets[WIDX_GRAPH_ALTITUDE].top = y;
6058     window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].top = y;
6059     window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].top = y;
6060     y += BUTTON_FACE_HEIGHT + 1;
6061     window_ride_graphs_widgets[WIDX_GRAPH_VELOCITY].bottom = y;
6062     window_ride_graphs_widgets[WIDX_GRAPH_ALTITUDE].bottom = y;
6063     window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].bottom = y;
6064     window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].bottom = y;
6065 
6066     window_ride_anchor_border_widgets(w);
6067     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
6068 }
6069 
6070 /**
6071  *
6072  *  rct2: 0x006AE4BC
6073  */
window_ride_graphs_paint(rct_window * w,rct_drawpixelinfo * dpi)6074 static void window_ride_graphs_paint(rct_window* w, rct_drawpixelinfo* dpi)
6075 {
6076     WindowDrawWidgets(w, dpi);
6077     window_ride_draw_tab_images(dpi, w);
6078 }
6079 
6080 /**
6081  *
6082  *  rct2: 0x006AE4C7
6083  */
window_ride_graphs_scrollpaint(rct_window * w,rct_drawpixelinfo * dpi,int32_t scrollIndex)6084 static void window_ride_graphs_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
6085 {
6086     gfx_clear(dpi, ColourMapA[COLOUR_SATURATED_GREEN].darker);
6087 
6088     auto widget = &window_ride_graphs_widgets[WIDX_GRAPH];
6089     auto ride = get_ride(w->rideId);
6090     if (ride == nullptr)
6091     {
6092         return;
6093     }
6094 
6095     auto [measurement, message] = ride->GetMeasurement();
6096 
6097     if (measurement == nullptr)
6098     {
6099         // No measurement message
6100         ScreenCoordsXY stringCoords(widget->width() / 2, widget->height() / 2 - 5);
6101         int32_t width = widget->width() - 2;
6102         DrawTextWrapped(dpi, stringCoords, width, message.str, message.args, { TextAlignment::CENTRE });
6103         return;
6104     }
6105 
6106     // Vertical grid lines
6107     const uint8_t lightColour = ColourMapA[COLOUR_SATURATED_GREEN].mid_light;
6108     const uint8_t darkColour = ColourMapA[COLOUR_SATURATED_GREEN].mid_dark;
6109 
6110     int32_t time = 0;
6111     for (int32_t x = 0; x < dpi->x + dpi->width; x += 80)
6112     {
6113         if (x + 80 >= dpi->x)
6114         {
6115             auto coord1 = ScreenCoordsXY{ x, dpi->y };
6116             auto coord2 = ScreenCoordsXY{ x, dpi->y + dpi->height - 1 };
6117             gfx_fill_rect(dpi, { coord1, coord2 }, lightColour);
6118             gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 16, 0 }, coord2 + ScreenCoordsXY{ 16, 0 } }, darkColour);
6119             gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 32, 0 }, coord2 + ScreenCoordsXY{ 32, 0 } }, darkColour);
6120             gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 48, 0 }, coord2 + ScreenCoordsXY{ 48, 0 } }, darkColour);
6121             gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 64, 0 }, coord2 + ScreenCoordsXY{ 64, 0 } }, darkColour);
6122         }
6123         time += 5;
6124     }
6125 
6126     // Horizontal grid lines
6127     int32_t listType = w->list_information_type & 0xFF;
6128     int16_t yUnit = window_graphs_y_axi[listType].unit;
6129     rct_string_id stringID = window_graphs_y_axi[listType].label;
6130     int16_t yUnitInterval = window_graphs_y_axi[listType].unit_interval;
6131     int16_t yInterval = window_graphs_y_axi[listType].interval;
6132 
6133     // Scale modifier
6134     if (listType == GRAPH_ALTITUDE)
6135     {
6136         yUnit -= gMapBaseZ * 3;
6137     }
6138 
6139     for (int32_t y = widget->height() - 13; y >= 8; y -= yInterval, yUnit += yUnitInterval)
6140     {
6141         // Minor / major line
6142         int32_t colour = yUnit == 0 ? lightColour : darkColour;
6143         gfx_fill_rect(dpi, { { dpi->x, y }, { dpi->x + dpi->width - 1, y } }, colour);
6144 
6145         int16_t scaled_yUnit = yUnit;
6146         // Scale modifier
6147         if (listType == GRAPH_ALTITUDE)
6148             scaled_yUnit /= 2;
6149 
6150         auto ft = Formatter();
6151         ft.Add<int16_t>(scaled_yUnit);
6152 
6153         DrawTextBasic(dpi, { w->scrolls[0].h_left + 1, y - 4 }, stringID, ft, { FontSpriteBase::SMALL });
6154     }
6155 
6156     // Time marks
6157     time = 0;
6158     for (int32_t x = 0; x < dpi->x + dpi->width; x += 80)
6159     {
6160         auto ft = Formatter();
6161         ft.Add<int32_t>(time);
6162         if (x + 80 >= dpi->x)
6163             DrawTextBasic(dpi, { x + 2, 1 }, STR_RIDE_STATS_TIME, ft, { FontSpriteBase::SMALL });
6164         time += 5;
6165     }
6166 
6167     // Plot
6168     int32_t x = dpi->x;
6169     int32_t top, bottom;
6170     // Uses the force limits (used to draw extreme G's in red on measurement tab) to determine if line should be drawn red.
6171     int32_t intensityThresholdPositive = 0;
6172     int32_t intensityThresholdNegative = 0;
6173     for (int32_t width = 0; width < dpi->width; width++, x++)
6174     {
6175         if (x < 0 || x >= measurement->num_items - 1)
6176             continue;
6177 
6178         switch (listType)
6179         {
6180             case GRAPH_VELOCITY:
6181                 top = measurement->velocity[x] / 2;
6182                 bottom = measurement->velocity[x + 1] / 2;
6183                 break;
6184             case GRAPH_ALTITUDE:
6185                 top = measurement->altitude[x];
6186                 bottom = measurement->altitude[x + 1];
6187                 break;
6188             case GRAPH_VERTICAL:
6189                 top = measurement->vertical[x] + 39;
6190                 bottom = measurement->vertical[x + 1] + 39;
6191                 intensityThresholdPositive = (RIDE_G_FORCES_RED_POS_VERTICAL / 8) + 39;
6192                 intensityThresholdNegative = (RIDE_G_FORCES_RED_NEG_VERTICAL / 8) + 39;
6193                 break;
6194             case GRAPH_LATERAL:
6195                 top = measurement->lateral[x] + 52;
6196                 bottom = measurement->lateral[x + 1] + 52;
6197                 intensityThresholdPositive = (RIDE_G_FORCES_RED_LATERAL / 8) + 52;
6198                 intensityThresholdNegative = -(RIDE_G_FORCES_RED_LATERAL / 8) + 52;
6199                 break;
6200             default:
6201                 log_error("Wrong graph type %d", listType);
6202                 top = bottom = 0;
6203                 break;
6204         }
6205 
6206         // Adjust line to match graph widget position.
6207         top = widget->height() - top - 13;
6208         bottom = widget->height() - bottom - 13;
6209         if (top > bottom)
6210         {
6211             std::swap(top, bottom);
6212         }
6213 
6214         // Adjust threshold line position as well
6215         if (listType == GRAPH_VERTICAL || listType == GRAPH_LATERAL)
6216         {
6217             intensityThresholdPositive = widget->height() - intensityThresholdPositive - 13;
6218             intensityThresholdNegative = widget->height() - intensityThresholdNegative - 13;
6219         }
6220 
6221         const bool previousMeasurement = x > measurement->current_item;
6222 
6223         // Draw the current line in grey.
6224         gfx_fill_rect(dpi, { { x, top }, { x, bottom } }, previousMeasurement ? PALETTE_INDEX_17 : PALETTE_INDEX_21);
6225 
6226         // Draw red over extreme values (if supported by graph type).
6227         if (listType == GRAPH_VERTICAL || listType == GRAPH_LATERAL)
6228         {
6229             const auto redLineColour = previousMeasurement ? PALETTE_INDEX_171 : PALETTE_INDEX_173;
6230 
6231             // Line exceeds negative threshold (at bottom of graph).
6232             if (bottom >= intensityThresholdNegative)
6233             {
6234                 const auto redLineTop = ScreenCoordsXY{ x, std::max(top, intensityThresholdNegative) };
6235                 const auto redLineBottom = ScreenCoordsXY{ x, std::max(bottom, intensityThresholdNegative) };
6236                 gfx_fill_rect(dpi, { redLineTop, redLineBottom }, redLineColour);
6237             }
6238 
6239             // Line exceeds positive threshold (at top of graph).
6240             if (top <= intensityThresholdPositive)
6241             {
6242                 const auto redLineTop = ScreenCoordsXY{ x, std::min(top, intensityThresholdPositive) };
6243                 const auto redLineBottom = ScreenCoordsXY{ x, std::min(bottom, intensityThresholdPositive) };
6244                 gfx_fill_rect(dpi, { redLineTop, redLineBottom }, redLineColour);
6245             }
6246         }
6247     }
6248 }
6249 
6250 #pragma endregion
6251 
6252 #pragma region Income
6253 
6254 static utf8 _moneyInputText[MONEY_STRING_MAXLENGTH];
6255 
update_same_price_throughout_flags(ShopItem shop_item)6256 static void update_same_price_throughout_flags(ShopItem shop_item)
6257 {
6258     uint64_t newFlags;
6259 
6260     if (GetShopItemDescriptor(shop_item).IsPhoto())
6261     {
6262         newFlags = gSamePriceThroughoutPark;
6263         newFlags ^= EnumsToFlags(ShopItem::Photo, ShopItem::Photo2, ShopItem::Photo3, ShopItem::Photo4);
6264         auto parkSetParameter = ParkSetParameterAction(ParkParameter::SamePriceInPark, newFlags);
6265         GameActions::Execute(&parkSetParameter);
6266     }
6267     else
6268     {
6269         newFlags = gSamePriceThroughoutPark;
6270         newFlags ^= EnumToFlag(shop_item);
6271         auto parkSetParameter = ParkSetParameterAction(ParkParameter::SamePriceInPark, newFlags);
6272         GameActions::Execute(&parkSetParameter);
6273     }
6274 }
6275 
6276 /**
6277  *
6278  *  rct2: 0x006ADEFD
6279  */
window_ride_income_toggle_primary_price(rct_window * w)6280 static void window_ride_income_toggle_primary_price(rct_window* w)
6281 {
6282     const auto rideId = w->rideId;
6283     auto ride = get_ride(rideId);
6284     if (ride == nullptr)
6285         return;
6286 
6287     ShopItem shop_item;
6288     if (ride->type == RIDE_TYPE_TOILETS)
6289     {
6290         shop_item = ShopItem::Admission;
6291     }
6292     else
6293     {
6294         auto rideEntry = get_ride_entry(ride->subtype);
6295         if (rideEntry != nullptr)
6296         {
6297             shop_item = rideEntry->shop_item[0];
6298             if (shop_item == ShopItem::None)
6299                 return;
6300         }
6301         else
6302         {
6303             return;
6304         }
6305     }
6306 
6307     update_same_price_throughout_flags(shop_item);
6308 
6309     auto rideSetPriceAction = RideSetPriceAction(rideId, ride->price[0], true);
6310     GameActions::Execute(&rideSetPriceAction);
6311 }
6312 
6313 /**
6314  *
6315  *  rct2: 0x006AE06E
6316  */
window_ride_income_toggle_secondary_price(rct_window * w)6317 static void window_ride_income_toggle_secondary_price(rct_window* w)
6318 {
6319     const auto rideId = w->rideId;
6320     auto ride = get_ride(rideId);
6321     if (ride == nullptr)
6322         return;
6323 
6324     auto rideEntry = get_ride_entry(ride->subtype);
6325     if (rideEntry == nullptr)
6326         return;
6327 
6328     auto shop_item = rideEntry->shop_item[1];
6329     if (shop_item == ShopItem::None)
6330         shop_item = ride->GetRideTypeDescriptor().PhotoItem;
6331 
6332     update_same_price_throughout_flags(shop_item);
6333 
6334     auto rideSetPriceAction = RideSetPriceAction(rideId, ride->price[1], false);
6335     GameActions::Execute(&rideSetPriceAction);
6336 }
6337 
window_ride_income_set_primary_price(rct_window * w,money16 price)6338 static void window_ride_income_set_primary_price(rct_window* w, money16 price)
6339 {
6340     auto rideSetPriceAction = RideSetPriceAction(w->rideId, price, true);
6341     GameActions::Execute(&rideSetPriceAction);
6342 }
6343 
6344 /**
6345  *
6346  *  rct2: 0x006AE1E4
6347  */
window_ride_income_increase_primary_price(rct_window * w)6348 static void window_ride_income_increase_primary_price(rct_window* w)
6349 {
6350     if (!window_ride_income_can_modify_primary_price(w))
6351         return;
6352 
6353     auto ride = get_ride(w->rideId);
6354     if (ride == nullptr)
6355         return;
6356 
6357     money16 price = ride->price[0];
6358     if (price < MONEY(20, 00))
6359         price++;
6360 
6361     window_ride_income_set_primary_price(w, price);
6362 }
6363 
6364 /**
6365  *
6366  *  rct2: 0x006AE237
6367  */
window_ride_income_decrease_primary_price(rct_window * w)6368 static void window_ride_income_decrease_primary_price(rct_window* w)
6369 {
6370     if (!window_ride_income_can_modify_primary_price(w))
6371         return;
6372 
6373     auto ride = get_ride(w->rideId);
6374     if (ride == nullptr)
6375         return;
6376 
6377     money16 price = ride->price[0];
6378     if (price > MONEY(0, 00))
6379         price--;
6380 
6381     window_ride_income_set_primary_price(w, price);
6382 }
6383 
window_ride_income_get_secondary_price(rct_window * w)6384 static money16 window_ride_income_get_secondary_price(rct_window* w)
6385 {
6386     auto ride = get_ride(w->rideId);
6387     if (ride == nullptr)
6388         return 0;
6389 
6390     money16 price = ride->price[1];
6391     return price;
6392 }
6393 
window_ride_income_set_secondary_price(rct_window * w,money16 price)6394 static void window_ride_income_set_secondary_price(rct_window* w, money16 price)
6395 {
6396     auto rideSetPriceAction = RideSetPriceAction(w->rideId, price, false);
6397     GameActions::Execute(&rideSetPriceAction);
6398 }
6399 
window_ride_income_can_modify_primary_price(rct_window * w)6400 static bool window_ride_income_can_modify_primary_price(rct_window* w)
6401 {
6402     auto ride = get_ride(w->rideId);
6403     if (ride == nullptr)
6404         return false;
6405 
6406     auto rideEntry = ride->GetRideEntry();
6407     return park_ride_prices_unlocked() || ride->type == RIDE_TYPE_TOILETS
6408         || (rideEntry != nullptr && rideEntry->shop_item[0] != ShopItem::None);
6409 }
6410 
6411 /**
6412  *
6413  *  rct2: 0x006AE269
6414  */
window_ride_income_increase_secondary_price(rct_window * w)6415 static void window_ride_income_increase_secondary_price(rct_window* w)
6416 {
6417     money16 price = window_ride_income_get_secondary_price(w);
6418 
6419     if (price < MONEY(20, 00))
6420         price++;
6421 
6422     window_ride_income_set_secondary_price(w, price);
6423 }
6424 
6425 /**
6426  *
6427  *  rct2: 0x006AE28D
6428  */
window_ride_income_decrease_secondary_price(rct_window * w)6429 static void window_ride_income_decrease_secondary_price(rct_window* w)
6430 {
6431     money16 price = window_ride_income_get_secondary_price(w);
6432 
6433     if (price > MONEY(0, 00))
6434         price--;
6435 
6436     window_ride_income_set_secondary_price(w, price);
6437 }
6438 
6439 /**
6440  *
6441  *  rct2: 0x006ADEA9
6442  */
window_ride_income_mouseup(rct_window * w,rct_widgetindex widgetIndex)6443 static void window_ride_income_mouseup(rct_window* w, rct_widgetindex widgetIndex)
6444 {
6445     switch (widgetIndex)
6446     {
6447         case WIDX_CLOSE:
6448             window_close(w);
6449             break;
6450         case WIDX_TAB_1:
6451         case WIDX_TAB_2:
6452         case WIDX_TAB_3:
6453         case WIDX_TAB_4:
6454         case WIDX_TAB_5:
6455         case WIDX_TAB_6:
6456         case WIDX_TAB_7:
6457         case WIDX_TAB_8:
6458         case WIDX_TAB_9:
6459         case WIDX_TAB_10:
6460             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
6461             break;
6462         case WIDX_PRIMARY_PRICE:
6463         {
6464             if (!window_ride_income_can_modify_primary_price(w))
6465                 return;
6466 
6467             auto ride = get_ride(w->rideId);
6468             if (ride != nullptr)
6469             {
6470                 money_to_string(static_cast<money32>(ride->price[0]), _moneyInputText, MONEY_STRING_MAXLENGTH, true);
6471                 window_text_input_raw_open(
6472                     w, WIDX_PRIMARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, {}, _moneyInputText,
6473                     MONEY_STRING_MAXLENGTH);
6474             }
6475             break;
6476         }
6477         case WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK:
6478             window_ride_income_toggle_primary_price(w);
6479             break;
6480         case WIDX_SECONDARY_PRICE:
6481         {
6482             money32 price32 = static_cast<money32>(window_ride_income_get_secondary_price(w));
6483 
6484             money_to_string(price32, _moneyInputText, MONEY_STRING_MAXLENGTH, true);
6485             window_text_input_raw_open(
6486                 w, WIDX_SECONDARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, {}, _moneyInputText, MONEY_STRING_MAXLENGTH);
6487         }
6488         break;
6489         case WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK:
6490             window_ride_income_toggle_secondary_price(w);
6491             break;
6492     }
6493 }
6494 
6495 /**
6496  *
6497  *  rct2: 0x006AE2F8
6498  */
window_ride_income_resize(rct_window * w)6499 static void window_ride_income_resize(rct_window* w)
6500 {
6501     window_set_resize(w, 316, 194, 316, 194);
6502 }
6503 
6504 /**
6505  *
6506  *  rct2: 0x006ADED4
6507  */
window_ride_income_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)6508 static void window_ride_income_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
6509 {
6510     switch (widgetIndex)
6511     {
6512         case WIDX_PRIMARY_PRICE_INCREASE:
6513             window_ride_income_increase_primary_price(w);
6514             break;
6515         case WIDX_PRIMARY_PRICE_DECREASE:
6516             window_ride_income_decrease_primary_price(w);
6517             break;
6518         case WIDX_SECONDARY_PRICE_INCREASE:
6519             window_ride_income_increase_secondary_price(w);
6520             break;
6521         case WIDX_SECONDARY_PRICE_DECREASE:
6522             window_ride_income_decrease_secondary_price(w);
6523             break;
6524     }
6525 }
6526 
6527 /**
6528  *
6529  *  rct2: 0x006AE2BF
6530  */
window_ride_income_update(rct_window * w)6531 static void window_ride_income_update(rct_window* w)
6532 {
6533     w->frame_no++;
6534     window_event_invalidate_call(w);
6535     widget_invalidate(w, WIDX_TAB_9);
6536 
6537     auto ride = get_ride(w->rideId);
6538     if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_INCOME)
6539     {
6540         ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_INCOME;
6541         w->Invalidate();
6542     }
6543 }
6544 
window_ride_income_textinput(rct_window * w,rct_widgetindex widgetIndex,char * text)6545 static void window_ride_income_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text)
6546 {
6547     if ((widgetIndex != WIDX_PRIMARY_PRICE && widgetIndex != WIDX_SECONDARY_PRICE) || text == nullptr)
6548         return;
6549 
6550     money32 price = string_to_money(text);
6551     if (price == MONEY32_UNDEFINED)
6552     {
6553         return;
6554     }
6555 
6556     price = std::clamp(price, MONEY(0, 00), MONEY(20, 00));
6557     money16 price16 = static_cast<money16>(price);
6558 
6559     if (widgetIndex == WIDX_PRIMARY_PRICE)
6560     {
6561         window_ride_income_set_primary_price(w, price16);
6562     }
6563     else
6564     {
6565         window_ride_income_set_secondary_price(w, price16);
6566     }
6567 }
6568 
6569 /**
6570  *
6571  *  rct2: 0x006ADAA3
6572  */
window_ride_income_invalidate(rct_window * w)6573 static void window_ride_income_invalidate(rct_window* w)
6574 {
6575     auto widgets = window_ride_page_widgets[w->page];
6576     if (w->widgets != widgets)
6577     {
6578         w->widgets = widgets;
6579         WindowInitScrollWidgets(w);
6580     }
6581 
6582     window_ride_set_pressed_tab(w);
6583 
6584     auto ride = get_ride(w->rideId);
6585     if (ride == nullptr)
6586         return;
6587 
6588     w->widgets[WIDX_TITLE].text = STR_ARG_18_STRINGID;
6589 
6590     auto ft = Formatter::Common();
6591     ft.Increment(18);
6592     ride->FormatNameTo(ft);
6593 
6594     auto rideEntry = ride->GetRideEntry();
6595     if (rideEntry == nullptr)
6596         return;
6597 
6598     // Primary item
6599     w->pressed_widgets &= ~(1ULL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK);
6600     w->disabled_widgets &= ~(1ULL << WIDX_PRIMARY_PRICE);
6601 
6602     window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].tooltip = STR_NONE;
6603     window_ride_income_widgets[WIDX_PRIMARY_PRICE].tooltip = STR_NONE;
6604 
6605     // If ride prices are locked, do not allow setting the price, unless we're dealing with a shop or toilet.
6606     if (!park_ride_prices_unlocked() && rideEntry->shop_item[0] == ShopItem::None && ride->type != RIDE_TYPE_TOILETS)
6607     {
6608         w->disabled_widgets |= (1ULL << WIDX_PRIMARY_PRICE);
6609         window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].tooltip = STR_RIDE_INCOME_ADMISSION_PAY_FOR_ENTRY_TIP;
6610         window_ride_income_widgets[WIDX_PRIMARY_PRICE].tooltip = STR_RIDE_INCOME_ADMISSION_PAY_FOR_ENTRY_TIP;
6611     }
6612 
6613     window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].text = STR_RIDE_INCOME_ADMISSION_PRICE;
6614     window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].text = STR_SHOP_ITEM_PRICE_LABEL_ON_RIDE_PHOTO;
6615     window_ride_income_widgets[WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Empty;
6616 
6617     window_ride_income_widgets[WIDX_PRIMARY_PRICE].text = STR_BOTTOM_TOOLBAR_CASH;
6618     money16 ridePrimaryPrice = ride_get_price(ride);
6619     ft.Rewind();
6620     ft.Add<money64>(ridePrimaryPrice);
6621     if (ridePrimaryPrice == 0)
6622         window_ride_income_widgets[WIDX_PRIMARY_PRICE].text = STR_FREE;
6623 
6624     ShopItem primaryItem = ShopItem::Admission;
6625     if (ride->type == RIDE_TYPE_TOILETS || ((primaryItem = rideEntry->shop_item[0]) != ShopItem::None))
6626     {
6627         window_ride_income_widgets[WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Checkbox;
6628 
6629         if (shop_item_has_common_price(primaryItem))
6630             w->pressed_widgets |= (1ULL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK);
6631 
6632         window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].text = GetShopItemDescriptor(primaryItem).Naming.PriceLabel;
6633     }
6634 
6635     // Get secondary item
6636     auto secondaryItem = ride->GetRideTypeDescriptor().PhotoItem;
6637     if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO))
6638     {
6639         if ((secondaryItem = rideEntry->shop_item[1]) != ShopItem::None)
6640         {
6641             window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].text = GetShopItemDescriptor(secondaryItem)
6642                                                                               .Naming.PriceLabel;
6643         }
6644     }
6645 
6646     if (secondaryItem == ShopItem::None)
6647     {
6648         // Hide secondary item widgets
6649         window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].type = WindowWidgetType::Empty;
6650         window_ride_income_widgets[WIDX_SECONDARY_PRICE].type = WindowWidgetType::Empty;
6651         window_ride_income_widgets[WIDX_SECONDARY_PRICE_INCREASE].type = WindowWidgetType::Empty;
6652         window_ride_income_widgets[WIDX_SECONDARY_PRICE_DECREASE].type = WindowWidgetType::Empty;
6653         window_ride_income_widgets[WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Empty;
6654     }
6655     else
6656     {
6657         // Set same price throughout park checkbox
6658         w->pressed_widgets &= ~(1ULL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK);
6659         if (shop_item_has_common_price(secondaryItem))
6660             w->pressed_widgets |= (1ULL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK);
6661 
6662         // Show widgets
6663         window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].type = WindowWidgetType::Label;
6664         window_ride_income_widgets[WIDX_SECONDARY_PRICE].type = WindowWidgetType::Spinner;
6665         window_ride_income_widgets[WIDX_SECONDARY_PRICE_INCREASE].type = WindowWidgetType::Button;
6666         window_ride_income_widgets[WIDX_SECONDARY_PRICE_DECREASE].type = WindowWidgetType::Button;
6667         window_ride_income_widgets[WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Checkbox;
6668 
6669         // Set secondary item price
6670         window_ride_income_widgets[WIDX_SECONDARY_PRICE].text = STR_RIDE_SECONDARY_PRICE_VALUE;
6671         ft.Rewind();
6672         ft.Increment(10);
6673         ft.Add<money64>(ride->price[1]);
6674         if (ride->price[1] == 0)
6675             window_ride_income_widgets[WIDX_SECONDARY_PRICE].text = STR_FREE;
6676     }
6677 
6678     window_ride_anchor_border_widgets(w);
6679     window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
6680 }
6681 
6682 /**
6683  *
6684  *  rct2: 0x006ADCE5
6685  */
window_ride_income_paint(rct_window * w,rct_drawpixelinfo * dpi)6686 static void window_ride_income_paint(rct_window* w, rct_drawpixelinfo* dpi)
6687 {
6688     rct_string_id stringId;
6689     money64 profit;
6690     ShopItem primaryItem, secondaryItem;
6691 
6692     WindowDrawWidgets(w, dpi);
6693     window_ride_draw_tab_images(dpi, w);
6694 
6695     auto ride = get_ride(w->rideId);
6696     if (ride == nullptr)
6697         return;
6698 
6699     auto rideEntry = ride->GetRideEntry();
6700     if (rideEntry == nullptr)
6701         return;
6702 
6703     auto screenCoords = w->windowPos
6704         + ScreenCoordsXY{ window_ride_income_widgets[WIDX_PAGE_BACKGROUND].left + 4,
6705                           window_ride_income_widgets[WIDX_PAGE_BACKGROUND].top + 33 };
6706 
6707     // Primary item profit / loss per item sold
6708     primaryItem = rideEntry->shop_item[0];
6709     if (primaryItem != ShopItem::None)
6710     {
6711         profit = ride->price[0];
6712 
6713         stringId = STR_PROFIT_PER_ITEM_SOLD;
6714         profit -= GetShopItemDescriptor(primaryItem).Cost;
6715         if (profit < 0)
6716         {
6717             profit *= -1;
6718             stringId = STR_LOSS_PER_ITEM_SOLD;
6719         }
6720 
6721         auto ft = Formatter();
6722         ft.Add<money64>(profit);
6723 
6724         DrawTextBasic(dpi, screenCoords, stringId, ft);
6725     }
6726     screenCoords.y += 44;
6727 
6728     // Secondary item profit / loss per item sold
6729     secondaryItem = ride->GetRideTypeDescriptor().PhotoItem;
6730     if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO))
6731         secondaryItem = rideEntry->shop_item[1];
6732 
6733     if (secondaryItem != ShopItem::None)
6734     {
6735         profit = ride->price[1];
6736 
6737         stringId = STR_PROFIT_PER_ITEM_SOLD;
6738         profit -= GetShopItemDescriptor(secondaryItem).Cost;
6739         if (profit < 0)
6740         {
6741             profit *= -1;
6742             stringId = STR_LOSS_PER_ITEM_SOLD;
6743         }
6744 
6745         auto ft = Formatter();
6746         ft.Add<money64>(profit);
6747 
6748         DrawTextBasic(dpi, screenCoords, stringId, ft);
6749     }
6750     screenCoords.y += 18;
6751 
6752     // Income per hour
6753     if (ride->income_per_hour != MONEY64_UNDEFINED)
6754     {
6755         auto ft = Formatter();
6756         ft.Add<money64>(ride->income_per_hour);
6757 
6758         DrawTextBasic(dpi, screenCoords, STR_INCOME_PER_HOUR, ft);
6759         screenCoords.y += LIST_ROW_HEIGHT;
6760     }
6761 
6762     // Running cost per hour
6763     money64 costPerHour = ride->upkeep_cost * 16;
6764     stringId = ride->upkeep_cost == MONEY16_UNDEFINED ? STR_RUNNING_COST_UNKNOWN : STR_RUNNING_COST_PER_HOUR;
6765     auto ft = Formatter();
6766     ft.Add<money64>(costPerHour);
6767     DrawTextBasic(dpi, screenCoords, stringId, ft);
6768     screenCoords.y += LIST_ROW_HEIGHT;
6769 
6770     // Profit per hour
6771     if (ride->profit != MONEY64_UNDEFINED)
6772     {
6773         ft = Formatter();
6774         ft.Add<money64>(ride->profit);
6775         DrawTextBasic(dpi, screenCoords, STR_PROFIT_PER_HOUR, ft);
6776         screenCoords.y += LIST_ROW_HEIGHT;
6777     }
6778     screenCoords.y += 5;
6779 
6780     // Total profit
6781     ft = Formatter();
6782     ft.Add<money64>(ride->total_profit);
6783     DrawTextBasic(dpi, screenCoords, STR_TOTAL_PROFIT, ft);
6784 }
6785 
6786 #pragma endregion
6787 
6788 #pragma region Customer
6789 
6790 /**
6791  *
6792  *  rct2: 0x006AD986
6793  */
window_ride_customer_mouseup(rct_window * w,rct_widgetindex widgetIndex)6794 static void window_ride_customer_mouseup(rct_window* w, rct_widgetindex widgetIndex)
6795 {
6796     switch (widgetIndex)
6797     {
6798         case WIDX_CLOSE:
6799             window_close(w);
6800             break;
6801         case WIDX_TAB_1:
6802         case WIDX_TAB_2:
6803         case WIDX_TAB_3:
6804         case WIDX_TAB_4:
6805         case WIDX_TAB_5:
6806         case WIDX_TAB_6:
6807         case WIDX_TAB_7:
6808         case WIDX_TAB_8:
6809         case WIDX_TAB_9:
6810         case WIDX_TAB_10:
6811             window_ride_set_page(w, widgetIndex - WIDX_TAB_1);
6812             break;
6813         case WIDX_SHOW_GUESTS_THOUGHTS:
6814         {
6815             auto intent = Intent(WC_GUEST_LIST);
6816             intent.putExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast<int32_t>(GuestListFilterType::GuestsThinkingAboutRide));
6817             intent.putExtra(INTENT_EXTRA_RIDE_ID, w->number);
6818             context_open_intent(&intent);
6819             break;
6820         }
6821         case WIDX_SHOW_GUESTS_ON_RIDE:
6822         {
6823             auto intent = Intent(WC_GUEST_LIST);
6824             intent.putExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast<int32_t>(GuestListFilterType::GuestsOnRide));
6825             intent.putExtra(INTENT_EXTRA_RIDE_ID, w->number);
6826             context_open_intent(&intent);
6827             break;
6828         }
6829         case WIDX_SHOW_GUESTS_QUEUING:
6830         {
6831             auto intent = Intent(WC_GUEST_LIST);
6832             intent.putExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast<int32_t>(GuestListFilterType::GuestsInQueue));
6833             intent.putExtra(INTENT_EXTRA_RIDE_ID, w->number);
6834             context_open_intent(&intent);
6835             break;
6836         }
6837     }
6838 }
6839 
6840 /**
6841  *
6842  *  rct2: 0x006ADA29
6843  */
window_ride_customer_resize(rct_window * w)6844 static void window_ride_customer_resize(rct_window* w)
6845 {
6846     w->flags |= WF_RESIZABLE;
6847     window_set_resize(w, 316, 163, 316, 163);
6848 }
6849 
6850 /**
6851  *
6852  *  rct2: 0x006AD9DD
6853  */
window_ride_customer_update(rct_window * w)6854 static void window_ride_customer_update(rct_window* w)
6855 {
6856     w->picked_peep_frame++;
6857     if (w->picked_peep_frame >= 24)
6858         w->picked_peep_frame = 0;
6859 
6860     window_event_invalidate_call(w);
6861     widget_invalidate(w, WIDX_TAB_10);
6862 
6863     auto ride = get_ride(w->rideId);
6864     if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_CUSTOMER)
6865     {
6866         ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_CUSTOMER;
6867         w->Invalidate();
6868     }
6869 }
6870 
6871 /**
6872  *
6873  *  rct2: 0x006AD5F8
6874  */
window_ride_customer_invalidate(rct_window * w)6875 static void window_ride_customer_invalidate(rct_window* w)
6876 {
6877     auto widgets = window_ride_page_widgets[w->page];
6878     if (w->widgets != widgets)
6879     {
6880         w->widgets = widgets;
6881         WindowInitScrollWidgets(w);
6882     }
6883 
6884     window_ride_set_pressed_tab(w);
6885 
6886     auto ride = get_ride(w->rideId);
6887     if (ride != nullptr)
6888     {
6889         auto ft = Formatter::Common();
6890         ride->FormatNameTo(ft);
6891 
6892         window_ride_customer_widgets[WIDX_SHOW_GUESTS_THOUGHTS].type = WindowWidgetType::FlatBtn;
6893         if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
6894         {
6895             window_ride_customer_widgets[WIDX_SHOW_GUESTS_ON_RIDE].type = WindowWidgetType::Empty;
6896             window_ride_customer_widgets[WIDX_SHOW_GUESTS_QUEUING].type = WindowWidgetType::Empty;
6897         }
6898         else
6899         {
6900             window_ride_customer_widgets[WIDX_SHOW_GUESTS_ON_RIDE].type = WindowWidgetType::FlatBtn;
6901             window_ride_customer_widgets[WIDX_SHOW_GUESTS_QUEUING].type = WindowWidgetType::FlatBtn;
6902         }
6903 
6904         window_ride_anchor_border_widgets(w);
6905         window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10);
6906     }
6907 }
6908 
6909 /**
6910  *
6911  *  rct2: 0x006AD6CD
6912  */
window_ride_customer_paint(rct_window * w,rct_drawpixelinfo * dpi)6913 static void window_ride_customer_paint(rct_window* w, rct_drawpixelinfo* dpi)
6914 {
6915     ShopItem shopItem;
6916     int16_t popularity, satisfaction, queueTime;
6917     rct_string_id stringId;
6918 
6919     WindowDrawWidgets(w, dpi);
6920     window_ride_draw_tab_images(dpi, w);
6921 
6922     auto ride = get_ride(w->rideId);
6923     if (ride == nullptr)
6924         return;
6925 
6926     auto screenCoords = w->windowPos
6927         + ScreenCoordsXY{ window_ride_customer_widgets[WIDX_PAGE_BACKGROUND].left + 4,
6928                           window_ride_customer_widgets[WIDX_PAGE_BACKGROUND].top + 4 };
6929 
6930     // Customers currently on ride
6931     if (ride->IsRide())
6932     {
6933         auto ft = Formatter();
6934         ft.Add<int16_t>(ride->num_riders);
6935         DrawTextBasic(dpi, screenCoords, STR_CUSTOMERS_ON_RIDE, ft);
6936         screenCoords.y += LIST_ROW_HEIGHT;
6937     }
6938 
6939     // Customers per hour
6940     auto ft = Formatter();
6941     ft.Add<int32_t>(ride_customers_per_hour(ride));
6942     DrawTextBasic(dpi, screenCoords, STR_CUSTOMERS_PER_HOUR, ft);
6943     screenCoords.y += LIST_ROW_HEIGHT;
6944 
6945     // Popularity
6946     popularity = ride->popularity;
6947     if (popularity == 255)
6948     {
6949         stringId = STR_POPULARITY_UNKNOWN;
6950     }
6951     else
6952     {
6953         stringId = STR_POPULARITY_PERCENT;
6954         popularity *= 4;
6955     }
6956     ft = Formatter();
6957     ft.Add<int16_t>(popularity);
6958     DrawTextBasic(dpi, screenCoords, stringId, ft);
6959     screenCoords.y += LIST_ROW_HEIGHT;
6960 
6961     // Satisfaction
6962     satisfaction = ride->satisfaction;
6963     if (satisfaction == 255)
6964     {
6965         stringId = STR_SATISFACTION_UNKNOWN;
6966     }
6967     else
6968     {
6969         stringId = STR_SATISFACTION_PERCENT;
6970         satisfaction *= 5;
6971     }
6972     ft = Formatter();
6973     ft.Add<int16_t>(satisfaction);
6974     DrawTextBasic(dpi, screenCoords, stringId, ft);
6975     screenCoords.y += LIST_ROW_HEIGHT;
6976 
6977     // Queue time
6978     if (ride->IsRide())
6979     {
6980         queueTime = ride->GetMaxQueueTime();
6981         stringId = queueTime == 1 ? STR_QUEUE_TIME_MINUTE : STR_QUEUE_TIME_MINUTES;
6982         ft = Formatter();
6983         ft.Add<int32_t>(queueTime);
6984         screenCoords.y += DrawTextWrapped(dpi, screenCoords, 308, stringId, ft, { TextAlignment::LEFT });
6985         screenCoords.y += 5;
6986     }
6987 
6988     // Primary shop items sold
6989     shopItem = ride->GetRideEntry()->shop_item[0];
6990     if (shopItem != ShopItem::None)
6991     {
6992         ft = Formatter();
6993         ft.Add<rct_string_id>(GetShopItemDescriptor(shopItem).Naming.Plural);
6994         ft.Add<uint32_t>(ride->no_primary_items_sold);
6995         DrawTextBasic(dpi, screenCoords, STR_ITEMS_SOLD, ft);
6996         screenCoords.y += LIST_ROW_HEIGHT;
6997     }
6998 
6999     // Secondary shop items sold / on-ride photos sold
7000     shopItem = (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO) ? ride->GetRideTypeDescriptor().PhotoItem
7001                                                                       : ride->GetRideEntry()->shop_item[1];
7002     if (shopItem != ShopItem::None)
7003     {
7004         ft = Formatter();
7005         ft.Add<rct_string_id>(GetShopItemDescriptor(shopItem).Naming.Plural);
7006         ft.Add<uint32_t>(ride->no_secondary_items_sold);
7007         DrawTextBasic(dpi, screenCoords, STR_ITEMS_SOLD, ft);
7008         screenCoords.y += LIST_ROW_HEIGHT;
7009     }
7010 
7011     // Total customers
7012     ft = Formatter();
7013     ft.Add<uint32_t>(ride->total_customers);
7014     DrawTextBasic(dpi, screenCoords, STR_TOTAL_CUSTOMERS, ft);
7015     screenCoords.y += LIST_ROW_HEIGHT;
7016 
7017     // Guests favourite
7018     if (ride->IsRide())
7019     {
7020         ft = Formatter();
7021         ft.Add<uint16_t>(ride->guests_favourite);
7022         stringId = ride->guests_favourite == 1 ? STR_FAVOURITE_RIDE_OF_GUEST : STR_FAVOURITE_RIDE_OF_GUESTS;
7023         DrawTextBasic(dpi, screenCoords, stringId, ft);
7024         screenCoords.y += LIST_ROW_HEIGHT;
7025     }
7026     screenCoords.y += 2;
7027 
7028     // Age
7029     // If the ride has a build date that is in the future, show it as built this year.
7030     int16_t age = std::max(date_get_year(ride->GetAge()), 0);
7031     stringId = age == 0 ? STR_BUILT_THIS_YEAR : age == 1 ? STR_BUILT_LAST_YEAR : STR_BUILT_YEARS_AGO;
7032     ft = Formatter();
7033     ft.Add<int16_t>(age);
7034     DrawTextBasic(dpi, screenCoords, stringId, ft);
7035 }
7036 
7037 #pragma endregion
7038