diff options
author | Peter Nelson <peter1138@openttd.org> | 2019-02-14 21:07:15 +0000 |
---|---|---|
committer | PeterN <peter@fuzzle.org> | 2019-03-09 16:33:47 +0000 |
commit | 8b1b3fd0f91fd30d09d3acaa65bd9a7d23d966cc (patch) | |
tree | d82e99507fa2035a84ccec7859c0267ade95cd35 | |
parent | 3542ed53d4b79df3018a3291db8d8e97e20db273 (diff) | |
download | openttd-8b1b3fd0f91fd30d09d3acaa65bd9a7d23d966cc.tar.xz |
Feature: Non-rectangular sparse station catchment area.
-rw-r--r-- | src/economy.cpp | 5 | ||||
-rw-r--r-- | src/industry.h | 1 | ||||
-rw-r--r-- | src/industry_cmd.cpp | 56 | ||||
-rw-r--r-- | src/saveload/afterload.cpp | 4 | ||||
-rw-r--r-- | src/script/api/script_industry.cpp | 4 | ||||
-rw-r--r-- | src/settings.cpp | 2 | ||||
-rw-r--r-- | src/station.cpp | 169 | ||||
-rw-r--r-- | src/station_base.h | 14 | ||||
-rw-r--r-- | src/station_cmd.cpp | 133 | ||||
-rw-r--r-- | src/station_func.h | 4 | ||||
-rw-r--r-- | src/subsidy.cpp | 14 | ||||
-rw-r--r-- | src/town.h | 1 | ||||
-rw-r--r-- | src/town_cmd.cpp | 25 |
13 files changed, 285 insertions, 147 deletions
diff --git a/src/economy.cpp b/src/economy.cpp index b02d4bd76..197298d9b 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -1049,11 +1049,6 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n if (ind->index == source) continue; - if (!_settings_game.station.serve_neutral_industries) { - /* If this industry is only served by its neutral station, check it's us. */ - if (ind->neutral_station != NULL && ind->neutral_station != st) continue; - } - uint cargo_index; for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) { if (cargo_type == ind->accepts_cargo[cargo_index]) break; diff --git a/src/industry.h b/src/industry.h index b7a954d40..4822976f2 100644 --- a/src/industry.h +++ b/src/industry.h @@ -63,6 +63,7 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { byte was_cargo_delivered; ///< flag that indicate this has been the closest industry chosen for cargo delivery by a station. see DeliverGoodsToIndustry PartOfSubsidyByte part_of_subsidy; ///< NOSAVE: is this industry a source/destination of a subsidy? + StationList stations_near; ///< NOSAVE: List of nearby stations. OwnerByte founder; ///< Founder of the industry Date construction_date; ///< Date of the construction of the industry diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index b1e48f76b..a4863ab12 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -182,6 +182,10 @@ Industry::~Industry() DeleteSubsidyWith(ST_INDUSTRY, this->index); CargoPacket::InvalidateAllFrom(ST_INDUSTRY, this->index); + + for (Station *st : this->stations_near) { + st->industries_near.erase(this); + } } /** @@ -191,7 +195,6 @@ Industry::~Industry() void Industry::PostDestructor(size_t index) { InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, 0); - Station::RecomputeIndustriesNearForAll(); } @@ -516,14 +519,6 @@ static bool TransportIndustryGoods(TileIndex tile) const IndustrySpec *indspec = GetIndustrySpec(i->type); bool moved_cargo = false; - StationFinder stations(i->location); - StationList neutral; - - if (i->neutral_station != NULL && !_settings_game.station.serve_neutral_industries) { - /* Industry has a neutral station. Use it and ignore any other nearby stations. */ - neutral.insert(i->neutral_station); - } - for (uint j = 0; j < lengthof(i->produced_cargo_waiting); j++) { uint cw = min(i->produced_cargo_waiting[j], 255); if (cw > indspec->minimal_cargo && i->produced_cargo[j] != CT_INVALID) { @@ -534,7 +529,7 @@ static bool TransportIndustryGoods(TileIndex tile) i->this_month_production[j] += cw; - uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, neutral.size() != 0 ? &neutral : stations.GetStations()); + uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, &i->stations_near); i->this_month_transported[j] += am; moved_cargo |= (am != 0); @@ -1651,6 +1646,37 @@ static void AdvertiseIndustryOpening(const Industry *ind) } /** + * Populate an industry's list of nearby stations, and if it accepts any cargo, also + * add the industry to each station's nearby industry list. + * @param ind Industry + */ +static void PopulateStationsNearby(Industry *ind) +{ + if (ind->neutral_station != NULL && !_settings_game.station.serve_neutral_industries) { + /* Industry has a neutral station. Use it and ignore any other nearby stations. */ + ind->stations_near.insert(ind->neutral_station); + ind->neutral_station->industries_near.clear(); + ind->neutral_station->industries_near.insert(ind); + return; + } + + /* Get our list of nearby stations. */ + FindStationsAroundTiles(ind->location, &ind->stations_near, false); + + /* Test if industry can accept cargo */ + uint cargo_index; + for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) { + if (ind->accepts_cargo[cargo_index] != CT_INVALID) break; + } + if (cargo_index >= lengthof(ind->accepts_cargo)) return; + + /* Cargo is accepted, add industry to nearby stations nearby industry list. */ + for (Station *st : ind->stations_near) { + st->industries_near.insert(ind); + } +} + +/** * Put an industry on the map. * @param i Just allocated poolitem, mostly empty. * @param tile North tile of the industry. @@ -1823,7 +1849,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type, } InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, 0); - Station::RecomputeIndustriesNearForAll(); + if (!_generating_world) PopulateStationsNearby(i); } /** @@ -2428,11 +2454,7 @@ static void CanCargoServiceIndustry(CargoID cargo, Industry *ind, bool *c_accept */ static int WhoCanServiceIndustry(Industry *ind) { - /* Find all stations within reach of the industry */ - StationList stations; - FindStationsAroundTiles(ind->location, &stations); - - if (stations.size() == 0) return 0; // No stations found at all => nobody services + if (ind->stations_near.size() == 0) return 0; // No stations found at all => nobody services const Vehicle *v; int result = 0; @@ -2468,7 +2490,7 @@ static int WhoCanServiceIndustry(Industry *ind) /* Same cargo produced by industry is dropped here => not serviced by vehicle v */ if ((o->GetUnloadType() & OUFB_UNLOAD) && !c_accepts) break; - if (stations.find(st) != stations.end()) { + if (ind->stations_near.find(st) != ind->stations_near.end()) { if (v->owner == _local_company) return 2; // Company services industry result = 1; // Competitor services industry } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index a29635eab..87e870056 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -285,7 +285,6 @@ static void InitializeWindowsAndCaches() GroupStatistics::UpdateAfterLoad(); - Station::RecomputeIndustriesNearForAll(); RebuildSubsidisedSourceAndDestinationCache(); /* Towns have a noise controlled number of airports system @@ -3104,6 +3103,9 @@ bool AfterLoadGame() FOR_ALL_INDUSTRIES(ind) if (ind->neutral_station != NULL) ind->neutral_station->industry = ind; } + /* Compute station catchment areas. This is needed here in case UpdateStationAcceptance is called below. */ + Station::RecomputeCatchmentForAll(); + /* Station acceptance is some kind of cache */ if (IsSavegameVersionBefore(SLV_127)) { Station *st; diff --git a/src/script/api/script_industry.cpp b/src/script/api/script_industry.cpp index b9025307b..68bb9003d 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -132,9 +132,7 @@ if (!IsValidIndustry(industry_id)) return -1; Industry *ind = ::Industry::Get(industry_id); - StationList stations; - ::FindStationsAroundTiles(ind->location, &stations); - return (int32)stations.size(); + return (int32)ind->stations_near.size(); } /* static */ int32 ScriptIndustry::GetDistanceManhattanToTile(IndustryID industry_id, TileIndex tile) diff --git a/src/settings.cpp b/src/settings.cpp index b93f4d706..d324ffc29 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1302,7 +1302,7 @@ static bool ChangeMaxHeightLevel(int32 p1) static bool StationCatchmentChanged(int32 p1) { - Station::RecomputeIndustriesNearForAll(); + Station::RecomputeCatchmentForAll(); return true; } diff --git a/src/station.cpp b/src/station.cpp index 2fc1bdb45..27063dcd7 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -23,6 +23,7 @@ #include "station_base.h" #include "roadstop_base.h" #include "industry.h" +#include "town.h" #include "core/random_func.hpp" #include "linkgraph/linkgraph.h" #include "linkgraph/linkgraphschedule.h" @@ -119,6 +120,9 @@ Station::~Station() } } + /* Remove station from industries and towns that reference it. */ + this->RemoveFromAllNearbyLists(); + /* Clear the persistent storage. */ delete this->airport.psa; @@ -262,6 +266,39 @@ void Station::MarkTilesDirty(bool cargo_change) const } /** + * Get the catchment size of an individual station tile. + * @param tile Station tile to get catchment size of. + * @param st Associated station of station tile. + * @pre IsTileType(tile, MP_STATION) + * @return The catchment size of the station tile. + */ +static uint GetTileCatchmentRadius(TileIndex tile, const Station *st) +{ + assert(IsTileType(tile, MP_STATION)); + + if (_settings_game.station.modified_catchment) { + switch (GetStationType(tile)) { + case STATION_RAIL: return CA_TRAIN; + case STATION_OILRIG: return CA_UNMODIFIED; + case STATION_AIRPORT: return st->airport.GetSpec()->catchment; + case STATION_TRUCK: return CA_TRUCK; + case STATION_BUS: return CA_BUS; + case STATION_DOCK: return CA_DOCK; + + default: NOT_REACHED(); + case STATION_BUOY: + case STATION_WAYPOINT: return CA_NONE; + } + } else { + switch (GetStationType(tile)) { + default: return CA_UNMODIFIED; + case STATION_BUOY: + case STATION_WAYPOINT: return CA_NONE; + } + } +} + +/** * Determines the catchment radius of the station * @return The radius */ @@ -305,85 +342,127 @@ Rect Station::GetCatchmentRect() const return ret; } -/** Rect and pointer to IndustryVector */ -struct RectAndIndustryVector { - Rect rect; ///< The rectangle to search the industries in. - IndustryList *industries_near; ///< The nearby industries. -}; - /** - * Callback function for Station::RecomputeIndustriesNear() - * Tests whether tile is an industry and possibly adds - * the industry to station's industries_near list. - * @param ind_tile tile to check - * @param user_data pointer to RectAndIndustryVector - * @return always false, we want to search all tiles + * Add nearby industry to station's industries_near list if it accepts cargo. + * @param ind Industry + * @param st Station */ -static bool FindIndustryToDeliver(TileIndex ind_tile, void *user_data) +static void AddIndustryToDeliver(Industry *ind, Station *st) { - /* Only process industry tiles */ - if (!IsTileType(ind_tile, MP_INDUSTRY)) return false; - - RectAndIndustryVector *riv = (RectAndIndustryVector *)user_data; - Industry *ind = Industry::GetByTile(ind_tile); - /* Don't check further if this industry is already in the list */ - if (riv->industries_near->find(ind) != riv->industries_near->end()) return false; - - /* Only process tiles in the station acceptance rectangle */ - int x = TileX(ind_tile); - int y = TileY(ind_tile); - if (x < riv->rect.left || x > riv->rect.right || y < riv->rect.top || y > riv->rect.bottom) return false; + if (st->industries_near.find(ind) != st->industries_near.end()) return; /* Include only industries that can accept cargo */ uint cargo_index; for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) { if (ind->accepts_cargo[cargo_index] != CT_INVALID) break; } - if (cargo_index >= lengthof(ind->accepts_cargo)) return false; + if (cargo_index >= lengthof(ind->accepts_cargo)) return; - riv->industries_near->insert(ind); + st->industries_near.insert(ind); +} + +/** + * Remove this station from the nearby stations lists of all towns and industries. + */ +void Station::RemoveFromAllNearbyLists() +{ + Town *t; + FOR_ALL_TOWNS(t) { t->stations_near.erase(this); } + Industry *i; + FOR_ALL_INDUSTRIES(i) { i->stations_near.erase(this); } +} +/** + * Test if the given town ID is covered by our catchment area. + * This is used when removing a house tile to determine if it was the last house tile + * within our catchment. + * @param t TownID to test. + * @return true if at least one house tile of TownID is covered. + */ +bool Station::CatchmentCoversTown(TownID t) const +{ + BitmapTileIterator it(this->catchment_tiles); + for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) { + if (IsTileType(tile, MP_HOUSE) && GetTownIndex(tile) == t) return true; + } return false; } /** - * Recomputes Station::industries_near, list of industries possibly - * accepting cargo in station's catchment radius + * Recompute tiles covered in our catchment area. + * This will additionally recompute nearby towns and industries. */ -void Station::RecomputeIndustriesNear() +void Station::RecomputeCatchment() { this->industries_near.clear(); - if (this->rect.IsEmpty()) return; + this->RemoveFromAllNearbyLists(); + + if (this->rect.IsEmpty()) { + this->catchment_tiles.Reset(); + return; + } + this->catchment_tiles.Initialize(GetCatchmentRect()); if (!_settings_game.station.serve_neutral_industries && this->industry != NULL) { /* Station is associated with an industry, so we only need to deliver to that industry. */ + TILE_AREA_LOOP(tile, this->industry->location) { + if (IsTileType(tile, MP_INDUSTRY) && GetIndustryIndex(tile) == this->industry->index) { + this->catchment_tiles.SetTile(tile); + } + } + /* The industry's stations_near may have been computed before its neutral station was built so clear and re-add here. */ + for (Station *st : this->industry->stations_near) { + st->industries_near.erase(this->industry); + } + this->industry->stations_near.clear(); + this->industry->stations_near.insert(this); this->industries_near.insert(this->industry); return; } - RectAndIndustryVector riv = { - this->GetCatchmentRect(), - &this->industries_near - }; + /* Loop finding all station tiles */ + TileArea ta(TileXY(this->rect.left, this->rect.top), TileXY(this->rect.right, this->rect.bottom)); + TILE_AREA_LOOP(tile, ta) { + if (!IsTileType(tile, MP_STATION) || GetStationIndex(tile) != this->index) continue; + + uint r = GetTileCatchmentRadius(tile, this); + if (r == CA_NONE) continue; + + /* This tile sub-loop doesn't need to test any tiles, they are simply added to the catchment set. */ + TileArea ta2(TileXY(max<int>(TileX(tile) - r, 0), max<int>(TileY(tile) - r, 0)), TileXY(min<int>(TileX(tile) + r, MapMaxX()), min<int>(TileY(tile) + r, MapMaxY()))); + TILE_AREA_LOOP(tile2, ta2) this->catchment_tiles.SetTile(tile2); + } - /* Compute maximum extent of acceptance rectangle wrt. station sign */ - TileIndex start_tile = this->xy; - uint max_radius = max( - max(DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.bottom))), - max(DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.bottom))) - ); + /* Search catchment tiles for towns and industries */ + BitmapTileIterator it(this->catchment_tiles); + for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) { + if (IsTileType(tile, MP_HOUSE)) { + Town *t = Town::GetByTile(tile); + t->stations_near.insert(this); + } + if (IsTileType(tile, MP_INDUSTRY)) { + Industry *i = Industry::GetByTile(tile); + + /* Ignore industry if it has a neutral station. It already can't be this station. */ + if (!_settings_game.station.serve_neutral_industries && i->neutral_station != NULL) continue; + + i->stations_near.insert(this); - CircularTileSearch(&start_tile, 2 * max_radius + 1, &FindIndustryToDeliver, &riv); + /* Add if we can deliver to this industry as well */ + AddIndustryToDeliver(i, this); + } + } } /** - * Recomputes Station::industries_near for all stations + * Recomputes catchment of all stations. + * This will additionally recompute nearby stations for all towns and industries. */ -/* static */ void Station::RecomputeIndustriesNearForAll() +/* static */ void Station::RecomputeCatchmentForAll() { Station *st; - FOR_ALL_STATIONS(st) st->RecomputeIndustriesNear(); + FOR_ALL_STATIONS(st) { st->RecomputeCatchment(); } } /************************************************************************/ diff --git a/src/station_base.h b/src/station_base.h index d1e62f614..a48daa653 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -19,6 +19,7 @@ #include "industry_type.h" #include "linkgraph/linkgraph_type.h" #include "newgrf_storage.h" +#include "bitmap_type.h" #include <map> #include <set> @@ -467,6 +468,8 @@ public: IndustryType indtype; ///< Industry type to get the name from + BitmapTileArea catchment_tiles; ///< NOSAVE: Set of individual tiles covered by catchment area + StationHadVehicleOfTypeByte had_vehicle_of_type; byte time_since_load; @@ -493,11 +496,18 @@ public: /* virtual */ uint GetPlatformLength(TileIndex tile, DiagDirection dir) const; /* virtual */ uint GetPlatformLength(TileIndex tile) const; - void RecomputeIndustriesNear(); - static void RecomputeIndustriesNearForAll(); + void RecomputeCatchment(); + static void RecomputeCatchmentForAll(); uint GetCatchmentRadius() const; Rect GetCatchmentRect() const; + bool CatchmentCoversTown(TownID t) const; + void RemoveFromAllNearbyLists(); + + inline bool TileIsInCatchment(TileIndex tile) const + { + return this->catchment_tiles.HasTile(tile); + } /* virtual */ inline bool TileBelongsToRailStation(TileIndex tile) const { diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 5218a75e9..2006243cb 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -527,7 +527,7 @@ CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad) * @param always_accepted bitmask of cargo accepted by houses and headquarters; can be NULL * @param ind Industry associated with neutral station (e.g. oil rig) or NULL */ -CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, CargoTypes *always_accepted, const Industry *ind) +CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, CargoTypes *always_accepted) { CargoArray acceptance; if (always_accepted != NULL) *always_accepted = 0; @@ -551,14 +551,9 @@ CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, Cargo for (int xc = x1; xc != x2; xc++) { TileIndex tile = TileXY(xc, yc); - if (!_settings_game.station.serve_neutral_industries) { - if (ind != NULL) { - if (!IsTileType(tile, MP_INDUSTRY)) continue; - if (Industry::GetByTile(tile) != ind) continue; - } else { - if (IsTileType(tile, MP_INDUSTRY) && Industry::GetByTile(tile)->neutral_station != NULL) continue; - } - } + /* Ignore industry if it has a neutral station. */ + if (!_settings_game.station.serve_neutral_industries && IsTileType(tile, MP_INDUSTRY) && Industry::GetByTile(tile)->neutral_station != NULL) continue; + AddAcceptedCargo(tile, acceptance, always_accepted); } } @@ -567,6 +562,24 @@ CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, Cargo } /** + * Get the acceptance of cargoes around the station in. + * @param st Station to get acceptance of. + * @param always_accepted bitmask of cargo accepted by houses and headquarters; can be NULL + */ +static CargoArray GetAcceptanceAroundStation(const Station *st, CargoTypes *always_accepted) +{ + CargoArray acceptance; + if (always_accepted != NULL) *always_accepted = 0; + + BitmapTileIterator it(st->catchment_tiles); + for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) { + AddAcceptedCargo(tile, acceptance, always_accepted); + } + + return acceptance; +} + +/** * Update the acceptance for a station. * @param st Station to update * @param show_msg controls whether to display a message that acceptance was changed. @@ -579,14 +592,7 @@ void UpdateStationAcceptance(Station *st, bool show_msg) /* And retrieve the acceptance. */ CargoArray acceptance; if (!st->rect.IsEmpty()) { - acceptance = GetAcceptanceAroundTiles( - TileXY(st->rect.left, st->rect.top), - st->rect.right - st->rect.left + 1, - st->rect.bottom - st->rect.top + 1, - st->GetCatchmentRadius(), - &st->always_accepted, - _settings_game.station.serve_neutral_industries ? NULL : st->industry - ); + acceptance = GetAcceptanceAroundStation(st, &st->always_accepted); } /* Adjust in case our station only accepts fewer kinds of goods */ @@ -736,7 +742,7 @@ static void DeleteStationIfEmpty(BaseStation *st) void Station::AfterStationTileSetChange(bool adding, StationType type) { this->UpdateVirtCoord(); - this->RecomputeIndustriesNear(); + this->RecomputeCatchment(); DirtyCompanyInfrastructureWindows(this->owner); if (adding) InvalidateWindowData(WC_STATION_LIST, this->owner, 0); @@ -1643,7 +1649,7 @@ CommandCost CmdRemoveFromRailStation(TileIndex start, DoCommandFlag flags, uint3 if (st->train_station.tile == INVALID_TILE) SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS); st->MarkTilesDirty(false); - st->RecomputeIndustriesNear(); + st->RecomputeCatchment(); } /* Now apply the rail cost to the number that we deleted */ @@ -1726,7 +1732,7 @@ static CommandCost RemoveRailStation(TileIndex tile, DoCommandFlag flags) Station *st = Station::GetByTile(tile); CommandCost cost = RemoveRailStation(st, flags, _price[PR_CLEAR_STATION_RAIL]); - if (flags & DC_EXEC) st->RecomputeIndustriesNear(); + if (flags & DC_EXEC) st->RecomputeCatchment(); return cost; } @@ -3215,9 +3221,8 @@ void TriggerWatchedCargoCallbacks(Station *st) if (cargoes == 0) return; /* Loop over all houses in the catchment. */ - Rect r = st->GetCatchmentRect(); - TileArea ta(TileXY(r.left, r.top), TileXY(r.right, r.bottom)); - TILE_AREA_LOOP(tile, ta) { + BitmapTileIterator it(st->catchment_tiles); + for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) { if (IsTileType(tile, MP_HOUSE)) { WatchedCargoCallback(tile, cargoes); } @@ -3623,7 +3628,7 @@ void IncreaseStats(Station *st, const Vehicle *front, StationID next_station_id) * As usage is not such an important figure anyway we just * ignore the additional cargo then.*/ IncreaseStats(st, v->cargo_type, next_station_id, v->refit_cap, - min(v->refit_cap, v->cargo.StoredCount()), EUM_INCREASE); + min(v->refit_cap, v->cargo.StoredCount()), EUM_INCREASE); } } } @@ -3789,57 +3794,63 @@ CommandCost CmdRenameStation(TileIndex tile, DoCommandFlag flags, uint32 p1, uin return CommandCost(); } +static void AddNearbyStationsByCatchment(TileIndex tile, StationList *stations, StationList &nearby) +{ + for (Station *st : nearby) { + if (st->TileIsInCatchment(tile)) stations->insert(st); + } +} + /** * Find all stations around a rectangular producer (industry, house, headquarter, ...) * * @param location The location/area of the producer * @param stations The list to store the stations in + * @param use_nearby Use nearby station list of industry/town associated with location.tile */ -void FindStationsAroundTiles(const TileArea &location, StationList *stations) +void FindStationsAroundTiles(const TileArea &location, StationList *stations, bool use_nearby) { - /* area to search = producer plus station catchment radius */ - uint max_rad = (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED); + if (use_nearby) { + /* Industries and towns maintain a list of nearby stations */ + if (IsTileType(location.tile, MP_INDUSTRY)) { + /* Industry nearby stations are already filtered by catchment. */ + stations = &Industry::GetByTile(location.tile)->stations_near; + return; + } else if (IsTileType(location.tile, MP_HOUSE)) { + /* Town nearby stations need to be filtered per tile. */ + assert(location.w == 1 && location.h == 1); + AddNearbyStationsByCatchment(location.tile, stations, Town::GetByTile(location.tile)->stations_near); + return; + } + } + /* Not using, or don't have a nearby stations list, so we need to scan. */ uint x = TileX(location.tile); uint y = TileY(location.tile); - uint min_x = (x > max_rad) ? x - max_rad : 0; - uint max_x = x + location.w + max_rad; - uint min_y = (y > max_rad) ? y - max_rad : 0; - uint max_y = y + location.h + max_rad; + std::set<StationID> seen_stations; - IndustryID ind = IsTileType(location.tile, MP_INDUSTRY) ? GetIndustryIndex(location.tile) : INVALID_INDUSTRY; - - if (min_x == 0 && _settings_game.construction.freeform_edges) min_x = 1; - if (min_y == 0 && _settings_game.construction.freeform_edges) min_y = 1; - if (max_x >= MapSizeX()) max_x = MapSizeX() - 1; - if (max_y >= MapSizeY()) max_y = MapSizeY() - 1; - - for (uint cy = min_y; cy < max_y; cy++) { - for (uint cx = min_x; cx < max_x; cx++) { - TileIndex cur_tile = TileXY(cx, cy); - if (!IsTileType(cur_tile, MP_STATION)) continue; - - Station *st = Station::GetByTile(cur_tile); - /* st can be NULL in case of waypoints */ - if (st == NULL) continue; + /* Scan an area around the building covering the maximum possible station + * to find the possible nearby stations. */ + uint max_c = _settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED; + TileArea ta(TileXY(max<int>(0, x - max_c), max<int>(0, y - max_c)), TileXY(min<int>(MapMaxX(), x + location.w + max_c), min<int>(MapMaxY(), y + location.h + max_c))); + TILE_AREA_LOOP(tile, ta) { + if (IsTileType(tile, MP_STATION)) seen_stations.insert(GetStationIndex(tile)); + } - /* Check if neutral station is attached to us */ - if (!_settings_game.station.serve_neutral_industries && st->industry != NULL && st->industry->index != ind) continue; + for (StationID stationid : seen_stations) { + Station *st = Station::GetIfValid(stationid); + if (st == NULL) continue; /* Waypoint */ - if (_settings_game.station.modified_catchment) { - int rad = st->GetCatchmentRadius(); - int rad_x = cx - x; - int rad_y = cy - y; + /* Check if station is attached to an industry */ + if (!_settings_game.station.serve_neutral_industries && st->industry != NULL) continue; - if (rad_x < -rad || rad_x >= rad + location.w) continue; - if (rad_y < -rad || rad_y >= rad + location.h) continue; + /* Test if the tile is within the station's catchment */ + TILE_AREA_LOOP(tile, location) { + if (st->TileIsInCatchment(tile)) { + stations->insert(st); + break; } - - /* Insert the station in the set. This will fail if it has - * already been added. - */ - stations->insert(st); } } } @@ -3950,8 +3961,8 @@ void BuildOilRig(TileIndex tile) st->rect.BeforeAddTile(tile, StationRect::ADD_FORCE); st->UpdateVirtCoord(); + st->RecomputeCatchment(); UpdateStationAcceptance(st, false); - st->RecomputeIndustriesNear(); } void DeleteOilRig(TileIndex tile) @@ -3968,7 +3979,7 @@ void DeleteOilRig(TileIndex tile) st->rect.AfterRemoveTile(st, tile); st->UpdateVirtCoord(); - st->RecomputeIndustriesNear(); + st->RecomputeCatchment(); if (!st->IsInUse()) delete st; } diff --git a/src/station_func.h b/src/station_func.h index ca3885e8b..34e996d23 100644 --- a/src/station_func.h +++ b/src/station_func.h @@ -23,13 +23,13 @@ void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint radius); -void FindStationsAroundTiles(const TileArea &location, StationList *stations); +void FindStationsAroundTiles(const TileArea &location, StationList *stations, bool use_nearby = true); void ShowStationViewWindow(StationID station); void UpdateAllStationVirtCoords(); CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad); -CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, CargoTypes *always_accepted = NULL, const Industry *ind = NULL); +CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, CargoTypes *always_accepted = NULL); void UpdateStationAcceptance(Station *st, bool show_msg); diff --git a/src/subsidy.cpp b/src/subsidy.cpp index 1b375d215..eac81e362 100644 --- a/src/subsidy.cpp +++ b/src/subsidy.cpp @@ -573,15 +573,11 @@ bool CheckSubsidised(CargoID cargo_type, CompanyID company, SourceType src_type, if (s->cargo_type != cargo_type || s->src_type != src_type || s->src != src) continue; if (s->IsAwarded() && s->awarded != company) continue; - Rect rect = st->GetCatchmentRect(); - - for (int y = rect.top; y <= rect.bottom; y++) { - for (int x = rect.left; x <= rect.right; x++) { - TileIndex tile = TileXY(x, y); - if (!IsTileType(tile, MP_HOUSE)) continue; - const Town *t = Town::GetByTile(tile); - if (t->cache.part_of_subsidy & POS_DST) towns_near.Include(t); - } + BitmapTileIterator it(st->catchment_tiles); + for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) { + if (!IsTileType(tile, MP_HOUSE)) continue; + const Town *t = Town::GetByTile(tile); + if (t->cache.part_of_subsidy & POS_DST) towns_near.Include(t); } break; } diff --git a/src/town.h b/src/town.h index a905ea837..6cff5f761 100644 --- a/src/town.h +++ b/src/town.h @@ -88,6 +88,7 @@ struct Town : TownPool::PoolItem<&_town_pool> { CargoTypes cargo_produced; ///< Bitmap of all cargoes produced by houses in this town. AcceptanceMatrix cargo_accepted; ///< Bitmap of cargoes accepted by houses for each 4*4 map square of the town. CargoTypes cargo_accepted_total; ///< NOSAVE: Bitmap of all cargoes accepted by houses in this town. + StationList stations_near; ///< NOSAVE: List of nearby stations. uint16 time_until_rebuild; ///< time until we rebuild a house diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index d7cb45bb9..4aad76f44 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -447,6 +447,22 @@ uint32 GetWorldPopulation() } /** + * Remove stations from nearby station list if a town is no longer in the catchment area of each. + * @param t Town to work on + */ +static void RemoveNearbyStations(Town *t) +{ + for (StationList::iterator it = t->stations_near.begin(); it != t->stations_near.end(); /* incremented inside loop */) { + const Station *st = *it; + if (!st->CatchmentCoversTown(t->index)) { + it = t->stations_near.erase(it); + } else { + ++it; + } + } +} + +/** * Helper function for house completion stages progression * @param tile TileIndex of the house (or parts of it) to "grow" */ @@ -599,7 +615,11 @@ static void TileLoop_Town(TileIndex tile) ClearTownHouse(t, tile); /* Rebuild with another house? */ - if (GB(r, 24, 8) >= 12) BuildTownHouse(t, tile); + if (GB(r, 24, 8) < 12 || !BuildTownHouse(t, tile)) + { + /* House wasn't replaced, so remove it */ + if (!_generating_world) RemoveNearbyStations(t); + } } cur_company.Restore(); @@ -628,6 +648,7 @@ static CommandCost ClearTile_Town(TileIndex tile, DoCommandFlag flags) ChangeTownRating(t, -rating, RATING_HOUSE_MINIMUM, flags); if (flags & DC_EXEC) { ClearTownHouse(t, tile); + RemoveNearbyStations(t); } return cost; @@ -2151,6 +2172,8 @@ static void MakeTownHouse(TileIndex t, Town *town, byte counter, byte stage, Hou if (size & BUILDING_2_TILES_Y) ClearMakeHouseTile(t + TileDiffXY(0, 1), town, counter, stage, ++type, random_bits); if (size & BUILDING_2_TILES_X) ClearMakeHouseTile(t + TileDiffXY(1, 0), town, counter, stage, ++type, random_bits); if (size & BUILDING_HAS_4_TILES) ClearMakeHouseTile(t + TileDiffXY(1, 1), town, counter, stage, ++type, random_bits); + + if (!_generating_world) FindStationsAroundTiles(TileArea(t, (size & BUILDING_2_TILES_X) ? 2 : 1, (size & BUILDING_2_TILES_Y) ? 2 : 1), &town->stations_near, false); } |