diff --git a/bin/data/progsignals.grf b/bin/data/progsignals.grf
new file mode 100644
index 0000000000..facb08c0a0
Binary files /dev/null and b/bin/data/progsignals.grf differ
diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj
index 8d1c9058fe..c958101718 100644
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -366,6 +366,8 @@
+
+
@@ -582,6 +584,7 @@
+
@@ -869,6 +872,7 @@
+
diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters
index 37c6b1559c..d83ca2a376 100644
--- a/projects/openttd_vs100.vcxproj.filters
+++ b/projects/openttd_vs100.vcxproj.filters
@@ -327,6 +327,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -975,6 +981,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -1836,6 +1845,9 @@
Save/Load handlers
+
+ Save/Load handlers
+
Save/Load handlers
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index 5c874a0801..a84a122b32 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -734,6 +734,14 @@
RelativePath=".\..\src\signal.cpp"
>
+
+
+
+
@@ -1602,6 +1610,10 @@
RelativePath=".\..\src\signal_type.h"
>
+
+
@@ -2770,6 +2782,10 @@
RelativePath=".\..\src\saveload\waypoint_sl.cpp"
>
+
+
diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj
index 7554ff3a58..afcdf8db8f 100644
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -731,6 +731,14 @@
RelativePath=".\..\src\signal.cpp"
>
+
+
+
+
@@ -1599,6 +1607,10 @@
RelativePath=".\..\src\signal_type.h"
>
+
+
@@ -2767,6 +2779,10 @@
RelativePath=".\..\src\saveload\waypoint_sl.cpp"
>
+
+
diff --git a/source.list b/source.list
index 8386d82d1e..72f157abf6 100644
--- a/source.list
+++ b/source.list
@@ -76,6 +76,8 @@ screenshot.cpp
#end
settings.cpp
signal.cpp
+programmable_signals.cpp
+programmable_signals_gui.cpp
signs.cpp
sound.cpp
sprite.cpp
@@ -321,6 +323,7 @@ settings_type.h
ship.h
signal_func.h
signal_type.h
+programmable_signals.h
signs_base.h
signs_func.h
signs_type.h
@@ -633,6 +636,7 @@ saveload/subsidy_sl.cpp
saveload/town_sl.cpp
saveload/vehicle_sl.cpp
saveload/waypoint_sl.cpp
+saveload/signal_sl.cpp
saveload/extended_ver_sl.h
saveload/extended_ver_sl.cpp
diff --git a/src/command.cpp b/src/command.cpp
index 96d88f3854..89ebea499e 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -200,6 +200,10 @@ CommandProc CmdOpenCloseAirport;
CommandProc CmdProgramSignalTraceRestrict;
+CommandProc CmdInsertSignalInstruction;
+CommandProc CmdModifySignalInstruction;
+CommandProc CmdRemoveSignalInstruction;
+
#define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type}
/**
@@ -358,6 +362,10 @@ static const Command _command_proc_table[] = {
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
};
/*!
diff --git a/src/command_type.h b/src/command_type.h
index 1f99797ada..ac639d127e 100644
--- a/src/command_type.h
+++ b/src/command_type.h
@@ -331,6 +331,10 @@ enum Commands {
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_END, ///< Must ALWAYS be on the end of this list!! (period)
};
diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp
index 10bc0afa17..0c325e856d 100644
--- a/src/gfxinit.cpp
+++ b/src/gfxinit.cpp
@@ -170,6 +170,9 @@ static void LoadSpriteTables()
_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
diff --git a/src/lang/english.txt b/src/lang/english.txt
index e077670977..005818319c 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1292,6 +1292,8 @@ STR_CONFIG_SETTING_LANDSCAPE :Landscape: {STR
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}
@@ -2357,12 +2359,14 @@ STR_BUILD_SIGNAL_SEMAPHORE_NORM_TOOLTIP :{BLACK}Block Si
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
@@ -2457,6 +2461,70 @@ STR_TRACE_RESTRICT_ERROR_CAN_T_COPY_PROGRAM :{WHITE}Can't co
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
@@ -2693,23 +2761,30 @@ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_SIGNALS :{STRING} track
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)
@@ -4389,6 +4464,8 @@ STR_ERROR_THERE_ARE_NO_SIGNALS :{WHITE}... ther
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
@@ -5042,6 +5119,8 @@ STR_TINY_BLACK_HEIGHT :{TINY_FONT}{BLA
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
diff --git a/src/openttd.cpp b/src/openttd.cpp
index 1e4450da9e..8b45c9e99b 100644
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -63,6 +63,7 @@
#include "subsidy_func.h"
#include "gfx_layout.h"
#include "viewport_sprite_sorter.h"
+#include "programmable_signals.h"
#include "linkgraph/linkgraphschedule.h"
#include "tracerestrict.h"
@@ -306,6 +307,9 @@ static void ShutdownGame()
ClearTraceRestrictMapping();
PoolBase::Clean(PT_ALL);
+ FreeSignalPrograms();
+ FreeSignalDependencies();
+
/* No NewGRFs were loaded when it was still bootstrapping. */
if (_game_mode != GM_BOOTSTRAP) ResetNewGRFData();
diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp
index 5a4b6531c3..1215a9c6d2 100644
--- a/src/pathfinder/yapf/yapf_costrail.hpp
+++ b/src/pathfinder/yapf/yapf_costrail.hpp
@@ -255,6 +255,7 @@ public:
/* 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:
diff --git a/src/programmable_signals.cpp b/src/programmable_signals.cpp
new file mode 100644
index 0000000000..c7d9e5d87c
--- /dev/null
+++ b/src/programmable_signals.cpp
@@ -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