Implement Signal toolUpdate/Down (#1093)

* Start implementing toolUpdate signal

* Implement getSide and finish signal tool update

* Implement signal toolDown

* Actually name the game command

* Use correct global address
This commit is contained in:
Duncan 2021-08-10 05:57:25 +01:00 committed by GitHub
parent 99049e5fab
commit 80952a1c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 229 additions and 20 deletions

View File

@ -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 },

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -47,12 +47,19 @@ namespace OpenLoco::Ui::Windows::Construction
static loco_global<uint16_t, 0x01135FBA> _word_1135FBA;
static loco_global<uint16_t, 0x01135FBC> _word_1135FBC;
static loco_global<uint16_t, 0x01135FBE> _word_1135FBE;
static loco_global<Map::Pos3, 0x01135FC6> _1135FC6;
static loco_global<uint16_t, 0x01135FCC> _1135FCC;
static loco_global<Map::Pos3, 0x01135FCE> _1135FCE;
static loco_global<uint16_t, 0x01135FD4> _word_1135FD4;
static loco_global<uint16_t, 0x01135FD6> _word_1135FD6;
static loco_global<uint16_t, 0x01135FD8> _word_1135FD8;
static loco_global<uint16_t, 0x01135FE4> _lastSelectedMods;
static loco_global<Map::Pos3, 0x01135FF8> _modGhostPos;
static loco_global<uint16_t, 0x01135FFE> _word_1135FFE;
static loco_global<int16_t, 0x01136000> _word_1136000;
static loco_global<uint16_t, 0x01136002> _signalGhostSides;
static loco_global<Map::Pos3, 0x01136004> _signalGhostPos;
static loco_global<uint16_t, 0x0113600A> _signalGhostTrackObjId;
static loco_global<uint8_t, 0x01136010> _modGhostTrackObjId;
static loco_global<uint8_t[17], 0x0113601D> _signalList;
static loco_global<uint8_t, 0x0113602E> _lastSelectedSignal;
@ -62,6 +69,9 @@ namespace OpenLoco::Ui::Windows::Construction
static loco_global<uint8_t, 0x0113603A> _byte_113603A;
static loco_global<uint8_t[17], 0x0113603B> _stationList;
static loco_global<uint8_t, 0x0113604C> _lastSelectedStationType;
static loco_global<uint8_t, 0x0113604D> _signalGhostRotation;
static loco_global<uint8_t, 0x0113604E> _signalGhostTrackId;
static loco_global<uint8_t, 0x0113604F> _signalGhostTileIndex;
static loco_global<uint8_t[4], 0x01136054> _modList;
static loco_global<uint8_t, 0x01136058> _modGhostRotation;
static loco_global<uint8_t, 0x01136059> _modGhostTrackId;

View File

@ -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<uint8_t[16], 0x00503CAC> _503CAC;
static loco_global<Map::Pos2[16], 0x00503C6C> _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<GameCommands::SignalPlacementArgs> getSignalPlacementArgsFromCursor(const int16_t x, const int16_t y, const bool isBothDirectons)
{
static loco_global<Ui::Point, 0x0113600C> _113600C;
static loco_global<Viewport*, 0x01135F52> _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<Map::TileElement*>(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