diff --git a/src/OpenLoco/GameCommands/GameCommands.cpp b/src/OpenLoco/GameCommands/GameCommands.cpp index 6dea2670..be9608e9 100644 --- a/src/OpenLoco/GameCommands/GameCommands.cpp +++ b/src/OpenLoco/GameCommands/GameCommands.cpp @@ -59,8 +59,8 @@ namespace OpenLoco::GameCommands { GameCommand::vehicleRename, Vehicles::rename, 0x004B6572, false }, { GameCommand::changeStationName, renameStation, 0x00490756, false }, { GameCommand::vehicleLocalExpress, nullptr, 0x004B694B, true }, - { GameCommand::gc_unk_13, nullptr, 0x00488BDB, true }, - { GameCommand::gc_unk_14, nullptr, 0x004891E4, true }, + { GameCommand::createSignal, nullptr, 0x00488BDB, true }, + { GameCommand::removeSignal, nullptr, 0x004891E4, true }, { GameCommand::gc_unk_15, nullptr, 0x0048BB20, true }, { GameCommand::removeTrackStation, nullptr, 0x0048C402, true }, { GameCommand::createTrackMod, nullptr, 0x004A6479, true }, diff --git a/src/OpenLoco/GameCommands/GameCommands.h b/src/OpenLoco/GameCommands/GameCommands.h index 841a2a60..24418910 100644 --- a/src/OpenLoco/GameCommands/GameCommands.h +++ b/src/OpenLoco/GameCommands/GameCommands.h @@ -46,8 +46,8 @@ namespace OpenLoco::GameCommands vehicleRename = 10, changeStationName = 11, vehicleLocalExpress = 12, - gc_unk_13 = 13, - gc_unk_14 = 14, + createSignal = 13, + removeSignal = 14, gc_unk_15 = 15, removeTrackStation = 16, createTrackMod = 17, @@ -281,6 +281,49 @@ namespace OpenLoco::GameCommands doCommand(GameCommand::vehicleLocalExpress, regs); } + struct SignalPlacementArgs + { + SignalPlacementArgs() = default; + explicit SignalPlacementArgs(const registers& regs) + : pos(regs.ax, regs.cx, regs.di) + , rotation(regs.bh & 0x3) + , trackId(regs.dl & 0x3F) + , index(regs.dh & 0x3) + , type((regs.edi >> 16) & 0xFF) + , trackObjType(regs.ebp & 0xFF) + , sides((regs.edi >> 16) & 0xC000) + { + } + + Map::Pos3 pos; + uint8_t rotation; + uint8_t trackId; + uint8_t index; + uint8_t type; + uint8_t trackObjType; + uint16_t sides; + + explicit operator registers() const + { + registers regs; + regs.ax = pos.x; + regs.cx = pos.y; + regs.bh = rotation; + regs.dl = trackId; + regs.dh = index; + regs.edi = pos.z | (type << 16) | ((sides & 0xC000) << 16); + regs.ebp = trackObjType; + return regs; + } + }; + + inline uint32_t do_13(uint8_t flags, const SignalPlacementArgs& args) + { + registers regs = registers(args); + regs.bl = flags; + return doCommand(GameCommand::createSignal, regs); + } + struct SignalRemovalArgs { SignalRemovalArgs() = default; @@ -319,7 +362,7 @@ namespace OpenLoco::GameCommands { registers regs = registers(args); regs.bl = flags; - return doCommand(GameCommand::gc_unk_14, regs) != FAILURE; + return doCommand(GameCommand::removeSignal, regs) != FAILURE; } struct TrackStationRemovalArgs diff --git a/src/OpenLoco/Localisation/StringIds.h b/src/OpenLoco/Localisation/StringIds.h index a2035428..befa5003 100644 --- a/src/OpenLoco/Localisation/StringIds.h +++ b/src/OpenLoco/Localisation/StringIds.h @@ -138,7 +138,8 @@ namespace OpenLoco::StringIds constexpr string_id tooltip_steep_slope_up = 136; constexpr string_id build_this = 137; constexpr string_id build_cost = 138; - + constexpr string_id cant_build_signal_here = 139; + constexpr string_id cant_build_signals_here = 140; constexpr string_id cant_remove_signal = 141; constexpr string_id menu_underground_view = 145; diff --git a/src/OpenLoco/Viewport.hpp b/src/OpenLoco/Viewport.hpp index 134c5c72..ad0bdb67 100644 --- a/src/OpenLoco/Viewport.hpp +++ b/src/OpenLoco/Viewport.hpp @@ -117,7 +117,7 @@ namespace OpenLoco::Ui /** * Maps a 2D viewport position to a UI (screen) position. */ - Point mapToUi(const viewport_pos& vpos) + Point mapToUi(const viewport_pos& vpos) const { auto uiX = x + ((vpos.x - view_x) >> zoom); auto uiY = y + ((vpos.y - view_y) >> zoom); @@ -127,7 +127,7 @@ namespace OpenLoco::Ui /** * Maps a UI (screen) position to a 2D viewport position. */ - viewport_pos uiToMap(const Point& pos) + viewport_pos uiToMap(const Point& pos) const { int16_t viewport_x = ((pos.x - x) << zoom) + view_x; int16_t viewport_y = ((pos.y - y) << zoom) + view_y; diff --git a/src/OpenLoco/Windows/Construction/Construction.h b/src/OpenLoco/Windows/Construction/Construction.h index 6611ce5d..709c0841 100644 --- a/src/OpenLoco/Windows/Construction/Construction.h +++ b/src/OpenLoco/Windows/Construction/Construction.h @@ -47,12 +47,19 @@ namespace OpenLoco::Ui::Windows::Construction static loco_global _word_1135FBA; static loco_global _word_1135FBC; static loco_global _word_1135FBE; + static loco_global _1135FC6; + static loco_global _1135FCC; + static loco_global _1135FCE; + static loco_global _word_1135FD4; static loco_global _word_1135FD6; static loco_global _word_1135FD8; static loco_global _lastSelectedMods; static loco_global _modGhostPos; static loco_global _word_1135FFE; static loco_global _word_1136000; + static loco_global _signalGhostSides; + static loco_global _signalGhostPos; + static loco_global _signalGhostTrackObjId; static loco_global _modGhostTrackObjId; static loco_global _signalList; static loco_global _lastSelectedSignal; @@ -62,6 +69,9 @@ namespace OpenLoco::Ui::Windows::Construction static loco_global _byte_113603A; static loco_global _stationList; static loco_global _lastSelectedStationType; + static loco_global _signalGhostRotation; + static loco_global _signalGhostTrackId; + static loco_global _signalGhostTileIndex; static loco_global _modList; static loco_global _modGhostRotation; static loco_global _modGhostTrackId; diff --git a/src/OpenLoco/Windows/Construction/SignalTab.cpp b/src/OpenLoco/Windows/Construction/SignalTab.cpp index b259a2df..ffd7359e 100644 --- a/src/OpenLoco/Windows/Construction/SignalTab.cpp +++ b/src/OpenLoco/Windows/Construction/SignalTab.cpp @@ -1,3 +1,5 @@ +#include "../../Audio/Audio.h" +#include "../../GameCommands/GameCommands.h" #include "../../Graphics/ImageIds.h" #include "../../Input.h" #include "../../Localisation/FormatArguments.hpp" @@ -5,6 +7,7 @@ #include "../../Objects/ObjectManager.h" #include "../../Objects/TrackObject.h" #include "../../Objects/TrainSignalObject.h" +#include "../../TrackData.h" #include "../../Ui/Dropdown.h" #include "../../Widget.h" #include "Construction.h" @@ -114,26 +117,178 @@ namespace OpenLoco::Ui::Windows::Construction::Signal Common::onUpdate(self, (1 << 2)); } + // Reverse direction map? + static loco_global _503CAC; + static loco_global _503C6C; + + // 0x004A417A + // false for left, true for right + static bool getSide(const Map::Pos3& loc, const Point& mousePos, const TrackElement& elTrack, const Viewport& viewport) + { + // Get coordinates of first tile of track piece under the mouse + const auto& piece = TrackData::getTrackPiece(elTrack.trackId())[elTrack.sequenceIndex()]; + const auto rotPos = Math::Vector::rotate(Map::Pos2(piece.x, piece.y), elTrack.unkDirection()); + const auto firstTile = loc - Map::Pos3(rotPos.x, rotPos.y, piece.z); + + // Get coordinates of the next tile after the end of the track piece + const auto trackAndDirection = (elTrack.trackId() << 3) | elTrack.unkDirection(); + const auto& trackSize = TrackData::getUnkTrack(trackAndDirection); + const auto nextTile = firstTile + trackSize.pos; + _1135FC6 = nextTile; + _1135FCC = trackSize.rotationEnd; + + // Get coordinates of the previous tile before the start of the track piece + const auto unk = _503CAC[trackSize.rotationBegin]; + auto previousTile = firstTile; + _word_1135FD4 = unk; + if (unk < 12) + { + previousTile += _503C6C[unk]; + } + _1135FCE = previousTile; + + // Side is goverened by distance mouse is to either next or previous track coordinate + const auto vpPosNext = gameToScreen(nextTile + Map::Pos3(16, 16, 0), viewport.getRotation()); + const auto uiPosNext = viewport.mapToUi(vpPosNext); + const auto distanceToNext = Math::Vector::manhattanDistance(uiPosNext, mousePos); + + const auto vpPosPrevious = gameToScreen(previousTile + Map::Pos3(16, 16, 0), viewport.getRotation()); + const auto uiPosPrevious = viewport.mapToUi(vpPosPrevious); + const auto distanceToPrevious = Math::Vector::manhattanDistance(uiPosPrevious, mousePos); + + return distanceToNext <= distanceToPrevious; + } + + static std::optional getSignalPlacementArgsFromCursor(const int16_t x, const int16_t y, const bool isBothDirectons) + { + static loco_global _113600C; + static loco_global _1135F52; + + _113600C = { x, y }; + + auto [interaction, viewport] = ViewportInteraction::getMapCoordinatesFromPos(x, y, ~(ViewportInteraction::InteractionItemFlags::track)); + _1135F52 = viewport; + + if (interaction.type != ViewportInteraction::InteractionItem::track) + { + return std::nullopt; + } + + auto* elTrack = reinterpret_cast(interaction.object)->asTrack(); + if (elTrack == nullptr) + { + return std::nullopt; + } + + GameCommands::SignalPlacementArgs args; + args.type = _lastSelectedSignal; + args.pos = Map::Pos3(interaction.pos.x, interaction.pos.y, elTrack->baseZ() * 4); + args.rotation = elTrack->unkDirection(); + args.trackId = elTrack->trackId(); + args.index = elTrack->sequenceIndex(); + args.trackObjType = elTrack->trackObjectId(); + if (isBothDirectons) + { + args.sides = 0xC000; + } + else + { + args.sides = getSide(args.pos, { x, y }, *elTrack, *viewport) ? 0x8000 : 0x4000; + } + return { args }; + } + + static uint32_t placeSignalGhost(const GameCommands::SignalPlacementArgs& args) + { + auto res = GameCommands::do_13(GameCommands::Flags::apply | GameCommands::Flags::flag_1 | GameCommands::Flags::flag_3 | GameCommands::Flags::flag_5 | GameCommands::Flags::flag_6, args); + if (res != GameCommands::FAILURE) + { + _byte_522096 = _byte_522096 | (1 << 2); + _signalGhostPos = args.pos; + _signalGhostRotation = args.rotation; + _signalGhostTrackId = args.trackId; + _signalGhostTileIndex = args.index; + _signalGhostSides = args.sides; + _signalGhostTrackObjId = args.trackObjType; + } + return res; + } + // 0x0049E745 static void onToolUpdate(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y) { - registers regs; - regs.esi = X86Pointer(&self); - regs.dx = widgetIndex; - regs.ax = x; - regs.bx = y; - call(0x0049E745, regs); + if (widgetIndex != widx::single_direction && widgetIndex != widx::both_directions) + { + return; + } + + const bool isBothDirections = widgetIndex == widx::both_directions; + + auto placementArgs = getSignalPlacementArgsFromCursor(x, y, isBothDirections); + if (!placementArgs || (placementArgs->trackObjType != _trackType)) + { + removeConstructionGhosts(); + if (_signalCost != 0x80000000) + { + _signalCost = 0x80000000; + self.invalidate(); + } + return; + } + + if (_byte_522096 & (1 << 2)) + { + if (*_signalGhostPos == placementArgs->pos + && _signalGhostRotation == placementArgs->rotation + && _signalGhostTrackId == placementArgs->trackId + && _signalGhostTileIndex == placementArgs->index + && _signalGhostSides == placementArgs->sides + && _signalGhostTrackObjId == placementArgs->trackObjType) + { + return; + } + } + + removeConstructionGhosts(); + + auto cost = placeSignalGhost(*placementArgs); + if (cost != _signalCost) + { + _signalCost = cost; + self.invalidate(); + } } // 0x0049E75A static void onToolDown(Window& self, const WidgetIndex_t widgetIndex, const int16_t x, const int16_t y) { - registers regs; - regs.esi = X86Pointer(&self); - regs.dx = widgetIndex; - regs.ax = x; - regs.bx = y; - call(0x0049E75A, regs); + if (widgetIndex != widx::single_direction && widgetIndex != widx::both_directions) + { + return; + } + + removeConstructionGhosts(); + + const bool isBothDirections = widgetIndex == widx::both_directions; + auto args = getSignalPlacementArgsFromCursor(x, y, isBothDirections); + if (!args) + { + return; + } + + if (args->trackObjType != _trackType) + { + Error::open(StringIds::cant_build_signal_here, StringIds::wrong_type_of_track_road); + return; + } + + GameCommands::setErrorTitle(isBothDirections ? StringIds::cant_build_signals_here : StringIds::cant_build_signal_here); + auto res = GameCommands::do_13(GameCommands::Flags::apply, *args); + if (res == GameCommands::FAILURE) + { + return; + } + Audio::playSound(Audio::SoundId::construct, GameCommands::getPosition()); } // 0x0049E499