diff --git a/bin/data/progsignals.grf b/bin/data/progsignals.grf new file mode 100644 index 0000000000..facb08c0a0 Binary files /dev/null and b/bin/data/progsignals.grf differ diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index 8d1c9058fe..c958101718 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -366,6 +366,8 @@ + + @@ -582,6 +584,7 @@ + @@ -869,6 +872,7 @@ + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index 37c6b1559c..d83ca2a376 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -327,6 +327,12 @@ Source Files + + Source Files + + + Source Files + Source Files @@ -975,6 +981,9 @@ Header Files + + Header Files + Header Files @@ -1836,6 +1845,9 @@ Save/Load handlers + + Save/Load handlers + Save/Load handlers diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index 5c874a0801..a84a122b32 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -734,6 +734,14 @@ RelativePath=".\..\src\signal.cpp" > + + + + @@ -1602,6 +1610,10 @@ RelativePath=".\..\src\signal_type.h" > + + @@ -2770,6 +2782,10 @@ RelativePath=".\..\src\saveload\waypoint_sl.cpp" > + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj index 7554ff3a58..afcdf8db8f 100644 --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -731,6 +731,14 @@ RelativePath=".\..\src\signal.cpp" > + + + + @@ -1599,6 +1607,10 @@ RelativePath=".\..\src\signal_type.h" > + + @@ -2767,6 +2779,10 @@ RelativePath=".\..\src\saveload\waypoint_sl.cpp" > + + diff --git a/source.list b/source.list index 8386d82d1e..72f157abf6 100644 --- a/source.list +++ b/source.list @@ -76,6 +76,8 @@ screenshot.cpp #end settings.cpp signal.cpp +programmable_signals.cpp +programmable_signals_gui.cpp signs.cpp sound.cpp sprite.cpp @@ -321,6 +323,7 @@ settings_type.h ship.h signal_func.h signal_type.h +programmable_signals.h signs_base.h signs_func.h signs_type.h @@ -633,6 +636,7 @@ saveload/subsidy_sl.cpp saveload/town_sl.cpp saveload/vehicle_sl.cpp saveload/waypoint_sl.cpp +saveload/signal_sl.cpp saveload/extended_ver_sl.h saveload/extended_ver_sl.cpp diff --git a/src/command.cpp b/src/command.cpp index 96d88f3854..89ebea499e 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -200,6 +200,10 @@ CommandProc CmdOpenCloseAirport; CommandProc CmdProgramSignalTraceRestrict; +CommandProc CmdInsertSignalInstruction; +CommandProc CmdModifySignalInstruction; +CommandProc CmdRemoveSignalInstruction; + #define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type} /** @@ -358,6 +362,10 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT DEF_CMD(CmdProgramSignalTraceRestrict, 0, CMDT_OTHER_MANAGEMENT ), // CMD_PROGRAM_TRACERESTRICT_SIGNAL + + DEF_CMD(CmdInsertSignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_INSERT_SIGNAL_INSTRUCTION + DEF_CMD(CmdModifySignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_MODIFY_SIGNAL_INSTRUCTION + DEF_CMD(CmdRemoveSignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNAL_INSTRUCTION }; /*! diff --git a/src/command_type.h b/src/command_type.h index 1f99797ada..ac639d127e 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -331,6 +331,10 @@ enum Commands { CMD_PROGRAM_TRACERESTRICT_SIGNAL, ///< modify a signal tracerestrict program + CMD_INSERT_SIGNAL_INSTRUCTION, ///< insert a signal instruction + CMD_MODIFY_SIGNAL_INSTRUCTION, ///< modifies a signal instruction + CMD_REMOVE_SIGNAL_INSTRUCTION, ///< removes a signal instruction + CMD_END, ///< Must ALWAYS be on the end of this list!! (period) }; diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp index 10bc0afa17..0c325e856d 100644 --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -170,6 +170,9 @@ static void LoadSpriteTables() _palette_remap_grf[i] = (PAL_DOS != used_set->palette); LoadGrfFile(used_set->files[GFT_BASE].filename, 0, i++); + /* Progsignal sprites. */ + LoadGrfFile("progsignals.grf", SPR_PROGSIGNAL_BASE, i++); + /* * The second basic file always starts at the given location and does * contain a different amount of sprites depending on the "type"; DOS diff --git a/src/lang/english.txt b/src/lang/english.txt index e077670977..005818319c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1292,6 +1292,8 @@ STR_CONFIG_SETTING_LANDSCAPE :Landscape: {STR STR_CONFIG_SETTING_LANDSCAPE_HELPTEXT :Landscapes define basic gameplay scenarios with different cargos and town growth requirements. NewGRF and Game Scripts allow finer control though STR_CONFIG_SETTING_LAND_GENERATOR :Land generator: {STRING2} STR_CONFIG_SETTING_LAND_GENERATOR_HELPTEXT :The original generator depends on the base graphics set, and composes fixed landscape shapes. TerraGenesis is a Perlin noise based generator with finer control settings +STR_CONFIG_SETTING_MAX_SIGNAL_EVALUATIONS :Maximum number of programmable signal changes permitted at once: {STRING2} +STR_CONFIG_SETTING_MAX_SIGNAL_EVALUATIONS_HELPTEXT :Sets the maximum number of programmable signal changes permitted at once STR_CONFIG_SETTING_LAND_GENERATOR_ORIGINAL :Original STR_CONFIG_SETTING_LAND_GENERATOR_TERRA_GENESIS :TerraGenesis STR_CONFIG_SETTING_TERRAIN_TYPE :Terrain type: {STRING2} @@ -2357,12 +2359,14 @@ STR_BUILD_SIGNAL_SEMAPHORE_NORM_TOOLTIP :{BLACK}Block Si STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP :{BLACK}Entry Signal (semaphore){}Green as long as there is one or more green exit-signal from the following section of track. Otherwise it shows red STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP :{BLACK}Exit Signal (semaphore){}Behaves in the same way as a block signal but is necessary to trigger the correct colour on entry & combo pre-signals STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP :{BLACK}Combo Signal (semaphore){}The combo signal simply acts as both an entry and exit signal. This allows you to build large "trees" of pre-signals +STR_BUILD_SIGNAL_SEMAPHORE_PROG_TOOLTIP :{BLACK}Programmable-Signal (semaphore){}The programmable signal is a combo-signal which can be programmed to behave in complex ways. STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP :{BLACK}Path Signal (semaphore){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. Standard path signals can be passed from the back side STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP :{BLACK}One-way Path Signal (semaphore){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. One-way path signals can't be passed from the back side STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP :{BLACK}Block Signal (electric){}This is the most basic type of signal, allowing only one train to be in the same block at the same time STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP :{BLACK}Entry Signal (electric){}Green as long as there is one or more green exit-signal from the following section of track. Otherwise it shows red STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP :{BLACK}Exit Signal (electric){}Behaves in the same way as a block signal but is necessary to trigger the correct colour on entry & combo pre-signals STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP :{BLACK}Combo Signal (electric){}The combo signal simply acts as both an entry and exit signal. This allows you to build large "trees" of pre-signals +STR_BUILD_SIGNAL_ELECTRIC_PROG_TOOLTIP :{BLACK}Programmable-Signal (electric){}The programmable signal is a combo-signal which can be programmed to behave in complex ways. STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP :{BLACK}Path Signal (electric){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. Standard path signals can be passed from the back side STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP :{BLACK}One-way Path Signal (electric){}A path signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. One-way path signals can't be passed from the back side STR_BUILD_SIGNAL_CONVERT_TOOLTIP :{BLACK}Signal Convert{}When selected, clicking an existing signal will convert it to the selected signal type and variant. Ctrl+Click will toggle the existing variant. Shift+Click shows estimated conversion cost @@ -2457,6 +2461,70 @@ STR_TRACE_RESTRICT_ERROR_CAN_T_COPY_PROGRAM :{WHITE}Can't co STR_TRACE_RESTRICT_ERROR_CAN_T_SHARE_PROGRAM :{WHITE}Can't share program STR_TRACE_RESTRICT_ERROR_CAN_T_UNSHARE_PROGRAM :{WHITE}Can't unshare program +# Programmable Signals +STR_PROGRAM_SIGNAL_TOOLTIP :{BLACK}Program signal + +STR_ERR_PROGSIG_INVALID_INSTRUCTION :{WHITE}Cannot insert instruction after instruction with invalid ID +STR_ERR_PROGSIG_INVALID_OPCODE :{WHITE}Cannot insert an instruction of that opcode +STR_ERR_PROGSIG_NOT_THERE :{WHITE}There is no programmable signal there +STR_ERR_PROGSIG_INVALID_SIGNAL_STATE :{WHITE}That signal state is invalid +STR_ERR_PROGSIG_INVALID_CONDITION :{WHITE}That condition is invalid +STR_ERR_PROGSIG_INVALID_CONDITION_FIELD :{WHITE}That field is not valid for the condition +STR_ERR_PROGSIG_INVALID_COMPARATOR :{WHITE}That comparator is not valid +STR_ERR_PROGSIG_INVALID_SIGNAL :{WHITE}Invalid signal selected + +STR_PROGSIG_CAPTION :{WHITE}Signal Program +STR_PROGSIG_COND_VARIABLE_TOOLTIP :{BLACK}Condition to compare upon +STR_PROGSIG_COND_COMPARATOR_TOOLTIP :{BLACK}Operator to use to compare variable +STR_PROGSIG_COND_VALUE_TOOLTIP :{BLACK}Value to compare variable against +STR_PROGSIG_SIGNAL_STATE_TOOLTIP :{BLACK}Set signal to state +STR_PROGSIG_COND_SET_SIGNAL :{BLACK}Set signal +STR_PROGSIG_COND_SET_SIGNAL_TOOLTIP :{BLACK}Set the signal to be looked at +STR_PROGSIG_GOTO_SIGNAL :{BLACK}Goto signal +STR_PROGSIG_GOTO_SIGNAL_TOOLTIP :{BLACK}Go to this signal +STR_PROGSIG_INSERT_TOOLTIP :{BLACK}Insert an instruction +STR_PROGSIG_REMOVE_TOOLTIP :{BLACK}Remove the selected instruction +STR_PROGSIG_REMOVE_PROGRAM_TOOLTIP :{BLACK}Remove entire program +STR_PROGSIG_COPY_PROGRAM_TOOLTIP :{BLACK}Copy program from existing signal + +STR_PROGSIG_REMOVE_PROGRAM :{RED}Remove program +STR_PROGSIG_COPY_PROGRAM :{BLUE}Copy program +STR_PROGSIG_REMOVE :{BLACK}Remove +STR_PROGSIG_INSERT :Insert +STR_PROGSIG_INSERT_IF :Condition +STR_PROGSIG_INSERT_SET_SIGNAL :Set signal state + +STR_PROGSIG_FIRST :Start +STR_PROGSIG_LAST :End + +STR_PROGSIG_IF :If {RAW_STRING} Then +STR_PROGSIG_ELSE :Else +STR_PROGSIG_ENDIF :End If + +STR_PROGSIG_SET_SIGNAL :Make signal {STRING} + +STR_PROGSIG_COND_ALWAYS :always +STR_PROGSIG_COND_NEVER :never +STR_PROGSIG_COND_COMPARE :{STRING} {STRING} {NUM} +STR_PROGSIG_COND_SIGNAL_STATE :signal state +STR_PROGSIG_CONDVAR_SIGNAL_STATE :{STRING1} is green +STR_PROGSIG_CONDVAR_SIGNAL_STATE_SPECIFIED :specified signal +STR_PROGSIG_CONDVAR_SIGNAL_STATE_UNSPECIFIED :{RED}unspecified signal{STRING} +STR_PROGSIG_CONDVAR_NUM_RED :red signals +STR_PROGSIG_CONDVAR_NUM_GREEN :green signals + +STR_PROGSIG_CONDITION_VALUE_CAPT :{WHITE}Condition value + +STR_ERROR_CAN_T_INSERT_INSTRUCTION :{WHITE}Can't insert instruction +STR_ERROR_CAN_T_MODIFY_INSTRUCTION :{WHITE}Can't modify instruction +STR_ERROR_CAN_T_REMOVE_INSTRUCTION :{WHITE}Can't remove instruction +STR_ERROR_CAN_T_GOTO_UNDEFINED_SIGNAL :{WHITE}Can't go to undefined signal +STR_ERROR_NOT_AN_EXIT_SIGNAL :{WHITE}Not an exit signal +STR_ERROR_NOT_AN_PROG_SIGNAL :{WHITE}Not an programmable signal +STR_ERROR_CANNOT_USE_SELF :{WHITE}Can't copy program from myself +STR_ERROR_CAN_T_DEPEND_UPON_BIDIRECTIONAL_SIGNALS :{WHITE}Cannot conditionally depend upon bidirectional signals +STR_ERROR_INVALID_SIGNAL :{WHITE}Invalid signal + # Bridge selection window STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Select Rail Bridge STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Select Road Bridge @@ -2693,23 +2761,30 @@ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_SIGNALS :{STRING} track STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRESIGNALS :{STRING} track with pre-signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXITSIGNALS :{STRING} track with exit-signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBOSIGNALS :{STRING} track with combo-signals +STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PROGSIGNALS :{STRING} track with programmable signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBSSIGNALS :{STRING} track with path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRYSIGNALS :{STRING} track with one-way path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PRESIGNALS :{STRING} track with block and pre-signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_EXITSIGNALS :{STRING} track with block and exit-signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_COMBOSIGNALS :{STRING} track with block and combo-signals +STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PROGSIGNALS :{STRING} track with block and programmable signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_PBSSIGNALS :{STRING} track with block and path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_NOENTRYSIGNALS :{STRING} track with block and one-way path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_EXITSIGNALS :{STRING} track with pre- and exit-signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_COMBOSIGNALS :{STRING} track with pre- and combo-signals +STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_PROGSIGNALS :{STRING} track with pre- and programmable signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_PBSSIGNALS :{STRING} track with pre- and path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PRE_NOENTRYSIGNALS :{STRING} track with pre- and one-way path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_COMBOSIGNALS :{STRING} track with exit- and combo-signals +STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PROGSIGNALS :{STRING} track with exit- and programmable signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_PBSSIGNALS :{STRING} track with exit- and path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS :{STRING} track with exit- and one-way path signals +STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PROGSIGNALS :{STRING} track with combo- and programmable signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PBSSIGNALS :{STRING} track with combo- and path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS :{STRING} track with combo- and one-way path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS :{STRING} track with path and one-way path signals +STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_PROGSIGNALS :{STRING} track with path and programmable signals +STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NOENTRY_PROGSIGNALS :{STRING} track with one-way path and programmable signals STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :{STRING} train depot STR_LAI_RAIL_DESCRIPTION_RESTRICTED_SIGNAL :{STRING1} (restricted) @@ -4389,6 +4464,8 @@ STR_ERROR_THERE_ARE_NO_SIGNALS :{WHITE}... ther STR_ERROR_CAN_T_CONVERT_RAIL :{WHITE}Can't convert rail type here... +STR_ERROR_SIGNAL_CHANGES :{WHITE}Number of programmable signal evaluations exceeded limit + # Road construction errors STR_ERROR_MUST_REMOVE_ROAD_FIRST :{WHITE}Must remove road first STR_ERROR_ONEWAY_ROADS_CAN_T_HAVE_JUNCTION :{WHITE}... one way roads can't have junctions @@ -5042,6 +5119,8 @@ STR_TINY_BLACK_HEIGHT :{TINY_FONT}{BLA STR_TINY_BLACK_VEHICLE :{TINY_FONT}{BLACK}{VEHICLE} STR_TINY_RIGHT_ARROW :{TINY_FONT}{RIGHT_ARROW} +STR_WHITE :{WHITE} +STR_BLACK :{BLACK} STR_BLACK_1 :{BLACK}1 STR_BLACK_2 :{BLACK}2 STR_BLACK_3 :{BLACK}3 diff --git a/src/openttd.cpp b/src/openttd.cpp index 1e4450da9e..8b45c9e99b 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -63,6 +63,7 @@ #include "subsidy_func.h" #include "gfx_layout.h" #include "viewport_sprite_sorter.h" +#include "programmable_signals.h" #include "linkgraph/linkgraphschedule.h" #include "tracerestrict.h" @@ -306,6 +307,9 @@ static void ShutdownGame() ClearTraceRestrictMapping(); PoolBase::Clean(PT_ALL); + FreeSignalPrograms(); + FreeSignalDependencies(); + /* No NewGRFs were loaded when it was still bootstrapping. */ if (_game_mode != GM_BOOTSTRAP) ResetNewGRFData(); diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 5a4b6531c3..1215a9c6d2 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -255,6 +255,7 @@ public: /* special signal penalties */ if (n.m_num_signals_passed == 0) { switch (sig_type) { + case SIGTYPE_PROG: case SIGTYPE_COMBO: case SIGTYPE_EXIT: cost += Yapf().PfGetSettings().rail_firstred_exit_penalty; break; // first signal is red pre-signal-exit case SIGTYPE_NORMAL: diff --git a/src/programmable_signals.cpp b/src/programmable_signals.cpp new file mode 100644 index 0000000000..c7d9e5d87c --- /dev/null +++ b/src/programmable_signals.cpp @@ -0,0 +1,699 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file programmable_signals.cpp Programmable Signals */ + +#include "stdafx.h" +#include "programmable_signals.h" +#include "debug.h" +#include "command_func.h" +#include "table/strings.h" +#include "window_func.h" +#include "company_func.h" +#include "cmd_helper.h" +#include + +ProgramList _signal_programs; + +SignalProgram::SignalProgram(TileIndex tile, Track track, bool raw) +{ + this->tile = tile; + this->track = track; + if (!raw) { + this->first_instruction = new SignalSpecial(this, PSO_FIRST); + this->last_instruction = new SignalSpecial(this, PSO_LAST); + SignalSpecial::link(this->first_instruction, this->last_instruction); + } +} + +SignalProgram::~SignalProgram() +{ + this->DebugPrintProgram(); + this->first_instruction->Remove(); + delete this->first_instruction; + delete this->last_instruction; +} + +struct SignalVM { + // Initial information + uint num_exits; ///< Number of exits from block + uint num_green; ///< Number of green exits from block + SignalProgram *program; ///< The program being run + + // Current state + SignalInstruction *instruction; ///< Instruction to execute next + + // Output state + SignalState state; + + void Execute() + { + DEBUG(misc, 6, "Begining execution of programmable signal on tile %x, track %d", + this->program->tile, this->program->track); + do { + DEBUG(misc, 10, " Executing instruction %d, opcode %d", this->instruction->Id(), this->instruction->Opcode()); + this->instruction->Evaluate(*this); + } while (this->instruction); + + DEBUG(misc, 6, "Completed"); + } +}; + +// -- Conditions + +SignalCondition::~SignalCondition() +{} + +SignalSimpleCondition::SignalSimpleCondition(SignalConditionCode code) + : SignalCondition(code) +{} + +/* virtual */ bool SignalSimpleCondition::Evaluate(SignalVM &vm) +{ + switch (this->cond_code) { + case PSC_ALWAYS: return true; + case PSC_NEVER: return false; + default: NOT_REACHED(); + } +} + +SignalVariableCondition::SignalVariableCondition(SignalConditionCode code) + : SignalCondition(code) +{ + switch (this->cond_code) { + case PSC_NUM_GREEN: comparator = SGC_NOT_EQUALS; break; + case PSC_NUM_RED: comparator = SGC_EQUALS; break; + default: NOT_REACHED(); + } + value = 0; +} + +/*virtual*/ bool SignalVariableCondition::Evaluate(SignalVM &vm) +{ + uint32 var_val; + switch (this->cond_code) { + case PSC_NUM_GREEN: var_val = vm.num_green; break; + case PSC_NUM_RED: var_val = vm.num_exits - vm.num_green; break; + default: NOT_REACHED(); + } + + switch (this->comparator) { + case SGC_EQUALS: return var_val == this->value; + case SGC_NOT_EQUALS: return var_val != this->value; + case SGC_LESS_THAN: return var_val < this->value; + case SGC_LESS_THAN_EQUALS: return var_val <= this->value; + case SGC_MORE_THAN: return var_val > this->value; + case SGC_MORE_THAN_EQUALS: return var_val >= this->value; + case SGC_IS_TRUE: return var_val != 0; + case SGC_IS_FALSE: return !var_val; + default: NOT_REACHED(); + } +} + +SignalStateCondition::SignalStateCondition(SignalReference this_sig, + TileIndex sig_tile, Trackdir sig_track) + : SignalCondition(PSC_SIGNAL_STATE), this_sig(this_sig), sig_tile(sig_tile) + , sig_track(sig_track) +{ + if (this->IsSignalValid()) + AddSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)), + this->this_sig); +} + +bool SignalStateCondition::IsSignalValid() +{ + if (IsValidTile(this->sig_tile)) { + if (HasSignalOnTrackdir(this->sig_tile, this->sig_track)) { + return true; + } else { + Invalidate(); + } + } + return false; +} + +void SignalStateCondition::Invalidate() +{ + this->sig_tile = INVALID_TILE; +} + + +void SignalStateCondition::SetSignal(TileIndex tile, Trackdir track) +{ + if (this->IsSignalValid()) + RemoveSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)), + this->this_sig); + this->sig_tile = tile; + this->sig_track = track; + if (this->IsSignalValid()) + AddSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)), + this->this_sig); +} + +/*virtual*/ SignalStateCondition::~SignalStateCondition() +{ + if (this->IsSignalValid()) + RemoveSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)), + this->this_sig); +} + +/*virtual*/ bool SignalStateCondition::Evaluate(SignalVM& vm) +{ + if (!this->IsSignalValid()) { + DEBUG(misc, 1, "Signal (%x, %d) has an invalid condition", this->this_sig.tile, this->this_sig.track); + return false; + } + + return GetSignalStateByTrackdir(this->sig_tile, this->sig_track) == SIGNAL_STATE_GREEN; +} + +// -- Instructions +SignalInstruction::SignalInstruction(SignalProgram *prog, SignalOpcode op) + : opcode(op), previous(NULL), program(prog) +{ + *program->instructions.Append() = this; +} + +SignalInstruction::~SignalInstruction() +{ + SignalInstruction** pthis = program->instructions.Find(this); + assert(pthis != program->instructions.End()); + program->instructions.Erase(pthis); +} + +void SignalInstruction::Insert(SignalInstruction *before_insn) +{ + this->previous = before_insn->Previous(); + before_insn->Previous()->SetNext(this); + before_insn->SetPrevious(this); + this->SetNext(before_insn); +} + +SignalSpecial::SignalSpecial(SignalProgram *prog, SignalOpcode op) + : SignalInstruction(prog, op) +{ + assert(op == PSO_FIRST || op == PSO_LAST); + this->next = NULL; +} + +/*virtual*/ void SignalSpecial::Remove() +{ + if (opcode == PSO_FIRST) { + while (this->next->Opcode() != PSO_LAST) this->next->Remove(); + } else if (opcode == PSO_LAST) { + } else NOT_REACHED(); +} + +/*static*/ void SignalSpecial::link(SignalSpecial *first, SignalSpecial *last) +{ + assert(first->opcode == PSO_FIRST && last->opcode == PSO_LAST); + first->next = last; + last->previous = first; +} + +void SignalSpecial::Evaluate(SignalVM &vm) +{ + if (this->opcode == PSO_FIRST) { + DEBUG(misc, 7, " Executing First"); + vm.instruction = this->next; + } else { + DEBUG(misc, 7, " Executing Last"); + vm.instruction = NULL; + } +} +/*virtual*/ void SignalSpecial::SetNext(SignalInstruction *next_insn) +{ + this->next = next_insn; +} + +SignalIf::PseudoInstruction::PseudoInstruction(SignalProgram *prog, SignalOpcode op) + : SignalInstruction(prog, op) + {} + +SignalIf::PseudoInstruction::PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op) + : SignalInstruction(prog, op) +{ + this->block = block; + if (op == PSO_IF_ELSE) { + previous = block; + } else if (op == PSO_IF_ENDIF) { + previous = block->if_true; + } else NOT_REACHED(); +} + +/*virtual*/ void SignalIf::PseudoInstruction::Remove() +{ + if (opcode == PSO_IF_ELSE) { + this->block->if_true = NULL; + while(this->block->if_false) this->block->if_false->Remove(); + } else if (opcode == PSO_IF_ENDIF) { + this->block->if_false = NULL; + } else NOT_REACHED(); + delete this; +} + +/*virtual*/ void SignalIf::PseudoInstruction::Evaluate(SignalVM &vm) +{ + DEBUG(misc, 7, " Executing If Pseudo Instruction %s", opcode == PSO_IF_ELSE ? "Else" : "Endif"); + vm.instruction = this->block->after; +} + +/*virtual*/ void SignalIf::PseudoInstruction::SetNext(SignalInstruction *next_insn) +{ + if (this->opcode == PSO_IF_ELSE) { + this->block->if_false = next_insn; + } else if (this->opcode == PSO_IF_ENDIF) { + this->block->after = next_insn; + } else NOT_REACHED(); +} + +SignalIf::SignalIf(SignalProgram *prog, bool raw) + : SignalInstruction(prog, PSO_IF) +{ + if (!raw) { + this->condition = new SignalSimpleCondition(PSC_ALWAYS); + this->if_true = new PseudoInstruction(prog, this, PSO_IF_ELSE); + this->if_false = new PseudoInstruction(prog, this, PSO_IF_ENDIF); + this->after = NULL; + } +} + +/*virtual*/ void SignalIf::Remove() +{ + delete this->condition; + while (this->if_true) this->if_true->Remove(); + + this->previous->SetNext(this->after); + this->after->SetPrevious(this->previous); + delete this; +} + +/*virtual*/ void SignalIf::Insert(SignalInstruction *before_insn) +{ + this->previous = before_insn->Previous(); + before_insn->Previous()->SetNext(this); + before_insn->SetPrevious(this->if_false); + this->after = before_insn; +} + +void SignalIf::SetCondition(SignalCondition *cond) +{ + assert(cond != this->condition); + delete this->condition; + this->condition = cond; +} + +/*virtual*/ void SignalIf::Evaluate(SignalVM &vm) +{ + bool is_true = this->condition->Evaluate(vm); + DEBUG(misc, 7, " Executing If, taking %s branch", is_true ? "then" : "else"); + if (is_true) { + vm.instruction = this->if_true; + } else { + vm.instruction = this->if_false; + } +} + +/*virtual*/ void SignalIf::SetNext(SignalInstruction *next_insn) +{ + this->if_true = next_insn; +} + + + +SignalSet::SignalSet(SignalProgram *prog, SignalState state) + : SignalInstruction(prog, PSO_SET_SIGNAL) +{ + this->to_state = state; +} + +/*virtual*/ void SignalSet::Remove() +{ + this->next->SetPrevious(this->previous); + this->previous->SetNext(this->next); + delete this; +} + +/*virtual*/ void SignalSet::Evaluate(SignalVM &vm) +{ + DEBUG(misc, 7, " Executing SetSignal, making %s", this->to_state? "green" : "red"); + vm.state = this->to_state; + vm.instruction = NULL; +} + + +/*virtual*/ void SignalSet::SetNext(SignalInstruction *next_insn) +{ + this->next = next_insn; +} + +SignalProgram *GetExistingSignalProgram(SignalReference ref) +{ + ProgramList::iterator i = _signal_programs.find(ref); + if (i != _signal_programs.end()) { + assert(i->first == ref); + return i->second; + } else { + return NULL; + } +} + + +SignalProgram *GetSignalProgram(SignalReference ref) +{ + SignalProgram *pr = GetExistingSignalProgram(ref); + if (!pr) { + pr = new SignalProgram(ref.tile, ref.track); + _signal_programs[ref] = pr; + } else assert(pr->tile == ref.tile && pr->track == ref.track); + return pr; +} + +void FreeSignalProgram(SignalReference ref) +{ + DeleteWindowById(WC_SIGNAL_PROGRAM, (ref.tile << 3) | ref.track); + ProgramList::iterator i = _signal_programs.find(ref); + if (i != _signal_programs.end()) { + delete i->second; + _signal_programs.erase(i); + } +} + +void FreeSignalPrograms() +{ + ProgramList::iterator i, e; + for (i = _signal_programs.begin(), e = _signal_programs.end(); i != e;) { + delete i->second; + // Must postincrement here to avoid iterator invalidation + _signal_programs.erase(i++); + } +} + +SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green) +{ + SignalProgram *program = GetSignalProgram(ref); + SignalVM vm; + vm.program = program; + vm.num_exits = num_exits; + vm.num_green = num_green; + + vm.instruction = program->first_instruction; + vm.state = SIGNAL_STATE_RED; + + DEBUG(misc, 7, "%d exits, of which %d green", vm.num_exits, vm.num_green); + vm.Execute(); + DEBUG(misc, 7, "Returning %s", vm.state == SIGNAL_STATE_GREEN ? "green" : "red"); + return vm.state; +} + +void RemoveProgramDependencies(SignalReference by, SignalReference on) +{ + SignalProgram *prog = GetSignalProgram(by); + for (SignalInstruction **b = prog->instructions.Begin(), **i = b, **e = prog->instructions.End(); + i != e; i++) { + SignalInstruction *insn = *i; + if (insn->Opcode() == PSO_IF) { + SignalIf* ifi = static_cast(insn); + if (ifi->condition->ConditionCode() == PSC_SIGNAL_STATE) { + SignalStateCondition* c = static_cast(ifi->condition); + if(c->sig_tile == by.tile && TrackdirToTrack(c->sig_track) == by.track) + c->Invalidate(); + } + } + } + + AddTrackToSignalBuffer(by.tile, by.track, GetTileOwner(by.tile)); + UpdateSignalsInBuffer(); +} + +void SignalProgram::DebugPrintProgram() +{ + DEBUG(misc, 5, "Program %p listing", this); + for (SignalInstruction **b = this->instructions.Begin(), **i = b, **e = this->instructions.End(); + i != e; i++) + { + SignalInstruction *insn = *i; + DEBUG(misc, 5, " %ld: Opcode %d, prev %d", long(i - b), int(insn->Opcode()), + int(insn->Previous() ? insn->Previous()->Id() : -1)); + } +} + +/** Insert a signal instruction into the signal program. + * + * @param tile The Tile on which to perform the operation + * @param p1 Flags and information + * - Bits 0-2 Which track the signal sits on + * - Bits 3-18 ID of instruction to insert before + * - Bits 19-26 Which opcode to create + * - Bits 27-31 Reserved + * @param p2 Depends upon instruction + * - PSO_SET_SIGNAL: + * - Colour to set the signal to + * @param text unused + */ +CommandCost CmdInsertSignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + Track track = Extract(p1); + uint instruction_id = GB(p1, 3, 16); + SignalOpcode op = Extract(p1); + + if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) { + return CMD_ERROR; + } + + if (!IsTileOwner(tile, _current_company)) + return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER); + + SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track)); + if (!prog) + return_cmd_error(STR_ERR_PROGSIG_NOT_THERE); + if (instruction_id > prog->instructions.Length()) + return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION); + + bool exec = (flags & DC_EXEC) != 0; + + SignalInstruction *insert_before = prog->instructions[instruction_id]; + switch (op) { + case PSO_IF: { + if (!exec) return CommandCost(); + SignalIf *if_ins = new SignalIf(prog); + if_ins->Insert(insert_before); + break; + } + + case PSO_SET_SIGNAL: { + SignalState ss = (SignalState) p2; + if (ss > SIGNAL_STATE_MAX) return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE); + if (!exec) return CommandCost(); + + SignalSet *set = new SignalSet(prog, ss); + set->Insert(insert_before); + break; + } + + case PSO_FIRST: + case PSO_LAST: + case PSO_IF_ELSE: + case PSO_IF_ENDIF: + default: + return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE); + } + + if (!exec) return CommandCost(); + AddTrackToSignalBuffer(tile, track, GetTileOwner(tile)); + UpdateSignalsInBuffer(); + InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track); + return CommandCost(); +} + +/** Modify a singal instruction + * + * @param tile The Tile on which to perform the operation + * @param p1 Flags and information + * - Bits 0-2 Which track the signal sits on + * - Bits 3-18 ID of instruction to insert before + * @param p2 Depends upon instruction + * - PSO_SET_SIGNAL: + * - Colour to set the signal to + * - PSO_IF: + * - Bit 0 If 0, set the condidion code: + * - Bit 1-8: Conditon code to change to + * - Otherwise, if SignalVariableCondition: + * - Bits 1-2: Which field to change (ConditionField) + * - Bits 3-31: Value to set field to + * - Otherwise, if SignalStateCondition: + * - Bits 1-4: Trackdir on which signal is located + * - Bits 5-31: Tile on which signal is located + * @param text unused + */ +CommandCost CmdModifySignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + Track track = Extract(p1); + uint instruction_id = GB(p1, 3, 16); + + if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) { + return CMD_ERROR; + } + + if (!IsTileOwner(tile, _current_company)) + return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER); + + SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track)); + if (!prog) + return_cmd_error(STR_ERR_PROGSIG_NOT_THERE); + + if (instruction_id > prog->instructions.Length()) + return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION); + + bool exec = (flags & DC_EXEC) != 0; + + SignalInstruction *insn = prog->instructions[instruction_id]; + switch (insn->Opcode()) { + case PSO_SET_SIGNAL: { + SignalState state = (SignalState) p2; + if (state > SIGNAL_STATE_MAX) + return_cmd_error(STR_ERR_PROGSIG_INVALID_SIGNAL_STATE); + if (!exec) + return CommandCost(); + SignalSet *ss = static_cast(insn); + ss->to_state = state; + } break; + + case PSO_IF: { + SignalIf *si = static_cast(insn); + byte act = GB(p2, 0, 1); + if (act == 0) { // Set code + SignalConditionCode code = (SignalConditionCode) GB(p2, 1, 8); + if (code > PSC_MAX) + return_cmd_error(STR_ERR_PROGSIG_INVALID_CONDITION); + if (!exec) return CommandCost(); + + SignalCondition *cond; + switch (code) { + case PSC_ALWAYS: + case PSC_NEVER: + cond = new SignalSimpleCondition(code); + break; + + case PSC_NUM_GREEN: + case PSC_NUM_RED: + cond = new SignalVariableCondition(code); + break; + + case PSC_SIGNAL_STATE: + cond = new SignalStateCondition(SignalReference(tile, track), INVALID_TILE, INVALID_TRACKDIR); + break; + + default: NOT_REACHED(); + } + si->SetCondition(cond); + } else { // modify condition + switch (si->condition->ConditionCode()) { + case PSC_ALWAYS: + case PSC_NEVER: + return CommandCost(STR_ERR_PROGSIG_INVALID_CONDITION_FIELD); + + case PSC_NUM_GREEN: + case PSC_NUM_RED: { + SignalVariableCondition *vc = static_cast(si->condition); + SignalConditionField f = (SignalConditionField) GB(p2, 1, 2); + uint32 val = GB(p2, 3, 27); + if (f == SCF_COMPARATOR) { + if (val > SGC_LAST) return_cmd_error(STR_ERR_PROGSIG_INVALID_COMPARATOR); + if (!exec) return CommandCost(); + vc->comparator = (SignalComparator) val; + } else if (f == SCF_VALUE) { + if (!exec) return CommandCost(); + vc->value = val; + } else CommandCost(STR_ERR_PROGSIG_INVALID_CONDITION_FIELD); + } break; + + case PSC_SIGNAL_STATE: { + SignalStateCondition *sc = static_cast(si->condition); + Trackdir td = (Trackdir) GB(p2, 1, 4); + TileIndex ti = (TileIndex) GB(p2, 5, 27); + + if (!IsValidTile(ti) || !IsValidTrackdir(td) || !HasSignalOnTrackdir(ti, td) + || GetTileOwner(ti) != _current_company) + return_cmd_error(STR_ERR_PROGSIG_INVALID_SIGNAL); + if (!exec) return CommandCost(); + sc->SetSignal(ti, td); + } break; + } + } + } break; + + case PSO_FIRST: + case PSO_LAST: + case PSO_IF_ELSE: + case PSO_IF_ENDIF: + default: + return CommandCost(STR_ERR_PROGSIG_INVALID_OPCODE); + } + + if (!exec) return CommandCost(); + + AddTrackToSignalBuffer(tile, track, GetTileOwner(tile)); + UpdateSignalsInBuffer(); + InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track); + return CommandCost(); +} + +/** Remove an instruction from a signal program + * + * @param tile The Tile on which to perform the operation + * @param p1 Flags and information + * - Bits 0-2 Which track the signal sits on + * - Bits 3-18 ID of instruction to insert before + * @param p2 unused + * @param text unused + */ +CommandCost CmdRemoveSignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + Track track = Extract(p1); + uint instruction_id = GB(p1, 3, 16); + + if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) { + return CMD_ERROR; + } + + if (!IsTileOwner(tile, _current_company)) + return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER); + + SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track)); + if (!prog) + return_cmd_error(STR_ERR_PROGSIG_NOT_THERE); + + if (instruction_id > prog->instructions.Length()) + return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION); + + bool exec = (flags & DC_EXEC) != 0; + + SignalInstruction *insn = prog->instructions[instruction_id]; + switch (insn->Opcode()) { + case PSO_SET_SIGNAL: + case PSO_IF: + if (!exec) return CommandCost(); + insn->Remove(); + break; + + case PSO_FIRST: + case PSO_LAST: + case PSO_IF_ELSE: + case PSO_IF_ENDIF: + default: + return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE); + } + + if (!exec) return CommandCost(); + AddTrackToSignalBuffer(tile, track, GetTileOwner(tile)); + UpdateSignalsInBuffer(); + InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track); + return CommandCost(); +} diff --git a/src/programmable_signals.h b/src/programmable_signals.h new file mode 100644 index 0000000000..d9bb94d4c1 --- /dev/null +++ b/src/programmable_signals.h @@ -0,0 +1,395 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file programmable_signals.h Programmable Signals */ + +#ifndef PROGRAMMABLE_SIGNALS_H +#define PROGRAMMABLE_SIGNALS_H +#include "rail_map.h" +#include "core/smallvec_type.hpp" +#include + +/** @defgroup progsigs Programmable Signals */ +///@{ + +/** The Programmable Signal virtual machine. + * + * This structure contains the state of the currently executing signal program. + */ +struct SignalVM; + +class SignalInstruction; +class SignalSpecial; +typedef SmallVector InstructionList; + +/** The actual programmable signal information */ +struct SignalProgram { + SignalProgram(TileIndex tile, Track track, bool raw = false); + ~SignalProgram(); + void DebugPrintProgram(); + + TileIndex tile; + Track track; + + SignalSpecial *first_instruction; + SignalSpecial *last_instruction; + InstructionList instructions; +}; + +/** Programmable Signal opcode. + * + * Opcode types are discriminated by this enumeration. It is primarily used for + * code which must be able to inspect the type of a signal operation, rather than + * evaluate it (such as the programming GUI) + */ +enum SignalOpcode { + PSO_FIRST = 0, ///< Start pseudo instruction + PSO_LAST = 1, ///< End pseudo instruction + PSO_IF = 2, ///< If instruction + PSO_IF_ELSE = 3, ///< If Else pseudo instruction + PSO_IF_ENDIF = 4, ///< If Endif pseudo instruction + PSO_SET_SIGNAL = 5, ///< Set signal instruction + + PSO_END, + PSO_INVALID = 0xFF +}; +template <> struct EnumPropsT : MakeEnumPropsT {}; + +/** Signal instruction base class. All instructions must derive from this. */ +class SignalInstruction { +public: + /// Get the instruction's opcode + inline SignalOpcode Opcode() const { return this->opcode; } + + /// Get the previous instruction. If this is NULL, then this is the first + /// instruction. + inline SignalInstruction *Previous() const { return this->previous; } + + /// Get the Id of this instruction + inline int Id() const + // Const cast is safe (perculiarity of SmallVector) + { return program->instructions.FindIndex(const_cast(this)); } + + /// Insert this instruction, placing it before @p before_insn + virtual void Insert(SignalInstruction *before_insn); + + /// Evaluate the instruction. The instruction should update the VM state. + virtual void Evaluate(SignalVM &vm) = 0; + + /// Remove the instruction. When removing itself, an instruction should + ///
    + ///
  • Set next->previous to previous + ///
  • Set previous->next to next + ///
  • Destroy any other children + ///
