diff --git a/docs/landscape.html b/docs/landscape.html index f592cd0f6a..c48aedcebc 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -876,6 +876,22 @@
  • m2: index into the array of stations
  • m3 bits 7..4: persistent random data for railway stations/waypoints and airports)
  • m3 bits 7..4: owner of tram tracks (road stop)
  • +
  • m3 bits 3..2: ground type (road waypoints) + + + + + + + + + + + + + +
    0  on bare land
    1  on grass
    2  paved
    +
  • m4: custom station id; 0 means standard graphics
  • m4: Roadtype for road stops
  • m5: graphics index (range from 0..255 for each station type): @@ -990,13 +1006,14 @@
  • m6 bit 7: rail station / waypoint may have catenary pylons
  • -
  • m6 bit 6: rail station / waypoint may have catenary wires
  • -
  • m6 bits 5..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
  • m7 bits 4..0: owner of road (road stops)
  • m7: animation frame (railway stations/waypoints, airports)
  • +
  • m8 bit 15: Snow or desert present (road waypoints)
  • m8 bits 11..6: Tramtype
  • m8 bits 5..0: track type for railway stations/waypoints
  • m8 bits 5..0: custom road stop id; 0 means standard graphics
  • diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index a0fa527a6b..11e5952777 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -181,14 +181,14 @@ the array so you can quickly see what is used and what is not. OOOO OOOO OOOO OOOO - 5 + 5 rail station - OXX XXXXX - XXXX XXXX XXXX XXXX + OXX XXXXX + XXXX XXXX XXXX XXXX XXXX OOOO XXXX XXXX XXXX XXXX - XXXXX XOX + XXXXX XXX XXXX XXXX OOOO OOOO OOXX XXXX @@ -199,12 +199,17 @@ the array so you can quickly see what is used and what is not. road stop XXXX OOOO - OOXX XXXX - OOOO OXXX - OOXX XOOO - OOOX XXXX + OOXX XXXX + OOOO OXXX + OXXX XOOO + OOOX XXXX OOOO XXXX XX XXXXXX + + road waypoint + XXXX XXOO + XOOO XXXX XXOO OOOO + airport XXXX OOOO diff --git a/media/baseset/openttd.grf b/media/baseset/openttd.grf index 7f4f6cbc49..6cee79959e 100644 Binary files a/media/baseset/openttd.grf and b/media/baseset/openttd.grf differ diff --git a/media/baseset/openttd.grf.hash b/media/baseset/openttd.grf.hash index 25a50247f5..f3b216de53 100644 --- a/media/baseset/openttd.grf.hash +++ b/media/baseset/openttd.grf.hash @@ -1 +1 @@ -4f03553f614a06d86dc06376db3353c7 +8bc3926cb50e19747de498357417d973 diff --git a/media/baseset/openttd/CMakeLists.txt b/media/baseset/openttd/CMakeLists.txt index 30844b8804..ee93ba80b1 100644 --- a/media/baseset/openttd/CMakeLists.txt +++ b/media/baseset/openttd/CMakeLists.txt @@ -20,6 +20,7 @@ if(GRFCODEC_FOUND) ${CMAKE_CURRENT_SOURCE_DIR}/openttdgui.nfo ${CMAKE_CURRENT_SOURCE_DIR}/palette.nfo ${CMAKE_CURRENT_SOURCE_DIR}/roadstops.nfo + ${CMAKE_CURRENT_SOURCE_DIR}/road_waypoints.nfo ${CMAKE_CURRENT_SOURCE_DIR}/signals.nfo ${CMAKE_CURRENT_SOURCE_DIR}/sloped_tracks.nfo ${CMAKE_CURRENT_SOURCE_DIR}/tramtracks.nfo @@ -42,6 +43,7 @@ if(GRFCODEC_FOUND) ${CMAKE_CURRENT_SOURCE_DIR}/openttdgui_convert_tram.png ${CMAKE_CURRENT_SOURCE_DIR}/openttdgui_group_livery.png ${CMAKE_CURRENT_SOURCE_DIR}/roadstops.png + ${CMAKE_CURRENT_SOURCE_DIR}/road_waypoints.png ${CMAKE_CURRENT_SOURCE_DIR}/signals.png ${CMAKE_CURRENT_SOURCE_DIR}/sloped_tracks.png ${CMAKE_CURRENT_SOURCE_DIR}/tramtracks.png diff --git a/media/baseset/openttd/openttd.nfo b/media/baseset/openttd/openttd.nfo index c1ed751c55..3d59f08543 100644 --- a/media/baseset/openttd/openttd.nfo +++ b/media/baseset/openttd/openttd.nfo @@ -98,3 +98,4 @@ #include "mono.nfo" #include "tunnel_portals.nfo" #include "palette.nfo" +#include "road_waypoints.nfo" diff --git a/media/baseset/openttd/road_waypoints.nfo b/media/baseset/openttd/road_waypoints.nfo new file mode 100644 index 0000000000..4e7a27019b --- /dev/null +++ b/media/baseset/openttd/road_waypoints.nfo @@ -0,0 +1,14 @@ +// This file is part of OpenTTD. +// OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. +// OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . +// + + -1 * 0 0C "Road waypoints" +//@@LINT OFF + -1 * 3 05 19 04 +//@@LINT ON + -1 sprites/road_waypoints.png 8bpp 10 10 64 40 -5 -22 normal + -1 sprites/road_waypoints.png 8bpp 90 10 64 40 -31 -9 normal + -1 sprites/road_waypoints.png 8bpp 170 10 64 35 -31 -4 normal + -1 sprites/road_waypoints.png 8bpp 240 10 64 35 -57 -17 normal diff --git a/media/baseset/openttd/road_waypoints.png b/media/baseset/openttd/road_waypoints.png new file mode 100644 index 0000000000..2934cb5b22 Binary files /dev/null and b/media/baseset/openttd/road_waypoints.png differ diff --git a/src/command_type.h b/src/command_type.h index dae768104b..3913131350 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -207,6 +207,9 @@ enum Commands : uint16_t { CMD_RENAME_WAYPOINT, ///< rename a waypoint CMD_REMOVE_FROM_RAIL_WAYPOINT, ///< remove a (rectangle of) tiles from a rail waypoint + CMD_BUILD_ROAD_WAYPOINT, ///< build a road waypoint + CMD_REMOVE_FROM_ROAD_WAYPOINT, ///< remove a (rectangle of) tiles from a road waypoint + CMD_BUILD_ROAD_STOP, ///< build a road stop CMD_REMOVE_ROAD_STOP, ///< remove a road stop CMD_BUILD_LONG_ROAD, ///< build a complete road (not a "half" one) diff --git a/src/lang/english.txt b/src/lang/english.txt index 36fea5c491..816757e324 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2881,6 +2881,8 @@ STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD :{BLACK}Build ro STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM :{BLACK}Build tramway section using the Autotram mode. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build road vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build tram vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT :{BLACK}Convert road to waypoint. Ctrl enables joining waypoints. Shift toggles building/showing cost estimate +STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT :{BLACK}Convert tram to waypoint. Ctrl enables joining waypoints. Shift toggles building/showing cost estimate STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION :{BLACK}Build bus station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION :{BLACK}Build passenger tram station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY :{BLACK}Build lorry station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only @@ -5088,11 +5090,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! @@ -5340,6 +5345,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.cpp b/src/newgrf.cpp index 0b8628f370..f6ebcff603 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -4887,7 +4887,7 @@ static ChangeInfoResult RoadStopChangeInfo(uint id, int numinfo, int prop, ByteR break; case 0x12: // General flags - rs->flags = (uint8_t)buf->ReadDWord(); // Future-proofing, size this as 4 bytes, but we only need one byte's worth of flags at present + rs->flags = (uint16_t)buf->ReadDWord(); // Future-proofing, size this as 4 bytes, but we only need two byte's worth of flags at present break; case 0x15: // Cost multipliers @@ -6425,6 +6425,7 @@ static constexpr auto _action5_types = std::to_array({ /* 0x16 */ { A5BLOCK_ALLOW_OFFSET, SPR_AIRPORT_PREVIEW_BASE, 1, SPR_AIRPORT_PREVIEW_COUNT, "Airport preview graphics" }, /* 0x17 */ { A5BLOCK_ALLOW_OFFSET, SPR_RAILTYPE_TUNNEL_BASE, 1, RAILTYPE_TUNNEL_BASE_COUNT, "Railtype tunnel base" }, /* 0x18 */ { A5BLOCK_ALLOW_OFFSET, SPR_PALETTE_BASE, 1, PALETTE_SPRITE_COUNT, "Palette" }, + /* 0x19 */ { A5BLOCK_ALLOW_OFFSET, SPR_ROAD_WAYPOINTS_BASE, 1, ROAD_WAYPOINTS_SPRITE_COUNT, "Road waypoints" }, }); /** diff --git a/src/newgrf_commons.cpp b/src/newgrf_commons.cpp index 9589e31ef4..822b641cbe 100644 --- a/src/newgrf_commons.cpp +++ b/src/newgrf_commons.cpp @@ -437,6 +437,9 @@ uint32_t GetNearbyTileInformation(TileIndex tile, bool grf_version8) /* Fake tile type for trees on shore */ if (IsTileType(tile, MP_TREES) && GetTreeGround(tile) == TREE_GROUND_SHORE) tile_type = MP_WATER; + /* Fake tile type for road waypoints */ + if (IsRoadWaypointTile(tile)) tile_type = MP_ROAD; + auto [tileh, z] = GetTilePixelSlope(tile); /* Return 0 if the tile is a land tile */ uint8_t terrain_type = (HasTileWaterClass(tile) ? (GetWaterClass(tile) + 1) & 3 : 0) << 5 | GetTerrainType(tile) << 2 | (tile_type == MP_WATER ? 1 : 0) << 1; diff --git a/src/newgrf_roadstop.cpp b/src/newgrf_roadstop.cpp index eaa76fe5f9..aaafd88738 100644 --- a/src/newgrf_roadstop.cpp +++ b/src/newgrf_roadstop.cpp @@ -119,6 +119,15 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u /* Animation frame */ case 0x49: return this->tile == INVALID_TILE ? 0 : this->st->GetRoadStopAnimationFrame(this->tile); + /* Misc info */ + case 0x50: { + uint32_t result = 0; + if (this->tile == INVALID_TILE) { + SetBit(result, 4); + } + return result; + } + /* Variables which use the parameter */ /* Variables 0x60 to 0x65 and 0x69 are handled separately below */ @@ -127,7 +136,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 +152,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 +160,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 +175,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 +186,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; @@ -283,7 +293,19 @@ void DrawRoadStopTile(int x, int y, RoadType roadtype, const RoadStopSpec *spec, SpriteID image = dts->ground.sprite; PaletteID pal = dts->ground.pal; - if (GB(image, 0, SPRITE_WIDTH) != 0) { + RoadStopDrawMode draw_mode; + if (HasBit(spec->flags, RSF_DRAW_MODE_REGISTER)) { + draw_mode = (RoadStopDrawMode)GetRegister(0x100); + } else { + draw_mode = spec->draw_mode; + } + + if (type == STATION_ROADWAYPOINT) { + DrawSprite(SPR_ROAD_PAVED_STRAIGHT_X, PAL_NONE, x, y); + if ((draw_mode & ROADSTOP_DRAW_MODE_WAYP_GROUND) && GB(image, 0, SPRITE_WIDTH) != 0) { + DrawSprite(image, GroundSpritePaletteTransform(image, pal, palette), x, y); + } + } else if (GB(image, 0, SPRITE_WIDTH) != 0) { DrawSprite(image, GroundSpritePaletteTransform(image, pal, palette), x, y); } @@ -292,7 +314,7 @@ void DrawRoadStopTile(int x, int y, RoadType roadtype, const RoadStopSpec *spec, uint sprite_offset = 5 - view; /* Road underlay takes precedence over tram */ - if (spec->draw_mode & ROADSTOP_DRAW_MODE_OVERLAY) { + if (type == STATION_ROADWAYPOINT || draw_mode & ROADSTOP_DRAW_MODE_OVERLAY) { if (rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_GROUND); DrawSprite(ground + sprite_offset, PAL_NONE, x, y); @@ -305,7 +327,7 @@ void DrawRoadStopTile(int x, int y, RoadType roadtype, const RoadStopSpec *spec, } } else { /* Bay stop */ - if ((spec->draw_mode & ROADSTOP_DRAW_MODE_ROAD) && rti->UsesOverlay()) { + if ((draw_mode & ROADSTOP_DRAW_MODE_ROAD) && rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_ROADSTOP); DrawSprite(ground + view, PAL_NONE, x, y); } diff --git a/src/newgrf_roadstop.h b/src/newgrf_roadstop.h index 4a6980883d..a2db330cef 100644 --- a/src/newgrf_roadstop.h +++ b/src/newgrf_roadstop.h @@ -59,6 +59,7 @@ enum RoadStopDrawMode : uint8_t { ROADSTOP_DRAW_MODE_NONE = 0, ROADSTOP_DRAW_MODE_ROAD = 1 << 0, ///< Bay stops: Draw the road itself ROADSTOP_DRAW_MODE_OVERLAY = 1 << 1, ///< Drive-through stops: Draw the road overlay, e.g. pavement + ROADSTOP_DRAW_MODE_WAYP_GROUND = 1 << 2, ///< Waypoints: Draw the sprite layout ground tile (on top of the road) }; DECLARE_ENUM_AS_BIT_SET(RoadStopDrawMode) @@ -69,6 +70,7 @@ enum RoadStopSpecFlags { RSF_NO_AUTO_ROAD_CONNECTION = 4, ///< No auto road connection. RSF_BUILD_MENU_ROAD_ONLY = 5, ///< Only show in the road build menu (not tram). RSF_BUILD_MENU_TRAM_ONLY = 6, ///< Only show in the tram build menu (not road). + RSF_DRAW_MODE_REGISTER = 8, ///< Read draw mode from register 0x100. }; /** Scope resolver for road stops. */ @@ -132,7 +134,7 @@ struct RoadStopSpec { RoadStopAvailabilityType stop_type = ROADSTOPTYPE_ALL; RoadStopDrawMode draw_mode = ROADSTOP_DRAW_MODE_ROAD | ROADSTOP_DRAW_MODE_OVERLAY; uint8_t callback_mask = 0; - uint8_t flags = 0; + uint16_t flags = 0; CargoTypes cargo_triggers = 0; ///< Bitmask of cargo types which cause trigger re-randomizing diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 003d879303..754e6767ff 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -831,6 +831,14 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se break; } + case VEH_ROAD: { + if (!(wp->facilities & FACIL_BUS_STOP) || !(wp->facilities & FACIL_TRUCK_STOP)) return CommandCost(STR_ERROR_CAN_T_ADD_ORDER, STR_ERROR_NO_ROAD_WAYPOINT); + + ret = CheckOwnership(wp->owner); + if (ret.Failed()) return ret; + break; + } + case VEH_SHIP: if (!(wp->facilities & FACIL_DOCK)) return CommandCost(STR_ERROR_CAN_T_ADD_ORDER, STR_ERROR_NO_BUOY); if (wp->owner != OWNER_NONE) { @@ -842,8 +850,8 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se /* Order flags can be any of the following for waypoints: * [non-stop] - * non-stop orders (if any) are only valid for trains */ - if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && v->type != VEH_TRAIN) return CMD_ERROR; + * non-stop orders (if any) are only valid for trains and road vehicles */ + if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR; break; } diff --git a/src/order_gui.cpp b/src/order_gui.cpp index 397502cd32..fe063dd66e 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -435,6 +435,15 @@ static Order GetOrderCmdFromTile(const Vehicle *v, TileIndex tile) return order; } + /* check road waypoint */ + if (IsRoadWaypointTile(tile) && + v->type == VEH_ROAD && + IsTileOwner(tile, _local_company)) { + order.MakeGoToWaypoint(GetStationIndex(tile)); + if (_settings_client.gui.new_nonstop != _ctrl_pressed) order.SetNonStopType(ONSF_NO_STOP_AT_ANY_STATION); + return order; + } + /* check buoy (no ownership) */ if (IsBuoyTile(tile) && v->type == VEH_SHIP) { order.MakeGoToWaypoint(GetStationIndex(tile)); diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index 513d3ab0c7..28b4d92375 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -225,7 +225,7 @@ protected: /* special handling for stations */ if (IsRailTT() && HasStationTileRail(m_new_tile)) { m_is_station = true; - } else if (IsRoadTT() && IsRoadStopTile(m_new_tile)) { + } else if (IsRoadTT() && IsStationRoadStopTile(m_new_tile)) { m_is_station = true; } } diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp index 5ac43cd8c9..060d36ff7e 100644 --- a/src/pathfinder/npf/npf.cpp +++ b/src/pathfinder/npf/npf.cpp @@ -359,6 +359,7 @@ static int32_t NPFRoadPathCost(AyStar *, AyStarNode *current, OpenListNode *) case MP_STATION: { cost = NPF_TILE_LENGTH; + if (IsRoadWaypoint(tile)) break; const RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); if (IsDriveThroughStopTile(tile)) { /* Increase the cost for drive-through road stops */ @@ -1132,7 +1133,7 @@ static void NPFFillWithOrderData(NPFFindStationOrTileData *fstd, const Vehicle * if (v->type == VEH_TRAIN) { fstd->station_type = v->current_order.IsType(OT_GOTO_STATION) ? STATION_RAIL : STATION_WAYPOINT; } else if (v->type == VEH_ROAD) { - fstd->station_type = RoadVehicle::From(v)->IsBus() ? STATION_BUS : STATION_TRUCK; + fstd->station_type = v->current_order.IsType(OT_GOTO_STATION) ? (RoadVehicle::From(v)->IsBus() ? STATION_BUS : STATION_TRUCK) : STATION_ROADWAYPOINT; } else if (v->type == VEH_SHIP) { fstd->station_type = v->current_order.IsType(OT_GOTO_STATION) ? STATION_DOCK : STATION_BUOY; } diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 41102c2e15..209b64b52a 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -70,6 +70,8 @@ protected: break; case MP_STATION: { + if (IsRoadWaypoint(tile)) break; + const RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); if (IsDriveThroughStopTile(tile)) { /* Increase the cost for drive-through road stops */ @@ -232,7 +234,7 @@ protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station; - bool m_bus; + StationType m_station_type; bool m_non_artic; public: @@ -240,8 +242,14 @@ public: { if (v->current_order.IsType(OT_GOTO_STATION)) { m_dest_station = v->current_order.GetDestination(); - m_bus = v->IsBus(); - m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_bus ? STATION_BUS : STATION_TRUCK); + m_station_type = v->IsBus() ? STATION_BUS : STATION_TRUCK; + m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type); + m_non_artic = !v->HasArticulatedPart(); + m_destTrackdirs = INVALID_TRACKDIR_BIT; + } else if (v->current_order.IsType(OT_GOTO_WAYPOINT)) { + m_dest_station = v->current_order.GetDestination(); + m_station_type = STATION_ROADWAYPOINT; + m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type); m_non_artic = !v->HasArticulatedPart(); m_destTrackdirs = INVALID_TRACKDIR_BIT; } else { @@ -275,7 +283,7 @@ public: if (m_dest_station != INVALID_STATION) { return IsTileType(tile, MP_STATION) && GetStationIndex(tile) == m_dest_station && - (m_bus ? IsBusStop(tile) : IsTruckStop(tile)) && + (m_station_type == GetStationType(tile)) && (m_non_artic || IsDriveThroughStopTile(tile)); } diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 137e78d494..1a98a9b6db 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -163,7 +163,7 @@ static void PlaceRail_Waypoint(TileIndex tile) return; } - Axis axis = GetAxisForNewWaypoint(tile); + Axis axis = GetAxisForNewRailWaypoint(tile); if (IsValidAxis(axis)) { /* Valid tile for waypoints */ VpStartPlaceSizing(tile, axis == AXIS_X ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_STATION); @@ -776,7 +776,7 @@ struct BuildRailToolbarWindow : Window { } }; - ShowSelectWaypointIfNeeded(ta, proc); + ShowSelectRailWaypointIfNeeded(ta, proc); } } break; @@ -816,7 +816,7 @@ struct BuildRailToolbarWindow : Window { void OnRealtimeTick([[maybe_unused]] uint delta_ms) override { - if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) CheckRedrawWaypointCoverage(this); + if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) CheckRedrawRailWaypointCoverage(this); } /** @@ -2203,7 +2203,7 @@ struct BuildRailWaypointWindow : PickerWindowBase { void OnRealtimeTick([[maybe_unused]] uint delta_ms) override { - CheckRedrawWaypointCoverage(this); + CheckRedrawRailWaypointCoverage(this); } }; diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index eee9e24efb..db238b76ff 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1359,13 +1359,13 @@ static uint GetRoadSpriteOffset(Slope slope, RoadBits bits) * By default, roads are always drawn as unpaved if they are on desert or * above the snow line, but NewGRFs can override this for desert. * - * @param tile The tile the road is on + * @param snow_or_desert Is snowy or desert tile * @param roadside What sort of road this is * @return True if snow/desert road sprites should be used. */ -static bool DrawRoadAsSnowDesert(TileIndex tile, Roadside roadside) +static bool DrawRoadAsSnowDesert(bool snow_or_desert, Roadside roadside) { - return (IsOnSnow(tile) && + return (snow_or_desert && !(_settings_game.game_creation.landscape == LT_TROPIC && HasGrfMiscBit(GMB_DESERT_PAVED_ROADS) && roadside != ROADSIDE_BARREN && roadside != ROADSIDE_GRASS && roadside != ROADSIDE_GRASS_ROAD_WORKS)); } @@ -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); @@ -1556,13 +1556,14 @@ void DrawRoadOverlays(const TileInfo *ti, PaletteID pal, const RoadTypeInfo *roa * @param roadside Road side type * @param rti Road type info * @param offset Road sprite offset + * @param snow_or_desert Whether to get snow/desert ground sprite * @param[out] pal Palette to draw. */ -static SpriteID GetRoadGroundSprite(const TileInfo *ti, Roadside roadside, const RoadTypeInfo *rti, uint offset, PaletteID *pal) +static SpriteID GetRoadGroundSprite(const TileInfo *ti, Roadside roadside, const RoadTypeInfo *rti, uint offset, bool snow_or_desert, PaletteID *pal) { /* Draw bare ground sprite if no road or road uses overlay system. */ if (rti == nullptr || rti->UsesOverlay()) { - if (DrawRoadAsSnowDesert(ti->tile, roadside)) { + if (DrawRoadAsSnowDesert(snow_or_desert, roadside)) { return SPR_FLAT_SNOW_DESERT_TILE + SlopeToSpriteOffset(ti->tileh); } @@ -1577,7 +1578,7 @@ static SpriteID GetRoadGroundSprite(const TileInfo *ti, Roadside roadside, const /* Draw original road base sprite */ SpriteID image = SPR_ROAD_Y + offset; - if (DrawRoadAsSnowDesert(ti->tile, roadside)) { + if (DrawRoadAsSnowDesert(snow_or_desert, roadside)) { image += 19; } else { switch (roadside) { @@ -1591,6 +1592,30 @@ static SpriteID GetRoadGroundSprite(const TileInfo *ti, Roadside roadside, const return image; } +/** + * Draw road ground sprites. + * @param ti TileInfo + * @param road Road bits + * @param tram Tram bits + * @param road_rti Road road type information + * @param tram_rti Tram road type information + * @param roadside Roadside type + * @param snow_or_desert Whether to draw snow/desert ground sprites + */ +void DrawRoadGroundSprites(const TileInfo *ti, RoadBits road, RoadBits tram, const RoadTypeInfo *road_rti, const RoadTypeInfo *tram_rti, Roadside roadside, bool snow_or_desert) +{ + /* Determine sprite offsets */ + uint road_offset = GetRoadSpriteOffset(ti->tileh, road); + uint tram_offset = GetRoadSpriteOffset(ti->tileh, tram); + + /* Draw baseset underlay */ + PaletteID pal = PAL_NONE; + SpriteID image = GetRoadGroundSprite(ti, roadside, road_rti, road == ROAD_NONE ? tram_offset : road_offset, snow_or_desert, &pal); + DrawGroundSprite(image, pal); + + DrawRoadOverlays(ti, pal, road_rti, tram_rti, road_offset, tram_offset); +} + /** * Draw ground sprite and road pieces * @param ti TileInfo @@ -1610,18 +1635,7 @@ static void DrawRoadBits(TileInfo *ti) /* DrawFoundation() modifies ti. */ } - /* Determine sprite offsets */ - uint road_offset = GetRoadSpriteOffset(ti->tileh, road); - uint tram_offset = GetRoadSpriteOffset(ti->tileh, tram); - - /* Draw baseset underlay */ - Roadside roadside = GetRoadside(ti->tile); - - PaletteID pal = PAL_NONE; - SpriteID image = GetRoadGroundSprite(ti, roadside, road_rti, road == ROAD_NONE ? tram_offset : road_offset, &pal); - DrawGroundSprite(image, pal); - - DrawRoadOverlays(ti, pal, road_rti, tram_rti, road_offset, tram_offset); + DrawRoadGroundSprites(ti, road, tram, road_rti, tram_rti, GetRoadside(ti->tile), IsOnSnow(ti->tile)); /* Draw one way */ if (road_rti != nullptr) { @@ -1654,6 +1668,7 @@ static void DrawRoadBits(TileInfo *ti) if (!HasBit(_display_opt, DO_FULL_DETAIL) || _cur_dpi->zoom > ZOOM_LVL_DETAIL) return; /* Do not draw details (street lights, trees) under low bridge */ + Roadside roadside = GetRoadside(ti->tile); if (IsBridgeAbove(ti->tile) && (roadside == ROADSIDE_TREES || roadside == ROADSIDE_STREET_LIGHTS)) { int height = GetBridgeHeight(GetNorthernBridgeEnd(ti->tile)); int minz = GetTileMaxZ(ti->tile) + 2; @@ -1712,7 +1727,7 @@ static void DrawTile_Road(TileInfo *ti) SpriteID image = SPR_ROAD_Y + axis; Roadside roadside = GetRoadside(ti->tile); - if (DrawRoadAsSnowDesert(ti->tile, roadside)) { + if (DrawRoadAsSnowDesert(IsOnSnow(ti->tile), roadside)) { image += 19; } else { switch (roadside) { @@ -1728,7 +1743,7 @@ static void DrawTile_Road(TileInfo *ti) if (IsCrossingBarred(ti->tile)) image += 2; Roadside roadside = GetRoadside(ti->tile); - if (DrawRoadAsSnowDesert(ti->tile, roadside)) { + if (DrawRoadAsSnowDesert(IsOnSnow(ti->tile), roadside)) { image += 8; } else { switch (roadside) { @@ -2448,7 +2463,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_func.h b/src/road_func.h index 7c73597b9f..ed3e3db6d0 100644 --- a/src/road_func.h +++ b/src/road_func.h @@ -159,6 +159,8 @@ void UpdateAdjacentLevelCrossingTilesOnLevelCrossingRemoval(TileIndex tile, Axis void UpdateCompanyRoadInfrastructure(RoadType rt, Owner o, int count); struct TileInfo; +enum Roadside : uint8_t; void DrawRoadOverlays(const TileInfo *ti, PaletteID pal, const RoadTypeInfo *road_rti, const RoadTypeInfo *tram_rit, uint road_offset, uint tram_offset, bool draw_underlay = true); +void DrawRoadGroundSprites(const TileInfo *ti, RoadBits road, RoadBits tram, const RoadTypeInfo *road_rti, const RoadTypeInfo *tram_rti, Roadside roadside, bool snow_or_desert); #endif /* ROAD_FUNC_H */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index efb8cc2713..d2b3617b16 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -16,6 +16,7 @@ #include "command_func.h" #include "road_cmd.h" #include "station_func.h" +#include "waypoint_func.h" #include "window_func.h" #include "vehicle_func.h" #include "sound_func.h" @@ -33,6 +34,7 @@ #include "strings_func.h" #include "core/geometry_func.hpp" #include "station_cmd.h" +#include "waypoint_cmd.h" #include "road_cmd.h" #include "tunnelbridge_cmd.h" #include "newgrf_roadstop.h" @@ -51,6 +53,7 @@ static void ShowRVStationPicker(Window *parent, RoadStopType rs); static void ShowRoadDepotPicker(Window *parent); +static void ShowBuildRoadWaypointPicker(Window *parent); static bool _remove_button_clicked; static bool _one_way_button_clicked; @@ -64,6 +67,8 @@ static RoadType _cur_roadtype; static DiagDirection _road_depot_orientation; +static uint16_t _cur_waypoint_type; ///< Currently selected waypoint type + struct RoadStopGUISettings { DiagDirection orientation; @@ -236,6 +241,29 @@ static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, RoadStopType ShowSelectStationIfNeeded(ta, proc); } +/** + * Place a road waypoint. + * @param tile Position to start dragging a waypoint. + */ +static void PlaceRoad_Waypoint(TileIndex tile) +{ + if (_remove_button_clicked) { + VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_ROAD_WAYPOINT); + return; + } + + Axis axis = GetAxisForNewRoadWaypoint(tile); + if (IsValidAxis(axis)) { + /* Valid tile for waypoints */ + VpStartPlaceSizing(tile, axis == AXIS_X ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_ROAD_WAYPOINT); + VpSetPlaceSizingLimit(_settings_game.station.station_spread); + } else { + /* Tile where we can't build road waypoints. This is always going to fail, + * but provides the user with a proper error message. */ + Command::Post(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT, tile, AXIS_X, 1, 1, ROADSTOP_CLASS_WAYP, 0, INVALID_STATION, false); + } +} + /** * Callback for placing a bus station. * @param tile Position to place the station. @@ -349,22 +377,26 @@ struct BuildRoadToolbarWindow : Window { bool can_build = CanBuildVehicleInfrastructure(VEH_ROAD, rtt); this->SetWidgetsDisabledState(!can_build, WID_ROT_DEPOT, + WID_ROT_BUILD_WAYPOINT, WID_ROT_BUS_STATION, WID_ROT_TRUCK_STATION); if (!can_build) { CloseWindowById(WC_BUS_STATION, TRANSPORT_ROAD); CloseWindowById(WC_TRUCK_STATION, TRANSPORT_ROAD); CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD); + CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_ROAD); } if (_game_mode != GM_EDITOR) { if (!can_build) { /* Show in the tooltip why this button is disabled. */ this->GetWidget(WID_ROT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); } else { this->GetWidget(WID_ROT_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT); + this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT : STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION); } @@ -444,6 +476,7 @@ struct BuildRoadToolbarWindow : Window { case WID_ROT_BUS_STATION: case WID_ROT_TRUCK_STATION: + case WID_ROT_BUILD_WAYPOINT: if (RoadTypeIsRoad(this->roadtype)) this->DisableWidget(WID_ROT_ONE_WAY); this->SetWidgetDisabledState(WID_ROT_REMOVE, !this->IsWidgetLowered(clicked_widget)); break; @@ -504,6 +537,13 @@ struct BuildRoadToolbarWindow : Window { } break; + case WID_ROT_BUILD_WAYPOINT: + this->last_started_action = widget; + if (HandlePlacePushButton(this, WID_ROT_BUILD_WAYPOINT, SPR_CURSOR_WAYPOINT, HT_RECT) && RoadStopClass::Get(ROADSTOP_CLASS_WAYP)->GetSpecCount() > 1) { + ShowBuildRoadWaypointPicker(this); + } + break; + case WID_ROT_BUS_STATION: if (HandlePlacePushButton(this, WID_ROT_BUS_STATION, SPR_CURSOR_BUS_STATION, HT_RECT)) { ShowRVStationPicker(this, ROADSTOP_BUS); @@ -593,6 +633,10 @@ struct BuildRoadToolbarWindow : Window { tile, _cur_roadtype, _road_depot_orientation); break; + case WID_ROT_BUILD_WAYPOINT: + PlaceRoad_Waypoint(tile); + break; + case WID_ROT_BUS_STATION: PlaceRoad_BusStation(tile); break; @@ -634,6 +678,7 @@ struct BuildRoadToolbarWindow : Window { CloseWindowById(WC_BUS_STATION, TRANSPORT_ROAD); CloseWindowById(WC_TRUCK_STATION, TRANSPORT_ROAD); CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD); + CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_ROAD); CloseWindowById(WC_SELECT_STATION, 0); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -706,6 +751,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 = _cur_waypoint_type; + + auto proc = [=](bool test, StationID to_join) -> bool { + if (test) { + return Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), ta.tile, axis, ta.w, ta.h, ROADSTOP_CLASS_WAYP, waypoint_type, INVALID_STATION, adjacent).Succeeded(); + } else { + return Command::Post(STR_ERROR_CAN_T_BUILD_ROAD_WAYPOINT, CcPlaySound_CONSTRUCTION_OTHER, ta.tile, axis, ta.w, ta.h, ROADSTOP_CLASS_WAYP, waypoint_type, to_join, adjacent); + } + }; + + ShowSelectRoadWaypointIfNeeded(ta, proc); + } + } + break; + case DDSP_BUILD_BUSSTOP: case DDSP_REMOVE_BUSSTOP: if (this->IsWidgetLowered(WID_ROT_BUS_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_BUS, _cur_roadtype)) { @@ -749,6 +818,11 @@ struct BuildRoadToolbarWindow : Window { return ES_NOT_HANDLED; } + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + if (this->IsWidgetLowered(WID_ROT_BUILD_WAYPOINT)) CheckRedrawRoadWaypointCoverage(this); + } + /** * Handler for global hotkeys of the BuildRoadToolbarWindow. * @param hotkey Hotkey @@ -797,6 +871,7 @@ struct BuildRoadToolbarWindow : Window { Hotkey('6', "bus_station", WID_ROT_BUS_STATION), Hotkey('7', "truck_station", WID_ROT_TRUCK_STATION), Hotkey('8', "oneway", WID_ROT_ONE_WAY), + Hotkey('9', "waypoint", WID_ROT_BUILD_WAYPOINT), Hotkey('B', "bridge", WID_ROT_BUILD_BRIDGE), Hotkey('T', "tunnel", WID_ROT_BUILD_TUNNEL), Hotkey('R', "remove", WID_ROT_REMOVE), @@ -811,6 +886,7 @@ struct BuildRoadToolbarWindow : Window { Hotkey('5', "depot", WID_ROT_DEPOT), Hotkey('6', "bus_station", WID_ROT_BUS_STATION), Hotkey('7', "truck_station", WID_ROT_TRUCK_STATION), + Hotkey('9', "waypoint", WID_ROT_BUILD_WAYPOINT), Hotkey('B', "bridge", WID_ROT_BUILD_BRIDGE), Hotkey('T', "tunnel", WID_ROT_BUILD_TUNNEL), Hotkey('R', "remove", WID_ROT_REMOVE), @@ -837,6 +913,8 @@ static constexpr NWidgetPart _nested_build_road_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUS_STATION), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_BUS_STATION, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_WAYPOINT), + SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRUCK_STATION), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(), @@ -878,6 +956,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), @@ -1790,6 +1870,238 @@ static void ShowRVStationPicker(Window *parent, RoadStopType rs) new BuildRoadStationWindow(RoadTypeIsRoad(_cur_roadtype) ? &_road_station_picker_desc : &_tram_station_picker_desc, parent, rs); } +struct BuildRoadWaypointWindow : PickerWindowBase { + using WaypointList = GUIList; + static const uint FILTER_LENGTH = 20; + + const RoadStopClass *waypoints; + WaypointList list; + StringFilter string_filter; ///< Filter for waypoint name + static QueryString editbox; ///< Filter editbox + + BuildRoadWaypointWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent) + { + this->waypoints = RoadStopClass::Get(ROADSTOP_CLASS_WAYP); + + this->CreateNestedTree(); + + NWidgetMatrix *matrix = this->GetWidget(WID_BROW_WAYPOINT_MATRIX); + matrix->SetScrollbar(this->GetScrollbar(WID_BROW_SCROLL)); + + this->FinishInitNested(TRANSPORT_ROAD); + + this->querystrings[WID_BROW_FILTER] = &this->editbox; + this->editbox.cancel_button = QueryString::ACTION_CLEAR; + this->string_filter.SetFilterTerm(this->editbox.text.buf); + + this->list.ForceRebuild(); + this->BuildPickerList(); + } + + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_STATION, 0); + this->PickerWindowBase::Close(); + } + + bool FilterByText(const RoadStopSpec *roadstopspec) + { + if (this->string_filter.IsEmpty()) return true; + this->string_filter.ResetState(); + if (roadstopspec == nullptr) { + this->string_filter.AddLine(GetString(STR_STATION_CLASS_WAYP_WAYPOINT)); + } else { + this->string_filter.AddLine(GetString(roadstopspec->name)); + if (roadstopspec->grf_prop.grffile != nullptr) { + const GRFConfig *gc = GetGRFConfig(roadstopspec->grf_prop.grffile->grfid); + this->string_filter.AddLine(gc->GetName()); + } + } + return this->string_filter.GetState(); + } + + void BuildPickerList() + { + if (!this->list.NeedRebuild()) return; + + this->list.clear(); + this->list.reserve(this->waypoints->GetSpecCount()); + for (uint i = 0; i < this->waypoints->GetSpecCount(); i++) { + const RoadStopSpec *roadstopspec = this->waypoints->GetSpec(i); + if (!FilterByText(roadstopspec)) continue; + + this->list.push_back(i); + } + this->list.RebuildDone(); + + NWidgetMatrix *matrix = this->GetWidget(WID_BROW_WAYPOINT_MATRIX); + matrix->SetCount((int)this->list.size()); + matrix->SetClicked(this->UpdateSelection(_cur_waypoint_type)); + } + + uint UpdateSelection(uint type) + { + auto found = std::find(std::begin(this->list), std::end(this->list), type); + if (found != std::end(this->list)) return found - std::begin(this->list); + + /* Selection isn't in the list, default to first */ + if (this->list.empty()) { + _cur_waypoint_type = 0; + return -1; + } else { + _cur_waypoint_type = this->list.front(); + return 0; + } + } + + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + { + switch (widget) { + case WID_BROW_WAYPOINT_MATRIX: + /* Two blobs high and three wide. */ + size.width += resize.width * 2; + size.height += resize.height * 1; + + /* Resizing in X direction only at blob size, but at pixel level in Y. */ + resize.height = 1; + break; + + case WID_BROW_WAYPOINT: + size.width = ScaleGUITrad(64) + WidgetDimensions::scaled.fullbevel.Horizontal(); + size.height = ScaleGUITrad(58) + WidgetDimensions::scaled.fullbevel.Vertical(); + break; + } + } + + void SetStringParameters(WidgetID widget) const override + { + if (widget == WID_BROW_NAME) { + if (!this->list.empty() && IsInsideBS(_cur_waypoint_type, 0, this->waypoints->GetSpecCount())) { + const RoadStopSpec *roadstopspec = this->waypoints->GetSpec(_cur_waypoint_type); + if (roadstopspec == nullptr) { + SetDParam(0, STR_STATION_CLASS_WAYP_WAYPOINT); + } else { + SetDParam(0, roadstopspec->name); + } + } else { + SetDParam(0, STR_EMPTY); + } + } + } + + void OnPaint() override + { + this->BuildPickerList(); + this->DrawWidgets(); + } + + void DrawWidget(const Rect &r, WidgetID widget) const override + { + switch (widget) { + case WID_BROW_WAYPOINT: { + uint16_t type = this->list.at(this->GetWidget(widget)->GetParentWidget()->GetCurrentElement()); + const RoadStopSpec *roadstopspec = this->waypoints->GetSpec(type); + + DrawPixelInfo tmp_dpi; + Rect ir = r.Shrink(WidgetDimensions::scaled.bevel); + if (FillDrawPixelInfo(&tmp_dpi, ir)) { + AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi); + int x = (ir.Width() - ScaleSpriteTrad(64)) / 2 + ScaleSpriteTrad(31); + int y = (ir.Height() + ScaleSpriteTrad(48)) / 2 - ScaleSpriteTrad(31); + if (roadstopspec == nullptr) { + StationPickerDrawSprite(x, y, STATION_ROADWAYPOINT, INVALID_RAILTYPE, _cur_roadtype, 4); + } else { + DrawRoadStopTile(x, y, _cur_roadtype, roadstopspec, STATION_ROADWAYPOINT, 4); + } + } + + if (!IsRoadStopAvailable(roadstopspec, STATION_ROADWAYPOINT)) { + GfxFillRect(ir, PC_BLACK, FILLRECT_CHECKER); + } + } + } + } + + void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override + { + switch (widget) { + case WID_BROW_WAYPOINT: { + uint16_t sel = this->GetWidget(widget)->GetParentWidget()->GetCurrentElement(); + assert(sel < this->list.size()); + uint16_t type = this->list.at(sel); + + const RoadStopSpec *roadstopspec = this->waypoints->GetSpec(type); + if (!IsRoadStopAvailable(roadstopspec, STATION_ROADWAYPOINT)) return; + + _cur_waypoint_type = type; + this->GetWidget(widget)->GetParentWidget()->SetClicked(sel); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + this->SetDirty(); + break; + } + } + } + + void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override + { + if (!gui_scope) return; + this->list.ForceRebuild(); + } + + void OnEditboxChanged(WidgetID wid) override + { + if (wid == WID_BROW_FILTER) { + this->string_filter.SetFilterTerm(this->editbox.text.buf); + this->InvalidateData(); + } + } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawRoadWaypointCoverage(this); + } +}; + +/* static */ QueryString BuildRoadWaypointWindow::editbox(BuildRoadWaypointWindow::FILTER_LENGTH * MAX_CHAR_LENGTH, BuildRoadWaypointWindow::FILTER_LENGTH); + +/** Nested widget definition for the build NewGRF road waypoint window */ +static constexpr NWidgetPart _nested_build_road_waypoint_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_WAYPOINT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(WWT_EDITBOX, COLOUR_DARK_GREEN, WID_BROW_FILTER), SetPadding(2), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BROW_SCROLL), + NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROW_WAYPOINT_MATRIX), SetPIP(0, 2, 0), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(WWT_PANEL, COLOUR_GREY, WID_BROW_WAYPOINT), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), SetScrollbar(WID_BROW_SCROLL), EndContainer(), + EndContainer(), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROW_SCROLL), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_BROW_NAME), SetPadding(2), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL), SetTextStyle(TC_ORANGE), SetAlignment(SA_CENTER), + EndContainer(), + NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), +}; + +static WindowDesc _build_road_waypoint_desc( + WDP_AUTO, "build_road_waypoint", 0, 0, + WC_BUILD_WAYPOINT, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + std::begin(_nested_build_road_waypoint_widgets), std::end(_nested_build_road_waypoint_widgets) +); + +static void ShowBuildRoadWaypointPicker(Window *parent) +{ + new BuildRoadWaypointWindow(&_build_road_waypoint_desc, parent); +} + void InitializeRoadGui() { _road_depot_orientation = DIAGDIR_NW; 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/road_map.h b/src/road_map.h index 9179579b4c..06c000384c 100644 --- a/src/road_map.h +++ b/src/road_map.h @@ -474,7 +474,7 @@ inline void ToggleSnow(Tile t) /** The possible road side decorations. */ -enum Roadside { +enum Roadside : uint8_t { ROADSIDE_BARREN = 0, ///< Road on barren land ROADSIDE_GRASS = 1, ///< Road on grass ROADSIDE_PAVED = 2, ///< Road with paved sidewalks diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 734059d580..add521c024 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1328,7 +1328,7 @@ again: v->tile != tile) { /* So, keep 'our' state */ dir = (Trackdir)v->state; - } else if (IsRoadStop(v->tile)) { + } else if (IsStationRoadStop(v->tile)) { /* We're not continuing our drive through road stop, so leave. */ RoadStop::GetByTile(v->tile, GetRoadStopType(v->tile))->Leave(v); } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index a1d016a1f1..249a635e03 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -907,6 +907,15 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_ROAD_WAYPOINTS)) { + /* Expansion of station type field in m6 */ + for (auto t : Map::Iterate()) { + if (IsTileType(t, MP_STATION)) { + ClrBit(t.m6(), 6); + } + } + } + for (auto t : Map::Iterate()) { switch (GetTileType(t)) { case MP_STATION: { @@ -1082,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: @@ -1136,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)); @@ -1287,7 +1296,7 @@ bool AfterLoadGame() has_road = true; break; case MP_STATION: - has_road = IsRoadStop(t); + has_road = IsAnyRoadStop(t); break; case MP_TUNNELBRIDGE: has_road = GetTunnelBridgeTransportType(t) == TRANSPORT_ROAD; diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 50a247da54..136b469f8b 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -151,7 +151,8 @@ void AfterLoadCompanyStats() break; case STATION_BUS: - case STATION_TRUCK: { + case STATION_TRUCK: + case STATION_ROADWAYPOINT: { /* Iterate all present road types as each can have a different owner. */ for (RoadTramType rtt : _roadtramtypes) { RoadType rt = GetRoadType(tile, rtt); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 05ffac1c04..722a8f98d2 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -379,6 +379,8 @@ enum SaveLoadVersion : uint16_t { SLV_SCRIPT_RANDOMIZER, ///< 333 PR#12063 v14.0-RC1 Save script randomizers. SLV_VEHICLE_ECONOMY_AGE, ///< 334 PR#12141 v14.0 Add vehicle age in economy year, for profit stats minimum age + SLV_ROAD_WAYPOINTS, ///< 335 PR#12572 Road waypoints + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 0db9509e53..b7e7c1c74d 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -655,6 +655,10 @@ public: SLE_CONDVAR(Waypoint, train_station.tile, SLE_UINT32, SLV_124, SL_MAX_VERSION), SLE_CONDVAR(Waypoint, train_station.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_124, SL_MAX_VERSION), SLE_CONDVAR(Waypoint, train_station.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_124, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, waypoint_flags, SLE_UINT16, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, road_waypoint_area.tile, SLE_UINT32, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, road_waypoint_area.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, road_waypoint_area.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_ROAD_WAYPOINTS, SL_MAX_VERSION), }; inline const static SaveLoadCompatTable compat_description = _station_waypoint_sl_compat; diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index bff546e8ce..25b7fc2631 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -54,7 +54,7 @@ if (!::IsValidTile(tile)) return false; if (!IsRoadTypeAvailable(GetCurrentRoadType())) return false; - return ::IsRoadStopTile(tile) && HasBit(::GetPresentRoadTypes(tile), (::RoadType)GetCurrentRoadType()); + return ::IsStationRoadStopTile(tile) && HasBit(::GetPresentRoadTypes(tile), (::RoadType)GetCurrentRoadType()); } /* static */ bool ScriptRoad::IsDriveThroughRoadStationTile(TileIndex tile) @@ -604,7 +604,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD EnforceCompanyModeValid(false); EnforcePrecondition(false, ::IsValidTile(tile)); EnforcePrecondition(false, IsTileType(tile, MP_STATION)); - EnforcePrecondition(false, IsRoadStop(tile)); + EnforcePrecondition(false, IsStationRoadStop(tile)); return ScriptObject::Command::Do(tile, 1, 1, GetRoadStopType(tile), false); } diff --git a/src/station.cpp b/src/station.cpp index efb767d50b..e0dab59067 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -327,13 +327,15 @@ static uint GetTileCatchmentRadius(TileIndex tile, const Station *st) default: NOT_REACHED(); case STATION_BUOY: - case STATION_WAYPOINT: return CA_NONE; + case STATION_WAYPOINT: + case STATION_ROADWAYPOINT: return CA_NONE; } } else { switch (GetStationType(tile)) { default: return CA_UNMODIFIED; case STATION_BUOY: - case STATION_WAYPOINT: return CA_NONE; + case STATION_WAYPOINT: + case STATION_ROADWAYPOINT: return CA_NONE; } } } diff --git a/src/station_base.h b/src/station_base.h index 12ed87355a..94b9f00943 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -511,7 +511,7 @@ public: inline bool TileBelongsToRoadStop(TileIndex tile) const { - return IsRoadStopTile(tile) && GetStationIndex(tile) == this->index; + return IsStationRoadStopTile(tile) && GetStationIndex(tile) == this->index; } inline bool TileBelongsToAirport(TileIndex tile) const diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 615e680723..72499dff40 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -66,6 +66,7 @@ #include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" #include "cheat_type.h" +#include "road_func.h" #include "widgets/station_widget.h" @@ -111,10 +112,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 +124,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 +961,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 +979,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 +1029,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 +1037,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 +1055,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 +1153,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 +1161,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 +1176,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 +1189,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 +1210,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 +1220,16 @@ static CommandCost FindJoiningStation(StationID existing_station, StationID stat * @param adjacent whether adjacent waypoints are allowed * @param ta the area of the newly build waypoint * @param wp 'return' pointer for the found waypoint + * @param is_road whether to find a road waypoint * @return command cost with the error or 'okay' */ -CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_to_join, bool adjacent, TileArea ta, Waypoint **wp) +CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road) { - return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp); + if (is_road) { + return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp, [](const Waypoint *wp) -> bool { return HasBit(wp->waypoint_flags, WPF_ROAD); }); + } else { + return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp, [](const Waypoint *wp) -> bool { return !HasBit(wp->waypoint_flags, WPF_ROAD); }); + } } /** @@ -1605,6 +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. @@ -1864,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. @@ -1876,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; }); } /** @@ -1884,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) { @@ -1906,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) { @@ -1985,7 +2007,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, unit_cost = _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]; } StationID est = INVALID_STATION; - CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop, axis, ddir, &est, rt, unit_cost); + CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop ? STATION_TRUCK : STATION_BUS, axis, ddir, &est, rt, unit_cost); if (cost.Failed()) return cost; Station *st = nullptr; @@ -2021,7 +2043,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, Owner road_owner = road_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_ROAD) : _current_company; Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company; - if (IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile)) { + if (IsTileType(cur_tile, MP_STATION) && IsStationRoadStop(cur_tile)) { RemoveRoadStop(cur_tile, flags, specindex); } @@ -2059,7 +2081,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8_t width, if (road_rt == INVALID_ROADTYPE && RoadTypeIsRoad(rt)) road_rt = rt; if (tram_rt == INVALID_ROADTYPE && RoadTypeIsTram(rt)) tram_rt = rt; - MakeDriveThroughRoadStop(cur_tile, st->owner, road_owner, tram_owner, st->index, rs_type, road_rt, tram_rt, axis); + MakeDriveThroughRoadStop(cur_tile, st->owner, road_owner, tram_owner, st->index, (rs_type == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK), road_rt, tram_rt, axis); road_stop->MakeDriveThrough(); } else { if (road_rt == INVALID_ROADTYPE && RoadTypeIsRoad(rt)) road_rt = rt; @@ -2219,6 +2241,132 @@ static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags, int repla return CommandCost(EXPENSES_CONSTRUCTION, spec != nullptr ? spec->GetClearCost(category) : _price[category]); } +/** + * Remove a road waypoint + * @param tile TileIndex been queried + * @param flags operation to perform + * @param replacement_spec_index replacement spec index to avoid deallocating, if < 0, tile is not being replaced + * @return cost or failure of operation + */ +CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index) +{ + Waypoint *wp = Waypoint::GetByTile(tile); + + if (_current_company != OWNER_WATER) { + CommandCost ret = CheckOwnership(wp->owner); + if (ret.Failed()) return ret; + } + + /* don't do the check for drive-through road stops when company bankrupts */ + if (!(flags & DC_BANKRUPT)) { + CommandCost ret = EnsureNoVehicleOnGround(tile); + if (ret.Failed()) return ret; + } + + const RoadStopSpec *spec = GetRoadStopSpec(tile); + + if (flags & DC_EXEC) { + /* Update company infrastructure counts. */ + for (RoadTramType rtt : _roadtramtypes) { + RoadType rt = GetRoadType(tile, rtt); + UpdateCompanyRoadInfrastructure(rt, GetRoadOwner(tile, rtt), -static_cast(ROAD_STOP_TRACKBIT_FACTOR)); + } + + Company::Get(wp->owner)->infrastructure.station--; + DirtyCompanyInfrastructureWindows(wp->owner); + + DeleteAnimatedTile(tile); + + uint specindex = GetCustomRoadStopSpecIndex(tile); + + DeleteNewGRFInspectWindow(GSF_ROADSTOPS, tile.base()); + + DoClearSquare(tile); + + wp->rect.AfterRemoveTile(wp, tile); + + wp->RemoveRoadStopTileData(tile); + if ((int)specindex != replacement_spec_index) DeallocateSpecFromRoadStop(wp, specindex); + + if (replacement_spec_index < 0) { + MakeRoadWaypointStationAreaSmaller(wp, wp->road_waypoint_area); + + UpdateStationSignCoord(wp); + + /* if we deleted the whole waypoint, delete the road facility. */ + if (wp->road_waypoint_area.tile == INVALID_TILE) { + wp->facilities &= ~(FACIL_BUS_STOP | FACIL_TRUCK_STOP); + SetWindowWidgetDirty(WC_STATION_VIEW, wp->index, WID_SV_ROADVEHS); + wp->UpdateVirtCoord(); + DeleteStationIfEmpty(wp); + } + } + } + + return CommandCost(EXPENSES_CONSTRUCTION, spec != nullptr ? spec->GetClearCost(PR_CLEAR_STATION_TRUCK) : _price[PR_CLEAR_STATION_TRUCK]); +} + +/** + * Remove a tile area of road stop or road waypoints + * @param flags operation to perform + * @param roadstop_area tile area of road stop or road waypoint tiles to remove + * @param station_type station type to remove + * @param remove_road Remove roads of drive-through stops? + * @return the cost of this operation or an error + */ +static CommandCost RemoveGenericRoadStop(DoCommandFlag flags, const TileArea &roadstop_area, StationType station_type, bool remove_road) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost last_error(STR_ERROR_THERE_IS_NO_STATION); + bool had_success = false; + + for (TileIndex cur_tile : roadstop_area) { + /* Make sure the specified tile is a road stop of the correct type */ + if (!IsTileType(cur_tile, MP_STATION) || !IsAnyRoadStop(cur_tile) || GetStationType(cur_tile) != station_type) continue; + + /* Save information on to-be-restored roads before the stop is removed. */ + RoadBits road_bits = ROAD_NONE; + RoadType road_type[] = { INVALID_ROADTYPE, INVALID_ROADTYPE }; + Owner road_owner[] = { OWNER_NONE, OWNER_NONE }; + if (IsDriveThroughStopTile(cur_tile)) { + for (RoadTramType rtt : _roadtramtypes) { + road_type[rtt] = GetRoadType(cur_tile, rtt); + if (road_type[rtt] == INVALID_ROADTYPE) continue; + road_owner[rtt] = GetRoadOwner(cur_tile, rtt); + /* If we don't want to preserve our roads then restore only roads of others. */ + if (remove_road && road_owner[rtt] == _current_company) road_type[rtt] = INVALID_ROADTYPE; + } + road_bits = AxisToRoadBits(DiagDirToAxis(GetRoadStopDir(cur_tile))); + } + + CommandCost ret; + if (station_type == STATION_ROADWAYPOINT) { + ret = RemoveRoadWaypointStop(cur_tile, flags); + } else { + ret = RemoveRoadStop(cur_tile, flags); + } + if (ret.Failed()) { + last_error = ret; + continue; + } + cost.AddCost(ret); + had_success = true; + + /* Restore roads. */ + if ((flags & DC_EXEC) && (road_type[RTT_ROAD] != INVALID_ROADTYPE || road_type[RTT_TRAM] != INVALID_ROADTYPE)) { + MakeRoadNormal(cur_tile, road_bits, road_type[RTT_ROAD], road_type[RTT_TRAM], ClosestTownFromTile(cur_tile, UINT_MAX)->index, + road_owner[RTT_ROAD], road_owner[RTT_TRAM]); + + /* Update company infrastructure counts. */ + int count = CountBits(road_bits); + UpdateCompanyRoadInfrastructure(road_type[RTT_ROAD], road_owner[RTT_ROAD], count); + UpdateCompanyRoadInfrastructure(road_type[RTT_TRAM], road_owner[RTT_TRAM], count); + } + } + + return had_success ? cost : last_error; +} + /** * Remove bus or truck stops. * @param flags Operation to perform. @@ -2241,50 +2389,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); } /** @@ -3130,6 +3252,20 @@ draw_default_foundation: DrawClearLandTile(ti, 3); } } + } else if (IsRoadWaypointTile(ti->tile)) { + RoadBits bits = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? ROAD_X : ROAD_Y; + RoadType road_rt = GetRoadTypeRoad(ti->tile); + RoadType tram_rt = GetRoadTypeTram(ti->tile); + RoadBits road = (road_rt != INVALID_ROADTYPE) ? bits : ROAD_NONE; + RoadBits tram = (tram_rt != INVALID_ROADTYPE) ? bits : ROAD_NONE; + const RoadTypeInfo *road_rti = (road_rt != INVALID_ROADTYPE) ? GetRoadTypeInfo(road_rt) : nullptr; + const RoadTypeInfo *tram_rti = (tram_rt != INVALID_ROADTYPE) ? GetRoadTypeInfo(tram_rt) : nullptr; + + if (ti->tileh != SLOPE_FLAT) { + DrawFoundation(ti, FOUNDATION_LEVELED); + } + + DrawRoadGroundSprites(ti, road, tram, road_rti, tram_rti, GetRoadWaypointRoadside(ti->tile), IsRoadWaypointOnSnowOrDesert(ti->tile)); } else { if (layout != nullptr) { /* Sprite layout which needs preprocessing */ @@ -3154,7 +3290,7 @@ draw_default_foundation: draw_ground = true; } - if (draw_ground && !IsRoadStop(ti->tile)) { + if (draw_ground && !IsAnyRoadStop(ti->tile)) { SpriteID image = t->ground.sprite; PaletteID pal = t->ground.pal; RailTrackOffset overlay_offset; @@ -3181,7 +3317,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); @@ -3192,13 +3328,21 @@ draw_default_foundation: StationType type = GetStationType(ti->tile); const RoadStopSpec *stopspec = GetRoadStopSpec(ti->tile); + RoadStopDrawMode stop_draw_mode{}; if (stopspec != nullptr) { + stop_draw_mode = stopspec->draw_mode; int view = dir; if (IsDriveThroughStopTile(ti->tile)) view += 4; st = BaseStation::GetByTile(ti->tile); RoadStopResolverObject object(stopspec, st, ti->tile, INVALID_ROADTYPE, type, view); const SpriteGroup *group = object.Resolve(); if (group != nullptr && group->type == SGT_TILELAYOUT) { + if (HasBit(stopspec->flags, RSF_DRAW_MODE_REGISTER)) { + stop_draw_mode = static_cast(GetRegister(0x100)); + } + if (type == STATION_ROADWAYPOINT && (stop_draw_mode & ROADSTOP_DRAW_MODE_WAYP_GROUND)) { + draw_ground = true; + } t = ((const TileLayoutSpriteGroup *)group)->ProcessRegisters(nullptr); } } @@ -3215,14 +3359,15 @@ draw_default_foundation: } if (IsDriveThroughStopTile(ti->tile)) { - uint sprite_offset = axis == AXIS_X ? 1 : 0; - - DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset); + if (type != STATION_ROADWAYPOINT && (stopspec == nullptr || (stop_draw_mode & ROADSTOP_DRAW_MODE_OVERLAY) != 0)) { + uint sprite_offset = axis == AXIS_X ? 1 : 0; + DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset); + } } else { /* Non-drivethrough road stops are only valid for roads. */ assert(road_rt != INVALID_ROADTYPE && tram_rt == INVALID_ROADTYPE); - if ((stopspec == nullptr || (stopspec->draw_mode & ROADSTOP_DRAW_MODE_ROAD) != 0) && road_rti->UsesOverlay()) { + if ((stopspec == nullptr || (stop_draw_mode & ROADSTOP_DRAW_MODE_ROAD) != 0) && road_rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(road_rti, ti->tile, ROTSG_ROADSTOP); DrawGroundSprite(ground + dir, PAL_NONE); } @@ -3290,7 +3435,7 @@ void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, Ro } /* Default waypoint has no railtype specific sprites */ - DrawRailTileSeqInGUI(x, y, t, st == STATION_WAYPOINT ? 0 : total_offset, 0, pal); + DrawRailTileSeqInGUI(x, y, t, (st == STATION_WAYPOINT || st == STATION_ROADWAYPOINT) ? 0 : total_offset, 0, pal); } static int GetSlopePixelZ_Station(TileIndex tile, uint, uint, bool) @@ -3383,7 +3528,7 @@ static void GetTileDesc_Station(TileIndex tile, TileDesc *td) td->owner[0] = GetTileOwner(tile); td->build_date = BaseStation::GetByTile(tile)->build_date; - if (IsRoadStop(tile)) FillTileDescRoadStop(tile, td); + if (IsAnyRoadStop(tile)) FillTileDescRoadStop(tile, td); if (HasStationRail(tile)) FillTileDescRailStation(tile, td); if (IsAirport(tile)) FillTileDescAirport(tile, td); @@ -3407,6 +3552,7 @@ static void GetTileDesc_Station(TileIndex tile, TileDesc *td) case STATION_DOCK: str = STR_LAI_STATION_DESCRIPTION_SHIP_DOCK; break; case STATION_BUOY: str = STR_LAI_STATION_DESCRIPTION_BUOY; break; case STATION_WAYPOINT: str = STR_LAI_STATION_DESCRIPTION_WAYPOINT; break; + case STATION_ROADWAYPOINT: str = STR_LAI_STATION_DESCRIPTION_WAYPOINT; break; } td->str = str; } @@ -3435,7 +3581,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; @@ -3476,6 +3622,40 @@ static void TileLoop_Station(TileIndex tile) TileLoop_Water(tile); break; + case STATION_ROADWAYPOINT: { + switch (_settings_game.game_creation.landscape) { + case LT_ARCTIC: + if (IsRoadWaypointOnSnowOrDesert(tile) != (GetTileZ(tile) > GetSnowLine())) { + ToggleRoadWaypointOnSnowOrDesert(tile); + MarkTileDirtyByTile(tile); + } + break; + + case LT_TROPIC: + if (GetTropicZone(tile) == TROPICZONE_DESERT && !IsRoadWaypointOnSnowOrDesert(tile)) { + ToggleRoadWaypointOnSnowOrDesert(tile); + MarkTileDirtyByTile(tile); + } + break; + } + + HouseZonesBits grp = HZB_TOWN_EDGE; + const Town *t = ClosestTownFromTile(tile, UINT_MAX); + if (t != nullptr) { + grp = GetTownRadiusGroup(t, tile); + } + + /* Adjust road ground type depending on 'grp' (grp is the distance to the center) */ + Roadside new_rs = grp > HZB_TOWN_EDGE ? ROADSIDE_PAVED : ROADSIDE_GRASS; + Roadside cur_rs = GetRoadWaypointRoadside(tile); + + if (new_rs != cur_rs) { + SetRoadWaypointRoadside(tile, cur_rs == ROADSIDE_BARREN ? new_rs : ROADSIDE_BARREN); + MarkTileDirtyByTile(tile); + } + break; + } + default: break; } } @@ -3493,7 +3673,7 @@ static void AnimateTile_Station(TileIndex tile) return; } - if (IsRoadStopTile(tile)) { + if (IsAnyRoadStopTile(tile)) { AnimateRoadStopTile(tile); return; } @@ -3553,7 +3733,7 @@ static VehicleEnterTileStatus VehicleEnter_Station(Vehicle *v, TileIndex tile, i } else if (v->type == VEH_ROAD) { RoadVehicle *rv = RoadVehicle::From(v); if (rv->state < RVSB_IN_ROAD_STOP && !IsReversingRoadTrackdir((Trackdir)rv->state) && rv->frame == 0) { - if (IsRoadStop(tile) && rv->IsFrontEngine()) { + if (IsStationRoadStop(tile) && rv->IsFrontEngine()) { /* Attempt to allocate a parking bay in a road stop */ return RoadStop::GetByTile(tile, GetRoadStopType(tile))->Enter(rv) ? VETSB_CONTINUE : VETSB_CANNOT_ENTER; } @@ -4379,7 +4559,7 @@ void DeleteOilRig(TileIndex tile) static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_owner) { - if (IsRoadStopTile(tile)) { + if (IsAnyRoadStopTile(tile)) { for (RoadTramType rtt : _roadtramtypes) { /* Update all roadtypes, no matter if they are present */ if (GetRoadOwner(tile, rtt) == old_owner) { @@ -4416,6 +4596,7 @@ static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_o case STATION_BUS: case STATION_TRUCK: + case STATION_ROADWAYPOINT: /* Road stops were already handled above. */ break; @@ -4443,7 +4624,11 @@ static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_o } else { if (IsDriveThroughStopTile(tile)) { /* Remove the drive-through road stop */ - Command::Do(DC_EXEC | DC_BANKRUPT, tile, 1, 1, (GetStationType(tile) == STATION_TRUCK) ? ROADSTOP_TRUCK : ROADSTOP_BUS, false); + if (IsRoadWaypoint(tile)) { + Command::Do(DC_EXEC | DC_BANKRUPT, tile, tile); + } else { + Command::Do(DC_EXEC | DC_BANKRUPT, tile, 1, 1, (GetStationType(tile) == STATION_TRUCK) ? ROADSTOP_TRUCK : ROADSTOP_BUS, false); + } assert(IsTileType(tile, MP_ROAD)); /* Change owner of tile and all roadtypes */ ChangeTileOwner(tile, old_owner, new_owner); @@ -4510,6 +4695,7 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) case STATION_AIRPORT: return_cmd_error(STR_ERROR_MUST_DEMOLISH_AIRPORT_FIRST); case STATION_TRUCK: return_cmd_error(HasTileRoadType(tile, RTT_TRAM) ? STR_ERROR_MUST_DEMOLISH_CARGO_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_TRUCK_STATION_FIRST); case STATION_BUS: return_cmd_error(HasTileRoadType(tile, RTT_TRAM) ? STR_ERROR_MUST_DEMOLISH_PASSENGER_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_BUS_STATION_FIRST); + case STATION_ROADWAYPOINT: return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); case STATION_BUOY: return_cmd_error(STR_ERROR_BUOY_IN_THE_WAY); case STATION_DOCK: return_cmd_error(STR_ERROR_MUST_DEMOLISH_DOCK_FIRST); case STATION_OILRIG: @@ -4529,6 +4715,12 @@ CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags) if (remove_road.Failed()) return remove_road; } return RemoveRoadStop(tile, flags); + case STATION_ROADWAYPOINT: + if (IsDriveThroughStopTile(tile)) { + CommandCost remove_road = CanRemoveRoadWithStop(tile, flags); + if (remove_road.Failed()) return remove_road; + } + return RemoveRoadWaypointStop(tile, flags); case STATION_BUOY: return RemoveBuoy(tile, flags); case STATION_DOCK: return RemoveDock(tile, flags); default: break; diff --git a/src/station_gui.cpp b/src/station_gui.cpp index 241a512b12..e3e0c65f6d 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -41,6 +41,29 @@ #include "safeguards.h" +struct StationTypeFilter +{ + using StationType = Station; + + 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; } +}; + +template +struct GenericWaypointTypeFilter +{ + using StationType = Waypoint; + + 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) * @param left x position where the string is to be drawn @@ -87,7 +110,7 @@ void FindStationsAroundSelection() { /* With distant join we don't know which station will be selected, so don't show any */ if (_ctrl_pressed) { - SetViewportCatchmentSpecializedStation(nullptr, true); + SetViewportCatchmentSpecializedStation(nullptr, true); return; } @@ -96,9 +119,9 @@ void FindStationsAroundSelection() /* If the current tile is already a station, then it must be the nearest station. */ if (IsTileType(location.tile, MP_STATION) && GetTileOwner(location.tile) == _local_company) { - T *st = T::GetByTile(location.tile); - if (st != nullptr) { - SetViewportCatchmentSpecializedStation(st, true); + typename T::StationType *st = T::StationType::GetByTile(location.tile); + if (st != nullptr && T::IsValidBaseStation(st)) { + SetViewportCatchmentSpecializedStation(st, true); return; } } @@ -107,17 +130,17 @@ 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::EXPECTED_FACIL == FACIL_WAYPOINT && !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))); - T *adjacent = nullptr; + typename T::StationType *adjacent = nullptr; /* Direct loop instead of ForAllStationsAroundTiles as we are not interested in catchment area */ for (TileIndex tile : ta) { if (IsTileType(tile, MP_STATION) && GetTileOwner(tile) == _local_company) { - T *st = T::GetByTile(tile); - if (st == nullptr) continue; + typename T::StationType *st = T::StationType::GetByTile(tile); + if (st == nullptr || !T::IsValidBaseStation(st)) continue; if (adjacent != nullptr && st != adjacent) { /* Multiple nearby, distant join is required. */ adjacent = nullptr; @@ -126,7 +149,7 @@ void FindStationsAroundSelection() adjacent = st; } } - SetViewportCatchmentSpecializedStation(adjacent, true); + SetViewportCatchmentSpecializedStation(adjacent, true); } /** @@ -148,12 +171,13 @@ void CheckRedrawStationCoverage(const Window *w) w->SetDirty(); if (_settings_client.gui.station_show_coverage && _thd.drawstyle == HT_RECT) { - FindStationsAroundSelection(); + FindStationsAroundSelection(); } } } -void CheckRedrawWaypointCoverage(const Window *) +template +void CheckRedrawWaypointCoverage() { /* Test if ctrl state changed */ static bool _last_ctrl_pressed; @@ -166,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 @@ -2171,7 +2205,7 @@ static std::vector _stations_nearby_list; * station spread. * @param tile Tile just being checked * @param user_data Pointer to TileArea context - * @tparam T the type of station to look for + * @tparam T the station filter type */ template static bool AddNearbyStation(TileIndex tile, void *user_data) @@ -2196,7 +2230,7 @@ static bool AddNearbyStation(TileIndex tile, void *user_data) /* This station is (likely) a waypoint */ if (!T::IsValidID(sid)) return false; - T *st = T::Get(sid); + BaseStation *st = BaseStation::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->rect.BeforeAddRect(ctx->tile, ctx->w, ctx->h, StationRect::ADD_TEST).Succeeded()) { @@ -2213,10 +2247,10 @@ static bool AddNearbyStation(TileIndex tile, void *user_data) * @param ta Base tile area of the to-be-built station * @param distant_join Search for adjacent stations (false) or stations fully * within station spread - * @tparam T the type of station to look for + * @tparam T the station filter type, for stations to look for */ template -static const T *FindStationsNearby(TileArea ta, bool distant_join) +static const BaseStation *FindStationsNearby(TileArea ta, bool distant_join) { TileArea ctx = ta; @@ -2226,12 +2260,12 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join) /* Check the inside, to return, if we sit on another station */ for (TileIndex t : ta) { - if (t < Map::Size() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) return T::GetByTile(t); + if (t < Map::Size() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) return BaseStation::GetByTile(t); } /* Look for deleted stations */ for (const BaseStation *st : BaseStation::Iterate()) { - if (T::IsExpected(st) && !st->IsInUse() && st->owner == _local_company) { + if (T::IsValidBaseStation(st) && !st->IsInUse() && st->owner == _local_company) { /* Include only within station spread (yes, it is strictly less than) */ if (std::max(DistanceMax(ta.tile, st->xy), DistanceMax(TileAddXY(ta.tile, ta.w - 1, ta.h - 1), st->xy)) < _settings_game.station.station_spread) { _deleted_stations_nearby.push_back({st->xy, st->index}); @@ -2274,7 +2308,7 @@ static constexpr NWidgetPart _nested_select_station_widgets[] = { /** * Window for selecting stations/waypoints to (distant) join to. - * @tparam T The type of station to join with + * @tparam T The station filter type, for stations to join with */ template struct SelectStationWindow : Window { @@ -2289,7 +2323,7 @@ struct SelectStationWindow : Window { { this->CreateNestedTree(); this->vscroll = this->GetScrollbar(WID_JS_SCROLLBAR); - this->GetWidget(WID_JS_CAPTION)->widget_data = T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CAPTION : STR_JOIN_STATION_CAPTION; + this->GetWidget(WID_JS_CAPTION)->widget_data = T::IsWaypoint() ? STR_JOIN_WAYPOINT_CAPTION : STR_JOIN_STATION_CAPTION; this->FinishInitNested(0); this->OnInvalidateData(0); @@ -2298,7 +2332,7 @@ struct SelectStationWindow : Window { void Close([[maybe_unused]] int data = 0) override { - SetViewportCatchmentSpecializedStation(nullptr, true); + SetViewportCatchmentSpecializedStation(nullptr, true); _thd.freeze = false; this->Window::Close(); @@ -2309,13 +2343,13 @@ struct SelectStationWindow : Window { if (widget != WID_JS_PANEL) return; /* Determine the widest string */ - Dimension d = GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION); + Dimension d = GetStringBoundingBox(T::IsWaypoint() ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION); for (const auto &station : _stations_nearby_list) { if (station == NEW_STATION) continue; - const T *st = T::Get(station); + const BaseStation *st = BaseStation::Get(station); SetDParam(0, st->index); SetDParam(1, st->facilities); - d = maxdim(d, GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION)); + d = maxdim(d, GetStringBoundingBox(T::IsWaypoint() ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION)); } resize.height = d.height; @@ -2333,12 +2367,12 @@ struct SelectStationWindow : Window { auto [first, last] = this->vscroll->GetVisibleRangeIterators(_stations_nearby_list); for (auto it = first; it != last; ++it, tr.top += this->resize.step_height) { if (*it == NEW_STATION) { - DrawString(tr, T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION); + DrawString(tr, T::IsWaypoint() ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION); } else { - const T *st = T::Get(*it); + const BaseStation *st = BaseStation::Get(*it); SetDParam(0, st->index); SetDParam(1, st->facilities); - DrawString(tr, T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION); + DrawString(tr, T::IsWaypoint() ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION); } } @@ -2387,14 +2421,14 @@ struct SelectStationWindow : Window { void OnMouseOver([[maybe_unused]] Point pt, WidgetID widget) override { if (widget != WID_JS_PANEL) { - SetViewportCatchmentSpecializedStation(nullptr, true); + SetViewportCatchmentSpecializedStation(nullptr, true); return; } /* Show coverage area of station under cursor */ auto it = this->vscroll->GetScrolledItemFromWidget(_stations_nearby_list, pt.y, this, WID_JS_PANEL, WidgetDimensions::scaled.framerect.top); - const T *st = it == _stations_nearby_list.end() || *it == NEW_STATION ? nullptr : T::Get(*it); - SetViewportCatchmentSpecializedStation(st, true); + const typename T::StationType *st = it == _stations_nearby_list.end() || *it == NEW_STATION ? nullptr : T::StationType::Get(*it); + SetViewportCatchmentSpecializedStation(st, true); } }; @@ -2410,7 +2444,7 @@ static WindowDesc _select_station_desc( * Check whether we need to show the station selection window. * @param cmd Command to build the station. * @param ta Tile area of the to-be-built station - * @tparam T the type of station + * @tparam T the station filter type * @return whether we need to show the station selection window. */ template @@ -2437,7 +2471,7 @@ static bool StationJoinerNeeded(TileArea ta, const StationPickerCmdProc &proc) /* 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. */ - const T *st = FindStationsNearby(ta, false); + const BaseStation *st = FindStationsNearby(ta, false); return st == nullptr && (_settings_game.station.adjacent_stations || std::any_of(std::begin(_stations_nearby_list), std::end(_stations_nearby_list), [](StationID s) { return s != NEW_STATION; })); } @@ -2465,15 +2499,25 @@ void ShowSelectBaseStationIfNeeded(TileArea ta, StationPickerCmdProc&& proc) */ void ShowSelectStationIfNeeded(TileArea ta, StationPickerCmdProc proc) { - ShowSelectBaseStationIfNeeded(ta, std::move(proc)); + ShowSelectBaseStationIfNeeded(ta, std::move(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 34228e9ecf..e4edd660e3 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -44,7 +44,7 @@ static const int GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET = 4; ///< The offset for the inline StationType GetStationType(Tile t) { assert(IsTileType(t, MP_STATION)); - return (StationType)GB(t.m6(), 3, 3); + return (StationType)GB(t.m6(), 3, 4); } /** @@ -193,13 +193,34 @@ inline bool IsBusStop(Tile t) return GetStationType(t) == STATION_BUS; } +/** + * Is the station at \a t a road waypoint? + * @param t Tile to check + * @pre IsTileType(t, MP_STATION) + * @return \c true if station is a road waypoint, \c false otherwise + */ +inline bool IsRoadWaypoint(Tile t) +{ + return GetStationType(t) == STATION_ROADWAYPOINT; +} + +/** + * Is this tile a station tile and a road waypoint? + * @param t the tile to get the information from + * @return true if and only if the tile is a road waypoint + */ +inline bool IsRoadWaypointTile(Tile t) +{ + return IsTileType(t, MP_STATION) && IsRoadWaypoint(t); +} + /** * Is the station at \a t a road station? * @param t Tile to check * @pre IsTileType(t, MP_STATION) - * @return \c true if station at the tile is a bus top or a truck stop, \c false otherwise + * @return \c true if station at the tile is a bus stop or a truck stop, \c false otherwise */ -inline bool IsRoadStop(Tile t) +inline bool IsStationRoadStop(Tile t) { assert(IsTileType(t, MP_STATION)); return IsTruckStop(t) || IsBusStop(t); @@ -208,11 +229,33 @@ inline bool IsRoadStop(Tile t) /** * Is tile \a t a road stop station? * @param t Tile to check - * @return \c true if the tile is a station tile and a road stop + * @return \c true if the tile is a station tile and a station road stop */ -inline bool IsRoadStopTile(Tile t) +inline bool IsStationRoadStopTile(Tile t) { - return IsTileType(t, MP_STATION) && IsRoadStop(t); + return IsTileType(t, MP_STATION) && IsStationRoadStop(t); +} + +/** + * Is the station at \a t a road station? + * @param t Tile to check + * @pre IsTileType(t, MP_STATION) + * @return \c true if station at the tile is a bus stop, truck stop or road waypoint, \c false otherwise + */ +inline bool IsAnyRoadStop(Tile t) +{ + assert(IsTileType(t, MP_STATION)); + return IsTruckStop(t) || IsBusStop(t) || IsRoadWaypoint(t); +} + +/** + * Is tile \a t a road stop station? + * @param t Tile to check + * @return \c true if the tile is a station tile and any road stop type + */ +inline bool IsAnyRoadStopTile(Tile t) +{ + return IsTileType(t, MP_STATION) && IsAnyRoadStop(t); } /** @@ -222,21 +265,64 @@ inline bool IsRoadStopTile(Tile t) */ inline bool IsBayRoadStopTile(Tile t) { - return IsRoadStopTile(t) && GetStationGfx(t) < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; + return IsStationRoadStopTile(t) && GetStationGfx(t) < GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; } /** - * Is tile \a t a drive through road stop station? + * Is tile \a t a drive through road stop station or waypoint? * @param t Tile to check * @return \c true if the tile is a station tile and a drive through road stop */ inline bool IsDriveThroughStopTile(Tile t) { - return IsRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; + return IsAnyRoadStopTile(t) && GetStationGfx(t) >= GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET; } StationGfx GetTranslatedAirportTileID(StationGfx gfx); +/** + * Get the decorations of a road waypoint. + * @param tile The tile to query. + * @return The road decoration of the tile. + */ +static inline Roadside GetRoadWaypointRoadside(Tile tile) +{ + assert(IsRoadWaypointTile(tile)); + return (Roadside)GB(tile.m3(), 2, 2); +} + +/** + * Set the decorations of a road waypoint. + * @param tile The tile to change. + * @param s The new road decoration of the tile. + */ +static inline void SetRoadWaypointRoadside(Tile tile, Roadside s) +{ + assert(IsRoadWaypointTile(tile)); + SB(tile.m3(), 2, 2, s); +} + +/** + * Check if a road waypoint tile has snow/desert. + * @param t The tile to query. + * @return True if the tile has snow/desert. + */ +static inline bool IsRoadWaypointOnSnowOrDesert(Tile t) +{ + assert(IsRoadWaypointTile(t)); + return HasBit(t.m8(), 15); +} + +/** + * Toggle the snow/desert state of a road waypoint tile. + * @param t The tile to change. + */ +static inline void ToggleRoadWaypointOnSnowOrDesert(Tile t) +{ + assert(IsRoadWaypointTile(t)); + ToggleBit(t.m8(), 15); +} + /** * Get the station graphics of this airport tile * @param t the tile to query @@ -252,13 +338,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 { @@ -362,7 +448,7 @@ inline void SetStationTileBlocked(Tile t, bool b) inline bool CanStationTileHaveWires(Tile t) { assert(HasStationRail(t)); - return HasBit(t.m6(), 6); + return HasBit(t.m6(), 1); } /** @@ -374,7 +460,7 @@ inline bool CanStationTileHaveWires(Tile t) inline void SetStationTileHaveWires(Tile t, bool b) { assert(HasStationRail(t)); - SB(t.m6(), 6, 1, b ? 1 : 0); + SB(t.m6(), 1, 1, b ? 1 : 0); } /** @@ -555,12 +641,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 +654,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); } @@ -632,7 +718,7 @@ inline void MakeStation(Tile t, Owner o, StationID sid, StationType st, uint8_t t.m4() = 0; t.m5() = section; SB(t.m6(), 2, 1, 0); - SB(t.m6(), 3, 3, st); + SB(t.m6(), 3, 4, st); t.m7() = 0; t.m8() = 0; } @@ -699,9 +785,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/sprites.h b/src/table/sprites.h index ea522724b5..0ab4f1bc99 100644 --- a/src/table/sprites.h +++ b/src/table/sprites.h @@ -307,8 +307,16 @@ static const uint16_t EMPTY_BOUNDING_BOX_SPRITE_COUNT = 1; static const SpriteID SPR_PALETTE_BASE = SPR_EMPTY_BOUNDING_BOX + EMPTY_BOUNDING_BOX_SPRITE_COUNT; static const uint16_t PALETTE_SPRITE_COUNT = 1; +/** Road waypoint sprites. */ +static const SpriteID SPR_ROAD_WAYPOINTS_BASE = SPR_PALETTE_BASE + PALETTE_SPRITE_COUNT; +static const SpriteID SPR_ROAD_WAYPOINT_Y_W = SPR_ROAD_WAYPOINTS_BASE; +static const SpriteID SPR_ROAD_WAYPOINT_Y_E = SPR_ROAD_WAYPOINTS_BASE + 1; +static const SpriteID SPR_ROAD_WAYPOINT_X_W = SPR_ROAD_WAYPOINTS_BASE + 2; +static const SpriteID SPR_ROAD_WAYPOINT_X_E = SPR_ROAD_WAYPOINTS_BASE + 3; +static const uint16_t ROAD_WAYPOINTS_SPRITE_COUNT = 4; + /* From where can we start putting NewGRFs? */ -static const SpriteID SPR_NEWGRFS_BASE = SPR_PALETTE_BASE + PALETTE_SPRITE_COUNT; +static const SpriteID SPR_NEWGRFS_BASE = SPR_ROAD_WAYPOINTS_BASE + ROAD_WAYPOINTS_SPRITE_COUNT; /* Manager face sprites */ static const SpriteID SPR_GRADIENT = 874; // background gradient behind manager face diff --git a/src/table/station_land.h b/src/table/station_land.h index 6e3db1d0d0..fc47de8d77 100644 --- a/src/table/station_land.h +++ b/src/table/station_land.h @@ -763,6 +763,20 @@ static const DrawTileSeqStruct _station_display_datas_0171[] = { TILE_SEQ_END() }; +/* road waypoint X */ +static const DrawTileSeqStruct _station_display_datas_road_waypoint_X[] = { + TILE_SEQ_LINE( 0, 0, 0, 16, 3, 16, SPR_ROAD_WAYPOINT_X_W | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 13, 0, 16, 3, 16, SPR_ROAD_WAYPOINT_X_E | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + +/* road waypoint Y */ +static const DrawTileSeqStruct _station_display_datas_road_waypoint_Y[] = { + TILE_SEQ_LINE(13, 0, 0, 3, 16, 16, SPR_ROAD_WAYPOINT_Y_W | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_LINE( 0, 0, 0, 3, 16, 16, SPR_ROAD_WAYPOINT_Y_E | (1U << PALETTE_MODIFIER_COLOUR)) + TILE_SEQ_END() +}; + static const DrawTileSeqStruct _station_display_datas_waypoint_X[] = { TILE_SEQ_LINE( 0, 0, 0, 16, 5, 23, SPR_WAYPOINT_X_1 | (1U << PALETTE_MODIFIER_COLOUR)) TILE_SEQ_LINE( 0, 11, 0, 16, 5, 23, SPR_WAYPOINT_X_2 | (1U << PALETTE_MODIFIER_COLOUR)) @@ -955,6 +969,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(0, nullptr) + TILE_SPRITE_LINE(0, nullptr) + TILE_SPRITE_LINE(0, nullptr) + TILE_SPRITE_LINE(0, nullptr) + TILE_SPRITE_LINE(SPR_ROAD_PAVED_STRAIGHT_X, _station_display_datas_road_waypoint_X) + TILE_SPRITE_LINE(SPR_ROAD_PAVED_STRAIGHT_Y, _station_display_datas_road_waypoint_Y) +}; + static const DrawTileSprites _station_display_datas_oilrig[] = { TILE_SPRITE_LINE(SPR_FLAT_WATER_TILE, _station_display_nothing) }; @@ -999,4 +1022,5 @@ static const DrawTileSprites * const _station_display_datas[] = { _station_display_datas_dock, _station_display_datas_buoy, _station_display_datas_waypoint, + _station_display_datas_road_waypoint, }; diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 2fae67fc47..16eac9d467 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1238,7 +1238,7 @@ static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, con /* If the next tile is a station, allow if it's a road station facing the proper direction. Otherwise return false. */ if (IsTileType(next_tile, MP_STATION)) { /* If the next tile is a road station, allow if it can be entered by the new tunnel/bridge, otherwise disallow. */ - return IsRoadStop(next_tile) && (GetRoadStopDir(next_tile) == ReverseDiagDir(road_dir) || (IsDriveThroughStopTile(next_tile) && GetRoadStopDir(next_tile) == road_dir)); + return IsAnyRoadStop(next_tile) && (GetRoadStopDir(next_tile) == ReverseDiagDir(road_dir) || (IsDriveThroughStopTile(next_tile) && GetRoadStopDir(next_tile) == road_dir)); } /* If the next tile is a road depot, allow if it's facing the right way. */ @@ -1441,7 +1441,7 @@ static inline bool RoadTypesAllowHouseHere(TileIndex t) TileIndex cur_tile = t + ToTileIndexDiff(ptr); if (!IsValidTile(cur_tile)) continue; - if (!(IsTileType(cur_tile, MP_ROAD) || IsRoadStopTile(cur_tile))) continue; + if (!(IsTileType(cur_tile, MP_ROAD) || IsAnyRoadStopTile(cur_tile))) continue; allow = true; RoadType road_rt = GetRoadTypeRoad(cur_tile); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 2e1b82b172..d329b0b09a 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2390,7 +2390,7 @@ void Vehicle::LeaveStation() } if (this->type == VEH_ROAD && !(this->vehstatus & VS_CRASHED)) { /* Trigger road stop animation */ - if (IsRoadStopTile(this->tile)) { + if (IsStationRoadStopTile(this->tile)) { TriggerRoadStopRandomisation(st, this->tile, RSRT_VEH_DEPARTS); TriggerRoadStopAnimation(st, this->tile, SAT_TRAIN_DEPARTS); } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 240a5cb9c5..dc20cd469c 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -3178,7 +3178,7 @@ public: break; case OT_GOTO_WAYPOINT: { - assert(v->type == VEH_TRAIN || v->type == VEH_SHIP); + assert(v->type == VEH_TRAIN || v->type == VEH_ROAD || v->type == VEH_SHIP); SetDParam(0, v->current_order.GetDestination()); str = HasBit(v->vehicle_flags, VF_PATHFINDER_LOST) ? STR_VEHICLE_STATUS_CANNOT_REACH_WAYPOINT_VEL : STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL; SetDParam(1, PackVelocity(v->GetDisplaySpeed(), v->type)); diff --git a/src/viewport_type.h b/src/viewport_type.h index 4a433387dd..0fde051f38 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -132,8 +132,10 @@ enum ViewportDragDropSelectionProcess { DDSP_PLACE_ROAD_X_DIR, ///< Road placement (X axis) DDSP_PLACE_ROAD_Y_DIR, ///< Road placement (Y axis) DDSP_PLACE_AUTOROAD, ///< Road placement (auto) + DDSP_BUILD_ROAD_WAYPOINT, ///< Road stop placement (waypoint) DDSP_BUILD_BUSSTOP, ///< Road stop placement (buses) DDSP_BUILD_TRUCKSTOP, ///< Road stop placement (trucks) + DDSP_REMOVE_ROAD_WAYPOINT, ///< Road stop removal (waypoint) DDSP_REMOVE_BUSSTOP, ///< Road stop removal (buses) DDSP_REMOVE_TRUCKSTOP, ///< Road stop removal (trucks) DDSP_CONVERT_ROAD, ///< Road conversion diff --git a/src/waypoint.cpp b/src/waypoint.cpp index db1b36b1ec..b08a971bad 100644 --- a/src/waypoint.cpp +++ b/src/waypoint.cpp @@ -38,6 +38,10 @@ void Waypoint::GetTileArea(TileArea *ta, StationType type) const *ta = this->train_station; return; + case STATION_ROADWAYPOINT: + *ta = this->road_waypoint_area; + return; + case STATION_BUOY: ta->tile = this->xy; ta->w = 1; diff --git a/src/waypoint_base.h b/src/waypoint_base.h index f431958e6e..091ef7fed2 100644 --- a/src/waypoint_base.h +++ b/src/waypoint_base.h @@ -12,9 +12,18 @@ #include "base_station_base.h" +/** + * Flags for Waypoint::waypoint_flags. + */ +enum WaypointFlags { + WPF_ROAD = 0, ///< This is a road waypoint +}; + /** Representation of a waypoint. */ struct Waypoint final : SpecializedStation { uint16_t town_cn; ///< The N-1th waypoint for this town (consecutive number) + uint16_t waypoint_flags{}; ///< Waypoint flags, see WaypointFlags + TileArea road_waypoint_area; ///< Tile area the road waypoint part covers /** * Create a waypoint at the given tile. diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index 0ffffcfc09..565a76411d 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -25,6 +25,7 @@ #include "string_func.h" #include "company_func.h" #include "newgrf_station.h" +#include "newgrf_roadstop.h" #include "company_base.h" #include "water.h" #include "company_gui.h" @@ -68,15 +69,16 @@ void Waypoint::MoveSign(TileIndex new_xy) * @param tile to search from * @param str the string to get the 'type' of * @param cid previous owner of the waypoint + * @param is_road whether to find a road waypoint * @return the deleted nearby waypoint */ -static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, CompanyID cid) +static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, CompanyID cid, bool is_road) { Waypoint *best = nullptr; uint thres = 8; for (Waypoint *wp : Waypoint::Iterate()) { - if (!wp->IsInUse() && wp->string_id == str && wp->owner == cid) { + if (!wp->IsInUse() && wp->string_id == str && wp->owner == cid && HasBit(wp->waypoint_flags, WPF_ROAD) == is_road) { uint cur_dist = DistanceManhattan(tile, wp->xy); if (cur_dist < thres) { @@ -90,13 +92,13 @@ static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, Compan } /** - * Get the axis for a new waypoint. This means that if it is a valid + * Get the axis for a new rail waypoint. This means that if it is a valid * tile to build a waypoint on it returns a valid Axis, otherwise an * invalid one. * @param tile the tile to look at. * @return the axis for the to-be-build waypoint. */ -Axis GetAxisForNewWaypoint(TileIndex tile) +Axis GetAxisForNewRailWaypoint(TileIndex tile) { /* The axis for rail waypoints is easy. */ if (IsRailWaypointTile(tile)) return GetRailStationAxis(tile); @@ -111,6 +113,29 @@ Axis GetAxisForNewWaypoint(TileIndex tile) } } +/** + * Get the axis for a new road waypoint. This means that if it is a valid + * tile to build a waypoint on it returns a valid Axis, otherwise an + * invalid one. + * @param tile the tile to look at. + * @return the axis for the to-be-build waypoint. + */ +Axis GetAxisForNewRoadWaypoint(TileIndex tile) +{ + /* The axis for rail waypoints is easy. */ + if (IsRoadWaypointTile(tile)) return DiagDirToAxis(GetRoadStopDir(tile)); + + /* Non-plain road type, no valid axis for waypoints. */ + if (!IsNormalRoadTile(tile)) return INVALID_AXIS; + + RoadBits bits = GetAllRoadBits(tile); + + if ((bits & ROAD_Y) == 0) return AXIS_X; + if ((bits & ROAD_X) == 0) return AXIS_Y; + + return INVALID_AXIS; +} + extern CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags); /** @@ -137,7 +162,7 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID * } } - if (GetAxisForNewWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); + if (GetAxisForNewRailWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); Owner owner = GetTileOwner(tile); CommandCost ret = CheckOwnership(owner); @@ -156,8 +181,10 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID * } extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec); -extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp); +extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road); extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta); +extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlag flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost); +extern CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index); /** * Convert existing rail to waypoint. Eg build a waypoint station over @@ -212,12 +239,12 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis } Waypoint *wp = nullptr; - CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, new_location, &wp); + CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, new_location, &wp, false); if (ret.Failed()) return ret; /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */ TileIndex center_tile = start_tile + (count / 2) * offset; - if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company); + if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company, false); if (wp != nullptr) { /* Reuse an existing waypoint. */ @@ -290,6 +317,145 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis return cost; } +/** + * Convert existing road to waypoint. Eg build a waypoint station over + * piece of road + * @param flags type of operation + * @param start_tile northern most tile where waypoint will be built + * @param axis orientation (Axis) + * @param width width of waypoint + * @param height height of waypoint + * @param spec_class custom road stop class + * @param spec_index custom road stop id + * @param station_to_join station ID to join (NEW_STATION if build new one) + * @param adjacent allow waypoints directly adjacent to other waypoints. + * @return the cost of this operation or an error + */ +CommandCost CmdBuildRoadWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, RoadStopClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent) +{ + if (!IsValidAxis(axis)) return CMD_ERROR; + /* Check if the given station class is valid */ + if (spec_class != ROADSTOP_CLASS_WAYP) return CMD_ERROR; + if (spec_index >= RoadStopClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR; + + const RoadStopSpec *roadstopspec = RoadStopClass::Get(spec_class)->GetSpec(spec_index); + + /* The number of parts to build */ + uint8_t count = axis == AXIS_X ? height : width; + + if ((axis == AXIS_X ? width : height) != 1) return CMD_ERROR; + if (count == 0 || count > _settings_game.station.station_spread) return CMD_ERROR; + + bool reuse = (station_to_join != NEW_STATION); + if (!reuse) station_to_join = INVALID_STATION; + bool distant_join = (station_to_join != INVALID_STATION); + + if (distant_join && (!_settings_game.station.distant_join_stations || !Waypoint::IsValidID(station_to_join))) return CMD_ERROR; + + TileArea roadstop_area(start_tile, width, height); + + /* Total road stop cost. */ + Money unit_cost; + if (roadstopspec != nullptr) { + unit_cost = roadstopspec->GetBuildCost(PR_BUILD_STATION_TRUCK); + } else { + unit_cost = _price[PR_BUILD_STATION_TRUCK]; + } + StationID est = INVALID_STATION; + CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, true, STATION_ROADWAYPOINT, axis, AxisToDiagDir(axis), &est, INVALID_ROADTYPE, unit_cost); + if (cost.Failed()) return cost; + + Waypoint *wp = nullptr; + CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, roadstop_area, &wp, true); + if (ret.Failed()) return ret; + + /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */ + TileIndex center_tile = start_tile + (count / 2) * TileOffsByDiagDir(AxisToDiagDir(OtherAxis(axis)));; + if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company, true); + + if (wp != nullptr) { + /* Reuse an existing waypoint. */ + if (!HasBit(wp->waypoint_flags, WPF_ROAD)) return CMD_ERROR; + if (wp->owner != _current_company) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT); + + ret = wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TEST); + if (ret.Failed()) return ret; + } else { + /* allocate and initialize new waypoint */ + if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); + } + + /* Check if we can allocate a custom stationspec to this station */ + if (AllocateSpecToRoadStop(roadstopspec, wp, false) == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS); + + if (flags & DC_EXEC) { + if (wp == nullptr) { + wp = new Waypoint(start_tile); + SetBit(wp->waypoint_flags, WPF_ROAD); + } else if (!wp->IsInUse()) { + /* Move existing (recently deleted) waypoint to the new location */ + wp->xy = start_tile; + } + wp->owner = _current_company; + + wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TRY); + + if (roadstopspec != nullptr) { + /* Include this road stop spec's animation trigger bitmask + * in the station's cached copy. */ + wp->cached_roadstop_anim_triggers |= roadstopspec->animation.triggers; + } + + wp->delete_ctr = 0; + wp->facilities |= FACIL_BUS_STOP | FACIL_TRUCK_STOP; + wp->build_date = TimerGameCalendar::date; + wp->string_id = STR_SV_STNAME_WAYPOINT; + + if (wp->town == nullptr) MakeDefaultName(wp); + + wp->UpdateVirtCoord(); + + uint8_t map_spec_index = AllocateSpecToRoadStop(roadstopspec, wp, true); + + /* Check every tile in the area. */ + for (TileIndex cur_tile : roadstop_area) { + /* Get existing road types and owners before any tile clearing */ + RoadType road_rt = MayHaveRoad(cur_tile) ? GetRoadType(cur_tile, RTT_ROAD) : INVALID_ROADTYPE; + RoadType tram_rt = MayHaveRoad(cur_tile) ? GetRoadType(cur_tile, RTT_TRAM) : INVALID_ROADTYPE; + Owner road_owner = road_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_ROAD) : _current_company; + Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company; + + if (IsRoadWaypointTile(cur_tile)) { + RemoveRoadWaypointStop(cur_tile, flags, map_spec_index); + } + + wp->road_waypoint_area.Add(cur_tile); + + wp->rect.BeforeAddTile(cur_tile, StationRect::ADD_TRY); + + /* Update company infrastructure counts. If the current tile is a normal road tile, remove the old + * bits first. */ + if (IsNormalRoadTile(cur_tile)) { + UpdateCompanyRoadInfrastructure(road_rt, road_owner, -(int)CountBits(GetRoadBits(cur_tile, RTT_ROAD))); + UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, -(int)CountBits(GetRoadBits(cur_tile, RTT_TRAM))); + } + + UpdateCompanyRoadInfrastructure(road_rt, road_owner, ROAD_STOP_TRACKBIT_FACTOR); + UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR); + + MakeDriveThroughRoadStop(cur_tile, wp->owner, road_owner, tram_owner, wp->index, STATION_ROADWAYPOINT, road_rt, tram_rt, axis); + SetCustomRoadStopSpecIndex(cur_tile, map_spec_index); + if (roadstopspec != nullptr) wp->SetRoadStopRandomBits(cur_tile, 0); + + Company::Get(wp->owner)->infrastructure.station++; + + MarkTileDirtyByTile(cur_tile); + } + DirtyCompanyInfrastructureWindows(wp->owner); + } + return cost; +} + /** * Build a buoy. * @param flags operation to perform @@ -304,7 +470,7 @@ CommandCost CmdBuildBuoy(DoCommandFlag flags, TileIndex tile) if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */ - Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE); + Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE, false); if (wp == nullptr && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_WAYPOINT_BUOY]); diff --git a/src/waypoint_cmd.h b/src/waypoint_cmd.h index 02914124c6..05c916c952 100644 --- a/src/waypoint_cmd.h +++ b/src/waypoint_cmd.h @@ -14,14 +14,19 @@ #include "station_type.h" enum StationClassID : uint16_t; +enum RoadStopClassID : uint16_t; CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, StationClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent); CommandCost CmdRemoveFromRailWaypoint(DoCommandFlag flags, TileIndex start, TileIndex end, bool keep_rail); +CommandCost CmdBuildRoadWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, RoadStopClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent); +CommandCost CmdRemoveFromRoadWaypoint(DoCommandFlag flags, TileIndex start, TileIndex end); CommandCost CmdBuildBuoy(DoCommandFlag flags, TileIndex tile); CommandCost CmdRenameWaypoint(DoCommandFlag flags, StationID waypoint_id, const std::string &text); DEF_CMD_TRAIT(CMD_BUILD_RAIL_WAYPOINT, CmdBuildRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_FROM_RAIL_WAYPOINT, CmdRemoveFromRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_BUILD_ROAD_WAYPOINT, CmdBuildRoadWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_REMOVE_FROM_ROAD_WAYPOINT, CmdRemoveFromRoadWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_BUOY, CmdBuildBuoy, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_RENAME_WAYPOINT, CmdRenameWaypoint, 0, CMDT_OTHER_MANAGEMENT) diff --git a/src/waypoint_func.h b/src/waypoint_func.h index 2906fa6369..4342a9b4ee 100644 --- a/src/waypoint_func.h +++ b/src/waypoint_func.h @@ -16,7 +16,8 @@ CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags); -Axis GetAxisForNewWaypoint(TileIndex tile); +Axis GetAxisForNewRailWaypoint(TileIndex tile); +Axis GetAxisForNewRoadWaypoint(TileIndex tile); void ShowWaypointWindow(const Waypoint *wp); void DrawWaypointSprite(int x, int y, int stat_id, RailType railtype); diff --git a/src/waypoint_gui.cpp b/src/waypoint_gui.cpp index f73e301a17..845da26023 100644 --- a/src/waypoint_gui.cpp +++ b/src/waypoint_gui.cpp @@ -43,8 +43,25 @@ private: { if (!this->wp->IsInUse()) return this->wp->xy; + StationType type; + switch (this->vt) { + case VEH_TRAIN: + type = STATION_WAYPOINT; + break; + + case VEH_ROAD: + type = STATION_ROADWAYPOINT; + break; + + case VEH_SHIP: + type = STATION_BUOY; + break; + + default: + NOT_REACHED(); + } TileArea ta; - this->wp->GetTileArea(&ta, this->vt == VEH_TRAIN ? STATION_WAYPOINT : STATION_BUOY); + this->wp->GetTileArea(&ta, type); return ta.GetCenterTile(); } @@ -57,11 +74,20 @@ public: WaypointWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) { this->wp = Waypoint::Get(window_number); - this->vt = (wp->string_id == STR_SV_STNAME_WAYPOINT) ? VEH_TRAIN : VEH_SHIP; + if (wp->string_id == STR_SV_STNAME_WAYPOINT) { + this->vt = HasBit(this->wp->waypoint_flags, WPF_ROAD) ? VEH_ROAD : VEH_TRAIN; + } else { + this->vt = VEH_SHIP; + } this->CreateNestedTree(); if (this->vt == VEH_TRAIN) { this->GetWidget(WID_W_SHOW_VEHICLES)->SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP); + } + if (this->vt == VEH_ROAD) { + this->GetWidget(WID_W_SHOW_VEHICLES)->SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP); + } + if (this->vt != VEH_SHIP) { this->GetWidget(WID_W_CENTER_VIEW)->tool_tip = STR_WAYPOINT_VIEW_CENTER_TOOLTIP; this->GetWidget(WID_W_RENAME)->tool_tip = STR_WAYPOINT_VIEW_CHANGE_WAYPOINT_NAME; } diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index 5da57d024c..6fa7fb24f6 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. @@ -70,4 +71,13 @@ enum BuildRoadStationWidgets : WidgetID { WID_BROS_NEWST_SCROLL, ///< Scrollbar of the #WID_BROS_NEWST_LIST. }; +/** Widgets of the #BuildRoadWaypointWindow class. */ +enum BuildRoadWaypointWidgets : WidgetID { + WID_BROW_FILTER, ///< Text filter. + WID_BROW_WAYPOINT_MATRIX, ///< Matrix with waypoints. + WID_BROW_WAYPOINT, ///< A single waypoint. + WID_BROW_SCROLL, ///< Scrollbar for the matrix. + WID_BROW_NAME, ///< Name of selected waypoint. +}; + #endif /* WIDGETS_ROAD_WIDGET_H */