tron@2186: /* $Id$ */ tron@2186: celestar@6447: /** @file station.cpp */ celestar@2213: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" celestar@5573: #include "bridge_map.h" tron@1299: #include "debug.h" tron@3315: #include "station_map.h" tron@1363: #include "table/sprites.h" tron@507: #include "table/strings.h" tron@679: #include "map.h" truelight@0: #include "station.h" truelight@0: #include "viewport.h" truelight@0: #include "town.h" truelight@0: #include "vehicle.h" truelight@0: #include "news.h" truelight@0: #include "saveload.h" truelight@0: #include "player.h" truelight@0: #include "airport.h" darkvater@405: #include "sprite.h" truelight@1313: #include "depot.h" bjarni@2676: #include "train.h" tron@3111: #include "water_map.h" belugas@3515: #include "industry_map.h" peter1138@3754: #include "newgrf_callbacks.h" peter1138@3754: #include "newgrf_station.h" KUDr@3900: #include "yapf/yapf.h" rubidium@4261: #include "date.h" rubidium@7965: #include "cargotype.h" rubidium@7965: #include "roadveh.h" smatz@8512: #include "station_gui.h" rubidium@8619: #include "zoom_func.h" rubidium@8627: #include "functions.h" rubidium@8627: #include "window_func.h" truelight@0: KUDr@5916: Station::Station(TileIndex tile) KUDr@5916: { KUDr@5916: DEBUG(station, cDebugCtorLevel, "I+%3d", index); KUDr@5583: KUDr@5916: xy = tile; KUDr@5916: airport_tile = dock_tile = train_tile = 0; KUDr@5916: bus_stops = truck_stops = NULL; KUDr@5916: had_vehicle_of_type = 0; KUDr@5916: time_since_load = 255; KUDr@5916: time_since_unload = 255; KUDr@5916: delete_ctr = 0; KUDr@5916: facilities = 0; KUDr@5916: rubidium@6585: last_vehicle_type = VEH_INVALID; KUDr@5916: KUDr@5972: random_bits = 0; // Random() must be called when station is really built (DC_EXEC) KUDr@5916: waiting_triggers = 0; KUDr@5916: } KUDr@5583: truelight@1272: /** belugas@6916: * Clean up a station by clearing vehicle orders and invalidating windows. belugas@6916: * Aircraft-Hangar orders need special treatment here, as the hangars are belugas@6916: * actually part of a station (tiletype is STATION), but the order type belugas@6916: * is OT_GOTO_DEPOT. belugas@6916: */ KUDr@5916: Station::~Station() truelight@1272: { KUDr@5916: DEBUG(station, cDebugCtorLevel, "I-%3d", index); truelight@1272: rubidium@7909: DeleteName(this->string_id); rubidium@7909: free(this->speclist); rubidium@7909: rubidium@7909: if (CleaningPool()) return; rubidium@7909: rubidium@7949: while (!loading_vehicles.empty()) { rubidium@7949: loading_vehicles.front()->LeaveStation(); rubidium@7949: } rubidium@7949: KUDr@5916: MarkDirty(); KUDr@5916: RebuildStationLists(); KUDr@5916: InvalidateWindowClasses(WC_STATION_LIST); KUDr@5916: KUDr@5916: DeleteWindowById(WC_STATION_VIEW, index); KUDr@5916: KUDr@5916: /* Now delete all orders that go to the station */ KUDr@5916: RemoveOrderFromAllVehicles(OT_GOTO_STATION, index); KUDr@5916: KUDr@5929: /* Subsidies need removal as well */ KUDr@5916: DeleteSubsidyWithStation(index); KUDr@5916: KUDr@5916: xy = 0; rubidium@7506: rubidium@7506: for (CargoID c = 0; c < NUM_CARGO; c++) { rubidium@7506: goods[c].cargo.Truncate(0); rubidium@7506: } rubidium@7872: } rubidium@7872: rubidium@7965: rubidium@7965: /** rubidium@7965: * Get the primary road stop (the first road stop) that the given vehicle can load/unload. rubidium@7965: * @param v the vehicle to get the first road stop for rubidium@7965: * @return the first roadstop that this vehicle can load at rubidium@7965: */ rubidium@7965: RoadStop *Station::GetPrimaryRoadStop(const Vehicle *v) const rubidium@7965: { rubidium@7965: RoadStop *rs = this->GetPrimaryRoadStop(IsCargoInClass(v->cargo_type, CC_PASSENGERS) ? RoadStop::BUS : RoadStop::TRUCK); rubidium@7965: rubidium@7965: for (; rs != NULL; rs = rs->next) { rubidium@7965: /* The vehicle cannot go to this roadstop (different roadtype) */ rubidium@7965: if ((GetRoadTypes(rs->xy) & v->u.road.compatible_roadtypes) == ROADTYPES_NONE) continue; rubidium@7965: /* The vehicle is articulated and can therefor not go the a standard road stop */ rubidium@7965: if (IsStandardRoadStopTile(rs->xy) && RoadVehHasArticPart(v)) continue; rubidium@7965: rubidium@7965: /* The vehicle can actually go to this road stop. So, return it! */ rubidium@7965: break; rubidium@7965: } rubidium@7965: rubidium@7965: return rs; rubidium@7965: } rubidium@7965: KUDr@5972: /** Called when new facility is built on the station. If it is the first facility belugas@6916: * it initializes also 'xy' and 'random_bits' members */ KUDr@5972: void Station::AddFacility(byte new_facility_bit, TileIndex facil_xy) KUDr@5972: { KUDr@5972: if (facilities == 0) { KUDr@5972: xy = facil_xy; KUDr@5972: random_bits = Random(); KUDr@5972: } KUDr@5972: facilities |= new_facility_bit; KUDr@5972: owner = _current_player; KUDr@5972: build_date = _date; KUDr@5972: } KUDr@5972: KUDr@5916: void Station::MarkDirty() const KUDr@5916: { KUDr@5916: if (sign.width_1 != 0) { smatz@8512: InvalidateWindowWidget(WC_STATION_VIEW, index, SVW_CAPTION); KUDr@5916: truelight@7196: /* We use ZOOM_LVL_MAX here, as every viewport can have an other zoom, truelight@7196: * and there is no way for us to know which is the biggest. So make the truelight@7196: * biggest area dirty, and we are safe for sure. */ KUDr@5916: MarkAllViewportsDirty( KUDr@5916: sign.left - 6, KUDr@5916: sign.top, truelight@7196: sign.left + ScaleByZoom(sign.width_1 + 12, ZOOM_LVL_MAX), truelight@7196: sign.top + ScaleByZoom(12, ZOOM_LVL_MAX)); peter1138@3587: } peter1138@3587: } peter1138@3587: peter1138@7319: void Station::MarkTilesDirty(bool cargo_change) const truelight@1284: { KUDr@5916: TileIndex tile = train_tile; Darkvater@4910: int w, h; peter1138@4904: KUDr@5929: /* XXX No station is recorded as 0, not INVALID_TILE... */ peter1138@4904: if (tile == 0) return; peter1138@4904: peter1138@7319: /* cargo_change is set if we're refreshing the tiles due to cargo moving peter1138@7319: * around. */ peter1138@7319: if (cargo_change) { peter1138@7319: /* Don't waste time updating if there are no custom station graphics peter1138@7319: * that might change. Even if there are custom graphics, they might peter1138@7319: * not change. Unfortunately we have no way of telling. */ peter1138@7319: if (this->num_specs == 0) return; peter1138@7319: } peter1138@7319: KUDr@5916: for (h = 0; h < trainst_h; h++) { KUDr@5916: for (w = 0; w < trainst_w; w++) { KUDr@5916: if (TileBelongsToRailStation(tile)) { peter1138@4904: MarkTileDirtyByTile(tile); peter1138@4904: } peter1138@4904: tile += TileDiffXY(1, 0); peter1138@4904: } peter1138@4904: tile += TileDiffXY(-w, 1); peter1138@4904: } peter1138@4904: } peter1138@4904: KUDr@5916: bool Station::TileBelongsToRailStation(TileIndex tile) const celestar@1217: { KUDr@5916: return IsTileType(tile, MP_STATION) && GetStationIndex(tile) == index && IsRailwayStation(tile); celestar@1217: } celestar@1217: celestar@6324: /** Obtain the length of a platform celestar@6324: * @pre tile must be a railway station tile celestar@6324: * @param tile A tile that contains the platform in question belugas@6916: * @return The length of the platform celestar@6324: */ celestar@6324: uint Station::GetPlatformLength(TileIndex tile) const celestar@6324: { celestar@6324: TileIndex t; celestar@6324: TileIndexDiff delta; celestar@6324: uint len = 0; celestar@6324: assert(TileBelongsToRailStation(tile)); celestar@6324: celestar@6324: delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); celestar@6324: celestar@6324: t = tile; celestar@6324: do { celestar@6324: t -= delta; celestar@6324: len++; celestar@6324: } while (IsCompatibleTrainStationTile(t, tile)); celestar@6324: celestar@6324: t = tile; celestar@6324: do { celestar@6324: t += delta; celestar@6324: len++; celestar@6324: } while (IsCompatibleTrainStationTile(t, tile)); celestar@6324: celestar@6324: return len - 1; celestar@6324: } celestar@6324: celestar@6324: /** Determines the REMAINING length of a platform, starting at (and including) celestar@6324: * the given tile. celestar@6324: * @param tile the tile from which to start searching. Must be a railway station tile celestar@6324: * @param dir The direction in which to search. celestar@6324: * @return The platform length celestar@6324: */ celestar@6324: uint Station::GetPlatformLength(TileIndex tile, DiagDirection dir) const celestar@6324: { celestar@6324: TileIndex start_tile = tile; celestar@6324: uint length = 0; celestar@6324: assert(IsRailwayStationTile(tile)); celestar@6324: assert(dir < DIAGDIR_END); celestar@6324: celestar@6324: do { celestar@6324: length ++; celestar@6324: tile += TileOffsByDiagDir(dir); celestar@6324: } while (IsCompatibleTrainStationTile(tile, start_tile)); celestar@6324: celestar@6324: return length; celestar@6324: } celestar@6324: celestar@6147: /** Determines whether a station is a buoy only. celestar@6147: * @todo Ditch this encoding of buoys celestar@6147: */ celestar@6147: bool Station::IsBuoy() const celestar@6147: { celestar@6323: return (had_vehicle_of_type & HVOT_BUOY) != 0; celestar@6147: } celestar@6147: KUDr@5927: KUDr@5927: /************************************************************************/ KUDr@5927: /* StationRect implementation */ KUDr@5927: /************************************************************************/ KUDr@5927: KUDr@5927: StationRect::StationRect() KUDr@5927: { KUDr@5927: MakeEmpty(); KUDr@5927: } KUDr@5927: KUDr@5927: void StationRect::MakeEmpty() KUDr@5927: { KUDr@5927: left = top = right = bottom = 0; KUDr@5927: } KUDr@5927: celestar@6161: /** celestar@6161: * Determines whether a given point (x, y) is within a certain distance of celestar@6161: * the station rectangle. celestar@6161: * @note x and y are in Tile coordinates celestar@6161: * @param x X coordinate celestar@6161: * @param y Y coordinate celestar@6161: * @param distance The maxmium distance a point may have (L1 norm) celestar@6161: * @return true if the point is within distance tiles of the station rectangle celestar@6161: */ celestar@6161: bool StationRect::PtInExtendedRect(int x, int y, int distance) const KUDr@5927: { celestar@6161: return (left - distance <= x && x <= right + distance && top - distance <= y && y <= bottom + distance); KUDr@5927: } KUDr@5927: KUDr@5927: bool StationRect::IsEmpty() const KUDr@5927: { KUDr@5927: return (left == 0 || left > right || top > bottom); KUDr@5927: } KUDr@5927: KUDr@5927: bool StationRect::BeforeAddTile(TileIndex tile, StationRectMode mode) KUDr@5927: { KUDr@5927: int x = TileX(tile); KUDr@5927: int y = TileY(tile); KUDr@5927: if (IsEmpty()) { KUDr@5929: /* we are adding the first station tile */ KUDr@5927: left = right = x; KUDr@5927: top = bottom = y; KUDr@5929: celestar@6161: } else if (!PtInExtendedRect(x, y)) { KUDr@5929: /* current rect is not empty and new point is outside this rect */ KUDr@5929: /* make new spread-out rectangle */ KUDr@5927: Rect new_rect = {min(x, left), min(y, top), max(x, right), max(y, bottom)}; KUDr@5929: KUDr@5929: /* check new rect dimensions against preset max */ KUDr@5927: int w = new_rect.right - new_rect.left + 1; KUDr@5927: int h = new_rect.bottom - new_rect.top + 1; KUDr@5927: if (mode != ADD_FORCE && (w > _patches.station_spread || h > _patches.station_spread)) { KUDr@5927: assert(mode != ADD_TRY); KUDr@5927: _error_message = STR_306C_STATION_TOO_SPREAD_OUT; KUDr@5927: return false; KUDr@5927: } KUDr@5929: KUDr@5929: /* spread-out ok, return true */ KUDr@5927: if (mode != ADD_TEST) { KUDr@5929: /* we should update the station rect */ KUDr@5927: *this = new_rect; KUDr@5927: } KUDr@5927: } else { KUDr@5927: ; // new point is inside the rect, we don't need to do anything KUDr@5927: } KUDr@5927: return true; KUDr@5927: } KUDr@5927: KUDr@5927: bool StationRect::BeforeAddRect(TileIndex tile, int w, int h, StationRectMode mode) KUDr@5927: { KUDr@5927: return BeforeAddTile(tile, mode) && BeforeAddTile(TILE_ADDXY(tile, w - 1, h - 1), mode); KUDr@5927: } KUDr@5927: KUDr@5927: /*static*/ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a) KUDr@5927: { KUDr@5927: TileIndex top_left = TileXY(left_a, top_a); KUDr@5927: int width = right_a - left_a + 1; KUDr@5927: int height = bottom_a - top_a + 1; KUDr@5929: KUDr@5927: BEGIN_TILE_LOOP(tile, width, height, top_left) KUDr@5927: if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true; KUDr@5927: END_TILE_LOOP(tile, width, height, top_left); KUDr@5929: KUDr@5927: return false; KUDr@5927: } KUDr@5927: KUDr@5927: bool StationRect::AfterRemoveTile(Station *st, TileIndex tile) KUDr@5927: { KUDr@5927: int x = TileX(tile); KUDr@5927: int y = TileY(tile); KUDr@5927: KUDr@5929: /* look if removed tile was on the bounding rect edge KUDr@5929: * and try to reduce the rect by this edge KUDr@5929: * do it until we have empty rect or nothing to do */ KUDr@5927: for (;;) { KUDr@5929: /* check if removed tile is on rect edge */ KUDr@5927: bool left_edge = (x == left); KUDr@5927: bool right_edge = (x == right); KUDr@5927: bool top_edge = (y == top); KUDr@5927: bool bottom_edge = (y == bottom); KUDr@5929: KUDr@5929: /* can we reduce the rect in either direction? */ KUDr@5929: bool reduce_x = ((left_edge || right_edge) && !ScanForStationTiles(st->index, x, top, x, bottom)); KUDr@5929: bool reduce_y = ((top_edge || bottom_edge) && !ScanForStationTiles(st->index, left, y, right, y)); KUDr@5927: if (!(reduce_x || reduce_y)) break; // nothing to do (can't reduce) KUDr@5929: KUDr@5927: if (reduce_x) { KUDr@5929: /* reduce horizontally */ KUDr@5927: if (left_edge) { KUDr@5929: /* move left edge right */ KUDr@5927: left = x = x + 1; KUDr@5927: } else { KUDr@5929: /* move right edge left */ KUDr@5927: right = x = x - 1; KUDr@5927: } KUDr@5927: } KUDr@5927: if (reduce_y) { KUDr@5929: /* reduce vertically */ KUDr@5927: if (top_edge) { KUDr@5929: /* move top edge down */ KUDr@5927: top = y = y + 1; KUDr@5927: } else { KUDr@5929: /* move bottom edge up */ KUDr@5927: bottom = y = y - 1; KUDr@5927: } KUDr@5927: } KUDr@5929: KUDr@5927: if (left > right || top > bottom) { KUDr@5929: /* can't continue, if the remaining rectangle is empty */ KUDr@5927: MakeEmpty(); KUDr@5927: return true; // empty remaining rect KUDr@5927: } KUDr@5927: } KUDr@5927: return false; // non-empty remaining rect KUDr@5927: } KUDr@5927: KUDr@5927: bool StationRect::AfterRemoveRect(Station *st, TileIndex tile, int w, int h) KUDr@5927: { celestar@6161: assert(PtInExtendedRect(TileX(tile), TileY(tile))); celestar@6161: assert(PtInExtendedRect(TileX(tile) + w - 1, TileY(tile) + h - 1)); KUDr@5929: KUDr@5929: bool empty = AfterRemoveTile(st, tile); KUDr@5927: if (w != 1 || h != 1) empty = empty || AfterRemoveTile(st, TILE_ADDXY(tile, w - 1, h - 1)); KUDr@5927: return empty; KUDr@5927: } KUDr@5927: KUDr@5927: StationRect& StationRect::operator = (Rect src) KUDr@5927: { KUDr@5927: left = src.left; KUDr@5927: top = src.top; KUDr@5927: right = src.right; KUDr@5927: bottom = src.bottom; KUDr@5927: return *this; KUDr@5927: } KUDr@5927: celestar@5959: celestar@5959: /************************************************************************/ celestar@5959: /* RoadStop implementation */ celestar@5959: /************************************************************************/ celestar@5959: celestar@5959: /** Initializes a RoadStop */ tron@5967: RoadStop::RoadStop(TileIndex tile) : tron@5964: xy(tile), tron@5964: status(3), // stop is free tron@5964: num_vehicles(0), tron@6118: next(NULL) celestar@5959: { tron@5967: DEBUG(ms, cDebugCtorLevel, "I+ at %d[0x%x]", tile, tile); celestar@5959: } celestar@5959: celestar@5959: /** De-Initializes a RoadStops. This includes clearing all slots that vehicles might celestar@5959: * have and unlinks it from the linked list of road stops at the given station celestar@5959: */ celestar@5959: RoadStop::~RoadStop() celestar@5959: { rubidium@7916: if (CleaningPool()) return; rubidium@7916: celestar@5959: /* Clear the slot assignment of all vehicles heading for this road stop */ celestar@5959: if (num_vehicles != 0) { tron@5964: Vehicle *v; tron@5964: celestar@5959: FOR_ALL_VEHICLES(v) { rubidium@6585: if (v->type == VEH_ROAD && v->u.road.slot == this) ClearSlot(v); celestar@5959: } celestar@5959: } celestar@5959: assert(num_vehicles == 0); celestar@5959: tron@5967: DEBUG(ms, cDebugCtorLevel , "I- at %d[0x%x]", xy, xy); celestar@5959: rubidium@7873: xy = 0; celestar@5959: } celestar@6086: rubidium@6316: /** Checks whether there is a free bay in this road stop */ rubidium@6316: bool RoadStop::HasFreeBay() const rubidium@6316: { rubidium@6316: return GB(status, 0, MAX_BAY_COUNT) != 0; rubidium@6316: } rubidium@6316: rubidium@6338: /** Checks whether the given bay is free in this road stop */ rubidium@6338: bool RoadStop::IsFreeBay(uint nr) const rubidium@6338: { rubidium@6338: assert(nr < MAX_BAY_COUNT); skidd13@8424: return HasBit(status, nr); rubidium@6338: } rubidium@6338: rubidium@6316: /** rubidium@6316: * Allocates a bay rubidium@6316: * @return the allocated bay number rubidium@6316: * @pre this->HasFreeBay() rubidium@6316: */ rubidium@6316: uint RoadStop::AllocateBay() rubidium@6316: { rubidium@6331: assert(HasFreeBay()); rubidium@6331: rubidium@6316: /* Find the first free bay. If the bit is set, the bay is free. */ rubidium@6331: uint bay_nr = 0; skidd13@8424: while (!HasBit(status, bay_nr)) bay_nr++; rubidium@6316: skidd13@8425: ClrBit(status, bay_nr); rubidium@6331: return bay_nr; rubidium@6316: } rubidium@6316: rubidium@6316: /** rubidium@6338: * Allocates a bay in a drive-through road stop rubidium@6338: * @param nr the number of the bay to allocate rubidium@6338: */ rubidium@6338: void RoadStop::AllocateDriveThroughBay(uint nr) rubidium@6338: { rubidium@6338: assert(nr < MAX_BAY_COUNT); skidd13@8425: ClrBit(status, nr); rubidium@6338: } rubidium@6338: rubidium@6338: /** rubidium@6316: * Frees the given bay rubidium@6316: * @param nr the number of the bay to free rubidium@6316: */ rubidium@6316: void RoadStop::FreeBay(uint nr) rubidium@6316: { rubidium@6316: assert(nr < MAX_BAY_COUNT); skidd13@8427: SetBit(status, nr); rubidium@6316: } rubidium@6316: rubidium@6316: rubidium@6316: /** Checks whether the entrance of the road stop is occupied by a vehicle */ rubidium@6316: bool RoadStop::IsEntranceBusy() const rubidium@6316: { skidd13@8424: return HasBit(status, 7); rubidium@6316: } rubidium@6316: rubidium@6316: /** Makes an entrance occupied or free */ rubidium@6316: void RoadStop::SetEntranceBusy(bool busy) rubidium@6316: { rubidium@6331: SB(status, 7, 1, busy); rubidium@6316: } rubidium@7965: rubidium@7965: /** rubidium@7965: * Get the next road stop accessible by this vehicle. rubidium@7965: * @param v the vehicle to get the next road stop for. rubidium@7965: * @return the next road stop accessible. rubidium@7965: */ rubidium@7965: RoadStop *RoadStop::GetNextRoadStop(const Vehicle *v) const rubidium@7965: { rubidium@7965: for (RoadStop *rs = this->next; rs != NULL; rs = rs->next) { rubidium@7965: /* The vehicle cannot go to this roadstop (different roadtype) */ rubidium@7967: if ((GetRoadTypes(rs->xy) & v->u.road.compatible_roadtypes) == ROADTYPES_NONE) continue; rubidium@7965: /* The vehicle is articulated and can therefor not go the a standard road stop */ rubidium@7965: if (IsStandardRoadStopTile(rs->xy) && RoadVehHasArticPart(v)) continue; rubidium@7965: rubidium@7965: /* The vehicle can actually go to this road stop. So, return it! */ rubidium@7965: return rs; rubidium@7965: } rubidium@7965: rubidium@7965: return NULL; rubidium@7965: }