From a34d095259409cf1454d9777deabbc00bcdb9407 Mon Sep 17 00:00:00 2001 From: Erich Eckner Date: Tue, 30 Oct 2018 11:13:12 +0100 Subject: underground patch applied --- config.lib | 4 +- layer.txt | 45 + projects/generate_vs90.vcproj | 25 +- projects/openttd_vs100.vcxproj | 18 + projects/openttd_vs100.vcxproj.filters | 59 +- projects/openttd_vs80.vcproj | 96 + projects/openttd_vs90.vcproj | 136 +- source.list | 26 + src/aircraft_cmd.cpp | 11 +- src/base_consist.h | 5 + src/base_station_base.h | 10 +- src/bridge_gui.cpp | 7 +- src/cargodest.cpp | 1197 ++++++++++ src/cargodest_base.h | 182 ++ src/cargodest_func.h | 35 + src/cargodest_gui.cpp | 176 ++ src/cargodest_gui.h | 48 + src/cargodest_type.h | 31 + src/cargopacket.cpp | 314 ++- src/cargopacket.h | 142 +- src/clear_cmd.cpp | 24 + src/command.cpp | 9 +- src/command_type.h | 4 + src/console_cmds.cpp | 2 + src/date.cpp | 11 + src/date_func.h | 3 + src/date_gui.cpp | 158 +- src/date_gui.h | 4 +- src/date_type.h | 21 +- src/debug.cpp | 2 + src/debug.h | 1 + src/departures.cpp | 646 ++++++ src/departures_func.h | 21 + src/departures_gui.cpp | 856 +++++++ src/departures_gui.h | 22 + src/departures_type.h | 101 + src/economy.cpp | 201 +- src/economy_base.h | 7 +- src/economy_func.h | 2 +- src/genworld.cpp | 2 + src/genworld_gui.cpp | 45 +- src/gfx_type.h | 1 + src/gfxinit.cpp | 3 + src/heightmap.cpp | 4 + src/industry.h | 47 +- src/industry_cmd.cpp | 71 +- src/industry_gui.cpp | 25 +- src/landscape.cpp | 11 +- src/lang/english.txt | 247 +- src/lang/hungarian.txt | 29 + src/lang/russian.txt | 39 + src/layer.cpp | 137 ++ src/layer_func.h | 159 ++ src/layer_gui.h | 17 + src/layer_type.h | 22 + src/main_gui.cpp | 2 +- src/map.cpp | 7 +- src/map_func.h | 2 +- src/misc.cpp | 4 +- src/newgrf.cpp | 3 + src/object_cmd.cpp | 4 +- src/order_base.h | 123 +- src/order_cmd.cpp | 196 +- src/os/macosx/osx_stdafx.h | 2 + src/pathfinder/follow_track.hpp | 4 +- src/pathfinder/npf/npf.cpp | 2 + src/pathfinder/yapf/nodelist.hpp | 4 +- src/pathfinder/yapf/yapf.h | 4 + src/pathfinder/yapf/yapf_cargo.cpp | 442 ++++ src/pathfinder/yapf/yapf_node.hpp | 22 +- src/pathfinder/yapf/yapf_node_rail.hpp | 4 +- src/pathfinder/yapf/yapf_node_road.hpp | 4 +- src/pathfinder/yapf/yapf_node_ship.hpp | 2 +- src/pathfinder/yapf/yapf_road.cpp | 3 + src/rail.h | 2 +- src/rail_cmd.cpp | 129 +- src/rail_gui.cpp | 92 +- src/rail_map.h | 28 +- src/road_cmd.cpp | 88 +- src/road_cmd.h | 3 + src/road_gui.cpp | 44 + src/road_map.h | 32 +- src/roadveh_cmd.cpp | 14 +- src/saveload/afterload.cpp | 47 + src/saveload/cargodest_sl.cpp | 181 ++ src/saveload/cargopacket_sl.cpp | 6 + src/saveload/economy_sl.cpp | 2 + src/saveload/industry_sl.cpp | 12 +- src/saveload/map_sl.cpp | 6 +- src/saveload/order_sl.cpp | 9 +- src/saveload/saveload.cpp | 10 +- src/saveload/saveload.h | 8 +- src/saveload/station_sl.cpp | 3 + src/saveload/town_sl.cpp | 16 +- src/saveload/vehicle_sl.cpp | 4 + src/script/api/script_window.hpp | 2 + src/settings.cpp | 77 + src/settings_gui.cpp | 95 +- src/settings_type.h | 73 +- src/ship_cmd.cpp | 44 +- src/signal.cpp | 56 +- src/smallmap_gui.cpp | 238 +- src/smallmap_gui.h | 1 + src/station.cpp | 64 +- src/station_base.h | 9 +- src/station_cmd.cpp | 99 +- src/station_cmd.cpp.orig | 3848 ++++++++++++++++++++++++++++++++ src/station_func.h | 2 + src/station_gui.cpp | 503 ++++- src/statusbar_gui.cpp | 10 +- src/strings.cpp | 60 + src/subsidy.cpp | 42 +- src/table/control_codes.h | 4 + src/table/settings.ini | 623 ++++++ src/table/sprites.h | 33 +- src/table/strgen_tables.h | 4 + src/terraform_cmd.cpp | 5 + src/tgp.cpp | 18 +- src/tile_map.h | 2 +- src/tilearea_type.h | 10 + src/tilehighlight_func.h | 8 + src/tilehighlight_type.h | 1 + src/timetable.h | 2 + src/timetable_cmd.cpp | 42 +- src/timetable_gui.cpp | 319 ++- src/toolbar_gui.cpp | 51 +- src/town.h | 37 +- src/town_cmd.cpp | 78 +- src/town_gui.cpp | 24 +- src/track_func.h | 11 + src/track_type.h | 1 + src/trafficlight.cpp | 291 +++ src/trafficlight.h | 133 ++ src/trafficlight_func.h | 19 + src/trafficlight_type.h | 21 + src/train_cmd.cpp | 241 +- src/train_gui.cpp | 19 +- src/tree_cmd.cpp | 8 +- src/tunnelbridge_cmd.cpp | 146 +- src/tunnelbridge_map.h | 94 + src/underground_gui.cpp | 294 +++ src/underground_gui.h | 19 + src/vehicle.cpp | 65 +- src/vehicle_base.h | 9 +- src/vehicle_cmd.cpp | 6 + src/vehicle_gui.cpp | 25 + src/vehicle_gui.h | 17 + src/vehicle_type.h | 2 +- src/viewport.cpp | 429 +++- src/viewport_func.h | 2 +- src/water_cmd.cpp | 11 +- src/waypoint_gui.cpp | 6 + src/widgets/departures_widget.h | 29 + src/widgets/genworld_widget.h | 2 + src/widgets/road_widget.h | 1 + src/widgets/smallmap_widget.h | 1 + src/widgets/station_widget.h | 2 + src/widgets/timetable_widget.h | 3 + src/widgets/underground_widget.h | 21 + src/widgets/waypoint_widget.h | 1 + src/window.cpp | 2 +- src/window_type.h | 11 + 162 files changed, 14992 insertions(+), 525 deletions(-) create mode 100644 layer.txt create mode 100644 src/cargodest.cpp create mode 100644 src/cargodest_base.h create mode 100644 src/cargodest_func.h create mode 100644 src/cargodest_gui.cpp create mode 100644 src/cargodest_gui.h create mode 100644 src/cargodest_type.h create mode 100644 src/departures.cpp create mode 100644 src/departures_func.h create mode 100644 src/departures_gui.cpp create mode 100644 src/departures_gui.h create mode 100644 src/departures_type.h create mode 100644 src/layer.cpp create mode 100644 src/layer_func.h create mode 100644 src/layer_gui.h create mode 100644 src/layer_type.h create mode 100644 src/pathfinder/yapf/yapf_cargo.cpp create mode 100644 src/saveload/cargodest_sl.cpp create mode 100644 src/station_cmd.cpp.orig create mode 100644 src/trafficlight.cpp create mode 100644 src/trafficlight.h create mode 100644 src/trafficlight_func.h create mode 100644 src/trafficlight_type.h create mode 100644 src/underground_gui.cpp create mode 100644 src/underground_gui.h create mode 100644 src/widgets/departures_widget.h create mode 100644 src/widgets/underground_widget.h diff --git a/config.lib b/config.lib index 2ce1bba98..19b873d61 100644 --- a/config.lib +++ b/config.lib @@ -1427,7 +1427,7 @@ make_cflags_and_ldflags() { LDFLAGS="$LDFLAGS -noixemul" fi - CFLAGS="-O2 -fomit-frame-pointer $CFLAGS" + CFLAGS="-O3 -fomit-frame-pointer $CFLAGS" else OBJS_SUBDIR="debug" @@ -1444,7 +1444,7 @@ make_cflags_and_ldflags() { if [ $enable_debug -ge 3 ]; then CFLAGS="$CFLAGS -O0" else - CFLAGS="$CFLAGS -O2" + CFLAGS="$CFLAGS -O3" fi fi diff --git a/layer.txt b/layer.txt new file mode 100644 index 000000000..6a2ad1d5f --- /dev/null +++ b/layer.txt @@ -0,0 +1,45 @@ + +* Генерация карты (расширенная карта) + +ВИЗУАЛИЗАЦИЯ + +* устроить "сдвиг" вьюпорта для выбора слоя +- и ограничение по перемещению ++ каждый вьюпорт -- свой слой + +ЛОГИКА + +подземелье: + недоступность: +* наводнение +* изменение ландшафта +* строительство +* домов +* деревьев +* вода +* фабрик +* мостов +* тоннелей +* аэропортов +* дороги на пересечении слоев +? генератор +? реки, города +* края (независимый подъем, void) + +ограничение активности +синхронизация изменения ландшафта + +ГУИ + +** строительство двухуровневой станции +** работа со станцией (строительство, удаление / блокирование частей / стоимость) +* шаблон меню для управления / строительства + ++ добавить выбор кол-ва слоев +- исправить координаты в "справке по местности" +- подписи, названия станций (на все слои), эффекты (на нужный слой) + +БАГИ ++ удаление станции (вылет) +* не работает расширение подземной станции (при пристройке клеточек - добавляется новая) +- грф. "скачет" (не редактируемая часть станции произвольно меняется, при модификации станции) diff --git a/projects/generate_vs90.vcproj b/projects/generate_vs90.vcproj index dc7b3e2f5..e1e374656 100644 --- a/projects/generate_vs90.vcproj +++ b/projects/generate_vs90.vcproj @@ -1,7 +1,7 @@ + + + + + + + + + + diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index 4ab6f45e7..785145289 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -296,6 +296,7 @@ + @@ -371,6 +372,7 @@ + @@ -392,6 +394,10 @@ + + + + @@ -599,6 +605,9 @@ + + + @@ -656,6 +665,7 @@ + @@ -792,6 +802,7 @@ + @@ -1187,6 +1198,7 @@ + @@ -1214,6 +1226,12 @@ + + + + + + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index 34a8b4d00..963c23f89 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -100,6 +100,9 @@ {c76ff9f1-1e62-46d8-8d55-000000000032} + + {c76ff9f1-1e62-46d8-8d55-000000000033} + @@ -117,6 +120,9 @@ Source Files + + Source Files + Source Files @@ -342,7 +348,10 @@ Source Files - + + Source Files + + Source Files @@ -405,6 +414,18 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + Header Files @@ -1026,6 +1047,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + Header Files @@ -1197,6 +1227,9 @@ GUI Source Code + + GUI Source Code + GUI Source Code @@ -1605,6 +1638,9 @@ Save/Load handlers + + Save/Load handlers + Save/Load handlers @@ -2790,6 +2826,9 @@ YAPF + + YAPF + YAPF @@ -2871,6 +2910,24 @@ Threading + + underground + + + underground + + + underground + + + underground + + + underground + + + underground + diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index c6e3e595f..6cf74a447 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -454,6 +454,10 @@ RelativePath=".\..\src\bmp.cpp" > + + @@ -502,6 +506,10 @@ RelativePath=".\..\src\dedicated.cpp" > + + @@ -754,6 +762,10 @@ RelativePath=".\..\src\townname.cpp" > + + @@ -842,6 +854,22 @@ RelativePath=".\..\src\cargo_type.h" > + + + + + + + + @@ -942,6 +970,18 @@ RelativePath=".\..\src\video\dedicated_v.h" > + + + + + + @@ -1670,6 +1710,18 @@ RelativePath=".\..\src\track_type.h" > + + + + + + @@ -1906,6 +1958,10 @@ RelativePath=".\..\src\build_vehicle_gui.cpp" > + + @@ -1922,6 +1978,10 @@ RelativePath=".\..\src\date_gui.cpp" > + + @@ -2462,6 +2522,10 @@ RelativePath=".\..\src\saveload\autoreplace_sl.cpp" > + + @@ -4126,6 +4190,10 @@ RelativePath=".\..\src\pathfinder\yapf\yapf_cache.h" > + + @@ -4255,6 +4323,34 @@ > + + + + + + + + + + + + + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj index b5155a469..112ab33c6 100644 --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -1,7 +1,7 @@ + + + + @@ -499,6 +505,10 @@ RelativePath=".\..\src\dedicated.cpp" > + + @@ -751,6 +761,10 @@ RelativePath=".\..\src\townname.cpp" > + + @@ -775,6 +789,22 @@ RelativePath=".\..\src\window.cpp" > + + + + + + + + + + + + + + + + @@ -939,6 +985,18 @@ RelativePath=".\..\src\video\dedicated_v.h" > + + + + + + @@ -1667,6 +1725,18 @@ RelativePath=".\..\src\track_type.h" > + + + + + + @@ -1903,6 +1973,10 @@ RelativePath=".\..\src\build_vehicle_gui.cpp" > + + @@ -1919,6 +1993,10 @@ RelativePath=".\..\src\date_gui.cpp" > + + @@ -2104,11 +2182,11 @@ Name="Widgets" > + + @@ -4123,6 +4205,10 @@ RelativePath=".\..\src\pathfinder\yapf\yapf_cache.h" > + + @@ -4209,49 +4295,65 @@ - - + + + + + + + + + + diff --git a/source.list b/source.list index 83dc19e83..2452906df 100644 --- a/source.list +++ b/source.list @@ -4,6 +4,7 @@ animated_tile.cpp articulated_vehicles.cpp autoreplace.cpp bmp.cpp +cargodest.cpp cargomonitor.cpp cargopacket.cpp cargotype.cpp @@ -16,6 +17,7 @@ currency.cpp date.cpp debug.cpp dedicated.cpp +departures.cpp depot.cpp driver.cpp economy.cpp @@ -81,6 +83,7 @@ tgp.cpp tile_map.cpp tilearea.cpp townname.cpp +trafficlight.cpp #if WIN32 #else #if WINCE @@ -125,6 +128,10 @@ base_station_base.h bmp.h bridge.h cargo_type.h +cargodest_base.h +cargodest_func.h +cargodest_gui.h +cargodest_type.h cargomonitor.h cargopacket.h cargotype.h @@ -150,6 +157,9 @@ date_gui.h date_type.h debug.h video/dedicated_v.h +departures_func.h +departures_gui.h +departures_type.h depot_base.h depot_func.h depot_map.h @@ -332,6 +342,9 @@ townname_func.h townname_type.h track_func.h track_type.h +trafficlight.h +trafficlight_func.h +trafficlight_type.h train.h transparency.h transparency_gui.h @@ -408,10 +421,12 @@ autoreplace_gui.cpp bootstrap_gui.cpp bridge_gui.cpp build_vehicle_gui.cpp +cargodest_gui.cpp cheat_gui.cpp company_gui.cpp console_gui.cpp date_gui.cpp +departures_gui.cpp depot_gui.cpp dock_gui.cpp engine_gui.cpp @@ -550,6 +565,7 @@ saveload/ai_sl.cpp saveload/airport_sl.cpp saveload/animated_tile_sl.cpp saveload/autoreplace_sl.cpp +saveload/cargodest_sl.cpp saveload/cargomonitor_sl.cpp saveload/cargopacket_sl.cpp saveload/cheat_sl.cpp @@ -990,6 +1006,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 @@ -1123,3 +1140,12 @@ thread/thread.h #else thread/thread_none.cpp #end + +# underground +layer.cpp +underground_gui.cpp +layer_type.h +layer_func.h +layer_gui.h +underground_gui.h +widgets/underground_widget.h diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 385eb9be1..93554585e 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -36,6 +36,7 @@ #include "core/random_func.hpp" #include "core/backup_type.hpp" #include "zoom_func.h" +#include "cargotype.h" #include "table/strings.h" @@ -575,6 +576,13 @@ void UpdateAircraftCache(Aircraft *v, bool update_range) /* Squared it now so we don't have to do it later all the time. */ v->acache.cached_max_range_sqr = v->acache.cached_max_range * v->acache.cached_max_range; } + + /* 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; } @@ -1221,7 +1229,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)) { @@ -1238,7 +1245,7 @@ static void AircraftEntersTerminal(Aircraft *v) Game::NewEvent(new ScriptEventStationFirstVehicle(st->index, v->index)); } - v->BeginLoading(); + v->BeginLoading(v->targetairport); } /** diff --git a/src/base_consist.h b/src/base_consist.h index 3679afd35..474ab9741 100644 --- a/src/base_consist.h +++ b/src/base_consist.h @@ -14,6 +14,7 @@ #include "order_type.h" #include "date_type.h" +#include "timetable.h" /** Various front vehicle properties that are preserved when autoreplacing, using order-backup or switching front engines within a consist. */ struct BaseConsist { @@ -22,7 +23,11 @@ struct BaseConsist { /* Used for timetabling. */ uint32 current_order_time; ///< How many ticks have passed since this order started. int32 lateness_counter; ///< How many ticks late (or early if negative) this vehicle is. +#if WALLCLOCK_NETWORK_COMPATIBLE Date timetable_start; ///< When the vehicle is supposed to start the timetable. +#else + DateTicks timetable_start; ///< When the vehicle is supposed to start the timetable. +#endif uint16 service_interval; ///< The interval for (automatic) servicing; either in days or %. diff --git a/src/base_station_base.h b/src/base_station_base.h index 812692bb1..dfbc2dd8c 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -28,6 +28,13 @@ struct StationSpecList { /** StationRect - used to track station spread out rectangle - cheaper than scanning whole map */ +/* +** "layer" +** : +** "Rect" () +** ( , -- +** Rect ) +*/ struct StationRect : public Rect { enum StationRectMode { @@ -38,7 +45,8 @@ struct StationRect : public Rect { StationRect(); void MakeEmpty(); - bool PtInExtendedRect(int x, int y, int distance = 0) const; + bool PtInExtendedRect(int topx, int topy, 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/bridge_gui.cpp b/src/bridge_gui.cpp index 4a349b605..f6412352c 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -24,6 +24,7 @@ #include "cmd_helper.h" #include "tunnelbridge_map.h" #include "road_gui.h" +#include "tilehighlight_func.h" #include "widgets/bridge_widget.h" @@ -116,8 +117,10 @@ private: case TRANSPORT_ROAD: _last_roadbridge_type = this->bridges->Get(i)->index; break; default: break; } - DoCommandP(this->end_tile, this->start_tile, this->type | this->bridges->Get(i)->index, - CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge); + if (DoCommandP(this->end_tile, this->start_tile, this->type | this->bridges->Get(i)->index, + CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge)) { + StoreRailPlacementEndpoints(this->start_tile, this->end_tile, (TileX(this->start_tile) == TileX(this->end_tile)) ? TRACK_Y : TRACK_X, false); + } } /** Sort the builable bridges */ diff --git a/src/cargodest.cpp b/src/cargodest.cpp new file mode 100644 index 000000000..26c9379d8 --- /dev/null +++ b/src/cargodest.cpp @@ -0,0 +1,1197 @@ +/* $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 cargoes +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 cargoes +static const uint BASE_IND_LINKS_SYMM = 2; ///< Index into _settings_game.economy.cargodest.base_ind_links for symmetric cargoes +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 cargoes +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->supplied[CT_PASSENGERS].old_max > _settings_game.economy.cargodest.big_town_pop[BIG_TOWN_POP_PAX] : t->supplied[CT_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->supplied[CT_PASSENGERS].old_max : t->supplied[CT_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 cargoes, cargoes + * with a town effect and all other cargoes. */ + 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->supplied[CT_PASSENGERS].old_max : this->supplied[CT_PASSENGERS].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); + + if (route_link == NULL) { + /* No suitable link found (or direct delivery), nothing is + * moved to the station, but count it as transported anyway. */ + if (found && l != NULL) l->amount.new_act += *amount; + *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 (found && l != NULL) l->amount.new_act += *amount; + + /* 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 cargoes 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 cargoes, 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(cargoes, 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_IMPLICIT) || 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_cargoes = 0; + for (Vehicle *u = v->FirstShared(); u != NULL; u = u->NextShared()) { + transported_cargoes |= 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 implicit order and not a go via-order, consider as destination. */ + if ((order->IsType(OT_IMPLICIT) || 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_cargoes, 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 000000000..8d71e2f8f --- /dev/null +++ b/src/cargodest_base.h @@ -0,0 +1,182 @@ +/* $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 ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner); + 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 000000000..846069d86 --- /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 000000000..bf5fdd1d4 --- /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 = 2 + this->link_list.Length(); + return lines > 2 ? 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 000000000..4c7915ea0 --- /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 000000000..fb18af0ea --- /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 0b92de0c8..42eca0b55 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 @@ inline 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 @@ inline 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 @@ inline 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; it = 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 dcb1415fc..c7ced9b49 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. + */ + inline TileIndex DestinationXY() const + { + return this->dest_xy; + } + + /** + * Gets the ID of the destination of the cargo. + * @return The destination ID. + */ + inline SourceID DestinationID() const + { + return this->dest_id; + } + + /** + * Gets the type of the destination of the cargo. + * @return The destination type. + */ + inline SourceType DestinationType() const + { + return this->dest_type; + } + + /** + * Gets the routing behaviour flags of this packet. + * @return The routing flags. + */ + inline byte Flags() const + { + return this->flags; + } + + /** + * Gets the order ID of the next desired hop. + * @return The order ID of the next desired hop. + */ + inline 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. + */ + inline 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; } }; @@ -307,12 +388,52 @@ public: * CargoList that is used for stations. */ 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/clear_cmd.cpp b/src/clear_cmd.cpp index 46598866f..74a38d139 100644 --- a/src/clear_cmd.cpp +++ b/src/clear_cmd.cpp @@ -14,6 +14,7 @@ #include "command_func.h" #include "landscape.h" #include "genworld.h" +#include "layer_func.h" #include "viewport_func.h" #include "water.h" #include "core/random_func.hpp" @@ -98,8 +99,28 @@ static void DrawClearLandFence(const TileInfo *ti) EndSpriteCombine(); } +static void DrawUndergroundTile_Clear(TileInfo *ti) +{ + +} + static void DrawTile_Clear(TileInfo *ti) { + uint base_tile = TopTile(ti->tile); + uint underground_tile = DownTile(base_tile); + + bool self_underground = IsUnderground(ti->tile); + + bool have_canalization = IsTileType(base_tile, MP_HOUSE); + bool have_underground = !IsTileType(underground_tile, MP_CLEAR); + + if (self_underground && !have_canalization) + DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + + if (self_underground && have_canalization) + DrawGroundSprite(SPR_FLAT_GRASS_TILE + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + + if (!self_underground) switch (GetClearGround(ti->tile)) { case CLEAR_GRASS: DrawClearLandTile(ti, GetClearDensity(ti->tile)); @@ -124,6 +145,9 @@ static void DrawTile_Clear(TileInfo *ti) break; } + if (!self_underground && have_underground) + DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh), PAL_NONE); + DrawBridgeMiddle(ti); } diff --git a/src/command.cpp b/src/command.cpp index eef7652f4..71c0f6259 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -180,9 +180,13 @@ CommandProc CmdChangeTimetable; CommandProc CmdSetVehicleOnTime; CommandProc CmdAutofillTimetable; CommandProc CmdSetTimetableStart; +CommandProc CmdReinitSeparation; CommandProc CmdOpenCloseAirport; +CommandProc CmdBuildTrafficLights; +CommandProc CmdRemoveTrafficLights; + #define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type} /** @@ -324,8 +328,11 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdSetVehicleOnTime, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_VEHICLE_ON_TIME DEF_CMD(CmdAutofillTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_AUTOFILL_TIMETABLE DEF_CMD(CmdSetTimetableStart, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_TIMETABLE_START - + DEF_CMD(CmdReinitSeparation, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_REINIT_SEPARATION DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT + + DEF_CMD(CmdBuildTrafficLights, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TRAFFICLIGHTS + DEF_CMD(CmdRemoveTrafficLights, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_TRAFFICLIGHTS }; /*! diff --git a/src/command_type.h b/src/command_type.h index 49b11d3e4..d0a0a21ac 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -301,9 +301,13 @@ enum Commands { CMD_SET_VEHICLE_ON_TIME, ///< set the vehicle on time feature (timetable) CMD_AUTOFILL_TIMETABLE, ///< autofill the timetable CMD_SET_TIMETABLE_START, ///< set the date that a timetable should start + CMD_REINIT_SEPARATION, ///< reinit timetable separation with new parameters CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft + CMD_BUILD_TRAFFICLIGHTS, ///< place traffic lights on a road crossing + CMD_REMOVE_TRAFFICLIGHTS, ///< remove traffic lights + CMD_END, ///< Must ALWAYS be on the end of this list!! (period) }; diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index a131131ad..679ec7299 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -27,6 +27,7 @@ #include "screenshot.h" #include "genworld.h" #include "strings_func.h" +#include "layer_func.h" #include "viewport_func.h" #include "window_func.h" #include "date_func.h" @@ -1059,6 +1060,7 @@ DEF_CONSOLE_CMD(ConRestart) } /* Don't copy the _newgame pointers to the real pointers, so call SwitchToMode directly */ + _settings_game.game_creation.layers = FindFirstBit(LayerCount()); _settings_game.game_creation.map_x = MapLogX(); _settings_game.game_creation.map_y = FindFirstBit(MapSizeY()); _switch_mode = SM_RESTARTGAME; diff --git a/src/date.cpp b/src/date.cpp index 9df648321..579c0ab0c 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) @@ -154,6 +155,15 @@ Date ConvertYMDToDate(Year year, Month month, Day day) return DAYS_TILL(year) + days; } +/** + * Converts the current day counter and date fraction into an absolute tick value. + * @returns current time as ticks + */ +Ticks GetCurrentTickCount() +{ + return _date * DAY_TICKS + _date_fract; +} + /** Functions used by the IncreaseDate function */ extern void EnginesDailyLoop(); @@ -238,6 +248,7 @@ static void OnNewMonth() IndustryMonthlyLoop(); SubsidyMonthlyLoop(); StationMonthlyLoop(); + UpdateCargoLinks(); #ifdef ENABLE_NETWORK if (_network_server) NetworkServerMonthlyLoop(); #endif /* ENABLE_NETWORK */ diff --git a/src/date_func.h b/src/date_func.h index 6bbde5955..f32e6c7c9 100644 --- a/src/date_func.h +++ b/src/date_func.h @@ -23,6 +23,9 @@ extern uint16 _tick_counter; void SetDate(Date date, DateFract fract); void ConvertDateToYMD(Date date, YearMonthDay *ymd); Date ConvertYMDToDate(Year year, Month month, Day day); +Ticks GetCurrentTickCount(); + +#define YMD_TO_DATE(ymd) (ConvertYMDToDate(ymd.year, ymd.month, ymd.day)) /** * Checks whether the given year is a leap year or not. diff --git a/src/date_gui.cpp b/src/date_gui.cpp index b2421556c..8a59b7e74 100644 --- a/src/date_gui.cpp +++ b/src/date_gui.cpp @@ -16,6 +16,7 @@ #include "window_gui.h" #include "date_gui.h" #include "core/geometry_func.hpp" +#include "settings_type.h" #include "widgets/dropdown_type.h" #include "widgets/date_widget.h" @@ -63,7 +64,7 @@ struct SetDateWindow : Window { * Helper function to construct the dropdown. * @param widget the dropdown widget to create the dropdown for */ - void ShowDateDropDown(int widget) + virtual void ShowDateDropDown(int widget) { int selected; DropDownList *list = new DropDownList(); @@ -144,9 +145,8 @@ struct SetDateWindow : Window { case WID_SD_YEAR: ShowDateDropDown(widget); break; - case WID_SD_SET_DATE: - if (this->callback != NULL) this->callback(this->parent, ConvertYMDToDate(this->date.year, this->date.month, this->date.day)); + if (this->callback != NULL) this->callback(this->parent, ConvertYMDToDate(this->date.year, this->date.month, this->date.day) * DAY_TICKS); delete this; break; } @@ -171,6 +171,122 @@ struct SetDateWindow : Window { } }; +struct SetMinutesWindow : SetDateWindow +{ + Minutes minutes; + + /** Constructor. */ + SetMinutesWindow(const WindowDesc *desc, WindowNumber window_number, Window *parent, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback) : + SetDateWindow(desc, window_number, parent, initial_date, min_year, max_year, callback), + minutes(initial_date / _settings_client.gui.ticks_per_minute) + { + } + + /** + * Helper function to construct the dropdown. + * @param widget the dropdown widget to create the dropdown for + */ + virtual void ShowDateDropDown(int widget) + { + int selected; + DropDownList *list = new DropDownList(); + + switch (widget) { + default: NOT_REACHED(); + + case WID_SD_DAY: + for (uint i = 0; i < 60; i++) { + DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false); + item->SetParam(0, i); + list->push_back(item); + } + selected = MINUTES_MINUTE(minutes); + break; + + case WID_SD_MONTH: + for (uint i = 0; i < 24; i++) { + DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false); + item->SetParam(0, i); + list->push_back(item); + } + selected = MINUTES_HOUR(minutes); + + break; + } + + ShowDropDownList(this, list, selected, widget); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + Dimension d = {0, 0}; + switch (widget) { + default: return; + + case WID_SD_DAY: + for (uint i = 0; i < 60; i++) { + SetDParam(0, i); + d = maxdim(d, GetStringBoundingBox(STR_JUST_INT)); + } + break; + + case WID_SD_MONTH: + for (uint i = 0; i < 24; i++) { + SetDParam(0, i); + d = maxdim(d, GetStringBoundingBox(STR_JUST_INT)); + } + break; + } + + d.width += padding.width; + d.height += padding.height; + *size = d; + } + + virtual void SetStringParameters(int widget) const + { + switch (widget) { + case WID_SD_DAY: SetDParam(0, MINUTES_MINUTE(minutes)); break; + case WID_SD_MONTH: SetDParam(0, MINUTES_HOUR(minutes)); break; + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_SD_DAY: + case WID_SD_MONTH: + case WID_SD_YEAR: + ShowDateDropDown(widget); + break; + + case WID_SD_SET_DATE: + if (this->callback != NULL) this->callback(this->parent, ((DateTicks)minutes - _settings_client.gui.clock_offset) * _settings_client.gui.ticks_per_minute); + delete this; + break; + } + } + + virtual void OnDropdownSelect(int widget, int index) + { + Minutes current = 0; + switch (widget) { + case WID_SD_DAY: + current = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), MINUTES_HOUR(minutes), index); + break; + + case WID_SD_MONTH: + current = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), index, MINUTES_MINUTE(minutes)); + break; + } + + if (current < (CURRENT_MINUTE - 60)) current += 60 * 24; + minutes = current; + + this->SetDirty(); + } +}; + /** Widgets for the date setting window. */ static const NWidgetPart _nested_set_date_widgets[] = { NWidget(NWID_HORIZONTAL), @@ -193,6 +309,26 @@ static const NWidgetPart _nested_set_date_widgets[] = { EndContainer() }; +static const NWidgetPart _nested_set_minutes_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_BROWN), + NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_DATE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_BROWN), + NWidget(NWID_VERTICAL), SetPIP(6, 6, 6), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(6, 6, 6), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_SD_MONTH), SetFill(1, 0), SetDataTip(STR_JUST_INT, STR_DATE_MINUTES_MONTH_TOOLTIP), + NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_SD_DAY), SetFill(1, 0), SetDataTip(STR_JUST_INT, STR_DATE_MINUTES_DAY_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SD_SET_DATE), SetMinimalSize(100, 12), SetDataTip(STR_DATE_SET_DATE, STR_DATE_SET_DATE_TOOLTIP), + NWidget(NWID_SPACER), SetFill(1, 0), + EndContainer(), + EndContainer(), + EndContainer() +}; + /** Description of the date setting window. */ static const WindowDesc _set_date_desc( WDP_CENTER, 0, 0, @@ -201,6 +337,13 @@ static const WindowDesc _set_date_desc( _nested_set_date_widgets, lengthof(_nested_set_date_widgets) ); +static const WindowDesc _set_minutes_desc( + WDP_CENTER, 0, 0, + WC_SET_DATE, WC_NONE, + 0, + _nested_set_minutes_widgets, lengthof(_nested_set_minutes_widgets) +); + /** * Create the new 'set date' window * @param window_number number for the window @@ -210,8 +353,13 @@ static const WindowDesc _set_date_desc( * @param max_year the maximum year (inclusive) to show in the year dropdown * @param callback the callback to call once a date has been selected */ -void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback) +void ShowSetDateWindow(Window *parent, int window_number, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback) { DeleteWindowByClass(WC_SET_DATE); - new SetDateWindow(&_set_date_desc, window_number, parent, initial_date, min_year, max_year, callback); + + if (!_settings_client.gui.time_in_minutes) { + new SetDateWindow(&_set_date_desc, window_number, parent, initial_date / DAY_TICKS, min_year, max_year, callback); + } else { + new SetMinutesWindow(&_set_minutes_desc, window_number, parent, initial_date + (_settings_client.gui.clock_offset * _settings_client.gui.ticks_per_minute), min_year, max_year, callback); + } } diff --git a/src/date_gui.h b/src/date_gui.h index 314baba3c..0c8dcba86 100644 --- a/src/date_gui.h +++ b/src/date_gui.h @@ -20,8 +20,8 @@ * @param w the window that sends the callback * @param date the date that has been chosen */ -typedef void SetDateCallback(const Window *w, Date date); +typedef void SetDateCallback(const Window *w, DateTicks date); -void ShowSetDateWindow(Window *parent, int window_number, Date initial_date, Year min_year, Year max_year, SetDateCallback *callback); +void ShowSetDateWindow(Window *parent, int window_number, DateTicks initial_date, Year min_year, Year max_year, SetDateCallback *callback); #endif /* DATE_GUI_H */ diff --git a/src/date_type.h b/src/date_type.h index a004413a7..120bab508 100644 --- a/src/date_type.h +++ b/src/date_type.h @@ -12,10 +12,11 @@ #ifndef DATE_TYPE_H #define DATE_TYPE_H - typedef int32 Date; ///< The type to store our dates in typedef uint16 DateFract; ///< The fraction of a date we're in, i.e. the number of ticks since the last date changeover typedef int32 Ticks; ///< The type to store ticks in +typedef int64 DateTicks; ///< The type to store dates in when tick-precision is required +typedef int32 Minutes; ///< The type to store minutes in typedef int32 Year; ///< Type for the year, note: 0 based, i.e. starts at the year 0. typedef uint8 Month; ///< Type for the month, note: 0 based, i.e. 0 = January, 11 = December. @@ -31,6 +32,8 @@ static const int DAY_TICKS = 74; ///< ticks per day static const int DAYS_IN_YEAR = 365; ///< days per year static const int DAYS_IN_LEAP_YEAR = 366; ///< sometimes, you need one day more... +#define DATE_UNIT_SIZE (_settings_client.gui.time_in_minutes ? _settings_client.gui.ticks_per_minute : DAY_TICKS) + static const int STATION_RATING_TICKS = 185; ///< cycle duration for updating station rating static const int STATION_ACCEPTANCE_TICKS = 250; ///< cycle duration for updating station acceptance static const int CARGO_AGING_TICKS = 185; ///< cycle duration for aging cargo @@ -38,7 +41,6 @@ static const int INDUSTRY_PRODUCE_TICKS = 256; ///< cycle duration for industr static const int TOWN_GROWTH_TICKS = 70; ///< cycle duration for towns trying to grow. (this originates from the size of the town array in TTD static const int INDUSTRY_CUT_TREE_TICKS = INDUSTRY_PRODUCE_TICKS * 2; ///< cycle duration for lumber mill's extra action - /* * ORIGINAL_BASE_YEAR, ORIGINAL_MAX_YEAR and DAYS_TILL_ORIGINAL_BASE_YEAR are * primarily used for loading newgrf and savegame data and returning some @@ -95,6 +97,21 @@ static const Year MAX_YEAR = 5000000; /** The number of days till the last day */ #define MAX_DAY (DAYS_TILL(MAX_YEAR + 1) - 1) +/** The day when converting to minutes */ +#define MINUTES_DAY(minutes) (minutes / 1440) + +/** The hour when converting to minutes */ +#define MINUTES_HOUR(minutes) ((minutes / 60) % 24) + +/** The day when converting to minutes */ +#define MINUTES_MINUTE(minutes) (minutes % 60) + +/** Convert minutes to a date */ +#define MINUTES_DATE(day, hour, minute) ((day * 1440) + (hour * 60) + minute) + +/** Get the current date in minutes */ +#define CURRENT_MINUTE ((((DateTicks)_date * DAY_TICKS) + _date_fract) / _settings_client.gui.ticks_per_minute) + /** * Data structure to convert between Date and triplet (year, month, and day). * @see ConvertDateToYMD(), ConvertYMDToDate() diff --git a/src/debug.cpp b/src/debug.cpp index e6fb61292..02afa9928 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; #ifdef RANDOM_DEBUG int _debug_random_level; #endif @@ -67,6 +68,7 @@ struct DebugLevel { DEBUG_LEVEL(gamelog), DEBUG_LEVEL(desync), DEBUG_LEVEL(console), + DEBUG_LEVEL(cargodest), #ifdef RANDOM_DEBUG DEBUG_LEVEL(random), #endif diff --git a/src/debug.h b/src/debug.h index f7e771719..630b1c98f 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; #ifdef RANDOM_DEBUG extern int _debug_random_level; #endif diff --git a/src/departures.cpp b/src/departures.cpp new file mode 100644 index 000000000..9edefa3e0 --- /dev/null +++ b/src/departures.cpp @@ -0,0 +1,646 @@ +/* $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 departures.cpp Scheduled departures from a station. */ + +#include "stdafx.h" +#include "debug.h" +#include "gui.h" +#include "textbuf_gui.h" +#include "strings_func.h" +#include "window_func.h" +#include "vehicle_func.h" +#include "string_func.h" +#include "window_gui.h" +#include "timetable.h" +#include "vehiclelist.h" +#include "company_base.h" +#include "date_func.h" +#include "departures_gui.h" +#include "station_base.h" +#include "vehicle_gui_base.h" +#include "vehicle_base.h" +#include "vehicle_gui.h" +#include "order_base.h" +#include "settings_type.h" +#include "core/smallvec_type.hpp" +#include "date_type.h" +#include "company_type.h" +#include "cargo_type.h" +#include "departures_func.h" +#include "departures_type.h" + +/** A scheduled order. */ +typedef struct OrderDate +{ + const Order *order; ///< The order + const Vehicle *v; ///< The vehicle carrying out the order + DateTicks expected_date;///< The date on which the order is expected to complete + Ticks lateness; ///< How late this order is expected to finish + DepartureStatus status; ///< Whether the vehicle has arrived to carry out the order yet +} OrderDate; + +static bool IsDeparture(const Order *order, StationID station) { + return (order->GetType() == OT_GOTO_STATION && + (StationID)order->GetDestination() == station && + (order->GetLoadType() != OLFB_NO_LOAD || + _settings_client.gui.departure_show_all_stops) && + order->wait_time != 0); +} + +static bool IsVia(const Order *order, StationID station) { + return ((order->GetType() == OT_GOTO_STATION || + order->GetType() == OT_GOTO_WAYPOINT) && + (StationID)order->GetDestination() == station && + (order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION || + order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION)); +} + +static bool IsArrival(const Order *order, StationID station) { + return (order->GetType() == OT_GOTO_STATION && + (StationID)order->GetDestination() == station && + (order->GetUnloadType() != OUFB_NO_UNLOAD || + _settings_client.gui.departure_show_all_stops) && + order->wait_time != 0); +} + +/** + * Compute an up-to-date list of departures for a station. + * @param station the station to compute the departures of + * @param show_vehicle_types the types of vehicles to include in the departure list + * @param type the type of departures to get (departures or arrivals) + * @param show_vehicles_via whether to include vehicles that have this station in their orders but do not stop at it + * @return a list of departures, which is empty if an error occurred + */ +DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], DepartureType type, bool show_vehicles_via) +{ + /* This function is the meat of the departure boards functionality. */ + /* As an overview, it works by repeatedly considering the best possible next departure to show. */ + /* By best possible we mean the one expected to arrive at the station first. */ + /* However, we do not consider departures whose scheduled time is too far in the future, even if they are expected before some delayed ones. */ + /* This code can probably be made more efficient. I haven't done so in order to keep both its (relative) simplicity and my (relative) sanity. */ + /* Having written that, it's not exactly slow at the moment. */ + + /* The list of departures which will be returned as a result. */ + SmallVector *result = new SmallVector(); + + /* A list of the next scheduled orders to be considered for inclusion in the departure list. */ + SmallVector next_orders; + + /* The maximum possible date for departures to be scheduled to occur. */ + DateTicks max_date = _settings_client.gui.max_departure_time * DAY_TICKS; + + /* The scheduled order in next_orders with the earliest expected_date field. */ + OrderDate *least_order = NULL; + + /* Get all the vehicles stopping at this station. */ + /* We do this to get the order which is the first time they will stop at this station. */ + /* This order is stored along with some more information. */ + /* We keep a pointer to the `least' order (the one with the soonest expected completion time). */ + for (uint i = 0; i < 4; ++i) { + VehicleList vehicles; + + if (!show_vehicle_types[i]) { + /* Don't show vehicles whose type we're not interested in. */ + continue; + } + + /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */ + if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) { + /* Something went wrong: panic! */ + return result; + } + + /* Get the first order for each vehicle for the station we're interested in that doesn't have No Loading set. */ + /* We find the least order while we're at it. */ + for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) { + if (_settings_client.gui.departure_only_passengers) { + bool carries_passengers = false; + + const Vehicle *u = *v; + while (u != NULL) { + if (u->cargo_type == CT_PASSENGERS && u->cargo_cap > 0) { + carries_passengers = true; + break; + } + u = u->Next(); + } + + if (carries_passengers == false) { + continue; + } + } + + const Order *order = (*v)->GetOrder((*v)->cur_implicit_order_index % (*v)->GetNumOrders()); + DateTicks start_date = (DateTicks)_date_fract - (*v)->current_order_time; + DepartureStatus status = D_TRAVELLING; + + /* If the vehicle is stopped in a depot, ignore it. */ + if ((*v)->IsStoppedInDepot()) { + continue; + } + + /* If the vehicle is heading for a depot to stop there, then its departures are cancelled. */ + if ((*v)->current_order.IsType(OT_GOTO_DEPOT) && (*v)->current_order.GetDepotActionType() & ODATFB_HALT) { + status = D_CANCELLED; + } + + if ((*v)->current_order.IsType(OT_LOADING)) { + /* Account for the vehicle having reached the current order and being in the loading phase. */ + status = D_ARRIVED; + start_date -= order->travel_time + (((*v)->lateness_counter < 0) ? (*v)->lateness_counter : 0); + } + + /* Loop through the vehicle's orders until we've found a suitable order or we've determined that no such order exists. */ + /* We only need to consider each order at most once. */ + bool foundOrder = false; + for (int i = (*v)->GetNumOrders(); i > 0; --i) { + start_date += order->travel_time + order->wait_time; + + /* If the scheduled departure date is too far in the future, stop. */ + if (start_date - (*v)->lateness_counter > max_date) { + break; + } + + /* If the order is a conditional branch, handle it. */ + if (order->IsType(OT_CONDITIONAL)) { + switch(_settings_client.gui.departure_conditionals) { + case 0: { + /* Give up */ + break; + } + case 1: { + /* Take the branch */ + if (status != D_CANCELLED) { + status = D_TRAVELLING; + } + order = (*v)->GetOrder(order->GetConditionSkipToOrder()); + if (order == NULL) { + break; + } + + start_date -= order->travel_time; + + continue; + } + case 2: { + /* Do not take the branch */ + if (status != D_CANCELLED) { + status = D_TRAVELLING; + } + order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next; + continue; + } + } + } + + /* Skip it if it's an automatic order. */ + if (order->IsType(OT_IMPLICIT)) { + order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next; + continue; + } + + /* If an order doesn't have a travel time set, then stop. */ + if (order->travel_time == 0) { + break; + } + + /* If the vehicle will be stopping at and loading from this station, and its wait time is not zero, then it is a departure. */ + /* If the vehicle will be stopping at and unloading at this station, and its wait time is not zero, then it is an arrival. */ + if ((type == D_DEPARTURE && IsDeparture(order, station)) || + (type == D_DEPARTURE && show_vehicles_via && IsVia(order, station)) || + (type == D_ARRIVAL && IsArrival(order, station))) { + /* If the departure was scheduled to have already begun and has been cancelled, do not show it. */ + if (start_date < 0 && status == D_CANCELLED) { + break; + } + + OrderDate *od = new OrderDate(); + od->order = order; + od->v = *v; + /* We store the expected date for now, so that vehicles will be shown in order of expected time. */ + od->expected_date = start_date; + od->lateness = (*v)->lateness_counter > 0 ? (*v)->lateness_counter : 0; + od->status = status; + + /* If we are early, use the scheduled date as the expected date. We also take lateness to be zero. */ + if ((*v)->lateness_counter < 0 && !(*v)->current_order.IsType(OT_LOADING)) { + od->expected_date -= (*v)->lateness_counter; + } + + /* Update least_order if this is the current least order. */ + if (least_order == NULL) { + least_order = od; + } else if (least_order->expected_date - least_order->lateness - (type == D_ARRIVAL ? least_order->order->wait_time : 0) > od->expected_date - od->lateness - (type == D_ARRIVAL ? od->order->wait_time : 0)) { + least_order = od; + } + + *(next_orders.Append(1)) = od; + + foundOrder = true; + /* We're done with this vehicle. */ + break; + } else { + /* Go to the next order in the list. */ + if (status != D_CANCELLED) { + status = D_TRAVELLING; + } + order = (order->next == NULL) ? (*v)->GetFirstOrder() : order->next; + } + } + } + } + + /* No suitable orders found? Then stop. */ + if (next_orders.Length() == 0) { + return result; + } + + /* We now find as many departures as we can. It's a little involved so I'll try to explain each major step. */ + /* The countdown from 10000 is a safeguard just in case something nasty happens. 10000 seemed large enough. */ + for(int i = 10000; i > 0; --i) { + /* I should probably try to convince you that this loop always terminates regardless of the safeguard. */ + /* 1. next_orders contains at least one element. */ + /* 2. The loop terminates if result->Length() exceeds a fixed (for this loop) value, or if the least order's scheduled date is later than max_date. */ + /* (We ignore the case that the least order's scheduled date has overflown, as it is a relative rather than absolute date.) */ + /* 3. Every time we loop round, either result->Length() will have increased -OR- we will have increased the expected_date of one of the elements of next_orders. */ + /* 4. Therefore the loop must eventually terminate. */ + + /* least_order is the best candidate for the next departure. */ + + /* First, we check if we can stop looking for departures yet. */ + if (result->Length() >= _settings_client.gui.max_departures || + least_order->expected_date - least_order->lateness > max_date) { + break; + } + + /* We already know the least order and that it's a suitable departure, so make it into a departure. */ + Departure *d = new Departure(); + d->scheduled_date = (DateTicks)_date * DAY_TICKS + least_order->expected_date - least_order->lateness; + d->lateness = least_order->lateness; + d->status = least_order->status; + d->vehicle = least_order->v; + d->type = type; + d->order = least_order->order; + + /* We'll be going through the order list later, so we need a separate variable for it. */ + const Order *order = least_order->order; + + if (type == D_DEPARTURE) { + /* Computing departures: */ + /* We want to find out where it will terminate, making a list of the stations it calls at along the way. */ + /* We only count stations where unloading happens as being called at - i.e. pickup-only stations are ignored. */ + /* Where the vehicle terminates is defined as the last unique station called at by the vehicle from the current order. */ + + /* If the vehicle loops round to the current order without a terminus being found, then it terminates upon reaching its current order again. */ + + /* We also determine which station this departure is going via, if any. */ + /* A departure goes via a station if it is the first station for which the vehicle has an order to go via or non-stop via. */ + /* Multiple departures on the same journey may go via different stations. That a departure can go via at most one station is intentional. */ + + /* We keep track of potential via stations along the way. If we call at a station immediately after going via it, then it is the via station. */ + StationID candidate_via = INVALID_STATION; + + /* Go through the order list, looping if necessary, to find a terminus. */ + /* Get the next order, which may be the vehicle's first order. */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + /* We only need to consider each order at most once. */ + bool found_terminus = false; + CallAt c = CallAt((StationID)order->GetDestination(), d->scheduled_date); + for (int i = least_order->v->GetNumOrders(); i > 0; --i) { + /* If we reach the order at which the departure occurs again, then use the departure station as the terminus. */ + if (order == least_order->order) { + /* If we're not calling anywhere, then skip this departure. */ + found_terminus = (d->calling_at.Length() > 0); + break; + } + + /* If the order is a conditional branch, handle it. */ + if (order->IsType(OT_CONDITIONAL)) { + switch(_settings_client.gui.departure_conditionals) { + case 0: { + /* Give up */ + break; + } + case 1: { + /* Take the branch */ + order = least_order->v->GetOrder(order->GetConditionSkipToOrder()); + if (order == NULL) { + break; + } + continue; + } + case 2: { + /* Do not take the branch */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + continue; + } + } + } + + /* If we reach the original station again, then use it as the terminus. */ + if (order->GetType() == OT_GOTO_STATION && + (StationID)order->GetDestination() == station && + (order->GetUnloadType() != OUFB_NO_UNLOAD || + _settings_client.gui.departure_show_all_stops) && + order->GetNonStopType() != ONSF_NO_STOP_AT_ANY_STATION && + order->GetNonStopType() != ONSF_NO_STOP_AT_DESTINATION_STATION) { + /* If we're not calling anywhere, then skip this departure. */ + found_terminus = (d->calling_at.Length() > 0); + break; + } + + /* Check if we're going via this station. */ + if ((order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION || + order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION) && + order->GetType() == OT_GOTO_STATION && + d->via == INVALID_STATION) { + candidate_via = (StationID)order->GetDestination(); + } + + if (c.scheduled_date != 0 && order->travel_time != 0) { + c.scheduled_date += order->travel_time; + } else { + c.scheduled_date = 0; + } + + c.station = (StationID)order->GetDestination(); + + /* We're not interested in this order any further if we're not calling at it. */ + if ((order->GetUnloadType() == OUFB_NO_UNLOAD && + !_settings_client.gui.departure_show_all_stops) || + (order->GetType() != OT_GOTO_STATION && + order->GetType() != OT_IMPLICIT) || + order->GetNonStopType() == ONSF_NO_STOP_AT_ANY_STATION || + order->GetNonStopType() == ONSF_NO_STOP_AT_DESTINATION_STATION) { + c.scheduled_date += order->wait_time; + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + continue; + } + + /* If this order's station is already in the calling, then the previous called at station is the terminus. */ + if (d->calling_at.Contains(c)) { + found_terminus = true; + break; + } + + /* If appropriate, add the station to the calling at list and make it the candidate terminus. */ + if ((order->GetType() == OT_GOTO_STATION || + order->GetType() == OT_IMPLICIT) && + order->GetNonStopType() != ONSF_NO_STOP_AT_ANY_STATION && + order->GetNonStopType() != ONSF_NO_STOP_AT_DESTINATION_STATION) { + if (d->via == INVALID_STATION && candidate_via == (StationID)order->GetDestination()) { + d->via = (StationID)order->GetDestination(); + } + d->terminus = c; + *(d->calling_at.Append(1)) = c; + } + + /* If we unload all at this station, then it is the terminus. */ + if (order->GetType() == OT_GOTO_STATION && + order->GetUnloadType() == OUFB_UNLOAD) { + if (d->calling_at.Length() > 0) { + found_terminus = true; + } + break; + } + + c.scheduled_date += order->wait_time; + + /* Get the next order, which may be the vehicle's first order. */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + } + + if (found_terminus) { + /* Add the departure to the result list. */ + bool duplicate = false; + + if (_settings_client.gui.departure_merge_identical) { + for (uint i = 0; i < result->Length(); ++i) { + if (*d == **(result->Get(i))) { + duplicate = true; + break; + } + } + } + + if (!duplicate) { + *(result->Append(1)) = d; + + if (_settings_client.gui.departure_smart_terminus && type == D_DEPARTURE) { + for (uint i = 0; i < result->Length()-1; ++i) { + Departure *d_first = *(result->Get(i)); + uint k = d_first->calling_at.Length()-2; + for (uint j = d->calling_at.Length(); j > 0; --j) { + CallAt c = CallAt(*(d->calling_at.Get(j-1))); + + if (d_first->terminus >= c && d_first->calling_at.Length() >= 2) { + d_first->terminus = CallAt(*(d_first->calling_at.Get(k))); + + if (k == 0) break; + + k--; + } + } + } + } + + /* If the vehicle is expected to be late, we want to know what time it will arrive rather than depart. */ + /* This is done because it looked silly to me to have a vehicle not be expected for another few days, yet it be at the same time pulling into the station. */ + if (d->status != D_ARRIVED && + d->lateness > 0) { + d->lateness -= least_order->order->wait_time; + } + } + } + } else { + /* Computing arrivals: */ + /* First we need to find the origin of the order. This is somewhat like finding a terminus, but a little more involved since order lists are singly linked. */ + /* The next stage is simpler. We just need to add all the stations called at on the way to the current station. */ + /* Again, we define a station as being called at if the vehicle loads from it. */ + + /* However, the very first thing we do is use the arrival time as the scheduled time instead of the departure time. */ + d->scheduled_date -= order->wait_time; + + const Order *candidate_origin = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + bool found_origin = false; + + while (candidate_origin != least_order->order) { + if ((candidate_origin->GetLoadType() != OLFB_NO_LOAD || + _settings_client.gui.departure_show_all_stops) && + (candidate_origin->GetType() == OT_GOTO_STATION || + candidate_origin->GetType() == OT_IMPLICIT) && + candidate_origin->GetDestination() != station) { + const Order *o = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next; + bool found_collision = false; + + /* Check if the candidate origin's destination appears again before the original order or the station does. */ + while (o != least_order->order) { + if (o->GetUnloadType() == OUFB_UNLOAD) { + found_collision = true; + break; + } + + if ((o->GetType() == OT_GOTO_STATION || + o->GetType() == OT_IMPLICIT) && + (o->GetDestination() == candidate_origin->GetDestination() || + o->GetDestination() == station)) { + found_collision = true; + break; + } + + o = (o->next == NULL) ? least_order->v->GetFirstOrder() : o->next; + } + + /* If it doesn't, then we have found the origin. */ + if (!found_collision) { + found_origin = true; + break; + } + } + + candidate_origin = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next; + } + + order = (candidate_origin->next == NULL) ? least_order->v->GetFirstOrder() : candidate_origin->next; + + while (order != least_order->order) { + if (order->GetType() == OT_GOTO_STATION && + (order->GetLoadType() != OLFB_NO_LOAD || + _settings_client.gui.departure_show_all_stops)) { + *(d->calling_at.Append(1)) = CallAt((StationID)order->GetDestination()); + } + + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + } + + d->terminus = CallAt((StationID)candidate_origin->GetDestination()); + + if (found_origin) { + bool duplicate = false; + + if (_settings_client.gui.departure_merge_identical) { + for (uint i = 0; i < result->Length(); ++i) { + if (*d == **(result->Get(i))) { + duplicate = true; + break; + } + } + } + + if (!duplicate) { + *(result->Append(1)) = d; + } + } + } + + /* Save on pointer dereferences in the coming loop. */ + order = least_order->order; + + /* Now we find the next suitable order for being a departure for this vehicle. */ + /* We do this in a similar way to finding the first suitable order for the vehicle. */ + + /* Go to the next order so we don't add the current order again. */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + least_order->expected_date += order->travel_time + order->wait_time; + + /* Go through the order list to find the next candidate departure. */ + /* We only need to consider each order at most once. */ + bool found_next_order = false; + for (int i = least_order->v->GetNumOrders(); i > 0; --i) { + /* If the order is a conditional branch, handle it. */ + if (order->IsType(OT_CONDITIONAL)) { + switch(_settings_client.gui.departure_conditionals) { + case 0: { + /* Give up */ + break; + } + case 1: { + /* Take the branch */ + order = least_order->v->GetOrder(order->GetConditionSkipToOrder()); + if (order == NULL) { + break; + } + + least_order->expected_date += order->wait_time; + + continue; + } + case 2: { + /* Do not take the branch */ + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + least_order->expected_date += order->travel_time + order->wait_time; + continue; + } + } + } + + /* Skip it if it's an automatic order. */ + if (order->IsType(OT_IMPLICIT)) { + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + continue; + } + + /* If an order doesn't have a travel time set, then stop. */ + if (order->travel_time == 0) { + break; + } + + /* If the departure is scheduled to be too late, then stop. */ + if (least_order->expected_date - least_order->lateness > max_date) { + break; + } + + /* If the order loads from this station (or unloads if we're computing arrivals) and has a wait time set, then it is suitable for being a departure. */ + if ((type == D_DEPARTURE && IsDeparture(order, station)) || + (type == D_DEPARTURE && show_vehicles_via && IsVia(order, station)) || + (type == D_ARRIVAL && IsArrival(order, station))) { + least_order->order = order; + found_next_order = true; + break; + } + + order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; + least_order->expected_date += order->travel_time + order->wait_time; + } + + /* If we didn't find a suitable order for being a departure, then we can ignore this vehicle from now on. */ + if (!found_next_order) { + /* Make sure we don't try to get departures out of this order. */ + /* This is cheaper than deleting it from next_orders. */ + /* If we ever get to a state where _date * DAY_TICKS is close to INT_MAX, then we'll have other problems anyway as departures' scheduled dates will wrap around. */ + least_order->expected_date = INT64_MAX; + } + + /* The vehicle can't possibly have arrived at its next candidate departure yet. */ + if (least_order->status == D_ARRIVED) { + least_order->status = D_TRAVELLING; + } + + /* Find the new least order. */ + for (uint i = 0; i < next_orders.Length(); ++i) { + OrderDate *od = *(next_orders.Get(i)); + + DateTicks lod = least_order->expected_date - least_order->lateness; + DateTicks odd = od->expected_date - od->lateness; + + if (type == D_ARRIVAL) { + lod -= least_order->order->wait_time; + odd -= od->order->wait_time; + } + + if (lod > odd && od->expected_date - od->lateness < max_date) { + least_order = od; + } + } + } + + /* Done. Phew! */ + return result; +} diff --git a/src/departures_func.h b/src/departures_func.h new file mode 100644 index 000000000..a5fb42bb9 --- /dev/null +++ b/src/departures_func.h @@ -0,0 +1,21 @@ +/* $Id: departures_func.h $ */ + +/* + * 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 departures_func.h Functions related to departures. */ + +#ifndef DEPARTURES_FUNC_H +#define DEPARTURES_FUNC_H + +#include "station_base.h" +#include "core/smallvec_type.hpp" +#include "departures_type.h" + +DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[4], DepartureType type = D_DEPARTURE, bool show_vehicles_via = false); + +#endif /* DEPARTURES_FUNC_H */ diff --git a/src/departures_gui.cpp b/src/departures_gui.cpp new file mode 100644 index 000000000..6ccc29f89 --- /dev/null +++ b/src/departures_gui.cpp @@ -0,0 +1,856 @@ +/* $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 departures_gui.cpp Scheduled departures from a station. */ + +#include "stdafx.h" +#include "debug.h" +#include "gui.h" +#include "textbuf_gui.h" +#include "strings_func.h" +#include "window_func.h" +#include "vehicle_func.h" +#include "string_func.h" +#include "window_gui.h" +#include "timetable.h" +#include "vehiclelist.h" +#include "company_base.h" +#include "date_func.h" +#include "departures_gui.h" +#include "station_base.h" +#include "vehicle_gui_base.h" +#include "vehicle_base.h" +#include "vehicle_gui.h" +#include "order_base.h" +#include "settings_type.h" +#include "core/smallvec_type.hpp" +#include "date_type.h" +#include "company_type.h" +#include "departures_func.h" +#include "cargotype.h" + +#include "table/sprites.h" +#include "table/strings.h" + +static const NWidgetPart _nested_departures_list[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_DB_CAPTION), SetDataTip(STR_DEPARTURES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_DB_LIST), SetMinimalSize(0, 0), SetFill(1, 0), SetResize(1, 1), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_DB_SCROLLBAR), + EndContainer(), + + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ARRS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_ARRIVALS, STR_DEPARTURES_ARRIVALS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_DEPS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_DEPARTURES, STR_DEPARTURES_DEPARTURES_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_VIA), SetMinimalSize(11, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_VIA_BUTTON, STR_DEPARTURES_VIA_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_SHIPS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_PLANES), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +static WindowDesc _departures_desc( + WDP_AUTO, 260, 246, + WC_DEPARTURES_BOARD, WC_NONE, + 0, + _nested_departures_list, lengthof(_nested_departures_list) +); + +static uint cached_date_width = 0; ///< The cached maximum width required to display a date. +static uint cached_status_width = 0; ///< The cached maximum width required to show the status field. +static uint cached_date_arrow_width = 0; ///< The cached width of the red/green arrows that may be displayed alongside times. +static bool cached_date_display_method; ///< Whether the above cached values refers to original (d,m,y) dates or the 24h clock. +static bool cached_arr_dep_display_method; ///< Whether to show departures and arrivals on a single line. + +template +struct DeparturesWindow : public Window { +protected: + StationID station; ///< The station whose departures we're showing. + DepartureList *departures; ///< The current list of departures from this station. + DepartureList *arrivals; ///< The current list of arrivals from this station. + uint entry_height; ///< The height of an entry in the departures list. + uint tick_count; ///< The number of ticks that have elapsed since the window was created. Used for scrolling text. + int calc_tick_countdown; ///< The number of ticks to wait until recomputing the departure list. Signed in case it goes below zero. + bool show_types[4]; ///< The vehicle types to show in the departure list. + bool departure_types[3]; ///< The types of departure to show in the departure list. + uint min_width; ///< The minimum width of this window. + Scrollbar *vscroll; + + virtual uint GetMinWidth() const; + static void RecomputeDateWidth(); + virtual void DrawDeparturesListItems(const Rect &r) const; + void DeleteDeparturesList(DepartureList* list); +public: + + DeparturesWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), + station(window_number), + departures(new DepartureList()), + arrivals(new DepartureList()), + entry_height(1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1), + tick_count(0), + calc_tick_countdown(0), + min_width(400) + { + this->CreateNestedTree(desc); + this->vscroll = this->GetScrollbar(WID_DB_SCROLLBAR); + this->FinishInitNested(desc, window_number); + + /* By default, only show departures. */ + departure_types[0] = true; + departure_types[1] = false; + departure_types[2] = false; + this->LowerWidget(WID_DB_SHOW_DEPS); + this->RaiseWidget(WID_DB_SHOW_ARRS); + this->RaiseWidget(WID_DB_SHOW_VIA); + + for (uint i = 0; i < 4; ++i) { + show_types[i] = true; + this->LowerWidget(WID_DB_SHOW_TRAINS + i); + } + + if (Twaypoint) { + this->GetWidget(WID_DB_CAPTION)->SetDataTip(STR_DEPARTURES_CAPTION_WAYPOINT, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS); + + for (uint i = 0; i < 4; ++i) { + this->DisableWidget(WID_DB_SHOW_TRAINS + i); + } + + this->DisableWidget(WID_DB_SHOW_ARRS); + this->DisableWidget(WID_DB_SHOW_DEPS); + this->DisableWidget(WID_DB_SHOW_VIA); + + departure_types[2] = true; + + this->LowerWidget(WID_DB_SHOW_VIA); + } + } + + virtual ~DeparturesWindow() + { + this->DeleteDeparturesList(departures); + this->DeleteDeparturesList(this->arrivals); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case WID_DB_LIST: + resize->height = DeparturesWindow::entry_height; + size->height = 2 * resize->height; + break; + } + } + + virtual void SetStringParameters(int widget) const + { + if (widget == WID_DB_CAPTION) { + const Station *st = Station::Get(this->station); + SetDParam(0, st->index); + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_DB_SHOW_TRAINS: // Show trains to this station + case WID_DB_SHOW_ROADVEHS: // Show road vehicles to this station + case WID_DB_SHOW_SHIPS: // Show ships to this station + case WID_DB_SHOW_PLANES: // Show aircraft to this station + this->show_types[widget - WID_DB_SHOW_TRAINS] = !this->show_types[widget - WID_DB_SHOW_TRAINS]; + if (this->show_types[widget - WID_DB_SHOW_TRAINS]) { + this->LowerWidget(widget); + } else { + this->RaiseWidget(widget); + } + /* We need to recompute the departures list. */ + this->calc_tick_countdown = 0; + /* We need to redraw the button that was pressed. */ + this->SetWidgetDirty(widget); + break; + + case WID_DB_SHOW_DEPS: + case WID_DB_SHOW_ARRS: + if (_settings_client.gui.departure_show_both) break; + /* FALL THROUGH */ + + case WID_DB_SHOW_VIA: + + this->departure_types[widget - WID_DB_SHOW_DEPS] = !this->departure_types[widget - WID_DB_SHOW_DEPS]; + if (this->departure_types[widget - WID_DB_SHOW_DEPS]) { + this->LowerWidget(widget); + } else { + this->RaiseWidget(widget); + } + + if (!this->departure_types[0]) { + this->RaiseWidget(WID_DB_SHOW_VIA); + this->DisableWidget(WID_DB_SHOW_VIA); + } else { + this->EnableWidget(WID_DB_SHOW_VIA); + + if (this->departure_types[2]) { + this->LowerWidget(WID_DB_SHOW_VIA); + } + } + /* We need to recompute the departures list. */ + this->calc_tick_countdown = 0; + /* We need to redraw the button that was pressed. */ + this->SetWidgetDirty(widget); + break; + + case WID_DB_LIST: // Matrix to show departures + /* We need to find the departure corresponding to where the user clicked. */ + uint32 id_v = (pt.y - this->GetWidget(WID_DB_LIST)->pos_y) / this->entry_height; + + if (id_v >= this->vscroll->GetCapacity()) return; // click out of bounds + + id_v += this->vscroll->GetPosition(); + + if (id_v >= (this->departures->Length() + this->arrivals->Length())) return; // click out of list bound + + uint departure = 0; + uint arrival = 0; + + /* Draw each departure. */ + for (uint i = 0; i <= id_v; ++i) { + const Departure *d; + + if (arrival == this->arrivals->Length()) { + d = (*(this->departures))[departure++]; + } else if (departure == this->departures->Length()) { + d = (*(this->arrivals))[arrival++]; + } else { + d = (*(this->departures))[departure]; + const Departure *a = (*(this->arrivals))[arrival]; + + if (a->scheduled_date < d->scheduled_date) { + d = a; + arrival++; + } else { + departure++; + } + } + + if (i == id_v) { + ShowVehicleViewWindow(d->vehicle); + break; + } + } + + break; + } + } + + virtual void OnTick() + { + if (_pause_mode == PM_UNPAUSED) { + this->tick_count += 1; + this->calc_tick_countdown -= 1; + } + + /* Recompute the minimum date display width if the cached one is no longer valid. */ + if (cached_date_width == 0 || + _settings_client.gui.time_in_minutes != cached_date_display_method || + _settings_client.gui.departure_show_both != cached_arr_dep_display_method) { + this->RecomputeDateWidth(); + } + + /* We need to redraw the scrolling text in its new position. */ + this->SetWidgetDirty(WID_DB_LIST); + + /* Recompute the list of departures if we're due to. */ + if (this->calc_tick_countdown <= 0) { + this->calc_tick_countdown = _settings_client.gui.departure_calc_frequency; + this->DeleteDeparturesList(this->departures); + this->DeleteDeparturesList(this->arrivals); + this->departures = (this->departure_types[0] ? MakeDepartureList(this->station, this->show_types, D_DEPARTURE, Twaypoint || this->departure_types[2]) : new DepartureList()); + this->arrivals = (this->departure_types[1] && !_settings_client.gui.departure_show_both ? MakeDepartureList(this->station, this->show_types, D_ARRIVAL ) : new DepartureList()); + this->SetWidgetDirty(WID_DB_LIST); + } + + uint new_width = this->GetMinWidth(); + + if (new_width != this->min_width) { + NWidgetCore *n = this->GetWidget(WID_DB_LIST); + n->SetMinimalSize(new_width, 0); + this->ReInit(); + this->min_width = new_width; + } + + uint new_height = 1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1; + + if (new_height != this->entry_height) { + this->entry_height = new_height; + this->SetWidgetDirty(WID_DB_LIST); + this->ReInit(); + } + } + + virtual void OnPaint() + { + if (Twaypoint || _settings_client.gui.departure_show_both) { + this->DisableWidget(WID_DB_SHOW_ARRS); + this->DisableWidget(WID_DB_SHOW_DEPS); + } else { + this->EnableWidget(WID_DB_SHOW_ARRS); + this->EnableWidget(WID_DB_SHOW_DEPS); + } + + this->vscroll->SetCount(min(_settings_client.gui.max_departures, this->departures->Length() + this->arrivals->Length())); + this->DrawWidgets(); + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (widget) { + case WID_DB_LIST: + this->DrawDeparturesListItems(r); + break; + } + } + + virtual void OnResize() + { + this->vscroll->SetCapacityFromWidget(this, WID_DB_LIST); + this->GetWidget(WID_DB_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + } +}; + +/** + * Shows a window of scheduled departures for a station. + * @param station the station to show a departures window for + */ +void ShowStationDepartures(StationID station) +{ + AllocateWindowDescFront >(&_departures_desc, station); +} + +/** + * Shows a window of scheduled departures for a station. + * @param station the station to show a departures window for + */ +void ShowWaypointDepartures(StationID waypoint) +{ + AllocateWindowDescFront >(&_departures_desc, waypoint); +} + +template +void DeparturesWindow::RecomputeDateWidth() +{ + cached_date_width = 0; + cached_status_width = 0; + cached_date_display_method = _settings_client.gui.time_in_minutes; + cached_arr_dep_display_method = _settings_client.gui.departure_show_both; + + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_ON_TIME)).width, cached_status_width); + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_DELAYED)).width, cached_status_width); + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_CANCELLED)).width, cached_status_width); + + uint interval = cached_date_display_method ? _settings_client.gui.ticks_per_minute : DAY_TICKS; + uint count = cached_date_display_method ? 24*60 : 365; + + for (uint i = 0; i < count; ++i) { + SetDParam(0, INT_MAX - (i*interval)); + SetDParam(1, INT_MAX - (i*interval)); + cached_date_width = max(GetStringBoundingBox(cached_arr_dep_display_method ? STR_DEPARTURES_TIME_BOTH : STR_DEPARTURES_TIME_DEP).width, cached_date_width); + cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_EXPECTED)).width, cached_status_width); + } + + SetDParam(0, 0); + cached_date_arrow_width = GetStringBoundingBox(STR_DEPARTURES_TIME_DEP).width - GetStringBoundingBox(STR_DEPARTURES_TIME).width; + + if (!_settings_client.gui.departure_show_both) { + cached_date_width -= cached_date_arrow_width; + } +} + +template +uint DeparturesWindow::GetMinWidth() const +{ + uint result = 0; + + /* Time */ + result = cached_date_width; + + /* Vehicle type icon */ + result += _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0; + + /* Status */ + result += cached_status_width; + + /* Find the maximum company name width. */ + int toc_width = 0; + + /* Find the maximum company name width. */ + int group_width = 0; + + /* Find the maximum vehicle name width. */ + int veh_width = 0; + + if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) { + for (uint i = 0; i < 4; ++i) { + VehicleList vehicles; + + /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */ + if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) { + /* Something went wrong: panic! */ + continue; + } + + for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) { + SetDParam(0, (uint64)((*v)->index)); + int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width; + if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width; + + if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) { + SetDParam(0, (uint64)((*v)->group_id)); + width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width; + if (_settings_client.gui.departure_show_group && width > group_width) group_width = width; + } + + SetDParam(0, (uint64)((*v)->owner)); + width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width; + if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width; + } + } + } + + result += toc_width + veh_width + group_width; + + return result + 140; +} + +/** + * Deletes this window's departure list. + */ +template +void DeparturesWindow::DeleteDeparturesList(DepartureList *list) +{ + /* SmallVector uses free rather than delete on its contents (which doesn't invoke the destructor), so we need to delete each departure manually. */ + for (uint i = 0; i < list->Length(); ++i) { + Departure **d = list->Get(i); + delete *d; + /* Make sure a double free doesn't happen. */ + *d = NULL; + } + list->Reset(); + delete list; + list = NULL; +} + +/** + * Draws a list of departures. + */ +template +void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const +{ + int left = r.left + WD_MATRIX_LEFT; + int right = r.right - WD_MATRIX_RIGHT; + + bool rtl = _current_text_dir == TD_RTL; + bool ltr = !rtl; + + int text_offset = WD_FRAMERECT_RIGHT; + int text_left = left + (rtl ? 0 : text_offset); + int text_right = right - (rtl ? text_offset : 0); + + int y = r.top + 1; + uint max_departures = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->departures->Length() + this->arrivals->Length()); + + if (max_departures > _settings_client.gui.max_departures) { + max_departures = _settings_client.gui.max_departures; + } + + byte small_font_size = _settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL; + + /* Draw the black background. */ + GfxFillRect(r.left + 1, r.top, r.right - 1, r.bottom, PC_BLACK); + + /* Nothing selected? Then display the information text. */ + bool none_selected[2] = {true, true}; + for (uint i = 0; i < 4; ++i) + { + if (this->show_types[i]) { + none_selected[0] = false; + break; + } + } + + for (uint i = 0; i < 2; ++i) + { + if (this->departure_types[i]) { + none_selected[1] = false; + break; + } + } + + if (none_selected[0] || none_selected[1]) { + DrawString(text_left, text_right, y + 1, STR_DEPARTURES_NONE_SELECTED); + return; + } + + /* No scheduled departures? Then display the information text. */ + if (max_departures == 0) { + DrawString(text_left, text_right, y + 1, STR_DEPARTURES_EMPTY); + return; + } + + /* Find the maximum possible width of the departure time and "Expt