Rearrange storage of reserved tracks for railway tiles
[openttd/fttd.git] / src / station.cpp
bloba874d904ef165024695c1f3c81864e4e48fad995
1 /* $Id$ */
3 /*
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/>.
8 */
10 /** @file station.cpp Implementation of the station base class. */
12 #include "stdafx.h"
13 #include "company_func.h"
14 #include "company_base.h"
15 #include "roadveh.h"
16 #include "viewport_func.h"
17 #include "date_func.h"
18 #include "command_func.h"
19 #include "news_func.h"
20 #include "aircraft.h"
21 #include "vehiclelist.h"
22 #include "core/pool_func.hpp"
23 #include "station_base.h"
24 #include "roadstop_base.h"
25 #include "industry.h"
26 #include "core/random_func.hpp"
27 #include "linkgraph/linkgraph.h"
28 #include "linkgraph/linkgraphschedule.h"
29 #include "map/road.h"
31 #include "table/strings.h"
33 /** The pool of stations. */
34 StationPool _station_pool("Station");
35 INSTANTIATE_POOL_METHODS(Station)
37 typedef StationIDStack::SmallStackPool StationIDStackPool;
38 template<> StationIDStackPool StationIDStack::_pool("StationIDStack");
39 INSTANTIATE_POOL_METHODS(StationIDStack)
41 BaseStation::~BaseStation()
43 free(this->name);
44 free(this->speclist);
46 if (CleaningPool()) return;
48 Owner owner = this->owner;
49 if (!Company::IsValidID(owner)) owner = _local_company;
50 if (!Company::IsValidID(owner)) return; // Spectators
51 DeleteWindowById(WC_TRAINS_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_TRAIN, owner, this->index).Pack());
52 DeleteWindowById(WC_ROADVEH_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_ROAD, owner, this->index).Pack());
53 DeleteWindowById(WC_SHIPS_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_SHIP, owner, this->index).Pack());
54 DeleteWindowById(WC_AIRCRAFT_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_AIRCRAFT, owner, this->index).Pack());
56 this->sign.MarkDirty();
59 Station::Station(TileIndex tile) :
60 SpecializedStation<Station, false>(tile),
61 bus_station(INVALID_TILE, 0, 0),
62 truck_station(INVALID_TILE, 0, 0),
63 dock_area(INVALID_TILE, 0, 0),
64 indtype(IT_INVALID),
65 time_since_load(255),
66 time_since_unload(255),
67 last_vehicle_type(VEH_INVALID)
69 /* this->random_bits is set in Station::AddFacility() */
72 /**
73 * Clean up a station by clearing vehicle orders, invalidating windows and
74 * removing link stats.
75 * Aircraft-Hangar orders need special treatment here, as the hangars are
76 * actually part of a station (tiletype is STATION), but the order type
77 * is OT_GOTO_DEPOT.
79 Station::~Station()
81 if (CleaningPool()) {
82 for (CargoID c = 0; c < NUM_CARGO; c++) {
83 this->goods[c].cargo.OnCleanPool();
85 return;
88 while (!this->loading_vehicles.empty()) {
89 this->loading_vehicles.front()->LeaveStation();
92 Aircraft *a;
93 FOR_ALL_AIRCRAFT(a) {
94 if (!a->IsNormalAircraft()) continue;
95 if (a->targetairport == this->index) a->targetairport = INVALID_STATION;
98 for (CargoID c = 0; c < NUM_CARGO; ++c) {
99 LinkGraph *lg = LinkGraph::GetIfValid(this->goods[c].link_graph);
100 if (lg == NULL) continue;
102 for (NodeID node = 0; node < lg->Size(); ++node) {
103 if ((*lg)[node][this->goods[c].node].LastUpdate() != INVALID_DATE) {
104 Station *st = Station::Get((*lg)[node].Station());
105 st->goods[c].flows.DeleteFlows(this->index);
106 RerouteCargo(st, c, this->index, st->index);
109 lg->RemoveNode(this->goods[c].node);
110 if (lg->Size() == 0) {
111 LinkGraphSchedule::Instance()->Unqueue(lg);
112 delete lg;
116 Vehicle *v;
117 FOR_ALL_VEHICLES(v) {
118 /* Forget about this station if this station is removed */
119 if (v->last_station_visited == this->index) {
120 v->last_station_visited = INVALID_STATION;
122 if (v->last_loading_station == this->index) {
123 v->last_loading_station = INVALID_STATION;
127 /* Clear the persistent storage. */
128 delete this->airport.psa;
130 if (this->owner == OWNER_NONE) {
131 /* Invalidate all in case of oil rigs. */
132 InvalidateWindowClassesData(WC_STATION_LIST, 0);
133 } else {
134 InvalidateWindowData(WC_STATION_LIST, this->owner, 0);
137 DeleteWindowById(WC_STATION_VIEW, index);
139 /* Now delete all orders that go to the station */
140 RemoveOrderFromAllVehicles(OT_GOTO_STATION, this->index);
142 /* Remove all news items */
143 DeleteStationNews(this->index);
145 for (CargoID c = 0; c < NUM_CARGO; c++) {
146 this->goods[c].cargo.Truncate();
149 CargoPacket::InvalidateAllFrom(this->index);
154 * Invalidating of the JoinStation window has to be done
155 * after removing item from the pool.
156 * @param index index of deleted item
158 void BaseStation::PostDestructor(size_t index)
160 InvalidateWindowData(WC_SELECT_STATION, 0, 0);
164 * Get the primary road stop (the first road stop) that the given vehicle can load/unload.
165 * @param v the vehicle to get the first road stop for
166 * @return the first roadstop that this vehicle can load at
168 RoadStop *Station::GetPrimaryRoadStop(const RoadVehicle *v) const
170 RoadStop *rs = this->GetPrimaryRoadStop(v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK);
172 for (; rs != NULL; rs = rs->next) {
173 /* The vehicle cannot go to this roadstop (different roadtype) */
174 if ((GetRoadTypes(rs->xy) & v->compatible_roadtypes) == ROADTYPES_NONE) continue;
175 /* The vehicle is articulated and can therefore not go to a standard road stop. */
176 if (IsStandardRoadStopTile(rs->xy) && v->HasArticulatedPart()) continue;
178 /* The vehicle can actually go to this road stop. So, return it! */
179 break;
182 return rs;
186 * Called when new facility is built on the station. If it is the first facility
187 * it initializes also 'xy' and 'random_bits' members
189 void Station::AddFacility(StationFacility new_facility_bit, TileIndex facil_xy)
191 if (this->facilities == FACIL_NONE) {
192 this->xy = facil_xy;
193 this->random_bits = Random();
195 this->facilities |= new_facility_bit;
196 this->owner = _current_company;
197 this->build_date = _date;
201 * Marks the tiles of the station as dirty.
203 * @ingroup dirty
205 void Station::MarkTilesDirty(bool cargo_change) const
207 TileIndex tile = this->train_station.tile;
208 int w, h;
210 if (tile == INVALID_TILE) return;
212 /* cargo_change is set if we're refreshing the tiles due to cargo moving
213 * around. */
214 if (cargo_change) {
215 /* Don't waste time updating if there are no custom station graphics
216 * that might change. Even if there are custom graphics, they might
217 * not change. Unfortunately we have no way of telling. */
218 if (this->num_specs == 0) return;
221 for (h = 0; h < train_station.h; h++) {
222 for (w = 0; w < train_station.w; w++) {
223 if (this->TileBelongsToRailStation(tile)) {
224 MarkTileDirtyByTile(tile);
226 tile += TileDiffXY(1, 0);
228 tile += TileDiffXY(-w, 1);
232 /* virtual */ uint Station::GetPlatformLength(TileIndex tile) const
234 assert(this->TileBelongsToRailStation(tile));
236 TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
238 TileIndex t = tile;
239 uint len = 0;
240 do {
241 t -= delta;
242 len++;
243 } while (IsCompatibleTrainStationTile(t, tile));
245 t = tile;
246 do {
247 t += delta;
248 len++;
249 } while (IsCompatibleTrainStationTile(t, tile));
251 return len - 1;
254 /* virtual */ uint Station::GetPlatformLength(TileIndex tile, DiagDirection dir) const
256 TileIndex start_tile = tile;
257 uint length = 0;
258 assert(IsRailStationTile(tile));
259 assert(dir < DIAGDIR_END);
261 do {
262 length++;
263 tile += TileOffsByDiagDir(dir);
264 } while (IsCompatibleTrainStationTile(tile, start_tile));
266 return length;
270 * Determines the catchment radius of the station
271 * @return The radius
273 uint Station::GetCatchmentRadius() const
275 uint ret = CA_NONE;
277 if (_settings_game.station.modified_catchment) {
278 if (this->bus_stops != NULL) ret = max<uint>(ret, CA_BUS);
279 if (this->truck_stops != NULL) ret = max<uint>(ret, CA_TRUCK);
280 if (this->train_station.tile != INVALID_TILE) ret = max<uint>(ret, CA_TRAIN);
281 if (this->docks != NULL) ret = max<uint>(ret, CA_DOCK);
282 if (this->airport.tile != INVALID_TILE) ret = max<uint>(ret, this->airport.GetSpec()->catchment);
283 } else {
284 if (this->bus_stops != NULL || this->truck_stops != NULL || this->train_station.tile != INVALID_TILE || this->docks != NULL || this->airport.tile != INVALID_TILE) {
285 ret = CA_UNMODIFIED;
289 return ret;
293 * Determines catchment rectangle of this station
294 * @return clamped catchment rectangle
296 Rect Station::GetCatchmentRect() const
298 assert(!this->rect.IsEmpty());
300 /* Compute acceptance rectangle */
301 int catchment_radius = this->GetCatchmentRadius();
303 Rect ret = {
304 max<int>(this->rect.left - catchment_radius, 0),
305 max<int>(this->rect.top - catchment_radius, 0),
306 min<int>(this->rect.right + catchment_radius, MapMaxX()),
307 min<int>(this->rect.bottom + catchment_radius, MapMaxY())
310 return ret;
313 /** Rect and pointer to IndustryVector */
314 struct RectAndIndustryVector {
315 Rect rect; ///< The rectangle to search the industries in.
316 IndustryVector *industries_near; ///< The nearby industries.
320 * Callback function for Station::RecomputeIndustriesNear()
321 * Tests whether tile is an industry and possibly adds
322 * the industry to station's industries_near list.
323 * @param ind_tile tile to check
324 * @param user_data pointer to RectAndIndustryVector
325 * @return always false, we want to search all tiles
327 static bool FindIndustryToDeliver(TileIndex ind_tile, void *user_data)
329 /* Only process industry tiles */
330 if (!IsIndustryTile(ind_tile)) return false;
332 RectAndIndustryVector *riv = (RectAndIndustryVector *)user_data;
333 Industry *ind = Industry::GetByTile(ind_tile);
335 /* Don't check further if this industry is already in the list */
336 if (riv->industries_near->Contains(ind)) return false;
338 /* Only process tiles in the station acceptance rectangle */
339 int x = TileX(ind_tile);
340 int y = TileY(ind_tile);
341 if (x < riv->rect.left || x > riv->rect.right || y < riv->rect.top || y > riv->rect.bottom) return false;
343 /* Include only industries that can accept cargo */
344 uint cargo_index;
345 for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
346 if (ind->accepts_cargo[cargo_index] != CT_INVALID) break;
348 if (cargo_index >= lengthof(ind->accepts_cargo)) return false;
350 *riv->industries_near->Append() = ind;
352 return false;
356 * Recomputes Station::industries_near, list of industries possibly
357 * accepting cargo in station's catchment radius
359 void Station::RecomputeIndustriesNear()
361 this->industries_near.Clear();
362 if (this->rect.IsEmpty()) return;
364 RectAndIndustryVector riv = {
365 this->GetCatchmentRect(),
366 &this->industries_near
369 /* Compute maximum extent of acceptance rectangle wrt. station sign */
370 TileIndex start_tile = this->xy;
371 uint max_radius = max(
372 max(DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.bottom))),
373 max(DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.bottom)))
376 CircularTileSearch(&start_tile, 2 * max_radius + 1, &FindIndustryToDeliver, &riv);
380 * Recomputes Station::industries_near for all stations
382 /* static */ void Station::RecomputeIndustriesNearForAll()
384 Station *st;
385 FOR_ALL_STATIONS(st) st->RecomputeIndustriesNear();
388 /************************************************************************/
389 /* StationRect implementation */
390 /************************************************************************/
392 StationRect::StationRect()
394 this->MakeEmpty();
397 void StationRect::MakeEmpty()
399 this->left = this->top = this->right = this->bottom = 0;
403 * Determines whether a given point (x, y) is within a certain distance of
404 * the station rectangle.
405 * @note x and y are in Tile coordinates
406 * @param x X coordinate
407 * @param y Y coordinate
408 * @param distance The maximum distance a point may have (L1 norm)
409 * @return true if the point is within distance tiles of the station rectangle
411 bool StationRect::PtInExtendedRect(int x, int y, int distance) const
413 return this->left - distance <= x && x <= this->right + distance &&
414 this->top - distance <= y && y <= this->bottom + distance;
417 bool StationRect::IsEmpty() const
419 return this->left == 0 || this->left > this->right || this->top > this->bottom;
422 CommandCost StationRect::BeforeAddTile(TileIndex tile, StationRectMode mode)
424 int x = TileX(tile);
425 int y = TileY(tile);
426 if (this->IsEmpty()) {
427 /* we are adding the first station tile */
428 if (mode != ADD_TEST) {
429 this->left = this->right = x;
430 this->top = this->bottom = y;
432 } else if (!this->PtInExtendedRect(x, y)) {
433 /* current rect is not empty and new point is outside this rect
434 * make new spread-out rectangle */
435 Rect new_rect = {min(x, this->left), min(y, this->top), max(x, this->right), max(y, this->bottom)};
437 /* check new rect dimensions against preset max */
438 int w = new_rect.right - new_rect.left + 1;
439 int h = new_rect.bottom - new_rect.top + 1;
440 if (mode != ADD_FORCE && (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread)) {
441 assert(mode != ADD_TRY);
442 return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
445 /* spread-out ok, return true */
446 if (mode != ADD_TEST) {
447 /* we should update the station rect */
448 *this = new_rect;
450 } else {
451 ; // new point is inside the rect, we don't need to do anything
453 return CommandCost();
456 CommandCost StationRect::BeforeAddRect(TileIndex tile, int w, int h, StationRectMode mode)
458 if (mode == ADD_FORCE || (w <= _settings_game.station.station_spread && h <= _settings_game.station.station_spread)) {
459 /* Important when the old rect is completely inside the new rect, resp. the old one was empty. */
460 CommandCost ret = this->BeforeAddTile(tile, mode);
461 if (ret.Succeeded()) ret = this->BeforeAddTile(TILE_ADDXY(tile, w - 1, h - 1), mode);
462 return ret;
464 return CommandCost();
468 * Check whether station tiles of the given station id exist in the given rectangle
469 * @param st_id Station ID to look for in the rectangle
470 * @param left_a Minimal tile X edge of the rectangle
471 * @param top_a Minimal tile Y edge of the rectangle
472 * @param right_a Maximal tile X edge of the rectangle (inclusive)
473 * @param bottom_a Maximal tile Y edge of the rectangle (inclusive)
474 * @return \c true if a station tile with the given \a st_id exists in the rectangle, \c false otherwise
476 /* static */ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a)
478 TileArea ta(TileXY(left_a, top_a), TileXY(right_a, bottom_a));
479 TILE_AREA_LOOP(tile, ta) {
480 if (IsStationTile(tile) && GetStationIndex(tile) == st_id) return true;
483 return false;
486 bool StationRect::AfterRemoveTile(BaseStation *st, TileIndex tile)
488 int x = TileX(tile);
489 int y = TileY(tile);
491 /* look if removed tile was on the bounding rect edge
492 * and try to reduce the rect by this edge
493 * do it until we have empty rect or nothing to do */
494 for (;;) {
495 /* check if removed tile is on rect edge */
496 bool left_edge = (x == this->left);
497 bool right_edge = (x == this->right);
498 bool top_edge = (y == this->top);
499 bool bottom_edge = (y == this->bottom);
501 /* can we reduce the rect in either direction? */
502 bool reduce_x = ((left_edge || right_edge) && !ScanForStationTiles(st->index, x, this->top, x, this->bottom));
503 bool reduce_y = ((top_edge || bottom_edge) && !ScanForStationTiles(st->index, this->left, y, this->right, y));
504 if (!(reduce_x || reduce_y)) break; // nothing to do (can't reduce)
506 if (reduce_x) {
507 /* reduce horizontally */
508 if (left_edge) {
509 /* move left edge right */
510 this->left = x = x + 1;
511 } else {
512 /* move right edge left */
513 this->right = x = x - 1;
516 if (reduce_y) {
517 /* reduce vertically */
518 if (top_edge) {
519 /* move top edge down */
520 this->top = y = y + 1;
521 } else {
522 /* move bottom edge up */
523 this->bottom = y = y - 1;
527 if (left > right || top > bottom) {
528 /* can't continue, if the remaining rectangle is empty */
529 this->MakeEmpty();
530 return true; // empty remaining rect
533 return false; // non-empty remaining rect
536 bool StationRect::AfterRemoveRect(BaseStation *st, TileArea ta)
538 assert(this->PtInExtendedRect(TileX(ta.tile), TileY(ta.tile)));
539 assert(this->PtInExtendedRect(TileX(ta.tile) + ta.w - 1, TileY(ta.tile) + ta.h - 1));
541 bool empty = this->AfterRemoveTile(st, ta.tile);
542 if (ta.w != 1 || ta.h != 1) empty = empty || this->AfterRemoveTile(st, TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1));
543 return empty;
546 StationRect& StationRect::operator = (const Rect &src)
548 this->left = src.left;
549 this->top = src.top;
550 this->right = src.right;
551 this->bottom = src.bottom;
552 return *this;
555 /** The pool of docks. */
556 DockPool _dock_pool("Dock");
557 INSTANTIATE_POOL_METHODS(Dock)
560 * Calculates the maintenance cost of all airports of a company.
561 * @param owner Company.
562 * @return Total cost.
564 Money AirportMaintenanceCost(Owner owner)
566 Money total_cost = 0;
568 const Station *st;
569 FOR_ALL_STATIONS(st) {
570 if (st->owner == owner && (st->facilities & FACIL_AIRPORT)) {
571 total_cost += _price[PR_INFRASTRUCTURE_AIRPORT] * st->airport.GetSpec()->maintenance_cost;
574 /* 3 bits fraction for the maintenance cost factor. */
575 return total_cost >> 3;