From 3bcf5dfd8f65d6098a89f6576a837c34c214dd9b Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 23 Apr 2024 19:17:21 +0100 Subject: [PATCH] Add: Road waypoint functionality --- docs/landscape.html | 2 +- src/command_type.h | 3 + src/lang/english.txt | 6 + src/newgrf_commons.cpp | 3 + src/newgrf_roadstop.cpp | 9 +- src/order_cmd.cpp | 12 +- src/order_gui.cpp | 9 + src/pathfinder/follow_track.hpp | 2 +- src/pathfinder/npf/npf.cpp | 3 +- src/pathfinder/yapf/yapf_road.cpp | 16 +- src/rail_gui.cpp | 8 +- src/road_cmd.cpp | 4 +- src/road_gui.cpp | 76 ++++++++ src/road_map.cpp | 2 +- src/roadveh_cmd.cpp | 2 +- src/saveload/afterload.cpp | 8 +- src/saveload/company_sl.cpp | 3 +- src/saveload/saveload.h | 2 + src/saveload/station_sl.cpp | 4 + src/script/api/script_road.cpp | 4 +- src/station.cpp | 6 +- src/station_base.h | 2 +- src/station_cmd.cpp | 288 ++++++++++++++++++++++-------- src/station_gui.cpp | 46 +++-- src/station_gui.h | 6 +- src/station_map.h | 79 ++++++-- src/station_type.h | 1 + src/table/station_land.h | 10 ++ src/town_cmd.cpp | 4 +- src/vehicle.cpp | 2 +- src/vehicle_gui.cpp | 2 +- src/viewport_type.h | 2 + src/waypoint.cpp | 4 + src/waypoint_base.h | 9 + src/waypoint_cmd.cpp | 184 ++++++++++++++++++- src/waypoint_cmd.h | 5 + src/waypoint_func.h | 3 +- src/waypoint_gui.cpp | 30 +++- src/widgets/road_widget.h | 1 + 39 files changed, 708 insertions(+), 154 deletions(-) diff --git a/docs/landscape.html b/docs/landscape.html index 5ef3a9f457..3971c151c5 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -990,7 +990,7 @@
  • m6 bit 7: rail station / waypoint may have catenary pylons
  • -
  • m6 bits 6..3: the station type (rail, airport, truck, bus, oilrig, dock, buoy, waypoint)
  • +
  • m6 bits 6..3: the station type (rail, airport, truck, bus, oilrig, dock, buoy, waypoint, road waypoint)
  • m6 bit 2: pbs reservation state for railway stations/waypoints
  • m6 bit 1: rail station / waypoint may have catenary wires
  • m6 bit 0: rail station / waypoint is blocked
  • diff --git a/src/command_type.h b/src/command_type.h index dae768104b..3913131350 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -207,6 +207,9 @@ enum Commands : uint16_t { CMD_RENAME_WAYPOINT, ///< rename a waypoint CMD_REMOVE_FROM_RAIL_WAYPOINT, ///< remove a (rectangle of) tiles from a rail waypoint + CMD_BUILD_ROAD_WAYPOINT, ///< build a road waypoint + CMD_REMOVE_FROM_ROAD_WAYPOINT, ///< remove a (rectangle of) tiles from a road waypoint + CMD_BUILD_ROAD_STOP, ///< build a road stop CMD_REMOVE_ROAD_STOP, ///< remove a road stop CMD_BUILD_LONG_ROAD, ///< build a complete road (not a "half" one) diff --git a/src/lang/english.txt b/src/lang/english.txt index 1530dd1e20..e190a9701d 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2877,6 +2877,8 @@ STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD :{BLACK}Build ro STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM :{BLACK}Build tramway section using the Autotram mode. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build road vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build tram vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT :{BLACK}Convert road to waypoint. Ctrl enables joining waypoints. Shift toggles building/showing cost estimate +STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT :{BLACK}Convert tram to waypoint. Ctrl enables joining waypoints. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION :{BLACK}Build bus station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION :{BLACK}Build passenger tram station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY :{BLACK}Build lorry station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only @@ -5084,11 +5086,14 @@ STR_ERROR_WAYPOINT_ADJOINS_MORE_THAN_ONE_EXISTING :{WHITE}Adjoins STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT :{WHITE}Too close to another waypoint STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT :{WHITE}Can't build train waypoint here... +STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT :{WHITE}Can't build road waypoint here... STR_ERROR_CAN_T_POSITION_BUOY_HERE :{WHITE}Can't place buoy here... STR_ERROR_CAN_T_CHANGE_WAYPOINT_NAME :{WHITE}Can't change waypoint name... STR_ERROR_CAN_T_REMOVE_TRAIN_WAYPOINT :{WHITE}Can't remove train waypoint here... +STR_ERROR_CAN_T_REMOVE_ROAD_WAYPOINT :{WHITE}Can't remove road waypoint here... STR_ERROR_MUST_REMOVE_RAILWAYPOINT_FIRST :{WHITE}Must remove rail waypoint first +STR_ERROR_MUST_REMOVE_ROADWAYPOINT_FIRST :{WHITE}Must remove road waypoint first STR_ERROR_BUOY_IN_THE_WAY :{WHITE}... buoy in the way STR_ERROR_BUOY_IS_IN_USE :{WHITE}... buoy is in use by another company! @@ -5336,6 +5341,7 @@ STR_ERROR_NO_STOP_ARTICULATED_VEHICLE :{WHITE}There ar STR_ERROR_AIRPORT_NO_PLANES :{WHITE}This plane cannot land at this heliport STR_ERROR_AIRPORT_NO_HELICOPTERS :{WHITE}This helicopter cannot land at this airport STR_ERROR_NO_RAIL_WAYPOINT :{WHITE}There is no railway waypoint +STR_ERROR_NO_ROAD_WAYPOINT :{WHITE}There is no road waypoint STR_ERROR_NO_BUOY :{WHITE}There is no buoy # Timetable related errors diff --git a/src/newgrf_commons.cpp b/src/newgrf_commons.cpp index 9589e31ef4..822b641cbe 100644 --- a/src/newgrf_commons.cpp +++ b/src/newgrf_commons.cpp @@ -437,6 +437,9 @@ uint32_t GetNearbyTileInformation(TileIndex tile, bool grf_version8) /* Fake tile type for trees on shore */ if (IsTileType(tile, MP_TREES) && GetTreeGround(tile) == TREE_GROUND_SHORE) tile_type = MP_WATER; + /* Fake tile type for road waypoints */ + if (IsRoadWaypointTile(tile)) tile_type = MP_ROAD; + auto [tileh, z] = GetTilePixelSlope(tile); /* Return 0 if the tile is a land tile */ uint8_t terrain_type = (HasTileWaterClass(tile) ? (GetWaterClass(tile) + 1) & 3 : 0) << 5 | GetTerrainType(tile) << 2 | (tile_type == MP_WATER ? 1 : 0) << 1; diff --git a/src/newgrf_roadstop.cpp b/src/newgrf_roadstop.cpp index eaa76fe5f9..d8586eb7fb 100644 --- a/src/newgrf_roadstop.cpp +++ b/src/newgrf_roadstop.cpp @@ -127,7 +127,7 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u if (this->tile == INVALID_TILE) return UINT_MAX; TileIndex tile = this->tile; if (parameter != 0) tile = GetNearbyTile(parameter, tile); - return (IsRoadStopTile(tile) && GetStationIndex(tile) == this->st->index) ? this->st->GetRoadStopAnimationFrame(tile) : UINT_MAX; + return (IsAnyRoadStopTile(tile) && GetStationIndex(tile) == this->st->index) ? this->st->GetRoadStopAnimationFrame(tile) : UINT_MAX; } /* Land info of nearby tile */ @@ -143,7 +143,7 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u if (this->tile == INVALID_TILE) return 0xFFFFFFFF; TileIndex nearby_tile = GetNearbyTile(parameter, this->tile); - if (!IsRoadStopTile(nearby_tile)) return 0xFFFFFFFF; + if (!IsAnyRoadStopTile(nearby_tile)) return 0xFFFFFFFF; uint32_t grfid = this->st->roadstop_speclist[GetCustomRoadStopSpecIndex(this->tile)].grfid; bool same_orientation = GetStationGfx(this->tile) == GetStationGfx(nearby_tile); @@ -151,6 +151,7 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u uint32_t res = GetStationGfx(nearby_tile) << 12 | !same_orientation << 11 | !!same_station << 10; StationType type = GetStationType(nearby_tile); if (type == STATION_TRUCK) res |= (1 << 16); + if (type == STATION_ROADWAYPOINT) res |= (2 << 16); if (type == this->type) SetBit(res, 20); if (IsCustomRoadStopSpecIndex(nearby_tile)) { @@ -165,7 +166,7 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u if (this->tile == INVALID_TILE) return 0xFFFFFFFF; TileIndex nearby_tile = GetNearbyTile(parameter, this->tile); - if (!IsRoadStopTile(nearby_tile)) return 0xFFFFFFFF; + if (!IsAnyRoadStopTile(nearby_tile)) return 0xFFFFFFFF; if (!IsCustomRoadStopSpecIndex(nearby_tile)) return 0; const auto &sm = BaseStation::GetByTile(nearby_tile)->roadstop_speclist[GetCustomRoadStopSpecIndex(nearby_tile)]; @@ -176,7 +177,7 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u case 0x6B: { TileIndex nearby_tile = GetNearbyTile(parameter, this->tile); - if (!IsRoadStopTile(nearby_tile)) return 0xFFFFFFFF; + if (!IsAnyRoadStopTile(nearby_tile)) return 0xFFFFFFFF; if (!IsCustomRoadStopSpecIndex(nearby_tile)) return 0xFFFE; uint32_t grfid = this->st->roadstop_speclist[GetCustomRoadStopSpecIndex(this->tile)].grfid; diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 003d879303..754e6767ff 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -831,6 +831,14 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se break; } + case VEH_ROAD: { + if (!(wp->facilities & FACIL_BUS_STOP) || !(wp->facilities & FACIL_TRUCK_STOP)) return CommandCost(STR_ERROR_CAN_T_ADD_ORDER, STR_ERROR_NO_ROAD_WAYPOINT); + + ret = CheckOwnership(wp->owner); + if (ret.Failed()) return ret; + break; + } + case VEH_SHIP: if (!(wp->facilities & FACIL_DOCK)) return CommandCost(STR_ERROR_CAN_T_ADD_ORDER, STR_ERROR_NO_BUOY); if (wp->owner != OWNER_NONE) { @@ -842,8 +850,8 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se /* Order flags can be any of the following for waypoints: * [non-stop] - * non-stop orders (if any) are only valid for trains */ - if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && v->type != VEH_TRAIN) return CMD_ERROR; + * non-stop orders (if any) are only valid for trains and road vehicles */ + if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR; break; } diff --git a/src/order_gui.cpp b/src/order_gui.cpp index 397502cd32..fe063dd66e 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -435,6 +435,15 @@ static Order GetOrderCmdFromTile(const Vehicle *v, TileIndex tile) return order; } + /* check road waypoint */ + if (IsRoadWaypointTile(tile) && + v->type == VEH_ROAD && + IsTileOwner(tile, _local_company)) { + order.MakeGoToWaypoint(GetStationIndex(tile)); + if (_settings_client.gui.new_nonstop != _ctrl_pressed) order.SetNonStopType(ONSF_NO_STOP_AT_ANY_STATION); + return order; + } + /* check buoy (no ownership) */ if (IsBuoyTile(tile) && v->type == VEH_SHIP) { order.MakeGoToWaypoint(GetStationIndex(tile)); diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index 513d3ab0c7..28b4d92375 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -225,7 +225,7 @@ protected: /* special handling for stations */ if (IsRailTT() && HasStationTileRail(m_new_tile)) { m_is_station = true; - } else if (IsRoadTT() && IsRoadStopTile(m_new_tile)) { + } else if (IsRoadTT() && IsStationRoadStopTile(m_new_tile)) { m_is_station = true; } } diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp index 5ac43cd8c9..060d36ff7e 100644 --- a/src/pathfinder/npf/npf.cpp +++ b/src/pathfinder/npf/npf.cpp @@ -359,6 +359,7 @@ static int32_t NPFRoadPathCost(AyStar *, AyStarNode *current, OpenListNode *) case MP_STATION: { cost = NPF_TILE_LENGTH; + if (IsRoadWaypoint(tile)) break; const RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); if (IsDriveThroughStopTile(tile)) { /* Increase the cost for drive-through road stops */ @@ -1132,7 +1133,7 @@ static void NPFFillWithOrderData(NPFFindStationOrTileData *fstd, const Vehicle * if (v->type == VEH_TRAIN) { fstd->station_type = v->current_order.IsType(OT_GOTO_STATION) ? STATION_RAIL : STATION_WAYPOINT; } else if (v->type == VEH_ROAD) { - fstd->station_type = RoadVehicle::From(v)->IsBus() ? STATION_BUS : STATION_TRUCK; + fstd->station_type = v->current_order.IsType(OT_GOTO_STATION) ? (RoadVehicle::From(v)->IsBus() ? STATION_BUS : STATION_TRUCK) : STATION_ROADWAYPOINT; } else if (v->type == VEH_SHIP) { fstd->station_type = v->current_order.IsType(OT_GOTO_STATION) ? STATION_DOCK : STATION_BUOY; } diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 41102c2e15..209b64b52a 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -70,6 +70,8 @@ protected: break; case MP_STATION: { + if (IsRoadWaypoint(tile)) break; + const RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); if (IsDriveThroughStopTile(tile)) { /* Increase the cost for drive-through road stops */ @@ -232,7 +234,7 @@ protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station; - bool m_bus; + StationType m_station_type; bool m_non_artic; public: @@ -240,8 +242,14 @@ public: { if (v->current_order.IsType(OT_GOTO_STATION)) { m_dest_station = v->current_order.GetDestination(); - m_bus = v->IsBus(); - m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_bus ? STATION_BUS : STATION_TRUCK); + m_station_type = v->IsBus() ? STATION_BUS : STATION_TRUCK; + m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type); + m_non_artic = !v->HasArticulatedPart(); + m_destTrackdirs = INVALID_TRACKDIR_BIT; + } else if (v->current_order.IsType(OT_GOTO_WAYPOINT)) { + m_dest_station = v->current_order.GetDestination(); + m_station_type = STATION_ROADWAYPOINT; + m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type); m_non_artic = !v->HasArticulatedPart(); m_destTrackdirs = INVALID_TRACKDIR_BIT; } else { @@ -275,7 +283,7 @@ public: if (m_dest_station != INVALID_STATION) { return IsTileType(tile, MP_STATION) && GetStationIndex(tile) == m_dest_station && - (m_bus ? IsBusStop(tile) : IsTruckStop(tile)) && + (m_station_type == GetStationType(tile)) && (m_non_artic || IsDriveThroughStopTile(tile)); } diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 7cf1dfeb3c..47673eef79 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -163,7 +163,7 @@ static void PlaceRail_Waypoint(TileIndex tile) return; } - Axis axis = GetAxisForNewWaypoint(tile); + Axis axis = GetAxisForNewRailWaypoint(tile); if (IsValidAxis(axis)) { /* Valid tile for waypoints */ VpStartPlaceSizing(tile, axis == AXIS_X ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_STATION); @@ -776,7 +776,7 @@ struct BuildRailToolbarWindow : Window { } }; - ShowSelectWaypointIfNeeded(ta, proc); + ShowSelectRailWaypointIfNeeded(ta, proc); } } break; @@ -816,7 +816,7 @@ struct BuildRailToolbarWindow : Window { void OnRealtimeTick([[maybe_unused]] uint delta_ms) override { - if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) CheckRedrawWaypointCoverage(this); + if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) CheckRedrawRailWaypointCoverage(this); } /** @@ -2203,7 +2203,7 @@ struct BuildRailWaypointWindow : PickerWindowBase { void OnRealtimeTick([[maybe_unused]] uint delta_ms) override { - CheckRedrawWaypointCoverage(this); + CheckRedrawRailWaypointCoverage(this); } }; diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index eee9e24efb..8515cac301 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1461,7 +1461,7 @@ void DrawRoadCatenary(const TileInfo *ti) tram = road = (GetCrossingRailAxis(ti->tile) == AXIS_Y ? ROAD_X : ROAD_Y); } } else if (IsTileType(ti->tile, MP_STATION)) { - if (IsRoadStop(ti->tile)) { + if (IsAnyRoadStop(ti->tile)) { if (IsDriveThroughStopTile(ti->tile)) { Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y; tram = road = (axis == AXIS_X ? ROAD_X : ROAD_Y); @@ -2448,7 +2448,7 @@ CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_s TileType tt = GetTileType(tile); switch (tt) { case MP_STATION: - if (!IsRoadStop(tile)) continue; + if (!IsAnyRoadStop(tile)) continue; break; case MP_ROAD: if (IsLevelCrossing(tile) && RoadNoLevelCrossing(to_type)) { diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 8637c910f1..7be4b7797f 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -16,6 +16,7 @@ #include "command_func.h" #include "road_cmd.h" #include "station_func.h" +#include "waypoint_func.h" #include "window_func.h" #include "vehicle_func.h" #include "sound_func.h" @@ -33,6 +34,7 @@ #include "strings_func.h" #include "core/geometry_func.hpp" #include "station_cmd.h" +#include "waypoint_cmd.h" #include "road_cmd.h" #include "tunnelbridge_cmd.h" #include "newgrf_roadstop.h" @@ -236,6 +238,29 @@ static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, RoadStopType ShowSelectStationIfNeeded(ta, proc); } +/** + * Place a road waypoint. + * @param tile Position to start dragging a waypoint. + */ +static void PlaceRoad_Waypoint(TileIndex tile) +{ + if (_remove_button_clicked) { + VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_ROAD_WAYPOINT); + return; + } + + Axis axis = GetAxisForNewRoadWaypoint(tile); + if (IsValidAxis(axis)) { + /* Valid tile for waypoints */ + VpStartPlaceSizing(tile, axis == AXIS_X ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_ROAD_WAYPOINT); + VpSetPlaceSizingLimit(_settings_game.station.station_spread); + } else { + /* Tile where we can't build road waypoints. This is always going to fail, + * but provides the user with a proper error message. */ + Command::Post(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT, tile, AXIS_X, 1, 1, ROADSTOP_CLASS_WAYP, 0, INVALID_STATION, false); + } +} + /** * Callback for placing a bus station. * @param tile Position to place the station. @@ -349,22 +374,26 @@ struct BuildRoadToolbarWindow : Window { bool can_build = CanBuildVehicleInfrastructure(VEH_ROAD, rtt); this->SetWidgetsDisabledState(!can_build, WID_ROT_DEPOT, + WID_ROT_BUILD_WAYPOINT, WID_ROT_BUS_STATION, WID_ROT_TRUCK_STATION); if (!can_build) { CloseWindowById(WC_BUS_STATION, TRANSPORT_ROAD); CloseWindowById(WC_TRUCK_STATION, TRANSPORT_ROAD); CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD); + CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_ROAD); } if (_game_mode != GM_EDITOR) { if (!can_build) { /* Show in the tooltip why this button is disabled. */ this->GetWidget(WID_ROT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); } else { this->GetWidget(WID_ROT_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT); + this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT : STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION); } @@ -444,6 +473,7 @@ struct BuildRoadToolbarWindow : Window { case WID_ROT_BUS_STATION: case WID_ROT_TRUCK_STATION: + case WID_ROT_BUILD_WAYPOINT: if (RoadTypeIsRoad(this->roadtype)) this->DisableWidget(WID_ROT_ONE_WAY); this->SetWidgetDisabledState(WID_ROT_REMOVE, !this->IsWidgetLowered(clicked_widget)); break; @@ -504,6 +534,12 @@ struct BuildRoadToolbarWindow : Window { } break; + case WID_ROT_BUILD_WAYPOINT: + if (HandlePlacePushButton(this, WID_ROT_BUILD_WAYPOINT, SPR_CURSOR_WAYPOINT, HT_RECT)) { + this->last_started_action = widget; + } + break; + case WID_ROT_BUS_STATION: if (HandlePlacePushButton(this, WID_ROT_BUS_STATION, SPR_CURSOR_BUS_STATION, HT_RECT)) { ShowRVStationPicker(this, ROADSTOP_BUS); @@ -593,6 +629,10 @@ struct BuildRoadToolbarWindow : Window { tile, _cur_roadtype, _road_depot_orientation); break; + case WID_ROT_BUILD_WAYPOINT: + PlaceRoad_Waypoint(tile); + break; + case WID_ROT_BUS_STATION: PlaceRoad_BusStation(tile); break; @@ -634,6 +674,7 @@ struct BuildRoadToolbarWindow : Window { CloseWindowById(WC_BUS_STATION, TRANSPORT_ROAD); CloseWindowById(WC_TRUCK_STATION, TRANSPORT_ROAD); CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD); + CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_ROAD); CloseWindowById(WC_SELECT_STATION, 0); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -706,6 +747,30 @@ struct BuildRoadToolbarWindow : Window { break; } + case DDSP_BUILD_ROAD_WAYPOINT: + case DDSP_REMOVE_ROAD_WAYPOINT: + if (this->IsWidgetLowered(WID_ROT_BUILD_WAYPOINT)) { + if (_remove_button_clicked) { + Command::Post(STR_ERROR_CAN_T_REMOVE_ROAD_WAYPOINT, CcPlaySound_CONSTRUCTION_OTHER, end_tile, start_tile); + } else { + TileArea ta(start_tile, end_tile); + Axis axis = select_method == VPM_X_LIMITED ? AXIS_X : AXIS_Y; + bool adjacent = _ctrl_pressed; + uint16_t waypoint_type = 0; + + auto proc = [=](bool test, StationID to_join) -> bool { + if (test) { + return Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), ta.tile, axis, ta.w, ta.h, ROADSTOP_CLASS_WAYP, waypoint_type, INVALID_STATION, adjacent).Succeeded(); + } else { + return Command::Post(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT, CcPlaySound_CONSTRUCTION_OTHER, ta.tile, axis, ta.w, ta.h, ROADSTOP_CLASS_WAYP, waypoint_type, to_join, adjacent); + } + }; + + ShowSelectRoadWaypointIfNeeded(ta, proc); + } + } + break; + case DDSP_BUILD_BUSSTOP: case DDSP_REMOVE_BUSSTOP: if (this->IsWidgetLowered(WID_ROT_BUS_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_BUS, _cur_roadtype)) { @@ -749,6 +814,11 @@ struct BuildRoadToolbarWindow : Window { return ES_NOT_HANDLED; } + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + if (this->IsWidgetLowered(WID_ROT_BUILD_WAYPOINT)) CheckRedrawRoadWaypointCoverage(this); + } + /** * Handler for global hotkeys of the BuildRoadToolbarWindow. * @param hotkey Hotkey @@ -797,6 +867,7 @@ struct BuildRoadToolbarWindow : Window { Hotkey('6', "bus_station", WID_ROT_BUS_STATION), Hotkey('7', "truck_station", WID_ROT_TRUCK_STATION), Hotkey('8', "oneway", WID_ROT_ONE_WAY), + Hotkey('9', "waypoint", WID_ROT_BUILD_WAYPOINT), Hotkey('B', "bridge", WID_ROT_BUILD_BRIDGE), Hotkey('T', "tunnel", WID_ROT_BUILD_TUNNEL), Hotkey('R', "remove", WID_ROT_REMOVE), @@ -811,6 +882,7 @@ struct BuildRoadToolbarWindow : Window { Hotkey('5', "depot", WID_ROT_DEPOT), Hotkey('6', "bus_station", WID_ROT_BUS_STATION), Hotkey('7', "truck_station", WID_ROT_TRUCK_STATION), + Hotkey('9', "waypoint", WID_ROT_BUILD_WAYPOINT), Hotkey('B', "bridge", WID_ROT_BUILD_BRIDGE), Hotkey('T', "tunnel", WID_ROT_BUILD_TUNNEL), Hotkey('R', "remove", WID_ROT_REMOVE), @@ -837,6 +909,8 @@ static constexpr NWidgetPart _nested_build_road_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUS_STATION), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_BUS_STATION, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_WAYPOINT), + SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRUCK_STATION), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(), @@ -878,6 +952,8 @@ static constexpr NWidgetPart _nested_build_tramway_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEPOT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_WAYPOINT), + SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUS_STATION), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_BUS_STATION, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRUCK_STATION), diff --git a/src/road_map.cpp b/src/road_map.cpp index d5642ff48c..66fe49010f 100644 --- a/src/road_map.cpp +++ b/src/road_map.cpp @@ -44,7 +44,7 @@ RoadBits GetAnyRoadBits(Tile tile, RoadTramType rtt, bool straight_tunnel_bridge } case MP_STATION: - if (!IsRoadStopTile(tile)) return ROAD_NONE; + if (!IsAnyRoadStopTile(tile)) return ROAD_NONE; if (IsDriveThroughStopTile(tile)) return (GetRoadStopDir(tile) == DIAGDIR_NE) ? ROAD_X : ROAD_Y; return DiagDirToRoadBits(GetRoadStopDir(tile)); diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 734059d580..add521c024 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1328,7 +1328,7 @@ again: v->tile != tile) { /* So, keep 'our' state */ dir = (Trackdir)v->state; - } else if (IsRoadStop(v->tile)) { + } else if (IsStationRoadStop(v->tile)) { /* We're not continuing our drive through road stop, so leave. */ RoadStop::GetByTile(v->tile, GetRoadStopType(v->tile))->Leave(v); } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 371b03dd4e..e2302d428d 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -907,7 +907,7 @@ bool AfterLoadGame() } } - if (IsSavegameVersionBeforeOrAt(SLV_VEHICLE_ECONOMY_AGE)) { + if (IsSavegameVersionBefore(SLV_ROAD_WAYPOINTS)) { /* Expansion of station type field in m6 */ for (auto t : Map::Iterate()) { if (IsTileType(t, MP_STATION)) { @@ -1091,7 +1091,7 @@ bool AfterLoadGame() break; case MP_STATION: - if (IsRoadStop(t)) SB(t.m7(), 6, 2, 1); + if (IsStationRoadStop(t)) SB(t.m7(), 6, 2, 1); break; case MP_TUNNELBRIDGE: @@ -1145,7 +1145,7 @@ bool AfterLoadGame() break; case MP_STATION: - if (!IsRoadStop(t)) break; + if (!IsStationRoadStop(t)) break; if (fix_roadtypes) SB(t.m7(), 6, 2, (RoadTypes)GB(t.m3(), 0, 3)); SB(t.m7(), 0, 5, HasBit(t.m6(), 2) ? OWNER_TOWN : GetTileOwner(t)); @@ -1296,7 +1296,7 @@ bool AfterLoadGame() has_road = true; break; case MP_STATION: - has_road = IsRoadStop(t); + has_road = IsAnyRoadStop(t); break; case MP_TUNNELBRIDGE: has_road = GetTunnelBridgeTransportType(t) == TRANSPORT_ROAD; diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 50a247da54..136b469f8b 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -151,7 +151,8 @@ void AfterLoadCompanyStats() break; case STATION_BUS: - case STATION_TRUCK: { + case STATION_TRUCK: + case STATION_ROADWAYPOINT: { /* Iterate all present road types as each can have a different owner. */ for (RoadTramType rtt : _roadtramtypes) { RoadType rt = GetRoadType(tile, rtt); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 05ffac1c04..722a8f98d2 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -379,6 +379,8 @@ enum SaveLoadVersion : uint16_t { SLV_SCRIPT_RANDOMIZER, ///< 333 PR#12063 v14.0-RC1 Save script randomizers. SLV_VEHICLE_ECONOMY_AGE, ///< 334 PR#12141 v14.0 Add vehicle age in economy year, for profit stats minimum age + SLV_ROAD_WAYPOINTS, ///< 335 PR#12572 Road waypoints + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 0db9509e53..b7e7c1c74d 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -655,6 +655,10 @@ public: SLE_CONDVAR(Waypoint, train_station.tile, SLE_UINT32, SLV_124, SL_MAX_VERSION), SLE_CONDVAR(Waypoint, train_station.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_124, SL_MAX_VERSION), SLE_CONDVAR(Waypoint, train_station.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_124, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, waypoint_flags, SLE_UINT16, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, road_waypoint_area.tile, SLE_UINT32, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, road_waypoint_area.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, road_waypoint_area.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), }; inline const static SaveLoadCompatTable compat_description = _station_waypoint_sl_compat; diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index bff546e8ce..25b7fc2631 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -54,7 +54,7 @@ if (!::IsValidTile(tile)) return false; if (!IsRoadTypeAvailable(GetCurrentRoadType())) return false; - return ::IsRoadStopTile(tile) && HasBit(::GetPresentRoadTypes(tile), (::RoadType)GetCurrentRoadType()); + return ::IsStationRoadStopTile(tile) && HasBit(::GetPresentRoadTypes(tile), (::RoadType)GetCurrentRoadType()); } /* static */ bool ScriptRoad::IsDriveThroughRoadStationTile(TileIndex tile) @@ -604,7 +604,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD EnforceCompanyModeValid(false); EnforcePrecondition(false, ::IsValidTile(tile)); EnforcePrecondition(false, IsTileType(tile, MP_STATION)); - EnforcePrecondition(false, IsRoadStop(tile)); + EnforcePrecondition(false, IsStationRoadStop(tile)); return ScriptObject::Command::Do(tile, 1, 1, GetRoadStopType(tile), false); } diff --git a/src/station.cpp b/src/station.cpp index efb767d50b..e0dab59067 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -327,13 +327,15 @@ static uint GetTileCatchmentRadius(TileIndex tile, const Station *st) default: NOT_REACHED(); case STATION_BUOY: - case STATION_WAYPOINT: return CA_NONE; + case STATION_WAYPOINT: + case STATION_ROADWAYPOINT: return CA_NONE; } } else { switch (GetStationType(tile)) { default: return CA_UNMODIFIED; case STATION_BUOY: - case STATION_WAYPOINT: return CA_NONE; + case STATION_WAYPOINT: + case STATION_ROADWAYPOINT: return CA_NONE; } } } diff --git a/src/station_base.h b/src/station_base.h index 12ed87355a..94b9f00943 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -511,7 +511,7 @@ public: inline bool TileBelongsToRoadStop(TileIndex tile) const { - return IsRoadStopTile(tile) && GetStationIndex(tile) == this->index; + return IsStationRoadStopTile(tile) && GetStationIndex(tile) == this->index; } inline bool TileBelongsToAirport(TileIndex tile) const diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 615e680723..0539660f80 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -111,10 +111,11 @@ bool IsHangar(Tile t) * @param closest_station the closest owned station found so far * @param company the company whose stations to look for * @param st to 'return' the found station + * @param filter Filter function * @return Succeeded command (if zero or one station found) or failed command (for two or more stations found). */ -template -CommandCost GetStationAround(TileArea ta, StationID closest_station, CompanyID company, T **st) +template +CommandCost GetStationAround(TileArea ta, StationID closest_station, CompanyID company, T **st, F filter) { ta.Expand(1); @@ -122,7 +123,7 @@ CommandCost GetStationAround(TileArea ta, StationID closest_station, CompanyID c for (TileIndex tile_cur : ta) { if (IsTileType(tile_cur, MP_STATION)) { StationID t = GetStationIndex(tile_cur); - if (!T::IsValidID(t) || Station::Get(t)->owner != company) continue; + if (!T::IsValidID(t) || T::Get(t)->owner != company || !filter(T::Get(t))) continue; if (closest_station == INVALID_STATION) { closest_station = t; } else if (closest_station != t) { @@ -959,13 +960,13 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_ * @param flags Operation to perform. * @param invalid_dirs Prohibited directions (set of DiagDirections). * @param is_drive_through True if trying to build a drive-through station. - * @param is_truck_stop True when building a truck stop, false otherwise. + * @param station_type Station type (bus, truck or road waypoint). * @param axis Axis of a drive-through road stop. * @param station StationID to be queried and returned if available. - * @param rt Road type to build. + * @param rt Road type to build, may be INVALID_ROADTYPE if an existing road is required. * @return The cost in case of success, or an error code if it failed. */ -static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, bool is_truck_stop, Axis axis, StationID *station, RoadType rt) +CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt) { CommandCost cost(EXPENSES_CONSTRUCTION); @@ -977,10 +978,10 @@ static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoC * Station points to INVALID_STATION if we can build on any station. * Or it points to a station if we're only allowed to build on exactly that station. */ if (station != nullptr && IsTileType(cur_tile, MP_STATION)) { - if (!IsRoadStop(cur_tile)) { + if (!IsAnyRoadStop(cur_tile)) { return ClearTile_Station(cur_tile, DC_AUTO); // Get error message. } else { - if (is_truck_stop != IsTruckStop(cur_tile) || + if (station_type != GetStationType(cur_tile) || is_drive_through != IsDriveThroughStopTile(cur_tile)) { return ClearTile_Station(cur_tile, DC_AUTO); // Get error message. } @@ -1027,7 +1028,7 @@ static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoC } uint num_pieces = CountBits(GetRoadBits(cur_tile, RTT_ROAD)); - if (RoadTypeIsRoad(rt) && !HasPowerOnRoad(rt, road_rt)) return_cmd_error(STR_ERROR_NO_SUITABLE_ROAD); + if (rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt) && !HasPowerOnRoad(rt, road_rt)) return_cmd_error(STR_ERROR_NO_SUITABLE_ROAD); if (GetDisallowedRoadDirections(cur_tile) != DRD_NONE && road_owner != OWNER_TOWN) { ret = CheckOwnership(road_owner); @@ -1035,7 +1036,7 @@ static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoC } cost.AddCost(RoadBuildCost(road_rt) * (2 - num_pieces)); - } else if (RoadTypeIsRoad(rt)) { + } else if (rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt)) { cost.AddCost(RoadBuildCost(rt) * 2); } @@ -1053,12 +1054,14 @@ static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoC } uint num_pieces = CountBits(GetRoadBits(cur_tile, RTT_TRAM)); - if (RoadTypeIsTram(rt) && !HasPowerOnRoad(rt, tram_rt)) return_cmd_error(STR_ERROR_NO_SUITABLE_ROAD); + if (rt != INVALID_ROADTYPE && RoadTypeIsTram(rt) && !HasPowerOnRoad(rt, tram_rt)) return_cmd_error(STR_ERROR_NO_SUITABLE_ROAD); cost.AddCost(RoadBuildCost(tram_rt) * (2 - num_pieces)); - } else if (RoadTypeIsTram(rt)) { + } else if (rt != INVALID_ROADTYPE && RoadTypeIsTram(rt)) { cost.AddCost(RoadBuildCost(rt) * 2); } + } else if (rt == INVALID_ROADTYPE) { + return_cmd_error(STR_ERROR_THERE_IS_NO_ROAD); } else { ret = Command::Do(flags, cur_tile); if (ret.Failed()) return ret; @@ -1149,6 +1152,7 @@ void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const Stat * Find a nearby station that joins this station. * @tparam T the class to find a station for * @tparam error_message the error message when building a station on top of others + * @tparam F the filter functor type * @param existing_station an existing station we build over * @param station_to_join the station to join to * @param adjacent whether adjacent stations are allowed @@ -1156,8 +1160,8 @@ void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const Stat * @param st 'return' pointer for the found station * @return command cost with the error or 'okay' */ -template -CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st) +template +CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st, F filter) { assert(*st == nullptr); bool check_surrounding = true; @@ -1171,7 +1175,8 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station } else { /* Extend the current station, and don't check whether it will * be near any other stations. */ - *st = T::GetIfValid(existing_station); + T *candidate = T::GetIfValid(existing_station); + if (candidate != nullptr && filter(candidate)) *st = candidate; check_surrounding = (*st == nullptr); } } else { @@ -1183,7 +1188,7 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station if (check_surrounding) { /* Make sure there is no more than one other station around us that is owned by us. */ - CommandCost ret = GetStationAround(ta, existing_station, _current_company, st); + CommandCost ret = GetStationAround(ta, existing_station, _current_company, st, filter); if (ret.Failed()) return ret; } @@ -1204,7 +1209,7 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station */ static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st) { - return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st); + return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st, [](const Station *) -> bool { return true; }); } /** @@ -1214,11 +1219,16 @@ static CommandCost FindJoiningStation(StationID existing_station, StationID stat * @param adjacent whether adjacent waypoints are allowed * @param ta the area of the newly build waypoint * @param wp 'return' pointer for the found waypoint + * @param is_road whether to find a road waypoint * @return command cost with the error or 'okay' */ -CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_to_join, bool adjacent, TileArea ta, Waypoint **wp) +CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road) { - return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp); + if (is_road) { + return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp, [](const Waypoint *wp) -> bool { return HasBit(wp->waypoint_flags, WPF_ROAD); }); + } else { + return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp, [](const Waypoint *wp) -> bool { return !HasBit(wp->waypoint_flags, WPF_ROAD); }); + } } /** @@ -1605,6 +1615,16 @@ static void MakeShipStationAreaSmaller(Station *st) UpdateStationDockingTiles(st); } +static bool TileBelongsToRoadWaypointStation(BaseStation *st, TileIndex tile) +{ + return IsRoadWaypointTile(tile) && GetStationIndex(tile) == st->index; +} + +void MakeRoadWaypointStationAreaSmaller(BaseStation *st, TileArea &road_waypoint_area) +{ + road_waypoint_area = MakeStationAreaSmaller(st, road_waypoint_area, TileBelongsToRoadWaypointStation); +} + /** * Remove a number of tiles from any rail station within the area. * @param ta the area to clear station tile from. @@ -1864,6 +1884,7 @@ static RoadStop **FindRoadStopSpot(bool truck_station, Station *st) } static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index = -1); +CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index = -1); /** * Find a nearby station that joins this road stop. @@ -1876,7 +1897,7 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags, int repla */ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID station_to_join, bool adjacent, TileArea ta, Station **st) { - return FindJoiningBaseStation(existing_stop, station_to_join, adjacent, ta, st); + return FindJoiningBaseStation(existing_stop, station_to_join, adjacent, ta, st, [](const Station *) -> bool { return true; }); } /** @@ -1884,15 +1905,15 @@ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID statio * @param tile_area Area to check. * @param flags Operation to perform. * @param is_drive_through True if trying to build a drive-through station. - * @param is_truck_stop True when building a truck stop, false otherwise. + * @param station_type Station type (bus, truck or road waypoint). * @param axis Axis of a drive-through road stop. * @param ddir Entrance direction (#DiagDirection) for normal stops. Converted to the axis for drive-through stops. * @param station StationID to be queried and returned if available. - * @param rt Road type to build. + * @param rt Road type to build, may be INVALID_ROADTYPE if an existing road is required. * @param unit_cost The cost to build one road stop of the current type. * @return The cost in case of success, or an error code if it failed. */ -static CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlag flags, bool is_drive_through, bool is_truck_stop, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost) +CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlag flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost) { uint invalid_dirs = 0; if (is_drive_through) { @@ -1906,10 +1927,10 @@ static CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlag flags int allowed_z = -1; CommandCost cost(EXPENSES_CONSTRUCTION); for (TileIndex cur_tile : tile_area) { - CommandCost ret = CheckFlatLandRoadStop(cur_tile, allowed_z, flags, invalid_dirs, is_drive_through, is_truck_stop, axis, est, rt); + CommandCost ret = CheckFlatLandRoadStop(cur_tile, allowed_z, flags, invalid_dirs, is_drive_through, station_type, axis, est, rt); if (ret.Failed()) return ret; - bool is_preexisting_roadstop = IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile); + bool is_preexisting_roadstop = IsTileType(cur_tile, MP_STATION) && IsAnyRoadStop(cur_tile); /* Only add costs if a stop doesn't already exist in the location */ if (!is_preexisting_roadstop) { @@ -1985,7 +2006,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, unit_cost = _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]; } StationID est = INVALID_STATION; - CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop, axis, ddir, &est, rt, unit_cost); + CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop ? STATION_TRUCK : STATION_BUS, axis, ddir, &est, rt, unit_cost); if (cost.Failed()) return cost; Station *st = nullptr; @@ -2021,7 +2042,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, Owner road_owner = road_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_ROAD) : _current_company; Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company; - if (IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile)) { + if (IsTileType(cur_tile, MP_STATION) && IsStationRoadStop(cur_tile)) { RemoveRoadStop(cur_tile, flags, specindex); } @@ -2059,7 +2080,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, if (road_rt == INVALID_ROADTYPE && RoadTypeIsRoad(rt)) road_rt = rt; if (tram_rt == INVALID_ROADTYPE && RoadTypeIsTram(rt)) tram_rt = rt; - MakeDriveThroughRoadStop(cur_tile, st->owner, road_owner, tram_owner, st->index, rs_type, road_rt, tram_rt, axis); + MakeDriveThroughRoadStop(cur_tile, st->owner, road_owner, tram_owner, st->index, (rs_type == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK), road_rt, tram_rt, axis); road_stop->MakeDriveThrough(); } else { if (road_rt == INVALID_ROADTYPE && RoadTypeIsRoad(rt)) road_rt = rt; @@ -2219,6 +2240,132 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags, int repla return CommandCost(EXPENSES_CONSTRUCTION, spec != nullptr ? spec->GetClearCost(category) : _price[category]); } +/** + * Remove a road waypoint + * @param tile TileIndex been queried + * @param flags operation to perform + * @param replacement_spec_index replacement spec index to avoid deallocating, if < 0, tile is not being replaced + * @return cost or failure of operation + */ +CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index) +{ + Waypoint *wp = Waypoint::GetByTile(tile); + + if (_current_company != OWNER_WATER) { + CommandCost ret = CheckOwnership(wp->owner); + if (ret.Failed()) return ret; + } + + /* don't do the check for drive-through road stops when company bankrupts */ + if (!(flags & DC_BANKRUPT)) { + CommandCost ret = EnsureNoVehicleOnGround(tile); + if (ret.Failed()) return ret; + } + + const RoadStopSpec *spec = GetRoadStopSpec(tile); + + if (flags & DC_EXEC) { + /* Update company infrastructure counts. */ + for (RoadTramType rtt : _roadtramtypes) { + RoadType rt = GetRoadType(tile, rtt); + UpdateCompanyRoadInfrastructure(rt, GetRoadOwner(tile, rtt), -static_cast(ROAD_STOP_TRACKBIT_FACTOR)); + } + + Company::Get(wp->owner)->infrastructure.station--; + DirtyCompanyInfrastructureWindows(wp->owner); + + DeleteAnimatedTile(tile); + + uint specindex = GetCustomRoadStopSpecIndex(tile); + + DeleteNewGRFInspectWindow(GSF_ROADSTOPS, tile.base()); + + DoClearSquare(tile); + + wp->rect.AfterRemoveTile(wp, tile); + + wp->RemoveRoadStopTileData(tile); + if ((int)specindex != replacement_spec_index) DeallocateSpecFromRoadStop(wp, specindex); + + if (replacement_spec_index < 0) { + MakeRoadWaypointStationAreaSmaller(wp, wp->road_waypoint_area); + + UpdateStationSignCoord(wp); + + /* if we deleted the whole waypoint, delete the road facility. */ + if (wp->road_waypoint_area.tile == INVALID_TILE) { + wp->facilities &= ~(FACIL_BUS_STOP | FACIL_TRUCK_STOP); + SetWindowWidgetDirty(WC_STATION_VIEW, wp->index, WID_SV_ROADVEHS); + wp->UpdateVirtCoord(); + DeleteStationIfEmpty(wp); + } + } + } + + return CommandCost(EXPENSES_CONSTRUCTION, spec != nullptr ? spec->GetClearCost(PR_CLEAR_STATION_TRUCK) : _price[PR_CLEAR_STATION_TRUCK]); +} + +/** + * Remove a tile area of road stop or road waypoints + * @param flags operation to perform + * @param roadstop_area tile area of road stop or road waypoint tiles to remove + * @param station_type station type to remove + * @param remove_road Remove roads of drive-through stops? + * @return the cost of this operation or an error + */ +static CommandCost RemoveGenericRoadStop(DoCommandFlag flags, const TileArea &roadstop_area, StationType station_type, bool remove_road) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost last_error(STR_ERROR_THERE_IS_NO_STATION); + bool had_success = false; + + for (TileIndex cur_tile : roadstop_area) { + /* Make sure the specified tile is a road stop of the correct type */ + if (!IsTileType(cur_tile, MP_STATION) || !IsAnyRoadStop(cur_tile) || GetStationType(cur_tile) != station_type) continue; + + /* Save information on to-be-restored roads before the stop is removed. */ + RoadBits road_bits = ROAD_NONE; + RoadType road_type[] = { INVALID_ROADTYPE, INVALID_ROADTYPE }; + Owner road_owner[] = { OWNER_NONE, OWNER_NONE }; + if (IsDriveThroughStopTile(cur_tile)) { + for (RoadTramType rtt : _roadtramtypes) { + road_type[rtt] = GetRoadType(cur_tile, rtt); + if (road_type[rtt] == INVALID_ROADTYPE) continue; + road_owner[rtt] = GetRoadOwner(cur_tile, rtt); + /* If we don't want to preserve our roads then restore only roads of others. */ + if (remove_road && road_owner[rtt] == _current_company) road_type[rtt] = INVALID_ROADTYPE; + } + road_bits = AxisToRoadBits(DiagDirToAxis(GetRoadStopDir(cur_tile))); + } + + CommandCost ret; + if (station_type == STATION_ROADWAYPOINT) { + ret = RemoveRoadWaypointStop(cur_tile, flags); + } else { + ret = RemoveRoadStop(cur_tile, flags); + } + if (ret.Failed()) { + last_error = ret; + continue; + } + cost.AddCost(ret); + had_success = true; + + /* Restore roads. */ + if ((flags & DC_EXEC) && (road_type[RTT_ROAD] != INVALID_ROADTYPE || road_type[RTT_TRAM] != INVALID_ROADTYPE)) { + MakeRoadNormal(cur_tile, road_bits, road_type[RTT_ROAD], road_type[RTT_TRAM], ClosestTownFromTile(cur_tile, UINT_MAX)->index, + road_owner[RTT_ROAD], road_owner[RTT_TRAM]); + + /* Update company infrastructure counts. */ + int count = CountBits(road_bits); + UpdateCompanyRoadInfrastructure(road_type[RTT_ROAD], road_owner[RTT_ROAD], count); + UpdateCompanyRoadInfrastructure(road_type[RTT_TRAM], road_owner[RTT_TRAM], count); + } + } + + return had_success ? cost : last_error; +} + /** * Remove bus or truck stops. * @param flags Operation to perform. @@ -2241,50 +2388,24 @@ CommandCost CmdRemoveRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width TileArea roadstop_area(tile, width, height); - CommandCost cost(EXPENSES_CONSTRUCTION); - CommandCost last_error(STR_ERROR_THERE_IS_NO_STATION); - bool had_success = false; + return RemoveGenericRoadStop(flags, roadstop_area, stop_type == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK, remove_road); +} - for (TileIndex cur_tile : roadstop_area) { - /* Make sure the specified tile is a road stop of the correct type */ - if (!IsTileType(cur_tile, MP_STATION) || !IsRoadStop(cur_tile) || GetRoadStopType(cur_tile) != stop_type) continue; +/** + * Remove road waypoints. + * @param flags operation to perform + * @param start tile of road waypoint piece to remove + * @param end other edge of the rect to remove + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveFromRoadWaypoint(DoCommandFlag flags, TileIndex start, TileIndex end) +{ + if (end == 0) end = start; + if (start >= Map::Size() || end >= Map::Size()) return CMD_ERROR; - /* Save information on to-be-restored roads before the stop is removed. */ - RoadBits road_bits = ROAD_NONE; - RoadType road_type[] = { INVALID_ROADTYPE, INVALID_ROADTYPE }; - Owner road_owner[] = { OWNER_NONE, OWNER_NONE }; - if (IsDriveThroughStopTile(cur_tile)) { - for (RoadTramType rtt : _roadtramtypes) { - road_type[rtt] = GetRoadType(cur_tile, rtt); - if (road_type[rtt] == INVALID_ROADTYPE) continue; - road_owner[rtt] = GetRoadOwner(cur_tile, rtt); - /* If we don't want to preserve our roads then restore only roads of others. */ - if (remove_road && road_owner[rtt] == _current_company) road_type[rtt] = INVALID_ROADTYPE; - } - road_bits = AxisToRoadBits(DiagDirToAxis(GetRoadStopDir(cur_tile))); - } + TileArea roadstop_area(start, end); - CommandCost ret = RemoveRoadStop(cur_tile, flags); - if (ret.Failed()) { - last_error = ret; - continue; - } - cost.AddCost(ret); - had_success = true; - - /* Restore roads. */ - if ((flags & DC_EXEC) && (road_type[RTT_ROAD] != INVALID_ROADTYPE || road_type[RTT_TRAM] != INVALID_ROADTYPE)) { - MakeRoadNormal(cur_tile, road_bits, road_type[RTT_ROAD], road_type[RTT_TRAM], ClosestTownFromTile(cur_tile, UINT_MAX)->index, - road_owner[RTT_ROAD], road_owner[RTT_TRAM]); - - /* Update company infrastructure counts. */ - int count = CountBits(road_bits); - UpdateCompanyRoadInfrastructure(road_type[RTT_ROAD], road_owner[RTT_ROAD], count); - UpdateCompanyRoadInfrastructure(road_type[RTT_TRAM], road_owner[RTT_TRAM], count); - } - } - - return had_success ? cost : last_error; + return RemoveGenericRoadStop(flags, roadstop_area, STATION_ROADWAYPOINT, false); } /** @@ -3154,7 +3275,7 @@ draw_default_foundation: draw_ground = true; } - if (draw_ground && !IsRoadStop(ti->tile)) { + if (draw_ground && !IsStationRoadStop(ti->tile)) { SpriteID image = t->ground.sprite; PaletteID pal = t->ground.pal; RailTrackOffset overlay_offset; @@ -3181,7 +3302,7 @@ draw_default_foundation: if (HasStationRail(ti->tile) && HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); - if (IsRoadStop(ti->tile)) { + if (IsStationRoadStop(ti->tile)) { RoadType road_rt = GetRoadTypeRoad(ti->tile); RoadType tram_rt = GetRoadTypeTram(ti->tile); const RoadTypeInfo *road_rti = road_rt == INVALID_ROADTYPE ? nullptr : GetRoadTypeInfo(road_rt); @@ -3383,7 +3504,7 @@ static void GetTileDesc_Station(TileIndex tile, TileDesc *td) td->owner[0] = GetTileOwner(tile); td->build_date = BaseStation::GetByTile(tile)->build_date; - if (IsRoadStop(tile)) FillTileDescRoadStop(tile, td); + if (IsAnyRoadStop(tile)) FillTileDescRoadStop(tile, td); if (HasStationRail(tile)) FillTileDescRailStation(tile, td); if (IsAirport(tile)) FillTileDescAirport(tile, td); @@ -3407,6 +3528,7 @@ static void GetTileDesc_Station(TileIndex tile, TileDesc *td) case STATION_DOCK: str = STR_LAI_STATION_DESCRIPTION_SHIP_DOCK; break; case STATION_BUOY: str = STR_LAI_STATION_DESCRIPTION_BUOY; break; case STATION_WAYPOINT: str = STR_LAI_STATION_DESCRIPTION_WAYPOINT; break; + case STATION_ROADWAYPOINT: str = STR_LAI_STATION_DESCRIPTION_WAYPOINT; break; } td->str = str; } @@ -3435,7 +3557,7 @@ static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode break; case TRANSPORT_ROAD: - if (IsRoadStop(tile)) { + if (IsAnyRoadStop(tile)) { RoadTramType rtt = (RoadTramType)sub_mode; if (!HasTileRoadType(tile, rtt)) break; @@ -3493,7 +3615,7 @@ static void AnimateTile_Station(TileIndex tile) return; } - if (IsRoadStopTile(tile)) { + if (IsAnyRoadStopTile(tile)) { AnimateRoadStopTile(tile); return; } @@ -3553,7 +3675,7 @@ static VehicleEnterTileStatus VehicleEnter_Station(Vehicle *v, TileIndex tile, i } else if (v->type == VEH_ROAD) { RoadVehicle *rv = RoadVehicle::From(v); if (rv->state < RVSB_IN_ROAD_STOP && !IsReversingRoadTrackdir((Trackdir)rv->state) && rv->frame == 0) { - if (IsRoadStop(tile) && rv->IsFrontEngine()) { + if (IsStationRoadStop(tile) && rv->IsFrontEngine()) { /* Attempt to allocate a parking bay in a road stop */ return RoadStop::GetByTile(tile, GetRoadStopType(tile))->Enter(rv) ? VETSB_CONTINUE : VETSB_CANNOT_ENTER; } @@ -4379,7 +4501,7 @@ void DeleteOilRig(TileIndex tile) static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_owner) { - if (IsRoadStopTile(tile)) { + if (IsAnyRoadStopTile(tile)) { for (RoadTramType rtt : _roadtramtypes) { /* Update all roadtypes, no matter if they are present */ if (GetRoadOwner(tile, rtt) == old_owner) { @@ -4416,6 +4538,7 @@ static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_o case STATION_BUS: case STATION_TRUCK: + case STATION_ROADWAYPOINT: /* Road stops were already handled above. */ break; @@ -4443,7 +4566,11 @@ static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_o } else { if (IsDriveThroughStopTile(tile)) { /* Remove the drive-through road stop */ - Command::Do(DC_EXEC | DC_BANKRUPT, tile, 1, 1, (GetStationType(tile) == STATION_TRUCK) ? ROADSTOP_TRUCK : ROADSTOP_BUS, false); + if (IsRoadWaypoint(tile)) { + Command::Do(DC_EXEC | DC_BANKRUPT, tile, tile); + } else { + Command::Do(DC_EXEC | DC_BANKRUPT, tile, 1, 1, (GetStationType(tile) == STATION_TRUCK) ? ROADSTOP_TRUCK : ROADSTOP_BUS, false); + } assert(IsTileType(tile, MP_ROAD)); /* Change owner of tile and all roadtypes */ ChangeTileOwner(tile, old_owner, new_owner); @@ -4510,6 +4637,7 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) case STATION_AIRPORT: return_cmd_error(STR_ERROR_MUST_DEMOLISH_AIRPORT_FIRST); case STATION_TRUCK: return_cmd_error(HasTileRoadType(tile, RTT_TRAM) ? STR_ERROR_MUST_DEMOLISH_CARGO_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_TRUCK_STATION_FIRST); case STATION_BUS: return_cmd_error(HasTileRoadType(tile, RTT_TRAM) ? STR_ERROR_MUST_DEMOLISH_PASSENGER_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_BUS_STATION_FIRST); + case STATION_ROADWAYPOINT: return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); case STATION_BUOY: return_cmd_error(STR_ERROR_BUOY_IN_THE_WAY); case STATION_DOCK: return_cmd_error(STR_ERROR_MUST_DEMOLISH_DOCK_FIRST); case STATION_OILRIG: @@ -4529,6 +4657,12 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) if (remove_road.Failed()) return remove_road; } return RemoveRoadStop(tile, flags); + case STATION_ROADWAYPOINT: + if (IsDriveThroughStopTile(tile)) { + CommandCost remove_road = CanRemoveRoadWithStop(tile, flags); + if (remove_road.Failed()) return remove_road; + } + return RemoveRoadWaypointStop(tile, flags); case STATION_BUOY: return RemoveBuoy(tile, flags); case STATION_DOCK: return RemoveDock(tile, flags); default: break; diff --git a/src/station_gui.cpp b/src/station_gui.cpp index 31194b6362..4adf21e004 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -47,17 +47,22 @@ struct StationTypeFilter static bool IsValidID(StationID id) { return Station::IsValidID(id); } static bool IsValidBaseStation(const BaseStation *st) { return Station::IsExpected(st); } + static bool IsAcceptableWaypointTile(TileIndex) { return false; } static constexpr bool IsWaypoint() { return false; } }; -struct WaypointTypeFilter +template +struct GenericWaypointTypeFilter { using StationType = Waypoint; - static bool IsValidID(StationID id) { return Waypoint::IsValidID(id); } - static bool IsValidBaseStation(const BaseStation *st) { return Waypoint::IsExpected(st); } + static bool IsValidID(StationID id) { return Waypoint::IsValidID(id) && HasBit(Waypoint::Get(id)->waypoint_flags, WPF_ROAD) == ROAD; } + static bool IsValidBaseStation(const BaseStation *st) { return Waypoint::IsExpected(st) && HasBit(Waypoint::From(st)->waypoint_flags, WPF_ROAD) == ROAD; } + static bool IsAcceptableWaypointTile(TileIndex tile) { return IsTileType(tile, TILE_TYPE); } static constexpr bool IsWaypoint() { return true; } }; +using RailWaypointTypeFilter = GenericWaypointTypeFilter; +using RoadWaypointTypeFilter = GenericWaypointTypeFilter; /** * Calculates and draws the accepted or supplied cargo around the selected tile(s) @@ -125,8 +130,8 @@ void FindStationsAroundSelection() uint x = TileX(location.tile); uint y = TileY(location.tile); - /* Waypoints can only be built on existing rail tiles, so don't extend area if not highlighting a rail tile. */ - int max_c = T::IsWaypoint() && !IsTileType(location.tile, MP_RAILWAY) ? 0 : 1; + /* Waypoints can only be built on existing rail/road tiles, so don't extend area if not highlighting a rail tile. */ + int max_c = T::IsWaypoint() && !T::IsAcceptableWaypointTile(location.tile) ? 0 : 1; TileArea ta(TileXY(std::max(0, x - max_c), std::max(0, y - max_c)), TileXY(std::min(Map::MaxX(), x + location.w + max_c), std::min(Map::MaxY(), y + location.h + max_c))); typename T::StationType *adjacent = nullptr; @@ -171,7 +176,8 @@ void CheckRedrawStationCoverage(const Window *w) } } -void CheckRedrawWaypointCoverage(const Window *) +template +void CheckRedrawWaypointCoverage() { /* Test if ctrl state changed */ static bool _last_ctrl_pressed; @@ -184,11 +190,21 @@ void CheckRedrawWaypointCoverage(const Window *) _thd.dirty &= ~1; if (_thd.drawstyle == HT_RECT) { - FindStationsAroundSelection(); + FindStationsAroundSelection(); } } } +void CheckRedrawRailWaypointCoverage(const Window *) +{ + CheckRedrawWaypointCoverage(); +} + +void CheckRedrawRoadWaypointCoverage(const Window *) +{ + CheckRedrawWaypointCoverage(); +} + /** * Draw small boxes of cargo amount and ratings data at the given * coordinates. If amount exceeds 576 units, it is shown 'full', same @@ -2488,11 +2504,21 @@ void ShowSelectStationIfNeeded(TileArea ta, StationPickerCmdProc proc) } /** - * Show the waypoint selection window when needed. If not, build the waypoint. + * Show the rail waypoint selection window when needed. If not, build the waypoint. * @param ta Area to build the waypoint in * @param proc Function called to execute the build command. */ -void ShowSelectWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc) +void ShowSelectRailWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc) { - ShowSelectBaseStationIfNeeded(ta, std::move(proc)); + ShowSelectBaseStationIfNeeded(ta, std::move(proc)); +} + +/** + * Show the road waypoint selection window when needed. If not, build the waypoint. + * @param ta Area to build the waypoint in + * @param proc Function called to execute the build command. + */ +void ShowSelectRoadWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc) +{ + ShowSelectBaseStationIfNeeded(ta, std::move(proc)); } diff --git a/src/station_gui.h b/src/station_gui.h index 009bcd7bb9..9b142d02b6 100644 --- a/src/station_gui.h +++ b/src/station_gui.h @@ -25,11 +25,13 @@ enum StationCoverageType { int DrawStationCoverageAreaText(int left, int right, int top, StationCoverageType sct, int rad, bool supplies); void CheckRedrawStationCoverage(const Window *w); -void CheckRedrawWaypointCoverage(const Window *w); +void CheckRedrawRailWaypointCoverage(const Window *w); +void CheckRedrawRoadWaypointCoverage(const Window *w); using StationPickerCmdProc = std::function; void ShowSelectStationIfNeeded(TileArea ta, StationPickerCmdProc proc); -void ShowSelectWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc); +void ShowSelectRailWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc); +void ShowSelectRoadWaypointIfNeeded(TileArea ta, StationPickerCmdProc proc); #endif /* STATION_GUI_H */ diff --git a/src/station_map.h b/src/station_map.h index 42f415e822..b9566dff26 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -193,13 +193,34 @@ inline bool IsBusStop(Tile t) return GetStationType(t) == STATION_BUS; } +/** + * Is the station at \a t a road waypoint? + * @param t Tile to check + * @pre IsTileType(t, MP_STATION) + * @return \c true if station is a road waypoint, \c false otherwise + */ +inline bool IsRoadWaypoint(Tile t) +{ + return GetStationType(t) == STATION_ROADWAYPOINT; +} + +/** + * Is this tile a station tile and a road waypoint? + * @param t the tile to get the information from + * @return true if and only if the tile is a road waypoint + */ +inline bool IsRoadWaypointTile(Tile t) +{ + return IsTileType(t, MP_STATION) && IsRoadWaypoint(t); +} + /** * Is the station at \a t a road station? * @param t Tile to check * @pre IsTileType(t, MP_STATION) - * @return \c true if station at the tile is a bus top or a truck stop, \c false otherwise + * @return \c true if station at the tile is a bus stop or a truck stop, \c false otherwise */ -inline bool IsRoadStop(Tile t) +inline bool IsStationRoadStop(Tile t) { assert(IsTileType(t, MP_STATION)); return IsTruckStop(t) || IsBusStop(t); @@ -208,11 +229,33 @@ inline bool IsRoadStop(Tile t) /** * Is tile \a t a road stop station? * @param t Tile to check - * @return \c true if the tile is a station tile and a road stop + * @return \c true if the tile is a station tile and a station road stop */ -inline bool IsRoadStopTile(Tile t) +inline bool IsStationRoadStopTile(Tile t) { - return IsTileType(t, MP_STATION) && IsRoadStop(t); + return IsTileType(t, MP_STATION) && IsStationRoadStop(t); +} + +/** + * Is the station at \a t a road station? + * @param t Tile to check + * @pre IsTileType(t, MP_STATION) + * @return \c true if station at the tile is a bus stop, truck stop or road waypoint, \c false otherwise + */ +inline bool IsAnyRoadStop(Tile t) +{ + assert(IsTileType(t, MP_STATION)); + return IsTruckStop(t) || IsBusStop(t) || IsRoadWaypoint(t); +} + +/** + * Is tile \a t a road stop station? + * @param t Tile to check + * @return \c true if the tile is a station tile and any road stop type + */ +inline bool IsAnyRoadStopTile(Tile t) +{ + return IsTileType(t, MP_STATION) && IsAnyRoadStop(t); } /** @@ -222,17 +265,17 @@ inline bool IsRoadStopTile(Tile t) */ inline bool IsBayRoadStopTile(Tile t) { - return IsRoadStopTile(t) && GetStationGfx(t) < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; + return IsStationRoadStopTile(t) && GetStationGfx(t) < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; } /** - * Is tile \a t a drive through road stop station? + * Is tile \a t a drive through road stop station or waypoint? * @param t Tile to check * @return \c true if the tile is a station tile and a drive through road stop */ inline bool IsDriveThroughStopTile(Tile t) { - return IsRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; + return IsAnyRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; } StationGfx GetTranslatedAirportTileID(StationGfx gfx); @@ -252,13 +295,13 @@ inline StationGfx GetAirportGfx(Tile t) /** * Gets the direction the road stop entrance points towards. * @param t the tile of the road stop - * @pre IsRoadStopTile(t) + * @pre IsAnyRoadStopTile(t) * @return the direction of the entrance */ inline DiagDirection GetRoadStopDir(Tile t) { StationGfx gfx = GetStationGfx(t); - assert(IsRoadStopTile(t)); + assert(IsAnyRoadStopTile(t)); if (gfx < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET) { return (DiagDirection)(gfx); } else { @@ -555,12 +598,12 @@ inline uint GetCustomStationSpecIndex(Tile t) /** * Is there a custom road stop spec on this tile? * @param t Tile to query - * @pre IsRoadStopTile(t) + * @pre IsAnyRoadStopTile(t) * @return True if this station is part of a newgrf station. */ inline bool IsCustomRoadStopSpecIndex(Tile t) { - assert(IsRoadStopTile(t)); + assert(IsAnyRoadStopTile(t)); return GB(t.m8(), 0, 6) != 0; } @@ -568,23 +611,23 @@ inline bool IsCustomRoadStopSpecIndex(Tile t) * Set the custom road stop spec for this tile. * @param t Tile to set the stationspec of. * @param specindex The new spec. - * @pre IsRoadStopTile(t) + * @pre IsAnyRoadStopTile(t) */ inline void SetCustomRoadStopSpecIndex(Tile t, uint8_t specindex) { - assert(IsRoadStopTile(t)); + assert(IsAnyRoadStopTile(t)); SB(t.m8(), 0, 6, specindex); } /** * Get the custom road stop spec for this tile. * @param t Tile to query - * @pre IsRoadStopTile(t) + * @pre IsAnyRoadStopTile(t) * @return The custom station spec of this tile. */ inline uint GetCustomRoadStopSpecIndex(Tile t) { - assert(IsRoadStopTile(t)); + assert(IsAnyRoadStopTile(t)); return GB(t.m8(), 0, 6); } @@ -699,9 +742,9 @@ inline void MakeRoadStop(Tile t, Owner o, StationID sid, RoadStopType rst, RoadT * @param tram_rt the tram roadtype on this tile * @param a the direction of the roadstop */ -inline void MakeDriveThroughRoadStop(Tile t, Owner station, Owner road, Owner tram, StationID sid, RoadStopType rst, RoadType road_rt, RoadType tram_rt, Axis a) +inline void MakeDriveThroughRoadStop(Tile t, Owner station, Owner road, Owner tram, StationID sid, StationType rst, RoadType road_rt, RoadType tram_rt, Axis a) { - MakeStation(t, station, sid, (rst == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK), GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + a); + MakeStation(t, station, sid, rst, GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + a); SetRoadTypes(t, road_rt, tram_rt); SetRoadOwner(t, RTT_ROAD, road); SetRoadOwner(t, RTT_TRAM, tram); diff --git a/src/station_type.h b/src/station_type.h index 4e6968ac31..54d7939e66 100644 --- a/src/station_type.h +++ b/src/station_type.h @@ -37,6 +37,7 @@ enum StationType { STATION_DOCK, STATION_BUOY, STATION_WAYPOINT, + STATION_ROADWAYPOINT, }; /** Types of RoadStops */ diff --git a/src/table/station_land.h b/src/table/station_land.h index 6e3db1d0d0..b67fbd94fe 100644 --- a/src/table/station_land.h +++ b/src/table/station_land.h @@ -955,6 +955,15 @@ static const DrawTileSprites _station_display_datas_bus[] = { TILE_SPRITE_LINE(SPR_ROAD_PAVED_STRAIGHT_Y, _station_display_datas_0171) }; +static const DrawTileSprites _station_display_datas_road_waypoint[] = { + TILE_SPRITE_LINE(SPR_BUS_STOP_NE_GROUND | (1U << PALETTE_MODIFIER_COLOUR), _station_display_datas_71) + TILE_SPRITE_LINE(SPR_BUS_STOP_SE_GROUND | (1U << PALETTE_MODIFIER_COLOUR), _station_display_datas_72) + TILE_SPRITE_LINE(SPR_BUS_STOP_SW_GROUND | (1U << PALETTE_MODIFIER_COLOUR), _station_display_datas_73) + TILE_SPRITE_LINE(SPR_BUS_STOP_NW_GROUND | (1U << PALETTE_MODIFIER_COLOUR), _station_display_datas_74) + TILE_SPRITE_LINE(SPR_ROAD_PAVED_STRAIGHT_X, _station_display_datas_0170) + TILE_SPRITE_LINE(SPR_ROAD_PAVED_STRAIGHT_Y, _station_display_datas_0171) +}; + static const DrawTileSprites _station_display_datas_oilrig[] = { TILE_SPRITE_LINE(SPR_FLAT_WATER_TILE, _station_display_nothing) }; @@ -999,4 +1008,5 @@ static const DrawTileSprites * const _station_display_datas[] = { _station_display_datas_dock, _station_display_datas_buoy, _station_display_datas_waypoint, + _station_display_datas_road_waypoint, }; diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 2fae67fc47..16eac9d467 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1238,7 +1238,7 @@ static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, con /* If the next tile is a station, allow if it's a road station facing the proper direction. Otherwise return false. */ if (IsTileType(next_tile, MP_STATION)) { /* If the next tile is a road station, allow if it can be entered by the new tunnel/bridge, otherwise disallow. */ - return IsRoadStop(next_tile) && (GetRoadStopDir(next_tile) == ReverseDiagDir(road_dir) || (IsDriveThroughStopTile(next_tile) && GetRoadStopDir(next_tile) == road_dir)); + return IsAnyRoadStop(next_tile) && (GetRoadStopDir(next_tile) == ReverseDiagDir(road_dir) || (IsDriveThroughStopTile(next_tile) && GetRoadStopDir(next_tile) == road_dir)); } /* If the next tile is a road depot, allow if it's facing the right way. */ @@ -1441,7 +1441,7 @@ static inline bool RoadTypesAllowHouseHere(TileIndex t) TileIndex cur_tile = t + ToTileIndexDiff(ptr); if (!IsValidTile(cur_tile)) continue; - if (!(IsTileType(cur_tile, MP_ROAD) || IsRoadStopTile(cur_tile))) continue; + if (!(IsTileType(cur_tile, MP_ROAD) || IsAnyRoadStopTile(cur_tile))) continue; allow = true; RoadType road_rt = GetRoadTypeRoad(cur_tile); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 2e1b82b172..d329b0b09a 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2390,7 +2390,7 @@ void Vehicle::LeaveStation() } if (this->type == VEH_ROAD && !(this->vehstatus & VS_CRASHED)) { /* Trigger road stop animation */ - if (IsRoadStopTile(this->tile)) { + if (IsStationRoadStopTile(this->tile)) { TriggerRoadStopRandomisation(st, this->tile, RSRT_VEH_DEPARTS); TriggerRoadStopAnimation(st, this->tile, SAT_TRAIN_DEPARTS); } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 93efd79c4e..5a5134c4a2 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -3178,7 +3178,7 @@ public: break; case OT_GOTO_WAYPOINT: { - assert(v->type == VEH_TRAIN || v->type == VEH_SHIP); + assert(v->type == VEH_TRAIN || v->type == VEH_ROAD || v->type == VEH_SHIP); SetDParam(0, v->current_order.GetDestination()); str = HasBit(v->vehicle_flags, VF_PATHFINDER_LOST) ? STR_VEHICLE_STATUS_CANNOT_REACH_WAYPOINT_VEL : STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL; SetDParam(1, PackVelocity(v->GetDisplaySpeed(), v->type)); diff --git a/src/viewport_type.h b/src/viewport_type.h index 4a433387dd..0fde051f38 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -132,8 +132,10 @@ enum ViewportDragDropSelectionProcess { DDSP_PLACE_ROAD_X_DIR, ///< Road placement (X axis) DDSP_PLACE_ROAD_Y_DIR, ///< Road placement (Y axis) DDSP_PLACE_AUTOROAD, ///< Road placement (auto) + DDSP_BUILD_ROAD_WAYPOINT, ///< Road stop placement (waypoint) DDSP_BUILD_BUSSTOP, ///< Road stop placement (buses) DDSP_BUILD_TRUCKSTOP, ///< Road stop placement (trucks) + DDSP_REMOVE_ROAD_WAYPOINT, ///< Road stop removal (waypoint) DDSP_REMOVE_BUSSTOP, ///< Road stop removal (buses) DDSP_REMOVE_TRUCKSTOP, ///< Road stop removal (trucks) DDSP_CONVERT_ROAD, ///< Road conversion diff --git a/src/waypoint.cpp b/src/waypoint.cpp index db1b36b1ec..b08a971bad 100644 --- a/src/waypoint.cpp +++ b/src/waypoint.cpp @@ -38,6 +38,10 @@ void Waypoint::GetTileArea(TileArea *ta, StationType type) const *ta = this->train_station; return; + case STATION_ROADWAYPOINT: + *ta = this->road_waypoint_area; + return; + case STATION_BUOY: ta->tile = this->xy; ta->w = 1; diff --git a/src/waypoint_base.h b/src/waypoint_base.h index f431958e6e..091ef7fed2 100644 --- a/src/waypoint_base.h +++ b/src/waypoint_base.h @@ -12,9 +12,18 @@ #include "base_station_base.h" +/** + * Flags for Waypoint::waypoint_flags. + */ +enum WaypointFlags { + WPF_ROAD = 0, ///< This is a road waypoint +}; + /** Representation of a waypoint. */ struct Waypoint final : SpecializedStation { uint16_t town_cn; ///< The N-1th waypoint for this town (consecutive number) + uint16_t waypoint_flags{}; ///< Waypoint flags, see WaypointFlags + TileArea road_waypoint_area; ///< Tile area the road waypoint part covers /** * Create a waypoint at the given tile. diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index 0ffffcfc09..565a76411d 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -25,6 +25,7 @@ #include "string_func.h" #include "company_func.h" #include "newgrf_station.h" +#include "newgrf_roadstop.h" #include "company_base.h" #include "water.h" #include "company_gui.h" @@ -68,15 +69,16 @@ void Waypoint::MoveSign(TileIndex new_xy) * @param tile to search from * @param str the string to get the 'type' of * @param cid previous owner of the waypoint + * @param is_road whether to find a road waypoint * @return the deleted nearby waypoint */ -static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, CompanyID cid) +static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, CompanyID cid, bool is_road) { Waypoint *best = nullptr; uint thres = 8; for (Waypoint *wp : Waypoint::Iterate()) { - if (!wp->IsInUse() && wp->string_id == str && wp->owner == cid) { + if (!wp->IsInUse() && wp->string_id == str && wp->owner == cid && HasBit(wp->waypoint_flags, WPF_ROAD) == is_road) { uint cur_dist = DistanceManhattan(tile, wp->xy); if (cur_dist < thres) { @@ -90,13 +92,13 @@ static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, Compan } /** - * Get the axis for a new waypoint. This means that if it is a valid + * Get the axis for a new rail waypoint. This means that if it is a valid * tile to build a waypoint on it returns a valid Axis, otherwise an * invalid one. * @param tile the tile to look at. * @return the axis for the to-be-build waypoint. */ -Axis GetAxisForNewWaypoint(TileIndex tile) +Axis GetAxisForNewRailWaypoint(TileIndex tile) { /* The axis for rail waypoints is easy. */ if (IsRailWaypointTile(tile)) return GetRailStationAxis(tile); @@ -111,6 +113,29 @@ Axis GetAxisForNewWaypoint(TileIndex tile) } } +/** + * Get the axis for a new road waypoint. This means that if it is a valid + * tile to build a waypoint on it returns a valid Axis, otherwise an + * invalid one. + * @param tile the tile to look at. + * @return the axis for the to-be-build waypoint. + */ +Axis GetAxisForNewRoadWaypoint(TileIndex tile) +{ + /* The axis for rail waypoints is easy. */ + if (IsRoadWaypointTile(tile)) return DiagDirToAxis(GetRoadStopDir(tile)); + + /* Non-plain road type, no valid axis for waypoints. */ + if (!IsNormalRoadTile(tile)) return INVALID_AXIS; + + RoadBits bits = GetAllRoadBits(tile); + + if ((bits & ROAD_Y) == 0) return AXIS_X; + if ((bits & ROAD_X) == 0) return AXIS_Y; + + return INVALID_AXIS; +} + extern CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags); /** @@ -137,7 +162,7 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID * } } - if (GetAxisForNewWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); + if (GetAxisForNewRailWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); Owner owner = GetTileOwner(tile); CommandCost ret = CheckOwnership(owner); @@ -156,8 +181,10 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID * } extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec); -extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp); +extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road); extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta); +extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlag flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost); +extern CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index); /** * Convert existing rail to waypoint. Eg build a waypoint station over @@ -212,12 +239,12 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis } Waypoint *wp = nullptr; - CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, new_location, &wp); + CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, new_location, &wp, false); if (ret.Failed()) return ret; /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */ TileIndex center_tile = start_tile + (count / 2) * offset; - if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company); + if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company, false); if (wp != nullptr) { /* Reuse an existing waypoint. */ @@ -290,6 +317,145 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis return cost; } +/** + * Convert existing road to waypoint. Eg build a waypoint station over + * piece of road + * @param flags type of operation + * @param start_tile northern most tile where waypoint will be built + * @param axis orientation (Axis) + * @param width width of waypoint + * @param height height of waypoint + * @param spec_class custom road stop class + * @param spec_index custom road stop id + * @param station_to_join station ID to join (NEW_STATION if build new one) + * @param adjacent allow waypoints directly adjacent to other waypoints. + * @return the cost of this operation or an error + */ +CommandCost CmdBuildRoadWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, RoadStopClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent) +{ + if (!IsValidAxis(axis)) return CMD_ERROR; + /* Check if the given station class is valid */ + if (spec_class != ROADSTOP_CLASS_WAYP) return CMD_ERROR; + if (spec_index >= RoadStopClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR; + + const RoadStopSpec *roadstopspec = RoadStopClass::Get(spec_class)->GetSpec(spec_index); + + /* The number of parts to build */ + uint8_t count = axis == AXIS_X ? height : width; + + if ((axis == AXIS_X ? width : height) != 1) return CMD_ERROR; + if (count == 0 || count > _settings_game.station.station_spread) return CMD_ERROR; + + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Waypoint::IsValidID(station_to_join))) return CMD_ERROR; + + TileArea roadstop_area(start_tile, width, height); + + /* Total road stop cost. */ + Money unit_cost; + if (roadstopspec != nullptr) { + unit_cost = roadstopspec->GetBuildCost(PR_BUILD_STATION_TRUCK); + } else { + unit_cost = _price[PR_BUILD_STATION_TRUCK]; + } + StationID est = INVALID_STATION; + CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, true, STATION_ROADWAYPOINT, axis, AxisToDiagDir(axis), &est, INVALID_ROADTYPE, unit_cost); + if (cost.Failed()) return cost; + + Waypoint *wp = nullptr; + CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, roadstop_area, &wp, true); + if (ret.Failed()) return ret; + + /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */ + TileIndex center_tile = start_tile + (count / 2) * TileOffsByDiagDir(AxisToDiagDir(OtherAxis(axis)));; + if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company, true); + + if (wp != nullptr) { + /* Reuse an existing waypoint. */ + if (!HasBit(wp->waypoint_flags, WPF_ROAD)) return CMD_ERROR; + if (wp->owner != _current_company) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT); + + ret = wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TEST); + if (ret.Failed()) return ret; + } else { + /* allocate and initialize new waypoint */ + if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); + } + + /* Check if we can allocate a custom stationspec to this station */ + if (AllocateSpecToRoadStop(roadstopspec, wp, false) == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS); + + if (flags & DC_EXEC) { + if (wp == nullptr) { + wp = new Waypoint(start_tile); + SetBit(wp->waypoint_flags, WPF_ROAD); + } else if (!wp->IsInUse()) { + /* Move existing (recently deleted) waypoint to the new location */ + wp->xy = start_tile; + } + wp->owner = _current_company; + + wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TRY); + + if (roadstopspec != nullptr) { + /* Include this road stop spec's animation trigger bitmask + * in the station's cached copy. */ + wp->cached_roadstop_anim_triggers |= roadstopspec->animation.triggers; + } + + wp->delete_ctr = 0; + wp->facilities |= FACIL_BUS_STOP | FACIL_TRUCK_STOP; + wp->build_date = TimerGameCalendar::date; + wp->string_id = STR_SV_STNAME_WAYPOINT; + + if (wp->town == nullptr) MakeDefaultName(wp); + + wp->UpdateVirtCoord(); + + uint8_t map_spec_index = AllocateSpecToRoadStop(roadstopspec, wp, true); + + /* Check every tile in the area. */ + for (TileIndex cur_tile : roadstop_area) { + /* Get existing road types and owners before any tile clearing */ + RoadType road_rt = MayHaveRoad(cur_tile) ? GetRoadType(cur_tile, RTT_ROAD) : INVALID_ROADTYPE; + RoadType tram_rt = MayHaveRoad(cur_tile) ? GetRoadType(cur_tile, RTT_TRAM) : INVALID_ROADTYPE; + Owner road_owner = road_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_ROAD) : _current_company; + Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company; + + if (IsRoadWaypointTile(cur_tile)) { + RemoveRoadWaypointStop(cur_tile, flags, map_spec_index); + } + + wp->road_waypoint_area.Add(cur_tile); + + wp->rect.BeforeAddTile(cur_tile, StationRect::ADD_TRY); + + /* Update company infrastructure counts. If the current tile is a normal road tile, remove the old + * bits first. */ + if (IsNormalRoadTile(cur_tile)) { + UpdateCompanyRoadInfrastructure(road_rt, road_owner, -(int)CountBits(GetRoadBits(cur_tile, RTT_ROAD))); + UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, -(int)CountBits(GetRoadBits(cur_tile, RTT_TRAM))); + } + + UpdateCompanyRoadInfrastructure(road_rt, road_owner, ROAD_STOP_TRACKBIT_FACTOR); + UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR); + + MakeDriveThroughRoadStop(cur_tile, wp->owner, road_owner, tram_owner, wp->index, STATION_ROADWAYPOINT, road_rt, tram_rt, axis); + SetCustomRoadStopSpecIndex(cur_tile, map_spec_index); + if (roadstopspec != nullptr) wp->SetRoadStopRandomBits(cur_tile, 0); + + Company::Get(wp->owner)->infrastructure.station++; + + MarkTileDirtyByTile(cur_tile); + } + DirtyCompanyInfrastructureWindows(wp->owner); + } + return cost; +} + /** * Build a buoy. * @param flags operation to perform @@ -304,7 +470,7 @@ CommandCost CmdBuildBuoy(DoCommandFlag flags, TileIndex tile) if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */ - Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE); + Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE, false); if (wp == nullptr && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_WAYPOINT_BUOY]); diff --git a/src/waypoint_cmd.h b/src/waypoint_cmd.h index 02914124c6..05c916c952 100644 --- a/src/waypoint_cmd.h +++ b/src/waypoint_cmd.h @@ -14,14 +14,19 @@ #include "station_type.h" enum StationClassID : uint16_t; +enum RoadStopClassID : uint16_t; CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, StationClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent); CommandCost CmdRemoveFromRailWaypoint(DoCommandFlag flags, TileIndex start, TileIndex end, bool keep_rail); +CommandCost CmdBuildRoadWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, RoadStopClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent); +CommandCost CmdRemoveFromRoadWaypoint(DoCommandFlag flags, TileIndex start, TileIndex end); CommandCost CmdBuildBuoy(DoCommandFlag flags, TileIndex tile); CommandCost CmdRenameWaypoint(DoCommandFlag flags, StationID waypoint_id, const std::string &text); DEF_CMD_TRAIT(CMD_BUILD_RAIL_WAYPOINT, CmdBuildRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_FROM_RAIL_WAYPOINT, CmdRemoveFromRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_BUILD_ROAD_WAYPOINT, CmdBuildRoadWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_REMOVE_FROM_ROAD_WAYPOINT, CmdRemoveFromRoadWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_BUOY, CmdBuildBuoy, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_RENAME_WAYPOINT, CmdRenameWaypoint, 0, CMDT_OTHER_MANAGEMENT) diff --git a/src/waypoint_func.h b/src/waypoint_func.h index 2906fa6369..4342a9b4ee 100644 --- a/src/waypoint_func.h +++ b/src/waypoint_func.h @@ -16,7 +16,8 @@ CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags); -Axis GetAxisForNewWaypoint(TileIndex tile); +Axis GetAxisForNewRailWaypoint(TileIndex tile); +Axis GetAxisForNewRoadWaypoint(TileIndex tile); void ShowWaypointWindow(const Waypoint *wp); void DrawWaypointSprite(int x, int y, int stat_id, RailType railtype); diff --git a/src/waypoint_gui.cpp b/src/waypoint_gui.cpp index f73e301a17..845da26023 100644 --- a/src/waypoint_gui.cpp +++ b/src/waypoint_gui.cpp @@ -43,8 +43,25 @@ private: { if (!this->wp->IsInUse()) return this->wp->xy; + StationType type; + switch (this->vt) { + case VEH_TRAIN: + type = STATION_WAYPOINT; + break; + + case VEH_ROAD: + type = STATION_ROADWAYPOINT; + break; + + case VEH_SHIP: + type = STATION_BUOY; + break; + + default: + NOT_REACHED(); + } TileArea ta; - this->wp->GetTileArea(&ta, this->vt == VEH_TRAIN ? STATION_WAYPOINT : STATION_BUOY); + this->wp->GetTileArea(&ta, type); return ta.GetCenterTile(); } @@ -57,11 +74,20 @@ public: WaypointWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) { this->wp = Waypoint::Get(window_number); - this->vt = (wp->string_id == STR_SV_STNAME_WAYPOINT) ? VEH_TRAIN : VEH_SHIP; + if (wp->string_id == STR_SV_STNAME_WAYPOINT) { + this->vt = HasBit(this->wp->waypoint_flags, WPF_ROAD) ? VEH_ROAD : VEH_TRAIN; + } else { + this->vt = VEH_SHIP; + } this->CreateNestedTree(); if (this->vt == VEH_TRAIN) { this->GetWidget(WID_W_SHOW_VEHICLES)->SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP); + } + if (this->vt == VEH_ROAD) { + this->GetWidget(WID_W_SHOW_VEHICLES)->SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP); + } + if (this->vt != VEH_SHIP) { this->GetWidget(WID_W_CENTER_VIEW)->tool_tip = STR_WAYPOINT_VIEW_CENTER_TOOLTIP; this->GetWidget(WID_W_RENAME)->tool_tip = STR_WAYPOINT_VIEW_CHANGE_WAYPOINT_NAME; } diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index 5da57d024c..3c8004c7b8 100644 --- a/src/widgets/road_widget.h +++ b/src/widgets/road_widget.h @@ -19,6 +19,7 @@ enum RoadToolbarWidgets : WidgetID { WID_ROT_AUTOROAD, ///< Autorail. WID_ROT_DEMOLISH, ///< Demolish. WID_ROT_DEPOT, ///< Build depot. + WID_ROT_BUILD_WAYPOINT, ///< Build waypoint. WID_ROT_BUS_STATION, ///< Build bus station. WID_ROT_TRUCK_STATION, ///< Build truck station. WID_ROT_ONE_WAY, ///< Build one-way road.