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 2d7fc86672..2fd494b5ca 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 d98fe8880a..fbe2e65aa2 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -2884,6 +2884,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}Build waypoint on road. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only
+STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT :{BLACK}Build waypoint on tramway. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only
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
@@ -5092,11 +5094,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!
@@ -5344,6 +5349,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 7e4e9be24a..dc7f789710 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/newgrf_roadstop.h b/src/newgrf_roadstop.h
index 6ec42f508b..c50e428a29 100644
--- a/src/newgrf_roadstop.h
+++ b/src/newgrf_roadstop.h
@@ -28,7 +28,7 @@ static const uint32_t ROADSTOP_CLASS_LABEL_WAYPOINT = 'WAYP';
enum RoadStopClassID : uint16_t {
ROADSTOP_CLASS_BEGIN = 0, ///< The lowest valid value
ROADSTOP_CLASS_DFLT = 0, ///< Default road stop class.
- ROADSTOP_CLASS_WAYP, ///< Waypoint class (unimplemented: this is reserved for future use with road waypoints).
+ ROADSTOP_CLASS_WAYP, ///< Waypoint class.
ROADSTOP_CLASS_MAX = UINT16_MAX, ///< Maximum number of classes.
};
DECLARE_POSTFIX_INCREMENT(RoadStopClassID)
@@ -74,6 +74,15 @@ enum RoadStopSpecFlags {
RSF_BUILD_MENU_TRAM_ONLY = 6, ///< Only show in the tram build menu (not road).
};
+enum RoadStopView {
+ RSV_BAY_NE = 0, ///< Bay road stop, facing Northeast
+ RSV_BAY_SE = 1, ///< Bay road stop, facing Southeast
+ RSV_BAY_SW = 2, ///< Bay road stop, facing Southwest
+ RSV_BAY_NW = 3, ///< Bay road stop, facing Northwest
+ RSV_DRIVE_THROUGH_X = 4, ///< Drive through road stop, X axis
+ RSV_DRIVE_THROUGH_Y = 5, ///< Drive through road stop, Y axis
+};
+
/** Scope resolver for road stops. */
struct RoadStopScopeResolver : public ScopeResolver {
TileIndex tile; ///< %Tile of the station.
diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp
index 003d879303..1d6743106e 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 | 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 bc914f029c..2e88c90240 100644
--- a/src/order_gui.cpp
+++ b/src/order_gui.cpp
@@ -427,6 +427,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 63bfabcc8a..d384568b62 100644
--- a/src/pathfinder/follow_track.hpp
+++ b/src/pathfinder/follow_track.hpp
@@ -227,7 +227,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/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 b6f4b45f76..41b4c80889 100644
--- a/src/rail_gui.cpp
+++ b/src/rail_gui.cpp
@@ -169,7 +169,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);
@@ -781,7 +781,7 @@ struct BuildRailToolbarWindow : Window {
}
};
- ShowSelectWaypointIfNeeded(ta, proc);
+ ShowSelectRailWaypointIfNeeded(ta, proc);
}
}
break;
@@ -821,7 +821,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);
}
/**
@@ -1832,7 +1832,7 @@ public:
{
bool default_added = false;
for (const Waypoint *wp : Waypoint::Iterate()) {
- if (wp->owner != _local_company) continue;
+ if (wp->owner != _local_company || HasBit(wp->waypoint_flags, WPF_ROAD)) continue;
if (!default_added && StationUsesDefaultType(wp)) {
items.insert({0, 0, STAT_CLASS_WAYP, 0});
default_added = true;
diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp
index e9032cbaaf..cd94e30a81 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);
@@ -2451,7 +2451,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 0f57d1ea64..be07a09574 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"
@@ -34,6 +35,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"
@@ -237,6 +239,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.
@@ -350,22 +375,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);
}
@@ -445,6 +474,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;
@@ -505,6 +535,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);
@@ -594,6 +630,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;
@@ -635,6 +675,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);
}
@@ -707,6 +748,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.sel_class), ROADSTOP_BUS, _cur_roadtype)) {
@@ -750,6 +815,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
@@ -798,6 +868,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),
@@ -812,6 +883,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),
@@ -836,6 +908,8 @@ static constexpr NWidgetPart _nested_build_road_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_ROAD_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_ROAD_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_BUS_STATION),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRUCK_STATION),
@@ -879,6 +953,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 a638f379fe..0cc513bb05 100644
--- a/src/roadveh_cmd.cpp
+++ b/src/roadveh_cmd.cpp
@@ -1318,7 +1318,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 ebe5aa9c19..06102930b7 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -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 651daa1bcb..8e49af0eed 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 c8bdf3f61f..a820c7c4f1 100644
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -382,6 +382,7 @@ enum SaveLoadVersion : uint16_t {
SLV_COMPANY_ALLOW_LIST, ///< 335 PR#12337 Saving of list of client keys that are allowed to join this company.
SLV_GROUP_NUMBERS, ///< 336 PR#12297 Add per-company group numbers.
SLV_INCREASE_STATION_TYPE_FIELD_SIZE, ///< 337 PR#12572 Increase size of StationType field in map array
+ SLV_ROAD_WAYPOINTS, ///< 338 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 c964ceb46a..2f427d8d52 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 155e2fe420..6dda3ca843 100644
--- a/src/station_base.h
+++ b/src/station_base.h
@@ -508,7 +508,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 ffca7085b4..7f0f0fc8f1 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); });
+ }
}
/**
@@ -1606,6 +1616,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.
@@ -1865,6 +1885,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.
@@ -1877,7 +1898,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; });
}
/**
@@ -1885,15 +1906,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) {
@@ -1907,10 +1928,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) {
@@ -1988,7 +2009,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;
@@ -2024,7 +2045,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);
}
@@ -2062,7 +2083,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;
@@ -2222,6 +2243,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;
+ }
+
+ /* Ignore vehicles when the company goes bankrupt. The road will remain, any vehicles going to the waypoint will be removed. */
+ 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.
@@ -2244,50 +2391,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);
}
/**
@@ -3157,7 +3278,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;
@@ -3184,7 +3305,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);
@@ -3386,7 +3507,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);
@@ -3410,6 +3531,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;
}
@@ -3438,7 +3560,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;
@@ -3496,7 +3618,7 @@ static void AnimateTile_Station(TileIndex tile)
return;
}
- if (IsRoadStopTile(tile)) {
+ if (IsAnyRoadStopTile(tile)) {
AnimateRoadStopTile(tile);
return;
}
@@ -3556,7 +3678,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;
}
@@ -4382,7 +4504,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) {
@@ -4419,6 +4541,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;
@@ -4446,7 +4569,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);
@@ -4513,6 +4640,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:
@@ -4532,6 +4660,11 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags)
if (remove_road.Failed()) return remove_road;
}
return RemoveRoadStop(tile, flags);
+ case STATION_ROADWAYPOINT: {
+ 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 65ba38ccd4..b9cf9846bf 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
@@ -2476,11 +2492,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..d4cc0ef039 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 (bus stop, truck stop or road waypoint)
+ */
+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
+ * @return \c true if the tile is a station tile and a drive through road stop or road waypoint
*/
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 870f34ced5..57e4aedb92 100644
--- a/src/town_cmd.cpp
+++ b/src/town_cmd.cpp
@@ -1242,7 +1242,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. */
@@ -1445,7 +1445,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 5903ea259f..9db7da2fa1 100644
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -2395,7 +2395,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 1b86da51c0..ba83859bf8 100644
--- a/src/vehicle_gui.cpp
+++ b/src/vehicle_gui.cpp
@@ -3166,7 +3166,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 78f9ba0df7..6ea1691d8d 100644
--- a/src/waypoint.cpp
+++ b/src/waypoint.cpp
@@ -39,6 +39,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 5e13f6e134..cbf2e1e608 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 e28cbe691b..26d791e236 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 existing road 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
@@ -214,12 +241,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. */
@@ -292,6 +319,146 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis
return cost;
}
+/**
+ * Build a road waypoint on an existing 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 (static_cast(spec_class) >= RoadStopClass::GetClassCount()) return CMD_ERROR;
+ const RoadStopClass *cls = RoadStopClass::Get(spec_class);
+ if (!IsWaypointClass(*cls)) return CMD_ERROR;
+ if (spec_index >= cls->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 {
+ /* Check if we can create a 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
@@ -306,7 +473,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 e919390576..62ae0ea6cb 100644
--- a/src/waypoint_func.h
+++ b/src/waypoint_func.h
@@ -18,7 +18,8 @@ enum StationClassID : uint16_t;
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, StationClassID station_class, uint16_t station_type, RailType railtype);
diff --git a/src/waypoint_gui.cpp b/src/waypoint_gui.cpp
index 0cc5981b27..f2e85d8d71 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 c876acbad2..679545728a 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.