KUDr@5665: /* $Id$ */ KUDr@5665: KUDr@5665: /** @file station_cmd.c */ 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 "functions.h" KUDr@5665: #include "station_map.h" KUDr@5665: #include "table/sprites.h" KUDr@5665: #include "table/strings.h" KUDr@5665: #include "map.h" KUDr@5665: #include "tile.h" KUDr@5665: #include "station.h" KUDr@5665: #include "gfx.h" KUDr@5665: #include "window.h" KUDr@5665: #include "viewport.h" KUDr@5665: #include "command.h" KUDr@5665: #include "town.h" KUDr@5665: #include "vehicle.h" KUDr@5665: #include "news.h" KUDr@5665: #include "saveload.h" KUDr@5665: #include "economy.h" KUDr@5665: #include "player.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" KUDr@5665: #include "date.h" KUDr@5665: #include "helpers.hpp" KUDr@5665: 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: KUDr@5665: 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: /** KUDr@5678: * Clean up a station by clearing vehicle orders and invalidating windows. KUDr@5678: * Aircraft-Hangar orders need special treatment here, as the hangars are KUDr@5678: * actually part of a station (tiletype is STATION), but the order type KUDr@5678: * is OT_GOTO_DEPOT. KUDr@5678: * @param st Station to be deleted KUDr@5678: */ KUDr@5665: Station::~Station() KUDr@5665: { KUDr@5665: DEBUG(station, cDebugCtorLevel, "I-%3d", index); KUDr@5665: KUDr@5665: DeleteName(string_id); 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: free(speclist); KUDr@5665: xy = 0; KUDr@5665: } KUDr@5665: KUDr@5665: void* Station::operator new(size_t size) KUDr@5665: { KUDr@5665: Station *st = AllocateRaw(); KUDr@5665: return st; KUDr@5665: } KUDr@5665: KUDr@5665: void* Station::operator new(size_t size, int st_idx) KUDr@5665: { KUDr@5665: if (!AddBlockIfNeeded(&_Station_pool, st_idx)) KUDr@5665: error("Stations: failed loading savegame: too many stations"); KUDr@5665: KUDr@5665: Station *st = GetStation(st_idx); KUDr@5665: return st; KUDr@5665: } KUDr@5665: KUDr@5665: void Station::operator delete(void *p) KUDr@5665: { KUDr@5665: } KUDr@5665: KUDr@5665: void Station::operator delete(void *p, int st_idx) KUDr@5665: { KUDr@5665: } KUDr@5665: KUDr@5721: /** Called when new facility is built on the station. If it is the first facility KUDr@5721: * 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) { KUDr@5665: InvalidateWindowWidget(WC_STATION_VIEW, index, 1); KUDr@5665: KUDr@5665: MarkAllViewportsDirty( KUDr@5665: sign.left - 6, KUDr@5665: sign.top, KUDr@5665: sign.left + (sign.width_1 << 2) + 12, KUDr@5665: sign.top + 48); KUDr@5665: } KUDr@5665: } KUDr@5665: KUDr@5665: void Station::MarkTilesDirty() 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: 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: KUDr@5665: /*static*/ Station *Station::AllocateRaw(void) KUDr@5665: { KUDr@5665: Station *st = NULL; KUDr@5665: KUDr@5665: /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. KUDr@5665: * TODO - This is just a temporary stage, this will be removed. */ KUDr@5665: for (st = GetStation(0); st != NULL; st = (st->index + 1U < GetStationPoolSize()) ? GetStation(st->index + 1U) : NULL) { KUDr@5665: if (!IsValidStation(st)) { KUDr@5665: StationID index = st->index; KUDr@5665: KUDr@5665: memset(st, 0, sizeof(Station)); KUDr@5665: st->index = index; KUDr@5665: return st; KUDr@5665: } KUDr@5665: } KUDr@5665: KUDr@5665: /* Check if we can add a block to the pool */ KUDr@5665: if (AddBlockToPool(&_Station_pool)) return AllocateRaw(); KUDr@5665: KUDr@5665: _error_message = STR_3008_TOO_MANY_STATIONS_LOADING; KUDr@5665: return NULL; KUDr@5665: } KUDr@5676: KUDr@5676: 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: KUDr@5676: bool StationRect::PtInRectXY(int x, int y) const KUDr@5676: { KUDr@5676: return (left <= x && x <= right && top <= y && y <= bottom); 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: KUDr@5676: } else if (!PtInRectXY(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: { KUDr@5676: assert(PtInRectXY(TileX(tile), TileY(tile))); KUDr@5676: assert(PtInRectXY(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: /** Allocates a new RoadStop onto the pool, or recycles an unsed one celestar@5708: * @return a pointer to the new roadstop celestar@5708: */ celestar@5708: void *RoadStop::operator new(size_t size) celestar@5708: { celestar@5708: RoadStop *rs = AllocateRaw(); celestar@5708: return rs; celestar@5708: } celestar@5708: celestar@5708: /** Gets a RoadStop with a given index and allocates it when needed celestar@5708: * @return a pointer to the roadstop celestar@5708: */ celestar@5708: void *RoadStop::operator new(size_t size, int index) celestar@5708: { celestar@5708: if (!AddBlockIfNeeded(&_RoadStop_pool, index)) { celestar@5708: error("RoadStops: failed loading savegame: too many RoadStops"); celestar@5708: } celestar@5708: celestar@5708: RoadStop *rs = GetRoadStop(index); celestar@5708: return rs; celestar@5708: } celestar@5708: celestar@5708: void RoadStop::operator delete(void *p) celestar@5708: { celestar@5708: } celestar@5708: celestar@5708: void RoadStop::operator delete(void *p, int index) celestar@5708: { 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@5713: next(NULL), tron@5713: prev(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: { 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) { celestar@5708: if (v->type == VEH_Road && v->u.road.slot == this) ClearSlot(v); celestar@5708: } celestar@5708: } celestar@5708: assert(num_vehicles == 0); celestar@5708: celestar@5708: if (prev != NULL) prev->next = next; celestar@5708: if (next != NULL) next->prev = prev; celestar@5708: tron@5716: DEBUG(ms, cDebugCtorLevel , "I- at %d[0x%x]", xy, xy); celestar@5708: celestar@5708: xy = INVALID_TILE; celestar@5708: } celestar@5708: celestar@5708: celestar@5708: /** Low-level function for allocating a RoadStop on the pool */ celestar@5708: RoadStop *RoadStop::AllocateRaw( void ) celestar@5708: { celestar@5708: RoadStop *rs; celestar@5708: celestar@5708: /* We don't use FOR_ALL here, because FOR_ALL skips invalid items. celestar@5708: * TODO - This is just a temporary stage, this will be removed. */ celestar@5708: for (rs = GetRoadStop(0); rs != NULL; rs = (rs->index + 1U < GetRoadStopPoolSize()) ? GetRoadStop(rs->index + 1U) : NULL) { celestar@5708: if (!IsValidRoadStop(rs)) { celestar@5708: RoadStopID index = rs->index; celestar@5708: celestar@5708: memset(rs, 0, sizeof(*rs)); celestar@5708: rs->index = index; celestar@5708: celestar@5708: return rs; celestar@5708: } celestar@5708: } celestar@5708: celestar@5708: /* Check if we can add a block to the pool */ celestar@5708: if (AddBlockToPool(&_RoadStop_pool)) return AllocateRaw(); celestar@5708: celestar@5708: return NULL; celestar@5708: }