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