diff --git a/docs/landscape.html b/docs/landscape.html index f9856b1de1..2e3d82313b 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -1032,7 +1032,7 @@ -
  • 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
  • m7 bits 4..0: owner of road (road stops)
  • diff --git a/src/command.cpp b/src/command.cpp index fcfff1a10f..f3274b4b4c 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -67,6 +67,7 @@ CommandProc CmdBuildTunnel; CommandProc CmdBuildTrainDepot; CommandProcEx CmdBuildRailWaypoint; +CommandProc CmdBuildRoadWaypoint; CommandProc CmdRenameWaypoint; CommandProc CmdSetWaypointLabelHidden; CommandProc CmdRemoveFromRailWaypoint; @@ -316,6 +317,7 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdRemoveFromRailStation, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_STATION DEF_CMD(CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CONVERT_RAIL DEF_CMD(CmdBuildRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_RAIL_WAYPOINT + DEF_CMD(CmdBuildRoadWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_ROAD_WAYPOINT DEF_CMD(CmdRenameWaypoint, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_WAYPOINT DEF_CMD(CmdSetWaypointLabelHidden, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_WAYPOINT_LABEL_HIDDEN DEF_CMD(CmdRemoveFromRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_WAYPOINT diff --git a/src/command_type.h b/src/command_type.h index f02980a101..9409520ef5 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -251,6 +251,7 @@ enum Commands { CMD_CONVERT_RAIL, ///< convert a rail type CMD_BUILD_RAIL_WAYPOINT, ///< build a waypoint + CMD_BUILD_ROAD_WAYPOINT, ///< build a road waypoint CMD_RENAME_WAYPOINT, ///< rename a waypoint CMD_SET_WAYPOINT_LABEL_HIDDEN, ///< set whether waypoint label is hidden CMD_REMOVE_FROM_RAIL_WAYPOINT, ///< remove a (rectangle of) tiles from a rail waypoint diff --git a/src/lang/english.txt b/src/lang/english.txt index fbc68a61f9..102f4aae6d 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3549,6 +3549,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 toggles build/remove for tramway construction. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build road vehicle depot (for buying and servicing vehicles). Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build tram vehicle depot (for buying and servicing vehicles). Shift toggles building/showing cost estimate +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 enables joining stations. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION :{BLACK}Build passenger tram station. Ctrl enables joining stations. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY :{BLACK}Build lorry loading bay. Ctrl enables joining stations. Shift toggles building/showing cost estimate @@ -6142,11 +6144,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! diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index a16114514a..23ae7e2e57 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -1090,6 +1090,14 @@ CommandCost CmdInsertOrderIntl(DoCommandFlag flags, Vehicle *v, VehicleOrderID s break; } + case VEH_ROAD: { + if (!(wp->facilities & FACIL_BUS_STOP) || !(wp->facilities & FACIL_TRUCK_STOP)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER); + + CommandCost ret = CheckInfraUsageAllowed(v->type, wp->owner); + if (ret.Failed()) return ret; + break; + } + case VEH_SHIP: if (!(wp->facilities & FACIL_DOCK)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER); if (wp->owner != OWNER_NONE) { @@ -1101,9 +1109,9 @@ CommandCost CmdInsertOrderIntl(DoCommandFlag flags, Vehicle *v, VehicleOrderID s /* 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; - if (_settings_game.order.nonstop_only && !(new_order.GetNonStopType() & ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS) && v->type == VEH_TRAIN) return CMD_ERROR; + * non-stop orders (if any) are only valid for trains/RVs */ + if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR; + if (_settings_game.order.nonstop_only && !(new_order.GetNonStopType() & ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS) && v->IsGroundVehicle()) return CMD_ERROR; break; } diff --git a/src/order_gui.cpp b/src/order_gui.cpp index ff4d02b82a..c14e7f0100 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -1130,6 +1130,15 @@ static Order GetOrderCmdFromTile(const Vehicle *v, TileIndex tile) return order; } + /* check road waypoint */ + if (IsRoadWaypointTile(tile) && + v->type == VEH_ROAD && + IsInfraTileUsageAllowed(VEH_ROAD, v->owner, tile)) { + order.MakeGoToWaypoint(GetStationIndex(tile)); + if (_settings_client.gui.new_nonstop != _ctrl_pressed || _settings_game.order.nonstop_only) 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 61698f1c2a..dd42553bc5 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -228,7 +228,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 2999a962cf..70c44a7a05 100644 --- a/src/pathfinder/npf/npf.cpp +++ b/src/pathfinder/npf/npf.cpp @@ -341,6 +341,7 @@ static int32 NPFRoadPathCost(AyStar *as, AyStarNode *current, OpenListNode *pare 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 */ @@ -1131,7 +1132,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 8777a80583..38b1694586 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -92,6 +92,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 */ @@ -270,7 +272,7 @@ protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station; - bool m_bus; + StationType m_station_type; bool m_non_artic; public: @@ -278,8 +280,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 { @@ -313,7 +321,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/road_cmd.cpp b/src/road_cmd.cpp index fdb349aa96..d7b7bdffa6 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1967,7 +1967,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); @@ -2976,7 +2976,7 @@ CommandCost CmdConvertRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 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)) { @@ -3036,7 +3036,7 @@ CommandCost CmdConvertRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 } uint num_pieces; - if (IsRoadDepotTile(tile) || IsRoadStopTile(tile)) { + if (IsRoadDepotTile(tile) || IsAnyRoadStopTile(tile)) { num_pieces = HasTileRoadType(tile, rtt) ? 2 : 0; } else { num_pieces = CountBits(GetAnyRoadBits(tile, rtt)); @@ -3047,7 +3047,7 @@ CommandCost CmdConvertRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 if (flags & DC_EXEC) { // we can safely convert, too /* Update the company infrastructure counters. */ - if (!IsRoadStopTile(tile) && owner == _current_company) { + if (!IsAnyRoadStopTile(tile) && owner == _current_company) { ConvertRoadTypeOwner(tile, num_pieces, owner, from_type, to_type); } else { UpdateCompanyRoadInfrastructure(from_type, owner, -(int)num_pieces); diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 9359e9aa8f..242ddcb975 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -32,6 +32,7 @@ #include "core/geometry_func.hpp" #include "date_func.h" #include "station_map.h" +#include "waypoint_func.h" #include "widgets/road_widget.h" #include "table/strings.h" @@ -197,6 +198,29 @@ static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, uint32 p2, u ShowSelectStationIfNeeded(cmdcont, ta); } +/** + * 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 rail waypoints. This is always going to fail, + * but provides the user with a proper error message. */ + DoCommandP(tile, 1 | 1 << 8, INVALID_STATION << 16, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT)); + } +} + /** * Callback for placing a bus station. * @param tile Position to place the station. @@ -309,6 +333,7 @@ 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, WIDGET_LIST_END); @@ -322,10 +347,12 @@ struct BuildRoadToolbarWindow : Window { 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); } @@ -405,6 +432,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; @@ -465,6 +493,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); @@ -554,6 +588,10 @@ struct BuildRoadToolbarWindow : Window { CMD_BUILD_ROAD_DEPOT | CMD_MSG(this->rti->strings.err_depot), CcRoadDepot); break; + case WID_ROT_BUILD_WAYPOINT: + PlaceRoad_Waypoint(tile); + break; + case WID_ROT_BUS_STATION: PlaceRoad_BusStation(tile); break; @@ -675,6 +713,22 @@ struct BuildRoadToolbarWindow : Window { CMD_BUILD_LONG_ROAD | CMD_MSG(this->rti->strings.err_build_road), CcPlaySound_CONSTRUCTION_OTHER); break; + case DDSP_BUILD_ROAD_WAYPOINT: + case DDSP_REMOVE_ROAD_WAYPOINT: + if (this->IsWidgetLowered(WID_ROT_BUILD_WAYPOINT)) { + TileArea ta(start_tile, end_tile); + if (_remove_button_clicked) { + DoCommandP(ta.tile, ta.w | ta.h << 8, (1 << 2), CMD_REMOVE_ROAD_STOP | CMD_MSG(STR_ERROR_CAN_T_REMOVE_ROAD_WAYPOINT), CcPlaySound_CONSTRUCTION_OTHER); + } else { + uint32 p1 = ta.w | ta.h << 8 | _ctrl_pressed << 16 | (select_method == VPM_X_LIMITED ? AXIS_X : AXIS_Y) << 17; + uint32 p2 = INVALID_STATION << 16; + + CommandContainer cmdcont = NewCommandContainerBasic(ta.tile, p1, p2, CMD_BUILD_ROAD_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT), CcPlaySound_CONSTRUCTION_OTHER); + ShowSelectWaypointIfNeeded(cmdcont, ta); + } + } + break; + case DDSP_BUILD_BUSSTOP: case DDSP_REMOVE_BUSSTOP: if (this->IsWidgetLowered(WID_ROT_BUS_STATION)) { @@ -812,6 +866,8 @@ static const 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), @@ -855,6 +911,8 @@ static const 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), @@ -1314,7 +1372,7 @@ static void SetDefaultRoadGui() std::array tram_count = {}; for (TileIndex t = 0; t < MapSize(); t++) { if (MayHaveRoad(t)) { - if (IsTileType(t, MP_STATION) && !IsRoadStop(t)) continue; + if (IsTileType(t, MP_STATION) && !IsAnyRoadStop(t)) continue; RoadType road_type = GetRoadTypeRoad(t); if (road_type != INVALID_ROADTYPE) road_count[road_type]++; RoadType tram_type = GetRoadTypeTram(t); diff --git a/src/road_map.cpp b/src/road_map.cpp index d5417e46f4..c1b784af98 100644 --- a/src/road_map.cpp +++ b/src/road_map.cpp @@ -44,7 +44,7 @@ RoadBits GetAnyRoadBits(TileIndex tile, RoadTramType rtt, bool straight_tunnel_b } 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 711784d5b0..59efd300d7 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1804,7 +1804,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 db1761db1b..8913e5cfcc 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -1234,7 +1234,7 @@ bool AfterLoadGame() break; case MP_STATION: - if (IsRoadStop(t)) SB(_me[t].m7, 6, 2, 1); + if (IsStationRoadStop(t)) SB(_me[t].m7, 6, 2, 1); break; case MP_TUNNELBRIDGE: @@ -1288,7 +1288,7 @@ bool AfterLoadGame() break; case MP_STATION: - if (!IsRoadStop(t)) break; + if (!IsStationRoadStop(t)) break; if (fix_roadtypes) SB(_me[t].m7, 6, 2, (RoadTypes)GB(_m[t].m3, 0, 3)); SB(_me[t].m7, 0, 5, HasBit(_me[t].m6, 2) ? OWNER_TOWN : GetTileOwner(t)); @@ -1445,7 +1445,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; @@ -1499,7 +1499,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 93234d7c44..1a3ca65d72 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -160,7 +160,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/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 33b1e1bce4..9936e4bd69 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -166,6 +166,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_ST_INDUSTRY_CARGO_MODE, XSCF_IGNORABLE_UNKNOWN, 1, 1, "st_industry_cargo_mode", nullptr, nullptr, nullptr }, { XSLFI_TL_SPEED_LIMIT, XSCF_IGNORABLE_UNKNOWN, 1, 1, "tl_speed_limit", nullptr, nullptr, nullptr }, { XSLFI_WAYPOINT_FLAGS, XSCF_NULL, 1, 1, "waypoint_flags", nullptr, nullptr, nullptr }, + { XSLFI_ROAD_WAYPOINTS, XSCF_NULL, 1, 1, "road_waypoints", nullptr, nullptr, nullptr }, { XSLFI_MORE_STATION_TYPES, XSCF_NULL, 1, 1, "more_station_types", nullptr, nullptr, nullptr }, { XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index ad0cfe05cf..001539950b 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -120,6 +120,7 @@ enum SlXvFeatureIndex { XSLFI_ST_INDUSTRY_CARGO_MODE, ///< Station industry cargo mode setting XSLFI_TL_SPEED_LIMIT, ///< Through load maximum speed setting XSLFI_WAYPOINT_FLAGS, ///< Waypoint flags + XSLFI_ROAD_WAYPOINTS, ///< Road waypoints XSLFI_MORE_STATION_TYPES, ///< More station types (field widening) XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64 diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 81be8b1e28..7c1ed34f89 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -462,6 +462,9 @@ static const SaveLoad _waypoint_desc[] = { 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_X(Waypoint, waypoint_flags, SLE_UINT16, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_WAYPOINT_FLAGS)), + SLE_CONDVAR_X(Waypoint, road_waypoint_area.tile, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_WAYPOINTS)), + SLE_CONDVAR_X(Waypoint, road_waypoint_area.w, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_WAYPOINTS)), + SLE_CONDVAR_X(Waypoint, road_waypoint_area.h, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_WAYPOINTS)), }; /** diff --git a/src/screenshot.cpp b/src/screenshot.cpp index 874400add7..f3f2b526e2 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -1135,6 +1135,8 @@ static byte GetTopographyValue(TileIndex tile) return MKCOLOUR(PC_WATER); case STATION_WAYPOINT: return MKCOLOUR(PC_GREY); + case STATION_ROADWAYPOINT: + return MKCOLOUR(PC_GREY); default: NOT_REACHED(); } } @@ -1228,6 +1230,8 @@ static byte GetIndustryValue(TileIndex tile) return MKCOLOUR(PC_BLACK); case STATION_WAYPOINT: return MKCOLOUR(PC_GREY); + case STATION_ROADWAYPOINT: + return MKCOLOUR(PC_GREY); default: NOT_REACHED(); } } diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index afad3a3012..8d769e1ea8 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -50,7 +50,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) @@ -624,7 +624,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); EnforcePrecondition(false, ::IsValidTile(tile)); EnforcePrecondition(false, IsTileType(tile, MP_STATION)); - EnforcePrecondition(false, IsRoadStop(tile)); + EnforcePrecondition(false, IsStationRoadStop(tile)); return ScriptObject::DoCommand(tile, 1 | 1 << 8, GetRoadStopType(tile), CMD_REMOVE_ROAD_STOP); } diff --git a/src/station.cpp b/src/station.cpp index a99bf26bb6..d996e18db7 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -314,13 +314,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 + inc; case STATION_BUOY: - case STATION_WAYPOINT: return CA_NONE; + case STATION_WAYPOINT: + case STATION_ROADWAYPOINT: return CA_NONE; } } } diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index f0a8dda687..31fea176da 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -98,8 +98,8 @@ bool IsHangar(TileIndex t) * @param st to 'return' the found station * @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); @@ -107,7 +107,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) { @@ -1068,13 +1068,14 @@ static CommandCost CheckFlatLandRailStation(TileArea tile_area, DoCommandFlag fl * @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 require_road Is existing road required. * @return The cost in case of success, or an error code if it failed. */ -static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, bool is_truck_stop, Axis axis, StationID *station, RoadType rt) +CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt, bool require_road) { CommandCost cost(EXPENSES_CONSTRUCTION); int allowed_z = -1; @@ -1097,10 +1098,10 @@ static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags * 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. } @@ -1147,10 +1148,10 @@ static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags } 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); cost.AddCost(RoadBuildCost(road_rt) * (2 - num_pieces)); - } else if (RoadTypeIsRoad(rt)) { + } else if (rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt)) { cost.AddCost(RoadBuildCost(rt) * 2); } @@ -1168,12 +1169,14 @@ static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags } 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 (require_road) { + return_cmd_error(STR_ERROR_THERE_IS_NO_ROAD); } else { ret = DoCommand(cur_tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); if (ret.Failed()) return ret; @@ -1316,8 +1319,8 @@ void GetStationLayout(byte *layout, uint numtracks, uint plat_len, const Station * @param error_message the error message when building a station on top of others * @return command cost with the error or 'okay' */ -template -CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st, StringID error_message) +template +CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st, StringID error_message, F filter) { assert(*st == nullptr); bool check_surrounding = true; @@ -1331,7 +1334,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 { @@ -1343,7 +1347,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; } @@ -1365,7 +1369,7 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station */ static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st, StringID error_message = STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST) { - return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st, error_message); + return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st, error_message, [](Station *st) -> bool { return true; }); } /** @@ -1377,9 +1381,11 @@ static CommandCost FindJoiningStation(StationID existing_station, StationID stat * @param wp 'return' pointer for the found 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, STR_ERROR_MUST_REMOVE_RAILWAYPOINT_FIRST); + return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp, + is_road ? STR_ERROR_MUST_REMOVE_ROADWAYPOINT_FIRST : STR_ERROR_MUST_REMOVE_RAILWAYPOINT_FIRST, + [is_road](Waypoint *wp) -> bool { return HasBit(wp->waypoint_flags, WPF_ROAD) == is_road; }); } /** @@ -1736,6 +1742,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. @@ -2004,7 +2020,7 @@ static RoadStop **FindRoadStopSpot(bool truck_station, Station *st) } } -static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags); +CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags); /** * Find a nearby station that joins this road stop. @@ -2017,7 +2033,7 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags); */ 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, STR_ERROR_MUST_REMOVE_ROAD_STOP_FIRST); + return FindJoiningBaseStation(existing_stop, station_to_join, adjacent, ta, st, STR_ERROR_MUST_REMOVE_ROAD_STOP_FIRST, [](Station *st) -> bool { return true; }); } /** @@ -2082,7 +2098,7 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin /* Total road stop cost. */ CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[type ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]); StationID est = INVALID_STATION; - ret = CheckFlatLandRoadStop(roadstop_area, flags, is_drive_through ? 5 << axis : 1 << ddir, is_drive_through, type, axis, &est, rt); + ret = CheckFlatLandRoadStop(roadstop_area, flags, is_drive_through ? 5 << axis : 1 << ddir, is_drive_through, type ? STATION_TRUCK : STATION_BUS, axis, &est, rt, false); if (ret.Failed()) return ret; cost.AddCost(ret); @@ -2106,7 +2122,7 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company; DisallowedRoadDirections drd = IsNormalRoadTile(cur_tile) ? GetDisallowedRoadDirections(cur_tile) : DRD_NONE; - if (IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile)) { + if (IsTileType(cur_tile, MP_STATION) && IsAnyRoadStop(cur_tile)) { RemoveRoadStop(cur_tile, flags); } @@ -2141,7 +2157,7 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin UpdateCompanyRoadInfrastructure(road_rt, road_owner, ROAD_STOP_TRACKBIT_FACTOR); UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR); - 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); SetDriveThroughStopDisallowedRoadDirections(cur_tile, drd); road_stop->MakeDriveThrough(); } else { @@ -2154,10 +2170,10 @@ CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uin Company::Get(st->owner)->infrastructure.station++; MarkTileDirtyByTile(cur_tile); + UpdateRoadCachedOneWayStatesAroundTile(cur_tile); } ZoningMarkDirtyStationCoverageArea(st); NotifyRoadLayoutChanged(true); - UpdateRoadCachedOneWayStatesAroundTile(tile); if (st != nullptr) { st->AfterStationTileSetChange(true, type ? STATION_TRUCK: STATION_BUS); @@ -2181,6 +2197,52 @@ static Vehicle *ClearRoadStopStatusEnum(Vehicle *v, void *) return nullptr; } +CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags) +{ + 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; + } + + 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); + + DoClearSquare(tile); + + wp->rect.AfterRemoveTile(wp, tile); + + 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); + } + + NotifyRoadLayoutChanged(false); + } + + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_STATION_TRUCK]); +} /** * Remove a bus station/truck stop @@ -2188,8 +2250,12 @@ static Vehicle *ClearRoadStopStatusEnum(Vehicle *v, void *) * @param flags operation to perform * @return cost or failure of operation */ -static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) +CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) { + if (IsRoadWaypoint(tile)) { + return RemoveRoadWaypointStop(tile, flags); + } + Station *st = Station::GetByTile(tile); if (_current_company != OWNER_WATER) { @@ -2289,6 +2355,7 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags) * bit 8..15: Height of the removal area. * @param p2 bit 0: 0 For bus stops, 1 for truck stops. * @param p2 bit 1: 0 to keep roads of all drive-through stops, 1 to remove them. + * @param p2 bit 2: 0 for bus/truck stops, 1 for road waypoints. * @param text Unused. * @return The cost of this operation or an error. */ @@ -2296,7 +2363,7 @@ CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, ui { uint8 width = (uint8)GB(p1, 0, 8); uint8 height = (uint8)GB(p1, 8, 8); - bool keep_drive_through_roads = !HasBit(p2, 1); + bool keep_drive_through_roads = !HasBit(p2, 1) || HasBit(p2, 2); /* Check for incorrect width / height. */ if (width == 0 || height == 0) return CMD_ERROR; @@ -2312,8 +2379,13 @@ CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, ui 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) || !IsRoadStop(cur_tile) || (uint32)GetRoadStopType(cur_tile) != GB(p2, 0, 1)) continue; + if (HasBit(p2, 2)) { + /* Make sure the specified tile is a road waypoint */ + if (!IsTileType(cur_tile, MP_STATION) || !IsRoadWaypoint(cur_tile)) continue; + } else { + /* Make sure the specified tile is a road stop of the correct type */ + if (!IsTileType(cur_tile, MP_STATION) || !IsStationRoadStop(cur_tile) || (uint32)GetRoadStopType(cur_tile) != GB(p2, 0, 1)) continue; + } /* Save information on to-be-restored roads before the stop is removed. */ RoadBits road_bits = ROAD_NONE; @@ -3336,7 +3408,7 @@ draw_default_foundation: if (HasStationRail(ti->tile) && HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); - if (IsRoadStop(ti->tile)) { + if (IsAnyRoadStop(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); @@ -3441,7 +3513,7 @@ static void GetTileDesc_Station(TileIndex tile, TileDesc *td) { td->owner[0] = GetTileOwner(tile); - if (IsRoadStopTile(tile)) { + if (IsAnyRoadStopTile(tile)) { RoadType road_rt = GetRoadTypeRoad(tile); RoadType tram_rt = GetRoadTypeTram(tile); Owner road_owner = INVALID_OWNER; @@ -3535,6 +3607,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; } @@ -3564,7 +3637,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; @@ -3733,7 +3806,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; } @@ -4712,7 +4785,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) { @@ -4749,6 +4822,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; @@ -4839,6 +4913,7 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) default: break; case STATION_RAIL: return_cmd_error(STR_ERROR_MUST_DEMOLISH_RAILROAD); case STATION_WAYPOINT: return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + case STATION_ROADWAYPOINT: return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); 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); @@ -4866,6 +4941,11 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) return RemoveRoadStopAndUpdateRoadCachedOneWayState(tile, flags); case STATION_BUOY: return RemoveBuoy(tile, flags); case STATION_DOCK: return RemoveDock(tile, flags); + case STATION_ROADWAYPOINT: + if (IsDriveThroughStopTile(tile) && !CanRemoveRoadWithStop(tile, flags)) { + return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + } + return RemoveRoadStopAndUpdateRoadCachedOneWayState(tile, flags); default: break; } @@ -4892,7 +4972,8 @@ static CommandCost TerraformTile_Station(TileIndex tile, DoCommandFlag flags, in return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); case STATION_TRUCK: - case STATION_BUS: { + case STATION_BUS: + case STATION_ROADWAYPOINT: { DiagDirection direction = GetRoadStopDir(tile); if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break; if (IsDriveThroughStopTile(tile)) { diff --git a/src/station_gui.cpp b/src/station_gui.cpp index 541cdb0984..8b1c90fdb3 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -2326,6 +2326,10 @@ struct TileAndStation { static std::vector _deleted_stations_nearby; static std::vector _stations_nearby_list; +static bool _station_nearby_road_waypoint_search; + +static bool IsNearbyStationRightType(const Station *st) { return true; } +static bool IsNearbyStationRightType(const Waypoint *wp) { return HasBit(wp->waypoint_flags, WPF_ROAD) == _station_nearby_road_waypoint_search; } /** * Add station on this tile to _stations_nearby_list if it's fully within the @@ -2358,7 +2362,7 @@ static bool AddNearbyStation(TileIndex tile, void *user_data) if (!T::IsValidID(sid)) return false; T *st = T::Get(sid); - if (st->owner != _local_company || std::find(_stations_nearby_list.begin(), _stations_nearby_list.end(), sid) != _stations_nearby_list.end()) return false; + if (st->owner != _local_company || !IsNearbyStationRightType(st) || std::find(_stations_nearby_list.begin(), _stations_nearby_list.end(), sid) != _stations_nearby_list.end()) return false; if (st->rect.BeforeAddRect(ctx->tile, ctx->w, ctx->h, StationRect::ADD_TEST).Succeeded()) { _stations_nearby_list.push_back(sid); @@ -2391,7 +2395,7 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join) /* Look for deleted stations */ for (const BaseStation *st : BaseStation::Iterate()) { - if (T::IsExpected(st) && !st->IsInUse() && st->owner == _local_company) { + if (T::IsExpected(st) && !st->IsInUse() && st->owner == _local_company && IsNearbyStationRightType(T::From(st))) { /* Include only within station spread (yes, it is strictly less than) */ if (std::max(DistanceMax(ta.tile, st->xy), DistanceMax(TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1), st->xy)) < _settings_game.station.station_spread) { _deleted_stations_nearby.push_back({st->xy, st->index}); @@ -2546,6 +2550,7 @@ struct SelectStationWindow : Window { void OnInvalidateData(int data = 0, bool gui_scope = true) override { if (!gui_scope) return; + if (T::EXPECTED_FACIL == FACIL_WAYPOINT) _station_nearby_road_waypoint_search = (this->select_station_cmd.cmd & CMD_ID_MASK) == CMD_BUILD_ROAD_WAYPOINT; FindStationsNearby(this->area, true); this->vscroll->SetCount((uint)_stations_nearby_list.size() + 1); this->SetDirty(); @@ -2608,6 +2613,7 @@ static bool StationJoinerNeeded(const CommandContainer &cmd, TileArea ta) /* Test for adjacent station or station below selection. * If adjacent-stations is disabled and we are building next to a station, do not show the selection window. * but join the other station immediately. */ + if (T::EXPECTED_FACIL == FACIL_WAYPOINT) _station_nearby_road_waypoint_search = (cmd.cmd & CMD_ID_MASK) == CMD_BUILD_ROAD_WAYPOINT; const T *st = FindStationsNearby(ta, false); return st == nullptr && (_settings_game.station.adjacent_stations || _stations_nearby_list.size() == 0); } diff --git a/src/station_map.h b/src/station_map.h index e53db5fd5d..98ef3236e3 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -193,13 +193,34 @@ static inline bool IsBusStop(TileIndex 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 + */ +static inline bool IsRoadWaypoint(TileIndex 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 + */ +static inline bool IsRoadWaypointTile(TileIndex 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, truck stop \c false otherwise */ -static inline bool IsRoadStop(TileIndex t) +static inline bool IsStationRoadStop(TileIndex t) { assert_tile(IsTileType(t, MP_STATION), t); return IsTruckStop(t) || IsBusStop(t); @@ -210,9 +231,31 @@ static inline bool IsRoadStop(TileIndex t) * @param t Tile to check * @return \c true if the tile is a station tile and a road stop */ -static inline bool IsRoadStopTile(TileIndex t) +static inline bool IsStationRoadStopTile(TileIndex 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 + */ +static inline bool IsAnyRoadStop(TileIndex t) +{ + assert_tile(IsTileType(t, MP_STATION), t); + 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 a road stop + */ +static inline bool IsAnyRoadStopTile(TileIndex t) { - return IsTileType(t, MP_STATION) && IsRoadStop(t); + return IsTileType(t, MP_STATION) && IsAnyRoadStop(t); } /** @@ -222,7 +265,7 @@ static inline bool IsRoadStopTile(TileIndex t) */ static inline bool IsStandardRoadStopTile(TileIndex t) { - return IsRoadStopTile(t) && GetStationGfx(t) < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; + return IsAnyRoadStopTile(t) && GetStationGfx(t) < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; } /** @@ -232,7 +275,7 @@ static inline bool IsStandardRoadStopTile(TileIndex t) */ static inline bool IsDriveThroughStopTile(TileIndex t) { - return IsRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; + return IsAnyRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; } /** @@ -274,13 +317,13 @@ static inline StationGfx GetAirportGfx(TileIndex 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 */ static inline DiagDirection GetRoadStopDir(TileIndex t) { StationGfx gfx = GetStationGfx(t); - assert_tile(IsRoadStopTile(t), t); + assert_tile(IsAnyRoadStopTile(t), t); if (gfx < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET) { return (DiagDirection)(gfx); } else { @@ -630,9 +673,9 @@ static inline void MakeRoadStop(TileIndex t, Owner o, StationID sid, RoadStopTyp * @param tram_rt the tram roadtype on this tile * @param a the direction of the roadstop */ -static inline void MakeDriveThroughRoadStop(TileIndex t, Owner station, Owner road, Owner tram, StationID sid, RoadStopType rst, RoadType road_rt, RoadType tram_rt, Axis a) +static inline void MakeDriveThroughRoadStop(TileIndex 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 2753e3adbb..84309897ca 100644 --- a/src/station_type.h +++ b/src/station_type.h @@ -41,6 +41,7 @@ enum StationType { STATION_DOCK, STATION_BUOY, STATION_WAYPOINT, + STATION_ROADWAYPOINT, }; /** Types of RoadStops */ diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 05d11d3dfe..9a1003567d 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -16,6 +16,7 @@ #include "../ship.h" #include "../aircraft.h" #include "../object_map.h" +#include "../waypoint_base.h" #include "../string_func_extra.h" /* Helper for filling property tables */ @@ -1269,6 +1270,27 @@ class NIHStationStruct : public NIHelper { seprintf(buffer, lastof(buffer), " Docking tiles: %X, %u x %u", st->docking_station.tile, st->docking_station.w, st->docking_station.h); output.print(buffer); } + const Waypoint *wp = Waypoint::GetIfValid(index); + if (wp) { + output.register_next_line_click_flag_toggle(1); + seprintf(buffer, lastof(buffer), " [%c] flags: 0x%X", output.flags & 1 ? '-' : '+', wp->waypoint_flags); + output.print(buffer); + if (output.flags & 1) { + auto print = [&](const char *name) { + seprintf(buffer, lastof(buffer), " %s", name); + output.print(buffer); + }; + auto check_flag = [&](WaypointFlags flag, const char *name) { + if (HasBit(wp->waypoint_flags, flag)) print(name); + }; + check_flag(WPF_HIDE_LABEL, "WPF_HIDE_LABEL"); + check_flag(WPF_ROAD, "WPF_ROAD"); + } + + seprintf(buffer, lastof(buffer), " road_waypoint_area: tile: %X (%u x %u), width: %u, height: %u", + wp->road_waypoint_area.tile, TileX(wp->road_waypoint_area.tile), TileY(wp->road_waypoint_area.tile), wp->road_waypoint_area.w, wp->road_waypoint_area.h); + output.print(buffer); + } } }; diff --git a/src/table/station_land.h b/src/table/station_land.h index 53a69e1e21..8b98505ee5 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 79356008df..4975431738 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1301,7 +1301,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. */ diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index 02dea527ec..59502a7d63 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -549,7 +549,8 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u } case STATION_BUS: - case STATION_TRUCK: { + case STATION_TRUCK: + case STATION_ROADWAYPOINT: { CommandCost ret = IsRoadStopBridgeAboveOK(tile, IsDriveThroughStopTile(tile), GetRoadStopDir(tile), tile_start, tile_end, z_start + 1, bridge_type, transport_type); if (ret.Failed()) { @@ -672,7 +673,8 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u } case STATION_BUS: - case STATION_TRUCK: { + case STATION_TRUCK: + case STATION_ROADWAYPOINT: { CommandCost ret = IsRoadStopBridgeAboveOK(tile, IsDriveThroughStopTile(tile), GetRoadStopDir(tile), tile_start, tile_end, z_start + 1, bridge_type, transport_type); if (ret.Failed()) { diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 716a8fc1a2..e9d017f385 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -2470,6 +2470,7 @@ public: /* check rail waypoint or buoy (no ownership) */ if ((IsRailWaypointTile(tile) && this->vli.vtype == VEH_TRAIN && IsInfraTileUsageAllowed(VEH_TRAIN, this->vli.company, tile)) + || (IsRoadWaypointTile(tile) && this->vli.vtype == VEH_ROAD && IsInfraTileUsageAllowed(VEH_ROAD, this->vli.company, tile)) || (IsBuoyTile(tile) && this->vli.vtype == VEH_SHIP)) { if (this->vli.type != VL_STATION_LIST) return; if (!(Station::Get(this->vli.index)->facilities & FACIL_WAYPOINT)) return; @@ -2480,7 +2481,7 @@ public: if (IsTileType(tile, MP_STATION)) { if (this->vli.type != VL_STATION_LIST) return; - if (Station::Get(this->vli.index)->facilities & FACIL_WAYPOINT) return; + if (BaseStation::Get(this->vli.index)->facilities & FACIL_WAYPOINT) return; StationID st_index = GetStationIndex(tile); const Station *st = Station::Get(st_index); @@ -3754,7 +3755,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, v->GetDisplaySpeed()); diff --git a/src/viewport_type.h b/src/viewport_type.h index f4494a0951..f84a73f757 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -194,8 +194,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 eb9ef17ac6..68992808c3 100644 --- a/src/waypoint.cpp +++ b/src/waypoint.cpp @@ -41,6 +41,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 649f1ed856..07e057a5a5 100644 --- a/src/waypoint_base.h +++ b/src/waypoint_base.h @@ -17,6 +17,7 @@ */ enum WaypointFlags { WPF_HIDE_LABEL = 0, ///< Hide waypoint label + WPF_ROAD = 1, ///< This is a road waypoint }; /** Representation of a waypoint. */ @@ -24,6 +25,8 @@ struct Waypoint FINAL : SpecializedStation { uint16 town_cn; ///< The N-1th waypoint for this town (consecutive number) uint16 waypoint_flags; ///< Waypoint flags, see WaypointFlags + TileArea road_waypoint_area; ///< Tile area the road waypoint part covers + /** * Create a waypoint at the given tile. * @param tile The location of the waypoint. diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index 40c4e49a91..6d665eb4df 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -68,13 +68,13 @@ void Waypoint::MoveSign(TileIndex new_xy) * @param cid previous owner of the 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) { @@ -109,6 +109,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); /** @@ -152,7 +175,7 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID * } extern void GetStationLayout(byte *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, Axis axis); extern CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, byte layout); @@ -232,15 +255,16 @@ CommandCost CmdBuildRailWaypoint(TileIndex start_tile, DoCommandFlag flags, uint Waypoint *wp = nullptr; TileArea new_location(start_tile, width, height); - 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. */ + 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); /* check if we want to expand an already existing waypoint? */ @@ -301,6 +325,134 @@ CommandCost CmdBuildRailWaypoint(TileIndex start_tile, DoCommandFlag flags, uint return CommandCost(EXPENSES_CONSTRUCTION, count * _price[PR_BUILD_WAYPOINT_RAIL]); } +/** + * Convert existing road to waypoint. Eg build a waypoint station over + * piece of road + * @param start_tile northern most tile where waypoint will be built + * @param flags Operation to perform. + * @param p1 bit 0..7: Width of the road stop. + * bit 8..15: Length of the road stop. + * bit 16: Allow stations directly adjacent to other stations. + * bit 17: #Axis of the road. + * @param p2 bit 16..31: Station ID to join (NEW_STATION if build new one). + * @param text Unused. + * @return The cost of this operation or an error. + */ +CommandCost CmdBuildRoadWaypoint(TileIndex start_tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + StationID station_to_join = GB(p2, 16, 16); + byte width = GB(p1, 0, 8); + byte height = GB(p1, 8, 8); + bool adjacent = HasBit(p1, 16); + Axis axis = Extract(p1); + + /* The number of parts to build */ + byte 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; + + /* Check if the first tile and the last tile are valid */ + if (!IsValidTile(start_tile) || TileAddWrap(start_tile, width - 1, height - 1) == INVALID_TILE) return CMD_ERROR; + + TileArea roadstop_area(start_tile, width, height); + /* Total road stop cost. */ + CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[PR_BUILD_STATION_TRUCK]); + StationID est = INVALID_STATION; + extern CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt, bool require_road); + CommandCost ret = CheckFlatLandRoadStop(roadstop_area, flags, 5 << axis, true, STATION_ROADWAYPOINT, axis, &est, INVALID_ROADTYPE, true); + if (ret.Failed()) return ret; + cost.AddCost(ret); + + Waypoint *wp = nullptr; + 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); + + CommandCost 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); + } + + 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); + + wp->delete_ctr = 0; + wp->facilities |= FACIL_BUS_STOP | FACIL_TRUCK_STOP; + wp->build_date = _date; + wp->string_id = STR_SV_STNAME_WAYPOINT; + + if (wp->town == nullptr) MakeDefaultName(wp); + + wp->UpdateVirtCoord(); + + /* 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; + DisallowedRoadDirections drd = IsNormalRoadTile(cur_tile) ? GetDisallowedRoadDirections(cur_tile) : DRD_NONE; + + extern CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags); + if (IsTileType(cur_tile, MP_STATION) && IsAnyRoadStop(cur_tile)) { + RemoveRoadStop(cur_tile, flags); + } + + 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); + SetDriveThroughStopDisallowedRoadDirections(cur_tile, drd); + + Company::Get(wp->owner)->infrastructure.station++; + + MarkTileDirtyByTile(cur_tile); + UpdateRoadCachedOneWayStatesAroundTile(cur_tile); + } + NotifyRoadLayoutChanged(true); + DirtyCompanyInfrastructureWindows(wp->owner); + } + return cost; +} + /** * Build a buoy. * @param tile tile where to place the buoy @@ -317,7 +469,7 @@ CommandCost CmdBuildBuoy(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 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_func.h b/src/waypoint_func.h index 2906fa6369..ba816957da 100644 --- a/src/waypoint_func.h +++ b/src/waypoint_func.h @@ -17,6 +17,7 @@ CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags); Axis GetAxisForNewWaypoint(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 44a75a9a27..c417843222 100644 --- a/src/waypoint_gui.cpp +++ b/src/waypoint_gui.cpp @@ -45,7 +45,24 @@ private: if (!this->wp->IsInUse()) return this->wp->xy; TileArea ta; - this->wp->GetTileArea(&ta, this->vt == VEH_TRAIN ? STATION_WAYPOINT : STATION_BUOY); + 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(); + } + this->wp->GetTileArea(&ta, type); return ta.GetCenterTile(); } @@ -58,15 +75,24 @@ 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; } - this->show_hide_label = (this->vt == VEH_TRAIN && _settings_client.gui.allow_hiding_waypoint_labels); + this->show_hide_label = (this->vt != VEH_SHIP && _settings_client.gui.allow_hiding_waypoint_labels); this->GetWidget(WID_W_TOGGLE_HIDDEN_SEL)->SetDisplayedPlane(this->show_hide_label ? 0 : SZSP_NONE); this->FinishInitNested(window_number); @@ -135,7 +161,7 @@ public: this->SetWidgetLoweredState(WID_W_TOGGLE_HIDDEN, HasBit(this->wp->waypoint_flags, WPF_HIDE_LABEL)); - bool show_hide_label = (this->vt == VEH_TRAIN && _settings_client.gui.allow_hiding_waypoint_labels); + bool show_hide_label = (this->vt != VEH_SHIP && _settings_client.gui.allow_hiding_waypoint_labels); if (show_hide_label != this->show_hide_label) { this->show_hide_label = show_hide_label; this->GetWidget(WID_W_TOGGLE_HIDDEN_SEL)->SetDisplayedPlane(this->show_hide_label ? 0 : SZSP_NONE); diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index 3d49e9ae9d..5d9ea034af 100644 --- a/src/widgets/road_widget.h +++ b/src/widgets/road_widget.h @@ -19,6 +19,7 @@ enum RoadToolbarWidgets { 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.