summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpeter1138 <peter1138@openttd.org>2019-03-11 10:37:47 +0000
committerNiels Martin Hansen <nielsm@indvikleren.dk>2019-06-30 16:46:32 +0200
commitf538179878370b3bec8bf1575dc30c1377461ebc (patch)
tree890738c37245a49eb2a8ad52f4afb805baaa9d42
parentf1c39153413b07a964dfde8fd9d6310f0da4987b (diff)
downloadopenttd-f538179878370b3bec8bf1575dc30c1377461ebc.tar.xz
Feature: Multi-tile docks and docking points.
-rw-r--r--docs/landscape.html4
-rw-r--r--docs/landscape_grid.html8
-rw-r--r--src/bridge_map.h2
-rw-r--r--src/industry_cmd.cpp7
-rw-r--r--src/order_cmd.cpp2
-rw-r--r--src/pathfinder/npf/npf.cpp22
-rw-r--r--src/pathfinder/yapf/yapf_node_ship.hpp14
-rw-r--r--src/pathfinder/yapf/yapf_ship.cpp94
-rw-r--r--src/rail_cmd.cpp8
-rw-r--r--src/rail_map.h3
-rw-r--r--src/saveload/afterload.cpp23
-rw-r--r--src/saveload/oldloader_sl.cpp2
-rw-r--r--src/saveload/saveload.h1
-rw-r--r--src/saveload/station_sl.cpp12
-rw-r--r--src/script/api/script_order.cpp6
-rw-r--r--src/ship.h2
-rw-r--r--src/ship_cmd.cpp66
-rw-r--r--src/station.cpp6
-rw-r--r--src/station_base.h5
-rw-r--r--src/station_cmd.cpp165
-rw-r--r--src/station_func.h3
-rw-r--r--src/station_map.h1
-rw-r--r--src/tunnelbridge_cmd.cpp11
-rw-r--r--src/water.h1
-rw-r--r--src/water_cmd.cpp55
-rw-r--r--src/water_map.h27
-rw-r--r--src/waypoint_cmd.cpp1
27 files changed, 471 insertions, 80 deletions
diff --git a/docs/landscape.html b/docs/landscape.html
index 1a6cd8386..4ee0da47e 100644
--- a/docs/landscape.html
+++ b/docs/landscape.html
@@ -249,6 +249,7 @@
<td valign=top nowrap>&nbsp;</td>
<td>
<ul>
+ <li>m1 bit 7: Ship docking tile status (for half-tile with water)</li>
<li>m1 bits 4..0: <a href="#OwnershipInfo">owner</a> of the tile</li>
<li>m2: see signals</li>
<li>m3 bits 7..4: see signals</li>
@@ -871,6 +872,7 @@
<td valign=top nowrap>&nbsp;</td>
<td>
<ul>
+ <li>m1 bit 7: Ship docking tile status (for buoys)</li>
<li>m1 bits 6..5: water class for buoys, water part of docks and for airport tiles</li>
<li>m1 bits 4..0: <a href="#OwnershipInfo">owner</a> of the station</li>
<li>m2: index into the array of stations</li>
@@ -1008,6 +1010,7 @@
<td valign=top nowrap>&nbsp;</td>
<td>
<ul>
+ <li>m1 bit 7: Ship docking tile status</li>
<li>m1 bits 6..5 : Water class (sea, canal or river)
<li>m1 bits 4..0: <a href="#OwnershipInfo">owner</a> (for sea, rivers, and coasts normally <tt>11</tt>)</li>
<li>m2: Depot index (for depots only)</li>
@@ -1459,6 +1462,7 @@
<td valign=top nowrap>&nbsp;</td>
<td>
<ul>
+ <li>m1 bit 7: Ship docking tile status (for aqueducts)</li>
<li>m1 bits 4..0: <a href="#OwnershipInfo">owner</a></li>
<li>m3 bits 7..4: <a href="#OwnershipInfo">owner</a> of tram</li>
<li>m4: <a href="#RoadType">Roadtype</a></li>
diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html
index 5d65214cb..d4a88d0bb 100644
--- a/docs/landscape_grid.html
+++ b/docs/landscape_grid.html
@@ -100,7 +100,7 @@ the array so you can quickly see what is used and what is not.
<td class="caption">rail</td>
<td class="bits">XXXX XXXX</td>
<td class="bits">XXXX XXXX</td>
- <td class="bits"><span class="free">OOO</span>X XXXX</td>
+ <td class="bits">X<span class="free">OO</span>X XXXX</td>
<td class="bits"><span class="free">OOOO</span> XXXX <span class="free">OOOO OOOO</span></td>
<td class="bits"><span class="free">OOOO OOOO</span></td>
<td class="bits"><span class="free">OOOO</span> XXXX</td>
@@ -208,7 +208,7 @@ the array so you can quickly see what is used and what is not.
<td class="caption">rail station</td>
<td class="bits">XXXX XXXX</td>
<td class="bits">XXXX XXXX</td>
- <td class="bits"><span class="free">O</span>XXX XXXX</td>
+ <td class="bits">XXXX XXXX</td>
<td class="bits">XXXX XXXX XXXX XXXX</td>
<td class="bits">XXXX <span class="free">OOOO</span></td>
<td class="bits">XXXX XXXX</td>
@@ -300,7 +300,7 @@ the array so you can quickly see what is used and what is not.
<td class="caption">sea, shore</td>
<td class="bits">XXXX XXXX</td>
<td class="bits">XXXX XXXX</td>
- <td class="bits"><span class="free">O</span>XXX XXXX</td>
+ <td class="bits">XXXX XXXX</td>
<td class="bits"><span class="free">OOOO OOOO OOOO OOOO</span></td>
<td class="bits"><span class="free">OOOO OOOO</span></td>
<td class="bits"><span class="free">OOOO OOOO</span></td>
@@ -354,7 +354,7 @@ the array so you can quickly see what is used and what is not.
<td class="caption">tunnel entrance</td>
<td class="bits">XXXX XXXX</td>
<td class="bits">XXXX XXXX</td>
- <td class="bits"><span class="free">OOO</span>X XXXX</td>
+ <td class="bits">X<span class="free">OO</span>X XXXX</td>
<td class="bits"><span class="free">OOOO OOOO OOOO OOOO</span></td>
<td class="bits">XXXX <span class="free">OOOO</span></td>
<td class="bits"><span class="free">OO</span>XX XXXX</td>
diff --git a/src/bridge_map.h b/src/bridge_map.h
index 1461df13a..be37dfd71 100644
--- a/src/bridge_map.h
+++ b/src/bridge_map.h
@@ -15,6 +15,7 @@
#include "rail_map.h"
#include "road_map.h"
#include "bridge.h"
+#include "water_map.h"
/**
* Checks if this is a bridge, instead of a tunnel
@@ -130,6 +131,7 @@ static inline void MakeBridgeRamp(TileIndex t, Owner o, BridgeType bridgetype, D
{
SetTileType(t, MP_TUNNELBRIDGE);
SetTileOwner(t, o);
+ SetDockingTile(t, false);
_m[t].m2 = 0;
_m[t].m3 = 0;
_m[t].m4 = INVALID_ROADTYPE;
diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp
index 06c6cde56..4e4ff5c82 100644
--- a/src/industry_cmd.cpp
+++ b/src/industry_cmd.cpp
@@ -155,6 +155,13 @@ Industry::~Industry()
}
}
+ if (this->neutral_station != nullptr) {
+ /* Remove possible docking tiles */
+ TILE_AREA_LOOP(tile_cur, this->location) {
+ ClearDockingTilesCheckingNeighbours(tile_cur);
+ }
+ }
+
if (GetIndustrySpec(this->type)->behaviour & INDUSTRYBEH_PLANT_FIELDS) {
TileArea ta = TileArea(this->location.tile, 0, 0).Expand(21);
diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp
index 26b999af7..ed2f444cc 100644
--- a/src/order_cmd.cpp
+++ b/src/order_cmd.cpp
@@ -2198,7 +2198,7 @@ bool ProcessOrders(Vehicle *v)
/* If it is unchanged, keep it. */
if (order->Equals(v->current_order) && (v->type == VEH_AIRCRAFT || v->dest_tile != 0) &&
- (v->type != VEH_SHIP || !order->IsType(OT_GOTO_STATION) || Station::Get(order->GetDestination())->dock_tile != INVALID_TILE)) {
+ (v->type != VEH_SHIP || !order->IsType(OT_GOTO_STATION) || Station::Get(order->GetDestination())->ship_station.tile != INVALID_TILE > 0)) {
return false;
}
diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp
index 2cf333890..53f84f2a7 100644
--- a/src/pathfinder/npf/npf.cpp
+++ b/src/pathfinder/npf/npf.cpp
@@ -165,8 +165,8 @@ static int32 NPFCalcStationOrTileHeuristic(AyStar *as, AyStarNode *current, Open
uint dist;
AyStarUserData *user = (AyStarUserData *)as->user_data;
- /* for train-stations, we are going to aim for the closest station tile */
- if (user->type != TRANSPORT_WATER && fstd->station_index != INVALID_STATION) {
+ /* aim for the closest station tile */
+ if (fstd->station_index != INVALID_STATION) {
to = CalcClosestStationTile(fstd->station_index, from, fstd->station_type);
}
@@ -563,6 +563,12 @@ static int32 NPFFindStationOrTile(const AyStar *as, const OpenListNode *current)
if (fstd->station_index == INVALID_STATION && tile == fstd->dest_coords) return AYSTAR_FOUND_END_NODE;
+ if (fstd->v->type == VEH_SHIP) {
+ /* Ships do not actually reach the destination station, so we check for a docking tile instead. */
+ if (IsDockingTile(tile) && IsShipDestinationTile(tile, fstd->station_index)) return AYSTAR_FOUND_END_NODE;
+ return AYSTAR_DONE;
+ }
+
if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == fstd->station_index) {
if (fstd->v->type == VEH_TRAIN) return AYSTAR_FOUND_END_NODE;
@@ -1111,10 +1117,16 @@ static void NPFFillWithOrderData(NPFFindStationOrTileData *fstd, const Vehicle *
* dest_tile, not just any stop of that station.
* So only for train orders to stations we fill fstd->station_index, for all
* others only dest_coords */
- if (v->type != VEH_SHIP && (v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_WAYPOINT))) {
- assert(v->IsGroundVehicle());
+ if (v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_WAYPOINT)) {
fstd->station_index = v->current_order.GetDestination();
- fstd->station_type = (v->type == VEH_TRAIN) ? (v->current_order.IsType(OT_GOTO_STATION) ? STATION_RAIL : STATION_WAYPOINT) : (RoadVehicle::From(v)->IsBus() ? STATION_BUS : STATION_TRUCK);
+ 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;
+ } else if (v->type == VEH_SHIP) {
+ fstd->station_type = v->current_order.IsType(OT_GOTO_STATION) ? STATION_DOCK : STATION_BUOY;
+ }
+
fstd->not_articulated = v->type == VEH_ROAD && !RoadVehicle::From(v)->HasArticulatedPart();
/* Let's take the closest tile of the station as our target for vehicles */
fstd->dest_coords = CalcClosestStationTile(fstd->station_index, v->tile, fstd->station_type);
diff --git a/src/pathfinder/yapf/yapf_node_ship.hpp b/src/pathfinder/yapf/yapf_node_ship.hpp
index df4254fd9..a8f827071 100644
--- a/src/pathfinder/yapf/yapf_node_ship.hpp
+++ b/src/pathfinder/yapf/yapf_node_ship.hpp
@@ -14,7 +14,19 @@
/** Yapf Node for ships */
template <class Tkey_>
-struct CYapfShipNodeT : CYapfNodeT<Tkey_, CYapfShipNodeT<Tkey_> > { };
+struct CYapfShipNodeT : CYapfNodeT<Tkey_, CYapfShipNodeT<Tkey_> > {
+ typedef CYapfNodeT<Tkey_, CYapfShipNodeT<Tkey_> > base;
+
+ TileIndex m_segment_last_tile;
+ Trackdir m_segment_last_td;
+
+ void Set(CYapfShipNodeT *parent, TileIndex tile, Trackdir td, bool is_choice)
+ {
+ base::Set(parent, tile, td, is_choice);
+ m_segment_last_tile = tile;
+ m_segment_last_td = td;
+ }
+};
/* now define two major node types (that differ by key type) */
typedef CYapfShipNodeT<CYapfNodeKeyExitDir> CYapfShipNodeExitDir;
diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp
index 09a4fc76a..c665fc53e 100644
--- a/src/pathfinder/yapf/yapf_ship.cpp
+++ b/src/pathfinder/yapf/yapf_ship.cpp
@@ -11,12 +11,95 @@
#include "../../stdafx.h"
#include "../../ship.h"
+#include "../../industry.h"
#include "yapf.hpp"
#include "yapf_node_ship.hpp"
#include "../../safeguards.h"
+template <class Types>
+class CYapfDestinationTileWaterT
+{
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::TrackFollower TrackFollower;
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+ typedef typename Node::Key Key; ///< key to hash tables
+
+protected:
+ TileIndex m_destTile;
+ TrackdirBits m_destTrackdirs;
+ StationID m_destStation;
+
+public:
+ void SetDestination(const Ship *v)
+ {
+ if (v->current_order.IsType(OT_GOTO_STATION)) {
+ m_destStation = v->current_order.GetDestination();
+ m_destTile = CalcClosestStationTile(m_destStation, v->tile, STATION_DOCK);
+ m_destTrackdirs = INVALID_TRACKDIR_BIT;
+ } else {
+ m_destStation = INVALID_STATION;
+ m_destTile = v->dest_tile;
+ m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_WATER, 0));
+ }
+ }
+
+protected:
+ /** to access inherited path finder */
+ inline Tpf& Yapf()
+ {
+ return *static_cast<Tpf*>(this);
+ }
+
+public:
+ /** Called by YAPF to detect if node ends in the desired destination */
+ inline bool PfDetectDestination(Node& n)
+ {
+ return PfDetectDestinationTile(n.m_segment_last_tile, n.m_segment_last_td);
+ }
+
+ inline bool PfDetectDestinationTile(TileIndex tile, Trackdir trackdir)
+ {
+ if (m_destStation != INVALID_STATION) {
+ return IsDockingTile(tile) && IsShipDestinationTile(tile, m_destStation);
+ }
+
+ return tile == m_destTile && ((m_destTrackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE);
+ }
+
+ /**
+ * Called by YAPF to calculate cost estimate. Calculates distance to the destination
+ * adds it to the actual cost from origin and stores the sum to the Node::m_estimate
+ */
+ inline bool PfCalcEstimate(Node& n)
+ {
+ static const int dg_dir_to_x_offs[] = {-1, 0, 1, 0};
+ static const int dg_dir_to_y_offs[] = {0, 1, 0, -1};
+ if (PfDetectDestination(n)) {
+ n.m_estimate = n.m_cost;
+ return true;
+ }
+
+ TileIndex tile = n.m_segment_last_tile;
+ DiagDirection exitdir = TrackdirToExitdir(n.m_segment_last_td);
+ int x1 = 2 * TileX(tile) + dg_dir_to_x_offs[(int)exitdir];
+ int y1 = 2 * TileY(tile) + dg_dir_to_y_offs[(int)exitdir];
+ int x2 = 2 * TileX(m_destTile);
+ int y2 = 2 * TileY(m_destTile);
+ int dx = abs(x1 - x2);
+ int dy = abs(y1 - y2);
+ int dmin = min(dx, dy);
+ int dxy = abs(dx - dy);
+ int d = dmin * YAPF_TILE_CORNER_LENGTH + (dxy - 1) * (YAPF_TILE_LENGTH / 2);
+ n.m_estimate = n.m_cost + d;
+ assert(n.m_estimate >= n.m_parent->m_estimate);
+ return true;
+ }
+};
+
+
/** Node Follower module of YAPF for ships */
template <class Types>
class CYapfFollowShipT
@@ -75,14 +158,12 @@ public:
/* convert origin trackdir to TrackdirBits */
TrackdirBits trackdirs = TrackdirToTrackdirBits(trackdir);
- /* get available trackdirs on the destination tile */
- TrackdirBits dest_trackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_WATER, 0));
/* create pathfinder instance */
Tpf pf;
/* set origin and destination nodes */
pf.SetOrigin(src_tile, trackdirs);
- pf.SetDestination(v->dest_tile, dest_trackdirs);
+ pf.SetDestination(v);
/* find best path */
path_found = pf.FindPath(v);
@@ -124,14 +205,11 @@ public:
*/
static bool CheckShipReverse(const Ship *v, TileIndex tile, Trackdir td1, Trackdir td2)
{
- /* get available trackdirs on the destination tile */
- TrackdirBits dest_trackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_WATER, 0));
-
/* create pathfinder instance */
Tpf pf;
/* set origin and destination nodes */
pf.SetOrigin(tile, TrackdirToTrackdirBits(td1) | TrackdirToTrackdirBits(td2));
- pf.SetDestination(v->dest_tile, dest_trackdirs);
+ pf.SetDestination(v);
/* find best path */
if (!pf.FindPath(v)) return false;
@@ -230,7 +308,7 @@ struct CYapfShip_TypesT
typedef CYapfBaseT<Types> PfBase; // base pathfinder class
typedef CYapfFollowShipT<Types> PfFollow; // node follower
typedef CYapfOriginTileT<Types> PfOrigin; // origin provider
- typedef CYapfDestinationTileT<Types> PfDestination; // destination/distance provider
+ typedef CYapfDestinationTileWaterT<Types> PfDestination; // destination/distance provider
typedef CYapfSegmentCostCacheNoneT<Types> PfCache; // segment cost cache provider
typedef CYapfCostShipT<Types> PfCost; // cost provider
};
diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp
index ab87fc40f..df862d4f2 100644
--- a/src/rail_cmd.cpp
+++ b/src/rail_cmd.cpp
@@ -570,6 +570,7 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u
default: {
/* Will there be flat water on the lower halftile? */
bool water_ground = IsTileType(tile, MP_WATER) && IsSlopeWithOneCornerRaised(tileh);
+ bool docking = IsPossibleDockingTile(tile) && IsDockingTile(tile);
CommandCost ret = CheckRailSlope(tileh, trackbit, TRACK_BIT_NONE, tile);
if (ret.Failed()) return ret;
@@ -586,7 +587,10 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u
if (flags & DC_EXEC) {
MakeRailNormal(tile, _current_company, trackbit, railtype);
- if (water_ground) SetRailGroundType(tile, RAIL_GROUND_WATER);
+ if (water_ground) {
+ SetRailGroundType(tile, RAIL_GROUND_WATER);
+ SetDockingTile(tile, docking);
+ }
Company::Get(_current_company)->infrastructure.rail[railtype]++;
DirtyCompanyInfrastructureWindows(_current_company);
}
@@ -708,7 +712,9 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1,
Slope tileh = GetTileSlope(tile);
/* If there is flat water on the lower halftile, convert the tile to shore so the water remains */
if (GetRailGroundType(tile) == RAIL_GROUND_WATER && IsSlopeWithOneCornerRaised(tileh)) {
+ bool docking = IsDockingTile(tile);
MakeShore(tile);
+ SetDockingTile(tile, docking);
} else {
DoClearSquare(tile);
}
diff --git a/src/rail_map.h b/src/rail_map.h
index 74afe5ace..0142559e7 100644
--- a/src/rail_map.h
+++ b/src/rail_map.h
@@ -17,6 +17,7 @@
#include "signal_func.h"
#include "track_func.h"
#include "tile_map.h"
+#include "water_map.h"
#include "signal_type.h"
@@ -521,6 +522,7 @@ static inline void MakeRailNormal(TileIndex t, Owner o, TrackBits b, RailType r)
{
SetTileType(t, MP_RAILWAY);
SetTileOwner(t, o);
+ SetDockingTile(t, false);
_m[t].m2 = 0;
_m[t].m3 = 0;
_m[t].m4 = 0;
@@ -535,6 +537,7 @@ static inline void MakeRailDepot(TileIndex t, Owner o, DepotID did, DiagDirectio
{
SetTileType(t, MP_RAILWAY);
SetTileOwner(t, o);
+ SetDockingTile(t, false);
_m[t].m2 = did;
_m[t].m3 = 0;
_m[t].m4 = 0;
diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp
index 662f5a77b..770237d53 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -57,6 +57,7 @@
#include "../error.h"
#include "../disaster_vehicle.h"
#include "../ship.h"
+#include "../water.h"
#include "saveload_internal.h"
@@ -675,7 +676,6 @@ bool AfterLoadGame()
Station *st;
FOR_ALL_STATIONS(st) {
if (st->airport.tile == 0) st->airport.tile = INVALID_TILE;
- if (st->dock_tile == 0) st->dock_tile = INVALID_TILE;
if (st->train_station.tile == 0) st->train_station.tile = INVALID_TILE;
}
@@ -3177,6 +3177,27 @@ bool AfterLoadGame()
}
}
+ /* Update structures for multitile docks */
+ if (IsSavegameVersionBefore(SLV_MULTITILE_DOCKS)) {
+ for (TileIndex t = 0; t < map_size; t++) {
+ /* Clear docking tile flag from relevant tiles as it
+ * was not previously cleared. */
+ if (IsTileType(t, MP_WATER) || IsTileType(t, MP_RAILWAY) || IsTileType(t, MP_STATION) || IsTileType(t, MP_TUNNELBRIDGE)) {
+ SetDockingTile(t, false);
+ }
+ /* Add docks and oilrigs to Station::ship_station. */
+ if (IsTileType(t, MP_STATION)) {
+ if (IsDock(t) || IsOilRig(t)) Station::GetByTile(t)->ship_station.Add(t);
+ }
+ }
+
+ /* Scan for docking tiles */
+ Station *st;
+ FOR_ALL_STATIONS(st) {
+ if (st->ship_station.tile != INVALID_TILE) UpdateStationDockingTiles(st);
+ }
+ }
+
/* Compute station catchment areas. This is needed here in case UpdateStationAcceptance is called below. */
Station::RecomputeCatchmentForAll();
diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp
index f7309a474..2287d30a1 100644
--- a/src/saveload/oldloader_sl.cpp
+++ b/src/saveload/oldloader_sl.cpp
@@ -725,7 +725,7 @@ static const OldChunks station_chunk[] = {
OCL_NULL( 4 ), ///< bus/lorry tile
OCL_SVAR( OC_TILE, Station, train_station.tile ),
OCL_SVAR( OC_TILE, Station, airport.tile ),
- OCL_SVAR( OC_TILE, Station, dock_tile ),
+ OCL_NULL( 4 ), ///< dock tile
OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, Station, train_station.w ),
OCL_NULL( 1 ), ///< sort-index, no longer in use
diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h
index fd8b3695f..323662f3f 100644
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -301,6 +301,7 @@ enum SaveLoadVersion : uint16 {
SLV_ROAD_TYPES, ///< 214 PR#6811 NewGRF road types.
SLV_SCRIPT_MEMLIMIT, ///< 215 PR#7516 Limit on AI/GS memory consumption.
+ SLV_MULTITILE_DOCKS, ///< 216 PR#7380 Multiple docks per station.
SL_MAX_VERSION, ///< Highest possible saveload version
};
diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp
index 5e94de1a2..b9dcbd9e8 100644
--- a/src/saveload/station_sl.cpp
+++ b/src/saveload/station_sl.cpp
@@ -174,8 +174,8 @@ static const SaveLoad _old_station_desc[] = {
SLE_CONDVAR(Station, train_station.tile, SLE_UINT32, SLV_6, SL_MAX_VERSION),
SLE_CONDVAR(Station, airport.tile, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6),
SLE_CONDVAR(Station, airport.tile, SLE_UINT32, SLV_6, SL_MAX_VERSION),
- SLE_CONDVAR(Station, dock_tile, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6),
- SLE_CONDVAR(Station, dock_tile, SLE_UINT32, SLV_6, SL_MAX_VERSION),
+ SLE_CONDNULL(2, SL_MIN_VERSION, SLV_6),
+ SLE_CONDNULL(4, SLV_6, SLV_MULTITILE_DOCKS),
SLE_REF(Station, town, REF_TOWN),
SLE_VAR(Station, train_station.w, SLE_FILE_U8 | SLE_VAR_U16),
SLE_CONDVAR(Station, train_station.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_2, SL_MAX_VERSION),
@@ -423,7 +423,13 @@ static const SaveLoad _station_desc[] = {
SLE_REF(Station, bus_stops, REF_ROADSTOPS),
SLE_REF(Station, truck_stops, REF_ROADSTOPS),
- SLE_VAR(Station, dock_tile, SLE_UINT32),
+ SLE_CONDNULL(4, SL_MIN_VERSION, SLV_MULTITILE_DOCKS),
+ SLE_CONDVAR(Station, ship_station.tile, SLE_UINT32, SLV_MULTITILE_DOCKS, SL_MAX_VERSION),
+ SLE_CONDVAR(Station, ship_station.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_MULTITILE_DOCKS, SL_MAX_VERSION),
+ SLE_CONDVAR(Station, ship_station.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_MULTITILE_DOCKS, SL_MAX_VERSION),
+ SLE_CONDVAR(Station, docking_station.tile, SLE_UINT32, SLV_MULTITILE_DOCKS, SL_MAX_VERSION),
+ SLE_CONDVAR(Station, docking_station.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_MULTITILE_DOCKS, SL_MAX_VERSION),
+ SLE_CONDVAR(Station, docking_station.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_MULTITILE_DOCKS, SL_MAX_VERSION),
SLE_VAR(Station, airport.tile, SLE_UINT32),
SLE_CONDVAR(Station, airport.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_140, SL_MAX_VERSION),
SLE_CONDVAR(Station, airport.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_140, SL_MAX_VERSION),
diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp
index 04f7c69f4..a72245d04 100644
--- a/src/script/api/script_order.cpp
+++ b/src/script/api/script_order.cpp
@@ -261,8 +261,10 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr
TILE_AREA_LOOP(t, st->train_station) {
if (st->TileBelongsToRailStation(t)) return t;
}
- } else if (st->dock_tile != INVALID_TILE) {
- return st->dock_tile;
+ } else if (st->ship_station.tile != INVALID_TILE) {
+ TILE_AREA_LOOP(t, st->ship_station) {
+ if (IsDockTile(t) && GetStationIndex(t) == st->index) return t;
+ }
} else if (st->bus_stops != nullptr) {
return st->bus_stops->xy;
} else if (st->truck_stops != nullptr) {
diff --git a/src/ship.h b/src/ship.h
index fff0aee5c..ef009046a 100644
--- a/src/ship.h
+++ b/src/ship.h
@@ -57,6 +57,8 @@ struct Ship FINAL : public SpecializedVehicle<Ship, VEH_SHIP> {
void SetDestTile(TileIndex tile);
};
+bool IsShipDestinationTile(TileIndex tile, StationID station);
+
/**
* Iterate over all ships.
* @param var The variable used for iteration.
diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp
index 08b07a07d..73f2ee97f 100644
--- a/src/ship_cmd.cpp
+++ b/src/ship_cmd.cpp
@@ -34,6 +34,8 @@
#include "tunnelbridge_map.h"
#include "zoom_func.h"
#include "framerate_type.h"
+#include "industry.h"
+#include "industry_map.h"
#include "table/strings.h"
@@ -289,8 +291,8 @@ TileIndex Ship::GetOrderStationLocation(StationID station)
if (station == this->last_station_visited) this->last_station_visited = INVALID_STATION;
const Station *st = Station::Get(station);
- if (st->dock_tile != INVALID_TILE) {
- return TILE_ADD(st->dock_tile, ToTileIndexDiff(GetDockOffset(st->dock_tile)));
+ if (CanVehicleUseStation(this, st)) {
+ return st->xy;
} else {
this->IncrementRealOrderIndex();
return 0;
@@ -597,6 +599,28 @@ static bool ShipMoveUpDownOnLock(Ship *v)
return true;
}
+/**
+ * Test if a tile is a docking tile for the given station.
+ * @param tile Docking tile to test.
+ * @param station Destination station.
+ * @return true iff docking tile is next to station.
+ */
+bool IsShipDestinationTile(TileIndex tile, StationID station)
+{
+ assert(IsDockingTile(tile));
+ /* Check each tile adjacent to docking tile. */
+ for (DiagDirection d = DIAGDIR_BEGIN; d != DIAGDIR_END; d++) {
+ TileIndex t = tile + TileOffsByDiagDir(d);
+ if (!IsValidTile(t)) continue;
+ if (IsDockTile(t) && GetStationIndex(t) == station) return true;
+ if (IsTileType(t, MP_INDUSTRY)) {
+ const Industry *i = Industry::GetByTile(t);
+ if (i->neutral_station != nullptr && i->neutral_station->index == station) return true;
+ }
+ }
+ return false;
+}
+
static void ShipController(Ship *v)
{
uint32 r;
@@ -665,26 +689,24 @@ static void ShipController(Ship *v)
UpdateVehicleTimetable(v, true);
v->IncrementRealOrderIndex();
v->current_order.MakeDummy();
- } else {
- /* Non-buoy orders really need to reach the tile */
- if (v->dest_tile == gp.new_tile) {
- if (v->current_order.IsType(OT_GOTO_DEPOT)) {
- if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) {
- VehicleEnterDepot(v);
- return;
- }
- } else if (v->current_order.IsType(OT_GOTO_STATION)) {
- v->last_station_visited = v->current_order.GetDestination();
-
- /* Process station in the orderlist. */
- Station *st = Station::Get(v->current_order.GetDestination());
- if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations
- ShipArrivesAt(v, st);
- v->BeginLoading();
- } else { // leave stations without docks right aways
- v->current_order.MakeLeaveStation();
- v->IncrementRealOrderIndex();
- }
+ } else if (v->current_order.IsType(OT_GOTO_DEPOT) &&
+ v->dest_tile == gp.new_tile) {
+ /* Depot orders really need to reach the tile */
+ if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) {
+ VehicleEnterDepot(v);
+ return;
+ }
+ } else if (v->current_order.IsType(OT_GOTO_STATION) && IsDockingTile(gp.new_tile)) {
+ /* Process station in the orderlist. */
+ Station *st = Station::Get(v->current_order.GetDestination());
+ if (st->docking_station.Contains(gp.new_tile) && IsShipDestinationTile(gp.new_tile, st->index)) {
+ v->last_station_visited = st->index;
+ if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations
+ ShipArrivesAt(v, st);
+ v->BeginLoading();
+ } else { // leave stations without docks right aways
+ v->current_order.MakeLeaveStation();
+ v->IncrementRealOrderIndex();
}
}
}
diff --git a/src/station.cpp b/src/station.cpp
index 096a3c373..1d2eea4ca 100644
--- a/src/station.cpp
+++ b/src/station.cpp
@@ -71,7 +71,7 @@ Station::Station(TileIndex tile) :
SpecializedStation<Station, false>(tile),
bus_station(INVALID_TILE, 0, 0),
truck_station(INVALID_TILE, 0, 0),
- dock_tile(INVALID_TILE),
+ ship_station(INVALID_TILE, 0, 0),
indtype(IT_INVALID),
time_since_load(255),
time_since_unload(255),
@@ -329,10 +329,10 @@ uint Station::GetCatchmentRadius() const
if (this->bus_stops != nullptr) ret = max<uint>(ret, CA_BUS);
if (this->truck_stops != nullptr) ret = max<uint>(ret, CA_TRUCK);
if (this->train_station.tile != INVALID_TILE) ret = max<uint>(ret, CA_TRAIN);
- if (this->dock_tile != INVALID_TILE) ret = max<uint>(ret, CA_DOCK);
+ if (this->ship_station.tile != INVALID_TILE) ret = max<uint>(ret, CA_DOCK);
if (this->airport.tile != INVALID_TILE) ret = max<uint>(ret, this->airport.GetSpec()->catchment);
} else {
- if (this->bus_stops != nullptr || this->truck_stops != nullptr || this->train_station.tile != INVALID_TILE || this->dock_tile != INVALID_TILE || this->airport.tile != INVALID_TILE) {
+ if (this->bus_stops != nullptr || this->truck_stops != nullptr || this->train_station.tile != INVALID_TILE || this->ship_station.tile != INVALID_TILE || this->airport.tile != INVALID_TILE) {
ret = CA_UNMODIFIED;
}
}
diff --git a/src/station_base.h b/src/station_base.h
index a0cb2c015..1f597a289 100644
--- a/src/station_base.h
+++ b/src/station_base.h
@@ -463,8 +463,9 @@ public:
RoadStop *truck_stops; ///< All the truck stops
TileArea truck_station; ///< Tile area the truck 'station' part covers
- Airport airport; ///< Tile area the airport covers
- TileIndex dock_tile; ///< The location of the dock
+ Airport airport; ///< Tile area the airport covers
+ TileArea ship_station; ///< Tile area the ship 'station' part covers
+ TileArea docking_station; ///< Tile area the docking tiles cover
IndustryType indtype; ///< Industry type to get the name from
diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp
index 9dbe356e7..a7d986676 100644
--- a/src/station_cmd.cpp
+++ b/src/station_cmd.cpp
@@ -56,6 +56,7 @@
#include "linkgraph/linkgraph_base.h"
#include "linkgraph/refresh.h"
#include "widgets/station_widget.h"
+#include "tunnelbridge_map.h"
#include "table/strings.h"
@@ -401,7 +402,7 @@ void Station::GetTileArea(TileArea *ta, StationType type) const
case STATION_DOCK:
case STATION_OILRIG:
- ta->tile = this->dock_tile;
+ *ta = this->docking_station;
break;
default: NOT_REACHED();
@@ -1459,16 +1460,14 @@ CommandCost CmdBuildRailStation(TileIndex tile_org, DoCommandFlag flags, uint32
return cost;
}
-static void MakeRailStationAreaSmaller(BaseStation *st)
+static TileArea MakeStationAreaSmaller(BaseStation *st, TileArea ta, bool (*func)(BaseStation *, TileIndex))
{
- TileArea ta = st->train_station;
-
restart:
/* too small? */
if (ta.w != 0 && ta.h != 0) {
/* check the left side, x = constant, y changes */
- for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(0, i));) {
+ for (uint i = 0; !func(st, ta.tile + TileDiffXY(0, i));) {
/* the left side is unused? */
if (++i == ta.h) {
ta.tile += TileDiffXY(1, 0);
@@ -1478,7 +1477,7 @@ restart:
}
/* check the right side, x = constant, y changes */
- for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(ta.w - 1, i));) {
+ for (uint i = 0; !func(st, ta.tile + TileDiffXY(ta.w - 1, i));) {
/* the right side is unused? */
if (++i == ta.h) {
ta.w--;
@@ -1487,7 +1486,7 @@ restart:
}
/* check the upper side, y = constant, x changes */
- for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(i, 0));) {
+ for (uint i = 0; !func(st, ta.tile + TileDiffXY(i, 0));) {
/* the left side is unused? */
if (++i == ta.w) {
ta.tile += TileDiffXY(0, 1);
@@ -1497,7 +1496,7 @@ restart:
}
/* check the lower side, y = constant, x changes */
- for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(i, ta.h - 1));) {
+ for (uint i = 0; !func(st, ta.tile + TileDiffXY(i, ta.h - 1));) {
/* the left side is unused? */
if (++i == ta.w) {
ta.h--;
@@ -1508,7 +1507,28 @@ restart:
ta.Clear();
}
- st->train_station = ta;
+ return ta;
+}
+
+static bool TileBelongsToRailStation(BaseStation *st, TileIndex tile)
+{
+ return st->TileBelongsToRailStation(tile);
+}
+
+static void MakeRailStationAreaSmaller(BaseStation *st)
+{
+ st->train_station = MakeStationAreaSmaller(st, st->train_station, TileBelongsToRailStation);
+}
+
+static bool TileBelongsToShipStation(BaseStation *st, TileIndex tile)
+{
+ return IsDockTile(tile) && GetStationIndex(tile) == st->index;
+}
+
+static void MakeShipStationAreaSmaller(Station *st)
+{
+ st->ship_station = MakeStationAreaSmaller(st, st->ship_station, TileBelongsToShipStation);
+ UpdateStationDockingTiles(st);
}
/**
@@ -2553,10 +2573,9 @@ CommandCost CmdBuildDock(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
ret = BuildStationPart(&st, flags, reuse, dock_area, STATIONNAMING_DOCK);
if (ret.Failed()) return ret;
- if (st != nullptr && st->dock_tile != INVALID_TILE) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_DOCK);
-
if (flags & DC_EXEC) {
- st->dock_tile = tile;
+ st->ship_station.Add(tile);
+ st->ship_station.Add(tile + TileOffsByDiagDir(direction));
st->AddFacility(FACIL_DOCK, tile);
st->rect.BeforeAddRect(dock_area.tile, dock_area.w, dock_area.h, StationRect::ADD_TRY);
@@ -2569,6 +2588,7 @@ CommandCost CmdBuildDock(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
Company::Get(st->owner)->infrastructure.station += 2;
MakeDock(tile, st->owner, st->index, direction, wc);
+ UpdateStationDockingTiles(st);
st->AfterStationTileSetChange(true, STATION_DOCK);
}
@@ -2576,6 +2596,63 @@ CommandCost CmdBuildDock(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_STATION_DOCK]);
}
+void RemoveDockingTile(TileIndex t)
+{
+ for (DiagDirection d = DIAGDIR_BEGIN; d != DIAGDIR_END; d++) {
+ TileIndex tile = t + TileOffsByDiagDir(d);
+ if (!IsValidTile(tile)) continue;
+
+ if (IsTileType(tile, MP_STATION)) {
+ UpdateStationDockingTiles(Station::GetByTile(tile));
+ } else if (IsTileType(tile, MP_INDUSTRY)) {
+ UpdateStationDockingTiles(Industry::GetByTile(tile)->neutral_station);
+ }
+ }
+}
+
+/**
+ * Clear docking tile status from tiles around a removed dock, if the tile has
+ * no neighbours which would keep it as a docking tile.
+ * @param tile Ex-dock tile to check.
+ */
+void ClearDockingTilesCheckingNeighbours(TileIndex tile)
+{
+ assert(IsValidTile(tile));
+
+ /* Clear and maybe re-set docking tile */
+ for (DiagDirection d = DIAGDIR_BEGIN; d != DIAGDIR_END; d++) {
+ TileIndex docking_tile = tile + TileOffsByDiagDir(d);
+ if (!IsValidTile(docking_tile)) continue;
+
+ if (IsPossibleDockingTile(docking_tile)) {
+ SetDockingTile(docking_tile, false);
+ CheckForDockingTile(docking_tile);
+ }
+ }
+}
+
+/**
+ * Find the part of a dock that is land-based
+ * @param t Dock tile to find land part of
+ * @return tile of land part of dock
+ */
+static TileIndex FindDockLandPart(TileIndex t)
+{
+ assert(IsDockTile(t));
+
+ StationGfx gfx = GetStationGfx(t);
+ if (gfx < GFX_DOCK_BASE_WATER_PART) return t;
+
+ for (DiagDirection d = DIAGDIR_BEGIN; d != DIAGDIR_END; d++) {
+ TileIndex tile = t + TileOffsByDiagDir(d);
+ if (!IsValidTile(tile)) continue;
+ if (!IsDockTile(tile)) continue;
+ if (GetStationGfx(tile) < GFX_DOCK_BASE_WATER_PART && tile + TileOffsByDiagDir(GetDockDirection(tile)) == t) return tile;
+ }
+
+ return INVALID_TILE;
+}
+
/**
* Remove a dock
* @param tile TileIndex been queried
@@ -2588,9 +2665,10 @@ static CommandCost RemoveDock(TileIndex tile, DoCommandFlag flags)
CommandCost ret = CheckOwnership(st->owner);
if (ret.Failed()) return ret;
- TileIndex docking_location = TILE_ADD(st->dock_tile, ToTileIndexDiff(GetDockOffset(st->dock_tile)));
+ if (!IsDockTile(tile)) return CMD_ERROR;
- TileIndex tile1 = st->dock_tile;
+ TileIndex tile1 = FindDockLandPart(tile);
+ if (tile1 == INVALID_TILE) return CMD_ERROR;
TileIndex tile2 = tile1 + TileOffsByDiagDir(GetDockDirection(tile1));
ret = EnsureNoVehicleOnGround(tile1);
@@ -2605,26 +2683,34 @@ static CommandCost RemoveDock(TileIndex tile, DoCommandFlag flags)
st->rect.AfterRemoveTile(st, tile1);
st->rect.AfterRemoveTile(st, tile2);
- st->dock_tile = INVALID_TILE;
- st->facilities &= ~FACIL_DOCK;
+ MakeShipStationAreaSmaller(st);
+ if (st->ship_station.tile == INVALID_TILE) {
+ st->ship_station.Clear();
+ st->docking_station.Clear();
+ st->facilities &= ~FACIL_DOCK;
+ }
Company::Get(st->owner)->infrastructure.station -= 2;
st->AfterStationTileSetChange(false, STATION_DOCK);
+ ClearDockingTilesCheckingNeighbours(tile1);
+ ClearDockingTilesCheckingNeighbours(tile2);
+
/* All ships that were going to our station, can't go to it anymore.
* Just clear the order, then automatically the next appropriate order
* will be selected and in case of no appropriate order it will just
* wander around the world. */
- Ship *s;
- FOR_ALL_SHIPS(s) {
- if (s->current_order.IsType(OT_LOADING) && s->tile == docking_location) {
- s->LeaveStation();
- }
+ if (!(st->facilities & FACIL_DOCK)) {
+ Ship *s;
+ FOR_ALL_SHIPS(s) {
+ if (s->current_order.IsType(OT_LOADING) && s->current_order.GetDestination() == st->index) {
+ s->LeaveStation();
+ }
- if (s->dest_tile == docking_location) {
- s->SetDestTile(0);
- s->current_order.Free();
+ if (s->current_order.IsType(OT_GOTO_STATION) && s->current_order.GetDestination() == st->index) {
+ s->SetDestTile(s->GetOrderStationLocation(st->index));
+ }
}
}
}
@@ -2873,7 +2959,7 @@ draw_default_foundation:
} else {
assert(IsDock(ti->tile));
TileIndex water_tile = ti->tile + TileOffsByDiagDir(GetDockDirection(ti->tile));
- WaterClass wc = GetWaterClass(water_tile);
+ WaterClass wc = HasTileWaterClass(water_tile) ? GetWaterClass(water_tile) : WATER_CLASS_INVALID;
if (wc == WATER_CLASS_SEA) {
DrawShoreTile(ti->tileh);
} else {
@@ -3991,6 +4077,32 @@ uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, Sourc
return moved + UpdateStationWaiting(st2, type, worst_cargo, source_type, source_id);
}
+void UpdateStationDockingTiles(Station *st)
+{
+ st->docking_station.Clear();
+
+ /* For neutral stations, start with the industry area instead of dock area */
+ const TileArea *area = st->industry != nullptr ? &st->industry->location : &st->ship_station;
+
+ if (area->tile == INVALID_TILE) return;
+
+ int x = TileX(area->tile);
+ int y = TileY(area->tile);
+
+ /* Expand the area by a tile on each side while
+ * making sure that we remain inside the map. */
+ int x2 = min(x + area->w + 1, MapSizeX());
+ int x1 = max(x - 1, 0);
+
+ int y2 = min(y + area->h + 1, MapSizeY());
+ int y1 = max(y - 1, 0);
+
+ TileArea ta(TileXY(x1, y1), TileXY(x2 - 1, y2 - 1));
+ TILE_AREA_LOOP(tile, ta) {
+ if (IsValidTile(tile) && IsPossibleDockingTile(tile)) CheckForDockingTile(tile);
+ }
+}
+
void BuildOilRig(TileIndex tile)
{
if (!Station::CanAllocateItem()) {
@@ -4014,9 +4126,10 @@ void BuildOilRig(TileIndex tile)
st->owner = OWNER_NONE;
st->airport.type = AT_OILRIG;
st->airport.Add(tile);
- st->dock_tile = tile;
+ st->ship_station.Add(tile);
st->facilities = FACIL_AIRPORT | FACIL_DOCK;
st->build_date = _date;
+ UpdateStationDockingTiles(st);
st->rect.BeforeAddTile(tile, StationRect::ADD_FORCE);
diff --git a/src/station_func.h b/src/station_func.h
index a6d082b93..44aec087f 100644
--- a/src/station_func.h
+++ b/src/station_func.h
@@ -40,6 +40,9 @@ void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, Ro
bool HasStationInUse(StationID station, bool include_company, CompanyID company);
void DeleteOilRig(TileIndex t);
+void UpdateStationDockingTiles(Station *st);
+void RemoveDockingTile(TileIndex t);
+void ClearDockingTilesCheckingNeighbours(TileIndex tile);
/* Check if a rail station tile is traversable. */
bool IsStationTileBlocked(TileIndex tile);
diff --git a/src/station_map.h b/src/station_map.h
index 35766ec69..98f8f288b 100644
--- a/src/station_map.h
+++ b/src/station_map.h
@@ -536,6 +536,7 @@ static inline void MakeStation(TileIndex t, Owner o, StationID sid, StationType
SetTileType(t, MP_STATION);
SetTileOwner(t, o);
SetWaterClass(t, wc);
+ SetDockingTile(t, false);
_m[t].m2 = sid;
_m[t].m3 = 0;
_m[t].m4 = 0;
diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp
index 83cec4fd3..fce0855d2 100644
--- a/src/tunnelbridge_cmd.cpp
+++ b/src/tunnelbridge_cmd.cpp
@@ -41,6 +41,7 @@
#include "object_base.h"
#include "water.h"
#include "company_gui.h"
+#include "station_func.h"
#include "table/strings.h"
#include "table/bridge_land.h"
@@ -533,6 +534,8 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u
if (is_new_owner && c != nullptr) c->infrastructure.water += (bridge_len + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR;
MakeAqueductBridgeRamp(tile_start, owner, dir);
MakeAqueductBridgeRamp(tile_end, owner, ReverseDiagDir(dir));
+ CheckForDockingTile(tile_start);
+ CheckForDockingTile(tile_end);
break;
default:
@@ -944,6 +947,9 @@ static CommandCost DoClearBridge(TileIndex tile, DoCommandFlag flags)
if (v != nullptr) FreeTrainTrackReservation(v);
}
+ bool removetile = false;
+ bool removeendtile = false;
+
/* Update company infrastructure counts. */
if (rail) {
if (Company::IsValidID(owner)) Company::Get(owner)->infrastructure.rail[GetRailType(tile)] -= len * TUNNELBRIDGE_TRACKBIT_FACTOR;
@@ -953,11 +959,16 @@ static CommandCost DoClearBridge(TileIndex tile, DoCommandFlag flags)
UpdateCompanyRoadInfrastructure(GetRoadTypeTram(tile), GetRoadOwner(tile, RTT_TRAM), -(int)(len * 2 * TUNNELBRIDGE_TRACKBIT_FACTOR));
} else { // Aqueduct
if (Company::IsValidID(owner)) Company::Get(owner)->infrastructure.water -= len * TUNNELBRIDGE_TRACKBIT_FACTOR;
+ removetile = IsDockingTile(tile);
+ removeendtile = IsDockingTile(endtile);
}
DirtyCompanyInfrastructureWindows(owner);
DoClearSquare(tile);
DoClearSquare(endtile);
+
+ if (removetile) RemoveDockingTile(tile);
+ if (removeendtile) RemoveDockingTile(endtile);
for (TileIndex c = tile + delta; c != endtile; c += delta) {
/* do not let trees appear from 'nowhere' after removing bridge */
if (IsNormalRoadTile(c) && GetRoadside(c) == ROADSIDE_TREES) {
diff --git a/src/water.h b/src/water.h
index 1b804720f..9e2c23418 100644
--- a/src/water.h
+++ b/src/water.h
@@ -38,6 +38,7 @@ void DrawWaterClassGround(const struct TileInfo *ti);
void DrawShoreTile(Slope tileh);
void MakeWaterKeepingClass(TileIndex tile, Owner o);
+void CheckForDockingTile(TileIndex t);
bool RiverModifyDesertZone(TileIndex tile, void *data);
static const uint RIVER_OFFSET_DESERT_DISTANCE = 5; ///< Circular tile search radius to create non-desert around a river tile.
diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp
index b603d7411..158bd13f0 100644
--- a/src/water_cmd.cpp
+++ b/src/water_cmd.cpp
@@ -39,6 +39,7 @@
#include "company_base.h"
#include "company_gui.h"
#include "newgrf_generic.h"
+#include "industry.h"
#include "table/strings.h"
@@ -148,6 +149,8 @@ CommandCost CmdBuildShipDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
MakeShipDepot(tile, _current_company, depot->index, DEPOT_PART_NORTH, axis, wc1);
MakeShipDepot(tile2, _current_company, depot->index, DEPOT_PART_SOUTH, axis, wc2);
+ CheckForDockingTile(tile);
+ CheckForDockingTile(tile2);
MarkTileDirtyByTile(tile);
MarkTileDirtyByTile(tile2);
MakeDefaultName(depot);
@@ -156,6 +159,48 @@ CommandCost CmdBuildShipDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
return cost;
}
+bool IsPossibleDockingTile(TileIndex t)
+{
+ assert(IsValidTile(t));
+ switch (GetTileType(t)) {
+ case MP_WATER:
+ if (IsLock(t) && GetLockPart(t) == LOCK_PART_MIDDLE) return false;
+ FALLTHROUGH;
+ case MP_RAILWAY:
+ case MP_STATION:
+ case MP_TUNNELBRIDGE:
+ return TrackStatusToTrackBits(GetTileTrackStatus(t, TRANSPORT_WATER, 0)) != TRACK_BIT_NONE;
+
+ default:
+ return false;
+ }
+}
+
+/**
+ * Mark the supplied tile as a docking tile if it is suitable for docking.
+ * Tiles surrounding the tile are tested to be docks with correct orientation.
+ * @param t Tile to test.
+ */
+void CheckForDockingTile(TileIndex t)
+{
+ for (DiagDirection d = DIAGDIR_BEGIN; d != DIAGDIR_END; d++) {
+ TileIndex tile = t + TileOffsByDiagDir(d);
+ if (!IsValidTile(tile)) continue;
+
+ if (IsDockTile(tile)) {
+ Station::GetByTile(tile)->docking_station.Add(t);
+ SetDockingTile(t, true);
+ }
+ if (IsTileType(tile, MP_INDUSTRY)) {
+ Station *st = Industry::GetByTile(tile)->neutral_station;
+ if (st != nullptr) {
+ st->docking_station.Add(t);
+ SetDockingTile(t, true);
+ }
+ }
+ }
+}
+
void MakeWaterKeepingClass(TileIndex tile, Owner o)
{
WaterClass wc = GetWaterClass(tile);
@@ -204,6 +249,7 @@ void MakeWaterKeepingClass(TileIndex tile, Owner o)
default: break;
}
+ if (wc != WATER_CLASS_INVALID) CheckForDockingTile(tile);
MarkTileDirtyByTile(tile);
}
@@ -303,6 +349,8 @@ static CommandCost DoBuildLock(TileIndex tile, DiagDirection dir, DoCommandFlag
}
MakeLock(tile, _current_company, dir, wc_lower, wc_upper, wc_middle);
+ CheckForDockingTile(tile - delta);
+ CheckForDockingTile(tile + delta);
MarkTileDirtyByTile(tile);
MarkTileDirtyByTile(tile - delta);
MarkTileDirtyByTile(tile + delta);
@@ -449,6 +497,7 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
}
MarkTileDirtyByTile(tile);
MarkCanalsAndRiversAroundDirty(tile);
+ CheckForDockingTile(tile);
}
cost.AddCost(_price[PR_BUILD_CANAL]);
@@ -489,8 +538,10 @@ static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags)
Company::Get(owner)->infrastructure.water--;
DirtyCompanyInfrastructureWindows(owner);
}
+ bool remove = IsDockingTile(tile);
DoClearSquare(tile);
MarkCanalsAndRiversAroundDirty(tile);
+ if (remove) RemoveDockingTile(tile);
}
return CommandCost(EXPENSES_CONSTRUCTION, base_cost);
@@ -504,8 +555,10 @@ static CommandCost ClearTile_Water(TileIndex tile, DoCommandFlag flags)
if (ret.Failed()) return ret;
if (flags & DC_EXEC) {
+ bool remove = IsDockingTile(tile);
DoClearSquare(tile);
MarkCanalsAndRiversAroundDirty(tile);
+ if (remove) RemoveDockingTile(tile);
}
if (IsSlopeWithOneCornerRaised(slope)) {
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WATER]);
@@ -1095,6 +1148,8 @@ void DoFloodTile(TileIndex target)
/* update signals if needed */
UpdateSignalsInBuffer();
+
+ if (IsPossibleDockingTile(target)) CheckForDockingTile(target);
}
cur_company.Restore();
diff --git a/src/water_map.h b/src/water_map.h
index 5d84d5ba6..375c80e6c 100644
--- a/src/water_map.h
+++ b/src/water_map.h
@@ -69,6 +69,8 @@ enum LockPart {
LOCK_PART_UPPER = 2, ///< Upper part of a lock.
};
+bool IsPossibleDockingTile(TileIndex t);
+
/**
* Get the water tile type at a tile.
* @param t Water tile to query.
@@ -346,6 +348,27 @@ static inline bool HasTileWaterGround(TileIndex t)
return HasTileWaterClass(t) && IsTileOnWater(t) && !IsCoastTile(t);
}
+/**
+ * Set the docking tile state of a tile. This is used by pathfinders to reach their destination.
+ * As well as water tiles, half-rail tiles, buoys and aqueduct ends can also be docking tiles.
+ * @param t the tile
+ * @param b the docking tile state
+ */
+static inline void SetDockingTile(TileIndex t, bool b)
+{
+ assert(IsTileType(t, MP_WATER) || IsTileType(t, MP_RAILWAY) || IsTileType(t, MP_STATION) || IsTileType(t, MP_TUNNELBRIDGE));
+ SB(_m[t].m1, 7, 1, b ? 1 : 0);
+}
+
+/**
+ * Checks whether the tile is marked as a dockling tile.
+ * @return true iff the tile is marked as a docking tile.
+ */
+static inline bool IsDockingTile(TileIndex t)
+{
+ return (IsTileType(t, MP_WATER) || IsTileType(t, MP_RAILWAY) || IsTileType(t, MP_STATION) || IsTileType(t, MP_TUNNELBRIDGE)) && HasBit(_m[t].m1, 7);
+}
+
/**
* Helper function to make a coast tile.
@@ -356,6 +379,7 @@ static inline void MakeShore(TileIndex t)
SetTileType(t, MP_WATER);
SetTileOwner(t, OWNER_WATER);
SetWaterClass(t, WATER_CLASS_SEA);
+ SetDockingTile(t, false);
_m[t].m2 = 0;
_m[t].m3 = 0;
_m[t].m4 = 0;
@@ -376,6 +400,7 @@ static inline void MakeWater(TileIndex t, Owner o, WaterClass wc, uint8 random_b
SetTileType(t, MP_WATER);
SetTileOwner(t, o);
SetWaterClass(t, wc);
+ SetDockingTile(t, false);
_m[t].m2 = 0;
_m[t].m3 = 0;
_m[t].m4 = random_bits;
@@ -429,6 +454,7 @@ static inline void MakeShipDepot(TileIndex t, Owner o, DepotID did, DepotPart pa
SetTileType(t, MP_WATER);
SetTileOwner(t, o);
SetWaterClass(t, original_water_class);
+ SetDockingTile(t, false);
_m[t].m2 = did;
_m[t].m3 = 0;
_m[t].m4 = 0;
@@ -451,6 +477,7 @@ static inline void MakeLockTile(TileIndex t, Owner o, LockPart part, DiagDirecti
SetTileType(t, MP_WATER);
SetTileOwner(t, o);
SetWaterClass(t, original_water_class);
+ SetDockingTile(t, false);
_m[t].m2 = 0;
_m[t].m3 = 0;
_m[t].m4 = 0;
diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp
index 76326a3a4..20e600b5e 100644
--- a/src/waypoint_cmd.cpp
+++ b/src/waypoint_cmd.cpp
@@ -332,6 +332,7 @@ CommandCost CmdBuildBuoy(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
if (wp->town == nullptr) MakeDefaultName(wp);
MakeBuoy(tile, wp->index, GetWaterClass(tile));
+ CheckForDockingTile(tile);
MarkTileDirtyByTile(tile);
wp->UpdateVirtCoord();