diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj
index 936689b..1f4f3f7 100644
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -296,6 +296,7 @@
+
@@ -385,6 +386,10 @@
+
+
+
+
@@ -570,6 +575,7 @@
+
@@ -634,6 +640,7 @@
+
@@ -715,6 +722,7 @@
+
@@ -1071,6 +1079,7 @@
+
diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters
index 587e3f6..2273250 100644
--- a/projects/openttd_vs100.vcxproj.filters
+++ b/projects/openttd_vs100.vcxproj.filters
@@ -108,6 +108,9 @@
Source Files
+
+ Source Files
+
Source Files
@@ -375,6 +378,18 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
Header Files
@@ -930,6 +945,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -1122,6 +1140,9 @@
GUI Source Code
+
+ GUI Source Code
+
GUI Source Code
@@ -1365,6 +1386,9 @@
Save/Load handlers
+
+ Save/Load handlers
+
Save/Load handlers
@@ -2433,6 +2457,9 @@
YAPF
+
+ YAPF
+
YAPF
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index 79212ee..fd38fbf 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -455,6 +455,10 @@
>
+
+
@@ -815,6 +819,22 @@
>
+
+
+
+
+
+
+
+
@@ -1555,6 +1575,10 @@
>
+
+
@@ -1819,6 +1843,10 @@
>
+
+
@@ -2155,6 +2183,10 @@
>
+
+
@@ -3651,6 +3683,10 @@
>
+
+
diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj
index c6487d6..8ed411d 100644
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -452,6 +452,10 @@
>
+
+
@@ -812,6 +816,22 @@
>
+
+
+
+
+
+
+
+
@@ -1552,6 +1572,10 @@
>
+
+
@@ -1816,6 +1840,10 @@
>
+
+
@@ -2152,6 +2180,10 @@
>
+
+
@@ -3648,6 +3680,10 @@
>
+
+
diff --git a/source.list b/source.list
index cc3562e..cbcdfac 100644
--- a/source.list
+++ b/source.list
@@ -4,6 +4,7 @@ animated_tile.cpp
articulated_vehicles.cpp
autoreplace.cpp
bmp.cpp
+cargodest.cpp
cargopacket.cpp
cargotype.cpp
cheat.cpp
@@ -118,6 +119,10 @@ base_station_base.h
bmp.h
bridge.h
cargo_type.h
+cargodest_base.h
+cargodest_func.h
+cargodest_gui.h
+cargodest_type.h
cargopacket.h
cargotype.h
cheat_func.h
@@ -303,6 +308,7 @@ tile_type.h
tilearea_type.h
tilehighlight_func.h
tilehighlight_type.h
+tilematrix_type.hpp
timetable.h
toolbar_gui.h
town.h
@@ -386,6 +392,7 @@ airport_gui.cpp
autoreplace_gui.cpp
bridge_gui.cpp
build_vehicle_gui.cpp
+cargodest_gui.cpp
cheat_gui.cpp
company_gui.cpp
console_gui.cpp
@@ -473,6 +480,7 @@ saveload/ai_sl.cpp
saveload/airport_sl.cpp
saveload/animated_tile_sl.cpp
saveload/autoreplace_sl.cpp
+saveload/cargodest_sl.cpp
saveload/cargopacket_sl.cpp
saveload/cheat_sl.cpp
saveload/company_sl.cpp
@@ -872,6 +880,7 @@ pathfinder/yapf/yapf.h
pathfinder/yapf/yapf.hpp
pathfinder/yapf/yapf_base.hpp
pathfinder/yapf/yapf_cache.h
+pathfinder/yapf/yapf_cargo.cpp
pathfinder/yapf/yapf_common.hpp
pathfinder/yapf/yapf_costbase.hpp
pathfinder/yapf/yapf_costcache.hpp
diff --git a/src/ai/api/ai_town.cpp b/src/ai/api/ai_town.cpp
index 6ad9401..870042c 100644
--- a/src/ai/api/ai_town.cpp
+++ b/src/ai/api/ai_town.cpp
@@ -71,8 +71,8 @@
const Town *t = ::Town::Get(town_id);
switch (AICargo::GetTownEffect(cargo_id)) {
- case AICargo::TE_PASSENGERS: return t->max_pass;
- case AICargo::TE_MAIL: return t->max_mail;
+ case AICargo::TE_PASSENGERS: return t->pass.old_max;
+ case AICargo::TE_MAIL: return t->mail.old_max;
default: return -1;
}
}
@@ -85,8 +85,8 @@
const Town *t = ::Town::Get(town_id);
switch (AICargo::GetTownEffect(cargo_id)) {
- case AICargo::TE_PASSENGERS: return t->act_pass;
- case AICargo::TE_MAIL: return t->act_mail;
+ case AICargo::TE_PASSENGERS: return t->pass.old_act;
+ case AICargo::TE_MAIL: return t->mail.old_act;
default: return -1;
}
}
diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp
index a330280..68fa3fd 100644
--- a/src/aircraft_cmd.cpp
+++ b/src/aircraft_cmd.cpp
@@ -35,6 +35,7 @@
#include "engine_base.h"
#include "core/random_func.hpp"
#include "core/backup_type.hpp"
+#include "cargotype.h"
#include "table/strings.h"
@@ -549,6 +550,13 @@ void UpdateAircraftCache(Aircraft *v)
/* Use the default max speed of the vehicle. */
v->vcache.cached_max_speed = AircraftVehInfo(v->engine_type)->max_speed;
}
+
+ /* Cache carried cargo types. */
+ uint32 cargo_mask = 0;
+ for (Aircraft *u = v; u != NULL; u = u->Next()) {
+ if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type);
+ }
+ v->vcache.cached_cargo_mask = cargo_mask;
}
@@ -1197,7 +1205,6 @@ static void AircraftEntersTerminal(Aircraft *v)
if (v->current_order.IsType(OT_GOTO_DEPOT)) return;
Station *st = Station::Get(v->targetairport);
- v->last_station_visited = v->targetairport;
/* Check if station was ever visited before */
if (!(st->had_vehicle_of_type & HVOT_AIRCRAFT)) {
@@ -1213,7 +1220,7 @@ static void AircraftEntersTerminal(Aircraft *v)
AI::NewEvent(v->owner, new AIEventStationFirstVehicle(st->index, v->index));
}
- v->BeginLoading();
+ v->BeginLoading(v->targetairport);
}
/**
diff --git a/src/base_station_base.h b/src/base_station_base.h
index c942120..3db1087 100644
--- a/src/base_station_base.h
+++ b/src/base_station_base.h
@@ -39,6 +39,7 @@ struct StationRect : public Rect {
StationRect();
void MakeEmpty();
bool PtInExtendedRect(int x, int y, int distance = 0) const;
+ bool AreaInExtendedRect(const TileArea& area, int distance = 0) const;
bool IsEmpty() const;
CommandCost BeforeAddTile(TileIndex tile, StationRectMode mode);
CommandCost BeforeAddRect(TileIndex tile, int w, int h, StationRectMode mode);
diff --git a/src/cargodest.cpp b/src/cargodest.cpp
new file mode 100644
index 0000000..b90071c
--- /dev/null
+++ b/src/cargodest.cpp
@@ -0,0 +1,1035 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file cargodest.cpp Implementation of cargo destinations. */
+
+#include "stdafx.h"
+#include "cargodest_type.h"
+#include "cargodest_base.h"
+#include "cargodest_func.h"
+#include "core/bitmath_func.hpp"
+#include "core/random_func.hpp"
+#include "core/pool_func.hpp"
+#include "cargotype.h"
+#include "settings_type.h"
+#include "town.h"
+#include "industry.h"
+#include "window_func.h"
+#include "vehicle_base.h"
+#include "station_base.h"
+#include "pathfinder/yapf/yapf.h"
+#include "company_base.h"
+
+
+static const uint MAX_EXTRA_LINKS = 2; ///< Number of extra links allowed.
+static const byte INTOWN_LINK_WEIGHTMOD = 8; ///< Weight modifier for in-town links.
+static const uint MAX_IND_STOCKPILE = 1000; ///< Maximum stockpile to consider for industry link weight.
+
+
+/** Are cargo destinations for this cargo type enabled? */
+bool CargoHasDestinations(CargoID cid)
+{
+ const CargoSpec *spec = CargoSpec::Get(cid);
+ if (spec->town_effect == TE_PASSENGERS || spec->town_effect == TE_MAIL) {
+ return HasBit(_settings_game.economy.cargodest.mode, CRM_TOWN_CARGOS);
+ } else {
+ return HasBit(_settings_game.economy.cargodest.mode, CRM_INDUSTRY_CARGOS);
+ }
+}
+
+/** Should this cargo type primarily have towns as a destination? */
+static bool IsTownCargo(CargoID cid)
+{
+ const CargoSpec *spec = CargoSpec::Get(cid);
+ return spec->town_effect != TE_NONE;
+}
+
+/** Does this cargo have a symmetric demand? */
+static bool IsSymmetricCargo(CargoID cid)
+{
+ const CargoSpec *spec = CargoSpec::Get(cid);
+ return spec->town_effect == TE_PASSENGERS;
+}
+
+/** Is this a passenger cargo. */
+static bool IsPassengerCargo(CargoID cid)
+{
+ const CargoSpec *spec = CargoSpec::Get(cid);
+ return spec->town_effect == TE_PASSENGERS;
+}
+
+
+/** Information for the town/industry enumerators. */
+struct EnumRandomData {
+ CargoSourceSink *source;
+ TileIndex source_xy;
+ CargoID cid;
+ bool limit_links;
+};
+
+/** Common helper for town/industry enumeration. */
+static bool EnumAnyDest(const CargoSourceSink *dest, EnumRandomData *erd)
+{
+ /* Already a destination? */
+ if (erd->source->HasLinkTo(erd->cid, dest)) return false;
+
+ /* Destination already has too many links? */
+ if (erd->limit_links && dest->cargo_links[erd->cid].Length() > dest->num_links_expected[erd->cid] + MAX_EXTRA_LINKS) return false;
+
+ return true;
+}
+
+/** Enumerate any town not already a destination and accepting a specific cargo.*/
+static bool EnumAnyTown(const Town *t, void *data)
+{
+ EnumRandomData *erd = (EnumRandomData *)data;
+ return EnumAnyDest(t, erd) && t->AcceptsCargo(erd->cid);
+}
+
+/** Enumerate cities. */
+static bool EnumCity(const Town *t, void *data)
+{
+ return EnumAnyTown(t, data) && t->larger_town;
+}
+
+/** Enumerate towns with a big population. */
+static bool EnumBigTown(const Town *t, void *data)
+{
+ EnumRandomData *erd = (EnumRandomData *)data;
+ return EnumAnyTown(t, erd) && (IsPassengerCargo(erd->cid) ? t->pass.old_max > _settings_game.economy.cargodest.big_town_pop[0] : t->mail.old_max > _settings_game.economy.cargodest.big_town_pop[1]);
+}
+
+/** Enumerate nearby towns. */
+static bool EnumNearbyTown(const Town *t, void *data)
+{
+ EnumRandomData *erd = (EnumRandomData *)data;
+ /* Scale distance by 1D map size to make sure that there are still
+ * candidates left on larger maps with few towns, but don't scale
+ * by 2D map size so the map still feels bigger. */
+ return EnumAnyTown(t, data) && DistanceSquare(t->xy, erd->source_xy) < ScaleByMapSize1D(_settings_game.economy.cargodest.town_nearby_dist);
+}
+
+/** Enumerate any industry not already a destination and accepting a specific cargo. */
+static bool EnumAnyIndustry(const Industry *ind, void *data)
+{
+ EnumRandomData *erd = (EnumRandomData *)data;
+ return EnumAnyDest(ind, erd) && ind->AcceptsCargo(erd->cid);
+}
+
+/** Enumerate nearby industries. */
+static bool EnumNearbyIndustry(const Industry *ind, void *data)
+{
+ EnumRandomData *erd = (EnumRandomData *)data;
+ /* Scale distance by 1D map size to make sure that there are still
+ * candidates left on larger maps with few industries, but don't scale
+ * by 2D map size so the map still feels bigger. */
+ return EnumAnyIndustry(ind, data) && DistanceSquare(ind->location.tile, erd->source_xy) < ScaleByMapSize1D(_settings_game.economy.cargodest.ind_nearby_dist);
+}
+
+/** Enumerate industries that are producing cargo. */
+static bool EnumProducingIndustry(const Industry *ind, void *data)
+{
+ return EnumAnyIndustry(ind, data) && (ind->produced_cargo[0] != CT_INVALID || ind->produced_cargo[1] != CT_INVALID);
+}
+
+/** Enumerate cargo sources supplying a specific cargo. */
+template
+static bool EnumAnySupplier(const T *css, void *data)
+{
+ return css->SuppliesCargo(((EnumRandomData *)data)->cid);
+}
+
+/** Enumerate nearby cargo sources supplying a specific cargo. */
+static bool EnumNearbySupplier(const Industry *ind, void *data)
+{
+ EnumRandomData *erd = (EnumRandomData *)data;
+ /* Scale distance by 1D map size to make sure that there are still
+ * candidates left on larger maps with few industries, but don't scale
+ * by 2D map size so the map still feels bigger. */
+ return EnumAnySupplier(ind, data) && DistanceSquare(ind->location.tile, erd->source_xy) < ScaleByMapSize1D(_settings_game.economy.cargodest.ind_nearby_dist);
+}
+
+/** Enumerate nearby cargo sources supplying a specific cargo. */
+static bool EnumNearbySupplier(const Town *t, void *data)
+{
+ EnumRandomData *erd = (EnumRandomData *)data;
+ /* Scale distance by 1D map size to make sure that there are still
+ * candidates left on larger maps with few industries, but don't scale
+ * by 2D map size so the map still feels bigger. */
+ return EnumAnySupplier(t, data) && DistanceSquare(t->xy, erd->source_xy) < ScaleByMapSize1D(_settings_game.economy.cargodest.town_nearby_dist);
+}
+
+
+/** Find a town as a destination. */
+static CargoSourceSink *FindTownDestination(byte &weight_mod, CargoSourceSink *source, TileIndex source_xy, CargoID cid, const uint8 destclass_chance[4], TownID skip = INVALID_TOWN)
+{
+ /* Enum functions for: nearby town, city, big town, and any town. */
+ static const Town::EnumTownProc destclass_enum[] = {
+ &EnumNearbyTown, &EnumCity, &EnumBigTown, &EnumAnyTown
+ };
+ static const byte weight_mods[] = {5, 4, 3, 2};
+ assert_compile(lengthof(destclass_enum) == lengthof(weight_mods));
+
+ EnumRandomData erd = {source, source_xy, cid, IsSymmetricCargo(cid)};
+
+ /* Determine destination class. If no town is found in this class,
+ * the search falls through to the following classes. */
+ byte destclass = RandomRange(destclass_chance[3]);
+
+ weight_mod = 1;
+ Town *dest = NULL;
+ for (uint i = 0; i < lengthof(destclass_enum) && dest == NULL; i++) {
+ /* Skip if destination class not reached. */
+ if (destclass > destclass_chance[i]) continue;
+
+ dest = Town::GetRandom(destclass_enum[i], skip, &erd);
+ weight_mod = weight_mods[i];
+ }
+
+ return dest;
+}
+
+/** Find an industry as a destination. */
+static CargoSourceSink *FindIndustryDestination(byte &weight_mod, CargoSourceSink *source, TileIndex source_xy, CargoID cid, IndustryID skip = INVALID_INDUSTRY)
+{
+ /* Enum functions for: nearby industry, producing industry, and any industry. */
+ static const Industry::EnumIndustryProc destclass_enum[] = {
+ &EnumNearbyIndustry, &EnumProducingIndustry, &EnumAnyIndustry
+ };
+ static const byte weight_mods[] = {5, 7, 3};
+ assert_compile(lengthof(destclass_enum) == lengthof(_settings_game.economy.cargodest.ind_chances));
+
+ EnumRandomData erd = {source, source_xy, cid, IsSymmetricCargo(cid)};
+
+ /* Determine destination class. If no industry is found in this class,
+ * the search falls through to the following classes. */
+ byte destclass = RandomRange(*lastof(_settings_game.economy.cargodest.ind_chances));
+
+ weight_mod = 1;
+ Industry *dest = NULL;
+ for (uint i = 0; i < lengthof(destclass_enum) && dest == NULL; i++) {
+ /* Skip if destination class not reached. */
+ if (destclass > _settings_game.economy.cargodest.ind_chances[i]) continue;
+
+ dest = Industry::GetRandom(destclass_enum[i], skip, &erd);
+ weight_mod = weight_mods[i];
+ }
+
+ return dest;
+}
+
+/** Find a supply for a cargo type. */
+static CargoSourceSink *FindSupplySource(Industry *dest, CargoID cid)
+{
+ EnumRandomData erd = {dest, dest->location.tile, cid, false};
+
+ CargoSourceSink *source = NULL;
+
+ /* Even chance for industry source first, town second and vice versa.
+ * Try a nearby supplier first, then check all suppliers. */
+ if (Chance16(1, 2)) {
+ source = Industry::GetRandom(&EnumNearbySupplier, dest->index, &erd);
+ if (source == NULL) source = Town::GetRandom(&EnumNearbySupplier, INVALID_TOWN, &erd);
+ if (source == NULL) source = Industry::GetRandom(&EnumAnySupplier, dest->index, &erd);
+ if (source == NULL) source = Town::GetRandom(&EnumAnySupplier, INVALID_TOWN, &erd);
+ } else {
+ source = Town::GetRandom(&EnumNearbySupplier, INVALID_TOWN, &erd);
+ if (source == NULL) source = Industry::GetRandom(&EnumNearbySupplier, dest->index, &erd);
+ if (source == NULL) source = Town::GetRandom(&EnumAnySupplier, INVALID_TOWN, &erd);
+ if (source == NULL) source = Industry::GetRandom(&EnumAnySupplier, dest->index, &erd);
+ }
+
+ return source;
+}
+
+/* virtual */ void CargoSourceSink::CreateSpecialLinks(CargoID cid)
+{
+ if (this->cargo_links[cid].Length() == 0) {
+ /* First link is for undetermined destinations. */
+ *this->cargo_links[cid].Append() = CargoLink(NULL);
+ }
+}
+
+/* virtual */ void Town::CreateSpecialLinks(CargoID cid)
+{
+ CargoSourceSink::CreateSpecialLinks(cid);
+
+ if (this->AcceptsCargo(cid)) {
+ /* Add special link for town-local demand if not already present. */
+ if (this->cargo_links[cid].Length() < 2) *this->cargo_links[cid].Append() = CargoLink(this, INTOWN_LINK_WEIGHTMOD);
+ if (this->cargo_links[cid].Get(1)->dest != this) {
+ /* Insert link at second place. */
+ *this->cargo_links[cid].Append() = *this->cargo_links[cid].Get(1);
+ *this->cargo_links[cid].Get(1) = CargoLink(this, INTOWN_LINK_WEIGHTMOD);
+ }
+ } else {
+ /* Remove link for town-local demand if present. */
+ if (this->cargo_links[cid].Length() > 1 && this->cargo_links[cid].Get(1)->dest == this) {
+ this->cargo_links[cid].Erase(this->cargo_links[cid].Get(1));
+ }
+ }
+}
+
+/**
+ * Remove the link with the lowest weight from a cargo source. The
+ * reverse link is removed as well if the cargo has symmetric demand.
+ * @param source Remove the link from this cargo source.
+ * @param cid Cargo type of the link to remove.
+ */
+static void RemoveLowestLink(CargoSourceSink *source, CargoID cid)
+{
+ uint lowest_weight = UINT_MAX;
+ CargoLink *lowest_link = NULL;
+
+ for (CargoLink *l = source->cargo_links[cid].Begin(); l != source->cargo_links[cid].End(); l++) {
+ /* Don't remove special links. */
+ if (l->dest == NULL || l->dest == source) continue;
+
+ if (l->weight < lowest_weight) {
+ lowest_weight = l->weight;
+ lowest_link = l;
+ }
+ }
+
+ if (lowest_link != NULL) {
+ /* If this is a symmetric cargo, also remove the reverse link. */
+ if (IsSymmetricCargo(cid) && lowest_link->dest->HasLinkTo(cid, source)) {
+ source->num_incoming_links[cid]--;
+ lowest_link->dest->cargo_links[cid].Erase(lowest_link->dest->cargo_links[cid].Find(CargoLink(source)));
+ }
+ lowest_link->dest->num_incoming_links[cid]--;
+ source->cargo_links[cid].Erase(lowest_link);
+ }
+}
+
+/** Create missing cargo links for a source. */
+static void CreateNewLinks(CargoSourceSink *source, TileIndex source_xy, CargoID cid, uint chance_a, uint chance_b, const uint8 town_chance[], TownID skip_town, IndustryID skip_ind)
+{
+ uint num_links = source->num_links_expected[cid];
+
+ /* Remove the link with the lowest weight if the
+ * town has more than links more than expected. */
+ if (source->cargo_links[cid].Length() > num_links + MAX_EXTRA_LINKS) {
+ RemoveLowestLink(source, cid);
+ }
+
+ /* Add new links until the expected link count is reached. */
+ while (source->cargo_links[cid].Length() < num_links) {
+ CargoSourceSink *dest = NULL;
+ byte weight_mod = 1;
+
+ /* Chance for town/industry is chance_a/chance_b, otherwise try industry/town. */
+ if (Chance16(chance_a, chance_b)) {
+ dest = FindTownDestination(weight_mod, source, source_xy, cid, town_chance, skip_town);
+ /* No town found? Try an industry. */
+ if (dest == NULL) dest = FindIndustryDestination(weight_mod, source, source_xy, cid, skip_ind);
+ } else {
+ dest = FindIndustryDestination(weight_mod, source, source_xy, cid, skip_ind);
+ /* No industry found? Try a town. */
+ if (dest == NULL) dest = FindTownDestination(weight_mod, source, source_xy, cid, town_chance, skip_town);
+ }
+
+ /* If we didn't find a destination, break out of the loop because no
+ * more destinations are left on the map. */
+ if (dest == NULL) break;
+
+ /* If this is a symmetric cargo and we accept it as well, create a back link. */
+ if (IsSymmetricCargo(cid) && dest->SuppliesCargo(cid) && source->AcceptsCargo(cid)) {
+ *dest->cargo_links[cid].Append() = CargoLink(source, weight_mod);
+ source->num_incoming_links[cid]++;
+ }
+
+ *source->cargo_links[cid].Append() = CargoLink(dest, weight_mod);
+ dest->num_incoming_links[cid]++;
+ }
+}
+
+/** Remove invalid links from a cargo source/sink. */
+static void RemoveInvalidLinks(CargoSourceSink *css)
+{
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ /* Remove outgoing links if cargo isn't supplied anymore. */
+ if (!css->SuppliesCargo(cid)) {
+ for (CargoLink *l = css->cargo_links[cid].Begin(); l != css->cargo_links[cid].End(); l++) {
+ if (l->dest != NULL && l->dest != css) l->dest->num_incoming_links[cid]--;
+ }
+ css->cargo_links[cid].Clear();
+ css->cargo_links_weight[cid] = 0;
+ }
+
+ /* Remove outgoing links if the dest doesn't accept the cargo anymore. */
+ for (CargoLink *l = css->cargo_links[cid].Begin(); l != css->cargo_links[cid].End(); ) {
+ if (l->dest != NULL && !l->dest->AcceptsCargo(cid)) {
+ if (l->dest != css) l->dest->num_incoming_links[cid]--;
+ css->cargo_links[cid].Erase(l);
+ } else {
+ l++;
+ }
+ }
+ }
+}
+
+/** Updated the desired link count for each cargo. */
+void UpdateExpectedLinks(Town *t)
+{
+ CargoID cid;
+
+ FOR_EACH_SET_CARGO_ID(cid, t->cargo_produced) {
+ if (CargoHasDestinations(cid)) {
+ t->CreateSpecialLinks(cid);
+
+ uint idx = IsPassengerCargo(cid) ? 1 : 0;
+ uint max_amt = IsPassengerCargo(cid) ? t->pass.old_max : t->mail.old_max;
+ uint big_amt = _settings_game.economy.cargodest.big_town_pop[idx];
+
+ uint num_links = _settings_game.economy.cargodest.base_town_links[IsSymmetricCargo(cid) ? 1 : 0];
+ /* Add links based on the available cargo amount. */
+ num_links += min(max_amt, big_amt) / _settings_game.economy.cargodest.pop_scale_town[idx * 2];
+ if (max_amt > big_amt) num_links += (max_amt - big_amt) / _settings_game.economy.cargodest.pop_scale_town[idx * 2 + 1];
+ /* Ensure a city has at least city_town_links more than the base value.
+ * This improves the link distribution at the beginning of a game when
+ * the towns are still small. */
+ if (t->larger_town) num_links = max(num_links, _settings_game.economy.cargodest.city_town_links + _settings_game.economy.cargodest.base_town_links[IsSymmetricCargo(cid) ? 1 : 0]);
+
+ /* Account for the two special links. */
+ num_links++;
+ if (t->cargo_links[cid].Length() > 1 && t->cargo_links[cid].Get(1)->dest == t) num_links++;
+
+ t->num_links_expected[cid] = ClampToU16(num_links);
+ }
+ }
+}
+
+/** Updated the desired link count for each cargo. */
+void UpdateExpectedLinks(Industry *ind)
+{
+ for (uint i = 0; i < lengthof(ind->produced_cargo); i++) {
+ CargoID cid = ind->produced_cargo[i];
+ if (cid == INVALID_CARGO) continue;
+
+ if (CargoHasDestinations(cid)) {
+ ind->CreateSpecialLinks(cid);
+
+ uint num_links;
+ /* Use different base values for symmetric cargos, cargos
+ * with a town effect and all other cargos. */
+ num_links = _settings_game.economy.cargodest.base_ind_links[IsSymmetricCargo(cid) ? 2 : (IsTownCargo(cid) ? 1 : 0)];
+ /* Add links based on the average industry production. */
+ num_links += ind->average_production[i] / _settings_game.economy.cargodest.cargo_scale_ind[IsTownCargo(cid) ? 1 : 0];
+
+ /* Account for the one special link. */
+ num_links++;
+
+ ind->num_links_expected[cid] = ClampToU16(num_links);
+ }
+ }
+}
+
+/** Make sure an industry has at least one incoming link for each accepted cargo. */
+void AddMissingIndustryLinks(Industry *ind)
+{
+ for (uint i = 0; i < lengthof(ind->accepts_cargo); i++) {
+ CargoID cid = ind->accepts_cargo[i];
+ if (cid == INVALID_CARGO) continue;
+
+ /* Do we already have at least one cargo source? */
+ if (ind->num_incoming_links[cid] > 0) continue;
+
+ CargoSourceSink *source = FindSupplySource(ind, cid);
+ if (source == NULL) continue; // Too bad...
+
+ if (source->cargo_links[cid].Length() >= source->num_links_expected[cid] + MAX_EXTRA_LINKS) {
+ /* Increase the expected link count if adding another link would
+ * exceed the count, as otherwise this (or another) link would
+ * get removed right again. */
+ source->num_links_expected[cid]++;
+ }
+
+ *source->cargo_links[cid].Append() = CargoLink(ind, 2);
+ ind->num_incoming_links[cid]++;
+
+ /* If this is a symmetric cargo and we produce it as well, create a back link. */
+ if (IsSymmetricCargo(cid) && ind->SuppliesCargo(cid) && source->AcceptsCargo(cid)) {
+ *ind->cargo_links[cid].Append() = CargoLink(source, 2);
+ source->num_incoming_links[cid]++;
+ }
+ }
+}
+
+/** Update the demand links. */
+void UpdateCargoLinks(Town *t)
+{
+ CargoID cid;
+
+ FOR_EACH_SET_CARGO_ID(cid, t->cargo_produced) {
+ if (CargoHasDestinations(cid)) {
+ /* If this is a town cargo, 95% chance for town/industry destination and
+ * 5% for industry/town. The reverse chance otherwise. */
+ CreateNewLinks(t, t->xy, cid, IsTownCargo(cid) ? 19 : 1, 20, t->larger_town ? _settings_game.economy.cargodest.town_chances_city : _settings_game.economy.cargodest.town_chances_town, t->index, INVALID_INDUSTRY);
+ }
+ }
+}
+
+/** Update the demand links. */
+void UpdateCargoLinks(Industry *ind)
+{
+ for (uint i = 0; i < lengthof(ind->produced_cargo); i++) {
+ CargoID cid = ind->produced_cargo[i];
+ if (cid == INVALID_CARGO) continue;
+
+ if (CargoHasDestinations(cid)) {
+ /* If this is a town cargo, 75% chance for town/industry destination and
+ * 25% for industry/town. The reverse chance otherwise. */
+ CreateNewLinks(ind, ind->location.tile, cid, IsTownCargo(cid) ? 3 : 1, 4, _settings_game.economy.cargodest.town_chances_town, INVALID_TOWN, ind->index);
+ }
+ }
+}
+
+/* virtual */ uint Town::GetDestinationWeight(CargoID cid, byte weight_mod) const
+{
+ uint idx = IsPassengerCargo(cid) ? 1 : 0;
+ uint max_amt = IsPassengerCargo(cid) ? this->pass.old_max : this->mail.old_max;
+ uint big_amt = _settings_game.economy.cargodest.big_town_pop[idx];
+
+ /* The weight is calculated by a piecewise function. We start with a predefined
+ * minimum weight and then add the weight for the cargo amount up to the big
+ * town amount. If the amount is more than the big town amount, this is also
+ * added to the weight with a different scale factor to make sure that big towns
+ * don't siphon the cargo away too much from the smaller destinations. */
+ uint weight = _settings_game.economy.cargodest.min_weight_town[idx];
+ weight += min(max_amt, big_amt) * weight_mod / _settings_game.economy.cargodest.weight_scale_town[idx * 2];
+ if (max_amt > big_amt) weight += (max_amt - big_amt) * weight_mod / _settings_game.economy.cargodest.weight_scale_town[idx * 2 + 1];
+
+ return weight;
+}
+
+/* virtual */ uint Industry::GetDestinationWeight(CargoID cid, byte weight_mod) const
+{
+ uint weight = _settings_game.economy.cargodest.min_weight_ind;
+
+ for (uint i = 0; i < lengthof(this->accepts_cargo); i++) {
+ if (this->accepts_cargo[i] != cid) continue;
+ /* Empty stockpile means more weight for the link. Stockpiles
+ * above a fixed maximum have no further effect. */
+ uint stockpile = ClampU(this->incoming_cargo_waiting[i], 0, MAX_IND_STOCKPILE);
+ weight += (MAX_IND_STOCKPILE - stockpile) * weight_mod / _settings_game.economy.cargodest.weight_scale_ind[1];
+ }
+
+ /* Add a weight for the produced cargo. Use the average production
+ * here so the weight isn't fluctuating that much when the input
+ * cargo isn't delivered regularly. */
+ weight += (this->average_production[0] + this->average_production[1]) * weight_mod / _settings_game.economy.cargodest.weight_scale_ind[0];
+
+ return weight;
+}
+
+/** Recalculate the link weights. */
+void UpdateLinkWeights(Town *t)
+{
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ uint weight_sum = 0;
+
+ if (t->cargo_links[cid].Length() == 0) continue;
+
+ t->cargo_links[cid].Begin()->amount.NewMonth();
+
+ /* Skip the special link for undetermined destinations. */
+ for (CargoLink *l = t->cargo_links[cid].Begin() + 1; l != t->cargo_links[cid].End(); l++) {
+ l->weight = l->dest->GetDestinationWeight(cid, l->weight_mod);
+ weight_sum += l->weight;
+
+ l->amount.NewMonth();
+ }
+
+ /* Limit the weight of the in-town link to at most 1/3 of the total weight. */
+ if (t->cargo_links[cid].Length() > 1 && t->cargo_links[cid].Get(1)->dest == t) {
+ uint new_weight = min(t->cargo_links[cid].Get(1)->weight, weight_sum / 3);
+ weight_sum -= t->cargo_links[cid].Get(1)->weight - new_weight;
+ t->cargo_links[cid].Get(1)->weight = new_weight;
+ }
+
+ /* Set weight for the undetermined destination link to random_dest_chance%. */
+ t->cargo_links[cid].Begin()->weight = weight_sum == 0 ? 1 : (weight_sum * _settings_game.economy.cargodest.random_dest_chance) / (100 - _settings_game.economy.cargodest.random_dest_chance);
+
+ t->cargo_links_weight[cid] = weight_sum + t->cargo_links[cid].Begin()->weight;
+ }
+}
+
+/** Recalculate the link weights. */
+void UpdateLinkWeights(CargoSourceSink *css)
+{
+ for (uint cid = 0; cid < NUM_CARGO; cid++) {
+ uint weight_sum = 0;
+
+ if (css->cargo_links[cid].Length() == 0) continue;
+
+ for (CargoLink *l = css->cargo_links[cid].Begin() + 1; l != css->cargo_links[cid].End(); l++) {
+ l->weight = l->dest->GetDestinationWeight(cid, l->weight_mod);
+ weight_sum += l->weight;
+
+ l->amount.NewMonth();
+ }
+
+ /* Set weight for the undetermined destination link to random_dest_chance%. */
+ css->cargo_links[cid].Begin()->weight = weight_sum == 0 ? 1 : (weight_sum * _settings_game.economy.cargodest.random_dest_chance) / (100 - _settings_game.economy.cargodest.random_dest_chance);
+
+ css->cargo_links_weight[cid] = weight_sum + css->cargo_links[cid].Begin()->weight;
+ }
+}
+
+/* virtual */ CargoSourceSink::~CargoSourceSink()
+{
+ /* Remove all demand links having us as a destination. */
+ Town *t;
+ FOR_ALL_TOWNS(t) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (t->HasLinkTo(cid, this)) {
+ t->cargo_links[cid].Erase(t->cargo_links[cid].Find(CargoLink(this)));
+ InvalidateWindowData(WC_TOWN_VIEW, t->index, 1);
+ }
+ }
+ }
+
+ Industry *ind;
+ FOR_ALL_INDUSTRIES(ind) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (ind->HasLinkTo(cid, this)) {
+ ind->cargo_links[cid].Erase(ind->cargo_links[cid].Find(CargoLink(this)));
+ InvalidateWindowData(WC_INDUSTRY_VIEW, ind->index, 1);
+ }
+ }
+ }
+
+ /* Decrement incoming link count for all link destinations. */
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) {
+ if (l->dest != NULL) l->dest->num_incoming_links[cid]--;
+ }
+ }
+}
+
+/** Rebuild the cached count of incoming cargo links. */
+void RebuildCargoLinkCounts()
+{
+ /* Clear incoming link count of all towns and industries. */
+ CargoSourceSink *source;
+ FOR_ALL_TOWNS(source) MemSetT(source->num_incoming_links, 0, lengthof(source->num_incoming_links));
+ FOR_ALL_INDUSTRIES(source) MemSetT(source->num_incoming_links, 0, lengthof(source->num_incoming_links));
+
+ /* Count all incoming links. */
+ FOR_ALL_TOWNS(source) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ for (CargoLink *l = source->cargo_links[cid].Begin(); l != source->cargo_links[cid].End(); l++) {
+ if (l->dest != NULL && l->dest != source) l->dest->num_incoming_links[cid]++;
+ }
+ }
+ }
+ FOR_ALL_INDUSTRIES(source) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ for (CargoLink *l = source->cargo_links[cid].Begin(); l != source->cargo_links[cid].End(); l++) {
+ if (l->dest != NULL && l->dest != source) l->dest->num_incoming_links[cid]++;
+ }
+ }
+ }
+}
+
+/** Update the demand links of all towns and industries. */
+void UpdateCargoLinks()
+{
+ if (_settings_game.economy.cargodest.mode == 0) return;
+
+ Town *t;
+ Industry *ind;
+
+ /* Remove links that have become invalid. */
+ FOR_ALL_TOWNS(t) RemoveInvalidLinks(t);
+ FOR_ALL_INDUSTRIES(ind) RemoveInvalidLinks(ind);
+
+ /* Recalculate the number of expected links. */
+ FOR_ALL_TOWNS(t) UpdateExpectedLinks(t);
+ FOR_ALL_INDUSTRIES(ind) UpdateExpectedLinks(ind);
+
+ /* Make sure each industry gets at at least some input cargo. */
+ FOR_ALL_INDUSTRIES(ind) AddMissingIndustryLinks(ind);
+
+ /* Update the demand link list. */
+ FOR_ALL_TOWNS(t) UpdateCargoLinks(t);
+ FOR_ALL_INDUSTRIES(ind) UpdateCargoLinks(ind);
+
+ /* Recalculate links weights. */
+ FOR_ALL_TOWNS(t) UpdateLinkWeights(t);
+ FOR_ALL_INDUSTRIES(ind) UpdateLinkWeights(ind);
+
+ InvalidateWindowClassesData(WC_TOWN_VIEW, 1);
+ InvalidateWindowClassesData(WC_INDUSTRY_VIEW, 1);
+}
+
+
+/** Get a random destination tile index for this cargo. */
+/* virtual */ TileArea Town::GetTileForDestination(CargoID cid)
+{
+ assert(this->cargo_accepted_weights[cid] != 0);
+
+ /* Randomly choose a target square. */
+ uint32 weight = RandomRange(this->cargo_accepted_weights[cid] - 1);
+
+ /* Iterate over all grid squares till the chosen square is found. */
+ uint32 weight_sum = 0;
+ const TileArea &area = this->cargo_accepted.GetArea();
+ TILE_AREA_LOOP(tile, area) {
+ if (TileX(tile) % AcceptanceMatrix::GRID == 0 && TileY(tile) % AcceptanceMatrix::GRID == 0) {
+ weight_sum += this->cargo_accepted_max_weight - (DistanceMax(this->xy_aligned, tile) / AcceptanceMatrix::GRID) * 2;
+ /* Return tile area inside the grid square if this is the chosen square. */
+ if (weight < weight_sum) return TileArea(tile + TileDiffXY(1, 1), 2, 2);
+ }
+ }
+
+ /* Something went wrong here... */
+ NOT_REACHED();
+ return TileArea();
+}
+
+/** Enumerate all towns accepting a specific cargo. */
+static bool EnumAcceptingTown(const Town *t, void *data)
+{
+ return t->AcceptsCargo((CargoID)(size_t)data);
+}
+
+/** Enumerate all industries accepting a specific cargo. */
+static bool EnumAcceptingIndustry(const Industry *ind, void *data)
+{
+ return ind->AcceptsCargo((CargoID)(size_t)data);
+}
+
+/**
+ * Move cargo to a station with destination information.
+ * @param cid Cargo type.
+ * @param amount[in,out] Cargo amount, return is actually moved cargo.
+ * @param source_type Type of the cargo source.
+ * @param source_id ID of the cargo source.
+ * @param all_stations List of possible target stations.
+ * @param src_tile Source tile.
+ * @return True if the cargo was handled has having destinations.
+ */
+bool MoveCargoWithDestinationToStation(CargoID cid, uint *amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile)
+{
+ if (!CargoHasDestinations(cid)) return false;
+
+ CargoSourceSink *source = NULL;
+ CargoSourceSink *dest = NULL;
+ CargoLink *l = NULL;
+
+ /* Company HQ doesn't have cargo links. */
+ if (source_type != ST_HEADQUARTERS) {
+ source = source_type == ST_TOWN ? static_cast(Town::Get(source_id)) : static_cast(Industry::Get(source_id));
+ /* No links yet? Create cargo without destination. */
+ if (source->cargo_links[cid].Length() == 0) return false;
+
+ /* Randomly choose a cargo link. */
+ uint weight = RandomRange(source->cargo_links_weight[cid] - 1);
+ uint cur_sum = 0;
+
+ for (l = source->cargo_links[cid].Begin(); l != source->cargo_links[cid].End(); l++) {
+ cur_sum += l->weight;
+ if (weight < cur_sum) {
+ /* Link is valid if it is random destination or accepts the cargo. */
+ if (l->dest == NULL || l->dest->AcceptsCargo(cid)) break;
+ }
+ }
+
+ if (l != source->cargo_links[cid].End()) {
+ l->amount.new_max += *amount;
+ dest = l->dest;
+ }
+ }
+
+ /* No destination or random destination? Try a random town. */
+ if (dest == NULL) dest = Town::GetRandom(&EnumAcceptingTown, INVALID_TOWN, (void *)(size_t)cid);
+ /* No luck? Try a random industry. */
+ if (dest == NULL) dest = Industry::GetRandom(&EnumAcceptingIndustry, INVALID_INDUSTRY, (void *)(size_t)cid);
+ /* Still no luck, nothing left to try. */
+ if (dest == NULL) return false;
+
+ /* Pick a tile that belongs to the destination. */
+ TileArea dest_area = dest->GetTileForDestination(cid);
+
+ /* Maximum pathfinder penalty based on distance. */
+ uint r = RandomRange(_settings_game.economy.cargodest.max_route_penalty[1]);
+ uint max_cost = _settings_game.economy.cargodest.max_route_penalty[0] + r;
+ max_cost *= DistanceManhattan(src_tile, dest_area.tile);
+
+ /* Randomly determine the routing flags for the packet.
+ * Right now only the two lowest bits are defined. */
+ byte flags = r & 0x3;
+
+ /* Find a route to the destination. */
+ StationID st, st_unload;
+ bool found = false;
+ RouteLink *route_link = YapfChooseRouteLink(cid, all_stations, src_tile, dest_area, &st, &st_unload, flags, &found, INVALID_ORDER, max_cost);
+
+ /* Cargo can move to the destination (it might be direct local
+ * delivery though), count it as actually transported. */
+ if (found && l != NULL) l->amount.new_act += *amount * (route_link == NULL ? 256 : Station::Get(st)->goods[cid].rating) / 256;
+
+ if (route_link == NULL) {
+ /* No suitable link found (or direct delivery), nothing
+ * is moved to the station. */
+ *amount = 0;
+ return true;
+ }
+
+ /* Move cargo to the station. */
+ Station *from = Station::Get(st);
+ *amount = UpdateStationWaiting(from, cid, *amount * from->goods[cid].rating, source_type, source_id, dest_area.tile, dest->GetType(), dest->GetID(), route_link->GetOriginOrderId(), st_unload, flags);
+
+ /* If this is a symmetric cargo type, try to generate some cargo going from
+ * destination to source as well. It's no error if that is not possible. */
+ if (IsSymmetricCargo(cid)) {
+ /* Try to find the matching cargo link back to the source. If no
+ * link is found, don't generate return traffic. */
+ CargoLink *back_link = dest->cargo_links[cid].Find(CargoLink(source));
+ if (back_link == dest->cargo_links[cid].End()) return true;
+
+ back_link->amount.new_max += *amount;
+
+ /* Find stations around the new source area. */
+ StationFinder stf(dest_area);
+ TileIndex tile = dest_area.tile;
+
+ /* The the new destination area. */
+ switch (source_type) {
+ case ST_INDUSTRY:
+ dest_area = static_cast(source)->location;
+ break;
+ case ST_TOWN:
+ dest_area = TileArea(src_tile, 2, 2);
+ break;
+ case ST_HEADQUARTERS:
+ dest_area = TileArea(Company::Get(source_id)->location_of_HQ, 2, 2);
+ break;
+ }
+
+ /* Find a route and update transported amount if found. */
+ route_link = YapfChooseRouteLink(cid, stf.GetStations(), tile, dest_area, &st, &st_unload, flags, &found, INVALID_ORDER, max_cost);
+ if (found) back_link->amount.new_act += *amount;
+
+ if (route_link != NULL) {
+ /* Found a back link, move to station. */
+ UpdateStationWaiting(Station::Get(st), cid, *amount * 256, dest->GetType(), dest->GetID(), dest_area.tile, source_type, source_id, route_link->GetOriginOrderId(), st_unload, flags);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Get the current best route link for a cargo packet at a station.
+ * @param st Station the route starts at.
+ * @param cid Cargo type.
+ * @param cp Cargo packet with destination information.
+ * @param order Incoming order of the cargo packet.
+ * @param[out] found Set to true if a route was found.
+ * @return The preferred route link or NULL if either no suitable link found or the station is the final destination.
+ */
+RouteLink *FindRouteLinkForCargo(Station *st, CargoID cid, const CargoPacket *cp, StationID *next_unload, OrderID order, bool *found)
+{
+ if (cp->DestinationID() == INVALID_SOURCE) return NULL;
+
+ StationList sl;
+ *sl.Append() = st;
+
+ TileArea area = (cp->DestinationType() == ST_INDUSTRY) ? Industry::Get(cp->DestinationID())->location : TileArea(cp->DestinationXY(), 2, 2);
+ return YapfChooseRouteLink(cid, &sl, st->xy, area, NULL, next_unload, cp->Flags(), found, order);
+}
+
+
+/* Initialize the RouteLink-pool */
+RouteLinkPool _routelink_pool("RouteLink");
+INSTANTIATE_POOL_METHODS(RouteLink)
+
+/**
+ * Update or create a single route link for a specific vehicle and cargo.
+ * @param v The vehicle.
+ * @param from Originating station.
+ * @param from_oid Originating order.
+ * @param to_id Destination station ID.
+ * @param to_oid Destination order.
+ * @param travel_time Travel time for the route.
+ */
+void UpdateVehicleRouteLinks(const Vehicle *v, Station *from, OrderID from_oid, StationID to_id, OrderID to_oid, uint32 travel_time)
+{
+ CargoID cid;
+ FOR_EACH_SET_CARGO_ID(cid, v->vcache.cached_cargo_mask) {
+ /* Skip cargo types that don't have destinations enabled. */
+ if (!CargoHasDestinations(cid)) continue;
+
+ RouteLinkList::iterator link;
+ for (link = from->goods[cid].routes.begin(); link != from->goods[cid].routes.end(); ++link) {
+ if ((*link)->GetOriginOrderId() == from_oid) {
+ /* Update destination if necessary. */
+ (*link)->SetDestination(to_id, to_oid);
+ (*link)->UpdateTravelTime(travel_time);
+ break;
+ }
+ }
+
+ /* No link found? Append a new one. */
+ if (link == from->goods[cid].routes.end() && RouteLink::CanAllocateItem()) {
+ from->goods[cid].routes.push_back(new RouteLink(to_id, from_oid, to_oid, v->owner, travel_time, v->type));
+ }
+ }
+}
+
+/**
+ * Update route links after a vehicle has arrived at a station.
+ * @param v The vehicle.
+ * @param arrived_at The station the vehicle arrived at.
+ */
+void UpdateVehicleRouteLinks(const Vehicle *v, StationID arrived_at)
+{
+ /* Only update links if we have valid previous station and orders. */
+ if (v->last_station_loaded == INVALID_STATION || v->last_order_id == INVALID_ORDER || v->current_order.index == INVALID_ORDER) return;
+ /* Loop? Not good. */
+ if (v->last_station_loaded == arrived_at) return;
+
+ Station *from = Station::Get(v->last_station_loaded);
+ Station *to = Station::Get(arrived_at);
+
+ /* Update incoming route link. */
+ UpdateVehicleRouteLinks(v, from, v->last_order_id, arrived_at, v->current_order.index, v->travel_time);
+
+ /* Update outgoing links. */
+ CargoID cid;
+ FOR_EACH_SET_CARGO_ID(cid, v->vcache.cached_cargo_mask) {
+ /* Skip cargo types that don't have destinations enabled. */
+ if (!CargoHasDestinations(cid)) continue;
+
+ for (RouteLinkList::iterator link = to->goods[cid].routes.begin(); link != to->goods[cid].routes.end(); ++link) {
+ if ((*link)->GetOriginOrderId() == v->current_order.index) {
+ (*link)->VehicleArrived();
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Pre-fill the route links from the orders of a vehicle.
+ * @param v The vehicle to get the orders from.
+ */
+void PrefillRouteLinks(const Vehicle *v)
+{
+ if (_settings_game.economy.cargodest.mode == 0) return;
+ if (v->orders.list == NULL || v->orders.list->GetNumOrders() < 2) return;
+
+ /* Can't pre-fill if the vehicle has refit or conditional orders. */
+ uint count = 0;
+ Order *order;
+ FOR_VEHICLE_ORDERS(v, order) {
+ if (order->IsType(OT_GOTO_DEPOT) && order->IsRefit()) return;
+ if (order->IsType(OT_CONDITIONAL)) return;
+ if ((order->IsType(OT_AUTOMATIC) || order->IsType(OT_GOTO_STATION)) && (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) count++;
+ }
+
+ /* Increment count by one to account for the circular nature of the order list. */
+ if (count > 0) count++;
+
+ /* Loop over all orders to update/pre-fill the route links. */
+ order = v->orders.list->GetFirstOrder();
+ Order *prev_order = NULL;
+ do {
+ /* Goto station or automatic order and not a go via-order, consider as destination. */
+ if ((order->IsType(OT_AUTOMATIC) || order->IsType(OT_GOTO_STATION)) && (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) {
+ /* Previous destination is set, create/update route links. */
+ if (prev_order != NULL && prev_order != order) {
+ Station *from = Station::Get(prev_order->GetDestination());
+ Station *to = Station::Get(order->GetDestination());
+ /* Use DistanceManhatten * 4 as a stupid guess for the initial travel time. */
+ UpdateVehicleRouteLinks(v, from, prev_order->index, order->GetDestination(), order->index, DistanceManhattan(from->xy, to->xy) * 4);
+ }
+
+ prev_order = order;
+ count--;
+ }
+
+ /* Get next order, wrap around if necessary. */
+ order = order->next;
+ if (order == NULL) order = v->orders.list->GetFirstOrder();
+ } while (count > 0);
+}
+
+/**
+ * Remove all route links to and from a station.
+ * @param station Station being removed.
+ */
+void InvalidateStationRouteLinks(Station *station)
+{
+ /* Delete all outgoing links. */
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ for (RouteLinkList::iterator link = station->goods[cid].routes.begin(); link != station->goods[cid].routes.end(); ++link) {
+ delete *link;
+ }
+ }
+
+ /* Delete all incoming link. */
+ Station *st_from;
+ FOR_ALL_STATIONS(st_from) {
+ if (st_from == station) continue;
+
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ /* Don't increment the iterator directly in the for loop as we don't want to increment when deleting a link. */
+ for (RouteLinkList::iterator link = st_from->goods[cid].routes.begin(); link != st_from->goods[cid].routes.end(); ) {
+ if ((*link)->GetDestination() == station->index) {
+ delete *link;
+ link = st_from->goods[cid].routes.erase(link);
+ } else {
+ ++link;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Remove all route links referencing an order.
+ * @param order The order being removed.
+ */
+void InvalidateOrderRouteLinks(OrderID order)
+{
+ Station *st;
+ FOR_ALL_STATIONS(st) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ /* Don't increment the iterator directly in the for loop as we don't want to increment when deleting a link. */
+ for (RouteLinkList::iterator link = st->goods[cid].routes.begin(); link != st->goods[cid].routes.end(); ) {
+ if ((*link)->GetOriginOrderId() == order || (*link)->GetDestOrderId() == order) {
+ delete *link;
+ link = st->goods[cid].routes.erase(link);
+ } else {
+ ++link;
+ }
+ }
+ }
+ }
+}
+
+/** Age and expire route links of a station. */
+void AgeRouteLinks(Station *st)
+{
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ /* Don't increment the iterator directly in the for loop as we don't want to increment when deleting a link. */
+ for (RouteLinkList::iterator link = st->goods[cid].routes.begin(); link != st->goods[cid].routes.end(); ) {
+ if ((*link)->wait_time++ > _settings_game.economy.cargodest.max_route_age) {
+ delete *link;
+ link = st->goods[cid].routes.erase(link);
+ } else {
+ ++link;
+ }
+ }
+ }
+}
diff --git a/src/cargodest_base.h b/src/cargodest_base.h
new file mode 100644
index 0000000..7b1d85c
--- /dev/null
+++ b/src/cargodest_base.h
@@ -0,0 +1,179 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file cargodest_base.h Classes and types for entities having cargo destinations. */
+
+#ifndef CARGODEST_BASE_H
+#define CARGODEST_BASE_H
+
+#include "cargodest_type.h"
+#include "cargo_type.h"
+#include "town_type.h"
+#include "core/smallvec_type.hpp"
+#include "core/pool_type.hpp"
+#include "order_type.h"
+#include "station_type.h"
+#include "company_type.h"
+#include "vehicle_type.h"
+
+struct CargoSourceSink;
+
+/** Information about a demand link for cargo. */
+struct CargoLink {
+ CargoSourceSink *dest; ///< Destination of the link.
+ TransportedCargoStat amount; ///< Transported cargo statistics.
+ uint weight; ///< Weight of this link.
+ byte weight_mod; ///< Weight modifier.
+
+ CargoLink(CargoSourceSink *d, byte mod = 1) : dest(d), weight(1), weight_mod(mod) {}
+
+ /* Compare two cargo links for inequality. */
+ bool operator !=(const CargoLink &other) const
+ {
+ return other.dest != dest;
+ }
+};
+
+/** An entity producing or accepting cargo with a destination. */
+struct CargoSourceSink {
+ /** List of destinations for each cargo type. */
+ SmallVector cargo_links[NUM_CARGO];
+ /** Sum of the destination weights for each cargo type. */
+ uint cargo_links_weight[NUM_CARGO];
+
+ /** NOSAVE: Desired link count for each cargo. */
+ uint16 num_links_expected[NUM_CARGO];
+
+ /** NOSAVE: Incoming link count for each cargo. */
+ uint num_incoming_links[NUM_CARGO];
+
+ virtual ~CargoSourceSink();
+
+ /** Get the type of this entity. */
+ virtual SourceType GetType() const = 0;
+ /** Get the source ID corresponding with this entity. */
+ virtual SourceID GetID() const = 0;
+
+ /**
+ * Test if a demand link to a destination exists.
+ * @param cid Cargo type for which a link should be searched.
+ * @param dest Destination to search for.
+ * @return True if a link to the destination is present.
+ */
+ bool HasLinkTo(CargoID cid, const CargoSourceSink *dest) const
+ {
+ return this->cargo_links[cid].Contains(CargoLink(const_cast(dest)));
+ }
+
+ /** Is this cargo accepted? */
+ virtual bool AcceptsCargo(CargoID cid) const = 0;
+ /** Is this cargo produced? */
+ virtual bool SuppliesCargo(CargoID cid) const = 0;
+
+ /** Get the link weight for this as a destination for a specific cargo. */
+ virtual uint GetDestinationWeight(CargoID cid, byte weight_mod) const = 0;
+
+ /** Create the special cargo links for a cargo if not already present. */
+ virtual void CreateSpecialLinks(CargoID cid);
+
+ /** Get a random destination tile index for this cargo. */
+ virtual TileArea GetTileForDestination(CargoID cid) = 0;
+
+ void SaveCargoSourceSink();
+ void LoadCargoSourceSink();
+ void PtrsCargoSourceSink();
+};
+
+
+/** Pool of route links. */
+typedef Pool RouteLinkPool;
+extern RouteLinkPool _routelink_pool;
+
+/** Holds information about a route service between two stations. */
+struct RouteLink : public RouteLinkPool::PoolItem<&_routelink_pool> {
+private:
+ friend const struct SaveLoad *GetRouteLinkDescription(); ///< Saving and loading of route links.
+ friend void AgeRouteLinks(Station *st);
+
+ StationID dest; ///< Destination station id.
+ OrderID prev_order; ///< Id of the order the vehicle had when arriving at the origin.
+ OrderID next_order; ///< Id of the order the vehicle will leave the station with.
+ OwnerByte owner; ///< Owner of the vehicle of the link.
+ VehicleTypeByte vtype; ///< Vehicle type traveling this link.
+ uint32 travel_time; ///< Average travel duration of this link.
+ uint16 wait_time; ///< Days since the last vehicle traveled this link.
+
+public:
+ /** Constructor */
+ RouteLink(StationID dest = INVALID_STATION, OrderID prev_order = INVALID_ORDER, OrderID next_order = INVALID_ORDER, Owner owner = INVALID_OWNER, uint32 travel_time = 0, VehicleType vtype = VEH_INVALID)
+ : dest(dest), prev_order(prev_order), next_order(next_order), travel_time(travel_time), wait_time(0)
+ {
+ this->owner = owner;
+ this->vtype = vtype;
+ }
+
+ ~RouteLink() {}
+
+ /** Get the target station of this link. */
+ inline StationID GetDestination() const { return this->dest; }
+
+ /** Get the order id that lead to the origin station. */
+ inline OrderID GetOriginOrderId() const { return this->prev_order; }
+
+ /** Get the order id that lead to the destination station. */
+ inline OrderID GetDestOrderId() const { return this->next_order; }
+
+ /** Get the owner of this link. */
+ inline Owner GetOwner() const { return this->owner; }
+
+ /** Get the type of the vehicles on this link. */
+ inline VehicleType GetVehicleType() const { return this->vtype; }
+
+ /** Get the travel time of this link. */
+ inline uint32 GetTravelTime() const { return this->travel_time; }
+
+ /** Get the wait time at the origin station. */
+ inline uint16 GetWaitTime() const { return this->wait_time; }
+
+ /** Update the destination of the route link. */
+ inline void SetDestination(StationID dest_id, OrderID dest_order_id)
+ {
+ this->dest = dest_id;
+ this->next_order = dest_order_id;
+ }
+
+ /** Update the travel time with a new travel time. */
+ void UpdateTravelTime(uint32 new_time)
+ {
+ /* Weighted average so that a single late vehicle will not skew the time. */
+ this->travel_time = (3 * this->travel_time + new_time) / 4;
+ }
+
+ /** A vehicle arrived at the origin of the link, reset waiting time. */
+ void VehicleArrived()
+ {
+ this->wait_time = 0;
+ }
+};
+
+
+/**
+ * Iterate over all valid route links from a given start.
+ * @param var The variable to use as the "iterator".
+ * @param start The #RouteLinkID to start the iteration from.
+ */
+#define FOR_ALL_ROUTELINKS_FROM(var, start) FOR_ALL_ITEMS_FROM(RouteLink, routelink_index, var, start)
+
+/**
+ * Iterate over all valid route links.
+ * @param var The variable to use as the "iterator".
+ */
+#define FOR_ALL_ROUTELINKS(var) FOR_ALL_ROUTELINKS_FROM(var, 0)
+
+#endif /* CARGODEST_BASE_H */
diff --git a/src/cargodest_func.h b/src/cargodest_func.h
new file mode 100644
index 0000000..846069d
--- /dev/null
+++ b/src/cargodest_func.h
@@ -0,0 +1,35 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file cargodest_func.h Functions related to cargo destinations. */
+
+#ifndef CARGODEST_FUNC_H
+#define CARGODEST_FUNC_H
+
+#include "cargodest_type.h"
+#include "cargo_type.h"
+#include "vehicle_type.h"
+#include "station_type.h"
+#include "order_type.h"
+
+bool CargoHasDestinations(CargoID cid);
+
+RouteLink *FindRouteLinkForCargo(Station *st, CargoID cid, const struct CargoPacket *cp, StationID *next_unload, OrderID order = INVALID_ORDER, bool *found = NULL);
+bool MoveCargoWithDestinationToStation(CargoID cid, uint *amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile);
+
+void UpdateVehicleRouteLinks(const Vehicle *v, StationID arrived_at);
+void PrefillRouteLinks(const Vehicle *v);
+void InvalidateStationRouteLinks(Station *station);
+void InvalidateOrderRouteLinks(OrderID order);
+void AgeRouteLinks(Station *st);
+
+void RebuildCargoLinkCounts();
+void UpdateCargoLinks();
+
+#endif /* CARGODEST_FUNC_H */
diff --git a/src/cargodest_gui.cpp b/src/cargodest_gui.cpp
new file mode 100644
index 0000000..bad4567
--- /dev/null
+++ b/src/cargodest_gui.cpp
@@ -0,0 +1,176 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file cargodest_gui.cpp GUI for cargo destinations. */
+
+#include "stdafx.h"
+#include "window_gui.h"
+#include "gfx_func.h"
+#include "strings_func.h"
+#include "cargodest_base.h"
+#include "cargodest_gui.h"
+#include "town.h"
+#include "industry.h"
+#include "string_func.h"
+#include "gui.h"
+#include "viewport_func.h"
+
+#include "table/strings.h"
+
+static const CargoSourceSink *_cur_cargo_source;
+
+int CDECL CargoLinkSorter(const GUICargoLink *a, const GUICargoLink *b)
+{
+ /* Sort by cargo type. */
+ if (a->cid != b->cid) return a->cid < b->cid ? -1 : +1;
+
+ /* Sort unspecified destination links always last. */
+ if (a->link->dest == NULL) return +1;
+ if (b->link->dest == NULL) return -1;
+
+ /* Sort link with the current source as destination first. */
+ if (a->link->dest == _cur_cargo_source) return -1;
+ if (b->link->dest == _cur_cargo_source) return +1;
+
+ /* Sort towns before industries. */
+ if (a->link->dest->GetType() != b->link->dest->GetType()) {
+ return a->link->dest->GetType() < b->link->dest->GetType() ? +1 : -1;
+ }
+
+ /* Sort by name. */
+ static const CargoLink *last_b = NULL;
+ static char last_name[64];
+
+ char name[64];
+ SetDParam(0, a->link->dest->GetID());
+ GetString(name, a->link->dest->GetType() == ST_TOWN ? STR_TOWN_NAME : STR_INDUSTRY_NAME, lastof(name));
+
+ /* Cache name lookup of 'b', as the sorter is often called
+ * multiple times with the same 'b'. */
+ if (b->link != last_b) {
+ last_b = b->link;
+
+ SetDParam(0, b->link->dest->GetID());
+ GetString(last_name, b->link->dest->GetType() == ST_TOWN ? STR_TOWN_NAME : STR_INDUSTRY_NAME, lastof(last_name));
+ }
+
+ return strcmp(name, last_name);
+}
+
+CargoDestinationList::CargoDestinationList(const CargoSourceSink *o) : obj(o)
+{
+ this->InvalidateData();
+}
+
+/** Rebuild the link list from the source object. */
+void CargoDestinationList::RebuildList()
+{
+ if (!this->link_list.NeedRebuild()) return;
+
+ this->link_list.Clear();
+ for (CargoID i = 0; i < lengthof(this->obj->cargo_links); i++) {
+ for (const CargoLink *l = this->obj->cargo_links[i].Begin(); l != this->obj->cargo_links[i].End(); l++) {
+ *this->link_list.Append() = GUICargoLink(i, l);
+ }
+ }
+
+ this->link_list.Compact();
+ this->link_list.RebuildDone();
+}
+
+/** Sort the link list. */
+void CargoDestinationList::SortList()
+{
+ _cur_cargo_source = this->obj;
+ this->link_list.Sort(&CargoLinkSorter);
+}
+
+/** Rebuild the list, e.g. when a new cargo link was added. */
+void CargoDestinationList::InvalidateData()
+{
+ this->link_list.ForceRebuild();
+ this->RebuildList();
+ this->SortList();
+}
+
+/** Resort the list, e.g. when a town is renamed. */
+void CargoDestinationList::Resort()
+{
+ this->link_list.ForceResort();
+ this->SortList();
+}
+
+/**
+ * Get the height needed to display the destination list.
+ * @param obj Object to display the destinations of.
+ * @return Height needed for display.
+ */
+uint CargoDestinationList::GetListHeight() const
+{
+ uint lines = 1 + this->link_list.Length();
+ return lines > 1 ? WD_PAR_VSEP_WIDE + lines * FONT_HEIGHT_NORMAL : 0;
+}
+
+/**
+ * Draw the destination list.
+ * @param left The left most position to draw on.
+ * @param right The right most position to draw on.
+ * @param y The top position to start drawing.
+ * @return New \c y value below the drawn text.
+ */
+uint CargoDestinationList::DrawList(uint left, uint right, uint y) const
+{
+ if (this->link_list.Length() == 0) return y;
+
+ DrawString(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += WD_PAR_VSEP_WIDE + FONT_HEIGHT_NORMAL, STR_VIEW_CARGO_LAST_MONTH_OUT);
+
+ for (const GUICargoLink *l = this->link_list.Begin(); l != this->link_list.End(); l++) {
+ SetDParam(0, l->cid);
+ SetDParam(1, l->link->amount.old_act);
+ SetDParam(2, l->cid);
+ SetDParam(3, l->link->amount.old_max);
+
+ /* Select string according to the destination type. */
+ if (l->link->dest == NULL) {
+ DrawString(left + 2*WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += FONT_HEIGHT_NORMAL, STR_VIEW_CARGO_LAST_MONTH_OTHER);
+ } else if (l->link->dest == obj) {
+ DrawString(left + 2*WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += FONT_HEIGHT_NORMAL, STR_VIEW_CARGO_LAST_MONTH_LOCAL);
+ } else {
+ SetDParam(4, l->link->dest->GetID());
+ DrawString(left + 2*WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, y += FONT_HEIGHT_NORMAL, l->link->dest->GetType() == ST_TOWN ? STR_VIEW_CARGO_LAST_MONTH_TOWN : STR_VIEW_CARGO_LAST_MONTH_INDUSTRY);
+ }
+ }
+
+ return y + FONT_HEIGHT_NORMAL;
+}
+
+/**
+ * Handle click event onto the destination list.
+ * @param y Position of the click in relative to the top of the destination list.
+ */
+void CargoDestinationList::OnClick(uint y) const
+{
+ /* Subtract caption height. */
+ y -= WD_PAR_VSEP_WIDE + 2*FONT_HEIGHT_NORMAL;
+
+ /* Calculate line from click pos. */
+ y /= FONT_HEIGHT_NORMAL;
+ if (y >= this->link_list.Length()) return;
+
+ /* Move viewpoint to the position of the destination. */
+ const CargoLink *l = this->link_list[y].link;
+ if (l->dest == NULL) return;
+
+ TileIndex xy = l->dest->GetType() == ST_TOWN ? static_cast(l->dest)->xy : static_cast(l->dest)->location.tile;
+ if (_ctrl_pressed) {
+ ShowExtraViewPortWindow(xy);
+ } else {
+ ScrollMainWindowToTile(xy);
+ }
+}
diff --git a/src/cargodest_gui.h b/src/cargodest_gui.h
new file mode 100644
index 0000000..4c7915e
--- /dev/null
+++ b/src/cargodest_gui.h
@@ -0,0 +1,48 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file cargodest_gui.h Functions/types related to the cargodest GUI. */
+
+#ifndef CARGODEST_GUI_H
+#define CARGODEST_GUI_H
+
+#include "cargodest_base.h"
+#include "sortlist_type.h"
+
+/** Helper encapsulating a #CargoLink. */
+struct GUICargoLink {
+ CargoID cid; ///< Cargo ID of this link.
+ const CargoLink *link; ///< Pointer to the link.
+
+ GUICargoLink(CargoID c, const CargoLink *l) : cid(c), link(l) {}
+};
+
+/** Sorted list of demand destinations for displaying. */
+class CargoDestinationList
+{
+private:
+ const CargoSourceSink *obj; ///< The object which destinations are displayed.
+ GUIList link_list; ///< Sorted list of destinations.
+
+ void RebuildList();
+ void SortList();
+
+public:
+ CargoDestinationList(const CargoSourceSink *o);
+
+ void InvalidateData();
+ void Resort();
+
+ uint GetListHeight() const;
+ uint DrawList(uint left, uint right, uint y) const;
+
+ void OnClick(uint y) const;
+};
+
+#endif /* CARGODEST_GUI_H */
diff --git a/src/cargodest_type.h b/src/cargodest_type.h
new file mode 100644
index 0000000..d2a7846
--- /dev/null
+++ b/src/cargodest_type.h
@@ -0,0 +1,31 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file cargodest_type.h Type definitions for cargo destinations. */
+
+#ifndef CARGODEST_TYPE_H
+#define CARGODEST_TYPE_H
+
+/** Flags specifying which cargos are routed to specific destinations. */
+enum CargoRoutingMode {
+ CRM_TOWN_CARGOS = 0, ///< Passengers and mail have destinations.
+ CRM_INDUSTRY_CARGOS = 1, ///< Cargos produced by industries have destinations.
+};
+
+/** Flags specifying how cargo should be routed. */
+enum RoutingFlags {
+ RF_WANT_FAST, ///< Cargo wants to travel as fast as possible.
+ RF_WANT_CHEAP, ///< Cargo wants to travel as cheap as possible.
+};
+
+/** Unique identifier for a routing link. */
+typedef uint32 RouteLinkID;
+struct RouteLink;
+
+#endif /* CARGODEST_TYPE_H */
diff --git a/src/cargopacket.cpp b/src/cargopacket.cpp
index 55c62cd..dd2e766 100644
--- a/src/cargopacket.cpp
+++ b/src/cargopacket.cpp
@@ -12,6 +12,10 @@
#include "stdafx.h"
#include "core/pool_func.hpp"
#include "economy_base.h"
+#include "cargodest_func.h"
+#include "cargodest_base.h"
+#include "settings_type.h"
+#include "station_base.h"
/* Initialize the cargopacket-pool */
CargoPacketPool _cargopacket_pool("CargoPacket");
@@ -24,6 +28,12 @@ CargoPacket::CargoPacket()
{
this->source_type = ST_INDUSTRY;
this->source_id = INVALID_SOURCE;
+ this->dest_xy = INVALID_TILE;
+ this->dest_id = INVALID_SOURCE;
+ this->dest_type = ST_INDUSTRY;
+ this->flags = 0;
+ this->next_order = INVALID_ORDER;
+ this->next_station = INVALID_STATION;
}
/**
@@ -33,21 +43,33 @@ CargoPacket::CargoPacket()
* @param count Number of cargo entities to put in this packet.
* @param source_type 'Type' of source the packet comes from (for subsidies).
* @param source_id Actual source of the packet (for subsidies).
+ * @param dest_xy Destination location of the packet.
+ * @param dest_type 'Type' of the destination.
+ * @param dest_id Actual destination of the packet.
+ * @param next_order Desired next hop of the packet.
+ * @param next_station Station to unload the packet next.
+ * @param flags Routing flags of the packet.
* @pre count != 0
* @note We have to zero memory ourselves here because we are using a 'new'
* that, in contrary to all other pools, does not memset to 0.
*/
-CargoPacket::CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id) :
+CargoPacket::CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id, TileIndex dest_xy, SourceType dest_type, SourceID dest_id, OrderID next_order, StationID next_station, byte flags) :
feeder_share(0),
count(count),
days_in_transit(0),
source_id(source_id),
source(source),
source_xy(source_xy),
- loaded_at_xy(0)
+ loaded_at_xy(0),
+ dest_xy(dest_xy),
+ dest_id(dest_id),
+ flags(flags),
+ next_order(next_order),
+ next_station(next_station)
{
assert(count != 0);
this->source_type = source_type;
+ this->dest_type = dest_type;
}
/**
@@ -61,20 +83,32 @@ CargoPacket::CargoPacket(StationID source, TileIndex source_xy, uint16 count, So
* @param feeder_share Feeder share the packet has already accumulated.
* @param source_type 'Type' of source the packet comes from (for subsidies).
* @param source_id Actual source of the packet (for subsidies).
+ * @param dest_xy Destination location of the packet.
+ * @param dest_type 'Type' of the destination.
+ * @param dest_id Actual destination of the packet.
+ * @param next_order Desired next hop of the packet.
+ * @param next_station Station to unload the packet next.
+ * @param flags Routing flags of the packet.
* @note We have to zero memory ourselves here because we are using a 'new'
* that, in contrary to all other pools, does not memset to 0.
*/
-CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share, SourceType source_type, SourceID source_id) :
+CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share, SourceType source_type, SourceID source_id, TileIndex dest_xy, SourceType dest_type, SourceID dest_id, OrderID next_order, StationID next_station, byte flags) :
feeder_share(feeder_share),
count(count),
days_in_transit(days_in_transit),
source_id(source_id),
source(source),
source_xy(source_xy),
- loaded_at_xy(loaded_at_xy)
+ loaded_at_xy(loaded_at_xy),
+ dest_xy(dest_xy),
+ dest_id(dest_id),
+ flags(flags),
+ next_order(next_order),
+ next_station(next_station)
{
assert(count != 0);
this->source_type = source_type;
+ this->dest_type = dest_type;
}
/**
@@ -87,7 +121,7 @@ FORCEINLINE CargoPacket *CargoPacket::Split(uint new_size)
if (!CargoPacket::CanAllocateItem()) return NULL;
Money fs = this->feeder_share * new_size / static_cast(this->count);
- CargoPacket *cp_new = new CargoPacket(new_size, this->days_in_transit, this->source, this->source_xy, this->loaded_at_xy, fs, this->source_type, this->source_id);
+ CargoPacket *cp_new = new CargoPacket(new_size, this->days_in_transit, this->source, this->source_xy, this->loaded_at_xy, fs, this->source_type, this->source_id, this->dest_xy, this->dest_type, this->dest_id, this->next_order, this->next_station, this->flags);
this->feeder_share -= fs;
this->count -= new_size;
return cp_new;
@@ -111,9 +145,16 @@ FORCEINLINE void CargoPacket::Merge(CargoPacket *cp)
*/
/* static */ void CargoPacket::InvalidateAllFrom(SourceType src_type, SourceID src)
{
+ /* Invalidate next hop of all packets that loose their destination. */
+ StationCargoList::InvalidateAllTo(src_type, src);
+
CargoPacket *cp;
FOR_ALL_CARGOPACKETS(cp) {
if (cp->source_type == src_type && cp->source_id == src) cp->source_id = INVALID_SOURCE;
+ if (cp->dest_type == src_type && cp->dest_id == src) {
+ cp->dest_id = INVALID_SOURCE;
+ cp->dest_xy = INVALID_TILE;
+ }
}
}
@@ -126,6 +167,7 @@ FORCEINLINE void CargoPacket::Merge(CargoPacket *cp)
CargoPacket *cp;
FOR_ALL_CARGOPACKETS(cp) {
if (cp->source == sid) cp->source = INVALID_STATION;
+ if (cp->next_station == sid) cp->next_station = INVALID_STATION;
}
}
@@ -229,6 +271,7 @@ void CargoList::Truncate(uint max_remaining)
uint diff = local_count - max_remaining;
this->count -= diff;
this->cargo_days_in_transit -= cp->days_in_transit * diff;
+ static_cast(this)->RemoveFromCacheLocal(cp, diff);
cp->count = max_remaining;
max_remaining = 0;
} else {
@@ -251,9 +294,11 @@ void CargoList::Truncate(uint max_remaining)
* @param data Depending on mta the data of this variable differs:
* - MTA_FINAL_DELIVERY - Station ID of packet's origin not to remove.
* - MTA_CARGO_LOAD - Station's tile index of load.
- * - MTA_TRANSFER - Unused.
- * - MTA_UNLOAD - Unused.
+ * - MTA_TRANSFER - Station ID unloading to.
+ * - MTA_UNLOAD - Station ID unloading to.
* @param payment The payment helper.
+ * @param cur_order The current order of the loading vehicle.
+ * @param did_transfer Set to true if some cargo was transfered.
*
* @pre mta == MTA_FINAL_DELIVERY || dest != NULL
* @pre mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL
@@ -261,26 +306,79 @@ void CargoList::Truncate(uint max_remaining)
*/
template
template
-bool CargoList::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta, CargoPayment *payment, uint data)
+bool CargoList::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta, CargoPayment *payment, uint data, OrderID cur_order, bool *did_transfer)
{
assert(mta == MTA_FINAL_DELIVERY || dest != NULL);
assert(mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL);
+restart:;
Iterator it(this->packets.begin());
while (it != this->packets.end() && max_move > 0) {
CargoPacket *cp = *it;
- if (cp->source == data && mta == MTA_FINAL_DELIVERY) {
+ MoveToAction cp_mta = mta;
+ OrderID current_next_order = cp->NextHop();
+ StationID current_next_unload = cp->NextStation();
+
+ if (cp_mta == MTA_CARGO_LOAD) {
+ /* Loading and not for the current vehicle? Skip. */
+ if (current_next_order != cur_order) {
+ ++it;
+ continue;
+ }
+ }
+
+ /* Has this packet a destination and are we unloading to a station (not autoreplace)? */
+ if (cp->DestinationID() != INVALID_SOURCE && cp_mta != MTA_CARGO_LOAD && payment != NULL) {
+ /* Not forced unload and not for unloading at this station? Skip the packet. */
+ if (cp_mta != MTA_UNLOAD && cp->NextStation() != INVALID_STATION && cp->NextStation() != data) {
+ ++it;
+ continue;
+ }
+
+ Station *st = Station::Get(data);
+
+ bool found;
+ StationID next_unload;
+ RouteLink *link = FindRouteLinkForCargo(st, payment->ct, cp, &next_unload, cur_order, &found);
+ if (!found) {
+ /* Sorry, link to destination vanished, make cargo disappear. */
+ static_cast(this)->RemoveFromCache(cp);
+ delete cp;
+ it = this->packets.erase(it);
+ continue;
+ }
+
+ if (link != NULL) {
+ /* Not final destination. */
+ if (link->GetOriginOrderId() == cur_order && cp_mta != MTA_UNLOAD) {
+ /* Cargo should stay on the vehicle and not forced unloading? Skip. */
+ ++it;
+ continue;
+ }
+ /* Force transfer and update next hop. */
+ cp_mta = MTA_TRANSFER;
+ current_next_order = link->GetOriginOrderId();
+ current_next_unload = next_unload;
+ } else {
+ /* Final destination, deliver. */
+ cp_mta = MTA_FINAL_DELIVERY;
+ }
+ } else if (cp->source == data && cp_mta == MTA_FINAL_DELIVERY) {
/* Skip cargo that originated from this station. */
++it;
continue;
}
+ if (did_transfer != NULL && cp_mta == MTA_TRANSFER) *did_transfer = true;
+
if (cp->count <= max_move) {
/* Can move the complete packet */
max_move -= cp->count;
this->packets.erase(it++);
static_cast(this)->RemoveFromCache(cp);
- switch (mta) {
+ cp->next_order = current_next_order;
+ cp->next_station = current_next_unload;
+ switch (cp_mta) {
case MTA_FINAL_DELIVERY:
payment->PayFinalDelivery(cp, cp->count);
delete cp;
@@ -302,7 +400,7 @@ bool CargoList::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta
}
/* Can move only part of the packet */
- if (mta == MTA_FINAL_DELIVERY) {
+ if (cp_mta == MTA_FINAL_DELIVERY) {
/* Final delivery doesn't need package splitting. */
payment->PayFinalDelivery(cp, max_move);
@@ -323,11 +421,13 @@ bool CargoList::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta
if (cp_new == NULL) return false;
static_cast(this)->RemoveFromCache(cp_new); // this reflects the changes in cp.
+ cp_new->next_order = current_next_order;
+ cp_new->next_station = current_next_unload;
- if (mta == MTA_TRANSFER) {
+ if (cp_mta == MTA_TRANSFER) {
/* Add the feeder share before inserting in dest. */
cp_new->feeder_share += payment->PayTransfer(cp_new, max_move);
- } else if (mta == MTA_CARGO_LOAD) {
+ } else if (cp_mta == MTA_CARGO_LOAD) {
cp_new->loaded_at_xy = data;
}
@@ -337,6 +437,12 @@ bool CargoList::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta
max_move = 0;
}
+ if (max_move > 0 && mta == MTA_CARGO_LOAD && cur_order != INVALID_ORDER) {
+ /* We loaded all packets for the next hop, now load all packets without destination. */
+ cur_order = INVALID_ORDER;
+ goto restart;
+ }
+
return it != packets.end();
}
@@ -396,6 +502,130 @@ void VehicleCargoList::InvalidateCache()
this->Parent::InvalidateCache();
}
+/** Invalidate next unload station of all cargo packets. */
+void VehicleCargoList::InvalidateNextStation()
+{
+ for (ConstIterator it(this->packets.begin()); it != this->packets.end(); it++) {
+ (*it)->next_station = INVALID_STATION;
+ }
+}
+
+/**
+ * Update the local next-hop count cache.
+ * @param cp Packet the be removed.
+ * @param amount Cargo amount to be removed.
+ */
+void StationCargoList::RemoveFromCacheLocal(const CargoPacket *cp, uint amount)
+{
+ this->order_cache[cp->next_order] -= amount;
+ if (this->order_cache[cp->next_order] == 0) this->order_cache.erase(cp->next_order);
+}
+
+/**
+ * Update the cached values to reflect the removal of this packet.
+ * Decreases count and days_in_transit.
+ * @param cp Packet to be removed from cache.
+ */
+void StationCargoList::RemoveFromCache(const CargoPacket *cp)
+{
+ this->RemoveFromCacheLocal(cp, cp->count);
+ this->Parent::RemoveFromCache(cp);
+}
+
+/**
+ * Update the cache to reflect adding of this packet.
+ * Increases count and days_in_transit.
+ * @param cp New packet to be inserted.
+ */
+void StationCargoList::AddToCache(const CargoPacket *cp)
+{
+ this->order_cache[cp->next_order] += cp->count;
+ this->Parent::AddToCache(cp);
+}
+
+/** Invalidates the cached data and rebuild it. */
+void StationCargoList::InvalidateCache()
+{
+ this->order_cache.clear();
+ this->Parent::InvalidateCache();
+}
+
+/**
+ * Recompute the desired next hop of all cargo packets.
+ * @param st Station of this list.
+ * @param cid Cargo type of this list.
+ */
+void StationCargoList::UpdateCargoNextHop(Station *st, CargoID cid, OrderID oid)
+{
+ int count = 0;
+ StationCargoList::Iterator iter;
+ for (iter = this->packets.begin(); count < this->next_start + _settings_game.economy.cargodest.route_recalc_chunk && iter != this->packets.end(); count++) {
+ if (count < this->next_start) continue;
+ if ((*iter)->DestinationID() != INVALID_SOURCE && (oid == INVALID_ORDER || (*iter)->NextHop() == oid)) {
+ StationID next_unload;
+ RouteLink *l = FindRouteLinkForCargo(st, cid, *iter, &next_unload);
+ if (l != NULL) {
+ /* Update next hop if needed. */
+ (*iter)->next_station = next_unload;
+ if ((*iter)->next_order != l->GetOriginOrderId()) {
+ this->RemoveFromCache(*iter);
+ (*iter)->next_order = l->GetOriginOrderId();
+ this->AddToCache(*iter);
+ }
+ ++iter;
+ } else {
+ /* No route to target anymore? Drop packet. */
+ this->RemoveFromCache(*iter);
+ delete *iter;
+ iter = this->packets.erase(iter);
+ }
+ } else {
+ ++iter;
+ }
+ }
+
+ /* Update start counter for next loop. */
+ this->next_start = (iter == this->packets.end()) ? 0 : count;
+}
+
+/**
+ * Invalidate and update all cargo packets with a specific next hop.
+ * @param order The order that was removed.
+ */
+/* static */ void StationCargoList::InvalidateNextHop(OrderID order)
+{
+ Station *st;
+ FOR_ALL_STATIONS(st) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ /* Update the next hop of all cargo with the given order. */
+ st->goods[cid].cargo.UpdateCargoNextHop(st, cid, order);
+ }
+ }
+}
+
+/**
+ * Invalidate the next hop of all cargo packets going to a given destination.
+ * @param type Type of destination.
+ * @param dest Index of destination.
+ */
+/* static */ void StationCargoList::InvalidateAllTo(SourceType type, SourceID dest)
+{
+ Station *st;
+ FOR_ALL_STATIONS(st) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ for (StationCargoList::Iterator it = st->goods[cid].cargo.packets.begin(); it != st->goods[cid].cargo.packets.end(); ++it) {
+ if ((*it)->dest_type == type && (*it)->dest_id == dest) {
+ /* Invalidate the next hop. */
+ st->goods[cid].cargo.RemoveFromCache(*it);
+ (*it)->next_order = INVALID_ORDER;
+ (*it)->next_station = INVALID_STATION;
+ st->goods[cid].cargo.AddToCache(*it);
+ }
+ }
+ }
+ }
+}
+
/*
* We have to instantiate everything we want to be usable.
*/
@@ -403,8 +633,8 @@ template class CargoList;
template class CargoList;
/** Autoreplace Vehicle -> Vehicle 'transfer'. */
-template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
+template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data, OrderID cur_order, bool *did_transfer);
/** Cargo unloading at a station. */
-template bool CargoList::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
+template bool CargoList::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data, OrderID cur_order, bool *did_transfer);
/** Cargo loading at a station. */
-template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
+template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data, OrderID cur_order, bool *did_transfer);
diff --git a/src/cargopacket.h b/src/cargopacket.h
index 0f2e461..2460744 100644
--- a/src/cargopacket.h
+++ b/src/cargopacket.h
@@ -17,7 +17,9 @@
#include "station_type.h"
#include "cargo_type.h"
#include "vehicle_type.h"
+#include "order_type.h"
#include
+#include