mirror of https://github.com/OpenRCT2/OpenRCT2.git
6914 lines
280 KiB
C++
6914 lines
280 KiB
C++
/*****************************************************************************
|
||
* Copyright (c) 2014-2024 OpenRCT2 developers
|
||
*
|
||
* For a complete list of all authors, please refer to contributors.md
|
||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||
*
|
||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||
*****************************************************************************/
|
||
|
||
#include "../interface/Theme.h"
|
||
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
#include <iterator>
|
||
#include <limits>
|
||
#include <memory>
|
||
#include <openrct2-ui/interface/Dropdown.h>
|
||
#include <openrct2-ui/interface/Viewport.h>
|
||
#include <openrct2-ui/interface/Widget.h>
|
||
#include <openrct2-ui/windows/Window.h>
|
||
#include <openrct2/Cheats.h>
|
||
#include <openrct2/Context.h>
|
||
#include <openrct2/Game.h>
|
||
#include <openrct2/GameState.h>
|
||
#include <openrct2/Input.h>
|
||
#include <openrct2/Limits.h>
|
||
#include <openrct2/OpenRCT2.h>
|
||
#include <openrct2/actions/GameAction.h>
|
||
#include <openrct2/actions/ParkSetParameterAction.h>
|
||
#include <openrct2/actions/RideSetAppearanceAction.h>
|
||
#include <openrct2/actions/RideSetColourSchemeAction.h>
|
||
#include <openrct2/actions/RideSetNameAction.h>
|
||
#include <openrct2/actions/RideSetPriceAction.h>
|
||
#include <openrct2/actions/RideSetSettingAction.h>
|
||
#include <openrct2/actions/RideSetStatusAction.h>
|
||
#include <openrct2/audio/audio.h>
|
||
#include <openrct2/config/Config.h>
|
||
#include <openrct2/core/String.hpp>
|
||
#include <openrct2/entity/EntityList.h>
|
||
#include <openrct2/entity/Staff.h>
|
||
#include <openrct2/localisation/Date.h>
|
||
#include <openrct2/localisation/Formatter.h>
|
||
#include <openrct2/localisation/Localisation.h>
|
||
#include <openrct2/localisation/LocalisationService.h>
|
||
#include <openrct2/localisation/StringIds.h>
|
||
#include <openrct2/network/network.h>
|
||
#include <openrct2/object/MusicObject.h>
|
||
#include <openrct2/object/ObjectManager.h>
|
||
#include <openrct2/object/ObjectRepository.h>
|
||
#include <openrct2/object/StationObject.h>
|
||
#include <openrct2/peep/PeepData.h>
|
||
#include <openrct2/rct1/RCT1.h>
|
||
#include <openrct2/rct2/T6Exporter.h>
|
||
#include <openrct2/ride/RideConstruction.h>
|
||
#include <openrct2/ride/RideData.h>
|
||
#include <openrct2/ride/ShopItem.h>
|
||
#include <openrct2/ride/Station.h>
|
||
#include <openrct2/ride/Track.h>
|
||
#include <openrct2/ride/TrackData.h>
|
||
#include <openrct2/ride/TrackDesign.h>
|
||
#include <openrct2/ride/TrackDesignRepository.h>
|
||
#include <openrct2/ride/Vehicle.h>
|
||
#include <openrct2/sprites.h>
|
||
#include <openrct2/windows/Intent.h>
|
||
#include <openrct2/world/Park.h>
|
||
#include <optional>
|
||
#include <string>
|
||
#include <string_view>
|
||
#include <vector>
|
||
|
||
using namespace OpenRCT2::TrackMetaData;
|
||
namespace OpenRCT2::Ui::Windows
|
||
{
|
||
static constexpr StringId WINDOW_TITLE = STR_RIDE_WINDOW_TITLE;
|
||
static constexpr int32_t WH = 207;
|
||
static constexpr int32_t WW = 316;
|
||
|
||
enum
|
||
{
|
||
WINDOW_RIDE_PAGE_MAIN,
|
||
WINDOW_RIDE_PAGE_VEHICLE,
|
||
WINDOW_RIDE_PAGE_OPERATING,
|
||
WINDOW_RIDE_PAGE_MAINTENANCE,
|
||
WINDOW_RIDE_PAGE_COLOUR,
|
||
WINDOW_RIDE_PAGE_MUSIC,
|
||
WINDOW_RIDE_PAGE_MEASUREMENTS,
|
||
WINDOW_RIDE_PAGE_GRAPHS,
|
||
WINDOW_RIDE_PAGE_INCOME,
|
||
WINDOW_RIDE_PAGE_CUSTOMER,
|
||
WINDOW_RIDE_PAGE_COUNT
|
||
};
|
||
|
||
#pragma region Widgets
|
||
|
||
// clang-format off
|
||
enum {
|
||
WIDX_BACKGROUND,
|
||
WIDX_TITLE,
|
||
WIDX_CLOSE,
|
||
WIDX_PAGE_BACKGROUND,
|
||
WIDX_TAB_1,
|
||
WIDX_TAB_2,
|
||
WIDX_TAB_3,
|
||
WIDX_TAB_4,
|
||
WIDX_TAB_5,
|
||
WIDX_TAB_6,
|
||
WIDX_TAB_7,
|
||
WIDX_TAB_8,
|
||
WIDX_TAB_9,
|
||
WIDX_TAB_10,
|
||
|
||
WIDX_VIEWPORT = 14,
|
||
WIDX_VIEW,
|
||
WIDX_VIEW_DROPDOWN,
|
||
WIDX_STATUS,
|
||
WIDX_OPEN,
|
||
WIDX_CONSTRUCTION,
|
||
WIDX_RENAME,
|
||
WIDX_LOCATE,
|
||
WIDX_DEMOLISH,
|
||
WIDX_CLOSE_LIGHT,
|
||
WIDX_SIMULATE_LIGHT,
|
||
WIDX_TEST_LIGHT,
|
||
WIDX_OPEN_LIGHT,
|
||
WIDX_RIDE_TYPE,
|
||
WIDX_RIDE_TYPE_DROPDOWN,
|
||
|
||
WIDX_VEHICLE_TYPE = 14,
|
||
WIDX_VEHICLE_TYPE_DROPDOWN,
|
||
WIDX_VEHICLE_REVERSED_TRAINS_CHECKBOX,
|
||
WIDX_VEHICLE_TRAINS_PREVIEW,
|
||
WIDX_VEHICLE_TRAINS,
|
||
WIDX_VEHICLE_TRAINS_INCREASE,
|
||
WIDX_VEHICLE_TRAINS_DECREASE,
|
||
WIDX_VEHICLE_CARS_PER_TRAIN,
|
||
WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE,
|
||
WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE,
|
||
|
||
WIDX_MODE_TWEAK = 14,
|
||
WIDX_MODE_TWEAK_INCREASE,
|
||
WIDX_MODE_TWEAK_DECREASE,
|
||
WIDX_LIFT_HILL_SPEED,
|
||
WIDX_LIFT_HILL_SPEED_INCREASE,
|
||
WIDX_LIFT_HILL_SPEED_DECREASE,
|
||
WIDX_LOAD_CHECKBOX,
|
||
WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX,
|
||
WIDX_MINIMUM_LENGTH_CHECKBOX,
|
||
WIDX_MINIMUM_LENGTH,
|
||
WIDX_MINIMUM_LENGTH_INCREASE,
|
||
WIDX_MINIMUM_LENGTH_DECREASE,
|
||
WIDX_MAXIMUM_LENGTH_CHECKBOX,
|
||
WIDX_MAXIMUM_LENGTH,
|
||
WIDX_MAXIMUM_LENGTH_INCREASE,
|
||
WIDX_MAXIMUM_LENGTH_DECREASE,
|
||
WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX,
|
||
WIDX_MODE_TWEAK_LABEL,
|
||
WIDX_LIFT_HILL_SPEED_LABEL,
|
||
WIDX_MODE,
|
||
WIDX_MODE_DROPDOWN,
|
||
WIDX_LOAD,
|
||
WIDX_LOAD_DROPDOWN,
|
||
WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL,
|
||
WIDX_OPERATE_NUMBER_OF_CIRCUITS,
|
||
WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE,
|
||
WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE,
|
||
|
||
WIDX_INSPECTION_INTERVAL = 14,
|
||
WIDX_INSPECTION_INTERVAL_DROPDOWN,
|
||
WIDX_LOCATE_MECHANIC,
|
||
WIDX_REFURBISH_RIDE,
|
||
WIDX_FORCE_BREAKDOWN,
|
||
|
||
WIDX_TRACK_PREVIEW = 14,
|
||
WIDX_TRACK_COLOUR_SCHEME,
|
||
WIDX_TRACK_COLOUR_SCHEME_DROPDOWN,
|
||
WIDX_TRACK_MAIN_COLOUR,
|
||
WIDX_TRACK_ADDITIONAL_COLOUR,
|
||
WIDX_TRACK_SUPPORT_COLOUR,
|
||
WIDX_MAZE_STYLE,
|
||
WIDX_MAZE_STYLE_DROPDOWN,
|
||
WIDX_PAINT_INDIVIDUAL_AREA,
|
||
WIDX_ENTRANCE_PREVIEW,
|
||
WIDX_ENTRANCE_STYLE,
|
||
WIDX_ENTRANCE_STYLE_DROPDOWN,
|
||
WIDX_VEHICLE_PREVIEW,
|
||
WIDX_VEHICLE_COLOUR_SCHEME,
|
||
WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN,
|
||
WIDX_VEHICLE_COLOUR_INDEX,
|
||
WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN,
|
||
WIDX_VEHICLE_BODY_COLOUR,
|
||
WIDX_VEHICLE_TRIM_COLOUR,
|
||
WIDX_VEHICLE_TERTIARY_COLOUR,
|
||
WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX,
|
||
|
||
WIDX_PLAY_MUSIC = 14,
|
||
WIDX_MUSIC,
|
||
WIDX_MUSIC_DROPDOWN,
|
||
|
||
WIDX_SAVE_TRACK_DESIGN = 14,
|
||
WIDX_SELECT_NEARBY_SCENERY,
|
||
WIDX_RESET_SELECTION,
|
||
WIDX_SAVE_DESIGN,
|
||
WIDX_CANCEL_DESIGN,
|
||
|
||
WIDX_GRAPH = 14,
|
||
WIDX_GRAPH_VELOCITY,
|
||
WIDX_GRAPH_ALTITUDE,
|
||
WIDX_GRAPH_VERTICAL,
|
||
WIDX_GRAPH_LATERAL,
|
||
|
||
WIDX_PRIMARY_PRICE_LABEL = 14,
|
||
WIDX_PRIMARY_PRICE,
|
||
WIDX_PRIMARY_PRICE_INCREASE,
|
||
WIDX_PRIMARY_PRICE_DECREASE,
|
||
WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK,
|
||
WIDX_SECONDARY_PRICE_LABEL,
|
||
WIDX_SECONDARY_PRICE,
|
||
WIDX_SECONDARY_PRICE_INCREASE,
|
||
WIDX_SECONDARY_PRICE_DECREASE,
|
||
WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK,
|
||
|
||
WIDX_SHOW_GUESTS_THOUGHTS = 14,
|
||
WIDX_SHOW_GUESTS_ON_RIDE,
|
||
WIDX_SHOW_GUESTS_QUEUING
|
||
};
|
||
|
||
constexpr int32_t RCT1_LIGHT_OFFSET = 4;
|
||
|
||
#define MAIN_RIDE_WIDGETS \
|
||
WINDOW_SHIM(WINDOW_TITLE, WW, WH), \
|
||
MakeWidget({ 0, 43}, {316, 137}, WindowWidgetType::Resize, WindowColour::Secondary), \
|
||
MakeTab ({ 3, 17}, STR_VIEW_OF_RIDE_ATTRACTION_TIP ), \
|
||
MakeTab ({ 34, 17}, STR_VEHICLE_DETAILS_AND_OPTIONS_TIP ), \
|
||
MakeTab ({ 65, 17}, STR_OPERATING_OPTIONS_TIP ), \
|
||
MakeTab ({ 96, 17}, STR_MAINTENANCE_OPTIONS_TIP ), \
|
||
MakeTab ({127, 17}, STR_COLOUR_SCHEME_OPTIONS_TIP ), \
|
||
MakeTab ({158, 17}, STR_SOUND_AND_MUSIC_OPTIONS_TIP ), \
|
||
MakeTab ({189, 17}, STR_MEASUREMENTS_AND_TEST_DATA_TIP ), \
|
||
MakeTab ({220, 17}, STR_GRAPHS_TIP ), \
|
||
MakeTab ({251, 17}, STR_INCOME_AND_COSTS_TIP ), \
|
||
MakeTab ({282, 17}, STR_CUSTOMER_INFORMATION_TIP )
|
||
|
||
// 0x009ADC34
|
||
static Widget _mainWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget({ 3, 60}, {288, 107}, WindowWidgetType::Viewport, WindowColour::Secondary, STR_VIEWPORT ),
|
||
MakeWidget({ 35, 46}, {222, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, 0xFFFFFFFF, STR_VIEW_SELECTION ),
|
||
MakeWidget({245, 47}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_VIEW_SELECTION ),
|
||
MakeWidget({ 3, 167}, {288, 11}, WindowWidgetType::LabelCentred, WindowColour::Secondary ),
|
||
MakeWidget({291, 46}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_OPEN_CLOSE_OR_TEST_RIDE),
|
||
MakeWidget({291, 70}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_CONSTRUCTION), STR_CONSTRUCTION ),
|
||
MakeWidget({291, 94}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RENAME), STR_NAME_RIDE_TIP ),
|
||
MakeWidget({291, 118}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_LOCATE), STR_LOCATE_SUBJECT_TIP ),
|
||
MakeWidget({291, 142}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_DEMOLISH), STR_DEMOLISH_RIDE_TIP ),
|
||
MakeWidget({296, 48}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, ImageId(SPR_G2_RCT1_CLOSE_BUTTON_0), STR_CLOSE_RIDE_TIP ),
|
||
MakeWidget({296, 62}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, ImageId(SPR_G2_RCT1_TEST_BUTTON_0), STR_SIMULATE_RIDE_TIP ),
|
||
MakeWidget({296, 62}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, ImageId(SPR_G2_RCT1_TEST_BUTTON_0), STR_TEST_RIDE_TIP ),
|
||
MakeWidget({296, 76}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, ImageId(SPR_G2_RCT1_OPEN_BUTTON_0), STR_OPEN_RIDE_TIP ),
|
||
MakeWidget({ 3, 180}, {305, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_ARG_6_STRINGID ),
|
||
MakeWidget({297, 180}, { 11, 12}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009ADDA8
|
||
static Widget _vehicleWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget ({ 7, 50}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ),
|
||
MakeWidget ({297, 51}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ),
|
||
MakeWidget ({ 7, 137}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_OPTION_REVERSE_TRAINS, STR_OPTION_REVERSE_TRAINS_TIP ),
|
||
MakeWidget ({ 7, 154}, {302, 43}, WindowWidgetType::Scroll, WindowColour::Secondary, STR_EMPTY ),
|
||
MakeSpinnerWidgets({ 7, 203}, {145, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_RIDE_VEHICLE_COUNT, STR_MAX_VEHICLES_TIP ),
|
||
MakeSpinnerWidgets({164, 203}, {145, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_1_CAR_PER_TRAIN, STR_MAX_CARS_PER_TRAIN_TIP),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009ADEFC
|
||
static Widget _operatingWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeSpinnerWidgets({157, 61}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_18_STRINGID ), // NB: 3 widgets
|
||
MakeSpinnerWidgets({157, 75}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_LIFT_HILL_CHAIN_SPEED_VALUE ), // NB: 3 widgets
|
||
MakeWidget ({ 7, 109}, { 80, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_WAIT_FOR, STR_WAIT_FOR_PASSENGERS_BEFORE_DEPARTING_TIP),
|
||
MakeWidget ({ 7, 124}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary ),
|
||
MakeWidget ({ 7, 139}, {150, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_MINIMUM_WAITING_TIME, STR_MINIMUM_LENGTH_BEFORE_DEPARTING_TIP ),
|
||
MakeSpinnerWidgets({157, 139}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_10_STRINGID ), // NB: 3 widgets
|
||
MakeWidget ({ 7, 154}, {150, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_MAXIMUM_WAITING_TIME, STR_MAXIMUM_LENGTH_BEFORE_DEPARTING_TIP ),
|
||
MakeSpinnerWidgets({157, 154}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_14_STRINGID ), // NB: 3 widgets
|
||
MakeWidget ({ 7, 169}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SYNCHRONISE_WITH_ADJACENT_STATIONS, STR_SYNCHRONISE_WITH_ADJACENT_STATIONS_TIP ),
|
||
MakeWidget ({ 21, 61}, {129, 12}, WindowWidgetType::Label, WindowColour::Secondary ),
|
||
MakeWidget ({ 21, 75}, {129, 12}, WindowWidgetType::Label, WindowColour::Secondary, STR_LIFT_HILL_CHAIN_SPEED ),
|
||
MakeWidget ({ 7, 47}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_OPERATING_MODE ),
|
||
MakeWidget ({297, 48}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_OPERATING_MODE ),
|
||
MakeWidget ({ 87, 109}, {222, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ),
|
||
MakeWidget ({297, 110}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ),
|
||
MakeWidget ({ 21, 89}, {129, 12}, WindowWidgetType::Label, WindowColour::Secondary, STR_NUMBER_OF_CIRCUITS, STR_NUMBER_OF_CIRCUITS_TIP ),
|
||
MakeSpinnerWidgets({157, 89}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_NUMBER_OF_CIRCUITS_VALUE ), // NB: 3 widgets
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009AE190
|
||
static Widget _maintenanceWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget({107, 71}, {202, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY, STR_SELECT_HOW_OFTEN_A_MECHANIC_SHOULD_CHECK_THIS_RIDE),
|
||
MakeWidget({297, 72}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_HOW_OFTEN_A_MECHANIC_SHOULD_CHECK_THIS_RIDE),
|
||
MakeWidget({289, 108}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_LOCATE_NEAREST_AVAILABLE_MECHANIC_TIP ),
|
||
MakeWidget({265, 108}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_CONSTRUCTION), STR_REFURBISH_RIDE_TIP ),
|
||
MakeWidget({241, 108}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_NO_ENTRY), STR_DEBUG_FORCE_BREAKDOWN_TIP ),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009AE2A4
|
||
static Widget _colourWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget({ 3, 47}, { 68, 47}, WindowWidgetType::Spinner, WindowColour::Secondary ),
|
||
MakeWidget({ 74, 49}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_ARG_14_STRINGID ),
|
||
MakeWidget({301, 50}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_COLOUR_SCHEME_TO_CHANGE_TIP ),
|
||
MakeWidget({ 79, 74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_MAIN_COLOUR_TIP ),
|
||
MakeWidget({ 99, 74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_ADDITIONAL_COLOUR_1_TIP ),
|
||
MakeWidget({119, 74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_SUPPORT_STRUCTURE_COLOUR_TIP ),
|
||
MakeWidget({ 74, 49}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ),
|
||
MakeWidget({301, 50}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ),
|
||
MakeWidget({289, 68}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_PAINTBRUSH), STR_PAINT_INDIVIDUAL_AREA_TIP ),
|
||
MakeWidget({245, 101}, { 68, 47}, WindowWidgetType::Spinner, WindowColour::Secondary ),
|
||
MakeWidget({103, 103}, {139, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY ),
|
||
MakeWidget({230, 104}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_STYLE_OF_ENTRANCE_EXIT_STATION_TIP),
|
||
MakeWidget({ 3, 157}, { 68, 47}, WindowWidgetType::Scroll, WindowColour::Secondary, STR_EMPTY ),
|
||
MakeWidget({ 74, 157}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_ARG_6_STRINGID ),
|
||
MakeWidget({301, 158}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_VEHICLE_COLOUR_SCHEME_TIP ),
|
||
MakeWidget({ 74, 173}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ),
|
||
MakeWidget({301, 174}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_VEHICLE_TO_MODIFY_TIP ),
|
||
MakeWidget({ 79, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_MAIN_COLOUR_TIP ),
|
||
MakeWidget({ 99, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_ADDITIONAL_COLOUR_1_TIP ),
|
||
MakeWidget({119, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_ADDITIONAL_COLOUR_2_TIP ),
|
||
MakeWidget({100, 74}, {239, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_RANDOM_COLOUR ),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009AE4C8
|
||
static Widget _musicWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget({ 7, 47}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_PLAY_MUSIC, STR_SELECT_MUSIC_TIP ),
|
||
MakeWidget({ 7, 62}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY ),
|
||
MakeWidget({297, 63}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_MUSIC_STYLE_TIP),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009AE5DC
|
||
static Widget _measurementWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget({288, 194}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_FLOPPY), STR_SAVE_TRACK_DESIGN),
|
||
MakeWidget({ 4, 127}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_SELECT_NEARBY_SCENERY ),
|
||
MakeWidget({158, 127}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RESET_SELECTION ),
|
||
MakeWidget({ 4, 177}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_DESIGN_SAVE ),
|
||
MakeWidget({158, 177}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_DESIGN_CANCEL ),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009AE710
|
||
static Widget _graphsWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget({ 3, 46}, {306, 112}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_HORIZONTAL, STR_LOGGING_DATA_FROM_TIP ),
|
||
MakeWidget({ 3, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_VELOCITY, STR_SHOW_GRAPH_OF_VELOCITY_AGAINST_TIME_TIP ),
|
||
MakeWidget({ 76, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_ALTITUDE, STR_SHOW_GRAPH_OF_ALTITUDE_AGAINST_TIME_TIP ),
|
||
MakeWidget({149, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_VERT_G, STR_SHOW_GRAPH_OF_VERTICAL_ACCELERATION_AGAINST_TIME_TIP),
|
||
MakeWidget({222, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_LAT_G, STR_SHOW_GRAPH_OF_LATERAL_ACCELERATION_AGAINST_TIME_TIP ),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009AE844
|
||
static Widget _incomeWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget ({ 19, 50}, {126, 14}, WindowWidgetType::Label, WindowColour::Secondary ),
|
||
MakeSpinnerWidgets({147, 50}, {162, 14}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_6_CURRENCY2DP ), // NB: 3 widgets
|
||
MakeWidget ({ 5, 62}, {306, 13}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SAME_PRICE_THROUGHOUT_PARK, STR_SAME_PRICE_THROUGHOUT_PARK_TIP),
|
||
MakeWidget ({ 19, 94}, {126, 14}, WindowWidgetType::Label, WindowColour::Secondary ),
|
||
MakeSpinnerWidgets({147, 94}, {162, 14}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_RIDE_SECONDARY_PRICE_VALUE ), // NB: 3 widgets
|
||
MakeWidget ({ 5, 106}, {306, 13}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SAME_PRICE_THROUGHOUT_PARK, STR_SAME_PRICE_THROUGHOUT_PARK_TIP),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
// 0x009AE9C8
|
||
static Widget _customerWidgets[] = {
|
||
MAIN_RIDE_WIDGETS,
|
||
MakeWidget({289, 54}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_SHOW_GUESTS_THOUGHTS_ABOUT_THIS_RIDE_ATTRACTION), STR_SHOW_GUESTS_THOUGHTS_ABOUT_THIS_RIDE_ATTRACTION_TIP),
|
||
MakeWidget({289, 78}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_SHOW_GUESTS_ON_THIS_RIDE_ATTRACTION), STR_SHOW_GUESTS_ON_THIS_RIDE_ATTRACTION_TIP ),
|
||
MakeWidget({289, 102}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_SHOW_GUESTS_QUEUING_FOR_THIS_RIDE_ATTRACTION), STR_SHOW_GUESTS_QUEUING_FOR_THIS_RIDE_ATTRACTION_TIP ),
|
||
kWidgetsEnd,
|
||
};
|
||
|
||
static const std::array PageWidgets = {
|
||
_mainWidgets,
|
||
_vehicleWidgets,
|
||
_operatingWidgets,
|
||
_maintenanceWidgets,
|
||
_colourWidgets,
|
||
_musicWidgets,
|
||
_measurementWidgets,
|
||
_graphsWidgets,
|
||
_incomeWidgets,
|
||
_customerWidgets,
|
||
};
|
||
static_assert(std::size(PageWidgets) == WINDOW_RIDE_PAGE_COUNT);
|
||
|
||
static constexpr std::array PageHoldDownWidgets = {
|
||
0uLL,
|
||
(1uLL << WIDX_VEHICLE_TRAINS_INCREASE) |
|
||
(1uLL << WIDX_VEHICLE_TRAINS_DECREASE) |
|
||
(1uLL << WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE) |
|
||
(1uLL << WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE),
|
||
(1uLL << WIDX_MODE_TWEAK_INCREASE) |
|
||
(1uLL << WIDX_MODE_TWEAK_DECREASE) |
|
||
(1uLL << WIDX_LIFT_HILL_SPEED_INCREASE) |
|
||
(1uLL << WIDX_LIFT_HILL_SPEED_DECREASE) |
|
||
(1uLL << WIDX_MINIMUM_LENGTH_INCREASE) |
|
||
(1uLL << WIDX_MINIMUM_LENGTH_DECREASE) |
|
||
(1uLL << WIDX_MAXIMUM_LENGTH_INCREASE) |
|
||
(1uLL << WIDX_MAXIMUM_LENGTH_DECREASE) |
|
||
(1uLL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE) |
|
||
(1uLL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE),
|
||
0uLL,
|
||
0uLL,
|
||
0uLL,
|
||
0uLL,
|
||
0uLL,
|
||
(1uLL << WIDX_PRIMARY_PRICE_INCREASE) |
|
||
(1uLL << WIDX_PRIMARY_PRICE_DECREASE) |
|
||
(1uLL << WIDX_SECONDARY_PRICE_INCREASE) |
|
||
(1uLL << WIDX_SECONDARY_PRICE_DECREASE),
|
||
0uLL,
|
||
};
|
||
static_assert(std::size(PageHoldDownWidgets) == WINDOW_RIDE_PAGE_COUNT);
|
||
// clang-format on
|
||
|
||
#pragma endregion
|
||
|
||
static void CancelScenerySelection();
|
||
|
||
static bool _collectTrackDesignScenery = false;
|
||
static int32_t _lastSceneryX = 0;
|
||
static int32_t _lastSceneryY = 0;
|
||
static std::unique_ptr<TrackDesign> _trackDesign;
|
||
|
||
// Cached overall view for each ride
|
||
// (Re)calculated when the ride window is opened
|
||
struct RideOverallView
|
||
{
|
||
CoordsXYZ loc;
|
||
ZoomLevel zoom;
|
||
};
|
||
|
||
static std::vector<RideOverallView> _rideOverallViewsCache = {};
|
||
|
||
static constexpr std::array PageTabAnimationDivisor = {
|
||
0, // WINDOW_RIDE_PAGE_MAIN
|
||
0, // WINDOW_RIDE_PAGE_VEHICLE
|
||
2, // WINDOW_RIDE_PAGE_OPERATING
|
||
2, // WINDOW_RIDE_PAGE_MAINTENANCE
|
||
4, // WINDOW_RIDE_PAGE_COLOUR
|
||
2, // WINDOW_RIDE_PAGE_MUSIC
|
||
8, // WINDOW_RIDE_PAGE_MEASUREMENTS
|
||
8, // WINDOW_RIDE_PAGE_GRAPHS
|
||
2, // WINDOW_RIDE_PAGE_INCOME
|
||
0, // WINDOW_RIDE_PAGE_CUSTOMER
|
||
};
|
||
static_assert(std::size(PageTabAnimationDivisor) == WINDOW_RIDE_PAGE_COUNT);
|
||
|
||
static constexpr std::array PageTabAnimationNumFrames = {
|
||
0, // WINDOW_RIDE_PAGE_MAIN
|
||
0, // WINDOW_RIDE_PAGE_VEHICLE
|
||
4, // WINDOW_RIDE_PAGE_OPERATING
|
||
16, // WINDOW_RIDE_PAGE_MAINTENANCE
|
||
8, // WINDOW_RIDE_PAGE_COLOUR
|
||
16, // WINDOW_RIDE_PAGE_MUSIC
|
||
8, // WINDOW_RIDE_PAGE_MEASUREMENTS
|
||
8, // WINDOW_RIDE_PAGE_GRAPHS
|
||
8, // WINDOW_RIDE_PAGE_INCOME
|
||
0, // WINDOW_RIDE_PAGE_CUSTOMER
|
||
};
|
||
static_assert(std::size(PageTabAnimationNumFrames) == WINDOW_RIDE_PAGE_COUNT);
|
||
|
||
// clang-format off
|
||
static constexpr std::array RatingNames = {
|
||
static_cast<StringId>(STR_RATING_LOW),
|
||
static_cast<StringId>(STR_RATING_MEDIUM),
|
||
static_cast<StringId>(STR_RATING_HIGH),
|
||
static_cast<StringId>(STR_RATING_VERY_HIGH),
|
||
static_cast<StringId>(STR_RATING_EXTREME),
|
||
static_cast<StringId>(STR_RATING_ULTRA_EXTREME),
|
||
};
|
||
static_assert(std::size(RatingNames) == 6);
|
||
// clang-format on
|
||
|
||
static constexpr std::array RideBreakdownReasonNames = {
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_SAFETY_CUT_OUT), // BREAKDOWN_SAFETY_CUT_OUT
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_RESTRAINTS_STUCK_CLOSED), // BREAKDOWN_RESTRAINTS_STUCK_CLOSED
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_RESTRAINTS_STUCK_OPEN), // BREAKDOWN_RESTRAINTS_STUCK_OPEN
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_DOORS_STUCK_CLOSED), // BREAKDOWN_DOORS_STUCK_CLOSED
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_DOORS_STUCK_OPEN), // BREAKDOWN_DOORS_STUCK_OPEN
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_VEHICLE_MALFUNCTION), // BREAKDOWN_VEHICLE_MALFUNCTION
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_BRAKES_FAILURE), // BREAKDOWN_BRAKES_FAILURE
|
||
static_cast<StringId>(STR_RIDE_BREAKDOWN_CONTROL_FAILURE), // BREAKDOWN_CONTROL_FAILURE
|
||
};
|
||
static_assert(std::size(RideBreakdownReasonNames) == BREAKDOWN_COUNT);
|
||
|
||
// Used in other places as well
|
||
const StringId ColourSchemeNames[4] = {
|
||
STR_MAIN_COLOUR_SCHEME, // RIDE_COLOUR_SCHEME_MAIN
|
||
STR_ALTERNATIVE_COLOUR_SCHEME_1, // RIDE_COLOUR_SCHEME_ADDITIONAL_1
|
||
STR_ALTERNATIVE_COLOUR_SCHEME_2, // RIDE_COLOUR_SCHEME_ADDITIONAL_2
|
||
STR_ALTERNATIVE_COLOUR_SCHEME_3, // RIDE_COLOUR_SCHEME_ADDITIONAL_3
|
||
};
|
||
static_assert(std::size(ColourSchemeNames) == RIDE_COLOUR_SCHEME_COUNT);
|
||
|
||
static constexpr std::array VehicleLoadNames = {
|
||
static_cast<StringId>(STR_QUARTER_LOAD), // WAIT_FOR_LOAD_QUARTER
|
||
static_cast<StringId>(STR_HALF_LOAD), // WAIT_FOR_LOAD_HALF
|
||
static_cast<StringId>(STR_THREE_QUARTER_LOAD), // WAIT_FOR_LOAD_THREE_QUARTER
|
||
static_cast<StringId>(STR_FULL_LOAD), // WAIT_FOR_LOAD_FULL
|
||
static_cast<StringId>(STR_ANY_LOAD), // WAIT_FOR_LOAD_ANY
|
||
};
|
||
static_assert(std::size(VehicleLoadNames) == WAIT_FOR_LOAD_COUNT);
|
||
|
||
static constexpr std::array VehicleColourSchemeNames = {
|
||
static_cast<StringId>(STR_ALL_VEHICLES_IN_SAME_COLOURS), // RIDE_COLOUR_SCHEME_MODE_ALL_SAME,
|
||
static_cast<StringId>(STR_DIFFERENT_COLOURS_PER), // RIDE_COLOUR_SCHEME_MODE_DIFFERENT_PER_TRAIN,
|
||
static_cast<StringId>(STR_DIFFERENT_COLOURS_PER_VEHICLE), // RIDE_COLOUR_SCHEME_MODE_DIFFERENT_PER_CAR,
|
||
};
|
||
static_assert(std::size(VehicleColourSchemeNames) == RIDE_COLOUR_SCHEME_MODE_COUNT);
|
||
|
||
static constexpr std::array VehicleStatusNames = {
|
||
static_cast<StringId>(STR_MOVING_TO_END_OF), // Vehicle::Status::MovingToEndOfStation
|
||
static_cast<StringId>(STR_WAITING_FOR_PASSENGERS_AT), // Vehicle::Status::WaitingForPassengers
|
||
static_cast<StringId>(STR_WAITING_TO_DEPART), // Vehicle::Status::WaitingToDepart
|
||
static_cast<StringId>(STR_DEPARTING), // Vehicle::Status::Departing
|
||
static_cast<StringId>(STR_TRAVELLING_AT_0), // Vehicle::Status::Travelling
|
||
static_cast<StringId>(STR_ARRIVING_AT), // Vehicle::Status::Arriving
|
||
static_cast<StringId>(STR_UNLOADING_PASSENGERS_AT), // Vehicle::Status::UnloadingPassengers
|
||
static_cast<StringId>(STR_TRAVELLING_AT_1), // Vehicle::Status::TravellingBoat
|
||
static_cast<StringId>(STR_CRASHING), // Vehicle::Status::Crashing
|
||
static_cast<StringId>(STR_CRASHED_0), // Vehicle::Status::Crashed
|
||
static_cast<StringId>(STR_TRAVELLING_AT_2), // Vehicle::Status::TravellingDodgems
|
||
static_cast<StringId>(STR_SWINGING), // Vehicle::Status::Swinging
|
||
static_cast<StringId>(STR_ROTATING_0), // Vehicle::Status::Rotating
|
||
static_cast<StringId>(STR_ROTATING_1), // Vehicle::Status::FerrisWheelRotating
|
||
static_cast<StringId>(STR_OPERATING_0), // Vehicle::Status::SimulatorOperating
|
||
static_cast<StringId>(STR_SHOWING_FILM), // Vehicle::Status::ShowingFilm
|
||
static_cast<StringId>(STR_ROTATING_2), // Vehicle::Status::SpaceRingsOperating
|
||
static_cast<StringId>(STR_OPERATING_1), // Vehicle::Status::TopSpinOperating
|
||
static_cast<StringId>(STR_OPERATING_2), // Vehicle::Status::HauntedHouseOperating
|
||
static_cast<StringId>(STR_DOING_CIRCUS_SHOW), // Vehicle::Status::DoingCircusShow
|
||
static_cast<StringId>(STR_OPERATING_3), // Vehicle::Status::CrookedHouseOperating
|
||
static_cast<StringId>(STR_WAITING_FOR_CABLE_LIFT), // Vehicle::Status::WaitingForCableLift
|
||
static_cast<StringId>(STR_TRAVELLING_AT_3), // Vehicle::Status::TravellingCableLift
|
||
static_cast<StringId>(STR_STOPPING_0), // Vehicle::Status::Stopping
|
||
static_cast<StringId>(STR_WAITING_FOR_PASSENGERS), // Vehicle::Status::WaitingForPassengers17
|
||
static_cast<StringId>(STR_WAITING_TO_START), // Vehicle::Status::WaitingToStart
|
||
static_cast<StringId>(STR_STARTING), // Vehicle::Status::Starting
|
||
static_cast<StringId>(STR_OPERATING), // Vehicle::Status::Operating1A
|
||
static_cast<StringId>(STR_STOPPING_1), // Vehicle::Status::Stopping1B
|
||
static_cast<StringId>(STR_UNLOADING_PASSENGERS), // Vehicle::Status::UnloadingPassengers1C
|
||
static_cast<StringId>(STR_STOPPED_BY_BLOCK_BRAKES), // Vehicle::Status::StoppedByBlockBrakes
|
||
};
|
||
static_assert(std::size(VehicleStatusNames) == 31);
|
||
|
||
static constexpr std::array SingleSessionVehicleStatusNames = {
|
||
static_cast<StringId>(STR_STOPPING_0), // Vehicle::Status::MovingToEndOfStation
|
||
static_cast<StringId>(STR_WAITING_FOR_PASSENGERS), // Vehicle::Status::WaitingForPassengers
|
||
static_cast<StringId>(STR_WAITING_TO_START), // Vehicle::Status::WaitingToDepart
|
||
static_cast<StringId>(STR_STARTING), // Vehicle::Status::Departing
|
||
static_cast<StringId>(STR_OPERATING), // Vehicle::Status::Travelling
|
||
static_cast<StringId>(STR_STOPPING_1), // Vehicle::Status::Arriving
|
||
static_cast<StringId>(STR_UNLOADING_PASSENGERS), // Vehicle::Status::UnloadingPassengers
|
||
};
|
||
static_assert(std::size(SingleSessionVehicleStatusNames) == 7);
|
||
|
||
struct WindowRideMazeDesignOption
|
||
{
|
||
StringId text;
|
||
uint32_t sprite;
|
||
};
|
||
|
||
static constexpr std::array MazeOptions = {
|
||
WindowRideMazeDesignOption{ STR_RIDE_DESIGN_MAZE_BRICK_WALLS, SPR_RIDE_DESIGN_PREVIEW_MAZE_BRICK_WALLS },
|
||
WindowRideMazeDesignOption{ STR_RIDE_DESIGN_MAZE_HEDGES, SPR_RIDE_DESIGN_PREVIEW_MAZE_HEDGES },
|
||
WindowRideMazeDesignOption{ STR_RIDE_DESIGN_MAZE_ICE_BLOCKS, SPR_RIDE_DESIGN_PREVIEW_MAZE_ICE_BLOCKS },
|
||
WindowRideMazeDesignOption{ STR_RIDE_DESIGN_MAZE_WOODEN_FENCES, SPR_RIDE_DESIGN_PREVIEW_MAZE_WOODEN_FENCES },
|
||
};
|
||
static_assert(std::size(MazeOptions) == 4);
|
||
|
||
struct GraphsYAxis
|
||
{
|
||
uint8_t interval;
|
||
int8_t unit;
|
||
int8_t unit_interval;
|
||
StringId label;
|
||
};
|
||
|
||
static constexpr const std::array GraphsYAxisDetails = {
|
||
GraphsYAxis{ 11, 0, 10, STR_RIDE_STATS_VELOCITY_FORMAT }, // GRAPH_VELOCITY
|
||
GraphsYAxis{ 10, 0, 15, STR_RIDE_STATS_ALTITUDE_FORMAT }, // GRAPH_ALTITUDE
|
||
GraphsYAxis{ 13, -3, 1, STR_RIDE_STATS_G_FORCE_FORMAT }, // GRAPH_VERTICAL
|
||
GraphsYAxis{ 13, -4, 1, STR_RIDE_STATS_G_FORCE_FORMAT }, // GRAPH_LATERAL
|
||
};
|
||
static_assert(std::size(GraphsYAxisDetails) == 4);
|
||
|
||
static constexpr auto RIDE_G_FORCES_RED_NEG_VERTICAL = -FIXED_2DP(2, 50);
|
||
static constexpr auto RIDE_G_FORCES_RED_LATERAL = FIXED_2DP(2, 80);
|
||
|
||
// Used for sorting the ride type cheat dropdown.
|
||
struct RideTypeLabel
|
||
{
|
||
ride_type_t RideTypeId;
|
||
StringId LabelId;
|
||
u8string_view LabelString;
|
||
};
|
||
|
||
// Used for sorting the vehicle type dropdown.
|
||
struct VehicleTypeLabel
|
||
{
|
||
ObjectEntryIndex SubTypeId;
|
||
StringId LabelId;
|
||
u8string_view LabelString;
|
||
};
|
||
|
||
// Used for sorting the entrance type dropdown.
|
||
struct EntranceTypeLabel
|
||
{
|
||
ObjectEntryIndex EntranceTypeId;
|
||
StringId LabelId;
|
||
u8string_view LabelString;
|
||
};
|
||
|
||
class RideWindow final : public Window
|
||
{
|
||
int16_t _viewIndex;
|
||
std::vector<RideTypeLabel> _rideDropdownData;
|
||
int32_t _rideDropdownDataLanguage = LANGUAGE_UNDEFINED;
|
||
int32_t _vehicleDropdownDataLanguage = LANGUAGE_UNDEFINED;
|
||
int32_t _entranceDropdownDataLanguage = LANGUAGE_UNDEFINED;
|
||
const RideObjectEntry* _vehicleDropdownRideType = nullptr;
|
||
bool _vehicleDropdownExpanded = false;
|
||
std::vector<VehicleTypeLabel> _vehicleDropdownData;
|
||
int16_t _vehicleIndex = 0;
|
||
uint16_t _rideColour = 0;
|
||
std::vector<EntranceTypeLabel> _entranceDropdownData;
|
||
bool _autoScrollGraph = true;
|
||
|
||
public:
|
||
RideWindow(const Ride& ride)
|
||
{
|
||
rideId = ride.id;
|
||
}
|
||
|
||
virtual void OnOpen() override
|
||
{
|
||
widgets = PageWidgets[WINDOW_RIDE_PAGE_MAIN];
|
||
hold_down_widgets = PageHoldDownWidgets[WINDOW_RIDE_PAGE_MAIN];
|
||
|
||
page = WINDOW_RIDE_PAGE_MAIN;
|
||
frame_no = 0;
|
||
list_information_type = 0;
|
||
picked_peep_frame = 0;
|
||
DisableTabs();
|
||
min_width = 316;
|
||
min_height = 180;
|
||
max_width = 500;
|
||
max_height = 450;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
{
|
||
Close();
|
||
return;
|
||
}
|
||
UpdateOverallView(*ride);
|
||
|
||
PopulateVehicleTypeDropdown(*ride, true);
|
||
}
|
||
|
||
virtual void OnClose() override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourClose();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsClose();
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnResize() override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MAINTENANCE:
|
||
MaintenanceResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MUSIC:
|
||
MusicResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_INCOME:
|
||
IncomeResize();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_CUSTOMER:
|
||
CustomerResize();
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnUpdate() override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MAINTENANCE:
|
||
MaintenanceUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MUSIC:
|
||
MusicUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_INCOME:
|
||
IncomeUpdate();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_CUSTOMER:
|
||
CustomerUpdate();
|
||
break;
|
||
}
|
||
}
|
||
|
||
virtual void OnPrepareDraw() override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MAINTENANCE:
|
||
MaintenanceOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MUSIC:
|
||
MusicOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_INCOME:
|
||
IncomeOnPrepareDraw();
|
||
break;
|
||
case WINDOW_RIDE_PAGE_CUSTOMER:
|
||
CustomerOnPrepareDraw();
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnDraw(DrawPixelInfo& dpi) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MAINTENANCE:
|
||
MaintenanceOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MUSIC:
|
||
MusicOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_INCOME:
|
||
IncomeOnDraw(dpi);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_CUSTOMER:
|
||
CustomerOnDraw(dpi);
|
||
break;
|
||
}
|
||
}
|
||
|
||
virtual OpenRCT2String OnTooltip(WidgetIndex widgetIndex, StringId fallback) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
return VehicleTooltip(widgetIndex, fallback);
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
return GraphsTooltip(widgetIndex, fallback);
|
||
}
|
||
return { fallback, {} };
|
||
}
|
||
virtual void OnMouseDown(WidgetIndex widgetIndex) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MAINTENANCE:
|
||
MaintenanceOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MUSIC:
|
||
MusicOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsOnMouseDown(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_INCOME:
|
||
IncomeOnMouseDown(widgetIndex);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnMouseUp(WidgetIndex widgetIndex) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MAINTENANCE:
|
||
MaintenanceOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MUSIC:
|
||
MusicOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_INCOME:
|
||
IncomeOnMouseUp(widgetIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_CUSTOMER:
|
||
CustomerOnMouseUp(widgetIndex);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainOnDropdown(widgetIndex, selectedIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleOnDropdown(widgetIndex, selectedIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingOnDropdown(widgetIndex, selectedIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MAINTENANCE:
|
||
MaintenanceOnDropdown(widgetIndex, selectedIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnDropdown(widgetIndex, selectedIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MUSIC:
|
||
MusicOnDropdown(widgetIndex, selectedIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnDropdown(widgetIndex, selectedIndex);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnTextInput(WidgetIndex widgetIndex, std::string_view text) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainOnTextInput(widgetIndex, text);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_OPERATING:
|
||
OperatingOnTextInput(widgetIndex, text);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_INCOME:
|
||
IncomeOnTextInput(widgetIndex, text);
|
||
break;
|
||
}
|
||
}
|
||
virtual ScreenSize OnScrollGetSize(int32_t scrollIndex) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
return GraphsScrollGetSize(scrollIndex);
|
||
}
|
||
return {};
|
||
}
|
||
virtual void OnScrollSelect(int32_t scrollIndex, int32_t scrollAreaType) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsOnScrollSelect(scrollIndex, scrollAreaType);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_VEHICLE:
|
||
VehicleOnScrollDraw(dpi, scrollIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnScrollDraw(dpi, scrollIndex);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_GRAPHS:
|
||
GraphsOnScrollDraw(dpi, scrollIndex);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnToolDown(widgetIndex, screenCoords);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnToolDown(widgetIndex, screenCoords);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_COLOUR:
|
||
ColourOnToolDrag(widgetIndex, screenCoords);
|
||
break;
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnToolDrag(widgetIndex, screenCoords);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnToolAbort(WidgetIndex widgetIndex) override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MEASUREMENTS:
|
||
MeasurementsOnToolAbort(widgetIndex);
|
||
break;
|
||
}
|
||
}
|
||
virtual void OnViewportRotate() override
|
||
{
|
||
switch (page)
|
||
{
|
||
case WINDOW_RIDE_PAGE_MAIN:
|
||
MainViewportRotate();
|
||
break;
|
||
}
|
||
}
|
||
|
||
void SetPage(int32_t newPage)
|
||
{
|
||
if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE))
|
||
if (classification == gCurrentToolWidget.window_classification && number == gCurrentToolWidget.window_number)
|
||
ToolCancel();
|
||
|
||
if (newPage == WINDOW_RIDE_PAGE_VEHICLE)
|
||
{
|
||
auto constructionWindow = WindowFindByClass(WindowClass::RideConstruction);
|
||
if (constructionWindow != nullptr && constructionWindow->number == number)
|
||
{
|
||
WindowCloseByClass(WindowClass::RideConstruction);
|
||
// Closing the construction window sets the tab to the first page, which we don't want here,
|
||
// as user just clicked the Vehicle page
|
||
SetPage(WINDOW_RIDE_PAGE_VEHICLE);
|
||
}
|
||
}
|
||
|
||
// Set listen only to viewport
|
||
bool listen = false;
|
||
if (newPage == WINDOW_RIDE_PAGE_MAIN && page == WINDOW_RIDE_PAGE_MAIN && viewport != nullptr
|
||
&& !(viewport->flags & VIEWPORT_FLAG_SOUND_ON))
|
||
listen = true;
|
||
|
||
page = newPage;
|
||
frame_no = 0;
|
||
picked_peep_frame = 0;
|
||
|
||
// There doesn't seem to be any need for this call, and it can sometimes modify the reported number of cars per
|
||
// train, so I've removed it if (newPage == WINDOW_RIDE_PAGE_VEHICLE) { ride_update_max_vehicles(ride);
|
||
//}
|
||
|
||
RemoveViewport();
|
||
|
||
hold_down_widgets = PageHoldDownWidgets[page];
|
||
pressed_widgets = 0;
|
||
widgets = PageWidgets[page];
|
||
DisableTabs();
|
||
Invalidate();
|
||
|
||
OnResize();
|
||
OnPrepareDraw();
|
||
InitScrollWidgets();
|
||
Invalidate();
|
||
|
||
if (listen && viewport != nullptr)
|
||
viewport->flags |= VIEWPORT_FLAG_SOUND_ON;
|
||
}
|
||
|
||
void SetViewIndex(int16_t newIndex)
|
||
{
|
||
_viewIndex = newIndex;
|
||
OnViewportRotate();
|
||
}
|
||
int16_t GetViewIndex() const
|
||
{
|
||
return _viewIndex;
|
||
}
|
||
|
||
void ResetVehicleIndex()
|
||
{
|
||
_vehicleIndex = 0;
|
||
}
|
||
|
||
private:
|
||
void DrawTabImage(DrawPixelInfo& dpi, int32_t tab, int32_t spriteIndex)
|
||
{
|
||
WidgetIndex widgetIndex = WIDX_TAB_1 + tab;
|
||
|
||
if (!WidgetIsDisabled(*this, widgetIndex))
|
||
{
|
||
if (page == tab)
|
||
{
|
||
int32_t frame = frame_no / PageTabAnimationDivisor[page];
|
||
spriteIndex += (frame % PageTabAnimationNumFrames[page]);
|
||
}
|
||
|
||
const auto& widget = widgets[widgetIndex];
|
||
GfxDrawSprite(dpi, ImageId(spriteIndex), windowPos + ScreenCoordsXY{ widget.left, widget.top });
|
||
}
|
||
}
|
||
|
||
void DrawTabMain(DrawPixelInfo& dpi)
|
||
{
|
||
WidgetIndex widgetIndex = WIDX_TAB_1 + static_cast<int32_t>(WINDOW_RIDE_PAGE_MAIN);
|
||
if (!WidgetIsDisabled(*this, widgetIndex))
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
int32_t spriteIndex = 0;
|
||
switch (ride->GetClassification())
|
||
{
|
||
case RideClassification::Ride:
|
||
spriteIndex = SPR_TAB_RIDE_0;
|
||
if (page == WINDOW_RIDE_PAGE_MAIN)
|
||
spriteIndex += (frame_no / 4) % 16;
|
||
break;
|
||
case RideClassification::ShopOrStall:
|
||
spriteIndex = SPR_TAB_SHOPS_AND_STALLS_0;
|
||
if (page == WINDOW_RIDE_PAGE_MAIN)
|
||
spriteIndex += (frame_no / 4) % 16;
|
||
break;
|
||
case RideClassification::KioskOrFacility:
|
||
spriteIndex = SPR_TAB_KIOSKS_AND_FACILITIES_0;
|
||
if (page == WINDOW_RIDE_PAGE_MAIN)
|
||
spriteIndex += (frame_no / 4) % 8;
|
||
break;
|
||
}
|
||
|
||
const auto& widget = widgets[widgetIndex];
|
||
GfxDrawSprite(dpi, ImageId(spriteIndex), windowPos + ScreenCoordsXY{ widget.left, widget.top });
|
||
}
|
||
}
|
||
}
|
||
|
||
void DrawTabVehicle(DrawPixelInfo& dpi)
|
||
{
|
||
WidgetIndex widgetIndex = WIDX_TAB_1 + static_cast<int32_t>(WINDOW_RIDE_PAGE_VEHICLE);
|
||
const auto& widget = widgets[widgetIndex];
|
||
|
||
if (!WidgetIsDisabled(*this, widgetIndex))
|
||
{
|
||
auto screenCoords = ScreenCoordsXY{ widget.left + 1, widget.top + 1 };
|
||
int32_t clipWidth = widget.right - screenCoords.x;
|
||
int32_t clipHeight = widget.bottom - 3 - screenCoords.y;
|
||
if (page == WINDOW_RIDE_PAGE_VEHICLE)
|
||
clipHeight += 4;
|
||
|
||
screenCoords += windowPos;
|
||
|
||
DrawPixelInfo clipDPI;
|
||
if (!ClipDrawPixelInfo(clipDPI, dpi, screenCoords, clipWidth, clipHeight))
|
||
{
|
||
return;
|
||
}
|
||
|
||
screenCoords = ScreenCoordsXY{ widget.width() / 2, widget.height() - 12 };
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
if (rideEntry->flags & RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF)
|
||
{
|
||
clipDPI.zoom_level = ZoomLevel{ 1 };
|
||
clipDPI.width *= 2;
|
||
clipDPI.height *= 2;
|
||
screenCoords.x *= 2;
|
||
screenCoords.y *= 2;
|
||
clipDPI.x *= 2;
|
||
clipDPI.y *= 2;
|
||
}
|
||
|
||
// For any suspended rides, move image higher in the vehicle tab on the rides window
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SUSPENDED))
|
||
{
|
||
screenCoords.y /= 4;
|
||
}
|
||
|
||
const auto vehicle = RideEntryGetVehicleAtPosition(ride->subtype, ride->num_cars_per_train, rideEntry->TabCar);
|
||
const auto& carEntry = rideEntry->Cars[vehicle];
|
||
|
||
auto vehicleId = ((ride->colour_scheme_type & 3) == VEHICLE_COLOUR_SCHEME_PER_VEHICLE) ? rideEntry->TabCar : 0;
|
||
VehicleColour vehicleColour = RideGetVehicleColour(*ride, vehicleId);
|
||
|
||
// imageIndex represents a precision of 64
|
||
auto imageIndex = OpenRCT2::Entity::Yaw::YawFrom4(2) * 2;
|
||
if (page == WINDOW_RIDE_PAGE_VEHICLE)
|
||
imageIndex += frame_no;
|
||
imageIndex = carEntry.SpriteByYaw(imageIndex / 2, SpriteGroupType::SlopeFlat);
|
||
imageIndex &= carEntry.TabRotationMask;
|
||
imageIndex *= carEntry.base_num_frames;
|
||
imageIndex += carEntry.base_image_id;
|
||
auto imageId = ImageId(imageIndex, vehicleColour.Body, vehicleColour.Trim, vehicleColour.Tertiary);
|
||
GfxDrawSprite(clipDPI, imageId, screenCoords);
|
||
}
|
||
}
|
||
|
||
void DrawTabCustomer(DrawPixelInfo& dpi)
|
||
{
|
||
WidgetIndex widgetIndex = WIDX_TAB_1 + static_cast<int32_t>(WINDOW_RIDE_PAGE_CUSTOMER);
|
||
|
||
if (!WidgetIsDisabled(*this, widgetIndex))
|
||
{
|
||
const auto& widget = widgets[widgetIndex];
|
||
int32_t spriteIndex = 0;
|
||
if (page == WINDOW_RIDE_PAGE_CUSTOMER)
|
||
spriteIndex = picked_peep_frame & ~3;
|
||
|
||
spriteIndex += GetPeepAnimation(PeepSpriteType::Normal).base_image + 1;
|
||
|
||
GfxDrawSprite(
|
||
dpi, ImageId(spriteIndex, COLOUR_BRIGHT_RED, COLOUR_TEAL),
|
||
windowPos + ScreenCoordsXY{ widget.midX(), widget.bottom - 6 });
|
||
}
|
||
}
|
||
|
||
void DrawTabImages(DrawPixelInfo& dpi)
|
||
{
|
||
DrawTabVehicle(dpi);
|
||
DrawTabImage(dpi, WINDOW_RIDE_PAGE_OPERATING, SPR_TAB_GEARS_0);
|
||
DrawTabImage(dpi, WINDOW_RIDE_PAGE_MAINTENANCE, SPR_TAB_WRENCH_0);
|
||
DrawTabImage(dpi, WINDOW_RIDE_PAGE_INCOME, SPR_TAB_ADMISSION_0);
|
||
DrawTabMain(dpi);
|
||
DrawTabImage(dpi, WINDOW_RIDE_PAGE_MEASUREMENTS, SPR_TAB_TIMER_0);
|
||
DrawTabImage(dpi, WINDOW_RIDE_PAGE_COLOUR, SPR_TAB_PAINT_0);
|
||
DrawTabImage(dpi, WINDOW_RIDE_PAGE_GRAPHS, SPR_TAB_GRAPH_A_0);
|
||
DrawTabCustomer(dpi);
|
||
DrawTabImage(dpi, WINDOW_RIDE_PAGE_MUSIC, SPR_TAB_MUSIC_0);
|
||
}
|
||
|
||
void DisableTabs()
|
||
{
|
||
uint32_t disabledTabs = 0;
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
|
||
if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_DATA_LOGGING))
|
||
disabledTabs |= (1uLL << WIDX_TAB_8); // 0x800
|
||
|
||
if (ride->type == RIDE_TYPE_MINI_GOLF)
|
||
disabledTabs |= (1uLL << WIDX_TAB_2 | 1uLL << WIDX_TAB_3 | 1uLL << WIDX_TAB_4); // 0xE0
|
||
|
||
if (rtd.HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
|
||
disabledTabs |= (1uLL << WIDX_TAB_2); // 0x20
|
||
|
||
if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL)
|
||
&& !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_VEHICLE_COLOURS)
|
||
&& !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT))
|
||
{
|
||
disabledTabs |= (1uLL << WIDX_TAB_5); // 0x100
|
||
}
|
||
|
||
if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY))
|
||
disabledTabs |= (1uLL << WIDX_TAB_3 | 1uLL << WIDX_TAB_4 | 1uLL << WIDX_TAB_7); // 0x4C0
|
||
|
||
if (!rtd.HasFlag(RIDE_TYPE_FLAG_ALLOW_MUSIC))
|
||
{
|
||
disabledTabs |= (1uLL << WIDX_TAB_6); // 0x200
|
||
}
|
||
|
||
if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_CASH_MACHINE) || rtd.HasFlag(RIDE_TYPE_FLAG_IS_FIRST_AID)
|
||
|| (GetGameState().Park.Flags & PARK_FLAGS_NO_MONEY) != 0)
|
||
disabledTabs |= (1uLL << WIDX_TAB_9); // 0x1000
|
||
|
||
if ((gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) != 0)
|
||
disabledTabs |= (1uLL << WIDX_TAB_4 | 1uLL << WIDX_TAB_6 | 1uLL << WIDX_TAB_9 | 1uLL << WIDX_TAB_10); // 0x3280
|
||
|
||
const auto* rideEntry = GetRideEntryByIndex(ride->subtype);
|
||
|
||
if (rideEntry == nullptr)
|
||
{
|
||
disabledTabs |= 1uLL << WIDX_TAB_2 | 1uLL << WIDX_TAB_3 | 1uLL << WIDX_TAB_4 | 1uLL << WIDX_TAB_5
|
||
| 1uLL << WIDX_TAB_6 | 1uLL << WIDX_TAB_7 | 1uLL << WIDX_TAB_8 | 1uLL << WIDX_TAB_9 | 1uLL << WIDX_TAB_10;
|
||
}
|
||
else if ((rideEntry->flags & RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB) != 0)
|
||
{
|
||
disabledTabs |= (1uLL << WIDX_TAB_5);
|
||
}
|
||
|
||
disabled_widgets = disabledTabs;
|
||
}
|
||
|
||
void UpdateOverallView(const Ride& ride) const
|
||
{
|
||
// Calculate x, y, z bounds of the entire ride using its track elements
|
||
TileElementIterator it;
|
||
|
||
TileElementIteratorBegin(&it);
|
||
|
||
CoordsXYZ min = { std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max(),
|
||
std::numeric_limits<int32_t>::max() };
|
||
CoordsXYZ max = { std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min(),
|
||
std::numeric_limits<int32_t>::min() };
|
||
|
||
while (TileElementIteratorNext(&it))
|
||
{
|
||
if (it.element->GetType() != TileElementType::Track)
|
||
continue;
|
||
|
||
if (it.element->AsTrack()->GetRideIndex() != ride.id)
|
||
continue;
|
||
|
||
auto location = TileCoordsXY(it.x, it.y).ToCoordsXY();
|
||
int32_t baseZ = it.element->GetBaseZ();
|
||
int32_t clearZ = it.element->GetClearanceZ();
|
||
|
||
min.x = std::min(min.x, location.x);
|
||
min.y = std::min(min.y, location.y);
|
||
min.z = std::min(min.z, baseZ);
|
||
|
||
max.x = std::max(max.x, location.x);
|
||
max.y = std::max(max.y, location.y);
|
||
max.z = std::max(max.z, clearZ);
|
||
}
|
||
|
||
const auto rideIndex = ride.id.ToUnderlying();
|
||
if (rideIndex >= _rideOverallViewsCache.size())
|
||
{
|
||
_rideOverallViewsCache.resize(rideIndex + 1);
|
||
}
|
||
|
||
auto& view = _rideOverallViewsCache[rideIndex];
|
||
view.loc = CoordsXYZ{ (min.x + max.x) / 2, (min.y + max.y) / 2, (min.z + max.z) / 2 } + CoordsXYZ{ 16, 16, -8 };
|
||
|
||
// Calculate size to determine from how far away to view the ride
|
||
const auto diff = max - min;
|
||
|
||
const int32_t size = static_cast<int32_t>(std::sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z));
|
||
|
||
if (size >= 80)
|
||
{
|
||
// Each farther zoom level shows twice as many tiles (log)
|
||
// Appropriate zoom is lowered by one to fill the entire view with the ride
|
||
const auto zoomValue = static_cast<int8_t>(std::ceil(std::log(size / 80)) - 1);
|
||
view.zoom = std::clamp(ZoomLevel{ zoomValue }, ZoomLevel{ 0 }, ZoomLevel::max());
|
||
}
|
||
else
|
||
{
|
||
// Small rides or stalls are zoomed in all the way.
|
||
view.zoom = ZoomLevel{ 0 };
|
||
}
|
||
}
|
||
|
||
void SetPressedTab()
|
||
{
|
||
int32_t i;
|
||
for (i = 0; i < WINDOW_RIDE_PAGE_COUNT; i++)
|
||
pressed_widgets &= ~(1 << (WIDX_TAB_1 + i));
|
||
pressed_widgets |= 1LL << (WIDX_TAB_1 + page);
|
||
}
|
||
|
||
void AnchorBorderWidgets()
|
||
{
|
||
ResizeFrameWithPage();
|
||
}
|
||
|
||
#pragma region Main
|
||
|
||
std::optional<StationIndex> GetStationIndexFromViewSelection() const
|
||
{
|
||
const auto* ride = GetRide(RideId::FromUnderlying(number));
|
||
if (ride == nullptr)
|
||
return std::nullopt;
|
||
|
||
int32_t viewSelectionIndex = _viewIndex - 1 - ride->NumTrains;
|
||
if (viewSelectionIndex < 0)
|
||
{
|
||
return std::nullopt;
|
||
}
|
||
|
||
for (const auto& station : ride->GetStations())
|
||
{
|
||
if (!station.Start.IsNull() && viewSelectionIndex-- == 0)
|
||
{
|
||
const auto stationIndex = ride->GetStationIndex(&station);
|
||
return std::make_optional(stationIndex);
|
||
}
|
||
}
|
||
return std::nullopt;
|
||
}
|
||
|
||
void InitViewport()
|
||
{
|
||
if (page != WINDOW_RIDE_PAGE_MAIN)
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
int32_t viewSelectionIndex = _viewIndex - 1;
|
||
|
||
std::optional<Focus> newFocus;
|
||
|
||
if (viewSelectionIndex >= 0 && viewSelectionIndex < ride->NumTrains
|
||
&& ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)
|
||
{
|
||
auto vehId = ride->vehicles[viewSelectionIndex];
|
||
const auto* rideEntry = ride->GetRideEntry();
|
||
if (rideEntry != nullptr && rideEntry->TabCar != 0)
|
||
{
|
||
Vehicle* vehicle = GetEntity<Vehicle>(vehId);
|
||
if (vehicle == nullptr)
|
||
{
|
||
vehId = EntityId::GetNull();
|
||
}
|
||
else if (!vehicle->next_vehicle_on_train.IsNull())
|
||
{
|
||
vehId = vehicle->next_vehicle_on_train;
|
||
}
|
||
}
|
||
if (!vehId.IsNull())
|
||
{
|
||
newFocus = Focus(vehId);
|
||
}
|
||
}
|
||
else if (viewSelectionIndex >= ride->NumTrains && viewSelectionIndex < (ride->NumTrains + ride->num_stations))
|
||
{
|
||
auto stationIndex = GetStationIndexFromViewSelection();
|
||
if (stationIndex)
|
||
{
|
||
const auto location = ride->GetStation(*stationIndex).GetStart();
|
||
newFocus = Focus(location);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (viewSelectionIndex > 0)
|
||
{
|
||
_viewIndex = 0;
|
||
}
|
||
if (number < _rideOverallViewsCache.size())
|
||
{
|
||
const auto& view = _rideOverallViewsCache[number];
|
||
newFocus = Focus(view.loc, view.zoom);
|
||
}
|
||
}
|
||
|
||
uint16_t newViewportFlags = 0;
|
||
if (viewport != nullptr)
|
||
{
|
||
if (focus == newFocus)
|
||
{
|
||
return;
|
||
}
|
||
newViewportFlags = viewport->flags;
|
||
RemoveViewport();
|
||
}
|
||
else if (gConfigGeneral.AlwaysShowGridlines)
|
||
{
|
||
newViewportFlags |= VIEWPORT_FLAG_GRIDLINES;
|
||
}
|
||
|
||
OnPrepareDraw();
|
||
|
||
focus = newFocus;
|
||
|
||
// rct2: 0x006aec9c only used here so brought it into the function
|
||
if (viewport == nullptr && !ride->overall_view.IsNull() && focus.has_value())
|
||
{
|
||
const auto& viewWidget = widgets[WIDX_VIEWPORT];
|
||
|
||
auto screenPos = windowPos + ScreenCoordsXY{ viewWidget.left + 1, viewWidget.top + 1 };
|
||
int32_t viewWidth = viewWidget.width() - 1;
|
||
int32_t viewHeight = viewWidget.height() - 1;
|
||
|
||
ViewportCreate(this, screenPos, viewWidth, viewHeight, focus.value());
|
||
|
||
flags |= WF_NO_SCROLLING;
|
||
Invalidate();
|
||
}
|
||
if (viewport != nullptr)
|
||
{
|
||
viewport->flags = newViewportFlags;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void Rename()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
auto rideName = ride->GetName();
|
||
WindowTextInputRawOpen(
|
||
this, WIDX_RENAME, STR_RIDE_ATTRACTION_NAME, STR_ENTER_NEW_NAME_FOR_THIS_RIDE_ATTRACTION, {},
|
||
rideName.c_str(), 32);
|
||
}
|
||
}
|
||
|
||
void MainOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_CONSTRUCTION:
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
RideConstructionStart(*ride);
|
||
if (WindowFindByNumber(WindowClass::RideConstruction, ride->id.ToUnderlying()) != nullptr)
|
||
{
|
||
Close();
|
||
return;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case WIDX_RENAME:
|
||
Rename();
|
||
break;
|
||
case WIDX_DEMOLISH:
|
||
ContextOpenDetailWindow(WD_DEMOLISH_RIDE, number);
|
||
break;
|
||
case WIDX_CLOSE_LIGHT:
|
||
case WIDX_SIMULATE_LIGHT:
|
||
case WIDX_TEST_LIGHT:
|
||
case WIDX_OPEN_LIGHT:
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
RideStatus status;
|
||
switch (widgetIndex)
|
||
{
|
||
default:
|
||
case WIDX_CLOSE_LIGHT:
|
||
status = RideStatus::Closed;
|
||
break;
|
||
case WIDX_SIMULATE_LIGHT:
|
||
status = RideStatus::Simulating;
|
||
break;
|
||
case WIDX_TEST_LIGHT:
|
||
status = RideStatus::Testing;
|
||
break;
|
||
case WIDX_OPEN_LIGHT:
|
||
status = RideStatus::Open;
|
||
break;
|
||
}
|
||
auto gameAction = RideSetStatusAction(ride->id, status);
|
||
GameActions::Execute(&gameAction);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
void MainResize()
|
||
{
|
||
int32_t minHeight = 180;
|
||
if (ThemeGetFlags() & UITHEME_FLAG_USE_LIGHTS_RIDE)
|
||
{
|
||
minHeight += 20 + RCT1_LIGHT_OFFSET;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
#ifdef __SIMULATE_IN_RIDE_WINDOW__
|
||
if (ride->SupportsStatus(RideStatus::Simulating))
|
||
{
|
||
minHeight += 14;
|
||
}
|
||
#endif
|
||
if (ride->SupportsStatus(RideStatus::Testing))
|
||
{
|
||
minHeight += 14;
|
||
}
|
||
}
|
||
}
|
||
if (GetGameState().Cheats.AllowArbitraryRideTypeChanges)
|
||
{
|
||
minHeight += 15;
|
||
}
|
||
|
||
flags |= WF_RESIZABLE;
|
||
WindowSetResize(*this, 316, minHeight, 500, 450);
|
||
// Unlike with other windows, the focus needs to be recentred so it’s best to just reset it.
|
||
focus = std::nullopt;
|
||
InitViewport();
|
||
}
|
||
|
||
size_t GetNumPeepsInTrain(const Ride& ride, int32_t trainIndex) const
|
||
{
|
||
auto numPeepsInTrain = 0;
|
||
const auto* vehicle = TryGetVehicle(ride.vehicles[trainIndex]);
|
||
while (vehicle != nullptr)
|
||
{
|
||
numPeepsInTrain += vehicle->num_peeps;
|
||
vehicle = TryGetVehicle(vehicle->next_vehicle_on_train);
|
||
}
|
||
return numPeepsInTrain;
|
||
}
|
||
|
||
bool TrainMustBeHidden(const Ride& ride, int32_t trainIndex) const
|
||
{
|
||
if (!(ride.lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
|
||
return true;
|
||
|
||
const auto* rideEntry = ride.GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return false;
|
||
|
||
if (!(rideEntry->flags & RIDE_ENTRY_FLAG_HIDE_EMPTY_TRAINS))
|
||
return false;
|
||
|
||
return GetNumPeepsInTrain(ride, trainIndex) == 0;
|
||
}
|
||
|
||
void ShowViewDropdown(Widget* widget)
|
||
{
|
||
Widget* dropdownWidget = widget - 1;
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
|
||
int32_t numItems = 1;
|
||
if (!rtd.HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
|
||
{
|
||
numItems += ride->num_stations;
|
||
numItems += ride->NumTrains;
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, 0, numItems, widget->right - dropdownWidget->left);
|
||
|
||
// First item
|
||
gDropdownItems[0].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[0].Args = STR_OVERALL_VIEW;
|
||
int32_t currentItem = 1;
|
||
|
||
// Vehicles
|
||
int32_t name = GetRideComponentName(rtd.NameConvention.vehicle).number;
|
||
for (int32_t i = 0; i < ride->NumTrains; i++)
|
||
{
|
||
gDropdownItems[currentItem].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[currentItem].Args = name | (currentItem << 16);
|
||
if (TrainMustBeHidden(*ride, i))
|
||
{
|
||
Dropdown::SetDisabled(currentItem, true);
|
||
}
|
||
currentItem++;
|
||
}
|
||
|
||
// Stations
|
||
name = GetRideComponentName(rtd.NameConvention.station).number;
|
||
for (int32_t i = 1; i <= ride->num_stations; i++)
|
||
{
|
||
gDropdownItems[currentItem].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[currentItem].Args = name | (i << 16);
|
||
currentItem++;
|
||
}
|
||
|
||
// Set checked item
|
||
Dropdown::SetChecked(_viewIndex, true);
|
||
}
|
||
|
||
RideStatus GetNextDefaultStatus(const Ride& ride) const
|
||
{
|
||
switch (ride.status)
|
||
{
|
||
default:
|
||
case RideStatus::Closed:
|
||
if ((ride.lifecycle_flags & RIDE_LIFECYCLE_CRASHED)
|
||
|| (ride.lifecycle_flags & RIDE_LIFECYCLE_HAS_STALLED_VEHICLE))
|
||
{
|
||
return RideStatus::Closed;
|
||
}
|
||
if (ride.SupportsStatus(RideStatus::Testing) && !(ride.lifecycle_flags & RIDE_LIFECYCLE_TESTED))
|
||
{
|
||
return RideStatus::Testing;
|
||
}
|
||
return RideStatus::Open;
|
||
case RideStatus::Simulating:
|
||
return RideStatus::Testing;
|
||
case RideStatus::Testing:
|
||
return (ride.lifecycle_flags & RIDE_LIFECYCLE_TESTED) ? RideStatus::Open : RideStatus::Closed;
|
||
case RideStatus::Open:
|
||
return RideStatus::Closed;
|
||
}
|
||
}
|
||
|
||
struct RideStatusDropdownInfo
|
||
{
|
||
struct Ride* Ride{};
|
||
RideStatus CurrentStatus{};
|
||
RideStatus DefaultStatus{};
|
||
|
||
int32_t NumItems{};
|
||
int32_t CheckedIndex = -1;
|
||
int32_t DefaultIndex = -1;
|
||
};
|
||
|
||
void SetDropdown(RideStatusDropdownInfo& info, RideStatus status, StringId text) const
|
||
{
|
||
if (info.Ride->SupportsStatus(status))
|
||
{
|
||
auto index = info.NumItems;
|
||
gDropdownItems[index].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[index].Args = text;
|
||
if (info.CurrentStatus == status)
|
||
{
|
||
info.CheckedIndex = index;
|
||
}
|
||
if (info.DefaultStatus == status)
|
||
{
|
||
info.DefaultIndex = index;
|
||
}
|
||
info.NumItems++;
|
||
}
|
||
}
|
||
|
||
void ShowOpenDropdown(Widget* widget)
|
||
{
|
||
RideStatusDropdownInfo info;
|
||
info.Ride = GetRide(rideId);
|
||
if (info.Ride == nullptr)
|
||
return;
|
||
|
||
info.CurrentStatus = info.Ride->status;
|
||
info.DefaultStatus = GetNextDefaultStatus(*info.Ride);
|
||
SetDropdown(info, RideStatus::Closed, STR_CLOSE_RIDE);
|
||
#ifdef __SIMULATE_IN_RIDE_WINDOW__
|
||
SetDropdown(info, RideStatus::Simulating, STR_SIMULATE_RIDE);
|
||
#endif
|
||
SetDropdown(info, RideStatus::Testing, STR_TEST_RIDE);
|
||
SetDropdown(info, RideStatus::Open, STR_OPEN_RIDE);
|
||
WindowDropdownShowText(
|
||
{ windowPos.x + widget->left, windowPos.y + widget->top }, widget->height() + 1, colours[1], 0, info.NumItems);
|
||
Dropdown::SetChecked(info.CheckedIndex, true);
|
||
gDropdownDefaultIndex = info.DefaultIndex;
|
||
}
|
||
|
||
static constexpr StringId GetRideTypeNameForDropdown(ride_type_t rideType)
|
||
{
|
||
switch (rideType)
|
||
{
|
||
case RIDE_TYPE_1D:
|
||
return STR_RIDE_NAME_1D;
|
||
case RIDE_TYPE_1F:
|
||
return STR_RIDE_NAME_1F;
|
||
case RIDE_TYPE_22:
|
||
return STR_RIDE_NAME_22;
|
||
case RIDE_TYPE_50:
|
||
return STR_RIDE_NAME_50;
|
||
case RIDE_TYPE_52:
|
||
return STR_RIDE_NAME_52;
|
||
case RIDE_TYPE_53:
|
||
return STR_RIDE_NAME_53;
|
||
case RIDE_TYPE_54:
|
||
return STR_RIDE_NAME_54;
|
||
case RIDE_TYPE_55:
|
||
return STR_RIDE_NAME_55;
|
||
case RIDE_TYPE_59:
|
||
return STR_RIDE_NAME_59;
|
||
default:
|
||
return GetRideTypeDescriptor(rideType).Naming.Name;
|
||
}
|
||
}
|
||
|
||
void PopulateRideTypeDropdown()
|
||
{
|
||
auto& ls = OpenRCT2::GetContext()->GetLocalisationService();
|
||
if (_rideDropdownDataLanguage == ls.GetCurrentLanguage())
|
||
return;
|
||
|
||
_rideDropdownData.clear();
|
||
|
||
for (uint8_t i = 0; i < RIDE_TYPE_COUNT; i++)
|
||
{
|
||
auto name = GetRideTypeNameForDropdown(i);
|
||
_rideDropdownData.push_back({ i, name, u8string_view{ ls.GetString(name) } });
|
||
}
|
||
|
||
std::sort(_rideDropdownData.begin(), _rideDropdownData.end(), [](auto& a, auto& b) {
|
||
return a.LabelString.compare(b.LabelString) < 0;
|
||
});
|
||
|
||
_rideDropdownDataLanguage = ls.GetCurrentLanguage();
|
||
}
|
||
|
||
void ShowRideTypeDropdown(Widget* widget)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
PopulateRideTypeDropdown();
|
||
|
||
for (size_t i = 0; i < _rideDropdownData.size(); i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = _rideDropdownData[i].LabelId;
|
||
}
|
||
|
||
Widget* dropdownWidget = widget - 1;
|
||
WindowDropdownShowText(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], Dropdown::Flag::StayOpen, RIDE_TYPE_COUNT);
|
||
|
||
// Find the current ride type in the ordered list.
|
||
int32_t pos = 0;
|
||
for (int32_t i = 0; i < RIDE_TYPE_COUNT; i++)
|
||
{
|
||
if (_rideDropdownData[i].RideTypeId == ride->type)
|
||
{
|
||
pos = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
gDropdownHighlightedIndex = pos;
|
||
gDropdownDefaultIndex = pos;
|
||
Dropdown::SetChecked(pos, true);
|
||
}
|
||
|
||
void ShowLocateDropdown(Widget* widget)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
gDropdownItems[0].Format = STR_LOCATE_SUBJECT_TIP;
|
||
gDropdownItems[1].Format = STR_FOLLOW_SUBJECT_TIP;
|
||
|
||
WindowDropdownShowText(
|
||
{ windowPos.x + widget->left, windowPos.y + widget->top }, widget->height() + 1, colours[1], 0, 2);
|
||
gDropdownDefaultIndex = 0;
|
||
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK) || _viewIndex == 0
|
||
|| _viewIndex > ride->NumTrains)
|
||
{
|
||
// Disable if we're a flat ride, 'overall view' is selected or a station is selected
|
||
Dropdown::SetDisabled(1, true);
|
||
}
|
||
}
|
||
|
||
void MainFollowRide()
|
||
{
|
||
auto* ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
if (!(ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAIN))
|
||
{
|
||
if (_viewIndex > 0)
|
||
{
|
||
if (_viewIndex <= ride->NumTrains)
|
||
{
|
||
Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[_viewIndex - 1]);
|
||
if (vehicle != nullptr)
|
||
{
|
||
auto headVehicleSpriteIndex = vehicle->Id;
|
||
WindowBase* w_main = WindowGetMain();
|
||
WindowFollowSprite(*w_main, headVehicleSpriteIndex);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void PopulateVehicleTypeDropdown(const Ride& ride, bool forceRefresh = false)
|
||
{
|
||
auto& objManager = GetContext()->GetObjectManager();
|
||
const auto* rideEntry = ride.GetRideEntry();
|
||
|
||
bool selectionShouldBeExpanded;
|
||
int32_t rideTypeIterator, rideTypeIteratorMax;
|
||
|
||
const auto& rtd = ride.GetRideTypeDescriptor();
|
||
if (GetGameState().Cheats.ShowVehiclesFromOtherTrackTypes
|
||
&& !(
|
||
rtd.HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE) || rtd.HasFlag(RIDE_TYPE_FLAG_IS_MAZE)
|
||
|| ride.type == RIDE_TYPE_MINI_GOLF))
|
||
{
|
||
selectionShouldBeExpanded = true;
|
||
rideTypeIterator = 0;
|
||
rideTypeIteratorMax = RIDE_TYPE_COUNT - 1;
|
||
}
|
||
else
|
||
{
|
||
selectionShouldBeExpanded = false;
|
||
rideTypeIterator = ride.type;
|
||
rideTypeIteratorMax = ride.type;
|
||
}
|
||
|
||
// Don't repopulate the list if we just did.
|
||
auto& ls = OpenRCT2::GetContext()->GetLocalisationService();
|
||
if (!forceRefresh && _vehicleDropdownExpanded == selectionShouldBeExpanded && _vehicleDropdownRideType == rideEntry
|
||
&& _vehicleDropdownDataLanguage == ls.GetCurrentLanguage())
|
||
return;
|
||
|
||
_vehicleDropdownData.clear();
|
||
|
||
for (; rideTypeIterator <= rideTypeIteratorMax; rideTypeIterator++)
|
||
{
|
||
const auto& rtdIterator = GetRideTypeDescriptor(rideTypeIterator);
|
||
if (selectionShouldBeExpanded && rtdIterator.HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
|
||
continue;
|
||
if (selectionShouldBeExpanded
|
||
&& (rtdIterator.HasFlag(RIDE_TYPE_FLAG_IS_MAZE) || rideTypeIterator == RIDE_TYPE_MINI_GOLF))
|
||
continue;
|
||
|
||
auto& rideEntries = objManager.GetAllRideEntries(rideTypeIterator);
|
||
for (auto rideEntryIndex : rideEntries)
|
||
{
|
||
const auto* currentRideEntry = GetRideEntryByIndex(rideEntryIndex);
|
||
if (currentRideEntry == nullptr)
|
||
continue;
|
||
|
||
// Skip if vehicle type has not been invented yet
|
||
if (!RideEntryIsInvented(rideEntryIndex) && !GetGameState().Cheats.IgnoreResearchStatus)
|
||
continue;
|
||
|
||
auto name = currentRideEntry->naming.Name;
|
||
_vehicleDropdownData.push_back({ rideEntryIndex, name, u8string_view{ ls.GetString(name) } });
|
||
}
|
||
}
|
||
|
||
std::sort(_vehicleDropdownData.begin(), _vehicleDropdownData.end(), [](auto& a, auto& b) {
|
||
return a.LabelString.compare(b.LabelString) < 0;
|
||
});
|
||
|
||
_vehicleDropdownExpanded = selectionShouldBeExpanded;
|
||
_vehicleDropdownRideType = rideEntry;
|
||
_vehicleDropdownDataLanguage = ls.GetCurrentLanguage();
|
||
}
|
||
|
||
void ShowVehicleTypeDropdown(Widget* widget)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
PopulateVehicleTypeDropdown(*ride);
|
||
|
||
size_t numItems = std::min<size_t>(_vehicleDropdownData.size(), Dropdown::ItemsMaxSize);
|
||
|
||
for (size_t i = 0; i < numItems; i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = _vehicleDropdownData[i].LabelId;
|
||
}
|
||
|
||
Widget* dropdownWidget = widget - 1;
|
||
auto ddWidth = WindowDropDownHasMultipleColumns(numItems) ? dropdownWidget->width() - 24 : dropdownWidget->width();
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, numItems, ddWidth);
|
||
|
||
// Find the current vehicle type in the ordered list.
|
||
int32_t pos = 0;
|
||
for (int32_t i = 0; i < static_cast<int32_t>(_vehicleDropdownData.size()); i++)
|
||
{
|
||
if (_vehicleDropdownData[i].SubTypeId == ride->subtype)
|
||
{
|
||
pos = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
gDropdownHighlightedIndex = pos;
|
||
gDropdownDefaultIndex = pos;
|
||
Dropdown::SetChecked(pos, true);
|
||
}
|
||
|
||
void PopulateEntranceStyleDropdown()
|
||
{
|
||
auto& ls = OpenRCT2::GetContext()->GetLocalisationService();
|
||
if (_entranceDropdownDataLanguage == ls.GetCurrentLanguage())
|
||
return;
|
||
|
||
_entranceDropdownData.clear();
|
||
|
||
auto& objManager = GetContext()->GetObjectManager();
|
||
|
||
for (ObjectEntryIndex i = 0; i < MAX_STATION_OBJECTS; i++)
|
||
{
|
||
auto stationObj = static_cast<StationObject*>(objManager.GetLoadedObject(ObjectType::Station, i));
|
||
if (stationObj != nullptr)
|
||
{
|
||
auto name = stationObj->NameStringId;
|
||
_entranceDropdownData.push_back({ i, name, u8string_view{ ls.GetString(name) } });
|
||
}
|
||
}
|
||
|
||
std::sort(_entranceDropdownData.begin(), _entranceDropdownData.end(), [](auto& a, auto& b) {
|
||
return a.LabelString.compare(b.LabelString) < 0;
|
||
});
|
||
|
||
_entranceDropdownDataLanguage = ls.GetCurrentLanguage();
|
||
}
|
||
|
||
void ShowEntranceStyleDropdown()
|
||
{
|
||
auto dropdownWidget = &widgets[WIDX_ENTRANCE_STYLE_DROPDOWN] - 1;
|
||
auto ride = GetRide(rideId);
|
||
|
||
PopulateEntranceStyleDropdown();
|
||
|
||
for (size_t i = 0; i < _entranceDropdownData.size(); i++)
|
||
{
|
||
gDropdownItems[i].Args = _entranceDropdownData[i].LabelId;
|
||
gDropdownItems[i].Format = _entranceDropdownData[i].EntranceTypeId == ride->entrance_style
|
||
? STR_DROPDOWN_MENU_LABEL_SELECTED
|
||
: STR_DROPDOWN_MENU_LABEL;
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, _entranceDropdownData.size(),
|
||
widgets[WIDX_ENTRANCE_STYLE_DROPDOWN].right - dropdownWidget->left);
|
||
}
|
||
|
||
void MainOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_VIEW_DROPDOWN:
|
||
ShowViewDropdown(&widgets[widgetIndex]);
|
||
break;
|
||
case WIDX_OPEN:
|
||
ShowOpenDropdown(&widgets[widgetIndex]);
|
||
break;
|
||
case WIDX_RIDE_TYPE_DROPDOWN:
|
||
ShowRideTypeDropdown(&widgets[widgetIndex]);
|
||
break;
|
||
case WIDX_LOCATE:
|
||
ShowLocateDropdown(&widgets[widgetIndex]);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MainOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_VIEW_DROPDOWN:
|
||
if (dropdownIndex == -1)
|
||
{
|
||
dropdownIndex = _viewIndex + 1;
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
if (dropdownIndex != 0 && dropdownIndex <= ride->NumTrains
|
||
&& !(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
|
||
{
|
||
dropdownIndex = ride->NumTrains + 1;
|
||
}
|
||
if (dropdownIndex >= gDropdownNumItems)
|
||
{
|
||
dropdownIndex = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
_viewIndex = dropdownIndex;
|
||
InitViewport();
|
||
Invalidate();
|
||
break;
|
||
case WIDX_OPEN:
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
auto status = RideStatus::Closed;
|
||
if (dropdownIndex < 0)
|
||
{
|
||
dropdownIndex = gDropdownHighlightedIndex;
|
||
}
|
||
if (dropdownIndex < static_cast<int32_t>(std::size(gDropdownItems)))
|
||
{
|
||
switch (gDropdownItems[dropdownIndex].Args)
|
||
{
|
||
case STR_CLOSE_RIDE:
|
||
status = RideStatus::Closed;
|
||
break;
|
||
case STR_SIMULATE_RIDE:
|
||
status = RideStatus::Simulating;
|
||
break;
|
||
case STR_TEST_RIDE:
|
||
status = RideStatus::Testing;
|
||
break;
|
||
case STR_OPEN_RIDE:
|
||
status = RideStatus::Open;
|
||
break;
|
||
}
|
||
}
|
||
auto gameAction = RideSetStatusAction(ride->id, status);
|
||
GameActions::Execute(&gameAction);
|
||
}
|
||
break;
|
||
}
|
||
case WIDX_RIDE_TYPE_DROPDOWN:
|
||
if (dropdownIndex != -1 && dropdownIndex < RIDE_TYPE_COUNT)
|
||
{
|
||
auto rideLabelId = std::clamp(dropdownIndex, 0, RIDE_TYPE_COUNT - 1);
|
||
auto rideType = _rideDropdownData[rideLabelId].RideTypeId;
|
||
if (rideType < RIDE_TYPE_COUNT)
|
||
{
|
||
auto rideSetSetting = RideSetSettingAction(rideId, RideSetSetting::RideType, rideType);
|
||
rideSetSetting.SetCallback([](const GameAction* ga, const GameActions::Result* result) {
|
||
// Reset ghost track if ride construction window is open, prevents a crash
|
||
// Will get set to the correct Alternative variable during set_default_next_piece.
|
||
// TODO: Rework construction window to prevent the need for this.
|
||
_currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES;
|
||
RideConstructionSetDefaultNextPiece();
|
||
});
|
||
GameActions::Execute(&rideSetSetting);
|
||
}
|
||
}
|
||
break;
|
||
case WIDX_LOCATE:
|
||
{
|
||
if (dropdownIndex == 0)
|
||
{
|
||
ScrollToViewport();
|
||
}
|
||
else if (dropdownIndex == 1)
|
||
{
|
||
MainFollowRide();
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
void MainUpdate()
|
||
{
|
||
// Update tab animation
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_1);
|
||
|
||
// Update status
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
if (!(ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAIN))
|
||
{
|
||
if (_viewIndex == 0)
|
||
return;
|
||
|
||
if (_viewIndex <= ride->NumTrains)
|
||
{
|
||
Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[_viewIndex - 1]);
|
||
if (vehicle == nullptr
|
||
|| (vehicle->status != Vehicle::Status::Travelling
|
||
&& vehicle->status != Vehicle::Status::TravellingCableLift
|
||
&& vehicle->status != Vehicle::Status::TravellingDodgems
|
||
&& vehicle->status != Vehicle::Status::TravellingBoat))
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_MAIN;
|
||
}
|
||
WidgetInvalidate(*this, WIDX_STATUS);
|
||
}
|
||
|
||
void MainOnTextInput(WidgetIndex widgetIndex, std::string_view text)
|
||
{
|
||
if (widgetIndex != WIDX_RENAME || text.empty())
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
auto strText = std::string(text);
|
||
auto gameAction = RideSetNameAction(ride->id, strText);
|
||
GameActions::Execute(&gameAction);
|
||
}
|
||
}
|
||
|
||
void MainViewportRotate()
|
||
{
|
||
InitViewport();
|
||
}
|
||
|
||
void MainOnPrepareDraw()
|
||
{
|
||
int32_t i, widgetHeight;
|
||
|
||
auto* newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
disabled_widgets &= ~((1uLL << WIDX_DEMOLISH) | (1uLL << WIDX_CONSTRUCTION));
|
||
if (ride->lifecycle_flags & (RIDE_LIFECYCLE_INDESTRUCTIBLE | RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK)
|
||
&& !GetGameState().Cheats.MakeAllDestructible)
|
||
disabled_widgets |= (1uLL << WIDX_DEMOLISH);
|
||
|
||
auto ft = Formatter::Common();
|
||
ride->FormatNameTo(ft);
|
||
|
||
uint32_t spriteIds[] = {
|
||
SPR_CLOSED,
|
||
SPR_OPEN,
|
||
SPR_TESTING,
|
||
SPR_G2_SIMULATE,
|
||
};
|
||
widgets[WIDX_OPEN].image = ImageId(spriteIds[EnumValue(ride->status)]);
|
||
|
||
#ifdef __SIMULATE_IN_RIDE_WINDOW__
|
||
widgets[WIDX_CLOSE_LIGHT].image = SPR_G2_RCT1_CLOSE_BUTTON_0 + (ride->status == RideStatus::Closed) * 2
|
||
+ WidgetIsPressed(*this, WIDX_CLOSE_LIGHT);
|
||
widgets[WIDX_SIMULATE_LIGHT].image = SPR_G2_RCT1_SIMULATE_BUTTON_0 + (ride->status == RideStatus::Simulating) * 2
|
||
+ WidgetIsPressed(*w, WIDX_SIMULATE_LIGHT);
|
||
widgets[WIDX_TEST_LIGHT].image = SPR_G2_RCT1_TEST_BUTTON_0 + (ride->status == RideStatus::Testing) * 2
|
||
+ WidgetIsPressed(*this, WIDX_TEST_LIGHT);
|
||
#else
|
||
const auto closeLightImage = SPR_G2_RCT1_CLOSE_BUTTON_0 + (ride->status == RideStatus::Closed) * 2
|
||
+ WidgetIsPressed(*this, WIDX_CLOSE_LIGHT);
|
||
widgets[WIDX_CLOSE_LIGHT].image = ImageId(closeLightImage);
|
||
|
||
auto baseSprite = ride->status == RideStatus::Simulating ? SPR_G2_RCT1_SIMULATE_BUTTON_0
|
||
: SPR_G2_RCT1_TEST_BUTTON_0;
|
||
const auto testLightImage = baseSprite
|
||
+ (ride->status == RideStatus::Testing || ride->status == RideStatus::Simulating) * 2
|
||
+ WidgetIsPressed(*this, WIDX_TEST_LIGHT);
|
||
widgets[WIDX_TEST_LIGHT].image = ImageId(testLightImage);
|
||
#endif
|
||
const auto openLightImage = SPR_G2_RCT1_OPEN_BUTTON_0 + (ride->status == RideStatus::Open) * 2
|
||
+ WidgetIsPressed(*this, WIDX_OPEN_LIGHT);
|
||
widgets[WIDX_OPEN_LIGHT].image = ImageId(openLightImage);
|
||
|
||
AnchorBorderWidgets();
|
||
|
||
const int32_t offset = GetGameState().Cheats.AllowArbitraryRideTypeChanges ? 15 : 0;
|
||
// Anchor main page specific widgets
|
||
widgets[WIDX_VIEWPORT].right = width - 26;
|
||
widgets[WIDX_VIEWPORT].bottom = height - (14 + offset);
|
||
widgets[WIDX_STATUS].right = width - 26;
|
||
widgets[WIDX_STATUS].top = height - (13 + offset);
|
||
widgets[WIDX_STATUS].bottom = height - (3 + offset);
|
||
widgets[WIDX_VIEW].right = width - 60;
|
||
widgets[WIDX_VIEW_DROPDOWN].right = width - 61;
|
||
widgets[WIDX_VIEW_DROPDOWN].left = width - 71;
|
||
widgets[WIDX_RIDE_TYPE].right = width - 26;
|
||
widgets[WIDX_RIDE_TYPE].top = height - 17;
|
||
widgets[WIDX_RIDE_TYPE].bottom = height - 4;
|
||
widgets[WIDX_RIDE_TYPE_DROPDOWN].left = width - 37;
|
||
widgets[WIDX_RIDE_TYPE_DROPDOWN].right = width - 27;
|
||
widgets[WIDX_RIDE_TYPE_DROPDOWN].top = height - 16;
|
||
widgets[WIDX_RIDE_TYPE_DROPDOWN].bottom = height - 5;
|
||
|
||
if (!GetGameState().Cheats.AllowArbitraryRideTypeChanges)
|
||
{
|
||
widgets[WIDX_RIDE_TYPE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_RIDE_TYPE_DROPDOWN].type = WindowWidgetType::Empty;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_RIDE_TYPE].type = WindowWidgetType::DropdownMenu;
|
||
widgets[WIDX_RIDE_TYPE].text = ride->GetRideTypeDescriptor().Naming.Name;
|
||
widgets[WIDX_RIDE_TYPE_DROPDOWN].type = WindowWidgetType::Button;
|
||
}
|
||
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
|
||
if (ThemeGetFlags() & UITHEME_FLAG_USE_LIGHTS_RIDE)
|
||
{
|
||
widgets[WIDX_OPEN].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::ImgBtn;
|
||
widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::Empty;
|
||
#ifdef __SIMULATE_IN_RIDE_WINDOW__
|
||
if (ride->SupportsStatus(RideStatus::Simulating))
|
||
widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::ImgBtn;
|
||
#endif
|
||
widgets[WIDX_TEST_LIGHT].type = ride->SupportsStatus(RideStatus::Testing) ? WindowWidgetType::ImgBtn
|
||
: WindowWidgetType::Empty;
|
||
widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::ImgBtn;
|
||
|
||
widgetHeight = 62;
|
||
if (widgets[WIDX_SIMULATE_LIGHT].type != WindowWidgetType::Empty)
|
||
{
|
||
widgets[WIDX_SIMULATE_LIGHT].top = widgetHeight;
|
||
widgets[WIDX_SIMULATE_LIGHT].bottom = widgetHeight + 13;
|
||
widgetHeight += 14;
|
||
}
|
||
if (widgets[WIDX_TEST_LIGHT].type != WindowWidgetType::Empty)
|
||
{
|
||
widgets[WIDX_TEST_LIGHT].top = widgetHeight;
|
||
widgets[WIDX_TEST_LIGHT].bottom = widgetHeight + 13;
|
||
widgetHeight += 14;
|
||
}
|
||
widgets[WIDX_OPEN_LIGHT].top = widgetHeight;
|
||
widgets[WIDX_OPEN_LIGHT].bottom = widgetHeight + 13;
|
||
widgetHeight += 14 - 24 + RCT1_LIGHT_OFFSET;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_OPEN].type = WindowWidgetType::FlatBtn;
|
||
widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_TEST_LIGHT].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::Empty;
|
||
widgetHeight = 46;
|
||
}
|
||
for (i = WIDX_CLOSE_LIGHT; i <= WIDX_OPEN_LIGHT; i++)
|
||
{
|
||
widgets[i].left = width - 20;
|
||
widgets[i].right = width - 7;
|
||
}
|
||
for (i = WIDX_OPEN; i <= WIDX_DEMOLISH; i++, widgetHeight += 24)
|
||
{
|
||
widgets[i].left = width - 25;
|
||
widgets[i].right = width - 2;
|
||
widgets[i].top = widgetHeight;
|
||
widgets[i].bottom = widgetHeight + 23;
|
||
}
|
||
}
|
||
|
||
StringId GetStatusOverallView(Formatter& ft) const
|
||
{
|
||
auto stringId = STR_NONE;
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
ride->FormatStatusTo(ft);
|
||
stringId = STR_BLACK_STRING;
|
||
if ((ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) || (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED))
|
||
{
|
||
stringId = STR_RED_OUTLINED_STRING;
|
||
}
|
||
}
|
||
return stringId;
|
||
}
|
||
|
||
StringId GetStatusVehicle(Formatter& ft) const
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return STR_EMPTY;
|
||
|
||
auto vehicle = GetEntity<Vehicle>(ride->vehicles[_viewIndex - 1]);
|
||
if (vehicle == nullptr)
|
||
return STR_EMPTY;
|
||
|
||
if (vehicle->status != Vehicle::Status::Crashing && vehicle->status != Vehicle::Status::Crashed)
|
||
{
|
||
auto trackType = vehicle->GetTrackType();
|
||
if (trackType == TrackElemType::BlockBrakes || trackType == TrackElemType::CableLiftHill
|
||
|| trackType == TrackElemType::Up25ToFlat || trackType == TrackElemType::Up60ToFlat
|
||
|| trackType == TrackElemType::DiagUp25ToFlat || trackType == TrackElemType::DiagUp60ToFlat
|
||
|| trackType == TrackElemType::DiagBlockBrakes)
|
||
{
|
||
if (ride->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_BLOCK_BRAKES) && vehicle->velocity == 0)
|
||
{
|
||
ft.Add<StringId>(STR_STOPPED_BY_BLOCK_BRAKES);
|
||
return STR_BLACK_STRING;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (ride->type == RIDE_TYPE_MINI_GOLF)
|
||
return STR_EMPTY;
|
||
|
||
auto stringId = VehicleStatusNames[EnumValue(vehicle->status)];
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SINGLE_SESSION)
|
||
&& vehicle->status <= Vehicle::Status::UnloadingPassengers)
|
||
{
|
||
stringId = SingleSessionVehicleStatusNames[EnumValue(vehicle->status)];
|
||
}
|
||
|
||
ft.Add<StringId>(stringId);
|
||
uint16_t speedInMph = (abs(vehicle->velocity) * 9) >> 18;
|
||
ft.Add<uint16_t>(speedInMph);
|
||
const RideComponentName stationName = GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.station);
|
||
ft.Add<StringId>(ride->num_stations > 1 ? stationName.number : stationName.singular);
|
||
ft.Add<uint16_t>(vehicle->current_station.ToUnderlying() + 1);
|
||
return stringId != STR_CRASHING && stringId != STR_CRASHED_0 ? STR_BLACK_STRING : STR_RED_OUTLINED_STRING;
|
||
}
|
||
|
||
StringId GetStatusStation(Formatter& ft) const
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return STR_NONE;
|
||
|
||
const auto stationIndex = GetStationIndexFromViewSelection();
|
||
if (!stationIndex)
|
||
{
|
||
return STR_NONE;
|
||
}
|
||
|
||
const auto& station = ride->GetStation(*stationIndex);
|
||
StringId stringId = STR_EMPTY;
|
||
// Entrance / exit
|
||
if (ride->status == RideStatus::Closed)
|
||
{
|
||
if (station.Entrance.IsNull())
|
||
stringId = STR_NO_ENTRANCE;
|
||
else if (station.Exit.IsNull())
|
||
stringId = STR_NO_EXIT;
|
||
}
|
||
else
|
||
{
|
||
if (station.Entrance.IsNull())
|
||
stringId = STR_EXIT_ONLY;
|
||
}
|
||
// Queue length
|
||
if (stringId == STR_EMPTY)
|
||
{
|
||
stringId = STR_QUEUE_EMPTY;
|
||
uint16_t queueLength = ride->GetStation(*stationIndex).QueueLength;
|
||
if (queueLength == 1)
|
||
stringId = STR_QUEUE_ONE_PERSON;
|
||
else if (queueLength > 1)
|
||
stringId = STR_QUEUE_PEOPLE;
|
||
|
||
ft.Add<StringId>(stringId);
|
||
ft.Add<uint16_t>(queueLength);
|
||
}
|
||
else
|
||
{
|
||
ft.Add<StringId>(stringId);
|
||
}
|
||
|
||
return STR_BLACK_STRING;
|
||
}
|
||
|
||
StringId GetStatus(Formatter& ft) const
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (_viewIndex == 0)
|
||
return GetStatusOverallView(ft);
|
||
if (ride != nullptr && _viewIndex <= ride->NumTrains)
|
||
return GetStatusVehicle(ft);
|
||
if (ride != nullptr && ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
|
||
return GetStatusOverallView(ft);
|
||
return GetStatusStation(ft);
|
||
}
|
||
|
||
void MainOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
WindowDrawWidgets(*this, dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
// Viewport and ear icon
|
||
if (viewport != nullptr)
|
||
{
|
||
WindowDrawViewport(dpi, *this);
|
||
if (viewport->flags & VIEWPORT_FLAG_SOUND_ON)
|
||
GfxDrawSprite(dpi, ImageId(SPR_HEARING_VIEWPORT), WindowGetViewportSoundIconPos(*this));
|
||
}
|
||
|
||
// View dropdown
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto ft = Formatter();
|
||
if (_viewIndex != 0)
|
||
{
|
||
if (_viewIndex > ride->NumTrains)
|
||
{
|
||
ft.Add<StringId>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.station).number);
|
||
ft.Add<uint16_t>(_viewIndex - ride->NumTrains);
|
||
}
|
||
else
|
||
{
|
||
ft.Add<StringId>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).number);
|
||
ft.Add<uint16_t>(_viewIndex);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ft.Add<StringId>(STR_OVERALL_VIEW);
|
||
}
|
||
|
||
auto* widget = &widgets[WIDX_VIEW];
|
||
DrawTextBasic(
|
||
dpi, { windowPos.x + (widget->left + widget->right - 11) / 2, windowPos.y + widget->top },
|
||
STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE });
|
||
|
||
// Status
|
||
ft = Formatter();
|
||
widget = &widgets[WIDX_STATUS];
|
||
StringId rideStatus = GetStatus(ft);
|
||
DrawTextEllipsised(
|
||
dpi, windowPos + ScreenCoordsXY{ (widget->left + widget->right) / 2, widget->top }, widget->width(), rideStatus,
|
||
ft, { TextAlignment::CENTRE });
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Vehicle
|
||
|
||
void VehicleOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void VehicleResize()
|
||
{
|
||
WindowSetResize(*this, 316, 221, 316, 221);
|
||
}
|
||
|
||
void VehicleOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_VEHICLE_TYPE_DROPDOWN:
|
||
ShowVehicleTypeDropdown(&widgets[widgetIndex]);
|
||
break;
|
||
case WIDX_VEHICLE_REVERSED_TRAINS_CHECKBOX:
|
||
ride->SetReversedTrains(!ride->HasLifecycleFlag(RIDE_LIFECYCLE_REVERSED_TRAINS));
|
||
break;
|
||
case WIDX_VEHICLE_TRAINS_INCREASE:
|
||
if (ride->NumTrains < OpenRCT2::Limits::MaxTrainsPerRide)
|
||
ride->SetNumTrains(ride->NumTrains + 1);
|
||
break;
|
||
case WIDX_VEHICLE_TRAINS_DECREASE:
|
||
if (ride->NumTrains > 1)
|
||
ride->SetNumTrains(ride->NumTrains - 1);
|
||
break;
|
||
case WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE:
|
||
if (ride->num_cars_per_train < OpenRCT2::Limits::MaxCarsPerTrain)
|
||
ride->SetNumCarsPerVehicle(ride->num_cars_per_train + 1);
|
||
break;
|
||
case WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE:
|
||
if (ride->num_cars_per_train > 1)
|
||
ride->SetNumCarsPerVehicle(ride->num_cars_per_train - 1);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void VehicleOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
|
||
{
|
||
if (dropdownIndex == -1)
|
||
return;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_VEHICLE_TYPE_DROPDOWN:
|
||
if (dropdownIndex >= 0 && static_cast<std::size_t>(dropdownIndex) < _vehicleDropdownData.size())
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
auto newRideType = _vehicleDropdownData[dropdownIndex].SubTypeId;
|
||
ride->SetRideEntry(newRideType);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void VehicleUpdate()
|
||
{
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_2);
|
||
}
|
||
|
||
OpenRCT2String VehicleTooltip(const WidgetIndex widgetIndex, StringId fallback)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return { STR_NONE, {} };
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_VEHICLE_TRAINS:
|
||
case WIDX_VEHICLE_TRAINS_DECREASE:
|
||
case WIDX_VEHICLE_TRAINS_INCREASE:
|
||
{
|
||
auto ft = Formatter();
|
||
ft.Increment(12);
|
||
|
||
RideComponentType vehicleType = ride->GetRideTypeDescriptor().NameConvention.vehicle;
|
||
StringId stringId = GetRideComponentName(vehicleType).count;
|
||
if (ride->max_trains > 1)
|
||
{
|
||
stringId = GetRideComponentName(vehicleType).count_plural;
|
||
}
|
||
ft.Add<StringId>(stringId);
|
||
ft.Add<uint16_t>(ride->max_trains);
|
||
return { fallback, ft };
|
||
}
|
||
case WIDX_VEHICLE_CARS_PER_TRAIN:
|
||
case WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE:
|
||
case WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE:
|
||
{
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return { STR_NONE, {} };
|
||
|
||
auto ft = Formatter();
|
||
ft.Increment(16);
|
||
ft.Add<uint16_t>(std::max(uint8_t(1), ride->MaxCarsPerTrain) - rideEntry->zero_cars);
|
||
|
||
StringId stringId = GetRideComponentName(RideComponentType::Car).singular;
|
||
if (ride->MaxCarsPerTrain - rideEntry->zero_cars > 1)
|
||
{
|
||
stringId = GetRideComponentName(RideComponentType::Car).plural;
|
||
}
|
||
ft.Add<StringId>(stringId);
|
||
return { fallback, ft };
|
||
}
|
||
}
|
||
return { fallback, {} };
|
||
}
|
||
|
||
void VehicleOnPrepareDraw()
|
||
{
|
||
StringId stringId;
|
||
int32_t carsPerTrain;
|
||
|
||
auto* newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
const auto* rideEntry = ride->GetRideEntry();
|
||
|
||
widgets[WIDX_TITLE].text = STR_ARG_20_STRINGID;
|
||
|
||
// Widget setup
|
||
carsPerTrain = ride->num_cars_per_train - rideEntry->zero_cars;
|
||
|
||
// Vehicle type
|
||
widgets[WIDX_VEHICLE_TYPE].text = rideEntry->naming.Name;
|
||
|
||
// Trains
|
||
if (rideEntry->cars_per_flat_ride > 1 || GetGameState().Cheats.DisableTrainLengthLimit)
|
||
{
|
||
widgets[WIDX_VEHICLE_TRAINS].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_VEHICLE_TRAINS_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_VEHICLE_TRAINS_DECREASE].type = WindowWidgetType::Button;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_TRAINS].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_TRAINS_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_TRAINS_DECREASE].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Cars per train
|
||
if (rideEntry->zero_cars + 1 < rideEntry->max_cars_in_train || GetGameState().Cheats.DisableTrainLengthLimit)
|
||
{
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE].type = WindowWidgetType::Button;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_ALLOW_REVERSED_TRAINS)
|
||
|| (GetGameState().Cheats.DisableTrainLengthLimit
|
||
&& !ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)))
|
||
{
|
||
widgets[WIDX_VEHICLE_REVERSED_TRAINS_CHECKBOX].type = WindowWidgetType::Checkbox;
|
||
if (ride->HasLifecycleFlag(RIDE_LIFECYCLE_REVERSED_TRAINS))
|
||
{
|
||
pressed_widgets |= (1uLL << WIDX_VEHICLE_REVERSED_TRAINS_CHECKBOX);
|
||
}
|
||
else
|
||
{
|
||
pressed_widgets &= ~(1uLL << WIDX_VEHICLE_REVERSED_TRAINS_CHECKBOX);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_REVERSED_TRAINS_CHECKBOX].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
auto ft = Formatter::Common();
|
||
ft.Increment(6);
|
||
ft.Add<uint16_t>(carsPerTrain);
|
||
RideComponentType vehicleType = ride->GetRideTypeDescriptor().NameConvention.vehicle;
|
||
stringId = GetRideComponentName(vehicleType).count;
|
||
if (ride->NumTrains > 1)
|
||
{
|
||
stringId = GetRideComponentName(vehicleType).count_plural;
|
||
}
|
||
ft.Add<StringId>(stringId);
|
||
ft.Add<uint16_t>(ride->NumTrains);
|
||
|
||
ft.Increment(8);
|
||
|
||
ride->FormatNameTo(ft);
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
|
||
if (abs(ride->num_cars_per_train - rideEntry->zero_cars) == 1)
|
||
{
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN].text = STR_1_CAR_PER_TRAIN;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_CARS_PER_TRAIN].text = STR_X_CARS_PER_TRAIN;
|
||
}
|
||
}
|
||
|
||
void VehicleOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
WindowDrawWidgets(*this, dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
auto screenCoords = windowPos + ScreenCoordsXY{ 8, 64 };
|
||
|
||
// Description
|
||
auto ft = Formatter();
|
||
ft.Add<StringId>(rideEntry->naming.Description);
|
||
screenCoords.y += DrawTextWrapped(dpi, screenCoords, 300, STR_BLACK_STRING, ft, { TextAlignment::LEFT });
|
||
screenCoords.y += 2;
|
||
|
||
// Capacity
|
||
ft = Formatter();
|
||
ft.Add<StringId>(rideEntry->capacity);
|
||
DrawTextBasic(dpi, screenCoords, STR_CAPACITY, ft);
|
||
|
||
// Excitement Factor
|
||
if (rideEntry->excitement_multiplier != 0)
|
||
{
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
ft = Formatter();
|
||
ft.Add<int16_t>(abs(rideEntry->excitement_multiplier));
|
||
StringId stringId = rideEntry->excitement_multiplier > 0 ? STR_EXCITEMENT_FACTOR
|
||
: STR_EXCITEMENT_FACTOR_NEGATIVE;
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
}
|
||
|
||
// Intensity Factor
|
||
if (rideEntry->intensity_multiplier != 0)
|
||
{
|
||
int32_t lineHeight = FontGetLineHeight(FontStyle::Medium);
|
||
if (lineHeight != 10)
|
||
screenCoords.x += 150;
|
||
else
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
ft = Formatter();
|
||
ft.Add<int16_t>(abs(rideEntry->intensity_multiplier));
|
||
StringId stringId = rideEntry->intensity_multiplier > 0 ? STR_INTENSITY_FACTOR : STR_INTENSITY_FACTOR_NEGATIVE;
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
|
||
if (lineHeight != 10)
|
||
screenCoords.x -= 150;
|
||
}
|
||
|
||
// Nausea Factor
|
||
if (rideEntry->nausea_multiplier != 0)
|
||
{
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
ft = Formatter();
|
||
ft.Add<int16_t>(abs(rideEntry->nausea_multiplier));
|
||
StringId stringId = rideEntry->nausea_multiplier > 0 ? STR_NAUSEA_FACTOR : STR_NAUSEA_FACTOR_NEGATIVE;
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
}
|
||
}
|
||
|
||
struct VehicleDrawInfo
|
||
{
|
||
int16_t x;
|
||
int16_t y;
|
||
ImageId imageId;
|
||
};
|
||
|
||
void VehicleOnScrollDraw(DrawPixelInfo& dpi, int32_t scrollIndex)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
const auto* rideEntry = ride->GetRideEntry();
|
||
|
||
// Background
|
||
GfxFillRect(dpi, { { dpi.x, dpi.y }, { dpi.x + dpi.width, dpi.y + dpi.height } }, PALETTE_INDEX_12);
|
||
|
||
Widget* widget = &widgets[WIDX_VEHICLE_TRAINS_PREVIEW];
|
||
int32_t startX = std::max(2, (widget->width() - ((ride->NumTrains - 1) * 36)) / 2 - 25);
|
||
int32_t startY = widget->height() - 4;
|
||
|
||
bool isReversed = ride->HasLifecycleFlag(RIDE_LIFECYCLE_REVERSED_TRAINS);
|
||
int32_t carIndex = (isReversed) ? ride->num_cars_per_train - 1 : 0;
|
||
|
||
const auto& firstCarEntry = rideEntry->Cars[RideEntryGetVehicleAtPosition(
|
||
ride->subtype, ride->num_cars_per_train, carIndex)];
|
||
startY += firstCarEntry.tab_height;
|
||
|
||
// For each train
|
||
for (int32_t i = 0; i < ride->NumTrains; i++)
|
||
{
|
||
VehicleDrawInfo trainCarImages[OpenRCT2::Limits::MaxCarsPerTrain];
|
||
VehicleDrawInfo* nextSpriteToDraw = trainCarImages;
|
||
int32_t x = startX;
|
||
int32_t y = startY;
|
||
|
||
// For each car in train
|
||
static_assert(std::numeric_limits<decltype(ride->num_cars_per_train)>::max() <= std::size(trainCarImages));
|
||
for (int32_t j = 0; j < ride->num_cars_per_train; j++)
|
||
{
|
||
carIndex = (isReversed) ? (ride->num_cars_per_train - 1) - j : j;
|
||
|
||
const auto& carEntry = rideEntry->Cars[RideEntryGetVehicleAtPosition(
|
||
ride->subtype, ride->num_cars_per_train, carIndex)];
|
||
x += carEntry.spacing / 17432;
|
||
y -= (carEntry.spacing / 2) / 17432;
|
||
|
||
// Get colour of vehicle
|
||
int32_t vehicleColourIndex = 0;
|
||
switch (ride->colour_scheme_type & 3)
|
||
{
|
||
case VEHICLE_COLOUR_SCHEME_SAME:
|
||
vehicleColourIndex = 0;
|
||
break;
|
||
case VEHICLE_COLOUR_SCHEME_PER_TRAIN:
|
||
vehicleColourIndex = i;
|
||
break;
|
||
case VEHICLE_COLOUR_SCHEME_PER_VEHICLE:
|
||
vehicleColourIndex = carIndex;
|
||
break;
|
||
}
|
||
VehicleColour vehicleColour = RideGetVehicleColour(*ride, vehicleColourIndex);
|
||
|
||
ImageIndex imageIndex = carEntry.SpriteByYaw(
|
||
OpenRCT2::Entity::Yaw::BaseRotation / 2, SpriteGroupType::SlopeFlat);
|
||
if (isReversed)
|
||
{
|
||
auto baseRotation = carEntry.NumRotationSprites(SpriteGroupType::SlopeFlat);
|
||
imageIndex = carEntry.SpriteByYaw(
|
||
(imageIndex + (baseRotation / 2)) & (baseRotation - 1), SpriteGroupType::SlopeFlat);
|
||
}
|
||
|
||
imageIndex &= carEntry.TabRotationMask;
|
||
imageIndex *= carEntry.base_num_frames;
|
||
imageIndex += carEntry.base_image_id;
|
||
|
||
auto imageId = ImageId(imageIndex, vehicleColour.Body, vehicleColour.Trim, vehicleColour.Tertiary);
|
||
|
||
nextSpriteToDraw->x = x;
|
||
nextSpriteToDraw->y = y;
|
||
nextSpriteToDraw->imageId = imageId;
|
||
nextSpriteToDraw++;
|
||
|
||
x += carEntry.spacing / 17432;
|
||
y -= (carEntry.spacing / 2) / 17432;
|
||
}
|
||
|
||
if (ride->type == RIDE_TYPE_REVERSER_ROLLER_COASTER)
|
||
{
|
||
VehicleDrawInfo tmp = *(nextSpriteToDraw - 1);
|
||
*(nextSpriteToDraw - 1) = *(nextSpriteToDraw - 2);
|
||
*(nextSpriteToDraw - 2) = tmp;
|
||
}
|
||
|
||
VehicleDrawInfo* current = nextSpriteToDraw;
|
||
while (--current >= trainCarImages)
|
||
GfxDrawSprite(dpi, current->imageId, { current->x, current->y });
|
||
|
||
startX += 36;
|
||
}
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Operating
|
||
|
||
void ModeTweakIncrease()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings;
|
||
uint8_t maxValue = operatingSettings.MaxValue;
|
||
uint8_t minValue = GetGameState().Cheats.UnlockOperatingLimits ? 0 : operatingSettings.MinValue;
|
||
|
||
if (GetGameState().Cheats.UnlockOperatingLimits)
|
||
{
|
||
maxValue = OpenRCT2::Limits::CheatsMaxOperatingLimit;
|
||
}
|
||
|
||
uint8_t increment = ride->mode == RideMode::Dodgems ? 10 : 1;
|
||
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::Operation, std::clamp<int16_t>(ride->operation_option + increment, minValue, maxValue));
|
||
}
|
||
|
||
void ModeTweakDecrease()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings;
|
||
uint8_t maxValue = operatingSettings.MaxValue;
|
||
uint8_t minValue = GetGameState().Cheats.UnlockOperatingLimits ? 0 : operatingSettings.MinValue;
|
||
if (GetGameState().Cheats.UnlockOperatingLimits)
|
||
{
|
||
maxValue = OpenRCT2::Limits::CheatsMaxOperatingLimit;
|
||
}
|
||
|
||
uint8_t decrement = ride->mode == RideMode::Dodgems ? 10 : 1;
|
||
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::Operation, std::clamp<int16_t>(ride->operation_option - decrement, minValue, maxValue));
|
||
}
|
||
|
||
void ModeDropdown(Widget* widget)
|
||
{
|
||
Widget* dropdownWidget;
|
||
|
||
dropdownWidget = widget - 1;
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto availableModes = ride->GetAvailableModes();
|
||
|
||
// Create dropdown list
|
||
auto numAvailableModes = 0;
|
||
auto checkedIndex = -1;
|
||
for (auto i = 0; i < static_cast<uint8_t>(RideMode::Count); i++)
|
||
{
|
||
if (availableModes & (1uLL << i))
|
||
{
|
||
gDropdownItems[numAvailableModes].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[numAvailableModes].Args = RideModeNames[i];
|
||
|
||
if (ride->mode == static_cast<RideMode>(i))
|
||
checkedIndex = numAvailableModes;
|
||
|
||
numAvailableModes++;
|
||
}
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, numAvailableModes, widget->right - dropdownWidget->left);
|
||
|
||
if (checkedIndex != -1)
|
||
{
|
||
Dropdown::SetChecked(checkedIndex, true);
|
||
}
|
||
}
|
||
|
||
void LoadDropdown(Widget* widget)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto dropdownWidget = widget - 1;
|
||
for (auto i = 0; i < 5; i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = VehicleLoadNames[i];
|
||
}
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, 5, widget->right - dropdownWidget->left);
|
||
|
||
Dropdown::SetChecked(ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD_MASK, true);
|
||
}
|
||
|
||
void OperatingOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_LOAD_CHECKBOX:
|
||
SetOperatingSetting(rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_LOAD);
|
||
break;
|
||
case WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX:
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_LEAVE_WHEN_ANOTHER_ARRIVES);
|
||
break;
|
||
case WIDX_MINIMUM_LENGTH_CHECKBOX:
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH);
|
||
break;
|
||
case WIDX_MAXIMUM_LENGTH_CHECKBOX:
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_MAXIMUM_LENGTH);
|
||
break;
|
||
case WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX:
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void OperatingResize()
|
||
{
|
||
WindowSetResize(*this, 316, 186, 316, 186);
|
||
}
|
||
|
||
void OperatingOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
uint8_t upperBound, lowerBound;
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_MODE_TWEAK:
|
||
OperatingTweakTextInput(*ride);
|
||
break;
|
||
case WIDX_MODE_TWEAK_INCREASE:
|
||
ModeTweakIncrease();
|
||
break;
|
||
case WIDX_MODE_TWEAK_DECREASE:
|
||
ModeTweakDecrease();
|
||
break;
|
||
case WIDX_LIFT_HILL_SPEED_INCREASE:
|
||
upperBound = GetGameState().Cheats.UnlockOperatingLimits
|
||
? OpenRCT2::Limits::CheatsMaxOperatingLimit
|
||
: ride->GetRideTypeDescriptor().LiftData.maximum_speed;
|
||
lowerBound = GetGameState().Cheats.UnlockOperatingLimits
|
||
? 0
|
||
: ride->GetRideTypeDescriptor().LiftData.minimum_speed;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::LiftHillSpeed,
|
||
std::clamp<int16_t>(ride->lift_hill_speed + 1, lowerBound, upperBound));
|
||
break;
|
||
case WIDX_LIFT_HILL_SPEED_DECREASE:
|
||
upperBound = GetGameState().Cheats.UnlockOperatingLimits
|
||
? OpenRCT2::Limits::CheatsMaxOperatingLimit
|
||
: ride->GetRideTypeDescriptor().LiftData.maximum_speed;
|
||
lowerBound = GetGameState().Cheats.UnlockOperatingLimits
|
||
? 0
|
||
: ride->GetRideTypeDescriptor().LiftData.minimum_speed;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::LiftHillSpeed,
|
||
std::clamp<int16_t>(ride->lift_hill_speed - 1, lowerBound, upperBound));
|
||
break;
|
||
case WIDX_MINIMUM_LENGTH:
|
||
OperatingLengthWindow(WIDX_MINIMUM_LENGTH);
|
||
break;
|
||
case WIDX_MAXIMUM_LENGTH:
|
||
OperatingLengthWindow(WIDX_MAXIMUM_LENGTH);
|
||
break;
|
||
case WIDX_MINIMUM_LENGTH_INCREASE:
|
||
upperBound = OpenRCT2::Limits::MaxWaitingTime;
|
||
lowerBound = 0;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::MinWaitingTime,
|
||
std::clamp<int16_t>(ride->min_waiting_time + 1, lowerBound, upperBound));
|
||
break;
|
||
case WIDX_MINIMUM_LENGTH_DECREASE:
|
||
upperBound = OpenRCT2::Limits::MaxWaitingTime;
|
||
lowerBound = 0;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::MinWaitingTime,
|
||
std::clamp<int16_t>(ride->min_waiting_time - 1, lowerBound, upperBound));
|
||
break;
|
||
case WIDX_MAXIMUM_LENGTH_INCREASE:
|
||
upperBound = OpenRCT2::Limits::MaxWaitingTime;
|
||
lowerBound = 0;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::MaxWaitingTime,
|
||
std::clamp<int16_t>(ride->max_waiting_time + 1, lowerBound, upperBound));
|
||
break;
|
||
case WIDX_MAXIMUM_LENGTH_DECREASE:
|
||
upperBound = OpenRCT2::Limits::MaxWaitingTime;
|
||
lowerBound = 0;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::MaxWaitingTime,
|
||
std::clamp<int16_t>(ride->max_waiting_time - 1, lowerBound, upperBound));
|
||
break;
|
||
case WIDX_MODE_DROPDOWN:
|
||
ModeDropdown(&widgets[widgetIndex]);
|
||
break;
|
||
case WIDX_LOAD_DROPDOWN:
|
||
LoadDropdown(&widgets[widgetIndex]);
|
||
break;
|
||
case WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE:
|
||
upperBound = GetGameState().Cheats.UnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit
|
||
: OpenRCT2::Limits::MaxCircuitsPerRide;
|
||
lowerBound = 1;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::NumCircuits,
|
||
std::clamp<int16_t>(ride->num_circuits + 1, lowerBound, upperBound));
|
||
break;
|
||
case WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE:
|
||
upperBound = GetGameState().Cheats.UnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit
|
||
: OpenRCT2::Limits::MaxCircuitsPerRide;
|
||
lowerBound = 1;
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::NumCircuits,
|
||
std::clamp<int16_t>(ride->num_circuits - 1, lowerBound, upperBound));
|
||
break;
|
||
}
|
||
}
|
||
|
||
void OperatingLengthWindow(WidgetIndex widgetIndex)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
uint8_t upperBound = OpenRCT2::Limits::MaxWaitingTime;
|
||
uint8_t lowerBound = 0;
|
||
Formatter ft;
|
||
ft.Add<int16_t>(lowerBound);
|
||
ft.Add<int16_t>(upperBound);
|
||
auto title = (widgetIndex == WIDX_MINIMUM_LENGTH) ? STR_MINIMUM_WAITING_TIME : STR_MAXIMUM_WAITING_TIME;
|
||
auto currentValue = (widgetIndex == WIDX_MINIMUM_LENGTH) ? ride->min_waiting_time : ride->max_waiting_time;
|
||
char buffer[5]{};
|
||
snprintf(buffer, std::size(buffer), "%u", currentValue);
|
||
WindowTextInputRawOpen(this, widgetIndex, title, STR_ENTER_VALUE, ft, buffer, 4);
|
||
}
|
||
|
||
void OperatingTweakTextInput(const Ride& ride)
|
||
{
|
||
switch (ride.mode)
|
||
{
|
||
case RideMode::PoweredLaunchPasstrough:
|
||
case RideMode::PoweredLaunch:
|
||
case RideMode::UpwardLaunch:
|
||
case RideMode::PoweredLaunchBlockSectioned:
|
||
case RideMode::StationToStation:
|
||
case RideMode::Dodgems:
|
||
return;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
const auto& operatingSettings = ride.GetRideTypeDescriptor().OperatingSettings;
|
||
int16_t maxValue = GetGameState().Cheats.UnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit
|
||
: operatingSettings.MaxValue;
|
||
int16_t minValue = GetGameState().Cheats.UnlockOperatingLimits ? 0 : operatingSettings.MinValue;
|
||
|
||
const auto& title = widgets[WIDX_MODE_TWEAK_LABEL].text;
|
||
Formatter ft;
|
||
ft.Add<int16_t>(minValue * operatingSettings.OperatingSettingMultiplier);
|
||
ft.Add<int16_t>(maxValue * operatingSettings.OperatingSettingMultiplier);
|
||
|
||
uint16_t currentValue = static_cast<uint16_t>(ride.operation_option) * operatingSettings.OperatingSettingMultiplier;
|
||
char buffer[6]{};
|
||
snprintf(buffer, std::size(buffer), "%u", currentValue);
|
||
|
||
WindowTextInputRawOpen(this, WIDX_MODE_TWEAK, title, STR_ENTER_VALUE, ft, buffer, 4);
|
||
}
|
||
|
||
void OperatingOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
|
||
{
|
||
if (dropdownIndex == -1)
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_MODE_DROPDOWN:
|
||
{
|
||
RideMode rideMode = RideMode::NullMode;
|
||
auto availableModes = ride->GetAvailableModes();
|
||
auto modeInDropdownIndex = -1;
|
||
for (RideMode rideModeIndex = RideMode::Normal; rideModeIndex < RideMode::Count; rideModeIndex++)
|
||
{
|
||
if (availableModes & EnumToFlag(rideModeIndex))
|
||
{
|
||
modeInDropdownIndex++;
|
||
if (modeInDropdownIndex == dropdownIndex)
|
||
{
|
||
rideMode = rideModeIndex;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (rideMode != RideMode::NullMode)
|
||
SetOperatingSetting(rideId, RideSetSetting::Mode, static_cast<uint8_t>(rideMode));
|
||
break;
|
||
}
|
||
case WIDX_LOAD_DROPDOWN:
|
||
SetOperatingSetting(
|
||
rideId, RideSetSetting::Departure,
|
||
(ride->depart_flags & ~RIDE_DEPART_WAIT_FOR_LOAD_MASK) | dropdownIndex);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void OperatingUpdate()
|
||
{
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_3);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_OPERATING)
|
||
{
|
||
ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_OPERATING;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void OperatingOnTextInput(WidgetIndex widgetIndex, std::string_view text)
|
||
{
|
||
if (text.empty())
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
if (widgetIndex == WIDX_MODE_TWEAK)
|
||
{
|
||
const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings;
|
||
uint32_t maxValue = GetGameState().Cheats.UnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit
|
||
: operatingSettings.MaxValue;
|
||
uint32_t minValue = GetGameState().Cheats.UnlockOperatingLimits ? 0 : operatingSettings.MinValue;
|
||
auto multiplier = ride->GetRideTypeDescriptor().OperatingSettings.OperatingSettingMultiplier;
|
||
|
||
try
|
||
{
|
||
uint32_t origSize = std::stol(std::string(text)) / multiplier;
|
||
uint8_t size = static_cast<uint8_t>(std::clamp(origSize, minValue, maxValue));
|
||
SetOperatingSetting(ride->id, RideSetSetting::Operation, size);
|
||
}
|
||
catch (const std::logic_error&)
|
||
{
|
||
// std::stol can throw std::out_of_range or std::invalid_argument
|
||
}
|
||
}
|
||
else if (widgetIndex == WIDX_MINIMUM_LENGTH || widgetIndex == WIDX_MAXIMUM_LENGTH)
|
||
{
|
||
try
|
||
{
|
||
auto rideSetSetting = widgetIndex == WIDX_MINIMUM_LENGTH ? RideSetSetting::MinWaitingTime
|
||
: RideSetSetting::MaxWaitingTime;
|
||
|
||
uint16_t upperBound = OpenRCT2::Limits::MaxWaitingTime;
|
||
uint16_t lowerBound = 0;
|
||
uint16_t size = std::stol(std::string(text));
|
||
size = std::clamp(size, lowerBound, upperBound);
|
||
SetOperatingSetting(ride->id, rideSetSetting, size);
|
||
}
|
||
catch (const std::logic_error&)
|
||
{
|
||
// std::stol can throw std::out_of_range or std::invalid_argument
|
||
}
|
||
}
|
||
}
|
||
|
||
void OperatingOnPrepareDraw()
|
||
{
|
||
StringId format, caption, tooltip;
|
||
|
||
auto* newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto ft = Formatter::Common();
|
||
ride->FormatNameTo(ft);
|
||
|
||
// Widget setup
|
||
pressed_widgets &= ~(
|
||
(1uLL << WIDX_LOAD_CHECKBOX) | (1uLL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX)
|
||
| (1uLL << WIDX_MINIMUM_LENGTH_CHECKBOX) | (1uLL << WIDX_MAXIMUM_LENGTH_CHECKBOX)
|
||
| (1uLL << WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX));
|
||
|
||
// Sometimes, only one of the alternatives support lift hill pieces. Make sure to check both.
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
bool hasAlternativeType = rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE);
|
||
if (rtd.TrackPaintFunctions.Regular.SupportsTrackPiece(TRACK_LIFT_HILL)
|
||
|| (hasAlternativeType && rtd.InvertedTrackPaintFunctions.SupportsTrackPiece(TRACK_LIFT_HILL)))
|
||
{
|
||
widgets[WIDX_LIFT_HILL_SPEED_LABEL].type = WindowWidgetType::Label;
|
||
widgets[WIDX_LIFT_HILL_SPEED].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_LIFT_HILL_SPEED_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_LIFT_HILL_SPEED_DECREASE].type = WindowWidgetType::Button;
|
||
ft.Rewind();
|
||
ft.Increment(20);
|
||
ft.Add<uint16_t>(ride->lift_hill_speed);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_LIFT_HILL_SPEED_LABEL].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_LIFT_HILL_SPEED].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_LIFT_HILL_SPEED_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_LIFT_HILL_SPEED_DECREASE].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Number of circuits
|
||
if (ride->CanHaveMultipleCircuits())
|
||
{
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL].type = WindowWidgetType::Label;
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE].type = WindowWidgetType::Button;
|
||
ft.Rewind();
|
||
ft.Increment(22);
|
||
ft.Add<uint16_t>(ride->num_circuits);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Leave if another vehicle arrives at station
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LEAVE_WHEN_ANOTHER_VEHICLE_ARRIVES_AT_STATION)
|
||
&& ride->NumTrains > 1 && !ride->IsBlockSectioned())
|
||
{
|
||
widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].type = WindowWidgetType::Checkbox;
|
||
widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].tooltip = STR_LEAVE_IF_ANOTHER_VEHICLE_ARRIVES_TIP;
|
||
widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].text = ride->GetRideTypeDescriptor().NameConvention.vehicle
|
||
== RideComponentType::Boat
|
||
? STR_LEAVE_IF_ANOTHER_BOAT_ARRIVES
|
||
: STR_LEAVE_IF_ANOTHER_TRAIN_ARRIVES;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Synchronise with adjacent stations
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_CAN_SYNCHRONISE_ADJACENT_STATIONS))
|
||
{
|
||
widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].type = WindowWidgetType::Checkbox;
|
||
widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].text = STR_SYNCHRONISE_WITH_ADJACENT_STATIONS;
|
||
widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].tooltip = STR_SYNCHRONISE_WITH_ADJACENT_STATIONS_TIP;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Mode
|
||
widgets[WIDX_MODE].text = RideModeNames[EnumValue(ride->mode)];
|
||
|
||
// Waiting
|
||
widgets[WIDX_LOAD].text = VehicleLoadNames[(ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD_MASK)];
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LOAD_OPTIONS))
|
||
{
|
||
widgets[WIDX_LOAD_CHECKBOX].type = WindowWidgetType::Checkbox;
|
||
widgets[WIDX_LOAD].type = WindowWidgetType::DropdownMenu;
|
||
widgets[WIDX_LOAD_DROPDOWN].type = WindowWidgetType::Button;
|
||
|
||
widgets[WIDX_MINIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Checkbox;
|
||
widgets[WIDX_MINIMUM_LENGTH].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_MINIMUM_LENGTH_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_MINIMUM_LENGTH_DECREASE].type = WindowWidgetType::Button;
|
||
|
||
widgets[WIDX_MAXIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Checkbox;
|
||
widgets[WIDX_MAXIMUM_LENGTH].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_MAXIMUM_LENGTH_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_MAXIMUM_LENGTH_DECREASE].type = WindowWidgetType::Button;
|
||
|
||
ft.Rewind();
|
||
ft.Increment(10);
|
||
ft.Add<StringId>(STR_FORMAT_SECONDS);
|
||
ft.Add<uint16_t>(ride->min_waiting_time);
|
||
ft.Add<StringId>(STR_FORMAT_SECONDS);
|
||
ft.Add<uint16_t>(ride->max_waiting_time);
|
||
|
||
if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD)
|
||
pressed_widgets |= (1uLL << WIDX_LOAD_CHECKBOX);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_LOAD_CHECKBOX].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_LOAD].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_LOAD_DROPDOWN].type = WindowWidgetType::Empty;
|
||
|
||
widgets[WIDX_MINIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MINIMUM_LENGTH].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MINIMUM_LENGTH_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MINIMUM_LENGTH_DECREASE].type = WindowWidgetType::Empty;
|
||
|
||
widgets[WIDX_MAXIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MAXIMUM_LENGTH].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MAXIMUM_LENGTH_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MAXIMUM_LENGTH_DECREASE].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
if (ride->depart_flags & RIDE_DEPART_LEAVE_WHEN_ANOTHER_ARRIVES)
|
||
pressed_widgets |= (1uLL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX);
|
||
if (ride->depart_flags & RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS)
|
||
pressed_widgets |= (1uLL << WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX);
|
||
if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH)
|
||
pressed_widgets |= (1uLL << WIDX_MINIMUM_LENGTH_CHECKBOX);
|
||
if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_MAXIMUM_LENGTH)
|
||
pressed_widgets |= (1uLL << WIDX_MAXIMUM_LENGTH_CHECKBOX);
|
||
|
||
// Mode specific functionality
|
||
auto multiplier = ride->GetRideTypeDescriptor().OperatingSettings.OperatingSettingMultiplier;
|
||
ft.Rewind();
|
||
ft.Increment(18);
|
||
ft.Add<uint16_t>(static_cast<uint16_t>(ride->operation_option) * multiplier);
|
||
switch (ride->mode)
|
||
{
|
||
case RideMode::PoweredLaunchPasstrough:
|
||
case RideMode::PoweredLaunch:
|
||
case RideMode::UpwardLaunch:
|
||
case RideMode::PoweredLaunchBlockSectioned:
|
||
ft.Rewind();
|
||
ft.Increment(18);
|
||
ft.Add<uint16_t>((ride->launch_speed * 9) / 4);
|
||
format = STR_RIDE_MODE_SPEED_VALUE;
|
||
caption = STR_LAUNCH_SPEED;
|
||
tooltip = STR_LAUNCH_SPEED_TIP;
|
||
break;
|
||
case RideMode::StationToStation:
|
||
ft.Rewind();
|
||
ft.Increment(18);
|
||
ft.Add<uint16_t>((ride->speed * 9) / 4);
|
||
format = STR_RIDE_MODE_SPEED_VALUE;
|
||
caption = STR_SPEED;
|
||
tooltip = STR_SPEED_TIP;
|
||
break;
|
||
case RideMode::Race:
|
||
ft.Rewind();
|
||
ft.Increment(18);
|
||
ft.Add<uint16_t>(ride->NumLaps);
|
||
format = STR_NUMBER_OF_LAPS_VALUE;
|
||
caption = STR_NUMBER_OF_LAPS;
|
||
tooltip = STR_NUMBER_OF_LAPS_TIP;
|
||
break;
|
||
case RideMode::Dodgems:
|
||
format = STR_RIDE_MODE_TIME_LIMIT_VALUE;
|
||
caption = STR_TIME_LIMIT;
|
||
tooltip = STR_TIME_LIMIT_TIP;
|
||
break;
|
||
case RideMode::Swing:
|
||
format = STR_RIDE_MODE_NUMBER_OF_SWINGS_VALUE;
|
||
caption = STR_NUMBER_OF_SWINGS;
|
||
tooltip = STR_NUMBER_OF_SWINGS_TIP;
|
||
break;
|
||
case RideMode::Rotation:
|
||
case RideMode::ForwardRotation:
|
||
case RideMode::BackwardRotation:
|
||
format = STR_NUMBER_OF_ROTATIONS_VALUE;
|
||
caption = STR_NUMBER_OF_ROTATIONS;
|
||
tooltip = STR_NUMBER_OF_ROTATIONS_TIP;
|
||
break;
|
||
default:
|
||
format = STR_MAX_PEOPLE_ON_RIDE_VALUE;
|
||
caption = STR_MAX_PEOPLE_ON_RIDE;
|
||
tooltip = STR_MAX_PEOPLE_ON_RIDE_TIP;
|
||
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
|
||
format = 0;
|
||
break;
|
||
}
|
||
|
||
if (format != 0)
|
||
{
|
||
widgets[WIDX_MODE_TWEAK_LABEL].type = WindowWidgetType::Label;
|
||
widgets[WIDX_MODE_TWEAK_LABEL].text = caption;
|
||
widgets[WIDX_MODE_TWEAK_LABEL].tooltip = tooltip;
|
||
widgets[WIDX_MODE_TWEAK].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_MODE_TWEAK].text = format;
|
||
widgets[WIDX_MODE_TWEAK_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_MODE_TWEAK_DECREASE].type = WindowWidgetType::Button;
|
||
pressed_widgets &= ~(1uLL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_MODE_TWEAK_LABEL].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MODE_TWEAK].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MODE_TWEAK_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MODE_TWEAK_DECREASE].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
}
|
||
|
||
void OperatingOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
// Horizontal rule between mode settings and depart settings
|
||
GfxFillRectInset(
|
||
dpi,
|
||
{ windowPos + ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, 103 },
|
||
windowPos + ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].right - 5, 104 } },
|
||
colours[1], INSET_RECT_FLAG_BORDER_INSET);
|
||
|
||
// Number of block sections
|
||
if (ride->IsBlockSectioned())
|
||
{
|
||
auto ft = Formatter();
|
||
ft.Add<uint16_t>(ride->num_block_brakes + ride->num_stations);
|
||
DrawTextBasic(
|
||
dpi, windowPos + ScreenCoordsXY{ 21, ride->mode == RideMode::PoweredLaunchBlockSectioned ? 89 : 61 },
|
||
STR_BLOCK_SECTIONS, ft, COLOUR_BLACK);
|
||
}
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Maintenance
|
||
|
||
void LocateMechanic()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
// First check if there is a mechanic assigned
|
||
Peep* mechanic = RideGetAssignedMechanic(*ride);
|
||
|
||
// Otherwise find the closest mechanic
|
||
if (mechanic == nullptr)
|
||
mechanic = RideFindClosestMechanic(*ride, 1);
|
||
|
||
if (mechanic == nullptr)
|
||
ContextShowError(STR_UNABLE_TO_LOCATE_MECHANIC, STR_NONE, {});
|
||
else
|
||
{
|
||
auto intent = Intent(WindowClass::Peep);
|
||
intent.PutExtra(INTENT_EXTRA_PEEP, mechanic);
|
||
ContextOpenIntent(&intent);
|
||
}
|
||
}
|
||
|
||
void MaintenanceDrawBar(DrawPixelInfo& dpi, const ScreenCoordsXY& coords, int32_t value, int32_t colour) const
|
||
{
|
||
GfxFillRectInset(dpi, { coords, coords + ScreenCoordsXY{ 149, 8 } }, colours[1], INSET_RECT_F_30);
|
||
if (colour & kBarBlink)
|
||
{
|
||
colour &= ~kBarBlink;
|
||
if (GameIsNotPaused() && (gCurrentRealTimeTicks & 8))
|
||
return;
|
||
}
|
||
|
||
value = ((186 * ((value * 2) & 0xFF)) >> 8) & 0xFF;
|
||
if (value > 2)
|
||
{
|
||
GfxFillRectInset(dpi, { coords + ScreenCoordsXY{ 2, 1 }, coords + ScreenCoordsXY{ value + 1, 7 } }, colour, 0);
|
||
}
|
||
}
|
||
|
||
void MaintenanceOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_LOCATE_MECHANIC:
|
||
LocateMechanic();
|
||
break;
|
||
case WIDX_REFURBISH_RIDE:
|
||
ContextOpenDetailWindow(WD_REFURBISH_RIDE, number);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MaintenanceResize()
|
||
{
|
||
WindowSetResize(*this, 316, 135, 316, 135);
|
||
}
|
||
|
||
void MaintenanceOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
Widget* dropdownWidget = &widgets[widgetIndex];
|
||
int32_t j, numItems;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_INSPECTION_INTERVAL_DROPDOWN:
|
||
dropdownWidget--;
|
||
for (int32_t i = 0; i < 7; i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = RideInspectionIntervalNames[i];
|
||
}
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, 7, widgets[widgetIndex].right - dropdownWidget->left);
|
||
|
||
Dropdown::SetChecked(ride->inspection_interval, true);
|
||
break;
|
||
|
||
case WIDX_FORCE_BREAKDOWN:
|
||
numItems = 1;
|
||
for (j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++)
|
||
{
|
||
if (rideEntry->ride_type[j] != RIDE_TYPE_NULL)
|
||
break;
|
||
}
|
||
gDropdownItems[0].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[0].Args = STR_DEBUG_FIX_RIDE;
|
||
for (int32_t i = 0; i < 8; i++)
|
||
{
|
||
assert(j < static_cast<int32_t>(std::size(rideEntry->ride_type)));
|
||
if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns & static_cast<uint8_t>(1 << i))
|
||
{
|
||
if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned())
|
||
{
|
||
if (ride->NumTrains != 1)
|
||
continue;
|
||
}
|
||
gDropdownItems[numItems].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[numItems].Args = RideBreakdownReasonNames[i];
|
||
numItems++;
|
||
}
|
||
}
|
||
if (numItems == 1)
|
||
{
|
||
ContextShowError(STR_DEBUG_NO_BREAKDOWNS_AVAILABLE, STR_NONE, {});
|
||
}
|
||
else
|
||
{
|
||
WindowDropdownShowText(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top },
|
||
dropdownWidget->height() + 1, colours[1], Dropdown::Flag::StayOpen, numItems);
|
||
|
||
numItems = 1;
|
||
int32_t breakdownReason = ride->breakdown_reason_pending;
|
||
if (breakdownReason != BREAKDOWN_NONE && (ride->lifecycle_flags & RIDE_LIFECYCLE_BREAKDOWN_PENDING))
|
||
{
|
||
for (int32_t i = 0; i < 8; i++)
|
||
{
|
||
if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns
|
||
& static_cast<uint8_t>(1 << i))
|
||
{
|
||
if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned())
|
||
{
|
||
if (ride->NumTrains != 1)
|
||
continue;
|
||
}
|
||
if (i == breakdownReason)
|
||
{
|
||
Dropdown::SetChecked(numItems, true);
|
||
break;
|
||
}
|
||
gDropdownItems[numItems].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[numItems].Args = RideBreakdownReasonNames[i];
|
||
numItems++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ((ride->lifecycle_flags & RIDE_LIFECYCLE_BREAKDOWN_PENDING) == 0)
|
||
{
|
||
Dropdown::SetDisabled(0, true);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MaintenanceOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
|
||
{
|
||
if (dropdownIndex == -1)
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_INSPECTION_INTERVAL_DROPDOWN:
|
||
SetOperatingSetting(rideId, RideSetSetting::InspectionInterval, dropdownIndex);
|
||
break;
|
||
|
||
case WIDX_FORCE_BREAKDOWN:
|
||
if (dropdownIndex == 0)
|
||
{
|
||
Vehicle* vehicle;
|
||
switch (ride->breakdown_reason_pending)
|
||
{
|
||
case BREAKDOWN_SAFETY_CUT_OUT:
|
||
if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
|
||
break;
|
||
for (int32_t i = 0; i < ride->NumTrains; ++i)
|
||
{
|
||
for (vehicle = GetEntity<Vehicle>(ride->vehicles[i]); vehicle != nullptr;
|
||
vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
|
||
{
|
||
vehicle->ClearFlag(
|
||
VehicleFlags::CarIsBroken | VehicleFlags::StoppedOnLift
|
||
| VehicleFlags::TrainIsBroken);
|
||
}
|
||
}
|
||
break;
|
||
case BREAKDOWN_RESTRAINTS_STUCK_CLOSED:
|
||
case BREAKDOWN_RESTRAINTS_STUCK_OPEN:
|
||
case BREAKDOWN_DOORS_STUCK_CLOSED:
|
||
case BREAKDOWN_DOORS_STUCK_OPEN:
|
||
vehicle = GetEntity<Vehicle>(ride->vehicles[ride->broken_vehicle]);
|
||
if (vehicle != nullptr)
|
||
{
|
||
vehicle->ClearFlag(VehicleFlags::CarIsBroken);
|
||
}
|
||
break;
|
||
case BREAKDOWN_VEHICLE_MALFUNCTION:
|
||
vehicle = GetEntity<Vehicle>(ride->vehicles[ride->broken_vehicle]);
|
||
if (vehicle != nullptr)
|
||
{
|
||
vehicle->ClearFlag(VehicleFlags::TrainIsBroken);
|
||
}
|
||
break;
|
||
}
|
||
ride->lifecycle_flags &= ~(RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN);
|
||
WindowInvalidateByNumber(WindowClass::Ride, number);
|
||
break;
|
||
}
|
||
if (ride->lifecycle_flags
|
||
& (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
|
||
{
|
||
ContextShowError(STR_DEBUG_CANT_FORCE_BREAKDOWN, STR_DEBUG_RIDE_ALREADY_BROKEN, {});
|
||
}
|
||
else if (ride->status == RideStatus::Closed)
|
||
{
|
||
ContextShowError(STR_DEBUG_CANT_FORCE_BREAKDOWN, STR_DEBUG_RIDE_IS_CLOSED, {});
|
||
}
|
||
else
|
||
{
|
||
int32_t j;
|
||
for (j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++)
|
||
{
|
||
if (rideEntry->ride_type[j] != RIDE_TYPE_NULL)
|
||
break;
|
||
}
|
||
int32_t i;
|
||
int32_t numItems = 1;
|
||
for (i = 0; i < BREAKDOWN_COUNT; i++)
|
||
{
|
||
assert(j < static_cast<int32_t>(std::size(rideEntry->ride_type)));
|
||
if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns
|
||
& static_cast<uint8_t>(1 << i))
|
||
{
|
||
if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned())
|
||
{
|
||
if (ride->NumTrains != 1)
|
||
continue;
|
||
}
|
||
if (numItems == dropdownIndex)
|
||
break;
|
||
numItems++;
|
||
}
|
||
}
|
||
RidePrepareBreakdown(*ride, i);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MaintenanceUpdate()
|
||
{
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_4);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAINTENANCE)
|
||
{
|
||
ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_MAINTENANCE;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void MaintenanceOnPrepareDraw()
|
||
{
|
||
auto newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto ft = Formatter::Common();
|
||
ride->FormatNameTo(ft);
|
||
|
||
widgets[WIDX_INSPECTION_INTERVAL].text = RideInspectionIntervalNames[ride->inspection_interval];
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
|
||
if (gConfigGeneral.DebuggingTools && NetworkGetMode() == NETWORK_MODE_NONE)
|
||
{
|
||
widgets[WIDX_FORCE_BREAKDOWN].type = WindowWidgetType::FlatBtn;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_FORCE_BREAKDOWN].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
if (ride->GetRideTypeDescriptor().AvailableBreakdowns == 0
|
||
|| !(ride->lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED))
|
||
{
|
||
disabled_widgets |= (1uLL << WIDX_REFURBISH_RIDE);
|
||
widgets[WIDX_REFURBISH_RIDE].tooltip = STR_CANT_REFURBISH_NOT_NEEDED;
|
||
}
|
||
else
|
||
{
|
||
disabled_widgets &= ~(1uLL << WIDX_REFURBISH_RIDE);
|
||
widgets[WIDX_REFURBISH_RIDE].tooltip = STR_REFURBISH_RIDE_TIP;
|
||
}
|
||
}
|
||
|
||
void MaintenanceOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
// Locate mechanic button image
|
||
Widget* widget = &widgets[WIDX_LOCATE_MECHANIC];
|
||
auto screenCoords = windowPos + ScreenCoordsXY{ widget->left, widget->top };
|
||
auto image = ImageId(SPR_MECHANIC, COLOUR_BLACK, GetGameState().StaffMechanicColour);
|
||
GfxDrawSprite(dpi, image, screenCoords);
|
||
|
||
// Inspection label
|
||
widget = &widgets[WIDX_INSPECTION_INTERVAL];
|
||
screenCoords = windowPos + ScreenCoordsXY{ 4, widget->top + 1 };
|
||
DrawTextBasic(dpi, screenCoords, STR_INSPECTION);
|
||
|
||
// Reliability
|
||
widget = &widgets[WIDX_PAGE_BACKGROUND];
|
||
screenCoords = windowPos + ScreenCoordsXY{ widget->left + 4, widget->top + 4 };
|
||
|
||
uint16_t reliability = ride->reliability_percentage;
|
||
auto ft = Formatter();
|
||
ft.Add<uint16_t>(reliability);
|
||
DrawTextBasic(dpi, screenCoords, STR_RELIABILITY_LABEL_1757, ft);
|
||
MaintenanceDrawBar(
|
||
dpi, screenCoords + ScreenCoordsXY{ 103, 0 }, std::max<int32_t>(10, reliability), COLOUR_BRIGHT_GREEN);
|
||
screenCoords.y += 11;
|
||
|
||
uint16_t downTime = ride->downtime;
|
||
ft = Formatter();
|
||
ft.Add<uint16_t>(downTime);
|
||
DrawTextBasic(dpi, screenCoords, STR_DOWN_TIME_LABEL_1889, ft);
|
||
MaintenanceDrawBar(dpi, screenCoords + ScreenCoordsXY{ 103, 0 }, downTime, COLOUR_BRIGHT_RED);
|
||
screenCoords.y += 26;
|
||
|
||
// Last inspection
|
||
StringId stringId;
|
||
if (ride->last_inspection <= 1)
|
||
stringId = STR_TIME_SINCE_LAST_INSPECTION_MINUTE;
|
||
else if (ride->last_inspection <= 240)
|
||
stringId = STR_TIME_SINCE_LAST_INSPECTION_MINUTES;
|
||
else
|
||
stringId = STR_TIME_SINCE_LAST_INSPECTION_MORE_THAN_4_HOURS;
|
||
|
||
ft = Formatter();
|
||
ft.Add<uint16_t>(ride->last_inspection);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += 12;
|
||
|
||
// Last / current breakdown
|
||
if (ride->breakdown_reason == BREAKDOWN_NONE)
|
||
return;
|
||
|
||
stringId = (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) ? STR_CURRENT_BREAKDOWN : STR_LAST_BREAKDOWN;
|
||
ft = Formatter();
|
||
ft.Add<StringId>(RideBreakdownReasonNames[ride->breakdown_reason]);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += 12;
|
||
|
||
// Mechanic status
|
||
if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
|
||
{
|
||
switch (ride->mechanic_status)
|
||
{
|
||
case RIDE_MECHANIC_STATUS_CALLING:
|
||
{
|
||
stringId = STR_NO_MECHANICS_ARE_HIRED_MESSAGE;
|
||
|
||
for (auto peep : EntityList<Staff>())
|
||
{
|
||
if (peep->IsMechanic())
|
||
{
|
||
stringId = STR_CALLING_MECHANIC;
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case RIDE_MECHANIC_STATUS_HEADING:
|
||
stringId = STR_MEHCANIC_IS_HEADING_FOR_THE_RIDE;
|
||
break;
|
||
case RIDE_MECHANIC_STATUS_FIXING:
|
||
case RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES:
|
||
stringId = STR_MEHCANIC_IS_FIXING_THE_RIDE;
|
||
break;
|
||
default:
|
||
stringId = STR_EMPTY;
|
||
break;
|
||
}
|
||
|
||
if (stringId != STR_EMPTY)
|
||
{
|
||
if (stringId == STR_CALLING_MECHANIC || stringId == STR_NO_MECHANICS_ARE_HIRED_MESSAGE)
|
||
{
|
||
DrawTextWrapped(dpi, screenCoords, 280, stringId, {}, { TextAlignment::LEFT });
|
||
}
|
||
else
|
||
{
|
||
auto staff = GetEntity<Staff>(ride->mechanic);
|
||
if (staff != nullptr && staff->IsMechanic())
|
||
{
|
||
ft = Formatter();
|
||
staff->FormatNameTo(ft);
|
||
DrawTextWrapped(dpi, screenCoords, 280, stringId, ft, { TextAlignment::LEFT });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Colour
|
||
|
||
int32_t HasTrackColour(const Ride& ride, int32_t trackColour)
|
||
{
|
||
// Get station flags (shops don't have them)
|
||
auto stationObjFlags = 0;
|
||
if (!ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY))
|
||
{
|
||
auto stationObj = ride.GetStationObject();
|
||
if (stationObj != nullptr)
|
||
{
|
||
stationObjFlags = stationObj->Flags;
|
||
}
|
||
}
|
||
|
||
switch (trackColour)
|
||
{
|
||
case 0:
|
||
return (stationObjFlags & STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR)
|
||
|| ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN);
|
||
case 1:
|
||
return (stationObjFlags & STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR)
|
||
|| ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL);
|
||
case 2:
|
||
return ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS);
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
void SetTrackColourScheme(const ScreenCoordsXY& screenPos)
|
||
{
|
||
auto newColourScheme = static_cast<uint8_t>(_rideColour);
|
||
auto info = GetMapCoordinatesFromPos(screenPos, EnumsToFlags(ViewportInteractionItem::Ride));
|
||
|
||
if (info.SpriteType != ViewportInteractionItem::Ride)
|
||
return;
|
||
if (info.Element->GetType() != TileElementType::Track)
|
||
return;
|
||
if (info.Element->AsTrack()->GetRideIndex() != rideId)
|
||
return;
|
||
if (info.Element->AsTrack()->GetColourScheme() == newColourScheme)
|
||
return;
|
||
|
||
auto z = info.Element->GetBaseZ();
|
||
auto direction = info.Element->GetDirection();
|
||
auto gameAction = RideSetColourSchemeAction(
|
||
CoordsXYZD{ info.Loc, z, static_cast<Direction>(direction) }, info.Element->AsTrack()->GetTrackType(),
|
||
newColourScheme);
|
||
GameActions::Execute(&gameAction);
|
||
}
|
||
|
||
void ColourClose()
|
||
{
|
||
if (!(InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)))
|
||
return;
|
||
|
||
if (gCurrentToolWidget.window_classification != classification)
|
||
return;
|
||
|
||
if (gCurrentToolWidget.window_number != number)
|
||
return;
|
||
|
||
ToolCancel();
|
||
}
|
||
|
||
void ColourOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_PAINT_INDIVIDUAL_AREA:
|
||
ToolSet(*this, WIDX_PAINT_INDIVIDUAL_AREA, Tool::PaintDown);
|
||
break;
|
||
case WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX:
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
const bool currentlyEnabled = ride->HasLifecycleFlag(RIDE_LIFECYCLE_RANDOM_SHOP_COLOURS);
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::SellingItemColourIsRandom, currentlyEnabled ? 0 : 1, 0);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void ColourResize()
|
||
{
|
||
WindowSetResize(*this, 316, 207, 316, 207);
|
||
}
|
||
|
||
void ColourOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
VehicleColour vehicleColour;
|
||
int32_t i, numItems;
|
||
StringId stringId;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
auto colourSchemeIndex = _rideColour;
|
||
auto dropdownWidget = &widgets[widgetIndex] - 1;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_TRACK_COLOUR_SCHEME_DROPDOWN:
|
||
for (i = 0; i < OpenRCT2::Limits::NumColourSchemes; i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = ColourSchemeNames[i];
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, 4, widgets[widgetIndex].right - dropdownWidget->left);
|
||
|
||
Dropdown::SetChecked(colourSchemeIndex, true);
|
||
break;
|
||
case WIDX_TRACK_MAIN_COLOUR:
|
||
WindowDropdownShowColour(
|
||
this, &widgets[widgetIndex], colours[1], ride->track_colour[colourSchemeIndex].main);
|
||
break;
|
||
case WIDX_TRACK_ADDITIONAL_COLOUR:
|
||
WindowDropdownShowColour(
|
||
this, &widgets[widgetIndex], colours[1], ride->track_colour[colourSchemeIndex].additional);
|
||
break;
|
||
case WIDX_TRACK_SUPPORT_COLOUR:
|
||
WindowDropdownShowColour(
|
||
this, &widgets[widgetIndex], colours[1], ride->track_colour[colourSchemeIndex].supports);
|
||
break;
|
||
case WIDX_MAZE_STYLE_DROPDOWN:
|
||
for (i = 0; i < 4; i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = MazeOptions[i].text;
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, 4, widgets[widgetIndex].right - dropdownWidget->left);
|
||
|
||
Dropdown::SetChecked(ride->track_colour[colourSchemeIndex].supports, true);
|
||
break;
|
||
case WIDX_ENTRANCE_STYLE_DROPDOWN:
|
||
ShowEntranceStyleDropdown();
|
||
break;
|
||
case WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN:
|
||
for (i = 0; i < 3; i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = (GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle)
|
||
.singular
|
||
<< 16)
|
||
| VehicleColourSchemeNames[i];
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, rideEntry->max_cars_in_train > 1 ? 3 : 2,
|
||
widgets[widgetIndex].right - dropdownWidget->left);
|
||
|
||
Dropdown::SetChecked(ride->colour_scheme_type & 3, true);
|
||
break;
|
||
case WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN:
|
||
numItems = ride->NumTrains;
|
||
if ((ride->colour_scheme_type & 3) != VEHICLE_COLOUR_SCHEME_PER_TRAIN)
|
||
numItems = ride->num_cars_per_train;
|
||
|
||
stringId = (ride->colour_scheme_type & 3) == VEHICLE_COLOUR_SCHEME_PER_TRAIN
|
||
? STR_RIDE_COLOUR_TRAIN_OPTION
|
||
: STR_RIDE_COLOUR_VEHICLE_OPTION;
|
||
for (i = 0; i < std::min(numItems, Dropdown::ItemsMaxSize); i++)
|
||
{
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = (static_cast<int64_t>(i + 1) << 32)
|
||
| ((GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).capitalised) << 16)
|
||
| stringId;
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, numItems, widgets[widgetIndex].right - dropdownWidget->left);
|
||
|
||
Dropdown::SetChecked(_vehicleIndex, true);
|
||
break;
|
||
case WIDX_VEHICLE_BODY_COLOUR:
|
||
vehicleColour = RideGetVehicleColour(*ride, _vehicleIndex);
|
||
WindowDropdownShowColour(this, &widgets[widgetIndex], colours[1], vehicleColour.Body);
|
||
break;
|
||
case WIDX_VEHICLE_TRIM_COLOUR:
|
||
vehicleColour = RideGetVehicleColour(*ride, _vehicleIndex);
|
||
WindowDropdownShowColour(this, &widgets[widgetIndex], colours[1], vehicleColour.Trim);
|
||
break;
|
||
case WIDX_VEHICLE_TERTIARY_COLOUR:
|
||
vehicleColour = RideGetVehicleColour(*ride, _vehicleIndex);
|
||
WindowDropdownShowColour(this, &widgets[widgetIndex], colours[1], vehicleColour.Tertiary);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void ColourOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
|
||
{
|
||
if (dropdownIndex == -1)
|
||
return;
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_TRACK_COLOUR_SCHEME_DROPDOWN:
|
||
_rideColour = static_cast<uint16_t>(dropdownIndex);
|
||
Invalidate();
|
||
break;
|
||
case WIDX_TRACK_MAIN_COLOUR:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::TrackColourMain, ColourDropDownIndexToColour(dropdownIndex),
|
||
_rideColour);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
case WIDX_TRACK_ADDITIONAL_COLOUR:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::TrackColourAdditional, ColourDropDownIndexToColour(dropdownIndex),
|
||
_rideColour);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
case WIDX_TRACK_SUPPORT_COLOUR:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::TrackColourSupports, ColourDropDownIndexToColour(dropdownIndex),
|
||
_rideColour);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
case WIDX_MAZE_STYLE_DROPDOWN:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::MazeStyle, dropdownIndex, _rideColour);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
case WIDX_ENTRANCE_STYLE_DROPDOWN:
|
||
{
|
||
if (static_cast<size_t>(dropdownIndex) >= _entranceDropdownData.size())
|
||
{
|
||
break;
|
||
}
|
||
auto objIndex = _entranceDropdownData[dropdownIndex].EntranceTypeId;
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::EntranceStyle, objIndex, 0);
|
||
rideSetAppearanceAction.SetCallback([objIndex](const GameAction*, const GameActions::Result* res) {
|
||
if (res->Error != GameActions::Status::Ok)
|
||
return;
|
||
GetGameState().LastEntranceStyle = objIndex;
|
||
});
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
break;
|
||
}
|
||
case WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::VehicleColourScheme, dropdownIndex, 0);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
_vehicleIndex = 0;
|
||
}
|
||
break;
|
||
case WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN:
|
||
_vehicleIndex = dropdownIndex;
|
||
Invalidate();
|
||
break;
|
||
case WIDX_VEHICLE_BODY_COLOUR:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::VehicleColourBody, ColourDropDownIndexToColour(dropdownIndex),
|
||
_vehicleIndex);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
case WIDX_VEHICLE_TRIM_COLOUR:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::VehicleColourTrim, ColourDropDownIndexToColour(dropdownIndex),
|
||
_vehicleIndex);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
case WIDX_VEHICLE_TERTIARY_COLOUR:
|
||
{
|
||
auto rideSetAppearanceAction = RideSetAppearanceAction(
|
||
rideId, RideSetAppearanceType::VehicleColourTertiary, ColourDropDownIndexToColour(dropdownIndex),
|
||
_vehicleIndex);
|
||
GameActions::Execute(&rideSetAppearanceAction);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void ColourUpdate()
|
||
{
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_5);
|
||
WidgetInvalidate(*this, WIDX_VEHICLE_PREVIEW);
|
||
}
|
||
|
||
void ColourOnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords)
|
||
{
|
||
if (widgetIndex == WIDX_PAINT_INDIVIDUAL_AREA)
|
||
SetTrackColourScheme(screenCoords);
|
||
}
|
||
|
||
void ColourOnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords)
|
||
{
|
||
if (widgetIndex == WIDX_PAINT_INDIVIDUAL_AREA)
|
||
SetTrackColourScheme(screenCoords);
|
||
}
|
||
|
||
void ColourOnPrepareDraw()
|
||
{
|
||
TrackColour trackColour;
|
||
VehicleColour vehicleColour;
|
||
|
||
auto newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
widgets[WIDX_TITLE].text = STR_ARG_16_STRINGID;
|
||
auto ft = Formatter::Common();
|
||
ft.Increment(16);
|
||
ride->FormatNameTo(ft);
|
||
|
||
// Track colours
|
||
int32_t colourScheme = _rideColour;
|
||
trackColour = ride->track_colour[colourScheme];
|
||
|
||
// Maze style
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_MAZE))
|
||
{
|
||
widgets[WIDX_MAZE_STYLE].type = WindowWidgetType::DropdownMenu;
|
||
widgets[WIDX_MAZE_STYLE_DROPDOWN].type = WindowWidgetType::Button;
|
||
widgets[WIDX_MAZE_STYLE].text = MazeOptions[trackColour.supports].text;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_MAZE_STYLE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_MAZE_STYLE_DROPDOWN].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Track, multiple colour schemes
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SUPPORTS_MULTIPLE_TRACK_COLOUR))
|
||
{
|
||
widgets[WIDX_TRACK_COLOUR_SCHEME].type = WindowWidgetType::DropdownMenu;
|
||
widgets[WIDX_TRACK_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Button;
|
||
widgets[WIDX_PAINT_INDIVIDUAL_AREA].type = WindowWidgetType::FlatBtn;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_TRACK_COLOUR_SCHEME].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_TRACK_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_PAINT_INDIVIDUAL_AREA].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Track main colour
|
||
if (HasTrackColour(*ride, 0))
|
||
{
|
||
widgets[WIDX_TRACK_MAIN_COLOUR].type = WindowWidgetType::ColourBtn;
|
||
widgets[WIDX_TRACK_MAIN_COLOUR].image = GetColourButtonImage(trackColour.main);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_TRACK_MAIN_COLOUR].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Track additional colour
|
||
if (HasTrackColour(*ride, 1))
|
||
{
|
||
widgets[WIDX_TRACK_ADDITIONAL_COLOUR].type = WindowWidgetType::ColourBtn;
|
||
widgets[WIDX_TRACK_ADDITIONAL_COLOUR].image = GetColourButtonImage(trackColour.additional);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_TRACK_ADDITIONAL_COLOUR].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Selling item random colour checkbox
|
||
if (ride->HasRecolourableShopItems())
|
||
{
|
||
widgets[WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX].type = WindowWidgetType::Checkbox;
|
||
if (ride->HasLifecycleFlag(RIDE_LIFECYCLE_RANDOM_SHOP_COLOURS))
|
||
{
|
||
pressed_widgets |= (1uLL << WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX);
|
||
}
|
||
else
|
||
{
|
||
pressed_widgets &= ~(1uLL << WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Track supports colour
|
||
if (HasTrackColour(*ride, 2) && !rtd.HasFlag(RIDE_TYPE_FLAG_IS_MAZE))
|
||
{
|
||
widgets[WIDX_TRACK_SUPPORT_COLOUR].type = WindowWidgetType::ColourBtn;
|
||
widgets[WIDX_TRACK_SUPPORT_COLOUR].image = GetColourButtonImage(trackColour.supports);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_TRACK_SUPPORT_COLOUR].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Track preview
|
||
if (ride->GetRideTypeDescriptor().HasFlag(
|
||
RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN | RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL
|
||
| RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS))
|
||
widgets[WIDX_TRACK_PREVIEW].type = WindowWidgetType::Spinner;
|
||
else
|
||
widgets[WIDX_TRACK_PREVIEW].type = WindowWidgetType::Empty;
|
||
|
||
// Entrance style
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT))
|
||
{
|
||
widgets[WIDX_ENTRANCE_PREVIEW].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_ENTRANCE_STYLE].type = WindowWidgetType::DropdownMenu;
|
||
widgets[WIDX_ENTRANCE_STYLE_DROPDOWN].type = WindowWidgetType::Button;
|
||
|
||
auto stringId = STR_NONE;
|
||
auto stationObj = ride->GetStationObject();
|
||
if (stationObj != nullptr)
|
||
{
|
||
stringId = stationObj->NameStringId;
|
||
}
|
||
widgets[WIDX_ENTRANCE_STYLE].text = stringId;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_ENTRANCE_PREVIEW].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_ENTRANCE_STYLE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_ENTRANCE_STYLE_DROPDOWN].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Vehicle colours
|
||
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES)
|
||
&& ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_VEHICLE_COLOURS))
|
||
{
|
||
int32_t vehicleColourSchemeType = ride->colour_scheme_type & 3;
|
||
if (vehicleColourSchemeType == 0)
|
||
_vehicleIndex = 0;
|
||
|
||
vehicleColour = RideGetVehicleColour(*ride, _vehicleIndex);
|
||
|
||
widgets[WIDX_VEHICLE_PREVIEW].type = WindowWidgetType::Scroll;
|
||
widgets[WIDX_VEHICLE_BODY_COLOUR].type = WindowWidgetType::ColourBtn;
|
||
widgets[WIDX_VEHICLE_BODY_COLOUR].image = GetColourButtonImage(vehicleColour.Body);
|
||
|
||
bool allowChangingTrimColour = false;
|
||
bool allowChangingTertiaryColour = false;
|
||
|
||
for (int32_t i = 0; i < ride->num_cars_per_train; i++)
|
||
{
|
||
uint8_t vehicleTypeIndex = RideEntryGetVehicleAtPosition(ride->subtype, ride->num_cars_per_train, i);
|
||
|
||
if (rideEntry->Cars[vehicleTypeIndex].flags & CAR_ENTRY_FLAG_ENABLE_TRIM_COLOUR)
|
||
{
|
||
allowChangingTrimColour = true;
|
||
}
|
||
if (rideEntry->Cars[vehicleTypeIndex].flags & CAR_ENTRY_FLAG_ENABLE_TERTIARY_COLOUR)
|
||
{
|
||
allowChangingTertiaryColour = true;
|
||
}
|
||
}
|
||
|
||
// Additional colours
|
||
if (allowChangingTrimColour)
|
||
{
|
||
widgets[WIDX_VEHICLE_TRIM_COLOUR].type = WindowWidgetType::ColourBtn;
|
||
widgets[WIDX_VEHICLE_TRIM_COLOUR].image = GetColourButtonImage(vehicleColour.Trim);
|
||
if (allowChangingTertiaryColour)
|
||
{
|
||
widgets[WIDX_VEHICLE_TERTIARY_COLOUR].type = WindowWidgetType::ColourBtn;
|
||
widgets[WIDX_VEHICLE_TERTIARY_COLOUR].image = GetColourButtonImage(vehicleColour.Tertiary);
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_TERTIARY_COLOUR].type = WindowWidgetType::Empty;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_TRIM_COLOUR].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_TERTIARY_COLOUR].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Vehicle colour scheme type
|
||
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_VEHICLE_IS_INTEGRAL)
|
||
&& (ride->num_cars_per_train | ride->NumTrains) > 1)
|
||
{
|
||
widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::DropdownMenu;
|
||
widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Button;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty;
|
||
}
|
||
ft.Rewind();
|
||
ft.Increment(6);
|
||
ft.Add<StringId>(VehicleColourSchemeNames[vehicleColourSchemeType]);
|
||
ft.Add<StringId>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).singular);
|
||
ft.Add<StringId>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).capitalised);
|
||
ft.Add<uint16_t>(_vehicleIndex + 1);
|
||
|
||
// Vehicle index
|
||
if (vehicleColourSchemeType != 0)
|
||
{
|
||
widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::DropdownMenu;
|
||
widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Button;
|
||
widgets[WIDX_VEHICLE_COLOUR_INDEX].text = vehicleColourSchemeType == 1 ? STR_RIDE_COLOUR_TRAIN_VALUE
|
||
: STR_RIDE_COLOUR_VEHICLE_VALUE;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Empty;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_VEHICLE_PREVIEW].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_BODY_COLOUR].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_TRIM_COLOUR].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_VEHICLE_TERTIARY_COLOUR].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
ft.Rewind();
|
||
ft.Increment(14);
|
||
ft.Add<StringId>(ColourSchemeNames[colourScheme]);
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
}
|
||
|
||
void ColourOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
// TODO: This should use lists and identified sprites
|
||
DrawPixelInfo clippedDpi;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
// Track / shop item preview
|
||
const auto& trackPreviewWidget = widgets[WIDX_TRACK_PREVIEW];
|
||
if (trackPreviewWidget.type != WindowWidgetType::Empty)
|
||
GfxFillRect(
|
||
dpi,
|
||
{ { windowPos + ScreenCoordsXY{ trackPreviewWidget.left + 1, trackPreviewWidget.top + 1 } },
|
||
{ windowPos + ScreenCoordsXY{ trackPreviewWidget.right - 1, trackPreviewWidget.bottom - 1 } } },
|
||
PALETTE_INDEX_12);
|
||
|
||
auto trackColour = ride->track_colour[_rideColour];
|
||
|
||
//
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr || rideEntry->shop_item[0] == ShopItem::None)
|
||
{
|
||
auto screenCoords = windowPos + ScreenCoordsXY{ trackPreviewWidget.left, trackPreviewWidget.top };
|
||
|
||
// Track
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_MAZE))
|
||
{
|
||
GfxDrawSprite(dpi, ImageId(MazeOptions[trackColour.supports].sprite), screenCoords);
|
||
}
|
||
else
|
||
{
|
||
auto typeDescriptor = ride->GetRideTypeDescriptor();
|
||
int32_t spriteIndex = typeDescriptor.ColourPreview.Track;
|
||
if (spriteIndex != 0)
|
||
{
|
||
GfxDrawSprite(dpi, ImageId(spriteIndex, trackColour.main, trackColour.additional), screenCoords);
|
||
}
|
||
|
||
// Supports
|
||
spriteIndex = typeDescriptor.ColourPreview.Supports;
|
||
if (spriteIndex != 0)
|
||
{
|
||
GfxDrawSprite(dpi, ImageId(spriteIndex, trackColour.supports), screenCoords);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
auto screenCoords = windowPos
|
||
+ ScreenCoordsXY{ (trackPreviewWidget.left + trackPreviewWidget.right) / 2 - 8,
|
||
(trackPreviewWidget.bottom + trackPreviewWidget.top) / 2 - 6 };
|
||
|
||
ShopItem shopItem = rideEntry->shop_item[1] == ShopItem::None ? rideEntry->shop_item[0]
|
||
: rideEntry->shop_item[1];
|
||
if (ride->HasLifecycleFlag(RIDE_LIFECYCLE_RANDOM_SHOP_COLOURS))
|
||
{
|
||
colour_t spriteColour = COLOUR_BLACK;
|
||
// Limit update rate of preview to avoid making people dizzy.
|
||
if ((GetGameState().CurrentTicks % 64) == 0)
|
||
{
|
||
spriteColour++;
|
||
if (spriteColour >= COLOUR_NUM_NORMAL)
|
||
{
|
||
spriteColour = COLOUR_BLACK;
|
||
}
|
||
}
|
||
|
||
GfxDrawSprite(dpi, ImageId(GetShopItemDescriptor(shopItem).Image, spriteColour), screenCoords);
|
||
}
|
||
else
|
||
{
|
||
GfxDrawSprite(
|
||
dpi, ImageId(GetShopItemDescriptor(shopItem).Image, ride->track_colour[0].main), screenCoords);
|
||
}
|
||
}
|
||
|
||
// Entrance preview
|
||
trackColour = ride->track_colour[0];
|
||
const auto& entrancePreviewWidget = widgets[WIDX_ENTRANCE_PREVIEW];
|
||
if (entrancePreviewWidget.type != WindowWidgetType::Empty)
|
||
{
|
||
if (ClipDrawPixelInfo(
|
||
clippedDpi, dpi,
|
||
windowPos + ScreenCoordsXY{ entrancePreviewWidget.left + 1, entrancePreviewWidget.top + 1 },
|
||
entrancePreviewWidget.width(), entrancePreviewWidget.height()))
|
||
{
|
||
GfxClear(clippedDpi, PALETTE_INDEX_12);
|
||
|
||
auto stationObj = ride->GetStationObject();
|
||
if (stationObj != nullptr && stationObj->BaseImageId != ImageIndexUndefined)
|
||
{
|
||
auto imageId = ImageId(stationObj->BaseImageId, trackColour.main, trackColour.additional);
|
||
|
||
// Back
|
||
GfxDrawSprite(clippedDpi, imageId, { 34, 20 });
|
||
|
||
// Front
|
||
GfxDrawSprite(clippedDpi, imageId.WithIndexOffset(4), { 34, 20 });
|
||
|
||
// Glass
|
||
if (stationObj->Flags & STATION_OBJECT_FLAGS::IS_TRANSPARENT)
|
||
{
|
||
auto glassImageId = ImageId(stationObj->BaseImageId + 20).WithTransparency(trackColour.main);
|
||
GfxDrawSprite(clippedDpi, glassImageId, { 34, 20 });
|
||
}
|
||
}
|
||
}
|
||
|
||
DrawTextEllipsised(dpi, { windowPos.x + 3, windowPos.y + 103 }, 97, STR_STATION_STYLE, {});
|
||
}
|
||
}
|
||
|
||
void ColourOnScrollDraw(DrawPixelInfo& dpi, int32_t scrollIndex) const
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
auto vehiclePreviewWidget = &widgets[WIDX_VEHICLE_PREVIEW];
|
||
auto vehicleColour = RideGetVehicleColour(*ride, _vehicleIndex);
|
||
|
||
// Background colour
|
||
GfxFillRect(dpi, { { dpi.x, dpi.y }, { dpi.x + dpi.width - 1, dpi.y + dpi.height - 1 } }, PALETTE_INDEX_12);
|
||
|
||
// ?
|
||
auto screenCoords = ScreenCoordsXY{ vehiclePreviewWidget->width() / 2, vehiclePreviewWidget->height() - 15 };
|
||
|
||
// ?
|
||
auto trainCarIndex = (ride->colour_scheme_type & 3) == RIDE_COLOUR_SCHEME_MODE_DIFFERENT_PER_CAR
|
||
? _vehicleIndex
|
||
: rideEntry->TabCar;
|
||
|
||
const auto& carEntry = rideEntry->Cars[RideEntryGetVehicleAtPosition(
|
||
ride->subtype, ride->num_cars_per_train, trainCarIndex)];
|
||
|
||
screenCoords.y += carEntry.tab_height;
|
||
|
||
// Draw the coloured spinning vehicle
|
||
// frame_no represents a SpritePrecision of 64
|
||
ImageIndex imageIndex = carEntry.SpriteByYaw(frame_no / 2, SpriteGroupType::SlopeFlat);
|
||
imageIndex &= carEntry.TabRotationMask;
|
||
imageIndex *= carEntry.base_num_frames;
|
||
imageIndex += carEntry.base_image_id;
|
||
auto imageId = ImageId(imageIndex, vehicleColour.Body, vehicleColour.Trim, vehicleColour.Tertiary);
|
||
GfxDrawSprite(dpi, imageId, screenCoords);
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Music
|
||
std::vector<ObjectEntryIndex> window_ride_current_music_style_order;
|
||
|
||
void ToggleMusic()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
int32_t activateMusic = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) ? 0 : 1;
|
||
SetOperatingSetting(rideId, RideSetSetting::Music, activateMusic);
|
||
}
|
||
}
|
||
|
||
void MusicOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_PLAY_MUSIC:
|
||
ToggleMusic();
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MusicResize()
|
||
{
|
||
flags |= WF_RESIZABLE;
|
||
WindowSetResize(*this, 316, 81, 316, 81);
|
||
}
|
||
|
||
static std::string GetMusicString(ObjectEntryIndex musicObjectIndex)
|
||
{
|
||
auto& objManager = GetContext()->GetObjectManager();
|
||
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, musicObjectIndex));
|
||
|
||
return LanguageGetString(musicObj->NameStringId);
|
||
}
|
||
|
||
/**
|
||
*
|
||
* rct2: 0x006B1EFC
|
||
*/
|
||
void MusicOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
if (widgetIndex != WIDX_MUSIC_DROPDOWN)
|
||
return;
|
||
|
||
auto dropdownWidget = &widgets[widgetIndex] - 1;
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
// Construct list of available music
|
||
auto& musicOrder = window_ride_current_music_style_order;
|
||
musicOrder.clear();
|
||
auto& objManager = GetContext()->GetObjectManager();
|
||
for (ObjectEntryIndex i = 0; i < MAX_MUSIC_OBJECTS; i++)
|
||
{
|
||
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, i));
|
||
if (musicObj != nullptr)
|
||
{
|
||
// Hide custom music if the WAV file does not exist
|
||
auto originalStyleId = musicObj->GetOriginalStyleId();
|
||
if (originalStyleId.has_value()
|
||
&& (originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_1 || originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_2))
|
||
{
|
||
auto numTracks = musicObj->GetTrackCount();
|
||
if (numTracks != 0)
|
||
{
|
||
auto track0 = musicObj->GetTrack(0);
|
||
if (!track0->Asset.IsAvailable())
|
||
{
|
||
continue;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (GetGameState().Cheats.UnlockOperatingLimits || musicObj->SupportsRideType(ride->type))
|
||
{
|
||
musicOrder.push_back(i);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sort available music by the alphabetical order
|
||
std::stable_sort(musicOrder.begin(), musicOrder.end(), [](const ObjectEntryIndex& a, const ObjectEntryIndex& b) {
|
||
return String::Compare(GetMusicString(b), GetMusicString(a), false) > 0;
|
||
});
|
||
|
||
// Setup dropdown list
|
||
auto numItems = musicOrder.size();
|
||
for (size_t i = 0; i < numItems; i++)
|
||
{
|
||
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, musicOrder[i]));
|
||
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
|
||
gDropdownItems[i].Args = musicObj->NameStringId;
|
||
}
|
||
|
||
WindowDropdownShowTextCustomWidth(
|
||
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
|
||
colours[1], 0, Dropdown::Flag::StayOpen, numItems, widgets[widgetIndex].right - dropdownWidget->left);
|
||
|
||
// Set currently checked item
|
||
for (size_t i = 0; i < numItems; i++)
|
||
{
|
||
if (musicOrder[i] == ride->music)
|
||
{
|
||
Dropdown::SetChecked(static_cast<int32_t>(i), true);
|
||
}
|
||
}
|
||
}
|
||
|
||
void MusicOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
|
||
{
|
||
if (widgetIndex == WIDX_MUSIC_DROPDOWN && dropdownIndex >= 0
|
||
&& static_cast<size_t>(dropdownIndex) < window_ride_current_music_style_order.size())
|
||
{
|
||
auto musicStyle = window_ride_current_music_style_order[dropdownIndex];
|
||
SetOperatingSetting(rideId, RideSetSetting::MusicType, musicStyle);
|
||
}
|
||
}
|
||
|
||
void MusicUpdate()
|
||
{
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_6);
|
||
}
|
||
|
||
void MusicOnPrepareDraw()
|
||
{
|
||
auto newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto ft = Formatter::Common();
|
||
ride->FormatNameTo(ft);
|
||
|
||
// Set selected music
|
||
StringId musicName = STR_NONE;
|
||
auto& objManager = GetContext()->GetObjectManager();
|
||
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music));
|
||
if (musicObj != nullptr)
|
||
{
|
||
musicName = musicObj->NameStringId;
|
||
}
|
||
widgets[WIDX_MUSIC].text = musicName;
|
||
|
||
// Set music activated
|
||
auto isMusicActivated = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) != 0;
|
||
if (isMusicActivated)
|
||
{
|
||
pressed_widgets |= (1uLL << WIDX_PLAY_MUSIC);
|
||
disabled_widgets &= ~(1uLL << WIDX_MUSIC);
|
||
disabled_widgets &= ~(1uLL << WIDX_MUSIC_DROPDOWN);
|
||
}
|
||
else
|
||
{
|
||
pressed_widgets &= ~(1uLL << WIDX_PLAY_MUSIC);
|
||
disabled_widgets |= (1uLL << WIDX_MUSIC);
|
||
disabled_widgets |= (1uLL << WIDX_MUSIC_DROPDOWN);
|
||
}
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
}
|
||
|
||
void MusicOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Measurements
|
||
|
||
static constexpr StringId GetRatingName(ride_rating rating)
|
||
{
|
||
int32_t index = std::clamp<int32_t>(rating >> 8, 0, static_cast<int32_t>(std::size(RatingNames)) - 1);
|
||
return RatingNames[index];
|
||
}
|
||
|
||
void SetupScenerySelection()
|
||
{
|
||
if (gTrackDesignSaveMode)
|
||
{
|
||
CancelScenerySelection();
|
||
}
|
||
|
||
while (ToolSet(*this, WIDX_BACKGROUND, Tool::Crosshair))
|
||
;
|
||
|
||
gTrackDesignSaveRideIndex = rideId;
|
||
|
||
TrackDesignSaveInit();
|
||
gGamePaused |= GAME_PAUSED_SAVING_TRACK;
|
||
gTrackDesignSaveMode = true;
|
||
|
||
OpenRCT2::Audio::StopAll();
|
||
|
||
WindowBase* w_main = WindowGetMain();
|
||
if (w_main != nullptr)
|
||
{
|
||
w_main->viewport->flags |= (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE);
|
||
}
|
||
|
||
GfxInvalidateScreen();
|
||
}
|
||
|
||
void MeasurementsDesignReset()
|
||
{
|
||
TrackDesignSaveResetScenery();
|
||
}
|
||
|
||
void MeasurementsDesignSelectNearbyScenery()
|
||
{
|
||
TrackDesignSaveSelectNearbyScenery(gTrackDesignSaveRideIndex);
|
||
}
|
||
|
||
void MeasurementsDesignCancel()
|
||
{
|
||
if (gTrackDesignSaveMode)
|
||
{
|
||
CancelScenerySelection();
|
||
}
|
||
}
|
||
|
||
static void TrackDesignCallback(int32_t result, [[maybe_unused]] const utf8* path)
|
||
{
|
||
if (result == MODAL_RESULT_OK)
|
||
{
|
||
TrackRepositoryScan();
|
||
}
|
||
GfxInvalidateScreen();
|
||
};
|
||
|
||
void MeasurementsDesignSave()
|
||
{
|
||
TrackDesignState tds{};
|
||
|
||
Ride* ride = GetRide(rideId);
|
||
_trackDesign = ride->SaveToTrackDesign(tds);
|
||
if (!_trackDesign)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (gTrackDesignSaveMode)
|
||
{
|
||
auto errMessage = _trackDesign->CreateTrackDesignScenery(tds);
|
||
if (!errMessage.Successful)
|
||
{
|
||
ContextShowError(STR_CANT_SAVE_TRACK_DESIGN, errMessage.Message, {});
|
||
return;
|
||
}
|
||
if (errMessage.HasMessage())
|
||
{
|
||
ContextShowError(errMessage.Message, STR_EMPTY, {});
|
||
}
|
||
}
|
||
|
||
auto trackName = ride->GetName();
|
||
auto intent = Intent(WindowClass::Loadsave);
|
||
intent.PutExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_TRACK);
|
||
intent.PutExtra(INTENT_EXTRA_TRACK_DESIGN, _trackDesign.get());
|
||
intent.PutExtra(INTENT_EXTRA_PATH, trackName);
|
||
intent.PutExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast<void*>(&TrackDesignCallback));
|
||
|
||
ContextOpenIntent(&intent);
|
||
}
|
||
|
||
void MeasurementsClose()
|
||
{
|
||
MeasurementsDesignCancel();
|
||
}
|
||
|
||
void MeasurementsOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_SELECT_NEARBY_SCENERY:
|
||
MeasurementsDesignSelectNearbyScenery();
|
||
break;
|
||
case WIDX_RESET_SELECTION:
|
||
MeasurementsDesignReset();
|
||
break;
|
||
case WIDX_SAVE_DESIGN:
|
||
MeasurementsDesignSave();
|
||
break;
|
||
case WIDX_CANCEL_DESIGN:
|
||
MeasurementsDesignCancel();
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MeasurementsResize()
|
||
{
|
||
WindowSetResize(*this, 316, 234, 316, 234);
|
||
}
|
||
|
||
void MeasurementsOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
if (widgetIndex != WIDX_SAVE_TRACK_DESIGN)
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
gDropdownItems[0].Format = STR_SAVE_TRACK_DESIGN_ITEM;
|
||
gDropdownItems[1].Format = STR_SAVE_TRACK_DESIGN_WITH_SCENERY_ITEM;
|
||
|
||
WindowDropdownShowText(
|
||
{ windowPos.x + widgets[widgetIndex].left, windowPos.y + widgets[widgetIndex].top },
|
||
widgets[widgetIndex].height() + 1, colours[1], Dropdown::Flag::StayOpen, 2);
|
||
gDropdownDefaultIndex = 0;
|
||
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
|
||
{
|
||
// Disable saving without scenery if we're a flat ride
|
||
Dropdown::SetDisabled(0, true);
|
||
gDropdownDefaultIndex = 1;
|
||
}
|
||
}
|
||
|
||
void MeasurementsOnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex)
|
||
{
|
||
if (widgetIndex != WIDX_SAVE_TRACK_DESIGN)
|
||
return;
|
||
|
||
if (dropdownIndex == -1)
|
||
dropdownIndex = gDropdownHighlightedIndex;
|
||
|
||
if (dropdownIndex == 0)
|
||
{
|
||
MeasurementsDesignSave();
|
||
}
|
||
else
|
||
SetupScenerySelection();
|
||
}
|
||
|
||
void MeasurementsUpdate()
|
||
{
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_7);
|
||
}
|
||
|
||
void MeasurementsOnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords)
|
||
{
|
||
_lastSceneryX = screenCoords.x;
|
||
_lastSceneryY = screenCoords.y;
|
||
_collectTrackDesignScenery = true; // Default to true in case user does not select anything valid
|
||
|
||
constexpr auto interactionFlags = EnumsToFlags(
|
||
ViewportInteractionItem::Scenery, ViewportInteractionItem::Footpath, ViewportInteractionItem::Wall,
|
||
ViewportInteractionItem::LargeScenery);
|
||
auto info = GetMapCoordinatesFromPos(screenCoords, interactionFlags);
|
||
switch (info.SpriteType)
|
||
{
|
||
case ViewportInteractionItem::Scenery:
|
||
case ViewportInteractionItem::LargeScenery:
|
||
case ViewportInteractionItem::Wall:
|
||
case ViewportInteractionItem::Footpath:
|
||
_collectTrackDesignScenery = !TrackDesignSaveContainsTileElement(info.Element);
|
||
TrackDesignSaveSelectTileElement(info.SpriteType, info.Loc, info.Element, _collectTrackDesignScenery);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MeasurementsOnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords)
|
||
{
|
||
if (screenCoords.x == _lastSceneryX && screenCoords.y == _lastSceneryY)
|
||
return;
|
||
_lastSceneryX = screenCoords.x;
|
||
_lastSceneryY = screenCoords.y;
|
||
|
||
auto interactionFlags = EnumsToFlags(
|
||
ViewportInteractionItem::Scenery, ViewportInteractionItem::Footpath, ViewportInteractionItem::Wall,
|
||
ViewportInteractionItem::LargeScenery);
|
||
auto info = GetMapCoordinatesFromPos(screenCoords, interactionFlags);
|
||
switch (info.SpriteType)
|
||
{
|
||
case ViewportInteractionItem::Scenery:
|
||
case ViewportInteractionItem::LargeScenery:
|
||
case ViewportInteractionItem::Wall:
|
||
case ViewportInteractionItem::Footpath:
|
||
TrackDesignSaveSelectTileElement(info.SpriteType, info.Loc, info.Element, _collectTrackDesignScenery);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MeasurementsOnToolAbort(WidgetIndex widgetIndex)
|
||
{
|
||
MeasurementsDesignCancel();
|
||
}
|
||
|
||
void MeasurementsOnPrepareDraw()
|
||
{
|
||
auto newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto ft = Formatter::Common();
|
||
ride->FormatNameTo(ft);
|
||
|
||
widgets[WIDX_SAVE_TRACK_DESIGN].tooltip = STR_SAVE_TRACK_DESIGN_NOT_POSSIBLE;
|
||
widgets[WIDX_SAVE_TRACK_DESIGN].type = WindowWidgetType::Empty;
|
||
if (gTrackDesignSaveMode && gTrackDesignSaveRideIndex == rideId)
|
||
{
|
||
widgets[WIDX_SELECT_NEARBY_SCENERY].type = WindowWidgetType::Button;
|
||
widgets[WIDX_RESET_SELECTION].type = WindowWidgetType::Button;
|
||
widgets[WIDX_SAVE_DESIGN].type = WindowWidgetType::Button;
|
||
widgets[WIDX_CANCEL_DESIGN].type = WindowWidgetType::Button;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_SELECT_NEARBY_SCENERY].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_RESET_SELECTION].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_SAVE_DESIGN].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_CANCEL_DESIGN].type = WindowWidgetType::Empty;
|
||
|
||
widgets[WIDX_SAVE_TRACK_DESIGN].type = WindowWidgetType::FlatBtn;
|
||
disabled_widgets |= (1uLL << WIDX_SAVE_TRACK_DESIGN);
|
||
if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)
|
||
{
|
||
if (ride->excitement != kRideRatingUndefined)
|
||
{
|
||
disabled_widgets &= ~(1uLL << WIDX_SAVE_TRACK_DESIGN);
|
||
widgets[WIDX_SAVE_TRACK_DESIGN].tooltip = STR_SAVE_TRACK_DESIGN;
|
||
}
|
||
}
|
||
}
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
}
|
||
|
||
void MeasurementsOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
if (widgets[WIDX_SAVE_DESIGN].type == WindowWidgetType::Button)
|
||
{
|
||
Widget* widget = &widgets[WIDX_PAGE_BACKGROUND];
|
||
|
||
ScreenCoordsXY widgetCoords(windowPos.x + widget->width() / 2, windowPos.y + widget->top + 40);
|
||
DrawTextWrapped(
|
||
dpi, widgetCoords, width - 8, STR_CLICK_ITEMS_OF_SCENERY_TO_SELECT, {}, { TextAlignment::CENTRE });
|
||
|
||
widgetCoords.x = windowPos.x + 4;
|
||
widgetCoords.y = windowPos.y + widgets[WIDX_SELECT_NEARBY_SCENERY].bottom + 17;
|
||
GfxFillRectInset(
|
||
dpi, { widgetCoords, { windowPos.x + 312, widgetCoords.y + 1 } }, colours[1], INSET_RECT_FLAG_BORDER_INSET);
|
||
}
|
||
else
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto screenCoords = windowPos
|
||
+ ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 };
|
||
|
||
if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)
|
||
{
|
||
// Excitement
|
||
StringId ratingName = GetRatingName(ride->excitement);
|
||
auto ft = Formatter();
|
||
ft.Add<uint32_t>(ride->excitement);
|
||
ft.Add<StringId>(ratingName);
|
||
StringId stringId = !RideHasRatings(*ride) ? STR_EXCITEMENT_RATING_NOT_YET_AVAILABLE
|
||
: STR_EXCITEMENT_RATING;
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Intensity
|
||
ratingName = GetRatingName(ride->intensity);
|
||
ft = Formatter();
|
||
ft.Add<uint32_t>(ride->intensity);
|
||
ft.Add<StringId>(ratingName);
|
||
|
||
stringId = STR_INTENSITY_RATING;
|
||
if (!RideHasRatings(*ride))
|
||
stringId = STR_INTENSITY_RATING_NOT_YET_AVAILABLE;
|
||
else if (ride->intensity >= RIDE_RATING(10, 00))
|
||
stringId = STR_INTENSITY_RATING_RED;
|
||
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Nausea
|
||
ratingName = GetRatingName(ride->nausea);
|
||
ft = Formatter();
|
||
ft.Add<uint32_t>(ride->nausea);
|
||
ft.Add<StringId>(ratingName);
|
||
stringId = !RideHasRatings(*ride) ? STR_NAUSEA_RATING_NOT_YET_AVAILABLE : STR_NAUSEA_RATING;
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += 2 * kListRowHeight;
|
||
|
||
// Horizontal rule
|
||
GfxFillRectInset(
|
||
dpi, { screenCoords - ScreenCoordsXY{ 0, 6 }, screenCoords + ScreenCoordsXY{ 303, -5 } }, colours[1],
|
||
INSET_RECT_FLAG_BORDER_INSET);
|
||
|
||
if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_NO_RAW_STATS))
|
||
{
|
||
if (ride->type == RIDE_TYPE_MINI_GOLF)
|
||
{
|
||
// Holes
|
||
ft = Formatter();
|
||
ft.Add<uint16_t>(ride->holes);
|
||
DrawTextBasic(dpi, screenCoords, STR_HOLES, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
else
|
||
{
|
||
// Max speed
|
||
ft = Formatter();
|
||
ft.Add<int32_t>((ride->max_speed * 9) >> 18);
|
||
DrawTextBasic(dpi, screenCoords, STR_MAX_SPEED, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Average speed
|
||
ft = Formatter();
|
||
ft.Add<int32_t>((ride->average_speed * 9) >> 18);
|
||
DrawTextBasic(dpi, screenCoords, STR_AVERAGE_SPEED, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Ride time
|
||
ft = Formatter();
|
||
int32_t numTimes = 0;
|
||
// TODO: STR_RIDE_TIME only takes up to 4 stations modify to take more
|
||
// also if modified may need to be split into multiple format strings
|
||
// as formatter cannot take more than 256 bytes
|
||
for (int32_t i = 0; i < std::min<int32_t>(ride->num_stations, 4); i++)
|
||
{
|
||
StationIndex stationIndex = StationIndex::FromUnderlying(numTimes);
|
||
auto time = ride->GetStation(stationIndex).SegmentTime;
|
||
if (time != 0)
|
||
{
|
||
ft.Add<uint16_t>(STR_RIDE_TIME_ENTRY_WITH_SEPARATOR);
|
||
ft.Add<uint16_t>(time);
|
||
numTimes++;
|
||
}
|
||
}
|
||
if (numTimes == 0)
|
||
{
|
||
ft.Add<uint16_t>(STR_RIDE_TIME_ENTRY);
|
||
ft.Add<uint16_t>(0);
|
||
numTimes++;
|
||
}
|
||
else
|
||
{
|
||
// sadly, STR_RIDE_TIME_ENTRY_WITH_SEPARATOR are defined with the separator AFTER an entry
|
||
// therefore we set the last entry to use the no-separator format now, post-format
|
||
ft.Rewind();
|
||
ft.Increment((numTimes - 1) * 4);
|
||
ft.Add<uint16_t>(STR_RIDE_TIME_ENTRY);
|
||
}
|
||
ft.Rewind();
|
||
ft.Increment(numTimes * 4);
|
||
ft.Add<uint16_t>(0);
|
||
ft.Add<uint16_t>(0);
|
||
ft.Add<uint16_t>(0);
|
||
ft.Add<uint16_t>(0);
|
||
DrawTextEllipsised(dpi, screenCoords, 308, STR_RIDE_TIME, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
|
||
// Ride length
|
||
ft = Formatter();
|
||
int32_t numLengths = 0;
|
||
// TODO: see above STR_RIDE_LENGTH is also only able to display max 4
|
||
for (int32_t i = 0; i < std::min<int32_t>(ride->num_stations, 4); i++)
|
||
{
|
||
StationIndex stationIndex = StationIndex::FromUnderlying(i);
|
||
auto length = ride->GetStation(stationIndex).SegmentLength;
|
||
if (length != 0)
|
||
{
|
||
length >>= 16;
|
||
ft.Add<StringId>(STR_RIDE_LENGTH_ENTRY_WITH_SEPARATOR);
|
||
ft.Add<uint16_t>(length & 0xFFFF);
|
||
numLengths++;
|
||
}
|
||
}
|
||
if (numLengths == 0)
|
||
{
|
||
ft.Add<StringId>(STR_RIDE_LENGTH_ENTRY);
|
||
ft.Add<uint16_t>(0);
|
||
numLengths++;
|
||
}
|
||
else
|
||
{
|
||
// sadly, STR_RIDE_LENGTH_ENTRY_WITH_SEPARATOR are defined with the separator AFTER an entry
|
||
// therefore we set the last entry to use the no-separator format now, post-format
|
||
ft.Rewind();
|
||
ft.Increment((numLengths - 1) * 4);
|
||
ft.Add<StringId>(STR_RIDE_LENGTH_ENTRY);
|
||
}
|
||
ft.Rewind();
|
||
ft.Increment(numLengths * 4);
|
||
ft.Add<uint16_t>(0);
|
||
ft.Add<uint16_t>(0);
|
||
ft.Add<uint16_t>(0);
|
||
ft.Add<uint16_t>(0);
|
||
DrawTextEllipsised(dpi, screenCoords, 308, STR_RIDE_LENGTH, ft);
|
||
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES))
|
||
{
|
||
// Max. positive vertical G's
|
||
stringId = STR_MAX_POSITIVE_VERTICAL_G;
|
||
|
||
ft = Formatter();
|
||
ft.Add<fixed16_2dp>(ride->max_positive_vertical_g);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Max. negative vertical G's
|
||
stringId = ride->max_negative_vertical_g <= RIDE_G_FORCES_RED_NEG_VERTICAL
|
||
? STR_MAX_NEGATIVE_VERTICAL_G_RED
|
||
: STR_MAX_NEGATIVE_VERTICAL_G;
|
||
ft = Formatter();
|
||
ft.Add<int32_t>(ride->max_negative_vertical_g);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Max lateral G's
|
||
stringId = ride->max_lateral_g > RIDE_G_FORCES_RED_LATERAL ? STR_MAX_LATERAL_G_RED
|
||
: STR_MAX_LATERAL_G;
|
||
ft = Formatter();
|
||
ft.Add<fixed16_2dp>(ride->max_lateral_g);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Total 'air' time
|
||
ft = Formatter();
|
||
ft.Add<fixed32_2dp>(ride->total_air_time * 3);
|
||
DrawTextBasic(dpi, screenCoords, STR_TOTAL_AIR_TIME, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_DROPS))
|
||
{
|
||
// Drops
|
||
auto drops = ride->drops & 0x3F;
|
||
ft = Formatter();
|
||
ft.Add<uint16_t>(drops);
|
||
DrawTextBasic(dpi, screenCoords, STR_DROPS, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Highest drop height
|
||
auto highestDropHeight = (ride->highest_drop_height * 3) / 4;
|
||
ft = Formatter();
|
||
ft.Add<int32_t>(highestDropHeight);
|
||
DrawTextBasic(dpi, screenCoords, STR_HIGHEST_DROP_HEIGHT, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
|
||
if (ride->type != RIDE_TYPE_MINI_GOLF)
|
||
{
|
||
// Inversions
|
||
if (ride->inversions != 0)
|
||
{
|
||
ft = Formatter();
|
||
ft.Add<uint16_t>(ride->inversions);
|
||
DrawTextBasic(dpi, screenCoords, STR_INVERSIONS, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
DrawTextBasic(dpi, screenCoords, STR_NO_TEST_RESULTS_YET);
|
||
}
|
||
}
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Graphs
|
||
|
||
enum
|
||
{
|
||
GRAPH_VELOCITY,
|
||
GRAPH_ALTITUDE,
|
||
GRAPH_VERTICAL,
|
||
GRAPH_LATERAL
|
||
};
|
||
|
||
void SetGraph(int32_t type)
|
||
{
|
||
if (list_information_type == type)
|
||
{
|
||
_autoScrollGraph = !_autoScrollGraph;
|
||
}
|
||
else
|
||
{
|
||
list_information_type = type;
|
||
}
|
||
Invalidate();
|
||
}
|
||
|
||
void GraphsOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void GraphsResize()
|
||
{
|
||
WindowSetResize(*this, 316, 182, 500, 450);
|
||
}
|
||
|
||
void GraphsOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_GRAPH_VELOCITY:
|
||
SetGraph(GRAPH_VELOCITY);
|
||
break;
|
||
case WIDX_GRAPH_ALTITUDE:
|
||
SetGraph(GRAPH_ALTITUDE);
|
||
break;
|
||
case WIDX_GRAPH_VERTICAL:
|
||
SetGraph(GRAPH_VERTICAL);
|
||
break;
|
||
case WIDX_GRAPH_LATERAL:
|
||
SetGraph(GRAPH_LATERAL);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void GraphsUpdate()
|
||
{
|
||
Widget* widget;
|
||
int32_t x;
|
||
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_8);
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_GRAPH);
|
||
|
||
widget = &widgets[WIDX_GRAPH];
|
||
x = scrolls[0].h_left;
|
||
if (_autoScrollGraph)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
RideMeasurement* measurement{};
|
||
std::tie(measurement, std::ignore) = ride->GetMeasurement();
|
||
x = measurement == nullptr ? 0 : measurement->current_item - ((widget->width() / 4) * 3);
|
||
}
|
||
}
|
||
|
||
scrolls[0].h_left = std::clamp(x, 0, scrolls[0].h_right - (widget->width() - 2));
|
||
WidgetScrollUpdateThumbs(*this, WIDX_GRAPH);
|
||
}
|
||
|
||
ScreenSize GraphsScrollGetSize(int32_t scrollIndex)
|
||
{
|
||
OnPrepareDraw();
|
||
|
||
ScreenSize size{};
|
||
// Set minimum size
|
||
size.width = widgets[WIDX_GRAPH].width() - 2;
|
||
|
||
// Get measurement size
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
RideMeasurement* measurement{};
|
||
std::tie(measurement, std::ignore) = ride->GetMeasurement();
|
||
if (measurement != nullptr)
|
||
{
|
||
size.width = std::max<int32_t>(size.width, measurement->num_items);
|
||
}
|
||
}
|
||
return size;
|
||
}
|
||
|
||
void GraphsOnScrollSelect(int32_t scrollIndex, int32_t scrollAreaType)
|
||
{
|
||
_autoScrollGraph = false;
|
||
}
|
||
|
||
OpenRCT2String GraphsTooltip(const WidgetIndex widgetIndex, const StringId fallback)
|
||
{
|
||
if (widgetIndex == WIDX_GRAPH)
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
auto [measurement, message] = ride->GetMeasurement();
|
||
if (measurement != nullptr && (measurement->flags & RIDE_MEASUREMENT_FLAG_RUNNING))
|
||
{
|
||
auto ft = Formatter();
|
||
ft.Increment(2);
|
||
ft.Add<StringId>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).number);
|
||
ft.Add<uint16_t>(measurement->vehicle_index + 1);
|
||
return { fallback, ft };
|
||
}
|
||
|
||
return message;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return { STR_NONE, {} };
|
||
}
|
||
return { fallback, {} };
|
||
}
|
||
|
||
void GraphsOnPrepareDraw()
|
||
{
|
||
auto newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto ft = Formatter::Common();
|
||
ride->FormatNameTo(ft);
|
||
|
||
// Set pressed graph button type
|
||
pressed_widgets &= ~(1uLL << WIDX_GRAPH_VELOCITY);
|
||
pressed_widgets &= ~(1uLL << WIDX_GRAPH_ALTITUDE);
|
||
pressed_widgets &= ~(1uLL << WIDX_GRAPH_VERTICAL);
|
||
pressed_widgets &= ~(1uLL << WIDX_GRAPH_LATERAL);
|
||
pressed_widgets |= (1LL << (WIDX_GRAPH_VELOCITY + list_information_type));
|
||
|
||
// Hide graph buttons that are not applicable
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES))
|
||
{
|
||
widgets[WIDX_GRAPH_VERTICAL].type = WindowWidgetType::Button;
|
||
widgets[WIDX_GRAPH_LATERAL].type = WindowWidgetType::Button;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_GRAPH_VERTICAL].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_GRAPH_LATERAL].type = WindowWidgetType::Empty;
|
||
}
|
||
|
||
// Anchor graph widget
|
||
auto x = width - 4;
|
||
auto y = height - kButtonFaceHeight - 8;
|
||
|
||
widgets[WIDX_GRAPH].right = x;
|
||
widgets[WIDX_GRAPH].bottom = y;
|
||
y += 3;
|
||
widgets[WIDX_GRAPH_VELOCITY].top = y;
|
||
widgets[WIDX_GRAPH_ALTITUDE].top = y;
|
||
widgets[WIDX_GRAPH_VERTICAL].top = y;
|
||
widgets[WIDX_GRAPH_LATERAL].top = y;
|
||
y += kButtonFaceHeight + 1;
|
||
widgets[WIDX_GRAPH_VELOCITY].bottom = y;
|
||
widgets[WIDX_GRAPH_ALTITUDE].bottom = y;
|
||
widgets[WIDX_GRAPH_VERTICAL].bottom = y;
|
||
widgets[WIDX_GRAPH_LATERAL].bottom = y;
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
}
|
||
|
||
void GraphsOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
}
|
||
|
||
void GraphsOnScrollDraw(DrawPixelInfo& dpi, int32_t scrollIndex)
|
||
{
|
||
GfxClear(dpi, ColourMapA[COLOUR_SATURATED_GREEN].darker);
|
||
|
||
auto widget = &widgets[WIDX_GRAPH];
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
{
|
||
return;
|
||
}
|
||
|
||
auto [measurement, message] = ride->GetMeasurement();
|
||
|
||
if (measurement == nullptr)
|
||
{
|
||
// No measurement message
|
||
ScreenCoordsXY stringCoords(widget->width() / 2, widget->height() / 2 - 5);
|
||
int32_t txtWidth = widget->width() - 2;
|
||
DrawTextWrapped(dpi, stringCoords, txtWidth, message.str, message.args, { TextAlignment::CENTRE });
|
||
return;
|
||
}
|
||
|
||
// Vertical grid lines
|
||
const uint8_t lightColour = ColourMapA[COLOUR_SATURATED_GREEN].mid_light;
|
||
const uint8_t darkColour = ColourMapA[COLOUR_SATURATED_GREEN].mid_dark;
|
||
|
||
int32_t time = 0;
|
||
for (int32_t x = 0; x < dpi.x + dpi.width; x += 80)
|
||
{
|
||
if (x + 80 >= dpi.x)
|
||
{
|
||
auto coord1 = ScreenCoordsXY{ x, dpi.y };
|
||
auto coord2 = ScreenCoordsXY{ x, dpi.y + dpi.height - 1 };
|
||
GfxFillRect(dpi, { coord1, coord2 }, lightColour);
|
||
GfxFillRect(dpi, { coord1 + ScreenCoordsXY{ 16, 0 }, coord2 + ScreenCoordsXY{ 16, 0 } }, darkColour);
|
||
GfxFillRect(dpi, { coord1 + ScreenCoordsXY{ 32, 0 }, coord2 + ScreenCoordsXY{ 32, 0 } }, darkColour);
|
||
GfxFillRect(dpi, { coord1 + ScreenCoordsXY{ 48, 0 }, coord2 + ScreenCoordsXY{ 48, 0 } }, darkColour);
|
||
GfxFillRect(dpi, { coord1 + ScreenCoordsXY{ 64, 0 }, coord2 + ScreenCoordsXY{ 64, 0 } }, darkColour);
|
||
}
|
||
time += 5;
|
||
}
|
||
|
||
// Horizontal grid lines
|
||
int32_t listType = list_information_type;
|
||
int16_t yUnit = GraphsYAxisDetails[listType].unit;
|
||
StringId stringID = GraphsYAxisDetails[listType].label;
|
||
int16_t yUnitInterval = GraphsYAxisDetails[listType].unit_interval;
|
||
int16_t yInterval = GraphsYAxisDetails[listType].interval;
|
||
|
||
// Scale modifier
|
||
if (listType == GRAPH_ALTITUDE)
|
||
{
|
||
yUnit -= kMapBaseZ * 3;
|
||
}
|
||
|
||
for (int32_t y = widget->height() - 13; y >= 8; y -= yInterval, yUnit += yUnitInterval)
|
||
{
|
||
// Minor / major line
|
||
int32_t colour = yUnit == 0 ? lightColour : darkColour;
|
||
GfxFillRect(dpi, { { dpi.x, y }, { dpi.x + dpi.width - 1, y } }, colour);
|
||
|
||
int16_t scaled_yUnit = yUnit;
|
||
// Scale modifier
|
||
if (listType == GRAPH_ALTITUDE)
|
||
scaled_yUnit /= 2;
|
||
|
||
auto ft = Formatter();
|
||
ft.Add<int16_t>(scaled_yUnit);
|
||
|
||
DrawTextBasic(dpi, { scrolls[0].h_left + 1, y - 4 }, stringID, ft, { FontStyle::Small });
|
||
}
|
||
|
||
// Time marks
|
||
time = 0;
|
||
for (int32_t x = 0; x < dpi.x + dpi.width; x += 80)
|
||
{
|
||
auto ft = Formatter();
|
||
ft.Add<int32_t>(time);
|
||
if (x + 80 >= dpi.x)
|
||
DrawTextBasic(dpi, { x + 2, 1 }, STR_RIDE_STATS_TIME, ft, { FontStyle::Small });
|
||
time += 5;
|
||
}
|
||
|
||
// Plot
|
||
int32_t x = dpi.x;
|
||
int32_t firstPoint, secondPoint;
|
||
// Uses the force limits (used to draw extreme G's in red on measurement tab) to determine if line should be drawn
|
||
// red.
|
||
int32_t intensityThresholdPositive = 0;
|
||
int32_t intensityThresholdNegative = 0;
|
||
for (int32_t graphWidth = 0; graphWidth < dpi.width; graphWidth++, x++)
|
||
{
|
||
if (x < 0 || x >= measurement->num_items - 1)
|
||
continue;
|
||
|
||
constexpr int32_t VerticalGraphHeightOffset = 39;
|
||
constexpr int32_t LateralGraphHeightOffset = 52;
|
||
|
||
switch (listType)
|
||
{
|
||
case GRAPH_VELOCITY:
|
||
firstPoint = measurement->velocity[x] / 2;
|
||
secondPoint = measurement->velocity[x + 1] / 2;
|
||
break;
|
||
case GRAPH_ALTITUDE:
|
||
firstPoint = measurement->altitude[x];
|
||
secondPoint = measurement->altitude[x + 1];
|
||
break;
|
||
case GRAPH_VERTICAL:
|
||
firstPoint = measurement->vertical[x] + VerticalGraphHeightOffset;
|
||
secondPoint = measurement->vertical[x + 1] + VerticalGraphHeightOffset;
|
||
intensityThresholdNegative = (RIDE_G_FORCES_RED_NEG_VERTICAL / 8) + VerticalGraphHeightOffset;
|
||
break;
|
||
case GRAPH_LATERAL:
|
||
firstPoint = measurement->lateral[x] + LateralGraphHeightOffset;
|
||
secondPoint = measurement->lateral[x + 1] + LateralGraphHeightOffset;
|
||
intensityThresholdPositive = (RIDE_G_FORCES_RED_LATERAL / 8) + LateralGraphHeightOffset;
|
||
intensityThresholdNegative = -(RIDE_G_FORCES_RED_LATERAL / 8) + LateralGraphHeightOffset;
|
||
break;
|
||
default:
|
||
LOG_ERROR("Wrong graph type %d", listType);
|
||
firstPoint = secondPoint = 0;
|
||
break;
|
||
}
|
||
|
||
// Adjust line to match graph widget position.
|
||
firstPoint = widget->height() - firstPoint - 13;
|
||
secondPoint = widget->height() - secondPoint - 13;
|
||
if (firstPoint > secondPoint)
|
||
{
|
||
std::swap(firstPoint, secondPoint);
|
||
}
|
||
|
||
// Adjust threshold line position as well
|
||
if (listType == GRAPH_VERTICAL || listType == GRAPH_LATERAL)
|
||
{
|
||
intensityThresholdPositive = widget->height() - intensityThresholdPositive - 13;
|
||
intensityThresholdNegative = widget->height() - intensityThresholdNegative - 13;
|
||
}
|
||
|
||
const bool previousMeasurement = x > measurement->current_item;
|
||
|
||
// Draw the current line in grey.
|
||
GfxFillRect(
|
||
dpi, { { x, firstPoint }, { x, secondPoint } }, previousMeasurement ? PALETTE_INDEX_17 : PALETTE_INDEX_21);
|
||
|
||
// Draw red over extreme values (if supported by graph type).
|
||
if (listType == GRAPH_VERTICAL || listType == GRAPH_LATERAL)
|
||
{
|
||
const auto redLineColour = previousMeasurement ? PALETTE_INDEX_171 : PALETTE_INDEX_173;
|
||
|
||
// Line exceeds negative threshold (at bottom of graph).
|
||
if (secondPoint >= intensityThresholdNegative)
|
||
{
|
||
const auto redLineTop = ScreenCoordsXY{ x, std::max(firstPoint, intensityThresholdNegative) };
|
||
const auto redLineBottom = ScreenCoordsXY{ x, std::max(secondPoint, intensityThresholdNegative) };
|
||
GfxFillRect(dpi, { redLineTop, redLineBottom }, redLineColour);
|
||
}
|
||
|
||
// Line exceeds positive threshold (at top of graph).
|
||
if (listType == GRAPH_LATERAL && firstPoint < intensityThresholdPositive)
|
||
{
|
||
const auto redLineTop = ScreenCoordsXY{ x, std::min(firstPoint, intensityThresholdPositive) };
|
||
const auto redLineBottom = ScreenCoordsXY{ x, std::min(secondPoint, intensityThresholdPositive) };
|
||
GfxFillRect(dpi, { redLineTop, redLineBottom }, redLineColour);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Income
|
||
|
||
static void UpdateSamePriceThroughoutFlags(ShopItem shop_item)
|
||
{
|
||
const auto& gameState = GetGameState();
|
||
const auto existingFlags = gameState.SamePriceThroughoutPark;
|
||
|
||
auto newFlags = existingFlags;
|
||
if (GetShopItemDescriptor(shop_item).IsPhoto())
|
||
{
|
||
if (existingFlags & EnumToFlag(shop_item))
|
||
newFlags &= ~EnumsToFlags(ShopItem::Photo, ShopItem::Photo2, ShopItem::Photo3, ShopItem::Photo4);
|
||
else
|
||
newFlags |= EnumsToFlags(ShopItem::Photo, ShopItem::Photo2, ShopItem::Photo3, ShopItem::Photo4);
|
||
}
|
||
else
|
||
{
|
||
newFlags ^= EnumToFlag(shop_item);
|
||
}
|
||
|
||
auto parkSetParameter = ParkSetParameterAction(ParkParameter::SamePriceInPark, newFlags);
|
||
GameActions::Execute(&parkSetParameter);
|
||
}
|
||
|
||
void IncomeTogglePrimaryPrice()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
ShopItem shopItem;
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET))
|
||
{
|
||
shopItem = ShopItem::Admission;
|
||
}
|
||
else
|
||
{
|
||
auto rideEntry = GetRideEntryByIndex(ride->subtype);
|
||
if (rideEntry != nullptr)
|
||
{
|
||
shopItem = rideEntry->shop_item[0];
|
||
if (shopItem == ShopItem::None)
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
UpdateSamePriceThroughoutFlags(shopItem);
|
||
|
||
auto rideSetPriceAction = RideSetPriceAction(rideId, ride->price[0], true);
|
||
GameActions::Execute(&rideSetPriceAction);
|
||
}
|
||
|
||
void IncomeToggleSecondaryPrice()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = GetRideEntryByIndex(ride->subtype);
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
auto shop_item = rideEntry->shop_item[1];
|
||
if (shop_item == ShopItem::None)
|
||
shop_item = ride->GetRideTypeDescriptor().PhotoItem;
|
||
|
||
UpdateSamePriceThroughoutFlags(shop_item);
|
||
|
||
auto rideSetPriceAction = RideSetPriceAction(rideId, ride->price[1], false);
|
||
GameActions::Execute(&rideSetPriceAction);
|
||
}
|
||
|
||
void IncomeSetPrimaryPrice(money64 price)
|
||
{
|
||
auto rideSetPriceAction = RideSetPriceAction(rideId, price, true);
|
||
GameActions::Execute(&rideSetPriceAction);
|
||
}
|
||
|
||
void IncomeIncreasePrimaryPrice()
|
||
{
|
||
if (!IncomeCanModifyPrimaryPrice())
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto price = ride->price[0];
|
||
if (price < 20.00_GBP)
|
||
price++;
|
||
|
||
IncomeSetPrimaryPrice(price);
|
||
}
|
||
|
||
void IncomeDecreasePrimaryPrice()
|
||
{
|
||
if (!IncomeCanModifyPrimaryPrice())
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto price = ride->price[0];
|
||
if (price > 0.00_GBP)
|
||
price--;
|
||
|
||
IncomeSetPrimaryPrice(price);
|
||
}
|
||
|
||
money64 IncomeGetSecondaryPrice()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return 0;
|
||
|
||
return ride->price[1];
|
||
}
|
||
|
||
void IncomeSetSecondaryPrice(money64 price)
|
||
{
|
||
auto rideSetPriceAction = RideSetPriceAction(rideId, price, false);
|
||
GameActions::Execute(&rideSetPriceAction);
|
||
}
|
||
|
||
bool IncomeCanModifyPrimaryPrice()
|
||
{
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return false;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
return Park::RidePricesUnlocked() || rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET)
|
||
|| (rideEntry != nullptr && rideEntry->shop_item[0] != ShopItem::None);
|
||
}
|
||
|
||
void IncomeIncreaseSecondaryPrice()
|
||
{
|
||
auto price = IncomeGetSecondaryPrice();
|
||
|
||
if (price < 20.00_GBP)
|
||
price++;
|
||
|
||
IncomeSetSecondaryPrice(price);
|
||
}
|
||
|
||
void IncomeDecreaseSecondaryPrice()
|
||
{
|
||
auto price = IncomeGetSecondaryPrice();
|
||
|
||
if (price > 0.00_GBP)
|
||
price--;
|
||
|
||
IncomeSetSecondaryPrice(price);
|
||
}
|
||
|
||
void IncomeOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
utf8 _moneyInputText[MONEY_STRING_MAXLENGTH] = {};
|
||
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_PRIMARY_PRICE:
|
||
{
|
||
if (!IncomeCanModifyPrimaryPrice())
|
||
return;
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
MoneyToString(ride->price[0], _moneyInputText, MONEY_STRING_MAXLENGTH, true);
|
||
WindowTextInputRawOpen(
|
||
this, WIDX_PRIMARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, {}, _moneyInputText,
|
||
MONEY_STRING_MAXLENGTH);
|
||
}
|
||
break;
|
||
}
|
||
case WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK:
|
||
IncomeTogglePrimaryPrice();
|
||
break;
|
||
case WIDX_SECONDARY_PRICE:
|
||
{
|
||
auto price64 = IncomeGetSecondaryPrice();
|
||
|
||
MoneyToString(price64, _moneyInputText, MONEY_STRING_MAXLENGTH, true);
|
||
WindowTextInputRawOpen(
|
||
this, WIDX_SECONDARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, {}, _moneyInputText,
|
||
MONEY_STRING_MAXLENGTH);
|
||
}
|
||
break;
|
||
case WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK:
|
||
IncomeToggleSecondaryPrice();
|
||
break;
|
||
}
|
||
}
|
||
|
||
void IncomeResize()
|
||
{
|
||
WindowSetResize(*this, 316, 194, 316, 194);
|
||
}
|
||
|
||
void IncomeOnMouseDown(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_PRIMARY_PRICE_INCREASE:
|
||
IncomeIncreasePrimaryPrice();
|
||
break;
|
||
case WIDX_PRIMARY_PRICE_DECREASE:
|
||
IncomeDecreasePrimaryPrice();
|
||
break;
|
||
case WIDX_SECONDARY_PRICE_INCREASE:
|
||
IncomeIncreaseSecondaryPrice();
|
||
break;
|
||
case WIDX_SECONDARY_PRICE_DECREASE:
|
||
IncomeDecreaseSecondaryPrice();
|
||
break;
|
||
}
|
||
}
|
||
|
||
void IncomeUpdate()
|
||
{
|
||
frame_no++;
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_9);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_INCOME)
|
||
{
|
||
ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_INCOME;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void IncomeOnTextInput(WidgetIndex widgetIndex, std::string_view text)
|
||
{
|
||
if ((widgetIndex != WIDX_PRIMARY_PRICE && widgetIndex != WIDX_SECONDARY_PRICE) || text.empty())
|
||
return;
|
||
|
||
std::string strText{ text };
|
||
money64 price = StringToMoney(strText.c_str());
|
||
if (price == kMoney64Undefined)
|
||
{
|
||
return;
|
||
}
|
||
|
||
price = std::clamp(price, 0.00_GBP, 20.00_GBP);
|
||
|
||
if (widgetIndex == WIDX_PRIMARY_PRICE)
|
||
{
|
||
IncomeSetPrimaryPrice(price);
|
||
}
|
||
else
|
||
{
|
||
IncomeSetSecondaryPrice(price);
|
||
}
|
||
}
|
||
|
||
void IncomeOnPrepareDraw()
|
||
{
|
||
auto newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
widgets[WIDX_TITLE].text = STR_ARG_18_STRINGID;
|
||
|
||
auto ft = Formatter::Common();
|
||
ft.Increment(18);
|
||
ride->FormatNameTo(ft);
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
// Primary item
|
||
pressed_widgets &= ~(1uLL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK);
|
||
disabled_widgets &= ~(1uLL << WIDX_PRIMARY_PRICE);
|
||
|
||
widgets[WIDX_PRIMARY_PRICE_LABEL].tooltip = STR_NONE;
|
||
widgets[WIDX_PRIMARY_PRICE].tooltip = STR_NONE;
|
||
|
||
// If ride prices are locked, do not allow setting the price, unless we're dealing with a shop or toilet.
|
||
const auto& rtd = ride->GetRideTypeDescriptor();
|
||
if (!Park::RidePricesUnlocked() && rideEntry->shop_item[0] == ShopItem::None
|
||
&& !rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET))
|
||
{
|
||
disabled_widgets |= (1uLL << WIDX_PRIMARY_PRICE);
|
||
widgets[WIDX_PRIMARY_PRICE_LABEL].tooltip = STR_RIDE_INCOME_ADMISSION_PAY_FOR_ENTRY_TIP;
|
||
widgets[WIDX_PRIMARY_PRICE].tooltip = STR_RIDE_INCOME_ADMISSION_PAY_FOR_ENTRY_TIP;
|
||
}
|
||
|
||
widgets[WIDX_PRIMARY_PRICE_LABEL].text = STR_RIDE_INCOME_ADMISSION_PRICE;
|
||
widgets[WIDX_SECONDARY_PRICE_LABEL].text = STR_SHOP_ITEM_PRICE_LABEL_ON_RIDE_PHOTO;
|
||
widgets[WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Empty;
|
||
|
||
widgets[WIDX_PRIMARY_PRICE].text = STR_BOTTOM_TOOLBAR_CASH;
|
||
auto ridePrimaryPrice = RideGetPrice(*ride);
|
||
ft.Rewind();
|
||
ft.Add<money64>(ridePrimaryPrice);
|
||
if (ridePrimaryPrice == 0)
|
||
widgets[WIDX_PRIMARY_PRICE].text = STR_FREE;
|
||
|
||
ShopItem primaryItem = ShopItem::Admission;
|
||
if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET) || ((primaryItem = rideEntry->shop_item[0]) != ShopItem::None))
|
||
{
|
||
widgets[WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Checkbox;
|
||
|
||
if (ShopItemHasCommonPrice(primaryItem))
|
||
pressed_widgets |= (1uLL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK);
|
||
|
||
widgets[WIDX_PRIMARY_PRICE_LABEL].text = GetShopItemDescriptor(primaryItem).Naming.PriceLabel;
|
||
}
|
||
|
||
// Get secondary item
|
||
auto secondaryItem = ride->GetRideTypeDescriptor().PhotoItem;
|
||
if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO))
|
||
{
|
||
if ((secondaryItem = rideEntry->shop_item[1]) != ShopItem::None)
|
||
{
|
||
widgets[WIDX_SECONDARY_PRICE_LABEL].text = GetShopItemDescriptor(secondaryItem).Naming.PriceLabel;
|
||
}
|
||
}
|
||
|
||
if (secondaryItem == ShopItem::None)
|
||
{
|
||
// Hide secondary item widgets
|
||
widgets[WIDX_SECONDARY_PRICE_LABEL].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_SECONDARY_PRICE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_SECONDARY_PRICE_INCREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_SECONDARY_PRICE_DECREASE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Empty;
|
||
}
|
||
else
|
||
{
|
||
// Set same price throughout park checkbox
|
||
pressed_widgets &= ~(1uLL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK);
|
||
if (ShopItemHasCommonPrice(secondaryItem))
|
||
pressed_widgets |= (1uLL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK);
|
||
|
||
// Show widgets
|
||
widgets[WIDX_SECONDARY_PRICE_LABEL].type = WindowWidgetType::Label;
|
||
widgets[WIDX_SECONDARY_PRICE].type = WindowWidgetType::Spinner;
|
||
widgets[WIDX_SECONDARY_PRICE_INCREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_SECONDARY_PRICE_DECREASE].type = WindowWidgetType::Button;
|
||
widgets[WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Checkbox;
|
||
|
||
// Set secondary item price
|
||
widgets[WIDX_SECONDARY_PRICE].text = STR_RIDE_SECONDARY_PRICE_VALUE;
|
||
ft.Rewind();
|
||
ft.Increment(10);
|
||
ft.Add<money64>(ride->price[1]);
|
||
if (ride->price[1] == 0)
|
||
widgets[WIDX_SECONDARY_PRICE].text = STR_FREE;
|
||
}
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
}
|
||
|
||
void IncomeOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
StringId stringId;
|
||
money64 profit;
|
||
ShopItem primaryItem, secondaryItem;
|
||
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto rideEntry = ride->GetRideEntry();
|
||
if (rideEntry == nullptr)
|
||
return;
|
||
|
||
auto screenCoords = windowPos
|
||
+ ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 33 };
|
||
|
||
// Primary item profit / loss per item sold
|
||
primaryItem = rideEntry->shop_item[0];
|
||
if (primaryItem != ShopItem::None)
|
||
{
|
||
profit = ride->price[0];
|
||
|
||
stringId = STR_PROFIT_PER_ITEM_SOLD;
|
||
profit -= GetShopItemDescriptor(primaryItem).Cost;
|
||
if (profit < 0)
|
||
{
|
||
profit *= -1;
|
||
stringId = STR_LOSS_PER_ITEM_SOLD;
|
||
}
|
||
|
||
auto ft = Formatter();
|
||
ft.Add<money64>(profit);
|
||
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
}
|
||
screenCoords.y += 44;
|
||
|
||
// Secondary item profit / loss per item sold
|
||
secondaryItem = ride->GetRideTypeDescriptor().PhotoItem;
|
||
if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO))
|
||
secondaryItem = rideEntry->shop_item[1];
|
||
|
||
if (secondaryItem != ShopItem::None)
|
||
{
|
||
profit = ride->price[1];
|
||
|
||
stringId = STR_PROFIT_PER_ITEM_SOLD;
|
||
profit -= GetShopItemDescriptor(secondaryItem).Cost;
|
||
if (profit < 0)
|
||
{
|
||
profit *= -1;
|
||
stringId = STR_LOSS_PER_ITEM_SOLD;
|
||
}
|
||
|
||
auto ft = Formatter();
|
||
ft.Add<money64>(profit);
|
||
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
}
|
||
screenCoords.y += 18;
|
||
|
||
// Income per hour
|
||
if (ride->income_per_hour != kMoney64Undefined)
|
||
{
|
||
auto ft = Formatter();
|
||
ft.Add<money64>(ride->income_per_hour);
|
||
|
||
DrawTextBasic(dpi, screenCoords, STR_INCOME_PER_HOUR, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
|
||
// Running cost per hour
|
||
money64 costPerHour = ride->upkeep_cost * 16;
|
||
stringId = ride->upkeep_cost == kMoney64Undefined ? STR_RUNNING_COST_UNKNOWN : STR_RUNNING_COST_PER_HOUR;
|
||
auto ft = Formatter();
|
||
ft.Add<money64>(costPerHour);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Profit per hour
|
||
if (ride->profit != kMoney64Undefined)
|
||
{
|
||
ft = Formatter();
|
||
ft.Add<money64>(ride->profit);
|
||
DrawTextBasic(dpi, screenCoords, STR_PROFIT_PER_HOUR, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
screenCoords.y += 5;
|
||
|
||
// Total profit
|
||
ft = Formatter();
|
||
ft.Add<money64>(ride->total_profit);
|
||
DrawTextBasic(dpi, screenCoords, STR_TOTAL_PROFIT, ft);
|
||
}
|
||
|
||
#pragma endregion
|
||
|
||
#pragma region Customer
|
||
|
||
void CustomerOnMouseUp(WidgetIndex widgetIndex)
|
||
{
|
||
switch (widgetIndex)
|
||
{
|
||
case WIDX_CLOSE:
|
||
Close();
|
||
return;
|
||
case WIDX_TAB_1:
|
||
case WIDX_TAB_2:
|
||
case WIDX_TAB_3:
|
||
case WIDX_TAB_4:
|
||
case WIDX_TAB_5:
|
||
case WIDX_TAB_6:
|
||
case WIDX_TAB_7:
|
||
case WIDX_TAB_8:
|
||
case WIDX_TAB_9:
|
||
case WIDX_TAB_10:
|
||
SetPage(widgetIndex - WIDX_TAB_1);
|
||
break;
|
||
case WIDX_SHOW_GUESTS_THOUGHTS:
|
||
{
|
||
auto intent = Intent(WindowClass::GuestList);
|
||
intent.PutExtra(
|
||
INTENT_EXTRA_GUEST_LIST_FILTER, static_cast<int32_t>(GuestListFilterType::GuestsThinkingAboutRide));
|
||
intent.PutExtra(INTENT_EXTRA_RIDE_ID, number);
|
||
ContextOpenIntent(&intent);
|
||
break;
|
||
}
|
||
case WIDX_SHOW_GUESTS_ON_RIDE:
|
||
{
|
||
auto intent = Intent(WindowClass::GuestList);
|
||
intent.PutExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast<int32_t>(GuestListFilterType::GuestsOnRide));
|
||
intent.PutExtra(INTENT_EXTRA_RIDE_ID, number);
|
||
ContextOpenIntent(&intent);
|
||
break;
|
||
}
|
||
case WIDX_SHOW_GUESTS_QUEUING:
|
||
{
|
||
auto intent = Intent(WindowClass::GuestList);
|
||
intent.PutExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast<int32_t>(GuestListFilterType::GuestsInQueue));
|
||
intent.PutExtra(INTENT_EXTRA_RIDE_ID, number);
|
||
ContextOpenIntent(&intent);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
void CustomerResize()
|
||
{
|
||
flags |= WF_RESIZABLE;
|
||
WindowSetResize(*this, 316, 163, 316, 163);
|
||
}
|
||
|
||
void CustomerUpdate()
|
||
{
|
||
picked_peep_frame++;
|
||
if (picked_peep_frame >= 24)
|
||
picked_peep_frame = 0;
|
||
|
||
OnPrepareDraw();
|
||
WidgetInvalidate(*this, WIDX_TAB_10);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_CUSTOMER)
|
||
{
|
||
ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_CUSTOMER;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
void CustomerOnPrepareDraw()
|
||
{
|
||
auto newWidgets = PageWidgets[page];
|
||
if (widgets != newWidgets)
|
||
{
|
||
widgets = newWidgets;
|
||
InitScrollWidgets();
|
||
}
|
||
|
||
SetPressedTab();
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride != nullptr)
|
||
{
|
||
auto ft = Formatter::Common();
|
||
ride->FormatNameTo(ft);
|
||
|
||
widgets[WIDX_SHOW_GUESTS_THOUGHTS].type = WindowWidgetType::FlatBtn;
|
||
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY))
|
||
{
|
||
widgets[WIDX_SHOW_GUESTS_ON_RIDE].type = WindowWidgetType::Empty;
|
||
widgets[WIDX_SHOW_GUESTS_QUEUING].type = WindowWidgetType::Empty;
|
||
}
|
||
else
|
||
{
|
||
widgets[WIDX_SHOW_GUESTS_ON_RIDE].type = WindowWidgetType::FlatBtn;
|
||
widgets[WIDX_SHOW_GUESTS_QUEUING].type = WindowWidgetType::FlatBtn;
|
||
}
|
||
|
||
AnchorBorderWidgets();
|
||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_10);
|
||
}
|
||
}
|
||
|
||
void CustomerOnDraw(DrawPixelInfo& dpi)
|
||
{
|
||
ShopItem shopItem;
|
||
int16_t popularity, satisfaction, queueTime;
|
||
StringId stringId;
|
||
|
||
DrawWidgets(dpi);
|
||
DrawTabImages(dpi);
|
||
|
||
auto ride = GetRide(rideId);
|
||
if (ride == nullptr)
|
||
return;
|
||
|
||
auto screenCoords = windowPos
|
||
+ ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 };
|
||
|
||
// Customers currently on ride
|
||
if (ride->IsRide())
|
||
{
|
||
auto ft = Formatter();
|
||
ft.Add<int16_t>(ride->num_riders);
|
||
DrawTextBasic(dpi, screenCoords, STR_CUSTOMERS_ON_RIDE, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
|
||
// Customers per hour
|
||
auto ft = Formatter();
|
||
ft.Add<int32_t>(RideCustomersPerHour(*ride));
|
||
DrawTextBasic(dpi, screenCoords, STR_CUSTOMERS_PER_HOUR, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Popularity
|
||
popularity = ride->popularity;
|
||
if (popularity == 255)
|
||
{
|
||
stringId = STR_POPULARITY_UNKNOWN;
|
||
}
|
||
else
|
||
{
|
||
stringId = STR_POPULARITY_PERCENT;
|
||
popularity *= 4;
|
||
}
|
||
ft = Formatter();
|
||
ft.Add<int16_t>(popularity);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Satisfaction
|
||
satisfaction = ride->satisfaction;
|
||
if (satisfaction == 255)
|
||
{
|
||
stringId = STR_SATISFACTION_UNKNOWN;
|
||
}
|
||
else
|
||
{
|
||
stringId = STR_SATISFACTION_PERCENT;
|
||
satisfaction *= 5;
|
||
}
|
||
ft = Formatter();
|
||
ft.Add<int16_t>(satisfaction);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Queue time
|
||
if (ride->IsRide())
|
||
{
|
||
queueTime = ride->GetMaxQueueTime();
|
||
stringId = queueTime == 1 ? STR_QUEUE_TIME_MINUTE : STR_QUEUE_TIME_MINUTES;
|
||
ft = Formatter();
|
||
ft.Add<int32_t>(queueTime);
|
||
screenCoords.y += DrawTextWrapped(dpi, screenCoords, 308, stringId, ft, { TextAlignment::LEFT });
|
||
screenCoords.y += 5;
|
||
}
|
||
|
||
// Primary shop items sold
|
||
shopItem = ride->GetRideEntry()->shop_item[0];
|
||
if (shopItem != ShopItem::None)
|
||
{
|
||
ft = Formatter();
|
||
ft.Add<StringId>(GetShopItemDescriptor(shopItem).Naming.Plural);
|
||
ft.Add<uint32_t>(ride->no_primary_items_sold);
|
||
DrawTextBasic(dpi, screenCoords, STR_ITEMS_SOLD, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
|
||
// Secondary shop items sold / on-ride photos sold
|
||
shopItem = (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO) ? ride->GetRideTypeDescriptor().PhotoItem
|
||
: ride->GetRideEntry()->shop_item[1];
|
||
if (shopItem != ShopItem::None)
|
||
{
|
||
ft = Formatter();
|
||
ft.Add<StringId>(GetShopItemDescriptor(shopItem).Naming.Plural);
|
||
ft.Add<uint32_t>(ride->no_secondary_items_sold);
|
||
DrawTextBasic(dpi, screenCoords, STR_ITEMS_SOLD, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
|
||
// Total customers
|
||
ft = Formatter();
|
||
ft.Add<uint32_t>(ride->total_customers);
|
||
DrawTextBasic(dpi, screenCoords, STR_TOTAL_CUSTOMERS, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
|
||
// Guests favourite
|
||
if (ride->IsRide())
|
||
{
|
||
ft = Formatter();
|
||
ft.Add<uint32_t>(ride->guests_favourite);
|
||
stringId = ride->guests_favourite == 1 ? STR_FAVOURITE_RIDE_OF_GUEST : STR_FAVOURITE_RIDE_OF_GUESTS;
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
screenCoords.y += kListRowHeight;
|
||
}
|
||
screenCoords.y += 2;
|
||
|
||
// Age
|
||
// If the ride has a build date that is in the future, show it as built this year.
|
||
int16_t age = std::max(DateGetYear(ride->GetAge()), 0);
|
||
stringId = age == 0 ? STR_BUILT_THIS_YEAR : age == 1 ? STR_BUILT_LAST_YEAR : STR_BUILT_YEARS_AGO;
|
||
ft = Formatter();
|
||
ft.Add<int16_t>(age);
|
||
DrawTextBasic(dpi, screenCoords, stringId, ft);
|
||
}
|
||
|
||
#pragma endregion
|
||
};
|
||
|
||
/**
|
||
*
|
||
* rct2: 0x006AEAB4
|
||
*/
|
||
static RideWindow* WindowRideOpen(const Ride& ride)
|
||
{
|
||
return WindowCreate<RideWindow>(WindowClass::Ride, 316, 207, WF_10 | WF_RESIZABLE, ride);
|
||
}
|
||
|
||
/**
|
||
*
|
||
* rct2: 0x006ACC28
|
||
*/
|
||
WindowBase* RideMainOpen(const Ride& ride)
|
||
{
|
||
if (ride.type >= RIDE_TYPE_COUNT)
|
||
{
|
||
return nullptr;
|
||
}
|
||
|
||
RideWindow* w = static_cast<RideWindow*>(WindowBringToFrontByNumber(WindowClass::Ride, ride.id.ToUnderlying()));
|
||
if (w == nullptr)
|
||
{
|
||
w = WindowRideOpen(ride);
|
||
w->SetViewIndex(0);
|
||
}
|
||
else if (w->GetViewIndex() >= (1 + ride.NumTrains + ride.num_stations))
|
||
{
|
||
w->SetViewIndex(0);
|
||
}
|
||
|
||
if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE))
|
||
{
|
||
if (w->classification == gCurrentToolWidget.window_classification && w->number == gCurrentToolWidget.window_number)
|
||
{
|
||
ToolCancel();
|
||
}
|
||
}
|
||
|
||
if (w->page != WINDOW_RIDE_PAGE_MAIN)
|
||
{
|
||
w->SetPage(WINDOW_RIDE_PAGE_MAIN);
|
||
}
|
||
|
||
w->OnViewportRotate();
|
||
return w;
|
||
}
|
||
|
||
/**
|
||
*
|
||
* rct2: 0x006ACCCE
|
||
*/
|
||
static WindowBase* WindowRideOpenStation(const Ride& ride, StationIndex stationIndex)
|
||
{
|
||
if (ride.type >= RIDE_TYPE_COUNT)
|
||
return nullptr;
|
||
|
||
if (ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
|
||
return RideMainOpen(ride);
|
||
|
||
auto* w = static_cast<RideWindow*>(WindowBringToFrontByNumber(WindowClass::Ride, ride.id.ToUnderlying()));
|
||
if (w == nullptr)
|
||
{
|
||
w = WindowRideOpen(ride);
|
||
}
|
||
|
||
if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == w->classification
|
||
&& gCurrentToolWidget.window_number == w->number)
|
||
{
|
||
ToolCancel();
|
||
}
|
||
|
||
// View
|
||
for (int32_t i = stationIndex.ToUnderlying(); i >= 0; i--)
|
||
{
|
||
if (ride.GetStations()[i].Start.IsNull())
|
||
{
|
||
stationIndex = StationIndex::FromUnderlying(stationIndex.ToUnderlying() - 1);
|
||
}
|
||
}
|
||
|
||
w->SetViewIndex(1 + ride.NumTrains + stationIndex.ToUnderlying());
|
||
|
||
return w;
|
||
}
|
||
|
||
WindowBase* RideOpenTrack(TileElement* tileElement)
|
||
{
|
||
assert(tileElement != nullptr);
|
||
auto rideIndex = tileElement->GetRideIndex();
|
||
if (!rideIndex.IsNull())
|
||
{
|
||
auto ride = GetRide(rideIndex);
|
||
if (ride != nullptr)
|
||
{
|
||
const auto type = tileElement->GetType();
|
||
if (type == TileElementType::Entrance)
|
||
{
|
||
// Open ride window in station view
|
||
auto entranceElement = tileElement->AsEntrance();
|
||
auto stationIndex = entranceElement->GetStationIndex();
|
||
return WindowRideOpenStation(*ride, stationIndex);
|
||
}
|
||
else if (type == TileElementType::Track)
|
||
{
|
||
// Open ride window in station view
|
||
auto trackElement = tileElement->AsTrack();
|
||
auto trackType = trackElement->GetTrackType();
|
||
const auto& ted = GetTrackElementDescriptor(trackType);
|
||
if (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN)
|
||
{
|
||
auto stationIndex = trackElement->GetStationIndex();
|
||
return WindowRideOpenStation(*ride, stationIndex);
|
||
}
|
||
}
|
||
|
||
// Open ride window in overview mode
|
||
return RideMainOpen(*ride);
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
/**
|
||
*
|
||
* rct2: 0x006ACAC2
|
||
*/
|
||
WindowBase* RideOpenVehicle(Vehicle* vehicle)
|
||
{
|
||
if (vehicle == nullptr)
|
||
return nullptr;
|
||
|
||
Vehicle* headVehicle = vehicle->TrainHead();
|
||
if (headVehicle == nullptr)
|
||
return nullptr;
|
||
|
||
EntityId headVehicleSpriteIndex = headVehicle->Id;
|
||
auto ride = headVehicle->GetRide();
|
||
if (ride == nullptr)
|
||
return nullptr;
|
||
|
||
// Get view index
|
||
int16_t view = 1;
|
||
for (int32_t i = 0; i <= OpenRCT2::Limits::MaxTrainsPerRide; i++)
|
||
{
|
||
if (ride->vehicles[i] == headVehicleSpriteIndex)
|
||
break;
|
||
|
||
view++;
|
||
}
|
||
|
||
auto* w = static_cast<RideWindow*>(WindowFindByNumber(WindowClass::Ride, ride->id.ToUnderlying()));
|
||
if (w != nullptr)
|
||
{
|
||
w->Invalidate();
|
||
|
||
if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == w->classification
|
||
&& gCurrentToolWidget.window_number == w->number)
|
||
{
|
||
ToolCancel();
|
||
}
|
||
|
||
int32_t openedPeepWindow = 0;
|
||
if (w->GetViewIndex() == view)
|
||
{
|
||
int32_t numPeepsLeft = vehicle->num_peeps;
|
||
for (int32_t i = 0; i < 32 && numPeepsLeft > 0; i++)
|
||
{
|
||
Peep* peep = GetEntity<Guest>(vehicle->peep[i]);
|
||
if (peep == nullptr)
|
||
continue;
|
||
|
||
numPeepsLeft--;
|
||
WindowBase* w2 = WindowFindByNumber(WindowClass::Peep, vehicle->peep[i]);
|
||
if (w2 == nullptr)
|
||
{
|
||
auto intent = Intent(WindowClass::Peep);
|
||
intent.PutExtra(INTENT_EXTRA_PEEP, peep);
|
||
ContextOpenIntent(&intent);
|
||
openedPeepWindow = 1;
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
w = static_cast<RideWindow*>(
|
||
openedPeepWindow ? WindowFindByNumber(WindowClass::Ride, ride->id.ToUnderlying())
|
||
: WindowBringToFrontByNumber(WindowClass::Ride, ride->id.ToUnderlying()));
|
||
}
|
||
|
||
if (w == nullptr)
|
||
{
|
||
w = WindowRideOpen(*ride);
|
||
}
|
||
|
||
w->SetViewIndex(view);
|
||
w->Invalidate();
|
||
|
||
return w;
|
||
}
|
||
|
||
void WindowRideInvalidateVehicle(const Vehicle& vehicle)
|
||
{
|
||
auto* w = static_cast<RideWindow*>(WindowFindByNumber(WindowClass::Ride, vehicle.ride.ToUnderlying()));
|
||
if (w == nullptr)
|
||
return;
|
||
|
||
auto ride = vehicle.GetRide();
|
||
auto viewVehicleIndex = w->GetViewIndex() - 1;
|
||
if (ride == nullptr || viewVehicleIndex < 0 || viewVehicleIndex >= ride->NumTrains)
|
||
return;
|
||
|
||
if (vehicle.Id != ride->vehicles[viewVehicleIndex])
|
||
return;
|
||
|
||
w->Invalidate();
|
||
}
|
||
|
||
void WindowRidePaintResetVehicle(RideId rideIndex)
|
||
{
|
||
auto w = static_cast<RideWindow*>(WindowFindByNumber(WindowClass::Ride, rideIndex.ToUnderlying()));
|
||
if (w != nullptr)
|
||
{
|
||
if (w->page == 4) // WINDOW_RIDE_PAGE_COLOUR
|
||
{
|
||
w->ResetVehicleIndex();
|
||
}
|
||
w->Invalidate();
|
||
}
|
||
}
|
||
|
||
static void CancelScenerySelection()
|
||
{
|
||
gGamePaused &= ~GAME_PAUSED_SAVING_TRACK;
|
||
gTrackDesignSaveMode = false;
|
||
OpenRCT2::Audio::Resume();
|
||
|
||
WindowBase* main_w = WindowGetMain();
|
||
if (main_w != nullptr)
|
||
{
|
||
main_w->viewport->flags &= ~(VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE);
|
||
}
|
||
|
||
GfxInvalidateScreen();
|
||
ToolCancel();
|
||
}
|
||
|
||
void WindowRideMeasurementsDesignCancel()
|
||
{
|
||
if (gTrackDesignSaveMode)
|
||
{
|
||
CancelScenerySelection();
|
||
}
|
||
}
|
||
} // namespace OpenRCT2::Ui::Windows
|