OpenLoco/src/openloco/windows/construction/ConstructionTab.cpp

2119 lines
75 KiB
C++

#include "../../TrackData.h"
#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
{
static loco_global<uint16_t[351][4], 0x004F7B62> _word_4F7B62; // TODO: Not sure on size?
static loco_global<uint8_t, 0x00508F09> _byte_508F09;
static loco_global<uint8_t, 0x00522090> _byte_522090;
static loco_global<uint8_t, 0x00522091> _byte_522091;
static loco_global<uint8_t, 0x00522092> _byte_522092;
static loco_global<uint16_t, 0x00523376> _clickRepeatTicks;
static loco_global<uint16_t, 0x0052338A> _tooltipTimeout;
static loco_global<uint32_t, 0x00523394> _toolWidgetIndex;
static loco_global<int32_t, 0x00E3F0B8> gCurrentRotation;
namespace TrackPiece
{
enum
{
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,
};
}
namespace TrackGradient
{
enum
{
level = 0,
slope_up = 2,
steep_slope_up = 4,
slope_down = 6,
steep_slope_down = 8,
};
};
struct TrackPieceId
{
uint8_t id;
uint8_t rotation;
};
widget_t widgets[] = {
commonWidgets(138, 276, string_ids::stringid_2),
makeWidget({ 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),
makeWidget({ 3, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_left_hand_curve_small, string_ids::tooltip_left_hand_curve_small),
makeWidget({ 25, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_left_hand_curve, string_ids::tooltip_left_hand_curve),
makeWidget({ 47, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_left_hand_curve_large, string_ids::tooltip_left_hand_curve_large),
makeWidget({ 69, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_right_hand_curve_large, string_ids::tooltip_right_hand_curve_large),
makeWidget({ 91, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_right_hand_curve, string_ids::tooltip_right_hand_curve),
makeWidget({ 113, 45 }, { 22, 24 }, widget_type::wt_9, 1, image_ids::construction_right_hand_curve_small, string_ids::tooltip_right_hand_curve_small),
makeWidget({ 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),
makeWidget({ 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),
makeWidget({ 33, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_s_bend_left, string_ids::tooltip_s_bend_left),
makeWidget({ 57, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_straight, string_ids::tooltip_straight),
makeWidget({ 81, 69 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_s_bend_right, string_ids::tooltip_s_bend_right),
makeWidget({ 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),
makeWidget({ 9, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_steep_slope_down, string_ids::tooltip_steep_slope_down),
makeWidget({ 33, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_slope_down, string_ids::tooltip_slope_down),
makeWidget({ 57, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_level, string_ids::tooltip_level),
makeWidget({ 81, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_slope_up, string_ids::tooltip_slope_up),
makeWidget({ 105, 96 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::construction_steep_slope_up, string_ids::tooltip_steep_slope_up),
makeWidget({ 40, 123 }, { 58, 20 }, widget_type::wt_18, 1, string_ids::empty, string_ids::tooltip_bridge_stats),
makeWidget({ 86, 124 }, { 11, 18 }, widget_type::wt_11, 1, string_ids::dropdown, string_ids::tooltip_bridge_stats),
makeWidget({ 3, 145 }, { 132, 100 }, widget_type::wt_5, 1, 0xFFFFFFFF, string_ids::tooltip_construct),
makeWidget({ 6, 248 }, { 46, 24 }, widget_type::wt_9, 1, image_ids::construction_remove, string_ids::tooltip_remove),
makeWidget({ 57, 248 }, { 24, 24 }, widget_type::wt_9, 1, image_ids::rotate_object, string_ids::rotate_90),
widgetEnd(),
};
const uint8_t trackPieceWidgets[] = {
widx::straight,
widx::left_hand_curve_very_small,
widx::right_hand_curve_very_small,
widx::left_hand_curve_small,
widx::right_hand_curve_small,
widx::left_hand_curve,
widx::right_hand_curve,
widx::left_hand_curve_large,
widx::right_hand_curve_large,
widx::s_bend_left,
widx::s_bend_right,
};
window_event_list events;
static std::optional<TrackPieceId> getRoadPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation);
static std::optional<TrackPieceId> getTrackPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation);
// 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 onMouseUp(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::construct:
constructTrack(self, widgetIndex);
break;
case widx::remove:
removeTrack(self, widgetIndex);
break;
case widx::rotate_90:
{
if (_constructionHover == 1)
{
_constructionRotation++;
_constructionRotation = _constructionRotation & 3;
_trackCost = 0x80000000;
activateSelectedConstructionWidgets();
break;
}
common::sub_49FEC7();
WindowManager::viewportSetVisibility(WindowManager::viewport_visibility::overgroundView);
input::toolSet(self, widx::construct, 12);
input::setFlag(input::input_flags::flag6);
_constructionHover = 1;
_byte_113607E = 0;
_constructionRotation = _constructionRotation & 3;
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 = getRoadPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation);
if (!road)
disabledWidgets |= (1 << widx::construct);
}
self->setDisabledWidgetsAndInvalidate(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 = getTrackPieceId(_lastSelectedTrackPiece, _lastSelectedTrackGradient, _constructionRotation);
if (!track)
disabledWidgets |= (1 << widx::construct);
}
self->setDisabledWidgetsAndInvalidate(disabledWidgets);
}
// 0x0049DAF3
static void disableTrackSlopes(window* self, track_object trackObj, uint64_t disabledWidgets)
{
auto maskedTrackPieces = trackObj.track_pieces & ((1 << 5) | (1 << 8));
if (maskedTrackPieces != ((1 << 5) | (1 << 8)))
disabledWidgets |= (1 << widx::slope_down) | (1 << widx::slope_up);
maskedTrackPieces = trackObj.track_pieces & ((1 << 6) | (1 << 8));
if (maskedTrackPieces != ((1 << 6) | (1 << 8)))
disabledWidgets |= (1 << widx::steep_slope_down) | (1 << widx::steep_slope_up);
disableUnusedTrackPieces(self, trackObj, disabledWidgets);
}
static void activateSelectedRoadWidgets(window* window)
{
tilemgr::mapInvalidateMapSelectionTiles();
_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;
}
const auto& roadPiece = openloco::map::TrackData::getRoadPiece(roadId);
auto posId = 0;
rotation &= 3;
for (const auto& roadPart : roadPiece)
{
if (roadPart.flags & openloco::map::TrackData::PreviewTrackFlags::diagonal)
{
continue;
}
map_pos pos = { roadPart.x, roadPart.y };
pos = rotate2dCoordinate(pos, rotation);
pos.x += x;
pos.y += y;
_mapSelectedTiles[posId] = pos;
posId++;
}
_mapSelectedTiles[posId].x = -1;
mapInvalidateMapSelectionTiles();
window->holdable_widgets = (1 << widx::construct) | (1 << widx::remove);
auto trackType = _trackType & ~(1 << 7);
auto roadObj = objectmgr::get<road_object>(trackType);
window->widgets[widx::s_bend_left].type = widget_type::none;
window->widgets[widx::s_bend_right].type = widget_type::none;
window->widgets[widx::left_hand_curve_large].type = widget_type::none;
window->widgets[widx::right_hand_curve_large].type = widget_type::none;
window->widgets[widx::left_hand_curve].type = widget_type::none;
window->widgets[widx::right_hand_curve].type = widget_type::none;
window->widgets[widx::left_hand_curve_small].type = widget_type::none;
window->widgets[widx::right_hand_curve_small].type = widget_type::none;
window->widgets[widx::left_hand_curve_very_small].type = widget_type::none;
window->widgets[widx::right_hand_curve_very_small].type = widget_type::none;
window->widgets[widx::left_hand_curve_small].left = 3;
window->widgets[widx::left_hand_curve_small].right = 24;
window->widgets[widx::right_hand_curve_small].left = 113;
window->widgets[widx::right_hand_curve_small].right = 134;
window->widgets[widx::left_hand_curve].left = 25;
window->widgets[widx::left_hand_curve].right = 46;
window->widgets[widx::right_hand_curve].left = 91;
window->widgets[widx::right_hand_curve].right = 112;
if (roadObj->road_pieces & road_piece_flags::track)
{
window->widgets[widx::left_hand_curve_small].left = 25;
window->widgets[widx::left_hand_curve_small].right = 46;
window->widgets[widx::right_hand_curve_small].left = 91;
window->widgets[widx::right_hand_curve_small].right = 112;
window->widgets[widx::left_hand_curve].left = 47;
window->widgets[widx::left_hand_curve].right = 68;
window->widgets[widx::right_hand_curve].left = 69;
window->widgets[widx::right_hand_curve].right = 90;
window->widgets[widx::left_hand_curve_very_small].type = widget_type::wt_9;
window->widgets[widx::right_hand_curve_very_small].type = widget_type::wt_9;
}
if (roadObj->road_pieces & road_piece_flags::one_way)
{
window->widgets[widx::left_hand_curve_small].type = widget_type::wt_9;
window->widgets[widx::right_hand_curve_small].type = widget_type::wt_9;
}
window->widgets[widx::s_bend_dual_track_left].type = widget_type::none;
window->widgets[widx::s_bend_dual_track_right].type = widget_type::none;
if (roadObj->road_pieces & road_piece_flags::one_sided)
{
window->widgets[widx::s_bend_dual_track_left].type = widget_type::wt_9;
window->widgets[widx::s_bend_dual_track_left].image = image_ids::construction_right_turnaround;
window->widgets[widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_turnaround;
if (_byte_525FAE == 0)
window->widgets[widx::s_bend_dual_track_left].image = image_ids::construction_left_turnaround;
}
window->widgets[widx::steep_slope_down].type = widget_type::none;
window->widgets[widx::slope_down].type = widget_type::none;
window->widgets[widx::slope_up].type = widget_type::none;
window->widgets[widx::steep_slope_up].type = widget_type::none;
if (roadObj->road_pieces & road_piece_flags::slope)
{
window->widgets[widx::slope_down].type = widget_type::wt_9;
window->widgets[widx::slope_up].type = widget_type::wt_9;
}
if (roadObj->road_pieces & road_piece_flags::steep_slope)
{
window->widgets[widx::steep_slope_down].type = widget_type::wt_9;
window->widgets[widx::steep_slope_up].type = widget_type::wt_9;
}
window->widgets[widx::bridge].type = widget_type::wt_18;
window->widgets[widx::bridge_dropdown].type = widget_type::wt_11;
if (_lastSelectedBridge == 0xFF || (_constructionHover != 1 && !(_byte_1136076 & 1)))
{
window->widgets[widx::bridge].type = widget_type::none;
window->widgets[widx::bridge_dropdown].type = widget_type::none;
}
auto activatedWidgets = window->activated_widgets;
activatedWidgets &= ~(construction::allTrack);
window->widgets[widx::construct].type = widget_type::none;
window->widgets[widx::remove].type = widget_type::wt_9;
window->widgets[widx::rotate_90].type = widget_type::none;
if (_constructionHover == 1)
{
window->widgets[widx::construct].type = widget_type::wt_5;
window->widgets[widx::construct].tooltip = string_ids::tooltip_start_construction;
window->widgets[widx::remove].type = widget_type::none;
window->widgets[widx::rotate_90].type = widget_type::wt_9;
window->widgets[widx::rotate_90].image = image_ids::rotate_object;
window->widgets[widx::rotate_90].tooltip = string_ids::rotate_90;
}
else if (_constructionHover == 0)
{
window->widgets[widx::construct].type = widget_type::wt_3;
window->widgets[widx::construct].tooltip = string_ids::tooltip_construct;
window->widgets[widx::rotate_90].type = widget_type::wt_9;
window->widgets[widx::rotate_90].image = image_ids::construction_new_position;
window->widgets[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 = widx::level;
switch (_lastSelectedTrackGradient)
{
case TrackGradient::level:
trackGradient = widx::level;
break;
case TrackGradient::slope_up:
trackGradient = widx::slope_up;
break;
case TrackGradient::slope_down:
trackGradient = widx::slope_down;
break;
case TrackGradient::steep_slope_up:
trackGradient = widx::steep_slope_up;
break;
case TrackGradient::steep_slope_down:
trackGradient = widx::steep_slope_down;
break;
}
activatedWidgets |= 1ULL << trackGradient;
}
window->activated_widgets = activatedWidgets;
window->invalidate();
}
static void activateSelectedTrackWidgets(window* window)
{
tilemgr::mapInvalidateMapSelectionTiles();
_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;
}
const auto& trackPiece = openloco::map::TrackData::getTrackPiece(trackId);
auto posId = 0;
rotation &= 3;
for (const auto& trackPart : trackPiece)
{
if (trackPart.flags & openloco::map::TrackData::PreviewTrackFlags::diagonal)
{
continue;
}
map_pos pos = { trackPart.x, trackPart.y };
pos = rotate2dCoordinate(pos, rotation);
pos.x += x;
pos.y += y;
_mapSelectedTiles[posId] = pos;
posId++;
}
_mapSelectedTiles[posId].x = -1;
mapInvalidateMapSelectionTiles();
window->holdable_widgets = (1 << widx::construct) | (1 << widx::remove);
auto trackObj = objectmgr::get<track_object>(_trackType);
window->widgets[widx::s_bend_left].type = widget_type::wt_9;
window->widgets[widx::s_bend_right].type = widget_type::wt_9;
window->widgets[widx::left_hand_curve_large].type = widget_type::none;
window->widgets[widx::right_hand_curve_large].type = widget_type::none;
window->widgets[widx::left_hand_curve].type = widget_type::none;
window->widgets[widx::right_hand_curve].type = widget_type::none;
window->widgets[widx::left_hand_curve_small].type = widget_type::none;
window->widgets[widx::right_hand_curve_small].type = widget_type::none;
window->widgets[widx::left_hand_curve_very_small].type = widget_type::none;
window->widgets[widx::right_hand_curve_very_small].type = widget_type::none;
window->widgets[widx::left_hand_curve_small].left = 3;
window->widgets[widx::left_hand_curve_small].right = 24;
window->widgets[widx::right_hand_curve_small].left = 113;
window->widgets[widx::right_hand_curve_small].right = 134;
window->widgets[widx::left_hand_curve].left = 25;
window->widgets[widx::left_hand_curve].right = 46;
window->widgets[widx::right_hand_curve].left = 91;
window->widgets[widx::right_hand_curve].right = 112;
if (trackObj->track_pieces & track_piece_flags::very_small_curve)
{
window->widgets[widx::left_hand_curve_small].left = 25;
window->widgets[widx::left_hand_curve_small].right = 46;
window->widgets[widx::right_hand_curve_small].left = 91;
window->widgets[widx::right_hand_curve_small].right = 112;
window->widgets[widx::left_hand_curve].left = 47;
window->widgets[widx::left_hand_curve].right = 68;
window->widgets[widx::right_hand_curve].left = 69;
window->widgets[widx::right_hand_curve].right = 90;
window->widgets[widx::left_hand_curve_very_small].type = widget_type::wt_9;
window->widgets[widx::right_hand_curve_very_small].type = widget_type::wt_9;
}
if (trackObj->track_pieces & track_piece_flags::large_curve)
{
window->widgets[widx::left_hand_curve_large].type = widget_type::wt_9;
window->widgets[widx::right_hand_curve_large].type = widget_type::wt_9;
}
if (trackObj->track_pieces & track_piece_flags::normal_curve)
{
window->widgets[widx::left_hand_curve].type = widget_type::wt_9;
window->widgets[widx::right_hand_curve].type = widget_type::wt_9;
}
if (trackObj->track_pieces & track_piece_flags::small_curve)
{
window->widgets[widx::left_hand_curve_small].type = widget_type::wt_9;
window->widgets[widx::right_hand_curve_small].type = widget_type::wt_9;
}
window->widgets[widx::s_bend_dual_track_left].type = widget_type::none;
window->widgets[widx::s_bend_dual_track_right].type = widget_type::none;
if (trackObj->track_pieces & track_piece_flags::one_sided)
{
window->widgets[widx::s_bend_dual_track_left].type = widget_type::wt_9;
window->widgets[widx::s_bend_dual_track_right].type = widget_type::wt_9;
window->widgets[widx::s_bend_dual_track_left].image = image_ids::construction_s_bend_dual_track_left;
window->widgets[widx::s_bend_dual_track_right].image = image_ids::construction_s_bend_dual_track_right;
window->widgets[widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_s_bend_left_dual_track;
window->widgets[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[widx::s_bend_dual_track_left].image = image_ids::construction_right_turnaround;
window->widgets[widx::s_bend_dual_track_right].image = image_ids::construction_s_bend_to_single_track_left;
window->widgets[widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_turnaround;
window->widgets[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[widx::s_bend_dual_track_left].image = image_ids::construction_s_bend_to_single_track_right;
window->widgets[widx::s_bend_dual_track_right].image = image_ids::construction_left_turnaround;
window->widgets[widx::s_bend_dual_track_left].tooltip = string_ids::tooltip_s_bend_to_single_track;
window->widgets[widx::s_bend_dual_track_right].tooltip = string_ids::tooltip_turnaround;
_byte_522091 = 16;
_byte_522092 = 20;
}
}
}
window->widgets[widx::steep_slope_down].type = widget_type::none;
window->widgets[widx::slope_down].type = widget_type::none;
window->widgets[widx::slope_up].type = widget_type::none;
window->widgets[widx::steep_slope_up].type = widget_type::none;
if (trackObj->track_pieces & track_piece_flags::slope)
{
window->widgets[widx::slope_down].type = widget_type::wt_9;
window->widgets[widx::slope_up].type = widget_type::wt_9;
}
if (trackObj->track_pieces & track_piece_flags::steep_slope)
{
window->widgets[widx::steep_slope_down].type = widget_type::wt_9;
window->widgets[widx::steep_slope_up].type = widget_type::wt_9;
}
window->widgets[widx::bridge].type = widget_type::wt_18;
window->widgets[widx::bridge_dropdown].type = widget_type::wt_11;
if (_lastSelectedBridge == 0xFF || (_constructionHover != 1 && !(_byte_1136076 & 1)))
{
window->widgets[widx::bridge].type = widget_type::none;
window->widgets[widx::bridge_dropdown].type = widget_type::none;
}
auto activatedWidgets = window->activated_widgets;
activatedWidgets &= ~(construction::allTrack);
window->widgets[widx::construct].type = widget_type::none;
window->widgets[widx::remove].type = widget_type::wt_9;
window->widgets[widx::rotate_90].type = widget_type::none;
if (_constructionHover == 1)
{
window->widgets[widx::construct].type = widget_type::wt_5;
window->widgets[widx::construct].tooltip = string_ids::tooltip_start_construction;
window->widgets[widx::remove].type = widget_type::none;
window->widgets[widx::rotate_90].type = widget_type::wt_9;
window->widgets[widx::rotate_90].image = image_ids::rotate_object;
window->widgets[widx::rotate_90].tooltip = string_ids::rotate_90;
}
else if (_constructionHover == 0)
{
window->widgets[widx::construct].type = widget_type::wt_3;
window->widgets[widx::construct].tooltip = string_ids::tooltip_construct;
window->widgets[widx::rotate_90].type = widget_type::wt_9;
window->widgets[widx::rotate_90].image = image_ids::construction_new_position;
window->widgets[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 = widx::level;
switch (_lastSelectedTrackGradient)
{
case TrackGradient::level:
trackGradient = widx::level;
break;
case TrackGradient::slope_up:
trackGradient = widx::slope_up;
break;
case TrackGradient::slope_down:
trackGradient = widx::slope_down;
break;
case TrackGradient::steep_slope_up:
trackGradient = widx::steep_slope_up;
break;
case TrackGradient::steep_slope_down:
trackGradient = 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 (window->current_tab == common::widx::tab_construction - common::widx::tab_construction)
{
if (_trackType & (1 << 7))
{
activateSelectedRoadWidgets(window);
}
else
{
activateSelectedTrackWidgets(window);
}
}
}
// 0x004A0832
static std::optional<TrackPieceId> getRoadPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation)
{
if (trackPiece == 0xFF)
return std::nullopt;
uint8_t id = 0;
switch (trackPiece)
{
case TrackPiece::straight: // 0x004A0856
{
if (rotation >= 4)
return std::nullopt;
switch (gradient)
{
default:
return std::nullopt;
case TrackGradient::level:
id = 0;
break;
case TrackGradient::slope_up:
id = 5;
break;
case TrackGradient::slope_down:
id = 6;
break;
case TrackGradient::steep_slope_up:
id = 7;
break;
case TrackGradient::steep_slope_down:
id = 8;
break;
}
break;
}
case TrackPiece::left_hand_curve_very_small: // 0x004A08A5
{
if (gradient != TrackGradient::level)
return std::nullopt;
if (rotation >= 4)
return std::nullopt;
id = 1;
break;
}
case TrackPiece::right_hand_curve_very_small: // 0x004A08CD
{
if (gradient != TrackGradient::level)
return std::nullopt;
if (rotation >= 4)
return std::nullopt;
id = 2;
break;
}
case TrackPiece::left_hand_curve_small: // 0x004A08ED
{
if (rotation >= 4)
return std::nullopt;
id = 3;
if (gradient != TrackGradient::level)
return std::nullopt;
break;
}
case TrackPiece::right_hand_curve_small: // 0x004A08FB
{
if (rotation >= 4)
return std::nullopt;
id = 4;
if (gradient != TrackGradient::level)
return std::nullopt;
break;
}
case TrackPiece::left_hand_curve: // 0x004A095F
case TrackPiece::right_hand_curve:
case TrackPiece::left_hand_curve_large:
case TrackPiece::right_hand_curve_large:
case TrackPiece::s_bend_left:
case TrackPiece::s_bend_right:
case TrackPiece::s_bend_to_dual_track:
case TrackPiece::s_bend_to_single_track:
{
return std::nullopt;
}
case TrackPiece::turnaround: // 0x004A0909
{
if (gradient != 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<TrackPieceId> getTrackPieceId(uint8_t trackPiece, uint8_t gradient, uint8_t rotation)
{
if (trackPiece == 0xFF)
return std::nullopt;
uint8_t id = 0;
switch (trackPiece)
{
case TrackPiece::straight: // loc_4A051C
{
if (rotation >= 12)
{
id = 1;
if (gradient != TrackGradient::level)
return std::nullopt;
}
else
{
if (rotation >= 8)
{
switch (gradient)
{
default:
return std::nullopt;
case TrackGradient::level:
id = 27;
break;
case TrackGradient::steep_slope_up:
id = 35;
break;
case TrackGradient::steep_slope_down:
id = 37;
break;
}
}
else if (rotation >= 4)
{
switch (gradient)
{
default:
return std::nullopt;
case TrackGradient::level:
id = 26;
break;
case TrackGradient::steep_slope_up:
id = 34;
break;
case TrackGradient::steep_slope_down:
id = 36;
break;
}
}
else
{
switch (gradient)
{
default:
return std::nullopt;
case TrackGradient::level:
id = 0;
break;
case TrackGradient::slope_up:
id = 14;
break;
case TrackGradient::slope_down:
id = 15;
break;
case TrackGradient::steep_slope_up:
id = 16;
break;
case TrackGradient::steep_slope_down:
id = 17;
break;
}
}
}
break;
}
case TrackPiece::left_hand_curve_very_small: // loc_4A05C3
{
if (gradient != 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 TrackPiece::right_hand_curve_very_small: // loc_4A05F4
{
if (gradient != 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 TrackPiece::left_hand_curve_small: // loc_4A0625
{
if (rotation >= 4)
return std::nullopt;
switch (gradient)
{
default:
return std::nullopt;
case TrackGradient::level:
id = 4;
break;
case TrackGradient::slope_up:
id = 18;
break;
case TrackGradient::slope_down:
id = 20;
break;
case TrackGradient::steep_slope_up:
id = 22;
break;
case TrackGradient::steep_slope_down:
id = 24;
break;
}
break;
}
case TrackPiece::right_hand_curve_small: // loc_4A066A
{
if (rotation >= 4)
return std::nullopt;
switch (gradient)
{
default:
return std::nullopt;
case TrackGradient::level:
id = 5;
break;
case TrackGradient::slope_up:
id = 19;
break;
case TrackGradient::slope_down:
id = 21;
break;
case TrackGradient::steep_slope_up:
id = 23;
break;
case TrackGradient::steep_slope_down:
id = 25;
break;
}
break;
}
case TrackPiece::left_hand_curve: // loc_4A06AF
{
if (rotation >= 4)
return std::nullopt;
id = 6;
if (gradient != TrackGradient::level)
return std::nullopt;
break;
}
case TrackPiece::right_hand_curve: // loc_4A06C8
{
if (rotation >= 4)
return std::nullopt;
id = 7;
if (gradient != TrackGradient::level)
return std::nullopt;
break;
}
case TrackPiece::left_hand_curve_large: // loc_4A06E1
{
if (gradient != TrackGradient::level)
return std::nullopt;
id = 10;
if (rotation >= 12)
break;
if (rotation >= 4)
return std::nullopt;
id = 8;
break;
}
case TrackPiece::right_hand_curve_large: // loc_4A0705
{
if (gradient != TrackGradient::level)
return std::nullopt;
id = 11;
if (rotation >= 12)
break;
if (rotation >= 4)
return std::nullopt;
id = 9;
break;
}
case TrackPiece::s_bend_left: // loc_4A0729
{
if (gradient != 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 TrackPiece::s_bend_right: // loc_4A0756
{
if (gradient != TrackGradient::level)
return std::nullopt;
if (rotation >= 8)
return std::nullopt;
id = 32;
if (rotation >= 4)
break;
id = 13;
break;
}
case TrackPiece::s_bend_to_dual_track: // loc_4A077C
{
if (gradient != TrackGradient::level)
return std::nullopt;
if (rotation >= 8)
return std::nullopt;
id = 40;
if (rotation >= 4)
break;
id = 38;
break;
}
case TrackPiece::s_bend_to_single_track: // loc_4A07A2
{
if (gradient != 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 TrackPiece::turnaround: // loc_4A07C0
{
if (gradient != 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 };
}
// 0x0049DAA5
static void onResize(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 TrackPiece::straight:
case TrackPiece::left_hand_curve:
case TrackPiece::right_hand_curve:
case TrackPiece::left_hand_curve_large:
case TrackPiece::right_hand_curve_large:
case TrackPiece::s_bend_left:
case TrackPiece::s_bend_right:
case TrackPiece::s_bend_to_dual_track:
case TrackPiece::s_bend_to_single_track:
{
disableUnusedRoadPieces(self, disabledWidgets);
break;
}
case TrackPiece::left_hand_curve_very_small:
case TrackPiece::right_hand_curve_very_small:
case TrackPiece::left_hand_curve_small:
case TrackPiece::right_hand_curve_small:
case 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<track_object>(trackType);
if (_lastSelectedTrackPiece == 0xFF)
{
disableUnusedTrackPieces(self, *trackObj, disabledWidgets);
return;
}
switch (_lastSelectedTrackPiece)
{
case TrackPiece::straight:
disableUnusedTrackPieces(self, *trackObj, disabledWidgets);
break;
case TrackPiece::left_hand_curve_very_small:
case TrackPiece::right_hand_curve_very_small:
case TrackPiece::left_hand_curve:
case TrackPiece::right_hand_curve:
case TrackPiece::left_hand_curve_large:
case TrackPiece::right_hand_curve_large:
case TrackPiece::s_bend_left:
case TrackPiece::s_bend_right:
case TrackPiece::s_bend_to_dual_track:
case TrackPiece::s_bend_to_single_track:
case 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 TrackPiece::left_hand_curve_small:
case 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;
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::setHighlightedItem(i);
auto bridgeObj = objectmgr::get<bridge_object>(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<uint16_t>(0);
}
else
{
args.push(string_ids::velocity);
args.push(bridgeObj->max_speed);
}
args.push<uint16_t>(bridgeObj->max_height);
dropdown::add(i, string_ids::dropdown_bridge_stats, args);
}
}
// 0x0049D42F
static void onMouseDown(window* self, widget_index widgetIndex)
{
switch (widgetIndex)
{
case widx::left_hand_curve:
changeTrackPiece(TrackPiece::left_hand_curve, false);
break;
case widx::right_hand_curve:
changeTrackPiece(TrackPiece::right_hand_curve, false);
break;
case widx::left_hand_curve_small:
changeTrackPiece(TrackPiece::left_hand_curve_small, false);
break;
case widx::right_hand_curve_small:
changeTrackPiece(TrackPiece::right_hand_curve_small, false);
break;
case widx::left_hand_curve_very_small:
changeTrackPiece(TrackPiece::left_hand_curve_very_small, false);
break;
case widx::right_hand_curve_very_small:
changeTrackPiece(TrackPiece::right_hand_curve_very_small, false);
break;
case widx::left_hand_curve_large:
changeTrackPiece(TrackPiece::left_hand_curve_large, false);
break;
case widx::right_hand_curve_large:
changeTrackPiece(TrackPiece::right_hand_curve_large, false);
break;
case widx::straight:
changeTrackPiece(TrackPiece::straight, false);
break;
case widx::s_bend_left:
changeTrackPiece(TrackPiece::s_bend_left, false);
break;
case widx::s_bend_right:
changeTrackPiece(TrackPiece::s_bend_right, false);
break;
case widx::s_bend_dual_track_left:
{
_byte_113603A = 0xFF;
common::sub_49FEC7();
_lastSelectedTrackPiece = 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 = 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 = TrackPiece::s_bend_to_single_track;
}
}
activateSelectedConstructionWidgets();
break;
}
case widx::s_bend_dual_track_right:
{
_byte_113603A = 0xFF;
common::sub_49FEC7();
_lastSelectedTrackPiece = 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 = TrackPiece::turnaround;
if (self->widgets[widx::s_bend_dual_track_left].image != image_ids::construction_left_turnaround)
_lastSelectedTrackPiece = TrackPiece::s_bend_to_dual_track;
}
activateSelectedConstructionWidgets();
break;
}
case widx::steep_slope_down:
changeTrackPiece(TrackGradient::steep_slope_down, true);
break;
case widx::slope_down:
changeTrackPiece(TrackGradient::slope_down, true);
break;
case widx::level:
changeTrackPiece(TrackGradient::level, true);
break;
case widx::slope_up:
changeTrackPiece(TrackGradient::slope_up, true);
break;
case widx::steep_slope_up:
changeTrackPiece(TrackGradient::steep_slope_up, true);
break;
case widx::bridge_dropdown:
{
bridgeDropdown(self);
break;
}
case widx::construct:
{
if (*_clickRepeatTicks >= 40)
constructTrack(self, widgetIndex);
break;
}
case widx::remove:
{
if (*_clickRepeatTicks >= 40)
removeTrack(self, widgetIndex);
break;
}
}
}
// 0x0049D4EA
static void onDropdown(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;
activateSelectedConstructionWidgets();
}
}
}
static void sub_49FD66()
{
registers regs;
call(0x0049FD66, regs);
}
// 0x0049DCA2
static void onUpdate(window* self)
{
self->frame_no++;
self->callPrepareDraw();
WindowManager::invalidate(WindowType::construction, self->number);
if (_constructionHover == 1)
{
if (!input::isToolActive(WindowType::construction, self->number) || _toolWidgetIndex != widx::construct)
WindowManager::close(self);
}
if (_constructionHover == 0)
{
if (input::isToolActive(WindowType::construction, self->number))
input::toolCancel();
}
sub_49FD66();
}
// 0x004A2395
static std::optional<int16_t> 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->baseZ() * 4;
if (surfaceTile->slopeCorners())
{
tileHeight += 16;
}
if (surfaceTile->isSlopeDoubleHeight())
{
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<std::pair<int16_t, int16_t>> 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<std::pair<int16_t, int16_t>> 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
// regs.ax = x;
// regs.bx = y;
// returns
// regs.ax = 0x8000 - probably in case of failure
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;
activateSelectedConstructionWidgets();
auto window = WindowManager::find(WindowType::construction);
if (window == nullptr)
return;
_byte_508F09 = _byte_508F09 | (1 << 0);
onMouseUp(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::hasKeyModifier(input::key_modifier::shift))
{
continue;
}
else
{
height += 32;
continue;
}
}
}
}
}
else
{
_byte_113607E = 1;
WindowManager::close(WindowType::error, 0);
return;
}
onMouseUp(window, widx::rotate_90);
audio::playSound(audio::sound_id::error, int32_t(input::getMouseLocation().x));
return;
}
}
// 0x0049DC8C
static void onToolUpdate(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 onToolDown(window& self, const widget_index widgetIndex, const int16_t x, const int16_t y)
{
if (widgetIndex != widx::construct)
return;
if (_trackType & (1 << 7))
{
mapInvalidateMapSelectionTiles();
common::sub_49FEC7();
auto road = 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::toolCancel();
auto maxRetries = 0;
if (input::hasKeyModifier(input::key_modifier::shift) || _byte_113605D != 1)
{
const auto& roadPiece = openloco::map::TrackData::getRoadPiece(_byte_1136065);
auto maxRoadPieceHeight = 0;
for (const auto& roadPart : roadPiece)
{
if (maxRoadPieceHeight > roadPart.z)
maxRoadPieceHeight = roadPart.z;
}
roadHeight -= maxRoadPieceHeight;
roadHeight -= 16;
maxRetries = 2;
if (input::hasKeyModifier(input::key_modifier::shift))
{
maxRetries = 0x80000008;
roadHeight -= 16;
}
}
else
{
maxRetries = 1;
roadHeight = _word_1135FFE;
}
constructionLoop(mapPos, maxRetries, roadHeight);
}
else
{
mapInvalidateMapSelectionTiles();
common::sub_49FEC7();
auto track = 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::toolCancel();
auto maxRetries = 0;
if (input::hasKeyModifier(input::key_modifier::shift) || _byte_113605D != 1)
{
const auto& trackPiece = openloco::map::TrackData::getTrackPiece(_byte_1136065);
auto maxTrackPieceHeight = 0;
for (const auto& trackPart : trackPiece)
{
if (maxTrackPieceHeight > trackPart.z)
maxTrackPieceHeight = trackPart.z;
}
trackHeight -= maxTrackPieceHeight;
trackHeight -= 16;
maxRetries = 2;
if (input::hasKeyModifier(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 prepareDraw(window* self)
{
common::prepareDraw(self);
auto args = FormatArguments();
if (_trackType & (1 << 7))
{
auto roadObj = objectmgr::get<road_object>(_trackType & ~(1 << 7));
args.push(roadObj->name);
}
else
{
auto trackObj = objectmgr::get<track_object>(_trackType);
args.push(trackObj->name);
}
if (_lastSelectedBridge != 0xFF)
{
auto bridgeObj = objectmgr::get<bridge_object>(_lastSelectedBridge);
if (bridgeObj != nullptr)
{
args.push(bridgeObj->name);
if (bridgeObj->max_speed == 0xFFFF)
{
args.push(string_ids::unlimited_speed);
args.push<uint16_t>(0);
}
else
{
args.push(string_ids::velocity);
args.push(bridgeObj->max_speed);
}
args.push<uint16_t>(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::drawStringCentred(*dpi, x, y, colour::black, string_ids::build_this);
y += 11;
if (_trackCost != 0x80000000)
{
if (_trackCost != 0)
{
auto args = FormatArguments();
args.push<uint32_t>(_trackCost);
gfx::drawStringCentred(*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<bridge_object>(_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::drawImage(dpi, x, y, imageId);
}
}
}
if (self->widgets[widx::construct].type == widget_type::none)
return;
if (_trackType & (1 << 7))
{
auto road = 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::clipDrawpixelinfo(&clipped, dpi, x, y, width, height))
{
const auto& roadPiece = openloco::map::TrackData::getRoadPiece(_lastSelectedTrackPieceId);
const auto& lastRoadPart = roadPiece.back();
map_pos3 pos3D = { lastRoadPart.x, lastRoadPart.y, lastRoadPart.z };
if (lastRoadPart.flags & openloco::map::TrackData::PreviewTrackFlags::unused)
{
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 = coordinate3dTo2d(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 = 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::clipDrawpixelinfo(&clipped, dpi, x, y, width, height))
{
const auto& trackPiece = openloco::map::TrackData::getTrackPiece(_lastSelectedTrackPieceId);
const auto& lastTrackPart = trackPiece.back();
map_pos3 pos3D = { lastTrackPart.x, lastTrackPart.y, lastTrackPart.z };
if (lastTrackPart.flags & openloco::map::TrackData::PreviewTrackFlags::unused)
{
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 = coordinate3dTo2d(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->callOnMouseUp(widx::rotate_90);
}
}
void initEvents()
{
events.on_close = common::onClose;
events.on_mouse_up = onMouseUp;
events.on_resize = onResize;
events.on_mouse_down = onMouseDown;
events.on_dropdown = onDropdown;
events.on_update = onUpdate;
events.on_tool_update = onToolUpdate;
events.on_tool_down = onToolDown;
events.cursor = cursor;
events.prepare_draw = prepareDraw;
events.draw = draw;
}
}