station.cpp

Go to the documentation of this file.
00001 /* $Id$ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * 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.
00006  * 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.
00007  * 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/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "company_func.h"
00014 #include "company_base.h"
00015 #include "roadveh.h"
00016 #include "viewport_func.h"
00017 #include "date_func.h"
00018 #include "command_func.h"
00019 #include "news_func.h"
00020 #include "aircraft.h"
00021 #include "vehiclelist.h"
00022 #include "core/pool_func.hpp"
00023 #include "station_base.h"
00024 #include "roadstop_base.h"
00025 #include "industry.h"
00026 #include "core/random_func.hpp"
00027 #include "linkgraph/linkgraph.h"
00028 #include "linkgraph/linkgraphschedule.h"
00029 
00030 #include "table/strings.h"
00031 
00033 StationPool _station_pool("Station");
00034 INSTANTIATE_POOL_METHODS(Station)
00035 
00036 BaseStation::~BaseStation()
00037 {
00038   free(this->name);
00039   free(this->speclist);
00040 
00041   if (CleaningPool()) return;
00042 
00043   Owner owner = this->owner;
00044   if (!Company::IsValidID(owner)) owner = _local_company;
00045   if (!Company::IsValidID(owner)) return; // Spectators
00046   DeleteWindowById(WC_TRAINS_LIST,   VehicleListIdentifier(VL_STATION_LIST, VEH_TRAIN,    owner, this->index).Pack());
00047   DeleteWindowById(WC_ROADVEH_LIST,  VehicleListIdentifier(VL_STATION_LIST, VEH_ROAD,     owner, this->index).Pack());
00048   DeleteWindowById(WC_SHIPS_LIST,    VehicleListIdentifier(VL_STATION_LIST, VEH_SHIP,     owner, this->index).Pack());
00049   DeleteWindowById(WC_AIRCRAFT_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_AIRCRAFT, owner, this->index).Pack());
00050 
00051   this->sign.MarkDirty();
00052 }
00053 
00054 Station::Station(TileIndex tile) :
00055   SpecializedStation<Station, false>(tile),
00056   bus_station(INVALID_TILE, 0, 0),
00057   truck_station(INVALID_TILE, 0, 0),
00058   dock_tile(INVALID_TILE),
00059   indtype(IT_INVALID),
00060   time_since_load(255),
00061   time_since_unload(255),
00062   last_vehicle_type(VEH_INVALID)
00063 {
00064   /* this->random_bits is set in Station::AddFacility() */
00065 }
00066 
00074 Station::~Station()
00075 {
00076   if (CleaningPool()) {
00077     for (CargoID c = 0; c < NUM_CARGO; c++) {
00078       this->goods[c].cargo.OnCleanPool();
00079     }
00080     return;
00081   }
00082 
00083   while (!this->loading_vehicles.empty()) {
00084     this->loading_vehicles.front()->LeaveStation();
00085   }
00086 
00087   Aircraft *a;
00088   FOR_ALL_AIRCRAFT(a) {
00089     if (!a->IsNormalAircraft()) continue;
00090     if (a->targetairport == this->index) a->targetairport = INVALID_STATION;
00091   }
00092 
00093   for (CargoID c = 0; c < NUM_CARGO; ++c) {
00094     LinkGraph *lg = LinkGraph::GetIfValid(this->goods[c].link_graph);
00095     if (lg != NULL) {
00096       lg->RemoveNode(this->goods[c].node);
00097       if (lg->Size() == 0) {
00098         LinkGraphSchedule::Instance()->Unqueue(lg);
00099         delete lg;
00100       }
00101     }
00102     Station *st;
00103     FOR_ALL_STATIONS(st) {
00104       GoodsEntry *ge = &st->goods[c];
00105       ge->flows.DeleteFlows(this->index);
00106       ge->cargo.Reroute(UINT_MAX, &ge->cargo, this->index, st->index, ge);
00107     }
00108   }
00109 
00110   Vehicle *v;
00111   FOR_ALL_VEHICLES(v) {
00112     /* Forget about this station if this station is removed */
00113     if (v->last_station_visited == this->index) {
00114       v->last_station_visited = INVALID_STATION;
00115     }
00116     if (v->last_loading_station == this->index) {
00117       v->last_loading_station = INVALID_STATION;
00118     }
00119   }
00120 
00121   /* Clear the persistent storage. */
00122   delete this->airport.psa;
00123 
00124   if (this->owner == OWNER_NONE) {
00125     /* Invalidate all in case of oil rigs. */
00126     InvalidateWindowClassesData(WC_STATION_LIST, 0);
00127   } else {
00128     InvalidateWindowData(WC_STATION_LIST, this->owner, 0);
00129   }
00130 
00131   DeleteWindowById(WC_STATION_VIEW, index);
00132 
00133   /* Now delete all orders that go to the station */
00134   RemoveOrderFromAllVehicles(OT_GOTO_STATION, this->index);
00135 
00136   /* Remove all news items */
00137   DeleteStationNews(this->index);
00138 
00139   for (CargoID c = 0; c < NUM_CARGO; c++) {
00140     this->goods[c].cargo.Truncate();
00141   }
00142 
00143   CargoPacket::InvalidateAllFrom(this->index);
00144 }
00145 
00146 
00152 void BaseStation::PostDestructor(size_t index)
00153 {
00154   InvalidateWindowData(WC_SELECT_STATION, 0, 0);
00155 }
00156 
00162 RoadStop *Station::GetPrimaryRoadStop(const RoadVehicle *v) const
00163 {
00164   RoadStop *rs = this->GetPrimaryRoadStop(v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK);
00165 
00166   for (; rs != NULL; rs = rs->next) {
00167     /* The vehicle cannot go to this roadstop (different roadtype) */
00168     if ((GetRoadTypes(rs->xy) & v->compatible_roadtypes) == ROADTYPES_NONE) continue;
00169     /* The vehicle is articulated and can therefore not go to a standard road stop. */
00170     if (IsStandardRoadStopTile(rs->xy) && v->HasArticulatedPart()) continue;
00171 
00172     /* The vehicle can actually go to this road stop. So, return it! */
00173     break;
00174   }
00175 
00176   return rs;
00177 }
00178 
00183 void Station::AddFacility(StationFacility new_facility_bit, TileIndex facil_xy)
00184 {
00185   if (this->facilities == FACIL_NONE) {
00186     this->xy = facil_xy;
00187     this->random_bits = Random();
00188   }
00189   this->facilities |= new_facility_bit;
00190   this->owner = _current_company;
00191   this->build_date = _date;
00192 }
00193 
00199 void Station::MarkTilesDirty(bool cargo_change) const
00200 {
00201   TileIndex tile = this->train_station.tile;
00202   int w, h;
00203 
00204   if (tile == INVALID_TILE) return;
00205 
00206   /* cargo_change is set if we're refreshing the tiles due to cargo moving
00207    * around. */
00208   if (cargo_change) {
00209     /* Don't waste time updating if there are no custom station graphics
00210      * that might change. Even if there are custom graphics, they might
00211      * not change. Unfortunately we have no way of telling. */
00212     if (this->num_specs == 0) return;
00213   }
00214 
00215   for (h = 0; h < train_station.h; h++) {
00216     for (w = 0; w < train_station.w; w++) {
00217       if (this->TileBelongsToRailStation(tile)) {
00218         MarkTileDirtyByTile(tile);
00219       }
00220       tile += TileDiffXY(1, 0);
00221     }
00222     tile += TileDiffXY(-w, 1);
00223   }
00224 }
00225 
00226 /* virtual */ uint Station::GetPlatformLength(TileIndex tile) const
00227 {
00228   assert(this->TileBelongsToRailStation(tile));
00229 
00230   TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
00231 
00232   TileIndex t = tile;
00233   uint len = 0;
00234   do {
00235     t -= delta;
00236     len++;
00237   } while (IsCompatibleTrainStationTile(t, tile));
00238 
00239   t = tile;
00240   do {
00241     t += delta;
00242     len++;
00243   } while (IsCompatibleTrainStationTile(t, tile));
00244 
00245   return len - 1;
00246 }
00247 
00248 /* virtual */ uint Station::GetPlatformLength(TileIndex tile, DiagDirection dir) const
00249 {
00250   TileIndex start_tile = tile;
00251   uint length = 0;
00252   assert(IsRailStationTile(tile));
00253   assert(dir < DIAGDIR_END);
00254 
00255   do {
00256     length++;
00257     tile += TileOffsByDiagDir(dir);
00258   } while (IsCompatibleTrainStationTile(tile, start_tile));
00259 
00260   return length;
00261 }
00262 
00267 uint Station::GetCatchmentRadius() const
00268 {
00269   uint ret = CA_NONE;
00270 
00271   if (_settings_game.station.modified_catchment) {
00272     if (this->bus_stops          != NULL)         ret = max<uint>(ret, CA_BUS);
00273     if (this->truck_stops        != NULL)         ret = max<uint>(ret, CA_TRUCK);
00274     if (this->train_station.tile != INVALID_TILE) ret = max<uint>(ret, CA_TRAIN);
00275     if (this->dock_tile          != INVALID_TILE) ret = max<uint>(ret, CA_DOCK);
00276     if (this->airport.tile       != INVALID_TILE) ret = max<uint>(ret, this->airport.GetSpec()->catchment);
00277   } else {
00278     if (this->bus_stops != NULL || this->truck_stops != NULL || this->train_station.tile != INVALID_TILE || this->dock_tile != INVALID_TILE || this->airport.tile != INVALID_TILE) {
00279       ret = CA_UNMODIFIED;
00280     }
00281   }
00282 
00283   return ret;
00284 }
00285 
00290 Rect Station::GetCatchmentRect() const
00291 {
00292   assert(!this->rect.IsEmpty());
00293 
00294   /* Compute acceptance rectangle */
00295   int catchment_radius = this->GetCatchmentRadius();
00296 
00297   Rect ret = {
00298     max<int>(this->rect.left   - catchment_radius, 0),
00299     max<int>(this->rect.top    - catchment_radius, 0),
00300     min<int>(this->rect.right  + catchment_radius, MapMaxX()),
00301     min<int>(this->rect.bottom + catchment_radius, MapMaxY())
00302   };
00303 
00304   return ret;
00305 }
00306 
00308 struct RectAndIndustryVector {
00309   Rect rect;                       
00310   IndustryVector *industries_near; 
00311 };
00312 
00321 static bool FindIndustryToDeliver(TileIndex ind_tile, void *user_data)
00322 {
00323   /* Only process industry tiles */
00324   if (!IsTileType(ind_tile, MP_INDUSTRY)) return false;
00325 
00326   RectAndIndustryVector *riv = (RectAndIndustryVector *)user_data;
00327   Industry *ind = Industry::GetByTile(ind_tile);
00328 
00329   /* Don't check further if this industry is already in the list */
00330   if (riv->industries_near->Contains(ind)) return false;
00331 
00332   /* Only process tiles in the station acceptance rectangle */
00333   int x = TileX(ind_tile);
00334   int y = TileY(ind_tile);
00335   if (x < riv->rect.left || x > riv->rect.right || y < riv->rect.top || y > riv->rect.bottom) return false;
00336 
00337   /* Include only industries that can accept cargo */
00338   uint cargo_index;
00339   for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
00340     if (ind->accepts_cargo[cargo_index] != CT_INVALID) break;
00341   }
00342   if (cargo_index >= lengthof(ind->accepts_cargo)) return false;
00343 
00344   *riv->industries_near->Append() = ind;
00345 
00346   return false;
00347 }
00348 
00353 void Station::RecomputeIndustriesNear()
00354 {
00355   this->industries_near.Clear();
00356   if (this->rect.IsEmpty()) return;
00357 
00358   RectAndIndustryVector riv = {
00359     this->GetCatchmentRect(),
00360     &this->industries_near
00361   };
00362 
00363   /* Compute maximum extent of acceptance rectangle wrt. station sign */
00364   TileIndex start_tile = this->xy;
00365   uint max_radius = max(
00366     max(DistanceManhattan(start_tile, TileXY(riv.rect.left,  riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.left,  riv.rect.bottom))),
00367     max(DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.bottom)))
00368   );
00369 
00370   CircularTileSearch(&start_tile, 2 * max_radius + 1, &FindIndustryToDeliver, &riv);
00371 }
00372 
00376 /* static */ void Station::RecomputeIndustriesNearForAll()
00377 {
00378   Station *st;
00379   FOR_ALL_STATIONS(st) st->RecomputeIndustriesNear();
00380 }
00381 
00382 /************************************************************************/
00383 /*                     StationRect implementation                       */
00384 /************************************************************************/
00385 
00386 StationRect::StationRect()
00387 {
00388   this->MakeEmpty();
00389 }
00390 
00391 void StationRect::MakeEmpty()
00392 {
00393   this->left = this->top = this->right = this->bottom = 0;
00394 }
00395 
00405 bool StationRect::PtInExtendedRect(int x, int y, int distance) const
00406 {
00407   return this->left - distance <= x && x <= this->right + distance &&
00408       this->top - distance <= y && y <= this->bottom + distance;
00409 }
00410 
00411 bool StationRect::IsEmpty() const
00412 {
00413   return this->left == 0 || this->left > this->right || this->top > this->bottom;
00414 }
00415 
00416 CommandCost StationRect::BeforeAddTile(TileIndex tile, StationRectMode mode)
00417 {
00418   int x = TileX(tile);
00419   int y = TileY(tile);
00420   if (this->IsEmpty()) {
00421     /* we are adding the first station tile */
00422     if (mode != ADD_TEST) {
00423       this->left = this->right = x;
00424       this->top = this->bottom = y;
00425     }
00426   } else if (!this->PtInExtendedRect(x, y)) {
00427     /* current rect is not empty and new point is outside this rect
00428      * make new spread-out rectangle */
00429     Rect new_rect = {min(x, this->left), min(y, this->top), max(x, this->right), max(y, this->bottom)};
00430 
00431     /* check new rect dimensions against preset max */
00432     int w = new_rect.right - new_rect.left + 1;
00433     int h = new_rect.bottom - new_rect.top + 1;
00434     if (mode != ADD_FORCE && (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread)) {
00435       assert(mode != ADD_TRY);
00436       return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
00437     }
00438 
00439     /* spread-out ok, return true */
00440     if (mode != ADD_TEST) {
00441       /* we should update the station rect */
00442       *this = new_rect;
00443     }
00444   } else {
00445     ; // new point is inside the rect, we don't need to do anything
00446   }
00447   return CommandCost();
00448 }
00449 
00450 CommandCost StationRect::BeforeAddRect(TileIndex tile, int w, int h, StationRectMode mode)
00451 {
00452   if (mode == ADD_FORCE || (w <= _settings_game.station.station_spread && h <= _settings_game.station.station_spread)) {
00453     /* Important when the old rect is completely inside the new rect, resp. the old one was empty. */
00454     CommandCost ret = this->BeforeAddTile(tile, mode);
00455     if (ret.Succeeded()) ret = this->BeforeAddTile(TILE_ADDXY(tile, w - 1, h - 1), mode);
00456     return ret;
00457   }
00458   return CommandCost();
00459 }
00460 
00470 /* static */ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a)
00471 {
00472   TileArea ta(TileXY(left_a, top_a), TileXY(right_a, bottom_a));
00473   TILE_AREA_LOOP(tile, ta) {
00474     if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true;
00475   }
00476 
00477   return false;
00478 }
00479 
00480 bool StationRect::AfterRemoveTile(BaseStation *st, TileIndex tile)
00481 {
00482   int x = TileX(tile);
00483   int y = TileY(tile);
00484 
00485   /* look if removed tile was on the bounding rect edge
00486    * and try to reduce the rect by this edge
00487    * do it until we have empty rect or nothing to do */
00488   for (;;) {
00489     /* check if removed tile is on rect edge */
00490     bool left_edge = (x == this->left);
00491     bool right_edge = (x == this->right);
00492     bool top_edge = (y == this->top);
00493     bool bottom_edge = (y == this->bottom);
00494 
00495     /* can we reduce the rect in either direction? */
00496     bool reduce_x = ((left_edge || right_edge) && !ScanForStationTiles(st->index, x, this->top, x, this->bottom));
00497     bool reduce_y = ((top_edge || bottom_edge) && !ScanForStationTiles(st->index, this->left, y, this->right, y));
00498     if (!(reduce_x || reduce_y)) break; // nothing to do (can't reduce)
00499 
00500     if (reduce_x) {
00501       /* reduce horizontally */
00502       if (left_edge) {
00503         /* move left edge right */
00504         this->left = x = x + 1;
00505       } else {
00506         /* move right edge left */
00507         this->right = x = x - 1;
00508       }
00509     }
00510     if (reduce_y) {
00511       /* reduce vertically */
00512       if (top_edge) {
00513         /* move top edge down */
00514         this->top = y = y + 1;
00515       } else {
00516         /* move bottom edge up */
00517         this->bottom = y = y - 1;
00518       }
00519     }
00520 
00521     if (left > right || top > bottom) {
00522       /* can't continue, if the remaining rectangle is empty */
00523       this->MakeEmpty();
00524       return true; // empty remaining rect
00525     }
00526   }
00527   return false; // non-empty remaining rect
00528 }
00529 
00530 bool StationRect::AfterRemoveRect(BaseStation *st, TileArea ta)
00531 {
00532   assert(this->PtInExtendedRect(TileX(ta.tile), TileY(ta.tile)));
00533   assert(this->PtInExtendedRect(TileX(ta.tile) + ta.w - 1, TileY(ta.tile) + ta.h - 1));
00534 
00535   bool empty = this->AfterRemoveTile(st, ta.tile);
00536   if (ta.w != 1 || ta.h != 1) empty = empty || this->AfterRemoveTile(st, TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1));
00537   return empty;
00538 }
00539 
00540 StationRect& StationRect::operator = (const Rect &src)
00541 {
00542   this->left = src.left;
00543   this->top = src.top;
00544   this->right = src.right;
00545   this->bottom = src.bottom;
00546   return *this;
00547 }
00548 
00554 Money AirportMaintenanceCost(Owner owner)
00555 {
00556   Money total_cost = 0;
00557 
00558   const Station *st;
00559   FOR_ALL_STATIONS(st) {
00560     if (st->owner == owner && (st->facilities & FACIL_AIRPORT)) {
00561       total_cost += _price[PR_INFRASTRUCTURE_AIRPORT] * st->airport.GetSpec()->maintenance_cost;
00562     }
00563   }
00564   /* 3 bits fraction for the maintenance cost factor. */
00565   return total_cost >> 3;
00566 }