diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 8f057453f9..3b92f814b9 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 StationInfo; + std::vector 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)