# station.cpp

Go to the documentation of this file.

    00001 /* $Id: station.cpp 19995 2010-06-19 16:35:37Z rubidium $ */
    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 "functions.h"
    00017 #include "window_func.h"
    00018 #include "date_func.h"
    00019 #include "command_func.h"
    00020 #include "news_func.h"
    00021 #include "aircraft.h"
    00022 #include "vehicle_gui.h"
    00023 #include "core/pool_func.hpp"
    00024 #include "station_base.h"
    00025 #include "roadstop_base.h"
    00026 #include "industry.h"
    00027 #include "core/random_func.hpp"
    00028
    00029 #include "table/strings.h"
    00030
    00031 StationPool _station_pool("Station");
    00032 INSTANTIATE_POOL_METHODS(Station)
    00033
    00034 BaseStation::~BaseStation()
    00035 {
    00036   free(this->name);
    00037   free(this->speclist);
    00038 }
    00039
    00040 Station::Station(TileIndex tile) :
    00041   SpecializedStation<Station, false>(tile),
    00042   bus_station(INVALID_TILE, 0, 0),
    00043   truck_station(INVALID_TILE, 0, 0),
    00044   airport_tile(INVALID_TILE),
    00045   dock_tile(INVALID_TILE),
    00046   indtype(IT_INVALID),
    00047   time_since_load(255),
    00048   time_since_unload(255),
    00049   last_vehicle_type(VEH_INVALID)
    00050 {
    00051   /* this->random_bits is set in Station::AddFacility() */
    00052 }
    00053
    00060 Station::~Station()
    00061 {
    00062   if (CleaningPool()) return;
    00063
    00064   while (!this->loading_vehicles.empty()) {
    00065     this->loading_vehicles.front()->LeaveStation();
    00066   }
    00067
    00068   Aircraft *a;
    00069   FOR_ALL_AIRCRAFT(a) {
    00070     if (!a->IsNormalAircraft()) continue;
    00071     if (a->targetairport == this->index) a->targetairport = INVALID_STATION;
    00072   }
    00073
    00074   Vehicle *v;
    00075   FOR_ALL_VEHICLES(v) {
    00076     /* Forget about this station if this station is removed */
    00077     if (v->last_station_visited == this->index) {
    00078       v->last_station_visited = INVALID_STATION;
    00079     }
    00080   }
    00081
    00082   this->sign.MarkDirty();
    00083   InvalidateWindowData(WC_STATION_LIST, this->owner, 0);
    00084
    00085   DeleteWindowById(WC_STATION_VIEW, index);
    00086
    00087   Owner owner = this->owner;
    00088   if (!Company::IsValidID(owner)) owner = _local_company;
    00089   WindowNumber wno = (this->index << 16) | VLW_STATION_LIST | owner;
    00090   DeleteWindowById(WC_TRAINS_LIST, wno | (VEH_TRAIN << 11));
    00091   DeleteWindowById(WC_ROADVEH_LIST, wno | (VEH_ROAD << 11));
    00092   DeleteWindowById(WC_SHIPS_LIST, wno | (VEH_SHIP << 11));
    00093   DeleteWindowById(WC_AIRCRAFT_LIST, wno | (VEH_AIRCRAFT << 11));
    00094
    00095   /* Now delete all orders that go to the station */
    00096   RemoveOrderFromAllVehicles(OT_GOTO_STATION, this->index);
    00097
    00098   /* Remove all news items */
    00099   DeleteStationNews(this->index);
    00100
    00101   for (CargoID c = 0; c < NUM_CARGO; c++) {
    00102     this->goods[c].cargo.Truncate(0);
    00103   }
    00104
    00105   CargoPacket::InvalidateAllFrom(this->index);
    00106 }
    00107
    00108
    00114 void BaseStation::PostDestructor(size_t index)
    00115 {
    00116   InvalidateWindowData(WC_SELECT_STATION, 0, 0);
    00117 }
    00118
    00124 RoadStop *Station::GetPrimaryRoadStop(const RoadVehicle *v) const
    00125 {
    00126   RoadStop *rs = this->GetPrimaryRoadStop(v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK);
    00127
    00128   for (; rs != NULL; rs = rs->next) {
    00129     /* The vehicle cannot go to this roadstop (different roadtype) */
    00130     if ((GetRoadTypes(rs->xy) & v->compatible_roadtypes) == ROADTYPES_NONE) continue;
    00131     /* The vehicle is articulated and can therefor not go the a standard road stop */
    00132     if (IsStandardRoadStopTile(rs->xy) && v->HasArticulatedPart()) continue;
    00133
    00134     /* The vehicle can actually go to this road stop. So, return it! */
    00135     break;
    00136   }
    00137
    00138   return rs;
    00139 }
    00140
    00143 void Station::AddFacility(StationFacility new_facility_bit, TileIndex facil_xy)
    00144 {
    00145   if (this->facilities == FACIL_NONE) {
    00146     this->xy = facil_xy;
    00147     this->random_bits = Random();
    00148   }
    00149   this->facilities |= new_facility_bit;
    00150   this->owner = _current_company;
    00151   this->build_date = _date;
    00152 }
    00153
    00154 void Station::MarkTilesDirty(bool cargo_change) const
    00155 {
    00156   TileIndex tile = this->train_station.tile;
    00157   int w, h;
    00158
    00159   if (tile == INVALID_TILE) return;
    00160
    00161   /* cargo_change is set if we're refreshing the tiles due to cargo moving
    00162    * around. */
    00163   if (cargo_change) {
    00164     /* Don't waste time updating if there are no custom station graphics
    00165      * that might change. Even if there are custom graphics, they might
    00166      * not change. Unfortunately we have no way of telling. */
    00167     if (this->num_specs == 0) return;
    00168   }
    00169
    00170   for (h = 0; h < train_station.h; h++) {
    00171     for (w = 0; w < train_station.w; w++) {
    00172       if (this->TileBelongsToRailStation(tile)) {
    00173         MarkTileDirtyByTile(tile);
    00174       }
    00175       tile += TileDiffXY(1, 0);
    00176     }
    00177     tile += TileDiffXY(-w, 1);
    00178   }
    00179 }
    00180
    00181 /* virtual */ uint Station::GetPlatformLength(TileIndex tile) const
    00182 {
    00183   assert(this->TileBelongsToRailStation(tile));
    00184
    00185   TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
    00186
    00187   TileIndex t = tile;
    00188   uint len = 0;
    00189   do {
    00190     t -= delta;
    00191     len++;
    00192   } while (IsCompatibleTrainStationTile(t, tile));
    00193
    00194   t = tile;
    00195   do {
    00196     t += delta;
    00197     len++;
    00198   } while (IsCompatibleTrainStationTile(t, tile));
    00199
    00200   return len - 1;
    00201 }
    00202
    00203 /* virtual */ uint Station::GetPlatformLength(TileIndex tile, DiagDirection dir) const
    00204 {
    00205   TileIndex start_tile = tile;
    00206   uint length = 0;
    00207   assert(IsRailStationTile(tile));
    00208   assert(dir < DIAGDIR_END);
    00209
    00210   do {
    00211     length++;
    00212     tile += TileOffsByDiagDir(dir);
    00213   } while (IsCompatibleTrainStationTile(tile, start_tile));
    00214
    00215   return length;
    00216 }
    00217
    00221 uint Station::GetCatchmentRadius() const
    00222 {
    00223   uint ret = CA_NONE;
    00224
    00225   if (_settings_game.station.modified_catchment) {
    00226     if (this->bus_stops          != NULL)         ret = max<uint>(ret, CA_BUS);
    00227     if (this->truck_stops        != NULL)         ret = max<uint>(ret, CA_TRUCK);
    00228     if (this->train_station.tile != INVALID_TILE) ret = max<uint>(ret, CA_TRAIN);
    00229     if (this->dock_tile          != INVALID_TILE) ret = max<uint>(ret, CA_DOCK);
    00230     if (this->airport_tile       != INVALID_TILE) ret = max<uint>(ret, this->GetAirportSpec()->catchment);
    00231   } else {
    00232     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) {
    00233       ret = CA_UNMODIFIED;
    00234     }
    00235   }
    00236
    00237   return ret;
    00238 }
    00239
    00244 Rect Station::GetCatchmentRect() const
    00245 {
    00246   assert(!this->rect.IsEmpty());
    00247
    00248   /* Compute acceptance rectangle */
    00249   int catchment_radius = this->GetCatchmentRadius();
    00250
    00251   Rect ret = {
    00252     max<int>(this->rect.left   - catchment_radius, 0),
    00253     max<int>(this->rect.top    - catchment_radius, 0),
    00254     min<int>(this->rect.right  + catchment_radius, MapMaxX()),
    00255     min<int>(this->rect.bottom + catchment_radius, MapMaxY())
    00256   };
    00257
    00258   return ret;
    00259 }
    00260
    00262 struct RectAndIndustryVector {
    00263   Rect rect;
    00264   IndustryVector *industries_near;
    00265 };
    00266
    00275 static bool FindIndustryToDeliver(TileIndex ind_tile, void *user_data)
    00276 {
    00277   /* Only process industry tiles */
    00278   if (!IsTileType(ind_tile, MP_INDUSTRY)) return false;
    00279
    00280   RectAndIndustryVector *riv = (RectAndIndustryVector *)user_data;
    00281   Industry *ind = Industry::GetByTile(ind_tile);
    00282
    00283   /* Don't check further if this industry is already in the list */
    00284   if (riv->industries_near->Contains(ind)) return false;
    00285
    00286   /* Only process tiles in the station acceptance rectangle */
    00287   int x = TileX(ind_tile);
    00288   int y = TileY(ind_tile);
    00289   if (x < riv->rect.left || x > riv->rect.right || y < riv->rect.top || y > riv->rect.bottom) return false;
    00290
    00291   /* Include only industries that can accept cargo */
    00292   uint cargo_index;
    00293   for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
    00294     if (ind->accepts_cargo[cargo_index] != CT_INVALID) break;
    00295   }
    00296   if (cargo_index >= lengthof(ind->accepts_cargo)) return false;
    00297
    00298   *riv->industries_near->Append() = ind;
    00299
    00300   return false;
    00301 }
    00302
    00307 void Station::RecomputeIndustriesNear()
    00308 {
    00309   this->industries_near.Clear();
    00310   if (this->rect.IsEmpty()) return;
    00311
    00312   RectAndIndustryVector riv = {
    00313     this->GetCatchmentRect(),
    00314     &this->industries_near
    00315   };
    00316
    00317   /* Compute maximum extent of acceptance rectangle wrt. station sign */
    00318   TileIndex start_tile = this->xy;
    00319   uint max_radius = max(
    00320     max(DistanceManhattan(start_tile, TileXY(riv.rect.left,  riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.left,  riv.rect.bottom))),
    00321     max(DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.bottom)))
    00322   );
    00323
    00324   CircularTileSearch(&start_tile, 2 * max_radius + 1, &FindIndustryToDeliver, &riv);
    00325 }
    00326
    00330 /* static */ void Station::RecomputeIndustriesNearForAll()
    00331 {
    00332   Station *st;
    00333   FOR_ALL_STATIONS(st) st->RecomputeIndustriesNear();
    00334 }
    00335
    00336 /************************************************************************/
    00337 /*                     StationRect implementation                       */
    00338 /************************************************************************/
    00339
    00340 StationRect::StationRect()
    00341 {
    00342   this->MakeEmpty();
    00343 }
    00344
    00345 void StationRect::MakeEmpty()
    00346 {
    00347   this->left = this->top = this->right = this->bottom = 0;
    00348 }
    00349
    00359 bool StationRect::PtInExtendedRect(int x, int y, int distance) const
    00360 {
    00361   return
    00362       this->left - distance <= x &&
    00363       x <= this->right + distance &&
    00364       this->top - distance <= y &&
    00365       y <= this->bottom + distance;
    00366 }
    00367
    00368 bool StationRect::IsEmpty() const
    00369 {
    00370   return this->left == 0 || this->left > this->right || this->top > this->bottom;
    00371 }
    00372
    00373 bool StationRect::BeforeAddTile(TileIndex tile, StationRectMode mode)
    00374 {
    00375   int x = TileX(tile);
    00376   int y = TileY(tile);
    00377   if (IsEmpty()) {
    00378     /* we are adding the first station tile */
    00379     if (mode != ADD_TEST) {
    00380       this->left = this->right = x;
    00381       this->top = this->bottom = y;
    00382     }
    00383   } else if (!PtInExtendedRect(x, y)) {
    00384     /* current rect is not empty and new point is outside this rect
    00385      * make new spread-out rectangle */
    00386     Rect new_rect = {min(x, this->left), min(y, this->top), max(x, this->right), max(y, this->bottom)};
    00387
    00388     /* check new rect dimensions against preset max */
    00389     int w = new_rect.right - new_rect.left + 1;
    00390     int h = new_rect.bottom - new_rect.top + 1;
    00391     if (mode != ADD_FORCE && (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread)) {
    00392       assert(mode != ADD_TRY);
    00393       _error_message = STR_ERROR_STATION_TOO_SPREAD_OUT;
    00394       return false;
    00395     }
    00396
    00397     /* spread-out ok, return true */
    00398     if (mode != ADD_TEST) {
    00399       /* we should update the station rect */
    00400       *this = new_rect;
    00401     }
    00402   } else {
    00403     ; // new point is inside the rect, we don't need to do anything
    00404   }
    00405   return true;
    00406 }
    00407
    00408 bool StationRect::BeforeAddRect(TileIndex tile, int w, int h, StationRectMode mode)
    00409 {
    00410   return (mode == ADD_FORCE || (w <= _settings_game.station.station_spread && h <= _settings_game.station.station_spread)) && // important when the old rect is completely inside the new rect, resp. the old one was empty
    00411       this->BeforeAddTile(tile, mode) && this->BeforeAddTile(TILE_ADDXY(tile, w - 1, h - 1), mode);
    00412 }
    00413
    00423 /* static */ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a)
    00424 {
    00425   TileIndex top_left = TileXY(left_a, top_a);
    00426   int width = right_a - left_a + 1;
    00427   int height = bottom_a - top_a + 1;
    00428
    00429   TILE_LOOP(tile, width, height, top_left) {
    00430     if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true;
    00431   }
    00432
    00433   return false;
    00434 }
    00435
    00436 bool StationRect::AfterRemoveTile(BaseStation *st, TileIndex tile)
    00437 {
    00438   int x = TileX(tile);
    00439   int y = TileY(tile);
    00440
    00441   /* look if removed tile was on the bounding rect edge
    00442    * and try to reduce the rect by this edge
    00443    * do it until we have empty rect or nothing to do */
    00444   for (;;) {
    00445     /* check if removed tile is on rect edge */
    00446     bool left_edge = (x == this->left);
    00447     bool right_edge = (x == this->right);
    00448     bool top_edge = (y == this->top);
    00449     bool bottom_edge = (y == this->bottom);
    00450
    00451     /* can we reduce the rect in either direction? */
    00452     bool reduce_x = ((left_edge || right_edge) && !ScanForStationTiles(st->index, x, this->top, x, this->bottom));
    00453     bool reduce_y = ((top_edge || bottom_edge) && !ScanForStationTiles(st->index, this->left, y, this->right, y));
    00454     if (!(reduce_x || reduce_y)) break; // nothing to do (can't reduce)
    00455
    00456     if (reduce_x) {
    00457       /* reduce horizontally */
    00458       if (left_edge) {
    00459         /* move left edge right */
    00460         this->left = x = x + 1;
    00461       } else {
    00462         /* move right edge left */
    00463         this->right = x = x - 1;
    00464       }
    00465     }
    00466     if (reduce_y) {
    00467       /* reduce vertically */
    00468       if (top_edge) {
    00469         /* move top edge down */
    00470         this->top = y = y + 1;
    00471       } else {
    00472         /* move bottom edge up */
    00473         this->bottom = y = y - 1;
    00474       }
    00475     }
    00476
    00477     if (left > right || top > bottom) {
    00478       /* can't continue, if the remaining rectangle is empty */
    00479       this->MakeEmpty();
    00480       return true; // empty remaining rect
    00481     }
    00482   }
    00483   return false; // non-empty remaining rect
    00484 }
    00485
    00486 bool StationRect::AfterRemoveRect(BaseStation *st, TileIndex tile, int w, int h)
    00487 {
    00488   assert(PtInExtendedRect(TileX(tile), TileY(tile)));
    00489   assert(PtInExtendedRect(TileX(tile) + w - 1, TileY(tile) + h - 1));
    00490
    00491   bool empty = this->AfterRemoveTile(st, tile);
    00492   if (w != 1 || h != 1) empty = empty || AfterRemoveTile(st, TILE_ADDXY(tile, w - 1, h - 1));
    00493   return empty;
    00494 }
    00495
    00496 StationRect& StationRect::operator = (Rect src)
    00497 {
    00498   this->left = src.left;
    00499   this->top = src.top;
    00500   this->right = src.right;
    00501   this->bottom = src.bottom;
    00502   return *this;
    00503 }
    00504
    00505
    00506 void InitializeStations()
    00507 {
    00508   _station_pool.CleanPool();
    00509 }