From be8bfa37e6a94e2901217fba32d89e9d197581fd Mon Sep 17 00:00:00 2001 From: Svelbeard <56926365+Svelbeard@users.noreply.github.com> Date: Fri, 3 Jul 2020 13:35:56 +0100 Subject: [PATCH] Construction Window (#493) * Initial work on open function * Further Setup * Further Progress * Fix Window crashing on open * Implement on dropdown, on_resize and cursor for construction tab * Implement on_mouse_down for construction tab * Further Work on Construction Tab * Fix track construction tool not working * Implement disabled widgets for track selection * Start work on 2nd Tab events * Finish Station Tab * Implement Events for signal Tab * Implement 4th Tab events * Fix window not displaying track and crash on selecting road bridge * Minor Formatting * Progress on construction on_tool_down * Fix crash on construction _on_tool_down * Implement on_tool_down * Refactor construction_draw * Fixes for CI * Fix Station Catchment being permanently displayed * Run clang format * Split tabs into seperate files * Fix for CI * Fix Window not drawing on open * Fix for CI * Fix window crashing due to incorrect station type * Replace while loops with for loops in Station and Signal Tabs * Refactor Station Dropdown loops into Templated function * Refactor ConstructionTab.cpp * Fixes for CI * Fix crash to desktop when constructing underground * Fix incorrect tiles highlighting on tool update * Fix for Xcode * Fix crash to desktop when opening tram track construction window --- src/openloco/company.cpp | 7 + src/openloco/company.h | 1 + src/openloco/graphics/image_ids.h | 25 + src/openloco/input.h | 8 + src/openloco/input/mouse_input.cpp | 15 + src/openloco/interop/hooks.cpp | 10 +- src/openloco/localisation/string_ids.h | 60 + src/openloco/map/tile.cpp | 27 + src/openloco/map/tile.h | 1 + src/openloco/map/tilemgr.cpp | 6 - src/openloco/map/tilemgr.h | 6 + src/openloco/objects/airport_object.h | 25 +- src/openloco/objects/bridge_object.h | 30 + src/openloco/objects/dock_object.h | 25 + src/openloco/objects/objectmgr.cpp | 63 +- src/openloco/objects/objectmgr.h | 8 + src/openloco/objects/road_extra_object.h | 10 +- src/openloco/objects/road_object.h | 33 +- src/openloco/objects/road_station_object.h | 24 +- src/openloco/objects/track_extra_object.h | 9 +- src/openloco/objects/track_object.h | 41 +- src/openloco/objects/train_signal_object.h | 26 + src/openloco/objects/train_station_object.h | 38 + src/openloco/openloco.cpp | 7 +- src/openloco/openloco.h | 3 +- src/openloco/openloco.vcxproj | 11 +- src/openloco/station.h | 5 +- src/openloco/ui/WindowManager.cpp | 147 +- src/openloco/ui/WindowManager.h | 15 +- src/openloco/ui/dropdown.cpp | 17 + src/openloco/ui/dropdown.h | 2 +- src/openloco/windows/build_vehicle.cpp | 2 +- src/openloco/windows/construction/Common.cpp | 2115 +++++++++++++++++ .../windows/construction/Construction.h | 391 +++ .../windows/construction/ConstructionTab.cpp | 1222 ++++++++++ .../windows/construction/OverheadTab.cpp | 309 +++ .../windows/construction/SignalTab.cpp | 216 ++ .../windows/construction/StationTab.cpp | 411 ++++ src/openloco/windows/constructionwnd.cpp | 75 - src/openloco/windows/stationwnd.cpp | 88 +- 40 files changed, 5392 insertions(+), 142 deletions(-) create mode 100644 src/openloco/objects/bridge_object.h create mode 100644 src/openloco/objects/dock_object.h create mode 100644 src/openloco/objects/train_signal_object.h create mode 100644 src/openloco/objects/train_station_object.h create mode 100644 src/openloco/windows/construction/Common.cpp create mode 100644 src/openloco/windows/construction/Construction.h create mode 100644 src/openloco/windows/construction/ConstructionTab.cpp create mode 100644 src/openloco/windows/construction/OverheadTab.cpp create mode 100644 src/openloco/windows/construction/SignalTab.cpp create mode 100644 src/openloco/windows/construction/StationTab.cpp delete mode 100644 src/openloco/windows/constructionwnd.cpp diff --git a/src/openloco/company.cpp b/src/openloco/company.cpp index 0de15781..1a190305 100644 --- a/src/openloco/company.cpp +++ b/src/openloco/company.cpp @@ -76,4 +76,11 @@ namespace openloco args.push(performanceIndex); args.push(getCorporateRatingAsStringId(performanceToRating(performanceIndex))); } + + bool company::isVehicleIndexUnlocked(const uint8_t vehicleIndex) const + { + auto vehicleTypeIndex = vehicleIndex >> 5; + + return (unlocked_vehicles[vehicleTypeIndex] & (1 << (vehicleIndex & 0x1F))) != 0; + } } diff --git a/src/openloco/company.h b/src/openloco/company.h index 086263ee..3dedc49f 100644 --- a/src/openloco/company.h +++ b/src/openloco/company.h @@ -96,6 +96,7 @@ namespace openloco company_id_t id() const; bool empty() const; void ai_think(); + bool isVehicleIndexUnlocked(const uint8_t vehicleIndex) const; }; #pragma pack(pop) diff --git a/src/openloco/graphics/image_ids.h b/src/openloco/graphics/image_ids.h index c98f115b..7515b98d 100644 --- a/src/openloco/graphics/image_ids.h +++ b/src/openloco/graphics/image_ids.h @@ -21,6 +21,31 @@ namespace openloco::image_ids constexpr uint32_t inline_green_up_arrow = 2324; constexpr uint32_t inline_red_down_arrow = 2325; + constexpr uint32_t construction_straight = 2335; + + constexpr uint32_t construction_left_hand_curve_very_small = 2340; + constexpr uint32_t construction_right_hand_curve_very_small = 2341; + constexpr uint32_t construction_left_hand_curve_small = 2342; + constexpr uint32_t construction_right_hand_curve_small = 2343; + constexpr uint32_t construction_left_hand_curve = 2344; + constexpr uint32_t construction_right_hand_curve = 2345; + constexpr uint32_t construction_left_hand_curve_large = 2346; + constexpr uint32_t construction_right_hand_curve_large = 2347; + constexpr uint32_t construction_steep_slope_down = 2348; + constexpr uint32_t construction_slope_down = 2349; + constexpr uint32_t construction_level = 2350; + constexpr uint32_t construction_slope_up = 2351; + constexpr uint32_t construction_steep_slope_up = 2352; + constexpr uint32_t construction_s_bend_left = 2353; + constexpr uint32_t construction_s_bend_right = 2354; + constexpr uint32_t construction_s_bend_dual_track_left = 2355; + constexpr uint32_t construction_s_bend_dual_track_right = 2356; + constexpr uint32_t construction_s_bend_to_single_track_left = 2357; + constexpr uint32_t construction_s_bend_to_single_track_right = 2358; + constexpr uint32_t construction_right_turnaround = 2359; + constexpr uint32_t construction_left_turnaround = 2360; + constexpr uint32_t construction_remove = 2361; + constexpr uint32_t construction_new_position = 2362; constexpr uint32_t rubbish_bin = 2363; constexpr uint32_t centre_viewport = 2364; constexpr uint32_t rotate_object = 2365; diff --git a/src/openloco/input.h b/src/openloco/input.h index 5587f5a3..351072a2 100644 --- a/src/openloco/input.h +++ b/src/openloco/input.h @@ -52,6 +52,11 @@ namespace openloco::input VSCROLLBAR_DOWN_PRESSED = (1 << 7), }; + namespace map_selection_flags + { + constexpr uint8_t catchment_area = 1 << 5; + }; + namespace key_modifier { constexpr uint8_t shift = 1 << 0; @@ -87,6 +92,9 @@ namespace openloco::input bool has_key_modifier(uint8_t modifier); uint16_t getMapSelectionFlags(); + bool hasMapSelectionFlag(uint8_t flags); + void setMapSelectionFlags(uint8_t flags); + void resetMapSelectionFlag(uint8_t flags); void handle_keyboard(); void handle_mouse(int16_t x, int16_t y, mouse_button button); diff --git a/src/openloco/input/mouse_input.cpp b/src/openloco/input/mouse_input.cpp index 135de5b4..fda80bb6 100644 --- a/src/openloco/input/mouse_input.cpp +++ b/src/openloco/input/mouse_input.cpp @@ -281,6 +281,21 @@ namespace openloco::input return _mapSelectionFlags; } + bool hasMapSelectionFlag(uint8_t flags) + { + return (_mapSelectionFlags & flags) != 0; + } + + void setMapSelectionFlags(uint8_t flags) + { + _mapSelectionFlags = _mapSelectionFlags | flags; + } + + void resetMapSelectionFlag(uint8_t flags) + { + _mapSelectionFlags = _mapSelectionFlags & ~flags; + } + #pragma mark - Mouse input // 0x004C7174 diff --git a/src/openloco/interop/hooks.cpp b/src/openloco/interop/hooks.cpp index 6795a37c..4b2e1018 100644 --- a/src/openloco/interop/hooks.cpp +++ b/src/openloco/interop/hooks.cpp @@ -706,15 +706,6 @@ void openloco::interop::register_hooks() return 0; }); - register_hook( - 0x0049D3F6, - [](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t { - registers backup = regs; - ui::windows::construction::on_mouse_up(*((ui::window*)regs.esi), regs.dx); - regs = backup; - return 0; - }); - register_hook( 0x004BA8D4, [](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t { @@ -775,6 +766,7 @@ void openloco::interop::register_hooks() ui::build_vehicle::registerHooks(); ui::windows::terraform::registerHooks(); ui::windows::error::registerHooks(); + ui::windows::construction::registerHooks(); ui::WindowManager::registerHooks(); ui::viewportmgr::registerHooks(); game_commands::registerHooks(); diff --git a/src/openloco/localisation/string_ids.h b/src/openloco/localisation/string_ids.h index c447f3a9..2549b936 100644 --- a/src/openloco/localisation/string_ids.h +++ b/src/openloco/localisation/string_ids.h @@ -108,6 +108,31 @@ namespace openloco::string_ids constexpr string_id screenshot_saved_as = 109; constexpr string_id screenshot_failed = 110; + constexpr string_id stringid_2 = 113; + constexpr string_id tooltip_left_hand_curve = 114; + constexpr string_id tooltip_right_hand_curve = 115; + constexpr string_id tooltip_left_hand_curve_small = 116; + constexpr string_id tooltip_right_hand_curve_small = 117; + constexpr string_id tooltip_left_hand_curve_very_small = 118; + constexpr string_id tooltip_right_hand_curve_very_small = 119; + constexpr string_id tooltip_left_hand_curve_large = 120; + constexpr string_id tooltip_right_hand_curve_large = 121; + constexpr string_id tooltip_straight = 122; + constexpr string_id tooltip_s_bend_left = 123; + constexpr string_id tooltip_s_bend_right = 124; + constexpr string_id tooltip_s_bend_left_dual_track = 125; + constexpr string_id tooltip_s_bend_right_dual_track = 126; + constexpr string_id tooltip_s_bend_to_single_track = 127; + constexpr string_id tooltip_turnaround = 128; + constexpr string_id tooltip_start_construction = 129; + constexpr string_id tooltip_construct = 130; + constexpr string_id tooltip_remove = 131; + constexpr string_id tooltip_steep_slope_down = 132; + constexpr string_id tooltip_slope_down = 133; + constexpr string_id tooltip_level = 134; + constexpr string_id tooltip_slope_up = 135; + constexpr string_id tooltip_steep_slope_up = 136; + constexpr string_id build_this = 137; constexpr string_id build_cost = 138; constexpr string_id menu_underground_view = 145; @@ -187,13 +212,37 @@ namespace openloco::string_ids constexpr string_id title_company_colour_scheme = 250; constexpr string_id title_company_challenge = 251; + constexpr string_id velocity = 263; + constexpr string_id unlimited_speed = 264; + + constexpr string_id tooltip_select_track_to_upgrade = 268; + + constexpr string_id signal_black = 270; + constexpr string_id tab_track_road_construction = 271; + constexpr string_id tab_station_construction = 272; + constexpr string_id tab_signal_construction = 273; + constexpr string_id tab_electrification_construction = 274; + constexpr string_id tooltip_select_signal_type = 275; + constexpr string_id tooltip_signal_both_directions = 276; + constexpr string_id tooltip_signal_single_direction = 277; + constexpr string_id tooltip_bridge_stats = 278; + constexpr string_id tooltip_select_station_type = 279; + constexpr string_id buffer_337 = 337; constexpr string_id buffer_338 = 338; constexpr string_id stringid_stringid = 347; + constexpr string_id single_section = 348; + constexpr string_id block_section = 349; + constexpr string_id all_connected_track = 350; + constexpr string_id upgrade_track_with_mods = 352; + constexpr string_id click_track_to_upgrade = 353; + constexpr string_id tooltip_select_track_mod = 354; constexpr string_id move_main_view_to_show_this = 355; + constexpr string_id error_can_only_build_above_ground = 360; + constexpr string_id title_station_name = 383; constexpr string_id prompt_type_new_station_name = 384; constexpr string_id error_cant_rename_station = 385; @@ -251,6 +300,9 @@ namespace openloco::string_ids constexpr string_id player_info_company_value = 572; constexpr string_id player_info_company_value_negative = 573; + constexpr string_id new_construction_position = 579; + constexpr string_id rotate_90 = 580; + constexpr string_id error_cant_build_this_here = 583; constexpr string_id date_monthyear = 584; @@ -645,6 +697,11 @@ namespace openloco::string_ids constexpr string_id prompt_type_new_town_name = 1309; constexpr string_id status_town_population = 1310; constexpr string_id error_cant_rename_town = 1311; + constexpr string_id new_station = 1312; + constexpr string_id new_station_buffer = 1313; + constexpr string_id catchment_area_accepts = 1314; + constexpr string_id catchment_area_produces = 1315; + constexpr string_id catchment_area_nothing = 1316; constexpr string_id title_industries = 1318; constexpr string_id title_fund_new_industries = 1319; @@ -804,6 +861,8 @@ namespace openloco::string_ids constexpr string_id forbid_trams = 1521; constexpr string_id forbid_aircraft = 1522; constexpr string_id forbid_ships = 1523; + constexpr string_id title_airport = 1524; + constexpr string_id title_ship_port = 1525; constexpr string_id currently_playing = 1535; constexpr string_id music_controls_stop_tip = 1536; @@ -1088,6 +1147,7 @@ namespace openloco::string_ids constexpr string_id title_industry_name = 2017; constexpr string_id prompt_enter_new_industry_name = 2018; constexpr string_id error_cant_rename_industry = 2019; + constexpr string_id dropdown_bridge_stats = 2020; constexpr string_id position_1st = 2023; constexpr string_id position_2nd = 2024; diff --git a/src/openloco/map/tile.cpp b/src/openloco/map/tile.cpp index 06c43209..f6b1689f 100644 --- a/src/openloco/map/tile.cpp +++ b/src/openloco/map/tile.cpp @@ -167,4 +167,31 @@ namespace openloco::map return coordinate_2d; } + + map_pos rotate2DCoordinate(map_pos pos, uint8_t rotation) + { + map_pos coordinate2D; + + switch (rotation) + { + default: + case 0: + coordinate2D = pos; + break; + case 1: + coordinate2D.x = pos.y; + coordinate2D.y = -pos.x; + break; + case 2: + coordinate2D.x = -pos.x; + coordinate2D.y = -pos.y; + break; + case 3: + coordinate2D.x = -pos.y; + coordinate2D.y = pos.x; + break; + } + + return coordinate2D; + } } diff --git a/src/openloco/map/tile.h b/src/openloco/map/tile.h index ba853b27..30144e0f 100644 --- a/src/openloco/map/tile.h +++ b/src/openloco/map/tile.h @@ -62,6 +62,7 @@ namespace openloco::map uint32_t tile_element_height(int16_t x, int16_t y); map_pos coordinate_3d_to_2d(int16_t x, int16_t y, int16_t z, int rotation); + map_pos rotate2DCoordinate(map_pos pos, uint8_t rotation); enum class element_type { diff --git a/src/openloco/map/tilemgr.cpp b/src/openloco/map/tilemgr.cpp index 0ab35ab2..b2f6feb3 100644 --- a/src/openloco/map/tilemgr.cpp +++ b/src/openloco/map/tilemgr.cpp @@ -7,12 +7,6 @@ using namespace openloco::interop; namespace openloco::map::tilemgr { - enum MapSelectFlag : uint16_t - { - enable = (1 << 0), - enableConstruct = (1 << 1) - }; - static loco_global _tiles; static loco_global _mapSelectionAX; static loco_global _mapSelectionBX; diff --git a/src/openloco/map/tilemgr.h b/src/openloco/map/tilemgr.h index 5f947e82..dfb12fe1 100644 --- a/src/openloco/map/tilemgr.h +++ b/src/openloco/map/tilemgr.h @@ -6,6 +6,12 @@ namespace openloco::map::tilemgr { + enum MapSelectFlag : uint16_t + { + enable = (1 << 0), + enableConstruct = (1 << 1) + }; + tile get(map_pos pos); tile get(coord_t x, coord_t y); std::tuple get_height(coord_t x, coord_t y); diff --git a/src/openloco/objects/airport_object.h b/src/openloco/objects/airport_object.h index 736b3488..7d14132e 100644 --- a/src/openloco/objects/airport_object.h +++ b/src/openloco/objects/airport_object.h @@ -1,6 +1,6 @@ #pragma once -#include "../localisation/stringmgr.h" +#include "../types.hpp" namespace openloco { @@ -26,10 +26,25 @@ namespace openloco struct airport_object { string_id name; - uint8_t pad_02[0x10 - 0x02]; - uint16_t var_10; - uint8_t pad_12[0xAD - 0x12]; - uint8_t var_AD; + uint16_t build_cost_factor; // 0x02 + uint16_t sell_cost_factor; // 0x04 + uint8_t cost_index; //0x06 + uint8_t var_07; + uint32_t var_08; + uint8_t pad_0C[0x10 - 0x0C]; + uint16_t allowed_plane_types; // 0x10 + uint8_t num_sprite_sets; // 0x12 + uint8_t num_tiles; // 0x13 + uint8_t pad_14[0xA0 - 0x14]; + uint32_t large_tiles; // 0xA0 + uint8_t min_x; // 0xA4 + uint8_t min_y; // 0xA5 + uint8_t max_x; // 0xA6 + uint8_t max_y; // 0xA7 + uint16_t designed_year; // 0xA8 + uint16_t obsolete_year; // 0xAA + uint8_t num_nodes; // 0xAC + uint8_t num_edges; // 0xAD airport_var_AE_object* var_AE; airport_var_B2_object* var_B2; uint8_t pad_B6[0xBA - 0xB6]; diff --git a/src/openloco/objects/bridge_object.h b/src/openloco/objects/bridge_object.h new file mode 100644 index 00000000..57ed6a8c --- /dev/null +++ b/src/openloco/objects/bridge_object.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../types.hpp" + +namespace openloco +{ +#pragma pack(push, 1) + struct bridge_object + { + string_id name; + uint8_t no_roof; // 0x02 + uint8_t pad_03[0x08 - 0x03]; + uint8_t span_length; // 0x08 + uint8_t pillar_spacing; // 0x09 + uint16_t max_speed; // 0x0A + uint8_t max_height; // 0x0C + uint8_t cost_index; // 0x0D + uint16_t base_cost_factor; // 0x0E + uint16_t height_cost_factor; // 0x10 + uint16_t sell_cost_factor; // 0x12 + uint16_t disabled_track_cfg; // 0x14 + uint32_t var_16; + uint8_t track_num_compatible; // 0x1A + uint8_t track_mods[7]; // 0x1B + uint8_t road_num_compatible; // 0x22 + uint8_t road_mods[7]; // 0x23 + uint16_t designed_year; // 0x2A + }; +#pragma pack(pop) +} diff --git a/src/openloco/objects/dock_object.h b/src/openloco/objects/dock_object.h new file mode 100644 index 00000000..ea0c6598 --- /dev/null +++ b/src/openloco/objects/dock_object.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../types.hpp" + +namespace openloco +{ +#pragma pack(push, 1) + struct dock_object + { + string_id name; + uint16_t build_cost_factor; // 0x02 + uint16_t sell_cost_factor; // 0x04 + uint8_t cost_index; // 0x06 + uint8_t var_07; + uint32_t var_08; + uint8_t pad_0C[0x12 - 0x0C]; + uint8_t num_aux_01; // 0x12 + uint8_t num_aux_02_ent; // 0x13 + uint8_t pad_14[0x20 - 0x14]; + uint16_t designed_year; // 0x20 + uint16_t obsolete_year; // 0x22 + uint8_t pad_24[0x28 - 0x24]; + }; +#pragma pack(pop) +} diff --git a/src/openloco/objects/objectmgr.cpp b/src/openloco/objects/objectmgr.cpp index 37eca3e2..4032e808 100644 --- a/src/openloco/objects/objectmgr.cpp +++ b/src/openloco/objects/objectmgr.cpp @@ -80,16 +80,31 @@ namespace openloco::objectmgr return nullptr; } + template<> + train_signal_object* get(size_t id) + { + if (_trainSignalObjects[id] != reinterpret_cast(-1)) + return _trainSignalObjects[id]; + else + return nullptr; + } + template<> road_station_object* get(size_t id) { - return _roadStationObjects[id]; + if (_roadStationObjects[id] != reinterpret_cast(-1)) + return _roadStationObjects[id]; + else + return nullptr; } template<> vehicle_object* get(size_t id) { - return _vehicleObjects[id]; + if (_vehicleObjects[id] != reinterpret_cast(-1)) + return _vehicleObjects[id]; + else + return nullptr; } template<> @@ -122,7 +137,7 @@ namespace openloco::objectmgr template<> industry_object* get(size_t id) { - if (_industryObjects[id] != (industry_object*)-1) + if (_industryObjects[id] != reinterpret_cast(-1)) return _industryObjects[id]; else return nullptr; @@ -134,6 +149,24 @@ namespace openloco::objectmgr return _currencyObjects[0]; } + template<> + bridge_object* get(size_t id) + { + if (_bridgeObjects[id] != reinterpret_cast(-1)) + return _bridgeObjects[id]; + else + return nullptr; + } + + template<> + train_station_object* get(size_t id) + { + if (_trainStationObjects[id] != reinterpret_cast(-1)) + return _trainStationObjects[id]; + else + return nullptr; + } + template<> track_extra_object* get(size_t id) { @@ -143,7 +176,10 @@ namespace openloco::objectmgr template<> track_object* get(size_t id) { - return _trackObjects[id]; + if (_trackObjects[id] != reinterpret_cast(-1)) + return _trackObjects[id]; + else + return nullptr; } template<> @@ -155,13 +191,28 @@ namespace openloco::objectmgr template<> road_object* get(size_t id) { - return _roadObjects[id]; + if (_roadObjects[id] != reinterpret_cast(-1)) + return _roadObjects[id]; + else + return nullptr; } template<> airport_object* get(size_t id) { - return _airportObjects[id]; + if (_airportObjects[id] != reinterpret_cast(-1)) + return _airportObjects[id]; + else + return nullptr; + } + + template<> + dock_object* get(size_t id) + { + if (_dockObjects[id] != reinterpret_cast(-1)) + return _dockObjects[id]; + else + return nullptr; } template<> diff --git a/src/openloco/objects/objectmgr.h b/src/openloco/objects/objectmgr.h index 96868914..19d14a66 100644 --- a/src/openloco/objects/objectmgr.h +++ b/src/openloco/objects/objectmgr.h @@ -147,6 +147,8 @@ namespace openloco::objectmgr template<> cargo_object* get(size_t id); template<> + train_signal_object* get(size_t id); + template<> road_station_object* get(size_t id); template<> vehicle_object* get(size_t id); @@ -161,12 +163,18 @@ namespace openloco::objectmgr template<> currency_object* get(); template<> + bridge_object* get(); + template<> + train_station_object* get(); + template<> track_object* get(size_t id); template<> road_object* get(size_t id); template<> airport_object* get(size_t id); template<> + dock_object* get(size_t id); + template<> land_object* get(size_t id); template<> water_object* get(); diff --git a/src/openloco/objects/road_extra_object.h b/src/openloco/objects/road_extra_object.h index 9739922f..8d0627bb 100644 --- a/src/openloco/objects/road_extra_object.h +++ b/src/openloco/objects/road_extra_object.h @@ -1,13 +1,21 @@ #pragma once -#include "../localisation/stringmgr.h" +#include "../types.hpp" namespace openloco { + #pragma pack(push, 1) struct road_extra_object { string_id name; + uint16_t road_pieces; // 0x02 + uint8_t is_overhead; // 0x04 + uint8_t cost_index; // 0x05 + uint16_t build_cost_factor; // 0x06 + uint16_t sell_cost_factor; // 0x08 + uint8_t pad_0A[0x0E - 0x0A]; + uint32_t var_0E; }; #pragma pack(pop) } diff --git a/src/openloco/objects/road_object.h b/src/openloco/objects/road_object.h index aea3593f..a54cbcdc 100644 --- a/src/openloco/objects/road_object.h +++ b/src/openloco/objects/road_object.h @@ -1,6 +1,6 @@ #pragma once -#include "../localisation/stringmgr.h" +#include "../types.hpp" namespace openloco { @@ -9,14 +9,39 @@ namespace openloco constexpr uint8_t unk_01 = 1 << 1; constexpr uint8_t unk_03 = 1 << 3; } + + namespace road_piece_flags + { + constexpr uint16_t one_way = 1 << 0; + constexpr uint16_t track = 1 << 1; + constexpr uint16_t slope = 1 << 2; + constexpr uint16_t steep_slope = 1 << 3; + constexpr uint16_t intersection = 1 << 2; + constexpr uint16_t one_sided = 1 << 5; + constexpr uint16_t overtake = 1 << 6; + constexpr uint16_t street_lights = 1 << 8; + } #pragma pack(push, 1) struct road_object { string_id name; - uint8_t pad_02[0x0E - 0x02]; + uint16_t road_pieces; // 0x02 + uint16_t build_cost_factor; // 0x04 + uint16_t sell_cost_factor; // 0x06 + uint16_t tunnel_cost_factor; // 0x08 + uint8_t cost_index; // 0x0A + uint8_t pad_0B[0x0E - 0x0B]; uint32_t var_0E; - uint16_t flags; //0x12 - uint8_t pad_14[0x30 - 0x14]; + uint16_t flags; // 0x12 + uint8_t num_bridges; // 0x14 + uint8_t bridges[7]; // 0x15 + uint8_t num_stations; // 0x1C + uint8_t stations[7]; // 0x1D + uint8_t var_24; + uint8_t num_mods; // 0x25 + uint8_t mods[2]; // 0x26 + uint8_t num_compatible; // 0x28 + uint8_t pad_29[0x30 - 0x29]; }; #pragma pack(pop) } diff --git a/src/openloco/objects/road_station_object.h b/src/openloco/objects/road_station_object.h index 3ec04249..b7b3cd8c 100644 --- a/src/openloco/objects/road_station_object.h +++ b/src/openloco/objects/road_station_object.h @@ -1,17 +1,31 @@ #pragma once -#include "../localisation/stringmgr.h" +#include "../types.hpp" namespace openloco { + namespace road_station_flags + { + constexpr uint8_t recolourable = 1 << 0; + } + #pragma pack(push, 1) struct road_station_object { string_id name; - uint8_t pad_02[0x0B - 0x02]; - uint8_t var_0B; - uint8_t pad_0C[0x2C - 0x0C]; - uint8_t var_2C; + uint8_t pad_02[0x04 - 0x02]; + uint16_t road_pieces; // 0x04 + uint16_t build_cost_factor; // 0x06 + uint16_t sell_cost_factor; // 0x08 + uint8_t cost_index; // 0x0A + uint8_t flags; // 0x0B + uint32_t var_0C; + uint8_t pad_10[0x20 - 0x10]; + uint8_t num_compatible; // 0x20 + uint8_t mods[7]; // 0x21 + uint16_t designed_year; // 0x28 + uint16_t obsolete_year; // 0x2A + uint8_t pad_2C[0x6E - 0x2C]; }; #pragma pack(pop) } diff --git a/src/openloco/objects/track_extra_object.h b/src/openloco/objects/track_extra_object.h index 146a887e..1196d6be 100644 --- a/src/openloco/objects/track_extra_object.h +++ b/src/openloco/objects/track_extra_object.h @@ -1,6 +1,6 @@ #pragma once -#include "../localisation/stringmgr.h" +#include "../types.hpp" namespace openloco { @@ -9,6 +9,13 @@ namespace openloco struct track_extra_object { string_id name; + uint16_t track_pieces; // 0x02 + uint8_t is_overhead; // 0x04 + uint8_t cost_index; // 0x05 + uint16_t build_cost_factor; // 0x06 + uint16_t sell_cost_factor; // 0x08 + uint8_t pad_0A[0x0E - 0x0A]; + uint32_t var_0E; }; #pragma pack(pop) } diff --git a/src/openloco/objects/track_object.h b/src/openloco/objects/track_object.h index c0d5565f..1344b5f4 100644 --- a/src/openloco/objects/track_object.h +++ b/src/openloco/objects/track_object.h @@ -1,6 +1,6 @@ #pragma once -#include "../localisation/stringmgr.h" +#include "../types.hpp" namespace openloco { @@ -8,13 +8,48 @@ namespace openloco { constexpr uint8_t unk_02 = 1 << 2; } + + namespace track_piece_flags + { + constexpr uint16_t diagonal = 1 << 0; + constexpr uint16_t large_curve = 1 << 1; + constexpr uint16_t normal_curve = 1 << 2; + constexpr uint16_t small_curve = 1 << 3; + constexpr uint16_t very_small_curve = 1 << 4; + constexpr uint16_t slope = 1 << 5; + constexpr uint16_t steep_slope = 1 << 6; + constexpr uint16_t one_sided = 1 << 7; + constexpr uint16_t sloped_curve = 1 << 8; + constexpr uint16_t s_bend = 1 << 9; + constexpr uint16_t junction = 1 << 10; + } + #pragma pack(push, 1) struct track_object { string_id name; - uint8_t pad_02[0x1E - 0x02]; + uint16_t track_pieces; // 0x02 + uint16_t station_track_pieces; // 0x04 + uint8_t var_06; + uint8_t num_compatible; // 0x07 + uint8_t num_mods; // 0x08 + uint8_t num_signals; // 0x09 + uint8_t mods[4]; // 0x0A + uint8_t var_0E; + uint8_t pad_0F[0x14 - 0x0F]; + uint16_t build_cost_factor; // 0x14 + uint16_t sell_cost_factor; // 0x16 + uint16_t tunnel_cost_factor; // 0x18 + uint8_t cost_index; // 0x1A + uint8_t var_1B; + uint16_t curve_speed; // 0x1C uint32_t var_1E; - uint16_t flags; // 0x22 + uint16_t flags; // 0x22 + uint8_t num_bridges; // 0x24 + uint8_t bridges[7]; // 0x25 + uint8_t num_stations; // 0x2C + uint8_t stations[7]; // 0x2D + uint8_t display_offset; // 0x34 }; #pragma pack(pop) } diff --git a/src/openloco/objects/train_signal_object.h b/src/openloco/objects/train_signal_object.h new file mode 100644 index 00000000..54047178 --- /dev/null +++ b/src/openloco/objects/train_signal_object.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../types.hpp" + +namespace openloco +{ +#pragma pack(push, 1) + struct train_signal_object + { + string_id name; + uint16_t track_side; // 0x02 + uint8_t var_04; + uint8_t num_frames; // 0x05 + uint16_t cost_factor; // 0x06 + uint16_t sell_cost_factor; // 0x08 + uint8_t cost_index; // 0x0A + uint8_t var_0B; + uint16_t var_0C; + uint32_t var_0E; + uint8_t num_compatible; // 0x12 + uint8_t mods[7]; // 0x13 + uint16_t designed_year; // 0x1A + uint16_t obsolete_year; // 0x1C + }; +#pragma pack(pop) +} diff --git a/src/openloco/objects/train_station_object.h b/src/openloco/objects/train_station_object.h new file mode 100644 index 00000000..a46620cb --- /dev/null +++ b/src/openloco/objects/train_station_object.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../types.hpp" + +namespace openloco +{ + namespace train_station_flags + { + constexpr uint8_t recolourable = 1 << 0; + } + +#pragma pack(push, 1) + struct train_station_object + { + string_id name; + uint8_t var_02; + uint8_t var_03; + uint16_t track_pieces; // 0x04 + uint16_t build_cost_factor; // 0x06 + uint16_t sell_cost_factor; // 0x08 + uint8_t cost_index; // 0x0A + uint8_t var_0B; + uint8_t flags; // 0x0C + uint8_t var_0D; + uint32_t var_0E; + uint8_t pad_12[0x22 - 0x12]; + uint8_t num_compatible; // 0x22 + uint8_t mods[7]; + uint16_t designed_year; // 0x2A + uint16_t obsolete_year; // 0x2C + uint8_t var_2E[16]; + uint8_t var_3E[16]; + uint8_t var_4E[16]; + uint8_t var_5E[16]; + uint8_t pad_6E[0xAC - 0x6E]; + }; +#pragma pack(pop) +} diff --git a/src/openloco/openloco.cpp b/src/openloco/openloco.cpp index 1771df00..bf822b50 100644 --- a/src/openloco/openloco.cpp +++ b/src/openloco/openloco.cpp @@ -139,6 +139,11 @@ namespace openloco return (_screen_flags & screen_flags::networked) != 0; } + bool isTrackUpgradeMode() + { + return (_screen_flags & screen_flags::trackUpgrade) != 0; + } + bool is_unknown_4_mode() { return (_screen_flags & screen_flags::unknown_4) != 0; @@ -388,7 +393,7 @@ namespace openloco } // Host/client? - if ((get_screen_flags() & screen_flags::unknown_3) != 0) + if (isTrackUpgradeMode()) { _updating_company_id = companymgr::get_controlling_id(); diff --git a/src/openloco/openloco.h b/src/openloco/openloco.h index dbbac29f..29ba7c0d 100644 --- a/src/openloco/openloco.h +++ b/src/openloco/openloco.h @@ -13,7 +13,7 @@ namespace openloco constexpr uint8_t title = 1 << 0; constexpr uint8_t editor = 1 << 1; constexpr uint8_t networked = 1 << 2; - constexpr uint8_t unknown_3 = 1 << 3; + constexpr uint8_t trackUpgrade = 1 << 3; constexpr uint8_t unknown_4 = 1 << 4; constexpr uint8_t unknown_5 = 1 << 5; constexpr uint8_t unknown_6 = 1 << 6; @@ -30,6 +30,7 @@ namespace openloco bool is_editor_mode(); bool is_title_mode(); bool isNetworked(); + bool isTrackUpgradeMode(); bool is_unknown_4_mode(); bool is_paused(); uint8_t get_pause_flags(); diff --git a/src/openloco/openloco.vcxproj b/src/openloco/openloco.vcxproj index 7503683e..676fec6e 100644 --- a/src/openloco/openloco.vcxproj +++ b/src/openloco/openloco.vcxproj @@ -92,7 +92,11 @@ - + + + + + @@ -175,10 +179,12 @@ + + @@ -191,6 +197,8 @@ + + @@ -230,6 +238,7 @@ + diff --git a/src/openloco/station.h b/src/openloco/station.h index 22560087..3737395b 100644 --- a/src/openloco/station.h +++ b/src/openloco/station.h @@ -77,7 +77,10 @@ namespace openloco town_id_t town; // 0x2C station_cargo_stats cargo_stats[max_cargo_stats]; // 0x2E uint16_t var_1CE; - uint8_t pad_1D0[0x3B0 - 0x1D0]; + uint16_t var_1D0; + uint16_t var_1D2; + uint16_t var_1D4; + uint8_t pad_1D6[0x3B0 - 0x1D6]; uint8_t var_3B0; uint8_t var_3B1; uint8_t pad_3B2[0x3D2 - 0x3B2]; diff --git a/src/openloco/ui/WindowManager.cpp b/src/openloco/ui/WindowManager.cpp index 0d21360c..392f769b 100644 --- a/src/openloco/ui/WindowManager.cpp +++ b/src/openloco/ui/WindowManager.cpp @@ -1836,6 +1836,104 @@ namespace openloco::ui::WindowManager } } + /** + * 0x004A0A18 + * + * @param visibility @ + */ + void viewportSetVisibility(viewport_visibility visibility) + { + auto window = WindowManager::getMainWindow(); + + if (window == nullptr) + return; + + auto viewport = window->viewports[0]; + bool flagsChanged = false; + + switch (visibility) + { + case viewport_visibility::undergroundView: + { + if (!(viewport->flags & (viewport_flags::underground_view))) + { + viewport->flags |= (viewport_flags::underground_view); + flagsChanged = true; + } + break; + } + + case viewport_visibility::heightMarksOnLand: + { + if (!(viewport->flags & (viewport_flags::height_marks_on_land))) + { + viewport->flags |= (viewport_flags::height_marks_on_land); + flagsChanged = true; + } + break; + } + + case viewport_visibility::overgroundView: + { + if ((viewport->flags & (viewport_flags::underground_view))) + { + viewport->flags &= ~(viewport_flags::underground_view); + flagsChanged = true; + } + break; + } + + default: + { + if (viewport->flags & (viewport_flags::underground_view)) + { + viewport->flags &= ~(viewport_flags::underground_view); + flagsChanged = true; + } + + if (viewport->flags & (viewport_flags::flag_7)) + { + viewport->flags &= ~(viewport_flags::flag_7); + flagsChanged = true; + } + + if (viewport->flags & (viewport_flags::flag_8)) + { + viewport->flags &= ~(viewport_flags::flag_8); + flagsChanged = true; + } + + if (viewport->flags & (viewport_flags::hide_foreground_tracks_roads)) + { + viewport->flags &= ~(viewport_flags::hide_foreground_tracks_roads); + flagsChanged = true; + } + + if (viewport->flags & (viewport_flags::hide_foreground_scenery_buildings)) + { + viewport->flags &= ~(viewport_flags::hide_foreground_scenery_buildings); + flagsChanged = true; + } + + if (viewport->flags & (viewport_flags::height_marks_on_land)) + { + viewport->flags &= ~(viewport_flags::height_marks_on_land); + flagsChanged = true; + } + + if (viewport->flags & (viewport_flags::height_marks_on_tracks_roads)) + { + viewport->flags &= ~(viewport_flags::height_marks_on_tracks_roads); + flagsChanged = true; + } + break; + } + } + + if (flagsChanged) + window->invalidate(); + } + // 0x004CF456 void closeAllFloatingWindows() { @@ -1871,7 +1969,8 @@ namespace openloco::ui::WindowManager namespace openloco::ui::windows { static loco_global suppressErrorSound; - static loco_global _gridlines_state; + static loco_global _gridlinesState; + static loco_global _directionArrowsState; // 0x00431A8A void show_error(string_id title, string_id message, bool sound) @@ -1889,7 +1988,7 @@ namespace openloco::ui::windows // 0x00468FD3 void showGridlines() { - if (!_gridlines_state) + if (!_gridlinesState) { auto window = WindowManager::getMainWindow(); if (window != nullptr) @@ -1901,14 +2000,14 @@ namespace openloco::ui::windows window->viewports[0]->flags |= viewport_flags::gridlines_on_landscape; } } - _gridlines_state++; + _gridlinesState++; } // 0x00468FFE void hideGridlines() { - _gridlines_state--; - if (!_gridlines_state) + _gridlinesState--; + if (!_gridlinesState) { if (!(config::get().flags & config::flags::gridlines_on_landscape)) { @@ -1919,7 +2018,43 @@ namespace openloco::ui::windows { window->invalidate(); } - window->viewports[0]->flags ^= viewport_flags::gridlines_on_landscape; + window->viewports[0]->flags &= ~viewport_flags::gridlines_on_landscape; + } + } + } + } + + // 0x004793C4 + void showDirectionArrows() + { + if (!_directionArrowsState) + { + auto mainWindow = WindowManager::getMainWindow(); + if (mainWindow != nullptr) + { + if (!(mainWindow->viewports[0]->flags & viewport_flags::one_way_direction_arrows)) + { + mainWindow->viewports[0]->flags |= viewport_flags::one_way_direction_arrows; + mainWindow->invalidate(); + } + } + } + _directionArrowsState++; + } + + // 0x004793EF + void hideDirectionArrows() + { + _directionArrowsState--; + if (!_directionArrowsState) + { + auto mainWindow = WindowManager::getMainWindow(); + if (mainWindow != nullptr) + { + if ((mainWindow->viewports[0]->flags & viewport_flags::one_way_direction_arrows)) + { + mainWindow->viewports[0]->flags &= ~viewport_flags::one_way_direction_arrows; + mainWindow->invalidate(); } } } diff --git a/src/openloco/ui/WindowManager.h b/src/openloco/ui/WindowManager.h index 5a1498b0..3016a85e 100644 --- a/src/openloco/ui/WindowManager.h +++ b/src/openloco/ui/WindowManager.h @@ -9,6 +9,14 @@ namespace openloco::ui::WindowManager { + enum class viewport_visibility + { + reset, + undergroundView, + heightMarksOnLand, + overgroundView, + }; + void init(); void registerHooks(); WindowType getCurrentModalType(); @@ -55,6 +63,7 @@ namespace openloco::ui::WindowManager int32_t getCurrentRotation(); void viewport_shift_pixels(ui::window* window, ui::viewport* viewport, int16_t dX, int16_t dY); + void viewportSetVisibility(viewport_visibility flags); } namespace openloco::ui::windows @@ -73,6 +82,8 @@ namespace openloco::ui::windows void showGridlines(); void hideGridlines(); + void showDirectionArrows(); + void hideDirectionArrows(); } namespace openloco::ui::about @@ -98,7 +109,8 @@ namespace openloco::ui::about_music namespace openloco::ui::windows::construction { window* openWithFlags(uint32_t flags); - void on_mouse_up(window& w, uint16_t widgetIndex); + void sub_4A6FAC(); + void registerHooks(); } namespace openloco::ui::windows::industry @@ -165,6 +177,7 @@ namespace openloco::ui::windows::ScenarioOptions namespace openloco::ui::windows::station { window* open(uint16_t id); + void showStationCatchment(uint16_t windowNumber); } namespace openloco::ui::windows::station_list diff --git a/src/openloco/ui/dropdown.cpp b/src/openloco/ui/dropdown.cpp index e7c06592..f0b4037f 100644 --- a/src/openloco/ui/dropdown.cpp +++ b/src/openloco/ui/dropdown.cpp @@ -150,6 +150,23 @@ namespace openloco::ui::dropdown call(0x004CC807, regs); } + // Custom dropdown height if flags & (1<<6) is true + void show(int16_t x, int16_t y, int16_t width, int16_t height, colour_t colour, size_t count, uint8_t itemHeight, uint8_t flags) + { + assert(count < std::numeric_limits::max()); + registers regs; + regs.cx = x; + regs.dx = y; + regs.al = colour; + regs.ah = itemHeight; + regs.bl = static_cast(count); + regs.bh = flags; + regs.bp = width; + regs.di = height; + + call(0x004CC807, regs); + } + /** * 0x004CCDE7 * diff --git a/src/openloco/ui/dropdown.h b/src/openloco/ui/dropdown.h index f70090e9..63b62c7b 100644 --- a/src/openloco/ui/dropdown.h +++ b/src/openloco/ui/dropdown.h @@ -64,6 +64,7 @@ namespace openloco::ui::dropdown void set_item_selected(size_t index); void show(int16_t x, int16_t y, int16_t width, int16_t height, colour_t colour, size_t count, uint8_t flags); + void show(int16_t x, int16_t y, int16_t width, int16_t height, colour_t colour, size_t count, uint8_t itemHeight, uint8_t flags); void show_image(int16_t x, int16_t y, int16_t width, int16_t height, int16_t heightOffset, colour_t colour, uint8_t columnCount, uint8_t count); void show_below(window* window, widget_index widgetIndex, size_t count); void show_below(window* window, widget_index widgetIndex, size_t count, int8_t height); @@ -72,7 +73,6 @@ namespace openloco::ui::dropdown void populateCompanySelect(window* window, widget_t* widget); company_id_t getCompanyIdFromSelection(int16_t itemIndex); - void populateTownSizeSelect(window* window, widget_t* widget); uint16_t getItemArgument(const uint8_t index, const uint8_t argument); uint16_t getItemsPerRow(uint8_t itemCount); } diff --git a/src/openloco/windows/build_vehicle.cpp b/src/openloco/windows/build_vehicle.cpp index 025f3725..dde42a86 100644 --- a/src/openloco/windows/build_vehicle.cpp +++ b/src/openloco/windows/build_vehicle.cpp @@ -436,7 +436,7 @@ namespace openloco::ui::build_vehicle for (uint16_t vehicleObjIndex = 0; vehicleObjIndex < objectmgr::get_max_objects(object_type::vehicle); ++vehicleObjIndex) { auto vehicleObj = objectmgr::get(vehicleObjIndex); - if ((uint32_t)vehicleObj == 0xFFFFFFFF) + if (vehicleObj == nullptr) { continue; } diff --git a/src/openloco/windows/construction/Common.cpp b/src/openloco/windows/construction/Common.cpp new file mode 100644 index 00000000..e6ce0b44 --- /dev/null +++ b/src/openloco/windows/construction/Common.cpp @@ -0,0 +1,2115 @@ +#include "../../companymgr.h" +#include "../../date.h" +#include "../../graphics/image_ids.h" +#include "../../input.h" +#include "../../objects/airport_object.h" +#include "../../objects/bridge_object.h" +#include "../../objects/dock_object.h" +#include "../../objects/interface_skin_object.h" +#include "../../objects/objectmgr.h" +#include "../../objects/road_extra_object.h" +#include "../../objects/road_object.h" +#include "../../objects/road_station_object.h" +#include "../../objects/track_extra_object.h" +#include "../../objects/track_object.h" +#include "../../objects/train_signal_object.h" +#include "../../objects/train_station_object.h" +#include "../../stationmgr.h" +#include "../../widget.h" +#include "Construction.h" + +using namespace openloco::interop; +using namespace openloco::map; +using namespace openloco::map::tilemgr; + +namespace openloco::ui::windows::construction +{ + static window* nonTrackWindow() + { + auto window = WindowManager::find(WindowType::construction); + + if (window != nullptr) + { + common::setDisabledWidgets(window); + } + + window = WindowManager::find(WindowType::construction); + + if (window != nullptr) + { + window->call_on_mouse_up(common::widx::tab_station); + } + return window; + } + + static window* trackWindow() + { + auto window = WindowManager::find(WindowType::construction); + + if (window != nullptr) + { + common::setDisabledWidgets(window); + } + + common::activateSelectedConstructionWidgets(); + window = WindowManager::find(WindowType::construction); + + if (window != nullptr) + { + window->call_on_mouse_up(construction::widx::rotate_90); + } + return window; + } + + static window* createTrackConstructionWindow() + { + common::createConstructionWindow(); + + common::refreshSignalList(_signalList, _trackType); + + auto lastSignal = _scenarioSignals[_trackType]; + + if (lastSignal == 0xFF) + lastSignal = _signalList[0]; + + _lastSelectedSignal = lastSignal; + + common::refreshStationList(_stationList, _trackType, TransportMode::rail); + + auto lastStation = _scenarioTrainStations[_trackType]; + + if (lastStation == 0xFF) + lastStation = _stationList[0]; + + _lastSelectedStationType = lastStation; + + common::refreshBridgeList(_bridgeList, _trackType, TransportMode::rail); + + auto lastBridge = _scenarioBridges[_trackType]; + + if (lastBridge == 0xFF) + lastBridge = _bridgeList[0]; + + _lastSelectedBridge = lastBridge; + + common::refreshModList(_modList, _trackType, TransportMode::rail); + + auto lastMod = _scenarioTrackMods[_trackType]; + + if (lastMod == 0xFF) + lastMod = 0; + + _lastSelectedMods = lastMod; + _byte_113603A = 0; + + return trackWindow(); + } + + static window* createRoadConstructionWindow() + { + common::createConstructionWindow(); + + _lastSelectedSignal = 0xFF; + + common::refreshStationList(_stationList, _trackType, TransportMode::road); + + auto lastStation = _scenarioRoadStations[(_trackType & ~(1ULL << 7))]; + + if (lastStation == 0xFF) + lastStation = _stationList[0]; + + _lastSelectedStationType = lastStation; + + common::refreshBridgeList(_bridgeList, _trackType, TransportMode::road); + + auto lastBridge = _scenarioBridges[(_trackType & ~(1ULL << 7))]; + + if (lastBridge == 0xFF) + lastBridge = _bridgeList[0]; + + _lastSelectedBridge = lastBridge; + + common::refreshModList(_modList, _trackType, TransportMode::road); + + auto lastMod = _scenarioRoadMods[(_trackType & ~(1ULL << 7))]; + + if (lastMod == 0xff) + lastMod = 0; + + _lastSelectedMods = lastMod; + _byte_113603A = 0; + + return trackWindow(); + } + + static window* createDockConstructionWindow() + { + common::createConstructionWindow(); + + _lastSelectedSignal = 0xFF; + + _modList[0] = 0xFF; + _modList[1] = 0xFF; + _modList[2] = 0xFF; + _modList[3] = 0xFF; + + _lastSelectedMods = 0; + _lastSelectedBridge = 0xFF; + + common::refreshDockList(_stationList); + + if (_lastShipPort == 0xFF) + { + _lastSelectedStationType = _stationList[0]; + } + else + { + _lastSelectedStationType = _lastShipPort; + } + + return nonTrackWindow(); + } + + static window* createAirportConstructionWindow() + { + common::createConstructionWindow(); + + _lastSelectedSignal = 0xFF; + _modList[0] = 0xFF; + _modList[1] = 0xFF; + _modList[2] = 0xFF; + _modList[3] = 0xFF; + _lastSelectedMods = 0; + _lastSelectedBridge = 0xFF; + + common::refreshAirportList(_stationList); + + if (_lastAirport == 0xFF) + { + _lastSelectedStationType = _stationList[0]; + } + else + { + _lastSelectedStationType = _lastAirport; + } + + return nonTrackWindow(); + } + + // 0x004A3B0D + window* openWithFlags(const uint32_t flags) + { + auto mainWindow = WindowManager::getMainWindow(); + if (mainWindow) + { + auto viewport = mainWindow->viewports[0]; + _word_1135F86 = viewport->flags; + } + + auto window = WindowManager::find(WindowType::construction); + + if (window != nullptr) + { + if (flags & (1 << 7)) + { + auto trackType = flags & ~(1 << 7); + auto roadObj = objectmgr::get(trackType); + + if (roadObj->flags & flags_12::unk_03) + { + if (_trackType & (1 << 7)) + { + trackType = _trackType & ~(1 << 7); + roadObj = objectmgr::get(trackType); + + if (roadObj->flags & flags_12::unk_03) + { + _trackType = static_cast(flags); + + common::sub_4A3A50(); + + _lastSelectedTrackPiece = 0; + _lastSelectedTrackGradient = 0; + + return window; + } + } + } + } + } + + WindowManager::closeConstructionWindows(); + common::sub_4CD454(); + + mainWindow = WindowManager::getMainWindow(); + + if (mainWindow) + { + auto viewport = mainWindow->viewports[0]; + viewport->flags = _word_1135F86; + } + + _trackType = static_cast(flags); + _byte_1136063 = flags >> 24; + _x = 0x1800; + _y = 0x1800; + _word_1135FB8 = 0x100; + _constructionRotation = 0; + _constructionHover = 0; + _byte_113607E = 1; + _trackCost = 0x80000000; + _byte_1136076 = 0; + _lastSelectedTrackPiece = 0; + _lastSelectedTrackGradient = 0; + _lastSelectedTrackModSection = 0; + + common::setTrackOptions(flags); + + if (flags & (1 << 31)) + { + return createAirportConstructionWindow(); + } + else if (flags & (1 << 30)) + { + return createDockConstructionWindow(); + } + else if (flags & (1 << 7)) + { + return createRoadConstructionWindow(); + } + + return createTrackConstructionWindow(); + } + + // 0x004A6FAC + void sub_4A6FAC() + { + auto window = WindowManager::find(WindowType::construction); + if (window == nullptr) + return; + if (window->current_tab == common::widx::tab_station - common::widx::tab_construction) + { + if (_byte_1136063 & ((1 << 7) | (1 << 6))) + WindowManager::close(window); + else + window->call_on_mouse_up(common::widx::tab_construction); + } + } + + namespace common + { + const uint8_t trackPieceWidgets[] = { + construction::widx::straight, + construction::widx::left_hand_curve_very_small, + construction::widx::right_hand_curve_very_small, + construction::widx::left_hand_curve_small, + construction::widx::right_hand_curve_small, + construction::widx::left_hand_curve, + construction::widx::right_hand_curve, + construction::widx::left_hand_curve_large, + construction::widx::right_hand_curve_large, + construction::widx::s_bend_left, + construction::widx::s_bend_right, + }; + + // 0x004F6D1C + const previewTrack* roadPieces[] = { + _unk_4F6D44, + _unk_4F6D4F, + _unk_4F6D5A, + _unk_4F6D65, + _unk_4F6D8E, + _unk_4F6DB7, + _unk_4F6DCC, + _unk_4F6DE1, + _unk_4F6DEC, + _unk_4F6DF7, + }; + + // 0x004F73D8 + const previewTrack* trackPieces[] = { + _unk_4F7488, + _unk_4F7493, + _unk_4F74BC, + _unk_4F74C7, + _unk_4F74D2, + _unk_4F74FB, + _unk_4F7524, + _unk_4F7557, + _unk_4F758A, + _unk_4F75BD, + _unk_4F75F0, + _unk_4F7623, + _unk_4F7656, + _unk_4F767F, + _unk_4F76A8, + _unk_4F76BD, + _unk_4F76D2, + _unk_4F76DD, + _unk_4F76E8, + _unk_4F7711, + _unk_4F773A, + _unk_4F7763, + _unk_4F778C, + _unk_4F77B5, + _unk_4F77DE, + _unk_4F7807, + _unk_4F7830, + _unk_4F783B, + _unk_4F7846, + _unk_4F7851, + _unk_4F785C, + _unk_4F7867, + _unk_4F7872, + _unk_4F787D, + _unk_4F7888, + _unk_4F7893, + _unk_4F789E, + _unk_4F78A9, + _unk_4F78B4, + _unk_4F78BF, + _unk_4F78CA, + _unk_4F78D5, + _unk_4F78E0, + _unk_4F78EB, + }; + + struct TabInformation + { + widget_t* widgets; + const widx widgetIndex; + window_event_list* events; + const uint64_t enabledWidgets; + void (*tabReset)(window*); + }; + + static TabInformation tabInformationByTabOffset[] = { + { construction::widgets, widx::tab_construction, &construction::events, construction::enabledWidgets, &construction::tabReset }, + { station::widgets, widx::tab_station, &station::events, station::enabledWidgets, &station::tabReset }, + { signal::widgets, widx::tab_signal, &signal::events, signal::enabledWidgets, &signal::tabReset }, + { overhead::widgets, widx::tab_overhead, &overhead::events, overhead::enabledWidgets, &overhead::tabReset }, + }; + + void prepare_draw(window* self) + { + // Reset tab widgets if needed + const auto& tabWidgets = tabInformationByTabOffset[self->current_tab].widgets; + if (self->widgets != tabWidgets) + { + self->widgets = tabWidgets; + self->init_scroll_widgets(); + } + + // Activate the current tab + self->activated_widgets &= ~((1ULL << tab_construction) | (1ULL << tab_overhead) | (1ULL << tab_signal) | (1ULL << tab_station)); + self->activated_widgets |= (1ULL << common::tabInformationByTabOffset[self->current_tab].widgetIndex); + } + + // 0x0049D93A + void switchTab(window* self, widget_index widgetIndex) + { + if (self->current_tab == widgetIndex - widx::tab_construction) + return; + if (widgetIndex == widx::tab_station) + { + ui::windows::station::showStationCatchment(station_id::null); + } + common::sub_49FEC7(); + tilemgr::map_invalidate_map_selection_tiles(); + _mapSelectionFlags = _mapSelectionFlags & ~MapSelectFlag::enableConstruct; + _trackCost = 0x80000000; + _signalCost = 0x80000000; + _stationCost = 0x80000000; + _modCost = 0x80000000; + _byte_1136076 = 0; + + if (input::is_tool_active(self->type, self->number)) + input::cancel_tool(); + + self->current_tab = widgetIndex - widx::tab_construction; + self->frame_no = 0; + self->flags &= ~(window_flags::flag_16); + + auto tabInfo = tabInformationByTabOffset[widgetIndex - widx::tab_construction]; + + self->enabled_widgets = tabInfo.enabledWidgets; + self->event_handlers = tabInfo.events; + self->activated_widgets = 0; + self->widgets = tabInfo.widgets; + + setDisabledWidgets(self); + + self->invalidate(); + + self->width = self->widgets[widx::frame].right + 1; + self->height = self->widgets[widx::frame].bottom + 1; + + self->call_on_resize(); + self->call_prepare_draw(); + self->init_scroll_widgets(); + self->invalidate(); + + tabInfo.tabReset(self); + + self->moveInsideScreenEdges(); + } + + // 0x0049EFEF + static void drawRoadTabs(window* self, gfx::drawpixelinfo_t* dpi) + { + auto company = companymgr::get(_playerCompany); + auto companyColour = company->mainColours.primary; + auto roadObj = objectmgr::get(_trackType & ~(1 << 7)); + // Construction Tab + { + auto imageId = roadObj->var_0E; + if (self->current_tab == widx::tab_construction - widx::tab_construction) + imageId += (self->frame_no / 4) % 32; + gfx::recolour(imageId, companyColour); + + widget::draw_tab(self, dpi, imageId, widx::tab_construction); + } + // Station Tab + { + widget::draw_tab(self, dpi, image_ids::null, widx::tab_station); + if (!self->is_disabled(widx::tab_station)) + { + auto x = self->widgets[widx::tab_station].left + self->x + 1; + auto y = self->widgets[widx::tab_station].top + self->y + 1; + auto width = 29; + auto height = 25; + if (self->current_tab == widx::tab_station - widx::tab_construction) + height++; + + gfx::drawpixelinfo_t* clipped = nullptr; + + if (gfx::clip_drawpixelinfo(&clipped, dpi, x, y, width, height)) + { + clipped->zoom_level = 1; + clipped->width <<= 1; + clipped->height <<= 1; + clipped->x <<= 1; + clipped->y <<= 1; + auto roadStationObj = objectmgr::get(_lastSelectedStationType); + auto imageId = gfx::recolour(roadStationObj->var_0C, companyColour); + gfx::draw_image(clipped, -4, -10, imageId); + auto colour = _byte_5045FA[companyColour]; + if (!(roadStationObj->flags & road_station_flags::recolourable)) + { + colour = 46; + } + imageId = gfx::recolour(roadStationObj->var_0C, colour) + 1; + gfx::draw_image(clipped, -4, -10, imageId); + } + + widget::draw_tab(self, dpi, -2, widx::tab_station); + } + } + // Overhead tab + { + widget::draw_tab(self, dpi, image_ids::null, widx::tab_overhead); + if (!self->is_disabled(widx::tab_station)) + { + auto x = self->widgets[widx::tab_overhead].left + self->x + 2; + auto y = self->widgets[widx::tab_overhead].top + self->y + 2; + + for (auto i = 0; i < 2; i++) + { + if (_modList[i] != 0xFF) + { + auto roadExtraObj = objectmgr::get(_modList[i]); + auto imageId = roadExtraObj->var_0E; + if (self->current_tab == widx::tab_overhead - widx::tab_construction) + imageId += (self->frame_no / 2) % 8; + gfx::draw_image(dpi, x, y, imageId); + } + } + + widget::draw_tab(self, dpi, -2, widx::tab_overhead); + } + } + } + + // 0x0049ED40 + static void drawTrackTabs(window* self, gfx::drawpixelinfo_t* dpi) + { + auto company = companymgr::get(_playerCompany); + auto companyColour = company->mainColours.primary; + auto trackObj = objectmgr::get(_trackType); + // Construction Tab + { + auto imageId = trackObj->var_1E; + if (self->current_tab == widx::tab_construction - widx::tab_construction) + imageId += (self->frame_no / 4) % 15; + gfx::recolour(imageId, companyColour); + + widget::draw_tab(self, dpi, imageId, widx::tab_construction); + } + // Station Tab + { + if (_byte_1136063 & (1 << 7)) + { + auto imageId = objectmgr::get()->img + interface_skin::image_ids::toolbar_menu_airport; + + widget::draw_tab(self, dpi, imageId, widx::tab_station); + } + else + { + if (_byte_1136063 & (1 << 6)) + { + auto imageId = objectmgr::get()->img + interface_skin::image_ids::toolbar_menu_ship_port; + + widget::draw_tab(self, dpi, imageId, widx::tab_station); + } + else + { + widget::draw_tab(self, dpi, image_ids::null, widx::tab_station); + if (!self->is_disabled(widx::tab_station)) + { + auto x = self->widgets[widx::tab_station].left + self->x + 1; + auto y = self->widgets[widx::tab_station].top + self->y + 1; + auto width = 29; + auto height = 25; + if (self->current_tab == widx::tab_station - widx::tab_construction) + height++; + + gfx::drawpixelinfo_t* clipped = nullptr; + + if (gfx::clip_drawpixelinfo(&clipped, dpi, x, y, width, height)) + { + clipped->zoom_level = 1; + clipped->width *= 2; + clipped->height *= 2; + clipped->x *= 2; + clipped->y *= 2; + auto trainStationObj = objectmgr::get(_lastSelectedStationType); + auto imageId = gfx::recolour(trainStationObj->var_0E, companyColour); + gfx::draw_image(clipped, -4, -9, imageId); + auto colour = _byte_5045FA[companyColour]; + if (!(trainStationObj->flags & train_station_flags::recolourable)) + { + colour = 46; + } + imageId = gfx::recolour(imageId, colour) + 1; + gfx::draw_image(clipped, -4, -9, imageId); + } + + widget::draw_tab(self, dpi, -2, widx::tab_station); + } + } + } + } + // Signal Tab + { + widget::draw_tab(self, dpi, image_ids::null, widx::tab_signal); + if (!self->is_disabled(widx::tab_signal)) + { + auto x = self->widgets[widx::tab_signal].left + self->x + 1; + auto y = self->widgets[widx::tab_signal].top + self->y + 1; + auto width = 29; + auto height = 25; + if (self->current_tab == widx::tab_station - widx::tab_construction) + height++; + + gfx::drawpixelinfo_t* clipped = nullptr; + + const std::vector signalFrames2State = { 1, 2, 3, 3, 3, 3, 3, 3, 2, 1, 0, 0, 0, 0, 0 }; + const std::vector signalFrames3State = { 1, 2, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0 }; + const std::vector signalFrames4State = { 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + static const std::vector> signalFrames = { + signalFrames2State, + signalFrames3State, + signalFrames4State, + }; + + if (gfx::clip_drawpixelinfo(&clipped, dpi, x, y, width, height)) + { + auto trainSignalObject = objectmgr::get(_lastSelectedSignal); + auto imageId = trainSignalObject->var_0E; + if (self->current_tab == widx::tab_signal - widx::tab_construction) + { + auto frames = signalFrames[(((trainSignalObject->num_frames + 2) / 3) - 2)]; + auto frameCount = std::size(frames) - 1; + frameCount &= (self->frame_no >> trainSignalObject->var_04); + auto frameIndex = frames[frameCount]; + frameIndex <<= 3; + imageId += frameIndex; + } + gfx::draw_image(clipped, 15, 31, imageId); + } + + widget::draw_tab(self, dpi, -2, widx::tab_signal); + } + } + // Overhead Tab + { + widget::draw_tab(self, dpi, image_ids::null, widx::tab_overhead); + if (!self->is_disabled(widx::tab_station)) + { + auto x = self->widgets[widx::tab_overhead].left + self->x + 2; + auto y = self->widgets[widx::tab_overhead].top + self->y + 2; + + for (auto i = 0; i < 4; i++) + { + if (_modList[i] != 0xFF) + { + auto trackExtraObj = objectmgr::get(_modList[i]); + auto imageId = trackExtraObj->var_0E; + if (self->current_tab == widx::tab_overhead - widx::tab_construction) + imageId += (self->frame_no / 2) % 8; + gfx::draw_image(dpi, x, y, imageId); + } + } + + widget::draw_tab(self, dpi, -2, widx::tab_overhead); + } + } + } + + // 0x0049ED33 + void drawTabs(window* self, gfx::drawpixelinfo_t* dpi) + { + if (_trackType & (1 << 7)) + { + drawRoadTabs(self, dpi); + } + else + { + drawTrackTabs(self, dpi); + } + } + + // 0x004A09DE + void repositionTabs(window* self) + { + int16_t xPos = self->widgets[widx::tab_construction].left; + const int16_t tabWidth = self->widgets[widx::tab_construction].right - xPos; + + for (uint8_t i = widx::tab_construction; i <= widx::tab_overhead; i++) + { + if (self->is_disabled(i)) + { + self->widgets[i].type = widget_type::none; + continue; + } + + self->widgets[i].type = widget_type::wt_8; + self->widgets[i].left = xPos; + self->widgets[i].right = xPos + tabWidth; + xPos = self->widgets[i].right + 1; + } + } + + // 0x0049FEC7 + void sub_49FEC7() + { + registers regs; + call(0x0049FEC7, regs); + } + + // 0x0049DD14 + void on_close(window* self) + { + sub_49FEC7(); + WindowManager::viewportSetVisibility(WindowManager::viewport_visibility::reset); + tilemgr::map_invalidate_map_selection_tiles(); + _mapSelectionFlags = _mapSelectionFlags & ~MapSelectFlag::enableConstruct; + windows::hideDirectionArrows(); + windows::hideGridlines(); + } + + // 0x0049E437, 0x0049E76F, 0x0049ECD1 + void on_update(window* self, uint8_t flag) + { + self->frame_no++; + self->call_prepare_draw(); + WindowManager::invalidateWidget(WindowType::construction, self->number, self->current_tab + common::widx::tab_construction); + + if (input::is_tool_active(WindowType::construction, self->number)) + return; + + if (!(_byte_522096 & flag)) + return; + + sub_49FEC7(); + } + + void init_events() + { + construction::init_events(); + station::init_events(); + signal::init_events(); + overhead::init_events(); + } + + // 0x004A0832 + std::optional getRoadPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation) + { + if (trackPiece == 0xFF) + return std::nullopt; + + uint8_t id = 0; + + switch (trackPiece) + { + case common::trackPiece::straight: // 0x004A0856 + { + if (rotation >= 4) + return std::nullopt; + switch (gradient) + { + default: + return std::nullopt; + case common::trackGradient::level: + id = 0; + break; + case common::trackGradient::slope_up: + id = 5; + break; + case common::trackGradient::slope_down: + id = 6; + break; + case common::trackGradient::steep_slope_up: + id = 7; + break; + case common::trackGradient::steep_slope_down: + id = 8; + break; + } + break; + } + case common::trackPiece::left_hand_curve_very_small: // 0x004A08A5 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 4) + return std::nullopt; + id = 1; + break; + } + case common::trackPiece::right_hand_curve_very_small: // 0x004A08CD + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 4) + return std::nullopt; + id = 2; + break; + } + case common::trackPiece::left_hand_curve_small: // 0x004A08ED + { + if (rotation >= 4) + return std::nullopt; + id = 3; + if (gradient != common::trackGradient::level) + return std::nullopt; + break; + } + case common::trackPiece::right_hand_curve_small: // 0x004A08FB + { + if (rotation >= 4) + return std::nullopt; + id = 4; + if (gradient != common::trackGradient::level) + return std::nullopt; + break; + } + case common::trackPiece::left_hand_curve: // 0x004A095F + case common::trackPiece::right_hand_curve: + case common::trackPiece::left_hand_curve_large: + case common::trackPiece::right_hand_curve_large: + case common::trackPiece::s_bend_left: + case common::trackPiece::s_bend_right: + case common::trackPiece::s_bend_to_dual_track: + case common::trackPiece::s_bend_to_single_track: + { + return std::nullopt; + } + case common::trackPiece::turnaround: // 0x004A0909 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 12) + return std::nullopt; + id = 9; + break; + } + } + + if (rotation < 12) + rotation &= 3; + + return trackPieceId{ id, rotation }; + } + + // 0x004A04F8 + std::optional getTrackPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation) + { + if (trackPiece == 0xFF) + return std::nullopt; + + uint8_t id = 0; + + switch (trackPiece) + { + case common::trackPiece::straight: // loc_4A051C + { + if (rotation >= 12) + { + id = 1; + if (gradient != common::trackGradient::level) + return std::nullopt; + } + else + { + if (rotation >= 8) + { + switch (gradient) + { + default: + return std::nullopt; + case common::trackGradient::level: + id = 27; + break; + case common::trackGradient::steep_slope_up: + id = 35; + break; + case common::trackGradient::steep_slope_down: + id = 37; + break; + } + } + else if (rotation >= 4) + { + switch (gradient) + { + default: + return std::nullopt; + case common::trackGradient::level: + id = 26; + break; + case common::trackGradient::steep_slope_up: + id = 34; + break; + case common::trackGradient::steep_slope_down: + id = 36; + break; + } + } + else + { + switch (gradient) + { + default: + return std::nullopt; + case common::trackGradient::level: + id = 0; + break; + case common::trackGradient::slope_up: + id = 14; + break; + case common::trackGradient::slope_down: + id = 15; + break; + case common::trackGradient::steep_slope_up: + id = 16; + break; + case common::trackGradient::steep_slope_down: + id = 17; + break; + } + } + } + break; + } + + case common::trackPiece::left_hand_curve_very_small: // loc_4A05C3 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 12) + return std::nullopt; + if (rotation >= 8) + { + id = 29; + break; + } + if (rotation >= 4) + { + id = 28; + break; + } + id = 2; + break; + } + + case common::trackPiece::right_hand_curve_very_small: // loc_4A05F4 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 12) + return std::nullopt; + if (rotation >= 8) + { + id = 31; + break; + } + if (rotation >= 4) + { + id = 30; + break; + } + id = 3; + break; + } + + case common::trackPiece::left_hand_curve_small: // loc_4A0625 + { + if (rotation >= 4) + return std::nullopt; + switch (gradient) + { + default: + return std::nullopt; + case common::trackGradient::level: + id = 4; + break; + case common::trackGradient::slope_up: + id = 18; + break; + case common::trackGradient::slope_down: + id = 20; + break; + case common::trackGradient::steep_slope_up: + id = 22; + break; + case common::trackGradient::steep_slope_down: + id = 24; + break; + } + break; + } + + case common::trackPiece::right_hand_curve_small: // loc_4A066A + { + if (rotation >= 4) + return std::nullopt; + switch (gradient) + { + default: + return std::nullopt; + case common::trackGradient::level: + id = 5; + break; + case common::trackGradient::slope_up: + id = 19; + break; + case common::trackGradient::slope_down: + id = 21; + break; + case common::trackGradient::steep_slope_up: + id = 23; + break; + case common::trackGradient::steep_slope_down: + id = 25; + break; + } + break; + } + + case common::trackPiece::left_hand_curve: // loc_4A06AF + { + if (rotation >= 4) + return std::nullopt; + id = 6; + if (gradient != common::trackGradient::level) + return std::nullopt; + break; + } + + case common::trackPiece::right_hand_curve: // loc_4A06C8 + { + if (rotation >= 4) + return std::nullopt; + id = 7; + if (gradient != common::trackGradient::level) + return std::nullopt; + break; + } + + case common::trackPiece::left_hand_curve_large: // loc_4A06E1 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + id = 10; + if (rotation >= 12) + break; + if (rotation >= 4) + return std::nullopt; + id = 8; + break; + } + + case common::trackPiece::right_hand_curve_large: // loc_4A0705 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + id = 11; + if (rotation >= 12) + break; + if (rotation >= 4) + return std::nullopt; + id = 9; + break; + } + + case common::trackPiece::s_bend_left: // loc_4A0729 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 12) + return std::nullopt; + id = 33; + if (rotation >= 8) + break; + if (rotation >= 4) + return std::nullopt; + id = 12; + break; + } + + case common::trackPiece::s_bend_right: // loc_4A0756 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 8) + return std::nullopt; + id = 32; + if (rotation >= 4) + break; + id = 13; + break; + } + + case common::trackPiece::s_bend_to_dual_track: // loc_4A077C + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 8) + return std::nullopt; + id = 40; + if (rotation >= 4) + break; + id = 38; + break; + } + + case common::trackPiece::s_bend_to_single_track: // loc_4A07A2 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 12) + return std::nullopt; + id = 41; + if (rotation >= 8) + break; + if (rotation >= 4) + return std::nullopt; + id = 39; + break; + } + + case common::trackPiece::turnaround: // loc_4A07C0 + { + if (gradient != common::trackGradient::level) + return std::nullopt; + if (rotation >= 12) + return std::nullopt; + id = 43; + if (rotation >= 8) + break; + id = 42; + if (rotation >= 4) + return std::nullopt; + break; + } + } + + if (rotation < 12) + rotation &= 3; + + return trackPieceId{ id, rotation }; + } + + static void activateSelectedRoadWidgets(window* window) + { + tilemgr::map_invalidate_map_selection_tiles(); + _mapSelectionFlags = _mapSelectionFlags | (1 << 3) | (1 << 1); + + auto road = getRoadPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + + uint8_t rotation; + uint8_t roadId; + + auto x = _x; + auto y = _y; + + if (!road) + { + rotation = _constructionRotation; + roadId = 0; + } + else + { + rotation = road->rotation; + roadId = road->id; + } + + auto roadPiece = roadPieces[roadId]; + auto i = 0; + auto posId = 0; + + while (roadPiece[i].index != 0xFF) + { + if (roadPiece[i].flags & previewTrackFlags::diagonal) + { + i++; + continue; + } + + map_pos pos = { roadPiece[i].x, roadPiece[i].y }; + + pos = rotate2DCoordinate(pos, rotation); + + pos.x += x; + pos.y += y; + _mapSelectedTiles[posId] = pos; + posId++; + i++; + } + + _mapSelectedTiles[posId].x = -1; + map_invalidate_map_selection_tiles(); + window->holdable_widgets = 0; + auto trackType = _trackType & ~(1 << 7); + auto roadObj = objectmgr::get(trackType); + + window->widgets[construction::widx::s_bend_left].type = widget_type::none; + window->widgets[construction::widx::s_bend_right].type = widget_type::none; + window->widgets[construction::widx::left_hand_curve_large].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve_large].type = widget_type::none; + window->widgets[construction::widx::left_hand_curve].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve].type = widget_type::none; + window->widgets[construction::widx::left_hand_curve_small].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve_small].type = widget_type::none; + window->widgets[construction::widx::left_hand_curve_very_small].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve_very_small].type = widget_type::none; + + window->widgets[construction::widx::left_hand_curve_small].left = 3; + window->widgets[construction::widx::left_hand_curve_small].right = 24; + window->widgets[construction::widx::right_hand_curve_small].left = 113; + window->widgets[construction::widx::right_hand_curve_small].right = 134; + window->widgets[construction::widx::left_hand_curve].left = 25; + window->widgets[construction::widx::left_hand_curve].right = 46; + window->widgets[construction::widx::right_hand_curve].left = 91; + window->widgets[construction::widx::right_hand_curve].right = 112; + + if (roadObj->road_pieces & road_piece_flags::track) + { + window->widgets[construction::widx::left_hand_curve_small].left = 25; + window->widgets[construction::widx::left_hand_curve_small].right = 46; + window->widgets[construction::widx::right_hand_curve_small].left = 91; + window->widgets[construction::widx::right_hand_curve_small].right = 112; + window->widgets[construction::widx::left_hand_curve].left = 47; + window->widgets[construction::widx::left_hand_curve].right = 68; + window->widgets[construction::widx::right_hand_curve].left = 69; + window->widgets[construction::widx::right_hand_curve].right = 90; + + window->widgets[construction::widx::left_hand_curve_very_small].type = widget_type::wt_9; + window->widgets[construction::widx::right_hand_curve_very_small].type = widget_type::wt_9; + } + + if (roadObj->road_pieces & road_piece_flags::one_way) + { + window->widgets[construction::widx::left_hand_curve_small].type = widget_type::wt_9; + window->widgets[construction::widx::right_hand_curve_small].type = widget_type::wt_9; + } + + window->widgets[construction::widx::s_bend_dual_track_left].type = widget_type::none; + window->widgets[construction::widx::s_bend_dual_track_right].type = widget_type::none; + + if (roadObj->road_pieces & road_piece_flags::one_sided) + { + window->widgets[construction::widx::s_bend_dual_track_left].type = widget_type::wt_9; + window->widgets[construction::widx::s_bend_dual_track_left].image = image_ids::construction_right_turnaround; + window->widgets[construction::widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_turnaround; + + if (_byte_525FAE == 0) + window->widgets[construction::widx::s_bend_dual_track_left].image = image_ids::construction_left_turnaround; + } + + window->widgets[construction::widx::steep_slope_down].type = widget_type::none; + window->widgets[construction::widx::slope_down].type = widget_type::none; + window->widgets[construction::widx::slope_up].type = widget_type::none; + window->widgets[construction::widx::steep_slope_up].type = widget_type::none; + + if (roadObj->road_pieces & road_piece_flags::slope) + { + window->widgets[construction::widx::slope_down].type = widget_type::wt_9; + window->widgets[construction::widx::slope_up].type = widget_type::wt_9; + } + + if (roadObj->road_pieces & road_piece_flags::steep_slope) + { + window->widgets[construction::widx::steep_slope_down].type = widget_type::wt_9; + window->widgets[construction::widx::steep_slope_up].type = widget_type::wt_9; + } + + window->widgets[construction::widx::bridge].type = widget_type::wt_18; + window->widgets[construction::widx::bridge_dropdown].type = widget_type::wt_11; + + if (_lastSelectedBridge == 0xFF || (_constructionHover != 1 && !(_byte_1136076 & 1))) + { + window->widgets[construction::widx::bridge].type = widget_type::none; + window->widgets[construction::widx::bridge_dropdown].type = widget_type::none; + } + + auto activatedWidgets = window->activated_widgets; + activatedWidgets &= ~(construction::allTrack); + + window->widgets[construction::widx::construct].type = widget_type::none; + window->widgets[construction::widx::remove].type = widget_type::wt_9; + window->widgets[construction::widx::rotate_90].type = widget_type::none; + + if (_constructionHover == 1) + { + window->widgets[construction::widx::construct].type = widget_type::wt_5; + window->widgets[construction::widx::construct].tooltip = string_ids::tooltip_start_construction; + window->widgets[construction::widx::remove].type = widget_type::none; + window->widgets[construction::widx::rotate_90].type = widget_type::wt_9; + window->widgets[construction::widx::rotate_90].image = image_ids::rotate_object; + window->widgets[construction::widx::rotate_90].tooltip = string_ids::rotate_90; + } + else if (_constructionHover == 0) + { + window->widgets[construction::widx::construct].type = widget_type::wt_3; + window->widgets[construction::widx::construct].tooltip = string_ids::tooltip_construct; + window->widgets[construction::widx::rotate_90].type = widget_type::wt_9; + window->widgets[construction::widx::rotate_90].image = image_ids::construction_new_position; + window->widgets[construction::widx::rotate_90].tooltip = string_ids::new_construction_position; + } + if (_constructionHover == 0 || _constructionHover == 1) + { + if (_lastSelectedTrackPiece != 0xFF) + { + auto trackPieceWidget = trackPieceWidgets[_lastSelectedTrackPiece]; + activatedWidgets |= 1ULL << trackPieceWidget; + } + + uint8_t trackGradient = construction::widx::level; + + switch (_lastSelectedTrackGradient) + { + case common::trackGradient::level: + trackGradient = construction::widx::level; + break; + + case common::trackGradient::slope_up: + trackGradient = construction::widx::slope_up; + break; + + case common::trackGradient::slope_down: + trackGradient = construction::widx::slope_down; + break; + + case common::trackGradient::steep_slope_up: + trackGradient = construction::widx::steep_slope_up; + break; + + case common::trackGradient::steep_slope_down: + trackGradient = construction::widx::steep_slope_down; + break; + } + + activatedWidgets |= 1ULL << trackGradient; + } + window->activated_widgets = activatedWidgets; + window->invalidate(); + } + + static void activateSelectedTrackWidgets(window* window) + { + tilemgr::map_invalidate_map_selection_tiles(); + _mapSelectionFlags = _mapSelectionFlags | (1 << 3) | (1 << 1); + + auto track = getTrackPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + + uint8_t rotation; + uint8_t trackId; + auto x = _x; + auto y = _y; + + if (!track) + { + rotation = _constructionRotation; + trackId = 0; + } + else + { + rotation = track->rotation; + trackId = track->id; + } + + auto trackPiece = trackPieces[trackId]; + auto i = 0; + auto posId = 0; + + while (trackPiece[i].index != 0xFF) + { + if (trackPiece[i].flags & previewTrackFlags::diagonal) + { + i++; + continue; + } + map_pos pos = { trackPiece[i].x, trackPiece[i].y }; + + pos = rotate2DCoordinate(pos, rotation); + + pos.x += x; + pos.y += y; + _mapSelectedTiles[posId] = pos; + posId++; + i++; + } + + _mapSelectedTiles[posId].x = -1; + map_invalidate_map_selection_tiles(); + window->holdable_widgets = 0; + + auto trackObj = objectmgr::get(_trackType); + + window->widgets[construction::widx::s_bend_left].type = widget_type::wt_9; + window->widgets[construction::widx::s_bend_right].type = widget_type::wt_9; + window->widgets[construction::widx::left_hand_curve_large].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve_large].type = widget_type::none; + window->widgets[construction::widx::left_hand_curve].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve].type = widget_type::none; + window->widgets[construction::widx::left_hand_curve_small].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve_small].type = widget_type::none; + window->widgets[construction::widx::left_hand_curve_very_small].type = widget_type::none; + window->widgets[construction::widx::right_hand_curve_very_small].type = widget_type::none; + + window->widgets[construction::widx::left_hand_curve_small].left = 3; + window->widgets[construction::widx::left_hand_curve_small].right = 24; + window->widgets[construction::widx::right_hand_curve_small].left = 113; + window->widgets[construction::widx::right_hand_curve_small].right = 134; + window->widgets[construction::widx::left_hand_curve].left = 25; + window->widgets[construction::widx::left_hand_curve].right = 46; + window->widgets[construction::widx::right_hand_curve].left = 91; + window->widgets[construction::widx::right_hand_curve].right = 112; + + if (trackObj->track_pieces & track_piece_flags::very_small_curve) + { + window->widgets[construction::widx::left_hand_curve_small].left = 25; + window->widgets[construction::widx::left_hand_curve_small].right = 46; + window->widgets[construction::widx::right_hand_curve_small].left = 91; + window->widgets[construction::widx::right_hand_curve_small].right = 112; + window->widgets[construction::widx::left_hand_curve].left = 47; + window->widgets[construction::widx::left_hand_curve].right = 68; + window->widgets[construction::widx::right_hand_curve].left = 69; + window->widgets[construction::widx::right_hand_curve].right = 90; + + window->widgets[construction::widx::left_hand_curve_very_small].type = widget_type::wt_9; + window->widgets[construction::widx::right_hand_curve_very_small].type = widget_type::wt_9; + } + + if (trackObj->track_pieces & track_piece_flags::large_curve) + { + window->widgets[construction::widx::left_hand_curve_large].type = widget_type::wt_9; + window->widgets[construction::widx::right_hand_curve_large].type = widget_type::wt_9; + } + + if (trackObj->track_pieces & track_piece_flags::normal_curve) + { + window->widgets[construction::widx::left_hand_curve].type = widget_type::wt_9; + window->widgets[construction::widx::right_hand_curve].type = widget_type::wt_9; + } + + if (trackObj->track_pieces & track_piece_flags::small_curve) + { + window->widgets[construction::widx::left_hand_curve_small].type = widget_type::wt_9; + window->widgets[construction::widx::right_hand_curve_small].type = widget_type::wt_9; + } + + window->widgets[construction::widx::s_bend_dual_track_left].type = widget_type::none; + window->widgets[construction::widx::s_bend_dual_track_right].type = widget_type::none; + + if (trackObj->track_pieces & track_piece_flags::one_sided) + { + window->widgets[construction::widx::s_bend_dual_track_left].type = widget_type::wt_9; + window->widgets[construction::widx::s_bend_dual_track_right].type = widget_type::wt_9; + window->widgets[construction::widx::s_bend_dual_track_left].image = image_ids::construction_s_bend_dual_track_left; + window->widgets[construction::widx::s_bend_dual_track_right].image = image_ids::construction_s_bend_dual_track_right; + window->widgets[construction::widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_s_bend_left_dual_track; + window->widgets[construction::widx::s_bend_dual_track_right].tooltip = string_ids::tooltip_s_bend_right_dual_track; + + _byte_522090 = 16; + _byte_522091 = 20; + + if (_constructionRotation >= 4 && _constructionRotation < 12) + { + window->widgets[construction::widx::s_bend_dual_track_left].image = image_ids::construction_right_turnaround; + window->widgets[construction::widx::s_bend_dual_track_right].image = image_ids::construction_s_bend_to_single_track_left; + window->widgets[construction::widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_turnaround; + window->widgets[construction::widx::s_bend_dual_track_right].tooltip = string_ids::tooltip_s_bend_to_single_track; + _byte_522090 = 20; + _byte_522092 = 16; + if (_constructionRotation >= 8) + { + window->widgets[construction::widx::s_bend_dual_track_left].image = image_ids::construction_s_bend_to_single_track_right; + window->widgets[construction::widx::s_bend_dual_track_right].image = image_ids::construction_left_turnaround; + window->widgets[construction::widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_s_bend_to_single_track; + window->widgets[construction::widx::s_bend_dual_track_right].tooltip = string_ids::tooltip_turnaround; + _byte_522091 = 16; + _byte_522092 = 20; + } + } + } + window->widgets[construction::widx::steep_slope_down].type = widget_type::none; + window->widgets[construction::widx::slope_down].type = widget_type::none; + window->widgets[construction::widx::slope_up].type = widget_type::none; + window->widgets[construction::widx::steep_slope_up].type = widget_type::none; + + if (trackObj->track_pieces & track_piece_flags::slope) + { + window->widgets[construction::widx::slope_down].type = widget_type::wt_9; + window->widgets[construction::widx::slope_up].type = widget_type::wt_9; + } + + if (trackObj->track_pieces & track_piece_flags::steep_slope) + { + window->widgets[construction::widx::steep_slope_down].type = widget_type::wt_9; + window->widgets[construction::widx::steep_slope_up].type = widget_type::wt_9; + } + + window->widgets[construction::widx::bridge].type = widget_type::wt_18; + window->widgets[construction::widx::bridge_dropdown].type = widget_type::wt_11; + + if (_lastSelectedBridge == 0xFF || (_constructionHover != 1 && !(_byte_1136076 & 1))) + { + window->widgets[construction::widx::bridge].type = widget_type::none; + window->widgets[construction::widx::bridge_dropdown].type = widget_type::none; + } + + auto activatedWidgets = window->activated_widgets; + activatedWidgets &= ~(construction::allTrack); + + window->widgets[construction::widx::construct].type = widget_type::none; + window->widgets[construction::widx::remove].type = widget_type::wt_9; + window->widgets[construction::widx::rotate_90].type = widget_type::none; + + if (_constructionHover == 1) + { + window->widgets[construction::widx::construct].type = widget_type::wt_5; + window->widgets[construction::widx::construct].tooltip = string_ids::tooltip_start_construction; + window->widgets[construction::widx::remove].type = widget_type::none; + window->widgets[construction::widx::rotate_90].type = widget_type::wt_9; + window->widgets[construction::widx::rotate_90].image = image_ids::rotate_object; + window->widgets[construction::widx::rotate_90].tooltip = string_ids::rotate_90; + } + else if (_constructionHover == 0) + { + window->widgets[construction::widx::construct].type = widget_type::wt_3; + window->widgets[construction::widx::construct].tooltip = string_ids::tooltip_construct; + window->widgets[construction::widx::rotate_90].type = widget_type::wt_9; + window->widgets[construction::widx::rotate_90].image = image_ids::construction_new_position; + window->widgets[construction::widx::rotate_90].tooltip = string_ids::new_construction_position; + } + if (_constructionHover == 0 || _constructionHover == 1) + { + if (_lastSelectedTrackPiece != 0xFF) + { + auto trackPieceWidget = trackPieceWidgets[_lastSelectedTrackPiece]; + activatedWidgets |= 1ULL << trackPieceWidget; + } + + uint8_t trackGradient = construction::widx::level; + + switch (_lastSelectedTrackGradient) + { + case common::trackGradient::level: + trackGradient = construction::widx::level; + break; + + case common::trackGradient::slope_up: + trackGradient = construction::widx::slope_up; + break; + + case common::trackGradient::slope_down: + trackGradient = construction::widx::slope_down; + break; + + case common::trackGradient::steep_slope_up: + trackGradient = construction::widx::steep_slope_up; + break; + + case common::trackGradient::steep_slope_down: + trackGradient = construction::widx::steep_slope_down; + break; + } + + activatedWidgets |= 1ULL << trackGradient; + } + window->activated_widgets = activatedWidgets; + window->invalidate(); + } + + // 0x0049F1B5 + void activateSelectedConstructionWidgets() + { + auto window = WindowManager::find(WindowType::construction); + + if (window == nullptr) + return; + + if (_trackType & (1 << 7)) + { + activateSelectedRoadWidgets(window); + } + else + { + activateSelectedTrackWidgets(window); + } + } + + // 0x004CD454 + void sub_4CD454() + { + if (isTrackUpgradeMode()) + { + auto window = WindowManager::find(_toolWindowType, _toolWindowNumber); + if (window != nullptr) + input::cancel_tool(); + } + } + + // 0x004A3A06 + void setTrackOptions(const uint8_t trackType) + { + auto newTrackType = trackType; + if (trackType & (1 << 7)) + { + newTrackType &= ~(1 << 7); + auto roadObj = objectmgr::get(newTrackType); + if (!(roadObj->flags & flags_12::unk_01)) + _lastRoadOption = trackType; + else + _lastRailroadOption = trackType; + } + else + { + auto trackObj = objectmgr::get(newTrackType); + if (!(trackObj->flags & flags_22::unk_02)) + _lastRailroadOption = trackType; + else + _lastRoadOption = trackType; + } + WindowManager::invalidate(WindowType::topToolbar, 0); + } + + // 0x0049CE33 + void setDisabledWidgets(window* self) + { + auto disabledWidgets = 0; + if (is_editor_mode()) + disabledWidgets |= (1ULL << common::widx::tab_station); + + if (_byte_1136063 & (1 << 7 | 1 << 6)) + disabledWidgets |= (1ULL << common::widx::tab_construction); + + if (_lastSelectedSignal == 0xFF) + disabledWidgets |= (1ULL << common::widx::tab_signal); + + if (_modList[0] == 0xFF && _modList[1] == 0xFF && _modList[2] == 0xFF && _modList[3] == 0xFF) + disabledWidgets |= (1ULL << common::widx::tab_overhead); + + if (_lastSelectedStationType == 0xFF) + disabledWidgets |= (1ULL << common::widx::tab_station); + + self->disabled_widgets = disabledWidgets; + } + + // 0x004A0963 + void createConstructionWindow() + { + auto window = WindowManager::createWindow( + WindowType::construction, + construction::windowSize, + window_flags::flag_11 | window_flags::no_auto_close, + &construction::events); + + window->widgets = construction::widgets; + window->current_tab = 0; + window->enabled_widgets = construction::enabledWidgets; + window->activated_widgets = 0; + + setDisabledWidgets(window); + + window->init_scroll_widgets(); + window->owner = _playerCompany; + + auto skin = objectmgr::get(); + window->colours[1] = skin->colour_0D; + + WindowManager::sub_4CEE0B(window); + ui::windows::showDirectionArrows(); + ui::windows::showGridlines(); + + common::init_events(); + } + + // 0x004723BD + static void sortList(uint8_t* list) + { + size_t count = 0; + while (list[count] != 0xFF) + { + count++; + } + while (count > 1) + { + for (size_t i = 1; i < count; i++) + { + uint8_t item1 = list[i]; + uint8_t item2 = list[i - 1]; + if (item2 > item1) + { + list[i - 1] = item1; + list[i] = item2; + } + } + count--; + } + } + + // 0x0048D70C + void refreshAirportList(uint8_t* stationList) + { + auto currentYear = current_year(); + auto airportCount = 0; + for (uint8_t i = 0; i < objectmgr::get_max_objects(object_type::airport); i++) + { + auto airportObj = objectmgr::get(i); + if (airportObj == nullptr) + continue; + if (currentYear < airportObj->designed_year) + continue; + if (currentYear > airportObj->obsolete_year) + continue; + stationList[airportCount] = i; + airportCount++; + } + stationList[airportCount] = 0xFF; + + sortList(stationList); + } + + // 0x0048D753 + void refreshDockList(uint8_t* stationList) + { + auto currentYear = current_year(); + auto dockCount = 0; + for (uint8_t i = 0; i < objectmgr::get_max_objects(object_type::dock); i++) + { + auto dockObj = objectmgr::get(i); + if (dockObj == nullptr) + continue; + if (currentYear < dockObj->designed_year) + continue; + if (currentYear > dockObj->obsolete_year) + continue; + stationList[dockCount] = i; + dockCount++; + } + stationList[dockCount] = 0xFF; + + sortList(stationList); + } + + // 0x0048D678, 0x0048D5E4 + void refreshStationList(uint8_t* stationList, uint8_t trackType, TransportMode transportMode) + { + auto currentYear = current_year(); + auto stationCount = 0; + + if (transportMode == TransportMode::road) + { + trackType &= ~(1 << 7); + auto roadObj = objectmgr::get(trackType); + + for (auto i = 0; i < roadObj->num_stations; i++) + { + auto station = roadObj->stations[i]; + if (station == 0xFF) + continue; + auto roadStationObj = objectmgr::get(station); + if (currentYear < roadStationObj->designed_year) + continue; + if (currentYear > roadStationObj->obsolete_year) + continue; + stationList[stationCount] = station; + stationCount++; + } + } + + if (transportMode == TransportMode::rail) + { + auto trackObj = objectmgr::get(trackType); + + for (auto i = 0; i < trackObj->num_stations; i++) + { + auto station = trackObj->stations[i]; + if (station == 0xFF) + continue; + auto trainStationObj = objectmgr::get(station); + if (currentYear < trainStationObj->designed_year) + continue; + if (currentYear > trainStationObj->obsolete_year) + continue; + stationList[stationCount] = station; + stationCount++; + } + } + + for (uint8_t i = 0; i < objectmgr::get_max_objects(object_type::road_station); i++) + { + uint8_t numCompatible; + uint8_t* mods; + uint16_t designedYear; + uint16_t obsoleteYear; + + if (transportMode == TransportMode::road) + { + auto roadStationObj = objectmgr::get(i); + + if (roadStationObj == nullptr) + continue; + + numCompatible = roadStationObj->num_compatible; + mods = roadStationObj->mods; + designedYear = roadStationObj->designed_year; + obsoleteYear = roadStationObj->obsolete_year; + } + else if (transportMode == TransportMode::rail) + { + auto trainStationObj = objectmgr::get(i); + + if (trainStationObj == nullptr) + continue; + + numCompatible = trainStationObj->num_compatible; + mods = trainStationObj->mods; + designedYear = trainStationObj->designed_year; + obsoleteYear = trainStationObj->obsolete_year; + } + else + { + return; + } + + for (auto modCount = 0; modCount < numCompatible; modCount++) + { + if (trackType != mods[modCount]) + continue; + if (currentYear < designedYear) + continue; + if (currentYear > obsoleteYear) + continue; + for (size_t k = 0; k < std::size(_stationList); k++) + { + if (&stationList[k] == &stationList[stationCount]) + { + stationList[stationCount] = i; + stationCount++; + break; + } + if (i == stationList[k]) + break; + } + } + } + + stationList[stationCount] = 0xFF; + + sortList(stationList); + } + + // 0x0042C518, 0x0042C490 + void refreshBridgeList(uint8_t* bridgeList, uint8_t trackType, TransportMode transportMode) + { + auto currentYear = current_year(); + auto bridgeCount = 0; + + if (transportMode == TransportMode::road) + { + trackType &= ~(1 << 7); + auto roadObj = objectmgr::get(trackType); + for (auto i = 0; i < roadObj->num_bridges; i++) + { + auto bridge = roadObj->bridges[i]; + if (bridge == 0xFF) + continue; + auto bridgeObj = objectmgr::get(bridge); + if (currentYear < bridgeObj->designed_year) + continue; + bridgeList[bridgeCount] = bridge; + bridgeCount++; + } + } + + if (transportMode == TransportMode::rail) + { + auto trackObj = objectmgr::get(trackType); + for (auto i = 0; i < trackObj->num_bridges; i++) + { + auto bridge = trackObj->bridges[i]; + if (bridge == 0xFF) + continue; + auto bridgeObj = objectmgr::get(bridge); + if (currentYear < bridgeObj->designed_year) + continue; + bridgeList[bridgeCount] = bridge; + bridgeCount++; + } + } + + for (uint8_t i = 0; i < objectmgr::get_max_objects(object_type::bridge); i++) + { + auto bridgeObj = objectmgr::get(i); + if (bridgeObj == nullptr) + continue; + + uint8_t numCompatible; + uint8_t* mods; + + if (transportMode == TransportMode::road) + { + numCompatible = bridgeObj->road_num_compatible; + mods = bridgeObj->road_mods; + } + else if (transportMode == TransportMode::rail) + { + numCompatible = bridgeObj->track_num_compatible; + mods = bridgeObj->track_mods; + } + else + { + return; + } + + for (auto modCount = 0; modCount < numCompatible; modCount++) + { + if (trackType != mods[modCount]) + continue; + if (currentYear < bridgeObj->designed_year) + continue; + for (size_t k = 0; k < std::size(_bridgeList); k++) + { + if (&bridgeList[k] == &bridgeList[bridgeCount]) + { + _bridgeList[bridgeCount] = i; + bridgeCount++; + break; + } + if (i == bridgeList[k]) + break; + } + } + } + + _bridgeList[bridgeCount] = 0xFF; + + sortList(_bridgeList); + } + + // 0x004781C5, 0x004A693D + void refreshModList(uint8_t* modList, uint8_t trackType, TransportMode transportMode) + { + if (transportMode == TransportMode::road) + { + trackType &= ~(1 << 7); + } + + auto companyId = _updatingCompanyId; + + modList[0] = 0xFF; + modList[1] = 0xFF; + modList[2] = 0xFF; + modList[3] = 0xFF; + auto flags = 0; + + for (uint8_t vehicle = 0; vehicle < objectmgr::get_max_objects(object_type::vehicle); vehicle++) + { + auto vehicleObj = objectmgr::get(vehicle); + + if (vehicleObj == nullptr) + continue; + + if (vehicleObj->mode != transportMode) + continue; + + if (trackType != vehicleObj->track_type) + continue; + + auto company = companymgr::get(companyId); + + if (!company->isVehicleIndexUnlocked(vehicle)) + continue; + + for (auto i = 0; i < vehicleObj->num_mods; i++) + { + flags |= 1ULL << vehicleObj->required_track_extras[i]; + } + + if (!(vehicleObj->flags & flags_E0::rack_rail)) + continue; + + flags |= 1ULL << vehicleObj->rack_rail_type; + } + + if (transportMode == TransportMode::road) + { + auto roadObj = objectmgr::get(trackType); + + for (auto i = 0; i < roadObj->num_mods; i++) + { + if (flags & (1 << roadObj->mods[i])) + modList[i] = roadObj->mods[i]; + } + } + + if (transportMode == TransportMode::rail) + { + auto trackObj = objectmgr::get(trackType); + + for (auto i = 0; i < trackObj->num_mods; i++) + { + if (flags & (1 << trackObj->mods[i])) + modList[i] = trackObj->mods[i]; + } + } + } + + // 0x004A3A50 + void sub_4A3A50() + { + common::sub_49FEC7(); + setTrackOptions(_trackType); + refreshStationList(_stationList, _trackType, TransportMode::road); + + auto lastStation = _scenarioRoadStations[(_trackType & ~(1ULL << 7))]; + if (lastStation == 0xFF) + lastStation = _stationList[0]; + _lastSelectedStationType = lastStation; + + refreshBridgeList(_bridgeList, _trackType, TransportMode::road); + + auto lastBridge = _scenarioBridges[(_trackType & ~(1ULL << 7))]; + if (lastBridge == 0xFF) + lastBridge = _bridgeList[0]; + _lastSelectedBridge = lastBridge; + + refreshModList(_modList, _trackType, TransportMode::road); + + auto lastMod = _scenarioRoadMods[(_trackType & ~(1ULL << 7))]; + if (lastMod == 0xFF) + lastMod = 0; + _lastSelectedMods = lastMod; + + auto window = WindowManager::find(WindowType::construction); + + if (window != nullptr) + { + setDisabledWidgets(window); + } + common::activateSelectedConstructionWidgets(); + } + + // 0x00488B4D + void refreshSignalList(uint8_t* signalList, uint8_t trackType) + { + auto currentYear = current_year(); + auto trackObj = objectmgr::get(trackType); + auto signalCount = 0; + auto var_0E = trackObj->var_0E; + while (var_0E > 0) + { + auto ecx = utility::bitscanforward(var_0E); + if (ecx == -1) + break; + var_0E &= ~(1 << ecx); + auto signalObj = objectmgr::get(ecx); + + if (currentYear > signalObj->obsolete_year) + continue; + if (currentYear < signalObj->designed_year) + continue; + signalList[signalCount] = ecx; + signalCount++; + } + + for (uint8_t i = 0; i < objectmgr::get_max_objects(object_type::track_signal); i++) + { + auto signalObj = objectmgr::get(i); + if (signalObj == nullptr) + continue; + for (auto modCount = 0; modCount < signalObj->num_compatible; modCount++) + { + if (trackType != objectmgr::get(i)->mods[modCount]) + continue; + if (currentYear < signalObj->designed_year) + continue; + if (currentYear > signalObj->obsolete_year) + continue; + for (size_t k = 0; k < std::size(_signalList); k++) + { + if (&signalList[k] == &signalList[signalCount]) + { + signalList[signalCount] = i; + signalCount++; + break; + } + if (i == signalList[k]) + break; + } + } + } + + signalList[signalCount] = 0xFF; + + sortList(signalList); + } + } + + void registerHooks() + { + register_hook( + 0x0049F1B5, + [](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t { + registers backup = regs; + common::activateSelectedConstructionWidgets(); + regs = backup; + return 0; + }); + + //register_hook( + // 0x0049DC97, + // [](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t { + // registers backup = regs; + // construction::on_tool_down(*((ui::window*)regs.esi), regs.dx, regs.ax, regs.cx); + // regs = backup; + // return 0; + // }); + } +} diff --git a/src/openloco/windows/construction/Construction.h b/src/openloco/windows/construction/Construction.h new file mode 100644 index 00000000..a5627563 --- /dev/null +++ b/src/openloco/windows/construction/Construction.h @@ -0,0 +1,391 @@ +#pragma once + +#include "../../interop/interop.hpp" +#include "../../map/tilemgr.h" +#include "../../objects/vehicle_object.h" +#include "../../ui/WindowManager.h" + +using namespace openloco::interop; +using namespace openloco::map; +using namespace openloco::map::tilemgr; + +namespace openloco::ui::windows::construction +{ +#pragma pack(push, 1) + struct previewTrack + { + uint8_t index; // 0x00 + int16_t x; // 0x01 + int16_t y; // 0x03 + int16_t z; // 0x05 + uint8_t var_07; + uint8_t var_08; + uint8_t flags; // 0x09 + }; +#pragma pack(pop) + + enum previewTrackFlags + { + diagonal = 1 << 7, + }; + + static loco_global gCurrentRotation; + static loco_global _unk_4F6D44; + static loco_global _unk_4F6D4F; + static loco_global _unk_4F6D5A; + static loco_global _unk_4F6D65; + static loco_global _unk_4F6D8E; + static loco_global _unk_4F6DB7; + static loco_global _unk_4F6DCC; + static loco_global _unk_4F6DE1; + static loco_global _unk_4F6DEC; + static loco_global _unk_4F6DF7; + + static loco_global _unk_4F7488; + static loco_global _unk_4F7493; + static loco_global _unk_4F74BC; + static loco_global _unk_4F74C7; + static loco_global _unk_4F74D2; + static loco_global _unk_4F74FB; + static loco_global _unk_4F7524; + static loco_global _unk_4F7557; + static loco_global _unk_4F758A; + static loco_global _unk_4F75BD; + static loco_global _unk_4F75F0; + static loco_global _unk_4F7623; + static loco_global _unk_4F7656; + static loco_global _unk_4F767F; + static loco_global _unk_4F76A8; + static loco_global _unk_4F76BD; + static loco_global _unk_4F76D2; + static loco_global _unk_4F76DD; + static loco_global _unk_4F76E8; + static loco_global _unk_4F7711; + static loco_global _unk_4F773A; + static loco_global _unk_4F7763; + static loco_global _unk_4F778C; + static loco_global _unk_4F77B5; + static loco_global _unk_4F77DE; + static loco_global _unk_4F7807; + static loco_global _unk_4F7830; + static loco_global _unk_4F783B; + static loco_global _unk_4F7846; + static loco_global _unk_4F7851; + static loco_global _unk_4F785C; + static loco_global _unk_4F7867; + static loco_global _unk_4F7872; + static loco_global _unk_4F787D; + static loco_global _unk_4F7888; + static loco_global _unk_4F7893; + static loco_global _unk_4F789E; + static loco_global _unk_4F78A9; + static loco_global _unk_4F78B4; + static loco_global _unk_4F78BF; + static loco_global _unk_4F78CA; + static loco_global _unk_4F78D5; + static loco_global _unk_4F78E0; + static loco_global _unk_4F78EB; + + static loco_global _word_4F7B62; // TODO: Not sure on size? + static loco_global _byte_5045FA; + static loco_global _byte_508F09; + static loco_global _byte_522090; + static loco_global _byte_522091; + static loco_global _byte_522092; + static loco_global _byte_522095; + static loco_global _byte_522096; + static loco_global _tooltipTimeout; + static loco_global _toolWindowNumber; + static loco_global _toolWindowType; + static loco_global _toolWidgetIndex; + static loco_global _playerCompany; + static loco_global _scenarioSignals; + static loco_global _scenarioBridges; + static loco_global _scenarioTrainStations; + static loco_global _scenarioTrackMods; + static loco_global _scenarioRoadStations; + static loco_global _scenarioRoadMods; + static loco_global _lastRailroadOption; + static loco_global _lastRoadOption; + static loco_global _lastAirport; + static loco_global _lastShipPort; + static loco_global _byte_525FAE; + static loco_global _byte_F24948; + static loco_global _word_F24942; + static loco_global _word_F24944; + static loco_global _word_F24946; + static loco_global _updatingCompanyId; + static loco_global _dword_E0C3E0; + static loco_global _mapSelectionFlags; + constexpr uint16_t mapSelectedTilesSize = 300; + static loco_global _mapSelectedTiles; + static loco_global gGameCommandErrorText; + static loco_global _currentFontSpriteBase; + static loco_global _stringFormatBuffer; + static loco_global _trackCost; + static loco_global _dword_1135F42; + static loco_global _modCost; + static loco_global _signalCost; + static loco_global _stationCost; + static loco_global _constructingStationId; + static loco_global _constructingStationAcceptedCargoTypes; + static loco_global _constructingStationProducedCargoTypes; + static loco_global _word_1135F86; + static loco_global _x; + static loco_global _y; + static loco_global _word_1135FB8; + static loco_global _word_1135FBA; + static loco_global _word_1135FBC; + static loco_global _word_1135FBE; + static loco_global _word_1135FD6; + static loco_global _word_1135FD8; + static loco_global _lastSelectedMods; + static loco_global _word_1135FFE; + static loco_global _word_1136000; + static loco_global _signalList; + static loco_global _lastSelectedSignal; + static loco_global _isSignalBothDirections; + static loco_global _bridgeList; + static loco_global _lastSelectedBridge; + static loco_global _byte_113603A; + static loco_global _stationList; + static loco_global _lastSelectedStationType; + static loco_global _modList; + static loco_global _byte_113605D; + static loco_global _constructionHover; + static loco_global _trackType; + static loco_global _byte_1136063; + static loco_global _constructionRotation; + static loco_global _byte_1136065; + static loco_global _byte_1136066; + static loco_global _lastSelectedTrackPiece; + static loco_global _lastSelectedTrackGradient; + static loco_global _lastSelectedTrackModSection; + static loco_global _byte_1136073; + static loco_global _byte_1136075; + static loco_global _byte_1136076; + static loco_global _byte_1136077; + static loco_global _byte_1136078; + static loco_global _lastSelectedTrackPieceId; + static loco_global _byte_113607E; + + namespace common + { + enum widx + { + frame, + caption, + close_button, + panel, + tab_construction, + tab_station, + tab_signal, + tab_overhead, + }; + + enum trackPiece + { + straight, + left_hand_curve_very_small, + right_hand_curve_very_small, + left_hand_curve_small, + right_hand_curve_small, + left_hand_curve, + right_hand_curve, + left_hand_curve_large, + right_hand_curve_large, + s_bend_left, + s_bend_right, + s_bend_to_dual_track, + s_bend_to_single_track, + turnaround, + }; + + enum trackGradient + { + level = 0, + slope_up = 2, + steep_slope_up = 4, + slope_down = 6, + steep_slope_down = 8, + }; + + struct trackPieceId + { + uint8_t id; + uint8_t rotation; + }; + +#define commonWidgets(frameWidth, frameHeight, windowCaptionId) \ + make_widget({ 0, 0 }, { frameWidth, frameHeight }, widget_type::frame, 0), \ + make_widget({ 1, 1 }, { frameWidth - 2, 13 }, widget_type::caption_24, 0, windowCaptionId), \ + make_widget({ frameWidth - 15, 2 }, { 13, 13 }, widget_type::wt_9, 0, image_ids::close_button, string_ids::tooltip_close_window), \ + make_widget({ 0, 41 }, { frameWidth, 235 }, widget_type::wt_3, 1), \ + make_remap_widget({ 3, 15 }, { 31, 27 }, widget_type::wt_8, 1, image_ids::tab, string_ids::tab_track_road_construction), \ + make_remap_widget({ 34, 15 }, { 31, 27 }, widget_type::wt_8, 1, image_ids::tab, string_ids::tab_station_construction), \ + make_remap_widget({ 65, 15 }, { 31, 27 }, widget_type::wt_8, 1, image_ids::tab, string_ids::tab_signal_construction), \ + make_remap_widget({ 96, 15 }, { 31, 27 }, widget_type::wt_8, 1, image_ids::tab, string_ids::tab_electrification_construction) + + constexpr uint64_t enabledWidgets = (1 << widx::caption) | (1 << widx::close_button) | (1 << widx::tab_construction) | (1 << widx::tab_station) | (1 << widx::tab_signal) | (1 << widx::tab_overhead); + + void prepare_draw(window* self); + void switchTab(window* self, widget_index widgetIndex); + void repositionTabs(window* self); + void drawTabs(window* self, gfx::drawpixelinfo_t* dpi); + void init_events(); + std::optional getRoadPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation); + std::optional getTrackPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation); + void activateSelectedConstructionWidgets(); + void sub_49FEC7(); + void on_close(window* self); + void on_update(window* self, uint8_t flag); + void sub_4CD454(); + void setTrackOptions(const uint8_t trackType); + void setDisabledWidgets(window* self); + void createConstructionWindow(); + void refreshAirportList(uint8_t* stationList); + void refreshDockList(uint8_t* stationList); + void refreshStationList(uint8_t* stationList, uint8_t trackType, TransportMode transportMode); + void refreshBridgeList(uint8_t* bridgeList, uint8_t trackType, TransportMode transportMode); + void refreshModList(uint8_t* modList, uint8_t trackType, TransportMode transportMode); + void sub_4A3A50(); + void refreshSignalList(uint8_t* signalList, uint8_t trackType); + + extern const uint8_t trackPieceWidgets[11]; + extern const previewTrack* roadPieces[10]; + extern const previewTrack* trackPieces[44]; + } + + namespace construction + { + static const gfx::ui_size_t windowSize = { 138, 276 }; + + enum widx + { + left_hand_curve_very_small = 8, + left_hand_curve_small, + left_hand_curve, + left_hand_curve_large, + right_hand_curve_large, + right_hand_curve, + right_hand_curve_small, + right_hand_curve_very_small, + s_bend_dual_track_left, + s_bend_left, + straight, + s_bend_right, + s_bend_dual_track_right, + steep_slope_down, + slope_down, + level, + slope_up, + steep_slope_up, + bridge, + bridge_dropdown, + construct, + remove, + rotate_90, + }; + + // clang-format off + constexpr uint64_t allTrack = { + (1ULL << widx::left_hand_curve_very_small) | + (1ULL << widx::left_hand_curve_small) | + (1ULL << widx::left_hand_curve) | + (1ULL << widx::left_hand_curve_large) | + (1ULL << widx::right_hand_curve_large) | + (1ULL << widx::right_hand_curve) | + (1ULL << widx::right_hand_curve_small) | + (1ULL << widx::right_hand_curve_very_small ) | + (1ULL << widx::s_bend_dual_track_left) | + (1ULL << widx::s_bend_left) | + (1ULL << widx::straight) | + (1ULL << widx::s_bend_right) | + (1ULL << widx::s_bend_dual_track_right) | + (1ULL << widx::steep_slope_down) | + (1ULL << widx::slope_down) | + (1ULL << widx::level) | + (1ULL << widx::slope_up) | + (1ULL << widx::steep_slope_up) + }; + + constexpr uint64_t allConstruction = { + allTrack | + (1ULL << widx::bridge) | + (1ULL << widx::bridge_dropdown) | + (1ULL << widx::construct) | + (1ULL << widx::remove) | + (1ULL << widx::rotate_90) + }; + //clang-format on + + extern widget_t widgets[32]; + + extern window_event_list events; + constexpr uint64_t enabledWidgets = common::enabledWidgets | allConstruction; + void tabReset(window* self); + void init_events(); + void drawTrack(uint16_t x, uint16_t y, uint16_t selectedMods, uint16_t di, uint8_t trackType, uint8_t trackPieceId, uint16_t colour, uint8_t bh); + void drawRoad(uint16_t x, uint16_t y, uint16_t selectedMods, uint16_t di, uint8_t trackType, uint8_t trackPieceId, uint16_t colour, uint8_t bh); + } + + namespace station + { + enum widx + { + station = 8, + station_dropdown, + image, + rotate, + }; + + extern widget_t widgets[13]; + + const uint64_t enabledWidgets = common::enabledWidgets | (1 << station) | (1 << station_dropdown) | (1 << image) | (1 << rotate); + + extern window_event_list events; + void tabReset(window* self); + void init_events(); + } + + namespace signal + { + enum widx + { + signal = 8, + signal_dropdown, + both_directions, + single_direction, + }; + + extern widget_t widgets[13]; + + const uint64_t enabledWidgets = common::enabledWidgets | (1 << signal) | (1 << signal_dropdown) | (1 << both_directions) | (1 << single_direction); + + extern window_event_list events; + void tabReset(window* self); + void init_events(); + } + + namespace overhead + { + enum widx + { + checkbox_1 = 8, + checkbox_2, + checkbox_3, + checkbox_4, + image, + track, + track_dropdown, + }; + + extern widget_t widgets[16]; + + const uint64_t enabledWidgets = common::enabledWidgets | (1 << checkbox_1) | (1 << checkbox_2) | (1 << checkbox_3) | (1 << checkbox_4) | (1 << image) | (1 << track) | (1 << track_dropdown); + + extern window_event_list events; + void tabReset(window* self); + void init_events(); + } +} diff --git a/src/openloco/windows/construction/ConstructionTab.cpp b/src/openloco/windows/construction/ConstructionTab.cpp new file mode 100644 index 00000000..b5bd6703 --- /dev/null +++ b/src/openloco/windows/construction/ConstructionTab.cpp @@ -0,0 +1,1222 @@ +#include "../../audio/audio.h" +#include "../../companymgr.h" +#include "../../graphics/image_ids.h" +#include "../../input.h" +#include "../../localisation/FormatArguments.hpp" +#include "../../objects/bridge_object.h" +#include "../../objects/objectmgr.h" +#include "../../objects/road_object.h" +#include "../../objects/track_object.h" +#include "../../ui/dropdown.h" +#include "Construction.h" + +using namespace openloco::interop; +using namespace openloco::map; +using namespace openloco::map::tilemgr; + +namespace openloco::ui::windows::construction::construction +{ + widget_t widgets[] = { + commonWidgets(138, 276, string_ids::stringid_2), + make_widget({ 3, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_left_hand_curve_very_small, string_ids::tooltip_left_hand_curve_very_small), + make_widget({ 3, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_left_hand_curve_small, string_ids::tooltip_left_hand_curve_small), + make_widget({ 25, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_left_hand_curve, string_ids::tooltip_left_hand_curve), + make_widget({ 47, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_left_hand_curve_large, string_ids::tooltip_left_hand_curve_large), + make_widget({ 69, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_right_hand_curve_large, string_ids::tooltip_right_hand_curve_large), + make_widget({ 91, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_right_hand_curve, string_ids::tooltip_right_hand_curve), + make_widget({ 113, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_right_hand_curve_small, string_ids::tooltip_right_hand_curve_small), + make_widget({ 113, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_right_hand_curve_very_small, string_ids::tooltip_right_hand_curve_very_small), + make_widget({ 9, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_s_bend_dual_track_left, string_ids::tooltip_s_bend_left_dual_track), + make_widget({ 33, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_s_bend_left, string_ids::tooltip_s_bend_left), + make_widget({ 57, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_straight, string_ids::tooltip_straight), + make_widget({ 81, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_s_bend_right, string_ids::tooltip_s_bend_right), + make_widget({ 105, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_s_bend_dual_track_right, string_ids::tooltip_s_bend_right_dual_track), + make_widget({ 9, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_steep_slope_down, string_ids::tooltip_steep_slope_down), + make_widget({ 33, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_slope_down, string_ids::tooltip_slope_down), + make_widget({ 57, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_level, string_ids::tooltip_level), + make_widget({ 81, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_slope_up, string_ids::tooltip_slope_up), + make_widget({ 105, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_steep_slope_up, string_ids::tooltip_steep_slope_up), + make_widget({ 40, 123 }, { 58, 20 }, widget_type::wt_18, 1, string_ids::empty, string_ids::tooltip_bridge_stats), + make_widget({ 86, 124 }, { 11, 18 }, widget_type::wt_11, 1, string_ids::dropdown, string_ids::tooltip_bridge_stats), + make_widget({ 3, 145 }, { 132, 100 }, widget_type::wt_5, 1, 0xFFFFFFFF, string_ids::tooltip_construct), + make_widget({ 6, 248 }, { 46, 24 }, widget_type::wt_9, 1, image_ids::construction_remove, string_ids::tooltip_remove), + make_widget({ 57, 248 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::rotate_object, string_ids::rotate_90), + widget_end(), + }; + + window_event_list events; + + // 0x0049F92D + static void constructTrack(window* self, widget_index widgetIndex) + { + registers regs; + regs.edx = widgetIndex; + regs.esi = (int32_t)self; + call(0x0049F92D, regs); + } + + // 0x004A0121 + static void removeTrack(window* self, widget_index widgetIndex) + { + registers regs; + regs.edx = widgetIndex; + regs.esi = (int32_t)self; + call(0x004A0121, regs); + } + + // 0x0049D3F6 + static void on_mouse_up(window* self, widget_index widgetIndex) + { + // Allow shift key to repeat the action multiple times + // This is useful for building very long tracks. + int multiplier = 1; + if (input::has_key_modifier(input::key_modifier::shift)) + { + multiplier = 10; + } + + registers regs; + regs.edx = widgetIndex; + regs.esi = (int32_t)self; + switch (widgetIndex) + { + case common::widx::close_button: + WindowManager::close(self); + break; + + case common::widx::tab_construction: + case common::widx::tab_overhead: + case common::widx::tab_signal: + case common::widx::tab_station: + common::switchTab(self, widgetIndex); + break; + + case widx::construct: + for (int i = 0; i < multiplier; i++) + { + constructTrack(self, widgetIndex); + } + break; + + case widx::remove: + for (int i = 0; i < multiplier; i++) + { + removeTrack(self, widgetIndex); + } + break; + + case widx::rotate_90: + { + if (_constructionHover == 1) + { + _constructionRotation++; + _constructionRotation = _constructionRotation & 3; + _trackCost = 0x80000000; + common::activateSelectedConstructionWidgets(); + break; + } + common::sub_49FEC7(); + WindowManager::viewportSetVisibility(WindowManager::viewport_visibility::overgroundView); + input::toolSet(self, widx::construct, 12); + input::set_flag(input::input_flags::flag6); + + _constructionHover = 1; + _byte_113607E = 0; + _constructionRotation = _constructionRotation & 3; + + common::activateSelectedConstructionWidgets(); + break; + } + } + } + + // 0x0049DB71 + static void disableUnusedPiecesRotation(uint64_t* disabledWidgets) + { + if (_constructionRotation < 12) + { + if (_constructionRotation >= 8) + { + *disabledWidgets |= (1 << widx::left_hand_curve_small) | (1 << widx::left_hand_curve) | (1 << widx::left_hand_curve_large) | (1 << widx::right_hand_curve_large) | (1 << widx::right_hand_curve) | (1 << widx::right_hand_curve_small); + *disabledWidgets |= (1 << widx::s_bend_right) | (1 << widx::slope_down) | (1 << widx::slope_up); + } + else + { + if (_constructionRotation >= 4) + { + *disabledWidgets |= (1 << widx::left_hand_curve_small) | (1 << widx::left_hand_curve) | (1 << widx::left_hand_curve_large) | (1 << widx::right_hand_curve_large) | (1 << widx::right_hand_curve) | (1 << widx::right_hand_curve_small); + *disabledWidgets |= (1 << widx::s_bend_left) | (1 << widx::slope_down) | (1 << widx::slope_up); + } + } + } + else + { + *disabledWidgets |= (1 << widx::left_hand_curve_very_small) | (1 << widx::left_hand_curve_small) | (1 << widx::left_hand_curve) | (1 << widx::right_hand_curve) | (1 << widx::right_hand_curve_very_small) | (1 << widx::right_hand_curve_small); + *disabledWidgets |= (1 << widx::s_bend_dual_track_left) | (1 << widx::s_bend_left) | (1 << widx::s_bend_right) | (1 << widx::s_bend_dual_track_right) | (1 << widx::steep_slope_down) | (1 << widx::slope_down) | (1 << widx::slope_up) | (1 << widx::steep_slope_up); + } + } + + // 0x0049DBEC + static void disableUnusedRoadPieces(window* self, uint64_t disabledWidgets) + { + if (_lastSelectedTrackGradient == 2 || _lastSelectedTrackGradient == 6 || _lastSelectedTrackGradient == 4 || _lastSelectedTrackGradient == 8) + { + disabledWidgets |= (1 << widx::left_hand_curve_very_small) | (1 << widx::left_hand_curve) | (1 << widx::left_hand_curve_large) | (1 << widx::right_hand_curve_large) | (1 << widx::right_hand_curve) | (1 << widx::right_hand_curve_very_small); + disabledWidgets |= (1 << widx::s_bend_dual_track_left) | (1 << widx::s_bend_left) | (1 << widx::s_bend_right) | (1 << widx::s_bend_dual_track_right); + disabledWidgets |= (1 << widx::left_hand_curve_small) | (1 << widx::right_hand_curve_small); + } + + disableUnusedPiecesRotation(&disabledWidgets); + + if (_constructionHover == 0) + { + auto road = common::getRoadPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + if (!road) + disabledWidgets |= (1 << widx::construct); + } + self->set_disabled_widgets_and_invalidate(disabledWidgets); + } + + // 0x0049DB1F + static void disableUnusedTrackPieces(window* self, track_object trackObj, uint64_t disabledWidgets) + { + if (_lastSelectedTrackGradient == 2 || _lastSelectedTrackGradient == 6 || _lastSelectedTrackGradient == 4 || _lastSelectedTrackGradient == 8) + { + disabledWidgets |= (1 << widx::left_hand_curve_very_small) | (1 << widx::left_hand_curve) | (1 << widx::left_hand_curve_large) | (1 << widx::right_hand_curve_large) | (1 << widx::right_hand_curve) | (1 << widx::right_hand_curve_very_small); + disabledWidgets |= (1 << widx::s_bend_dual_track_left) | (1 << widx::s_bend_left) | (1 << widx::s_bend_right) | (1 << widx::s_bend_dual_track_right); + + if (!(trackObj.track_pieces & (1 << 8))) + disabledWidgets |= (1 << widx::left_hand_curve_small) | (1 << widx::right_hand_curve_small); + } + + disableUnusedPiecesRotation(&disabledWidgets); + + if (_constructionHover == 0) + { + auto track = common::getTrackPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + + if (!track) + disabledWidgets |= (1 << widx::construct); + } + self->set_disabled_widgets_and_invalidate(disabledWidgets); + } + + // 0x0049DAF3 + static void disableTrackSlopes(window* self, track_object trackObj, uint64_t disabledWidgets) + { + auto trackPieces = trackObj.track_pieces & ((1 << 5) | (1 << 8)); + + if (trackPieces != ((1 << 5) | (1 << 8))) + disabledWidgets |= (1 << widx::slope_down) | (1 << widx::slope_up); + + trackPieces = trackObj.track_pieces & ((1 << 6) | (1 << 8)); + + if (trackPieces != ((1 << 6) | (1 << 8))) + disabledWidgets |= (1 << widx::steep_slope_down) | (1 << widx::steep_slope_up); + + disableUnusedTrackPieces(self, trackObj, disabledWidgets); + } + + // 0x0049DAA5 + static void on_resize(window* self) + { + self->enabled_widgets &= ~(1 << widx::construct); + + if (_constructionHover != 1) + self->enabled_widgets |= (1 << widx::construct); + + auto disabledWidgets = self->disabled_widgets; + disabledWidgets &= (1 << common::widx::tab_construction | 1 << common::widx::tab_overhead | 1 << common::widx::tab_signal | 1 << common::widx::tab_station); + uint8_t trackType = _trackType; + + if (trackType & (1 << 7)) + { + trackType &= ~(1 << 7); + + if (_lastSelectedTrackPiece == 0xFF) + { + disableUnusedRoadPieces(self, disabledWidgets); + return; + } + switch (_lastSelectedTrackPiece) + { + case common::trackPiece::straight: + case common::trackPiece::left_hand_curve: + case common::trackPiece::right_hand_curve: + case common::trackPiece::left_hand_curve_large: + case common::trackPiece::right_hand_curve_large: + case common::trackPiece::s_bend_left: + case common::trackPiece::s_bend_right: + case common::trackPiece::s_bend_to_dual_track: + case common::trackPiece::s_bend_to_single_track: + { + disableUnusedRoadPieces(self, disabledWidgets); + break; + } + + case common::trackPiece::left_hand_curve_very_small: + case common::trackPiece::right_hand_curve_very_small: + case common::trackPiece::left_hand_curve_small: + case common::trackPiece::right_hand_curve_small: + case common::trackPiece::turnaround: + { + disabledWidgets |= (1 << widx::steep_slope_down) | (1 << widx::slope_down) | (1 << widx::slope_up) | (1 << widx::steep_slope_up); + disableUnusedRoadPieces(self, disabledWidgets); + break; + } + } + } + else + { + auto trackObj = objectmgr::get(trackType); + if (_lastSelectedTrackPiece == 0xFF) + { + disableUnusedTrackPieces(self, *trackObj, disabledWidgets); + return; + } + switch (_lastSelectedTrackPiece) + { + case common::trackPiece::straight: + disableUnusedTrackPieces(self, *trackObj, disabledWidgets); + break; + + case common::trackPiece::left_hand_curve_very_small: + case common::trackPiece::right_hand_curve_very_small: + case common::trackPiece::left_hand_curve: + case common::trackPiece::right_hand_curve: + case common::trackPiece::left_hand_curve_large: + case common::trackPiece::right_hand_curve_large: + case common::trackPiece::s_bend_left: + case common::trackPiece::s_bend_right: + case common::trackPiece::s_bend_to_dual_track: + case common::trackPiece::s_bend_to_single_track: + case common::trackPiece::turnaround: + { + disabledWidgets |= (1 << widx::steep_slope_down) | (1 << widx::slope_down) | (1 << widx::slope_up) | (1 << widx::steep_slope_up); + disableUnusedTrackPieces(self, *trackObj, disabledWidgets); + break; + } + + case common::trackPiece::left_hand_curve_small: + case common::trackPiece::right_hand_curve_small: + { + disableTrackSlopes(self, *trackObj, disabledWidgets); + break; + } + } + } + } + + // 0x0049d600 (based on) + static void changeTrackPiece(uint8_t trackPiece, bool slope) + { + _byte_113603A = 0xFF; + common::sub_49FEC7(); + + if (slope) + _lastSelectedTrackGradient = trackPiece; + else + _lastSelectedTrackPiece = trackPiece; + + _trackCost = 0x80000000; + common::activateSelectedConstructionWidgets(); + } + + // 0x0049D83A + static void bridgeDropdown(window* self) + { + auto bridgeCount = 0; + for (; bridgeCount < 9; bridgeCount++) + { + if (_bridgeList[bridgeCount] == 0xFF) + break; + } + + uint8_t flags = (1 << 7) | (1 << 6); + auto widget = self->widgets[widx::bridge]; + auto x = widget.left + self->x; + auto y = widget.top + self->y; + auto width = 155; + auto height = widget.height(); + + dropdown::show(x, y, width, height, self->colours[1], bridgeCount, 22, flags); + for (auto i = 0; i < 9; i++) + { + auto bridge = _bridgeList[i]; + + if (bridge == 0xFF) + return; + + if (bridge == _lastSelectedBridge) + dropdown::set_highlighted_item(i); + + auto bridgeObj = objectmgr::get(bridge); + auto company = companymgr::get(_playerCompany); + auto companyColour = company->mainColours.primary; + auto imageId = gfx::recolour(bridgeObj->var_16, companyColour); + + auto args = FormatArguments(); + args.push(imageId); + + if (bridgeObj->max_speed == 0xFFFF) + { + args.push(string_ids::unlimited_speed); + args.push(0); + } + else + { + args.push(string_ids::velocity); + args.push(bridgeObj->max_speed); + } + args.push(bridgeObj->max_height); + + dropdown::add(i, string_ids::dropdown_bridge_stats, args); + } + } + + // 0x0049D42F + static void on_mouse_down(window* self, widget_index widgetIndex) + { + switch (widgetIndex) + { + case widx::left_hand_curve: + changeTrackPiece(common::trackPiece::left_hand_curve, false); + break; + + case widx::right_hand_curve: + changeTrackPiece(common::trackPiece::right_hand_curve, false); + break; + + case widx::left_hand_curve_small: + changeTrackPiece(common::trackPiece::left_hand_curve_small, false); + break; + + case widx::right_hand_curve_small: + changeTrackPiece(common::trackPiece::right_hand_curve_small, false); + break; + + case widx::left_hand_curve_very_small: + changeTrackPiece(common::trackPiece::left_hand_curve_very_small, false); + break; + + case widx::right_hand_curve_very_small: + changeTrackPiece(common::trackPiece::right_hand_curve_very_small, false); + break; + + case widx::left_hand_curve_large: + changeTrackPiece(common::trackPiece::left_hand_curve_large, false); + break; + + case widx::right_hand_curve_large: + changeTrackPiece(common::trackPiece::right_hand_curve_large, false); + break; + + case widx::straight: + changeTrackPiece(common::trackPiece::straight, false); + break; + + case widx::s_bend_left: + changeTrackPiece(common::trackPiece::s_bend_left, false); + break; + + case widx::s_bend_right: + changeTrackPiece(common::trackPiece::s_bend_right, false); + break; + + case widx::s_bend_dual_track_left: + { + _byte_113603A = 0xFF; + common::sub_49FEC7(); + _lastSelectedTrackPiece = common::trackPiece::s_bend_to_dual_track; + _trackCost = 0x80000000; + if (self->widgets[widx::s_bend_dual_track_left].image != image_ids::construction_s_bend_dual_track_left) + { + _lastSelectedTrackPiece = common::trackPiece::turnaround; + if (self->widgets[widx::s_bend_dual_track_left].image != image_ids::construction_right_turnaround) + { + if (self->widgets[widx::s_bend_dual_track_left].image != image_ids::construction_left_turnaround) + _lastSelectedTrackPiece = common::trackPiece::s_bend_to_single_track; + } + } + common::activateSelectedConstructionWidgets(); + break; + } + + case widx::s_bend_dual_track_right: + { + _byte_113603A = 0xFF; + common::sub_49FEC7(); + _lastSelectedTrackPiece = common::trackPiece::s_bend_to_single_track; + _trackCost = 0x80000000; + if (self->widgets[widx::s_bend_dual_track_right].image != image_ids::construction_s_bend_dual_track_right) + { + _lastSelectedTrackPiece = common::trackPiece::turnaround; + if (self->widgets[widx::s_bend_dual_track_left].image != image_ids::construction_left_turnaround) + _lastSelectedTrackPiece = common::trackPiece::s_bend_to_dual_track; + } + common::activateSelectedConstructionWidgets(); + break; + } + + case widx::steep_slope_down: + changeTrackPiece(common::trackGradient::steep_slope_down, true); + break; + + case widx::slope_down: + changeTrackPiece(common::trackGradient::slope_down, true); + break; + + case widx::level: + changeTrackPiece(common::trackGradient::level, true); + break; + + case widx::slope_up: + changeTrackPiece(common::trackGradient::slope_up, true); + break; + + case widx::steep_slope_up: + changeTrackPiece(common::trackGradient::steep_slope_up, true); + break; + + case widx::bridge_dropdown: + { + bridgeDropdown(self); + break; + } + } + } + + // 0x0049D4EA + static void on_dropdown(window* self, widget_index widgetIndex, int16_t itemIndex) + { + if (widgetIndex == widx::bridge_dropdown) + { + if (itemIndex != -1) + { + auto bridge = _bridgeList[itemIndex]; + _lastSelectedBridge = bridge; + + // TODO: & ~(1 << 7) added to prevent crashing when selecting bridges for road/trams + _scenarioBridges[_trackType & ~(1 << 7)] = bridge; + common::sub_49FEC7(); + _trackCost = 0x80000000; + common::activateSelectedConstructionWidgets(); + } + } + } + + static void sub_49FD66() + { + registers regs; + call(0x0049FD66, regs); + } + + // 0x0049DCA2 + static void on_update(window* self) + { + self->frame_no++; + self->call_prepare_draw(); + WindowManager::invalidate(WindowType::construction, self->number); + + if (_constructionHover == 1) + { + if (!input::is_tool_active(WindowType::construction, self->number) || _toolWidgetIndex != construction::widx::construct) + WindowManager::close(self); + } + if (_constructionHover == 0) + { + if (input::is_tool_active(WindowType::construction, self->number)) + input::cancel_tool(); + } + sub_49FD66(); + } + + // 0x004A2395 + static std::optional getConstructionHeight(const map_pos& mapPos, int16_t height, bool isSelected) + { + auto tile = tilemgr::get(mapPos); + + auto surfaceTile = tile.surface(); + + if (surfaceTile == nullptr) + return std::nullopt; + + int16_t tileHeight = surfaceTile->base_z() * 4; + + if (surfaceTile->slope_corners()) + { + tileHeight += 16; + } + + if (surfaceTile->is_slope_dbl_height()) + { + tileHeight += 16; + } + + if (isSelected) + { + if (tileHeight > height) + { + height = tileHeight; + } + } + else + { + if (tileHeight > _word_1136000) + { + height = _word_1136000; + } + } + + if (isSelected) + { + if (surfaceTile->water()) + { + tileHeight = surfaceTile->water() * 16; + tileHeight += 16; + + if (tileHeight > height) + { + height = tileHeight; + } + } + } + else + { + tileHeight = surfaceTile->water() * 16; + if (tileHeight > height) + { + height = tileHeight; + } + } + + return height; + } + + // 0x00478361 + static std::optional> sub_478361(int16_t x, int16_t y) + { + registers regs; + regs.ax = x; + regs.bx = y; + auto flags = call(0x00478361, regs); + + if (flags & (1 << 8)) + return std::nullopt; + + return { std::make_pair(regs.di, regs.dl) }; + } + + // 0x004A4011 + static std::optional> sub_4A4011(int16_t x, int16_t y) + { + registers regs; + regs.ax = x; + regs.bx = y; + auto flags = call(0x004A4011, regs); + + if (flags & (1 << 8)) + return std::nullopt; + + return { std::make_pair(regs.di, regs.dl) }; + } + + // 0x00460781 + static map_pos sub_460781(int16_t x, int16_t y) + { + registers regs; + regs.ax = x; + regs.bx = y; + call(0x00460781, regs); + + map_pos mapPos = { regs.ax, regs.bx }; + + return mapPos; + } + + static void constructionLoop(map_pos mapPos, uint32_t maxRetries, int16_t height) + { + while (true) + { + _constructionHover = 0; + _byte_113607E = 0; + _x = mapPos.x; + _y = mapPos.y; + _word_1135FB8 = height; + _byte_522096 = 0; + _byte_1136066 = 0; + + common::activateSelectedConstructionWidgets(); + auto window = WindowManager::find(WindowType::construction); + + if (window == nullptr) + return; + + _byte_508F09 = _byte_508F09 | (1 << 0); + + on_mouse_up(window, widx::construct); + + _byte_508F09 = _byte_508F09 & ~(1 << 0); + + if (_dword_1135F42 == 0x80000000) + { + if (gGameCommandErrorText != string_ids::error_can_only_build_above_ground) + { + maxRetries--; + if (maxRetries != 0) + { + height -= 16; + if (height >= 0) + { + if (input::has_key_modifier(input::key_modifier::shift)) + { + continue; + } + else + { + height += 32; + continue; + } + } + } + } + } + else + { + _byte_113607E = 1; + WindowManager::close(WindowType::error, 0); + return; + } + + on_mouse_up(window, widx::rotate_90); + + audio::play_sound(audio::sound_id::error, int32_t(input::getMouseLocation().x)); + + return; + } + } + + // 0x0049DC8C + static void on_tool_update(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + registers regs; + regs.esi = (int32_t)&self; + regs.dx = widgetIndex; + regs.ax = x; + regs.bx = y; + call(0x0049DC8C, regs); + } + + // 0x0049DC97 + static void on_tool_down(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + if (widgetIndex != widx::construct) + return; + + if (_trackType & (1 << 7)) + { + map_invalidate_map_selection_tiles(); + common::sub_49FEC7(); + + auto road = common::getRoadPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + + if (!road) + return; + + _byte_1136065 = road->id; + int16_t roadHeight = 0; + + auto i = 0; + if (_mapSelectionFlags & (1 << 1)) + { + for (auto& tile = _mapSelectedTiles[i]; tile.x != -1; tile = _mapSelectedTiles[++i]) + { + if (tile.x >= 0x2FFF) + continue; + + if (tile.y >= 0x2FFF) + continue; + + auto height = getConstructionHeight(_mapSelectedTiles[i], roadHeight, true); + + if (height) + roadHeight = *height; + } + } + // loc_4A23F8 + _word_1136000 = roadHeight; + _mapSelectionFlags = _mapSelectionFlags & ~((1 << 2) | (1 << 1) | (1 << 0)); + + auto height = sub_478361(x, y); + map_pos mapPos; + + if (height) + { + auto pos = screenGetMapXyWithZ(xy32(x, y), height->first); + if (pos) + { + mapPos.x = pos->x; + mapPos.y = pos->y; + mapPos.x &= 0xFFE0; + mapPos.y &= 0xFFE0; + _byte_113605D = 1; + _word_1135FFE = roadHeight; + } + else + { + mapPos.x = -32768; + } + } + + if (!height || mapPos.x == -32768) + { + mapPos = sub_460781(x, y); + + if (mapPos.x == -32768) + return; + + auto constructionHeight = getConstructionHeight(mapPos, roadHeight, false); + + if (constructionHeight) + roadHeight = *constructionHeight; + + _byte_113605D = 0; + } + input::cancel_tool(); + + auto maxRetries = 0; + if (input::has_key_modifier(input::key_modifier::shift) || _byte_113605D != 1) + { + auto roadPiece = common::roadPieces[_byte_1136065]; + i = 0; + auto maxRoadPieceHeight = 0; + + while (roadPiece[i].index != 0xFF) + { + if (maxRoadPieceHeight > roadPiece[i].z) + maxRoadPieceHeight = roadPiece[i].z; + i++; + } + + roadHeight -= maxRoadPieceHeight; + roadHeight -= 16; + maxRetries = 2; + + if (input::has_key_modifier(input::key_modifier::shift)) + { + maxRetries = 0x80000008; + roadHeight -= 16; + } + } + else + { + maxRetries = 1; + roadHeight = _word_1135FFE; + } + + constructionLoop(mapPos, maxRetries, roadHeight); + } + else + { + map_invalidate_map_selection_tiles(); + common::sub_49FEC7(); + + auto track = common::getTrackPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + + if (!track) + return; + + _byte_1136065 = track->id; + int16_t trackHeight = 0; + auto i = 0; + + if (_mapSelectionFlags & (1 << 1)) + { + for (auto& tile = _mapSelectedTiles[i]; tile.x != -1; tile = _mapSelectedTiles[++i]) + { + if (tile.x >= 0x2FFF) + continue; + + if (tile.y >= 0x2FFF) + continue; + + auto height = getConstructionHeight(_mapSelectedTiles[i], trackHeight, true); + + if (height) + trackHeight = *height; + } + } + _word_1136000 = trackHeight; + _mapSelectionFlags = _mapSelectionFlags & ~((1 << 2) | (1 << 1) | (1 << 0)); + + auto height = sub_4A4011(x, y); + map_pos mapPos; + + if (height) + { + if (_word_4F7B62[height->second] == 0) + { + auto pos = screenGetMapXyWithZ(xy32(x, y), height->first); + if (pos) + { + mapPos.x = pos->x; + mapPos.y = pos->y; + mapPos.x &= 0xFFE0; + mapPos.y &= 0xFFE0; + _byte_113605D = 1; + _word_1135FFE = trackHeight; + } + else + { + mapPos.x = -32768; + } + } + } + + if (!height || mapPos.x == -32768 || _word_4F7B62[track->id * 8] != 0) + { + mapPos = sub_460781(x, y); + + if (mapPos.x == -32768) + return; + + auto constructionHeight = getConstructionHeight(mapPos, trackHeight, false); + + if (constructionHeight) + trackHeight = *constructionHeight; + + _byte_113605D = 0; + } + input::cancel_tool(); + + auto maxRetries = 0; + if (input::has_key_modifier(input::key_modifier::shift) || _byte_113605D != 1) + { + auto trackPiece = common::trackPieces[_byte_1136065]; + i = 0; + auto maxTrackPieceHeight = 0; + + while (trackPiece[i].index != 0xFF) + { + if (maxTrackPieceHeight > trackPiece[i].z) + maxTrackPieceHeight = trackPiece[i].z; + i++; + } + + trackHeight -= maxTrackPieceHeight; + trackHeight -= 16; + maxRetries = 2; + + if (input::has_key_modifier(input::key_modifier::shift)) + { + maxRetries = 0x80000008; + trackHeight -= 16; + } + } + else + { + maxRetries = 1; + trackHeight = _word_1135FFE; + } + + constructionLoop(mapPos, maxRetries, trackHeight); + } + } + + // 0x0049D4F5 + static ui::cursor_id cursor(window* self, int16_t widgetIndex, int16_t xPos, int16_t yPos, ui::cursor_id fallback) + { + if (widgetIndex == widx::bridge || widgetIndex == widx::bridge_dropdown) + _tooltipTimeout = 2000; + return fallback; + } + + // 0x0049CE79 + static void prepare_draw(window* self) + { + common::prepare_draw(self); + auto args = FormatArguments(); + if (_trackType & (1 << 7)) + { + auto roadObj = objectmgr::get(_trackType & ~(1 << 7)); + args.push(roadObj->name); + } + else + { + auto trackObj = objectmgr::get(_trackType); + args.push(trackObj->name); + } + if (_lastSelectedBridge != 0xFF) + { + auto bridgeObj = objectmgr::get(_lastSelectedBridge); + if (bridgeObj != nullptr) + { + args.push(bridgeObj->name); + if (bridgeObj->max_speed == 0xFFFF) + { + args.push(string_ids::unlimited_speed); + args.push(0); + } + else + { + args.push(string_ids::velocity); + args.push(bridgeObj->max_speed); + } + args.push(bridgeObj->max_height); + } + } + common::repositionTabs(self); + } + + // 0x004A0AE5 + void drawTrack(uint16_t x, uint16_t y, uint16_t selectedMods, uint16_t di, uint8_t trackType, uint8_t trackPieceId, uint16_t colour, uint8_t bh) + { + registers regs; + regs.ax = x; + regs.cx = y; + regs.edi = selectedMods << 16 | di; + regs.bh = bh; + regs.edx = colour << 16 | trackPieceId << 8 | trackType; + call(0x004A0AE5, regs); + } + + // 0x00478F1F + void drawRoad(uint16_t x, uint16_t y, uint16_t selectedMods, uint16_t di, uint8_t trackType, uint8_t trackPieceId, uint16_t colour, uint8_t bh) + { + registers regs; + regs.ax = x; + regs.cx = y; + regs.edi = selectedMods << 16 | di; + regs.bh = bh; + regs.edx = colour << 16 | trackPieceId << 8 | trackType; + call(0x00478F1F, regs); + } + + // 0x0049D38A and 0x0049D16B + static void drawCostString(window* self, gfx::drawpixelinfo_t* dpi) + { + auto x = self->widgets[widx::construct].mid_x(); + x += self->x; + auto y = self->widgets[widx::construct].bottom + self->y - 23; + + if (_constructionHover != 1) + gfx::draw_string_centred(*dpi, x, y, colour::black, string_ids::build_this); + + y += 11; + + if (_trackCost != 0x80000000) + { + if (_trackCost != 0) + { + auto args = FormatArguments(); + args.push(_trackCost); + gfx::draw_string_centred(*dpi, x, y, colour::black, string_ids::build_cost, &args); + } + } + } + + // 0x0049D106 + static void drawTrackCost(window* self, gfx::drawpixelinfo_t* clipped, gfx::drawpixelinfo_t* dpi, xy32 pos, uint16_t width, uint16_t height) + { + width >>= 1; + height >>= 1; + height += 16; + pos.x -= width; + pos.y -= height; + clipped->x += pos.x; + clipped->y += pos.y; + _dword_E0C3E0 = clipped; + + _byte_522095 = _byte_522095 | (1 << 1); + + drawTrack(0x2000, 0x2000, _word_1135FD8, 0x1E0, _byte_1136077, _lastSelectedTrackPieceId, _word_1135FD6, _byte_1136078); + + _byte_522095 = _byte_522095 & ~(1 << 1); + + drawCostString(self, dpi); + } + + // 0x0049D325 + static void drawRoadCost(window* self, gfx::drawpixelinfo_t* clipped, gfx::drawpixelinfo_t* dpi, xy32 pos, uint16_t width, uint16_t height) + { + width >>= 1; + height >>= 1; + height += 16; + pos.x -= width; + pos.y -= height; + clipped->x += pos.x; + clipped->y += pos.y; + _dword_E0C3E0 = clipped; + + _byte_522095 = _byte_522095 | (1 << 1); + + drawRoad(0x2000, 0x2000, _word_1135FD8, 0x1E0, _byte_1136077, _lastSelectedTrackPieceId, _word_1135FD6, _byte_1136078); + + _byte_522095 = _byte_522095 & ~(1 << 1); + + drawCostString(self, dpi); + } + + // 0x0049CF36 + static void draw(window* self, gfx::drawpixelinfo_t* dpi) + { + self->draw(dpi); + common::drawTabs(self, dpi); + + if (self->widgets[widx::bridge].type != widget_type::none) + { + if (_lastSelectedBridge != 0xFF) + { + auto bridgeObj = objectmgr::get(_lastSelectedBridge); + if (bridgeObj != nullptr) + { + auto company = companymgr::get(_playerCompany); + auto imageId = gfx::recolour(bridgeObj->var_16, company->mainColours.primary); + auto x = self->x + self->widgets[widx::bridge].left + 2; + auto y = self->y + self->widgets[widx::bridge].top + 1; + + gfx::draw_image(dpi, x, y, imageId); + } + } + } + + if (self->widgets[widx::construct].type == widget_type::none) + return; + + if (_trackType & (1 << 7)) + { + auto road = common::getRoadPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + + _word_1135FD8 = _lastSelectedMods; + + if (!road) + return; + + _byte_1136077 = _trackType & ~(1 << 7); + _byte_1136078 = road->rotation; + _lastSelectedTrackPieceId = road->id; + _word_1135FD6 = (_lastSelectedBridge << 8) & 0x1F; + + auto x = self->x + self->widgets[widx::construct].left + 1; + auto y = self->y + self->widgets[widx::construct].top + 1; + auto width = self->widgets[widx::construct].width(); + auto height = self->widgets[widx::construct].height(); + + gfx::drawpixelinfo_t* clipped = nullptr; + + if (gfx::clip_drawpixelinfo(&clipped, dpi, x, y, width, height)) + { + auto roadPiece = common::roadPieces[_lastSelectedTrackPieceId]; + auto i = 0; + + while (roadPiece[i + 1].index != 0xFF) + { + i++; + } + + map_pos3 pos3D = { roadPiece[i].x, roadPiece[i].y, roadPiece[i].z }; + + if (roadPiece[i].flags & (1 << 6)) + { + pos3D.x = 0; + pos3D.y = 0; + } + + auto rotatedPos = rotate2DCoordinate({ pos3D.x, pos3D.y }, _byte_1136078 & 3); + pos3D.x = rotatedPos.x / 2; + pos3D.y = rotatedPos.y / 2; + pos3D.x += 0x2010; + pos3D.y += 0x2010; + pos3D.z += 0x1CC; + + auto pos2D = coordinate_3d_to_2d(pos3D.x, pos3D.y, pos3D.z, gCurrentRotation); + xy32 pos = { pos2D.x, pos2D.y }; + drawRoadCost(self, clipped, dpi, pos, width, height); + } + else + { + drawCostString(self, dpi); + } + } + else + { + auto track = common::getTrackPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation); + + _word_1135FD8 = _lastSelectedMods; + + if (!track) + return; + + _byte_1136077 = _trackType; + _byte_1136078 = track->rotation; + _lastSelectedTrackPieceId = track->id; + _word_1135FD6 = (_lastSelectedBridge << 8) & 0x1F; + + auto x = self->x + self->widgets[widx::construct].left + 1; + auto y = self->y + self->widgets[widx::construct].top + 1; + auto width = self->widgets[widx::construct].width(); + auto height = self->widgets[widx::construct].height(); + + gfx::drawpixelinfo_t* clipped = nullptr; + + if (gfx::clip_drawpixelinfo(&clipped, dpi, x, y, width, height)) + { + auto trackPiece = common::trackPieces[_lastSelectedTrackPieceId]; + auto i = 0; + + while (trackPiece[i + 1].index != 0xFF) + { + i++; + } + + map_pos3 pos3D = { trackPiece[i].x, trackPiece[i].y, trackPiece[i].z }; + + if (trackPiece[i].flags & (1 << 6)) + { + pos3D.x = 0; + pos3D.y = 0; + } + + auto rotatedPos = rotate2DCoordinate({ pos3D.x, pos3D.y }, _byte_1136078 & 3); + pos3D.x = rotatedPos.x / 2; + pos3D.y = rotatedPos.y / 2; + pos3D.x += 0x2010; + pos3D.y += 0x2010; + pos3D.z += 0x1CC; + + auto pos2D = coordinate_3d_to_2d(pos3D.x, pos3D.y, pos3D.z, gCurrentRotation); + xy32 pos = { pos2D.x, pos2D.y }; + drawTrackCost(self, clipped, dpi, pos, width, height); + } + else + { + drawCostString(self, dpi); + } + } + } + + void tabReset(window* self) + { + if (_constructionHover != 0) + { + _constructionHover = 0; + _byte_113607E = 1; + self->call_on_mouse_up(construction::widx::rotate_90); + } + } + + void init_events() + { + events.on_close = common::on_close; + events.on_mouse_up = on_mouse_up; + events.on_resize = on_resize; + events.on_mouse_down = on_mouse_down; + events.on_dropdown = on_dropdown; + events.on_update = on_update; + events.on_tool_update = on_tool_update; + events.on_tool_down = on_tool_down; + events.cursor = cursor; + events.prepare_draw = prepare_draw; + events.draw = draw; + } +} diff --git a/src/openloco/windows/construction/OverheadTab.cpp b/src/openloco/windows/construction/OverheadTab.cpp new file mode 100644 index 00000000..5a4d3670 --- /dev/null +++ b/src/openloco/windows/construction/OverheadTab.cpp @@ -0,0 +1,309 @@ +#include "../../companymgr.h" +#include "../../graphics/image_ids.h" +#include "../../input.h" +#include "../../localisation/FormatArguments.hpp" +#include "../../objects/objectmgr.h" +#include "../../objects/road_extra_object.h" +#include "../../objects/road_object.h" +#include "../../objects/track_extra_object.h" +#include "../../objects/track_object.h" +#include "../../ui/dropdown.h" +#include "Construction.h" + +using namespace openloco::interop; +using namespace openloco::map; +using namespace openloco::map::tilemgr; + +namespace openloco::ui::windows::construction::overhead +{ + widget_t widgets[] = { + commonWidgets(138, 192, string_ids::stringid_2), + make_widget({ 3, 45 }, { 132, 12 }, widget_type::checkbox, 1, string_ids::empty, string_ids::tooltip_select_track_mod), + make_widget({ 3, 57 }, { 132, 12 }, widget_type::checkbox, 1, string_ids::empty, string_ids::tooltip_select_track_mod), + make_widget({ 3, 69 }, { 132, 12 }, widget_type::checkbox, 1, string_ids::empty, string_ids::tooltip_select_track_mod), + make_widget({ 3, 81 }, { 132, 12 }, widget_type::checkbox, 1, string_ids::empty, string_ids::tooltip_select_track_mod), + make_widget({ 35, 110 }, { 66, 66 }, widget_type::wt_3, 1), + make_widget({ 3, 95 }, { 132, 12 }, widget_type::wt_18, 1, 0xFFFFFFFF, string_ids::tooltip_select_track_to_upgrade), + make_widget({ 123, 96 }, { 11, 10 }, widget_type::wt_11, 1, string_ids::dropdown, string_ids::tooltip_select_track_to_upgrade), + widget_end(), + }; + + window_event_list events; + + // 0x0049EBD1 + static void on_mouse_up(window* self, widget_index widgetIndex) + { + switch (widgetIndex) + { + case common::widx::close_button: + WindowManager::close(self); + break; + + case common::widx::tab_construction: + case common::widx::tab_overhead: + case common::widx::tab_signal: + case common::widx::tab_station: + common::switchTab(self, widgetIndex); + break; + + case widx::checkbox_1: + case widx::checkbox_2: + case widx::checkbox_3: + case widx::checkbox_4: + { + auto checkboxIndex = widgetIndex - widx::checkbox_1; + + _lastSelectedMods = _lastSelectedMods ^ (1 << checkboxIndex); + + // TODO: & ~(1 << 7) added to prevent crashing when selecting/deselecting overhead wires for trams + _scenarioTrackMods[_trackType & ~(1 << 7)] = _lastSelectedMods; + + self->invalidate(); + break; + } + } + } + + // 0x0049EBFC + static void on_mouse_down(window* self, widget_index widgetIndex) + { + switch (widgetIndex) + { + case widx::track_dropdown: + { + uint8_t modCount = 3; + + auto widget = self->widgets[widx::track]; + auto xPos = widget.left + self->x; + auto yPos = widget.top + self->y; + auto width = widget.width() + 2; + auto height = widget.height(); + + dropdown::show(xPos, yPos, width, height, self->colours[1], modCount, (1 << 7)); + + dropdown::add(0, string_ids::single_section); + dropdown::add(1, string_ids::block_section); + dropdown::add(2, string_ids::all_connected_track); + + dropdown::set_highlighted_item(_lastSelectedTrackModSection); + break; + } + + case widx::image: + { + input::cancel_tool(); + input::toolSet(self, widgetIndex, 12); + break; + } + } + } + + // 0x0049EC09 + static void on_dropdown(window* self, widget_index widgetIndex, int16_t itemIndex) + { + if (widgetIndex != widx::track_dropdown) + return; + + if (itemIndex != -1) + { + _lastSelectedTrackModSection = itemIndex; + self->invalidate(); + } + } + + // 0x0049ECD1 + static void on_update(window* self) + { + common::on_update(self, (1 << 5)); + } + + // 0x0049EC15 + static void on_tool_update(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + registers regs; + regs.esi = (int32_t)&self; + regs.dx = widgetIndex; + regs.ax = x; + regs.bx = y; + call(0x0049EC15, regs); + } + + // 0x0049EC20 + static void on_tool_down(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + registers regs; + regs.esi = (int32_t)&self; + regs.dx = widgetIndex; + regs.ax = x; + regs.bx = y; + call(0x0049EC20, regs); + } + + static void setCheckbox(window* self, widget_index checkboxIndex, string_id name) + { + auto widgetIndex = checkboxIndex + widx::checkbox_1; + self->widgets[widgetIndex].type = widget_type::checkbox; + self->widgets[widgetIndex].text = name; + + if (_lastSelectedMods & (1 << checkboxIndex)) + self->activated_widgets |= (1ULL << widgetIndex); + } + + // 0x0049E7D3 + static void prepare_draw(window* self) + { + common::prepare_draw(self); + + self->activated_widgets &= ~(1 << widx::checkbox_1 | 1 << widx::checkbox_2 | 1 << widx::checkbox_3 | 1 << widx::checkbox_4); + + self->widgets[widx::checkbox_1].type = widget_type::none; + self->widgets[widx::checkbox_2].type = widget_type::none; + self->widgets[widx::checkbox_3].type = widget_type::none; + self->widgets[widx::checkbox_4].type = widget_type::none; + + if (_trackType & (1 << 7)) + { + auto trackType = _trackType & ~(1 << 7); + auto roadObj = objectmgr::get(trackType); + + auto args = FormatArguments(); + args.push(roadObj->name); + + for (auto i = 0; i < 2; i++) + { + if (_modList[i] != 0xFF) + { + auto extraName = objectmgr::get(_modList[i])->name; + setCheckbox(self, i, extraName); + } + } + } + else + { + auto trackObj = objectmgr::get(_trackType); + + auto args = FormatArguments(); + args.push(trackObj->name); + + for (auto i = 0; i < 4; i++) + { + if (_modList[i] != 0xFF) + { + auto extraName = objectmgr::get(_modList[i])->name; + setCheckbox(self, i, extraName); + } + } + } + + //self->activated_widgets = activatedWidgets; + + self->widgets[widx::image].type = widget_type::none; + self->widgets[widx::track].type = widget_type::none; + self->widgets[widx::track_dropdown].type = widget_type::none; + + self->widgets[widx::image].tooltip = string_ids::null; + + if (_lastSelectedMods & 0xF) + { + self->widgets[widx::image].type = widget_type::wt_3; + self->widgets[widx::track].type = widget_type::wt_18; + self->widgets[widx::track_dropdown].type = widget_type::wt_11; + + self->widgets[widx::image].tooltip = string_ids::upgrade_track_with_mods; + + if (isTrackUpgradeMode()) + { + if (_toolWindowType == WindowType::construction) + self->widgets[widx::image].tooltip = string_ids::click_track_to_upgrade; + } + } + + static string_id modString[] = { + string_ids::single_section, + string_ids::block_section, + string_ids::all_connected_track, + }; + + self->widgets[widx::track].text = modString[_lastSelectedTrackModSection]; + + common::repositionTabs(self); + } + + // 0x0049EA3E + static void draw(window* self, gfx::drawpixelinfo_t* dpi) + { + self->draw(dpi); + common::drawTabs(self, dpi); + if (_lastSelectedMods & 0xF) + { + gfx::drawpixelinfo_t* clipped = nullptr; + auto xPos = self->x + self->widgets[widx::image].left + 1; + auto yPos = self->y + self->widgets[widx::image].top + 1; + auto width = self->widgets[widx::image].width(); + auto height = self->widgets[widx::image].height(); + + if (gfx::clip_drawpixelinfo(&clipped, dpi, xPos, yPos, width, height)) + { + coord_t x = 0x2010; + coord_t y = 0x2010; + + auto rotCoord = rotate2DCoordinate({ x, y }, gCurrentRotation); + gfx::point_t screenPos = { static_cast(rotCoord.y - rotCoord.x), static_cast(((rotCoord.x + rotCoord.y) >> 1) - 460) }; + + screenPos.x -= (self->widgets[widx::image].width() / 2); + screenPos.y -= ((self->widgets[widx::image].width() / 2) + 16); + clipped->x += screenPos.x; + clipped->y += screenPos.y; + + _dword_E0C3E0 = clipped; + + x = 0x2000; + y = 0x2000; + + auto company = companymgr::get(_playerCompany); + auto companyColour = company->mainColours.primary; + _byte_522095 = _byte_522095 | (1 << 0); + + if (_trackType & (1 << 7)) + { + uint8_t trackType = _trackType & ~(1 << 7); + construction::drawRoad(x, y, _lastSelectedMods, 0x1D0, trackType, 0, companyColour, gCurrentRotation); + } + else + { + construction::drawTrack(x, y, _lastSelectedMods, 0x1D0, _trackType, 0, companyColour, gCurrentRotation); + } + _byte_522095 = _byte_522095 & ~(1 << 0); + } + } + + auto xPos = self->x + 69; + auto yPos = self->widgets[widx::image].bottom + self->y + 4; + + if (_modCost != 0x80000000 && _modCost != 0) + { + auto args = FormatArguments(); + args.push(_modCost); + + gfx::draw_string_centred(*dpi, xPos, yPos, colour::black, string_ids::build_cost, &args); + } + } + + void tabReset(window* self) + { + self->call_on_mouse_down(overhead::widx::image); + } + + void init_events() + { + events.on_close = common::on_close; + events.on_mouse_up = on_mouse_up; + events.on_mouse_down = on_mouse_down; + events.on_dropdown = on_dropdown; + events.on_update = on_update; + events.on_tool_update = on_tool_update; + events.on_tool_down = on_tool_down; + events.prepare_draw = prepare_draw; + events.draw = draw; + } +} diff --git a/src/openloco/windows/construction/SignalTab.cpp b/src/openloco/windows/construction/SignalTab.cpp new file mode 100644 index 00000000..f87e5789 --- /dev/null +++ b/src/openloco/windows/construction/SignalTab.cpp @@ -0,0 +1,216 @@ +#include "../../graphics/image_ids.h" +#include "../../input.h" +#include "../../localisation/FormatArguments.hpp" +#include "../../objects/objectmgr.h" +#include "../../objects/track_object.h" +#include "../../objects/train_signal_object.h" +#include "../../ui/dropdown.h" +#include "Construction.h" + +using namespace openloco::interop; +using namespace openloco::map; +using namespace openloco::map::tilemgr; + +namespace openloco::ui::windows::construction::signal +{ + widget_t widgets[] = { + commonWidgets(138, 167, string_ids::stringid_2), + make_widget({ 3, 45 }, { 132, 12 }, widget_type::wt_18, 1, 0xFFFFFFFF, string_ids::tooltip_select_signal_type), + make_widget({ 123, 46 }, { 11, 10 }, widget_type::wt_11, 1, string_ids::dropdown, string_ids::tooltip_select_signal_type), + make_widget({ 27, 110 }, { 40, 40 }, widget_type::wt_9, 1, 0xFFFFFFFF, string_ids::tooltip_signal_both_directions), + make_widget({ 71, 110 }, { 40, 40 }, widget_type::wt_9, 1, 0xFFFFFFFF, string_ids::tooltip_signal_single_direction), + widget_end(), + }; + + window_event_list events; + + // 0x0049E64E + static void on_mouse_up(window* self, widget_index widgetIndex) + { + switch (widgetIndex) + { + case common::widx::close_button: + WindowManager::close(self); + break; + + case common::widx::tab_construction: + case common::widx::tab_overhead: + case common::widx::tab_signal: + case common::widx::tab_station: + common::switchTab(self, widgetIndex); + break; + } + } + + // 0x0049E669 + static void on_mouse_down(window* self, widget_index widgetIndex) + { + switch (widgetIndex) + { + case widx::signal_dropdown: + { + uint8_t signalCount = 0; + while (_signalList[signalCount] != 0xFF) + signalCount++; + + auto widget = self->widgets[widx::signal]; + auto xPos = widget.left + self->x; + auto yPos = widget.top + self->y; + auto width = widget.width() + 2; + auto height = widget.height(); + + dropdown::show(xPos, yPos, width, height, self->colours[1], signalCount, (1 << 7)); + + for (auto signalIndex = 0; signalIndex < signalCount; signalIndex++) + { + auto signal = _signalList[signalIndex]; + if (signal == _lastSelectedSignal) + dropdown::set_highlighted_item(signalIndex); + + auto trainSignalObj = objectmgr::get(signal); + + dropdown::add(signalIndex, trainSignalObj->name); + } + break; + } + + case widx::both_directions: + { + _isSignalBothDirections = 1; + input::cancel_tool(); + input::toolSet(self, widgetIndex, 42); + break; + } + + case widx::single_direction: + { + _isSignalBothDirections = 0; + input::cancel_tool(); + input::toolSet(self, widgetIndex, 42); + break; + } + } + } + + // 0x0049E67C + static void on_dropdown(window* self, widget_index widgetIndex, int16_t itemIndex) + { + if (widgetIndex != widx::signal_dropdown) + return; + + if (itemIndex != -1) + { + _lastSelectedSignal = _signalList[itemIndex]; + _scenarioSignals[_trackType] = _signalList[itemIndex]; + self->invalidate(); + } + } + + // 0x0049E76F + static void on_update(window* self) + { + common::on_update(self, (1 << 2)); + } + + // 0x0049E745 + static void on_tool_update(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + registers regs; + regs.esi = (int32_t)&self; + regs.dx = widgetIndex; + regs.ax = x; + regs.bx = y; + call(0x0049E745, regs); + } + + // 0x0049E75A + static void on_tool_down(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + registers regs; + regs.esi = (int32_t)&self; + regs.dx = widgetIndex; + regs.ax = x; + regs.bx = y; + call(0x0049E75A, regs); + } + + // 0x0049E499 + static void prepare_draw(window* self) + { + common::prepare_draw(self); + + auto trackObj = objectmgr::get(_trackType); + + auto args = FormatArguments(); + args.push(trackObj->name); + + auto trainSignalObject = objectmgr::get(_lastSelectedSignal); + + self->widgets[widx::signal].text = trainSignalObject->name; + + common::repositionTabs(self); + } + + // 0x0049E501 + static void draw(window* self, gfx::drawpixelinfo_t* dpi) + { + self->draw(dpi); + common::drawTabs(self, dpi); + + auto trainSignalObject = objectmgr::get(_lastSelectedSignal); + + auto xPos = self->x + 3; + auto yPos = self->y + 63; + auto width = 130; + + { + auto args = FormatArguments(); + args.push(trainSignalObject->var_0C); + + gfx::draw_string_495224(*dpi, xPos, yPos, width, colour::black, string_ids::signal_black, &args); + } + + auto imageId = trainSignalObject->var_0E; + + xPos = self->widgets[widx::both_directions].mid_x() + self->x; + yPos = self->widgets[widx::both_directions].bottom + self->y - 4; + + gfx::draw_image(dpi, xPos - 8, yPos, imageId); + + gfx::draw_image(dpi, xPos + 8, yPos, imageId + 4); + + xPos = self->widgets[widx::single_direction].mid_x() + self->x; + yPos = self->widgets[widx::single_direction].bottom + self->y - 4; + + gfx::draw_image(dpi, xPos, yPos, imageId); + + if (_signalCost != 0x80000000 && _signalCost != 0) + { + auto args = FormatArguments(); + args.push(_signalCost); + + xPos = self->x + 69; + yPos = self->widgets[widx::single_direction].bottom + self->y + 5; + + gfx::draw_string_centred(*dpi, xPos, yPos, colour::black, string_ids::build_cost, &args); + } + } + + void tabReset(window* self) + { + self->call_on_mouse_down(signal::widx::both_directions); + } + + void init_events() + { + events.on_close = common::on_close; + events.on_mouse_up = on_mouse_up; + events.on_mouse_down = on_mouse_down; + events.on_dropdown = on_dropdown; + events.on_update = on_update; + events.on_tool_update = on_tool_update; + events.on_tool_down = on_tool_down; + events.prepare_draw = prepare_draw; + events.draw = draw; + } +} diff --git a/src/openloco/windows/construction/StationTab.cpp b/src/openloco/windows/construction/StationTab.cpp new file mode 100644 index 00000000..6d92c7cd --- /dev/null +++ b/src/openloco/windows/construction/StationTab.cpp @@ -0,0 +1,411 @@ +#include "../../companymgr.h" +#include "../../graphics/image_ids.h" +#include "../../input.h" +#include "../../localisation/FormatArguments.hpp" +#include "../../objects/airport_object.h" +#include "../../objects/cargo_object.h" +#include "../../objects/dock_object.h" +#include "../../objects/objectmgr.h" +#include "../../objects/road_object.h" +#include "../../objects/road_station_object.h" +#include "../../objects/track_object.h" +#include "../../objects/train_station_object.h" +#include "../../stationmgr.h" +#include "../../ui/dropdown.h" +#include "Construction.h" + +using namespace openloco::interop; +using namespace openloco::map; +using namespace openloco::map::tilemgr; + +namespace openloco::ui::windows::construction::station +{ + widget_t widgets[] = { + commonWidgets(138, 190, string_ids::stringid_2), + make_widget({ 3, 45 }, { 132, 12 }, widget_type::wt_18, 1, 0xFFFFFFFF, string_ids::tooltip_select_station_type), + make_widget({ 123, 46 }, { 11, 10 }, widget_type::wt_11, 1, string_ids::dropdown, string_ids::tooltip_select_station_type), + make_widget({ 35, 60 }, { 68, 68 }, widget_type::wt_3, 1), + make_widget({ 112, 104 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::rotate_object, string_ids::rotate_90), + widget_end(), + }; + + window_event_list events; + + // 0x0049E228 + static void on_mouse_up(window* self, widget_index widgetIndex) + { + switch (widgetIndex) + { + case common::widx::close_button: + WindowManager::close(self); + break; + + case common::widx::tab_construction: + case common::widx::tab_overhead: + case common::widx::tab_signal: + case common::widx::tab_station: + common::switchTab(self, widgetIndex); + break; + + case widx::rotate: + _constructionRotation++; + _constructionRotation = _constructionRotation & 3; + _stationCost = 0x80000000; + self->invalidate(); + break; + } + } + + template + void AddStationsToDropdown(const uint8_t stationCount) + { + for (auto stationIndex = 0; stationIndex < stationCount; stationIndex++) + { + auto station = _stationList[stationIndex]; + if (station == _lastSelectedStationType) + dropdown::set_highlighted_item(stationIndex); + + auto obj = objectmgr::get(station); + dropdown::add(stationIndex, obj->name); + } + } + + // 0x0049E249 + static void on_mouse_down(window* self, widget_index widgetIndex) + { + switch (widgetIndex) + { + case widx::station_dropdown: + { + uint8_t stationCount = 0; + while (_stationList[stationCount] != 0xFF) + stationCount++; + + auto widget = self->widgets[widx::station]; + auto xPos = widget.left + self->x; + auto yPos = widget.top + self->y; + auto width = widget.width() + 2; + auto height = widget.height(); + dropdown::show(xPos, yPos, width, height, self->colours[1], stationCount, (1 << 7)); + + if (_byte_1136063 & (1 << 7)) + { + AddStationsToDropdown(stationCount); + } + else if (_byte_1136063 & (1 << 6)) + { + AddStationsToDropdown(stationCount); + } + else if (_trackType & (1 << 7)) + { + AddStationsToDropdown(stationCount); + } + else + { + AddStationsToDropdown(stationCount); + } + break; + } + case widx::image: + { + input::cancel_tool(); + input::toolSet(self, widgetIndex, 44); + break; + } + } + } + + // 0x0049E256 + static void on_dropdown(window* self, widget_index widgetIndex, int16_t itemIndex) + { + if (widgetIndex == widx::station_dropdown) + { + if (itemIndex == -1) + return; + + auto selectedStation = _stationList[itemIndex]; + _lastSelectedStationType = selectedStation; + + if (_byte_1136063 & (1 << 7)) + { + _lastAirport = selectedStation; + } + else if (_byte_1136063 & (1 << 6)) + { + _lastShipPort = selectedStation; + } + else if (_trackType & (1 << 7)) + { + auto trackType = _trackType & ~(1 << 7); + _scenarioRoadStations[trackType] = selectedStation; + } + else + { + _scenarioTrainStations[_trackType] = selectedStation; + } + + self->invalidate(); + } + } + + // 0x0049E437 + static void on_update(window* self) + { + common::on_update(self, (1 << 3)); + } + + // 0x0049E421 + static void on_tool_update(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + registers regs; + regs.esi = (int32_t)&self; + regs.dx = widgetIndex; + regs.ax = x; + regs.bx = y; + call(0x0049E421, regs); + } + + // 0x0049E42C + static void on_tool_down(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y) + { + registers regs; + regs.esi = (int32_t)&self; + regs.dx = widgetIndex; + regs.ax = x; + regs.bx = y; + call(0x0049E42C, regs); + } + + // 0x0049DD39 + static void prepare_draw(window* self) + { + common::prepare_draw(self); + + self->widgets[widx::rotate].type = widget_type::none; + + auto args = FormatArguments(); + + if (_byte_1136063 & (1 << 7)) + { + self->widgets[widx::rotate].type = widget_type::wt_9; + + auto airportObj = objectmgr::get(_lastSelectedStationType); + + self->widgets[widx::station].text = airportObj->name; + + args.push(string_ids::title_airport); + } + else if (_byte_1136063 & (1 << 6)) + { + auto dockObj = objectmgr::get(_lastSelectedStationType); + + self->widgets[widx::station].text = dockObj->name; + + args.push(string_ids::title_ship_port); + } + else if (_trackType & (1 << 7)) + { + auto trackType = _trackType & ~(1 << 7); + + auto roadObj = objectmgr::get(trackType); + + args.push(roadObj->name); + + auto roadStationObject = objectmgr::get(_lastSelectedStationType); + + self->widgets[widx::station].text = roadStationObject->name; + } + else + { + auto trackObj = objectmgr::get(_trackType); + + args.push(trackObj->name); + + auto trainStationObject = objectmgr::get(_lastSelectedStationType); + + self->widgets[widx::station].text = trainStationObject->name; + } + + common::repositionTabs(self); + } + + // 0x0049DE40 + static void draw(window* self, gfx::drawpixelinfo_t* dpi) + { + self->draw(dpi); + common::drawTabs(self, dpi); + + auto company = companymgr::get(_playerCompany); + auto companyColour = company->mainColours.primary; + int16_t xPos = self->widgets[widx::image].left + self->x; + int16_t yPos = self->widgets[widx::image].top + self->y; + + if (_byte_1136063 & (1 << 7)) + { + auto airportObj = objectmgr::get(_lastSelectedStationType); + + auto imageId = gfx::recolour(airportObj->var_08, companyColour); + + gfx::draw_image(dpi, xPos, yPos, imageId); + } + else if (_byte_1136063 & (1 << 6)) + { + auto dockObj = objectmgr::get(_lastSelectedStationType); + + auto imageId = gfx::recolour(dockObj->var_08, companyColour); + + gfx::draw_image(dpi, xPos, yPos, imageId); + } + else if (_trackType & (1 << 7)) + { + auto roadStationObj = objectmgr::get(_lastSelectedStationType); + + auto imageId = gfx::recolour(roadStationObj->var_0C, companyColour); + + gfx::draw_image(dpi, xPos, yPos, imageId); + + auto colour = _byte_5045FA[companyColour]; + + if (!(roadStationObj->flags & road_station_flags::recolourable)) + { + colour = 46; + } + + imageId = gfx::recolour(imageId, colour) + 1; + + gfx::draw_image(dpi, xPos, yPos, imageId); + } + else + { + auto trainStationObj = objectmgr::get(_lastSelectedStationType); + + auto imageId = gfx::recolour(trainStationObj->var_0E, companyColour); + + gfx::draw_image(dpi, xPos, yPos, imageId); + + auto colour = _byte_5045FA[companyColour]; + + if (!(trainStationObj->flags & train_station_flags::recolourable)) + { + colour = 46; + } + + imageId = gfx::recolour(imageId, colour) + 1; + + gfx::draw_image(dpi, xPos, yPos, imageId); + } + + if (_stationCost != 0x80000000 && _stationCost != 0) + { + xPos = self->x + 69; + yPos = self->widgets[widx::image].bottom + self->y + 4; + + auto args = FormatArguments(); + args.push(_stationCost); + + gfx::draw_string_centred(*dpi, xPos, yPos, colour::black, string_ids::build_cost, &args); + } + + xPos = self->x + 3; + yPos = self->widgets[widx::image].bottom + self->y + 16; + auto width = self->width - 4; + gfx::draw_rect_inset(dpi, xPos, yPos, width, 1, self->colours[1], (1 << 5)); + + if (!(_byte_522096 & (1 << 3))) + return; + + auto args = FormatArguments(); + + if (_constructingStationId == 0xFFFFFFFF) + { + args.push(string_ids::new_station); + } + else + { + auto station = stationmgr::get(_constructingStationId); + args.push(station->name); + args.push(station->town); + } + + xPos = self->x + 69; + yPos = self->widgets[widx::image].bottom + self->y + 18; + width = self->width - 4; + gfx::draw_string_centred_clipped(*dpi, xPos, yPos, width, colour::black, string_ids::new_station_buffer, &args); + + xPos = self->x + 2; + yPos = self->widgets[widx::image].bottom + self->y + 29; + gfx::point_t origin = { xPos, yPos }; + + gfx::draw_string_494B3F(*dpi, &origin, colour::black, string_ids::catchment_area_accepts); + + if (_constructingStationAcceptedCargoTypes == 0) + { + gfx::draw_string_494B3F(*dpi, origin.x, origin.y, colour::black, string_ids::catchment_area_nothing); + } + else + { + yPos--; + for (uint8_t i = 0; i < objectmgr::get_max_objects(object_type::cargo); i++) + { + if (_constructingStationAcceptedCargoTypes & (1 << i)) + { + auto xPosMax = self->x + self->width - 12; + if (origin.x <= xPosMax) + { + auto cargoObj = objectmgr::get(i); + + gfx::draw_image(dpi, origin.x, origin.y, cargoObj->unit_inline_sprite); + origin.x += 10; + } + } + } + } + + xPos = self->x + 2; + yPos = self->widgets[widx::image].bottom + self->y + 49; + origin = { xPos, yPos }; + + gfx::draw_string_494B3F(*dpi, &origin, colour::black, string_ids::catchment_area_produces); + + if (_constructingStationProducedCargoTypes == 0) + { + gfx::draw_string_494B3F(*dpi, origin.x, origin.y, colour::black, string_ids::catchment_area_nothing); + } + else + { + yPos--; + for (uint8_t i = 0; i < objectmgr::get_max_objects(object_type::cargo); i++) + { + if (_constructingStationProducedCargoTypes & (1 << i)) + { + auto xPosMax = self->x + self->width - 12; + if (origin.x <= xPosMax) + { + auto cargoObj = objectmgr::get(i); + + gfx::draw_image(dpi, origin.x, origin.y, cargoObj->unit_inline_sprite); + origin.x += 10; + } + } + } + } + } + + void tabReset(window* self) + { + self->call_on_mouse_down(station::widx::image); + } + + void init_events() + { + events.on_close = common::on_close; + events.on_mouse_up = on_mouse_up; + events.on_mouse_down = on_mouse_down; + events.on_dropdown = on_dropdown; + events.on_update = on_update; + events.on_tool_update = on_tool_update; + events.on_tool_down = on_tool_down; + events.prepare_draw = prepare_draw; + events.draw = draw; + } +} diff --git a/src/openloco/windows/constructionwnd.cpp b/src/openloco/windows/constructionwnd.cpp deleted file mode 100644 index 037844b9..00000000 --- a/src/openloco/windows/constructionwnd.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "../input.h" -#include "../interop/interop.hpp" -#include "../ui/WindowManager.h" - -using namespace openloco::interop; - -namespace openloco::ui::windows::construction -{ - namespace widx - { - enum - { - close = 2, - tab_0 = 4, - tab_1, - tab_2, - tab_3, - construct = 28, - remove, - place, - }; - } - - // 0x004A3B0D - window* openWithFlags(const uint32_t flags) - { - registers regs; - regs.ecx = flags; - call(0x004A3B0D, regs); - return (window*)regs.esi; - } - - // 0x0049D3F6 - void on_mouse_up(window& w, const uint16_t widgetIndex) - { - // Allow shift key to repeat the action multiple times - // This is useful for building very long tracks. - int multiplier = 1; - if (input::has_key_modifier(input::key_modifier::shift)) - { - multiplier = 10; - } - - registers regs; - regs.edx = widgetIndex; - regs.esi = (int32_t)&w; - switch (widgetIndex) - { - case widx::close: - WindowManager::close(&w); - break; - case widx::tab_0: - case widx::tab_1: - case widx::tab_2: - case widx::tab_3: - call(0x0049D93A, regs); - break; - case widx::construct: - for (int i = 0; i < multiplier; i++) - { - call(0x0049F92D, regs); - } - break; - case widx::remove: - for (int i = 0; i < multiplier; i++) - { - call(0x004A0121, regs); - } - break; - case widx::place: - call(0x0049D7DC, regs); - break; - } - } -} diff --git a/src/openloco/windows/stationwnd.cpp b/src/openloco/windows/stationwnd.cpp index 258b02b3..de695384 100644 --- a/src/openloco/windows/stationwnd.cpp +++ b/src/openloco/windows/stationwnd.cpp @@ -8,6 +8,8 @@ #include "../interop/interop.hpp" #include "../localisation/FormatArguments.hpp" #include "../localisation/string_ids.h" +#include "../map/tile_loop.hpp" +#include "../map/tilemgr.h" #include "../objects/cargo_object.h" #include "../objects/interface_skin_object.h" #include "../objects/objectmgr.h" @@ -18,10 +20,13 @@ #include "../widget.h" using namespace openloco::interop; +using namespace openloco::map; namespace openloco::ui::windows::station { - static loco_global word_112C786; + static loco_global _byte_F00484; + static loco_global _mapSelectionFlags; + static loco_global _lastSelectedStation; static loco_global gGameCommandErrorTitle; @@ -361,7 +366,7 @@ namespace openloco::ui::windows::station common::repositionTabs(self); self->activated_widgets &= ~(1 << widx::station_catchment); - if (self->number == word_112C786) + if (self->number == _lastSelectedStation) self->activated_widgets |= (1 << widx::station_catchment); } @@ -427,15 +432,14 @@ namespace openloco::ui::windows::station break; case widx::station_catchment: - auto windowNumber = self->number; - if (self->number == word_112C786) - windowNumber = -1; + { + station_id_t windowNumber = self->number; + if (windowNumber == _lastSelectedStation) + windowNumber = station_id::null; - // 0x0049271A - registers regs; - regs.ax = windowNumber; - call(0x0049271A, regs); + showStationCatchment(windowNumber); break; + } } } @@ -718,6 +722,65 @@ namespace openloco::ui::windows::station } } + // 0x00491BC6 + static void sub_491BC6() + { + tile_loop tileLoop; + + for (uint32_t posId = 0; posId < 0x24000; posId++) + { + if (_byte_F00484[posId] & (1 << 0)) + { + tilemgr::map_invalidate_tile_full(tileLoop.current()); + } + tileLoop.next(); + } + } + + // 0x00491D70 + static void setStationCatchmentDisplay(openloco::station* station, uint16_t dx) + { + registers regs; + regs.ebp = uint32_t(station); + regs.dx = dx; + call(0x00491D70, regs); + } + + // 0x0049271A + void showStationCatchment(uint16_t stationId) + { + if (stationId == _lastSelectedStation) + return; + + uint16_t oldStationId = *_lastSelectedStation; + _lastSelectedStation = stationId; + + if (oldStationId != station_id::null) + { + if (input::hasMapSelectionFlag(input::map_selection_flags::catchment_area)) + { + WindowManager::invalidate(WindowType::station, oldStationId); + sub_491BC6(); + input::resetMapSelectionFlag(input::map_selection_flags::catchment_area); + } + } + + auto newStationId = _lastSelectedStation; + + if (newStationId != station_id::null) + { + ui::windows::construction::sub_4A6FAC(); + auto station = stationmgr::get(_lastSelectedStation); + + setStationCatchmentDisplay(station, 0); + input::setMapSelectionFlags(input::map_selection_flags::catchment_area); + + WindowManager::invalidate(WindowType::station, newStationId); + + sub_491BC6(); + } + } + namespace common { struct TabInformation @@ -847,12 +910,9 @@ namespace openloco::ui::windows::station static void switchTab(window* self, widget_index widgetIndex) { if (widgetIndex == widx::tab_cargo) - if (self->number == word_112C786) + if (self->number == _lastSelectedStation) { - // 0x0049271A - registers regs; - regs.ax = -1; - call(0x0049271A, regs); + showStationCatchment(station_id::null); } if (input::is_tool_active(self->type, self->number))