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..e27c990 --- /dev/null +++ b/src/cargodest.cpp @@ -0,0 +1,1199 @@ +/* $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" + + +/* Possible link weight modifiers. */ +static const byte LWM_ANYWHERE = 1; ///< Weight modifier for undetermined destinations. +static const byte LWM_TONY_ANY = 2; ///< Default weight modifier for towns. +static const byte LWM_TONY_BIG = 3; ///< Weight modifier for big towns. +static const byte LWM_TONY_CITY = 4; ///< Weight modifier for cities. +static const byte LWM_TONY_NEARBY = 5; ///< Weight modifier for nearby towns. +static const byte LWM_INTOWN = 8; ///< Weight modifier for in-town links. +static const byte LWM_IND_ANY = 2; ///< Default weight modifier for industries. +static const byte LWM_IND_NEARBY = 3; ///< Weight modifier for nearby industries. +static const byte LWM_IND_PRODUCING = 4; ///< Weight modifier for producing industries. + +static const uint MAX_EXTRA_LINKS = 2; ///< Number of extra links allowed. +static const uint MAX_IND_STOCKPILE = 1000; ///< Maximum stockpile to consider for industry link weight. + +static const uint BASE_TOWN_LINKS = 0; ///< Index into _settings_game.economy.cargodest.base_town_links for normal cargo +static const uint BASE_TOWN_LINKS_SYMM = 1; ///< Index into _settings_game.economy.cargodest.base_town_links for symmteric cargos +static const uint BASE_IND_LINKS = 0; ///< Index into _settings_game.economy.cargodest.base_ind_links for normal cargo +static const uint BASE_IND_LINKS_TOWN = 1; ///< Index into _settings_game.economy.cargodest.base_ind_links for town cargos +static const uint BASE_IND_LINKS_SYMM = 2; ///< Index into _settings_game.economy.cargodest.base_ind_links for symmetric cargos +static const uint BIG_TOWN_POP_MAIL = 0; ///< Index into _settings_game.economy.cargodest.big_town_pop for mail +static const uint BIG_TOWN_POP_PAX = 1; ///< Index into _settings_game.economy.cargodest.big_town_pop for passengers +static const uint SCALE_TOWN = 0; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for normal cargo +static const uint SCALE_TOWN_BIG = 1; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for normal cargo of big towns +static const uint SCALE_TOWN_PAX = 2; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for passengers +static const uint SCALE_TOWN_BIG_PAX = 3; ///< Index into _settings_game.economy.cargodest.pop_scale_town/weight_scale_town for passengers of big towns +static const uint CARGO_SCALE_IND = 0; ///< Index into _settings_game.economy.cargodest.cargo_scale_ind for normal cargo +static const uint CARGO_SCALE_IND_TOWN = 1; ///< Index into _settings_game.economy.cargodest.cargo_scale_ind for town cargos +static const uint MIN_WEIGHT_TOWN = 0; ///< Index into _settings_game.economy.cargodest.min_weight_town for normal cargo +static const uint MIN_WEIGHT_TOWN_PAX = 1; ///< Index into _settings_game.economy.cargodest.min_weight_town for passengers +static const uint WEIGHT_SCALE_IND_PROD = 0; ///< Index into _settings_game.economy.cargodest.weight_scale_ind for produced cargo +static const uint WEIGHT_SCALE_IND_PILE = 1; ///< Index into _settings_game.economy.cargodest.weight_scale_ind for stockpiled cargo + +/** Are cargo destinations for this cargo type enabled? */ +bool CargoHasDestinations(CargoID cid) +{ + const CargoSpec *spec = CargoSpec::Get(cid); + switch (spec->town_effect) { + case TE_PASSENGERS: + case TE_MAIL: + return _settings_game.economy.cargodest.mode_pax_mail != CRM_OFF; + + case TE_GOODS: + case TE_WATER: + case TE_FOOD: + return _settings_game.economy.cargodest.mode_town_cargo != CRM_OFF; + + default: + return _settings_game.economy.cargodest.mode_others != CRM_OFF; + } +} + +/** Are cargo destinations for all cargo types disabled? */ +bool CargoDestinationsDisabled() +{ + return _settings_game.economy.cargodest.mode_pax_mail == CRM_OFF && _settings_game.economy.cargodest.mode_town_cargo == CRM_OFF && _settings_game.economy.cargodest.mode_others == CRM_OFF; +} + +/** 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; +}; + +/** + * Test whether two tiles are nearby with map-size scaling. + * @param t1 First tile. + * @param t2 Second tile. + * @param dist_square Allowed squared distance between the tiles. + * @return True if the tiles are nearby. + */ +static bool IsNearby(TileIndex t1, TileIndex t2, uint32 dist_square) +{ + /* 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 DistanceSquare(t1, t2) < ScaleByMapSize1D(dist_square); +} + +/** + * Test whether a tiles is near a town. + * @param t The town. + * @param ti The tile to test. + * @return True if the tiles is near the town. + */ +static bool IsTownNearby(const Town *t, TileIndex ti) +{ + return IsNearby(t->xy, ti, _settings_game.economy.cargodest.town_nearby_dist); +} + +/** + * Test whether a tiles is near an industry. + * @param ind The industry. + * @param ti The tile to test. + * @return True if the tiles is near the town. + */ +static bool IsIndustryNearby(const Industry *ind, TileIndex ti) +{ + return IsNearby(ind->location.tile, ti, _settings_game.economy.cargodest.ind_nearby_dist); +} + +/** 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[BIG_TOWN_POP_PAX] : t->mail.old_max > _settings_game.economy.cargodest.big_town_pop[BIG_TOWN_POP_MAIL]); +} + +/** Enumerate nearby towns. */ +static bool EnumNearbyTown(const Town *t, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnyTown(t, data) && IsTownNearby(t, erd->source_xy); +} + +/** 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; + return EnumAnyIndustry(ind, data) && IsIndustryNearby(ind, erd->source_xy); +} + +/** 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; + return EnumAnySupplier(ind, data) && IsIndustryNearby(ind, erd->source_xy); +} + +/** Enumerate nearby cargo sources supplying a specific cargo. */ +static bool EnumNearbySupplier(const Town *t, void *data) +{ + EnumRandomData *erd = (EnumRandomData *)data; + return EnumAnySupplier(t, data) && IsTownNearby(t, erd->source_xy); +} + + +/** 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[] = {LWM_TONY_NEARBY, LWM_TONY_CITY, LWM_TONY_BIG, LWM_TONY_ANY}; + 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 = LWM_ANYWHERE; + 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[] = {LWM_IND_NEARBY, LWM_IND_PRODUCING, LWM_IND_ANY}; + 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 = LWM_ANYWHERE; + 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) +{ + /* First link is for undetermined destinations. */ + if (this->cargo_links[cid].Length() == 0) { + *this->cargo_links[cid].Append() = CargoLink(NULL, LWM_ANYWHERE); + } + if (this->cargo_links[cid].Get(0)->dest != NULL) { + /* Insert link at first place. */ + *this->cargo_links[cid].Append() = *this->cargo_links[cid].Get(0); + *this->cargo_links[cid].Get(0) = CargoLink(NULL, LWM_ANYWHERE); + } +} + +/* 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, LWM_INTOWN); + 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, LWM_INTOWN); + } + } 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, LWM_ANYWHERE))); + } + 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 = LWM_ANYWHERE; + + /* 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 max_amt = IsPassengerCargo(cid) ? t->pass.old_max : t->mail.old_max; + uint big_amt = _settings_game.economy.cargodest.big_town_pop[IsPassengerCargo(cid) ? BIG_TOWN_POP_PAX : BIG_TOWN_POP_MAIL]; + + uint num_links = _settings_game.economy.cargodest.base_town_links[IsSymmetricCargo(cid) ? BASE_TOWN_LINKS_SYMM : BASE_TOWN_LINKS]; + /* Add links based on the available cargo amount. */ + num_links += min(max_amt, big_amt) / _settings_game.economy.cargodest.pop_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_PAX : SCALE_TOWN]; + if (max_amt > big_amt) num_links += (max_amt - big_amt) / _settings_game.economy.cargodest.pop_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_BIG_PAX : SCALE_TOWN_BIG]; + /* 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) ? BASE_TOWN_LINKS_SYMM : BASE_TOWN_LINKS]); + + /* 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) ? BASE_IND_LINKS_SYMM : (IsTownCargo(cid) ? BASE_IND_LINKS_TOWN : BASE_IND_LINKS)]; + /* Add links based on the average industry production. */ + num_links += ind->average_production[i] / _settings_game.economy.cargodest.cargo_scale_ind[IsTownCargo(cid) ? CARGO_SCALE_IND_TOWN : CARGO_SCALE_IND]; + + /* 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, LWM_IND_ANY); + 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, LWM_IND_ANY); + 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 max_amt = IsPassengerCargo(cid) ? this->pass.old_max : this->mail.old_max; + uint big_amt = _settings_game.economy.cargodest.big_town_pop[IsPassengerCargo(cid) ? BIG_TOWN_POP_PAX : BIG_TOWN_POP_MAIL]; + + /* 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[IsPassengerCargo(cid) ? MIN_WEIGHT_TOWN_PAX : MIN_WEIGHT_TOWN]; + weight += min(max_amt, big_amt) * weight_mod / _settings_game.economy.cargodest.weight_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_PAX : SCALE_TOWN]; + if (max_amt > big_amt) weight += (max_amt - big_amt) * weight_mod / _settings_game.economy.cargodest.weight_scale_town[IsPassengerCargo(cid) ? SCALE_TOWN_BIG_PAX : SCALE_TOWN_BIG]; + + 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[WEIGHT_SCALE_IND_PILE]; + } + + /* 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[WEIGHT_SCALE_IND_PROD]; + + 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; + + css->cargo_links[cid].Begin()->amount.NewMonth(); + + 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() +{ + if (Town::CleaningPool() || Industry::CleaningPool()) return; + + /* 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, LWM_ANYWHERE))); + 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, LWM_ANYWHERE))); + 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 (CargoDestinationsDisabled()) 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 demand link. + * @param cid Cargo type + * @param allow_self Indicates if the local link is acceptable as a result. + * @return Pointer to a demand link or this->cargo_links[cid].End() if no link found. + */ +CargoLink *CargoSourceSink::GetRandomLink(CargoID cid, bool allow_self) +{ + /* Randomly choose a cargo link. */ + uint weight = RandomRange(this->cargo_links_weight[cid] - 1); + uint cur_sum = 0; + + CargoLink *l; + for (l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); ++l) { + cur_sum += l->weight; + if (weight < cur_sum) { + /* Link is valid if it is random destination or only the + * local link if allowed and accepts the cargo. */ + if (l->dest == NULL || ((allow_self || l->dest != this) && l->dest->AcceptsCargo(cid))) break; + } + } + + return l; +} + + +/** 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(); +} + +/** 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 a single packet of 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 MoveCargoWithDestinationToStationWorker(CargoID cid, uint *amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile) +{ + 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. */ + l = source->GetRandomLink(cid, true); + + 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 *= DistanceSquare(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 + 1) / 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, LWM_ANYWHERE)); + 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; +} + +/** + * 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; + + /* Split the cargo into multiple destinations for industries. */ + int num_packets = 1; + if (source_type == ST_INDUSTRY) { + if (*amount > 5) num_packets++; + if (*amount > 50) num_packets += 2; + } + + /* Generate num_packets different destinations while making + * sure no cargo is lost due to rounding. */ + uint amount_packet = *amount / num_packets; + uint last_packet = *amount - (num_packets - 1) * amount_packet; + uint moved = 0; + for (; num_packets > 0; num_packets--) { + uint cur_amount = (num_packets == 1) ? last_packet : amount_packet; + + /* If we fail once, we will fail always. */ + if (!MoveCargoWithDestinationToStationWorker(cid, &cur_amount, source_type, source_id, all_stations, src_tile)) return false; + moved += cur_amount; + } + + *amount = moved; + 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) + +/** + * Invalidate some stuff on destruction. + */ +RouteLink::~RouteLink() +{ + if (RouteLink::CleaningPool()) return; + + if (this->GetOriginOrderId() != INVALID_ORDER) StationCargoList::InvalidateAllTo(this->GetOriginOrderId(), this->GetDestination()); +} + +/** + * Update or create a single route link for a specific vehicle and cargo. + * @param v The vehicle. + * @param cargos Create links for the cargo types whose bit is set. + * @param clear_others Should route links for cargo types nor carried be cleared? + * @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, uint32 cargos, bool clear_others, Station *from, OrderID from_oid, StationID to_id, OrderID to_oid, uint32 travel_time) +{ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + bool has_cargo = HasBit(cargos, cid); + /* Skip if cargo not carried and we aren't supposed to clear other links. */ + if (!clear_others && !has_cargo) continue; + /* 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) { + if (has_cargo) { + /* Update destination if necessary. */ + (*link)->SetDestination(to_id, to_oid); + (*link)->UpdateTravelTime(travel_time); + } else { + /* Remove link. */ + delete *link; + from->goods[cid].routes.erase(link); + } + break; + } + } + + /* No link found? Append a new one. */ + if (has_cargo && 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, v->vcache.cached_cargo_mask, false, 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 (CargoDestinationsDisabled()) 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++; + + /* Collect cargo types carried by all vehicles in the shared order list. */ + uint32 transported_cargos = 0; + for (Vehicle *u = v->FirstShared(); u != NULL; u = u->NextShared()) { + transported_cargos |= u->vcache.cached_cargo_mask; + } + + /* 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 and the new destination is different, create/update route links. */ + if (prev_order != NULL && prev_order != order && prev_order->GetDestination() != order->GetDestination()) { + Station *from = Station::Get(prev_order->GetDestination()); + Station *to = Station::Get(order->GetDestination()); + /* A vehicle with the speed of 128 km/h-ish would take one tick for each of the + * #TILE_SIZE steps per tile. For aircraft, the time needs to be scaled with the + * plane speed factor. */ + uint time = DistanceManhattan(from->xy, to->xy) * TILE_SIZE * 128 / v->GetDisplayMaxSpeed(); + if (v->type == VEH_AIRCRAFT) time *= _settings_game.vehicle.plane_speed; + UpdateVehicleRouteLinks(v, transported_cargos, true, from, prev_order->index, order->GetDestination(), order->index, time); + } + + 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) +{ + /* Reset waiting time for all vehicles currently loading. */ + for (std::list::const_iterator v_itr = st->loading_vehicles.begin(); v_itr != st->loading_vehicles.end(); ++v_itr) { + CargoID cid; + FOR_EACH_SET_CARGO_ID(cid, (*v_itr)->vcache.cached_cargo_mask) { + for (RouteLinkList::iterator link = st->goods[cid].routes.begin(); link != st->goods[cid].routes.end(); ++link) { + if ((*link)->GetOriginOrderId() == (*v_itr)->last_order_id) (*link)->wait_time = 0; + } + } + } + + 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..83aea44 --- /dev/null +++ b/src/cargodest_base.h @@ -0,0 +1,181 @@ +/* $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) : 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), 1)); + } + + /** 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; + + CargoLink *GetRandomLink(CargoID cid, bool allow_self); + + /** 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..8767d40 --- /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[128]; + + char name[128]; + 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..fb18af0 --- /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 the demand mode for cargo. */ +enum CargoRoutingMode { + CRM_OFF, ///< No routing, original behaviour + CRM_FIXED_DEST, ///< Fixed 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 2c93d8c..40147f4 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 "station_base.h" +#include "cargodest_func.h" +#include "cargodest_base.h" +#include "settings_type.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) { + /* Clear next hop of those 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 { @@ -245,56 +288,121 @@ void CargoList::Truncate(uint max_remaining) * - MTA_CARGO_LOAD: Sets the loaded_at_xy value of the moved packets. * - MTA_TRANSFER: Just move without side effects. * - MTA_UNLOAD: Just move without side effects. + * - MTA_NO_ACTION: Does nothing for packets without destination, otherwise either like MTA_TRANSFER or MTA_FINAL_DELIVERY. * @param dest Destination to move the cargo to. * @param max_move Amount of cargo entities to move. * @param mta How to handle the moving (side effects). - * @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. + * @param st Station ID where we are loading/unloading or STATION_INVALID for move from vehicle to vehicle. * @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 + * @pre st != INVALID_STATION || (mta != MTA_CARGO_LOAD && payment == NULL) * @return True if there are still packets that might be moved from this cargo list. */ 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, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer) { assert(mta == MTA_FINAL_DELIVERY || dest != NULL); assert(mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL); + assert(st != INVALID_STATION || (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) { - /* Skip cargo that originated from this station. */ + MoveToAction cp_mta = mta; + OrderID current_next_order = cp->NextHop(); + StationID current_next_unload = cp->NextStation(); + + if (cp_mta == MTA_CARGO_LOAD) { + /* Invalid next hop but valid destination? Recompute next hop. */ + if (current_next_order == INVALID_ORDER && cp->DestinationID() != INVALID_SOURCE) { + if (!this->UpdateCargoNextHop(cp, Station::Get(st), cid)) { + /* Failed to find destination, drop packet. */ + it = this->packets.erase(it); + continue; + } + current_next_order = cp->NextHop(); + current_next_unload = cp->NextStation(); + } + + /* 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() != st) { + ++it; + continue; + } + + Station *station = Station::Get(st); + + bool found; + StationID next_unload; + RouteLink *link = FindRouteLinkForCargo(station, cid, 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_mta == MTA_NO_ACTION || (cp->source == st && cp_mta == MTA_FINAL_DELIVERY)) { + /* Skip cargo that is not accepted or 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; continue; // of the loop case MTA_CARGO_LOAD: - cp->loaded_at_xy = data; + cp->loaded_at_xy = Station::Get(st)->xy; break; case MTA_TRANSFER: cp->feeder_share += payment->PayTransfer(cp, cp->count); break; - case MTA_UNLOAD: + default: break; } dest->Append(cp); @@ -302,7 +410,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,12 +431,14 @@ 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) { - cp_new->loaded_at_xy = data; + } else if (cp_mta == MTA_CARGO_LOAD) { + cp_new->loaded_at_xy = Station::Get(st)->xy; } dest->Append(cp_new); @@ -337,6 +447,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 && Station::Get(st)->goods[cid].cargo.CountForNextHop(INVALID_ORDER) > 0) { + /* 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 +512,158 @@ void VehicleCargoList::InvalidateCache() this->Parent::InvalidateCache(); } +/** Invalidate next unload station of all cargo packets. */ +void VehicleCargoList::InvalidateNextStation() +{ + for (VehicleCargoList::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 a cargo packet. + * @param cp Cargo packet to update. + * @param st Station of this list. + * @param cid Cargo type of this list. + * @return False if the packet was deleted, true otherwise. + */ +bool StationCargoList::UpdateCargoNextHop(CargoPacket *cp, Station *st, CargoID cid) +{ + StationID next_unload; + RouteLink *l = FindRouteLinkForCargo(st, cid, cp, &next_unload); + + if (l == NULL) { + /* No link to destination, drop packet. */ + this->RemoveFromCache(cp); + delete cp; + return false; + } + + /* Update next hop info. */ + this->RemoveFromCache(cp); + cp->next_station = next_unload; + cp->next_order = l->GetOriginOrderId(); + this->AddToCache(cp); + + return true; +} + +/** + * 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) +{ + uint 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) { + if (this->UpdateCargoNextHop(*iter, st, cid)) { + ++iter; + } else { + iter = this->packets.erase(iter); + } + } else { + ++iter; + } + } + + /* Update start counter for next loop. */ + this->next_start = count; + if (this->next_start >= this->packets.size()) this->next_start = 0; +} + +/** + * Invalidates the next hop info of all cargo packets with a given next order or unload station. + * @param order Next order to invalidate. + * @param st_unload Unload station to invalidate. + */ +/* static */ void StationCargoList::InvalidateAllTo(OrderID order, StationID st_unload) +{ + 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) { + CargoPacket *cp = *it; + if (cp->next_order == order || cp->next_station == st_unload) { + /* Invalidate both order and unload station as both likely + * don't make sense anymore. */ + st->goods[cid].cargo.RemoveFromCache(cp); + cp->next_order = INVALID_ORDER; + cp->next_station = INVALID_STATION; + st->goods[cid].cargo.AddToCache(cp); + } + } + } + } +} + +/** + * Invalidates the next hop info of all cargo packets for a given destination. + * @param order Next order to invalidate. + */ +/* 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) { + CargoPacket *cp = *it; + if (cp->dest_id == dest && cp->dest_type == type) { + /* Invalidate both next order and unload station as we + * want the packets to be not routed anymore. */ + st->goods[cid].cargo.RemoveFromCache(cp); + cp->next_order = INVALID_ORDER; + cp->next_station = INVALID_STATION; + st->goods[cid].cargo.AddToCache(cp); + } + } + } + } +} + /* * We have to instantiate everything we want to be usable. */ @@ -403,8 +671,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, StationID st, OrderID cur_order, CargoID cid, 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, StationID st, OrderID cur_order, CargoID cid, 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, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer); diff --git a/src/cargopacket.h b/src/cargopacket.h index 0f2e461..80a5395 100644 --- a/src/cargopacket.h +++ b/src/cargopacket.h @@ -17,7 +17,10 @@ #include "station_type.h" #include "cargo_type.h" #include "vehicle_type.h" +#include "order_type.h" +#include "cargotype.h" #include +#include /** Unique identifier for a single cargo packet. */ typedef uint32 CargoPacketID; @@ -44,6 +47,12 @@ private: StationID source; ///< The station where the cargo came from first. TileIndex source_xy; ///< The origin of the cargo (first station in feeder chain). TileIndex loaded_at_xy; ///< Location where this cargo has been loaded into the vehicle. + TileIndex dest_xy; ///< Destination tile or INVALID_TILE if no specific destination + SourceID dest_id; ///< Index of the destination. + SourceTypeByte dest_type; ///< Type of #dest_id. + byte flags; ///< Flags influencing the routing decision of this packet, see #RouteFlags. + OrderID next_order; ///< Next desired hop. + StationID next_station; ///< Unload at this station next. /** The CargoList caches, thus needs to know about it. */ template friend class CargoList; @@ -51,13 +60,14 @@ private: friend class StationCargoList; /** We want this to be saved, right? */ friend const struct SaveLoad *GetCargoPacketDesc(); + friend bool CargodestModeChanged(int32 p1); public: /** Maximum number of items in a single cargo packet. */ static const uint16 MAX_COUNT = UINT16_MAX; CargoPacket(); - CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id); - CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share = 0, SourceType source_type = ST_INDUSTRY, SourceID source_id = INVALID_SOURCE); + CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id, TileIndex dest_xy = INVALID_TILE, SourceType dest_type = ST_INDUSTRY, SourceID dest_id = INVALID_SOURCE, OrderID next_order = INVALID_ORDER, StationID next_station = INVALID_STATION, byte flags = 0); + CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share = 0, SourceType source_type = ST_INDUSTRY, SourceID source_id = INVALID_SOURCE, TileIndex dest_xy = INVALID_TILE, SourceType dest_type = ST_INDUSTRY, SourceID dest_id = INVALID_SOURCE, OrderID next_order = INVALID_ORDER, StationID next_station = INVALID_STATION, byte flags = 0); /** Destroy the packet. */ ~CargoPacket() { } @@ -140,6 +150,59 @@ public: return this->loaded_at_xy; } + /** + * Gets the coordinates of the cargo's destination. + * @return The destination tile. + */ + FORCEINLINE TileIndex DestinationXY() const + { + return this->dest_xy; + } + + /** + * Gets the ID of the destination of the cargo. + * @return The destination ID. + */ + FORCEINLINE SourceID DestinationID() const + { + return this->dest_id; + } + + /** + * Gets the type of the destination of the cargo. + * @return The destination type. + */ + FORCEINLINE SourceType DestinationType() const + { + return this->dest_type; + } + + /** + * Gets the routing behaviour flags of this packet. + * @return The routing flags. + */ + FORCEINLINE byte Flags() const + { + return this->flags; + } + + /** + * Gets the order ID of the next desired hop. + * @return The order ID of the next desired hop. + */ + FORCEINLINE OrderID NextHop() const + { + return this->next_order; + } + + /** + * Gets the station ID where the packet should be unloaded next. + * @return The station ID where the packet should be unloaded. + */ + FORCEINLINE StationID NextStation() const + { + return this->next_station; + } static void InvalidateAllFrom(SourceType src_type, SourceID src); static void InvalidateAllFrom(StationID sid); @@ -179,8 +242,11 @@ public: MTA_CARGO_LOAD, ///< Load the packet onto a vehicle, i.e. set the last loaded station ID. MTA_TRANSFER, ///< The cargo is moved as part of a transfer. MTA_UNLOAD, ///< The cargo is moved as part of a forced unload. + MTA_NO_ACTION, ///< The station doesn't accept the cargo, so do nothing (only applicable to cargo without destination) }; + friend bool CargodestModeChanged(int32 p1); + protected: uint count; ///< Cache for the number of cargo entities. uint cargo_days_in_transit; ///< Cache for the sum of number of days in transit of each entity; comparable to man-hours. @@ -191,11 +257,18 @@ protected: void RemoveFromCache(const CargoPacket *cp); + void RemoveFromCacheLocal(const CargoPacket *cp, uint amount) {} + + virtual bool UpdateCargoNextHop(CargoPacket *cp, Station *st, CargoID cid) + { + return true; + } + public: /** Create the cargo list. */ CargoList() {} - ~CargoList(); + virtual ~CargoList(); void OnCleanPool(); @@ -249,7 +322,7 @@ public: void Truncate(uint max_remaining); template - bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, uint data = 0); + bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, StationID st = INVALID_STATION, OrderID cur_order = INVALID_ORDER, CargoID cid = INVALID_CARGO, bool *did_transfer = NULL); void InvalidateCache(); }; @@ -286,6 +359,8 @@ public: void InvalidateCache(); + void InvalidateNextStation(); + /** * Are two the two CargoPackets mergeable in the context of * a list of CargoPackets for a Vehicle? @@ -299,7 +374,13 @@ public: cp1->days_in_transit == cp2->days_in_transit && cp1->source_type == cp2->source_type && cp1->source_id == cp2->source_id && - cp1->loaded_at_xy == cp2->loaded_at_xy; + cp1->loaded_at_xy == cp2->loaded_at_xy && + cp1->dest_xy == cp2->dest_xy && + cp1->dest_type == cp2->dest_type && + cp1->dest_id == cp2->dest_id && + cp1->next_order == cp2->next_order && + cp1->next_station == cp2->next_station && + cp1->flags == cp2->flags; } }; @@ -308,11 +389,51 @@ public: */ class StationCargoList : public CargoList { public: + typedef std::map OrderMap; + +protected: + /** The (direct) parent of this class. */ + typedef CargoList Parent; + + OrderMap order_cache; + uint32 next_start; ///< Packet number to start the next hop update loop from. + + void AddToCache(const CargoPacket *cp); + void RemoveFromCache(const CargoPacket *cp); + void RemoveFromCacheLocal(const CargoPacket *cp, uint amount); + + /* virtual */ bool UpdateCargoNextHop(CargoPacket *cp, Station *st, CargoID cid); + +public: /** The super class ought to know what it's doing. */ friend class CargoList; /** The stations, via GoodsEntry, have a CargoList. */ friend const struct SaveLoad *GetGoodsDesc(); + void InvalidateCache(); + + void UpdateCargoNextHop(Station *st, CargoID cid); + + /** + * Gets the cargo counts per next hop. + * @return Cargo counts. + */ + const OrderMap& CountForNextHop() const + { + return this->order_cache; + } + + /** + * Gets the cargo count for a next hop. + * @param order The next hop. + * @return The cargo count for the specified next hop. + */ + int CountForNextHop(OrderID order) const + { + OrderMap::const_iterator i = this->order_cache.find(order); + return i != this->order_cache.end() ? i->second : 0; + } + /** * Are two the two CargoPackets mergeable in the context of * a list of CargoPackets for a Vehicle? @@ -325,8 +446,17 @@ public: return cp1->source_xy == cp2->source_xy && cp1->days_in_transit == cp2->days_in_transit && cp1->source_type == cp2->source_type && - cp1->source_id == cp2->source_id; + cp1->source_id == cp2->source_id && + cp1->dest_xy == cp2->dest_xy && + cp1->dest_type == cp2->dest_type && + cp1->dest_id == cp2->dest_id && + cp1->next_order == cp2->next_order && + cp1->next_station == cp2->next_station && + cp1->flags == cp2->flags; } + + static void InvalidateAllTo(OrderID order, StationID st_unload); + static void InvalidateAllTo(SourceType type, SourceID dest); }; #endif /* CARGOPACKET_H */ diff --git a/src/date.cpp b/src/date.cpp index 52827d1..031ba2f 100644 --- a/src/date.cpp +++ b/src/date.cpp @@ -19,6 +19,7 @@ #include "vehicle_base.h" #include "rail_gui.h" #include "saveload/saveload.h" +#include "cargodest_func.h" Year _cur_year; ///< Current year, starting at 0 Month _cur_month; ///< Current month (0..11) @@ -238,6 +239,7 @@ static void OnNewMonth() TownsMonthlyLoop(); IndustryMonthlyLoop(); StationMonthlyLoop(); + UpdateCargoLinks(); #ifdef ENABLE_NETWORK if (_network_server) NetworkServerMonthlyLoop(); #endif /* ENABLE_NETWORK */ diff --git a/src/debug.cpp b/src/debug.cpp index 3cdb6aa..17f5a10 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -39,6 +39,7 @@ int _debug_sl_level; int _debug_gamelog_level; int _debug_desync_level; int _debug_console_level; +int _debug_cargodest_level; uint32 _realtime_tick = 0; @@ -64,6 +65,7 @@ struct DebugLevel { DEBUG_LEVEL(gamelog), DEBUG_LEVEL(desync), DEBUG_LEVEL(console), + DEBUG_LEVEL(cargodest), }; #undef DEBUG_LEVEL diff --git a/src/debug.h b/src/debug.h index 76b66f3..e3df698 100644 --- a/src/debug.h +++ b/src/debug.h @@ -51,6 +51,7 @@ extern int _debug_gamelog_level; extern int _debug_desync_level; extern int _debug_console_level; + extern int _debug_cargodest_level; void CDECL debug(const char *dbg, const char *format, ...) WARN_FORMAT(2, 3); #endif /* NO_DEBUG_MESSAGES */ diff --git a/src/economy.cpp b/src/economy.cpp index 6f2604c..093e6af 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -46,6 +46,7 @@ #include "core/pool_func.hpp" #include "newgrf.h" #include "core/backup_type.hpp" +#include "cargodest_func.h" #include "table/strings.h" #include "table/pricebase.h" @@ -871,6 +872,33 @@ Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, C static SmallIndustryList _cargo_delivery_destinations; /** + * Deliver goods to an industry. Cargo acceptance by the industry is checked. + * @param ind The industry to deliver to. + * @param cargo_type Type of cargo delivered. + * @param num_pieces Amount of cargo delivered. + * @return Accepted pieces of cargo. + */ +static uint DeliverGoodsToIndustry(Industry *ind, CargoID cargo_type, uint num_pieces) +{ + uint cargo_index; + for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) { + if (cargo_type == ind->accepts_cargo[cargo_index]) break; + } + /* Check if matching cargo has been found */ + if (cargo_index >= lengthof(ind->accepts_cargo)) return 0; + + /* Check if industry temporarily refuses acceptance */ + if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) return 0; + + /* Insert the industry into _cargo_delivery_destinations, if not yet contained */ + _cargo_delivery_destinations.Include(ind); + + uint amount = min(num_pieces, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]); + ind->incoming_cargo_waiting[cargo_index] += amount; + return amount; +} + +/** * 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 @@ -894,21 +922,7 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n Industry *ind = st->industries_near[i]; if (ind->index == source) continue; - uint cargo_index; - for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) { - if (cargo_type == ind->accepts_cargo[cargo_index]) break; - } - /* Check if matching cargo has been found */ - if (cargo_index >= lengthof(ind->accepts_cargo)) continue; - - /* Check if industry temporarily refuses acceptance */ - if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) continue; - - /* Insert the industry into _cargo_delivery_destinations, if not yet contained */ - _cargo_delivery_destinations.Include(ind); - - uint amount = min(num_pieces, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]); - ind->incoming_cargo_waiting[cargo_index] += amount; + uint amount = DeliverGoodsToIndustry(ind, cargo_type, num_pieces); num_pieces -= amount; accepted += amount; } @@ -926,17 +940,25 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n * @param company The company delivering the cargo * @param src_type Type of source of cargo (industry, town, headquarters) * @param src Index of source of cargo + * @param cp_dest_type Type of destination of cargo + * @param cp_dest Index of the destination of cargo * @return Revenue for delivering cargo * @note The cargo is just added to the stockpile of the industry. It is due to the caller to trigger the industry's production machinery */ -static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID dest, TileIndex source_tile, byte days_in_transit, Company *company, SourceType src_type, SourceID src) +static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID dest, TileIndex source_tile, byte days_in_transit, Company *company, SourceType src_type, SourceID src, SourceType cp_dest_type, SourceID cp_dest) { assert(num_pieces > 0); const Station *st = Station::Get(dest); - /* Give the goods to the industry. */ - uint accepted = DeliverGoodsToIndustry(st, cargo_type, num_pieces, src_type == ST_INDUSTRY ? src : INVALID_INDUSTRY); + uint accepted = 0; + if (cp_dest != INVALID_SOURCE) { + /* If this cargo has an industry as destination, deliver the cargo to it. */ + if (cp_dest_type == ST_INDUSTRY) accepted = DeliverGoodsToIndustry(Industry::Get(cp_dest), cargo_type, num_pieces); + } else { + /* Give the goods to any accepting industry. */ + accepted = DeliverGoodsToIndustry(st, cargo_type, num_pieces, src_type == ST_INDUSTRY ? src : INVALID_INDUSTRY); + } /* If this cargo type is always accepted, accept all */ if (HasBit(st->always_accepted, cargo_type)) accepted = num_pieces; @@ -1017,21 +1039,29 @@ CargoPayment::~CargoPayment() this->front->cargo_payment = NULL; - if (this->visual_profit == 0) return; + if (this->visual_profit == 0 && this->transfer_profit == 0) return; Backup cur_company(_current_company, this->front->owner, FILE_LINE); SubtractMoneyFromCompany(CommandCost(this->front->GetExpenseType(true), -this->route_profit)); - this->front->profit_this_year += this->visual_profit << 8; + this->front->profit_this_year += (this->visual_profit + this->transfer_profit) << 8; + + int z = this->front->z_pos; if (this->route_profit != 0) { - if (IsLocalCompany() && !PlayVehicleSound(this->front, VSE_LOAD_UNLOAD)) { - SndPlayVehicleFx(SND_14_CASHTILL, this->front); - } + /* Show profit/loss from final delivery. */ + ShowCostOrIncomeAnimation(this->front->x_pos, this->front->y_pos, z, -this->visual_profit); + z += VPSM_TOP + FONT_HEIGHT_NORMAL + VPSM_BOTTOM; + } - ShowCostOrIncomeAnimation(this->front->x_pos, this->front->y_pos, this->front->z_pos, -this->visual_profit); - } else { - ShowFeederIncomeAnimation(this->front->x_pos, this->front->y_pos, this->front->z_pos, this->visual_profit); + if (this->transfer_profit != 0) { + /* Show transfer credits. */ + ShowFeederIncomeAnimation(this->front->x_pos, this->front->y_pos, z, this->transfer_profit); + } + + /* Play cash sound. */ + if (IsLocalCompany() && !PlayVehicleSound(this->front, VSE_LOAD_UNLOAD)) { + SndPlayVehicleFx(SND_14_CASHTILL, this->front); } cur_company.Restore(); @@ -1049,7 +1079,7 @@ void CargoPayment::PayFinalDelivery(const CargoPacket *cp, uint count) } /* Handle end of route payment */ - Money profit = DeliverGoods(count, this->ct, this->current_station, cp->SourceStationXY(), cp->DaysInTransit(), this->owner, cp->SourceSubsidyType(), cp->SourceSubsidyID()); + Money profit = DeliverGoods(count, this->ct, this->current_station, cp->SourceStationXY(), cp->DaysInTransit(), this->owner, cp->SourceSubsidyType(), cp->SourceSubsidyID(), cp->DestinationType(), cp->DestinationID()); this->route_profit += profit; /* The vehicle's profit is whatever route profit there is minus feeder shares. */ @@ -1073,7 +1103,7 @@ Money CargoPayment::PayTransfer(const CargoPacket *cp, uint count) profit = profit * _settings_game.economy.feeder_payment_share / 100; - this->visual_profit += profit; // accumulate transfer profits for whole vehicle + this->transfer_profit += profit; // accumulate transfer profits for whole vehicle return profit; // account for the (virtual) profit already made for the cargo packet } @@ -1113,17 +1143,27 @@ void PrepareUnload(Vehicle *front_v) * picked up by another vehicle when all * previous vehicles have loaded. */ -static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) +static void LoadUnloadVehicle(Vehicle *v, StationCargoList::OrderMap (&cargo_left)[NUM_CARGO]) { assert(v->current_order.IsType(OT_LOADING)); + OrderID last_order = v->last_order_id; + /* We have not waited enough time till the next round of loading/unloading */ if (v->load_unload_ticks != 0) { if (_settings_game.order.improved_load && (v->current_order.GetLoadType() & OLFB_FULL_LOAD)) { /* 'Reserve' this cargo for this vehicle, because we were first. */ for (; v != NULL; v = v->Next()) { int cap_left = v->cargo_cap - v->cargo.Count(); - if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left; + if (cap_left > 0) { + /* Try the bucket for our next destination first. */ + int loaded = min(cap_left, cargo_left[v->cargo_type][last_order]); + cargo_left[v->cargo_type][last_order] -= loaded; + + /* Reserve from the common bucket if still space left. */ + loaded = min(cap_left - loaded, cargo_left[v->cargo_type][INVALID_ORDER]); + cargo_left[v->cargo_type][INVALID_ORDER] -= loaded; + } } } return; @@ -1177,31 +1217,56 @@ static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) uint amount_unloaded = _settings_game.order.gradual_loading ? min(cargo_count, load_amount) : cargo_count; bool remaining = false; // Are there cargo entities in this vehicle that can still be unloaded here? bool accepted = false; // Is the cargo accepted by the station? + bool did_transfer = false; payment->SetCargo(v->cargo_type); - if (HasBit(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE) && !(u->current_order.GetUnloadType() & OUFB_TRANSFER)) { - /* The cargo has reached its final destination, the packets may now be destroyed */ - remaining = v->cargo.MoveTo(NULL, amount_unloaded, VehicleCargoList::MTA_FINAL_DELIVERY, payment, last_visited); + if (CargoHasDestinations(v->cargo_type)) { + /* This cargo type has destinations enabled, this means explicit transfer + * orders are overridden for cargo packets with destination. + * Default action for destination-less cargo packets is final delivery when + * accepted, otherwise no action. If the current order is forced unload, + * always unload all cargo. */ + VehicleCargoList::MoveToAction mta = HasBit(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE) ? VehicleCargoList::MTA_FINAL_DELIVERY : VehicleCargoList::MTA_NO_ACTION; + if (u->current_order.GetUnloadType() & OUFB_UNLOAD) mta = VehicleCargoList::MTA_UNLOAD; + remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, mta, payment, last_visited, last_order, v->cargo_type, &did_transfer); dirty_vehicle = true; accepted = true; + } else { + /* Cargo destinations are not enabled, handle transfer orders. */ + if (HasBit(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE) && !(u->current_order.GetUnloadType() & OUFB_TRANSFER)) { + /* The cargo has reached its final destination, the packets may now be destroyed */ + remaining = v->cargo.MoveTo(NULL, amount_unloaded, VehicleCargoList::MTA_FINAL_DELIVERY, payment, last_visited, v->cargo_type); + + dirty_vehicle = true; + accepted = true; + } + + /* The !accepted || v->cargo.Count == cargo_count clause is there + * to make it possible to force unload vehicles at the station where + * they were loaded, but to not force unload the vehicle when the + * station is still accepting the cargo in the vehicle. It doesn't + * accept cargo that was loaded at the same station. */ + if ((u->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) { + remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, u->current_order.GetUnloadType() & OUFB_TRANSFER ? VehicleCargoList::MTA_TRANSFER : VehicleCargoList::MTA_UNLOAD, payment, last_visited, v->cargo_type); + + did_transfer = true; + dirty_vehicle = true; + accepted = true; + } } - /* The !accepted || v->cargo.Count == cargo_count clause is there - * to make it possible to force unload vehicles at the station where - * they were loaded, but to not force unload the vehicle when the - * station is still accepting the cargo in the vehicle. It doesn't - * accept cargo that was loaded at the same station. */ - if ((u->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) { - remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, u->current_order.GetUnloadType() & OUFB_TRANSFER ? VehicleCargoList::MTA_TRANSFER : VehicleCargoList::MTA_UNLOAD, payment); + if (did_transfer) { + /* Update station information. */ if (!HasBit(ge->acceptance_pickup, GoodsEntry::PICKUP)) { InvalidateWindowData(WC_STATION_LIST, last_visited); SetBit(ge->acceptance_pickup, GoodsEntry::PICKUP); } + dirty_station = true; + } - dirty_vehicle = dirty_station = true; - } else if (!accepted) { + if (!accepted) { /* The order changed while unloading (unset unload/transfer) or the * station does not accept our goods. */ ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING); @@ -1262,11 +1327,12 @@ static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) int cap_left = v->cargo_cap - v->cargo.Count(); if (!ge->cargo.Empty() && cap_left > 0) { uint cap = cap_left; - uint count = ge->cargo.Count(); + uint count = ge->cargo.CountForNextHop(last_order) + ge->cargo.CountForNextHop(INVALID_ORDER); /* Skip loading this vehicle if another train/vehicle is already handling - * the same cargo type at this station */ - if (_settings_game.order.improved_load && cargo_left[v->cargo_type] <= 0) { + * the same cargo type at this station. Check the buckets for or next + * destination and the general bucket. */ + if (_settings_game.order.improved_load && cargo_left[v->cargo_type][last_order] <= 0 && cargo_left[v->cargo_type][INVALID_ORDER] <= 0) { SetBit(cargo_not_full, v->cargo_type); continue; } @@ -1278,9 +1344,17 @@ static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) } if (_settings_game.order.improved_load) { /* Don't load stuff that is already 'reserved' for other vehicles */ - cap = min((uint)cargo_left[v->cargo_type], cap); - count = cargo_left[v->cargo_type]; - cargo_left[v->cargo_type] -= cap; + count = cargo_left[v->cargo_type][last_order] + cargo_left[v->cargo_type][INVALID_ORDER]; + + /* Try the bucket for our next destination first. */ + int load_next = min(cap, cargo_left[v->cargo_type][last_order]); + cargo_left[v->cargo_type][last_order] -= load_next; + + /* Reserve from the common bucket if still space left. */ + int load_common = min(cap - load_next, cargo_left[v->cargo_type][INVALID_ORDER]); + cargo_left[v->cargo_type][INVALID_ORDER] -= load_common; + + cap = load_next + load_common; } /* Store whether the maximum possible load amount was loaded or not.*/ @@ -1302,7 +1376,7 @@ static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) completely_emptied = false; anything_loaded = true; - ge->cargo.MoveTo(&v->cargo, cap, StationCargoList::MTA_CARGO_LOAD, NULL, st->xy); + ge->cargo.MoveTo(&v->cargo, cap, StationCargoList::MTA_CARGO_LOAD, NULL, last_visited, last_order, v->cargo_type); st->time_since_load = 0; st->last_vehicle_type = v->type; @@ -1333,7 +1407,15 @@ static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) /* Update left cargo */ for (v = u; v != NULL; v = v->Next()) { int cap_left = v->cargo_cap - v->cargo.Count(); - if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left; + if (cap_left > 0) { + /* Try the bucket for our next destination first. */ + int loaded = min(cap_left, cargo_left[v->cargo_type][last_order]); + cargo_left[v->cargo_type][last_order] -= loaded; + + /* Reserve from the common bucket if still space left. */ + loaded = min(cap_left - loaded, cargo_left[v->cargo_type][INVALID_ORDER]); + cargo_left[v->cargo_type][INVALID_ORDER] -= loaded; + } } } @@ -1450,9 +1532,10 @@ void LoadUnloadStation(Station *st) */ if (last_loading == NULL) return; - int cargo_left[NUM_CARGO]; - - for (uint i = 0; i < NUM_CARGO; i++) cargo_left[i] = st->goods[i].cargo.Count(); + StationCargoList::OrderMap cargo_left[NUM_CARGO]; + for (CargoID i = 0; i < NUM_CARGO; i++) { + cargo_left[i] = st->goods[i].cargo.CountForNextHop(); + } for (iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end(); ++iter) { Vehicle *v = *iter; diff --git a/src/economy_base.h b/src/economy_base.h index 742d018..4db6d8c 100644 --- a/src/economy_base.h +++ b/src/economy_base.h @@ -26,7 +26,8 @@ extern CargoPaymentPool _cargo_payment_pool; struct CargoPayment : CargoPaymentPool::PoolItem<&_cargo_payment_pool> { Vehicle *front; ///< The front vehicle to do the payment of Money route_profit; ///< The amount of money to add/remove from the bank account - Money visual_profit; ///< The visual profit to show + Money visual_profit; ///< The visual (non-transfer) profit to show + Money transfer_profit; ///< The transfer profit to show /* Unsaved variables */ Company *owner; ///< The owner of the vehicle diff --git a/src/economy_func.h b/src/economy_func.h index e9c9c17..e71234b 100644 --- a/src/economy_func.h +++ b/src/economy_func.h @@ -31,7 +31,7 @@ int UpdateCompanyRatingAndValue(Company *c, bool update); void StartupIndustryDailyChanges(bool init_counter); Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, CargoID cargo_type); -uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations); +uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile); void PrepareUnload(Vehicle *front_v); void LoadUnloadStation(Station *st); diff --git a/src/genworld.cpp b/src/genworld.cpp index 7be576c..6f56ebe 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -32,6 +32,7 @@ #include "newgrf.h" #include "core/random_func.hpp" #include "core/backup_type.hpp" +#include "cargodest_func.h" #include "table/sprites.h" @@ -142,6 +143,7 @@ static void _GenerateWorld(void *) GenerateIndustries(); GenerateObjects(); GenerateTrees(); + UpdateCargoLinks(); } } diff --git a/src/industry.h b/src/industry.h index 18a20a7..8b358b9 100644 --- a/src/industry.h +++ b/src/industry.h @@ -17,6 +17,7 @@ #include "subsidy_type.h" #include "industry_map.h" #include "tilearea_type.h" +#include "cargodest_base.h" typedef Pool IndustryPool; @@ -37,7 +38,7 @@ enum ProductionLevels { /** * Defines the internal data of a functional industry. */ -struct Industry : IndustryPool::PoolItem<&_industry_pool> { +struct Industry : IndustryPool::PoolItem<&_industry_pool>, CargoSourceSink { typedef PersistentStorageArray PersistentStorage; TileArea location; ///< Location of the industry @@ -48,11 +49,13 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { byte production_rate[2]; ///< production rate for each cargo byte prod_level; ///< general production level CargoID accepts_cargo[3]; ///< 3 input cargo slots + uint32 produced_accepted_mask; ///< Bit mask of all cargos that are always accepted and also produced uint16 this_month_production[2]; ///< stats of this month's production per cargo uint16 this_month_transported[2]; ///< stats of this month's transport per cargo byte last_month_pct_transported[2]; ///< percentage transported per cargo in the last full month uint16 last_month_production[2]; ///< total units produced per cargo in the last full month uint16 last_month_transported[2]; ///< total units transported per cargo in the last full month + uint16 average_production[2]; ///< average production during the last months uint16 counter; ///< used for animation and/or production (if available cargo) IndustryType type; ///< type of industry. @@ -79,6 +82,41 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { void RecomputeProductionMultipliers(); + /* virtual */ SourceType GetType() const + { + return ST_INDUSTRY; + } + + /* virtual */ SourceID GetID() const + { + return this->index; + } + + /* virtual */ bool AcceptsCargo(CargoID cid) const + { + if (HasBit(this->produced_accepted_mask, cid)) return true; + + for (uint i = 0; i < lengthof(this->accepts_cargo); i++) { + if (this->accepts_cargo[i] == cid) return true; + } + return false; + } + + /* virtual */ bool SuppliesCargo(CargoID cid) const + { + for (uint i = 0; i < lengthof(this->produced_cargo); i++) { + if (this->produced_cargo[i] == cid) return true; + } + return false; + } + + /* virtual */ uint GetDestinationWeight(CargoID cid, byte weight_mod) const; + + /* virtual */ TileArea GetTileForDestination(CargoID cid) + { + return this->location; + } + /** * Get the industry of the given tile * @param tile the tile to get the industry from @@ -90,7 +128,10 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { return Industry::Get(GetIndustryIndex(tile)); } - static Industry *GetRandom(); + /** Callback function for #Industry::GetRandom. */ + typedef bool (*EnumIndustryProc)(const Industry *ind, void *data); + + static Industry *GetRandom(EnumIndustryProc enum_proc = NULL, IndustryID skip = INVALID_INDUSTRY, void *data = NULL); static void PostDestructor(size_t index); /** @@ -140,6 +181,8 @@ void PlantRandomFarmField(const Industry *i); void ReleaseDisastersTargetingIndustry(IndustryID); +void UpdateIndustryAcceptance(Industry *ind); + #define FOR_ALL_INDUSTRIES_FROM(var, start) FOR_ALL_ITEMS_FROM(Industry, industry_index, var, start) #define FOR_ALL_INDUSTRIES(var) FOR_ALL_INDUSTRIES_FROM(var, 0) diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 84b9ef9..20b81c9 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -189,17 +189,36 @@ void Industry::PostDestructor(size_t index) /** - * Return a random valid industry. - * @return random industry, NULL if there are no industries + * Return a random industry that statisfies some criteria + * specified with a callback function. + * + * @param enum_proc Callback function. Return true for a matching industry and false to continue iterating. + * @param skip Skip over this industry id when searching. + * @param data Optional data passed to the callback function. + * @return An industry satisfying the search criteria or NULL if no such industry exists. */ -/* static */ Industry *Industry::GetRandom() +/* static */ Industry *Industry::GetRandom(EnumIndustryProc enum_proc, IndustryID skip, void *data) { - if (Industry::GetNumItems() == 0) return NULL; - int num = RandomRange((uint16)Industry::GetNumItems()); - size_t index = MAX_UVALUE(size_t); + assert(skip == INVALID_INDUSTRY || Industry::IsValidID(skip)); + + uint16 max_num = 0; + if (enum_proc != NULL) { + /* A callback was given, count all matching industries. */ + Industry *ind; + FOR_ALL_INDUSTRIES(ind) { + if (ind->index != skip && enum_proc(ind, data)) max_num++; + } + } else { + max_num = (uint16)Industry::GetNumItems(); + /* Subtract one if an industry to skip was given. max_num is at least + * one here as otherwise skip could not be valid. */ + if (skip != INVALID_INDUSTRY) max_num--; + } + if (max_num == 0) return NULL; - while (num >= 0) { - num--; + uint num = RandomRange(max_num) + 1; + size_t index = MAX_UVALUE(size_t); + do { index++; /* Make sure we have a valid industry */ @@ -207,7 +226,9 @@ void Industry::PostDestructor(size_t index) index++; assert(index < Industry::GetPoolSize()); } - } + + if (index != skip && (enum_proc == NULL || enum_proc(Industry::Get(index), data))) num--; + } while (num > 0); return Industry::Get(index); } @@ -502,7 +523,7 @@ static void TransportIndustryGoods(TileIndex tile) i->this_month_production[j] += cw; - uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, stations.GetStations()); + uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, stations.GetStations(), tile); i->this_month_transported[j] += am; moved_cargo |= (am != 0); @@ -1529,6 +1550,28 @@ static CommandCost CheckIfFarEnoughFromConflictingIndustry(TileIndex tile, int t return CommandCost(); } +/** Update the mask of always accepted cargos that are also produced. */ +void UpdateIndustryAcceptance(Industry *ind) +{ + CargoArray accepted; + uint32 always_accepted = 0; + + /* Gather always accepted cargos for all tiles of this industry. */ + TILE_AREA_LOOP(tile, ind->location) { + if (IsTileType(tile, MP_INDUSTRY) && GetIndustryIndex(tile) == ind->index) { + AddAcceptedCargo_Industry(tile, accepted, &always_accepted); + } + } + + /* Create mask of produced cargos. */ + uint32 produced = 0; + for (uint i = 0; i < lengthof(ind->produced_cargo); i++) { + if (ind->produced_cargo[i] != CT_INVALID) SetBit(produced, ind->produced_cargo[i]); + } + + ind->produced_accepted_mask = always_accepted & produced; +} + /** * Put an industry on the map. * @param i Just allocated poolitem, mostly empty. @@ -1657,6 +1700,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type, } InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, 0); + UpdateIndustryAcceptance(i); Station::RecomputeIndustriesNearForAll(); } @@ -2079,6 +2123,9 @@ static void UpdateIndustryStatistics(Industry *i) i->last_month_transported[j] = i->this_month_transported[j]; i->this_month_transported[j] = 0; + + /* Average production over the last eight months. */ + i->average_production[j] = (i->average_production[j] * 7 + i->last_month_production[j]) / 8; } } } @@ -2621,6 +2668,7 @@ void IndustryMonthlyLoop() Industry *i; FOR_ALL_INDUSTRIES(i) { UpdateIndustryStatistics(i); + UpdateIndustryAcceptance(i); if (i->prod_level == PRODLEVEL_CLOSURE) { delete i; } else { diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index b50eddd..409911a 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -35,6 +35,7 @@ #include "core/backup_type.hpp" #include "genworld.h" #include "smallmap_gui.h" +#include "cargodest_gui.h" #include "table/strings.h" #include "table/sprites.h" @@ -662,8 +663,11 @@ class IndustryViewWindow : public Window int production_offset_y; ///< The offset of the production texts/buttons int info_height; ///< Height needed for the #IVW_INFO panel + CargoDestinationList dest_list; ///< Sorted list of demand destinations. + int dest_list_top; ///< Top coordinate of the destination list. + public: - IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window() + IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), dest_list(Industry::Get(window_number)) { this->flags4 |= WF_DISABLE_VP_SCROLL; this->editbox_line = IL_NONE; @@ -797,6 +801,10 @@ public: } } } + + this->dest_list_top = y; + y = this->dest_list.DrawList(left, right, y); + return y + WD_FRAMERECT_BOTTOM; } @@ -816,6 +824,13 @@ public: case IVW_INFO: { Industry *i = Industry::Get(this->window_number); InfoLine line = IL_NONE; + NWidgetBase *nwi = this->GetWidget(widget); + + /* Test for click on destination list. */ + if (pt.y > this->dest_list_top) { + this->dest_list.OnClick(pt.y - this->dest_list_top); + return; + } switch (this->editable) { case EA_NONE: break; @@ -840,7 +855,6 @@ public: } if (line == IL_NONE) return; - NWidgetBase *nwi = this->GetWidget(widget); int left = nwi->pos_x + WD_FRAMETEXT_LEFT; int right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT; if (IsInsideMM(pt.x, left, left + 20)) { @@ -968,6 +982,13 @@ public: } else { this->editable = EA_NONE; } + + /* Rebuild destination list if data is not zero, otherwise just resort. */ + if (data != 0) { + this->dest_list.InvalidateData(); + } else { + this->dest_list.Resort(); + } } virtual bool IsNewGRFInspectable() const diff --git a/src/lang/english.txt b/src/lang/english.txt index 444c737..c460fd7 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -655,17 +655,20 @@ STR_SMALLMAP_CAPTION :{WHITE}Map - {S STR_SMALLMAP_TYPE_CONTOURS :Contours STR_SMALLMAP_TYPE_VEHICLES :Vehicles STR_SMALLMAP_TYPE_INDUSTRIES :Industries +STR_SMALLMAP_TYPE_ROUTELINKS :Route links STR_SMALLMAP_TYPE_ROUTES :Routes STR_SMALLMAP_TYPE_VEGETATION :Vegetation STR_SMALLMAP_TYPE_OWNERS :Owners STR_SMALLMAP_TOOLTIP_SHOW_LAND_CONTOURS_ON_MAP :{BLACK}Show land contours on map STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP :{BLACK}Show vehicles on map STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP :{BLACK}Show industries on map +STR_SMALLMAP_TOOLTIP_SHOW_ROUTE_LINKS_ON_MAP :{BLACK}Show route links on map STR_SMALLMAP_TOOLTIP_SHOW_TRANSPORT_ROUTES_ON :{BLACK}Show transport routes on map STR_SMALLMAP_TOOLTIP_SHOW_VEGETATION_ON_MAP :{BLACK}Show vegetation on map STR_SMALLMAP_TOOLTIP_SHOW_LAND_OWNERS_ON_MAP :{BLACK}Show land owners on map STR_SMALLMAP_TOOLTIP_INDUSTRY_SELECTION :{BLACK}Click on an industry type to toggle displaying it. Ctrl+Click disables all types except the selected one. Ctrl+Click on it again to enable all industry types STR_SMALLMAP_TOOLTIP_COMPANY_SELECTION :{BLACK}Click on a company to toggle displaying its property. Ctrl+Click disables all companies except the selected one. Ctrl+Click on it again to enable all companies +STR_SMALLMAP_TOOLTIP_ROUTELINK_SELECTION :{BLACK}Click on a cargo to toggle displaying the route links. Ctrl+Click disables all route links except the selected one. Ctrl+Click on it again to enable all route links STR_SMALLMAP_LEGENDA_ROADS :{TINYFONT}{BLACK}Roads STR_SMALLMAP_LEGENDA_RAILROADS :{TINYFONT}{BLACK}Railways @@ -701,6 +704,7 @@ STR_SMALLMAP_CENTER :{BLACK}Centre t STR_SMALLMAP_INDUSTRY :{TINYFONT}{STRING} ({NUM}) STR_SMALLMAP_COMPANY :{TINYFONT}{COMPANY} STR_SMALLMAP_TOWN :{TINYFONT}{WHITE}{TOWN} +STR_SMALLMAP_CARGO :{TINYFONT}{STRING} STR_SMALLMAP_DISABLE_ALL :{BLACK}Disable all STR_SMALLMAP_ENABLE_ALL :{BLACK}Enable all STR_SMALLMAP_SHOW_HEIGHT :{BLACK}Show height @@ -709,6 +713,8 @@ STR_SMALLMAP_TOOLTIP_ENABLE_ALL_INDUSTRIES :{BLACK}Display STR_SMALLMAP_TOOLTIP_SHOW_HEIGHT :{BLACK}Toggle display of heightmap STR_SMALLMAP_TOOLTIP_DISABLE_ALL_COMPANIES :{BLACK}Display no company property on the map STR_SMALLMAP_TOOLTIP_ENABLE_ALL_COMPANIES :{BLACK}Display all company property on the map +STR_SMALLMAP_TOOLTIP_DISABLE_ALL_ROUTELINKS :{BLACK}Display no route links on the map +STR_SMALLMAP_TOOLTIP_ENABLE_ALL_ROUTELINKS :{BLACK}Display route links for all cargoes on the map # Status bar messages STR_STATUSBAR_TOOLTIP_SHOW_LAST_NEWS :{BLACK}Show last message or news report @@ -1326,6 +1332,12 @@ STR_CONFIG_SETTING_LARGER_TOWNS_DISABLED :{LTBLUE}Proport STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER :{LTBLUE}Initial city size multiplier: {ORANGE}{STRING1} STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD :{LTBLUE}Remove absurd road-elements during the road construction: {ORANGE}{STRING1} +STR_CONFIG_SETTING_CARGODEST_PAX :{LTBLUE}Destination mode for passengers/mail: {ORANGE}{STRING1} +STR_CONFIG_SETTING_CARGODEST_TOWN :{LTBLUE}Destination mode for town-accepted cargoes: {ORANGE}{STRING1} +STR_CONFIG_SETTING_CARGODEST_OTHER :{LTBLUE}Destination mode for other cargoes: {ORANGE}{STRING1} +STR_CONFIG_SETTING_CARGODEST_MODE_OFF :Original +STR_CONFIG_SETTING_CARGODEST_MODE_DEST :Fixed destinations + STR_CONFIG_SETTING_GUI :{ORANGE}Interface STR_CONFIG_SETTING_CONSTRUCTION :{ORANGE}Construction STR_CONFIG_SETTING_VEHICLES :{ORANGE}Vehicles @@ -2521,6 +2533,13 @@ STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP :{BLACK}Go to pr STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Enter a name for the sign +# Cargodest UI strings +STR_VIEW_CARGO_LAST_MONTH_OUT :{BLACK}Outgoing cargo last month: +STR_VIEW_CARGO_LAST_MONTH_TOWN :{BLACK}{SHORTCARGO} out of {CARGO} to {TOWN} +STR_VIEW_CARGO_LAST_MONTH_INDUSTRY :{BLACK}{SHORTCARGO} out of {CARGO} to {INDUSTRY} +STR_VIEW_CARGO_LAST_MONTH_LOCAL :{BLACK}{SHORTCARGO} out of {CARGO} to local destinations +STR_VIEW_CARGO_LAST_MONTH_OTHER :{BLACK}{SHORTCARGO} out of {CARGO} to other destinations + # Town directory window STR_TOWN_DIRECTORY_CAPTION :{WHITE}Towns STR_TOWN_DIRECTORY_NONE :{ORANGE}- None - @@ -2600,9 +2619,20 @@ STR_STATION_LIST_NO_WAITING_CARGO :{BLACK}No cargo # Station view window STR_STATION_VIEW_CAPTION :{WHITE}{STATION} {STATIONFEATURES} +STR_STATION_VIEW_WAITING_BUTTON :{BLACK}Source +STR_STATION_VIEW_WAITING_TOOLTIP :{BLACK}Show list of waiting cargo with its source +STR_STATION_VIEW_WAITING_TO_BUTTON :{BLACK}Destination +STR_STATION_VIEW_WAITING_TO_TOOLTIP :{BLACK}Show list of waiting cargo with its destination +STR_STATION_VIEW_WAITING_VIA_BUTTON :{BLACK}Next hop +STR_STATION_VIEW_WAITING_VIA_TOOLTIP :{BLACK}Show list of waiting cargo with its next hop +STR_STATION_VIEW_WAITING_TRANSFER_BUTTON :{BLACK}Transfer +STR_STATION_VIEW_WAITING_TRANSFER_TOOLTIP :{BLACK}Show list of waiting cargo with its next transfer station STR_STATION_VIEW_WAITING_TITLE :{BLACK}Waiting: {WHITE}{STRING} STR_STATION_VIEW_WAITING_CARGO :{WHITE}{CARGO} STR_STATION_VIEW_EN_ROUTE_FROM :{YELLOW}({SHORTCARGO} en-route from {STATION}) +STR_STATION_VIEW_WAITING_VIA :{YELLOW}{SHORTCARGO} en-route via {STATION} +STR_STATION_VIEW_WAITING_TRANSFER :{YELLOW}{SHORTCARGO} transfering at {STATION} +STR_STATION_VIEW_WAITING_TO :{YELLOW}{SHORTCARGO} en-route to {STRING1} STR_STATION_VIEW_ACCEPTS_BUTTON :{BLACK}Accepts STR_STATION_VIEW_ACCEPTS_TOOLTIP :{BLACK}Show list of accepted cargo @@ -3096,6 +3126,7 @@ STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT :{LTBLUE}- {CARG STR_VEHICLE_DETAILS_CARGO_EMPTY :{LTBLUE}Empty STR_VEHICLE_DETAILS_CARGO_FROM :{LTBLUE}{CARGO} from {STATION} STR_VEHICLE_DETAILS_CARGO_FROM_MULT :{LTBLUE}{CARGO} from {STATION} (x{NUM}) +STR_VEHICLE_DETAILS_CARGO_TO :{LTBLUE}{SHORTCARGO} to {STRING1} STR_VEHICLE_DETAIL_TAB_CARGO :{BLACK}Cargo STR_VEHICLE_DETAILS_TRAIN_CARGO_TOOLTIP :{BLACK}Show details of cargo carried diff --git a/src/newgrf.cpp b/src/newgrf.cpp index dcd34de..8a147df 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -8193,6 +8193,9 @@ static void AfterLoadGRFs() InitializeSortedCargoSpecs(); + /* Create dynamic list of cargo legends for smallmap_gui.cpp. */ + BuildCargoTypesLegend(); + /* Sort the list of industry types. */ SortIndustryTypes(); diff --git a/src/newgrf_town.cpp b/src/newgrf_town.cpp index 2dfc05b..98129ec 100644 --- a/src/newgrf_town.cpp +++ b/src/newgrf_town.cpp @@ -71,22 +71,22 @@ uint32 TownGetVariable(byte variable, byte parameter, bool *available, const Tow case 0xB2: return t->statues; case 0xB6: return ClampToU16(t->num_houses); case 0xB9: return t->growth_rate; - case 0xBA: return ClampToU16(t->new_max_pass); - case 0xBB: return GB(ClampToU16(t->new_max_pass), 8, 8); - case 0xBC: return ClampToU16(t->new_max_mail); - case 0xBD: return GB(ClampToU16(t->new_max_mail), 8, 8); - case 0xBE: return ClampToU16(t->new_act_pass); - case 0xBF: return GB(ClampToU16(t->new_act_pass), 8, 8); - case 0xC0: return ClampToU16(t->new_act_mail); - case 0xC1: return GB(ClampToU16(t->new_act_mail), 8, 8); - case 0xC2: return ClampToU16(t->max_pass); - case 0xC3: return GB(ClampToU16(t->max_pass), 8, 8); - case 0xC4: return ClampToU16(t->max_mail); - case 0xC5: return GB(ClampToU16(t->max_mail), 8, 8); - case 0xC6: return ClampToU16(t->act_pass); - case 0xC7: return GB(ClampToU16(t->act_pass), 8, 8); - case 0xC8: return ClampToU16(t->act_mail); - case 0xC9: return GB(ClampToU16(t->act_mail), 8, 8); + case 0xBA: return ClampToU16(t->pass.new_max); + case 0xBB: return GB(ClampToU16(t->pass.new_max), 8, 8); + case 0xBC: return ClampToU16(t->mail.new_max); + case 0xBD: return GB(ClampToU16(t->mail.new_max), 8, 8); + case 0xBE: return ClampToU16(t->pass.new_act); + case 0xBF: return GB(ClampToU16(t->pass.new_act), 8, 8); + case 0xC0: return ClampToU16(t->mail.new_act); + case 0xC1: return GB(ClampToU16(t->mail.new_act), 8, 8); + case 0xC2: return ClampToU16(t->pass.old_max); + case 0xC3: return GB(ClampToU16(t->pass.old_max), 8, 8); + case 0xC4: return ClampToU16(t->mail.old_max); + case 0xC5: return GB(ClampToU16(t->mail.old_max), 8, 8); + case 0xC6: return ClampToU16(t->pass.old_act); + case 0xC7: return GB(ClampToU16(t->pass.old_act), 8, 8); + case 0xC8: return ClampToU16(t->mail.old_act); + case 0xC9: return GB(ClampToU16(t->mail.old_act), 8, 8); case 0xCA: return t->pct_pass_transported; case 0xCB: return t->pct_mail_transported; case 0xCC: return t->new_act_food; diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index 2f8dcda..2bf2522 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -549,7 +549,7 @@ static void TileLoop_Object(TileIndex tile) if (GB(r, 0, 8) < (256 / 4 / (6 - level))) { uint amt = GB(r, 0, 8) / 8 / 4 + 1; if (EconomyIsInRecession()) amt = (amt + 1) >> 1; - MoveGoodsToStation(CT_PASSENGERS, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations()); + MoveGoodsToStation(CT_PASSENGERS, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations(), tile); } /* Top town building generates 90, HQ can make up to 196. The @@ -558,7 +558,7 @@ static void TileLoop_Object(TileIndex tile) if (GB(r, 8, 8) < (196 / 4 / (6 - level))) { uint amt = GB(r, 8, 8) / 8 / 4 + 1; if (EconomyIsInRecession()) amt = (amt + 1) >> 1; - MoveGoodsToStation(CT_MAIL, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations()); + MoveGoodsToStation(CT_MAIL, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations(), tile); } } diff --git a/src/order_base.h b/src/order_base.h index 44ce7a3..ad20bbe 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -175,6 +175,8 @@ public: uint32 Pack() const; uint16 MapOldOrder() const; void ConvertFromOldSavegame(); + + static void PostDestructor(size_t index); }; void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord); diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 3adaab2..cef7f1b 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -27,6 +27,7 @@ #include "station_base.h" #include "waypoint_base.h" #include "company_base.h" +#include "cargodest_func.h" #include "table/strings.h" @@ -232,6 +233,21 @@ Order::Order(uint32 packed) } /** + * Invalidating some stuff after removing item from the pool. + * @param index index of deleted item. + */ +/* static */ void Order::PostDestructor(size_t index) +{ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->current_order.index == index) v->current_order.index = INVALID_ORDER; + if (v->last_order_id == index) v->last_order_id = INVALID_ORDER; + } + + InvalidateOrderRouteLinks((OrderID)index); +} + +/** * * Updates the widgets of a vehicle which contains the order-data * @@ -864,6 +880,8 @@ void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord) cur_order_id++; } + PrefillRouteLinks(v); + /* Make sure to rebuild the whole list */ InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); } @@ -929,6 +947,17 @@ static void CancelLoadingDueToDeletedOrder(Vehicle *v) } /** + * Invalidate the next unload station of all cargo packets of a vehicle chain. + * @param v The vehicle. + */ +static void InvalidateNextStation(Vehicle *v) +{ + for (; v != NULL; v = v->Next()) { + v->cargo.InvalidateNextStation(); + } +} + +/** * Delete an order but skip the parameter validation. * @param v The vehicle to delete the order from. * @param sel_ord The id of the order to be deleted. @@ -939,6 +968,7 @@ void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord) Vehicle *u = v->FirstShared(); DeleteOrderWarnings(u); + PrefillRouteLinks(u); for (; u != NULL; u = u->NextShared()) { assert(v->orders.list == u->orders.list); @@ -967,6 +997,9 @@ void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord) /* Update any possible open window of the vehicle */ InvalidateVehicleOrder(u, sel_ord | (INVALID_VEH_ORDER_ID << 8)); + + /* Clear the next unload station of all cargo packets, it might not be in the orders anymore. */ + InvalidateNextStation(u); } /* As we delete an order, the order to skip to will be 'wrong'. */ @@ -1256,6 +1289,8 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 switch (mof) { case MOF_NON_STOP: order->SetNonStopType((OrderNonStopFlags)data); + if (data & ONSF_NO_STOP_AT_DESTINATION_STATION) InvalidateOrderRouteLinks(order->index); + PrefillRouteLinks(v); break; case MOF_STOP_LOCATION: @@ -1352,6 +1387,9 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 u->current_order.SetLoadType(order->GetLoadType()); } InvalidateVehicleOrder(u, -2); + + /* Invalidate the next unload station of all packets as we might not unload there anymore. */ + InvalidateNextStation(u); } } @@ -1421,6 +1459,7 @@ CommandCost CmdCloneOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 /* Link this vehicle in the shared-list */ dst->AddToShared(src); + PrefillRouteLinks(dst); InvalidateVehicleOrder(dst, -1); InvalidateVehicleOrder(src, -2); @@ -1482,6 +1521,7 @@ CommandCost CmdCloneOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 dst->orders.list = new OrderList(first, dst); } + PrefillRouteLinks(dst); InvalidateVehicleOrder(dst, -1); InvalidateWindowClassesData(GetWindowClassForVehicleType(dst->type), 0); @@ -1711,6 +1751,9 @@ void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist, bool reset_order_indic if (!keep_orderlist) v->orders.list = NULL; } + /* Invalidate the next unload station of all cargo. */ + InvalidateNextStation(v); + if (reset_order_indices) { v->cur_auto_order_index = v->cur_real_order_index = 0; if (v->current_order.IsType(OT_LOADING)) { @@ -1908,6 +1951,11 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth) } v->current_order = *order; + /* Set the index of the current order to the index of the auto order, + * this is needed as the index is used for route link generation. */ + Order *auto_order = v->GetOrder(v->cur_auto_order_index); + v->current_order.index = auto_order->index; + return UpdateOrderDest(v, order, conditional_depth + 1); } @@ -1991,6 +2039,10 @@ bool ProcessOrders(Vehicle *v) /* Otherwise set it, and determine the destination tile. */ v->current_order = *order; + /* Set the index of the current order to the index of the auto order, + * this is needed as the index is used for route link generation. */ + Order *auto_order = v->GetOrder(v->cur_auto_order_index); + v->current_order.index = auto_order->index; InvalidateVehicleOrder(v, -2); switch (v->type) { diff --git a/src/os/macosx/osx_stdafx.h b/src/os/macosx/osx_stdafx.h index 9c1b5b3..cbd9c0d 100644 --- a/src/os/macosx/osx_stdafx.h +++ b/src/os/macosx/osx_stdafx.h @@ -42,12 +42,14 @@ #define Rect OTTDRect #define Point OTTDPoint #define WindowClass OTTDWindowClass +#define RoutingFlags OTTDRoutingFlags #include #undef Rect #undef Point #undef WindowClass +#undef RoutingFlags /* remove the variables that CoreServices defines, but we define ourselves too */ #undef bool diff --git a/src/pathfinder/yapf/nodelist.hpp b/src/pathfinder/yapf/nodelist.hpp index b81fd65..98cca2c 100644 --- a/src/pathfinder/yapf/nodelist.hpp +++ b/src/pathfinder/yapf/nodelist.hpp @@ -21,7 +21,7 @@ * Implements open list, closed list and priority queue for A-star * path finder. */ -template +template class CNodeList_HashTableT { public: /** make Titem_ visible from outside of class */ @@ -29,7 +29,7 @@ public: /** make Titem_::Key a property of HashTable */ typedef typename Titem_::Key Key; /** type that we will use as item container */ - typedef SmallArray CItemArray; + typedef SmallArray CItemArray; /** how pointers to open nodes will be stored */ typedef CHashTableT COpenList; /** how pointers to closed nodes will be stored */ diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h index b02d9d0..ca28483 100644 --- a/src/pathfinder/yapf/yapf.h +++ b/src/pathfinder/yapf/yapf.h @@ -15,6 +15,8 @@ #include "../../direction_type.h" #include "../../track_type.h" #include "../../vehicle_type.h" +#include "../../cargodest_type.h" +#include "../../order_type.h" #include "../pathfinder_type.h" /** @@ -90,4 +92,6 @@ bool YapfTrainCheckReverse(const Train *v); */ bool YapfTrainFindNearestSafeTile(const Train *v, TileIndex tile, Trackdir td, bool override_railtype); +RouteLink *YapfChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found = NULL, OrderID order = INVALID_ORDER, int max_cost = INT_MAX); + #endif /* YAPF_H */ diff --git a/src/pathfinder/yapf/yapf_cargo.cpp b/src/pathfinder/yapf/yapf_cargo.cpp new file mode 100644 index 0000000..db9d63c --- /dev/null +++ b/src/pathfinder/yapf/yapf_cargo.cpp @@ -0,0 +1,429 @@ +/* $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 yapf_cargo.cpp Implementation of YAPF for cargo routing. */ + +#include "../../stdafx.h" +#include "../../cargodest_base.h" +#include "../../station_base.h" +#include "../../town.h" +#include "yapf.hpp" + + +/** YAPF node key for cargo routing. */ +struct CYapfRouteLinkNodeKeyT { + RouteLink *m_link; + + /** Initialize this node key. */ + FORCEINLINE void Set(RouteLink *link) + { + this->m_link = link; + } + + /** Calculate the hash of this cargo/route key. */ + FORCEINLINE int CalcHash() const + { + return (int)(size_t)this->m_link >> 4; + } + + FORCEINLINE bool operator == (const CYapfRouteLinkNodeKeyT& other) const + { + return this->m_link == other.m_link; + } + + void Dump(DumpTarget &dmp) const + { + dmp.WriteLine("m_link = %u", this->m_link->GetDestination()); + } +}; + +/** YAPF node class for cargo routing. */ +struct CYapfRouteLinkNodeT : public CYapfNodeT { + typedef CYapfNodeT Base; + + uint m_num_transfers; ///< Number of transfers to reach this node. + + /** Initialize this node. */ + FORCEINLINE void Set(CYapfRouteLinkNodeT *parent, RouteLink *link) + { + Base::Set(parent, false); + this->m_key.Set(link); + this->m_num_transfers = (parent != NULL) ? parent->m_num_transfers : 0; + } + + /** Get the route link of this node. */ + FORCEINLINE RouteLink *GetRouteLink() const { return this->m_key.m_link; } + + /** Get the number of transfers needed to reach this node. */ + FORCEINLINE int GetNumberOfTransfers() const { return this->m_num_transfers; } +}; + +typedef CNodeList_HashTableT CRouteLinkNodeList; + +/** Route link follower. */ +struct CFollowRouteLinkT { + CargoID m_cid; + RouteLink *m_old_link; + RouteLinkList *m_new_links; + + CFollowRouteLinkT(CargoID cid) : m_cid(cid) {} + + /** Fill in route links reachable by this route link. */ + inline bool Follow(RouteLink *from) + { + this->m_old_link = from; + + Station *st = Station::Get(from->GetDestination()); + m_new_links = &st->goods[this->m_cid].routes; + return !this->m_new_links->empty(); + } +}; + +/** YAPF cost provider for route links. */ +template +class CYapfCostRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::TrackFollower Follower; ///< The route follower. + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + static const int PENALTY_DIVISOR = 16; ///< Penalty factor divisor for fixed-point arithmetics. + static const int LOCAL_PENALTY_FACTOR = 20; ///< Penalty factor for source-local delivery. + static const int RF_DISTANCE_FACTOR = 2; ///< Vehicle modifier for "cheap" cargo packets. + static const int RF_TIME_FACTOR = 3; ///< Time modifier for "fast" cargo packets. + + /** To access inherited path finder. */ + FORCEINLINE Tpf& Yapf() { return *static_cast(this); } + FORCEINLINE const Tpf& Yapf() const { return *static_cast(this); } + + /** Check if this is a valid connection. */ + FORCEINLINE bool ValidLink(Node &n, const RouteLink *link, const RouteLink *parent) const + { + /* If the parent link has an owner, and the owner is different to + * the new owner, discard the node. Otherwise cargo could switch + * companies at oil rigs, which would mess up payment. */ + if (parent->GetOwner() != INVALID_OWNER && link->GetOwner() != parent->GetOwner()) return false; + + /* Check for no loading/no unloading when transferring. */ + if (link->GetOriginOrderId() != parent->GetDestOrderId() || (Order::Get(link->GetOriginOrderId())->GetUnloadType() & OUFB_UNLOAD) != 0) { + /* Can't transfer if the current order prohibits loading. */ + if ((Order::Get(link->GetOriginOrderId())->GetLoadType() & OLFB_NO_LOAD) != 0) return false; + + /* Can't transfer if the last order prohibits unloading. */ + if (parent->GetDestOrderId() != INVALID_ORDER && (Order::Get(parent->GetDestOrderId())->GetUnloadType() & OUFB_NO_UNLOAD) != 0) return false; + + /* Increase transfer counter and stop if max number of transfers is exceeded. */ + if (++n.m_num_transfers > Yapf().PfGetSettings().route_max_transfers) return false; + } + + return true; + } + + /** Cost of a single route link. */ + FORCEINLINE int RouteLinkCost(const RouteLink *link, const RouteLink *parent) const + { + int cost = 0; + + /* Distance cost. */ + const Station *from = Station::Get(parent->GetDestination()); + const Station *to = Station::Get(link->GetDestination()); + cost = DistanceManhattan(from->xy, to->xy) * this->Yapf().PfGetSettings().route_distance_factor; + + /* Modulate the distance by a vehicle-type specific factor to + * simulate the different costs. Cost is doubled if the cargo + * wants to go cheap. */ + assert_compile(lengthof(_settings_game.pf.yapf.route_mode_cost_factor) == VEH_AIRCRAFT + 1); + byte dfactor = this->Yapf().PfGetSettings().route_mode_cost_factor[link->GetVehicleType()]; + if (HasBit(this->Yapf().GetFlags(), RF_WANT_CHEAP)) dfactor *= RF_DISTANCE_FACTOR; + cost *= dfactor; + + /* Factor for the time penalties based on whether the cargo wants to go fast. */ + uint time_factor = HasBit(this->Yapf().GetFlags(), RF_WANT_FAST) ? RF_TIME_FACTOR : 1; + + /* Transfer penalty when switching vehicles or forced unloading. */ + if (link->GetOriginOrderId() != parent->GetDestOrderId() || (Order::Get(link->GetOriginOrderId())->GetUnloadType() & OUFB_UNLOAD) != 0) { + cost += this->Yapf().PfGetSettings().route_transfer_cost; + + /* Penalty for time since the last vehicle arrived. */ + cost += link->GetWaitTime() * this->Yapf().PfGetSettings().route_station_last_veh_factor * time_factor / PENALTY_DIVISOR; + + /* Penalty for cargo waiting on our link. */ + cost += (from->goods[this->Yapf().GetCargoID()].cargo.CountForNextHop(link->GetOriginOrderId()) * this->Yapf().PfGetSettings().route_station_waiting_factor) / PENALTY_DIVISOR; + } + + /* Penalty for travel time. */ + cost += (link->GetTravelTime() * this->Yapf().PfGetSettings().route_travel_time_factor * time_factor) / PENALTY_DIVISOR; + + return cost; + } + +public: + /** Called by YAPF to calculate the cost from the origin to the given node. */ + inline bool PfCalcCost(Node& n, const Follower *follow) + { + int segment_cost = 0; + + if (this->Yapf().PfDetectDestination(n)) { + Station *st = Station::Get(n.m_parent->GetRouteLink()->GetDestination()); + /* Discard node if the station doesn't accept the cargo type. */ + if (!HasBit(st->goods[follow->m_cid].acceptance_pickup, GoodsEntry::ACCEPTANCE)) return false; + /* Destination node, get delivery cost. Parent has the station. */ + segment_cost += this->Yapf().DeliveryCost(st); + /* If this link comes from an origin station, penalize it to encourage + * delivery using other stations. */ + if (n.m_parent->GetRouteLink()->GetDestOrderId() == INVALID_ORDER) segment_cost *= LOCAL_PENALTY_FACTOR; + } else { + RouteLink *link = n.GetRouteLink(); + RouteLink *parent = n.m_parent->GetRouteLink(); + + /* Check if the link is a valid connection. */ + if (!this->ValidLink(n, link, parent)) return false; + + /* Cost of the single route link. */ + segment_cost += this->RouteLinkCost(link, parent); + } + + /* Apply it. */ + n.m_cost = n.m_parent->m_cost + segment_cost; + return n.m_cost <= this->Yapf().GetMaxCost(); + } +}; + +/** YAPF origin provider for route links. */ +template +class CYapfOriginRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + CargoID m_cid; + TileIndex m_src; + OrderID m_order; + byte m_flags; + SmallVector m_origin; + + /** To access inherited path finder. */ + FORCEINLINE Tpf& Yapf() { return *static_cast(this); } + +public: + /** Get the current cargo type. */ + FORCEINLINE CargoID GetCargoID() const + { + return this->m_cid; + } + + /** Get the cargo routing flags. */ + FORCEINLINE byte GetFlags() const + { + return this->m_flags; + } + + /** Set origin. */ + void SetOrigin(CargoID cid, TileIndex src, const StationList *stations, bool cargo_creation, OrderID order, byte flags) + { + this->m_cid = cid; + this->m_src = src; + this->m_order = order; + this->m_flags = flags; + /* Create fake links for the origin stations. */ + for (const Station * const *st = stations->Begin(); st != stations->End(); st++) { + if (cargo_creation) { + /* Exclusive rights in effect? Only serve those stations. */ + if ((*st)->town->exclusive_counter > 0 && (*st)->town->exclusivity != (*st)->owner) continue; + /* Selectively servicing stations, and not this one. */ + if (_settings_game.order.selectgoods && (*st)->goods[cid].last_speed == 0) continue; + } + + *this->m_origin.Append() = RouteLink((*st)->index, INVALID_ORDER, this->m_order); + } + } + + /** Called when YAPF needs to place origin nodes into the open list. */ + void PfSetStartupNodes() + { + for (RouteLink *link = this->m_origin.Begin(); link != this->m_origin.End(); link++) { + Node &n = this->Yapf().CreateNewNode(); + n.Set(NULL, link); + /* Prefer stations closer to the source tile. */ + n.m_cost = DistanceSquare(this->m_src, Station::Get(link->GetDestination())->xy) * this->Yapf().PfGetSettings().route_distance_factor; + this->Yapf().AddStartupNode(n); + } + } +}; + +/** YAPF destination provider for route links. */ +template +class CYapfDestinationRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + TileArea m_dest; + int m_max_cost; ///< Maximum node cost. + + /** To access inherited path finder. */ + FORCEINLINE Tpf& Yapf() { return *static_cast(this); } + +public: + /** Get the maximum allowed node cost. */ + FORCEINLINE int GetMaxCost() const + { + return this->m_max_cost; + } + + /** Set destination. */ + void SetDestination(const TileArea &dest, uint max_cost) + { + this->m_dest = dest; + this->m_max_cost = max_cost; + } + + /** Cost for delivering the cargo to the final destination tile. */ + FORCEINLINE int DeliveryCost(Station *st) + { + return DistanceSquare(st->xy, this->m_dest.tile) * this->Yapf().PfGetSettings().route_distance_factor; + } + + /** Called by YAPF to detect if the station reaches the destination. */ + FORCEINLINE bool PfDetectDestination(StationID st_id) const + { + const Station *st = Station::Get(st_id); + return st->rect.AreaInExtendedRect(this->m_dest, st->GetCatchmentRadius()); + } + + /** Called by YAPF to detect if the node reaches the destination. */ + FORCEINLINE bool PfDetectDestination(const Node& n) const + { + return n.GetRouteLink() == NULL; + } + + /** Called by YAPF to calculate the estimated cost to the destination. */ + FORCEINLINE bool PfCalcEstimate(Node& n) + { + if (this->PfDetectDestination(n)) { + n.m_estimate = n.m_cost; + return true; + } + + /* Estimate based on Manhattan distance to destination. */ + Station *from = Station::Get(n.GetRouteLink()->GetDestination()); + int d = DistanceManhattan(from->xy, this->m_dest.tile) * this->Yapf().PfGetSettings().route_distance_factor; + + n.m_estimate = n.m_cost + d; + assert(n.m_estimate >= n.m_parent->m_estimate); + return true; + } +}; + +/** Main route finding class. */ +template +class CYapfFollowRouteLinkT { + typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class). + typedef typename Types::TrackFollower Follower; ///< The route follower. + typedef typename Types::NodeList::Titem Node; ///< This will be our node type. + + /** To access inherited path finder. */ + FORCEINLINE Tpf& Yapf() { return *static_cast(this); } + +public: + /** Called by YAPF to move from the given node to the next nodes. */ + inline void PfFollowNode(Node& old_node) + { + Follower f(this->Yapf().GetCargoID()); + + if (this->Yapf().PfDetectDestination(old_node.GetRouteLink()->GetDestination()) && (old_node.GetRouteLink()->GetDestOrderId() == INVALID_ORDER || Order::Get(old_node.GetRouteLink()->GetDestOrderId())->GetUnloadType() & OUFB_NO_UNLOAD) == 0) { + /* Possible destination? Add sentinel node for final delivery. */ + Node &n = this->Yapf().CreateNewNode(); + n.Set(&old_node, NULL); + this->Yapf().AddNewNode(n, f); + } + + if (f.Follow(old_node.GetRouteLink())) { + for (RouteLinkList::iterator link = f.m_new_links->begin(); link != f.m_new_links->end(); ++link) { + /* Add new node. */ + Node &n = this->Yapf().CreateNewNode(); + n.Set(&old_node, *link); + this->Yapf().AddNewNode(n, f); + } + } + } + + /** Return debug report character to identify the transportation type. */ + FORCEINLINE char TransportTypeChar() const + { + return 'c'; + } + + /** Find the best cargo routing from a station to a destination. */ + static RouteLink *ChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found, OrderID order, int max_cost) + { + /* Initialize pathfinder instance. */ + Tpf pf; + pf.SetOrigin(cid, src, stations, start_station != NULL, order, flags); + pf.SetDestination(dest, max_cost); + + *next_unload = INVALID_STATION; + + /* Do it. Exit if we didn't find a path. */ + bool res = pf.FindPath(NULL); + if (found != NULL) *found = res; + if (!res) return NULL; + + /* Walk back to find the start node. */ + Node *node = pf.GetBestNode(); + while (node->m_parent->m_parent != NULL) { + /* Transfer? Then save transfer station as next unload station. */ + if (node->GetRouteLink() == NULL || (node->GetRouteLink()->GetOriginOrderId() != node->m_parent->GetRouteLink()->GetDestOrderId())) { + *next_unload = node->m_parent->GetRouteLink()->GetDestination(); + } + + node = node->m_parent; + } + + /* Save result. */ + if (start_station != NULL) *start_station = node->m_parent->GetRouteLink()->GetDestination(); + return node->GetRouteLink(); + } +}; + +/** Config struct for route link finding. */ +template +struct CYapfRouteLink_TypesT { + typedef CYapfRouteLink_TypesT Types; + + typedef Tpf_ Tpf; ///< Pathfinder type + typedef CFollowRouteLinkT TrackFollower; ///< Node follower + typedef CRouteLinkNodeList NodeList; ///< Node list type + typedef Vehicle VehicleType; ///< Dummy type + + typedef CYapfBaseT PfBase; ///< Base pathfinder class + typedef CYapfFollowRouteLinkT PfFollow; ///< Node follower + typedef CYapfOriginRouteLinkT PfOrigin; ///< Origin provider + typedef CYapfDestinationRouteLinkT PfDestination; ///< Destination/distance provider + typedef CYapfSegmentCostCacheNoneT PfCache; ///< Cost cache provider + typedef CYapfCostRouteLinkT PfCost; ///< Cost provider +}; + +struct CYapfRouteLink : CYapfT > {}; + + +/** + * Find the best cargo routing from a station to a destination. + * @param cid Cargo type to route. + * @param stations Set of possible originating stations. + * @param dest Destination tile area. + * @param[out] start_station Station the best route link originates from. + * @param[out] next_unload Next station the cargo should be unloaded from the vehicle. + * @param flags Routing flags of the cargo. + * @param[out] found True if a link was found. + * @param order Order the vehicle arrived at the origin station. + * @param max_cost Maxmimum allowed node cost. + * @return The best RouteLink to the target or NULL if either no link found or one of the origin stations is the best destination. + */ +RouteLink *YapfChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found, OrderID order, int max_cost) +{ + return CYapfRouteLink::ChooseRouteLink(cid, stations, src, dest, start_station, next_unload, flags, found, order, max_cost); +} diff --git a/src/pathfinder/yapf/yapf_node.hpp b/src/pathfinder/yapf/yapf_node.hpp index 0eb9802..7905831 100644 --- a/src/pathfinder/yapf/yapf_node.hpp +++ b/src/pathfinder/yapf/yapf_node.hpp @@ -54,9 +54,8 @@ struct CYapfNodeT { int m_cost; int m_estimate; - FORCEINLINE void Set(Node *parent, TileIndex tile, Trackdir td, bool is_choice) + FORCEINLINE void Set(Node *parent, bool is_choice) { - m_key.Set(tile, td); m_hash_next = NULL; m_parent = parent; m_cost = 0; @@ -65,8 +64,6 @@ struct CYapfNodeT { FORCEINLINE Node *GetHashNext() {return m_hash_next;} FORCEINLINE void SetHashNext(Node *pNext) {m_hash_next = pNext;} - FORCEINLINE TileIndex GetTile() const {return m_key.m_tile;} - FORCEINLINE Trackdir GetTrackdir() const {return m_key.m_td;} FORCEINLINE const Tkey_& GetKey() const {return m_key;} FORCEINLINE int GetCost() const {return m_cost;} FORCEINLINE int GetCostEstimate() const {return m_estimate;} @@ -81,4 +78,21 @@ struct CYapfNodeT { } }; +/** Yapf Node base for trackdir based specialisation. */ +template +struct CYapfNodeTrackT : public CYapfNodeT +{ + typedef CYapfNodeT Base; + typedef Tnode Node; + + FORCEINLINE void Set(Node *parent, TileIndex tile, Trackdir td, bool is_choice) + { + Base::Set(parent, is_choice); + this->m_key.Set(tile, td); + } + + FORCEINLINE TileIndex GetTile() const { return this->m_key.m_tile; } + FORCEINLINE Trackdir GetTrackdir() const { return this->m_key.m_td; } +}; + #endif /* YAPF_NODE_HPP */ diff --git a/src/pathfinder/yapf/yapf_node_rail.hpp b/src/pathfinder/yapf/yapf_node_rail.hpp index 275133b..36110a0 100644 --- a/src/pathfinder/yapf/yapf_node_rail.hpp +++ b/src/pathfinder/yapf/yapf_node_rail.hpp @@ -190,9 +190,9 @@ struct CYapfRailSegment /** Yapf Node for rail YAPF */ template struct CYapfRailNodeT - : CYapfNodeT > + : CYapfNodeTrackT > { - typedef CYapfNodeT > base; + typedef CYapfNodeTrackT > base; typedef CYapfRailSegment CachedData; CYapfRailSegment *m_segment; diff --git a/src/pathfinder/yapf/yapf_node_road.hpp b/src/pathfinder/yapf/yapf_node_road.hpp index 5cc2d55..55f9fc4 100644 --- a/src/pathfinder/yapf/yapf_node_road.hpp +++ b/src/pathfinder/yapf/yapf_node_road.hpp @@ -15,9 +15,9 @@ /** Yapf Node for road YAPF */ template struct CYapfRoadNodeT - : CYapfNodeT > + : CYapfNodeTrackT > { - typedef CYapfNodeT > base; + typedef CYapfNodeTrackT > base; TileIndex m_segment_last_tile; Trackdir m_segment_last_td; diff --git a/src/pathfinder/yapf/yapf_node_ship.hpp b/src/pathfinder/yapf/yapf_node_ship.hpp index 7a1358a..59f0e9f 100644 --- a/src/pathfinder/yapf/yapf_node_ship.hpp +++ b/src/pathfinder/yapf/yapf_node_ship.hpp @@ -15,7 +15,7 @@ /** Yapf Node for ships */ template struct CYapfShipNodeT - : CYapfNodeT > + : CYapfNodeTrackT > { }; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index e255c68..39c16b9 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -185,6 +185,8 @@ void RoadVehUpdateCache(RoadVehicle *v) v->gcache.cached_total_length = 0; + uint32 cargo_mask = 0; + for (RoadVehicle *u = v; u != NULL; u = u->Next()) { /* Check the v->first cache. */ assert(u->First() == v); @@ -201,10 +203,14 @@ void RoadVehUpdateCache(RoadVehicle *v) /* Invalidate the vehicle colour map */ u->colourmap = PAL_NONE; + + /* Update carried cargo. */ + if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type); } uint max_speed = GetVehicleProperty(v, PROP_ROADVEH_SPEED, 0); v->vcache.cached_max_speed = (max_speed != 0) ? max_speed * 4 : RoadVehInfo(v->engine_type)->max_speed; + v->vcache.cached_cargo_mask = cargo_mask; } /** @@ -1353,9 +1359,8 @@ again: v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) && GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) { Station *st = Station::GetByTile(v->tile); - v->last_station_visited = st->index; RoadVehArrivesAt(v, st); - v->BeginLoading(); + v->BeginLoading(st->index); } return false; } @@ -1413,13 +1418,13 @@ again: rs->SetEntranceBusy(false); SetBit(v->state, RVS_ENTERED_STOP); - v->last_station_visited = st->index; - if (IsDriveThroughStopTile(v->tile) || (v->current_order.IsType(OT_GOTO_STATION) && v->current_order.GetDestination() == st->index)) { RoadVehArrivesAt(v, st); - v->BeginLoading(); + v->BeginLoading(st->index); return false; } + + v->last_station_visited = st->index; } else { /* Vehicle is ready to leave a bay in a road stop */ if (rs->IsEntranceBusy()) { diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 4acd05b..90cfba1 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -52,6 +52,7 @@ #include "../core/backup_type.hpp" #include "../smallmap_gui.h" #include "../news_func.h" +#include "../cargodest_func.h" #include "table/strings.h" @@ -256,6 +257,7 @@ static void InitializeWindowsAndCaches() Station::RecomputeIndustriesNearForAll(); RebuildSubsidisedSourceAndDestinationCache(); + RebuildCargoLinkCounts(); /* Towns have a noise controlled number of airports system * So each airport's noise value must be added to the town->noise_reached value @@ -2595,6 +2597,39 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(161)) { + /* Update cargo acceptance map of towns. */ + for (TileIndex t = 0; t < map_size; t++) { + if (!IsTileType(t, MP_HOUSE)) continue; + Town::Get(GetTownIndex(t))->cargo_accepted.Add(t); + } + + Town *town; + FOR_ALL_TOWNS(town) { + UpdateTownCargos(town); + } + + /* Update cargo acceptance of industries. */ + Industry *ind; + FOR_ALL_INDUSTRIES(ind) { + UpdateIndustryAcceptance(ind); + ind->average_production[0] = ind->last_month_production[0]; + ind->average_production[1] = ind->last_month_production[1]; + } + + UpdateCargoLinks(); + + Vehicle *v; + FOR_ALL_VEHICLES(v) { + /* Set the current order index from the order list. */ + Order *o = v->GetOrder(v->cur_auto_order_index); + if (o != NULL) v->current_order.index = o->index; + + /* Pre-fill route links from orders. */ + if (v->IsFrontEngine()) PrefillRouteLinks(v); + } + } + /* Road stops is 'only' updating some caches */ AfterLoadRoadStops(); AfterLoadLabelMaps(); diff --git a/src/saveload/cargodest_sl.cpp b/src/saveload/cargodest_sl.cpp new file mode 100644 index 0000000..e1572e7 --- /dev/null +++ b/src/saveload/cargodest_sl.cpp @@ -0,0 +1,181 @@ +/* $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_sl.cpp Code handling saving and loading of cargo destinations. */ + +#include "../stdafx.h" +#include "../cargodest_base.h" +#include "../town.h" +#include "../industry.h" +#include "saveload.h" + +static uint32 _cargolink_uint; +static const SaveLoadGlobVarList _cargolink_uint_desc[] = { + SLEG_VAR(_cargolink_uint, SLE_UINT32), + SLEG_END() +}; + +static const SaveLoad _cargolink_desc[] = { + SLE_VAR(CargoLink, amount.old_max, SLE_UINT32), + SLE_VAR(CargoLink, amount.new_max, SLE_UINT32), + SLE_VAR(CargoLink, amount.old_act, SLE_UINT32), + SLE_VAR(CargoLink, amount.new_act, SLE_UINT32), + SLE_VAR(CargoLink, weight, SLE_UINT32), + SLE_VAR(CargoLink, weight_mod, SLE_UINT8), + SLE_END() +}; + +void CargoSourceSink::SaveCargoSourceSink() +{ + if (IsSavegameVersionBefore(161)) return; + + static const SaveLoad _cargosourcesink_desc[] = { + SLE_ARR(CargoSourceSink, cargo_links_weight, SLE_UINT32, NUM_CARGO), + SLE_END() + }; + SlObject(this, _cargosourcesink_desc); + + for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) { + _cargolink_uint = this->cargo_links[cid].Length(); + SlObject(NULL, _cargolink_uint_desc); + for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) { + SourceID dest = INVALID_SOURCE; + SourceTypeByte type; + type = ST_TOWN; + + if (l->dest != NULL) { + type = l->dest->GetType(); + dest = l->dest->GetID(); + } + + /* Pack type and destination index into temp variable. */ + assert_compile(sizeof(SourceID) <= 3); + _cargolink_uint = type | (dest << 8); + + SlGlobList(_cargolink_uint_desc); + SlObject(l, _cargolink_desc); + } + } +} + +void CargoSourceSink::LoadCargoSourceSink() +{ + if (IsSavegameVersionBefore(161)) return; + + static const SaveLoad _cargosourcesink_desc[] = { + SLE_ARR(CargoSourceSink, cargo_links_weight, SLE_UINT32, NUM_CARGO), + SLE_END() + }; + SlObject(this, _cargosourcesink_desc); + + for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) { + /* Remove links created by constructors. */ + this->cargo_links[cid].Clear(); + /* Read vector length and allocate storage. */ + SlObject(NULL, _cargolink_uint_desc); + this->cargo_links[cid].Append(_cargolink_uint); + + for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) { + /* Read packed type and dest and store in dest pointer. */ + SlGlobList(_cargolink_uint_desc); + *(size_t*)&l->dest = _cargolink_uint; + + SlObject(l, _cargolink_desc); + } + } +} + +void CargoSourceSink::PtrsCargoSourceSink() +{ + if (IsSavegameVersionBefore(161)) return; + + for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) { + for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) { + /* Extract type and destination index. */ + SourceType type = (SourceType)((size_t)l->dest & 0xFF); + SourceID dest = (SourceID)((size_t)l->dest >> 8); + + /* Resolve index. */ + l->dest = NULL; + if (dest != INVALID_SOURCE) { + switch (type) { + case ST_TOWN: + if (!Town::IsValidID(dest)) SlErrorCorrupt("Invalid cargo link destination"); + l->dest = Town::Get(dest); + break; + + case ST_INDUSTRY: + if (!Industry::IsValidID(dest)) SlErrorCorrupt("Invalid cargo link destination"); + l->dest = Industry::Get(dest); + break; + + default: + SlErrorCorrupt("Invalid cargo link destination type"); + } + } + } + } +} + +/** + * Wrapper function to get the RouteLinks's internal structure while + * some of the variables itself are private. + * @return The SaveLoad description for RouteLinks. + */ +const SaveLoad *GetRouteLinkDescription() +{ + static const SaveLoad _routelink_desc[] = { + SLE_VAR(RouteLink, dest, SLE_UINT16), + SLE_VAR(RouteLink, prev_order, SLE_UINT16), + SLE_VAR(RouteLink, next_order, SLE_UINT16), + SLE_VAR(RouteLink, owner, SLE_UINT8), + SLE_VAR(RouteLink, vtype, SLE_UINT8), + SLE_VAR(RouteLink, travel_time, SLE_UINT32), + SLE_VAR(RouteLink, wait_time, SLE_UINT16), + + SLE_END() + }; + return _routelink_desc; +} + +/** Save the RouteLink chunk. */ +static void Save_RTLN() +{ + RouteLink *link; + + FOR_ALL_ROUTELINKS(link) { + SlSetArrayIndex(link->index); + SlObject(link, GetRouteLinkDescription()); + } +} + +/** Load the RouteLink chunk. */ +static void Load_RTLN() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + RouteLink *link = new (index) RouteLink(); + SlObject(link, GetRouteLinkDescription()); + } +} + +/** Resolve references after loading the RouteLink chunk. */ +static void Ptrs_RTLN() +{ + RouteLink *link; + + FOR_ALL_ROUTELINKS(link) { + SlObject(link, GetRouteLinkDescription()); + } +} + +extern const ChunkHandler _routelink_chunk_handlers[] = { + { 'RTLN', Save_RTLN, Load_RTLN, Ptrs_RTLN, NULL, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/cargopacket_sl.cpp b/src/saveload/cargopacket_sl.cpp index e36cede..c9c87ab 100644 --- a/src/saveload/cargopacket_sl.cpp +++ b/src/saveload/cargopacket_sl.cpp @@ -95,6 +95,12 @@ const SaveLoad *GetCargoPacketDesc() SLE_VAR(CargoPacket, feeder_share, SLE_INT64), SLE_CONDVAR(CargoPacket, source_type, SLE_UINT8, 125, SL_MAX_VERSION), SLE_CONDVAR(CargoPacket, source_id, SLE_UINT16, 125, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, dest_xy, SLE_UINT32, 161, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, dest_id, SLE_UINT16, 161, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, dest_type, SLE_UINT8, 161, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, flags, SLE_UINT8, 161, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, next_order, SLE_UINT16, 161, SL_MAX_VERSION), + SLE_CONDVAR(CargoPacket, next_station, SLE_UINT16, 161, SL_MAX_VERSION), /* Used to be paid_for, but that got changed. */ SLE_CONDNULL(1, 0, 120), diff --git a/src/saveload/economy_sl.cpp b/src/saveload/economy_sl.cpp index 9bdad61..f446b2f 100644 --- a/src/saveload/economy_sl.cpp +++ b/src/saveload/economy_sl.cpp @@ -67,6 +67,8 @@ static const SaveLoad _cargopayment_desc[] = { SLE_VAR(CargoPayment, route_profit, SLE_INT64), SLE_VAR(CargoPayment, visual_profit, SLE_INT64), + SLE_CONDVAR(CargoPayment, transfer_profit, SLE_INT64, 161, SL_MAX_VERSION), + SLE_END() }; diff --git a/src/saveload/industry_sl.cpp b/src/saveload/industry_sl.cpp index 2ba07a1..124349c 100644 --- a/src/saveload/industry_sl.cpp +++ b/src/saveload/industry_sl.cpp @@ -28,12 +28,14 @@ static const SaveLoad _industry_desc[] = { SLE_ARR(Industry, production_rate, SLE_UINT8, 2), SLE_CONDNULL( 3, 0, 60), ///< used to be industry's accepts_cargo SLE_CONDARR(Industry, accepts_cargo, SLE_UINT8, 3, 78, SL_MAX_VERSION), + SLE_CONDVAR(Industry, produced_accepted_mask, SLE_UINT32, 161, SL_MAX_VERSION), SLE_VAR(Industry, prod_level, SLE_UINT8), SLE_ARR(Industry, this_month_production, SLE_UINT16, 2), SLE_ARR(Industry, this_month_transported, SLE_UINT16, 2), SLE_ARR(Industry, last_month_pct_transported, SLE_UINT8, 2), SLE_ARR(Industry, last_month_production, SLE_UINT16, 2), SLE_ARR(Industry, last_month_transported, SLE_UINT16, 2), + SLE_CONDARR(Industry, average_production, SLE_UINT16, 2, 161, SL_MAX_VERSION), SLE_VAR(Industry, counter, SLE_UINT16), @@ -60,6 +62,12 @@ static const SaveLoad _industry_desc[] = { SLE_END() }; +static void RealSave_INDY(Industry *ind) +{ + SlObject(ind, _industry_desc); + ind->SaveCargoSourceSink(); +} + static void Save_INDY() { Industry *ind; @@ -67,7 +75,7 @@ static void Save_INDY() /* Write the industries */ FOR_ALL_INDUSTRIES(ind) { SlSetArrayIndex(ind->index); - SlObject(ind, _industry_desc); + SlAutolength((AutolengthProc *)RealSave_INDY, ind); } } @@ -90,6 +98,7 @@ static void Load_INDY() while ((index = SlIterateArray()) != -1) { Industry *i = new (index) Industry(); SlObject(i, _industry_desc); + i->LoadCargoSourceSink(); Industry::IncIndustryTypeCount(i->type); } } @@ -110,6 +119,7 @@ static void Ptrs_INDY() FOR_ALL_INDUSTRIES(i) { SlObject(i, _industry_desc); + i->PtrsCargoSourceSink(); } } diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index 362df6b..ff8423c 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -568,14 +568,14 @@ static const OldChunks town_chunk[] = { OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, Town, time_until_rebuild ), OCL_SVAR( OC_FILE_U8 | OC_VAR_I16, Town, growth_rate ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, new_max_pass ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, new_max_mail ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, new_act_pass ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, new_act_mail ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, max_pass ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, max_mail ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, act_pass ), - OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, act_mail ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, pass.new_max ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, mail.new_max ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, pass.new_act ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, mail.new_act ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, pass.old_max ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, mail.old_max ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, pass.old_act ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Town, mail.old_act ), OCL_SVAR( OC_UINT8, Town, pct_pass_transported ), OCL_SVAR( OC_UINT8, Town, pct_mail_transported ), diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 11d4929..96dca38 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -42,6 +42,7 @@ #include "../engine_base.h" #include "../fios.h" #include "../gui.h" +#include "../cargodest_base.h" #include "table/strings.h" @@ -225,8 +226,9 @@ * 158 21933 * 159 21962 * 160 21974 + * 161 yacd */ -extern const uint16 SAVEGAME_VERSION = 160; ///< Current savegame version of OpenTTD. +extern const uint16 SAVEGAME_VERSION = 161; ///< Current savegame version of OpenTTD. SavegameType _savegame_type; ///< type of savegame we are loading @@ -404,6 +406,7 @@ extern const ChunkHandler _autoreplace_chunk_handlers[]; extern const ChunkHandler _labelmaps_chunk_handlers[]; extern const ChunkHandler _airport_chunk_handlers[]; extern const ChunkHandler _object_chunk_handlers[]; +extern const ChunkHandler _routelink_chunk_handlers[]; /** Array of all chunks in a savegame, \c NULL terminated. */ static const ChunkHandler * const _chunk_handlers[] = { @@ -434,6 +437,7 @@ static const ChunkHandler * const _chunk_handlers[] = { _labelmaps_chunk_handlers, _airport_chunk_handlers, _object_chunk_handlers, + _routelink_chunk_handlers, NULL, }; @@ -1176,6 +1180,7 @@ static size_t ReferenceToInt(const void *obj, SLRefType rt) case REF_ENGINE_RENEWS: return ((const EngineRenew*)obj)->index + 1; case REF_CARGO_PACKET: return ((const CargoPacket*)obj)->index + 1; case REF_ORDERLIST: return ((const OrderList*)obj)->index + 1; + case REF_ROUTE_LINK: return ((const RouteLink*)obj)->index + 1; default: NOT_REACHED(); } } @@ -1245,6 +1250,10 @@ static void *IntToReference(size_t index, SLRefType rt) if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index); SlErrorCorrupt("Referencing invalid CargoPacket"); + case REF_ROUTE_LINK: + if (RouteLink::IsValidID(index)) return RouteLink::Get(index); + SlErrorCorrupt("Referencing invalid RouteLink"); + default: NOT_REACHED(); } } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index b5fb001..64d32e7 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -88,6 +88,7 @@ enum SLRefType { REF_ENGINE_RENEWS = 6, ///< Load/save a reference to an engine renewal (autoreplace). REF_CARGO_PACKET = 7, ///< Load/save a reference to a cargo packet. REF_ORDERLIST = 8, ///< Load/save a reference to an orderlist. + REF_ROUTE_LINK = 9, ///< Load/save a reference to a route link. }; /** Highest possible savegame version. */ diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index f32e476..3e7ec9a 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -258,7 +258,10 @@ const SaveLoad *GetGoodsDesc() SLEG_CONDVAR( _cargo_feeder_share, SLE_FILE_U32 | SLE_VAR_I64, 14, 64), SLEG_CONDVAR( _cargo_feeder_share, SLE_INT64, 65, 67), SLE_CONDVAR(GoodsEntry, amount_fract, SLE_UINT8, 150, SL_MAX_VERSION), + SLE_CONDVAR(GoodsEntry, cargo_counter, SLE_UINT16, 161, SL_MAX_VERSION), SLE_CONDLST(GoodsEntry, cargo.packets, REF_CARGO_PACKET, 68, SL_MAX_VERSION), + SLE_CONDVAR(GoodsEntry, cargo.next_start, SLE_UINT32, 161, SL_MAX_VERSION), + SLE_CONDLST(GoodsEntry, routes, REF_ROUTE_LINK, 161, SL_MAX_VERSION), SLE_END() }; diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp index e246f61..824cd84 100644 --- a/src/saveload/town_sl.cpp +++ b/src/saveload/town_sl.cpp @@ -97,6 +97,7 @@ void UpdateHousesAndTowns() /* Update the population and num_house dependant values */ FOR_ALL_TOWNS(town) { UpdateTownRadius(town); + UpdateTownCargos(town); } } @@ -128,23 +129,23 @@ static const SaveLoad _town_desc[] = { SLE_CONDARR(Town, unwanted, SLE_INT8, 8, 4, 103), SLE_CONDARR(Town, unwanted, SLE_INT8, MAX_COMPANIES, 104, SL_MAX_VERSION), - SLE_CONDVAR(Town, max_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - SLE_CONDVAR(Town, max_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - SLE_CONDVAR(Town, new_max_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - SLE_CONDVAR(Town, new_max_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - SLE_CONDVAR(Town, act_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - SLE_CONDVAR(Town, act_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - SLE_CONDVAR(Town, new_act_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - SLE_CONDVAR(Town, new_act_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), - - SLE_CONDVAR(Town, max_pass, SLE_UINT32, 9, SL_MAX_VERSION), - SLE_CONDVAR(Town, max_mail, SLE_UINT32, 9, SL_MAX_VERSION), - SLE_CONDVAR(Town, new_max_pass, SLE_UINT32, 9, SL_MAX_VERSION), - SLE_CONDVAR(Town, new_max_mail, SLE_UINT32, 9, SL_MAX_VERSION), - SLE_CONDVAR(Town, act_pass, SLE_UINT32, 9, SL_MAX_VERSION), - SLE_CONDVAR(Town, act_mail, SLE_UINT32, 9, SL_MAX_VERSION), - SLE_CONDVAR(Town, new_act_pass, SLE_UINT32, 9, SL_MAX_VERSION), - SLE_CONDVAR(Town, new_act_mail, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, pass.old_max, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, mail.old_max, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, pass.new_max, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, mail.new_max, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, pass.old_act, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, mail.old_act, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, pass.new_act, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, mail.new_act, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + + SLE_CONDVAR(Town, pass.old_max, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, mail.old_max, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, pass.new_max, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, mail.new_max, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, pass.old_act, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, mail.old_act, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, pass.new_act, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, mail.new_act, SLE_UINT32, 9, SL_MAX_VERSION), SLE_VAR(Town, pct_pass_transported, SLE_UINT8), SLE_VAR(Town, pct_mail_transported, SLE_UINT8), @@ -171,6 +172,8 @@ static const SaveLoad _town_desc[] = { SLE_CONDVAR(Town, larger_town, SLE_BOOL, 56, SL_MAX_VERSION), SLE_CONDVAR(Town, layout, SLE_UINT8, 113, SL_MAX_VERSION), + SLE_CONDVAR(Town, cargo_produced, SLE_UINT32, 161, SL_MAX_VERSION), + /* reserve extra space in savegame here. (currently 30 bytes) */ SLE_CONDNULL(30, 2, SL_MAX_VERSION), @@ -187,27 +190,81 @@ static void Load_HIDS() Load_NewGRFMapping(_house_mngr); } +const SaveLoad *GetTileMatrixDesc() +{ + /* Here due to private member vars. */ + static const SaveLoad _tilematrix_desc[] = { + SLE_VAR(AcceptanceMatrix, area.tile, SLE_UINT32), + SLE_VAR(AcceptanceMatrix, area.w, SLE_UINT16), + SLE_VAR(AcceptanceMatrix, area.h, SLE_UINT16), + SLE_END() + }; + + return _tilematrix_desc; +} + +void RealSave_TOWN(Town *t) +{ + SlObject(t, _town_desc); + t->SaveCargoSourceSink(); + + if (IsSavegameVersionBefore(161)) return; + + SlObject(&t->cargo_accepted, GetTileMatrixDesc()); + if (t->cargo_accepted.area.w != 0) { + uint arr_len = t->cargo_accepted.area.w / AcceptanceMatrix::GRID * t->cargo_accepted.area.h / AcceptanceMatrix::GRID; + SlArray(t->cargo_accepted.data, arr_len, SLE_UINT32); + } +} + static void Save_TOWN() { Town *t; FOR_ALL_TOWNS(t) { SlSetArrayIndex(t->index); - SlObject(t, _town_desc); + SlAutolength((AutolengthProc *)RealSave_TOWN, t); } } -static void Load_TOWN() +void Load_TOWN() { int index; while ((index = SlIterateArray()) != -1) { Town *t = new (index) Town(); SlObject(t, _town_desc); + t->LoadCargoSourceSink(); + + if (IsSavegameVersionBefore(161)) continue; + + SlObject(&t->cargo_accepted, GetTileMatrixDesc()); + if (t->cargo_accepted.area.w != 0) { + uint arr_len = t->cargo_accepted.area.w / AcceptanceMatrix::GRID * t->cargo_accepted.area.h / AcceptanceMatrix::GRID; + t->cargo_accepted.data = MallocT(arr_len); + SlArray(t->cargo_accepted.data, arr_len, SLE_UINT32); + + /* Rebuild total cargo acceptance. */ + UpdateTownCargoTotal(t); + } + + /* Cache the aligned tile index of the centre tile. */ + uint town_x = (TileX(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + uint town_y = (TileY(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + t->xy_aligned= TileXY(town_x, town_y); + } +} + +static void Ptrs_TOWN() +{ + Town *t; + + FOR_ALL_TOWNS(t) { + t->PtrsCargoSourceSink(); } } extern const ChunkHandler _town_chunk_handlers[] = { - { 'HIDS', Save_HIDS, Load_HIDS, NULL, NULL, CH_ARRAY }, - { 'CITY', Save_TOWN, Load_TOWN, NULL, NULL, CH_ARRAY | CH_LAST}, + { 'HIDS', Save_HIDS, Load_HIDS, NULL, NULL, CH_ARRAY }, + { 'CITY', Save_TOWN, Load_TOWN, Ptrs_TOWN, NULL, CH_ARRAY | CH_LAST}, }; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index f3af881..106b2ba 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -489,6 +489,8 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) SLE_VAR(Vehicle, vehstatus, SLE_UINT8), SLE_CONDVAR(Vehicle, last_station_visited, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), SLE_CONDVAR(Vehicle, last_station_visited, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, last_station_loaded, SLE_UINT16, 161, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, last_order_id, SLE_UINT16, 161, SL_MAX_VERSION), SLE_VAR(Vehicle, cargo_type, SLE_UINT8), SLE_CONDVAR(Vehicle, cargo_subtype, SLE_UINT8, 35, SL_MAX_VERSION), @@ -500,6 +502,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) SLEG_CONDVAR( _cargo_count, SLE_UINT16, 0, 67), SLE_CONDLST(Vehicle, cargo.packets, REF_CARGO_PACKET, 68, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, travel_time, SLE_UINT32, 161, SL_MAX_VERSION), SLE_VAR(Vehicle, day_counter, SLE_UINT8), SLE_VAR(Vehicle, tick_counter, SLE_UINT8), SLE_CONDVAR(Vehicle, running_ticks, SLE_UINT8, 88, SL_MAX_VERSION), @@ -519,6 +522,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) SLE_CONDVAR(Vehicle, current_order.type, SLE_UINT8, 5, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, current_order.flags, SLE_UINT8, 5, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, current_order.dest, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, current_order.index, SLE_UINT16, 161, SL_MAX_VERSION), /* Refit in current order */ SLE_CONDVAR(Vehicle, current_order.refit_cargo, SLE_UINT8, 36, SL_MAX_VERSION), diff --git a/src/settings.cpp b/src/settings.cpp index 38101e7..6deeb2a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -61,6 +61,8 @@ #include "smallmap_gui.h" #include "roadveh.h" #include "fios.h" +#include "industry.h" +#include "cargodest_func.h" #include "void_map.h" #include "station_base.h" @@ -1148,6 +1150,64 @@ static bool StationCatchmentChanged(int32 p1) return true; } +bool CargodestModeChanged(int32 p1) +{ + /* Clear route links and destinations for cargoes that aren't routed anymore. */ + Station *st; + FOR_ALL_STATIONS(st) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (CargoHasDestinations(cid)) continue; + + /* Clear route links. */ + for (RouteLinkList::iterator i = st->goods[cid].routes.begin(); i != st->goods[cid].routes.end(); ++i) { + delete *i; + } + st->goods[cid].routes.clear(); + + /* Remove destinations from cargo packets. */ + for (StationCargoList::Iterator i = st->goods[cid].cargo.packets.begin(); i != st->goods[cid].cargo.packets.end(); ++i) { + (*i)->dest_id = INVALID_SOURCE; + (*i)->next_order = INVALID_ORDER; + (*i)->next_station = INVALID_STATION; + } + st->goods[cid].cargo.InvalidateCache(); + } + } + + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->IsFrontEngine()) PrefillRouteLinks(v); + if (CargoHasDestinations(v->cargo_type)) continue; + /* Remove destination from all cargoes that aren't routed anymore. */ + for (VehicleCargoList::Iterator i = v->cargo.packets.begin(); i != v->cargo.packets.end(); ++i) { + (*i)->dest_id = INVALID_SOURCE; + (*i)->next_order = INVALID_ORDER; + (*i)->next_station = INVALID_STATION; + } + v->cargo.InvalidateCache(); + } + + /* Clear all links for cargoes that aren't routed anymore. */ + CargoSourceSink *css; + FOR_ALL_TOWNS(css) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (!CargoHasDestinations(cid)) css->cargo_links[cid].Clear(); + } + } + FOR_ALL_INDUSTRIES(css) { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (!CargoHasDestinations(cid)) css->cargo_links[cid].Clear(); + } + } + + /* Recount incoming cargo links. */ + RebuildCargoLinkCounts(); + + /* Update remaining links. */ + UpdateCargoLinks(); + + return true; +} #ifdef ENABLE_NETWORK diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 9d0ce42..557a94a 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1475,6 +1475,9 @@ static SettingEntry _settings_economy[] = { SettingEntry("economy.inflation"), SettingEntry("economy.smooth_economy"), SettingEntry("economy.feeder_payment_share"), + SettingEntry("economy.cargodest.mode_pax_mail"), + SettingEntry("economy.cargodest.mode_town_cargo"), + SettingEntry("economy.cargodest.mode_others"), }; /** Economy sub-page */ static SettingsPage _settings_economy_page = {_settings_economy, lengthof(_settings_economy)}; diff --git a/src/settings_type.h b/src/settings_type.h index 5179c7d..8111fe5 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -325,6 +325,14 @@ struct YAPFSettings { uint32 rail_longer_platform_per_tile_penalty; ///< penalty for longer station platform than train (per tile) uint32 rail_shorter_platform_penalty; ///< penalty for shorter station platform than train uint32 rail_shorter_platform_per_tile_penalty; ///< penalty for shorter station platform than train (per tile) + + uint32 route_transfer_cost; ///< penalty for transferring to a different vehicle + uint32 route_max_transfers; ///< maximum number of allowed transfers + uint16 route_distance_factor; ///< factor for the link length + uint16 route_travel_time_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the link travel time + uint16 route_station_last_veh_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the time since the last vehicle arrived at a station + uint16 route_station_waiting_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the waiting cargo at a station + byte route_mode_cost_factor[4]; ///< vehicle type dependent factor for the link length }; /** Settings related to all pathfinders. */ @@ -382,6 +390,33 @@ struct VehicleSettings { uint8 plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal }; +/** Settings related to cargo destinations. */ +struct CargodestSettings { + uint8 mode_pax_mail; ///< routing mode for cargoes with TE_PASSENGERS or TE_MAIL + uint8 mode_town_cargo; ///< routing mode for cargoes with other town effects + uint8 mode_others; ///< routing mode for all other cargoes + uint8 base_town_links[2]; ///< minimum number of town demand links for (0=#BASE_TOWN_LINKS) all cargos except (1=#BASE_TOWN_LINKS_SYMM) symmetric cargos + uint8 base_ind_links[3]; ///< minimum number of industry demand links for (0=#BASE_IND_LINKS) all cargos except (1=#BASE_IND_LINKS_TOWN) town cargos and (2=#BASE_IND_LINKS_SYMM) symmetric cargos + uint8 city_town_links; ///< additional number of links for cities + uint8 town_chances_town[4]; ///< chances a link from a town to a town has a specific destination class (@see FindTownDestination) + uint8 town_chances_city[4]; ///< chances a link from a city to a town has a specific destination class (@see FindTownDestination) + uint8 ind_chances[3]; ///< chances a link to an industry has a specific destination class (@see FindIndustryDestination) + uint8 random_dest_chance; ///< percentage for traffic with random destination + uint32 big_town_pop[2]; ///< (0=#BIG_TOWN_POP_MAIL) mail, (1=#BIG_TOWN_POP_PAX) passenger amount to count as a big town + uint16 pop_scale_town[4]; ///< population/cargo amount scale divisor for (0=#SCALE_TOWN) all cargos (1=#SCALE_TOWN_BIG) for big towns except (2=#SCALE_TOWN_PAX) passengers (3=#SCALE_TOWN_BIG_PAX) for big towns + uint16 cargo_scale_ind[2]; ///< cargo amount scale divisor for (0=#CARGO_SCALE_IND) all cargos except (1=#CARGO_SCALE_IND_TOWN) town cargos + uint16 min_weight_town[2]; ///< minimum link weight for (0=MIN_WEIGHT_TOWN) all cargos except (1=MIN_WEIGHT_TOWN_PAX) passengers + uint16 min_weight_ind; ///< minimum link weight for industry links + uint16 weight_scale_town[4]; ///< weight scale divisor for (0=#SCALE_TOWN) all cargos (1=#SCALE_TOWN_BIG) for big towns except (2=#SCALE_TOWN_PAX) passengers (3=#SCALE_TOWN_BIG_PAX) for big towns + uint16 weight_scale_ind[2]; ///< weight scale divisor for (0=#WEIGHT_SCALE_IND_PROD) produced cargo (1=#WEIGHT_SCALE_IND_PILE) stockpiled cargo + uint32 town_nearby_dist; ///< squared distance (on a 256x256 map) inside which a town is considered nearby + uint32 ind_nearby_dist; ///< squared distance (on a 256x256 map) inside which an industry is considered nearby + uint16 max_route_age; ///< maximum days since the last vehicle traveled a link until link expiration + uint16 route_recalc_delay; ///< delay in ticks between recalculating the next hop of cargo packets + uint16 route_recalc_chunk; ///< maximum amount of cargo packets to recalculate in one step + uint16 max_route_penalty[2]; ///< maximum penalty factor based on distance, (1) base value, (2) random additional span +}; + /** Settings related to the economy. */ struct EconomySettings { bool inflation; ///< disable inflation @@ -404,6 +439,8 @@ struct EconomySettings { bool station_noise_level; ///< build new airports when the town noise level is still within accepted limits uint16 town_noise_population[3]; ///< population to base decision on noise evaluation (@see town_council_tolerance) bool allow_town_level_crossings; ///< towns are allowed to build level crossings + + CargodestSettings cargodest; ///< settings related to cargo destinations }; /** Settings related to stations. */ diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 142dec0..99965d9 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -32,6 +32,7 @@ #include "pathfinder/opf/opf_ship.h" #include "engine_base.h" #include "company_base.h" +#include "cargotype.h" #include "table/strings.h" @@ -156,6 +157,7 @@ static void CheckIfShipNeedsService(Vehicle *v) void Ship::UpdateCache() { this->vcache.cached_max_speed = GetVehicleProperty(this, PROP_SHIP_SPEED, ShipVehInfo(this->engine_type)->max_speed); + this->vcache.cached_cargo_mask = (this->cargo_type != INVALID_CARGO && this->cargo_cap > 0) ? 1 << this->cargo_type : 0; this->UpdateVisualEffect(); } @@ -500,14 +502,13 @@ static void ShipController(Ship *v) return; } } else if (v->current_order.IsType(OT_GOTO_STATION)) { - v->last_station_visited = v->current_order.GetDestination(); - /* Process station in the orderlist. */ Station *st = Station::Get(v->current_order.GetDestination()); if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations ShipArrivesAt(v, st); - v->BeginLoading(); + v->BeginLoading(st->index); } else { // leave stations without docks right aways + v->last_station_visited = v->current_order.GetDestination(); v->current_order.MakeLeaveStation(); v->IncrementRealOrderIndex(); } diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index 7989c5d..0c8f792 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -26,6 +26,10 @@ #include "sound_func.h" #include "window_func.h" #include "company_base.h" +#include "station_base.h" +#include "company_func.h" +#include "cargotype.h" +#include "core/smallmap_type.hpp" #include "table/strings.h" @@ -40,6 +44,7 @@ enum SmallMapWindowWidgets { SM_WIDGET_CONTOUR, ///< Button to select the contour view (height map). SM_WIDGET_VEHICLES, ///< Button to select the vehicles view. SM_WIDGET_INDUSTRIES, ///< Button to select the industries view. + SM_WIDGET_ROUTE_LINKS, ///< Button to select the route link view. SM_WIDGET_ROUTES, ///< Button to select the routes view. SM_WIDGET_VEGETATION, ///< Button to select the vegetation view. SM_WIDGET_OWNERS, ///< Button to select the owners view. @@ -53,6 +58,7 @@ enum SmallMapWindowWidgets { static int _smallmap_industry_count; ///< Number of used industries static int _smallmap_company_count; ///< Number of entries in the owner legend. +static int _smallmap_cargo_count; ///< Number of entries in the cargo legend. static const int NUM_NO_COMPANY_ENTRIES = 4; ///< Number of entries in the owner legend that are not companies. @@ -64,25 +70,25 @@ static const uint8 PC_TREES = 0x57; ///< Green palette colour for tree static const uint8 PC_WATER = 0xCA; ///< Dark blue palette colour for water. /** Macro for ordinary entry of LegendAndColour */ -#define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, false} +#define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, false} /** Macro for a height legend entry with configurable colour. */ -#define MC(height) {0, STR_TINY_BLACK_HEIGHT, INVALID_INDUSTRYTYPE, height, INVALID_COMPANY, true, false, false} +#define MC(height) {0, STR_TINY_BLACK_HEIGHT, INVALID_INDUSTRYTYPE, height, INVALID_COMPANY, INVALID_CARGO, true, false, false} /** Macro for non-company owned property entry of LegendAndColour */ -#define MO(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, false} +#define MO(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, false} /** Macro used for forcing a rebuild of the owner legend the first time it is used. */ -#define MOEND() {0, 0, INVALID_INDUSTRYTYPE, 0, OWNER_NONE, true, true, false} +#define MOEND() {0, 0, INVALID_INDUSTRYTYPE, 0, OWNER_NONE, INVALID_CARGO, true, true, false} /** Macro for end of list marker in arrays of LegendAndColour */ -#define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, true, false} +#define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, true, false} /** * Macro for break marker in arrays of LegendAndColour. * It will have valid data, though */ -#define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, true} +#define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, true} /** Structure for holding relevant data for legends in small map */ struct LegendAndColour { @@ -91,6 +97,7 @@ struct LegendAndColour { IndustryType type; ///< Type of industry. Only valid for industry entries. uint8 height; ///< Height in tiles. Only valid for height legend entries. CompanyID company; ///< Company to display. Only valid for company entries of the owner legend. + CargoID cid; ///< Cargo type to display. Only valid for entries of the cargo legend. bool show_on_map; ///< For filtering industries, if \c true, industry is shown on the map in colour. bool end; ///< This is the end of the list. bool col_break; ///< Perform a column break and go further at the next column. @@ -176,6 +183,10 @@ static LegendAndColour _legend_land_owners[NUM_NO_COMPANY_ENTRIES + MAX_COMPANIE static LegendAndColour _legend_from_industries[NUM_INDUSTRYTYPES + 1]; /** For connecting industry type to position in industries list(small map legend) */ static uint _industry_to_list_pos[NUM_INDUSTRYTYPES]; +/** Legend text for the cargo types in the route link legend. */ +static LegendAndColour _legend_from_cargoes[NUM_CARGO + 1]; +/** For connecting cargo type to position in route link legend. */ +static uint _cargotype_to_list_pos[NUM_CARGO]; /** Show heightmap in industry and owner mode of smallmap window. */ static bool _smallmap_show_heightmap = false; /** For connecting company ID to position in owner list (small map legend) */ @@ -212,10 +223,38 @@ void BuildIndustriesLegend() _smallmap_industry_count = j; } +/** Fills the array for the route link legend. */ +void BuildCargoTypesLegend() +{ + uint j = 0; + + /* Add all standard cargo types. */ + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + _legend_from_cargoes[j].legend = cs->name; + _legend_from_cargoes[j].colour = cs->legend_colour; + _legend_from_cargoes[j].cid = cs->Index(); + _legend_from_cargoes[j].show_on_map = true; + _legend_from_cargoes[j].col_break = false; + _legend_from_cargoes[j].end = false; + + /* Store widget number for this cargo type. */ + _cargotype_to_list_pos[cs->Index()] = j; + j++; + } + + /* Terminate list. */ + _legend_from_cargoes[j].end = true; + + /* Store number of enabled cargos. */ + _smallmap_cargo_count = j; +} + static const LegendAndColour * const _legend_table[] = { _legend_land_contours, _legend_vehicles, _legend_from_industries, + _legend_from_cargoes, _legend_routes, _legend_vegetation, _legend_land_owners, @@ -478,9 +517,10 @@ static inline uint32 GetSmallMapIndustriesPixels(TileIndex tile, TileType t) * * @param tile The tile of which we would like to get the colour. * @param t Effective tile type of the tile (see #GetEffectiveTileType). + * @param show_height Whether to show the height of plain tiles. * @return The colour of tile in the small map in mode "Routes" */ -static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t) +static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t, bool show_height = false) { if (t == MP_STATION) { switch (GetStationType(tile)) { @@ -503,7 +543,7 @@ static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t) /* Ground colour */ const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour]; - return ApplyMask(cs->default_colour, &_smallmap_contours_andor[t]); + return ApplyMask(show_height ? cs->height_colours[TileHeight(tile)] : cs->default_colour, &_smallmap_contours_andor[t]); } @@ -590,6 +630,7 @@ class SmallMapWindow : public Window { SMT_CONTOUR, SMT_VEHICLES, SMT_INDUSTRY, + SMT_ROUTE_LINKS, SMT_ROUTES, SMT_VEGETATION, SMT_OWNER, @@ -779,6 +820,9 @@ class SmallMapWindow : public Window { case SMT_INDUSTRY: return GetSmallMapIndustriesPixels(tile, et); + case SMT_ROUTE_LINKS: + return GetSmallMapRoutesPixels(tile, et, _smallmap_show_heightmap); + case SMT_ROUTES: return GetSmallMapRoutesPixels(tile, et); @@ -907,6 +951,92 @@ class SmallMapWindow : public Window { } /** + * Adds the route links to the smallmap. + */ + void DrawRouteLinks() const + { + /* Iterate all shown cargo types. */ + for (int i = 0; i < _smallmap_cargo_count; i++) { + if (_legend_from_cargoes[i].show_on_map) { + CargoID cid = _legend_from_cargoes[i].cid; + + /* Iterate all stations. */ + const Station *st; + FOR_ALL_STATIONS(st) { + Point src_pt = this->RemapTile(TileX(st->xy), TileY(st->xy)); + src_pt.x -= this->subscroll; + + /* Collect waiting cargo per destination station. */ + std::map links; + for (RouteLinkList::const_iterator l = st->goods[cid].routes.begin(); l != st->goods[cid].routes.end(); ++l) { + if (IsInteractiveCompany((*l)->GetOwner())) links[(*l)->GetDestination()] += st->goods[cid].cargo.CountForNextHop((*l)->GetOriginOrderId()); + } + + /* Add cargo count on back-links. */ + for (std::map::iterator itr = links.begin(); itr != links.end(); ++itr) { + /* Get destination location. */ + const Station *dest = Station::Get(itr->first); + Point dest_pt = this->RemapTile(TileX(dest->xy), TileY(dest->xy)); + dest_pt.x -= this->subscroll; + + /* Get total count including back-links. */ + uint count = itr->second; + for (RouteLinkList::const_iterator j = dest->goods[cid].routes.begin(); j != dest->goods[cid].routes.end(); ++j) { + if ((*j)->GetDestination() == st->index && IsInteractiveCompany((*j)->GetOwner())) count += dest->goods[cid].cargo.CountForNextHop((*j)->GetOriginOrderId()); + } + + /* Calculate line size from waiting cargo. */ + int size = 1; + if (count >= 400) size++; + if (count >= 800) size++; + if (count >= 1600) size++; + if (count >= 3200) size++; + + /* Draw black border and cargo coloured line. */ + GfxDrawLine(src_pt.x, src_pt.y, dest_pt.x, dest_pt.y, PC_BLACK, size + 2); + GfxDrawLine(src_pt.x, src_pt.y, dest_pt.x, dest_pt.y, _legend_from_cargoes[i].colour, size); + } + } + } + } + + /* Draw station rect. */ + const Station *st; + FOR_ALL_STATIONS(st) { + /* Count total cargo and check for links for all shown cargo types. */ + uint total = 0; + bool show = false; + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (_legend_from_cargoes[_cargotype_to_list_pos[cid]].show_on_map) { + total += st->goods[cid].cargo.Count(); + show |= !st->goods[cid].routes.empty(); + } + } + + if (!show) continue; + + /* Get rect size from total cargo count. */ + int d = 1; + if (total >= 200) d++; + if (total >= 400) d++; + if (total >= 800) d++; + if (total >= 1600) d++; + if (total >= 3200) d++; + if (total >= 6400) d++; + + /* Get top-left corner of the rect. */ + Point dest_pt = this->RemapTile(TileX(st->xy), TileY(st->xy)); + dest_pt.x -= this->subscroll + d/2; + dest_pt.y -= d/2; + + /* Draw black border and company-colour inset. */ + byte colour = _colour_gradient[Company::IsValidID(st->owner) ? Company::Get(st->owner)->colour : (byte)COLOUR_GREY][6]; + GfxFillRect(dest_pt.x - 1, dest_pt.y - 1, dest_pt.x + d + 1, dest_pt.y + d + 1, PC_BLACK); // Draw black frame + GfxFillRect(dest_pt.x, dest_pt.y, dest_pt.x + d, dest_pt.y + d, colour); // Draw colour insert + } + } + + /** * Draws vertical part of map indicator * @param x X coord of left/right border of main viewport * @param y Y coord of top border of main viewport @@ -1013,6 +1143,9 @@ class SmallMapWindow : public Window { /* Draw vehicles */ if (this->map_type == SMT_CONTOUR || this->map_type == SMT_VEHICLES) this->DrawVehicles(dpi, blitter); + /* Draw route links. */ + if (this->map_type == SMT_ROUTE_LINKS) this->DrawRouteLinks(); + /* Draw town names */ if (this->show_towns) this->DrawTowns(dpi); @@ -1046,6 +1179,13 @@ class SmallMapWindow : public Window { plane = 0; break; + case SMT_ROUTE_LINKS: + legend_tooltip = STR_SMALLMAP_TOOLTIP_ROUTELINK_SELECTION; + enable_all_tooltip = STR_SMALLMAP_TOOLTIP_ENABLE_ALL_ROUTELINKS; + disable_all_tooltip = STR_SMALLMAP_TOOLTIP_DISABLE_ALL_ROUTELINKS; + plane = 0; + break; + default: legend_tooltip = STR_NULL; enable_all_tooltip = STR_NULL; @@ -1145,6 +1285,9 @@ public: } else { str = tbl->legend; } + } else if (i == SMT_ROUTE_LINKS) { + SetDParam(0, tbl->legend); + str = STR_SMALLMAP_CARGO; } else { if (tbl->col_break) { this->min_number_of_fixed_rows = max(this->min_number_of_fixed_rows, height); @@ -1192,7 +1335,7 @@ public: case SM_WIDGET_LEGEND: { uint columns = this->GetNumberColumnsLegend(r.right - r.left + 1); - uint number_of_rows = max((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) ? CeilDiv(max(_smallmap_company_count, _smallmap_industry_count), columns) : 0, this->min_number_of_fixed_rows); + uint number_of_rows = max((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) ? CeilDiv(max(_smallmap_company_count, max(_smallmap_industry_count, _smallmap_cargo_count)), columns) : 0, this->min_number_of_fixed_rows); bool rtl = _current_text_dir == TD_RTL; uint y_org = r.top + WD_FRAMERECT_TOP; uint x = rtl ? r.right - this->column_width - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT; @@ -1206,7 +1349,7 @@ public: uint blob_right = rtl ? this->column_width - 1 : LEGEND_BLOB_WIDTH; for (const LegendAndColour *tbl = _legend_table[this->map_type]; !tbl->end; ++tbl) { - if (tbl->col_break || ((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) && i++ >= number_of_rows)) { + if (tbl->col_break || ((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) && i++ >= number_of_rows)) { /* Column break needed, continue at top, COLUMN_WIDTH pixels * (one "row") to the right. */ x += rtl ? -(int)this->column_width : this->column_width; @@ -1227,6 +1370,16 @@ public: DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_INDUSTRY, TC_BLACK); GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, PC_BLACK); // Outer border of the legend colour } + } else if (this->map_type == SMT_ROUTE_LINKS) { + /* Cargo name needs formatting for tiny font. */ + SetDParam(0, tbl->legend); + if (!tbl->show_on_map) { + /* Draw only the string and not the border of the legend colour. */ + DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_CARGO, TC_GREY); + } else { + DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_CARGO, TC_BLACK); + GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, PC_BLACK); // Outer border of the legend colour + } } else if (this->map_type == SMT_OWNER && tbl->company != INVALID_COMPANY) { SetDParam(0, tbl->company); if (!tbl->show_on_map) { @@ -1311,6 +1464,7 @@ public: case SM_WIDGET_CONTOUR: // Show land contours case SM_WIDGET_VEHICLES: // Show vehicles case SM_WIDGET_INDUSTRIES: // Show industries + case SM_WIDGET_ROUTE_LINKS:// Show route links case SM_WIDGET_ROUTES: // Show transport routes case SM_WIDGET_VEGETATION: // Show vegetation case SM_WIDGET_OWNERS: // Show land owners @@ -1333,11 +1487,11 @@ public: break; case SM_WIDGET_LEGEND: // Legend - if (this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) { + if (this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) { const NWidgetBase *wi = this->GetWidget(SM_WIDGET_LEGEND); // Label panel uint line = (pt.y - wi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_SMALL; uint columns = this->GetNumberColumnsLegend(wi->current_x); - uint number_of_rows = max(CeilDiv(max(_smallmap_company_count, _smallmap_industry_count), columns), this->min_number_of_fixed_rows); + uint number_of_rows = max(CeilDiv(max(_smallmap_company_count, max(_smallmap_industry_count, _smallmap_cargo_count)), columns), this->min_number_of_fixed_rows); if (line >= number_of_rows) break; bool rtl = _current_text_dir == TD_RTL; @@ -1395,6 +1549,30 @@ public: _legend_land_owners[company_pos].show_on_map = !_legend_land_owners[company_pos].show_on_map; } } + } else if (this->map_type == SMT_ROUTE_LINKS) { + /* If click on cargo label, find right cargo type and enable/disable it. */ + int cargo_pos = (column * number_of_rows) + line; + if (cargo_pos < _smallmap_cargo_count) { + if (_ctrl_pressed) { + /* Disable all, except the clicked one */ + bool changes = false; + for (int i = 0; i != _smallmap_cargo_count; i++) { + bool new_state = i == cargo_pos; + if (_legend_from_cargoes[i].show_on_map != new_state) { + changes = true; + _legend_from_cargoes[i].show_on_map = new_state; + } + } + if (!changes) { + /* Nothing changed? Then show all (again). */ + for (int i = 0; i != _smallmap_cargo_count; i++) { + _legend_from_cargoes[i].show_on_map = true; + } + } + } else { + _legend_from_cargoes[cargo_pos].show_on_map = !_legend_from_cargoes[cargo_pos].show_on_map; + } + } } this->SetDirty(); } @@ -1409,6 +1587,10 @@ public: for (int i = NUM_NO_COMPANY_ENTRIES; i != _smallmap_company_count; i++) { _legend_land_owners[i].show_on_map = true; } + } else if (this->map_type == SMT_ROUTE_LINKS) { + for (int i = 0; i != _smallmap_cargo_count; i++) { + _legend_from_cargoes[i].show_on_map = true; + } } this->SetDirty(); break; @@ -1418,10 +1600,14 @@ public: for (int i = 0; i != _smallmap_industry_count; i++) { _legend_from_industries[i].show_on_map = false; } - } else { + } else if (this->map_type == SMT_OWNER) { for (int i = NUM_NO_COMPANY_ENTRIES; i != _smallmap_company_count; i++) { _legend_land_owners[i].show_on_map = false; } + } else if (this->map_type == SMT_ROUTE_LINKS) { + for (int i = 0; i != _smallmap_cargo_count; i++) { + _legend_from_cargoes[i].show_on_map = false; + } } this->SetDirty(); break; @@ -1657,6 +1843,8 @@ static const NWidgetPart _nested_smallmap_bar[] = { SetDataTip(SPR_IMG_SHOW_VEHICLES, STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP), SetFill(1, 1), NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_INDUSTRIES), SetDataTip(SPR_IMG_INDUSTRY, STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP), SetFill(1, 1), + NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_ROUTE_LINKS), + SetDataTip(SPR_IMG_SHOW_ROUTES, STR_SMALLMAP_TOOLTIP_SHOW_ROUTE_LINKS_ON_MAP), SetFill(1, 1), EndContainer(), /* Bottom button row. */ NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), @@ -1670,6 +1858,7 @@ static const NWidgetPart _nested_smallmap_bar[] = { SetDataTip(SPR_IMG_PLANTTREES, STR_SMALLMAP_TOOLTIP_SHOW_VEGETATION_ON_MAP), SetFill(1, 1), NWidget(WWT_IMGBTN, COLOUR_BROWN, SM_WIDGET_OWNERS), SetDataTip(SPR_IMG_COMPANY_GENERAL, STR_SMALLMAP_TOOLTIP_SHOW_LAND_OWNERS_ON_MAP), SetFill(1, 1), + NWidget(NWID_SPACER), SetFill(1, 1), EndContainer(), NWidget(NWID_SPACER), SetResize(0, 1), EndContainer(), diff --git a/src/smallmap_gui.h b/src/smallmap_gui.h index 572a175..1cb23e9 100644 --- a/src/smallmap_gui.h +++ b/src/smallmap_gui.h @@ -13,6 +13,7 @@ #define SMALLMAP_GUI_H void BuildIndustriesLegend(); +void BuildCargoTypesLegend(); void ShowSmallMap(); void BuildLandLegend(); void BuildOwnerLegend(); diff --git a/src/station.cpp b/src/station.cpp index 0f1db37..4670c56 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -25,6 +25,7 @@ #include "roadstop_base.h" #include "industry.h" #include "core/random_func.hpp" +#include "cargodest_func.h" #include "table/strings.h" @@ -94,6 +95,9 @@ Station::~Station() if (v->last_station_visited == this->index) { v->last_station_visited = INVALID_STATION; } + if (v->last_station_loaded == this->index) { + v->last_station_loaded = INVALID_STATION; + } } InvalidateWindowData(WC_STATION_LIST, this->owner, 0); @@ -111,6 +115,7 @@ Station::~Station() } CargoPacket::InvalidateAllFrom(this->index); + InvalidateStationRouteLinks(this); } @@ -378,6 +383,23 @@ bool StationRect::PtInExtendedRect(int x, int y, int distance) const this->top - distance <= y && y <= this->bottom + distance; } +/** + * Determines whether a tile area intersects the station rectangle with a given offset. + * @param area The tile area to test. + * @param distance Offset the station rect is grown on all sides (L1 norm). + * @return True if the tile area intersects with the station rectangle. + */ +bool StationRect::AreaInExtendedRect(const TileArea& area, int distance) const +{ + int area_left = TileX(area.tile); + int area_right = area_left + area.w; + int area_top = TileY(area.tile); + int area_bottom = area_top + area.h; + + return this->left - distance <= area_right && area_left <= this->right + distance && + this->top - distance <= area_bottom && area_top <= this->bottom + distance; +} + bool StationRect::IsEmpty() const { return this->left == 0 || this->left > this->right || this->top > this->bottom; diff --git a/src/station_base.h b/src/station_base.h index 7d64664..373a956 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -17,12 +17,16 @@ #include "cargopacket.h" #include "industry_type.h" #include "newgrf_storage.h" +#include "cargodest_type.h" typedef Pool StationPool; extern StationPool _station_pool; static const byte INITIAL_STATION_RATING = 175; +/** List of RouteLinks. */ +typedef std::list RouteLinkList; + struct GoodsEntry { enum AcceptancePickup { ACCEPTANCE, @@ -34,7 +38,8 @@ struct GoodsEntry { days_since_pickup(255), rating(INITIAL_STATION_RATING), last_speed(0), - last_age(255) + last_age(255), + cargo_counter(0) {} byte acceptance_pickup; @@ -43,7 +48,9 @@ struct GoodsEntry { byte last_speed; byte last_age; byte amount_fract; ///< Fractional part of the amount in the cargo list + uint16 cargo_counter; ///< Update timer for the packets' next hop StationCargoList cargo; ///< The cargo packets of cargo waiting in this station + RouteLinkList routes; ///< List of originating route links }; /** All airport-related information. Only valid if tile != INVALID_TILE. */ diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index d8a4a55..6a07d90 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -48,6 +48,7 @@ #include "table/airporttile_ids.h" #include "newgrf_airporttiles.h" #include "order_backup.h" +#include "cargodest_func.h" #include "table/strings.h" @@ -3144,6 +3145,17 @@ void OnTick_Station() TriggerStationAnimation(st, st->xy, SAT_250_TICKS); if (Station::IsExpected(st)) AirportAnimationTrigger(Station::From(st), AAT_STATION_250_TICKS); } + + if (Station::IsExpected(st)) { + /* Age and expire route links. */ + Station *s = Station::From(st); + if (s->index % DAY_TICKS == _date_fract) AgeRouteLinks(s); + + /* Decrement cargo update counter. */ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + if (s->goods[cid].cargo_counter > 0) s->goods[cid].cargo_counter--; + } + } } } @@ -3171,7 +3183,7 @@ void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint rad } } -static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id) +uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id, TileIndex dest_tile, SourceType dest_type, SourceID dest_id, OrderID next_hop, StationID next_unload, byte flags) { /* We can't allocate a CargoPacket? Then don't do anything * at all; i.e. just discard the incoming cargo. */ @@ -3185,7 +3197,7 @@ static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceT /* No new "real" cargo item yet. */ if (amount == 0) return 0; - ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id)); + ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id, dest_tile, dest_type, dest_id, next_hop, next_unload, flags)); if (!HasBit(ge.acceptance_pickup, GoodsEntry::PICKUP)) { InvalidateWindowData(WC_STATION_LIST, st->index); @@ -3291,11 +3303,14 @@ const StationList *StationFinder::GetStations() return &this->stations; } -uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations) +uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile) { /* Return if nothing to do. Also the rounding below fails for 0. */ if (amount == 0) return 0; + /* Handle cargo that has cargo destinations enabled. */ + if (MoveCargoWithDestinationToStation(type, &amount, source_type, source_id, all_stations, src_tile)) return amount; + Station *st1 = NULL; // Station with best rating Station *st2 = NULL; // Second best station uint best_rating1 = 0; // rating of st1 diff --git a/src/station_func.h b/src/station_func.h index 0fe5b2f..66dd78c 100644 --- a/src/station_func.h +++ b/src/station_func.h @@ -18,6 +18,7 @@ #include "road_type.h" #include "cargo_type.h" #include "company_type.h" +#include "order_type.h" void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint radius); @@ -30,6 +31,7 @@ CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad); CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, uint32 *always_accepted = NULL); void UpdateStationAcceptance(Station *st, bool show_msg); +uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id, TileIndex dest_tile = INVALID_TILE, SourceType dest_type = ST_INDUSTRY, SourceID dest_id = INVALID_SOURCE, OrderID next_hop = INVALID_ORDER, StationID next_unload = INVALID_STATION, byte flags = 0); const DrawTileSprites *GetStationTileLayout(StationType st, byte gfx); void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image); diff --git a/src/station_gui.cpp b/src/station_gui.cpp index b2f5a6e..fd7b675 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -29,6 +29,9 @@ #include "sortlist_type.h" #include "core/geometry_func.hpp" #include "vehiclelist.h" +#include "cargodest_base.h" +#include "industry.h" +#include "town.h" #include "table/strings.h" @@ -829,6 +832,8 @@ static const NWidgetPart _nested_station_view_widgets[] = { SetDataTip(STR_BUTTON_LOCATION, STR_STATION_VIEW_CENTER_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_ACCEPTS), SetMinimalSize(61, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_STATION_VIEW_RATINGS_BUTTON, STR_STATION_VIEW_RATINGS_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_CARGO_VIA), SetMinimalSize(60, 12), SetResize(1, 0), SetFill(1, 1), + SetDataTip(STR_STATION_VIEW_WAITING_VIA_BUTTON, STR_STATION_VIEW_WAITING_VIA_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_RENAME), SetMinimalSize(60, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BUTTON_RENAME, STR_STATION_VIEW_RENAME_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SVW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP), @@ -865,18 +870,145 @@ static void DrawCargoIcons(CargoID i, uint waiting, int left, int right, int y) struct CargoData { CargoID cargo; - StationID source; + union { + StationID station; + SourceID css; + }; uint count; + SourceType type; - CargoData(CargoID cargo, StationID source, uint count) : + CargoData(CargoID cargo, StationID station, uint count, SourceType type = ST_INDUSTRY) : cargo(cargo), - source(source), - count(count) + station(station), + count(count), + type(type) { } }; typedef std::list CargoDataList; +/** List of cargo for either one next hop or one destination. */ +struct CargoDestEntry { + typedef std::list List; + + /** Enum for type of stored data. */ + enum Type { + FINAL_DEST, ///< Data is the final destination. + NEXT_HOP, ///< Data is the next hop. + TRANSFER_HOP ///< Data is the transfer station. + }; + + List children; ///< Child entries of this entry. + CargoData data; ///< Stores the info for the current item. + Type type; ///< Type of the data stored in #entry. + uint16 start_row; ///< Row number of the header line. + bool expanded; ///< Is this entry expanded? + + CargoDestEntry(Type type, StationID station, uint count, SourceType st = ST_INDUSTRY) : + data(INVALID_CARGO, station, count, st), + type(type), + start_row(0), + expanded(false) + { } + + /** Zero out this entry and all child entries. */ + void Zero() + { + for (List::iterator i = this->children.begin(); i != this->children.end(); ++i ) { + i->Zero(); + } + this->data.count = 0; + this->start_row = 0; + } + + /** Remove all empty child entries. */ + void RemoveEmpty() + { + for (List::iterator i = this->children.begin(); i != this->children.end(); ) { + if (i->data.count > 0) { + i->RemoveEmpty(); + ++i; + } else { + i = this->children.erase(i); + } + } + } + + /** Update header row number. */ + int UpdateRowCount(int row) + { + this->start_row = ++row; + if (this->expanded) { + for (List::iterator i = this->children.begin(); i != this->children.end(); ++i) { + row = i->UpdateRowCount(row); + } + } + return row; + } +}; + +/** + * Get the next hop of a cargo packet. + * @param ge Station cargo info for the matching cargo type. + * @param cp The cargo packet. + * @return Station ID of the next hop or INVALID_STATION if not possible. + */ +static StationID GetNextHopStation(const GoodsEntry &ge, const CargoPacket *cp) +{ + StationID next = INVALID_STATION; + for (RouteLinkList::const_iterator i = ge.routes.begin(); i != ge.routes.end(); ++i) { + if ((*i)->GetOriginOrderId() == cp->NextHop()) { + next = (*i)->GetDestination(); + break; + } + } + return next; +} + +/** + * Add a cargo packet to a #CargoDestEntry list. + * @param list The list to add the packet to. + * @param type Which value to select as the entry info. + * @param cp The cargo packet. + * @param ge Where this cargo packets belongs to. + * @return Pointer to the added entry or NULL if the packet had no valid destination of the specified type. + */ +static CargoDestEntry *AddCargoPacketToList(CargoDestEntry::List &list, CargoDestEntry::Type type, const CargoPacket *cp, const GoodsEntry &ge) +{ + assert_compile(INVALID_STATION == INVALID_SOURCE); + + /* Extract the wanted sort type from the cargo packet. */ + uint16 sort_val; + switch (type) { + case CargoDestEntry::FINAL_DEST: + sort_val = cp->DestinationID(); + break; + case CargoDestEntry::NEXT_HOP: + sort_val = GetNextHopStation(ge, cp); + break; + case CargoDestEntry::TRANSFER_HOP: + sort_val = cp->NextStation(); + break; + default: + NOT_REACHED(); + } + + if (sort_val == INVALID_STATION) return NULL; + + /* Search for a matching child. */ + for (CargoDestEntry::List::iterator i = list.begin(); i != list.end(); ++i) { + if (type == CargoDestEntry::FINAL_DEST ? i->data.css == sort_val && i->data.type == cp->DestinationType() : i->data.station == sort_val) { + i->data.count += cp->Count(); + return &*i; + } + } + + /* No entry found, add new. */ + list.push_back(CargoDestEntry(type, sort_val, cp->Count(), cp->DestinationType())); + return &list.back(); +} + + /** * The StationView window */ @@ -887,6 +1019,10 @@ struct StationViewWindow : public Window { int rating_lines; ///< Number of lines in the cargo ratings view. int accepts_lines; ///< Number of lines in the accepted cargo view. Scrollbar *vscroll; + CargoDestEntry::List cargodest_list[NUM_CARGO]; ///< List of cargoes sorted by destination. + + static StringID last_cargo_from_str; + static StringID last_cargo_from_tooltip; /** Height of the #SVW_ACCEPTLIST widget for different views. */ enum AcceptListHeight { @@ -901,6 +1037,7 @@ struct StationViewWindow : public Window { this->CreateNestedTree(desc); this->vscroll = this->GetScrollbar(SVW_SCROLLBAR); + this->GetWidget(SVW_CARGO_FROM)->SetDataTip(StationViewWindow::last_cargo_from_str, StationViewWindow::last_cargo_from_tooltip); /* Nested widget tree creation is done in two steps to ensure that this->GetWidget(SVW_ACCEPTS) exists in UpdateWidgetSize(). */ this->FinishInitNested(desc, window_number); @@ -938,9 +1075,30 @@ struct StationViewWindow : public Window { { CargoDataList cargolist; uint32 transfers = 0; - this->OrderWaitingCargo(&cargolist, &transfers); - this->vscroll->SetCount((int)cargolist.size() + 1); // update scrollbar + NWidgetCore *cargo_btn = this->GetWidget(SVW_CARGO_FROM); + if (cargo_btn->widget_data == STR_STATION_VIEW_WAITING_TO_BUTTON) { + this->OrderWaitingCargo(&cargolist, &transfers); + this->vscroll->SetCount((int)cargolist.size() + 1); // update scrollbar + } else { + /* Determine the current view. */ + CargoDestEntry::Type dest_type; + switch (cargo_btn->widget_data) { + case STR_STATION_VIEW_WAITING_VIA_BUTTON: + dest_type = CargoDestEntry::FINAL_DEST; + break; + case STR_STATION_VIEW_WAITING_TRANSFER_BUTTON: + dest_type = CargoDestEntry::NEXT_HOP; + break; + case STR_STATION_VIEW_WAITING_BUTTON: + dest_type = CargoDestEntry::TRANSFER_HOP; + break; + default: + NOT_REACHED(); + } + int num = this->FillCargodestList(dest_type, this->cargodest_list); + this->vscroll->SetCount(num + 1); // update scrollbar + } /* disable some buttons */ const Station *st = Station::Get(this->window_number); @@ -975,7 +1133,11 @@ struct StationViewWindow : public Window { /* Draw waiting cargo. */ NWidgetBase *nwi = this->GetWidget(SVW_WAITING); Rect waiting_rect = {nwi->pos_x, nwi->pos_y, nwi->pos_x + nwi->current_x - 1, nwi->pos_y + nwi->current_y - 1}; - this->DrawWaitingCargo(waiting_rect, cargolist, transfers); + if (cargo_btn->widget_data == STR_STATION_VIEW_WAITING_TO_BUTTON) { + this->DrawWaitingCargo(waiting_rect, cargolist, transfers); + } else { + this->DrawWaitingCargoByDest(waiting_rect, this->cargodest_list); + } } } @@ -1029,7 +1191,7 @@ struct StationViewWindow : public Window { /* Check if we already have this source in the list */ for (CargoDataList::iterator jt(cargolist->begin()); jt != cargolist->end(); jt++) { CargoData *cd = &(*jt); - if (cd->cargo == i && cd->source == cp->SourceStation()) { + if (cd->cargo == i && cd->station == cp->SourceStation()) { cd->count += cp->Count(); added = true; break; @@ -1044,6 +1206,70 @@ struct StationViewWindow : public Window { } /** + * Fill cargo list sorted by type and destination/next hop. + * @param sort_via Set to true to sort by next hop, false to sort by final destination. + * @param list Cargo list to fill. + * @return Number of visible lines. + */ + int FillCargodestList(CargoDestEntry::Type sort_by, CargoDestEntry::List *list) + { + StationID station_id = this->window_number; + const Station *st = Station::Get(station_id); + + int lines = 0; + + /* Fill the list for each cargo type. */ + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + /* Zero out all existing items. */ + for (CargoDestEntry::List::iterator i = list[cid].begin(); i != list[cid].end(); ++i) { + i->Zero(); + } + + /* Remove all entries if no cargo of this type is present. */ + if (st->goods[cid].cargo.Empty()) { + this->cargo_rows[cid] = 0; + list[cid].clear(); + continue; + } + + /* Store line number of the header line. */ + this->cargo_rows[cid] = ++lines; + + /* Add each cargo packet to the list. */ + const StationCargoList::List *packets = st->goods[cid].cargo.Packets(); + for (StationCargoList::ConstIterator it = packets->begin(); it != packets->end(); ++it) { + const CargoPacket *cp = *it; + + /* Add entry and sub-entries according to the chosen sort type. */ + static const CargoDestEntry::Type sort_types[][3] = { + {CargoDestEntry::FINAL_DEST, CargoDestEntry::NEXT_HOP, CargoDestEntry::TRANSFER_HOP}, + {CargoDestEntry::NEXT_HOP, CargoDestEntry::TRANSFER_HOP, CargoDestEntry::FINAL_DEST}, + {CargoDestEntry::TRANSFER_HOP, CargoDestEntry::NEXT_HOP, CargoDestEntry::FINAL_DEST} + }; + + CargoDestEntry *entry = AddCargoPacketToList(list[cid], sort_types[sort_by][0], cp, st->goods[cid]); + if (entry != NULL) { + entry = AddCargoPacketToList(entry->children, sort_types[sort_by][1], cp, st->goods[cid]); + if (entry != NULL) AddCargoPacketToList(entry->children, sort_types[sort_by][2], cp, st->goods[cid]); + } + } + + /* Remove all empty list items and update visible row numbers. */ + for (CargoDestEntry::List::iterator i = list[cid].begin(); i != list[cid].end(); ) { + if (i->data.count > 0) { + i->RemoveEmpty(); + if (HasBit(this->cargo, cid)) lines = i->UpdateRowCount(lines); + ++i; + } else { + i = list[cid].erase(i); + } + } + } + + return lines; + } + + /** * Draw waiting cargo. * @param r Rectangle of the widget. * @param cargolist Cargo, ordered by type and destination. @@ -1076,7 +1302,7 @@ struct StationViewWindow : public Window { for (CargoDataList::const_iterator it = cargolist.begin(); it != cargolist.end() && pos > -maxrows; ++it) { if (--pos < 0) { const CargoData *cd = &(*it); - if (cd->source == INVALID_STATION) { + if (cd->station == INVALID_STATION) { /* Heading */ DrawCargoIcons(cd->cargo, cd->count, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y); SetDParam(0, cd->cargo); @@ -1092,7 +1318,7 @@ struct StationViewWindow : public Window { } else { SetDParam(0, cd->cargo); SetDParam(1, cd->count); - SetDParam(2, cd->source); + SetDParam(2, cd->station); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_EN_ROUTE_FROM, TC_FROMSTRING, SA_RIGHT); } @@ -1102,6 +1328,111 @@ struct StationViewWindow : public Window { } /** + * Draw a dest entry and its children. + * @param cid Current cargo type. + * @param pos Scroll position + * @param maxrows Number of visible rows. + * @param left Left string bound. + * @param right Right string bound. + * @param shrink_left Left bound of the expand marker. + * @param shrink_right Right bound of the expand marker. + * @param offs_left Child offset of the left bound. + * @param offs_right Child offset of the right bound. + * @param y Top of the current line. + * @param entry The entry to draw. + * @return The new y value. + */ + int DrawSingleDestEntry(CargoID cid, int *pos, int maxrows, int left, int right, int shrink_left, int shrink_right, int offs_left, int offs_right, int y, const CargoDestEntry &entry) const + { + if (--(*pos) < 0) { + /* Draw current line. */ + StringID str; + + SetDParam(0, cid); + SetDParam(1, entry.data.count); + if (entry.type == CargoDestEntry::FINAL_DEST) { + SetDParam(2, entry.data.type == ST_INDUSTRY ? STR_INDUSTRY_NAME : (entry.data.type == ST_TOWN ? STR_TOWN_NAME : STR_COMPANY_NAME)); + SetDParam(3, entry.data.css); + str = STR_STATION_VIEW_WAITING_TO; + } else { + SetDParam(2, entry.data.station); + str = (entry.type == CargoDestEntry::NEXT_HOP) ? STR_STATION_VIEW_WAITING_VIA : STR_STATION_VIEW_WAITING_TRANSFER; + } + DrawString(left, right, y, str); + y += FONT_HEIGHT_NORMAL; + + if (!entry.children.empty()) { + /* Draw expand/collapse marker. */ + DrawString(shrink_left, shrink_right, y - FONT_HEIGHT_NORMAL, entry.expanded ? "-" : "+", TC_YELLOW, SA_RIGHT); + + if (entry.expanded) { + /* Draw visible children. */ + for (CargoDestEntry::List::const_iterator i = entry.children.begin(); i != entry.children.end() && *pos > -maxrows; ++i) { + y = this->DrawSingleDestEntry(cid, pos, maxrows, left + offs_left, right + offs_right, shrink_left, shrink_right, offs_left, offs_right, y, *i); + } + } + } + } + + return y; + } + + /** + * Draw waiting cargo ordered by destination/next hop. + * @param r Rectangle of the widget. + * @param list List to draw. + */ + void DrawWaitingCargoByDest(const Rect &r, const CargoDestEntry::List *list) const + { + int y = r.top + WD_FRAMERECT_TOP; + int pos = this->vscroll->GetPosition(); + + const Station *st = Station::Get(this->window_number); + if (--pos < 0) { + StringID str = STR_JUST_NOTHING; + for (CargoID i = 0; i < NUM_CARGO; i++) { + if (!st->goods[i].cargo.Empty()) str = STR_EMPTY; + } + SetDParam(0, str); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_WAITING_TITLE); + y += FONT_HEIGHT_NORMAL; + } + + bool rtl = _current_text_dir == TD_RTL; + int text_left = rtl ? r.left + this->expand_shrink_width : r.left + WD_FRAMERECT_LEFT; + int text_right = rtl ? r.right - WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width; + int shrink_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width + WD_FRAMERECT_LEFT; + int shrink_right = rtl ? r.left + this->expand_shrink_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT; + + int offs_left = rtl ? 0 : this->expand_shrink_width; + int offs_right = rtl ? this->expand_shrink_width : 0; + + int maxrows = this->vscroll->GetCapacity(); + for (CargoID cid = 0; cid < NUM_CARGO && pos > -maxrows; cid++) { + if (st->goods[cid].cargo.Empty()) continue; + + if (--pos < 0) { + /* Draw heading. */ + DrawCargoIcons(cid, st->goods[cid].cargo.Count(), r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMERECT_RIGHT, y); + SetDParam(0, cid); + SetDParam(1, st->goods[cid].cargo.Count()); + DrawString(text_left, text_right, y, STR_STATION_VIEW_WAITING_CARGO, TC_FROMSTRING, SA_RIGHT); + if (!list[cid].empty()) { + DrawString(shrink_left, shrink_right, y, HasBit(this->cargo, cid) ? "-" : "+", TC_YELLOW, SA_RIGHT); + } + y += FONT_HEIGHT_NORMAL; + } + + /* Draw sub-entries. */ + if (HasBit(this->cargo, cid)) { + for (CargoDestEntry::List::const_iterator i = list[cid].begin(); i != list[cid].end() && pos > -maxrows; ++i) { + y = this->DrawSingleDestEntry(cid, &pos, maxrows, text_left + offs_left, text_right + offs_right, shrink_left, shrink_right, offs_left, offs_right, y, *i); + } + } + } + } + + /** * Draw accepted cargo in the #SVW_ACCEPTLIST widget. * @param r Rectangle of the widget. * @return Number of lines needed for drawing the accepted cargo. @@ -1146,16 +1477,93 @@ struct StationViewWindow : public Window { return CeilDiv(y - r.top - WD_FRAMERECT_TOP, FONT_HEIGHT_NORMAL); } + /** + * Test and handle a possible mouse click on a dest entry and its children. + * @param entry The entry to test for a hit. + * @param row The number of the clicked row. + * @return True if further entries need to be processed. + */ + bool HandleCargoDestEntryClick(CargoDestEntry &entry, int row) + { + if (entry.start_row == row) { + if (_ctrl_pressed) { + /* Scroll viewport to destination tile .*/ + TileIndex dest_tile = 0; + switch (entry.type) { + case CargoDestEntry::FINAL_DEST: + switch (entry.data.type) { + case ST_INDUSTRY: + dest_tile = Industry::Get(entry.data.css)->location.tile; + break; + case ST_TOWN: + dest_tile = Town::Get(entry.data.css)->xy; + break; + case ST_HEADQUARTERS: + dest_tile = Company::Get(entry.data.css)->location_of_HQ; + break; + + default: + NOT_REACHED(); + } + break; + + case CargoDestEntry::NEXT_HOP: + case CargoDestEntry::TRANSFER_HOP: + dest_tile = Station::Get(entry.data.station)->xy; + break; + + default: + NOT_REACHED(); + } + ScrollMainWindowToTile(dest_tile); + } else if (!entry.children.empty()) { + /* Expand/collapse entry. */ + entry.expanded = !entry.expanded; + this->SetWidgetDirty(SVW_WAITING); + this->SetWidgetDirty(SVW_SCROLLBAR); + } + } + + if (entry.start_row < row) { + /* Test child entries. */ + for (CargoDestEntry::List::iterator i = entry.children.begin(); i != entry.children.end(); ++i) { + if (!this->HandleCargoDestEntryClick(*i, row)) return false; + } + return true; + } + + return false; + } + void HandleCargoWaitingClick(int row) { if (row == 0) return; + bool dest_view = this->GetWidget(SVW_CARGO_FROM)->widget_data != STR_STATION_VIEW_WAITING_TO_BUTTON; + for (CargoID c = 0; c < NUM_CARGO; c++) { + /* Test for cargo type line. */ if (this->cargo_rows[c] == row) { ToggleBit(this->cargo, c); this->SetWidgetDirty(SVW_WAITING); + this->SetWidgetDirty(SVW_SCROLLBAR); break; } + + if (dest_view) { + /* Test for dest view lines. */ + for (CargoDestEntry::List::iterator i = this->cargodest_list[c].begin(); i != this->cargodest_list[c].end(); ++i) { + if (!this->HandleCargoDestEntryClick(*i, row)) break; + } + } + } + } + + /** Clear the 'cargo by destination' list. */ + void ClearCargodestList() + { + for (CargoID cid = 0; cid < NUM_CARGO; cid++) { + this->cargodest_list[cid].clear(); } } @@ -1189,6 +1597,38 @@ struct StationViewWindow : public Window { break; } + case SVW_CARGO_FROM: { + /* Swap between 'Source', 'Destination', 'Next hop' and 'Transfer' view. + * Store the new view so the next opened station window shows the same view. */ + NWidgetCore *nwi = this->GetWidget(SVW_CARGO_FROM); + switch (nwi->widget_data) { + case STR_STATION_VIEW_WAITING_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_TO_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TO_TOOLTIP; + break; + case STR_STATION_VIEW_WAITING_TO_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_VIA_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_VIA_TOOLTIP; + break; + case STR_STATION_VIEW_WAITING_VIA_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_TRANSFER_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TRANSFER_TOOLTIP; + break; + case STR_STATION_VIEW_WAITING_TRANSFER_BUTTON: + StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_BUTTON; + StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TOOLTIP; + break; + default: + NOT_REACHED(); + } + nwi->SetDataTip(StationViewWindow::last_cargo_from_str, StationViewWindow::last_cargo_from_tooltip); + this->ClearCargodestList(); + this->SetWidgetDirty(SVW_CARGO_FROM); + this->SetWidgetDirty(SVW_WAITING); + this->SetWidgetDirty(SVW_SCROLLBAR); + break; + } + case SVW_RENAME: SetDParam(0, this->window_number); ShowQueryString(STR_STATION_NAME, STR_STATION_VIEW_RENAME_STATION_CAPTION, MAX_LENGTH_STATION_NAME_CHARS, @@ -1217,6 +1657,8 @@ struct StationViewWindow : public Window { } }; +StringID StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_VIA_BUTTON; +StringID StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_VIA_TOOLTIP; static const WindowDesc _station_view_desc( WDP_AUTO, 249, 110, diff --git a/src/station_gui.h b/src/station_gui.h index dd442dc..f19ae60 100644 --- a/src/station_gui.h +++ b/src/station_gui.h @@ -26,8 +26,11 @@ enum StationViewWidgets { SVW_LOCATION = 4, ///< 'Location' button SVW_RATINGS = 5, ///< 'Ratings' button SVW_ACCEPTS = 5, ///< 'Accepts' button - SVW_RENAME = 6, ///< 'Rename' button - SVW_TRAINS = 7, ///< List of scheduled trains button + SVW_CARGO_FROM = 6, ///< 'Source' button + SVW_CARGO_TO = 6, ///< 'Destination' button + SVW_CARGO_VIA = 6, ///< 'Next hop' button + SVW_RENAME = 7, ///< 'Rename' button + SVW_TRAINS = 8, ///< List of scheduled trains button SVW_ROADVEHS, ///< List of scheduled road vehs button SVW_SHIPS, ///< List of scheduled ships button SVW_PLANES, ///< List of scheduled planes button diff --git a/src/strings.cpp b/src/strings.cpp index 1be6855..63362d4 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -1570,6 +1570,7 @@ bool ReadLanguagePack(const LanguageMetadata *lang) /* Some lists need to be sorted again after a language change. */ InitializeSortedCargoSpecs(); + BuildCargoTypesLegend(); SortIndustryTypes(); BuildIndustriesLegend(); SortNetworkLanguages(); diff --git a/src/subsidy.cpp b/src/subsidy.cpp index d4e7012..9f7331a 100644 --- a/src/subsidy.cpp +++ b/src/subsidy.cpp @@ -23,6 +23,7 @@ #include "subsidy_func.h" #include "core/pool_func.hpp" #include "core/random_func.hpp" +#include "cargodest_func.h" #include "table/strings.h" @@ -168,13 +169,22 @@ static Subsidy *FindSubsidyPassengerRoute() { assert(Subsidy::CanAllocateItem()); - const Town *src = Town::GetRandom(); + Town *src = Town::GetRandom(); if (src->population < SUBSIDY_PAX_MIN_POPULATION || src->pct_pass_transported > SUBSIDY_MAX_PCT_TRANSPORTED) { return NULL; } - const Town *dst = Town::GetRandom(); + const Town *dst = NULL; + if (CargoHasDestinations(CT_PASSENGERS)) { + /* Try to get a town from the demand destinations. */ + CargoLink *link = src->GetRandomLink(CT_PASSENGERS, false); + if (link == src->cargo_links[CT_PASSENGERS].End()) return NULL; + if (link->dest != NULL && link->dest->GetType() != ST_TOWN) return NULL; + dst = static_cast(link->dest); + } + if (dst == NULL) dst = Town::GetRandom(); + if (dst->population < SUBSIDY_PAX_MIN_POPULATION || src == dst) { return NULL; } @@ -195,7 +205,7 @@ static Subsidy *FindSubsidyCargoRoute() { assert(Subsidy::CanAllocateItem()); - const Industry *i = Industry::GetRandom(); + Industry *i = Industry::GetRandom(); if (i == NULL) return NULL; CargoID cargo; @@ -224,9 +234,17 @@ static Subsidy *FindSubsidyCargoRoute() SourceID dst; if (cs->town_effect == TE_GOODS || cs->town_effect == TE_FOOD) { - /* The destination is a town */ + /* The destination is a town */ dst_type = ST_TOWN; - const Town *t = Town::GetRandom(); + const Town *t = NULL; + if (CargoHasDestinations(cargo)) { + /* Try to get a town from the demand destinations. */ + CargoLink *link = i->GetRandomLink(cargo, false); + if (link == i->cargo_links[cargo].End()) return NULL; + if (link->dest != NULL && link->dest->GetType() != dst_type) return NULL; + t = static_cast(link->dest); + } + if (t == NULL) t = Town::GetRandom(); /* Only want big towns */ if (t->population < SUBSIDY_CARGO_MIN_POPULATION) return NULL; @@ -237,7 +255,16 @@ static Subsidy *FindSubsidyCargoRoute() } else { /* The destination is an industry */ dst_type = ST_INDUSTRY; - const Industry *i2 = Industry::GetRandom(); + const Industry *i2 = NULL; + if (CargoHasDestinations(cargo)) { + /* Try to get a town from the demand destinations. */ + CargoLink *link = i->GetRandomLink(cargo, false); + if (link == i->cargo_links[cargo].End()) return NULL; + if (link->dest != NULL && link->dest->GetType() != dst_type) return NULL; + i2 = static_cast(link->dest); + } + if (i2 == NULL) i2 = Industry::GetRandom(); + /* The industry must accept the cargo */ if (i2 == NULL || i == i2 || diff --git a/src/table/settings.ini b/src/table/settings.ini index c5fe20e..749ec3e 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -39,6 +39,7 @@ static bool InvalidateNewGRFChangeWindows(int32 p1); static bool InvalidateIndustryViewWindow(int32 p1); static bool RedrawTownAuthority(int32 p1); extern bool UpdateNewGRFConfigPalette(int32 p1); +bool CargodestModeChanged(int32 p1); #ifdef ENABLE_NETWORK static bool UpdateClientName(int32 p1); @@ -69,6 +70,7 @@ SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, SDTC_STR = SDTC_STR( $var, $type, $flags, $guiflags, $def, $str, $strval, $proc, $from, $to), SDTC_VAR = SDTC_VAR( $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strval, $proc, $from, $to), SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strval, $proc, $from, $to), +SDT_LIST = SDT_LIST($base, $var, $type, $flags, $guiflags, "$def", $str, $strval, $proc, $from, $to), SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strval, $proc, $from, $to, $load), SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strval, $proc, $from, $to), SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strval, $proc, $from, $to), @@ -1117,6 +1119,215 @@ from = 77 def = true str = STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD +[SDT_VAR] +base = GameSettings +var = economy.cargodest.mode_pax_mail +type = SLE_UINT8 +from = 161 +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 1 +interval = 1 +str = STR_CONFIG_SETTING_CARGODEST_PAX +strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF +proc = CargodestModeChanged + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.mode_town_cargo +type = SLE_UINT8 +from = 161 +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 1 +interval = 1 +str = STR_CONFIG_SETTING_CARGODEST_TOWN +strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF +proc = CargodestModeChanged + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.mode_others +type = SLE_UINT8 +from = 161 +guiflags = SGF_MULTISTRING +def = 0 +min = 0 +max = 1 +interval = 1 +str = STR_CONFIG_SETTING_CARGODEST_OTHER +strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF +proc = CargodestModeChanged + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.base_town_links +type = SLE_UINT8 +from = 161 +def = 3,3 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.base_ind_links +type = SLE_UINT8 +from = 161 +def = 2,4,1 + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.city_town_links +type = SLE_UINT8 +from = 161 +def = 8 +min = 0 +max = 255 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.town_chances_town +type = SLE_UINT8 +from = 161 +def = 100,100,100,100 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.town_chances_city +type = SLE_UINT8 +from = 161 +def = 70,100,100,100 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.ind_chances +type = SLE_UINT8 +from = 161 +def = 60,85,100 + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.random_dest_chance +type = SLE_UINT8 +from = 161 +def = 5 +min = 0 +max = 99 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.big_town_pop +type = SLE_UINT32 +from = 161 +def = 500,2000 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.pop_scale_town +type = SLE_UINT16 +from = 161 +def = 100,180,200,1000 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.cargo_scale_ind +type = SLE_UINT16 +from = 161 +def = 250,200 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.min_weight_town +type = SLE_UINT16 +from = 161 +def = 5,5 + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.min_weight_ind +type = SLE_UINT16 +from = 161 +def = 10 +min = 0 +max = 1000 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.weight_scale_town +type = SLE_UINT16 +from = 161 +def = 10,40,20,80 + +[SDT_LIST] +base = GameSettings +var = economy.cargodest.weight_scale_ind +type = SLE_UINT16 +from = 161 +def = 20,50 + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.town_nearby_dist +type = SLE_UINT32 +from = 161 +def = 48*48 +min = 1 +max = UINT32_MAX + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.ind_nearby_dist +type = SLE_UINT32 +from = 161 +def = 52*52 +min = 1 +max = UINT32_MAX + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.max_route_age +type = SLE_UINT16 +from = 161 +def = 2*DAYS_IN_YEAR +min = 0 +max = UINT16_MAX + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.route_recalc_delay +type = SLE_UINT16 +from = 161 +def = 20 +min = 0 +max = UINT16_MAX + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.route_recalc_chunk +type = SLE_UINT16 +from = 161 +def = 15 +min = 0 +max = UINT16_MAX + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.max_route_penalty[0] +type = SLE_UINT16 +from = 161 +def = 200 +min = 0 +max = UINT16_MAX + +[SDT_VAR] +base = GameSettings +var = economy.cargodest.max_route_penalty[1] +type = SLE_UINT16 +from = 161 +def = 150 +min = 0 +max = UINT16_MAX + ; previously ai-new setting. [SDT_NULL] length = 1 @@ -1707,6 +1918,67 @@ def = 20 * YAPF_TILE_LENGTH min = 0 max = 1000000 +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_transfer_cost +type = SLE_UINT +from = 161 +def = 500 +min = 0 +max = 1000000 + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_max_transfers +type = SLE_UINT16 +from = 161 +def = 5 +min = 0 +max = 100000 + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_distance_factor +type = SLE_UINT16 +from = 161 +def = 8 +min = 0 +max = 1000 + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_travel_time_factor +type = SLE_UINT16 +from = 161 +def = 32 +min = 0 +max = UINT16_MAX + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_station_last_veh_factor +type = SLE_UINT16 +from = 161 +def = 64 +min = 0 +max = UINT16_MAX + +[SDT_VAR] +base = GameSettings +var = pf.yapf.route_station_waiting_factor +type = SLE_UINT16 +from = 161 +def = 128 +min = 0 +max = UINT16_MAX + +[SDT_LIST] +base = GameSettings +var = pf.yapf.route_mode_cost_factor +type = SLE_UINT8 +from = 161 +def = "4,2,1,8" + ## [SDT_VAR] base = GameSettings diff --git a/src/tilearea.cpp b/src/tilearea.cpp index 29a1643..3f2726a 100644 --- a/src/tilearea.cpp +++ b/src/tilearea.cpp @@ -94,6 +94,25 @@ bool TileArea::Intersects(const TileArea &ta) const } /** + * Does this tile area contain a tile? + * @param tile Tile to test for. + * @return True if the tile is inside the area. + */ +bool TileArea::Contains(TileIndex tile) const +{ + if (this->w == 0) return false; + + assert(this->w != 0 && this->h != 0); + + uint left = TileX(this->tile); + uint top = TileY(this->tile); + uint tile_x = TileX(tile); + uint tile_y = TileY(tile); + + return tile_x >= left && tile_x < left + this->w && tile_y >= top && tile_y < top + this->h; +} + +/** * Clamp the tile area to map borders. */ void TileArea::ClampToMap() diff --git a/src/tilearea_type.h b/src/tilearea_type.h index 46a093f..5826ce1 100644 --- a/src/tilearea_type.h +++ b/src/tilearea_type.h @@ -48,6 +48,8 @@ struct TileArea { bool Intersects(const TileArea &ta) const; + bool Contains(TileIndex tile) const; + void ClampToMap(); /** diff --git a/src/tilematrix_type.hpp b/src/tilematrix_type.hpp new file mode 100644 index 0000000..ad22a86 --- /dev/null +++ b/src/tilematrix_type.hpp @@ -0,0 +1,149 @@ +/* $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 tilematrix_type.hpp */ + +#ifndef TILEMATRIX_TYPE_HPP +#define TILEMATRIX_TYPE_HPP + +#include "core/mem_func.hpp" +#include "tilearea_type.h" + +/** + * A simple matrix that stores one value per N*N square of the map. + * Storage is only allocated for the part of the map that has values + * assigned. + * + * @note No constructor is called for newly allocated values, you + * have to do this yourself if needed. + * @tparam T The type of the stored items. + * @tparam N Grid size. + */ +template +class TileMatrix { + TileArea area; ///< Area covered by the matrix. + + T *data; ///< Pointer to data array. + + void AllocateStorage(TileIndex tile) + { + uint old_left = TileX(this->area.tile) / N; + uint old_top = TileY(this->area.tile) / N; + uint old_w = this->area.w / N; + uint old_h = this->area.h / N; + + /* Add the square the tile is in to the tile area. We do this + * by adding top-left and bottom-right of the square. */ + uint grid_x = (TileX(tile) / N) * N; + uint grid_y = (TileY(tile) / N) * N; + this->area.Add(TileXY(grid_x, grid_y)); + this->area.Add(TileXY(grid_x + N - 1, grid_y + N - 1)); + + /* Allocate new storage. */ + T *new_data = CallocT(this->area.w / N * this->area.h / N); + + if (old_w > 0) { + /* Copy old data if present. */ + uint offs_x = old_left - TileX(this->area.tile) / N; + uint offs_y = old_top - TileY(this->area.tile) / N; + + for (uint row = 0; row < old_h; row++) { + MemCpyT(&new_data[(row + offs_y) * this->area.w / N + offs_x], &this->data[row * old_w], old_w); + } + } + + free(this->data); + this->data = new_data; + } + + /* Friend declarations for town save/load. */ + friend const struct SaveLoad *GetTileMatrixDesc(); + friend void RealSave_TOWN(struct Town *t); + friend void Load_TOWN(); + +public: + static const uint GRID = N; + + TileMatrix() : area(INVALID_TILE, 0, 0), data(NULL) {} + + ~TileMatrix() + { + free(this->data); + } + + /** + * Get the total covered area. + * @return The area covered by the matrix. + */ + const TileArea& GetArea() const + { + return this->area; + } + + /** + * Get the area of the matrix square that contains a specific tile. + * @param The tile to get the map area for. + * @param extend Extend the area by this many squares on all sides. + * @return Tile area containing the tile. + */ + static TileArea GetAreaForTile(TileIndex tile, uint extend = 0) + { + uint tile_x = (TileX(tile) / N) * N; + uint tile_y = (TileY(tile) / N) * N; + uint w = N, h = N; + + if (tile_x >= extend * N) { + tile_x -= extend * N; + w += extend * N; + } + if (tile_y >= extend * N) { + tile_y -= extend * N; + h += extend * N; + } + if (tile_x + w < MapSizeX() - extend * N) w += extend * N; + if (tile_y + h < MapSizeY() - extend * N) h += extend * N; + + return TileArea(TileXY(tile_x, tile_y), w, h); + } + + /** + * Extend the coverage area to include a tile. + * @param tile The tile to include. + */ + void Add(TileIndex tile) + { + if (!this->area.Contains(tile)) { + this->AllocateStorage(tile); + } + } + + /** + * Get the value associated to a tile index. + * @param tile The tile to get the value for. + * @return Pointer to the value. + */ + T *Get(TileIndex tile) + { + this->Add(tile); + + tile -= this->area.tile; + uint x = TileX(tile) / N; + uint y = TileY(tile) / N; + + return &this->data[y * this->area.w / N + x]; + } + + /** Array access operator, see #Get. */ + FORCEINLINE T &operator[](TileIndex tile) + { + return *this->Get(tile); + } +}; + +#endif /* TILEMATRIX_TYPE_HPP */ diff --git a/src/town.h b/src/town.h index d2f0ba1..5526c8f 100644 --- a/src/town.h +++ b/src/town.h @@ -17,6 +17,8 @@ #include "command_type.h" #include "town_map.h" #include "subsidy_type.h" +#include "cargodest_base.h" +#include "tilematrix_type.hpp" template struct BuildingCounts { @@ -24,6 +26,8 @@ struct BuildingCounts { T class_count[HOUSE_CLASS_MAX]; }; +typedef TileMatrix AcceptanceMatrix; + static const uint CUSTOM_TOWN_NUMBER_DIFFICULTY = 4; ///< value for custom town number in difficulty settings static const uint CUSTOM_TOWN_MAX_NUMBER = 5000; ///< this is the maximum number of towns a user can specify in customisation @@ -33,8 +37,9 @@ typedef Pool TownPool; extern TownPool _town_pool; /** Town data structure. */ -struct Town : TownPool::PoolItem<&_town_pool> { +struct Town : TownPool::PoolItem<&_town_pool>, CargoSourceSink { TileIndex xy; + TileIndex xy_aligned; ///< NOSAVE: Town centre aligned to the #AcceptanceMatrix grid. /* Current population of people and amount of houses. */ uint32 num_houses; @@ -69,14 +74,8 @@ struct Town : TownPool::PoolItem<&_town_pool> { int16 ratings[MAX_COMPANIES]; ///< Ratings of each company for this town. /* Maximum amount of passengers and mail that can be transported. */ - uint32 max_pass; - uint32 max_mail; - uint32 new_max_pass; - uint32 new_max_mail; - uint32 act_pass; - uint32 act_mail; - uint32 new_act_pass; - uint32 new_act_mail; + TransportedCargoStat pass; + TransportedCargoStat mail; /* Amount of passengers that were transported. */ byte pct_pass_transported; @@ -105,6 +104,13 @@ struct Town : TownPool::PoolItem<&_town_pool> { bool larger_town; TownLayoutByte layout; ///< town specific road layout + /* Current cargo acceptance and production. */ + uint32 cargo_produced; ///< Bitmap of all cargos produced by houses in this town. + AcceptanceMatrix cargo_accepted; ///< Bitmap of cargos accepted by houses for each 4*4 map square of the town. + uint32 cargo_accepted_total; ///< NOSAVE: Bitmap of all cargos accepted by houses in this town. + uint32 cargo_accepted_weights[NUM_CARGO]; ///< NOSAVE: Weight sum of accepting squares per cargo. + uint32 cargo_accepted_max_weight; ///< NOSAVE: Cached maximum weight for an accepting square. + PartOfSubsidyByte part_of_subsidy; ///< NOSAVE: is this town a source/destination of a subsidy? /* NOSAVE: UpdateTownRadius updates this given the house count. */ @@ -122,6 +128,30 @@ struct Town : TownPool::PoolItem<&_town_pool> { void InitializeLayout(TownLayout layout); + /* virtual */ SourceType GetType() const + { + return ST_TOWN; + } + + /* virtual */ SourceID GetID() const + { + return this->index; + } + + /* virtual */ bool AcceptsCargo(CargoID cid) const + { + return HasBit(this->cargo_accepted_total, cid); + } + + /* virtual */ bool SuppliesCargo(CargoID cid) const + { + return HasBit(this->cargo_produced, cid); + } + + /* virtual */ uint GetDestinationWeight(CargoID cid, byte weight_mod) const; + /* virtual */ void CreateSpecialLinks(CargoID cid); + /* virtual */ TileArea GetTileForDestination(CargoID cid); + /** * Calculate the max town noise. * The value is counted using the population divided by the content of the @@ -143,7 +173,10 @@ struct Town : TownPool::PoolItem<&_town_pool> { return Town::Get(GetTownIndex(tile)); } - static Town *GetRandom(); + /** Callback function for #Town::GetRandom. */ + typedef bool (*EnumTownProc)(const Town *t, void *data); + + static Town *GetRandom(EnumTownProc enum_proc = NULL, TownID skip = INVALID_TOWN, void *data = NULL); static void PostDestructor(size_t index); }; @@ -198,6 +231,8 @@ void ResetHouses(); void ClearTownHouse(Town *t, TileIndex tile); void UpdateTownMaxPass(Town *t); void UpdateTownRadius(Town *t); +void UpdateTownCargos(Town *t); +void UpdateTownCargoTotal(Town *t); CommandCost CheckIfAuthorityAllowsNewStation(TileIndex tile, DoCommandFlag flags); Town *ClosestTownFromTile(TileIndex tile, uint threshold); void ChangeTownRating(Town *t, int add, int max, DoCommandFlag flags); @@ -232,6 +267,7 @@ DECLARE_ENUM_AS_BIT_SET(TownActions) extern const byte _town_action_costs[TACT_COUNT]; extern TownID _new_town_id; + /** * Set the default name for a depot/waypoint * @tparam T The type/class to make a default name for diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 25f59d1..9d441dc 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -133,17 +133,36 @@ void Town::InitializeLayout(TownLayout layout) } /** - * Return a random valid town. - * @return random town, NULL if there are no towns + * Return a random town that statisfies some criteria specified + * with a callback function. + * + * @param enum_proc Callback function. Return true for a matching town and false to continue iterating. + * @param skip Skip over this town id when searching. + * @param data Optional data passed to the callback function. + * @return A town satisfying the search criteria or NULL if no such town exists. */ -/* static */ Town *Town::GetRandom() +/* static */ Town *Town::GetRandom(EnumTownProc enum_proc, TownID skip, void *data) { - if (Town::GetNumItems() == 0) return NULL; - int num = RandomRange((uint16)Town::GetNumItems()); - size_t index = MAX_UVALUE(size_t); + assert(skip == INVALID_TOWN || Town::IsValidID(skip)); - while (num >= 0) { - num--; + uint16 max_num = 0; + if (enum_proc != NULL) { + /* A callback was given, count all matching towns. */ + Town *t; + FOR_ALL_TOWNS(t) { + if (t->index != skip && enum_proc(t, data)) max_num++; + } + } else { + max_num = (uint16)Town::GetNumItems(); + /* Subtract one if a town to skip was given. max_num is at least + * one here as otherwise skip could not be valid. */ + if (skip != INVALID_TOWN) max_num--; + } + if (max_num == 0) return NULL; + + uint num = RandomRange(max_num) + 1; + size_t index = MAX_UVALUE(size_t); + do { index++; /* Make sure we have a valid town */ @@ -151,7 +170,9 @@ void Town::InitializeLayout(TownLayout layout) index++; assert(index < Town::GetPoolSize()); } - } + + if (index != skip && (enum_proc == NULL || enum_proc(Town::Get(index), data))) num--; + } while (num > 0); return Town::Get(index); } @@ -479,18 +500,18 @@ static void TileLoop_Town(TileIndex tile) uint amt = GB(callback, 0, 8); if (amt == 0) continue; - uint moved = MoveGoodsToStation(cargo, amt, ST_TOWN, t->index, stations.GetStations()); + uint moved = MoveGoodsToStation(cargo, amt, ST_TOWN, t->index, stations.GetStations(), tile); const CargoSpec *cs = CargoSpec::Get(cargo); switch (cs->town_effect) { case TE_PASSENGERS: - t->new_max_pass += amt; - t->new_act_pass += moved; + t->pass.new_max += amt; + t->pass.new_act += moved; break; case TE_MAIL: - t->new_max_mail += amt; - t->new_act_mail += moved; + t->mail.new_max += amt; + t->mail.new_act += moved; break; default: @@ -502,16 +523,16 @@ static void TileLoop_Town(TileIndex tile) uint amt = GB(r, 0, 8) / 8 + 1; if (EconomyIsInRecession()) amt = (amt + 1) >> 1; - t->new_max_pass += amt; - t->new_act_pass += MoveGoodsToStation(CT_PASSENGERS, amt, ST_TOWN, t->index, stations.GetStations()); + t->pass.new_max += amt; + t->pass.new_act += MoveGoodsToStation(CT_PASSENGERS, amt, ST_TOWN, t->index, stations.GetStations(), tile); } if (GB(r, 8, 8) < hs->mail_generation) { uint amt = GB(r, 8, 8) / 8 + 1; if (EconomyIsInRecession()) amt = (amt + 1) >> 1; - t->new_max_mail += amt; - t->new_act_mail += MoveGoodsToStation(CT_MAIL, amt, ST_TOWN, t->index, stations.GetStations()); + t->mail.new_max += amt; + t->mail.new_act += MoveGoodsToStation(CT_MAIL, amt, ST_TOWN, t->index, stations.GetStations(), tile); } } @@ -678,6 +699,87 @@ static void ChangeTileOwner_Town(TileIndex tile, Owner old_owner, Owner new_owne /* not used */ } +/** Update the total cargo acceptance of the whole town. */ +void UpdateTownCargoTotal(Town *t) +{ + t->cargo_accepted_total = 0; + MemSetT(t->cargo_accepted_weights, 0, lengthof(t->cargo_accepted_weights)); + + /* Calculate the maximum weight based on the grid square furthest + * from the town centre. The maximum weight is two times the L-inf + * norm plus 1 so that max weight - furthest square weight == 1. */ + const TileArea &area = t->cargo_accepted.GetArea(); + uint max_dist = max(DistanceMax(t->xy_aligned, area.tile), DistanceMax(t->xy_aligned, TILE_ADDXY(area.tile, area.w - 1, area.h - 1))) / AcceptanceMatrix::GRID; + t->cargo_accepted_max_weight = max_dist * 2 + 1; + + /* Collect acceptance from all grid squares. */ + TILE_AREA_LOOP(tile, area) { + if (TileX(tile) % AcceptanceMatrix::GRID == 0 && TileY(tile) % AcceptanceMatrix::GRID == 0) { + uint32 acc = t->cargo_accepted[tile]; + t->cargo_accepted_total |= acc; + + CargoID cid; + FOR_EACH_SET_CARGO_ID(cid, acc) { + /* For each accepted cargo, the grid square weight is the maximum weight + * minus two times the L-inf norm between this square and the centre square. */ + t->cargo_accepted_weights[cid] += t->cargo_accepted_max_weight - (DistanceMax(t->xy_aligned, tile) / AcceptanceMatrix::GRID) * 2; + } + } + } +} + +/** + * Update accepted and produced town cargos around a specific tile. + * @param t The town to update. + * @param start Update the values around this tile. + * @param update_total Set to true if the total cargo acceptance should be updated. + */ +static void UpdateTownCargos(Town *t, TileIndex start, bool update_total = true) +{ + CargoArray accepted, produced; + uint32 dummy; + + /* Gather acceptance and production for all houses in an area around the start tile. + * The area is composed of the square the tile is in, extended one square in all + * directions as the coverage area of a single station is bigger than just one square. */ + TileArea area = AcceptanceMatrix::GetAreaForTile(start, 1); + TILE_AREA_LOOP(tile, area) { + if (!IsTileType(tile, MP_HOUSE) || GetTownIndex(tile) != t->index) continue; + + AddAcceptedCargo_Town(tile, accepted, &dummy); + AddProducedCargo_Town(tile, produced); + } + + /* Create bitmask of accepted/produced cargos. */ + uint32 acc = 0; + for (uint cid = 0; cid < NUM_CARGO; cid++) { + if (accepted[cid] >= 8) SetBit(acc, cid); + if (produced[cid] > 0) SetBit(t->cargo_produced, cid); + } + t->cargo_accepted[start] = acc; + + if (update_total) UpdateTownCargoTotal(t); +} + +/** Update cargo production and acceptance for the complete town. */ +void UpdateTownCargos(Town *t) +{ + t->cargo_produced = 0; + + const TileArea &area = t->cargo_accepted.GetArea(); + if (area.tile == INVALID_TILE) return; + + /* Update acceptance for each grid square. */ + TILE_AREA_LOOP(tile, area) { + if (TileX(tile) % AcceptanceMatrix::GRID == 0 && TileY(tile) % AcceptanceMatrix::GRID == 0) { + UpdateTownCargos(t, tile, false); + } + } + + /* Update the total acceptance. */ + UpdateTownCargoTotal(t); +} + static bool GrowTown(Town *t); static void TownTickHandler(Town *t) @@ -1380,8 +1482,8 @@ void UpdateTownRadius(Town *t) void UpdateTownMaxPass(Town *t) { - t->max_pass = t->population >> 3; - t->max_mail = t->population >> 4; + t->pass.old_max = t->population >> 3; + t->mail.old_max = t->population >> 4; } /** @@ -1405,14 +1507,14 @@ static void DoCreateTown(Town *t, TileIndex tile, uint32 townnameparts, TownSize t->population = 0; t->grow_counter = 0; t->growth_rate = 250; - t->new_max_pass = 0; - t->new_max_mail = 0; - t->new_act_pass = 0; - t->new_act_mail = 0; - t->max_pass = 0; - t->max_mail = 0; - t->act_pass = 0; - t->act_mail = 0; + t->pass.new_max = 0; + t->mail.new_max = 0; + t->pass.new_act = 0; + t->mail.new_act = 0; + t->pass.old_max = 0; + t->mail.old_max = 0; + t->pass.old_act = 0; + t->mail.old_act = 0; t->pct_pass_transported = 0; t->pct_mail_transported = 0; @@ -1448,6 +1550,11 @@ static void DoCreateTown(Town *t, TileIndex tile, uint32 townnameparts, TownSize t->larger_town = city; + /* Cache the aligned tile index of the centre tile. */ + uint town_x = (TileX(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + uint town_y = (TileY(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID; + t->xy_aligned= TileXY(town_x, town_y); + int x = (int)size * 16 + 3; if (size == TSZ_RANDOM) x = (Random() & 0xF) + 8; /* Don't create huge cities when founding town in-game */ @@ -2206,6 +2313,7 @@ static bool BuildTownHouse(Town *t, TileIndex tile) } MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits); + UpdateTownCargos(t, tile); return true; } @@ -2287,6 +2395,9 @@ void ClearTownHouse(Town *t, TileIndex tile) if (eflags & BUILDING_2_TILES_Y) DoClearTownHouseHelper(tile + TileDiffXY(0, 1), t, ++house); if (eflags & BUILDING_2_TILES_X) DoClearTownHouseHelper(tile + TileDiffXY(1, 0), t, ++house); if (eflags & BUILDING_HAS_4_TILES) DoClearTownHouseHelper(tile + TileDiffXY(1, 1), t, ++house); + + /* Update cargo acceptance. */ + UpdateTownCargos(t, tile); } /** @@ -2316,6 +2427,8 @@ CommandCost CmdRenameTown(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 t->UpdateVirtCoord(); InvalidateWindowData(WC_TOWN_DIRECTORY, 0, 1); + InvalidateWindowClassesData(WC_TOWN_VIEW); + InvalidateWindowClassesData(WC_INDUSTRY_VIEW); UpdateAllStationVirtCoords(); } return CommandCost(); @@ -2780,17 +2893,15 @@ static void UpdateTownGrowRate(Town *t) static void UpdateTownAmounts(Town *t) { /* Using +1 here to prevent overflow and division by zero */ - t->pct_pass_transported = t->new_act_pass * 256 / (t->new_max_pass + 1); + t->pct_pass_transported = t->pass.new_act * 256 / (t->pass.new_max + 1); - t->max_pass = t->new_max_pass; t->new_max_pass = 0; - t->act_pass = t->new_act_pass; t->new_act_pass = 0; + t->pass.NewMonth(); t->act_food = t->new_act_food; t->new_act_food = 0; t->act_water = t->new_act_water; t->new_act_water = 0; /* Using +1 here to prevent overflow and division by zero */ - t->pct_mail_transported = t->new_act_mail * 256 / (t->new_max_mail + 1); - t->max_mail = t->new_max_mail; t->new_max_mail = 0; - t->act_mail = t->new_act_mail; t->new_act_mail = 0; + t->pct_mail_transported = t->mail.new_act * 256 / (t->mail.new_max + 1); + t->mail.NewMonth(); SetWindowDirty(WC_TOWN_VIEW, t->index); } @@ -3016,6 +3127,7 @@ void TownsMonthlyLoop() UpdateTownGrowRate(t); UpdateTownAmounts(t); UpdateTownUnwanted(t); + UpdateTownCargos(t); } } diff --git a/src/town_gui.cpp b/src/town_gui.cpp index f5b5d0f..2c0eede 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -34,6 +34,7 @@ #include "core/geometry_func.hpp" #include "genworld.h" #include "sprite.h" +#include "cargodest_gui.h" #include "table/strings.h" @@ -319,10 +320,13 @@ struct TownViewWindow : Window { private: Town *town; ///< Town displayed by the window. + CargoDestinationList dest_list; ///< Sorted list of demand destinations. + uint dest_list_top; ///< Top coordinate of the destination list in the #TVW_INFOPANEL widget. + public: static const int TVW_HEIGHT_NORMAL = 150; - TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window() + TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), dest_list(Town::Get(window_number)) { this->CreateNestedTree(desc); @@ -368,12 +372,12 @@ public: SetDParam(1, this->town->num_houses); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y, STR_TOWN_VIEW_POPULATION_HOUSES); - SetDParam(0, this->town->act_pass); - SetDParam(1, this->town->max_pass); + SetDParam(0, this->town->pass.old_act); + SetDParam(1, this->town->pass.old_max); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_PASSENGERS_LAST_MONTH_MAX); - SetDParam(0, this->town->act_mail); - SetDParam(1, this->town->max_mail); + SetDParam(0, this->town->mail.old_act); + SetDParam(1, this->town->mail.old_max); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_MAIL_LAST_MONTH_MAX); StringID required_text = STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED; @@ -433,6 +437,8 @@ public: SetDParam(1, this->town->MaxTownNoise()); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_NOISE_IN_TOWN); } + + this->dest_list.DrawList(r.left, r.right, y); } virtual void OnClick(Point pt, int widget, int click_count) @@ -471,6 +477,10 @@ public: case TVW_DELETE: // delete town - only available on Scenario editor DoCommandP(0, this->window_number, 0, CMD_DELETE_TOWN | CMD_MSG(STR_ERROR_TOWN_CAN_T_DELETE)); break; + + case TVW_INFOPANEL: // jump to demand destination + this->dest_list.OnClick(pt.y - this->dest_list_top - this->GetWidget(widget)->pos_y); + break; } } @@ -487,7 +497,7 @@ public: * Gets the desired height for the information panel. * @return the desired height in pixels. */ - uint GetDesiredInfoHeight() const + uint GetDesiredInfoHeight() { uint aimed_height = 3 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; @@ -505,6 +515,9 @@ public: if (_settings_game.economy.station_noise_level) aimed_height += FONT_HEIGHT_NORMAL; + this->dest_list_top = aimed_height - FONT_HEIGHT_NORMAL; + aimed_height += this->dest_list.GetListHeight(); + return aimed_height; } @@ -538,6 +551,13 @@ public: /* Called when setting station noise or required cargos have changed, in order to resize the window */ this->SetDirty(); // refresh display for current size. This will allow to avoid glitches when downgrading this->ResizeWindowAsNeeded(); + + /* Rebuild destination list if data is not zero, otherwise just resort. */ + if (data != 0) { + this->dest_list.InvalidateData(); + } else { + this->dest_list.Resort(); + } } virtual void OnQueryTextFinished(char *str) diff --git a/src/town_type.h b/src/town_type.h index 14d840c..ed1c505 100644 --- a/src/town_type.h +++ b/src/town_type.h @@ -107,4 +107,21 @@ typedef SimpleTinyEnumT TownFoundingByte; static const uint MAX_LENGTH_TOWN_NAME_CHARS = 32; ///< The maximum length of a town name in characters including '\0' +/** Store the maximum and actually transported cargo amount for the current and the last month. */ +struct TransportedCargoStat { + uint32 old_max; ///< Maximum amount last month + uint32 new_max; ///< Maximum amount this month + uint32 old_act; ///< Actually transported last month + uint32 new_act; ///< Actually transported this month + + TransportedCargoStat() : old_max(0), new_max(0), old_act(0), new_act(0) {} + + /** Update stats for a new month. */ + void NewMonth() + { + this->old_max = this->new_max; this->new_max = 0; + this->old_act = this->new_act; this->new_act = 0; + } +}; + #endif /* TOWN_TYPE_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 92ee36e..3ec153a 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -36,6 +36,7 @@ #include "company_base.h" #include "newgrf.h" #include "order_backup.h" +#include "cargodest_func.h" #include "table/strings.h" #include "table/train_cmd.h" @@ -188,6 +189,8 @@ void Train::ConsistChanged(bool same_length) u->InvalidateNewGRFCache(); } + uint32 cargo_mask = 0; + for (Train *u = this; u != NULL; u = u->Next()) { const Engine *e_u = Engine::Get(u->engine_type); const RailVehicleInfo *rvi_u = &e_u->u.rail; @@ -232,7 +235,9 @@ void Train::ConsistChanged(bool same_length) } } + /* Store carried cargo. */ u->cargo_cap = GetVehicleCapacity(u); + if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type); /* check the vehicle length (callback) */ uint16 veh_len = CALLBACK_FAILED; @@ -254,6 +259,7 @@ void Train::ConsistChanged(bool same_length) } /* store consist weight/max speed in cache */ + this->vcache.cached_cargo_mask = cargo_mask; this->vcache.cached_max_speed = max_speed; this->tcache.cached_tilt = train_can_tilt; this->tcache.cached_max_curve_speed = this->GetCurveSpeedLimit(); @@ -1091,6 +1097,8 @@ static void NormaliseTrainHead(Train *head) /* Not a front engine, i.e. a free wagon chain. No need to do more. */ if (!head->IsFrontEngine()) return; + PrefillRouteLinks(head); + /* Update the refit button and window */ InvalidateWindowData(WC_VEHICLE_REFIT, head->index); SetWindowWidgetDirty(WC_VEHICLE_VIEW, head->index, VVW_WIDGET_REFIT_VEH); @@ -1276,6 +1284,9 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u NormaliseTrainHead(src_head); NormaliseTrainHead(dst_head); + /* Pre-fill route links after adding a vehicle. */ + if (dst_head != NULL && dst_head->IsFrontEngine()) PrefillRouteLinks(dst_head); + /* We are undoubtedly changing something in the depot and train list. */ InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); @@ -2658,8 +2669,6 @@ int Train::UpdateSpeed() */ static void TrainEnterStation(Train *v, StationID station) { - v->last_station_visited = station; - /* check if a train ever visited this station before */ Station *st = Station::Get(station); if (!(st->had_vehicle_of_type & HVOT_TRAIN)) { @@ -2677,7 +2686,7 @@ static void TrainEnterStation(Train *v, StationID station) v->force_proceed = TFP_NONE; SetWindowDirty(WC_VEHICLE_VIEW, v->index); - v->BeginLoading(); + v->BeginLoading(station); TriggerStationAnimation(st, v->tile, SAT_TRAIN_ARRIVES); } diff --git a/src/train_gui.cpp b/src/train_gui.cpp index 7ae0103..362c3d7 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -302,9 +302,11 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab) if (det_tab == TDW_TAB_TOTALS) { // Total cargo tab CargoArray act_cargo; CargoArray max_cargo; + CargoDestSummary dests[NUM_CARGO]; for (const Vehicle *v = Vehicle::Get(veh_id); v != NULL; v = v->Next()) { act_cargo[v->cargo_type] += v->cargo.Count(); max_cargo[v->cargo_type] += v->cargo_cap; + AddVehicleCargoDestSummary(v, &dests[v->cargo_type]); } /* Set scroll-amount seperately from counting, as to not compute num double @@ -312,8 +314,9 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab) */ for (CargoID i = 0; i < NUM_CARGO; i++) { if (max_cargo[i] > 0) num++; // only count carriages that the train has + num += (int)dests[i].size(); } - num++; // needs one more because first line is description string + num += 2; // needs one more because first line is description string } else { for (const Train *v = Train::Get(veh_id); v != NULL; v = v->GetNextVehicle()) { GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary); @@ -412,12 +415,15 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po } else { CargoArray act_cargo; CargoArray max_cargo; + CargoDestSummary dests[NUM_CARGO]; Money feeder_share = 0; for (const Vehicle *u = v; u != NULL; u = u->Next()) { act_cargo[u->cargo_type] += u->cargo.Count(); max_cargo[u->cargo_type] += u->cargo_cap; feeder_share += u->cargo.FeederShare(); + + AddVehicleCargoDestSummary(u, &dests[u->cargo_type]); } /* draw total cargo tab */ @@ -434,6 +440,17 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po DrawString(left, right, y, FreightWagonMult(i) > 1 ? STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT : STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY); y += WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM; } + + for (CargoDestSummary::const_iterator row = dests[i].begin(); row != dests[i].end() && vscroll_pos > -vscroll_cap; ++row) { + if (--vscroll_pos < 0) { + SetDParam(0, i); // {SHORTCARGO} #1 + SetDParam(1, row->count); // {SHORTCARGO} #2 + SetDParam(2, row->type == ST_INDUSTRY ? STR_INDUSTRY_NAME : (row->type == ST_TOWN ? STR_TOWN_NAME : STR_COMPANY_NAME)); // {STRING1} + SetDParam(3, row->dest); // Parameter of {STRING1} + DrawString(left + 2 * WD_PAR_VSEP_WIDE, right, y, STR_VEHICLE_DETAILS_CARGO_TO); + y += WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM; + } + } } SetDParam(0, feeder_share); DrawString(left, right, y, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 5b0b885..f72878d 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -52,6 +52,7 @@ #include "bridge_map.h" #include "tunnel_map.h" #include "depot_map.h" +#include "cargodest_func.h" #include "table/strings.h" @@ -245,6 +246,9 @@ Vehicle::Vehicle(VehicleType type) this->fill_percent_te_id = INVALID_TE_ID; this->first = this; this->colourmap = PAL_NONE; + this->last_station_loaded = INVALID_STATION; + this->current_order.index = INVALID_ORDER; + this->last_order_id = INVALID_ORDER; } /** @@ -828,9 +832,12 @@ void CallVehicleTicks() if (_age_cargo_skip_counter == 0) v->cargo.AgeCargo(); if (v->type == VEH_TRAIN && Train::From(v)->IsWagon()) continue; - if (v->type == VEH_AIRCRAFT && v->subtype != AIR_HELICOPTER) continue; if (v->type == VEH_ROAD && !RoadVehicle::From(v)->IsFrontEngine()) continue; + v->travel_time++; + + if (v->type == VEH_AIRCRAFT && v->subtype != AIR_HELICOPTER) continue; + v->motion_counter += v->cur_speed; /* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */ if (GB(v->motion_counter, 0, 8) < v->cur_speed) PlayVehicleSound(v, VSE_RUNNING); @@ -1832,12 +1839,15 @@ void Vehicle::DeleteUnreachedAutoOrders() /** * Prepare everything to begin the loading when arriving at a station. + * @param station The station ID of the station. * @pre IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP. */ -void Vehicle::BeginLoading() +void Vehicle::BeginLoading(StationID station) { assert(IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP); + this->last_station_visited = station; + if (this->current_order.IsType(OT_GOTO_STATION) && this->current_order.GetDestination() == this->last_station_visited) { this->DeleteUnreachedAutoOrders(); @@ -1918,6 +1928,7 @@ void Vehicle::BeginLoading() auto_order->MakeAutomatic(this->last_station_visited); InsertOrder(this, auto_order, this->cur_auto_order_index); if (this->cur_auto_order_index > 0) --this->cur_auto_order_index; + this->current_order.index = auto_order->index; /* InsertOrder disabled creation of automatic orders for all vehicles with the same automatic order. * Reenable it for this vehicle */ @@ -1929,7 +1940,25 @@ void Vehicle::BeginLoading() this->current_order.MakeLoading(false); } - Station::Get(this->last_station_visited)->loading_vehicles.push_back(this); + UpdateVehicleRouteLinks(this, station); + + /* Save the id of the order which made us arrive here. MakeLoading + * does not overwrite the index so it is still valid here. */ + this->last_order_id = this->current_order.index; + this->last_station_loaded = station; + + Station *last_visited = Station::Get(this->last_station_visited); + last_visited->loading_vehicles.push_back(this); + + /* Update the next hop for waiting cargo. */ + CargoID cid; + FOR_EACH_SET_CARGO_ID(cid, this->vcache.cached_cargo_mask) { + /* Only update if the last update was at least route_recalc_delay ticks earlier. */ + if (CargoHasDestinations(cid) && last_visited->goods[cid].cargo_counter == 0) { + last_visited->goods[cid].cargo.UpdateCargoNextHop(last_visited, cid); + last_visited->goods[cid].cargo_counter = _settings_game.economy.cargodest.route_recalc_delay; + } + } PrepareUnload(this); @@ -1956,6 +1985,9 @@ void Vehicle::LeaveStation() /* Only update the timetable if the vehicle was supposed to stop here. */ if (this->current_order.GetNonStopType() != ONSF_STOP_EVERYWHERE) UpdateVehicleTimetable(this, false); + /* Reset travel time counter. */ + this->travel_time = 0; + this->current_order.MakeLeaveStation(); Station *st = Station::Get(this->last_station_visited); st->loading_vehicles.remove(this); diff --git a/src/vehicle_base.h b/src/vehicle_base.h index a673bcf..9d0663f 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -100,6 +100,7 @@ enum GroundVehicleSubtypeFlags { /** Cached often queried values common to all vehicles. */ struct VehicleCache { + uint32 cached_cargo_mask; ///< Mask of all cargoes carried by the consist. uint16 cached_max_speed; ///< Maximum speed of the consist (minimum of the max speed of all vehicles in the consist). byte cached_vis_effect; ///< Visual effect to show (see #VisualEffect) @@ -208,12 +209,15 @@ public: byte waiting_triggers; ///< Triggers to be yet matched before rerandomizing the random bits. StationID last_station_visited; ///< The last station we stopped at. + StationID last_station_loaded; ///< Last station the vehicle loaded cargo at. + OrderID last_order_id; ///< Order id which caused the vehicle to arrive at the last loading station. CargoID cargo_type; ///< type of cargo this vehicle is carrying byte cargo_subtype; ///< Used for livery refits (NewGRF variations) uint16 cargo_cap; ///< total capacity VehicleCargoList cargo; ///< The cargo this vehicle is carrying + uint32 travel_time; ///< Ticks since last loading byte day_counter; ///< Increased by one for each day byte tick_counter; ///< Increased by one for each tick byte running_ticks; ///< Number of ticks this vehicle was not stopped this day @@ -243,7 +247,7 @@ public: /** We want to 'destruct' the right class. */ virtual ~Vehicle(); - void BeginLoading(); + void BeginLoading(StationID station); void LeaveStation(); GroundVehicleCache *GetGroundVehicleCache(); diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index ec34116..0bbb71a 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -30,6 +30,8 @@ #include "autoreplace_gui.h" #include "company_base.h" #include "order_backup.h" +#include "ship.h" +#include "cargodest_func.h" #include "table/strings.h" @@ -143,7 +145,10 @@ CommandCost CmdBuildVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint Company::Get(_current_company)->num_engines[eid]++; - if (v->IsPrimaryVehicle()) OrderBackup::Restore(v, p2); + if (v->IsPrimaryVehicle()) { + OrderBackup::Restore(v, p2); + PrefillRouteLinks(v); + } } return value; @@ -373,14 +378,21 @@ CommandCost CmdRefitVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint break; case VEH_SHIP: + v->InvalidateNewGRFCacheOfChain(); + v->colourmap = PAL_NONE; // invalidate vehicle colour map + Ship::From(v)->UpdateCache(); + break; case VEH_AIRCRAFT: v->InvalidateNewGRFCacheOfChain(); v->colourmap = PAL_NONE; // invalidate vehicle colour map + UpdateAircraftCache(Aircraft::From(v)); break; default: NOT_REACHED(); } + if (front->IsPrimaryVehicle()) PrefillRouteLinks(front); + InvalidateWindowData(WC_VEHICLE_DETAILS, front->index); SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index af325a9..6581162 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -2714,3 +2714,28 @@ int GetVehicleWidth(Vehicle *v) return vehicle_width; } + +/** + * Sum the cargo carried by a vehicle by final destination. + * @param v The vehicle. + * @param sum The cargo summary is added to this. + */ +void AddVehicleCargoDestSummary(const Vehicle *v, CargoDestSummary *sum) +{ + const VehicleCargoList::List *packets = v->cargo.Packets(); + for (VehicleCargoList::ConstIterator it = packets->begin(); it != packets->end(); ++it) { + const CargoPacket *cp = *it; + + /* Search for an existing list entry. */ + CargoDestSummary::iterator data; + for (data = sum->begin(); data != sum->end(); ++data) { + if (data->type == cp->DestinationType() && data->dest == cp->DestinationID()) { + data->count += cp->Count(); + break; + } + } + + /* Not found, insert new entry. */ + if (data == sum->end() && cp->DestinationID() != INVALID_SOURCE) sum->push_back(CargoDestSummaryData(cp->DestinationID(), cp->DestinationType(), cp->Count())); + } +} diff --git a/src/vehicle_gui.h b/src/vehicle_gui.h index 6f41130..11c2f10 100644 --- a/src/vehicle_gui.h +++ b/src/vehicle_gui.h @@ -18,6 +18,7 @@ #include "station_type.h" #include "engine_type.h" #include "company_type.h" +#include void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent); @@ -46,6 +47,22 @@ enum TrainDetailsWindowTabs { TDW_TAB_TOTALS, ///< Tab with sum of total cargo transported }; +/** List item for one destination. */ +struct CargoDestSummaryData { + SourceID dest; ///< Destination ID + SourceType type; ///< Destination type + uint count; ///< Cargo count + + CargoDestSummaryData(SourceID dest, SourceType type, uint count) + : dest(dest), type(type), count(count) + { } +}; + +/** List of cargo amounts grouped by final destination. */ +typedef std::list CargoDestSummary; + +void AddVehicleCargoDestSummary(const Vehicle *v, CargoDestSummary *sum); + int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number); void DrawTrainImage(const Train *v, int left, int right, int y, VehicleID selection, int skip, VehicleID drag_dest = INVALID_VEHICLE);