4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file station.cpp Implementation of the station base class. */
16 #include "company_func.h"
17 #include "company_base.h"
19 #include "viewport_func.h"
20 #include "date_func.h"
21 #include "command_func.h"
22 #include "news_func.h"
24 #include "vehiclelist.h"
25 #include "core/pool_func.hpp"
26 #include "station_base.h"
27 #include "roadstop_base.h"
29 #include "core/random_func.hpp"
30 #include "linkgraph/linkgraph.h"
31 #include "linkgraph/linkgraphschedule.h"
34 #include "table/strings.h"
36 /** The pool of stations. */
37 template<> BaseStation::Pool
BaseStation::PoolItem::pool ("Station");
38 INSTANTIATE_POOL_METHODS(Station
)
40 BaseStation::~BaseStation()
45 if (CleaningPool()) return;
47 DeleteWindowById(WC_TRAINS_LIST
, VehicleListIdentifier(VL_STATION_LIST
, VEH_TRAIN
, this->owner
, this->index
).Pack());
48 DeleteWindowById(WC_ROADVEH_LIST
, VehicleListIdentifier(VL_STATION_LIST
, VEH_ROAD
, this->owner
, this->index
).Pack());
49 DeleteWindowById(WC_SHIPS_LIST
, VehicleListIdentifier(VL_STATION_LIST
, VEH_SHIP
, this->owner
, this->index
).Pack());
50 DeleteWindowById(WC_AIRCRAFT_LIST
, VehicleListIdentifier(VL_STATION_LIST
, VEH_AIRCRAFT
, this->owner
, this->index
).Pack());
52 this->sign
.MarkDirty();
55 Station::Station(TileIndex tile
) :
56 SpecializedStation
<Station
, false>(tile
),
57 bus_station(INVALID_TILE
, 0, 0),
58 truck_station(INVALID_TILE
, 0, 0),
59 dock_area(INVALID_TILE
, 0, 0),
62 time_since_unload(255),
63 last_vehicle_type(VEH_INVALID
)
65 /* this->random_bits is set in Station::AddFacility() */
69 * Clean up a station by clearing vehicle orders, invalidating windows and
70 * removing link stats.
71 * Aircraft-Hangar orders need special treatment here, as the hangars are
72 * actually part of a station (tiletype is STATION), but the order type
78 for (CargoID c
= 0; c
< NUM_CARGO
; c
++) {
79 this->goods
[c
].cargo
.OnCleanPool();
84 while (!this->loading_vehicles
.empty()) {
85 this->loading_vehicles
.front()->LeaveStation();
90 if (!a
->IsNormalAircraft()) continue;
91 if (a
->targetairport
== this->index
) a
->targetairport
= INVALID_STATION
;
94 for (CargoID c
= 0; c
< NUM_CARGO
; ++c
) {
95 LinkGraph
*lg
= LinkGraph::GetIfValid(this->goods
[c
].link_graph
);
96 if (lg
== NULL
) continue;
98 for (NodeID node
= 0; node
< lg
->Size(); ++node
) {
99 Station
*st
= Station::Get((*lg
)[node
]->Station());
100 st
->goods
[c
].flows
.erase(this->index
);
101 if ((*lg
)[node
][this->goods
[c
].node
].LastUpdate() != INVALID_DATE
) {
102 st
->goods
[c
].flows
.DeleteFlows(this->index
);
103 RerouteCargo (st
, c
, this->index
);
106 lg
->RemoveNode(this->goods
[c
].node
);
107 if (lg
->Size() == 0) {
108 LinkGraphSchedule::instance
.Unqueue(lg
);
114 FOR_ALL_VEHICLES(v
) {
115 /* Forget about this station if this station is removed */
116 if (v
->last_station_visited
== this->index
) {
117 v
->last_station_visited
= INVALID_STATION
;
119 if (v
->last_loading_station
== this->index
) {
120 v
->last_loading_station
= INVALID_STATION
;
124 /* Clear the persistent storage. */
125 delete this->airport
.psa
;
127 if (this->owner
== OWNER_NONE
) {
128 /* Invalidate all in case of oil rigs. */
129 InvalidateWindowClassesData(WC_STATION_LIST
, 0);
131 InvalidateWindowData(WC_STATION_LIST
, this->owner
, 0);
134 DeleteWindowById(WC_STATION_VIEW
, index
);
136 /* Now delete all orders that go to the station */
137 RemoveOrderFromAllVehicles(OT_GOTO_STATION
, this->index
);
139 /* Remove all news items */
140 DeleteStationNews(this->index
);
142 for (CargoID c
= 0; c
< NUM_CARGO
; c
++) {
143 this->goods
[c
].cargo
.Truncate();
146 CargoPacket::InvalidateAllFrom(this->index
);
151 * Invalidating of the JoinStation window has to be done
152 * after removing item from the pool.
153 * @param index index of deleted item
155 void BaseStation::PostDestructor(size_t index
)
157 InvalidateWindowData(WC_SELECT_STATION
, 0, 0);
161 * Get the primary road stop (the first road stop) that the given vehicle can load/unload.
162 * @param v the vehicle to get the first road stop for
163 * @return the first roadstop that this vehicle can load at
165 RoadStop
*Station::GetPrimaryRoadStop(const RoadVehicle
*v
) const
167 RoadStop
*rs
= this->GetPrimaryRoadStop(v
->IsBus() ? ROADSTOP_BUS
: ROADSTOP_TRUCK
);
169 for (; rs
!= NULL
; rs
= rs
->next
) {
170 /* The vehicle cannot go to this roadstop (different roadtype) */
171 if ((GetRoadTypes(rs
->xy
) & v
->compatible_roadtypes
) == ROADTYPES_NONE
) continue;
172 /* The vehicle is articulated and can therefore not go to a standard road stop. */
173 if (IsStandardRoadStopTile(rs
->xy
) && v
->HasArticulatedPart()) continue;
175 /* The vehicle can actually go to this road stop. So, return it! */
183 * Called when new facility is built on the station. If it is the first facility
184 * it initializes also 'xy' and 'random_bits' members
186 void Station::AddFacility(StationFacility new_facility_bit
, TileIndex facil_xy
)
188 if (this->facilities
== FACIL_NONE
) {
190 this->random_bits
= Random();
192 this->facilities
|= new_facility_bit
;
193 this->owner
= _current_company
;
194 this->build_date
= _date
;
198 * Marks the tiles of the station as dirty.
202 void Station::MarkTilesDirty(bool cargo_change
) const
204 TileIndex tile
= this->train_station
.tile
;
207 if (tile
== INVALID_TILE
) return;
209 /* cargo_change is set if we're refreshing the tiles due to cargo moving
212 /* Don't waste time updating if there are no custom station graphics
213 * that might change. Even if there are custom graphics, they might
214 * not change. Unfortunately we have no way of telling. */
215 if (this->num_specs
== 0) return;
218 for (h
= 0; h
< train_station
.h
; h
++) {
219 for (w
= 0; w
< train_station
.w
; w
++) {
220 if (this->TileBelongsToRailStation(tile
)) {
221 MarkTileDirtyByTile(tile
);
223 tile
+= TileDiffXY(1, 0);
225 tile
+= TileDiffXY(-w
, 1);
230 * Determines the REMAINING length of a platform, starting at (and including)
232 * @param tile the tile from which to start searching. Must be a rail station tile
233 * @param dir The direction in which to search.
234 * @return The platform length
236 uint
Station::GetPlatformLength (TileIndex tile
, DiagDirection dir
)
238 TileIndex start_tile
= tile
;
240 assert(IsRailStationTile(tile
));
241 assert(dir
< DIAGDIR_END
);
242 TileIndexDiff delta
= TileOffsByDiagDir (dir
);
247 } while (IsCompatibleTrainStationTile(tile
, start_tile
));
253 * Determines the catchment radius of the station
256 uint
Station::GetCatchmentRadius() const
260 if (_settings_game
.station
.modified_catchment
) {
261 if (this->bus_stops
!= NULL
) ret
= max
<uint
>(ret
, CA_BUS
);
262 if (this->truck_stops
!= NULL
) ret
= max
<uint
>(ret
, CA_TRUCK
);
263 if (this->train_station
.tile
!= INVALID_TILE
) ret
= max
<uint
>(ret
, CA_TRAIN
);
264 if (this->docks
!= NULL
) ret
= max
<uint
>(ret
, CA_DOCK
);
265 if (this->airport
.tile
!= INVALID_TILE
) ret
= max
<uint
>(ret
, this->airport
.GetSpec()->catchment
);
267 if (this->bus_stops
!= NULL
|| this->truck_stops
!= NULL
|| this->train_station
.tile
!= INVALID_TILE
|| this->docks
!= NULL
|| this->airport
.tile
!= INVALID_TILE
) {
276 * Determines the catchment area of this station
277 * @return clamped catchment rectangle
279 TileArea
Station::GetCatchmentArea() const
281 assert(!this->rect
.empty());
283 TileArea
catchment (this->rect
);
284 catchment
.expand (this->GetCatchmentRadius());
289 * Recomputes Station::industries_near, list of industries possibly
290 * accepting cargo in station's catchment radius
292 void Station::RecomputeIndustriesNear()
294 this->industries_near
.Clear();
295 if (this->rect
.empty()) return;
297 TileArea catchment
= this->GetCatchmentArea();
299 /* Compute maximum extent of acceptance rectangle wrt. station sign */
300 CircularTileIterator
iter (this->xy
, 2 * catchment
.get_radius_max (this->xy
) + 1);
301 for (TileIndex tile
= iter
; tile
!= INVALID_TILE
; tile
= ++iter
) {
302 /* Only process industry tiles within the catchment area */
303 if (!IsIndustryTile(tile
)) continue;
304 if (!catchment
.Contains(tile
)) continue;
306 Industry
*ind
= Industry::GetByTile (tile
);
308 /* Don't check further if this industry is already in the list */
309 if (this->industries_near
.Contains(ind
)) continue;
311 /* Include only industries that can accept cargo */
312 for (uint cid
= 0; cid
< lengthof(ind
->accepts_cargo
); cid
++) {
313 if (ind
->accepts_cargo
[cid
] != CT_INVALID
) {
314 *this->industries_near
.Append() = ind
;
322 * Recomputes Station::industries_near for all stations
324 /* static */ void Station::RecomputeIndustriesNearForAll()
327 FOR_ALL_STATIONS(st
) st
->RecomputeIndustriesNear();
330 /** Test if adding an area would exceed the maximum station spread. */
331 bool BaseStation::TestAddRect (const TileArea
&ta
)
333 assert (ta
.w
<= _settings_game
.station
.station_spread
);
334 assert (ta
.h
<= _settings_game
.station
.station_spread
);
336 if (this->rect
.empty()) return true;
338 TileArea
new_rect (this->rect
);
341 /* special case when new area is already contained in old area */
342 if (new_rect
.tile
== this->rect
.tile
&& new_rect
.w
== this->rect
.w
&& new_rect
.h
== this->rect
.h
) {
346 /* check new rect dimensions against preset max */
347 return new_rect
.w
<= _settings_game
.station
.station_spread
&&
348 new_rect
.h
<= _settings_game
.station
.station_spread
;
351 /** Update station area after removing a rectangle. */
352 void BaseStation::AfterRemoveRect (const TileArea
&ta
)
354 this->rect
.shrink_span (std::bind1st (std::mem_fun (&BaseStation::TileBelongsToStation
), this), ta
);
358 /** The pool of docks. */
359 template<> Dock::Pool
Dock::PoolItem::pool ("Dock");
360 INSTANTIATE_POOL_METHODS(Dock
)
363 * Calculates the maintenance cost of all airports of a company.
364 * @param owner Company.
365 * @return Total cost.
367 Money
AirportMaintenanceCost(Owner owner
)
369 Money total_cost
= 0;
372 FOR_ALL_STATIONS(st
) {
373 if (st
->owner
== owner
&& (st
->facilities
& FACIL_AIRPORT
)) {
374 total_cost
+= _price
[PR_INFRASTRUCTURE_AIRPORT
] * st
->airport
.GetSpec()->maintenance_cost
;
377 /* 3 bits fraction for the maintenance cost factor. */
378 return total_cost
>> 3;