diff --git a/src/economy.cpp b/src/economy.cpp index f982e71f13e0d..6313b93431f58 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -1075,30 +1075,9 @@ Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, C /** The industries we've currently brought cargo to. */ static SmallIndustryList _cargo_delivery_destinations; -/** - * Transfer goods from station to industry. - * All cargo is delivered to the nearest (Manhattan) industry to the station sign, which is inside the acceptance rectangle and actually accepts the cargo. - * @param st The station that accepted the cargo - * @param cargo_type Type of cargo delivered - * @param num_pieces Amount of cargo delivered - * @param source The source of the cargo - * @param company The company delivering the cargo - * @return actually accepted pieces of cargo - */ -static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint num_pieces, IndustryID source, CompanyID company) -{ - /* Find the nearest industrytile to the station sign inside the catchment area, whose industry accepts the cargo. - * This fails in three cases: - * 1) The station accepts the cargo because there are enough houses around it accepting the cargo. - * 2) The industries in the catchment area temporarily reject the cargo, and the daily station loop has not yet updated station acceptance. - * 3) The results of callbacks CBID_INDUSTRY_REFUSE_CARGO and CBID_INDTILE_CARGO_ACCEPTANCE are inconsistent. (documented behaviour) - */ - - uint accepted = 0; - +template +void ForAcceptingIndustries(const Station *st, CargoID cargo_type, IndustryID source, CompanyID company, F&& f) { for (Industry *ind : st->industries_near) { - if (num_pieces == 0) break; - if (ind->index == source) continue; uint cargo_index; @@ -1113,6 +1092,22 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n if (ind->exclusive_supplier != INVALID_OWNER && ind->exclusive_supplier != st->owner) continue; + if (!f(ind, cargo_index)) break; + } +} + +uint DeliverGoodsToIndustryNearestFirst(const Station *st, CargoID cargo_type, uint num_pieces, IndustryID source, CompanyID company) +{ + /* Find the nearest industrytile to the station sign inside the catchment area, whose industry accepts the cargo. + * This fails in three cases: + * 1) The station accepts the cargo because there are enough houses around it accepting the cargo. + * 2) The industries in the catchment area temporarily reject the cargo, and the daily station loop has not yet updated station acceptance. + * 3) The results of callbacks CBID_INDUSTRY_REFUSE_CARGO and CBID_INDTILE_CARGO_ACCEPTANCE are inconsistent. (documented behaviour) + */ + + uint accepted = 0; + + ForAcceptingIndustries(st, cargo_type, source, company, [&](Industry *ind, uint cargo_index) { /* Insert the industry into _cargo_delivery_destinations, if not yet contained */ include(_cargo_delivery_destinations, ind); @@ -1124,11 +1119,127 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n /* Update the cargo monitor. */ AddCargoDelivery(cargo_type, company, amount, ST_INDUSTRY, source, st, ind->index); + + return num_pieces != 0; + }); + + return accepted; +} + +uint DeliverGoodsToIndustryEqually(const Station *st, CargoID cargo_type, uint num_pieces, IndustryID source, CompanyID company) +{ + struct AcceptingIndustry { + Industry *ind; + uint cargo_index; + uint capacity; + uint delivered; + }; + + std::vector acceptingIndustries; + + ForAcceptingIndustries(st, cargo_type, source, company, [&](Industry *ind, uint cargo_index) { + uint capacity = 0xFFFFu - ind->incoming_cargo_waiting[cargo_index]; + if (capacity > 0) acceptingIndustries.push_back({ ind, cargo_index, capacity, 0 }); + return true; + }); + + if (acceptingIndustries.empty()) return 0; + + uint accepted = 0; + + auto distributeCargo = [&](AcceptingIndustry &e, uint amount) { + e.capacity -= amount; + e.delivered += amount; + num_pieces -= amount; + accepted += amount; + }; + + auto finalizeCargo = [&](AcceptingIndustry &e) { + if (e.delivered == 0) return; + include(_cargo_delivery_destinations, e.ind); + e.ind->incoming_cargo_waiting[e.cargo_index] += e.delivered; + e.ind->last_cargo_accepted_at[e.cargo_index] = _date; + AddCargoDelivery(cargo_type, company, e.delivered, ST_INDUSTRY, source, st, e.ind->index); + }; + + if (acceptingIndustries.size() == 1) { + distributeCargo(acceptingIndustries[0], std::min(acceptingIndustries[0].capacity, num_pieces)); + finalizeCargo(acceptingIndustries[0]); + return accepted; + } + + /* Sort in order of decreasing capacity */ + std::sort(acceptingIndustries.begin(), acceptingIndustries.end(), [](AcceptingIndustry &a, AcceptingIndustry &b) { + return std::tie(a.capacity, a.ind->index) > std::tie(b.capacity, b.ind->index); + }); + + /* Handle low-capacity industries first */ + do { + uint amount = num_pieces / static_cast(acceptingIndustries.size()); + AcceptingIndustry &acc = acceptingIndustries.back(); + if (amount >= acc.capacity) { + distributeCargo(acc, acc.capacity); + finalizeCargo(acc); + acceptingIndustries.pop_back(); + } else { + break; + } + } while (!acceptingIndustries.empty()); + + /* Remaining industries can accept all remaining cargo when distributed evenly */ + if (!acceptingIndustries.empty()) { + uint amount = num_pieces / static_cast(acceptingIndustries.size()); + + if (amount > 0) { + for (auto &e : acceptingIndustries) { + distributeCargo(e, amount); + } + } + + /* If cargo didn't divide evenly into remaining industries, distribute the remainder randomly */ + if (num_pieces > 0) { + assert(num_pieces < acceptingIndustries.size()); + + uint idx = RandomRange(acceptingIndustries.size()); + for (uint i = 0; i < acceptingIndustries.size(); ++i) { + if (acceptingIndustries[idx].capacity > 0) { + distributeCargo(acceptingIndustries[idx], 1); + if (num_pieces == 0) break; + } + idx++; + if (idx == acceptingIndustries.size()) idx = 0; + } + } + + for (auto &e : acceptingIndustries) { + finalizeCargo(e); + } } return accepted; } +/** + * Transfer goods from station to industry. + * Original distribution mode: All cargo is delivered to the nearest (Manhattan) industry to the station sign, which is inside the acceptance rectangle and actually accepts the cargo. + * Balanced distribution: Cargo distributed equally amongst the accepting industries in the acceptance rectangle. + * @param st The station that accepted the cargo + * @param cargo_type Type of cargo delivered + * @param num_pieces Amount of cargo delivered + * @param source The source of the cargo + * @param company The company delivering the cargo + * @return actually accepted pieces of cargo + */ +static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint num_pieces, IndustryID source, CompanyID company) +{ + switch (_settings_game.station.station_delivery_mode) { + case SD_BALANCED: + return DeliverGoodsToIndustryEqually(st, cargo_type, num_pieces, source, company); + default: + return DeliverGoodsToIndustryNearestFirst(st, cargo_type, num_pieces, source, company); + } +} + /** * Delivers goods to industries/towns and calculates the payment * @param num_pieces amount of cargo delivered diff --git a/src/lang/english.txt b/src/lang/english.txt index e8d25d8ae767b..d9135289a75c1 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1259,6 +1259,8 @@ STR_CONFIG_SETTING_TRAIN_BRAKING_REALISTIC :Realistic {PUSH STR_CONFIG_SETTING_REALISTIC_BRAKING_SIGNALS_NOT_ALLOWED :{WHITE}Realistic braking can't be enabled. At least one pre-signal or two-way signal is present. +STR_CONFIG_SETTING_DELIVERY_BALANCED :Balanced + ###length 3 STR_CONFIG_SETTING_HORIZONTAL_POS_LEFT :Left STR_CONFIG_SETTING_HORIZONTAL_POS_CENTER :Centre @@ -1340,6 +1342,9 @@ STR_CONFIG_SETTING_STATION_RATING_SIZE_CARGO_AMOUNT_HELPTEXT :When enabled, t STR_CONFIG_SETTING_EXTRADYNAMITE :Allow removal of more town-owned roads, bridges and tunnels: {STRING2} STR_CONFIG_SETTING_EXTRADYNAMITE_HELPTEXT :Make it easier to remove town-owned infrastructure and buildings +STR_CONFIG_SETTING_CARGO_DELIVERY_MODE :Cargo delivery distribution mode: {STRING2} +STR_CONFIG_SETTING_CARGO_DELIVERY_MODE_HELPTEXT :The method used to deliver cargo to industries surrounding a station. + STR_CONFIG_SETTING_TRAIN_LENGTH :Maximum length of trains: {STRING2} STR_CONFIG_SETTING_TRAIN_LENGTH_HELPTEXT :Set the maximum length of trains STR_CONFIG_SETTING_TILE_LENGTH :{COMMA} tile{P 0 "" s} diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 8e1aa434e0b8e..b34c9da9d7912 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -3991,6 +3991,10 @@ bool AfterLoadGame() } } + if (SlXvIsFeatureMissing(XSLFI_ST_INDUSTRY_CARGO_MODE)) { + _settings_game.station.station_delivery_mode = SD_NEAREST_FIRST; + } + InitializeRoadGUI(); /* This needs to be done after conversion. */ diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 2d1255d204a44..607ca31c5e354 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -162,6 +162,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_OBJECT_GROUND_TYPES, XSCF_NULL, 1, 1, "object_ground_types", nullptr, nullptr, nullptr }, { XSLFI_LINKGRAPH_AIRCRAFT, XSCF_NULL, 1, 1, "linkgraph_aircraft", nullptr, nullptr, nullptr }, { XSLFI_COMPANY_PW, XSCF_IGNORABLE_ALL, 1, 1, "company_password", nullptr, nullptr, "PLYP" }, + { XSLFI_ST_INDUSTRY_CARGO_MODE, XSCF_IGNORABLE_UNKNOWN, 1, 1, "st_industry_cargo_mode", nullptr, nullptr, nullptr }, { XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index 84ad33732057f..2900a1050760e 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -116,6 +116,7 @@ enum SlXvFeatureIndex { XSLFI_OBJECT_GROUND_TYPES, ///< Object ground types XSLFI_LINKGRAPH_AIRCRAFT, ///< Link graph last aircraft update field and aircraft link scaling setting XSLFI_COMPANY_PW, ///< Company passwords + XSLFI_ST_INDUSTRY_CARGO_MODE, ///< Station industry cargo mode setting XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64 diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 2cb06653b670c..2f6a129cd8b72 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2181,6 +2181,7 @@ static SettingsContainer &GetSettingsTree() industries->Add(new SettingEntry("economy.type")); industries->Add(new SettingEntry("station.serve_neutral_industries")); industries->Add(new SettingEntry("economy.industry_cargo_scale_factor")); + industries->Add(new SettingEntry("station.station_delivery_mode")); } SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST)); diff --git a/src/settings_type.h b/src/settings_type.h index c45098ba17c0c..3ee54e9f584e2 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -21,6 +21,7 @@ #include "zoom_type.h" #include "openttd.h" #include "rail_gui_type.h" +#include "station_type.h" /* Used to validate sizes of "max" value in settings. */ const size_t MAX_SLE_UINT8 = UINT8_MAX; @@ -715,6 +716,7 @@ struct StationSettings { byte catchment_increase; ///< amount by which station catchment is increased bool cargo_class_rating_wait_time; ///< station rating tolerance to time since last cargo pickup depends on cargo class bool station_size_rating_cargo_amount; ///< station rating tolerance to waiting cargo amount depends on station size + StationDelivery station_delivery_mode; ///< method to use for distributing cargo from stations to accepting industries }; /** Default settings for vehicles. */ diff --git a/src/station_type.h b/src/station_type.h index a8e657efc1b45..2753e3adbbd9c 100644 --- a/src/station_type.h +++ b/src/station_type.h @@ -87,6 +87,11 @@ enum CatchmentArea { MAX_CATCHMENT = 10, ///< Maximum catchment for airports with "modified catchment" enabled }; +enum StationDelivery : byte { + SD_NEAREST_FIRST = 0, ///< Station delivers cargo only to the nearest accepting industry + SD_BALANCED = 1 ///< Station delivers cargo equally among accepting industries +}; + static const uint MAX_LENGTH_STATION_NAME_CHARS = 128; ///< The maximum length of a station name in characters including '\0' struct StationCompare { diff --git a/src/table/settings/settings.ini b/src/table/settings/settings.ini index 724d7595072e5..5bd847d04f478 100644 --- a/src/table/settings/settings.ini +++ b/src/table/settings/settings.ini @@ -122,6 +122,12 @@ static const SettingDescEnumEntry _train_braking_model[] = { { 0, STR_NULL } }; +static const SettingDescEnumEntry _station_delivery_mode[] = { +{ SD_NEAREST_FIRST, STR_CONFIG_SETTING_ORIGINAL}, +{ SD_BALANCED, STR_CONFIG_SETTING_DELIVERY_BALANCED}, +{ 0, STR_NULL } +}; + /* Some settings do not need to be synchronised when playing in multiplayer. * These include for example the GUI settings and will not be saved with the * savegame. @@ -1948,6 +1954,15 @@ str = STR_CONFIG_SETTING_SERVE_NEUTRAL_INDUSTRIES strhelp = STR_CONFIG_SETTING_SERVE_NEUTRAL_INDUSTRIES_HELPTEXT post_cb = StationCatchmentChanged +[SDT_ENUM] +var = station.station_delivery_mode +type = SLE_UINT8 +def = SD_NEAREST_FIRST +enumlist = _station_delivery_mode +str = STR_CONFIG_SETTING_CARGO_DELIVERY_MODE +strhelp = STR_CONFIG_SETTING_CARGO_DELIVERY_MODE_HELPTEXT +patxname = ""station.station_delivery_mode"" + [SDT_BOOL] var = order.gradual_loading from = SLV_40