summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/aircraft_cmd.cpp11
-rw-r--r--src/base_consist.h5
-rw-r--r--src/base_station_base.h10
-rw-r--r--src/bridge_gui.cpp7
-rw-r--r--src/cargodest.cpp1197
-rw-r--r--src/cargodest_base.h182
-rw-r--r--src/cargodest_func.h35
-rw-r--r--src/cargodest_gui.cpp176
-rw-r--r--src/cargodest_gui.h48
-rw-r--r--src/cargodest_type.h31
-rw-r--r--src/cargopacket.cpp314
-rw-r--r--src/cargopacket.h142
-rw-r--r--src/clear_cmd.cpp24
-rw-r--r--src/command.cpp9
-rw-r--r--src/command_type.h4
-rw-r--r--src/console_cmds.cpp2
-rw-r--r--src/date.cpp11
-rw-r--r--src/date_func.h3
-rw-r--r--src/date_gui.cpp158
-rw-r--r--src/date_gui.h4
-rw-r--r--src/date_type.h21
-rw-r--r--src/debug.cpp2
-rw-r--r--src/debug.h1
-rw-r--r--src/departures.cpp646
-rw-r--r--src/departures_func.h21
-rw-r--r--src/departures_gui.cpp856
-rw-r--r--src/departures_gui.h22
-rw-r--r--src/departures_type.h101
-rw-r--r--src/economy.cpp201
-rw-r--r--src/economy_base.h7
-rw-r--r--src/economy_func.h2
-rw-r--r--src/genworld.cpp2
-rw-r--r--src/genworld_gui.cpp45
-rw-r--r--src/gfx_type.h1
-rw-r--r--src/gfxinit.cpp3
-rw-r--r--src/heightmap.cpp4
-rw-r--r--src/industry.h47
-rw-r--r--src/industry_cmd.cpp71
-rw-r--r--src/industry_gui.cpp25
-rw-r--r--src/landscape.cpp11
-rw-r--r--src/lang/english.txt247
-rw-r--r--src/lang/hungarian.txt29
-rw-r--r--src/lang/russian.txt39
-rw-r--r--src/layer.cpp137
-rw-r--r--src/layer_func.h159
-rw-r--r--src/layer_gui.h17
-rw-r--r--src/layer_type.h22
-rw-r--r--src/main_gui.cpp2
-rw-r--r--src/map.cpp7
-rw-r--r--src/map_func.h2
-rw-r--r--src/misc.cpp4
-rw-r--r--src/newgrf.cpp3
-rw-r--r--src/object_cmd.cpp4
-rw-r--r--src/order_base.h123
-rw-r--r--src/order_cmd.cpp196
-rw-r--r--src/os/macosx/osx_stdafx.h2
-rw-r--r--src/pathfinder/follow_track.hpp4
-rw-r--r--src/pathfinder/npf/npf.cpp2
-rw-r--r--src/pathfinder/yapf/nodelist.hpp4
-rw-r--r--src/pathfinder/yapf/yapf.h4
-rw-r--r--src/pathfinder/yapf/yapf_cargo.cpp442
-rw-r--r--src/pathfinder/yapf/yapf_node.hpp22
-rw-r--r--src/pathfinder/yapf/yapf_node_rail.hpp4
-rw-r--r--src/pathfinder/yapf/yapf_node_road.hpp4
-rw-r--r--src/pathfinder/yapf/yapf_node_ship.hpp2
-rw-r--r--src/pathfinder/yapf/yapf_road.cpp3
-rw-r--r--src/rail.h2
-rw-r--r--src/rail_cmd.cpp129
-rw-r--r--src/rail_gui.cpp92
-rw-r--r--src/rail_map.h28
-rw-r--r--src/road_cmd.cpp88
-rw-r--r--src/road_cmd.h3
-rw-r--r--src/road_gui.cpp44
-rw-r--r--src/road_map.h32
-rw-r--r--src/roadveh_cmd.cpp14
-rw-r--r--src/saveload/afterload.cpp47
-rw-r--r--src/saveload/cargodest_sl.cpp181
-rw-r--r--src/saveload/cargopacket_sl.cpp6
-rw-r--r--src/saveload/economy_sl.cpp2
-rw-r--r--src/saveload/industry_sl.cpp12
-rw-r--r--src/saveload/map_sl.cpp6
-rw-r--r--src/saveload/order_sl.cpp9
-rw-r--r--src/saveload/saveload.cpp10
-rw-r--r--src/saveload/saveload.h8
-rw-r--r--src/saveload/station_sl.cpp3
-rw-r--r--src/saveload/town_sl.cpp16
-rw-r--r--src/saveload/vehicle_sl.cpp4
-rw-r--r--src/script/api/script_window.hpp2
-rw-r--r--src/settings.cpp77
-rw-r--r--src/settings_gui.cpp95
-rw-r--r--src/settings_type.h73
-rw-r--r--src/ship_cmd.cpp44
-rw-r--r--src/signal.cpp56
-rw-r--r--src/smallmap_gui.cpp238
-rw-r--r--src/smallmap_gui.h1
-rw-r--r--src/station.cpp64
-rw-r--r--src/station_base.h9
-rw-r--r--src/station_cmd.cpp99
-rw-r--r--src/station_cmd.cpp.orig3848
-rw-r--r--src/station_func.h2
-rw-r--r--src/station_gui.cpp503
-rw-r--r--src/statusbar_gui.cpp10
-rw-r--r--src/strings.cpp60
-rw-r--r--src/subsidy.cpp42
-rw-r--r--src/table/control_codes.h4
-rw-r--r--src/table/settings.ini623
-rw-r--r--src/table/sprites.h33
-rw-r--r--src/table/strgen_tables.h4
-rw-r--r--src/terraform_cmd.cpp5
-rw-r--r--src/tgp.cpp18
-rw-r--r--src/tile_map.h2
-rw-r--r--src/tilearea_type.h10
-rw-r--r--src/tilehighlight_func.h8
-rw-r--r--src/tilehighlight_type.h1
-rw-r--r--src/timetable.h2
-rw-r--r--src/timetable_cmd.cpp42
-rw-r--r--src/timetable_gui.cpp319
-rw-r--r--src/toolbar_gui.cpp51
-rw-r--r--src/town.h37
-rw-r--r--src/town_cmd.cpp78
-rw-r--r--src/town_gui.cpp24
-rw-r--r--src/track_func.h11
-rw-r--r--src/track_type.h1
-rw-r--r--src/trafficlight.cpp291
-rw-r--r--src/trafficlight.h133
-rw-r--r--src/trafficlight_func.h19
-rw-r--r--src/trafficlight_type.h21
-rw-r--r--src/train_cmd.cpp241
-rw-r--r--src/train_gui.cpp19
-rw-r--r--src/tree_cmd.cpp8
-rw-r--r--src/tunnelbridge_cmd.cpp146
-rw-r--r--src/tunnelbridge_map.h94
-rw-r--r--src/underground_gui.cpp294
-rw-r--r--src/underground_gui.h19
-rw-r--r--src/vehicle.cpp65
-rw-r--r--src/vehicle_base.h9
-rw-r--r--src/vehicle_cmd.cpp6
-rw-r--r--src/vehicle_gui.cpp25
-rw-r--r--src/vehicle_gui.h17
-rw-r--r--src/vehicle_type.h2
-rw-r--r--src/viewport.cpp429
-rw-r--r--src/viewport_func.h2
-rw-r--r--src/water_cmd.cpp11
-rw-r--r--src/waypoint_gui.cpp6
-rw-r--r--src/widgets/departures_widget.h29
-rw-r--r--src/widgets/genworld_widget.h2
-rw-r--r--src/widgets/road_widget.h1
-rw-r--r--src/widgets/smallmap_widget.h1
-rw-r--r--src/widgets/station_widget.h2
-rw-r--r--src/widgets/timetable_widget.h3
-rw-r--r--src/widgets/underground_widget.h21
-rw-r--r--src/widgets/waypoint_widget.h1
-rw-r--r--src/window.cpp2
-rw-r--r--src/window_type.h11
154 files changed, 14606 insertions, 502 deletions
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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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 <typename T>
+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<uint>(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<CargoSourceSink *>(Town::Get(source_id)) : static_cast<CargoSourceSink *>(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<Industry *>(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<Vehicle *>::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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<uint32> 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<CargoLink, 8> 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<CargoSourceSink *>(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<RouteLink, RouteLinkID, 512, 262144> 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<const Town *>(l->dest)->xy : static_cast<const Industry *>(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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<GUICargoLink> 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<uint>(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<Tinst>::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<Tinst *>(this)->RemoveFromCacheLocal(cp, diff);
cp->count = max_remaining;
max_remaining = 0;
} else {
@@ -245,56 +288,121 @@ void CargoList<Tinst>::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 <class Tinst>
template <class Tother_inst>
-bool CargoList<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta, CargoPayment *payment, uint data)
+bool CargoList<Tinst>::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<Tinst *>(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<Tinst *>(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<Tinst>::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<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta
if (cp_new == NULL) return false;
static_cast<Tinst *>(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<Tinst>::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<VehicleCargoList>;
template class CargoList<StationCargoList>;
/** Autoreplace Vehicle -> Vehicle 'transfer'. */
-template bool CargoList<VehicleCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
+template bool CargoList<VehicleCargoList>::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<VehicleCargoList>::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
+template bool CargoList<VehicleCargoList>::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<StationCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
+template bool CargoList<StationCargoList>::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 <list>
+#include <map>
/** 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 <class Tinst> 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 <class Tother_inst>
- bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, uint data = 0);
+ bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, StationID st = INVALID_STATION, OrderID cur_order = INVALID_ORDER, CargoID cid = INVALID_CARGO, bool *did_transfer = NULL);
void InvalidateCache();
};
@@ -286,6 +359,8 @@ public:
void InvalidateCache();
+ void InvalidateNextStation();
+
/**
* Are two the two CargoPackets mergeable in the context of
* a list of CargoPackets for a Vehicle?
@@ -299,7 +374,13 @@ public:
cp1->days_in_transit == cp2->days_in_transit &&
cp1->source_type == cp2->source_type &&
cp1->source_id == cp2->source_id &&
- cp1->loaded_at_xy == cp2->loaded_at_xy;
+ cp1->loaded_at_xy == cp2->loaded_at_xy &&
+ cp1->dest_xy == cp2->dest_xy &&
+ cp1->dest_type == cp2->dest_type &&
+ cp1->dest_id == cp2->dest_id &&
+ cp1->next_order == cp2->next_order &&
+ cp1->next_station == cp2->next_station &&
+ cp1->flags == cp2->flags;
}
};
@@ -308,11 +389,51 @@ public:
*/
class StationCargoList : public CargoList<StationCargoList> {
public:
+ typedef std::map<OrderID, int> OrderMap;
+
+protected:
+ /** The (direct) parent of this class. */
+ typedef CargoList<StationCargoList> 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<StationCargoList>;
/** 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<Departure*, 32> *result = new SmallVector<Departure*, 32>();
+
+ /* A list of the next scheduled orders to be considered for inclusion in the departure list. */
+ SmallVector<OrderDate*, 32> 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<bool Twaypoint = false>
+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<NWidgetCore>(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<NWidgetBase>(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<NWidgetCore>(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<NWidgetCore>(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<DeparturesWindow<> >(&_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<DeparturesWindow<true> >(&_departures_desc, waypoint);
+}
+
+template<bool Twaypoint>
+void DeparturesWindow<Twaypoint>::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<bool Twaypoint>
+uint DeparturesWindow<Twaypoint>::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<bool Twaypoint>
+void DeparturesWindow<Twaypoint>::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<bool Twaypoint>
+void DeparturesWindow<Twaypoint>::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 <time>" fields. */
+ int time_width = cached_date_width;
+
+ if (!_settings_client.gui.departure_show_both) {
+ time_width += (departure_types[0] && departure_types[1] ? cached_date_arrow_width : 0);
+ }
+
+ /* Vehicle type icon */
+ int type_width = _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
+
+ /* Find the maximum width of the status field */
+ int status_width = cached_status_width;
+
+ /* Find the width of the "Calling at:" field. */
+ int calling_at_width = (GetStringBoundingBox(_settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)).width;
+
+ /* Find the maximum company name width. */
+ int toc_width = 0;
+
+ /* Find the maximum group 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;
+ }
+ }
+ }
+
+ uint departure = 0;
+ uint arrival = 0;
+
+ /* Draw each departure. */
+ for (uint i = 0; i < max_departures; ++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 < this->vscroll->GetPosition()) {
+ continue;
+ }
+
+ /* If for some reason the departure is too far in the future or is at a negative time, skip it. */
+ if ((d->scheduled_date / DAY_TICKS) > (_date + _settings_client.gui.max_departure_time) ||
+ d->scheduled_date < 0) {
+ continue;
+ }
+
+ if (d->terminus == INVALID_STATION) continue;
+
+ StringID time_str = (departure_types[0] && departure_types[1]) ? (d->type == D_DEPARTURE ? STR_DEPARTURES_TIME_DEP : STR_DEPARTURES_TIME_ARR) : STR_DEPARTURES_TIME;
+
+ if (_settings_client.gui.departure_show_both) time_str = STR_DEPARTURES_TIME_BOTH;
+
+ /* Time */
+ SetDParam(0, d->scheduled_date);
+ SetDParam(1, d->scheduled_date - d->order->wait_time);
+ ltr ? DrawString( text_left, text_left + time_width, y + 1, time_str)
+ : DrawString(text_right - time_width, text_right, y + 1, time_str);
+
+ /* Vehicle type icon, with thanks to sph */
+ if (_settings_client.gui.departure_show_vehicle_type) {
+ StringID type = STR_DEPARTURES_TYPE_TRAIN;
+ int offset = (_settings_client.gui.departure_show_vehicle_color ? 1 : 0);
+
+ switch (d->vehicle->type) {
+ case VEH_TRAIN:
+ type = STR_DEPARTURES_TYPE_TRAIN;
+ break;
+ case VEH_ROAD:
+ type = IsCargoInClass(d->vehicle->cargo_type, CC_PASSENGERS) ? STR_DEPARTURES_TYPE_BUS : STR_DEPARTURES_TYPE_LORRY;
+ break;
+ case VEH_SHIP:
+ type = STR_DEPARTURES_TYPE_SHIP;
+ break;
+ case VEH_AIRCRAFT:
+ type = STR_DEPARTURES_TYPE_PLANE;
+ break;
+ default:
+ break;
+ }
+
+ type += offset;
+
+ DrawString(text_left + time_width + 3, text_left + time_width + type_width + 3, y, type);
+ }
+
+ /* The icons to show with the destination and via stations. */
+ StringID icon = STR_DEPARTURES_STATION_NONE;
+ StringID icon_via = STR_DEPARTURES_STATION_NONE;
+
+ if (_settings_client.gui.departure_destination_type) {
+ Station *t = Station::Get(d->terminus.station);
+
+ if (t->facilities & FACIL_DOCK &&
+ t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_SHIP &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon = STR_DEPARTURES_STATION_PORTAIRPORT;
+ } else if (t->facilities & FACIL_DOCK &&
+ d->vehicle->type != VEH_SHIP) {
+ icon = STR_DEPARTURES_STATION_PORT;
+ } else if (t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon = STR_DEPARTURES_STATION_AIRPORT;
+ }
+ }
+
+ if (_settings_client.gui.departure_destination_type && d->via != INVALID_STATION) {
+ Station *t = Station::Get(d->via);
+
+ if (t->facilities & FACIL_DOCK &&
+ t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_SHIP &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon_via = STR_DEPARTURES_STATION_PORTAIRPORT;
+ } else if (t->facilities & FACIL_DOCK &&
+ d->vehicle->type != VEH_SHIP) {
+ icon_via = STR_DEPARTURES_STATION_PORT;
+ } else if (t->facilities & FACIL_AIRPORT &&
+ d->vehicle->type != VEH_AIRCRAFT) {
+ icon_via = STR_DEPARTURES_STATION_AIRPORT;
+ }
+ }
+
+ /* Destination */
+ if (d->via == INVALID_STATION) {
+ /* Only show the terminus. */
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS);
+ } else {
+ /* Show the terminus and the via station. */
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ SetDParam(2, d->via);
+ SetDParam(3, icon_via);
+ int text_width = (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION)).width;
+
+ if (text_width < text_right - status_width - (toc_width + veh_width + group_width + 2) - 2 - (text_left + time_width + type_width + 6)) {
+ /* They will both fit, so show them both. */
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ SetDParam(2, d->via);
+ SetDParam(3, icon_via);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION);
+ } else {
+ /* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */
+ if (this->tick_count & (1 << 7)) {
+ SetDParam(0, d->via);
+ SetDParam(1, icon_via);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_VIA)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_VIA);
+ } else {
+ SetDParam(0, d->terminus.station);
+ SetDParam(1, icon);
+ ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA)
+ : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA);
+ }
+ }
+ }
+
+ /* Status */
+ {
+ int status_left = ltr ? text_right - status_width - 2 - (toc_width + veh_width + group_width + 2) : text_left + (toc_width + veh_width + group_width + 2) + 2;
+ int status_right = ltr ? text_right - (toc_width + veh_width + group_width + 2) + 2 : text_left + status_width + 2 + (toc_width + veh_width + group_width + 2);
+
+ if (d->status == D_ARRIVED) {
+ /* The vehicle has arrived. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ARRIVED);
+ } else if(d->status == D_CANCELLED) {
+ /* The vehicle has been cancelled. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_CANCELLED);
+ } else{
+ if (d->lateness <= DAY_TICKS && d->scheduled_date > ((_date * DAY_TICKS) + _date_fract)) {
+ /* We have no evidence that the vehicle is late, so assume it is on time. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ON_TIME);
+ } else {
+ if ((d->scheduled_date + d->lateness) < ((_date * DAY_TICKS) + _date_fract)) {
+ /* The vehicle was expected to have arrived by now, even if we knew it was going to be late. */
+ /* We assume that the train stays at least a day at a station so it won't accidentally be marked as delayed for a fraction of a day. */
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_DELAYED);
+ } else {
+ /* The vehicle is expected to be late and is not yet due to arrive. */
+ SetDParam(0, d->scheduled_date + d->lateness);
+ DrawString(status_left, status_right, y + 1, STR_DEPARTURES_EXPECTED);
+ }
+ }
+ }
+ }
+
+ /* Vehicle name */
+
+ if (_settings_client.gui.departure_show_vehicle) {
+ SetDParam(0, (uint64)(d->vehicle->index));
+ ltr ? DrawString(text_right - (toc_width + veh_width + group_width + 2), text_right - toc_width - group_width - 2, y + 1, STR_DEPARTURES_VEH)
+ : DrawString( text_left + toc_width + group_width + 2, text_left + (toc_width + veh_width + group_width + 2), y + 1, STR_DEPARTURES_VEH);
+ }
+
+ /* Group name */
+
+ if (_settings_client.gui.departure_show_group && d->vehicle->group_id != INVALID_GROUP && d->vehicle->group_id != DEFAULT_GROUP) {
+ SetDParam(0, (uint64)(d->vehicle->group_id));
+ ltr ? DrawString(text_right - (toc_width + group_width + 2), text_right - toc_width - 2, y + 1, STR_DEPARTURES_GROUP)
+ : DrawString( text_left + toc_width + 2, text_left + (toc_width + group_width + 2), y + 1, STR_DEPARTURES_GROUP);
+ }
+
+ /* Operating company */
+ if (_settings_client.gui.departure_show_company) {
+ SetDParam(0, (uint64)(d->vehicle->owner));
+ ltr ? DrawString(text_right - toc_width, text_right, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_RIGHT)
+ : DrawString( text_left, text_left + toc_width, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_LEFT);
+ }
+
+ int bottom_y = y + this->entry_height - small_font_size - (_settings_client.gui.departure_larger_font ? 1 : 3);
+
+ /* Calling at */
+ ltr ? DrawString( text_left, text_left + calling_at_width, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)
+ : DrawString(text_right - calling_at_width, text_right, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT);
+
+ /* List of stations */
+ /* RTL languages can be handled in the language file, e.g. by having the following: */
+ /* STR_DEPARTURES_CALLING_AT_STATION :{STATION}, {RAW_STRING} */
+ /* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/
+ char buffer[512], scratch[512];
+
+ if (d->calling_at.Length() != 0) {
+ SetDParam(0, (uint64)(*d->calling_at.Get(0)).station);
+ GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
+
+ StationID continuesTo = INVALID_STATION;
+
+ if (d->calling_at.Get(0)->station == d->terminus.station && d->calling_at.Length() > 1) {
+ continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station;
+ } else if (d->calling_at.Length() > 1) {
+ /* There's more than one stop. */
+
+ uint i;
+ /* For all but the last station, write out ", <station>". */
+ for (i = 1; i < d->calling_at.Length() - 1; ++i) {
+ StationID s = d->calling_at.Get(i)->station;
+ if (s == d->terminus.station) {
+ continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station;
+ break;
+ }
+ SetDParam(0, (uint64)scratch);
+ SetDParam(1, (uint64)s);
+ GetString(buffer, STR_DEPARTURES_CALLING_AT_STATION, lastof(buffer));
+ strncpy(scratch, buffer, sizeof(scratch));
+ }
+
+ /* Finally, finish off with " and <station>". */
+ SetDParam(0, (uint64)scratch);
+ SetDParam(1, (uint64)d->calling_at.Get(i)->station);
+ GetString(buffer, STR_DEPARTURES_CALLING_AT_LAST_STATION, lastof(buffer));
+ strncpy(scratch, buffer, sizeof(scratch));
+ }
+
+ SetDParam(0, (uint64)scratch);
+ StringID string;
+ if (continuesTo == INVALID_STATION) {
+ string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_LARGE : STR_DEPARTURES_CALLING_AT_LIST;
+ } else {
+ SetDParam(1, continuesTo);
+ string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS_LARGE : STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS;
+ }
+ GetString(buffer, string, lastof(buffer));
+ } else {
+ buffer[0] = 0;
+ //SetDParam(0, d->terminus);
+ //GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
+ }
+
+ int list_width = (GetStringBoundingBox(buffer, _settings_client.gui.departure_larger_font ? FS_NORMAL : FS_SMALL)).width;
+
+ /* Draw the whole list if it will fit. Otherwise scroll it. */
+ if (list_width < text_right - (text_left + calling_at_width + 2)) {
+ ltr ? DrawString(text_left + calling_at_width + 2, text_right, bottom_y, buffer)
+ : DrawString( text_left, text_right - calling_at_width - 2, bottom_y, buffer);
+ } else {
+ DrawPixelInfo tmp_dpi;
+ if (ltr
+ ? !FillDrawPixelInfo(&tmp_dpi, text_left + calling_at_width + 2, bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)
+ : !FillDrawPixelInfo(&tmp_dpi, text_left , bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)) {
+ y += this->entry_height;
+ continue;
+ }
+ DrawPixelInfo *old_dpi = _cur_dpi;
+ _cur_dpi = &tmp_dpi;
+
+ /* The scrolling text starts out of view at the right of the screen and finishes when it is out of view at the left of the screen. */
+ int pos = ltr
+ ? text_right - (this->tick_count % (list_width + text_right - text_left))
+ : text_left + (this->tick_count % (list_width + text_right - text_left));
+
+ ltr ? DrawString( pos, INT16_MAX, 0, buffer, TC_FROMSTRING, SA_LEFT | SA_FORCE)
+ : DrawString(-INT16_MAX, pos, 0, buffer, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
+
+ _cur_dpi = old_dpi;
+ }
+
+ y += this->entry_height;
+ }
+}
diff --git a/src/departures_gui.h b/src/departures_gui.h
new file mode 100644
index 000000000..aedbf2986
--- /dev/null
+++ b/src/departures_gui.h
@@ -0,0 +1,22 @@
+/* $Id: departures_gui.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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file departures_gui.h */
+
+#ifndef DEPARTURES_GUI_H
+#define DEPARTURES_GUI_H
+
+#include "departures_type.h"
+#include "station_base.h"
+#include "widgets/departures_widget.h"
+
+void ShowStationDepartures(StationID station);
+void ShowWaypointDepartures(StationID waypoint);
+
+#endif /* DEPARTURES_GUI_H */
diff --git a/src/departures_type.h b/src/departures_type.h
new file mode 100644
index 000000000..cb06b144f
--- /dev/null
+++ b/src/departures_type.h
@@ -0,0 +1,101 @@
+/* $Id: departures_type.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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file departures_type.h Types related to departures. */
+
+#ifndef DEPARTURES_TYPE_H
+#define DEPARTURES_TYPE_H
+
+#include "station_base.h"
+#include "order_base.h"
+#include "vehicle_base.h"
+
+/** Whether or not a vehicle has arrived for a departure. */
+typedef enum {
+ D_TRAVELLING = 0,
+ D_ARRIVED = 1,
+ D_CANCELLED = 2,
+} DepartureStatus;
+
+/** The type of departures. */
+typedef enum {
+ D_DEPARTURE = 0,
+ D_ARRIVAL = 1,
+} DepartureType;
+
+typedef struct CallAt {
+ StationID station;
+ DateTicks scheduled_date;
+
+ CallAt(const StationID& s) : station(s), scheduled_date(0) { }
+ CallAt(const StationID& s, const DateTicks& t) : station(s), scheduled_date(t) { }
+ CallAt(const CallAt& c) : station(c.station), scheduled_date(c.scheduled_date) { }
+
+ inline bool operator==(const CallAt& c) const {
+ return this->station == c.station;
+ }
+
+ inline bool operator!=(const CallAt& c) const {
+ return this->station != c.station;
+ }
+
+ inline bool operator>=(const CallAt& c) const {
+ return this->station == c.station &&
+ this->scheduled_date != 0 &&
+ c.scheduled_date != 0 &&
+ this->scheduled_date >= c.scheduled_date;
+ }
+
+ CallAt& operator=(const CallAt& c) {
+ this->station = c.station;
+ this->scheduled_date = c.scheduled_date;
+ return *this;
+ }
+
+ inline bool operator==(StationID s) const {
+ return this->station == s;
+ }
+} CallAt;
+
+/** A scheduled departure. */
+typedef struct Departure {
+ DateTicks scheduled_date; ///< The date this departure is scheduled to finish on (i.e. when the vehicle leaves the station)
+ DateTicks lateness; ///< How delayed the departure is expected to be
+ CallAt terminus; ///< The station at which the vehicle will terminate following this departure
+ StationID via; ///< The station the departure should list as going via
+ SmallVector<CallAt, 32> calling_at; ///< The stations both called at and unloaded at by the vehicle after this departure before it terminates
+ DepartureStatus status; ///< Whether the vehicle has arrived yet for this departure
+ DepartureType type; ///< The type of the departure (departure or arrival)
+ const Vehicle *vehicle; ///< The vehicle performing this departure
+ const Order *order; ///< The order corresponding to this departure
+ Departure() : terminus(INVALID_STATION), via(INVALID_STATION), calling_at(), vehicle(NULL) { }
+ ~Departure()
+ {
+ calling_at.Reset();
+ }
+
+ inline bool operator==(const Departure& d) const {
+ if (this->calling_at.Length() != d.calling_at.Length()) return false;
+
+ for (uint i = 0; i < this->calling_at.Length(); ++i) {
+ if (*(this->calling_at.Get(i)) != *(d.calling_at.Get(i))) return false;
+ }
+
+ return
+ (this->scheduled_date / DATE_UNIT_SIZE) == (d.scheduled_date / DATE_UNIT_SIZE) &&
+ this->vehicle->type == d.vehicle->type &&
+ this->via == d.via &&
+ this->type == d.type
+ ;
+ }
+} Departure;
+
+typedef SmallVector<Departure*, 32> DepartureList;
+
+#endif /* DEPARTURES_TYPE_H */
diff --git a/src/economy.cpp b/src/economy.cpp
index 39cb8ddaa..424f3c29f 100644
--- a/src/economy.cpp
+++ b/src/economy.cpp
@@ -46,6 +46,7 @@
#include "game/game.hpp"
#include "cargomonitor.h"
#include "goal_base.h"
+#include "cargodest_func.h"
#include "table/strings.h"
#include "table/pricebase.h"
@@ -493,6 +494,13 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
/* if a company goes bankrupt, set owner to OWNER_NONE so the sign doesn't disappear immediately
* also, drawing station window would cause reading invalid company's colour */
st->owner = new_owner == INVALID_OWNER ? OWNER_NONE : new_owner;
+
+ /* Move route links to the new company. */
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ for (RouteLinkList::iterator itr = st->goods[cid].routes.begin(); itr != st->goods[cid].routes.end(); ++itr) {
+ if ((*itr)->owner == old_owner) (*itr)->owner = new_owner;
+ }
+ }
}
}
@@ -985,6 +993,33 @@ Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, C
/** The industries we've currently brought cargo to. */
static SmallIndustryList _cargo_delivery_destinations;
+ /**
+ * Deliver goods to an industry. Cargo acceptance by the industry is checked.
+ * @param ind The industry to deliver to.
+ * @param cargo_type Type of cargo delivered.
+ * @param num_pieces Amount of cargo delivered.
+ * @return Accepted pieces of cargo.
+ */
+static uint DeliverGoodsToIndustry(Industry *ind, CargoID cargo_type, uint num_pieces)
+{
+ uint cargo_index;
+ for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
+ if (cargo_type == ind->accepts_cargo[cargo_index]) break;
+ }
+ /* Check if matching cargo has been found */
+ if (cargo_index >= lengthof(ind->accepts_cargo)) return 0;
+
+ /* Check if industry temporarily refuses acceptance */
+ if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) return 0;
+
+ /* Insert the industry into _cargo_delivery_destinations, if not yet contained */
+ _cargo_delivery_destinations.Include(ind);
+
+ uint amount = min(num_pieces, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]);
+ ind->incoming_cargo_waiting[cargo_index] += amount;
+ return amount;
+}
+
/**
* Transfer goods from station to industry.
* All cargo is delivered to the nearest (Manhattan) industry to the station sign, which is inside the acceptance rectangle and actually accepts the cargo.
@@ -1009,21 +1044,7 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n
Industry *ind = st->industries_near[i];
if (ind->index == source) continue;
- uint cargo_index;
- for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
- if (cargo_type == ind->accepts_cargo[cargo_index]) break;
- }
- /* Check if matching cargo has been found */
- if (cargo_index >= lengthof(ind->accepts_cargo)) continue;
-
- /* Check if industry temporarily refuses acceptance */
- if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) continue;
-
- /* Insert the industry into _cargo_delivery_destinations, if not yet contained */
- _cargo_delivery_destinations.Include(ind);
-
- uint amount = min(num_pieces, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]);
- ind->incoming_cargo_waiting[cargo_index] += amount;
+ uint amount = DeliverGoodsToIndustry(ind, cargo_type, num_pieces);
num_pieces -= amount;
accepted += amount;
}
@@ -1041,17 +1062,25 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint n
* @param company The company delivering the cargo
* @param src_type Type of source of cargo (industry, town, headquarters)
* @param src Index of source of cargo
+ * @param cp_dest_type Type of destination of cargo
+ * @param cp_dest Index of the destination of cargo
* @return Revenue for delivering cargo
* @note The cargo is just added to the stockpile of the industry. It is due to the caller to trigger the industry's production machinery
*/
-static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID dest, TileIndex source_tile, byte days_in_transit, Company *company, SourceType src_type, SourceID src)
+static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID dest, TileIndex source_tile, byte days_in_transit, Company *company, SourceType src_type, SourceID src, SourceType cp_dest_type, SourceID cp_dest)
{
assert(num_pieces > 0);
Station *st = Station::Get(dest);
- /* Give the goods to the industry. */
- uint accepted = DeliverGoodsToIndustry(st, cargo_type, num_pieces, src_type == ST_INDUSTRY ? src : INVALID_INDUSTRY);
+ uint accepted = 0;
+ if (cp_dest != INVALID_SOURCE) {
+ /* If this cargo has an industry as destination, deliver the cargo to it. */
+ if (cp_dest_type == ST_INDUSTRY) accepted = DeliverGoodsToIndustry(Industry::Get(cp_dest), cargo_type, num_pieces);
+ } else {
+ /* Give the goods to any accepting industry. */
+ accepted = DeliverGoodsToIndustry(st, cargo_type, num_pieces, src_type == ST_INDUSTRY ? src : INVALID_INDUSTRY);
+ }
/* If this cargo type is always accepted, accept all */
if (HasBit(st->always_accepted, cargo_type)) accepted = num_pieces;
@@ -1140,21 +1169,29 @@ CargoPayment::~CargoPayment()
this->front->cargo_payment = NULL;
- if (this->visual_profit == 0) return;
+ if (this->visual_profit == 0 && this->transfer_profit == 0) return;
Backup<CompanyByte> cur_company(_current_company, this->front->owner, FILE_LINE);
SubtractMoneyFromCompany(CommandCost(this->front->GetExpenseType(true), -this->route_profit));
- this->front->profit_this_year += this->visual_profit << 8;
+ this->front->profit_this_year += (this->visual_profit + this->transfer_profit) << 8;
+
+ int z = this->front->z_pos;
if (this->route_profit != 0) {
- if (IsLocalCompany() && !PlayVehicleSound(this->front, VSE_LOAD_UNLOAD)) {
- SndPlayVehicleFx(SND_14_CASHTILL, this->front);
- }
+ /* Show profit/loss from final delivery. */
+ ShowCostOrIncomeAnimation(this->front->x_pos, this->front->y_pos, z, -this->visual_profit);
+ z += VPSM_TOP + FONT_HEIGHT_NORMAL + VPSM_BOTTOM;
+ }
- ShowCostOrIncomeAnimation(this->front->x_pos, this->front->y_pos, this->front->z_pos, -this->visual_profit);
- } else {
- ShowFeederIncomeAnimation(this->front->x_pos, this->front->y_pos, this->front->z_pos, this->visual_profit);
+ if (this->transfer_profit != 0) {
+ /* Show transfer credits. */
+ ShowFeederIncomeAnimation(this->front->x_pos, this->front->y_pos, z, this->transfer_profit);
+ }
+
+ /* Play cash sound. */
+ if (IsLocalCompany() && !PlayVehicleSound(this->front, VSE_LOAD_UNLOAD)) {
+ SndPlayVehicleFx(SND_14_CASHTILL, this->front);
}
cur_company.Restore();
@@ -1172,7 +1209,7 @@ void CargoPayment::PayFinalDelivery(const CargoPacket *cp, uint count)
}
/* Handle end of route payment */
- Money profit = DeliverGoods(count, this->ct, this->current_station, cp->SourceStationXY(), cp->DaysInTransit(), this->owner, cp->SourceSubsidyType(), cp->SourceSubsidyID());
+ Money profit = DeliverGoods(count, this->ct, this->current_station, cp->SourceStationXY(), cp->DaysInTransit(), this->owner, cp->SourceSubsidyType(), cp->SourceSubsidyID(), cp->DestinationType(), cp->DestinationID());
this->route_profit += profit;
/* The vehicle's profit is whatever route profit there is minus feeder shares. */
@@ -1196,7 +1233,7 @@ Money CargoPayment::PayTransfer(const CargoPacket *cp, uint count)
profit = profit * _settings_game.economy.feeder_payment_share / 100;
- this->visual_profit += profit; // accumulate transfer profits for whole vehicle
+ this->transfer_profit += profit; // accumulate transfer profits for whole vehicle
return profit; // account for the (virtual) profit already made for the cargo packet
}
@@ -1252,10 +1289,12 @@ static bool IsArticulatedVehicleEmpty(Vehicle *v)
* picked up by another vehicle when all
* previous vehicles have loaded.
*/
-static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
+static void LoadUnloadVehicle(Vehicle *front, StationCargoList::OrderMap (&cargo_left)[NUM_CARGO])
{
assert(front->current_order.IsType(OT_LOADING));
+ OrderID last_order = front->last_order_id;
+
/* We have not waited enough time till the next round of loading/unloading */
if (front->load_unload_ticks != 0) {
if (_settings_game.order.improved_load && (front->current_order.GetLoadType() & OLFB_FULL_LOAD)) {
@@ -1263,7 +1302,15 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
for (Vehicle *v = front; v != NULL; v = v->Next()) {
int cap_left = v->cargo_cap;
if (!HasBit(v->vehicle_flags, VF_CARGO_UNLOADING)) cap_left -= v->cargo.Count();
- if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left;
+ if (cap_left > 0) {
+ /* Try the bucket for our next destination first. */
+ int loaded = min(cap_left, cargo_left[v->cargo_type][last_order]);
+ cargo_left[v->cargo_type][last_order] -= loaded;
+
+ /* Reserve from the common bucket if still space left. */
+ loaded = min(cap_left - loaded, cargo_left[v->cargo_type][INVALID_ORDER]);
+ cargo_left[v->cargo_type][INVALID_ORDER] -= loaded;
+ }
}
}
return;
@@ -1344,15 +1391,44 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
uint amount_unloaded = _settings_game.order.gradual_loading ? min(cargo_count, load_amount) : cargo_count;
bool remaining = false; // Are there cargo entities in this vehicle that can still be unloaded here?
bool accepted = false; // Is the cargo accepted by the station?
+ bool did_transfer = false;
payment->SetCargo(v->cargo_type);
- if (HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) && !(front->current_order.GetUnloadType() & OUFB_TRANSFER)) {
- /* The cargo has reached its final destination, the packets may now be destroyed */
- remaining = v->cargo.MoveTo<StationCargoList>(NULL, amount_unloaded, VehicleCargoList::MTA_FINAL_DELIVERY, payment, last_visited);
+ if (CargoHasDestinations(v->cargo_type)) {
+ /* This cargo type has destinations enabled, this means explicit transfer
+ * orders are overridden for cargo packets with destination.
+ * Default action for destination-less cargo packets is final delivery when
+ * accepted, otherwise no action. If the current order is forced unload,
+ * always unload all cargo. */
+ VehicleCargoList::MoveToAction mta = HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) ? VehicleCargoList::MTA_FINAL_DELIVERY : VehicleCargoList::MTA_NO_ACTION;
+ if (front->current_order.GetUnloadType() & OUFB_UNLOAD) mta = VehicleCargoList::MTA_UNLOAD; //front was u
+ remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, mta, payment, last_visited, last_order, v->cargo_type, &did_transfer);
dirty_vehicle = true;
accepted = true;
+ } else {
+ /* Cargo destinations are not enabled, handle transfer orders. */
+ if (HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) && !(front->current_order.GetUnloadType() & OUFB_TRANSFER)) { //front was u
+ /* The cargo has reached its final destination, the packets may now be destroyed */
+ remaining = v->cargo.MoveTo<StationCargoList>(NULL, amount_unloaded, VehicleCargoList::MTA_FINAL_DELIVERY, payment, last_visited, v->cargo_type);
+
+ dirty_vehicle = true;
+ accepted = true;
+ }
+
+ /* The !accepted || v->cargo.Count == cargo_count clause is there
+ * to make it possible to force unload vehicles at the station where
+ * they were loaded, but to not force unload the vehicle when the
+ * station is still accepting the cargo in the vehicle. It doesn't
+ * accept cargo that was loaded at the same station. */
+ if ((front->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) { //front was u
+ remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, front->current_order.GetUnloadType() & OUFB_TRANSFER ? VehicleCargoList::MTA_TRANSFER : VehicleCargoList::MTA_UNLOAD, payment, last_visited, v->cargo_type); //front was u
+
+ did_transfer = true;
+ dirty_vehicle = true;
+ accepted = true;
+ }
}
/* The !accepted || v->cargo.Count == cargo_count clause is there
@@ -1360,15 +1436,15 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
* they were loaded, but to not force unload the vehicle when the
* station is still accepting the cargo in the vehicle. It doesn't
* accept cargo that was loaded at the same station. */
- if ((front->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) {
- remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, front->current_order.GetUnloadType() & OUFB_TRANSFER ? VehicleCargoList::MTA_TRANSFER : VehicleCargoList::MTA_UNLOAD, payment);
+ if (did_transfer) {
+ /* Update station information. */
if (!HasBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP)) {
SetBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP);
InvalidateWindowData(WC_STATION_LIST, last_visited);
}
-
- dirty_vehicle = dirty_station = true;
- } else if (!accepted) {
+ dirty_station = true;
+ }
+ if (!accepted) {
/* The order changed while unloading (unset unload/transfer) or the
* station does not accept our goods. */
ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING);
@@ -1432,13 +1508,13 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
FOR_EACH_SET_CARGO_ID(cid, refit_mask) {
/* Consider refitting to this cargo, if other vehicles of the consist cannot
* already take the cargo without refitting */
- if (cargo_left[cid] > (int)consist_capleft[cid] + amount) {
+ if (cargo_left[cid][last_order] + cargo_left[cid][INVALID_ORDER] > (int)consist_capleft[cid] + amount) {
/* Try to find out if auto-refitting would succeed. In case the refit is allowed,
* the returned refit capacity will be greater than zero. */
new_subtype = GetBestFittingSubType(v, v, cid);
DoCommand(v_start->tile, v_start->index, cid | 1U << 6 | new_subtype << 8 | 1U << 16, DC_QUERY_COST, GetCmdRefitVeh(v_start)); // Auto-refit and only this vehicle including artic parts.
if (_returned_refit_capacity > 0) {
- amount = cargo_left[cid] - consist_capleft[cid];
+ amount = cargo_left[cid][last_order] + cargo_left[cid][INVALID_ORDER] - consist_capleft[cid];
new_cid = cid;
}
}
@@ -1491,11 +1567,12 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
int cap_left = v->cargo_cap - v->cargo.Count();
if (!ge->cargo.Empty() && cap_left > 0) {
uint cap = cap_left;
- uint count = ge->cargo.Count();
+ uint count = ge->cargo.CountForNextHop(last_order) + ge->cargo.CountForNextHop(INVALID_ORDER);
/* Skip loading this vehicle if another train/vehicle is already handling
- * the same cargo type at this station */
- if (_settings_game.order.improved_load && cargo_left[v->cargo_type] <= 0) {
+ * the same cargo type at this station. Check the buckets for our next
+ * destination and the general bucket. */
+ if (_settings_game.order.improved_load && cargo_left[v->cargo_type][last_order] <= 0 && cargo_left[v->cargo_type][INVALID_ORDER] <= 0) {
SetBit(cargo_not_full, v->cargo_type);
continue;
}
@@ -1507,14 +1584,23 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
}
if (_settings_game.order.improved_load) {
/* Don't load stuff that is already 'reserved' for other vehicles */
- cap = min((uint)cargo_left[v->cargo_type], cap);
- count = cargo_left[v->cargo_type];
+ count = cargo_left[v->cargo_type][last_order] + cargo_left[v->cargo_type][INVALID_ORDER];
+
+ /* Try the bucket for our next destination first. */
+ int load_next = min<uint>(cap, cargo_left[v->cargo_type][last_order]);
+
+ /* Reserve from the common bucket if still space left. */
+ int load_common = min<uint>(cap - load_next, cargo_left[v->cargo_type][INVALID_ORDER]);
+
+ cap = load_next + load_common;
+ cargo_left[v->cargo_type][last_order] -= load_next;
+
if (use_autorefit) {
/* When using autorefit, reserve all cargo for this wagon to prevent other wagons
* from feeling the need to refit. */
- uint total_cap_left = v->cargo_cap - v->cargo.Count();
- cargo_left[v->cargo_type] -= total_cap_left;
+ uint total_cap_left = v->cargo_cap - v->cargo.Count() - load_common;
consist_capleft[v->cargo_type] -= total_cap_left;
+ cargo_left[v->cargo_type][INVALID_ORDER] -= total_cap_left;
if (total_cap_left > cap && count > cap) {
/* Remember if there are reservations left so that we don't stop
* loading before they're loaded. */
@@ -1523,7 +1609,7 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
} else {
/* Update cargo left; but don't reserve everything yet, so other wagons
* of the same consist load in parallel. */
- cargo_left[v->cargo_type] -= cap;
+ cargo_left[v->cargo_type][INVALID_ORDER] -= load_common;
}
}
@@ -1546,7 +1632,7 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
completely_emptied = false;
anything_loaded = true;
- ge->cargo.MoveTo(&v->cargo, cap, StationCargoList::MTA_CARGO_LOAD, NULL, st->xy);
+ ge->cargo.MoveTo(&v->cargo, cap, StationCargoList::MTA_CARGO_LOAD, NULL, last_visited, last_order, v->cargo_type);
st->time_since_load = 0;
st->last_vehicle_type = v->type;
@@ -1596,7 +1682,15 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
cap_left -= v->cargo.Count();
}
}
- if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left;
+ if (cap_left > 0) {
+ /* Try the bucket for our next destination first. */
+ int loaded = min(cap_left, cargo_left[v->cargo_type][last_order]);
+ cargo_left[v->cargo_type][last_order] -= loaded;
+
+ /* Reserve from the common bucket if still space left. */
+ loaded = min(cap_left - loaded, cargo_left[v->cargo_type][INVALID_ORDER]);
+ cargo_left[v->cargo_type][INVALID_ORDER] -= loaded;
+ }
}
}
@@ -1714,9 +1808,10 @@ void LoadUnloadStation(Station *st)
*/
if (last_loading == NULL) return;
- int cargo_left[NUM_CARGO];
-
- for (uint i = 0; i < NUM_CARGO; i++) cargo_left[i] = st->goods[i].cargo.Count();
+ StationCargoList::OrderMap cargo_left[NUM_CARGO];
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ cargo_left[i] = st->goods[i].cargo.CountForNextHop();
+ }
for (iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end(); ++iter) {
Vehicle *v = *iter;
diff --git a/src/economy_base.h b/src/economy_base.h
index 742d01878..1e32f3b08 100644
--- a/src/economy_base.h
+++ b/src/economy_base.h
@@ -24,9 +24,10 @@ extern CargoPaymentPool _cargo_payment_pool;
* Helper class to perform the cargo payment.
*/
struct CargoPayment : CargoPaymentPool::PoolItem<&_cargo_payment_pool> {
- Vehicle *front; ///< The front vehicle to do the payment of
- Money route_profit; ///< The amount of money to add/remove from the bank account
- Money visual_profit; ///< The visual profit to show
+ Vehicle *front; ///< The front vehicle to do the payment of
+ Money route_profit; ///< The amount of money to add/remove from the bank account
+ Money visual_profit; ///< The visual (non-transfer) profit to show
+ Money transfer_profit; ///< The transfer profit to show
/* Unsaved variables */
Company *owner; ///< The owner of the vehicle
diff --git a/src/economy_func.h b/src/economy_func.h
index 111ce85a2..b3517896c 100644
--- a/src/economy_func.h
+++ b/src/economy_func.h
@@ -31,7 +31,7 @@ int UpdateCompanyRatingAndValue(Company *c, bool update);
void StartupIndustryDailyChanges(bool init_counter);
Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, CargoID cargo_type);
-uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations);
+uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile);
void PrepareUnload(Vehicle *front_v);
void LoadUnloadStation(Station *st);
diff --git a/src/genworld.cpp b/src/genworld.cpp
index 78fa6e3e7..7f3d426a1 100644
--- a/src/genworld.cpp
+++ b/src/genworld.cpp
@@ -29,6 +29,7 @@
#include "newgrf.h"
#include "core/random_func.hpp"
#include "core/backup_type.hpp"
+#include "cargodest_func.h"
#include "progress.h"
#include "error.h"
#include "game/game.hpp"
@@ -138,6 +139,7 @@ static void _GenerateWorld(void *)
GenerateIndustries();
GenerateObjects();
GenerateTrees();
+ UpdateCargoLinks();
}
}
diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp
index 94a06e748..8c2798779 100644
--- a/src/genworld_gui.cpp
+++ b/src/genworld_gui.cpp
@@ -18,6 +18,8 @@
#include "window_func.h"
#include "date_func.h"
#include "sound_func.h"
+#include "map_type.h"
+#include "layer_type.h"
#include "fios.h"
#include "string_func.h"
#include "widgets/dropdown_type.h"
@@ -79,6 +81,7 @@ static const NWidgetPart _nested_generate_landscape_widgets[] = {
/* Left column with labels. */
NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_MAPSIZE, STR_MAPGEN_MAPSIZE_TOOLTIP), SetFill(1, 1),
+ NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_UNDERGROUND_LAYER_COUNT, STR_NULL), SetFill(1, 1),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_LAND_GENERATOR, STR_NULL), SetFill(1, 1),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(1, 1),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_TERRAIN_TYPE, STR_NULL), SetFill(1, 1),
@@ -96,6 +99,7 @@ static const NWidgetPart _nested_generate_landscape_widgets[] = {
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(1, 1),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_MAPGEN_MAPSIZE_TOOLTIP), SetFill(1, 0),
EndContainer(),
+ NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_LAYER_COUNT_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_LANDSCAPE_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_TERRAIN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
@@ -197,6 +201,7 @@ static const NWidgetPart _nested_heightmap_load_widgets[] = {
NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 4, 0),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_HEIGHTMAP_NAME, STR_NULL), SetFill(1, 1),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_MAPSIZE, STR_NULL), SetFill(1, 1),
+ NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_UNDERGROUND_LAYER_COUNT, STR_NULL), SetFill(1, 1),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_NUMBER_OF_TOWNS, STR_NULL), SetFill(1, 1),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_NUMBER_OF_INDUSTRIES, STR_NULL), SetFill(1, 1),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_RANDOM_SEED, STR_NULL), SetFill(1, 1),
@@ -215,6 +220,7 @@ static const NWidgetPart _nested_heightmap_load_widgets[] = {
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 0, 0, 0), SetFill(1, 1),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0),
EndContainer(),
+ NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_LAYER_COUNT_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetFill(1, 0),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_TOWN_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_GL_INDUSTRY_PULLDOWN), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
NWidget(WWT_EDITBOX, COLOUR_ORANGE, WID_GL_RANDOM_EDITBOX), SetDataTip(STR_MAPGEN_RANDOM_SEED_OSKTITLE, STR_MAPGEN_RANDOM_SEED_HELP), SetFill(1, 1),
@@ -277,11 +283,11 @@ static void LandscapeGenerationCallback(Window *w, bool confirmed)
if (confirmed) StartGeneratingLandscape((GenenerateLandscapeWindowMode)w->window_number);
}
-static DropDownList *BuildMapsizeDropDown()
+static DropDownList *BuildBitListDropDown(uint min_bits, uint max_bits)
{
DropDownList *list = new DropDownList();
- for (uint i = MIN_MAP_SIZE_BITS; i <= MAX_MAP_SIZE_BITS; i++) {
+ for (uint i = min_bits; i <= max_bits; i++) {
DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false);
item->SetParam(0, 1 << i);
list->push_back(item);
@@ -290,6 +296,16 @@ static DropDownList *BuildMapsizeDropDown()
return list;
}
+static DropDownList *BuildMapsizeDropDown()
+{
+ return BuildBitListDropDown(MIN_MAP_SIZE_BITS, MAX_MAP_SIZE_BITS);
+}
+
+static DropDownList *BuildLayerDropDown()
+{
+ return BuildBitListDropDown(MIN_LAYER_COUNT_BITS, MAX_LAYER_COUNT_BITS);
+}
+
static const StringID _elevations[] = {STR_TERRAIN_TYPE_VERY_FLAT, STR_TERRAIN_TYPE_FLAT, STR_TERRAIN_TYPE_HILLY, STR_TERRAIN_TYPE_MOUNTAINOUS, INVALID_STRING_ID};
static const StringID _sea_lakes[] = {STR_SEA_LEVEL_VERY_LOW, STR_SEA_LEVEL_LOW, STR_SEA_LEVEL_MEDIUM, STR_SEA_LEVEL_HIGH, STR_SEA_LEVEL_CUSTOM, INVALID_STRING_ID};
static const StringID _rivers[] = {STR_RIVERS_NONE, STR_RIVERS_FEW, STR_RIVERS_MODERATE, STR_RIVERS_LOT, INVALID_STRING_ID};
@@ -339,6 +355,7 @@ struct GenerateLandscapeWindow : public Window {
case WID_GL_START_DATE_TEXT: SetDParam(0, ConvertYMDToDate(_settings_newgame.game_creation.starting_year, 0, 1)); break;
case WID_GL_MAPSIZE_X_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_x); break;
case WID_GL_MAPSIZE_Y_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.map_y); break;
+ case WID_GL_LAYER_COUNT_PULLDOWN: SetDParam(0, 1 << _settings_newgame.game_creation.layers); break;
case WID_GL_SNOW_LEVEL_TEXT: SetDParam(0, _settings_newgame.game_creation.snow_line_height); break;
case WID_GL_TOWN_PULLDOWN:
@@ -453,6 +470,11 @@ struct GenerateLandscapeWindow : public Window {
*size = GetStringBoundingBox(STR_JUST_INT);
break;
+ case WID_GL_LAYER_COUNT_PULLDOWN:
+ SetDParam(0, MAX_LAYER_COUNT);
+ *size = GetStringBoundingBox(STR_JUST_INT);
+ break;
+
case WID_GL_SNOW_LEVEL_TEXT:
SetDParamMaxValue(0, MAX_TILE_HEIGHT);
*size = GetStringBoundingBox(STR_JUST_INT);
@@ -539,6 +561,10 @@ struct GenerateLandscapeWindow : public Window {
ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, WID_GL_MAPSIZE_Y_PULLDOWN);
break;
+ case WID_GL_LAYER_COUNT_PULLDOWN: // Mapsize Z
+ ShowDropDownList(this, BuildLayerDropDown(), _settings_newgame.game_creation.layers, WID_GL_LAYER_COUNT_PULLDOWN);
+ break;
+
case WID_GL_TOWN_PULLDOWN: // Number of towns
ShowDropDownMenu(this, _num_towns, _settings_newgame.difficulty.number_towns, WID_GL_TOWN_PULLDOWN, 0, 0);
break;
@@ -709,6 +735,7 @@ struct GenerateLandscapeWindow : public Window {
switch (widget) {
case WID_GL_MAPSIZE_X_PULLDOWN: _settings_newgame.game_creation.map_x = index; break;
case WID_GL_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break;
+ case WID_GL_LAYER_COUNT_PULLDOWN: _settings_newgame.game_creation.layers = index; break;
case WID_GL_TREE_PULLDOWN: _settings_newgame.game_creation.tree_placer = index; break;
case WID_GL_RIVER_PULLDOWN: _settings_newgame.game_creation.amount_of_rivers = index; break;
case WID_GL_SMOOTHNESS_PULLDOWN: _settings_newgame.game_creation.tgen_smoothness = index; break;
@@ -891,6 +918,10 @@ struct CreateScenarioWindow : public Window
SetDParam(0, 1 << _settings_newgame.game_creation.map_y);
break;
+ case WID_CS_LAYER_COUNT_PULLDOWN:
+ SetDParam(0, 1 << _settings_newgame.game_creation.layers);
+ break;
+
case WID_CS_FLAT_LAND_HEIGHT_TEXT:
SetDParam(0, _settings_newgame.game_creation.se_flat_world_height);
break;
@@ -924,6 +955,10 @@ struct CreateScenarioWindow : public Window
case WID_CS_MAPSIZE_X_PULLDOWN:
case WID_CS_MAPSIZE_Y_PULLDOWN:
SetDParamMaxValue(0, MAX_MAP_SIZE);
+ break;
+
+ case WID_CS_LAYER_COUNT_PULLDOWN:
+ SetDParam(0, MAX_LAYER_COUNT);
break;
case WID_CS_FLAT_LAND_HEIGHT_TEXT:
@@ -957,6 +992,10 @@ struct CreateScenarioWindow : public Window
ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, WID_CS_MAPSIZE_Y_PULLDOWN);
break;
+ case WID_CS_LAYER_COUNT_PULLDOWN: // Mapsize Y
+ ShowDropDownList(this, BuildLayerDropDown(), _settings_newgame.game_creation.layers, WID_CS_LAYER_COUNT_PULLDOWN);
+ break;
+
case WID_CS_EMPTY_WORLD: // Empty world / flat world
StartGeneratingLandscape(GLWM_SCENARIO);
break;
@@ -1019,6 +1058,7 @@ struct CreateScenarioWindow : public Window
switch (widget) {
case WID_CS_MAPSIZE_X_PULLDOWN: _settings_newgame.game_creation.map_x = index; break;
case WID_CS_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break;
+ case WID_CS_LAYER_COUNT_PULLDOWN: _settings_newgame.game_creation.layers = index; break;
}
this->SetDirty();
}
@@ -1074,6 +1114,7 @@ static const NWidgetPart _nested_create_scenario_widgets[] = {
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_CS_MAPSIZE_X_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL), SetPadding(0, 4, 0, 0),
NWidget(WWT_TEXT, COLOUR_ORANGE), SetDataTip(STR_MAPGEN_BY, STR_NULL), SetPadding(1, 2, 0, 0),
NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_CS_MAPSIZE_Y_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL),
+ NWidget(WWT_DROPDOWN, COLOUR_ORANGE, WID_CS_LAYER_COUNT_PULLDOWN), SetDataTip(STR_JUST_INT, STR_NULL),
EndContainer(),
/* Date. */
NWidget(NWID_HORIZONTAL),
diff --git a/src/gfx_type.h b/src/gfx_type.h
index 2b792d090..e7d7cb1a5 100644
--- a/src/gfx_type.h
+++ b/src/gfx_type.h
@@ -145,6 +145,7 @@ struct DrawPixelInfo {
void *dst_ptr;
int left, top, width, height;
int pitch;
+ int layer;
ZoomLevel zoom;
};
diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp
index 0ee82d05e..7cc8ab7c6 100644
--- a/src/gfxinit.cpp
+++ b/src/gfxinit.cpp
@@ -192,6 +192,9 @@ static void LoadSpriteTables()
/* Initialize the unicode to sprite mapping table */
InitializeUnicodeGlyphMap();
+ /* Load traffic lights graphics. */
+ LoadGrfFile("trafficlights.grf", SPR_TRAFFICLIGHTS_BASE, i++);
+
/*
* Load the base NewGRF with OTTD required graphics as first NewGRF.
* However, we do not want it to show up in the list of used NewGRFs,
diff --git a/src/heightmap.cpp b/src/heightmap.cpp
index 8f0fac6ad..27fc27e63 100644
--- a/src/heightmap.cpp
+++ b/src/heightmap.cpp
@@ -16,6 +16,7 @@
#include "error.h"
#include "saveload/saveload.h"
#include "bmp.h"
+#include "layer_func.h"
#include "gfx_func.h"
#include "fios.h"
#include "fileio_func.h"
@@ -386,6 +387,9 @@ void FixSlopes()
width = MapSizeX();
height = MapSizeY();
+ /* Layers height correct */
+ FixUndergroundHeights();
+
/* Top and left edge */
for (row = 0; (uint)row < height; row++) {
for (col = 0; (uint)col < width; col++) {
diff --git a/src/industry.h b/src/industry.h
index 5e5d04688..7e329a7bd 100644
--- a/src/industry.h
+++ b/src/industry.h
@@ -16,6 +16,7 @@
#include "subsidy_type.h"
#include "industry_map.h"
#include "tilearea_type.h"
+#include "cargodest_base.h"
typedef Pool<Industry, IndustryID, 64, 64000> IndustryPool;
@@ -36,7 +37,7 @@ enum ProductionLevels {
/**
* Defines the internal data of a functional industry.
*/
-struct Industry : IndustryPool::PoolItem<&_industry_pool> {
+struct Industry : IndustryPool::PoolItem<&_industry_pool>, CargoSourceSink {
TileArea location; ///< Location of the industry
Town *town; ///< Nearest town
CargoID produced_cargo[2]; ///< 2 production cargo slots
@@ -45,11 +46,13 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
byte production_rate[2]; ///< production rate for each cargo
byte prod_level; ///< general production level
CargoID accepts_cargo[3]; ///< 3 input cargo slots
+ uint32 produced_accepted_mask; ///< Bit mask of all cargoes that are always accepted and also produced
uint16 this_month_production[2]; ///< stats of this month's production per cargo
uint16 this_month_transported[2]; ///< stats of this month's transport per cargo
byte last_month_pct_transported[2]; ///< percentage transported per cargo in the last full month
uint16 last_month_production[2]; ///< total units produced per cargo in the last full month
uint16 last_month_transported[2]; ///< total units transported per cargo in the last full month
+ uint16 average_production[2]; ///< average production during the last months
uint16 counter; ///< used for animation and/or production (if available cargo)
IndustryType type; ///< type of industry.
@@ -76,6 +79,41 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
void RecomputeProductionMultipliers();
+ /* virtual */ SourceType GetType() const
+ {
+ return ST_INDUSTRY;
+ }
+
+ /* virtual */ SourceID GetID() const
+ {
+ return this->index;
+ }
+
+ /* virtual */ bool AcceptsCargo(CargoID cid) const
+ {
+ if (HasBit(this->produced_accepted_mask, cid)) return true;
+
+ for (uint i = 0; i < lengthof(this->accepts_cargo); i++) {
+ if (this->accepts_cargo[i] == cid) return true;
+ }
+ return false;
+ }
+
+ /* virtual */ bool SuppliesCargo(CargoID cid) const
+ {
+ for (uint i = 0; i < lengthof(this->produced_cargo); i++) {
+ if (this->produced_cargo[i] == cid) return true;
+ }
+ return false;
+ }
+
+ /* virtual */ uint GetDestinationWeight(CargoID cid, byte weight_mod) const;
+
+ /* virtual */ TileArea GetTileForDestination(CargoID cid)
+ {
+ return this->location;
+ }
+
/**
* Check if a given tile belongs to this industry.
* @param tile The tile to check.
@@ -97,7 +135,10 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
return Industry::Get(GetIndustryIndex(tile));
}
- static Industry *GetRandom();
+ /** Callback function for #Industry::GetRandom. */
+ typedef bool (*EnumIndustryProc)(const Industry *ind, void *data);
+
+ static Industry *GetRandom(EnumIndustryProc enum_proc = NULL, IndustryID skip = INVALID_INDUSTRY, void *data = NULL);
static void PostDestructor(size_t index);
/**
@@ -147,6 +188,8 @@ void PlantRandomFarmField(const Industry *i);
void ReleaseDisastersTargetingIndustry(IndustryID);
+void UpdateIndustryAcceptance(Industry *ind);
+
bool IsTileForestIndustry(TileIndex tile);
#define FOR_ALL_INDUSTRIES_FROM(var, start) FOR_ALL_ITEMS_FROM(Industry, industry_index, var, start)
diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp
index 69d83ba14..4cead964f 100644
--- a/src/industry_cmd.cpp
+++ b/src/industry_cmd.cpp
@@ -14,6 +14,7 @@
#include "industry.h"
#include "station_base.h"
#include "landscape.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "command_func.h"
#include "town.h"
@@ -193,17 +194,36 @@ void Industry::PostDestructor(size_t index)
/**
- * Return a random valid industry.
- * @return random industry, NULL if there are no industries
+ * Return a random industry that statisfies some criteria
+ * specified with a callback function.
+ *
+ * @param enum_proc Callback function. Return true for a matching industry and false to continue iterating.
+ * @param skip Skip over this industry id when searching.
+ * @param data Optional data passed to the callback function.
+ * @return An industry satisfying the search criteria or NULL if no such industry exists.
*/
-/* static */ Industry *Industry::GetRandom()
+/* static */ Industry *Industry::GetRandom(EnumIndustryProc enum_proc, IndustryID skip, void *data)
{
- if (Industry::GetNumItems() == 0) return NULL;
- int num = RandomRange((uint16)Industry::GetNumItems());
- size_t index = MAX_UVALUE(size_t);
+ assert(skip == INVALID_INDUSTRY || Industry::IsValidID(skip));
+
+ uint16 max_num = 0;
+ if (enum_proc != NULL) {
+ /* A callback was given, count all matching industries. */
+ Industry *ind;
+ FOR_ALL_INDUSTRIES(ind) {
+ if (ind->index != skip && enum_proc(ind, data)) max_num++;
+ }
+ } else {
+ max_num = (uint16)Industry::GetNumItems();
+ /* Subtract one if an industry to skip was given. max_num is at least
+ * one here as otherwise skip could not be valid. */
+ if (skip != INVALID_INDUSTRY) max_num--;
+ }
+ if (max_num == 0) return NULL;
- while (num >= 0) {
- num--;
+ uint num = RandomRange(max_num) + 1;
+ size_t index = MAX_UVALUE(size_t);
+ do {
index++;
/* Make sure we have a valid industry */
@@ -211,7 +231,9 @@ void Industry::PostDestructor(size_t index)
index++;
assert(index < Industry::GetPoolSize());
}
- }
+
+ if (index != skip && (enum_proc == NULL || enum_proc(Industry::Get(index), data))) num--;
+ } while (num > 0);
return Industry::Get(index);
}
@@ -506,7 +528,7 @@ static void TransportIndustryGoods(TileIndex tile)
i->this_month_production[j] += cw;
- uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, stations.GetStations());
+ uint am = MoveGoodsToStation(i->produced_cargo[j], cw, ST_INDUSTRY, i->index, stations.GetStations(), tile);
i->this_month_transported[j] += am;
moved_cargo |= (am != 0);
@@ -1380,7 +1402,7 @@ static CommandCost CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTil
IndustryGfx gfx = GetTranslatedIndustryTileID(it->gfx);
TileIndex cur_tile = TileAddWrap(tile, it->ti.x, it->ti.y);
- if (!IsValidTile(cur_tile)) {
+ if (!IsValidTile(cur_tile) || IsUnderground(cur_tile)) {
return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
}
@@ -1583,6 +1605,28 @@ static CommandCost CheckIfFarEnoughFromConflictingIndustry(TileIndex tile, int t
return CommandCost();
}
+/** Update the mask of always accepted cargoes that are also produced. */
+void UpdateIndustryAcceptance(Industry *ind)
+{
+ CargoArray accepted;
+ uint32 always_accepted = 0;
+
+ /* Gather always accepted cargoes for all tiles of this industry. */
+ TILE_AREA_LOOP(tile, ind->location) {
+ if (IsTileType(tile, MP_INDUSTRY) && GetIndustryIndex(tile) == ind->index) {
+ AddAcceptedCargo_Industry(tile, accepted, &always_accepted);
+ }
+ }
+
+ /* Create mask of produced cargoes. */
+ uint32 produced = 0;
+ for (uint i = 0; i < lengthof(ind->produced_cargo); i++) {
+ if (ind->produced_cargo[i] != CT_INVALID) SetBit(produced, ind->produced_cargo[i]);
+ }
+
+ ind->produced_accepted_mask = always_accepted & produced;
+}
+
/**
* Advertise about a new industry opening.
* @param ind Industry being opened.
@@ -1756,6 +1800,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type,
}
InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, 0);
+ UpdateIndustryAcceptance(i);
Station::RecomputeIndustriesNearForAll();
}
@@ -2153,6 +2198,9 @@ static void UpdateIndustryStatistics(Industry *i)
i->last_month_transported[j] = i->this_month_transported[j];
i->this_month_transported[j] = 0;
+
+ /* Average production over the last eight months. */
+ i->average_production[j] = (i->average_production[j] * 7 + i->last_month_production[j]) / 8;
}
}
}
@@ -2697,6 +2745,7 @@ void IndustryMonthlyLoop()
Industry *i;
FOR_ALL_INDUSTRIES(i) {
UpdateIndustryStatistics(i);
+ UpdateIndustryAcceptance(i);
if (i->prod_level == PRODLEVEL_CLOSURE) {
delete i;
} else {
diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp
index 9db4a5a94..58550b1e2 100644
--- a/src/industry_gui.cpp
+++ b/src/industry_gui.cpp
@@ -38,6 +38,7 @@
#include "smallmap_gui.h"
#include "widgets/dropdown_type.h"
#include "widgets/industry_widget.h"
+#include "cargodest_gui.h"
#include "table/strings.h"
@@ -653,8 +654,11 @@ class IndustryViewWindow : public Window
int production_offset_y; ///< The offset of the production texts/buttons
int info_height; ///< Height needed for the #WID_IV_INFO panel
+ CargoDestinationList dest_list; ///< Sorted list of demand destinations.
+ int dest_list_top; ///< Top coordinate of the destination list.
+
public:
- IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
+ IndustryViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), dest_list(Industry::Get(window_number))
{
this->flags |= WF_DISABLE_VP_SCROLL;
this->editbox_line = IL_NONE;
@@ -792,6 +796,10 @@ public:
}
}
}
+
+ this->dest_list_top = y;
+ y = this->dest_list.DrawList(left, right, y);
+
return y + WD_FRAMERECT_BOTTOM;
}
@@ -811,6 +819,13 @@ public:
case WID_IV_INFO: {
Industry *i = Industry::Get(this->window_number);
InfoLine line = IL_NONE;
+ NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget);
+
+ /* Test for click on destination list. */
+ if (pt.y > this->dest_list_top) {
+ this->dest_list.OnClick(pt.y - this->dest_list_top);
+ return;
+ }
switch (this->editable) {
case EA_NONE: break;
@@ -835,7 +850,6 @@ public:
}
if (line == IL_NONE) return;
- NWidgetBase *nwi = this->GetWidget<NWidgetBase>(widget);
int left = nwi->pos_x + WD_FRAMETEXT_LEFT;
int right = nwi->pos_x + nwi->current_x - 1 - WD_FRAMERECT_RIGHT;
if (IsInsideMM(pt.x, left, left + SETTING_BUTTON_WIDTH)) {
@@ -963,6 +977,13 @@ public:
} else {
this->editable = EA_NONE;
}
+
+ /* Rebuild destination list if data is not zero, otherwise just resort. */
+ if (data != 0) {
+ this->dest_list.InvalidateData();
+ } else {
+ this->dest_list.Resort();
+ }
}
virtual bool IsNewGRFInspectable() const
diff --git a/src/landscape.cpp b/src/landscape.cpp
index a4b12f857..e39693f6e 100644
--- a/src/landscape.cpp
+++ b/src/landscape.cpp
@@ -722,11 +722,18 @@ void RunTileLoop()
* shift register (LFSR). This allows a deterministic pseudorandom ordering, but
* still with minimal state and fast iteration. */
- /* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) to 22-bit (for 2048x2048 maps).
+ /* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) over 22-bit (for 2048x2048 maps) to 32-bit.
* Extracted from http://www.ece.cmu.edu/~koopman/lfsr/ */
static const uint32 feedbacks[] = {
- 0xD8F, 0x1296, 0x2496, 0x4357, 0x8679, 0x1030E, 0x206CD, 0x403FE, 0x807B8, 0x1004B2, 0x2006A8
+ // 12 - 22
+ 0xD8F, 0x1296, 0x2496, 0x4357, 0x8679, 0x1030E, 0x206CD, 0x403FE, 0x807B8, 0x1004B2, 0x2006A8,
+ // 23 - 30
+ 0x4004B2, 0x800B87, 0x10004F3, 0x200072D, 0x40006AE, 0x80009E3, 0x10000583, 0x20000C92,
+ // 31 - 32
+ 0x400005B6, 0x80000EA6
};
+
+ assert(MapLogX() + MapLogY() <= 32);
const uint32 feedback = feedbacks[MapLogX() + MapLogY() - 12];
/* We update every tile every 256 ticks, so divide the map size by 2^8 = 256 */
diff --git a/src/lang/english.txt b/src/lang/english.txt
index f2f28f794..893a8fa77 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -431,6 +431,13 @@ STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION :Airport constru
STR_LANDSCAPING_MENU_LANDSCAPING :Landscaping
STR_LANDSCAPING_MENU_PLANT_TREES :Plant trees
STR_LANDSCAPING_MENU_PLACE_SIGN :Place sign
+STR_LANDSCAPING_MENU_SEPARATOR1 :
+STR_LANDSCAPING_MENU_UNDERGROUND :Special constructions
+STR_LANDSCAPING_MENU_SEPARATOR2 :
+STR_LANDSCAPING_MENU_LAYER_1 :Surface
+STR_LANDSCAPING_MENU_LAYER_2 :Undergroound (-1)
+STR_LANDSCAPING_MENU_LAYER_3 :Undergroound (-2)
+STR_LANDSCAPING_MENU_LAYER_4 :Undergroound (-3)
############ range ends here
############ range for music menu starts
@@ -661,17 +668,20 @@ STR_SMALLMAP_CAPTION :{WHITE}Map - {S
STR_SMALLMAP_TYPE_CONTOURS :Contours
STR_SMALLMAP_TYPE_VEHICLES :Vehicles
STR_SMALLMAP_TYPE_INDUSTRIES :Industries
+STR_SMALLMAP_TYPE_ROUTELINKS :Route links
STR_SMALLMAP_TYPE_ROUTES :Routes
STR_SMALLMAP_TYPE_VEGETATION :Vegetation
STR_SMALLMAP_TYPE_OWNERS :Owners
STR_SMALLMAP_TOOLTIP_SHOW_LAND_CONTOURS_ON_MAP :{BLACK}Show land contours on map
STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP :{BLACK}Show vehicles on map
STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP :{BLACK}Show industries on map
+STR_SMALLMAP_TOOLTIP_SHOW_ROUTE_LINKS_ON_MAP :{BLACK}Show route links on map
STR_SMALLMAP_TOOLTIP_SHOW_TRANSPORT_ROUTES_ON :{BLACK}Show transport routes on map
STR_SMALLMAP_TOOLTIP_SHOW_VEGETATION_ON_MAP :{BLACK}Show vegetation on map
STR_SMALLMAP_TOOLTIP_SHOW_LAND_OWNERS_ON_MAP :{BLACK}Show land owners on map
STR_SMALLMAP_TOOLTIP_INDUSTRY_SELECTION :{BLACK}Click on an industry type to toggle displaying it. Ctrl+Click disables all types except the selected one. Ctrl+Click on it again to enable all industry types
STR_SMALLMAP_TOOLTIP_COMPANY_SELECTION :{BLACK}Click on a company to toggle displaying its property. Ctrl+Click disables all companies except the selected one. Ctrl+Click on it again to enable all companies
+STR_SMALLMAP_TOOLTIP_ROUTELINK_SELECTION :{BLACK}Click on a cargo to toggle displaying the route links. Ctrl+Click disables all route links except the selected one. Ctrl+Click on it again to enable all route links
STR_SMALLMAP_LEGENDA_ROADS :{TINY_FONT}{BLACK}Roads
STR_SMALLMAP_LEGENDA_RAILROADS :{TINY_FONT}{BLACK}Railways
@@ -707,6 +717,7 @@ STR_SMALLMAP_CENTER :{BLACK}Centre t
STR_SMALLMAP_INDUSTRY :{TINY_FONT}{STRING} ({NUM})
STR_SMALLMAP_COMPANY :{TINY_FONT}{COMPANY}
STR_SMALLMAP_TOWN :{TINY_FONT}{WHITE}{TOWN}
+STR_SMALLMAP_CARGO :{TINY_FONT}{STRING}
STR_SMALLMAP_DISABLE_ALL :{BLACK}Disable all
STR_SMALLMAP_ENABLE_ALL :{BLACK}Enable all
STR_SMALLMAP_SHOW_HEIGHT :{BLACK}Show height
@@ -715,6 +726,8 @@ STR_SMALLMAP_TOOLTIP_ENABLE_ALL_INDUSTRIES :{BLACK}Display
STR_SMALLMAP_TOOLTIP_SHOW_HEIGHT :{BLACK}Toggle display of heightmap
STR_SMALLMAP_TOOLTIP_DISABLE_ALL_COMPANIES :{BLACK}Display no company property on the map
STR_SMALLMAP_TOOLTIP_ENABLE_ALL_COMPANIES :{BLACK}Display all company property on the map
+STR_SMALLMAP_TOOLTIP_DISABLE_ALL_ROUTELINKS :{BLACK}Display no route links on the map
+STR_SMALLMAP_TOOLTIP_ENABLE_ALL_ROUTELINKS :{BLACK}Display route links for all cargoes on the map
# Status bar messages
STR_STATUSBAR_TOOLTIP_SHOW_LAST_NEWS :{BLACK}Show last message or news report
@@ -1340,8 +1353,23 @@ STR_CONFIG_SETTING_LOADING_INDICATORS :Use loading ind
STR_CONFIG_SETTING_LOADING_INDICATORS_HELPTEXT :Select whether loading indicators are displayed above loading or unloading vehicles
STR_CONFIG_SETTING_TIMETABLE_IN_TICKS :Show timetable in ticks rather than days: {STRING2}
STR_CONFIG_SETTING_TIMETABLE_IN_TICKS_HELPTEXT :Show travel times in time tables in game ticks instead of days
+STR_CONFIG_SETTING_TIME_IN_MINUTES :Show time in minutes rather than days: {STRING2}
+STR_CONFIG_SETTING_TIME_IN_MINUTES_HELPTEXT :Select whether to use hours and minutes instead of days
+STR_CONFIG_SETTING_TICKS_PER_MINUTE :Ticks per minute: {STRING2}
+STR_CONFIG_SETTING_TICKS_PER_MINUTE_HELPTEXT :The number of game ticks per minute
+STR_CONFIG_SETTING_DATE_WITH_TIME :Show date with time in status bar: {STRING2}
+STR_CONFIG_SETTING_DATE_WITH_TIME_HELPTEXT :Show the real game date in the status bar as well as the time
+STR_CONFIG_SETTING_CLOCK_OFFSET :Clock offset in minutes: {STRING2}
+STR_CONFIG_SETTING_CLOCK_OFFSET_HELPTEXT :The number of minutes the game clock is offset by
+STR_CONFIG_SETTING_DATE_WITH_TIME_NONE :None
+STR_CONFIG_SETTING_DATE_WITH_TIME_Y :Year
+STR_CONFIG_SETTING_DATE_WITH_TIME_YM :Month and year
+STR_CONFIG_SETTING_DATE_WITH_TIME_YMD :Full date
+STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY :Enter timetable start times as text (requires time to be in minutes): {STRING2}
+STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY_HELPTEXT :Select whether timetable start times may be entered as text if time is being shown in minutes
STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE :Show arrival and departure in timetables: {STRING2}
STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE_HELPTEXT :Display anticipated arrival and departure times in timetables
+STR_CONFIG_SETTING_TIMETABLE_ENABLE_SEPARATION :{LTBLUE}Enable automatic timetable separation: {ORANGE}{STRING1}
STR_CONFIG_SETTING_QUICKGOTO :Quick creation of vehicle orders: {STRING2}
STR_CONFIG_SETTING_QUICKGOTO_HELPTEXT :Pre-select the 'goto cursor' when opening the orders window
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE :Default rail type (after new game/game load): {STRING2}
@@ -1467,6 +1495,8 @@ STR_CONFIG_SETTING_ALLOW_SHARES :Allow buying sh
STR_CONFIG_SETTING_ALLOW_SHARES_HELPTEXT :When enabled, allow buying and selling of company shares. Shares will only be available for companies reaching a certain age
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Percentage of leg profit to pay in feeder systems: {STRING2}
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE_HELPTEXT :Percentage of income given to the intermediate legs in feeder systems, giving more control over the income
+STR_CONFIG_SETTING_SIMULATE_SIGNALS :Simulate signals in tunnels, bridges every: {STRING2}
+STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE :{COMMA} tile{P 0 "" s}
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY :When dragging, place signals every: {STRING2}
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_HELPTEXT :Set the distance at which signals will be built on a track up to the next obstacle (signal, junction), if signals are dragged
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} tile{P 0 "" s}
@@ -1486,6 +1516,7 @@ STR_CONFIG_SETTING_CYCLE_SIGNAL_TYPES_HELPTEXT :Select which si
STR_CONFIG_SETTING_CYCLE_SIGNAL_NORMAL :Block signals only
STR_CONFIG_SETTING_CYCLE_SIGNAL_PBS :Path signals only
STR_CONFIG_SETTING_CYCLE_SIGNAL_ALL :All
+STR_CONFIG_PATCHES_RANDOM_ROAD_CONSTRUCTION :Probability of random road construction (0 = off): {STRING2}
STR_CONFIG_SETTING_TOWN_LAYOUT :Road layout for new towns: {STRING2}
STR_CONFIG_SETTING_TOWN_LAYOUT_HELPTEXT :Layout for the road network of towns
@@ -1549,6 +1580,27 @@ STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER :Initial city si
STR_CONFIG_SETTING_CITY_SIZE_MULTIPLIER_HELPTEXT :Average size of cities relative to normal towns at start of the game
STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD :Remove absurd road-elements during the road construction: {STRING2}
STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD_HELPTEXT :Remove dead road ends during funded road reconstruction
+STR_CONFIG_SETTING_TRAFFIC_LIGHTS :Enable traffic lights:{STRING2}
+STR_CONFIG_SETTING_TRAFFIC_LIGHTS_HELPTEXT :Enables/disables building of traffic lights completely
+STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS :Towns can build traffic lights: {STRING2}
+STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS_HELPTEXT :Allows/dissallows towns to build traffic lights during road construction
+STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS :Build and remove traffic lights on town roads: {STRING2}
+STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS_HELPTEXT :Allows/disallows the players building and removing traffic lights on town roads
+STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE :Length of traffic lights' green phase: {STRING2}
+STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE_HELPTEXT :Time in seconds that traffic lights stay green
+STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE :Maximum TL consist size: {STRING2}
+STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE_HELPTEXT :Maximum number of trafficlights that can be synchronised. Warning: High setting slow down the game
+STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE :Maximum distance between two trafficlights for synchronising: {STRING2}
+STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE_HELPTEXT :Sets the maximum tile distance between two trafficlights for them to be synchronised
+
+STR_CONFIG_SETTING_CARGODEST_PAX :Destination mode for passengers/mail: {STRING2}
+STR_CONFIG_SETTING_CARGODEST_PAX_HELPTEXT :Enable passengers and mail choosing destinations on their own
+STR_CONFIG_SETTING_CARGODEST_TOWN :Destination mode for town-accepted cargoes: {STRING2}
+STR_CONFIG_SETTING_CARGODEST_TOWN_HELPTEXT :Enable town-accepted cargoes choosing destinations on their own
+STR_CONFIG_SETTING_CARGODEST_OTHER :Destination mode for other cargoes: {STRING2}
+STR_CONFIG_SETTING_CARGODEST_OTHER_HELPTEXT :Enable other cargoes choosing destinations on their own
+STR_CONFIG_SETTING_CARGODEST_MODE_OFF :Original
+STR_CONFIG_SETTING_CARGODEST_MODE_DEST :Fixed destinations
STR_CONFIG_SETTING_GUI :{ORANGE}Interface
STR_CONFIG_SETTING_CONSTRUCTION :{ORANGE}Construction
@@ -1561,6 +1613,7 @@ STR_CONFIG_SETTING_INTERACTION :{ORANGE}Interac
STR_CONFIG_SETTING_SOUND :{ORANGE}Sound effects
STR_CONFIG_SETTING_NEWS :{ORANGE}News and messages
STR_CONFIG_SETTING_CONSTRUCTION_SIGNALS :{ORANGE}Signals
+STR_CONFIG_SETTING_CONSTRUCTION_TRAFFIC_LIGHTS :{ORANGE}Traffic lights
STR_CONFIG_SETTING_STATIONS_CARGOHANDLING :{ORANGE}Cargo handling
STR_CONFIG_SETTING_AI_NPC :{ORANGE}Computer players
STR_CONFIG_SETTING_VEHICLES_AUTORENEW :{ORANGE}Autorenew
@@ -2261,6 +2314,7 @@ STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION :{BLACK}Build pa
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY :{BLACK}Build lorry loading bay. Ctrl enables joining stations. Shift toggles building/showing cost estimate
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION :{BLACK}Build freight tram station. Ctrl enables joining stations. Shift toggles building/showing cost estimate
STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD :{BLACK}Activate/Deactivate one way roads
+STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT :{BLACK}Build traffic light
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE :{BLACK}Build road bridge. Shift toggles building/showing cost estimate
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_BRIDGE :{BLACK}Build tramway bridge. Shift toggles building/showing cost estimate
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_TUNNEL :{BLACK}Build road tunnel. Shift toggles building/showing cost estimate
@@ -2490,6 +2544,7 @@ STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_STREETLIGHTS :Road with stree
STR_LAI_ROAD_DESCRIPTION_TREE_LINED_ROAD :Tree-lined road
STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Road vehicle depot
STR_LAI_ROAD_DESCRIPTION_ROAD_RAIL_LEVEL_CROSSING :Road/rail level crossing
+STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_TRAFFIC_LIGHTS :Road with traffic lights
STR_LAI_ROAD_DESCRIPTION_TRAMWAY :Tramway
# Houses come directly from their building names
@@ -2518,8 +2573,10 @@ STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot
# Industries come directly from their industry names
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Railway tunnel
+STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL :Railway tunnel with signal simulation
STR_LAI_TUNNEL_DESCRIPTION_ROAD :Road tunnel
+STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL :Railway bridge with signal simulation
STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Steel suspension rail bridge
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Steel girder rail bridge
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Steel cantilever rail bridge
@@ -2812,6 +2869,13 @@ STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP :{BLACK}Go to pr
STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Enter a name for the sign
+# Cargodest UI strings
+STR_VIEW_CARGO_LAST_MONTH_OUT :{BLACK}Outgoing cargo last month:
+STR_VIEW_CARGO_LAST_MONTH_TOWN :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to {TOWN}
+STR_VIEW_CARGO_LAST_MONTH_INDUSTRY :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to {INDUSTRY}
+STR_VIEW_CARGO_LAST_MONTH_LOCAL :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to local destinations
+STR_VIEW_CARGO_LAST_MONTH_OTHER :{BLACK}{CARGO_SHORT} out of {CARGO_LONG} to other destinations
+
# Town directory window
STR_TOWN_DIRECTORY_CAPTION :{WHITE}Towns
STR_TOWN_DIRECTORY_NONE :{ORANGE}- None -
@@ -2930,9 +2994,20 @@ STR_STATION_LIST_NO_WAITING_CARGO :{BLACK}No cargo
# Station view window
STR_STATION_VIEW_CAPTION :{WHITE}{STATION} {STATION_FEATURES}
+STR_STATION_VIEW_WAITING_BUTTON :{BLACK}Source
+STR_STATION_VIEW_WAITING_TOOLTIP :{BLACK}Show list of waiting cargo with its source
+STR_STATION_VIEW_WAITING_TO_BUTTON :{BLACK}Destination
+STR_STATION_VIEW_WAITING_TO_TOOLTIP :{BLACK}Show list of waiting cargo with its destination
+STR_STATION_VIEW_WAITING_VIA_BUTTON :{BLACK}Next hop
+STR_STATION_VIEW_WAITING_VIA_TOOLTIP :{BLACK}Show list of waiting cargo with its next hop
+STR_STATION_VIEW_WAITING_TRANSFER_BUTTON :{BLACK}Transfer
+STR_STATION_VIEW_WAITING_TRANSFER_TOOLTIP :{BLACK}Show list of waiting cargo with its next transfer station
STR_STATION_VIEW_WAITING_TITLE :{BLACK}Waiting: {WHITE}{STRING}
STR_STATION_VIEW_WAITING_CARGO :{WHITE}{CARGO_LONG}
STR_STATION_VIEW_EN_ROUTE_FROM :{YELLOW}({CARGO_SHORT} en-route from {STATION})
+STR_STATION_VIEW_WAITING_VIA :{YELLOW}{CARGO_SHORT} en-route via {STATION}
+STR_STATION_VIEW_WAITING_TRANSFER :{YELLOW}{CARGO_SHORT} transfering at {STATION}
+STR_STATION_VIEW_WAITING_TO :{YELLOW}{CARGO_SHORT} en-route to {STRING1}
STR_STATION_VIEW_ACCEPTS_BUTTON :{BLACK}Accepts
STR_STATION_VIEW_ACCEPTS_TOOLTIP :{BLACK}Show list of accepted cargo
@@ -2946,6 +3021,9 @@ STR_STATION_VIEW_RATINGS_TOOLTIP :{BLACK}Show sta
STR_STATION_VIEW_CARGO_RATINGS_TITLE :{BLACK}Local rating of transport service:
STR_STATION_VIEW_CARGO_RATING :{WHITE}{STRING}: {YELLOW}{STRING} ({COMMA}%)
+STR_STATION_VIEW_DEPARTURES_BUTTON :{BLACK}Departures
+STR_STATION_VIEW_DEPARTURES_TOOLTIP :{BLACK}Show list of scheduled departures
+
############ range for rating starts
STR_CARGO_RATING_APPALLING :Appalling
STR_CARGO_RATING_VERY_POOR :Very Poor
@@ -2966,10 +3044,102 @@ STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP :{BLACK}Show all
STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP :{BLACK}Show all ships which have this station on their schedule
STR_STATION_VIEW_RENAME_STATION_CAPTION :Rename station/loading area
-
STR_STATION_VIEW_CLOSE_AIRPORT :{BLACK}Close airport
STR_STATION_VIEW_CLOSE_AIRPORT_TOOLTIP :{BLACK}Prevent aircraft from landing on this airport
+# Departures window
+STR_DEPARTURES_CAPTION :{WHITE}{STATION} Live Travel Information
+STR_DEPARTURES_CAPTION_WAYPOINT :{WHITE}{WAYPOINT} Live Travel Information
+STR_DEPARTURES_DEPARTURES :{BLACK}{TINY_FONT}D
+STR_DEPARTURES_ARRIVALS :{BLACK}{TINY_FONT}A
+STR_DEPARTURES_VIA_BUTTON :{BLACK}{TINY_FONT}via
+STR_DEPARTURES_DEPARTURES_TOOLTIP :{BLACK}Show timetabled departures
+STR_DEPARTURES_ARRIVALS_TOOLTIP :{BLACK}Show timetabled arrivals
+STR_DEPARTURES_VIA_TOOLTIP :{BLACK}Show timetabled vehicles that do not stop here
+STR_DEPARTURES_EMPTY :{ORANGE}No vehicles are currently timetabled for this station.
+STR_DEPARTURES_NONE_SELECTED :{ORANGE}No timetable information has been requested.
+STR_DEPARTURES_TIME :{ORANGE}{DATE_WALLCLOCK_TINY}
+STR_DEPARTURES_TIME_DEP :{ORANGE}{DATE_WALLCLOCK_TINY} {GREEN}{UP_ARROW}
+STR_DEPARTURES_TIME_ARR :{ORANGE}{DATE_WALLCLOCK_TINY} {RED}{DOWN_ARROW}
+STR_DEPARTURES_TIME_BOTH :{ORANGE}{1:DATE_WALLCLOCK_TINY} {RED}{DOWN_ARROW} {ORANGE}{0:DATE_WALLCLOCK_TINY} {GREEN}{UP_ARROW}
+STR_DEPARTURES_TERMINUS :{ORANGE}{STATION}{STRING}
+STR_DEPARTURES_TERMINUS_VIA_STATION :{ORANGE}{STATION}{STRING} via {STATION}{STRING}
+STR_DEPARTURES_TERMINUS_VIA :{ORANGE}{STATION}{STRING} via
+STR_DEPARTURES_VIA :{ORANGE}via {STATION}{STRING}
+STR_DEPARTURES_TOC :{ORANGE}{COMPANY}
+STR_DEPARTURES_GROUP :{ORANGE}{GROUP}
+STR_DEPARTURES_VEH :{ORANGE}{VEHICLE}
+STR_DEPARTURES_CALLING_AT :{TINY_FONT}{ORANGE}Calling at:
+STR_DEPARTURES_CALLING_AT_LARGE :{ORANGE}Calling at:
+STR_DEPARTURES_CALLING_AT_FIRST_STATION :{STATION}
+STR_DEPARTURES_CALLING_AT_STATION :{RAW_STRING}, {STATION}
+STR_DEPARTURES_CALLING_AT_LAST_STATION :{RAW_STRING} and {STATION}
+STR_DEPARTURES_CALLING_AT_LIST :{TINY_FONT}{ORANGE}{RAW_STRING}.
+STR_DEPARTURES_CALLING_AT_LIST_LARGE :{ORANGE}{RAW_STRING}.
+STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS :{TINY_FONT}{ORANGE}{RAW_STRING}. This service continues to {STATION}.
+STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS_LARGE :{ORANGE}{RAW_STRING}. This service continues to {STATION}.
+
+STR_DEPARTURES_TYPE_TRAIN :{ORANGE}{TRAIN}
+STR_DEPARTURES_TYPE_TRAIN_SILVER :{SILVER}{TRAIN}
+STR_DEPARTURES_TYPE_BUS :{ORANGE}{BUS}
+STR_DEPARTURES_TYPE_BUS_SILVER :{SILVER}{BUS}
+STR_DEPARTURES_TYPE_LORRY :{ORANGE}{LORRY}
+STR_DEPARTURES_TYPE_LORRY_SILVER :{SILVER}{LORRY}
+STR_DEPARTURES_TYPE_PLANE :{ORANGE}{PLANE}
+STR_DEPARTURES_TYPE_PLANE_SILVER :{SILVER}{PLANE}
+STR_DEPARTURES_TYPE_SHIP :{ORANGE}{SHIP}
+STR_DEPARTURES_TYPE_SHIP_SILVER :{SILVER}{SHIP}
+
+STR_DEPARTURES_STATION_NONE :
+STR_DEPARTURES_STATION_PORT :{ORANGE} {SHIP}
+STR_DEPARTURES_STATION_AIRPORT :{ORANGE} {PLANE}
+STR_DEPARTURES_STATION_PORTAIRPORT :{ORANGE} {SHIP} {PLANE}
+
+############ possible statuses start
+STR_DEPARTURES_ON_TIME :{GREEN}On time
+STR_DEPARTURES_ARRIVED :{GREEN}Arrived
+STR_DEPARTURES_DELAYED :{YELLOW}Delayed
+STR_DEPARTURES_EXPECTED :{YELLOW}Expt {DATE_WALLCLOCK_TINY}
+STR_DEPARTURES_CANCELLED :{RED}Cancelled
+
+############ config settings
+STR_CONFIG_SETTING_DEPARTUREBOARDS :{ORANGE}Departure boards
+STR_CONFIG_MAX_DEPARTURES :Show at most {STRING2} departures at each station
+STR_CONFIG_MAX_DEPARTURES_HELPTEXT :The maximum number of departures to show on a departure board
+STR_CONFIG_MAX_DEPARTURE_TIME :Show departures at most {STRING2} days in advance
+STR_CONFIG_MAX_DEPARTURE_TIME_HELPTEXT :How far in advance to show departures, in days
+STR_CONFIG_DEPARTURE_CALC_FREQUENCY :Calculate departures every {STRING2} ticks
+STR_CONFIG_DEPARTURE_CALC_FREQUENCY_HELPTEXT :How frequently to refresh a list of departures, in ticks
+STR_CONFIG_DEPARTURE_VEHICLE_NAME :Show vehicle name with departures: {STRING2}
+STR_CONFIG_DEPARTURE_VEHICLE_NAME_HELPTEXT :Whether to show vehicle names next to their departures
+STR_CONFIG_DEPARTURE_GROUP_NAME :Show group name with departures: {STRING2}
+STR_CONFIG_DEPARTURE_GROUP_NAME_HELPTEXT :Whether to show names of groups that a vehicle belongs to next to its departures
+STR_CONFIG_DEPARTURE_COMPANY_NAME :Show company name with departures: {STRING2}
+STR_CONFIG_DEPARTURE_COMPANY_NAME_HELPTEXT :Whether to show a company's name next to its vehicles' departures
+STR_CONFIG_DEPARTURE_VEHICLE_TYPE :Show vehicle type icon with departures: {STRING2}
+STR_CONFIG_DEPARTURE_VEHICLE_TYPE_HELPTEXT :Whether to show a vehicle's type as an icon next to its departures
+STR_CONFIG_DEPARTURE_VEHICLE_COLOR :Show vehicle type icon in silver: {STRING2}
+STR_CONFIG_DEPARTURE_VEHICLE_COLOR_HELPTEXT :Whether to show vehicle type icons in silver
+STR_CONFIG_DEPARTURE_LARGER_FONT :Use larger font for stations called at on departure boards: {STRING2}
+STR_CONFIG_DEPARTURE_LARGER_FONT_HELPTEXT :Whether to use a larger font for lists of stations called at
+STR_CONFIG_DEPARTURE_DESTINATION_TYPE :Show icons for destinations that are docks or airports: {STRING2}
+STR_CONFIG_DEPARTURE_DESTINATION_TYPE_HELPTEXT :Whether to show icons next to destinations that are docks or airports
+STR_CONFIG_DEPARTURE_SHOW_BOTH :Show arrival and departure times on the same line: {STRING2}
+STR_CONFIG_DEPARTURE_SHOW_BOTH_HELPTEXT :Whether to show both arrival and departure times next to departures
+STR_CONFIG_DEPARTURE_ONLY_PASSENGERS :Only show departures for vehicles that can carry passengers: {STRING2}
+STR_CONFIG_DEPARTURE_ONLY_PASSENGERS_HELPTEXT :Whether to only show departures of vehicles that can carry passengers
+STR_CONFIG_DEPARTURE_SMART_TERMINUS :Don't show termini that can be reached sooner on a later vehicle: {STRING2}
+STR_CONFIG_DEPARTURE_SMART_TERMINUS_HELPTEXT :Whether to show termini that can be reached sooner on another vehicle that departs later
+STR_CONFIG_DEPARTURE_CONDITIONALS :Handle conditional order jumps by: {STRING2}
+STR_CONFIG_DEPARTURE_CONDITIONALS_HELPTEXT :How conditional orders should be dealt with when calculating departures
+STR_CONFIG_DEPARTURE_CONDITIONALS_1 :giving up
+STR_CONFIG_DEPARTURE_CONDITIONALS_2 :assuming they will be taken
+STR_CONFIG_DEPARTURE_CONDITIONALS_3 :assuming they will not be taken
+STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS :Show all stations called at regardless of loading/unloading: {STRING2}
+STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS_HELPTEXT :Whether stations that a vehicle only loads from will be shown in the calling at list
+STR_CONFIG_DEPARTURE_MERGE_IDENTICAL :Merge identical departures: {STRING2}
+STR_CONFIG_DEPARTURE_MERGE_IDENTICAL_HELPTEXT :Whether identical departures should be merged into a single departure
+
# Waypoint/buoy view window
STR_WAYPOINT_VIEW_CAPTION :{WHITE}{WAYPOINT}
STR_WAYPOINT_VIEW_CENTER_TOOLTIP :{BLACK}Centre main view on waypoint location. Ctrl+Click opens a new viewport on waypoint location
@@ -3472,6 +3642,7 @@ STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT :{LTBLUE}- {CARG
STR_VEHICLE_DETAILS_CARGO_EMPTY :{LTBLUE}Empty
STR_VEHICLE_DETAILS_CARGO_FROM :{LTBLUE}{CARGO_LONG} from {STATION}
STR_VEHICLE_DETAILS_CARGO_FROM_MULT :{LTBLUE}{CARGO_LONG} from {STATION} (x{NUM})
+STR_VEHICLE_DETAILS_CARGO_TO :{LTBLUE}{CARGO_SHORT} to {STRING1}
STR_VEHICLE_DETAIL_TAB_CARGO :{BLACK}Cargo
STR_VEHICLE_DETAILS_TRAIN_CARGO_TOOLTIP :{BLACK}Show details of cargo carried
@@ -3680,6 +3851,7 @@ STR_TIMETABLE_STAY_FOR :and stay for {S
STR_TIMETABLE_AND_TRAVEL_FOR :and travel for {STRING1}
STR_TIMETABLE_DAYS :{COMMA} day{P "" s}
STR_TIMETABLE_TICKS :{COMMA} tick{P "" s}
+STR_TIMETABLE_MINUTES :{COMMA} minute{P "" s}
STR_TIMETABLE_TOTAL_TIME :{BLACK}This timetable will take {STRING1} to complete
STR_TIMETABLE_TOTAL_TIME_INCOMPLETE :{BLACK}This timetable will take at least {STRING1} to complete (not all timetabled)
@@ -3726,6 +3898,8 @@ STR_DATE_SET_DATE_TOOLTIP :{BLACK}Use the
STR_DATE_DAY_TOOLTIP :{BLACK}Select day
STR_DATE_MONTH_TOOLTIP :{BLACK}Select month
STR_DATE_YEAR_TOOLTIP :{BLACK}Select year
+STR_DATE_MINUTES_DAY_TOOLTIP :{BLACK}Select minute
+STR_DATE_MINUTES_MONTH_TOOLTIP :{BLACK}Select hour
# AI debug window
@@ -3830,6 +4004,27 @@ STR_FEEDER :{YELLOW}Transfe
STR_MESSAGE_ESTIMATED_COST :{WHITE}Estimated Cost: {CURRENCY_LONG}
STR_MESSAGE_ESTIMATED_INCOME :{WHITE}Estimated Income: {CURRENCY_LONG}
+# Timetable separation UI strings
+STR_TTSEPARATION_AUTO :Auto
+STR_TTSEPARATION_MAN_NUM :Manual Number
+STR_TTSEPARATION_MAN_TIME :Manual Time
+STR_TTSEPARATION_BUFFERED_AUTO :Buffered Auto
+STR_TTSEPARATION_OFF :Off
+STR_TTSEPARATION_APPLY :{BLACK}Apply
+STR_TTSEPARATION_RESET :{BLACK}Reset
+STR_TTSEPARATION_MODE_DESC :{BLACK}Separation mode
+STR_TTSEPARATION_SETTINGS_DESC :{BLACK}Separation settings
+STR_TTSEPARATION_SET_XX :{BLACK}Set {STRING}
+STR_TTSEPARATION_SET_NUM :number
+STR_TTSEPARATION_SET_TIME :time
+STR_TTSEPARATION_STATUS_DESC :{BLACK}Status:{}{STRING}
+STR_TTSEPARATION_STATUS_INIT :{RED}Initializing
+STR_TTSEPARATION_STATUS_RUNNING :{GREEN}Running
+STR_TTSEPARATION_STATUS_OFF :{BLACK}Off
+STR_TTSEPARATION_REQ_TIME_DESC_TICKS :{NUM} ticks between
+STR_TTSEPARATION_REQ_TIME_DESC_DAYS :{NUM} days between
+STR_TTSEPARATION_REQ_NUM_DESC :{NUM} vehicles
+
# Saveload messages
STR_ERROR_SAVE_STILL_IN_PROGRESS :{WHITE}Saving still in progress,{}please wait until it is finished!
STR_ERROR_AUTOSAVE_FAILED :{WHITE}Autosave failed
@@ -4071,6 +4266,13 @@ STR_ERROR_CAN_T_BUILD_ROAD_HERE :{WHITE}Can't bu
STR_ERROR_CAN_T_BUILD_TRAMWAY_HERE :{WHITE}Can't build tramway here...
STR_ERROR_CAN_T_REMOVE_ROAD_FROM :{WHITE}Can't remove road from here...
STR_ERROR_CAN_T_REMOVE_TRAMWAY_FROM :{WHITE}Can't remove tramway from here...
+STR_ERROR_CAN_T_REMOVE_TRAFFIC_LIGHTS_FROM :{WHITE}Can't remove traffic lights from here...
+STR_ERROR_MUST_REMOVE_TRAFFIC_LIGHTS_FIRST :{WHITE}... must remove traffic lights first
+STR_ERROR_TRAFFIC_LIGHT_CONSIST_TOO_BIG :{WHITE}... traffic light consist too big
+STR_ERROR_CAN_T_PLACE_TRAFFIC_LIGHTS :{WHITE}Can't place traffic lights...
+STR_ERROR_CAN_ONLY_BE_PLACED_ON_ROAD_JUNCTIONS :{WHITE}... can only be placed on road junctions
+STR_ERROR_BUILDING_TRAFFIC_LIGHTS_DISABLED :{WHITE}... building trafficlights is disabled, enable it in Advanced settings -> Construction -> Traffic lights
+STR_ERROR_TRAFFIC_LIGHTS_NOT_ALLOWED_ON_TOWN_ROADS :{WHITE}... building trafficlights on town roads is disabled, enable it in Advanced settings -> Construction -> Traffic lights
STR_ERROR_THERE_IS_NO_ROAD :{WHITE}... there is no road
STR_ERROR_THERE_IS_NO_TRAMWAY :{WHITE}... there is no tramway
@@ -4602,6 +4804,11 @@ STR_FORMAT_DATE_SHORT :{STRING} {NUM}
STR_FORMAT_DATE_LONG :{STRING} {STRING} {NUM}
STR_FORMAT_DATE_ISO :{2:NUM}-{1:RAW_STRING}-{0:RAW_STRING}
+STR_FORMAT_DATE_MINUTES :{0:RAW_STRING}:{1:RAW_STRING}
+STR_FORMAT_DATE_MINUTES_WITH_Y :{0:RAW_STRING}:{1:RAW_STRING} / {2:NUM}
+STR_FORMAT_DATE_MINUTES_WITH_YM :{0:RAW_STRING}:{1:RAW_STRING} / {2:DATE_SHORT}
+STR_FORMAT_DATE_MINUTES_WITH_YMD :{0:RAW_STRING}:{1:RAW_STRING} / {2:DATE_LONG}
+
STR_FORMAT_BUOY_NAME :{TOWN} Buoy
STR_FORMAT_BUOY_NAME_SERIAL :{TOWN} Buoy #{COMMA}
STR_FORMAT_COMPANY_NUM :(Company {COMMA})
@@ -4665,6 +4872,10 @@ STR_JUST_DATE_TINY :{DATE_TINY}
STR_JUST_DATE_SHORT :{DATE_SHORT}
STR_JUST_DATE_LONG :{DATE_LONG}
STR_JUST_DATE_ISO :{DATE_ISO}
+STR_JUST_DATE_WALLCLOCK_TINY :{DATE_WALLCLOCK_TINY}
+STR_JUST_DATE_WALLCLOCK_SHORT :{DATE_WALLCLOCK_SHORT}
+STR_JUST_DATE_WALLCLOCK_LONG :{DATE_WALLCLOCK_LONG}
+STR_JUST_DATE_WALLCLOCK_ISO :{DATE_WALLCLOCK_ISO}
STR_JUST_STRING :{STRING}
STR_JUST_STRING_STRING :{STRING}{STRING}
STR_JUST_RAW_STRING :{RAW_STRING}
@@ -4682,6 +4893,8 @@ STR_BLACK_DATE_LONG :{BLACK}{DATE_LO
STR_BLACK_CROSS :{BLACK}{CROSS}
STR_SILVER_CROSS :{SILVER}{CROSS}
STR_WHITE_DATE_LONG :{WHITE}{DATE_LONG}
+STR_WHITE_DATE_WALLCLOCK_LONG :{WHITE}{DATE_WALLCLOCK_LONG}
+STR_WHITE_DATE_WALLCLOCK_SHORT :{WHITE}{DATE_WALLCLOCK_SHORT}
STR_SHORT_DATE :{WHITE}{DATE_TINY}
STR_DATE_LONG_SMALL :{TINY_FONT}{BLACK}{DATE_LONG}
STR_TINY_GROUP :{TINY_FONT}{GROUP}
@@ -4717,3 +4930,35 @@ STR_PLANE :{BLACK}{PLANE}
STR_SHIP :{BLACK}{SHIP}
STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY})
+
+# underground
+
+# error
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND :{WHITE}Can't build this underground
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_OVER_GROUND :{WHITE}Can't build this on surface
+STR_ERROR_UNDERGROUND_CAN_T_TERRAFORM :{WHITE}Can't terraform underground
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_PART :{WHITE}Can't build escalator here...
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_TOP_PART :{WHITE}Can't build escalator top here...
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_BOTTOM_PART :{WHITE}Can't build escalator bottom here...
+
+# menus
+STR_UNDERGROUND_LAYER_COUNT :{BLACK}Layers count
+STR_UNDERGROUND_BUILD :{WHITE}Underground
+
+# underground land types
+STR_UNDERGROUND_FLAT :Cave
+STR_UNDERGROUND_SOLID :Ground
+STR_UNDERGROUND_ROCKS :Rocks
+STR_UNDERGROUND_HEAVY_ROCKS :Hard rocks
+
+# underground special build types
+STR_UNDERGROUND_ESCALATOR :Escalator
+STR_UNDERGROUND_CONNECT :Connect
+STR_UNDERGROUND_PIPE :Pipe
+
+# underground tool tip
+STR_UNDERGROUND_TOOLTIP_ESCALATOR :{BLACK}Build escalator (underground station connect with surface)
+STR_UNDERGROUND_TOOLTIP_CONNECT :{BLACK}Build connect (underground track connect with surface)
+STR_UNDERGROUND_TOOLTIP_PIPE :{BLACK}Pipe (xm... anything...)
+
+# end underground string
diff --git a/src/lang/hungarian.txt b/src/lang/hungarian.txt
index c22dc927e..c54ddcff0 100644
--- a/src/lang/hungarian.txt
+++ b/src/lang/hungarian.txt
@@ -3010,6 +3010,35 @@ STR_STATION_VIEW_RATINGS_TOOLTIP :{BLACK}VГ©lemГ©
STR_STATION_VIEW_CARGO_RATINGS_TITLE :{BLACK}A szГЎllГ­tГЎs helyi megГ­tГ©lГ©se:
STR_STATION_VIEW_CARGO_RATING :{WHITE}{STRING}: {YELLOW}{STRING} ({COMMA}%)
+STR_STATION_VIEW_DEPARTURES_BUTTON :{BLACK}Menetrend
+STR_STATION_VIEW_DEPARTURES_TOOLTIP :{BLACK}Mutasd az ГЎllomГЎs menetrendjГ©t
+
+# Departures window
+STR_DEPARTURES_CAPTION :{WHITE}{STATION} AktuГЎlis menetrend
+STR_DEPARTURES_CAPTION_WAYPOINT :{WHITE}{WAYPOINT} AktuГЎlis menetrend
+STR_DEPARTURES_DEPARTURES :{BLACK}{TINY_FONT}Ind
+STR_DEPARTURES_ARRIVALS :{BLACK}{TINY_FONT}Г‰rk
+STR_DEPARTURES_VIA_BUTTON :{BLACK}{TINY_FONT}ГЃth
+STR_DEPARTURES_DEPARTURES_TOOLTIP :{BLACK}Mutasd az indulГЎsi idЕ‘pontokat
+STR_DEPARTURES_ARRIVALS_TOOLTIP :{BLACK}Mutasd az Г©rkezГ©si idЕ‘pontokat
+STR_DEPARTURES_VIA_TOOLTIP :{BLACK}Mutasd a keresztГјlhaladГі forgalmat
+STR_DEPARTURES_EMPTY :{ORANGE}Ez az ГЎllomГЎs mГ©g nincs benne egy jГЎrmЕ± menetrendjГ©ben sem.
+STR_DEPARTURES_NONE_SELECTED :{ORANGE}Nincs megjelenГ­thetЕ‘ menetrend
+
+############ possible statuses start
+STR_DEPARTURES_ON_TIME :{GREEN}IdЕ‘ben
+STR_DEPARTURES_ARRIVED :{GREEN}MegГ©rkezett
+STR_DEPARTURES_DELAYED :{YELLOW}KГ©sik
+STR_DEPARTURES_EXPECTED :{YELLOW}VГЎrhatГі {DATE_WALLCLOCK_TINY}
+STR_DEPARTURES_CANCELLED :{RED}Törölve
+
+############ config settings
+STR_CONFIG_SETTING_DEPARTUREBOARDS :{ORANGE}Menetrendek
+STR_TIMETABLE_MINUTES :{COMMA} perc
+STR_DATE_MINUTES_DAY_TOOLTIP :{BLACK}VГЎlassz percet
+STR_DATE_MINUTES_MONTH_TOOLTIP :{BLACK}VГЎlassz ГіrГЎt
+
+
############ range for rating starts
STR_CARGO_RATING_APPALLING :szörnyű
STR_CARGO_RATING_VERY_POOR :nagyon rossz
diff --git a/src/lang/russian.txt b/src/lang/russian.txt
index 68b2f76ab..768ea7011 100644
--- a/src/lang/russian.txt
+++ b/src/lang/russian.txt
@@ -557,6 +557,13 @@ STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION :СтроитеР
STR_LANDSCAPING_MENU_LANDSCAPING :Ландшафт
STR_LANDSCAPING_MENU_PLANT_TREES :Высадка деревьев
STR_LANDSCAPING_MENU_PLACE_SIGN :Поставить метку
+STR_LANDSCAPING_MENU_SEPARATOR1 :
+STR_LANDSCAPING_MENU_UNDERGROUND :Спец. конструкции
+STR_LANDSCAPING_MENU_SEPARATOR2 :
+STR_LANDSCAPING_MENU_LAYER_1 :Поверхность
+STR_LANDSCAPING_MENU_LAYER_2 :Подземелье (-1)
+STR_LANDSCAPING_MENU_LAYER_3 :Подземелье (-2)
+STR_LANDSCAPING_MENU_LAYER_4 :Подземелье (-3)
############ range ends here
############ range for music menu starts
@@ -4940,3 +4947,35 @@ STR_PLANE :{BLACK}{PLANE}
STR_SHIP :{BLACK}{SHIP}
STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY})
+
+# underground
+
+# error
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND :{WHITE}Не может строиться под землей
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_OVER_GROUND :{WHITE}Не может строиться на поверхности
+STR_ERROR_UNDERGROUND_CAN_T_TERRAFORM :{WHITE}Рзменение ландшафта недоступно
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_PART :{WHITE}Нельзя построить эскалатор...
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_TOP_PART :{WHITE}Нельзя построить верхнюю часть эскалатора...
+STR_ERROR_UNDERGROUND_CAN_T_BUILD_BOTTOM_PART :{WHITE}Нельзя построить нижнюю часть эскалатора...
+
+# menus
+STR_UNDERGROUND_LAYER_COUNT :{BLACK}Количество слоев
+STR_UNDERGROUND_BUILD :{WHITE}Подземелье
+
+# underground land types
+STR_UNDERGROUND_FLAT :Пещера
+STR_UNDERGROUND_SOLID :Грунт
+STR_UNDERGROUND_ROCKS :Скалы
+STR_UNDERGROUND_HEAVY_ROCKS :Твердые породы
+
+# underground special build types
+STR_UNDERGROUND_ESCALATOR :Эскалатор
+STR_UNDERGROUND_CONNECT :Соединение
+STR_UNDERGROUND_PIPE :РўСЂСѓР±С‹
+
+# underground tool tip
+STR_UNDERGROUND_TOOLTIP_ESCALATOR :{BLACK}Постройка эскалатора (связь подземной станции с поверхностью)
+STR_UNDERGROUND_TOOLTIP_CONNECT :{BLACK}Постройка подъема (подземные рельсы выходят наружу)
+STR_UNDERGROUND_TOOLTIP_PIPE :{BLACK}Трубы (хм... что-то...)
+
+# end underground string
diff --git a/src/layer.cpp b/src/layer.cpp
new file mode 100644
index 000000000..b897c45a3
--- /dev/null
+++ b/src/layer.cpp
@@ -0,0 +1,137 @@
+/* $Id: map.cpp 23740 2012-01-03 21:32:51Z $ */
+/*
+
+Это модуль, для возможности полноценной игры в трех измерениях
+(подземелье, метро, итп.)
+
+
+Плоское игровое поле выглядит примерно так:
+*-----------*
+| |
+| |
+| |
+*-----------*
+
+Мысленно нарежем его на одинаковые части:
+*---*---*---*
+| | | |
+| | | |
+| | | |
+*---*---*---*
+
+Мысленно соберем части в вертикальную стопочку:
+*---*
+| *---*
+| | *---*
+| | | |
+* | | |
+ * | |
+ *---*
+
+Таким образом имея плоскую карту мы описываем 3д пространство.
+
+Для простоты вся карта делится только по оси Y (разрезы параллельны оси X)
+Деление происходит на 1, 2, 4, или 8 кусочков.
+
+Например было выбрано поле 64х64, с 4-мя слоями:
+Создается карта 64х256, подразумевается, что:
+
+ X Y
+ верхний слой 0--63 0--63
+ второй слой 0--63 64--127
+ третий слой 0--63 128--191
+ четвертый слой 0--63 192--255
+
+
+Обычная карта
+ MapSizeX х (MapSizeY)
+
+Представление в виде слоев
+ LayerSizeX x (LayerSizeY x LayerCount)
+
+Иными словами игровые координаты "плоского" пространства
+
+ MapX, MapY, MapZ
+
+переходят в координаты нового "3д" пространства
+
+ // Константы
+ LayerCount = число кусочков... (1, или 2, или 4, итд.)
+ LayerSizeZ = сдвиг слоя по вертикали (например 1)
+
+ // Аксиомы
+ MapSizeX == LayerSizeX
+ MapSizeY == LayerSizeY * LayerCount
+
+ // Расчет координат
+ LayerIndex = MapY / LayerSizeY
+
+ WorldX = MapX
+ WorldY = MapY - LayerIndex*LayerSizeY
+ WorldZ = MapZ + LayerIndex*LayerSizeZ
+*/
+
+/** @file map.cpp Base functions related to the map and distances on them. */
+
+#include "stdafx.h"
+#include "debug.h"
+#include "core/alloc_func.hpp"
+#include "void_map.h"
+#include "layer_func.h"
+#include "layer_type.h"
+#include "landscape.h"
+
+#if defined(_MSC_VER)
+/* Why the hell is that not in all MSVC headers?? */
+extern "C" _CRTIMP void __cdecl _assert(void *, void *, unsigned);
+#endif
+
+uint _layer_size_x; ///< Size of the map along the X
+uint _layer_size_y; ///< Size of the map along the Y
+uint _layer_count; ///< The number of tiles on the map
+uint _layer_count_log;
+uint _layer_size; ///< Layer size (sizeX * sizeY)
+
+void InstallLayerSystem(uint size_x, uint size_y, uint layer_count)
+{
+ if (!IsInsideMM(layer_count, MIN_LAYER_COUNT, MAX_LAYER_COUNT))
+ error("invalid layer count");
+
+ _layer_size_x = size_x;
+ _layer_size_y = size_y;
+ _layer_size = size_x * size_y;
+ _layer_count = layer_count;
+ _layer_count_log = FindFirstBit(layer_count);
+}
+
+void FixUndergroundHeights()
+{
+ uint width = MapSizeX();
+ uint height = MapSizeY();
+
+ /* Layer correct */
+ for (uint row = 0; (uint)row < height; row++) {
+
+ /* Граница между слоями */
+ if (!(row % LayerSizeY()))
+ for (uint x = 0; x < width; x++) MakeVoid(width * row + x);
+
+ for (uint col = 0; (uint)col < width; col++) {
+ uint tile = TileXY(row, col);
+ if (IsUnderground(tile))
+ SetTileHeight(tile, 0);
+ }
+ }
+}
+
+uint8 calculateLayer(const ViewPort *vp)
+{
+ // Функция ViewportDoDraw вызывается несколько раз с разными параметрами
+ // Нужно же найти только один слой.
+ // Опираемся на вьюпорт.
+
+ Point pt = InverseRemapCoords(vp->virtual_left+(vp->virtual_width >> 1),vp->virtual_top+(vp->virtual_height >> 1));
+ TileIndex center = TileVirtXY(pt.x, pt.y);
+ return LayerIndex(center);
+}
+
diff --git a/src/layer_func.h b/src/layer_func.h
new file mode 100644
index 000000000..250e79a3d
--- /dev/null
+++ b/src/layer_func.h
@@ -0,0 +1,159 @@
+/* $Id: layer_func.h 2012-09-07 18:11:11 constructor $ */
+
+/*
+* Подробое описание см. в layer.cpp
+*/
+
+/** @file layer_func.h Functions related to layer in maps. */
+
+#ifndef LAYER_FUNC_H
+#define LAYER_FUNC_H
+
+#include "map_func.h"
+#include "viewport_type.h"
+
+/*
+*
+* Инициализация "подземелий"
+* Количество слоев "1" равносильно игре без "подземелий"
+*
+*/
+void InstallLayerSystem(uint size_x, uint size_y, uint layer_count);
+
+/* Корректировка "подземных" слоев
+* (в будущем слои могут менять высоты -- в пределах соседей) */
+void FixUndergroundHeights();
+
+#define FOR_ALL_LAYERS(var) for (uint var = 0; var < LayerCount(); var++)
+
+
+/**
+ * Get the size of the layer along the X
+ * @return the number of tiles along the X of the layer
+ */
+static inline uint LayerSizeX()
+{
+ extern uint _map_size_x;
+ return _map_size_x;
+}
+
+/**
+ * Get the size of the layer along the Y
+ * @return the number of tiles along the Y of the layer
+ */
+static inline uint LayerSizeY()
+{
+ extern uint _layer_size_y;
+ return _layer_size_y;
+}
+
+/**
+ * Gets the maximum X coordinate within the map, including MP_VOID
+ * @return the maximum X coordinate
+ */
+static inline uint LayerMaxX()
+{
+ return LayerSizeX() - 1;
+}
+
+/**
+ * Gets the maximum Y coordinate within the map, including MP_VOID
+ * @return the maximum Y coordinate
+ */
+static inline uint LayerMaxY()
+{
+ return LayerSizeY() - 1;
+}
+
+/**
+ * Get the layer counts
+ * @return the number of layers
+ */
+static inline uint LayerCount()
+{
+ extern uint _layer_count;
+ return _layer_count;
+}
+
+/**
+ * Get the layer counts
+ * @return the number of layers
+ */
+static inline uint LayerCountLog()
+{
+ extern uint _layer_count_log;
+ return _layer_count_log;
+}
+
+/**
+ * Get the X component of a tile
+ * @param tile the tile to get the X component of
+ * @return the X component
+ */
+static inline uint LayerX(TileIndex tile)
+{
+ return tile & LayerMaxX();
+}
+
+/**
+ * Get the Y component of a tile
+ * @param tile the tile to get the Y component of
+ * @return the Y component
+ */
+static inline uint LayerY(TileIndex tile)
+{
+ return (tile >> MapLogX()) & LayerMaxY();
+}
+
+static inline uint LayerIndex(TileIndex tile)
+{
+ return (tile >> MapLogX()) / LayerSizeY();
+}
+
+static inline bool IsUnderground(TileIndex tile)
+{
+ return LayerIndex(tile) != 0;
+}
+
+/**
+* Размер слоя.
+* Можно прибавить к клетке, чтобы получить клетку ниже
+*/
+static inline uint LayerSize()
+{
+ extern uint _layer_size;
+ return _layer_size;
+}
+
+/**
+ * Ищем клетку поверхности для данной (самую верхнюю клетку)
+ * @param tile the tile to get the Y component of
+ * @return the Y component
+ */
+static inline uint TopTile(TileIndex tile)
+{
+ uint layer = LayerIndex(tile);
+ return (tile - layer * LayerSize());
+}
+
+/* Определить верхняя ли клеточка очень просто */
+static inline bool IsTopTile(TileIndex tile)
+{
+ return (tile < LayerSize());
+}
+
+/* Ищет клетку над данной. (Для самой верхней вернет нижнюю??)
+*/
+static inline uint UpTile(TileIndex tile)
+{
+ return TILE_MASK(tile - LayerSize());
+}
+
+/* Ищет клетку под данной. (Для самой нижней вернет верхнюю??)
+*/
+static inline uint DownTile(TileIndex tile)
+{
+ return TILE_MASK(tile + LayerSize());
+}
+
+#endif /* LAYER_FUNC_H */
diff --git a/src/layer_gui.h b/src/layer_gui.h
new file mode 100644
index 000000000..303fa32a3
--- /dev/null
+++ b/src/layer_gui.h
@@ -0,0 +1,17 @@
+/* $Id: layer_func.h 2012-09-07 18:11:11 constructor $ */
+
+/*
+* Подробое описание см. в layer.cpp
+*/
+
+/** @file layer_gui.h Functions for visualisation map with support layers */
+
+#ifndef LAYER_GUI_H
+#define LAYER_GUI_H
+
+#include "layer_func.h"
+#include "viewport_type.h"
+
+uint8 calculateLayer(const ViewPort *vp);
+
+#endif /* LAYER_GUI_H */
diff --git a/src/layer_type.h b/src/layer_type.h
new file mode 100644
index 000000000..53eafdde6
--- /dev/null
+++ b/src/layer_type.h
@@ -0,0 +1,22 @@
+/* $Id: layer_type.h 21493 2012-09-11 2:21:53Z constructor $ */
+
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file layer_type.h Types related to maps. */
+
+#ifndef LAYER_TYPE_H
+#define LAYER_TYPE_H
+
+/** Minimal and maximal layer counts */
+static const uint MIN_LAYER_COUNT_BITS = 0; ///< Minimal size of map is equal to 2 ^ MIN_LAYER_SIZE_BITS
+static const uint MAX_LAYER_COUNT_BITS = 3; ///< Maximal size of map is equal to 2 ^ MAX_LAYER_SIZE_BITS
+static const uint MIN_LAYER_COUNT = 1 << MIN_LAYER_COUNT_BITS; ///< Minimal layer count = 1
+static const uint MAX_LAYER_COUNT = 1 << MAX_LAYER_COUNT_BITS; ///< Maximal layer count = 8
+
+
+#endif /* LAYER_TYPE_H */
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 5cced077f..7e8ae90f6 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -104,7 +104,7 @@ bool HandlePlacePushButton(Window *w, int widget, CursorID cursor, HighLightStyl
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
w->SetDirty();
- if (w->IsWidgetLowered(widget)) {
+ if (w->IsWidgetLowered(widget) && mode == _thd.place_mode) {
ResetObjectToPlace();
return false;
}
diff --git a/src/map.cpp b/src/map.cpp
index 1ee0ba247..cc62390db 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -13,6 +13,7 @@
#include "debug.h"
#include "core/alloc_func.hpp"
#include "water_map.h"
+#include "layer_func.h"
#if defined(_MSC_VER)
/* Why the hell is that not in all MSVC headers?? */
@@ -35,7 +36,7 @@ TileExtended *_me = NULL; ///< Extended Tiles of the map
* @param size_x the width of the map along the NE/SW edge
* @param size_y the 'height' of the map along the SE/NW edge
*/
-void AllocateMap(uint size_x, uint size_y)
+void AllocateMap(uint size_x, uint size_y, uint layer_count)
{
/* Make sure that the map size is within the limits and that
* size of both axes is a power of 2. */
@@ -46,6 +47,10 @@ void AllocateMap(uint size_x, uint size_y)
error("Invalid map size");
}
+ /* Поскольку слои часть карты, включаем их здесь */
+ InstallLayerSystem(size_x, size_y, layer_count);
+ size_y *= layer_count;
+
DEBUG(map, 1, "Allocating map of size %dx%d", size_x, size_y);
_map_log_x = FindFirstBit(size_x);
diff --git a/src/map_func.h b/src/map_func.h
index 9198c2cd1..f999e0dcf 100644
--- a/src/map_func.h
+++ b/src/map_func.h
@@ -43,7 +43,7 @@ extern Tile *_m;
*/
extern TileExtended *_me;
-void AllocateMap(uint size_x, uint size_y);
+void AllocateMap(uint size_x, uint size_y, uint layer_count);
/**
* Logarithm of the map size along the X side.
diff --git a/src/misc.cpp b/src/misc.cpp
index c967d5369..49c5850fd 100644
--- a/src/misc.cpp
+++ b/src/misc.cpp
@@ -10,6 +10,7 @@
/** @file misc.cpp Misc functions that shouldn't be here. */
#include "stdafx.h"
+#include "layer_func.h"
#include "landscape.h"
#include "news_func.h"
#include "ai/ai.hpp"
@@ -54,7 +55,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin
* related to the new game we're about to start/load. */
UnInitWindowSystem();
- AllocateMap(size_x, size_y);
+ AllocateMap(size_x, size_y, 1 << _settings_game.game_creation.layers);
_pause_mode = PM_UNPAUSED;
_fast_forward = 0;
@@ -106,6 +107,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin
InitializeEconomy();
ResetObjectToPlace();
+ ClearRailPlacementEndpoints();
GamelogReset();
GamelogStartAction(GLAT_START);
diff --git a/src/newgrf.cpp b/src/newgrf.cpp
index a1dfd7917..c8d5530a7 100644
--- a/src/newgrf.cpp
+++ b/src/newgrf.cpp
@@ -9013,6 +9013,9 @@ static void AfterLoadGRFs()
InitializeSortedCargoSpecs();
+ /* Create dynamic list of cargo legends for smallmap_gui.cpp. */
+ BuildCargoTypesLegend();
+
/* Sort the list of industry types. */
SortIndustryTypes();
diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp
index 9ba2ba83c..eb8bbb0bc 100644
--- a/src/object_cmd.cpp
+++ b/src/object_cmd.cpp
@@ -586,7 +586,7 @@ static void TileLoop_Object(TileIndex tile)
if (GB(r, 0, 8) < (256 / 4 / (6 - level))) {
uint amt = GB(r, 0, 8) / 8 / 4 + 1;
if (EconomyIsInRecession()) amt = (amt + 1) >> 1;
- MoveGoodsToStation(CT_PASSENGERS, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations());
+ MoveGoodsToStation(CT_PASSENGERS, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations(), tile);
}
/* Top town building generates 90, HQ can make up to 196. The
@@ -595,7 +595,7 @@ static void TileLoop_Object(TileIndex tile)
if (GB(r, 8, 8) < (196 / 4 / (6 - level))) {
uint amt = GB(r, 8, 8) / 8 / 4 + 1;
if (EconomyIsInRecession()) amt = (amt + 1) >> 1;
- MoveGoodsToStation(CT_MAIL, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations());
+ MoveGoodsToStation(CT_MAIL, amt, ST_HEADQUARTERS, GetTileOwner(tile), stations.GetStations(), tile);
}
}
diff --git a/src/order_base.h b/src/order_base.h
index 82e1371ee..8e059142f 100644
--- a/src/order_base.h
+++ b/src/order_base.h
@@ -20,6 +20,7 @@
#include "station_type.h"
#include "vehicle_type.h"
#include "date_type.h"
+#include "date_func.h"
typedef Pool<Order, OrderID, 256, 64000> OrderPool;
typedef Pool<OrderList, OrderListID, 128, 64000> OrderListPool;
@@ -192,13 +193,41 @@ public:
uint32 Pack() const;
uint16 MapOldOrder() const;
void ConvertFromOldSavegame();
+
+ static void PostDestructor(size_t index);
};
void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord);
void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord);
-/**
- * Shared order list linking together the linked list of orders and the list
+/** Working modes for timetable separation. */
+enum TTSepMode {
+ /** Timetable separation works in fully automatic mode, determining its parameters from the situation in game. */
+ TTS_MODE_AUTO,
+
+ /** Timetable separation is deactivated altogether. */
+ TTS_MODE_OFF,
+
+ /** Timetable separation is active in manual time mode. The amount of time between vehicles is set in ticks by the user. */
+ TTS_MODE_MAN_T,
+
+ /** Timetable separation is active in manual number mode. The user sets a number of vehicles that are supposed to be running
+ * the timetable simultaneously. The algorithm acts according to this specification regardless of the actual number of
+ * running vehicles.*/
+ TTS_MODE_MAN_N,
+
+ /** Timetable separation works in buffered automatic mode that keeps one vehicle waiting at the first stop as
+ * reserve for delay compensation and behaves like full automatic otherwise. */
+ TTS_MODE_BUFFERED_AUTO,
+};
+
+struct TTSepSettings {
+ TTSepMode mode;
+ uint num_veh, sep_ticks;
+ TTSepSettings() : mode(TTS_MODE_AUTO), num_veh(0), sep_ticks(0) { }
+};
+
+/** Shared order list linking together the linked list of orders and the list
* of vehicles sharing this order list.
*/
struct OrderList : OrderListPool::PoolItem<&_orderlist_pool> {
@@ -206,19 +235,29 @@ private:
friend void AfterLoadVehicles(bool part_of_load); ///< For instantiating the shared vehicle chain
friend const struct SaveLoad *GetOrderListDescription(); ///< Saving and loading of order lists.
+ /** Returns the number of running (i.e. not stopped) vehicles in the shared orders list. */
+ int GetNumRunningVehicles();
+
Order *first; ///< First order of the order list.
VehicleOrderID num_orders; ///< NOSAVE: How many orders there are in the list.
VehicleOrderID num_manual_orders; ///< NOSAVE: How many manually added orders are there in the list.
uint num_vehicles; ///< NOSAVE: Number of vehicles that share this order list.
Vehicle *first_shared; ///< NOSAVE: pointer to the first vehicle in the shared order chain.
-
Ticks timetable_duration; ///< NOSAVE: Total duration of the order list
+ Ticks last_timetable_init; ///< Contains the last absolute time of initialization in ticks.
+ uint separation_counter; ///< Counts the vehicles that arrive at the first shared order for separation timing.
+ bool is_separation_valid; ///< Is true if the separation has been initialized since last load or vehicle list change.
+ Ticks current_separation; ///< The current separation between vehicles in the shared order list.
+ TTSepMode current_sep_mode; ///< The current mode of vehicle separation.
+ uint num_sep_vehicles; ///< Number of planned vehicles for separation.
public:
/** Default constructor producing an invalid order list. */
OrderList(VehicleOrderID num_orders = INVALID_VEH_ORDER_ID)
: first(NULL), num_orders(num_orders), num_manual_orders(0), num_vehicles(0), first_shared(NULL),
- timetable_duration(0) { }
+ timetable_duration(0), last_timetable_init(INVALID_TICKS), separation_counter(0),
+ is_separation_valid(false), current_separation(INVALID_TICKS), current_sep_mode(TTS_MODE_OFF),
+ num_sep_vehicles(0) { }
/**
* Create an order list with the given order chain for the given vehicle.
@@ -280,6 +319,24 @@ public:
*/
inline uint GetNumVehicles() const { return this->num_vehicles; }
+ /**
+ * Returns the amount of separation time between vehicles.
+ * @return the amount of separation time between vehicles.
+ */
+ inline uint GetSepTime() const
+ {
+ if (this->is_separation_valid) {
+ return this->current_separation;
+ } else {
+ return this->GetTimetableTotalDuration() / this->GetNumVehicles();
+ }
+ }
+
+ TTSepSettings GetSepSettings();
+
+ void SetSepSettings(TTSepSettings s);
+ void SetSepSettings(TTSepMode Mode, uint Parameter);
+
bool IsVehicleInSharedOrdersList(const Vehicle *v) const;
int GetPositionInSharedOrderList(const Vehicle *v) const;
@@ -313,6 +370,64 @@ public:
*/
void UpdateOrderTimetable(Ticks delta) { this->timetable_duration += delta; }
+ /**
+ * Gets the last absolute time in Ticks since separation was initalized.
+ * @return last arrival time of first vehicle at first order.
+ */
+ inline Ticks GetSeparationInitTime() const
+ {
+ return this->last_timetable_init;
+ }
+
+ /**
+ * Gets the current value of the timetable separation counter.
+ * @return the current value of the timetable separation counter.
+ */
+ inline uint GetSeparationCounter() const
+ {
+ return this->separation_counter;
+ }
+
+ /** Increases the timetable separation counter. */
+ void IncreaseSeparationCounter()
+ {
+ this->separation_counter++;
+ }
+
+ /** Marks timetable separation invalid so it has to be initialized again. */
+ void MarkSeparationInvalid()
+ {
+ if ((this->current_sep_mode == TTS_MODE_AUTO)||(this->current_sep_mode == TTS_MODE_BUFFERED_AUTO))
+ this->is_separation_valid = false;
+ }
+
+ /**
+ * Returns the new delay for the current vehicle and increases the separation counter.
+ * @return the new delay
+ */
+ Ticks SeparateVehicle();
+
+ /**
+ * Gets whether the timetable separation is currently valid or not.
+ * @return whether the timetable separation is currently valid or not.
+ */
+ inline bool IsSeparationValid() const
+ {
+ return this->is_separation_valid;
+ }
+
+ /**
+ * Gets whether timetable separation is currently switched on or not.
+ * @return whether the timetable separation is currently switched on or not.
+ */
+ inline bool IsSeparationOn() const
+ {
+ return this->current_sep_mode != TTS_MODE_OFF;
+ }
+
+ /** Initializes the separation system. */
+ void InitializeSeparation();
+
void FreeChain(bool keep_orderlist = false);
void DebugCheckSanity() const;
diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp
index e86e31900..145f27652 100644
--- a/src/order_cmd.cpp
+++ b/src/order_cmd.cpp
@@ -26,6 +26,7 @@
#include "waypoint_base.h"
#include "company_base.h"
#include "order_backup.h"
+#include "cargodest_func.h"
#include "table/strings.h"
@@ -245,6 +246,21 @@ Order::Order(uint32 packed)
}
/**
+ * Invalidating some stuff after removing item from the pool.
+ * @param index index of deleted item.
+ */
+/* static */ void Order::PostDestructor(size_t index)
+{
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if (v->current_order.index == index) v->current_order.index = INVALID_ORDER;
+ if (v->last_order_id == index) v->last_order_id = INVALID_ORDER;
+ }
+
+ InvalidateOrderRouteLinks((OrderID)index);
+}
+
+/**
*
* Updates the widgets of a vehicle which contains the order-data
*
@@ -529,6 +545,146 @@ void OrderList::DebugCheckSanity() const
this->num_vehicles, this->timetable_duration);
}
+/** Returns the number of running (i.e. not stopped) vehicles in the shared orders list. */
+int OrderList::GetNumRunningVehicles()
+{
+ int num_running_vehicles = 0;
+
+ for (const Vehicle *v = this->first_shared; v != NULL; v = v->NextShared()) {
+ if (!(v->vehstatus & (VS_STOPPED | VS_CRASHED))) num_running_vehicles++;
+ }
+
+ return num_running_vehicles;
+}
+
+/** (Re-)Initializes Separation if necessary and possible. */
+void OrderList::InitializeSeparation()
+{
+ // Check whether separation can be used at all
+ if(!this->IsCompleteTimetable() || this->current_sep_mode == TTS_MODE_OFF) {
+ this->is_separation_valid = false;
+ return;
+ }
+
+ // Save current tick count as reference for future timetable start dates and reset the separation counter.
+ this->last_timetable_init = GetCurrentTickCount();
+ this->separation_counter = 0;
+
+ // Calculate separation amount depending on mode of operation.
+ switch (current_sep_mode) {
+ case TTS_MODE_AUTO: {
+ int num_running_vehicles = this->GetNumRunningVehicles();
+ assert(num_running_vehicles > 0);
+
+ this->current_separation = this->GetTimetableTotalDuration() / num_running_vehicles;
+ break;
+ }
+
+ case TTS_MODE_MAN_N:
+ this->current_separation = this->GetTimetableTotalDuration() / this->num_sep_vehicles;
+ break;
+
+ case TTS_MODE_MAN_T:
+ // separation is set manually -> nothing to do
+ break;
+
+ case TTS_MODE_BUFFERED_AUTO: {
+ int num_running_vehicles = this->GetNumRunningVehicles();
+ assert(num_running_vehicles > 0);
+
+ if(num_running_vehicles > 1)
+ num_running_vehicles--;
+
+ this->current_separation = this->GetTimetableTotalDuration() / num_running_vehicles;
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+
+ this->is_separation_valid = true;
+}
+
+/**
+ * Returns the delay setting required for correct separation and increases the separation counter by 1.
+ * @return the delay setting required for correct separation. */
+Ticks OrderList::SeparateVehicle()
+{
+ if (!this->is_separation_valid || this->current_sep_mode == TTS_MODE_OFF)
+ return INVALID_TICKS;
+
+ Ticks result = GetCurrentTickCount() - (this->separation_counter * this->current_separation + this->last_timetable_init);
+ this->separation_counter++;
+
+ return result;
+}
+
+/**
+ * Returns the current separation settings.
+ * @return the current separation settings.
+ */
+TTSepSettings OrderList::GetSepSettings()
+{
+ TTSepSettings result;
+
+ result.mode = this->current_sep_mode;
+ result.sep_ticks = GetSepTime();
+
+ // Depending on the operation mode return either the user setting or the true amount of vehicles running the timetable.
+ result.num_veh = (result.mode == TTS_MODE_MAN_N) ? this->num_sep_vehicles : GetNumVehicles();
+ return result;
+}
+
+/**
+ * Prepares command to set new separation settings.
+ * @param s Contains the new settings to be used for separation.
+ * @todo Clean this up (e.g. via union type)
+ */
+void OrderList::SetSepSettings(TTSepSettings s)
+{
+ uint32 p2 = GB<uint32>(s.mode,0,3);
+ AB<uint32, uint>(p2,3,29, (s.mode == TTS_MODE_MAN_N) ? s.num_veh : s.sep_ticks);
+ DoCommandP(0, this->first_shared->index, p2, CMD_REINIT_SEPARATION);
+}
+
+/**
+ * Sets new separation settings.
+ * @param mode Contains the operation mode that is to be used for separation.
+ * @param parameter Depending on the operation mode this contains either the number of vehicles (#TTS_MODE_MAN_N)
+ * or the time between vehicles in ticks (#TTS_MODE_MAN_T). For other modes, this is undefined.
+ */
+void OrderList::SetSepSettings(TTSepMode mode, uint32 parameter)
+{
+ this->current_sep_mode = mode;
+
+ switch (this->current_sep_mode)
+ {
+ case TTS_MODE_MAN_N:
+ this->current_separation = this->GetTimetableTotalDuration() / parameter;
+ this->num_sep_vehicles = parameter;
+ break;
+
+ case TTS_MODE_MAN_T:
+ this->current_separation = parameter;
+ this->num_sep_vehicles = this->GetTimetableTotalDuration() / this->current_separation;
+ break;
+
+ case TTS_MODE_AUTO:
+ case TTS_MODE_BUFFERED_AUTO:
+ case TTS_MODE_OFF:
+ /* nothing to do */
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+
+ this->is_separation_valid = false;
+}
+
/**
* Checks whether the order goes to a station or not, i.e. whether the
* destination is a station
@@ -902,6 +1058,8 @@ void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord)
cur_order_id++;
}
+ PrefillRouteLinks(v);
+
/* Make sure to rebuild the whole list */
InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0);
}
@@ -967,6 +1125,17 @@ static void CancelLoadingDueToDeletedOrder(Vehicle *v)
}
/**
+ * Invalidate the next unload station of all cargo packets of a vehicle chain.
+ * @param v The vehicle.
+ */
+static void InvalidateNextStation(Vehicle *v)
+{
+ for (; v != NULL; v = v->Next()) {
+ v->cargo.InvalidateNextStation();
+ }
+}
+
+/**
* Delete an order but skip the parameter validation.
* @param v The vehicle to delete the order from.
* @param sel_ord The id of the order to be deleted.
@@ -977,6 +1146,7 @@ void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord)
Vehicle *u = v->FirstShared();
DeleteOrderWarnings(u);
+ PrefillRouteLinks(u);
for (; u != NULL; u = u->NextShared()) {
assert(v->orders.list == u->orders.list);
@@ -1005,6 +1175,9 @@ void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord)
/* Update any possible open window of the vehicle */
InvalidateVehicleOrder(u, sel_ord | (INVALID_VEH_ORDER_ID << 8));
+
+ /* Clear the next unload station of all cargo packets, it might not be in the orders anymore. */
+ InvalidateNextStation(u);
}
/* As we delete an order, the order to skip to will be 'wrong'. */
@@ -1294,7 +1467,11 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
switch (mof) {
case MOF_NON_STOP:
order->SetNonStopType((OrderNonStopFlags)data);
- if (data & ONSF_NO_STOP_AT_DESTINATION_STATION) order->SetRefit(CT_NO_REFIT);
+ if (data & ONSF_NO_STOP_AT_DESTINATION_STATION) {
+ InvalidateOrderRouteLinks(order->index);
+ order->SetRefit(CT_NO_REFIT);
+ }
+ PrefillRouteLinks(v);
break;
case MOF_STOP_LOCATION:
@@ -1394,6 +1571,9 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
u->current_order.SetLoadType(order->GetLoadType());
}
InvalidateVehicleOrder(u, VIWD_MODIFY_ORDERS);
+
+ /* Invalidate the next unload station of all packets as we might not unload there anymore. */
+ InvalidateNextStation(u);
}
}
@@ -1502,6 +1682,7 @@ CommandCost CmdCloneOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
/* Link this vehicle in the shared-list */
dst->AddToShared(src);
+ PrefillRouteLinks(dst);
InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS);
InvalidateVehicleOrder(src, VIWD_MODIFY_ORDERS);
@@ -1566,6 +1747,7 @@ CommandCost CmdCloneOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
dst->orders.list = new OrderList(first, dst);
}
+ PrefillRouteLinks(dst);
InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS);
InvalidateWindowClassesData(GetWindowClassForVehicleType(dst->type), 0);
@@ -1804,6 +1986,9 @@ void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist, bool reset_order_indic
if (!keep_orderlist) v->orders.list = NULL;
}
+ /* Invalidate the next unload station of all cargo. */
+ InvalidateNextStation(v);
+
if (reset_order_indices) {
v->cur_implicit_order_index = v->cur_real_order_index = 0;
if (v->current_order.IsType(OT_LOADING)) {
@@ -2011,7 +2196,12 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
}
v->current_order = *order;
+ /* Set the index of the current order to the index of the implicit order,
+ * this is needed as the index is used for route link generation. */
+ Order *implicit_order = v->GetOrder(v->cur_implicit_order_index);
+ v->current_order.index = implicit_order->index;
return UpdateOrderDest(v, order, conditional_depth + 1, pbs_look_ahead);
+
}
/**
@@ -2094,6 +2284,10 @@ bool ProcessOrders(Vehicle *v)
/* Otherwise set it, and determine the destination tile. */
v->current_order = *order;
+ /* Set the index of the current order to the index of the implicit order,
+ * this is needed as the index is used for route link generation. */
+ Order *implicit_order = v->GetOrder(v->cur_implicit_order_index);
+ v->current_order.index = implicit_order->index;
InvalidateVehicleOrder(v, VIWD_MODIFY_ORDERS);
switch (v->type) {
diff --git a/src/os/macosx/osx_stdafx.h b/src/os/macosx/osx_stdafx.h
index 688762be9..86bd56ace 100644
--- a/src/os/macosx/osx_stdafx.h
+++ b/src/os/macosx/osx_stdafx.h
@@ -44,6 +44,7 @@
#define WindowClass OTTDWindowClass
#define ScriptOrder OTTDScriptOrder
#define Palette OTTDPalette
+#define RoutingFlags OTTDRoutingFlags
#include <CoreServices/CoreServices.h>
#include <ApplicationServices/ApplicationServices.h>
@@ -53,6 +54,7 @@
#undef WindowClass
#undef ScriptOrder
#undef Palette
+#undef RoutingFlags
/* remove the variables that CoreServices defines, but we define ourselves too */
#undef bool
diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp
index 3fdc3f1f8..228817ab2 100644
--- a/src/pathfinder/follow_track.hpp
+++ b/src/pathfinder/follow_track.hpp
@@ -352,7 +352,7 @@ protected:
if (IsTunnel(m_new_tile)) {
if (!m_is_tunnel) {
DiagDirection tunnel_enterdir = GetTunnelBridgeDirection(m_new_tile);
- if (tunnel_enterdir != m_exitdir) {
+ if (tunnel_enterdir != m_exitdir || IsTunnelBridgeExit(m_new_tile)) {
m_err = EC_NO_WAY;
return false;
}
@@ -360,7 +360,7 @@ protected:
} else { // IsBridge(m_new_tile)
if (!m_is_bridge) {
DiagDirection ramp_enderdir = GetTunnelBridgeDirection(m_new_tile);
- if (ramp_enderdir != m_exitdir) {
+ if (ramp_enderdir != m_exitdir || IsTunnelBridgeExit(m_new_tile)) {
m_err = EC_NO_WAY;
return false;
}
diff --git a/src/pathfinder/npf/npf.cpp b/src/pathfinder/npf/npf.cpp
index 5c4ccb3cc..f8cca276f 100644
--- a/src/pathfinder/npf/npf.cpp
+++ b/src/pathfinder/npf/npf.cpp
@@ -333,6 +333,8 @@ static int32 NPFRoadPathCost(AyStar *as, AyStarNode *current, OpenListNode *pare
cost = NPF_TILE_LENGTH;
/* Increase the cost for level crossings */
if (IsLevelCrossing(tile)) cost += _settings_game.pf.npf.npf_crossing_penalty;
+ /* Increase the cost for juctions with trafficlights. */
+ if (HasTrafficLights(tile)) cost += _settings_game.pf.npf.npf_road_trafficlight_penalty;
break;
case MP_STATION: {
diff --git a/src/pathfinder/yapf/nodelist.hpp b/src/pathfinder/yapf/nodelist.hpp
index f0924a414..a8bcff35a 100644
--- a/src/pathfinder/yapf/nodelist.hpp
+++ b/src/pathfinder/yapf/nodelist.hpp
@@ -21,7 +21,7 @@
* Implements open list, closed list and priority queue for A-star
* path finder.
*/
-template <class Titem_, int Thash_bits_open_, int Thash_bits_closed_>
+template <class Titem_, int Thash_bits_open_, int Thash_bits_closed_, int Tnum_array_items_ = 65536>
class CNodeList_HashTableT {
public:
/** make Titem_ visible from outside of class */
@@ -29,7 +29,7 @@ public:
/** make Titem_::Key a property of HashTable */
typedef typename Titem_::Key Key;
/** type that we will use as item container */
- typedef SmallArray<Titem_, 65536, 256> CItemArray;
+ typedef SmallArray<Titem_, Tnum_array_items_, 256> CItemArray;
/** how pointers to open nodes will be stored */
typedef CHashTableT<Titem_, Thash_bits_open_ > COpenList;
/** how pointers to closed nodes will be stored */
diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h
index 00eb7e562..ce4ae4b66 100644
--- a/src/pathfinder/yapf/yapf.h
+++ b/src/pathfinder/yapf/yapf.h
@@ -15,6 +15,8 @@
#include "../../direction_type.h"
#include "../../track_type.h"
#include "../../vehicle_type.h"
+#include "../../cargodest_type.h"
+#include "../../order_type.h"
#include "../pathfinder_type.h"
/**
@@ -97,4 +99,6 @@ bool YapfTrainCheckReverse(const Train *v);
*/
bool YapfTrainFindNearestSafeTile(const Train *v, TileIndex tile, Trackdir td, bool override_railtype);
+RouteLink *YapfChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found = NULL, OrderID order = INVALID_ORDER, int max_cost = INT_MAX);
+
#endif /* YAPF_H */
diff --git a/src/pathfinder/yapf/yapf_cargo.cpp b/src/pathfinder/yapf/yapf_cargo.cpp
new file mode 100644
index 000000000..22e995197
--- /dev/null
+++ b/src/pathfinder/yapf/yapf_cargo.cpp
@@ -0,0 +1,442 @@
+/* $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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file yapf_cargo.cpp Implementation of YAPF for cargo routing. */
+
+#include "../../stdafx.h"
+#include "../../cargodest_base.h"
+#include "../../station_base.h"
+#include "../../town.h"
+#include "yapf.hpp"
+
+
+/** YAPF node key for cargo routing. */
+struct CYapfRouteLinkNodeKeyT {
+ RouteLink *m_link;
+
+ /** Initialize this node key. */
+ inline void Set(RouteLink *link)
+ {
+ this->m_link = link;
+ }
+
+ /** Calculate the hash of this cargo/route key. */
+ inline int CalcHash() const
+ {
+ return (int)(size_t)this->m_link >> 4;
+ }
+
+ inline bool operator == (const CYapfRouteLinkNodeKeyT& other) const
+ {
+ return this->m_link == other.m_link;
+ }
+
+ void Dump(DumpTarget &dmp) const
+ {
+ dmp.WriteLine("m_link = %u", this->m_link->GetDestination());
+ }
+};
+
+/** YAPF node class for cargo routing. */
+struct CYapfRouteLinkNodeT : public CYapfNodeT<CYapfRouteLinkNodeKeyT, CYapfRouteLinkNodeT> {
+ typedef CYapfNodeT<CYapfRouteLinkNodeKeyT, CYapfRouteLinkNodeT> Base;
+
+ uint m_num_transfers; ///< Number of transfers to reach this node.
+
+ /** Initialize this node. */
+ inline void Set(CYapfRouteLinkNodeT *parent, RouteLink *link)
+ {
+ Base::Set(parent, false);
+ this->m_key.Set(link);
+ this->m_num_transfers = (parent != NULL) ? parent->m_num_transfers : 0;
+ }
+
+ /** Get the route link of this node. */
+ inline RouteLink *GetRouteLink() const { return this->m_key.m_link; }
+
+ /** Get the number of transfers needed to reach this node. */
+ inline int GetNumberOfTransfers() const { return this->m_num_transfers; }
+};
+
+typedef CNodeList_HashTableT<CYapfRouteLinkNodeT, 8, 10, 2048> CRouteLinkNodeList;
+
+/** Route link follower. */
+struct CFollowRouteLinkT {
+ CargoID m_cid;
+ RouteLink *m_old_link;
+ RouteLinkList *m_new_links;
+
+ CFollowRouteLinkT(CargoID cid) : m_cid(cid) {}
+
+ /** Fill in route links reachable by this route link. */
+ inline bool Follow(RouteLink *from)
+ {
+ this->m_old_link = from;
+
+ Station *st = Station::Get(from->GetDestination());
+ m_new_links = &st->goods[this->m_cid].routes;
+ return !this->m_new_links->empty();
+ }
+};
+
+/** YAPF cost provider for route links. */
+template <class Types>
+class CYapfCostRouteLinkT {
+ typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
+ typedef typename Types::TrackFollower Follower; ///< The route follower.
+ typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
+
+ static const int PENALTY_DIVISOR = 16; ///< Penalty factor divisor for fixed-point arithmetics.
+ static const int LOCAL_PENALTY_FACTOR = 16; ///< Penalty factor for source-local delivery.
+ static const int RF_DISTANCE_FACTOR = 2; ///< Vehicle modifier for "cheap" cargo packets.
+ static const int RF_TIME_FACTOR = 3; ///< Time modifier for "fast" cargo packets.
+
+ /** To access inherited path finder. */
+ inline Tpf& Yapf() { return *static_cast<Tpf*>(this); }
+ inline const Tpf& Yapf() const { return *static_cast<const Tpf*>(this); }
+
+ /** Check if this is a valid connection. */
+ inline bool ValidLink(Node &n, const RouteLink *link, const RouteLink *parent) const
+ {
+ /* If the parent link has an owner, and the owner is different to
+ * the new owner, discard the node. Otherwise cargo could switch
+ * companies at oil rigs, which would mess up payment. */
+ if (parent->GetOwner() != INVALID_OWNER && link->GetOwner() != parent->GetOwner()) return false;
+
+ /* Check for no loading/no unloading when transferring. */
+ if (link->GetOriginOrderId() != parent->GetDestOrderId() || (Order::Get(link->GetOriginOrderId())->GetUnloadType() & OUFB_UNLOAD) != 0) {
+ /* Can't transfer if the current order prohibits loading. */
+ if ((Order::Get(link->GetOriginOrderId())->GetLoadType() & OLFB_NO_LOAD) != 0) return false;
+
+ /* Can't transfer if the last order prohibits unloading. */
+ if (parent->GetDestOrderId() != INVALID_ORDER && (Order::Get(parent->GetDestOrderId())->GetUnloadType() & OUFB_NO_UNLOAD) != 0) return false;
+
+ /* Increase transfer counter and stop if max number of transfers is exceeded. */
+ if (++n.m_num_transfers > Yapf().PfGetSettings().route_max_transfers) return false;
+ }
+
+ return true;
+ }
+
+ /** Cost of a single route link. */
+ inline int RouteLinkCost(const RouteLink *link, const RouteLink *parent) const
+ {
+ int cost = 0;
+
+ /* Distance cost. */
+ const Station *from = Station::Get(parent->GetDestination());
+ const Station *to = Station::Get(link->GetDestination());
+ cost = DistanceManhattan(from->xy, to->xy) * this->Yapf().PfGetSettings().route_distance_factor;
+
+ /* Modulate the distance by a vehicle-type specific factor to
+ * simulate the different costs. Cost is doubled if the cargo
+ * wants to go cheap. */
+ assert_compile(lengthof(_settings_game.pf.yapf.route_mode_cost_factor) == VEH_AIRCRAFT + 1);
+ byte dfactor = this->Yapf().PfGetSettings().route_mode_cost_factor[link->GetVehicleType()];
+ if (HasBit(this->Yapf().GetFlags(), RF_WANT_CHEAP)) dfactor *= RF_DISTANCE_FACTOR;
+ cost *= dfactor;
+
+ /* Factor for the time penalties based on whether the cargo wants to go fast. */
+ uint time_factor = HasBit(this->Yapf().GetFlags(), RF_WANT_FAST) ? RF_TIME_FACTOR : 1;
+
+ /* Transfer penalty when switching vehicles or forced unloading. */
+ if (link->GetOriginOrderId() != parent->GetDestOrderId() || (Order::Get(link->GetOriginOrderId())->GetUnloadType() & OUFB_UNLOAD) != 0) {
+ cost += this->Yapf().PfGetSettings().route_transfer_cost;
+
+ /* Penalty for time since the last vehicle arrived. */
+ cost += link->GetWaitTime() * this->Yapf().PfGetSettings().route_station_last_veh_factor * time_factor / PENALTY_DIVISOR;
+
+ /* Penalty for cargo waiting on our link. */
+ cost += (from->goods[this->Yapf().GetCargoID()].cargo.CountForNextHop(link->GetOriginOrderId()) * this->Yapf().PfGetSettings().route_station_waiting_factor) / PENALTY_DIVISOR;
+ }
+
+ /* Penalty for travel time. */
+ cost += (link->GetTravelTime() * this->Yapf().PfGetSettings().route_travel_time_factor * time_factor) / PENALTY_DIVISOR;
+
+ return cost;
+ }
+
+public:
+ /** Called by YAPF to calculate the cost from the origin to the given node. */
+ inline bool PfCalcCost(Node& n, const Follower *follow)
+ {
+ int segment_cost = 0;
+
+ if (this->Yapf().PfDetectDestination(n)) {
+ Station *st = Station::Get(n.m_parent->GetRouteLink()->GetDestination());
+ /* Discard node if the station doesn't accept the cargo type. */
+ if (!HasBit(st->goods[follow->m_cid].acceptance_pickup, GoodsEntry::GES_ACCEPTANCE)) return false;
+ /* Destination node, get delivery cost. Parent has the station. */
+ segment_cost += this->Yapf().DeliveryCost(st);
+ /* If this link comes from an origin station, penalize it to encourage
+ * delivery using other stations. */
+ if (n.m_parent->GetRouteLink()->GetDestOrderId() == INVALID_ORDER) segment_cost *= LOCAL_PENALTY_FACTOR;
+ } else {
+ RouteLink *link = n.GetRouteLink();
+ RouteLink *parent = n.m_parent->GetRouteLink();
+
+ /* Check if the link is a valid connection. */
+ if (!this->ValidLink(n, link, parent)) return false;
+
+ /* Cost of the single route link. */
+ segment_cost += this->RouteLinkCost(link, parent);
+ }
+
+ /* Apply it. */
+ n.m_cost = n.m_parent->m_cost + segment_cost;
+ return n.m_cost <= this->Yapf().GetMaxCost();
+ }
+};
+
+/** YAPF origin provider for route links. */
+template <class Types>
+class CYapfOriginRouteLinkT {
+ typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
+ typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
+
+ CargoID m_cid;
+ TileIndex m_src;
+ OrderID m_order;
+ byte m_flags;
+ SmallVector<RouteLink, 2> m_origin;
+
+ /** To access inherited path finder. */
+ inline Tpf& Yapf() { return *static_cast<Tpf*>(this); }
+
+public:
+ /** Get the current cargo type. */
+ inline CargoID GetCargoID() const
+ {
+ return this->m_cid;
+ }
+
+ /** Get the cargo routing flags. */
+ inline byte GetFlags() const
+ {
+ return this->m_flags;
+ }
+
+ /** Set origin. */
+ void SetOrigin(CargoID cid, TileIndex src, const StationList *stations, bool cargo_creation, OrderID order, byte flags)
+ {
+ this->m_cid = cid;
+ this->m_src = src;
+ this->m_order = order;
+ this->m_flags = flags;
+ /* Create fake links for the origin stations. */
+ for (const Station * const *st = stations->Begin(); st != stations->End(); st++) {
+ if (cargo_creation) {
+ /* Exclusive rights in effect? Only serve those stations. */
+ if ((*st)->town->exclusive_counter > 0 && (*st)->town->exclusivity != (*st)->owner) continue;
+ /* Selectively servicing stations, and not this one. */
+ if (_settings_game.order.selectgoods && (*st)->goods[cid].last_speed == 0) continue;
+ }
+
+ *this->m_origin.Append() = RouteLink((*st)->index, INVALID_ORDER, this->m_order);
+ }
+ }
+
+ /** Called when YAPF needs to place origin nodes into the open list. */
+ void PfSetStartupNodes()
+ {
+ for (RouteLink *link = this->m_origin.Begin(); link != this->m_origin.End(); link++) {
+ Node &n = this->Yapf().CreateNewNode();
+ n.Set(NULL, link);
+ /* Prefer stations closer to the source tile. */
+ n.m_cost = DistanceSquare(this->m_src, Station::Get(link->GetDestination())->xy) * this->Yapf().PfGetSettings().route_distance_factor;
+ this->Yapf().AddStartupNode(n);
+ }
+ }
+};
+
+/** YAPF destination provider for route links. */
+template <class Types>
+class CYapfDestinationRouteLinkT {
+ typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
+ typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
+
+ TileArea m_dest;
+ int m_max_cost; ///< Maximum node cost.
+
+ /** To access inherited path finder. */
+ inline Tpf& Yapf() { return *static_cast<Tpf*>(this); }
+
+public:
+ /** Get the maximum allowed node cost. */
+ inline int GetMaxCost() const
+ {
+ return this->m_max_cost;
+ }
+
+ /** Set destination. */
+ void SetDestination(const TileArea &dest, uint max_cost)
+ {
+ this->m_dest = dest;
+ this->m_max_cost = max_cost;
+ }
+
+ /** Cost for delivering the cargo to the final destination tile. */
+ inline int DeliveryCost(Station *st)
+ {
+ int x = TileX(this->m_dest.tile);
+ int y = TileY(this->m_dest.tile);
+
+ /* Inside the station area? Delivery costs "nothing". */
+ if (st->rect.PtInExtendedRect(x, y)) return 0;
+
+ int dist_x = x < st->rect.left ? x - st->rect.left : x - st->rect.right;
+ int dist_y = y < st->rect.top ? y - st->rect.top : y - st->rect.bottom;
+
+ return (dist_x * dist_x + dist_y * dist_y) * this->Yapf().PfGetSettings().route_distance_factor;
+ }
+
+ /** Called by YAPF to detect if the station reaches the destination. */
+ inline bool PfDetectDestination(StationID st_id) const
+ {
+ const Station *st = Station::Get(st_id);
+ return st->rect.AreaInExtendedRect(this->m_dest, st->GetCatchmentRadius());
+ }
+
+ /** Called by YAPF to detect if the node reaches the destination. */
+ inline bool PfDetectDestination(const Node& n) const
+ {
+ return n.GetRouteLink() == NULL;
+ }
+
+ /** Called by YAPF to calculate the estimated cost to the destination. */
+ inline bool PfCalcEstimate(Node& n)
+ {
+ if (this->PfDetectDestination(n)) {
+ n.m_estimate = n.m_cost;
+ return true;
+ }
+
+ /* Estimate based on Manhattan distance to destination. */
+ Station *from = Station::Get(n.GetRouteLink()->GetDestination());
+ int d = DistanceManhattan(from->xy, this->m_dest.tile) * this->Yapf().PfGetSettings().route_distance_factor;
+
+ n.m_estimate = n.m_cost + d;
+ assert(n.m_estimate >= n.m_parent->m_estimate);
+ return true;
+ }
+};
+
+/** Main route finding class. */
+template <class Types>
+class CYapfFollowRouteLinkT {
+ typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
+ typedef typename Types::TrackFollower Follower; ///< The route follower.
+ typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
+
+ /** To access inherited path finder. */
+ inline Tpf& Yapf() { return *static_cast<Tpf*>(this); }
+
+public:
+ /** Called by YAPF to move from the given node to the next nodes. */
+ inline void PfFollowNode(Node& old_node)
+ {
+ Follower f(this->Yapf().GetCargoID());
+
+ if (this->Yapf().PfDetectDestination(old_node.GetRouteLink()->GetDestination()) && (old_node.GetRouteLink()->GetDestOrderId() == INVALID_ORDER || (Order::Get(old_node.GetRouteLink()->GetDestOrderId())->GetUnloadType() & OUFB_NO_UNLOAD) == 0)) {
+ /* Possible destination? Add sentinel node for final delivery. */
+ Node &n = this->Yapf().CreateNewNode();
+ n.Set(&old_node, NULL);
+ this->Yapf().AddNewNode(n, f);
+ }
+
+ if (f.Follow(old_node.GetRouteLink())) {
+ for (RouteLinkList::iterator link = f.m_new_links->begin(); link != f.m_new_links->end(); ++link) {
+ /* Add new node. */
+ Node &n = this->Yapf().CreateNewNode();
+ n.Set(&old_node, *link);
+ this->Yapf().AddNewNode(n, f);
+ }
+ }
+ }
+
+ /** Return debug report character to identify the transportation type. */
+ inline char TransportTypeChar() const
+ {
+ return 'c';
+ }
+
+ /** Find the best cargo routing from a station to a destination. */
+ static RouteLink *ChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found, OrderID order, int max_cost)
+ {
+ /* Initialize pathfinder instance. */
+ Tpf pf;
+ pf.SetOrigin(cid, src, stations, start_station != NULL, order, flags);
+ pf.SetDestination(dest, max_cost);
+
+ *next_unload = INVALID_STATION;
+
+ /* Do it. Exit if we didn't find a path. */
+ bool res = pf.FindPath(NULL);
+ if (found != NULL) *found = res;
+ if (!res) return NULL;
+
+ /* Walk back to find the start node. */
+ Node *node = pf.GetBestNode();
+ while (node->m_parent->m_parent != NULL) {
+ /* Transfer? Then save transfer station as next unload station. */
+ if (node->GetRouteLink() == NULL || (node->GetRouteLink()->GetOriginOrderId() != node->m_parent->GetRouteLink()->GetDestOrderId())) {
+ *next_unload = node->m_parent->GetRouteLink()->GetDestination();
+ }
+
+ node = node->m_parent;
+ }
+
+ /* Save result. */
+ if (start_station != NULL) {
+ *start_station = node->m_parent->GetRouteLink()->GetDestination();
+ /* Path starts and ends at the same station, do local delivery. */
+ if (*start_station == pf.GetBestNode()->m_parent->GetRouteLink()->GetDestination()) return NULL;
+ }
+ return node->GetRouteLink();
+ }
+};
+
+/** Config struct for route link finding. */
+template <class Tpf_>
+struct CYapfRouteLink_TypesT {
+ typedef CYapfRouteLink_TypesT<Tpf_> Types;
+
+ typedef Tpf_ Tpf; ///< Pathfinder type
+ typedef CFollowRouteLinkT TrackFollower; ///< Node follower
+ typedef CRouteLinkNodeList NodeList; ///< Node list type
+ typedef Vehicle VehicleType; ///< Dummy type
+
+ typedef CYapfBaseT<Types> PfBase; ///< Base pathfinder class
+ typedef CYapfFollowRouteLinkT<Types> PfFollow; ///< Node follower
+ typedef CYapfOriginRouteLinkT<Types> PfOrigin; ///< Origin provider
+ typedef CYapfDestinationRouteLinkT<Types> PfDestination; ///< Destination/distance provider
+ typedef CYapfSegmentCostCacheNoneT<Types> PfCache; ///< Cost cache provider
+ typedef CYapfCostRouteLinkT<Types> PfCost; ///< Cost provider
+};
+
+struct CYapfRouteLink : CYapfT<CYapfRouteLink_TypesT<CYapfRouteLink> > {};
+
+
+/**
+ * Find the best cargo routing from a station to a destination.
+ * @param cid Cargo type to route.
+ * @param stations Set of possible originating stations.
+ * @param dest Destination tile area.
+ * @param[out] start_station Station the best route link originates from.
+ * @param[out] next_unload Next station the cargo should be unloaded from the vehicle.
+ * @param flags Routing flags of the cargo.
+ * @param[out] found True if a link was found.
+ * @param order Order the vehicle arrived at the origin station.
+ * @param max_cost Maxmimum allowed node cost.
+ * @return The best RouteLink to the target or NULL if either no link found or one of the origin stations is the best destination.
+ */
+RouteLink *YapfChooseRouteLink(CargoID cid, const StationList *stations, TileIndex src, const TileArea &dest, StationID *start_station, StationID *next_unload, byte flags, bool *found, OrderID order, int max_cost)
+{
+ return CYapfRouteLink::ChooseRouteLink(cid, stations, src, dest, start_station, next_unload, flags, found, order, max_cost);
+}
diff --git a/src/pathfinder/yapf/yapf_node.hpp b/src/pathfinder/yapf/yapf_node.hpp
index 39f393381..e1b41c615 100644
--- a/src/pathfinder/yapf/yapf_node.hpp
+++ b/src/pathfinder/yapf/yapf_node.hpp
@@ -54,9 +54,8 @@ struct CYapfNodeT {
int m_cost;
int m_estimate;
- inline void Set(Node *parent, TileIndex tile, Trackdir td, bool is_choice)
+ inline void Set(Node *parent, bool is_choice)
{
- m_key.Set(tile, td);
m_hash_next = NULL;
m_parent = parent;
m_cost = 0;
@@ -65,8 +64,6 @@ struct CYapfNodeT {
inline Node *GetHashNext() {return m_hash_next;}
inline void SetHashNext(Node *pNext) {m_hash_next = pNext;}
- inline TileIndex GetTile() const {return m_key.m_tile;}
- inline Trackdir GetTrackdir() const {return m_key.m_td;}
inline const Tkey_& GetKey() const {return m_key;}
inline int GetCost() const {return m_cost;}
inline int GetCostEstimate() const {return m_estimate;}
@@ -81,4 +78,21 @@ struct CYapfNodeT {
}
};
+/** Yapf Node base for trackdir based specialisation. */
+template <class Tkey_, class Tnode>
+struct CYapfNodeTrackT : public CYapfNodeT<Tkey_, Tnode>
+{
+ typedef CYapfNodeT<Tkey_, Tnode> Base;
+ typedef Tnode Node;
+
+ inline void Set(Node *parent, TileIndex tile, Trackdir td, bool is_choice)
+ {
+ Base::Set(parent, is_choice);
+ this->m_key.Set(tile, td);
+ }
+
+ inline TileIndex GetTile() const { return this->m_key.m_tile; }
+ inline Trackdir GetTrackdir() const { return this->m_key.m_td; }
+};
+
#endif /* YAPF_NODE_HPP */
diff --git a/src/pathfinder/yapf/yapf_node_rail.hpp b/src/pathfinder/yapf/yapf_node_rail.hpp
index 765fa0daf..cc562b9fa 100644
--- a/src/pathfinder/yapf/yapf_node_rail.hpp
+++ b/src/pathfinder/yapf/yapf_node_rail.hpp
@@ -190,9 +190,9 @@ struct CYapfRailSegment
/** Yapf Node for rail YAPF */
template <class Tkey_>
struct CYapfRailNodeT
- : CYapfNodeT<Tkey_, CYapfRailNodeT<Tkey_> >
+ : CYapfNodeTrackT<Tkey_, CYapfRailNodeT<Tkey_> >
{
- typedef CYapfNodeT<Tkey_, CYapfRailNodeT<Tkey_> > base;
+ typedef CYapfNodeTrackT<Tkey_, CYapfRailNodeT<Tkey_> > base;
typedef CYapfRailSegment CachedData;
CYapfRailSegment *m_segment;
diff --git a/src/pathfinder/yapf/yapf_node_road.hpp b/src/pathfinder/yapf/yapf_node_road.hpp
index 5cc2d5539..55f9fc46b 100644
--- a/src/pathfinder/yapf/yapf_node_road.hpp
+++ b/src/pathfinder/yapf/yapf_node_road.hpp
@@ -15,9 +15,9 @@
/** Yapf Node for road YAPF */
template <class Tkey_>
struct CYapfRoadNodeT
- : CYapfNodeT<Tkey_, CYapfRoadNodeT<Tkey_> >
+ : CYapfNodeTrackT<Tkey_, CYapfRoadNodeT<Tkey_> >
{
- typedef CYapfNodeT<Tkey_, CYapfRoadNodeT<Tkey_> > base;
+ typedef CYapfNodeTrackT<Tkey_, CYapfRoadNodeT<Tkey_> > base;
TileIndex m_segment_last_tile;
Trackdir m_segment_last_td;
diff --git a/src/pathfinder/yapf/yapf_node_ship.hpp b/src/pathfinder/yapf/yapf_node_ship.hpp
index 7a1358af6..59f0e9f5a 100644
--- a/src/pathfinder/yapf/yapf_node_ship.hpp
+++ b/src/pathfinder/yapf/yapf_node_ship.hpp
@@ -15,7 +15,7 @@
/** Yapf Node for ships */
template <class Tkey_>
struct CYapfShipNodeT
- : CYapfNodeT<Tkey_, CYapfShipNodeT<Tkey_> >
+ : CYapfNodeTrackT<Tkey_, CYapfShipNodeT<Tkey_> >
{
};
diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp
index 82493241f..9e4317810 100644
--- a/src/pathfinder/yapf/yapf_road.cpp
+++ b/src/pathfinder/yapf/yapf_road.cpp
@@ -90,6 +90,9 @@ protected:
} else {
/* non-diagonal trackdir */
cost = YAPF_TILE_CORNER_LENGTH + Yapf().PfGetSettings().road_curve_penalty;
+
+ /* Extra cost for traffic lights. */
+ if (HasTrafficLights(tile)) cost += Yapf().PfGetSettings().road_trafficlight_penalty;
}
return cost;
}
diff --git a/src/rail.h b/src/rail.h
index 249c1bea1..d8386fd15 100644
--- a/src/rail.h
+++ b/src/rail.h
@@ -141,7 +141,7 @@ struct RailtypeInfo {
SpriteID signals[SIGTYPE_END][2][2]; ///< signal GUI sprites (type, variant, state)
} gui_sprites;
- struct {
+ struct Cursor {
CursorID rail_ns; ///< Cursor for building rail in N-S direction
CursorID rail_swne; ///< Cursor for building rail in X direction
CursorID rail_ew; ///< Cursor for building rail in E-W direction
diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp
index e362a132b..327d8f998 100644
--- a/src/rail_cmd.cpp
+++ b/src/rail_cmd.cpp
@@ -41,6 +41,7 @@
typedef SmallVector<Train *, 16> TrainList;
RailtypeInfo _railtypes[RAILTYPE_END];
+TileIndex _rail_track_endtile; ///< The end of a rail track; as hidden return from the rail build/remove command for GUI purposes.
assert_compile(sizeof(_original_railtypes) <= sizeof(_railtypes));
@@ -557,6 +558,7 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u
}
cost.AddCost(RailBuildCost(railtype));
+ _rail_track_endtile = tile;
return cost;
}
@@ -701,6 +703,7 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1,
if (v != NULL) TryPathReserve(v, true);
}
+ _rail_track_endtile = tile;
return cost;
}
@@ -1011,9 +1014,12 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
if (cycle_start > cycle_stop || cycle_stop > SIGTYPE_LAST) return CMD_ERROR;
- /* You can only build signals on plain rail tiles, and the selected track must exist */
- if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) ||
- !HasTrack(tile, track)) {
+ /* You can only build signals on plain rail tiles or tunnel/bridges, and the selected track must exist */
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return CMD_ERROR;
+ CommandCost ret = EnsureNoTrainOnTrack(GetOtherTunnelBridgeEnd(tile), track);
+ //if (ret.Failed()) return ret;
+ } else if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
}
/* Protect against invalid signal copying */
@@ -1022,6 +1028,53 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
CommandCost ret = CheckTileOwnership(tile);
if (ret.Failed()) return ret;
+ CommandCost cost;
+ /* handle signals simulation on tunnel/bridge. */
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ TileIndex tile_exit = GetOtherTunnelBridgeEnd(tile);
+ cost = CommandCost();
+ if (!HasWormholeSignals(tile)) { // toggle signal zero costs.
+ if (p2 != 12) cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS] * ((GetTunnelBridgeLength(tile, tile_exit) + 4) >> 2)); // minimal 1
+ }
+ if (flags & DC_EXEC) {
+ if (p2 == 0 && HasWormholeSignals(tile)){ // Toggle signal if already signals present.
+ if (IsTunnelBridgeEntrance (tile)) {
+ ClrBitTunnelBridgeSignal(tile);
+ ClrBitTunnelBridgeExit(tile_exit);
+ SetBitTunnelBridgeExit(tile);
+ SetBitTunnelBridgeSignal(tile_exit);
+ } else {
+ ClrBitTunnelBridgeSignal(tile_exit);
+ ClrBitTunnelBridgeExit(tile);
+ SetBitTunnelBridgeExit(tile_exit);
+ SetBitTunnelBridgeSignal(tile);
+ }
+ } else{
+ /* Create one direction tunnel/bridge if required. */
+ if (p2 == 0) {
+ SetBitTunnelBridgeSignal(tile);
+ SetBitTunnelBridgeExit(tile_exit);
+ } else if (p2 == 4 || p2 == 8) {
+ DiagDirection tbdir = GetTunnelBridgeDirection(tile);
+ /* If signal only on one side build accoringly one-way tunnel/bridge. */
+ if ((p2 == 8 && (tbdir == DIAGDIR_NE || tbdir == DIAGDIR_SE)) ||
+ (p2 == 4 && (tbdir == DIAGDIR_SW || tbdir == DIAGDIR_NW))) {
+ SetBitTunnelBridgeSignal(tile);
+ SetBitTunnelBridgeExit(tile_exit);
+ } else {
+ SetBitTunnelBridgeSignal(tile_exit);
+ SetBitTunnelBridgeExit(tile);
+ }
+ }
+ }
+ MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile_exit);
+ AddSideToSignalBuffer(tile, INVALID_DIAGDIR, _current_company);
+ YapfNotifyTrackLayoutChange(tile, track);
+ }
+ return cost;
+ }
+
{
/* See if this is a valid track combination for signals, (ie, no overlap) */
TrackBits trackbits = GetTrackBits(tile);
@@ -1038,7 +1091,6 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
/* you can not convert a signal if no signal is on track */
if (convert_signal && !HasSignalOnTrack(tile, track)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
- CommandCost cost;
if (!HasSignalOnTrack(tile, track)) {
/* build new signals */
cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]);
@@ -1196,6 +1248,7 @@ static bool CheckSignalAutoFill(TileIndex &tile, Trackdir &trackdir, int &signal
return true;
case MP_TUNNELBRIDGE: {
+ if (!remove && HasWormholeSignals(tile)) return false;
TileIndex orig_tile = tile; // backup old value
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return false;
@@ -1307,7 +1360,8 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin
bool had_success = false;
for (;;) {
/* only build/remove signals with the specified density */
- if (remove || minimise_gaps || signal_ctr % signal_density == 0) {
+
+ if (remove || minimise_gaps || signal_ctr % signal_density == 0 || IsTileType(tile, MP_TUNNELBRIDGE)) {
uint32 p1 = GB(TrackdirToTrack(trackdir), 0, 3);
SB(p1, 3, 1, mode);
SB(p1, 4, 1, semaphores);
@@ -1343,13 +1397,20 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin
/* Collect cost. */
if (!test_only) {
- /* Be user-friendly and try placing signals as much as possible */
- if (ret.Succeeded()) {
- had_success = true;
- total_cost.AddCost(ret);
- last_used_ctr = last_suitable_ctr;
- last_suitable_tile = INVALID_TILE;
+ /* Be user-friendly and try placing signals as much as possible */
+ if (ret.Succeeded()) {
+ had_success = true;
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ if ((!autofill && GetTunnelBridgeDirection(tile) == TrackdirToExitdir(trackdir)) ||
+ (autofill && GetTunnelBridgeDirection(tile) != TrackdirToExitdir(trackdir))) {
+ total_cost.AddCost(ret);
+ }
} else {
+ total_cost.AddCost(ret);
+ }
+ last_used_ctr = last_suitable_ctr;
+ last_suitable_tile = INVALID_TILE;
+ } else {
/* The "No railway" error is the least important one. */
if (ret.GetErrorMessage() != STR_ERROR_THERE_IS_NO_RAILROAD_TRACK ||
last_error.GetErrorMessage() == INVALID_STRING_ID) {
@@ -1420,22 +1481,48 @@ CommandCost CmdBuildSignalTrack(TileIndex tile, DoCommandFlag flags, uint32 p1,
CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
Track track = Extract<Track, 0, 3>(p1);
+ Money cost = _price[PR_CLEAR_SIGNALS];
- if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
- return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
- }
- if (!HasSignalOnTrack(tile, track)) {
- return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ TileIndex end = GetOtherTunnelBridgeEnd(tile);
+ if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
+ if (!HasWormholeSignals(tile)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
+
+ cost *= ((GetTunnelBridgeLength(tile, end) + 4) >> 2);
+
+ CommandCost ret = EnsureNoTrainOnTrack(GetOtherTunnelBridgeEnd(tile), track);
+ //if (ret.Failed()) return ret;
+ } else {
+ if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
+ return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
+ }
+ if (!HasSignalOnTrack(tile, track)) {
+ return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
+ }
+ CommandCost ret = EnsureNoTrainOnTrack(tile, track);
+ //if (ret.Failed()) return ret;
}
/* Only water can remove signals from anyone */
if (_current_company != OWNER_WATER) {
- CommandCost ret = CheckTileOwnership(tile);
- if (ret.Failed()) return ret;
}
/* Do it? */
if (flags & DC_EXEC) {
+
+ if (HasWormholeSignals(tile)) { // handle tunnel/bridge signals.
+ TileIndex end = GetOtherTunnelBridgeEnd(tile);
+ ClrBitTunnelBridgeExit(tile);
+ ClrBitTunnelBridgeExit(end);
+ ClrBitTunnelBridgeSignal(tile);
+ ClrBitTunnelBridgeSignal(end);
+ _m[tile].m2 = 0;
+ _m[end].m2 = 0;
+ MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(end);
+ return CommandCost(EXPENSES_CONSTRUCTION, cost);
+ }
+
Train *v = NULL;
if (HasReservedTracks(tile, TrackToTrackBits(track))) {
v = GetTrainForReservation(tile, track);
@@ -1471,7 +1558,7 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1
MarkTileDirtyByTile(tile);
}
- return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_SIGNALS]);
+ return CommandCost(EXPENSES_CONSTRUCTION, cost);
}
/**
@@ -1946,6 +2033,7 @@ static void DrawTrackFence_WE_1(const TileInfo *ti, SpriteID base_image)
ti->x + TILE_SIZE / 2, ti->y + TILE_SIZE / 2, 1, 1, 4, z);
}
+
/**
* Draw fence at northern side of track.
*/
@@ -2483,11 +2571,14 @@ static Foundation GetFoundation_Track(TileIndex tile, Slope tileh)
return IsPlainRail(tile) ? GetRailFoundation(tileh, GetTrackBits(tile)) : FlatteningFoundation(tileh);
}
+
static void TileLoop_Track(TileIndex tile)
{
RailGroundType old_ground = GetRailGroundType(tile);
RailGroundType new_ground;
+ ReduceStuckCounter(tile);
+
if (old_ground == RAIL_GROUND_WATER) {
TileLoop_Water(tile);
return;
diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp
index ed1bd0ac0..254be5a81 100644
--- a/src/rail_gui.cpp
+++ b/src/rail_gui.cpp
@@ -49,6 +49,8 @@ static bool _convert_signal_button; ///< convert signal button in the s
static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI)
static SignalType _cur_signal_type; ///< set the signal type (for signal GUI)
+extern TileIndex _rail_track_endtile; // rail_cmd.cpp
+
/* Map the setting: default_signal_type to the corresponding signal type */
static const SignalType _default_signal_type[] = {SIGTYPE_NORMAL, SIGTYPE_PBS, SIGTYPE_PBS_ONEWAY};
@@ -88,9 +90,9 @@ void CcPlaySound1E(const CommandCost &result, TileIndex tile, uint32 p1, uint32
if (result.Succeeded() && _settings_client.sound.confirm) SndPlayTileFx(SND_20_SPLAT_2, tile);
}
-static void GenericPlaceRail(TileIndex tile, int cmd)
+static bool GenericPlaceRail(TileIndex tile, Track track)
{
- DoCommandP(tile, _cur_railtype, cmd,
+ return DoCommandP(tile, _cur_railtype, track,
_remove_button_clicked ?
CMD_REMOVE_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) :
CMD_BUILD_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK),
@@ -279,6 +281,17 @@ void CcBuildRailTunnel(const CommandCost &result, TileIndex tile, uint32 p1, uin
}
/**
+ * Place a rail tunnel.
+ * @param tile Position of the first tile of the tunnel.
+ */
+static void PlaceRail_Tunnel(TileIndex tile)
+{
+ if (DoCommandP(tile, _cur_railtype | (TRANSPORT_RAIL << 8), 0, CMD_BUILD_TUNNEL | CMD_MSG(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE), CcBuildRailTunnel)) {
+ StoreRailPlacementEndpoints(tile, _build_tunnel_endtile, TileX(tile) == TileX(_build_tunnel_endtile) ? TRACK_Y : TRACK_X, false);
+ }
+}
+
+/**
* Toggles state of the Remove button of Build rail toolbar
* @param w window the button belongs to
*/
@@ -346,9 +359,9 @@ static void BuildRailClick_Remove(Window *w)
}
}
-static void DoRailroadTrack(int mode)
+static bool DoRailroadTrack(TileIndex start_tile, TileIndex end_tile, Track track)
{
- DoCommandP(TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), _cur_railtype | (mode << 4),
+ return DoCommandP(start_tile, end_tile, _cur_railtype | (track << 4),
_remove_button_clicked ?
CMD_REMOVE_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) :
CMD_BUILD_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK));
@@ -356,14 +369,15 @@ static void DoRailroadTrack(int mode)
static void HandleAutodirPlacement()
{
- int trackstat = _thd.drawstyle & HT_DIR_MASK; // 0..5
-
- if (_thd.drawstyle & HT_RAIL) { // one tile case
- GenericPlaceRail(TileVirtXY(_thd.selend.x, _thd.selend.y), trackstat);
- return;
+ Track track = (Track)(_thd.drawstyle & HT_DIR_MASK); // 0..5
+ TileIndex start_tile = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
+ TileIndex end_tile = TileVirtXY(_thd.selend.x, _thd.selend.y);
+
+ if (_thd.drawstyle & HT_RAIL ?
+ GenericPlaceRail(end_tile, track) : // one tile case
+ DoRailroadTrack(start_tile, end_tile, track)) { // longer track
+ StoreRailPlacementEndpoints(start_tile, _rail_track_endtile, track, true);
}
-
- DoRailroadTrack(trackstat);
}
/**
@@ -409,6 +423,20 @@ static void HandleAutoSignalPlacement()
CcPlaySound1E);
}
+typedef CursorID (RailtypeInfo::Cursor::* RailCursorType);
+
+static const RailCursorType RAIL_CURSOR_NS = &RailtypeInfo::Cursor::rail_ns;
+static const RailCursorType RAIL_CURSOR_X = &RailtypeInfo::Cursor::rail_swne;
+static const RailCursorType RAIL_CURSOR_EW = &RailtypeInfo::Cursor::rail_ew;
+static const RailCursorType RAIL_CURSOR_Y = &RailtypeInfo::Cursor::rail_nwse;
+static const RailCursorType RAIL_CURSOR_AUTORAIL = &RailtypeInfo::Cursor::autorail;
+
+void HandleRailPlacePushButton(Window *w, int widget, RailCursorType cursor, HighLightStyle mode)
+{
+ if (_ctrl_pressed) mode |= HT_POLY;
+ HandlePlacePushButton(w, widget, GetRailTypeInfo(_cur_railtype)->cursor.*cursor, mode);
+}
+
/** Rail toolbar management class. */
struct BuildRailToolbarWindow : Window {
@@ -490,6 +518,24 @@ struct BuildRailToolbarWindow : Window {
}
}
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch (widget) {
+ case WID_RAT_BUILD_NS:
+ case WID_RAT_BUILD_X:
+ case WID_RAT_BUILD_EW:
+ case WID_RAT_BUILD_Y:
+ case WID_RAT_AUTORAIL:
+ if (this->IsWidgetLowered(widget) && _thd.place_mode & HT_POLY) {
+ DrawSprite(SPR_BLOT, PALETTE_TO_WHITE, r.left + WD_IMGBTN_LEFT, r.top + WD_IMGBTN_TOP);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
virtual void SetStringParameters(int widget) const
{
if (widget == WID_RAT_CAPTION) {
@@ -511,27 +557,27 @@ struct BuildRailToolbarWindow : Window {
_remove_button_clicked = false;
switch (widget) {
case WID_RAT_BUILD_NS:
- HandlePlacePushButton(this, WID_RAT_BUILD_NS, GetRailTypeInfo(_cur_railtype)->cursor.rail_ns, HT_LINE | HT_DIR_VL);
+ HandleRailPlacePushButton(this, WID_RAT_BUILD_NS, RAIL_CURSOR_NS, HT_LINE | HT_DIR_VL);
this->last_user_action = widget;
break;
case WID_RAT_BUILD_X:
- HandlePlacePushButton(this, WID_RAT_BUILD_X, GetRailTypeInfo(_cur_railtype)->cursor.rail_swne, HT_LINE | HT_DIR_X);
+ HandleRailPlacePushButton(this, WID_RAT_BUILD_X, RAIL_CURSOR_X, HT_LINE | HT_DIR_X);
this->last_user_action = widget;
break;
case WID_RAT_BUILD_EW:
- HandlePlacePushButton(this, WID_RAT_BUILD_EW, GetRailTypeInfo(_cur_railtype)->cursor.rail_ew, HT_LINE | HT_DIR_HL);
+ HandleRailPlacePushButton(this, WID_RAT_BUILD_EW, RAIL_CURSOR_EW, HT_LINE | HT_DIR_HL);
this->last_user_action = widget;
break;
case WID_RAT_BUILD_Y:
- HandlePlacePushButton(this, WID_RAT_BUILD_Y, GetRailTypeInfo(_cur_railtype)->cursor.rail_nwse, HT_LINE | HT_DIR_Y);
+ HandleRailPlacePushButton(this, WID_RAT_BUILD_Y, RAIL_CURSOR_Y, HT_LINE | HT_DIR_Y);
this->last_user_action = widget;
break;
case WID_RAT_AUTORAIL:
- HandlePlacePushButton(this, WID_RAT_AUTORAIL, GetRailTypeInfo(_cur_railtype)->cursor.autorail, HT_RAIL);
+ HandleRailPlacePushButton(this, WID_RAT_AUTORAIL, RAIL_CURSOR_AUTORAIL, HT_RAIL);
this->last_user_action = widget;
break;
@@ -607,6 +653,14 @@ struct BuildRailToolbarWindow : Window {
virtual void OnPlaceObject(Point pt, TileIndex tile)
{
+ /* Test if we are in "polyline" mode. */
+ if (_thd.drawstyle & HT_POLY) {
+ /* There is no drag-dropping while in "polyline" mode, we build the track
+ * immidiately after pushing mouse button down. */
+ HandleAutodirPlacement();
+ return;
+ }
+
switch (this->last_user_action) {
case WID_RAT_BUILD_NS:
VpStartPlaceSizing(tile, VPM_FIX_VERTICAL | VPM_RAILDIRS, DDSP_PLACE_RAIL);
@@ -655,7 +709,7 @@ struct BuildRailToolbarWindow : Window {
break;
case WID_RAT_BUILD_TUNNEL:
- DoCommandP(tile, _cur_railtype | (TRANSPORT_RAIL << 8), 0, CMD_BUILD_TUNNEL | CMD_MSG(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE), CcBuildRailTunnel);
+ PlaceRail_Tunnel(tile);
break;
case WID_RAT_CONVERT_RAIL:
@@ -758,13 +812,15 @@ struct BuildRailToolbarWindow : Window {
};
const uint16 _railtoolbar_autorail_keys[] = {'5', 'A' | WKC_GLOBAL_HOTKEY, 0};
+const uint16 _railtoolbar_polyline_autorail_keys[] = {'5' | WKC_CTRL, 'A' | WKC_GLOBAL_HOTKEY | WKC_CTRL, 0};
Hotkey<BuildRailToolbarWindow> BuildRailToolbarWindow::railtoolbar_hotkeys[] = {
Hotkey<BuildRailToolbarWindow>('1', "build_ns", WID_RAT_BUILD_NS),
Hotkey<BuildRailToolbarWindow>('2', "build_x", WID_RAT_BUILD_X),
Hotkey<BuildRailToolbarWindow>('3', "build_ew", WID_RAT_BUILD_EW),
Hotkey<BuildRailToolbarWindow>('4', "build_y", WID_RAT_BUILD_Y),
- Hotkey<BuildRailToolbarWindow>(_railtoolbar_autorail_keys, "autorail", WID_RAT_AUTORAIL),
+ Hotkey<BuildRailToolbarWindow>(_railtoolbar_autorail_keys, "autorail", WID_RAT_AUTORAIL), // without CTRL
+ Hotkey<BuildRailToolbarWindow>(_railtoolbar_polyline_autorail_keys, "polyrail", WID_RAT_AUTORAIL), // with CTRL
Hotkey<BuildRailToolbarWindow>('6', "demolish", WID_RAT_DEMOLISH),
Hotkey<BuildRailToolbarWindow>('7', "depot", WID_RAT_BUILD_DEPOT),
Hotkey<BuildRailToolbarWindow>('8', "waypoint", WID_RAT_BUILD_WAYPOINT),
diff --git a/src/rail_map.h b/src/rail_map.h
index c8033a311..8a385c5b4 100644
--- a/src/rail_map.h
+++ b/src/rail_map.h
@@ -525,9 +525,7 @@ static inline void MakeRailNormal(TileIndex t, Owner o, TrackBits b, RailType r)
_m[t].m3 = r;
_m[t].m4 = 0;
_m[t].m5 = RAIL_TILE_NORMAL << 6 | b;
- SB(_m[t].m6, 2, 4, 0);
- _me[t].m7 = 0;
-}
+ SB(_m[t].m6, 2, 4, 0); _me[t].m7 = 0;}
static inline void MakeRailDepot(TileIndex t, Owner o, DepotID did, DiagDirection d, RailType r)
@@ -542,4 +540,28 @@ static inline void MakeRailDepot(TileIndex t, Owner o, DepotID did, DiagDirectio
_me[t].m7 = 0;
}
+
+
+static inline void IncreaseStuckCounter(TileIndex t)
+{
+ if (!IsTileType(t, MP_RAILWAY)) return;
+ if (_me[t].m7 == 255) return;
+ _me[t].m7++;
+}
+
+
+static inline void ReduceStuckCounter(TileIndex t)
+{
+ if (!IsTileType(t, MP_RAILWAY)) return;
+ _me[t].m7 -= _me[t].m7/4;
+}
+
+
+static inline byte GetStuckCounter(TileIndex t)
+{
+ if (!IsTileType(t, MP_RAILWAY)) return 0;
+ return _me[t].m7;
+
+}
+
#endif /* RAIL_MAP_H */
diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp
index 7931fa965..5954b6547 100644
--- a/src/road_cmd.cpp
+++ b/src/road_cmd.cpp
@@ -35,6 +35,7 @@
#include "date_func.h"
#include "genworld.h"
#include "company_gui.h"
+#include "trafficlight_func.h"
#include "table/strings.h"
@@ -173,9 +174,10 @@ CommandCost CheckAllowRemoveRoad(TileIndex tile, RoadBits remove, Owner owner, R
* @param pieces roadbits to remove
* @param rt roadtype to remove
* @param crossing_check should we check if there is a tram track when we are removing road from crossing?
+ * @param trafficlights_check Should we check if there is a traffic light before removing road bits?
* @param town_check should we check if the town allows removal?
*/
-static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits pieces, RoadType rt, bool crossing_check, bool town_check = true)
+static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits pieces, RoadType rt, bool crossing_check, bool trafficlights_check, bool town_check = true)
{
RoadTypes rts = GetRoadTypes(tile);
/* The tile doesn't have the given road type */
@@ -299,6 +301,11 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec
/* Now set present what it will be after the remove */
present ^= pieces;
+ /* Check if traffic lights are present and if removing them would cause the tile to have less than three roadbits (of any kind!). */
+ if (trafficlights_check && HasTrafficLights(tile) && (CountBits(present | GetOtherRoadBits(tile, rt)) < 3)) {
+ return_cmd_error(STR_ERROR_MUST_REMOVE_TRAFFIC_LIGHTS_FIRST);
+ }
+
/* Check for invalid RoadBit combinations on slopes */
if (tileh != SLOPE_FLAT && present != ROAD_NONE &&
(present & _invalid_tileh_slopes_road[0][tileh & SLOPE_ELEVATED]) == present) {
@@ -399,7 +406,6 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec
}
}
-
/**
* Calculate the costs for roads on slopes
* Aside modify the RoadBits to fit on the slopes
@@ -956,7 +962,7 @@ CommandCost CmdRemoveLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32
/* try to remove the halves. */
if (bits != 0) {
- CommandCost ret = RemoveRoad(tile, flags & ~DC_EXEC, bits, rt, true);
+ CommandCost ret = RemoveRoad(tile, flags & ~DC_EXEC, bits, rt, true, true);
if (ret.Succeeded()) {
if (flags & DC_EXEC) {
money -= ret.GetCost();
@@ -964,7 +970,7 @@ CommandCost CmdRemoveLongRoad(TileIndex start_tile, DoCommandFlag flags, uint32
_additional_cash_required = DoCommand(start_tile, end_tile, p2, flags & ~DC_EXEC, CMD_REMOVE_LONG_ROAD).GetCost();
return cost;
}
- RemoveRoad(tile, flags, bits, rt, true, false);
+ RemoveRoad(tile, flags, bits, rt, true, true, false);
}
cost.AddCost(ret);
had_success = true;
@@ -1067,9 +1073,18 @@ static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags)
/* Clear the road if only one piece is on the tile OR we are not using the DC_AUTO flag */
if ((HasExactlyOneBit(b) && GetRoadBits(tile, ROADTYPE_TRAM) == ROAD_NONE) || !(flags & DC_AUTO)) {
CommandCost ret(EXPENSES_CONSTRUCTION);
+
+ /* Remove traffic light if necessary. */
+ if (HasTrafficLights(tile)) {
+ CommandCost tl_ret = CmdRemoveTrafficLights(tile, flags, 0, 0, 0);
+ if (tl_ret.Failed()) return tl_ret;
+ ret.AddCost(tl_ret);
+ }
+
+ /* Remove road bits. */
RoadType rt;
FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(tile)) {
- CommandCost tmp_ret = RemoveRoad(tile, flags, GetRoadBits(tile, rt), rt, true);
+ CommandCost tmp_ret = RemoveRoad(tile, flags, GetRoadBits(tile, rt), rt, true, false);
if (tmp_ret.Failed()) return tmp_ret;
ret.AddCost(tmp_ret);
}
@@ -1089,7 +1104,7 @@ static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags)
RoadType rt = ROADTYPE_TRAM;
do {
if (HasBit(rts, rt)) {
- CommandCost tmp_ret = RemoveRoad(tile, flags, GetCrossingRoadBits(tile), rt, false);
+ CommandCost tmp_ret = RemoveRoad(tile, flags, GetCrossingRoadBits(tile), rt, false, false);
if (tmp_ret.Failed()) return tmp_ret;
ret.AddCost(tmp_ret);
}
@@ -1212,7 +1227,7 @@ void DrawTramCatenary(const TileInfo *ti, RoadBits tram)
* @param dy the offset from the top of the BB of the tile
* @param h the height of the sprite to draw
*/
-static void DrawRoadDetail(SpriteID img, const TileInfo *ti, int dx, int dy, int h)
+void DrawRoadDetail(SpriteID img, TileInfo *ti, int dx, int dy, int h)
{
int x = ti->x | dx;
int y = ti->y | dy;
@@ -1278,6 +1293,8 @@ static void DrawRoadBits(TileInfo *ti)
}
}
+ if (_settings_game.construction.traffic_lights && HasTrafficLights(ti->tile) && _cur_dpi->zoom <= ZOOM_LVL_DETAIL) DrawTrafficLights(ti);
+
if (HasRoadWorks(ti->tile)) {
/* Road works */
DrawGroundSprite((road | tram) & ROAD_X ? SPR_EXCAVATION_X : SPR_EXCAVATION_Y, PAL_NONE);
@@ -1466,6 +1483,19 @@ static Foundation GetFoundation_Road(TileIndex tile, Slope tileh)
}
}
+/**
+ * Animates the traffic lights on a tile.
+ * @param tile This tile.
+ * @pre The setting must be anabled.
+ * @pre The tile must have trafficlights.
+ */
+static void AnimateTile_Road(TileIndex tile)
+{
+ if (_settings_game.construction.traffic_lights && HasTrafficLights(tile)) {
+ if (_tick_counter % 16 == 0) MarkTileDirtyByTile(tile);
+ }
+}
+
static const Roadside _town_road_types[][2] = {
{ ROADSIDE_GRASS, ROADSIDE_GRASS },
{ ROADSIDE_PAVED, ROADSIDE_PAVED },
@@ -1511,7 +1541,7 @@ static void TileLoop_Road(TileIndex tile)
grp = GetTownRadiusGroup(t, tile);
/* Show an animation to indicate road work */
- if (t->road_build_months != 0 &&
+ if ((t->road_build_months != 0 || Chance16(_settings_game.economy.random_road_construction, 100)) &&
(DistanceManhattan(t->xy, tile) < 8 || grp != HZB_TOWN_EDGE) &&
IsNormalRoad(tile) && !HasAtMostOneBit(GetAllRoadBits(tile))) {
if (GetFoundationSlope(tile) == SLOPE_FLAT && EnsureNoVehicleOnGround(tile).Succeeded() && Chance16(1, 40)) {
@@ -1550,20 +1580,29 @@ static void TileLoop_Road(TileIndex tile)
SetRoadside(tile, cur_rs);
MarkTileDirtyByTile(tile);
}
- } else if (IncreaseRoadWorksCounter(tile)) {
- TerminateRoadWorks(tile);
+ } else {
+ /* In the first half of roadworks, generate traffic lights with a certain chance. */
+ if (_settings_game.construction.traffic_lights && _settings_game.construction.towns_build_traffic_lights &&
+ (GetRoadWorksCounter(tile) < 8) && (CountBits(GetRoadBits(tile, ROADTYPE_ROAD)) >= 3) &&
+ !HasTrafficLights(tile) && Chance16(1, 20)) {
+ CmdBuildTrafficLights(tile, DC_EXEC | DC_AUTO | DC_NO_WATER, 0, 0, 0);
+ MarkTileDirtyByTile(tile);
+ }
+ if (IncreaseRoadWorksCounter(tile)) {
+ TerminateRoadWorks(tile);
- if (_settings_game.economy.mod_road_rebuild) {
- /* Generate a nicer town surface */
- const RoadBits old_rb = GetAnyRoadBits(tile, ROADTYPE_ROAD);
- const RoadBits new_rb = CleanUpRoadBits(tile, old_rb);
+ if (_settings_game.economy.mod_road_rebuild) {
+ /* Generate a nicer town surface. */
+ const RoadBits old_rb = GetAnyRoadBits(tile, ROADTYPE_ROAD);
+ const RoadBits new_rb = CleanUpRoadBits(tile, old_rb);
- if (old_rb != new_rb) {
- RemoveRoad(tile, DC_EXEC | DC_AUTO | DC_NO_WATER, (old_rb ^ new_rb), ROADTYPE_ROAD, true);
+ if (old_rb != new_rb) {
+ RemoveRoad(tile, DC_EXEC | DC_AUTO | DC_NO_WATER, (old_rb ^ new_rb), ROADTYPE_ROAD, true, true);
+ }
}
- }
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile);
+ }
}
}
@@ -1598,7 +1637,7 @@ static const TrackBits _road_trackbits[16] = {
static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side)
{
TrackdirBits trackdirbits = TRACKDIR_BIT_NONE;
- TrackdirBits red_signals = TRACKDIR_BIT_NONE; // crossing barred
+ TrackdirBits red_signals = TRACKDIR_BIT_NONE; // Crossing barred or red traffic light.
switch (mode) {
case TRANSPORT_RAIL:
if (IsLevelCrossing(tile)) trackdirbits = TrackBitsToTrackdirBits(GetCrossingRailBits(tile));
@@ -1617,6 +1656,8 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u
uint multiplier = drd_to_multiplier[rt == ROADTYPE_TRAM ? DRD_NONE : GetDisallowedRoadDirections(tile)];
if (!HasRoadWorks(tile)) trackdirbits = (TrackdirBits)(_road_trackbits[bits] * multiplier);
+ if (_settings_game.construction.traffic_lights && HasTrafficLights(tile))
+ red_signals = trackdirbits & GetTrafficLightDisallowedDirections(tile);
break;
}
@@ -1686,11 +1727,16 @@ static void GetTileDesc_Road(TileIndex tile, TileDesc *td)
default: {
RoadTypes rts = GetRoadTypes(tile);
- td->str = (HasBit(rts, ROADTYPE_ROAD) ? _road_tile_strings[GetRoadside(tile)] : STR_LAI_ROAD_DESCRIPTION_TRAMWAY);
+ if (HasTrafficLights(tile)) {
+ td->str = STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_TRAFFIC_LIGHTS;
+ } else {
+ td->str = (HasBit(rts, ROADTYPE_ROAD) ? _road_tile_strings[GetRoadside(tile)] : STR_LAI_ROAD_DESCRIPTION_TRAMWAY);
+ }
if (HasBit(rts, ROADTYPE_ROAD)) road_owner = GetRoadOwner(tile, ROADTYPE_ROAD);
if (HasBit(rts, ROADTYPE_TRAM)) tram_owner = GetRoadOwner(tile, ROADTYPE_TRAM);
break;
}
+
}
/* Now we have to discover, if the tile has only one owner or many:
@@ -1846,7 +1892,7 @@ extern const TileTypeProcs _tile_type_road_procs = {
GetTileDesc_Road, // get_tile_desc_proc
GetTileTrackStatus_Road, // get_tile_track_status_proc
ClickTile_Road, // click_tile_proc
- NULL, // animate_tile_proc
+ AnimateTile_Road, // animate_tile_proc
TileLoop_Road, // tile_loop_proc
ChangeTileOwner_Road, // change_tile_owner_proc
NULL, // add_produced_cargo_proc
diff --git a/src/road_cmd.h b/src/road_cmd.h
index 3cf588dcd..e00f808f2 100644
--- a/src/road_cmd.h
+++ b/src/road_cmd.h
@@ -14,8 +14,11 @@
#include "direction_type.h"
#include "road_type.h"
+#include "tile_cmd.h"
+#include "gfx_type.h"
void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt);
void UpdateNearestTownForRoadTiles(bool invalidate);
+void DrawRoadDetail(SpriteID img, TileInfo *ti, int dx, int dy, int h);
#endif /* ROAD_CMD_H */
diff --git a/src/road_gui.cpp b/src/road_gui.cpp
index bf4ef40d1..1ca1b3ced 100644
--- a/src/road_gui.cpp
+++ b/src/road_gui.cpp
@@ -67,6 +67,23 @@ void CcPlaySound1D(const CommandCost &result, TileIndex tile, uint32 p1, uint32
}
/**
+ * Place trafficlights on a tile or returns an error.
+ * @param tile This tile.
+ */
+static void PlaceRoad_TrafficLights(TileIndex tile, Window *w)
+{
+ if (_remove_button_clicked) {
+ DoCommandP(tile, 0, 0, CMD_REMOVE_TRAFFICLIGHTS | CMD_MSG(STR_ERROR_CAN_T_REMOVE_TRAFFIC_LIGHTS_FROM), CcPlaySound1D);
+ } else {
+ if (!_settings_game.construction.traffic_lights) {
+ DoCommandP(tile, 0, 0, CMD_BUILD_TRAFFICLIGHTS | CMD_MSG(STR_ERROR_BUILDING_TRAFFIC_LIGHTS_DISABLED), CcPlaySound1D);
+ } else{
+ DoCommandP(tile, 0, 0, CMD_BUILD_TRAFFICLIGHTS | CMD_MSG(STR_ERROR_CAN_T_PLACE_TRAFFIC_LIGHTS), CcPlaySound1D);
+ }
+ }
+}
+
+/**
* Callback to start placing a bridge.
* @param tile Start tile of the bridge.
*/
@@ -301,6 +318,12 @@ static bool RoadToolbar_CtrlChanged(Window *w)
}
}
+ /* Allow ctrl also for traffic lights. */
+ if (w->IsWidgetLowered(WID_ROT_TRAFFIC_LIGHT)) {
+ ToggleRoadButton_Remove(w);
+ return true;
+ }
+
return false;
}
@@ -316,6 +339,11 @@ struct BuildRoadToolbarWindow : Window {
WID_ROT_ONE_WAY,
WIDGET_LIST_END);
+ this->SetWidgetsDisabledState(!_settings_game.construction.traffic_lights,
+ WID_ROT_TRAFFIC_LIGHT,
+ WIDGET_LIST_END);
+ if (!_settings_game.construction.traffic_lights && this->IsWidgetLowered(WID_ROT_TRAFFIC_LIGHT)) ResetObjectToPlace();
+
this->OnInvalidateData();
this->last_started_action = WIDGET_LIST_END;
@@ -365,6 +393,7 @@ struct BuildRoadToolbarWindow : Window {
case WID_ROT_BUS_STATION:
case WID_ROT_TRUCK_STATION:
+ case WID_ROT_TRAFFIC_LIGHT:
this->DisableWidget(WID_ROT_ONE_WAY);
this->SetWidgetDisabledState(WID_ROT_REMOVE, !this->IsWidgetLowered(clicked_widget));
break;
@@ -449,6 +478,11 @@ struct BuildRoadToolbarWindow : Window {
SetSelectionRed(false);
break;
+ case WID_ROT_TRAFFIC_LIGHT:
+ HandlePlacePushButton(this, WID_ROT_TRAFFIC_LIGHT, SPR_CURSOR_TRAFFIC_LIGHT, HT_RECT);
+ this->last_started_action = widget;
+ break;
+
case WID_ROT_BUILD_BRIDGE:
HandlePlacePushButton(this, WID_ROT_BUILD_BRIDGE, SPR_CURSOR_BRIDGE, HT_RECT);
this->last_started_action = widget;
@@ -523,6 +557,10 @@ struct BuildRoadToolbarWindow : Window {
PlaceRoad_TruckStation(tile);
break;
+ case WID_ROT_TRAFFIC_LIGHT:
+ PlaceRoad_TrafficLights(tile, this);
+ break;
+
case WID_ROT_BUILD_BRIDGE:
PlaceRoad_Bridge(tile, this);
break;
@@ -704,6 +742,8 @@ static const NWidgetPart _nested_build_road_widgets[] = {
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_ONE_WAY),
SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRAFFIC_LIGHT),
+ SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAFFIC_LIGHT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_BRIDGE),
SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_TUNNEL),
@@ -743,6 +783,8 @@ static const NWidgetPart _nested_build_tramway_widgets[] = {
SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRUCK_BAY, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(),
NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_ROT_ONE_WAY), SetMinimalSize(0, 0),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRAFFIC_LIGHT),
+ SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAFFIC_LIGHT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_BRIDGE),
SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_BRIDGE),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_TUNNEL),
@@ -803,6 +845,8 @@ static const NWidgetPart _nested_build_road_scen_widgets[] = {
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, -1), SetMinimalSize(0, 22), SetFill(1, 1), EndContainer(),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_ONE_WAY),
SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_ONE_WAY, STR_ROAD_TOOLBAR_TOOLTIP_TOGGLE_ONE_WAY_ROAD),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_TRAFFIC_LIGHT),
+ SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_TRAFFIC_LIGHT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAFFIC_LIGHT),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_BRIDGE),
SetFill(0, 1), SetMinimalSize(43, 22), SetDataTip(SPR_IMG_BRIDGE, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_BRIDGE),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_TUNNEL),
diff --git a/src/road_map.h b/src/road_map.h
index 6fe1a02b1..f79192f1e 100644
--- a/src/road_map.h
+++ b/src/road_map.h
@@ -251,6 +251,29 @@ static inline bool HasTownOwnedRoad(TileIndex t)
return HasTileRoadType(t, ROADTYPE_ROAD) && IsRoadOwner(t, ROADTYPE_ROAD, OWNER_TOWN);
}
+static inline void MakeTrafficLights(TileIndex t)
+{
+ assert(IsTileType(t, MP_ROAD));
+ assert(GetRoadTileType(t) == ROAD_TILE_NORMAL);
+ SetBit(_me[t].m7, 4);
+}
+
+static inline void ClearTrafficLights(TileIndex t)
+{
+ assert(IsTileType(t, MP_ROAD));
+ assert(GetRoadTileType(t) == ROAD_TILE_NORMAL);
+ ClrBit(_me[t].m7, 4);
+}
+
+/**
+ * Check if a tile has traffic lights returns true if tile has traffic lights.
+ * @param t The tile to check.
+ */
+static inline bool HasTrafficLights(TileIndex t)
+{
+ return (IsTileType(t, MP_ROAD) && (GetRoadTileType(t) == ROAD_TILE_NORMAL) && HasBit(_me[t].m7, 4));
+}
+
/** Which directions are disallowed ? */
enum DisallowedRoadDirections {
DRD_NONE, ///< None of the directions are disallowed
@@ -493,7 +516,14 @@ static inline bool IncreaseRoadWorksCounter(TileIndex t)
{
AB(_me[t].m7, 0, 4, 1);
- return GB(_me[t].m7, 0, 4) == 15;
+// return GB(_me[t].m7, 0, 4) == 15;
+//DC-1 - kevesebb idot az utjavitasra
+ return GB(_me[t].m7, 0, 4) == 1;
+}
+
+static inline byte GetRoadWorksCounter(TileIndex t)
+{
+ return GB(_m[t].m3, 0, 4);
}
/**
diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp
index e740e26f9..13ec454e0 100644
--- a/src/roadveh_cmd.cpp
+++ b/src/roadveh_cmd.cpp
@@ -216,6 +216,8 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length)
v->gcache.cached_total_length = 0;
+ uint32 cargo_mask = 0;
+
for (RoadVehicle *u = v; u != NULL; u = u->Next()) {
/* Check the v->first cache. */
assert(u->First() == v);
@@ -237,12 +239,15 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length)
/* Invalidate the vehicle colour map */
u->colourmap = PAL_NONE;
+ /* Update carried cargo. */
+ if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type);
/* Update cargo aging period. */
u->vcache.cached_cargo_age_period = GetVehicleProperty(u, PROP_ROADVEH_CARGO_AGE_PERIOD, EngInfo(u->engine_type)->cargo_age_period);
}
uint max_speed = GetVehicleProperty(v, PROP_ROADVEH_SPEED, 0);
v->vcache.cached_max_speed = (max_speed != 0) ? max_speed * 4 : RoadVehInfo(v->engine_type)->max_speed;
+ v->vcache.cached_cargo_mask = cargo_mask;
}
/**
@@ -1392,9 +1397,8 @@ again:
v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) &&
GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) {
Station *st = Station::GetByTile(v->tile);
- v->last_station_visited = st->index;
RoadVehArrivesAt(v, st);
- v->BeginLoading();
+ v->BeginLoading(st->index);
}
return false;
}
@@ -1453,13 +1457,13 @@ again:
rs->SetEntranceBusy(false);
SetBit(v->state, RVS_ENTERED_STOP);
- v->last_station_visited = st->index;
-
if (IsDriveThroughStopTile(v->tile) || (v->current_order.IsType(OT_GOTO_STATION) && v->current_order.GetDestination() == st->index)) {
RoadVehArrivesAt(v, st);
- v->BeginLoading();
+ v->BeginLoading(st->index);
return false;
}
+
+ v->last_station_visited = st->index;
} else {
/* Vehicle is ready to leave a bay in a road stop */
if (rs->IsEntranceBusy()) {
diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp
index 15c44d392..2f84ef0e8 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -51,7 +51,9 @@
#include "../core/backup_type.hpp"
#include "../smallmap_gui.h"
#include "../news_func.h"
+#include "../cargodest_func.h"
#include "../error.h"
+#include "../trafficlight_func.h"
#include "saveload_internal.h"
@@ -254,6 +256,7 @@ static void InitializeWindowsAndCaches()
Station::RecomputeIndustriesNearForAll();
RebuildSubsidisedSourceAndDestinationCache();
+ RebuildCargoLinkCounts();
/* Towns have a noise controlled number of airports system
* So each airport's noise value must be added to the town->noise_reached value
@@ -2761,6 +2764,50 @@ bool AfterLoadGame()
_settings_game.script.settings_profile = IsInsideMM(_old_diff_level, SP_BEGIN, SP_END) ? _old_diff_level : (uint)SP_MEDIUM;
}
+ if (IsSavegameVersionBefore(180)) {
+ /* Update cargo acceptance map of towns. */
+ for (TileIndex t = 0; t < map_size; t++) {
+ if (!IsTileType(t, MP_HOUSE)) continue;
+ Town::Get(GetTownIndex(t))->cargo_accepted.Add(t);
+ }
+
+ Town *town;
+ FOR_ALL_TOWNS(town) {
+ UpdateTownCargoes(town);
+ }
+
+ /* Update cargo acceptance of industries. */
+ Industry *ind;
+ FOR_ALL_INDUSTRIES(ind) {
+ UpdateIndustryAcceptance(ind);
+ ind->average_production[0] = ind->last_month_production[0];
+ ind->average_production[1] = ind->last_month_production[1];
+ }
+
+ UpdateCargoLinks();
+
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ /* Set the current order index from the order list. */
+ Order *o = v->GetOrder(v->cur_implicit_order_index);
+ if (o != NULL) v->current_order.index = o->index;
+
+ /* Pre-fill route links from orders. */
+ if (v->IsFrontEngine()) PrefillRouteLinks(v);
+ }
+ }
+
+ if (IsSavegameVersionBefore(TL_SV)) {
+ /* Savegames before this version can not have trafficlights already.
+ * Sometimes they are added randomly when loading old savegames.
+ * Unfortunately also in the wrong places -> not on crossings.
+ *
+ * Iterate over the whole map and remove any trafficlights found. */
+ for (TileIndex tile = 0; tile < MapSize(); tile++) {
+ if (HasTrafficLights(tile)) ClearTrafficLights(tile);
+ }
+ }
+
/* Road stops is 'only' updating some caches */
AfterLoadRoadStops();
AfterLoadLabelMaps();
diff --git a/src/saveload/cargodest_sl.cpp b/src/saveload/cargodest_sl.cpp
new file mode 100644
index 000000000..1950268c0
--- /dev/null
+++ b/src/saveload/cargodest_sl.cpp
@@ -0,0 +1,181 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file cargodest_sl.cpp Code handling saving and loading of cargo destinations. */
+
+#include "../stdafx.h"
+#include "../cargodest_base.h"
+#include "../town.h"
+#include "../industry.h"
+#include "saveload.h"
+
+static uint32 _cargolink_uint;
+static const SaveLoadGlobVarList _cargolink_uint_desc[] = {
+ SLEG_VAR(_cargolink_uint, SLE_UINT32),
+ SLEG_END()
+};
+
+static const SaveLoad _cargolink_desc[] = {
+ SLE_VAR(CargoLink, amount.old_max, SLE_UINT32),
+ SLE_VAR(CargoLink, amount.new_max, SLE_UINT32),
+ SLE_VAR(CargoLink, amount.old_act, SLE_UINT32),
+ SLE_VAR(CargoLink, amount.new_act, SLE_UINT32),
+ SLE_VAR(CargoLink, weight, SLE_UINT32),
+ SLE_VAR(CargoLink, weight_mod, SLE_UINT8),
+ SLE_END()
+};
+
+void CargoSourceSink::SaveCargoSourceSink()
+{
+ if (IsSavegameVersionBefore(180)) return;
+
+ static const SaveLoad _cargosourcesink_desc[] = {
+ SLE_ARR(CargoSourceSink, cargo_links_weight, SLE_UINT32, NUM_CARGO),
+ SLE_END()
+ };
+ SlObject(this, _cargosourcesink_desc);
+
+ for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) {
+ _cargolink_uint = this->cargo_links[cid].Length();
+ SlObject(NULL, _cargolink_uint_desc);
+ for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) {
+ SourceID dest = INVALID_SOURCE;
+ SourceTypeByte type;
+ type = ST_TOWN;
+
+ if (l->dest != NULL) {
+ type = l->dest->GetType();
+ dest = l->dest->GetID();
+ }
+
+ /* Pack type and destination index into temp variable. */
+ assert_compile(sizeof(SourceID) <= 3);
+ _cargolink_uint = type | (dest << 8);
+
+ SlGlobList(_cargolink_uint_desc);
+ SlObject(l, _cargolink_desc);
+ }
+ }
+}
+
+void CargoSourceSink::LoadCargoSourceSink()
+{
+ if (IsSavegameVersionBefore(180)) return;
+
+ static const SaveLoad _cargosourcesink_desc[] = {
+ SLE_ARR(CargoSourceSink, cargo_links_weight, SLE_UINT32, NUM_CARGO),
+ SLE_END()
+ };
+ SlObject(this, _cargosourcesink_desc);
+
+ for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) {
+ /* Remove links created by constructors. */
+ this->cargo_links[cid].Clear();
+ /* Read vector length and allocate storage. */
+ SlObject(NULL, _cargolink_uint_desc);
+ this->cargo_links[cid].Append(_cargolink_uint);
+
+ for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) {
+ /* Read packed type and dest and store in dest pointer. */
+ SlGlobList(_cargolink_uint_desc);
+ *(size_t*)&l->dest = _cargolink_uint;
+
+ SlObject(l, _cargolink_desc);
+ }
+ }
+}
+
+void CargoSourceSink::PtrsCargoSourceSink()
+{
+ if (IsSavegameVersionBefore(180)) return;
+
+ for (uint cid = 0; cid < lengthof(this->cargo_links); cid++) {
+ for (CargoLink *l = this->cargo_links[cid].Begin(); l != this->cargo_links[cid].End(); l++) {
+ /* Extract type and destination index. */
+ SourceType type = (SourceType)((size_t)l->dest & 0xFF);
+ SourceID dest = (SourceID)((size_t)l->dest >> 8);
+
+ /* Resolve index. */
+ l->dest = NULL;
+ if (dest != INVALID_SOURCE) {
+ switch (type) {
+ case ST_TOWN:
+ if (!Town::IsValidID(dest)) SlErrorCorrupt("Invalid cargo link destination");
+ l->dest = Town::Get(dest);
+ break;
+
+ case ST_INDUSTRY:
+ if (!Industry::IsValidID(dest)) SlErrorCorrupt("Invalid cargo link destination");
+ l->dest = Industry::Get(dest);
+ break;
+
+ default:
+ SlErrorCorrupt("Invalid cargo link destination type");
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Wrapper function to get the RouteLinks's internal structure while
+ * some of the variables itself are private.
+ * @return The SaveLoad description for RouteLinks.
+ */
+const SaveLoad *GetRouteLinkDescription()
+{
+ static const SaveLoad _routelink_desc[] = {
+ SLE_VAR(RouteLink, dest, SLE_UINT16),
+ SLE_VAR(RouteLink, prev_order, SLE_UINT16),
+ SLE_VAR(RouteLink, next_order, SLE_UINT16),
+ SLE_VAR(RouteLink, owner, SLE_UINT8),
+ SLE_VAR(RouteLink, vtype, SLE_UINT8),
+ SLE_VAR(RouteLink, travel_time, SLE_UINT32),
+ SLE_VAR(RouteLink, wait_time, SLE_UINT16),
+
+ SLE_END()
+ };
+ return _routelink_desc;
+}
+
+/** Save the RouteLink chunk. */
+static void Save_RTLN()
+{
+ RouteLink *link;
+
+ FOR_ALL_ROUTELINKS(link) {
+ SlSetArrayIndex(link->index);
+ SlObject(link, GetRouteLinkDescription());
+ }
+}
+
+/** Load the RouteLink chunk. */
+static void Load_RTLN()
+{
+ int index;
+
+ while ((index = SlIterateArray()) != -1) {
+ RouteLink *link = new (index) RouteLink();
+ SlObject(link, GetRouteLinkDescription());
+ }
+}
+
+/** Resolve references after loading the RouteLink chunk. */
+static void Ptrs_RTLN()
+{
+ RouteLink *link;
+
+ FOR_ALL_ROUTELINKS(link) {
+ SlObject(link, GetRouteLinkDescription());
+ }
+}
+
+extern const ChunkHandler _routelink_chunk_handlers[] = {
+ { 'RTLN', Save_RTLN, Load_RTLN, Ptrs_RTLN, NULL, CH_ARRAY | CH_LAST},
+};
diff --git a/src/saveload/cargopacket_sl.cpp b/src/saveload/cargopacket_sl.cpp
index 092301e95..95a8d1554 100644
--- a/src/saveload/cargopacket_sl.cpp
+++ b/src/saveload/cargopacket_sl.cpp
@@ -95,6 +95,12 @@ const SaveLoad *GetCargoPacketDesc()
SLE_VAR(CargoPacket, feeder_share, SLE_INT64),
SLE_CONDVAR(CargoPacket, source_type, SLE_UINT8, 125, SL_MAX_VERSION),
SLE_CONDVAR(CargoPacket, source_id, SLE_UINT16, 125, SL_MAX_VERSION),
+ SLE_CONDVAR(CargoPacket, dest_xy, SLE_UINT32, 180, SL_MAX_VERSION),
+ SLE_CONDVAR(CargoPacket, dest_id, SLE_UINT16, 180, SL_MAX_VERSION),
+ SLE_CONDVAR(CargoPacket, dest_type, SLE_UINT8, 180, SL_MAX_VERSION),
+ SLE_CONDVAR(CargoPacket, flags, SLE_UINT8, 180, SL_MAX_VERSION),
+ SLE_CONDVAR(CargoPacket, next_order, SLE_UINT16, 180, SL_MAX_VERSION),
+ SLE_CONDVAR(CargoPacket, next_station, SLE_UINT16, 180, SL_MAX_VERSION),
/* Used to be paid_for, but that got changed. */
SLE_CONDNULL(1, 0, 120),
diff --git a/src/saveload/economy_sl.cpp b/src/saveload/economy_sl.cpp
index 9bdad61a9..e5d6c9ed4 100644
--- a/src/saveload/economy_sl.cpp
+++ b/src/saveload/economy_sl.cpp
@@ -67,6 +67,8 @@ static const SaveLoad _cargopayment_desc[] = {
SLE_VAR(CargoPayment, route_profit, SLE_INT64),
SLE_VAR(CargoPayment, visual_profit, SLE_INT64),
+ SLE_CONDVAR(CargoPayment, transfer_profit, SLE_INT64, 180, SL_MAX_VERSION),
+
SLE_END()
};
diff --git a/src/saveload/industry_sl.cpp b/src/saveload/industry_sl.cpp
index 8943a5d52..be51bdc57 100644
--- a/src/saveload/industry_sl.cpp
+++ b/src/saveload/industry_sl.cpp
@@ -30,12 +30,14 @@ static const SaveLoad _industry_desc[] = {
SLE_ARR(Industry, production_rate, SLE_UINT8, 2),
SLE_CONDNULL( 3, 0, 60), ///< used to be industry's accepts_cargo
SLE_CONDARR(Industry, accepts_cargo, SLE_UINT8, 3, 78, SL_MAX_VERSION),
+ SLE_CONDVAR(Industry, produced_accepted_mask, SLE_UINT32, 180, SL_MAX_VERSION),
SLE_VAR(Industry, prod_level, SLE_UINT8),
SLE_ARR(Industry, this_month_production, SLE_UINT16, 2),
SLE_ARR(Industry, this_month_transported, SLE_UINT16, 2),
SLE_ARR(Industry, last_month_pct_transported, SLE_UINT8, 2),
SLE_ARR(Industry, last_month_production, SLE_UINT16, 2),
SLE_ARR(Industry, last_month_transported, SLE_UINT16, 2),
+ SLE_CONDARR(Industry, average_production, SLE_UINT16, 2, 180, SL_MAX_VERSION),
SLE_VAR(Industry, counter, SLE_UINT16),
@@ -63,6 +65,12 @@ static const SaveLoad _industry_desc[] = {
SLE_END()
};
+static void RealSave_INDY(Industry *ind)
+{
+ SlObject(ind, _industry_desc);
+ ind->SaveCargoSourceSink();
+}
+
static void Save_INDY()
{
Industry *ind;
@@ -70,7 +78,7 @@ static void Save_INDY()
/* Write the industries */
FOR_ALL_INDUSTRIES(ind) {
SlSetArrayIndex(ind->index);
- SlObject(ind, _industry_desc);
+ SlAutolength((AutolengthProc *)RealSave_INDY, ind);
}
}
@@ -93,6 +101,7 @@ static void Load_INDY()
while ((index = SlIterateArray()) != -1) {
Industry *i = new (index) Industry();
SlObject(i, _industry_desc);
+ i->LoadCargoSourceSink();
/* Before savegame version 161, persistent storages were not stored in a pool. */
if (IsSavegameVersionBefore(161) && !IsSavegameVersionBefore(76)) {
@@ -121,6 +130,7 @@ static void Ptrs_INDY()
FOR_ALL_INDUSTRIES(i) {
SlObject(i, _industry_desc);
+ i->PtrsCargoSourceSink();
}
}
diff --git a/src/saveload/map_sl.cpp b/src/saveload/map_sl.cpp
index 7088a4406..c54e54e92 100644
--- a/src/saveload/map_sl.cpp
+++ b/src/saveload/map_sl.cpp
@@ -11,6 +11,7 @@
#include "../stdafx.h"
#include "../map_func.h"
+#include "../layer_func.h"
#include "../core/bitmath_func.hpp"
#include "../fios.h"
@@ -18,10 +19,12 @@
static uint32 _map_dim_x;
static uint32 _map_dim_y;
+static uint32 _layer_count;
static const SaveLoadGlobVarList _map_dimensions[] = {
SLEG_CONDVAR(_map_dim_x, SLE_UINT32, 6, SL_MAX_VERSION),
SLEG_CONDVAR(_map_dim_y, SLE_UINT32, 6, SL_MAX_VERSION),
+ SLEG_CONDVAR(_layer_count, SLE_UINT32, 6, SL_MAX_VERSION),
SLEG_END()
};
@@ -29,13 +32,14 @@ static void Save_MAPS()
{
_map_dim_x = MapSizeX();
_map_dim_y = MapSizeY();
+ _layer_count = LayerCount();
SlGlobList(_map_dimensions);
}
static void Load_MAPS()
{
SlGlobList(_map_dimensions);
- AllocateMap(_map_dim_x, _map_dim_y);
+ AllocateMap(_map_dim_x, _map_dim_y/_layer_count, _layer_count);
}
static void Check_MAPS()
diff --git a/src/saveload/order_sl.cpp b/src/saveload/order_sl.cpp
index 4d2a4b02e..0799b0b33 100644
--- a/src/saveload/order_sl.cpp
+++ b/src/saveload/order_sl.cpp
@@ -203,7 +203,14 @@ static void Ptrs_ORDR()
const SaveLoad *GetOrderListDescription()
{
static const SaveLoad _orderlist_desc[] = {
- SLE_REF(OrderList, first, REF_ORDER),
+ SLE_REF( OrderList, first, REF_ORDER),
+ SLE_CONDVAR(OrderList, current_sep_mode, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION),
+ SLE_CONDVAR(OrderList, num_sep_vehicles, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION),
+ SLE_CONDVAR(OrderList, separation_counter, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION),
+ SLE_CONDVAR(OrderList, separation_counter, SLE_UINT, SL_TTSEP_VER, SL_MAX_VERSION),
+ SLE_CONDVAR(OrderList, is_separation_valid, SLE_BOOL, SL_TTSEP_VER, SL_MAX_VERSION),
+ SLE_CONDVAR(OrderList, current_separation, SLE_INT, SL_TTSEP_VER, SL_MAX_VERSION),
+ SLE_CONDVAR(OrderList, last_timetable_init, SLE_INT, SL_TTSEP_VER, SL_MAX_VERSION),
SLE_END()
};
diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp
index a21f6242a..da3dab7c9 100644
--- a/src/saveload/saveload.cpp
+++ b/src/saveload/saveload.cpp
@@ -41,6 +41,7 @@
#include "../string_func.h"
#include "../fios.h"
#include "../error.h"
+#include "../cargodest_base.h"
#include "table/strings.h"
@@ -245,7 +246,7 @@
* 179 24810
* 180 24998
*/
-extern const uint16 SAVEGAME_VERSION = 180; ///< Current savegame version of OpenTTD.
+extern const uint16 SAVEGAME_VERSION = SL_TTSEP_VER; ///< Current savegame version of OpenTTD.
SavegameType _savegame_type; ///< type of savegame we are loading
@@ -426,6 +427,7 @@ extern const ChunkHandler _autoreplace_chunk_handlers[];
extern const ChunkHandler _labelmaps_chunk_handlers[];
extern const ChunkHandler _airport_chunk_handlers[];
extern const ChunkHandler _object_chunk_handlers[];
+extern const ChunkHandler _routelink_chunk_handlers[];
extern const ChunkHandler _persistent_storage_chunk_handlers[];
/** Array of all chunks in a savegame, \c NULL terminated. */
@@ -460,6 +462,7 @@ static const ChunkHandler * const _chunk_handlers[] = {
_labelmaps_chunk_handlers,
_airport_chunk_handlers,
_object_chunk_handlers,
+ _routelink_chunk_handlers,
_persistent_storage_chunk_handlers,
NULL,
};
@@ -1214,6 +1217,7 @@ static size_t ReferenceToInt(const void *obj, SLRefType rt)
case REF_ENGINE_RENEWS: return ((const EngineRenew*)obj)->index + 1;
case REF_CARGO_PACKET: return ((const CargoPacket*)obj)->index + 1;
case REF_ORDERLIST: return ((const OrderList*)obj)->index + 1;
+ case REF_ROUTE_LINK: return ((const RouteLink*)obj)->index + 1;
case REF_STORAGE: return ((const PersistentStorage*)obj)->index + 1;
default: NOT_REACHED();
}
@@ -1284,6 +1288,10 @@ static void *IntToReference(size_t index, SLRefType rt)
if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index);
SlErrorCorrupt("Referencing invalid CargoPacket");
+ case REF_ROUTE_LINK:
+ if (RouteLink::IsValidID(index)) return RouteLink::Get(index);
+ SlErrorCorrupt("Referencing invalid RouteLink");
+
case REF_STORAGE:
if (PersistentStorage::IsValidID(index)) return PersistentStorage::Get(index);
SlErrorCorrupt("Referencing invalid PersistentStorage");
diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h
index c5ffbeff8..c4a26b4fa 100644
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -15,6 +15,8 @@
#include "../fileio_type.h"
#include "../strings_type.h"
+#define SL_TTSEP_VER 200
+
/** Save or load result codes. */
enum SaveOrLoadResult {
SL_OK = 0, ///< completed successfully
@@ -82,9 +84,13 @@ enum SLRefType {
REF_ENGINE_RENEWS = 6, ///< Load/save a reference to an engine renewal (autoreplace).
REF_CARGO_PACKET = 7, ///< Load/save a reference to a cargo packet.
REF_ORDERLIST = 8, ///< Load/save a reference to an orderlist.
- REF_STORAGE = 9, ///< Load/save a reference to a persistent storage.
+ REF_ROUTE_LINK = 9, ///< Load/save a reference to a route link.
+ REF_STORAGE = 10, ///< Load/save a reference to a persistent storage.
};
+/* Current savegame version. */
+static const uint TL_SV = 200;
+
/** Highest possible savegame version. */
#define SL_MAX_VERSION 255
diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp
index 9feb1f274..f7793bd8c 100644
--- a/src/saveload/station_sl.cpp
+++ b/src/saveload/station_sl.cpp
@@ -258,7 +258,10 @@ const SaveLoad *GetGoodsDesc()
SLEG_CONDVAR( _cargo_feeder_share, SLE_FILE_U32 | SLE_VAR_I64, 14, 64),
SLEG_CONDVAR( _cargo_feeder_share, SLE_INT64, 65, 67),
SLE_CONDVAR(GoodsEntry, amount_fract, SLE_UINT8, 150, SL_MAX_VERSION),
+ SLE_CONDVAR(GoodsEntry, cargo_counter, SLE_UINT16, 180, SL_MAX_VERSION),
SLE_CONDLST(GoodsEntry, cargo.packets, REF_CARGO_PACKET, 68, SL_MAX_VERSION),
+ SLE_CONDVAR(GoodsEntry, cargo.next_start, SLE_UINT32, 180, SL_MAX_VERSION),
+ SLE_CONDLST(GoodsEntry, routes, REF_ROUTE_LINK, 180, SL_MAX_VERSION),
SLE_END()
};
diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp
index de52604f5..6041bd98e 100644
--- a/src/saveload/town_sl.cpp
+++ b/src/saveload/town_sl.cpp
@@ -249,6 +249,8 @@ static void RealSave_Town(Town *t)
SlObject(&t->received[i], _town_received_desc);
}
+ t->SaveCargoSourceSink();
+
if (IsSavegameVersionBefore(166)) return;
SlObject(&t->cargo_accepted, GetTileMatrixDesc());
@@ -287,6 +289,8 @@ static void Load_TOWN()
SlErrorCorrupt("Invalid town name generator");
}
+ t->LoadCargoSourceSink();
+
if (IsSavegameVersionBefore(166)) continue;
SlObject(&t->cargo_accepted, GetTileMatrixDesc());
@@ -298,16 +302,26 @@ static void Load_TOWN()
/* Rebuild total cargo acceptance. */
UpdateTownCargoTotal(t);
}
+
+ /* Cache the aligned tile index of the centre tile. */
+ uint town_x = (TileX(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID;
+ uint town_y = (TileY(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID;
+ t->xy_aligned= TileXY(town_x, town_y);
}
}
/** Fix pointers when loading town data. */
static void Ptrs_TOWN()
{
+ Town *t;
+
+ FOR_ALL_TOWNS(t) {
+ t->PtrsCargoSourceSink();
+ }
+
/* Don't run when savegame version lower than 161. */
if (IsSavegameVersionBefore(161)) return;
- Town *t;
FOR_ALL_TOWNS(t) {
SlObject(t, _town_desc);
}
diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp
index a33ceb6d3..8599f3d9f 100644
--- a/src/saveload/vehicle_sl.cpp
+++ b/src/saveload/vehicle_sl.cpp
@@ -604,6 +604,8 @@ const SaveLoad *GetVehicleDescription(VehicleType vt)
SLE_VAR(Vehicle, vehstatus, SLE_UINT8),
SLE_CONDVAR(Vehicle, last_station_visited, SLE_FILE_U8 | SLE_VAR_U16, 0, 4),
SLE_CONDVAR(Vehicle, last_station_visited, SLE_UINT16, 5, SL_MAX_VERSION),
+ SLE_CONDVAR(Vehicle, last_station_loaded, SLE_UINT16, 180, SL_MAX_VERSION),
+ SLE_CONDVAR(Vehicle, last_order_id, SLE_UINT16, 180, SL_MAX_VERSION),
SLE_VAR(Vehicle, cargo_type, SLE_UINT8),
SLE_CONDVAR(Vehicle, cargo_subtype, SLE_UINT8, 35, SL_MAX_VERSION),
@@ -616,6 +618,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt)
SLE_CONDLST(Vehicle, cargo.packets, REF_CARGO_PACKET, 68, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, cargo_age_counter, SLE_UINT16, 162, SL_MAX_VERSION),
+ SLE_CONDVAR(Vehicle, travel_time, SLE_UINT32, 180, SL_MAX_VERSION),
SLE_VAR(Vehicle, day_counter, SLE_UINT8),
SLE_VAR(Vehicle, tick_counter, SLE_UINT8),
SLE_CONDVAR(Vehicle, running_ticks, SLE_UINT8, 88, SL_MAX_VERSION),
@@ -635,6 +638,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt)
SLE_CONDVAR(Vehicle, current_order.type, SLE_UINT8, 5, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, current_order.flags, SLE_UINT8, 5, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, current_order.dest, SLE_UINT16, 5, SL_MAX_VERSION),
+ SLE_CONDVAR(Vehicle, current_order.index, SLE_UINT16, 180, SL_MAX_VERSION),
/* Refit in current order */
SLE_CONDVAR(Vehicle, current_order.refit_cargo, SLE_UINT8, 36, SL_MAX_VERSION),
diff --git a/src/script/api/script_window.hpp b/src/script/api/script_window.hpp
index 5500f53fc..ef4ba3085 100644
--- a/src/script/api/script_window.hpp
+++ b/src/script/api/script_window.hpp
@@ -1261,6 +1261,7 @@ public:
WID_GL_MAPSIZE_X_PULLDOWN = ::WID_GL_MAPSIZE_X_PULLDOWN, ///< Dropdown 'map X size'.
WID_GL_MAPSIZE_Y_PULLDOWN = ::WID_GL_MAPSIZE_Y_PULLDOWN, ///< Dropdown 'map Y size'.
+ WID_GL_LAYER_COUNT_PULLDOWN = ::WID_GL_LAYER_COUNT_PULLDOWN, ///< Dropdown 'map layer count'
WID_GL_TOWN_PULLDOWN = ::WID_GL_TOWN_PULLDOWN, ///< Dropdown 'No. of towns'.
WID_GL_INDUSTRY_PULLDOWN = ::WID_GL_INDUSTRY_PULLDOWN, ///< Dropdown 'No. of industries'.
@@ -1308,6 +1309,7 @@ public:
WID_CS_RANDOM_WORLD = ::WID_CS_RANDOM_WORLD, ///< Generate random land button
WID_CS_MAPSIZE_X_PULLDOWN = ::WID_CS_MAPSIZE_X_PULLDOWN, ///< Pull-down arrow for x map size.
WID_CS_MAPSIZE_Y_PULLDOWN = ::WID_CS_MAPSIZE_Y_PULLDOWN, ///< Pull-down arrow for y map size.
+ WID_CS_LAYER_COUNT_PULLDOWN = ::WID_CS_LAYER_COUNT_PULLDOWN, ///< Pull-down arrow for map layer count.
WID_CS_START_DATE_DOWN = ::WID_CS_START_DATE_DOWN, ///< Decrease start year (start earlier).
WID_CS_START_DATE_TEXT = ::WID_CS_START_DATE_TEXT, ///< Clickable start date value.
WID_CS_START_DATE_UP = ::WID_CS_START_DATE_UP, ///< Increase start year (start later).
diff --git a/src/settings.cpp b/src/settings.cpp
index 293ce406d..8e7db97e7 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -32,6 +32,7 @@
#include "command_func.h"
#include "console_func.h"
#include "pathfinder/pathfinder_type.h"
+#include "layer_type.h"
#include "genworld.h"
#include "train.h"
#include "news_func.h"
@@ -64,6 +65,9 @@
#include "roadveh.h"
#include "fios.h"
#include "strings_func.h"
+#include "trafficlight_func.h"
+#include "industry.h"
+#include "cargodest_func.h"
#include "void_map.h"
#include "station_base.h"
@@ -1170,6 +1174,21 @@ static size_t ConvertLandscape(const char *value)
return LookupOneOfMany("normal|hilly|desert|candy", value);
}
+/**
+ * What to do when traffic light Setting was changed.
+ * @param p1 unused
+ * @return always 0
+ */
+static bool TLSettingChanged(int32 p1)
+{
+ /* Road building gui changed. */
+ MarkWholeScreenDirty();
+
+ /* If traffic lights got disabled, clear them all. */
+ if (!_settings_game.construction.traffic_lights) ClearAllTrafficLights();
+ return true;
+}
+
static bool CheckFreeformEdges(int32 p1)
{
if (_game_mode == GM_MENU) return true;
@@ -1253,6 +1272,64 @@ static bool StationCatchmentChanged(int32 p1)
return true;
}
+bool CargodestModeChanged(int32 p1)
+{
+ /* Clear route links and destinations for cargoes that aren't routed anymore. */
+ Station *st;
+ FOR_ALL_STATIONS(st) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (CargoHasDestinations(cid)) continue;
+
+ /* Clear route links. */
+ for (RouteLinkList::iterator i = st->goods[cid].routes.begin(); i != st->goods[cid].routes.end(); ++i) {
+ delete *i;
+ }
+ st->goods[cid].routes.clear();
+
+ /* Remove destinations from cargo packets. */
+ for (StationCargoList::Iterator i = st->goods[cid].cargo.packets.begin(); i != st->goods[cid].cargo.packets.end(); ++i) {
+ (*i)->dest_id = INVALID_SOURCE;
+ (*i)->next_order = INVALID_ORDER;
+ (*i)->next_station = INVALID_STATION;
+ }
+ st->goods[cid].cargo.InvalidateCache();
+ }
+ }
+
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if (v->IsFrontEngine()) PrefillRouteLinks(v);
+ if (CargoHasDestinations(v->cargo_type)) continue;
+ /* Remove destination from all cargoes that aren't routed anymore. */
+ for (VehicleCargoList::Iterator i = v->cargo.packets.begin(); i != v->cargo.packets.end(); ++i) {
+ (*i)->dest_id = INVALID_SOURCE;
+ (*i)->next_order = INVALID_ORDER;
+ (*i)->next_station = INVALID_STATION;
+ }
+ v->cargo.InvalidateCache();
+ }
+
+ /* Clear all links for cargoes that aren't routed anymore. */
+ CargoSourceSink *css;
+ FOR_ALL_TOWNS(css) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (!CargoHasDestinations(cid)) css->cargo_links[cid].Clear();
+ }
+ }
+ FOR_ALL_INDUSTRIES(css) {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (!CargoHasDestinations(cid)) css->cargo_links[cid].Clear();
+ }
+ }
+
+ /* Recount incoming cargo links. */
+ RebuildCargoLinkCounts();
+
+ /* Update remaining links. */
+ UpdateCargoLinks();
+
+ return true;
+}
#ifdef ENABLE_NETWORK
diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp
index 0b5917490..9a17ab1c7 100644
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -1459,44 +1459,67 @@ static SettingEntry _settings_ui_interaction[] = {
static SettingsPage _settings_ui_interaction_page = {_settings_ui_interaction, lengthof(_settings_ui_interaction)};
static SettingEntry _settings_ui_sound[] = {
- SettingEntry("sound.click_beep"),
- SettingEntry("sound.confirm"),
- SettingEntry("sound.news_ticker"),
- SettingEntry("sound.news_full"),
- SettingEntry("sound.new_year"),
- SettingEntry("sound.disaster"),
- SettingEntry("sound.vehicle"),
- SettingEntry("sound.ambient"),
+ SettingEntry("sound.click_beep"),
+ SettingEntry("sound.confirm"),
+ SettingEntry("sound.news_ticker"),
+ SettingEntry("sound.news_full"),
+ SettingEntry("sound.new_year"),
+ SettingEntry("sound.disaster"),
+ SettingEntry("sound.vehicle"),
+ SettingEntry("sound.ambient"),
};
/** Sound effects sub-page */
static SettingsPage _settings_ui_sound_page = {_settings_ui_sound, lengthof(_settings_ui_sound)};
static SettingEntry _settings_ui_news[] = {
- SettingEntry("news_display.arrival_player"),
- SettingEntry("news_display.arrival_other"),
- SettingEntry("news_display.accident"),
- SettingEntry("news_display.company_info"),
- SettingEntry("news_display.open"),
- SettingEntry("news_display.close"),
- SettingEntry("news_display.economy"),
- SettingEntry("news_display.production_player"),
- SettingEntry("news_display.production_other"),
- SettingEntry("news_display.production_nobody"),
- SettingEntry("news_display.advice"),
- SettingEntry("news_display.new_vehicles"),
- SettingEntry("news_display.acceptance"),
- SettingEntry("news_display.subsidies"),
- SettingEntry("news_display.general"),
- SettingEntry("gui.coloured_news_year"),
+ SettingEntry("news_display.arrival_player"),
+ SettingEntry("news_display.arrival_other"),
+ SettingEntry("news_display.accident"),
+ SettingEntry("news_display.company_info"),
+ SettingEntry("news_display.open"),
+ SettingEntry("news_display.close"),
+ SettingEntry("news_display.economy"),
+ SettingEntry("news_display.production_player"),
+ SettingEntry("news_display.production_other"),
+ SettingEntry("news_display.production_nobody"),
+ SettingEntry("news_display.advice"),
+ SettingEntry("news_display.new_vehicles"),
+ SettingEntry("news_display.acceptance"),
+ SettingEntry("news_display.subsidies"),
+ SettingEntry("news_display.general"),
+ SettingEntry("gui.coloured_news_year"),
};
/** News sub-page */
static SettingsPage _settings_ui_news_page = {_settings_ui_news, lengthof(_settings_ui_news)};
+
+static SettingEntry _settings_ui_departureboards[] = {
+ SettingEntry("gui.max_departures"),
+ SettingEntry("gui.max_departure_time"),
+ SettingEntry("gui.departure_calc_frequency"),
+ SettingEntry("gui.departure_show_vehicle"),
+ SettingEntry("gui.departure_show_group"),
+ SettingEntry("gui.departure_show_company"),
+ SettingEntry("gui.departure_show_vehicle_type"),
+ SettingEntry("gui.departure_show_vehicle_color"),
+ SettingEntry("gui.departure_larger_font"),
+ SettingEntry("gui.departure_destination_type"),
+ SettingEntry("gui.departure_show_both"),
+ SettingEntry("gui.departure_only_passengers"),
+ SettingEntry("gui.departure_smart_terminus"),
+ SettingEntry("gui.departure_conditionals"),
+ SettingEntry("gui.departure_show_all_stops"),
+ SettingEntry("gui.departure_merge_identical"),
+};
+/** Departureboards sub-page */
+static SettingsPage _settings_ui_departureboards_page = {_settings_ui_departureboards, lengthof(_settings_ui_departureboards)};
+
static SettingEntry _settings_ui[] = {
SettingEntry(&_settings_ui_display_page, STR_CONFIG_SETTING_DISPLAY_OPTIONS),
SettingEntry(&_settings_ui_interaction_page, STR_CONFIG_SETTING_INTERACTION),
SettingEntry(&_settings_ui_sound_page, STR_CONFIG_SETTING_SOUND),
SettingEntry(&_settings_ui_news_page, STR_CONFIG_SETTING_NEWS),
+ SettingEntry(&_settings_ui_departureboards_page, STR_CONFIG_SETTING_DEPARTUREBOARDS),
SettingEntry("gui.show_finances"),
SettingEntry("gui.errmsg_duration"),
SettingEntry("gui.hover_delay"),
@@ -1506,6 +1529,11 @@ static SettingEntry _settings_ui[] = {
SettingEntry("gui.pause_on_newgame"),
SettingEntry("gui.advanced_vehicle_list"),
SettingEntry("gui.timetable_in_ticks"),
+ SettingEntry("gui.time_in_minutes"),
+ SettingEntry("gui.timetable_start_text_entry"),
+ SettingEntry("gui.ticks_per_minute"),
+ SettingEntry("gui.date_with_time"),
+ SettingEntry("gui.clock_offset"),
SettingEntry("gui.timetable_arrival_departure"),
SettingEntry("gui.quick_goto"),
SettingEntry("gui.default_rail_type"),
@@ -1518,6 +1546,7 @@ static SettingsPage _settings_ui_page = {_settings_ui, lengthof(_settings_ui)};
static SettingEntry _settings_construction_signals[] = {
SettingEntry("construction.train_signal_side"),
SettingEntry("gui.enable_signal_gui"),
+ SettingEntry("gui.simulated_wormhole_signals"),
SettingEntry("gui.drag_signals_fixed_distance"),
SettingEntry("gui.semaphore_build_before"),
SettingEntry("gui.default_signal_type"),
@@ -1526,8 +1555,21 @@ static SettingEntry _settings_construction_signals[] = {
/** Signals subpage */
static SettingsPage _settings_construction_signals_page = {_settings_construction_signals, lengthof(_settings_construction_signals)};
+static SettingEntry _settings_construction_trafficlights[] = {
+ SettingEntry("construction.traffic_lights"),
+ SettingEntry("construction.towns_build_traffic_lights"),
+ SettingEntry("construction.allow_building_tls_in_towns"),
+ SettingEntry("construction.traffic_lights_green_phase"),
+ SettingEntry("construction.max_tlc_size"),
+ SettingEntry("construction.max_tlc_distance"),
+};
+
+/** Traffic lights subpage */
+static SettingsPage _settings_construction_trafficlights_page = {_settings_construction_trafficlights, lengthof(_settings_construction_trafficlights)};
+
static SettingEntry _settings_construction[] = {
SettingEntry(&_settings_construction_signals_page, STR_CONFIG_SETTING_CONSTRUCTION_SIGNALS),
+ SettingEntry(&_settings_construction_trafficlights_page, STR_CONFIG_SETTING_CONSTRUCTION_TRAFFIC_LIGHTS),
SettingEntry("construction.build_on_slopes"),
SettingEntry("construction.autoslope"),
SettingEntry("construction.extra_dynamite"),
@@ -1576,6 +1618,7 @@ static SettingEntry _settings_economy_towns[] = {
SettingEntry("economy.town_growth_rate"),
SettingEntry("economy.larger_towns"),
SettingEntry("economy.initial_city_size"),
+ SettingEntry("economy.random_road_construction"),
};
/** Towns sub-page */
static SettingsPage _settings_economy_towns_page = {_settings_economy_towns, lengthof(_settings_economy_towns)};
@@ -1601,6 +1644,9 @@ static SettingEntry _settings_economy[] = {
SettingEntry("economy.smooth_economy"),
SettingEntry("economy.feeder_payment_share"),
SettingEntry("economy.infrastructure_maintenance"),
+ SettingEntry("economy.cargodest.mode_pax_mail"),
+ SettingEntry("economy.cargodest.mode_town_cargo"),
+ SettingEntry("economy.cargodest.mode_others"),
SettingEntry("difficulty.vehicle_costs"),
SettingEntry("difficulty.construction_cost"),
SettingEntry("difficulty.disasters"),
@@ -1690,6 +1736,7 @@ static SettingEntry _settings_vehicles[] = {
SettingEntry("vehicle.max_ships"),
SettingEntry("vehicle.plane_speed"),
SettingEntry("vehicle.plane_crashes"),
+ SettingEntry("order.automatic_timetable_separation"),
SettingEntry("vehicle.dynamic_engines"),
SettingEntry("vehicle.roadveh_acceleration_model"),
SettingEntry("vehicle.roadveh_slope_steepness"),
diff --git a/src/settings_type.h b/src/settings_type.h
index e6ac3e015..d567152e8 100644
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -110,13 +110,35 @@ struct GUISettings {
uint8 scrollwheel_scrolling; ///< scrolling using the scroll wheel?
uint8 scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS?
bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables
+ uint8 max_departures; ///< maximum number of departures to show per station
+ uint16 max_departure_time; ///< maximum time in advance to show departures
+ uint16 departure_calc_frequency; ///< how often to calculate departures (in ticks)
+ bool departure_show_vehicle; ///< whether to show vehicle names with departures
+ bool departure_show_group; ///< whether to show group names with departures
+ bool departure_show_company; ///< whether to show company names with departures
+ bool departure_show_vehicle_type; ///< whether to show vehicle type icons with departures
+ bool departure_show_vehicle_color; ///< whether to show vehicle type icons in silver instead of orange
+ bool departure_larger_font; ///< whether to show the calling at list in a larger font
+ bool departure_destination_type; ///< whether to show destination types for ports and airports
+ bool departure_show_both; ///< whether to show departure and arrival times on the same line
+ bool departure_only_passengers; ///< whether to only show passenger services
+ bool departure_smart_terminus; ///< whether to only show passenger services
+ uint8 departure_conditionals; ///< how to handle conditional orders
+ bool departure_show_all_stops; ///< whether to show stops regardless of loading/unloading done at them
+ bool departure_merge_identical; ///< whether to merge identical departures
bool left_mouse_btn_scrolling; ///< left mouse button scroll
bool pause_on_newgame; ///< whether to start new games paused or not
bool enable_signal_gui; ///< show the signal GUI when the signal button is pressed
Year coloured_news_year; ///< when does newspaper become coloured?
bool timetable_in_ticks; ///< whether to show the timetable in ticks rather than days
+ bool time_in_minutes; ///< whether to use the hh:mm conversion when printing dates
+ bool timetable_start_text_entry; ///< whether to enter timetable start times as text (hhmm format)
+ uint8 ticks_per_minute; ///< how many ticks per minute
+ uint8 date_with_time; ///< whether to show the month and year with the time
+ uint16 clock_offset; ///< clock offset in minutes
bool quick_goto; ///< Allow quick access to 'goto button' in vehicle orders window
bool auto_euro; ///< automatically switch to euro in 2002
+ byte simulated_wormhole_signals; ///< simulate signals in tunnel
byte drag_signals_density; ///< many signals density
bool drag_signals_fixed_distance; ///< keep fixed distance between signals when dragging
Year semaphore_build_before; ///< build semaphore signals automatically before this year
@@ -135,6 +157,8 @@ struct GUISettings {
uint8 graph_line_thickness; ///< the thickness of the lines in the various graph guis
uint8 osk_activation; ///< Mouse gesture to trigger the OSK.
+ uint32 layer_view_type; ///< зарезервировано (тип отображения)
+
uint16 console_backlog_timeout; ///< the minimum amount of time items should be in the console backlog before they will be removed in ~3 seconds granularity.
uint16 console_backlog_length; ///< the minimum amount of items in the console backlog before items will be removed.
#ifdef ENABLE_NETWORK
@@ -151,7 +175,6 @@ struct GUISettings {
uint8 settings_restriction_mode; ///< selected restriction mode in adv. settings GUI. @see RestrictionMode
bool newgrf_show_old_versions; ///< whether to show old versions in the NewGRF list
uint8 newgrf_default_palette; ///< default palette to use for NewGRFs without action 14 palette information
-
/**
* Returns true when the user has sufficient privileges to edit newgrfs on a running game
* @return whether the user has sufficient privileges to edit newgrfs in an existing game
@@ -265,6 +288,7 @@ struct GameCreationSettings {
Year starting_year; ///< starting date
uint8 map_x; ///< X size of map
uint8 map_y; ///< Y size of map
+ uint8 layers; ///< map layer count
byte land_generator; ///< the landscape generator
byte oil_refinery_limit; ///< distance oil refineries allowed from map edge
byte snow_line_height; ///< a number 0-15 that configured snow line height
@@ -305,6 +329,13 @@ struct ConstructionSettings {
uint16 clear_frame_burst; ///< how many tiles may, over a short period, be cleared?
uint32 tree_per_64k_frames; ///< how many trees may, over a long period, be planted per 65536 frames?
uint16 tree_frame_burst; ///< how many trees may, over a short period, be planted?
+
+ bool traffic_lights; ///< Whether traffic lights are enabled.
+ bool towns_build_traffic_lights; ///< Whether towns build traffic lights during road construction.
+ bool allow_building_tls_in_towns; ///< Whether the players are allowed to build traffic lights on town owned roads.
+ uint8 traffic_lights_green_phase; ///< How long traffic lights' green phase lasts.
+ uint8 max_tlc_size; ///< Maximum size for traffic light consists.
+ uint8 max_tlc_distance; ///< Maximum distance between traffic lights for synchronising them.
};
/** Settings related to the AI. */
@@ -352,6 +383,7 @@ struct NPFSettings {
uint32 npf_road_curve_penalty; ///< the penalty for curves
uint32 npf_crossing_penalty; ///< the penalty for level crossings
uint32 npf_road_drive_through_penalty; ///< the penalty for going through a drive-through road stop
+ uint32 npf_road_trafficlight_penalty; ///< Penalty for junctions with traffic lights.
uint32 npf_road_dt_occupied_penalty; ///< the penalty multiplied by the fill percentage of a drive-through road stop
uint32 npf_road_bay_occupied_penalty; ///< the penalty multiplied by the fill percentage of a road bay
};
@@ -368,6 +400,7 @@ struct YAPFSettings {
uint32 road_curve_penalty; ///< penalty for curves
uint32 road_crossing_penalty; ///< penalty for level crossing
uint32 road_stop_penalty; ///< penalty for going through a drive-through road stop
+ uint32 road_trafficlight_penalty; ///< Penalty for junctions with traffic lights.
uint32 road_stop_occupied_penalty; ///< penalty multiplied by the fill percentage of a drive-through road stop
uint32 road_stop_bay_occupied_penalty; ///< penalty multiplied by the fill percentage of a road bay
bool rail_firstred_twoway_eol; ///< treat first red two-way signal as dead end
@@ -394,6 +427,14 @@ struct YAPFSettings {
uint32 rail_longer_platform_per_tile_penalty; ///< penalty for longer station platform than train (per tile)
uint32 rail_shorter_platform_penalty; ///< penalty for shorter station platform than train
uint32 rail_shorter_platform_per_tile_penalty; ///< penalty for shorter station platform than train (per tile)
+
+ uint32 route_transfer_cost; ///< penalty for transferring to a different vehicle
+ uint32 route_max_transfers; ///< maximum number of allowed transfers
+ uint16 route_distance_factor; ///< factor for the link length
+ uint16 route_travel_time_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the link travel time
+ uint16 route_station_last_veh_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the time since the last vehicle arrived at a station
+ uint16 route_station_waiting_factor; ///< factor * CYapfCostRouteLinkT::PENALTY_DIVISOR (=16) for the waiting cargo at a station
+ byte route_mode_cost_factor[4]; ///< vehicle type dependent factor for the link length
};
/** Settings related to all pathfinders. */
@@ -425,6 +466,7 @@ struct OrderSettings {
bool gradual_loading; ///< load vehicles gradually
bool selectgoods; ///< only send the goods to station if a train has been there
bool no_servicing_if_no_breakdowns; ///< don't send vehicles to depot when breakdowns are disabled
+ bool automatic_timetable_separation; ///< Enable automatic separation of vehicles in the timetable.
bool serviceathelipad; ///< service helicopters at helipads automatically (no need to send to depot)
};
@@ -451,6 +493,33 @@ struct VehicleSettings {
uint8 plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal
};
+/** Settings related to cargo destinations. */
+struct CargodestSettings {
+ uint8 mode_pax_mail; ///< routing mode for cargoes with TE_PASSENGERS or TE_MAIL
+ uint8 mode_town_cargo; ///< routing mode for cargoes with other town effects
+ uint8 mode_others; ///< routing mode for all other cargoes
+ uint8 base_town_links[2]; ///< minimum number of town demand links for (0=#BASE_TOWN_LINKS) all cargoes except (1=#BASE_TOWN_LINKS_SYMM) symmetric cargoes
+ uint8 base_ind_links[3]; ///< minimum number of industry demand links for (0=#BASE_IND_LINKS) all cargoes except (1=#BASE_IND_LINKS_TOWN) town cargoes and (2=#BASE_IND_LINKS_SYMM) symmetric cargoes
+ uint8 city_town_links; ///< additional number of links for cities
+ uint8 town_chances_town[4]; ///< chances a link from a town to a town has a specific destination class (@see FindTownDestination)
+ uint8 town_chances_city[4]; ///< chances a link from a city to a town has a specific destination class (@see FindTownDestination)
+ uint8 ind_chances[3]; ///< chances a link to an industry has a specific destination class (@see FindIndustryDestination)
+ uint8 random_dest_chance; ///< percentage for traffic with random destination
+ uint32 big_town_pop[2]; ///< (0=#BIG_TOWN_POP_MAIL) mail, (1=#BIG_TOWN_POP_PAX) passenger amount to count as a big town
+ uint16 pop_scale_town[4]; ///< population/cargo amount scale divisor for (0=#SCALE_TOWN) all cargoes (1=#SCALE_TOWN_BIG) for big towns except (2=#SCALE_TOWN_PAX) passengers (3=#SCALE_TOWN_BIG_PAX) for big towns
+ uint16 cargo_scale_ind[2]; ///< cargo amount scale divisor for (0=#CARGO_SCALE_IND) all cargoes except (1=#CARGO_SCALE_IND_TOWN) town cargoes
+ uint16 min_weight_town[2]; ///< minimum link weight for (0=MIN_WEIGHT_TOWN) all cargoes except (1=MIN_WEIGHT_TOWN_PAX) passengers
+ uint16 min_weight_ind; ///< minimum link weight for industry links
+ uint16 weight_scale_town[4]; ///< weight scale divisor for (0=#SCALE_TOWN) all cargoes (1=#SCALE_TOWN_BIG) for big towns except (2=#SCALE_TOWN_PAX) passengers (3=#SCALE_TOWN_BIG_PAX) for big towns
+ uint16 weight_scale_ind[2]; ///< weight scale divisor for (0=#WEIGHT_SCALE_IND_PROD) produced cargo (1=#WEIGHT_SCALE_IND_PILE) stockpiled cargo
+ uint32 town_nearby_dist; ///< squared distance (on a 256x256 map) inside which a town is considered nearby
+ uint32 ind_nearby_dist; ///< squared distance (on a 256x256 map) inside which an industry is considered nearby
+ uint16 max_route_age; ///< maximum days since the last vehicle traveled a link until link expiration
+ uint16 route_recalc_delay; ///< delay in ticks between recalculating the next hop of cargo packets
+ uint16 route_recalc_chunk; ///< maximum amount of cargo packets to recalculate in one step
+ uint16 max_route_penalty[2]; ///< maximum penalty factor based on distance, (1) base value, (2) random additional span
+};
+
/** Settings related to the economy. */
struct EconomySettings {
bool inflation; ///< disable inflation
@@ -473,8 +542,10 @@ struct EconomySettings {
TownFoundingByte found_town; ///< town founding, @see TownFounding
bool station_noise_level; ///< build new airports when the town noise level is still within accepted limits
uint16 town_noise_population[3]; ///< population to base decision on noise evaluation (@see town_council_tolerance)
+ uint8 random_road_construction; ///< Chance for towns to start random road construction
bool allow_town_level_crossings; ///< towns are allowed to build level crossings
bool infrastructure_maintenance; ///< enable monthly maintenance fee for owner infrastructure
+ CargodestSettings cargodest; ///< settings related to cargo destinations
};
/** Settings related to stations. */
diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp
index 55c695603..63b693d72 100644
--- a/src/ship_cmd.cpp
+++ b/src/ship_cmd.cpp
@@ -31,6 +31,7 @@
#include "pathfinder/opf/opf_ship.h"
#include "engine_base.h"
#include "company_base.h"
+#include "cargotype.h"
#include "tunnelbridge_map.h"
#include "zoom_func.h"
@@ -181,6 +182,7 @@ static void CheckIfShipNeedsService(Vehicle *v)
void Ship::UpdateCache()
{
const ShipVehicleInfo *svi = ShipVehInfo(this->engine_type);
+ this->vcache.cached_cargo_mask = (this->cargo_type != INVALID_CARGO && this->cargo_cap > 0) ? 1 << this->cargo_type : 0;
/* Get speed fraction for the current water type. Aqueducts are always canals. */
bool is_ocean = GetEffectiveWaterClass(this->tile) == WATER_CLASS_SEA;
@@ -496,6 +498,24 @@ static const byte _ship_subcoord[4][6][3] = {
}
};
+/* Used to find DiagDirection from tile to next tile if track is followed */
+static const DiagDirection _diagdir_to_next_tile[6][4] = {
+ { DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_NW },
+ { DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_NW },
+ { DIAGDIR_SE, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_SW },
+ { DIAGDIR_SE, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_SW },
+ { DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_SE, DIAGDIR_NE },
+ { DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_SE, DIAGDIR_NE }
+};
+
+/** Helper function for collision avoidance. */
+static Vehicle *FindShipOnTile(Vehicle *v, void *data)
+{
+ if (v->type != VEH_SHIP || v->vehstatus & VS_STOPPED) return NULL;
+
+ return v;
+}
+
static void ShipController(Ship *v)
{
uint32 r;
@@ -558,14 +578,13 @@ static void ShipController(Ship *v)
return;
}
} else if (v->current_order.IsType(OT_GOTO_STATION)) {
- v->last_station_visited = v->current_order.GetDestination();
-
/* Process station in the orderlist. */
Station *st = Station::Get(v->current_order.GetDestination());
if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations
ShipArrivesAt(v, st);
- v->BeginLoading();
+ v->BeginLoading(st->index);
} else { // leave stations without docks right aways
+ v->last_station_visited = v->current_order.GetDestination();
v->current_order.MakeLeaveStation();
v->IncrementRealOrderIndex();
}
@@ -588,6 +607,25 @@ static void ShipController(Ship *v)
track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks);
if (track == INVALID_TRACK) goto reverse_direction;
+ /* Try to avoid collision and keep distance between each other. */
+ if (_settings_game.pf.forbid_90_deg && DistanceManhattan(v->dest_tile, gp.new_tile) > 3) {
+ if (HasVehicleOnPos(gp.new_tile, NULL, &FindShipOnTile) ||
+ HasVehicleOnPos(TileAddByDiagDir(gp.new_tile, _diagdir_to_next_tile[track][diagdir]), NULL, &FindShipOnTile)) {
+
+ v->cur_speed /= 4; // Go quarter speed.
+
+ Track old = track;
+ switch (tracks) {
+ default: break;
+ case TRACK_BIT_3WAY_NE: track == TRACK_RIGHT ? track = TRACK_X : track = TRACK_UPPER; break;
+ case TRACK_BIT_3WAY_SE: track == TRACK_LOWER ? track = TRACK_Y : track = TRACK_RIGHT; break;
+ case TRACK_BIT_3WAY_SW: track == TRACK_LEFT ? track = TRACK_X : track = TRACK_LOWER; break;
+ case TRACK_BIT_3WAY_NW: track == TRACK_UPPER ? track = TRACK_Y : track = TRACK_LEFT; break;
+ }
+ if (!IsWaterTile(gp.new_tile) || !IsWaterTile(TileAddByDiagDir(gp.new_tile, _diagdir_to_next_tile[track][diagdir]))) track = old; // Don't bump in coast, don't get stuck.
+ }
+ }
+
b = _ship_subcoord[diagdir][track];
gp.x = (gp.x & ~0xF) | b[0];
diff --git a/src/signal.cpp b/src/signal.cpp
index 2d16e9a73..dd6911a27 100644
--- a/src/signal.cpp
+++ b/src/signal.cpp
@@ -195,6 +195,14 @@ static Vehicle *TrainOnTileEnum(Vehicle *v, void *)
return v;
}
+/** Check whether there is a train only on ramp. */
+static Vehicle *TrainInWormholeTileEnum(Vehicle *v, void *data)
+{
+ /* Only look for front engine or last wagon. */
+ if (v->type != VEH_TRAIN || (v->Previous() != NULL && v->Next() != NULL)) return NULL;
+ if (*(TileIndex *)data != TileVirtXY(v->x_pos, v->y_pos)) return NULL;
+ return v;
+}
/**
* Perform some operations before adding data into Todo set
@@ -373,17 +381,39 @@ static SigFlags ExploreSegment(Owner owner)
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) continue;
DiagDirection dir = GetTunnelBridgeDirection(tile);
- if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
- enterdir = dir;
- exitdir = ReverseDiagDir(dir);
- tile += TileOffsByDiagDir(exitdir); // just skip to next tile
- } else { // NOT incoming from the wormhole!
- if (ReverseDiagDir(enterdir) != dir) continue;
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
- tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
- enterdir = INVALID_DIAGDIR;
- exitdir = INVALID_DIAGDIR;
+ if (HasWormholeSignals(tile)) {
+ if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
+ if (!(flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) { // tunnel entrence is ignored
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ }
+ enterdir = dir;
+ exitdir = ReverseDiagDir(dir);
+ tile += TileOffsByDiagDir(exitdir); // just skip to next tile
+ } else { // NOT incoming from the wormhole!
+ if (ReverseDiagDir(enterdir) != dir) continue;
+ if (!(flags & SF_TRAIN)) {
+ if (HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ if (!(flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) {
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ }
+ }
+ continue;
+ }
+ } else {
+ if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
+ if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ enterdir = dir;
+ exitdir = ReverseDiagDir(dir);
+ tile += TileOffsByDiagDir(exitdir); // just skip to next tile
+ } else { // NOT incoming from the wormhole!
+ if (ReverseDiagDir(enterdir) != dir) continue;
+ if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
+ enterdir = INVALID_DIAGDIR;
+ exitdir = INVALID_DIAGDIR;
+ }
+
}
}
break;
@@ -491,7 +521,9 @@ static SigSegState UpdateSignalsInBuffer(Owner owner)
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile)));
_tbdset.Add(tile, INVALID_DIAGDIR); // we can safely start from wormhole centre
- _tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR);
+ if (!HasWormholeSignals(tile)) { // Don't worry with other side of tunnel.
+ _tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR);
+ }
break;
case MP_RAILWAY:
diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp
index 4a573f3a9..fa1c6f6c7 100644
--- a/src/smallmap_gui.cpp
+++ b/src/smallmap_gui.cpp
@@ -26,6 +26,10 @@
#include "sound_func.h"
#include "window_func.h"
#include "company_base.h"
+#include "station_base.h"
+#include "company_func.h"
+#include "cargotype.h"
+#include "core/smallmap_type.hpp"
#include "widgets/smallmap_widget.h"
@@ -33,6 +37,7 @@
static int _smallmap_industry_count; ///< Number of used industries
static int _smallmap_company_count; ///< Number of entries in the owner legend.
+static int _smallmap_cargo_count; ///< Number of entries in the cargo legend.
static const int NUM_NO_COMPANY_ENTRIES = 4; ///< Number of entries in the owner legend that are not companies.
@@ -44,25 +49,25 @@ static const uint8 PC_TREES = 0x57; ///< Green palette colour for tree
static const uint8 PC_WATER = 0xCA; ///< Dark blue palette colour for water.
/** Macro for ordinary entry of LegendAndColour */
-#define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, false}
+#define MK(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, false}
/** Macro for a height legend entry with configurable colour. */
-#define MC(height) {0, STR_TINY_BLACK_HEIGHT, INVALID_INDUSTRYTYPE, height, INVALID_COMPANY, true, false, false}
+#define MC(height) {0, STR_TINY_BLACK_HEIGHT, INVALID_INDUSTRYTYPE, height, INVALID_COMPANY, INVALID_CARGO, true, false, false}
/** Macro for non-company owned property entry of LegendAndColour */
-#define MO(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, false}
+#define MO(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, false}
/** Macro used for forcing a rebuild of the owner legend the first time it is used. */
-#define MOEND() {0, 0, INVALID_INDUSTRYTYPE, 0, OWNER_NONE, true, true, false}
+#define MOEND() {0, 0, INVALID_INDUSTRYTYPE, 0, OWNER_NONE, INVALID_CARGO, true, true, false}
/** Macro for end of list marker in arrays of LegendAndColour */
-#define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, true, false}
+#define MKEND() {0, STR_NULL, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, true, false}
/**
* Macro for break marker in arrays of LegendAndColour.
* It will have valid data, though
*/
-#define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, true, false, true}
+#define MS(a, b) {a, b, INVALID_INDUSTRYTYPE, 0, INVALID_COMPANY, INVALID_CARGO, true, false, true}
/** Structure for holding relevant data for legends in small map */
struct LegendAndColour {
@@ -71,6 +76,7 @@ struct LegendAndColour {
IndustryType type; ///< Type of industry. Only valid for industry entries.
uint8 height; ///< Height in tiles. Only valid for height legend entries.
CompanyID company; ///< Company to display. Only valid for company entries of the owner legend.
+ CargoID cid; ///< Cargo type to display. Only valid for entries of the cargo legend.
bool show_on_map; ///< For filtering industries, if \c true, industry is shown on the map in colour.
bool end; ///< This is the end of the list.
bool col_break; ///< Perform a column break and go further at the next column.
@@ -156,6 +162,10 @@ static LegendAndColour _legend_land_owners[NUM_NO_COMPANY_ENTRIES + MAX_COMPANIE
static LegendAndColour _legend_from_industries[NUM_INDUSTRYTYPES + 1];
/** For connecting industry type to position in industries list(small map legend) */
static uint _industry_to_list_pos[NUM_INDUSTRYTYPES];
+/** Legend text for the cargo types in the route link legend. */
+static LegendAndColour _legend_from_cargoes[NUM_CARGO + 1];
+/** For connecting cargo type to position in route link legend. */
+static uint _cargotype_to_list_pos[NUM_CARGO];
/** Show heightmap in industry and owner mode of smallmap window. */
static bool _smallmap_show_heightmap = false;
/** Highlight a specific industry type */
@@ -196,10 +206,38 @@ void BuildIndustriesLegend()
_smallmap_industry_count = j;
}
+/** Fills the array for the route link legend. */
+void BuildCargoTypesLegend()
+{
+ uint j = 0;
+
+ /* Add all standard cargo types. */
+ const CargoSpec *cs;
+ FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) {
+ _legend_from_cargoes[j].legend = cs->name;
+ _legend_from_cargoes[j].colour = cs->legend_colour;
+ _legend_from_cargoes[j].cid = cs->Index();
+ _legend_from_cargoes[j].show_on_map = true;
+ _legend_from_cargoes[j].col_break = false;
+ _legend_from_cargoes[j].end = false;
+
+ /* Store widget number for this cargo type. */
+ _cargotype_to_list_pos[cs->Index()] = j;
+ j++;
+ }
+
+ /* Terminate list. */
+ _legend_from_cargoes[j].end = true;
+
+ /* Store number of enabled cargoes. */
+ _smallmap_cargo_count = j;
+}
+
static const LegendAndColour * const _legend_table[] = {
_legend_land_contours,
_legend_vehicles,
_legend_from_industries,
+ _legend_from_cargoes,
_legend_routes,
_legend_vegetation,
_legend_land_owners,
@@ -242,6 +280,21 @@ static const uint32 _green_map_heights[] = {
};
assert_compile(lengthof(_green_map_heights) == MAX_TILE_HEIGHT + 1);
+/**
+ * Colour Coding for Stuck Counter
+ */
+static const uint32 _stuck_counter_colours[] = {
+ MKCOLOUR(0xD0D0D0D0),
+ MKCOLOUR(0xCECECECE),
+ MKCOLOUR(0xBFBFBFBF),
+ MKCOLOUR(0xBDBDBDBD),
+ MKCOLOUR(0xBABABABA),
+ MKCOLOUR(0xB8B8B8B8),
+ MKCOLOUR(0xB6B6B6B6),
+ MKCOLOUR(0xB4B4B4B4),
+};
+assert_compile(lengthof(_stuck_counter_colours) == 8);
+
/** Height map colours for the dark green colour scheme, ordered by height. */
static const uint32 _dark_green_map_heights[] = {
MKCOLOUR_XXXX(0x60),
@@ -464,9 +517,10 @@ static inline uint32 GetSmallMapIndustriesPixels(TileIndex tile, TileType t)
*
* @param tile The tile of which we would like to get the colour.
* @param t Effective tile type of the tile (see #GetEffectiveTileType).
+ * @param show_height Whether to show the height of plain tiles.
* @return The colour of tile in the small map in mode "Routes"
*/
-static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t)
+static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t, bool show_height = false)
{
if (t == MP_STATION) {
switch (GetStationType(tile)) {
@@ -478,18 +532,14 @@ static inline uint32 GetSmallMapRoutesPixels(TileIndex tile, TileType t)
default: return MKCOLOUR_FFFF;
}
} else if (t == MP_RAILWAY) {
- AndOr andor = {
- MKCOLOUR_0XX0(GetRailTypeInfo(GetRailType(tile))->map_colour),
- _smallmap_contours_andor[t].mand
- };
-
- const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour];
- return ApplyMask(cs->default_colour, &andor);
+ byte c = GetStuckCounter(tile);
+ if (c==0) return 0;
+ return _stuck_counter_colours[c/32];
}
/* Ground colour */
const SmallMapColourScheme *cs = &_heightmap_schemes[_settings_client.gui.smallmap_land_colour];
- return ApplyMask(cs->default_colour, &_smallmap_contours_andor[t]);
+ return ApplyMask(show_height ? cs->height_colours[TileHeight(tile)] : cs->default_colour, &_smallmap_contours_andor[t]);
}
@@ -576,6 +626,7 @@ class SmallMapWindow : public Window {
SMT_CONTOUR,
SMT_VEHICLES,
SMT_INDUSTRY,
+ SMT_ROUTE_LINKS,
SMT_ROUTES,
SMT_VEGETATION,
SMT_OWNER,
@@ -774,6 +825,9 @@ class SmallMapWindow : public Window {
case SMT_INDUSTRY:
return GetSmallMapIndustriesPixels(tile, et);
+ case SMT_ROUTE_LINKS:
+ return GetSmallMapRoutesPixels(tile, et, _smallmap_show_heightmap);
+
case SMT_ROUTES:
return GetSmallMapRoutesPixels(tile, et);
@@ -901,6 +955,92 @@ class SmallMapWindow : public Window {
}
/**
+ * Adds the route links to the smallmap.
+ */
+ void DrawRouteLinks() const
+ {
+ /* Iterate all shown cargo types. */
+ for (int i = 0; i < _smallmap_cargo_count; i++) {
+ if (_legend_from_cargoes[i].show_on_map) {
+ CargoID cid = _legend_from_cargoes[i].cid;
+
+ /* Iterate all stations. */
+ const Station *st;
+ FOR_ALL_STATIONS(st) {
+ Point src_pt = this->RemapTile(TileX(st->xy), TileY(st->xy));
+ src_pt.x -= this->subscroll;
+
+ /* Collect waiting cargo per destination station. */
+ std::map<StationID, uint> links;
+ for (RouteLinkList::const_iterator l = st->goods[cid].routes.begin(); l != st->goods[cid].routes.end(); ++l) {
+ if (IsInteractiveCompany((*l)->GetOwner())) links[(*l)->GetDestination()] += st->goods[cid].cargo.CountForNextHop((*l)->GetOriginOrderId());
+ }
+
+ /* Add cargo count on back-links. */
+ for (std::map<StationID, uint>::iterator itr = links.begin(); itr != links.end(); ++itr) {
+ /* Get destination location. */
+ const Station *dest = Station::Get(itr->first);
+ Point dest_pt = this->RemapTile(TileX(dest->xy), TileY(dest->xy));
+ dest_pt.x -= this->subscroll;
+
+ /* Get total count including back-links. */
+ uint count = itr->second;
+ for (RouteLinkList::const_iterator j = dest->goods[cid].routes.begin(); j != dest->goods[cid].routes.end(); ++j) {
+ if ((*j)->GetDestination() == st->index && IsInteractiveCompany((*j)->GetOwner())) count += dest->goods[cid].cargo.CountForNextHop((*j)->GetOriginOrderId());
+ }
+
+ /* Calculate line size from waiting cargo. */
+ int size = 1;
+ if (count >= 400) size++;
+ if (count >= 800) size++;
+ if (count >= 1600) size++;
+ if (count >= 3200) size++;
+
+ /* Draw black border and cargo coloured line. */
+ GfxDrawLine(src_pt.x, src_pt.y, dest_pt.x, dest_pt.y, PC_BLACK, size + 2);
+ GfxDrawLine(src_pt.x, src_pt.y, dest_pt.x, dest_pt.y, _legend_from_cargoes[i].colour, size);
+ }
+ }
+ }
+ }
+
+ /* Draw station rect. */
+ const Station *st;
+ FOR_ALL_STATIONS(st) {
+ /* Count total cargo and check for links for all shown cargo types. */
+ uint total = 0;
+ bool show = false;
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (_legend_from_cargoes[_cargotype_to_list_pos[cid]].show_on_map) {
+ total += st->goods[cid].cargo.Count();
+ show |= !st->goods[cid].routes.empty();
+ }
+ }
+
+ if (!show) continue;
+
+ /* Get rect size from total cargo count. */
+ int d = 1;
+ if (total >= 200) d++;
+ if (total >= 400) d++;
+ if (total >= 800) d++;
+ if (total >= 1600) d++;
+ if (total >= 3200) d++;
+ if (total >= 6400) d++;
+
+ /* Get top-left corner of the rect. */
+ Point dest_pt = this->RemapTile(TileX(st->xy), TileY(st->xy));
+ dest_pt.x -= this->subscroll + d/2;
+ dest_pt.y -= d/2;
+
+ /* Draw black border and company-colour inset. */
+ byte colour = _colour_gradient[Company::IsValidID(st->owner) ? Company::Get(st->owner)->colour : (byte)COLOUR_GREY][6];
+ GfxFillRect(dest_pt.x - 1, dest_pt.y - 1, dest_pt.x + d + 1, dest_pt.y + d + 1, PC_BLACK); // Draw black frame
+ GfxFillRect(dest_pt.x, dest_pt.y, dest_pt.x + d, dest_pt.y + d, colour); // Draw colour insert
+ }
+ }
+
+ /**
* Draws vertical part of map indicator
* @param x X coord of left/right border of main viewport
* @param y Y coord of top border of main viewport
@@ -1007,6 +1147,9 @@ class SmallMapWindow : public Window {
/* Draw vehicles */
if (this->map_type == SMT_CONTOUR || this->map_type == SMT_VEHICLES) this->DrawVehicles(dpi, blitter);
+ /* Draw route links. */
+ if (this->map_type == SMT_ROUTE_LINKS) this->DrawRouteLinks();
+
/* Draw town names */
if (this->show_towns) this->DrawTowns(dpi);
@@ -1040,6 +1183,13 @@ class SmallMapWindow : public Window {
plane = 0;
break;
+ case SMT_ROUTE_LINKS:
+ legend_tooltip = STR_SMALLMAP_TOOLTIP_ROUTELINK_SELECTION;
+ enable_all_tooltip = STR_SMALLMAP_TOOLTIP_ENABLE_ALL_ROUTELINKS;
+ disable_all_tooltip = STR_SMALLMAP_TOOLTIP_DISABLE_ALL_ROUTELINKS;
+ plane = 0;
+ break;
+
default:
legend_tooltip = STR_NULL;
enable_all_tooltip = STR_NULL;
@@ -1140,6 +1290,9 @@ public:
} else {
str = tbl->legend;
}
+ } else if (i == SMT_ROUTE_LINKS) {
+ SetDParam(0, tbl->legend);
+ str = STR_SMALLMAP_CARGO;
} else {
if (tbl->col_break) {
this->min_number_of_fixed_rows = max(this->min_number_of_fixed_rows, height);
@@ -1187,7 +1340,7 @@ public:
case WID_SM_LEGEND: {
uint columns = this->GetNumberColumnsLegend(r.right - r.left + 1);
- uint number_of_rows = max((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) ? CeilDiv(max(_smallmap_company_count, _smallmap_industry_count), columns) : 0, this->min_number_of_fixed_rows);
+ uint number_of_rows = max((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) ? CeilDiv(max(_smallmap_company_count, max(_smallmap_industry_count, _smallmap_cargo_count)), columns) : 0, this->min_number_of_fixed_rows);
bool rtl = _current_text_dir == TD_RTL;
uint y_org = r.top + WD_FRAMERECT_TOP;
uint x = rtl ? r.right - this->column_width - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT;
@@ -1201,7 +1354,7 @@ public:
uint blob_right = rtl ? this->column_width - 1 : LEGEND_BLOB_WIDTH;
for (const LegendAndColour *tbl = _legend_table[this->map_type]; !tbl->end; ++tbl) {
- if (tbl->col_break || ((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) && i++ >= number_of_rows)) {
+ if (tbl->col_break || ((this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) && i++ >= number_of_rows)) {
/* Column break needed, continue at top, COLUMN_WIDTH pixels
* (one "row") to the right. */
x += rtl ? -(int)this->column_width : this->column_width;
@@ -1227,6 +1380,16 @@ public:
DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_INDUSTRY, TC_BLACK);
GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, PC_BLACK); // Outer border of the legend colour
}
+ } else if (this->map_type == SMT_ROUTE_LINKS) {
+ /* Cargo name needs formatting for tiny font. */
+ SetDParam(0, tbl->legend);
+ if (!tbl->show_on_map) {
+ /* Draw only the string and not the border of the legend colour. */
+ DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_CARGO, TC_GREY);
+ } else {
+ DrawString(x + text_left, x + text_right, y, STR_SMALLMAP_CARGO, TC_BLACK);
+ GfxFillRect(x + blob_left, y + 1, x + blob_right, y + row_height - 1, PC_BLACK); // Outer border of the legend colour
+ }
} else if (this->map_type == SMT_OWNER && tbl->company != INVALID_COMPANY) {
SetDParam(0, tbl->company);
if (!tbl->show_on_map) {
@@ -1277,7 +1440,7 @@ public:
const NWidgetBase *wi = this->GetWidget<NWidgetBase>(WID_SM_LEGEND);
uint line = (pt.y - wi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_SMALL;
uint columns = this->GetNumberColumnsLegend(wi->current_x);
- uint number_of_rows = max(CeilDiv(max(_smallmap_company_count, _smallmap_industry_count), columns), this->min_number_of_fixed_rows);
+ uint number_of_rows = max(CeilDiv(max(_smallmap_company_count, max(_smallmap_industry_count, _smallmap_cargo_count)), columns), this->min_number_of_fixed_rows);
if (line >= number_of_rows) return -1;
bool rtl = _current_text_dir == TD_RTL;
@@ -1349,6 +1512,7 @@ public:
case WID_SM_CONTOUR: // Show land contours
case WID_SM_VEHICLES: // Show vehicles
case WID_SM_INDUSTRIES: // Show industries
+ case WID_SM_ROUTE_LINKS:// Show route links
case WID_SM_ROUTES: // Show transport routes
case WID_SM_VEGETATION: // Show vegetation
case WID_SM_OWNERS: // Show land owners
@@ -1371,7 +1535,7 @@ public:
break;
case WID_SM_LEGEND: // Legend
- if (this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER) {
+ if (this->map_type == SMT_INDUSTRY || this->map_type == SMT_OWNER || this->map_type == SMT_ROUTE_LINKS) {
/* If industry type small map*/
if (this->map_type == SMT_INDUSTRY) {
/* If click on industries label, find right industry type and enable/disable it. */
@@ -1422,6 +1586,30 @@ public:
_legend_land_owners[company_pos].show_on_map = !_legend_land_owners[company_pos].show_on_map;
}
}
+ } else if (this->map_type == SMT_ROUTE_LINKS) {
+ /* If click on cargo label, find right cargo type and enable/disable it. */
+ int cargo_pos = GetPositionOnLegend(pt);
+ if (cargo_pos < _smallmap_cargo_count) {
+ if (_ctrl_pressed) {
+ /* Disable all, except the clicked one */
+ bool changes = false;
+ for (int i = 0; i != _smallmap_cargo_count; i++) {
+ bool new_state = i == cargo_pos;
+ if (_legend_from_cargoes[i].show_on_map != new_state) {
+ changes = true;
+ _legend_from_cargoes[i].show_on_map = new_state;
+ }
+ }
+ if (!changes) {
+ /* Nothing changed? Then show all (again). */
+ for (int i = 0; i != _smallmap_cargo_count; i++) {
+ _legend_from_cargoes[i].show_on_map = true;
+ }
+ }
+ } else {
+ _legend_from_cargoes[cargo_pos].show_on_map = !_legend_from_cargoes[cargo_pos].show_on_map;
+ }
+ }
}
this->SetDirty();
}
@@ -1436,6 +1624,10 @@ public:
for (int i = NUM_NO_COMPANY_ENTRIES; i != _smallmap_company_count; i++) {
_legend_land_owners[i].show_on_map = true;
}
+ } else if (this->map_type == SMT_ROUTE_LINKS) {
+ for (int i = 0; i != _smallmap_cargo_count; i++) {
+ _legend_from_cargoes[i].show_on_map = true;
+ }
}
this->SetDirty();
break;
@@ -1445,10 +1637,14 @@ public:
for (int i = 0; i != _smallmap_industry_count; i++) {
_legend_from_industries[i].show_on_map = false;
}
- } else {
+ } else if (this->map_type == SMT_OWNER) {
for (int i = NUM_NO_COMPANY_ENTRIES; i != _smallmap_company_count; i++) {
_legend_land_owners[i].show_on_map = false;
}
+ } else if (this->map_type == SMT_ROUTE_LINKS) {
+ for (int i = 0; i != _smallmap_cargo_count; i++) {
+ _legend_from_cargoes[i].show_on_map = false;
+ }
}
this->SetDirty();
break;
@@ -1686,6 +1882,8 @@ static const NWidgetPart _nested_smallmap_bar[] = {
SetDataTip(SPR_IMG_SHOW_VEHICLES, STR_SMALLMAP_TOOLTIP_SHOW_VEHICLES_ON_MAP), SetFill(1, 1),
NWidget(WWT_IMGBTN, COLOUR_BROWN, WID_SM_INDUSTRIES),
SetDataTip(SPR_IMG_INDUSTRY, STR_SMALLMAP_TOOLTIP_SHOW_INDUSTRIES_ON_MAP), SetFill(1, 1),
+ NWidget(WWT_IMGBTN, COLOUR_BROWN, WID_SM_ROUTE_LINKS),
+ SetDataTip(SPR_IMG_SHOW_ROUTES, STR_SMALLMAP_TOOLTIP_SHOW_ROUTE_LINKS_ON_MAP), SetFill(1, 1),
EndContainer(),
/* Bottom button row. */
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
diff --git a/src/smallmap_gui.h b/src/smallmap_gui.h
index 572a17534..1cb23e9f6 100644
--- a/src/smallmap_gui.h
+++ b/src/smallmap_gui.h
@@ -13,6 +13,7 @@
#define SMALLMAP_GUI_H
void BuildIndustriesLegend();
+void BuildCargoTypesLegend();
void ShowSmallMap();
void BuildLandLegend();
void BuildOwnerLegend();
diff --git a/src/station.cpp b/src/station.cpp
index 43b659476..edfbf7d49 100644
--- a/src/station.cpp
+++ b/src/station.cpp
@@ -13,6 +13,7 @@
#include "company_func.h"
#include "company_base.h"
#include "roadveh.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "date_func.h"
#include "command_func.h"
@@ -24,6 +25,7 @@
#include "roadstop_base.h"
#include "industry.h"
#include "core/random_func.hpp"
+#include "cargodest_func.h"
#include "table/strings.h"
@@ -93,6 +95,9 @@ Station::~Station()
if (v->last_station_visited == this->index) {
v->last_station_visited = INVALID_STATION;
}
+ if (v->last_station_loaded == this->index) {
+ v->last_station_loaded = INVALID_STATION;
+ }
}
/* Clear the persistent storage. */
@@ -118,6 +123,7 @@ Station::~Station()
}
CargoPacket::InvalidateAllFrom(this->index);
+ InvalidateStationRouteLinks(this);
}
@@ -307,8 +313,8 @@ static bool FindIndustryToDeliver(TileIndex ind_tile, void *user_data)
if (riv->industries_near->Contains(ind)) return false;
/* Only process tiles in the station acceptance rectangle */
- int x = TileX(ind_tile);
- int y = TileY(ind_tile);
+ int x = LayerX(ind_tile);
+ int y = LayerY(ind_tile);
if (x < riv->rect.left || x > riv->rect.right || y < riv->rect.top || y > riv->rect.bottom) return false;
/* Include only industries that can accept cargo */
@@ -338,7 +344,8 @@ void Station::RecomputeIndustriesNear()
};
/* Compute maximum extent of acceptance rectangle wrt. station sign */
- TileIndex start_tile = this->xy;
+ /* Охватываем верхнюю территорию */
+ TileIndex start_tile = TopTile(this->xy);
uint max_radius = max(
max(DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.left, riv.rect.bottom))),
max(DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.top)), DistanceManhattan(start_tile, TileXY(riv.rect.right, riv.rect.bottom)))
@@ -373,7 +380,7 @@ void StationRect::MakeEmpty()
/**
* Determines whether a given point (x, y) is within a certain distance of
* the station rectangle.
- * @note x and y are in Tile coordinates
+ * @note x and y are in Tile coordinates (in top layer)
* @param x X coordinate
* @param y Y coordinate
* @param distance The maximum distance a point may have (L1 norm)
@@ -385,6 +392,23 @@ bool StationRect::PtInExtendedRect(int x, int y, int distance) const
this->top - distance <= y && y <= this->bottom + distance;
}
+/**
+ * Determines whether a tile area intersects the station rectangle with a given offset.
+ * @param area The tile area to test.
+ * @param distance Offset the station rect is grown on all sides (L1 norm).
+ * @return True if the tile area intersects with the station rectangle.
+ */
+bool StationRect::AreaInExtendedRect(const TileArea& area, int distance) const
+{
+ int area_left = TileX(area.tile);
+ int area_right = area_left + area.w;
+ int area_top = TileY(area.tile);
+ int area_bottom = area_top + area.h;
+
+ return this->left - distance <= area_right && area_left <= this->right + distance &&
+ this->top - distance <= area_bottom && area_top <= this->bottom + distance;
+}
+
bool StationRect::IsEmpty() const
{
return this->left == 0 || this->left > this->right || this->top > this->bottom;
@@ -392,8 +416,10 @@ bool StationRect::IsEmpty() const
CommandCost StationRect::BeforeAddTile(TileIndex tile, StationRectMode mode)
{
- int x = TileX(tile);
- int y = TileY(tile);
+ /* Станция может находится на любом уровне.
+ * Но охватывает только поверхность */
+ int x = LayerX(tile);
+ int y = LayerY(tile);
if (this->IsEmpty()) {
/* we are adding the first station tile */
if (mode != ADD_TEST) {
@@ -446,18 +472,25 @@ CommandCost StationRect::BeforeAddRect(TileIndex tile, int w, int h, StationRect
*/
/* static */ bool StationRect::ScanForStationTiles(StationID st_id, int left_a, int top_a, int right_a, int bottom_a)
{
+ /* Станция может находится на любом уровне.
+ * Значит надо обойти все слои */
TileArea ta(TileXY(left_a, top_a), TileXY(right_a, bottom_a));
- TILE_AREA_LOOP(tile, ta) {
- if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true;
- }
+ FOR_ALL_LAYERS(layer) {
+ ta.tile += layer * LayerSize();
+ TILE_AREA_LOOP(tile, ta) {
+ if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st_id) return true;
+ }
+ }
return false;
}
bool StationRect::AfterRemoveTile(BaseStation *st, TileIndex tile)
{
- int x = TileX(tile);
- int y = TileY(tile);
+ /* Станция может находится на любом уровне.
+ * Но охватывает только поверхность */
+ int x = LayerX(tile);
+ int y = LayerY(tile);
/* look if removed tile was on the bounding rect edge
* and try to reduce the rect by this edge
@@ -506,8 +539,13 @@ bool StationRect::AfterRemoveTile(BaseStation *st, TileIndex tile)
bool StationRect::AfterRemoveRect(BaseStation *st, TileArea ta)
{
- assert(this->PtInExtendedRect(TileX(ta.tile), TileY(ta.tile)));
- assert(this->PtInExtendedRect(TileX(ta.tile) + ta.w - 1, TileY(ta.tile) + ta.h - 1));
+ /* Станция может находится на любом уровне.
+ * Но охватывает только поверхность */
+ int topx = LayerX(ta.tile);
+ int topy = LayerY(ta.tile);
+
+ assert(this->PtInExtendedRect(topx, topy));
+ assert(this->PtInExtendedRect(topx + ta.w - 1, topy + ta.h - 1));
bool empty = this->AfterRemoveTile(st, ta.tile);
if (ta.w != 1 || ta.h != 1) empty = empty || this->AfterRemoveTile(st, TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1));
diff --git a/src/station_base.h b/src/station_base.h
index 07d1d2294..d0f6e5b51 100644
--- a/src/station_base.h
+++ b/src/station_base.h
@@ -17,12 +17,16 @@
#include "cargopacket.h"
#include "industry_type.h"
#include "newgrf_storage.h"
+#include "cargodest_type.h"
typedef Pool<BaseStation, StationID, 32, 64000> StationPool;
extern StationPool _station_pool;
static const byte INITIAL_STATION_RATING = 175;
+/** List of RouteLinks. */
+typedef std::list<RouteLink *> RouteLinkList;
+
/**
* Stores station stats for a single cargo.
*/
@@ -74,7 +78,8 @@ struct GoodsEntry {
time_since_pickup(255),
rating(INITIAL_STATION_RATING),
last_speed(0),
- last_age(255)
+ last_age(255),
+ cargo_counter(0)
{}
byte acceptance_pickup; ///< Status of this cargo, see #GoodsEntryStatus.
@@ -106,7 +111,9 @@ struct GoodsEntry {
byte last_age;
byte amount_fract; ///< Fractional part of the amount in the cargo list
+ uint16 cargo_counter; ///< Update timer for the packets' next hop
StationCargoList cargo; ///< The cargo packets of cargo waiting in this station
+ RouteLinkList routes; ///< List of originating route links
/**
* Reports whether a vehicle has ever tried to load the cargo at this station.
diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp
index dc898d9bc..acfbc55de 100644
--- a/src/station_cmd.cpp
+++ b/src/station_cmd.cpp
@@ -13,6 +13,7 @@
#include "aircraft.h"
#include "bridge_map.h"
#include "cmd_helper.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "command_func.h"
#include "town.h"
@@ -48,6 +49,7 @@
#include "table/airporttile_ids.h"
#include "newgrf_airporttiles.h"
#include "order_backup.h"
+#include "cargodest_func.h"
#include "newgrf_house.h"
#include "company_gui.h"
#include "widgets/station_widget.h"
@@ -85,24 +87,28 @@ bool IsHangar(TileIndex t)
* @return Succeeded command (if zero or one station found) or failed command (for two or more stations found).
*/
template <class T>
-CommandCost GetStationAround(TileArea ta, StationID closest_station, T **st)
+CommandCost GetStationAround(TileArea ta, StationID closest_station, T **st, bool layers=false)
{
ta.tile -= TileDiffXY(1, 1);
ta.w += 2;
ta.h += 2;
/* check around to see if there's any stations there */
- TILE_AREA_LOOP(tile_cur, ta) {
- if (IsTileType(tile_cur, MP_STATION)) {
- StationID t = GetStationIndex(tile_cur);
- if (!T::IsValidID(t)) continue;
-
- if (closest_station == INVALID_STATION) {
- closest_station = t;
- } else if (closest_station != t) {
- return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
+ FOR_ALL_LAYERS(layer) {
+ TILE_AREA_LOOP(tile, ta) {
+ TileIndex tile_cur = (layers ? TopTile(tile) + layer*LayerSize() : tile);
+ if (IsTileType(tile_cur, MP_STATION)) {
+ StationID t = GetStationIndex(tile_cur);
+ if (!T::IsValidID(t)) continue;
+
+ if (closest_station == INVALID_STATION) {
+ closest_station = t;
+ } else if (closest_station != t) {
+ return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
+ }
}
}
+ if (!layers) break;
}
*st = (closest_station == INVALID_STATION) ? NULL : T::Get(closest_station);
return CommandCost();
@@ -656,7 +662,6 @@ static CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reus
if (flags & DC_EXEC) {
*st = new Station(area.tile);
-
(*st)->town = ClosestTownFromTile(area.tile, UINT_MAX);
(*st)->string_id = GenerateStationName(*st, area.tile, name_class);
@@ -967,11 +972,13 @@ CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta, Axis a
TileArea cur_ta = st->train_station;
/* determine new size of train station region.. */
- int x = min(TileX(cur_ta.tile), TileX(new_ta.tile));
- int y = min(TileY(cur_ta.tile), TileY(new_ta.tile));
- new_ta.w = max(TileX(cur_ta.tile) + cur_ta.w, TileX(new_ta.tile) + new_ta.w) - x;
- new_ta.h = max(TileY(cur_ta.tile) + cur_ta.h, TileY(new_ta.tile) + new_ta.h) - y;
- new_ta.tile = TileXY(x, y);
+ /* Фактически, подземная станция ("эскалатор") больше ширины карты.
+ * Поэтому проверям размер в пределах одного слоя */
+ int topx = min(LayerX(cur_ta.tile), LayerX(new_ta.tile));
+ int topy = min(LayerY(cur_ta.tile), LayerY(new_ta.tile));
+ new_ta.w = max(LayerX(cur_ta.tile) + cur_ta.w, LayerX(new_ta.tile) + new_ta.w) - topx;
+ new_ta.h = max(LayerY(cur_ta.tile) + cur_ta.h, LayerY(new_ta.tile) + new_ta.h) - topy;
+ new_ta.tile = TileXY(topx, topy);
/* make sure the final size is not too big. */
if (new_ta.w > _settings_game.station.station_spread || new_ta.h > _settings_game.station.station_spread) {
@@ -1043,7 +1050,7 @@ void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSp
* @return command cost with the error or 'okay'
*/
template <class T, StringID error_message>
-CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st)
+CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st, bool layers=false)
{
assert(*st == NULL);
bool check_surrounding = true;
@@ -1069,7 +1076,7 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station
if (check_surrounding) {
/* Make sure there are no similar stations around us. */
- CommandCost ret = GetStationAround(ta, existing_station, st);
+ CommandCost ret = GetStationAround(ta, existing_station, st, layers);
if (ret.Failed()) return ret;
}
@@ -1088,9 +1095,9 @@ CommandCost FindJoiningBaseStation(StationID existing_station, StationID station
* @param st 'return' pointer for the found station
* @return command cost with the error or 'okay'
*/
-static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st)
+static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st, bool layers=false)
{
- return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST>(existing_station, station_to_join, adjacent, ta, st);
+ return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST>(existing_station, station_to_join, adjacent, ta, st, layers);
}
/**
@@ -1216,8 +1223,10 @@ CommandCost CmdBuildRailStation(TileIndex tile_org, DoCommandFlag flags, uint32
byte numtracks_orig;
Track track;
+ TileIndex top_tile = TopTile(new_location.tile);
st->train_station = new_location;
- st->AddFacility(FACIL_TRAIN, new_location.tile);
+ st->train_station.tile = top_tile;
+ st->AddFacility(FACIL_TRAIN, tile_org);
st->rect.BeforeAddRect(tile_org, w_org, h_org, StationRect::ADD_TRY);
@@ -1497,7 +1506,7 @@ CommandCost RemoveFromRailBaseStation(TileArea ta, SmallVector<T *, 4> &affected
/* now we need to make the "spanned" area of the railway station smaller
* if we deleted something at the edges.
* we also need to adjust train_tile. */
- MakeRailStationAreaSmaller(st);
+ st->rect.AfterRemoveTile(st, st->xy);
UpdateStationSignCoord(st);
/* if we deleted the whole station, delete the train facility. */
@@ -1590,11 +1599,16 @@ CommandCost RemoveRailStation(T *st, DoCommandFlag flags)
/* determine width and height of platforms */
TileArea ta = st->train_station;
- assert(ta.w != 0 && ta.h != 0);
+ /* TileArea is top finite area */
+ assert(IsTopTile(ta.tile));
+ assert(ta.IsFinite());
CommandCost cost(EXPENSES_CONSTRUCTION);
+ /* Check all layers */
+ FOR_ALL_LAYERS(layer)
/* clear all areas of the station */
- TILE_AREA_LOOP(tile, ta) {
+ TILE_AREA_LOOP(top_tile, ta) {
+ TileIndex tile = top_tile + layer * LayerSize();
/* only remove tiles that are actually train station tiles */
if (!st->TileBelongsToRailStation(tile)) continue;
@@ -1992,11 +2006,20 @@ CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, ui
/* Check if the first tile and the last tile are valid */
if (!IsValidTile(tile) || TileAddWrap(tile, width - 1, height - 1) == INVALID_TILE) return CMD_ERROR;
- TileArea roadstop_area(tile, width, height);
+ /* Это территория удаления остановок (НЕ самих остановок) */
+ TileArea roadstop_area(TopTile(tile), width, height);
+
+ /* TileArea is top finite area */
+ assert(IsTopTile(roadstop_area.tile));
+ assert(roadstop_area.IsFinite());
int quantity = 0;
CommandCost cost(EXPENSES_CONSTRUCTION);
- TILE_AREA_LOOP(cur_tile, roadstop_area) {
+
+ /* Check all layers */
+ FOR_ALL_LAYERS(layer)
+ TILE_AREA_LOOP(top_tile, roadstop_area) {
+ TileIndex cur_tile = top_tile + layer * LayerSize();
/* Make sure the specified tile is a road stop of the correct type */
if (!IsTileType(cur_tile, MP_STATION) || !IsRoadStop(cur_tile) || (uint32)GetRoadStopType(cur_tile) != GB(p2, 0, 1)) continue;
@@ -2173,6 +2196,10 @@ CommandCost CmdBuildAirport(TileIndex tile, DoCommandFlag flags, uint32 p1, uint
if (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread) {
return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
}
+ /* can't make underground airport */
+ if (IsUnderground(tile)) {
+ return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND);
+ }
CommandCost cost = CheckFlatLand(airport_area, flags);
if (cost.Failed()) return cost;
@@ -3349,6 +3376,17 @@ void OnTick_Station()
TriggerStationAnimation(st, st->xy, SAT_250_TICKS);
if (Station::IsExpected(st)) AirportAnimationTrigger(Station::From(st), AAT_STATION_250_TICKS);
}
+
+ if (Station::IsExpected(st)) {
+ /* Age and expire route links. */
+ Station *s = Station::From(st);
+ if (s->index % DAY_TICKS == _date_fract) AgeRouteLinks(s);
+
+ /* Decrement cargo update counter. */
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (s->goods[cid].cargo_counter > 0) s->goods[cid].cargo_counter--;
+ }
+ }
}
}
@@ -3385,7 +3423,7 @@ void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint rad
}
}
-static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id)
+uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id, TileIndex dest_tile, SourceType dest_type, SourceID dest_id, OrderID next_hop, StationID next_unload, byte flags)
{
/* We can't allocate a CargoPacket? Then don't do anything
* at all; i.e. just discard the incoming cargo. */
@@ -3399,7 +3437,7 @@ static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceT
/* No new "real" cargo item yet. */
if (amount == 0) return 0;
- ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id));
+ ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id, dest_tile, dest_type, dest_id, next_hop, next_unload, flags));
if (!HasBit(ge.acceptance_pickup, GoodsEntry::GES_PICKUP)) {
InvalidateWindowData(WC_STATION_LIST, st->index);
@@ -3524,11 +3562,14 @@ const StationList *StationFinder::GetStations()
return &this->stations;
}
-uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations)
+uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations, TileIndex src_tile)
{
/* Return if nothing to do. Also the rounding below fails for 0. */
if (amount == 0) return 0;
+ /* Handle cargo that has cargo destinations enabled. */
+ if (MoveCargoWithDestinationToStation(type, &amount, source_type, source_id, all_stations, src_tile)) return amount;
+
Station *st1 = NULL; // Station with best rating
Station *st2 = NULL; // Second best station
uint best_rating1 = 0; // rating of st1
diff --git a/src/station_cmd.cpp.orig b/src/station_cmd.cpp.orig
new file mode 100644
index 000000000..dc898d9bc
--- /dev/null
+++ b/src/station_cmd.cpp.orig
@@ -0,0 +1,3848 @@
+/* $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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file station_cmd.cpp Handling of station tiles. */
+
+#include "stdafx.h"
+#include "aircraft.h"
+#include "bridge_map.h"
+#include "cmd_helper.h"
+#include "viewport_func.h"
+#include "command_func.h"
+#include "town.h"
+#include "news_func.h"
+#include "train.h"
+#include "ship.h"
+#include "roadveh.h"
+#include "industry.h"
+#include "newgrf_cargo.h"
+#include "newgrf_debug.h"
+#include "newgrf_station.h"
+#include "newgrf_canal.h" /* For the buoy */
+#include "pathfinder/yapf/yapf_cache.h"
+#include "road_internal.h" /* For drawing catenary/checking road removal */
+#include "autoslope.h"
+#include "water.h"
+#include "strings_func.h"
+#include "clear_func.h"
+#include "date_func.h"
+#include "vehicle_func.h"
+#include "string_func.h"
+#include "animated_tile_func.h"
+#include "elrail_func.h"
+#include "station_base.h"
+#include "roadstop_base.h"
+#include "newgrf_railtype.h"
+#include "waypoint_base.h"
+#include "waypoint_func.h"
+#include "pbs.h"
+#include "debug.h"
+#include "core/random_func.hpp"
+#include "company_base.h"
+#include "table/airporttile_ids.h"
+#include "newgrf_airporttiles.h"
+#include "order_backup.h"
+#include "newgrf_house.h"
+#include "company_gui.h"
+#include "widgets/station_widget.h"
+
+#include "table/strings.h"
+
+/**
+ * Check whether the given tile is a hangar.
+ * @param t the tile to of whether it is a hangar.
+ * @pre IsTileType(t, MP_STATION)
+ * @return true if and only if the tile is a hangar.
+ */
+bool IsHangar(TileIndex t)
+{
+ assert(IsTileType(t, MP_STATION));
+
+ /* If the tile isn't an airport there's no chance it's a hangar. */
+ if (!IsAirport(t)) return false;
+
+ const Station *st = Station::GetByTile(t);
+ const AirportSpec *as = st->airport.GetSpec();
+
+ for (uint i = 0; i < as->nof_depots; i++) {
+ if (st->airport.GetHangarTile(i) == t) return true;
+ }
+
+ return false;
+}
+
+/**
+ * Look for a station around the given tile area.
+ * @param ta the area to search over
+ * @param closest_station the closest station found so far
+ * @param st to 'return' the found station
+ * @return Succeeded command (if zero or one station found) or failed command (for two or more stations found).
+ */
+template <class T>
+CommandCost GetStationAround(TileArea ta, StationID closest_station, T **st)
+{
+ ta.tile -= TileDiffXY(1, 1);
+ ta.w += 2;
+ ta.h += 2;
+
+ /* check around to see if there's any stations there */
+ TILE_AREA_LOOP(tile_cur, ta) {
+ if (IsTileType(tile_cur, MP_STATION)) {
+ StationID t = GetStationIndex(tile_cur);
+ if (!T::IsValidID(t)) continue;
+
+ if (closest_station == INVALID_STATION) {
+ closest_station = t;
+ } else if (closest_station != t) {
+ return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
+ }
+ }
+ }
+ *st = (closest_station == INVALID_STATION) ? NULL : T::Get(closest_station);
+ return CommandCost();
+}
+
+/**
+ * Function to check whether the given tile matches some criterion.
+ * @param tile the tile to check
+ * @return true if it matches, false otherwise
+ */
+typedef bool (*CMSAMatcher)(TileIndex tile);
+
+/**
+ * Counts the numbers of tiles matching a specific type in the area around
+ * @param tile the center tile of the 'count area'
+ * @param cmp the comparator/matcher (@see CMSAMatcher)
+ * @return the number of matching tiles around
+ */
+static int CountMapSquareAround(TileIndex tile, CMSAMatcher cmp)
+{
+ int num = 0;
+
+ for (int dx = -3; dx <= 3; dx++) {
+ for (int dy = -3; dy <= 3; dy++) {
+ TileIndex t = TileAddWrap(tile, dx, dy);
+ if (t != INVALID_TILE && cmp(t)) num++;
+ }
+ }
+
+ return num;
+}
+
+/**
+ * Check whether the tile is a mine.
+ * @param tile the tile to investigate.
+ * @return true if and only if the tile is a mine
+ */
+static bool CMSAMine(TileIndex tile)
+{
+ /* No industry */
+ if (!IsTileType(tile, MP_INDUSTRY)) return false;
+
+ const Industry *ind = Industry::GetByTile(tile);
+
+ /* No extractive industry */
+ if ((GetIndustrySpec(ind->type)->life_type & INDUSTRYLIFE_EXTRACTIVE) == 0) return false;
+
+ for (uint i = 0; i < lengthof(ind->produced_cargo); i++) {
+ /* The industry extracts something non-liquid, i.e. no oil or plastic, so it is a mine.
+ * Also the production of passengers and mail is ignored. */
+ if (ind->produced_cargo[i] != CT_INVALID &&
+ (CargoSpec::Get(ind->produced_cargo[i])->classes & (CC_LIQUID | CC_PASSENGERS | CC_MAIL)) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Check whether the tile is water.
+ * @param tile the tile to investigate.
+ * @return true if and only if the tile is a water tile
+ */
+static bool CMSAWater(TileIndex tile)
+{
+ return IsTileType(tile, MP_WATER) && IsWater(tile);
+}
+
+/**
+ * Check whether the tile is a tree.
+ * @param tile the tile to investigate.
+ * @return true if and only if the tile is a tree tile
+ */
+static bool CMSATree(TileIndex tile)
+{
+ return IsTileType(tile, MP_TREES);
+}
+
+#define M(x) ((x) - STR_SV_STNAME)
+
+enum StationNaming {
+ STATIONNAMING_RAIL,
+ STATIONNAMING_ROAD,
+ STATIONNAMING_AIRPORT,
+ STATIONNAMING_OILRIG,
+ STATIONNAMING_DOCK,
+ STATIONNAMING_HELIPORT,
+};
+
+/** Information to handle station action 0 property 24 correctly */
+struct StationNameInformation {
+ uint32 free_names; ///< Current bitset of free names (we can remove names).
+ bool *indtypes; ///< Array of bools telling whether an industry type has been found.
+};
+
+/**
+ * Find a station action 0 property 24 station name, or reduce the
+ * free_names if needed.
+ * @param tile the tile to search
+ * @param user_data the StationNameInformation to base the search on
+ * @return true if the tile contains an industry that has not given
+ * its name to one of the other stations in town.
+ */
+static bool FindNearIndustryName(TileIndex tile, void *user_data)
+{
+ /* All already found industry types */
+ StationNameInformation *sni = (StationNameInformation*)user_data;
+ if (!IsTileType(tile, MP_INDUSTRY)) return false;
+
+ /* If the station name is undefined it means that it doesn't name a station */
+ IndustryType indtype = GetIndustryType(tile);
+ if (GetIndustrySpec(indtype)->station_name == STR_UNDEFINED) return false;
+
+ /* In all cases if an industry that provides a name is found two of
+ * the standard names will be disabled. */
+ sni->free_names &= ~(1 << M(STR_SV_STNAME_OILFIELD) | 1 << M(STR_SV_STNAME_MINES));
+ return !sni->indtypes[indtype];
+}
+
+static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class)
+{
+ static const uint32 _gen_station_name_bits[] = {
+ 0, // STATIONNAMING_RAIL
+ 0, // STATIONNAMING_ROAD
+ 1U << M(STR_SV_STNAME_AIRPORT), // STATIONNAMING_AIRPORT
+ 1U << M(STR_SV_STNAME_OILFIELD), // STATIONNAMING_OILRIG
+ 1U << M(STR_SV_STNAME_DOCKS), // STATIONNAMING_DOCK
+ 1U << M(STR_SV_STNAME_HELIPORT), // STATIONNAMING_HELIPORT
+ };
+
+ const Town *t = st->town;
+ uint32 free_names = UINT32_MAX;
+
+ bool indtypes[NUM_INDUSTRYTYPES];
+ memset(indtypes, 0, sizeof(indtypes));
+
+ const Station *s;
+ FOR_ALL_STATIONS(s) {
+ if (s != st && s->town == t) {
+ if (s->indtype != IT_INVALID) {
+ indtypes[s->indtype] = true;
+ continue;
+ }
+ uint str = M(s->string_id);
+ if (str <= 0x20) {
+ if (str == M(STR_SV_STNAME_FOREST)) {
+ str = M(STR_SV_STNAME_WOODS);
+ }
+ ClrBit(free_names, str);
+ }
+ }
+ }
+
+ TileIndex indtile = tile;
+ StationNameInformation sni = { free_names, indtypes };
+ if (CircularTileSearch(&indtile, 7, FindNearIndustryName, &sni)) {
+ /* An industry has been found nearby */
+ IndustryType indtype = GetIndustryType(indtile);
+ const IndustrySpec *indsp = GetIndustrySpec(indtype);
+ /* STR_NULL means it only disables oil rig/mines */
+ if (indsp->station_name != STR_NULL) {
+ st->indtype = indtype;
+ return STR_SV_STNAME_FALLBACK;
+ }
+ }
+
+ /* Oil rigs/mines name could be marked not free by looking for a near by industry. */
+ free_names = sni.free_names;
+
+ /* check default names */
+ uint32 tmp = free_names & _gen_station_name_bits[name_class];
+ if (tmp != 0) return STR_SV_STNAME + FindFirstBit(tmp);
+
+ /* check mine? */
+ if (HasBit(free_names, M(STR_SV_STNAME_MINES))) {
+ if (CountMapSquareAround(tile, CMSAMine) >= 2) {
+ return STR_SV_STNAME_MINES;
+ }
+ }
+
+ /* check close enough to town to get central as name? */
+ if (DistanceMax(tile, t->xy) < 8) {
+ if (HasBit(free_names, M(STR_SV_STNAME))) return STR_SV_STNAME;
+
+ if (HasBit(free_names, M(STR_SV_STNAME_CENTRAL))) return STR_SV_STNAME_CENTRAL;
+ }
+
+ /* Check lakeside */
+ if (HasBit(free_names, M(STR_SV_STNAME_LAKESIDE)) &&
+ DistanceFromEdge(tile) < 20 &&
+ CountMapSquareAround(tile, CMSAWater) >= 5) {
+ return STR_SV_STNAME_LAKESIDE;
+ }
+
+ /* Check woods */
+ if (HasBit(free_names, M(STR_SV_STNAME_WOODS)) && (
+ CountMapSquareAround(tile, CMSATree) >= 8 ||
+ CountMapSquareAround(tile, IsTileForestIndustry) >= 2)
+ ) {
+ return _settings_game.game_creation.landscape == LT_TROPIC ? STR_SV_STNAME_FOREST : STR_SV_STNAME_WOODS;
+ }
+
+ /* check elevation compared to town */
+ int z = GetTileZ(tile);
+ int z2 = GetTileZ(t->xy);
+ if (z < z2) {
+ if (HasBit(free_names, M(STR_SV_STNAME_VALLEY))) return STR_SV_STNAME_VALLEY;
+ } else if (z > z2) {
+ if (HasBit(free_names, M(STR_SV_STNAME_HEIGHTS))) return STR_SV_STNAME_HEIGHTS;
+ }
+
+ /* check direction compared to town */
+ static const int8 _direction_and_table[] = {
+ ~( (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ),
+ ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_NORTH)) ),
+ ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ),
+ ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_EAST)) ),
+ };
+
+ free_names &= _direction_and_table[
+ (TileX(tile) < TileX(t->xy)) +
+ (TileY(tile) < TileY(t->xy)) * 2];
+
+ tmp = free_names & ((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 6) | (1 << 7) | (1 << 12) | (1 << 26) | (1 << 27) | (1 << 28) | (1 << 29) | (1 << 30));
+ return (tmp == 0) ? STR_SV_STNAME_FALLBACK : (STR_SV_STNAME + FindFirstBit(tmp));
+}
+#undef M
+
+/**
+ * Find the closest deleted station of the current company
+ * @param tile the tile to search from.
+ * @return the closest station or NULL if too far.
+ */
+static Station *GetClosestDeletedStation(TileIndex tile)
+{
+ uint threshold = 8;
+ Station *best_station = NULL;
+ Station *st;
+
+ FOR_ALL_STATIONS(st) {
+ if (!st->IsInUse() && st->owner == _current_company) {
+ uint cur_dist = DistanceManhattan(tile, st->xy);
+
+ if (cur_dist < threshold) {
+ threshold = cur_dist;
+ best_station = st;
+ }
+ }
+ }
+
+ return best_station;
+}
+
+
+void Station::GetTileArea(TileArea *ta, StationType type) const
+{
+ switch (type) {
+ case STATION_RAIL:
+ *ta = this->train_station;
+ return;
+
+ case STATION_AIRPORT:
+ *ta = this->airport;
+ return;
+
+ case STATION_TRUCK:
+ *ta = this->truck_station;
+ return;
+
+ case STATION_BUS:
+ *ta = this->bus_station;
+ return;
+
+ case STATION_DOCK:
+ case STATION_OILRIG:
+ ta->tile = this->dock_tile;
+ break;
+
+ default: NOT_REACHED();
+ }
+
+ ta->w = 1;
+ ta->h = 1;
+}
+
+/**
+ * Update the virtual coords needed to draw the station sign.
+ */
+void Station::UpdateVirtCoord()
+{
+ Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE);
+
+ pt.y -= 32 * ZOOM_LVL_BASE;
+ if ((this->facilities & FACIL_AIRPORT) && this->airport.type == AT_OILRIG) pt.y -= 16 * ZOOM_LVL_BASE;
+
+ SetDParam(0, this->index);
+ SetDParam(1, this->facilities);
+ this->sign.UpdatePosition(pt.x, pt.y, STR_VIEWPORT_STATION);
+
+ SetWindowDirty(WC_STATION_VIEW, this->index);
+}
+
+/** Update the virtual coords needed to draw the station sign for all stations. */
+void UpdateAllStationVirtCoords()
+{
+ BaseStation *st;
+
+ FOR_ALL_BASE_STATIONS(st) {
+ st->UpdateVirtCoord();
+ }
+}
+
+/**
+ * Get a mask of the cargo types that the station accepts.
+ * @param st Station to query
+ * @return the expected mask
+ */
+static uint GetAcceptanceMask(const Station *st)
+{
+ uint mask = 0;
+
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ if (HasBit(st->goods[i].acceptance_pickup, GoodsEntry::GES_ACCEPTANCE)) mask |= 1 << i;
+ }
+ return mask;
+}
+
+/**
+ * Items contains the two cargo names that are to be accepted or rejected.
+ * msg is the string id of the message to display.
+ */
+static void ShowRejectOrAcceptNews(const Station *st, uint num_items, CargoID *cargo, StringID msg)
+{
+ for (uint i = 0; i < num_items; i++) {
+ SetDParam(i + 1, CargoSpec::Get(cargo[i])->name);
+ }
+
+ SetDParam(0, st->index);
+ AddNewsItem(msg, NT_ACCEPTANCE, NF_INCOLOUR | NF_SMALL, NR_STATION, st->index);
+}
+
+/**
+ * Get the cargo types being produced around the tile (in a rectangle).
+ * @param tile Northtile of area
+ * @param w X extent of the area
+ * @param h Y extent of the area
+ * @param rad Search radius in addition to the given area
+ */
+CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad)
+{
+ CargoArray produced;
+
+ int x = TileX(tile);
+ int y = TileY(tile);
+
+ /* expand the region by rad tiles on each side
+ * while making sure that we remain inside the board. */
+ int x2 = min(x + w + rad, MapSizeX());
+ int x1 = max(x - rad, 0);
+
+ int y2 = min(y + h + rad, MapSizeY());
+ int y1 = max(y - rad, 0);
+
+ assert(x1 < x2);
+ assert(y1 < y2);
+ assert(w > 0);
+ assert(h > 0);
+
+ TileArea ta(TileXY(x1, y1), TileXY(x2 - 1, y2 - 1));
+
+ /* Loop over all tiles to get the produced cargo of
+ * everything except industries */
+ TILE_AREA_LOOP(tile, ta) AddProducedCargo(tile, produced);
+
+ /* Loop over the industries. They produce cargo for
+ * anything that is within 'rad' from their bounding
+ * box. As such if you have e.g. a oil well the tile
+ * area loop might not hit an industry tile while
+ * the industry would produce cargo for the station.
+ */
+ const Industry *i;
+ FOR_ALL_INDUSTRIES(i) {
+ if (!ta.Intersects(i->location)) continue;
+
+ for (uint j = 0; j < lengthof(i->produced_cargo); j++) {
+ CargoID cargo = i->produced_cargo[j];
+ if (cargo != CT_INVALID) produced[cargo]++;
+ }
+ }
+
+ return produced;
+}
+
+/**
+ * Get the acceptance of cargoes around the tile in 1/8.
+ * @param tile Center of the search area
+ * @param w X extent of area
+ * @param h Y extent of area
+ * @param rad Search radius in addition to given area
+ * @param always_accepted bitmask of cargo accepted by houses and headquarters; can be NULL
+ */
+CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, uint32 *always_accepted)
+{
+ CargoArray acceptance;
+ if (always_accepted != NULL) *always_accepted = 0;
+
+ int x = TileX(tile);
+ int y = TileY(tile);
+
+ /* expand the region by rad tiles on each side
+ * while making sure that we remain inside the board. */
+ int x2 = min(x + w + rad, MapSizeX());
+ int y2 = min(y + h + rad, MapSizeY());
+ int x1 = max(x - rad, 0);
+ int y1 = max(y - rad, 0);
+
+ assert(x1 < x2);
+ assert(y1 < y2);
+ assert(w > 0);
+ assert(h > 0);
+
+ for (int yc = y1; yc != y2; yc++) {
+ for (int xc = x1; xc != x2; xc++) {
+ TileIndex tile = TileXY(xc, yc);
+ AddAcceptedCargo(tile, acceptance, always_accepted);
+ }
+ }
+
+ return acceptance;
+}
+
+/**
+ * Update the acceptance for a station.
+ * @param st Station to update
+ * @param show_msg controls whether to display a message that acceptance was changed.
+ */
+void UpdateStationAcceptance(Station *st, bool show_msg)
+{
+ /* old accepted goods types */
+ uint old_acc = GetAcceptanceMask(st);
+
+ /* And retrieve the acceptance. */
+ CargoArray acceptance;
+ if (!st->rect.IsEmpty()) {
+ acceptance = GetAcceptanceAroundTiles(
+ TileXY(st->rect.left, st->rect.top),
+ st->rect.right - st->rect.left + 1,
+ st->rect.bottom - st->rect.top + 1,
+ st->GetCatchmentRadius(),
+ &st->always_accepted
+ );
+ }
+
+ /* Adjust in case our station only accepts fewer kinds of goods */
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ uint amt = min(acceptance[i], 15);
+
+ /* Make sure the station can accept the goods type. */
+ bool is_passengers = IsCargoInClass(i, CC_PASSENGERS);
+ if ((!is_passengers && !(st->facilities & ~FACIL_BUS_STOP)) ||
+ (is_passengers && !(st->facilities & ~FACIL_TRUCK_STOP))) {
+ amt = 0;
+ }
+
+ SB(st->goods[i].acceptance_pickup, GoodsEntry::GES_ACCEPTANCE, 1, amt >= 8);
+ }
+
+ /* Only show a message in case the acceptance was actually changed. */
+ uint new_acc = GetAcceptanceMask(st);
+ if (old_acc == new_acc) return;
+
+ /* show a message to report that the acceptance was changed? */
+ if (show_msg && st->owner == _local_company && st->IsInUse()) {
+ /* List of accept and reject strings for different number of
+ * cargo types */
+ static const StringID accept_msg[] = {
+ STR_NEWS_STATION_NOW_ACCEPTS_CARGO,
+ STR_NEWS_STATION_NOW_ACCEPTS_CARGO_AND_CARGO,
+ };
+ static const StringID reject_msg[] = {
+ STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO,
+ STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO_OR_CARGO,
+ };
+
+ /* Array of accepted and rejected cargo types */
+ CargoID accepts[2] = { CT_INVALID, CT_INVALID };
+ CargoID rejects[2] = { CT_INVALID, CT_INVALID };
+ uint num_acc = 0;
+ uint num_rej = 0;
+
+ /* Test each cargo type to see if its acceptance has changed */
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ if (HasBit(new_acc, i)) {
+ if (!HasBit(old_acc, i) && num_acc < lengthof(accepts)) {
+ /* New cargo is accepted */
+ accepts[num_acc++] = i;
+ }
+ } else {
+ if (HasBit(old_acc, i) && num_rej < lengthof(rejects)) {
+ /* Old cargo is no longer accepted */
+ rejects[num_rej++] = i;
+ }
+ }
+ }
+
+ /* Show news message if there are any changes */
+ if (num_acc > 0) ShowRejectOrAcceptNews(st, num_acc, accepts, accept_msg[num_acc - 1]);
+ if (num_rej > 0) ShowRejectOrAcceptNews(st, num_rej, rejects, reject_msg[num_rej - 1]);
+ }
+
+ /* redraw the station view since acceptance changed */
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_ACCEPT_RATING_LIST);
+}
+
+static void UpdateStationSignCoord(BaseStation *st)
+{
+ const StationRect *r = &st->rect;
+
+ if (r->IsEmpty()) return; // no tiles belong to this station
+
+ /* clamp sign coord to be inside the station rect */
+ st->xy = TileXY(ClampU(TileX(st->xy), r->left, r->right), ClampU(TileY(st->xy), r->top, r->bottom));
+ st->UpdateVirtCoord();
+}
+
+/**
+ * Common part of building various station parts and possibly attaching them to an existing one.
+ * @param [in,out] st Station to attach to
+ * @param flags Command flags
+ * @param reuse Whether to try to reuse a deleted station (gray sign) if possible
+ * @param area Area occupied by the new part
+ * @param name_class Station naming class to use to generate the new station's name
+ * @return Command error that occured, if any
+ */
+static CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reuse, TileArea area, StationNaming name_class)
+{
+ /* Find a deleted station close to us */
+ if (*st == NULL && reuse) *st = GetClosestDeletedStation(area.tile);
+
+ if (*st != NULL) {
+ if ((*st)->owner != _current_company) {
+ return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_STATION);
+ }
+
+ CommandCost ret = (*st)->rect.BeforeAddRect(area.tile, area.w, area.h, StationRect::ADD_TEST);
+ if (ret.Failed()) return ret;
+ } else {
+ /* allocate and initialize new station */
+ if (!Station::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
+
+ if (flags & DC_EXEC) {
+ *st = new Station(area.tile);
+
+ (*st)->town = ClosestTownFromTile(area.tile, UINT_MAX);
+ (*st)->string_id = GenerateStationName(*st, area.tile, name_class);
+
+ if (Company::IsValidID(_current_company)) {
+ SetBit((*st)->town->have_ratings, _current_company);
+ }
+ }
+ }
+ return CommandCost();
+}
+
+/**
+ * This is called right after a station was deleted.
+ * It checks if the whole station is free of substations, and if so, the station will be
+ * deleted after a little while.
+ * @param st Station
+ */
+static void DeleteStationIfEmpty(BaseStation *st)
+{
+ if (!st->IsInUse()) {
+ st->delete_ctr = 0;
+ InvalidateWindowData(WC_STATION_LIST, st->owner, 0);
+ }
+ /* station remains but it probably lost some parts - station sign should stay in the station boundaries */
+ UpdateStationSignCoord(st);
+}
+
+CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags);
+
+/**
+ * Checks if the given tile is buildable, flat and has a certain height.
+ * @param tile TileIndex to check.
+ * @param invalid_dirs Prohibited directions for slopes (set of #DiagDirection).
+ * @param allowed_z Height allowed for the tile. If allowed_z is negative, it will be set to the height of this tile.
+ * @param allow_steep Whether steep slopes are allowed.
+ * @param check_bridge Check for the existence of a bridge.
+ * @return The cost in case of success, or an error code if it failed.
+ */
+CommandCost CheckBuildableTile(TileIndex tile, uint invalid_dirs, int &allowed_z, bool allow_steep, bool check_bridge = true)
+{
+ if (check_bridge && MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) {
+ return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
+ }
+
+ CommandCost ret = EnsureNoVehicleOnGround(tile);
+ if (ret.Failed()) return ret;
+
+ int z;
+ Slope tileh = GetTileSlope(tile, &z);
+
+ /* Prohibit building if
+ * 1) The tile is "steep" (i.e. stretches two height levels).
+ * 2) The tile is non-flat and the build_on_slopes switch is disabled.
+ */
+ if ((!allow_steep && IsSteepSlope(tileh)) ||
+ ((!_settings_game.construction.build_on_slopes) && tileh != SLOPE_FLAT)) {
+ return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
+ }
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ int flat_z = z + GetSlopeMaxZ(tileh);
+ if (tileh != SLOPE_FLAT) {
+ /* Forbid building if the tile faces a slope in a invalid direction. */
+ for (DiagDirection dir = DIAGDIR_BEGIN; dir != DIAGDIR_END; dir++) {
+ if (HasBit(invalid_dirs, dir) && !CanBuildDepotByTileh(dir, tileh)) {
+ return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
+ }
+ }
+ cost.AddCost(_price[PR_BUILD_FOUNDATION]);
+ }
+
+ /* The level of this tile must be equal to allowed_z. */
+ if (allowed_z < 0) {
+ /* First tile. */
+ allowed_z = flat_z;
+ } else if (allowed_z != flat_z) {
+ return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
+ }
+
+ return cost;
+}
+
+/**
+ * Tries to clear the given area.
+ * @param tile_area Area to check.
+ * @param flags Operation to perform.
+ * @return The cost in case of success, or an error code if it failed.
+ */
+CommandCost CheckFlatLand(TileArea tile_area, DoCommandFlag flags)
+{
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ int allowed_z = -1;
+
+ TILE_AREA_LOOP(tile_cur, tile_area) {
+ CommandCost ret = CheckBuildableTile(tile_cur, 0, allowed_z, true);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+
+ ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+ }
+
+ return cost;
+}
+
+/**
+ * Checks if a rail station can be built at the given area.
+ * @param tile_area Area to check.
+ * @param flags Operation to perform.
+ * @param axis Rail station axis.
+ * @param station StationID to be queried and returned if available.
+ * @param rt The rail type to check for (overbuilding rail stations over rail).
+ * @param affected_vehicles List of trains with PBS reservations on the tiles
+ * @param spec_class Station class.
+ * @param spec_index Index into the station class.
+ * @param plat_len Platform length.
+ * @param numtracks Number of platforms.
+ * @return The cost in case of success, or an error code if it failed.
+ */
+static CommandCost CheckFlatLandRailStation(TileArea tile_area, DoCommandFlag flags, Axis axis, StationID *station, RailType rt, SmallVector<Train *, 4> &affected_vehicles, StationClassID spec_class, byte spec_index, byte plat_len, byte numtracks)
+{
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ int allowed_z = -1;
+ uint invalid_dirs = 5 << axis;
+
+ const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index);
+ bool slope_cb = statspec != NULL && HasBit(statspec->callback_mask, CBM_STATION_SLOPE_CHECK);
+
+ TILE_AREA_LOOP(tile_cur, tile_area) {
+ CommandCost ret = CheckBuildableTile(tile_cur, invalid_dirs, allowed_z, false);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+
+ if (slope_cb) {
+ /* Do slope check if requested. */
+ ret = PerformStationTileSlopeCheck(tile_area.tile, tile_cur, statspec, axis, plat_len, numtracks);
+ if (ret.Failed()) return ret;
+ }
+
+ /* if station is set, then we have special handling to allow building on top of already existing stations.
+ * so station points to INVALID_STATION if we can build on any station.
+ * Or it points to a station if we're only allowed to build on exactly that station. */
+ if (station != NULL && IsTileType(tile_cur, MP_STATION)) {
+ if (!IsRailStation(tile_cur)) {
+ return ClearTile_Station(tile_cur, DC_AUTO); // get error message
+ } else {
+ StationID st = GetStationIndex(tile_cur);
+ if (*station == INVALID_STATION) {
+ *station = st;
+ } else if (*station != st) {
+ return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
+ }
+ }
+ } else {
+ /* Rail type is only valid when building a railway station; if station to
+ * build isn't a rail station it's INVALID_RAILTYPE. */
+ if (rt != INVALID_RAILTYPE &&
+ IsPlainRailTile(tile_cur) && !HasSignals(tile_cur) &&
+ HasPowerOnRail(GetRailType(tile_cur), rt)) {
+ /* Allow overbuilding if the tile:
+ * - has rail, but no signals
+ * - it has exactly one track
+ * - the track is in line with the station
+ * - the current rail type has power on the to-be-built type (e.g. convert normal rail to el rail)
+ */
+ TrackBits tracks = GetTrackBits(tile_cur);
+ Track track = RemoveFirstTrack(&tracks);
+ Track expected_track = HasBit(invalid_dirs, DIAGDIR_NE) ? TRACK_X : TRACK_Y;
+
+ if (tracks == TRACK_BIT_NONE && track == expected_track) {
+ /* Check for trains having a reservation for this tile. */
+ if (HasBit(GetRailReservationTrackBits(tile_cur), track)) {
+ Train *v = GetTrainForReservation(tile_cur, track);
+ if (v != NULL) {
+ *affected_vehicles.Append() = v;
+ }
+ }
+ CommandCost ret = DoCommand(tile_cur, 0, track, flags, CMD_REMOVE_SINGLE_RAIL);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+ /* With flags & ~DC_EXEC CmdLandscapeClear would fail since the rail still exists */
+ continue;
+ }
+ }
+ ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+ }
+ }
+
+ return cost;
+}
+
+/**
+ * Checks if a road stop can be built at the given tile.
+ * @param tile_area Area to check.
+ * @param flags Operation to perform.
+ * @param invalid_dirs Prohibited directions (set of DiagDirections).
+ * @param is_drive_through True if trying to build a drive-through station.
+ * @param is_truck_stop True when building a truck stop, false otherwise.
+ * @param axis Axis of a drive-through road stop.
+ * @param station StationID to be queried and returned if available.
+ * @param rts Road types to build.
+ * @return The cost in case of success, or an error code if it failed.
+ */
+static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, bool is_truck_stop, Axis axis, StationID *station, RoadTypes rts)
+{
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ int allowed_z = -1;
+
+ TILE_AREA_LOOP(cur_tile, tile_area) {
+ CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+
+ /* If station is set, then we have special handling to allow building on top of already existing stations.
+ * Station points to INVALID_STATION if we can build on any station.
+ * Or it points to a station if we're only allowed to build on exactly that station. */
+ if (station != NULL && IsTileType(cur_tile, MP_STATION)) {
+ if (!IsRoadStop(cur_tile)) {
+ return ClearTile_Station(cur_tile, DC_AUTO); // Get error message.
+ } else {
+ if (is_truck_stop != IsTruckStop(cur_tile) ||
+ is_drive_through != IsDriveThroughStopTile(cur_tile)) {
+ return ClearTile_Station(cur_tile, DC_AUTO); // Get error message.
+ }
+ /* Drive-through station in the wrong direction. */
+ if (is_drive_through && IsDriveThroughStopTile(cur_tile) && DiagDirToAxis(GetRoadStopDir(cur_tile)) != axis){
+ return_cmd_error(STR_ERROR_DRIVE_THROUGH_DIRECTION);
+ }
+ StationID st = GetStationIndex(cur_tile);
+ if (*station == INVALID_STATION) {
+ *station = st;
+ } else if (*station != st) {
+ return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
+ }
+ }
+ } else {
+ bool build_over_road = is_drive_through && IsNormalRoadTile(cur_tile);
+ /* Road bits in the wrong direction. */
+ RoadBits rb = IsNormalRoadTile(cur_tile) ? GetAllRoadBits(cur_tile) : ROAD_NONE;
+ if (build_over_road && (rb & (axis == AXIS_X ? ROAD_Y : ROAD_X)) != 0) {
+ /* Someone was pedantic and *NEEDED* three fracking different error messages. */
+ switch (CountBits(rb)) {
+ case 1:
+ return_cmd_error(STR_ERROR_DRIVE_THROUGH_DIRECTION);
+
+ case 2:
+ if (rb == ROAD_X || rb == ROAD_Y) return_cmd_error(STR_ERROR_DRIVE_THROUGH_DIRECTION);
+ return_cmd_error(STR_ERROR_DRIVE_THROUGH_CORNER);
+
+ default: // 3 or 4
+ return_cmd_error(STR_ERROR_DRIVE_THROUGH_JUNCTION);
+ }
+ }
+
+ RoadTypes cur_rts = IsNormalRoadTile(cur_tile) ? GetRoadTypes(cur_tile) : ROADTYPES_NONE;
+ uint num_roadbits = 0;
+ if (build_over_road) {
+ /* There is a road, check if we can build road+tram stop over it. */
+ if (HasBit(cur_rts, ROADTYPE_ROAD)) {
+ Owner road_owner = GetRoadOwner(cur_tile, ROADTYPE_ROAD);
+ if (road_owner == OWNER_TOWN) {
+ if (!_settings_game.construction.road_stop_on_town_road) return_cmd_error(STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD);
+ } else if (!_settings_game.construction.road_stop_on_competitor_road && road_owner != OWNER_NONE) {
+ CommandCost ret = CheckOwnership(road_owner);
+ if (ret.Failed()) return ret;
+ }
+ num_roadbits += CountBits(GetRoadBits(cur_tile, ROADTYPE_ROAD));
+ }
+
+ /* There is a tram, check if we can build road+tram stop over it. */
+ if (HasBit(cur_rts, ROADTYPE_TRAM)) {
+ Owner tram_owner = GetRoadOwner(cur_tile, ROADTYPE_TRAM);
+ if (!_settings_game.construction.road_stop_on_competitor_road && tram_owner != OWNER_NONE) {
+ CommandCost ret = CheckOwnership(tram_owner);
+ if (ret.Failed()) return ret;
+ }
+ num_roadbits += CountBits(GetRoadBits(cur_tile, ROADTYPE_TRAM));
+ }
+
+ /* Take into account existing roadbits. */
+ rts |= cur_rts;
+ } else {
+ ret = DoCommand(cur_tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+ }
+
+ uint roadbits_to_build = CountBits(rts) * 2 - num_roadbits;
+ cost.AddCost(_price[PR_BUILD_ROAD] * roadbits_to_build);
+ }
+ }
+
+ return cost;
+}
+
+/**
+ * Check whether we can expand the rail part of the given station.
+ * @param st the station to expand
+ * @param new_ta the current (and if all is fine new) tile area of the rail part of the station
+ * @param axis the axis of the newly build rail
+ * @return Succeeded or failed command.
+ */
+CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta, Axis axis)
+{
+ TileArea cur_ta = st->train_station;
+
+ /* determine new size of train station region.. */
+ int x = min(TileX(cur_ta.tile), TileX(new_ta.tile));
+ int y = min(TileY(cur_ta.tile), TileY(new_ta.tile));
+ new_ta.w = max(TileX(cur_ta.tile) + cur_ta.w, TileX(new_ta.tile) + new_ta.w) - x;
+ new_ta.h = max(TileY(cur_ta.tile) + cur_ta.h, TileY(new_ta.tile) + new_ta.h) - y;
+ new_ta.tile = TileXY(x, y);
+
+ /* make sure the final size is not too big. */
+ if (new_ta.w > _settings_game.station.station_spread || new_ta.h > _settings_game.station.station_spread) {
+ return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
+ }
+
+ return CommandCost();
+}
+
+static inline byte *CreateSingle(byte *layout, int n)
+{
+ int i = n;
+ do *layout++ = 0; while (--i);
+ layout[((n - 1) >> 1) - n] = 2;
+ return layout;
+}
+
+static inline byte *CreateMulti(byte *layout, int n, byte b)
+{
+ int i = n;
+ do *layout++ = b; while (--i);
+ if (n > 4) {
+ layout[0 - n] = 0;
+ layout[n - 1 - n] = 0;
+ }
+ return layout;
+}
+
+/**
+ * Create the station layout for the given number of tracks and platform length.
+ * @param layout The layout to write to.
+ * @param numtracks The number of tracks to write.
+ * @param plat_len The length of the platforms.
+ * @param statspec The specification of the station to (possibly) get the layout from.
+ */
+void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec)
+{
+ if (statspec != NULL && statspec->lengths >= plat_len &&
+ statspec->platforms[plat_len - 1] >= numtracks &&
+ statspec->layouts[plat_len - 1][numtracks - 1]) {
+ /* Custom layout defined, follow it. */
+ memcpy(layout, statspec->layouts[plat_len - 1][numtracks - 1],
+ plat_len * numtracks);
+ return;
+ }
+
+ if (plat_len == 1) {
+ CreateSingle(layout, numtracks);
+ } else {
+ if (numtracks & 1) layout = CreateSingle(layout, plat_len);
+ numtracks >>= 1;
+
+ while (--numtracks >= 0) {
+ layout = CreateMulti(layout, plat_len, 4);
+ layout = CreateMulti(layout, plat_len, 6);
+ }
+ }
+}
+
+/**
+ * Find a nearby station that joins this station.
+ * @tparam T the class to find a station for
+ * @tparam error_message the error message when building a station on top of others
+ * @param existing_station an existing station we build over
+ * @param station_to_join the station to join to
+ * @param adjacent whether adjacent stations are allowed
+ * @param ta the area of the newly build station
+ * @param st 'return' pointer for the found station
+ * @return command cost with the error or 'okay'
+ */
+template <class T, StringID error_message>
+CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st)
+{
+ assert(*st == NULL);
+ bool check_surrounding = true;
+
+ if (_settings_game.station.adjacent_stations) {
+ if (existing_station != INVALID_STATION) {
+ if (adjacent && existing_station != station_to_join) {
+ /* You can't build an adjacent station over the top of one that
+ * already exists. */
+ return_cmd_error(error_message);
+ } else {
+ /* Extend the current station, and don't check whether it will
+ * be near any other stations. */
+ *st = T::GetIfValid(existing_station);
+ check_surrounding = (*st == NULL);
+ }
+ } else {
+ /* There's no station here. Don't check the tiles surrounding this
+ * one if the company wanted to build an adjacent station. */
+ if (adjacent) check_surrounding = false;
+ }
+ }
+
+ if (check_surrounding) {
+ /* Make sure there are no similar stations around us. */
+ CommandCost ret = GetStationAround(ta, existing_station, st);
+ if (ret.Failed()) return ret;
+ }
+
+ /* Distant join */
+ if (*st == NULL && station_to_join != INVALID_STATION) *st = T::GetIfValid(station_to_join);
+
+ return CommandCost();
+}
+
+/**
+ * Find a nearby station that joins this station.
+ * @param existing_station an existing station we build over
+ * @param station_to_join the station to join to
+ * @param adjacent whether adjacent stations are allowed
+ * @param ta the area of the newly build station
+ * @param st 'return' pointer for the found station
+ * @return command cost with the error or 'okay'
+ */
+static CommandCost FindJoiningStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st)
+{
+ return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST>(existing_station, station_to_join, adjacent, ta, st);
+}
+
+/**
+ * Find a nearby waypoint that joins this waypoint.
+ * @param existing_waypoint an existing waypoint we build over
+ * @param waypoint_to_join the waypoint to join to
+ * @param adjacent whether adjacent waypoints are allowed
+ * @param ta the area of the newly build waypoint
+ * @param wp 'return' pointer for the found waypoint
+ * @return command cost with the error or 'okay'
+ */
+CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_to_join, bool adjacent, TileArea ta, Waypoint **wp)
+{
+ return FindJoiningBaseStation<Waypoint, STR_ERROR_MUST_REMOVE_RAILWAYPOINT_FIRST>(existing_waypoint, waypoint_to_join, adjacent, ta, wp);
+}
+
+/**
+ * Build rail station
+ * @param tile_org northern most position of station dragging/placement
+ * @param flags operation to perform
+ * @param p1 various bitstuffed elements
+ * - p1 = (bit 0- 3) - railtype
+ * - p1 = (bit 4) - orientation (Axis)
+ * - p1 = (bit 8-15) - number of tracks
+ * - p1 = (bit 16-23) - platform length
+ * - p1 = (bit 24) - allow stations directly adjacent to other stations.
+ * @param p2 various bitstuffed elements
+ * - p2 = (bit 0- 7) - custom station class
+ * - p2 = (bit 8-15) - custom station id
+ * - p2 = (bit 16-31) - station ID to join (NEW_STATION if build new one)
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdBuildRailStation(TileIndex tile_org, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ /* Unpack parameters */
+ RailType rt = Extract<RailType, 0, 4>(p1);
+ Axis axis = Extract<Axis, 4, 1>(p1);
+ byte numtracks = GB(p1, 8, 8);
+ byte plat_len = GB(p1, 16, 8);
+ bool adjacent = HasBit(p1, 24);
+
+ StationClassID spec_class = Extract<StationClassID, 0, 8>(p2);
+ byte spec_index = GB(p2, 8, 8);
+ StationID station_to_join = GB(p2, 16, 16);
+
+ /* Does the authority allow this? */
+ CommandCost ret = CheckIfAuthorityAllowsNewStation(tile_org, flags);
+ if (ret.Failed()) return ret;
+
+ if (!ValParamRailtype(rt)) return CMD_ERROR;
+
+ /* Check if the given station class is valid */
+ if ((uint)spec_class >= StationClass::GetClassCount() || spec_class == STAT_CLASS_WAYP) return CMD_ERROR;
+ if (spec_index >= StationClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR;
+ if (plat_len == 0 || numtracks == 0) return CMD_ERROR;
+
+ int w_org, h_org;
+ if (axis == AXIS_X) {
+ w_org = plat_len;
+ h_org = numtracks;
+ } else {
+ h_org = plat_len;
+ w_org = numtracks;
+ }
+
+ bool reuse = (station_to_join != NEW_STATION);
+ if (!reuse) station_to_join = INVALID_STATION;
+ bool distant_join = (station_to_join != INVALID_STATION);
+
+ if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR;
+
+ if (h_org > _settings_game.station.station_spread || w_org > _settings_game.station.station_spread) return CMD_ERROR;
+
+ /* these values are those that will be stored in train_tile and station_platforms */
+ TileArea new_location(tile_org, w_org, h_org);
+
+ /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
+ StationID est = INVALID_STATION;
+ SmallVector<Train *, 4> affected_vehicles;
+ /* Clear the land below the station. */
+ CommandCost cost = CheckFlatLandRailStation(new_location, flags, axis, &est, rt, affected_vehicles, spec_class, spec_index, plat_len, numtracks);
+ if (cost.Failed()) return cost;
+ /* Add construction expenses. */
+ cost.AddCost((numtracks * _price[PR_BUILD_STATION_RAIL] + _price[PR_BUILD_STATION_RAIL_LENGTH]) * plat_len);
+ cost.AddCost(numtracks * plat_len * RailBuildCost(rt));
+
+ Station *st = NULL;
+ ret = FindJoiningStation(est, station_to_join, adjacent, new_location, &st);
+ if (ret.Failed()) return ret;
+
+ ret = BuildStationPart(&st, flags, reuse, new_location, STATIONNAMING_RAIL);
+ if (ret.Failed()) return ret;
+
+ if (st != NULL && st->train_station.tile != INVALID_TILE) {
+ CommandCost ret = CanExpandRailStation(st, new_location, axis);
+ if (ret.Failed()) return ret;
+ }
+
+ /* Check if we can allocate a custom stationspec to this station */
+ const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index);
+ int specindex = AllocateSpecToStation(statspec, st, (flags & DC_EXEC) != 0);
+ if (specindex == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS);
+
+ if (statspec != NULL) {
+ /* Perform NewStation checks */
+
+ /* Check if the station size is permitted */
+ if (HasBit(statspec->disallowed_platforms, numtracks - 1) || HasBit(statspec->disallowed_lengths, plat_len - 1)) {
+ return CMD_ERROR;
+ }
+
+ /* Check if the station is buildable */
+ if (HasBit(statspec->callback_mask, CBM_STATION_AVAIL)) {
+ uint16 cb_res = GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, NULL, INVALID_TILE);
+ if (cb_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(statspec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res)) return CMD_ERROR;
+ }
+ }
+
+ if (flags & DC_EXEC) {
+ TileIndexDiff tile_delta;
+ byte *layout_ptr;
+ byte numtracks_orig;
+ Track track;
+
+ st->train_station = new_location;
+ st->AddFacility(FACIL_TRAIN, new_location.tile);
+
+ st->rect.BeforeAddRect(tile_org, w_org, h_org, StationRect::ADD_TRY);
+
+ if (statspec != NULL) {
+ /* Include this station spec's animation trigger bitmask
+ * in the station's cached copy. */
+ st->cached_anim_triggers |= statspec->animation.triggers;
+ }
+
+ tile_delta = (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
+ track = AxisToTrack(axis);
+
+ layout_ptr = AllocaM(byte, numtracks * plat_len);
+ GetStationLayout(layout_ptr, numtracks, plat_len, statspec);
+
+ numtracks_orig = numtracks;
+
+ Company *c = Company::Get(st->owner);
+ TileIndex tile_track = tile_org;
+ do {
+ TileIndex tile = tile_track;
+ int w = plat_len;
+ do {
+ byte layout = *layout_ptr++;
+ if (IsRailStationTile(tile) && HasStationReservation(tile)) {
+ /* Check for trains having a reservation for this tile. */
+ Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile)));
+ if (v != NULL) {
+ FreeTrainTrackReservation(v);
+ *affected_vehicles.Append() = v;
+ if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false);
+ for (; v->Next() != NULL; v = v->Next()) { }
+ if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), false);
+ }
+ }
+
+ /* Railtype can change when overbuilding. */
+ if (IsRailStationTile(tile)) {
+ if (!IsStationTileBlocked(tile)) c->infrastructure.rail[GetRailType(tile)]--;
+ c->infrastructure.station--;
+ }
+
+ /* Remove animation if overbuilding */
+ DeleteAnimatedTile(tile);
+ byte old_specindex = HasStationTileRail(tile) ? GetCustomStationSpecIndex(tile) : 0;
+ MakeRailStation(tile, st->owner, st->index, axis, layout & ~1, rt);
+ /* Free the spec if we overbuild something */
+ DeallocateSpecFromStation(st, old_specindex);
+
+ SetCustomStationSpecIndex(tile, specindex);
+ SetStationTileRandomBits(tile, GB(Random(), 0, 4));
+ SetAnimationFrame(tile, 0);
+
+ if (!IsStationTileBlocked(tile)) c->infrastructure.rail[rt]++;
+ c->infrastructure.station++;
+
+ if (statspec != NULL) {
+ /* Use a fixed axis for GetPlatformInfo as our platforms / numtracks are always the right way around */
+ uint32 platinfo = GetPlatformInfo(AXIS_X, GetStationGfx(tile), plat_len, numtracks_orig, plat_len - w, numtracks_orig - numtracks, false);
+
+ /* As the station is not yet completely finished, the station does not yet exist. */
+ uint16 callback = GetStationCallback(CBID_STATION_TILE_LAYOUT, platinfo, 0, statspec, NULL, tile);
+ if (callback != CALLBACK_FAILED) {
+ if (callback < 8) {
+ SetStationGfx(tile, (callback & ~1) + axis);
+ } else {
+ ErrorUnknownCallbackResult(statspec->grf_prop.grffile->grfid, CBID_STATION_TILE_LAYOUT, callback);
+ }
+ }
+
+ /* Trigger station animation -- after building? */
+ TriggerStationAnimation(st, tile, SAT_BUILT);
+ }
+
+ tile += tile_delta;
+ } while (--w);
+ AddTrackToSignalBuffer(tile_track, track, _current_company);
+ YapfNotifyTrackLayoutChange(tile_track, track);
+ tile_track += tile_delta ^ TileDiffXY(1, 1); // perpendicular to tile_delta
+ } while (--numtracks);
+
+ for (uint i = 0; i < affected_vehicles.Length(); ++i) {
+ /* Restore reservations of trains. */
+ Train *v = affected_vehicles[i];
+ if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true);
+ TryPathReserve(v, true, true);
+ for (; v->Next() != NULL; v = v->Next()) { }
+ if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true);
+ }
+
+ /* Check whether we need to expand the reservation of trains already on the station. */
+ TileArea update_reservation_area;
+ if (axis == AXIS_X) {
+ update_reservation_area = TileArea(tile_org, 1, numtracks_orig);
+ } else {
+ update_reservation_area = TileArea(tile_org, numtracks_orig, 1);
+ }
+
+ TILE_AREA_LOOP(tile, update_reservation_area) {
+ DiagDirection dir = AxisToDiagDir(axis);
+ TileIndexDiff tile_offset = TileOffsByDiagDir(dir);
+ TileIndex platform_begin = tile - tile_offset * (st->GetPlatformLength(tile, ReverseDiagDir(dir)) - 1);
+ TileIndex platform_end = tile + tile_offset * (st->GetPlatformLength(tile, dir) - 1);
+
+ /* If there is at least on reservation on the platform, we reserve the whole platform. */
+ bool reservation = false;
+ for (TileIndex t = platform_begin; !reservation && t <= platform_end; t += tile_offset) {
+ reservation = HasStationReservation(t);
+ }
+
+ if (reservation) {
+ SetRailStationPlatformReservation(platform_begin, dir, true);
+ }
+ }
+
+ st->MarkTilesDirty(false);
+ st->UpdateVirtCoord();
+ UpdateStationAcceptance(st, false);
+ st->RecomputeIndustriesNear();
+ InvalidateWindowData(WC_SELECT_STATION, 0, 0);
+ InvalidateWindowData(WC_STATION_LIST, st->owner, 0);
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS);
+ DirtyCompanyInfrastructureWindows(st->owner);
+ }
+
+ return cost;
+}
+
+static void MakeRailStationAreaSmaller(BaseStation *st)
+{
+ TileArea ta = st->train_station;
+
+restart:
+
+ /* too small? */
+ if (ta.w != 0 && ta.h != 0) {
+ /* check the left side, x = constant, y changes */
+ for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(0, i));) {
+ /* the left side is unused? */
+ if (++i == ta.h) {
+ ta.tile += TileDiffXY(1, 0);
+ ta.w--;
+ goto restart;
+ }
+ }
+
+ /* check the right side, x = constant, y changes */
+ for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(ta.w - 1, i));) {
+ /* the right side is unused? */
+ if (++i == ta.h) {
+ ta.w--;
+ goto restart;
+ }
+ }
+
+ /* check the upper side, y = constant, x changes */
+ for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(i, 0));) {
+ /* the left side is unused? */
+ if (++i == ta.w) {
+ ta.tile += TileDiffXY(0, 1);
+ ta.h--;
+ goto restart;
+ }
+ }
+
+ /* check the lower side, y = constant, x changes */
+ for (uint i = 0; !st->TileBelongsToRailStation(ta.tile + TileDiffXY(i, ta.h - 1));) {
+ /* the left side is unused? */
+ if (++i == ta.w) {
+ ta.h--;
+ goto restart;
+ }
+ }
+ } else {
+ ta.Clear();
+ }
+
+ st->train_station = ta;
+}
+
+/**
+ * Remove a number of tiles from any rail station within the area.
+ * @param ta the area to clear station tile from.
+ * @param affected_stations the stations affected.
+ * @param flags the command flags.
+ * @param removal_cost the cost for removing the tile, including the rail.
+ * @param keep_rail whether to keep the rail of the station.
+ * @tparam T the type of station to remove.
+ * @return the number of cleared tiles or an error.
+ */
+template <class T>
+CommandCost RemoveFromRailBaseStation(TileArea ta, SmallVector<T *, 4> &affected_stations, DoCommandFlag flags, Money removal_cost, bool keep_rail)
+{
+ /* Count of the number of tiles removed */
+ int quantity = 0;
+ CommandCost total_cost(EXPENSES_CONSTRUCTION);
+
+ /* Do the action for every tile into the area */
+ TILE_AREA_LOOP(tile, ta) {
+ /* Make sure the specified tile is a rail station */
+ if (!HasStationTileRail(tile)) continue;
+
+ /* If there is a vehicle on ground, do not allow to remove (flood) the tile */
+ CommandCost ret = EnsureNoVehicleOnGround(tile);
+ if (ret.Failed()) continue;
+
+ /* Check ownership of station */
+ T *st = T::GetByTile(tile);
+ if (st == NULL) continue;
+
+ if (_current_company != OWNER_WATER) {
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) continue;
+ }
+
+ /* If we reached here, the tile is valid so increase the quantity of tiles we will remove */
+ quantity++;
+
+ if (keep_rail || IsStationTileBlocked(tile)) {
+ /* Don't refund the 'steel' of the track when we keep the
+ * rail, or when the tile didn't have any rail at all. */
+ total_cost.AddCost(-_price[PR_CLEAR_RAIL]);
+ }
+
+ if (flags & DC_EXEC) {
+ /* read variables before the station tile is removed */
+ uint specindex = GetCustomStationSpecIndex(tile);
+ Track track = GetRailStationTrack(tile);
+ Owner owner = GetTileOwner(tile);
+ RailType rt = GetRailType(tile);
+ Train *v = NULL;
+
+ if (HasStationReservation(tile)) {
+ v = GetTrainForReservation(tile, track);
+ if (v != NULL) {
+ /* Free train reservation. */
+ FreeTrainTrackReservation(v);
+ if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false);
+ Vehicle *temp = v;
+ for (; temp->Next() != NULL; temp = temp->Next()) { }
+ if (IsRailStationTile(temp->tile)) SetRailStationPlatformReservation(temp->tile, TrackdirToExitdir(ReverseTrackdir(temp->GetVehicleTrackdir())), false);
+ }
+ }
+
+ bool build_rail = keep_rail && !IsStationTileBlocked(tile);
+ if (!build_rail && !IsStationTileBlocked(tile)) Company::Get(owner)->infrastructure.rail[rt]--;
+
+ DoClearSquare(tile);
+ DeleteNewGRFInspectWindow(GSF_STATIONS, tile);
+ if (build_rail) MakeRailNormal(tile, owner, TrackToTrackBits(track), rt);
+ Company::Get(owner)->infrastructure.station--;
+ DirtyCompanyInfrastructureWindows(owner);
+
+ st->rect.AfterRemoveTile(st, tile);
+ AddTrackToSignalBuffer(tile, track, owner);
+ YapfNotifyTrackLayoutChange(tile, track);
+
+ DeallocateSpecFromStation(st, specindex);
+
+ affected_stations.Include(st);
+
+ if (v != NULL) {
+ /* Restore station reservation. */
+ if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true);
+ TryPathReserve(v, true, true);
+ for (; v->Next() != NULL; v = v->Next()) { }
+ if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true);
+ }
+ }
+ }
+
+ if (quantity == 0) return_cmd_error(STR_ERROR_THERE_IS_NO_STATION);
+
+ for (T **stp = affected_stations.Begin(); stp != affected_stations.End(); stp++) {
+ T *st = *stp;
+
+ /* now we need to make the "spanned" area of the railway station smaller
+ * if we deleted something at the edges.
+ * we also need to adjust train_tile. */
+ MakeRailStationAreaSmaller(st);
+ UpdateStationSignCoord(st);
+
+ /* if we deleted the whole station, delete the train facility. */
+ if (st->train_station.tile == INVALID_TILE) {
+ st->facilities &= ~FACIL_TRAIN;
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS);
+ st->UpdateVirtCoord();
+ DeleteStationIfEmpty(st);
+ }
+ }
+
+ total_cost.AddCost(quantity * removal_cost);
+ return total_cost;
+}
+
+/**
+ * Remove a single tile from a rail station.
+ * This allows for custom-built station with holes and weird layouts
+ * @param start tile of station piece to remove
+ * @param flags operation to perform
+ * @param p1 start_tile
+ * @param p2 various bitstuffed elements
+ * - p2 = bit 0 - if set keep the rail
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdRemoveFromRailStation(TileIndex start, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ TileIndex end = p1 == 0 ? start : p1;
+ if (start >= MapSize() || end >= MapSize()) return CMD_ERROR;
+
+ TileArea ta(start, end);
+ SmallVector<Station *, 4> affected_stations;
+
+ CommandCost ret = RemoveFromRailBaseStation(ta, affected_stations, flags, _price[PR_CLEAR_STATION_RAIL], HasBit(p2, 0));
+ if (ret.Failed()) return ret;
+
+ /* Do all station specific functions here. */
+ for (Station **stp = affected_stations.Begin(); stp != affected_stations.End(); stp++) {
+ Station *st = *stp;
+
+ if (st->train_station.tile == INVALID_TILE) SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS);
+ st->MarkTilesDirty(false);
+ st->RecomputeIndustriesNear();
+ }
+
+ /* Now apply the rail cost to the number that we deleted */
+ return ret;
+}
+
+/**
+ * Remove a single tile from a waypoint.
+ * This allows for custom-built waypoint with holes and weird layouts
+ * @param start tile of waypoint piece to remove
+ * @param flags operation to perform
+ * @param p1 start_tile
+ * @param p2 various bitstuffed elements
+ * - p2 = bit 0 - if set keep the rail
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdRemoveFromRailWaypoint(TileIndex start, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ TileIndex end = p1 == 0 ? start : p1;
+ if (start >= MapSize() || end >= MapSize()) return CMD_ERROR;
+
+ TileArea ta(start, end);
+ SmallVector<Waypoint *, 4> affected_stations;
+
+ return RemoveFromRailBaseStation(ta, affected_stations, flags, _price[PR_CLEAR_WAYPOINT_RAIL], HasBit(p2, 0));
+}
+
+
+/**
+ * Remove a rail station/waypoint
+ * @param st The station/waypoint to remove the rail part from
+ * @param flags operation to perform
+ * @tparam T the type of station to remove
+ * @return cost or failure of operation
+ */
+template <class T>
+CommandCost RemoveRailStation(T *st, DoCommandFlag flags)
+{
+ /* Current company owns the station? */
+ if (_current_company != OWNER_WATER) {
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+ }
+
+ /* determine width and height of platforms */
+ TileArea ta = st->train_station;
+
+ assert(ta.w != 0 && ta.h != 0);
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ /* clear all areas of the station */
+ TILE_AREA_LOOP(tile, ta) {
+ /* only remove tiles that are actually train station tiles */
+ if (!st->TileBelongsToRailStation(tile)) continue;
+
+ CommandCost ret = EnsureNoVehicleOnGround(tile);
+ if (ret.Failed()) return ret;
+
+ cost.AddCost(_price[PR_CLEAR_STATION_RAIL]);
+ if (flags & DC_EXEC) {
+ /* read variables before the station tile is removed */
+ Track track = GetRailStationTrack(tile);
+ Owner owner = GetTileOwner(tile); // _current_company can be OWNER_WATER
+ Train *v = NULL;
+ if (HasStationReservation(tile)) {
+ v = GetTrainForReservation(tile, track);
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
+ if (!IsStationTileBlocked(tile)) Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--;
+ Company::Get(owner)->infrastructure.station--;
+ DoClearSquare(tile);
+ DeleteNewGRFInspectWindow(GSF_STATIONS, tile);
+ AddTrackToSignalBuffer(tile, track, owner);
+ YapfNotifyTrackLayoutChange(tile, track);
+ if (v != NULL) TryPathReserve(v, true);
+ }
+ }
+
+ if (flags & DC_EXEC) {
+ st->rect.AfterRemoveRect(st, st->train_station);
+
+ st->train_station.Clear();
+
+ st->facilities &= ~FACIL_TRAIN;
+
+ free(st->speclist);
+ st->num_specs = 0;
+ st->speclist = NULL;
+ st->cached_anim_triggers = 0;
+
+ DirtyCompanyInfrastructureWindows(st->owner);
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_TRAINS);
+ st->UpdateVirtCoord();
+ DeleteStationIfEmpty(st);
+ }
+
+ return cost;
+}
+
+/**
+ * Remove a rail station
+ * @param tile Tile of the station.
+ * @param flags operation to perform
+ * @return cost or failure of operation
+ */
+static CommandCost RemoveRailStation(TileIndex tile, DoCommandFlag flags)
+{
+ /* if there is flooding, remove platforms tile by tile */
+ if (_current_company == OWNER_WATER) {
+ return DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_FROM_RAIL_STATION);
+ }
+
+ Station *st = Station::GetByTile(tile);
+ CommandCost cost = RemoveRailStation(st, flags);
+
+ if (flags & DC_EXEC) st->RecomputeIndustriesNear();
+
+ return cost;
+}
+
+/**
+ * Remove a rail waypoint
+ * @param tile Tile of the waypoint.
+ * @param flags operation to perform
+ * @return cost or failure of operation
+ */
+static CommandCost RemoveRailWaypoint(TileIndex tile, DoCommandFlag flags)
+{
+ /* if there is flooding, remove waypoints tile by tile */
+ if (_current_company == OWNER_WATER) {
+ return DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_FROM_RAIL_WAYPOINT);
+ }
+
+ return RemoveRailStation(Waypoint::GetByTile(tile), flags);
+}
+
+
+/**
+ * @param truck_station Determines whether a stop is #ROADSTOP_BUS or #ROADSTOP_TRUCK
+ * @param st The Station to do the whole procedure for
+ * @return a pointer to where to link a new RoadStop*
+ */
+static RoadStop **FindRoadStopSpot(bool truck_station, Station *st)
+{
+ RoadStop **primary_stop = (truck_station) ? &st->truck_stops : &st->bus_stops;
+
+ if (*primary_stop == NULL) {
+ /* we have no roadstop of the type yet, so write a "primary stop" */
+ return primary_stop;
+ } else {
+ /* there are stops already, so append to the end of the list */
+ RoadStop *stop = *primary_stop;
+ while (stop->next != NULL) stop = stop->next;
+ return &stop->next;
+ }
+}
+
+static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags);
+
+/**
+ * Find a nearby station that joins this road stop.
+ * @param existing_stop an existing road stop we build over
+ * @param station_to_join the station to join to
+ * @param adjacent whether adjacent stations are allowed
+ * @param ta the area of the newly build station
+ * @param st 'return' pointer for the found station
+ * @return command cost with the error or 'okay'
+ */
+static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID station_to_join, bool adjacent, TileArea ta, Station **st)
+{
+ return FindJoiningBaseStation<Station, STR_ERROR_MUST_REMOVE_ROAD_STOP_FIRST>(existing_stop, station_to_join, adjacent, ta, st);
+}
+
+/**
+ * Build a bus or truck stop.
+ * @param tile Northernmost tile of the stop.
+ * @param flags Operation to perform.
+ * @param p1 bit 0..7: Width of the road stop.
+ * bit 8..15: Length of the road stop.
+ * @param p2 bit 0: 0 For bus stops, 1 for truck stops.
+ * bit 1: 0 For normal stops, 1 for drive-through.
+ * bit 2..3: The roadtypes.
+ * bit 5: Allow stations directly adjacent to other stations.
+ * bit 6..7: Entrance direction (#DiagDirection).
+ * bit 16..31: Station ID to join (NEW_STATION if build new one).
+ * @param text Unused.
+ * @return The cost of this operation or an error.
+ */
+CommandCost CmdBuildRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ bool type = HasBit(p2, 0);
+ bool is_drive_through = HasBit(p2, 1);
+ RoadTypes rts = Extract<RoadTypes, 2, 2>(p2);
+ StationID station_to_join = GB(p2, 16, 16);
+ bool reuse = (station_to_join != NEW_STATION);
+ if (!reuse) station_to_join = INVALID_STATION;
+ bool distant_join = (station_to_join != INVALID_STATION);
+
+ uint8 width = (uint8)GB(p1, 0, 8);
+ uint8 lenght = (uint8)GB(p1, 8, 8);
+
+ /* Check if the requested road stop is too big */
+ if (width > _settings_game.station.station_spread || lenght > _settings_game.station.station_spread) return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
+ /* Check for incorrect width / length. */
+ if (width == 0 || lenght == 0) return CMD_ERROR;
+ /* Check if the first tile and the last tile are valid */
+ if (!IsValidTile(tile) || TileAddWrap(tile, width - 1, lenght - 1) == INVALID_TILE) return CMD_ERROR;
+
+ TileArea roadstop_area(tile, width, lenght);
+
+ if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR;
+
+ if (!HasExactlyOneBit(rts) || !HasRoadTypesAvail(_current_company, rts)) return CMD_ERROR;
+
+ /* Trams only have drive through stops */
+ if (!is_drive_through && HasBit(rts, ROADTYPE_TRAM)) return CMD_ERROR;
+
+ DiagDirection ddir = Extract<DiagDirection, 6, 2>(p2);
+
+ /* Safeguard the parameters. */
+ if (!IsValidDiagDirection(ddir)) return CMD_ERROR;
+ /* If it is a drive-through stop, check for valid axis. */
+ if (is_drive_through && !IsValidAxis((Axis)ddir)) return CMD_ERROR;
+
+ CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags);
+ if (ret.Failed()) return ret;
+
+ /* Total road stop cost. */
+ CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[type ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]);
+ StationID est = INVALID_STATION;
+ ret = CheckFlatLandRoadStop(roadstop_area, flags, is_drive_through ? 5 << ddir : 1 << ddir, is_drive_through, type, DiagDirToAxis(ddir), &est, rts);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+
+ Station *st = NULL;
+ ret = FindJoiningRoadStop(est, station_to_join, HasBit(p2, 5), roadstop_area, &st);
+ if (ret.Failed()) return ret;
+
+ /* Check if this number of road stops can be allocated. */
+ if (!RoadStop::CanAllocateItem(roadstop_area.w * roadstop_area.h)) return_cmd_error(type ? STR_ERROR_TOO_MANY_TRUCK_STOPS : STR_ERROR_TOO_MANY_BUS_STOPS);
+
+ ret = BuildStationPart(&st, flags, reuse, roadstop_area, STATIONNAMING_ROAD);
+ if (ret.Failed()) return ret;
+
+ if (flags & DC_EXEC) {
+ /* Check every tile in the area. */
+ TILE_AREA_LOOP(cur_tile, roadstop_area) {
+ RoadTypes cur_rts = GetRoadTypes(cur_tile);
+ Owner road_owner = HasBit(cur_rts, ROADTYPE_ROAD) ? GetRoadOwner(cur_tile, ROADTYPE_ROAD) : _current_company;
+ Owner tram_owner = HasBit(cur_rts, ROADTYPE_TRAM) ? GetRoadOwner(cur_tile, ROADTYPE_TRAM) : _current_company;
+
+ if (IsTileType(cur_tile, MP_STATION) && IsRoadStop(cur_tile)) {
+ RemoveRoadStop(cur_tile, flags);
+ }
+
+ RoadStop *road_stop = new RoadStop(cur_tile);
+ /* Insert into linked list of RoadStops. */
+ RoadStop **currstop = FindRoadStopSpot(type, st);
+ *currstop = road_stop;
+
+ if (type) {
+ st->truck_station.Add(cur_tile);
+ } else {
+ st->bus_station.Add(cur_tile);
+ }
+
+ /* Initialize an empty station. */
+ st->AddFacility((type) ? FACIL_TRUCK_STOP : FACIL_BUS_STOP, cur_tile);
+
+ st->rect.BeforeAddTile(cur_tile, StationRect::ADD_TRY);
+
+ RoadStopType rs_type = type ? ROADSTOP_TRUCK : ROADSTOP_BUS;
+ if (is_drive_through) {
+ /* Update company infrastructure counts. If the current tile is a normal
+ * road tile, count only the new road bits needed to get a full diagonal road. */
+ RoadType rt;
+ FOR_EACH_SET_ROADTYPE(rt, cur_rts | rts) {
+ Company *c = Company::GetIfValid(rt == ROADTYPE_ROAD ? road_owner : tram_owner);
+ if (c != NULL) {
+ c->infrastructure.road[rt] += 2 - (IsNormalRoadTile(cur_tile) && HasBit(cur_rts, rt) ? CountBits(GetRoadBits(cur_tile, rt)) : 0);
+ DirtyCompanyInfrastructureWindows(c->index);
+ }
+ }
+
+ MakeDriveThroughRoadStop(cur_tile, st->owner, road_owner, tram_owner, st->index, rs_type, rts | cur_rts, DiagDirToAxis(ddir));
+ road_stop->MakeDriveThrough();
+ } else {
+ /* Non-drive-through stop never overbuild and always count as two road bits. */
+ Company::Get(st->owner)->infrastructure.road[FIND_FIRST_BIT(rts)] += 2;
+ MakeRoadStop(cur_tile, st->owner, st->index, rs_type, rts, ddir);
+ }
+ Company::Get(st->owner)->infrastructure.station++;
+ DirtyCompanyInfrastructureWindows(st->owner);
+
+ MarkTileDirtyByTile(cur_tile);
+ }
+ }
+
+ if (st != NULL) {
+ st->UpdateVirtCoord();
+ UpdateStationAcceptance(st, false);
+ st->RecomputeIndustriesNear();
+ InvalidateWindowData(WC_SELECT_STATION, 0, 0);
+ InvalidateWindowData(WC_STATION_LIST, st->owner, 0);
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_ROADVEHS);
+ }
+ return cost;
+}
+
+
+static Vehicle *ClearRoadStopStatusEnum(Vehicle *v, void *)
+{
+ if (v->type == VEH_ROAD) {
+ /* Okay... we are a road vehicle on a drive through road stop.
+ * But that road stop has just been removed, so we need to make
+ * sure we are in a valid state... however, vehicles can also
+ * turn on road stop tiles, so only clear the 'road stop' state
+ * bits and only when the state was 'in road stop', otherwise
+ * we'll end up clearing the turn around bits. */
+ RoadVehicle *rv = RoadVehicle::From(v);
+ if (HasBit(rv->state, RVS_IN_DT_ROAD_STOP)) rv->state &= RVSB_ROAD_STOP_TRACKDIR_MASK;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Remove a bus station/truck stop
+ * @param tile TileIndex been queried
+ * @param flags operation to perform
+ * @return cost or failure of operation
+ */
+static CommandCost RemoveRoadStop(TileIndex tile, DoCommandFlag flags)
+{
+ Station *st = Station::GetByTile(tile);
+
+ if (_current_company != OWNER_WATER) {
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+ }
+
+ bool is_truck = IsTruckStop(tile);
+
+ RoadStop **primary_stop;
+ RoadStop *cur_stop;
+ if (is_truck) { // truck stop
+ primary_stop = &st->truck_stops;
+ cur_stop = RoadStop::GetByTile(tile, ROADSTOP_TRUCK);
+ } else {
+ primary_stop = &st->bus_stops;
+ cur_stop = RoadStop::GetByTile(tile, ROADSTOP_BUS);
+ }
+
+ assert(cur_stop != NULL);
+
+ /* don't do the check for drive-through road stops when company bankrupts */
+ if (IsDriveThroughStopTile(tile) && (flags & DC_BANKRUPT)) {
+ /* remove the 'going through road stop' status from all vehicles on that tile */
+ if (flags & DC_EXEC) FindVehicleOnPos(tile, NULL, &ClearRoadStopStatusEnum);
+ } else {
+ CommandCost ret = EnsureNoVehicleOnGround(tile);
+ if (ret.Failed()) return ret;
+ }
+
+ if (flags & DC_EXEC) {
+ if (*primary_stop == cur_stop) {
+ /* removed the first stop in the list */
+ *primary_stop = cur_stop->next;
+ /* removed the only stop? */
+ if (*primary_stop == NULL) {
+ st->facilities &= (is_truck ? ~FACIL_TRUCK_STOP : ~FACIL_BUS_STOP);
+ }
+ } else {
+ /* tell the predecessor in the list to skip this stop */
+ RoadStop *pred = *primary_stop;
+ while (pred->next != cur_stop) pred = pred->next;
+ pred->next = cur_stop->next;
+ }
+
+ /* Update company infrastructure counts. */
+ RoadType rt;
+ FOR_EACH_SET_ROADTYPE(rt, GetRoadTypes(tile)) {
+ Company *c = Company::GetIfValid(GetRoadOwner(tile, rt));
+ if (c != NULL) {
+ c->infrastructure.road[rt] -= 2;
+ DirtyCompanyInfrastructureWindows(c->index);
+ }
+ }
+ Company::Get(st->owner)->infrastructure.station--;
+
+ if (IsDriveThroughStopTile(tile)) {
+ /* Clears the tile for us */
+ cur_stop->ClearDriveThrough();
+ } else {
+ DoClearSquare(tile);
+ }
+
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_ROADVEHS);
+ delete cur_stop;
+
+ /* Make sure no vehicle is going to the old roadstop */
+ RoadVehicle *v;
+ FOR_ALL_ROADVEHICLES(v) {
+ if (v->First() == v && v->current_order.IsType(OT_GOTO_STATION) &&
+ v->dest_tile == tile) {
+ v->dest_tile = v->GetOrderStationLocation(st->index);
+ }
+ }
+
+ st->rect.AfterRemoveTile(st, tile);
+
+ st->UpdateVirtCoord();
+ st->RecomputeIndustriesNear();
+ DeleteStationIfEmpty(st);
+
+ /* Update the tile area of the truck/bus stop */
+ if (is_truck) {
+ st->truck_station.Clear();
+ for (const RoadStop *rs = st->truck_stops; rs != NULL; rs = rs->next) st->truck_station.Add(rs->xy);
+ } else {
+ st->bus_station.Clear();
+ for (const RoadStop *rs = st->bus_stops; rs != NULL; rs = rs->next) st->bus_station.Add(rs->xy);
+ }
+ }
+
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[is_truck ? PR_CLEAR_STATION_TRUCK : PR_CLEAR_STATION_BUS]);
+}
+
+/**
+ * Remove bus or truck stops.
+ * @param tile Northernmost tile of the removal area.
+ * @param flags Operation to perform.
+ * @param p1 bit 0..7: Width of the removal area.
+ * bit 8..15: Height of the removal area.
+ * @param p2 bit 0: 0 For bus stops, 1 for truck stops.
+ * @param text Unused.
+ * @return The cost of this operation or an error.
+ */
+CommandCost CmdRemoveRoadStop(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ uint8 width = (uint8)GB(p1, 0, 8);
+ uint8 height = (uint8)GB(p1, 8, 8);
+
+ /* Check for incorrect width / height. */
+ if (width == 0 || height == 0) return CMD_ERROR;
+ /* Check if the first tile and the last tile are valid */
+ if (!IsValidTile(tile) || TileAddWrap(tile, width - 1, height - 1) == INVALID_TILE) return CMD_ERROR;
+
+ TileArea roadstop_area(tile, width, height);
+
+ int quantity = 0;
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ TILE_AREA_LOOP(cur_tile, roadstop_area) {
+ /* Make sure the specified tile is a road stop of the correct type */
+ if (!IsTileType(cur_tile, MP_STATION) || !IsRoadStop(cur_tile) || (uint32)GetRoadStopType(cur_tile) != GB(p2, 0, 1)) continue;
+
+ /* Save the stop info before it is removed */
+ bool is_drive_through = IsDriveThroughStopTile(cur_tile);
+ RoadTypes rts = GetRoadTypes(cur_tile);
+ RoadBits road_bits = IsDriveThroughStopTile(cur_tile) ?
+ ((GetRoadStopDir(cur_tile) == DIAGDIR_NE) ? ROAD_X : ROAD_Y) :
+ DiagDirToRoadBits(GetRoadStopDir(cur_tile));
+
+ Owner road_owner = GetRoadOwner(cur_tile, ROADTYPE_ROAD);
+ Owner tram_owner = GetRoadOwner(cur_tile, ROADTYPE_TRAM);
+ CommandCost ret = RemoveRoadStop(cur_tile, flags);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+
+ quantity++;
+ /* If the stop was a drive-through stop replace the road */
+ if ((flags & DC_EXEC) && is_drive_through) {
+ MakeRoadNormal(cur_tile, road_bits, rts, ClosestTownFromTile(cur_tile, UINT_MAX)->index,
+ road_owner, tram_owner);
+
+ /* Update company infrastructure counts. */
+ RoadType rt;
+ FOR_EACH_SET_ROADTYPE(rt, rts) {
+ Company *c = Company::GetIfValid(GetRoadOwner(cur_tile, rt));
+ if (c != NULL) {
+ c->infrastructure.road[rt] += CountBits(road_bits);
+ DirtyCompanyInfrastructureWindows(c->index);
+ }
+ }
+ }
+ }
+
+ if (quantity == 0) return_cmd_error(STR_ERROR_THERE_IS_NO_STATION);
+
+ return cost;
+}
+
+/**
+ * Computes the minimal distance from town's xy to any airport's tile.
+ * @param it An iterator over all airport tiles.
+ * @param town_tile town's tile (t->xy)
+ * @return minimal manhattan distance from town_tile to any airport's tile
+ */
+static uint GetMinimalAirportDistanceToTile(TileIterator &it, TileIndex town_tile)
+{
+ uint mindist = UINT_MAX;
+
+ for (TileIndex cur_tile = it; cur_tile != INVALID_TILE; cur_tile = ++it) {
+ mindist = min(mindist, DistanceManhattan(town_tile, cur_tile));
+ }
+
+ return mindist;
+}
+
+/**
+ * Get a possible noise reduction factor based on distance from town center.
+ * The further you get, the less noise you generate.
+ * So all those folks at city council can now happily slee... work in their offices
+ * @param as airport information
+ * @param it An iterator over all airport tiles.
+ * @param town_tile TileIndex of town's center, the one who will receive the airport's candidature
+ * @return the noise that will be generated, according to distance
+ */
+uint8 GetAirportNoiseLevelForTown(const AirportSpec *as, TileIterator &it, TileIndex town_tile)
+{
+ /* 0 cannot be accounted, and 1 is the lowest that can be reduced from town.
+ * So no need to go any further*/
+ if (as->noise_level < 2) return as->noise_level;
+
+ uint distance = GetMinimalAirportDistanceToTile(it, town_tile);
+
+ /* The steps for measuring noise reduction are based on the "magical" (and arbitrary) 8 base distance
+ * adding the town_council_tolerance 4 times, as a way to graduate, depending of the tolerance.
+ * Basically, it says that the less tolerant a town is, the bigger the distance before
+ * an actual decrease can be granted */
+ uint8 town_tolerance_distance = 8 + (_settings_game.difficulty.town_council_tolerance * 4);
+
+ /* now, we want to have the distance segmented using the distance judged bareable by town
+ * This will give us the coefficient of reduction the distance provides. */
+ uint noise_reduction = distance / town_tolerance_distance;
+
+ /* If the noise reduction equals the airport noise itself, don't give it for free.
+ * Otherwise, simply reduce the airport's level. */
+ return noise_reduction >= as->noise_level ? 1 : as->noise_level - noise_reduction;
+}
+
+/**
+ * Finds the town nearest to given airport. Based on minimal manhattan distance to any airport's tile.
+ * If two towns have the same distance, town with lower index is returned.
+ * @param as airport's description
+ * @param it An iterator over all airport tiles
+ * @return nearest town to airport
+ */
+Town *AirportGetNearestTown(const AirportSpec *as, const TileIterator &it)
+{
+ Town *t, *nearest = NULL;
+ uint add = as->size_x + as->size_y - 2; // GetMinimalAirportDistanceToTile can differ from DistanceManhattan by this much
+ uint mindist = UINT_MAX - add; // prevent overflow
+ FOR_ALL_TOWNS(t) {
+ if (DistanceManhattan(t->xy, it) < mindist + add) { // avoid calling GetMinimalAirportDistanceToTile too often
+ TileIterator *copy = it.Clone();
+ uint dist = GetMinimalAirportDistanceToTile(*copy, t->xy);
+ delete copy;
+ if (dist < mindist) {
+ nearest = t;
+ mindist = dist;
+ }
+ }
+ }
+
+ return nearest;
+}
+
+
+/** Recalculate the noise generated by the airports of each town */
+void UpdateAirportsNoise()
+{
+ Town *t;
+ const Station *st;
+
+ FOR_ALL_TOWNS(t) t->noise_reached = 0;
+
+ FOR_ALL_STATIONS(st) {
+ if (st->airport.tile != INVALID_TILE && st->airport.type != AT_OILRIG) {
+ const AirportSpec *as = st->airport.GetSpec();
+ AirportTileIterator it(st);
+ Town *nearest = AirportGetNearestTown(as, it);
+ nearest->noise_reached += GetAirportNoiseLevelForTown(as, it, nearest->xy);
+ }
+ }
+}
+
+/**
+ * Place an Airport.
+ * @param tile tile where airport will be built
+ * @param flags operation to perform
+ * @param p1
+ * - p1 = (bit 0- 7) - airport type, @see airport.h
+ * - p1 = (bit 8-15) - airport layout
+ * @param p2 various bitstuffed elements
+ * - p2 = (bit 0) - allow airports directly adjacent to other airports.
+ * - p2 = (bit 16-31) - station ID to join (NEW_STATION if build new one)
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdBuildAirport(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ StationID station_to_join = GB(p2, 16, 16);
+ bool reuse = (station_to_join != NEW_STATION);
+ if (!reuse) station_to_join = INVALID_STATION;
+ bool distant_join = (station_to_join != INVALID_STATION);
+ byte airport_type = GB(p1, 0, 8);
+ byte layout = GB(p1, 8, 8);
+
+ if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR;
+
+ if (airport_type >= NUM_AIRPORTS) return CMD_ERROR;
+
+ CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags);
+ if (ret.Failed()) return ret;
+
+ /* Check if a valid, buildable airport was chosen for construction */
+ const AirportSpec *as = AirportSpec::Get(airport_type);
+ if (!as->IsAvailable() || layout >= as->num_table) return CMD_ERROR;
+
+ Direction rotation = as->rotation[layout];
+ int w = as->size_x;
+ int h = as->size_y;
+ if (rotation == DIR_E || rotation == DIR_W) Swap(w, h);
+ TileArea airport_area = TileArea(tile, w, h);
+
+ if (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread) {
+ return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
+ }
+
+ CommandCost cost = CheckFlatLand(airport_area, flags);
+ if (cost.Failed()) return cost;
+
+ /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. */
+ AirportTileTableIterator iter(as->table[layout], tile);
+ Town *nearest = AirportGetNearestTown(as, iter);
+ uint newnoise_level = GetAirportNoiseLevelForTown(as, iter, nearest->xy);
+
+ /* Check if local auth would allow a new airport */
+ StringID authority_refuse_message = STR_NULL;
+ Town *authority_refuse_town = NULL;
+
+ if (_settings_game.economy.station_noise_level) {
+ /* do not allow to build a new airport if this raise the town noise over the maximum allowed by town */
+ if ((nearest->noise_reached + newnoise_level) > nearest->MaxTownNoise()) {
+ authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE;
+ authority_refuse_town = nearest;
+ }
+ } else {
+ Town *t = ClosestTownFromTile(tile, UINT_MAX);
+ uint num = 0;
+ const Station *st;
+ FOR_ALL_STATIONS(st) {
+ if (st->town == t && (st->facilities & FACIL_AIRPORT) && st->airport.type != AT_OILRIG) num++;
+ }
+ if (num >= 2) {
+ authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT;
+ authority_refuse_town = t;
+ }
+ }
+
+ if (authority_refuse_message != STR_NULL) {
+ SetDParam(0, authority_refuse_town->index);
+ return_cmd_error(authority_refuse_message);
+ }
+
+ Station *st = NULL;
+ ret = FindJoiningStation(INVALID_STATION, station_to_join, HasBit(p2, 0), airport_area, &st);
+ if (ret.Failed()) return ret;
+
+ /* Distant join */
+ if (st == NULL && distant_join) st = Station::GetIfValid(station_to_join);
+
+ ret = BuildStationPart(&st, flags, reuse, airport_area, (GetAirport(airport_type)->flags & AirportFTAClass::AIRPLANES) ? STATIONNAMING_AIRPORT : STATIONNAMING_HELIPORT);
+ if (ret.Failed()) return ret;
+
+ if (st != NULL && st->airport.tile != INVALID_TILE) {
+ return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT);
+ }
+
+ for (AirportTileTableIterator iter(as->table[layout], tile); iter != INVALID_TILE; ++iter) {
+ cost.AddCost(_price[PR_BUILD_STATION_AIRPORT]);
+ }
+
+ if (flags & DC_EXEC) {
+ /* Always add the noise, so there will be no need to recalculate when option toggles */
+ nearest->noise_reached += newnoise_level;
+
+ st->AddFacility(FACIL_AIRPORT, tile);
+ st->airport.type = airport_type;
+ st->airport.layout = layout;
+ st->airport.flags = 0;
+ st->airport.rotation = rotation;
+
+ st->rect.BeforeAddRect(tile, w, h, StationRect::ADD_TRY);
+
+ for (AirportTileTableIterator iter(as->table[layout], tile); iter != INVALID_TILE; ++iter) {
+ MakeAirport(iter, st->owner, st->index, iter.GetStationGfx(), WATER_CLASS_INVALID);
+ SetStationTileRandomBits(iter, GB(Random(), 0, 4));
+ st->airport.Add(iter);
+
+ if (AirportTileSpec::Get(GetTranslatedAirportTileID(iter.GetStationGfx()))->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(iter);
+ }
+
+ /* Only call the animation trigger after all tiles have been built */
+ for (AirportTileTableIterator iter(as->table[layout], tile); iter != INVALID_TILE; ++iter) {
+ AirportTileAnimationTrigger(st, iter, AAT_BUILT);
+ }
+
+ UpdateAirplanesOnNewStation(st);
+
+ Company::Get(st->owner)->infrastructure.airport++;
+ DirtyCompanyInfrastructureWindows(st->owner);
+
+ st->UpdateVirtCoord();
+ UpdateStationAcceptance(st, false);
+ st->RecomputeIndustriesNear();
+ InvalidateWindowData(WC_SELECT_STATION, 0, 0);
+ InvalidateWindowData(WC_STATION_LIST, st->owner, 0);
+ InvalidateWindowData(WC_STATION_VIEW, st->index);
+
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, st->town->index);
+ }
+ }
+
+ return cost;
+}
+
+/**
+ * Remove an airport
+ * @param tile TileIndex been queried
+ * @param flags operation to perform
+ * @return cost or failure of operation
+ */
+static CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags)
+{
+ Station *st = Station::GetByTile(tile);
+
+ if (_current_company != OWNER_WATER) {
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+ }
+
+ tile = st->airport.tile;
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+
+ const Aircraft *a;
+ FOR_ALL_AIRCRAFT(a) {
+ if (!a->IsNormalAircraft()) continue;
+ if (a->targetairport == st->index && a->state != FLYING) return CMD_ERROR;
+ }
+
+ if (flags & DC_EXEC) {
+ const AirportSpec *as = st->airport.GetSpec();
+ /* The noise level is the noise from the airport and reduce it to account for the distance to the town center.
+ * And as for construction, always remove it, even if the setting is not set, in order to avoid the
+ * need of recalculation */
+ AirportTileIterator it(st);
+ Town *nearest = AirportGetNearestTown(as, it);
+ nearest->noise_reached -= GetAirportNoiseLevelForTown(as, it, nearest->xy);
+ }
+
+ TILE_AREA_LOOP(tile_cur, st->airport) {
+ if (!st->TileBelongsToAirport(tile_cur)) continue;
+
+ CommandCost ret = EnsureNoVehicleOnGround(tile_cur);
+ if (ret.Failed()) return ret;
+
+ cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT]);
+
+ if (flags & DC_EXEC) {
+ if (IsHangarTile(tile_cur)) OrderBackup::Reset(tile_cur, false);
+ DeleteAnimatedTile(tile_cur);
+ DoClearSquare(tile_cur);
+ DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile_cur);
+ }
+ }
+
+ if (flags & DC_EXEC) {
+ /* Clear the persistent storage. */
+ delete st->airport.psa;
+
+ for (uint i = 0; i < st->airport.GetNumHangars(); ++i) {
+ DeleteWindowById(
+ WC_VEHICLE_DEPOT, st->airport.GetHangarTile(i)
+ );
+ }
+
+ st->rect.AfterRemoveRect(st, st->airport);
+
+ st->airport.Clear();
+ st->facilities &= ~FACIL_AIRPORT;
+
+ InvalidateWindowData(WC_STATION_VIEW, st->index);
+
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, st->town->index);
+ }
+
+ Company::Get(st->owner)->infrastructure.airport--;
+ DirtyCompanyInfrastructureWindows(st->owner);
+
+ st->UpdateVirtCoord();
+ st->RecomputeIndustriesNear();
+ DeleteStationIfEmpty(st);
+ DeleteNewGRFInspectWindow(GSF_AIRPORTS, st->index);
+ }
+
+ return cost;
+}
+
+/**
+ * Open/close an airport to incoming aircraft.
+ * @param tile Unused.
+ * @param flags Operation to perform.
+ * @param p1 Station ID of the airport.
+ * @param p2 Unused.
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdOpenCloseAirport(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ if (!Station::IsValidID(p1)) return CMD_ERROR;
+ Station *st = Station::Get(p1);
+
+ if (!(st->facilities & FACIL_AIRPORT) || st->owner == OWNER_NONE) return CMD_ERROR;
+
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+
+ if (flags & DC_EXEC) {
+ st->airport.flags ^= AIRPORT_CLOSED_block;
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_CLOSE_AIRPORT);
+ }
+ return CommandCost();
+}
+
+/**
+ * Tests whether the company's vehicles have this station in orders
+ * @param station station ID
+ * @param include_company If true only check vehicles of \a company, if false only check vehicles of other companies
+ * @param company company ID
+ */
+bool HasStationInUse(StationID station, bool include_company, CompanyID company)
+{
+ const Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if ((v->owner == company) == include_company) {
+ const Order *order;
+ FOR_VEHICLE_ORDERS(v, order) {
+ if ((order->IsType(OT_GOTO_STATION) || order->IsType(OT_GOTO_WAYPOINT)) && order->GetDestination() == station) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static const TileIndexDiffC _dock_tileoffs_chkaround[] = {
+ {-1, 0},
+ { 0, 0},
+ { 0, 0},
+ { 0, -1}
+};
+static const byte _dock_w_chk[4] = { 2, 1, 2, 1 };
+static const byte _dock_h_chk[4] = { 1, 2, 1, 2 };
+
+/**
+ * Build a dock/haven.
+ * @param tile tile where dock will be built
+ * @param flags operation to perform
+ * @param p1 (bit 0) - allow docks directly adjacent to other docks.
+ * @param p2 bit 16-31: station ID to join (NEW_STATION if build new one)
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdBuildDock(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ StationID station_to_join = GB(p2, 16, 16);
+ bool reuse = (station_to_join != NEW_STATION);
+ if (!reuse) station_to_join = INVALID_STATION;
+ bool distant_join = (station_to_join != INVALID_STATION);
+
+ if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR;
+
+ DiagDirection direction = GetInclinedSlopeDirection(GetTileSlope(tile));
+ if (direction == INVALID_DIAGDIR) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
+ direction = ReverseDiagDir(direction);
+
+ /* Docks cannot be placed on rapids */
+ if (HasTileWaterGround(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
+
+ CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags);
+ if (ret.Failed()) return ret;
+
+ if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
+
+ ret = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
+ if (ret.Failed()) return ret;
+
+ TileIndex tile_cur = tile + TileOffsByDiagDir(direction);
+
+ if (!IsTileType(tile_cur, MP_WATER) || GetTileSlope(tile_cur) != SLOPE_FLAT) {
+ return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
+ }
+
+ if (MayHaveBridgeAbove(tile_cur) && IsBridgeAbove(tile_cur)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
+
+ /* Get the water class of the water tile before it is cleared.*/
+ WaterClass wc = GetWaterClass(tile_cur);
+
+ ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
+ if (ret.Failed()) return ret;
+
+ tile_cur += TileOffsByDiagDir(direction);
+ if (!IsTileType(tile_cur, MP_WATER) || GetTileSlope(tile_cur) != SLOPE_FLAT) {
+ return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
+ }
+
+ TileArea dock_area = TileArea(tile + ToTileIndexDiff(_dock_tileoffs_chkaround[direction]),
+ _dock_w_chk[direction], _dock_h_chk[direction]);
+
+ /* middle */
+ Station *st = NULL;
+ ret = FindJoiningStation(INVALID_STATION, station_to_join, HasBit(p1, 0), dock_area, &st);
+ if (ret.Failed()) return ret;
+
+ /* Distant join */
+ if (st == NULL && distant_join) st = Station::GetIfValid(station_to_join);
+
+ ret = BuildStationPart(&st, flags, reuse, dock_area, STATIONNAMING_DOCK);
+ if (ret.Failed()) return ret;
+
+ if (st != NULL && st->dock_tile != INVALID_TILE) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_DOCK);
+
+ if (flags & DC_EXEC) {
+ st->dock_tile = tile;
+ st->AddFacility(FACIL_DOCK, tile);
+
+ st->rect.BeforeAddRect(dock_area.tile, dock_area.w, dock_area.h, StationRect::ADD_TRY);
+
+ /* If the water part of the dock is on a canal, update infrastructure counts.
+ * This is needed as we've unconditionally cleared that tile before. */
+ if (wc == WATER_CLASS_CANAL) {
+ Company::Get(st->owner)->infrastructure.water++;
+ }
+ Company::Get(st->owner)->infrastructure.station += 2;
+ DirtyCompanyInfrastructureWindows(st->owner);
+
+ MakeDock(tile, st->owner, st->index, direction, wc);
+
+ st->UpdateVirtCoord();
+ UpdateStationAcceptance(st, false);
+ st->RecomputeIndustriesNear();
+ InvalidateWindowData(WC_SELECT_STATION, 0, 0);
+ InvalidateWindowData(WC_STATION_LIST, st->owner, 0);
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_SHIPS);
+ }
+
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_STATION_DOCK]);
+}
+
+/**
+ * Remove a dock
+ * @param tile TileIndex been queried
+ * @param flags operation to perform
+ * @return cost or failure of operation
+ */
+static CommandCost RemoveDock(TileIndex tile, DoCommandFlag flags)
+{
+ Station *st = Station::GetByTile(tile);
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+
+ TileIndex docking_location = TILE_ADD(st->dock_tile, ToTileIndexDiff(GetDockOffset(st->dock_tile)));
+
+ TileIndex tile1 = st->dock_tile;
+ TileIndex tile2 = tile1 + TileOffsByDiagDir(GetDockDirection(tile1));
+
+ ret = EnsureNoVehicleOnGround(tile1);
+ if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile2);
+ if (ret.Failed()) return ret;
+
+ if (flags & DC_EXEC) {
+ DoClearSquare(tile1);
+ MarkTileDirtyByTile(tile1);
+ MakeWaterKeepingClass(tile2, st->owner);
+
+ st->rect.AfterRemoveTile(st, tile1);
+ st->rect.AfterRemoveTile(st, tile2);
+
+ st->dock_tile = INVALID_TILE;
+ st->facilities &= ~FACIL_DOCK;
+
+ Company::Get(st->owner)->infrastructure.station -= 2;
+ DirtyCompanyInfrastructureWindows(st->owner);
+
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_SHIPS);
+ st->UpdateVirtCoord();
+ st->RecomputeIndustriesNear();
+ DeleteStationIfEmpty(st);
+
+ /* All ships that were going to our station, can't go to it anymore.
+ * Just clear the order, then automatically the next appropriate order
+ * will be selected and in case of no appropriate order it will just
+ * wander around the world. */
+ Ship *s;
+ FOR_ALL_SHIPS(s) {
+ if (s->current_order.IsType(OT_LOADING) && s->tile == docking_location) {
+ s->LeaveStation();
+ }
+
+ if (s->dest_tile == docking_location) {
+ s->dest_tile = 0;
+ s->current_order.Free();
+ }
+ }
+ }
+
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_STATION_DOCK]);
+}
+
+#include "table/station_land.h"
+
+const DrawTileSprites *GetStationTileLayout(StationType st, byte gfx)
+{
+ return &_station_display_datas[st][gfx];
+}
+
+/**
+ * Check whether a sprite is a track sprite, which can be replaced by a non-track ground sprite and a rail overlay.
+ * If the ground sprite is suitable, \a ground is replaced with the new non-track ground sprite, and \a overlay_offset
+ * is set to the overlay to draw.
+ * @param ti Positional info for the tile to decide snowyness etc. May be NULL.
+ * @param [in,out] ground Groundsprite to draw.
+ * @param [out] overlay_offset Overlay to draw.
+ * @return true if overlay can be drawn.
+ */
+bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrackOffset *overlay_offset)
+{
+ bool snow_desert;
+ switch (*ground) {
+ case SPR_RAIL_TRACK_X:
+ snow_desert = false;
+ *overlay_offset = RTO_X;
+ break;
+
+ case SPR_RAIL_TRACK_Y:
+ snow_desert = false;
+ *overlay_offset = RTO_Y;
+ break;
+
+ case SPR_RAIL_TRACK_X_SNOW:
+ snow_desert = true;
+ *overlay_offset = RTO_X;
+ break;
+
+ case SPR_RAIL_TRACK_Y_SNOW:
+ snow_desert = true;
+ *overlay_offset = RTO_Y;
+ break;
+
+ default:
+ return false;
+ }
+
+ if (ti != NULL) {
+ /* Decide snow/desert from tile */
+ switch (_settings_game.game_creation.landscape) {
+ case LT_ARCTIC:
+ snow_desert = (uint)ti->z > GetSnowLine() * TILE_HEIGHT;
+ break;
+
+ case LT_TROPIC:
+ snow_desert = GetTropicZone(ti->tile) == TROPICZONE_DESERT;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *ground = snow_desert ? SPR_FLAT_SNOW_DESERT_TILE : SPR_FLAT_GRASS_TILE;
+ return true;
+}
+
+static void DrawTile_Station(TileInfo *ti)
+{
+ const NewGRFSpriteLayout *layout = NULL;
+ DrawTileSprites tmp_rail_layout;
+ const DrawTileSprites *t = NULL;
+ RoadTypes roadtypes;
+ int32 total_offset;
+ const RailtypeInfo *rti = NULL;
+ uint32 relocation = 0;
+ uint32 ground_relocation = 0;
+ BaseStation *st = NULL;
+ const StationSpec *statspec = NULL;
+ uint tile_layout = 0;
+
+ if (HasStationRail(ti->tile)) {
+ rti = GetRailTypeInfo(GetRailType(ti->tile));
+ roadtypes = ROADTYPES_NONE;
+ total_offset = rti->GetRailtypeSpriteOffset();
+
+ if (IsCustomStationSpecIndex(ti->tile)) {
+ /* look for customization */
+ st = BaseStation::GetByTile(ti->tile);
+ statspec = st->speclist[GetCustomStationSpecIndex(ti->tile)].spec;
+
+ if (statspec != NULL) {
+ tile_layout = GetStationGfx(ti->tile);
+
+ if (HasBit(statspec->callback_mask, CBM_STATION_SPRITE_LAYOUT)) {
+ uint16 callback = GetStationCallback(CBID_STATION_SPRITE_LAYOUT, 0, 0, statspec, st, ti->tile);
+ if (callback != CALLBACK_FAILED) tile_layout = (callback & ~1) + GetRailStationAxis(ti->tile);
+ }
+
+ /* Ensure the chosen tile layout is valid for this custom station */
+ if (statspec->renderdata != NULL) {
+ layout = &statspec->renderdata[tile_layout < statspec->tiles ? tile_layout : (uint)GetRailStationAxis(ti->tile)];
+ if (!layout->NeedsPreprocessing()) {
+ t = layout;
+ layout = NULL;
+ }
+ }
+ }
+ }
+ } else {
+ roadtypes = IsRoadStop(ti->tile) ? GetRoadTypes(ti->tile) : ROADTYPES_NONE;
+ total_offset = 0;
+ }
+
+ StationGfx gfx = GetStationGfx(ti->tile);
+ if (IsAirport(ti->tile)) {
+ gfx = GetAirportGfx(ti->tile);
+ if (gfx >= NEW_AIRPORTTILE_OFFSET) {
+ const AirportTileSpec *ats = AirportTileSpec::Get(gfx);
+ if (ats->grf_prop.spritegroup[0] != NULL && DrawNewAirportTile(ti, Station::GetByTile(ti->tile), gfx, ats)) {
+ return;
+ }
+ /* No sprite group (or no valid one) found, meaning no graphics associated.
+ * Use the substitute one instead */
+ assert(ats->grf_prop.subst_id != INVALID_AIRPORTTILE);
+ gfx = ats->grf_prop.subst_id;
+ }
+ switch (gfx) {
+ case APT_RADAR_GRASS_FENCE_SW:
+ t = &_station_display_datas_airport_radar_grass_fence_sw[GetAnimationFrame(ti->tile)];
+ break;
+ case APT_GRASS_FENCE_NE_FLAG:
+ t = &_station_display_datas_airport_flag_grass_fence_ne[GetAnimationFrame(ti->tile)];
+ break;
+ case APT_RADAR_FENCE_SW:
+ t = &_station_display_datas_airport_radar_fence_sw[GetAnimationFrame(ti->tile)];
+ break;
+ case APT_RADAR_FENCE_NE:
+ t = &_station_display_datas_airport_radar_fence_ne[GetAnimationFrame(ti->tile)];
+ break;
+ case APT_GRASS_FENCE_NE_FLAG_2:
+ t = &_station_display_datas_airport_flag_grass_fence_ne_2[GetAnimationFrame(ti->tile)];
+ break;
+ }
+ }
+
+ Owner owner = GetTileOwner(ti->tile);
+
+ PaletteID palette;
+ if (Company::IsValidID(owner)) {
+ palette = COMPANY_SPRITE_COLOUR(owner);
+ } else {
+ /* Some stations are not owner by a company, namely oil rigs */
+ palette = PALETTE_TO_GREY;
+ }
+
+ if (layout == NULL && (t == NULL || t->seq == NULL)) t = GetStationTileLayout(GetStationType(ti->tile), gfx);
+
+ /* don't show foundation for docks */
+ if (ti->tileh != SLOPE_FLAT && !IsDock(ti->tile)) {
+ if (statspec != NULL && HasBit(statspec->flags, SSF_CUSTOM_FOUNDATIONS)) {
+ /* Station has custom foundations.
+ * Check whether the foundation continues beyond the tile's upper sides. */
+ uint edge_info = 0;
+ int z;
+ Slope slope = GetFoundationPixelSlope(ti->tile, &z);
+ if (!HasFoundationNW(ti->tile, slope, z)) SetBit(edge_info, 0);
+ if (!HasFoundationNE(ti->tile, slope, z)) SetBit(edge_info, 1);
+ SpriteID image = GetCustomStationFoundationRelocation(statspec, st, ti->tile, tile_layout, edge_info);
+ if (image == 0) goto draw_default_foundation;
+
+ if (HasBit(statspec->flags, SSF_EXTENDED_FOUNDATIONS)) {
+ /* Station provides extended foundations. */
+
+ static const uint8 foundation_parts[] = {
+ 0, 0, 0, 0, // Invalid, Invalid, Invalid, SLOPE_SW
+ 0, 1, 2, 3, // Invalid, SLOPE_EW, SLOPE_SE, SLOPE_WSE
+ 0, 4, 5, 6, // Invalid, SLOPE_NW, SLOPE_NS, SLOPE_NWS
+ 7, 8, 9 // SLOPE_NE, SLOPE_ENW, SLOPE_SEN
+ };
+
+ AddSortableSpriteToDraw(image + foundation_parts[ti->tileh], PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z);
+ } else {
+ /* Draw simple foundations, built up from 8 possible foundation sprites. */
+
+ /* Each set bit represents one of the eight composite sprites to be drawn.
+ * 'Invalid' entries will not drawn but are included for completeness. */
+ static const uint8 composite_foundation_parts[] = {
+ /* Invalid (00000000), Invalid (11010001), Invalid (11100100), SLOPE_SW (11100000) */
+ 0x00, 0xD1, 0xE4, 0xE0,
+ /* Invalid (11001010), SLOPE_EW (11001001), SLOPE_SE (11000100), SLOPE_WSE (11000000) */
+ 0xCA, 0xC9, 0xC4, 0xC0,
+ /* Invalid (11010010), SLOPE_NW (10010001), SLOPE_NS (11100100), SLOPE_NWS (10100000) */
+ 0xD2, 0x91, 0xE4, 0xA0,
+ /* SLOPE_NE (01001010), SLOPE_ENW (00001001), SLOPE_SEN (01000100) */
+ 0x4A, 0x09, 0x44
+ };
+
+ uint8 parts = composite_foundation_parts[ti->tileh];
+
+ /* If foundations continue beyond the tile's upper sides then
+ * mask out the last two pieces. */
+ if (HasBit(edge_info, 0)) ClrBit(parts, 6);
+ if (HasBit(edge_info, 1)) ClrBit(parts, 7);
+
+ if (parts == 0) {
+ /* We always have to draw at least one sprite to make sure there is a boundingbox and a sprite with the
+ * correct offset for the childsprites.
+ * So, draw the (completely empty) sprite of the default foundations. */
+ goto draw_default_foundation;
+ }
+
+ StartSpriteCombine();
+ for (int i = 0; i < 8; i++) {
+ if (HasBit(parts, i)) {
+ AddSortableSpriteToDraw(image + i, PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z);
+ }
+ }
+ EndSpriteCombine();
+ }
+
+ OffsetGroundSprite(31, 1);
+ ti->z += ApplyPixelFoundationToSlope(FOUNDATION_LEVELED, &ti->tileh);
+ } else {
+draw_default_foundation:
+ DrawFoundation(ti, FOUNDATION_LEVELED);
+ }
+ }
+
+ if (IsBuoy(ti->tile)) {
+ DrawWaterClassGround(ti);
+ SpriteID sprite = GetCanalSprite(CF_BUOY, ti->tile);
+ if (sprite != 0) total_offset = sprite - SPR_IMG_BUOY;
+ } else if (IsDock(ti->tile) || (IsOilRig(ti->tile) && IsTileOnWater(ti->tile))) {
+ if (ti->tileh == SLOPE_FLAT) {
+ DrawWaterClassGround(ti);
+ } else {
+ assert(IsDock(ti->tile));
+ TileIndex water_tile = ti->tile + TileOffsByDiagDir(GetDockDirection(ti->tile));
+ WaterClass wc = GetWaterClass(water_tile);
+ if (wc == WATER_CLASS_SEA) {
+ DrawShoreTile(ti->tileh);
+ } else {
+ DrawClearLandTile(ti, 3);
+ }
+ }
+ } else {
+ if (layout != NULL) {
+ /* Sprite layout which needs preprocessing */
+ bool separate_ground = HasBit(statspec->flags, SSF_SEPARATE_GROUND);
+ uint32 var10_values = layout->PrepareLayout(total_offset, rti->fallback_railtype, 0, 0, separate_ground);
+ uint8 var10;
+ FOR_EACH_SET_BIT(var10, var10_values) {
+ uint32 var10_relocation = GetCustomStationRelocation(statspec, st, ti->tile, var10);
+ layout->ProcessRegisters(var10, var10_relocation, separate_ground);
+ }
+ tmp_rail_layout.seq = layout->GetLayout(&tmp_rail_layout.ground);
+ t = &tmp_rail_layout;
+ total_offset = 0;
+ } else if (statspec != NULL) {
+ /* Simple sprite layout */
+ ground_relocation = relocation = GetCustomStationRelocation(statspec, st, ti->tile, 0);
+ if (HasBit(statspec->flags, SSF_SEPARATE_GROUND)) {
+ ground_relocation = GetCustomStationRelocation(statspec, st, ti->tile, 1);
+ }
+ ground_relocation += rti->fallback_railtype;
+ }
+
+ SpriteID image = t->ground.sprite;
+ PaletteID pal = t->ground.pal;
+ RailTrackOffset overlay_offset;
+ if (rti != NULL && rti->UsesOverlay() && SplitGroundSpriteForOverlay(ti, &image, &overlay_offset)) {
+ SpriteID ground = GetCustomRailSprite(rti, ti->tile, RTSG_GROUND);
+ DrawGroundSprite(image, PAL_NONE);
+ DrawGroundSprite(ground + overlay_offset, PAL_NONE);
+
+ if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasStationReservation(ti->tile)) {
+ SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
+ DrawGroundSprite(overlay + overlay_offset, PALETTE_CRASH);
+ }
+ } else {
+ image += HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE) ? ground_relocation : total_offset;
+ if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += ground_relocation;
+ DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, palette));
+
+ /* PBS debugging, draw reserved tracks darker */
+ if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasStationRail(ti->tile) && HasStationReservation(ti->tile)) {
+ const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
+ DrawGroundSprite(GetRailStationAxis(ti->tile) == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH);
+ }
+ }
+ }
+
+ if (HasStationRail(ti->tile) && HasCatenaryDrawn(GetRailType(ti->tile))) DrawCatenary(ti);
+
+ if (HasBit(roadtypes, ROADTYPE_TRAM)) {
+ Axis axis = GetRoadStopDir(ti->tile) == DIAGDIR_NE ? AXIS_X : AXIS_Y;
+ DrawGroundSprite((HasBit(roadtypes, ROADTYPE_ROAD) ? SPR_TRAMWAY_OVERLAY : SPR_TRAMWAY_TRAM) + (axis ^ 1), PAL_NONE);
+ DrawTramCatenary(ti, axis == AXIS_X ? ROAD_X : ROAD_Y);
+ }
+
+ if (IsRailWaypoint(ti->tile)) {
+ /* Don't offset the waypoint graphics; they're always the same. */
+ total_offset = 0;
+ }
+
+ DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, relocation, palette);
+}
+
+void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image)
+{
+ int32 total_offset = 0;
+ PaletteID pal = COMPANY_SPRITE_COLOUR(_local_company);
+ const DrawTileSprites *t = GetStationTileLayout(st, image);
+ const RailtypeInfo *rti = NULL;
+
+ if (railtype != INVALID_RAILTYPE) {
+ rti = GetRailTypeInfo(railtype);
+ total_offset = rti->GetRailtypeSpriteOffset();
+ }
+
+ SpriteID img = t->ground.sprite;
+ RailTrackOffset overlay_offset;
+ if (rti != NULL && rti->UsesOverlay() && SplitGroundSpriteForOverlay(NULL, &img, &overlay_offset)) {
+ SpriteID ground = GetCustomRailSprite(rti, INVALID_TILE, RTSG_GROUND);
+ DrawSprite(img, PAL_NONE, x, y);
+ DrawSprite(ground + overlay_offset, PAL_NONE, x, y);
+ } else {
+ DrawSprite(img + total_offset, HasBit(img, PALETTE_MODIFIER_COLOUR) ? pal : PAL_NONE, x, y);
+ }
+
+ if (roadtype == ROADTYPE_TRAM) {
+ DrawSprite(SPR_TRAMWAY_TRAM + (t->ground.sprite == SPR_ROAD_PAVED_STRAIGHT_X ? 1 : 0), PAL_NONE, x, y);
+ }
+
+ /* Default waypoint has no railtype specific sprites */
+ DrawRailTileSeqInGUI(x, y, t, st == STATION_WAYPOINT ? 0 : total_offset, 0, pal);
+}
+
+static int GetSlopePixelZ_Station(TileIndex tile, uint x, uint y)
+{
+ return GetTileMaxPixelZ(tile);
+}
+
+static Foundation GetFoundation_Station(TileIndex tile, Slope tileh)
+{
+ return FlatteningFoundation(tileh);
+}
+
+static void GetTileDesc_Station(TileIndex tile, TileDesc *td)
+{
+ td->owner[0] = GetTileOwner(tile);
+ if (IsDriveThroughStopTile(tile)) {
+ Owner road_owner = INVALID_OWNER;
+ Owner tram_owner = INVALID_OWNER;
+ RoadTypes rts = GetRoadTypes(tile);
+ if (HasBit(rts, ROADTYPE_ROAD)) road_owner = GetRoadOwner(tile, ROADTYPE_ROAD);
+ if (HasBit(rts, ROADTYPE_TRAM)) tram_owner = GetRoadOwner(tile, ROADTYPE_TRAM);
+
+ /* Is there a mix of owners? */
+ if ((tram_owner != INVALID_OWNER && tram_owner != td->owner[0]) ||
+ (road_owner != INVALID_OWNER && road_owner != td->owner[0])) {
+ uint i = 1;
+ if (road_owner != INVALID_OWNER) {
+ td->owner_type[i] = STR_LAND_AREA_INFORMATION_ROAD_OWNER;
+ td->owner[i] = road_owner;
+ i++;
+ }
+ if (tram_owner != INVALID_OWNER) {
+ td->owner_type[i] = STR_LAND_AREA_INFORMATION_TRAM_OWNER;
+ td->owner[i] = tram_owner;
+ }
+ }
+ }
+ td->build_date = BaseStation::GetByTile(tile)->build_date;
+
+ if (HasStationTileRail(tile)) {
+ const StationSpec *spec = GetStationSpec(tile);
+
+ if (spec != NULL) {
+ td->station_class = StationClass::Get(spec->cls_id)->name;
+ td->station_name = spec->name;
+
+ if (spec->grf_prop.grffile != NULL) {
+ const GRFConfig *gc = GetGRFConfig(spec->grf_prop.grffile->grfid);
+ td->grf = gc->GetName();
+ }
+ }
+
+ const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(tile));
+ td->rail_speed = rti->max_speed;
+ }
+
+ if (IsAirport(tile)) {
+ const AirportSpec *as = Station::GetByTile(tile)->airport.GetSpec();
+ td->airport_class = AirportClass::Get(as->cls_id)->name;
+ td->airport_name = as->name;
+
+ const AirportTileSpec *ats = AirportTileSpec::GetByTile(tile);
+ td->airport_tile_name = ats->name;
+
+ if (as->grf_prop.grffile != NULL) {
+ const GRFConfig *gc = GetGRFConfig(as->grf_prop.grffile->grfid);
+ td->grf = gc->GetName();
+ } else if (ats->grf_prop.grffile != NULL) {
+ const GRFConfig *gc = GetGRFConfig(ats->grf_prop.grffile->grfid);
+ td->grf = gc->GetName();
+ }
+ }
+
+ StringID str;
+ switch (GetStationType(tile)) {
+ default: NOT_REACHED();
+ case STATION_RAIL: str = STR_LAI_STATION_DESCRIPTION_RAILROAD_STATION; break;
+ case STATION_AIRPORT:
+ str = (IsHangar(tile) ? STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR : STR_LAI_STATION_DESCRIPTION_AIRPORT);
+ break;
+ case STATION_TRUCK: str = STR_LAI_STATION_DESCRIPTION_TRUCK_LOADING_AREA; break;
+ case STATION_BUS: str = STR_LAI_STATION_DESCRIPTION_BUS_STATION; break;
+ case STATION_OILRIG: str = STR_INDUSTRY_NAME_OIL_RIG; break;
+ case STATION_DOCK: str = STR_LAI_STATION_DESCRIPTION_SHIP_DOCK; break;
+ case STATION_BUOY: str = STR_LAI_STATION_DESCRIPTION_BUOY; break;
+ case STATION_WAYPOINT: str = STR_LAI_STATION_DESCRIPTION_WAYPOINT; break;
+ }
+ td->str = str;
+}
+
+
+static TrackStatus GetTileTrackStatus_Station(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side)
+{
+ TrackBits trackbits = TRACK_BIT_NONE;
+
+ switch (mode) {
+ case TRANSPORT_RAIL:
+ if (HasStationRail(tile) && !IsStationTileBlocked(tile)) {
+ trackbits = TrackToTrackBits(GetRailStationTrack(tile));
+ }
+ break;
+
+ case TRANSPORT_WATER:
+ /* buoy is coded as a station, it is always on open water */
+ if (IsBuoy(tile)) {
+ trackbits = TRACK_BIT_ALL;
+ /* remove tracks that connect NE map edge */
+ if (TileX(tile) == 0) trackbits &= ~(TRACK_BIT_X | TRACK_BIT_UPPER | TRACK_BIT_RIGHT);
+ /* remove tracks that connect NW map edge */
+ if (TileY(tile) == 0) trackbits &= ~(TRACK_BIT_Y | TRACK_BIT_LEFT | TRACK_BIT_UPPER);
+ }
+ break;
+
+ case TRANSPORT_ROAD:
+ if ((GetRoadTypes(tile) & sub_mode) != 0 && IsRoadStop(tile)) {
+ DiagDirection dir = GetRoadStopDir(tile);
+ Axis axis = DiagDirToAxis(dir);
+
+ if (side != INVALID_DIAGDIR) {
+ if (axis != DiagDirToAxis(side) || (IsStandardRoadStopTile(tile) && dir != side)) break;
+ }
+
+ trackbits = AxisToTrackBits(axis);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return CombineTrackStatus(TrackBitsToTrackdirBits(trackbits), TRACKDIR_BIT_NONE);
+}
+
+
+static void TileLoop_Station(TileIndex tile)
+{
+ /* FIXME -- GetTileTrackStatus_Station -> animated stationtiles
+ * hardcoded.....not good */
+ switch (GetStationType(tile)) {
+ case STATION_AIRPORT:
+ AirportTileAnimationTrigger(Station::GetByTile(tile), tile, AAT_TILELOOP);
+ break;
+
+ case STATION_DOCK:
+ if (GetTileSlope(tile) != SLOPE_FLAT) break; // only handle water part
+ /* FALL THROUGH */
+ case STATION_OILRIG: //(station part)
+ case STATION_BUOY:
+ TileLoop_Water(tile);
+ break;
+
+ default: break;
+ }
+}
+
+
+static void AnimateTile_Station(TileIndex tile)
+{
+ if (HasStationRail(tile)) {
+ AnimateStationTile(tile);
+ return;
+ }
+
+ if (IsAirport(tile)) {
+ AnimateAirportTile(tile);
+ }
+}
+
+
+static bool ClickTile_Station(TileIndex tile)
+{
+ const BaseStation *bst = BaseStation::GetByTile(tile);
+
+ if (bst->facilities & FACIL_WAYPOINT) {
+ ShowWaypointWindow(Waypoint::From(bst));
+ } else if (IsHangar(tile)) {
+ const Station *st = Station::From(bst);
+ ShowDepotWindow(st->airport.GetHangarTile(st->airport.GetHangarNum(tile)), VEH_AIRCRAFT);
+ } else {
+ ShowStationViewWindow(bst->index);
+ }
+ return true;
+}
+
+static VehicleEnterTileStatus VehicleEnter_Station(Vehicle *v, TileIndex tile, int x, int y)
+{
+ if (v->type == VEH_TRAIN) {
+ StationID station_id = GetStationIndex(tile);
+ if (!v->current_order.ShouldStopAtStation(v, station_id)) return VETSB_CONTINUE;
+ if (!IsRailStation(tile) || !v->IsFrontEngine()) return VETSB_CONTINUE;
+
+ int station_ahead;
+ int station_length;
+ int stop = GetTrainStopLocation(station_id, tile, Train::From(v), &station_ahead, &station_length);
+
+ /* Stop whenever that amount of station ahead + the distance from the
+ * begin of the platform to the stop location is longer than the length
+ * of the platform. Station ahead 'includes' the current tile where the
+ * vehicle is on, so we need to subtract that. */
+ if (!IsInsideBS(stop + station_ahead, station_length, TILE_SIZE)) return VETSB_CONTINUE;
+
+ DiagDirection dir = DirToDiagDir(v->direction);
+
+ x &= 0xF;
+ y &= 0xF;
+
+ if (DiagDirToAxis(dir) != AXIS_X) Swap(x, y);
+ if (y == TILE_SIZE / 2) {
+ if (dir != DIAGDIR_SE && dir != DIAGDIR_SW) x = TILE_SIZE - 1 - x;
+ stop &= TILE_SIZE - 1;
+
+ if (x == stop) return VETSB_ENTERED_STATION | (VehicleEnterTileStatus)(station_id << VETS_STATION_ID_OFFSET); // enter station
+ if (x < stop) {
+ uint16 spd;
+
+ v->vehstatus |= VS_TRAIN_SLOWING;
+ spd = max(0, (stop - x) * 20 - 15);
+ if (spd < v->cur_speed) v->cur_speed = spd;
+ }
+ }
+ } else if (v->type == VEH_ROAD) {
+ RoadVehicle *rv = RoadVehicle::From(v);
+ if (rv->state < RVSB_IN_ROAD_STOP && !IsReversingRoadTrackdir((Trackdir)rv->state) && rv->frame == 0) {
+ if (IsRoadStop(tile) && rv->IsFrontEngine()) {
+ /* Attempt to allocate a parking bay in a road stop */
+ return RoadStop::GetByTile(tile, GetRoadStopType(tile))->Enter(rv) ? VETSB_CONTINUE : VETSB_CANNOT_ENTER;
+ }
+ }
+ }
+
+ return VETSB_CONTINUE;
+}
+
+/**
+ * Run the watched cargo callback for all houses in the catchment area.
+ * @param st Station.
+ */
+void TriggerWatchedCargoCallbacks(Station *st)
+{
+ /* Collect cargoes accepted since the last big tick. */
+ uint cargoes = 0;
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ if (HasBit(st->goods[cid].acceptance_pickup, GoodsEntry::GES_ACCEPTED_BIGTICK)) SetBit(cargoes, cid);
+ }
+
+ /* Anything to do? */
+ if (cargoes == 0) return;
+
+ /* Loop over all houses in the catchment. */
+ Rect r = st->GetCatchmentRect();
+ TileArea ta(TileXY(r.left, r.top), TileXY(r.right, r.bottom));
+ TILE_AREA_LOOP(tile, ta) {
+ if (IsTileType(tile, MP_HOUSE)) {
+ WatchedCargoCallback(tile, cargoes);
+ }
+ }
+}
+
+/**
+ * This function is called for each station once every 250 ticks.
+ * Not all stations will get the tick at the same time.
+ * @param st the station receiving the tick.
+ * @return true if the station is still valid (wasn't deleted)
+ */
+static bool StationHandleBigTick(BaseStation *st)
+{
+ if (!st->IsInUse()) {
+ if (++st->delete_ctr >= 8) delete st;
+ return false;
+ }
+
+ if (Station::IsExpected(st)) {
+ TriggerWatchedCargoCallbacks(Station::From(st));
+
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ ClrBit(Station::From(st)->goods[i].acceptance_pickup, GoodsEntry::GES_ACCEPTED_BIGTICK);
+ }
+ }
+
+
+ if ((st->facilities & FACIL_WAYPOINT) == 0) UpdateStationAcceptance(Station::From(st), true);
+
+ return true;
+}
+
+static inline void byte_inc_sat(byte *p)
+{
+ byte b = *p + 1;
+ if (b != 0) *p = b;
+}
+
+static void UpdateStationRating(Station *st)
+{
+ bool waiting_changed = false;
+
+ byte_inc_sat(&st->time_since_load);
+ byte_inc_sat(&st->time_since_unload);
+
+ const CargoSpec *cs;
+ FOR_ALL_CARGOSPECS(cs) {
+ GoodsEntry *ge = &st->goods[cs->Index()];
+ /* Slowly increase the rating back to his original level in the case we
+ * didn't deliver cargo yet to this station. This happens when a bribe
+ * failed while you didn't moved that cargo yet to a station. */
+ if (!HasBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP) && ge->rating < INITIAL_STATION_RATING) {
+ ge->rating++;
+ }
+
+ /* Only change the rating if we are moving this cargo */
+ if (HasBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP)) {
+ byte_inc_sat(&ge->time_since_pickup);
+
+ bool skip = false;
+ int rating = 0;
+ uint waiting = ge->cargo.Count();
+
+ if (HasBit(cs->callback_mask, CBM_CARGO_STATION_RATING_CALC)) {
+ /* Perform custom station rating. If it succeeds the speed, days in transit and
+ * waiting cargo ratings must not be executed. */
+
+ /* NewGRFs expect last speed to be 0xFF when no vehicle has arrived yet. */
+ uint last_speed = ge->HasVehicleEverTriedLoading() ? ge->last_speed : 0xFF;
+
+ uint32 var18 = min(ge->time_since_pickup, 0xFF) | (min(waiting, 0xFFFF) << 8) | (min(last_speed, 0xFF) << 24);
+ /* Convert to the 'old' vehicle types */
+ uint32 var10 = (st->last_vehicle_type == VEH_INVALID) ? 0x0 : (st->last_vehicle_type + 0x10);
+ uint16 callback = GetCargoCallback(CBID_CARGO_STATION_RATING_CALC, var10, var18, cs);
+ if (callback != CALLBACK_FAILED) {
+ skip = true;
+ rating = GB(callback, 0, 14);
+
+ /* Simulate a 15 bit signed value */
+ if (HasBit(callback, 14)) rating -= 0x4000;
+ }
+ }
+
+ if (!skip) {
+ int b = ge->last_speed - 85;
+ if (b >= 0) rating += b >> 2;
+
+ byte waittime = ge->time_since_pickup;
+ if (st->last_vehicle_type == VEH_SHIP) waittime >>= 2;
+ (waittime > 21) ||
+ (rating += 25, waittime > 12) ||
+ (rating += 25, waittime > 6) ||
+ (rating += 45, waittime > 3) ||
+ (rating += 35, true);
+
+ (rating -= 90, waiting > 1500) ||
+ (rating += 55, waiting > 1000) ||
+ (rating += 35, waiting > 600) ||
+ (rating += 10, waiting > 300) ||
+ (rating += 20, waiting > 100) ||
+ (rating += 10, true);
+ }
+
+ if (Company::IsValidID(st->owner) && HasBit(st->town->statues, st->owner)) rating += 26;
+
+ byte age = ge->last_age;
+ (age >= 3) ||
+ (rating += 10, age >= 2) ||
+ (rating += 10, age >= 1) ||
+ (rating += 13, true);
+
+ {
+ int or_ = ge->rating; // old rating
+
+ /* only modify rating in steps of -2, -1, 0, 1 or 2 */
+ ge->rating = rating = or_ + Clamp(Clamp(rating, 0, 255) - or_, -2, 2);
+
+ /* if rating is <= 64 and more than 200 items waiting,
+ * remove some random amount of goods from the station */
+ if (rating <= 64 && waiting >= 200) {
+ int dec = Random() & 0x1F;
+ if (waiting < 400) dec &= 7;
+ waiting -= dec + 1;
+ waiting_changed = true;
+ }
+
+ /* if rating is <= 127 and there are any items waiting, maybe remove some goods. */
+ if (rating <= 127 && waiting != 0) {
+ uint32 r = Random();
+ if (rating <= (int)GB(r, 0, 7)) {
+ /* Need to have int, otherwise it will just overflow etc. */
+ waiting = max((int)waiting - (int)GB(r, 8, 2) - 1, 0);
+ waiting_changed = true;
+ }
+ }
+
+ /* At some point we really must cap the cargo. Previously this
+ * was a strict 4095, but now we'll have a less strict, but
+ * increasingly aggressive truncation of the amount of cargo. */
+ static const uint WAITING_CARGO_THRESHOLD = 1 << 12;
+ static const uint WAITING_CARGO_CUT_FACTOR = 1 << 6;
+ static const uint MAX_WAITING_CARGO = 1 << 15;
+
+ if (waiting > WAITING_CARGO_THRESHOLD) {
+ uint difference = waiting - WAITING_CARGO_THRESHOLD;
+ waiting -= (difference / WAITING_CARGO_CUT_FACTOR);
+
+ waiting = min(waiting, MAX_WAITING_CARGO);
+ waiting_changed = true;
+ }
+
+ if (waiting_changed) ge->cargo.Truncate(waiting);
+ }
+ }
+ }
+
+ StationID index = st->index;
+ if (waiting_changed) {
+ SetWindowDirty(WC_STATION_VIEW, index); // update whole window
+ } else {
+ SetWindowWidgetDirty(WC_STATION_VIEW, index, WID_SV_ACCEPT_RATING_LIST); // update only ratings list
+ }
+}
+
+/* called for every station each tick */
+static void StationHandleSmallTick(BaseStation *st)
+{
+ if ((st->facilities & FACIL_WAYPOINT) != 0 || !st->IsInUse()) return;
+
+ byte b = st->delete_ctr + 1;
+ if (b >= STATION_RATING_TICKS) b = 0;
+ st->delete_ctr = b;
+
+ if (b == 0) UpdateStationRating(Station::From(st));
+}
+
+void OnTick_Station()
+{
+ if (_game_mode == GM_EDITOR) return;
+
+ BaseStation *st;
+ FOR_ALL_BASE_STATIONS(st) {
+ StationHandleSmallTick(st);
+
+ /* Run STATION_ACCEPTANCE_TICKS = 250 tick interval trigger for station animation.
+ * Station index is included so that triggers are not all done
+ * at the same time. */
+ if ((_tick_counter + st->index) % STATION_ACCEPTANCE_TICKS == 0) {
+ /* Stop processing this station if it was deleted */
+ if (!StationHandleBigTick(st)) continue;
+ TriggerStationAnimation(st, st->xy, SAT_250_TICKS);
+ if (Station::IsExpected(st)) AirportAnimationTrigger(Station::From(st), AAT_STATION_250_TICKS);
+ }
+ }
+}
+
+/** Monthly loop for stations. */
+void StationMonthlyLoop()
+{
+ Station *st;
+
+ FOR_ALL_STATIONS(st) {
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ GoodsEntry *ge = &st->goods[i];
+ SB(ge->acceptance_pickup, GoodsEntry::GES_LAST_MONTH, 1, GB(ge->acceptance_pickup, GoodsEntry::GES_CURRENT_MONTH, 1));
+ ClrBit(ge->acceptance_pickup, GoodsEntry::GES_CURRENT_MONTH);
+ }
+ }
+}
+
+
+void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint radius)
+{
+ Station *st;
+
+ FOR_ALL_STATIONS(st) {
+ if (st->owner == owner &&
+ DistanceManhattan(tile, st->xy) <= radius) {
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ GoodsEntry *ge = &st->goods[i];
+
+ if (ge->acceptance_pickup != 0) {
+ ge->rating = Clamp(ge->rating + amount, 0, 255);
+ }
+ }
+ }
+ }
+}
+
+static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id)
+{
+ /* We can't allocate a CargoPacket? Then don't do anything
+ * at all; i.e. just discard the incoming cargo. */
+ if (!CargoPacket::CanAllocateItem()) return 0;
+
+ GoodsEntry &ge = st->goods[type];
+ amount += ge.amount_fract;
+ ge.amount_fract = GB(amount, 0, 8);
+
+ amount >>= 8;
+ /* No new "real" cargo item yet. */
+ if (amount == 0) return 0;
+
+ ge.cargo.Append(new CargoPacket(st->index, st->xy, amount, source_type, source_id));
+
+ if (!HasBit(ge.acceptance_pickup, GoodsEntry::GES_PICKUP)) {
+ InvalidateWindowData(WC_STATION_LIST, st->index);
+ SetBit(ge.acceptance_pickup, GoodsEntry::GES_PICKUP);
+ }
+
+ TriggerStationRandomisation(st, st->xy, SRT_NEW_CARGO, type);
+ TriggerStationAnimation(st, st->xy, SAT_NEW_CARGO, type);
+ AirportAnimationTrigger(st, AAT_STATION_NEW_CARGO, type);
+
+ SetWindowDirty(WC_STATION_VIEW, st->index);
+ st->MarkTilesDirty(true);
+ return amount;
+}
+
+static bool IsUniqueStationName(const char *name)
+{
+ const Station *st;
+
+ FOR_ALL_STATIONS(st) {
+ if (st->name != NULL && strcmp(st->name, name) == 0) return false;
+ }
+
+ return true;
+}
+
+/**
+ * Rename a station
+ * @param tile unused
+ * @param flags operation to perform
+ * @param p1 station ID that is to be renamed
+ * @param p2 unused
+ * @param text the new name or an empty string when resetting to the default
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdRenameStation(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ Station *st = Station::GetIfValid(p1);
+ if (st == NULL) return CMD_ERROR;
+
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+
+ bool reset = StrEmpty(text);
+
+ if (!reset) {
+ if (Utf8StringLength(text) >= MAX_LENGTH_STATION_NAME_CHARS) return CMD_ERROR;
+ if (!IsUniqueStationName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE);
+ }
+
+ if (flags & DC_EXEC) {
+ free(st->name);
+ st->name = reset ? NULL : strdup(text);
+
+ st->UpdateVirtCoord();
+ InvalidateWindowData(WC_STATION_LIST, st->owner, 1);
+ }
+
+ return CommandCost();
+}
+
+/**
+ * Find all stations around a rectangular producer (industry, house, headquarter, ...)
+ *
+ * @param location The location/area of the producer
+ * @param stations The list to store the stations in
+ */
+void FindStationsAroundTiles(const TileArea &location, StationList *stations)
+{
+ /* area to search = producer plus station catchment radius */
+ uint max_rad = (_settings_game.station.modified_catchment ? MAX_CATCHMENT : CA_UNMODIFIED);
+
+ uint x = TileX(location.tile);
+ uint y = TileY(location.tile);
+
+ uint min_x = (x > max_rad) ? x - max_rad : 0;
+ uint max_x = x + location.w + max_rad;
+ uint min_y = (y > max_rad) ? y - max_rad : 0;
+ uint max_y = y + location.h + max_rad;
+
+ if (min_x == 0 && _settings_game.construction.freeform_edges) min_x = 1;
+ if (min_y == 0 && _settings_game.construction.freeform_edges) min_y = 1;
+ if (max_x >= MapSizeX()) max_x = MapSizeX() - 1;
+ if (max_y >= MapSizeY()) max_y = MapSizeY() - 1;
+
+ for (uint cy = min_y; cy < max_y; cy++) {
+ for (uint cx = min_x; cx < max_x; cx++) {
+ TileIndex cur_tile = TileXY(cx, cy);
+ if (!IsTileType(cur_tile, MP_STATION)) continue;
+
+ Station *st = Station::GetByTile(cur_tile);
+ /* st can be NULL in case of waypoints */
+ if (st == NULL) continue;
+
+ if (_settings_game.station.modified_catchment) {
+ int rad = st->GetCatchmentRadius();
+ int rad_x = cx - x;
+ int rad_y = cy - y;
+
+ if (rad_x < -rad || rad_x >= rad + location.w) continue;
+ if (rad_y < -rad || rad_y >= rad + location.h) continue;
+ }
+
+ /* Insert the station in the set. This will fail if it has
+ * already been added.
+ */
+ stations->Include(st);
+ }
+ }
+}
+
+/**
+ * Run a tile loop to find stations around a tile, on demand. Cache the result for further requests
+ * @return pointer to a StationList containing all stations found
+ */
+const StationList *StationFinder::GetStations()
+{
+ if (this->tile != INVALID_TILE) {
+ FindStationsAroundTiles(*this, &this->stations);
+ this->tile = INVALID_TILE;
+ }
+ return &this->stations;
+}
+
+uint MoveGoodsToStation(CargoID type, uint amount, SourceType source_type, SourceID source_id, const StationList *all_stations)
+{
+ /* Return if nothing to do. Also the rounding below fails for 0. */
+ if (amount == 0) return 0;
+
+ Station *st1 = NULL; // Station with best rating
+ Station *st2 = NULL; // Second best station
+ uint best_rating1 = 0; // rating of st1
+ uint best_rating2 = 0; // rating of st2
+
+ for (Station * const *st_iter = all_stations->Begin(); st_iter != all_stations->End(); ++st_iter) {
+ Station *st = *st_iter;
+
+ /* Is the station reserved exclusively for somebody else? */
+ if (st->town->exclusive_counter > 0 && st->town->exclusivity != st->owner) continue;
+
+ if (st->goods[type].rating == 0) continue; // Lowest possible rating, better not to give cargo anymore
+
+ if (_settings_game.order.selectgoods && !st->goods[type].HasVehicleEverTriedLoading()) continue; // Selectively servicing stations, and not this one
+
+ if (IsCargoInClass(type, CC_PASSENGERS)) {
+ if (st->facilities == FACIL_TRUCK_STOP) continue; // passengers are never served by just a truck stop
+ } else {
+ if (st->facilities == FACIL_BUS_STOP) continue; // non-passengers are never served by just a bus stop
+ }
+
+ /* This station can be used, add it to st1/st2 */
+ if (st1 == NULL || st->goods[type].rating >= best_rating1) {
+ st2 = st1; best_rating2 = best_rating1; st1 = st; best_rating1 = st->goods[type].rating;
+ } else if (st2 == NULL || st->goods[type].rating >= best_rating2) {
+ st2 = st; best_rating2 = st->goods[type].rating;
+ }
+ }
+
+ /* no stations around at all? */
+ if (st1 == NULL) return 0;
+
+ /* From now we'll calculate with fractal cargo amounts.
+ * First determine how much cargo we really have. */
+ amount *= best_rating1 + 1;
+
+ if (st2 == NULL) {
+ /* only one station around */
+ return UpdateStationWaiting(st1, type, amount, source_type, source_id);
+ }
+
+ /* several stations around, the best two (highest rating) are in st1 and st2 */
+ assert(st1 != NULL);
+ assert(st2 != NULL);
+ assert(best_rating1 != 0 || best_rating2 != 0);
+
+ /* Then determine the amount the worst station gets. We do it this way as the
+ * best should get a bonus, which in this case is the rounding difference from
+ * this calculation. In reality that will mean the bonus will be pretty low.
+ * Nevertheless, the best station should always get the most cargo regardless
+ * of rounding issues. */
+ uint worst_cargo = amount * best_rating2 / (best_rating1 + best_rating2);
+ assert(worst_cargo <= (amount - worst_cargo));
+
+ /* And then send the cargo to the stations! */
+ uint moved = UpdateStationWaiting(st1, type, amount - worst_cargo, source_type, source_id);
+ /* These two UpdateStationWaiting's can't be in the statement as then the order
+ * of execution would be undefined and that could cause desyncs with callbacks. */
+ return moved + UpdateStationWaiting(st2, type, worst_cargo, source_type, source_id);
+}
+
+void BuildOilRig(TileIndex tile)
+{
+ if (!Station::CanAllocateItem()) {
+ DEBUG(misc, 0, "Can't allocate station for oilrig at 0x%X, reverting to oilrig only", tile);
+ return;
+ }
+
+ Station *st = new Station(tile);
+ st->town = ClosestTownFromTile(tile, UINT_MAX);
+
+ st->string_id = GenerateStationName(st, tile, STATIONNAMING_OILRIG);
+
+ assert(IsTileType(tile, MP_INDUSTRY));
+ DeleteAnimatedTile(tile);
+ MakeOilrig(tile, st->index, GetWaterClass(tile));
+
+ st->owner = OWNER_NONE;
+ st->airport.type = AT_OILRIG;
+ st->airport.Add(tile);
+ st->dock_tile = tile;
+ st->facilities = FACIL_AIRPORT | FACIL_DOCK;
+ st->build_date = _date;
+
+ st->rect.BeforeAddTile(tile, StationRect::ADD_FORCE);
+
+ st->UpdateVirtCoord();
+ UpdateStationAcceptance(st, false);
+ st->RecomputeIndustriesNear();
+}
+
+void DeleteOilRig(TileIndex tile)
+{
+ Station *st = Station::GetByTile(tile);
+
+ MakeWaterKeepingClass(tile, OWNER_NONE);
+
+ st->dock_tile = INVALID_TILE;
+ st->airport.Clear();
+ st->facilities &= ~(FACIL_AIRPORT | FACIL_DOCK);
+ st->airport.flags = 0;
+
+ st->rect.AfterRemoveTile(st, tile);
+
+ st->UpdateVirtCoord();
+ st->RecomputeIndustriesNear();
+ if (!st->IsInUse()) delete st;
+}
+
+static void ChangeTileOwner_Station(TileIndex tile, Owner old_owner, Owner new_owner)
+{
+ if (IsRoadStopTile(tile)) {
+ for (RoadType rt = ROADTYPE_ROAD; rt < ROADTYPE_END; rt++) {
+ /* Update all roadtypes, no matter if they are present */
+ if (GetRoadOwner(tile, rt) == old_owner) {
+ if (HasTileRoadType(tile, rt)) {
+ /* A drive-through road-stop has always two road bits. No need to dirty windows here, we'll redraw the whole screen anyway. */
+ Company::Get(old_owner)->infrastructure.road[rt] -= 2;
+ if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.road[rt] += 2;
+ }
+ SetRoadOwner(tile, rt, new_owner == INVALID_OWNER ? OWNER_NONE : new_owner);
+ }
+ }
+ }
+
+ if (!IsTileOwner(tile, old_owner)) return;
+
+ if (new_owner != INVALID_OWNER) {
+ /* Update company infrastructure counts. Only do it here
+ * if the new owner is valid as otherwise the clear
+ * command will do it for us. No need to dirty windows
+ * here, we'll redraw the whole screen anyway.*/
+ Company *old_company = Company::Get(old_owner);
+ Company *new_company = Company::Get(new_owner);
+
+ /* Update counts for underlying infrastructure. */
+ switch (GetStationType(tile)) {
+ case STATION_RAIL:
+ case STATION_WAYPOINT:
+ if (!IsStationTileBlocked(tile)) {
+ old_company->infrastructure.rail[GetRailType(tile)]--;
+ new_company->infrastructure.rail[GetRailType(tile)]++;
+ }
+ break;
+
+ case STATION_BUS:
+ case STATION_TRUCK:
+ /* Road stops were already handled above. */
+ break;
+
+ case STATION_BUOY:
+ case STATION_DOCK:
+ if (GetWaterClass(tile) == WATER_CLASS_CANAL) {
+ old_company->infrastructure.water--;
+ new_company->infrastructure.water++;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Update station tile count. */
+ if (!IsBuoy(tile) && !IsAirport(tile)) {
+ old_company->infrastructure.station--;
+ new_company->infrastructure.station++;
+ }
+
+ /* for buoys, owner of tile is owner of water, st->owner == OWNER_NONE */
+ SetTileOwner(tile, new_owner);
+ InvalidateWindowClassesData(WC_STATION_LIST, 0);
+ } else {
+ if (IsDriveThroughStopTile(tile)) {
+ /* Remove the drive-through road stop */
+ DoCommand(tile, 1 | 1 << 8, (GetStationType(tile) == STATION_TRUCK) ? ROADSTOP_TRUCK : ROADSTOP_BUS, DC_EXEC | DC_BANKRUPT, CMD_REMOVE_ROAD_STOP);
+ assert(IsTileType(tile, MP_ROAD));
+ /* Change owner of tile and all roadtypes */
+ ChangeTileOwner(tile, old_owner, new_owner);
+ } else {
+ DoCommand(tile, 0, 0, DC_EXEC | DC_BANKRUPT, CMD_LANDSCAPE_CLEAR);
+ /* Set tile owner of water under (now removed) buoy and dock to OWNER_NONE.
+ * Update owner of buoy if it was not removed (was in orders).
+ * Do not update when owned by OWNER_WATER (sea and rivers). */
+ if ((IsTileType(tile, MP_WATER) || IsBuoyTile(tile)) && IsTileOwner(tile, old_owner)) SetTileOwner(tile, OWNER_NONE);
+ }
+ }
+}
+
+/**
+ * Check if a drive-through road stop tile can be cleared.
+ * Road stops built on town-owned roads check the conditions
+ * that would allow clearing of the original road.
+ * @param tile road stop tile to check
+ * @param flags command flags
+ * @return true if the road can be cleared
+ */
+static bool CanRemoveRoadWithStop(TileIndex tile, DoCommandFlag flags)
+{
+ /* Yeah... water can always remove stops, right? */
+ if (_current_company == OWNER_WATER) return true;
+
+ RoadTypes rts = GetRoadTypes(tile);
+ if (HasBit(rts, ROADTYPE_TRAM)) {
+ Owner tram_owner = GetRoadOwner(tile, ROADTYPE_TRAM);
+ if (tram_owner != OWNER_NONE && CheckOwnership(tram_owner).Failed()) return false;
+ }
+ if (HasBit(rts, ROADTYPE_ROAD)) {
+ Owner road_owner = GetRoadOwner(tile, ROADTYPE_ROAD);
+ if (road_owner != OWNER_TOWN) {
+ if (road_owner != OWNER_NONE && CheckOwnership(road_owner).Failed()) return false;
+ } else {
+ if (CheckAllowRemoveRoad(tile, GetAnyRoadBits(tile, ROADTYPE_ROAD), OWNER_TOWN, ROADTYPE_ROAD, flags).Failed()) return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Clear a single tile of a station.
+ * @param tile The tile to clear.
+ * @param flags The DoCommand flags related to the "command".
+ * @return The cost, or error of clearing.
+ */
+CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags)
+{
+ if (flags & DC_AUTO) {
+ switch (GetStationType(tile)) {
+ default: break;
+ case STATION_RAIL: return_cmd_error(STR_ERROR_MUST_DEMOLISH_RAILROAD);
+ case STATION_WAYPOINT: return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED);
+ case STATION_AIRPORT: return_cmd_error(STR_ERROR_MUST_DEMOLISH_AIRPORT_FIRST);
+ case STATION_TRUCK: return_cmd_error(HasTileRoadType(tile, ROADTYPE_TRAM) ? STR_ERROR_MUST_DEMOLISH_CARGO_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_TRUCK_STATION_FIRST);
+ case STATION_BUS: return_cmd_error(HasTileRoadType(tile, ROADTYPE_TRAM) ? STR_ERROR_MUST_DEMOLISH_PASSENGER_TRAM_STATION_FIRST : STR_ERROR_MUST_DEMOLISH_BUS_STATION_FIRST);
+ case STATION_BUOY: return_cmd_error(STR_ERROR_BUOY_IN_THE_WAY);
+ case STATION_DOCK: return_cmd_error(STR_ERROR_MUST_DEMOLISH_DOCK_FIRST);
+ case STATION_OILRIG:
+ SetDParam(1, STR_INDUSTRY_NAME_OIL_RIG);
+ return_cmd_error(STR_ERROR_GENERIC_OBJECT_IN_THE_WAY);
+ }
+ }
+
+ switch (GetStationType(tile)) {
+ case STATION_RAIL: return RemoveRailStation(tile, flags);
+ case STATION_WAYPOINT: return RemoveRailWaypoint(tile, flags);
+ case STATION_AIRPORT: return RemoveAirport(tile, flags);
+ case STATION_TRUCK:
+ if (IsDriveThroughStopTile(tile) && !CanRemoveRoadWithStop(tile, flags)) {
+ return_cmd_error(STR_ERROR_MUST_DEMOLISH_TRUCK_STATION_FIRST);
+ }
+ return RemoveRoadStop(tile, flags);
+ case STATION_BUS:
+ if (IsDriveThroughStopTile(tile) && !CanRemoveRoadWithStop(tile, flags)) {
+ return_cmd_error(STR_ERROR_MUST_DEMOLISH_BUS_STATION_FIRST);
+ }
+ return RemoveRoadStop(tile, flags);
+ case STATION_BUOY: return RemoveBuoy(tile, flags);
+ case STATION_DOCK: return RemoveDock(tile, flags);
+ default: break;
+ }
+
+ return CMD_ERROR;
+}
+
+static CommandCost TerraformTile_Station(TileIndex tile, DoCommandFlag flags, int z_new, Slope tileh_new)
+{
+ if (_settings_game.construction.build_on_slopes && AutoslopeEnabled()) {
+ /* TODO: If you implement newgrf callback 149 'land slope check', you have to decide what to do with it here.
+ * TTDP does not call it.
+ */
+ if (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) {
+ switch (GetStationType(tile)) {
+ case STATION_WAYPOINT:
+ case STATION_RAIL: {
+ DiagDirection direction = AxisToDiagDir(GetRailStationAxis(tile));
+ if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break;
+ if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break;
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]);
+ }
+
+ case STATION_AIRPORT:
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]);
+
+ case STATION_TRUCK:
+ case STATION_BUS: {
+ DiagDirection direction = GetRoadStopDir(tile);
+ if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break;
+ if (IsDriveThroughStopTile(tile)) {
+ if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break;
+ }
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]);
+ }
+
+ default: break;
+ }
+ }
+ }
+ return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
+}
+
+
+extern const TileTypeProcs _tile_type_station_procs = {
+ DrawTile_Station, // draw_tile_proc
+ GetSlopePixelZ_Station, // get_slope_z_proc
+ ClearTile_Station, // clear_tile_proc
+ NULL, // add_accepted_cargo_proc
+ GetTileDesc_Station, // get_tile_desc_proc
+ GetTileTrackStatus_Station, // get_tile_track_status_proc
+ ClickTile_Station, // click_tile_proc
+ AnimateTile_Station, // animate_tile_proc
+ TileLoop_Station, // tile_loop_proc
+ ChangeTileOwner_Station, // change_tile_owner_proc
+ NULL, // add_produced_cargo_proc
+ VehicleEnter_Station, // vehicle_enter_tile_proc
+ GetFoundation_Station, // get_foundation_proc
+ TerraformTile_Station, // terraform_tile_proc
+};
diff --git a/src/station_func.h b/src/station_func.h
index 000bd19ba..13984aeb6 100644
--- a/src/station_func.h
+++ b/src/station_func.h
@@ -17,6 +17,7 @@
#include "road_type.h"
#include "economy_func.h"
#include "rail.h"
+#include "order_type.h"
void ModifyStationRatingAround(TileIndex tile, Owner owner, int amount, uint radius);
@@ -29,6 +30,7 @@ CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad);
CargoArray GetAcceptanceAroundTiles(TileIndex tile, int w, int h, int rad, uint32 *always_accepted = NULL);
void UpdateStationAcceptance(Station *st, bool show_msg);
+uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceType source_type, SourceID source_id, TileIndex dest_tile = INVALID_TILE, SourceType dest_type = ST_INDUSTRY, SourceID dest_id = INVALID_SOURCE, OrderID next_hop = INVALID_ORDER, StationID next_unload = INVALID_STATION, byte flags = 0);
const DrawTileSprites *GetStationTileLayout(StationType st, byte gfx);
void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image);
diff --git a/src/station_gui.cpp b/src/station_gui.cpp
index b0709e2c2..219bcae9d 100644
--- a/src/station_gui.cpp
+++ b/src/station_gui.cpp
@@ -20,6 +20,7 @@
#include "station_gui.h"
#include "strings_func.h"
#include "window_func.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "widgets/dropdown_func.h"
#include "station_base.h"
@@ -30,6 +31,9 @@
#include "core/geometry_func.hpp"
#include "vehiclelist.h"
#include "town.h"
+#include "industry.h"
+#include "cargodest_base.h"
+#include "departures_gui.h"
#include "widgets/station_widget.h"
@@ -743,14 +747,18 @@ static const NWidgetPart _nested_station_view_widgets[] = {
NWidget(WWT_PANEL, COLOUR_GREY, WID_SV_ACCEPT_RATING_LIST), SetMinimalSize(249, 32), SetResize(1, 0), EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
- NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_LOCATION), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_LOCATION), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1),
SetDataTip(STR_BUTTON_LOCATION, STR_STATION_VIEW_CENTER_TOOLTIP),
- NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ACCEPTS_RATINGS), SetMinimalSize(46, 12), SetResize(1, 0), SetFill(1, 1),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ACCEPTS_RATINGS), SetMinimalSize(37, 12), SetResize(1, 0), SetFill(1, 1),
SetDataTip(STR_STATION_VIEW_RATINGS_BUTTON, STR_STATION_VIEW_RATINGS_TOOLTIP),
- NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_RENAME), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_CARGO_FROM_TO_VIA), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1),
+ SetDataTip(STR_STATION_VIEW_WAITING_VIA_BUTTON, STR_STATION_VIEW_WAITING_VIA_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_RENAME), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1),
SetDataTip(STR_BUTTON_RENAME, STR_STATION_VIEW_RENAME_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_DEPARTURES), SetMinimalSize(80, 12), SetResize(1, 0), SetFill(1, 1),
+ SetDataTip(STR_STATION_VIEW_DEPARTURES_BUTTON, STR_STATION_VIEW_DEPARTURES_TOOLTIP),
EndContainer(),
- NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_CLOSE_AIRPORT), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_CLOSE_AIRPORT), SetMinimalSize(36, 12), SetResize(1, 0), SetFill(1, 1),
SetDataTip(STR_STATION_VIEW_CLOSE_AIRPORT, STR_STATION_VIEW_CLOSE_AIRPORT_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
@@ -786,18 +794,145 @@ static void DrawCargoIcons(CargoID i, uint waiting, int left, int right, int y)
struct CargoData {
CargoID cargo;
- StationID source;
+ union {
+ StationID station;
+ SourceID css;
+ };
uint count;
+ SourceType type;
- CargoData(CargoID cargo, StationID source, uint count) :
+ CargoData(CargoID cargo, StationID station, uint count, SourceType type = ST_INDUSTRY) :
cargo(cargo),
- source(source),
- count(count)
+ station(station),
+ count(count),
+ type(type)
{ }
};
typedef std::list<CargoData> CargoDataList;
+/** List of cargo for either one next hop or one destination. */
+struct CargoDestEntry {
+ typedef std::list<CargoDestEntry> List;
+
+ /** Enum for type of stored data. */
+ enum Type {
+ FINAL_DEST, ///< Data is the final destination.
+ NEXT_HOP, ///< Data is the next hop.
+ TRANSFER_HOP ///< Data is the transfer station.
+ };
+
+ List children; ///< Child entries of this entry.
+ CargoData data; ///< Stores the info for the current item.
+ Type type; ///< Type of the data stored in #entry.
+ uint16 start_row; ///< Row number of the header line.
+ bool expanded; ///< Is this entry expanded?
+
+ CargoDestEntry(Type type, StationID station, uint count, SourceType st = ST_INDUSTRY) :
+ data(INVALID_CARGO, station, count, st),
+ type(type),
+ start_row(0),
+ expanded(false)
+ { }
+
+ /** Zero out this entry and all child entries. */
+ void Zero()
+ {
+ for (List::iterator i = this->children.begin(); i != this->children.end(); ++i ) {
+ i->Zero();
+ }
+ this->data.count = 0;
+ this->start_row = 0;
+ }
+
+ /** Remove all empty child entries. */
+ void RemoveEmpty()
+ {
+ for (List::iterator i = this->children.begin(); i != this->children.end(); ) {
+ if (i->data.count > 0) {
+ i->RemoveEmpty();
+ ++i;
+ } else {
+ i = this->children.erase(i);
+ }
+ }
+ }
+
+ /** Update header row number. */
+ int UpdateRowCount(int row)
+ {
+ this->start_row = ++row;
+ if (this->expanded) {
+ for (List::iterator i = this->children.begin(); i != this->children.end(); ++i) {
+ row = i->UpdateRowCount(row);
+ }
+ }
+ return row;
+ }
+};
+
+/**
+ * Get the next hop of a cargo packet.
+ * @param ge Station cargo info for the matching cargo type.
+ * @param cp The cargo packet.
+ * @return Station ID of the next hop or INVALID_STATION if not possible.
+ */
+static StationID GetNextHopStation(const GoodsEntry &ge, const CargoPacket *cp)
+{
+ StationID next = INVALID_STATION;
+ for (RouteLinkList::const_iterator i = ge.routes.begin(); i != ge.routes.end(); ++i) {
+ if ((*i)->GetOriginOrderId() == cp->NextHop()) {
+ next = (*i)->GetDestination();
+ break;
+ }
+ }
+ return next;
+}
+
+/**
+ * Add a cargo packet to a #CargoDestEntry list.
+ * @param list The list to add the packet to.
+ * @param type Which value to select as the entry info.
+ * @param cp The cargo packet.
+ * @param ge Where this cargo packets belongs to.
+ * @return Pointer to the added entry or NULL if the packet had no valid destination of the specified type.
+ */
+static CargoDestEntry *AddCargoPacketToList(CargoDestEntry::List &list, CargoDestEntry::Type type, const CargoPacket *cp, const GoodsEntry &ge)
+{
+ assert_compile(INVALID_STATION == INVALID_SOURCE);
+
+ /* Extract the wanted sort type from the cargo packet. */
+ uint16 sort_val;
+ switch (type) {
+ case CargoDestEntry::FINAL_DEST:
+ sort_val = cp->DestinationID();
+ break;
+ case CargoDestEntry::NEXT_HOP:
+ sort_val = GetNextHopStation(ge, cp);
+ break;
+ case CargoDestEntry::TRANSFER_HOP:
+ sort_val = cp->NextStation();
+ break;
+ default:
+ NOT_REACHED();
+ }
+
+ if (sort_val == INVALID_STATION) return NULL;
+
+ /* Search for a matching child. */
+ for (CargoDestEntry::List::iterator i = list.begin(); i != list.end(); ++i) {
+ if (type == CargoDestEntry::FINAL_DEST ? i->data.css == sort_val && i->data.type == cp->DestinationType() : i->data.station == sort_val) {
+ i->data.count += cp->Count();
+ return &*i;
+ }
+ }
+
+ /* No entry found, add new. */
+ list.push_back(CargoDestEntry(type, sort_val, cp->Count(), cp->DestinationType()));
+ return &list.back();
+}
+
+
/**
* The StationView window
*/
@@ -808,6 +943,10 @@ struct StationViewWindow : public Window {
int rating_lines; ///< Number of lines in the cargo ratings view.
int accepts_lines; ///< Number of lines in the accepted cargo view.
Scrollbar *vscroll;
+ CargoDestEntry::List cargodest_list[NUM_CARGO]; ///< List of cargoes sorted by destination.
+
+ static StringID last_cargo_from_str;
+ static StringID last_cargo_from_tooltip;
/** Height of the #WID_SV_ACCEPT_RATING_LIST widget for different views. */
enum AcceptListHeight {
@@ -822,6 +961,7 @@ struct StationViewWindow : public Window {
this->CreateNestedTree(desc);
this->vscroll = this->GetScrollbar(WID_SV_SCROLLBAR);
+ this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA)->SetDataTip(StationViewWindow::last_cargo_from_str, StationViewWindow::last_cargo_from_tooltip);
/* Nested widget tree creation is done in two steps to ensure that this->GetWidget<NWidgetCore>(WID_SV_ACCEPTS_RATINGS) exists in UpdateWidgetSize(). */
this->FinishInitNested(desc, window_number);
@@ -866,9 +1006,30 @@ struct StationViewWindow : public Window {
{
CargoDataList cargolist;
uint32 transfers = 0;
- this->OrderWaitingCargo(&cargolist, &transfers);
- this->vscroll->SetCount((int)cargolist.size() + 1); // update scrollbar
+ NWidgetCore *cargo_btn = this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA);
+ if (cargo_btn->widget_data == STR_STATION_VIEW_WAITING_TO_BUTTON) {
+ this->OrderWaitingCargo(&cargolist, &transfers);
+ this->vscroll->SetCount((int)cargolist.size() + 1); // update scrollbar
+ } else {
+ /* Determine the current view. */
+ CargoDestEntry::Type dest_type;
+ switch (cargo_btn->widget_data) {
+ case STR_STATION_VIEW_WAITING_VIA_BUTTON:
+ dest_type = CargoDestEntry::FINAL_DEST;
+ break;
+ case STR_STATION_VIEW_WAITING_TRANSFER_BUTTON:
+ dest_type = CargoDestEntry::NEXT_HOP;
+ break;
+ case STR_STATION_VIEW_WAITING_BUTTON:
+ dest_type = CargoDestEntry::TRANSFER_HOP;
+ break;
+ default:
+ NOT_REACHED();
+ }
+ int num = this->FillCargodestList(dest_type, this->cargodest_list);
+ this->vscroll->SetCount(num + 1); // update scrollbar
+ }
/* disable some buttons */
const Station *st = Station::Get(this->window_number);
@@ -905,7 +1066,11 @@ struct StationViewWindow : public Window {
/* Draw waiting cargo. */
NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_SV_WAITING);
Rect waiting_rect = {nwi->pos_x, nwi->pos_y, nwi->pos_x + nwi->current_x - 1, nwi->pos_y + nwi->current_y - 1};
- this->DrawWaitingCargo(waiting_rect, cargolist, transfers);
+ if (cargo_btn->widget_data == STR_STATION_VIEW_WAITING_TO_BUTTON) {
+ this->DrawWaitingCargo(waiting_rect, cargolist, transfers);
+ } else {
+ this->DrawWaitingCargoByDest(waiting_rect, this->cargodest_list);
+ }
}
}
@@ -959,7 +1124,7 @@ struct StationViewWindow : public Window {
/* Check if we already have this source in the list */
for (CargoDataList::iterator jt(cargolist->begin()); jt != cargolist->end(); jt++) {
CargoData *cd = &(*jt);
- if (cd->cargo == i && cd->source == cp->SourceStation()) {
+ if (cd->cargo == i && cd->station == cp->SourceStation()) {
cd->count += cp->Count();
added = true;
break;
@@ -974,6 +1139,70 @@ struct StationViewWindow : public Window {
}
/**
+ * Fill cargo list sorted by type and destination/next hop.
+ * @param sort_via Set to true to sort by next hop, false to sort by final destination.
+ * @param list Cargo list to fill.
+ * @return Number of visible lines.
+ */
+ int FillCargodestList(CargoDestEntry::Type sort_by, CargoDestEntry::List *list)
+ {
+ StationID station_id = this->window_number;
+ const Station *st = Station::Get(station_id);
+
+ int lines = 0;
+
+ /* Fill the list for each cargo type. */
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ /* Zero out all existing items. */
+ for (CargoDestEntry::List::iterator i = list[cid].begin(); i != list[cid].end(); ++i) {
+ i->Zero();
+ }
+
+ /* Remove all entries if no cargo of this type is present. */
+ if (st->goods[cid].cargo.Empty()) {
+ this->cargo_rows[cid] = 0;
+ list[cid].clear();
+ continue;
+ }
+
+ /* Store line number of the header line. */
+ this->cargo_rows[cid] = ++lines;
+
+ /* Add each cargo packet to the list. */
+ const StationCargoList::List *packets = st->goods[cid].cargo.Packets();
+ for (StationCargoList::ConstIterator it = packets->begin(); it != packets->end(); ++it) {
+ const CargoPacket *cp = *it;
+
+ /* Add entry and sub-entries according to the chosen sort type. */
+ static const CargoDestEntry::Type sort_types[][3] = {
+ {CargoDestEntry::FINAL_DEST, CargoDestEntry::NEXT_HOP, CargoDestEntry::TRANSFER_HOP},
+ {CargoDestEntry::NEXT_HOP, CargoDestEntry::TRANSFER_HOP, CargoDestEntry::FINAL_DEST},
+ {CargoDestEntry::TRANSFER_HOP, CargoDestEntry::NEXT_HOP, CargoDestEntry::FINAL_DEST}
+ };
+
+ CargoDestEntry *entry = AddCargoPacketToList(list[cid], sort_types[sort_by][0], cp, st->goods[cid]);
+ if (entry != NULL) {
+ entry = AddCargoPacketToList(entry->children, sort_types[sort_by][1], cp, st->goods[cid]);
+ if (entry != NULL) AddCargoPacketToList(entry->children, sort_types[sort_by][2], cp, st->goods[cid]);
+ }
+ }
+
+ /* Remove all empty list items and update visible row numbers. */
+ for (CargoDestEntry::List::iterator i = list[cid].begin(); i != list[cid].end(); ) {
+ if (i->data.count > 0) {
+ i->RemoveEmpty();
+ if (HasBit(this->cargo, cid)) lines = i->UpdateRowCount(lines);
+ ++i;
+ } else {
+ i = list[cid].erase(i);
+ }
+ }
+ }
+
+ return lines;
+ }
+
+ /**
* Draw waiting cargo.
* @param r Rectangle of the widget.
* @param cargolist Cargo, ordered by type and destination.
@@ -1006,7 +1235,7 @@ struct StationViewWindow : public Window {
for (CargoDataList::const_iterator it = cargolist.begin(); it != cargolist.end() && pos > -maxrows; ++it) {
if (--pos < 0) {
const CargoData *cd = &(*it);
- if (cd->source == INVALID_STATION) {
+ if (cd->station == INVALID_STATION) {
/* Heading */
DrawCargoIcons(cd->cargo, cd->count, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y);
SetDParam(0, cd->cargo);
@@ -1022,7 +1251,7 @@ struct StationViewWindow : public Window {
} else {
SetDParam(0, cd->cargo);
SetDParam(1, cd->count);
- SetDParam(2, cd->source);
+ SetDParam(2, cd->station);
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_EN_ROUTE_FROM, TC_FROMSTRING, SA_RIGHT);
}
@@ -1032,6 +1261,111 @@ struct StationViewWindow : public Window {
}
/**
+ * Draw a dest entry and its children.
+ * @param cid Current cargo type.
+ * @param pos Scroll position
+ * @param maxrows Number of visible rows.
+ * @param left Left string bound.
+ * @param right Right string bound.
+ * @param shrink_left Left bound of the expand marker.
+ * @param shrink_right Right bound of the expand marker.
+ * @param offs_left Child offset of the left bound.
+ * @param offs_right Child offset of the right bound.
+ * @param y Top of the current line.
+ * @param entry The entry to draw.
+ * @return The new y value.
+ */
+ int DrawSingleDestEntry(CargoID cid, int *pos, int maxrows, int left, int right, int shrink_left, int shrink_right, int offs_left, int offs_right, int y, const CargoDestEntry &entry) const
+ {
+ if (--(*pos) < 0) {
+ /* Draw current line. */
+ StringID str;
+
+ SetDParam(0, cid);
+ SetDParam(1, entry.data.count);
+ if (entry.type == CargoDestEntry::FINAL_DEST) {
+ SetDParam(2, entry.data.type == ST_INDUSTRY ? STR_INDUSTRY_NAME : (entry.data.type == ST_TOWN ? STR_TOWN_NAME : STR_COMPANY_NAME));
+ SetDParam(3, entry.data.css);
+ str = STR_STATION_VIEW_WAITING_TO;
+ } else {
+ SetDParam(2, entry.data.station);
+ str = (entry.type == CargoDestEntry::NEXT_HOP) ? STR_STATION_VIEW_WAITING_VIA : STR_STATION_VIEW_WAITING_TRANSFER;
+ }
+ DrawString(left, right, y, str);
+ y += FONT_HEIGHT_NORMAL;
+
+ if (!entry.children.empty()) {
+ /* Draw expand/collapse marker. */
+ DrawString(shrink_left, shrink_right, y - FONT_HEIGHT_NORMAL, entry.expanded ? "-" : "+", TC_YELLOW, SA_RIGHT);
+
+ if (entry.expanded) {
+ /* Draw visible children. */
+ for (CargoDestEntry::List::const_iterator i = entry.children.begin(); i != entry.children.end() && *pos > -maxrows; ++i) {
+ y = this->DrawSingleDestEntry(cid, pos, maxrows, left + offs_left, right + offs_right, shrink_left, shrink_right, offs_left, offs_right, y, *i);
+ }
+ }
+ }
+ }
+
+ return y;
+ }
+
+ /**
+ * Draw waiting cargo ordered by destination/next hop.
+ * @param r Rectangle of the widget.
+ * @param list List to draw.
+ */
+ void DrawWaitingCargoByDest(const Rect &r, const CargoDestEntry::List *list) const
+ {
+ int y = r.top + WD_FRAMERECT_TOP;
+ int pos = this->vscroll->GetPosition();
+
+ const Station *st = Station::Get(this->window_number);
+ if (--pos < 0) {
+ StringID str = STR_JUST_NOTHING;
+ for (CargoID i = 0; i < NUM_CARGO; i++) {
+ if (!st->goods[i].cargo.Empty()) str = STR_EMPTY;
+ }
+ SetDParam(0, str);
+ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_WAITING_TITLE);
+ y += FONT_HEIGHT_NORMAL;
+ }
+
+ bool rtl = _current_text_dir == TD_RTL;
+ int text_left = rtl ? r.left + this->expand_shrink_width : r.left + WD_FRAMERECT_LEFT;
+ int text_right = rtl ? r.right - WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width;
+ int shrink_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - this->expand_shrink_width + WD_FRAMERECT_LEFT;
+ int shrink_right = rtl ? r.left + this->expand_shrink_width - WD_FRAMERECT_RIGHT : r.right - WD_FRAMERECT_RIGHT;
+
+ int offs_left = rtl ? 0 : this->expand_shrink_width;
+ int offs_right = rtl ? this->expand_shrink_width : 0;
+
+ int maxrows = this->vscroll->GetCapacity();
+ for (CargoID cid = 0; cid < NUM_CARGO && pos > -maxrows; cid++) {
+ if (st->goods[cid].cargo.Empty()) continue;
+
+ if (--pos < 0) {
+ /* Draw heading. */
+ DrawCargoIcons(cid, st->goods[cid].cargo.Count(), r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMERECT_RIGHT, y);
+ SetDParam(0, cid);
+ SetDParam(1, st->goods[cid].cargo.Count());
+ DrawString(text_left, text_right, y, STR_STATION_VIEW_WAITING_CARGO, TC_FROMSTRING, SA_RIGHT);
+ if (!list[cid].empty()) {
+ DrawString(shrink_left, shrink_right, y, HasBit(this->cargo, cid) ? "-" : "+", TC_YELLOW, SA_RIGHT);
+ }
+ y += FONT_HEIGHT_NORMAL;
+ }
+
+ /* Draw sub-entries. */
+ if (HasBit(this->cargo, cid)) {
+ for (CargoDestEntry::List::const_iterator i = list[cid].begin(); i != list[cid].end() && pos > -maxrows; ++i) {
+ y = this->DrawSingleDestEntry(cid, &pos, maxrows, text_left + offs_left, text_right + offs_right, shrink_left, shrink_right, offs_left, offs_right, y, *i);
+ }
+ }
+ }
+ }
+
+ /**
* Draw accepted cargo in the #WID_SV_ACCEPT_RATING_LIST widget.
* @param r Rectangle of the widget.
* @return Number of lines needed for drawing the accepted cargo.
@@ -1082,16 +1416,93 @@ struct StationViewWindow : public Window {
return CeilDiv(y - r.top - WD_FRAMERECT_TOP, FONT_HEIGHT_NORMAL);
}
+ /**
+ * Test and handle a possible mouse click on a dest entry and its children.
+ * @param entry The entry to test for a hit.
+ * @param row The number of the clicked row.
+ * @return True if further entries need to be processed.
+ */
+ bool HandleCargoDestEntryClick(CargoDestEntry &entry, int row)
+ {
+ if (entry.start_row == row) {
+ if (_ctrl_pressed) {
+ /* Scroll viewport to destination tile .*/
+ TileIndex dest_tile = 0;
+ switch (entry.type) {
+ case CargoDestEntry::FINAL_DEST:
+ switch (entry.data.type) {
+ case ST_INDUSTRY:
+ dest_tile = Industry::Get(entry.data.css)->location.tile;
+ break;
+ case ST_TOWN:
+ dest_tile = Town::Get(entry.data.css)->xy;
+ break;
+ case ST_HEADQUARTERS:
+ dest_tile = Company::Get(entry.data.css)->location_of_HQ;
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+ break;
+
+ case CargoDestEntry::NEXT_HOP:
+ case CargoDestEntry::TRANSFER_HOP:
+ dest_tile = Station::Get(entry.data.station)->xy;
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+ ScrollMainWindowToTile(dest_tile);
+ } else if (!entry.children.empty()) {
+ /* Expand/collapse entry. */
+ entry.expanded = !entry.expanded;
+ this->SetWidgetDirty(WID_SV_WAITING);
+ this->SetWidgetDirty(WID_SV_SCROLLBAR);
+ }
+ }
+
+ if (entry.start_row < row) {
+ /* Test child entries. */
+ for (CargoDestEntry::List::iterator i = entry.children.begin(); i != entry.children.end(); ++i) {
+ if (!this->HandleCargoDestEntryClick(*i, row)) return false;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
void HandleCargoWaitingClick(int row)
{
if (row == 0) return;
+ bool dest_view = this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA)->widget_data != STR_STATION_VIEW_WAITING_TO_BUTTON;
+
for (CargoID c = 0; c < NUM_CARGO; c++) {
+ /* Test for cargo type line. */
if (this->cargo_rows[c] == row) {
ToggleBit(this->cargo, c);
this->SetWidgetDirty(WID_SV_WAITING);
+ this->SetWidgetDirty(WID_SV_SCROLLBAR);
break;
}
+
+ if (dest_view) {
+ /* Test for dest view lines. */
+ for (CargoDestEntry::List::iterator i = this->cargodest_list[c].begin(); i != this->cargodest_list[c].end(); ++i) {
+ if (!this->HandleCargoDestEntryClick(*i, row)) break;
+ }
+ }
+ }
+ }
+
+ /** Clear the 'cargo by destination' list. */
+ void ClearCargodestList()
+ {
+ for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
+ this->cargodest_list[cid].clear();
}
}
@@ -1125,6 +1536,38 @@ struct StationViewWindow : public Window {
break;
}
+ case WID_SV_CARGO_FROM_TO_VIA: {
+ /* Swap between 'Source', 'Destination', 'Next hop' and 'Transfer' view.
+ * Store the new view so the next opened station window shows the same view. */
+ NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_SV_CARGO_FROM_TO_VIA);
+ switch (nwi->widget_data) {
+ case STR_STATION_VIEW_WAITING_BUTTON:
+ StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_TO_BUTTON;
+ StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TO_TOOLTIP;
+ break;
+ case STR_STATION_VIEW_WAITING_TO_BUTTON:
+ StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_VIA_BUTTON;
+ StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_VIA_TOOLTIP;
+ break;
+ case STR_STATION_VIEW_WAITING_VIA_BUTTON:
+ StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_TRANSFER_BUTTON;
+ StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TRANSFER_TOOLTIP;
+ break;
+ case STR_STATION_VIEW_WAITING_TRANSFER_BUTTON:
+ StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_BUTTON;
+ StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_TOOLTIP;
+ break;
+ default:
+ NOT_REACHED();
+ }
+ nwi->SetDataTip(StationViewWindow::last_cargo_from_str, StationViewWindow::last_cargo_from_tooltip);
+ this->ClearCargodestList();
+ this->SetWidgetDirty(WID_SV_CARGO_FROM_TO_VIA);
+ this->SetWidgetDirty(WID_SV_WAITING);
+ this->SetWidgetDirty(WID_SV_SCROLLBAR);
+ break;
+ }
+
case WID_SV_RENAME:
SetDParam(0, this->window_number);
ShowQueryString(STR_STATION_NAME, STR_STATION_VIEW_RENAME_STATION_CAPTION, MAX_LENGTH_STATION_NAME_CHARS,
@@ -1142,7 +1585,11 @@ struct StationViewWindow : public Window {
Owner owner = Station::Get(this->window_number)->owner;
ShowVehicleListWindow(owner, (VehicleType)(widget - WID_SV_TRAINS), (StationID)this->window_number);
break;
- }
+ }
+
+ case WID_SV_DEPARTURES:
+ ShowStationDepartures((StationID)this->window_number);
+ break;
}
}
@@ -1169,6 +1616,8 @@ struct StationViewWindow : public Window {
}
};
+StringID StationViewWindow::last_cargo_from_str = STR_STATION_VIEW_WAITING_VIA_BUTTON;
+StringID StationViewWindow::last_cargo_from_tooltip = STR_STATION_VIEW_WAITING_VIA_TOOLTIP;
static const WindowDesc _station_view_desc(
WDP_AUTO, 249, 110,
@@ -1254,8 +1703,15 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join)
_deleted_stations_nearby.Clear();
/* Check the inside, to return, if we sit on another station */
- TILE_AREA_LOOP(t, ta) {
- if (t < MapSize() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t))) return T::GetByTile(t);
+ FOR_ALL_LAYERS(layer) {
+ TILE_AREA_LOOP(tile, ta) {
+ TileIndex t = TopTile(tile) + layer * LayerSize();
+ if (t < MapSize() && IsTileType(t, MP_STATION) && T::IsValidID(GetStationIndex(t)))
+ {
+ if (t == tile) return T::GetByTile(t);
+ AddNearbyStation<T>(t, &ctx);
+ }
+ }
}
/* Look for deleted stations */
@@ -1263,14 +1719,14 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join)
FOR_ALL_BASE_STATIONS(st) {
if (T::IsExpected(st) && !st->IsInUse() && st->owner == _local_company) {
/* Include only within station spread (yes, it is strictly less than) */
- if (max(DistanceMax(ta.tile, st->xy), DistanceMax(TILE_ADDXY(ta.tile, ta.w - 1, ta.h - 1), st->xy)) < _settings_game.station.station_spread) {
+ if (max(DistanceMax(TopTile(ta.tile), TopTile(st->xy)), DistanceMax(TILE_ADDXY(TopTile(ta.tile), ta.w - 1, ta.h - 1), TopTile(st->xy))) < _settings_game.station.station_spread) {
TileAndStation *ts = _deleted_stations_nearby.Append();
ts->tile = st->xy;
ts->station = st->index;
/* Add the station when it's within where we're going to build */
- if (IsInsideBS(TileX(st->xy), TileX(ctx.tile), ctx.w) &&
- IsInsideBS(TileY(st->xy), TileY(ctx.tile), ctx.h)) {
+ if (IsInsideBS(LayerX(st->xy), LayerX(ctx.tile), ctx.w) &&
+ IsInsideBS(LayerY(st->xy), LayerY(ctx.tile), ctx.h)) {
AddNearbyStation<T>(st->xy, &ctx);
}
}
@@ -1283,8 +1739,11 @@ static const T *FindStationsNearby(TileArea ta, bool distant_join)
if (distant_join && min(ta.w, ta.h) >= _settings_game.station.station_spread) return NULL;
uint max_dist = distant_join ? _settings_game.station.station_spread - min(ta.w, ta.h) : 1;
- TileIndex tile = TILE_ADD(ctx.tile, TileOffsByDir(DIR_N));
- CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx);
+ FOR_ALL_LAYERS(layer) {
+ ctx.tile = TopTile(ctx.tile) + layer * LayerSize();
+ TileIndex tile = TILE_ADD(ctx.tile, TileOffsByDir(DIR_N));
+ CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyStation<T>, &ctx);
+ }
return NULL;
}
diff --git a/src/statusbar_gui.cpp b/src/statusbar_gui.cpp
index f8d39ceaf..7ac28b5eb 100644
--- a/src/statusbar_gui.cpp
+++ b/src/statusbar_gui.cpp
@@ -131,8 +131,8 @@ struct StatusBarWindow : Window {
switch (widget) {
case WID_S_LEFT:
/* Draw the date */
- SetDParam(0, _date);
- DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_WHITE_DATE_LONG, TC_FROMSTRING, SA_HOR_CENTER);
+ SetDParam(0, ((DateTicks)_date * DAY_TICKS) + _date_fract);
+ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_WHITE_DATE_WALLCLOCK_LONG, TC_FROMSTRING, SA_HOR_CENTER);
break;
case WID_S_RIGHT: {
@@ -213,6 +213,10 @@ struct StatusBarWindow : Window {
{
if (_pause_mode != PM_UNPAUSED) return;
+ if (_settings_client.gui.time_in_minutes) {
+ this->SetWidgetDirty(WID_S_LEFT);
+ }
+
if (this->ticker_scroll < TICKER_STOP) { // Scrolling text
this->ticker_scroll += COUNTER_STEP;
this->SetWidgetDirty(WID_S_MIDDLE);
@@ -229,7 +233,7 @@ struct StatusBarWindow : Window {
static const NWidgetPart _nested_main_status_widgets[] = {
NWidget(NWID_HORIZONTAL),
- NWidget(WWT_PANEL, COLOUR_GREY, WID_S_LEFT), SetMinimalSize(140, 12), EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_GREY, WID_S_LEFT), SetMinimalSize(160, 12), EndContainer(),
NWidget(WWT_PUSHBTN, COLOUR_GREY, WID_S_MIDDLE), SetMinimalSize(40, 12), SetDataTip(0x0, STR_STATUSBAR_TOOLTIP_SHOW_LAST_NEWS), SetResize(1, 0),
NWidget(WWT_PUSHBTN, COLOUR_GREY, WID_S_RIGHT), SetMinimalSize(140, 12),
EndContainer(),
diff --git a/src/strings.cpp b/src/strings.cpp
index 94de57f6f..8f69a2cc5 100644
--- a/src/strings.cpp
+++ b/src/strings.cpp
@@ -413,6 +413,29 @@ static char *FormatBytes(char *buff, int64 number, const char *last)
return buff;
}
+static char *FormatWallClockString(char *buff, DateTicks ticks, const char *last, bool show_date, uint case_index)
+{
+ Minutes minutes = ticks / _settings_client.gui.ticks_per_minute + _settings_client.gui.clock_offset;
+ char hour[3], minute[3];
+ snprintf(hour, lengthof(hour), "%02i", MINUTES_HOUR(minutes) );
+ snprintf(minute, lengthof(minute), "%02i", MINUTES_MINUTE(minutes));
+ if (show_date) {
+ int64 args[3] = { (int64)hour, (int64)minute, (int64)ticks / DAY_TICKS };
+ if (_settings_client.gui.date_with_time == 1) {
+ YearMonthDay ymd;
+ ConvertDateToYMD(args[2], &ymd);
+ args[2] = ymd.year;
+ }
+
+ StringParameters tmp_params(args);
+ return FormatString(buff, GetStringPtr(STR_FORMAT_DATE_MINUTES + _settings_client.gui.date_with_time), &tmp_params, last, case_index);
+ } else {
+ int64 args[2] = { (int64)hour, (int64)minute };
+ StringParameters tmp_params(args);
+ return FormatString(buff, GetStringPtr(STR_FORMAT_DATE_MINUTES), &tmp_params, last, case_index);
+ }
+}
+
static char *FormatYmdString(char *buff, Date date, const char *last, uint case_index)
{
YearMonthDay ymd;
@@ -1215,6 +1238,42 @@ static char *FormatString(char *buff, const char *str_arg, StringParameters *arg
next_substr_case_index = 0;
break;
+ case SCC_DATE_WALLCLOCK_LONG: { // {DATE_WALLCLOCK_LONG}
+ if (_settings_client.gui.time_in_minutes) {
+ buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_LONG), last, _settings_client.gui.date_with_time, next_substr_case_index);
+ } else {
+ buff = FormatYmdString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_LONG) / DAY_TICKS, last, next_substr_case_index);
+ }
+ break;
+ }
+
+ case SCC_DATE_WALLCLOCK_SHORT: { // {DATE_WALLCLOCK_SHORT}
+ if (_settings_client.gui.time_in_minutes) {
+ buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_SHORT), last, _settings_client.gui.date_with_time, next_substr_case_index);
+ } else {
+ buff = FormatYmdString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_SHORT) / DAY_TICKS, last, next_substr_case_index);
+ }
+ break;
+ }
+
+ case SCC_DATE_WALLCLOCK_TINY: { // {DATE_WALLCLOCK_TINY}
+ if (_settings_client.gui.time_in_minutes) {
+ buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_TINY), last, false, next_substr_case_index);
+ } else {
+ buff = FormatTinyOrISODate(buff, args->GetInt64(SCC_DATE_WALLCLOCK_TINY) / DAY_TICKS, STR_FORMAT_DATE_TINY, last);
+ }
+ break;
+ }
+
+ case SCC_DATE_WALLCLOCK_ISO: { // {DATE_WALLCLOCK_ISO}
+ if (_settings_client.gui.time_in_minutes) {
+ buff = FormatWallClockString(buff, args->GetInt64(SCC_DATE_WALLCLOCK_ISO), last, false, next_substr_case_index);
+ } else {
+ buff = FormatTinyOrISODate(buff, args->GetInt64(SCC_DATE_WALLCLOCK_ISO) / DAY_TICKS, STR_FORMAT_DATE_ISO, last);
+ }
+ break;
+ }
+
case SCC_DATE_ISO: // {DATE_ISO}
buff = FormatTinyOrISODate(buff, args->GetInt32(), STR_FORMAT_DATE_ISO, last);
break;
@@ -1825,6 +1884,7 @@ bool ReadLanguagePack(const LanguageMetadata *lang)
/* Some lists need to be sorted again after a language change. */
ReconsiderGameScriptLanguage();
InitializeSortedCargoSpecs();
+ BuildCargoTypesLegend();
SortIndustryTypes();
BuildIndustriesLegend();
SortNetworkLanguages();
diff --git a/src/subsidy.cpp b/src/subsidy.cpp
index c69b2b23d..eb055f4b9 100644
--- a/src/subsidy.cpp
+++ b/src/subsidy.cpp
@@ -24,6 +24,7 @@
#include "core/random_func.hpp"
#include "game/game.hpp"
#include "command_func.h"
+#include "cargodest_func.h"
#include "table/strings.h"
@@ -296,13 +297,22 @@ bool FindSubsidyPassengerRoute()
{
if (!Subsidy::CanAllocateItem()) return false;
- const Town *src = Town::GetRandom();
+ Town *src = Town::GetRandom();
if (src->cache.population < SUBSIDY_PAX_MIN_POPULATION ||
src->GetPercentTransported(CT_PASSENGERS) > SUBSIDY_MAX_PCT_TRANSPORTED) {
return false;
}
- const Town *dst = Town::GetRandom();
+ const Town *dst = NULL;
+ if (CargoHasDestinations(CT_PASSENGERS)) {
+ /* Try to get a town from the demand destinations. */
+ CargoLink *link = src->GetRandomLink(CT_PASSENGERS, false);
+ if (link == src->cargo_links[CT_PASSENGERS].End()) return NULL;
+ if (link->dest != NULL && link->dest->GetType() != ST_TOWN) return NULL;
+ dst = static_cast<const Town *>(link->dest);
+ }
+ if (dst == NULL) dst = Town::GetRandom();
+
if (dst->cache.population < SUBSIDY_PAX_MIN_POPULATION || src == dst) {
return false;
}
@@ -408,11 +418,22 @@ bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src)
/* Choose a random destination. Only consider towns if they can accept the cargo. */
SourceType dst_type = (HasBit(_town_cargoes_accepted, cid) && Chance16(1, 2)) ? ST_TOWN : ST_INDUSTRY;
+ CargoSourceSink *src_sink = (src_type == ST_TOWN) ? (CargoSourceSink *) Town::Get(src) : (CargoSourceSink *) Industry::Get(src);
+
SourceID dst;
switch (dst_type) {
case ST_TOWN: {
/* Select a random town. */
- const Town *dst_town = Town::GetRandom();
+ const Town *dst_town = NULL;
+
+ if (CargoHasDestinations(cid)) {
+ /* Try to get a town from the demand destinations. */
+ CargoLink *link = src_sink->GetRandomLink(cid, false);
+ if (link == src_sink->cargo_links[cid].End()) return NULL;
+ if (link->dest != NULL && link->dest->GetType() != dst_type) return NULL;
+ dst_town = static_cast<const Town *>(link->dest);
+ }
+ if (dst_town == NULL) dst_town = Town::GetRandom();
/* Check if the town can accept this cargo. */
if (!HasBit(dst_town->cargo_accepted_total, cid)) return false;
@@ -425,15 +446,24 @@ bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src)
/* Select a random industry. */
const Industry *dst_ind = Industry::GetRandom();
+ if (CargoHasDestinations(cid)) {
+ /* Try to get a town from the demand destinations. */
+ CargoLink *link = src_sink->GetRandomLink(cid, false);
+ if (link == src_sink->cargo_links[cid].End()) return NULL;
+ if (link->dest != NULL && link->dest->GetType() != dst_type) return NULL;
+ dst_ind = static_cast<const Industry *>(link->dest);
+ }
+ if (dst_ind == NULL) dst_ind = Industry::GetRandom();
+
+ dst = dst_ind->index;
+
/* The industry must accept the cargo */
- if (dst_ind == NULL ||
+ if (dst_ind == NULL || (src_type == dst_type && src == dst) ||
(cid != dst_ind->accepts_cargo[0] &&
cid != dst_ind->accepts_cargo[1] &&
cid != dst_ind->accepts_cargo[2])) {
return false;
}
-
- dst = dst_ind->index;
break;
}
diff --git a/src/table/control_codes.h b/src/table/control_codes.h
index 50233d5f6..5ab5331f9 100644
--- a/src/table/control_codes.h
+++ b/src/table/control_codes.h
@@ -68,6 +68,10 @@ enum StringControlCode {
SCC_DATE_SHORT,
SCC_DATE_LONG,
SCC_DATE_ISO,
+ SCC_DATE_WALLCLOCK_TINY,
+ SCC_DATE_WALLCLOCK_SHORT,
+ SCC_DATE_WALLCLOCK_LONG,
+ SCC_DATE_WALLCLOCK_ISO,
/* Must be consecutive */
SCC_STRING1,
diff --git a/src/table/settings.ini b/src/table/settings.ini
index b39d29a54..897cb5a5d 100644
--- a/src/table/settings.ini
+++ b/src/table/settings.ini
@@ -40,6 +40,8 @@ static bool RedrawTownAuthority(int32 p1);
static bool InvalidateCompanyInfrastructureWindow(int32 p1);
static bool InvalidateCompanyWindow(int32 p1);
static bool ZoomMinMaxChanged(int32 p1);
+static bool TLSettingChanged(int32 p1);
+bool CargodestModeChanged(int32 p1);
#ifdef ENABLE_NETWORK
static bool UpdateClientName(int32 p1);
@@ -70,6 +72,7 @@ SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def,
SDTC_STR = SDTC_STR( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
SDTC_VAR = SDTC_VAR( $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
+SDT_LIST = SDT_LIST($base, $var, $type, $flags, $guiflags, "$def", $str, $strhelp, $strval, $proc, $from, $to, $cat),
SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat),
SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
@@ -553,6 +556,18 @@ strhelp = STR_CONFIG_SETTING_ALLOW_TOWN_ROADS_HELPTEXT
[SDT_VAR]
base = GameSettings
+var = economy.random_road_construction
+type = SLE_UINT8
+from = TL_SV
+def = 7
+min = 0
+max = 100
+interval = 1
+str = STR_CONFIG_PATCHES_RANDOM_ROAD_CONSTRUCTION
+strval = STR_JUST_INT
+
+[SDT_VAR]
+base = GameSettings
var = economy.found_town
type = SLE_UINT8
from = 128
@@ -968,6 +983,13 @@ cat = SC_BASIC
length = 1
to = 158
+[SDT_BOOL]
+base = GameSettings
+var = order.automatic_timetable_separation
+from = SL_TTSEP_VER
+def = true
+str = STR_CONFIG_SETTING_TIMETABLE_ENABLE_SEPARATION
+
[SDTC_BOOL]
var = gui.sg_full_load_any
from = 22
@@ -1292,6 +1314,242 @@ str = STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD
strhelp = STR_CONFIG_SETTING_MODIFIED_ROAD_REBUILD_HELPTEXT
cat = SC_EXPERT
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.mode_pax_mail
+type = SLE_UINT8
+from = 180
+guiflags = SGF_MULTISTRING
+def = 0
+min = 0
+max = 1
+interval = 1
+str = STR_CONFIG_SETTING_CARGODEST_PAX
+strhelp = STR_CONFIG_SETTING_CARGODEST_PAX_HELPTEXT
+strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF
+proc = CargodestModeChanged
+cat = SC_BASIC
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.mode_town_cargo
+type = SLE_UINT8
+from = 180
+guiflags = SGF_MULTISTRING
+def = 0
+min = 0
+max = 1
+interval = 1
+str = STR_CONFIG_SETTING_CARGODEST_TOWN
+strhelp = STR_CONFIG_SETTING_CARGODEST_TOWN_HELPTEXT
+strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF
+proc = CargodestModeChanged
+cat = SC_BASIC
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.mode_others
+type = SLE_UINT8
+from = 180
+guiflags = SGF_MULTISTRING
+def = 0
+min = 0
+max = 1
+interval = 1
+str = STR_CONFIG_SETTING_CARGODEST_OTHER
+strhelp = STR_CONFIG_SETTING_CARGODEST_OTHER_HELPTEXT
+strval = STR_CONFIG_SETTING_CARGODEST_MODE_OFF
+proc = CargodestModeChanged
+cat = SC_BASIC
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.base_town_links
+type = SLE_UINT8
+from = 180
+def = 3,3
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.base_ind_links
+type = SLE_UINT8
+from = 180
+def = 2,4,1
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.city_town_links
+type = SLE_UINT8
+from = 180
+def = 8
+min = 0
+max = 255
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.town_chances_town
+type = SLE_UINT8
+from = 180
+def = 100,100,100,100
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.town_chances_city
+type = SLE_UINT8
+from = 180
+def = 70,100,100,100
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.ind_chances
+type = SLE_UINT8
+from = 180
+def = 90,95,100
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.random_dest_chance
+type = SLE_UINT8
+from = 180
+def = 5
+min = 0
+max = 99
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.big_town_pop
+type = SLE_UINT32
+from = 180
+def = 500,2000
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.pop_scale_town
+type = SLE_UINT16
+from = 180
+def = 100,180,200,1000
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.cargo_scale_ind
+type = SLE_UINT16
+from = 180
+def = 250,200
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.min_weight_town
+type = SLE_UINT16
+from = 180
+def = 5,5
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.min_weight_ind
+type = SLE_UINT16
+from = 180
+def = 10
+min = 0
+max = 1000
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.weight_scale_town
+type = SLE_UINT16
+from = 180
+def = 10,40,20,80
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = economy.cargodest.weight_scale_ind
+type = SLE_UINT16
+from = 180
+def = 20,50
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.town_nearby_dist
+type = SLE_UINT32
+from = 180
+def = 48*48
+min = 1
+max = UINT32_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.ind_nearby_dist
+type = SLE_UINT32
+from = 180
+def = 64*64
+min = 1
+max = UINT32_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.max_route_age
+type = SLE_UINT16
+from = 180
+def = 2*DAYS_IN_YEAR
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.route_recalc_delay
+type = SLE_UINT16
+from = 180
+def = 20
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.route_recalc_chunk
+type = SLE_UINT16
+from = 180
+def = 15
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.max_route_penalty[0]
+type = SLE_UINT16
+from = 180
+def = 200
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = economy.cargodest.max_route_penalty[1]
+type = SLE_UINT16
+from = 180
+def = 150
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
; previously ai-new setting.
[SDT_NULL]
length = 1
@@ -1363,6 +1621,68 @@ strhelp = STR_CONFIG_SETTING_SCRIPT_MAX_OPCODES_HELPTEXT
strval = STR_JUST_COMMA
cat = SC_EXPERT
+[SDT_BOOL]
+base = GameSettings
+var = construction.traffic_lights
+from = TL_SV
+def = true
+str = STR_CONFIG_SETTING_TRAFFIC_LIGHTS
+strhelp = STR_CONFIG_SETTING_TRAFFIC_LIGHTS_HELPTEXT
+proc = TLSettingChanged
+
+[SDT_BOOL]
+base = GameSettings
+var = construction.towns_build_traffic_lights
+from = TL_SV
+def = true
+str = STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS
+strhelp = STR_CONFIG_SETTING_TOWNS_BUILD_TRAFFIC_LIGHTS_HELPTEXT
+
+[SDT_BOOL]
+base = GameSettings
+var = construction.allow_building_tls_in_towns
+from = TL_SV
+def = false
+str = STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS
+strhelp = STR_CONFIG_SETTING_ALLOW_BUILDING_TLS_ON_TOWN_ROADS_HELPTEXT
+
+[SDT_VAR]
+base = GameSettings
+var = construction.traffic_lights_green_phase
+type = SLE_UINT8
+from = TL_SV
+def = 2
+min = 1
+max = 16
+str = STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE
+strhelp = STR_CONFIG_SETTING_TRAFFIC_LIGHTS_GREEN_PHASE_HELPTEXT
+strval = STR_JUST_INT
+
+[SDT_VAR]
+base = GameSettings
+var = construction.max_tlc_size
+type = SLE_UINT8
+from = TL_SV
+guiflags = SGF_0ISDISABLED
+def = 4
+min = 1
+max = 32
+str = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE
+strhelp = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_SIZE_HELPTEXT
+strval = STR_JUST_INT
+
+[SDT_VAR]
+base = GameSettings
+var = construction.max_tlc_distance
+type = SLE_UINT8
+from = TL_SV
+def = 1
+min = 0
+max = 4
+str = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE
+strhelp = STR_CONFIG_SETTING_MAX_TRAFFIC_LIGHT_CONSIST_DISTANCE_HELPTEXT
+strval = STR_CONFIG_SETTING_TILE_LENGTH
+
##
[SDT_VAR]
base = GameSettings
@@ -1636,6 +1956,16 @@ cat = SC_EXPERT
[SDT_VAR]
base = GameSettings
+var = pf.npf.npf_road_trafficlight_penalty
+type = SLE_UINT
+from = TL_SV
+def = 2 * NPF_TILE_LENGTH
+min = 0
+max = 100000
+str = STR_NULL
+
+[SDT_VAR]
+base = GameSettings
var = pf.npf.npf_road_dt_occupied_penalty
type = SLE_UINT
from = 130
@@ -1682,6 +2012,15 @@ min = 500
max = 1000000
cat = SC_EXPERT
+[SDT_VAR]
+base = GameSettings
+var = game_creation.layers
+type = SLE_UINT8
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = 1
+min = MIN_LAYER_COUNT_BITS
+max = MAX_LAYER_COUNT_BITS
+
[SDT_BOOL]
base = GameSettings
var = pf.yapf.rail_firstred_twoway_eol
@@ -1941,6 +2280,15 @@ cat = SC_EXPERT
[SDT_VAR]
base = GameSettings
+var = pf.yapf.road_trafficlight_penalty
+type = SLE_UINT
+from = TL_SV
+def = 2 * YAPF_TILE_LENGTH
+min = 0
+max = 1000000
+
+[SDT_VAR]
+base = GameSettings
var = pf.yapf.road_stop_penalty
type = SLE_UINT
from = 47
@@ -1979,6 +2327,74 @@ min = 0
max = 1000000
cat = SC_EXPERT
+[SDT_VAR]
+base = GameSettings
+var = pf.yapf.route_transfer_cost
+type = SLE_UINT
+from = 180
+def = 500
+min = 0
+max = 1000000
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = pf.yapf.route_max_transfers
+type = SLE_UINT16
+from = 180
+def = 5
+min = 0
+max = 100000
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = pf.yapf.route_distance_factor
+type = SLE_UINT16
+from = 180
+def = 8
+min = 0
+max = 1000
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = pf.yapf.route_travel_time_factor
+type = SLE_UINT16
+from = 180
+def = 32
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = pf.yapf.route_station_last_veh_factor
+type = SLE_UINT16
+from = 180
+def = 64
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
+[SDT_VAR]
+base = GameSettings
+var = pf.yapf.route_station_waiting_factor
+type = SLE_UINT16
+from = 180
+def = 128
+min = 0
+max = UINT16_MAX
+cat = SC_EXPERT
+
+[SDT_LIST]
+base = GameSettings
+var = pf.yapf.route_mode_cost_factor
+type = SLE_UINT8
+from = 180
+def = 4,2,1,8
+cat = SC_EXPERT
+
##
[SDT_VAR]
base = GameSettings
@@ -2271,6 +2687,7 @@ str = STR_CONFIG_SETTING_SHOWFINANCES
strhelp = STR_CONFIG_SETTING_SHOWFINANCES_HELPTEXT
cat = SC_BASIC
+
[SDTC_VAR]
var = gui.auto_scrolling
type = SLE_UINT8
@@ -2538,6 +2955,59 @@ proc = InvalidateVehTimetableWindow
cat = SC_EXPERT
[SDTC_BOOL]
+var = gui.time_in_minutes
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_SETTING_TIME_IN_MINUTES
+strhelp = STR_CONFIG_SETTING_TIME_IN_MINUTES_HELPTEXT
+proc = InvalidateVehTimetableWindow
+
+[SDTC_BOOL]
+var = gui.timetable_start_text_entry
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY
+strhelp = STR_CONFIG_SETTING_TIMETABLE_START_TEXT_ENTRY_HELPTEXT
+
+[SDTC_VAR]
+var = gui.ticks_per_minute
+type = SLE_UINT8
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+strval = STR_JUST_INT
+def = 74
+min = 1
+max = 255
+str = STR_CONFIG_SETTING_TICKS_PER_MINUTE
+strhelp = STR_CONFIG_SETTING_TICKS_PER_MINUTE_HELPTEXT
+proc = RedrawScreen
+
+[SDTC_VAR]
+var = gui.date_with_time
+type = SLE_UINT8
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+guiflags = SGF_MULTISTRING
+def = 0
+min = 0
+max = 3
+str = STR_CONFIG_SETTING_DATE_WITH_TIME
+strval = STR_CONFIG_SETTING_DATE_WITH_TIME_NONE
+strhelp = STR_CONFIG_SETTING_DATE_WITH_TIME_HELPTEXT
+proc = RedrawScreen
+
+[SDTC_VAR]
+var = gui.clock_offset
+type = SLE_UINT16
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+strval = STR_JUST_INT
+def = 0
+min = 0
+max = 1439
+interval = 60
+str = STR_CONFIG_SETTING_CLOCK_OFFSET
+strhelp = STR_CONFIG_SETTING_CLOCK_OFFSET_HELPTEXT
+proc = RedrawScreen
+
+[SDTC_BOOL]
var = gui.timetable_arrival_departure
flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
def = true
@@ -2545,6 +3015,139 @@ str = STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE
strhelp = STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE_HELPTEXT
proc = InvalidateVehTimetableWindow
+[SDTC_VAR]
+var = gui.max_departures
+type = SLE_UINT8
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+strval = STR_JUST_INT
+def = 10
+min = 1
+max = 30
+interval = 1
+str = STR_CONFIG_MAX_DEPARTURES
+strhelp = STR_CONFIG_MAX_DEPARTURES_HELPTEXT
+
+[SDTC_VAR]
+var = gui.max_departure_time
+type = SLE_UINT16
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+strval = STR_JUST_INT
+def = 120
+min = 30
+max = 240
+interval = 1
+str = STR_CONFIG_MAX_DEPARTURE_TIME
+strhelp = STR_CONFIG_MAX_DEPARTURE_TIME_HELPTEXT
+
+[SDTC_VAR]
+var = gui.departure_calc_frequency
+type = SLE_UINT16
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+strval = STR_JUST_INT
+def = 10
+min = 1
+max = 120
+interval = 1
+str = STR_CONFIG_DEPARTURE_CALC_FREQUENCY
+strhelp = STR_CONFIG_DEPARTURE_CALC_FREQUENCY_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_show_vehicle
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_VEHICLE_NAME
+strhelp = STR_CONFIG_DEPARTURE_VEHICLE_NAME_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_show_group
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_GROUP_NAME
+strhelp = STR_CONFIG_DEPARTURE_GROUP_NAME_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_show_company
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_COMPANY_NAME
+strhelp = STR_CONFIG_DEPARTURE_COMPANY_NAME_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_show_vehicle_type
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_VEHICLE_TYPE
+strhelp = STR_CONFIG_DEPARTURE_VEHICLE_TYPE_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_show_vehicle_color
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_VEHICLE_COLOR
+strhelp = STR_CONFIG_DEPARTURE_VEHICLE_COLOR_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_larger_font
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_LARGER_FONT
+strhelp = STR_CONFIG_DEPARTURE_LARGER_FONT_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_destination_type
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_DESTINATION_TYPE
+strhelp = STR_CONFIG_DEPARTURE_DESTINATION_TYPE_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_show_both
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_SHOW_BOTH
+strhelp = STR_CONFIG_DEPARTURE_SHOW_BOTH_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_only_passengers
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_ONLY_PASSENGERS
+strhelp = STR_CONFIG_DEPARTURE_ONLY_PASSENGERS_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_smart_terminus
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_SMART_TERMINUS
+strhelp = STR_CONFIG_DEPARTURE_SMART_TERMINUS_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_show_all_stops
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS
+strhelp = STR_CONFIG_DEPARTURE_SHOW_ALL_STOPS_HELPTEXT
+
+[SDTC_BOOL]
+var = gui.departure_merge_identical
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_DEPARTURE_MERGE_IDENTICAL
+strhelp = STR_CONFIG_DEPARTURE_MERGE_IDENTICAL_HELPTEXT
+
+[SDTC_VAR]
+var = gui.departure_conditionals
+type = SLE_UINT8
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+guiflags = SGF_MULTISTRING
+def = 0
+min = 0
+max = 2
+str = STR_CONFIG_DEPARTURE_CONDITIONALS
+strval = STR_CONFIG_DEPARTURE_CONDITIONALS_1
+strhelp = STR_CONFIG_DEPARTURE_CONDITIONALS_HELPTEXT
+proc = RedrawScreen
+
[SDTC_BOOL]
var = gui.quick_goto
flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
@@ -2603,6 +3206,17 @@ strval = STR_JUST_INT
cat = SC_EXPERT
[SDTC_VAR]
+var = gui.simulated_wormhole_signals
+type = SLE_UINT8
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = 2
+min = 1
+max = 16
+str = STR_CONFIG_SETTING_SIMULATE_SIGNALS
+strval = STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE
+proc = RedrawScreen
+
+[SDTC_VAR]
var = gui.drag_signals_density
type = SLE_UINT8
flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
@@ -2829,6 +3443,15 @@ strhelp = STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS_HELPTEXT
strval = STR_JUST_COMMA
proc = RedrawScreen
+[SDTC_VAR]
+var = gui.layer_view_type
+type = SLE_UINT32
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = 0
+min = 0
+max = UINT32_MAX
+proc = RedrawScreen
+
; For the dedicated build we'll enable dates in logs by default.
[SDTC_BOOL]
ifdef = DEDICATED
diff --git a/src/table/sprites.h b/src/table/sprites.h
index 463716cb1..916bff3d2 100644
--- a/src/table/sprites.h
+++ b/src/table/sprites.h
@@ -200,7 +200,10 @@ static const uint16 AUTORAIL_SPRITE_COUNT = 55;
static const SpriteID SPR_ELRAIL_BASE = SPR_AUTORAIL_BASE + AUTORAIL_SPRITE_COUNT;
static const uint16 ELRAIL_SPRITE_COUNT = 48;
-static const SpriteID SPR_2CCMAP_BASE = SPR_ELRAIL_BASE + ELRAIL_SPRITE_COUNT;
+static const SpriteID SPR_TRAFFICLIGHTS_BASE = SPR_ELRAIL_BASE + ELRAIL_SPRITE_COUNT;
+static const uint16 TRAFFICLIGHTS_SPRITE_COUNT = 22;
+
+static const SpriteID SPR_2CCMAP_BASE = SPR_TRAFFICLIGHTS_BASE + TRAFFICLIGHTS_SPRITE_COUNT;
static const uint16 TWOCCMAP_SPRITE_COUNT = 256;
/** shore tiles - action 05-0D */
@@ -1087,6 +1090,34 @@ static const SpriteID SPR_IMG_QUERY = 723;
static const SpriteID SPR_IMG_SIGN = 4082;
static const SpriteID SPR_IMG_BUY_LAND = 4791;
+/* Traffic lights */
+static const SpriteID SPR_IMG_TRAFFIC_LIGHT = SPR_TRAFFICLIGHTS_BASE;
+static const SpriteID SPR_CURSOR_TRAFFIC_LIGHT = SPR_TRAFFICLIGHTS_BASE + 1;
+
+static const SpriteID SPR_TL_SW_RED = SPR_TRAFFICLIGHTS_BASE + 2;
+static const SpriteID SPR_TL_SW_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 3;
+static const SpriteID SPR_TL_SW_GREEN = SPR_TRAFFICLIGHTS_BASE + 4;
+static const SpriteID SPR_TL_SW_YELLOW = SPR_TRAFFICLIGHTS_BASE + 5;
+static const SpriteID SPR_TL_SW_NONE = SPR_TRAFFICLIGHTS_BASE + 6;
+
+static const SpriteID SPR_TL_SE_RED = SPR_TRAFFICLIGHTS_BASE + 7;
+static const SpriteID SPR_TL_SE_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 8;
+static const SpriteID SPR_TL_SE_GREEN = SPR_TRAFFICLIGHTS_BASE + 9;
+static const SpriteID SPR_TL_SE_YELLOW = SPR_TRAFFICLIGHTS_BASE + 10;
+static const SpriteID SPR_TL_SE_NONE = SPR_TRAFFICLIGHTS_BASE + 11;
+
+static const SpriteID SPR_TL_NW_RED = SPR_TRAFFICLIGHTS_BASE + 12;
+static const SpriteID SPR_TL_NW_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 13;
+static const SpriteID SPR_TL_NW_GREEN = SPR_TRAFFICLIGHTS_BASE + 14;
+static const SpriteID SPR_TL_NW_YELLOW = SPR_TRAFFICLIGHTS_BASE + 15;
+static const SpriteID SPR_TL_NW_NONE = SPR_TRAFFICLIGHTS_BASE + 16;
+
+static const SpriteID SPR_TL_NE_RED = SPR_TRAFFICLIGHTS_BASE + 17;
+static const SpriteID SPR_TL_NE_RED_YELLOW = SPR_TRAFFICLIGHTS_BASE + 18;
+static const SpriteID SPR_TL_NE_GREEN = SPR_TRAFFICLIGHTS_BASE + 19;
+static const SpriteID SPR_TL_NE_YELLOW = SPR_TRAFFICLIGHTS_BASE + 20;
+static const SpriteID SPR_TL_NE_NONE = SPR_TRAFFICLIGHTS_BASE + 21;
+
/* OpenTTD in gamescreen */
static const SpriteID SPR_OTTD_O = 4842;
static const SpriteID SPR_OTTD_P = 4841;
diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h
index ed405aa48..19f5beb45 100644
--- a/src/table/strgen_tables.h
+++ b/src/table/strgen_tables.h
@@ -90,6 +90,10 @@ static const CmdStruct _cmd_structs[] = {
{"DATE_SHORT", EmitSingleChar, SCC_DATE_SHORT, 1, C_CASE},
{"DATE_LONG", EmitSingleChar, SCC_DATE_LONG, 1, C_CASE},
{"DATE_ISO", EmitSingleChar, SCC_DATE_ISO, 1, C_NONE},
+ {"DATE_WALLCLOCK_TINY", EmitSingleChar, SCC_DATE_WALLCLOCK_TINY, 1, C_NONE},
+ {"DATE_WALLCLOCK_SHORT", EmitSingleChar, SCC_DATE_WALLCLOCK_SHORT, 1, C_NONE},
+ {"DATE_WALLCLOCK_LONG", EmitSingleChar, SCC_DATE_WALLCLOCK_LONG, 1, C_NONE},
+ {"DATE_WALLCLOCK_ISO", EmitSingleChar, SCC_DATE_WALLCLOCK_ISO, 1, C_NONE},
{"STRING", EmitSingleChar, SCC_STRING, 1, C_CASE | C_GENDER},
{"RAW_STRING", EmitSingleChar, SCC_RAW_STRING_POINTER, 1, C_NONE | C_GENDER},
diff --git a/src/terraform_cmd.cpp b/src/terraform_cmd.cpp
index 8c0c63ac0..42e76ab46 100644
--- a/src/terraform_cmd.cpp
+++ b/src/terraform_cmd.cpp
@@ -10,6 +10,7 @@
/** @file terraform_cmd.cpp Commands related to terraforming. */
#include "stdafx.h"
+#include "layer_func.h"
#include "command_func.h"
#include "tunnel_map.h"
#include "bridge_map.h"
@@ -308,6 +309,10 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
if (z_N > z_min) tileh |= SLOPE_N;
if (pass == 0) {
+ /* Terrafrom enable only top layer */
+ if (IsUnderground(tile)) {
+ return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_TERRAFORM);
+ }
/* Check if bridge would take damage */
if (direction == 1 && MayHaveBridgeAbove(tile) && IsBridgeAbove(tile) &&
GetBridgeHeight(GetSouthernBridgeEnd(tile)) <= z_max) {
diff --git a/src/tgp.cpp b/src/tgp.cpp
index 6d17fd853..81541dd66 100644
--- a/src/tgp.cpp
+++ b/src/tgp.cpp
@@ -14,6 +14,7 @@
#include "clear_map.h"
#include "void_map.h"
#include "genworld.h"
+#include "layer_func.h"
#include "core/random_func.hpp"
#include "landscape_type.h"
@@ -165,8 +166,10 @@ struct HeightMap
height_t *h; //< array of heights
uint dim_x; //< height map size_x MapSizeX() + 1
uint total_size; //< height map total size
- uint size_x; //< MapSizeX()
- uint size_y; //< MapSizeY()
+ uint size_x; //< LayerSizeX()
+ uint size_y; //< LayerSizeY()
+ uint map_x; //< MapSizeX()
+ uint map_y; //< MapSizeY()
/**
* Height map accessor
@@ -250,8 +253,11 @@ static inline bool AllocHeightMap()
{
height_t *h;
- _height_map.size_x = MapSizeX();
- _height_map.size_y = MapSizeY();
+ _height_map.map_x = MapSizeX();
+ _height_map.map_y = MapSizeY();
+
+ _height_map.size_x = LayerSizeX();
+ _height_map.size_y = LayerSizeY();
/* Allocate memory block for height map row pointers */
_height_map.total_size = (_height_map.size_x + 1) * (_height_map.size_y + 1);
@@ -980,8 +986,8 @@ void GenerateTerrainPerlin()
/* First make sure the tiles at the north border are void tiles if needed. */
if (_settings_game.construction.freeform_edges) {
- for (y = 0; y < _height_map.size_y - 1; y++) MakeVoid(_height_map.size_x * y);
- for (x = 0; x < _height_map.size_x; x++) MakeVoid(x);
+ for (y = 0; y < _height_map.map_y - 1; y++) MakeVoid(_height_map.map_x * y);
+ for (x = 0; x < _height_map.map_x; x++) MakeVoid(x);
}
/* Transfer height map into OTTD map */
diff --git a/src/tile_map.h b/src/tile_map.h
index 2c5cfffbf..459c27ab2 100644
--- a/src/tile_map.h
+++ b/src/tile_map.h
@@ -95,7 +95,7 @@ static inline void SetTileType(TileIndex tile, TileType type)
/* VOID tiles (and no others) are exactly allowed at the lower left and right
* edges of the map. If _settings_game.construction.freeform_edges is true,
* the upper edges of the map are also VOID tiles. */
- assert((TileX(tile) == MapMaxX() || TileY(tile) == MapMaxY() || (_settings_game.construction.freeform_edges && (TileX(tile) == 0 || TileY(tile) == 0))) == (type == MP_VOID));
+// assert((TileX(tile) == MapMaxX() || TileY(tile) == MapMaxY() || (_settings_game.construction.freeform_edges && (TileX(tile) == 0 || TileY(tile) == 0))) == (type == MP_VOID));
SB(_m[tile].type_height, 4, 4, type);
}
diff --git a/src/tilearea_type.h b/src/tilearea_type.h
index 62d3d75df..88c2928ac 100644
--- a/src/tilearea_type.h
+++ b/src/tilearea_type.h
@@ -46,6 +46,16 @@ struct TileArea {
this->h = 0;
}
+ inline bool IsEmpty() const
+ {
+ return (w==0 && h==0);
+ }
+
+ inline bool IsFinite() const
+ {
+ return (w!=0 && h!=0);
+ }
+
bool Intersects(const TileArea &ta) const;
bool Contains(TileIndex tile) const;
diff --git a/src/tilehighlight_func.h b/src/tilehighlight_func.h
index 3edef509a..a17571fe3 100644
--- a/src/tilehighlight_func.h
+++ b/src/tilehighlight_func.h
@@ -14,6 +14,7 @@
#include "gfx_type.h"
#include "tilehighlight_type.h"
+#include "track_type.h"
void PlaceProc_DemolishArea(TileIndex tile);
bool GUIPlaceProcDragXY(ViewportDragDropSelectionProcess proc, TileIndex start_tile, TileIndex end_tile);
@@ -29,6 +30,13 @@ void VpSetPresizeRange(TileIndex from, TileIndex to);
void VpSetPlaceSizingLimit(int limit);
void UpdateTileSelection();
+void StoreRailPlacementEndpoints(TileIndex start_tile, TileIndex end_tile, Track start_track, bool bidirectional = true);
+
+/** Clear all rail track endpoints stored for highlighting purposes. @see StoreRailPlacementEndpoints */
+static inline void ClearRailPlacementEndpoints()
+{
+ StoreRailPlacementEndpoints(INVALID_TILE, INVALID_TILE, TRACK_BEGIN, false);
+}
extern TileHighlightData _thd;
diff --git a/src/tilehighlight_type.h b/src/tilehighlight_type.h
index 3d64248df..838b98186 100644
--- a/src/tilehighlight_type.h
+++ b/src/tilehighlight_type.h
@@ -28,6 +28,7 @@ enum HighLightStyle {
HT_RAIL = 0x080, ///< autorail (one piece), lower bits: direction
HT_VEHICLE = 0x100, ///< vehicle is accepted as target as well (bitmask)
HT_DIAGONAL = 0x200, ///< Also allow 'diagonal rectangles'. Only usable in combination with #HT_RECT or #HT_POINT.
+ HT_POLY = 0x400, ///< polyline mode; connect highlighted track with previous one
HT_DRAG_MASK = 0x0F8, ///< Mask for the tile drag-type modes.
/* lower bits (used with HT_LINE and HT_RAIL):
diff --git a/src/timetable.h b/src/timetable.h
index fe0848b56..08bce2f32 100644
--- a/src/timetable.h
+++ b/src/timetable.h
@@ -15,6 +15,8 @@
#include "date_type.h"
#include "vehicle_type.h"
+#define WALLCLOCK_NETWORK_COMPATIBLE 0 ///< Whether wallclock should preserve network compatibility. If so, then timetable start dates cannot be set exactly using minutes.
+
void ShowTimetableWindow(const Vehicle *v);
void UpdateVehicleTimetable(Vehicle *v, bool travelling);
void SetTimetableParams(int param1, int param2, Ticks ticks);
diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp
index 166b817eb..560cf7192 100644
--- a/src/timetable_cmd.cpp
+++ b/src/timetable_cmd.cpp
@@ -16,6 +16,7 @@
#include "window_func.h"
#include "vehicle_base.h"
#include "cmd_helper.h"
+#include "settings_type.h"
#include "table/strings.h"
@@ -69,6 +70,7 @@ static void ChangeTimetable(Vehicle *v, VehicleOrderID order_number, uint16 val,
default:
NOT_REACHED();
}
+ v->MarkSeparationInvalid();
}
SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
}
@@ -195,11 +197,16 @@ CommandCost CmdSetTimetableStart(TileIndex tile, DoCommandFlag flags, uint32 p1,
CommandCost ret = CheckOwnership(v->owner);
if (ret.Failed()) return ret;
+ DateTicks start_date = (Date)p2 / DAY_TICKS;
+
+#if WALLCLOCK_NETWORK_COMPATIBLE
/* Don't let a timetable start more than 15 years into the future or 1 year in the past. */
- Date start_date = (Date)p2;
if (start_date < 0 || start_date > MAX_DAY) return CMD_ERROR;
if (start_date - _date > 15 * DAYS_IN_LEAP_YEAR) return CMD_ERROR;
if (_date - start_date > DAYS_IN_LEAP_YEAR) return CMD_ERROR;
+#else
+ start_date = ((DateTicks)_date * DAY_TICKS) + _date_fract + (DateTicks)(int32)p2;
+#endif
if (flags & DC_EXEC) {
v->lateness_counter = 0;
@@ -268,6 +275,33 @@ CommandCost CmdAutofillTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1,
}
/**
+ * Set new separation parameters
+ * @param tile Not used.
+ * @param flags Operation to perform.
+ * @param p1 Order lit id.
+ * @param p2
+ * - p2 = (bit 0-1) - Separation mode (@see TTSepMode)
+ * - p2 = (bit 2-31) - Separation parameter (Unused if #TTS_MODE_OFF | #TTS_MODE_AUTO,
+ * Number of vehicles if #TTS_MODE_MAN_N, separation delay in ticks if #TTS_MODE_MAN_T).
+ * @param text Not used.
+ * @return The error or cost of the operation.
+ */
+CommandCost CmdReinitSeparation(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 20));
+ if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
+
+ CommandCost ret = CheckOwnership(v->owner);
+ if (ret.Failed()) return ret;
+
+ if (flags & DC_EXEC) {
+ v->SetSepSettings((TTSepMode)GB(p2,0,3), GB(p2,2,29));
+ }
+
+ return CommandCost();
+}
+
+/**
* Update the timetable for the vehicle.
* @param v The vehicle to update the timetable for.
* @param travelling Whether we just travelled or waited at a station.
@@ -297,7 +331,11 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling)
just_started = !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
if (v->timetable_start != 0) {
+#if WALLCLOCK_NETWORK_COMPATIBLE
v->lateness_counter = (_date - v->timetable_start) * DAY_TICKS + _date_fract;
+#else
+ v->lateness_counter = (_date * DAY_TICKS) + _date_fract - v->timetable_start;
+#endif
v->timetable_start = 0;
}
@@ -327,7 +365,7 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling)
* the timetable entry like is done for road vehicles/ships.
* Thus always make sure at least one tick is used between the
* processing of different orders when filling the timetable. */
- time_taken = CeilDiv(max(time_taken, 1U), DAY_TICKS) * DAY_TICKS;
+ time_taken = CeilDiv(max(time_taken, 1U), DATE_UNIT_SIZE) * DATE_UNIT_SIZE;
ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME);
}
diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp
index 7fc8da483..dcc850e80 100644
--- a/src/timetable_gui.cpp
+++ b/src/timetable_gui.cpp
@@ -29,6 +29,18 @@
#include "table/sprites.h"
#include "table/strings.h"
+#include "widgets/dropdown_func.h"
+
+
+/** Entries for mode selection dropdown list. Order must be identical to the one in #TTSepMode */
+static const StringID TimetableSeparationDropdownOptions[6] = {
+ STR_TTSEPARATION_AUTO,
+ STR_TTSEPARATION_OFF,
+ STR_TTSEPARATION_MAN_TIME,
+ STR_TTSEPARATION_MAN_NUM,
+ STR_TTSEPARATION_BUFFERED_AUTO,
+ INVALID_STRING_ID,
+};
/** Container for the arrival/departure dates of a vehicle */
struct TimetableArrivalDeparture {
@@ -45,11 +57,14 @@ struct TimetableArrivalDeparture {
void SetTimetableParams(int param1, int param2, Ticks ticks)
{
if (_settings_client.gui.timetable_in_ticks) {
- SetDParam(param1, STR_TIMETABLE_TICKS);
SetDParam(param2, ticks);
+ SetDParam(param1, STR_TIMETABLE_TICKS);
+ } else if (_settings_client.gui.time_in_minutes) {
+ SetDParam(param2, ticks / DATE_UNIT_SIZE);
+ SetDParam(param1, STR_TIMETABLE_MINUTES);
} else {
+ SetDParam(param2, ticks / DATE_UNIT_SIZE);
SetDParam(param1, STR_TIMETABLE_DAYS);
- SetDParam(param2, ticks / DAY_TICKS);
}
}
@@ -61,8 +76,18 @@ void SetTimetableParams(int param1, int param2, Ticks ticks)
*/
static void SetArrivalDepartParams(int param1, int param2, Ticks ticks)
{
- SetDParam(param1, STR_JUST_DATE_TINY);
- SetDParam(param2, _date + (ticks / DAY_TICKS));
+ SetDParam(param1, STR_JUST_DATE_WALLCLOCK_TINY);
+ SetDParam(param2, ((DateTicks)_date * DAY_TICKS) + ticks);
+}
+
+/**
+ * Check whether the order's time can be changed manually.
+ * @param order the order to check
+ * @return true if the order's time can be changed.
+ */
+static bool CanChangeTime(const Order *order)
+{
+ return !(order == NULL || ((!order->IsType(OT_GOTO_STATION) || (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL)));
}
/**
@@ -149,20 +174,28 @@ static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID
* @param window the window related to the setting of the date
* @param date the actually chosen date
*/
-static void ChangeTimetableStartCallback(const Window *w, Date date)
+static void ChangeTimetableStartCallback(const Window *w, DateTicks date)
{
- DoCommandP(0, w->window_number, date, CMD_SET_TIMETABLE_START | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+#if WALLCLOCK_NETWORK_COMPATIBLE
+ DoCommandP(0, w->window_number, (Date)(date / DAY_TICKS), CMD_SET_TIMETABLE_START | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+#else
+ DoCommandP(0, w->window_number, (Ticks)(date - (((DateTicks)_date * DAY_TICKS) + _date_fract)), CMD_SET_TIMETABLE_START | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+#endif
}
struct TimetableWindow : Window {
int sel_index;
- const Vehicle *vehicle; ///< Vehicle monitored by the window.
- bool show_expected; ///< Whether we show expected arrival or scheduled
- uint deparr_time_width; ///< The width of the departure/arrival time
- uint deparr_abbr_width; ///< The width of the departure/arrival abbreviation
+ const Vehicle *vehicle; ///< Vehicle monitored by the window.
+ bool show_expected; ///< Whether we show expected arrival or scheduled
+ uint deparr_time_width; ///< The width of the departure/arrival time
+ uint deparr_abbr_width; ///< The width of the departure/arrival abbreviation
+ int ctrl_pressed; // We need this to influence the ShowQueryString result.
+ int clicked_widget; ///< The widget that was clicked (used to determine what to do in OnQueryTextFinished)
Scrollbar *vscroll;
bool query_is_speed_query; ///< The currently open query window is a speed query and not a time query.
+ TTSepSettings new_sep_settings; ///< Contains new separation settings.
+ VehicleTimetableWidgets query_widget; ///< Required to determinate source of input query
TimetableWindow(const WindowDesc *desc, WindowNumber window_number) :
Window(),
@@ -170,6 +203,7 @@ struct TimetableWindow : Window {
vehicle(Vehicle::Get(window_number)),
show_expected(true)
{
+ this->new_sep_settings = (vehicle->orders.list != NULL) ? vehicle->orders.list->GetSepSettings() : TTSepSettings();
this->CreateNestedTree(desc);
this->vscroll = this->GetScrollbar(WID_VT_SCROLLBAR);
this->UpdateSelectionStates();
@@ -200,8 +234,8 @@ struct TimetableWindow : Window {
{
switch (widget) {
case WID_VT_ARRIVAL_DEPARTURE_PANEL:
- SetDParamMaxValue(0, MAX_YEAR * DAYS_IN_YEAR);
- this->deparr_time_width = GetStringBoundingBox(STR_JUST_DATE_TINY).width;
+ SetDParamMaxValue(0, _settings_client.gui.time_in_minutes ? 0 : MAX_YEAR * DAYS_IN_YEAR);
+ this->deparr_time_width = GetStringBoundingBox(STR_JUST_DATE_WALLCLOCK_TINY).width + 4;
this->deparr_abbr_width = max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_ABBREVIATION).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_ABBREVIATION).width);
size->width = WD_FRAMERECT_LEFT + this->deparr_abbr_width + 10 + this->deparr_time_width + WD_FRAMERECT_RIGHT;
/* FALL THROUGH */
@@ -317,7 +351,7 @@ struct TimetableWindow : Window {
if (selected % 2 == 1) {
disable = order != NULL && (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT));
} else {
- disable = order == NULL || ((!order->IsType(OT_GOTO_STATION) || (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL));
+ disable = !CanChangeTime(order);
}
}
bool disable_speed = disable || selected % 2 != 1 || v->type == VEH_AIRCRAFT;
@@ -344,6 +378,20 @@ struct TimetableWindow : Window {
this->SetWidgetLoweredState(WID_VT_AUTOFILL, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE));
+ bool b;
+
+ if(this->vehicle->orders.list != NULL) {
+ b = !(_settings_game.order.automatic_timetable_separation && this->vehicle->orders.list->IsCompleteTimetable());
+ } else {
+ b = false;
+ }
+ this->SetWidgetsDisabledState(b, WID_VT_TTSEP_SET_PARAMETER, WID_VT_TTSEP_MODE_DROPDOWN, WIDGET_LIST_END);
+
+ /* We can only set parameters if we're in one of the manual modes. */
+ bool enabled_state = (this->new_sep_settings.mode == TTS_MODE_MAN_N) || (this->new_sep_settings.mode == TTS_MODE_MAN_T);
+
+ this->SetWidgetDisabledState(WID_VT_TTSEP_SET_PARAMETER, !enabled_state);
+
this->DrawWidgets();
}
@@ -352,6 +400,8 @@ struct TimetableWindow : Window {
switch (widget) {
case WID_VT_CAPTION: SetDParam(0, this->vehicle->index); break;
case WID_VT_EXPECTED: SetDParam(0, this->show_expected ? STR_TIMETABLE_EXPECTED : STR_TIMETABLE_SCHEDULED); break;
+ case WID_VT_TTSEP_MODE_DROPDOWN: SetDParam(0, TimetableSeparationDropdownOptions[this->new_sep_settings.mode]); break;
+ case WID_VT_TTSEP_SET_PARAMETER: SetDParam(0, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ? STR_TTSEPARATION_SET_NUM : STR_TTSEPARATION_SET_TIME); break;
}
}
@@ -430,7 +480,7 @@ struct TimetableWindow : Window {
int y = r.top + WD_FRAMERECT_TOP;
- bool show_late = this->show_expected && v->lateness_counter > DAY_TICKS;
+ bool show_late = this->show_expected && v->lateness_counter > DATE_UNIT_SIZE;
Ticks offset = show_late ? 0 : -v->lateness_counter;
bool rtl = _current_text_dir == TD_RTL;
@@ -479,14 +529,18 @@ struct TimetableWindow : Window {
if (v->timetable_start != 0) {
/* We are running towards the first station so we can start the
* timetable at the given time. */
- SetDParam(0, STR_JUST_DATE_TINY);
+ SetDParam(0, STR_JUST_DATE_WALLCLOCK_TINY);
+#if WALLCLOCK_NETWORK_COMPATIBLE
+ SetDParam(1, v->timetable_start * DAY_TICKS);
+#else
SetDParam(1, v->timetable_start);
+#endif
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_START_AT);
} else if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) {
/* We aren't running on a timetable yet, so how can we be "on time"
* when we aren't even "on service"/"on duty"? */
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_NOT_STARTED);
- } else if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / DAY_TICKS == 0)) {
+ } else if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / DATE_UNIT_SIZE == 0)) {
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_ON_TIME);
} else {
SetTimetableParams(0, 1, abs(v->lateness_counter));
@@ -494,6 +548,75 @@ struct TimetableWindow : Window {
}
break;
}
+
+ case WID_VT_TTSEP_PANEL_TEXT: {
+ int y = r.top + WD_FRAMERECT_TOP; // Represents the current vertical position
+ const int left_border = r.left + WD_FRAMERECT_LEFT; // Represents the left border of the separation display frame
+ const int right_border = r.right - WD_FRAMERECT_RIGHT; // Represents the right border of the separation display frame.
+
+ /* If separation is inactive, we can stop here. */
+ if (!_settings_game.order.automatic_timetable_separation || (this->vehicle->orders.list == NULL))
+ break;
+
+ /* If the new mode is OFF... */
+ if (this->new_sep_settings.mode == TTS_MODE_OFF) {
+ /* ... skip description lines. */
+ int offset = _settings_client.gui.timetable_in_ticks ? GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_TICKS).height
+ : GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_DAYS).height;
+
+ y = y + GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC).height + offset;
+
+ } else {
+ /* If separation hasn't just been switched off, we need to draw various description lines.
+ * The first line is the amount of separation which is either saved in the stuct or must
+ * be calculated on the fly.
+ */
+ uint64 par;
+ if (this->new_sep_settings.mode == TTS_MODE_MAN_T || this->new_sep_settings.mode == TTS_MODE_AUTO) {
+ par = this->new_sep_settings.sep_ticks;
+ } else {
+ par = this->vehicle->orders.list->GetTimetableTotalDuration() / this->new_sep_settings.num_veh;
+ }
+
+ /* Depending on the setting for time displays, set up and draw either tick or days string. */
+ if (_settings_client.gui.timetable_in_ticks) {
+ SetDParam(0, par);
+ DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_TIME_DESC_TICKS, TC_BLACK);
+
+ y += GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_TICKS).height;
+ } else {
+ SetDParam(0, par / DAY_TICKS);
+ DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_TIME_DESC_DAYS, TC_BLACK);
+
+ y += GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_DAYS).height;
+ }
+
+ /* Print either the chosen amount of vehicles (when in MAN_N mode) or the calculated result... */
+ if (this->new_sep_settings.mode == TTS_MODE_MAN_N || this->new_sep_settings.mode == TTS_MODE_AUTO) {
+ par = this->new_sep_settings.num_veh;
+ } else {
+ par = this->vehicle->orders.list->GetTimetableTotalDuration() / this->new_sep_settings.sep_ticks;
+ }
+
+ SetDParam(0, par);
+ DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_NUM_DESC, TC_BLACK);
+
+ y += GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC).height;
+ }
+
+ /* If separation is switched on at all... */
+ if(this->vehicle->orders.list->IsSeparationOn())
+ {
+ /* ... set displayed status to either "Running" or "Initializing" */
+ SetDParam(0, (this->vehicle->orders.list->IsSeparationValid()) ? STR_TTSEPARATION_STATUS_RUNNING : STR_TTSEPARATION_STATUS_INIT);
+ } else {
+ /* If separation is switched off, show this instead. */
+ SetDParam(0, STR_TTSEPARATION_STATUS_OFF);
+ }
+
+ /* Print status description. */
+ DrawStringMultiLine(left_border, right_border, y, r.bottom - WD_FRAMERECT_BOTTOM, STR_TTSEPARATION_STATUS_DESC);
+ }
}
}
@@ -510,6 +633,9 @@ struct TimetableWindow : Window {
virtual void OnClick(Point pt, int widget, int click_count)
{
const Vehicle *v = this->vehicle;
+ ctrl_pressed = _ctrl_pressed;
+
+ this->clicked_widget = widget;
switch (widget) {
case WID_VT_ORDER_VIEW: // Order view button
@@ -518,14 +644,24 @@ struct TimetableWindow : Window {
case WID_VT_TIMETABLE_PANEL: { // Main panel.
int selected = GetOrderFromTimetableWndPt(pt.y, v);
-
this->DeleteChildWindows();
this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected;
break;
}
case WID_VT_START_DATE: // Change the date that the timetable starts.
- ShowSetDateWindow(this, v->index, _date, _cur_year, _cur_year + 15, ChangeTimetableStartCallback);
+ if (_settings_client.gui.time_in_minutes && _settings_client.gui.timetable_start_text_entry) {
+ StringID str = STR_JUST_INT;
+ uint64 time = ((DateTicks)_date * DAY_TICKS) + _date_fract;
+ time /= _settings_client.gui.ticks_per_minute;
+ time += _settings_client.gui.clock_offset;
+ time %= 24*60;
+ time = (time % 60) + (((time / 60) % 24) * 100);
+ SetDParam(0, time);
+ ShowQueryString(str, STR_TIMETABLE_STARTING_DATE, 31, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED);
+ } else {
+ ShowSetDateWindow(this, v->index, ((DateTicks)_date * DAY_TICKS) + _date_fract, _cur_year, _cur_year + 15, ChangeTimetableStartCallback);
+ }
break;
case WID_VT_CHANGE_TIME: { // "Wait For" button.
@@ -539,7 +675,7 @@ struct TimetableWindow : Window {
if (order != NULL) {
uint time = (selected % 2 == 1) ? order->travel_time : order->wait_time;
- if (!_settings_client.gui.timetable_in_ticks) time /= DAY_TICKS;
+ if (!_settings_client.gui.timetable_in_ticks) time /= DATE_UNIT_SIZE;
if (time != 0) {
SetDParam(0, time);
@@ -547,6 +683,7 @@ struct TimetableWindow : Window {
}
}
+ this->query_widget = WID_VT_CHANGE_TIME;
this->query_is_speed_query = false;
ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_NONE);
break;
@@ -603,29 +740,140 @@ struct TimetableWindow : Window {
case WID_VT_SHARED_ORDER_LIST:
ShowVehicleListWindow(v);
break;
+
+ case WID_VT_TTSEP_MODE_DROPDOWN: {
+ ShowDropDownMenu(this, TimetableSeparationDropdownOptions, this->new_sep_settings.mode, WID_VT_TTSEP_MODE_DROPDOWN, 0, 0);
+ break;
+ }
+
+ case WID_VT_TTSEP_SET_PARAMETER: {
+ this->query_widget = WID_VT_TTSEP_SET_PARAMETER;
+ SetDParam(0, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ? this->new_sep_settings.num_veh : this->new_sep_settings.sep_ticks);
+ ShowQueryString(STR_JUST_INT, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_NONE);
+ break;
+ }
}
this->SetDirty();
}
- virtual void OnQueryTextFinished(char *str)
+ virtual void OnDropdownSelect(int widget, int index)
{
- if (str == NULL) return;
-
- const Vehicle *v = this->vehicle;
+ assert(widget == WID_VT_TTSEP_MODE_DROPDOWN);
- uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
+ this->new_sep_settings = this->vehicle->orders.list->GetSepSettings();
+ this->new_sep_settings.mode = (TTSepMode)index;
+ this->vehicle->orders.list->SetSepSettings(this->new_sep_settings);
+ this->InvalidateData();
+ }
- uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
- if (this->query_is_speed_query) {
- val = ConvertDisplaySpeedToKmhishSpeed(val);
- } else {
- if (!_settings_client.gui.timetable_in_ticks) val *= DAY_TICKS;
+ virtual void OnQueryTextFinished(char *str)
+ {
+ if(str == NULL || StrEmpty(str))
+ return;
+
+ switch(this->query_widget) {
+ case WID_VT_CHANGE_TIME: {
+ const Vehicle *v = this->vehicle;
+
+// uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
+ uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
+
+// uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
+// if (this->query_is_speed_query) {
+// val = ConvertDisplaySpeedToKmhishSpeed(val);
+// } else {
+// if (!_settings_client.gui.timetable_in_ticks) val *= DAY_TICKS;
+// }
+ switch (this->clicked_widget) {
+ default: NOT_REACHED();
+
+// uint32 p2 = minu(val, UINT16_MAX);
+ case WID_VT_CHANGE_SPEED:
+ case WID_VT_CHANGE_TIME: {
+ uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
+
+// DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
+ if (this->query_is_speed_query) {
+ val = ConvertDisplaySpeedToKmhishSpeed(val);
+ } else {
+ if (!_settings_client.gui.timetable_in_ticks) val *= DATE_UNIT_SIZE;
+ }
+
+ uint32 p2 = minu(val, UINT16_MAX);
+
+// DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ if (! ctrl_pressed) {
+ /* Do a normal update. */
+ uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
+ DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ } else {
+ /* Update all valid stations. */
+ for (int i = 0, num_orders=v->GetNumOrders(); i < num_orders; ++i) {
+ const Order *order = v->GetOrder(i);
+
+ if (CanChangeTime(order)) {
+ uint32 p1 = PackTimetableArgs(v, i*2, this->query_is_speed_query);
+ DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ }
+ }
+ }
+
+ break;
+ }
+
+ case WID_VT_START_DATE: {
+ if (val > 0) {
+ uint minutes = (val % 100) % 60;
+ uint hours = (val / 100) % 24;
+ val = MINUTES_DATE(MINUTES_DAY(CURRENT_MINUTE), hours, minutes);
+ val -= _settings_client.gui.clock_offset;
+
+ if (val < (CURRENT_MINUTE - 60)) val += 60 * 24;
+ val *= DATE_UNIT_SIZE;
+ ChangeTimetableStartCallback(this, val);
+ }
+ break;
+ }
+ }
+
+
+
+// DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ break;
}
+ case WID_VT_TTSEP_SET_PARAMETER: {
+ int value = atoi(str);
+
+ switch (this->new_sep_settings.mode)
+ {
+ case TTS_MODE_AUTO:
+ case TTS_MODE_BUFFERED_AUTO:
+ case TTS_MODE_OFF:
+ break;
+
+ case TTS_MODE_MAN_N:
+ this->new_sep_settings.num_veh = Clamp(value, 1, 65535);
+ break;
+
+ case TTS_MODE_MAN_T:
+ this->new_sep_settings.sep_ticks = Clamp(value, 1, 65535);
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
- uint32 p2 = minu(val, UINT16_MAX);
-
- DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ this->vehicle->orders.list->SetSepSettings(this->new_sep_settings);
+ this->InvalidateData();
+ break;
+ }
+ default:
+ NOT_REACHED();
+ break;
+ }
}
virtual void OnResize()
@@ -658,6 +906,13 @@ static const NWidgetPart _nested_timetable_widgets[] = {
NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_ARRIVAL_DEPARTURE_PANEL), SetMinimalSize(110, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), SetScrollbar(WID_VT_SCROLLBAR), EndContainer(),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VT_SCROLLBAR),
+ NWidget(WWT_PANEL, COLOUR_GREY),
+ NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_TTSEPARATION_SETTINGS_DESC, STR_NULL), SetPadding(3),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VT_TTSEP_MODE_DROPDOWN), SetDataTip(STR_JUST_STRING, STR_TIMETABLE_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_TTSEP_SET_PARAMETER), SetFill(1, 0), SetDataTip(STR_TTSEPARATION_SET_XX, STR_TIMETABLE_TOOLTIP),
+ NWidget(WWT_PANEL,COLOUR_GREY, WID_VT_TTSEP_PANEL_TEXT), SetFill(1,1), SetResize(0,1), SetMinimalSize(0,44), EndContainer(),
+ EndContainer(),
+ EndContainer(),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
NWidget(NWID_HORIZONTAL),
diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp
index 7cb18de3d..c583b1506 100644
--- a/src/toolbar_gui.cpp
+++ b/src/toolbar_gui.cpp
@@ -13,7 +13,10 @@
#include "gui.h"
#include "window_gui.h"
#include "window_func.h"
+#include "layer_gui.h"
+#include "layer_func.h"
#include "viewport_func.h"
+#include "landscape.h"
#include "command_func.h"
#include "vehicle_gui.h"
#include "rail_gui.h"
@@ -22,6 +25,7 @@
#include "vehicle_func.h"
#include "sound_func.h"
#include "terraform_gui.h"
+#include "underground_gui.h"
#include "strings_func.h"
#include "company_func.h"
#include "company_gui.h"
@@ -874,10 +878,54 @@ static CallBackFunction MenuClickBuildAir(int index)
static CallBackFunction ToolbarForestClick(Window *w)
{
- PopupMainToolbMenu(w, WID_TN_LANDSCAPE, STR_LANDSCAPING_MENU_LANDSCAPING, 3);
+ // ПОДЗЕМЕЛЬЕ:
+ // Временно все меню будет храниться здесь. С пункта 3 и выше
+ PopupMainToolbMenu(w, WID_TN_LANDSCAPE, STR_LANDSCAPING_MENU_LANDSCAPING, 10);
return CBF_NONE;
}
+bool ScrollWindowRel(ViewportData *viewport, int dx, int dy)
+{
+ Point pt = RemapCoords(dx, dy, 0);
+
+ viewport->dest_scrollpos_x += pt.x;
+ viewport->dest_scrollpos_y += pt.y;
+ return true;
+}
+
+void SelectLayer(ViewportData *viewport, int layer)
+{
+ if (layer < 0) layer = 0;
+ if (uint(layer) >= LayerCount()) layer = LayerCount() - 1;
+
+ int delta_layer = calculateLayer(viewport) - layer;
+ bool res = ScrollWindowRel(viewport, 0, -delta_layer * LayerSizeY() * TILE_SIZE);
+}
+
+void SelectLayerInMainWindow(int layer)
+{
+ Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
+ SelectLayer(w->viewport, layer);
+ w->InvalidateData();
+}
+
+/**
+ * Handle click on the entry in the underground menu.
+ *
+ * @param index Menu entry clicked.
+ */
+static void ToMenuClickUnderground(int index)
+{
+ switch (index) {
+ case 4: ShowUndergroundToolbar(); break;
+
+ case 6: SelectLayerInMainWindow(index-6); break;
+ case 7: SelectLayerInMainWindow(index-6); break;
+ case 8: SelectLayerInMainWindow(index-6); break;
+ case 9: SelectLayerInMainWindow(index-6); break;
+ }
+}
+
/**
* Handle click on the entry in the landscaping menu.
*
@@ -891,6 +939,7 @@ static CallBackFunction MenuClickForest(int index)
case 1: ShowBuildTreesToolbar(); break;
case 2: return SelectSignTool();
}
+ ToMenuClickUnderground(index);
return CBF_NONE;
}
diff --git a/src/town.h b/src/town.h
index a672c2cee..9fcb07c5f 100644
--- a/src/town.h
+++ b/src/town.h
@@ -18,6 +18,7 @@
#include "newgrf_storage.h"
#include "cargotype.h"
#include "tilematrix_type.hpp"
+#include "cargodest_base.h"
#include <list>
template <typename T>
@@ -51,8 +52,9 @@ struct TownCache {
};
/** Town data structure. */
-struct Town : TownPool::PoolItem<&_town_pool> {
+struct Town : TownPool::PoolItem<&_town_pool>, CargoSourceSink {
TileIndex xy; ///< town center tile
+ TileIndex xy_aligned; ///< NOSAVE: Town centre aligned to the #AcceptanceMatrix grid.
TownCache cache; ///< Container for all cacheable data.
@@ -105,6 +107,10 @@ struct Town : TownPool::PoolItem<&_town_pool> {
std::list<PersistentStorage *> psa_list;
+ /* Current cargo acceptance and production. */
+ uint32 cargo_accepted_weights[NUM_CARGO]; ///< NOSAVE: Weight sum of accepting squares per cargo.
+ uint32 cargo_accepted_max_weight; ///< NOSAVE: Cached maximum weight for an accepting square.
+
/**
* Creates a new town.
* @param tile center tile of the town
@@ -116,6 +122,30 @@ struct Town : TownPool::PoolItem<&_town_pool> {
void InitializeLayout(TownLayout layout);
+ /* virtual */ SourceType GetType() const
+ {
+ return ST_TOWN;
+ }
+
+ /* virtual */ SourceID GetID() const
+ {
+ return this->index;
+ }
+
+ /* virtual */ bool AcceptsCargo(CargoID cid) const
+ {
+ return HasBit(this->cargo_accepted_total, cid);
+ }
+
+ /* virtual */ bool SuppliesCargo(CargoID cid) const
+ {
+ return HasBit(this->cargo_produced, cid);
+ }
+
+ /* virtual */ uint GetDestinationWeight(CargoID cid, byte weight_mod) const;
+ /* virtual */ void CreateSpecialLinks(CargoID cid);
+ /* virtual */ TileArea GetTileForDestination(CargoID cid);
+
/**
* Calculate the max town noise.
* The value is counted using the population divided by the content of the
@@ -137,7 +167,10 @@ struct Town : TownPool::PoolItem<&_town_pool> {
return Town::Get(GetTownIndex(tile));
}
- static Town *GetRandom();
+ /** Callback function for #Town::GetRandom. */
+ typedef bool (*EnumTownProc)(const Town *t, void *data);
+
+ static Town *GetRandom(EnumTownProc enum_proc = NULL, TownID skip = INVALID_TOWN, void *data = NULL);
static void PostDestructor(size_t index);
};
diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp
index abfa18106..6b351b2cd 100644
--- a/src/town_cmd.cpp
+++ b/src/town_cmd.cpp
@@ -13,6 +13,7 @@
#include "road_internal.h" /* Cleaning up road bits */
#include "road_cmd.h"
#include "landscape.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "cmd_helper.h"
#include "command_func.h"
@@ -137,17 +138,36 @@ void Town::InitializeLayout(TownLayout layout)
}
/**
- * Return a random valid town.
- * @return random town, NULL if there are no towns
+ * Return a random town that statisfies some criteria specified
+ * with a callback function.
+ *
+ * @param enum_proc Callback function. Return true for a matching town and false to continue iterating.
+ * @param skip Skip over this town id when searching.
+ * @param data Optional data passed to the callback function.
+ * @return A town satisfying the search criteria or NULL if no such town exists.
*/
-/* static */ Town *Town::GetRandom()
+/* static */ Town *Town::GetRandom(EnumTownProc enum_proc, TownID skip, void *data)
{
- if (Town::GetNumItems() == 0) return NULL;
- int num = RandomRange((uint16)Town::GetNumItems());
- size_t index = MAX_UVALUE(size_t);
+ assert(skip == INVALID_TOWN || Town::IsValidID(skip));
- while (num >= 0) {
- num--;
+ uint16 max_num = 0;
+ if (enum_proc != NULL) {
+ /* A callback was given, count all matching towns. */
+ Town *t;
+ FOR_ALL_TOWNS(t) {
+ if (t->index != skip && enum_proc(t, data)) max_num++;
+ }
+ } else {
+ max_num = (uint16)Town::GetNumItems();
+ /* Subtract one if a town to skip was given. max_num is at least
+ * one here as otherwise skip could not be valid. */
+ if (skip != INVALID_TOWN) max_num--;
+ }
+ if (max_num == 0) return NULL;
+
+ uint num = RandomRange(max_num) + 1;
+ size_t index = MAX_UVALUE(size_t);
+ do {
index++;
/* Make sure we have a valid town */
@@ -155,7 +175,9 @@ void Town::InitializeLayout(TownLayout layout)
index++;
assert(index < Town::GetPoolSize());
}
- }
+
+ if (index != skip && (enum_proc == NULL || enum_proc(Town::Get(index), data))) num--;
+ } while (num > 0);
return Town::Get(index);
}
@@ -483,7 +505,7 @@ static void TileLoop_Town(TileIndex tile)
uint amt = GB(callback, 0, 8);
if (amt == 0) continue;
- uint moved = MoveGoodsToStation(cargo, amt, ST_TOWN, t->index, stations.GetStations());
+ uint moved = MoveGoodsToStation(cargo, amt, ST_TOWN, t->index, stations.GetStations(), tile);
const CargoSpec *cs = CargoSpec::Get(cargo);
t->supplied[cs->Index()].new_max += amt;
@@ -495,7 +517,7 @@ static void TileLoop_Town(TileIndex tile)
if (EconomyIsInRecession()) amt = (amt + 1) >> 1;
t->supplied[CT_PASSENGERS].new_max += amt;
- t->supplied[CT_PASSENGERS].new_act += MoveGoodsToStation(CT_PASSENGERS, amt, ST_TOWN, t->index, stations.GetStations());
+ t->supplied[CT_PASSENGERS].new_act += MoveGoodsToStation(CT_PASSENGERS, amt, ST_TOWN, t->index, stations.GetStations(), tile);
}
if (GB(r, 8, 8) < hs->mail_generation) {
@@ -503,7 +525,7 @@ static void TileLoop_Town(TileIndex tile)
if (EconomyIsInRecession()) amt = (amt + 1) >> 1;
t->supplied[CT_MAIL].new_max += amt;
- t->supplied[CT_MAIL].new_act += MoveGoodsToStation(CT_MAIL, amt, ST_TOWN, t->index, stations.GetStations());
+ t->supplied[CT_MAIL].new_act += MoveGoodsToStation(CT_MAIL, amt, ST_TOWN, t->index, stations.GetStations(), tile);
}
}
@@ -680,17 +702,33 @@ static void ChangeTileOwner_Town(TileIndex tile, Owner old_owner, Owner new_owne
void UpdateTownCargoTotal(Town *t)
{
t->cargo_accepted_total = 0;
+ MemSetT(t->cargo_accepted_weights, 0, lengthof(t->cargo_accepted_weights));
+ /* Calculate the maximum weight based on the grid square furthest
+ * from the town centre. The maximum weight is two times the L-inf
+ * norm plus 1 so that max weight - furthest square weight == 1. */
const TileArea &area = t->cargo_accepted.GetArea();
+ uint max_dist = max(DistanceMax(t->xy_aligned, area.tile), DistanceMax(t->xy_aligned, TILE_ADDXY(area.tile, area.w - 1, area.h - 1))) / AcceptanceMatrix::GRID;
+ t->cargo_accepted_max_weight = max_dist * 2 + 1;
+
+ /* Collect acceptance from all grid squares. */
TILE_AREA_LOOP(tile, area) {
if (TileX(tile) % AcceptanceMatrix::GRID == 0 && TileY(tile) % AcceptanceMatrix::GRID == 0) {
- t->cargo_accepted_total |= t->cargo_accepted[tile];
+ uint32 acc = t->cargo_accepted[tile];
+ t->cargo_accepted_total |= acc;
+
+ CargoID cid;
+ FOR_EACH_SET_CARGO_ID(cid, acc) {
+ /* For each accepted cargo, the grid square weight is the maximum weight
+ * minus two times the L-inf norm between this square and the centre square. */
+ t->cargo_accepted_weights[cid] += t->cargo_accepted_max_weight - (DistanceMax(t->xy_aligned, tile) / AcceptanceMatrix::GRID) * 2;
+ }
}
}
}
/**
- * Update accepted town cargoes around a specific tile.
+ * Update accepted and produced town cargoes around a specific tile.
* @param t The town to update.
* @param start Update the values around this tile.
* @param update_total Set to true if the total cargo acceptance should be updated.
@@ -700,7 +738,7 @@ static void UpdateTownCargoes(Town *t, TileIndex start, bool update_total = true
CargoArray accepted, produced;
uint32 dummy;
- /* Gather acceptance for all houses in an area around the start tile.
+ /* Gather acceptance and production for all houses in an area around the start tile.
* The area is composed of the square the tile is in, extended one square in all
* directions as the coverage area of a single station is bigger than just one square. */
TileArea area = AcceptanceMatrix::GetAreaForTile(start, 1);
@@ -1537,6 +1575,11 @@ static void DoCreateTown(Town *t, TileIndex tile, uint32 townnameparts, TownSize
t->larger_town = city;
+ /* Cache the aligned tile index of the centre tile. */
+ uint town_x = (TileX(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID;
+ uint town_y = (TileY(t->xy) / AcceptanceMatrix::GRID) * AcceptanceMatrix::GRID;
+ t->xy_aligned= TileXY(town_x, town_y);
+
int x = (int)size * 16 + 3;
if (size == TSZ_RANDOM) x = (Random() & 0xF) + 8;
/* Don't create huge cities when founding town in-game */
@@ -1987,6 +2030,9 @@ static inline bool CanBuildHouseHere(TileIndex tile, TownID town, bool noslope)
Slope slope = GetTileSlope(tile);
if ((noslope && slope != SLOPE_FLAT) || IsSteepSlope(slope)) return false;
+ /* Недопустимо строительство объекта под землей */
+ if (IsUnderground(tile)) return false;
+
/* building under a bridge? */
if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) return false;
@@ -2422,6 +2468,8 @@ CommandCost CmdRenameTown(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
t->UpdateVirtCoord();
InvalidateWindowData(WC_TOWN_DIRECTORY, 0, 1);
+ InvalidateWindowClassesData(WC_TOWN_VIEW);
+ InvalidateWindowClassesData(WC_INDUSTRY_VIEW);
UpdateAllStationVirtCoords();
}
return CommandCost();
diff --git a/src/town_gui.cpp b/src/town_gui.cpp
index 624cc7446..696cd5b96 100644
--- a/src/town_gui.cpp
+++ b/src/town_gui.cpp
@@ -31,6 +31,7 @@
#include "townname_func.h"
#include "core/geometry_func.hpp"
#include "genworld.h"
+#include "cargodest_gui.h"
#include "widgets/town_widget.h"
@@ -297,10 +298,13 @@ struct TownViewWindow : Window {
private:
Town *town; ///< Town displayed by the window.
+ CargoDestinationList dest_list; ///< Sorted list of demand destinations.
+ uint dest_list_top; ///< Top coordinate of the destination list in the #WID_TV_INFOPANEL widget.
+
public:
static const int WID_TV_HEIGHT_NORMAL = 150;
- TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
+ TownViewWindow(const WindowDesc *desc, WindowNumber window_number) : Window(), dest_list(Town::Get(window_number))
{
this->CreateNestedTree(desc);
@@ -400,6 +404,8 @@ public:
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, STR_TOWN_VIEW_NOISE_IN_TOWN);
}
+ y = this->dest_list.DrawList(r.left, r.right, y);
+
if (this->town->text != NULL) {
SetDParamStr(0, this->town->text);
DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_LEFT, y += FONT_HEIGHT_NORMAL, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK);
@@ -442,6 +448,10 @@ public:
case WID_TV_DELETE: // delete town - only available on Scenario editor
DoCommandP(0, this->window_number, 0, CMD_DELETE_TOWN | CMD_MSG(STR_ERROR_TOWN_CAN_T_DELETE));
break;
+
+ case WID_TV_INFO: // jump to demand destination
+ this->dest_list.OnClick(pt.y - this->dest_list_top - this->GetWidget<NWidgetBase>(widget)->pos_y);
+ break;
}
}
@@ -458,7 +468,7 @@ public:
* Gets the desired height for the information panel.
* @return the desired height in pixels.
*/
- uint GetDesiredInfoHeight(int width) const
+ uint GetDesiredInfoHeight(int width)
{
uint aimed_height = 3 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
@@ -478,6 +488,9 @@ public:
if (_settings_game.economy.station_noise_level) aimed_height += FONT_HEIGHT_NORMAL;
+ this->dest_list_top = aimed_height - FONT_HEIGHT_NORMAL;
+ aimed_height += this->dest_list.GetListHeight();
+
if (this->town->text != NULL) {
SetDParamStr(0, this->town->text);
aimed_height += GetStringHeight(STR_JUST_RAW_STRING, width);
@@ -516,6 +529,13 @@ public:
/* Called when setting station noise or required cargoes have changed, in order to resize the window */
this->SetDirty(); // refresh display for current size. This will allow to avoid glitches when downgrading
this->ResizeWindowAsNeeded();
+
+ /* Rebuild destination list if data is not zero, otherwise just resort. */
+ if (data != 0) {
+ this->dest_list.InvalidateData();
+ } else {
+ this->dest_list.Resort();
+ }
}
virtual void OnQueryTextFinished(char *str)
diff --git a/src/track_func.h b/src/track_func.h
index 0cd3dcf31..d34d90f88 100644
--- a/src/track_func.h
+++ b/src/track_func.h
@@ -28,6 +28,17 @@
#define FOR_EACH_SET_TRACK(var, track_bits) FOR_EACH_SET_BIT_EX(Track, var, TrackBits, track_bits)
/**
+ * Iterate through each set Trackdir in a TrackdirBits value.
+ * For more informations see FOR_EACH_SET_BIT_EX.
+ *
+ * @param var Loop index variable that stores fallowing set track. Must be of type Track.
+ * @param trackdir_bits The value to iterate through (any expression).
+ *
+ * @see FOR_EACH_SET_BIT_EX
+ */
+#define FOR_EACH_SET_TRACKDIR(var, trackdir_bits) FOR_EACH_SET_BIT_EX(Trackdir, var, TrackdirBits, trackdir_bits)
+
+/**
* Convert an Axis to the corresponding Track
* AXIS_X -> TRACK_X
* AXIS_Y -> TRACK_Y
diff --git a/src/track_type.h b/src/track_type.h
index c0bfa0887..2a6ed2f2b 100644
--- a/src/track_type.h
+++ b/src/track_type.h
@@ -92,6 +92,7 @@ enum Trackdir {
TRACKDIR_END, ///< Used for iterations
INVALID_TRACKDIR = 0xFF, ///< Flag for an invalid trackdir
};
+DECLARE_POSTFIX_INCREMENT(Trackdir);
/** Define basic enum properties */
template <> struct EnumPropsT<Trackdir> : MakeEnumPropsT<Trackdir, byte, TRACKDIR_BEGIN, TRACKDIR_END, INVALID_TRACKDIR, 4> {};
diff --git a/src/trafficlight.cpp b/src/trafficlight.cpp
new file mode 100644
index 000000000..4eeff8fe7
--- /dev/null
+++ b/src/trafficlight.cpp
@@ -0,0 +1,291 @@
+/* $Id: trafficlight.cpp $ */
+
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file trafficlight.cpp Handling of trafficlights. */
+
+#include "stdafx.h"
+#include "openttd.h"
+#include "landscape.h"
+#include "sprite.h"
+#include "viewport_func.h"
+#include "road_map.h"
+#include "command_func.h"
+#include "cheat_func.h"
+#include "animated_tile_func.h"
+#include "economy_func.h"
+#include "road_cmd.h"
+#include "company_func.h"
+#include "company_base.h"
+#include "settings_type.h"
+#include "trafficlight.h"
+#include "trafficlight_type.h"
+#include "date_func.h"
+#include "company_func.h"
+
+#include "table/sprites.h"
+#include "table/strings.h"
+
+#include <set>
+
+
+/* A traffic light consist (TLC) is a set of adjacent tiles with traffic lights on them.
+ * They are linked together to form a big traffic light junction. */
+typedef std::set<TileIndex> TLC;
+
+/**
+ * Gets the traffic light consist (a set of adjacent tiles with traffic lights).
+ * If specified by the checkroadworks parameter, it returns 0 instead if road works are found within the consist.
+ * @param tile Tile of the traffic light consist.
+ * @param checkroadworks Should we check for roadworks in the consist (and return 0 if found).
+ * @return visited The traffic light consist (TLC); if checkroadworks == true and roadworks were found, return 0 instead.
+ */
+TLC *GetTrafficLightConsist(TileIndex tile, bool checkroadworks)
+{
+ TLC *visited;
+ TLC *candidates;
+
+ visited = new TLC;
+ candidates = new TLC;
+ candidates->insert(tile);
+
+ while (!candidates->empty()) {
+ TLC::iterator cur = candidates->begin();
+ if (checkroadworks && HasRoadWorks(*cur)) {
+ delete visited;
+ delete candidates;
+ return 0;
+ }
+ uint8 distance_between_traffic_lights = _tlc_distance[_settings_game.construction.max_tlc_distance];
+ for (int i = 0; i < distance_between_traffic_lights; i++) {
+ TileIndex neighbor = *cur + ToTileIndexDiff(_tl_check_offsets[i]);
+ if (HasTrafficLights(neighbor) && (visited->find(neighbor) == visited->end())) candidates->insert(neighbor);
+ }
+ visited->insert(*cur);
+ candidates->erase(cur);
+ }
+ delete candidates;
+ return visited;
+}
+
+/**
+ * Gets the lowest TileIndex of the traffic light consist or 0 if roadworks
+ * are found in the consist.
+ * @param tile Tile of the traffic light consist.
+ * @return lowest TileIndex in the consist or 0 if roadworks were found.
+ */
+TileIndex GetTLCLowestTileIndexOrRoadWorks(TileIndex tile)
+{
+ TLC *consist = GetTrafficLightConsist(tile, true);
+ TileIndex result = 0;
+ if (consist != 0) result = *(consist->begin());
+ delete consist;
+ return result;
+}
+
+/**
+ * Returns the state of the trafficlights on a tile.
+ * @note In the scenario editor trafficlights are disabled.
+ * @param tile This tile.
+ * @pre The tile must have trafficlights.
+ * @return Trafficlights state or disabled state.
+ */
+TrafficLightState GetTLState(TileIndex tile)
+{
+ assert(HasTrafficLights(tile));
+ if (_game_mode == GM_EDITOR) return TLS_OFF; ///< All lights are off in scenario editor.
+ tile = GetTLCLowestTileIndexOrRoadWorks(tile);
+ if (tile == 0) return TLS_OFF; ///< All lights are off when roadworks are in the consist.
+
+ uint16 tl_total = 16 * _settings_game.construction.traffic_lights_green_phase; // There are (16 * patchsetting) "TL ticks".
+ uint16 tl_tick = ((_tick_counter / 16) + 5 * TileX(tile) + 7 * TileY(tile)) % tl_total; // Each "TL tick" consists of 16 gameticks.
+
+ if (tl_tick < ((tl_total / 2) - 2)) return TLS_X_GREEN_Y_RED; ///< SW and NE are green, NW and SE are red.
+ if (tl_tick < ((tl_total / 2) - 1)) return TLS_X_YELLOW_Y_RED; ///< SW and NE are yellow, NW and SE are red.
+ if (tl_tick < (tl_total / 2)) return TLS_X_RED_Y_REDYELLOW; ///< SW and NE are red, NW and SE are red-yellow.
+ if (tl_tick < (tl_total - 2)) return TLS_X_RED_Y_GREEN; ///< SW and NE are red, NW and SE are green.
+ if (tl_tick < (tl_total - 1)) return TLS_X_RED_Y_YELLOW; ///< SW and NE are red, NW and SE are yellow.
+ if (tl_tick < tl_total) return TLS_X_REDYELLOW_Y_RED; ///< SW and NE are red-yellow, NW and SE are red.
+
+ NOT_REACHED();
+ return TLS_OFF;
+}
+
+/**
+ * Which directions are disallowed due to the TLState (red lights..).
+ */
+static const TrackdirBits _tls_to_trackdir[7] = {
+ TRACKDIR_BIT_MASK, ///< 0) all directions disallowed
+ TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_Y_SE | ///< 1) all directions from
+ TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_W | ///< y sides
+ TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_N, ///< are disallowed
+ TRACKDIR_BIT_MASK, ///< 2) all directions disallowed
+ TRACKDIR_BIT_MASK, ///< 3) all directions disallowed
+ TRACKDIR_BIT_X_SW | TRACKDIR_BIT_X_NE | ///< 4) all directions from
+ TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_E | ///< x sides
+ TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_S, ///< are disallowed
+ TRACKDIR_BIT_MASK, ///< 5) all directions disallowed
+ TRACKDIR_BIT_MASK, ///< 6) all directions disallowed
+};
+
+/**
+ * Which directions in tile are allowed to be taken due to adjacent traffic lights (traffic light consist).
+ * @param tile Tile to search on.
+ * @return trackdirbits Bitmask of allowed directions.
+ */
+TrackdirBits GetIntraTLCAllowedDirections(TileIndex tile)
+{
+ TrackdirBits trackdirbits = TRACKDIR_BIT_NONE;
+
+ if (HasTrafficLights(tile + TileDiffXY( 1, 0))) // SW.
+ trackdirbits |= TRACKDIR_BIT_X_NE | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_LEFT_N;
+ if (HasTrafficLights(tile + TileDiffXY( 0, 1))) // SE
+ trackdirbits |= TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_LOWER_W | TRACKDIR_BIT_RIGHT_N;
+ if (HasTrafficLights(tile + TileDiffXY( 0, -1))) // NW.
+ trackdirbits |= TRACKDIR_BIT_Y_SE | TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LEFT_S;
+ if (HasTrafficLights(tile + TileDiffXY(-1, 0))) // NE.
+ trackdirbits |= TRACKDIR_BIT_X_SW | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_RIGHT_S;
+
+ return trackdirbits;
+}
+
+/**
+ * Get a bitmask of the directions forbidden to drive on due to traffic light(s).
+ * @param tile Tile to check.
+ * @return Bitmask of forbidden directions.
+ */
+TrackdirBits GetTrafficLightDisallowedDirections(TileIndex tile)
+{
+ return (_tls_to_trackdir[GetTLState(tile)] & ~GetIntraTLCAllowedDirections(tile));
+}
+
+/**
+ * Checks if the size of a traffic light consist is within the allowed range.
+ * @param tile Tile to check (can also be a new tile to be added to the TLC).
+ * @return result True if the TLC size is within the allowed range, else false.
+ */
+bool CheckTLCSize(TileIndex tile)
+{
+ if (_settings_game.construction.max_tlc_size == 0) return true;
+ TLC *consist = GetTrafficLightConsist(tile, false);
+ bool result = (consist->size() <= _settings_game.construction.max_tlc_size);
+ delete consist;
+ return result;
+}
+
+/**
+ * Build traffic lights on a crossing.
+ * @param tile Tile where to place the traffic lights.
+ * @param flags Operation to perform.
+ * @param p1 Unused.
+ * @param p2 Unused.
+ * @return CommandCost Cost or error.
+ */
+CommandCost CmdBuildTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ /* Check if traffic lights are enabled. */
+ if (!_settings_game.construction.traffic_lights) return CMD_ERROR; // Sanity check.
+
+ /* Check for correct location (road). */
+ if (!IsTileType(tile, MP_ROAD) || GetRoadTileType(tile) != ROAD_TILE_NORMAL) return_cmd_error(STR_ERROR_THERE_IS_NO_ROAD);
+
+ /* Check owner only if a valid player is executing this command. */
+ if (Company::IsValidID(_current_company)) {
+ Owner owner = GetTileOwner(tile);
+ if (owner == OWNER_TOWN) {
+ if (!_settings_game.construction.allow_building_tls_in_towns) return_cmd_error(STR_ERROR_TRAFFIC_LIGHTS_NOT_ALLOWED_ON_TOWN_ROADS);
+ } else {
+ if (owner != OWNER_NONE && !IsTileOwner(tile, _current_company)) return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER); // Owned by ... already displayed in CheckOwnership.
+ }
+ }
+
+ /* Check junction and already built. */
+ if (CountBits(GetAllRoadBits(tile)) < 3) return_cmd_error(STR_ERROR_CAN_ONLY_BE_PLACED_ON_ROAD_JUNCTIONS);
+ if (HasTrafficLights(tile)) return_cmd_error(STR_ERROR_ALREADY_BUILT);
+
+ if (!CheckTLCSize(tile)) return_cmd_error(STR_ERROR_TRAFFIC_LIGHT_CONSIST_TOO_BIG);
+
+ /* Now we may build the traffic lights. */
+ if (flags & DC_EXEC) {
+ MakeTrafficLights(tile);
+ AddAnimatedTile(tile);
+ MarkTileDirtyByTile(tile);
+ }
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]);
+}
+
+/**
+ * Removes traffic lights from a tile.
+ * @param tile Tile where to remove the traffic lights.
+ * @param flags Operation to perform.
+ * @param p1 Unused.
+ * @param p2 Unused.
+ * @return CommandCost Cost or error.
+ */
+CommandCost CmdRemoveTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ /* Check for correct location (road with traffic lights). */
+ if (!IsTileType(tile, MP_ROAD) || GetRoadTileType(tile) != ROAD_TILE_NORMAL || !HasTrafficLights(tile)) return CMD_ERROR;
+
+ /* Check owner, but only if a valid player is executing this command. */
+ if (Company::IsValidID(_current_company)) {
+ Owner owner = GetTileOwner(tile);
+ if (owner == OWNER_TOWN) {
+ if (!_settings_game.construction.allow_building_tls_in_towns && !_cheats.magic_bulldozer.value) return_cmd_error(STR_ERROR_TRAFFIC_LIGHTS_NOT_ALLOWED_ON_TOWN_ROADS);
+ } else {
+ if (owner != OWNER_NONE && !IsTileOwner(tile, _current_company)) return CMD_ERROR; // Owned by ... already displayed in CheckOwnership.
+ }
+ }
+
+ /* Now we may remove the traffic lights. */
+ if (flags & DC_EXEC) {
+ DeleteAnimatedTile(tile);
+ ClearTrafficLights(tile);
+ MarkTileDirtyByTile(tile);
+ }
+ return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]);
+}
+/**
+ * Clear all traffic lights from the map.
+ */
+void ClearAllTrafficLights()
+{
+ /* Iterate over the whole map and remove any trafficlights found. */
+ for (TileIndex tile = 0; tile < MapSize(); tile++) {
+ if (HasTrafficLights(tile)) {
+ DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_TRAFFICLIGHTS);
+ }
+ }
+}
+
+/**
+ * Draws traffic lights on a tile.
+ * @param ti TileInfo of the tile to draw on.
+ */
+void DrawTrafficLights(TileInfo* ti)
+{
+ RoadBits road = GetAllRoadBits(ti->tile);
+ TrafficLightState state = GetTLState(ti->tile);
+
+ const TileIndex neighbor[4] = { ti->tile + TileDiffXY(1, 0), // SW.
+ ti->tile + TileDiffXY(0, 1), // SE.
+ ti->tile + TileDiffXY(0, -1), // NW.
+ ti->tile + TileDiffXY(-1, 0)}; // NE.
+ const RoadBits rb[4] = {ROAD_SW, ROAD_SE, ROAD_NW, ROAD_NE};
+
+ /* Draw the four directions. */
+ byte rs = _settings_game.vehicle.road_side;
+ for (int i = 0; i < 4; i++) {
+ if (road & rb[i] && !HasTrafficLights(neighbor[i])) {
+ DisallowedRoadDirections drd = DRD_NONE;
+ if (IsTileType(neighbor[i], MP_ROAD) && IsNormalRoad(neighbor[i])) drd = GetDisallowedRoadDirections(neighbor[i]);
+ if (drd != ((i % 2 == 0) ? DRD_SOUTHBOUND : DRD_NORTHBOUND) && drd != DRD_BOTH)
+ DrawRoadDetail(_tls_to_sprites[state][i], ti, _tl_offsets[rs][i].x, _tl_offsets[rs][i].y, 12);
+ }
+ }
+}
diff --git a/src/trafficlight.h b/src/trafficlight.h
new file mode 100644
index 000000000..4563a690c
--- /dev/null
+++ b/src/trafficlight.h
@@ -0,0 +1,133 @@
+/* $Id: trafficlight.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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file trafficlight.h variables used for handling trafficlights. */
+
+
+/**
+ * Used for synchronising traffic light signals.
+ * Number below is how far we look into the _tl_check_offsets array when
+ * placing trafficlights, based on _settings_game.construction.max_tlc_distance.
+ */
+static const uint8 _tlc_distance[5] = {
+0, ///< no synchronizing.
+8, ///< adjecant tiles only.
+24, ///< 2 tiles away.
+48, ///< 3 tiles away.
+80 ///< 4 tiles away.
+};
+
+/** TileDiffs for the adjacent tiles and those a little further away. */
+static const TileIndexDiffC _tl_check_offsets[80] = {
+ /* Tiles next to this tile (8 tiles). */
+ {-1, -1},
+ { 0, -1},
+ { 1, -1},
+ { 1, 0},
+ { 1, 1},
+ { 0, 1},
+ {-1, 1},
+ {-1, 0},
+ /* Tiles two tiles away from this tile (16 tiles). */
+ {-2, -2},
+ {-1, -2},
+ { 0, -2},
+ { 1, -2},
+ { 2, -2},
+ { 2, -1},
+ { 2, 0},
+ { 2, 1},
+ { 2, 2},
+ { 1, 2},
+ { 0, 2},
+ {-1, 2},
+ {-2, 2},
+ {-2, 1},
+ {-2, 0},
+ {-2, -1},
+ /* Tiles three tiles away from this tile (24 tiles). */
+ {-3, -3},
+ {-3, -2},
+ {-3, -1},
+ {-3, 0},
+ {-3, 1},
+ {-3, 2},
+ {-3, 3},
+ {-2, 3},
+ {-1, 3},
+ { 0, 3},
+ { 1, 3},
+ { 2, 3},
+ { 3, 3},
+ { 3, 2},
+ { 3, 1},
+ { 3, 0},
+ { 3, -1},
+ { 3, -2},
+ { 3, -3},
+ { 2, -3},
+ { 1, -3},
+ { 0, -3},
+ {-1, -3},
+ {-2, -3},
+ /* Tiles four tiles away from this tile (32 tiles). */
+ {-4, -4},
+ {-3, -4},
+ {-2, -4},
+ {-1, -4},
+ { 0, -4},
+ { 1, -4},
+ { 2, -4},
+ { 3, -4},
+ { 4, -4},
+ { 4, -3},
+ { 4, -2},
+ { 4, -1},
+ { 4, 0},
+ { 4, 1},
+ { 4, 2},
+ { 4, 3},
+ { 4, 4},
+ { 3, 4},
+ { 2, 4},
+ { 1, 4},
+ { 0, 4},
+ {-1, 4},
+ {-2, 4},
+ {-3, 4},
+ {-4, 4},
+ {-4, 3},
+ {-4, 2},
+ {-4, 1},
+ {-4, 0},
+ {-4, -1},
+ {-4, -2},
+ {-4, -3}
+};
+
+/**
+ * Drawing offsets for the traffic light posts [roadside (left, right)][direction (SW, SE, NW, NE)].
+ */
+static const Point _tl_offsets[2][4] = {
+ {{15, 1}, {14, 15}, {1, 0}, {0, 14}}, // Left side driving.
+ {{15, 14}, {1, 15}, {14, 0}, {0, 1}} // Right side driving.
+};
+
+/**
+ * Sprites needed for the various states of a TL crossing [state][direction].
+ */
+static const SpriteID _tls_to_sprites[7][4] = {
+ {SPR_TL_SW_NONE, SPR_TL_SE_NONE, SPR_TL_NW_NONE, SPR_TL_NE_NONE},
+ {SPR_TL_SW_GREEN, SPR_TL_SE_RED, SPR_TL_NW_RED, SPR_TL_NE_GREEN},
+ {SPR_TL_SW_YELLOW, SPR_TL_SE_RED, SPR_TL_NW_RED, SPR_TL_NE_YELLOW},
+ {SPR_TL_SW_RED, SPR_TL_SE_RED_YELLOW, SPR_TL_NW_RED_YELLOW, SPR_TL_NE_RED},
+ {SPR_TL_SW_RED, SPR_TL_SE_GREEN, SPR_TL_NW_GREEN, SPR_TL_NE_RED},
+ {SPR_TL_SW_RED, SPR_TL_SE_YELLOW, SPR_TL_NW_YELLOW, SPR_TL_NE_RED},
+ {SPR_TL_SW_RED_YELLOW, SPR_TL_SE_RED, SPR_TL_NW_RED, SPR_TL_NE_RED_YELLOW}
+};
diff --git a/src/trafficlight_func.h b/src/trafficlight_func.h
new file mode 100644
index 000000000..d72331308
--- /dev/null
+++ b/src/trafficlight_func.h
@@ -0,0 +1,19 @@
+/* $Id: trafficlight_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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file trafficlight_func.h Functions related to trafficlights. */
+
+#include "road_map.h"
+
+TrackdirBits GetTrafficLightDisallowedDirections(TileIndex tile);
+void DrawTrafficLights(TileInfo* ti);
+
+CommandCost CmdBuildTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text);
+CommandCost CmdRemoveTrafficLights(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text);
+void ClearAllTrafficLights();
diff --git a/src/trafficlight_type.h b/src/trafficlight_type.h
new file mode 100644
index 000000000..8cddb8be8
--- /dev/null
+++ b/src/trafficlight_type.h
@@ -0,0 +1,21 @@
+/* $Id: trafficlight_type.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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file trafficlight_type.h Types related to the states of trafficlights.*/
+
+/* Which state a traffic light is in. */
+enum TrafficLightState {
+ TLS_OFF, ///< all lights are off (during roadworks; always in scedit)
+ TLS_X_GREEN_Y_RED, ///< SW and NE are green, NW and SE are red
+ TLS_X_YELLOW_Y_RED, ///< SW and NE are yellow, NW and SE are red
+ TLS_X_RED_Y_REDYELLOW, ///< SW and NE are red, NW and SE are red-yellow
+ TLS_X_RED_Y_GREEN, ///< SW and NE are red, NW and SE are green
+ TLS_X_RED_Y_YELLOW, ///< SW and NE are red, NW and SE are yellow
+ TLS_X_REDYELLOW_Y_RED, ///< SW and NE are red-yellow, NW and SE are red
+};
diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp
index a01612511..280643b3c 100644
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -34,6 +34,7 @@
#include "newgrf.h"
#include "order_backup.h"
#include "zoom_func.h"
+#include "cargodest_func.h"
#include "table/strings.h"
#include "table/train_cmd.h"
@@ -156,6 +157,8 @@ void Train::ConsistChanged(bool same_length)
u->InvalidateNewGRFCache();
}
+ uint32 cargo_mask = 0;
+
for (Train *u = this; u != NULL; u = u->Next()) {
const Engine *e_u = u->GetEngine();
const RailVehicleInfo *rvi_u = &e_u->u.rail;
@@ -200,7 +203,9 @@ void Train::ConsistChanged(bool same_length)
}
}
+ /* Store carried cargo. */
u->cargo_cap = e_u->DetermineCapacity(u);
+ if (u->cargo_type != INVALID_CARGO && u->cargo_cap > 0) SetBit(cargo_mask, u->cargo_type);
u->vcache.cached_cargo_age_period = GetVehicleProperty(u, PROP_TRAIN_CARGO_AGE_PERIOD, e_u->info.cargo_age_period);
/* check the vehicle length (callback) */
@@ -231,6 +236,7 @@ void Train::ConsistChanged(bool same_length)
}
/* store consist weight/max speed in cache */
+ this->vcache.cached_cargo_mask = cargo_mask;
this->vcache.cached_max_speed = max_speed;
this->tcache.cached_tilt = train_can_tilt;
this->tcache.cached_max_curve_speed = this->GetCurveSpeedLimit();
@@ -1110,6 +1116,8 @@ static void NormaliseTrainHead(Train *head)
/* Not a front engine, i.e. a free wagon chain. No need to do more. */
if (!head->IsFrontEngine()) return;
+ PrefillRouteLinks(head);
+
/* Update the refit button and window */
InvalidateWindowData(WC_VEHICLE_REFIT, head->index, VIWD_CONSIST_CHANGED);
SetWindowWidgetDirty(WC_VEHICLE_VIEW, head->index, WID_VV_REFIT);
@@ -1296,6 +1304,9 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u
CheckCargoCapacity(dst_head);
}
+ /* Pre-fill route links after adding a vehicle. */
+ if (dst_head != NULL && dst_head->IsFrontEngine()) PrefillRouteLinks(dst_head);
+
/* We are undoubtedly changing something in the depot and train list. */
InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile);
InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
@@ -1828,6 +1839,17 @@ void ReverseTrainDirection(Train *v)
return;
}
+ /* We are inside tunnel/bidge with signals, reversing will close the entrance. */
+ if (HasWormholeSignals(v->tile)) {
+ /* Flip signal on tunnel entrance tile red. */
+ SetBitTunnelBridgeExit(v->tile);
+ MarkTileDirtyByTile(v->tile);
+ /* Clear counters. */
+ v->wait_counter = 0;
+ v->load_unload_ticks = 0;
+ return;
+ }
+
/* TrainExitDir does not always produce the desired dir for depots and
* tunnels/bridges that is needed for UpdateSignalsOnSegment. */
DiagDirection dir = TrainExitDir(v->direction, v->track);
@@ -2164,6 +2186,42 @@ static bool CheckTrainStayInDepot(Train *v)
return false;
}
+static void HandleLastTunnelBridgeSignals(TileIndex tile, TileIndex end, DiagDirection dir, bool free)
+{
+ if (IsBridge(end) && _m[end].m2 > 0){
+ /* Clearing last bridge signal. */
+ uint16 m = _m[end].m2;
+ byte i = 15;
+ while((m & 0x8000) == 0 && --i > 0) m <<= 1;
+ ClrBit(_m[end].m2, i);
+
+ uint x = TileX(end)* TILE_SIZE;
+ uint y = TileY(end)* TILE_SIZE;
+ uint distance = (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) * ++i;
+ switch (dir) {
+ default: NOT_REACHED();
+ case DIAGDIR_NE: MarkTileDirtyByTile(TileVirtXY(x - distance, y)); break;
+ case DIAGDIR_SE: MarkTileDirtyByTile(TileVirtXY(x, y + distance)); break;
+ case DIAGDIR_SW: MarkTileDirtyByTile(TileVirtXY(x + distance, y)); break;
+ case DIAGDIR_NW: MarkTileDirtyByTile(TileVirtXY(x, y - distance)); break;
+ }
+ MarkTileDirtyByTile(tile);
+ }
+ if (free) {
+ /* Open up the wormhole and clear m2. */
+ _m[tile].m2 = 0;
+ _m[end].m2 = 0;
+
+ if (IsTunnelBridgeWithSignRed(end)) {
+ ClrBitTunnelBridgeExit(end);
+ if (!_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(end);
+ } else if (IsTunnelBridgeWithSignRed(tile)) {
+ ClrBitTunnelBridgeExit(tile);
+ if (!_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(tile);
+ }
+ }
+}
+
/**
* Clear the reservation of \a tile that was just left by a wagon on \a track_dir.
* @param v %Train owning the reservation.
@@ -2179,7 +2237,8 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_
if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) {
TileIndex end = GetOtherTunnelBridgeEnd(tile);
- if (TunnelBridgeIsFree(tile, end, v).Succeeded()) {
+ bool free = TunnelBridgeIsFree(tile, end, v).Succeeded();
+ if (free) {
/* Free the reservation only if no other train is on the tiles. */
SetTunnelBridgeReservation(tile, false);
SetTunnelBridgeReservation(end, false);
@@ -2189,6 +2248,7 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_
MarkTileDirtyByTile(end);
}
}
+ if (HasWormholeSignals(tile)) HandleLastTunnelBridgeSignals(tile, end, dir, free);
}
} else if (IsRailStationTile(tile)) {
TileIndex new_tile = TileAddByDiagDir(tile, dir);
@@ -2789,8 +2849,6 @@ int Train::UpdateSpeed()
*/
static void TrainEnterStation(Train *v, StationID station)
{
- v->last_station_visited = station;
-
/* check if a train ever visited this station before */
Station *st = Station::Get(station);
if (!(st->had_vehicle_of_type & HVOT_TRAIN)) {
@@ -2809,7 +2867,7 @@ static void TrainEnterStation(Train *v, StationID station)
v->force_proceed = TFP_NONE;
SetWindowDirty(WC_VEHICLE_VIEW, v->index);
- v->BeginLoading();
+ v->BeginLoading(station);
TriggerStationRandomisation(st, v->tile, SRT_TRAIN_ARRIVES);
TriggerStationAnimation(st, v->tile, SAT_TRAIN_ARRIVES);
@@ -3055,6 +3113,101 @@ static Vehicle *CheckTrainAtSignal(Vehicle *v, void *data)
return t;
}
+
+
+/** Find train in front and keep distance between trains in tunnel/bridge. */
+static Vehicle *FindSpaceBetweenTrainsEnum(Vehicle *v, void *data)
+{
+ /* Don't look at wagons between front and back of train. */
+ if (v->type != VEH_TRAIN || (v->Previous() != NULL && v->Next() != NULL)) return NULL;
+
+ const Vehicle *u = (Vehicle*)data;
+ int32 a, b = 0;
+
+ switch (u->direction) {
+ default: NOT_REACHED();
+ case DIR_NE: a = u->x_pos; b = v->x_pos; break;
+ case DIR_SE: a = v->y_pos; b = u->y_pos; break;
+ case DIR_SW: a = v->x_pos; b = u->x_pos; break;
+ case DIR_NW: a = u->y_pos; b = v->y_pos; break;
+ }
+
+ if (a > b && a <= (b + (int)(Train::From(u)->wait_counter)) + (int)(TILE_SIZE)) return v;
+ return NULL;
+}
+
+static bool IsToCloseBehindTrain(Vehicle *v, TileIndex tile, bool check_endtile)
+{
+ Train *t = (Train *)v;
+
+ if (t->force_proceed != 0) return false;
+
+ if (HasVehicleOnPos(t->tile, v, &FindSpaceBetweenTrainsEnum)) {
+ /* Revert train if not going with tunnel direction. */
+ if (DirToDiagDir(t->direction) != GetTunnelBridgeDirection(t->tile)) {
+ v->cur_speed = 0;
+ ToggleBit(t->flags, VRF_REVERSING);
+ }
+ return true;
+ }
+ /* Cover blind spot at end of tunnel bridge. */
+ if (check_endtile){
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(t->tile), v, &FindSpaceBetweenTrainsEnum)) {
+ /* Revert train if not going with tunnel direction. */
+ if (DirToDiagDir(t->direction) != GetTunnelBridgeDirection(t->tile)) {
+ v->cur_speed = 0;
+ ToggleBit(t->flags, VRF_REVERSING);
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/** Simulate signals in tunnel - bridge. */
+static bool CheckTrainStayInWormHole(Train *t, TileIndex tile)
+{
+ if (t->force_proceed != 0) return false;
+
+ /* When not exit reverse train. */
+ if (!IsTunnelBridgeExit(tile)) {
+ t->cur_speed = 0;
+ ToggleBit(t->flags, VRF_REVERSING);
+ return true;
+ }
+ SigSegState seg_state = _settings_game.pf.reserve_paths ? SIGSEG_PBS : UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, t->owner);
+ if (seg_state == SIGSEG_FULL || (seg_state == SIGSEG_PBS && !TryPathReserve(t))) {
+ t->cur_speed = 0;
+ return true;
+ }
+
+ return false;
+}
+
+static void HandleSignalBehindTrain(Train *v, uint signal_number)
+{
+ TileIndex tile;
+ switch (v->direction) {
+ default: NOT_REACHED();
+ case DIR_NE: tile = TileVirtXY(v->x_pos + (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals), v->y_pos); break;
+ case DIR_SE: tile = TileVirtXY(v->x_pos, v->y_pos - (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) ); break;
+ case DIR_SW: tile = TileVirtXY(v->x_pos - (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals), v->y_pos); break;
+ case DIR_NW: tile = TileVirtXY(v->x_pos, v->y_pos + (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals)); break;
+ }
+
+ if(tile == v->tile) {
+ /* Flip signal on ramp. */
+ if (IsTunnelBridgeWithSignRed(tile)) {
+ ClrBitTunnelBridgeExit(tile);
+ MarkTileDirtyByTile(tile);
+ }
+ } else if (IsBridge(v->tile) && signal_number <= 16) {
+ ClrBit(_m[v->tile].m2, signal_number);
+ MarkTileDirtyByTile(tile);
+ }
+}
+
/**
* Move a vehicle chain one movement stop forwards.
* @param v First vehicle to move.
@@ -3154,7 +3307,9 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
/* Don't handle stuck trains here. */
if (HasBit(v->flags, VRF_TRAIN_STUCK)) return false;
-
+ /* this codepath seems to be run every 5 ticks, so increase counter twice every 20 ticks */
+ IncreaseStuckCounter(v->tile);
+ if (v->tick_counter % 4 == 0) IncreaseStuckCounter(v->tile);
if (!HasSignalOnTrackdir(gp.new_tile, ReverseTrackdir(i))) {
v->cur_speed = 0;
v->subspeed = 0;
@@ -3239,6 +3394,23 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
goto invalid_rail;
}
+ if (HasWormholeSignals(gp.new_tile)) {
+ /* If red signal stop. */
+ if (v->IsFrontEngine() && v->force_proceed == 0) {
+ if (IsTunnelBridgeWithSignRed(gp.new_tile)) {
+ v->cur_speed = 0;
+ return false;
+ }
+ if (IsTunnelBridgeExit(gp.new_tile)) {
+ v->cur_speed = 0;
+ goto invalid_rail;
+ }
+ /* Flip signal on tunnel entrance tile red. */
+ SetBitTunnelBridgeExit(gp.new_tile);
+ MarkTileDirtyByTile(gp.new_tile);
+ }
+ }
+
if (!HasBit(r, VETS_ENTERED_WORMHOLE)) {
Track track = FindFirstTrack(chosen_track);
Trackdir tdir = TrackDirectionToTrackdir(track, chosen_dir);
@@ -3291,6 +3463,64 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
}
}
} else {
+ /* Handle signal simulation on tunnel/bridge. */
+ TileIndex old_tile = TileVirtXY(v->x_pos, v->y_pos);
+ if (old_tile != gp.new_tile && HasWormholeSignals(v->tile) && (v->IsFrontEngine() || v->Next() == NULL)){
+ if (old_tile == v->tile) {
+ if (v->IsFrontEngine() && v->force_proceed == 0 && IsTunnelBridgeExit(v->tile)) goto invalid_rail;
+ /* Entered wormhole set counters. */
+ v->wait_counter = (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) - TILE_SIZE;
+ v->load_unload_ticks = 0;
+ }
+
+ uint distance = v->wait_counter;
+ bool leaving = false;
+ if (distance == 0) v->wait_counter = (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals);
+
+ if (v->IsFrontEngine()) {
+ /* Check if track in front is free and see if we can leave wormhole. */
+ int z = GetSlopePixelZ(gp.x, gp.y) - v->z_pos;
+ if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && !(abs(z) > 2)) {
+ if (CheckTrainStayInWormHole(v, gp.new_tile)) return false;
+ leaving = true;
+ } else {
+ if (IsToCloseBehindTrain(v, gp.new_tile, distance == 0)) {
+ if (distance == 0) v->wait_counter = 0;
+ v->cur_speed = 0;
+ return false;
+ }
+ /* flip signal in front to red on bridges*/
+ if (distance == 0 && v->load_unload_ticks <= 15 && IsBridge(v->tile)){
+ SetBit(_m[v->tile].m2, v->load_unload_ticks);
+ MarkTileDirtyByTile(gp.new_tile);
+ }
+ }
+ }
+ if (v->Next() == NULL) {
+ if (v->load_unload_ticks > 0 && v->load_unload_ticks <= 16 && distance == (TILE_SIZE * _settings_client.gui.simulated_wormhole_signals) - TILE_SIZE) HandleSignalBehindTrain(v, v->load_unload_ticks - 2);
+ if (old_tile == v->tile) {
+ /* We left ramp into wormhole. */
+ v->x_pos = gp.x;
+ v->y_pos = gp.y;
+ UpdateSignalsOnSegment(old_tile, INVALID_DIAGDIR, v->owner);
+ }
+ }
+ if (distance == 0) v->load_unload_ticks++;
+ v->wait_counter -= TILE_SIZE;
+
+ if (leaving) { // Reset counters.
+ v->force_proceed = 0;
+ v->wait_counter = 0;
+ v->load_unload_ticks = 0;
+ v->x_pos = gp.x;
+ v->y_pos = gp.y;
+ VehicleUpdatePosition(v);
+ VehicleUpdateViewport(v, false);
+ UpdateSignalsOnSegment(gp.new_tile, INVALID_DIAGDIR, v->owner);
+ continue;
+ }
+ }
+
if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) {
/* Perform look-ahead on tunnel exit. */
if (v->IsFrontEngine()) {
@@ -3761,6 +3991,7 @@ static bool TrainLocoHandler(Train *v, bool mode)
/* Handle stuck trains. */
if (!mode && HasBit(v->flags, VRF_TRAIN_STUCK)) {
++v->wait_counter;
+ if (v->tick_counter % 4 == 0) IncreaseStuckCounter(v->tile);
/* Should we try reversing this tick if still stuck? */
bool turn_around = v->wait_counter % (_settings_game.pf.wait_for_pbs_path * DAY_TICKS) == 0 && _settings_game.pf.reverse_at_signals;
diff --git a/src/train_gui.cpp b/src/train_gui.cpp
index f302d759c..f733d0588 100644
--- a/src/train_gui.cpp
+++ b/src/train_gui.cpp
@@ -310,9 +310,11 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab)
if (det_tab == TDW_TAB_TOTALS) { // Total cargo tab
CargoArray act_cargo;
CargoArray max_cargo;
+ CargoDestSummary dests[NUM_CARGO];
for (const Vehicle *v = Vehicle::Get(veh_id); v != NULL; v = v->Next()) {
act_cargo[v->cargo_type] += v->cargo.Count();
max_cargo[v->cargo_type] += v->cargo_cap;
+ AddVehicleCargoDestSummary(v, &dests[v->cargo_type]);
}
/* Set scroll-amount separately from counting, as to not compute num double
@@ -320,8 +322,9 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab)
*/
for (CargoID i = 0; i < NUM_CARGO; i++) {
if (max_cargo[i] > 0) num++; // only count carriages that the train has
+ num += (int)dests[i].size();
}
- num++; // needs one more because first line is description string
+ num += 2; // needs one more because first line is description string
} else {
for (const Train *v = Train::Get(veh_id); v != NULL; v = v->GetNextVehicle()) {
GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary);
@@ -421,12 +424,15 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po
} else {
CargoArray act_cargo;
CargoArray max_cargo;
+ CargoDestSummary dests[NUM_CARGO];
Money feeder_share = 0;
for (const Vehicle *u = v; u != NULL; u = u->Next()) {
act_cargo[u->cargo_type] += u->cargo.Count();
max_cargo[u->cargo_type] += u->cargo_cap;
feeder_share += u->cargo.FeederShare();
+
+ AddVehicleCargoDestSummary(u, &dests[u->cargo_type]);
}
/* draw total cargo tab */
@@ -443,6 +449,17 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po
DrawString(left, right, y, FreightWagonMult(i) > 1 ? STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT : STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY);
y += WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
}
+
+ for (CargoDestSummary::const_iterator row = dests[i].begin(); row != dests[i].end() && vscroll_pos > -vscroll_cap; ++row) {
+ if (--vscroll_pos < 0) {
+ SetDParam(0, i); // {SHORTCARGO} #1
+ SetDParam(1, row->count); // {SHORTCARGO} #2
+ SetDParam(2, row->type == ST_INDUSTRY ? STR_INDUSTRY_NAME : (row->type == ST_TOWN ? STR_TOWN_NAME : STR_COMPANY_NAME)); // {STRING1}
+ SetDParam(3, row->dest); // Parameter of {STRING1}
+ DrawString(left + 2 * WD_PAR_VSEP_WIDE, right, y, STR_VEHICLE_DETAILS_CARGO_TO);
+ y += WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
+ }
+ }
}
SetDParam(0, feeder_share);
DrawString(left, right, y, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE);
diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp
index c30609e78..e3b036e66 100644
--- a/src/tree_cmd.cpp
+++ b/src/tree_cmd.cpp
@@ -13,6 +13,7 @@
#include "clear_map.h"
#include "landscape.h"
#include "tree_map.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "command_func.h"
#include "town.h"
@@ -70,7 +71,8 @@ static bool CanPlantTreesOnTile(TileIndex tile, bool allow_desert)
case MP_CLEAR:
return !IsBridgeAbove(tile) && !IsClearGround(tile, CLEAR_FIELDS) && GetRawClearGround(tile) != CLEAR_ROCKS &&
- (allow_desert || !IsClearGround(tile, CLEAR_DESERT));
+ (allow_desert || !IsClearGround(tile, CLEAR_DESERT))
+ && !IsUnderground(tile);
default: return false;
}
@@ -337,6 +339,10 @@ CommandCost CmdPlantTree(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
const byte tree_to_plant = GB(p1, 0, 8); // We cannot use Extract as min and max are climate specific.
if (p2 >= MapSize()) return CMD_ERROR;
+
+ /* tree only top layer */
+ if (IsUnderground(p2)) return CMD_ERROR;
+
/* Check the tree type within the current climate */
if (tree_to_plant != TREE_INVALID && !IsInsideBS(tree_to_plant, _tree_base_by_landscape[_settings_game.game_creation.landscape], _tree_count_by_landscape[_settings_game.game_creation.landscape])) return CMD_ERROR;
diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp
index 1bcbf7163..1055e40da 100644
--- a/src/tunnelbridge_cmd.cpp
+++ b/src/tunnelbridge_cmd.cpp
@@ -15,6 +15,7 @@
#include "stdafx.h"
#include "newgrf_object.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "cmd_helper.h"
#include "command_func.h"
@@ -30,6 +31,7 @@
#include "date_func.h"
#include "clear_func.h"
#include "vehicle_func.h"
+#include "vehicle_gui.h"
#include "sound_func.h"
#include "tunnelbridge.h"
#include "cheat_type.h"
@@ -260,6 +262,10 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u
return_cmd_error(STR_ERROR_CAN_T_START_AND_END_ON);
}
+ if (IsUnderground(tile_start) || IsUnderground(tile_end)) {
+ return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND);
+ }
+
Axis direction;
if (TileX(tile_start) == TileX(tile_end)) {
direction = AXIS_Y;
@@ -610,6 +616,12 @@ CommandCost CmdBuildTunnel(TileIndex start_tile, DoCommandFlag flags, uint32 p1,
for (;;) {
end_tile += delta;
if (!IsValidTile(end_tile)) return_cmd_error(STR_ERROR_TUNNEL_THROUGH_MAP_BORDER);
+
+
+ if (IsUnderground(start_tile) || IsUnderground(end_tile)) {
+ return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND);
+ }
+
end_tileh = GetTileSlope(end_tile, &end_z);
if (start_z == end_z) break;
@@ -1081,6 +1093,103 @@ static void DrawBridgeTramBits(int x, int y, int z, int offset, bool overlay, bo
}
}
+/* Draws a signal on tunnel / bridge entrance tile. */
+static void DrawTunnelBridgeRampSignal(const TileInfo *ti)
+{
+ bool side = (_settings_game.vehicle.road_side != 0) &&_settings_game.construction.train_signal_side;
+
+ static const Point SignalPositions[2][4] = {
+ { /* X X Y Y Signals on the left side */
+ {13, 3}, { 2, 13}, { 3, 4}, {13, 14}
+ }, {/* X X Y Y Signals on the right side */
+ {14, 13}, { 3, 3}, {13, 2}, { 3, 13}
+ }
+ };
+
+ uint position;
+ DiagDirection dir = GetTunnelBridgeDirection(ti->tile);
+
+ switch (dir) {
+ default: NOT_REACHED();
+ case DIAGDIR_NE: position = 0; break;
+ case DIAGDIR_SE: position = 2; break;
+ case DIAGDIR_SW: position = 1; break;
+ case DIAGDIR_NW: position = 3; break;
+ }
+
+ uint x = TileX(ti->tile) * TILE_SIZE + SignalPositions[side][position].x;
+ uint y = TileY(ti->tile) * TILE_SIZE + SignalPositions[side][position].y;
+ uint z = ti->z;
+
+ if (ti->tileh != SLOPE_FLAT && IsBridge(ti->tile)) z += 8; // sloped bridge head
+ SignalVariant variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
+
+ SpriteID sprite;
+ if (variant == SIG_ELECTRIC) {
+ /* Normal electric signals are picked from original sprites. */
+ sprite = SPR_ORIGINAL_SIGNALS_BASE + ((position << 1) + IsTunnelBridgeWithSignGreen(ti->tile));
+ } else {
+ /* All other signals are picked from add on sprites. */
+ sprite = SPR_SIGNALS_BASE + ((SIGTYPE_NORMAL - 1) * 16 + variant * 64 + (position << 1) + IsTunnelBridgeWithSignGreen(ti->tile));
+ }
+
+ AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, TILE_HEIGHT, z, false, 0, 0, BB_Z_SEPARATOR);
+}
+
+/* Draws a signal on tunnel / bridge entrance tile. */
+static void DrawBrigeSignalOnMiddelPart(const TileInfo *ti, TileIndex bridge_start_tile, uint z)
+{
+
+ uint bridge_signal_position = 0;
+ int m2_position = 0;
+
+ uint bridge_section = GetTunnelBridgeLength(ti->tile, bridge_start_tile) + 1;
+
+ while (bridge_signal_position <= bridge_section) {
+ bridge_signal_position += _settings_client.gui.simulated_wormhole_signals;
+ if (bridge_signal_position == bridge_section) {
+ bool side = (_settings_game.vehicle.road_side != 0) && _settings_game.construction.train_signal_side;
+
+ static const Point SignalPositions[2][4] = {
+ { /* X X Y Y Signals on the left side */
+ {11, 3}, { 4, 13}, { 3, 4}, {11, 13}
+ }, {/* X X Y Y Signals on the right side */
+ {11, 13}, { 4, 3}, {13, 4}, { 3, 11}
+ }
+ };
+
+ uint position;
+
+ switch (GetTunnelBridgeDirection(bridge_start_tile)) {
+ default: NOT_REACHED();
+ case DIAGDIR_NE: position = 0; break;
+ case DIAGDIR_SE: position = 2; break;
+ case DIAGDIR_SW: position = 1; break;
+ case DIAGDIR_NW: position = 3; break;
+ }
+
+ uint x = TileX(ti->tile) * TILE_SIZE + SignalPositions[side][position].x;
+ uint y = TileY(ti->tile) * TILE_SIZE + SignalPositions[side][position].y;
+ z += 5;
+
+ SignalVariant variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
+
+ SpriteID sprite;
+
+ if (variant == SIG_ELECTRIC) {
+ /* Normal electric signals are picked from original sprites. */
+ sprite = SPR_ORIGINAL_SIGNALS_BASE + ((position << 1) + !HasBit(_m[bridge_start_tile].m2, m2_position));
+ } else {
+ /* All other signals are picked from add on sprites. */
+ sprite = SPR_SIGNALS_BASE + ((SIGTYPE_NORMAL - 1) * 16 + variant * 64 + (position << 1) + !HasBit(_m[bridge_start_tile].m2, m2_position));
+ }
+
+ AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, TILE_HEIGHT, z, false, 0, 0, BB_Z_SEPARATOR);
+ }
+ m2_position++;
+ }
+}
+
/**
* Draws a tunnel of bridge tile.
* For tunnels, this is rather simple, as you only need to draw the entrance.
@@ -1190,6 +1299,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x, ti->y, BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x + BB_data[4], ti->y + BB_data[5], BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
+ /* Draw signals for tunnel. */
+ if (IsTunnelBridgeEntrance(ti->tile)) DrawTunnelBridgeRampSignal(ti);
+
DrawBridgeMiddle(ti);
} else { // IsBridge(ti->tile)
const PalSpriteID *psid;
@@ -1289,6 +1401,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
}
}
+ /* Draw signals for bridge. */
+ if (HasWormholeSignals(ti->tile)) DrawTunnelBridgeRampSignal(ti);
+
DrawBridgeMiddle(ti);
}
}
@@ -1427,6 +1542,9 @@ void DrawBridgeMiddle(const TileInfo *ti)
if (HasCatenaryDrawn(GetRailType(rampsouth))) {
DrawCatenaryOnBridge(ti);
}
+ if (HasWormholeSignals(rampsouth)) {
+ IsTunnelBridgeExit(rampsouth) ? DrawBrigeSignalOnMiddelPart(ti, rampnorth, z): DrawBrigeSignalOnMiddelPart(ti, rampsouth, z);
+ }
}
/* draw roof, the component of the bridge which is logically between the vehicle and the camera */
@@ -1515,9 +1633,9 @@ static void GetTileDesc_TunnelBridge(TileIndex tile, TileDesc *td)
TransportType tt = GetTunnelBridgeTransportType(tile);
if (IsTunnel(tile)) {
- td->str = (tt == TRANSPORT_RAIL) ? STR_LAI_TUNNEL_DESCRIPTION_RAILROAD : STR_LAI_TUNNEL_DESCRIPTION_ROAD;
- } else { // IsBridge(tile)
- td->str = (tt == TRANSPORT_WATER) ? STR_LAI_BRIDGE_DESCRIPTION_AQUEDUCT : GetBridgeSpec(GetBridgeType(tile))->transport_name[tt];
+ td->str = (tt == TRANSPORT_RAIL) ? HasWormholeSignals(tile) ? STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL : STR_LAI_TUNNEL_DESCRIPTION_RAILROAD : STR_LAI_TUNNEL_DESCRIPTION_ROAD;
+ } else { // IsBridge(tile)
+ td->str = (tt == TRANSPORT_WATER) ? STR_LAI_BRIDGE_DESCRIPTION_AQUEDUCT : HasWormholeSignals(tile) ? STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL : GetBridgeSpec(GetBridgeType(tile))->transport_name[tt];
}
td->owner[0] = GetTileOwner(tile);
@@ -1584,6 +1702,26 @@ static void TileLoop_TunnelBridge(TileIndex tile)
}
}
+static bool ClickTile_TunnelBridge(TileIndex tile)
+{
+ /* Show vehicles found in tunnel. */
+ if (IsTunnelTile(tile)) {
+ int count = 0;
+ const Train *t;
+ TileIndex tile_end = GetOtherTunnelBridgeEnd(tile);
+ FOR_ALL_TRAINS(t) {
+ if (!t->IsFrontEngine()) continue;
+ if (tile == t->tile || tile_end == t->tile) {
+ ShowVehicleViewWindow(t);
+ count++;
+ }
+ if (count > 19) break; // no more than 20 windows open
+ }
+ if (count > 0) return true;
+ }
+ return false;
+}
+
static TrackStatus GetTileTrackStatus_TunnelBridge(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side)
{
TransportType transport_type = GetTunnelBridgeTransportType(tile);
@@ -1836,7 +1974,7 @@ extern const TileTypeProcs _tile_type_tunnelbridge_procs = {
NULL, // add_accepted_cargo_proc
GetTileDesc_TunnelBridge, // get_tile_desc_proc
GetTileTrackStatus_TunnelBridge, // get_tile_track_status_proc
- NULL, // click_tile_proc
+ ClickTile_TunnelBridge, // click_tile_proc
NULL, // animate_tile_proc
TileLoop_TunnelBridge, // tile_loop_proc
ChangeTileOwner_TunnelBridge, // change_tile_owner_proc
diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h
index 0f7f17b3a..57f338b8b 100644
--- a/src/tunnelbridge_map.h
+++ b/src/tunnelbridge_map.h
@@ -121,4 +121,98 @@ static inline TrackBits GetTunnelBridgeReservationTrackBits(TileIndex t)
return HasTunnelBridgeReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE;
}
+/**
+ * Declare tunnel/bridge with signal simulation.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void SetBitTunnelBridgeSignal(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ SetBit(_m[t].m5, 5);
+}
+
+/**
+ * Remove tunnel/bridge with signal simulation.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void ClrBitTunnelBridgeSignal(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ ClrBit(_m[t].m5, 5);
+}
+
+/**
+ * Declare tunnel/bridge exit.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void SetBitTunnelBridgeExit(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ SetBit(_m[t].m5, 6);
+}
+
+/**
+ * Remove tunnel/bridge exit declaration.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void ClrBitTunnelBridgeExit(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ ClrBit(_m[t].m5, 6);
+}
+
+/**
+ * Is this a tunnel/bridge pair with signal simulation?
+ * On tunnel/bridge pair minimal one of the two bits is set.
+ * @param t the tile that might be a tunnel/bridge.
+ * @return true if and only if this tile is a tunnel/bridge with signal simulation.
+ */
+static inline bool HasWormholeSignals(TileIndex t)
+{
+ return IsTileType(t, MP_TUNNELBRIDGE) && (HasBit(_m[t].m5, 5) || HasBit(_m[t].m5, 6)) ;
+}
+
+/**
+ * Is this a tunnel/bridge with sign on green?
+ * @param t the tile that might be a tunnel/bridge with sign set green.
+ * @pre IsTileType(t, MP_TUNNELBRIDGE)
+ * @return true if and only if this tile is a tunnel/bridge entrance.
+ */
+static inline bool IsTunnelBridgeWithSignGreen(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return HasBit(_m[t].m5, 5) && !HasBit(_m[t].m5, 6);
+}
+
+static inline bool IsTunnelBridgeWithSignRed(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return HasBit(_m[t].m5, 5) && HasBit(_m[t].m5, 6);
+}
+
+/**
+ * Is this a tunnel/bridge entrance tile with signal?
+ * Tunnel bridge signal simulation has allways bit 5 on at entrance.
+ * @param t the tile that might be a tunnel/bridge.
+ * @return true if and only if this tile is a tunnel/bridge entrance.
+ */
+static inline bool IsTunnelBridgeEntrance(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return HasBit(_m[t].m5, 5) ;
+}
+
+/**
+ * Is this a tunnel/bridge exit?
+ * @param t the tile that might be a tunnel/bridge.
+ * @return true if and only if this tile is a tunnel/bridge exit.
+ */
+static inline bool IsTunnelBridgeExit(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return !HasBit(_m[t].m5, 5) && HasBit(_m[t].m5, 6);
+}
+
+
+
#endif /* TUNNELBRIDGE_MAP_H */
diff --git a/src/underground_gui.cpp b/src/underground_gui.cpp
new file mode 100644
index 000000000..958ddb2b0
--- /dev/null
+++ b/src/underground_gui.cpp
@@ -0,0 +1,294 @@
+/* $Id: terraform_gui.cpp 23547 2011-12-16 18:21:13Z truebrain $ */
+
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file terraform_gui.cpp GUI related to terraforming the map. */
+
+#include "stdafx.h"
+#include "clear_map.h"
+#include "company_func.h"
+#include "company_base.h"
+#include "gui.h"
+#include "window_gui.h"
+#include "window_func.h"
+#include "layer_func.h"
+#include "viewport_func.h"
+#include "command_func.h"
+#include "signs_func.h"
+#include "sound_func.h"
+#include "base_station_base.h"
+#include "textbuf_gui.h"
+#include "genworld.h"
+#include "tree_map.h"
+#include "landscape_type.h"
+#include "tilehighlight_func.h"
+#include "strings_func.h"
+#include "newgrf_object.h"
+#include "newgrf_station.h"
+#include "object.h"
+#include "hotkeys.h"
+#include "engine_base.h"
+
+#include "widgets/underground_widget.h"
+
+#include "table/strings.h"
+#include "error.h"
+
+void ShowError(TileIndex tile, CommandCost res, uint32 cmd)
+{
+ int x = TileX(tile) * TILE_SIZE;
+ int y = TileY(tile) * TILE_SIZE;
+ StringID error_part1 = GB(cmd, 16, 16);
+
+ if (IsLocalCompany() && error_part1 != 0) {
+ ShowErrorMessage(error_part1, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackSize(), res.GetTextRefStack());
+ }
+}
+
+
+/**
+ * Place a escalator.
+ * @param tile Position to place or start dragging a station.
+ */
+static void PlaceUnderground_Escalator(TileIndex tile)
+{
+ RailType railtype = RAILTYPE_RAIL;
+ Axis orientation = AXIS_X;
+ StationClassID station_class = STAT_CLASS_DFLT;
+ byte station_type = 0;
+
+ uint32 p1 = railtype | orientation << 4 | 1 << 8 | 1 << 16 | _ctrl_pressed << 24;
+ uint32 p2 = station_class | station_type << 8 | INVALID_STATION << 16;
+
+ int w = 1;
+ int h = 1;
+
+ uint top_tile = TopTile(tile);
+ uint base_tile = tile;
+ bool from_top = false; // Строим от верхнего слоя
+
+ if (top_tile == base_tile)
+ {
+ from_top = true;
+ base_tile += LayerSize();
+ };
+
+ uint32 cmdS = CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_UNDERGROUND_CAN_T_BUILD_PART);
+ uint32 cmdT = CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_UNDERGROUND_CAN_T_BUILD_TOP_PART);
+ uint32 cmdB = CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_UNDERGROUND_CAN_T_BUILD_BOTTOM_PART);
+ CommandContainer cmdTop = { top_tile, p1, p2, cmdT, CcStation, "" };
+ CommandContainer cmdBase = { base_tile, p1, p2, cmdB, CcStation, "" };
+
+ DoCommandFlag flags = DC_AUTO | DC_NO_WATER;
+ CommandCost resTop;
+ CommandCost res;
+
+ // Проверяем возможность постройки верха и низа:
+ resTop=DoCommand(&cmdTop, flags | DC_QUERY_COST);
+ if (resTop.Failed())
+ {
+ ShowError(tile, resTop, cmdT);
+ return;
+ }
+
+ res=DoCommand(&cmdBase, flags | DC_QUERY_COST);
+ if (res.Failed())
+ {
+ ShowError(tile, res, cmdB);
+ return;
+ }
+
+ res.AddCost(resTop.GetCost());
+ if (_shift_pressed || !CheckCompanyHasMoney(res))
+ {
+ if (res.Failed()) ShowError(tile, res, cmdS);
+ else ShowEstimatedCostOrIncome(res.GetCost(), TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE);
+ return;
+ }
+
+ // Станции могут быть сверху и снизу.
+ // Чтобы не было конфликта, нужно строить от активного слоя
+ CommandContainer *cmd1 = from_top ? &cmdTop : &cmdBase;
+ CommandContainer *cmd2 = from_top ? &cmdBase : &cmdTop;
+
+ // Строим уровень (в активном слое)
+ res=DoCommand(cmd1, flags | DC_EXEC);
+ assert(!res.Failed());
+
+ // Уточняем параметры
+ StationID station = GetStationIndex(cmd1->tile);
+ cmd2->p2 = station_class | station_type << 8 | station << 16;
+
+ // Строим уровень (в другом слое)
+ res=DoCommand(cmd2, flags | DC_EXEC);
+ assert(!res.Failed());
+}
+
+/** Underground toolbar managing class. */
+struct UndergroundToolbarWindow : Window {
+ int last_user_action; ///< Last started user action.
+
+ UndergroundToolbarWindow(const WindowDesc *desc, WindowNumber window_number) : Window()
+ {
+ /* This is needed as we like to have the tree available on OnInit. */
+ this->CreateNestedTree(desc);
+ this->FinishInitNested(desc, window_number);
+ this->last_user_action = WIDGET_LIST_END;
+ }
+
+ ~UndergroundToolbarWindow()
+ {
+ }
+
+ virtual void OnInit()
+ {
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch (widget) {
+ case WID_UT_BUILD_ESCALATOR: // WID_TT_BUILD_ESCALATOR
+ HandlePlacePushButton(this, WID_UT_BUILD_ESCALATOR, SPR_CURSOR_RAIL_STATION, HT_RECT);
+ this->last_user_action = widget;
+ break;
+
+ case WID_UT_DEMOLISH: // Demolish aka dynamite button
+ HandlePlacePushButton(this, WID_UT_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT | HT_DIAGONAL);
+ this->last_user_action = widget;
+ break;
+
+ default: NOT_REACHED();
+ }
+ }
+
+ virtual void OnTimeout()
+ {
+ }
+
+ virtual EventState OnKeyPress(uint16 key, uint16 keycode)
+ {
+ int num = CheckHotkeyMatch(underground_hotkeys, keycode, this);
+ if (num == -1) return ES_NOT_HANDLED;
+ this->OnClick(Point(), num, 1);
+ return ES_HANDLED;
+ }
+
+ virtual void OnPlaceObject(Point pt, TileIndex tile)
+ {
+ switch (this->last_user_action) {
+ case WID_UT_BUILD_ESCALATOR:
+ PlaceUnderground_Escalator(tile);
+ break;
+
+ case WID_UT_DEMOLISH: // Demolish aka dynamite button
+ PlaceProc_DemolishArea(tile);
+ break;
+
+ default: NOT_REACHED();
+ }
+ }
+
+ virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
+ {
+ VpSelectTilesWithMethod(pt.x, pt.y, select_method);
+ }
+
+ virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
+ {
+ Point pt = GetToolbarAlignedWindowPosition(sm_width);
+ pt.y += sm_height;
+ return pt;
+ }
+
+ virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
+ {
+ if (pt.x != -1) {
+ switch (select_proc) {
+ default: NOT_REACHED();
+ case DDSP_DEMOLISH_AREA:
+ GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
+ break;
+ }
+ }
+ }
+
+ virtual void OnPlaceObjectAbort()
+ {
+ DeleteWindowById(WC_BUILD_OBJECT, 0);
+ this->RaiseButtons();
+ }
+
+ static Hotkey<UndergroundToolbarWindow> underground_hotkeys[];
+};
+
+Hotkey<UndergroundToolbarWindow> UndergroundToolbarWindow::underground_hotkeys[] = {
+ Hotkey<UndergroundToolbarWindow>('D' | WKC_GLOBAL_HOTKEY, "dynamite", WID_UT_DEMOLISH),
+ Hotkey<UndergroundToolbarWindow>('0', "placeescalator", WID_UT_BUILD_ESCALATOR),
+ HOTKEY_LIST_END(UndergroundToolbarWindow)
+};
+Hotkey<UndergroundToolbarWindow> *_underground_hotkeys = UndergroundToolbarWindow::underground_hotkeys;
+
+static const NWidgetPart _nested_underground_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_UNDERGROUND_BUILD, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_UT_BUILD_ESCALATOR),
+ SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_RAIL_STATION, STR_UNDERGROUND_TOOLTIP_ESCALATOR),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(64, 22), EndContainer(),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_UT_DEMOLISH), SetMinimalSize(22, 22),
+ SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
+ EndContainer(),
+};
+
+static const WindowDesc _underground_desc(
+ WDP_MANUAL, 0, 0,
+ WC_UNDERGROUND, WC_NONE,
+ WDF_CONSTRUCTION,
+ _nested_underground_widgets, lengthof(_nested_underground_widgets)
+);
+
+/**
+ * Show the toolbar for terraforming in the game.
+ * @param link The toolbar we might want to link to.
+ * @return The allocated toolbar.
+ */
+Window *ShowUndergroundToolbar(Window *link)
+{
+ if (!Company::IsValidID(_local_company)) return NULL;
+
+ Window *w;
+ if (link == NULL) {
+ w = AllocateWindowDescFront<UndergroundToolbarWindow>(&_underground_desc, 0);
+ return w;
+ }
+
+ /* Delete the terraform toolbar to place it again. */
+ DeleteWindowById(WC_UNDERGROUND, 0, true);
+ w = AllocateWindowDescFront<UndergroundToolbarWindow>(&_underground_desc, 0);
+ /* Align the terraform toolbar under the main toolbar. */
+ w->top -= w->height;
+ w->SetDirty();
+ /* Put the linked toolbar to the left / right of it. */
+ link->left = w->left + (_current_text_dir == TD_RTL ? w->width : -link->width);
+ link->top = w->top;
+ link->SetDirty();
+
+ return w;
+}
+
+EventState UndergroundToolbarGlobalHotkeys(uint16 key, uint16 keycode)
+{
+ int num = CheckHotkeyMatch<UndergroundToolbarWindow>(_underground_hotkeys, keycode, NULL, true);
+ if (num == -1) return ES_NOT_HANDLED;
+ Window *w = ShowUndergroundToolbar(NULL);
+ if (w == NULL) return ES_NOT_HANDLED;
+ return w->OnKeyPress(key, keycode);
+}
diff --git a/src/underground_gui.h b/src/underground_gui.h
new file mode 100644
index 000000000..81b119545
--- /dev/null
+++ b/src/underground_gui.h
@@ -0,0 +1,19 @@
+/* $Id: underground_gui.h 21608 2012-09-08 1:13:14 constructor $ */
+
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file underground_gui.h GUI stuff related to terraforming. */
+
+#ifndef UNDERGROUND_GUI_H
+#define UNDERGROUND_GUI_H
+
+#include "window_type.h"
+
+Window *ShowUndergroundToolbar(Window *link = NULL);
+
+#endif /* UNDERGROUND_GUI_H */
diff --git a/src/vehicle.cpp b/src/vehicle.cpp
index 2f11421d6..b1ee33a1b 100644
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -15,6 +15,7 @@
#include "ship.h"
#include "spritecache.h"
#include "timetable.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "news_func.h"
#include "command_func.h"
@@ -49,6 +50,7 @@
#include "bridge_map.h"
#include "tunnel_map.h"
#include "depot_map.h"
+#include "cargodest_func.h"
#include "gamelog.h"
#include "table/strings.h"
@@ -206,6 +208,17 @@ uint Vehicle::Crash(bool flooded)
return RandomRange(pass + 1); // Randomise deceased passengers.
}
+/** Marks the separation of this vehicle's order list invalid. */
+void Vehicle::MarkSeparationInvalid()
+{
+ if (this->orders.list != NULL) this->orders.list->MarkSeparationInvalid();
+}
+
+/** Sets new separation settings for this vehicle's shared orders. */
+void Vehicle::SetSepSettings(TTSepMode Mode, uint Parameter)
+{
+ if (this->orders.list != NULL) this->orders.list->SetSepSettings(Mode, Parameter);
+}
/**
* Displays a "NewGrf Bug" error message for a engine, and pauses the game if not networking.
@@ -268,7 +281,10 @@ Vehicle::Vehicle(VehicleType type)
this->fill_percent_te_id = INVALID_TE_ID;
this->first = this;
this->colourmap = PAL_NONE;
+ this->last_station_loaded = INVALID_STATION;
this->cargo_age_counter = 1;
+ this->current_order.index = INVALID_ORDER;
+ this->last_order_id = INVALID_ORDER;
}
/**
@@ -887,9 +903,12 @@ void CallVehicleTicks()
}
if (v->type == VEH_TRAIN && Train::From(v)->IsWagon()) continue;
- if (v->type == VEH_AIRCRAFT && v->subtype != AIR_HELICOPTER) continue;
if (v->type == VEH_ROAD && !RoadVehicle::From(v)->IsFrontEngine()) continue;
+ v->travel_time++;
+
+ if (v->type == VEH_AIRCRAFT && v->subtype != AIR_HELICOPTER) continue;
+
v->motion_counter += v->cur_speed;
/* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */
if (GB(v->motion_counter, 0, 8) < v->cur_speed) PlayVehicleSound(v, VSE_RUNNING);
@@ -1008,8 +1027,8 @@ void ViewportAddVehicles(DrawPixelInfo *dpi)
for (int y = yl;; y = (y + (1 << 6)) & (0x3F << 6)) {
for (int x = xl;; x = (x + 1) & 0x3F) {
const Vehicle *v = _vehicle_viewport_hash[x + y]; // already masked & 0xFFF
-
while (v != NULL) {
+ if (LayerIndex(v->tile) == dpi->layer)
if (!(v->vehstatus & VS_HIDDEN) &&
l <= v->coord.right &&
t <= v->coord.bottom &&
@@ -1398,6 +1417,7 @@ void VehicleEnterDepot(Vehicle *v)
AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index);
}
AI::NewEvent(v->owner, new ScriptEventVehicleWaitingInDepot(v->index));
+ v->MarkSeparationInvalid();
}
}
}
@@ -1869,12 +1889,15 @@ void Vehicle::DeleteUnreachedImplicitOrders()
/**
* Prepare everything to begin the loading when arriving at a station.
+ * @param station The station ID of the station.
* @pre IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP.
*/
-void Vehicle::BeginLoading()
+void Vehicle::BeginLoading(StationID station)
{
assert(IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP);
+ this->last_station_visited = station;
+
if (this->current_order.IsType(OT_GOTO_STATION) &&
this->current_order.GetDestination() == this->last_station_visited) {
this->DeleteUnreachedImplicitOrders();
@@ -1955,6 +1978,7 @@ void Vehicle::BeginLoading()
implicit_order->MakeImplicit(this->last_station_visited);
InsertOrder(this, implicit_order, this->cur_implicit_order_index);
if (this->cur_implicit_order_index > 0) --this->cur_implicit_order_index;
+ this->current_order.index = implicit_order->index;
/* InsertOrder disabled creation of implicit orders for all vehicles with the same implicit order.
* Reenable it for this vehicle */
@@ -1966,7 +1990,35 @@ void Vehicle::BeginLoading()
this->current_order.MakeLoading(false);
}
- Station::Get(this->last_station_visited)->loading_vehicles.push_back(this);
+ UpdateVehicleRouteLinks(this, station);
+
+ /* Save the id of the order which made us arrive here. MakeLoading
+ * does not overwrite the index so it is still valid here. */
+ this->last_order_id = this->current_order.index;
+ this->last_station_loaded = station;
+ /* If all requirements for separation are met, we can initialize it. */
+ if (_settings_game.order.automatic_timetable_separation
+ && this->IsOrderListShared()
+ && this->orders.list->IsCompleteTimetable()
+ && (this->cur_real_order_index == 0)) {
+
+ if (!this->orders.list->IsSeparationValid()) this->orders.list->InitializeSeparation();
+ this->lateness_counter = this->orders.list->SeparateVehicle();
+ }
+
+
+ Station *last_visited = Station::Get(this->last_station_visited);
+ last_visited->loading_vehicles.push_back(this);
+
+ /* Update the next hop for waiting cargo. */
+ CargoID cid;
+ FOR_EACH_SET_CARGO_ID(cid, this->vcache.cached_cargo_mask) {
+ /* Only update if the last update was at least route_recalc_delay ticks earlier. */
+ if (CargoHasDestinations(cid) && last_visited->goods[cid].cargo_counter == 0) {
+ last_visited->goods[cid].cargo.UpdateCargoNextHop(last_visited, cid);
+ last_visited->goods[cid].cargo_counter = _settings_game.economy.cargodest.route_recalc_delay;
+ }
+ }
PrepareUnload(this);
@@ -1993,6 +2045,9 @@ void Vehicle::LeaveStation()
/* Only update the timetable if the vehicle was supposed to stop here. */
if (this->current_order.GetNonStopType() != ONSF_STOP_EVERYWHERE) UpdateVehicleTimetable(this, false);
+ /* Reset travel time counter. */
+ this->travel_time = 0;
+
this->current_order.MakeLeaveStation();
Station *st = Station::Get(this->last_station_visited);
st->loading_vehicles.remove(this);
@@ -2381,6 +2436,7 @@ void Vehicle::AddToShared(Vehicle *shared_chain)
if (this->next_shared != NULL) this->next_shared->previous_shared = this;
shared_chain->orders.list->AddVehicle(this);
+ shared_chain->orders.list->MarkSeparationInvalid();
}
/**
@@ -2393,6 +2449,7 @@ void Vehicle::RemoveFromShared()
bool were_first = (this->FirstShared() == this);
VehicleListIdentifier vli(VL_SHARED_ORDERS, this->type, this->owner, this->FirstShared()->index);
+ this->orders.list->MarkSeparationInvalid();
this->orders.list->RemoveVehicle(this);
if (!were_first) {
diff --git a/src/vehicle_base.h b/src/vehicle_base.h
index f0502a218..9d34cb6b0 100644
--- a/src/vehicle_base.h
+++ b/src/vehicle_base.h
@@ -21,6 +21,7 @@
#include "order_func.h"
#include "transport_type.h"
#include "group_type.h"
+#include "timetable.h"
#include "base_consist.h"
/** Vehicle status bits in #Vehicle::vehstatus. */
@@ -103,6 +104,7 @@ enum GroundVehicleSubtypeFlags {
/** Cached often queried values common to all vehicles. */
struct VehicleCache {
+ uint32 cached_cargo_mask; ///< Mask of all cargoes carried by the consist.
uint16 cached_max_speed; ///< Maximum speed of the consist (minimum of the max speed of all vehicles in the consist).
uint16 cached_cargo_age_period; ///< Number of ticks before carried cargo is aged.
@@ -211,6 +213,8 @@ public:
byte waiting_triggers; ///< Triggers to be yet matched before rerandomizing the random bits.
StationID last_station_visited; ///< The last station we stopped at.
+ StationID last_station_loaded; ///< Last station the vehicle loaded cargo at.
+ OrderID last_order_id; ///< Order id which caused the vehicle to arrive at the last loading station.
CargoID cargo_type; ///< type of cargo this vehicle is carrying
byte cargo_subtype; ///< Used for livery refits (NewGRF variations)
@@ -218,6 +222,7 @@ public:
VehicleCargoList cargo; ///< The cargo this vehicle is carrying
uint16 cargo_age_counter; ///< Ticks till cargo is aged next.
+ uint32 travel_time; ///< Ticks since last loading
byte day_counter; ///< Increased by one for each day
byte tick_counter; ///< Increased by one for each tick
byte running_ticks; ///< Number of ticks this vehicle was not stopped this day
@@ -243,7 +248,7 @@ public:
/** We want to 'destruct' the right class. */
virtual ~Vehicle();
- void BeginLoading();
+ void BeginLoading(StationID station);
void LeaveStation();
GroundVehicleCache *GetGroundVehicleCache();
@@ -761,6 +766,8 @@ public:
bool HasEngineType() const;
bool HasDepotOrder() const;
void HandlePathfindingResult(bool path_found);
+ void MarkSeparationInvalid();
+ void SetSepSettings(TTSepMode Mode, uint Parameter);
/**
* Check if the vehicle is a front engine.
diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp
index 7271d27b0..545629108 100644
--- a/src/vehicle_cmd.cpp
+++ b/src/vehicle_cmd.cpp
@@ -30,6 +30,7 @@
#include "order_backup.h"
#include "ship.h"
#include "newgrf.h"
+#include "cargodest_func.h"
#include "company_base.h"
#include "table/strings.h"
@@ -147,6 +148,7 @@ CommandCost CmdBuildVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint
if (v->IsPrimaryVehicle()) {
GroupStatistics::CountVehicle(v, 1);
OrderBackup::Restore(v, p2);
+ PrefillRouteLinks(v);
}
}
@@ -478,6 +480,8 @@ CommandCost CmdRefitVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint
default: NOT_REACHED();
}
+ if (front->IsPrimaryVehicle()) PrefillRouteLinks(front);
+
InvalidateWindowData(WC_VEHICLE_DETAILS, front->index);
SetWindowDirty(WC_VEHICLE_DEPOT, front->tile);
InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0);
@@ -565,6 +569,8 @@ CommandCost CmdStartStopVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1,
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
SetWindowDirty(WC_VEHICLE_DEPOT, v->tile);
SetWindowClassesDirty(GetWindowClassForVehicleType(v->type));
+
+ v->MarkSeparationInvalid();
}
return CommandCost();
}
diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp
index d6a1bec67..03ad805c9 100644
--- a/src/vehicle_gui.cpp
+++ b/src/vehicle_gui.cpp
@@ -2730,3 +2730,28 @@ int GetVehicleWidth(Vehicle *v, EngineImageType image_type)
return vehicle_width;
}
+
+/**
+ * Sum the cargo carried by a vehicle by final destination.
+ * @param v The vehicle.
+ * @param sum The cargo summary is added to this.
+ */
+void AddVehicleCargoDestSummary(const Vehicle *v, CargoDestSummary *sum)
+{
+ const VehicleCargoList::List *packets = v->cargo.Packets();
+ for (VehicleCargoList::ConstIterator it = packets->begin(); it != packets->end(); ++it) {
+ const CargoPacket *cp = *it;
+
+ /* Search for an existing list entry. */
+ CargoDestSummary::iterator data;
+ for (data = sum->begin(); data != sum->end(); ++data) {
+ if (data->type == cp->DestinationType() && data->dest == cp->DestinationID()) {
+ data->count += cp->Count();
+ break;
+ }
+ }
+
+ /* Not found, insert new entry. */
+ if (data == sum->end() && cp->DestinationID() != INVALID_SOURCE) sum->push_back(CargoDestSummaryData(cp->DestinationID(), cp->DestinationType(), cp->Count()));
+ }
+}
diff --git a/src/vehicle_gui.h b/src/vehicle_gui.h
index 83e098dcd..c3b702fc8 100644
--- a/src/vehicle_gui.h
+++ b/src/vehicle_gui.h
@@ -18,6 +18,7 @@
#include "station_type.h"
#include "engine_type.h"
#include "company_type.h"
+#include <list>
void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit = false);
@@ -37,6 +38,22 @@ enum VehicleInvalidateWindowData {
VIWD_AUTOREPLACE = -4, ///< Autoreplace replaced the vehicle.
};
+/** List item for one destination. */
+struct CargoDestSummaryData {
+ SourceID dest; ///< Destination ID
+ SourceType type; ///< Destination type
+ uint count; ///< Cargo count
+
+ CargoDestSummaryData(SourceID dest, SourceType type, uint count)
+ : dest(dest), type(type), count(count)
+ { }
+};
+
+/** List of cargo amounts grouped by final destination. */
+typedef std::list<CargoDestSummaryData> CargoDestSummary;
+
+void AddVehicleCargoDestSummary(const Vehicle *v, CargoDestSummary *sum);
+
int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number);
void DrawTrainImage(const Train *v, int left, int right, int y, VehicleID selection, EngineImageType image_type, int skip, VehicleID drag_dest = INVALID_VEHICLE);
diff --git a/src/vehicle_type.h b/src/vehicle_type.h
index 0921b39e3..e66a28aba 100644
--- a/src/vehicle_type.h
+++ b/src/vehicle_type.h
@@ -72,7 +72,7 @@ enum DepotCommand {
DEPOT_COMMAND_MASK = 0xFU << 28,
};
-static const uint MAX_LENGTH_VEHICLE_NAME_CHARS = 32; ///< The maximum length of a vehicle name in characters including '\0'
+static const uint MAX_LENGTH_VEHICLE_NAME_CHARS = 128; ///< The maximum length of a vehicle name in characters including '\0'
/** The length of a vehicle in tile units. */
static const uint VEHICLE_LENGTH = 8;
diff --git a/src/viewport.cpp b/src/viewport.cpp
index bfcc8d9d1..10cc8f360 100644
--- a/src/viewport.cpp
+++ b/src/viewport.cpp
@@ -28,6 +28,8 @@
#include "stdafx.h"
#include "landscape.h"
+#include "layer_gui.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "station_base.h"
#include "waypoint_base.h"
@@ -51,6 +53,13 @@
Point _tile_fract_coords;
+struct RailTrackEndpoint {
+ TileIndex tile;
+ TrackdirBits dirs;
+};
+
+RailTrackEndpoint _rail_track_endpoints[4];
+
struct StringSpriteToDraw {
StringID string;
Colours colour;
@@ -1080,6 +1089,8 @@ static void ViewportAddLandscape()
y_cur < MapMaxY() * TILE_SIZE) {
TileIndex tile = TileVirtXY(x_cur, y_cur);
+ /* Валидны только клетки текущего слоя */
+ if (LayerIndex(tile) == _vd.dpi.layer)
if (!_settings_game.construction.freeform_edges || (TileX(tile) != 0 && TileY(tile) != 0)) {
if (x_cur == ((int)MapMaxX() - 1) * TILE_SIZE || y_cur == ((int)MapMaxY() - 1) * TILE_SIZE) {
uint maxh = max<uint>(TileHeight(tile), 1);
@@ -1475,6 +1486,9 @@ void ViewportDoDraw(const ViewPort *vp, int left, int top, int right, int bottom
_vd.dpi.dst_ptr = BlitterFactoryBase::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top);
+ /* Определение слоя (который будем рисовать) */
+ _vd.dpi.layer = calculateLayer(vp);
+
ViewportAddLandscape();
ViewportAddVehicles(&_vd.dpi);
@@ -1953,7 +1967,7 @@ static void PlaceObject()
}
-bool HandleViewportClicked(const ViewPort *vp, int x, int y)
+bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click)
{
const Vehicle *v = CheckClickOnVehicle(vp, x, y);
@@ -1961,6 +1975,13 @@ bool HandleViewportClicked(const ViewPort *vp, int x, int y)
if (v != NULL && VehicleClicked(v)) return true;
}
+ /* Double-clicking finishes current polyline and starts new one. */
+ if (double_click && (_thd.place_mode & HT_POLY)) {
+ ClearRailPlacementEndpoints();
+ SetTileSelectSize(1, 1);
+ return true;
+ }
+
/* Vehicle placement mode already handled above. */
if ((_thd.place_mode & HT_DRAG_MASK) != HT_NONE) {
PlaceObject();
@@ -2113,7 +2134,7 @@ Window *TileHighlightData::GetCallbackWnd()
return FindWindowById(this->window_class, this->window_number);
}
-
+static HighLightStyle CalcPolyrailDrawstyle(Point pt);
/**
* Updates tile highlighting for all cases.
@@ -2170,28 +2191,42 @@ void UpdateTileSelection()
y1 += TILE_SIZE / 2;
break;
case HT_RAIL:
- /* Draw one highlighted tile in any direction */
- new_drawstyle = GetAutorailHT(pt.x, pt.y);
- break;
case HT_LINE:
- switch (_thd.place_mode & HT_DIR_MASK) {
- case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
- case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
-
- case HT_DIR_HU:
- case HT_DIR_HL:
- new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
- break;
-
- case HT_DIR_VL:
- case HT_DIR_VR:
- new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
+ /* Handle polyline highlight */
+ if (_thd.place_mode & HT_POLY) {
+ new_drawstyle = CalcPolyrailDrawstyle(pt);
+ if (new_drawstyle != HT_NONE) {
+ x1 = min(_thd.selstart.x, _thd.selend.x);
+ y1 = min(_thd.selstart.y, _thd.selend.y);
+ _thd.new_size.x = abs<int>(_thd.selstart.x - (_thd.selend.x & ~TILE_UNIT_MASK)) + TILE_SIZE;
+ _thd.new_size.y = abs<int>(_thd.selstart.y - (_thd.selend.y & ~TILE_UNIT_MASK)) + TILE_SIZE;
break;
-
- default: NOT_REACHED();
+ }
+ }
+ /* Handle regular (non-polyline) highlight */
+ if (_thd.place_mode & HT_RAIL) {
+ /* Draw one highlighted tile in any direction */
+ new_drawstyle = GetAutorailHT(pt.x, pt.y);
+ } else { // HT_LINE
+ switch (_thd.place_mode & HT_DIR_MASK) {
+ case HT_DIR_X: new_drawstyle = HT_LINE | HT_DIR_X; break;
+ case HT_DIR_Y: new_drawstyle = HT_LINE | HT_DIR_Y; break;
+
+ case HT_DIR_HU:
+ case HT_DIR_HL:
+ new_drawstyle = (pt.x & TILE_UNIT_MASK) + (pt.y & TILE_UNIT_MASK) <= TILE_SIZE ? HT_LINE | HT_DIR_HU : HT_LINE | HT_DIR_HL;
+ break;
+
+ case HT_DIR_VL:
+ case HT_DIR_VR:
+ new_drawstyle = (pt.x & TILE_UNIT_MASK) > (pt.y & TILE_UNIT_MASK) ? HT_LINE | HT_DIR_VL : HT_LINE | HT_DIR_VR;
+ break;
+
+ default: NOT_REACHED();
+ }
+ _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
+ _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
}
- _thd.selstart.x = x1 & ~TILE_UNIT_MASK;
- _thd.selstart.y = y1 & ~TILE_UNIT_MASK;
break;
default:
NOT_REACHED();
@@ -2455,7 +2490,31 @@ static int CalcHeightdiff(HighLightStyle style, uint distance, TileIndex start_t
return (int)(h1 - h0) * TILE_HEIGHT_STEP;
}
-static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF};
+static void ShowLengthMeasurement(HighLightStyle style, TileIndex start_tile, TileIndex end_tile, TooltipCloseCondition close_cond = TCC_LEFT_CLICK, bool show_single_tile_length = false)
+{
+ static const StringID measure_strings_length[] = {STR_NULL, STR_MEASURE_LENGTH, STR_MEASURE_LENGTH_HEIGHTDIFF};
+
+ if (_settings_client.gui.measure_tooltip) {
+ uint distance = DistanceManhattan(start_tile, end_tile) + 1;
+ byte index = 0;
+ uint64 params[2];
+
+ if (show_single_tile_length || distance != 1) {
+ int heightdiff = CalcHeightdiff(style, distance, start_tile, end_tile);
+ /* If we are showing a tooltip for horizontal or vertical drags,
+ * 2 tiles have a length of 1. To bias towards the ceiling we add
+ * one before division. It feels more natural to count 3 lengths as 2 */
+ if ((style & HT_DIR_MASK) != HT_DIR_X && (style & HT_DIR_MASK) != HT_DIR_Y) {
+ distance = CeilDiv(distance, 2);
+ }
+
+ params[index++] = distance;
+ if (heightdiff != 0) params[index++] = heightdiff;
+ }
+
+ ShowMeasurementTooltips(measure_strings_length[index], index, params, close_cond);
+ }
+}
/**
* Check for underflowing the map.
@@ -2486,8 +2545,178 @@ static void CheckOverflow(int &test, int &other, int max, int mult)
test = max;
}
+static const struct {
+ Point start_point_offset;
+ Point direction;
+}
+_auto_line_by_trackdir[] = {
+ { { TILE_SIZE - 2, TILE_SIZE / 2 }, { -1, 0 } }, // TRACKDIR_X_NE
+ { { TILE_SIZE / 2, 0 }, { 0, +1 } }, // TRACKDIR_Y_SE
+ { { TILE_SIZE / 2 - 1, 0 }, { -1, +1 } }, // TRACKDIR_UPPER_E
+ { { TILE_SIZE - 1, TILE_SIZE / 2 }, { -1, +1 } }, // TRACKDIR_LOWER_E
+ { { TILE_SIZE / 2, 0 }, { +1, +1 } }, // TRACKDIR_LEFT_S
+ { { 0, TILE_SIZE / 2 }, { +1, +1 } }, // TRACKDIR_RIGHT_S
+ { { 0, 0 }, { 0, 0 } }, // TRACKDIR_RVREV_NE
+ { { 0, 0 }, { 0, 0 } }, // TRACKDIR_RVREV_SE
+ { { 0, TILE_SIZE / 2 }, { +1, 0 } }, // TRACKDIR_X_SW
+ { { TILE_SIZE / 2, TILE_SIZE - 1 }, { 0, -1 } }, // TRACKDIR_Y_NW
+ { { 0, TILE_SIZE / 2 - 1 }, { +1, -1 } }, // TRACKDIR_UPPER_W
+ { { TILE_SIZE / 2, TILE_SIZE - 1 }, { +1, -1 } }, // TRACKDIR_LOWER_W
+ { { TILE_SIZE - 1, TILE_SIZE / 2 - 1 }, { -1, -1 } }, // TRACKDIR_LEFT_N
+ { { TILE_SIZE / 2 - 1, TILE_SIZE - 1 }, { -1, -1 } } // TRACKDIR_RIGHT_N
+};
+
+/**
+ * Returns the distnce from a given point to a rail line.
+ *
+ * @param pt The point to get the distance from.
+ * @param start_tile Coordinates, in tile "units", of the tile where the line starts.
+ * @param trackdir The first trackdir of the line.
+ * @return X/Y coordinates of the vector that connects the 'pt' point with the line at the best path.
+ */
+static Point GetDistanceToAutoLine(Point pt, Point start_tile, Trackdir trackdir)
+{
+ assert(IsValidTrackdir(trackdir) && !IsReversingRoadTrackdir(trackdir));
+
+ /* calculate distance from the given point to the point where the line starts */
+ Point d = {
+ start_tile.x + _auto_line_by_trackdir[trackdir].start_point_offset.x - pt.x,
+ start_tile.y + _auto_line_by_trackdir[trackdir].start_point_offset.y - pt.y
+ };
+ /* get line direction */
+ Point direction = _auto_line_by_trackdir[trackdir].direction;
+ /* correct the start point for "diagonal" dirs; there are two possible lines, choose the closer one */
+ if (direction.x == 0) { // TRACKDIR_Y_SE and TRACKDIR_Y_NW trackdirs
+ d.x -= (int)(d.x > 0);
+ } else if (direction.y == 0) { // TRACKDIR_X_NE and TRACKDIR_X_SW trackdirs
+ d.y -= (int)(d.y > 0);
+ }
+
+ /* calculate distance to the end of the line */
+ int scale = direction.x * direction.x + direction.y * direction.y;
+ int length = d.x * direction.y - d.y * direction.x;
+ Point ret = { direction.y, -direction.x }; // 'direction' rotated 90 degree right
+ if (length > 0) { // is the 'p' point on the left side of the line ("up" is pointed by 'direction')
+ ret.x -= direction.x;
+ ret.y -= direction.y;
+ } else {
+ ret.x += direction.x;
+ ret.y += direction.y;
+ }
+ ret.x = length * ret.x / scale;
+ ret.y = length * ret.y / scale;
+
+ /* test if the calculated end point is behind the start point;
+ * if not return the distance to the start point */
+ if (((d.x < ret.x) == (direction.x < 0)) && ((d.y < ret.y) == (direction.y < 0))) return d;
+
+ return ret;
+}
+
+static void ClampAutoLineToMapBorders(Point *line_end, Track line_orientation)
+{
+ int padding = _settings_game.construction.freeform_edges ? TILE_SIZE : 0;
+
+ Rect borders = {
+ padding, // left
+ padding, // top
+ MapSizeX() * TILE_SIZE - padding - 1, // right
+ MapSizeY() * TILE_SIZE - padding - 1 // bottom
+ };
+
+ switch (line_orientation) {
+ case TRACK_X:
+ line_end->y = Clamp(line_end->y, borders.top, borders.bottom);
+ break;
+
+ case TRACK_Y:
+ line_end->x = Clamp(line_end->x, borders.left, borders.right);
+ break;
+
+ case TRACK_UPPER:
+ case TRACK_LOWER:
+ if (line_end->x < borders.left) {
+ line_end->y += borders.left - line_end->x;
+ line_end->x += borders.left - line_end->x;
+ } else if (line_end->x > borders.right) {
+ line_end->y += borders.right - line_end->x;
+ line_end->x += borders.right - line_end->x;
+ }
+ if (line_end->y < borders.top) {
+ line_end->x += borders.top - line_end->y;
+ line_end->y += borders.top - line_end->y;
+ } else if (line_end->y > borders.bottom) {
+ line_end->x += borders.bottom - line_end->y;
+ line_end->y += borders.bottom - line_end->y;
+ }
+ break;
+
+ case TRACK_LEFT:
+ case TRACK_RIGHT:
+ if (line_end->x < borders.left) {
+ line_end->y -= borders.left - line_end->x;
+ line_end->x += borders.left - line_end->x;
+ } else if (line_end->x > borders.right) {
+ line_end->y -= borders.right - line_end->x;
+ line_end->x += borders.right - line_end->x;
+ }
+ if (line_end->y < borders.top) {
+ line_end->x -= borders.top - line_end->y;
+ line_end->y += borders.top - line_end->y;
+ } else if (line_end->y > borders.bottom) {
+ line_end->x -= borders.bottom - line_end->y;
+ line_end->y += borders.bottom - line_end->y;
+ }
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+
+ assert(IsInsideMM(line_end->x, borders.left, borders.right + 1) && IsInsideMM(line_end->y, borders.top, borders.bottom + 1));
+}
+
+static const TrackdirBits _autoline_dirs_allowed_by_highlight_dir[] = {
+ TRACKDIR_BIT_X_NE | TRACKDIR_BIT_X_SW, // HT_DIR_X
+ TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_Y_SE, // HT_DIR_Y
+ TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_W, // HT_DIR_HU
+ TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_LOWER_W, // HT_DIR_HL
+ TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_N | TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_S, // HT_DIR_VL
+ TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_RIGHT_N | TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_RIGHT_S, // HT_DIR_VR
+};
+
+static Trackdir FindBestAutoLine(const Point &pt, RailTrackEndpoint *start_points, uint num_start_points, TrackdirBits allowed_trackdirs, Point *ret_start_tile, Point *ret_end_pos)
+{
+ Trackdir ret = INVALID_TRACKDIR;
+ uint best_distance = UINT_MAX;
+
+ for (; num_start_points-- > 0; start_points++) {
+ /* skip invalid tiles */
+ if (!IsValidTile(start_points->tile)) continue;
+
+ Trackdir trackdir;
+ FOR_EACH_SET_TRACKDIR(trackdir, start_points->dirs & allowed_trackdirs) {
+ Point start_tile = { TileX(start_points->tile) * TILE_SIZE, TileY(start_points->tile) * TILE_SIZE };
+ Point offset = GetDistanceToAutoLine(pt, start_tile, trackdir);
+ uint distance = (uint)(offset.x * offset.x + offset.y * offset.y);
+ if (distance < best_distance) {
+ *ret_start_tile = start_tile;
+ ret_end_pos->x = pt.x + offset.x;
+ ret_end_pos->y = pt.y + offset.y;
+ best_distance = distance;
+ ret = trackdir;
+ }
+ }
+ }
+
+ /* cut the line at map borders */
+ if (ret != INVALID_TRACKDIR) ClampAutoLineToMapBorders(ret_end_pos, TrackdirToTrack(ret));
+
+ return ret;
+}
+
/** while dragging */
-static void CalcRaildirsDrawstyle(int x, int y, int method)
+static void CalcRaildirsDrawstyle(int x, int y, ViewportPlaceMethod method)
{
HighLightStyle b;
@@ -2672,32 +2901,31 @@ static void CalcRaildirsDrawstyle(int x, int y, int method)
}
}
- if (_settings_client.gui.measure_tooltip) {
- TileIndex t0 = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
- TileIndex t1 = TileVirtXY(x, y);
- uint distance = DistanceManhattan(t0, t1) + 1;
- byte index = 0;
- uint64 params[2];
+ _thd.selend.x = x;
+ _thd.selend.y = y;
+ _thd.next_drawstyle = b;
- if (distance != 1) {
- int heightdiff = CalcHeightdiff(b, distance, t0, t1);
- /* If we are showing a tooltip for horizontal or vertical drags,
- * 2 tiles have a length of 1. To bias towards the ceiling we add
- * one before division. It feels more natural to count 3 lengths as 2 */
- if ((b & HT_DIR_MASK) != HT_DIR_X && (b & HT_DIR_MASK) != HT_DIR_Y) {
- distance = CeilDiv(distance, 2);
- }
+ ShowLengthMeasurement(b, TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y));
+}
- params[index++] = distance;
- if (heightdiff != 0) params[index++] = heightdiff;
- }
+static HighLightStyle CalcPolyrailDrawstyle(Point pt)
+{
+ /* directions allowed by highlight method */
+ TrackdirBits allowed_trackdirs = (_thd.place_mode & HT_RAIL) ? TRACKDIR_BIT_MASK : _autoline_dirs_allowed_by_highlight_dir[_thd.place_mode & HT_DIR_MASK];
- ShowMeasurementTooltips(measure_strings_length[index], index, params);
- }
+ /* now find the best track */
+ Trackdir best_trackdir = FindBestAutoLine(pt, _rail_track_endpoints, lengthof(_rail_track_endpoints), allowed_trackdirs, &_thd.selstart, &_thd.selend);
+ if (best_trackdir == INVALID_TRACKDIR) return HT_NONE; // no match
- _thd.selend.x = x;
- _thd.selend.y = y;
- _thd.next_drawstyle = b;
+ TileIndex start_tile = TileVirtXY(_thd.selstart.x, _thd.selstart.y);
+ TileIndex end_tile = TileVirtXY(_thd.selend.x, _thd.selend.y);
+
+ HighLightStyle ret = HT_POLY |
+ (HighLightStyle)TrackdirToTrack(best_trackdir) | // cast TRACK_XXX to HT_DIR_XXX
+ (start_tile == end_tile ? HT_RAIL : HT_LINE); // one tile case or multitile selection
+
+ ShowLengthMeasurement(ret, start_tile, end_tile, TCC_HOVER, true);
+ return ret;
}
/**
@@ -2769,27 +2997,12 @@ calc_heightdiff_single_direction:;
x = sx + Clamp(x - sx, -limit, limit);
y = sy + Clamp(y - sy, -limit, limit);
}
- if (_settings_client.gui.measure_tooltip) {
- TileIndex t0 = TileVirtXY(sx, sy);
- TileIndex t1 = TileVirtXY(x, y);
- uint distance = DistanceManhattan(t0, t1) + 1;
- byte index = 0;
- uint64 params[2];
-
- if (distance != 1) {
- /* With current code passing a HT_LINE style to calculate the height
- * difference is enough. However if/when a point-tool is created
- * with this method, function should be called with new_style (below)
- * instead of HT_LINE | style case HT_POINT is handled specially
- * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
- int heightdiff = CalcHeightdiff(HT_LINE | style, 0, t0, t1);
-
- params[index++] = distance;
- if (heightdiff != 0) params[index++] = heightdiff;
- }
-
- ShowMeasurementTooltips(measure_strings_length[index], index, params);
- }
+ /* With current code passing a HT_LINE style to calculate the height
+ * difference is enough. However if/when a point-tool is created
+ * with this method, function should be called with new_style (below)
+ * instead of HT_LINE | style case HT_POINT is handled specially
+ * new_style := (_thd.next_drawstyle & HT_RECT) ? HT_LINE | style : _thd.next_drawstyle; */
+ ShowLengthMeasurement(HT_LINE | style, TileVirtXY(sx, sy), TileVirtXY(x, y));
break;
case VPM_X_AND_Y_LIMITED: // Drag an X by Y constrained rect area.
@@ -2900,7 +3113,7 @@ EventState VpHandlePlaceSizingDrag()
} else if (_thd.select_method & VPM_SIGNALDIRS) {
_thd.place_mode = HT_RECT | others;
} else if (_thd.select_method & VPM_RAILDIRS) {
- _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS) ? _thd.next_drawstyle : (HT_RAIL | others);
+ _thd.place_mode = (_thd.select_method & ~VPM_RAILDIRS ? _thd.next_drawstyle : HT_RAIL) | others;
} else {
_thd.place_mode = HT_POINT | others;
}
@@ -2966,3 +3179,85 @@ void ResetObjectToPlace()
{
SetObjectToPlace(SPR_CURSOR_MOUSE, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
}
+
+void RemoveConnectedEndpointDirs(RailTrackEndpoint *a, const RailTrackEndpoint &b)
+{
+ if (IsValidTile(a->tile) && IsValidTile(b.tile)) {
+ for (DiagDirection test_dir = DIAGDIR_BEGIN; test_dir < DIAGDIR_END; test_dir++) {
+ TrackdirBits test_trackdirs = DiagdirReachesTrackdirs(test_dir);
+ if (test_trackdirs & a->dirs) {
+ if (a->tile == b.tile) {
+ if (b.dirs & test_trackdirs) a->dirs &= ~test_trackdirs;
+ } else if (a->tile - TileOffsByDiagDir(test_dir) == b.tile) {
+ if (b.dirs & DiagdirReachesTrackdirs(ReverseDiagDir(test_dir))) a->dirs &= ~test_trackdirs;
+ }
+ if (a->dirs == TRACKDIR_BIT_NONE) {
+ a->tile = INVALID_TILE;
+ return;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Store the position of lastly built rail track; for highlighting purposes.
+ *
+ * In "polyline" highlighting mode, the stored end point of the track
+ * will be used as the start point of a being highlighted track.
+ *
+ * @param start_tile tile where the track starts
+ * @param end_tile tile where the track ends
+ * @param start_track track piece on the start_tile
+ * @param bidirectional whether to allow to highlight next track in any direction; otherwise new track will have to fallow the stored one (usefull when placing tunnels and bridges)
+ */
+void StoreRailPlacementEndpoints(TileIndex start_tile, TileIndex end_tile, Track start_track, bool bidirectional)
+{
+ const uint NUM_ENDPOINTS = lengthof(_rail_track_endpoints);
+
+ RailTrackEndpoint new_endpoints[NUM_ENDPOINTS] = {
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ { INVALID_TILE, TRACKDIR_BIT_NONE },
+ };
+
+ if (start_tile != INVALID_TILE && end_tile != INVALID_TILE) {
+ /* calculate trackdirs at booth ends of the track (pointing toward track middle) */
+ Trackdir start_trackdir = TrackToTrackdir(start_track);
+ Trackdir end_trackdir = ReverseTrackdir(start_trackdir);
+ if (start_tile != end_tile) { // multi-tile case
+ /* determine proper direction (toward track middle) */
+ uint distance = DistanceManhattan(start_tile, end_tile);
+ if (distance < DistanceManhattan(TileAddByDiagDir(start_tile, TrackdirToExitdir(start_trackdir)), end_tile)) {
+ Swap(start_trackdir, end_trackdir);
+ }
+ /* determine proper track on the end tile - switch between upper/lower or left/right based on the length */
+ if (distance % 2 != 0) end_trackdir = NextTrackdir(end_trackdir);
+ }
+
+ /* compute new endpoints */
+ DiagDirection start_exit_dir = TrackdirToExitdir(ReverseTrackdir(start_trackdir));
+ DiagDirection end_exit_dir = TrackdirToExitdir(ReverseTrackdir(end_trackdir));
+ new_endpoints[0].tile = TileAddByDiagDir(start_tile, start_exit_dir);
+ new_endpoints[0].dirs = DiagdirReachesTrackdirs(start_exit_dir);
+ new_endpoints[1].tile = TileAddByDiagDir(end_tile, end_exit_dir);
+ new_endpoints[1].dirs = DiagdirReachesTrackdirs(end_exit_dir);
+ if (bidirectional) {
+ new_endpoints[2].tile = start_tile;
+ new_endpoints[2].dirs = DiagdirReachesTrackdirs(ReverseDiagDir(start_exit_dir));
+ new_endpoints[3].tile = end_tile;
+ new_endpoints[3].dirs = DiagdirReachesTrackdirs(ReverseDiagDir(end_exit_dir));
+ }
+
+ /* exclude all endpoints stored previously */
+ for (uint i = 0; i < NUM_ENDPOINTS; i++) {
+ for (uint j = 0; j < NUM_ENDPOINTS; j++) {
+ RemoveConnectedEndpointDirs(&new_endpoints[i], _rail_track_endpoints[j]);
+ }
+ }
+ }
+
+ /* store endpoints */
+ MemCpyT(_rail_track_endpoints, new_endpoints, NUM_ENDPOINTS);
+}
diff --git a/src/viewport_func.h b/src/viewport_func.h
index dba28bb9e..7788cba7c 100644
--- a/src/viewport_func.h
+++ b/src/viewport_func.h
@@ -57,7 +57,7 @@ void ViewportAddString(const DrawPixelInfo *dpi, ZoomLevel small_from, const Vie
void StartSpriteCombine();
void EndSpriteCombine();
-bool HandleViewportClicked(const ViewPort *vp, int x, int y);
+bool HandleViewportClicked(const ViewPort *vp, int x, int y, bool double_click);
void SetRedErrorSquare(TileIndex tile);
void SetTileSelectSize(int w, int h);
void SetTileSelectBigSize(int ox, int oy, int sx, int sy);
diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp
index 6b4f41eb9..ad047cfd6 100644
--- a/src/water_cmd.cpp
+++ b/src/water_cmd.cpp
@@ -12,6 +12,7 @@
#include "stdafx.h"
#include "cmd_helper.h"
#include "landscape.h"
+#include "layer_func.h"
#include "viewport_func.h"
#include "command_func.h"
#include "town.h"
@@ -419,6 +420,11 @@ CommandCost CmdBuildCanal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32
/* can't make water of water! */
if (IsTileType(tile, MP_WATER) && (!IsTileOwner(tile, OWNER_WATER) || wc == WATER_CLASS_SEA)) continue;
+ /* can't make underground water */
+ if (IsUnderground(tile)) {
+ return_cmd_error(STR_ERROR_UNDERGROUND_CAN_T_BUILD_UNDER_GROUND);
+ }
+
bool water = IsWaterTile(tile);
ret = DoCommand(tile, 0, 0, flags | DC_FORCE_CLEAR_TILE, CMD_LANDSCAPE_CLEAR);
if (ret.Failed()) return ret;
@@ -1043,6 +1049,9 @@ void DoFloodTile(TileIndex target)
{
assert(!IsTileType(target, MP_WATER));
+ /* Подземная часть карты не заливается */
+ if (IsUnderground(target)) return;
+
bool flooded = false; // Will be set to true if something is changed.
Backup<CompanyByte> cur_company(_current_company, OWNER_WATER, FILE_LINE);
@@ -1197,7 +1206,7 @@ void ConvertGroundTilesIntoWaterTiles()
for (TileIndex tile = 0; tile < MapSize(); ++tile) {
Slope slope = GetTileSlope(tile, &z);
- if (IsTileType(tile, MP_CLEAR) && z == 0) {
+ if (IsTileType(tile, MP_CLEAR) && z == 0 && !IsUnderground(tile)) {
/* Make both water for tiles at level 0
* and make shore, as that looks much better
* during the generation. */
diff --git a/src/waypoint_gui.cpp b/src/waypoint_gui.cpp
index cd50a9ca8..513c9ae9e 100644
--- a/src/waypoint_gui.cpp
+++ b/src/waypoint_gui.cpp
@@ -22,6 +22,7 @@
#include "company_base.h"
#include "window_func.h"
#include "waypoint_base.h"
+#include "departures_gui.h"
#include "widgets/waypoint_widget.h"
@@ -109,6 +110,10 @@ public:
case WID_W_SHOW_VEHICLES: // show list of vehicles having this waypoint in their orders
ShowVehicleListWindow(this->wp->owner, this->vt, this->wp->index);
break;
+
+ case WID_W_DEPARTURES: // show departure times of vehicles
+ ShowWaypointDepartures((StationID)this->wp->index);
+ break;
}
}
@@ -164,6 +169,7 @@ static const NWidgetPart _nested_waypoint_view_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_CENTER_VIEW), SetMinimalSize(100, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_LOCATION, STR_BUOY_VIEW_CENTER_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_RENAME), SetMinimalSize(100, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_RENAME, STR_BUOY_VIEW_CHANGE_BUOY_NAME),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_DEPARTURES), SetMinimalSize(100, 12), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_STATION_VIEW_DEPARTURES_BUTTON, STR_STATION_VIEW_DEPARTURES_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_W_SHOW_VEHICLES), SetMinimalSize(15, 12), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
EndContainer(),
diff --git a/src/widgets/departures_widget.h b/src/widgets/departures_widget.h
new file mode 100644
index 000000000..e39b4d6cd
--- /dev/null
+++ b/src/widgets/departures_widget.h
@@ -0,0 +1,29 @@
+/* $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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file waypoint_widget.h Types related to the waypoint widgets. */
+
+#ifndef WIDGETS_DEPARTURES_WIDGET_H
+#define WIDGETS_DEPARTURES_WIDGET_H
+
+/** Widgets of the WC_DEPARTURES_BOARD. */
+enum DeparturesWindowWidgets {
+ WID_DB_CAPTION, ///< Window caption
+ WID_DB_LIST, ///< List of departures
+ WID_DB_SCROLLBAR, ///< List scrollbar
+ WID_DB_SHOW_DEPS, ///< Toggle departures button
+ WID_DB_SHOW_ARRS, ///< Toggle arrivals button
+ WID_DB_SHOW_VIA, ///< Toggle via button
+ WID_DB_SHOW_TRAINS, ///< Toggle trains button
+ WID_DB_SHOW_ROADVEHS, ///< Toggle road vehicles button
+ WID_DB_SHOW_SHIPS, ///< Toggle ships button
+ WID_DB_SHOW_PLANES, ///< Toggle planes button
+};
+
+#endif /* WIDGETS_DEPARTURES_WIDGET_H */
diff --git a/src/widgets/genworld_widget.h b/src/widgets/genworld_widget.h
index 10727f103..f9cc4e104 100644
--- a/src/widgets/genworld_widget.h
+++ b/src/widgets/genworld_widget.h
@@ -21,6 +21,7 @@ enum GenerateLandscapeWidgets {
WID_GL_MAPSIZE_X_PULLDOWN, ///< Dropdown 'map X size'.
WID_GL_MAPSIZE_Y_PULLDOWN, ///< Dropdown 'map Y size'.
+ WID_GL_LAYER_COUNT_PULLDOWN, ///< Dropdown 'map layer count'.
WID_GL_TOWN_PULLDOWN, ///< Dropdown 'No. of towns'.
WID_GL_INDUSTRY_PULLDOWN, ///< Dropdown 'No. of industries'.
@@ -68,6 +69,7 @@ enum CreateScenarioWidgets {
WID_CS_RANDOM_WORLD, ///< Generate random land button
WID_CS_MAPSIZE_X_PULLDOWN, ///< Pull-down arrow for x map size.
WID_CS_MAPSIZE_Y_PULLDOWN, ///< Pull-down arrow for y map size.
+ WID_CS_LAYER_COUNT_PULLDOWN, ///< Pull-down arrow for map layer count.
WID_CS_START_DATE_DOWN, ///< Decrease start year (start earlier).
WID_CS_START_DATE_TEXT, ///< Clickable start date value.
WID_CS_START_DATE_UP, ///< Increase start year (start later).
diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h
index f022489e5..d12d870c5 100644
--- a/src/widgets/road_widget.h
+++ b/src/widgets/road_widget.h
@@ -23,6 +23,7 @@ enum RoadToolbarWidgets {
WID_ROT_BUS_STATION, ///< Build bus station.
WID_ROT_TRUCK_STATION, ///< Build truck station.
WID_ROT_ONE_WAY, ///< Build one-way road.
+ WID_ROT_TRAFFIC_LIGHT, ///< Build trafficlights.
WID_ROT_BUILD_BRIDGE, ///< Build bridge.
WID_ROT_BUILD_TUNNEL, ///< Build tunnel.
WID_ROT_REMOVE, ///< Remove road.
diff --git a/src/widgets/smallmap_widget.h b/src/widgets/smallmap_widget.h
index 6476b8f1b..7b7f1ba16 100644
--- a/src/widgets/smallmap_widget.h
+++ b/src/widgets/smallmap_widget.h
@@ -23,6 +23,7 @@ enum SmallMapWidgets {
WID_SM_CONTOUR, ///< Button to select the contour view (height map).
WID_SM_VEHICLES, ///< Button to select the vehicles view.
WID_SM_INDUSTRIES, ///< Button to select the industries view.
+ WID_SM_ROUTE_LINKS, ///< Button to select the route link view.
WID_SM_ROUTES, ///< Button to select the routes view.
WID_SM_VEGETATION, ///< Button to select the vegetation view.
WID_SM_OWNERS, ///< Button to select the owners view.
diff --git a/src/widgets/station_widget.h b/src/widgets/station_widget.h
index e33565ea1..76ad864d9 100644
--- a/src/widgets/station_widget.h
+++ b/src/widgets/station_widget.h
@@ -20,12 +20,14 @@ enum StationViewWidgets {
WID_SV_ACCEPT_RATING_LIST, ///< List of accepted cargoes / rating of cargoes.
WID_SV_LOCATION, ///< 'Location' button.
WID_SV_ACCEPTS_RATINGS, ///< 'Accepts' / 'Ratings' button.
+ WID_SV_CARGO_FROM_TO_VIA, ///< 'Source' button
WID_SV_RENAME, ///< 'Rename' button.
WID_SV_CLOSE_AIRPORT, ///< 'Close airport' button.
WID_SV_TRAINS, ///< List of scheduled trains button.
WID_SV_ROADVEHS, ///< List of scheduled road vehs button.
WID_SV_SHIPS, ///< List of scheduled ships button.
WID_SV_PLANES, ///< List of scheduled planes button.
+ WID_SV_DEPARTURES, ///< Departures button.
};
/** Widgets of the #CompanyStationsWindow class. */
diff --git a/src/widgets/timetable_widget.h b/src/widgets/timetable_widget.h
index 09beb6167..cce1533c4 100644
--- a/src/widgets/timetable_widget.h
+++ b/src/widgets/timetable_widget.h
@@ -31,6 +31,9 @@ enum VehicleTimetableWidgets {
WID_VT_EXPECTED_SELECTION, ///< Disable/hide the expected selection button.
WID_VT_CHANGE_SPEED, ///< Change speed limit button.
WID_VT_CLEAR_SPEED, ///< Clear speed limit button.
+ WID_VT_TTSEP_MODE_DROPDOWN, ///< Select separation mode dropdown
+ WID_VT_TTSEP_SET_PARAMETER, ///< Set the separation parameter (time / number)
+ WID_VT_TTSEP_PANEL_TEXT ///< Panel area for separation info text
};
#endif /* WIDGETS_TIMETABLE_WIDGET_H */
diff --git a/src/widgets/underground_widget.h b/src/widgets/underground_widget.h
new file mode 100644
index 000000000..522c9db8b
--- /dev/null
+++ b/src/widgets/underground_widget.h
@@ -0,0 +1,21 @@
+/* $Id: terraform_widget.h 23600 2011-12-19 20:46:17Z truebrain $ */
+
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file terraform_widget.h Types related to the terraform widgets. */
+
+#ifndef WIDGETS_UNDERGROUND_WIDGET_H
+#define WIDGETS_UNDERGROUND_WIDGET_H
+
+/** Widgets of the #TerraformToolbarWindow class. */
+enum UndergroundToolbarWidgets {
+ WID_UT_BUILD_ESCALATOR, ///< Build escalator
+ WID_UT_DEMOLISH, ///< Demolish aka dynamite button.
+};
+
+#endif /* WIDGETS_UNDERGROUND_WIDGET_H */
diff --git a/src/widgets/waypoint_widget.h b/src/widgets/waypoint_widget.h
index 8fceddaa7..e73f3af6d 100644
--- a/src/widgets/waypoint_widget.h
+++ b/src/widgets/waypoint_widget.h
@@ -19,6 +19,7 @@ enum WaypointWidgets {
WID_W_CENTER_VIEW, ///< Center the main view on this waypoint.
WID_W_RENAME, ///< Rename this waypoint.
WID_W_SHOW_VEHICLES, ///< Show the vehicles visiting this waypoint.
+ WID_W_DEPARTURES, ///< Departures button.
};
#endif /* WIDGETS_WAYPOINT_WIDGET_H */
diff --git a/src/window.cpp b/src/window.cpp
index f818471bd..800906f60 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -2528,7 +2528,7 @@ static void MouseLoop(MouseClick click, int mousewheel)
case MC_DOUBLE_LEFT:
case MC_LEFT:
DEBUG(misc, 2, "Cursor: 0x%X (%d)", _cursor.sprite, _cursor.sprite);
- if (!HandleViewportClicked(vp, x, y) &&
+ if (!HandleViewportClicked(vp, x, y, click == MC_DOUBLE_LEFT) &&
!(w->flags & WF_DISABLE_VP_SCROLL) &&
_settings_client.gui.left_mouse_btn_scrolling) {
_scrolling_viewport = true;
diff --git a/src/window_type.h b/src/window_type.h
index c69e44cd0..150ce7dfb 100644
--- a/src/window_type.h
+++ b/src/window_type.h
@@ -439,6 +439,12 @@ enum WindowClass {
WC_SCEN_LAND_GEN,
/**
+ * Underground (in game); %Window numbers:
+ * - 0 = #UndergroundToolbarWidgets
+ */
+ WC_UNDERGROUND,
+
+ /**
* Generate landscape (newgame); %Window numbers:
* - GLWM_SCENARIO = #CreateScenarioWidgets
* - #GenenerateLandscapeWindowMode = #GenerateLandscapeWidgets
@@ -664,6 +670,11 @@ enum WindowClass {
*/
WC_SPRITE_ALIGNER,
+ /**
+ * Departure boards
+ */
+ WC_DEPARTURES_BOARD,
+
WC_INVALID = 0xFFFF, ///< Invalid window.
};