tron@2186: /* $Id$ */ tron@2186: rubidium@4549: /** @file station_cmd.c */ celestar@2213: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" celestar@5573: #include "bridge_map.h" tron@1299: #include "debug.h" tron@2163: #include "functions.h" tron@3315: #include "station_map.h" tron@1363: #include "table/sprites.h" tron@507: #include "table/strings.h" tron@679: #include "map.h" tron@1209: #include "tile.h" truelight@0: #include "station.h" truelight@0: #include "gfx.h" truelight@0: #include "window.h" truelight@0: #include "viewport.h" truelight@0: #include "command.h" truelight@0: #include "town.h" truelight@0: #include "vehicle.h" truelight@0: #include "news.h" truelight@0: #include "saveload.h" truelight@0: #include "economy.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@5838: #include "helpers.hpp" 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: KUDr@5916: 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: /** KUDr@5929: * Clean up a station by clearing vehicle orders and invalidating windows. KUDr@5929: * Aircraft-Hangar orders need special treatment here, as the hangars are KUDr@5929: * actually part of a station (tiletype is STATION), but the order type KUDr@5929: * is OT_GOTO_DEPOT. KUDr@5929: * @param st Station to be deleted KUDr@5929: */ KUDr@5916: Station::~Station() truelight@1272: { KUDr@5916: DEBUG(station, cDebugCtorLevel, "I-%3d", index); truelight@1272: KUDr@5916: DeleteName(string_id); 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: free(speclist); KUDr@5916: xy = 0; truelight@1272: } truelight@1272: KUDr@5916: void* Station::operator new(size_t size) peter1138@3587: { KUDr@5916: Station *st = AllocateRaw(); KUDr@5916: return st; KUDr@5916: } peter1138@3587: KUDr@5916: void* Station::operator new(size_t size, int st_idx) KUDr@5916: { KUDr@5916: if (!AddBlockIfNeeded(&_Station_pool, st_idx)) KUDr@5916: error("Stations: failed loading savegame: too many stations"); KUDr@5916: KUDr@5916: Station *st = GetStation(st_idx); KUDr@5916: return st; KUDr@5916: } KUDr@5916: KUDr@5916: void Station::operator delete(void *p) KUDr@5916: { KUDr@5916: } KUDr@5916: KUDr@5916: void Station::operator delete(void *p, int st_idx) KUDr@5916: { KUDr@5916: } KUDr@5916: KUDr@5972: /** Called when new facility is built on the station. If it is the first facility KUDr@5972: * 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) { KUDr@5916: InvalidateWindowWidget(WC_STATION_VIEW, index, 1); KUDr@5916: KUDr@5916: MarkAllViewportsDirty( KUDr@5916: sign.left - 6, KUDr@5916: sign.top, KUDr@5916: sign.left + (sign.width_1 << 2) + 12, KUDr@5916: sign.top + 48); peter1138@3587: } peter1138@3587: } peter1138@3587: KUDr@5916: void Station::MarkTilesDirty() 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: 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: KUDr@5916: /*static*/ Station *Station::AllocateRaw(void) truelight@0: { truelight@1272: Station *st = NULL; truelight@0: truelight@4346: /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. truelight@4346: * TODO - This is just a temporary stage, this will be removed. */ tron@4980: for (st = GetStation(0); st != NULL; st = (st->index + 1U < GetStationPoolSize()) ? GetStation(st->index + 1U) : NULL) { truelight@4346: if (!IsValidStation(st)) { celestar@1551: StationID index = st->index; truelight@1272: truelight@1272: memset(st, 0, sizeof(Station)); truelight@1272: st->index = index; truelight@1272: return st; truelight@0: } truelight@0: } truelight@0: truelight@1272: /* Check if we can add a block to the pool */ KUDr@5916: if (AddBlockToPool(&_Station_pool)) return AllocateRaw(); truelight@1272: truelight@1272: _error_message = STR_3008_TOO_MANY_STATIONS_LOADING; truelight@1272: return NULL; truelight@0: } KUDr@5927: KUDr@5927: 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@6147: return (this->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: /** Allocates a new RoadStop onto the pool, or recycles an unsed one celestar@5959: * @return a pointer to the new roadstop celestar@5959: */ celestar@5959: void *RoadStop::operator new(size_t size) celestar@5959: { celestar@5959: RoadStop *rs = AllocateRaw(); celestar@5959: return rs; celestar@5959: } celestar@5959: celestar@5959: /** Gets a RoadStop with a given index and allocates it when needed celestar@5959: * @return a pointer to the roadstop celestar@5959: */ celestar@5959: void *RoadStop::operator new(size_t size, int index) celestar@5959: { celestar@5959: if (!AddBlockIfNeeded(&_RoadStop_pool, index)) { celestar@5959: error("RoadStops: failed loading savegame: too many RoadStops"); celestar@5959: } celestar@5959: celestar@5959: RoadStop *rs = GetRoadStop(index); celestar@5959: return rs; celestar@5959: } celestar@5959: celestar@5959: void RoadStop::operator delete(void *p) celestar@5959: { celestar@5959: } celestar@5959: celestar@5959: void RoadStop::operator delete(void *p, int index) celestar@5959: { 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: { 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) { celestar@5959: 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: celestar@5959: xy = INVALID_TILE; celestar@5959: } celestar@5959: celestar@5959: celestar@5959: /** Low-level function for allocating a RoadStop on the pool */ celestar@5959: RoadStop *RoadStop::AllocateRaw( void ) celestar@5959: { celestar@5959: RoadStop *rs; celestar@5959: celestar@5959: /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. celestar@5959: * TODO - This is just a temporary stage, this will be removed. */ celestar@5959: for (rs = GetRoadStop(0); rs != NULL; rs = (rs->index + 1U < GetRoadStopPoolSize()) ? GetRoadStop(rs->index + 1U) : NULL) { celestar@6086: if (!rs->IsValid()) { celestar@5959: RoadStopID index = rs->index; celestar@5959: celestar@5959: memset(rs, 0, sizeof(*rs)); celestar@5959: rs->index = index; celestar@5959: celestar@5959: return rs; celestar@5959: } celestar@5959: } celestar@5959: celestar@5959: /* Check if we can add a block to the pool */ celestar@5959: if (AddBlockToPool(&_RoadStop_pool)) return AllocateRaw(); celestar@5959: celestar@5959: return NULL; celestar@5959: } celestar@6086: celestar@6086: /** Determines whether a RoadStop is a valid (i.e. existing) one */ celestar@6086: bool RoadStop::IsValid() const celestar@6086: { celestar@6086: return xy != INVALID_TILE; celestar@6086: } rubidium@6316: 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@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@6316: /* Find the first free bay. If the bit is set, the bay is free. */ rubidium@6316: for (uint bay_nr = 0; bay_nr < MAX_BAY_COUNT; bay_nr++) { rubidium@6316: if (HASBIT(status, bay_nr)) { rubidium@6316: CLRBIT(status, bay_nr); rubidium@6316: return bay_nr; rubidium@6316: } rubidium@6316: } rubidium@6316: rubidium@6316: /* There has to be a free bay (precondition) */ rubidium@6316: NOT_REACHED(); rubidium@6316: return 0; rubidium@6316: } rubidium@6316: rubidium@6316: /** 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); rubidium@6316: 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: { rubidium@6316: 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@6316: SB(status, 7, 1, !!busy); rubidium@6316: }