Remove track Construction Window (#1110)

* Implement track removal

* Add road equivalent connections

* Implement removeRoad
This commit is contained in:
Duncan 2021-08-17 07:14:50 +01:00 committed by GitHub
parent ef420ec6e6
commit 6e74345b08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 537 additions and 12 deletions

View File

@ -54,7 +54,7 @@ namespace OpenLoco::GameCommands
{ GameCommand::vehicleCreate, Vehicles::create, 0x004AE5E4, true },
{ GameCommand::vehicleSell, nullptr, 0x004AED34, true },
{ GameCommand::createTrack, nullptr, 0x0049BB98, true },
{ GameCommand::gc_unk_8, nullptr, 0x0049C7F2, true },
{ GameCommand::removeTrack, nullptr, 0x0049C7F2, true },
{ GameCommand::changeLoan, nullptr, 0x0046DE88, false },
{ GameCommand::vehicleRename, Vehicles::rename, 0x004B6572, false },
{ GameCommand::changeStationName, renameStation, 0x00490756, false },

View File

@ -41,7 +41,7 @@ namespace OpenLoco::GameCommands
vehicleCreate = 5,
vehicleSell = 6,
createTrack = 7,
gc_unk_8 = 8,
removeTrack = 8,
changeLoan = 9,
vehicleRename = 10,
changeStationName = 11,
@ -276,6 +276,40 @@ namespace OpenLoco::GameCommands
}
};
struct TrackRemovalArgs
{
static constexpr auto command = GameCommand::removeTrack;
TrackRemovalArgs() = default;
explicit TrackRemovalArgs(const registers& regs)
: pos(regs.ax, regs.cx, regs.di)
, rotation(regs.bh & 0x3)
, trackId(regs.dl & 0x3F)
, index(regs.dh)
, trackObjectId(regs.ebp)
{
}
Map::Pos3 pos;
uint8_t rotation;
uint8_t trackId;
uint8_t index;
uint8_t trackObjectId;
explicit operator registers() const
{
registers regs;
regs.eax = pos.x;
regs.cx = pos.y;
regs.di = pos.z;
regs.bh = rotation;
regs.dl = trackId;
regs.dh = index;
regs.ebp = trackObjectId;
return regs;
}
};
// Change loan
inline void do_9(currency32_t newLoan)
{

View File

@ -82,6 +82,7 @@ namespace OpenLoco::Map
bool isGhost() const { return _flags & ElementFlags::ghost; }
bool isFlag5() const { return _flags & ElementFlags::flag_5; }
bool isFlag6() const { return _flags & ElementFlags::flag_6; }
void setFlag6(bool state)
{
_flags &= ~ElementFlags::flag_6;
@ -273,11 +274,12 @@ namespace OpenLoco::Map
bool hasSignal() const { return (_type & 0x40) != 0; }
uint8_t unkDirection() const { return _type & 0x03; }
uint8_t trackId() const { return _4 & 0x3F; } // _4
bool has_4_80() const { return (_4 & 0x80) != 0; }
bool hasBridge() const { return (_4 & 0x80) != 0; }
uint8_t trackObjectId() const { return _5 >> 4; } // _5u
uint8_t sequenceIndex() const { return _5 & 0xF; } // _5l
uint8_t bridge() const { return _6 >> 5; } // _6u
uint8_t owner() const { return _7 & 0xF; } // _7l
bool has_6_10() const { return (_6 & 0x10) != 0; }
uint8_t bridge() const { return _6 >> 5; } // _6u
uint8_t owner() const { return _7 & 0xF; } // _7l
void setOwner(uint8_t newOwner) { _7 = (_7 & 0xF0) | (newOwner & 0xF); }
bool hasMod(uint8_t mod) const { return _7 & (1 << (4 + mod)); } // _7u
uint8_t mods() const { return _7 >> 4; } // _7u
@ -309,7 +311,7 @@ namespace OpenLoco::Map
public:
uint8_t unkDirection() const { return _type & 0x03; }
uint8_t roadId() const { return _4 & 0xF; } // _4l
bool has_4_80() const { return (_4 & 0x80) != 0; }
bool hasBridge() const { return (_4 & 0x80) != 0; }
uint8_t roadObjectId() const { return _5 >> 4; } // _5u
uint8_t sequenceIndex() const { return _5 & 0x3; } // _5l
uint8_t bridge() const { return _6 >> 5; } // _6u

View File

@ -12,6 +12,7 @@ namespace OpenLoco
namespace Flags12
{
constexpr uint8_t unk_01 = 1 << 1;
constexpr uint8_t unk_02 = 1 << 2;
constexpr uint8_t unk_03 = 1 << 3;
constexpr uint8_t isRoad = 1 << 6; // If not set this is tram track
}

View File

@ -278,7 +278,7 @@ namespace OpenLoco::Ui::Windows::Construction
_lastSelectedBridge = lastBridge;
if (copyElement->has_4_80())
if (copyElement->hasBridge())
{
_lastSelectedBridge = copyElement->bridge();
}
@ -360,7 +360,7 @@ namespace OpenLoco::Ui::Windows::Construction
lastBridge = _bridgeList[0];
_lastSelectedBridge = lastBridge;
if (copyElement->has_4_80())
if (copyElement->hasBridge())
{
_lastSelectedBridge = copyElement->bridge();
}

View File

@ -9,6 +9,7 @@
#include "../../Objects/ObjectManager.h"
#include "../../Objects/RoadObject.h"
#include "../../Objects/TrackObject.h"
#include "../../Station.h"
#include "../../TrackData.h"
#include "../../Ui/Dropdown.h"
#include "../../Widget.h"
@ -304,13 +305,500 @@ namespace OpenLoco::Ui::Windows::Construction::Construction
activateSelectedConstructionWidgets();
}
static loco_global<Map::Pos2[16], 0x00503C6C> _503C6C;
struct TrackConnections
{
uint32_t size;
uint16_t data[16];
void push_back(uint16_t value)
{
if (size < (sizeof(data) / sizeof(*data)) - 1)
{
data[size++] = value;
data[size] = 0xFFFF;
}
}
uint16_t pop_back()
{
return data[--size];
}
};
static_assert(sizeof(TrackConnections) == 0x24);
static loco_global<TrackConnections, 0x0113609C> _113609C;
static loco_global<uint8_t, 0x0113607D> _113607D;
static loco_global<uint8_t, 0x0112C2EE> _112C2EE;
static loco_global<uint8_t, 0x0112C2EE> _112C2ED;
static loco_global<uint32_t, 0x00525FC0> _525FC0;
static loco_global<StationId_t, 0x01135FAE> _1135FAE;
static loco_global<StationId_t, 0x01136087> _1136087;
static loco_global<uint8_t[2], 0x0113601A> _113601A;
// 0x00478895
static void getRoadConnections(const Map::Pos3& pos, TrackConnections& data, const CompanyId_t company, const uint8_t roadObjectId, const uint16_t trackAndDirection)
{
const auto nextTrackPos = pos + TrackData::getUnkTrack(trackAndDirection).pos;
_1135FAE = StationId::null; // stationId
uint8_t baseZ = nextTrackPos.z / 4;
uint8_t nextRotation = TrackData::getUnkTrack(trackAndDirection).rotationEnd;
_112C2EE = nextRotation;
const auto tile = Map::TileManager::get(nextTrackPos);
for (const auto& el : tile)
{
auto* elRoad = el.asRoad();
if (elRoad == nullptr)
{
continue;
}
if (!(_525FC0 & (1 << elRoad->roadObjectId())))
{
if (elRoad->owner() != company)
{
continue;
}
}
if (elRoad->roadObjectId() != roadObjectId)
{
if (roadObjectId != 0xFF)
{
continue;
}
if (!(_525FC0 & (1 << roadObjectId)))
{
continue;
}
}
if ((elRoad->mods() & _113601A[0]) != _113601A[0])
{
continue;
}
if (elRoad->isGhost() || elRoad->isFlag5())
{
continue;
}
if (elRoad->sequenceIndex() == 0)
{
auto trackAndDirection2 = (elRoad->roadId() << 3) | elRoad->unkDirection();
if (nextRotation == TrackData::getUnkRoad(trackAndDirection2).rotationBegin)
{
const auto& roadPiece = TrackData::getRoadPiece(elRoad->roadId());
if (baseZ == (elRoad->baseZ() - roadPiece[0].z / 4))
{
if (elRoad->hasBridge())
{
trackAndDirection2 |= elRoad->bridge() << 9;
trackAndDirection2 |= (1 << 12);
}
if (_113601A[1] != elRoad->mods())
{
trackAndDirection2 |= (1 << 13);
}
if (elRoad->hasStationElement())
{
auto* elStation = tile.roadStation(elRoad->roadId(), elRoad->unkDirection(), elRoad->baseZ());
if (elStation == nullptr)
{
continue;
}
if (!elStation->isFlag5() && !elStation->isGhost())
{
_1135FAE = elStation->stationId();
_1136087 = elStation->objectId();
}
}
_112C2ED = elRoad->roadObjectId();
data.push_back(trackAndDirection2);
}
}
}
if (!elRoad->isFlag6())
{
continue;
}
auto trackAndDirection2 = (elRoad->roadId() << 3) | (1 << 2) | elRoad->unkDirection();
if (nextRotation != TrackData::getUnkRoad(trackAndDirection2).rotationBegin)
{
continue;
}
const auto& roadPiece = TrackData::getRoadPiece(elRoad->roadId());
if (baseZ != (elRoad->baseZ() - (TrackData::getUnkRoad(trackAndDirection2).pos.z + roadPiece[elRoad->sequenceIndex()].z) / 4))
{
continue;
}
if (elRoad->hasBridge())
{
trackAndDirection2 |= elRoad->bridge() << 9;
trackAndDirection2 |= (1 << 12);
}
if (_113601A[1] != elRoad->mods())
{
trackAndDirection2 |= (1 << 13);
}
if (elRoad->hasStationElement())
{
auto* elStation = tile.roadStation(elRoad->roadId(), elRoad->unkDirection(), elRoad->baseZ());
if (elStation == nullptr)
{
continue;
}
if (!elStation->isFlag5() && !elStation->isGhost())
{
_1135FAE = elStation->stationId();
_1136087 = elStation->objectId();
}
}
data.push_back(trackAndDirection2);
}
}
// 0x004A2604
static void getTrackConnections(const Map::Pos3& pos, TrackConnections& data, const CompanyId_t company, const uint8_t trackObjectId, const uint16_t trackAndDirection)
{
const auto nextTrackPos = pos + TrackData::getUnkTrack(trackAndDirection).pos;
_1135FAE = StationId::null; // stationId
_113607D = 0;
uint8_t baseZ = nextTrackPos.z / 4;
uint8_t nextRotation = TrackData::getUnkTrack(trackAndDirection).rotationEnd;
const auto tile = Map::TileManager::get(nextTrackPos);
for (const auto& el : tile)
{
auto* elTrack = el.asTrack();
if (elTrack == nullptr)
{
continue;
}
if (elTrack->owner() != company)
{
continue;
}
if (elTrack->trackObjectId() != trackObjectId)
{
continue;
}
if ((elTrack->mods() & _113601A[0]) != _113601A[0])
{
continue;
}
if (elTrack->isGhost() || elTrack->isFlag5())
{
continue;
}
if (elTrack->sequenceIndex() == 0)
{
auto trackAndDirection2 = (elTrack->trackId() << 3) | elTrack->unkDirection();
if (nextRotation == TrackData::getUnkTrack(trackAndDirection2).rotationBegin)
{
const auto& trackPiece = TrackData::getTrackPiece(elTrack->trackId());
if (baseZ == (elTrack->baseZ() - trackPiece[0].z / 4))
{
if (elTrack->hasBridge())
{
trackAndDirection2 |= elTrack->bridge() << 9;
trackAndDirection2 |= (1 << 12);
}
if (_113601A[1] != elTrack->mods())
{
trackAndDirection2 |= (1 << 13);
}
if (elTrack->hasStationElement())
{
auto* elStation = (reinterpret_cast<TileElement*>(elTrack) + 1)->asStation();
if (elStation == nullptr)
{
continue;
}
if (!elStation->isFlag5() && !elStation->isGhost())
{
_1135FAE = elStation->stationId();
}
}
if (elTrack->has_6_10())
{
_113607D = 1;
}
if (elTrack->hasSignal())
{
auto* elSignal = (reinterpret_cast<TileElement*>(elTrack) + 1)->asSignal();
if (elSignal == nullptr)
{
continue;
}
if (!elSignal->isFlag5() && !elSignal->isGhost())
{
trackAndDirection2 |= (1 << 15);
}
}
data.push_back(trackAndDirection2);
}
}
}
if (!elTrack->isFlag6())
{
continue;
}
auto trackAndDirection2 = (elTrack->trackId() << 3) | (1 << 2) | elTrack->unkDirection();
if (nextRotation != TrackData::getUnkTrack(trackAndDirection2).rotationBegin)
{
continue;
}
const auto previousBaseZ = elTrack->baseZ() - (TrackData::getTrackPiece(elTrack->trackId())[elTrack->sequenceIndex()].z + TrackData::getUnkTrack(trackAndDirection2).pos.z) / 4;
if (previousBaseZ != baseZ)
{
continue;
}
if (elTrack->hasBridge())
{
trackAndDirection2 |= elTrack->bridge() << 9;
trackAndDirection2 |= (1 << 12);
}
if (_113601A[1] != elTrack->mods())
{
trackAndDirection2 |= (1 << 13);
}
if (elTrack->hasStationElement())
{
auto* elStation = (reinterpret_cast<TileElement*>(elTrack) + 1)->asStation();
if (elStation == nullptr)
{
continue;
}
if (!elStation->isFlag5() && !elStation->isGhost())
{
_1135FAE = elStation->stationId();
}
}
if (elTrack->has_6_10())
{
_113607D = 1;
}
if (elTrack->hasSignal())
{
auto* elSignal = (reinterpret_cast<TileElement*>(elTrack) + 1)->asSignal();
if (elSignal == nullptr)
{
continue;
}
if (!elSignal->isFlag5() && !elSignal->isGhost())
{
trackAndDirection2 |= (1 << 15);
}
}
data.push_back(trackAndDirection2);
}
}
// 0x004A012E
static void removeTrack()
{
_trackCost = 0x80000000;
_byte_1136076 = 0;
removeConstructionGhosts();
if (_constructionHover != 0)
{
return;
}
Map::Pos3 loc(_x, _y, _constructionZ);
uint32_t trackAndDirection = 0;
if (_constructionRotation < 4)
{
trackAndDirection = 0;
}
else if (_constructionRotation < 8)
{
trackAndDirection = 26 << 3;
}
else if (_constructionRotation < 12)
{
trackAndDirection = 27 << 3;
}
else
{
trackAndDirection = 1 << 3;
loc += _503C6C[_constructionRotation];
}
trackAndDirection |= (1 << 2) | (_constructionRotation & 0x3);
_113601A[0] = 0;
_113601A[1] = 0;
_113609C->size = 0;
getTrackConnections(loc, _113609C, CompanyManager::getControllingId(), _trackType, trackAndDirection);
if (_113609C->size == 0)
{
return;
}
const auto trackAndDirection2 = (_113609C->data[_113609C->size - 1] & 0x1FF) ^ (1 << 2);
Map::Pos3 loc2(_x, _y, _constructionZ);
loc2 -= TrackData::getUnkTrack(trackAndDirection2).pos;
if (trackAndDirection2 & (1 << 2))
{
loc2.z += TrackData::getUnkTrack(trackAndDirection2).pos.z;
}
const auto& trackPiece = TrackData::getTrackPiece(trackAndDirection2 >> 3);
const auto i = (trackAndDirection2 & (1 << 2)) ? trackPiece.size() - 1 : 0;
loc2.z += trackPiece[i].z;
GameCommands::TrackRemovalArgs args;
args.pos = loc2;
args.index = trackPiece[i].index;
args.rotation = trackAndDirection2 & 0x3;
args.trackId = trackAndDirection2 >> 3;
args.trackObjectId = _trackType;
auto* trackObj = ObjectManager::get<TrackObject>(_trackType);
auto formatArgs = FormatArguments::common();
formatArgs.skip(3 * sizeof(string_id));
formatArgs.push(trackObj->name);
GameCommands::setErrorTitle(StringIds::cant_build_pop3_string);
if (GameCommands::doCommand(args, GameCommands::Flags::apply) != GameCommands::FAILURE)
{
Map::Pos3 newConstructLoc = Map::Pos3(_x, _y, _constructionZ) - TrackData::getUnkTrack(trackAndDirection2).pos;
_x = newConstructLoc.x;
_y = newConstructLoc.y;
_constructionZ = newConstructLoc.z;
_constructionRotation = TrackData::getUnkTrack(trackAndDirection2).rotationBegin;
_lastSelectedTrackPiece = 0;
_lastSelectedTrackGradient = 0;
activateSelectedConstructionWidgets();
}
}
// 0x004A02F2
static void removeRoad()
{
_trackCost = 0x80000000;
_byte_1136076 = 0;
removeConstructionGhosts();
if (_constructionHover != 0)
{
return;
}
Map::Pos3 loc(_x, _y, _constructionZ);
uint32_t trackAndDirection = (1 << 2) | (_constructionRotation & 0x3);
_113601A[0] = 0;
_113601A[1] = 0;
_113609C->size = 0;
getRoadConnections(loc, _113609C, CompanyManager::getControllingId(), _trackType & ~(1 << 7), trackAndDirection);
if (_113609C->size == 0)
{
return;
}
for (size_t i = 0; i < _113609C->size; ++i)
{
// If trackId is zero
if ((_113609C->data[i] & 0x1F8) == 0)
{
std::swap(_113609C->data[0], _113609C->data[i]);
}
}
auto* roadObj = ObjectManager::get<RoadObject>(_trackType & ~(1 << 7));
if (!(roadObj->flags & Flags12::unk_02))
{
_113609C->size = 1;
_113609C->data[1] = 0xFFFF;
}
uint16_t trackAndDirection2 = 0;
while (_113609C->size != 0)
{
trackAndDirection2 = (_113609C->pop_back() & 0x1FF) ^ (1 << 2);
Map::Pos3 loc2(_x, _y, _constructionZ);
loc2 -= TrackData::getUnkRoad(trackAndDirection2).pos;
if (trackAndDirection2 & (1 << 2))
{
loc2.z += TrackData::getUnkRoad(trackAndDirection2).pos.z;
}
const auto& roadPiece = TrackData::getRoadPiece(trackAndDirection2 >> 3);
const auto i = (trackAndDirection2 & (1 << 2)) ? roadPiece.size() - 1 : 0;
loc2.z += roadPiece[i].z;
GameCommands::RoadRemovalArgs args;
args.pos = loc2;
args.sequenceIndex = roadPiece[i].index;
args.unkDirection = trackAndDirection2 & 0x3;
args.roadId = trackAndDirection2 >> 3;
args.objectId = _trackType & ~(1 << 7);
auto* trackObj = ObjectManager::get<RoadObject>(_trackType & ~(1 << 7));
auto formatArgs = FormatArguments::common();
formatArgs.skip(3 * sizeof(string_id));
formatArgs.push(trackObj->name);
GameCommands::setErrorTitle(StringIds::cant_build_pop3_string);
if (GameCommands::doCommand(args, GameCommands::Flags::apply) == GameCommands::FAILURE)
{
return;
}
}
Map::Pos3 newConstructLoc = Map::Pos3(_x, _y, _constructionZ) - TrackData::getUnkRoad(trackAndDirection2).pos;
_x = newConstructLoc.x;
_y = newConstructLoc.y;
_constructionZ = newConstructLoc.z;
_constructionRotation = TrackData::getUnkRoad(trackAndDirection2).rotationBegin;
_lastSelectedTrackPiece = 0;
_lastSelectedTrackGradient = 0;
activateSelectedConstructionWidgets();
}
// 0x004A0121
static void removeTrack(Window* self, WidgetIndex_t widgetIndex)
{
registers regs;
regs.edx = widgetIndex;
regs.esi = X86Pointer(self);
call(0x004A0121, regs);
if (_trackType & (1 << 7))
{
removeRoad();
}
else
{
removeTrack();
}
}
// 0x0049D3F6