+ virtual void Remove() = 0; + + /// Gets a reference to the previous member. This is only intended for use by + /// the saveload code. + inline SignalInstruction *&GetPrevHandle() + { return previous; } + + /// Sets the previous instruction of this instruction. This is only intended + /// to be used by instructions to update links during insertion and removal. + inline void SetPrevious(SignalInstruction *prev) + { previous = prev; } + /// Set the next instruction. This is only intended to be used by instructions + /// to update links during insertion and removal + virtual void SetNext(SignalInstruction *next_insn) = 0; + +protected: + /// Constructs an instruction + /// @param prog the program to add this instruction to + /// @param op the opcode of the instruction + SignalInstruction(SignalProgram *prog, SignalOpcode op) ; + virtual ~SignalInstruction(); + + const SignalOpcode opcode; + SignalInstruction *previous; + SignalProgram *program; +}; + +/** Programmable Signal condition code. + * + * These discriminate conditions in much the same way that SignalOpcode + * discriminates instructions. + */ +enum SignalConditionCode { + PSC_ALWAYS = 0, ///< Always true + PSC_NEVER = 1, ///< Always false + PSC_NUM_GREEN = 2, ///< Number of green signals behind this signal + PSC_NUM_RED = 3, ///< Number of red signals behind this signal + PSC_SIGNAL_STATE = 4, ///< State of another signal + + PSC_MAX = PSC_SIGNAL_STATE +}; + +class SignalCondition { +public: + /// Get the condition's code + inline SignalConditionCode ConditionCode() const { return this->cond_code; } + + /// Evaluate the condition + virtual bool Evaluate(SignalVM& vm) = 0; + + /// Destroy the condition. Any children should also be destroyed + virtual ~SignalCondition(); + +protected: + SignalCondition(SignalConditionCode code) : cond_code(code) {} + + const SignalConditionCode cond_code; +}; + +// -- Condition codes -- +/** Simple condition code. These conditions have no complex inputs, and can be + * evaluated directly from VM state and their condition code. + */ +class SignalSimpleCondition: public SignalCondition { +public: + SignalSimpleCondition(SignalConditionCode code); + virtual bool Evaluate(SignalVM& vm); +}; + +/** Comparator to use for variable conditions. */ +enum SignalComparator { + SGC_EQUALS = 0, ///< the variable is equal to the specified value + SGC_NOT_EQUALS = 1, ///< the variable is not equal to the specified value + SGC_LESS_THAN = 2, ///< the variable is less than specified value + SGC_LESS_THAN_EQUALS = 3, ///< the variable is less than or equal to the specified value + SGC_MORE_THAN = 4, ///< the variable is greater than the specified value + SGC_MORE_THAN_EQUALS = 5, ///< the variable is grater than or equal to the specified value + SGC_IS_TRUE = 6, ///< the variable is true (non-zero) + SGC_IS_FALSE = 7, ///< the variable is false (zero) + + SGC_LAST = SGC_IS_FALSE +}; + +/** Which field to modify in a condition. A parameter to CMD_MODIFY_SIGNAL_INSTRUCTION */ +enum SignalConditionField { + SCF_COMPARATOR = 0, ///< the comparator (value from SignalComparator enum) + SCF_VALUE = 1, ///< the value (integer value) +}; + +/** A conditon based upon comparing a variable and a value. This condition can be + * considered similar to the conditonal jumps in vehicle orders. + * + * The variable is specified by the conditon code, the comparison by @p comparator, and + * the value to compare against by @p value. The condition returns the result of that value. + */ +class SignalVariableCondition: public SignalCondition { +public: + /// Constructs a condition refering to the value @p code refers to. Sets the + /// comparator and value to sane defaults. + SignalVariableCondition(SignalConditionCode code); + + SignalComparator comparator; + uint32 value; + + /// Evaluates the condition + virtual bool Evaluate(SignalVM &vm); +}; + +/** A condition which is based upon the state of another signal. */ +class SignalStateCondition: public SignalCondition { + public: + SignalStateCondition(SignalReference this_sig, TileIndex sig_tile, Trackdir sig_track); + + void SetSignal(TileIndex tile, Trackdir track); + bool IsSignalValid(); + void Invalidate(); + + virtual bool Evaluate(SignalVM& vm); + virtual ~SignalStateCondition(); + + SignalReference this_sig; + TileIndex sig_tile; + Trackdir sig_track; + SignalState state; +}; + +// -- Instructions + +/** The special start and end pseudo instructions. + * + * These instructions serve two purposes: + *
    + *
  1. 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)
  2. + *
  3. 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
  4. + *
