station_cmd.cpp

Go to the documentation of this file.
00001 /* $Id: station_cmd.cpp 14933 2009-01-09 14:59:02Z rubidium $ */
00002 
00005 #include "stdafx.h"
00006 #include "openttd.h"
00007 #include "aircraft.h"
00008 #include "bridge_map.h"
00009 #include "cmd_helper.h"
00010 #include "debug.h"
00011 #include "tile_cmd.h"
00012 #include "landscape.h"
00013 #include "station_map.h"
00014 #include "viewport_func.h"
00015 #include "command_func.h"
00016 #include "town.h"
00017 #include "news_func.h"
00018 #include "airport.h"
00019 #include "sprite.h"
00020 #include "train.h"
00021 #include "roadveh.h"
00022 #include "water_map.h"
00023 #include "industry_map.h"
00024 #include "newgrf_callbacks.h"
00025 #include "newgrf_station.h"
00026 #include "newgrf_commons.h"
00027 #include "yapf/yapf.h"
00028 #include "road_type.h"
00029 #include "road_internal.h" /* For drawing catenary/checking road removal */
00030 #include "cargotype.h"
00031 #include "variables.h"
00032 #include "autoslope.h"
00033 #include "transparency.h"
00034 #include "water.h"
00035 #include "station_gui.h"
00036 #include "strings_func.h"
00037 #include "functions.h"
00038 #include "window_func.h"
00039 #include "date_func.h"
00040 #include "vehicle_func.h"
00041 #include "string_func.h"
00042 #include "signal_func.h"
00043 #include "oldpool_func.h"
00044 #include "animated_tile_func.h"
00045 #include "elrail_func.h"
00046 #include "newgrf.h"
00047 #include "core/smallvec_type.hpp"
00048 
00049 #include "table/sprites.h"
00050 #include "table/strings.h"
00051 
00052 DEFINE_OLD_POOL_GENERIC(Station, Station)
00053 DEFINE_OLD_POOL_GENERIC(RoadStop, RoadStop)
00054 
00055 
00062 bool IsHangar(TileIndex t)
00063 {
00064   assert(IsTileType(t, MP_STATION));
00065 
00066   const Station *st = GetStationByTile(t);
00067   const AirportFTAClass *apc = st->Airport();
00068 
00069   for (uint i = 0; i < apc->nof_depots; i++) {
00070     if (st->airport_tile + ToTileIndexDiff(apc->airport_depots[i]) == t) return true;
00071   }
00072 
00073   return false;
00074 }
00075 
00076 RoadStop *GetRoadStopByTile(TileIndex tile, RoadStopType type)
00077 {
00078   const Station *st = GetStationByTile(tile);
00079 
00080   for (RoadStop *rs = st->GetPrimaryRoadStop(type);; rs = rs->next) {
00081     if (rs->xy == tile) return rs;
00082     assert(rs->next != NULL);
00083   }
00084 }
00085 
00086 
00087 static uint GetNumRoadStopsInStation(const Station *st, RoadStopType type)
00088 {
00089   uint num = 0;
00090 
00091   assert(st != NULL);
00092   for (const RoadStop *rs = st->GetPrimaryRoadStop(type); rs != NULL; rs = rs->next) {
00093     num++;
00094   }
00095 
00096   return num;
00097 }
00098 
00099 
00100 #define CHECK_STATIONS_ERR ((Station*)-1)
00101 
00102 static Station *GetStationAround(TileIndex tile, int w, int h, StationID closest_station)
00103 {
00104   /* check around to see if there's any stations there */
00105   BEGIN_TILE_LOOP(tile_cur, w + 2, h + 2, tile - TileDiffXY(1, 1))
00106     if (IsTileType(tile_cur, MP_STATION)) {
00107       StationID t = GetStationIndex(tile_cur);
00108 
00109       if (closest_station == INVALID_STATION) {
00110         closest_station = t;
00111       } else if (closest_station != t) {
00112         _error_message = STR_3006_ADJOINS_MORE_THAN_ONE_EXISTING;
00113         return CHECK_STATIONS_ERR;
00114       }
00115     }
00116   END_TILE_LOOP(tile_cur, w + 2, h + 2, tile - TileDiffXY(1, 1))
00117   return (closest_station == INVALID_STATION) ? NULL : GetStation(closest_station);
00118 }
00119 
00125 typedef bool (*CMSAMatcher)(TileIndex tile);
00126 
00135 static int CountMapSquareAround(TileIndex tile, CMSAMatcher cmp)
00136 {
00137   int num = 0;
00138 
00139   for (int dx = -3; dx <= 3; dx++) {
00140     for (int dy = -3; dy <= 3; dy++) {
00141       if (cmp(TILE_MASK(tile + TileDiffXY(dx, dy)))) num++;
00142     }
00143   }
00144 
00145   return num;
00146 }
00147 
00153 static bool CMSAMine(TileIndex tile)
00154 {
00155   /* No industry */
00156   if (!IsTileType(tile, MP_INDUSTRY)) return false;
00157 
00158   const Industry *ind = GetIndustryByTile(tile);
00159 
00160   /* No extractive industry */
00161   if ((GetIndustrySpec(ind->type)->life_type & INDUSTRYLIFE_EXTRACTIVE) == 0) return false;
00162 
00163   for (uint i = 0; i < lengthof(ind->produced_cargo); i++) {
00164     /* The industry extracts something non-liquid, i.e. no oil or plastic, so it is a mine */
00165     if (ind->produced_cargo[i] != CT_INVALID && (GetCargo(ind->produced_cargo[i])->classes & CC_LIQUID) == 0) return true;
00166   }
00167 
00168   return false;
00169 }
00170 
00176 static bool CMSAWater(TileIndex tile)
00177 {
00178   return IsTileType(tile, MP_WATER) && IsWater(tile);
00179 }
00180 
00186 static bool CMSATree(TileIndex tile)
00187 {
00188   return IsTileType(tile, MP_TREES);
00189 }
00190 
00196 static bool CMSAForest(TileIndex tile)
00197 {
00198   /* No industry */
00199   if (!IsTileType(tile, MP_INDUSTRY)) return false;
00200 
00201   const Industry *ind = GetIndustryByTile(tile);
00202 
00203   /* No extractive industry */
00204   if ((GetIndustrySpec(ind->type)->life_type & INDUSTRYLIFE_ORGANIC) == 0) return false;
00205 
00206   for (uint i = 0; i < lengthof(ind->produced_cargo); i++) {
00207     /* The industry produces wood. */
00208     if (ind->produced_cargo[i] != CT_INVALID && GetCargo(ind->produced_cargo[i])->label == 'WOOD') return true;
00209   }
00210 
00211   return false;
00212 }
00213 
00214 #define M(x) ((x) - STR_SV_STNAME)
00215 
00216 enum StationNaming {
00217   STATIONNAMING_RAIL = 0,
00218   STATIONNAMING_ROAD = 0,
00219   STATIONNAMING_AIRPORT,
00220   STATIONNAMING_OILRIG,
00221   STATIONNAMING_DOCK,
00222   STATIONNAMING_BUOY,
00223   STATIONNAMING_HELIPORT,
00224 };
00225 
00227 struct StationNameInformation {
00228   uint32 free_names; 
00229   bool *indtypes;    
00230 };
00231 
00240 static bool FindNearIndustryName(TileIndex tile, void *user_data)
00241 {
00242   /* All already found industry types */
00243   StationNameInformation *sni = (StationNameInformation*)user_data;
00244   if (!IsTileType(tile, MP_INDUSTRY)) return false;
00245 
00246   /* If the station name is undefined it means that it doesn't name a station */
00247   IndustryType indtype = GetIndustryType(tile);
00248   if (GetIndustrySpec(indtype)->station_name == STR_UNDEFINED) return false;
00249 
00250   /* In all cases if an industry that provides a name is found two of
00251    * the standard names will be disabled. */
00252   sni->free_names &= ~(1 << M(STR_SV_STNAME_OILFIELD) | 1 << M(STR_SV_STNAME_MINES));
00253   return !sni->indtypes[indtype];
00254 }
00255 
00256 static StringID GenerateStationName(Station *st, TileIndex tile, int flag)
00257 {
00258   static const uint32 _gen_station_name_bits[] = {
00259     0,                                      /* 0 */
00260     1 << M(STR_SV_STNAME_AIRPORT),          /* 1 */
00261     1 << M(STR_SV_STNAME_OILFIELD),         /* 2 */
00262     1 << M(STR_SV_STNAME_DOCKS),            /* 3 */
00263     0x1FF << M(STR_SV_STNAME_BUOY_1),       /* 4 */
00264     1 << M(STR_SV_STNAME_HELIPORT),         /* 5 */
00265   };
00266 
00267   const Town *t = st->town;
00268   uint32 free_names = UINT32_MAX;
00269 
00270   bool indtypes[NUM_INDUSTRYTYPES];
00271   memset(indtypes, 0, sizeof(indtypes));
00272 
00273   const Station *s;
00274   FOR_ALL_STATIONS(s) {
00275     if (s != st && s->town == t) {
00276       if (s->indtype != IT_INVALID) {
00277         indtypes[s->indtype] = true;
00278         continue;
00279       }
00280       uint str = M(s->string_id);
00281       if (str <= 0x20) {
00282         if (str == M(STR_SV_STNAME_FOREST)) {
00283           str = M(STR_SV_STNAME_WOODS);
00284         }
00285         ClrBit(free_names, str);
00286       }
00287     }
00288   }
00289 
00290   if (flag != STATIONNAMING_BUOY) {
00291     TileIndex indtile = tile;
00292     StationNameInformation sni = { free_names, indtypes };
00293     if (CircularTileSearch(&indtile, 7, FindNearIndustryName, &sni)) {
00294       /* An industry has been found nearby */
00295       IndustryType indtype = GetIndustryType(indtile);
00296       const IndustrySpec *indsp = GetIndustrySpec(indtype);
00297       /* STR_NULL means it only disables oil rig/mines */
00298       if (indsp->station_name != STR_NULL) {
00299         st->indtype = indtype;
00300         return STR_SV_STNAME_FALLBACK;
00301       }
00302     }
00303 
00304     /* Oil rigs/mines name could be marked not free by looking for a near by industry. */
00305     free_names = sni.free_names;
00306   }
00307 
00308   /* check default names */
00309   uint32 tmp = free_names & _gen_station_name_bits[flag];
00310   if (tmp != 0) return STR_SV_STNAME + FindFirstBit(tmp);
00311 
00312   /* check mine? */
00313   if (HasBit(free_names, M(STR_SV_STNAME_MINES))) {
00314     if (CountMapSquareAround(tile, CMSAMine) >= 2) {
00315       return STR_SV_STNAME_MINES;
00316     }
00317   }
00318 
00319   /* check close enough to town to get central as name? */
00320   if (DistanceMax(tile, t->xy) < 8) {
00321     if (HasBit(free_names, M(STR_SV_STNAME))) return STR_SV_STNAME;
00322 
00323     if (HasBit(free_names, M(STR_SV_STNAME_CENTRAL))) return STR_SV_STNAME_CENTRAL;
00324   }
00325 
00326   /* Check lakeside */
00327   if (HasBit(free_names, M(STR_SV_STNAME_LAKESIDE)) &&
00328       DistanceFromEdge(tile) < 20 &&
00329       CountMapSquareAround(tile, CMSAWater) >= 5) {
00330     return STR_SV_STNAME_LAKESIDE;
00331   }
00332 
00333   /* Check woods */
00334   if (HasBit(free_names, M(STR_SV_STNAME_WOODS)) && (
00335         CountMapSquareAround(tile, CMSATree) >= 8 ||
00336         CountMapSquareAround(tile, CMSAForest) >= 2)
00337       ) {
00338     return _settings_game.game_creation.landscape == LT_TROPIC ? STR_SV_STNAME_FOREST : STR_SV_STNAME_WOODS;
00339   }
00340 
00341   /* check elevation compared to town */
00342   uint z = GetTileZ(tile);
00343   uint z2 = GetTileZ(t->xy);
00344   if (z < z2) {
00345     if (HasBit(free_names, M(STR_SV_STNAME_VALLEY))) return STR_SV_STNAME_VALLEY;
00346   } else if (z > z2) {
00347     if (HasBit(free_names, M(STR_SV_STNAME_HEIGHTS))) return STR_SV_STNAME_HEIGHTS;
00348   }
00349 
00350   /* check direction compared to town */
00351   static const int8 _direction_and_table[] = {
00352     ~( (1 << M(STR_SV_STNAME_WEST))  | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ),
00353     ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_NORTH)) ),
00354     ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ),
00355     ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_EAST)) ),
00356   };
00357 
00358   free_names &= _direction_and_table[
00359     (TileX(tile) < TileX(t->xy)) +
00360     (TileY(tile) < TileY(t->xy)) * 2];
00361 
00362   tmp = free_names & ((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 6) | (1 << 7) | (1 << 12) | (1 << 26) | (1 << 27) | (1 << 28) | (1 << 29) | (1 << 30));
00363   return (tmp == 0) ? STR_SV_STNAME_FALLBACK : (STR_SV_STNAME + FindFirstBit(tmp));
00364 }
00365 #undef M
00366 
00372 static Station *GetClosestDeletedStation(TileIndex tile)
00373 {
00374   uint threshold = 8;
00375   Station *best_station = NULL;
00376   Station *st;
00377 
00378   FOR_ALL_STATIONS(st) {
00379     if (st->facilities == 0 && st->owner == _current_company) {
00380       uint cur_dist = DistanceManhattan(tile, st->xy);
00381 
00382       if (cur_dist < threshold) {
00383         threshold = cur_dist;
00384         best_station = st;
00385       }
00386     }
00387   }
00388 
00389   return best_station;
00390 }
00391 
00395 static void UpdateStationVirtCoord(Station *st)
00396 {
00397   Point pt = RemapCoords2(TileX(st->xy) * TILE_SIZE, TileY(st->xy) * TILE_SIZE);
00398 
00399   pt.y -= 32;
00400   if (st->facilities & FACIL_AIRPORT && st->airport_type == AT_OILRIG) pt.y -= 16;
00401 
00402   SetDParam(0, st->index);
00403   SetDParam(1, st->facilities);
00404   UpdateViewportSignPos(&st->sign, pt.x, pt.y, STR_305C_0);
00405 }
00406 
00408 void UpdateAllStationVirtCoord()
00409 {
00410   Station *st;
00411 
00412   FOR_ALL_STATIONS(st) {
00413     UpdateStationVirtCoord(st);
00414   }
00415 }
00416 
00425 static void UpdateStationVirtCoordDirty(Station *st)
00426 {
00427   st->MarkDirty();
00428   UpdateStationVirtCoord(st);
00429   st->MarkDirty();
00430 }
00431 
00436 static uint GetAcceptanceMask(const Station *st)
00437 {
00438   uint mask = 0;
00439 
00440   for (CargoID i = 0; i < NUM_CARGO; i++) {
00441     if (HasBit(st->goods[i].acceptance_pickup, GoodsEntry::ACCEPTANCE)) mask |= 1 << i;
00442   }
00443   return mask;
00444 }
00445 
00449 static void ShowRejectOrAcceptNews(const Station *st, uint num_items, CargoID *cargo, StringID msg)
00450 {
00451   for (uint i = 0; i < num_items; i++) {
00452     SetDParam(i + 1, GetCargo(cargo[i])->name);
00453   }
00454 
00455   SetDParam(0, st->index);
00456   AddNewsItem(msg, NS_ACCEPTANCE, st->xy, st->index);
00457 }
00458 
00467 void GetProductionAroundTiles(AcceptedCargo produced, TileIndex tile,
00468   int w, int h, int rad)
00469 {
00470   memset(produced, 0, sizeof(AcceptedCargo)); // sizeof(AcceptedCargo) != sizeof(produced) (== sizeof(uint *))
00471 
00472   int x = TileX(tile);
00473   int y = TileY(tile);
00474 
00475   /* expand the region by rad tiles on each side
00476    * while making sure that we remain inside the board. */
00477   int x2 = min(x + w + rad, MapSizeX());
00478   int x1 = max(x - rad, 0);
00479 
00480   int y2 = min(y + h + rad, MapSizeY());
00481   int y1 = max(y - rad, 0);
00482 
00483   assert(x1 < x2);
00484   assert(y1 < y2);
00485   assert(w > 0);
00486   assert(h > 0);
00487 
00488   for (int yc = y1; yc != y2; yc++) {
00489     for (int xc = x1; xc != x2; xc++) {
00490       TileIndex tile = TileXY(xc, yc);
00491 
00492       if (!IsTileType(tile, MP_STATION)) {
00493         GetProducedCargoProc *gpc = _tile_type_procs[GetTileType(tile)]->get_produced_cargo_proc;
00494         if (gpc != NULL) {
00495           CargoID cargos[256]; // Required for CBID_HOUSE_PRODUCE_CARGO.
00496           memset(cargos, CT_INVALID, sizeof(cargos));
00497 
00498           gpc(tile, cargos);
00499           for (uint i = 0; i < lengthof(cargos); ++i) {
00500             if (cargos[i] != CT_INVALID) produced[cargos[i]]++;
00501           }
00502         }
00503       }
00504     }
00505   }
00506 }
00507 
00516 void GetAcceptanceAroundTiles(AcceptedCargo accepts, TileIndex tile,
00517   int w, int h, int rad)
00518 {
00519   memset(accepts, 0, sizeof(AcceptedCargo)); // sizeof(AcceptedCargo) != sizeof(accepts) (== sizeof(uint *))
00520 
00521   int x = TileX(tile);
00522   int y = TileY(tile);
00523 
00524   /* expand the region by rad tiles on each side
00525    * while making sure that we remain inside the board. */
00526   int x2 = min(x + w + rad, MapSizeX());
00527   int y2 = min(y + h + rad, MapSizeY());
00528   int x1 = max(x - rad, 0);
00529   int y1 = max(y - rad, 0);
00530 
00531   assert(x1 < x2);
00532   assert(y1 < y2);
00533   assert(w > 0);
00534   assert(h > 0);
00535 
00536   for (int yc = y1; yc != y2; yc++) {
00537     for (int xc = x1; xc != x2; xc++) {
00538       TileIndex tile = TileXY(xc, yc);
00539 
00540       if (!IsTileType(tile, MP_STATION)) {
00541         AcceptedCargo ac;
00542 
00543         GetAcceptedCargo(tile, ac);
00544         for (uint i = 0; i < lengthof(ac); ++i) accepts[i] += ac[i];
00545       }
00546     }
00547   }
00548 }
00549 
00550 static inline void MergePoint(Rect *rect, TileIndex tile)
00551 {
00552   int x = TileX(tile);
00553   int y = TileY(tile);
00554 
00555   if (rect->left   > x) rect->left   = x;
00556   if (rect->bottom > y) rect->bottom = y;
00557   if (rect->right  < x) rect->right  = x;
00558   if (rect->top    < y) rect->top    = y;
00559 }
00560 
00565 static void UpdateStationAcceptance(Station *st, bool show_msg)
00566 {
00567   /* Don't update acceptance for a buoy */
00568   if (st->IsBuoy()) return;
00569 
00570   Rect rect;
00571   rect.left   = MapSizeX();
00572   rect.bottom = MapSizeY();
00573   rect.right  = 0;
00574   rect.top    = 0;
00575 
00576   /* old accepted goods types */
00577   uint old_acc = GetAcceptanceMask(st);
00578 
00579   /* Put all the tiles that span an area in the table. */
00580   if (st->train_tile != INVALID_TILE) {
00581     MergePoint(&rect, st->train_tile);
00582     MergePoint(&rect, st->train_tile + TileDiffXY(st->trainst_w - 1, st->trainst_h - 1));
00583   }
00584 
00585   if (st->airport_tile != INVALID_TILE) {
00586     const AirportFTAClass *afc = st->Airport();
00587 
00588     MergePoint(&rect, st->airport_tile);
00589     MergePoint(&rect, st->airport_tile + TileDiffXY(afc->size_x - 1, afc->size_y - 1));
00590   }
00591 
00592   if (st->dock_tile != INVALID_TILE) {
00593     MergePoint(&rect, st->dock_tile);
00594     if (IsDockTile(st->dock_tile)) {
00595       MergePoint(&rect, st->dock_tile + TileOffsByDiagDir(GetDockDirection(st->dock_tile)));
00596     } // else OilRig
00597   }
00598 
00599   for (const RoadStop *rs = st->bus_stops; rs != NULL; rs = rs->next) {
00600     MergePoint(&rect, rs->xy);
00601   }
00602 
00603   for (const RoadStop *rs = st->truck_stops; rs != NULL; rs = rs->next) {
00604     MergePoint(&rect, rs->xy);
00605   }
00606 
00607   /* And retrieve the acceptance. */
00608   AcceptedCargo accepts;
00609   assert((rect.right >= rect.left) == !st->rect.IsEmpty());
00610   if (rect.right >= rect.left) {
00611     assert(rect.left == st->rect.left);
00612     assert(rect.top == st->rect.bottom);
00613     assert(rect.right == st->rect.right);
00614     assert(rect.bottom == st->rect.top);
00615     GetAcceptanceAroundTiles(
00616       accepts,
00617       TileXY(rect.left, rect.bottom),
00618       rect.right - rect.left   + 1,
00619       rect.top   - rect.bottom + 1,
00620       st->GetCatchmentRadius()
00621     );
00622   } else {
00623     memset(accepts, 0, sizeof(accepts));
00624   }
00625 
00626   /* Adjust in case our station only accepts fewer kinds of goods */
00627   for (CargoID i = 0; i < NUM_CARGO; i++) {
00628     uint amt = min(accepts[i], 15);
00629 
00630     /* Make sure the station can accept the goods type. */
00631     bool is_passengers = IsCargoInClass(i, CC_PASSENGERS);
00632     if ((!is_passengers && !(st->facilities & (byte)~FACIL_BUS_STOP)) ||
00633         (is_passengers && !(st->facilities & (byte)~FACIL_TRUCK_STOP))) {
00634       amt = 0;
00635     }
00636 
00637     SB(st->goods[i].acceptance_pickup, GoodsEntry::ACCEPTANCE, 1, amt >= 8);
00638   }
00639 
00640   /* Only show a message in case the acceptance was actually changed. */
00641   uint new_acc = GetAcceptanceMask(st);
00642   if (old_acc == new_acc) return;
00643 
00644   /* show a message to report that the acceptance was changed? */
00645   if (show_msg && st->owner == _local_company && st->facilities) {
00646     /* List of accept and reject strings for different number of
00647      * cargo types */
00648     static const StringID accept_msg[] = {
00649       STR_3040_NOW_ACCEPTS,
00650       STR_3041_NOW_ACCEPTS_AND,
00651     };
00652     static const StringID reject_msg[] = {
00653       STR_303E_NO_LONGER_ACCEPTS,
00654       STR_303F_NO_LONGER_ACCEPTS_OR,
00655     };
00656 
00657     /* Array of accepted and rejected cargo types */
00658     CargoID accepts[2] = { CT_INVALID, CT_INVALID };
00659     CargoID rejects[2] = { CT_INVALID, CT_INVALID };
00660     uint num_acc = 0;
00661     uint num_rej = 0;
00662 
00663     /* Test each cargo type to see if its acceptange has changed */
00664     for (CargoID i = 0; i < NUM_CARGO; i++) {
00665       if (HasBit(new_acc, i)) {
00666         if (!HasBit(old_acc, i) && num_acc < lengthof(accepts)) {
00667           /* New cargo is accepted */
00668           accepts[num_acc++] = i;
00669         }
00670       } else {
00671         if (HasBit(old_acc, i) && num_rej < lengthof(rejects)) {
00672           /* Old cargo is no longer accepted */
00673           rejects[num_rej++] = i;
00674         }
00675       }
00676     }
00677 
00678     /* Show news message if there are any changes */
00679     if (num_acc > 0) ShowRejectOrAcceptNews(st, num_acc, accepts, accept_msg[num_acc - 1]);
00680     if (num_rej > 0) ShowRejectOrAcceptNews(st, num_rej, rejects, reject_msg[num_rej - 1]);
00681   }
00682 
00683   /* redraw the station view since acceptance changed */
00684   InvalidateWindowWidget(WC_STATION_VIEW, st->index, SVW_ACCEPTLIST);
00685 }
00686 
00687 static void UpdateStationSignCoord(Station *st)
00688 {
00689   const StationRect *r = &st->rect;
00690 
00691   if (r->IsEmpty()) return; /* no tiles belong to this station */
00692 
00693   /* clamp sign coord to be inside the station rect */
00694   st->xy = TileXY(ClampU(TileX(st->xy), r->left, r->right), ClampU(TileY(st->xy), r->top, r->bottom));
00695   UpdateStationVirtCoordDirty(st);
00696 }
00697 
00703 static void DeleteStationIfEmpty(Station *st)
00704 {
00705   if (st->facilities == 0) {
00706     st->delete_ctr = 0;
00707     InvalidateWindowData(WC_STATION_LIST, st->owner, 0);
00708   }
00709   /* station remains but it probably lost some parts - station sign should stay in the station boundaries */
00710   UpdateStationSignCoord(st);
00711 }
00712 
00713 static CommandCost ClearTile_Station(TileIndex tile, byte flags);
00714 
00725 CommandCost CheckFlatLandBelow(TileIndex tile, uint w, uint h, uint flags, uint invalid_dirs, StationID *station, bool check_clear = true)
00726 {
00727   CommandCost cost(EXPENSES_CONSTRUCTION);
00728   int allowed_z = -1;
00729 
00730   BEGIN_TILE_LOOP(tile_cur, w, h, tile) {
00731     if (MayHaveBridgeAbove(tile_cur) && IsBridgeAbove(tile_cur)) {
00732       return_cmd_error(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST);
00733     }
00734 
00735     if (!EnsureNoVehicleOnGround(tile_cur)) return CMD_ERROR;
00736 
00737     uint z;
00738     Slope tileh = GetTileSlope(tile_cur, &z);
00739 
00740     /* Prohibit building if
00741      *   1) The tile is "steep" (i.e. stretches two height levels)
00742      * -OR-
00743      *   2) The tile is non-flat if
00744      *     a) the company building is an "old-school" AI
00745      *   -OR-
00746      *     b) the build_on_slopes switch is disabled
00747      */
00748     if (IsSteepSlope(tileh) ||
00749         ((_is_old_ai_company || !_settings_game.construction.build_on_slopes) && tileh != SLOPE_FLAT)) {
00750       return_cmd_error(STR_0007_FLAT_LAND_REQUIRED);
00751     }
00752 
00753     int flat_z = z;
00754     if (tileh != SLOPE_FLAT) {
00755       /* need to check so the entrance to the station is not pointing at a slope.
00756        * This must be valid for all station tiles, as the user can remove single station tiles. */
00757       if ((HasBit(invalid_dirs, DIAGDIR_NE) && !(tileh & SLOPE_NE)) ||
00758           (HasBit(invalid_dirs, DIAGDIR_SE) && !(tileh & SLOPE_SE)) ||
00759           (HasBit(invalid_dirs, DIAGDIR_SW) && !(tileh & SLOPE_SW)) ||
00760           (HasBit(invalid_dirs, DIAGDIR_NW) && !(tileh & SLOPE_NW))) {
00761         return_cmd_error(STR_0007_FLAT_LAND_REQUIRED);
00762       }
00763       cost.AddCost(_price.terraform);
00764       flat_z += TILE_HEIGHT;
00765     }
00766 
00767     /* get corresponding flat level and make sure that all parts of the station have the same level. */
00768     if (allowed_z == -1) {
00769       /* first tile */
00770       allowed_z = flat_z;
00771     } else if (allowed_z != flat_z) {
00772       return_cmd_error(STR_0007_FLAT_LAND_REQUIRED);
00773     }
00774 
00775     /* if station is set, then we have special handling to allow building on top of already existing stations.
00776      * so station points to INVALID_STATION if we can build on any station.
00777      * Or it points to a station if we're only allowed to build on exactly that station. */
00778     if (station != NULL && IsTileType(tile_cur, MP_STATION)) {
00779       if (!IsRailwayStation(tile_cur)) {
00780         return ClearTile_Station(tile_cur, DC_AUTO); // get error message
00781       } else {
00782         StationID st = GetStationIndex(tile_cur);
00783         if (*station == INVALID_STATION) {
00784           *station = st;
00785         } else if (*station != st) {
00786           return_cmd_error(STR_3006_ADJOINS_MORE_THAN_ONE_EXISTING);
00787         }
00788       }
00789     } else if (check_clear) {
00790       CommandCost ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
00791       if (CmdFailed(ret)) return ret;
00792       cost.AddCost(ret);
00793     }
00794   } END_TILE_LOOP(tile_cur, w, h, tile)
00795 
00796   return cost;
00797 }
00798 
00799 static bool CanExpandRailroadStation(const Station *st, uint *fin, Axis axis)
00800 {
00801   uint curw = st->trainst_w;
00802   uint curh = st->trainst_h;
00803   TileIndex tile = fin[0];
00804   uint w = fin[1];
00805   uint h = fin[2];
00806 
00807   if (_settings_game.station.nonuniform_stations) {
00808     /* determine new size of train station region.. */
00809     int x = min(TileX(st->train_tile), TileX(tile));
00810     int y = min(TileY(st->train_tile), TileY(tile));
00811     curw = max(TileX(st->train_tile) + curw, TileX(tile) + w) - x;
00812     curh = max(TileY(st->train_tile) + curh, TileY(tile) + h) - y;
00813     tile = TileXY(x, y);
00814   } else {
00815     /* do not allow modifying non-uniform stations,
00816      * the uniform-stations code wouldn't handle it well */
00817     BEGIN_TILE_LOOP(t, st->trainst_w, st->trainst_h, st->train_tile)
00818       if (!st->TileBelongsToRailStation(t)) { // there may be adjoined station
00819         _error_message = STR_NONUNIFORM_STATIONS_DISALLOWED;
00820         return false;
00821       }
00822     END_TILE_LOOP(t, st->trainst_w, st->trainst_h, st->train_tile)
00823 
00824     /* check so the orientation is the same */
00825     if (GetRailStationAxis(st->train_tile) != axis) {
00826       _error_message = STR_NONUNIFORM_STATIONS_DISALLOWED;
00827       return false;
00828     }
00829 
00830     /* check if the new station adjoins the old station in either direction */
00831     if (curw == w && st->train_tile == tile + TileDiffXY(0, h)) {
00832       /* above */
00833       curh += h;
00834     } else if (curw == w && st->train_tile == tile - TileDiffXY(0, curh)) {
00835       /* below */
00836       tile -= TileDiffXY(0, curh);
00837       curh += h;
00838     } else if (curh == h && st->train_tile == tile + TileDiffXY(w, 0)) {
00839       /* to the left */
00840       curw += w;
00841     } else if (curh == h && st->train_tile == tile - TileDiffXY(curw, 0)) {
00842       /* to the right */
00843       tile -= TileDiffXY(curw, 0);
00844       curw += w;
00845     } else {
00846       _error_message = STR_NONUNIFORM_STATIONS_DISALLOWED;
00847       return false;
00848     }
00849   }
00850   /* make sure the final size is not too big. */
00851   if (curw > _settings_game.station.station_spread || curh > _settings_game.station.station_spread) {
00852     _error_message = STR_306C_STATION_TOO_SPREAD_OUT;
00853     return false;
00854   }
00855 
00856   /* now tile contains the new value for st->train_tile
00857    * curw, curh contain the new value for width and height */
00858   fin[0] = tile;
00859   fin[1] = curw;
00860   fin[2] = curh;
00861   return true;
00862 }
00863 
00864 static inline byte *CreateSingle(byte *layout, int n)
00865 {
00866   int i = n;
00867   do *layout++ = 0; while (--i);
00868   layout[((n - 1) >> 1) - n] = 2;
00869   return layout;
00870 }
00871 
00872 static inline byte *CreateMulti(byte *layout, int n, byte b)
00873 {
00874   int i = n;
00875   do *layout++ = b; while (--i);
00876   if (n > 4) {
00877     layout[0 - n] = 0;
00878     layout[n - 1 - n] = 0;
00879   }
00880   return layout;
00881 }
00882 
00883 static void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec)
00884 {
00885   if (statspec != NULL && statspec->lengths >= plat_len &&
00886       statspec->platforms[plat_len - 1] >= numtracks &&
00887       statspec->layouts[plat_len - 1][numtracks - 1]) {
00888     /* Custom layout defined, follow it. */
00889     memcpy(layout, statspec->layouts[plat_len - 1][numtracks - 1],
00890       plat_len * numtracks);
00891     return;
00892   }
00893 
00894   if (plat_len == 1) {
00895     CreateSingle(layout, numtracks);
00896   } else {
00897     if (numtracks & 1) layout = CreateSingle(layout, plat_len);
00898     numtracks >>= 1;
00899 
00900     while (--numtracks >= 0) {
00901       layout = CreateMulti(layout, plat_len, 4);
00902       layout = CreateMulti(layout, plat_len, 6);
00903     }
00904   }
00905 }
00906 
00921 CommandCost CmdBuildRailroadStation(TileIndex tile_org, uint32 flags, uint32 p1, uint32 p2, const char *text)
00922 {
00923   /* Does the authority allow this? */
00924   if (!(flags & DC_NO_TOWN_RATING) && !CheckIfAuthorityAllows(tile_org)) return CMD_ERROR;
00925   if (!ValParamRailtype((RailType)(p1 & 0xF))) return CMD_ERROR;
00926 
00927   /* unpack parameters */
00928   Axis axis = Extract<Axis, 4>(p1);
00929   uint numtracks = GB(p1,  8, 8);
00930   uint plat_len  = GB(p1, 16, 8);
00931 
00932   int w_org, h_org;
00933   if (axis == AXIS_X) {
00934     w_org = plat_len;
00935     h_org = numtracks;
00936   } else {
00937     h_org = plat_len;
00938     w_org = numtracks;
00939   }
00940 
00941   StationID station_to_join = GB(p2, 16, 16);
00942   bool distant_join = (station_to_join != INVALID_STATION);
00943 
00944   if (distant_join && (!_settings_game.station.distant_join_stations || !IsValidStationID(station_to_join))) return CMD_ERROR;
00945 
00946   if (h_org > _settings_game.station.station_spread || w_org > _settings_game.station.station_spread) return CMD_ERROR;
00947 
00948   /* these values are those that will be stored in train_tile and station_platforms */
00949   uint finalvalues[3];
00950   finalvalues[0] = tile_org;
00951   finalvalues[1] = w_org;
00952   finalvalues[2] = h_org;
00953 
00954   /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
00955   StationID est = INVALID_STATION;
00956   /* If DC_EXEC is in flag, do not want to pass it to CheckFlatLandBelow, because of a nice bug
00957    * for detail info, see:
00958    * https://sourceforge.net/tracker/index.php?func=detail&aid=1029064&group_id=103924&atid=636365 */
00959   CommandCost ret = CheckFlatLandBelow(tile_org, w_org, h_org, flags & ~DC