summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordP <dp@dpointer.org>2020-01-10 00:39:35 +0300
committerCharles Pigott <charlespigott@googlemail.com>2020-01-12 14:01:12 +0000
commit1225693b9c904144555bb009c7836ef214f4cf87 (patch)
treede43ce7e6039170331fe669a830857fc848c1ea6 /src
parentb144258bf0be94a056773ec66c3a0370678df90a (diff)
downloadopenttd-1225693b9c904144555bb009c7836ef214f4cf87.tar.xz
Feature: Improved logic of sharing industry production between 3 or more stations
Diffstat (limited to 'src')
-rw-r--r--src/station_cmd.cpp133
1 files changed, 87 insertions, 46 deletions
diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp
index 8f057453f..3b92f814b 100644
--- a/src/station_cmd.cpp
+++ b/src/station_cmd.cpp
@@ -4014,68 +4014,109 @@ const StationList *StationFinder::GetStations()
return &this->stations;
}
+static bool CanMoveGoodsToStation(const Station *st, CargoID type)
+{
+ /* Is the station reserved exclusively for somebody else? */
+ if (st->owner != OWNER_NONE && st->town->exclusive_counter > 0 && st->town->exclusivity != st->owner) return false;
+
+ /* Lowest possible rating, better not to give cargo anymore. */
+ if (st->goods[type].rating == 0) return false;
+
+ /* Selectively servicing stations, and not this one. */
+ if (_settings_game.order.selectgoods && !st->goods[type].HasVehicleEverTriedLoading()) return false;
+
+ if (IsCargoInClass(type, CC_PASSENGERS)) {
+ /* Passengers are never served by just a truck stop. */
+ if (st->facilities == FACIL_TRUCK_STOP) return false;
+ } else {
+ /* Non-passengers are never served by just a bus stop. */
+ if (st->facilities == FACIL_BUS_STOP) return false;
+ }
+ return true;
+}
+
uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations)
{
/* Return if nothing to do. Also the rounding below fails for 0. */
+ if (all_stations->empty()) return 0;
if (amount == 0) return 0;
- Station *st1 = nullptr; // Station with best rating
- Station *st2 = nullptr; // Second best station
- uint best_rating1 = 0; // rating of st1
- uint best_rating2 = 0; // rating of st2
+ Station *first_station = nullptr;
+ typedef std::pair<Station *, uint> StationInfo;
+ std::vector<StationInfo> used_stations;
for (Station *st : *all_stations) {
- /* Is the station reserved exclusively for somebody else? */
- if (st->owner != OWNER_NONE && st->town->exclusive_counter > 0 && st->town->exclusivity != st->owner) continue;
+ if (!CanMoveGoodsToStation(st, type)) continue;
- if (st->goods[type].rating == 0) continue; // Lowest possible rating, better not to give cargo anymore
-
- if (_settings_game.order.selectgoods && !st->goods[type].HasVehicleEverTriedLoading()) continue; // Selectively servicing stations, and not this one
-
- if (IsCargoInClass(type, CC_PASSENGERS)) {
- if (st->facilities == FACIL_TRUCK_STOP) continue; // passengers are never served by just a truck stop
- } else {
- if (st->facilities == FACIL_BUS_STOP) continue; // non-passengers are never served by just a bus stop
+ /* Avoid allocating a vector if there is only one station to significantly
+ * improve performance in this common case. */
+ if (first_station == nullptr) {
+ first_station = st;
+ continue;
}
-
- /* This station can be used, add it to st1/st2 */
- if (st1 == nullptr || st->goods[type].rating >= best_rating1) {
- st2 = st1; best_rating2 = best_rating1; st1 = st; best_rating1 = st->goods[type].rating;
- } else if (st2 == nullptr || st->goods[type].rating >= best_rating2) {
- st2 = st; best_rating2 = st->goods[type].rating;
+ if (used_stations.empty()) {
+ used_stations.reserve(2);
+ used_stations.emplace_back(std::make_pair(first_station, 0));
}
+ used_stations.emplace_back(std::make_pair(st, 0));
}
/* no stations around at all? */
- if (st1 == nullptr) return 0;
+ if (first_station == nullptr) return 0;
+
+ if (used_stations.empty()) {
+ /* only one station around */
+ amount *= first_station->goods[type].rating + 1;
+ return UpdateStationWaiting(first_station, type, amount, source_type, source_id);
+ }
+
+ uint company_best[OWNER_NONE + 1] = {}; // best rating for each company, including OWNER_NONE
+ uint company_sum[OWNER_NONE + 1] = {}; // sum of ratings for each company
+ uint best_rating = 0;
+ uint best_sum = 0; // sum of best ratings for each company
+
+ for (auto &p : used_stations) {
+ auto owner = p.first->owner;
+ auto rating = p.first->goods[type].rating;
+ if (rating > company_best[owner]) {
+ best_sum += rating - company_best[owner]; // it's usually faster than iterating companies later
+ company_best[owner] = rating;
+ if (rating > best_rating) best_rating = rating;
+ }
+ company_sum[owner] += rating;
+ }
- /* From now we'll calculate with fractal cargo amounts.
+ /* From now we'll calculate with fractional cargo amounts.
* First determine how much cargo we really have. */
- amount *= best_rating1 + 1;
+ amount *= best_rating + 1;
+
+ uint moving = 0;
+ for (auto &p : used_stations) {
+ uint owner = p.first->owner;
+ /* Multiply the amount by (company best / sum of best for each company) to get cargo allocated to a company
+ * and by (station rating / sum of ratings in a company) to get the result for a single station. */
+ p.second = amount * company_best[owner] * p.first->goods[type].rating / best_sum / company_sum[owner];
+ moving += p.second;
+ }
+
+ /* If there is some cargo left due to rounding issues distribute it among the best rated stations. */
+ if (amount > moving) {
+ std::sort(used_stations.begin(), used_stations.end(), [type] (const StationInfo &a, const StationInfo &b) {
+ return b.first->goods[type].rating < a.first->goods[type].rating;
+ });
+
+ assert(amount - moving <= used_stations.size());
+ for (uint i = 0; i < amount - moving; i++) {
+ used_stations[i].second++;
+ }
+ }
- if (st2 == nullptr) {
- /* only one station around */
- return UpdateStationWaiting(st1, type, amount, source_type, source_id);
- }
-
- /* several stations around, the best two (highest rating) are in st1 and st2 */
- assert(st1 != nullptr);
- assert(st2 != nullptr);
- assert(best_rating1 != 0 || best_rating2 != 0);
-
- /* Then determine the amount the worst station gets. We do it this way as the
- * best should get a bonus, which in this case is the rounding difference from
- * this calculation. In reality that will mean the bonus will be pretty low.
- * Nevertheless, the best station should always get the most cargo regardless
- * of rounding issues. */
- uint worst_cargo = amount * best_rating2 / (best_rating1 + best_rating2);
- assert(worst_cargo <= (amount - worst_cargo));
-
- /* And then send the cargo to the stations! */
- uint moved = UpdateStationWaiting(st1, type, amount - worst_cargo, source_type, source_id);
- /* These two UpdateStationWaiting's can't be in the statement as then the order
- * of execution would be undefined and that could cause desyncs with callbacks. */
- return moved + UpdateStationWaiting(st2, type, worst_cargo, source_type, source_id);
+ uint moved = 0;
+ for (auto &p : used_stations) {
+ moved += UpdateStationWaiting(p.first, type, p.second, source_type, source_id);
+ }
+
+ return moved;
}
void UpdateStationDockingTiles(Station *st)