Fix crash when setting separation mode for vehicles with no orders list.
[openttd-joker.git] / src / logic_signal_program.cpp
blob71d9309bb64173d420274f8b5ad7cd6113d87d4b
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file logic_signal_program.cpp Implementation of the SignalProgram class functions. */
10 #include "logic_signals.h"
11 #include "overlay_cmd.h"
12 #include "rail_map.h"
13 #include "viewport_func.h"
15 #include <algorithm>
17 /**
18 * Return the opposite of the given signal state.
19 * @param state The original signal state to revert.
20 * @return Green if the given argument is Red and vice versa.
22 static SignalState OppositeSignalState(SignalState state)
24 return state == SIGNAL_STATE_RED ? SIGNAL_STATE_GREEN : SIGNAL_STATE_RED;
27 /**
28 * The maximum number of signal programs which are evaluated in succession.
30 static const int MAX_LOGIC_SIGNAL_RECURSIONS = 5;
32 /**
33 * The constructor for creating a new signal program.
34 * @param tile The tile where the logic signal for this program is located.
35 * @param track The track where the logic signal for this program is located.
37 SignalProgram::SignalProgram(TileIndex tile, Track track)
39 this->tile = tile;
40 this->track = track;
42 // Default to a priority signal: if any of the linked input
43 // signals are red, this one goes red.
44 this->own_default_state = SIGNAL_STATE_GREEN;
45 this->trigger_state = SIGNAL_STATE_RED;
46 this->signal_op = SIGNAL_OP_OR;
48 this->blocked_by_train = false;
51 /**
52 * Add a new signal as input for this signal program.
53 * @param tile The tile of the signal to be linked.
54 * @param track The track of the signal to be linked.
56 void SignalProgram::AddLink(TileIndex tile, Track track)
58 const SignalReference input = GetSignalReference(tile, track);
60 if (std::find(this->linked_signals.begin(), this->linked_signals.end(), input) == this->linked_signals.end()) {
61 this->linked_signals.push_back(input);
62 _signal_link_list.push_back(std::make_pair(input, GetSignalReference(this->tile, this->track)));
65 Overlays::Instance()->RefreshLogicSignalOverlay();
68 /**
69 * Remove a linked signal from this program. The link must exist.
70 * @param tile The tile of the signal to be removed.
71 * @param track The track of the signal to be removed.
73 void SignalProgram::RemoveLink(TileIndex tile, Track track)
75 // Refresh BEFORE because we need to know what tiles to refresh before we remove the reference to them.
76 Overlays::Instance()->RefreshLogicSignalOverlay();
78 SignalReference input_to_remove = GetSignalReference(tile, track);
79 const auto found_input = std::find(this->linked_signals.begin(), this->linked_signals.end(), input_to_remove);
81 assert(found_input != this->linked_signals.end());
83 this->linked_signals.erase(found_input);
85 const auto link_to_remove = std::make_pair(input_to_remove, GetSignalReference(this->tile, this->track));
86 const auto found_link = std::find(_signal_link_list.begin(), _signal_link_list.end(), link_to_remove);
88 assert(found_link != _signal_link_list.end());
90 _signal_link_list.erase(found_link);
93 /**
94 * Remove all links that this signal program has.
96 void SignalProgram::ClearAllLinks()
98 // Refresh BEFORE because we need to know what tiles to refresh before we remove the reference to them.
99 Overlays::Instance()->RefreshLogicSignalOverlay();
100 auto this_signal = GetSignalReference(this->tile, this->track);
102 _signal_link_list.erase(std::remove_if(_signal_link_list.begin(), _signal_link_list.end(), [&](std::pair<SignalReference, SignalReference> link)
104 return link.second == this_signal;
105 }), _signal_link_list.end());
107 this->linked_signals.clear();
111 * Return all signal input references.
113 const std::list<SignalReference>& SignalProgram::GetSignalReferences() const
115 return this->linked_signals;
119 * The number of signals linked to this signal program.
121 size_t SignalProgram::LinkCount() const
123 return this->linked_signals.size();
127 * This function is run when one of the signals linked to this program has changed.
128 * It will (possibly) change the state of the signal, and then recursively change
129 * the state of any signal linked to it.
130 * @param depth The recursion depth, which starts at 1.
132 void SignalProgram::InputChanged(int depth)
134 // If this signal is blocked by a train, we can't do anything.
135 if (this->blocked_by_train) {
136 return;
139 const SignalState new_state = this->Evaluate();
141 if (new_state != DetermineSignalState(this->tile, this->track)) {
142 SetSignalStateForTrack(this->tile, this->track, new_state);
143 MarkTileDirtyByTile(tile);
145 // Recursively update any signals that have this one as input.
146 if (depth < MAX_LOGIC_SIGNAL_RECURSIONS) {
147 SignalStateChanged(this->tile, this->track, depth + 1);
153 * The main evaluation function which determines the state of the signal linked
154 * to this signal program. It should be short, simple and readable.
156 SignalState SignalProgram::Evaluate()
158 int trigger_states = 0, not_trigger_states = 0;
160 // We need at least one linked signal to evaluate anything.
161 if (this->LinkCount() == 0) return this->own_default_state;
163 // Loop through all linked signals and count the states.
164 for (auto reference : this->linked_signals) {
165 const TileIndex target_tile = GetTileFromSignalReference(reference);
166 const Track target_track = GetTrackFromSignalReference(reference);
167 const SignalState target_state = DetermineSignalState(target_tile, target_track);
169 if (target_state == trigger_state) {
170 trigger_states++;
171 } else {
172 not_trigger_states++;
176 switch (this->signal_op) {
177 case SIGNAL_OP_OR:
178 // OR is triggered if we have at least one signal of trigger color.
179 if (trigger_states > 0) return OppositeSignalState(this->own_default_state);
180 break;
182 case SIGNAL_OP_AND:
183 // AND is triggered if no signals were of the 'wrong' color.
184 if (not_trigger_states == 0) return OppositeSignalState(this->own_default_state);
185 break;
187 case SIGNAL_OP_NAND:
188 // NAND is triggered if we have at least one signal of the 'wrong' color.
189 if (not_trigger_states > 0) return OppositeSignalState(this->own_default_state);
190 break;
192 case SIGNAL_OP_XOR:
193 // XOR is triggered if the number of signals in trigger color is uneven.
194 if ((trigger_states % 2) > 0) return OppositeSignalState(this->own_default_state);
195 break;
197 default:
198 break;
201 // Not triggered, return default color.
202 return this->own_default_state;