2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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 <http://www.gnu.org/licenses/>.
8 /** @file roadstop.cpp Implementation of the roadstop base class. */
12 #include "core/pool_func.hpp"
13 #include "roadstop_base.h"
14 #include "station_base.h"
15 #include "vehicle_func.h"
17 #include "safeguards.h"
19 /** The pool of roadstops. */
20 RoadStopPool
_roadstop_pool("RoadStop");
21 INSTANTIATE_POOL_METHODS(RoadStop
)
24 * De-Initializes RoadStops.
28 /* When we are the head we need to free the entries */
29 if (HasBit(this->status
, RSSFB_BASE_ENTRY
)) {
34 if (CleaningPool()) return;
38 * Get the next road stop accessible by this vehicle.
39 * @param v the vehicle to get the next road stop for.
40 * @return the next road stop accessible.
42 RoadStop
*RoadStop::GetNextRoadStop(const RoadVehicle
*v
) const
44 for (RoadStop
*rs
= this->next
; rs
!= nullptr; rs
= rs
->next
) {
45 /* The vehicle cannot go to this roadstop (different roadtype) */
46 if (!HasTileAnyRoadType(rs
->xy
, v
->compatible_roadtypes
)) continue;
47 /* The vehicle is articulated and can therefore not go to a standard road stop. */
48 if (IsBayRoadStopTile(rs
->xy
) && v
->HasArticulatedPart()) continue;
50 /* The vehicle can actually go to this road stop. So, return it! */
58 * Join this road stop to another 'base' road stop if possible;
59 * fill all necessary data to become an actual drive through road stop.
60 * Also update the length etc.
62 void RoadStop::MakeDriveThrough()
64 assert(this->east
== nullptr && this->west
== nullptr);
66 RoadStopType rst
= GetRoadStopType(this->xy
);
67 Axis axis
= GetDriveThroughStopAxis(this->xy
);
68 TileIndexDiff offset
= TileOffsByAxis(axis
);
70 /* Information about the tile north of us */
71 TileIndex north_tile
= this->xy
- offset
;
72 bool north
= IsDriveThroughRoadStopContinuation(this->xy
, north_tile
);
73 RoadStop
*rs_north
= north
? RoadStop::GetByTile(north_tile
, rst
) : nullptr;
75 /* Information about the tile south of us */
76 TileIndex south_tile
= this->xy
+ offset
;
77 bool south
= IsDriveThroughRoadStopContinuation(this->xy
, south_tile
);
78 RoadStop
*rs_south
= south
? RoadStop::GetByTile(south_tile
, rst
) : nullptr;
80 /* Amount of road stops that will be added to the 'northern' head */
82 if (north
&& rs_north
->east
!= nullptr) { // (east != nullptr) == (west != nullptr)
83 /* There is a more northern one, so this can join them */
84 this->east
= rs_north
->east
;
85 this->west
= rs_north
->west
;
87 if (south
&& rs_south
->east
!= nullptr) { // (east != nullptr) == (west != nullptr)
88 /* There more southern tiles too, they must 'join' us too */
89 ClrBit(rs_south
->status
, RSSFB_BASE_ENTRY
);
90 this->east
->occupied
+= rs_south
->east
->occupied
;
91 this->west
->occupied
+= rs_south
->west
->occupied
;
93 /* Free the now unneeded entry structs */
94 delete rs_south
->east
;
95 delete rs_south
->west
;
97 /* Make all 'children' of the southern tile take the new master */
98 for (; IsDriveThroughRoadStopContinuation(this->xy
, south_tile
); south_tile
+= offset
) {
99 rs_south
= RoadStop::GetByTile(south_tile
, rst
);
100 if (rs_south
->east
== nullptr) break;
101 rs_south
->east
= rs_north
->east
;
102 rs_south
->west
= rs_north
->west
;
106 } else if (south
&& rs_south
->east
!= nullptr) { // (east != nullptr) == (west != nullptr)
107 /* There is one to the south, but not to the north... so we become 'parent' */
108 this->east
= rs_south
->east
;
109 this->west
= rs_south
->west
;
110 SetBit(this->status
, RSSFB_BASE_ENTRY
);
111 ClrBit(rs_south
->status
, RSSFB_BASE_ENTRY
);
113 /* We are the only... so we are automatically the master */
114 this->east
= new Entry();
115 this->west
= new Entry();
116 SetBit(this->status
, RSSFB_BASE_ENTRY
);
119 /* Now update the lengths */
121 this->east
->length
+= added
;
122 this->west
->length
+= added
;
126 * Prepare for removal of this stop; update other neighbouring stops
127 * if needed. Also update the length etc.
129 void RoadStop::ClearDriveThrough()
131 assert(this->east
!= nullptr && this->west
!= nullptr);
133 RoadStopType rst
= GetRoadStopType(this->xy
);
134 Axis axis
= GetDriveThroughStopAxis(this->xy
);
135 TileIndexDiff offset
= TileOffsByAxis(axis
);
137 /* Information about the tile north of us */
138 TileIndex north_tile
= this->xy
- offset
;
139 bool north
= IsDriveThroughRoadStopContinuation(this->xy
, north_tile
);
140 RoadStop
*rs_north
= north
? RoadStop::GetByTile(north_tile
, rst
) : nullptr;
142 /* Information about the tile south of us */
143 TileIndex south_tile
= this->xy
+ offset
;
144 bool south
= IsDriveThroughRoadStopContinuation(this->xy
, south_tile
);
145 RoadStop
*rs_south
= south
? RoadStop::GetByTile(south_tile
, rst
) : nullptr;
147 /* Must only be cleared after we determined which neighbours are
148 * part of our little entry 'queue' */
149 DoClearSquare(this->xy
);
152 /* There is a tile to the north, so we can't clear ourselves. */
154 /* There are more southern tiles too, they must be split;
155 * first make the new southern 'base' */
156 SetBit(rs_south
->status
, RSSFB_BASE_ENTRY
);
157 rs_south
->east
= new Entry();
158 rs_south
->west
= new Entry();
160 /* Keep track of the base because we need it later on */
161 RoadStop
*rs_south_base
= rs_south
;
162 TileIndex base_tile
= south_tile
;
164 /* Make all (even more) southern stops part of the new entry queue */
165 for (south_tile
+= offset
; IsDriveThroughRoadStopContinuation(base_tile
, south_tile
); south_tile
+= offset
) {
166 rs_south
= RoadStop::GetByTile(south_tile
, rst
);
167 rs_south
->east
= rs_south_base
->east
;
168 rs_south
->west
= rs_south_base
->west
;
171 /* Find the other end; the northern most tile */
172 for (; IsDriveThroughRoadStopContinuation(base_tile
, north_tile
); north_tile
-= offset
) {
173 rs_north
= RoadStop::GetByTile(north_tile
, rst
);
176 /* We have to rebuild the entries because we cannot easily determine
177 * how full each part is. So instead of keeping and maintaining a list
178 * of vehicles and using that to 'rebuild' the occupied state we just
179 * rebuild it from scratch as that removes lots of maintenance code
180 * for the vehicle list and it's faster in real games as long as you
181 * do not keep split and merge road stop every tick by the millions. */
182 rs_south_base
->east
->Rebuild(rs_south_base
);
183 rs_south_base
->west
->Rebuild(rs_south_base
);
185 assert(HasBit(rs_north
->status
, RSSFB_BASE_ENTRY
));
186 rs_north
->east
->Rebuild(rs_north
);
187 rs_north
->west
->Rebuild(rs_north
);
189 /* Only we left, so simple update the length. */
190 rs_north
->east
->length
-= TILE_SIZE
;
191 rs_north
->west
->length
-= TILE_SIZE
;
194 /* There is only something to the south. Hand over the base entry */
195 SetBit(rs_south
->status
, RSSFB_BASE_ENTRY
);
196 rs_south
->east
->length
-= TILE_SIZE
;
197 rs_south
->west
->length
-= TILE_SIZE
;
199 /* We were the last */
204 /* Make sure we don't get used for something 'incorrect' */
205 ClrBit(this->status
, RSSFB_BASE_ENTRY
);
206 this->east
= nullptr;
207 this->west
= nullptr;
211 * Leave the road stop
212 * @param rv the vehicle that leaves the stop
214 void RoadStop::Leave(RoadVehicle
*rv
)
216 if (IsBayRoadStopTile(rv
->tile
)) {
217 /* Vehicle is leaving a road stop tile, mark bay as free */
218 this->FreeBay(HasBit(rv
->state
, RVS_USING_SECOND_BAY
));
219 this->SetEntranceBusy(false);
221 /* Otherwise just leave the drive through's entry cache. */
222 this->GetEntry(DirToDiagDir(rv
->direction
))->Leave(rv
);
227 * Enter the road stop
228 * @param rv the vehicle that enters the stop
229 * @return whether the road stop could actually be entered
231 bool RoadStop::Enter(RoadVehicle
*rv
)
233 if (IsBayRoadStopTile(this->xy
)) {
234 /* For normal (non drive-through) road stops
235 * Check if station is busy or if there are no free bays or whether it is a articulated vehicle. */
236 if (this->IsEntranceBusy() || !this->HasFreeBay() || rv
->HasArticulatedPart()) return false;
238 SetBit(rv
->state
, RVS_IN_ROAD_STOP
);
240 /* Allocate a bay and update the road state */
241 uint bay_nr
= this->AllocateBay();
242 SB(rv
->state
, RVS_USING_SECOND_BAY
, 1, bay_nr
);
244 /* Mark the station entrance as busy */
245 this->SetEntranceBusy(true);
249 /* Vehicles entering a drive-through stop from the 'normal' side use first bay (bay 0). */
250 this->GetEntry(DirToDiagDir(rv
->direction
))->Enter(rv
);
252 /* Indicate a drive-through stop */
253 SetBit(rv
->state
, RVS_IN_DT_ROAD_STOP
);
258 * Find a roadstop at given tile
259 * @param tile tile with roadstop
260 * @param type roadstop type
261 * @return pointer to RoadStop
262 * @pre there has to be roadstop of given type there!
264 /* static */ RoadStop
*RoadStop::GetByTile(TileIndex tile
, RoadStopType type
)
266 const Station
*st
= Station::GetByTile(tile
);
268 for (RoadStop
*rs
= st
->GetPrimaryRoadStop(type
);; rs
= rs
->next
) {
269 if (rs
->xy
== tile
) return rs
;
270 assert(rs
->next
!= nullptr);
275 * Leave the road stop
276 * @param rv the vehicle that leaves the stop
278 void RoadStop::Entry::Leave(const RoadVehicle
*rv
)
280 this->occupied
-= rv
->gcache
.cached_total_length
;
281 assert(this->occupied
>= 0);
285 * Enter the road stop
286 * @param rv the vehicle that enters the stop
288 void RoadStop::Entry::Enter(const RoadVehicle
*rv
)
290 /* we cannot assert on this->occupied < this->length because of the
291 * remote possibility that RVs are running through each other when
292 * trying to prevention an infinite jam. */
293 this->occupied
+= rv
->gcache
.cached_total_length
;
297 * Checks whether the 'next' tile is still part of the road same drive through
298 * stop 'rs' in the same direction for the same vehicle.
299 * @param rs the road stop tile to check against
300 * @param next the 'next' tile to check
301 * @return true if the 'next' tile is part of the road stop at 'next'.
303 /* static */ bool RoadStop::IsDriveThroughRoadStopContinuation(TileIndex rs
, TileIndex next
)
305 return IsTileType(next
, MP_STATION
) &&
306 GetStationIndex(next
) == GetStationIndex(rs
) &&
307 GetStationType(next
) == GetStationType(rs
) &&
308 IsDriveThroughStopTile(next
) &&
309 GetDriveThroughStopAxis(next
) == GetDriveThroughStopAxis(rs
);
312 typedef std::list
<const RoadVehicle
*> RVList
; ///< A list of road vehicles
314 /** Helper for finding RVs in a road stop. */
315 struct RoadStopEntryRebuilderHelper
{
316 RVList vehicles
; ///< The list of vehicles to possibly add to.
317 DiagDirection dir
; ///< The direction the vehicle has to face to be added.
321 * Add road vehicles to the station's list if needed.
322 * @param v the found vehicle
323 * @param data the extra data used to make our decision
324 * @return always nullptr
326 Vehicle
*FindVehiclesInRoadStop(Vehicle
*v
, void *data
)
328 RoadStopEntryRebuilderHelper
*rserh
= (RoadStopEntryRebuilderHelper
*)data
;
329 /* Not a RV or not in the right direction or crashed :( */
330 if (v
->type
!= VEH_ROAD
|| DirToDiagDir(v
->direction
) != rserh
->dir
|| !v
->IsPrimaryVehicle() || (v
->vehstatus
& VS_CRASHED
) != 0) return nullptr;
332 RoadVehicle
*rv
= RoadVehicle::From(v
);
333 /* Don't add ones not in a road stop */
334 if (rv
->state
< RVSB_IN_ROAD_STOP
) return nullptr;
336 /* Do not add duplicates! */
337 for (const auto &it
: rserh
->vehicles
) {
338 if (rv
== it
) return nullptr;
341 rserh
->vehicles
.push_back(rv
);
346 * Get the DiagDirection for entering the drive through stop from the given 'side' (east or west) on the given axis.
347 * @param east Enter from the east when true or from the west when false.
348 * @param axis The axis of the drive through stop.
349 * @return The DiagDirection the vehicles far when entering 'our' side of the drive through stop.
351 static DiagDirection
GetEntryDirection(bool east
, Axis axis
)
354 case AXIS_X
: return east
? DIAGDIR_NE
: DIAGDIR_SW
;
355 case AXIS_Y
: return east
? DIAGDIR_SE
: DIAGDIR_NW
;
356 default: NOT_REACHED();
361 * Rebuild, from scratch, the vehicles and other metadata on this stop.
362 * @param rs the roadstop this entry is part of
364 void RoadStop::Entry::Rebuild(const RoadStop
*rs
)
366 assert(HasBit(rs
->status
, RSSFB_BASE_ENTRY
));
368 Axis axis
= GetDriveThroughStopAxis(rs
->xy
);
370 RoadStopEntryRebuilderHelper rserh
;
371 rserh
.dir
= GetEntryDirection(rs
->east
== this, axis
);
374 TileIndexDiff offset
= TileOffsByAxis(axis
);
375 for (TileIndex tile
= rs
->xy
; IsDriveThroughRoadStopContinuation(rs
->xy
, tile
); tile
+= offset
) {
376 this->length
+= TILE_SIZE
;
377 FindVehicleOnPos(tile
, &rserh
, FindVehiclesInRoadStop
);
381 for (const auto &it
: rserh
.vehicles
) {
382 this->occupied
+= it
->gcache
.cached_total_length
;
388 * Check the integrity of the data in this struct.
389 * @param rs the roadstop this entry is part of
391 void RoadStop::Entry::CheckIntegrity(const RoadStop
*rs
) const
393 if (!HasBit(rs
->status
, RSSFB_BASE_ENTRY
)) return;
395 /* The tile 'before' the road stop must not be part of this 'line' */
396 assert(IsDriveThroughStopTile(rs
->xy
));
397 assert(!IsDriveThroughRoadStopContinuation(rs
->xy
, rs
->xy
- TileOffsByAxis(GetDriveThroughStopAxis(rs
->xy
))));
401 if (temp
.length
!= this->length
|| temp
.occupied
!= this->occupied
) NOT_REACHED();