+ */ +class SignalSpecial: public SignalInstruction { +public: + /** Constructs a special signal of the opcode @p op in program @p prog. + * + * Generally you should not need to call this; it will be called by the + * program's constructor. An exception is in the saveload code, which needs + * to construct raw objects to deserialize into + */ + SignalSpecial(SignalProgram *prog, SignalOpcode op); + + /** Evaluates the instruction. If this is an Start instruction, flow will be + * vectored to the first instruction; if it is an End instruction, the program + * will terminate and the signal will be left red. + */ + virtual void Evaluate(SignalVM &vm); + + /** Links the first and last instructions in the program. Generally only to be + * called from the SignalProgram constructor. + */ + static void link(SignalSpecial *first, SignalSpecial *last); + + /** Removes this instruction. If this is the start instruction, then all of + * the other instructions in the program will be successively removed, + * (emptying it). If this is the End instruction, then it will do nothing. + * + * This operation, unlike when executed on most instructions, does not destroy + * the instruction. + */ + virtual void Remove(); + + /** The next instruction after this one. On the End instruction, this should + * be NULL. + */ + SignalInstruction *next; + + virtual void SetNext(SignalInstruction *next_insn); +}; + +/** If signal instruction. This is perhaps the most important, as without it, + * programmable signals are pretty useless. + * + * It's also the most complex! + */ +class SignalIf: public SignalInstruction { +public: + /** The If-Else and If-Endif pseudo instructions. The Else instruction + * follows the Then block, and the Endif instruction follows the Else block. + * + * These serve two purposes: + *
    + *
  • They correctly vector the execution to after the if block + * (if needed) + *
  • They provide an instruction for the GUI to insert other instructions + * before. + *
+ */ + class PseudoInstruction: public SignalInstruction { + public: + /** Normal constructor. The pseudo instruction will be constructed as + * belonging to @p block. + */ + PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op); + + /** Constructs an empty instruction of type @p op. This should only be used + * by the saveload code during deserialization. The instruction must have + * its block field set correctly before the program is run. + */ + PseudoInstruction(SignalProgram *prog, SignalOpcode op); + + /** Removes the pseudo instruction. Unless you are also removing the If it + * belongs to, this is nonsense and dangerous. + */ + virtual void Remove(); + + /** Evaluate the pseudo instruction. This involves vectoring execution to + * the instruction after the if. + */ + virtual void Evaluate(SignalVM &vm); + + /** The block to which this instruction belongs */ + SignalIf *block; + virtual void SetNext(SignalInstruction *next_insn); + }; + +public: + /** Constructs an If instruction belonging to program @p prog. If @p raw is + * true, then the instruction is constructed raw (in order for the + * deserializer to be able to correctly deserialize the instruction). + */ + SignalIf(SignalProgram *prog, bool raw = false); + + /** Sets the instruction's condition, and releases the old condition */ + void SetCondition(SignalCondition *cond); + + /** Evaluates the If and takes the appropriate branch */ + virtual void Evaluate(SignalVM &vm); + + virtual void Insert(SignalInstruction *before_insn); + + /** Removes the If and all of its children */ + virtual void Remove(); + + SignalCondition *condition; ///< The if conditon + SignalInstruction *if_true; ///< The branch to take if true + SignalInstruction *if_false; ///< The branch to take if false + SignalInstruction *after; ///< The branch to take after the If + + virtual void SetNext(SignalInstruction *next_insn); +}; + +/** Set signal instruction. This sets the state of the signal and terminates execution */ +class SignalSet: public SignalInstruction { +public: + /// Constructs the instruction and sets the state the signal is to be set to + SignalSet(SignalProgram *prog, SignalState = SIGNAL_STATE_RED); + + virtual void Evaluate(SignalVM &vm); + virtual void Remove(); + + /// The state to set the signal to + SignalState to_state; + + /// The instruction following this one (for the editor) + SignalInstruction *next; + + virtual void SetNext(SignalInstruction *next_insn); +}; + +/// The map type used for looking up signal programs +typedef std::map ProgramList; + +/// The global signal program list +extern ProgramList _signal_programs; + +/// Verifies that a SignalReference refers to a signal which has a program. +static inline bool HasProgrammableSignals(SignalReference ref) +{ + return IsTileType(ref.tile, MP_RAILWAY) && GetRailTileType(ref.tile) == RAIL_TILE_SIGNALS + && IsPresignalProgrammable(ref.tile, ref.track); +} + +/// Shows the programming window for the signal identified by @p tile and +/// @p track. +void ShowSignalProgramWindow(SignalReference ref); + +/// Gets the signal program for the tile identified by @p t and @p track. +/// An empty program will be constructed if none is specified +SignalProgram *GetSignalProgram(SignalReference ref); + +SignalProgram *GetExistingSignalProgram(SignalReference ref); + +/// Frees a signal program by tile and track +void FreeSignalProgram(SignalReference ref); + +/// Frees all signal programs (For use when creating a new game) +void FreeSignalPrograms(); + +/// Runs the signal program, specifying the following parameters. +SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green); + +/// Remove dependencies on signal @p on from @p by +void RemoveProgramDependencies(SignalReference by, SignalReference on); +///@} + +#endif diff --git a/src/programmable_signals_gui.cpp b/src/programmable_signals_gui.cpp new file mode 100644 index 0000000000..ed4c0049f7 --- /dev/null +++ b/src/programmable_signals_gui.cpp @@ -0,0 +1,904 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file programmable_signals_gui.cpp GUI related to programming signals */ + +#include "stdafx.h" +#include "programmable_signals.h" +#include "command_func.h" +#include "window_func.h" +#include "strings_func.h" +#include "string_func.h" +#include "viewport_func.h" +#include "textbuf_gui.h" +#include "company_func.h" +#include "widgets/dropdown_func.h" +#include "gui.h" +#include "gfx_func.h" +#include "tilehighlight_func.h" +#include "rail_map.h" +#include "tile_cmd.h" +#include "error.h" + +#include "table/sprites.h" +#include "table/strings.h" + +enum ProgramWindowWidgets { + PROGRAM_WIDGET_CAPTION, + PROGRAM_WIDGET_INSTRUCTION_LIST, + PROGRAM_WIDGET_SCROLLBAR, + + PROGRAM_WIDGET_SEL_TOP_LEFT, + PROGRAM_WIDGET_SEL_TOP_MIDDLE, + PROGRAM_WIDGET_SEL_TOP_RIGHT, + + PROGRAM_WIDGET_SET_STATE, + PROGRAM_WIDGET_COND_VARIABLE, + PROGRAM_WIDGET_COND_COMPARATOR, + PROGRAM_WIDGET_COND_VALUE, + PROGRAM_WIDGET_COND_GOTO_SIGNAL, + PROGRAM_WIDGET_COND_SET_SIGNAL, + + PROGRAM_WIDGET_GOTO_SIGNAL, + PROGRAM_WIDGET_INSERT, + PROGRAM_WIDGET_REMOVE, + + PROGRAM_WIDGET_REMOVE_PROGRAM, + PROGRAM_WIDGET_COPY_PROGRAM, +}; + +enum PanelWidgets { + // Left + DPL_COND_VARIABLE = 0, + DPL_SET_STATE, + + // Middle + DPM_COND_COMPARATOR = 0, + DPM_COND_GOTO_SIGNAL, + + // Right + DPR_COND_VALUE = 0, + DPR_COND_SET_SIGNAL +}; + +static const StringID _program_insert[] = { + STR_PROGSIG_INSERT_IF, + STR_PROGSIG_INSERT_SET_SIGNAL, + INVALID_STRING_ID +}; + +static SignalOpcode OpcodeForIndex(int index) +{ + switch (index) { + case 0: return PSO_IF; + case 1: return PSO_SET_SIGNAL; + default: NOT_REACHED(); + } +} + +static bool IsConditionComparator(SignalCondition *cond) +{ + switch (cond->ConditionCode()) { + case PSC_NUM_GREEN: + case PSC_NUM_RED: + return true; + + default: + return false; + } +} + +static const StringID _program_condvar[] = { + /* PSC_ALWAYS */ STR_PROGSIG_COND_ALWAYS, + /* PSC_NEVER */ STR_PROGSIG_COND_NEVER, + /* PSC_NUM_GREEN */ STR_PROGSIG_CONDVAR_NUM_GREEN, + /* PSC_NUM_RED */ STR_PROGSIG_CONDVAR_NUM_RED, + /* PSC_SIGNAL_STATE*/ STR_PROGSIG_COND_SIGNAL_STATE, + INVALID_STRING_ID +}; + +// TODO: These should probably lose the ORDER +static const StringID _program_comparator[] = { + /* SGC_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_EQUALS, + /* SGC_NOT_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_NOT_EQUALS, + /* SGC_LESS_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_THAN, + /* SGC_LESS_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_EQUALS, + /* SGC_MORE_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_THAN, + /* SGC_MORE_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_EQUALS, + /* SGC_IS_TRUE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_TRUE, + /* SGC_IS_FALSE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_FALSE, + INVALID_STRING_ID +}; + +static const StringID _program_sigstate[] = { + STR_COLOUR_RED, + STR_COLOUR_GREEN, + INVALID_STRING_ID +}; + +/** Get the string for a condition */ +static char *GetConditionString(SignalCondition *cond, char *buf, char *buflast, bool selected) +{ + StringID string = INVALID_STRING_ID; + bool comparator = IsConditionComparator(cond); + + if (comparator) { + SignalVariableCondition *cv = static_cast(cond); + string = STR_PROGSIG_COND_COMPARE; + SetDParam(0, _program_condvar[cond->ConditionCode()]); + SetDParam(1, _program_comparator[cv->comparator]); + SetDParam(2, cv->value); + } else { + string = _program_condvar[cond->ConditionCode()]; + if (cond->ConditionCode() == PSC_SIGNAL_STATE) { + string = STR_PROGSIG_CONDVAR_SIGNAL_STATE; + SetDParam(0, static_cast(cond)->IsSignalValid() + ? STR_PROGSIG_CONDVAR_SIGNAL_STATE_SPECIFIED : STR_PROGSIG_CONDVAR_SIGNAL_STATE_UNSPECIFIED); + SetDParam(1, selected ? STR_WHITE : STR_BLACK); + } + } + return GetString(buf, string, buflast); +} + +/** + * Draws an instruction in the programming GUI + * @param instruction The instruction to draw + * @param y Y position for drawing + * @param selected True, if the order is selected + * @param indent How many levels the instruction is indented + * @param left Left border for text drawing + * @param right Right border for text drawing + */ +static void DrawInstructionString(SignalInstruction *instruction, int y, bool selected, int indent, int left, int right) +{ + StringID instruction_string = INVALID_STRING_ID; + + char condstr[512]; + + switch (instruction->Opcode()) { + case PSO_FIRST: + instruction_string = STR_PROGSIG_FIRST; + break; + + case PSO_LAST: + instruction_string = STR_PROGSIG_LAST; + break; + + case PSO_IF: { + SignalIf *if_ins = static_cast(instruction); + GetConditionString(if_ins->condition, condstr, lastof(condstr), selected); + SetDParamStr(0, condstr); + instruction_string = STR_PROGSIG_IF; + break; + } + + case PSO_IF_ELSE: + instruction_string = STR_PROGSIG_ELSE; + break; + + case PSO_IF_ENDIF: + instruction_string = STR_PROGSIG_ENDIF; + break; + + case PSO_SET_SIGNAL: { + instruction_string = STR_PROGSIG_SET_SIGNAL; + SignalSet *set = static_cast(instruction); + SetDParam(0, _program_sigstate[set->to_state]); + break; + } + + default: NOT_REACHED(); + } + + DrawString(left + indent * 16, right, y, instruction_string, selected ? TC_WHITE : TC_BLACK); +} + +struct GuiInstruction { + SignalInstruction *insn; + uint indent; +}; + +typedef SmallVector GuiInstructionList; + +class ProgramWindow: public Window { +public: + ProgramWindow(WindowDesc *desc, SignalReference ref): Window(desc) + { + // this->InitNested(desc, (ref.tile << 3) | ref.track); + this->tile = ref.tile; + this->track = ref.track; + this->selected_instruction = -1; + + this->CreateNestedTree(desc); + this->vscroll = this->GetScrollbar(PROGRAM_WIDGET_SCROLLBAR); + this->FinishInitNested((ref.tile << 3) | ref.track); + + program = GetSignalProgram(ref); + RebuildInstructionList(); + } + + virtual void OnClick(Point pt, int widget, int click_count) OVERRIDE + { + switch (widget) { + case PROGRAM_WIDGET_INSTRUCTION_LIST: { + int sel = this->GetInstructionFromPt(pt.y); + + this->DeleteChildWindows(); + HideDropDownMenu(this); + + if (sel == -1 || this->GetOwner() != _local_company) { + // Deselect + this->selected_instruction = -1; + } else { + this->selected_instruction = sel; + } + + this->UpdateButtonState(); + } break; + + case PROGRAM_WIDGET_INSERT: { + DEBUG(misc, 5, "Selection is %d", this->selected_instruction); + if (this->GetOwner() != _local_company || this->selected_instruction < 1) + return; + ShowDropDownMenu(this, _program_insert, -1, PROGRAM_WIDGET_INSERT, 0, 0, 0); + } break; + + case PROGRAM_WIDGET_REMOVE: { + SignalInstruction *ins = GetSelected(); + if (this->GetOwner() != _local_company || !ins) + return; + + uint32 p1 = 0; + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, ins->Id()); + + DoCommandP(this->tile, p1, 0, CMD_REMOVE_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); + this->RebuildInstructionList(); + } break; + + case PROGRAM_WIDGET_SET_STATE: { + SignalInstruction *si = this->GetSelected(); + if (!si || si->Opcode() != PSO_SET_SIGNAL) return; + SignalSet *ss = static_cast (si); + + ShowDropDownMenu(this, _program_sigstate, ss->to_state, PROGRAM_WIDGET_SET_STATE, 0, 0, 0); + } break; + + case PROGRAM_WIDGET_COND_VARIABLE: { + SignalInstruction *si = this->GetSelected(); + if (!si || si->Opcode() != PSO_IF) return; + SignalIf *sif = static_cast (si); + + ShowDropDownMenu(this, _program_condvar, sif->condition->ConditionCode(), PROGRAM_WIDGET_COND_VARIABLE, 0, 0, 0); + this->UpdateButtonState(); + } break; + + case PROGRAM_WIDGET_COND_COMPARATOR: { + SignalInstruction *si = this->GetSelected(); + if (!si || si->Opcode() != PSO_IF) return; + SignalIf *sif = static_cast (si); + if (!IsConditionComparator(sif->condition)) return; + SignalVariableCondition *vc = static_cast(sif->condition); + + ShowDropDownMenu(this, _program_comparator, vc->comparator, PROGRAM_WIDGET_COND_COMPARATOR, 0, 0, 0); + } break; + + case PROGRAM_WIDGET_COND_VALUE: { + SignalInstruction *si = this->GetSelected(); + if (!si || si->Opcode() != PSO_IF) return; + SignalIf *sif = static_cast (si); + if (!IsConditionComparator(sif->condition)) return; + SignalVariableCondition *vc = static_cast(sif->condition); + + SetDParam(0, vc->value); + //ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, 100, this, CS_NUMERAL, QSF_NONE); + ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, this, CS_NUMERAL, QSF_NONE); + this->UpdateButtonState(); + } break; + + case PROGRAM_WIDGET_COND_GOTO_SIGNAL: { + SignalInstruction *si = this->GetSelected(); + if (!si || si->Opcode() != PSO_IF) return; + SignalIf *sif = static_cast (si); + if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return; + SignalStateCondition *sc = static_cast(sif->condition); + + if (sc->IsSignalValid()) { + ScrollMainWindowToTile(sc->sig_tile); + } else { + ShowErrorMessage(STR_ERROR_CAN_T_GOTO_UNDEFINED_SIGNAL, STR_EMPTY, WL_INFO); + } + // this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); + } break; + + case PROGRAM_WIDGET_COND_SET_SIGNAL: { + this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COND_SET_SIGNAL); + this->SetWidgetDirty(PROGRAM_WIDGET_COND_SET_SIGNAL); + if (this->IsWidgetLowered(PROGRAM_WIDGET_COND_SET_SIGNAL)) { + SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this); + } else { + ResetObjectToPlace(); + } + } break; + + case PROGRAM_WIDGET_GOTO_SIGNAL: { + ScrollMainWindowToTile(this->tile); + // this->RaiseWidget(PROGRAM_WIDGET_GOTO_SIGNAL); + } break; + case PROGRAM_WIDGET_REMOVE_PROGRAM: { + if (this->GetOwner() != _local_company) + return; + program->first_instruction->Remove(); + + this->RebuildInstructionList(); + } break; + case PROGRAM_WIDGET_COPY_PROGRAM: { + + this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COPY_PROGRAM); + this->SetWidgetDirty(PROGRAM_WIDGET_COPY_PROGRAM); + if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) { + SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this); + } else { + ResetObjectToPlace(); + } + } break; + } + } + + void InsertInstruction(SignalInstruction *si, uint32 next) + { + uint64 p1 = 0; + while(true) { + switch(si->Opcode()) { + case PSO_SET_SIGNAL: { + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, next); + SB(p1, 19, 8, si->Opcode()); + + DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION)); + this->RebuildInstructionList(); + si = ((SignalSet*)si)->next; + } break; + case PSO_IF: { + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, next); + SB(p1, 19, 8, si->Opcode()); + + DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION)); + this->RebuildInstructionList(); + + SignalInstruction *s = ((SignalIf*)si)->if_true; + while(s->Opcode() != PSO_IF_ELSE) { + if(s->Opcode() == PSO_IF) s = ((SignalIf*)s)->after; + if(s->Opcode() == PSO_SET_SIGNAL) s = ((SignalSet*)s)->next; + else break; + } + InsertInstruction(((SignalIf*)si)->if_true, s->Id()); + this->RebuildInstructionList(); + + s = ((SignalIf*)si)->if_false; + while(s->Opcode() != PSO_IF_ENDIF) { + if(s->Opcode() == PSO_IF) s = ((SignalIf*)s)->after; + if(s->Opcode() == PSO_SET_SIGNAL) s = ((SignalSet*)s)->next; + else break; + } + InsertInstruction(((SignalIf*)si)->if_false, s->Id()); + this->RebuildInstructionList(); + + si = ((SignalIf*)si)->after; + } break; + } + if(si == NULL) break; + if(si->Opcode() == PSO_LAST) break; + if(si->Opcode() == PSO_IF_ELSE) break; + if(si->Opcode() == PSO_IF_ENDIF) break; + } + } + + virtual void OnPlaceObject(Point pt, TileIndex tile1) OVERRIDE + { + if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) { + //Copy program from another progsignal + TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile1, TRANSPORT_RAIL, 0)); + if (trackbits & TRACK_BIT_VERT) { // N-S direction + trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT; + } + if (trackbits & TRACK_BIT_HORZ) { // E-W direction + trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER; + } + Track track1 = FindFirstTrack(trackbits); + if(track1 == INVALID_TRACK) { + return; + } + Trackdir td = TrackToTrackdir(track1); + Trackdir tdr = ReverseTrackdir(td); + if (!(HasSignalOnTrackdir(tile1, td) || HasSignalOnTrackdir(tile1, tdr))) + return; + + if (GetSignalType(tile1, track1) != SIGTYPE_PROG) { + ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_PROG_SIGNAL, WL_INFO); + return; + } + if(this->tile == tile1 && this->track == track1) { + ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CANNOT_USE_SELF, WL_INFO); + return; + } + + SignalProgram *sp = GetExistingSignalProgram(SignalReference(tile1, track1)); + if (!sp) { + ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO); + return; + } + program->first_instruction->Remove(); + this->RebuildInstructionList(); + + SignalInstruction *si = ((SignalSpecial*)sp->first_instruction)->next; + InsertInstruction(si, program->last_instruction->Id()); + ResetObjectToPlace(); + this->RaiseWidget(PROGRAM_WIDGET_COPY_PROGRAM); + //OnPaint(); // this appears to cause visual artefacts + return; + } + + SignalInstruction *si = this->GetSelected(); + if (!si || si->Opcode() != PSO_IF) return; + SignalIf *sif = static_cast (si); + if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return; + + if (!IsPlainRailTile(tile1)) { + return; + } + + TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile1, TRANSPORT_RAIL, 0)); + if (trackbits & TRACK_BIT_VERT) { // N-S direction + trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT; + } + + if (trackbits & TRACK_BIT_HORZ) { // E-W direction + trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER; + } + Track track1 = FindFirstTrack(trackbits); + if(track1 == INVALID_TRACK) { + return; + } + + Trackdir td = TrackToTrackdir(track1); + Trackdir tdr = ReverseTrackdir(td); + + if (HasSignalOnTrackdir(tile1, td) && HasSignalOnTrackdir(tile1, tdr)) { + ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CAN_T_DEPEND_UPON_BIDIRECTIONAL_SIGNALS, WL_INFO); + return; + } else if (HasSignalOnTrackdir(tile1, tdr) && !HasSignalOnTrackdir(tile1, td)) { + td = tdr; + } + + if (!HasSignalOnTrackdir(tile1, td)) { + return; + } + + //!!!!!!!!!!!!!!! + if (!(GetSignalType(tile1, track1) == SIGTYPE_EXIT || GetSignalType(tile1, track1) == SIGTYPE_PROG)) { + //!!!!!!!!!!!!!!! + ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO); + return; + } + + uint32 p1 = 0, p2 = 0; + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, si->Id()); + + SB(p2, 0, 1, 1); + SB(p2, 1, 4, td); + SB(p2, 5, 27, tile1); + + DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); + ResetObjectToPlace(); + this->RaiseWidget(PROGRAM_WIDGET_COND_SET_SIGNAL); + //OnPaint(); // this appears to cause visual artefacts + } + + virtual void OnQueryTextFinished(char *str) OVERRIDE + { + if (!StrEmpty(str)) { + SignalInstruction *si = this->GetSelected(); + if (!si || si->Opcode() != PSO_IF) return; + SignalIf *sif = static_cast (si); + if (!IsConditionComparator(sif->condition)) return; + + uint value = atoi(str); + + uint32 p1 = 0, p2 = 0; + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, si->Id()); + + SB(p2, 0, 1, 1); + SB(p2, 1, 2, SCF_VALUE); + SB(p2, 3, 27, value); + + DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); + } + } + + virtual void OnDropdownSelect(int widget, int index) OVERRIDE + { + SignalInstruction *ins = this->GetSelected(); + if (!ins) return; + + switch (widget) { + case PROGRAM_WIDGET_INSERT: { + uint64 p1 = 0; + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, ins->Id()); + SB(p1, 19, 8, OpcodeForIndex(index)); + + DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION)); + this->RebuildInstructionList(); + break; + } + + case PROGRAM_WIDGET_SET_STATE: { + uint64 p1 = 0; + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, ins->Id()); + + DoCommandP(this->tile, p1, index, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); + break; + } + + case PROGRAM_WIDGET_COND_VARIABLE: { + uint64 p1 = 0, p2 = 0; + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, ins->Id()); + + SB(p2, 0, 1, 0); + SB(p2, 1, 8, index); + + DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); + break; + } + + case PROGRAM_WIDGET_COND_COMPARATOR: { + uint64 p1 = 0, p2 = 0; + SB(p1, 0, 3, this->track); + SB(p1, 3, 16, ins->Id()); + + SB(p2, 0, 1, 1); + SB(p2, 1, 2, SCF_COMPARATOR); + SB(p2, 3, 27, index); + + DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION)); + break; + } + } + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) OVERRIDE + { + switch (widget) { + case PROGRAM_WIDGET_INSTRUCTION_LIST: + resize->height = FONT_HEIGHT_NORMAL; + size->height = 6 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; + break; + } + } + + virtual void OnResize() OVERRIDE + { + /* Update the scroll bar */ + this->vscroll->SetCapacityFromWidget(this, PROGRAM_WIDGET_INSTRUCTION_LIST); + } + + virtual void OnPaint() OVERRIDE + { + this->DrawWidgets(); + } + + virtual void DrawWidget(const Rect &r, int widget) const OVERRIDE + { + if (widget != PROGRAM_WIDGET_INSTRUCTION_LIST) return; + + int y = r.top + WD_FRAMERECT_TOP; + int line_height = this->GetWidget(PROGRAM_WIDGET_INSTRUCTION_LIST)->resize_y; + + int no = this->vscroll->GetPosition(); + + for (const GuiInstruction *i = instructions.Begin() + no, *e = instructions.End(); + i != e; ++i, no++) { + /* Don't draw anything if it extends past the end of the window. */ + if (!this->vscroll->IsVisible(no)) break; + + DrawInstructionString(i->insn, y, no == this->selected_instruction, i->indent, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT); + y += line_height; + } + } + + virtual void OnInvalidateData(int data, bool gui_scope) OVERRIDE { + if (gui_scope) { + this->RebuildInstructionList(); + } + } + + + virtual void SetStringParameters(int widget) const OVERRIDE + { + switch (widget) { + case PROGRAM_WIDGET_COND_VALUE: { + SetDParam(0, 0); + SignalInstruction *insn = this->GetSelected(); + if (!insn || insn->Opcode() != PSO_IF) return; + SignalIf *si = static_cast(insn); + if (!IsConditionComparator(si->condition)) return; + SignalVariableCondition *vc = static_cast(si->condition); + SetDParam(0, vc->value); + } break; + } + } + +private: + SignalInstruction *GetSelected() const + { + if (this->selected_instruction == -1 + || this->selected_instruction >= int(this->instructions.Length())) + return NULL; + + return this->instructions[this->selected_instruction].insn; + } + + Owner GetOwner() + { + return GetTileOwner(this->tile); + } + + int GetInstructionFromPt(int y) + { + NWidgetBase *nwid = this->GetWidget(PROGRAM_WIDGET_INSTRUCTION_LIST); + int sel = (y - nwid->pos_y - WD_FRAMERECT_TOP) / nwid->resize_y; // Selected line + + if ((uint)sel >= this->vscroll->GetCapacity()) return -1; + + sel += this->vscroll->GetPosition(); + + return (sel <= int(this->instructions.Length()) && sel >= 0) ? sel : -1; + } + + void RebuildInstructionList() + { + uint old_len = this->instructions.Length(); + this->instructions.Clear(); + SignalInstruction *insn = program->first_instruction; + uint indent = 0; + + do { + DEBUG(misc, 5, "PSig Gui: Opcode %d", insn->Opcode()); + switch (insn->Opcode()) { + case PSO_FIRST: + case PSO_LAST: { + SignalSpecial *s = static_cast(insn); + GuiInstruction *gi = this->instructions.Append(); + gi->insn = s; + gi->indent = indent; + insn = s->next; + break; + } + + case PSO_IF: { + SignalIf *i = static_cast(insn); + GuiInstruction *gi = this->instructions.Append(); + gi->insn = i; + gi->indent = indent++; + insn = i->if_true; + break; + } + + case PSO_IF_ELSE: { + SignalIf::PseudoInstruction *p = static_cast(insn); + GuiInstruction *gi = this->instructions.Append(); + gi->insn = p; + gi->indent = indent - 1; + insn = p->block->if_false; + break; + } + + case PSO_IF_ENDIF: { + SignalIf::PseudoInstruction *p = static_cast(insn); + GuiInstruction *gi = this->instructions.Append(); + gi->insn = p; + gi->indent = --indent; + insn = p->block->after; + break; + } + + case PSO_SET_SIGNAL: { + SignalSet *s = static_cast(insn); + GuiInstruction *gi = this->instructions.Append(); + gi->insn = s; + gi->indent = indent; + insn = s->next; + break; + } + + default: NOT_REACHED(); + } + } while (insn); + + this->vscroll->SetCount(this->instructions.Length()); + if (this->instructions.Length() != old_len) + selected_instruction = -1; + UpdateButtonState(); + } + + void UpdateButtonState() + { + // Do not close the Signals GUI when opening the ProgrammableSignals GUI + // ResetObjectToPlace(); + this->RaiseWidget(PROGRAM_WIDGET_INSERT); + this->RaiseWidget(PROGRAM_WIDGET_REMOVE); + this->RaiseWidget(PROGRAM_WIDGET_SET_STATE); + this->RaiseWidget(PROGRAM_WIDGET_COND_VARIABLE); + this->RaiseWidget(PROGRAM_WIDGET_COND_COMPARATOR); + this->RaiseWidget(PROGRAM_WIDGET_COND_VALUE); + this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); + + NWidgetStacked *left_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_LEFT); + NWidgetStacked *middle_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_MIDDLE); + NWidgetStacked *right_sel = this->GetWidget(PROGRAM_WIDGET_SEL_TOP_RIGHT); + + // Disable all the modifier buttons - we will re-enable them if applicable + this->DisableWidget(PROGRAM_WIDGET_SET_STATE); + this->DisableWidget(PROGRAM_WIDGET_COND_VARIABLE); + this->DisableWidget(PROGRAM_WIDGET_COND_COMPARATOR); + this->DisableWidget(PROGRAM_WIDGET_COND_VALUE); + this->DisableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL); + this->DisableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); + + // Don't allow modifications if don't own, or have selected invalid instruction + if (this->GetOwner() != _local_company || this->selected_instruction < 1) { + this->DisableWidget(PROGRAM_WIDGET_INSERT); + this->DisableWidget(PROGRAM_WIDGET_REMOVE); + this->SetDirty(); + return; + } else { + this->EnableWidget(PROGRAM_WIDGET_INSERT); + this->EnableWidget(PROGRAM_WIDGET_REMOVE); + } + + SignalInstruction *insn = GetSelected(); + if (!insn) return; + + switch (insn->Opcode()) { + case PSO_IF: { + SignalIf *i = static_cast(insn); + left_sel->SetDisplayedPlane(DPL_COND_VARIABLE); + middle_sel->SetDisplayedPlane(DPM_COND_COMPARATOR); + right_sel->SetDisplayedPlane(DPR_COND_VALUE); + + this->EnableWidget(PROGRAM_WIDGET_COND_VARIABLE); + this->GetWidget(PROGRAM_WIDGET_COND_VARIABLE)->widget_data = + _program_condvar[i->condition->ConditionCode()]; + + if (IsConditionComparator(i->condition)) { + SignalVariableCondition *vc = static_cast(i->condition); + this->EnableWidget(PROGRAM_WIDGET_COND_COMPARATOR); + this->EnableWidget(PROGRAM_WIDGET_COND_VALUE); + + this->GetWidget(PROGRAM_WIDGET_COND_COMPARATOR)->widget_data = + _program_comparator[vc->comparator]; + + } else if (i->condition->ConditionCode() == PSC_SIGNAL_STATE) { + this->EnableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL); + this->EnableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL); + middle_sel->SetDisplayedPlane(DPM_COND_GOTO_SIGNAL); + right_sel->SetDisplayedPlane(DPR_COND_SET_SIGNAL); + } + } break; + + case PSO_SET_SIGNAL: { + SignalSet *s = static_cast(insn); + left_sel->SetDisplayedPlane(DPL_SET_STATE); + this->SetWidgetDisabledState(PROGRAM_WIDGET_SET_STATE, false); + this->GetWidget(PROGRAM_WIDGET_SET_STATE)->widget_data = + _program_sigstate[s->to_state]; + } break; + + case PSO_FIRST: + case PSO_LAST: + case PSO_IF_ELSE: + case PSO_IF_ENDIF: + // All cannot be modified + this->DisableWidget(PROGRAM_WIDGET_REMOVE); + break; + + default: + NOT_REACHED(); + } + + this->SetDirty(); + } + + TileIndex tile; + Track track; + SignalProgram *program; + GuiInstructionList instructions; + int selected_instruction; + Scrollbar *vscroll; +}; + +static const NWidgetPart _nested_program_widgets[] = { + // Title bar + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, PROGRAM_WIDGET_CAPTION), SetDataTip(STR_PROGSIG_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + + // Program display + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, PROGRAM_WIDGET_INSTRUCTION_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_PROGSIG_CAPTION), SetResize(1, 1), EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, PROGRAM_WIDGET_SCROLLBAR), + EndContainer(), + + // Button Bar + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_LEFT), + NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_PROGSIG_COND_VARIABLE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_SET_STATE), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_PROGSIG_SIGNAL_STATE_TOOLTIP), SetResize(1, 0), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_MIDDLE), + NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_PROGSIG_COND_COMPARATOR_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_GOTO_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_PROGSIG_GOTO_SIGNAL, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP), SetResize(1, 0), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_RIGHT), + NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_BLACK_COMMA, STR_PROGSIG_COND_VALUE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_SET_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_PROGSIG_COND_SET_SIGNAL, STR_PROGSIG_COND_SET_SIGNAL_TOOLTIP), SetResize(1, 0), + EndContainer(), + EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_GREY, PROGRAM_WIDGET_GOTO_SIGNAL), SetMinimalSize(12, 12), SetDataTip(SPR_ARROW_RIGHT, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP), + EndContainer(), + + /* Second button row. */ + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_INSERT), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_PROGSIG_INSERT, STR_PROGSIG_INSERT_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE), SetMinimalSize(186, 12), SetFill(1, 0), + SetDataTip(STR_PROGSIG_REMOVE, STR_PROGSIG_REMOVE_TOOLTIP), SetResize(1, 0), + EndContainer(), + EndContainer(), + + /* Third button row*/ + NWidget(NWID_HORIZONTAL), + NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_REMOVE_PROGRAM, STR_PROGSIG_REMOVE_PROGRAM_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COPY_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_COPY_PROGRAM, STR_PROGSIG_COPY_PROGRAM_TOOLTIP), SetResize(1, 0), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +static WindowDesc _program_desc( + WDP_AUTO, "signal_program", 384, 100, + WC_SIGNAL_PROGRAM, WC_BUILD_SIGNAL, + WDF_CONSTRUCTION, + _nested_program_widgets, lengthof(_nested_program_widgets) +); + +void ShowSignalProgramWindow(SignalReference ref) +{ + uint32 window_id = (ref.tile << 3) | ref.track; + if (BringWindowToFrontById(WC_SIGNAL_PROGRAM, window_id) != NULL) return; + + new ProgramWindow(&_program_desc, ref); +} diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 35b7f1e3c3..1d40878c78 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -34,6 +34,7 @@ #include "company_gui.h" #include "object_map.h" #include "tracerestrict.h" +#include "programmable_signals.h" #include "table/strings.h" #include "table/railtypes.h" @@ -94,10 +95,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)) { @@ -643,6 +646,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)); } @@ -992,8 +996,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 @@ -1008,12 +1011,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(p1); // the signal type of the new signal bool convert_signal = HasBit(p1, 8); // convert button pressed - SignalType cycle_start = Extract(p1); - SignalType cycle_stop = Extract(p1); uint num_dir_cycle = GB(p1, 15, 2); - if (sigtype > SIGTYPE_LAST) return CMD_ERROR; - if (cycle_start > cycle_stop || cycle_stop > SIGTYPE_LAST) return CMD_ERROR; + uint which_signals = GB(p1, 9, 6); /* You can only build signals on plain rail tiles, and the selected track must exist */ if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || @@ -1099,6 +1099,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)) { @@ -1107,10 +1109,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)) { @@ -1128,6 +1132,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); } @@ -1260,7 +1266,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(p2); if (sigtype > SIGTYPE_LAST) return CMD_ERROR; byte signals; @@ -1450,6 +1456,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)); @@ -1782,6 +1789,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? */ @@ -1867,9 +1877,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)); } @@ -2709,14 +2724,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, @@ -2724,7 +2740,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, @@ -2732,7 +2749,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, @@ -2740,7 +2758,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, @@ -2748,7 +2767,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, @@ -2756,7 +2776,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 } }; diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 1776a0e118..d45313c5f0 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -35,6 +35,7 @@ #include "zoom_func.h" #include "rail_gui.h" #include "tracerestrict.h" +#include "programmable_signals.h" #include "station_map.h" #include "tunnelbridge_map.h" @@ -51,8 +52,10 @@ static byte _waypoint_count = 1; ///< Number of waypoint types static byte _cur_waypoint_type; ///< Currently selected waypoint type static bool _convert_signal_button; ///< convert signal button in the signal GUI pressed static bool _trace_restrict_button; ///< trace restrict button in the signal GUI pressed +static bool _program_signal_button; ///< program signal button in the signal GUI pressed static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI) static SignalType _cur_signal_type; ///< set the signal type (for signal GUI) +static uint _cur_signal_button; ///< set the signal button (for signal GUI) /* Map the setting: default_signal_type to the corresponding signal type */ static const SignalType _default_signal_type[] = {SIGTYPE_NORMAL, SIGTYPE_PBS, SIGTYPE_PBS_ONEWAY}; @@ -226,38 +229,51 @@ 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 if (_trace_restrict_button) { + return; + } + + if (_trace_restrict_button) { if (IsPlainRailTile(tile) && HasTrack(tile, track) && HasSignalOnTrack(tile, track)) { ShowTraceRestrictProgramWindow(tile, track); } - } 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); } /** @@ -1525,6 +1541,7 @@ public: { _convert_signal_button = false; _trace_restrict_button = false; + _program_signal_button = false; } virtual void OnInit() @@ -1571,7 +1588,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)]; @@ -1579,6 +1596,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) { @@ -1586,17 +1619,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. */ @@ -1604,17 +1640,30 @@ 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) _trace_restrict_button = false; + if (_convert_signal_button) { + _trace_restrict_button = false; + _program_signal_button = false; + } break; case WID_BS_TRACE_RESTRICT: _trace_restrict_button = !_trace_restrict_button; - if (_trace_restrict_button) _convert_signal_button = false; + if (_trace_restrict_button) { + _convert_signal_button = false; + _program_signal_button = false; + } + break; + + case WID_BS_PROGRAM: + _program_signal_button = !_program_signal_button; + if(_program_signal_button) { + _trace_restrict_button = false; + _convert_signal_button = false; + } break; case WID_BS_DRAG_SIGNALS_DENSITY_DECREASE: @@ -1645,10 +1694,11 @@ 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_TRACE_RESTRICT, _trace_restrict_button); + this->SetWidgetLoweredState(WID_BS_PROGRAM, _program_signal_button); this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, _settings_client.gui.drag_signals_density == 1); this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, _settings_client.gui.drag_signals_density == 20); @@ -1667,6 +1717,7 @@ 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), @@ -1677,6 +1728,7 @@ static const NWidgetPart _nested_signal_builder_widgets[] = { 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), @@ -1689,7 +1741,7 @@ static const NWidgetPart _nested_signal_builder_widgets[] = { EndContainer(), NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetFill(1, 0), EndContainer(), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN), EndContainer(), SetFill(1, 1), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_PROGRAM), SetDataTip(SPR_IMG_SETTINGS, STR_PROGRAM_SIGNAL_TOOLTIP), SetFill(1, 1), EndContainer(), EndContainer(), }; @@ -1973,7 +2025,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; } @@ -1991,7 +2043,12 @@ void InitializeRailGUI() _convert_signal_button = false; _trace_restrict_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(); } diff --git a/src/rail_map.h b/src/rail_map.h index e6f4d7e406..584524394d 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -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. */ diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 7053a9bd96..77a2a8255f 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -45,7 +45,8 @@ std::vector _sl_xv_discardable_chunk_ids; ///< list of chunks static const uint32 _sl_xv_slxi_chunk_version = 0; ///< current version os SLXI chunk const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { - { XSLFI_TRACE_RESTRICT, XSCF_NULL, 1, 1, "tracerestrict", NULL, NULL, "TRRM,TRRP" }, + { XSLFI_TRACE_RESTRICT, XSCF_NULL, 1, 1, "tracerestrict", NULL, NULL, "TRRM,TRRP" }, + { XSLFI_PROG_SIGS, XSCF_NULL, 1, 1, "programmable_signals", NULL, NULL, "SPRG" }, { XSLFI_NULL, XSCF_NULL, 0, 0, NULL, NULL, NULL, NULL },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index 5532f1b6fd..f705eef0eb 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -22,6 +22,7 @@ enum SlXvFeatureIndex { XSLFI_NULL = 0, ///< Unused value, to indicate that no extended feature test is in use XSLFI_TRACE_RESTRICT, ///< Trace restrict + XSLFI_PROG_SIGS, ///< programmable signals patch XSLFI_SIZE, ///< Total count of features, including null feature }; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 2e2d7e1465..1b3bb0fbd2 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -455,6 +455,7 @@ extern const ChunkHandler _airport_chunk_handlers[]; extern const ChunkHandler _object_chunk_handlers[]; extern const ChunkHandler _persistent_storage_chunk_handlers[]; extern const ChunkHandler _trace_restrict_chunk_handlers[]; +extern const ChunkHandler _signal_chunk_handlers[]; /** Array of all chunks in a savegame, \c NULL terminated. */ static const ChunkHandler * const _chunk_handlers[] = { @@ -493,6 +494,7 @@ static const ChunkHandler * const _chunk_handlers[] = { _object_chunk_handlers, _persistent_storage_chunk_handlers, _trace_restrict_chunk_handlers, + _signal_chunk_handlers, NULL, }; diff --git a/src/saveload/signal_sl.cpp b/src/saveload/signal_sl.cpp new file mode 100644 index 0000000000..e0198b109d --- /dev/null +++ b/src/saveload/signal_sl.cpp @@ -0,0 +1,286 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file signal_sl.cpp Code handling saving and loading of signals */ + +#include "../stdafx.h" +#include "../programmable_signals.h" +#include "../core/alloc_type.hpp" +#include "../core/bitmath_func.hpp" +#include +#include "saveload.h" + +typedef std::vector Buffer; + +// Variable length integers are stored in Variable Length Quantity +// format (http://en.wikipedia.org/wiki/Variable-length_quantity) + +static void WriteVLI(Buffer &b, uint i) +{ + uint lsmask = 0x7F; + uint msmask = ~0x7F; + while(i & msmask) { + byte part = (i & lsmask) | 0x80; + b.push_back(part); + i >>= 7; + } + b.push_back((byte) i); +} + +static uint ReadVLI() +{ + uint shift = 0; + uint val = 0; + byte b; + + b = SlReadByte(); + while(b & 0x80) { + val |= uint(b & 0x7F) << shift; + shift += 7; + b = SlReadByte(); + } + val |= uint(b) << shift; + return val; +} + +static void WriteCondition(Buffer &b, SignalCondition *c) +{ + WriteVLI(b, c->ConditionCode()); + switch(c->ConditionCode()) { + case PSC_NUM_GREEN: + case PSC_NUM_RED: { + SignalVariableCondition *vc = static_cast(c); + WriteVLI(b, vc->comparator); + WriteVLI(b, vc->value); + } break; + + case PSC_SIGNAL_STATE: { + SignalStateCondition *sc = static_cast(c); + WriteVLI(b, sc->sig_tile); + WriteVLI(b, sc->sig_track); + } break; + + default: + break; + } +} + +static SignalCondition *ReadCondition(SignalReference this_sig) +{ + SignalConditionCode code = (SignalConditionCode) ReadVLI(); + switch(code) { + case PSC_NUM_GREEN: + case PSC_NUM_RED: { + SignalVariableCondition *c = new SignalVariableCondition(code); + c->comparator = (SignalComparator) ReadVLI(); + if(c->comparator > SGC_LAST) NOT_REACHED(); + c->value = ReadVLI(); + return c; + } + + case PSC_SIGNAL_STATE: { + TileIndex ti = (TileIndex) ReadVLI(); + Trackdir td = (Trackdir) ReadVLI(); + return new SignalStateCondition(this_sig, ti, td); + } + + default: + return new SignalSimpleCondition(code); + } +} + +static void Save_SPRG() +{ + // Check for, and dispose of, any signal information on a tile which doesn't have signals. + // This indicates that someone removed the signals from the tile but didn't clean them up. + // (This code is to detect bugs and limit their consquences, not to cover them up!) + for(ProgramList::iterator i = _signal_programs.begin(), e = _signal_programs.end(); + i != e; ++i) { + SignalReference ref = i->first; + if(!HasProgrammableSignals(ref)) { + DEBUG(sl, 0, "Programmable signal information for (%x, %d) has been leaked!", + ref.tile, ref.track); + ++i; + FreeSignalProgram(ref); + if(i == e) break; + } + } + + // OK, we can now write out our programs + Buffer b; + WriteVLI(b, _signal_programs.size()); + for(ProgramList::iterator i = _signal_programs.begin(), e = _signal_programs.end(); + i != e; ++i) { + SignalReference ref = i->first; + SignalProgram *prog = i->second; + + prog->DebugPrintProgram(); + + WriteVLI(b, prog->tile); + WriteVLI(b, prog->track); + WriteVLI(b, prog->instructions.Length()); + for(SignalInstruction **j = prog->instructions.Begin(), **je = prog->instructions.End(); + j != je; ++j) { + SignalInstruction *insn = *j; + WriteVLI(b, insn->Opcode()); + if(insn->Opcode() != PSO_FIRST) + WriteVLI(b, insn->Previous()->Id()); + switch(insn->Opcode()) { + case PSO_FIRST: { + SignalSpecial *s = static_cast(insn); + WriteVLI(b, s->next->Id()); + break; + } + + case PSO_LAST: break; + + case PSO_IF: { + SignalIf *i = static_cast(insn); + WriteCondition(b, i->condition); + WriteVLI(b, i->if_true->Id()); + WriteVLI(b, i->if_false->Id()); + WriteVLI(b, i->after->Id()); + break; + } + + case PSO_IF_ELSE: + case PSO_IF_ENDIF: { + SignalIf::PseudoInstruction *p = static_cast(insn); + WriteVLI(b, p->block->Id()); + break; + } + + case PSO_SET_SIGNAL: { + SignalSet *s = static_cast(insn); + WriteVLI(b, s->next->Id()); + WriteVLI(b, s->to_state ? 1 : 0); + break; + } + + default: NOT_REACHED(); + } + } + } + + uint size = b.size(); + SlSetLength(size); + for(uint i = 0; i < size; i++) + SlWriteByte(b[i]); // TODO Gotta be a better way +} + +// We don't know the pointer values that need to be stored in various +// instruction fields at load time, so we need to instead store the IDs and +// then fix them up once all of the instructions have been loaded. +// +// Additionally, we store the opcode type we expect (if we expect a specific one) +// to check for consistency (For example, an If Pseudo Instruction's block should +// point at an If!) +struct Fixup { + Fixup(SignalInstruction **p, SignalOpcode type) + : type(type), ptr(p) + {} + + SignalOpcode type; + SignalInstruction **ptr; +}; + +typedef SmallVector FixupList; + +template +static void MakeFixup(FixupList &l, T *&ir, uint id, SignalOpcode op = PSO_INVALID) +{ + ir = reinterpret_cast(id); + new(l.Append()) Fixup(reinterpret_cast(&ir), op); +} + +static void DoFixups(FixupList &l, InstructionList &il) +{ + for(Fixup *i = l.Begin(), *e = l.End(); i != e; ++i) { + uint id = reinterpret_cast(*i->ptr); + if(id >= il.Length()) + NOT_REACHED(); + + *i->ptr = il[id]; + + if(i->type != PSO_INVALID && (*i->ptr)->Opcode() != i->type) { + DEBUG(sl, 0, "Expected Id %d to be %d, but was in fact %d", id, i->type, (*i->ptr)->Opcode()); + NOT_REACHED(); + } + } +} + +static void Load_SPRG() +{ + uint count = ReadVLI(); + for(uint i = 0; i < count; i++) { + FixupList l; + TileIndex tile = ReadVLI(); + Track track = (Track) ReadVLI(); + uint instructions = ReadVLI(); + SignalReference ref(tile, track); + + SignalProgram *sp = new SignalProgram(tile, track, true); + _signal_programs[ref] = sp; + + for(uint j = 0; j < instructions; j++) { + SignalOpcode op = (SignalOpcode) ReadVLI(); + switch(op) { + case PSO_FIRST: { + sp->first_instruction = new SignalSpecial(sp, PSO_FIRST); + sp->first_instruction->GetPrevHandle() = NULL; + MakeFixup(l, sp->first_instruction->next, ReadVLI()); + break; + } + + case PSO_LAST: { + sp->last_instruction = new SignalSpecial(sp, PSO_LAST); + sp->last_instruction->next = NULL; + MakeFixup(l, sp->last_instruction->GetPrevHandle(), ReadVLI()); + break; + } + + case PSO_IF: { + SignalIf *i = new SignalIf(sp, true); + MakeFixup(l, i->GetPrevHandle(), ReadVLI()); + i->condition = ReadCondition(ref); + MakeFixup(l, i->if_true, ReadVLI()); + MakeFixup(l, i->if_false, ReadVLI()); + MakeFixup(l, i->after, ReadVLI()); + break; + } + + case PSO_IF_ELSE: + case PSO_IF_ENDIF: { + SignalIf::PseudoInstruction *p = new SignalIf::PseudoInstruction(sp, op); + MakeFixup(l, p->GetPrevHandle(), ReadVLI()); + MakeFixup(l, p->block, ReadVLI(), PSO_IF); + break; + } + + case PSO_SET_SIGNAL: { + SignalSet *s = new SignalSet(sp); + MakeFixup(l, s->GetPrevHandle(), ReadVLI()); + MakeFixup(l, s->next, ReadVLI()); + s->to_state = (SignalState) ReadVLI(); + if(s->to_state > SIGNAL_STATE_MAX) NOT_REACHED(); + break; + } + + default: NOT_REACHED(); + } + } + + DoFixups(l, sp->instructions); + sp->DebugPrintProgram(); + } +} + +extern const ChunkHandler _signal_chunk_handlers[] = { + { 'SPRG', Save_SPRG, Load_SPRG, NULL, NULL, CH_RIFF | CH_LAST}, +}; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index f1af4804e4..6133f2ee73 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -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)); diff --git a/src/settings_type.h b/src/settings_type.h index 41366a7719..0f02c35806 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -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? diff --git a/src/signal.cpp b/src/signal.cpp index 8e870b53db..3850c08eee 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -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 SignalDependencyList; + +/// Map of dependencies. The key identifies the signal, +/// the value is a list of all of the signals which depend upon that signal. +typedef std::map SignalDependencyMap; + +static SignalDependencyMap _signal_dependencies; +static void MarkDependencidesForUpdate(SignalReference sig); /** these are the maximums used for updating signal blocks */ static const uint SIG_TBU_SIZE = 64; ///< number of signals entering to block @@ -188,6 +201,7 @@ static SmallSet _tbuset("_tbuset"); ///< set of static SmallSet _tbdset("_tbdset"); ///< set of open nodes in current signal block static SmallSet _globset("_globset"); ///< set of places to be updated in following runs +static uint _num_signals_evaluated; ///< Number of programmable signals evaluated /** Check whether there is a train on rail, not in a depot */ static Vehicle *TrainOnTileEnum(Vehicle *v, void *) @@ -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); + } +} diff --git a/src/signal_func.h b/src/signal_func.h index 4597a039b0..805f1218c0 100644 --- a/src/signal_func.h +++ b/src/signal_func.h @@ -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); diff --git a/src/signal_type.h b/src/signal_type.h index c7d06072a7..9fefa4a7f2 100644 --- a/src/signal_type.h +++ b/src/signal_type.h @@ -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 : MakeEnumPropsT {}; +/** Reference to a signal + * + * A reference to a signal by its tile and track + */ +struct SignalReference { + inline SignalReference(TileIndex t, Track tr) : tile(t), track(tr) {} + inline bool operator<(const SignalReference& o) const { return tile < o.tile || (tile == o.tile && track < o.track); } + inline bool operator==(const SignalReference& o) const { return tile == o.tile && track == o.track; } + inline bool operator!=(const SignalReference& o) const { return tile != o.tile || track != o.track; } + + TileIndex tile; + Track track; +}; /** * These are states in which a signal can be. Currently these are only two, so @@ -46,6 +62,7 @@ template <> struct EnumPropsT : MakeEnumPropsT