Index: bin/data/innerhighlight.grf
new file mode 100644
===================================================================
Binary files /dev/null and bin/data/innerhighlight.grf differ
Index: bin/data/progsignals.grf
new file mode 100644
===================================================================
Binary files /dev/null and bin/data/progsignals.grf differ
Index: bin/data/route_step.grf
new file mode 100644
===================================================================
Binary files /dev/null and bin/data/route_step.grf differ
Index: findversion.sh
===================================================================
--- findversion.sh (revision 27386)
+++ findversion.sh (working copy)
@@ -99,7 +99,7 @@
# No rev? Maybe it is a custom git-svn clone
REV_NR=`LC_ALL=C git log --pretty=format:%b --grep="git-svn-id:.*@[0-9]*" -1 | sed "s@.*\@\([0-9]*\).*@\1@"`
fi
- TAG="`git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null | sed 's@\^0$@@'`"
+ TAG="`git describe --tags 2>/dev/null`"
if [ -n "$TAG" ]; then
BRANCH=""
REV="$TAG"
Index: projects/openttd_vs100.vcxproj
===================================================================
--- projects/openttd_vs100.vcxproj (revision 27386)
+++ projects/openttd_vs100.vcxproj (working copy)
@@ -331,6 +331,7 @@
+
@@ -357,6 +358,7 @@
+
@@ -366,6 +368,8 @@
+
+
@@ -481,6 +485,7 @@
+
@@ -557,6 +562,9 @@
+
+
+
@@ -582,11 +590,13 @@
+
+
@@ -660,6 +670,7 @@
+
@@ -723,6 +734,7 @@
+
@@ -745,6 +757,7 @@
+
@@ -784,6 +797,7 @@
+
@@ -812,6 +826,7 @@
+
@@ -828,6 +843,7 @@
+
@@ -856,6 +872,7 @@
+
@@ -869,6 +886,9 @@
+
+
+
@@ -880,6 +900,7 @@
+
@@ -1271,6 +1292,10 @@
+
+
+
+
Index: projects/openttd_vs100.vcxproj.filters
===================================================================
--- projects/openttd_vs100.vcxproj.filters (revision 27386)
+++ projects/openttd_vs100.vcxproj.filters (working copy)
@@ -222,6 +222,9 @@
Source Files
+
+ Source Files
+
Source Files
@@ -300,6 +303,9 @@
Source Files
+
+ Source Files
+
Source Files
@@ -327,6 +333,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -672,6 +684,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -900,6 +915,15 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
Header Files
@@ -975,6 +999,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -990,6 +1017,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -1209,6 +1239,9 @@
Header Files
+
+ Header Files
+
Core Source Code
@@ -1398,6 +1431,9 @@
GUI Source Code
+
+ GUI Source Code
+
GUI Source Code
@@ -1464,6 +1500,9 @@
GUI Source Code
+
+ GUI Source Code
+
Widgets
@@ -1581,6 +1620,9 @@
Widgets
+
+ Widgets
+
Widgets
@@ -1665,6 +1707,9 @@
Command handlers
+
+ Command handlers
+
Command handlers
@@ -1713,6 +1758,9 @@
Command handlers
+
+ Command handlers
+
Save/Load handlers
@@ -1797,6 +1845,9 @@
Save/Load handlers
+
+ Save/Load handlers
+
Save/Load handlers
@@ -1836,6 +1887,15 @@
Save/Load handlers
+
+ Save/Load handlers
+
+
+ Save/Load handlers
+
+
+ Save/Load handlers
+
Tables
@@ -1869,6 +1929,9 @@
Tables
+
+ Tables
+
Tables
@@ -3042,6 +3105,18 @@
Threading
+
+ Threading
+
+
+ Threading
+
+
+ Threading
+
+
+ Threading
+
Index: projects/openttd_vs80.vcproj
===================================================================
--- projects/openttd_vs80.vcproj (revision 27386)
+++ projects/openttd_vs80.vcproj (working copy)
@@ -511,6 +511,10 @@
>
+
+
@@ -595,6 +599,10 @@
>
+
+
@@ -699,6 +707,10 @@
>
+
+
@@ -735,6 +747,14 @@
>
+
+
+
+
@@ -1003,6 +1023,18 @@
>
+
+
+
+
+
+
@@ -1199,6 +1231,10 @@
>
+
+
@@ -1503,6 +1539,18 @@
>
+
+
+
+
+
+
@@ -1603,6 +1651,10 @@
>
+
+
@@ -1623,6 +1675,10 @@
>
+
+
@@ -1914,6 +1970,10 @@
RelativePath=".\..\src\zoom_type.h"
>
+
+
+
+
@@ -2175,6 +2239,10 @@
>
+
+
@@ -2262,6 +2330,10 @@
RelativePath=".\..\src\waypoint_gui.cpp"
>
+
+
+
+
@@ -2539,6 +2615,10 @@
>
+
+
@@ -2602,6 +2682,10 @@
RelativePath=".\..\src\waypoint_cmd.cpp"
>
+
+
+
+
@@ -2770,6 +2858,18 @@
RelativePath=".\..\src\saveload\waypoint_sl.cpp"
>
+
+
+
+
+
+
+
+
@@ -4482,6 +4586,22 @@
RelativePath=".\..\src\thread\thread_win32.cpp"
>
+
+
+
+
+
+
+
+
+
+
@@ -592,6 +596,10 @@
>
+
+
@@ -696,6 +704,10 @@
>
+
+
@@ -732,6 +744,14 @@
>
+
+
+
+
@@ -1000,6 +1020,18 @@
>
+
+
+
+
+
+
@@ -1196,6 +1228,10 @@
>
+
+
@@ -1500,6 +1536,18 @@
>
+
+
+
+
+
+
@@ -1600,6 +1648,10 @@
>
+
+
@@ -1620,6 +1672,10 @@
>
+
+
@@ -1911,6 +1967,10 @@
RelativePath=".\..\src\zoom_type.h"
>
+
+
+
+
@@ -2172,6 +2236,10 @@
>
+
+
@@ -2259,6 +2327,10 @@
RelativePath=".\..\src\waypoint_gui.cpp"
>
+
+
+
+
@@ -2536,6 +2612,10 @@
>
+
+
@@ -2599,6 +2679,10 @@
RelativePath=".\..\src\waypoint_cmd.cpp"
>
+
+
+
+
@@ -2767,6 +2855,18 @@
RelativePath=".\..\src\saveload\waypoint_sl.cpp"
>
+
+
+
+
+
+
+
+
@@ -4479,6 +4583,22 @@
RelativePath=".\..\src\thread\thread_win32.cpp"
>
+
+
+
+
+
+
+
+
Index: src/aircraft_cmd.cpp
===================================================================
--- src/aircraft_cmd.cpp (revision 27386)
+++ src/aircraft_cmd.cpp (working copy)
@@ -35,6 +35,7 @@
#include "engine_base.h"
#include "core/random_func.hpp"
#include "core/backup_type.hpp"
+#include "infrastructure_func.h"
#include "zoom_func.h"
#include "disaster_vehicle.h"
@@ -126,10 +127,15 @@
const AircraftVehicleInfo *avi = AircraftVehInfo(v->engine_type);
FOR_ALL_STATIONS(st) {
- if (st->owner != v->owner || !(st->facilities & FACIL_AIRPORT)) continue;
+ if (!IsInfraUsageAllowed(VEH_AIRCRAFT, v->owner, st->owner) || !(st->facilities & FACIL_AIRPORT)) continue;
const AirportFTAClass *afc = st->airport.GetFTA();
if (!st->airport.HasHangar() || (
+ /* the airport needs to have facilities for this plane type */
+ (AircraftVehInfo(v->engine_type)->subtype & AIR_CTOL) ?
+ !(afc->flags & AirportFTAClass::AIRPLANES) :
+ !(afc->flags & AirportFTAClass::HELICOPTERS)
+ ) || (
/* don't crash the plane if we know it can't land at the airport */
(afc->flags & AirportFTAClass::SHORT_STRIP) &&
(avi->subtype & AIR_FAST) &&
@@ -301,6 +307,9 @@
v->reliability = e->reliability;
v->reliability_spd_dec = e->reliability_spd_dec;
+ /* higher speed means higher breakdown chance */
+ /* to somewhat compensate for the fact that fast aircraft spend less time in the air */
+ v->breakdown_chance = Clamp(64 + (AircraftVehInfo(v->engine_type)->max_speed >> 3), 0, 255);
v->max_age = e->GetLifeLengthInDays();
_new_vehicle_id = v->index;
@@ -642,7 +651,7 @@
spd = min(v->cur_speed + (spd >> 8) + (v->subspeed < t), speed_limit);
/* adjust speed for broken vehicles */
- if (v->vehstatus & VS_AIRCRAFT_BROKEN) spd = min(spd, SPEED_LIMIT_BROKEN);
+ if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED) spd = min(v->breakdown_severity << 3, spd);
/* updates statusbar only if speed have changed to save CPU time */
if (spd != v->cur_speed) {
@@ -1095,6 +1104,39 @@
}
/**
+ * Send a broken plane that needs to visit a depot to the correct location.
+ * @param v The airplane in question
+ */
+void FindBreakdownDestination(Aircraft *v)
+{
+ assert(v->type == VEH_AIRCRAFT && v->breakdown_ctr == 1);
+
+ DestinationID destination = INVALID_STATION;
+ if (v->breakdown_type == BREAKDOWN_AIRCRAFT_DEPOT) {
+ /* Go to a hangar, if possible at our current destination */
+ v->FindClosestDepot(NULL, &destination, NULL);
+ } else if (v->breakdown_type == BREAKDOWN_AIRCRAFT_EM_LANDING) {
+ /* Go to the nearest airport with a hangar */
+ destination = FindNearestHangar(v);
+ } else {
+ NOT_REACHED();
+ }
+
+ if(destination != INVALID_STATION) {
+ if(destination != v->current_order.GetDestination()) {
+ v->current_order.MakeGoToDepot(destination, ODTFB_BREAKDOWN);
+ AircraftNextAirportPos_and_Order(v);
+ } else {
+ v->current_order.MakeGoToDepot(destination, ODTFB_BREAKDOWN);
+ }
+ } else {
+ /* If no hangar was found, crash */
+ v->targetairport = INVALID_STATION;
+ CrashAirplane(v);
+ }
+}
+
+/**
* Handle crashed aircraft \a v.
* @param v Crashed aircraft.
*/
@@ -1174,8 +1216,9 @@
if (!(v->vehstatus & VS_AIRCRAFT_BROKEN)) return;
+ /* breakdown-related speed limits are lifted when we are on the ground */
/* Stop smoking when landed */
- if (v->cur_speed < 10) {
+ if (v->state != FLYING && v->state != LANDING && v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED) {
v->vehstatus &= ~VS_AIRCRAFT_BROKEN;
v->breakdown_ctr = 0;
return;
@@ -1291,13 +1334,18 @@
Station *st = Station::Get(v->targetairport);
/* FIXME -- MaybeCrashAirplane -> increase crashing chances of very modern airplanes on smaller than AT_METROPOLITAN airports */
- uint32 prob = (0x4000 << _settings_game.vehicle.plane_crashes);
+ uint32 prob = (_settings_game.vehicle.improved_breakdowns && _settings_game.difficulty.vehicle_breakdowns) ?
+ 0x10000 / 10000 : 0x4000 << _settings_game.vehicle.plane_crashes;
+
if ((st->airport.GetFTA()->flags & AirportFTAClass::SHORT_STRIP) &&
(AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) &&
!_cheats.no_jetcrash.value) {
prob /= 20;
- } else {
+ } else if (!_settings_game.vehicle.improved_breakdowns) {
prob /= 1500;
+ } else if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_AIRCRAFT_EM_LANDING) {
+ /* Airplanes that are attempting an emergency landing have a 2% chance to crash */
+ prob = 0x10000 / 50;
}
if (GB(Random(), 0, 22) > prob) return;
@@ -1434,6 +1482,13 @@
return;
}
+ if (v->current_order.IsWaitTimetabled()) {
+ v->HandleWaiting(false);
+ }
+ if (v->current_order.IsType(OT_WAITING)) {
+ return;
+ }
+
/* if we were sent to the depot, stay there */
if (v->current_order.IsType(OT_GOTO_DEPOT) && (v->vehstatus & VS_STOPPED)) {
v->current_order.Free();
@@ -1570,7 +1625,7 @@
Station *st = Station::Get(v->targetairport);
/* Runway busy, not allowed to use this airstation or closed, circle. */
- if (CanVehicleUseStation(v, st) && (st->owner == OWNER_NONE || st->owner == v->owner) && !(st->airport.flags & AIRPORT_CLOSED_block)) {
+ if (CanVehicleUseStation(v, st) && IsInfraUsageAllowed(VEH_AIRCRAFT, v->owner, st->owner) && !(st->airport.flags & AIRPORT_CLOSED_block)) {
/* {32,FLYING,NOTHING_block,37}, {32,LANDING,N,33}, {32,HELILANDING,N,41},
* if it is an airplane, look for LANDING, for helicopter HELILANDING
* it is possible to choose from multiple landing runways, so loop until a free one is found */
Index: src/airport_gui.cpp
===================================================================
--- src/airport_gui.cpp (revision 27386)
+++ src/airport_gui.cpp (working copy)
@@ -62,7 +62,7 @@
uint32 p1 = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex();
p1 |= _selected_airport_layout << 8;
- CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_AIRPORT | CMD_MSG(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE), CcBuildAirport, "" };
+ CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_AIRPORT | CMD_MSG(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE), CcBuildAirport, 0, "" };
ShowSelectStationIfNeeded(cmdcont, TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE));
}
Index: src/animated_tile.cpp
===================================================================
--- src/animated_tile.cpp (revision 27386)
+++ src/animated_tile.cpp (working copy)
@@ -37,7 +37,7 @@
*/
memmove(ti, ti + 1, (_animated_tile_list + _animated_tile_count - (ti + 1)) * sizeof(*ti));
_animated_tile_count--;
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
return;
}
}
@@ -50,7 +50,7 @@
*/
void AddAnimatedTile(TileIndex tile)
{
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
for (const TileIndex *ti = _animated_tile_list; ti < _animated_tile_list + _animated_tile_count; ti++) {
if (tile == *ti) return;
Index: src/base_consist.h
===================================================================
--- src/base_consist.h (revision 27386)
+++ src/base_consist.h (working copy)
@@ -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 @@
/* 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 %.
Index: src/blitter/32bpp_anim.cpp
===================================================================
--- src/blitter/32bpp_anim.cpp (revision 27386)
+++ src/blitter/32bpp_anim.cpp (working copy)
@@ -317,6 +317,50 @@
this->anim_buf[((uint32 *)video - (uint32 *)_screen.dst_ptr) + x + y * this->anim_buf_width] = colour | (DEFAULT_BRIGHTNESS << 8);
}
+void Blitter_32bppAnim::SetLine(void *video, int x, int y, uint8 *colours, uint width)
+{
+ Colour *dst = (Colour *)video + x + y * _screen.pitch;
+
+ if (_screen_disable_anim) {
+ do {
+ *dst = LookupColourInPalette(*colours);
+ dst++;
+ colours++;
+ } while (--width);
+ } else {
+ uint16 *dstanim = (uint16 *)(&this->anim_buf[(uint32 *)video - (uint32 *)_screen.dst_ptr + x + y * _screen.pitch]);
+ do {
+ *dstanim = *colours | (DEFAULT_BRIGHTNESS << 8);
+ *dst = LookupColourInPalette(*colours);
+ dst++;
+ dstanim++;
+ colours++;
+ } while (--width);
+ }
+}
+
+void Blitter_32bppAnim::SetLine32(void *video, int x, int y, uint32 *colours, uint width)
+{
+ Colour *dst = (Colour *)video + x + y * _screen.pitch;
+
+ if (_screen_disable_anim) {
+ do {
+ *dst = *colours;
+ dst++;
+ colours++;
+ } while (--width);
+ } else {
+ uint16 *dstanim = (uint16 *)(&this->anim_buf[(uint32 *)video - (uint32 *)_screen.dst_ptr + x + y * _screen.pitch]);
+ do {
+ *dstanim = 0;
+ *dst = *colours;
+ dst++;
+ dstanim++;
+ colours++;
+ } while (--width);
+ }
+}
+
void Blitter_32bppAnim::DrawRect(void *video, int width, int height, uint8 colour)
{
if (_screen_disable_anim) {
Index: src/blitter/32bpp_anim.hpp
===================================================================
--- src/blitter/32bpp_anim.hpp (revision 27386)
+++ src/blitter/32bpp_anim.hpp (working copy)
@@ -32,6 +32,8 @@
/* virtual */ void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom);
/* virtual */ void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal);
/* virtual */ void SetPixel(void *video, int x, int y, uint8 colour);
+ /* virtual */ void SetLine(void *video, int x, int y, uint8 *colours, uint width);
+ /* virtual */ void SetLine32(void *video, int x, int y, uint32 *colours, uint width);
/* virtual */ void DrawRect(void *video, int width, int height, uint8 colour);
/* virtual */ void CopyFromBuffer(void *video, const void *src, int width, int height);
/* virtual */ void CopyToBuffer(const void *video, void *dst, int width, int height);
Index: src/blitter/32bpp_base.cpp
===================================================================
--- src/blitter/32bpp_base.cpp (revision 27386)
+++ src/blitter/32bpp_base.cpp (working copy)
@@ -24,6 +24,26 @@
*((Colour *)video + x + y * _screen.pitch) = LookupColourInPalette(colour);
}
+void Blitter_32bppBase::SetLine(void *video, int x, int y, uint8 *colours, uint width)
+{
+ Colour *dst = (Colour *)video + x + y * _screen.pitch;
+ do {
+ *dst = LookupColourInPalette(*colours);
+ dst++;
+ colours++;
+ } while (--width);
+}
+
+void Blitter_32bppBase::SetLine32(void *video, int x, int y, uint32 *colours, uint width)
+{
+ Colour *dst = (Colour *)video + x + y * _screen.pitch;
+ do {
+ *dst = *colours;
+ dst++;
+ colours++;
+ } while (--width);
+}
+
void Blitter_32bppBase::DrawRect(void *video, int width, int height, uint8 colour)
{
Colour colour32 = LookupColourInPalette(colour);
Index: src/blitter/32bpp_base.hpp
===================================================================
--- src/blitter/32bpp_base.hpp (revision 27386)
+++ src/blitter/32bpp_base.hpp (working copy)
@@ -23,6 +23,8 @@
/* virtual */ uint8 GetScreenDepth() { return 32; }
/* virtual */ void *MoveTo(void *video, int x, int y);
/* virtual */ void SetPixel(void *video, int x, int y, uint8 colour);
+ /* virtual */ void SetLine(void *video, int x, int y, uint8 *colours, uint width);
+ /* virtual */ void SetLine32(void *video, int x, int y, uint32 *colours, uint width);
/* virtual */ void DrawRect(void *video, int width, int height, uint8 colour);
/* virtual */ void CopyFromBuffer(void *video, const void *src, int width, int height);
/* virtual */ void CopyToBuffer(const void *video, void *dst, int width, int height);
Index: src/blitter/32bpp_optimized.cpp
===================================================================
--- src/blitter/32bpp_optimized.cpp (revision 27386)
+++ src/blitter/32bpp_optimized.cpp (working copy)
@@ -280,8 +280,8 @@
zoom_max = ZOOM_LVL_NORMAL;
} else {
zoom_min = _settings_client.gui.zoom_min;
- zoom_max = _settings_client.gui.zoom_max;
- if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX;
+ zoom_max = (ZoomLevel) min(_settings_client.gui.zoom_max, ZOOM_LVL_DRAW_SPR);
+ if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_DRAW_SPR;
}
for (ZoomLevel z = zoom_min; z <= zoom_max; z++) {
Index: src/blitter/8bpp_base.cpp
===================================================================
--- src/blitter/8bpp_base.cpp (revision 27386)
+++ src/blitter/8bpp_base.cpp (working copy)
@@ -35,6 +35,11 @@
*((uint8 *)video + x + y * _screen.pitch) = colour;
}
+void Blitter_8bppBase::SetLine(void *video, int x, int y, uint8 *colours, uint width)
+{
+ memcpy((uint8 *)video + x + y * _screen.pitch, colours, width * sizeof(uint8));
+}
+
void Blitter_8bppBase::DrawRect(void *video, int width, int height, uint8 colour)
{
do {
Index: src/blitter/8bpp_base.hpp
===================================================================
--- src/blitter/8bpp_base.hpp (revision 27386)
+++ src/blitter/8bpp_base.hpp (working copy)
@@ -21,6 +21,7 @@
/* virtual */ void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal);
/* virtual */ void *MoveTo(void *video, int x, int y);
/* virtual */ void SetPixel(void *video, int x, int y, uint8 colour);
+ /* virtual */ void SetLine(void *video, int x, int y, uint8 *colours, uint width);
/* virtual */ void DrawRect(void *video, int width, int height, uint8 colour);
/* virtual */ void CopyFromBuffer(void *video, const void *src, int width, int height);
/* virtual */ void CopyToBuffer(const void *video, void *dst, int width, int height);
Index: src/blitter/8bpp_optimized.cpp
===================================================================
--- src/blitter/8bpp_optimized.cpp (revision 27386)
+++ src/blitter/8bpp_optimized.cpp (working copy)
@@ -134,8 +134,8 @@
zoom_max = ZOOM_LVL_NORMAL;
} else {
zoom_min = _settings_client.gui.zoom_min;
- zoom_max = _settings_client.gui.zoom_max;
- if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX;
+ zoom_max = (ZoomLevel) min(_settings_client.gui.zoom_max, ZOOM_LVL_DRAW_SPR);
+ if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_DRAW_SPR;
}
for (ZoomLevel i = zoom_min; i <= zoom_max; i++) {
Index: src/blitter/base.hpp
===================================================================
--- src/blitter/base.hpp (revision 27386)
+++ src/blitter/base.hpp (working copy)
@@ -101,6 +101,26 @@
virtual void SetPixel(void *video, int x, int y, uint8 colour) = 0;
/**
+ * Draw a sequence of pixels on the video-buffer.
+ * @param video The destination pointer (video-buffer).
+ * @param x The x position within video-buffer.
+ * @param y The y position within video-buffer.
+ * @param colours A 8bpp colour mapping buffer.
+ * @param width The length of the line.
+ */
+ virtual void SetLine(void *video, int x, int y, uint8 *colours, uint width) = 0;
+
+ /**
+ * Draw a sequence of pixels on the video-buffer (no LookupColourInPalette).
+ * @param video The destination pointer (video-buffer).
+ * @param x The x position within video-buffer.
+ * @param y The y position within video-buffer.
+ * @param colours A 32bpp colour buffer.
+ * @param width The length of the line.
+ */
+ virtual void SetLine32(void *video, int x, int y, uint32 *colours, uint width) { NOT_REACHED(); };
+
+ /**
* Make a single horizontal line in a single colour on the video-buffer.
* @param video The destination pointer (video-buffer).
* @param width The length of the line.
Index: src/blitter/null.hpp
===================================================================
--- src/blitter/null.hpp (revision 27386)
+++ src/blitter/null.hpp (working copy)
@@ -23,6 +23,7 @@
/* virtual */ Sprite *Encode(const SpriteLoader::Sprite *sprite, AllocatorProc *allocator);
/* virtual */ void *MoveTo(void *video, int x, int y) { return NULL; };
/* virtual */ void SetPixel(void *video, int x, int y, uint8 colour) {};
+ /* virtual */ void SetLine(void *video, int x, int y, uint8 *colours, uint width) {};
/* virtual */ void DrawRect(void *video, int width, int height, uint8 colour) {};
/* virtual */ void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8 colour, int width, int dash) {};
/* virtual */ void CopyFromBuffer(void *video, const void *src, int width, int height) {};
Index: src/clear_cmd.cpp
===================================================================
--- src/clear_cmd.cpp (revision 27386)
+++ src/clear_cmd.cpp (working copy)
@@ -46,20 +46,45 @@
return price;
}
+SpriteID GetSpriteIDForClearLand(const Slope slope, byte set)
+{
+ return SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(slope) + set * 19;
+}
+
void DrawClearLandTile(const TileInfo *ti, byte set)
{
- DrawGroundSprite(SPR_FLAT_BARE_LAND + SlopeToSpriteOffset(ti->tileh) + set * 19, PAL_NONE);
+ DrawGroundSprite(GetSpriteIDForClearLand(ti->tileh, set), PAL_NONE);
}
-void DrawHillyLandTile(const TileInfo *ti)
+SpriteID GetSpriteIDForHillyLand(const Slope slope, const uint rough_index)
{
- if (ti->tileh != SLOPE_FLAT) {
- DrawGroundSprite(SPR_FLAT_ROUGH_LAND + SlopeToSpriteOffset(ti->tileh), PAL_NONE);
+ if (slope != SLOPE_FLAT) {
+ return SPR_FLAT_ROUGH_LAND + SlopeToSpriteOffset(slope);
} else {
- DrawGroundSprite(_landscape_clear_sprites_rough[GB(ti->x ^ ti->y, 4, 3)], PAL_NONE);
+ return _landscape_clear_sprites_rough[rough_index];
}
}
+void DrawHillyLandTile(const TileInfo *ti)
+{
+ DrawGroundSprite(GetSpriteIDForHillyLand(ti->tileh, GB(ti->x ^ ti->y, 4, 3)), PAL_NONE);
+}
+
+SpriteID GetSpriteIDForRocks(const Slope slope, const uint tile_hash)
+{
+ return ((HasGrfMiscBit(GMB_SECOND_ROCKY_TILE_SET) && (tile_hash & 1)) ? SPR_FLAT_ROCKY_LAND_2 : SPR_FLAT_ROCKY_LAND_1) + SlopeToSpriteOffset(slope);
+}
+
+SpriteID GetSpriteIDForFields(const Slope slope, const uint field_type)
+{
+ return _clear_land_sprites_farmland[field_type] + SlopeToSpriteOffset(slope);
+}
+
+SpriteID GetSpriteIDForSnowDesert(const Slope slope, const uint density)
+{
+ return _clear_land_sprites_snow_desert[density] + SlopeToSpriteOffset(slope);
+}
+
static void DrawClearLandFence(const TileInfo *ti)
{
/* combine fences into one sprite object */
@@ -112,17 +137,17 @@
break;
case CLEAR_ROCKS:
- DrawGroundSprite((HasGrfMiscBit(GMB_SECOND_ROCKY_TILE_SET) && (TileHash(ti->x, ti->y) & 1) ? SPR_FLAT_ROCKY_LAND_2 : SPR_FLAT_ROCKY_LAND_1) + SlopeToSpriteOffset(ti->tileh), PAL_NONE);
+ DrawGroundSprite(GetSpriteIDForRocks(ti->tileh, TileHash(ti->x, ti->y)), PAL_NONE);
break;
case CLEAR_FIELDS:
- DrawGroundSprite(_clear_land_sprites_farmland[GetFieldType(ti->tile)] + SlopeToSpriteOffset(ti->tileh), PAL_NONE);
+ DrawGroundSprite(GetSpriteIDForFields(ti->tileh, GetFieldType(ti->tile)), PAL_NONE);
DrawClearLandFence(ti);
break;
case CLEAR_SNOW:
case CLEAR_DESERT:
- DrawGroundSprite(_clear_land_sprites_snow_desert[GetClearDensity(ti->tile)] + SlopeToSpriteOffset(ti->tileh), PAL_NONE);
+ DrawGroundSprite(GetSpriteIDForSnowDesert(ti->tileh, GetClearDensity(ti->tile)), PAL_NONE);
break;
}
@@ -171,7 +196,7 @@
dirty = true;
}
- if (dirty) MarkTileDirtyByTile(tile);
+ if (dirty) MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
@@ -253,7 +278,6 @@
int z;
if (IsTileFlat(tile, &z) && z == 0) {
DoFloodTile(tile);
- MarkTileDirtyByTile(tile);
return;
}
}
@@ -307,7 +331,7 @@
return;
}
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
void GenerateClearTile()
Index: src/clear_func.h
===================================================================
--- src/clear_func.h (revision 27386)
+++ src/clear_func.h (working copy)
@@ -17,4 +17,10 @@
void DrawHillyLandTile(const TileInfo *ti);
void DrawClearLandTile(const TileInfo *ti, byte set);
+SpriteID GetSpriteIDForClearLand(const Slope slope, byte set);
+SpriteID GetSpriteIDForHillyLand(const Slope slope, const uint rough_index);
+SpriteID GetSpriteIDForRocks(const Slope slope, const uint tile_hash);
+SpriteID GetSpriteIDForFields(const Slope slope, const uint field_type);
+SpriteID GetSpriteIDForSnowDesert(const Slope slope, const uint density);
+
#endif /* CLEAR_FUNC_H */
Index: src/command.cpp
===================================================================
--- src/command.cpp (revision 27386)
+++ src/command.cpp (working copy)
@@ -50,6 +50,7 @@
CommandProc CmdTerraformLand;
CommandProc CmdBuildObject;
+CommandProc CmdBuildHouse;
CommandProc CmdSellLandArea;
CommandProc CmdBuildTunnel;
@@ -194,10 +195,23 @@
CommandProc CmdChangeTimetable;
CommandProc CmdSetVehicleOnTime;
CommandProc CmdAutofillTimetable;
+CommandProc CmdAutomateTimetable;
CommandProc CmdSetTimetableStart;
CommandProc CmdOpenCloseAirport;
+CommandProc CmdProgramSignalTraceRestrict;
+
+CommandProc CmdInsertSignalInstruction;
+CommandProc CmdModifySignalInstruction;
+CommandProc CmdRemoveSignalInstruction;
+
+CommandProc CmdAddPlan;
+CommandProc CmdAddPlanLine;
+CommandProc CmdRemovePlan;
+CommandProc CmdRemovePlanLine;
+CommandProc CmdChangePlanVisibility;
+
#define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type}
/**
@@ -220,6 +234,7 @@
DEF_CMD(CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNALS
DEF_CMD(CmdTerraformLand, CMD_ALL_TILES | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_TERRAFORM_LAND
DEF_CMD(CmdBuildObject, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_OBJECT
+ DEF_CMD(CmdBuildHouse, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_HOUSE
DEF_CMD(CmdBuildTunnel, CMD_DEITY | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TUNNEL
DEF_CMD(CmdRemoveFromRailStation, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_STATION
DEF_CMD(CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CONVERT_RAILD
@@ -351,9 +366,22 @@
DEF_CMD(CmdChangeTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_CHANGE_TIMETABLE
DEF_CMD(CmdSetVehicleOnTime, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_VEHICLE_ON_TIME
DEF_CMD(CmdAutofillTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_AUTOFILL_TIMETABLE
+ DEF_CMD(CmdAutomateTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_AUTOMATE_TIMETABLE]
DEF_CMD(CmdSetTimetableStart, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_TIMETABLE_START
DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT
+
+ DEF_CMD(CmdProgramSignalTraceRestrict, 0, CMDT_OTHER_MANAGEMENT ), // CMD_PROGRAM_TRACERESTRICT_SIGNAL
+
+ DEF_CMD(CmdInsertSignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_INSERT_SIGNAL_INSTRUCTION
+ DEF_CMD(CmdModifySignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_MODIFY_SIGNAL_INSTRUCTION
+ DEF_CMD(CmdRemoveSignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNAL_INSTRUCTION
+
+ DEF_CMD(CmdAddPlan, 0, CMDT_OTHER_MANAGEMENT ), // CMD_ADD_PLAN
+ DEF_CMD(CmdAddPlanLine, 0, CMDT_OTHER_MANAGEMENT ), // CMD_ADD_PLAN_LINE
+ DEF_CMD(CmdRemovePlan, 0, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_PLAN
+ DEF_CMD(CmdRemovePlanLine, 0, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_PLAN_LINE
+ DEF_CMD(CmdChangePlanVisibility, 0, CMDT_OTHER_MANAGEMENT ), // CMD_CHANGE_PLAN_VISIBILITY
};
/*!
@@ -542,9 +570,10 @@
* @param callback A callback function to call after the command is finished
* @param text The text to pass
* @param my_cmd indicator if the command is from a company or server (to display error messages for a user)
+ * @param binary_length The quantity of binary data in text
* @return \c true if the command succeeded, else \c false.
*/
-bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd)
+bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, uint32 binary_length)
{
/* Cost estimation is generally only done when the
* local user presses shift while doing somthing.
@@ -574,7 +603,7 @@
if (!(cmd & CMD_NETWORK_COMMAND) && GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = CLIENT_ID_SERVER;
#endif
- CommandCost res = DoCommandPInternal(tile, p1, p2, cmd, callback, text, my_cmd, estimate_only);
+ CommandCost res = DoCommandPInternal(tile, p1, p2, cmd, callback, text, my_cmd, estimate_only, binary_length);
if (res.Failed()) {
/* Only show the error when it's for us. */
StringID error_part1 = GB(cmd, 16, 16);
@@ -620,7 +649,7 @@
* @param estimate_only whether to give only the estimate or also execute the command
* @return the command cost of this function.
*/
-CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only)
+CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only, uint32 binary_length)
{
/* Prevent recursion; it gives a mess over the network */
assert(_docommand_recursive == 0);
@@ -699,7 +728,7 @@
* send it to the command-queue and abort execution
*/
if (_networking && !_generating_world && !(cmd & CMD_NETWORK_COMMAND)) {
- NetworkSendCommand(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text, _current_company);
+ NetworkSendCommand(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text, _current_company, binary_length);
cur_company.Restore();
/* Don't return anything special here; no error, no costs.
Index: src/command_func.h
===================================================================
--- src/command_func.h (revision 27386)
+++ src/command_func.h (working copy)
@@ -37,13 +37,13 @@
CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const char *text = NULL);
CommandCost DoCommand(const CommandContainer *container, DoCommandFlag flags);
-bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback = NULL, const char *text = NULL, bool my_cmd = true);
+bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback = NULL, const char *text = NULL, bool my_cmd = true, uint32 binary_length = 0);
bool DoCommandP(const CommandContainer *container, bool my_cmd = true);
-CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only);
+CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only, uint32 binary_length);
#ifdef ENABLE_NETWORK
-void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, CompanyID company);
+void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, CompanyID company, uint32 binary_length);
#endif /* ENABLE_NETWORK */
extern Money _additional_cash_required;
@@ -102,6 +102,9 @@
CommandCallback CcTerraform;
CommandCallback CcGiveMoney;
+/* plans_gui.cpp */
+CommandCallback CcAddPlan;
+
/* rail_gui.cpp */
CommandCallback CcPlaySound1E;
CommandCallback CcRailDepot;
Index: src/command_type.h
===================================================================
--- src/command_type.h (revision 27386)
+++ src/command_type.h (working copy)
@@ -187,6 +187,7 @@
CMD_REMOVE_SIGNALS, ///< remove a signal
CMD_TERRAFORM_LAND, ///< terraform a tile
CMD_BUILD_OBJECT, ///< build an object
+ CMD_BUILD_HOUSE, ///< build a house
CMD_BUILD_TUNNEL, ///< build a tunnel
CMD_REMOVE_FROM_RAIL_STATION, ///< remove a (rectangle of) tiles from a rail station
@@ -325,10 +326,23 @@
CMD_CHANGE_TIMETABLE, ///< change the timetable for a vehicle
CMD_SET_VEHICLE_ON_TIME, ///< set the vehicle on time feature (timetable)
CMD_AUTOFILL_TIMETABLE, ///< autofill the timetable
+ CMD_AUTOMATE_TIMETABLE, ///< automate the timetable
CMD_SET_TIMETABLE_START, ///< set the date that a timetable should start
CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft
+ CMD_PROGRAM_TRACERESTRICT_SIGNAL, ///< modify a signal tracerestrict program
+
+ CMD_INSERT_SIGNAL_INSTRUCTION, ///< insert a signal instruction
+ CMD_MODIFY_SIGNAL_INSTRUCTION, ///< modifies a signal instruction
+ CMD_REMOVE_SIGNAL_INSTRUCTION, ///< removes a signal instruction
+
+ CMD_ADD_PLAN,
+ CMD_ADD_PLAN_LINE,
+ CMD_REMOVE_PLAN,
+ CMD_REMOVE_PLAN_LINE,
+ CMD_CHANGE_PLAN_VISIBILITY,
+
CMD_END, ///< Must ALWAYS be on the end of this list!! (period)
};
@@ -466,6 +480,8 @@
*/
typedef void CommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2);
+#define MAX_CMD_TEXT_LENGTH 32000
+
/**
* Structure for buffering the build command when selecting a station to join.
*/
@@ -475,7 +491,8 @@
uint32 p2; ///< parameter p2.
uint32 cmd; ///< command being executed.
CommandCallback *callback; ///< any callback function executed upon successful completion of the command.
- char text[32 * MAX_CHAR_LENGTH]; ///< possible text sent for name changes etc, in bytes including '\0'.
+ uint32 binary_length; ///< in case text contains binary data, this describes its length.
+ char text[MAX_CMD_TEXT_LENGTH]; ///< possible text sent for name changes etc, in bytes including '\0'.
};
#endif /* COMMAND_TYPE_H */
Index: src/company_cmd.cpp
===================================================================
--- src/company_cmd.cpp (revision 27386)
+++ src/company_cmd.cpp (working copy)
@@ -220,14 +220,16 @@
if (HasBit(1 << EXPENSES_TRAIN_INC |
1 << EXPENSES_ROADVEH_INC |
1 << EXPENSES_AIRCRAFT_INC |
- 1 << EXPENSES_SHIP_INC, cost.GetExpensesType())) {
+ 1 << EXPENSES_SHIP_INC |
+ 1 << EXPENSES_SHARING_INC, cost.GetExpensesType())) {
c->cur_economy.income -= cost.GetCost();
} else if (HasBit(1 << EXPENSES_TRAIN_RUN |
1 << EXPENSES_ROADVEH_RUN |
1 << EXPENSES_AIRCRAFT_RUN |
1 << EXPENSES_SHIP_RUN |
1 << EXPENSES_PROPERTY |
- 1 << EXPENSES_LOAN_INT, cost.GetExpensesType())) {
+ 1 << EXPENSES_LOAN_INT |
+ 1 << EXPENSES_SHARING_COST, cost.GetExpensesType())) {
c->cur_economy.expenses -= cost.GetCost();
}
@@ -576,6 +578,8 @@
AI::BroadcastNewEvent(new ScriptEventCompanyNew(c->index), c->index);
Game::NewEvent(new ScriptEventCompanyNew(c->index));
+ if (!is_ai) UpdateAllTownVirtCoords();
+
return c;
}
Index: src/company_gui.cpp
===================================================================
--- src/company_gui.cpp (revision 27386)
+++ src/company_gui.cpp (working copy)
@@ -63,6 +63,8 @@
EXPENSES_AIRCRAFT_INC,
EXPENSES_SHIP_INC,
EXPENSES_LOAN_INT,
+ EXPENSES_SHARING_INC,
+ EXPENSES_SHARING_COST,
EXPENSES_OTHER,
};
@@ -72,6 +74,7 @@
EXPENSES_ROADVEH_INC,
EXPENSES_AIRCRAFT_INC,
EXPENSES_SHIP_INC,
+ EXPENSES_SHARING_INC,
INVALID_EXPENSES,
EXPENSES_TRAIN_RUN,
EXPENSES_ROADVEH_RUN,
@@ -79,6 +82,7 @@
EXPENSES_SHIP_RUN,
EXPENSES_PROPERTY,
EXPENSES_LOAN_INT,
+ EXPENSES_SHARING_COST,
INVALID_EXPENSES,
EXPENSES_CONSTRUCTION,
EXPENSES_NEW_VEHICLES,
Index: src/console_gui.cpp
===================================================================
--- src/console_gui.cpp (revision 27386)
+++ src/console_gui.cpp (working copy)
@@ -374,7 +374,7 @@
this->Scroll(-wheel);
}
- virtual void OnFocusLost()
+ virtual void OnFocusLost(Window *newly_focused_window)
{
VideoDriver::GetInstance()->EditBoxLostFocus();
}
Index: src/date.cpp
===================================================================
--- src/date.cpp (revision 27386)
+++ src/date.cpp (working copy)
@@ -28,6 +28,7 @@
Date _date; ///< Current date in days (day counter)
DateFract _date_fract; ///< Fractional part of the day.
uint16 _tick_counter; ///< Ever incrementing (and sometimes wrapping) tick counter for setting off various events
+uint8 _tick_skip_counter; ///< Counter for ticks, when only vehicles are moving and nothing else happens
/**
* Set the date.
Index: src/date_func.h
===================================================================
--- src/date_func.h (revision 27386)
+++ src/date_func.h (working copy)
@@ -18,12 +18,15 @@
extern Month _cur_month;
extern Date _date;
extern DateFract _date_fract;
-extern uint16 _tick_counter;
+extern uint16 _tick_counter;
+extern uint8 _tick_skip_counter;
void SetDate(Date date, DateFract fract);
void ConvertDateToYMD(Date date, YearMonthDay *ymd);
Date ConvertYMDToDate(Year year, Month month, Day day);
+#define YMD_TO_DATE(ymd) (ConvertYMDToDate(ymd.year, ymd.month, ymd.day))
+
/**
* Checks whether the given year is a leap year or not.
* @param yr The year to check.
Index: src/date_gui.cpp
===================================================================
--- src/date_gui.cpp (revision 27386)
+++ src/date_gui.cpp (working copy)
@@ -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"
@@ -65,7 +66,7 @@
* 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();
@@ -146,9 +147,8 @@
case WID_SD_YEAR:
ShowDateDropDown(widget);
break;
-
case WID_SD_SET_DATE:
- if (this->callback != NULL) this->callback(this, ConvertYMDToDate(this->date.year, this->date.month, this->date.day));
+ if (this->callback != NULL) this->callback(this, ConvertYMDToDate(this->date.year, this->date.month, this->date.day) * DAY_TICKS);
delete this;
break;
}
@@ -173,6 +173,122 @@
}
};
+struct SetMinutesWindow : SetDateWindow
+{
+ Minutes minutes;
+
+ /** Constructor. */
+ SetMinutesWindow(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->Append() = 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->Append() = 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),
@@ -195,6 +311,26 @@
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 WindowDesc _set_date_desc(
WDP_CENTER, NULL, 0, 0,
@@ -203,6 +339,13 @@
_nested_set_date_widgets, lengthof(_nested_set_date_widgets)
);
+static WindowDesc _set_minutes_desc(
+ WDP_CENTER, NULL, 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
@@ -212,8 +355,13 @@
* @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);
+ }
}
Index: src/date_gui.h
===================================================================
--- src/date_gui.h (revision 27386)
+++ src/date_gui.h (working copy)
@@ -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 */
Index: src/date_type.h
===================================================================
--- src/date_type.h (revision 27386)
+++ src/date_type.h (working copy)
@@ -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 int32 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 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 STATION_LINKGRAPH_TICKS = 504; ///< cycle duration for cleaning dead links
@@ -39,7 +42,6 @@
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
@@ -96,6 +98,21 @@
/** 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()
Index: src/departures.cpp
new file mode 100644
===================================================================
--- src/departures.cpp (revision 0)
+++ src/departures.cpp (working copy)
@@ -0,0 +1,644 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file departures.cpp Scheduled departures from a station. */
+
+#include "stdafx.h"
+#include "debug.h"
+#include "gui.h"
+#include "textbuf_gui.h"
+#include "strings_func.h"
+#include "window_func.h"
+#include "vehicle_func.h"
+#include "string_func.h"
+#include "window_gui.h"
+#include "timetable.h"
+#include "vehiclelist.h"
+#include "company_base.h"
+#include "date_func.h"
+#include "departures_gui.h"
+#include "station_base.h"
+#include "vehicle_gui_base.h"
+#include "vehicle_base.h"
+#include "vehicle_gui.h"
+#include "order_base.h"
+#include "settings_type.h"
+#include "core/smallvec_type.hpp"
+#include "date_type.h"
+#include "company_type.h"
+#include "cargo_type.h"
+#include "departures_func.h"
+#include "departures_type.h"
+
+/** A scheduled order. */
+typedef struct OrderDate
+{
+ const Order *order; ///< The order
+ const Vehicle *v; ///< The vehicle carrying out the order
+ DateTicks expected_date;///< The date on which the order is expected to complete
+ Ticks lateness; ///< How late this order is expected to finish
+ DepartureStatus status; ///< Whether the vehicle has arrived to carry out the order yet
+} OrderDate;
+
+static bool IsDeparture(const Order *order, StationID station) {
+ return (order->GetType() == OT_GOTO_STATION &&
+ (StationID)order->GetDestination() == station &&
+ (order->GetLoadType() != OLFB_NO_LOAD ||
+ _settings_client.gui.departure_show_all_stops) &&
+ order->GetWaitTime() != 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->GetWaitTime() != 0);
+}
+
+/**
+ * Compute an up-to-date list of departures for a station.
+ * @param station the station to compute the departures of
+ * @param show_vehicle_types the types of vehicles to include in the departure list
+ * @param type the type of departures to get (departures or arrivals)
+ * @param show_vehicles_via whether to include vehicles that have this station in their orders but do not stop at it
+ * @return a list of departures, which is empty if an error occurred
+ */
+DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], DepartureType type, bool show_vehicles_via)
+{
+ /* This function is the meat of the departure boards functionality. */
+ /* As an overview, it works by repeatedly considering the best possible next departure to show. */
+ /* By best possible we mean the one expected to arrive at the station first. */
+ /* However, we do not consider departures whose scheduled time is too far in the future, even if they are expected before some delayed ones. */
+ /* This code can probably be made more efficient. I haven't done so in order to keep both its (relative) simplicity and my (relative) sanity. */
+ /* Having written that, it's not exactly slow at the moment. */
+
+ /* The list of departures which will be returned as a result. */
+ SmallVector *result = new SmallVector();
+
+ /* A list of the next scheduled orders to be considered for inclusion in the departure list. */
+ SmallVector next_orders;
+
+ /* The maximum possible date for departures to be scheduled to occur. */
+ DateTicks max_date = _settings_client.gui.max_departure_time * DAY_TICKS;
+
+ /* The scheduled order in next_orders with the earliest expected_date field. */
+ OrderDate *least_order = NULL;
+
+ /* Get all the vehicles stopping at this station. */
+ /* We do this to get the order which is the first time they will stop at this station. */
+ /* This order is stored along with some more information. */
+ /* We keep a pointer to the `least' order (the one with the soonest expected completion time). */
+ for (uint i = 0; i < 4; ++i) {
+ VehicleList vehicles;
+
+ if (!show_vehicle_types[i]) {
+ /* Don't show vehicles whose type we're not interested in. */
+ continue;
+ }
+
+ /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */
+ if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) {
+ /* Something went wrong: panic! */
+ return result;
+ }
+
+ /* Get the first order for each vehicle for the station we're interested in that doesn't have No Loading set. */
+ /* We find the least order while we're at it. */
+ for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
+ if (_settings_client.gui.departure_only_passengers) {
+ bool carries_passengers = false;
+
+ const Vehicle *u = *v;
+ while (u != NULL) {
+ if (u->cargo_type == CT_PASSENGERS && u->cargo_cap > 0) {
+ carries_passengers = true;
+ break;
+ }
+ u = u->Next();
+ }
+
+ if (carries_passengers == false) {
+ continue;
+ }
+ }
+
+ const Order *order = (*v)->GetOrder((*v)->cur_implicit_order_index % (*v)->GetNumOrders());
+ DateTicks start_date = (DateTicks)_date_fract - (*v)->current_order_time;
+ DepartureStatus status = D_TRAVELLING;
+
+ /* If the vehicle is stopped in a depot, ignore it. */
+ if ((*v)->IsStoppedInDepot()) {
+ continue;
+ }
+
+ /* If the vehicle is heading for a depot to stop there, then its departures are cancelled. */
+ if ((*v)->current_order.IsType(OT_GOTO_DEPOT) && (*v)->current_order.GetDepotActionType() & ODATFB_HALT) {
+ status = D_CANCELLED;
+ }
+
+ if ((*v)->current_order.IsType(OT_LOADING)) {
+ /* Account for the vehicle having reached the current order and being in the loading phase. */
+ status = D_ARRIVED;
+ start_date -= order->GetTravelTime() + (((*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. */
+ for (int i = (*v)->GetNumOrders(); i > 0; --i) {
+ start_date += order->GetTravelTime() + order->GetWaitTime();
+
+ /* 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->GetTravelTime();
+
+ 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 has a 0 travel time, and it's not explictly set, then stop. */
+ if (order->GetTravelTime() == 0 && !order->IsTravelTimetabled()) {
+ 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->GetWaitTime() : 0) > od->expected_date - od->lateness - (type == D_ARRIVAL ? od->order->GetWaitTime() : 0)) {
+ least_order = od;
+ }
+
+ *(next_orders.Append(1)) = od;
+
+ /* 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->GetTravelTime() != 0 || order->IsTravelTimetabled())) {
+ c.scheduled_date += order->GetTravelTime();
+ } 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->GetWaitTime();
+ 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->GetWaitTime();
+
+ /* 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->GetWaitTime();
+ }
+ }
+ }
+ } 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->GetWaitTime();
+
+ 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->GetTravelTime() + order->GetWaitTime();
+
+ /* 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->GetWaitTime();
+
+ continue;
+ }
+ case 2: {
+ /* Do not take the branch */
+ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next;
+ least_order->expected_date += order->GetTravelTime() + order->GetWaitTime();
+ 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 has a 0 travel time, and it's not explictly set, then stop. */
+ if (order->GetTravelTime() == 0 && !order->IsTravelTimetabled()) {
+ 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->GetTravelTime() + order->GetWaitTime();
+ }
+
+ /* 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 = INT32_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->GetWaitTime();
+ odd -= od->order->GetWaitTime();
+ }
+
+ if (lod > odd && od->expected_date - od->lateness < max_date) {
+ least_order = od;
+ }
+ }
+ }
+
+ /* Done. Phew! */
+ return result;
+}
Index: src/departures_func.h
new file mode 100644
===================================================================
--- src/departures_func.h (revision 0)
+++ src/departures_func.h (working copy)
@@ -0,0 +1,21 @@
+/* $Id: departures_func.h $ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file departures_func.h Functions related to departures. */
+
+#ifndef DEPARTURES_FUNC_H
+#define DEPARTURES_FUNC_H
+
+#include "station_base.h"
+#include "core/smallvec_type.hpp"
+#include "departures_type.h"
+
+DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[4], DepartureType type = D_DEPARTURE, bool show_vehicles_via = false);
+
+#endif /* DEPARTURES_FUNC_H */
Index: src/departures_gui.cpp
new file mode 100644
===================================================================
--- src/departures_gui.cpp (revision 0)
+++ src/departures_gui.cpp (working copy)
@@ -0,0 +1,856 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file departures_gui.cpp Scheduled departures from a station. */
+
+#include "stdafx.h"
+#include "debug.h"
+#include "gui.h"
+#include "textbuf_gui.h"
+#include "strings_func.h"
+#include "window_func.h"
+#include "vehicle_func.h"
+#include "string_func.h"
+#include "window_gui.h"
+#include "timetable.h"
+#include "vehiclelist.h"
+#include "company_base.h"
+#include "date_func.h"
+#include "departures_gui.h"
+#include "station_base.h"
+#include "vehicle_gui_base.h"
+#include "vehicle_base.h"
+#include "vehicle_gui.h"
+#include "order_base.h"
+#include "settings_type.h"
+#include "core/smallvec_type.hpp"
+#include "date_type.h"
+#include "company_type.h"
+#include "departures_func.h"
+#include "cargotype.h"
+
+#include "table/sprites.h"
+#include "table/strings.h"
+
+static const NWidgetPart _nested_departures_list[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+ NWidget(WWT_CAPTION, COLOUR_GREY, WID_DB_CAPTION), SetDataTip(STR_DEPARTURES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_SHADEBOX, COLOUR_GREY),
+ NWidget(WWT_STICKYBOX, COLOUR_GREY),
+ EndContainer(),
+
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_MATRIX, COLOUR_GREY, WID_DB_LIST), SetMinimalSize(0, 0), SetFill(1, 0), SetResize(1, 1),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_DB_SCROLLBAR),
+ EndContainer(),
+
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ARRS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_ARRIVALS, STR_DEPARTURES_ARRIVALS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_DEPS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_DEPARTURES, STR_DEPARTURES_DEPARTURES_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_VIA), SetMinimalSize(11, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_VIA_BUTTON, STR_DEPARTURES_VIA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_SHIPS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_PLANES), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP),
+ NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+ EndContainer(),
+};
+
+static WindowDesc _departures_desc(
+ WDP_AUTO, NULL, 260, 246,
+ WC_DEPARTURES_BOARD, WC_NONE,
+ 0,
+ _nested_departures_list, lengthof(_nested_departures_list)
+);
+
+static uint cached_date_width = 0; ///< The cached maximum width required to display a date.
+static uint cached_status_width = 0; ///< The cached maximum width required to show the status field.
+static uint cached_date_arrow_width = 0; ///< The cached width of the red/green arrows that may be displayed alongside times.
+static bool cached_date_display_method; ///< Whether the above cached values refers to original (d,m,y) dates or the 24h clock.
+static bool cached_arr_dep_display_method; ///< Whether to show departures and arrivals on a single line.
+
+template
+struct DeparturesWindow : public Window {
+protected:
+ StationID station; ///< The station whose departures we're showing.
+ DepartureList *departures; ///< The current list of departures from this station.
+ DepartureList *arrivals; ///< The current list of arrivals from this station.
+ uint entry_height; ///< The height of an entry in the departures list.
+ uint tick_count; ///< The number of ticks that have elapsed since the window was created. Used for scrolling text.
+ int calc_tick_countdown; ///< The number of ticks to wait until recomputing the departure list. Signed in case it goes below zero.
+ bool show_types[4]; ///< The vehicle types to show in the departure list.
+ bool departure_types[3]; ///< The types of departure to show in the departure list.
+ uint min_width; ///< The minimum width of this window.
+ Scrollbar *vscroll;
+
+ virtual uint GetMinWidth() const;
+ static void RecomputeDateWidth();
+ virtual void DrawDeparturesListItems(const Rect &r) const;
+ void DeleteDeparturesList(DepartureList* list);
+public:
+
+ DeparturesWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc),
+ 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();
+ this->vscroll = this->GetScrollbar(WID_DB_SCROLLBAR);
+ this->FinishInitNested(window_number);
+
+ /* By default, only show departures. */
+ departure_types[0] = true;
+ departure_types[1] = false;
+ departure_types[2] = false;
+ this->LowerWidget(WID_DB_SHOW_DEPS);
+ this->RaiseWidget(WID_DB_SHOW_ARRS);
+ this->RaiseWidget(WID_DB_SHOW_VIA);
+
+ for (uint i = 0; i < 4; ++i) {
+ show_types[i] = true;
+ this->LowerWidget(WID_DB_SHOW_TRAINS + i);
+ }
+
+ if (Twaypoint) {
+ this->GetWidget(WID_DB_CAPTION)->SetDataTip(STR_DEPARTURES_CAPTION_WAYPOINT, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
+
+ for (uint i = 0; i < 4; ++i) {
+ this->DisableWidget(WID_DB_SHOW_TRAINS + i);
+ }
+
+ this->DisableWidget(WID_DB_SHOW_ARRS);
+ this->DisableWidget(WID_DB_SHOW_DEPS);
+ this->DisableWidget(WID_DB_SHOW_VIA);
+
+ departure_types[2] = true;
+
+ this->LowerWidget(WID_DB_SHOW_VIA);
+ }
+ }
+
+ virtual ~DeparturesWindow()
+ {
+ this->DeleteDeparturesList(departures);
+ this->DeleteDeparturesList(this->arrivals);
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ switch (widget) {
+ case WID_DB_LIST:
+ resize->height = DeparturesWindow::entry_height;
+ size->height = 2 * resize->height;
+ break;
+ }
+ }
+
+ virtual void SetStringParameters(int widget) const
+ {
+ if (widget == WID_DB_CAPTION) {
+ const Station *st = Station::Get(this->station);
+ SetDParam(0, st->index);
+ }
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch (widget) {
+ case WID_DB_SHOW_TRAINS: // Show trains to this station
+ case WID_DB_SHOW_ROADVEHS: // Show road vehicles to this station
+ case WID_DB_SHOW_SHIPS: // Show ships to this station
+ case WID_DB_SHOW_PLANES: // Show aircraft to this station
+ this->show_types[widget - WID_DB_SHOW_TRAINS] = !this->show_types[widget - WID_DB_SHOW_TRAINS];
+ if (this->show_types[widget - WID_DB_SHOW_TRAINS]) {
+ this->LowerWidget(widget);
+ } else {
+ this->RaiseWidget(widget);
+ }
+ /* We need to recompute the departures list. */
+ this->calc_tick_countdown = 0;
+ /* We need to redraw the button that was pressed. */
+ this->SetWidgetDirty(widget);
+ break;
+
+ case WID_DB_SHOW_DEPS:
+ case WID_DB_SHOW_ARRS:
+ if (_settings_client.gui.departure_show_both) break;
+ /* FALL THROUGH */
+
+ case WID_DB_SHOW_VIA:
+
+ this->departure_types[widget - WID_DB_SHOW_DEPS] = !this->departure_types[widget - WID_DB_SHOW_DEPS];
+ if (this->departure_types[widget - WID_DB_SHOW_DEPS]) {
+ this->LowerWidget(widget);
+ } else {
+ this->RaiseWidget(widget);
+ }
+
+ if (!this->departure_types[0]) {
+ this->RaiseWidget(WID_DB_SHOW_VIA);
+ this->DisableWidget(WID_DB_SHOW_VIA);
+ } else {
+ this->EnableWidget(WID_DB_SHOW_VIA);
+
+ if (this->departure_types[2]) {
+ this->LowerWidget(WID_DB_SHOW_VIA);
+ }
+ }
+ /* We need to recompute the departures list. */
+ this->calc_tick_countdown = 0;
+ /* We need to redraw the button that was pressed. */
+ this->SetWidgetDirty(widget);
+ break;
+
+ case WID_DB_LIST: // Matrix to show departures
+ /* We need to find the departure corresponding to where the user clicked. */
+ uint32 id_v = (pt.y - this->GetWidget(WID_DB_LIST)->pos_y) / this->entry_height;
+
+ if (id_v >= this->vscroll->GetCapacity()) return; // click out of bounds
+
+ id_v += this->vscroll->GetPosition();
+
+ if (id_v >= (this->departures->Length() + this->arrivals->Length())) return; // click out of list bound
+
+ uint departure = 0;
+ uint arrival = 0;
+
+ /* Draw each departure. */
+ for (uint i = 0; i <= id_v; ++i) {
+ const Departure *d;
+
+ if (arrival == this->arrivals->Length()) {
+ d = (*(this->departures))[departure++];
+ } else if (departure == this->departures->Length()) {
+ d = (*(this->arrivals))[arrival++];
+ } else {
+ d = (*(this->departures))[departure];
+ const Departure *a = (*(this->arrivals))[arrival];
+
+ if (a->scheduled_date < d->scheduled_date) {
+ d = a;
+ arrival++;
+ } else {
+ departure++;
+ }
+ }
+
+ if (i == id_v) {
+ ShowVehicleViewWindow(d->vehicle);
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ virtual void OnTick()
+ {
+ if (_pause_mode == PM_UNPAUSED) {
+ this->tick_count += 1;
+ this->calc_tick_countdown -= 1;
+ }
+
+ /* Recompute the minimum date display width if the cached one is no longer valid. */
+ if (cached_date_width == 0 ||
+ _settings_client.gui.time_in_minutes != cached_date_display_method ||
+ _settings_client.gui.departure_show_both != cached_arr_dep_display_method) {
+ this->RecomputeDateWidth();
+ }
+
+ /* We need to redraw the scrolling text in its new position. */
+ this->SetWidgetDirty(WID_DB_LIST);
+
+ /* Recompute the list of departures if we're due to. */
+ if (this->calc_tick_countdown <= 0) {
+ this->calc_tick_countdown = _settings_client.gui.departure_calc_frequency;
+ this->DeleteDeparturesList(this->departures);
+ this->DeleteDeparturesList(this->arrivals);
+ this->departures = (this->departure_types[0] ? MakeDepartureList(this->station, this->show_types, D_DEPARTURE, Twaypoint || this->departure_types[2]) : new DepartureList());
+ this->arrivals = (this->departure_types[1] && !_settings_client.gui.departure_show_both ? MakeDepartureList(this->station, this->show_types, D_ARRIVAL ) : new DepartureList());
+ this->SetWidgetDirty(WID_DB_LIST);
+ }
+
+ uint new_width = this->GetMinWidth();
+
+ if (new_width != this->min_width) {
+ NWidgetCore *n = this->GetWidget(WID_DB_LIST);
+ n->SetMinimalSize(new_width, 0);
+ this->ReInit();
+ this->min_width = new_width;
+ }
+
+ uint new_height = 1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1;
+
+ if (new_height != this->entry_height) {
+ this->entry_height = new_height;
+ this->SetWidgetDirty(WID_DB_LIST);
+ this->ReInit();
+ }
+ }
+
+ virtual void OnPaint()
+ {
+ if (Twaypoint || _settings_client.gui.departure_show_both) {
+ this->DisableWidget(WID_DB_SHOW_ARRS);
+ this->DisableWidget(WID_DB_SHOW_DEPS);
+ } else {
+ this->EnableWidget(WID_DB_SHOW_ARRS);
+ this->EnableWidget(WID_DB_SHOW_DEPS);
+ }
+
+ this->vscroll->SetCount(min(_settings_client.gui.max_departures, this->departures->Length() + this->arrivals->Length()));
+ this->DrawWidgets();
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch (widget) {
+ case WID_DB_LIST:
+ this->DrawDeparturesListItems(r);
+ break;
+ }
+ }
+
+ virtual void OnResize()
+ {
+ this->vscroll->SetCapacityFromWidget(this, WID_DB_LIST);
+ this->GetWidget(WID_DB_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
+ }
+};
+
+/**
+ * Shows a window of scheduled departures for a station.
+ * @param station the station to show a departures window for
+ */
+void ShowStationDepartures(StationID station)
+{
+ AllocateWindowDescFront >(&_departures_desc, station);
+}
+
+/**
+ * Shows a window of scheduled departures for a station.
+ * @param station the station to show a departures window for
+ */
+void ShowWaypointDepartures(StationID waypoint)
+{
+ AllocateWindowDescFront >(&_departures_desc, waypoint);
+}
+
+template
+void DeparturesWindow::RecomputeDateWidth()
+{
+ cached_date_width = 0;
+ cached_status_width = 0;
+ cached_date_display_method = _settings_client.gui.time_in_minutes;
+ cached_arr_dep_display_method = _settings_client.gui.departure_show_both;
+
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_ON_TIME)).width, cached_status_width);
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_DELAYED)).width, cached_status_width);
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_CANCELLED)).width, cached_status_width);
+
+ uint interval = cached_date_display_method ? _settings_client.gui.ticks_per_minute : DAY_TICKS;
+ uint count = cached_date_display_method ? 24*60 : 365;
+
+ for (uint i = 0; i < count; ++i) {
+ SetDParam(0, INT_MAX - (i*interval));
+ SetDParam(1, INT_MAX - (i*interval));
+ cached_date_width = max(GetStringBoundingBox(cached_arr_dep_display_method ? STR_DEPARTURES_TIME_BOTH : STR_DEPARTURES_TIME_DEP).width, cached_date_width);
+ cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_EXPECTED)).width, cached_status_width);
+ }
+
+ SetDParam(0, 0);
+ cached_date_arrow_width = GetStringBoundingBox(STR_DEPARTURES_TIME_DEP).width - GetStringBoundingBox(STR_DEPARTURES_TIME).width;
+
+ if (!_settings_client.gui.departure_show_both) {
+ cached_date_width -= cached_date_arrow_width;
+ }
+}
+
+template
+uint DeparturesWindow::GetMinWidth() const
+{
+ uint result = 0;
+
+ /* Time */
+ result = cached_date_width;
+
+ /* Vehicle type icon */
+ result += _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
+
+ /* Status */
+ result += cached_status_width;
+
+ /* Find the maximum company name width. */
+ int toc_width = 0;
+
+ /* Find the maximum company name width. */
+ int group_width = 0;
+
+ /* Find the maximum vehicle name width. */
+ int veh_width = 0;
+
+ if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) {
+ for (uint i = 0; i < 4; ++i) {
+ VehicleList vehicles;
+
+ /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */
+ if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station).Pack())) {
+ /* Something went wrong: panic! */
+ continue;
+ }
+
+ for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
+ SetDParam(0, (uint64)((*v)->index));
+ int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width;
+ if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width;
+
+ if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) {
+ SetDParam(0, (uint64)((*v)->group_id));
+ width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width;
+ if (_settings_client.gui.departure_show_group && width > group_width) group_width = width;
+ }
+
+ SetDParam(0, (uint64)((*v)->owner));
+ width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width;
+ if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width;
+ }
+ }
+ }
+
+ result += toc_width + veh_width + group_width;
+
+ return result + 140;
+}
+
+/**
+ * Deletes this window's departure list.
+ */
+template
+void DeparturesWindow::DeleteDeparturesList(DepartureList *list)
+{
+ /* SmallVector uses free rather than delete on its contents (which doesn't invoke the destructor), so we need to delete each departure manually. */
+ for (uint i = 0; i < list->Length(); ++i) {
+ Departure **d = list->Get(i);
+ delete *d;
+ /* Make sure a double free doesn't happen. */
+ *d = NULL;
+ }
+ list->Reset();
+ delete list;
+ list = NULL;
+}
+
+/**
+ * Draws a list of departures.
+ */
+template
+void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
+{
+ int left = r.left + WD_MATRIX_LEFT;
+ int right = r.right - WD_MATRIX_RIGHT;
+
+ bool rtl = _current_text_dir == TD_RTL;
+ bool ltr = !rtl;
+
+ int text_offset = WD_FRAMERECT_RIGHT;
+ int text_left = left + (rtl ? 0 : text_offset);
+ int text_right = right - (rtl ? text_offset : 0);
+
+ int y = r.top + 1;
+ uint max_departures = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->departures->Length() + this->arrivals->Length());
+
+ if (max_departures > _settings_client.gui.max_departures) {
+ max_departures = _settings_client.gui.max_departures;
+ }
+
+ byte small_font_size = _settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL;
+
+ /* Draw the black background. */
+ GfxFillRect(r.left + 1, r.top, r.right - 1, r.bottom, PC_BLACK);
+
+ /* Nothing selected? Then display the information text. */
+ bool none_selected[2] = {true, true};
+ for (uint i = 0; i < 4; ++i)
+ {
+ if (this->show_types[i]) {
+ none_selected[0] = false;
+ break;
+ }
+ }
+
+ for (uint i = 0; i < 2; ++i)
+ {
+ if (this->departure_types[i]) {
+ none_selected[1] = false;
+ break;
+ }
+ }
+
+ if (none_selected[0] || none_selected[1]) {
+ DrawString(text_left, text_right, y + 1, STR_DEPARTURES_NONE_SELECTED);
+ return;
+ }
+
+ /* No scheduled departures? Then display the information text. */
+ if (max_departures == 0) {
+ DrawString(text_left, text_right, y + 1, STR_DEPARTURES_EMPTY);
+ return;
+ }
+
+ /* Find the maximum possible width of the departure time and "Expt " 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->GetWaitTime());
+ 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 ", ". */
+ 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 ". */
+ 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;
+ }
+}
Index: src/departures_gui.h
new file mode 100644
===================================================================
--- src/departures_gui.h (revision 0)
+++ src/departures_gui.h (working copy)
@@ -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 .
+ */
+
+/** @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 */
Index: src/departures_type.h
new file mode 100644
===================================================================
--- src/departures_type.h (revision 0)
+++ src/departures_type.h (working copy)
@@ -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 .
+ */
+
+/** @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 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 DepartureList;
+
+#endif /* DEPARTURES_TYPE_H */
Index: src/depot.cpp
===================================================================
--- src/depot.cpp (revision 27386)
+++ src/depot.cpp (working copy)
@@ -17,6 +17,7 @@
#include "core/pool_func.hpp"
#include "vehicle_gui.h"
#include "vehiclelist.h"
+#include "tracerestrict.h"
#include "safeguards.h"
@@ -42,6 +43,8 @@
/* Clear the depot from all order-lists */
RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->index);
+ TraceRestrictRemoveDestinationID(TROCAF_DEPOT, this->index);
+
/* Delete the depot-window */
DeleteWindowById(WC_VEHICLE_DEPOT, this->xy);
Index: src/depot_gui.cpp
===================================================================
--- src/depot_gui.cpp (revision 27386)
+++ src/depot_gui.cpp (working copy)
@@ -26,6 +26,8 @@
#include "tilehighlight_func.h"
#include "window_gui.h"
#include "vehiclelist.h"
+#include "company_base.h"
+#include "infrastructure_func.h"
#include "order_backup.h"
#include "zoom_func.h"
@@ -686,7 +688,7 @@
/* Setup disabled buttons. */
TileIndex tile = this->window_number;
- this->SetWidgetsDisabledState(!IsTileOwner(tile, _local_company),
+ this->SetWidgetsDisabledState(!Company::IsValidID(_local_company) || !IsInfraTileUsageAllowed(this->type, _local_company, tile),
WID_D_STOP_ALL,
WID_D_START_ALL,
WID_D_SELL,
Index: src/disaster_vehicle.cpp
===================================================================
--- src/disaster_vehicle.cpp (revision 27386)
+++ src/disaster_vehicle.cpp (working copy)
@@ -362,6 +362,7 @@
uint dist = Delta(v->x_pos, u->x_pos) + Delta(v->y_pos, u->y_pos);
if (dist < TILE_SIZE && !(u->vehstatus & VS_HIDDEN) && u->breakdown_ctr == 0) {
+ u->breakdown_type = BREAKDOWN_CRITICAL;
u->breakdown_ctr = 3;
u->breakdown_delay = 140;
}
@@ -547,6 +548,7 @@
if (Delta(target->x_pos, v->x_pos) + Delta(target->y_pos, v->y_pos) <= 12 * (int)TILE_SIZE) {
target->breakdown_ctr = 5;
target->breakdown_delay = 0xF0;
+ target->breakdown_type = BREAKDOWN_CRITICAL;
}
}
}
Index: src/dock_gui.cpp
===================================================================
--- src/dock_gui.cpp (revision 27386)
+++ src/dock_gui.cpp (working copy)
@@ -196,7 +196,7 @@
uint32 p2 = (uint32)INVALID_STATION << 16; // no station to join
/* tile is always the land tile, so need to evaluate _thd.pos */
- CommandContainer cmdcont = { tile, _ctrl_pressed, p2, CMD_BUILD_DOCK | CMD_MSG(STR_ERROR_CAN_T_BUILD_DOCK_HERE), CcBuildDocks, "" };
+ CommandContainer cmdcont = { tile, _ctrl_pressed, p2, CMD_BUILD_DOCK | CMD_MSG(STR_ERROR_CAN_T_BUILD_DOCK_HERE), CcBuildDocks, 0, "" };
/* Determine the watery part of the dock. */
DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(tile));
Index: src/economy.cpp
===================================================================
--- src/economy.cpp (revision 27386)
+++ src/economy.cpp (working copy)
@@ -43,6 +43,7 @@
#include "economy_base.h"
#include "core/pool_func.hpp"
#include "core/backup_type.hpp"
+#include "infrastructure_func.h"
#include "cargo_type.h"
#include "water.h"
#include "game/game.hpp"
@@ -425,7 +426,8 @@
}
}
- {
+ /* Change ownership of vehicles */
+ if (new_owner != INVALID_OWNER) {
FreeUnitIDGenerator unitidgen[] = {
FreeUnitIDGenerator(VEH_TRAIN, new_owner), FreeUnitIDGenerator(VEH_ROAD, new_owner),
FreeUnitIDGenerator(VEH_SHIP, new_owner), FreeUnitIDGenerator(VEH_AIRCRAFT, new_owner)
@@ -483,6 +485,10 @@
}
if (new_owner != INVALID_OWNER) GroupStatistics::UpdateAutoreplace(new_owner);
+ } else {
+ /* Depending on sharing settings, other companies could be affected too.
+ * Let the infrastructure sharing code handle this. */
+ HandleSharingCompanyDeletion(old_owner);
}
/* Change ownership of tiles */
@@ -497,22 +503,14 @@
* and signals were not propagated
* Similar with crossings - it is needed to bar crossings that weren't before
* because of different owner of crossing and approaching train */
- tile = 0;
- do {
- if (IsTileType(tile, MP_RAILWAY) && IsTileOwner(tile, new_owner) && HasSignals(tile)) {
- TrackBits tracks = GetTrackBits(tile);
- do { // there may be two tracks with signals for TRACK_BIT_HORZ and TRACK_BIT_VERT
- Track track = RemoveFirstTrack(&tracks);
- if (HasSignalOnTrack(tile, track)) AddTrackToSignalBuffer(tile, track, new_owner);
- } while (tracks != TRACK_BIT_NONE);
- } else if (IsLevelCrossingTile(tile) && IsTileOwner(tile, new_owner)) {
- UpdateLevelCrossing(tile);
+ UpdateAllBlockSignals(new_owner);
+ } else if (_settings_game.economy.infrastructure_sharing[VEH_TRAIN]) {
+ /* tracks are being removed while sharing is enabled.
+ * Thus, update all signals and crossings. */
+ UpdateAllBlockSignals();
}
- } while (++tile != MapSize());
- }
-
- /* update signals in buffer */
+ /* Update any signals in the buffer */
UpdateSignalsInBuffer();
}
@@ -1213,7 +1211,13 @@
/* Handle end of route payment */
Money profit = DeliverGoods(count, this->ct, this->current_station, cp->SourceStationXY(), cp->DaysInTransit(), this->owner, cp->SourceSubsidyType(), cp->SourceSubsidyID());
+#ifndef INFRASTRUCTURE_FUNC_H
this->route_profit += profit;
+#else
+ /* For Infrastructure patch. Handling transfers between other companies */
+ this->route_profit += profit - cp->FeederShare();
+#endif
+
/* The vehicle's profit is whatever route profit there is minus feeder shares. */
this->visual_profit += profit - cp->FeederShare(count);
@@ -1236,6 +1240,9 @@
profit = profit * _settings_game.economy.feeder_payment_share / 100;
+#ifdef INFRASTRUCTURE_FUNC_H
+ this->route_profit += profit;
+#endif
this->visual_transfer += profit; // accumulate transfer profits for whole vehicle
return profit; // account for the (virtual) profit already made for the cargo packet
}
Index: src/economy_type.h
===================================================================
--- src/economy_type.h (revision 27386)
+++ src/economy_type.h (working copy)
@@ -161,6 +161,8 @@
EXPENSES_SHIP_INC, ///< Income from ships.
EXPENSES_LOAN_INT, ///< Interest payments over the loan.
EXPENSES_OTHER, ///< Other expenses.
+ EXPENSES_SHARING_COST, ///< Infrastructure sharing costs
+ EXPENSES_SHARING_INC, ///< Infrastructure sharing income
EXPENSES_END, ///< Number of expense types.
INVALID_EXPENSES = 0xFF, ///< Invalid expense type.
};
Index: src/fios.h
===================================================================
--- src/fios.h (revision 27386)
+++ src/fios.h (working copy)
@@ -92,9 +92,11 @@
/** First slot usable for (New)GRFs used during the game. */
FIRST_GRF_SLOT = 2,
/** Last slot usable for (New)GRFs used during the game. */
- LAST_GRF_SLOT = 63,
+ LAST_GRF_SLOT = 255,
/** Maximum number of slots. */
- MAX_FILE_SLOTS = 64
+ MAX_FILE_SLOTS = 256,
+ /** Maximum number of slots for network game */
+ MAX_FILE_SLOTS_IN_NETWORK = 63
};
/** Mode of the file dialogue window. */
Index: src/gfx.cpp
===================================================================
--- src/gfx.cpp (revision 27386)
+++ src/gfx.cpp (working copy)
@@ -1191,6 +1191,9 @@
_dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
_dirty_blocks = ReallocT(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
+ extern uint32 *_vp_map_line;
+ _vp_map_line = ReallocT(_vp_map_line, _screen.width);
+
/* check the dirty rect */
if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width;
if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height;
Index: src/gfxinit.cpp
===================================================================
--- src/gfxinit.cpp (revision 27386)
+++ src/gfxinit.cpp (working copy)
@@ -19,6 +19,12 @@
#include "blitter/factory.hpp"
#include "video/video_driver.hpp"
#include "window_func.h"
+#include "zoom_func.h"
+#include "clear_map.h"
+#include "clear_func.h"
+#include "tree_map.h"
+#include "table/tree_land.h"
+#include "blitter/32bpp_base.hpp"
/* The type of set we're replacing */
#define SET_TYPE "graphics"
@@ -170,6 +176,9 @@
_palette_remap_grf[i] = (PAL_DOS != used_set->palette);
LoadGrfFile(used_set->files[GFT_BASE].filename, 0, i++);
+ /* Progsignal sprites. */
+ LoadGrfFile("progsignals.grf", SPR_PROGSIGNAL_BASE, i++);
+
/*
* The second basic file always starts at the given location and does
* contain a different amount of sprites depending on the "type"; DOS
@@ -193,6 +202,11 @@
);
}
+ LoadGrfFile("innerhighlight.grf", SPR_ZONING_INNER_HIGHLIGHT_BASE, i++);
+
+ /* Load route step graphics */
+ LoadGrfFile("route_step.grf", SPR_ROUTE_STEP_BASE, i++);
+
/* Initialize the unicode to sprite mapping table */
InitializeUnicodeGlyphMap();
@@ -308,6 +322,103 @@
ReInitAllWindows();
}
+static void UpdateRouteStepSpriteSize()
+{
+ extern uint _vp_route_step_width;
+ extern uint _vp_route_step_height_top;
+ extern uint _vp_route_step_height_middle;
+ extern uint _vp_route_step_height_bottom;
+ extern SubSprite _vp_route_step_subsprite;
+
+ Dimension d = GetSpriteSize(SPR_ROUTE_STEP_TOP);
+ _vp_route_step_width = d.width;
+ _vp_route_step_height_top = d.height;
+
+ d = GetSpriteSize(SPR_ROUTE_STEP_MIDDLE);
+ _vp_route_step_height_middle = d.height;
+ assert(_vp_route_step_width == d.width);
+
+ d = GetSpriteSize(SPR_ROUTE_STEP_BOTTOM);
+ _vp_route_step_height_bottom = d.height;
+ assert(_vp_route_step_width == d.width);
+
+ const int char_height = GetCharacterHeight(FS_SMALL) + 1;
+ _vp_route_step_subsprite.right = ScaleByZoom(_vp_route_step_width, ZOOM_LVL_GUI);
+ _vp_route_step_subsprite.bottom = ScaleByZoom(char_height, ZOOM_LVL_GUI);
+ _vp_route_step_subsprite.left = 0;
+ _vp_route_step_subsprite.top = 0;
+}
+
+/* multi can be density, field type, ... */
+static SpriteID GetSpriteIDForClearGround(const ClearGround cg, const Slope slope, const uint multi)
+{
+ switch (cg) {
+ case CLEAR_GRASS:
+ return GetSpriteIDForClearLand(slope, (byte) multi);
+ case CLEAR_ROUGH:
+ return GetSpriteIDForHillyLand(slope, multi);
+ case CLEAR_ROCKS:
+ return GetSpriteIDForRocks(slope, multi);
+ case CLEAR_FIELDS:
+ return GetSpriteIDForFields(slope, multi);
+ case CLEAR_SNOW:
+ case CLEAR_DESERT:
+ return GetSpriteIDForSnowDesert(slope, multi);
+ default: NOT_REACHED();
+ }
+}
+
+/** Once the sprites are loaded, we can determine main colours of ground/water/... */
+void GfxDetermineMainColours()
+{
+ /* Water. */
+ extern uint32 _vp_map_water_colour[5];
+ _vp_map_water_colour[0] = GetSpriteMainColour(SPR_FLAT_WATER_TILE, PAL_NONE);
+ if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) {
+ _vp_map_water_colour[1] = Blitter_32bppBase::MakeTransparent(_vp_map_water_colour[0], 256, 192).data; // lighter
+ _vp_map_water_colour[2] = Blitter_32bppBase::MakeTransparent(_vp_map_water_colour[0], 192, 256).data; // darker
+ _vp_map_water_colour[3] = _vp_map_water_colour[2];
+ _vp_map_water_colour[4] = _vp_map_water_colour[1];
+ }
+
+ /* Clear ground. */
+ extern uint32 _vp_map_vegetation_clear_colours[16][6][8];
+ memset(_vp_map_vegetation_clear_colours, 0, sizeof(_vp_map_vegetation_clear_colours));
+ const struct {
+ byte min;
+ byte max;
+ } multi[6] = {
+ { 0, 3 }, // CLEAR_GRASS, density
+ { 0, 7 }, // CLEAR_ROUGH, "random" based on position
+ { 0, 1 }, // CLEAR_ROCKS, tile hash parity
+ { 0, 7 }, // CLEAR_FIELDS, some field types
+ { 0, 3 }, // CLEAR_SNOW, density
+ { 1, 3 }, // CLEAR_DESERT, density
+ };
+ for (uint s = 0; s <= SLOPE_ELEVATED; s++) {
+ for (uint cg = 0; cg < 6; cg++) {
+ for (uint m = multi[cg].min; m <= multi[cg].max; m++) {
+ _vp_map_vegetation_clear_colours[s][cg][m] = GetSpriteMainColour(GetSpriteIDForClearGround((ClearGround) cg, (Slope) s, m), PAL_NONE);
+ }
+ }
+ }
+
+ /* Trees. */
+ extern uint32 _vp_map_vegetation_tree_colours[5][MAX_TREE_COUNT_BY_LANDSCAPE];
+ const uint base = _tree_base_by_landscape[_settings_game.game_creation.landscape];
+ const uint count = _tree_count_by_landscape[_settings_game.game_creation.landscape];
+ for (uint tg = 0; tg < 5; tg++) {
+ for (uint i = base; i < base + count; i++) {
+ _vp_map_vegetation_tree_colours[tg][i - base] = GetSpriteMainColour(_tree_sprites[i].sprite, _tree_sprites[i].pal);
+ }
+ const int diff = MAX_TREE_COUNT_BY_LANDSCAPE - count;
+ if (diff > 0) {
+ for (uint i = count; i < MAX_TREE_COUNT_BY_LANDSCAPE; i++)
+ _vp_map_vegetation_tree_colours[tg][i] = _vp_map_vegetation_tree_colours[tg][i - count];
+ }
+ }
+}
+
/** Initialise and load all the sprites. */
void GfxLoadSprites()
{
@@ -318,7 +429,9 @@
GfxInitSpriteMem();
LoadSpriteTables();
GfxInitPalettes();
+ GfxDetermineMainColours();
+ UpdateRouteStepSpriteSize();
UpdateCursorSize();
}
Index: src/ground_vehicle.cpp
===================================================================
--- src/ground_vehicle.cpp (revision 27386)
+++ src/ground_vehicle.cpp (working copy)
@@ -30,12 +30,9 @@
uint32 number_of_parts = 0;
uint16 max_track_speed = v->GetDisplayMaxSpeed();
- for (const T *u = v; u != NULL; u = u->Next()) {
- uint32 current_power = u->GetPower() + u->GetPoweredPartPower(u);
- total_power += current_power;
+ this->CalculatePower(total_power, max_te, false);
- /* Only powered parts add tractive effort. */
- if (current_power > 0) max_te += u->GetWeight() * u->GetTractiveEffort();
+ for (const T *u = v; u != NULL; u = u->Next()) {
number_of_parts++;
/* Get minimum max speed for this track. */
@@ -58,8 +55,6 @@
this->gcache.cached_air_drag = air_drag + 3 * air_drag * number_of_parts / 20;
- max_te *= 10000; // Tractive effort in (tonnes * 1000 * 10 =) N.
- max_te /= 256; // Tractive effort is a [0-255] coefficient.
if (this->gcache.cached_power != total_power || this->gcache.cached_max_te != max_te) {
/* Stop the vehicle if it has no power. */
if (total_power == 0) this->vehstatus |= VS_STOPPED;
@@ -73,6 +68,30 @@
this->gcache.cached_max_track_speed = max_track_speed;
}
+template
+void GroundVehicle::CalculatePower(uint32& total_power, uint32& max_te, bool breakdowns) const {
+
+ total_power = 0;
+ max_te = 0;
+
+ const T *v = T::From(this);
+
+ for (const T *u = v; u != NULL; u = u->Next()) {
+ uint32 current_power = u->GetPower() + u->GetPoweredPartPower(u);
+ total_power += current_power;
+
+ /* Only powered parts add tractive effort. */
+ if (current_power > 0) max_te += u->GetWeight() * u->GetTractiveEffort();
+
+ if (breakdowns && u->breakdown_ctr == 1 && u->breakdown_type == BREAKDOWN_LOW_POWER) {
+ total_power = total_power * u->breakdown_severity / 256;
+ }
+ }
+
+ max_te *= 10000; // Tractive effort in (tonnes * 1000 * 10 =) N.
+ max_te /= 256; // Tractive effort is a [0-255] coefficient.
+}
+
/**
* Recalculates the cached weight of a vehicle and its parts. Should be called each time the cargo on
* the consist changes.
@@ -104,7 +123,7 @@
* @return Current acceleration of the vehicle.
*/
template
-int GroundVehicle::GetAcceleration() const
+int GroundVehicle::GetAcceleration()
{
/* Templated class used for function calls for performance reasons. */
const T *v = T::From(this);
@@ -119,6 +138,7 @@
* and km/h to m/s conversion below result in a maxium of
* about 1.1E11, way more than 4.3E9 of int32. */
int64 power = this->gcache.cached_power * 746ll;
+ uint32 max_te = this->gcache.cached_max_te; // [N]
/* This is constructed from:
* - axle resistance: U16 power * 10 for 128 vehicles.
@@ -150,7 +170,16 @@
/* This value allows to know if the vehicle is accelerating or braking. */
AccelStatus mode = v->GetAccelerationStatus();
- const int max_te = this->gcache.cached_max_te; // [N]
+ /* handle breakdown power reduction */
+ //TODO
+ if( Type == VEH_TRAIN && mode == AS_ACCEL && HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER)) {
+ /* We'd like to cache this, but changing cached_power has too many unwanted side-effects */
+ uint32 power_temp;
+ this->CalculatePower(power_temp, max_te, true);
+ power = power_temp * 74611;
+ }
+
+
/* Constructued from power, with need to multiply by 18 and assuming
* low speed, it needs to be a 64 bit integer too. */
int64 force;
@@ -158,7 +187,7 @@
if (!maglev) {
/* Conversion factor from km/h to m/s is 5/18 to get [N] in the end. */
force = power * 18 / (speed * 5);
- if (mode == AS_ACCEL && force > max_te) force = max_te;
+ if (mode == AS_ACCEL && force > (int)max_te) force = max_te;
} else {
force = power / 25;
}
@@ -168,6 +197,34 @@
force = max(force, (mass * 8) + resistance);
}
+ /* If power is 0 because of a breakdown, we make the force 0 if accelerating */
+ if ( Type == VEH_TRAIN && mode == AS_ACCEL && HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER) && power == 0) {
+ force = 0;
+ }
+
+ /* Calculate the breakdown chance */
+ if (_settings_game.vehicle.improved_breakdowns) {
+ assert(this->gcache.cached_max_track_speed > 0);
+ /** First, calculate (resistance / force * current speed / max speed) << 16.
+ * This yields a number x on a 0-1 scale, but shifted 16 bits to the left.
+ * We then calculate 64 + 128x, clamped to 0-255, but still shifted 16 bits to the left.
+ * Then we apply a correction for multiengine trains, and in the end we shift it 16 bits to the right to get a 0-255 number.
+ * @note A seperate correction for multiheaded engines is done in CheckVehicleBreakdown. We can't do that here because it would affect the whole consist.
+ */
+ uint64 breakdown_factor = (uint64)abs(resistance) * (uint64)(this->cur_speed << 16);
+ breakdown_factor /= (max(force, (int64)100) * this->gcache.cached_max_track_speed);
+ breakdown_factor = min((64 << 16) + (breakdown_factor * 128), 255 << 16);
+ if ( Type == VEH_TRAIN && Train::From(this)->tcache.cached_num_engines > 1) {
+ /* For multiengine trains, breakdown chance is multiplied by 3 / (num_engines + 2) */
+ breakdown_factor *= 3;
+ breakdown_factor /= (Train::From(this)->tcache.cached_num_engines + 2);
+ }
+ /* breakdown_chance is at least 5 (5 / 128 = ~4% of the normal chance) */
+ this->breakdown_chance = max(breakdown_factor >> 16, (uint64)5);
+ } else {
+ this->breakdown_chance = 128;
+ }
+
if (mode == AS_ACCEL) {
/* Easy way out when there is no acceleration. */
if (force == resistance) return 0;
@@ -178,7 +235,27 @@
* a hill will never speed up enough to (eventually) get back to the
* same (maximum) speed. */
int accel = ClampToI32((force - resistance) / (mass * 4));
- return force < resistance ? min(-1, accel) : max(1, accel);
+ accel = force < resistance ? min(-1, accel) : max(1, accel);
+ if (this->type == VEH_TRAIN ) {
+ if(_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL &&
+ HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER)) {
+ /* We need to apply the power reducation for non-realistic acceleration here */
+ uint32 power;
+ CalculatePower(power, max_te, true);
+ accel = accel * power / this->gcache.cached_power;
+ accel -= this->acceleration >> 1;
+ }
+
+
+ if ( this->IsFrontEngine() && !(this->current_order_time & 0x1FF) &&
+ !(this->current_order.IsType(OT_LOADING)) &&
+ !(Train::From(this)->flags & (VRF_IS_BROKEN | (1 << VRF_TRAIN_STUCK))) &&
+ this->cur_speed < 3 && accel < 5) {
+ SetBit(Train::From(this)->flags, VRF_TOO_HEAVY);
+ }
+ }
+
+ return accel;
} else {
return ClampToI32(min(-force - resistance, -10000) / mass);
}
Index: src/ground_vehicle.hpp
===================================================================
--- src/ground_vehicle.hpp (revision 27386)
+++ src/ground_vehicle.hpp (working copy)
@@ -91,9 +91,12 @@
void PowerChanged();
void CargoChanged();
- int GetAcceleration() const;
bool IsChainInDepot() const;
+ void CalculatePower(uint32& power, uint32& max_te, bool breakdowns) const;
+
+ int GetAcceleration();
+
/**
* Common code executed for crashed ground vehicles
* @param flooded was this vehicle flooded?
@@ -369,7 +372,22 @@
/* When we are going faster than the maximum speed, reduce the speed
* somewhat gradually. But never lower than the maximum speed. */
- int tempmax = max_speed;
+ int tempmax = ((this->breakdown_ctr == 1) ? this->cur_speed : max_speed);
+
+ if (this->breakdown_ctr == 1) {
+ if (this->breakdown_type == BREAKDOWN_LOW_POWER) {
+ if((this->tick_counter & 0x7) == 0) {
+ if(this->cur_speed > (this->breakdown_severity * max_speed) >> 8) {
+ tempmax = this->cur_speed - (this->cur_speed / 10) - 1;
+ } else {
+ tempmax = (this->breakdown_severity * max_speed) >> 8;
+ }
+ }
+ }
+ if(this->breakdown_type == BREAKDOWN_LOW_SPEED)
+ tempmax = min(max_speed, this->breakdown_severity);
+ }
+
if (this->cur_speed > max_speed) {
tempmax = max(this->cur_speed - (this->cur_speed / 10) - 1, max_speed);
}
Index: src/group_gui.cpp
===================================================================
--- src/group_gui.cpp (revision 27386)
+++ src/group_gui.cpp (working copy)
@@ -55,6 +55,7 @@
SetFill(1, 0), SetResize(0, 1), SetScrollbar(WID_GL_LIST_GROUP_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_GL_LIST_GROUP_SCROLLBAR),
EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_GREY, WID_GL_INFO), SetMinimalSize(200, 37), SetFill(1, 0), EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GL_CREATE_GROUP), SetFill(0, 1),
SetDataTip(SPR_GROUP_CREATE_TRAIN, STR_GROUP_CREATE_TOOLTIP),
@@ -371,6 +372,9 @@
max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget(WID_GL_DELETE_GROUP)->widget_data).height);
max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget(WID_GL_REPLACE_PROTECTION)->widget_data).height);
+ /* ... minus the height of the group info ... */
+ max_icon_height += 37;
+
/* Get a multiple of tiny_step_height of that amount */
size->height = Ceil(size->height - max_icon_height, tiny_step_height);
break;
@@ -527,6 +531,40 @@
DrawGroupInfo(r.top + WD_FRAMERECT_TOP, r.left, r.right, DEFAULT_GROUP);
break;
+ case WID_GL_INFO: {
+ Money this_year = 0;
+ Money last_year = 0;
+ uint32 occupancy = 0;
+ uint32 group_vehicles = 0;
+
+ for (uint i = 0, vehicle_count = this->vehicles.Length(); i < vehicle_count; i++) {
+ const Vehicle *v = this->vehicles[i];
+
+ assert(v->owner == this->owner);
+
+ this_year += v->GetDisplayProfitThisYear();
+ last_year += v->GetDisplayProfitLastYear();
+ occupancy += v->trip_occupancy;
+ group_vehicles++;
+ }
+
+ DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT - 16, r.top + WD_FRAMERECT_TOP + 1, STR_GROUP_PROFIT_THIS_YEAR, TC_BLACK);
+ SetDParam(0, this_year);
+ DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT - 16, r.top + WD_FRAMERECT_TOP + 1, STR_JUST_CURRENCY_LONG, TC_BLACK, SA_RIGHT);
+
+ DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT - 16, r.top + WD_FRAMERECT_TOP + FONT_HEIGHT_NORMAL + 2, STR_GROUP_PROFIT_LAST_YEAR, TC_BLACK);
+ SetDParam(0, last_year);
+ DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT - 16, r.top + WD_FRAMERECT_TOP + FONT_HEIGHT_NORMAL + 2, STR_JUST_CURRENCY_LONG, TC_BLACK, SA_RIGHT);
+
+ DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT - 16, r.top + WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + 3, STR_GROUP_OCCUPANCY, TC_BLACK);
+ if (group_vehicles > 0) {
+ SetDParam(0, occupancy / group_vehicles);
+ DrawString(r.left + WD_FRAMERECT_LEFT + 8, r.right - WD_FRAMERECT_RIGHT - 16, r.top + WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + 3, STR_GROUP_OCCUPANCY_VALUE, TC_BLACK, SA_RIGHT);
+ }
+
+ break;
+ }
+
case WID_GL_LIST_GROUP: {
int y1 = r.top + WD_FRAMERECT_TOP;
int max = min(this->group_sb->GetPosition() + this->group_sb->GetCapacity(), this->groups.Length());
Index: src/hotkeys.cpp
===================================================================
--- src/hotkeys.cpp (revision 27386)
+++ src/hotkeys.cpp (working copy)
@@ -61,6 +61,8 @@
{"NUM_MINUS", WKC_NUM_MINUS},
{"=", WKC_EQUALS},
{"-", WKC_MINUS},
+ {"PAGE_UP", WKC_PAGEUP},
+ {"PAGE_DOWN", WKC_PAGEDOWN},
};
/**
Index: src/house.h
===================================================================
--- src/house.h (revision 27386)
+++ src/house.h (working copy)
@@ -143,4 +143,9 @@
return hs->grf_prop.override == INVALID_HOUSE_ID ? hid : hs->grf_prop.override;
}
+StringID GetHouseName(HouseID house, TileIndex tile = INVALID_TILE);
+void DrawHouseImage(HouseID house_id, int left, int top, int right, int bottom);
+void AddProducedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &produced);
+void AddAcceptedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &acceptance, uint32 *always_accepted = NULL);
+
#endif /* HOUSE_H */
Index: src/industry_cmd.cpp
===================================================================
--- src/industry_cmd.cpp (revision 27386)
+++ src/industry_cmd.cpp (working copy)
@@ -523,7 +523,7 @@
ResetIndustryConstructionStage(tile);
SetIndustryCompleted(tile);
SetIndustryGfx(tile, newgfx);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
}
@@ -556,7 +556,7 @@
}
SetAnimationFrame(tile, m);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
@@ -574,7 +574,7 @@
}
SetAnimationFrame(tile, m);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
@@ -588,7 +588,7 @@
}
SetAnimationFrame(tile, m);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
@@ -601,7 +601,7 @@
DeleteAnimatedTile(tile);
} else {
SetAnimationFrame(tile, m + 1);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
break;
@@ -627,7 +627,7 @@
}
SetAnimationFrame(tile, m);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
@@ -640,7 +640,7 @@
gfx = (gfx < 155) ? gfx + 1 : 148;
SetIndustryGfx(tile, gfx);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
@@ -659,7 +659,7 @@
} else {
SetAnimationFrame(tile, m);
SetIndustryGfx(tile, gfx);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
break;
@@ -685,7 +685,7 @@
byte m = (GetAnimationFrame(tile) + 1) | 0x40;
if (m > 0xC2) m = 0xC0;
SetAnimationFrame(tile, m);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
} else if (state >= 0x200 && state < 0x3A0) {
int i = (state < 0x220 || state >= 0x380) ? 7 : 3;
if (state & i) return;
@@ -693,7 +693,7 @@
byte m = (GetAnimationFrame(tile) & 0xBF) - 1;
if (m < 0x80) m = 0x82;
SetAnimationFrame(tile, m);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
}
@@ -723,7 +723,7 @@
StartStopIndustryTileAnimation(tile, IAT_CONSTRUCTION_STATE_CHANGE);
if (stage == INDUSTRY_COMPLETED) SetIndustryCompleted(tile);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
if (!IsIndustryCompleted(tile)) return;
@@ -818,7 +818,7 @@
if (newgfx != INDUSTRYTILE_NOANIM) {
ResetIndustryConstructionStage(tile);
SetIndustryGfx(tile, newgfx);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
return;
}
@@ -1021,7 +1021,7 @@
if (IsSuitableForFarmField(cur_tile, true)) {
MakeField(cur_tile, field_type, industry);
SetClearCounter(cur_tile, counter);
- MarkTileDirtyByTile(cur_tile);
+ MarkTileDirtyByTile(cur_tile, ZOOM_LVL_DRAW_MAP);
}
}
Index: src/industry_gui.cpp
===================================================================
--- src/industry_gui.cpp (revision 27386)
+++ src/industry_gui.cpp (working copy)
@@ -2285,6 +2285,14 @@
/* Only notify the smallmap window if it exists. In particular, do not
* bring it to the front to prevent messing up any nice layout of the user. */
InvalidateWindowClassesData(WC_SMALLMAP, 0);
+
+ /* Notify viewports too. */
+ Window *w;
+ FOR_ALL_WINDOWS_FROM_BACK(w) {
+ if (w->viewport != NULL)
+ if (w->viewport->zoom >= ZOOM_LVL_DRAW_MAP && w->viewport->map_type == VPMT_INDUSTRY)
+ w->InvalidateData();
+ }
}
/**
Index: src/infrastructure.cpp
new file mode 100644
===================================================================
--- src/infrastructure.cpp (revision 0)
+++ src/infrastructure.cpp (working copy)
@@ -0,0 +1,346 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file infrastructure.cpp Implementation of infrastructure sharing */
+
+#include "stdafx.h"
+#include "infrastructure_func.h"
+#include "train.h"
+#include "aircraft.h"
+#include "error.h"
+#include "vehicle_func.h"
+#include "station_base.h"
+#include "depot_base.h"
+#include "pbs.h"
+#include "signal_func.h"
+#include "window_func.h"
+#include "gui.h"
+#include "pathfinder/yapf/yapf_cache.h"
+#include "company_base.h"
+
+#include "table/strings.h"
+
+/**
+ * Helper function for transferring sharing fees
+ * @param v The vehicle involved
+ * @param infra_owner The owner of the infrastructure
+ * @param cost Amount to transfer as money fraction (shifted 8 bits to the left)
+ */
+static void PaySharingFee(Vehicle *v, Owner infra_owner, Money cost)
+{
+ Company *c = Company::Get(v->owner);
+ if (!_settings_game.economy.sharing_payment_in_debt) {
+ /* Do not allow fee payment to drop (money - loan) below 0. */
+ cost = min(cost, (c->money - c->current_loan) << 8);
+ if (cost <= 0) return;
+ }
+ v->profit_this_year -= cost;
+ SubtractMoneyFromCompanyFract(v->owner, CommandCost(EXPENSES_SHARING_COST, cost));
+ SubtractMoneyFromCompanyFract(infra_owner, CommandCost(EXPENSES_SHARING_INC, -cost));
+}
+
+/**
+ * Pay the fee for spending a single tick inside a station.
+ * @param v The vehicle that is using the station.
+ * @param st The station that it uses.
+ */
+void PayStationSharingFee(Vehicle *v, const Station *st)
+{
+ if (v->owner == st->owner || st->owner == OWNER_NONE || v->type == VEH_TRAIN) return;
+ Money cost = _settings_game.economy.sharing_fee[v->type];
+ PaySharingFee(v, st->owner, (cost << 8) / DAY_TICKS);
+}
+
+uint16 is2_GetWeight(Train *v)
+{
+ uint16 weight = (CargoSpec::Get(v->cargo_type)->weight * v->cargo.StoredCount() * FreightWagonMult(v->cargo_type)) / 16;
+ /* Vehicle weight is not added for articulated parts. */
+ if (!v->IsArticulatedPart()) {
+ weight += GetVehicleProperty(v, PROP_TRAIN_WEIGHT, RailVehInfo(v->engine_type)->weight);
+ }
+ /* Powered wagons have extra weight added. */
+ if (HasBit(v->flags, VRF_POWEREDWAGON)) {
+ weight += RailVehInfo(v->gcache.first_engine)->pow_wag_weight;
+ }
+ return weight;
+}
+
+
+/**
+ * Pay the daily fee for trains on foreign tracks.
+ * @param v The vehicle to pay the fee for.
+ */
+void PayDailyTrackSharingFee(Train *v)
+{
+ Owner owner = GetTileOwner(v->tile);
+ if (owner == v->owner) return;
+ Money cost = _settings_game.economy.sharing_fee[VEH_TRAIN] << 8;
+ /* Cost is calculated per 1000 tonnes */
+ cost = cost * is2_GetWeight(v) / 1000;
+ /* Only pay the required fraction */
+ cost = cost * v->running_ticks / DAY_TICKS;
+ if (cost != 0) PaySharingFee(v, owner, cost);
+}
+
+/**
+ * Check whether a vehicle is in an allowed position.
+ * @param v The vehicle to check.
+ * @param owner Owner whose infrastructure is not allowed, because the company will be removed. Ignored if INVALID_OWNER.
+ * @return True if the vehicle is compeletely in an allowed position.
+ */
+static bool VehiclePositionIsAllowed(const Vehicle *v, Owner owner = INVALID_OWNER)
+{
+ switch (v->type) {
+ case VEH_TRAIN:
+ for (const Vehicle *u = v; u != NULL; u = u->Next()) {
+ if (!IsInfraTileUsageAllowed(VEH_TRAIN, v->owner, u->tile) || GetTileOwner(u->tile) == owner) return false;
+ }
+ return true;
+ case VEH_ROAD:
+ for (const Vehicle *u = v; u != NULL; u = u->Next()) {
+ if (IsRoadDepotTile(u->tile) || IsStandardRoadStopTile(u->tile)) {
+ if (!IsInfraTileUsageAllowed(VEH_ROAD, v->owner, u->tile) || GetTileOwner(u->tile) == owner) return false;
+ }
+ }
+ return true;
+ case VEH_SHIP:
+ if (IsShipDepotTile(v->tile) && v->IsStoppedInDepot()) {
+ if (!IsInfraTileUsageAllowed(VEH_SHIP, v->owner, v->tile) || GetTileOwner(v->tile) == owner) return false;
+ }
+ return true;
+ case VEH_AIRCRAFT: {
+ const Aircraft *a = Aircraft::From(v);
+ if (a->state != FLYING && Station::IsValidID(a->targetairport)) {
+ Owner station_owner = Station::Get(a->targetairport)->owner;
+ if (!IsInfraUsageAllowed(VEH_AIRCRAFT, a->owner, station_owner) || station_owner == owner) return false;
+ }
+ return true;
+ }
+ default: return true;
+ }
+}
+
+/**
+ * Check whether an order has a destination that is allowed.
+ * I.e. it refers to a station/depot/waypoint the vehicle is allowed to visit.
+ * @param order The order to check
+ * @param v The vehicle this order belongs to.
+ * @param owner Owner whose infrastructure is not allowed, because the company will be removed. Ignored if INVALID_OWNER.
+ * @return True if the order has an allowed destination.
+ */
+static bool OrderDestinationIsAllowed(const Order *order, const Vehicle *v, Owner owner = INVALID_OWNER)
+{
+ Owner dest_owner;
+ switch (order->GetType()) {
+ case OT_GOTO_STATION:
+ case OT_GOTO_WAYPOINT: dest_owner = BaseStation::Get(order->GetDestination())->owner; break;
+ case OT_GOTO_DEPOT: dest_owner = (v->type == VEH_AIRCRAFT) ? Station::Get(order->GetDestination())->owner : GetTileOwner(Depot::Get(order->GetDestination())->xy); break;
+ case OT_LOADING: dest_owner = Station::Get(v->last_station_visited)->owner; break;
+ default: return true;
+ }
+ return dest_owner != owner && IsInfraUsageAllowed(v->type, v->owner, dest_owner);
+}
+
+/**
+ * Sell a vehicle, no matter where it may be.
+ * @param v The vehicle to sell
+ * @param give_money Do we actually need to give money to the vehicle owner?
+ */
+static void RemoveAndSellVehicle(Vehicle *v, bool give_money)
+{
+ assert(v->Previous() == NULL);
+
+ if (give_money) {
+ /* compute total value and give that to the owner */
+ Money value = 0;
+ for (Vehicle *u = v->First(); u != NULL; u = u->Next()) {
+ value += v->value;
+ }
+ CompanyID old = _current_company;
+ _current_company = v->owner;
+ SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES, -value));
+ _current_company = old;
+ }
+
+ /* take special measures for trains, but not when sharing is disabled or when the train is a free wagon chain */
+ if (_settings_game.economy.infrastructure_sharing[VEH_TRAIN] && v->type == VEH_TRAIN && Train::From(v)->IsFrontEngine()) {
+ DeleteVisibleTrain(Train::From(v));
+ } else {
+ delete v;
+ }
+}
+
+/**
+ * Check all path reservations, and reserve a new path if the current path is invalid.
+ */
+static void FixAllReservations()
+{
+ /* if this function is called, we can safely assume that sharing of rails is being switched off */
+ assert(!_settings_game.economy.infrastructure_sharing[VEH_TRAIN]);
+ Train *v;
+ FOR_ALL_TRAINS(v) {
+ if (!v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0) continue;
+ /* It might happen that the train reserved additional tracks,
+ * but FollowTrainReservation can't detect those because they are no longer reachable.
+ * detect this by first finding the end of the reservation,
+ * then switch sharing on and try again. If these two ends differ,
+ * unreserve the path, switch sharing off and try to reserve a new path */
+ PBSTileInfo end_tile_info = FollowTrainReservation(v);
+
+ /* first do a quick test to determine whether the next tile has any reservation at all */
+ TileIndex next_tile = end_tile_info.tile + TileOffsByDiagDir(TrackdirToExitdir(end_tile_info.trackdir));
+ /* If the next tile doesn't have a reservation at all, the reservation surely ends here. Thus all is well */
+ if (GetReservedTrackbits(next_tile) == TRACK_BIT_NONE) continue;
+
+ /* change sharing setting temporarily */
+ _settings_game.economy.infrastructure_sharing[VEH_TRAIN] = true;
+ PBSTileInfo end_tile_info2 = FollowTrainReservation(v);
+ /* if these two reservation ends differ, unreserve the path and try to reserve a new path */
+ if (end_tile_info.tile != end_tile_info2.tile || end_tile_info.trackdir != end_tile_info2.trackdir) {
+ FreeTrainTrackReservation(v);
+ _settings_game.economy.infrastructure_sharing[VEH_TRAIN] = false;
+ TryPathReserve(v, true);
+ } else {
+ _settings_game.economy.infrastructure_sharing[VEH_TRAIN] = false;
+ }
+ }
+}
+
+/**
+ * Check if a sharing change is possible.
+ * If vehicles are still on others' infrastructure or using others' stations,
+ * The change is not possible and false is returned.
+ * @param type The type of vehicle whose setting will be changed.
+ * @return True if the change can take place, false otherwise.
+ */
+bool CheckSharingChangePossible(VehicleType type)
+{
+ if (type != VEH_AIRCRAFT) YapfNotifyTrackLayoutChange(INVALID_TILE, INVALID_TRACK);
+ /* Only do something when sharing is being disabled */
+ if (_settings_game.economy.infrastructure_sharing[type]) return true;
+
+ StringID error_message = STR_NULL;
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if (type != v->type) continue;
+ if (v->Previous() != NULL) continue;
+
+ /* Check vehicle positiion */
+ if (!VehiclePositionIsAllowed(v)) {
+ error_message = STR_CONFIG_SETTING_SHARING_USED_BY_VEHICLES;
+ /* Break immediately, this error message takes precedence over the others. */
+ break;
+ }
+
+ /* Check current order */
+ if (!OrderDestinationIsAllowed(&v->current_order, v)) {
+ error_message = STR_CONFIG_SETTING_SHARING_ORDERS_TO_OTHERS;
+ }
+
+ /* Check order list */
+ if (v->FirstShared() != v) continue;
+ Order *o;
+ FOR_VEHICLE_ORDERS(v, o) {
+ if (!OrderDestinationIsAllowed(o, v)) {
+ error_message = STR_CONFIG_SETTING_SHARING_ORDERS_TO_OTHERS;
+ }
+ }
+ }
+
+ if (error_message != STR_NULL) {
+ ShowErrorMessage(error_message, INVALID_STRING_ID, WL_ERROR);
+ return false;
+ }
+
+ if (type == VEH_TRAIN) FixAllReservations();
+
+ return true;
+}
+
+/**
+ * Handle the removal (through reset_company or bankruptcy) of a company.
+ * i.e. remove all vehicles owned by that company or on its infrastructure,
+ * and delete all now-invalid orders.
+ * @param Owner the company to be removed.
+ */
+void HandleSharingCompanyDeletion(Owner owner)
+{
+ YapfNotifyTrackLayoutChange(INVALID_TILE, INVALID_TRACK);
+
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if (!IsCompanyBuildableVehicleType(v) || v->Previous() != NULL) continue;
+ /* vehicle position */
+ if (v->owner == owner || !VehiclePositionIsAllowed(v, owner)) {
+ RemoveAndSellVehicle(v, v->owner != owner);
+ continue;
+ }
+ /* current order */
+ if (!OrderDestinationIsAllowed(&v->current_order, v, owner)) {
+ if (v->current_order.IsType(OT_LOADING)) {
+ v->LeaveStation();
+ } else {
+ v->current_order.MakeDummy();
+ }
+ SetWindowDirty(WC_VEHICLE_VIEW, v->index);
+ }
+
+ /* order list */
+ if (v->FirstShared() != v) continue;
+
+ Order *o;
+ int id = -1;
+ FOR_VEHICLE_ORDERS(v, o) {
+ id++;
+ if (OrderDestinationIsAllowed(o, v, owner)) continue;
+
+ o->MakeDummy();
+ for (const Vehicle *w = v; w != NULL; w = w->NextShared()) {
+ /* In GUI, simulate by removing the order and adding it back */
+ InvalidateVehicleOrder(w, id | (INVALID_VEH_ORDER_ID << 8));
+ InvalidateVehicleOrder(w, (id << 8) | INVALID_VEH_ORDER_ID);
+ }
+ }
+ }
+}
+
+/**
+ * Update all block signals on the map.
+ * To be called after the setting for sharing of rails changes.
+ * @param owner Owner whose signals to update. If INVALID_OWNER, update everything.
+ */
+void UpdateAllBlockSignals(Owner owner)
+{
+ Owner last_owner = INVALID_OWNER;
+ TileIndex tile = 0;
+ do {
+ if (IsTileType(tile, MP_RAILWAY) && HasSignals(tile)) {
+ Owner track_owner = GetTileOwner(tile);
+ if (owner != INVALID_OWNER && track_owner != owner) continue;
+
+ if (!IsOneSignalBlock(track_owner, last_owner)) {
+ /* Cannot update signals of two different companies in one run,
+ * if these signal blocks are not joined */
+ UpdateSignalsInBuffer();
+ last_owner = track_owner;
+ }
+ TrackBits bits = GetTrackBits(tile);
+ do {
+ Track track = RemoveFirstTrack(&bits);
+ if (HasSignalOnTrack(tile, track)) {
+ AddTrackToSignalBuffer(tile, track, track_owner);
+ }
+ } while (bits != TRACK_BIT_NONE);
+ } else if (IsLevelCrossingTile(tile) && (owner == INVALID_OWNER || GetTileOwner(tile) == owner)) {
+ UpdateLevelCrossing(tile);
+ }
+ } while (++tile != MapSize());
+
+ UpdateSignalsInBuffer();
+}
Index: src/infrastructure_func.h
new file mode 100644
===================================================================
--- src/infrastructure_func.h (revision 0)
+++ src/infrastructure_func.h (working copy)
@@ -0,0 +1,107 @@
+
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file infrastructure_func.h Functions for access to (shared) infrastructure */
+
+#ifndef INFRASTRUCTURE_FUNC_H
+#define INFRASTRUCTURE_FUNC_H
+
+#include "vehicle_base.h"
+#include "settings_type.h"
+#include "command_type.h"
+#include "company_func.h"
+#include "tile_map.h"
+
+void PayStationSharingFee(Vehicle *v, const Station *st);
+void PayDailyTrackSharingFee(Train *v);
+
+bool CheckSharingChangePossible(VehicleType type);
+void HandleSharingCompanyDeletion(Owner owner);
+void UpdateAllBlockSignals(Owner owner = INVALID_OWNER);
+
+/**
+ * Check whether a vehicle of a given owner and type can use the infrastrucutre of a given company.
+ * @param type Type of vehicle we are talking about.
+ * @param veh_owner Owner of the vehicle in question.
+ * @param infra_owner The owner of the infrastructure.
+ * @return True if infrastructure usage is allowed, false otherwise.
+ */
+static inline bool IsInfraUsageAllowed(VehicleType type, Owner veh_owner, Owner infra_owner)
+{
+ return infra_owner == veh_owner || infra_owner == OWNER_NONE || _settings_game.economy.infrastructure_sharing[type];
+}
+
+/**
+ * Check whether a vehicle of a given owner and type can use the infrastrucutre on a given tile.
+ * @param type Type of vehicle we are talking about.
+ * @param veh_owner Owner of the vehicle in question.
+ * @param tile The tile that may or may not be used.
+ * @return True if infrastructure usage is allowed, false otherwise.
+ */
+static inline bool IsInfraTileUsageAllowed(VehicleType type, Owner veh_owner, TileIndex tile)
+{
+ return IsInfraUsageAllowed(type, veh_owner, GetTileOwner(tile));
+}
+
+/**
+ * Is a vehicle owned by _current_company allowed to use the infrastructure of infra_owner?
+ * If this is not allowed, this function provides the appropriate error message.
+ * @see IsInfraUsageAllowed
+ * @see CheckOwnership
+ * @param type Type of vehicle.
+ * @param infra_owner Owner of the infrastructure.
+ * @param tile Tile of the infrastructure.
+ * @return CommandCost indicating success or failure.
+ */
+static inline CommandCost CheckInfraUsageAllowed(VehicleType type, Owner infra_owner, TileIndex tile = 0)
+{
+ if (infra_owner == OWNER_NONE || _settings_game.economy.infrastructure_sharing[type]) return CommandCost();
+ return CheckOwnership(infra_owner, tile);
+}
+
+/**
+ * Check whether a given company can control this vehicle.
+ * Controlling a vehicle means permission to start, stop or reverse it or to make it ignore signals.
+ * @param v The vehicle which may or may not be controlled.
+ * @param o The company which may or may not control this vehicle.
+ * @return True if the given company is allowed to control this vehicle.
+ */
+static inline bool IsVehicleControlAllowed(const Vehicle *v, Owner o)
+{
+ return v->owner == o || (v->type == VEH_TRAIN && IsTileOwner(v->tile, o));
+}
+
+/**
+ * Check whether _current_company can control this vehicle.
+ * If this is not allowed, this function provides the appropriate error message.
+ * @see IsVehicleControlAllowed
+ * @param v The vehicle which may or may not be controlled.
+ * @return CommandCost indicating success or failure.
+ */
+static inline CommandCost CheckVehicleControlAllowed(const Vehicle *v)
+{
+ if (v->type == VEH_TRAIN && IsTileOwner(v->tile, _current_company)) return CommandCost();
+ return CheckOwnership(v->owner);
+}
+
+/**
+ * Do signal states propagate from the tracks of one owner to the other?
+ * @note This function should be consistent, so if it returns true for (a, b) and (b, c),
+ * it should also return true for (a, c).
+ * @param o1 First track owner.
+ * @param o2 Second track owner.
+ * @return True if tracks of the two owners are part of the same signal block.
+ */
+static inline bool IsOneSignalBlock(Owner o1, Owner o2)
+{
+ return o1 == o2 || _settings_game.economy.infrastructure_sharing[VEH_TRAIN];
+}
+
+#endif /* INFRASTRUCTURE_FUNC_H */
Index: src/lang/danish.txt
===================================================================
--- src/lang/danish.txt (revision 27386)
+++ src/lang/danish.txt (working copy)
@@ -376,6 +376,7 @@
STR_SETTINGS_MENU_SCRIPT_SETTINGS :AI/spilscript-indstillinger
STR_SETTINGS_MENU_NEWGRF_SETTINGS :NewGRF indstillinger
STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS :Gennemsigtighedsvalg
+STR_SETTINGS_MENU_ZONING :Zoning
STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Bynavne vist
STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED :Stationsnavne vist
STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED :Waypointnavne vist
Index: src/lang/english.txt
===================================================================
--- src/lang/english.txt (revision 27386)
+++ src/lang/english.txt (working copy)
@@ -273,6 +273,7 @@
STR_MEASURE_AREA :{BLACK}Area: {NUM} x {NUM}
STR_MEASURE_LENGTH_HEIGHTDIFF :{BLACK}Length: {NUM}{}Height difference: {HEIGHT}
STR_MEASURE_AREA_HEIGHTDIFF :{BLACK}Area: {NUM} x {NUM}{}Height difference: {HEIGHT}
+STR_MEASURE_DIST_HEIGHTDIFF :{BLACK}Manhattan Distance: {NUM}{}Bird Fly Distance: {NUM}{}Distance from the nearest edge: {NUM}{}Height from sea level: {HEIGHT}{}Height difference: {HEIGHT}
# These are used in buttons
@@ -359,6 +360,7 @@
STR_SCENEDIT_TOOLBAR_PLANT_TREES :{BLACK}Plant trees. Shift toggles building/showing cost estimate
STR_SCENEDIT_TOOLBAR_PLACE_SIGN :{BLACK}Place sign
STR_SCENEDIT_TOOLBAR_PLACE_OBJECT :{BLACK}Place object. Shift toggles building/showing cost estimate
+STR_SCENEDIT_TOOLBAR_PLACE_HOUSE :{BLACK}Place house
############ range for SE file menu starts
STR_SCENEDIT_FILE_MENU_SAVE_SCENARIO :Save scenario
@@ -376,6 +378,7 @@
STR_SETTINGS_MENU_SCRIPT_SETTINGS :AI/Game script settings
STR_SETTINGS_MENU_NEWGRF_SETTINGS :NewGRF settings
STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS :Transparency options
+STR_SETTINGS_MENU_ZONING :Zoning
STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Town names displayed
STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED :Station names displayed
STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED :Waypoint names displayed
@@ -400,6 +403,7 @@
STR_MAP_MENU_EXTRA_VIEW_PORT :Extra viewport
STR_MAP_MENU_LINGRAPH_LEGEND :Cargo Flow Legend
STR_MAP_MENU_SIGN_LIST :Sign list
+STR_MAP_MENU_PLAN_LIST :Plan list
############ range for town menu starts
STR_TOWN_MENU_TOWN_DIRECTORY :Town directory
@@ -753,6 +757,7 @@
STR_SMALLMAP_LINKSTATS :{TINY_FONT}{STRING}
STR_SMALLMAP_COMPANY :{TINY_FONT}{COMPANY}
STR_SMALLMAP_TOWN :{TINY_FONT}{WHITE}{TOWN}
+STR_SMALLMAP_SCREENSHOT :{BLACK}Screenshot
STR_SMALLMAP_DISABLE_ALL :{BLACK}Disable all
STR_SMALLMAP_ENABLE_ALL :{BLACK}Enable all
STR_SMALLMAP_SHOW_HEIGHT :{BLACK}Show height
@@ -1220,6 +1225,7 @@
STR_CONFIG_SETTING_STOP_LOCATION_NEAR_END :near end
STR_CONFIG_SETTING_STOP_LOCATION_MIDDLE :middle
STR_CONFIG_SETTING_STOP_LOCATION_FAR_END :far end
+STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS :Enable improved breakdowns: {STRING2}
STR_CONFIG_SETTING_AUTOSCROLL :Pan window when mouse is at the edge: {STRING2}
STR_CONFIG_SETTING_AUTOSCROLL_HELPTEXT :When enabled, viewports will start to scroll when the mouse is near the edge of the window
STR_CONFIG_SETTING_AUTOSCROLL_DISABLED :Disabled
@@ -1283,7 +1289,7 @@
STR_CONFIG_SETTING_HOVER_DELAY_HELPTEXT :Delay before tooltips are displayed when hovering the mouse over some interface element. Alternatively tooltips are bound to the right mouse button when this value is set to 0.
STR_CONFIG_SETTING_HOVER_DELAY_VALUE :Hover for {COMMA} millisecond{P 0 "" s}
STR_CONFIG_SETTING_HOVER_DELAY_DISABLED :Right click
-STR_CONFIG_SETTING_POPULATION_IN_LABEL :Show town population in the town name label: {STRING2}
+STR_CONFIG_SETTING_POPULATION_IN_LABEL :Show extra information in the town name label: {STRING2}
STR_CONFIG_SETTING_POPULATION_IN_LABEL_HELPTEXT :Display the population of towns in their label on the map
STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS :Thickness of lines in graphs: {STRING2}
STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS_HELPTEXT :Width of the line in the graphs. A thin line is more precisely readable, a thicker line is easier to see and colours are easier to distinguish
@@ -1292,6 +1298,8 @@
STR_CONFIG_SETTING_LANDSCAPE_HELPTEXT :Landscapes define basic gameplay scenarios with different cargos and town growth requirements. NewGRF and Game Scripts allow finer control though
STR_CONFIG_SETTING_LAND_GENERATOR :Land generator: {STRING2}
STR_CONFIG_SETTING_LAND_GENERATOR_HELPTEXT :The original generator depends on the base graphics set, and composes fixed landscape shapes. TerraGenesis is a Perlin noise based generator with finer control settings
+STR_CONFIG_SETTING_MAX_SIGNAL_EVALUATIONS :Maximum number of programmable signal changes permitted at once: {STRING2}
+STR_CONFIG_SETTING_MAX_SIGNAL_EVALUATIONS_HELPTEXT :Sets the maximum number of programmable signal changes permitted at once
STR_CONFIG_SETTING_LAND_GENERATOR_ORIGINAL :Original
STR_CONFIG_SETTING_LAND_GENERATOR_TERRA_GENESIS :TerraGenesis
STR_CONFIG_SETTING_TERRAIN_TYPE :Terrain type: {STRING2}
@@ -1363,6 +1371,32 @@
STR_CONFIG_SETTING_OSK_ACTIVATION_DOUBLE_CLICK :Double click
STR_CONFIG_SETTING_OSK_ACTIVATION_SINGLE_CLICK_FOCUS :Single click (when focussed)
STR_CONFIG_SETTING_OSK_ACTIVATION_SINGLE_CLICK :Single click (immediately)
+STR_CONFIG_SETTING_SHOW_VEHICLE_ROUTE_STEPS :Show the vehicle's route steps: {STRING2}
+
+STR_CONFIG_SETTING_VIEWPORT_MAP_SCAN_SURROUNDINGS :Scan surroundings (better for high zoom out levels): {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SLOPES :Show slopes: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_BRIDGES :Show bridges: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_TUNNELS :Show tunnels: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SCROLLING_VP :Show scrolling viewport: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SCROLLING_VP_NOTHING :No
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SCROLLING_VP_CONTOUR :Contour
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SCROLLING_VP_BLEND :Blend with some white (32bpp only)
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SCROLLING_VP_ALL :Blend + contour
+STR_CONFIG_SETTING_VIEWPORT_MAP_USE_OWNER_COLOUR_BRIDGE_TUNNEL :Use owner's colour for bridges and tunnels: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_DEFAULT_MODE :Default mode: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_DEFAULT_MODE_VEGETATION :Vegetation
+STR_CONFIG_SETTING_VIEWPORT_MAP_DEFAULT_MODE_OWNER :Owner
+STR_CONFIG_SETTING_VIEWPORT_MAP_DEFAULT_MODE_INDUSTRY :Industry
+STR_CONFIG_SETTING_VIEWPORT_MAP_ACTION_DBLCLICK :Function of double-click: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_ACTION_DBLCLICK_DO_NOTHING :Do nothing
+STR_CONFIG_SETTING_VIEWPORT_MAP_ACTION_DBLCLICK_ZOOM_MAIN :Zoom in directly to 1X
+STR_CONFIG_SETTING_VIEWPORT_MAP_ACTION_DBLCLICK_NEW_EXTRA :Open an extra viewport
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_VEHICLE_ROUTE :Show the vehicle's route: {STRING2}
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_VEHICLE_ROUTE_NO :No
+STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_VEHICLE_ROUTE_SIMPLE :Simple
+STR_CONFIG_SETTING_VIEWPORT_MAP_DRAW_ROUTE_DASH :Drawing style of vehicle's route: {STRING}
+STR_CONFIG_SETTING_VIEWPORT_MAP_DRAW_ROUTE_DASH_VALUE :dashed lines of {COMMA} pixel{P "" s}
+STR_CONFIG_SETTING_VIEWPORT_MAP_DRAW_ROUTE_DASH_DISABLED :plain lines
STR_CONFIG_SETTING_RIGHT_MOUSE_BTN_EMU :Right-click emulation: {STRING2}
STR_CONFIG_SETTING_RIGHT_MOUSE_BTN_EMU_HELPTEXT :Select the method to emulate right mouse-button clicks
@@ -1394,8 +1428,28 @@
STR_CONFIG_SETTING_ADVANCED_VEHICLE_LISTS_HELPTEXT :Enable usage of the advanced vehicle lists for grouping vehicles
STR_CONFIG_SETTING_LOADING_INDICATORS :Use loading indicators: {STRING2}
STR_CONFIG_SETTING_LOADING_INDICATORS_HELPTEXT :Select whether loading indicators are displayed above loading or unloading vehicles
+STR_CONFIG_SETTING_TIMETABLE_AUTOMATED :Automatically manage timetables: {STRING2}
+STR_CONFIG_SETTING_TIMETABLE_AUTOMATED_HELPTEXT :Whether to enable automatic timetables
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_SEPARATION :Use timetable to ensure vehicle separation: {STRING2}
+STR_CONFIG_SETTING_TIMETABLE_SEPARATION_HELPTEXT :Select whether to ensure separation of vehicles when using automatic timetables
+STR_CONFIG_SETTING_TIMETABLE_SEPARATION_RATE :Vehicle separation factor: {STRING2}
+STR_CONFIG_SETTING_TIMETABLE_SEPARATION_RATE_HELPTEXT :How much of the vehicle separation timetable change to apply at each step
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_QUICKGOTO :Quick creation of vehicle orders: {STRING2}
@@ -1405,6 +1459,7 @@
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_FIRST :First available
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_LAST :Last available
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_MOST_USED :Most used
+
STR_CONFIG_SETTING_SHOW_TRACK_RESERVATION :Show path reservations for tracks: {STRING2}
STR_CONFIG_SETTING_SHOW_TRACK_RESERVATION_HELPTEXT :Give reserved tracks a different colour to assist in problems with trains refusing to enter path-based blocks
STR_CONFIG_SETTING_PERSISTENT_BUILDINGTOOLS :Keep building tools active after usage: {STRING2}
@@ -1460,6 +1515,18 @@
STR_CONFIG_SETTING_SCRIPT_MAX_OPCODES :#opcodes before scripts are suspended: {STRING2}
STR_CONFIG_SETTING_SCRIPT_MAX_OPCODES_HELPTEXT :Maximum number of computation steps that a script can take in one turn
+STR_CONFIG_SETTING_SHARING_RAIL :Enable sharing of railways: {STRING2}
+STR_CONFIG_SETTING_SHARING_ROAD :Enable sharing of road stops and depots: {STRING2}
+STR_CONFIG_SETTING_SHARING_WATER :Enable sharing of docks and ship depots: {STRING2}
+STR_CONFIG_SETTING_SHARING_AIR :Enable sharing of airports: {STRING2}
+STR_CONFIG_SETTING_SHARING_FEE_RAIL :Daily track toll for trains: {STRING2} per 1000 tonnes
+STR_CONFIG_SETTING_SHARING_FEE_ROAD :Stopping fee for road vehicles: {STRING2} per day
+STR_CONFIG_SETTING_SHARING_FEE_WATER :Docking fee for ships: {STRING2} per day
+STR_CONFIG_SETTING_SHARING_FEE_AIR :Terminal fee for aircraft: {STRING2} per day
+STR_CONFIG_SETTING_SHARING_PAYMENT_IN_DEBT :Allow companies in debt to pay sharing fees: {STRING2}
+STR_CONFIG_SETTING_SHARING_USED_BY_VEHICLES :Can't change this setting, vehicles are using shared infrastructure.
+STR_CONFIG_SETTING_SHARING_ORDERS_TO_OTHERS :Can't change this setting, vehicles have orders to destinations of others.
+
STR_CONFIG_SETTING_SERVINT_ISPERCENT :Service intervals are in percents: {STRING2}
STR_CONFIG_SETTING_SERVINT_ISPERCENT_HELPTEXT :Choose whether servicing of vehicles is triggered by the time passed since last service or by reliability dropping by a certain percentage of the maximum reliability
STR_CONFIG_SETTING_SERVINT_TRAINS :Default service interval for trains: {STRING2}
@@ -1523,6 +1590,10 @@
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_DAY_LENGTH_FACTOR :Day length factor: {STRING2}
+STR_CONFIG_SETTING_DAY_LENGTH_FACTOR_HELPTEXT :Game pace is slowed by this factor
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}
@@ -1561,6 +1632,7 @@
STR_CONFIG_SETTING_TOWN_FOUNDING_FORBIDDEN :Forbidden
STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED :Allowed
STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED_CUSTOM_LAYOUT :Allowed, custom town layout
+STR_CONFIG_SETTING_TOWN_CARGO_FACTOR :Town cargo generation factor (less < 0 < more): {STRING2}
STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT :In game placement of trees: {STRING2}
STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_HELPTEXT :Control random appearance of trees during the game. This might affect industries which rely on tree growth, for example lumber mills
@@ -1590,6 +1662,10 @@
STR_CONFIG_SETTING_ZOOM_LVL_OUT_2X :2x
STR_CONFIG_SETTING_ZOOM_LVL_OUT_4X :4x
STR_CONFIG_SETTING_ZOOM_LVL_OUT_8X :8x
+STR_CONFIG_SETTING_ZOOM_LVL_OUT_16X :16x
+STR_CONFIG_SETTING_ZOOM_LVL_OUT_32X :32x
+STR_CONFIG_SETTING_ZOOM_LVL_OUT_64X :64x
+STR_CONFIG_SETTING_ZOOM_LVL_OUT_128X :128x
STR_CONFIG_SETTING_TOWN_GROWTH :Town growth speed: {STRING2}
STR_CONFIG_SETTING_TOWN_GROWTH_HELPTEXT :Speed of town growth
STR_CONFIG_SETTING_TOWN_GROWTH_NONE :None
@@ -1671,6 +1747,8 @@
STR_CONFIG_SETTING_INTERFACE_GENERAL :{ORANGE}General
STR_CONFIG_SETTING_INTERFACE_VIEWPORTS :{ORANGE}Viewports
STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION :{ORANGE}Construction
+STR_CONFIG_SETTING_INTERFACE_DEPARTUREBOARDS :{ORANGE}Departure boards
+STR_CONFIG_SETTING_INTERFACE_WALLCLOCK :{ORANGE}Wall clock
STR_CONFIG_SETTING_ADVISORS :{ORANGE}News / Advisors
STR_CONFIG_SETTING_COMPANY :{ORANGE}Company
STR_CONFIG_SETTING_ACCOUNTING :{ORANGE}Accounting
@@ -1687,6 +1765,8 @@
STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST :{ORANGE}Cargo distribution
STR_CONFIG_SETTING_AI :{ORANGE}Competitors
STR_CONFIG_SETTING_AI_NPC :{ORANGE}Computer players
+STR_CONFIG_SETTING_VIEWPORT_MAP_OPTIONS :{ORANGE}Map mode
+STR_CONFIG_SETTING_SHARING :{ORANGE}Infrastructure sharing
STR_CONFIG_SETTING_PATHFINDER_OPF :Original
STR_CONFIG_SETTING_PATHFINDER_NPF :NPF
@@ -1703,6 +1783,17 @@
STR_CONFIG_SETTING_QUERY_CAPTION :{WHITE}Change setting value
+STR_CONFIG_SETTING_ADJACENT_CROSSINGS :Close adjacent level crossings: {STRING2}
+STR_CONFIG_SETTING_ADJACENT_CROSSINGS_HELPTEXT :Closes all adjacent level crossings on parallel tracks whenever one or more is occupied
+
+STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE :Pay for repairing vehicle: {STRING2}
+STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE_HELPTEXT :Pay for repairing vehicle
+STR_CONFIG_SETTING_REPAIR_COST :Cost of repairing vehicle: 1/{STRING2} of total cost
+STR_CONFIG_SETTING_REPAIR_COST_HELPTEXT :Cost of repairing vehicle
+
+STR_CONFIG_OCCUPANCY_SMOOTHNESS :Smoothness of order occupancy measurement: {STRING2}
+STR_CONFIG_OCCUPANCY_SMOOTHNESS_HELPTEXT :0% sets the measurement to the most recent value, 100% leaves it unchanged
+
# Config errors
STR_CONFIG_ERROR :{WHITE}Error with the configuration file...
STR_CONFIG_ERROR_ARRAY :{WHITE}... error in array '{RAW_STRING}'
@@ -2275,6 +2366,7 @@
STR_TRANSPARENT_STRUCTURES_TOOLTIP :{BLACK}Toggle transparency for structures like lighthouses and antennas. Ctrl+Click to lock
STR_TRANSPARENT_CATENARY_TOOLTIP :{BLACK}Toggle transparency for catenary. Ctrl+Click to lock
STR_TRANSPARENT_LOADING_TOOLTIP :{BLACK}Toggle transparency for loading indicators. Ctrl+Click to lock
+STR_TRANSPARENT_TUNNELS_TOOLTIP :{BLACK}Toggle transparency for vehicles in tunnels. Ctrl+Click to lock.
STR_TRANSPARENT_INVISIBLE_TOOLTIP :{BLACK}Set objects invisible instead of transparent
# Linkgraph legend window
@@ -2357,12 +2449,14 @@
STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP :{BLACK}Entry Signal (semaphore){}Green as long as there is one or more green exit-signal from the following section of track. Otherwise it shows red
STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP :{BLACK}Exit Signal (semaphore){}Behaves in the same way as a block signal but is necessary to trigger the correct colour on entry & combo pre-signals
STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP :{BLACK}Combo Signal (semaphore){}The combo signal simply acts as both an entry and exit signal. This allows you to build large "trees" of pre-signals
+STR_BUILD_SIGNAL_SEMAPHORE_PROG_TOOLTIP :{BLACK}Programmable-Signal (semaphore){}The programmable signal is a combo-signal which can be programmed to behave in complex ways.
STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP :{BLACK}Path Signal (semaphore){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. Standard path signals can be passed from the back side
STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP :{BLACK}One-way Path Signal (semaphore){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. One-way path signals can't be passed from the back side
STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP :{BLACK}Block Signal (electric){}This is the most basic type of signal, allowing only one train to be in the same block at the same time
STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP :{BLACK}Entry Signal (electric){}Green as long as there is one or more green exit-signal from the following section of track. Otherwise it shows red
STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP :{BLACK}Exit Signal (electric){}Behaves in the same way as a block signal but is necessary to trigger the correct colour on entry & combo pre-signals
STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP :{BLACK}Combo Signal (electric){}The combo signal simply acts as both an entry and exit signal. This allows you to build large "trees" of pre-signals
+STR_BUILD_SIGNAL_ELECTRIC_PROG_TOOLTIP :{BLACK}Programmable-Signal (electric){}The programmable signal is a combo-signal which can be programmed to behave in complex ways.
STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP :{BLACK}Path Signal (electric){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. Standard path signals can be passed from the back side
STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP :{BLACK}One-way Path Signal (electric){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. One-way path signals can't be passed from the back side
STR_BUILD_SIGNAL_CONVERT_TOOLTIP :{BLACK}Signal Convert{}When selected, clicking an existing signal will convert it to the selected signal type and variant. Ctrl+Click will toggle the existing variant. Shift+Click shows estimated conversion cost
@@ -2370,6 +2464,166 @@
STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_DECREASE_TOOLTIP :{BLACK}Decrease dragging signal density
STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP :{BLACK}Increase dragging signal density
+# Tracerestrict GUI
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_EQUALS :is
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_NOT_EQUALS :is not
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_THAN :<
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_EQUALS :<=
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_THAN :>
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_EQUALS :>=
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_EQUALS :can carry
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_NOT_EQUALS :can't carry
+STR_TRACE_RESTRICT_CONDITIONAL_IF :If
+STR_TRACE_RESTRICT_CONDITIONAL_ELIF :Else if
+STR_TRACE_RESTRICT_CONDITIONAL_ORIF :Or if
+STR_TRACE_RESTRICT_CONDITIONAL_ELSE :Else
+STR_TRACE_RESTRICT_CONDITIONAL_ENDIF :End if
+STR_TRACE_RESTRICT_VARIABLE_TRAIN_LENGTH :train length
+STR_TRACE_RESTRICT_VARIABLE_MAX_SPEED :max speed
+STR_TRACE_RESTRICT_VARIABLE_CURRENT_ORDER :current order
+STR_TRACE_RESTRICT_VARIABLE_NEXT_ORDER :next order
+STR_TRACE_RESTRICT_VARIABLE_LAST_VISITED_STATION :last visited station
+STR_TRACE_RESTRICT_VARIABLE_CARGO :cargo
+STR_TRACE_RESTRICT_VARIABLE_ENTRY_DIRECTION :entry direction
+STR_TRACE_RESTRICT_VARIABLE_PBS_ENTRY_SIGNAL :PBS entry signal
+STR_TRACE_RESTRICT_VARIABLE_PBS_ENTRY_SIGNAL_LONG :entered signal of PBS block
+STR_TRACE_RESTRICT_VARIABLE_UNDEFINED :undefined
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_INTEGER :{STRING} {STRING} {STRING} {COMMA} then
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_SPEED :{STRING} {STRING} {STRING} {VELOCITY} then
+STR_TRACE_RESTRICT_CONDITIONAL_ORDER_STATION :{STRING} {STRING} {STRING} {STATION} then
+STR_TRACE_RESTRICT_CONDITIONAL_ORDER_WAYPOINT :{STRING} {STRING} {STRING} {WAYPOINT} then
+STR_TRACE_RESTRICT_CONDITIONAL_ORDER_DEPOT :{STRING} {STRING} {STRING} {DEPOT} then
+STR_TRACE_RESTRICT_CONDITIONAL_CARGO :{STRING} train {STRING} cargo: {STRING} then
+STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_DIRECTION :{STRING} train {STRING} entering from {STRING} tile edge then
+STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_SIGNAL_FACE :{STRING} train {STRING} entering from {STRING} of signal then
+STR_TRACE_RESTRICT_CONDITIONAL_TILE_INDEX :{STRING} {STRING} {STRING} at {NUM} x {NUM} then
+STR_TRACE_RESTRICT_CONDITIONAL_UNDEFINED :{STRING} {STRING} {STRING} {RED}undefined {BLACK}{STRING}then
+STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_UNDEFINED :{STRING} {RED}undefined {BLACK}{STRING}then
+STR_TRACE_RESTRICT_PF_PENALTY_ITEM :Add pathfinder penalty: {COMMA}
+STR_TRACE_RESTRICT_PF_PENALTY_ITEM_PRESET :Add {STRING} pathfinder penalty
+STR_TRACE_RESTRICT_WHITE :{WHITE}
+STR_TRACE_RESTRICT_START :Start
+STR_TRACE_RESTRICT_END :End
+STR_TRACE_RESTRICT_PF_DENY :Deny
+STR_TRACE_RESTRICT_PF_ALLOW :Allow
+STR_TRACE_RESTRICT_PF_ALLOW_LONG :Allow (cancel previous Deny)
+STR_TRACE_RESTRICT_PF_PENALTY :Penalty
+STR_TRACE_RESTRICT_PF_VALUE_SMALL :small
+STR_TRACE_RESTRICT_PF_VALUE_MEDIUM :medium
+STR_TRACE_RESTRICT_PF_VALUE_LARGE :large
+STR_TRACE_RESTRICT_PF_VALUE_CUSTOM :custom
+STR_TRACE_RESTRICT_DIRECTION_FRONT :front
+STR_TRACE_RESTRICT_DIRECTION_BACK :back
+STR_TRACE_RESTRICT_DIRECTION_NE :north-east
+STR_TRACE_RESTRICT_DIRECTION_SE :south-east
+STR_TRACE_RESTRICT_DIRECTION_SW :south-west
+STR_TRACE_RESTRICT_DIRECTION_NW :north-west
+STR_TRACE_RESTRICT_VALUE_CAPTION :{WHITE}Value
+STR_TRACE_RESTRICT_CAPTION :{WHITE}Routefinding restriction
+STR_TRACE_RESTRICT_CAPTION_SHARED :{WHITE}Routefinding restriction - shared by {COMMA} signals
+STR_TRACE_RESTRICT_TYPE_TOOLTIP :{BLACK}Type
+STR_TRACE_RESTRICT_COND_COMPARATOR_TOOLTIP :{BLACK}Comparison operator
+STR_TRACE_RESTRICT_COND_VALUE_TOOLTIP :{BLACK}Value
+STR_TRACE_RESTRICT_CONDFLAGS_TOOLTIP :{BLACK}Condition type
+STR_TRACE_RESTRICT_GOTO_SIGNAL_TOOLTIP :{BLACK}Go to signal
+STR_TRACE_RESTRICT_INSERT :{BLACK}Insert
+STR_TRACE_RESTRICT_REMOVE :{BLACK}Remove
+STR_TRACE_RESTRICT_RESET :{BLACK}Reset
+STR_TRACE_RESTRICT_COPY :{BLACK}Copy
+STR_TRACE_RESTRICT_SHARE :{BLACK}Share
+STR_TRACE_RESTRICT_UNSHARE :{BLACK}Unshare
+STR_TRACE_RESTRICT_SELECT_TARGET :{BLACK}Select Target
+STR_TRACE_RESTRICT_SELECT_SIGNAL :{BLACK}Select Signal
+STR_TRACE_RESTRICT_INSERT_TOOLTIP :{BLACK}Insert an instruction
+STR_TRACE_RESTRICT_REMOVE_TOOLTIP :{BLACK}Remove the selected instruction
+STR_TRACE_RESTRICT_RESET_TOOLTIP :{BLACK}Reset the current signal (without affecting shared programs)
+STR_TRACE_RESTRICT_COPY_TOOLTIP :{BLACK}Copy program from another signal
+STR_TRACE_RESTRICT_SHARE_TOOLTIP :{BLACK}Share program with another signal
+STR_TRACE_RESTRICT_UNSHARE_TOOLTIP :{BLACK}Stop sharing program with other signals, create a copy of the program
+STR_TRACE_RESTRICT_SIGNAL_GUI_TOOLTIP :{BLACK}Routefinding restriction
+STR_TRACE_RESTRICT_INSTRUCTION_LIST_TOOLTIP :{BLACK}Click an instruction to select it{}Ctrl+Click to scroll to the instruction's target (if any)
+STR_TRACE_RESTRICT_ERROR_CAN_T_INSERT_ITEM :{WHITE}Can't insert instruction
+STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM :{WHITE}Can't modify instruction
+STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ITEM :{WHITE}Can't remove instruction
+STR_TRACE_RESTRICT_ERROR_VALUE_TOO_LARGE :{WHITE}Value too large, maximum is {COMMA}
+STR_TRACE_RESTRICT_ERROR_NO_PROGRAM :No trace restrict program exists
+STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE :Offset too large
+STR_TRACE_RESTRICT_ERROR_CAN_T_CHANGE_CONDITIONALITY :Can't change conditionality
+STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ENDIF :Can't remove an 'end if'
+STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK :Validation failed: condstack non-empty at exit
+STR_TRACE_RESTRICT_ERROR_VALIDATE_NO_IF :Validation failed: else/endif without opening if
+STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE :Validation failed: duplicate else
+STR_TRACE_RESTRICT_ERROR_VALIDATE_ELIF_NO_IF :Validation failed: else if without opening if
+STR_TRACE_RESTRICT_ERROR_SOURCE_SAME_AS_TARGET :Source and target signals are the same
+STR_TRACE_RESTRICT_ERROR_CAN_T_RESET_SIGNAL :{WHITE}Can't reset signal
+STR_TRACE_RESTRICT_ERROR_CAN_T_COPY_PROGRAM :{WHITE}Can't copy program
+STR_TRACE_RESTRICT_ERROR_CAN_T_SHARE_PROGRAM :{WHITE}Can't share program
+STR_TRACE_RESTRICT_ERROR_CAN_T_UNSHARE_PROGRAM :{WHITE}Can't unshare program
+
+# Programmable Signals
+STR_PROGRAM_SIGNAL_TOOLTIP :{BLACK}Program signal
+
+STR_ERR_PROGSIG_INVALID_INSTRUCTION :{WHITE}Cannot insert instruction after instruction with invalid ID
+STR_ERR_PROGSIG_INVALID_OPCODE :{WHITE}Cannot insert an instruction of that opcode
+STR_ERR_PROGSIG_NOT_THERE :{WHITE}There is no programmable signal there
+STR_ERR_PROGSIG_INVALID_SIGNAL_STATE :{WHITE}That signal state is invalid
+STR_ERR_PROGSIG_INVALID_CONDITION :{WHITE}That condition is invalid
+STR_ERR_PROGSIG_INVALID_CONDITION_FIELD :{WHITE}That field is not valid for the condition
+STR_ERR_PROGSIG_INVALID_COMPARATOR :{WHITE}That comparator is not valid
+STR_ERR_PROGSIG_INVALID_SIGNAL :{WHITE}Invalid signal selected
+
+STR_PROGSIG_CAPTION :{WHITE}Signal Program
+STR_PROGSIG_COND_VARIABLE_TOOLTIP :{BLACK}Condition to compare upon
+STR_PROGSIG_COND_COMPARATOR_TOOLTIP :{BLACK}Operator to use to compare variable
+STR_PROGSIG_COND_VALUE_TOOLTIP :{BLACK}Value to compare variable against
+STR_PROGSIG_SIGNAL_STATE_TOOLTIP :{BLACK}Set signal to state
+STR_PROGSIG_COND_SET_SIGNAL :{BLACK}Set signal
+STR_PROGSIG_COND_SET_SIGNAL_TOOLTIP :{BLACK}Set the signal to be looked at
+STR_PROGSIG_GOTO_SIGNAL :{BLACK}Goto signal
+STR_PROGSIG_GOTO_SIGNAL_TOOLTIP :{BLACK}Go to this signal
+STR_PROGSIG_INSERT_TOOLTIP :{BLACK}Insert an instruction
+STR_PROGSIG_REMOVE_TOOLTIP :{BLACK}Remove the selected instruction
+STR_PROGSIG_REMOVE_PROGRAM_TOOLTIP :{BLACK}Remove entire program
+STR_PROGSIG_COPY_PROGRAM_TOOLTIP :{BLACK}Copy program from existing signal
+
+STR_PROGSIG_REMOVE_PROGRAM :{RED}Remove program
+STR_PROGSIG_COPY_PROGRAM :{BLUE}Copy program
+STR_PROGSIG_REMOVE :{BLACK}Remove
+STR_PROGSIG_INSERT :Insert
+STR_PROGSIG_INSERT_IF :Condition
+STR_PROGSIG_INSERT_SET_SIGNAL :Set signal state
+
+STR_PROGSIG_FIRST :Start
+STR_PROGSIG_LAST :End
+
+STR_PROGSIG_IF :If {RAW_STRING} Then
+STR_PROGSIG_ELSE :Else
+STR_PROGSIG_ENDIF :End If
+
+STR_PROGSIG_SET_SIGNAL :Make signal {STRING}
+
+STR_PROGSIG_COND_ALWAYS :always
+STR_PROGSIG_COND_NEVER :never
+STR_PROGSIG_COND_COMPARE :{STRING} {STRING} {NUM}
+STR_PROGSIG_COND_SIGNAL_STATE :signal state
+STR_PROGSIG_CONDVAR_SIGNAL_STATE :{STRING1} is green
+STR_PROGSIG_CONDVAR_SIGNAL_STATE_SPECIFIED :specified signal
+STR_PROGSIG_CONDVAR_SIGNAL_STATE_UNSPECIFIED :{RED}unspecified signal{STRING}
+STR_PROGSIG_CONDVAR_NUM_RED :red signals
+STR_PROGSIG_CONDVAR_NUM_GREEN :green signals
+
+STR_PROGSIG_CONDITION_VALUE_CAPT :{WHITE}Condition value
+
+STR_ERROR_CAN_T_INSERT_INSTRUCTION :{WHITE}Can't insert instruction
+STR_ERROR_CAN_T_MODIFY_INSTRUCTION :{WHITE}Can't modify instruction
+STR_ERROR_CAN_T_REMOVE_INSTRUCTION :{WHITE}Can't remove instruction
+STR_ERROR_CAN_T_GOTO_UNDEFINED_SIGNAL :{WHITE}Can't go to undefined signal
+STR_ERROR_NOT_AN_EXIT_SIGNAL :{WHITE}Not an exit signal
+STR_ERROR_NOT_AN_PROG_SIGNAL :{WHITE}Not an programmable signal
+STR_ERROR_CANNOT_USE_SELF :{WHITE}Can't copy program from myself
+STR_ERROR_CAN_T_DEPEND_UPON_BIDIRECTIONAL_SIGNALS :{WHITE}Cannot conditionally depend upon bidirectional signals
+STR_ERROR_INVALID_SIGNAL :{WHITE}Invalid signal
+
# Bridge selection window
STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Select Rail Bridge
STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Select Road Bridge
@@ -2475,6 +2729,7 @@
STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND :{BLACK}Raise a corner of land. Dragging raises the first selected corner and levels the selected area to the new corner height. Ctrl selects the area diagonally. Shift toggles building/showing cost estimate
STR_LANDSCAPING_LEVEL_LAND_TOOLTIP :{BLACK}Level an area of land to the height of the first selected corner. Ctrl selects the area diagonally. Shift toggles building/showing cost estimate
STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND :{BLACK}Purchase land for future use. Shift toggles building/showing cost estimate
+STR_LANDSCAPING_TOOLTIP_RULER_TOOL :{BLACK}Use a virtual ruler to measure distance and height
# Object construction window
STR_OBJECT_BUILD_CAPTION :{WHITE}Object Selection
@@ -2486,6 +2741,36 @@
STR_OBJECT_CLASS_LTHS :Lighthouses
STR_OBJECT_CLASS_TRNS :Transmitters
+#House construction window (for SE only)
+STR_HOUSE_BUILD_CAPTION :{WHITE}House Selection
+STR_HOUSE_BUILD_CUSTOM_CAPTION :{WHITE}{RAW_STRING}
+STR_HOUSE_BUILD_HOUSESET_LIST_TOOLTIP :{BLACK}Select set of houses
+STR_HOUSE_BUILD_SELECT_HOUSE_TOOLTIP :{BLACK}Select house to build
+STR_HOUSE_BUILD_HOUSE_NAME :{GOLD}{STRING1}
+STR_HOUSE_BUILD_HISTORICAL_BUILDING :{GOLD}(historical building)
+STR_HOUSE_BUILD_HOUSE_POPULATION :{BLACK}Population: {GOLD}{NUM}
+STR_HOUSE_BUILD_HOUSE_ZONES :{BLACK}House zones: {STRING1} {STRING1} {STRING1} {STRING1} {STRING1}
+STR_HOUSE_BUILD_HOUSE_ZONE_DISABLED :{GRAY}{NUM}
+STR_HOUSE_BUILD_HOUSE_ZONE_ENABLED :{GOLD}{NUM}
+STR_HOUSE_BUILD_LANDSCAPE :{BLACK}Landscape: {STRING}
+STR_HOUSE_BUILD_LANDSCAPE_ABOVE_OR_BELOW_SNOWLINE :{GOLD}above or below snowline
+STR_HOUSE_BUILD_LANDSCAPE_ONLY_ABOVE_SNOWLINE :{GOLD}only above snowline
+STR_HOUSE_BUILD_LANDSCAPE_ONLY_BELOW_SNOWLINE :{GOLD}only below snowline
+STR_HOUSE_BUILD_YEARS :{BLACK}Years: {STRING1}{GOLD} - {STRING1}
+STR_HOUSE_BUILD_YEARS_BAD_YEAR :{RED}{NUM}
+STR_HOUSE_BUILD_YEARS_GOOD_YEAR :{GOLD}{NUM}
+STR_HOUSE_BUILD_SUPPLIED_CARGO :{BLACK}Supplies: {GOLD}{CARGO_LIST}
+STR_HOUSE_BUILD_ACCEPTED_CARGO :{BLACK}Accepts: {GOLD}{RAW_STRING}
+STR_HOUSE_BUILD_CARGO_FIRST :{STRING2}
+STR_HOUSE_BUILD_CARGO_SEPARATED :, {STRING2}
+STR_HOUSE_BUILD_CARGO_VALUE_JUST_NAME :{1:STRING}
+STR_HOUSE_BUILD_CARGO_VALUE_EIGHTS :({COMMA}/8 {STRING})
+STR_BASIC_HOUSE_SET_NAME :Basic houses
+
+#Town select window (for SE only)
+STR_SELECT_TOWN_CAPTION :{WHITE}Select town
+STR_SELECT_TOWN_LIST_ITEM :{BLACK}{TOWN}
+
# Tree planting window (last two for SE only)
STR_PLANT_TREE_CAPTION :{WHITE}Trees
STR_PLANT_TREE_TOOLTIP :{BLACK}Select tree type to plant. If the tile already has a tree, this will add more trees of mixed types independent of the selected type
@@ -2606,25 +2891,34 @@
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRESIGNALS :{STRING} track with pre-signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXITSIGNALS :{STRING} track with exit-signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBOSIGNALS :{STRING} track with combo-signals
+STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PROGSIGNALS :{STRING} track with programmable signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBSSIGNALS :{STRING} track with path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRYSIGNALS :{STRING} track with one-way path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PRESIGNALS :{STRING} track with block and pre-signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_EXITSIGNALS :{STRING} track with block and exit-signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_COMBOSIGNALS :{STRING} track with block and combo-signals
+STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PROGSIGNALS :{STRING} track with block and programmable signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PBSSIGNALS :{STRING} track with block and path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_NOENTRYSIGNALS :{STRING} track with block and one-way path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_EXITSIGNALS :{STRING} track with pre- and exit-signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_COMBOSIGNALS :{STRING} track with pre- and combo-signals
+STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_PROGSIGNALS :{STRING} track with pre- and programmable signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_PBSSIGNALS :{STRING} track with pre- and path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_NOENTRYSIGNALS :{STRING} track with pre- and one-way path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_COMBOSIGNALS :{STRING} track with exit- and combo-signals
+STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PROGSIGNALS :{STRING} track with exit- and programmable signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PBSSIGNALS :{STRING} track with exit- and path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS :{STRING} track with exit- and one-way path signals
+STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PROGSIGNALS :{STRING} track with combo- and programmable signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PBSSIGNALS :{STRING} track with combo- and path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS :{STRING} track with combo- and one-way path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS :{STRING} track with path and one-way path signals
+STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_PROGSIGNALS :{STRING} track with path and programmable signals
+STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRY_PROGSIGNALS :{STRING} track with one-way path and programmable signals
STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :{STRING} train depot
+STR_LAI_RAIL_DESCRIPTION_RESTRICTED_SIGNAL :{STRING1} (restricted)
+
STR_LAI_ROAD_DESCRIPTION_ROAD :Road
STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_STREETLIGHTS :Road with street lights
STR_LAI_ROAD_DESCRIPTION_TREE_LINED_ROAD :Tree-lined road
@@ -2658,8 +2952,10 @@
# 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
@@ -3145,6 +3441,9 @@
STR_STATION_VIEW_GROUP_D_S_V :Destination-Source-Via
STR_STATION_VIEW_GROUP_D_V_S :Destination-Via-Source
+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
@@ -3165,10 +3464,102 @@
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
@@ -3195,6 +3586,8 @@
STR_FINANCES_SECTION_SHIP_INCOME :{GOLD}Ship Income
STR_FINANCES_SECTION_LOAN_INTEREST :{GOLD}Loan Interest
STR_FINANCES_SECTION_OTHER :{GOLD}Other
+STR_FINANCES_SECTION_INFRASTRUCTURE_COSTS :{GOLD}Infrastructure Sharing Costs
+STR_FINANCES_SECTION_INFRASTRUCTURE_INCOME :{GOLD}Infrastructure Sharing Income
STR_FINANCES_NEGATIVE_INCOME :{BLACK}-{CURRENCY_LONG}
STR_FINANCES_POSITIVE_INCOME :{BLACK}+{CURRENCY_LONG}
STR_FINANCES_TOTAL_CAPTION :{WHITE}Total:
@@ -3288,6 +3681,14 @@
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Production level: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure!
+# Industry tooltip
+STR_INDUSTRY_VIEW_TRANSPORTED_TOOLTIP :{BLACK}{STRING}
+STR_INDUSTRY_VIEW_TRANSPORTED_TOOLTIP_TOOLTIP :{BLACK}{STRING}{}{CARGO_LONG} ({COMMA}%)
+STR_INDUSTRY_VIEW_TRANSPORTED_TOOLTIP_TOOLTIP_TOOLTIP :{BLACK}{STRING}{}{CARGO_LONG} ({COMMA}%){}{BLACK}{CARGO_LONG} ({COMMA}%)
+
+# Town tooltip
+STR_TOWN_NAME_TOOLTIP :{BLACK}{TOWN}
+
############ range for requires starts
STR_INDUSTRY_VIEW_REQUIRES_CARGO :{BLACK}Requires: {YELLOW}{STRING}{RAW_STRING}
STR_INDUSTRY_VIEW_REQUIRES_CARGO_CARGO :{BLACK}Requires: {YELLOW}{STRING}{RAW_STRING}, {STRING}{RAW_STRING}
@@ -3363,6 +3764,11 @@
STR_GROUP_RENAME_CAPTION :{BLACK}Rename a group
+STR_GROUP_PROFIT_THIS_YEAR :Profit this year:
+STR_GROUP_PROFIT_LAST_YEAR :Profit last year:
+STR_GROUP_OCCUPANCY :Current usage:
+STR_GROUP_OCCUPANCY_VALUE :{NUM}%
+
# Build vehicle window
STR_BUY_VEHICLE_TRAIN_RAIL_CAPTION :New Rail Vehicles
STR_BUY_VEHICLE_TRAIN_ELRAIL_CAPTION :New Electric Rail Vehicles
@@ -3621,11 +4027,20 @@
STR_VEHICLE_STATUS_CRASHED :{RED}Crashed!
STR_VEHICLE_STATUS_BROKEN_DOWN :{RED}Broken down
STR_VEHICLE_STATUS_STOPPED :{RED}Stopped
+STR_VEHICLE_STATUS_BROKEN_DOWN_VEL :{RED}Broken down - {STRING1}, {LTBLUE} {VELOCITY}
STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL :{RED}Stopping, {VELOCITY}
STR_VEHICLE_STATUS_TRAIN_NO_POWER :{RED}No power
STR_VEHICLE_STATUS_TRAIN_STUCK :{ORANGE}Waiting for free path
STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR :{ORANGE}Too far to next destination
+STR_BREAKDOWN_TYPE_CRITICAL :Mechanical failure
+STR_BREAKDOWN_TYPE_EM_STOP :Emergency stop
+STR_BREAKDOWN_TYPE_LOW_SPEED :Limited to {VELOCITY}
+STR_BREAKDOWN_TYPE_LOW_POWER :{COMMA}% Power
+STR_BREAKDOWN_TYPE_DEPOT :Heading to {STATION} Hangar for repairs
+STR_BREAKDOWN_TYPE_LANDING :Heading to {STATION} for emergency landing
+STR_ERROR_TRAIN_TOO_HEAVY :{WHITE}{VEHICLE} is too heavy
+
STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL :{LTBLUE}Heading for {STATION}, {VELOCITY}
STR_VEHICLE_STATUS_NO_ORDERS_VEL :{LTBLUE}No orders, {VELOCITY}
STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL :{LTBLUE}Heading for {WAYPOINT}, {VELOCITY}
@@ -3648,6 +4063,11 @@
STR_VEHICLE_DETAILS_AIRCRAFT_RENAME :{BLACK}Name aircraft
STR_VEHICLE_INFO_AGE_RUNNING_COST_YR :{BLACK}Age: {LTBLUE}{STRING2}{BLACK} Running Cost: {LTBLUE}{CURRENCY_LONG}/yr
+
+STR_RUNNING :{LTBLUE}Running
+STR_NEED_REPAIR :{ORANGE}Train needs repair - max speed reduced to {VELOCITY}
+STR_CURRENT_STATUS :{BLACK}Current status: {STRING2}
+
# The next two need to stay in this order
STR_VEHICLE_INFO_AGE :{COMMA} year{P "" s} ({COMMA})
STR_VEHICLE_INFO_AGE_RED :{RED}{COMMA} year{P "" s} ({COMMA})
@@ -3685,7 +4105,9 @@
# Extra buttons for train details windows
STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE :{LTBLUE}{ENGINE}{BLACK} Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY_LONG}
+STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE_AND_SPEED :{LTBLUE}{ENGINE}{BLACK} Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} {BLACK}Max. speed: {LTBLUE}{VELOCITY}
STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE :{LTBLUE}{ENGINE}{BLACK} Value: {LTBLUE}{CURRENCY_LONG}
+STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE_AND_SPEED :{LTBLUE}{ENGINE}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} {BLACK}Max. speed: {LTBLUE}{VELOCITY}
STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT :{BLACK}Total cargo capacity of this train:
STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY :{LTBLUE}- {CARGO_LONG} ({CARGO_SHORT})
@@ -3819,6 +4241,11 @@
STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP :{BLACK}Show all vehicles that share this schedule
+STR_ORDERS_OCCUPANCY_BUTTON :{BLACK}%
+STR_ORDERS_OCCUPANCY_BUTTON_TOOLTIP :{BLACK}Show occupancy running averages
+STR_ORDERS_OCCUPANCY_LIST_TOOLTIP :{BLACK}Order occupancy - this shows runnings averages of recent occupancy levels when leaving a station, for all vehicles sharing these orders
+STR_ORDERS_OCCUPANCY_PERCENT :{NUM}%
+
# String parts to build the order string
STR_ORDER_GO_TO_WAYPOINT :Go via {WAYPOINT}
STR_ORDER_GO_NON_STOP_TO_WAYPOINT :Go non-stop via {WAYPOINT}
@@ -3906,6 +4333,7 @@
STR_TIMETABLE_AND_TRAVEL_FOR :and travel for {STRING1}
STR_TIMETABLE_DAYS :{COMMA}{NBSP}day{P "" s}
STR_TIMETABLE_TICKS :{COMMA}{NBSP}tick{P "" s}
+STR_TIMETABLE_MINUTES :{COMMA}{NBSP}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)
@@ -3937,6 +4365,9 @@
STR_TIMETABLE_AUTOFILL :{BLACK}Autofill
STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Fill the timetable automatically with the values from the next journey (Ctrl+Click to try to keep waiting times)
+STR_TIMETABLE_AUTOMATE :{BLACK}Automate
+STR_TIMETABLE_AUTOMATE_TOOLTIP :{BLACK}Manage the timetables automatically by updating the values for each journey
+
STR_TIMETABLE_EXPECTED :{BLACK}Expected
STR_TIMETABLE_SCHEDULED :{BLACK}Scheduled
STR_TIMETABLE_EXPECTED_TOOLTIP :{BLACK}Switch between expected and scheduled
@@ -3952,6 +4383,8 @@
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
@@ -4037,6 +4470,22 @@
STR_TEXTFILE_VIEW_CHANGELOG :{BLACK}Changelog
STR_TEXTFILE_VIEW_LICENCE :{BLACK}Licence
+# Plans window
+STR_PLANS_CAPTION :{WHITE}Plans
+STR_PLANS_NEW_PLAN :{BLACK}New
+STR_PLANS_NEW_PLAN_TOOLTIP :{BLACK}Create a new plan
+STR_PLANS_ADD_LINES :{BLACK}Add lines
+STR_PLANS_ADDING_LINES :{BLACK}Adding...
+STR_PLANS_HIDE_ALL :{BLACK}Hide all
+STR_PLANS_HIDE_ALL_TOOLTIP :{BLACK}Set the visibility of all the plans and all their lines to false
+STR_PLANS_VISIBILITY_PRIVATE :{BLACK}Make private
+STR_PLANS_VISIBILITY_PUBLIC :{BLACK}Make public
+STR_PLANS_VISIBILITY_TOOLTIP :{BLACK}Toggle the visibility of a plan (private is yellow, public is blue). A public plan will be displayed in the plan window of the other companies but only its owner can add lines to it.
+STR_PLANS_DELETE :{BLACK}Delete
+STR_PLANS_DELETE_TOOLTIP :{BLACK}Delete the selected item in the list
+STR_PLANS_LIST_ITEM_PLAN :Plan #{NUM}: {NUM} line{P "" s} ({DATE_SHORT})
+STR_PLANS_LIST_ITEM_LINE : -- Line #{NUM}: {NUM} segment{P "" s}
+STR_PLANS_LIST_TOOLTIP :{BLACK}Double click any item in the list to (un)fold the related plan
# Vehicle loading indicators
STR_PERCENT_UP_SMALL :{TINY_FONT}{WHITE}{NUM}%{UP_ARROW}
@@ -4166,6 +4615,7 @@
STR_ERROR_CAN_T_RENAME_TOWN :{WHITE}Can't rename town...
STR_ERROR_CAN_T_FOUND_TOWN_HERE :{WHITE}Can't found town here...
STR_ERROR_CAN_T_EXPAND_TOWN :{WHITE}Can't expand town...
+STR_ERROR_CAN_T_BUILD_HOUSE_HERE :{WHITE}Can't build house here...
STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP_SUB :{WHITE}... too close to edge of map
STR_ERROR_TOO_CLOSE_TO_ANOTHER_TOWN :{WHITE}... too close to another town
STR_ERROR_TOO_MANY_TOWNS :{WHITE}... too many towns
@@ -4174,6 +4624,15 @@
STR_ERROR_ROAD_WORKS_IN_PROGRESS :{WHITE}Road works in progress
STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}Can't delete this town...{}A station or depot is referring to the town or a town owned tile can't be removed
STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... there is no suitable place for a statue in the centre of this town
+STR_ERROR_BUILDING_NOT_ALLOWED_IN_THIS_TOWN_ZONE :{WHITE}... not allowed in this town zone.
+STR_ERROR_BUILDING_NOT_ALLOWED_ABOVE_SNOW_LINE :{WHITE}... not allowed above the snow line.
+STR_ERROR_BUILDING_NOT_ALLOWED_BELOW_SNOW_LINE :{WHITE}... not allowed below the snow line.
+STR_ERROR_TOO_MANY_HOUSE_SETS :{WHITE}... too many house sets in the town
+STR_ERROR_TOO_MANY_HOUSE_TYPES :{WHITE}... too many house types in the town
+STR_ERROR_BUILDING_IS_TOO_OLD :{WHITE}... building is too old.
+STR_ERROR_BUILDING_IS_TOO_MODERN :{WHITE}... building is too modern.
+STR_ERROR_ONLY_ONE_BUILDING_ALLOWED_PER_TOWN :{WHITE}... only one building of this type is allowed in a town.
+STR_ERROR_BUILDING_NOT_ALLOWED :{WHITE}... the building is not allowed.
# Industry related errors
STR_ERROR_TOO_MANY_INDUSTRIES :{WHITE}... too many industries
@@ -4300,6 +4759,8 @@
STR_ERROR_CAN_T_CONVERT_RAIL :{WHITE}Can't convert rail type here...
+STR_ERROR_SIGNAL_CHANGES :{WHITE}Number of programmable signal evaluations exceeded limit
+
# Road construction errors
STR_ERROR_MUST_REMOVE_ROAD_FIRST :{WHITE}Must remove road first
STR_ERROR_ONEWAY_ROADS_CAN_T_HAVE_JUNCTION :{WHITE}... one way roads can't have junctions
@@ -4460,6 +4921,11 @@
STR_ERROR_CAN_T_CHANGE_SIGN_NAME :{WHITE}Can't change sign name...
STR_ERROR_CAN_T_DELETE_SIGN :{WHITE}Can't delete sign...
+# Plan related errors
+STR_ERROR_TOO_MANY_PLANS :{WHITE}... too many plans
+STR_ERROR_TOO_MANY_NODES :{WHITE}... too many nodes in plan line
+STR_ERROR_NO_MORE_SPACE_FOR_LINES :{WHITE}No more space for lines
+
# Translatable comment for OpenTTD's desktop shortcut
STR_DESKTOP_SHORTCUT_COMMENT :A simulation game based on Transport Tycoon Deluxe
@@ -4855,6 +5321,11 @@
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})
@@ -4879,10 +5350,24 @@
STR_SAVEGAME_NAME_SPECTATOR :Spectator, {1:STRING1}
# Viewport strings
+STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP :{BROWN}{NUM} {STRING}
+STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_STATION :{BLACK}ST
+STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_WAYPOINT :{GRAY}WP
+STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_IMPLICIT :{GRAY}IM
+STR_VIEWPORT_SHOW_VEHICLE_ROUTE_STEP_DEPOT :{RED}DE
+
+STR_VIEWPORT_TOWN_POP_VERY_POOR_RATING :{WHITE}{TOWN} {RED}({COMMA})
+STR_VIEWPORT_TOWN_POP_MEDIOCRE_RATING :{WHITE}{TOWN} {ORANGE}({COMMA})
+STR_VIEWPORT_TOWN_POP_GOOD_RATING :{WHITE}{TOWN} {YELLOW}({COMMA})
STR_VIEWPORT_TOWN_POP :{WHITE}{TOWN} ({COMMA})
+STR_VIEWPORT_TOWN_POP_EXCELLENT_RATING :{WHITE}{TOWN} {GREEN}({COMMA})
STR_VIEWPORT_TOWN :{WHITE}{TOWN}
STR_VIEWPORT_TOWN_TINY_BLACK :{TINY_FONT}{BLACK}{TOWN}
+STR_VIEWPORT_TOWN_TINY_VERY_POOR_RATING :{TINY_FONT}{RED}{TOWN}
+STR_VIEWPORT_TOWN_TINY_MEDIOCRE_RATING :{TINY_FONT}{ORANGE}{TOWN}
+STR_VIEWPORT_TOWN_TINY_GOOD_RATING :{TINY_FONT}{YELLOW}{TOWN}
STR_VIEWPORT_TOWN_TINY_WHITE :{TINY_FONT}{WHITE}{TOWN}
+STR_VIEWPORT_TOWN_TINY_EXCELLENT_RATING :{TINY_FONT}{GREEN}{TOWN}
STR_VIEWPORT_SIGN_SMALL_BLACK :{TINY_FONT}{BLACK}{SIGN}
STR_VIEWPORT_SIGN_SMALL_WHITE :{TINY_FONT}{WHITE}{SIGN}
@@ -4919,6 +5404,10 @@
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}
@@ -4935,6 +5424,8 @@
STR_COMPANY_MONEY :{WHITE}{CURRENCY_LONG}
STR_BLACK_DATE_LONG :{BLACK}{DATE_LONG}
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}
@@ -4953,6 +5444,8 @@
STR_TINY_BLACK_VEHICLE :{TINY_FONT}{BLACK}{VEHICLE}
STR_TINY_RIGHT_ARROW :{TINY_FONT}{RIGHT_ARROW}
+STR_WHITE :{WHITE}
+STR_BLACK :{BLACK}
STR_BLACK_1 :{BLACK}1
STR_BLACK_2 :{BLACK}2
STR_BLACK_3 :{BLACK}3
@@ -4968,3 +5461,18 @@
STR_SHIP :{BLACK}{SHIP}
STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY})
+
+######## Zoning toolbar
+
+STR_ZONING_TOOLBAR :{WHITE}Zoning toolbar
+STR_ZONING_OUTER :{BLACK}Outer tile borders:
+STR_ZONING_INNER :{BLACK}Inner tile borders:
+STR_ZONING_OUTER_INFO :{BLACK}Select which type of zoning you want on the outer border of a tile.
+STR_ZONING_INNER_INFO :{BLACK}Select which type of zoning you want on the inner border of a tile.
+
+STR_ZONING_NO_ZONING :Nothing
+STR_ZONING_AUTHORITY :Authority
+STR_ZONING_CAN_BUILD :Where I can't build
+STR_ZONING_STA_CATCH :Station catchment
+STR_ZONING_BUL_UNSER :Unserved buildings
+STR_ZONING_IND_UNSER :Unserved industries
Index: src/lang/russian.txt
===================================================================
--- src/lang/russian.txt (revision 27386)
+++ src/lang/russian.txt (working copy)
@@ -399,6 +399,7 @@
STR_MEASURE_AREA :{BLACK}Площадь: {NUM} x {NUM}
STR_MEASURE_LENGTH_HEIGHTDIFF :{BLACK}Длина: {NUM}{}Разница высот: {HEIGHT}
STR_MEASURE_AREA_HEIGHTDIFF :{BLACK}Площадь: {NUM} x {NUM}{}Разница высот: {HEIGHT}
+STR_MEASURE_DIST_HEIGHTDIFF :{BLACK}Расстояние «Манхэттэн»: {NUM}{}Расстояние по прямой: {NUM}{}Расстояние от ближайшего края: {NUM}{}Высота над уровнем моря: {HEIGHT}{}Разница высот: {HEIGHT}
# These are used in buttons
@@ -1675,6 +1676,8 @@
STR_CONFIG_SETTING_ALLOW_SHARES_HELPTEXT :Разрешает торговлю акциями транспортных компаний. Акции выпускаются компаниями через некоторое время после основания.
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Процент дохода, начисляемый при частичной перевозке: {STRING}
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE_HELPTEXT :Процент прибыли, начисляемый транспорту за частичную перевозку груза.
+STR_CONFIG_SETTING_SIMULATE_SIGNALS :Симуляция светофоров в туннелях и на мостах каждые: {STRING}
+STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE :{COMMA} клет{P ку ки ок}
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY :При перетаскивании ставить сигналы каждые: {STRING}
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_HELPTEXT :Настройка периодичности расстановки сигналов методом перетаскивания. Сигналы будут устанавливаться до первого встреченного препятствия (пересечения или другого сигнала).
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} клет{P ку ки ок}
@@ -2655,6 +2658,7 @@
STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND :{BLACK}Поднять угол земли. Перетаскивание поднимает первый выбранный угол и выравнивает выбранную область до новой высоты угла. Ctrl выбирает область по диагонали. При нажатом Shift - оценка стоимости строительства.
STR_LANDSCAPING_LEVEL_LAND_TOOLTIP :{BLACK}Выровнять землю до высоты первого выбранного угла. Ctrl выбирает область по диагонали. При нажатом Shift - оценка стоимости выравнивания.
STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND :{BLACK}Покупка земли. При нажатом Shift - оценка стоимости покупки.
+STR_LANDSCAPING_TOOLTIP_RULER_TOOL :{BLACK}Виртуальная линейка для измерения расстояний и высот
# Object construction window
STR_OBJECT_BUILD_CAPTION :{WHITE}Выбор объекта
@@ -2838,8 +2842,10 @@
# Industries come directly from their industry names
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Железнодорожный туннель
+STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL :Железнодорожный туннель с симуляцией светофоров
STR_LAI_TUNNEL_DESCRIPTION_ROAD :Автомобильный туннель
+STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL :Железнодорожный мост с симуляцией светофоров
STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Стальной висячий ж/д мост
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Стальной балочный ж/д мост
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Стальной консольный ж/д мост
@@ -5195,3 +5201,8 @@
STR_SHIP :{BLACK}{SHIP}
STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY})
+
+STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE :Взымать плату за починку тс: {STRING}
+STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE_HELPTEXT :Если включено, в депо бужет взыматься плата за ремонт каждой поломки транспортного средства
+STR_CONFIG_SETTING_REPAIR_COST :Стоимость ремонта: 1/{STRING} полной стоимости тс
+STR_CONFIG_SETTING_REPAIR_COST_HELPTEXT :Стоимость ремонта одной поломки транспортного средства
Index: src/main_gui.cpp
===================================================================
--- src/main_gui.cpp (revision 27386)
+++ src/main_gui.cpp (working copy)
@@ -231,6 +231,8 @@
GHK_CHAT_ALL,
GHK_CHAT_COMPANY,
GHK_CHAT_SERVER,
+ GHK_CHANGE_MAP_MODE_PREV,
+ GHK_CHANGE_MAP_MODE_NEXT,
};
struct MainWindow : Window
@@ -249,6 +251,7 @@
NWidgetViewport *nvp = this->GetWidget(WID_M_VIEWPORT);
nvp->InitializeViewport(this, TileXY(32, 32), ZOOM_LVL_VIEWPORT);
+ this->viewport->map_type = (ViewportMapType) _settings_client.gui.default_viewport_map_mode;
this->viewport->overlay = new LinkGraphOverlay(this, WID_M_VIEWPORT, 0, 0, 3);
this->refresh = LINKGRAPH_DELAY;
}
@@ -423,6 +426,25 @@
break;
#endif
+ case GHK_CHANGE_MAP_MODE_PREV:
+ if (_focused_window && _focused_window->viewport && _focused_window->viewport->zoom >= ZOOM_LVL_DRAW_MAP) {
+ _focused_window->viewport->map_type = ChangeRenderMode(_focused_window->viewport, true);
+ _focused_window->SetDirty();
+ } else if (this->viewport->zoom >= ZOOM_LVL_DRAW_MAP) {
+ this->viewport->map_type = ChangeRenderMode(this->viewport, true);
+ this->SetDirty();
+ }
+ break;
+ case GHK_CHANGE_MAP_MODE_NEXT:
+ if (_focused_window && _focused_window->viewport && _focused_window->viewport->zoom >= ZOOM_LVL_DRAW_MAP) {
+ _focused_window->viewport->map_type = ChangeRenderMode(_focused_window->viewport, false);
+ _focused_window->SetDirty();
+ } else if (this->viewport->zoom >= ZOOM_LVL_DRAW_MAP) {
+ this->viewport->map_type = ChangeRenderMode(this->viewport, false);
+ this->SetDirty();
+ }
+ break;
+
default: return ES_NOT_HANDLED;
}
return ES_HANDLED;
@@ -439,7 +461,11 @@
virtual void OnMouseWheel(int wheel)
{
- if (_settings_client.gui.scrollwheel_scrolling == 0) {
+ if (_ctrl_pressed) {
+ /* Cycle through the drawing modes */
+ this->viewport->map_type = ChangeRenderMode(this->viewport, wheel < 0);
+ this->SetDirty();
+ } else if (_settings_client.gui.scrollwheel_scrolling == 0) {
ZoomInOrOutToCursorWindow(wheel < 0, this);
}
}
@@ -465,6 +491,16 @@
InvalidateWindowData(WC_MAIN_TOOLBAR, 0, data, true);
}
+ virtual void OnMouseOver(Point pt, int widget)
+ {
+ if (pt.x != -1 && _game_mode != GM_MENU) {
+ /* Show tooltip with last month production or town name */
+ const Point p = GetTileBelowCursor();
+ const TileIndex tile = TileVirtXY(p.x, p.y);
+ if (tile < MapSize()) ShowTooltipForTile(this, tile);
+ }
+ }
+
static HotkeyList hotkeys;
};
@@ -517,6 +553,8 @@
Hotkey(_ghk_chat_company_keys, "chat_company", GHK_CHAT_COMPANY),
Hotkey(_ghk_chat_server_keys, "chat_server", GHK_CHAT_SERVER),
#endif
+ Hotkey(WKC_PAGEUP, "previous_map_mode", GHK_CHANGE_MAP_MODE_PREV),
+ Hotkey(WKC_PAGEDOWN, "next_map_mode", GHK_CHANGE_MAP_MODE_NEXT),
HOTKEY_LIST_END
};
HotkeyList MainWindow::hotkeys("global", global_hotkeys);
Index: src/misc.cpp
===================================================================
--- src/misc.cpp (revision 27386)
+++ src/misc.cpp (working copy)
@@ -28,6 +28,7 @@
#include "core/pool_type.hpp"
#include "game/game.hpp"
#include "linkgraph/linkgraphschedule.h"
+#include "tracerestrict.h"
#include "safeguards.h"
@@ -62,6 +63,7 @@
_pause_mode = PM_UNPAUSED;
_fast_forward = 0;
_tick_counter = 0;
+ _tick_skip_counter = 0;
_cur_tileloop_tile = 1;
_thd.redsq = INVALID_TILE;
if (reset_settings) MakeNewgameSettingsLive();
@@ -72,6 +74,7 @@
}
LinkGraphSchedule::Clear();
+ ClearTraceRestrictMapping();
PoolBase::Clean(PT_NORMAL);
ResetPersistentNewGRFData();
Index: src/misc_gui.cpp
===================================================================
--- src/misc_gui.cpp (revision 27386)
+++ src/misc_gui.cpp (working copy)
@@ -175,6 +175,7 @@
/* Tiletype */
SetDParam(0, td.dparam[0]);
+ SetDParam(1, td.dparam[1]);
GetString(this->landinfo_data[line_nr], td.str, lastof(this->landinfo_data[line_nr]));
line_nr++;
@@ -643,6 +644,7 @@
byte paramcount; ///< Number of string parameters in #string_id.
uint64 params[5]; ///< The string parameters.
TooltipCloseCondition close_cond; ///< Condition for closing the window.
+ char buffer[DRAW_STRING_BUFFER]; ///< Text to draw
TooltipsWindow(Window *parent, StringID str, uint paramcount, const uint64 params[], TooltipCloseCondition close_tooltip) : Window(&_tool_tips_desc)
{
@@ -653,6 +655,7 @@
memcpy(this->params, params, sizeof(this->params[0]) * paramcount);
this->paramcount = paramcount;
this->close_cond = close_tooltip;
+ if (this->paramcount == 0) GetString(this->buffer, str, lastof(this->buffer)); // Get the text while params are available
this->InitNested();
@@ -698,10 +701,14 @@
GfxFillRect(r.left, r.top, r.right, r.bottom, PC_BLACK);
GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_LIGHT_YELLOW);
- for (uint arg = 0; arg < this->paramcount; arg++) {
- SetDParam(arg, this->params[arg]);
+ if (this->paramcount == 0) {
+ DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, this->buffer, TC_FROMSTRING, SA_CENTER);
+ } else {
+ for (uint arg = 0; arg < this->paramcount; arg++) {
+ SetDParam(arg, this->params[arg]);
+ }
+ DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, this->string_id, TC_FROMSTRING, SA_CENTER);
}
- DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, this->string_id, TC_FROMSTRING, SA_CENTER);
}
virtual void OnMouseLoop()
Index: src/network/core/packet.cpp
===================================================================
--- src/network/core/packet.cpp (revision 27386)
+++ src/network/core/packet.cpp (working copy)
@@ -15,6 +15,7 @@
#include "../../stdafx.h"
#include "../../string_func.h"
+#include "../../command_type.h"
#include "packet.h"
@@ -32,7 +33,7 @@
this->next = NULL;
this->pos = 0; // We start reading from here
this->size = 0;
- this->buffer = MallocT(SEND_MTU);
+ this->buffer = MallocT(SHRT_MAX);
}
/**
@@ -47,7 +48,7 @@
/* Skip the size so we can write that in before sending the packet */
this->pos = 0;
this->size = sizeof(PacketSize);
- this->buffer = MallocT(SEND_MTU);
+ this->buffer = MallocT(SHRT_MAX);
this->buffer[this->size++] = type;
}
@@ -99,7 +100,7 @@
*/
void Packet::Send_uint8(uint8 data)
{
- assert(this->size < SEND_MTU - sizeof(data));
+ assert(this->size < SHRT_MAX - sizeof(data));
this->buffer[this->size++] = data;
}
@@ -109,7 +110,7 @@
*/
void Packet::Send_uint16(uint16 data)
{
- assert(this->size < SEND_MTU - sizeof(data));
+ assert(this->size < SHRT_MAX - sizeof(data));
this->buffer[this->size++] = GB(data, 0, 8);
this->buffer[this->size++] = GB(data, 8, 8);
}
@@ -120,7 +121,7 @@
*/
void Packet::Send_uint32(uint32 data)
{
- assert(this->size < SEND_MTU - sizeof(data));
+ assert(this->size < SHRT_MAX - sizeof(data));
this->buffer[this->size++] = GB(data, 0, 8);
this->buffer[this->size++] = GB(data, 8, 8);
this->buffer[this->size++] = GB(data, 16, 8);
@@ -133,7 +134,7 @@
*/
void Packet::Send_uint64(uint64 data)
{
- assert(this->size < SEND_MTU - sizeof(data));
+ assert(this->size < SHRT_MAX - sizeof(data));
this->buffer[this->size++] = GB(data, 0, 8);
this->buffer[this->size++] = GB(data, 8, 8);
this->buffer[this->size++] = GB(data, 16, 8);
@@ -153,10 +154,22 @@
{
assert(data != NULL);
/* The <= *is* valid due to the fact that we are comparing sizes and not the index. */
- assert(this->size + strlen(data) + 1 <= SEND_MTU);
+ assert(this->size + strlen(data) + 1 <= SHRT_MAX);
while ((this->buffer[this->size++] = *data++) != '\0') {}
}
+/**
+ * Sends a binary data over the network.
+ * @param data The data to send
+ */
+void Packet::Send_binary(const char *data, const size_t size)
+{
+ assert(data != NULL);
+ assert(size < MAX_CMD_TEXT_LENGTH);
+ memcpy(&this->buffer[this->size], data, size);
+ this->size += (PacketSize) size;
+}
+
/*
* Receiving commands
@@ -311,4 +324,18 @@
str_validate(bufp, last, settings);
}
+/**
+ * Reads binary data.
+ * @param buffer The buffer to put the data into.
+ * @param size The size of the buffer.
+ */
+void Packet::Recv_binary(char *buffer, size_t size)
+{
+ /* Don't allow reading from a closed socket */
+ if (cs->HasClientQuit()) return;
+
+ memcpy(buffer, &this->buffer[this->pos], size);
+ this->pos += (PacketSize) size;
+}
+
#endif /* ENABLE_NETWORK */
Index: src/network/core/packet.h
===================================================================
--- src/network/core/packet.h (revision 27386)
+++ src/network/core/packet.h (working copy)
@@ -52,7 +52,7 @@
PacketSize size;
/** The current read/write position in the packet */
PacketSize pos;
- /** The buffer of this packet, of basically variable length up to SEND_MTU. */
+ /** The buffer of this packet, of basically variable length up to SHRT_MAX. */
byte *buffer;
private:
@@ -73,6 +73,7 @@
void Send_uint32(uint32 data);
void Send_uint64(uint64 data);
void Send_string(const char *data);
+ void Send_binary(const char *data, const size_t size);
/* Reading/receiving of packets */
void ReadRawPacketSize();
@@ -85,6 +86,7 @@
uint32 Recv_uint32();
uint64 Recv_uint64();
void Recv_string(char *buffer, size_t size, StringValidationSettings settings = SVS_REPLACE_WITH_QUESTION_MARK);
+ void Recv_binary(char *buffer, size_t size);
};
#endif /* ENABLE_NETWORK */
Index: src/network/network_client.cpp
===================================================================
--- src/network/network_client.cpp (revision 27386)
+++ src/network/network_client.cpp (working copy)
@@ -862,7 +862,7 @@
* the server will give us a client-id and let us in */
_network_join_status = NETWORK_JOIN_STATUS_REGISTERING;
ShowJoinStatusWindow();
- NetworkSendCommand(0, 0, 0, CMD_COMPANY_CTRL, NULL, NULL, _local_company);
+ NetworkSendCommand(0, 0, 0, CMD_COMPANY_CTRL, NULL, NULL, _local_company, 0);
}
} else {
/* take control over an existing company */
Index: src/network/network_command.cpp
===================================================================
--- src/network/network_command.cpp (revision 27386)
+++ src/network/network_command.cpp (working copy)
@@ -51,6 +51,7 @@
/* 0x19 */ CcStartStopVehicle,
/* 0x1A */ CcGame,
/* 0x1B */ CcAddVehicleNewGroup,
+ /* 0x1C */ CcAddPlan,
};
/**
@@ -136,8 +137,9 @@
* @param callback A callback function to call after the command is finished
* @param text The text to pass
* @param company The company that wants to send the command
+ * @param binary_length The quantity of binary data in text
*/
-void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, CompanyID company)
+void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, CompanyID company, uint32 binary_length)
{
assert((cmd & CMD_FLAGS_MASK) == 0);
@@ -149,7 +151,12 @@
c.cmd = cmd;
c.callback = callback;
- strecpy(c.text, (text != NULL) ? text : "", lastof(c.text));
+ c.binary_length = binary_length;
+ if (binary_length == 0) {
+ strecpy(c.text, (text != NULL) ? text : "", lastof(c.text));
+ } else {
+ memcpy(c.text, text, binary_length);
+ }
if (_network_server) {
/* If we are the server, we queue the command in our 'special' queue.
@@ -310,7 +317,13 @@
cp->p1 = p->Recv_uint32();
cp->p2 = p->Recv_uint32();
cp->tile = p->Recv_uint32();
- p->Recv_string(cp->text, lengthof(cp->text), (!_network_server && GetCommandFlags(cp->cmd) & CMD_STR_CTRL) != 0 ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK);
+ cp->binary_length = p->Recv_uint32();
+ if (cp->binary_length == 0) {
+ p->Recv_string(cp->text, lengthof(cp->text), (!_network_server && GetCommandFlags(cp->cmd) & CMD_STR_CTRL) != 0 ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK);
+ } else {
+ if ((p->pos + (PacketSize) cp->binary_length + /* callback index */ 1) > p->size) return "invalid binary data length";
+ p->Recv_binary(cp->text, cp->binary_length);
+ }
byte callback = p->Recv_uint8();
if (callback >= lengthof(_callback_table)) return "invalid callback";
@@ -331,7 +344,12 @@
p->Send_uint32(cp->p1);
p->Send_uint32(cp->p2);
p->Send_uint32(cp->tile);
- p->Send_string(cp->text);
+ p->Send_uint32(cp->binary_length);
+ if (cp->binary_length == 0) {
+ p->Send_string(cp->text);
+ } else {
+ p->Send_binary(cp->text, cp->binary_length);
+ }
byte callback = 0;
while (callback < lengthof(_callback_table) && _callback_table[callback] != cp->callback) {
Index: src/network/network_gui.cpp
===================================================================
--- src/network/network_gui.cpp (revision 27386)
+++ src/network/network_gui.cpp (working copy)
@@ -31,6 +31,8 @@
#include "../core/geometry_func.hpp"
#include "../genworld.h"
#include "../map_type.h"
+#include "../newgrf.h"
+#include "../error.h"
#include "../widgets/network_widget.h"
@@ -1177,6 +1179,10 @@
}
case WID_NSS_GENERATE_GAME: // Start game
+ if (CountSelectedGRFs (_grfconfig_newgame) >= MAX_FILE_SLOTS_IN_NETWORK) {
+ ShowErrorMessage(STR_NEWGRF_ERROR_TOO_MANY_NEWGRFS_LOADED, INVALID_STRING_ID, WL_ERROR);
+ break;
+ }
_is_network_server = true;
if (_ctrl_pressed) {
StartNewGameWithoutGUI(GENERATE_NEW_SEED);
Index: src/network/network_server.cpp
===================================================================
--- src/network/network_server.cpp (revision 27386)
+++ src/network/network_server.cpp (working copy)
@@ -2192,7 +2192,7 @@
/* ci is NULL when replaying, or for AIs. In neither case there is a client. */
ci->client_playas = c->index;
NetworkUpdateClientInfo(ci->client_id);
- NetworkSendCommand(0, 0, 0, CMD_RENAME_PRESIDENT, NULL, ci->client_name, c->index);
+ NetworkSendCommand(0, 0, 0, CMD_RENAME_PRESIDENT, NULL, ci->client_name, c->index, 0);
}
/* Announce new company on network. */
Index: src/newgrf.cpp
===================================================================
--- src/newgrf.cpp (revision 27386)
+++ src/newgrf.cpp (working copy)
@@ -9299,3 +9299,16 @@
_tick_counter = tick_counter;
_display_opt = display_opt;
}
+
+/**
+ * Returns amount of user selected NewGRFs files.
+ */
+int CountSelectedGRFs(GRFConfig *grfconf)
+{
+ int i = 0;
+
+ /* Find last entry in the list */
+ for (const GRFConfig *list = grfconf; list != NULL; list = list->next, i++) {
+ }
+ return i;
+}
Index: src/newgrf.h
===================================================================
--- src/newgrf.h (revision 27386)
+++ src/newgrf.h (working copy)
@@ -195,5 +195,6 @@
StringID MapGRFStringID(uint32 grfid, StringID str);
void ShowNewGRFError();
+int CountSelectedGRFs(GRFConfig *grfconf);
#endif /* NEWGRF_H */
Index: src/newgrf_animation_base.h
===================================================================
--- src/newgrf_animation_base.h (revision 27386)
+++ src/newgrf_animation_base.h (working copy)
@@ -102,7 +102,7 @@
}
SetAnimationFrame(tile, frame);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
/**
Index: src/newgrf_house.cpp
===================================================================
--- src/newgrf_house.cpp (revision 27386)
+++ src/newgrf_house.cpp (working copy)
@@ -14,6 +14,7 @@
#include "landscape.h"
#include "newgrf_house.h"
#include "newgrf_spritegroup.h"
+#include "newgrf_text.h"
#include "newgrf_town.h"
#include "newgrf_sound.h"
#include "company_func.h"
@@ -26,6 +27,8 @@
#include "safeguards.h"
+#include "table/strings.h"
+
static BuildingCounts _building_counts;
static HouseClassMapping _class_mapping[HOUSE_CLASS_MAX];
@@ -67,7 +70,7 @@
/**
* Construct a resolver for a house.
* @param house_id House to query.
- * @param tile %Tile containing the house.
+ * @param tile %Tile containing the house. INVALID_TILE to query a house type rather then a certian house tile.
* @param town %Town containing the house.
* @param callback Callback ID.
* @param param1 First parameter (var 10) of the callback.
@@ -79,13 +82,28 @@
HouseResolverObject::HouseResolverObject(HouseID house_id, TileIndex tile, Town *town,
CallbackID callback, uint32 param1, uint32 param2,
bool not_yet_constructed, uint8 initial_random_bits, uint32 watched_cargo_triggers)
- : ResolverObject(GetHouseSpecGrf(house_id), callback, param1, param2),
- house_scope(*this, house_id, tile, town, not_yet_constructed, initial_random_bits, watched_cargo_triggers),
- town_scope(*this, town, not_yet_constructed) // Don't access StorePSA if house is not yet constructed.
+ : ResolverObject(GetHouseSpecGrf(house_id), callback, param1, param2)
{
+ assert((tile != INVALID_TILE) == (town != NULL));
+ assert(tile == INVALID_TILE || (not_yet_constructed ? IsValidTile(tile) : GetHouseType(tile) == house_id && Town::GetByTile(tile) == town));
+
+ this->house_scope = (tile != INVALID_TILE) ?
+ (ScopeResolver*)new HouseScopeResolver(*this, house_id, tile, town, not_yet_constructed, initial_random_bits, watched_cargo_triggers) :
+ (ScopeResolver*)new FakeHouseScopeResolver(*this, house_id);
+
+ this->town_scope = (town != NULL) ?
+ (ScopeResolver*)new TownScopeResolver(*this, town, not_yet_constructed) : // Don't access StorePSA if house is not yet constructed.
+ (ScopeResolver*)new FakeTownScopeResolver(*this);
+
this->root_spritegroup = HouseSpec::Get(house_id)->grf_prop.spritegroup[0];
}
+/* virtual */ HouseResolverObject::~HouseResolverObject()
+{
+ delete this->house_scope;
+ delete this->town_scope;
+}
+
HouseClassID AllocateHouseClassID(byte grf_class_id, uint32 grfid)
{
/* Start from 1 because 0 means that no class has been assigned. */
@@ -435,29 +453,120 @@
return UINT_MAX;
}
+
+/**
+ * @note Used by the resolver to get values for feature 07 deterministic spritegroups.
+ */
+/* virtual */ uint32 FakeHouseScopeResolver::GetVariable(byte variable, uint32 parameter, bool *available) const
+{
+ switch (variable) {
+ /* Construction stage. */
+ case 0x40: return TOWN_HOUSE_COMPLETED;
+
+ /* Building age. */
+ case 0x41: return 0;
+
+ /* Town zone */
+ case 0x42: return FIND_FIRST_BIT(HouseSpec::Get(this->house_id)->building_availability & HZ_ZONALL); // first available
+
+ /* Terrain type */
+ case 0x43: return _settings_game.game_creation.landscape == LT_ARCTIC && (HouseSpec::Get(house_id)->building_availability & (HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW)) == HZ_SUBARTC_ABOVE ? 4 : 0;
+
+ /* Number of this type of building on the map. */
+ case 0x44: return 0;
+
+ /* Whether the town is being created or just expanded. */
+ case 0x45: return 0;
+
+ /* Current animation frame. */
+ case 0x46: return 0;
+
+ /* Position of the house */
+ case 0x47: return 0xFFFFFFFF;
+
+ /* Building counts for old houses with id = parameter. */
+ case 0x60: return 0;
+
+ /* Building counts for new houses with id = parameter. */
+ case 0x61: return 0;
+
+ /* Land info for nearby tiles. */
+ case 0x62: return 0;
+
+ /* Current animation frame of nearby house tiles */
+ case 0x63: return 0;
+
+ /* Cargo acceptance history of nearby stations */
+ case 0x64: return 0;
+
+ /* Distance test for some house types */
+ case 0x65: return 0;
+
+ /* Class and ID of nearby house tile */
+ case 0x66: return 0xFFFFFFFF;
+
+ /* GRFID of nearby house tile */
+ case 0x67: return 0xFFFFFFFF;
+ }
+
+ DEBUG(grf, 1, "Unhandled house variable 0x%X", variable);
+
+ *available = false;
+ return UINT_MAX;
+}
+
uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town, TileIndex tile,
bool not_yet_constructed, uint8 initial_random_bits, uint32 watched_cargo_triggers)
{
- assert(IsValidTile(tile) && (not_yet_constructed || IsTileType(tile, MP_HOUSE)));
-
HouseResolverObject object(house_id, tile, town, callback, param1, param2,
not_yet_constructed, initial_random_bits, watched_cargo_triggers);
return object.ResolveCallback();
}
-static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte stage, HouseID house_id)
+/**
+ * Get the name of a house.
+ * @param house House type.
+ * @param tile Tile where the house is located. INVALID_TILE to get the general name of houses of the given type.
+ * @return Name of the house.
+ */
+StringID GetHouseName(HouseID house_id, TileIndex tile)
{
- const DrawTileSprites *dts = group->ProcessRegisters(&stage);
+ const HouseSpec *hs = HouseSpec::Get(house_id);
+ bool house_completed = (tile == INVALID_TILE) || IsHouseCompleted(tile);
+ Town *t = (tile == INVALID_TILE) ? NULL : Town::GetByTile(tile);
+ uint16 callback_res = GetHouseCallback(CBID_HOUSE_CUSTOM_NAME, house_completed ? 1 : 0, 0, house_id, t, tile);
+ if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
+ if (callback_res > 0x400) {
+ ErrorUnknownCallbackResult(hs->grf_prop.grffile->grfid, CBID_HOUSE_CUSTOM_NAME, callback_res);
+ } else {
+ StringID ret = GetGRFStringID(hs->grf_prop.grffile->grfid, 0xD000 + callback_res);
+ if (ret != STR_NULL && ret != STR_UNDEFINED) return ret;
+ }
+ }
+
+ return hs->building_name;
+}
+
+static inline PaletteID GetHouseColour(HouseID house_id, TileIndex tile = INVALID_TILE)
+{
const HouseSpec *hs = HouseSpec::Get(house_id);
- PaletteID palette = hs->random_colour[TileHash2Bit(ti->x, ti->y)] + PALETTE_RECOLOUR_START;
if (HasBit(hs->callback_mask, CBM_HOUSE_COLOUR)) {
- uint16 callback = GetHouseCallback(CBID_HOUSE_COLOUR, 0, 0, house_id, Town::GetByTile(ti->tile), ti->tile);
+ Town *t = (tile != INVALID_TILE) ? Town::GetByTile(tile) : NULL;
+ uint16 callback = GetHouseCallback(CBID_HOUSE_COLOUR, 0, 0, house_id, t, tile);
if (callback != CALLBACK_FAILED) {
/* If bit 14 is set, we should use a 2cc colour map, else use the callback value. */
- palette = HasBit(callback, 14) ? GB(callback, 0, 8) + SPR_2CCMAP_BASE : callback;
+ return HasBit(callback, 14) ? GB(callback, 0, 8) + SPR_2CCMAP_BASE : callback;
}
}
+ return hs->random_colour[TileHash2Bit(TileX(tile), TileY(tile))] + PALETTE_RECOLOUR_START;
+}
+
+static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte stage, HouseID house_id)
+{
+ const DrawTileSprites *dts = group->ProcessRegisters(&stage);
+
+ PaletteID palette = GetHouseColour(house_id, ti->tile);
SpriteID image = dts->ground.sprite;
PaletteID pal = dts->ground.pal;
@@ -472,6 +581,26 @@
DrawNewGRFTileSeq(ti, dts, TO_HOUSES, stage, palette);
}
+static void DrawTileLayoutInGUI(int x, int y, const TileLayoutSpriteGroup *group, HouseID house_id, bool ground)
+{
+ byte stage = TOWN_HOUSE_COMPLETED;
+ const DrawTileSprites *dts = group->ProcessRegisters(&stage);
+
+ PaletteID palette = GetHouseColour(house_id);
+
+ if (ground) {
+ PalSpriteID image = dts->ground;
+ if (HasBit(image.sprite, SPRITE_MODIFIER_CUSTOM_SPRITE)) image.sprite += stage;
+ if (HasBit(image.pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) image.pal += stage;
+
+ if (GB(image.sprite, 0, SPRITE_WIDTH) != 0) {
+ DrawSprite(image.sprite, GroundSpritePaletteTransform(image.sprite, image.pal, palette), x, y);
+ }
+ } else {
+ DrawNewGRFTileSeqInGUI(x, y, dts, stage, palette);
+ }
+}
+
void DrawNewHouseTile(TileInfo *ti, HouseID house_id)
{
const HouseSpec *hs = HouseSpec::Get(house_id);
@@ -498,6 +627,15 @@
}
}
+void DrawNewHouseTileInGUI(int x, int y, HouseID house_id, bool ground)
+{
+ HouseResolverObject object(house_id);
+ const SpriteGroup *group = object.Resolve();
+ if (group != NULL && group->type == SGT_TILELAYOUT) {
+ DrawTileLayoutInGUI(x, y, (const TileLayoutSpriteGroup*)group, house_id, ground);
+ }
+}
+
/* Simple wrapper for GetHouseCallback to keep the animation unified. */
uint16 GetSimpleHouseCallback(CallbackID callback, uint32 param1, uint32 param2, const HouseSpec *spec, Town *town, TileIndex tile, uint32 extra_data)
{
@@ -530,6 +668,26 @@
}
}
+/**
+ * Check if GRF allows a given house to be constructed (callback 17)
+ * @param house_id house type
+ * @param tile tile where the house is about to be placed
+ * @param t town in which we are building
+ * @param random_bits feature random bits for the house
+ * @return false if callback 17 disallows construction, true in other cases
+ */
+bool HouseAllowsConstruction(HouseID house_id, TileIndex tile, Town *t, byte random_bits)
+{
+ const HouseSpec *hs = HouseSpec::Get(house_id);
+ if (HasBit(hs->callback_mask, CBM_HOUSE_ALLOW_CONSTRUCTION)) {
+ uint16 callback_res = GetHouseCallback(CBID_HOUSE_ALLOW_CONSTRUCTION, 0, 0, house_id, t, tile, true, random_bits);
+ if (callback_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(hs->grf_prop.grffile, CBID_HOUSE_ALLOW_CONSTRUCTION, callback_res)) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool CanDeleteHouse(TileIndex tile)
{
const HouseSpec *hs = HouseSpec::Get(GetHouseType(tile));
@@ -597,7 +755,7 @@
}
SetHouseProcessingTime(tile, hs->processing_time);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
return true;
}
@@ -632,7 +790,7 @@
case HOUSE_TRIGGER_TILE_LOOP_TOP:
if (!first) {
/* The top tile is marked dirty by the usual TileLoop */
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
break;
}
/* Random value of first tile already set. */
Index: src/newgrf_house.h
===================================================================
--- src/newgrf_house.h (revision 27386)
+++ src/newgrf_house.h (working copy)
@@ -36,20 +36,43 @@
/* virtual */ void SetTriggers(int triggers) const;
};
+/**
+ * Fake scope resolver for nonexistent houses.
+ *
+ * The purpose of this class is to provide a house resolver for a given house type
+ * but not an actual house instatntion. We need this when e.g. drawing houses in
+ * GUI to keep backward compatibility with GRFs that were created before this
+ * functionality. When querying house sprites, certain GRF may read various house
+ * variables e.g. the town zone where the building is located or the XY coordinates.
+ * Since the building doesn't exists we have no real values that we can return.
+ * Instead of failing, this resolver will return fake values.
+ */
+struct FakeHouseScopeResolver : public ScopeResolver {
+ HouseID house_id; ///< Type of house being queried.
+
+ FakeHouseScopeResolver(ResolverObject &ro, HouseID house_id)
+ : ScopeResolver(ro), house_id(house_id)
+ { }
+
+ /* virtual */ uint32 GetVariable(byte variable, uint32 parameter, bool *available) const;
+};
+
/** Resolver object to be used for houses (feature 07 spritegroups). */
struct HouseResolverObject : public ResolverObject {
- HouseScopeResolver house_scope;
- TownScopeResolver town_scope;
+ ScopeResolver *house_scope;
+ ScopeResolver *town_scope;
- HouseResolverObject(HouseID house_id, TileIndex tile, Town *town,
+ HouseResolverObject(HouseID house_id, TileIndex tile = INVALID_TILE, Town *town = NULL,
CallbackID callback = CBID_NO_CALLBACK, uint32 param1 = 0, uint32 param2 = 0,
bool not_yet_constructed = false, uint8 initial_random_bits = 0, uint32 watched_cargo_triggers = 0);
+ /* virtual */ ~HouseResolverObject();
+
/* virtual */ ScopeResolver *GetScope(VarSpriteGroupScope scope = VSG_SCOPE_SELF, byte relative = 0)
{
switch (scope) {
- case VSG_SCOPE_SELF: return &this->house_scope;
- case VSG_SCOPE_PARENT: return &this->town_scope;
+ case VSG_SCOPE_SELF: return this->house_scope;
+ case VSG_SCOPE_PARENT: return this->town_scope;
default: return ResolverObject::GetScope(scope, relative);
}
}
@@ -80,13 +103,15 @@
void DecreaseBuildingCount(Town *t, HouseID house_id);
void DrawNewHouseTile(TileInfo *ti, HouseID house_id);
+void DrawNewHouseTileInGUI(int x, int y, HouseID house_id, bool ground);
void AnimateNewHouseTile(TileIndex tile);
void AnimateNewHouseConstruction(TileIndex tile);
-uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town, TileIndex tile,
+uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town = NULL, TileIndex tile = INVALID_TILE,
bool not_yet_constructed = false, uint8 initial_random_bits = 0, uint32 watched_cargo_triggers = 0);
void WatchedCargoCallback(TileIndex tile, uint32 trigger_cargoes);
+bool HouseAllowsConstruction(HouseID house_id, TileIndex tile, Town *t, byte random_bits);
bool CanDeleteHouse(TileIndex tile);
bool NewHouseTileLoop(TileIndex tile);
Index: src/newgrf_industrytiles.cpp
===================================================================
--- src/newgrf_industrytiles.cpp (revision 27386)
+++ src/newgrf_industrytiles.cpp (working copy)
@@ -336,7 +336,7 @@
random_bits &= ~object.reseed[VSG_SCOPE_SELF];
random_bits |= new_random_bits & object.reseed[VSG_SCOPE_SELF];
SetIndustryRandomBits(tile, random_bits);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
reseed_industry |= object.reseed[VSG_SCOPE_PARENT];
}
Index: src/newgrf_station.cpp
===================================================================
--- src/newgrf_station.cpp (revision 27386)
+++ src/newgrf_station.cpp (working copy)
@@ -1030,7 +1030,7 @@
random_bits |= Random() & reseed;
SetStationTileRandomBits(tile, random_bits);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
}
Index: src/newgrf_town.cpp
===================================================================
--- src/newgrf_town.cpp (revision 27386)
+++ src/newgrf_town.cpp (working copy)
@@ -162,6 +162,30 @@
t->psa_list.push_back(psa);
}
+/* virtual */ uint32 FakeTownScopeResolver::GetVariable(byte variable, uint32 parameter, bool *available) const
+{
+ switch (variable) {
+ /* Town index */
+ case 0x41: return 0xFFFF;
+
+ case 0x40: case 0x7C: case 0x80: case 0x81: case 0x82: case 0x83: case 0x8A: case 0x92:
+ case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A:
+ case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: case 0xA0: case 0xA1: case 0xA2:
+ case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA:
+ case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xB2: case 0xB6: case 0xB9: case 0xBA:
+ case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xC0: case 0xC1: case 0xC2:
+ case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA:
+ case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2:
+ case 0xD3: case 0xD4: case 0xD5:
+ return 0;
+ }
+
+ DEBUG(grf, 1, "Unhandled town variable 0x%X", variable);
+
+ *available = false;
+ return UINT_MAX;
+}
+
/**
* Resolver for a town.
* @param grffile NewGRF file associated with the town.
Index: src/newgrf_town.h
===================================================================
--- src/newgrf_town.h (revision 27386)
+++ src/newgrf_town.h (working copy)
@@ -31,6 +31,24 @@
virtual void StorePSA(uint reg, int32 value);
};
+/**
+ * Fake scope resolver for nonexistent towns.
+ *
+ * The purpose of this class is to provide a house resolver for a given house type
+ * but not an actual house instatntion. We need this when e.g. drawing houses in
+ * GUI to keep backward compatibility with GRFs that were created before this
+ * functionality. When querying house sprites, certain GRF may read various town
+ * variables e.g. the population. Since the building doesn't exists and is not
+ * bounded to any town we have no real values that we can return. Instead of
+ * failing, this resolver will return fake values.
+ */
+struct FakeTownScopeResolver : public ScopeResolver {
+ FakeTownScopeResolver(ResolverObject &ro) : ScopeResolver(ro)
+ { }
+
+ virtual uint32 GetVariable(byte variable, uint32 parameter, bool *available) const;
+};
+
/** Resolver of town properties. */
struct TownResolverObject : public ResolverObject {
TownScopeResolver town_scope; ///< Scope resolver specific for towns.
Index: src/object_cmd.cpp
===================================================================
--- src/object_cmd.cpp (revision 27386)
+++ src/object_cmd.cpp (working copy)
@@ -124,7 +124,7 @@
DirtyCompanyInfrastructureWindows(owner);
}
MakeObject(t, owner, o->index, wc, Random());
- MarkTileDirtyByTile(t);
+ MarkTileDirtyByTile(t, ZOOM_LVL_DRAW_MAP);
}
Object::IncTypeCount(type);
@@ -140,7 +140,7 @@
TileArea ta = Object::GetByTile(tile)->location;
TILE_AREA_LOOP(t, ta) {
SetAnimationFrame(t, GetAnimationFrame(t) + 1);
- MarkTileDirtyByTile(t);
+ MarkTileDirtyByTile(t, ZOOM_LVL_DRAW_MAP);
}
}
Index: src/openttd.cpp
===================================================================
--- src/openttd.cpp (revision 27386)
+++ src/openttd.cpp (working copy)
@@ -56,6 +56,7 @@
#include "core/backup_type.hpp"
#include "hotkeys.h"
#include "newgrf.h"
+#include "newgrf_commons.h"
#include "misc/getoptdata.h"
#include "game/game.hpp"
#include "game/game_config.hpp"
@@ -63,8 +64,11 @@
#include "subsidy_func.h"
#include "gfx_layout.h"
#include "viewport_sprite_sorter.h"
+#include "programmable_signals.h"
+#include "smallmap_gui.h"
#include "linkgraph/linkgraphschedule.h"
+#include "tracerestrict.h"
#include
@@ -302,8 +306,12 @@
#endif
LinkGraphSchedule::Clear();
+ ClearTraceRestrictMapping();
PoolBase::Clean(PT_ALL);
+ FreeSignalPrograms();
+ FreeSignalDependencies();
+
/* No NewGRFs were loaded when it was still bootstrapping. */
if (_game_mode != GM_BOOTSTRAP) ResetNewGRFData();
@@ -845,6 +853,13 @@
DriverFactoryBase::SelectDriver(musicdriver, Driver::DT_MUSIC);
free(musicdriver);
+ // Check if not too much GRFs are loaded for network game
+ if (dedicated && CountSelectedGRFs( _grfconfig ) >= MAX_FILE_SLOTS_IN_NETWORK) {
+ DEBUG(net, 0, "Too many GRF loaded. Max %d are allowed.\nExiting ...", MAX_FILE_SLOTS_IN_NETWORK);
+ ShutdownGame();
+ goto exit_normal;
+ }
+
/* Take our initial lock on whatever we might want to do! */
_modal_progress_paint_mutex->BeginCritical();
_modal_progress_work_mutex->BeginCritical();
@@ -1178,6 +1193,8 @@
default: NOT_REACHED();
}
+
+ SmallMapWindow::RebuildColourIndexIfNecessary();
}
@@ -1377,11 +1394,17 @@
Backup cur_company(_current_company, OWNER_NONE, FILE_LINE);
BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP);
- AnimateAnimatedTiles();
- IncreaseDate();
- RunTileLoop();
- CallVehicleTicks();
- CallLandscapeTick();
+ _tick_skip_counter++;
+ if (_tick_skip_counter < _settings_game.economy.day_length_factor) {
+ CallVehicleTicks();
+ } else {
+ _tick_skip_counter = 0;
+ AnimateAnimatedTiles();
+ IncreaseDate();
+ RunTileLoop();
+ CallVehicleTicks();
+ CallLandscapeTick();
+ }
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP);
#ifndef DEBUG_DUMP_COMMANDS
Index: src/order_backup.cpp
===================================================================
--- src/order_backup.cpp (revision 27386)
+++ src/order_backup.cpp (working copy)
@@ -210,7 +210,7 @@
/* We need to circumvent the "prevention" from this command being executed
* while the game is paused, so use the internal method. Nor do we want
* this command to get its cost estimated when shift is pressed. */
- DoCommandPInternal(ob->tile, 0, user, CMD_CLEAR_ORDER_BACKUP, NULL, NULL, true, false);
+ DoCommandPInternal(ob->tile, 0, user, CMD_CLEAR_ORDER_BACKUP, NULL, NULL, true, false, 0);
} else {
/* The command came from the game logic, i.e. the clearing of a tile.
* In that case we have no need to actually sync this, just do it. */
Index: src/order_base.h
===================================================================
--- src/order_base.h (revision 27386)
+++ src/order_base.h (working copy)
@@ -43,6 +43,8 @@
CargoID refit_cargo; ///< Refit CargoID
+ uint8 occupancy; ///< Estimate of vehicle occupancy on departure, for the current order, 0 indicates invalid, 1 - 101 indicate 0 - 100%
+
uint16 wait_time; ///< How long in ticks to wait at the destination.
uint16 travel_time; ///< How long in ticks the journey to this destination should take.
uint16 max_speed; ///< How fast the vehicle may go on the way to the destination.
@@ -78,6 +80,7 @@
void MakeDummy();
void MakeConditional(VehicleOrderID order);
void MakeImplicit(StationID destination);
+ void MakeWaiting();
/**
* Is this a 'goto' order with a real destination?
@@ -217,6 +220,18 @@
*/
inline void SetMaxSpeed(uint16 speed) { this->max_speed = speed; }
+ /**
+ * Get the occupancy value
+ * @return occupancy
+ */
+ inline uint8 GetOccupancy() const { return this->occupancy; }
+
+ /**
+ * Set the occupancy value
+ * @param occupancy The occupancy to set
+ */
+ inline void SetOccupancy(uint8 occupancy) { this->occupancy = occupancy; }
+
bool ShouldStopAtStation(const Vehicle *v, StationID station) const;
bool CanLoadOrUnload() const;
bool CanLeaveWithCargo(bool has_cargo) const;
Index: src/order_cmd.cpp
===================================================================
--- src/order_cmd.cpp (revision 27386)
+++ src/order_cmd.cpp (working copy)
@@ -26,6 +26,7 @@
#include "station_base.h"
#include "waypoint_base.h"
#include "company_base.h"
+#include "infrastructure_func.h"
#include "order_backup.h"
#include "cheat_type.h"
@@ -158,6 +159,11 @@
this->dest = destination;
}
+void Order::MakeWaiting()
+{
+ this->type = OT_WAITING;
+}
+
/**
* Make this depot/station order also a refit order.
* @param cargo the cargo type to change to.
@@ -239,6 +245,7 @@
this->dest = GB(packed, 16, 16);
this->next = NULL;
this->refit_cargo = CT_NO_REFIT;
+ this->occupancy = 0;
this->wait_time = 0;
this->travel_time = 0;
this->max_speed = UINT16_MAX;
@@ -745,7 +752,7 @@
if (st == NULL) return CMD_ERROR;
if (st->owner != OWNER_NONE) {
- CommandCost ret = CheckOwnership(st->owner);
+ CommandCost ret = CheckInfraUsageAllowed(v->type, st->owner);
if (ret.Failed()) return ret;
}
@@ -790,7 +797,7 @@
if (st == NULL) return CMD_ERROR;
- CommandCost ret = CheckOwnership(st->owner);
+ CommandCost ret = CheckInfraUsageAllowed(v->type, st->owner);
if (ret.Failed()) return ret;
if (!CanVehicleUseStation(v, st) || !st->airport.HasHangar()) {
@@ -801,7 +808,7 @@
if (dp == NULL) return CMD_ERROR;
- CommandCost ret = CheckOwnership(GetTileOwner(dp->xy));
+ CommandCost ret = CheckInfraUsageAllowed(v->type, GetTileOwner(dp->xy), dp->xy);
if (ret.Failed()) return ret;
switch (v->type) {
@@ -839,7 +846,7 @@
case VEH_TRAIN: {
if (!(wp->facilities & FACIL_TRAIN)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER);
- CommandCost ret = CheckOwnership(wp->owner);
+ CommandCost ret = CheckInfraUsageAllowed(v->type, wp->owner);
if (ret.Failed()) return ret;
break;
}
@@ -847,7 +854,7 @@
case VEH_SHIP:
if (!(wp->facilities & FACIL_DOCK)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER);
if (wp->owner != OWNER_NONE) {
- CommandCost ret = CheckOwnership(wp->owner);
+ CommandCost ret = CheckInfraUsageAllowed(v->type, wp->owner);
if (ret.Failed()) return ret;
}
break;
@@ -1155,11 +1162,15 @@
if (flags & DC_EXEC) {
if (v->current_order.IsType(OT_LOADING)) v->LeaveStation();
+ if (v->current_order.IsType(OT_WAITING)) v->HandleWaiting(true);
v->cur_implicit_order_index = v->cur_real_order_index = sel_ord;
v->UpdateRealOrderIndex();
InvalidateVehicleOrder(v, VIWD_MODIFY_ORDERS);
+
+ if (_settings_game.order.timetable_separation) v->ClearSeparation();
+ if (_settings_game.order.timetable_separation) ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
}
/* We have an aircraft/ship, they have a mini-schedule, so update them all */
@@ -1617,9 +1628,19 @@
/* Link this vehicle in the shared-list */
dst->AddToShared(src);
+
+ /* Set automation bit if target has it. */
+ if (HasBit(src->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
+ SetBit(dst->vehicle_flags, VF_AUTOMATE_TIMETABLE);
+ }
+
+ if (_settings_game.order.timetable_separation) dst->ClearSeparation();
+ if (_settings_game.order.timetable_separation) ClrBit(dst->vehicle_flags, VF_TIMETABLE_STARTED);
+
InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS);
InvalidateVehicleOrder(src, VIWD_MODIFY_ORDERS);
+
InvalidateWindowClassesData(GetWindowClassForVehicleType(dst->type), 0);
}
break;
@@ -2165,6 +2186,9 @@
case OT_LOADING:
return false;
+ case OT_WAITING:
+ return false;
+
case OT_LEAVESTATION:
if (v->type != VEH_AIRCRAFT) return false;
break;
Index: src/order_gui.cpp
===================================================================
--- src/order_gui.cpp (revision 27386)
+++ src/order_gui.cpp (working copy)
@@ -26,6 +26,7 @@
#include "station_base.h"
#include "waypoint_base.h"
#include "core/geometry_func.hpp"
+#include "infrastructure_func.h"
#include "hotkeys.h"
#include "aircraft.h"
#include "engine_func.h"
@@ -312,6 +313,13 @@
SetDParam(5, (order->GetDepotActionType() & ODATFB_HALT) ? STR_ORDER_REFIT_STOP_ORDER : STR_ORDER_REFIT_ORDER);
SetDParam(6, CargoSpec::Get(order->GetRefitCargo())->name);
}
+
+ if (timetable) {
+ if (order->GetWaitTime() > 0) {
+ SetDParam(5, order->IsWaitTimetabled() ? STR_TIMETABLE_STAY_FOR : STR_TIMETABLE_STAY_FOR_ESTIMATED);
+ SetTimetableParams(6, 7, order->GetWaitTime());
+ }
+ }
break;
case OT_GOTO_WAYPOINT:
@@ -363,7 +371,7 @@
order.index = 0;
/* check depot first */
- if (IsDepotTypeTile(tile, (TransportType)(uint)v->type) && IsTileOwner(tile, _local_company)) {
+ if (IsDepotTypeTile(tile, (TransportType)(uint)v->type) && IsInfraTileUsageAllowed(v->type, v->owner, tile)) {
order.MakeGoToDepot(v->type == VEH_AIRCRAFT ? GetStationIndex(tile) : GetDepotIndex(tile),
ODTFB_PART_OF_ORDERS,
(_settings_client.gui.new_nonstop && v->IsGroundVehicle()) ? ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS : ONSF_STOP_EVERYWHERE);
@@ -376,7 +384,7 @@
/* check rail waypoint */
if (IsRailWaypointTile(tile) &&
v->type == VEH_TRAIN &&
- IsTileOwner(tile, _local_company)) {
+ IsInfraTileUsageAllowed(VEH_TRAIN, v->owner, tile)) {
order.MakeGoToWaypoint(GetStationIndex(tile));
if (_settings_client.gui.new_nonstop != _ctrl_pressed) order.SetNonStopType(ONSF_NO_STOP_AT_ANY_STATION);
return order;
@@ -392,7 +400,7 @@
StationID st_index = GetStationIndex(tile);
const Station *st = Station::Get(st_index);
- if (st->owner == _local_company || st->owner == OWNER_NONE) {
+ if (IsInfraUsageAllowed(v->type, v->owner, st->owner)) {
byte facil;
(facil = FACIL_DOCK, v->type == VEH_SHIP) ||
(facil = FACIL_TRAIN, v->type == VEH_TRAIN) ||
@@ -617,6 +625,8 @@
order.SetDepotActionType(ODATFB_NEAREST_DEPOT);
DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 20), order.Pack(), CMD_INSERT_ORDER | CMD_MSG(STR_ERROR_CAN_T_INSERT_NEW_ORDER));
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
}
/**
@@ -702,6 +712,8 @@
/* When networking, move one order lower */
int selected = this->selected_order + (int)_networking;
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
if (DoCommandP(this->vehicle->tile, this->vehicle->index, this->OrderGetSel(), CMD_DELETE_ORDER | CMD_MSG(STR_ERROR_CAN_T_DELETE_THIS_ORDER))) {
this->selected_order = selected >= this->vehicle->GetNumOrders() ? -1 : selected;
this->UpdateButtonState();
@@ -770,6 +782,7 @@
this->CreateNestedTree();
this->vscroll = this->GetScrollbar(WID_O_SCROLLBAR);
+ this->GetWidget(WID_O_SEL_OCCUPANCY)->SetDisplayedPlane(SZSP_NONE);
this->FinishInitNested(v->index);
if (v->owner == _local_company) {
this->DisableWidget(WID_O_EMPTY);
@@ -795,9 +808,22 @@
this->OnInvalidateData(VIWD_MODIFY_ORDERS);
}
+ ~OrdersWindow()
+ {
+ if (!FocusWindowById(WC_VEHICLE_VIEW, this->window_number)) {
+ MarkAllRouteStepsDirty(this);
+ }
+ }
+
virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
{
switch (widget) {
+ case WID_O_OCCUPANCY_LIST:
+ SetDParamMaxValue(0, 100);
+ size->width = WD_FRAMERECT_LEFT + GetStringBoundingBox(STR_ORDERS_OCCUPANCY_PERCENT).width + 10 + WD_FRAMERECT_RIGHT;
+ /* FALL THROUGH */
+
+ case WID_O_SEL_OCCUPANCY:
case WID_O_ORDER_LIST:
resize->height = FONT_HEIGHT_NORMAL;
size->height = 6 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
@@ -1064,6 +1090,8 @@
/* Disable list of vehicles with the same shared orders if there is no list */
this->SetWidgetDisabledState(WID_O_SHARED_ORDER_LIST, !shared_orders);
+ this->GetWidget(WID_O_SEL_OCCUPANCY)->SetDisplayedPlane(IsWidgetLowered(WID_O_OCCUPANCY_TOGGLE) ? 0 : SZSP_NONE);
+
this->SetDirty();
}
@@ -1079,8 +1107,19 @@
virtual void DrawWidget(const Rect &r, int widget) const
{
- if (widget != WID_O_ORDER_LIST) return;
+ switch (widget) {
+ case WID_O_ORDER_LIST:
+ DrawOrderListWidget(r);
+ break;
+ case WID_O_OCCUPANCY_LIST:
+ DrawOccupancyListWidget(r);
+ break;
+ }
+ }
+
+ void DrawOrderListWidget(const Rect &r) const
+ {
bool rtl = _current_text_dir == TD_RTL;
SetDParamMaxValue(0, this->vehicle->GetNumOrders(), 2);
int index_column_width = GetStringBoundingBox(STR_ORDER_INDEX).width + 2 * GetSpriteSize(rtl ? SPR_ARROW_RIGHT : SPR_ARROW_LEFT).width + 3;
@@ -1135,6 +1174,30 @@
}
}
+ void DrawOccupancyListWidget(const Rect &r) const
+ {
+ int y = r.top + WD_FRAMERECT_TOP;
+ int line_height = this->GetWidget(WID_O_ORDER_LIST)->resize_y;
+
+ int i = this->vscroll->GetPosition();
+ const Order *order = this->vehicle->GetOrder(i);
+ /* Draw the orders. */
+ while (order != NULL) {
+ /* Don't draw anything if it extends past the end of the window. */
+ if (!this->vscroll->IsVisible(i)) break;
+
+ uint8 occupancy = order->GetOccupancy();
+ if (occupancy > 0) {
+ SetDParam(0, occupancy - 1);
+ DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_ORDERS_OCCUPANCY_PERCENT, (i == this->selected_order) ? TC_WHITE : TC_BLACK);
+ }
+ y += line_height;
+
+ i++;
+ order = order->next;
+ }
+ }
+
virtual void SetStringParameters(int widget) const
{
switch (widget) {
@@ -1169,6 +1232,8 @@
order.MakeConditional(order_id);
DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 20), order.Pack(), CMD_INSERT_ORDER | CMD_MSG(STR_ERROR_CAN_T_INSERT_NEW_ORDER));
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
}
ResetObjectToPlace();
break;
@@ -1227,7 +1292,7 @@
} else {
const Order *o = this->vehicle->GetOrder(this->OrderGetSel());
ShowDropDownMenu(this, _order_non_stop_drowdown, o->GetNonStopType(), WID_O_NON_STOP, 0,
- o->IsType(OT_GOTO_STATION) ? 0 : (o->IsType(OT_GOTO_WAYPOINT) ? 3 : 12));
+ o->IsType(OT_GOTO_STATION) ? 0 : (o->IsType(OT_GOTO_WAYPOINT) ? 3 : 12), 0, DDSF_LOST_FOCUS);
}
break;
@@ -1247,7 +1312,7 @@
case OPOS_SHARE: sel = 3; break;
default: NOT_REACHED();
}
- ShowDropDownMenu(this, this->vehicle->type == VEH_AIRCRAFT ? _order_goto_dropdown_aircraft : _order_goto_dropdown, sel, WID_O_GOTO, 0, 0);
+ ShowDropDownMenu(this, this->vehicle->type == VEH_AIRCRAFT ? _order_goto_dropdown_aircraft : _order_goto_dropdown, sel, WID_O_GOTO, 0, 0, 0, DDSF_LOST_FOCUS);
}
break;
@@ -1255,7 +1320,7 @@
if (this->GetWidget(widget)->ButtonHit(pt)) {
this->OrderClick_FullLoad(-1);
} else {
- ShowDropDownMenu(this, _order_full_load_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetLoadType(), WID_O_FULL_LOAD, 0, 2);
+ ShowDropDownMenu(this, _order_full_load_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetLoadType(), WID_O_FULL_LOAD, 0, 2, 0, DDSF_LOST_FOCUS);
}
break;
@@ -1263,7 +1328,7 @@
if (this->GetWidget(widget)->ButtonHit(pt)) {
this->OrderClick_Unload(-1);
} else {
- ShowDropDownMenu(this, _order_unload_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetUnloadType(), WID_O_UNLOAD, 0, 8);
+ ShowDropDownMenu(this, _order_unload_drowdown, this->vehicle->GetOrder(this->OrderGetSel())->GetUnloadType(), WID_O_UNLOAD, 0, 8, 0, DDSF_LOST_FOCUS);
}
break;
@@ -1275,7 +1340,7 @@
if (this->GetWidget(widget)->ButtonHit(pt)) {
this->OrderClick_Service(-1);
} else {
- ShowDropDownMenu(this, _order_depot_action_dropdown, DepotActionStringIndex(this->vehicle->GetOrder(this->OrderGetSel())), WID_O_SERVICE, 0, 0);
+ ShowDropDownMenu(this, _order_depot_action_dropdown, DepotActionStringIndex(this->vehicle->GetOrder(this->OrderGetSel())), WID_O_SERVICE, 0, 0, 0, DDSF_LOST_FOCUS);
}
break;
@@ -1283,7 +1348,7 @@
if (this->GetWidget(widget)->ButtonHit(pt)) {
this->OrderClick_Refit(0, true);
} else {
- ShowDropDownMenu(this, _order_refit_action_dropdown, 0, WID_O_REFIT_DROPDOWN, 0, 0);
+ ShowDropDownMenu(this, _order_refit_action_dropdown, 0, WID_O_REFIT_DROPDOWN, 0, 0, 0, DDSF_LOST_FOCUS);
}
break;
@@ -1302,7 +1367,7 @@
case WID_O_COND_COMPARATOR: {
const Order *o = this->vehicle->GetOrder(this->OrderGetSel());
- ShowDropDownMenu(this, _order_conditional_condition, o->GetConditionComparator(), WID_O_COND_COMPARATOR, 0, (o->GetConditionVariable() == OCV_REQUIRES_SERVICE) ? 0x3F : 0xC0);
+ ShowDropDownMenu(this, _order_conditional_condition, o->GetConditionComparator(), WID_O_COND_COMPARATOR, 0, (o->GetConditionVariable() == OCV_REQUIRES_SERVICE) ? 0x3F : 0xC0, 0, DDSF_LOST_FOCUS);
break;
}
@@ -1318,6 +1383,12 @@
case WID_O_SHARED_ORDER_LIST:
ShowVehicleListWindow(this->vehicle);
break;
+
+ case WID_O_OCCUPANCY_TOGGLE:
+ ToggleWidgetLoweredState(WID_O_OCCUPANCY_TOGGLE);
+ this->UpdateButtonState();
+ this->ReInit();
+ break;
}
}
@@ -1448,6 +1519,8 @@
if (cmd.IsType(OT_NOTHING)) return;
if (DoCommandP(this->vehicle->tile, this->vehicle->index + (this->OrderGetSel() << 20), cmd.Pack(), CMD_INSERT_ORDER | CMD_MSG(STR_ERROR_CAN_T_INSERT_NEW_ORDER))) {
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
/* With quick goto the Go To button stays active */
if (!_settings_client.gui.quick_goto) ResetObjectToPlace();
}
@@ -1510,6 +1583,27 @@
this->vscroll->SetCapacityFromWidget(this, WID_O_ORDER_LIST);
}
+ virtual void OnFocus(Window *previously_focused_window)
+ {
+ if (HasFocusedVehicleChanged(this->window_number, previously_focused_window)) {
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
+ }
+ }
+
+ virtual void OnFocusLost(Window *newly_focused_window)
+ {
+ if (HasFocusedVehicleChanged(this->window_number, newly_focused_window)) {
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
+ }
+ }
+
+ const Vehicle *GetVehicle()
+ {
+ return this->vehicle;
+ }
+
static HotkeyList hotkeys;
};
@@ -1541,6 +1635,10 @@
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_O_ORDER_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_ORDERS_LIST_TOOLTIP), SetResize(1, 1), SetScrollbar(WID_O_SCROLLBAR), EndContainer(),
+ NWidget(NWID_SELECTION, INVALID_COLOUR, WID_O_SEL_OCCUPANCY),
+ NWidget(WWT_PANEL, COLOUR_GREY, WID_O_OCCUPANCY_LIST), SetMinimalSize(50, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_ORDERS_OCCUPANCY_LIST_TOOLTIP),
+ SetScrollbar(WID_O_SCROLLBAR), EndContainer(),
+ EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_O_SCROLLBAR),
EndContainer(),
@@ -1578,6 +1676,7 @@
SetDataTip(STR_BLACK_COMMA, STR_ORDER_CONDITIONAL_VALUE_TOOLTIP), SetResize(1, 0),
EndContainer(),
EndContainer(),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_O_OCCUPANCY_TOGGLE), SetMinimalSize(12, 12), SetDataTip(STR_ORDERS_OCCUPANCY_BUTTON, STR_ORDERS_OCCUPANCY_BUTTON_TOOLTIP),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_O_SHARED_ORDER_LIST), SetMinimalSize(12, 12), SetDataTip(SPR_SHARED_ORDERS_ICON, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP),
EndContainer(),
@@ -1619,6 +1718,10 @@
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY, WID_O_ORDER_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_ORDERS_LIST_TOOLTIP), SetResize(1, 1), SetScrollbar(WID_O_SCROLLBAR), EndContainer(),
+ NWidget(NWID_SELECTION, INVALID_COLOUR, WID_O_SEL_OCCUPANCY),
+ NWidget(WWT_PANEL, COLOUR_GREY, WID_O_OCCUPANCY_LIST), SetMinimalSize(50, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_ORDERS_OCCUPANCY_LIST_TOOLTIP),
+ SetScrollbar(WID_O_SCROLLBAR), EndContainer(),
+ EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_O_SCROLLBAR),
EndContainer(),
@@ -1653,6 +1756,7 @@
EndContainer(),
EndContainer(),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_O_OCCUPANCY_TOGGLE), SetMinimalSize(12, 12), SetDataTip(STR_ORDERS_OCCUPANCY_BUTTON, STR_ORDERS_OCCUPANCY_BUTTON_TOOLTIP),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_O_SHARED_ORDER_LIST), SetMinimalSize(12, 12), SetDataTip(SPR_SHARED_ORDERS_ICON, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP),
EndContainer(),
Index: src/order_type.h
===================================================================
--- src/order_type.h (revision 27386)
+++ src/order_type.h (working copy)
@@ -45,6 +45,7 @@
OT_GOTO_WAYPOINT = 6,
OT_CONDITIONAL = 7,
OT_IMPLICIT = 8,
+ OT_WAITING = 9,
OT_END
};
@@ -100,6 +101,7 @@
ODTF_MANUAL = 0, ///< Manually initiated order.
ODTFB_SERVICE = 1 << 0, ///< This depot order is because of the servicing limit.
ODTFB_PART_OF_ORDERS = 1 << 1, ///< This depot order is because of a regular order.
+ ODTFB_BREAKDOWN = 1 << 2, ///< This depot order is because of a breakdown.
};
/**
Index: src/osk_gui.cpp
===================================================================
--- src/osk_gui.cpp (revision 27386)
+++ src/osk_gui.cpp (working copy)
@@ -206,7 +206,7 @@
this->parent->SetWidgetDirty(this->text_btn);
}
- virtual void OnFocusLost()
+ virtual void OnFocusLost(Window *newly_focused_window)
{
VideoDriver::GetInstance()->EditBoxLostFocus();
delete this;
Index: src/pathfinder/follow_track.hpp
===================================================================
--- src/pathfinder/follow_track.hpp (revision 27386)
+++ src/pathfinder/follow_track.hpp (working copy)
@@ -19,6 +19,7 @@
#include "../tunnelbridge.h"
#include "../tunnelbridge_map.h"
#include "../depot_map.h"
+#include "../infrastructure_func.h"
#include "pf_performance_timer.hpp"
/**
@@ -299,6 +300,11 @@
m_err = EC_NO_WAY;
return false;
}
+ /* road stops shouldn't be entered unless allowed to */
+ if (!IsInfraTileUsageAllowed(VEH_ROAD, m_veh_owner, m_new_tile)) {
+ m_err = EC_OWNER;
+ return false;
+ }
}
/* single tram bits can only be entered from one direction */
@@ -317,8 +323,8 @@
m_err = EC_NO_WAY;
return false;
}
- /* don't try to enter other company's depots */
- if (GetTileOwner(m_new_tile) != m_veh_owner) {
+ /* don't try to enter other company's depots if not allowed */
+ if (!IsInfraTileUsageAllowed(VEH_ROAD, m_veh_owner, m_new_tile)) {
m_err = EC_OWNER;
return false;
}
@@ -331,8 +337,8 @@
}
}
- /* rail transport is possible only on tiles with the same owner as vehicle */
- if (IsRailTT() && GetTileOwner(m_new_tile) != m_veh_owner) {
+ /* rail transport is possible only on allowed tiles */
+ if (IsRailTT() && !IsInfraTileUsageAllowed(VEH_TRAIN, m_veh_owner, m_new_tile)) {
/* different owner */
m_err = EC_NO_WAY;
return false;
@@ -353,7 +359,7 @@
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;
}
@@ -361,7 +367,7 @@
} 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;
}
Index: src/pathfinder/npf/npf.cpp
===================================================================
--- src/pathfinder/npf/npf.cpp (revision 27386)
+++ src/pathfinder/npf/npf.cpp (working copy)
@@ -14,6 +14,7 @@
#include "../../viewport_func.h"
#include "../../ship.h"
#include "../../roadstop_base.h"
+#include "../../infrastructure_func.h"
#include "../pathfinder_func.h"
#include "../pathfinder_type.h"
#include "../follow_track.hpp"
@@ -281,14 +282,14 @@
/* DEBUG: mark visited tiles by mowing the grass under them ;-) */
if (!IsRailDepot(tile)) {
SetRailGroundType(tile, RAIL_GROUND_BARREN);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
case MP_ROAD:
if (!IsRoadDepot(tile)) {
SetRoadside(tile, ROADSIDE_BARREN);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
@@ -664,25 +665,31 @@
*/
static bool CanEnterTileOwnerCheck(Owner owner, TileIndex tile, DiagDirection enterdir)
{
- if (IsTileType(tile, MP_RAILWAY) || // Rail tile (also rail depot)
- HasStationTileRail(tile) || // Rail station tile/waypoint
- IsRoadDepotTile(tile) || // Road depot tile
- IsStandardRoadStopTile(tile)) { // Road station tile (but not drive-through stops)
- return IsTileOwner(tile, owner); // You need to own these tiles entirely to use them
- }
-
switch (GetTileType(tile)) {
+ case MP_RAILWAY:
+ return IsInfraTileUsageAllowed(VEH_TRAIN, owner, tile); // Rail tile (also rail depot)
+
case MP_ROAD:
/* rail-road crossing : are we looking at the railway part? */
if (IsLevelCrossing(tile) &&
DiagDirToAxis(enterdir) != GetCrossingRoadAxis(tile)) {
- return IsTileOwner(tile, owner); // Railway needs owner check, while the street is public
+ return IsInfraTileUsageAllowed(VEH_TRAIN, owner, tile); // Railway needs owner check, while the street is public
+ } else if (IsRoadDepot(tile)) { // Road depot tile
+ return IsInfraTileUsageAllowed(VEH_ROAD, owner, tile);
+ }
+ break;
+
+ case MP_STATION:
+ if (HasStationRail(tile)) { // Rail station tile/waypoint
+ return IsInfraTileUsageAllowed(VEH_TRAIN, owner, tile);
+ } else if (IsStandardRoadStopTile(tile)) { // Road station tile (but not drive-through stops)
+ return IsInfraTileUsageAllowed(VEH_ROAD, owner, tile);
}
break;
case MP_TUNNELBRIDGE:
if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL) {
- return IsTileOwner(tile, owner);
+ return IsInfraTileUsageAllowed(VEH_TRAIN, owner, tile);
}
break;
Index: src/pathfinder/yapf/yapf_costrail.hpp
===================================================================
--- src/pathfinder/yapf/yapf_costrail.hpp (revision 27386)
+++ src/pathfinder/yapf/yapf_costrail.hpp (working copy)
@@ -13,6 +13,7 @@
#define YAPF_COSTRAIL_HPP
#include "../../pbs.h"
+#include "../../tracerestrict.h"
template
class CYapfCostRailT : public CYapfCostBase {
@@ -175,6 +176,106 @@
return 0;
}
+private:
+ // returns true if ExecuteTraceRestrict should be called
+ inline bool ShouldCheckTraceRestrict(Node& n, TileIndex tile)
+ {
+ return n.m_num_signals_passed < m_sig_look_ahead_costs.Size() &&
+ IsRestrictedSignal(tile);
+ }
+
+ /**
+ * This is called to retrieve the previous signal, as required
+ * This is not run all the time as it is somewhat expensive and most restrictions will not test for the previous signal
+ */
+ static TileIndex TraceRestrictPreviousSignalCallback(const Train *v, const void *node_ptr)
+ {
+ const Node *node = static_cast(node_ptr);
+ for (;;) {
+ TileIndex last_signal_tile = node->m_segment->m_last_signal_tile;
+ if (last_signal_tile != INVALID_TILE) {
+ Trackdir last_signal_trackdir = node->m_segment->m_last_signal_td;
+ if (HasPbsSignalOnTrackdir(last_signal_tile, last_signal_trackdir)) {
+ return last_signal_tile;
+ } else {
+ return INVALID_TILE;
+ }
+ }
+
+ if (node->m_parent) {
+ node = node->m_parent;
+ } else {
+ // scan forwards from vehicle position, for the case that train is waiting at/approaching PBS signal
+
+ /*
+ * TODO: can this be made more efficient?
+ * This track scan will have been performed upstack, however extracting the entry signal
+ * during that scan and passing it through to this point would likely require relatively
+ * invasive changes to the pathfinder code, or at least an extra param on a number of wrapper
+ * functions between there and here, which would be best avoided.
+ */
+
+ TileIndex origin_tile = node->GetTile();
+ Trackdir origin_trackdir = node->GetTrackdir();
+
+ TileIndex tile = v->tile;
+ Trackdir trackdir = v->GetVehicleTrackdir();
+
+ CFollowTrackRail ft(v);
+
+ TileIndex candidate_tile = INVALID_TILE;
+
+ for (;;) {
+ if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir)) {
+ if (HasPbsSignalOnTrackdir(tile, trackdir)) {
+ // found PBS signal
+ candidate_tile = tile;
+ } else {
+ // wrong type of signal
+ candidate_tile = INVALID_TILE;
+ }
+ }
+
+ if (tile == origin_tile && trackdir == origin_trackdir) {
+ // reached pathfinder origin
+ return candidate_tile;
+ }
+
+ // advance to next tile
+ if (!ft.Follow(tile, trackdir)) {
+ // ran out of track
+ return INVALID_TILE;
+ }
+
+ if (KillFirstBit(ft.m_new_td_bits) != TRACKDIR_BIT_NONE) {
+ // reached a junction tile
+ return INVALID_TILE;
+ }
+
+ tile = ft.m_new_tile;
+ trackdir = FindFirstTrackdir(ft.m_new_td_bits);
+ }
+ }
+ }
+ NOT_REACHED();
+ }
+
+ // returns true if dead end bit has been set
+ inline bool ExecuteTraceRestrict(Node& n, TileIndex tile, Trackdir trackdir, int& cost, TraceRestrictProgramResult &out)
+ {
+ const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(tile, TrackdirToTrack(trackdir));
+ if (prog) {
+ prog->Execute(Yapf().GetVehicle(), TraceRestrictProgramInput(tile, trackdir, &TraceRestrictPreviousSignalCallback, &n), out);
+ if (out.flags & TRPRF_DENY) {
+ n.m_segment->m_end_segment_reason |= ESRB_DEAD_END;
+ return true;
+ }
+ cost += out.penalty;
+ }
+ return false;
+ }
+
+public:
int SignalCost(Node &n, TileIndex tile, Trackdir trackdir)
{
int cost = 0;
@@ -225,6 +326,7 @@
/* special signal penalties */
if (n.m_num_signals_passed == 0) {
switch (sig_type) {
+ case SIGTYPE_PROG:
case SIGTYPE_COMBO:
case SIGTYPE_EXIT: cost += Yapf().PfGetSettings().rail_firstred_exit_penalty; break; // first signal is red pre-signal-exit
case SIGTYPE_NORMAL:
@@ -234,6 +336,13 @@
}
}
+ if (ShouldCheckTraceRestrict(n, tile)) {
+ TraceRestrictProgramResult out;
+ if (ExecuteTraceRestrict(n, tile, trackdir, cost, out)) {
+ return -1;
+ }
+ }
+
n.m_num_signals_passed++;
n.m_segment->m_last_signal_tile = tile;
n.m_segment->m_last_signal_td = trackdir;
@@ -241,6 +350,13 @@
if (has_signal_against && IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) {
cost += n.m_num_signals_passed < Yapf().PfGetSettings().rail_look_ahead_max_signals ? Yapf().PfGetSettings().rail_pbs_signal_back_penalty : 0;
+
+ if (ShouldCheckTraceRestrict(n, tile)) {
+ TraceRestrictProgramResult out;
+ if (ExecuteTraceRestrict(n, tile, trackdir, cost, out)) {
+ return -1;
+ }
+ }
}
}
}
Index: src/pathfinder/yapf/yapf_rail.cpp
===================================================================
--- src/pathfinder/yapf/yapf_rail.cpp (revision 27386)
+++ src/pathfinder/yapf/yapf_rail.cpp (working copy)
@@ -78,7 +78,7 @@
do {
if (HasStationReservation(tile)) return false;
SetRailStationReservation(tile, true);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
tile = TILE_ADD(tile, diff);
} while (IsCompatibleTrainStationTile(tile, start) && tile != m_origin_tile);
Index: src/pbs.cpp
===================================================================
--- src/pbs.cpp (revision 27386)
+++ src/pbs.cpp (working copy)
@@ -66,7 +66,7 @@
do {
SetRailStationReservation(tile, b);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
tile = TILE_ADD(tile, diff);
} while (IsCompatibleTrainStationTile(tile, start));
}
@@ -86,9 +86,9 @@
if (_settings_client.gui.show_track_reservation) {
/* show the reserved rail if needed */
if (IsBridgeTile(tile)) {
- MarkBridgeDirty(tile);
+ MarkBridgeDirty(tile, ZOOM_LVL_DRAW_MAP);
} else {
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
@@ -98,7 +98,7 @@
if (IsRailDepot(tile)) {
if (!HasDepotReservation(tile)) {
SetDepotReservation(tile, true);
- MarkTileDirtyByTile(tile); // some GRFs change their appearance when tile is reserved
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP); // some GRFs change their appearance when tile is reserved
return true;
}
}
@@ -107,8 +107,7 @@
case MP_ROAD:
if (IsLevelCrossing(tile) && !HasCrossingReservation(tile)) {
SetCrossingReservation(tile, true);
- BarCrossing(tile);
- MarkTileDirtyByTile(tile); // crossing barred, make tile dirty
+ UpdateLevelCrossing(tile, false);
return true;
}
break;
@@ -117,7 +116,7 @@
if (HasStationRail(tile) && !HasStationReservation(tile)) {
SetRailStationReservation(tile, true);
if (trigger_stations && IsRailStation(tile)) TriggerStationRandomisation(NULL, tile, SRT_PATH_RESERVATION);
- MarkTileDirtyByTile(tile); // some GRFs need redraw after reserving track
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP); // some GRFs need redraw after reserving track
return true;
}
break;
@@ -146,9 +145,9 @@
if (_settings_client.gui.show_track_reservation) {
if (IsBridgeTile(tile)) {
- MarkBridgeDirty(tile);
+ MarkBridgeDirty(tile, ZOOM_LVL_DRAW_MAP);
} else {
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
@@ -156,7 +155,7 @@
case MP_RAILWAY:
if (IsRailDepot(tile)) {
SetDepotReservation(tile, false);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
break;
}
if (IsPlainRail(tile)) UnreserveTrack(tile, t);
@@ -172,7 +171,7 @@
case MP_STATION:
if (HasStationRail(tile)) {
SetRailStationReservation(tile, false);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
break;
Index: src/plans.cpp
new file mode 100644
===================================================================
--- src/plans.cpp (revision 0)
+++ src/plans.cpp (working copy)
@@ -0,0 +1,21 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file plans.cpp Handling of plans. */
+
+#include "stdafx.h"
+#include "plans_base.h"
+#include "core/pool_func.hpp"
+
+/** Initialize the plan-pool */
+PlanPool _plan_pool("Plan");
+INSTANTIATE_POOL_METHODS(Plan)
+
+Plan *_current_plan = NULL;
+Plan *_new_plan = NULL;
Index: src/plans_base.h
new file mode 100644
===================================================================
--- src/plans_base.h (revision 0)
+++ src/plans_base.h (working copy)
@@ -0,0 +1,237 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file plans_base.h Base class for plans. */
+
+#ifndef PLANS_BASE_H
+#define PLANS_BASE_H
+
+#include "plans_func.h"
+#include "core/pool_type.hpp"
+#include "company_type.h"
+#include "company_func.h"
+#include "command_func.h"
+#include "map_func.h"
+#include "date_func.h"
+#include "viewport_func.h"
+#include "core/endian_func.hpp"
+#include
+
+typedef Pool PlanPool;
+typedef std::vector TileVector;
+typedef std::vector PlanLineVector;
+extern PlanPool _plan_pool;
+
+struct PlanLine {
+ bool visible;
+ bool focused;
+ TileVector tiles;
+
+ PlanLine()
+ {
+ this->visible = true;
+ this->focused = false;
+ }
+
+ ~PlanLine()
+ {
+ this->Clear();
+ }
+
+ void Clear()
+ {
+ this->tiles.clear();
+ }
+
+ bool AppendTile(TileIndex tile)
+ {
+ const uint cnt = (uint) this->tiles.size();
+
+ if (cnt > 0) {
+ const TileIndex last_tile = this->tiles[cnt - 1];
+ if (last_tile == tile) return false;
+ MarkTileLineDirty(last_tile, tile);
+
+ if (cnt > 1) {
+ const TileIndex t0 = this->tiles[cnt - 2];
+ const int x0 = (int) TileX(t0);
+ const int y0 = (int) TileY(t0);
+ const TileIndex t1 = this->tiles[cnt - 1];
+ const int x1 = (int) TileX(t1);
+ const int y1 = (int) TileY(t1);
+ const int x2 = (int) TileX(tile);
+ const int y2 = (int) TileY(tile);
+
+ if ((y1 - y0) * (x2 - x1) == (y2 - y1) * (x1 - x0)) { // Same direction.
+ if (abs(x2 - x1) <= abs(x2 - x0) && abs(y2 - y1) <= abs(y2 - y0)) { // Tile i+1 is between i and i+2.
+ /* The new tile is in the continuity, just update the last tile. */
+ this->tiles[cnt - 1] = tile;
+ MarkTileLineDirty(t1, tile);
+ return true;
+ }
+ }
+ }
+ }
+
+ if (this->tiles.size() * sizeof(TileIndex) >= MAX_CMD_TEXT_LENGTH) return false;
+
+ this->tiles.push_back(tile);
+ return true;
+ }
+
+ void SetFocus(bool focused)
+ {
+ if (this->focused != focused) this->MarkDirty();
+ this->focused = focused;
+ }
+
+ bool ToggleVisibility()
+ {
+ this->SetVisibility(!this->visible);
+ return this->visible;
+ }
+
+ void SetVisibility(bool visible)
+ {
+ if (this->visible != visible) this->MarkDirty();
+ this->visible = visible;
+ }
+
+ void MarkDirty()
+ {
+ const uint sz = (uint) this->tiles.size();
+ for (uint i = 1; i < sz; i++) {
+ MarkTileLineDirty(this->tiles[i-1], this->tiles[i]);
+ }
+ }
+
+ TileIndex *Export(uint *buffer_length)
+ {
+ const uint cnt = (uint) this->tiles.size();
+ const uint datalen = sizeof(TileIndex) * cnt;
+ TileIndex *buffer = (TileIndex *) malloc(datalen);
+ if (buffer) {
+ for (uint i = 0; i < cnt; i++) {
+ buffer[i] = TO_LE32(this->tiles[i]);
+ }
+ if (buffer_length) *buffer_length = datalen;
+ }
+ return buffer;
+ }
+
+ void Import(const TileIndex* data, const uint data_length)
+ {
+ for (uint i = data_length; i != 0; i--, data++) {
+ this->tiles.push_back(FROM_LE32(*data));
+ }
+ }
+};
+
+struct Plan : PlanPool::PoolItem<&_plan_pool> {
+ OwnerByte owner;
+ PlanLineVector lines;
+ PlanLine *temp_line;
+ bool visible;
+ bool visible_by_all;
+ bool show_lines;
+ Date creation_date;
+
+ Plan(Owner owner = INVALID_OWNER)
+ {
+ this->owner = owner;
+ this->creation_date = _date;
+ this->visible = false;
+ this->visible_by_all = false;
+ this->show_lines = false;
+ this->temp_line = new PlanLine();
+ }
+
+ ~Plan()
+ {
+ for (PlanLineVector::iterator it = lines.begin(); it != lines.end(); it++) {
+ delete (*it);
+ }
+ this->lines.clear();
+ delete temp_line;
+ }
+
+ void SetFocus(bool focused)
+ {
+ for (PlanLineVector::iterator it = lines.begin(); it != lines.end(); it++) {
+ (*it)->SetFocus(focused);
+ }
+ }
+
+ void SetVisibility(bool visible, bool do_lines = true)
+ {
+ this->visible = visible;
+
+ if (!do_lines) return;
+ for (PlanLineVector::iterator it = lines.begin(); it != lines.end(); it++) {
+ (*it)->SetVisibility(visible);
+ }
+ }
+
+ bool ToggleVisibility()
+ {
+ this->SetVisibility(!this->visible);
+ return this->visible;
+ }
+
+ PlanLine *NewLine()
+ {
+ PlanLine *pl = new PlanLine();
+ if (pl) this->lines.push_back(pl);
+ return pl;
+ }
+
+ bool StoreTempTile(TileIndex tile)
+ {
+ return this->temp_line->AppendTile(tile);
+ }
+
+ bool ValidateNewLine()
+ {
+ bool ret = false;
+ if (this->temp_line->tiles.size() > 1) {
+ uint buffer_length = 0;
+ const TileIndex *buffer = this->temp_line->Export(&buffer_length);
+ if (buffer) {
+ _current_plan->SetVisibility(true, false);
+ ret = DoCommandP(0, _current_plan->index, (uint32) this->temp_line->tiles.size(), CMD_ADD_PLAN_LINE, NULL, (const char *) buffer, true, buffer_length);
+ free(buffer);
+ }
+ _current_plan->temp_line->MarkDirty();
+ this->temp_line->Clear();
+ }
+ return ret;
+ }
+
+ bool IsListable()
+ {
+ return (this->owner == _local_company || this->visible_by_all);
+ }
+
+ bool IsVisible()
+ {
+ if (!this->IsListable()) return false;
+ return this->visible;
+ }
+
+ bool ToggleVisibilityByAll()
+ {
+ if (_current_plan->owner == _local_company) DoCommandP(0, _current_plan->index, !this->visible_by_all, CMD_CHANGE_PLAN_VISIBILITY);
+ return this->visible_by_all;
+ }
+};
+
+#define FOR_ALL_PLANS_FROM(var, start) FOR_ALL_ITEMS_FROM(Plan, plan_index, var, start)
+#define FOR_ALL_PLANS(var) FOR_ALL_PLANS_FROM(var, 0)
+
+#endif /* PLANS_BASE_H */
Index: src/plans_cmd.cpp
new file mode 100644
===================================================================
--- src/plans_cmd.cpp (revision 0)
+++ src/plans_cmd.cpp (working copy)
@@ -0,0 +1,140 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file plans_cmd.cpp Handling of plan related commands. */
+
+#include "stdafx.h"
+#include "command_func.h"
+#include "plans_base.h"
+#include "plans_func.h"
+#include "window_func.h"
+#include "company_func.h"
+#include "window_gui.h"
+#include "table/strings.h"
+
+/**
+ * Create a new plan.
+ * @param tile unused
+ * @param flags type of operation
+ * @param p1 owner of the plan
+ * @param p2 unused
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdAddPlan(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ if (!Plan::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_PLANS);
+ if (flags & DC_EXEC) {
+ Owner o = (Owner) p1;
+ _new_plan = new Plan(o);
+ if (o == _local_company) {
+ _new_plan->SetVisibility(true);
+ Window *w = FindWindowById(WC_PLANS, 0);
+ if (w) w->InvalidateData(INVALID_PLAN, false);
+ }
+ }
+ return CommandCost();
+}
+
+/**
+ * Create a new line in a plan.
+ * @param tile unused
+ * @param flags type of operation
+ * @param p1 plan id
+ * @param p2 number of nodes
+ * @param text list of tile indexes that compose the line, encoded in base64
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdAddPlanLine(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ if (flags & DC_EXEC) {
+ Plan *p = Plan::Get(p1);
+ PlanLine *pl = p->NewLine();
+ if (!pl) return_cmd_error(STR_ERROR_NO_MORE_SPACE_FOR_LINES);
+ if (p2 > (MAX_CMD_TEXT_LENGTH / sizeof(TileIndex))) return_cmd_error(STR_ERROR_TOO_MANY_NODES);
+ pl->Import((const TileIndex *) text, p2);
+ if (p->IsListable()) {
+ pl->SetVisibility(p->visible);
+ if (p->visible) pl->MarkDirty();
+ Window *w = FindWindowById(WC_PLANS, 0);
+ if (w) w->InvalidateData(INVALID_PLAN, false);
+ }
+ }
+ return CommandCost();
+}
+
+/**
+ * Edit the visibility of a plan.
+ * @param tile unused
+ * @param flags type of operation
+ * @param p1 plan id
+ * @param p2 visibility
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdChangePlanVisibility(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ if (flags & DC_EXEC) {
+ Plan *p = Plan::Get(p1);
+ p->visible_by_all = p2 != 0;
+ Window *w = FindWindowById(WC_PLANS, 0);
+ if (w) w->InvalidateData(INVALID_PLAN, false);
+ }
+ return CommandCost();
+}
+
+/**
+ * Delete a plan.
+ * @param tile unused
+ * @param flags type of operation
+ * @param p1 plan id
+ * @param p2 unused
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdRemovePlan(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ if (flags & DC_EXEC) {
+ Plan *p = Plan::Get(p1);
+ if (p->IsListable()) {
+ p->SetVisibility(false);
+ Window *w = FindWindowById(WC_PLANS, 0);
+ if (w) w->InvalidateData(p->index, false);
+ }
+ if (p == _current_plan) _current_plan = NULL;
+ delete p;
+ }
+ return CommandCost();
+}
+
+/**
+ * Remove a line from a plan.
+ * @param tile unused
+ * @param flags type of operation
+ * @param p1 plan id
+ * @param p2 line id
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdRemovePlanLine(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ if (flags & DC_EXEC) {
+ Plan *p = Plan::Get(p1);
+ PlanLineVector::iterator it = p->lines.begin();
+ std::advance(it, p2);
+ (*it)->SetVisibility(false);
+ delete *it;
+ p->lines.erase(it);
+ if (p->IsListable()) {
+ Window *w = FindWindowById(WC_PLANS, 0);
+ if (w) w->InvalidateData(p->index, false);
+ }
+ }
+ return CommandCost();
+}
Index: src/plans_func.h
new file mode 100644
===================================================================
--- src/plans_func.h (revision 0)
+++ src/plans_func.h (working copy)
@@ -0,0 +1,22 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file plans_func.h Functions related to plans. */
+
+#ifndef PLANS_FUNC_H
+#define PLANS_FUNC_H
+
+#include "plans_type.h"
+
+void ShowPlansWindow();
+
+extern Plan *_new_plan;
+extern Plan *_current_plan;
+
+#endif /* PLANS_FUNC_H */
Index: src/plans_gui.cpp
new file mode 100644
===================================================================
--- src/plans_gui.cpp (revision 0)
+++ src/plans_gui.cpp (working copy)
@@ -0,0 +1,366 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file plans_gui.cpp The GUI for planning. */
+
+#include "stdafx.h"
+#include "plans_func.h"
+#include "plans_base.h"
+#include "command_func.h"
+#include "company_func.h"
+#include "company_gui.h"
+#include "settings_gui.h"
+#include "window_gui.h"
+#include "window_func.h"
+#include "viewport_func.h"
+#include "gfx_func.h"
+#include "tilehighlight_func.h"
+#include "strings_func.h"
+#include "core/pool_func.hpp"
+#include "widgets/plans_widget.h"
+#include "table/strings.h"
+#include "table/sprites.h"
+
+static const NWidgetPart _nested_plans_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+ NWidget(WWT_CAPTION, COLOUR_GREY, WID_PLN_CAPTION), SetDataTip(STR_PLANS_CAPTION, STR_NULL),
+ NWidget(WWT_SHADEBOX, COLOUR_GREY),
+ NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
+ NWidget(WWT_STICKYBOX, COLOUR_GREY),
+ EndContainer(),
+
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PANEL, COLOUR_GREY),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_INSET, COLOUR_GREY, WID_PLN_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2), SetResize(1, 0), SetScrollbar(WID_PLN_SCROLLBAR), SetDataTip(STR_NULL, STR_PLANS_LIST_TOOLTIP),
+ EndContainer(),
+ EndContainer(),
+ EndContainer(),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_PLN_SCROLLBAR),
+ EndContainer(),
+
+ NWidget(WWT_PANEL, COLOUR_GREY),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_NEW), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_NEW_PLAN, STR_NULL),
+ NWidget(WWT_TEXTBTN_2, COLOUR_GREY, WID_PLN_ADD_LINES), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_ADD_LINES, STR_PLANS_ADDING_LINES),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_PLN_VISIBILITY), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_VISIBILITY_PUBLIC, STR_PLANS_VISIBILITY_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_HIDE_ALL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_HIDE_ALL, STR_NULL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_PLN_DELETE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_PLANS_DELETE, STR_PLANS_DELETE_TOOLTIP),
+ NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _plans_desc(
+ WDP_AUTO, "plans", 300, 100,
+ WC_PLANS, WC_NONE,
+ WDF_CONSTRUCTION,
+ _nested_plans_widgets, lengthof(_nested_plans_widgets)
+);
+
+struct PlansWindow : Window {
+ typedef struct {
+ bool is_plan;
+ int plan_id;
+ int line_id;
+ } ListItem;
+
+ Scrollbar *vscroll;
+ int text_offset;
+ std::vector list; ///< The translation table linking panel indices to their related PlanID.
+ int selected; ///< What item is currently selected in the panel.
+
+ PlansWindow(WindowDesc *desc) : Window(desc)
+ {
+ this->CreateNestedTree();
+ this->vscroll = this->GetScrollbar(WID_PLN_SCROLLBAR);
+ this->FinishInitNested();
+
+ Dimension spr_dim = GetSpriteSize(SPR_COMPANY_ICON);
+ this->text_offset = WD_FRAMETEXT_LEFT + spr_dim.width + 2 + SETTING_BUTTON_WIDTH;
+
+ this->selected = INT_MAX;
+ RebuildList();
+ }
+
+ ~PlansWindow()
+ {
+ this->list.clear();
+ _current_plan = NULL;
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch (widget) {
+ case WID_PLN_NEW:
+ DoCommandP(0, _local_company, 0, CMD_ADD_PLAN, CcAddPlan);
+ break;
+ case WID_PLN_ADD_LINES:
+ if (_current_plan) HandlePlacePushButton(this, widget, SPR_CURSOR_MOUSE, HT_POINT);
+ break;
+ case WID_PLN_DELETE:
+ if (this->selected != INT_MAX) {
+ if (this->list[this->selected].is_plan) {
+ DoCommandP(0, this->list[this->selected].plan_id, 0, CMD_REMOVE_PLAN);
+ } else {
+ DoCommandP(0, this->list[this->selected].plan_id, this->list[this->selected].line_id, CMD_REMOVE_PLAN_LINE);
+ }
+ }
+ break;
+ case WID_PLN_HIDE_ALL: {
+ Plan *p;
+ FOR_ALL_PLANS(p) {
+ if (p->IsListable()) p->SetVisibility(false);
+ }
+ this->SetWidgetDirty(WID_PLN_LIST);
+ break;
+ }
+ case WID_PLN_VISIBILITY:
+ if (_current_plan) _current_plan->ToggleVisibilityByAll();
+ break;
+ case WID_PLN_LIST: {
+ int new_selected = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_PLN_LIST, WD_FRAMERECT_TOP);
+ if (this->selected != INT_MAX) {
+ _current_plan->SetFocus(false);
+ }
+ if (new_selected != INT_MAX) {
+ if (this->list[new_selected].is_plan) {
+ _current_plan = Plan::Get(this->list[new_selected].plan_id);
+ _current_plan->SetFocus(true);
+ if (pt.x >= 22 && pt.x < 41) _current_plan->ToggleVisibility();
+ } else {
+ _current_plan = Plan::Get(this->list[new_selected].plan_id);
+ PlanLine *pl = _current_plan->lines[this->list[new_selected].line_id];
+ pl->SetFocus(true);
+ if (pt.x >= 22 && pt.x < 41) {
+ if (pl->ToggleVisibility()) _current_plan->SetVisibility(true, false);
+ }
+ }
+ if (click_count > 1 && (pt.x < 22 || pt.x >= 41)) {
+ _current_plan->show_lines = !_current_plan->show_lines;
+ this->InvalidateData(INVALID_PLAN);
+ }
+ } else {
+ if (_current_plan) {
+ _current_plan->SetFocus(false);
+ _current_plan = NULL;
+ }
+ }
+ this->selected = new_selected;
+ this->SetDirty();
+ break;
+ }
+ default: break;
+ }
+ }
+
+ virtual void OnPaint()
+ {
+ this->SetWidgetDisabledState(WID_PLN_HIDE_ALL, this->vscroll->GetCount() == 0);
+ if (_current_plan) {
+ this->SetWidgetsDisabledState(_current_plan->owner != _local_company, WID_PLN_ADD_LINES, WID_PLN_VISIBILITY, WID_PLN_DELETE, WIDGET_LIST_END);
+ this->GetWidget(WID_PLN_VISIBILITY)->widget_data = _current_plan->visible_by_all ? STR_PLANS_VISIBILITY_PRIVATE : STR_PLANS_VISIBILITY_PUBLIC;
+ } else {
+ this->SetWidgetsDisabledState(true, WID_PLN_ADD_LINES, WID_PLN_VISIBILITY, WID_PLN_DELETE, WIDGET_LIST_END);
+ }
+ this->DrawWidgets();
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch (widget) {
+ case WID_PLN_LIST: {
+ uint y = r.top + WD_FRAMERECT_TOP; // Offset from top of widget.
+ if (this->vscroll->GetCount() == 0) {
+ DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_STATION_LIST_NONE);
+ return;
+ }
+
+ bool rtl = _current_text_dir == TD_RTL;
+ uint icon_left = 4 + (rtl ? r.right - this->text_offset : r.left);
+ uint btn_left = (rtl ? icon_left - SETTING_BUTTON_WIDTH + 4 : icon_left + SETTING_BUTTON_WIDTH - 4);
+ uint text_left = r.left + (rtl ? WD_FRAMERECT_LEFT : this->text_offset);
+ uint text_right = r.right - (rtl ? this->text_offset : WD_FRAMERECT_RIGHT);
+
+ for (uint16 i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++) {
+ Plan *p = Plan::Get(list[i].plan_id);
+
+ if (i == this->selected) GfxFillRect(r.left + 1, y, r.right, y + this->resize.step_height, PC_DARK_GREY);
+
+ if (list[i].is_plan) {
+ DrawCompanyIcon(p->owner, icon_left, y + (FONT_HEIGHT_NORMAL - 10) / 2 + 1);
+ DrawBoolButton(btn_left, y + (FONT_HEIGHT_NORMAL - 10) / 2, p->visible, true);
+ SetDParam(0, list[i].plan_id + 1);
+ SetDParam(1, p->lines.size());
+ SetDParam(2, p->creation_date);
+ DrawString(text_left, text_right, y, STR_PLANS_LIST_ITEM_PLAN, p->visible_by_all ? TC_LIGHT_BLUE : TC_YELLOW);
+ } else {
+ PlanLine *pl = p->lines[list[i].line_id];
+ DrawBoolButton(btn_left, y + (FONT_HEIGHT_NORMAL - 10) / 2, pl->visible, true);
+ SetDParam(0, list[i].line_id + 1);
+ SetDParam(1, pl->tiles.size() - 1);
+ DrawString(text_left, text_right, y, STR_PLANS_LIST_ITEM_LINE, TC_WHITE);
+ }
+ y += this->resize.step_height;
+ }
+ break;
+ }
+ }
+ }
+
+ virtual void OnResize()
+ {
+ this->vscroll->SetCapacityFromWidget(this, WID_PLN_LIST, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ switch (widget) {
+ case WID_PLN_LIST:
+ resize->height = FONT_HEIGHT_NORMAL;
+ size->height = resize->height * 5 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
+ break;
+ }
+ }
+
+ /** The drawing of a line starts. */
+ virtual void OnPlaceObject(Point pt, TileIndex tile)
+ {
+ /* A player can't add lines to a public plan of another company. */
+ if (_current_plan && _current_plan->owner == _local_company) VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_DRAW_PLANLINE);
+ }
+
+ /** The drawing of a line is in progress. */
+ virtual void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt)
+ {
+ const Point p = GetTileBelowCursor();
+ const TileIndex tile = TileVirtXY(p.x, p.y);
+ if (_current_plan && tile < MapSize()) {
+ _current_plan->StoreTempTile(tile);
+ _thd.selstart = _thd.selend;
+ }
+ }
+
+ /** The drawing of a line ends up normally. */
+ virtual void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile)
+ {
+ if (_current_plan) _current_plan->ValidateNewLine();
+ }
+
+ /** The drawing of a line is aborted. */
+ virtual void OnPlaceObjectAbort()
+ {
+ if (_current_plan) {
+ _current_plan->temp_line->MarkDirty();
+ _current_plan->temp_line->Clear();
+ }
+
+ this->RaiseWidget(WID_PLN_ADD_LINES);
+ this->SetWidgetDirty(WID_PLN_ADD_LINES);
+ }
+
+ void RebuildList()
+ {
+ int old_focused_plan_id = this->selected == INT_MAX ? INT_MAX : this->list[this->selected].plan_id;
+
+ int sbcnt = 0;
+ this->list.clear();
+ Plan *p;
+ FOR_ALL_PLANS(p) {
+ if (!p->IsListable()) continue;
+
+ ListItem li;
+ li.is_plan = true;
+ li.plan_id = p->index;
+ this->list.push_back(li);
+ if (old_focused_plan_id == p->index) this->selected = sbcnt;
+ sbcnt++;
+
+ if (p->show_lines) {
+ const int sz = (int) p->lines.size();
+ sbcnt += sz;
+ li.is_plan = false;
+ for (int i = 0; i < sz; i++) {
+ li.line_id = i;
+ this->list.push_back(li);
+ }
+ }
+ }
+
+ if (this->selected == INT_MAX) ResetObjectToPlace();
+
+ this->vscroll->SetCount(sbcnt);
+ }
+
+ virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
+ {
+ if (data != INVALID_PLAN && this->selected != INT_MAX) {
+ if (this->list[this->selected].plan_id == data) {
+ /* Invalidate the selection if the selected plan has been modified or deleted. */
+ this->selected = INT_MAX;
+
+ /* Cancel drawing associated to the deleted plan. */
+ ResetObjectToPlace();
+ }
+ }
+
+ RebuildList();
+ }
+
+ void SelectPlan(PlanID plan_index)
+ {
+ if (this->selected != INT_MAX) {
+ if (plan_index == this->list[this->selected].plan_id) return;
+ Plan::Get(this->list[this->selected].plan_id)->SetFocus(false);
+ }
+
+ if (plan_index == INVALID_PLAN) {
+ this->selected = INT_MAX;
+ return;
+ }
+ Plan::Get(plan_index)->SetFocus(true);
+
+ for (size_t i = 0; i < this->list.size(); i++) {
+ if (this->list[i].is_plan && this->list[i].plan_id == plan_index) {
+ this->selected = (int) i;
+ return;
+ }
+ }
+ }
+};
+
+/** Show the window to manage plans. */
+void ShowPlansWindow()
+{
+ if (BringWindowToFrontById(WC_PLANS, 0) != NULL) return;
+ new PlansWindow(&_plans_desc);
+}
+
+/**
+ * Only the creator of a plan executes this function.
+ * The other players should not be bothered with these changes.
+ */
+void CcAddPlan(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
+{
+ if (result.Failed()) return;
+
+ _current_plan = _new_plan;
+ _current_plan->SetVisibility(true);
+
+ Window *w = FindWindowById(WC_PLANS, 0);
+ if (w) {
+ w->InvalidateData(INVALID_PLAN, false);
+ ((PlansWindow *) w)->SelectPlan(_current_plan->index);
+ if (!w->IsWidgetLowered(WID_PLN_ADD_LINES)) {
+ w->SetWidgetDisabledState(WID_PLN_ADD_LINES, false);
+ HandlePlacePushButton(w, WID_PLN_ADD_LINES, SPR_CURSOR_MOUSE, HT_POINT);
+ }
+ }
+}
Index: src/plans_type.h
new file mode 100644
===================================================================
--- src/plans_type.h (revision 0)
+++ src/plans_type.h (working copy)
@@ -0,0 +1,25 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file plans_type.h Types related to planning. */
+
+#ifndef PLANS_TYPE_H
+#define PLANS_TYPE_H
+
+#include "stdafx.h"
+#include "core/smallvec_type.hpp"
+#include "tile_type.h"
+
+typedef uint16 PlanID;
+struct PlanLine;
+struct Plan;
+
+static const PlanID INVALID_PLAN = 0xFFFF; ///< Sentinel for an invalid plan.
+
+#endif /* PLANS_TYPE_H */
Index: src/programmable_signals.cpp
new file mode 100644
===================================================================
--- src/programmable_signals.cpp (revision 0)
+++ src/programmable_signals.cpp (working copy)
@@ -0,0 +1,699 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file programmable_signals.cpp Programmable Signals */
+
+#include "stdafx.h"
+#include "programmable_signals.h"
+#include "debug.h"
+#include "command_func.h"
+#include "table/strings.h"
+#include "window_func.h"
+#include "company_func.h"
+#include "cmd_helper.h"
+#include
+
+ProgramList _signal_programs;
+
+SignalProgram::SignalProgram(TileIndex tile, Track track, bool raw)
+{
+ this->tile = tile;
+ this->track = track;
+ if (!raw) {
+ this->first_instruction = new SignalSpecial(this, PSO_FIRST);
+ this->last_instruction = new SignalSpecial(this, PSO_LAST);
+ SignalSpecial::link(this->first_instruction, this->last_instruction);
+ }
+}
+
+SignalProgram::~SignalProgram()
+{
+ this->DebugPrintProgram();
+ this->first_instruction->Remove();
+ delete this->first_instruction;
+ delete this->last_instruction;
+}
+
+struct SignalVM {
+ // Initial information
+ uint num_exits; ///< Number of exits from block
+ uint num_green; ///< Number of green exits from block
+ SignalProgram *program; ///< The program being run
+
+ // Current state
+ SignalInstruction *instruction; ///< Instruction to execute next
+
+ // Output state
+ SignalState state;
+
+ void Execute()
+ {
+ DEBUG(misc, 6, "Begining execution of programmable signal on tile %x, track %d",
+ this->program->tile, this->program->track);
+ do {
+ DEBUG(misc, 10, " Executing instruction %d, opcode %d", this->instruction->Id(), this->instruction->Opcode());
+ this->instruction->Evaluate(*this);
+ } while (this->instruction);
+
+ DEBUG(misc, 6, "Completed");
+ }
+};
+
+// -- Conditions
+
+SignalCondition::~SignalCondition()
+{}
+
+SignalSimpleCondition::SignalSimpleCondition(SignalConditionCode code)
+ : SignalCondition(code)
+{}
+
+/* virtual */ bool SignalSimpleCondition::Evaluate(SignalVM &vm)
+{
+ switch (this->cond_code) {
+ case PSC_ALWAYS: return true;
+ case PSC_NEVER: return false;
+ default: NOT_REACHED();
+ }
+}
+
+SignalVariableCondition::SignalVariableCondition(SignalConditionCode code)
+ : SignalCondition(code)
+{
+ switch (this->cond_code) {
+ case PSC_NUM_GREEN: comparator = SGC_NOT_EQUALS; break;
+ case PSC_NUM_RED: comparator = SGC_EQUALS; break;
+ default: NOT_REACHED();
+ }
+ value = 0;
+}
+
+/*virtual*/ bool SignalVariableCondition::Evaluate(SignalVM &vm)
+{
+ uint32 var_val;
+ switch (this->cond_code) {
+ case PSC_NUM_GREEN: var_val = vm.num_green; break;
+ case PSC_NUM_RED: var_val = vm.num_exits - vm.num_green; break;
+ default: NOT_REACHED();
+ }
+
+ switch (this->comparator) {
+ case SGC_EQUALS: return var_val == this->value;
+ case SGC_NOT_EQUALS: return var_val != this->value;
+ case SGC_LESS_THAN: return var_val < this->value;
+ case SGC_LESS_THAN_EQUALS: return var_val <= this->value;
+ case SGC_MORE_THAN: return var_val > this->value;
+ case SGC_MORE_THAN_EQUALS: return var_val >= this->value;
+ case SGC_IS_TRUE: return var_val != 0;
+ case SGC_IS_FALSE: return !var_val;
+ default: NOT_REACHED();
+ }
+}
+
+SignalStateCondition::SignalStateCondition(SignalReference this_sig,
+ TileIndex sig_tile, Trackdir sig_track)
+ : SignalCondition(PSC_SIGNAL_STATE), this_sig(this_sig), sig_tile(sig_tile)
+ , sig_track(sig_track)
+{
+ if (this->IsSignalValid())
+ AddSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
+ this->this_sig);
+}
+
+bool SignalStateCondition::IsSignalValid()
+{
+ if (IsValidTile(this->sig_tile)) {
+ if (HasSignalOnTrackdir(this->sig_tile, this->sig_track)) {
+ return true;
+ } else {
+ Invalidate();
+ }
+ }
+ return false;
+}
+
+void SignalStateCondition::Invalidate()
+{
+ this->sig_tile = INVALID_TILE;
+}
+
+
+void SignalStateCondition::SetSignal(TileIndex tile, Trackdir track)
+{
+ if (this->IsSignalValid())
+ RemoveSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
+ this->this_sig);
+ this->sig_tile = tile;
+ this->sig_track = track;
+ if (this->IsSignalValid())
+ AddSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
+ this->this_sig);
+}
+
+/*virtual*/ SignalStateCondition::~SignalStateCondition()
+{
+ if (this->IsSignalValid())
+ RemoveSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
+ this->this_sig);
+}
+
+/*virtual*/ bool SignalStateCondition::Evaluate(SignalVM& vm)
+{
+ if (!this->IsSignalValid()) {
+ DEBUG(misc, 1, "Signal (%x, %d) has an invalid condition", this->this_sig.tile, this->this_sig.track);
+ return false;
+ }
+
+ return GetSignalStateByTrackdir(this->sig_tile, this->sig_track) == SIGNAL_STATE_GREEN;
+}
+
+// -- Instructions
+SignalInstruction::SignalInstruction(SignalProgram *prog, SignalOpcode op)
+ : opcode(op), previous(NULL), program(prog)
+{
+ *program->instructions.Append() = this;
+}
+
+SignalInstruction::~SignalInstruction()
+{
+ SignalInstruction** pthis = program->instructions.Find(this);
+ assert(pthis != program->instructions.End());
+ program->instructions.Erase(pthis);
+}
+
+void SignalInstruction::Insert(SignalInstruction *before_insn)
+{
+ this->previous = before_insn->Previous();
+ before_insn->Previous()->SetNext(this);
+ before_insn->SetPrevious(this);
+ this->SetNext(before_insn);
+}
+
+SignalSpecial::SignalSpecial(SignalProgram *prog, SignalOpcode op)
+ : SignalInstruction(prog, op)
+{
+ assert(op == PSO_FIRST || op == PSO_LAST);
+ this->next = NULL;
+}
+
+/*virtual*/ void SignalSpecial::Remove()
+{
+ if (opcode == PSO_FIRST) {
+ while (this->next->Opcode() != PSO_LAST) this->next->Remove();
+ } else if (opcode == PSO_LAST) {
+ } else NOT_REACHED();
+}
+
+/*static*/ void SignalSpecial::link(SignalSpecial *first, SignalSpecial *last)
+{
+ assert(first->opcode == PSO_FIRST && last->opcode == PSO_LAST);
+ first->next = last;
+ last->previous = first;
+}
+
+void SignalSpecial::Evaluate(SignalVM &vm)
+{
+ if (this->opcode == PSO_FIRST) {
+ DEBUG(misc, 7, " Executing First");
+ vm.instruction = this->next;
+ } else {
+ DEBUG(misc, 7, " Executing Last");
+ vm.instruction = NULL;
+ }
+}
+/*virtual*/ void SignalSpecial::SetNext(SignalInstruction *next_insn)
+{
+ this->next = next_insn;
+}
+
+SignalIf::PseudoInstruction::PseudoInstruction(SignalProgram *prog, SignalOpcode op)
+ : SignalInstruction(prog, op)
+ {}
+
+SignalIf::PseudoInstruction::PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op)
+ : SignalInstruction(prog, op)
+{
+ this->block = block;
+ if (op == PSO_IF_ELSE) {
+ previous = block;
+ } else if (op == PSO_IF_ENDIF) {
+ previous = block->if_true;
+ } else NOT_REACHED();
+}
+
+/*virtual*/ void SignalIf::PseudoInstruction::Remove()
+{
+ if (opcode == PSO_IF_ELSE) {
+ this->block->if_true = NULL;
+ while(this->block->if_false) this->block->if_false->Remove();
+ } else if (opcode == PSO_IF_ENDIF) {
+ this->block->if_false = NULL;
+ } else NOT_REACHED();
+ delete this;
+}
+
+/*virtual*/ void SignalIf::PseudoInstruction::Evaluate(SignalVM &vm)
+{
+ DEBUG(misc, 7, " Executing If Pseudo Instruction %s", opcode == PSO_IF_ELSE ? "Else" : "Endif");
+ vm.instruction = this->block->after;
+}
+
+/*virtual*/ void SignalIf::PseudoInstruction::SetNext(SignalInstruction *next_insn)
+{
+ if (this->opcode == PSO_IF_ELSE) {
+ this->block->if_false = next_insn;
+ } else if (this->opcode == PSO_IF_ENDIF) {
+ this->block->after = next_insn;
+ } else NOT_REACHED();
+}
+
+SignalIf::SignalIf(SignalProgram *prog, bool raw)
+ : SignalInstruction(prog, PSO_IF)
+{
+ if (!raw) {
+ this->condition = new SignalSimpleCondition(PSC_ALWAYS);
+ this->if_true = new PseudoInstruction(prog, this, PSO_IF_ELSE);
+ this->if_false = new PseudoInstruction(prog, this, PSO_IF_ENDIF);
+ this->after = NULL;
+ }
+}
+
+/*virtual*/ void SignalIf::Remove()
+{
+ delete this->condition;
+ while (this->if_true) this->if_true->Remove();
+
+ this->previous->SetNext(this->after);
+ this->after->SetPrevious(this->previous);
+ delete this;
+}
+
+/*virtual*/ void SignalIf::Insert(SignalInstruction *before_insn)
+{
+ this->previous = before_insn->Previous();
+ before_insn->Previous()->SetNext(this);
+ before_insn->SetPrevious(this->if_false);
+ this->after = before_insn;
+}
+
+void SignalIf::SetCondition(SignalCondition *cond)
+{
+ assert(cond != this->condition);
+ delete this->condition;
+ this->condition = cond;
+}
+
+/*virtual*/ void SignalIf::Evaluate(SignalVM &vm)
+{
+ bool is_true = this->condition->Evaluate(vm);
+ DEBUG(misc, 7, " Executing If, taking %s branch", is_true ? "then" : "else");
+ if (is_true) {
+ vm.instruction = this->if_true;
+ } else {
+ vm.instruction = this->if_false;
+ }
+}
+
+/*virtual*/ void SignalIf::SetNext(SignalInstruction *next_insn)
+{
+ this->if_true = next_insn;
+}
+
+
+
+SignalSet::SignalSet(SignalProgram *prog, SignalState state)
+ : SignalInstruction(prog, PSO_SET_SIGNAL)
+{
+ this->to_state = state;
+}
+
+/*virtual*/ void SignalSet::Remove()
+{
+ this->next->SetPrevious(this->previous);
+ this->previous->SetNext(this->next);
+ delete this;
+}
+
+/*virtual*/ void SignalSet::Evaluate(SignalVM &vm)
+{
+ DEBUG(misc, 7, " Executing SetSignal, making %s", this->to_state? "green" : "red");
+ vm.state = this->to_state;
+ vm.instruction = NULL;
+}
+
+
+/*virtual*/ void SignalSet::SetNext(SignalInstruction *next_insn)
+{
+ this->next = next_insn;
+}
+
+SignalProgram *GetExistingSignalProgram(SignalReference ref)
+{
+ ProgramList::iterator i = _signal_programs.find(ref);
+ if (i != _signal_programs.end()) {
+ assert(i->first == ref);
+ return i->second;
+ } else {
+ return NULL;
+ }
+}
+
+
+SignalProgram *GetSignalProgram(SignalReference ref)
+{
+ SignalProgram *pr = GetExistingSignalProgram(ref);
+ if (!pr) {
+ pr = new SignalProgram(ref.tile, ref.track);
+ _signal_programs[ref] = pr;
+ } else assert(pr->tile == ref.tile && pr->track == ref.track);
+ return pr;
+}
+
+void FreeSignalProgram(SignalReference ref)
+{
+ DeleteWindowById(WC_SIGNAL_PROGRAM, (ref.tile << 3) | ref.track);
+ ProgramList::iterator i = _signal_programs.find(ref);
+ if (i != _signal_programs.end()) {
+ delete i->second;
+ _signal_programs.erase(i);
+ }
+}
+
+void FreeSignalPrograms()
+{
+ ProgramList::iterator i, e;
+ for (i = _signal_programs.begin(), e = _signal_programs.end(); i != e;) {
+ delete i->second;
+ // Must postincrement here to avoid iterator invalidation
+ _signal_programs.erase(i++);
+ }
+}
+
+SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green)
+{
+ SignalProgram *program = GetSignalProgram(ref);
+ SignalVM vm;
+ vm.program = program;
+ vm.num_exits = num_exits;
+ vm.num_green = num_green;
+
+ vm.instruction = program->first_instruction;
+ vm.state = SIGNAL_STATE_RED;
+
+ DEBUG(misc, 7, "%d exits, of which %d green", vm.num_exits, vm.num_green);
+ vm.Execute();
+ DEBUG(misc, 7, "Returning %s", vm.state == SIGNAL_STATE_GREEN ? "green" : "red");
+ return vm.state;
+}
+
+void RemoveProgramDependencies(SignalReference by, SignalReference on)
+{
+ SignalProgram *prog = GetSignalProgram(by);
+ for (SignalInstruction **b = prog->instructions.Begin(), **i = b, **e = prog->instructions.End();
+ i != e; i++) {
+ SignalInstruction *insn = *i;
+ if (insn->Opcode() == PSO_IF) {
+ SignalIf* ifi = static_cast(insn);
+ if (ifi->condition->ConditionCode() == PSC_SIGNAL_STATE) {
+ SignalStateCondition* c = static_cast(ifi->condition);
+ if(c->sig_tile == by.tile && TrackdirToTrack(c->sig_track) == by.track)
+ c->Invalidate();
+ }
+ }
+ }
+
+ AddTrackToSignalBuffer(by.tile, by.track, GetTileOwner(by.tile));
+ UpdateSignalsInBuffer();
+}
+
+void SignalProgram::DebugPrintProgram()
+{
+ DEBUG(misc, 5, "Program %p listing", this);
+ for (SignalInstruction **b = this->instructions.Begin(), **i = b, **e = this->instructions.End();
+ i != e; i++)
+ {
+ SignalInstruction *insn = *i;
+ DEBUG(misc, 5, " %ld: Opcode %d, prev %d", long(i - b), int(insn->Opcode()),
+ int(insn->Previous() ? insn->Previous()->Id() : -1));
+ }
+}
+
+/** Insert a signal instruction into the signal program.
+ *
+ * @param tile The Tile on which to perform the operation
+ * @param p1 Flags and information
+ * - Bits 0-2 Which track the signal sits on
+ * - Bits 3-18 ID of instruction to insert before
+ * - Bits 19-26 Which opcode to create
+ * - Bits 27-31 Reserved
+ * @param p2 Depends upon instruction
+ * - PSO_SET_SIGNAL:
+ * - Colour to set the signal to
+ * @param text unused
+ */
+CommandCost CmdInsertSignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ Track track = Extract(p1);
+ uint instruction_id = GB(p1, 3, 16);
+ SignalOpcode op = Extract(p1);
+
+ if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
+ return CMD_ERROR;
+ }
+
+ if (!IsTileOwner(tile, _current_company))
+ return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
+
+ SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track));
+ if (!prog)
+ return_cmd_error(STR_ERR_PROGSIG_NOT_THERE);
+ if (instruction_id > prog->instructions.Length())
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION);
+
+ bool exec = (flags & DC_EXEC) != 0;
+
+ SignalInstruction *insert_before = prog->instructions[instruction_id];
+ switch (op) {
+ case PSO_IF: {
+ if (!exec) return CommandCost();
+ SignalIf *if_ins = new SignalIf(prog);
+ if_ins->Insert(insert_before);
+ break;
+ }
+
+ case PSO_SET_SIGNAL: {
+ SignalState ss = (SignalState) p2;
+ if (ss > SIGNAL_STATE_MAX) return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE);
+ if (!exec) return CommandCost();
+
+ SignalSet *set = new SignalSet(prog, ss);
+ set->Insert(insert_before);
+ break;
+ }
+
+ case PSO_FIRST:
+ case PSO_LAST:
+ case PSO_IF_ELSE:
+ case PSO_IF_ENDIF:
+ default:
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE);
+ }
+
+ if (!exec) return CommandCost();
+ AddTrackToSignalBuffer(tile, track, GetTileOwner(tile));
+ UpdateSignalsInBuffer();
+ InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track);
+ return CommandCost();
+}
+
+/** Modify a singal instruction
+ *
+ * @param tile The Tile on which to perform the operation
+ * @param p1 Flags and information
+ * - Bits 0-2 Which track the signal sits on
+ * - Bits 3-18 ID of instruction to insert before
+ * @param p2 Depends upon instruction
+ * - PSO_SET_SIGNAL:
+ * - Colour to set the signal to
+ * - PSO_IF:
+ * - Bit 0 If 0, set the condidion code:
+ * - Bit 1-8: Conditon code to change to
+ * - Otherwise, if SignalVariableCondition:
+ * - Bits 1-2: Which field to change (ConditionField)
+ * - Bits 3-31: Value to set field to
+ * - Otherwise, if SignalStateCondition:
+ * - Bits 1-4: Trackdir on which signal is located
+ * - Bits 5-31: Tile on which signal is located
+ * @param text unused
+ */
+CommandCost CmdModifySignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ Track track = Extract(p1);
+ uint instruction_id = GB(p1, 3, 16);
+
+ if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
+ return CMD_ERROR;
+ }
+
+ if (!IsTileOwner(tile, _current_company))
+ return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
+
+ SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track));
+ if (!prog)
+ return_cmd_error(STR_ERR_PROGSIG_NOT_THERE);
+
+ if (instruction_id > prog->instructions.Length())
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION);
+
+ bool exec = (flags & DC_EXEC) != 0;
+
+ SignalInstruction *insn = prog->instructions[instruction_id];
+ switch (insn->Opcode()) {
+ case PSO_SET_SIGNAL: {
+ SignalState state = (SignalState) p2;
+ if (state > SIGNAL_STATE_MAX)
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_SIGNAL_STATE);
+ if (!exec)
+ return CommandCost();
+ SignalSet *ss = static_cast(insn);
+ ss->to_state = state;
+ } break;
+
+ case PSO_IF: {
+ SignalIf *si = static_cast(insn);
+ byte act = GB(p2, 0, 1);
+ if (act == 0) { // Set code
+ SignalConditionCode code = (SignalConditionCode) GB(p2, 1, 8);
+ if (code > PSC_MAX)
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_CONDITION);
+ if (!exec) return CommandCost();
+
+ SignalCondition *cond;
+ switch (code) {
+ case PSC_ALWAYS:
+ case PSC_NEVER:
+ cond = new SignalSimpleCondition(code);
+ break;
+
+ case PSC_NUM_GREEN:
+ case PSC_NUM_RED:
+ cond = new SignalVariableCondition(code);
+ break;
+
+ case PSC_SIGNAL_STATE:
+ cond = new SignalStateCondition(SignalReference(tile, track), INVALID_TILE, INVALID_TRACKDIR);
+ break;
+
+ default: NOT_REACHED();
+ }
+ si->SetCondition(cond);
+ } else { // modify condition
+ switch (si->condition->ConditionCode()) {
+ case PSC_ALWAYS:
+ case PSC_NEVER:
+ return CommandCost(STR_ERR_PROGSIG_INVALID_CONDITION_FIELD);
+
+ case PSC_NUM_GREEN:
+ case PSC_NUM_RED: {
+ SignalVariableCondition *vc = static_cast(si->condition);
+ SignalConditionField f = (SignalConditionField) GB(p2, 1, 2);
+ uint32 val = GB(p2, 3, 27);
+ if (f == SCF_COMPARATOR) {
+ if (val > SGC_LAST) return_cmd_error(STR_ERR_PROGSIG_INVALID_COMPARATOR);
+ if (!exec) return CommandCost();
+ vc->comparator = (SignalComparator) val;
+ } else if (f == SCF_VALUE) {
+ if (!exec) return CommandCost();
+ vc->value = val;
+ } else CommandCost(STR_ERR_PROGSIG_INVALID_CONDITION_FIELD);
+ } break;
+
+ case PSC_SIGNAL_STATE: {
+ SignalStateCondition *sc = static_cast(si->condition);
+ Trackdir td = (Trackdir) GB(p2, 1, 4);
+ TileIndex ti = (TileIndex) GB(p2, 5, 27);
+
+ if (!IsValidTile(ti) || !IsValidTrackdir(td) || !HasSignalOnTrackdir(ti, td)
+ || GetTileOwner(ti) != _current_company)
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_SIGNAL);
+ if (!exec) return CommandCost();
+ sc->SetSignal(ti, td);
+ } break;
+ }
+ }
+ } break;
+
+ case PSO_FIRST:
+ case PSO_LAST:
+ case PSO_IF_ELSE:
+ case PSO_IF_ENDIF:
+ default:
+ return CommandCost(STR_ERR_PROGSIG_INVALID_OPCODE);
+ }
+
+ if (!exec) return CommandCost();
+
+ AddTrackToSignalBuffer(tile, track, GetTileOwner(tile));
+ UpdateSignalsInBuffer();
+ InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track);
+ return CommandCost();
+}
+
+/** Remove an instruction from a signal program
+ *
+ * @param tile The Tile on which to perform the operation
+ * @param p1 Flags and information
+ * - Bits 0-2 Which track the signal sits on
+ * - Bits 3-18 ID of instruction to insert before
+ * @param p2 unused
+ * @param text unused
+ */
+CommandCost CmdRemoveSignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ Track track = Extract(p1);
+ uint instruction_id = GB(p1, 3, 16);
+
+ if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
+ return CMD_ERROR;
+ }
+
+ if (!IsTileOwner(tile, _current_company))
+ return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
+
+ SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track));
+ if (!prog)
+ return_cmd_error(STR_ERR_PROGSIG_NOT_THERE);
+
+ if (instruction_id > prog->instructions.Length())
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION);
+
+ bool exec = (flags & DC_EXEC) != 0;
+
+ SignalInstruction *insn = prog->instructions[instruction_id];
+ switch (insn->Opcode()) {
+ case PSO_SET_SIGNAL:
+ case PSO_IF:
+ if (!exec) return CommandCost();
+ insn->Remove();
+ break;
+
+ case PSO_FIRST:
+ case PSO_LAST:
+ case PSO_IF_ELSE:
+ case PSO_IF_ENDIF:
+ default:
+ return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE);
+ }
+
+ if (!exec) return CommandCost();
+ AddTrackToSignalBuffer(tile, track, GetTileOwner(tile));
+ UpdateSignalsInBuffer();
+ InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track);
+ return CommandCost();
+}
Index: src/programmable_signals.h
new file mode 100644
===================================================================
--- src/programmable_signals.h (revision 0)
+++ src/programmable_signals.h (working copy)
@@ -0,0 +1,395 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file programmable_signals.h Programmable Signals */
+
+#ifndef PROGRAMMABLE_SIGNALS_H
+#define PROGRAMMABLE_SIGNALS_H
+#include "rail_map.h"
+#include "core/smallvec_type.hpp"
+#include
+
+/** @defgroup progsigs Programmable Signals */
+///@{
+
+/** The Programmable Signal virtual machine.
+ *
+ * This structure contains the state of the currently executing signal program.
+ */
+struct SignalVM;
+
+class SignalInstruction;
+class SignalSpecial;
+typedef SmallVector InstructionList;
+
+/** The actual programmable signal information */
+struct SignalProgram {
+ SignalProgram(TileIndex tile, Track track, bool raw = false);
+ ~SignalProgram();
+ void DebugPrintProgram();
+
+ TileIndex tile;
+ Track track;
+
+ SignalSpecial *first_instruction;
+ SignalSpecial *last_instruction;
+ InstructionList instructions;
+};
+
+/** Programmable Signal opcode.
+ *
+ * Opcode types are discriminated by this enumeration. It is primarily used for
+ * code which must be able to inspect the type of a signal operation, rather than
+ * evaluate it (such as the programming GUI)
+ */
+enum SignalOpcode {
+ PSO_FIRST = 0, ///< Start pseudo instruction
+ PSO_LAST = 1, ///< End pseudo instruction
+ PSO_IF = 2, ///< If instruction
+ PSO_IF_ELSE = 3, ///< If Else pseudo instruction
+ PSO_IF_ENDIF = 4, ///< If Endif pseudo instruction
+ PSO_SET_SIGNAL = 5, ///< Set signal instruction
+
+ PSO_END,
+ PSO_INVALID = 0xFF
+};
+template <> struct EnumPropsT : MakeEnumPropsT {};
+
+/** Signal instruction base class. All instructions must derive from this. */
+class SignalInstruction {
+public:
+ /// Get the instruction's opcode
+ inline SignalOpcode Opcode() const { return this->opcode; }
+
+ /// Get the previous instruction. If this is NULL, then this is the first
+ /// instruction.
+ inline SignalInstruction *Previous() const { return this->previous; }
+
+ /// Get the Id of this instruction
+ inline int Id() const
+ // Const cast is safe (perculiarity of SmallVector)
+ { return program->instructions.FindIndex(const_cast(this)); }
+
+ /// Insert this instruction, placing it before @p before_insn
+ virtual void Insert(SignalInstruction *before_insn);
+
+ /// Evaluate the instruction. The instruction should update the VM state.
+ virtual void Evaluate(SignalVM &vm) = 0;
+
+ /// Remove the instruction. When removing itself, an instruction should
+ ///
+ /// Set next->previous to previous
+ /// Set previous->next to next
+ /// Destroy any other children
+ ///
+ virtual void Remove() = 0;
+
+ /// Gets a reference to the previous member. This is only intended for use by
+ /// the saveload code.
+ inline SignalInstruction *&GetPrevHandle()
+ { return previous; }
+
+ /// Sets the previous instruction of this instruction. This is only intended
+ /// to be used by instructions to update links during insertion and removal.
+ inline void SetPrevious(SignalInstruction *prev)
+ { previous = prev; }
+ /// Set the next instruction. This is only intended to be used by instructions
+ /// to update links during insertion and removal
+ virtual void SetNext(SignalInstruction *next_insn) = 0;
+
+protected:
+ /// Constructs an instruction
+ /// @param prog the program to add this instruction to
+ /// @param op the opcode of the instruction
+ SignalInstruction(SignalProgram *prog, SignalOpcode op) ;
+ virtual ~SignalInstruction();
+
+ const SignalOpcode opcode;
+ SignalInstruction *previous;
+ SignalProgram *program;
+};
+
+/** Programmable Signal condition code.
+ *
+ * These discriminate conditions in much the same way that SignalOpcode
+ * discriminates instructions.
+ */
+enum SignalConditionCode {
+ PSC_ALWAYS = 0, ///< Always true
+ PSC_NEVER = 1, ///< Always false
+ PSC_NUM_GREEN = 2, ///< Number of green signals behind this signal
+ PSC_NUM_RED = 3, ///< Number of red signals behind this signal
+ PSC_SIGNAL_STATE = 4, ///< State of another signal
+
+ PSC_MAX = PSC_SIGNAL_STATE
+};
+
+class SignalCondition {
+public:
+ /// Get the condition's code
+ inline SignalConditionCode ConditionCode() const { return this->cond_code; }
+
+ /// Evaluate the condition
+ virtual bool Evaluate(SignalVM& vm) = 0;
+
+ /// Destroy the condition. Any children should also be destroyed
+ virtual ~SignalCondition();
+
+protected:
+ SignalCondition(SignalConditionCode code) : cond_code(code) {}
+
+ const SignalConditionCode cond_code;
+};
+
+// -- Condition codes --
+/** Simple condition code. These conditions have no complex inputs, and can be
+ * evaluated directly from VM state and their condition code.
+ */
+class SignalSimpleCondition: public SignalCondition {
+public:
+ SignalSimpleCondition(SignalConditionCode code);
+ virtual bool Evaluate(SignalVM& vm);
+};
+
+/** Comparator to use for variable conditions. */
+enum SignalComparator {
+ SGC_EQUALS = 0, ///< the variable is equal to the specified value
+ SGC_NOT_EQUALS = 1, ///< the variable is not equal to the specified value
+ SGC_LESS_THAN = 2, ///< the variable is less than specified value
+ SGC_LESS_THAN_EQUALS = 3, ///< the variable is less than or equal to the specified value
+ SGC_MORE_THAN = 4, ///< the variable is greater than the specified value
+ SGC_MORE_THAN_EQUALS = 5, ///< the variable is grater than or equal to the specified value
+ SGC_IS_TRUE = 6, ///< the variable is true (non-zero)
+ SGC_IS_FALSE = 7, ///< the variable is false (zero)
+
+ SGC_LAST = SGC_IS_FALSE
+};
+
+/** Which field to modify in a condition. A parameter to CMD_MODIFY_SIGNAL_INSTRUCTION */
+enum SignalConditionField {
+ SCF_COMPARATOR = 0, ///< the comparator (value from SignalComparator enum)
+ SCF_VALUE = 1, ///< the value (integer value)
+};
+
+/** A conditon based upon comparing a variable and a value. This condition can be
+ * considered similar to the conditonal jumps in vehicle orders.
+ *
+ * The variable is specified by the conditon code, the comparison by @p comparator, and
+ * the value to compare against by @p value. The condition returns the result of that value.
+ */
+class SignalVariableCondition: public SignalCondition {
+public:
+ /// Constructs a condition refering to the value @p code refers to. Sets the
+ /// comparator and value to sane defaults.
+ SignalVariableCondition(SignalConditionCode code);
+
+ SignalComparator comparator;
+ uint32 value;
+
+ /// Evaluates the condition
+ virtual bool Evaluate(SignalVM &vm);
+};
+
+/** A condition which is based upon the state of another signal. */
+class SignalStateCondition: public SignalCondition {
+ public:
+ SignalStateCondition(SignalReference this_sig, TileIndex sig_tile, Trackdir sig_track);
+
+ void SetSignal(TileIndex tile, Trackdir track);
+ bool IsSignalValid();
+ void Invalidate();
+
+ virtual bool Evaluate(SignalVM& vm);
+ virtual ~SignalStateCondition();
+
+ SignalReference this_sig;
+ TileIndex sig_tile;
+ Trackdir sig_track;
+ SignalState state;
+};
+
+// -- Instructions
+
+/** The special start and end pseudo instructions.
+ *
+ * These instructions serve two purposes:
+ *
+ * They permit every other instruction to assume that there is another
+ * following it. This makes the code much simpler (and by extension less
+ * error prone)
+ * Particularly in the case of the End instruction, they provide an
+ * instruction in the user interface that can be clicked on to add
+ * instructions at the end of a program
+ *
+ */
+class SignalSpecial: public SignalInstruction {
+public:
+ /** Constructs a special signal of the opcode @p op in program @p prog.
+ *
+ * Generally you should not need to call this; it will be called by the
+ * program's constructor. An exception is in the saveload code, which needs
+ * to construct raw objects to deserialize into
+ */
+ SignalSpecial(SignalProgram *prog, SignalOpcode op);
+
+ /** Evaluates the instruction. If this is an Start instruction, flow will be
+ * vectored to the first instruction; if it is an End instruction, the program
+ * will terminate and the signal will be left red.
+ */
+ virtual void Evaluate(SignalVM &vm);
+
+ /** Links the first and last instructions in the program. Generally only to be
+ * called from the SignalProgram constructor.
+ */
+ static void link(SignalSpecial *first, SignalSpecial *last);
+
+ /** Removes this instruction. If this is the start instruction, then all of
+ * the other instructions in the program will be successively removed,
+ * (emptying it). If this is the End instruction, then it will do nothing.
+ *
+ * This operation, unlike when executed on most instructions, does not destroy
+ * the instruction.
+ */
+ virtual void Remove();
+
+ /** The next instruction after this one. On the End instruction, this should
+ * be NULL.
+ */
+ SignalInstruction *next;
+
+ virtual void SetNext(SignalInstruction *next_insn);
+};
+
+/** If signal instruction. This is perhaps the most important, as without it,
+ * programmable signals are pretty useless.
+ *
+ * It's also the most complex!
+ */
+class SignalIf: public SignalInstruction {
+public:
+ /** The If-Else and If-Endif pseudo instructions. The Else instruction
+ * follows the Then block, and the Endif instruction follows the Else block.
+ *
+ * These serve two purposes:
+ *
+ * They correctly vector the execution to after the if block
+ * (if needed)
+ * They provide an instruction for the GUI to insert other instructions
+ * before.
+ *
+ */
+ class PseudoInstruction: public SignalInstruction {
+ public:
+ /** Normal constructor. The pseudo instruction will be constructed as
+ * belonging to @p block.
+ */
+ PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op);
+
+ /** Constructs an empty instruction of type @p op. This should only be used
+ * by the saveload code during deserialization. The instruction must have
+ * its block field set correctly before the program is run.
+ */
+ PseudoInstruction(SignalProgram *prog, SignalOpcode op);
+
+ /** Removes the pseudo instruction. Unless you are also removing the If it
+ * belongs to, this is nonsense and dangerous.
+ */
+ virtual void Remove();
+
+ /** Evaluate the pseudo instruction. This involves vectoring execution to
+ * the instruction after the if.
+ */
+ virtual void Evaluate(SignalVM &vm);
+
+ /** The block to which this instruction belongs */
+ SignalIf *block;
+ virtual void SetNext(SignalInstruction *next_insn);
+ };
+
+public:
+ /** Constructs an If instruction belonging to program @p prog. If @p raw is
+ * true, then the instruction is constructed raw (in order for the
+ * deserializer to be able to correctly deserialize the instruction).
+ */
+ SignalIf(SignalProgram *prog, bool raw = false);
+
+ /** Sets the instruction's condition, and releases the old condition */
+ void SetCondition(SignalCondition *cond);
+
+ /** Evaluates the If and takes the appropriate branch */
+ virtual void Evaluate(SignalVM &vm);
+
+ virtual void Insert(SignalInstruction *before_insn);
+
+ /** Removes the If and all of its children */
+ virtual void Remove();
+
+ SignalCondition *condition; ///< The if conditon
+ SignalInstruction *if_true; ///< The branch to take if true
+ SignalInstruction *if_false; ///< The branch to take if false
+ SignalInstruction *after; ///< The branch to take after the If
+
+ virtual void SetNext(SignalInstruction *next_insn);
+};
+
+/** Set signal instruction. This sets the state of the signal and terminates execution */
+class SignalSet: public SignalInstruction {
+public:
+ /// Constructs the instruction and sets the state the signal is to be set to
+ SignalSet(SignalProgram *prog, SignalState = SIGNAL_STATE_RED);
+
+ virtual void Evaluate(SignalVM &vm);
+ virtual void Remove();
+
+ /// The state to set the signal to
+ SignalState to_state;
+
+ /// The instruction following this one (for the editor)
+ SignalInstruction *next;
+
+ virtual void SetNext(SignalInstruction *next_insn);
+};
+
+/// The map type used for looking up signal programs
+typedef std::map ProgramList;
+
+/// The global signal program list
+extern ProgramList _signal_programs;
+
+/// Verifies that a SignalReference refers to a signal which has a program.
+static inline bool HasProgrammableSignals(SignalReference ref)
+{
+ return IsTileType(ref.tile, MP_RAILWAY) && GetRailTileType(ref.tile) == RAIL_TILE_SIGNALS
+ && IsPresignalProgrammable(ref.tile, ref.track);
+}
+
+/// Shows the programming window for the signal identified by @p tile and
+/// @p track.
+void ShowSignalProgramWindow(SignalReference ref);
+
+/// Gets the signal program for the tile identified by @p t and @p track.
+/// An empty program will be constructed if none is specified
+SignalProgram *GetSignalProgram(SignalReference ref);
+
+SignalProgram *GetExistingSignalProgram(SignalReference ref);
+
+/// Frees a signal program by tile and track
+void FreeSignalProgram(SignalReference ref);
+
+/// Frees all signal programs (For use when creating a new game)
+void FreeSignalPrograms();
+
+/// Runs the signal program, specifying the following parameters.
+SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green);
+
+/// Remove dependencies on signal @p on from @p by
+void RemoveProgramDependencies(SignalReference by, SignalReference on);
+///@}
+
+#endif
Index: src/programmable_signals_gui.cpp
new file mode 100644
===================================================================
--- src/programmable_signals_gui.cpp (revision 0)
+++ src/programmable_signals_gui.cpp (working copy)
@@ -0,0 +1,904 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file programmable_signals_gui.cpp GUI related to programming signals */
+
+#include "stdafx.h"
+#include "programmable_signals.h"
+#include "command_func.h"
+#include "window_func.h"
+#include "strings_func.h"
+#include "string_func.h"
+#include "viewport_func.h"
+#include "textbuf_gui.h"
+#include "company_func.h"
+#include "widgets/dropdown_func.h"
+#include "gui.h"
+#include "gfx_func.h"
+#include "tilehighlight_func.h"
+#include "rail_map.h"
+#include "tile_cmd.h"
+#include "error.h"
+
+#include "table/sprites.h"
+#include "table/strings.h"
+
+enum ProgramWindowWidgets {
+ PROGRAM_WIDGET_CAPTION,
+ PROGRAM_WIDGET_INSTRUCTION_LIST,
+ PROGRAM_WIDGET_SCROLLBAR,
+
+ PROGRAM_WIDGET_SEL_TOP_LEFT,
+ PROGRAM_WIDGET_SEL_TOP_MIDDLE,
+ PROGRAM_WIDGET_SEL_TOP_RIGHT,
+
+ PROGRAM_WIDGET_SET_STATE,
+ PROGRAM_WIDGET_COND_VARIABLE,
+ PROGRAM_WIDGET_COND_COMPARATOR,
+ PROGRAM_WIDGET_COND_VALUE,
+ PROGRAM_WIDGET_COND_GOTO_SIGNAL,
+ PROGRAM_WIDGET_COND_SET_SIGNAL,
+
+ PROGRAM_WIDGET_GOTO_SIGNAL,
+ PROGRAM_WIDGET_INSERT,
+ PROGRAM_WIDGET_REMOVE,
+
+ PROGRAM_WIDGET_REMOVE_PROGRAM,
+ PROGRAM_WIDGET_COPY_PROGRAM,
+};
+
+enum PanelWidgets {
+ // Left
+ DPL_COND_VARIABLE = 0,
+ DPL_SET_STATE,
+
+ // Middle
+ DPM_COND_COMPARATOR = 0,
+ DPM_COND_GOTO_SIGNAL,
+
+ // Right
+ DPR_COND_VALUE = 0,
+ DPR_COND_SET_SIGNAL
+};
+
+static const StringID _program_insert[] = {
+ STR_PROGSIG_INSERT_IF,
+ STR_PROGSIG_INSERT_SET_SIGNAL,
+ INVALID_STRING_ID
+};
+
+static SignalOpcode OpcodeForIndex(int index)
+{
+ switch (index) {
+ case 0: return PSO_IF;
+ case 1: return PSO_SET_SIGNAL;
+ default: NOT_REACHED();
+ }
+}
+
+static bool IsConditionComparator(SignalCondition *cond)
+{
+ switch (cond->ConditionCode()) {
+ case PSC_NUM_GREEN:
+ case PSC_NUM_RED:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static const StringID _program_condvar[] = {
+ /* PSC_ALWAYS */ STR_PROGSIG_COND_ALWAYS,
+ /* PSC_NEVER */ STR_PROGSIG_COND_NEVER,
+ /* PSC_NUM_GREEN */ STR_PROGSIG_CONDVAR_NUM_GREEN,
+ /* PSC_NUM_RED */ STR_PROGSIG_CONDVAR_NUM_RED,
+ /* PSC_SIGNAL_STATE*/ STR_PROGSIG_COND_SIGNAL_STATE,
+ INVALID_STRING_ID
+};
+
+// TODO: These should probably lose the ORDER
+static const StringID _program_comparator[] = {
+ /* SGC_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_EQUALS,
+ /* SGC_NOT_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_NOT_EQUALS,
+ /* SGC_LESS_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_THAN,
+ /* SGC_LESS_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_EQUALS,
+ /* SGC_MORE_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_THAN,
+ /* SGC_MORE_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_EQUALS,
+ /* SGC_IS_TRUE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_TRUE,
+ /* SGC_IS_FALSE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_FALSE,
+ INVALID_STRING_ID
+};
+
+static const StringID _program_sigstate[] = {
+ STR_COLOUR_RED,
+ STR_COLOUR_GREEN,
+ INVALID_STRING_ID
+};
+
+/** Get the string for a condition */
+static char *GetConditionString(SignalCondition *cond, char *buf, char *buflast, bool selected)
+{
+ StringID string = INVALID_STRING_ID;
+ bool comparator = IsConditionComparator(cond);
+
+ if (comparator) {
+ SignalVariableCondition *cv = static_cast(cond);
+ string = STR_PROGSIG_COND_COMPARE;
+ SetDParam(0, _program_condvar[cond->ConditionCode()]);
+ SetDParam(1, _program_comparator[cv->comparator]);
+ SetDParam(2, cv->value);
+ } else {
+ string = _program_condvar[cond->ConditionCode()];
+ if (cond->ConditionCode() == PSC_SIGNAL_STATE) {
+ string = STR_PROGSIG_CONDVAR_SIGNAL_STATE;
+ SetDParam(0, static_cast(cond)->IsSignalValid()
+ ? STR_PROGSIG_CONDVAR_SIGNAL_STATE_SPECIFIED : STR_PROGSIG_CONDVAR_SIGNAL_STATE_UNSPECIFIED);
+ SetDParam(1, selected ? STR_WHITE : STR_BLACK);
+ }
+ }
+ return GetString(buf, string, buflast);
+}
+
+/**
+ * Draws an instruction in the programming GUI
+ * @param instruction The instruction to draw
+ * @param y Y position for drawing
+ * @param selected True, if the order is selected
+ * @param indent How many levels the instruction is indented
+ * @param left Left border for text drawing
+ * @param right Right border for text drawing
+ */
+static void DrawInstructionString(SignalInstruction *instruction, int y, bool selected, int indent, int left, int right)
+{
+ StringID instruction_string = INVALID_STRING_ID;
+
+ char condstr[512];
+
+ switch (instruction->Opcode()) {
+ case PSO_FIRST:
+ instruction_string = STR_PROGSIG_FIRST;
+ break;
+
+ case PSO_LAST:
+ instruction_string = STR_PROGSIG_LAST;
+ break;
+
+ case PSO_IF: {
+ SignalIf *if_ins = static_cast(instruction);
+ GetConditionString(if_ins->condition, condstr, lastof(condstr), selected);
+ SetDParamStr(0, condstr);
+ instruction_string = STR_PROGSIG_IF;
+ break;
+ }
+
+ case PSO_IF_ELSE:
+ instruction_string = STR_PROGSIG_ELSE;
+ break;
+
+ case PSO_IF_ENDIF:
+ instruction_string = STR_PROGSIG_ENDIF;
+ break;
+
+ case PSO_SET_SIGNAL: {
+ instruction_string = STR_PROGSIG_SET_SIGNAL;
+ SignalSet *set = static_cast(instruction);
+ SetDParam(0, _program_sigstate[set->to_state]);
+ break;
+ }
+
+ default: NOT_REACHED();
+ }
+
+ DrawString(left + indent * 16, right, y, instruction_string, selected ? TC_WHITE : TC_BLACK);
+}
+
+struct GuiInstruction {
+ SignalInstruction *insn;
+ uint indent;
+};
+
+typedef SmallVector GuiInstructionList;
+
+class ProgramWindow: public Window {
+public:
+ ProgramWindow(WindowDesc *desc, SignalReference ref): Window(desc)
+ {
+ // this->InitNested(desc, (ref.tile << 3) | ref.track);
+ this->tile = ref.tile;
+ this->track = ref.track;
+ this->selected_instruction = -1;
+
+ this->CreateNestedTree(desc);
+ this->vscroll = this->GetScrollbar(PROGRAM_WIDGET_SCROLLBAR);
+ this->FinishInitNested((ref.tile << 3) | ref.track);
+
+ program = GetSignalProgram(ref);
+ RebuildInstructionList();
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count) OVERRIDE
+ {
+ switch (widget) {
+ case PROGRAM_WIDGET_INSTRUCTION_LIST: {
+ int sel = this->GetInstructionFromPt(pt.y);
+
+ this->DeleteChildWindows();
+ HideDropDownMenu(this);
+
+ if (sel == -1 || this->GetOwner() != _local_company) {
+ // Deselect
+ this->selected_instruction = -1;
+ } else {
+ this->selected_instruction = sel;
+ }
+
+ this->UpdateButtonState();
+ } break;
+
+ case PROGRAM_WIDGET_INSERT: {
+ DEBUG(misc, 5, "Selection is %d", this->selected_instruction);
+ if (this->GetOwner() != _local_company || this->selected_instruction < 1)
+ return;
+ ShowDropDownMenu(this, _program_insert, -1, PROGRAM_WIDGET_INSERT, 0, 0, 0);
+ } break;
+
+ case PROGRAM_WIDGET_REMOVE: {
+ SignalInstruction *ins = GetSelected();
+ if (this->GetOwner() != _local_company || !ins)
+ return;
+
+ uint32 p1 = 0;
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, ins->Id());
+
+ DoCommandP(this->tile, p1, 0, CMD_REMOVE_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
+ this->RebuildInstructionList();
+ } break;
+
+ case PROGRAM_WIDGET_SET_STATE: {
+ SignalInstruction *si = this->GetSelected();
+ if (!si || si->Opcode() != PSO_SET_SIGNAL) return;
+ SignalSet *ss = static_cast (si);
+
+ ShowDropDownMenu(this, _program_sigstate, ss->to_state, PROGRAM_WIDGET_SET_STATE, 0, 0, 0);
+ } break;
+
+ case PROGRAM_WIDGET_COND_VARIABLE: {
+ SignalInstruction *si = this->GetSelected();
+ if (!si || si->Opcode() != PSO_IF) return;
+ SignalIf *sif = static_cast (si);
+
+ ShowDropDownMenu(this, _program_condvar, sif->condition->ConditionCode(), PROGRAM_WIDGET_COND_VARIABLE, 0, 0, 0);
+ this->UpdateButtonState();
+ } break;
+
+ case PROGRAM_WIDGET_COND_COMPARATOR: {
+ SignalInstruction *si = this->GetSelected();
+ if (!si || si->Opcode() != PSO_IF) return;
+ SignalIf *sif = static_cast (si);
+ if (!IsConditionComparator(sif->condition)) return;
+ SignalVariableCondition *vc = static_cast(sif->condition);
+
+ ShowDropDownMenu(this, _program_comparator, vc->comparator, PROGRAM_WIDGET_COND_COMPARATOR, 0, 0, 0);
+ } break;
+
+ case PROGRAM_WIDGET_COND_VALUE: {
+ SignalInstruction *si = this->GetSelected();
+ if (!si || si->Opcode() != PSO_IF) return;
+ SignalIf *sif = static_cast (si);
+ if (!IsConditionComparator(sif->condition)) return;
+ SignalVariableCondition *vc = static_cast(sif->condition);
+
+ SetDParam(0, vc->value);
+ //ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, 100, this, CS_NUMERAL, QSF_NONE);
+ ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, this, CS_NUMERAL, QSF_NONE);
+ this->UpdateButtonState();
+ } break;
+
+ case PROGRAM_WIDGET_COND_GOTO_SIGNAL: {
+ SignalInstruction *si = this->GetSelected();
+ if (!si || si->Opcode() != PSO_IF) return;
+ SignalIf *sif = static_cast (si);
+ if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return;
+ SignalStateCondition *sc = static_cast(sif->condition);
+
+ if (sc->IsSignalValid()) {
+ ScrollMainWindowToTile(sc->sig_tile);
+ } else {
+ ShowErrorMessage(STR_ERROR_CAN_T_GOTO_UNDEFINED_SIGNAL, STR_EMPTY, WL_INFO);
+ }
+ // this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
+ } break;
+
+ case PROGRAM_WIDGET_COND_SET_SIGNAL: {
+ this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COND_SET_SIGNAL);
+ this->SetWidgetDirty(PROGRAM_WIDGET_COND_SET_SIGNAL);
+ if (this->IsWidgetLowered(PROGRAM_WIDGET_COND_SET_SIGNAL)) {
+ SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this);
+ } else {
+ ResetObjectToPlace();
+ }
+ } break;
+
+ case PROGRAM_WIDGET_GOTO_SIGNAL: {
+ ScrollMainWindowToTile(this->tile);
+ // this->RaiseWidget(PROGRAM_WIDGET_GOTO_SIGNAL);
+ } break;
+ case PROGRAM_WIDGET_REMOVE_PROGRAM: {
+ if (this->GetOwner() != _local_company)
+ return;
+ program->first_instruction->Remove();
+
+ this->RebuildInstructionList();
+ } break;
+ case PROGRAM_WIDGET_COPY_PROGRAM: {
+
+ this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COPY_PROGRAM);
+ this->SetWidgetDirty(PROGRAM_WIDGET_COPY_PROGRAM);
+ if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) {
+ SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this);
+ } else {
+ ResetObjectToPlace();
+ }
+ } break;
+ }
+ }
+
+ void InsertInstruction(SignalInstruction *si, uint32 next)
+ {
+ uint64 p1 = 0;
+ while(true) {
+ switch(si->Opcode()) {
+ case PSO_SET_SIGNAL: {
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, next);
+ SB(p1, 19, 8, si->Opcode());
+
+ DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION));
+ this->RebuildInstructionList();
+ si = ((SignalSet*)si)->next;
+ } break;
+ case PSO_IF: {
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, next);
+ SB(p1, 19, 8, si->Opcode());
+
+ DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION));
+ this->RebuildInstructionList();
+
+ SignalInstruction *s = ((SignalIf*)si)->if_true;
+ while(s->Opcode() != PSO_IF_ELSE) {
+ if(s->Opcode() == PSO_IF) s = ((SignalIf*)s)->after;
+ if(s->Opcode() == PSO_SET_SIGNAL) s = ((SignalSet*)s)->next;
+ else break;
+ }
+ InsertInstruction(((SignalIf*)si)->if_true, s->Id());
+ this->RebuildInstructionList();
+
+ s = ((SignalIf*)si)->if_false;
+ while(s->Opcode() != PSO_IF_ENDIF) {
+ if(s->Opcode() == PSO_IF) s = ((SignalIf*)s)->after;
+ if(s->Opcode() == PSO_SET_SIGNAL) s = ((SignalSet*)s)->next;
+ else break;
+ }
+ InsertInstruction(((SignalIf*)si)->if_false, s->Id());
+ this->RebuildInstructionList();
+
+ si = ((SignalIf*)si)->after;
+ } break;
+ }
+ if(si == NULL) break;
+ if(si->Opcode() == PSO_LAST) break;
+ if(si->Opcode() == PSO_IF_ELSE) break;
+ if(si->Opcode() == PSO_IF_ENDIF) break;
+ }
+ }
+
+ virtual void OnPlaceObject(Point pt, TileIndex tile1) OVERRIDE
+ {
+ if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) {
+ //Copy program from another progsignal
+ TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile1, TRANSPORT_RAIL, 0));
+ if (trackbits & TRACK_BIT_VERT) { // N-S direction
+ trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT;
+ }
+ if (trackbits & TRACK_BIT_HORZ) { // E-W direction
+ trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER;
+ }
+ Track track1 = FindFirstTrack(trackbits);
+ if(track1 == INVALID_TRACK) {
+ return;
+ }
+ Trackdir td = TrackToTrackdir(track1);
+ Trackdir tdr = ReverseTrackdir(td);
+ if (!(HasSignalOnTrackdir(tile1, td) || HasSignalOnTrackdir(tile1, tdr)))
+ return;
+
+ if (GetSignalType(tile1, track1) != SIGTYPE_PROG) {
+ ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_PROG_SIGNAL, WL_INFO);
+ return;
+ }
+ if(this->tile == tile1 && this->track == track1) {
+ ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CANNOT_USE_SELF, WL_INFO);
+ return;
+ }
+
+ SignalProgram *sp = GetExistingSignalProgram(SignalReference(tile1, track1));
+ if (!sp) {
+ ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO);
+ return;
+ }
+ program->first_instruction->Remove();
+ this->RebuildInstructionList();
+
+ SignalInstruction *si = ((SignalSpecial*)sp->first_instruction)->next;
+ InsertInstruction(si, program->last_instruction->Id());
+ ResetObjectToPlace();
+ this->RaiseWidget(PROGRAM_WIDGET_COPY_PROGRAM);
+ //OnPaint(); // this appears to cause visual artefacts
+ return;
+ }
+
+ SignalInstruction *si = this->GetSelected();
+ if (!si || si->Opcode() != PSO_IF) return;
+ SignalIf *sif = static_cast (si);
+ if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return;
+
+ if (!IsPlainRailTile(tile1)) {
+ return;
+ }
+
+ TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile1, TRANSPORT_RAIL, 0));
+ if (trackbits & TRACK_BIT_VERT) { // N-S direction
+ trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT;
+ }
+
+ if (trackbits & TRACK_BIT_HORZ) { // E-W direction
+ trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER;
+ }
+ Track track1 = FindFirstTrack(trackbits);
+ if(track1 == INVALID_TRACK) {
+ return;
+ }
+
+ Trackdir td = TrackToTrackdir(track1);
+ Trackdir tdr = ReverseTrackdir(td);
+
+ if (HasSignalOnTrackdir(tile1, td) && HasSignalOnTrackdir(tile1, tdr)) {
+ ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CAN_T_DEPEND_UPON_BIDIRECTIONAL_SIGNALS, WL_INFO);
+ return;
+ } else if (HasSignalOnTrackdir(tile1, tdr) && !HasSignalOnTrackdir(tile1, td)) {
+ td = tdr;
+ }
+
+ if (!HasSignalOnTrackdir(tile1, td)) {
+ return;
+ }
+
+ //!!!!!!!!!!!!!!!
+ if (!(GetSignalType(tile1, track1) == SIGTYPE_EXIT || GetSignalType(tile1, track1) == SIGTYPE_PROG)) {
+ //!!!!!!!!!!!!!!!
+ ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO);
+ return;
+ }
+
+ uint32 p1 = 0, p2 = 0;
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, si->Id());
+
+ SB(p2, 0, 1, 1);
+ SB(p2, 1, 4, td);
+ SB(p2, 5, 27, tile1);
+
+ DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
+ ResetObjectToPlace();
+ this->RaiseWidget(PROGRAM_WIDGET_COND_SET_SIGNAL);
+ //OnPaint(); // this appears to cause visual artefacts
+ }
+
+ virtual void OnQueryTextFinished(char *str) OVERRIDE
+ {
+ if (!StrEmpty(str)) {
+ SignalInstruction *si = this->GetSelected();
+ if (!si || si->Opcode() != PSO_IF) return;
+ SignalIf *sif = static_cast (si);
+ if (!IsConditionComparator(sif->condition)) return;
+
+ uint value = atoi(str);
+
+ uint32 p1 = 0, p2 = 0;
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, si->Id());
+
+ SB(p2, 0, 1, 1);
+ SB(p2, 1, 2, SCF_VALUE);
+ SB(p2, 3, 27, value);
+
+ DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
+ }
+ }
+
+ virtual void OnDropdownSelect(int widget, int index) OVERRIDE
+ {
+ SignalInstruction *ins = this->GetSelected();
+ if (!ins) return;
+
+ switch (widget) {
+ case PROGRAM_WIDGET_INSERT: {
+ uint64 p1 = 0;
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, ins->Id());
+ SB(p1, 19, 8, OpcodeForIndex(index));
+
+ DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION));
+ this->RebuildInstructionList();
+ break;
+ }
+
+ case PROGRAM_WIDGET_SET_STATE: {
+ uint64 p1 = 0;
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, ins->Id());
+
+ DoCommandP(this->tile, p1, index, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
+ break;
+ }
+
+ case PROGRAM_WIDGET_COND_VARIABLE: {
+ uint64 p1 = 0, p2 = 0;
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, ins->Id());
+
+ SB(p2, 0, 1, 0);
+ SB(p2, 1, 8, index);
+
+ DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
+ break;
+ }
+
+ case PROGRAM_WIDGET_COND_COMPARATOR: {
+ uint64 p1 = 0, p2 = 0;
+ SB(p1, 0, 3, this->track);
+ SB(p1, 3, 16, ins->Id());
+
+ SB(p2, 0, 1, 1);
+ SB(p2, 1, 2, SCF_COMPARATOR);
+ SB(p2, 3, 27, index);
+
+ DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
+ break;
+ }
+ }
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) OVERRIDE
+ {
+ switch (widget) {
+ case PROGRAM_WIDGET_INSTRUCTION_LIST:
+ resize->height = FONT_HEIGHT_NORMAL;
+ size->height = 6 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
+ break;
+ }
+ }
+
+ virtual void OnResize() OVERRIDE
+ {
+ /* Update the scroll bar */
+ this->vscroll->SetCapacityFromWidget(this, PROGRAM_WIDGET_INSTRUCTION_LIST);
+ }
+
+ virtual void OnPaint() OVERRIDE
+ {
+ this->DrawWidgets();
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const OVERRIDE
+ {
+ if (widget != PROGRAM_WIDGET_INSTRUCTION_LIST) return;
+
+ int y = r.top + WD_FRAMERECT_TOP;
+ int line_height = this->GetWidget(PROGRAM_WIDGET_INSTRUCTION_LIST)->resize_y;
+
+ int no = this->vscroll->GetPosition();
+
+ for (const GuiInstruction *i = instructions.Begin() + no, *e = instructions.End();
+ i != e; ++i, no++) {
+ /* Don't draw anything if it extends past the end of the window. */
+ if (!this->vscroll->IsVisible(no)) break;
+
+ DrawInstructionString(i->insn, y, no == this->selected_instruction, i->indent, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT);
+ y += line_height;
+ }
+ }
+
+ virtual void OnInvalidateData(int data, bool gui_scope) OVERRIDE {
+ if (gui_scope) {
+ this->RebuildInstructionList();
+ }
+ }
+
+
+ virtual void SetStringParameters(int widget) const OVERRIDE
+ {
+ switch (widget) {
+ case PROGRAM_WIDGET_COND_VALUE: {
+ SetDParam(0, 0);
+ SignalInstruction *insn = this->GetSelected();
+ if (!insn || insn->Opcode() != PSO_IF) return;
+ SignalIf *si = static_cast(insn);
+ if (!IsConditionComparator(si->condition)) return;
+ SignalVariableCondition *vc = static_cast(si->condition);
+ SetDParam(0, vc->value);
+ } break;
+ }
+ }
+
+private:
+ SignalInstruction *GetSelected() const
+ {
+ if (this->selected_instruction == -1
+ || this->selected_instruction >= int(this->instructions.Length()))
+ return NULL;
+
+ return this->instructions[this->selected_instruction].insn;
+ }
+
+ Owner GetOwner()
+ {
+ return GetTileOwner(this->tile);
+ }
+
+ int GetInstructionFromPt(int y)
+ {
+ NWidgetBase *nwid = this->GetWidget(PROGRAM_WIDGET_INSTRUCTION_LIST);
+ int sel = (y - nwid->pos_y - WD_FRAMERECT_TOP) / nwid->resize_y; // Selected line
+
+ if ((uint)sel >= this->vscroll->GetCapacity()) return -1;
+
+ sel += this->vscroll->GetPosition();
+
+ return (sel <= int(this->instructions.Length()) && sel >= 0) ? sel : -1;
+ }
+
+ void RebuildInstructionList()
+ {
+ uint old_len = this->instructions.Length();
+ this->instructions.Clear();
+ SignalInstruction *insn = program->first_instruction;
+ uint indent = 0;
+
+ do {
+ DEBUG(misc, 5, "PSig Gui: Opcode %d", insn->Opcode());
+ switch (insn->Opcode()) {
+ case PSO_FIRST:
+ case PSO_LAST: {
+ SignalSpecial *s = static_cast(insn);
+ GuiInstruction *gi = this->instructions.Append();
+ gi->insn = s;
+ gi->indent = indent;
+ insn = s->next;
+ break;
+ }
+
+ case PSO_IF: {
+ SignalIf *i = static_cast(insn);
+ GuiInstruction *gi = this->instructions.Append();
+ gi->insn = i;
+ gi->indent = indent++;
+ insn = i->if_true;
+ break;
+ }
+
+ case PSO_IF_ELSE: {
+ SignalIf::PseudoInstruction *p = static_cast(insn);
+ GuiInstruction *gi = this->instructions.Append();
+ gi->insn = p;
+ gi->indent = indent - 1;
+ insn = p->block->if_false;
+ break;
+ }
+
+ case PSO_IF_ENDIF: {
+ SignalIf::PseudoInstruction *p = static_cast(insn);
+ GuiInstruction *gi = this->instructions.Append();
+ gi->insn = p;
+ gi->indent = --indent;
+ insn = p->block->after;
+ break;
+ }
+
+ case PSO_SET_SIGNAL: {
+ SignalSet *s = static_cast(insn);
+ GuiInstruction *gi = this->instructions.Append();
+ gi->insn = s;
+ gi->indent = indent;
+ insn = s->next;
+ break;
+ }
+
+ default: NOT_REACHED();
+ }
+ } while (insn);
+
+ this->vscroll->SetCount(this->instructions.Length());
+ if (this->instructions.Length() != old_len)
+ selected_instruction = -1;
+ UpdateButtonState();
+ }
+
+ void UpdateButtonState()
+ {
+ // Do not close the Signals GUI when opening the ProgrammableSignals GUI
+ // ResetObjectToPlace();
+ this->RaiseWidget(PROGRAM_WIDGET_INSERT);
+ this->RaiseWidget(PROGRAM_WIDGET_REMOVE);
+ this->RaiseWidget(PROGRAM_WIDGET_SET_STATE);
+ this->RaiseWidget(PROGRAM_WIDGET_COND_VARIABLE);
+ this->RaiseWidget(PROGRAM_WIDGET_COND_COMPARATOR);
+ this->RaiseWidget(PROGRAM_WIDGET_COND_VALUE);
+ this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
+
+ NWidgetStacked *left_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_LEFT);
+ NWidgetStacked *middle_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_MIDDLE);
+ NWidgetStacked *right_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_RIGHT);
+
+ // Disable all the modifier buttons - we will re-enable them if applicable
+ this->DisableWidget(PROGRAM_WIDGET_SET_STATE);
+ this->DisableWidget(PROGRAM_WIDGET_COND_VARIABLE);
+ this->DisableWidget(PROGRAM_WIDGET_COND_COMPARATOR);
+ this->DisableWidget(PROGRAM_WIDGET_COND_VALUE);
+ this->DisableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL);
+ this->DisableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
+
+ // Don't allow modifications if don't own, or have selected invalid instruction
+ if (this->GetOwner() != _local_company || this->selected_instruction < 1) {
+ this->DisableWidget(PROGRAM_WIDGET_INSERT);
+ this->DisableWidget(PROGRAM_WIDGET_REMOVE);
+ this->SetDirty();
+ return;
+ } else {
+ this->EnableWidget(PROGRAM_WIDGET_INSERT);
+ this->EnableWidget(PROGRAM_WIDGET_REMOVE);
+ }
+
+ SignalInstruction *insn = GetSelected();
+ if (!insn) return;
+
+ switch (insn->Opcode()) {
+ case PSO_IF: {
+ SignalIf *i = static_cast(insn);
+ left_sel->SetDisplayedPlane(DPL_COND_VARIABLE);
+ middle_sel->SetDisplayedPlane(DPM_COND_COMPARATOR);
+ right_sel->SetDisplayedPlane(DPR_COND_VALUE);
+
+ this->EnableWidget(PROGRAM_WIDGET_COND_VARIABLE);
+ this->GetWidget(PROGRAM_WIDGET_COND_VARIABLE)->widget_data =
+ _program_condvar[i->condition->ConditionCode()];
+
+ if (IsConditionComparator(i->condition)) {
+ SignalVariableCondition *vc = static_cast(i->condition);
+ this->EnableWidget(PROGRAM_WIDGET_COND_COMPARATOR);
+ this->EnableWidget(PROGRAM_WIDGET_COND_VALUE);
+
+ this->GetWidget(PROGRAM_WIDGET_COND_COMPARATOR)->widget_data =
+ _program_comparator[vc->comparator];
+
+ } else if (i->condition->ConditionCode() == PSC_SIGNAL_STATE) {
+ this->EnableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
+ this->EnableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL);
+ middle_sel->SetDisplayedPlane(DPM_COND_GOTO_SIGNAL);
+ right_sel->SetDisplayedPlane(DPR_COND_SET_SIGNAL);
+ }
+ } break;
+
+ case PSO_SET_SIGNAL: {
+ SignalSet *s = static_cast(insn);
+ left_sel->SetDisplayedPlane(DPL_SET_STATE);
+ this->SetWidgetDisabledState(PROGRAM_WIDGET_SET_STATE, false);
+ this->GetWidget(PROGRAM_WIDGET_SET_STATE)->widget_data =
+ _program_sigstate[s->to_state];
+ } break;
+
+ case PSO_FIRST:
+ case PSO_LAST:
+ case PSO_IF_ELSE:
+ case PSO_IF_ENDIF:
+ // All cannot be modified
+ this->DisableWidget(PROGRAM_WIDGET_REMOVE);
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+
+ this->SetDirty();
+ }
+
+ TileIndex tile;
+ Track track;
+ SignalProgram *program;
+ GuiInstructionList instructions;
+ int selected_instruction;
+ Scrollbar *vscroll;
+};
+
+static const NWidgetPart _nested_program_widgets[] = {
+ // Title bar
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+ NWidget(WWT_CAPTION, COLOUR_GREY, PROGRAM_WIDGET_CAPTION), SetDataTip(STR_PROGSIG_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_SHADEBOX, COLOUR_GREY),
+ NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
+ NWidget(WWT_STICKYBOX, COLOUR_GREY),
+ EndContainer(),
+
+ // Program display
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PANEL, COLOUR_GREY, PROGRAM_WIDGET_INSTRUCTION_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_PROGSIG_CAPTION), SetResize(1, 1), EndContainer(),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, PROGRAM_WIDGET_SCROLLBAR),
+ EndContainer(),
+
+ // Button Bar
+ NWidget(NWID_HORIZONTAL),
+ NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
+ NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_LEFT),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(1, 0),
+ SetDataTip(STR_NULL, STR_PROGSIG_COND_VARIABLE_TOOLTIP), SetResize(1, 0),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_SET_STATE), SetMinimalSize(124, 12), SetFill(1, 0),
+ SetDataTip(STR_NULL, STR_PROGSIG_SIGNAL_STATE_TOOLTIP), SetResize(1, 0),
+ EndContainer(),
+ NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_MIDDLE),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(1, 0),
+ SetDataTip(STR_NULL, STR_PROGSIG_COND_COMPARATOR_TOOLTIP), SetResize(1, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_GOTO_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0),
+ SetDataTip(STR_PROGSIG_GOTO_SIGNAL, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP), SetResize(1, 0),
+ EndContainer(),
+ NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_RIGHT),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(1, 0),
+ SetDataTip(STR_BLACK_COMMA, STR_PROGSIG_COND_VALUE_TOOLTIP), SetResize(1, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_SET_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0),
+ SetDataTip(STR_PROGSIG_COND_SET_SIGNAL, STR_PROGSIG_COND_SET_SIGNAL_TOOLTIP), SetResize(1, 0),
+ EndContainer(),
+ EndContainer(),
+ NWidget(WWT_IMGBTN, COLOUR_GREY, PROGRAM_WIDGET_GOTO_SIGNAL), SetMinimalSize(12, 12), SetDataTip(SPR_ARROW_RIGHT, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP),
+ EndContainer(),
+
+ /* Second button row. */
+ NWidget(NWID_HORIZONTAL),
+ NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_INSERT), SetMinimalSize(124, 12), SetFill(1, 0),
+ SetDataTip(STR_PROGSIG_INSERT, STR_PROGSIG_INSERT_TOOLTIP), SetResize(1, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE), SetMinimalSize(186, 12), SetFill(1, 0),
+ SetDataTip(STR_PROGSIG_REMOVE, STR_PROGSIG_REMOVE_TOOLTIP), SetResize(1, 0),
+ EndContainer(),
+ EndContainer(),
+
+ /* Third button row*/
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_REMOVE_PROGRAM, STR_PROGSIG_REMOVE_PROGRAM_TOOLTIP), SetResize(1, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COPY_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_COPY_PROGRAM, STR_PROGSIG_COPY_PROGRAM_TOOLTIP), SetResize(1, 0),
+ NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+ EndContainer(),
+};
+
+static WindowDesc _program_desc(
+ WDP_AUTO, "signal_program", 384, 100,
+ WC_SIGNAL_PROGRAM, WC_BUILD_SIGNAL,
+ WDF_CONSTRUCTION,
+ _nested_program_widgets, lengthof(_nested_program_widgets)
+);
+
+void ShowSignalProgramWindow(SignalReference ref)
+{
+ uint32 window_id = (ref.tile << 3) | ref.track;
+ if (BringWindowToFrontById(WC_SIGNAL_PROGRAM, window_id) != NULL) return;
+
+ new ProgramWindow(&_program_desc, ref);
+}
Index: src/rail_cmd.cpp
===================================================================
--- src/rail_cmd.cpp (revision 27386)
+++ src/rail_cmd.cpp (working copy)
@@ -33,6 +33,8 @@
#include "strings_func.h"
#include "company_gui.h"
#include "object_map.h"
+#include "tracerestrict.h"
+#include "programmable_signals.h"
#include "table/strings.h"
#include "table/railtypes.h"
@@ -93,10 +95,12 @@
/* Array of default GUI signal sprite numbers. */
const SpriteID _signal_lookup[2][SIGTYPE_END] = {
{SPR_IMG_SIGNAL_ELECTRIC_NORM, SPR_IMG_SIGNAL_ELECTRIC_ENTRY, SPR_IMG_SIGNAL_ELECTRIC_EXIT,
- SPR_IMG_SIGNAL_ELECTRIC_COMBO, SPR_IMG_SIGNAL_ELECTRIC_PBS, SPR_IMG_SIGNAL_ELECTRIC_PBS_OWAY},
+ SPR_IMG_SIGNAL_ELECTRIC_COMBO, SPR_IMG_SIGNAL_ELECTRIC_PBS, SPR_IMG_SIGNAL_ELECTRIC_PBS_OWAY,
+ SPR_IMG_SIGNAL_ELECTRIC_PROG},
{SPR_IMG_SIGNAL_SEMAPHORE_NORM, SPR_IMG_SIGNAL_SEMAPHORE_ENTRY, SPR_IMG_SIGNAL_SEMAPHORE_EXIT,
- SPR_IMG_SIGNAL_SEMAPHORE_COMBO, SPR_IMG_SIGNAL_SEMAPHORE_PBS, SPR_IMG_SIGNAL_SEMAPHORE_PBS_OWAY},
+ SPR_IMG_SIGNAL_SEMAPHORE_COMBO, SPR_IMG_SIGNAL_SEMAPHORE_PBS, SPR_IMG_SIGNAL_SEMAPHORE_PBS_OWAY,
+ SPR_IMG_SIGNAL_SEMAPHORE_PROG},
};
for (SignalType type = SIGTYPE_NORMAL; type < SIGTYPE_END; type = (SignalType)(type + 1)) {
@@ -642,6 +646,7 @@
/* Charge extra to remove signals on the track, if they are there */
if (HasSignalOnTrack(tile, track)) {
+ CheckRemoveSignal(tile, track);
cost.AddCost(DoCommand(tile, track, 0, flags, CMD_REMOVE_SIGNALS));
}
@@ -991,8 +996,7 @@
* - p1 = (bit 4) - 0 = signals, 1 = semaphores
* - p1 = (bit 5-7) - type of the signal, for valid values see enum SignalType in rail_map.h
* - p1 = (bit 8) - convert the present signal type and variant
- * - p1 = (bit 9-11)- start cycle from this signal type
- * - p1 = (bit 12-14)-wrap around after this signal type
+ * - p1 = (bit 9-14)- cycle through which signal set?
* - p1 = (bit 15-16)-cycle the signal direction this many times
* - p1 = (bit 17) - 1 = don't modify an existing signal but don't fail either, 0 = always set new signal type
* @param p2 used for CmdBuildManySignals() to copy direction of first signal
@@ -1007,16 +1011,16 @@
SignalVariant sigvar = (ctrl_pressed ^ HasBit(p1, 4)) ? SIG_SEMAPHORE : SIG_ELECTRIC; // the signal variant of the new signal
SignalType sigtype = Extract(p1); // the signal type of the new signal
bool convert_signal = HasBit(p1, 8); // convert button pressed
- SignalType cycle_start = Extract(p1);
- SignalType cycle_stop = Extract(p1);
uint num_dir_cycle = GB(p1, 15, 2);
- if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
- if (cycle_start > cycle_stop || cycle_stop > SIGTYPE_LAST) return CMD_ERROR;
+ uint which_signals = GB(p1, 9, 6);
- /* 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 */
@@ -1025,6 +1029,53 @@
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 (no overlap) */
if (TracksOverlap(GetTrackBits(tile))) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK);
@@ -1034,7 +1085,6 @@
/* 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]);
@@ -1098,6 +1148,8 @@
sigtype = GetSignalType(tile, track);
} else {
/* convert the present signal to the chosen type and variant */
+ if (IsPresignalProgrammable(tile, track))
+ FreeSignalProgram(SignalReference(tile, track));
SetSignalType(tile, track, sigtype);
SetSignalVariant(tile, track, sigvar);
if (IsPbsSignal(sigtype) && (GetPresentSignals(tile) & SignalOnTrack(track)) == SignalOnTrack(track)) {
@@ -1106,10 +1158,12 @@
}
} else if (ctrl_pressed) {
- /* cycle between cycle_start and cycle_end */
- sigtype = (SignalType)(GetSignalType(tile, track) + 1);
+ /* cycle through signal types */
+ sigtype = (SignalType)(GetSignalType(tile, track));
+ if(IsProgrammableSignal(sigtype))
+ FreeSignalProgram(SignalReference(tile, track));
- if (sigtype < cycle_start || sigtype > cycle_stop) sigtype = cycle_start;
+ sigtype = NextSignalType(sigtype, which_signals);
SetSignalType(tile, track, sigtype);
if (IsPbsSignal(sigtype) && (GetPresentSignals(tile) & SignalOnTrack(track)) == SignalOnTrack(track)) {
@@ -1127,6 +1181,8 @@
* direction of the first signal given as parameter by CmdBuildManySignals */
SetPresentSignals(tile, (GetPresentSignals(tile) & ~SignalOnTrack(track)) | (p2 & SignalOnTrack(track)));
SetSignalVariant(tile, track, sigvar);
+ if (IsPresignalProgrammable(tile, track))
+ FreeSignalProgram(SignalReference(tile, track));
SetSignalType(tile, track, sigtype);
}
@@ -1139,7 +1195,7 @@
uint mask = GetPresentSignals(tile) & SignalOnTrack(track);
SetSignalStates(tile, (GetSignalStates(tile) & ~mask) | ((HasBit(GetRailReservationTrackBits(tile), track) && EnsureNoVehicleOnGround(tile).Succeeded() ? UINT_MAX : 0) & mask));
}
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
AddTrackToSignalBuffer(tile, track, _current_company);
YapfNotifyTrackLayoutChange(tile, track);
if (v != NULL) {
@@ -1192,6 +1248,7 @@
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;
@@ -1259,7 +1316,7 @@
/* Must start on a valid track to be able to avoid loops */
if (!HasTrack(tile, track)) return CMD_ERROR;
- SignalType sigtype = (SignalType)GB(p2, 7, 3);
+ SignalType sigtype = Extract(p2);
if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
byte signals;
@@ -1303,7 +1360,8 @@
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);
@@ -1342,7 +1400,14 @@
/* Be user-friendly and try placing signals as much as possible */
if (ret.Succeeded()) {
had_success = true;
- total_cost.AddCost(ret);
+ 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 {
@@ -1416,12 +1481,26 @@
CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
Track track = Extract(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 */
@@ -1432,6 +1511,20 @@
/* 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);
@@ -1449,9 +1542,11 @@
}
}
Company::Get(GetTileOwner(tile))->infrastructure.signal -= CountBits(GetPresentSignals(tile));
+ CheckRemoveSignal(tile, track);
SetPresentSignals(tile, GetPresentSignals(tile) & ~SignalOnTrack(track));
Company::Get(GetTileOwner(tile))->infrastructure.signal += CountBits(GetPresentSignals(tile));
DirtyCompanyInfrastructureWindows(GetTileOwner(tile));
+ TraceRestrictNotifySignalRemoval(tile, track);
/* removed last signal from tile? */
if (GetPresentSignals(tile) == 0) {
@@ -1464,10 +1559,10 @@
YapfNotifyTrackLayoutChange(tile, track);
if (v != NULL) TryPathReserve(v, false);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
- return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_SIGNALS]);
+ return CommandCost(EXPENSES_CONSTRUCTION, cost);
}
/**
@@ -1607,7 +1702,7 @@
}
SetRailType(tile, totype);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
/* update power of train on this tile */
FindVehicleOnPos(tile, &affected_trains, &UpdateTrainPowerProc);
}
@@ -1691,10 +1786,10 @@
YapfNotifyTrackLayoutChange(endtile, track);
if (IsBridge(tile)) {
- MarkBridgeDirty(tile);
+ MarkBridgeDirty(tile, ZOOM_LVL_DRAW_MAP);
} else {
- MarkTileDirtyByTile(tile);
- MarkTileDirtyByTile(endtile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
+ MarkTileDirtyByTile(endtile, ZOOM_LVL_DRAW_MAP);
}
}
@@ -1780,6 +1875,9 @@
switch (GetRailTileType(tile)) {
case RAIL_TILE_SIGNALS:
+ if (flags & DC_EXEC) CheckRemoveSignalsFromTile(tile);
+ // FALL THROUGH
+
case RAIL_TILE_NORMAL: {
Slope tileh = GetTileSlope(tile);
/* Is there flat water on the lower halftile that gets cleared expensively? */
@@ -1865,9 +1963,14 @@
} else {
/* Normal electric signals are stored in a different sprite block than all other signals. */
sprite = (type == SIGTYPE_NORMAL && variant == SIG_ELECTRIC) ? SPR_ORIGINAL_SIGNALS_BASE : SPR_SIGNALS_BASE - 16;
- sprite += type * 16 + variant * 64 + image * 2 + condition + (type > SIGTYPE_LAST_NOPBS ? 64 : 0);
+ sprite += type * 16 + variant * 64 + image * 2 + condition + (IsSignalSpritePBS(type) ? 64 : 0);
}
+ if (type == SIGTYPE_PROG && variant == SIG_SEMAPHORE) {
+ sprite = SPR_PROGSIGNAL_BASE + image * 2 + condition;
+ } else if (type == SIGTYPE_PROG && variant == SIG_ELECTRIC) {
+ sprite = SPR_PROGSIGNAL_BASE + 16 + image * 2 + condition;
+ }
AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, GetSaveSlopeZ(x, y, track));
}
@@ -2707,14 +2810,15 @@
break;
case RAIL_TILE_SIGNALS: {
- static const StringID signal_type[6][6] = {
+ static const StringID signal_type[7][7] = {
{
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_SIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PRESIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_EXITSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_COMBOSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PBSSIGNALS,
- STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_NOENTRYSIGNALS
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_NOENTRYSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PROGSIGNALS
},
{
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PRESIGNALS,
@@ -2722,7 +2826,8 @@
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_EXITSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_COMBOSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_PBSSIGNALS,
- STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_NOENTRYSIGNALS
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_NOENTRYSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_PROGSIGNALS
},
{
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_EXITSIGNALS,
@@ -2730,7 +2835,8 @@
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXITSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_COMBOSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PBSSIGNALS,
- STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PROGSIGNALS
},
{
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_COMBOSIGNALS,
@@ -2738,7 +2844,8 @@
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_COMBOSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBOSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PBSSIGNALS,
- STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PROGSIGNALS
},
{
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PBSSIGNALS,
@@ -2746,7 +2853,8 @@
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PBSSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PBSSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBSSIGNALS,
- STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_PROGSIGNALS
},
{
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_NOENTRYSIGNALS,
@@ -2754,7 +2862,17 @@
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS,
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS,
- STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRYSIGNALS
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRYSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRY_PROGSIGNALS
+ },
+ {
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PROGSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_PROGSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PROGSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PROGSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_PROGSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRY_PROGSIGNALS,
+ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PROGSIGNALS
}
};
@@ -2768,6 +2886,12 @@
}
td->str = signal_type[secondary_signal][primary_signal];
+
+ if (IsRestrictedSignal(tile)) {
+ SetDParamX(td->dparam, 0, td->str);
+ SetDParamX(td->dparam, 1, rti->strings.name);
+ td->str = STR_LAI_RAIL_DESCRIPTION_RESTRICTED_SIGNAL;
+ }
break;
}
Index: src/rail_gui.cpp
===================================================================
--- src/rail_gui.cpp (revision 27386)
+++ src/rail_gui.cpp (working copy)
@@ -34,6 +34,8 @@
#include "vehicle_func.h"
#include "zoom_func.h"
#include "rail_gui.h"
+#include "tracerestrict.h"
+#include "programmable_signals.h"
#include "station_map.h"
#include "tunnelbridge_map.h"
@@ -49,8 +51,11 @@
static byte _waypoint_count = 1; ///< Number of waypoint types
static byte _cur_waypoint_type; ///< Currently selected waypoint type
static bool _convert_signal_button; ///< convert signal button in the signal GUI pressed
+static bool _trace_restrict_button; ///< trace restrict button in the signal GUI pressed
+static bool _program_signal_button; ///< program signal button in the signal GUI pressed
static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI)
static SignalType _cur_signal_type; ///< set the signal type (for signal GUI)
+static uint _cur_signal_button; ///< set the signal button (for signal GUI)
/* Map the setting: default_signal_type to the corresponding signal type */
static const SignalType _default_signal_type[] = {SIGTYPE_NORMAL, SIGTYPE_PBS, SIGTYPE_PBS_ONEWAY};
@@ -198,7 +203,7 @@
int h = _settings_client.gui.station_platlength;
if (!_railstation.orientation) Swap(w, h);
- CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, "" };
+ CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, 0, "" };
ShowSelectStationIfNeeded(cmdcont, TileArea(tile, w, h));
}
}
@@ -224,34 +229,51 @@
if (_remove_button_clicked) {
DoCommandP(tile, track, 0, CMD_REMOVE_SIGNALS | CMD_MSG(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM), CcPlaySound1E);
- } else {
- const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
-
- /* Map the setting cycle_signal_types to the lower and upper allowed signal type. */
- static const uint cycle_bounds[] = {SIGTYPE_NORMAL | (SIGTYPE_LAST_NOPBS << 3), SIGTYPE_PBS | (SIGTYPE_LAST << 3), SIGTYPE_NORMAL | (SIGTYPE_LAST << 3)};
+ return;
+ }
- /* various bitstuffed elements for CmdBuildSingleSignal() */
- uint32 p1 = track;
+ if (_trace_restrict_button) {
+ if (IsPlainRailTile(tile) && HasTrack(tile, track) && HasSignalOnTrack(tile, track)) {
+ ShowTraceRestrictProgramWindow(tile, track);
+ }
+ return;
+ }
- if (w != NULL) {
- /* signal GUI is used */
- SB(p1, 3, 1, _ctrl_pressed);
- SB(p1, 4, 1, _cur_signal_variant);
- SB(p1, 5, 3, _cur_signal_type);
- SB(p1, 8, 1, _convert_signal_button);
- SB(p1, 9, 6, cycle_bounds[_settings_client.gui.cycle_signal_types]);
- } else {
- SB(p1, 3, 1, _ctrl_pressed);
- SB(p1, 4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
- SB(p1, 5, 3, _default_signal_type[_settings_client.gui.default_signal_type]);
- SB(p1, 8, 1, 0);
- SB(p1, 9, 6, cycle_bounds[_settings_client.gui.cycle_signal_types]);
+ if (_program_signal_button) {
+ if (IsPlainRailTile(tile) && HasTrack(tile, track) && HasSignalOnTrack(tile,track) && IsPresignalProgrammable(tile, track)) {
+ // Show program gui if there is a programmable signal
+ ShowSignalProgramWindow(SignalReference(tile, track));
+ return;
}
- DoCommandP(tile, p1, 0, CMD_BUILD_SIGNALS |
- CMD_MSG((w != NULL && _convert_signal_button) ? STR_ERROR_SIGNAL_CAN_T_CONVERT_SIGNALS_HERE : STR_ERROR_CAN_T_BUILD_SIGNALS_HERE),
- CcPlaySound1E);
+ // Don't display error here even though program-button is pressed and there is no programmable signal,
+ // instead just handle it normally. That way player can keep the program-button pressed all the time
+ // to build slightly faster.
+ }
+
+ const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
+
+ /* various bitstuffed elements for CmdBuildSingleSignal() */
+ uint32 p1 = track;
+
+ if (w != NULL) {
+ /* signal GUI is used */
+ SB(p1, 3, 1, _ctrl_pressed);
+ SB(p1, 4, 1, _cur_signal_variant);
+ SB(p1, 5, 3, _cur_signal_type);
+ SB(p1, 8, 1, _convert_signal_button);
+ SB(p1, 9, 6, _settings_client.gui.cycle_signal_types);
+ } else {
+ SB(p1, 3, 1, _ctrl_pressed);
+ SB(p1, 4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
+ SB(p1, 5, 3, _default_signal_type[_settings_client.gui.default_signal_type]);
+ SB(p1, 8, 1, 0);
+ SB(p1, 9, 6, _settings_client.gui.cycle_signal_types);
}
+
+ DoCommandP(tile, p1, 0, CMD_BUILD_SIGNALS |
+ CMD_MSG((w != NULL && _convert_signal_button) ? STR_ERROR_SIGNAL_CAN_T_CONVERT_SIGNALS_HERE : STR_ERROR_CAN_T_BUILD_SIGNALS_HERE),
+ CcPlaySound1E);
}
/**
@@ -731,7 +753,7 @@
uint32 p1 = _cur_railtype | (select_method == VPM_FIX_X ? AXIS_X : AXIS_Y) << 4 | ta.w << 8 | ta.h << 16 | _ctrl_pressed << 24;
uint32 p2 = STAT_CLASS_WAYP | _cur_waypoint_type << 8 | INVALID_STATION << 16;
- CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT), CcPlaySound1E, "" };
+ CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT), CcPlaySound1E, 0, "" };
ShowSelectWaypointIfNeeded(cmdcont, ta);
}
}
@@ -888,7 +910,7 @@
uint32 p1 = _cur_railtype | _railstation.orientation << 4 | numtracks << 8 | platlength << 16 | _ctrl_pressed << 24;
uint32 p2 = _railstation.station_class | _railstation.station_type << 8 | INVALID_STATION << 16;
- CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, "" };
+ CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, 0, "" };
ShowSelectStationIfNeeded(cmdcont, ta);
}
@@ -1518,6 +1540,8 @@
~BuildSignalWindow()
{
_convert_signal_button = false;
+ _trace_restrict_button = false;
+ _program_signal_button = false;
}
virtual void OnInit()
@@ -1564,7 +1588,7 @@
{
if (IsInsideMM(widget, WID_BS_SEMAPHORE_NORM, WID_BS_ELECTRIC_PBS_OWAY + 1)) {
/* Extract signal from widget number. */
- int type = (widget - WID_BS_SEMAPHORE_NORM) % SIGTYPE_END;
+ SignalType type = TypeForClick((widget - WID_BS_SEMAPHORE_NORM) % SIGTYPE_END);
int var = SIG_SEMAPHORE - (widget - WID_BS_SEMAPHORE_NORM) / SIGTYPE_END; // SignalVariant order is reversed compared to the widgets.
SpriteID sprite = GetRailTypeInfo(_cur_railtype)->gui_sprites.signals[type][var][this->IsWidgetLowered(widget)];
@@ -1572,6 +1596,22 @@
}
}
+ inline SignalType TypeForClick(uint id) const
+ {
+ switch(id) {
+ case 0: return SIGTYPE_NORMAL;
+ case 1: return SIGTYPE_ENTRY;
+ case 2: return SIGTYPE_EXIT;
+ case 3: return SIGTYPE_COMBO;
+ case 4: return SIGTYPE_PROG;
+ case 5: return SIGTYPE_PBS;
+ case 6: return SIGTYPE_PBS_ONEWAY;
+ default:
+ assert(!"Bad signal type button ID");
+ return SIGTYPE_NORMAL;
+ }
+ }
+
virtual void OnClick(Point pt, int widget, int click_count)
{
switch (widget) {
@@ -1579,17 +1619,20 @@
case WID_BS_SEMAPHORE_ENTRY:
case WID_BS_SEMAPHORE_EXIT:
case WID_BS_SEMAPHORE_COMBO:
+ case WID_BS_SEMAPHORE_PROG:
case WID_BS_SEMAPHORE_PBS:
case WID_BS_SEMAPHORE_PBS_OWAY:
case WID_BS_ELECTRIC_NORM:
case WID_BS_ELECTRIC_ENTRY:
case WID_BS_ELECTRIC_EXIT:
case WID_BS_ELECTRIC_COMBO:
+ case WID_BS_ELECTRIC_PROG:
case WID_BS_ELECTRIC_PBS:
case WID_BS_ELECTRIC_PBS_OWAY:
- this->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
+ this->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_button);
- _cur_signal_type = (SignalType)((uint)((widget - WID_BS_SEMAPHORE_NORM) % (SIGTYPE_LAST + 1)));
+ _cur_signal_button = (uint)((widget - WID_BS_SEMAPHORE_NORM) % (SIGTYPE_END));
+ _cur_signal_type = TypeForClick(_cur_signal_button);
_cur_signal_variant = widget >= WID_BS_ELECTRIC_NORM ? SIG_ELECTRIC : SIG_SEMAPHORE;
/* If 'remove' button of rail build toolbar is active, disable it. */
@@ -1597,11 +1640,30 @@
Window *w = FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL);
if (w != NULL) ToggleRailButton_Remove(w);
}
-
break;
case WID_BS_CONVERT:
_convert_signal_button = !_convert_signal_button;
+ if (_convert_signal_button) {
+ _trace_restrict_button = false;
+ _program_signal_button = false;
+ }
+ break;
+
+ case WID_BS_TRACE_RESTRICT:
+ _trace_restrict_button = !_trace_restrict_button;
+ if (_trace_restrict_button) {
+ _convert_signal_button = false;
+ _program_signal_button = false;
+ }
+ break;
+
+ case WID_BS_PROGRAM:
+ _program_signal_button = !_program_signal_button;
+ if(_program_signal_button) {
+ _trace_restrict_button = false;
+ _convert_signal_button = false;
+ }
break;
case WID_BS_DRAG_SIGNALS_DENSITY_DECREASE:
@@ -1632,9 +1694,11 @@
virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
{
if (!gui_scope) return;
- this->LowerWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
+ this->LowerWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_button);
this->SetWidgetLoweredState(WID_BS_CONVERT, _convert_signal_button);
+ this->SetWidgetLoweredState(WID_BS_TRACE_RESTRICT, _trace_restrict_button);
+ this->SetWidgetLoweredState(WID_BS_PROGRAM, _program_signal_button);
this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, _settings_client.gui.drag_signals_density == 1);
this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, _settings_client.gui.drag_signals_density == 20);
@@ -1653,15 +1717,18 @@
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PROG), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PROG_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_CONVERT), SetDataTip(SPR_IMG_SIGNAL_CONVERT, STR_BUILD_SIGNAL_CONVERT_TOOLTIP), SetFill(1, 1),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_TRACE_RESTRICT), SetDataTip(SPR_IMG_SETTINGS, STR_TRACE_RESTRICT_SIGNAL_GUI_TOOLTIP), SetFill(1, 1),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PROG), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PROG_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(1, 1),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(1, 1),
@@ -1674,6 +1741,7 @@
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetFill(1, 0),
EndContainer(),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_PROGRAM), SetDataTip(SPR_IMG_SETTINGS, STR_PROGRAM_SIGNAL_TOOLTIP), SetFill(1, 1),
EndContainer(),
EndContainer(),
};
@@ -1957,7 +2025,7 @@
Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
if (w != NULL) {
w->SetDirty();
- w->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
+ w->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_button);
}
_cur_signal_variant = new_variant;
}
@@ -1974,7 +2042,13 @@
SetDefaultRailGui();
_convert_signal_button = false;
- _cur_signal_type = _default_signal_type[_settings_client.gui.default_signal_type];
+ _trace_restrict_button = false;
+ _program_signal_button = false;
+ _cur_signal_type = _default_signal_type[_settings_client.gui.default_signal_type];
+ _cur_signal_button =
+ _cur_signal_type == SIGTYPE_PROG ? 4 :
+ _cur_signal_type == SIGTYPE_PBS ? 5 :
+ _cur_signal_type == SIGTYPE_PBS_ONEWAY ? 6 : _cur_signal_type;
ResetSignalVariant();
}
Index: src/rail_map.h
===================================================================
--- src/rail_map.h (revision 27386)
+++ src/rail_map.h (working copy)
@@ -285,12 +285,6 @@
return HasDepotReservation(t) ? TrackToTrackBits(GetRailDepotTrack(t)) : TRACK_BIT_NONE;
}
-
-static inline bool IsPbsSignal(SignalType s)
-{
- return s == SIGTYPE_PBS || s == SIGTYPE_PBS_ONEWAY;
-}
-
static inline SignalType GetSignalType(TileIndex t, Track track)
{
assert(GetRailTileType(t) == RAIL_TILE_SIGNALS);
@@ -308,12 +302,22 @@
static inline bool IsPresignalEntry(TileIndex t, Track track)
{
- return GetSignalType(t, track) == SIGTYPE_ENTRY || GetSignalType(t, track) == SIGTYPE_COMBO;
+ return IsEntrySignal(GetSignalType(t, track));
}
static inline bool IsPresignalExit(TileIndex t, Track track)
{
- return GetSignalType(t, track) == SIGTYPE_EXIT || GetSignalType(t, track) == SIGTYPE_COMBO;
+ return IsExitSignal(GetSignalType(t, track));
+}
+
+static inline bool IsPresignalCombo(TileIndex t, Track track)
+{
+ return IsComboSignal(GetSignalType(t, track));
+}
+
+static inline bool IsPresignalProgrammable(TileIndex t, Track track)
+{
+ return IsProgrammableSignal(GetSignalType(t, track));
}
/** One-way signals can't be passed the 'wrong' way. */
@@ -479,6 +483,26 @@
!HasSignalOnTrackdir(tile, td) && IsOnewaySignal(tile, TrackdirToTrack(td));
}
+/**
+ * Does signal tile have "one or more trace restrict mappings present" bit set
+ * @param tile the tile to check
+ */
+static inline bool IsRestrictedSignal(TileIndex tile)
+{
+ assert(GetRailTileType(tile) == RAIL_TILE_SIGNALS);
+ return (bool) GB(_m[tile].m2, 12, 1);
+}
+
+/**
+ * Set signal tile "one or more trace restrict mappings present" bit
+ * @param tile the tile to set
+ */
+static inline void SetRestrictedSignal(TileIndex tile, bool is_restricted)
+{
+ assert(GetRailTileType(tile) == RAIL_TILE_SIGNALS);
+ SB(_m[tile].m2, 12, 1, is_restricted);
+}
+
RailType GetTileRailType(TileIndex tile);
Index: src/road_cmd.cpp
===================================================================
--- src/road_cmd.cpp (revision 27386)
+++ src/road_cmd.cpp (working copy)
@@ -1534,7 +1534,7 @@
TileY(tile) * TILE_SIZE + 7,
0,
EV_BULLDOZER);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
return;
}
}
@@ -1559,7 +1559,7 @@
cur_rs = ROADSIDE_BARREN;
}
SetRoadside(tile, cur_rs);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
} else if (IncreaseRoadWorksCounter(tile)) {
TerminateRoadWorks(tile);
@@ -1574,7 +1574,7 @@
}
}
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
@@ -1638,6 +1638,14 @@
trackdirbits = TrackBitsToTrackdirBits(AxisToTrackBits(axis));
if (IsCrossingBarred(tile)) red_signals = trackdirbits;
+ if (IsLevelCrossingTile(TileAddByDiagDir(tile, AxisToDiagDir(axis))) &&
+ IsCrossingBarred(TileAddByDiagDir(tile, AxisToDiagDir(axis)))) {
+ red_signals &= (TrackdirBits)0x0102; // magic value. I think TRACKBIT_X_SW and TRACKBIT_X_NE should be swapped
+ }
+ if (IsLevelCrossingTile(TileAddByDiagDir(tile, ReverseDiagDir(AxisToDiagDir(axis)))) &&
+ IsCrossingBarred(TileAddByDiagDir(tile, ReverseDiagDir(AxisToDiagDir(axis))))) {
+ red_signals &= (TrackdirBits)0x0201; // inverse of above magic value
+ }
break;
}
Index: src/road_gui.cpp
===================================================================
--- src/road_gui.cpp (revision 27386)
+++ src/road_gui.cpp (working copy)
@@ -233,7 +233,7 @@
p2 |= ddir << 6; // Set the DiagDirecion into p2 bits 6 and 7.
TileArea ta(start_tile, end_tile);
- CommandContainer cmdcont = { ta.tile, (uint32)(ta.w | ta.h << 8), p2, cmd, CcRoadStop, "" };
+ CommandContainer cmdcont = { ta.tile, (uint32)(ta.w | ta.h << 8), p2, cmd, CcRoadStop, 0, "" };
ShowSelectStationIfNeeded(cmdcont, ta);
}
Index: src/roadveh.h
===================================================================
--- src/roadveh.h (revision 27386)
+++ src/roadveh.h (working copy)
@@ -163,6 +163,11 @@
if (!this->IsArticulatedPart()) {
/* Road vehicle weight is in units of 1/4 t. */
weight += GetVehicleProperty(this, PROP_ROADVEH_WEIGHT, RoadVehInfo(this->engine_type)->weight) / 4;
+
+ /*
+ * TODO: DIRTY HACK: at least 1 for realistic accelerate
+ */
+ if (weight == 0) weight = 1;
}
return weight;
Index: src/roadveh_cmd.cpp
===================================================================
--- src/roadveh_cmd.cpp (revision 27386)
+++ src/roadveh_cmd.cpp (working copy)
@@ -33,6 +33,7 @@
#include "core/random_func.hpp"
#include "company_base.h"
#include "core/backup_type.hpp"
+#include "infrastructure_func.h"
#include "newgrf.h"
#include "zoom_func.h"
@@ -298,6 +299,7 @@
v->reliability = e->reliability;
v->reliability_spd_dec = e->reliability_spd_dec;
+ v->breakdown_chance = 128;
v->max_age = e->GetLifeLengthInDays();
_new_vehicle_id = v->index;
@@ -378,12 +380,11 @@
if (!v->IsPrimaryVehicle()) return CMD_ERROR;
- CommandCost ret = CheckOwnership(v->owner);
+ CommandCost ret = CheckVehicleControlAllowed(v);
if (ret.Failed()) return ret;
if ((v->vehstatus & VS_STOPPED) ||
(v->vehstatus & VS_CRASHED) ||
- v->breakdown_ctr != 0 ||
v->overtaking != 0 ||
v->state == RVSB_WORMHOLE ||
v->IsInDepot() ||
@@ -817,6 +818,9 @@
/* For now, articulated road vehicles can't overtake anything. */
if (v->HasArticulatedPart()) return;
+ /* Don't overtake if the vehicle is broken or about to break down */
+ if (v->breakdown_ctr != 0) return;
+
/* Vehicles are not driving in same direction || direction is not a diagonal direction */
if (v->direction != u->direction || !(v->direction & 1)) return;
@@ -895,14 +899,14 @@
TrackdirBits trackdirs = TrackStatusToTrackdirBits(ts);
if (IsTileType(tile, MP_ROAD)) {
- if (IsRoadDepot(tile) && (!IsTileOwner(tile, v->owner) || GetRoadDepotDirection(tile) == enterdir || (GetRoadTypes(tile) & v->compatible_roadtypes) == 0)) {
+ if (IsRoadDepot(tile) && (!IsInfraTileUsageAllowed(VEH_ROAD, v->owner, tile) || GetRoadDepotDirection(tile) == enterdir || (GetRoadTypes(tile) & v->compatible_roadtypes) == 0)) {
/* Road depot owned by another company or with the wrong orientation */
trackdirs = TRACKDIR_BIT_NONE;
}
} else if (IsTileType(tile, MP_STATION) && IsStandardRoadStopTile(tile)) {
/* Standard road stop (drive-through stops are treated as normal road) */
- if (!IsTileOwner(tile, v->owner) || GetRoadStopDir(tile) == enterdir || v->HasArticulatedPart()) {
+ if (!IsInfraTileUsageAllowed(VEH_ROAD, v->owner, tile) || GetRoadStopDir(tile) == enterdir || v->HasArticulatedPart()) {
/* different station owner or wrong orientation or the vehicle has articulated parts */
trackdirs = TRACKDIR_BIT_NONE;
} else {
@@ -1160,7 +1164,7 @@
v->x_pos = gp.x;
v->y_pos = gp.y;
v->UpdatePosition();
- if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true);
+ if (v->IsDrawn()) v->Vehicle::UpdateViewport(true);
return true;
}
@@ -1409,7 +1413,7 @@
/* In case an RV is stopped in a road stop, why not try to load? */
if (v->cur_speed == 0 && IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) &&
v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile)) &&
- v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) &&
+ IsInfraTileUsageAllowed(VEH_ROAD, v->owner, 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;
@@ -1442,7 +1446,7 @@
_road_stop_stop_frame[v->state - RVSB_IN_ROAD_STOP + (_settings_game.vehicle.road_side << RVS_DRIVE_SIDE)] == v->frame) ||
(IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) &&
v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile)) &&
- v->owner == GetTileOwner(v->tile) &&
+ IsInfraTileUsageAllowed(VEH_ROAD, v->owner, v->tile) &&
GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK) &&
v->frame == RVC_DRIVE_THROUGH_STOP_FRAME))) {
@@ -1537,6 +1541,9 @@
if (v->current_order.IsType(OT_LOADING)) return true;
+ v->HandleWaiting(false);
+ if (v->current_order.IsType(OT_WAITING)) return true;
+
if (v->IsInDepot() && RoadVehLeaveDepot(v, true)) return true;
v->ShowVisualEffect();
@@ -1568,7 +1575,7 @@
v->SetLastSpeed();
for (RoadVehicle *u = v; u != NULL; u = u->Next()) {
- if ((u->vehstatus & VS_HIDDEN) != 0) continue;
+ if (!(u->IsDrawn())) continue;
u->UpdateViewport(false, false);
}
Index: src/saveload/afterload.cpp
===================================================================
--- src/saveload/afterload.cpp (revision 27386)
+++ src/saveload/afterload.cpp (working copy)
@@ -50,6 +50,7 @@
#include "../engine_func.h"
#include "../rail_gui.h"
#include "../core/backup_type.hpp"
+#include "../core/mem_func.hpp"
#include "../smallmap_gui.h"
#include "../news_func.h"
#include "../order_backup.h"
@@ -2389,6 +2390,20 @@
FOR_ALL_DEPOTS(d) d->build_date = _date;
}
+ if (SlXvIsFeatureMissing(XSLFI_INFRA_SHARING)) {
+ Company *c;
+ FOR_ALL_COMPANIES(c) {
+ /* yearly_expenses has 3*15 entries now, saveload code gave us 3*13.
+ * Move the old data to the right place in the new array and clear the new data.
+ * The move has to be done in reverse order (first 2, then 1). */
+ MemMoveT(&c->yearly_expenses[2][0], &c->yearly_expenses[1][11], 13);
+ MemMoveT(&c->yearly_expenses[1][0], &c->yearly_expenses[0][13], 13);
+ /* Clear the old location of just-moved data, so sharing income/expenses is set to 0 */
+ MemSetT(&c->yearly_expenses[0][13], 0, 2);
+ MemSetT(&c->yearly_expenses[1][13], 0, 2);
+ }
+ }
+
/* In old versions it was possible to remove an airport while a plane was
* taking off or landing. This gives all kind of problems when building
* another airport in the same station so we don't allow that anymore.
@@ -2818,6 +2833,42 @@
}
}
+ /* Set some breakdown-related variables to the correct values. */
+ if (SlXvIsFeatureMissing(XSLFI_IMPROVED_BREAKDOWNS)) {
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ switch(v->type) {
+ case VEH_TRAIN: {
+ if (Train::From(v)->IsFrontEngine()) {
+ if (v->breakdown_ctr == 1) SetBit(Train::From(v)->flags, VRF_BREAKDOWN_STOPPED);
+ } else if (Train::From(v)->IsEngine() || Train::From(v)->IsMultiheaded()) {
+ /** Non-front engines could have a reliability of 0.
+ * Set it to the reliability of the front engine or the maximum, whichever is lower. */
+ const Engine *e = Engine::Get(v->engine_type);
+ v->reliability_spd_dec = e->reliability_spd_dec;
+ v->reliability = min(v->First()->reliability, e->reliability);
+ }
+ }
+ /* FALL THROUGH */
+ case VEH_ROAD:
+ v->breakdown_chance = 128;
+ break;
+
+ case VEH_SHIP:
+ v->breakdown_chance = 64;
+ break;
+
+ case VEH_AIRCRAFT:
+ v->breakdown_chance = Clamp(64 + (AircraftVehInfo(v->engine_type)->max_speed >> 3), 0, 255);
+ v->breakdown_severity = 40;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
/* The road owner of standard road stops was not properly accounted for. */
if (IsSavegameVersionBefore(172)) {
for (TileIndex t = 0; t < map_size; t++) {
@@ -2968,6 +3019,23 @@
#endif
}
+ if (SlXvIsFeaturePresent(XSLFI_TIMETABLES_START_TICKS) && WALLCLOCK_NETWORK_COMPATIBLE) {
+ // savegame timetable start is in ticks, but we want it in days, fix it up
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if (v->timetable_start != 0) {
+ v->timetable_start /= DAY_TICKS;
+ }
+ }
+ } else if (SlXvIsFeatureMissing(XSLFI_TIMETABLES_START_TICKS) && (!WALLCLOCK_NETWORK_COMPATIBLE)) {
+ // savegame timetable start is in days, but we want it in ticks, fix it up
+ Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if (v->timetable_start != 0) {
+ v->timetable_start *= DAY_TICKS;
+ }
+ }
+ }
/* Station acceptance is some kind of cache */
if (IsSavegameVersionBefore(127)) {
@@ -2988,6 +3056,8 @@
ResetSignalHandlers();
AfterLoadLinkGraphs();
+
+ AfterLoadTraceRestrict();
return true;
}
Index: src/saveload/company_sl.cpp
===================================================================
--- src/saveload/company_sl.cpp (revision 27386)
+++ src/saveload/company_sl.cpp (working copy)
@@ -283,7 +283,8 @@
/* yearly expenses was changed to 64-bit in savegame version 2. */
SLE_CONDARR(CompanyProperties, yearly_expenses, SLE_FILE_I32 | SLE_VAR_I64, 3 * 13, 0, 1),
- SLE_CONDARR(CompanyProperties, yearly_expenses, SLE_INT64, 3 * 13, 2, SL_MAX_VERSION),
+ SLE_CONDARR_X(CompanyProperties, yearly_expenses, SLE_INT64, 3 * 13, 2, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING, 0, 0)),
+ SLE_CONDARR_X(CompanyProperties, yearly_expenses, SLE_INT64, 3 * 15, 2, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)),
SLE_CONDVAR(CompanyProperties, is_ai, SLE_BOOL, 2, SL_MAX_VERSION),
SLE_CONDNULL(1, 107, 111), ///< is_noai
Index: src/saveload/extended_ver_sl.cpp
new file mode 100644
===================================================================
--- src/saveload/extended_ver_sl.cpp (revision 0)
+++ src/saveload/extended_ver_sl.cpp (working copy)
@@ -0,0 +1,360 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file extended_ver_sl.cpp Functions related to handling save/load extended version info.
+ *
+ * Known extended features are stored in _sl_xv_feature_versions, features which are currently enabled/in use and their versions are stored in the savegame.
+ * On load, the list of features and their versions are loaded from the savegame. If the savegame contains a feature which is either unknown, or has too high a version,
+ * loading can be either aborted, or the feature can be ignored if the the feature flags in the savegame indicate that it can be ignored. The savegame may also list any additional
+ * chunk IDs which are associated with an extended feature, these can be discarded if the feature is discarded.
+ * This information is stored in the SLXI chunk, the contents of which has the following format:
+ *
+ * uint32 chunk version
+ * uint32 chunk flags
+ * uint32 number of sub chunks/features
+ * For each of N sub chunk/feature:
+ * uint32 feature flags (SlxiSubChunkFlags)
+ * uint16 feature version
+ * SLE_STR feature name
+ * uint32* extra data length [only present iff feature flags & XSCF_EXTRA_DATA_PRESENT]
+ * N bytes extra data
+ * uint32* chunk ID list count [only present iff feature flags & XSCF_CHUNK_ID_LIST_PRESENT]
+ * N x uint32 chunk ID list
+ */
+
+#include "../stdafx.h"
+#include "../debug.h"
+#include "saveload.h"
+#include "extended_ver_sl.h"
+#include "../timetable.h"
+
+#include
+
+#include "../safeguards.h"
+
+uint16 _sl_xv_feature_versions[XSLFI_SIZE]; ///< array of all known feature types and their current versions
+bool _sl_is_ext_version; ///< is this an extended savegame version, with more info in the SLXI chunk?
+bool _sl_is_faked_ext; ///< is this a faked extended savegame version, with no SLXI chunk?
+std::vector _sl_xv_discardable_chunk_ids; ///< list of chunks IDs which we can discard if no chunk loader exists
+
+static const uint32 _sl_xv_slxi_chunk_version = 0; ///< current version os SLXI chunk
+
+const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
+ { XSLFI_TRACE_RESTRICT, XSCF_NULL, 2, 2, "tracerestrict", NULL, NULL, "TRRM,TRRP" },
+ { XSLFI_PROG_SIGS, XSCF_NULL, 1, 1, "programmable_signals", NULL, NULL, "SPRG" },
+ { XSLFI_ADJACENT_CROSSINGS, XSCF_NULL, 1, 1, "adjacent_crossings", NULL, NULL, NULL },
+ { XSLFI_DEPARTURE_BOARDS, XSCF_IGNORABLE_UNKNOWN, 1, 1, "departure_boards", NULL, NULL, NULL },
+ { XSLFI_TIMETABLES_START_TICKS, XSCF_NULL, WALLCLOCK_NETWORK_COMPATIBLE ? 0 : 1, 1, "timetable_start_ticks", NULL, NULL, NULL },
+ { XSLFI_TOWN_CARGO_ADJ, XSCF_IGNORABLE_UNKNOWN, 1, 1, "town_cargo_adj", NULL, NULL, NULL },
+ { XSLFI_SIG_TUNNEL_BRIDGE, XSCF_NULL, 1, 1, "signal_tunnel_bridge", NULL, NULL, NULL },
+ { XSLFI_IMPROVED_BREAKDOWNS, XSCF_NULL, 1, 1, "improved_breakdowns", NULL, NULL, NULL },
+ { XSLFI_TT_WAIT_IN_DEPOT, XSCF_NULL, 1, 1, "tt_wait_in_depot", NULL, NULL, NULL },
+ { XSLFI_AUTO_TIMETABLE, XSCF_NULL, 1, 1, "auto_timetables", NULL, NULL, NULL },
+ { XSLFI_VEHICLE_REPAIR_COST, XSCF_NULL, 1, 1, "vehicle_repair_cost", NULL, NULL, NULL },
+ { XSLFI_ENH_VIEWPORT_PLANS, XSCF_IGNORABLE_ALL, 1, 1, "enh_viewport_plans", NULL, NULL, "PLAN,PLLN" },
+ { XSLFI_INFRA_SHARING, XSCF_NULL, 1, 1, "infra_sharing", NULL, NULL, NULL },
+ { XSLFI_VARIABLE_DAY_LENGTH, XSCF_NULL, 1, 1, "variable_day_length", NULL, NULL, NULL },
+ { XSLFI_ORDER_OCCUPANCY, XSCF_NULL, 1, 1, "order_occupancy", NULL, NULL, NULL },
+ { XSLFI_NULL, XSCF_NULL, 0, 0, NULL, NULL, NULL, NULL },// This is the end marker
+};
+
+/**
+ * Extended save/load feature test
+ *
+ * First performs a tradional check on the provided @p savegame_version against @p savegame_version_from and @p savegame_version_to.
+ * Then, if the feature set in the constructor is not XSLFI_NULL, also check than the feature version is inclusively bounded by @p min_version and @p max_version,
+ * and return the combination of the two tests using the operator defined in the constructor.
+ * Otherwise just returns the result of the savegame version test
+ */
+bool SlXvFeatureTest::IsFeaturePresent(uint16 savegame_version, uint16 savegame_version_from, uint16 savegame_version_to) const
+{
+ bool savegame_version_ok = savegame_version >= savegame_version_from && savegame_version <= savegame_version_to;
+
+ if (this->feature == XSLFI_NULL) return savegame_version_ok;
+
+ bool feature_ok = SlXvIsFeaturePresent(this->feature, this->min_version, this->max_version);
+
+ switch (op) {
+ case XSLFTO_OR:
+ return savegame_version_ok || feature_ok;
+
+ case XSLFTO_AND:
+ return savegame_version_ok && feature_ok;
+
+ default:
+ NOT_REACHED();
+ return false;
+ }
+}
+
+/**
+ * Returns true if @p feature is present and has a version inclusively bounded by @p min_version and @p max_version
+ */
+bool SlXvIsFeaturePresent(SlXvFeatureIndex feature, uint16 min_version, uint16 max_version)
+{
+ assert(feature < XSLFI_SIZE);
+ return _sl_xv_feature_versions[feature] >= min_version && _sl_xv_feature_versions[feature] <= max_version;
+}
+
+/**
+ * Resets all extended feature versions to 0
+ */
+void SlXvResetState()
+{
+ _sl_is_ext_version = false;
+ _sl_is_faked_ext = false;
+ memset(_sl_xv_feature_versions, 0, sizeof(_sl_xv_feature_versions));
+}
+
+/**
+ * Resets all extended feature versions to their currently enabled versions, i.e. versions suitable for saving
+ */
+void SlXvSetCurrentState()
+{
+ SlXvResetState();
+ _sl_is_ext_version = true;
+
+ const SlxiSubChunkInfo *info = _sl_xv_sub_chunk_infos;
+ for (; info->index != XSLFI_NULL; ++info) {
+ _sl_xv_feature_versions[info->index] = info->save_version;
+ }
+}
+
+/**
+ * Check for "special" savegame versions (i.e. known patchpacks) and set correct savegame version, settings, etc.
+ */
+void SlXvCheckSpecialSavegameVersions()
+{
+ extern uint16 _sl_version;
+
+ if (_sl_version == 2000) {
+ DEBUG(sl, 1, "Loading a trace restrict patch savegame version %d as version 194", _sl_version);
+ _sl_version = 194;
+ _sl_is_faked_ext = true;
+ _sl_xv_feature_versions[XSLFI_TRACE_RESTRICT] = 1;
+ }
+}
+
+/**
+ * Return true if this chunk has been marked as discardable
+ */
+bool SlXvIsChunkDiscardable(uint32 id)
+{
+ for(size_t i = 0; i < _sl_xv_discardable_chunk_ids.size(); i++) {
+ if (_sl_xv_discardable_chunk_ids[i] == id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Writes a chunk ID list string to the savegame, returns the number of chunks written
+ * In dry run mode, only returns the number of chunk which would have been written
+ */
+static uint32 WriteChunkIdList(const char *chunk_list, bool dry_run)
+{
+ unsigned int chunk_count = 0; // number of chunks output
+ unsigned int id_offset = 0; // how far are we into the ID
+ for (; *chunk_list != 0; chunk_list++) {
+ if (id_offset == 4) {
+ assert(*chunk_list == ',');
+ id_offset = 0;
+ } else {
+ if (!dry_run) {
+ SlWriteByte(*chunk_list);
+ }
+ if (id_offset == 3) {
+ chunk_count++;
+ }
+ id_offset++;
+ }
+ }
+ assert(id_offset == 4);
+ return chunk_count;
+}
+
+static void Save_SLXI()
+{
+ SlXvSetCurrentState();
+
+ static const SaveLoad _xlsi_sub_chunk_desc[] = {
+ SLE_VAR(SlxiSubChunkInfo, save_version, SLE_UINT16),
+ SLE_STR(SlxiSubChunkInfo, name, SLE_STR, 0),
+ SLE_END()
+ };
+
+ // calculate lengths
+ uint32 item_count = 0;
+ uint32 length = 12;
+ std::vector extra_data_lengths;
+ std::vector chunk_counts;
+ extra_data_lengths.resize(XSLFI_SIZE);
+ chunk_counts.resize(XSLFI_SIZE);
+ const SlxiSubChunkInfo *info = _sl_xv_sub_chunk_infos;
+ for (; info->index != XSLFI_NULL; ++info) {
+ if (info->save_version > 0) {
+ item_count++;
+ length += 4;
+ length += SlCalcObjLength(info, _xlsi_sub_chunk_desc);
+ if (info->save_proc) {
+ uint32 extra_data_length = info->save_proc(info, true);
+ if (extra_data_length) {
+ extra_data_lengths[info->index] = extra_data_length;
+ length += 4 + extra_data_length;
+ }
+ }
+ if (info->chunk_list) {
+ uint32 chunk_count = WriteChunkIdList(info->chunk_list, true);
+ if (chunk_count) {
+ chunk_counts[info->index] = chunk_count;
+ length += 4 * (1 + chunk_count);
+ }
+ }
+ }
+ }
+
+ // write header
+ SlSetLength(length);
+ SlWriteUint32(_sl_xv_slxi_chunk_version); // chunk version
+ SlWriteUint32(0); // flags
+ SlWriteUint32(item_count); // item count
+
+ // write data
+ info = _sl_xv_sub_chunk_infos;
+ for (; info->index != XSLFI_NULL; ++info) {
+ if (info->save_version > 0) {
+ SlxiSubChunkFlags flags = info->flags;
+ assert(!(flags & (XSCF_EXTRA_DATA_PRESENT | XSCF_CHUNK_ID_LIST_PRESENT)));
+ uint32 extra_data_length = extra_data_lengths[info->index];
+ uint32 chunk_count = chunk_counts[info->index];
+ if (extra_data_length > 0) flags |= XSCF_EXTRA_DATA_PRESENT;
+ if (chunk_count > 0) flags |= XSCF_CHUNK_ID_LIST_PRESENT;
+ SlWriteUint32(flags);
+ SlObject(const_cast(info), _xlsi_sub_chunk_desc);
+
+ if (extra_data_length > 0) {
+ SlWriteUint32(extra_data_length);
+ size_t written = SlGetBytesWritten();
+ info->save_proc(info, false);
+ assert(SlGetBytesWritten() == written + extra_data_length);
+ }
+ if (chunk_count > 0) {
+ SlWriteUint32(chunk_count);
+ size_t written = SlGetBytesWritten();
+ WriteChunkIdList(info->chunk_list, false);
+ assert(SlGetBytesWritten() == written + (chunk_count * 4));
+ }
+ }
+ }
+}
+
+static void Load_SLXI()
+{
+ if (_sl_is_faked_ext || !_sl_is_ext_version) {
+ SlErrorCorrupt("SXLI chunk is unexpectedly present");
+ }
+
+ SlXvResetState();
+ _sl_is_ext_version = true;
+
+ uint32 version = SlReadUint32();
+ if (version > _sl_xv_slxi_chunk_version) SlErrorCorruptFmt("SLXI chunk: version: %u is too new (expected max: %u)", version, _sl_xv_slxi_chunk_version);
+
+ uint32 chunk_flags = SlReadUint32();
+ // flags are not in use yet, reserve for future expansion
+ if (chunk_flags != 0) SlErrorCorruptFmt("SLXI chunk: unknown chunk header flags: 0x%X", chunk_flags);
+
+ char name_buffer[256];
+ const SaveLoadGlobVarList xlsi_sub_chunk_name_desc[] = {
+ SLEG_STR(name_buffer, SLE_STRB),
+ SLEG_END()
+ };
+
+ uint32 item_count = SlReadUint32();
+ for (uint32 i = 0; i < item_count; i++) {
+ SlxiSubChunkFlags flags = static_cast(SlReadUint32());
+ uint16 version = SlReadUint16();
+ SlGlobList(xlsi_sub_chunk_name_desc);
+
+ // linearly scan through feature list until found name match
+ bool found = false;
+ const SlxiSubChunkInfo *info = _sl_xv_sub_chunk_infos;
+ for (; info->index != XSLFI_NULL; ++info) {
+ if (strcmp(name_buffer, info->name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ bool discard_chunks = false;
+ if (found) {
+ if (version > info->max_version) {
+ if (flags & XSCF_IGNORABLE_VERSION) {
+ // version too large but carry on regardless
+ discard_chunks = true;
+ if (flags & XSCF_EXTRA_DATA_PRESENT) {
+ SlSkipBytes(SlReadUint32()); // skip extra data field
+ }
+ DEBUG(sl, 1, "SLXI chunk: too large version for feature: '%s', version: %d, max version: %d, ignoring", name_buffer, version, info->max_version);
+ } else {
+ SlErrorCorruptFmt("SLXI chunk: too large version for feature: '%s', version: %d, max version: %d", name_buffer, version, info->max_version);
+ }
+ } else {
+ // success path :)
+
+ _sl_xv_feature_versions[info->index] = version;
+ if (flags & XSCF_EXTRA_DATA_PRESENT) {
+ uint32 extra_data_size = SlReadUint32();
+ if (extra_data_size) {
+ if (info->load_proc) {
+ size_t read = SlGetBytesRead();
+ info->load_proc(info, extra_data_size);
+ if (SlGetBytesRead() != read + extra_data_size) {
+ SlErrorCorruptFmt("SLXI chunk: feature: %s, version: %d, extra data length mismatch", name_buffer, version);
+ }
+ } else {
+ SlErrorCorruptFmt("SLXI chunk: feature: %s, version: %d, unexpectedly includes extra data", name_buffer, version);
+ }
+ }
+ }
+
+ DEBUG(sl, 1, "SLXI chunk: found known feature: '%s', version: %d, max version: %d", name_buffer, version, info->max_version);
+ }
+ } else {
+ if (flags & XSCF_IGNORABLE_UNKNOWN) {
+ // not found but carry on regardless
+ discard_chunks = true;
+ if (flags & XSCF_EXTRA_DATA_PRESENT) {
+ SlSkipBytes(SlReadUint32()); // skip extra data field
+ }
+ DEBUG(sl, 1, "SLXI chunk: unknown feature: '%s', version: %d, ignoring", name_buffer, version);
+ } else {
+ SlErrorCorruptFmt("SLXI chunk: unknown feature: %s, version: %d", name_buffer, version);
+ }
+ }
+
+ // at this point the extra data field should have been consumed
+ // handle chunk ID list field
+ if (flags & XSCF_CHUNK_ID_LIST_PRESENT) {
+ uint32 chunk_count = SlReadUint32();
+ for (uint32 j = 0; j < chunk_count; j++) {
+ uint32 chunk_id = SlReadUint32();
+ if (discard_chunks) {
+ _sl_xv_discardable_chunk_ids.push_back(chunk_id);
+ DEBUG(sl, 2, "SLXI chunk: unknown feature: '%s', discarding chunk: %c%c%c%c", name_buffer, chunk_id >> 24, chunk_id >> 16, chunk_id >> 8, chunk_id);
+ }
+ }
+ }
+ }
+}
+
+extern const ChunkHandler _version_ext_chunk_handlers[] = {
+ { 'SLXI', Save_SLXI, Load_SLXI, NULL, Load_SLXI, CH_RIFF | CH_LAST},
+};
Index: src/saveload/extended_ver_sl.h
new file mode 100644
===================================================================
--- src/saveload/extended_ver_sl.h (revision 0)
+++ src/saveload/extended_ver_sl.h (working copy)
@@ -0,0 +1,123 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file extended_ver_sl.h Functions/types related to handling save/load extended version info. */
+
+#ifndef EXTENDED_VER_SL_H
+#define EXTENDED_VER_SL_H
+
+#include "../core/bitmath_func.hpp"
+
+#include
+
+/**
+ * List of extended features, each feature has its own (16 bit) version
+ */
+enum SlXvFeatureIndex {
+ XSLFI_NULL = 0, ///< Unused value, to indicate that no extended feature test is in use
+ XSLFI_TRACE_RESTRICT, ///< Trace restrict
+ XSLFI_PROG_SIGS, ///< programmable signals patch
+ XSLFI_ADJACENT_CROSSINGS, ///< Adjacent level crossings closure patch
+ XSLFI_DEPARTURE_BOARDS, ///< Departure boards patch, in ticks mode
+ XSLFI_TIMETABLES_START_TICKS, ///< Timetable start time is in ticks, instead of days (from departure boards patch)
+ XSLFI_TOWN_CARGO_ADJ, ///< Town cargo adjustment patch
+ XSLFI_SIG_TUNNEL_BRIDGE, ///< Signals on tunnels and bridges
+ XSLFI_IMPROVED_BREAKDOWNS, ///< Improved breakdowns patch
+ XSLFI_TT_WAIT_IN_DEPOT, ///< Timetabling waiting time in depot patch
+ XSLFI_AUTO_TIMETABLE, ///< Auto timetables and separation patch
+ XSLFI_VEHICLE_REPAIR_COST, ///< Vehicle repair costs patch
+ XSLFI_ENH_VIEWPORT_PLANS, ///< Enhanced viewport patch: plans
+ XSLFI_INFRA_SHARING, ///< Infrastructure sharing patch
+ XSLFI_VARIABLE_DAY_LENGTH, ///< Variable day length patch
+ XSLFI_ORDER_OCCUPANCY, ///< Running average of order occupancy
+
+ XSLFI_SIZE, ///< Total count of features, including null feature
+};
+
+extern uint16 _sl_xv_feature_versions[XSLFI_SIZE];
+
+/**
+ * Operator to use when combining traditional savegame number test with an extended feature version test
+ */
+enum SlXvFeatureTestOperator {
+ XSLFTO_OR = 0, ///< Test if traditional savegame version is in bounds OR extended feature is in version bounds
+ XSLFTO_AND ///< Test if traditional savegame version is in bounds AND extended feature is in version bounds
+};
+
+/**
+ * Structure to describe an extended feature version test, and how it combines with a traditional savegame version test
+ */
+struct SlXvFeatureTest {
+ private:
+ uint16 min_version;
+ uint16 max_version;
+ SlXvFeatureIndex feature;
+ SlXvFeatureTestOperator op;
+
+ public:
+ SlXvFeatureTest()
+ : min_version(0), max_version(0), feature(XSLFI_NULL), op(XSLFTO_OR) { }
+
+ SlXvFeatureTest(SlXvFeatureTestOperator op_, SlXvFeatureIndex feature_, uint16 min_version_ = 1, uint16 max_version_ = 0xFFFF)
+ : min_version(min_version_), max_version(max_version_), feature(feature_), op(op_) { }
+
+ bool IsFeaturePresent(uint16 savegame_version, uint16 savegame_version_from, uint16 savegame_version_to) const;
+};
+
+bool SlXvIsFeaturePresent(SlXvFeatureIndex feature, uint16 min_version = 1, uint16 max_version = 0xFFFF);
+
+/**
+ * Returns true if @p feature is missing (i.e. has a version of 0)
+ */
+inline bool SlXvIsFeatureMissing(SlXvFeatureIndex feature)
+{
+ return !SlXvIsFeaturePresent(feature);
+}
+
+/**
+ * sub chunk flags, this is saved as-is
+ * (XSCF_EXTRA_DATA_PRESENT and XSCF_CHUNK_ID_LIST_PRESENT must only be set by the save code, and read by the load code)
+ */
+enum SlxiSubChunkFlags {
+ XSCF_NULL = 0, ///< zero value
+ XSCF_IGNORABLE_UNKNOWN = 1 << 0, ///< the loader is free to ignore this without aborting the load if it doesn't know what it is at all
+ XSCF_IGNORABLE_VERSION = 1 << 1, ///< the loader is free to ignore this without aborting the load if the version is greater than the maximum that can be loaded
+ XSCF_EXTRA_DATA_PRESENT = 1 << 2, ///< extra data field is present, extra data in some sub-chunk/feature specific format, not used for anything yet
+ XSCF_CHUNK_ID_LIST_PRESENT = 1 << 3, ///< chunk ID list field is present, list of chunks which this sub-chunk/feature adds to the save game, this can be used to discard the chunks if the feature is unknown
+
+ XSCF_IGNORABLE_ALL = XSCF_IGNORABLE_UNKNOWN | XSCF_IGNORABLE_VERSION, ///< all "ignorable" flags
+};
+DECLARE_ENUM_AS_BIT_SET(SlxiSubChunkFlags)
+
+struct SlxiSubChunkInfo;
+
+typedef uint32 SlxiSubChunkSaveProc(const SlxiSubChunkInfo *info, bool dry_run); ///< sub chunk save procedure type, must return length and write no data when dry_run is true
+typedef void SlxiSubChunkLoadProc(const SlxiSubChunkInfo *info, uint32 length); ///< sub chunk load procedure, must consume length bytes
+
+/** Handlers and description of chunk. */
+struct SlxiSubChunkInfo {
+ SlXvFeatureIndex index; ///< feature index, this is saved
+ SlxiSubChunkFlags flags; ///< flags, this is saved
+ uint16 save_version; ///< version to save
+ uint16 max_version; ///< maximum version to accept on load
+ const char *name; ///< feature name, this *IS* saved, so must be globally unique
+ SlxiSubChunkSaveProc *save_proc; ///< save procedure of the sub chunk, this may be NULL in which case no extra chunk data is saved
+ SlxiSubChunkLoadProc *load_proc; ///< load procedure of the sub chunk, this may be NULL in which case the extra chunk data must be missing or of 0 length
+ const char *chunk_list; ///< this is a list of chunks that this feature uses, which should be written to the savegame, this must be a comma-seperated list of 4-character IDs, with no spaces, or NULL
+};
+
+void SlXvResetState();
+
+void SlXvSetCurrentState();
+
+void SlXvCheckSpecialSavegameVersions();
+
+bool SlXvIsChunkDiscardable(uint32 id);
+
+#endif /* EXTENDED_VER_SL_H */
Index: src/saveload/misc_sl.cpp
===================================================================
--- src/saveload/misc_sl.cpp (revision 27386)
+++ src/saveload/misc_sl.cpp (working copy)
@@ -75,6 +75,7 @@
SLEG_CONDVAR(_date, SLE_INT32, 31, SL_MAX_VERSION),
SLEG_VAR(_date_fract, SLE_UINT16),
SLEG_VAR(_tick_counter, SLE_UINT16),
+ SLEG_CONDVAR_X(_tick_skip_counter, SLE_UINT8, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_VARIABLE_DAY_LENGTH)),
SLE_CONDNULL(2, 0, 156), // _vehicle_id_ctr_day
SLEG_CONDVAR(_age_cargo_skip_counter, SLE_UINT8, 0, 161),
SLE_CONDNULL(1, 0, 45),
@@ -100,6 +101,7 @@
SLEG_CONDVAR(_load_check_data.current_date, SLE_INT32, 31, SL_MAX_VERSION),
SLE_NULL(2), // _date_fract
SLE_NULL(2), // _tick_counter
+ SLEG_CONDVAR_X(_tick_skip_counter, SLE_UINT8, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_VARIABLE_DAY_LENGTH)),
SLE_CONDNULL(2, 0, 156), // _vehicle_id_ctr_day
SLE_CONDNULL(1, 0, 161), // _age_cargo_skip_counter
SLE_CONDNULL(1, 0, 45),
Index: src/saveload/order_sl.cpp
===================================================================
--- src/saveload/order_sl.cpp (revision 27386)
+++ src/saveload/order_sl.cpp (working copy)
@@ -110,6 +110,7 @@
SLE_REF(Order, next, REF_ORDER),
SLE_CONDVAR(Order, refit_cargo, SLE_UINT8, 36, SL_MAX_VERSION),
SLE_CONDNULL(1, 36, 181), // refit_subtype
+ SLE_CONDVAR_X(Order, occupancy, SLE_UINT8, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ORDER_OCCUPANCY)),
SLE_CONDVAR(Order, wait_time, SLE_UINT16, 67, SL_MAX_VERSION),
SLE_CONDVAR(Order, travel_time, SLE_UINT16, 67, SL_MAX_VERSION),
SLE_CONDVAR(Order, max_speed, SLE_UINT16, 172, SL_MAX_VERSION),
Index: src/saveload/plans_sl.cpp
new file mode 100644
===================================================================
--- src/saveload/plans_sl.cpp (revision 0)
+++ src/saveload/plans_sl.cpp (working copy)
@@ -0,0 +1,87 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file plans_sl.cpp Code handling saving and loading of plans data. */
+
+#include "../stdafx.h"
+#include "../plans_base.h"
+#include "../fios.h"
+
+#include "saveload.h"
+
+/** Description of a plan within the savegame. */
+static const SaveLoad _plan_desc[] = {
+ SLE_VAR(Plan, owner, SLE_UINT8),
+ SLE_VAR(Plan, visible, SLE_BOOL),
+ SLE_VAR(Plan, visible_by_all, SLE_BOOL),
+ SLE_VAR(Plan, creation_date, SLE_INT32),
+ SLE_END()
+};
+
+/** Save all plans. */
+static void Save_PLAN()
+{
+ Plan *p;
+ FOR_ALL_PLANS(p) {
+ SlSetArrayIndex(p->index);
+ SlObject(p, _plan_desc);
+ }
+}
+
+/** Load all plans. */
+static void Load_PLAN()
+{
+ int index;
+ while ((index = SlIterateArray()) != -1) {
+ Plan *p = new (index) Plan();
+ SlObject(p, _plan_desc);
+ }
+}
+
+/** Save all plan lines. */
+static void Save_PLANLINE()
+{
+ Plan *p;
+ FOR_ALL_PLANS(p) {
+ for (size_t i = 0; i < p->lines.size(); i++) {
+ SlSetArrayIndex((uint) p->index << 16 | (uint) i);
+ PlanLine *pl = p->lines[i];
+ size_t plsz = pl->tiles.size();
+ SlSetLength(plsz * sizeof(TileIndex));
+ SlArray(&pl->tiles[0], plsz, SLE_UINT32);
+ }
+ }
+}
+
+/** Load all plan lines. */
+static void Load_PLANLINE()
+{
+ int index;
+ while ((index = SlIterateArray()) != -1) {
+ Plan *p = Plan::Get((uint) index >> 16);
+ uint line_index = index & 0xFFFF;
+ if (p->lines.size() <= line_index) p->lines.resize(line_index + 1);
+ PlanLine *pl = new PlanLine();
+ p->lines[line_index] = pl;
+ size_t plsz = SlGetFieldLength() / sizeof(TileIndex);
+ pl->tiles.resize(plsz);
+ SlArray(&pl->tiles[0], plsz, SLE_UINT32);
+ }
+
+ Plan *p;
+ FOR_ALL_PLANS(p) {
+ p->SetVisibility(false);
+ }
+}
+
+/** Chunk handlers related to plans. */
+extern const ChunkHandler _plan_chunk_handlers[] = {
+ { 'PLAN', Save_PLAN, Load_PLAN, NULL, NULL, CH_ARRAY},
+ { 'PLLN', Save_PLANLINE, Load_PLANLINE, NULL, NULL, CH_ARRAY | CH_LAST},
+};
Index: src/saveload/saveload.cpp
===================================================================
--- src/saveload/saveload.cpp (revision 27386)
+++ src/saveload/saveload.cpp (working copy)
@@ -48,9 +48,12 @@
#include "saveload_internal.h"
#include "saveload_filter.h"
+#include "extended_ver_sl.h"
#include "../safeguards.h"
+#include
+
/*
* Previous savegame versions, the trunk revision where they were
* introduced and the released version that had that particular
@@ -264,6 +267,7 @@
* 194 26881 1.5.x
*/
extern const uint16 SAVEGAME_VERSION = 194; ///< Current savegame version of OpenTTD.
+const uint16 SAVEGAME_VERSION_EXT = 0x8000; ///< Savegame extension indicator mask
SavegameType _savegame_type; ///< type of savegame we are loading
@@ -273,6 +277,8 @@
char _savegame_format[8]; ///< how to compress savegames
bool _do_autosave; ///< are we doing an autosave at the moment?
+extern bool _sl_is_ext_version;
+
/** What are we currently doing? */
enum SaveLoadAction {
SLA_LOAD, ///< loading
@@ -414,6 +420,7 @@
static SaveLoadParams _sl; ///< Parameters used for/at saveload.
/* these define the chunks */
+extern const ChunkHandler _version_ext_chunk_handlers[];
extern const ChunkHandler _gamelog_chunk_handlers[];
extern const ChunkHandler _map_chunk_handlers[];
extern const ChunkHandler _misc_chunk_handlers[];
@@ -447,9 +454,13 @@
extern const ChunkHandler _airport_chunk_handlers[];
extern const ChunkHandler _object_chunk_handlers[];
extern const ChunkHandler _persistent_storage_chunk_handlers[];
+extern const ChunkHandler _trace_restrict_chunk_handlers[];
+extern const ChunkHandler _signal_chunk_handlers[];
+extern const ChunkHandler _plan_chunk_handlers[];
/** Array of all chunks in a savegame, \c NULL terminated. */
static const ChunkHandler * const _chunk_handlers[] = {
+ _version_ext_chunk_handlers, // this should be first, such that it is saved first, as when loading it affects the loading of subsequent chunks
_gamelog_chunk_handlers,
_map_chunk_handlers,
_misc_chunk_handlers,
@@ -483,6 +494,9 @@
_airport_chunk_handlers,
_object_chunk_handlers,
_persistent_storage_chunk_handlers,
+ _trace_restrict_chunk_handlers,
+ _signal_chunk_handlers,
+ _plan_chunk_handlers,
NULL,
};
@@ -503,6 +517,7 @@
* during NULLing; especially those that try to get
* pointers from other pools. */
_sl_version = SAVEGAME_VERSION;
+ SlXvSetCurrentState();
DEBUG(sl, 1, "Nulling pointers");
@@ -526,17 +541,22 @@
* @note This function does never return as it throws an exception to
* break out of all the saveload code.
*/
-void NORETURN SlError(StringID string, const char *extra_msg)
+void NORETURN SlError(StringID string, const char *extra_msg, bool already_malloced)
{
+ char *str = NULL;
+ if (extra_msg != NULL) {
+ str = already_malloced ? const_cast(extra_msg) : stredup(extra_msg);
+ }
+
/* Distinguish between loading into _load_check_data vs. normal save/load. */
if (_sl.action == SLA_LOAD_CHECK) {
_load_check_data.error = string;
free(_load_check_data.error_data);
- _load_check_data.error_data = (extra_msg == NULL) ? NULL : stredup(extra_msg);
+ _load_check_data.error_data = str;
} else {
_sl.error_str = string;
free(_sl.extra_msg);
- _sl.extra_msg = (extra_msg == NULL) ? NULL : stredup(extra_msg);
+ _sl.extra_msg = str;
}
/* We have to NULL all pointers here; we might be in a state where
@@ -548,15 +568,39 @@
}
/**
+ * As SlError, except that it takes a format string and additional parameters
+ */
+void CDECL NORETURN SlErrorFmt(StringID string, const char *msg, ...)
+{
+ va_list va;
+ va_start(va, msg);
+ char *str = str_vfmt(msg, va);
+ va_end(va);
+ SlError(string, str, true);
+}
+
+/**
* Error handler for corrupt savegames. Sets everything up to show the
* error message and to clean up the mess of a partial savegame load.
* @param msg Location the corruption has been spotted.
* @note This function does never return as it throws an exception to
* break out of all the saveload code.
*/
-void NORETURN SlErrorCorrupt(const char *msg)
+void NORETURN SlErrorCorrupt(const char *msg, bool already_malloced)
{
- SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, msg);
+ SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, msg, already_malloced);
+}
+
+/**
+ * As SlErrorCorruptFmt, except that it takes a format string and additional parameters
+ */
+void CDECL NORETURN SlErrorCorruptFmt(const char *msg, ...)
+{
+ va_list va;
+ va_start(va, msg);
+ char *str = str_vfmt(msg, va);
+ va_end(va);
+ SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, str, true);
}
@@ -612,51 +656,24 @@
_sl.dumper->WriteByte(b);
}
-static inline int SlReadUint16()
-{
- int x = SlReadByte() << 8;
- return x | SlReadByte();
-}
-
-static inline uint32 SlReadUint32()
-{
- uint32 x = SlReadUint16() << 16;
- return x | SlReadUint16();
-}
-
-static inline uint64 SlReadUint64()
-{
- uint32 x = SlReadUint32();
- uint32 y = SlReadUint32();
- return (uint64)x << 32 | y;
-}
-
-static inline void SlWriteUint16(uint16 v)
-{
- SlWriteByte(GB(v, 8, 8));
- SlWriteByte(GB(v, 0, 8));
-}
-
-static inline void SlWriteUint32(uint32 v)
-{
- SlWriteUint16(GB(v, 16, 16));
- SlWriteUint16(GB(v, 0, 16));
-}
-
-static inline void SlWriteUint64(uint64 x)
+/**
+ * Returns number of bytes read so far
+ * May only be called during a load/load check action
+ */
+size_t SlGetBytesRead()
{
- SlWriteUint32((uint32)(x >> 32));
- SlWriteUint32((uint32)x);
+ assert(_sl.action == SLA_LOAD || _sl.action == SLA_LOAD_CHECK);
+ return _sl.reader->GetSize();
}
/**
- * Read in bytes from the file/data structure but don't do
- * anything with them, discarding them in effect
- * @param length The amount of bytes that is being treated this way
+ * Returns number of bytes written so far
+ * May only be called during a save action
*/
-static inline void SlSkipBytes(size_t length)
+size_t SlGetBytesWritten()
{
- for (; length != 0; length--) SlReadByte();
+ assert(_sl.action == SLA_SAVE);
+ return _sl.dumper->GetSize();
}
/**
@@ -1414,7 +1431,7 @@
/** Are we going to save this object or not? */
static inline bool SlIsObjectValidInSavegame(const SaveLoad *sld)
{
- if (_sl_version < sld->version_from || _sl_version > sld->version_to) return false;
+ if (!sld->ext_feature_test.IsFeaturePresent(_sl_version, sld->version_from, sld->version_to)) return false;
if (sld->conv & SLF_NOT_IN_SAVE) return false;
return true;
@@ -1692,7 +1709,7 @@
/**
* Load a chunk of data for checking savegames.
* If the chunkhandler is NULL, the chunk is skipped.
- * @param ch The chunkhandler that will be used for the operation
+ * @param ch The chunkhandler that will be used for the operation, this may be NULL
*/
static void SlLoadCheckChunk(const ChunkHandler *ch)
{
@@ -1706,14 +1723,14 @@
switch (m) {
case CH_ARRAY:
_sl.array_index = 0;
- if (ch->load_check_proc) {
+ if (ch && ch->load_check_proc) {
ch->load_check_proc();
} else {
SlSkipArray();
}
break;
case CH_SPARSE_ARRAY:
- if (ch->load_check_proc) {
+ if (ch && ch->load_check_proc) {
ch->load_check_proc();
} else {
SlSkipArray();
@@ -1726,7 +1743,7 @@
len += SlReadUint16();
_sl.obj_len = len;
endoffs = _sl.reader->GetSize() + len;
- if (ch->load_check_proc) {
+ if (ch && ch->load_check_proc) {
ch->load_check_proc();
} else {
SlSkipBytes(len);
@@ -1840,8 +1857,16 @@
DEBUG(sl, 2, "Loading chunk %c%c%c%c", id >> 24, id >> 16, id >> 8, id);
ch = SlFindChunkHandler(id);
- if (ch == NULL) SlErrorCorrupt("Unknown chunk type");
- SlLoadChunk(ch);
+ if (ch == NULL) {
+ if (SlXvIsChunkDiscardable(id)) {
+ DEBUG(sl, 1, "Discarding chunk %c%c%c%c", id >> 24, id >> 16, id >> 8, id);
+ SlLoadCheckChunk(NULL);
+ } else {
+ SlErrorCorrupt("Unknown chunk type");
+ }
+ } else {
+ SlLoadChunk(ch);
+ }
}
}
@@ -1855,7 +1880,7 @@
DEBUG(sl, 2, "Loading chunk %c%c%c%c", id >> 24, id >> 16, id >> 8, id);
ch = SlFindChunkHandler(id);
- if (ch == NULL) SlErrorCorrupt("Unknown chunk type");
+ if (ch == NULL && !SlXvIsChunkDiscardable(id)) SlErrorCorrupt("Unknown chunk type");
SlLoadCheckChunk(ch);
}
}
@@ -2511,7 +2536,7 @@
const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression);
/* We have written our stuff to memory, now write it to file! */
- uint32 hdr[2] = { fmt->tag, TO_BE32(SAVEGAME_VERSION << 16) };
+ uint32 hdr[2] = { fmt->tag, TO_BE32((SAVEGAME_VERSION | SAVEGAME_VERSION_EXT) << 16) };
_sl.sf->Write((byte*)hdr, sizeof(hdr));
_sl.sf = fmt->init_write(_sl.sf, compression);
@@ -2578,6 +2603,7 @@
_sl.sf = writer;
_sl_version = SAVEGAME_VERSION;
+ SlXvSetCurrentState();
SaveViewportBeforeSaveGame();
SlSaveChunks();
@@ -2629,6 +2655,8 @@
_load_check_data.checkable = true;
}
+ SlXvResetState();
+
uint32 hdr[2];
if (_sl.lf->Read((byte*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
@@ -2641,6 +2669,7 @@
_sl.lf->Reset();
_sl_version = 0;
_sl_minor_version = 0;
+ SlXvResetState();
/* Try to find the LZO savegame format; it uses 'OTTD' as tag. */
fmt = _saveload_formats;
@@ -2663,7 +2692,14 @@
* Therefore it is loaded, but never saved (or, it saves a 0 in any scenario). */
_sl_minor_version = (TO_BE32(hdr[1]) >> 8) & 0xFF;
- DEBUG(sl, 1, "Loading savegame version %d", _sl_version);
+ if (_sl_version & SAVEGAME_VERSION_EXT) {
+ _sl_version &= ~SAVEGAME_VERSION_EXT;
+ _sl_is_ext_version = true;
+ } else {
+ SlXvCheckSpecialSavegameVersions();
+ }
+
+ DEBUG(sl, 1, "Loading savegame version %d%s", _sl_version, _sl_is_ext_version ? " (extended)" : "");
/* Is the version higher than the current? */
if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME);
@@ -2748,6 +2784,8 @@
GamelogStopAction();
}
+ SlXvSetCurrentState();
+
return SL_OK;
}
@@ -2800,12 +2838,14 @@
if (!LoadOldSaveGame(filename)) return SL_REINIT;
_sl_version = 0;
_sl_minor_version = 0;
+ SlXvResetState();
GamelogStartAction(GLAT_LOAD);
if (!AfterLoadGame()) {
GamelogStopAction();
return SL_REINIT;
}
GamelogStopAction();
+ SlXvSetCurrentState();
return SL_OK;
}
Index: src/saveload/saveload.h
===================================================================
--- src/saveload/saveload.h (revision 27386)
+++ src/saveload/saveload.h (working copy)
@@ -14,6 +14,9 @@
#include "../fileio_type.h"
#include "../strings_type.h"
+#include "extended_ver_sl.h"
+
+#include
/** Save or load result codes. */
enum SaveOrLoadResult {
@@ -213,6 +216,7 @@
* that is called to save it. address: global=true, offset: global=false */
void *address; ///< address of variable OR offset of variable in the struct (max offset is 65536)
size_t size; ///< the sizeof size.
+ SlXvFeatureTest ext_feature_test; ///< extended feature test
};
/** Same as #SaveLoad but global variables are used (for better readability); */
@@ -226,9 +230,11 @@
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
* @note In general, it is better to use one of the SLE_* macros below.
*/
-#define SLE_GENERAL(cmd, base, variable, type, length, from, to) {false, cmd, type, length, from, to, (void*)cpp_offsetof(base, variable), cpp_sizeof(base, variable)}
+#define SLE_GENERAL_X(cmd, base, variable, type, length, from, to, extver) {false, cmd, type, length, from, to, (void*)cpp_offsetof(base, variable), cpp_sizeof(base, variable), extver}
+#define SLE_GENERAL(cmd, base, variable, type, length, from, to) SLE_GENERAL_X(cmd, base, variable, type, length, from, to, SlXvFeatureTest())
/**
* Storage of a variable in some savegame versions.
@@ -237,8 +243,10 @@
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLE_CONDVAR(base, variable, type, from, to) SLE_GENERAL(SL_VAR, base, variable, type, 0, from, to)
+#define SLE_CONDVAR_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_VAR, base, variable, type, 0, from, to, extver)
+#define SLE_CONDVAR(base, variable, type, from, to) SLE_CONDVAR_X(base, variable, type, from, to, SlXvFeatureTest())
/**
* Storage of a reference in some savegame versions.
@@ -247,8 +255,10 @@
* @param type Type of the reference, a value from #SLRefType.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLE_CONDREF(base, variable, type, from, to) SLE_GENERAL(SL_REF, base, variable, type, 0, from, to)
+#define SLE_CONDREF_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_REF, base, variable, type, 0, from, to, extver)
+#define SLE_CONDREF(base, variable, type, from, to) SLE_CONDREF_X(base, variable, type, from, to, SlXvFeatureTest())
/**
* Storage of an array in some savegame versions.
@@ -258,8 +268,10 @@
* @param length Number of elements in the array.
* @param from First savegame version that has the array.
* @param to Last savegame version that has the array.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLE_CONDARR(base, variable, type, length, from, to) SLE_GENERAL(SL_ARR, base, variable, type, length, from, to)
+#define SLE_CONDARR_X(base, variable, type, length, from, to, extver) SLE_GENERAL_X(SL_ARR, base, variable, type, length, from, to, extver)
+#define SLE_CONDARR(base, variable, type, length, from, to) SLE_CONDARR_X(base, variable, type, length, from, to, SlXvFeatureTest())
/**
* Storage of a string in some savegame versions.
@@ -269,8 +281,10 @@
* @param length Number of elements in the string (only used for fixed size buffers).
* @param from First savegame version that has the string.
* @param to Last savegame version that has the string.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLE_CONDSTR(base, variable, type, length, from, to) SLE_GENERAL(SL_STR, base, variable, type, length, from, to)
+#define SLE_CONDSTR_X(base, variable, type, length, from, to, extver) SLE_GENERAL_X(SL_STR, base, variable, type, length, from, to, extver)
+#define SLE_CONDSTR(base, variable, type, length, from, to) SLE_CONDSTR_X(base, variable, type, length, from, to, SlXvFeatureTest())
/**
* Storage of a list in some savegame versions.
@@ -279,8 +293,10 @@
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the list.
* @param to Last savegame version that has the list.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLE_CONDLST(base, variable, type, from, to) SLE_GENERAL(SL_LST, base, variable, type, 0, from, to)
+#define SLE_CONDLST_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_LST, base, variable, type, 0, from, to, extver)
+#define SLE_CONDLST(base, variable, type, from, to) SLE_CONDLST_X(base, variable, type, from, to, SlXvFeatureTest())
/**
* Storage of a variable in every version of a savegame.
@@ -335,17 +351,19 @@
* @param length Length of the empty space.
* @param from First savegame version that has the empty space.
* @param to Last savegame version that has the empty space.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have empty space
*/
-#define SLE_CONDNULL(length, from, to) SLE_CONDARR(NullStruct, null, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to)
+#define SLE_CONDNULL_X(length, from, to, extver) SLE_CONDARR_X(NullStruct, null, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to, extver)
+#define SLE_CONDNULL(length, from, to) SLE_CONDNULL_X(length, from, to, SlXvFeatureTest())
/** Translate values ingame to different values in the savegame and vv. */
#define SLE_WRITEBYTE(base, variable, value) SLE_GENERAL(SL_WRITEBYTE, base, variable, 0, 0, value, value)
-#define SLE_VEH_INCLUDE() {false, SL_VEH_INCLUDE, 0, 0, 0, SL_MAX_VERSION, NULL, 0}
-#define SLE_ST_INCLUDE() {false, SL_ST_INCLUDE, 0, 0, 0, SL_MAX_VERSION, NULL, 0}
+#define SLE_VEH_INCLUDE() {false, SL_VEH_INCLUDE, 0, 0, 0, SL_MAX_VERSION, NULL, 0, SlXvFeatureTest()}
+#define SLE_ST_INCLUDE() {false, SL_ST_INCLUDE, 0, 0, 0, SL_MAX_VERSION, NULL, 0, SlXvFeatureTest()}
/** End marker of a struct/class save or load. */
-#define SLE_END() {false, SL_END, 0, 0, 0, 0, NULL, 0}
+#define SLE_END() {false, SL_END, 0, 0, 0, 0, NULL, 0, SlXvFeatureTest()}
/**
* Storage of global simple variables, references (pointers), and arrays.
@@ -354,9 +372,11 @@
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
* @note In general, it is better to use one of the SLEG_* macros below.
*/
-#define SLEG_GENERAL(cmd, variable, type, length, from, to) {true, cmd, type, length, from, to, (void*)&variable, sizeof(variable)}
+#define SLEG_GENERAL_X(cmd, variable, type, length, from, to, extver) {true, cmd, type, length, from, to, (void*)&variable, sizeof(variable), extver}
+#define SLEG_GENERAL(cmd, variable, type, length, from, to) SLEG_GENERAL_X(cmd, variable, type, length, from, to, SlXvFeatureTest())
/**
* Storage of a global variable in some savegame versions.
@@ -364,8 +384,10 @@
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLEG_CONDVAR(variable, type, from, to) SLEG_GENERAL(SL_VAR, variable, type, 0, from, to)
+#define SLEG_CONDVAR_X(variable, type, from, to, extver) SLEG_GENERAL_X(SL_VAR, variable, type, 0, from, to, extver)
+#define SLEG_CONDVAR(variable, type, from, to) SLEG_CONDVAR_X(variable, type, from, to, SlXvFeatureTest())
/**
* Storage of a global reference in some savegame versions.
@@ -373,8 +395,10 @@
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLEG_CONDREF(variable, type, from, to) SLEG_GENERAL(SL_REF, variable, type, 0, from, to)
+#define SLEG_CONDREF_X(variable, type, from, to, extver) SLEG_GENERAL_X(SL_REF, variable, type, 0, from, to, extver)
+#define SLEG_CONDREF(variable, type, from, to) SLEG_CONDREF_X(variable, type, from, to, SlXvFeatureTest())
/**
* Storage of a global array in some savegame versions.
@@ -383,8 +407,10 @@
* @param length Number of elements in the array.
* @param from First savegame version that has the array.
* @param to Last savegame version that has the array.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLEG_CONDARR(variable, type, length, from, to) SLEG_GENERAL(SL_ARR, variable, type, length, from, to)
+#define SLEG_CONDARR_X(variable, type, length, from, to, extver) SLEG_GENERAL_X(SL_ARR, variable, type, length, from, to, extver)
+#define SLEG_CONDARR(variable, type, length, from, to) SLEG_CONDARR_X(variable, type, length, from, to, SlXvFeatureTest())
/**
* Storage of a global string in some savegame versions.
@@ -393,8 +419,10 @@
* @param length Number of elements in the string (only used for fixed size buffers).
* @param from First savegame version that has the string.
* @param to Last savegame version that has the string.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLEG_CONDSTR(variable, type, length, from, to) SLEG_GENERAL(SL_STR, variable, type, length, from, to)
+#define SLEG_CONDSTR_X(variable, type, length, from, to, extver) SLEG_GENERAL_X(SL_STR, variable, type, length, from, to, extver)
+#define SLEG_CONDSTR(variable, type, length, from, to) SLEG_CONDSTR_X(variable, type, length, from, to, SlXvFeatureTest())
/**
* Storage of a global list in some savegame versions.
@@ -402,8 +430,10 @@
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the list.
* @param to Last savegame version that has the list.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have the field
*/
-#define SLEG_CONDLST(variable, type, from, to) SLEG_GENERAL(SL_LST, variable, type, 0, from, to)
+#define SLEG_CONDLST_X(variable, type, from, to, extver) SLEG_GENERAL_X(SL_LST, variable, type, 0, from, to, extver)
+#define SLEG_CONDLST(variable, type, from, to) SLEG_CONDLST_X(variable, type, from, to, SlXvFeatureTest())
/**
* Storage of a global variable in every savegame version.
@@ -445,11 +475,12 @@
* @param length Length of the empty space.
* @param from First savegame version that has the empty space.
* @param to Last savegame version that has the empty space.
+ * @param extver SlXvFeatureTest to test (along with from and to) which savegames have empty space
*/
-#define SLEG_CONDNULL(length, from, to) {true, SL_ARR, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to, (void*)NULL}
+#define SLEG_CONDNULL(length, from, to) {true, SL_ARR, SLE_FILE_U8 | SLE_VAR_NULL | SLF_NOT_IN_CONFIG, length, from, to, (void*)NULL, SlXvFeatureTest()}
/** End marker of global variables save or load. */
-#define SLEG_END() {true, SL_END, 0, 0, 0, 0, NULL, 0}
+#define SLEG_END() {true, SL_END, 0, 0, 0, 0, NULL, 0, SlXvFeatureTest()}
/**
* Checks whether the savegame is below \a major.\a minor.
@@ -471,10 +502,10 @@
* @param version_to Highest version number that falls within the range.
* @return Active savegame version falls within the given range.
*/
-static inline bool SlIsObjectCurrentlyValid(uint16 version_from, uint16 version_to)
+static inline bool SlIsObjectCurrentlyValid(uint16 version_from, uint16 version_to, SlXvFeatureTest ext_feature_test)
{
extern const uint16 SAVEGAME_VERSION;
- if (SAVEGAME_VERSION < version_from || SAVEGAME_VERSION > version_to) return false;
+ if (!ext_feature_test.IsFeaturePresent(SAVEGAME_VERSION, version_from, version_to)) return false;
return true;
}
@@ -537,12 +568,64 @@
byte SlReadByte();
void SlWriteByte(byte b);
+static inline int SlReadUint16()
+{
+ int x = SlReadByte() << 8;
+ return x | SlReadByte();
+}
+
+static inline uint32 SlReadUint32()
+{
+ uint32 x = SlReadUint16() << 16;
+ return x | SlReadUint16();
+}
+
+static inline uint64 SlReadUint64()
+{
+ uint32 x = SlReadUint32();
+ uint32 y = SlReadUint32();
+ return (uint64)x << 32 | y;
+}
+
+static inline void SlWriteUint16(uint16 v)
+{
+ SlWriteByte(GB(v, 8, 8));
+ SlWriteByte(GB(v, 0, 8));
+}
+
+static inline void SlWriteUint32(uint32 v)
+{
+ SlWriteUint16(GB(v, 16, 16));
+ SlWriteUint16(GB(v, 0, 16));
+}
+
+static inline void SlWriteUint64(uint64 x)
+{
+ SlWriteUint32((uint32)(x >> 32));
+ SlWriteUint32((uint32)x);
+}
+
+/**
+ * Read in bytes from the file/data structure but don't do
+ * anything with them, discarding them in effect
+ * @param length The amount of bytes that is being treated this way
+ */
+static inline void SlSkipBytes(size_t length)
+{
+ for (; length != 0; length--) SlReadByte();
+}
+
+size_t SlGetBytesRead();
+size_t SlGetBytesWritten();
+
void SlGlobList(const SaveLoadGlobVarList *sldg);
void SlArray(void *array, size_t length, VarType conv);
void SlObject(void *object, const SaveLoad *sld);
bool SlObjectMember(void *object, const SaveLoad *sld);
-void NORETURN SlError(StringID string, const char *extra_msg = NULL);
-void NORETURN SlErrorCorrupt(const char *msg);
+void NORETURN SlError(StringID string, const char *extra_msg = NULL, bool already_malloced = false);
+void NORETURN SlErrorCorrupt(const char *msg, bool already_malloced = false);
+void CDECL NORETURN SlErrorFmt(StringID string, const char *msg, ...) WARN_FORMAT(2, 3);
+void CDECL NORETURN SlErrorCorruptFmt(const char *msg, ...) WARN_FORMAT(1, 2);
bool SaveloadCrashWithMissingNewGRFs();
Index: src/saveload/saveload_internal.h
===================================================================
--- src/saveload/saveload_internal.h (revision 27386)
+++ src/saveload/saveload_internal.h (working copy)
@@ -34,6 +34,7 @@
void AfterLoadStoryBook();
void AfterLoadLinkGraphs();
void AfterLoadCompanyStats();
+void AfterLoadTraceRestrict();
void UpdateHousesAndTowns();
void UpdateOldAircraft();
Index: src/saveload/signal_sl.cpp
new file mode 100644
===================================================================
--- src/saveload/signal_sl.cpp (revision 0)
+++ src/saveload/signal_sl.cpp (working copy)
@@ -0,0 +1,286 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file signal_sl.cpp Code handling saving and loading of signals */
+
+#include "../stdafx.h"
+#include "../programmable_signals.h"
+#include "../core/alloc_type.hpp"
+#include "../core/bitmath_func.hpp"
+#include
+#include "saveload.h"
+
+typedef std::vector Buffer;
+
+// Variable length integers are stored in Variable Length Quantity
+// format (http://en.wikipedia.org/wiki/Variable-length_quantity)
+
+static void WriteVLI(Buffer &b, uint i)
+{
+ uint lsmask = 0x7F;
+ uint msmask = ~0x7F;
+ while(i & msmask) {
+ byte part = (i & lsmask) | 0x80;
+ b.push_back(part);
+ i >>= 7;
+ }
+ b.push_back((byte) i);
+}
+
+static uint ReadVLI()
+{
+ uint shift = 0;
+ uint val = 0;
+ byte b;
+
+ b = SlReadByte();
+ while(b & 0x80) {
+ val |= uint(b & 0x7F) << shift;
+ shift += 7;
+ b = SlReadByte();
+ }
+ val |= uint(b) << shift;
+ return val;
+}
+
+static void WriteCondition(Buffer &b, SignalCondition *c)
+{
+ WriteVLI(b, c->ConditionCode());
+ switch(c->ConditionCode()) {
+ case PSC_NUM_GREEN:
+ case PSC_NUM_RED: {
+ SignalVariableCondition *vc = static_cast(c);
+ WriteVLI(b, vc->comparator);
+ WriteVLI(b, vc->value);
+ } break;
+
+ case PSC_SIGNAL_STATE: {
+ SignalStateCondition *sc = static_cast(c);
+ WriteVLI(b, sc->sig_tile);
+ WriteVLI(b, sc->sig_track);
+ } break;
+
+ default:
+ break;
+ }
+}
+
+static SignalCondition *ReadCondition(SignalReference this_sig)
+{
+ SignalConditionCode code = (SignalConditionCode) ReadVLI();
+ switch(code) {
+ case PSC_NUM_GREEN:
+ case PSC_NUM_RED: {
+ SignalVariableCondition *c = new SignalVariableCondition(code);
+ c->comparator = (SignalComparator) ReadVLI();
+ if(c->comparator > SGC_LAST) NOT_REACHED();
+ c->value = ReadVLI();
+ return c;
+ }
+
+ case PSC_SIGNAL_STATE: {
+ TileIndex ti = (TileIndex) ReadVLI();
+ Trackdir td = (Trackdir) ReadVLI();
+ return new SignalStateCondition(this_sig, ti, td);
+ }
+
+ default:
+ return new SignalSimpleCondition(code);
+ }
+}
+
+static void Save_SPRG()
+{
+ // Check for, and dispose of, any signal information on a tile which doesn't have signals.
+ // This indicates that someone removed the signals from the tile but didn't clean them up.
+ // (This code is to detect bugs and limit their consquences, not to cover them up!)
+ for(ProgramList::iterator i = _signal_programs.begin(), e = _signal_programs.end();
+ i != e; ++i) {
+ SignalReference ref = i->first;
+ if(!HasProgrammableSignals(ref)) {
+ DEBUG(sl, 0, "Programmable signal information for (%x, %d) has been leaked!",
+ ref.tile, ref.track);
+ ++i;
+ FreeSignalProgram(ref);
+ if(i == e) break;
+ }
+ }
+
+ // OK, we can now write out our programs
+ Buffer b;
+ WriteVLI(b, _signal_programs.size());
+ for(ProgramList::iterator i = _signal_programs.begin(), e = _signal_programs.end();
+ i != e; ++i) {
+ SignalReference ref = i->first;
+ SignalProgram *prog = i->second;
+
+ prog->DebugPrintProgram();
+
+ WriteVLI(b, prog->tile);
+ WriteVLI(b, prog->track);
+ WriteVLI(b, prog->instructions.Length());
+ for(SignalInstruction **j = prog->instructions.Begin(), **je = prog->instructions.End();
+ j != je; ++j) {
+ SignalInstruction *insn = *j;
+ WriteVLI(b, insn->Opcode());
+ if(insn->Opcode() != PSO_FIRST)
+ WriteVLI(b, insn->Previous()->Id());
+ switch(insn->Opcode()) {
+ case PSO_FIRST: {
+ SignalSpecial *s = static_cast(insn);
+ WriteVLI(b, s->next->Id());
+ break;
+ }
+
+ case PSO_LAST: break;
+
+ case PSO_IF: {
+ SignalIf *i = static_cast(insn);
+ WriteCondition(b, i->condition);
+ WriteVLI(b, i->if_true->Id());
+ WriteVLI(b, i->if_false->Id());
+ WriteVLI(b, i->after->Id());
+ break;
+ }
+
+ case PSO_IF_ELSE:
+ case PSO_IF_ENDIF: {
+ SignalIf::PseudoInstruction *p = static_cast(insn);
+ WriteVLI(b, p->block->Id());
+ break;
+ }
+
+ case PSO_SET_SIGNAL: {
+ SignalSet *s = static_cast(insn);
+ WriteVLI(b, s->next->Id());
+ WriteVLI(b, s->to_state ? 1 : 0);
+ break;
+ }
+
+ default: NOT_REACHED();
+ }
+ }
+ }
+
+ uint size = b.size();
+ SlSetLength(size);
+ for(uint i = 0; i < size; i++)
+ SlWriteByte(b[i]); // TODO Gotta be a better way
+}
+
+// We don't know the pointer values that need to be stored in various
+// instruction fields at load time, so we need to instead store the IDs and
+// then fix them up once all of the instructions have been loaded.
+//
+// Additionally, we store the opcode type we expect (if we expect a specific one)
+// to check for consistency (For example, an If Pseudo Instruction's block should
+// point at an If!)
+struct Fixup {
+ Fixup(SignalInstruction **p, SignalOpcode type)
+ : type(type), ptr(p)
+ {}
+
+ SignalOpcode type;
+ SignalInstruction **ptr;
+};
+
+typedef SmallVector FixupList;
+
+template
+static void MakeFixup(FixupList &l, T *&ir, uint id, SignalOpcode op = PSO_INVALID)
+{
+ ir = reinterpret_cast(id);
+ new(l.Append()) Fixup(reinterpret_cast(&ir), op);
+}
+
+static void DoFixups(FixupList &l, InstructionList &il)
+{
+ for(Fixup *i = l.Begin(), *e = l.End(); i != e; ++i) {
+ uint id = reinterpret_cast(*i->ptr);
+ if(id >= il.Length())
+ NOT_REACHED();
+
+ *i->ptr = il[id];
+
+ if(i->type != PSO_INVALID && (*i->ptr)->Opcode() != i->type) {
+ DEBUG(sl, 0, "Expected Id %d to be %d, but was in fact %d", id, i->type, (*i->ptr)->Opcode());
+ NOT_REACHED();
+ }
+ }
+}
+
+static void Load_SPRG()
+{
+ uint count = ReadVLI();
+ for(uint i = 0; i < count; i++) {
+ FixupList l;
+ TileIndex tile = ReadVLI();
+ Track track = (Track) ReadVLI();
+ uint instructions = ReadVLI();
+ SignalReference ref(tile, track);
+
+ SignalProgram *sp = new SignalProgram(tile, track, true);
+ _signal_programs[ref] = sp;
+
+ for(uint j = 0; j < instructions; j++) {
+ SignalOpcode op = (SignalOpcode) ReadVLI();
+ switch(op) {
+ case PSO_FIRST: {
+ sp->first_instruction = new SignalSpecial(sp, PSO_FIRST);
+ sp->first_instruction->GetPrevHandle() = NULL;
+ MakeFixup(l, sp->first_instruction->next, ReadVLI());
+ break;
+ }
+
+ case PSO_LAST: {
+ sp->last_instruction = new SignalSpecial(sp, PSO_LAST);
+ sp->last_instruction->next = NULL;
+ MakeFixup(l, sp->last_instruction->GetPrevHandle(), ReadVLI());
+ break;
+ }
+
+ case PSO_IF: {
+ SignalIf *i = new SignalIf(sp, true);
+ MakeFixup(l, i->GetPrevHandle(), ReadVLI());
+ i->condition = ReadCondition(ref);
+ MakeFixup(l, i->if_true, ReadVLI());
+ MakeFixup(l, i->if_false, ReadVLI());
+ MakeFixup(l, i->after, ReadVLI());
+ break;
+ }
+
+ case PSO_IF_ELSE:
+ case PSO_IF_ENDIF: {
+ SignalIf::PseudoInstruction *p = new SignalIf::PseudoInstruction(sp, op);
+ MakeFixup(l, p->GetPrevHandle(), ReadVLI());
+ MakeFixup(l, p->block, ReadVLI(), PSO_IF);
+ break;
+ }
+
+ case PSO_SET_SIGNAL: {
+ SignalSet *s = new SignalSet(sp);
+ MakeFixup(l, s->GetPrevHandle(), ReadVLI());
+ MakeFixup(l, s->next, ReadVLI());
+ s->to_state = (SignalState) ReadVLI();
+ if(s->to_state > SIGNAL_STATE_MAX) NOT_REACHED();
+ break;
+ }
+
+ default: NOT_REACHED();
+ }
+ }
+
+ DoFixups(l, sp->instructions);
+ sp->DebugPrintProgram();
+ }
+}
+
+extern const ChunkHandler _signal_chunk_handlers[] = {
+ { 'SPRG', Save_SPRG, Load_SPRG, NULL, NULL, CH_RIFF | CH_LAST},
+};
Index: src/saveload/tracerestrict_sl.cpp
new file mode 100644
===================================================================
--- src/saveload/tracerestrict_sl.cpp (revision 0)
+++ src/saveload/tracerestrict_sl.cpp (working copy)
@@ -0,0 +1,111 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tracerestrict_sl.cpp Code handling saving and loading of trace restrict programs */
+
+#include "../stdafx.h"
+#include "../tracerestrict.h"
+#include "saveload.h"
+#include
+#include "saveload.h"
+
+static const SaveLoad _trace_restrict_mapping_desc[] = {
+ SLE_VAR(TraceRestrictMappingItem, program_id, SLE_UINT32),
+ SLE_END()
+};
+
+/**
+ * Load mappings
+ */
+static void Load_TRRM()
+{
+ int index;
+ while ((index = SlIterateArray()) != -1) {
+ TraceRestrictMappingItem &item = _tracerestrictprogram_mapping[index];
+ SlObject(&item, _trace_restrict_mapping_desc);
+ }
+}
+
+/**
+ * Save mappings
+ */
+static void Save_TRRM()
+{
+ for (TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.begin();
+ iter != _tracerestrictprogram_mapping.end(); ++iter) {
+ SlSetArrayIndex(iter->first);
+ SlObject(&(iter->second), _trace_restrict_mapping_desc);
+ }
+}
+
+/** program length save header struct */
+struct TraceRestrictProgramStub {
+ uint32 length;
+};
+
+static const SaveLoad _trace_restrict_program_stub_desc[] = {
+ SLE_VAR(TraceRestrictProgramStub, length, SLE_UINT32),
+ SLE_END()
+};
+
+/**
+ * Load program pool
+ */
+static void Load_TRRP()
+{
+ int index;
+ TraceRestrictProgramStub stub;
+ while ((index = SlIterateArray()) != -1) {
+ TraceRestrictProgram *prog = new (index) TraceRestrictProgram();
+ SlObject(&stub, _trace_restrict_program_stub_desc);
+ prog->items.resize(stub.length);
+ SlArray(&(prog->items[0]), stub.length, SLE_UINT32);
+ assert(prog->Validate().Succeeded());
+ }
+}
+
+/**
+ * Save a program, used by SlAutolength
+ */
+static void RealSave_TRRP(TraceRestrictProgram *prog)
+{
+ TraceRestrictProgramStub stub;
+ stub.length = prog->items.size();
+ SlObject(&stub, _trace_restrict_program_stub_desc);
+ SlArray(&(prog->items[0]), stub.length, SLE_UINT32);
+}
+
+/**
+ * Save program pool
+ */
+static void Save_TRRP()
+{
+ TraceRestrictProgram *prog;
+
+ FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog) {
+ SlSetArrayIndex(prog->index);
+ SlAutolength((AutolengthProc*) RealSave_TRRP, prog);
+ }
+}
+
+/**
+ * Update program reference counts from just-loaded mapping
+ */
+void AfterLoadTraceRestrict()
+{
+ for (TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.begin();
+ iter != _tracerestrictprogram_mapping.end(); ++iter) {
+ _tracerestrictprogram_pool.Get(iter->second.program_id)->IncrementRefCount();
+ }
+}
+
+extern const ChunkHandler _trace_restrict_chunk_handlers[] = {
+ { 'TRRM', Save_TRRM, Load_TRRM, NULL, NULL, CH_SPARSE_ARRAY}, // Trace Restrict Mapping chunk
+ { 'TRRP', Save_TRRP, Load_TRRP, NULL, NULL, CH_ARRAY | CH_LAST}, // Trace Restrict Mapping Program Pool chunk
+};
Index: src/saveload/vehicle_sl.cpp
===================================================================
--- src/saveload/vehicle_sl.cpp (revision 27386)
+++ src/saveload/vehicle_sl.cpp (working copy)
@@ -368,6 +368,24 @@
v->SetServiceIntervalIsPercent(c->settings.vehicle.servint_ispercent);
}
}
+
+ if (SlXvIsFeatureMissing(XSLFI_VEHICLE_REPAIR_COST)) {
+ /* repair cost is value for new vehicles and each week +/256 part for old */
+ FOR_ALL_VEHICLES(v) {
+ if (!v->IsPrimaryVehicle()) continue;
+
+ v->repair_cost = v->value;
+ for (int w = 0; w < v->age / 7; w++, v->repair_cost += v->repair_cost >> 8);
+ //DEBUG(misc,0, "eid#%d, value=%lld, weeks=%d/%d, repair cost=%lld",
+ // v->engine_type, (int64)v->value, v->age, v->max_age, (int64)v->repair_cost );
+
+ if (v->age > v->max_age) {
+ Date weeks = (v->age - v->max_age) / 7;
+ for (int w = 0; w < weeks; w++, v->repair_cost += v->repair_cost >> 8);
+ //DEBUG(misc,0, "OLD: value=%lld, weeks=%d, repair cost=%lld", (int64)v->value, weeks, (int64)v->repair_cost );
+ }
+ }
+ }
}
CheckValidVehicles();
@@ -375,6 +393,8 @@
FOR_ALL_VEHICLES(v) {
assert(v->first != NULL);
+ v->trip_occupancy = CalcPercentVehicleFilled(v, NULL);
+
switch (v->type) {
case VEH_TRAIN: {
Train *t = Train::From(v);
@@ -671,6 +691,8 @@
SLE_VAR(Vehicle, breakdown_delay, SLE_UINT8),
SLE_VAR(Vehicle, breakdowns_since_last_service, SLE_UINT8),
SLE_VAR(Vehicle, breakdown_chance, SLE_UINT8),
+ SLE_CONDVAR_X(Vehicle, breakdown_type, SLE_UINT8, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_IMPROVED_BREAKDOWNS)),
+ SLE_CONDVAR_X(Vehicle, breakdown_severity, SLE_UINT8, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_IMPROVED_BREAKDOWNS)),
SLE_CONDVAR(Vehicle, build_year, SLE_FILE_U8 | SLE_VAR_I32, 0, 30),
SLE_CONDVAR(Vehicle, build_year, SLE_INT32, 31, SL_MAX_VERSION),
@@ -688,10 +710,14 @@
SLEG_CONDVAR( _cargo_loaded_at_xy, SLE_UINT32, 51, 67),
SLE_CONDVAR(Vehicle, value, SLE_FILE_I32 | SLE_VAR_I64, 0, 64),
SLE_CONDVAR(Vehicle, value, SLE_INT64, 65, SL_MAX_VERSION),
+ SLE_CONDVAR_X(Vehicle, repair_cost, SLE_INT64, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_VEHICLE_REPAIR_COST)),
SLE_CONDVAR(Vehicle, random_bits, SLE_UINT8, 2, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, waiting_triggers, SLE_UINT8, 2, SL_MAX_VERSION),
+ SLE_CONDREF_X(Vehicle, ahead_separation, REF_VEHICLE, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)),
+ SLE_CONDREF_X(Vehicle, behind_separation, REF_VEHICLE, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)),
+
SLE_CONDREF(Vehicle, next_shared, REF_VEHICLE, 2, SL_MAX_VERSION),
SLE_CONDNULL(2, 2, 68),
SLE_CONDNULL(4, 69, 100),
@@ -699,6 +725,7 @@
SLE_CONDVAR(Vehicle, group_id, SLE_UINT16, 60, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, current_order_time, SLE_UINT32, 67, SL_MAX_VERSION),
+ SLE_CONDVAR_X(Vehicle, current_loading_time, SLE_UINT32, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)),
SLE_CONDVAR(Vehicle, lateness_counter, SLE_INT32, 67, SL_MAX_VERSION),
SLE_CONDNULL(10, 2, 143), // old reserved space
Index: src/screenshot.cpp
===================================================================
--- src/screenshot.cpp (revision 27386)
+++ src/screenshot.cpp (working copy)
@@ -25,6 +25,7 @@
#include "window_func.h"
#include "tile_map.h"
#include "landscape.h"
+#include "smallmap_gui.h"
#include "table/strings.h"
@@ -811,6 +812,20 @@
}
/**
+ * Show a a success or failure message indicating the result of a screenshot action
+ * @param ret whether the screenshot action was successful
+ */
+static void ShowScreenshotResultMessage(bool ret)
+{
+ if (ret) {
+ SetDParamStr(0, _screenshot_name);
+ ShowErrorMessage(STR_MESSAGE_SCREENSHOT_SUCCESSFULLY, INVALID_STRING_ID, WL_WARNING);
+ } else {
+ ShowErrorMessage(STR_ERROR_SCREENSHOT_FAILED, INVALID_STRING_ID, WL_ERROR);
+ }
+}
+
+/**
* Make an actual screenshot.
* @param t the type of screenshot to make.
* @param name the name to give to the screenshot.
@@ -856,12 +871,37 @@
NOT_REACHED();
}
- if (ret) {
- SetDParamStr(0, _screenshot_name);
- ShowErrorMessage(STR_MESSAGE_SCREENSHOT_SUCCESSFULLY, INVALID_STRING_ID, WL_WARNING);
- } else {
- ShowErrorMessage(STR_ERROR_SCREENSHOT_FAILED, INVALID_STRING_ID, WL_ERROR);
- }
+ ShowScreenshotResultMessage(ret);
return ret;
}
+
+/**
+ * Callback for generating a smallmap screenshot.
+ * @param userdata SmallMapWindow window pointer
+ * @param buf Videobuffer with same bitdepth as current blitter
+ * @param y First line to render
+ * @param pitch Pitch of the videobuffer
+ * @param n Number of lines to render
+ */
+static void SmallMapCallback(void *userdata, void *buf, uint y, uint pitch, uint n)
+{
+ SmallMapWindow *window = static_cast(userdata);
+ window->ScreenshotCallbackHandler(buf, y, pitch, n);
+}
+
+/**
+ * Make a screenshot of the smallmap
+ * @param width the width of the screenshot
+ * @param height the height of the screenshot
+ * @param window a pointer to the smallmap window to use, the current mode and zoom status of the window is used for the screenshot
+ * @return true iff the screenshot was made successfully
+ */
+bool MakeSmallMapScreenshot(unsigned int width, unsigned int height, SmallMapWindow *window)
+{
+ _screenshot_name[0] = '\0';
+ const ScreenshotFormat *sf = _screenshot_formats + _cur_screenshot_format;
+ bool ret = sf->proc(MakeScreenshotName(SCREENSHOT_NAME, sf->extension), SmallMapCallback, window, width, height, BlitterFactory::GetCurrentBlitter()->GetScreenDepth(), _cur_palette.palette);
+ ShowScreenshotResultMessage(ret);
+ return ret;
+}
Index: src/screenshot.h
===================================================================
--- src/screenshot.h (revision 27386)
+++ src/screenshot.h (working copy)
@@ -26,8 +26,11 @@
SC_HEIGHTMAP, ///< Heightmap of the world.
};
+class SmallMapWindow;
+
void SetupScreenshotViewport(ScreenshotType t, struct ViewPort *vp);
bool MakeHeightmapScreenshot(const char *filename);
+bool MakeSmallMapScreenshot(unsigned int width, unsigned int height, SmallMapWindow *window);
bool MakeScreenshot(ScreenshotType t, const char *name);
extern char _screenshot_format_name[8];
Index: src/script/api/game/game_window.hpp.sq
===================================================================
--- src/script/api/game/game_window.hpp.sq (revision 27386)
+++ src/script/api/game/game_window.hpp.sq (working copy)
@@ -68,6 +68,7 @@
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_COMPANY_COLOUR, "WC_COMPANY_COLOUR");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_COMPANY_MANAGER_FACE, "WC_COMPANY_MANAGER_FACE");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_SELECT_STATION, "WC_SELECT_STATION");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WC_SELECT_TOWN, "WC_SELECT_TOWN");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_NEWS_WINDOW, "WC_NEWS_WINDOW");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_TOWN_DIRECTORY, "WC_TOWN_DIRECTORY");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_SUBSIDIES_LIST, "WC_SUBSIDIES_LIST");
@@ -90,6 +91,7 @@
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_INDUSTRY_VIEW, "WC_INDUSTRY_VIEW");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_COMPANY, "WC_COMPANY");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_OBJECT, "WC_BUILD_OBJECT");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_HOUSE, "WC_BUILD_HOUSE");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_VEHICLE, "WC_BUILD_VEHICLE");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_BRIDGE, "WC_BUILD_BRIDGE");
SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_STATION, "WC_BUILD_STATION");
@@ -1139,6 +1141,7 @@
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_ROCKS, "WID_ETT_PLACE_ROCKS");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_DESERT, "WID_ETT_PLACE_DESERT");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_OBJECT, "WID_ETT_PLACE_OBJECT");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_HOUSE, "WID_ETT_PLACE_HOUSE");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_BUTTONS_END, "WID_ETT_BUTTONS_END");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_INCREASE_SIZE, "WID_ETT_INCREASE_SIZE");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_DECREASE_SIZE, "WID_ETT_DECREASE_SIZE");
@@ -1249,6 +1252,26 @@
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TF_LAYOUT_GRID2, "WID_TF_LAYOUT_GRID2");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TF_LAYOUT_GRID3, "WID_TF_LAYOUT_GRID3");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TF_LAYOUT_RANDOM, "WID_TF_LAYOUT_RANDOM");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_CAPTION, "WID_HP_CAPTION");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_MAIN_PANEL_SEL, "WID_HP_MAIN_PANEL_SEL");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SETS_SEL, "WID_HP_HOUSE_SETS_SEL");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SETS, "WID_HP_HOUSE_SETS");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SELECT_MATRIX, "WID_HP_HOUSE_SELECT_MATRIX");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SELECT_SCROLL, "WID_HP_HOUSE_SELECT_SCROLL");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SELECT, "WID_HP_HOUSE_SELECT");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_PREVIEW, "WID_HP_HOUSE_PREVIEW");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_NAME, "WID_HP_HOUSE_NAME");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HISTORICAL_BUILDING, "WID_HP_HISTORICAL_BUILDING");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_POPULATION, "WID_HP_HOUSE_POPULATION");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_ZONES, "WID_HP_HOUSE_ZONES");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_LANDSCAPE, "WID_HP_HOUSE_LANDSCAPE");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_LANDSCAPE_SEL, "WID_HP_HOUSE_LANDSCAPE_SEL");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_YEARS, "WID_HP_HOUSE_YEARS");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_ACCEPTANCE, "WID_HP_HOUSE_ACCEPTANCE");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SUPPLY, "WID_HP_HOUSE_SUPPLY");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ST_CAPTION, "WID_ST_CAPTION");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ST_PANEL, "WID_ST_PANEL");
+ SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ST_SCROLLBAR, "WID_ST_SCROLLBAR");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TT_BEGIN, "WID_TT_BEGIN");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TT_SIGNS, "WID_TT_SIGNS");
SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TT_TREES, "WID_TT_TREES");
Index: src/script/api/script_object.cpp
===================================================================
--- src/script/api/script_object.cpp (revision 27386)
+++ src/script/api/script_object.cpp (working copy)
@@ -307,7 +307,7 @@
#endif
/* Try to perform the command. */
- CommandCost res = ::DoCommandPInternal(tile, p1, p2, cmd, (_networking && !_generating_world) ? ScriptObject::GetActiveInstance()->GetDoCommandCallback() : NULL, text, false, estimate_only);
+ CommandCost res = ::DoCommandPInternal(tile, p1, p2, cmd, (_networking && !_generating_world) ? ScriptObject::GetActiveInstance()->GetDoCommandCallback() : NULL, text, false, estimate_only, 0);
/* We failed; set the error and bail out */
if (res.Failed()) {
Index: src/script/api/script_window.hpp
===================================================================
--- src/script/api/script_window.hpp (revision 27386)
+++ src/script/api/script_window.hpp (working copy)
@@ -320,6 +320,12 @@
WC_SELECT_STATION = ::WC_SELECT_STATION,
/**
+ * Select town (when placing a house); %Window numbers:
+ * - 0 = #SelectTownWidgets
+ */
+ WC_SELECT_TOWN = ::WC_SELECT_TOWN,
+
+ /**
* News window; %Window numbers:
* - 0 = #NewsWidgets
*/
@@ -454,6 +460,12 @@
WC_BUILD_OBJECT = ::WC_BUILD_OBJECT,
/**
+ * Build house; %Window numbers:
+ * - 0 = #BuildHouseWidgets
+ */
+ WC_BUILD_HOUSE = ::WC_BUILD_HOUSE,
+
+ /**
* Build vehicle; %Window numbers:
* - #VehicleType = #BuildVehicleWidgets
* - #TileIndex = #BuildVehicleWidgets
@@ -2054,6 +2066,7 @@
WID_BS_ELECTRIC_PBS = ::WID_BS_ELECTRIC_PBS, ///< Build an electric path signal.
WID_BS_ELECTRIC_PBS_OWAY = ::WID_BS_ELECTRIC_PBS_OWAY, ///< Build an electric one way path signal.
WID_BS_CONVERT = ::WID_BS_CONVERT, ///< Convert the signal.
+ WID_BS_TRACE_RESTRICT = ::WID_BS_TRACE_RESTRICT, ///< Open trace restrict window.
WID_BS_DRAG_SIGNALS_DENSITY_LABEL = ::WID_BS_DRAG_SIGNALS_DENSITY_LABEL, ///< The current signal density.
WID_BS_DRAG_SIGNALS_DENSITY_DECREASE = ::WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, ///< Decrease the signal density.
WID_BS_DRAG_SIGNALS_DENSITY_INCREASE = ::WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, ///< Increase the signal density.
@@ -2330,6 +2343,7 @@
WID_ETT_PLACE_ROCKS = ::WID_ETT_PLACE_ROCKS, ///< Place rocks button.
WID_ETT_PLACE_DESERT = ::WID_ETT_PLACE_DESERT, ///< Place desert button (in tropical climate).
WID_ETT_PLACE_OBJECT = ::WID_ETT_PLACE_OBJECT, ///< Place transmitter button.
+ WID_ETT_PLACE_HOUSE = ::WID_ETT_PLACE_HOUSE, ///< Place house button.
WID_ETT_BUTTONS_END = ::WID_ETT_BUTTONS_END, ///< End of pushable buttons.
WID_ETT_INCREASE_SIZE = ::WID_ETT_INCREASE_SIZE, ///< Upwards arrow button to increase terraforming size.
WID_ETT_DECREASE_SIZE = ::WID_ETT_DECREASE_SIZE, ///< Downwards arrow button to decrease terraforming size.
@@ -2474,6 +2488,34 @@
WID_TF_LAYOUT_RANDOM = ::WID_TF_LAYOUT_RANDOM, ///< Selection for a randomly chosen town layout.
};
+ /** Widgets of the #HousePickerWindow class. */
+ enum HousePickerWidgets {
+ WID_HP_CAPTION = ::WID_HP_CAPTION,
+ WID_HP_MAIN_PANEL_SEL = ::WID_HP_MAIN_PANEL_SEL, ///< Selection widget to show/hide the main panel.
+ WID_HP_HOUSE_SETS_SEL = ::WID_HP_HOUSE_SETS_SEL, ///< Selection widget to show/hide the list of house sets.
+ WID_HP_HOUSE_SETS = ::WID_HP_HOUSE_SETS, ///< List of available house sets.
+ WID_HP_HOUSE_SELECT_MATRIX = ::WID_HP_HOUSE_SELECT_MATRIX, ///< Matrix with houses to select.
+ WID_HP_HOUSE_SELECT_SCROLL = ::WID_HP_HOUSE_SELECT_SCROLL, ///< Scrollbar associated with the house matrix.
+ WID_HP_HOUSE_SELECT = ::WID_HP_HOUSE_SELECT, ///< Panels with house images in the house matrix.
+ WID_HP_HOUSE_PREVIEW = ::WID_HP_HOUSE_PREVIEW, ///< House preview panel.
+ WID_HP_HOUSE_NAME = ::WID_HP_HOUSE_NAME, ///< House name display.
+ WID_HP_HISTORICAL_BUILDING = ::WID_HP_HISTORICAL_BUILDING, ///< "Historical building" label.
+ WID_HP_HOUSE_POPULATION = ::WID_HP_HOUSE_POPULATION, ///< House population display.
+ WID_HP_HOUSE_ZONES = ::WID_HP_HOUSE_ZONES, ///< House zones display.
+ WID_HP_HOUSE_LANDSCAPE = ::WID_HP_HOUSE_LANDSCAPE, ///< Information about house availability against the landscape.
+ WID_HP_HOUSE_LANDSCAPE_SEL = ::WID_HP_HOUSE_LANDSCAPE_SEL, ///< Selection widget to show/hide the landscape info.
+ WID_HP_HOUSE_YEARS = ::WID_HP_HOUSE_YEARS, ///< Years display.
+ WID_HP_HOUSE_ACCEPTANCE = ::WID_HP_HOUSE_ACCEPTANCE, ///< Cargo accepted.
+ WID_HP_HOUSE_SUPPLY = ::WID_HP_HOUSE_SUPPLY, ///< Cargo supplied.
+ };
+
+ /** Widgets of the #SelectTownWindow class. */
+ enum SelectTownWidgets {
+ WID_ST_CAPTION = ::WID_ST_CAPTION, ///< Caption of the window.
+ WID_ST_PANEL = ::WID_ST_PANEL, ///< Main panel.
+ WID_ST_SCROLLBAR = ::WID_ST_SCROLLBAR, ///< Scrollbar of the panel.
+ };
+
/* automatically generated from ../../widgets/transparency_widget.h */
/** Widgets of the #TransparenciesWindow class. */
enum TransparencyToolbarWidgets {
Index: src/script/api/template/template_window.hpp.sq
===================================================================
--- src/script/api/template/template_window.hpp.sq (revision 27386)
+++ src/script/api/template/template_window.hpp.sq (working copy)
@@ -233,6 +233,10 @@
template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::TownViewWidgets res) { sq_pushinteger(vm, (int32)res); return 1; }
template <> inline ScriptWindow::TownFoundingWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::TownFoundingWidgets)tmp; }
template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::TownFoundingWidgets res) { sq_pushinteger(vm, (int32)res); return 1; }
+ template <> inline ScriptWindow::HousePickerWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::HousePickerWidgets)tmp; }
+ template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::HousePickerWidgets res) { sq_pushinteger(vm, (int32)res); return 1; }
+ template <> inline ScriptWindow::SelectTownWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::SelectTownWidgets)tmp; }
+ template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::SelectTownWidgets res) { sq_pushinteger(vm, (int32)res); return 1; }
template <> inline ScriptWindow::TransparencyToolbarWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::TransparencyToolbarWidgets)tmp; }
template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::TransparencyToolbarWidgets res) { sq_pushinteger(vm, (int32)res); return 1; }
template <> inline ScriptWindow::BuildTreesWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::BuildTreesWidgets)tmp; }
Index: src/settings.cpp
===================================================================
--- src/settings.cpp (revision 27386)
+++ src/settings.cpp (working copy)
@@ -64,13 +64,19 @@
#include "roadveh.h"
#include "fios.h"
#include "strings_func.h"
+#include "string_func.h"
+#include "debug.h"
#include "void_map.h"
#include "station_base.h"
+#include "infrastructure_func.h"
#include "table/strings.h"
#include "table/settings.h"
+#include
+#include
+
#include "safeguards.h"
ClientSettings _settings_client;
@@ -486,7 +492,7 @@
const SettingDescBase *sdb = &sd->desc;
const SaveLoad *sld = &sd->save;
- if (!SlIsObjectCurrentlyValid(sld->version_from, sld->version_to)) continue;
+ if (!SlIsObjectCurrentlyValid(sld->version_from, sld->version_to, sld->ext_feature_test)) continue;
/* For settings.xx.yy load the settings from [xx] yy = ? */
s = strchr(sdb->name, '.');
@@ -585,7 +591,7 @@
/* If the setting is not saved to the configuration
* file, just continue with the next setting */
- if (!SlIsObjectCurrentlyValid(sld->version_from, sld->version_to)) continue;
+ if (!SlIsObjectCurrentlyValid(sld->version_from, sld->version_to, sld->ext_feature_test)) continue;
if (sld->conv & SLF_NOT_IN_CONFIG) continue;
/* XXX - wtf is this?? (group override?) */
@@ -1304,6 +1310,28 @@
return true;
}
+static bool CheckSharingRail(int32 p1)
+{
+ if (!CheckSharingChangePossible(VEH_TRAIN)) return false;
+ UpdateAllBlockSignals();
+ return true;
+}
+
+static bool CheckSharingRoad(int32 p1)
+{
+ return CheckSharingChangePossible(VEH_ROAD);
+}
+
+static bool CheckSharingWater(int32 p1)
+{
+ return CheckSharingChangePossible(VEH_SHIP);
+}
+
+static bool CheckSharingAir(int32 p1)
+{
+ return CheckSharingChangePossible(VEH_AIRCRAFT);
+}
+
static bool MaxVehiclesChanged(int32 p1)
{
InvalidateWindowClassesData(WC_BUILD_TOOLBAR);
@@ -1311,7 +1339,6 @@
return true;
}
-
#ifdef ENABLE_NETWORK
static bool UpdateClientName(int32 p1)
@@ -1381,7 +1408,7 @@
for (uint i = 0; i < options_to_load; i++) {
const SettingDesc *sd = &_settings[i];
/* Skip deprecated options */
- if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
+ if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) continue;
void *var = GetVariableAddress(savegame ? &_settings_game : &_settings_newgame, &sd->save);
Write_ValidateSetting(var, sd, (int32)((i == 4 ? 1000 : 1) * _old_diff_custom[i]));
}
@@ -1839,7 +1866,7 @@
const SettingDesc *sd = GetSettingDescription(p1);
if (sd == NULL) return CMD_ERROR;
- if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) return CMD_ERROR;
+ if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) return CMD_ERROR;
if (!sd->IsEditable(true)) return CMD_ERROR;
@@ -1994,7 +2021,7 @@
const void *new_var = GetVariableAddress(&_settings_client.company, &sd->save);
uint32 old_value = (uint32)ReadValue(old_var, sd->save.conv);
uint32 new_value = (uint32)ReadValue(new_var, sd->save.conv);
- if (old_value != new_value) NetworkSendCommand(0, i, new_value, CMD_CHANGE_COMPANY_SETTING, NULL, NULL, _local_company);
+ if (old_value != new_value) NetworkSendCommand(0, i, new_value, CMD_CHANGE_COMPANY_SETTING, NULL, NULL, _local_company, 0);
}
}
#endif /* ENABLE_NETWORK */
@@ -2050,13 +2077,13 @@
/* First check all full names */
for (*i = 0, sd = _settings; sd->save.cmd != SL_END; sd++, (*i)++) {
- if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
+ if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) continue;
if (strcmp(sd->desc.name, name) == 0) return sd;
}
/* Then check the shortcut variant of the name. */
for (*i = 0, sd = _settings; sd->save.cmd != SL_END; sd++, (*i)++) {
- if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
+ if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) continue;
const char *short_name = strchr(sd->desc.name, '.');
if (short_name != NULL) {
short_name++;
@@ -2067,7 +2094,7 @@
if (strncmp(name, "company.", 8) == 0) name += 8;
/* And finally the company-based settings */
for (*i = 0, sd = _company_settings; sd->save.cmd != SL_END; sd++, (*i)++) {
- if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
+ if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) continue;
if (strcmp(sd->desc.name, name) == 0) return sd;
}
@@ -2161,7 +2188,7 @@
IConsolePrintF(CC_WARNING, "All settings with their current value:");
for (const SettingDesc *sd = _settings; sd->save.cmd != SL_END; sd++) {
- if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
+ if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) continue;
if (prefilter != NULL && strstr(sd->desc.name, prefilter) == NULL) continue;
char value[80];
const void *ptr = GetVariableAddress(&GetGameSettings(), &sd->save);
@@ -2180,7 +2207,7 @@
}
/**
- * Save and load handler for settings
+ * Save and load handler for settings, except for those which go in the PATX chunk
* @param osd SettingDesc struct containing all information
* @param object can be either NULL in which case we load global variables or
* a pointer to a struct which is getting saved
@@ -2188,6 +2215,7 @@
static void LoadSettings(const SettingDesc *osd, void *object)
{
for (; osd->save.cmd != SL_END; osd++) {
+ if (osd->patx_name != NULL) continue;
const SaveLoad *sld = &osd->save;
void *ptr = GetVariableAddress(object, sld);
@@ -2197,7 +2225,7 @@
}
/**
- * Save and load handler for settings
+ * Save and load handler for settings, except for those which go in the PATX chunk
* @param sd SettingDesc struct containing all information
* @param object can be either NULL in which case we load global variables or
* a pointer to a struct which is getting saved
@@ -2209,16 +2237,195 @@
const SettingDesc *i;
size_t length = 0;
for (i = sd; i->save.cmd != SL_END; i++) {
+ if (i->patx_name != NULL) continue;
length += SlCalcObjMemberLength(object, &i->save);
}
SlSetLength(length);
for (i = sd; i->save.cmd != SL_END; i++) {
+ if (i->patx_name != NULL) continue;
void *ptr = GetVariableAddress(object, &i->save);
SlObjectMember(ptr, &i->save);
}
}
+/** @file
+ *
+ * The PATX chunk stores additional settings in an unordered format
+ * which is tolerant of extra, missing or reordered settings.
+ * Additional settings generally means those that aren't in trunk.
+ *
+ * The PATX chunk contents has the following format:
+ *
+ * uint32 chunk flags
+ * uint32 number of settings
+ * For each of N settings:
+ * uint32 setting flags
+ * SLE_STR setting name
+ * uint32 length of setting field
+ * N bytes setting field
+ */
+
+/** Sorted list of PATX settings, generated by MakeSettingsPatxList */
+static std::vector _sorted_patx_settings;
+
+/**
+ * Prepare a sorted list of settings to be potentially be loaded out of the PATX chunk
+ * This is to enable efficient lookup of settings by name
+ * This is stored in _sorted_patx_settings
+ */
+static void MakeSettingsPatxList(const SettingDesc *sd)
+{
+ static const SettingDesc *previous = NULL;
+
+ if (sd == previous) return;
+
+ _sorted_patx_settings.clear();
+ for (const SettingDesc *desc = sd; desc->save.cmd != SL_END; desc++) {
+ if (desc->patx_name == NULL) continue;
+ _sorted_patx_settings.push_back(desc);
+ }
+
+ // this makes me miss lambdas :/
+ struct StringSorter {
+ bool operator()(const SettingDesc *a, const SettingDesc *b)
+ {
+ return strcmp(a->patx_name, b->patx_name) < 0;
+ }
+ };
+ std::sort(_sorted_patx_settings.begin(), _sorted_patx_settings.end(), StringSorter());
+}
+
+/**
+ * Load handler for settings which go in the PATX chunk
+ * @param osd SettingDesc struct containing all information
+ * @param object can be either NULL in which case we load global variables or
+ * a pointer to a struct which is getting saved
+ */
+static void LoadSettingsPatx(const SettingDesc *sd, void *object)
+{
+ MakeSettingsPatxList(sd);
+
+ struct SettingsPatxLoad {
+ uint32 flags;
+ char name[256];
+ uint32 setting_length;
+ };
+ SettingsPatxLoad current_setting;
+
+ static const SaveLoad _settings_patx_desc[] = {
+ SLE_VAR(SettingsPatxLoad, flags, SLE_UINT32),
+ SLE_STR(SettingsPatxLoad, name, SLE_STRB, 256),
+ SLE_VAR(SettingsPatxLoad, setting_length, SLE_UINT32),
+ SLE_END()
+ };
+
+ uint32 flags = SlReadUint32();
+ // flags are not in use yet, reserve for future expansion
+ if (flags != 0) SlErrorCorruptFmt("PATX chunk: unknown chunk header flags: 0x%X", flags);
+
+ uint32 settings_count = SlReadUint32();
+ for (uint32 i = 0; i < settings_count; i++) {
+ SlObject(¤t_setting, _settings_patx_desc);
+
+ // flags are not in use yet, reserve for future expansion
+ if (current_setting.flags != 0) SlErrorCorruptFmt("PATX chunk: unknown setting header flags: 0x%X", current_setting.flags);
+
+ // now try to find corresponding setting, this would be much easier with C++11 support...
+ bool exact_match = false;
+ struct StringSearcher {
+ bool &m_exact_match;
+
+ StringSearcher(bool &exact_match)
+ : m_exact_match(exact_match) { }
+
+ bool operator()(const SettingDesc *a, const char *b)
+ {
+ int result = strcmp(a->patx_name, b);
+ if (result == 0) m_exact_match = true;
+ return result < 0;
+ }
+ };
+ std::vector::iterator iter = std::lower_bound(_sorted_patx_settings.begin(), _sorted_patx_settings.end(), current_setting.name, StringSearcher(exact_match));
+
+ if (exact_match) {
+ assert(iter != _sorted_patx_settings.end());
+ // found setting
+ const SaveLoad *sld = &((*iter)->save);
+ size_t read = SlGetBytesRead();
+ void *ptr = GetVariableAddress(object, sld);
+ SlObjectMember(ptr, sld);
+ if (SlGetBytesRead() != read + current_setting.setting_length) {
+ SlErrorCorruptFmt("PATX chunk: setting read length mismatch for setting: '%s'", current_setting.name);
+ }
+ if (IsNumericType(sld->conv)) Write_ValidateSetting(ptr, *iter, ReadValue(ptr, sld->conv));
+ } else {
+ DEBUG(sl, 1, "PATX chunk: Could not find setting: '%s', ignoring", current_setting.name);
+ SlSkipBytes(current_setting.setting_length);
+ }
+ }
+}
+
+/**
+ * Save handler for settings which go in the PATX chunk
+ * @param sd SettingDesc struct containing all information
+ * @param object can be either NULL in which case we load global variables or
+ * a pointer to a struct which is getting saved
+ */
+static void SaveSettingsPatx(const SettingDesc *sd, void *object)
+{
+ struct SettingsPatxSave {
+ uint32 flags;
+ const char *name;
+ uint32 setting_length;
+ };
+ SettingsPatxSave current_setting;
+
+ static const SaveLoad _settings_patx_desc[] = {
+ SLE_VAR(SettingsPatxSave, flags, SLE_UINT32),
+ SLE_STR(SettingsPatxSave, name, SLE_STR, 0),
+ SLE_VAR(SettingsPatxSave, setting_length, SLE_UINT32),
+ SLE_END()
+ };
+
+ struct SettingToAdd {
+ const SettingDesc *setting;
+ uint32 setting_length;
+ };
+ std::vector settings_to_add;
+
+ size_t length = 8;
+ for (const SettingDesc *desc = sd; desc->save.cmd != SL_END; desc++) {
+ if (desc->patx_name == NULL) continue;
+ uint32 setting_length = SlCalcObjMemberLength(object, &desc->save);
+ if (!setting_length) continue;
+
+ current_setting.name = desc->patx_name;
+
+ // add length of setting header
+ length += SlCalcObjLength(¤t_setting, _settings_patx_desc);
+
+ // add length of actual setting
+ length += setting_length;
+
+ settings_to_add.push_back({ desc, setting_length });
+ }
+ SlSetLength(length);
+
+ SlWriteUint32(0); // flags
+ SlWriteUint32(settings_to_add.size()); // settings count
+
+ for (size_t i = 0; i < settings_to_add.size(); i++) {
+ const SettingDesc *desc = settings_to_add[i].setting;
+ current_setting.flags = 0;
+ current_setting.name = desc->patx_name;
+ current_setting.setting_length = settings_to_add[i].setting_length;
+ SlObject(¤t_setting, _settings_patx_desc);
+ void *ptr = GetVariableAddress(object, &desc->save);
+ SlObjectMember(ptr, &desc->save);
+ }
+}
+
static void Load_OPTS()
{
/* Copy over default setting since some might not get loaded in
@@ -2247,6 +2454,21 @@
SaveSettings(_settings, &_settings_game);
}
+static void Load_PATX()
+{
+ LoadSettingsPatx(_settings, &_settings_game);
+}
+
+static void Check_PATX()
+{
+ LoadSettingsPatx(_settings, &_load_check_data.settings);
+}
+
+static void Save_PATX()
+{
+ SaveSettingsPatx(_settings, &_settings_game);
+}
+
void CheckConfig()
{
/*
@@ -2261,7 +2483,8 @@
extern const ChunkHandler _setting_chunk_handlers[] = {
{ 'OPTS', NULL, Load_OPTS, NULL, NULL, CH_RIFF},
- { 'PATS', Save_PATS, Load_PATS, NULL, Check_PATS, CH_RIFF | CH_LAST},
+ { 'PATS', Save_PATS, Load_PATS, NULL, Check_PATS, CH_RIFF},
+ { 'PATX', Save_PATX, Load_PATX, NULL, Check_PATX, CH_RIFF | CH_LAST},
};
static bool IsSignedVarMemType(VarType vt)
Index: src/settings_gui.cpp
===================================================================
--- src/settings_gui.cpp (revision 27386)
+++ src/settings_gui.cpp (working copy)
@@ -1256,7 +1256,6 @@
return biggest;
}
-
/**
* Draw a row in the settings panel.
*
@@ -1478,6 +1477,9 @@
graphics->Add(new SettingEntry("gui.zoom_max"));
graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
graphics->Add(new SettingEntry("gui.graph_line_thickness"));
+ graphics->Add(new SettingEntry("gui.show_vehicle_route_steps"));
+ graphics->Add(new SettingEntry("gui.show_vehicle_route"));
+ graphics->Add(new SettingEntry("gui.dash_level_of_route_lines"));
}
SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
@@ -1505,6 +1507,18 @@
SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
{
+ SettingsPage *viewport_map = interface->Add(new SettingsPage(STR_CONFIG_SETTING_VIEWPORT_MAP_OPTIONS));
+ {
+ viewport_map->Add(new SettingEntry("gui.default_viewport_map_mode"));
+ viewport_map->Add(new SettingEntry("gui.action_when_viewport_map_is_dblclicked"));
+ viewport_map->Add(new SettingEntry("gui.viewport_map_scan_surroundings"));
+ viewport_map->Add(new SettingEntry("gui.show_scrolling_viewport_on_map"));
+ viewport_map->Add(new SettingEntry("gui.show_slopes_on_viewport_map"));
+ viewport_map->Add(new SettingEntry("gui.show_bridges_on_map"));
+ viewport_map->Add(new SettingEntry("gui.show_tunnels_on_map"));
+ viewport_map->Add(new SettingEntry("gui.use_owner_colour_for_tunnelbridge"));
+ }
+
viewports->Add(new SettingEntry("gui.auto_scrolling"));
viewports->Add(new SettingEntry("gui.reverse_scroll"));
viewports->Add(new SettingEntry("gui.smooth_scroll"));
@@ -1529,6 +1543,7 @@
SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
{
construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
+ construction->Add(new SettingEntry("construction.simulated_wormhole_signals"));
construction->Add(new SettingEntry("gui.enable_signal_gui"));
construction->Add(new SettingEntry("gui.persistent_buildingtools"));
construction->Add(new SettingEntry("gui.quick_goto"));
@@ -1536,6 +1551,35 @@
construction->Add(new SettingEntry("gui.disable_unsuitable_building"));
}
+ SettingsPage *departureboards = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_DEPARTUREBOARDS));
+ {
+ departureboards->Add(new SettingEntry("gui.max_departures"));
+ departureboards->Add(new SettingEntry("gui.max_departure_time"));
+ departureboards->Add(new SettingEntry("gui.departure_calc_frequency"));
+ departureboards->Add(new SettingEntry("gui.departure_show_vehicle"));
+ departureboards->Add(new SettingEntry("gui.departure_show_group"));
+ departureboards->Add(new SettingEntry("gui.departure_show_company"));
+ departureboards->Add(new SettingEntry("gui.departure_show_vehicle_type"));
+ departureboards->Add(new SettingEntry("gui.departure_show_vehicle_color"));
+ departureboards->Add(new SettingEntry("gui.departure_larger_font"));
+ departureboards->Add(new SettingEntry("gui.departure_destination_type"));
+ departureboards->Add(new SettingEntry("gui.departure_show_both"));
+ departureboards->Add(new SettingEntry("gui.departure_only_passengers"));
+ departureboards->Add(new SettingEntry("gui.departure_smart_terminus"));
+ departureboards->Add(new SettingEntry("gui.departure_conditionals"));
+ departureboards->Add(new SettingEntry("gui.departure_show_all_stops"));
+ departureboards->Add(new SettingEntry("gui.departure_merge_identical"));
+ }
+
+ SettingsPage *wallclock = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_WALLCLOCK));
+ {
+ wallclock->Add(new SettingEntry("gui.time_in_minutes"));
+ wallclock->Add(new SettingEntry("gui.timetable_start_text_entry"));
+ wallclock->Add(new SettingEntry("gui.ticks_per_minute"));
+ wallclock->Add(new SettingEntry("gui.date_with_time"));
+ wallclock->Add(new SettingEntry("gui.clock_offset"));
+ }
+
interface->Add(new SettingEntry("gui.autosave"));
interface->Add(new SettingEntry("gui.toolbar_pos"));
interface->Add(new SettingEntry("gui.statusbar_pos"));
@@ -1581,6 +1625,8 @@
company->Add(new SettingEntry("company.engine_renew"));
company->Add(new SettingEntry("company.engine_renew_months"));
company->Add(new SettingEntry("company.engine_renew_money"));
+ company->Add(new SettingEntry("vehicle.pay_for_repair"));
+ company->Add(new SettingEntry("vehicle.repair_cost"));
company->Add(new SettingEntry("vehicle.servint_ispercent"));
company->Add(new SettingEntry("vehicle.servint_trains"));
company->Add(new SettingEntry("vehicle.servint_roadveh"));
@@ -1591,6 +1637,7 @@
SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
{
accounting->Add(new SettingEntry("economy.inflation"));
+ accounting->Add(new SettingEntry("economy.day_length_factor"));
accounting->Add(new SettingEntry("difficulty.initial_interest"));
accounting->Add(new SettingEntry("difficulty.max_loan"));
accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
@@ -1626,6 +1673,11 @@
vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
vehicles->Add(new SettingEntry("order.serviceathelipad"));
+ vehicles->Add(new SettingEntry("order.timetable_automated"));
+ vehicles->Add(new SettingEntry("order.timetable_separation"));
+ vehicles->Add(new SettingEntry("order.timetable_separation_rate"));
+ vehicles->Add(new SettingEntry("vehicle.adjacent_crossings"));
+ vehicles->Add(new SettingEntry("order.occupancy_smoothness"));
}
SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
@@ -1649,6 +1701,7 @@
limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
limitations->Add(new SettingEntry("vehicle.disable_elrails"));
+ limitations->Add(new SettingEntry("construction.maximum_signal_evaluations"));
}
SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
@@ -1656,6 +1709,7 @@
disasters->Add(new SettingEntry("difficulty.disasters"));
disasters->Add(new SettingEntry("difficulty.economy"));
disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
+ disasters->Add(new SettingEntry("vehicle.improved_breakdowns"));
disasters->Add(new SettingEntry("vehicle.plane_crashes"));
}
@@ -1695,6 +1749,7 @@
towns->Add(new SettingEntry("economy.allow_town_roads"));
towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
towns->Add(new SettingEntry("economy.found_town"));
+ towns->Add(new SettingEntry("economy.town_cargo_factor"));
}
SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
@@ -1738,6 +1793,19 @@
npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
}
+ SettingsPage *sharing = ai->Add(new SettingsPage(STR_CONFIG_SETTING_SHARING));
+ {
+ sharing->Add(new SettingEntry("economy.infrastructure_sharing[0]"));
+ sharing->Add(new SettingEntry("economy.infrastructure_sharing[1]"));
+ sharing->Add(new SettingEntry("economy.infrastructure_sharing[2]"));
+ sharing->Add(new SettingEntry("economy.infrastructure_sharing[3]"));
+ sharing->Add(new SettingEntry("economy.sharing_fee[0]"));
+ sharing->Add(new SettingEntry("economy.sharing_fee[1]"));
+ sharing->Add(new SettingEntry("economy.sharing_fee[2]"));
+ sharing->Add(new SettingEntry("economy.sharing_fee[3]"));
+ sharing->Add(new SettingEntry("economy.sharing_payment_in_debt"));
+ }
+
ai->Add(new SettingEntry("economy.give_money"));
ai->Add(new SettingEntry("economy.allow_shares"));
}
Index: src/settings_internal.h
===================================================================
--- src/settings_internal.h (revision 27386)
+++ src/settings_internal.h (working copy)
@@ -111,6 +111,7 @@
struct SettingDesc {
SettingDescBase desc; ///< Settings structure (going to configuration file)
SaveLoad save; ///< Internal structure (going to savegame, parts to config)
+ const char *patx_name; ///< Name to save/load setting from in PATX chunk, if NULL save/load from PATS chunk as normal
bool IsEditable(bool do_command = false) const;
SettingType GetType() const;
Index: src/settings_type.h
===================================================================
--- src/settings_type.h (revision 27386)
+++ src/settings_type.h (working copy)
@@ -111,12 +111,43 @@
uint8 right_mouse_btn_emulation; ///< should we emulate right mouse clicking?
uint8 scrollwheel_scrolling; ///< scrolling using the scroll wheel?
uint8 scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS?
+ bool viewport_map_scan_surroundings; ///< look for the most important tile in surroundings
+ bool show_slopes_on_viewport_map; ///< use slope orientation to render the ground
+ uint32 default_viewport_map_mode; ///< the mode to use by default when a viewport is in map mode, 0=owner, 1=industry, 2=vegetation
+ uint32 action_when_viewport_map_is_dblclicked; ///< what to do when a doubleclick occurs on the viewport map
+ uint32 show_scrolling_viewport_on_map; ///< when a no map viewport is scrolled, its location is marked on the other map viewports
+ bool show_bridges_on_map; ///< bridges are rendered on a viewport in map mode
+ bool show_tunnels_on_map; ///< tunnels are rendered on a viewport in map mode
+ uint32 show_vehicle_route; ///< show a vehicle's route when its orders/timetable window is focused
+ uint32 dash_level_of_route_lines; ///< the dash level passed to GfxDrawLine() (plain if 0)
+ bool use_owner_colour_for_tunnelbridge;///< bridges and tunnels are rendered with their owner's colour
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 drag_signals_density; ///< many signals density
@@ -136,6 +167,7 @@
byte missing_strings_threshold; ///< the number of missing strings before showing the warning
uint8 graph_line_thickness; ///< the thickness of the lines in the various graph guis
uint8 osk_activation; ///< Mouse gesture to trigger the OSK.
+ bool show_vehicle_route_steps; ///< when a window related to a specific vehicle is focused, show route steps
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.
@@ -311,6 +343,8 @@
bool freeform_edges; ///< allow terraforming the tiles at the map edges
uint8 extra_tree_placement; ///< (dis)allow building extra trees in-game
uint8 command_pause_level; ///< level/amount of commands that can't be executed while paused
+ uint16 maximum_signal_evaluations; ///< maximum number of programmable signals which may be evaluated in one pass
+ byte simulated_wormhole_signals; ///< simulate signals in tunnel
uint32 terraform_per_64k_frames; ///< how many tile heights may, over a long period, be terraformed per 65536 frames?
uint16 terraform_frame_burst; ///< how many tile heights may, over a short period, be terraformed?
@@ -438,7 +472,11 @@
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 timetable_automated; ///< whether to automatically manage timetables
+ bool timetable_separation; ///< whether to perform automatic separation based on timetable
+ uint8 timetable_separation_rate; ///< percentage of timetable separation change to apply
bool serviceathelipad; ///< service helicopters at helipads automatically (no need to send to depot)
+ uint8 occupancy_smoothness; ///< percentage smoothness of occupancy measurement changes
};
/** Settings related to vehicles. */
@@ -462,6 +500,10 @@
byte extend_vehicle_life; ///< extend vehicle life by this many years
byte road_side; ///< the side of the road vehicles drive on
uint8 plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal
+ bool adjacent_crossings; ///< enable closing of adjacent level crossings
+ bool improved_breakdowns; ///< different types, chances and serverities of breakdowns
+ bool pay_for_repair; ///< pay for repairing vehicle
+ uint8 repair_cost; ///< cost of repairing vehicle
};
/** Settings related to the economy. */
@@ -486,8 +528,13 @@
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)
+ bool infrastructure_sharing[4]; ///< enable infrastructure sharing for rail/road/water/air
+ uint sharing_fee[4]; ///< fees for infrastructure sharing for rail/road/water/air
+ bool sharing_payment_in_debt; ///< allow fee payment for companies with more loan than money (switch off to prevent MP exploits)
bool allow_town_level_crossings; ///< towns are allowed to build level crossings
+ int8 town_cargo_factor; ///< power-of-two multiplier for town (passenger, mail) generation. May be negative.
bool infrastructure_maintenance; ///< enable monthly maintenance fee for owner infrastructure
+ uint8 day_length_factor; ///< factor which the length of day is multiplied
};
struct LinkGraphSettings {
Index: src/ship_cmd.cpp
===================================================================
--- src/ship_cmd.cpp (revision 27386)
+++ src/ship_cmd.cpp (working copy)
@@ -31,6 +31,7 @@
#include "pathfinder/opf/opf_ship.h"
#include "engine_base.h"
#include "company_base.h"
+#include "infrastructure_func.h"
#include "tunnelbridge_map.h"
#include "zoom_func.h"
@@ -144,7 +145,7 @@
FOR_ALL_DEPOTS(depot) {
TileIndex tile = depot->xy;
- if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) {
+ if (IsShipDepotTile(tile) && IsInfraTileUsageAllowed(VEH_SHIP, v->owner, tile)) {
uint dist = DistanceManhattan(tile, v->tile);
if (dist < best_dist) {
best_dist = dist;
@@ -318,6 +319,13 @@
{
if (!v->IsChainInDepot()) return false;
+ if (v->current_order.IsWaitTimetabled()) {
+ v->HandleWaiting(false);
+ }
+ if (v->current_order.IsType(OT_WAITING)) {
+ return true;
+ }
+
/* We are leaving a depot, but have to go to the exact same one; re-enter */
if (v->current_order.IsType(OT_GOTO_DEPOT) &&
IsShipDepotTile(v->tile) && GetDepotIndex(v->tile) == v->current_order.GetDestination()) {
@@ -382,6 +390,18 @@
spd = min(v->cur_speed + 1, v->vcache.cached_max_speed);
spd = min(spd, v->current_order.GetMaxSpeed() * 2);
+ if(v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_POWER && v->cur_speed > (v->breakdown_severity * ShipVehInfo(v->engine_type)->max_speed) >> 8) {
+ if((v->tick_counter & 0x7) == 0 && v->cur_speed > 0) {
+ spd = v->cur_speed - 1;
+ } else {
+ spd = v->cur_speed;
+ }
+ }
+
+ if(v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_SPEED) {
+ spd = min(spd, v->breakdown_severity);
+ }
+
/* updates statusbar only if speed have changed to save CPU time */
if (spd != v->cur_speed) {
v->cur_speed = spd;
@@ -702,6 +722,7 @@
v->reliability = e->reliability;
v->reliability_spd_dec = e->reliability_spd_dec;
+ v->breakdown_chance = 64; // ships have a 50% lower breakdown chance than normal
v->max_age = e->GetLifeLengthInDays();
_new_vehicle_id = v->index;
Index: src/signal.cpp
===================================================================
--- src/signal.cpp (revision 27386)
+++ src/signal.cpp (working copy)
@@ -17,9 +17,23 @@
#include "viewport_func.h"
#include "train.h"
#include "company_base.h"
+#include "gui.h"
+#include "table/strings.h"
+#include "programmable_signals.h"
+#include "error.h"
+#include "infrastructure_func.h"
#include "safeguards.h"
+/// List of signals dependent upon this one
+typedef SmallVector SignalDependencyList;
+
+/// Map of dependencies. The key identifies the signal,
+/// the value is a list of all of the signals which depend upon that signal.
+typedef std::map SignalDependencyMap;
+
+static SignalDependencyMap _signal_dependencies;
+static void MarkDependencidesForUpdate(SignalReference sig);
/** these are the maximums used for updating signal blocks */
static const uint SIG_TBU_SIZE = 64; ///< number of signals entering to block
@@ -188,6 +202,7 @@
static SmallSet _tbdset("_tbdset"); ///< set of open nodes in current signal block
static SmallSet _globset("_globset"); ///< set of places to be updated in following runs
+static uint _num_signals_evaluated; ///< Number of programmable signals evaluated
/** Check whether there is a train on rail, not in a depot */
static Vehicle *TrainOnTileEnum(Vehicle *v, void *)
@@ -197,6 +212,14 @@
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
@@ -247,18 +270,25 @@
/** Current signal block state flags */
enum SigFlags {
- SF_NONE = 0,
- SF_TRAIN = 1 << 0, ///< train found in segment
- SF_EXIT = 1 << 1, ///< exitsignal found
- SF_EXIT2 = 1 << 2, ///< two or more exits found
- SF_GREEN = 1 << 3, ///< green exitsignal found
- SF_GREEN2 = 1 << 4, ///< two or more green exits found
- SF_FULL = 1 << 5, ///< some of buffers was full, do not continue
- SF_PBS = 1 << 6, ///< pbs signal found
+ SF_NONE = 0,
+ SF_TRAIN = 1 << 0, ///< train found in segment
+ SF_FULL = 1 << 1, ///< some of buffers was full, do not continue
+ SF_PBS = 1 << 2, ///< pbs signal found
};
DECLARE_ENUM_AS_BIT_SET(SigFlags)
+struct SigInfo {
+ inline SigInfo()
+ {
+ flags = SF_NONE;
+ num_exits = 0;
+ num_green = 0;
+ }
+ SigFlags flags;
+ uint num_exits;
+ uint num_green;
+};
/**
* Search signal block
@@ -266,9 +296,9 @@
* @param owner owner whose signals we are updating
* @return SigFlags
*/
-static SigFlags ExploreSegment(Owner owner)
+static SigInfo ExploreSegment(Owner owner)
{
- SigFlags flags = SF_NONE;
+ SigInfo info;
TileIndex tile;
DiagDirection enterdir;
@@ -279,17 +309,17 @@
switch (GetTileType(tile)) {
case MP_RAILWAY: {
- if (GetTileOwner(tile) != owner) continue; // do not propagate signals on others' tiles (remove for tracksharing)
+ if (!IsOneSignalBlock(owner, GetTileOwner(tile))) continue;
if (IsRailDepot(tile)) {
if (enterdir == INVALID_DIAGDIR) { // from 'inside' - train just entered or left the depot
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.flags |= SF_TRAIN;
exitdir = GetRailDepotDirection(tile);
tile += TileOffsByDiagDir(exitdir);
enterdir = ReverseDiagDir(exitdir);
break;
} else if (enterdir == GetRailDepotDirection(tile)) { // entered a depot
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.flags |= SF_TRAIN;
continue;
} else {
continue;
@@ -303,10 +333,10 @@
if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) { // there is exactly one incidating track, no need to check
tracks = tracks_masked;
/* If no train detected yet, and there is not no train -> there is a train -> set the flag */
- if (!(flags & SF_TRAIN) && EnsureNoTrainOnTrackBits(tile, tracks).Failed()) flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && EnsureNoTrainOnTrackBits(tile, tracks).Failed()) info.flags |= SF_TRAIN;
} else {
if (tracks_masked == TRACK_BIT_NONE) continue; // no incidating track
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.flags |= SF_TRAIN;
}
if (HasSignals(tile)) { // there is exactly one track - not zero, because there is exit from this tile
@@ -320,20 +350,19 @@
* (if it is a presignal EXIT and it changes, it will be added to 'to-be-done' set later) */
if (HasSignalOnTrackdir(tile, reversedir)) {
if (IsPbsSignal(sig)) {
- flags |= SF_PBS;
+ info.flags |= SF_PBS;
} else if (!_tbuset.Add(tile, reversedir)) {
- return flags | SF_FULL;
+ info.flags |= SF_FULL;
+ return info;
}
}
- if (HasSignalOnTrackdir(tile, trackdir) && !IsOnewaySignal(tile, track)) flags |= SF_PBS;
+ if (HasSignalOnTrackdir(tile, trackdir) && !IsOnewaySignal(tile, track)) info.flags |= SF_PBS;
- /* if it is a presignal EXIT in OUR direction and we haven't found 2 green exits yes, do special check */
- if (!(flags & SF_GREEN2) && IsPresignalExit(tile, track) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit
- if (flags & SF_EXIT) flags |= SF_EXIT2; // found two (or more) exits
- flags |= SF_EXIT; // found at least one exit - allow for compiler optimizations
+ /* if it is a presignal EXIT in OUR direction, count it */
+ if (IsPresignalExit(tile, track) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit
+ info.num_exits++;
if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { // found green presignal exit
- if (flags & SF_GREEN) flags |= SF_GREEN2;
- flags |= SF_GREEN;
+ info.num_green++;
}
}
@@ -345,7 +374,10 @@
if (dir != enterdir && (tracks & _enterdir_to_trackbits[dir])) { // any track incidating?
TileIndex newtile = tile + TileOffsByDiagDir(dir); // new tile to check
DiagDirection newdir = ReverseDiagDir(dir); // direction we are entering from
- if (!MaybeAddToTodoSet(newtile, newdir, tile, dir)) return flags | SF_FULL;
+ if (!MaybeAddToTodoSet(newtile, newdir, tile, dir)) {
+ info.flags |= SF_FULL;
+ return info;
+ }
}
}
@@ -354,51 +386,74 @@
case MP_STATION:
if (!HasStationRail(tile)) continue;
- if (GetTileOwner(tile) != owner) continue;
+ if (!IsOneSignalBlock(owner, GetTileOwner(tile))) continue;
if (DiagDirToAxis(enterdir) != GetRailStationAxis(tile)) continue; // different axis
if (IsStationTileBlocked(tile)) continue; // 'eye-candy' station tile
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.flags |= SF_TRAIN;
tile += TileOffsByDiagDir(exitdir);
break;
case MP_ROAD:
if (!IsLevelCrossing(tile)) continue;
- if (GetTileOwner(tile) != owner) continue;
+ if (!IsOneSignalBlock(owner, GetTileOwner(tile))) continue;
if (DiagDirToAxis(enterdir) == GetCrossingRoadAxis(tile)) continue; // different axis
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.flags |= SF_TRAIN;
tile += TileOffsByDiagDir(exitdir);
break;
case MP_TUNNELBRIDGE: {
- if (GetTileOwner(tile) != owner) continue;
+ if (!IsOneSignalBlock(owner, GetTileOwner(tile))) continue;
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 (!(info.flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) { // tunnel entrence is ignored
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) info.flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) info.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 (!(info.flags & SF_TRAIN)) {
+ if (HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) info.flags |= SF_TRAIN;
+ if (!(info.flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) {
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) info.flags |= SF_TRAIN;
+ }
+ }
+ continue;
+ }
+ } else {
+ if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
+ if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.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 (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) info.flags |= SF_TRAIN;
+ tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
+ enterdir = INVALID_DIAGDIR;
+ exitdir = INVALID_DIAGDIR;
+ }
}
break;
+ }
default:
continue; // continue the while() loop
}
- if (!MaybeAddToTodoSet(tile, enterdir, oldtile, exitdir)) return flags | SF_FULL;
+ if (!MaybeAddToTodoSet(tile, enterdir, oldtile, exitdir)) {
+ info.flags |= SF_FULL;
+ }
}
- return flags;
+ return info;
}
@@ -407,46 +462,70 @@
*
* @param flags info about segment
*/
-static void UpdateSignalsAroundSegment(SigFlags flags)
+static void UpdateSignalsAroundSegment(SigInfo info)
{
TileIndex tile;
Trackdir trackdir;
+ Track track;
while (_tbuset.Get(&tile, &trackdir)) {
assert(HasSignalOnTrackdir(tile, trackdir));
- SignalType sig = GetSignalType(tile, TrackdirToTrack(trackdir));
+ track = TrackdirToTrack(trackdir);
+ SignalType sig = GetSignalType(tile, track);
SignalState newstate = SIGNAL_STATE_GREEN;
/* determine whether the new state is red */
- if (flags & SF_TRAIN) {
+ if (info.flags & SF_TRAIN) {
/* train in the segment */
newstate = SIGNAL_STATE_RED;
+ } else if (sig == SIGTYPE_PROG &&
+ _num_signals_evaluated > _settings_game.construction.maximum_signal_evaluations) {
+ /* too many cascades */
+ newstate = SIGNAL_STATE_RED;
} else {
/* is it a bidir combo? - then do not count its other signal direction as exit */
- if (sig == SIGTYPE_COMBO && HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir))) {
- /* at least one more exit */
- if ((flags & SF_EXIT2) &&
- /* no green exit */
- (!(flags & SF_GREEN) ||
- /* only one green exit, and it is this one - so all other exits are red */
- (!(flags & SF_GREEN2) && GetSignalStateByTrackdir(tile, ReverseTrackdir(trackdir)) == SIGNAL_STATE_GREEN))) {
- newstate = SIGNAL_STATE_RED;
+ if (IsComboSignal(sig) && HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir))) {
+ // Don't count ourselves
+ uint exits = info.num_exits - 1;
+ uint green = info.num_green;
+ if (GetSignalStateByTrackdir(tile, ReverseTrackdir(trackdir)) == SIGNAL_STATE_GREEN)
+ green--;
+
+ if (sig == SIGTYPE_PROG) { /* Programmable */
+ _num_signals_evaluated++;
+
+ if (!RunSignalProgram(SignalReference(tile, track), exits, green))
+ newstate = SIGNAL_STATE_RED;
+ } else { /* traditional combo */
+ if (!green && exits)
+ newstate = SIGNAL_STATE_RED;
}
} else { // entry, at least one exit, no green exit
- if (IsPresignalEntry(tile, TrackdirToTrack(trackdir)) && (flags & SF_EXIT) && !(flags & SF_GREEN)) newstate = SIGNAL_STATE_RED;
+ if (IsEntrySignal(sig)) {
+ if (sig == SIGTYPE_PROG) {
+ _num_signals_evaluated++;
+ if (!RunSignalProgram(SignalReference(tile, track), info.num_exits, info.num_green))
+ newstate = SIGNAL_STATE_RED;
+ } else { /* traditional combo */
+ if (!info.num_green && info.num_exits) newstate = SIGNAL_STATE_RED;
+ }
+ }
}
}
/* only when the state changes */
if (newstate != GetSignalStateByTrackdir(tile, trackdir)) {
- if (IsPresignalExit(tile, TrackdirToTrack(trackdir))) {
+ if (IsExitSignal(sig)) {
/* for pre-signal exits, add block to the global set */
DiagDirection exitdir = TrackdirToExitdir(ReverseTrackdir(trackdir));
_globset.Add(tile, exitdir); // do not check for full global set, first update all signals
+
+ // Progsig dependencies
+ MarkDependencidesForUpdate(SignalReference(tile, track));
}
SetSignalStateByTrackdir(tile, trackdir, newstate);
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
}
@@ -475,6 +554,7 @@
bool first = true; // first block?
SigSegState state = SIGSEG_FREE; // value to return
+ _num_signals_evaluated = 0;
TileIndex tile;
DiagDirection dir;
@@ -494,7 +574,9 @@
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:
@@ -529,25 +611,29 @@
assert(!_tbdset.Overflowed()); // it really shouldn't overflow by these one or two items
assert(!_tbdset.IsEmpty()); // it wouldn't hurt anyone, but shouldn't happen too
- SigFlags flags = ExploreSegment(owner);
+ SigInfo info = ExploreSegment(owner);
if (first) {
first = false;
/* SIGSEG_FREE is set by default */
- if (flags & SF_PBS) {
+ if (info.flags & SF_PBS) {
state = SIGSEG_PBS;
- } else if ((flags & SF_TRAIN) || ((flags & SF_EXIT) && !(flags & SF_GREEN)) || (flags & SF_FULL)) {
+ } else if ((info.flags & SF_TRAIN) || ((info.num_exits) && !(info.num_green)) || (info.flags & SF_FULL)) {
state = SIGSEG_FULL;
}
}
/* do not do anything when some buffer was full */
- if (flags & SF_FULL) {
+ if (info.flags & SF_FULL) {
ResetSets(); // free all sets
break;
}
- UpdateSignalsAroundSegment(flags);
+ if (_num_signals_evaluated > _settings_game.construction.maximum_signal_evaluations) {
+ ShowErrorMessage(STR_ERROR_SIGNAL_CHANGES, STR_EMPTY, WL_INFO);
+ }
+
+ UpdateSignalsAroundSegment(info);
}
return state;
@@ -586,8 +672,9 @@
DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE
};
- /* do not allow signal updates for two companies in one run */
- assert(_globset.IsEmpty() || owner == _last_owner);
+ /* do not allow signal updates for two companies in one run,
+ * if these companies are not part of the same signal block */
+ assert(_globset.IsEmpty() || IsOneSignalBlock(owner, _last_owner));
_last_owner = owner;
@@ -611,8 +698,9 @@
*/
void AddSideToSignalBuffer(TileIndex tile, DiagDirection side, Owner owner)
{
- /* do not allow signal updates for two companies in one run */
- assert(_globset.IsEmpty() || owner == _last_owner);
+ /* do not allow signal updates for two companies in one run,
+ * if these companies are not part of the same signal block */
+ assert(_globset.IsEmpty() || IsOneSignalBlock(owner, _last_owner));
_last_owner = owner;
@@ -660,3 +748,89 @@
AddTrackToSignalBuffer(tile, track, owner);
UpdateSignalsInBuffer(owner);
}
+
+void AddSignalDependency(SignalReference on, SignalReference dep)
+{
+ assert(GetTileOwner(on.tile) == GetTileOwner(dep.tile));
+ SignalDependencyList &dependencies = _signal_dependencies[on];
+ (*dependencies.Append()) = dep;
+}
+
+void RemoveSignalDependency(SignalReference on, SignalReference dep)
+{
+ SignalDependencyList &dependencies = _signal_dependencies[on];
+ SignalReference *ob = dependencies.Find(dep);
+
+ // Destroying both signals in same command
+ if(ob == dependencies.End())
+ return;
+
+ dependencies.Erase(ob);
+ if (dependencies.Length() == 0)
+ _signal_dependencies.erase(on);
+}
+
+void FreeSignalDependencies()
+{
+ _signal_dependencies.clear();
+}
+
+static void MarkDependencidesForUpdate(SignalReference on)
+{
+ SignalDependencyMap::iterator f = _signal_dependencies.find(on);
+ if (f == _signal_dependencies.end()) return;
+
+ SignalDependencyList &dependencies = f->second;
+ for (SignalReference *i = dependencies.Begin(), *e = dependencies.End();
+ i != e; ++i) {
+ assert(GetTileOwner(i->tile) == GetTileOwner(on.tile));
+
+ Trackdir td = TrackToTrackdir(i->track);
+ _globset.Add(i->tile, TrackdirToExitdir(td));
+ _globset.Add(i->tile, TrackdirToExitdir(ReverseTrackdir(td)));
+ }
+}
+
+void CheckRemoveSignalsFromTile(TileIndex tile)
+{
+ if (!HasSignals(tile)) return;
+
+ TrackBits tb = GetTrackBits(tile);
+ Track tr;
+ while ((tr = RemoveFirstTrack(&tb)) != INVALID_TRACK) {
+ if (HasSignalOnTrack(tile, tr)) CheckRemoveSignal(tile, tr);
+ }
+}
+
+static void NotifyRemovingDependentSignal(SignalReference on, SignalReference by)
+{
+ SignalType t = GetSignalType(by.tile, by.track);
+ if (IsProgrammableSignal(t)) {
+ RemoveProgramDependencies(by, on);
+ } else DEBUG(misc, 0, "Removing dependency held by non-programmable signal (Unexpected)");
+}
+
+void CheckRemoveSignal(TileIndex tile, Track track)
+{
+ if (!HasSignalOnTrack(tile, track)) return;
+ SignalReference thisRef(tile, track);
+
+ SignalType t = GetSignalType(tile, track);
+ if (IsProgrammableSignal(t)) {
+ FreeSignalProgram(thisRef);
+ }
+
+ SignalDependencyMap::iterator i = _signal_dependencies.find(SignalReference(tile, track)),
+ e = _signal_dependencies.end();
+
+ if (i != e) {
+ SignalDependencyList &dependencies = i->second;
+
+ for (SignalReference *ir = dependencies.Begin(), *er = dependencies.End(); ir != er; ++ir) {
+ assert(GetTileOwner(ir->tile) == GetTileOwner(tile));
+ NotifyRemovingDependentSignal(thisRef, *ir);
+ }
+
+ _signal_dependencies.erase(i);
+ }
+}
Index: src/signal_func.h
===================================================================
--- src/signal_func.h (revision 27386)
+++ src/signal_func.h (working copy)
@@ -12,10 +12,12 @@
#ifndef SIGNAL_FUNC_H
#define SIGNAL_FUNC_H
+#include "signal_type.h"
#include "track_type.h"
#include "tile_type.h"
#include "direction_type.h"
#include "company_type.h"
+#include "debug.h"
/**
* Maps a trackdir to the bit that stores its status in the map arrays, in the
@@ -47,6 +49,61 @@
return _signal_on_track[track];
}
+/// Is a given signal type a presignal entry signal?
+static inline bool IsEntrySignal(SignalType type)
+{
+ return type == SIGTYPE_ENTRY || type == SIGTYPE_COMBO || type == SIGTYPE_PROG;
+}
+
+/// Is a given signal type a presignal exit signal?
+static inline bool IsExitSignal(SignalType type)
+{
+ return type == SIGTYPE_EXIT || type == SIGTYPE_COMBO || type == SIGTYPE_PROG;
+}
+
+/// Is a given signal type a presignal combo signal?
+static inline bool IsComboSignal(SignalType type)
+{
+ return type == SIGTYPE_COMBO || type == SIGTYPE_PROG;
+}
+
+/// Is a given signal type a PBS signal?
+static inline bool IsPbsSignal(SignalType type)
+{
+ return type == SIGTYPE_PBS || type == SIGTYPE_PBS_ONEWAY;
+}
+
+/// Is this a programmable signal?
+static inline bool IsProgrammableSignal(SignalType type)
+{
+ return type == SIGTYPE_PROG;
+}
+
+/// Does a given signal have a PBS sprite?
+static inline bool IsSignalSpritePBS(SignalType type)
+{
+ return type >= SIGTYPE_FIRST_PBS_SPRITE;
+}
+
+static inline SignalType NextSignalType(SignalType cur, uint which_signals)
+{
+ bool pbs = (which_signals != 0);
+ bool block = (which_signals != 1);
+
+ switch(cur) {
+ case SIGTYPE_NORMAL: return block ? SIGTYPE_ENTRY : SIGTYPE_PBS;
+ case SIGTYPE_ENTRY: return block ? SIGTYPE_EXIT : SIGTYPE_PBS;
+ case SIGTYPE_EXIT: return block ? SIGTYPE_COMBO : SIGTYPE_PBS;
+ case SIGTYPE_COMBO: return block ? SIGTYPE_PROG : SIGTYPE_PBS;
+ case SIGTYPE_PROG: return pbs ? SIGTYPE_PBS : SIGTYPE_NORMAL;
+ case SIGTYPE_PBS: return pbs ? SIGTYPE_PBS_ONEWAY : SIGTYPE_NORMAL;
+ case SIGTYPE_PBS_ONEWAY: return block ? SIGTYPE_NORMAL : SIGTYPE_PBS;
+ default:
+ DEBUG(map, 0, "Attempt to cycle from signal type %d", cur);
+ return SIGTYPE_NORMAL; // Fortunately mostly harmless
+ }
+}
+
/** State of the signal segment */
enum SigSegState {
SIGSEG_FREE, ///< Free and has no pre-signal exits or at least one green exit
@@ -54,6 +111,30 @@
SIGSEG_PBS, ///< Segment is a PBS segment
};
+/** Checks for any data attached to any signals, and removes it. Call when performing
+ * an action which may potentially remove signals from a tile, in order to avoid leaking
+ * data.
+ */
+void CheckRemoveSignalsFromTile(TileIndex tile);
+
+/** Checks for, and removes, any extra signal data. Call when removing a piece of track
+ * which is potentially signalled, in order to free any extra data that may be associated
+ * with said track.
+ */
+void CheckRemoveSignal(TileIndex tile, Track track);
+
+/** Adds a signal dependency
+ * The signal identified by @p dep will be marked as dependend upon
+ * the signal identified by @p on
+ */
+void AddSignalDependency(SignalReference on, SignalReference dep);
+
+/// Removes a signal dependency. Arguments same as AddSignalDependency
+void RemoveSignalDependency(SignalReference on, SignalReference dep);
+
+/// Frees signal dependencies (for newgame/load)
+void FreeSignalDependencies();
+
SigSegState UpdateSignalsOnSegment(TileIndex tile, DiagDirection side, Owner owner);
void SetSignalsOnBothDir(TileIndex tile, Track track, Owner owner);
void AddTrackToSignalBuffer(TileIndex tile, Track track, Owner owner);
Index: src/signal_type.h
===================================================================
--- src/signal_type.h (revision 27386)
+++ src/signal_type.h (working copy)
@@ -13,6 +13,8 @@
#define SIGNAL_TYPE_H
#include "core/enum_type.hpp"
+#include "track_type.h"
+#include "tile_type.h"
/** Variant of the signal, i.e. how does the signal look? */
enum SignalVariant {
@@ -29,14 +31,28 @@
SIGTYPE_COMBO = 3, ///< presignal inter-block
SIGTYPE_PBS = 4, ///< normal pbs signal
SIGTYPE_PBS_ONEWAY = 5, ///< no-entry signal
+ SIGTYPE_PROG = 6, ///< programmable presignal
SIGTYPE_END,
- SIGTYPE_LAST = SIGTYPE_PBS_ONEWAY,
- SIGTYPE_LAST_NOPBS = SIGTYPE_COMBO,
+ SIGTYPE_LAST = SIGTYPE_PROG,
+ SIGTYPE_FIRST_PBS_SPRITE = SIGTYPE_PBS,
};
/** Helper information for extract tool. */
template <> struct EnumPropsT : MakeEnumPropsT {};
+/** Reference to a signal
+ *
+ * A reference to a signal by its tile and track
+ */
+struct SignalReference {
+ inline SignalReference(TileIndex t, Track tr) : tile(t), track(tr) {}
+ inline bool operator<(const SignalReference& o) const { return tile < o.tile || (tile == o.tile && track < o.track); }
+ inline bool operator==(const SignalReference& o) const { return tile == o.tile && track == o.track; }
+ inline bool operator!=(const SignalReference& o) const { return tile != o.tile || track != o.track; }
+
+ TileIndex tile;
+ Track track;
+};
/**
* These are states in which a signal can be. Currently these are only two, so
@@ -46,6 +62,7 @@
enum SignalState {
SIGNAL_STATE_RED = 0, ///< The signal is red
SIGNAL_STATE_GREEN = 1, ///< The signal is green
+ SIGNAL_STATE_MAX = SIGNAL_STATE_GREEN,
};
#endif /* SIGNAL_TYPE_H */
Index: src/smallmap_colours.h
new file mode 100644
===================================================================
--- src/smallmap_colours.h (revision 0)
+++ src/smallmap_colours.h (working copy)
@@ -0,0 +1,106 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file smallmap_colours.h Colours used by smallmap. */
+
+#ifndef SMALLMAP_COLOURS_H
+#define SMALLMAP_COLOURS_H
+
+#include "core/endian_func.hpp"
+
+static const uint8 PC_ROUGH_LAND = 0x52; ///< Dark green palette colour for rough land.
+static const uint8 PC_GRASS_LAND = 0x54; ///< Dark green palette colour for grass land.
+static const uint8 PC_BARE_LAND = 0x37; ///< Brown palette colour for bare land.
+static const uint8 PC_FIELDS = 0x25; ///< Light brown palette colour for fields.
+static const uint8 PC_TREES = 0x57; ///< Green palette colour for trees.
+static const uint8 PC_WATER = 0xCA; ///< Dark blue palette colour for water.
+
+#define MKCOLOUR(x) TO_LE32X(x)
+
+#define MKCOLOUR_XXXX(x) (MKCOLOUR(0x01010101) * (uint)(x))
+#define MKCOLOUR_X0X0(x) (MKCOLOUR(0x01000100) * (uint)(x))
+#define MKCOLOUR_0X0X(x) (MKCOLOUR(0x00010001) * (uint)(x))
+#define MKCOLOUR_0XX0(x) (MKCOLOUR(0x00010100) * (uint)(x))
+#define MKCOLOUR_X00X(x) (MKCOLOUR(0x01000001) * (uint)(x))
+
+#define MKCOLOUR_XYXY(x, y) (MKCOLOUR_X0X0(x) | MKCOLOUR_0X0X(y))
+#define MKCOLOUR_XYYX(x, y) (MKCOLOUR_X00X(x) | MKCOLOUR_0XX0(y))
+
+#define MKCOLOUR_0000 MKCOLOUR_XXXX(0x00)
+#define MKCOLOUR_0FF0 MKCOLOUR_0XX0(0xFF)
+#define MKCOLOUR_F00F MKCOLOUR_X00X(0xFF)
+#define MKCOLOUR_FFFF MKCOLOUR_XXXX(0xFF)
+
+#include "table/heightmap_colours.h"
+#include "table/darklight_colours.h"
+
+/** Colour scheme of the smallmap. */
+struct SmallMapColourScheme {
+ uint32 *height_colours; ///< Cached colours for each level in a map.
+ const uint32 *height_colours_base; ///< Base table for determining the colours
+ size_t colour_count; ///< The number of colours.
+ uint32 default_colour; ///< Default colour of the land.
+};
+
+extern SmallMapColourScheme _heightmap_schemes[];
+
+struct AndOr {
+ uint32 mor;
+ uint32 mand;
+};
+
+static inline uint32 ApplyMask(uint32 colour, const AndOr *mask)
+{
+ return (colour & mask->mand) | mask->mor;
+}
+
+/** Colour masks for "Contour" and "Routes" modes. */
+static const AndOr _smallmap_contours_andor[] = {
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_CLEAR
+ {MKCOLOUR_0XX0(PC_GREY ), MKCOLOUR_F00F}, // MP_RAILWAY
+ {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_ROAD
+ {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_HOUSE
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TREES
+ {MKCOLOUR_XXXX(PC_LIGHT_BLUE), MKCOLOUR_0000}, // MP_STATION
+ {MKCOLOUR_XXXX(PC_WATER ), MKCOLOUR_0000}, // MP_WATER
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_VOID
+ {MKCOLOUR_XXXX(PC_DARK_RED ), MKCOLOUR_0000}, // MP_INDUSTRY
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TUNNELBRIDGE
+ {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_OBJECT
+ {MKCOLOUR_0XX0(PC_GREY ), MKCOLOUR_F00F},
+};
+
+/** Colour masks for "Vehicles", "Industry", and "Vegetation" modes. */
+static const AndOr _smallmap_vehicles_andor[] = {
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_CLEAR
+ {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_RAILWAY
+ {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_ROAD
+ {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_HOUSE
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TREES
+ {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_STATION
+ {MKCOLOUR_XXXX(PC_WATER ), MKCOLOUR_0000}, // MP_WATER
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_VOID
+ {MKCOLOUR_XXXX(PC_DARK_RED ), MKCOLOUR_0000}, // MP_INDUSTRY
+ {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TUNNELBRIDGE
+ {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_OBJECT
+ {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F},
+};
+
+static const uint32 _vegetation_clear_bits[] = {
+ MKCOLOUR_XXXX(PC_GRASS_LAND), ///< full grass
+ MKCOLOUR_XXXX(PC_ROUGH_LAND), ///< rough land
+ MKCOLOUR_XXXX(PC_GREY), ///< rocks
+ MKCOLOUR_XXXX(PC_FIELDS), ///< fields
+ MKCOLOUR_XXXX(PC_LIGHT_BLUE), ///< snow
+ MKCOLOUR_XXXX(PC_ORANGE), ///< desert
+ MKCOLOUR_XXXX(PC_GRASS_LAND), ///< unused
+ MKCOLOUR_XXXX(PC_GRASS_LAND), ///< unused
+};
+
+#endif /* SMALLMAP_COLOURS_H */
Index: src/smallmap_gui.cpp
===================================================================
--- src/smallmap_gui.cpp (revision 27386)
+++ src/smallmap_gui.cpp (working copy)
@@ -23,7 +23,9 @@
#include "sound_func.h"
#include "window_func.h"
#include "company_base.h"
+#include "screenshot.h"
+#include "smallmap_colours.h"
#include "smallmap_gui.h"
#include "table/strings.h"
@@ -39,15 +41,6 @@
/** Link stat colours shown in legenda. */
static uint8 _linkstat_colours_in_legenda[] = {0, 1, 3, 5, 7, 9, 11};
-static const int NUM_NO_COMPANY_ENTRIES = 4; ///< Number of entries in the owner legend that are not companies.
-
-static const uint8 PC_ROUGH_LAND = 0x52; ///< Dark green palette colour for rough land.
-static const uint8 PC_GRASS_LAND = 0x54; ///< Dark green palette colour for grass land.
-static const uint8 PC_BARE_LAND = 0x37; ///< Brown palette colour for bare land.
-static const uint8 PC_FIELDS = 0x25; ///< Light brown palette colour for fields.
-static const uint8 PC_TREES = 0x57; ///< Green palette colour for trees.
-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}
@@ -135,7 +128,7 @@
MKEND()
};
-static LegendAndColour _legend_land_owners[NUM_NO_COMPANY_ENTRIES + MAX_COMPANIES + 1] = {
+LegendAndColour _legend_land_owners[NUM_NO_COMPANY_ENTRIES + MAX_COMPANIES + 1] = {
MO(PC_WATER, STR_SMALLMAP_LEGENDA_WATER),
MO(0x00, STR_SMALLMAP_LEGENDA_NO_OWNER), // This colour will vary depending on settings.
MO(PC_DARK_RED, STR_SMALLMAP_LEGENDA_TOWNS),
@@ -157,17 +150,17 @@
* Allow room for all industries, plus a terminator entry
* This is required in order to have the industry slots all filled up
*/
-static LegendAndColour _legend_from_industries[NUM_INDUSTRYTYPES + 1];
+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];
+uint _industry_to_list_pos[NUM_INDUSTRYTYPES];
/** Show heightmap in industry and owner mode of smallmap window. */
-static bool _smallmap_show_heightmap = false;
+bool _smallmap_show_heightmap = false;
/** Highlight a specific industry type */
static IndustryType _smallmap_industry_highlight = INVALID_INDUSTRYTYPE;
/** State of highlight blinking */
static bool _smallmap_industry_highlight_state;
/** For connecting company ID to position in owner list (small map legend) */
-static uint _company_to_list_pos[MAX_COMPANIES];
+uint _company_to_list_pos[MAX_COMPANIES];
/**
* Fills an array for the industries legends.
@@ -243,35 +236,9 @@
_legend_land_owners,
};
-#define MKCOLOUR(x) TO_LE32X(x)
-
-#define MKCOLOUR_XXXX(x) (MKCOLOUR(0x01010101) * (uint)(x))
-#define MKCOLOUR_X0X0(x) (MKCOLOUR(0x01000100) * (uint)(x))
-#define MKCOLOUR_0X0X(x) (MKCOLOUR(0x00010001) * (uint)(x))
-#define MKCOLOUR_0XX0(x) (MKCOLOUR(0x00010100) * (uint)(x))
-#define MKCOLOUR_X00X(x) (MKCOLOUR(0x01000001) * (uint)(x))
-
-#define MKCOLOUR_XYXY(x, y) (MKCOLOUR_X0X0(x) | MKCOLOUR_0X0X(y))
-#define MKCOLOUR_XYYX(x, y) (MKCOLOUR_X00X(x) | MKCOLOUR_0XX0(y))
-
-#define MKCOLOUR_0000 MKCOLOUR_XXXX(0x00)
-#define MKCOLOUR_0FF0 MKCOLOUR_0XX0(0xFF)
-#define MKCOLOUR_F00F MKCOLOUR_X00X(0xFF)
-#define MKCOLOUR_FFFF MKCOLOUR_XXXX(0xFF)
-
-#include "table/heightmap_colours.h"
-
-/** Colour scheme of the smallmap. */
-struct SmallMapColourScheme {
- uint32 *height_colours; ///< Cached colours for each level in a map.
- const uint32 *height_colours_base; ///< Base table for determining the colours
- size_t colour_count; ///< The number of colours.
- uint32 default_colour; ///< Default colour of the land.
-};
-
/** Available colour schemes for height maps. */
-static SmallMapColourScheme _heightmap_schemes[] = {
- {NULL, _green_map_heights, lengthof(_green_map_heights), MKCOLOUR_XXXX(0x54)}, ///< Green colour scheme.
+SmallMapColourScheme _heightmap_schemes[] = {
+ {NULL, _green_map_heights, lengthof(_green_map_heights), MKCOLOUR_XXXX(0x5B)}, ///< Green colour scheme.
{NULL, _dark_green_map_heights, lengthof(_dark_green_map_heights), MKCOLOUR_XXXX(0x62)}, ///< Dark green colour scheme.
{NULL, _violet_map_heights, lengthof(_violet_map_heights), MKCOLOUR_XXXX(0x82)}, ///< Violet colour scheme.
};
@@ -353,66 +320,6 @@
_smallmap_company_count = i;
}
-struct AndOr {
- uint32 mor;
- uint32 mand;
-};
-
-static inline uint32 ApplyMask(uint32 colour, const AndOr *mask)
-{
- return (colour & mask->mand) | mask->mor;
-}
-
-
-/** Colour masks for "Contour" and "Routes" modes. */
-static const AndOr _smallmap_contours_andor[] = {
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_CLEAR
- {MKCOLOUR_0XX0(PC_GREY ), MKCOLOUR_F00F}, // MP_RAILWAY
- {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_ROAD
- {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_HOUSE
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TREES
- {MKCOLOUR_XXXX(PC_LIGHT_BLUE), MKCOLOUR_0000}, // MP_STATION
- {MKCOLOUR_XXXX(PC_WATER ), MKCOLOUR_0000}, // MP_WATER
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_VOID
- {MKCOLOUR_XXXX(PC_DARK_RED ), MKCOLOUR_0000}, // MP_INDUSTRY
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TUNNELBRIDGE
- {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_OBJECT
- {MKCOLOUR_0XX0(PC_GREY ), MKCOLOUR_F00F},
-};
-
-/** Colour masks for "Vehicles", "Industry", and "Vegetation" modes. */
-static const AndOr _smallmap_vehicles_andor[] = {
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_CLEAR
- {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_RAILWAY
- {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_ROAD
- {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_HOUSE
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TREES
- {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F}, // MP_STATION
- {MKCOLOUR_XXXX(PC_WATER ), MKCOLOUR_0000}, // MP_WATER
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_VOID
- {MKCOLOUR_XXXX(PC_DARK_RED ), MKCOLOUR_0000}, // MP_INDUSTRY
- {MKCOLOUR_0000 , MKCOLOUR_FFFF}, // MP_TUNNELBRIDGE
- {MKCOLOUR_0XX0(PC_DARK_RED ), MKCOLOUR_F00F}, // MP_OBJECT
- {MKCOLOUR_0XX0(PC_BLACK ), MKCOLOUR_F00F},
-};
-
-/** Mapping of tile type to importance of the tile (higher number means more interesting to show). */
-static const byte _tiletype_importance[] = {
- 2, // MP_CLEAR
- 8, // MP_RAILWAY
- 7, // MP_ROAD
- 5, // MP_HOUSE
- 2, // MP_TREES
- 9, // MP_STATION
- 2, // MP_WATER
- 1, // MP_VOID
- 6, // MP_INDUSTRY
- 8, // MP_TUNNELBRIDGE
- 2, // MP_OBJECT
- 0,
-};
-
-
static inline TileType GetEffectiveTileType(TileIndex tile)
{
TileType t = GetTileType(tile);
@@ -524,17 +431,6 @@
return _smallmap_show_heightmap ? GetSmallMapContoursPixels(tile, t) : GetSmallMapRoutesPixels(tile, t);
}
-static const uint32 _vegetation_clear_bits[] = {
- MKCOLOUR_XXXX(PC_GRASS_LAND), ///< full grass
- MKCOLOUR_XXXX(PC_ROUGH_LAND), ///< rough land
- MKCOLOUR_XXXX(PC_GREY), ///< rocks
- MKCOLOUR_XXXX(PC_FIELDS), ///< fields
- MKCOLOUR_XXXX(PC_LIGHT_BLUE), ///< snow
- MKCOLOUR_XXXX(PC_ORANGE), ///< desert
- MKCOLOUR_XXXX(PC_GRASS_LAND), ///< unused
- MKCOLOUR_XXXX(PC_GRASS_LAND), ///< unused
-};
-
/**
* Return the colour a tile would be displayed with in the smallmap in mode "Vegetation".
*
@@ -594,6 +490,16 @@
return MKCOLOUR_XXXX(_legend_land_owners[_company_to_list_pos[o]].colour);
}
+static void NotifyAllViewports(ViewportMapType map_type)
+{
+ Window *w;
+ FOR_ALL_WINDOWS_FROM_BACK(w) {
+ if (w->viewport != NULL)
+ if (w->viewport->zoom >= ZOOM_LVL_DRAW_MAP && w->viewport->map_type == map_type)
+ w->InvalidateData();
+ }
+}
+
/** Vehicle colours in #SMT_VEHICLES mode. Indexed by #VehicleTypeByte. */
static const byte _vehicle_type_colours[6] = {
PC_RED, PC_YELLOW, PC_LIGHT_BLUE, PC_WHITE, PC_BLACK, PC_RED
@@ -936,7 +842,7 @@
*
* @param dpi pointer to pixel to write onto
*/
-void SmallMapWindow::DrawSmallMap(DrawPixelInfo *dpi) const
+void SmallMapWindow::DrawSmallMap(DrawPixelInfo *dpi, bool draw_indicators) const
{
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
DrawPixelInfo *old_dpi;
@@ -992,7 +898,7 @@
if (this->show_towns) this->DrawTowns(dpi);
/* Draw map indicators */
- this->DrawMapIndicators();
+ if (draw_indicators) this->DrawMapIndicators();
_cur_dpi = old_dpi;
}
@@ -1442,6 +1348,7 @@
/* If click on industries label, find right industry type and enable/disable it. */
if (click_pos < _smallmap_industry_count) {
this->SelectLegendItem(click_pos, _legend_from_industries, _smallmap_industry_count);
+ NotifyAllViewports(VPMT_INDUSTRY);
}
} else if (this->map_type == SMT_LINKSTATS) {
if (click_pos < _smallmap_cargo_count) {
@@ -1451,6 +1358,7 @@
} else if (this->map_type == SMT_OWNER) {
if (click_pos < _smallmap_company_count) {
this->SelectLegendItem(click_pos, _legend_land_owners, _smallmap_company_count, NUM_NO_COMPANY_ENTRIES);
+ NotifyAllViewports(VPMT_OWNER);
}
}
this->SetDirty();
@@ -1464,9 +1372,11 @@
switch (this->map_type) {
case SMT_INDUSTRY:
tbl = _legend_from_industries;
+ NotifyAllViewports(VPMT_INDUSTRY);
break;
case SMT_OWNER:
tbl = &(_legend_land_owners[NUM_NO_COMPANY_ENTRIES]);
+ NotifyAllViewports(VPMT_OWNER);
break;
case SMT_LINKSTATS:
tbl = _legend_linkstats;
@@ -1485,8 +1395,13 @@
case WID_SM_SHOW_HEIGHT: // Enable/disable showing of heightmap.
_smallmap_show_heightmap = !_smallmap_show_heightmap;
this->SetWidgetLoweredState(WID_SM_SHOW_HEIGHT, _smallmap_show_heightmap);
+ NotifyAllViewports(VPMT_INDUSTRY);
this->SetDirty();
break;
+
+ case WID_SM_SCREENSHOT:
+ TakeScreenshot();
+ break;
}
}
@@ -1531,7 +1446,7 @@
{
if (widget != WID_SM_MAP || _scrolling_viewport) return false;
- _scrolling_viewport = true;
+ _scrolling_viewport = this;
return true;
}
@@ -1650,6 +1565,71 @@
return ret;
}
+/**
+ * Take a screenshot of the contents of the smallmap window, at the current zoom level and mode
+ * This calls MakeSmallMapScreenshot which uses ScreenshotCallbackHandler as the screenshot callback
+ */
+void SmallMapWindow::TakeScreenshot()
+{
+ int32 width = ((MapMaxX() + MapMaxY()) * 2) / this->zoom;
+ int32 height = (MapMaxX() + MapMaxY() + 1) / this->zoom;
+
+ int32 saved_scroll_x = this->scroll_x;
+ int32 saved_scroll_y = this->scroll_y;
+ int32 saved_subscroll = this->subscroll;
+ this->subscroll = 0;
+ MakeSmallMapScreenshot(width, height, this);
+ this->scroll_x = saved_scroll_x;
+ this->scroll_y = saved_scroll_y;
+ this->subscroll = saved_subscroll;
+}
+
+/**
+ * Callback called by the screenshot code to draw a section of the smallmap to the output buffer
+ * @param buf Videobuffer with same bitdepth as current blitter
+ * @param y First line to render
+ * @param pitch Pitch of the videobuffer
+ * @param n Number of lines to render
+ */
+void SmallMapWindow::ScreenshotCallbackHandler(void *buf, uint y, uint pitch, uint n)
+{
+ DrawPixelInfo dpi, *old_dpi;
+
+ /* We are no longer rendering to the screen */
+ DrawPixelInfo old_screen = _screen;
+ bool old_disable_anim = _screen_disable_anim;
+
+ _screen.dst_ptr = buf;
+ _screen.width = pitch;
+ _screen.height = n;
+ _screen.pitch = pitch;
+ _screen_disable_anim = true;
+
+ old_dpi = _cur_dpi;
+ _cur_dpi = &dpi;
+
+ dpi.dst_ptr = buf;
+ dpi.height = n;
+ dpi.width = ((MapMaxX() + MapMaxY()) * 2) / this->zoom;
+ dpi.pitch = pitch;
+ dpi.zoom = ZOOM_LVL_NORMAL;
+ dpi.left = 0;
+ dpi.top = y;
+
+ int32 pos = (((int32)MapMaxX() + 1) * TILE_PIXELS) / 4;
+ this->scroll_x = pos;
+ this->scroll_y = -pos;
+
+ /* make the screenshot */
+ this->DrawSmallMap(&dpi, false);
+
+ _cur_dpi = old_dpi;
+
+ /* Switch back to rendering to the screen */
+ _screen = old_screen;
+ _screen_disable_anim = old_disable_anim;
+}
+
SmallMapWindow::SmallMapType SmallMapWindow::map_type = SMT_CONTOUR;
bool SmallMapWindow::show_towns = true;
int SmallMapWindow::max_heightlevel = -1;
@@ -1798,6 +1778,7 @@
NWidgetFunction(SmallMapDisplay), // Smallmap display and legend bar + image buttons.
/* Bottom button row and resize box. */
NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SM_SCREENSHOT), SetDataTip(STR_SMALLMAP_SCREENSHOT, STR_NULL),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SM_SELECT_BUTTONS),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SM_ENABLE_ALL), SetDataTip(STR_SMALLMAP_ENABLE_ALL, STR_NULL),
Index: src/smallmap_gui.h
===================================================================
--- src/smallmap_gui.h (revision 27386)
+++ src/smallmap_gui.h (working copy)
@@ -20,9 +20,34 @@
#include "linkgraph/linkgraph_gui.h"
#include "widgets/smallmap_widget.h"
+static const int NUM_NO_COMPANY_ENTRIES = 4; ///< Number of entries in the owner legend that are not companies.
+
+/** Mapping of tile type to importance of the tile (higher number means more interesting to show). */
+static const byte _tiletype_importance[] = {
+ 2, // MP_CLEAR
+ 8, // MP_RAILWAY
+ 7, // MP_ROAD
+ 5, // MP_HOUSE
+ 2, // MP_TREES
+ 9, // MP_STATION
+ 2, // MP_WATER
+ 1, // MP_VOID
+ 6, // MP_INDUSTRY
+ 8, // MP_TUNNELBRIDGE
+ 2, // MP_OBJECT
+ 0,
+};
+
/* set up the cargos to be displayed in the smallmap's route legend */
void BuildLinkStatsLegend();
+struct TunnelBridgeToMap {
+ TileIndex from_tile;
+ TileIndex to_tile;
+ uint8 colour;
+};
+typedef SmallVector TunnelBridgeToMapVector;
+
void BuildIndustriesLegend();
void ShowSmallMap();
void BuildLandLegend();
@@ -147,7 +172,6 @@
return Company::IsValidID(_local_company) ? 1U << _local_company : 0xffffffff;
}
- void RebuildColourIndexIfNecessary();
uint GetNumberRowsLegend(uint columns) const;
void SelectLegendItem(int click_pos, LegendAndColour *legend, int end_legend_item, int begin_legend_item = 0);
void SwitchMapType(SmallMapType map_type);
@@ -157,7 +181,7 @@
void DrawSmallMapColumn(void *dst, uint xc, uint yc, int pitch, int reps, int start_pos, int end_pos, Blitter *blitter) const;
void DrawVehicles(const DrawPixelInfo *dpi, Blitter *blitter) const;
void DrawTowns(const DrawPixelInfo *dpi) const;
- void DrawSmallMap(DrawPixelInfo *dpi) const;
+ void DrawSmallMap(DrawPixelInfo *dpi, bool draw_indicators = true) const;
Point RemapTile(int tile_x, int tile_y) const;
Point PixelToTile(int px, int py, int *sub, bool add_sub = true) const;
@@ -175,6 +199,8 @@
SmallMapWindow(WindowDesc *desc, int window_number);
virtual ~SmallMapWindow() { delete this->overlay; }
+ static void RebuildColourIndexIfNecessary();
+
void SmallMapCenterOnCurrentPos();
Point GetStationMiddle(const Station *st) const;
@@ -189,6 +215,9 @@
virtual void OnTick();
virtual void OnScroll(Point delta);
virtual void OnMouseOver(Point pt, int widget);
+
+ void TakeScreenshot();
+ void ScreenshotCallbackHandler(void *buf, uint y, uint pitch, uint n);
};
#endif /* SMALLMAP_GUI_H */
Index: src/sound.cpp
===================================================================
--- src/sound.cpp (revision 27386)
+++ src/sound.cpp (working copy)
@@ -189,7 +189,7 @@
}
-static const byte _vol_factor_by_zoom[] = {255, 255, 255, 190, 134, 87};
+static const byte _vol_factor_by_zoom[] = {255, 255, 255, 190, 134, 87, 10, 1, 1, 1};
assert_compile(lengthof(_vol_factor_by_zoom) == ZOOM_LVL_COUNT);
static const byte _sound_base_vol[] = {
Index: src/spritecache.cpp
===================================================================
--- src/spritecache.cpp (revision 27386)
+++ src/spritecache.cpp (working copy)
@@ -853,6 +853,102 @@
}
}
+/**
+ * Reads a sprite and finds its most representative colour.
+ * @param sprite Sprite to read.
+ * @param palette_id Palette for remapping colours.
+ * @return if blitter supports 32bpp, average Colour.data else a palette index.
+ */
+uint32 GetSpriteMainColour(SpriteID sprite_id, PaletteID palette_id)
+{
+ if (!SpriteExists(sprite_id)) return 0;
+
+ SpriteCache *sc = GetSpriteCache(sprite_id);
+ if (sc->type != ST_NORMAL) return 0;
+
+ const byte * const remap = (palette_id == PAL_NONE ? NULL : GetNonSprite(GB(palette_id, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1);
+
+ uint8 file_slot = sc->file_slot;
+ size_t file_pos = sc->file_pos;
+
+ SpriteLoader::Sprite sprites[ZOOM_LVL_COUNT];
+ SpriteLoader::Sprite *sprite = &sprites[ZOOM_LVL_SHIFT];
+ sprites[ZOOM_LVL_NORMAL].type = ST_NORMAL;
+ SpriteLoaderGrf sprite_loader(sc->container_ver);
+ uint8 sprite_avail;
+ const uint8 screen_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
+
+ /* Try to read the 32bpp sprite first. */
+ if (screen_depth == 32) {
+ sprite_avail = sprite_loader.LoadSprite(sprites, file_slot, file_pos, ST_NORMAL, true);
+ if (sprite_avail & ZOOM_LVL_BASE) {
+ /* Return the average colour. */
+ uint32 r = 0, g = 0, b = 0, cnt = 0;
+ SpriteLoader::CommonPixel *pixel = sprite->data;
+ for (uint x = sprite->width * sprite->height; x != 0; x--) {
+ if (pixel->a) {
+ if (remap && pixel->m) {
+ const Colour c = _cur_palette.palette[remap[pixel->m]];
+ if (c.a) {
+ r += c.r;
+ g += c.g;
+ b += c.b;
+ cnt++;
+ }
+ } else {
+ r += pixel->r;
+ g += pixel->g;
+ b += pixel->b;
+ cnt++;
+ }
+ }
+ pixel++;
+ }
+ return cnt ? Colour(r / cnt, g / cnt, b / cnt).data : 0;
+ }
+ }
+
+ /* No 32bpp, try 8bpp. */
+ sprite_avail = sprite_loader.LoadSprite(sprites, file_slot, file_pos, ST_NORMAL, false);
+ if (sprite_avail & ZOOM_LVL_BASE) {
+ SpriteLoader::CommonPixel *pixel = sprite->data;
+ if (screen_depth == 32) {
+ /* Return the average colour. */
+ uint32 r = 0, g = 0, b = 0, cnt = 0;
+ for (uint x = sprite->width * sprite->height; x != 0; x--) {
+ if (pixel->a) {
+ const uint col_index = remap ? remap[pixel->m] : pixel->m;
+ const Colour c = _cur_palette.palette[col_index];
+ r += c.r;
+ g += c.g;
+ b += c.b;
+ cnt++;
+ }
+ pixel++;
+ }
+ return cnt ? Colour(r / cnt, g / cnt, b / cnt).data : 0;
+ } else {
+ /* Return the most used indexed colour. */
+ int cnt[256];
+ memset(cnt, 0, sizeof(cnt));
+ for (uint x = sprite->width * sprite->height; x != 0; x--) {
+ cnt[remap ? remap[pixel->m] : pixel->m]++;
+ pixel++;
+ }
+ int cnt_max = -1;
+ uint32 rk = 0;
+ for (uint x = 1; x < lengthof(cnt); x++) {
+ if (cnt[x] > cnt_max) {
+ rk = x;
+ cnt_max = cnt[x];
+ }
+ }
+ return rk;
+ }
+ }
+
+ return 0;
+}
static void GfxInitSpriteCache()
{
Index: src/spritecache.h
===================================================================
--- src/spritecache.h (revision 27386)
+++ src/spritecache.h (working copy)
@@ -57,4 +57,6 @@
bool SkipSpriteData(byte type, uint16 num);
void DupSprite(SpriteID old_spr, SpriteID new_spr);
+uint32 GetSpriteMainColour(SpriteID sprite_id, PaletteID palette_id);
+
#endif /* SPRITECACHE_H */
Index: src/station.cpp
===================================================================
--- src/station.cpp (revision 27386)
+++ src/station.cpp (working copy)
@@ -26,6 +26,7 @@
#include "core/random_func.hpp"
#include "linkgraph/linkgraph.h"
#include "linkgraph/linkgraphschedule.h"
+#include "tracerestrict.h"
#include "table/strings.h"
@@ -137,6 +138,8 @@
/* Now delete all orders that go to the station */
RemoveOrderFromAllVehicles(OT_GOTO_STATION, this->index);
+ TraceRestrictRemoveDestinationID(TROCAF_STATION, this->index);
+
/* Remove all news items */
DeleteStationNews(this->index);
@@ -219,7 +222,7 @@
for (h = 0; h < train_station.h; h++) {
for (w = 0; w < train_station.w; w++) {
if (this->TileBelongsToRailStation(tile)) {
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
tile += TileDiffXY(1, 0);
}
Index: src/station_cmd.cpp
===================================================================
--- src/station_cmd.cpp (revision 27386)
+++ src/station_cmd.cpp (working copy)
@@ -990,6 +990,47 @@
return cost;
}
+/** Checks if an airport can be built at the given area.
+ * @param tile_area Area to check.
+ * @param flags Operation to perform.
+ * @param station StationID of airport allowed in search area.
+ * @return The cost in case of success, or an error code if it failed.
+ */
+static CommandCost CheckFlatLandAirport(TileArea tile_area, DoCommandFlag flags, StationID *station)
+{
+ 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);
+
+ /* if station is set, then allow building on top of an already
+ * existing airport, either the one in *station if it is not
+ * INVALID_STATION, or anyone otherwise and store which one
+ * in *station */
+ if (station != NULL && IsTileType(tile_cur, MP_STATION)) {
+ if (!IsAirport(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 {
+ ret = DoCommand(tile_cur, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+ }
+ }
+
+ return cost;
+}
+
/**
* Check whether we can expand the rail part of the given station.
* @param st the station to expand
@@ -1069,16 +1110,16 @@
/**
* 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
+ * @param error_message the error message when building a station on top of others
* @return command cost with the error or 'okay'
*/
-template
-CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st)
+template
+CommandCost FindJoiningBaseStation(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, T **st, StringID error_message)
{
assert(*st == NULL);
bool check_surrounding = true;
@@ -1121,11 +1162,12 @@
* @param adjacent whether adjacent stations are allowed
* @param ta the area of the newly build station
* @param st 'return' pointer for the found station
+ * @param error_message the error message when building a station on top of others
* @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, StringID error_message = STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST)
{
- return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st);
+ return FindJoiningBaseStation(existing_station, station_to_join, adjacent, ta, st, error_message);
}
/**
@@ -1139,7 +1181,7 @@
*/
CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_to_join, bool adjacent, TileArea ta, Waypoint **wp)
{
- return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp);
+ return FindJoiningBaseStation(existing_waypoint, waypoint_to_join, adjacent, ta, wp, STR_ERROR_MUST_REMOVE_RAILWAYPOINT_FIRST);
}
/**
@@ -1731,7 +1773,7 @@
*/
static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID station_to_join, bool adjacent, TileArea ta, Station **st)
{
- return FindJoiningBaseStation(existing_stop, station_to_join, adjacent, ta, st);
+ return FindJoiningBaseStation(existing_stop, station_to_join, adjacent, ta, st, STR_ERROR_MUST_REMOVE_ROAD_STOP_FIRST);
}
/**
@@ -2169,6 +2211,37 @@
}
}
+
+/**
+ * Checks if an airport can be removed (no aircraft on it or landing)
+ * @param st Station whose airport is to be removed
+ * @param flags Operation to perform
+ * @return Cost or failure of operation
+ */
+static CommandCost CanRemoveAirport(Station *st, DoCommandFlag flags)
+{
+ const Aircraft *a;
+ FOR_ALL_AIRCRAFT(a) {
+ if (!a->IsNormalAircraft()) continue;
+ if (a->targetairport == st->index && a->state != FLYING)
+ return_cmd_error(STR_ERROR_AIRCRAFT_IN_THE_WAY);
+ }
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+
+ 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]);
+ }
+
+ return cost;
+}
+
+
/**
* Place an Airport.
* @param tile tile where airport will be built
@@ -2212,13 +2285,46 @@
return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
}
- CommandCost cost = CheckFlatLand(airport_area, flags);
+ StationID est = INVALID_STATION;
+ CommandCost cost = CheckFlatLandAirport(airport_area, flags, &est);
if (cost.Failed()) return cost;
+ Station *st = NULL;
+ ret = FindJoiningStation(est, station_to_join, HasBit(p2, 0), airport_area, &st, STR_ERROR_MUST_DEMOLISH_AIRPORT_FIRST);
+ 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;
+
+ /* action to be performed */
+ enum {
+ AIRPORT_NEW, // airport is a new station
+ AIRPORT_ADD, // add an airport to an existing station
+ AIRPORT_UPGRADE, // upgrade the airport in a station
+ } action =
+ (est != INVALID_STATION) ? AIRPORT_UPGRADE :
+ (st != NULL) ? AIRPORT_ADD : AIRPORT_NEW;
+
+ if (action == AIRPORT_ADD && st->airport.tile != INVALID_TILE) {
+ return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT);
+ }
+
/* 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);
+ uint newnoise_level = nearest->noise_reached + GetAirportNoiseLevelForTown(as, iter, nearest->xy);
+
+ if (action == AIRPORT_UPGRADE) {
+ const AirportSpec *old_as = st->airport.GetSpec();
+ AirportTileTableIterator old_iter(old_as->table[st->airport.layout], st->airport.tile);
+ Town *old_nearest = AirportGetNearestTown(old_as, old_iter);
+ if (old_nearest == nearest) {
+ newnoise_level -= GetAirportNoiseLevelForTown(old_as, old_iter, nearest->xy);
+ }
+ }
/* Check if local auth would allow a new airport */
StringID authority_refuse_message = STR_NULL;
@@ -2226,11 +2332,11 @@
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()) {
+ if (newnoise_level > nearest->MaxTownNoise()) {
authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE;
authority_refuse_town = nearest;
}
- } else {
+ } else if (action != AIRPORT_UPGRADE) {
Town *t = ClosestTownFromTile(tile, UINT_MAX);
uint num = 0;
const Station *st;
@@ -2248,18 +2354,11 @@
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);
+ if (action == AIRPORT_UPGRADE) {
+ /* check that the old airport can be removed */
+ CommandCost r = CanRemoveAirport(st, flags);
+ if (r.Failed()) return r;
+ cost.AddCost(r);
}
for (AirportTileTableIterator iter(as->table[layout], tile); iter != INVALID_TILE; ++iter) {
@@ -2267,8 +2366,38 @@
}
if (flags & DC_EXEC) {
+ if (action == AIRPORT_UPGRADE) {
+ /* delete old airport if upgrading */
+ const AirportSpec *old_as = st->airport.GetSpec();
+ AirportTileTableIterator old_iter(old_as->table[st->airport.layout], st->airport.tile);
+ Town *old_nearest = AirportGetNearestTown(old_as, old_iter);
+
+ if (old_nearest != nearest) {
+ old_nearest->noise_reached -= GetAirportNoiseLevelForTown(old_as, old_iter, old_nearest->xy);
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, st->town->index);
+ }
+ }
+
+ TILE_AREA_LOOP(tile_cur, st->airport) {
+ if (IsHangarTile(tile_cur)) OrderBackup::Reset(tile_cur, false);
+ DeleteAnimatedTile(tile_cur);
+ DoClearSquare(tile_cur);
+ DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile_cur);
+ }
+
+ 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();
+ }
+
/* Always add the noise, so there will be no need to recalculate when option toggles */
- nearest->noise_reached += newnoise_level;
+ nearest->noise_reached = newnoise_level;
st->AddFacility(FACIL_AIRPORT, tile);
st->airport.type = airport_type;
@@ -2291,12 +2420,16 @@
AirportTileAnimationTrigger(st, iter, AAT_BUILT);
}
- UpdateAirplanesOnNewStation(st);
+ if (action != AIRPORT_NEW) UpdateAirplanesOnNewStation(st);
- Company::Get(st->owner)->infrastructure.airport++;
- DirtyCompanyInfrastructureWindows(st->owner);
+ if (action == AIRPORT_UPGRADE) {
+ UpdateStationSignCoord(st);
+ } else {
+ Company::Get(st->owner)->infrastructure.airport++;
+ DirtyCompanyInfrastructureWindows(st->owner);
+ st->UpdateVirtCoord();
+ }
- st->UpdateVirtCoord();
UpdateStationAcceptance(st, false);
st->RecomputeIndustriesNear();
InvalidateWindowData(WC_SELECT_STATION, 0, 0);
@@ -2326,15 +2459,8 @@
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;
- }
+ CommandCost cost = CanRemoveAirport(st, flags);
+ if (cost.Failed()) return cost;
if (flags & DC_EXEC) {
const AirportSpec *as = st->airport.GetSpec();
@@ -2344,25 +2470,14 @@
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) {
+ TILE_AREA_LOOP(tile_cur, st->airport) {
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;
Index: src/station_gui.cpp
===================================================================
--- src/station_gui.cpp (revision 27386)
+++ src/station_gui.cpp (working copy)
@@ -33,6 +33,7 @@
#include "town.h"
#include "linkgraph/linkgraph.h"
#include "zoom_func.h"
+#include "departures_gui.h"
#include "widgets/station_widget.h"
@@ -783,6 +784,8 @@
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),
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),
SetDataTip(STR_STATION_VIEW_CLOSE_AIRPORT, STR_STATION_VIEW_CLOSE_AIRPORT_TOOLTIP),
@@ -1949,6 +1952,11 @@
this->LowerWidget(WID_SV_SORT_ORDER);
break;
}
+
+ case WID_SV_DEPARTURES: {
+ ShowStationDepartures((StationID)this->window_number);
+ break;
+ }
}
}
Index: src/statusbar_gui.cpp
===================================================================
--- src/statusbar_gui.cpp (revision 27386)
+++ src/statusbar_gui.cpp (working copy)
@@ -139,8 +139,8 @@
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: {
@@ -221,6 +221,10 @@
{
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);
@@ -237,7 +241,7 @@
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(),
Index: src/stdafx.h
===================================================================
--- src/stdafx.h (revision 27386)
+++ src/stdafx.h (working copy)
@@ -516,4 +516,30 @@
#define IGNORE_UNINITIALIZED_WARNING_STOP
#endif
+/*
+ * Conditional define for the override keyword.
+ * Use of the override keyword can prevent various types of problems when the base method signature is changed, but derived overriding methods are not
+ * This is conditional to maintain compatibility with legacy compilers
+ */
+#if __cplusplus >= 201103L || defined(__STDCXX_VERSION__) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__)
+ #define OVERRIDE override
+#else
+ #define OVERRIDE
+#endif
+
+/**
+ * Using _mm_prefetch() with gcc implies the compile flag -msse.
+ * This is not the case with __builtin_prefetch() so the latter can be used in normal .cpp files.
+ */
+#if defined(_MSC_VER)
+ #define INCLUDE_FOR_PREFETCH_NTA
+ #define PREFETCH_NTA(address) _mm_prefetch((const char *) (address), _MM_HINT_NTA);
+#elif defined(__GNUC__)
+ #define INCLUDE_FOR_PREFETCH_NTA "stdafx.h"
+ #define PREFETCH_NTA(address) __builtin_prefetch((const void *) (address), 0, 0);
+#else
+ #define INCLUDE_FOR_PREFETCH_NTA "stdafx.h"
+ #define PREFETCH_NTA(address)
+#endif
+
#endif /* STDAFX_H */
Index: src/string.cpp
===================================================================
--- src/string.cpp (revision 27386)
+++ src/string.cpp (working copy)
@@ -131,6 +131,16 @@
return tmp;
}
+char *str_vfmt(const char *str, va_list va)
+{
+ char buf[4096];
+
+ int len = vseprintf(buf, lastof(buf), str, va);
+ char *p = MallocT(len + 1);
+ memcpy(p, buf, len + 1);
+ return p;
+}
+
/**
* Format, "printf", into a newly allocated string.
* @param str The formatting string.
@@ -138,15 +148,11 @@
*/
char *CDECL str_fmt(const char *str, ...)
{
- char buf[4096];
va_list va;
-
va_start(va, str);
- int len = vseprintf(buf, lastof(buf), str, va);
+ char *output = str_vfmt(str, va);
va_end(va);
- char *p = MallocT(len + 1);
- memcpy(p, buf, len + 1);
- return p;
+ return output;
}
/**
Index: src/string_func.h
===================================================================
--- src/string_func.h (revision 27386)
+++ src/string_func.h (working copy)
@@ -39,6 +39,7 @@
int CDECL vseprintf(char *str, const char *last, const char *format, va_list ap);
char *CDECL str_fmt(const char *str, ...) WARN_FORMAT(1, 2);
+char *str_vfmt(const char *str, va_list ap);
void str_validate(char *str, const char *last, StringValidationSettings settings = SVS_REPLACE_WITH_QUESTION_MARK);
void ValidateString(const char *str);
Index: src/strings.cpp
===================================================================
--- src/strings.cpp (revision 27386)
+++ src/strings.cpp (working copy)
@@ -405,6 +405,29 @@
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];
+ seprintf(hour, lastof(hour), "%02i", MINUTES_HOUR(minutes) );
+ seprintf(minute, lastof(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;
@@ -1202,6 +1225,42 @@
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;
Index: src/table/company_settings.ini
===================================================================
--- src/table/company_settings.ini (revision 27386)
+++ src/table/company_settings.ini (working copy)
@@ -18,8 +18,8 @@
[post-amble]
};
[templates]
-SDT_BOOL = SDT_BOOL($base, $var, $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),
+SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
SDT_END = SDT_END()
[defaults]
@@ -34,6 +34,7 @@
from = 0
to = SL_MAX_VERSION
cat = SC_ADVANCED
+extver = SlXvFeatureTest()
Index: src/table/control_codes.h
===================================================================
--- src/table/control_codes.h (revision 27386)
+++ src/table/control_codes.h (working copy)
@@ -66,6 +66,10 @@
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,
Index: src/table/currency_settings.ini
===================================================================
--- src/table/currency_settings.ini (revision 27386)
+++ src/table/currency_settings.ini (working copy)
@@ -11,9 +11,9 @@
[post-amble]
};
[templates]
-SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDT_CHR = SDT_CHR($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $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, $extver, NULL),
+SDT_CHR = SDT_CHR($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
SDT_END = SDT_END()
[defaults]
@@ -28,6 +28,7 @@
from = 0
to = SL_MAX_VERSION
cat = SC_ADVANCED
+extver = SlXvFeatureTest()
Index: src/table/darklight_colours.h
new file mode 100644
===================================================================
--- src/table/darklight_colours.h (revision 0)
+++ src/table/darklight_colours.h (working copy)
@@ -0,0 +1,52 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/**
+ * @file darklight_colours.h The colour tables to lighten and darken.
+ */
+
+static const byte _lighten_colour[] = {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, // 0
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x0F, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x0F, // 1
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x0F, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, // 2
+ 0x31, 0x0F, 0x33, 0x34, 0x45, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x0F, 0x3D, 0x3E, 0x3F, 0x40, // 3
+ 0x41, 0x42, 0x43, 0x44, 0x45, 0x0F, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x0F, // 4
+ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x0F, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x0F, // 5
+ 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x0F, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x0F, // 6
+ 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x0F, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x0F, // 7
+ 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x0F, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x0F, // 8
+ 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x0F, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, // 9
+ 0xA1, 0x0F, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0x0F, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, // A
+ 0xB1, 0x0F, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0x0F, // B
+ 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0x0F, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0x0F, 0xCF, 0xD0, // C
+ 0xD1, 0x0F, 0x0F, 0xD2, 0xD3, 0xD4, 0xD5, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0x0F, // D
+ 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0x0F, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0x0F, 0xE3, // E
+ 0xE8, 0xF2, 0xF3, 0xCE, 0x44, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0x0F, 0xFC, 0xFD, 0xFF, // F
+};
+
+static const byte _darken_colour[] = {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0x00, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 0
+ 0x02, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x02, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, // 1
+ 0x02, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x02, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, // 2
+ 0x2F, 0x30, 0x42, 0x32, 0x33, 0x02, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x02, 0x3C, 0x3D, 0x3E, // 3
+ 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x01, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, // 4
+ 0x02, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x02, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, // 5
+ 0x02, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x02, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, // 6
+ 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0xEF, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, // 7
+ 0x02, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x02, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, // 8
+ 0x02, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x02, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, // 9
+ 0x9F, 0xA0, 0xB4, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0x02, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, // A
+ 0xAF, 0xB0, 0x02, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, // B
+ 0x3F, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0x02, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xF3, 0xCE, // C
+ 0xCF, 0xD0, 0xD3, 0xD4, 0xD5, 0xD6, 0xDF, 0x02, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, // D
+ 0xE1, 0xE2, 0x02, 0xEF, 0xE3, 0xE4, 0xE5, 0xE6, 0xB8, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0x02, // E
+ 0xB5, 0x02, 0xF1, 0xF2, 0x43, 0x02, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFE, 0xF5, 0x0F // F
+};
Index: src/table/gameopt_settings.ini
===================================================================
--- src/table/gameopt_settings.ini (revision 27386)
+++ src/table/gameopt_settings.ini (working copy)
@@ -41,13 +41,13 @@
[post-amble]
};
[templates]
-SDTG_GENERAL = SDTG_GENERAL($name, $sdt_cmd, $sle_cmd, $type, $flags, $guiflags, $var, $length, $def, $min, $max, $interval, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDT_NULL = SDT_NULL($length, $from, $to),
-SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $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_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
+SDTG_GENERAL = SDTG_GENERAL($name, $sdt_cmd, $sle_cmd, $type, $flags, $guiflags, $var, $length, $def, $min, $max, $interval, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDT_NULL = SDT_NULL($length, $from, $to, $extver),
+SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat, $extver, NULL),
+SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
SDT_END = SDT_END()
[defaults]
@@ -62,6 +62,7 @@
from = 0
to = SL_MAX_VERSION
cat = SC_ADVANCED
+extver = SlXvFeatureTest()
Index: src/table/misc_settings.ini
===================================================================
--- src/table/misc_settings.ini (revision 27386)
+++ src/table/misc_settings.ini (working copy)
@@ -15,12 +15,12 @@
[post-amble]
};
[templates]
-SDTG_LIST = SDTG_LIST($name, $type, $length, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_MMANY = SDTG_MMANY($name, $type, $flags, $guiflags, $var, $def, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_STR = SDTG_STR($name, $type, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
+SDTG_LIST = SDTG_LIST($name, $type, $length, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_MMANY = SDTG_MMANY($name, $type, $flags, $guiflags, $var, $def, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_STR = SDTG_STR($name, $type, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
SDTG_END = SDTG_END()
[defaults]
@@ -35,6 +35,7 @@
from = 0
to = SL_MAX_VERSION
cat = SC_ADVANCED
+extver = SlXvFeatureTest()
@@ -255,7 +256,7 @@
var = _transparency_opt
def = 0
min = 0
-max = 0x1FF
+max = 0x3FF
cat = SC_BASIC
[SDTG_VAR]
@@ -264,7 +265,7 @@
var = _transparency_lock
def = 0
min = 0
-max = 0x1FF
+max = 0x3FF
cat = SC_BASIC
[SDTG_VAR]
Index: src/table/settings.h.preamble
===================================================================
--- src/table/settings.h.preamble (revision 27386)
+++ src/table/settings.h.preamble (working copy)
@@ -61,76 +61,76 @@
/* Macros for various objects to go in the configuration file.
* This section is for global variables */
-#define SDTG_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, var, length, def, min, max, interval, full, str, strhelp, strval, proc, from, to, cat)\
- {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, NULL, cat), SLEG_GENERAL(sle_cmd, var, type | flags, length, from, to)}
+#define SDTG_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, var, length, def, min, max, interval, full, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, NULL, cat), SLEG_GENERAL_X(sle_cmd, var, type | flags, length, from, to, extver), patxname}
-#define SDTG_VAR(name, type, flags, guiflags, var, def, min, max, interval, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(name, SDT_NUMX, SL_VAR, type, flags, guiflags, var, 0, def, min, max, interval, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTG_VAR(name, type, flags, guiflags, var, def, min, max, interval, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(name, SDT_NUMX, SL_VAR, type, flags, guiflags, var, 0, def, min, max, interval, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTG_BOOL(name, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(name, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, var, 0, def, 0, 1, 0, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTG_BOOL(name, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(name, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, var, 0, def, 0, 1, 0, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTG_LIST(name, type, length, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(name, SDT_INTLIST, SL_ARR, type, flags, guiflags, var, length, def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTG_LIST(name, type, length, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(name, SDT_INTLIST, SL_ARR, type, flags, guiflags, var, length, def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTG_STR(name, type, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(name, SDT_STRING, SL_STR, type, flags, guiflags, var, lengthof(var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTG_STR(name, type, flags, guiflags, var, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(name, SDT_STRING, SL_STR, type, flags, guiflags, var, lengthof(var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTG_OMANY(name, type, flags, guiflags, var, def, max, full, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(name, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat)
+#define SDTG_OMANY(name, type, flags, guiflags, var, def, max, full, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(name, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTG_MMANY(name, type, flags, guiflags, var, def, full, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(name, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, 0, 0, full, str, strhelp, strval, proc, from, to, cat)
+#define SDTG_MMANY(name, type, flags, guiflags, var, def, full, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(name, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, 0, 0, full, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTG_NULL(length, from, to)\
- {{"", NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLEG_NULL(length, from, to)}
+#define SDTG_NULL(length, from, to, extver)\
+ {{"", NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLEG_NULL_X(length, from, to, extver), NULL}
-#define SDTG_END() {{NULL, NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLEG_END()}
+#define SDTG_END() {{NULL, NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLEG_END(), NULL}
/* Macros for various objects to go in the configuration file.
* This section is for structures where their various members are saved */
-#define SDT_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, base, var, length, def, min, max, interval, full, str, strhelp, strval, proc, load, from, to, cat)\
- {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, load, cat), SLE_GENERAL(sle_cmd, base, var, type | flags, length, from, to)}
+#define SDT_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, base, var, length, def, min, max, interval, full, str, strhelp, strval, proc, load, from, to, cat, extver, patxname)\
+ {NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, interval, full, str, strhelp, strval, proc, load, cat), SLE_GENERAL_X(sle_cmd, base, var, type | flags, length, from, to, extver), patxname}
-#define SDT_VAR(base, var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat)\
- SDT_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, base, var, 1, def, min, max, interval, NULL, str, strhelp, strval, proc, NULL, from, to, cat)
+#define SDT_VAR(base, var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDT_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, base, var, 1, def, min, max, interval, NULL, str, strhelp, strval, proc, NULL, from, to, cat, extver, patxname)
-#define SDT_BOOL(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\
- SDT_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, base, var, 1, def, 0, 1, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat)
+#define SDT_BOOL(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDT_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, base, var, 1, def, 0, 1, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat, extver, patxname)
-#define SDT_LIST(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\
- SDT_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat)
+#define SDT_LIST(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDT_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat, extver, patxname)
-#define SDT_STR(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\
- SDT_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat)
+#define SDT_STR(base, var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDT_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat, extver, patxname)
-#define SDT_CHR(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\
- SDT_GENERAL(#var, SDT_STRING, SL_VAR, SLE_CHAR, flags, guiflags, base, var, 1, def, 0, 0, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat)
+#define SDT_CHR(base, var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDT_GENERAL(#var, SDT_STRING, SL_VAR, SLE_CHAR, flags, guiflags, base, var, 1, def, 0, 0, 0, NULL, str, strhelp, strval, proc, NULL, from, to, cat, extver, patxname)
-#define SDT_OMANY(base, var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, load, cat)\
- SDT_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, load, from, to, cat)
+#define SDT_OMANY(base, var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, load, cat, extver, patxname)\
+ SDT_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, load, from, to, cat, extver, patxname)
-#define SDT_MMANY(base, var, type, flags, guiflags, def, full, str, proc, strhelp, strval, from, to, cat)\
- SDT_GENERAL(#var, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, 0, 0, full, str, strhelp, strval, proc, NULL, from, to, cat)
+#define SDT_MMANY(base, var, type, flags, guiflags, def, full, str, proc, strhelp, strval, from, to, cat, extver, patxname)\
+ SDT_GENERAL(#var, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, 0, 0, full, str, strhelp, strval, proc, NULL, from, to, cat, extver, patxname)
-#define SDT_NULL(length, from, to)\
- {{"", NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLE_CONDNULL(length, from, to)}
+#define SDT_NULL(length, from, to, extver)\
+ {{"", NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLE_CONDNULL_X(length, from, to, extver), NULL}
-#define SDTC_VAR(var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, min, max, interval, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTC_VAR(var, type, flags, guiflags, def, min, max, interval, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, min, max, interval, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTC_BOOL(var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, _settings_client.var, 1, def, 0, 1, 0, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTC_BOOL(var, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, _settings_client.var, 1, def, 0, 1, 0, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTC_LIST(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, _settings_client.var, lengthof(_settings_client.var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTC_LIST(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, _settings_client.var, lengthof(_settings_client.var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTC_STR(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, _settings_client.var, lengthof(_settings_client.var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat)
+#define SDTC_STR(var, type, flags, guiflags, def, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, _settings_client.var, lengthof(_settings_client.var), def, 0, 0, 0, NULL, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDTC_OMANY(var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, cat)\
- SDTG_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat)
+#define SDTC_OMANY(var, type, flags, guiflags, def, max, full, str, strhelp, strval, proc, from, to, cat, extver, patxname)\
+ SDTG_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, _settings_client.var, 1, def, 0, max, 0, full, str, strhelp, strval, proc, from, to, cat, extver, patxname)
-#define SDT_END() {{NULL, NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLE_END()}
+#define SDT_END() {{NULL, NULL, {0}, {0}, 0, 0, 0, NULL, STR_NULL, STR_NULL, STR_NULL, NULL, NULL, SC_NONE}, SLE_END(), NULL}
Index: src/table/settings.ini
===================================================================
--- src/table/settings.ini (revision 27386)
+++ src/table/settings.ini (working copy)
@@ -48,6 +48,10 @@
static bool UpdateServerPassword(int32 p1);
static bool UpdateRconPassword(int32 p1);
static bool UpdateClientConfigValues(int32 p1);
+static bool CheckSharingRail(int32 p1);
+static bool CheckSharingRoad(int32 p1);
+static bool CheckSharingWater(int32 p1);
+static bool CheckSharingAir(int32 p1);
#endif /* ENABLE_NETWORK */
/* End - Callback Functions for the various settings */
@@ -64,19 +68,19 @@
[post-amble]
};
[templates]
-SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTC_BOOL = SDTC_BOOL( $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTC_LIST = SDTC_LIST( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-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_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),
-SDT_NULL = SDT_NULL($length, $from, $to),
+SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $guiflags, $var, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDTC_BOOL = SDTC_BOOL( $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDTC_LIST = SDTC_LIST( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDTC_STR = SDTC_STR( $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDTC_VAR = SDTC_VAR( $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDT_OMANY = SDT_OMANY($base, $var, $type, $flags, $guiflags, $def, $max, $full, $str, $strhelp, $strval, $proc, $from, $to, $load, $cat, $extver, $patxname),
+SDT_STR = SDT_STR($base, $var, $type, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, $patxname),
+SDT_NULL = SDT_NULL($length, $from, $to, $extver),
SDT_END = SDT_END()
[defaults]
@@ -91,6 +95,8 @@
from = 0
to = SL_MAX_VERSION
cat = SC_ADVANCED
+extver = SlXvFeatureTest()
+patxname = NULL
@@ -329,6 +335,41 @@
max = 3
cat = SC_BASIC
+[SDT_BOOL]
+base = GameSettings
+var = order.timetable_automated
+def = true
+str = STR_CONFIG_SETTING_TIMETABLE_AUTOMATED
+strhelp = STR_CONFIG_SETTING_TIMETABLE_AUTOMATED_HELPTEXT
+cat = SC_EXPERT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)
+patxname = ""auto_timetables.order.timetable_automated""
+
+[SDT_BOOL]
+base = GameSettings
+var = order.timetable_separation
+def = true
+str = STR_CONFIG_SETTING_TIMETABLE_SEPARATION
+strhelp = STR_CONFIG_SETTING_TIMETABLE_SEPARATION_HELPTEXT
+cat = SC_EXPERT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)
+patxname = ""auto_timetables.order.timetable_separation""
+
+[SDT_VAR]
+base = GameSettings
+var = order.timetable_separation_rate
+type = SLE_UINT8
+def = 100
+min = 0
+max = 100
+interval = 10
+str = STR_CONFIG_SETTING_TIMETABLE_SEPARATION_RATE
+strhelp = STR_CONFIG_SETTING_TIMETABLE_SEPARATION_RATE_HELPTEXT
+strval = STR_CONFIG_SETTING_PERCENTAGE
+cat = SC_EXPERT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)
+patxname = ""auto_timetables.order.timetable_separation_rate""
+
; There are only 21 predefined town_name values (0-20), but you can have more with newgrf action F so allow
; these bigger values (21-255). Invalid values will fallback to english on use and (undefined string) in GUI.
[SDT_OMANY]
@@ -541,6 +582,21 @@
strhelp = STR_CONFIG_SETTING_MAX_TUNNEL_LENGTH_HELPTEXT
strval = STR_CONFIG_SETTING_TILE_LENGTH
+[SDT_VAR]
+base = GameSettings
+var = construction.simulated_wormhole_signals
+type = SLE_UINT8
+flags = 0
+def = 2
+min = 1
+max = 16
+str = STR_CONFIG_SETTING_SIMULATE_SIGNALS
+strval = STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE
+proc = RedrawScreen
+from = 0
+cat = SC_BASIC
+patxname = ""signal_tunnel_bridge.construction.simulated_wormhole_signals""
+
# construction.longbridges
[SDT_NULL]
length = 1
@@ -753,6 +809,19 @@
strval = STR_CONFIG_SETTING_PERCENTAGE
strhelp = STR_CONFIG_SETTING_SHORT_PATH_SATURATION_HELPTEXT
+[SDT_VAR]
+base = GameSettings
+var = economy.town_cargo_factor
+type = SLE_INT8
+from = 160
+def = 0
+min = -16
+max = +8
+interval = 1
+str = STR_CONFIG_SETTING_TOWN_CARGO_FACTOR
+strval = STR_JUST_INT
+patxname = ""town_cargo_adj.economy.town_cargo_factor""
+
; Vehicles
[SDT_VAR]
@@ -1136,6 +1205,14 @@
strval = STR_CONFIG_SETTING_PLANE_CRASHES_NONE
cat = SC_BASIC
+[SDT_BOOL]
+base = GameSettings
+var = vehicle.improved_breakdowns
+guiflags = SGF_NO_NETWORK
+def = false
+str = STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS
+patxname = ""improved_breakdowns.vehicle.improved_breakdowns""
+
; station.join_stations
[SDT_NULL]
length = 1
@@ -1264,6 +1341,20 @@
[SDT_VAR]
base = GameSettings
+var = economy.day_length_factor
+type = SLE_UINT8
+def = 1
+min = 1
+max = 125
+str = STR_CONFIG_SETTING_DAY_LENGTH_FACTOR
+strhelp = STR_CONFIG_SETTING_DAY_LENGTH_FACTOR_HELPTEXT
+strval = STR_JUST_COMMA
+cat = SC_BASIC
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_VARIABLE_DAY_LENGTH)
+patxname = ""variable_day_length.economy.day_length_factor""
+
+[SDT_VAR]
+base = GameSettings
var = construction.raw_industry_construction
type = SLE_UINT8
guiflags = SGF_MULTISTRING
@@ -1458,6 +1549,121 @@
def = true
cat = SC_EXPERT
+[SDT_VAR]
+base = GameSettings
+var = construction.maximum_signal_evaluations
+type = SLE_UINT16
+def = 256
+min = 64
+max = 4096
+interval = 1
+str = STR_CONFIG_SETTING_MAX_SIGNAL_EVALUATIONS
+strhelp = STR_CONFIG_SETTING_MAX_SIGNAL_EVALUATIONS_HELPTEXT
+strval = STR_JUST_COMMA
+cat = SC_EXPERT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_PROG_SIGS)
+patxname = ""programmable_signals.construction.maximum_signal_evaluations""
+
+[SDT_BOOL]
+base = GameSettings
+var = economy.infrastructure_sharing[0]
+def = false
+str = STR_CONFIG_SETTING_SHARING_RAIL
+proc = CheckSharingRail
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.infrastructure_sharing.rail""
+
+[SDT_BOOL]
+base = GameSettings
+var = economy.infrastructure_sharing[1]
+def = false
+str = STR_CONFIG_SETTING_SHARING_ROAD
+proc = CheckSharingRoad
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.infrastructure_sharing.road""
+
+[SDT_BOOL]
+base = GameSettings
+var = economy.infrastructure_sharing[2]
+def = false
+str = STR_CONFIG_SETTING_SHARING_WATER
+proc = CheckSharingWater
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.infrastructure_sharing.water""
+
+[SDT_BOOL]
+base = GameSettings
+var = economy.infrastructure_sharing[3]
+def = false
+str = STR_CONFIG_SETTING_SHARING_AIR
+proc = CheckSharingAir
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.infrastructure_sharing.air""
+
+[SDT_VAR]
+base = GameSettings
+var = economy.sharing_fee[0]
+type = SLE_UINT
+guiflags = SGF_CURRENCY
+def = 100
+min = 0
+max = 1000000
+interval = 10
+str = STR_CONFIG_SETTING_SHARING_FEE_RAIL
+strval = STR_JUST_CURRENCY_LONG
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.sharing_fee.rail""
+
+[SDT_VAR]
+base = GameSettings
+var = economy.sharing_fee[1]
+type = SLE_UINT
+guiflags = SGF_CURRENCY
+def = 100
+min = 0
+max = 1000000
+interval = 10
+str = STR_CONFIG_SETTING_SHARING_FEE_ROAD
+strval = STR_JUST_CURRENCY_LONG
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.sharing_fee.road""
+
+[SDT_VAR]
+base = GameSettings
+var = economy.sharing_fee[2]
+type = SLE_UINT
+guiflags = SGF_CURRENCY
+def = 100
+min = 0
+max = 1000000
+interval = 10
+str = STR_CONFIG_SETTING_SHARING_FEE_WATER
+strval = STR_JUST_CURRENCY_LONG
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.sharing_fee.water""
+
+[SDT_VAR]
+base = GameSettings
+var = economy.sharing_fee[3]
+type = SLE_UINT
+guiflags = SGF_CURRENCY
+def = 100
+min = 0
+max = 1000000
+interval = 10
+str = STR_CONFIG_SETTING_SHARING_FEE_AIR
+strval = STR_JUST_CURRENCY_LONG
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.sharing_fee.air""
+
+[SDT_BOOL]
+base = GameSettings
+var = economy.sharing_payment_in_debt
+def = false
+str = STR_CONFIG_SETTING_SHARING_PAYMENT_IN_DEBT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRA_SHARING)
+patxname = ""infra_sharing.economy.sharing_payment_in_debt""
+
; previously ai-new setting.
[SDT_NULL]
length = 1
@@ -2145,6 +2351,21 @@
max = 1000000
cat = SC_EXPERT
+[SDT_VAR]
+base = GameSettings
+var = order.occupancy_smoothness
+type = SLE_UINT8
+def = 75
+min = 0
+max = 100
+interval = 10
+str = STR_CONFIG_OCCUPANCY_SMOOTHNESS
+strhelp = STR_CONFIG_OCCUPANCY_SMOOTHNESS_HELPTEXT
+strval = STR_CONFIG_SETTING_PERCENTAGE
+cat = SC_EXPERT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_ORDER_OCCUPANCY)
+patxname = ""order_occupancy.order.occupancy_smoothness""
+
##
[SDT_VAR]
base = GameSettings
@@ -2504,6 +2725,16 @@
proc = RedrawScreen
cat = SC_BASIC
+[SDT_BOOL]
+base = GameSettings
+var = vehicle.adjacent_crossings
+guiflags = SGF_NO_NETWORK
+def = true
+str = STR_CONFIG_SETTING_ADJACENT_CROSSINGS
+strhelp = STR_CONFIG_SETTING_ADJACENT_CROSSINGS_HELPTEXT
+cat = SC_BASIC
+patxname = ""adjacent_crossings.vehicle.adjacent_crossings""
+
;***************************************************************************
; Unsaved setting variables.
@@ -2786,6 +3017,104 @@
cat = SC_BASIC
[SDTC_BOOL]
+var = gui.viewport_map_scan_surroundings
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = true
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_SCAN_SURROUNDINGS
+proc = RedrawScreen
+
+[SDTC_BOOL]
+var = gui.show_slopes_on_viewport_map
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = true
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SLOPES
+proc = RedrawScreen
+
+[SDTC_BOOL]
+var = gui.show_bridges_on_map
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = true
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_BRIDGES
+proc = RedrawScreen
+
+[SDTC_BOOL]
+var = gui.show_tunnels_on_map
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = true
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_TUNNELS
+proc = RedrawScreen
+
+[SDTC_VAR]
+var = gui.show_vehicle_route
+type = SLE_UINT32
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+guiflags = SGF_MULTISTRING
+def = 0
+min = 0
+max = 1
+interval = 1
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_VEHICLE_ROUTE
+strval = STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_VEHICLE_ROUTE_NO
+proc = RedrawScreen
+
+[SDTC_VAR]
+var = gui.dash_level_of_route_lines
+type = SLE_UINT32
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+guiflags = SGF_0ISDISABLED
+def = 0
+min = 0
+max = 10
+interval = 1
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_DRAW_ROUTE_DASH
+strval = STR_CONFIG_SETTING_VIEWPORT_MAP_DRAW_ROUTE_DASH_VALUE
+proc = RedrawScreen
+cat = SC_EXPERT
+
+[SDTC_BOOL]
+var = gui.use_owner_colour_for_tunnelbridge
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = false
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_USE_OWNER_COLOUR_BRIDGE_TUNNEL
+proc = RedrawScreen
+
+[SDTC_VAR]
+var = gui.show_scrolling_viewport_on_map
+type = SLE_UINT32
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+guiflags = SGF_MULTISTRING
+def = 3
+min = 0
+max = 3
+interval = 1
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SCROLLING_VP
+strval = STR_CONFIG_SETTING_VIEWPORT_MAP_SHOW_SCROLLING_VP_NOTHING
+
+[SDTC_VAR]
+var = gui.default_viewport_map_mode
+type = SLE_UINT32
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+guiflags = SGF_MULTISTRING
+def = 0
+min = 0
+max = 2
+interval = 1
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_DEFAULT_MODE
+strval = STR_CONFIG_SETTING_VIEWPORT_MAP_DEFAULT_MODE_VEGETATION
+
+[SDTC_VAR]
+var = gui.action_when_viewport_map_is_dblclicked
+type = SLE_UINT32
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+guiflags = SGF_MULTISTRING
+def = 1
+min = 0
+max = 2
+interval = 1
+str = STR_CONFIG_SETTING_VIEWPORT_MAP_ACTION_DBLCLICK
+strval = STR_CONFIG_SETTING_VIEWPORT_MAP_ACTION_DBLCLICK_DO_NOTHING
+
+[SDTC_BOOL]
var = gui.pause_on_newgame
flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
def = false
@@ -2815,6 +3144,59 @@
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
@@ -2822,6 +3204,139 @@
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
@@ -3133,6 +3648,13 @@
strval = STR_JUST_COMMA
proc = RedrawScreen
+[SDTC_BOOL]
+var = gui.show_vehicle_route_steps
+flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
+def = true
+str = STR_CONFIG_SETTING_SHOW_VEHICLE_ROUTE_STEPS
+proc = RedrawScreen
+
; For the dedicated build we'll enable dates in logs by default.
[SDTC_BOOL]
ifdef = DEDICATED
@@ -3936,5 +4458,30 @@
strval = STR_CONFIG_SETTING_RIGHT_MOUSE_BTN_EMU_COMMAND
cat = SC_BASIC
+[SDT_BOOL]
+base = GameSettings
+var = vehicle.pay_for_repair
+def = true
+str = STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE
+strhelp = STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE_HELPTEXT
+cat = SC_EXPERT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_VEHICLE_REPAIR_COST)
+patxname = ""vehicle_repair_cost.vehicle.pay_for_repair""
+
+[SDT_VAR]
+base = GameSettings
+var = vehicle.repair_cost
+type = SLE_UINT8
+def = 100
+min = 1
+max = 255
+interval = 1
+str = STR_CONFIG_SETTING_REPAIR_COST
+strhelp = STR_CONFIG_SETTING_REPAIR_COST_HELPTEXT
+strval = STR_JUST_INT
+cat = SC_EXPERT
+extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_VEHICLE_REPAIR_COST)
+patxname = ""vehicle_repair_cost.vehicle.repair_cost""
+
[SDT_END]
Index: src/table/sprites.h
===================================================================
--- src/table/sprites.h (revision 27386)
+++ src/table/sprites.h (working copy)
@@ -163,7 +163,15 @@
static const SpriteID SPR_IMG_CARGOFLOW = SPR_OPENTTD_BASE + 174;
-static const SpriteID SPR_SIGNALS_BASE = SPR_OPENTTD_BASE + OPENTTD_SPRITE_COUNT;
+/** Sprites for the route step marker. */
+static const SpriteID SPR_ROUTE_STEP_BASE = SPR_OPENTTD_BASE + OPENTTD_SPRITE_COUNT;
+static const SpriteID SPR_ROUTE_STEP_TOP = SPR_ROUTE_STEP_BASE + 0;
+static const SpriteID SPR_ROUTE_STEP_MIDDLE = SPR_ROUTE_STEP_BASE + 1;
+static const SpriteID SPR_ROUTE_STEP_BOTTOM = SPR_ROUTE_STEP_BASE + 2;
+static const SpriteID SPR_ROUTE_STEP_BOTTOM_SHADOW = SPR_ROUTE_STEP_BASE + 3;
+static const SpriteID ROUTE_STEP_SPRITE_COUNT = 4;
+
+static const SpriteID SPR_SIGNALS_BASE = SPR_ROUTE_STEP_BASE + ROUTE_STEP_SPRITE_COUNT;
static const uint16 PRESIGNAL_SPRITE_COUNT = 48;
static const uint16 PRESIGNAL_AND_SEMAPHORE_SPRITE_COUNT = 112;
static const uint16 PRESIGNAL_SEMAPHORE_AND_PBS_SPRITE_COUNT = 240;
@@ -296,8 +304,22 @@
static const SpriteID SPR_PALETTE_BASE = SPR_EMPTY_BOUNDING_BOX + EMPTY_BOUNDING_BOX_SPRITE_COUNT;
static const uint16 PALETTE_SPRITE_COUNT = 1;
+/* Programmable signal sprites */
+static const SpriteID SPR_PROGSIGNAL_BASE = SPR_PALETTE_BASE + PALETTE_SPRITE_COUNT;
+static const uint16 PROGSIGNAL_SPRITE_COUNT = 32;
+
+/* Zoning sprites */
+static const SpriteID SPR_ZONING_INNER_HIGHLIGHT_BASE = SPR_PROGSIGNAL_BASE + PROGSIGNAL_SPRITE_COUNT;
+static const uint16 ZONING_INNER_HIGHLIGHT_SPRITE_COUNT = 32;
+static const SpriteID SPR_ZONING_INNER_HIGHLIGHT_RED = SPR_ZONING_INNER_HIGHLIGHT_BASE + 19;
+static const SpriteID SPR_ZONING_INNER_HIGHLIGHT_GREEN = SPR_ZONING_INNER_HIGHLIGHT_BASE + 20;
+static const SpriteID SPR_ZONING_INNER_HIGHLIGHT_BLACK = SPR_ZONING_INNER_HIGHLIGHT_BASE + 21;
+static const SpriteID SPR_ZONING_INNER_HIGHLIGHT_LIGHT_BLUE = SPR_ZONING_INNER_HIGHLIGHT_BASE + 22;
+static const SpriteID SPR_ZONING_INNER_HIGHLIGHT_ORANGE = SPR_ZONING_INNER_HIGHLIGHT_BASE + 23;
+static const SpriteID SPR_ZONING_INNER_HIGHLIGHT_WHITE = SPR_ZONING_INNER_HIGHLIGHT_BASE + 24;
+
/* From where can we start putting NewGRFs? */
-static const SpriteID SPR_NEWGRFS_BASE = SPR_PALETTE_BASE + PALETTE_SPRITE_COUNT;
+static const SpriteID SPR_NEWGRFS_BASE = SPR_ZONING_INNER_HIGHLIGHT_BASE + ZONING_INNER_HIGHLIGHT_SPRITE_COUNT;
/* Manager face sprites */
static const SpriteID SPR_GRADIENT = 874; // background gradient behind manager face
@@ -1309,12 +1331,14 @@
static const SpriteID SPR_IMG_SIGNAL_ELECTRIC_COMBO = SPR_SIGNALS_BASE + 44;
static const SpriteID SPR_IMG_SIGNAL_ELECTRIC_PBS = SPR_SIGNALS_BASE + 124;
static const SpriteID SPR_IMG_SIGNAL_ELECTRIC_PBS_OWAY = SPR_SIGNALS_BASE + 140;
+static const SpriteID SPR_IMG_SIGNAL_ELECTRIC_PROG = SPR_PROGSIGNAL_BASE + 28;
static const SpriteID SPR_IMG_SIGNAL_SEMAPHORE_NORM = SPR_SIGNALS_BASE + 60;
static const SpriteID SPR_IMG_SIGNAL_SEMAPHORE_ENTRY = SPR_SIGNALS_BASE + 76;
static const SpriteID SPR_IMG_SIGNAL_SEMAPHORE_EXIT = SPR_SIGNALS_BASE + 92;
static const SpriteID SPR_IMG_SIGNAL_SEMAPHORE_COMBO = SPR_SIGNALS_BASE + 108;
static const SpriteID SPR_IMG_SIGNAL_SEMAPHORE_PBS = SPR_SIGNALS_BASE + 188;
static const SpriteID SPR_IMG_SIGNAL_SEMAPHORE_PBS_OWAY= SPR_SIGNALS_BASE + 204;
+static const SpriteID SPR_IMG_SIGNAL_SEMAPHORE_PROG = SPR_PROGSIGNAL_BASE + 12;
static const SpriteID SPR_IMG_SIGNAL_CONVERT = SPR_OPENTTD_BASE + 135;
static const SpriteID SPR_IMG_TUNNEL_RAIL = 2430;
Index: src/table/strgen_tables.h
===================================================================
--- src/table/strgen_tables.h (revision 27386)
+++ src/table/strgen_tables.h (working copy)
@@ -91,6 +91,10 @@
{"DATE_SHORT", EmitSingleChar, SCC_DATE_SHORT, 1, -1, C_CASE},
{"DATE_LONG", EmitSingleChar, SCC_DATE_LONG, 1, -1, C_CASE},
{"DATE_ISO", EmitSingleChar, SCC_DATE_ISO, 1, -1, C_NONE},
+ {"DATE_WALLCLOCK_TINY", EmitSingleChar, SCC_DATE_WALLCLOCK_TINY, 1, -1, C_NONE},
+ {"DATE_WALLCLOCK_SHORT", EmitSingleChar, SCC_DATE_WALLCLOCK_SHORT, 1, -1, C_NONE},
+ {"DATE_WALLCLOCK_LONG", EmitSingleChar, SCC_DATE_WALLCLOCK_LONG, 1, -1, C_NONE},
+ {"DATE_WALLCLOCK_ISO", EmitSingleChar, SCC_DATE_WALLCLOCK_ISO, 1, -1, C_NONE},
{"STRING", EmitSingleChar, SCC_STRING, 1, -1, C_CASE | C_GENDER},
{"RAW_STRING", EmitSingleChar, SCC_RAW_STRING_POINTER, 1, -1, C_NONE | C_GENDER},
Index: src/table/tree_land.h
===================================================================
--- src/table/tree_land.h (revision 27386)
+++ src/table/tree_land.h (working copy)
@@ -12,8 +12,11 @@
#ifndef TREE_LAND_H
#define TREE_LAND_H
+#include "../sprite.h"
+
static const byte _tree_base_by_landscape[4] = {0, 12, 20, 32};
static const byte _tree_count_by_landscape[4] = {12, 8, 12, 9};
+#define MAX_TREE_COUNT_BY_LANDSCAPE 12
struct TreePos {
uint8 x;
@@ -227,4 +230,18 @@
{ { 0x716, PAL_NONE }, { 0x701, PAL_NONE }, { 0x6fa, PAL_NONE }, { 0x716, PAL_NONE } }, // 31
};
+/** Tree Sprites with their palettes */
+static const PalSpriteID _tree_sprites[] = {
+ { 1621, PAL_NONE }, { 1587, PAL_NONE }, { 1656, PAL_NONE }, { 1579, PAL_NONE },
+ { 1607, PAL_NONE }, { 1593, PAL_NONE }, { 1614, PAL_NONE }, { 1586, PAL_NONE },
+ { 1663, PAL_NONE }, { 1677, PAL_NONE }, { 1691, PAL_NONE }, { 1705, PAL_NONE },
+ { 1711, PAL_NONE }, { 1746, PAL_NONE }, { 1753, PAL_NONE }, { 1732, PAL_NONE },
+ { 1739, PAL_NONE }, { 1718, PAL_NONE }, { 1725, PAL_NONE }, { 1760, PAL_NONE },
+ { 1838, PAL_NONE }, { 1844, PAL_NONE }, { 1866, PAL_NONE }, { 1871, PAL_NONE },
+ { 1899, PAL_NONE }, { 1935, PAL_NONE }, { 1928, PAL_NONE }, { 1915, PAL_NONE },
+ { 1887, PAL_NONE }, { 1908, PAL_NONE }, { 1824, PAL_NONE }, { 1943, PAL_NONE },
+ { 1950, PAL_NONE }, { 1957, PALETTE_TO_GREEN }, { 1964, PALETTE_TO_RED }, { 1971, PAL_NONE },
+ { 1978, PAL_NONE }, { 1985, PALETTE_TO_RED, }, { 1992, PALETTE_TO_PALE_GREEN }, { 1999, PALETTE_TO_YELLOW }, { 2006, PALETTE_TO_RED }
+};
+
#endif /* TREE_LAND_H */
Index: src/table/win32_settings.ini
===================================================================
--- src/table/win32_settings.ini (revision 27386)
+++ src/table/win32_settings.ini (working copy)
@@ -17,8 +17,8 @@
};
#endif /* WIN32 */
[templates]
-SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat),
-SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat),
+SDTG_BOOL = SDTG_BOOL($name, $flags, $guiflags, $var, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDTG_VAR = SDTG_VAR($name, $type, $flags, $guiflags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
SDTG_END = SDTG_END()
[defaults]
@@ -33,6 +33,7 @@
from = 0
to = SL_MAX_VERSION
cat = SC_ADVANCED
+extver = SlXvFeatureTest()
Index: src/table/window_settings.ini
===================================================================
--- src/table/window_settings.ini (revision 27386)
+++ src/table/window_settings.ini (working copy)
@@ -12,8 +12,8 @@
[post-amble]
};
[templates]
-SDT_BOOL = SDT_BOOL($base, $var, $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),
+SDT_BOOL = SDT_BOOL($base, $var, $flags, $guiflags, $def, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
+SDT_VAR = SDT_VAR($base, $var, $type, $flags, $guiflags, $def, $min, $max, $interval, $str, $strhelp, $strval, $proc, $from, $to, $cat, $extver, NULL),
SDT_END = SDT_END()
[defaults]
@@ -29,6 +29,7 @@
from = 0
to = SL_MAX_VERSION
cat = SC_ADVANCED
+extver = SlXvFeatureTest()
Index: src/terraform_gui.cpp
===================================================================
--- src/terraform_gui.cpp (revision 27386)
+++ src/terraform_gui.cpp (working copy)
@@ -32,6 +32,7 @@
#include "hotkeys.h"
#include "engine_base.h"
#include "terraform_gui.h"
+#include "town_gui.h"
#include "zoom_func.h"
#include "widgets/terraform_widget.h"
@@ -147,6 +148,11 @@
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_DEMOLISH_AREA);
}
+static void PlaceProc_Measure(TileIndex tile)
+{
+ VpStartPlaceSizing(tile, VPM_A_B_LINE, DDSP_MEASURE);
+}
+
/** Terra form toolbar managing class. */
struct TerraformToolbarWindow : Window {
int last_user_action; ///< Last started user action.
@@ -204,6 +210,11 @@
ShowBuildTreesToolbar();
break;
+ case WID_TT_MEASUREMENT_TOOL:
+ HandlePlacePushButton(this, WID_TT_MEASUREMENT_TOOL, SPR_CURSOR_QUERY, HT_RECT);
+ this->last_user_action = widget;
+ break;
+
case WID_TT_PLACE_SIGN: // Place sign button
HandlePlacePushButton(this, WID_TT_PLACE_SIGN, SPR_CURSOR_SIGN, HT_RECT);
this->last_user_action = widget;
@@ -240,6 +251,10 @@
DoCommandP(tile, OBJECT_OWNED_LAND, 0, CMD_BUILD_OBJECT | CMD_MSG(STR_ERROR_CAN_T_PURCHASE_THIS_LAND), CcPlaySound1E);
break;
+ case WID_TT_MEASUREMENT_TOOL:
+ PlaceProc_Measure(tile);
+ break;
+
case WID_TT_PLACE_SIGN: // Place sign button
PlaceProc_Sign(tile);
break;
@@ -271,6 +286,9 @@
case DDSP_LEVEL_AREA:
GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
break;
+ case DDSP_MEASURE:
+ //nothing to do, just draw a tooltip
+ break;
}
}
}
@@ -303,6 +321,7 @@
Hotkey('D' | WKC_GLOBAL_HOTKEY, "dynamite", WID_TT_DEMOLISH),
Hotkey('U', "buyland", WID_TT_BUY_LAND),
Hotkey('I', "trees", WID_TT_PLANT_TREES),
+ Hotkey('R', "ruler", WID_TT_MEASUREMENT_TOOL),
Hotkey('O', "placesign", WID_TT_PLACE_SIGN),
Hotkey('P', "placeobject", WID_TT_PLACE_OBJECT),
HOTKEY_LIST_END
@@ -331,6 +350,8 @@
SetFill(0, 1), SetDataTip(SPR_IMG_BUY_LAND, STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND),
NWidget(WWT_PUSHIMGBTN, COLOUR_DARK_GREEN, WID_TT_PLANT_TREES), SetMinimalSize(22, 22),
SetFill(0, 1), SetDataTip(SPR_IMG_PLANTTREES, STR_SCENEDIT_TOOLBAR_PLANT_TREES),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_TT_MEASUREMENT_TOOL), SetMinimalSize(22,22),
+ SetFill(0, 1), SetDataTip(SPR_IMG_QUERY, STR_LANDSCAPING_TOOLTIP_RULER_TOOL),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_TT_PLACE_SIGN), SetMinimalSize(22, 22),
SetFill(0, 1), SetDataTip(SPR_IMG_SIGN, STR_SCENEDIT_TOOLBAR_PLACE_SIGN),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_TT_SHOW_PLACE_OBJECT),
@@ -465,6 +486,9 @@
NWidget(WWT_IMGBTN, COLOUR_GREY, WID_ETT_PLACE_OBJECT), SetMinimalSize(23, 22),
SetFill(0, 1), SetDataTip(SPR_IMG_TRANSMITTER, STR_SCENEDIT_TOOLBAR_PLACE_OBJECT),
NWidget(NWID_SPACER), SetFill(1, 0),
+ NWidget(WWT_IMGBTN, COLOUR_GREY, WID_ETT_PLACE_HOUSE), SetMinimalSize(23, 22),
+ SetFill(0, 1), SetDataTip(SPR_IMG_TOWN, STR_SCENEDIT_TOOLBAR_PLACE_HOUSE),
+ NWidget(NWID_SPACER), SetFill(1, 0),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_SPACER), SetFill(1, 0),
@@ -609,6 +633,13 @@
ShowBuildObjectPicker();
break;
+ case WID_ETT_PLACE_HOUSE: // Place house button
+ if (HandlePlacePushButton(this, WID_ETT_PLACE_HOUSE, SPR_CURSOR_TOWN, HT_RECT)) {
+ ShowBuildHousePicker(this);
+ this->last_user_action = widget;
+ }
+ break;
+
case WID_ETT_INCREASE_SIZE:
case WID_ETT_DECREASE_SIZE: { // Increase/Decrease terraform size
int size = (widget == WID_ETT_INCREASE_SIZE) ? 1 : -1;
@@ -674,6 +705,10 @@
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CREATE_DESERT);
break;
+ case WID_ETT_PLACE_HOUSE: // Place house button
+ PlaceProc_House(tile);
+ break;
+
default: NOT_REACHED();
}
}
@@ -705,6 +740,8 @@
this->RaiseButtons();
this->SetDirty();
DeleteWindowById(WC_BUILD_OBJECT, 0);
+ DeleteWindowById(WC_BUILD_HOUSE, 0);
+ DeleteWindowById(WC_SELECT_STATION, 0);
}
static HotkeyList hotkeys;
@@ -731,6 +768,7 @@
Hotkey('R', "rocky", WID_ETT_PLACE_ROCKS),
Hotkey('T', "desert", WID_ETT_PLACE_DESERT),
Hotkey('O', "object", WID_ETT_PLACE_OBJECT),
+ Hotkey('H', "house", WID_ETT_PLACE_HOUSE),
HOTKEY_LIST_END
};
Index: src/tilearea_type.h
===================================================================
--- src/tilearea_type.h (revision 27386)
+++ src/tilearea_type.h (working copy)
@@ -12,6 +12,8 @@
#ifndef TILEAREA_TYPE_H
#define TILEAREA_TYPE_H
+#include "stdafx.h"
+#include INCLUDE_FOR_PREFETCH_NTA
#include "map_func.h"
/** Represents the covered area of e.g. a rail station */
@@ -184,6 +186,65 @@
}
};
+/** Iterator to iterate over a tile area (rectangle) of the map.
+ * It prefetches tiles once per row.
+ */
+class OrthogonalPrefetchTileIterator {
+private:
+ TileIndex tile; ///< The current tile we are at.
+ int w; ///< The width of the iterated area.
+ int x; ///< The current 'x' position in the rectangle.
+ int y; ///< The current 'y' position in the rectangle.
+
+public:
+ /**
+ * Construct the iterator.
+ * @param ta Area, i.e. begin point and width/height of to-be-iterated area.
+ */
+ OrthogonalPrefetchTileIterator(const TileArea &ta) : tile(ta.w == 0 || ta.h == 0 ? INVALID_TILE : ta.tile), w(ta.w), x(ta.w), y(ta.h)
+ {
+ PREFETCH_NTA(&_m[ta.tile]);
+ }
+
+ /** Some compilers really like this. */
+ virtual ~OrthogonalPrefetchTileIterator()
+ {
+ }
+
+ /**
+ * Get the tile we are currently at.
+ * @return The tile we are at, or INVALID_TILE when we're done.
+ */
+ inline operator TileIndex () const
+ {
+ return this->tile;
+ }
+
+ /**
+ * Move ourselves to the next tile in the rectangle on the map.
+ */
+ inline OrthogonalPrefetchTileIterator& operator ++()
+ {
+ assert(this->tile != INVALID_TILE);
+
+ if (--this->x > 0) {
+ this->tile++;
+ } else if (--this->y > 0) {
+ this->x = this->w;
+ this->tile += TileDiffXY(1, 1) - this->w;
+ PREFETCH_NTA(&_m[tile]);
+ } else {
+ this->tile = INVALID_TILE;
+ }
+ return *this;
+ }
+
+ virtual OrthogonalPrefetchTileIterator *Clone() const
+ {
+ return new OrthogonalPrefetchTileIterator(*this);
+ }
+};
+
/** Iterator to iterate over a diagonal area of the map. */
class DiagonalTileIterator : public TileIterator {
private:
@@ -230,5 +291,6 @@
* @param ta The tile area to search over.
*/
#define TILE_AREA_LOOP(var, ta) for (OrthogonalTileIterator var(ta); var != INVALID_TILE; ++var)
+#define TILE_AREA_LOOP_WITH_PREFETCH(var, ta) for (OrthogonalPrefetchTileIterator var(ta); var != INVALID_TILE; ++var)
#endif /* TILEAREA_TYPE_H */
Index: src/timetable.h
===================================================================
--- src/timetable.h (revision 27386)
+++ src/timetable.h (working copy)
@@ -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);
Index: src/timetable_cmd.cpp
===================================================================
--- src/timetable_cmd.cpp (revision 27386)
+++ src/timetable_cmd.cpp (working copy)
@@ -15,8 +15,10 @@
#include "date_func.h"
#include "window_func.h"
#include "vehicle_base.h"
+#include "settings_type.h"
#include "cmd_helper.h"
#include "core/sort_func.hpp"
+#include "settings_type.h"
#include "table/strings.h"
@@ -94,6 +96,7 @@
* - p1 = (bit 0-19) - Vehicle with the orders to change.
* - p1 = (bit 20-27) - Order index to modify.
* - p1 = (bit 28-29) - Timetable data to change (@see ModifyTimetableFlags)
+ * - p1 = (bit 30) - 0 to set timetable wait/travel time, 1 to clear it
* @param p2 The amount of time to wait.
* - p2 = (bit 0-15) - The data to modify as specified by p1 bits 28-29.
* 0 to clear times, UINT16_MAX to clear speed limit.
@@ -117,16 +120,20 @@
ModifyTimetableFlags mtf = Extract(p1);
if (mtf >= MTF_END) return CMD_ERROR;
+ bool clear_field = GB(p1, 30, 1) == 1;
+
int wait_time = order->GetWaitTime();
int travel_time = order->GetTravelTime();
int max_speed = order->GetMaxSpeed();
switch (mtf) {
case MTF_WAIT_TIME:
wait_time = GB(p2, 0, 16);
+ if (clear_field) assert(wait_time == 0);
break;
case MTF_TRAVEL_TIME:
travel_time = GB(p2, 0, 16);
+ if (clear_field) assert(travel_time == 0);
break;
case MTF_TRAVEL_SPEED:
@@ -144,6 +151,9 @@
if (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) return_cmd_error(STR_ERROR_TIMETABLE_NOT_STOPPING_HERE);
break;
+ case OT_GOTO_DEPOT:
+ break;
+
case OT_CONDITIONAL:
break;
@@ -158,15 +168,15 @@
switch (mtf) {
case MTF_WAIT_TIME:
/* Set time if changing the value or confirming an estimated time as timetabled. */
- if (wait_time != order->GetWaitTime() || (wait_time > 0 && !order->IsWaitTimetabled())) {
- ChangeTimetable(v, order_number, wait_time, MTF_WAIT_TIME, wait_time > 0);
+ if (wait_time != order->GetWaitTime() || (clear_field == order->IsWaitTimetabled())) {
+ ChangeTimetable(v, order_number, wait_time, MTF_WAIT_TIME, !clear_field);
}
break;
case MTF_TRAVEL_TIME:
/* Set time if changing the value or confirming an estimated time as timetabled. */
- if (travel_time != order->GetTravelTime() || (travel_time > 0 && !order->IsTravelTimetabled())) {
- ChangeTimetable(v, order_number, travel_time, MTF_TRAVEL_TIME, travel_time > 0);
+ if (travel_time != order->GetTravelTime() || (clear_field == order->IsTravelTimetabled())) {
+ ChangeTimetable(v, order_number, travel_time, MTF_TRAVEL_TIME, !clear_field);
}
break;
@@ -273,12 +283,17 @@
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;
if (timetable_all && !v->orders.list->IsCompleteTimetable()) return CMD_ERROR;
+#else
+ start_date = ((DateTicks)_date * DAY_TICKS) + _date_fract + (DateTicks)(int32)p2;
+#endif
if (flags & DC_EXEC) {
SmallVector vehs;
@@ -372,15 +387,200 @@
}
/**
+* Start or stop automatic management of timetables.
+ * @param tile Not used.
+ * @param flags Operation to perform.
+ * @param p1 Vehicle index.
+ * @param p2 Various bitstuffed elements
+ * - p2 = (bit 0) - Set to 1 to enable, 0 to disable automation.
+ * - p2 = (bit 1) - Ctrl was pressed. Used when disabling to keep times.
+ * @param text unused
+ * @return the cost of this operation or an error
+ */
+
+CommandCost CmdAutomateTimetable(TileIndex index, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ if (!_settings_game.order.timetable_automated) return CMD_ERROR;
+
+ VehicleID veh = GB(p1, 0, 16);
+
+ Vehicle *v = Vehicle::GetIfValid(veh);
+ if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
+
+ CommandCost ret = CheckOwnership(v->owner);
+ if (ret.Failed()) return ret;
+
+ if (flags & DC_EXEC) {
+ for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
+ if (HasBit(p2, 0)) {
+ /* Automated timetable. Set flags and clear current times. */
+ SetBit(v2->vehicle_flags, VF_AUTOMATE_TIMETABLE);
+ ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
+ ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
+ ClrBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
+ v2->timetable_start = 0;
+ v2->lateness_counter = 0;
+ v2->current_loading_time = 0;
+ v2->ClearSeparation();
+ } else {
+ /* De-automate timetable. Clear flags. */
+ ClrBit(v2->vehicle_flags, VF_AUTOMATE_TIMETABLE);
+ ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
+ ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
+ v2->ClearSeparation();
+ if (!HasBit(p2, 1)) {
+ /* Ctrl wasn't pressed, so clear all timetabled times. */
+ SetBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
+ v2->timetable_start = 0;
+ v2->lateness_counter = 0;
+ v2->current_loading_time = 0;
+ OrderList *orders = v2->orders.list;
+ if (orders != NULL) {
+ for (int i = 0; i < orders->GetNumOrders(); i++) {
+ ChangeTimetable(v2, i, 0, MTF_WAIT_TIME, true);
+ ChangeTimetable(v2, i, 0, MTF_TRAVEL_TIME, true);
+ }
+ }
+ }
+ }
+ SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
+ }
+ }
+
+ return CommandCost();
+}
+
+static inline bool IsOrderUsableForSeparation(const Order *order)
+{
+ if (order->IsType(OT_CONDITIONAL)) {
+ // Auto separation is unlikely to useful work at all if one of these is present, so give up
+ return false;
+ }
+
+ if (order->GetWaitTime() == 0 && order->IsType(OT_GOTO_STATION)) {
+ // non-station orders are permitted to have 0 wait times
+ return false;
+ }
+
+ if (order->GetTravelTime() == 0 && !order->IsTravelTimetabled()) {
+ // 0 travel times are permitted, if explicitly timetabled
+ // this is useful for depot service orders
+ return false;
+ }
+
+ return true;
+}
+
+int TimeToFinishOrder(Vehicle *v, int n)
+{
+ int left;
+ Order *order = v->GetOrder(n);
+ int wait_time = order->GetWaitTime();
+ int travel_time = order->GetTravelTime();
+ assert(order != NULL);
+ if (!IsOrderUsableForSeparation(order)) return -1;
+ if ((v->cur_real_order_index == n) && (v->last_station_visited == order->GetDestination())) {
+ if (v->current_loading_time > 0) {
+ left = wait_time - v->current_order_time;
+ } else {
+ left = wait_time;
+ }
+ if (left < 0) left = 0;
+ } else {
+ left = travel_time;
+ if (v->cur_real_order_index == n) left -= v->current_order_time;
+ if (left < 0) left = 0;
+ left +=wait_time;
+ }
+ return left;
+}
+
+int SeparationBetween(Vehicle *v1, Vehicle *v2)
+{
+ if (v1 == v2) return -1;
+ int separation = 0;
+ int time;
+ int n = v1->cur_real_order_index;
+ while (n != v2->cur_real_order_index) {
+ time = TimeToFinishOrder(v1, n);
+ if (time == -1) return -1;
+ separation += time;
+ n++;
+ if (n >= v1->GetNumOrders()) n = 0;
+ }
+ int time1 = TimeToFinishOrder(v1, n);
+ int time2 = TimeToFinishOrder(v2, n);
+ if (time1 == -1 || time2 == -1) return -1;
+ time = time1 - time2;
+ if (time < 0) {
+ for (n = 0; n < v1->GetNumOrders(); n++) {
+ Order *order = v1->GetOrder(n);
+ if (!IsOrderUsableForSeparation(order)) return -1;
+ time += order->GetTravelTime() + order->GetWaitTime();
+ }
+ }
+ separation += time;
+ assert(separation >= 0);
+ if (separation == 0) return -1;
+ return separation;
+}
+
+void UpdateSeparationOrder(Vehicle *v_start)
+{
+ /* First check if we have a vehicle ahead, and if not search for one. */
+ if (v_start->AheadSeparation() == NULL) {
+ v_start->InitSeparation();
+ }
+ if (v_start->AheadSeparation() == NULL) {
+ return;
+ }
+ /* Switch positions if necessary. */
+ int swaps = 0;
+ bool done = false;
+ while (!done) {
+ done = true;
+ int min_sep = SeparationBetween(v_start, v_start->AheadSeparation());
+ Vehicle *v = v_start;
+ do {
+ if (v != v_start) {
+ int tmp_sep = SeparationBetween(v_start, v);
+ if (tmp_sep < min_sep && tmp_sep != -1) {
+ swaps++;
+ if (swaps >= 50) {
+ return;
+ }
+ done = false;
+ v_start->ClearSeparation();
+ v_start->AddToSeparationBehind(v);
+ break;
+ }
+ }
+ int separation_ahead = SeparationBetween(v, v->AheadSeparation());
+ int separation_behind = SeparationBetween(v->BehindSeparation(), v);
+ if (separation_ahead != -1 && separation_behind != -1) {
+ int new_lateness = (separation_ahead - separation_behind) / 2;
+ v->lateness_counter = (new_lateness * _settings_game.order.timetable_separation_rate +
+ v->lateness_counter * (100 - _settings_game.order.timetable_separation_rate)) / 100;
+ }
+ v = v->AheadSeparation();
+ } while (v != v_start);
+ }
+}
+
+
+/**
* 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.
*/
void UpdateVehicleTimetable(Vehicle *v, bool travelling)
{
+ if (!travelling) v->current_loading_time++; // +1 because this time is one tick behind
uint time_taken = v->current_order_time;
+ uint time_loading = v->current_loading_time;
v->current_order_time = 0;
+ v->current_loading_time = 0;
if (v->current_order.IsType(OT_IMPLICIT)) return; // no timetabling of auto orders
@@ -394,6 +594,18 @@
bool just_started = false;
+ /* Start automated timetables at first opportunity */
+ if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED) && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
+ if (_settings_game.order.timetable_separation) v->ClearSeparation();
+ SetBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
+ v->lateness_counter = 0;
+ if (_settings_game.order.timetable_separation) UpdateSeparationOrder(v);
+ for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
+ SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
+ }
+ return;
+ }
+
/* This vehicle is arriving at the first destination in the timetable. */
if (v->cur_real_order_index == first_manual_order && travelling) {
/* If the start date hasn't been set, or it was set automatically when
@@ -403,7 +615,11 @@
just_started = !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
if (v->timetable_start != 0) {
- v->lateness_counter = (_date - v->timetable_start) * DAY_TICKS + _date_fract;
+#if WALLCLOCK_NETWORK_COMPATIBLE
+ v->lateness_counter = ((_date - v->timetable_start) * DAY_TICKS + _date_fract) * _settings_game.economy.day_length_factor + _tick_skip_counter;
+#else
+ v->lateness_counter = ((_date * DAY_TICKS) + _date_fract - v->timetable_start) * _settings_game.economy.day_length_factor + _tick_skip_counter;
+#endif
v->timetable_start = 0;
}
@@ -437,7 +653,7 @@
* 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. */
- uint time_to_set = CeilDiv(max(time_taken, 1U), DAY_TICKS) * DAY_TICKS;
+ uint time_to_set = CeilDiv(max(time_taken, 1U), DATE_UNIT_SIZE) * DATE_UNIT_SIZE;
if (travelling && (autofilling || !real_current_order->IsTravelTimetabled())) {
ChangeTimetable(v, v->cur_real_order_index, time_to_set, MTF_TRAVEL_TIME, autofilling);
@@ -459,12 +675,61 @@
uint timetabled = travelling ? real_current_order->GetTimetabledTravel() :
real_current_order->GetTimetabledWait();
+ /* Update the timetable to gradually shift order times towards the actual travel times. */
+ if (timetabled != 0 && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
+ int32 new_time;
+ if (travelling) {
+ new_time = time_taken;
+ } else {
+ new_time = time_loading;
+ }
+
+ if (new_time > (int32)timetabled * 4 && travelling) {
+ /* Possible jam, clear time and restart timetable for all vehicles.
+ * Otherwise we risk trains blocking 1-lane stations for long times. */
+ ChangeTimetable(v, v->cur_real_order_index, 0, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
+ for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
+ if (_settings_game.order.timetable_separation) v2->ClearSeparation();
+ ClrBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
+ SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
+ }
+ return;
+ } else if (new_time >= (int32)timetabled / 2) {
+ /* Compute running average, with sign conversion to avoid negative overflow. */
+ if (new_time < (int32)timetabled) {
+ new_time = ((int32)timetabled * 3 + new_time * 2 + 2) / 5;
+ } else {
+ new_time = ((int32)timetabled * 9 + new_time + 5) / 10;
+ }
+ } else {
+ /* new time is less than hald old time, set value directly */
+ }
+
+ if (new_time < 1) new_time = 1;
+ if (new_time != (int32)timetabled)
+ ChangeTimetable(v, v->cur_real_order_index, new_time, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
+ } else if (timetabled == 0 && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
+ /* Add times for orders that are not yet timetabled, even while not autofilling */
+ if (travelling)
+ ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
+ else
+ ChangeTimetable(v, v->cur_real_order_index, time_loading, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME, true);
+ }
+
/* Vehicles will wait at stations if they arrive early even if they are not
* timetabled to wait there, so make sure the lateness counter is updated
* when this happens. */
if (timetabled == 0 && (travelling || v->lateness_counter >= 0)) return;
- v->lateness_counter -= (timetabled - time_taken);
+ if (_settings_game.order.timetable_separation && HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) {
+ v->current_order_time = time_taken;
+ v->current_loading_time = time_loading;
+ UpdateSeparationOrder(v);
+ v->current_order_time = 0;
+ v->current_loading_time = 0;
+ } else {
+ v->lateness_counter -= (timetabled - time_taken);
+ }
/* When we are more late than this timetabled bit takes we (somewhat expensively)
* check how many ticks the (fully filled) timetable has. If a timetable cycle is
Index: src/timetable_gui.cpp
===================================================================
--- src/timetable_gui.cpp (revision 27386)
+++ src/timetable_gui.cpp (working copy)
@@ -24,6 +24,7 @@
#include "date_gui.h"
#include "vehicle_gui.h"
#include "settings_type.h"
+#include "viewport_func.h"
#include "widgets/timetable_widget.h"
@@ -47,11 +48,14 @@
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);
}
}
@@ -142,9 +146,13 @@
* @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
}
@@ -154,6 +162,7 @@
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 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.
@@ -171,6 +180,13 @@
this->owner = this->vehicle->owner;
}
+ ~TimetableWindow()
+ {
+ if (!FocusWindowById(WC_VEHICLE_VIEW, this->window_number)) {
+ MarkAllRouteStepsDirty(this);
+ }
+ }
+
/**
* Build the arrival-departure list for a given vehicle
* @param v the vehicle to make the list for
@@ -181,7 +197,7 @@
{
assert(HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED));
- bool travelling = (!v->current_order.IsType(OT_LOADING) || v->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE);
+ bool travelling = (!(v->current_order.IsType(OT_LOADING) || v->current_order.IsType(OT_WAITING)) || v->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE);
Ticks start_time = _date_fract - v->current_order_time;
FillTimetableArrivalDepartureTable(v, v->cur_real_order_index % v->GetNumOrders(), travelling, table, start_time);
@@ -195,6 +211,8 @@
case WID_VT_ARRIVAL_DEPARTURE_PANEL:
SetDParamMaxValue(0, MAX_YEAR * DAYS_IN_YEAR, 0, FS_SMALL);
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 */
@@ -310,7 +328,9 @@
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 = (order == NULL) ||
+ ((!(order->IsType(OT_GOTO_STATION) || (order->IsType(OT_GOTO_DEPOT) && !(order->GetDepotActionType() & ODATFB_HALT))) ||
+ (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL));
}
}
bool disable_speed = disable || selected % 2 != 1 || v->type == VEH_AIRCRAFT;
@@ -324,6 +344,7 @@
this->SetWidgetDisabledState(WID_VT_START_DATE, v->orders.list == NULL);
this->SetWidgetDisabledState(WID_VT_RESET_LATENESS, v->orders.list == NULL);
this->SetWidgetDisabledState(WID_VT_AUTOFILL, v->orders.list == NULL);
+ this->EnableWidget(WID_VT_AUTOMATE);
} else {
this->DisableWidget(WID_VT_START_DATE);
this->DisableWidget(WID_VT_CHANGE_TIME);
@@ -332,10 +353,17 @@
this->DisableWidget(WID_VT_CLEAR_SPEED);
this->DisableWidget(WID_VT_RESET_LATENESS);
this->DisableWidget(WID_VT_AUTOFILL);
+ this->DisableWidget(WID_VT_AUTOMATE);
this->DisableWidget(WID_VT_SHARED_ORDER_LIST);
}
this->SetWidgetLoweredState(WID_VT_AUTOFILL, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE));
+ this->SetWidgetLoweredState(WID_VT_AUTOMATE, HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE));
+ this->SetWidgetDisabledState(WID_VT_START_DATE, _settings_game.order.timetable_separation);
+ this->SetWidgetDisabledState(WID_VT_AUTOMATE, !_settings_game.order.timetable_automated);
+ this->SetWidgetDisabledState(WID_VT_CHANGE_TIME, HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE));
+ this->SetWidgetDisabledState(WID_VT_AUTOFILL, HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE));
+ this->SetWidgetDisabledState(WID_VT_CLEAR_TIME, HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE));
this->DrawWidgets();
}
@@ -433,7 +461,7 @@
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;
@@ -450,19 +478,19 @@
if (arr_dep[i / 2].arrival != INVALID_TICKS) {
DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_ARRIVAL_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
if (this->show_expected && i / 2 == earlyID) {
- SetDParam(0, _date + arr_dep[i / 2].arrival / DAY_TICKS);
- DrawString(time_left, time_right, y, STR_JUST_DATE_TINY, TC_GREEN);
+ SetDParam(0, ((DateTicks)_date * DAY_TICKS) + arr_dep[i / 2].arrival);
+ DrawString(time_left, time_right, y, STR_JUST_DATE_WALLCLOCK_TINY, TC_GREEN);
} else {
- SetDParam(0, _date + (arr_dep[i / 2].arrival + offset) / DAY_TICKS);
- DrawString(time_left, time_right, y, STR_JUST_DATE_TINY,
+ SetDParam(0, ((DateTicks)_date * DAY_TICKS) + (arr_dep[i / 2].arrival + offset));
+ DrawString(time_left, time_right, y, STR_JUST_DATE_WALLCLOCK_TINY,
show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
}
}
} else {
if (arr_dep[i / 2].departure != INVALID_TICKS) {
DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_DEPARTURE_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
- SetDParam(0, _date + (arr_dep[i/2].departure + offset) / DAY_TICKS);
- DrawString(time_left, time_right, y, STR_JUST_DATE_TINY,
+ SetDParam(0, ((DateTicks)_date * DAY_TICKS) + (arr_dep[i/2].departure + offset));
+ DrawString(time_left, time_right, y, STR_JUST_DATE_WALLCLOCK_TINY,
show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
}
}
@@ -484,14 +512,18 @@
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));
@@ -502,20 +534,22 @@
}
}
- static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected, bool speed)
+ static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected, bool speed, bool clear = false)
{
uint order_number = (selected + 1) / 2;
ModifyTimetableFlags mtf = (selected % 2 == 1) ? (speed ? MTF_TRAVEL_SPEED : MTF_TRAVEL_TIME) : MTF_WAIT_TIME;
if (order_number >= v->GetNumOrders()) order_number = 0;
- return v->index | (order_number << 20) | (mtf << 28);
+ return v->index | (order_number << 20) | (mtf << 28) | (clear ? 1 << 30 : 0);
}
virtual void OnClick(Point pt, int widget, int click_count)
{
const Vehicle *v = this->vehicle;
+ this->clicked_widget = widget;
+
switch (widget) {
case WID_VT_ORDER_VIEW: // Order view button
ShowOrdersWindow(v);
@@ -530,7 +564,18 @@
}
case WID_VT_START_DATE: // Change the date that the timetable starts.
- ShowSetDateWindow(this, v->index | (v->orders.list->IsCompleteTimetable() && _ctrl_pressed ? 1U << 20 : 0), _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 | (v->orders.list->IsCompleteTimetable() && _ctrl_pressed ? 1U << 20 : 0), _date, _cur_year, _cur_year + 15, ChangeTimetableStartCallback);
+ }
break;
case WID_VT_CHANGE_TIME: { // "Wait For" button.
@@ -544,7 +589,7 @@
if (order != NULL) {
uint time = (selected % 2 == 1) ? order->GetTravelTime() : order->GetWaitTime();
- 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);
@@ -578,7 +623,7 @@
}
case WID_VT_CLEAR_TIME: { // Clear waiting time.
- uint32 p1 = PackTimetableArgs(v, this->sel_index, false);
+ uint32 p1 = PackTimetableArgs(v, this->sel_index, false, true);
DoCommandP(0, p1, 0, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
break;
}
@@ -601,6 +646,14 @@
break;
}
+ case WID_VT_AUTOMATE: {
+ uint32 p2 = 0;
+ if (!HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) SetBit(p2, 0);
+ if (!_ctrl_pressed) SetBit(p2, 1);
+ DoCommandP(0, v->index, p2, CMD_AUTOMATE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ break;
+ }
+
case WID_VT_EXPECTED:
this->show_expected = !this->show_expected;
break;
@@ -619,18 +672,41 @@
const Vehicle *v = this->vehicle;
- uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
+ switch (this->clicked_widget) {
+ default: NOT_REACHED();
- 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;
- }
+ case WID_VT_CHANGE_SPEED:
+ case WID_VT_CHANGE_TIME: {
+ uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
+
+ 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);
+ uint32 p2 = minu(val, UINT16_MAX);
- DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
+ break;
+ }
+
+ case WID_VT_START_DATE: {
+ int32 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
+ 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;
+ }
+ }
}
virtual void OnResize()
@@ -645,8 +721,30 @@
void UpdateSelectionStates()
{
this->GetWidget(WID_VT_ARRIVAL_DEPARTURE_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : SZSP_NONE);
+ // this->GetWidget(TTV_AUTO_SELECTION)->SetDisplayedPlane(!_settings_game.order.timetable_automated ? 0 : 1);
this->GetWidget(WID_VT_EXPECTED_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : 1);
}
+
+ virtual void OnFocus(Window *previously_focused_window)
+ {
+ if (HasFocusedVehicleChanged(this->window_number, previously_focused_window)) {
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
+ }
+ }
+
+ virtual void OnFocusLost(Window *newly_focused_window)
+ {
+ if (HasFocusedVehicleChanged(this->window_number, newly_focused_window)) {
+ MarkAllRoutePathsDirty(this->vehicle);
+ MarkAllRouteStepsDirty(this);
+ }
+ }
+
+ const Vehicle *GetVehicle()
+ {
+ return this->vehicle;
+ }
};
static const NWidgetPart _nested_timetable_widgets[] = {
@@ -682,6 +780,7 @@
EndContainer(),
NWidget(NWID_VERTICAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_AUTOFILL), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_AUTOMATE), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_AUTOMATE, STR_TIMETABLE_AUTOMATE_TOOLTIP),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VT_EXPECTED_SELECTION),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_EXPECTED), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BLACK_STRING, STR_TIMETABLE_EXPECTED_TOOLTIP),
NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
@@ -690,6 +789,7 @@
EndContainer(),
NWidget(NWID_VERTICAL, NC_EQUALSIZE),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VT_SHARED_ORDER_LIST), SetFill(0, 1), SetDataTip(SPR_SHARED_ORDERS_ICON, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP),
+ NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
NWidget(WWT_RESIZEBOX, COLOUR_GREY), SetFill(0, 1),
EndContainer(),
EndContainer(),
Index: src/toolbar_gui.cpp
===================================================================
--- src/toolbar_gui.cpp (revision 27386)
+++ src/toolbar_gui.cpp (working copy)
@@ -46,7 +46,9 @@
#include "game/game.hpp"
#include "goal_base.h"
#include "story_base.h"
+#include "plans_func.h"
#include "toolbar_gui.h"
+#include "zoning.h"
#include "widgets/toolbar_widget.h"
@@ -289,6 +291,7 @@
OME_SETTINGS,
OME_SCRIPT_SETTINGS,
OME_NEWGRFSETTINGS,
+ OME_ZONING,
OME_TRANSPARENCIES,
OME_SHOW_TOWNNAMES,
OME_SHOW_STATIONNAMES,
@@ -317,6 +320,7 @@
* to network clients. */
if (!_networking || _network_server) *list->Append() = new DropDownListStringItem(STR_SETTINGS_MENU_SCRIPT_SETTINGS, OME_SCRIPT_SETTINGS, false);
*list->Append() = new DropDownListStringItem(STR_SETTINGS_MENU_NEWGRF_SETTINGS, OME_NEWGRFSETTINGS, false);
+ *list->Append() = new DropDownListStringItem(STR_SETTINGS_MENU_ZONING, OME_ZONING, false);
*list->Append() = new DropDownListStringItem(STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS, OME_TRANSPARENCIES, false);
*list->Append() = new DropDownListItem(-1, false);
*list->Append() = new DropDownListCheckedItem(STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED, OME_SHOW_TOWNNAMES, false, HasBit(_display_opt, DO_SHOW_TOWN_NAMES));
@@ -347,6 +351,7 @@
case OME_SETTINGS: ShowGameSettings(); return CBF_NONE;
case OME_SCRIPT_SETTINGS: ShowAIConfigWindow(); return CBF_NONE;
case OME_NEWGRFSETTINGS: ShowNewGRFSettings(!_networking && _settings_client.gui.UserIsAllowedToChangeNewGRFs(), true, true, &_grfconfig); return CBF_NONE;
+ case OME_ZONING: ShowZoningToolbar(); break;
case OME_TRANSPARENCIES: ShowTransparencyToolbar(); break;
case OME_SHOW_TOWNNAMES: ToggleBit(_display_opt, DO_SHOW_TOWN_NAMES); break;
@@ -451,6 +456,7 @@
MME_SHOW_SIGNLISTS,
MME_SHOW_TOWNDIRECTORY,
MME_SHOW_INDUSTRYDIRECTORY,
+ MME_SHOW_PLANS,
};
static CallBackFunction ToolbarMapClick(Window *w)
@@ -460,6 +466,7 @@
*list->Append() = new DropDownListStringItem(STR_MAP_MENU_EXTRA_VIEW_PORT, MME_SHOW_EXTRAVIEWPORTS, false);
*list->Append() = new DropDownListStringItem(STR_MAP_MENU_LINGRAPH_LEGEND, MME_SHOW_LINKGRAPH, false);
*list->Append() = new DropDownListStringItem(STR_MAP_MENU_SIGN_LIST, MME_SHOW_SIGNLISTS, false);
+ *list->Append() = new DropDownListStringItem(STR_MAP_MENU_PLAN_LIST, MME_SHOW_PLANS, false);
PopupMainToolbMenu(w, WID_TN_SMALL_MAP, list, 0);
return CBF_NONE;
}
@@ -491,6 +498,7 @@
case MME_SHOW_SIGNLISTS: ShowSignList(); break;
case MME_SHOW_TOWNDIRECTORY: ShowTownDirectory(); break;
case MME_SHOW_INDUSTRYDIRECTORY: ShowIndustryDirectory(); break;
+ case MME_SHOW_PLANS: ShowPlansWindow(); break;
}
return CBF_NONE;
}
@@ -609,7 +617,7 @@
if (_network_server) {
DoCommandP(0, 0, _network_own_client_id, CMD_COMPANY_CTRL);
} else {
- NetworkSendCommand(0, 0, 0, CMD_COMPANY_CTRL, NULL, NULL, _local_company);
+ NetworkSendCommand(0, 0, 0, CMD_COMPANY_CTRL, NULL, NULL, _local_company, 0);
}
return CBF_NONE;
@@ -1652,6 +1660,7 @@
MTHK_EXTRA_VIEWPORT,
MTHK_CLIENT_LIST,
MTHK_SIGN_LIST,
+ MTHK_PLAN_LIST,
};
/** Main toolbar. */
@@ -1747,6 +1756,7 @@
case MTHK_CLIENT_LIST: if (_networking) ShowClientList(); break;
#endif
case MTHK_SIGN_LIST: ShowSignList(); break;
+ case MTHK_PLAN_LIST: ShowPlansWindow(); break;
default: return ES_NOT_HANDLED;
}
return ES_HANDLED;
@@ -1852,6 +1862,7 @@
Hotkey((uint16)0, "client_list", MTHK_CLIENT_LIST),
#endif
Hotkey((uint16)0, "sign_list", MTHK_SIGN_LIST),
+ Hotkey('P', "plan_list", MTHK_PLAN_LIST),
HOTKEY_LIST_END
};
HotkeyList MainToolbarWindow::hotkeys("maintoolbar", maintoolbar_hotkeys);
Index: src/town.h
===================================================================
--- src/town.h (revision 27386)
+++ src/town.h (working copy)
@@ -18,6 +18,9 @@
#include "newgrf_storage.h"
#include "cargotype.h"
#include "tilematrix_type.hpp"
+#include "openttd.h"
+#include "table/strings.h"
+#include "company_func.h"
#include
template
@@ -75,6 +78,7 @@
CompanyByte exclusivity; ///< which company has exclusivity
uint8 exclusive_counter; ///< months till the exclusivity expires
int16 ratings[MAX_COMPANIES]; ///< ratings of each company for this town
+ StringID town_label; ///< Label dependent on _local_company rating.
TransportedCargoStat supplied[NUM_CARGO]; ///< Cargo statistics about supplied cargo.
TransportedCargoStat received[NUM_TE]; ///< Cargo statistics about received cargotypes.
@@ -113,6 +117,30 @@
void InitializeLayout(TownLayout layout);
+ void UpdateLabel();
+
+ /**
+ * Returns the correct town label, based on rating.
+ */
+ inline StringID Label() const{
+ if (!(_game_mode == GM_EDITOR) && (_local_company < MAX_COMPANIES)) {
+ return STR_VIEWPORT_TOWN_POP_VERY_POOR_RATING + this->town_label;
+ } else {
+ return _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_VIEWPORT_TOWN;
+ }
+ }
+
+ /**
+ * Returns the correct town small label, based on rating.
+ */
+ inline StringID SmallLabel() const{
+ if (!(_game_mode == GM_EDITOR) && (_local_company < MAX_COMPANIES)) {
+ return STR_VIEWPORT_TOWN_TINY_VERY_POOR_RATING + this->town_label;
+ } else {
+ return STR_VIEWPORT_TOWN_TINY_WHITE;
+ }
+ }
+
/**
* Calculate the max town noise.
* The value is counted using the population divided by the content of the
@@ -188,6 +216,7 @@
CommandCost CheckIfAuthorityAllowsNewStation(TileIndex tile, DoCommandFlag flags);
Town *ClosestTownFromTile(TileIndex tile, uint threshold);
void ChangeTownRating(Town *t, int add, int max, DoCommandFlag flags);
+HouseZonesBits TryGetTownRadiusGroup(const Town *t, TileIndex tile);
HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile);
void SetTownRatingTestMode(bool mode);
uint GetMaskOfTownActions(int *nump, CompanyID cid, const Town *t);
Index: src/town_cmd.cpp
===================================================================
--- src/town_cmd.cpp (revision 27386)
+++ src/town_cmd.cpp (working copy)
@@ -25,7 +25,6 @@
#include "genworld.h"
#include "newgrf_debug.h"
#include "newgrf_house.h"
-#include "newgrf_text.h"
#include "autoslope.h"
#include "tunnelbridge_map.h"
#include "strings_func.h"
@@ -46,6 +45,7 @@
#include "object_base.h"
#include "ai/ai.hpp"
#include "game/game.hpp"
+#include "zoom_func.h"
#include "table/strings.h"
#include "table/town_land.h"
@@ -163,6 +163,26 @@
}
/**
+ * Updates the town label of the town after changes in rating. The colour scheme is:
+ * Red: Appalling and Very poor ratings.
+ * Orange: Poor and mediocre ratings.
+ * Yellow: Good rating.
+ * White: Very good rating (standard).
+ * Green: Excellent and outstanding ratings.
+ */
+void Town::UpdateLabel()
+{
+ if (!(_game_mode == GM_EDITOR) && (_local_company < MAX_COMPANIES)) {
+ int r = this->ratings[_local_company];
+ (this->town_label = 0, r <= RATING_VERYPOOR) || // Appalling and Very Poor
+ (this->town_label++, r <= RATING_MEDIOCRE) || // Poor and Mediocre
+ (this->town_label++, r <= RATING_GOOD) || // Good
+ (this->town_label++, r <= RATING_VERYGOOD) || // Very Good
+ (this->town_label++, true); // Excellent and Outstanding
+ }
+}
+
+/**
* Get the cost for removing this house
* @return the cost (inflation corrected etc)
*/
@@ -189,6 +209,11 @@
AddChildSpriteScreen(SPR_LIFT, PAL_NONE, 14, 60 - GetLiftPosition(ti->tile));
}
+static void DrawHouseLiftInGUI(int x, int y)
+{
+ DrawSprite(SPR_LIFT, PAL_NONE, x - 18, y + 7);
+}
+
typedef void TownDrawTileProc(const TileInfo *ti);
static TownDrawTileProc * const _town_draw_tile_procs[1] = {
TownDrawHouseLift
@@ -258,6 +283,85 @@
}
}
+static void DrawOldHouseTileInGUI(int x, int y, HouseID house_id, bool ground)
+{
+ /* Retrieve pointer to the draw town tile struct */
+ const DrawBuildingsTileStruct *dcts = &_town_draw_tile_data[house_id << 4 | TOWN_HOUSE_COMPLETED];
+ if (ground) {
+ /* Draw the ground sprite */
+ DrawSprite(dcts->ground.sprite, dcts->ground.pal, x, y);
+ } else {
+ /* Add a house on top of the ground? */
+ if (dcts->building.sprite != 0) {
+ DrawSprite(dcts->building.sprite, dcts->building.pal, x + dcts->subtile_x, y + dcts->subtile_y);
+ }
+ /* Draw the lift */
+ if (dcts->draw_proc == 1) DrawHouseLiftInGUI(x, y);
+ }
+}
+
+/**
+ * Draw image of a house. Image will be centered between the \c left and the \c right and verticaly aligned to the \c bottom.
+ *
+ * @param house_id house type
+ * @param left left bound of the drawing area
+ * @param top top bound of the drawing area
+ * @param right right bound of the drawing area
+ * @param bottom bottom bound of the drawing area
+ */
+void DrawHouseImage(HouseID house_id, int left, int top, int right, int bottom)
+{
+ DrawPixelInfo tmp_dpi;
+ if (!FillDrawPixelInfo(&tmp_dpi, left, top, right - left + 1, bottom - top + 1)) return;
+ DrawPixelInfo *old_dpi = _cur_dpi;
+ _cur_dpi = &tmp_dpi;
+
+ const HouseSpec *hs = HouseSpec::Get(house_id);
+
+ /* sprites are relative to the topmost pixel of the ground tile */
+ uint x = (right - left + 1) / 2;
+ uint y = bottom - top + 1 - TILE_PIXELS;
+ if (hs->building_flags & TILE_SIZE_1x2) x -= TILE_PIXELS / 2;
+ if (hs->building_flags & TILE_SIZE_2x1) x += TILE_PIXELS / 2;
+ if (hs->building_flags & BUILDING_HAS_2_TILES) y -= TILE_PIXELS / 2;
+ if (hs->building_flags & BUILDING_HAS_4_TILES) y -= TILE_PIXELS / 2;
+
+ bool new_house = false;
+ if (house_id >= NEW_HOUSE_OFFSET) {
+ /* Houses don't necessarily need new graphics. If they don't have a
+ * spritegroup associated with them, then the sprite for the substitute
+ * house id is drawn instead. */
+ if (hs->grf_prop.spritegroup[0] != NULL) {
+ new_house = true;
+ } else {
+ house_id = hs->grf_prop.subst_id;
+ }
+ }
+
+ uint num_row = (hs->building_flags & BUILDING_2_TILES_X) ? 2 : 1;
+ uint num_col = (hs->building_flags & BUILDING_2_TILES_Y) ? 2 : 1;
+
+ for (bool ground = true; ; ground = !ground) {
+ HouseID hid = house_id;
+ for (uint row = 0; row < num_row; row++) {
+ for (uint col = 0; col < num_col; col++) {
+ Point offset = RemapCoords(row * TILE_SIZE, col * TILE_SIZE, 0); // offset for current tile
+ offset.x = UnScaleByZoom(offset.x, ZOOM_LVL_GUI);
+ offset.y = UnScaleByZoom(offset.y, ZOOM_LVL_GUI);
+ if (new_house) {
+ DrawNewHouseTileInGUI(x + offset.x, y + offset.y, hid, ground);
+ } else {
+ DrawOldHouseTileInGUI(x + offset.x, y + offset.y, hid, ground);
+ }
+ hid++;
+ }
+ }
+ if (!ground) break;
+ }
+
+ _cur_dpi = old_dpi;
+}
+
static int GetSlopePixelZ_Town(TileIndex tile, uint x, uint y)
{
return GetTileMaxPixelZ(tile);
@@ -330,7 +434,7 @@
DeleteAnimatedTile(tile);
}
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
/**
@@ -373,12 +477,11 @@
*/
void Town::UpdateVirtCoord()
{
+ this->UpdateLabel();
Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE);
SetDParam(0, this->index);
SetDParam(1, this->cache.population);
- this->cache.sign.UpdatePosition(pt.x, pt.y - 24 * ZOOM_LVL_BASE,
- _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_VIEWPORT_TOWN,
- STR_VIEWPORT_TOWN);
+ this->cache.sign.UpdatePosition(pt.x, pt.y - 24 * ZOOM_LVL_BASE, this->Label(), STR_VIEWPORT_TOWN);
SetWindowDirty(WC_TOWN_VIEW, this->index);
}
@@ -441,7 +544,7 @@
ChangePopulation(Town::GetByTile(tile), HouseSpec::Get(GetHouseType(tile))->population);
ResetHouseAge(tile);
}
- MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile, ZOOM_LVL_DRAW_MAP);
}
/**
@@ -458,6 +561,61 @@
}
/**
+ * Generate cargo for a town (house).
+ *
+ * The amount of cargo should be and will be greater than zero.
+ *
+ * @param t current town
+ * @param ct type of cargo to generate, usually CT_PASSENGERS or CT_MAIL
+ * @param amount how many units of cargo
+ * @param stations available stations for this house
+ */
+static void TownGenerateCargo (Town *t, CargoID ct, uint amount, StationFinder &stations)
+{
+ // custom cargo generation factor
+ int cf = _settings_game.economy.town_cargo_factor;
+
+ // when the economy flunctuates, everyone wants to stay at home
+ if (EconomyIsInRecession()) {
+ amount = (amount + 1) >> 1;
+ }
+
+ // apply custom factor?
+ if (cf < 0) {
+ // approx (amount / 2^cf)
+ // adjust with a constant offset of {(2 ^ cf) - 1} (i.e. add cf * 1-bits) before dividing to ensure that it doesn't become zero
+ // this skews the curve a little so that isn't entirely exponential, but will still decrease
+ amount = (amount + ((1 << -cf) - 1)) >> -cf;
+ }
+
+ else if (cf > 0) {
+ // approx (amount * 2^cf)
+ // XXX: overflow?
+ amount = amount << cf;
+ }
+
+ // with the adjustments above, this should never happen
+ assert(amount > 0);
+
+ // calculate for town stats
+ const CargoSpec *cs = CargoSpec::Get(ct);
+ switch (cs->town_effect) {
+ case TE_PASSENGERS:
+ t->supplied[CT_PASSENGERS].new_max += amount;
+ t->supplied[CT_PASSENGERS].new_act += MoveGoodsToStation(CT_PASSENGERS, amount, ST_TOWN, t->index, stations.GetStations());
+ break;
+
+ case TE_MAIL:
+ t->supplied[CT_MAIL].new_max += amount;
+ t->supplied[CT_MAIL].new_act += MoveGoodsToStation(CT_MAIL, amount, ST_TOWN, t->index, stations.GetStations());
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
* Tile callback function.
*
* Periodic tic handler for houses and town
@@ -504,27 +662,20 @@
uint amt = GB(callback, 0, 8);
if (amt == 0) continue;
- uint moved = MoveGoodsToStation(cargo, amt, ST_TOWN, t->index, stations.GetStations());
-
- const CargoSpec *cs = CargoSpec::Get(cargo);
- t->supplied[cs->Index()].new_max += amt;
- t->supplied[cs->Index()].new_act += moved;
+ // XXX: no economy flunctuation for GRF cargos?
+ TownGenerateCargo(t, cargo, amt, stations);
}
} else {
if (GB(r, 0, 8) < hs->population) {
uint amt = GB(r, 0, 8) / 8 + 1;
- 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());
+ TownGenerateCargo(t, CT_PASSENGERS, amt, stations);
}
if (GB(r, 8, 8) < hs->mail_generation) {
uint amt = GB(r, 8, 8) / 8 + 1;
- 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());
+ TownGenerateCargo(t, CT_MAIL, amt, stations);
}
}
@@ -574,13 +725,12 @@
return cost;
}
-static void AddProducedCargo_Town(TileIndex tile, CargoArray &produced)
+void AddProducedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &produced)
{
- HouseID house_id = GetHouseType(tile);
const HouseSpec *hs = HouseSpec::Get(house_id);
- Town *t = Town::GetByTile(tile);
if (HasBit(hs->callback_mask, CBM_HOUSE_PRODUCE_CARGO)) {
+ Town *t = (tile == INVALID_TILE) ? NULL : Town::GetByTile(tile);
for (uint i = 0; i < 256; i++) {
uint16 callback = GetHouseCallback(CBID_HOUSE_PRODUCE_CARGO, i, 0, house_id, t, tile);
@@ -601,6 +751,11 @@
}
}
+static void AddProducedCargo_Town(TileIndex tile, CargoArray &produced)
+{
+ AddProducedHouseCargo(GetHouseType(tile), tile, produced);
+}
+
static inline void AddAcceptedCargoSetMask(CargoID cargo, uint amount, CargoArray &acceptance, uint32 *always_accepted)
{
if (cargo == CT_INVALID || amount == 0) return;
@@ -608,9 +763,10 @@
SetBit(*always_accepted, cargo);
}
-static void AddAcceptedCargo_Town(TileIndex tile, CargoArray &acceptance, uint32 *always_accepted)
+void AddAcceptedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &acceptance, uint32 *always_accepted)
{
- const HouseSpec *hs = HouseSpec::Get(GetHouseType(tile));
+ const HouseSpec *hs = HouseSpec::Get(house_id);
+ Town *t = (tile == INVALID_TILE) ? NULL : Town::GetByTile(tile);
CargoID accepts[3];
/* Set the initial accepted cargo types */
@@ -620,7 +776,7 @@
/* Check for custom accepted cargo types */
if (HasBit(hs->callback_mask, CBM_HOUSE_ACCEPT_CARGO)) {
- uint16 callback = GetHouseCallback(CBID_HOUSE_ACCEPT_CARGO, 0, 0, GetHouseType(tile), Town::GetByTile(tile), tile);
+ uint16 callback = GetHouseCallback(CBID_HOUSE_ACCEPT_CARGO, 0, 0, house_id, t, tile);
if (callback != CALLBACK_FAILED) {
/* Replace accepted cargo types with translated values from callback */
accepts[0] = GetCargoTranslation(GB(callback, 0, 5), hs->grf_prop.grffile);
@@ -631,7 +787,7 @@
/* Check for custom cargo acceptance */
if (HasBit(hs->callback_mask, CBM_HOUSE_CARGO_ACCEPTANCE)) {
- uint16 callback = GetHouseCallback(CBID_HOUSE_CARGO_ACCEPTANCE, 0, 0, GetHouseType(tile), Town::GetByTile(tile), tile);
+ uint16 callback = GetHouseCallback(CBID_HOUSE_CARGO_ACCEPTANCE, 0, 0, house_id, t, tile);
if (callback != CALLBACK_FAILED) {
AddAcceptedCargoSetMask(accepts[0], GB(callback, 0, 4), acceptance, always_accepted);
AddAcceptedCargoSetMask(accepts[1], GB(callback, 4, 4), acceptance, always_accepted);
@@ -651,31 +807,23 @@
}
}
+static void AddAcceptedCargo_Town(TileIndex tile, CargoArray &acceptance, uint32 *always_accepted)
+{
+ AddAcceptedHouseCargo(GetHouseType(tile), tile, acceptance, always_accepted);
+}
+
static void GetTileDesc_Town(TileIndex tile, TileDesc *td)
{
const HouseID house = GetHouseType(tile);
- const HouseSpec *hs = HouseSpec::Get(house);
- bool house_completed = IsHouseCompleted(tile);
- td->str = hs->building_name;
+ td->str = GetHouseName(house, tile);
- uint16 callback_res = GetHouseCallback(CBID_HOUSE_CUSTOM_NAME, house_completed ? 1 : 0, 0, house, Town::GetByTile(tile), tile);
- if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
- if (callback_res > 0x400) {
- ErrorUnknownCallbackResult(hs->grf_prop.grffile->grfid, CBID_HOUSE_CUSTOM_NAME, callback_res);
- } else {
- StringID new_name = GetGRFStringID(hs->grf_prop.grffile->grfid, 0xD000 + callback_res);
- if (new_name != STR_NULL && new_name != STR_UNDEFINED) {
- td->str = new_name;
- }
- }
- }
-
- if (!house_completed) {
+ if (!IsHouseCompleted(tile)) {
SetDParamX(td->dparam, 0, td->str);
td->str = STR_LAI_TOWN_INDUSTRY_DESCRIPTION_UNDER_CONSTRUCTION;
}
+ const HouseSpec *hs = HouseSpec::Get(house);
if (hs->grf_prop.grffile != NULL) {
const GRFConfig *gc = GetGRFConfig(hs->grf_prop.grffile->grfid);
td->grf = gc->GetName();
@@ -1992,17 +2140,21 @@
/**
* Returns the bit corresponding to the town zone of the specified tile
+ * or #HZB_END if the tile is ouside of the town.
+ *
* @param t Town on which town zone is to be found
* @param tile TileIndex where town zone needs to be found
* @return the bit position of the given zone, as defined in HouseZones
+ *
+ * @see GetTownRadiusGroup
*/
-HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile)
+HouseZonesBits TryGetTownRadiusGroup(const Town *t, TileIndex tile)
{
uint dist = DistanceSquare(tile, t->xy);
if (t->fund_buildings_months && dist <= 25) return HZB_TOWN_CENTRE;
- HouseZonesBits smallest = HZB_TOWN_EDGE;
+ HouseZonesBits smallest = HZB_END;
for (HouseZonesBits i = HZB_BEGIN; i < HZB_END; i++) {
if (dist < t->cache.squared_town_zone_radius[i]) smallest = i;
}
@@ -2011,6 +2163,22 @@
}
/**
+ * Returns the bit corresponding to the town zone of the specified tile.
+ * Returns #HZB_TOWN_EDGE if the tile is either in an edge zone or ouside of the town.
+ *
+ * @param t Town on which town zone is to be found
+ * @param tile TileIndex where town zone needs to be found
+ * @return the bit position of the given zone, as defined in HouseZones
+ *
+ * @see TryGetTownRadiusGroup
+ */
+HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile)
+{
+ HouseZonesBits ret = TryGetTownRadiusGroup(t, tile);
+ return ret != HZB_END ? ret : HZB_TOWN_EDGE;
+}
+
+/**
* Clears tile and builds a house or house part.
* @param tile tile index
* @param t The town to clear the house for
@@ -2061,184 +2229,307 @@
* @param tile tile to check
* @param town town that is checking
* @param noslope are slopes (foundations) allowed?
- * @return true iff house can be built here
+ * @return success if house can be built here, error message otherwise
*/
-static inline bool CanBuildHouseHere(TileIndex tile, TownID town, bool noslope)
+static inline CommandCost CanBuildHouseHere(TileIndex tile, TownID town, bool noslope)
{
/* cannot build on these slopes... */
- Slope slope = GetTileSlope(tile);
- if ((noslope && slope != SLOPE_FLAT) || IsSteepSlope(slope)) return false;
+ if (noslope) {
+ if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
+ } else {
+ if (IsSteepSlope(GetTileSlope(tile))) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
+ }
/* building under a bridge? */
- if (IsBridgeAbove(tile)) return false;
+ if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
+
+ /* can we clear the land? */
+ CommandCost ret = DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_LANDSCAPE_CLEAR);
+ if (ret.Failed()) return ret;
/* do not try to build over house owned by another town */
- if (IsTileType(tile, MP_HOUSE) && GetTownIndex(tile) != town) return false;
+ if (IsTileType(tile, MP_HOUSE) && GetTownIndex(tile) != town) return CMD_ERROR;
- /* can we clear the land? */
- return DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_LANDSCAPE_CLEAR).Succeeded();
+ return CommandCost();
}
/**
- * Checks if a house can be built at this tile, must have the same max z as parameter.
- * @param tile tile to check
+ * Checks if a house can be built here. Important is slope, bridge above
+ * and ability to clear the land.
+ *
+ * @param ta tile area to check
* @param town town that is checking
- * @param z max z of this tile so more parts of a house are at the same height (with foundation)
+ * @param maxz z level of the house, check if all tiles have this max z level
* @param noslope are slopes (foundations) allowed?
- * @return true iff house can be built here
- * @see CanBuildHouseHere()
+ * @return success if house can be built here, error message otherwise
+ *
+ * @see TownLayoutAllowsHouseHere
*/
-static inline bool CheckBuildHouseSameZ(TileIndex tile, TownID town, int z, bool noslope)
+static inline CommandCost CanBuildHouseHere(const TileArea &ta, TownID town, int maxz, bool noslope)
{
- if (!CanBuildHouseHere(tile, town, noslope)) return false;
+ TILE_AREA_LOOP(tile, ta) {
+ CommandCost ret = CanBuildHouseHere(tile, town, noslope);
+ /* if building on slopes is allowed, there will be flattening foundation (to tile max z) */
+ if (ret.Succeeded() && GetTileMaxZ(tile) != maxz) ret = CommandCost(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
+ if (ret.Failed()) return ret;
+ }
- /* if building on slopes is allowed, there will be flattening foundation (to tile max z) */
- if (GetTileMaxZ(tile) != z) return false;
+ return CommandCost();
+}
- return true;
+
+/**
+ * Test whether houses of given type are avaliable in current game.
+ *
+ * The function will check whether the house is available at all e.g. is not overriden.
+ * Also availability for current climate and given house zone will be tested.
+ *
+ * @param house house type
+ * @param above_snowline true to test availability above the snow line, false for below (arctic climate only)
+ * @param zone return error if houses are forbidden in this house zone
+ * @return success if house is avaliable, error message otherwise
+ */
+static inline CommandCost IsHouseTypeAllowed(HouseID house, bool above_snowline, HouseZonesBits zone)
+ {
+ const HouseSpec *hs = HouseSpec::Get(house);
+ /* Disallow disabled and replaced houses. */
+ if (!hs->enabled || hs->grf_prop.override != INVALID_HOUSE_ID) return CMD_ERROR;
+
+ /* Check if we can build this house in current climate. */
+ if (_settings_game.game_creation.landscape != LT_ARCTIC) {
+ if (!(hs->building_availability & (HZ_TEMP << _settings_game.game_creation.landscape))) return CMD_ERROR;
+ } else if (above_snowline) {
+ if (!(hs->building_availability & HZ_SUBARTC_ABOVE)) return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED_ABOVE_SNOW_LINE);
+ } else {
+ if (!(hs->building_availability & HZ_SUBARTC_BELOW)) return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED_BELOW_SNOW_LINE);
+ }
+
+ /* Check if the house zone is allowed for this type of houses. */
+ if (!HasBit(hs->building_availability & HZ_ZONALL, zone)) {
+ return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED_IN_THIS_TOWN_ZONE);
+ }
+
+ return CommandCost();
}
/**
- * Checks if a house of size 2x2 can be built at this tile
- * @param tile tile, N corner
- * @param town town that is checking
- * @param z maximum tile z so all tile have the same max z
- * @param noslope are slopes (foundations) allowed?
- * @return true iff house can be built
- * @see CheckBuildHouseSameZ()
+ * Check whether a town can hold more house types.
+ * @param t the town we wan't to check
+ * @param house type of the house we wan't to add
+ * @return success if houses of this type are allowed, error message otherwise
*/
-static bool CheckFree2x2Area(TileIndex tile, TownID town, int z, bool noslope)
+static inline CommandCost IsAnotherHouseTypeAllowedInTown(Town *t, HouseID house)
{
- /* we need to check this tile too because we can be at different tile now */
- if (!CheckBuildHouseSameZ(tile, town, z, noslope)) return false;
+ const HouseSpec *hs = HouseSpec::Get(house);
- for (DiagDirection d = DIAGDIR_SE; d < DIAGDIR_END; d++) {
- tile += TileOffsByDiagDir(d);
- if (!CheckBuildHouseSameZ(tile, town, z, noslope)) return false;
+ /* Don't let these counters overflow. Global counters are 32bit, there will never be that many houses. */
+ if (hs->class_id != HOUSE_NO_CLASS) {
+ /* id_count is always <= class_count, so it doesn't need to be checked */
+ if (t->cache.building_counts.class_count[hs->class_id] == UINT16_MAX) return_cmd_error(STR_ERROR_TOO_MANY_HOUSE_SETS);
+ } else {
+ /* If the house has no class, check id_count instead */
+ if (t->cache.building_counts.id_count[house] == UINT16_MAX) return_cmd_error(STR_ERROR_TOO_MANY_HOUSE_TYPES);
}
- return true;
+ return CommandCost();
}
-
/**
* Checks if current town layout allows building here
* @param t town
- * @param tile tile to check
+ * @param ta tile area to check
* @return true iff town layout allows building here
* @note see layouts
*/
-static inline bool TownLayoutAllowsHouseHere(Town *t, TileIndex tile)
+static inline bool TownLayoutAllowsHouseHere(Town *t, const TileArea &ta)
{
/* Allow towns everywhere when we don't build roads */
if (!_settings_game.economy.allow_town_roads && !_generating_world) return true;
- TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, tile);
+ TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, ta.tile);
+ const uint overflow = 3 * 4 * UINT16_MAX; // perform "floor division"
switch (t->layout) {
- case TL_2X2_GRID:
- if ((grid_pos.x % 3) == 0 || (grid_pos.y % 3) == 0) return false;
- break;
+ case TL_2X2_GRID: return (uint)(grid_pos.x + overflow) % 3 >= ta.w && (uint)(grid_pos.y + overflow) % 3 >= ta.h;
+ case TL_3X3_GRID: return (uint)(grid_pos.x + overflow) % 4 >= ta.w && (uint)(grid_pos.y + overflow) % 4 >= ta.h;
+ default: return true;
+ }
+}
- case TL_3X3_GRID:
- if ((grid_pos.x % 4) == 0 || (grid_pos.y % 4) == 0) return false;
- break;
- default:
- break;
+/**
+ * Find a suitable place (free of any obstacles) for a new town house. Search around a given location
+ * taking into account the layout of the town.
+ *
+ * @param tile tile that must be included by the building
+ * @param t the town we are building in
+ * @param house house type
+ * @return where the building can be placed, INVALID_TILE if no lacation was found
+ *
+ * @pre CanBuildHouseHere(tile, t->index, false)
+ *
+ * @see CanBuildHouseHere
+ */
+static TileIndex FindPlaceForTownHouseAroundTile(TileIndex tile, Town *t, HouseID house)
+{
+ const HouseSpec *hs = HouseSpec::Get(house);
+ bool noslope = (hs->building_flags & TILE_NOT_SLOPED) != 0;
+
+ TileArea ta(tile, 1, 1);
+ DiagDirection dir;
+ uint count;
+ if (hs->building_flags & TILE_SIZE_2x2) {
+ ta.w = ta.h = 2;
+ dir = DIAGDIR_NW; // 'd' goes through DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_SE
+ count = 4;
+ } else if (hs->building_flags & TILE_SIZE_2x1) {
+ ta.w = 2;
+ dir = DIAGDIR_NE;
+ count = 2;
+ } else if (hs->building_flags & TILE_SIZE_1x2) {
+ ta.h = 2;
+ dir = DIAGDIR_NW;
+ count = 2;
+ } else { // TILE_SIZE_1x1
+ /* CanBuildHouseHere(tile, t->index, false) already checked */
+ if (noslope && !IsTileFlat(tile)) return INVALID_TILE;
+ return tile;
}
- return true;
+ int maxz = GetTileMaxZ(tile);
+ /* Drift around the tile and find a place for the house. For 1x2 and 2x1 houses just two
+ * positions will be checked (at the exact tile and the other). In case of 2x2 houses
+ * 4 positions have to be checked (clockwise). */
+ while (count-- > 0) {
+ if (!TownLayoutAllowsHouseHere(t, ta)) continue;
+ if (CanBuildHouseHere(ta, t->index, maxz, noslope).Succeeded()) return ta.tile;
+ ta.tile += TileOffsByDiagDir(dir);
+ dir = ChangeDiagDir(dir, DIAGDIRDIFF_90RIGHT);
+ }
+
+ return INVALID_TILE;
}
/**
- * Checks if current town layout allows 2x2 building here
- * @param t town
- * @param tile tile to check
- * @return true iff town layout allows 2x2 building here
- * @note see layouts
+ * Check if a given house can be built in a given town.
+ * @param house house type
+ * @param t the town
+ * @return success if house can be built, error message otherwise
*/
-static inline bool TownLayoutAllows2x2HouseHere(Town *t, TileIndex tile)
+static CommandCost CheckCanBuildHouse(HouseID house, const Town *t)
{
- /* Allow towns everywhere when we don't build roads */
- if (!_settings_game.economy.allow_town_roads && !_generating_world) return true;
-
- /* Compute relative position of tile. (Positive offsets are towards north) */
- TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, tile);
+ const HouseSpec *hs = HouseSpec::Get(house);
- switch (t->layout) {
- case TL_2X2_GRID:
- grid_pos.x %= 3;
- grid_pos.y %= 3;
- if ((grid_pos.x != 2 && grid_pos.x != -1) ||
- (grid_pos.y != 2 && grid_pos.y != -1)) return false;
- break;
+ if (_loaded_newgrf_features.has_newhouses && !_generating_world &&
+ _game_mode != GM_EDITOR && (hs->extra_flags & BUILDING_IS_HISTORICAL) != 0) {
+ return CMD_ERROR;
+ }
- case TL_3X3_GRID:
- if ((grid_pos.x & 3) < 2 || (grid_pos.y & 3) < 2) return false;
- break;
+ if (_cur_year > hs->max_year) return_cmd_error(STR_ERROR_BUILDING_IS_TOO_OLD);
+ if (_cur_year < hs->min_year) return_cmd_error(STR_ERROR_BUILDING_IS_TOO_MODERN);
- default:
- break;
+ /* Special houses that there can be only one of. */
+ if (hs->building_flags & BUILDING_IS_CHURCH) {
+ if (HasBit(t->flags, TOWN_HAS_CHURCH)) return_cmd_error(STR_ERROR_ONLY_ONE_BUILDING_ALLOWED_PER_TOWN);
+ } else if (hs->building_flags & BUILDING_IS_STADIUM) {
+ if (HasBit(t->flags, TOWN_HAS_STADIUM)) return_cmd_error(STR_ERROR_ONLY_ONE_BUILDING_ALLOWED_PER_TOWN);
}
- return true;
+ return CommandCost();
}
/**
- * Checks if 1x2 or 2x1 building is allowed here, also takes into account current town layout
- * Also, tests both building positions that occupy this tile
- * @param tile tile where the building should be built
- * @param t town
- * @param maxz all tiles should have the same height
- * @param noslope are slopes forbidden?
- * @param second diagdir from first tile to second tile
+ * Really build a house.
+ * @param t town to build house in
+ * @param tile house location
+ * @param house house type
+ * @param random_bits random bits for the house
*/
-static bool CheckTownBuild2House(TileIndex *tile, Town *t, int maxz, bool noslope, DiagDirection second)
+static void DoBuildHouse(Town *t, TileIndex tile, HouseID house, byte random_bits)
{
- /* 'tile' is already checked in BuildTownHouse() - CanBuildHouseHere() and slope test */
+ t->cache.num_houses++;
- TileIndex tile2 = *tile + TileOffsByDiagDir(second);
- if (TownLayoutAllowsHouseHere(t, tile2) && CheckBuildHouseSameZ(tile2, t->index, maxz, noslope)) return true;
+ const HouseSpec *hs = HouseSpec::Get(house);
- tile2 = *tile + TileOffsByDiagDir(ReverseDiagDir(second));
- if (TownLayoutAllowsHouseHere(t, tile2) && CheckBuildHouseSameZ(tile2, t->index, maxz, noslope)) {
- *tile = tile2;
- return true;
+ /* Special houses that there can be only one of. */
+ if (hs->building_flags & BUILDING_IS_CHURCH) {
+ SetBit(t->flags, TOWN_HAS_CHURCH);
+ } else if (hs->building_flags & BUILDING_IS_STADIUM) {
+ SetBit(t->flags, TOWN_HAS_STADIUM);
}
- return false;
-}
+ byte construction_counter = 0;
+ byte construction_stage = 0;
+
+ if (_generating_world || _game_mode == GM_EDITOR) {
+ uint32 r = Random();
+
+ construction_stage = TOWN_HOUSE_COMPLETED;
+ if (Chance16(1, 7)) construction_stage = GB(r, 0, 2);
+
+ if (construction_stage == TOWN_HOUSE_COMPLETED) {
+ ChangePopulation(t, hs->population);
+ } else {
+ construction_counter = GB(r, 2, 2);
+ }
+ }
+ MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits);
+ UpdateTownRadius(t);
+ UpdateTownCargoes(t, tile);
+}
/**
- * Checks if 2x2 building is allowed here, also takes into account current town layout
- * Also, tests all four building positions that occupy this tile
- * @param tile tile where the building should be built
- * @param t town
- * @param maxz all tiles should have the same height
- * @param noslope are slopes forbidden?
+ * Place a custom house
+ * @param tile tile where the house will be located
+ * @param flags flags for the command
+ * @param p1 \n
+ * bits 0..15 - the HouseID of the house \n
+ * bits 16..31 - the TownID of the town \n
+ * @param p2 \n
+ * bits 0..7 - random bits \n
+ * @param text unused
+ * @return the cost of this operation or an error
*/
-static bool CheckTownBuild2x2House(TileIndex *tile, Town *t, int maxz, bool noslope)
+CommandCost CmdBuildHouse(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
- TileIndex tile2 = *tile;
+ if (_game_mode != GM_EDITOR && // in scenario editor anyone can build a house
+ _current_company != OWNER_TOWN && // towns naturally can build houses
+ _current_company != OWNER_DEITY) { // GameScript can place a house too
+ return CMD_ERROR;
+ }
- for (DiagDirection d = DIAGDIR_SE;; d++) { // 'd' goes through DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_END
- if (TownLayoutAllows2x2HouseHere(t, tile2) && CheckFree2x2Area(tile2, t->index, maxz, noslope)) {
- *tile = tile2;
- return true;
- }
- if (d == DIAGDIR_END) break;
- tile2 += TileOffsByDiagDir(ReverseDiagDir(d)); // go clockwise
+ HouseID house = GB(p1, 0, 16);
+ Town *t = Town::Get(GB(p1, 16, 16));
+ if (t == NULL) return CMD_ERROR;
+ byte random_bits = GB(p2, 0, 8);
+
+ int max_z = GetTileMaxZ(tile);
+ bool above_snowline = (_settings_game.game_creation.landscape == LT_ARCTIC) && (max_z > HighestSnowLine());
+
+ CommandCost ret = IsHouseTypeAllowed(house, above_snowline, TryGetTownRadiusGroup(t, tile));
+ if (ret.Succeeded()) ret = IsAnotherHouseTypeAllowedInTown(t, house);
+ if (ret.Succeeded()) ret = CheckCanBuildHouse(house, t);
+ if (ret.Succeeded()) {
+ /* While placing a house manually, try only at exact position and ignore the layout */
+ const HouseSpec *hs = HouseSpec::Get(house);
+ uint w = hs->building_flags & BUILDING_2_TILES_X ? 2 : 1;
+ uint h = hs->building_flags & BUILDING_2_TILES_Y ? 2 : 1;
+ bool noslope = (hs->building_flags & TILE_NOT_SLOPED) != 0;
+ ret = CanBuildHouseHere(TileArea(tile, w, h), t->index, max_z, noslope);
}
+ if (ret.Failed()) return ret;
- return false;
-}
+ /* Check if GRF allows this house */
+ if (!HouseAllowsConstruction(house, tile, t, random_bits)) return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED);
+ if (flags & DC_EXEC) DoBuildHouse(t, tile, house, random_bits);
+ return CommandCost();
+}
/**
* Tries to build a house at this tile
@@ -2249,23 +2540,13 @@
static bool BuildTownHouse(Town *t, TileIndex tile)
{
/* forbidden building here by town layout */
- if (!TownLayoutAllowsHouseHere(t, tile)) return false;
+ if (!TownLayoutAllowsHouseHere(t, TileArea(tile, 1, 1))) return false;
/* no house allowed at all, bail out */
- if (!CanBuildHouseHere(tile, t->index, false)) return false;
-
- Slope slope = GetTileSlope(tile);
- int maxz = GetTileMaxZ(tile);
-
- /* Get the town zone type of the current tile, as well as the climate.
- * This will allow to easily compare with the specs of the new house to build */
- HouseZonesBits rad = GetTownRadiusGroup(t, tile);
+ if (CanBuildHouseHere(tile, t->index, false).Failed()) return false;
- /* Above snow? */
- int land = _settings_game.game_creation.landscape;
- if (land == LT_ARCTIC && maxz > HighestSnowLine()) land = -1;
-
- uint bitmask = (1 << rad) + (1 << (land + 12));
+ bool above_snowline = _settings_game.game_creation.landscape == LT_ARCTIC && GetTileMaxZ(tile) > HighestSnowLine();
+ HouseZonesBits zone = GetTownRadiusGroup(t, tile);
/* bits 0-4 are used
* bits 11-15 are used
@@ -2277,22 +2558,11 @@
/* Generate a list of all possible houses that can be built. */
for (uint i = 0; i < NUM_HOUSES; i++) {
- const HouseSpec *hs = HouseSpec::Get(i);
-
- /* Verify that the candidate house spec matches the current tile status */
- if ((~hs->building_availability & bitmask) != 0 || !hs->enabled || hs->grf_prop.override != INVALID_HOUSE_ID) continue;
-
- /* Don't let these counters overflow. Global counters are 32bit, there will never be that many houses. */
- if (hs->class_id != HOUSE_NO_CLASS) {
- /* id_count is always <= class_count, so it doesn't need to be checked */
- if (t->cache.building_counts.class_count[hs->class_id] == UINT16_MAX) continue;
- } else {
- /* If the house has no class, check id_count instead */
- if (t->cache.building_counts.id_count[i] == UINT16_MAX) continue;
- }
+ if (IsHouseTypeAllowed((HouseID)i, above_snowline, zone).Failed()) continue;
+ if (IsAnotherHouseTypeAllowedInTown(t, (HouseID)i).Failed()) continue;
/* Without NewHouses, all houses have probability '1' */
- uint cur_prob = (_loaded_newgrf_features.has_newhouses ? hs->probability : 1);
+ uint cur_prob = (_loaded_newgrf_features.has_newhouses ? HouseSpec::Get(i)->probability : 1);
probability_max += cur_prob;
probs[num] = cur_prob;
houses[num++] = (HouseID)i;
@@ -2323,73 +2593,18 @@
houses[i] = houses[num];
probs[i] = probs[num];
- const HouseSpec *hs = HouseSpec::Get(house);
-
- if (_loaded_newgrf_features.has_newhouses && !_generating_world &&
- _game_mode != GM_EDITOR && (hs->extra_flags & BUILDING_IS_HISTORICAL) != 0) {
- continue;
- }
+ CommandCost ret = CheckCanBuildHouse(house, t);
+ if (ret.Failed()) continue;
- if (_cur_year < hs->min_year || _cur_year > hs->max_year) continue;
-
- /* Special houses that there can be only one of. */
- uint oneof = 0;
-
- if (hs->building_flags & BUILDING_IS_CHURCH) {
- SetBit(oneof, TOWN_HAS_CHURCH);
- } else if (hs->building_flags & BUILDING_IS_STADIUM) {
- SetBit(oneof, TOWN_HAS_STADIUM);
- }
-
- if (t->flags & oneof) continue;
-
- /* Make sure there is no slope? */
- bool noslope = (hs->building_flags & TILE_NOT_SLOPED) != 0;
- if (noslope && slope != SLOPE_FLAT) continue;
-
- if (hs->building_flags & TILE_SIZE_2x2) {
- if (!CheckTownBuild2x2House(&tile, t, maxz, noslope)) continue;
- } else if (hs->building_flags & TILE_SIZE_2x1) {
- if (!CheckTownBuild2House(&tile, t, maxz, noslope, DIAGDIR_SW)) continue;
- } else if (hs->building_flags & TILE_SIZE_1x2) {
- if (!CheckTownBuild2House(&tile, t, maxz, noslope, DIAGDIR_SE)) continue;
- } else {
- /* 1x1 house checks are already done */
- }
+ tile = FindPlaceForTownHouseAroundTile(tile, t, house);
+ if (tile == INVALID_TILE) continue;
byte random_bits = Random();
- if (HasBit(hs->callback_mask, CBM_HOUSE_ALLOW_CONSTRUCTION)) {
- uint16 callback_res = GetHouseCallback(CBID_HOUSE_ALLOW_CONSTRUCTION, 0, 0, house, t, tile, true, random_bits);
- if (callback_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(hs->grf_prop.grffile, CBID_HOUSE_ALLOW_CONSTRUCTION, callback_res)) continue;
- }
-
- /* build the house */
- t->cache.num_houses++;
-
- /* Special houses that there can be only one of. */
- t->flags |= oneof;
-
- byte construction_counter = 0;
- byte construction_stage = 0;
-
- if (_generating_world || _game_mode == GM_EDITOR) {
- uint32 r = Random();
-
- construction_stage = TOWN_HOUSE_COMPLETED;
- if (Chance16(1, 7)) construction_stage = GB(r, 0, 2);
-
- if (construction_stage == TOWN_HOUSE_COMPLETED) {
- ChangePopulation(t, hs->population);
- } else {
- construction_counter = GB(r, 2, 2);
- }
- }
-
- MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits);
- UpdateTownRadius(t);
- UpdateTownCargoes(t, tile);
+ /* Check if GRF allows this house */
+ if (!HouseAllowsConstruction(house, tile, t, random_bits)) continue;
+ DoBuildHouse(t, tile, house, random_bits);
return true;
}
@@ -2965,6 +3180,7 @@
*/
if (t->ratings[_current_company] > RATING_BRIBE_DOWN_TO) {
t->ratings[_current_company] = RATING_BRIBE_DOWN_TO;
+ t->UpdateVirtCoord();
SetWindowDirty(WC_TOWN_AUTHORITY, t->index);
}
} else {
@@ -3097,6 +3313,7 @@
t->ratings[i] = Clamp(t->ratings[i], RATING_MINIMUM, RATING_MAXIMUM);
}
+ t->UpdateVirtCoord();
SetWindowDirty(WC_TOWN_AUTHORITY, t->index);
}
@@ -3348,6 +3565,7 @@
} else {
SetBit(t->have_ratings, _current_company);
t->ratings[_current_company] = rating;
+ t->UpdateVirtCoord();
SetWindowDirty(WC_TOWN_AUTHORITY, t->index);
}
}
Index: src/town_gui.cpp
===================================================================
--- src/town_gui.cpp (revision 27386)
+++ src/town_gui.cpp (working copy)
@@ -32,6 +32,10 @@
#include "core/geometry_func.hpp"
#include "genworld.h"
#include "widgets/dropdown_func.h"
+#include "newgrf_config.h"
+#include "newgrf_house.h"
+#include "date_func.h"
+#include "core/random_func.hpp"
#include "widgets/town_widget.h"
@@ -1197,3 +1201,677 @@
if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return;
AllocateWindowDescFront(&_found_town_desc, 0);
}
+
+class GUIHouseList : public SmallVector {
+protected:
+ SmallVector house_sets; ///< list of house sets, each item points the first house of the set in the houses array
+
+ static int CDECL HouseSorter(const HouseID *a, const HouseID *b)
+ {
+ const HouseSpec *a_hs = HouseSpec::Get(*a);
+ const GRFFile *a_set = a_hs->grf_prop.grffile;
+ const HouseSpec *b_hs = HouseSpec::Get(*b);
+ const GRFFile *b_set = b_hs->grf_prop.grffile;
+
+ int ret = (a_set != NULL) - (b_set != NULL);
+ if (ret == 0) {
+ if (a_set != NULL) {
+ assert_compile(sizeof(a_set->grfid) <= sizeof(int));
+ ret = a_set->grfid - b_set->grfid;
+ if (ret == 0) ret = a_hs->grf_prop.local_id - b_hs->grf_prop.local_id;
+ } else {
+ ret = *a - *b;
+ }
+ }
+ return ret;
+ }
+
+public:
+ GUIHouseList()
+ {
+ *this->house_sets.Append() = 0; // terminator
+ }
+
+ inline HouseID GetHouseAtOffset(uint house_set, uint house_offset) const
+ {
+ return *this->Get(this->house_sets[house_set] + house_offset);
+ }
+
+ uint NumHouseSets() const
+ {
+ return this->house_sets.Length() - 1; // last item is a terminator
+ }
+
+ uint NumHousesInHouseSet(uint house_set) const
+ {
+ assert(house_set < this->NumHouseSets());
+ /* There is a terminator on the list of house sets. It's equal to the number
+ * of all houses. We can safely use "house_set + 1" even for the last
+ * house set. */
+ return this->house_sets[house_set + 1] - this->house_sets[house_set];
+ }
+
+ int FindHouseSet(HouseID house) const
+ {
+ const GRFFile *house_set = HouseSpec::Get(house)->grf_prop.grffile;
+ for (uint i = 0; i < this->NumHouseSets(); i++) {
+ if (HouseSpec::Get(this->GetHouseAtOffset(i, 0))->grf_prop.grffile == house_set) return i;
+ }
+ return -1;
+ }
+
+ int FindHouseOffset(uint house_set, HouseID house) const
+ {
+ assert(house_set < this->NumHouseSets());
+ uint count = this->NumHousesInHouseSet(house_set);
+ for (uint i = 0; i < count; i++) {
+ if (this->GetHouseAtOffset(house_set, i) == house) return i;
+ }
+ return -1;
+ }
+
+ const char *GetNameOfHouseSet(uint house_set) const
+ {
+ assert(house_set < this->NumHouseSets());
+ const GRFFile *gf = HouseSpec::Get(this->GetHouseAtOffset(house_set, 0))->grf_prop.grffile;
+ if (gf != NULL) return GetGRFConfig(gf->grfid)->GetName();
+
+ static char name[DRAW_STRING_BUFFER];
+ GetString(name, STR_BASIC_HOUSE_SET_NAME, lastof(name));
+ return name;
+ }
+
+ /**
+ * Notify the sortlist that the rebuild is done
+ *
+ * @note This forces a resort
+ */
+ void Build()
+ {
+ /* collect items */
+ this->Clear();
+ for (HouseID house = 0; house < NUM_HOUSES; house++) {
+ const HouseSpec *hs = HouseSpec::Get(house);
+ /* is the house enabled? */
+ if (!hs->enabled) continue;
+ /* is the house overriden? */
+ if (hs->grf_prop.override != INVALID_HOUSE_ID) continue;
+ /* is the house allownd in current landscape? */
+ HouseZones landscapes = (HouseZones)(HZ_TEMP << _settings_game.game_creation.landscape);
+ if (_settings_game.game_creation.landscape == LT_ARCTIC) landscapes |= HZ_SUBARTC_ABOVE;
+ if (!(hs->building_availability & landscapes)) continue;
+ /* is the house allowed at any of house zones at all? */
+ if (!(hs->building_availability & HZ_ZONALL)) continue;
+ /* is there any year in which the house is allowed? */
+ if (hs->min_year > hs->max_year) continue;
+
+ /* add the house */
+ *this->Append() = house;
+ }
+
+ /* arrange items */
+ QSortT(this->Begin(), this->Length(), HouseSorter);
+
+ /* list house sets */
+ this->house_sets.Clear();
+ const GRFFile *last_set;
+ for (uint i = 0; i < this->Length(); i++) {
+ const HouseSpec *hs = HouseSpec::Get((*this)[i]);
+ /* add house set */
+ if (this->house_sets.Length() == 0 || last_set != hs->grf_prop.grffile) {
+ last_set = hs->grf_prop.grffile;
+ *this->house_sets.Append() = i;
+ }
+ }
+ /* put a terminator on the list to make counting easier */
+ *this->house_sets.Append() = this->Length();
+ }
+};
+
+static HouseID _cur_house = INVALID_HOUSE_ID; ///< house selected in the house picker window
+
+/** The window used for building houses. */
+class HousePickerWindow : public PickerWindowBase {
+protected:
+ GUIHouseList house_list; ///< list of houses and house sets
+ uint house_offset; ///< index of selected house
+ uint house_set; ///< index of selected house set
+ uint line_height; ///< height of a single line in the list of house sets
+
+ void RestoreSelectedHouseIndex()
+ {
+ this->house_set = 0;
+ this->house_offset = 0;
+
+ if (this->house_list.Length() == 0) { // no houses at all?
+ _cur_house = INVALID_HOUSE_ID;
+ return;
+ }
+
+ if (_cur_house != INVALID_HOUSE_ID) {
+ int house_set = this->house_list.FindHouseSet(_cur_house);
+ if (house_set >= 0) {
+ this->house_set = house_set;
+ int house_offset = this->house_list.FindHouseOffset(house_set, _cur_house);
+ if (house_offset >= 0) {
+ this->house_offset = house_offset;
+ return;
+ }
+ }
+ }
+ _cur_house = this->house_list.GetHouseAtOffset(this->house_set, this->house_offset);
+ }
+
+ /**
+ * Select another house.
+ * @param new_house_set index of the house set
+ * @param new_house_offset offset of the house
+ */
+ void SelectOtherHouse(uint new_house_set, uint new_house_offset)
+ {
+ assert(new_house_set < this->house_list.NumHouseSets());
+ assert(new_house_offset < this->house_list.NumHousesInHouseSet(new_house_set));
+
+ _cur_house = this->house_list.GetHouseAtOffset(new_house_set, new_house_offset);
+ this->house_set = new_house_set;
+ this->house_offset = new_house_offset;
+
+ NWidgetMatrix *matrix = this->GetWidget(WID_HP_HOUSE_SELECT_MATRIX);
+ matrix->SetCount(this->house_list.NumHousesInHouseSet(this->house_set));
+ matrix->SetClicked(this->house_offset);
+ this->UpdateSelectSize();
+ this->SetDirty();
+ }
+
+ void UpdateSelectSize()
+ {
+ uint w = 1, h = 1;
+ if (_cur_house != INVALID_HOUSE_ID) {
+ const HouseSpec *hs = HouseSpec::Get(_cur_house);
+ if (hs->building_flags & BUILDING_2_TILES_X) w++;
+ if (hs->building_flags & BUILDING_2_TILES_Y) h++;
+ }
+ SetTileSelectSize(w, h);
+ }
+
+public:
+ HousePickerWindow(WindowDesc *desc, Window *w) : PickerWindowBase(desc, w)
+ {
+ this->CreateNestedTree();
+ /* there is no shade box but we will shade the window if there is no house to show */
+ this->shade_select = this->GetWidget(WID_HP_MAIN_PANEL_SEL);
+ NWidgetMatrix *matrix = this->GetWidget(WID_HP_HOUSE_SELECT_MATRIX);
+ matrix->SetScrollbar(this->GetScrollbar(WID_HP_HOUSE_SELECT_SCROLL));
+ this->FinishInitNested(0);
+
+ if (_cur_house != INVALID_HOUSE_ID) matrix->SetClicked(this->house_offset); // set clicked item again to make it visible
+ }
+
+ ~HousePickerWindow()
+ {
+ DeleteWindowById(WC_SELECT_TOWN, 0);
+ }
+
+ virtual void OnInit()
+ {
+ this->house_list.Build();
+ this->RestoreSelectedHouseIndex();
+ this->UpdateSelectSize();
+
+ /* if we have exactly one set of houses and it's not the default one then display it's name in the title bar */
+ this->GetWidget(WID_HP_CAPTION)->widget_data =
+ (this->house_list.NumHouseSets() == 1 && HouseSpec::Get(this->house_list[0])->grf_prop.grffile != NULL) ?
+ STR_HOUSE_BUILD_CUSTOM_CAPTION : STR_HOUSE_BUILD_CAPTION;
+
+ /* hide widgets if we have no houses to show */
+ this->SetShaded(this->house_list.Length() == 0);
+
+ if (this->house_list.Length() != 0) {
+ /* show the list of house sets if we have at least 2 items to show */
+ this->GetWidget(WID_HP_HOUSE_SETS_SEL)->SetDisplayedPlane(this->house_list.NumHouseSets() > 1 ? 0 : SZSP_NONE);
+ /* set number of items in the list of house sets */
+ this->GetWidget(WID_HP_HOUSE_SETS)->widget_data = (this->house_list.NumHouseSets() << MAT_ROW_START) | (1 << MAT_COL_START);
+ /* show the landscape info only in arctic climate (above/below snowline) */
+ this->GetWidget(WID_HP_HOUSE_LANDSCAPE_SEL)->SetDisplayedPlane(_settings_game.game_creation.landscape == LT_ARCTIC ? 0 : SZSP_NONE);
+ /* update the matrix of houses */
+ NWidgetMatrix *matrix = this->GetWidget(WID_HP_HOUSE_SELECT_MATRIX);
+ matrix->SetCount(this->house_list.NumHousesInHouseSet(this->house_set));
+ matrix->SetClicked(this->house_offset);
+ }
+ }
+
+ virtual void SetStringParameters(int widget) const
+ {
+ switch (widget) {
+ case WID_HP_CAPTION:
+ if (this->house_list.NumHouseSets() == 1) SetDParamStr(0, this->house_list.GetNameOfHouseSet(0));
+ break;
+
+ case WID_HP_HOUSE_NAME:
+ SetDParam(0, GetHouseName(_cur_house));
+ break;
+
+ case WID_HP_HISTORICAL_BUILDING:
+ SetDParam(0, HouseSpec::Get(_cur_house)->extra_flags & BUILDING_IS_HISTORICAL ? STR_HOUSE_BUILD_HISTORICAL_BUILDING : STR_EMPTY);
+ break;
+
+ case WID_HP_HOUSE_POPULATION:
+ SetDParam(0, HouseSpec::Get(_cur_house)->population);
+ break;
+
+ case WID_HP_HOUSE_ZONES: {
+ HouseZones zones = (HouseZones)(HouseSpec::Get(_cur_house)->building_availability & HZ_ZONALL);
+ for (int i = 0; i < HZB_END; i++) {
+ /* colour: gold(enabled)/grey(disabled) */
+ SetDParam(2 * i, HasBit(zones, HZB_END - i - 1) ? STR_HOUSE_BUILD_HOUSE_ZONE_ENABLED : STR_HOUSE_BUILD_HOUSE_ZONE_DISABLED);
+ /* digit: 1(center)/2/3/4/5(edge) */
+ SetDParam(2 * i + 1, i + 1);
+ }
+ break;
+ }
+
+ case WID_HP_HOUSE_LANDSCAPE: {
+ StringID info = STR_HOUSE_BUILD_LANDSCAPE_ABOVE_OR_BELOW_SNOWLINE;
+ switch (HouseSpec::Get(_cur_house)->building_availability & (HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW)) {
+ case HZ_SUBARTC_ABOVE: info = STR_HOUSE_BUILD_LANDSCAPE_ONLY_ABOVE_SNOWLINE; break;
+ case HZ_SUBARTC_BELOW: info = STR_HOUSE_BUILD_LANDSCAPE_ONLY_BELOW_SNOWLINE; break;
+ default: break;
+ }
+ SetDParam(0, info);
+ break;
+ }
+
+ case WID_HP_HOUSE_YEARS: {
+ const HouseSpec *hs = HouseSpec::Get(_cur_house);
+ SetDParam(0, hs->min_year <= _cur_year ? STR_HOUSE_BUILD_YEARS_GOOD_YEAR : STR_HOUSE_BUILD_YEARS_BAD_YEAR);
+ SetDParam(1, hs->min_year);
+ SetDParam(2, hs->max_year >= _cur_year ? STR_HOUSE_BUILD_YEARS_GOOD_YEAR : STR_HOUSE_BUILD_YEARS_BAD_YEAR);
+ SetDParam(3, hs->max_year);
+ break;
+ }
+
+ case WID_HP_HOUSE_ACCEPTANCE: {
+ static char buff[DRAW_STRING_BUFFER] = "";
+ char *str = buff;
+ CargoArray cargo;
+ uint32 dummy = 0;
+ AddAcceptedHouseCargo(_cur_house, INVALID_TILE, cargo, &dummy);
+ for (uint i = 0; i < NUM_CARGO; i++) {
+ if (cargo[i] == 0) continue;
+ /* If the accepted value is less than 8, show it in 1/8:ths */
+ SetDParam(0, cargo[i] < 8 ? STR_HOUSE_BUILD_CARGO_VALUE_EIGHTS : STR_HOUSE_BUILD_CARGO_VALUE_JUST_NAME);
+ SetDParam(1, cargo[i]);
+ SetDParam(2, CargoSpec::Get(i)->name);
+ str = GetString(str, str == buff ? STR_HOUSE_BUILD_CARGO_FIRST : STR_HOUSE_BUILD_CARGO_SEPARATED, lastof(buff));
+ }
+ if (str == buff) GetString(buff, STR_JUST_NOTHING, lastof(buff));
+ SetDParamStr(0, buff);
+ break;
+ }
+
+ case WID_HP_HOUSE_SUPPLY: {
+ CargoArray cargo;
+ AddProducedHouseCargo(_cur_house, INVALID_TILE, cargo);
+ uint32 cargo_mask = 0;
+ for (uint i = 0; i < NUM_CARGO; i++) if (cargo[i] != 0) SetBit(cargo_mask, i);
+ SetDParam(0, cargo_mask);
+ break;
+ }
+
+ default: break;
+ }
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ switch (widget) {
+ case WID_HP_HOUSE_SETS: {
+ uint max_w = 0;
+ for (uint i = 0; i < this->house_list.NumHouseSets(); i++) {
+ max_w = max(max_w, GetStringBoundingBox(this->house_list.GetNameOfHouseSet(i)).width);
+ }
+ size->width = max(size->width, max_w + padding.width);
+ this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
+ size->height = this->house_list.NumHouseSets() * this->line_height;
+ break;
+ }
+
+ case WID_HP_HOUSE_NAME:
+ size->width = 120; // we do not want this window to get too wide, better clip
+ break;
+
+ case WID_HP_HISTORICAL_BUILDING:
+ size->width = max(size->width, GetStringBoundingBox(STR_HOUSE_BUILD_HISTORICAL_BUILDING).width + padding.width);
+ break;
+
+ case WID_HP_HOUSE_POPULATION:
+ SetDParam(0, 0);
+ /* max popultion is 255 - 3 digits */
+ size->width = max(size->width, GetStringBoundingBox(STR_HOUSE_BUILD_HOUSE_POPULATION).width + 3 * GetDigitWidth() + padding.width);
+ break;
+
+ case WID_HP_HOUSE_ZONES: {
+ for (int i = 0; i < HZB_END; i++) {
+ SetDParam(2 * i, STR_HOUSE_BUILD_HOUSE_ZONE_ENABLED); // colour
+ SetDParam(2 * i + 1, i + 1); // digit: 1(center)/2/3/4/5(edge)
+ }
+ size->width = max(size->width, GetStringBoundingBox(STR_HOUSE_BUILD_HOUSE_ZONES).width + padding.width);
+ break;
+ }
+
+ case WID_HP_HOUSE_LANDSCAPE: {
+ SetDParam(0, STR_HOUSE_BUILD_LANDSCAPE_ABOVE_OR_BELOW_SNOWLINE);
+ Dimension dim = GetStringBoundingBox(STR_HOUSE_BUILD_LANDSCAPE);
+ SetDParam(0, STR_HOUSE_BUILD_LANDSCAPE_ONLY_ABOVE_SNOWLINE);
+ dim = maxdim(dim, GetStringBoundingBox(STR_HOUSE_BUILD_LANDSCAPE));
+ SetDParam(0, STR_HOUSE_BUILD_LANDSCAPE_ONLY_BELOW_SNOWLINE);
+ dim = maxdim(dim, GetStringBoundingBox(STR_HOUSE_BUILD_LANDSCAPE));
+ dim.width += padding.width;
+ dim.height += padding.height;
+ *size = maxdim(*size, dim);
+ break;
+ }
+
+ case WID_HP_HOUSE_YEARS: {
+ SetDParam(0, STR_HOUSE_BUILD_YEARS_GOOD_YEAR);
+ SetDParam(1, 0);
+ SetDParam(2, STR_HOUSE_BUILD_YEARS_GOOD_YEAR);
+ SetDParam(3, 0);
+ Dimension dim = GetStringBoundingBox(STR_HOUSE_BUILD_YEARS);
+ dim.width += 14 * GetDigitWidth() + padding.width; // space for about 16 digits (14 + two zeros) should be enough, don't make the window too wide
+ dim.height += padding.height;
+ *size = maxdim(*size, dim);
+ break;
+ }
+
+ case WID_HP_HOUSE_SELECT_MATRIX:
+ resize->height = 1; // don't snap to rows of this matrix
+ break;
+
+ /* these texts can be long, better clip */
+ case WID_HP_HOUSE_ACCEPTANCE:
+ case WID_HP_HOUSE_SUPPLY:
+ size->width = 0;
+ break;
+
+ default: break;
+ }
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch (GB(widget, 0, 16)) {
+ case WID_HP_HOUSE_SETS: {
+ int y = r.top + WD_MATRIX_TOP;
+ for (uint i = 0; i < this->house_list.NumHouseSets(); i++) {
+ SetDParamStr(0, this->house_list.GetNameOfHouseSet(i));
+ DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y, STR_JUST_RAW_STRING, i == this->house_set ? TC_WHITE : TC_BLACK);
+ y += this->line_height;
+ }
+ break;
+ }
+
+ case WID_HP_HOUSE_PREVIEW:
+ DrawHouseImage(_cur_house, r.left, r.top, r.right, r.bottom);
+ break;
+
+ case WID_HP_HOUSE_SELECT: {
+ HouseID house = this->house_list.GetHouseAtOffset(this->house_set, GB(widget, 16, 16));
+ int lowered = (house == _cur_house) ? 1 : 0;
+ DrawHouseImage(house,
+ r.left + WD_MATRIX_LEFT + lowered, r.top + WD_MATRIX_TOP + lowered,
+ r.right - WD_MATRIX_RIGHT + lowered, r.bottom - WD_MATRIX_BOTTOM + lowered);
+ const HouseSpec *hs = HouseSpec::Get(house);
+ /* disabled? */
+ if (_cur_year < hs->min_year || _cur_year > hs->max_year) {
+ GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK, FILLRECT_CHECKER);
+ }
+ break;
+ }
+ }
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch (GB(widget, 0, 16)) {
+ case WID_HP_HOUSE_SETS: {
+ uint index = (uint)(pt.y - this->GetWidget(widget)->pos_y) / this->line_height;
+ if (index < this->house_list.NumHouseSets() && index != this->house_set) this->SelectOtherHouse(index, 0);
+ break;
+ }
+
+ case WID_HP_HOUSE_SELECT:
+ this->SelectOtherHouse(this->house_set, GB(widget, 16, 16));
+ break;
+ }
+ }
+};
+
+static const NWidgetPart _nested_house_picker_widgets[] = {
+ /* TOP */
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_HP_CAPTION), SetDataTip(STR_HOUSE_BUILD_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ NWidget(NWID_SELECTION, COLOUR_DARK_GREEN, WID_HP_MAIN_PANEL_SEL),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_HP_HOUSE_SELECT_SCROLL),
+ /* MIDDLE */
+ NWidget(NWID_HORIZONTAL), SetPIP(5, 0, 0),
+ /* LEFT */
+ NWidget(NWID_VERTICAL), SetPIP(5, 2, 2),
+ /* LIST OF HOUSE SETS */
+ NWidget(NWID_SELECTION, COLOUR_DARK_GREEN, WID_HP_HOUSE_SETS_SEL),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_MATRIX, COLOUR_GREY, WID_HP_HOUSE_SETS), SetMinimalSize(0, 60), SetFill(1, 0), SetResize(0, 0),
+ SetMatrixDataTip(1, 1, STR_HOUSE_BUILD_HOUSESET_LIST_TOOLTIP),
+ EndContainer(),
+ EndContainer(),
+ /* HOUSE PICTURE AND LABEL */
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_PREVIEW), SetFill(1, 1), SetResize(0, 1), SetMinimalSize(2 * TILE_PIXELS, 142),
+ NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_HP_HOUSE_NAME), SetDataTip(STR_HOUSE_BUILD_HOUSE_NAME, STR_NULL), SetMinimalSize(120, 0),
+ NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_HP_HISTORICAL_BUILDING), SetDataTip(STR_JUST_STRING, STR_NULL),
+ /* HOUSE INFOS (SHORT TEXTS) */
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_POPULATION), SetDataTip(STR_HOUSE_BUILD_HOUSE_POPULATION, STR_NULL), SetPadding(5, 0, 0, 0),
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_ZONES), SetDataTip(STR_HOUSE_BUILD_HOUSE_ZONES, STR_NULL),
+ NWidget(NWID_SELECTION, COLOUR_DARK_GREEN, WID_HP_HOUSE_LANDSCAPE_SEL),
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_LANDSCAPE), SetDataTip(STR_HOUSE_BUILD_LANDSCAPE, STR_NULL),
+ EndContainer(),
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_YEARS), SetDataTip(STR_HOUSE_BUILD_YEARS, STR_NULL),
+ EndContainer(),
+ /* RIGHT: MATRIX OF HOUSES */
+ NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_HP_HOUSE_SELECT_MATRIX), SetPIP(0, 2, 0), SetPadding(2, 2, 2, 2), SetScrollbar(WID_HP_HOUSE_SELECT_SCROLL),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_HP_HOUSE_SELECT), SetMinimalSize(64, 64), SetFill(0, 0), SetResize(0, 0),
+ SetDataTip(0x0, STR_HOUSE_BUILD_SELECT_HOUSE_TOOLTIP), SetScrollbar(WID_HP_HOUSE_SELECT_SCROLL),
+ EndContainer(),
+ EndContainer(),
+ NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_HP_HOUSE_SELECT_SCROLL),
+ EndContainer(),
+ /* BOTTOM */
+ NWidget(NWID_HORIZONTAL), SetPIP(5, 2, 0),
+ /* HOUSE INFOS (LONG TEXTS) */
+ NWidget(NWID_VERTICAL), SetPIP(0, 2, 5),
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_ACCEPTANCE), SetDataTip(STR_HOUSE_BUILD_ACCEPTED_CARGO, STR_NULL), SetFill(1, 0), SetResize(1, 0),
+ NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_SUPPLY), SetDataTip(STR_HOUSE_BUILD_SUPPLIED_CARGO, STR_NULL), SetFill(1, 0), SetResize(1, 0),
+ EndContainer(),
+ /* RESIZE BOX */
+ NWidget(NWID_VERTICAL),
+ NWidget(NWID_SPACER), SetFill(0, 1),
+ NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ EndContainer(),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _house_picker_desc(
+ WDP_AUTO, "build_house", 0, 0,
+ WC_BUILD_HOUSE, WC_BUILD_TOOLBAR,
+ WDF_CONSTRUCTION,
+ _nested_house_picker_widgets, lengthof(_nested_house_picker_widgets)
+);
+
+/**
+ * Show our house picker.
+ * @param parent The toolbar window we're associated with.
+ */
+void ShowBuildHousePicker(Window *parent)
+{
+ new HousePickerWindow(&_house_picker_desc, parent);
+}
+
+
+/**
+ * Window for selecting towns to build a house in.
+ */
+struct SelectTownWindow : Window {
+ TownList towns; ///< list of towns
+ CommandContainer cmd; ///< command to build the house (CMD_BUILD_HOUSE)
+ Scrollbar *vscroll; ///< scrollbar for the town list
+
+ SelectTownWindow(WindowDesc *desc, const TownList &towns, const CommandContainer &cmd) : Window(desc), towns(towns), cmd(cmd)
+ {
+ this->CreateNestedTree();
+ this->vscroll = this->GetScrollbar(WID_ST_SCROLLBAR);
+ this->vscroll->SetCount(this->towns.Length());
+ this->FinishInitNested();
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ if (widget != WID_ST_PANEL) return;
+
+ /* Determine the widest string */
+ Dimension d = { 0, 0 };
+ for (uint i = 0; i < this->towns.Length(); i++) {
+ SetDParam(0, this->towns[i]);
+ d = maxdim(d, GetStringBoundingBox(STR_SELECT_TOWN_LIST_ITEM));
+ }
+
+ resize->height = d.height;
+ d.height *= 5;
+ d.width += WD_FRAMERECT_RIGHT + WD_FRAMERECT_LEFT;
+ d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
+ *size = d;
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ if (widget != WID_ST_PANEL) return;
+
+ uint y = r.top + WD_FRAMERECT_TOP;
+ uint end = min(this->vscroll->GetCount(), this->vscroll->GetPosition() + this->vscroll->GetCapacity());
+ for (uint i = this->vscroll->GetPosition(); i < end; i++) {
+ SetDParam(0, this->towns[i]);
+ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SELECT_TOWN_LIST_ITEM);
+ y += this->resize.step_height;
+ }
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ if (widget != WID_ST_PANEL) return;
+
+ uint pos = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_ST_PANEL, WD_FRAMERECT_TOP);
+ if (pos >= this->towns.Length()) return;
+
+ /* Place a house */
+ SB(this->cmd.p1, 16, 16, this->towns[pos]);
+ DoCommandP(&this->cmd);
+
+ /* Close the window */
+ delete this;
+ }
+
+ virtual void OnResize()
+ {
+ this->vscroll->SetCapacityFromWidget(this, WID_ST_PANEL, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
+ }
+};
+
+static const NWidgetPart _nested_select_town_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_ST_CAPTION), SetDataTip(STR_SELECT_TOWN_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_ST_PANEL), SetResize(1, 0), SetScrollbar(WID_ST_SCROLLBAR), EndContainer(),
+ NWidget(NWID_VERTICAL),
+ NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_ST_SCROLLBAR),
+ NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _select_town_desc(
+ WDP_AUTO, "select_town", 100, 0,
+ WC_SELECT_TOWN, WC_NONE,
+ WDF_CONSTRUCTION,
+ _nested_select_town_widgets, lengthof(_nested_select_town_widgets)
+);
+
+static void ShowSelectTownWindow(const TownList &towns, const CommandContainer &cmd)
+{
+ DeleteWindowByClass(WC_SELECT_TOWN);
+ new SelectTownWindow(&_select_town_desc, towns, cmd);
+}
+
+
+void PlaceProc_House(TileIndex tile)
+{
+ if (_town_pool.items == 0) {
+ ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HOUSE_HERE, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO);
+ return;
+ }
+
+ DeleteWindowById(WC_SELECT_TOWN, 0);
+
+ if (_cur_house == INVALID_HOUSE_ID) return;
+
+ /* build a list of towns to join to */
+ TownList towns;
+ HouseZones house_zones = HouseSpec::Get(_cur_house)->building_availability & HZ_ZONALL;
+ uint best_dist = UINT_MAX;
+ int best_zone = (int)HZB_BEGIN - 1;
+ const Town *t;
+ FOR_ALL_TOWNS(t) {
+ HouseZonesBits town_zone = TryGetTownRadiusGroup(t, tile);
+ if (HasBit(house_zones, town_zone)) {
+ /* If CTRL is NOT pressed keep only single town on the list, the best one.
+ * Otherwise add all towns to the list so they can be shown to the player. */
+ if (!_ctrl_pressed) {
+ if ((int)town_zone < best_zone) continue;
+ uint dist = DistanceSquare(tile, t->xy);
+ if (dist >= best_dist) continue;
+ best_dist = dist;
+ best_zone = town_zone;
+ towns.Clear();
+ }
+ *towns.Append() = t->index;
+ }
+ }
+
+ if (towns.Length() == 0) {
+ ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HOUSE_HERE, STR_ERROR_BUILDING_NOT_ALLOWED_IN_THIS_TOWN_ZONE, WL_INFO);
+ return;
+ }
+
+ CommandContainer cmd = {
+ tile,
+ _cur_house, // p1 - house type and town index (town not yet set)
+ InteractiveRandom(), // p2 - random bits for the house
+ CMD_BUILD_HOUSE | CMD_MSG(STR_ERROR_CAN_T_BUILD_HOUSE_HERE),
+ CcPlaySound1E,
+ 0,
+ ""
+ };
+
+ if (!_ctrl_pressed) {
+ SB(cmd.p1, 16, 16, towns[0]); // set the town, it's alone on the list
+ DoCommandP(&cmd);
+ } else {
+ if (!_settings_client.gui.persistent_buildingtools) DeleteWindowById(WC_BUILD_HOUSE, 0);
+ ShowSelectTownWindow(towns, cmd);
+ }
+}
Index: src/town_gui.h
new file mode 100644
===================================================================
--- src/town_gui.h (revision 0)
+++ src/town_gui.h (working copy)
@@ -0,0 +1,22 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file town_gui.h Types and functions related to the town GUI. */
+
+#ifndef TOWN_GUI_H
+#define TOWN_GUI_H
+
+#include "tile_type.h"
+
+struct Window;
+
+void ShowBuildHousePicker(Window *parent);
+void PlaceProc_House(TileIndex tile);
+
+#endif /* TOWN_GUI_H */
Index: src/town_type.h
===================================================================
--- src/town_type.h (revision 27386)
+++ src/town_type.h (working copy)
@@ -13,10 +13,13 @@
#define TOWN_TYPE_H
#include "core/enum_type.hpp"
+#include "core/smallvec_type.hpp"
typedef uint16 TownID;
struct Town;
+typedef SmallVector TownList;
+
/** Supported initial town sizes */
enum TownSize {
TSZ_SMALL, ///< Small town.
Index: src/tracerestrict.cpp
new file mode 100644
===================================================================
--- src/tracerestrict.cpp (revision 0)
+++ src/tracerestrict.cpp (working copy)
@@ -0,0 +1,1032 @@
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tracerestrict.cpp Main file for Trace Restrict */
+
+#include "stdafx.h"
+#include "tracerestrict.h"
+#include "train.h"
+#include "core/bitmath_func.hpp"
+#include "core/pool_func.hpp"
+#include "command_func.h"
+#include "company_func.h"
+#include "viewport_func.h"
+#include "window_func.h"
+#include "order_base.h"
+#include "cargotype.h"
+#include "pathfinder/yapf/yapf_cache.h"
+#include
+
+/** @file
+ *
+ * Trace Restrict Data Storage Model Notes:
+ *
+ * Signals may have 0, 1 or 2 trace restrict programs attached to them,
+ * up to one for each track. Two-way signals share the same program.
+ *
+ * The mapping between signals and programs is defined in terms of
+ * TraceRestrictRefId to TraceRestrictProgramID,
+ * where TraceRestrictRefId is formed of the tile index and track,
+ * and TraceRestrictProgramID is an index into the program pool.
+ *
+ * If one or more mappings exist for a given signal tile, bit 12 of M3 will be set to 1.
+ * This is updated whenever mappings are added/removed for that tile. This is to avoid
+ * needing to do a mapping lookup for the common case where there is no trace restrict
+ * program mapping for the given tile.
+ *
+ * Programs in the program pool are refcounted based on the number of mappings which exist.
+ * When this falls to 0, the program is deleted from the pool.
+ * If a program has a refcount greater than 1, it is a shared program.
+ *
+ * In all cases, an empty program is evaluated the same as the absence of a program.
+ * Therefore it is not necessary to store mappings to empty unshared programs.
+ * Any editing action which would otherwise result in a mapping to an empty program
+ * which has no other references, instead removes the mapping.
+ * This is not done for shared programs as this would delete the shared aspect whenever
+ * the program became empty.
+ *
+ * Empty programs with a refcount of 1 may still exist due to the edge case where:
+ * 1: There is an empty program with refcount 2
+ * 2: One of the two mappings is deleted
+ * Finding the other mapping would entail a linear search of the mappings, and there is little
+ * to be gained by doing so.
+ */
+
+TraceRestrictProgramPool _tracerestrictprogram_pool("TraceRestrictProgram");
+INSTANTIATE_POOL_METHODS(TraceRestrictProgram)
+
+/**
+ * TraceRestrictRefId --> TraceRestrictProgramID (Pool ID) mapping
+ * The indirection is mainly to enable shared programs
+ * TODO: use a more efficient container/indirection mechanism
+ */
+TraceRestrictMapping _tracerestrictprogram_mapping;
+
+/**
+ * Default value for pathfinder penalty instructions
+ */
+static const uint16 _tracerestrict_penalty_item_default_value = 500;
+
+/**
+ * List of pre-defined pathfinder penalty values
+ * This is indexed by TraceRestrictPathfinderPenaltyPresetIndex
+ */
+const uint16 _tracerestrict_pathfinder_penalty_preset_values[] = {
+ 500,
+ 2000,
+ 8000,
+};
+
+assert_compile(lengthof(_tracerestrict_pathfinder_penalty_preset_values) == TRPPPI_END);
+
+/**
+ * This should be used when all pools have been or are immediately about to be also cleared
+ * Calling this at other times will leave dangling refcounts
+ */
+void ClearTraceRestrictMapping() {
+ _tracerestrictprogram_mapping.clear();
+}
+
+/**
+ * Flags used for the program execution condition stack
+ * Each 'if' pushes onto the stack
+ * Each 'end if' pops from the stack
+ * Elif/orif/else may modify the stack top
+ */
+enum TraceRestrictCondStackFlags {
+ TRCSF_DONE_IF = 1<<0, ///< The if/elif/else is "done", future elif/else branches will not be executed
+ TRCSF_SEEN_ELSE = 1<<1, ///< An else branch has been seen already, error if another is seen afterwards
+ TRCSF_ACTIVE = 1<<2, ///< The condition is currently active
+ TRCSF_PARENT_INACTIVE = 1<<3, ///< The parent condition is not active, thus this condition is also not active
+};
+DECLARE_ENUM_AS_BIT_SET(TraceRestrictCondStackFlags)
+
+/**
+ * Helper function to handle condition stack manipulatoin
+ */
+static void HandleCondition(std::vector &condstack, TraceRestrictCondFlags condflags, bool value)
+{
+ if (condflags & TRCF_OR) {
+ assert(!condstack.empty());
+ if (condstack.back() & TRCSF_ACTIVE) {
+ // leave TRCSF_ACTIVE set
+ return;
+ }
+ }
+
+ if (condflags & (TRCF_OR | TRCF_ELSE)) {
+ assert(!condstack.empty());
+ if (condstack.back() & (TRCSF_DONE_IF | TRCSF_PARENT_INACTIVE)) {
+ condstack.back() &= ~TRCSF_ACTIVE;
+ return;
+ }
+ } else {
+ if (!condstack.empty() && !(condstack.back() & TRCSF_ACTIVE)) {
+ //this is a 'nested if', the 'parent if' is not active
+ condstack.push_back(TRCSF_PARENT_INACTIVE);
+ return;
+ }
+ condstack.push_back(static_cast(0));
+ }
+
+ if (value) {
+ condstack.back() |= TRCSF_DONE_IF | TRCSF_ACTIVE;
+ } else {
+ condstack.back() &= ~TRCSF_ACTIVE;
+ }
+}
+
+/**
+ * Integer condition testing
+ * Test value op condvalue
+ */
+static bool TestCondition(uint16 value, TraceRestrictCondOp condop, uint16 condvalue)
+{
+ switch (condop) {
+ case TRCO_IS:
+ return value == condvalue;
+ case TRCO_ISNOT:
+ return value != condvalue;
+ case TRCO_LT:
+ return value < condvalue;
+ case TRCO_LTE:
+ return value <= condvalue;
+ case TRCO_GT:
+ return value > condvalue;
+ case TRCO_GTE:
+ return value >= condvalue;
+ default:
+ NOT_REACHED();
+ return false;
+ }
+}
+
+/**
+ * Binary condition testing helper function
+ */
+static bool TestBinaryConditionCommon(TraceRestrictItem item, bool input)
+{
+ switch (GetTraceRestrictCondOp(item)) {
+ case TRCO_IS:
+ return input;
+
+ case TRCO_ISNOT:
+ return !input;
+
+ default:
+ NOT_REACHED();
+ return false;
+ }
+}
+
+/**
+ * Test order condition
+ * @p order may be NULL
+ */
+static bool TestOrderCondition(const Order *order, TraceRestrictItem item)
+{
+ bool result = false;
+
+ if (order) {
+ DestinationID condvalue = GetTraceRestrictValue(item);
+ switch (static_cast(GetTraceRestrictAuxField(item))) {
+ case TROCAF_STATION:
+ result = order->IsType(OT_GOTO_STATION) && order->GetDestination() == condvalue;
+ break;
+
+ case TROCAF_WAYPOINT:
+ result = order->IsType(OT_GOTO_WAYPOINT) && order->GetDestination() == condvalue;
+ break;
+
+ case OT_GOTO_DEPOT:
+ result = order->IsType(OT_GOTO_DEPOT) && order->GetDestination() == condvalue;
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+ }
+ return TestBinaryConditionCommon(item, result);
+}
+
+/**
+ * Test station condition
+ */
+static bool TestStationCondition(StationID station, TraceRestrictItem item)
+{
+ bool result = (GetTraceRestrictAuxField(item) == TROCAF_STATION) && (station == GetTraceRestrictValue(item));
+ return TestBinaryConditionCommon(item, result);
+
+}
+
+/**
+ * Execute program on train and store results in out
+ * @p v may not be NULL
+ * @p out should be zero-initialised
+ */
+void TraceRestrictProgram::Execute(const Train* v, const TraceRestrictProgramInput &input, TraceRestrictProgramResult& out) const
+{
+ // static to avoid needing to re-alloc/resize on each execution
+ static std::vector condstack;
+ condstack.clear();
+
+ bool have_previous_signal = false;
+ TileIndex previous_signal_tile = INVALID_TILE;
+
+ size_t size = this->items.size();
+ for (size_t i = 0; i < size; i++) {
+ TraceRestrictItem item = this->items[i];
+ TraceRestrictItemType type = GetTraceRestrictType(item);
+
+ if (IsTraceRestrictConditional(item)) {
+ TraceRestrictCondFlags condflags = GetTraceRestrictCondFlags(item);
+ TraceRestrictCondOp condop = GetTraceRestrictCondOp(item);
+
+ if (type == TRIT_COND_ENDIF) {
+ assert(!condstack.empty());
+ if (condflags & TRCF_ELSE) {
+ // else
+ assert(!(condstack.back() & TRCSF_SEEN_ELSE));
+ HandleCondition(condstack, condflags, true);
+ condstack.back() |= TRCSF_SEEN_ELSE;
+ } else {
+ // end if
+ condstack.pop_back();
+ }
+ } else {
+ uint16 condvalue = GetTraceRestrictValue(item);
+ bool result = false;
+ switch(type) {
+ case TRIT_COND_UNDEFINED:
+ result = false;
+ break;
+
+ case TRIT_COND_TRAIN_LENGTH:
+ result = TestCondition(CeilDiv(v->gcache.cached_total_length, TILE_SIZE), condop, condvalue);
+ break;
+
+ case TRIT_COND_MAX_SPEED:
+ result = TestCondition(v->GetDisplayMaxSpeed(), condop, condvalue);
+ break;
+
+ case TRIT_COND_CURRENT_ORDER:
+ result = TestOrderCondition(&(v->current_order), item);
+ break;
+
+ case TRIT_COND_NEXT_ORDER: {
+ if (v->orders.list == NULL) break;
+ if (v->orders.list->GetNumOrders() == 0) break;
+
+ const Order *current_order = v->GetOrder(v->cur_real_order_index);
+ for (const Order *order = v->orders.list->GetNext(current_order); order != current_order; order = v->orders.list->GetNext(order)) {
+ if (order->IsGotoOrder()) {
+ result = TestOrderCondition(order, item);
+ break;
+ }
+ }
+ break;
+ }
+
+ case TRIT_COND_LAST_STATION:
+ result = TestStationCondition(v->last_station_visited, item);
+ break;
+
+ case TRIT_COND_CARGO: {
+ bool have_cargo = false;
+ for (const Vehicle *v_iter = v; v_iter != NULL; v_iter = v_iter->Next()) {
+ if (v_iter->cargo_type == GetTraceRestrictValue(item) && v_iter->cargo_cap > 0) {
+ have_cargo = true;
+ break;
+ }
+ }
+ result = TestBinaryConditionCommon(item, have_cargo);
+ break;
+ }
+
+ case TRIT_COND_ENTRY_DIRECTION: {
+ bool direction_match;
+ switch (GetTraceRestrictValue(item)) {
+ case TRNTSV_NE:
+ case TRNTSV_SE:
+ case TRNTSV_SW:
+ case TRNTSV_NW:
+ direction_match = (static_cast(GetTraceRestrictValue(item)) == TrackdirToExitdir(ReverseTrackdir(input.trackdir)));
+ break;
+
+ case TRDTSV_FRONT:
+ direction_match = IsTileType(input.tile, MP_RAILWAY) && HasSignalOnTrackdir(input.tile, input.trackdir);
+ break;
+
+ case TRDTSV_BACK:
+ direction_match = IsTileType(input.tile, MP_RAILWAY) && !HasSignalOnTrackdir(input.tile, input.trackdir);
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+ result = TestBinaryConditionCommon(item, direction_match);
+ break;
+ }
+
+ case TRIT_COND_PBS_ENTRY_SIGNAL: {
+ // TRVT_TILE_INDEX value type uses the next slot
+ i++;
+ uint32_t signal_tile = this->items[i];
+ if (!have_previous_signal) {
+ if (input.previous_signal_callback) {
+ previous_signal_tile = input.previous_signal_callback(v, input.previous_signal_ptr);
+ }
+ have_previous_signal = true;
+ }
+ bool match = (signal_tile != INVALID_TILE)
+ && (previous_signal_tile == signal_tile);
+ result = TestBinaryConditionCommon(item, match);
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ }
+ HandleCondition(condstack, condflags, result);
+ }
+ } else {
+ if (condstack.empty() || condstack.back() & TRCSF_ACTIVE) {
+ switch(type) {
+ case TRIT_PF_DENY:
+ if (GetTraceRestrictValue(item)) {
+ out.flags &= ~TRPRF_DENY;
+ } else {
+ out.flags |= TRPRF_DENY;
+ }
+ break;
+
+ case TRIT_PF_PENALTY:
+ switch (static_cast(GetTraceRestrictAuxField(item))) {
+ case TRPPAF_VALUE:
+ out.penalty += GetTraceRestrictValue(item);
+ break;
+
+ case TRPPAF_PRESET: {
+ uint16 index = GetTraceRestrictValue(item);
+ assert(index < TRPPPI_END);
+ out.penalty += _tracerestrict_pathfinder_penalty_preset_values[index];
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ }
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+ }
+ }
+ }
+ assert(condstack.empty());
+}
+
+/**
+ * Decrement ref count, only use when removing a mapping
+ */
+void TraceRestrictProgram::DecrementRefCount() {
+ assert(this->refcount > 0);
+ this->refcount--;
+ if (this->refcount == 0) {
+ delete this;
+ }
+}
+
+/**
+ * Validate a instruction list
+ * Returns successful result if program seems OK
+ * This only validates that conditional nesting is correct, at present
+ */
+CommandCost TraceRestrictProgram::Validate(const std::vector &items) {
+ // static to avoid needing to re-alloc/resize on each execution
+ static std::vector condstack;
+ condstack.clear();
+
+ size_t size = items.size();
+ for (size_t i = 0; i < size; i++) {
+ TraceRestrictItem item = items[i];
+ TraceRestrictItemType type = GetTraceRestrictType(item);
+
+ if (IsTraceRestrictConditional(item)) {
+ TraceRestrictCondFlags condflags = GetTraceRestrictCondFlags(item);
+
+ if (type == TRIT_COND_ENDIF) {
+ if (condstack.empty()) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_NO_IF); // else/endif with no starting if
+ }
+ if (condflags & TRCF_ELSE) {
+ // else
+ if (condstack.back() & TRCSF_SEEN_ELSE) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE); // Two else clauses
+ }
+ HandleCondition(condstack, condflags, true);
+ condstack.back() |= TRCSF_SEEN_ELSE;
+ } else {
+ // end if
+ condstack.pop_back();
+ }
+ } else {
+ if (condflags & (TRCF_OR | TRCF_ELSE)) { // elif/orif
+ if (condstack.empty()) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_ELIF_NO_IF); // Pre-empt assertions in HandleCondition
+ }
+ if (condstack.back() & TRCSF_SEEN_ELSE) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE); // else clause followed by elif/orif
+ }
+ }
+ HandleCondition(condstack, condflags, true);
+ }
+ } else {
+ // check multi-word instructions
+ if (IsTraceRestrictDoubleItem(item)) {
+ i++;
+ if (i >= size) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE); // instruction ran off end
+ }
+ }
+ }
+ }
+ if(!condstack.empty()) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK);
+ }
+ return CommandCost();
+}
+
+/**
+ * Convert an instruction index into an item array index
+ */
+size_t TraceRestrictProgram::InstructionOffsetToArrayOffset(const std::vector &items, size_t offset)
+{
+ size_t output_offset = 0;
+ size_t size = items.size();
+ for (size_t i = 0; i < offset && output_offset < size; i++, output_offset++) {
+ if (IsTraceRestrictDoubleItem(items[output_offset])) {
+ output_offset++;
+ }
+ }
+ return output_offset;
+}
+
+/**
+ * Convert an item array index into an instruction index
+ */
+size_t TraceRestrictProgram::ArrayOffsetToInstructionOffset(const std::vector &items, size_t offset)
+{
+ size_t output_offset = 0;
+ for (size_t i = 0; i < offset; i++, output_offset++) {
+ if (IsTraceRestrictDoubleItem(items[i])) {
+ i++;
+ }
+ }
+ return output_offset;
+}
+
+/**
+ * Set the value and aux field of @p item, as per the value type in @p value_type
+ */
+void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueType value_type)
+{
+ switch (value_type) {
+ case TRVT_NONE:
+ case TRVT_INT:
+ case TRVT_DENY:
+ case TRVT_SPEED:
+ case TRVT_TILE_INDEX:
+ SetTraceRestrictValue(item, 0);
+ SetTraceRestrictAuxField(item, 0);
+ break;
+
+ case TRVT_ORDER:
+ SetTraceRestrictValue(item, INVALID_STATION);
+ SetTraceRestrictAuxField(item, TROCAF_STATION);
+ break;
+
+ case TRVT_CARGO_ID:
+ assert(_sorted_standard_cargo_specs_size > 0);
+ SetTraceRestrictValue(item, _sorted_cargo_specs[0]->Index());
+ SetTraceRestrictAuxField(item, 0);
+ break;
+
+ case TRVT_DIRECTION:
+ SetTraceRestrictValue(item, TRDTSV_FRONT);
+ SetTraceRestrictAuxField(item, 0);
+ break;
+
+ case TRVT_PF_PENALTY:
+ SetTraceRestrictValue(item, TRPPPI_SMALL);
+ SetTraceRestrictAuxField(item, TRPPAF_PRESET);
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+}
+
+/**
+ * Set the type field of a TraceRestrictItem, and resets any other fields which are no longer valid/meaningful to sensible defaults
+ */
+void SetTraceRestrictTypeAndNormalise(TraceRestrictItem &item, TraceRestrictItemType type)
+{
+ if (item != 0) {
+ assert(GetTraceRestrictType(item) != TRIT_NULL);
+ assert(IsTraceRestrictConditional(item) == IsTraceRestrictTypeConditional(type));
+ }
+ assert(type != TRIT_NULL);
+
+ TraceRestrictTypePropertySet old_properties = GetTraceRestrictTypeProperties(item);
+ SetTraceRestrictType(item, type);
+ TraceRestrictTypePropertySet new_properties = GetTraceRestrictTypeProperties(item);
+
+ if (old_properties.cond_type != new_properties.cond_type ||
+ old_properties.value_type != new_properties.value_type) {
+ SetTraceRestrictCondOp(item, TRCO_IS);
+ SetTraceRestrictValueDefault(item, new_properties.value_type);
+ }
+}
+
+/**
+ * Sets the "signal has a trace restrict mapping" bit
+ * This looks for mappings with that tile index
+ */
+void SetIsSignalRestrictedBit(TileIndex t)
+{
+ // First mapping for this tile, or later
+ TraceRestrictMapping::iterator lower_bound = _tracerestrictprogram_mapping.lower_bound(MakeTraceRestrictRefId(t, static_cast(0)));
+
+ // First mapping for next tile, or later
+ TraceRestrictMapping::iterator upper_bound = _tracerestrictprogram_mapping.lower_bound(MakeTraceRestrictRefId(t + 1, static_cast(0)));
+
+ // If iterators are the same, there are no mappings for this tile
+ SetRestrictedSignal(t, lower_bound != upper_bound);
+}
+
+/**
+ * Create a new program mapping to an existing program
+ * If a mapping already exists, it is removed
+ */
+void TraceRestrictCreateProgramMapping(TraceRestrictRefId ref, TraceRestrictProgram *prog)
+{
+ std::pair insert_result =
+ _tracerestrictprogram_mapping.insert(std::make_pair(ref, TraceRestrictMappingItem(prog->index)));
+
+ if (!insert_result.second) {
+ // value was not inserted, there is an existing mapping
+ // unref the existing mapping before updating it
+ _tracerestrictprogram_pool.Get(insert_result.first->second.program_id)->DecrementRefCount();
+ insert_result.first->second = prog->index;
+ }
+ prog->IncrementRefCount();
+
+ TileIndex tile = GetTraceRestrictRefIdTileIndex(ref);
+ Track track = GetTraceRestrictRefIdTrack(ref);
+ SetIsSignalRestrictedBit(tile);
+ MarkTileDirtyByTile(tile);
+ YapfNotifyTrackLayoutChange(tile, track);
+}
+
+/**
+ * Remove a program mapping
+ */
+void TraceRestrictRemoveProgramMapping(TraceRestrictRefId ref)
+{
+ TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.find(ref);
+ if (iter != _tracerestrictprogram_mapping.end()) {
+ // Found
+ _tracerestrictprogram_pool.Get(iter->second.program_id)->DecrementRefCount();
+ _tracerestrictprogram_mapping.erase(iter);
+
+ TileIndex tile = GetTraceRestrictRefIdTileIndex(ref);
+ Track track = GetTraceRestrictRefIdTrack(ref);
+ SetIsSignalRestrictedBit(tile);
+ MarkTileDirtyByTile(tile);
+ YapfNotifyTrackLayoutChange(tile, track);
+ }
+}
+
+/**
+ * Gets the signal program for the tile ref @p ref
+ * An empty program will be constructed if none exists, and @p create_new is true, unless the pool is full
+ */
+TraceRestrictProgram *GetTraceRestrictProgram(TraceRestrictRefId ref, bool create_new)
+{
+ // Optimise for lookup, creating doesn't have to be that fast
+
+ TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.find(ref);
+ if (iter != _tracerestrictprogram_mapping.end()) {
+ // Found
+ return _tracerestrictprogram_pool.Get(iter->second.program_id);
+ } else if (create_new) {
+ // Not found
+
+ // Create new pool item
+ if (!TraceRestrictProgram::CanAllocateItem()) {
+ return NULL;
+ }
+ TraceRestrictProgram *prog = new TraceRestrictProgram();
+
+ // Create new mapping to pool item
+ TraceRestrictCreateProgramMapping(ref, prog);
+ return prog;
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * Notify that a signal is being removed
+ * Remove any trace restrict mappings associated with it
+ */
+void TraceRestrictNotifySignalRemoval(TileIndex tile, Track track)
+{
+ TraceRestrictRefId ref = MakeTraceRestrictRefId(tile, track);
+ TraceRestrictRemoveProgramMapping(ref);
+ DeleteWindowById(WC_TRACE_RESTRICT, ref);
+}
+
+/**
+ * Helper function to perform parameter bit-packing and call DoCommandP, for instruction modification actions
+ */
+void TraceRestrictDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, uint32 offset, uint32 value, StringID error_msg)
+{
+ uint32 p1 = 0;
+ SB(p1, 0, 3, track);
+ SB(p1, 3, 5, type);
+ assert(offset < (1 << 16));
+ SB(p1, 8, 16, offset);
+ DoCommandP(tile, p1, value, CMD_PROGRAM_TRACERESTRICT_SIGNAL | CMD_MSG(error_msg));
+}
+
+/**
+ * Check whether a tile/tracl pair contains a usable signal
+ */
+static CommandCost TraceRestrictCheckTileIsUsable(TileIndex tile, Track track)
+{
+ // Check that there actually is a signal here
+ if (!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);
+ }
+
+ // Check tile ownership, do this afterwards to avoid tripping up on house/industry tiles
+ CommandCost ret = CheckTileOwnership(tile);
+ if (ret.Failed()) {
+ return ret;
+ }
+
+ return CommandCost();
+}
+
+/**
+ * Returns an appropriate default value for the second item of a dual-item instruction
+ * @p item is the first item of the instruction
+ */
+static uint32 GetDualInstructionInitialValue(TraceRestrictItem item)
+{
+ switch (GetTraceRestrictType(item)) {
+ case TRIT_COND_PBS_ENTRY_SIGNAL:
+ return INVALID_TILE;
+
+ default:
+ NOT_REACHED();
+ }
+}
+
+/**
+ * The main command for editing a signal tracerestrict program.
+ * @param tile The tile which contains the signal.
+ * @param flags Internal command handler stuff.
+ * Below apply for instruction modification actions only
+ * @param p1 Bitstuffed items
+ * @param p2 Item, for insert and modify operations
+ * @return the cost of this operation (which is free), or an error
+ */
+CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ TraceRestrictDoCommandType type = static_cast(GB(p1, 3, 5));
+
+ if (type >= TRDCT_PROG_COPY) {
+ return CmdProgramSignalTraceRestrictProgMgmt(tile, flags, p1, p2, text);
+ }
+
+ Track track = static_cast(GB(p1, 0, 3));
+ uint32 offset = GB(p1, 8, 16);
+ TraceRestrictItem item = static_cast(p2);
+
+ CommandCost ret = TraceRestrictCheckTileIsUsable(tile, track);
+ if (ret.Failed()) {
+ return ret;
+ }
+
+ bool can_make_new = (type == TRDCT_INSERT_ITEM) && (flags & DC_EXEC);
+ bool need_existing = (type != TRDCT_INSERT_ITEM);
+ TraceRestrictProgram *prog = GetTraceRestrictProgram(MakeTraceRestrictRefId(tile, track), can_make_new);
+ if (need_existing && !prog) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_NO_PROGRAM);
+ }
+
+ uint32 offset_limit_exclusive = ((type == TRDCT_INSERT_ITEM) ? 1 : 0);
+ if (prog) offset_limit_exclusive += prog->items.size();
+
+ if (offset >= offset_limit_exclusive) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE);
+ }
+
+ // copy program
+ std::vector items;
+ if (prog) items = prog->items;
+
+ switch (type) {
+ case TRDCT_INSERT_ITEM:
+ items.insert(TraceRestrictProgram::InstructionAt(items, offset), item);
+ if (IsTraceRestrictConditional(item) &&
+ GetTraceRestrictCondFlags(item) == 0 &&
+ GetTraceRestrictType(item) != TRIT_COND_ENDIF) {
+ // this is an opening if block, insert a corresponding end if
+ TraceRestrictItem endif_item = 0;
+ SetTraceRestrictType(endif_item, TRIT_COND_ENDIF);
+ items.insert(TraceRestrictProgram::InstructionAt(items, offset) + 1, endif_item);
+ } else if (IsTraceRestrictDoubleItem(item)) {
+ items.insert(TraceRestrictProgram::InstructionAt(items, offset) + 1, GetDualInstructionInitialValue(item));
+ }
+ break;
+
+ case TRDCT_MODIFY_ITEM: {
+ std::vector::iterator old_item = TraceRestrictProgram::InstructionAt(items, offset);
+ if (IsTraceRestrictConditional(*old_item) != IsTraceRestrictConditional(item)) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_CHANGE_CONDITIONALITY);
+ }
+ bool old_is_dual = IsTraceRestrictDoubleItem(*old_item);
+ bool new_is_dual = IsTraceRestrictDoubleItem(item);
+ *old_item = item;
+ if (old_is_dual && !new_is_dual) {
+ items.erase(old_item + 1);
+ } else if (!old_is_dual && new_is_dual) {
+ items.insert(old_item + 1, GetDualInstructionInitialValue(item));
+ }
+ break;
+ }
+
+ case TRDCT_MODIFY_DUAL_ITEM: {
+ std::vector::iterator old_item = TraceRestrictProgram::InstructionAt(items, offset);
+ if (!IsTraceRestrictDoubleItem(*old_item)) {
+ return CMD_ERROR;
+ }
+ *(old_item + 1) = p2;
+ break;
+ }
+
+ case TRDCT_REMOVE_ITEM: {
+ TraceRestrictItem old_item = *TraceRestrictProgram::InstructionAt(items, offset);
+ if (IsTraceRestrictConditional(old_item)) {
+ bool remove_whole_block = false;
+ if (GetTraceRestrictCondFlags(old_item) == 0) {
+ if (GetTraceRestrictType(old_item) == TRIT_COND_ENDIF) {
+ // this is an end if, can't remove these
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ENDIF);
+ } else {
+ // this is an opening if
+ remove_whole_block = true;
+ }
+ }
+
+ uint32 recursion_depth = 1;
+ std::vector::iterator remove_start = TraceRestrictProgram::InstructionAt(items, offset);
+ std::vector::iterator remove_end = remove_start + 1;
+
+ // iterate until matching end block found
+ for (; remove_end != items.end(); ++remove_end) {
+ TraceRestrictItem current_item = *remove_end;
+ if (IsTraceRestrictConditional(current_item)) {
+ if (GetTraceRestrictCondFlags(current_item) == 0) {
+ if (GetTraceRestrictType(current_item) == TRIT_COND_ENDIF) {
+ // this is an end if
+ recursion_depth--;
+ if (recursion_depth == 0) {
+ if (remove_whole_block) {
+ // inclusively remove up to here
+ ++remove_end;
+ break;
+ } else {
+ // exclusively remove up to here
+ break;
+ }
+ }
+ } else {
+ // this is an opening if
+ recursion_depth++;
+ }
+ } else {
+ // this is an else/or type block
+ if (recursion_depth == 1 && !remove_whole_block) {
+ // exclusively remove up to here
+ recursion_depth = 0;
+ break;
+ }
+ }
+ } else if (IsTraceRestrictDoubleItem(current_item)) {
+ // this is a double-item, jump over the next item as well
+ ++remove_end;
+ }
+ }
+ if (recursion_depth != 0) return CMD_ERROR; // ran off the end
+ items.erase(remove_start, remove_end);
+ } else {
+ std::vector::iterator remove_start = TraceRestrictProgram::InstructionAt(items, offset);
+ std::vector::iterator remove_end = remove_start + 1;
+
+ if (IsTraceRestrictDoubleItem(old_item)) {
+ // this is a double-item, remove the next item as well
+ ++remove_end;
+ }
+ items.erase(remove_start, remove_end);
+ }
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+
+ CommandCost validation_result = TraceRestrictProgram::Validate(items);
+ if (validation_result.Failed()) {
+ return validation_result;
+ }
+
+ if (flags & DC_EXEC) {
+ assert(prog);
+
+ // move in modified program
+ prog->items.swap(items);
+
+ if (prog->items.size() == 0 && prog->refcount == 1) {
+ // program is empty, and this tile is the only reference to it
+ // so delete it, as it's redundant
+ TraceRestrictRemoveProgramMapping(MakeTraceRestrictRefId(tile, track));
+ }
+
+ // update windows
+ InvalidateWindowClassesData(WC_TRACE_RESTRICT);
+ }
+
+ return CommandCost();
+}
+
+/**
+ * Helper function to perform parameter bit-packing and call DoCommandP, for program management actions
+ */
+void TraceRestrictProgMgmtWithSourceDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type,
+ TileIndex source_tile, Track source_track, StringID error_msg)
+{
+ uint32 p1 = 0;
+ SB(p1, 0, 3, track);
+ SB(p1, 3, 5, type);
+ SB(p1, 8, 3, source_track);
+ DoCommandP(tile, p1, source_tile, CMD_PROGRAM_TRACERESTRICT_SIGNAL | CMD_MSG(error_msg));
+}
+
+/**
+ * Sub command for copy/share/unshare operations on signal tracerestrict programs.
+ * @param tile The tile which contains the signal.
+ * @param flags Internal command handler stuff.
+ * @param p1 Bitstuffed items
+ * @param p2 Source tile, for share/copy operations
+ * @return the cost of this operation (which is free), or an error
+ */
+CommandCost CmdProgramSignalTraceRestrictProgMgmt(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
+{
+ TraceRestrictDoCommandType type = static_cast(GB(p1, 3, 5));
+ Track track = static_cast(GB(p1, 0, 3));
+ Track source_track = static_cast(GB(p1, 8, 3));
+ TileIndex source_tile = static_cast(p2);
+
+ TraceRestrictRefId self = MakeTraceRestrictRefId(tile, track);
+ TraceRestrictRefId source = MakeTraceRestrictRefId(source_tile, source_track);
+
+ assert(type >= TRDCT_PROG_COPY);
+
+ CommandCost ret = TraceRestrictCheckTileIsUsable(tile, track);
+ if (ret.Failed()) {
+ return ret;
+ }
+
+ if (type == TRDCT_PROG_SHARE || type == TRDCT_PROG_COPY) {
+ if (self == source) {
+ return_cmd_error(STR_TRACE_RESTRICT_ERROR_SOURCE_SAME_AS_TARGET);
+ }
+
+ ret = TraceRestrictCheckTileIsUsable(source_tile, source_track);
+ if (ret.Failed()) {
+ return ret;
+ }
+ }
+
+ if (!(flags & DC_EXEC)) {
+ return CommandCost();
+ }
+
+ switch (type) {
+ case TRDCT_PROG_COPY: {
+ TraceRestrictRemoveProgramMapping(self);
+ TraceRestrictProgram *prog = GetTraceRestrictProgram(self, true);
+ if (!prog) {
+ // allocation failed
+ return CMD_ERROR;
+ }
+
+ TraceRestrictProgram *source_prog = GetTraceRestrictProgram(source, false);
+ if (source_prog) {
+ prog->items = source_prog->items; // copy
+ }
+ break;
+ }
+
+ case TRDCT_PROG_SHARE: {
+ TraceRestrictRemoveProgramMapping(self);
+ TraceRestrictProgram *source_prog = GetTraceRestrictProgram(source, true);
+ if (!source_prog) {
+ // allocation failed
+ return CMD_ERROR;
+ }
+
+ TraceRestrictCreateProgramMapping(self, source_prog);
+ break;
+ }
+
+ case TRDCT_PROG_UNSHARE: {
+ std::vector items;
+ TraceRestrictProgram *prog = GetTraceRestrictProgram(self, false);
+ if (prog) {
+ // copy program into temporary
+ items = prog->items;
+ }
+ // remove old program
+ TraceRestrictRemoveProgramMapping(self);
+
+ if (items.size()) {
+ // if prog is non-empty, create new program and move temporary in
+ TraceRestrictProgram *new_prog = GetTraceRestrictProgram(self, true);
+ if (!new_prog) {
+ // allocation failed
+ return CMD_ERROR;
+ }
+
+ new_prog->items.swap(items);
+ }
+ break;
+ }
+
+ case TRDCT_PROG_RESET: {
+ TraceRestrictRemoveProgramMapping(self);
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+
+ // update windows
+ InvalidateWindowClassesData(WC_TRACE_RESTRICT);
+
+ return CommandCost();
+}
+
+/**
+ * This is called when a station, waypoint or depot is about to be deleted
+ * Scan program pool and change any references to it to the invalid station ID, to avoid dangling references
+ */
+void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type, uint16 index)
+{
+ TraceRestrictProgram *prog;
+
+ FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog) {
+ for (size_t i = 0; i < prog->items.size(); i++) {
+ TraceRestrictItem &item = prog->items[i]; // note this is a reference,
+ if (GetTraceRestrictType(item) == TRIT_COND_CURRENT_ORDER ||
+ GetTraceRestrictType(item) == TRIT_COND_NEXT_ORDER ||
+ GetTraceRestrictType(item) == TRIT_COND_LAST_STATION) {
+ if (GetTraceRestrictAuxField(item) == type && GetTraceRestrictValue(item) == index) {
+ SetTraceRestrictValueDefault(item, TRVT_ORDER); // this updates the instruction in-place
+ }
+ }
+ }
+ }
+
+ // update windows
+ InvalidateWindowClassesData(WC_TRACE_RESTRICT);
+}
Index: src/tracerestrict.h
new file mode 100644
===================================================================
--- src/tracerestrict.h (revision 0)
+++ src/tracerestrict.h (working copy)
@@ -0,0 +1,546 @@
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tracerestrict.h Header file for Trace Restrict */
+
+#ifndef TRACERESTRICT_H
+#define TRACERESTRICT_H
+
+#include "stdafx.h"
+#include "core/bitmath_func.hpp"
+#include "core/enum_type.hpp"
+#include "core/pool_type.hpp"
+#include "command_func.h"
+#include "rail_map.h"
+#include "tile_type.h"
+#include
+#include
+
+struct Train;
+
+/** Program pool ID type. */
+typedef uint32 TraceRestrictProgramID;
+struct TraceRestrictProgram;
+
+/** Tile/track mapping type. */
+typedef uint32 TraceRestrictRefId;
+
+/** Type of the pool for trace restrict programs. */
+typedef Pool TraceRestrictProgramPool;
+/** The actual pool for trace restrict nodes. */
+extern TraceRestrictProgramPool _tracerestrictprogram_pool;
+
+extern const uint16 _tracerestrict_pathfinder_penalty_preset_values[];
+
+#define FOR_ALL_TRACE_RESTRICT_PROGRAMS_FROM(var, start) FOR_ALL_ITEMS_FROM(TraceRestrictProgram, tr_index, var, start)
+#define FOR_ALL_TRACE_RESTRICT_PROGRAMS(var) FOR_ALL_TRACE_RESTRICT_PROGRAMS_FROM(var, 0)
+
+/** Type used for the TraceRestrictRefId -> TraceRestrictProgramID mapping */
+struct TraceRestrictMappingItem {
+ TraceRestrictProgramID program_id;
+
+ TraceRestrictMappingItem() { }
+
+ TraceRestrictMappingItem(TraceRestrictProgramID program_id_)
+ : program_id(program_id_) { }
+};
+
+typedef std::map TraceRestrictMapping;
+
+/** The actual mapping from TraceRestrictRefId to TraceRestrictProgramID. */
+extern TraceRestrictMapping _tracerestrictprogram_mapping;
+
+void ClearTraceRestrictMapping();
+
+/** Type of a single instruction, this is bit-packed as per TraceRestrictItemFlagAllocation */
+typedef uint32 TraceRestrictItem;
+
+/**
+ * Describes the allocation of bits to fields in TraceRestrictItem
+ * Of the fields below, the type seem the most likely
+ * to need future expansion, hence the reserved bits are placed
+ * immediately after them
+ *
+ * COUNT values describe the field bit width
+ * OFFSET values describe the field bit offset
+ */
+enum TraceRestrictItemFlagAllocation {
+ TRIFA_TYPE_COUNT = 5,
+ TRIFA_TYPE_OFFSET = 0,
+
+ /* 3 bits reserved for future use */
+
+ TRIFA_COND_FLAGS_COUNT = 3,
+ TRIFA_COND_FLAGS_OFFSET = 8,
+
+ TRIFA_AUX_FIELD_COUNT = 2,
+ TRIFA_AUX_FIELD_OFFSET = 11,
+
+ TRIFA_COND_OP_COUNT = 3,
+ TRIFA_COND_OP_OFFSET = 13,
+
+ TRIFA_VALUE_COUNT = 16,
+ TRIFA_VALUE_OFFSET = 16,
+};
+
+/**
+ * Enumeration of TraceRestrictItem type field
+ * This is split into two halves:
+ * * non-conditionals < TRIT_COND_BEGIN
+ * * conditionals, >= TRIT_COND_BEGIN
+ */
+enum TraceRestrictItemType {
+ TRIT_NULL = 0, ///< Null-type, not in programs and not valid for execution, mainly used with TraceRestrictNullTypeSpecialValue for start/end
+ TRIT_PF_DENY = 1, ///< Pathfinder deny/allow
+ TRIT_PF_PENALTY = 2, ///< Add to pathfinder penalty
+
+ TRIT_COND_BEGIN = 8, ///< Start of conditional item types, note that this has the save value as TRIT_COND_ENDIF
+ TRIT_COND_ENDIF = 8, ///< This is an endif block or an else block
+ TRIT_COND_UNDEFINED = 9, ///< This condition has no type defined (evaluate as false)
+ TRIT_COND_TRAIN_LENGTH = 10, ///< Test train length
+ TRIT_COND_MAX_SPEED = 11, ///< Test train max speed
+ TRIT_COND_CURRENT_ORDER = 12, ///< Test train current order (station, waypoint or depot)
+ TRIT_COND_NEXT_ORDER = 13, ///< Test train next order (station, waypoint or depot)
+ TRIT_COND_LAST_STATION = 14, ///< Test train last visited station
+ TRIT_COND_CARGO = 15, ///< Test if train can carry cargo type
+ TRIT_COND_ENTRY_DIRECTION = 16, ///< Test which side of signal/signal tile is being entered from
+ TRIT_COND_PBS_ENTRY_SIGNAL = 17, ///< Test tile and PBS-state of previous signal
+ /* space up to 31 */
+};
+
+/**
+ * TraceRestrictItem condition flags field, only valid with conditional types (IsTraceRestrictTypeConditional() is true)
+ */
+enum TraceRestrictCondFlags {
+ TRCF_DEFAULT = 0, ///< indicates end if for type: TRIT_COND_ENDIF, if otherwise
+ TRCF_ELSE = 1 << 0, ///< indicates an else block for type: TRIT_COND_ENDIF, elif otherwise
+ TRCF_OR = 1 << 1, ///< indicates an orif block, not valid with type: TRIT_COND_ENDIF
+ /* 1 bit spare */
+};
+DECLARE_ENUM_AS_BIT_SET(TraceRestrictCondFlags)
+
+/**
+ * Enumeration of TraceRestrictItemvalue type field when type is TRIT_NULL
+ */
+enum TraceRestrictNullTypeSpecialValue {
+ TRNTSV_NULL = 0, ///< null, what you get when you zero-init a TraceRestrictItemvalue
+ TRNTSV_START = 1, ///< start tag, generated within GUI
+ TRNTSV_END = 2, ///< end tag, generated within GUI
+};
+
+/**
+ * Enumeration of TraceRestrictItemvalue type field when value type is TRVT_DIRECTION
+ */
+enum TraceRestrictDirectionTypeSpecialValue {
+ TRNTSV_NE = 0, ///< DIAGDIR_NE: entering at NE tile edge
+ TRNTSV_SE = 1, ///< DIAGDIR_SE: entering at SE tile edge
+ TRNTSV_SW = 2, ///< DIAGDIR_SW: entering at SW tile edge
+ TRNTSV_NW = 3, ///< DIAGDIR_NW: entering at NW tile edge
+ TRDTSV_FRONT = 4, ///< entering at front face of signal
+ TRDTSV_BACK = 5, ///< entering at rear face of signal
+};
+
+/**
+ * TraceRestrictItem condition operator field, only valid with conditional types (IsTraceRestrictTypeConditional() is true)
+ */
+enum TraceRestrictCondOp {
+ TRCO_IS = 0, ///< equality test, or can carry test for cargo
+ TRCO_ISNOT = 1, ///< inequality test, or can't carry test for cargo
+ TRCO_LT = 2, ///< less than test
+ TRCO_LTE = 3, ///< less than or equal test
+ TRCO_GT = 4, ///< greater than test
+ TRCO_GTE = 5, ///< greater than or equal test
+ /* space up to 7 */
+};
+
+/**
+ * TraceRestrictItem auxiliary type field, for order type conditionals
+ */
+enum TraceRestrictOrderCondAuxField {
+ TROCAF_STATION = 0, ///< value field is a station StationID
+ TROCAF_WAYPOINT = 1, ///< value field is a waypoint StationID
+ TROCAF_DEPOT = 2, ///< value field is a depot DepotID
+ /* space up to 3 */
+};
+
+/**
+ * TraceRestrictItem auxiliary type field, for order type conditionals
+ */
+enum TraceRestrictPathfinderPenaltyAuxField {
+ TRPPAF_VALUE = 0, ///< value field is a the pathfinder penalty to use
+ TRPPAF_PRESET = 1, ///< value field is a pathfinder penalty prefix index: TraceRestrictPathfinderPenaltyPresetIndex
+ /* space up to 3 */
+};
+
+/**
+ * TraceRestrictItem pathfinder penalty preset index
+ * This may not be shortened, only lengthened, as preset indexes are stored in save games
+ */
+enum TraceRestrictPathfinderPenaltyPresetIndex {
+ TRPPPI_SMALL = 0, ///< small preset value
+ TRPPPI_MEDIUM = 1, ///< medium preset value
+ TRPPPI_LARGE = 2, ///< large preset value
+ TRPPPI_END, ///< end value
+};
+
+/**
+ * Enumeration for TraceRestrictProgramResult::flags
+ */
+enum TraceRestrictProgramResultFlags {
+ TRPRF_DENY = 1 << 0, ///< Pathfinder deny is set
+};
+DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramResultFlags)
+
+/**
+ * Execution input of a TraceRestrictProgram
+ */
+struct TraceRestrictProgramInput {
+ typedef TileIndex PreviousSignalProc(const Train *v, const void *ptr);
+
+ TileIndex tile; ///< Tile of restrict signal, for direction testing
+ Trackdir trackdir; ///< Track direction on tile of restrict signal, for direction testing
+ PreviousSignalProc *previous_signal_callback; ///< Callback to retrieve tile and direction of previous signal, may be NULL
+ const void *previous_signal_ptr; ///< Opaque pointer suitable to be passed to previous_signal_callback
+
+ TraceRestrictProgramInput(TileIndex tile_, Trackdir trackdir_, PreviousSignalProc *previous_signal_callback_, const void *previous_signal_ptr_)
+ : tile(tile_), trackdir(trackdir_), previous_signal_callback(previous_signal_callback_), previous_signal_ptr(previous_signal_ptr_) { }
+};
+
+/**
+ * Execution result of a TraceRestrictProgram
+ */
+struct TraceRestrictProgramResult {
+ uint32 penalty; ///< Total additional pathfinder penalty
+ TraceRestrictProgramResultFlags flags; ///< Flags of other actions to take
+
+ TraceRestrictProgramResult()
+ : penalty(0), flags(static_cast(0)) { }
+};
+
+/**
+ * Program type, this stores the instruction list
+ * This is refcounted, see info at top of tracerestrict.cpp
+ */
+struct TraceRestrictProgram : TraceRestrictProgramPool::PoolItem<&_tracerestrictprogram_pool> {
+ std::vector items;
+ uint32 refcount;
+
+ TraceRestrictProgram()
+ : refcount(0) { }
+
+ void Execute(const Train *v, const TraceRestrictProgramInput &input, TraceRestrictProgramResult &out) const;
+
+ /**
+ * Increment ref count, only use when creating a mapping
+ */
+ void IncrementRefCount() { refcount++; }
+
+ void DecrementRefCount();
+
+ static CommandCost Validate(const std::vector &items);
+
+ static size_t InstructionOffsetToArrayOffset(const std::vector &items, size_t offset);
+
+ static size_t ArrayOffsetToInstructionOffset(const std::vector &items, size_t offset);
+
+ /** Call InstructionOffsetToArrayOffset on current program instruction list */
+ size_t InstructionOffsetToArrayOffset(size_t offset) const
+ {
+ return TraceRestrictProgram::InstructionOffsetToArrayOffset(this->items, offset);
+ }
+
+ /** Call ArrayOffsetToInstructionOffset on current program instruction list */
+ size_t ArrayOffsetToInstructionOffset(size_t offset) const
+ {
+ return TraceRestrictProgram::ArrayOffsetToInstructionOffset(this->items, offset);
+ }
+
+ /** Get number of instructions in @p items */
+ static size_t GetInstructionCount(const std::vector &items)
+ {
+ return ArrayOffsetToInstructionOffset(items, items.size());
+ }
+
+ /** Call GetInstructionCount on current program instruction list */
+ size_t GetInstructionCount() const
+ {
+ return TraceRestrictProgram::GetInstructionCount(this->items);
+ }
+
+ /** Get an iterator to the instruction at a given @p instruction_offset in @p items */
+ static std::vector::iterator InstructionAt(std::vector &items, size_t instruction_offset)
+ {
+ return items.begin() + TraceRestrictProgram::InstructionOffsetToArrayOffset(items, instruction_offset);
+ }
+
+ /** Get a const_iterator to the instruction at a given @p instruction_offset in @p items */
+ static std::vector::const_iterator InstructionAt(const std::vector &items, size_t instruction_offset)
+ {
+ return items.begin() + TraceRestrictProgram::InstructionOffsetToArrayOffset(items, instruction_offset);
+ }
+
+ /**
+ * Call validation function on current program instruction list
+ */
+ CommandCost Validate() const { return TraceRestrictProgram::Validate(items); }
+};
+
+/** Get TraceRestrictItem type field */
+static inline TraceRestrictItemType GetTraceRestrictType(TraceRestrictItem item)
+{
+ return static_cast(GB(item, TRIFA_TYPE_OFFSET, TRIFA_TYPE_COUNT));
+}
+
+/** Get TraceRestrictItem condition flags field */
+static inline TraceRestrictCondFlags GetTraceRestrictCondFlags(TraceRestrictItem item)
+{
+ return static_cast(GB(item, TRIFA_COND_FLAGS_OFFSET, TRIFA_COND_FLAGS_COUNT));
+}
+
+/** Get TraceRestrictItem condition operator field */
+static inline TraceRestrictCondOp GetTraceRestrictCondOp(TraceRestrictItem item)
+{
+ return static_cast(GB(item, TRIFA_COND_OP_OFFSET, TRIFA_COND_OP_COUNT));
+}
+
+/** Get TraceRestrictItem auxiliary field */
+static inline uint8 GetTraceRestrictAuxField(TraceRestrictItem item)
+{
+ return GB(item, TRIFA_AUX_FIELD_OFFSET, TRIFA_AUX_FIELD_COUNT);
+}
+
+/** Get TraceRestrictItem value field */
+static inline uint16 GetTraceRestrictValue(TraceRestrictItem item)
+{
+ return static_cast(GB(item, TRIFA_VALUE_OFFSET, TRIFA_VALUE_COUNT));
+}
+
+/** Set TraceRestrictItem type field */
+static inline void SetTraceRestrictType(TraceRestrictItem &item, TraceRestrictItemType type)
+{
+ SB(item, TRIFA_TYPE_OFFSET, TRIFA_TYPE_COUNT, type);
+}
+
+/** Set TraceRestrictItem condition operator field */
+static inline void SetTraceRestrictCondOp(TraceRestrictItem &item, TraceRestrictCondOp condop)
+{
+ SB(item, TRIFA_COND_OP_OFFSET, TRIFA_COND_OP_COUNT, condop);
+}
+
+/** Set TraceRestrictItem condition flags field */
+static inline void SetTraceRestrictCondFlags(TraceRestrictItem &item, TraceRestrictCondFlags condflags)
+{
+ SB(item, TRIFA_COND_FLAGS_OFFSET, TRIFA_COND_FLAGS_COUNT, condflags);
+}
+
+/** Set TraceRestrictItem auxiliary field */
+static inline void SetTraceRestrictAuxField(TraceRestrictItem &item, uint8 data)
+{
+ SB(item, TRIFA_AUX_FIELD_OFFSET, TRIFA_AUX_FIELD_COUNT, data);
+}
+
+/** Set TraceRestrictItem value field */
+static inline void SetTraceRestrictValue(TraceRestrictItem &item, uint16 value)
+{
+ SB(item, TRIFA_VALUE_OFFSET, TRIFA_VALUE_COUNT, value);
+}
+
+/** Is TraceRestrictItemType a conditional type? */
+static inline bool IsTraceRestrictTypeConditional(TraceRestrictItemType type)
+{
+ return type >= TRIT_COND_BEGIN;
+}
+
+/** Is TraceRestrictItem type field a conditional type? */
+static inline bool IsTraceRestrictConditional(TraceRestrictItem item)
+{
+ return IsTraceRestrictTypeConditional(GetTraceRestrictType(item));
+}
+
+/** Is TraceRestrictItem a double-item type? */
+static inline bool IsTraceRestrictDoubleItem(TraceRestrictItem item)
+{
+ return GetTraceRestrictType(item) == TRIT_COND_PBS_ENTRY_SIGNAL;
+}
+
+/**
+ * Categorisation of what is allowed in the TraceRestrictItem condition op field
+ * see TraceRestrictTypePropertySet
+ */
+enum TraceRestrictConditionOpType {
+ TRCOT_NONE = 0, ///< takes no condition op
+ TRCOT_BINARY = 1, ///< takes "is" and "is not" condition ops
+ TRCOT_ALL = 2, ///< takes all condition ops (i.e. all relational ops)
+};
+
+/**
+ * Categorisation of what is in the TraceRestrictItem value field
+ * see TraceRestrictTypePropertySet
+ */
+enum TraceRestrictValueType {
+ TRVT_NONE = 0, ///< value field not used (set to 0)
+ TRVT_SPECIAL = 1, ///< special handling of value field
+ TRVT_INT = 2, ///< takes an unsigned integer value
+ TRVT_DENY = 3, ///< takes a value 0 = deny, 1 = allow (cancel previous deny)
+ TRVT_SPEED = 4, ///< takes an integer speed value
+ TRVT_ORDER = 5, ///< takes an order target ID, as per the auxiliary field as type: TraceRestrictOrderCondAuxField
+ TRVT_CARGO_ID = 6, ///< takes a CargoID
+ TRVT_DIRECTION = 7, ///< takes a TraceRestrictDirectionTypeSpecialValue
+ TRVT_TILE_INDEX = 8, ///< takes a TileIndex in the next item slot
+ TRVT_PF_PENALTY = 9, ///< takes a pathfinder penalty value or preset index, as per the auxiliary field as type: TraceRestrictPathfinderPenaltyAuxField
+};
+
+/**
+ * Describes formats of TraceRestrictItem condition op and value fields
+ */
+struct TraceRestrictTypePropertySet {
+ TraceRestrictConditionOpType cond_type;
+ TraceRestrictValueType value_type;
+};
+
+void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueType value_type);
+void SetTraceRestrictTypeAndNormalise(TraceRestrictItem &item, TraceRestrictItemType type);
+
+/**
+ * Get TraceRestrictTypePropertySet for a given instruction, only looks at value field
+ */
+static inline TraceRestrictTypePropertySet GetTraceRestrictTypeProperties(TraceRestrictItem item)
+{
+ TraceRestrictTypePropertySet out;
+
+ if (GetTraceRestrictType(item) == TRIT_NULL) {
+ out.cond_type = TRCOT_NONE;
+ out.value_type = TRVT_SPECIAL;
+ } else if (GetTraceRestrictType(item) == TRIT_COND_ENDIF ||
+ GetTraceRestrictType(item) == TRIT_COND_UNDEFINED) {
+ out.cond_type = TRCOT_NONE;
+ out.value_type = TRVT_NONE;
+ } else if (IsTraceRestrictConditional(item)) {
+ out.cond_type = TRCOT_ALL;
+
+ switch (GetTraceRestrictType(item)) {
+ case TRIT_COND_TRAIN_LENGTH:
+ out.value_type = TRVT_INT;
+ break;
+
+ case TRIT_COND_MAX_SPEED:
+ out.value_type = TRVT_SPEED;
+ break;
+
+ case TRIT_COND_CURRENT_ORDER:
+ case TRIT_COND_NEXT_ORDER:
+ case TRIT_COND_LAST_STATION:
+ out.value_type = TRVT_ORDER;
+ out.cond_type = TRCOT_BINARY;
+ break;
+
+ case TRIT_COND_CARGO:
+ out.value_type = TRVT_CARGO_ID;
+ out.cond_type = TRCOT_BINARY;
+ break;
+
+ case TRIT_COND_ENTRY_DIRECTION:
+ out.value_type = TRVT_DIRECTION;
+ out.cond_type = TRCOT_BINARY;
+ break;
+
+ case TRIT_COND_PBS_ENTRY_SIGNAL:
+ out.value_type = TRVT_TILE_INDEX;
+ out.cond_type = TRCOT_BINARY;
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+ } else {
+ out.cond_type = TRCOT_NONE;
+ if (GetTraceRestrictType(item) == TRIT_PF_PENALTY) {
+ out.value_type = TRVT_PF_PENALTY;
+ } else if (GetTraceRestrictType(item) == TRIT_PF_DENY) {
+ out.value_type = TRVT_DENY;
+ } else {
+ out.value_type = TRVT_NONE;
+ }
+ }
+
+ return out;
+}
+
+/** Get mapping ref ID from tile and track */
+static inline TraceRestrictRefId MakeTraceRestrictRefId(TileIndex t, Track track)
+{
+ return (t << 3) | track;
+}
+
+/** Get tile from mapping ref ID */
+static inline TileIndex GetTraceRestrictRefIdTileIndex(TraceRestrictRefId ref)
+{
+ return static_cast(ref >> 3);
+}
+
+/** Get track from mapping ref ID */
+static inline Track GetTraceRestrictRefIdTrack(TraceRestrictRefId ref)
+{
+ return static_cast(ref & 7);
+}
+
+void TraceRestrictCreateProgramMapping(TraceRestrictRefId ref, TraceRestrictProgram *prog);
+void TraceRestrictRemoveProgramMapping(TraceRestrictRefId ref);
+
+TraceRestrictProgram *GetTraceRestrictProgram(TraceRestrictRefId ref, bool create_new);
+
+void TraceRestrictNotifySignalRemoval(TileIndex tile, Track track);
+
+/**
+ * Gets the existing signal program for the tile identified by @p t and @p track, or NULL
+ */
+static inline const TraceRestrictProgram *GetExistingTraceRestrictProgram(TileIndex t, Track track)
+{
+ if (IsRestrictedSignal(t)) {
+ return GetTraceRestrictProgram(MakeTraceRestrictRefId(t, track), false);
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * Enumeration for command action type field, indicates what command to do
+ */
+enum TraceRestrictDoCommandType {
+ TRDCT_INSERT_ITEM, ///< insert new instruction before offset field as given value
+ TRDCT_MODIFY_ITEM, ///< modify instruction at offset field to given value
+ TRDCT_MODIFY_DUAL_ITEM, ///< modify second item of dual-part instruction at offset field to given value
+ TRDCT_REMOVE_ITEM, ///< remove instruction at offset field
+
+ TRDCT_PROG_COPY, ///< copy program operation. Do not re-order this with respect to other values
+ TRDCT_PROG_SHARE, ///< share program operation
+ TRDCT_PROG_UNSHARE, ///< unshare program (copy as a new program)
+ TRDCT_PROG_RESET, ///< reset program state of signal
+};
+
+void TraceRestrictDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, uint32 offset, uint32 value, StringID error_msg);
+
+void TraceRestrictProgMgmtWithSourceDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type,
+ TileIndex source_tile, Track source_track, StringID error_msg);
+
+/**
+ * Short-hand to call TraceRestrictProgMgmtWithSourceDoCommandP with 0 for source tile/track
+ */
+inline void TraceRestrictProgMgmtDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, StringID error_msg)
+{
+ TraceRestrictProgMgmtWithSourceDoCommandP(tile, track, type, static_cast(0), static_cast(0), error_msg);
+}
+
+CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text);
+CommandCost CmdProgramSignalTraceRestrictProgMgmt(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text);
+
+void ShowTraceRestrictProgramWindow(TileIndex tile, Track track);
+
+void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type, uint16 index);
+
+#endif /* TRACERESTRICT_H */
Index: src/tracerestrict_gui.cpp
new file mode 100644
===================================================================
--- src/tracerestrict_gui.cpp (revision 0)
+++ src/tracerestrict_gui.cpp (working copy)
@@ -0,0 +1,1760 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tracerestrict_gui.cpp GUI code for Trace Restrict
+ *
+ * This is largely based on the programmable signals patch's GUI
+ * */
+
+#include "stdafx.h"
+#include "tracerestrict.h"
+#include "command_func.h"
+#include "window_func.h"
+#include "strings_func.h"
+#include "string_func.h"
+#include "viewport_func.h"
+#include "textbuf_gui.h"
+#include "company_func.h"
+#include "tilehighlight_func.h"
+#include "widgets/dropdown_func.h"
+#include "gui.h"
+#include "gfx_func.h"
+#include "rail_map.h"
+#include "depot_map.h"
+#include "tile_cmd.h"
+#include "station_base.h"
+#include "waypoint_base.h"
+#include "depot_base.h"
+#include "error.h"
+#include "cargotype.h"
+#include "table/sprites.h"
+
+extern uint ConvertSpeedToDisplaySpeed(uint speed);
+extern uint ConvertDisplaySpeedToSpeed(uint speed);
+
+/** Widget IDs */
+enum TraceRestrictWindowWidgets {
+ TR_WIDGET_CAPTION,
+ TR_WIDGET_INSTRUCTION_LIST,
+ TR_WIDGET_SCROLLBAR,
+
+ TR_WIDGET_SEL_TOP_LEFT_2,
+ TR_WIDGET_SEL_TOP_LEFT,
+ TR_WIDGET_SEL_TOP_MIDDLE,
+ TR_WIDGET_SEL_TOP_RIGHT,
+ TR_WIDGET_SEL_SHARE,
+
+ TR_WIDGET_TYPE_COND,
+ TR_WIDGET_TYPE_NONCOND,
+ TR_WIDGET_CONDFLAGS,
+ TR_WIDGET_COMPARATOR,
+ TR_WIDGET_VALUE_INT,
+ TR_WIDGET_VALUE_DROPDOWN,
+ TR_WIDGET_VALUE_DEST,
+ TR_WIDGET_VALUE_SIGNAL,
+
+ TR_WIDGET_BLANK_L2,
+ TR_WIDGET_BLANK_L,
+ TR_WIDGET_BLANK_M,
+ TR_WIDGET_BLANK_R,
+
+ TR_WIDGET_GOTO_SIGNAL,
+ TR_WIDGET_INSERT,
+ TR_WIDGET_REMOVE,
+ TR_WIDGET_RESET,
+ TR_WIDGET_COPY,
+ TR_WIDGET_SHARE,
+ TR_WIDGET_UNSHARE,
+};
+
+/** Selection mappings for NWID_SELECTION selectors */
+enum PanelWidgets {
+ // Left 2
+ DPL2_TYPE = 0,
+ DPL2_CONDFLAGS,
+ DPL2_BLANK,
+
+ // Left
+ DPL_TYPE = 0,
+ DPL_BLANK,
+
+ // Middle
+ DPM_COMPARATOR = 0,
+ DPM_BLANK,
+
+ // Right
+ DPR_VALUE_INT = 0,
+ DPR_VALUE_DROPDOWN,
+ DPR_VALUE_DEST,
+ DPR_VALUE_SIGNAL,
+ DPR_BLANK,
+
+ // Share
+ DPS_SHARE = 0,
+ DPS_UNSHARE,
+};
+
+/**
+ * drop down list string array, and corresponding integer values
+ *
+ * value_array *must* be at least as long as string_array,
+ * where the length of string_array is defined as the offset
+ * of the first INVALID_STRING_ID
+ */
+struct TraceRestrictDropDownListSet {
+ const StringID *string_array;
+ const uint *value_array;
+};
+
+static const StringID _program_insert_str[] = {
+ STR_TRACE_RESTRICT_CONDITIONAL_IF,
+ STR_TRACE_RESTRICT_CONDITIONAL_ELIF,
+ STR_TRACE_RESTRICT_CONDITIONAL_ELSE,
+ STR_TRACE_RESTRICT_PF_DENY,
+ STR_TRACE_RESTRICT_PF_PENALTY,
+ INVALID_STRING_ID
+};
+static const uint _program_insert_else_flag = 0x100; ///< flag to indicate that TRCF_ELSE should be set
+static const uint32 _program_insert_else_hide_mask = 4; ///< disable bitmask for else
+static const uint32 _program_insert_else_if_hide_mask = 2; ///< disable bitmask for elif
+static const uint _program_insert_val[] = {
+ TRIT_COND_UNDEFINED, // if block
+ TRIT_COND_UNDEFINED | _program_insert_else_flag, // elif block
+ TRIT_COND_ENDIF | _program_insert_else_flag, // else block
+ TRIT_PF_DENY, // deny
+ TRIT_PF_PENALTY, // penalty
+};
+
+/** insert drop down list strings and values */
+static const TraceRestrictDropDownListSet _program_insert = {
+ _program_insert_str, _program_insert_val,
+};
+
+static const StringID _deny_value_str[] = {
+ STR_TRACE_RESTRICT_PF_DENY,
+ STR_TRACE_RESTRICT_PF_ALLOW,
+ INVALID_STRING_ID
+};
+static const uint _deny_value_val[] = {
+ 0,
+ 1,
+};
+
+/** value drop down list for deny types strings and values */
+static const TraceRestrictDropDownListSet _deny_value = {
+ _deny_value_str, _deny_value_val,
+};
+
+static const StringID _direction_value_str[] = {
+ STR_TRACE_RESTRICT_DIRECTION_FRONT,
+ STR_TRACE_RESTRICT_DIRECTION_BACK,
+ STR_TRACE_RESTRICT_DIRECTION_NE,
+ STR_TRACE_RESTRICT_DIRECTION_SE,
+ STR_TRACE_RESTRICT_DIRECTION_SW,
+ STR_TRACE_RESTRICT_DIRECTION_NW,
+ INVALID_STRING_ID
+};
+static const uint _direction_value_val[] = {
+ TRDTSV_FRONT,
+ TRDTSV_BACK,
+ TRNTSV_NE,
+ TRNTSV_SE,
+ TRNTSV_SW,
+ TRNTSV_NW,
+};
+
+/** value drop down list for direction type strings and values */
+static const TraceRestrictDropDownListSet _direction_value = {
+ _direction_value_str, _direction_value_val,
+};
+
+/**
+ * Get index of @p value in @p list_set
+ * if @p value is not present, assert if @p missing_ok is false, otherwise return -1
+ */
+static int GetDropDownListIndexByValue(const TraceRestrictDropDownListSet *list_set, uint value, bool missing_ok)
+{
+ const StringID *string_array = list_set->string_array;
+ const uint *value_array = list_set->value_array;
+
+ for (; *string_array != INVALID_STRING_ID; string_array++, value_array++) {
+ if (*value_array == value) {
+ return value_array - list_set->value_array;
+ }
+ }
+ assert(missing_ok == true);
+ return -1;
+}
+
+/**
+ * Get StringID correspoding to @p value, in @list_set
+ * @p value must be present
+ */
+static StringID GetDropDownStringByValue(const TraceRestrictDropDownListSet *list_set, uint value)
+{
+ return list_set->string_array[GetDropDownListIndexByValue(list_set, value, false)];
+}
+
+/**
+ * Return the appropriate type dropdown TraceRestrictDropDownListSet for the given item type @p type
+ */
+static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictItemType type)
+{
+ static const StringID str_action[] = {
+ STR_TRACE_RESTRICT_PF_DENY,
+ STR_TRACE_RESTRICT_PF_PENALTY,
+ INVALID_STRING_ID,
+ };
+ static const uint val_action[] = {
+ TRIT_PF_DENY,
+ TRIT_PF_PENALTY,
+ };
+ static const TraceRestrictDropDownListSet set_action = {
+ str_action, val_action,
+ };
+
+ static const StringID str_cond[] = {
+ STR_TRACE_RESTRICT_VARIABLE_TRAIN_LENGTH,
+ STR_TRACE_RESTRICT_VARIABLE_MAX_SPEED,
+ STR_TRACE_RESTRICT_VARIABLE_CURRENT_ORDER,
+ STR_TRACE_RESTRICT_VARIABLE_NEXT_ORDER,
+ STR_TRACE_RESTRICT_VARIABLE_LAST_VISITED_STATION,
+ STR_TRACE_RESTRICT_VARIABLE_CARGO,
+ STR_TRACE_RESTRICT_VARIABLE_ENTRY_DIRECTION,
+ STR_TRACE_RESTRICT_VARIABLE_PBS_ENTRY_SIGNAL,
+ STR_TRACE_RESTRICT_VARIABLE_UNDEFINED,
+ INVALID_STRING_ID,
+ };
+ static const uint val_cond[] = {
+ TRIT_COND_TRAIN_LENGTH,
+ TRIT_COND_MAX_SPEED,
+ TRIT_COND_CURRENT_ORDER,
+ TRIT_COND_NEXT_ORDER,
+ TRIT_COND_LAST_STATION,
+ TRIT_COND_CARGO,
+ TRIT_COND_ENTRY_DIRECTION,
+ TRIT_COND_PBS_ENTRY_SIGNAL,
+ TRIT_COND_UNDEFINED,
+ };
+ static const TraceRestrictDropDownListSet set_cond = {
+ str_cond, val_cond,
+ };
+
+ return IsTraceRestrictTypeConditional(type) ? &set_cond : &set_action;
+}
+
+/**
+ * Get a TraceRestrictDropDownListSet of the sorted cargo list
+ */
+static const TraceRestrictDropDownListSet *GetSortedCargoTypeDropDownListSet()
+{
+ static StringID cargo_list_str[NUM_CARGO + 1];
+ static uint cargo_list_id[NUM_CARGO];
+ static const TraceRestrictDropDownListSet cargo_list = {
+ cargo_list_str, cargo_list_id,
+ };
+
+ for (size_t i = 0; i < _sorted_standard_cargo_specs_size; ++i) {
+ const CargoSpec *cs = _sorted_cargo_specs[i];
+ cargo_list_str[i] = cs->name;
+ cargo_list_id[i] = cs->Index();
+ }
+ cargo_list_str[_sorted_standard_cargo_specs_size] = INVALID_STRING_ID;
+
+ return &cargo_list;
+}
+
+static const StringID _cargo_cond_ops_str[] = {
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_EQUALS,
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_NOT_EQUALS,
+ INVALID_STRING_ID,
+};
+static const uint _cargo_cond_ops_val[] = {
+ TRCO_IS,
+ TRCO_ISNOT,
+};
+/** cargo conditional operators dropdown list set */
+static const TraceRestrictDropDownListSet _cargo_cond_ops = {
+ _cargo_cond_ops_str, _cargo_cond_ops_val,
+};
+
+/**
+ * Get the StringID for a given CargoID @p cargo, or STR_NEWGRF_INVALID_CARGO
+ */
+static StringID GetCargoStringByID(CargoID cargo)
+{
+ const CargoSpec *cs = CargoSpec::Get(cargo);
+ return cs->IsValid() ? cs->name : STR_NEWGRF_INVALID_CARGO;
+}
+
+/**
+ * Get the StringID for a given item type @p type
+ */
+static StringID GetTypeString(TraceRestrictItemType type)
+{
+ return GetDropDownStringByValue(GetTypeDropDownListSet(type), type);
+}
+
+/**
+ * Get the conditional operator field drop down list set for a given type property set @p properties
+ */
+static const TraceRestrictDropDownListSet *GetCondOpDropDownListSet(TraceRestrictTypePropertySet properties)
+{
+ static const StringID str_long[] = {
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_EQUALS,
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_NOT_EQUALS,
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_THAN,
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_EQUALS,
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_THAN,
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_EQUALS,
+ INVALID_STRING_ID,
+ };
+ static const uint val_long[] = {
+ TRCO_IS,
+ TRCO_ISNOT,
+ TRCO_LT,
+ TRCO_LTE,
+ TRCO_GT,
+ TRCO_GTE,
+ };
+ static const TraceRestrictDropDownListSet set_long = {
+ str_long, val_long,
+ };
+
+ static const StringID str_short[] = {
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_EQUALS,
+ STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_NOT_EQUALS,
+ INVALID_STRING_ID,
+ };
+ static const uint val_short[] = {
+ TRCO_IS,
+ TRCO_ISNOT,
+ };
+ static const TraceRestrictDropDownListSet set_short = {
+ str_short, val_short,
+ };
+
+ if (properties.value_type == TRVT_CARGO_ID) return &_cargo_cond_ops;
+
+ switch (properties.cond_type) {
+ case TRCOT_NONE:
+ return NULL;
+
+ case TRCOT_BINARY:
+ return &set_short;
+
+ case TRCOT_ALL:
+ return &set_long;
+ }
+ NOT_REACHED();
+ return NULL;
+}
+
+/**
+ * Return true if item type field @p type is an integer value type
+ */
+static bool IsIntegerValueType(TraceRestrictValueType type)
+{
+ switch (type) {
+ case TRVT_INT:
+ case TRVT_SPEED:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/**
+ * Convert integer values or custom penalty values between internal units and display units
+ */
+static uint ConvertIntegerValue(TraceRestrictValueType type, uint in, bool to_display)
+{
+ switch (type) {
+ case TRVT_INT:
+ return in;
+
+ case TRVT_SPEED:
+ return to_display
+ ? ConvertSpeedToDisplaySpeed(in) * 10 / 16
+ : ConvertDisplaySpeedToSpeed(in) * 16 / 10;
+
+ case TRVT_PF_PENALTY:
+ return in;
+
+ default:
+ NOT_REACHED();
+ return 0;
+ }
+}
+
+/** String values for TraceRestrictCondFlags, value gives offset into array */
+static const StringID _program_cond_type[] = {
+ STR_TRACE_RESTRICT_CONDITIONAL_IF, // TRCF_DEFAULT
+ STR_TRACE_RESTRICT_CONDITIONAL_ELIF, // TRCF_ELSE
+ STR_TRACE_RESTRICT_CONDITIONAL_ORIF, // TRCF_OR
+};
+
+/** condition flags field drop down value types */
+enum CondFlagsDropDownType {
+ CFDDT_ELSE = 0, ///< This is an else block
+ CFDDT_ELIF = TRCF_ELSE, ///< This is an else-if block
+ CFDDT_ORIF = TRCF_OR, ///< This is an or-if block
+};
+
+static const uint32 _condflags_dropdown_else_hide_mask = 1; ///< disable bitmask for CFDDT_ELSE
+static const uint32 _condflags_dropdown_else_if_hide_mask = 6; ///< disable bitmask for CFDDT_ELIF and CFDDT_ORIF
+
+static const StringID _condflags_dropdown_str[] = {
+ STR_TRACE_RESTRICT_CONDITIONAL_ELSE,
+ STR_TRACE_RESTRICT_CONDITIONAL_ELIF,
+ STR_TRACE_RESTRICT_CONDITIONAL_ORIF,
+ INVALID_STRING_ID,
+};
+static const uint _condflags_dropdown_val[] = {
+ CFDDT_ELSE,
+ CFDDT_ELIF,
+ CFDDT_ORIF,
+};
+/** condition flags dropdown list set */
+static const TraceRestrictDropDownListSet _condflags_dropdown = {
+ _condflags_dropdown_str, _condflags_dropdown_val,
+};
+
+static const StringID _pf_penalty_dropdown_str[] = {
+ STR_TRACE_RESTRICT_PF_VALUE_SMALL,
+ STR_TRACE_RESTRICT_PF_VALUE_MEDIUM,
+ STR_TRACE_RESTRICT_PF_VALUE_LARGE,
+ STR_TRACE_RESTRICT_PF_VALUE_CUSTOM,
+ INVALID_STRING_ID,
+};
+static const uint _pf_penalty_dropdown_val[] = {
+ TRPPPI_SMALL,
+ TRPPPI_MEDIUM,
+ TRPPPI_LARGE,
+ TRPPPI_END, // this is a placeholder for "custom"
+};
+/** Pathfinder penalty dropdown set */
+static const TraceRestrictDropDownListSet _pf_penalty_dropdown = {
+ _pf_penalty_dropdown_str, _pf_penalty_dropdown_val,
+};
+
+static uint GetPathfinderPenaltyDropdownIndex(TraceRestrictItem item)
+{
+ switch (static_cast(GetTraceRestrictAuxField(item))) {
+ case TRPPAF_VALUE:
+ return TRPPPI_END;
+
+ case TRPPAF_PRESET: {
+ uint16 index = GetTraceRestrictValue(item);
+ assert(index < TRPPPI_END);
+ return index;
+ }
+
+ default:
+ NOT_REACHED();
+ }
+}
+
+/** Common function for drawing an ordinary conditional instruction */
+static void DrawInstructionStringConditionalCommon(TraceRestrictItem item, const TraceRestrictTypePropertySet &properties)
+{
+ assert(GetTraceRestrictCondFlags(item) <= TRCF_OR);
+ SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]);
+ SetDParam(1, GetTypeString(GetTraceRestrictType(item)));
+ SetDParam(2, GetDropDownStringByValue(GetCondOpDropDownListSet(properties), GetTraceRestrictCondOp(item)));
+}
+
+/** Common function for drawing an integer conditional instruction */
+static void DrawInstructionStringConditionalIntegerCommon(TraceRestrictItem item, const TraceRestrictTypePropertySet &properties)
+{
+ DrawInstructionStringConditionalCommon(item, properties);
+ SetDParam(3, GetTraceRestrictValue(item));
+}
+
+/** Common function for drawing an integer conditional instruction with an invalid value */
+static void DrawInstructionStringConditionalInvalidValue(TraceRestrictItem item, const TraceRestrictTypePropertySet &properties, StringID &instruction_string, bool selected)
+{
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_UNDEFINED;
+ DrawInstructionStringConditionalCommon(item, properties);
+ SetDParam(3, selected ? STR_TRACE_RESTRICT_WHITE : STR_EMPTY);
+}
+
+/**
+ * Draws an instruction in the programming GUI
+ * @param prog The program (may be NULL)
+ * @param item The instruction to draw
+ * @param index The instruction index
+ * @param y Y position for drawing
+ * @param selected True, if the order is selected
+ * @param indent How many levels the instruction is indented
+ * @param left Left border for text drawing
+ * @param right Right border for text drawing
+ */
+static void DrawInstructionString(const TraceRestrictProgram *prog, TraceRestrictItem item, int index, int y, bool selected, int indent, int left, int right)
+{
+ StringID instruction_string = INVALID_STRING_ID;
+
+ TraceRestrictTypePropertySet properties = GetTraceRestrictTypeProperties(item);
+
+ if (IsTraceRestrictConditional(item)) {
+ if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) {
+ if (GetTraceRestrictCondFlags(item) & TRCF_ELSE) {
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ELSE;
+ } else {
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ENDIF;
+ }
+ } else if (GetTraceRestrictType(item) == TRIT_COND_UNDEFINED) {
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_UNDEFINED;
+ SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]);
+ SetDParam(1, selected ? STR_TRACE_RESTRICT_WHITE : STR_EMPTY);
+ } else {
+ switch (properties.value_type) {
+ case TRVT_INT:
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_INTEGER;
+ DrawInstructionStringConditionalIntegerCommon(item, properties);
+ break;
+
+ case TRVT_SPEED:
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_SPEED;
+ DrawInstructionStringConditionalIntegerCommon(item, properties);
+ break;
+
+ case TRVT_ORDER: {
+ switch (static_cast(GetTraceRestrictAuxField(item))) {
+ case TROCAF_STATION:
+ if (GetTraceRestrictValue(item) != INVALID_STATION) {
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ORDER_STATION;
+ DrawInstructionStringConditionalIntegerCommon(item, properties);
+ } else {
+ // this is an invalid station, use a seperate string
+ DrawInstructionStringConditionalInvalidValue(item, properties, instruction_string, selected);
+ }
+ break;
+
+ case TROCAF_WAYPOINT:
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ORDER_WAYPOINT;
+ DrawInstructionStringConditionalIntegerCommon(item, properties);
+ break;
+
+ case TROCAF_DEPOT:
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ORDER_DEPOT;
+ DrawInstructionStringConditionalCommon(item, properties);
+ SetDParam(3, VEH_TRAIN);
+ SetDParam(4, GetTraceRestrictValue(item));
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+ break;
+ }
+
+ case TRVT_CARGO_ID:
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_CARGO;
+ assert(GetTraceRestrictCondFlags(item) <= TRCF_OR);
+ SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]);
+ SetDParam(1, GetDropDownStringByValue(&_cargo_cond_ops, GetTraceRestrictCondOp(item)));
+ SetDParam(2, GetCargoStringByID(GetTraceRestrictValue(item)));
+ break;
+
+ case TRVT_DIRECTION:
+ if (GetTraceRestrictValue(item) >= TRDTSV_FRONT) {
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_SIGNAL_FACE;
+ } else {
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_DIRECTION;
+ }
+ SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]);
+ SetDParam(1, GetDropDownStringByValue(GetCondOpDropDownListSet(properties), GetTraceRestrictCondOp(item)));
+ SetDParam(2, GetDropDownStringByValue(&_direction_value, GetTraceRestrictValue(item)));
+ break;
+
+ case TRVT_TILE_INDEX: {
+ assert(prog != NULL);
+ assert(GetTraceRestrictType(item) == TRIT_COND_PBS_ENTRY_SIGNAL);
+ TileIndex tile = *(TraceRestrictProgram::InstructionAt(prog->items, index - 1) + 1);
+ if (tile == INVALID_TILE) {
+ DrawInstructionStringConditionalInvalidValue(item, properties, instruction_string, selected);
+ } else {
+ instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_TILE_INDEX;
+ SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]);
+ SetDParam(1, STR_TRACE_RESTRICT_VARIABLE_PBS_ENTRY_SIGNAL_LONG);
+ SetDParam(2, GetDropDownStringByValue(GetCondOpDropDownListSet(properties), GetTraceRestrictCondOp(item)));
+ SetDParam(3, TileX(tile));
+ SetDParam(4, TileY(tile));
+ }
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+ }
+ } else {
+ switch (GetTraceRestrictType(item)) {
+ case TRIT_NULL:
+ switch (GetTraceRestrictValue(item)) {
+ case TRNTSV_START:
+ instruction_string = STR_TRACE_RESTRICT_START;
+ break;
+
+ case TRNTSV_END:
+ instruction_string = STR_TRACE_RESTRICT_END;
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+ break;
+
+ case TRIT_PF_DENY:
+ instruction_string = GetTraceRestrictValue(item) ? STR_TRACE_RESTRICT_PF_ALLOW_LONG : STR_TRACE_RESTRICT_PF_DENY;
+ break;
+
+ case TRIT_PF_PENALTY:
+ switch (static_cast(GetTraceRestrictAuxField(item))) {
+ case TRPPAF_VALUE:
+ instruction_string = STR_TRACE_RESTRICT_PF_PENALTY_ITEM;
+ SetDParam(0, GetTraceRestrictValue(item));
+ break;
+
+ case TRPPAF_PRESET: {
+ instruction_string = STR_TRACE_RESTRICT_PF_PENALTY_ITEM_PRESET;
+ uint16 index = GetTraceRestrictValue(item);
+ assert(index < TRPPPI_END);
+ SetDParam(0, _pf_penalty_dropdown_str[index]);
+ break;
+ }
+
+ default:
+ NOT_REACHED();
+ }
+ break;
+
+ default:
+ NOT_REACHED();
+ break;
+ }
+ }
+
+ DrawString(left + indent * 16, right, y, instruction_string, selected ? TC_WHITE : TC_BLACK);
+}
+
+/** Main GUI window class */
+class TraceRestrictWindow: public Window {
+ TileIndex tile; ///< tile this window is for
+ Track track; ///< track this window is for
+ int selected_instruction; ///< selected instruction index, this is offset by one due to the display of the "start" item
+ Scrollbar *vscroll; ///< scrollbar widget
+ std::map drop_down_list_mapping; ///< mapping of widget IDs to drop down list sets
+ TraceRestrictItem expecting_inserted_item; ///< set to instruction when performing an instruction insertion, used to handle selection update on insertion
+ int current_placement_widget; ///< which widget has a SetObjectToPlaceWnd, if any
+
+public:
+ TraceRestrictWindow(WindowDesc *desc, TileIndex tile, Track track)
+ : Window(desc)
+ {
+ this->tile = tile;
+ this->track = track;
+ this->selected_instruction = -1;
+ this->expecting_inserted_item = static_cast(0);
+ this->current_placement_widget = -1;
+
+ this->CreateNestedTree();
+ this->vscroll = this->GetScrollbar(TR_WIDGET_SCROLLBAR);
+ this->FinishInitNested(MakeTraceRestrictRefId(tile, track));
+
+ this->ReloadProgramme();
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count) OVERRIDE
+ {
+ switch (widget) {
+ case TR_WIDGET_INSTRUCTION_LIST: {
+ int sel = this->GetItemIndexFromPt(pt.y);
+
+ if (_ctrl_pressed) {
+ // scroll to target (for stations, waypoints, depots)
+
+ if (sel == -1) return;
+
+ TraceRestrictItem item = this->GetItem(this->GetProgram(), sel);
+ if (GetTraceRestrictTypeProperties(item).value_type == TRVT_ORDER) {
+ switch (static_cast(GetTraceRestrictAuxField(item))) {
+ case TROCAF_STATION:
+ case TROCAF_WAYPOINT: {
+ BaseStation *st = BaseStation::GetIfValid(GetTraceRestrictValue(item));
+ if (st) {
+ ScrollMainWindowToTile(st->xy);
+ }
+ break;
+ }
+
+ case TROCAF_DEPOT: {
+ Depot *depot = Depot::GetIfValid(GetTraceRestrictValue(item));
+ if (depot) {
+ ScrollMainWindowToTile(depot->xy);
+ }
+ break;
+ }
+ }
+ }
+ return;
+ }
+
+ this->DeleteChildWindows();
+ HideDropDownMenu(this);
+
+ if (sel == -1 || this->GetOwner() != _local_company) {
+ // Deselect
+ this->selected_instruction = -1;
+ } else {
+ this->selected_instruction = sel;
+ }
+
+ this->expecting_inserted_item = static_cast(0);
+
+ this->UpdateButtonState();
+ break;
+ }
+
+ case TR_WIDGET_INSERT: {
+ if (this->GetOwner() != _local_company || this->selected_instruction < 1) {
+ return;
+ }
+
+ uint32 disabled = 0;
+ TraceRestrictItem item = this->GetSelected();
+ if (GetTraceRestrictType(item) == TRIT_COND_ENDIF ||
+ (IsTraceRestrictConditional(item) && GetTraceRestrictCondFlags(item) != 0)) {
+ // this is either: an else/or if, an else, or an end if
+ // try to include else if, else in insertion list
+ if (!ElseInsertionDryRun(false)) disabled |= _program_insert_else_hide_mask;
+ if (!ElseIfInsertionDryRun(false)) disabled |= _program_insert_else_if_hide_mask;
+ } else {
+ // can't insert else/end if here
+ disabled |= _program_insert_else_hide_mask | _program_insert_else_if_hide_mask;
+ }
+
+ this->ShowDropDownListWithValue(&_program_insert, 0, true, TR_WIDGET_INSERT, disabled, 0, 0);
+ break;
+ }
+
+ case TR_WIDGET_REMOVE: {
+ TraceRestrictItem item = this->GetSelected();
+ if (this->GetOwner() != _local_company || item == 0) {
+ return;
+ }
+
+ TraceRestrictDoCommandP(tile, track, TRDCT_REMOVE_ITEM, this->selected_instruction - 1, 0, STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ITEM);
+ break;
+ }
+
+ case TR_WIDGET_CONDFLAGS: {
+ TraceRestrictItem item = this->GetSelected();
+ if (this->GetOwner() != _local_company || item == 0) {
+ return;
+ }
+
+ CondFlagsDropDownType type;
+ if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) {
+ if (GetTraceRestrictCondFlags(item) == 0) return; // end if
+ type = CFDDT_ELSE;
+ } else if (IsTraceRestrictConditional(item) && GetTraceRestrictCondFlags(item) != 0) {
+ type = static_cast(GetTraceRestrictCondFlags(item));
+ } else {
+ return;
+ }
+
+ uint32 disabled = 0;
+ if (!ElseInsertionDryRun(true)) disabled |= _condflags_dropdown_else_hide_mask;
+ if (!ElseIfInsertionDryRun(true)) disabled |= _condflags_dropdown_else_if_hide_mask;
+
+ this->ShowDropDownListWithValue(&_condflags_dropdown, type, false, TR_WIDGET_CONDFLAGS, disabled, 0, 0);
+ break;
+ }
+
+ case TR_WIDGET_TYPE_COND:
+ case TR_WIDGET_TYPE_NONCOND: {
+ TraceRestrictItem item = this->GetSelected();
+ TraceRestrictItemType type = GetTraceRestrictType(item);
+
+ if (type != TRIT_NULL) {
+ this->ShowDropDownListWithValue(GetTypeDropDownListSet(type), type, false, widget, 0, 0, 0);
+ }
+ break;
+ }
+
+ case TR_WIDGET_COMPARATOR: {
+ TraceRestrictItem item = this->GetSelected();
+ const TraceRestrictDropDownListSet *list_set = GetCondOpDropDownListSet(GetTraceRestrictTypeProperties(item));
+ if (list_set) {
+ this->ShowDropDownListWithValue(list_set, GetTraceRestrictCondOp(item), false, TR_WIDGET_COMPARATOR, 0, 0, 0);
+ }
+ break;
+ }
+
+ case TR_WIDGET_VALUE_INT: {
+ TraceRestrictItem item = this->GetSelected();
+ TraceRestrictValueType type = GetTraceRestrictTypeProperties(item).value_type;
+ if (IsIntegerValueType(type)) {
+ SetDParam(0, ConvertIntegerValue(type, GetTraceRestrictValue(item), true));
+ ShowQueryString(STR_JUST_INT, STR_TRACE_RESTRICT_VALUE_CAPTION, 10, this, CS_NUMERAL, QSF_NONE);
+ }
+ break;
+ }
+
+ case TR_WIDGET_VALUE_DROPDOWN: {
+ TraceRestrictItem item = this->GetSelected();
+ switch (GetTraceRestrictTypeProperties(item).value_type) {
+ case TRVT_DENY:
+ this->ShowDropDownListWithValue(&_deny_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0);
+ break;
+
+ case TRVT_CARGO_ID:
+ this->ShowDropDownListWithValue(GetSortedCargoTypeDropDownListSet(), GetTraceRestrictValue(item), true, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); // current cargo is permitted to not be in list
+ break;
+
+ case TRVT_DIRECTION:
+ this->ShowDropDownListWithValue(&_direction_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0);
+ break;
+
+ case TRVT_PF_PENALTY:
+ this->ShowDropDownListWithValue(&_pf_penalty_dropdown, GetPathfinderPenaltyDropdownIndex(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ case TR_WIDGET_VALUE_DEST: {
+ SetObjectToPlaceAction(widget, ANIMCURSOR_PICKSTATION);
+ break;
+ }
+
+ case TR_WIDGET_VALUE_SIGNAL: {
+ SetObjectToPlaceAction(widget, ANIMCURSOR_BUILDSIGNALS);
+ break;
+ }
+
+ case TR_WIDGET_GOTO_SIGNAL:
+ ScrollMainWindowToTile(this->tile);
+ break;
+
+ case TR_WIDGET_RESET: {
+ TraceRestrictProgMgmtDoCommandP(tile, track, TRDCT_PROG_RESET, STR_TRACE_RESTRICT_ERROR_CAN_T_RESET_SIGNAL);
+ break;
+ }
+
+ case TR_WIDGET_COPY:
+ case TR_WIDGET_SHARE:
+ SetObjectToPlaceAction(widget, ANIMCURSOR_BUILDSIGNALS);
+ break;
+
+ case TR_WIDGET_UNSHARE: {
+ TraceRestrictProgMgmtDoCommandP(tile, track, TRDCT_PROG_UNSHARE, STR_TRACE_RESTRICT_ERROR_CAN_T_UNSHARE_PROGRAM);
+ break;
+ }
+ }
+ }
+
+ virtual void OnQueryTextFinished(char *str) OVERRIDE
+ {
+ if (StrEmpty(str)) {
+ return;
+ }
+
+ TraceRestrictItem item = GetSelected();
+ TraceRestrictValueType type = GetTraceRestrictTypeProperties(item).value_type;
+ if (!IsIntegerValueType(type) && type != TRVT_PF_PENALTY) {
+ return;
+ }
+
+ uint value = ConvertIntegerValue(type, atoi(str), false);
+ if (value >= (1 << TRIFA_VALUE_COUNT)) {
+ SetDParam(0, ConvertIntegerValue(type, (1 << TRIFA_VALUE_COUNT) - 1, true));
+ ShowErrorMessage(STR_TRACE_RESTRICT_ERROR_VALUE_TOO_LARGE, STR_EMPTY, WL_INFO);
+ return;
+ }
+
+ if (type == TRVT_PF_PENALTY) {
+ SetTraceRestrictAuxField(item, TRPPAF_VALUE);
+ }
+
+ SetTraceRestrictValue(item, value);
+ TraceRestrictDoCommandP(tile, track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM);
+ }
+
+ virtual void OnDropdownSelect(int widget, int index) OVERRIDE
+ {
+ TraceRestrictItem item = GetSelected();
+ if (item == 0 || index < 0 || this->selected_instruction < 1) {
+ return;
+ }
+
+ const TraceRestrictDropDownListSet *list_set = this->drop_down_list_mapping[widget];
+ if (!list_set) {
+ return;
+ }
+
+ uint value = list_set->value_array[index];
+
+ switch (widget) {
+ case TR_WIDGET_INSERT: {
+ TraceRestrictItem insert_item = 0;
+
+ bool have_else = false;
+ if (value & _program_insert_else_flag) {
+ value &= ~_program_insert_else_flag;
+ have_else = true;
+ }
+ SetTraceRestrictTypeAndNormalise(insert_item, static_cast(value));
+ if (have_else) SetTraceRestrictCondFlags(insert_item, TRCF_ELSE); // this needs to happen after calling SetTraceRestrictTypeAndNormalise
+
+ this->expecting_inserted_item = insert_item;
+ TraceRestrictDoCommandP(this->tile, this->track, TRDCT_INSERT_ITEM, this->selected_instruction - 1, insert_item, STR_TRACE_RESTRICT_ERROR_CAN_T_INSERT_ITEM);
+ break;
+ }
+
+ case TR_WIDGET_CONDFLAGS: {
+ CondFlagsDropDownType cond_type = static_cast(value);
+ if (cond_type == CFDDT_ELSE) {
+ SetTraceRestrictTypeAndNormalise(item, TRIT_COND_ENDIF);
+ SetTraceRestrictCondFlags(item, TRCF_ELSE);
+ } else {
+ if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) {
+ // item is currently an else, convert to else/or if
+ SetTraceRestrictTypeAndNormalise(item, TRIT_COND_UNDEFINED);
+ }
+
+ SetTraceRestrictCondFlags(item, static_cast(cond_type));
+ }
+
+ TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM);
+ break;
+ }
+
+ case TR_WIDGET_TYPE_COND:
+ case TR_WIDGET_TYPE_NONCOND: {
+ SetTraceRestrictTypeAndNormalise(item, static_cast