From ef420ec6e6e8f3b84d5afd8179f368b6653f4245 Mon Sep 17 00:00:00 2001 From: Duncan Date: Mon, 16 Aug 2021 21:11:15 +0100 Subject: [PATCH] Construction Window StationTab toolDown (#1105) * Implement airport toolDown * Implement dock toolDown * Tidy up * Refactor and use alternative simpler search * Implement roadStation toolDown * Implement trainStation toolDown * Revert vehicle body changes as no longer required * Include correct headers * Fix off by one error --- src/OpenLoco/GameCommands/GameCommands.cpp | 8 +- src/OpenLoco/GameCommands/GameCommands.h | 136 ++++++- .../Windows/Construction/StationTab.cpp | 357 +++++++++++++++++- 3 files changed, 487 insertions(+), 14 deletions(-) diff --git a/src/OpenLoco/GameCommands/GameCommands.cpp b/src/OpenLoco/GameCommands/GameCommands.cpp index 602932ac..cc35d242 100644 --- a/src/OpenLoco/GameCommands/GameCommands.cpp +++ b/src/OpenLoco/GameCommands/GameCommands.cpp @@ -61,7 +61,7 @@ namespace OpenLoco::GameCommands { GameCommand::vehicleLocalExpress, nullptr, 0x004B694B, true }, { GameCommand::createSignal, nullptr, 0x00488BDB, true }, { GameCommand::removeSignal, nullptr, 0x004891E4, true }, - { GameCommand::gc_unk_15, nullptr, 0x0048BB20, true }, + { GameCommand::createTrainStation, nullptr, 0x0048BB20, true }, { GameCommand::removeTrackStation, nullptr, 0x0048C402, true }, { GameCommand::createTrackMod, nullptr, 0x004A6479, true }, { GameCommand::removeTrackMod, nullptr, 0x004A668A, true }, @@ -88,7 +88,7 @@ namespace OpenLoco::GameCommands { GameCommand::removeRoad, nullptr, 0x004775A5, true }, { GameCommand::createRoadMod, nullptr, 0x0047A21E, true }, { GameCommand::removeRoadMod, nullptr, 0x0047A42F, true }, - { GameCommand::gc_unk_42, nullptr, 0x0048C708, true }, + { GameCommand::createRoadStation, nullptr, 0x0048C708, true }, { GameCommand::removeRoadStation, nullptr, 0x0048D2AC, true }, { GameCommand::createBuilding, nullptr, 0x0042D133, true }, { GameCommand::removeBuilding, nullptr, 0x0042D74E, true }, @@ -102,11 +102,11 @@ namespace OpenLoco::GameCommands { GameCommand::gc_unk_53, nullptr, 0x0047AF0B, true }, { GameCommand::buildCompanyHeadquarters, nullptr, 0x0042ECFC, true }, { GameCommand::removeCompanyHeadquarters, nullptr, 0x0042EEAF, true }, - { GameCommand::gc_unk_56, nullptr, 0x00492C41, true }, + { GameCommand::createAirport, nullptr, 0x00492C41, true }, { GameCommand::removeAirport, nullptr, 0x00493559, true }, { GameCommand::vehiclePlaceAir, nullptr, 0x004267BE, true }, { GameCommand::vehiclePickupAir, nullptr, 0x00426B29, true }, - { GameCommand::gc_unk_60, nullptr, 0x00493AA7, true }, + { GameCommand::createPort, nullptr, 0x00493AA7, true }, { GameCommand::removePort, nullptr, 0x00494570, true }, { GameCommand::vehiclePlaceWater, nullptr, 0x0042773C, true }, { GameCommand::vehiclePickupWater, nullptr, 0x004279CC, true }, diff --git a/src/OpenLoco/GameCommands/GameCommands.h b/src/OpenLoco/GameCommands/GameCommands.h index bd239fa0..b327434a 100644 --- a/src/OpenLoco/GameCommands/GameCommands.h +++ b/src/OpenLoco/GameCommands/GameCommands.h @@ -48,7 +48,7 @@ namespace OpenLoco::GameCommands vehicleLocalExpress = 12, createSignal = 13, removeSignal = 14, - gc_unk_15 = 15, + createTrainStation = 15, removeTrackStation = 16, createTrackMod = 17, removeTrackMod = 18, @@ -75,7 +75,7 @@ namespace OpenLoco::GameCommands removeRoad = 39, createRoadMod = 40, removeRoadMod = 41, - gc_unk_42 = 42, + createRoadStation = 42, removeRoadStation = 43, createBuilding = 44, removeBuilding = 45, @@ -89,11 +89,11 @@ namespace OpenLoco::GameCommands gc_unk_53 = 53, buildCompanyHeadquarters = 54, removeCompanyHeadquarters = 55, - gc_unk_56 = 56, + createAirport = 56, removeAirport = 57, vehiclePlaceAir = 58, vehiclePickupAir = 59, - gc_unk_60 = 60, + createPort = 60, removePort = 61, vehiclePlaceWater = 62, vehiclePickupWater = 63, @@ -394,6 +394,42 @@ namespace OpenLoco::GameCommands } }; + struct TrackStationPlacementArgs + { + static constexpr auto command = GameCommand::createTrainStation; + + TrackStationPlacementArgs() = default; + explicit TrackStationPlacementArgs(const registers& regs) + : pos(regs.ax, regs.cx, regs.di) + , rotation(regs.bh & 0x3) + , trackId(regs.dl & 0xF) + , index(regs.dh & 0x3) + , trackObjectId(regs.bp) + , type(regs.edi >> 16) + { + } + + Map::Pos3 pos; + uint8_t rotation; + uint8_t trackId; + uint8_t index; + uint8_t trackObjectId; + uint8_t type; + + explicit operator registers() const + { + registers regs; + regs.ax = pos.x; + regs.cx = pos.y; + regs.edi = pos.z | (type << 16); + regs.bh = rotation; + regs.dl = trackId; + regs.dh = index; + regs.bp = trackObjectId; + return regs; + } + }; + struct TrackStationRemovalArgs { static constexpr auto command = GameCommand::removeTrackStation; @@ -869,6 +905,42 @@ namespace OpenLoco::GameCommands } }; + struct RoadStationPlacementArgs + { + static constexpr auto command = GameCommand::createRoadStation; + + RoadStationPlacementArgs() = default; + explicit RoadStationPlacementArgs(const registers& regs) + : pos(regs.ax, regs.cx, regs.di) + , rotation(regs.bh & 0x3) + , roadId(regs.dl & 0xF) + , index(regs.dh & 0x3) + , roadObjectId(regs.bp) + , type(regs.edi >> 16) + { + } + + Map::Pos3 pos; + uint8_t rotation; + uint8_t roadId; + uint8_t index; + uint8_t roadObjectId; + uint8_t type; + + explicit operator registers() const + { + registers regs; + regs.ax = pos.x; + regs.cx = pos.y; + regs.edi = pos.z | (type << 16); + regs.bh = rotation; + regs.dl = roadId; + regs.dh = index; + regs.bp = roadObjectId; + return regs; + } + }; + struct RoadStationRemovalArgs { static constexpr auto command = GameCommand::removeRoadStation; @@ -1105,6 +1177,34 @@ namespace OpenLoco::GameCommands } }; + struct AirportPlacementArgs + { + static constexpr auto command = GameCommand::createAirport; + + AirportPlacementArgs() = default; + explicit AirportPlacementArgs(const registers regs) + : pos(regs.ax, regs.cx, regs.di) + , rotation(regs.bh) + , type(regs.dl) + { + } + + Map::Pos3 pos; + uint8_t rotation; + uint8_t type; + + explicit operator registers() const + { + registers regs; + regs.ax = pos.x; + regs.cx = pos.y; + regs.di = pos.z; + regs.bh = rotation; + regs.dl = type; + return regs; + } + }; + struct AirportRemovalArgs { static constexpr auto command = GameCommand::removeAirport; @@ -1164,6 +1264,34 @@ namespace OpenLoco::GameCommands return doCommand(GameCommand::vehiclePickupAir, regs) != FAILURE; } + struct PortPlacementArgs + { + static constexpr auto command = GameCommand::createPort; + + PortPlacementArgs() = default; + explicit PortPlacementArgs(const registers& regs) + : pos(regs.ax, regs.cx, regs.di) + , rotation(regs.bh) + , type(regs.dl) + { + } + + Map::Pos3 pos; + uint8_t rotation; + uint8_t type; + + explicit operator registers() const + { + registers regs; + regs.ax = pos.x; + regs.cx = pos.y; + regs.di = pos.z; + regs.bh = rotation; + regs.dl = type; + return regs; + } + }; + struct PortRemovalArgs { static constexpr auto command = GameCommand::removePort; diff --git a/src/OpenLoco/Windows/Construction/StationTab.cpp b/src/OpenLoco/Windows/Construction/StationTab.cpp index 1f2cf472..f5970b40 100644 --- a/src/OpenLoco/Windows/Construction/StationTab.cpp +++ b/src/OpenLoco/Windows/Construction/StationTab.cpp @@ -1,11 +1,15 @@ +#include "../../Audio/Audio.h" #include "../../CompanyManager.h" +#include "../../GameCommands/GameCommands.h" #include "../../Graphics/ImageIds.h" +#include "../../Industry.h" #include "../../Input.h" #include "../../Localisation/FormatArguments.hpp" #include "../../Localisation/StringIds.h" #include "../../Objects/AirportObject.h" #include "../../Objects/CargoObject.h" #include "../../Objects/DockObject.h" +#include "../../Objects/IndustryObject.h" #include "../../Objects/ObjectManager.h" #include "../../Objects/RoadObject.h" #include "../../Objects/RoadStationObject.h" @@ -167,15 +171,356 @@ namespace OpenLoco::Ui::Windows::Construction::Station call(0x0049E421, regs); } + static loco_global _1135F7C; + static loco_global _1135F90; + + // 0x004A47D9 + static std::optional getAirportPlacementArgsFromCursor(const int16_t x, const int16_t y) + { + auto pos = ViewportInteraction::getSurfaceOrWaterLocFromUi({ x, y }); + if (!pos) + { + return std::nullopt; + } + + GameCommands::AirportPlacementArgs placementArgs; + placementArgs.type = _lastSelectedStationType; + placementArgs.rotation = _constructionRotation; + auto* airportObject = ObjectManager::get(placementArgs.type); + TilePos2 minPos(airportObject->min_x, airportObject->min_y); + TilePos2 maxPos(airportObject->max_x, airportObject->max_y); + + minPos = Math::Vector::rotate(minPos, placementArgs.rotation); + maxPos = Math::Vector::rotate(maxPos, placementArgs.rotation); + + minPos += *pos; + maxPos += *pos; + + if (minPos.x > maxPos.x) + { + std::swap(minPos.x, maxPos.x); + } + + if (minPos.y > maxPos.y) + { + std::swap(minPos.y, maxPos.y); + } + + _1135F7C = minPos; + _1135F90 = maxPos; + auto maxBaseZ = 0; + for (auto checkPos = minPos; checkPos.y <= maxPos.y; ++checkPos.y) + { + for (checkPos.x = minPos.x; checkPos.x <= maxPos.x; ++checkPos.x) + { + if (!validCoords(checkPos)) + { + continue; + } + const auto tile = TileManager::get(checkPos); + const auto* surface = tile.surface(); + if (surface == nullptr) + { + return std::nullopt; + } + + const auto baseZ = surface->water() ? surface->water() * 4 : surface->baseZ(); + maxBaseZ = std::max(maxBaseZ, baseZ); + } + } + placementArgs.pos = Map::Pos3(pos->x, pos->y, maxBaseZ * 4); + return { placementArgs }; + } + + // 0x004A5550 + static void onToolDownAirport(const int16_t x, const int16_t y) + { + static loco_global _113600C; + _113600C = Point(x, y); + removeConstructionGhosts(); + + const auto args = getAirportPlacementArgsFromCursor(x, y); + if (!args) + { + return; + } + + const auto* airportObject = ObjectManager::get(_lastSelectedStationType); + auto formatArgs = FormatArguments::common(); + formatArgs.skip(3 * sizeof(string_id)); + formatArgs.push(airportObject->name); + GameCommands::setErrorTitle(StringIds::cant_build_pop3_string); + GameCommands::doCommand(*args, GameCommands::Flags::apply); + } + + // 0x004A4903 + static std::optional getDockPlacementArgsFromCursor(const int16_t x, const int16_t y) + { + auto pos = ViewportInteraction::getSurfaceOrWaterLocFromUi({ x, y }); + if (!pos) + { + return std::nullopt; + } + + // count of water on each side of the placement + // 0x0113608B + std::array _nearbyWaterCount = { 0 }; + + uint8_t directionOfIndustry = 0xFF; + uint8_t waterHeight = 0; + _1135F7C = *pos; + _1135F90 = *pos + TilePos2(1, 1); + + constexpr std::array, 4> searchArea = { + std::array{ TilePos2{ -1, 0 }, TilePos2{ -1, 1 } }, + std::array{ TilePos2{ 0, 2 }, TilePos2{ 1, 2 } }, + std::array{ TilePos2{ 2, 0 }, TilePos2{ 2, 1 } }, + std::array{ TilePos2{ 0, -1 }, TilePos2{ 1, -1 } }, + }; + for (auto side = 0; side < 4; ++side) + { + for (const auto& offset : searchArea[side]) + { + const auto searchPos = offset + *pos; + if (!validCoords(searchPos)) + { + continue; + } + const auto tile = TileManager::get(searchPos); + bool surfaceFound = false; + for (auto el : tile) + { + if (surfaceFound) + { + const auto* elIndustry = el.asIndustry(); + if (elIndustry == nullptr) + { + continue; + } + if (elIndustry->isGhost()) + { + continue; + } + + auto* industry = elIndustry->industry(); + auto* industryObj = industry->object(); + if (!(industryObj->flags & IndustryObjectFlags::built_on_water)) + { + continue; + } + + directionOfIndustry = side; + } + const auto* surface = el.asSurface(); + if (surface == nullptr) + { + continue; + } + else + { + surfaceFound = true; + + if (!surface->water()) + { + continue; + } + waterHeight = surface->water() * 4; + if (waterHeight - 4 == surface->baseZ() && surface->isSlopeDoubleHeight()) + { + continue; + } + + _nearbyWaterCount[(side + 2) & 0x3]++; + } + } + } + } + + if (waterHeight == 0) + { + return std::nullopt; + } + + GameCommands::PortPlacementArgs placementArgs; + placementArgs.type = _lastSelectedStationType; + placementArgs.pos = Map::Pos3(pos->x, pos->y, waterHeight * 4); + if (directionOfIndustry != 0xFF) + { + placementArgs.rotation = directionOfIndustry; + } + else + { + auto res = std::find_if(std::begin(_nearbyWaterCount), std::end(_nearbyWaterCount), [](uint8_t value) { return value >= 2; }); + if (res != std::end(_nearbyWaterCount)) + { + placementArgs.rotation = std::distance(std::begin(_nearbyWaterCount), res); + } + else + { + res = std::find_if(std::begin(_nearbyWaterCount), std::end(_nearbyWaterCount), [](uint8_t value) { return value >= 1; }); + if (res != std::end(_nearbyWaterCount)) + { + placementArgs.rotation = std::distance(std::begin(_nearbyWaterCount), res); + } + else + { + static loco_global _113608A; // ai rotation?? + placementArgs.rotation = _113608A; + } + } + } + return { placementArgs }; + } + + // 0x004A55AB + static void onToolDownDock(const int16_t x, const int16_t y) + { + static loco_global _113600C; + _113600C = Point(x, y); + removeConstructionGhosts(); + + const auto args = getDockPlacementArgsFromCursor(x, y); + if (!args) + { + return; + } + + const auto* dockObject = ObjectManager::get(_lastSelectedStationType); + auto formatArgs = FormatArguments::common(); + formatArgs.skip(3 * sizeof(string_id)); + formatArgs.push(dockObject->name); + GameCommands::setErrorTitle(StringIds::cant_build_pop3_string); + GameCommands::doCommand(*args, GameCommands::Flags::apply); + } + + static std::optional getRoadStationPlacementArgsFromCursor(const int16_t x, const int16_t y) + { + const auto res = ViewportInteraction::getMapCoordinatesFromPos(x, y, ~ViewportInteraction::InteractionItemFlags::roadAndTram); + const auto& interaction = res.first; + if (interaction.type != ViewportInteraction::InteractionItem::road) + { + return std::nullopt; + } + + auto* elRoad = reinterpret_cast(interaction.object)->asRoad(); + if (elRoad == nullptr) + { + return std::nullopt; + } + + GameCommands::RoadStationPlacementArgs placementArgs; + placementArgs.pos = Map::Pos3(interaction.pos.x, interaction.pos.y, elRoad->baseZ() * 4); + placementArgs.rotation = elRoad->unkDirection(); + placementArgs.roadId = elRoad->roadId(); + placementArgs.index = elRoad->sequenceIndex(); + placementArgs.roadObjectId = elRoad->roadObjectId(); + placementArgs.type = _lastSelectedStationType; + return { placementArgs }; + } + + // 0x004A548F + static void onToolDownRoadStation(const int16_t x, const int16_t y) + { + static loco_global _113600C; + _113600C = Point(x, y); + removeConstructionGhosts(); + + const auto args = getRoadStationPlacementArgsFromCursor(x, y); + if (!args) + { + return; + } + + const auto* roadStationObject = ObjectManager::get(_lastSelectedStationType); + auto formatArgs = FormatArguments::common(); + formatArgs.skip(3 * sizeof(string_id)); + formatArgs.push(roadStationObject->name); + GameCommands::setErrorTitle(StringIds::cant_build_pop3_string); + if (GameCommands::doCommand(*args, GameCommands::Flags::apply) != GameCommands::FAILURE) + { + Audio::playSound(Audio::SoundId::construct, GameCommands::getPosition()); + } + } + + static std::optional getTrackStationPlacementArgsFromCursor(const int16_t x, const int16_t y) + { + const auto res = ViewportInteraction::getMapCoordinatesFromPos(x, y, ~ViewportInteraction::InteractionItemFlags::track); + const auto& interaction = res.first; + if (interaction.type != ViewportInteraction::InteractionItem::track) + { + return std::nullopt; + } + + auto* elTrack = reinterpret_cast(interaction.object)->asTrack(); + if (elTrack == nullptr) + { + return std::nullopt; + } + + GameCommands::TrackStationPlacementArgs placementArgs; + placementArgs.pos = Map::Pos3(interaction.pos.x, interaction.pos.y, elTrack->baseZ() * 4); + placementArgs.rotation = elTrack->unkDirection(); + placementArgs.trackId = elTrack->trackId(); + placementArgs.index = elTrack->sequenceIndex(); + placementArgs.trackObjectId = elTrack->trackObjectId(); + placementArgs.type = _lastSelectedStationType; + return { placementArgs }; + } + + // 0x004A5390 + static void onToolDownTrackStation(const int16_t x, const int16_t y) + { + static loco_global _113600C; + _113600C = Point(x, y); + removeConstructionGhosts(); + + const auto args = getTrackStationPlacementArgsFromCursor(x, y); + if (!args) + { + return; + } + + const auto* trainStationObject = ObjectManager::get(_lastSelectedStationType); + auto formatArgs = FormatArguments::common(); + formatArgs.skip(3 * sizeof(string_id)); + formatArgs.push(trainStationObject->name); + GameCommands::setErrorTitle(StringIds::cant_build_pop3_string); + + if (args->trackObjectId != _trackType) + { + Error::open(StringIds::null, StringIds::wrong_type_of_track_road); + return; + } + if (GameCommands::doCommand(*args, GameCommands::Flags::apply) != GameCommands::FAILURE) + { + Audio::playSound(Audio::SoundId::construct, GameCommands::getPosition()); + } + } + // 0x0049E42C 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(0x0049E42C, regs); + if (widgetIndex != widx::image) + { + return; + } + + if (_byte_1136063 & (1 << 7)) + { + onToolDownAirport(x, y); + } + else if (_byte_1136063 & (1 << 6)) + { + onToolDownDock(x, y); + } + else if (_trackType & (1 << 7)) + { + onToolDownRoadStation(x, y); + } + else + { + onToolDownTrackStation(x, y); + } } // 0x0049DD39