Port of the programmable signals patch to recent trunk

Also add some additional changes from the SpringPP patch,
and make some other minor changes/fixes.
pull/3/head
patch-import 11 years ago committed by Jonathan G Rennison
parent 0b09a7ac61
commit fc0efe599e

Binary file not shown.

@ -366,6 +366,8 @@
<ClCompile Include="..\src\sdl.cpp" />
<ClCompile Include="..\src\settings.cpp" />
<ClCompile Include="..\src\signal.cpp" />
<ClCompile Include="..\src\programmable_signals.cpp" />
<ClCompile Include="..\src\programmable_signals_gui.cpp" />
<ClCompile Include="..\src\signs.cpp" />
<ClCompile Include="..\src\sound.cpp" />
<ClCompile Include="..\src\sprite.cpp" />
@ -582,6 +584,7 @@
<ClInclude Include="..\src\ship.h" />
<ClInclude Include="..\src\signal_func.h" />
<ClInclude Include="..\src\signal_type.h" />
<ClInclude Include="..\src\programmable_signals.h" />
<ClInclude Include="..\src\signs_base.h" />
<ClInclude Include="..\src\signs_func.h" />
<ClInclude Include="..\src\signs_type.h" />
@ -869,6 +872,7 @@
<ClCompile Include="..\src\saveload\town_sl.cpp" />
<ClCompile Include="..\src\saveload\vehicle_sl.cpp" />
<ClCompile Include="..\src\saveload\waypoint_sl.cpp" />
<ClCompile Include="..\src\saveload\signal_sl.cpp" />
<ClInclude Include="..\src\table\airport_defaults.h" />
<ClInclude Include="..\src\table\airport_movement.h" />
<ClInclude Include="..\src\table\airporttile_ids.h" />

@ -327,6 +327,12 @@
<ClCompile Include="..\src\signal.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\programmable_signals.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\programmable_signals_gui.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\signs.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -975,6 +981,9 @@
<ClInclude Include="..\src\signal_type.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\programmable_signals.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\src\signs_base.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -1836,6 +1845,9 @@
<ClCompile Include="..\src\saveload\waypoint_sl.cpp">
<Filter>Save/Load handlers</Filter>
</ClCompile>
<ClCompile Include="..\src\saveload\signal_sl.cpp">
<Filter>Save/Load handlers</Filter>
</ClCompile>
<ClInclude Include="..\src\table\airport_defaults.h">
<Filter>Tables</Filter>
</ClInclude>

@ -734,6 +734,14 @@
RelativePath=".\..\src\signal.cpp"
>
</File>
<File
RelativePath=".\..\src\programmable_signals.cpp"
>
</File>
<File
RelativePath=".\..\src\programmable_signals_gui.cpp"
>
</File>
<File
RelativePath=".\..\src\signs.cpp"
>
@ -1602,6 +1610,10 @@
RelativePath=".\..\src\signal_type.h"
>
</File>
<File
RelativePath=".\..\src\programmable_signals.h"
>
</File>
<File
RelativePath=".\..\src\signs_base.h"
>
@ -2770,6 +2782,10 @@
RelativePath=".\..\src\saveload\waypoint_sl.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\signal_sl.cpp"
>
</File>
</Filter>
<Filter
Name="Tables"

@ -731,6 +731,14 @@
RelativePath=".\..\src\signal.cpp"
>
</File>
<File
RelativePath=".\..\src\programmable_signals.cpp"
>
</File>
<File
RelativePath=".\..\src\programmable_signals_gui.cpp"
>
</File>
<File
RelativePath=".\..\src\signs.cpp"
>
@ -1599,6 +1607,10 @@
RelativePath=".\..\src\signal_type.h"
>
</File>
<File
RelativePath=".\..\src\programmable_signals.h"
>
</File>
<File
RelativePath=".\..\src\signs_base.h"
>
@ -2767,6 +2779,10 @@
RelativePath=".\..\src\saveload\waypoint_sl.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\signal_sl.cpp"
>
</File>
</Filter>
<Filter
Name="Tables"

@ -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
# Tables
table/airport_defaults.h

