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