@ -198,6 +198,10 @@ CommandProc CmdSetTimetableStart;
CommandProc CmdOpenCloseAirport;
CommandProc CmdInsertSignalInstruction;
CommandProc CmdModifySignalInstruction;
CommandProc CmdRemoveSignalInstruction;
#define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type}
/**
@ -354,6 +358,10 @@ static const Command _command_proc_table[] = {
DEF_CMD(CmdSetTimetableStart, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_TIMETABLE_START
DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT
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
};
/*!

@ -329,6 +329,10 @@ enum Commands {
CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft
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)
};

@ -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

@ -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
@ -2370,6 +2374,70 @@ STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP :{BLACK}Dragging
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
# 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
@ -2606,23 +2674,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_ROAD_DESCRIPTION_ROAD :Road
@ -4300,6 +4375,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
@ -4953,6 +5030,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

@ -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"
@ -304,6 +305,9 @@ static void ShutdownGame()
LinkGraphSchedule::Clear();
PoolBase::Clean(PT_ALL);
FreeSignalPrograms();
FreeSignalDependencies();
/* No NewGRFs were loaded when it was still bootstrapping. */
if (_game_mode != GM_BOOTSTRAP) ResetNewGRFData();

@ -230,6 +230,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:

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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 <assert.h>
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<SignalIf*>(insn);
if (ifi->condition->ConditionCode() == PSC_SIGNAL_STATE) {
SignalStateCondition* c = static_cast<SignalStateCondition*>(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<Track, 0, 3>(p1);
uint instruction_id = GB(p1, 3, 16);
SignalOpcode op = Extract<SignalOpcode, 19, 8>(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<Track, 0, 3 >(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<SignalSet*>(insn);
ss->to_state = state;
} break;
case PSO_IF: {
SignalIf *si = static_cast<SignalIf*>(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<SignalVariableCondition*>(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<SignalStateCondition*>(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<Track, 0, 3 >(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();
}

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file programmable_signals.h Programmable Signals */
#ifndef PROGRAMMABLE_SIGNALS_H
#define PROGRAMMABLE_SIGNALS_H
#include "rail_map.h"
#include "core/smallvec_type.hpp"
#include <map>
/** @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<SignalInstruction*, 4> 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<SignalOpcode> : MakeEnumPropsT<SignalOpcode, byte, PSO_FIRST, PSO_END, PSO_INVALID, 8> {};
/** 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<SignalInstruction*>(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
/// <ul>
/// <li>Set next->previous to previous
/// <li>Set previous->next to next
/// <li>Destroy any other children
/// </ul>
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:
* <ol>
* <li>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)</li>
* <li>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</li>
* </ol>
*/
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:
* <ul>
* <li>They correctly vector the execution to after the if block
* (if needed)
* <li>They provide an instruction for the GUI to insert other instructions
* before.
* </ul>
*/
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<SignalReference, SignalProgram*> 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

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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<SignalVariableCondition*>(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<SignalStateCondition*>(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<SignalIf*>(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<SignalSet*>(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<GuiInstruction, 4> 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 <SignalSet*>(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 <SignalIf*>(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 <SignalIf*>(si);
if (!IsConditionComparator(sif->condition)) return;
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(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 <SignalIf*>(si);
if (!IsConditionComparator(sif->condition)) return;
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(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 <SignalIf*>(si);
if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return;
SignalStateCondition *sc = static_cast<SignalStateCondition*>(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 <SignalIf*>(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 <SignalIf*>(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<NWidgetBase>(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<SignalIf*>(insn);
if (!IsConditionComparator(si->condition)) return;
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(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<NWidgetBase>(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<SignalSpecial*>(insn);
GuiInstruction *gi = this->instructions.Append();
gi->insn = s;
gi->indent = indent;
insn = s->next;
break;
}
case PSO_IF: {
SignalIf *i = static_cast<SignalIf*>(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<SignalIf::PseudoInstruction*>(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<SignalIf::PseudoInstruction*>(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<SignalSet*>(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<NWidgetStacked>(PROGRAM_WIDGET_SEL_TOP_LEFT);
NWidgetStacked *middle_sel = this->GetWidget<NWidgetStacked>(PROGRAM_WIDGET_SEL_TOP_MIDDLE);
NWidgetStacked *right_sel = this->GetWidget<NWidgetStacked>(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<SignalIf*>(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<NWidgetCore>(PROGRAM_WIDGET_COND_VARIABLE)->widget_data =
_program_condvar[i->condition->ConditionCode()];
if (IsConditionComparator(i->condition)) {
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(i->condition);
this->EnableWidget(PROGRAM_WIDGET_COND_COMPARATOR);
this->EnableWidget(PROGRAM_WIDGET_COND_VALUE);
this->GetWidget<NWidgetCore>(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<SignalSet*>(insn);
left_sel->SetDisplayedPlane(DPL_SET_STATE);
this->SetWidgetDisabledState(PROGRAM_WIDGET_SET_STATE, false);
this->GetWidget<NWidgetCore>(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);
}

@ -33,6 +33,7 @@
#include "strings_func.h"
#include "company_gui.h"
#include "object_map.h"
#include "programmable_signals.h"
#include "table/strings.h"
#include "table/railtypes.h"
@ -93,10 +94,12 @@ void ResolveRailTypeGUISprites(RailtypeInfo *rti)
/* 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 +645,7 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1,
/* 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 +995,7 @@ CommandCost CmdBuildTrainDepot(TileIndex tile, DoCommandFlag flags, uint32 p1, u
* - 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,12 +1010,9 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
SignalVariant sigvar = (ctrl_pressed ^ HasBit(p1, 4)) ? SIG_SEMAPHORE : SIG_ELECTRIC; // the signal variant of the new signal
SignalType sigtype = Extract<SignalType, 5, 3>(p1); // the signal type of the new signal
bool convert_signal = HasBit(p1, 8); // convert button pressed
SignalType cycle_start = Extract<SignalType, 9, 3>(p1);
SignalType cycle_stop = Extract<SignalType, 12, 3>(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) ||
@ -1098,6 +1098,8 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
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 +1108,12 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
}
} 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 +1131,8 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
* 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);
}
@ -1259,7 +1265,7 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin
/* 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<SignalType, 7, 3>(p2);
if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
byte signals;
@ -1449,6 +1455,7 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1
}
}
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));
@ -1780,6 +1787,9 @@ static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags)
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 +1875,14 @@ static void DrawSingleSignal(TileIndex tile, const RailtypeInfo *rti, Track trac
} 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 +2722,15 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
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 +2738,8 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
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 +2747,8 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
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 +2756,8 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
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 +2765,8 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
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 +2774,17 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
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
}
};

@ -34,6 +34,7 @@
#include "vehicle_func.h"
#include "zoom_func.h"
#include "rail_gui.h"
#include "programmable_signals.h"
#include "station_map.h"
#include "tunnelbridge_map.h"
@ -49,8 +50,10 @@ static DiagDirection _build_depot_direction; ///< Currently selected depot direc
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 _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};
@ -224,34 +227,44 @@ static void GenericPlaceSignals(TileIndex tile)
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);
return;
}
/* 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)};
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;
}
/* various bitstuffed elements for CmdBuildSingleSignal() */
uint32 p1 = track;
// 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.
}
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]);
}
const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
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);
/* 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);
}
/**
@ -1564,7 +1577,7 @@ public:
{
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 +1585,22 @@ public:
}
}
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 +1608,20 @@ public:
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 +1629,18 @@ public:
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)
_program_signal_button = false;
break;
case WID_BS_PROGRAM:
_program_signal_button = !_program_signal_button;
if(_program_signal_button)
_convert_signal_button = false;
break;
case WID_BS_DRAG_SIGNALS_DENSITY_DECREASE:
@ -1632,9 +1671,10 @@ public:
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_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 +1693,18 @@ static const NWidgetPart _nested_signal_builder_widgets[] = {
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_PANEL, COLOUR_DARK_GREEN), EndContainer(), 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 +1717,7 @@ static const NWidgetPart _nested_signal_builder_widgets[] = {
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 +2001,7 @@ bool ResetSignalVariant(int32 p)
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 +2018,12 @@ void InitializeRailGUI()
SetDefaultRailGui();
_convert_signal_button = false;
_cur_signal_type = _default_signal_type[_settings_client.gui.default_signal_type];
_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();
}

@ -285,12 +285,6 @@ static inline TrackBits GetDepotReservationTrackBits(TileIndex t)
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 void SetSignalType(TileIndex t, Track track, SignalType s)
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. */

@ -262,8 +262,9 @@
* 192 26700
* 193 26802
* 194 26881 1.5.x
* 200 Programmable Signals patch added
*/
extern const uint16 SAVEGAME_VERSION = 194; ///< Current savegame version of OpenTTD.
extern const uint16 SAVEGAME_VERSION = 200; ///< Current savegame version of OpenTTD.
SavegameType _savegame_type; ///< type of savegame we are loading
@ -447,6 +448,7 @@ extern const ChunkHandler _linkgraph_chunk_handlers[];
extern const ChunkHandler _airport_chunk_handlers[];
extern const ChunkHandler _object_chunk_handlers[];
extern const ChunkHandler _persistent_storage_chunk_handlers[];
extern const ChunkHandler _signal_chunk_handlers[];
/** Array of all chunks in a savegame, \c NULL terminated. */
static const ChunkHandler * const _chunk_handlers[] = {
@ -483,6 +485,7 @@ static const ChunkHandler * const _chunk_handlers[] = {
_airport_chunk_handlers,
_object_chunk_handlers,
_persistent_storage_chunk_handlers,
_signal_chunk_handlers,
NULL,
};

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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 <vector>
#include "saveload.h"
typedef std::vector<byte> 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<SignalVariableCondition*>(c);
WriteVLI(b, vc->comparator);
WriteVLI(b, vc->value);
} break;
case PSC_SIGNAL_STATE: {
SignalStateCondition *sc = static_cast<SignalStateCondition*>(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<SignalSpecial*>(insn);
WriteVLI(b, s->next->Id());
break;
}
case PSO_LAST: break;
case PSO_IF: {
SignalIf *i = static_cast<SignalIf*>(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<SignalIf::PseudoInstruction*>(insn);
WriteVLI(b, p->block->Id());
break;
}
case PSO_SET_SIGNAL: {
SignalSet *s = static_cast<SignalSet*>(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<Fixup, 4> FixupList;
template<typename T>
static void MakeFixup(FixupList &l, T *&ir, uint id, SignalOpcode op = PSO_INVALID)
{
ir = reinterpret_cast<T*>(id);
new(l.Append()) Fixup(reinterpret_cast<SignalInstruction**>(&ir), op);
}
static void DoFixups(FixupList &l, InstructionList &il)
{
for(Fixup *i = l.Begin(), *e = l.End(); i != e; ++i) {
uint id = reinterpret_cast<size_t>(*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},
};

@ -1649,6 +1649,7 @@ static SettingsContainer &GetSettingsTree()
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));

@ -311,6 +311,7 @@ struct ConstructionSettings {
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
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?

@ -17,9 +17,22 @@
#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 "safeguards.h"
/// List of signals dependent upon this one
typedef SmallVector<SignalReference, 4> 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<SignalReference, SignalDependencyList> 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 +201,7 @@ static SmallSet<Trackdir, SIG_TBU_SIZE> _tbuset("_tbuset"); ///< set of
static SmallSet<DiagDirection, SIG_TBD_SIZE> _tbdset("_tbdset"); ///< set of open nodes in current signal block
static SmallSet<DiagDirection, SIG_GLOB_SIZE> _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 *)
@ -247,18 +261,25 @@ static inline bool MaybeAddToTodoSet(TileIndex t1, DiagDirection d1, TileIndex t
/** 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 +287,9 @@ DECLARE_ENUM_AS_BIT_SET(SigFlags)
* @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;
@ -283,13 +304,13 @@ static SigFlags ExploreSegment(Owner owner)
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 +324,10 @@ static SigFlags ExploreSegment(Owner owner)
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 +341,19 @@ static SigFlags ExploreSegment(Owner owner)
* (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 +365,10 @@ static SigFlags ExploreSegment(Owner owner)
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;
}
}
}
@ -358,7 +381,7 @@ static SigFlags ExploreSegment(Owner owner)
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;
@ -367,7 +390,7 @@ static SigFlags ExploreSegment(Owner owner)
if (GetTileOwner(tile) != owner) 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;
@ -377,13 +400,13 @@ static SigFlags ExploreSegment(Owner owner)
DiagDirection dir = GetTunnelBridgeDirection(tile);
if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
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 (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
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;
@ -395,10 +418,12 @@ static SigFlags ExploreSegment(Owner owner)
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,43 +432,67 @@ static SigFlags ExploreSegment(Owner owner)
*
* @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);
@ -475,6 +524,7 @@ static SigSegState UpdateSignalsInBuffer(Owner owner)
bool first = true; // first block?
SigSegState state = SIGSEG_FREE; // value to return
_num_signals_evaluated = 0;
TileIndex tile;
DiagDirection dir;
@ -529,25 +579,29 @@ static SigSegState UpdateSignalsInBuffer(Owner owner)
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;
@ -660,3 +714,89 @@ void SetSignalsOnBothDir(TileIndex tile, Track track, Owner owner)
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);
}
}

@ -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 @@ static inline byte SignalOnTrack(Track track)
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 @@ enum SigSegState {
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);

@ -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 @@ enum SignalType {
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<SignalType> : MakeEnumPropsT<SignalType, byte, SIGTYPE_NORMAL, SIGTYPE_END, SIGTYPE_END, 3> {};
/** 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 @@ template <> struct EnumPropsT<SignalType> : MakeEnumPropsT<SignalType, byte, SIG
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 */

@ -1458,6 +1458,21 @@ from = 77
def = true
cat = SC_EXPERT
[SDT_VAR]
base = GameSettings
var = construction.maximum_signal_evaluations
type = SLE_UINT16
from = 200
to = SL_MAX_VERSION
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
; previously ai-new setting.
[SDT_NULL]
length = 1

@ -296,8 +296,12 @@ static const uint16 EMPTY_BOUNDING_BOX_SPRITE_COUNT = 1;
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;
/* 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_PROGSIGNAL_BASE + PROGSIGNAL_SPRITE_COUNT;
/* Manager face sprites */
static const SpriteID SPR_GRADIENT = 874; // background gradient behind manager face
@ -1309,12 +1313,14 @@ static const SpriteID SPR_IMG_SIGNAL_ELECTRIC_EXIT = SPR_SIGNALS_BASE + 28;
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;

@ -82,15 +82,18 @@ enum BuildSignalWidgets {
WID_BS_SEMAPHORE_ENTRY, ///< Build a semaphore entry block signal
WID_BS_SEMAPHORE_EXIT, ///< Build a semaphore exit block signal
WID_BS_SEMAPHORE_COMBO, ///< Build a semaphore combo block signal
WID_BS_SEMAPHORE_PROG, ///< Build a semahore programmable signal
WID_BS_SEMAPHORE_PBS, ///< Build a semaphore path signal.
WID_BS_SEMAPHORE_PBS_OWAY, ///< Build a semaphore one way path signal.
WID_BS_ELECTRIC_NORM, ///< Build an electric normal block signal
WID_BS_ELECTRIC_ENTRY, ///< Build an electric entry block signal
WID_BS_ELECTRIC_EXIT, ///< Build an electric exit block signal
WID_BS_ELECTRIC_COMBO, ///< Build an electric combo block signal
WID_BS_ELECTRIC_PROG, ///< Build an electric programmable signal
WID_BS_ELECTRIC_PBS, ///< Build an electric path signal.
WID_BS_ELECTRIC_PBS_OWAY, ///< Build an electric one way path signal.
WID_BS_CONVERT, ///< Convert the signal.
WID_BS_PROGRAM, ///< Enter program to prog signal
WID_BS_DRAG_SIGNALS_DENSITY_LABEL, ///< The current signal density.
WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, ///< Decrease the signal density.
WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, ///< Increase the signal density.

@ -681,6 +681,11 @@ enum WindowClass {
*/
WC_SAVE_PRESET,
/**
* Programmable signals window
*/
WC_SIGNAL_PROGRAM,
WC_INVALID = 0xFFFF, ///< Invalid window.
};

Loading…
Cancel
Save