Merge branch 'progsig-sx' into jgrpp
Conflicts: src/command.cpp src/command_type.h src/lang/english.txt src/rail_cmd.cpp src/rail_gui.cpp src/saveload/extended_ver_sl.cpp src/saveload/extended_ver_sl.h src/saveload/saveload.cpp src/widgets/rail_widget.h src/window_type.hpull/3/head
commit
2bcbeea011
Binary file not shown.
@ -0,0 +1,699 @@
|
||||
/* $Id$ */
|
||||
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file programmable_signals.cpp Programmable Signals */
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "programmable_signals.h"
|
||||
#include "debug.h"
|
||||
#include "command_func.h"
|
||||
#include "table/strings.h"
|
||||
#include "window_func.h"
|
||||
#include "company_func.h"
|
||||
#include "cmd_helper.h"
|
||||
#include <assert.h>
|
||||
|
||||
ProgramList _signal_programs;
|
||||
|
||||
SignalProgram::SignalProgram(TileIndex tile, Track track, bool raw)
|
||||
{
|
||||
this->tile = tile;
|
||||
this->track = track;
|
||||
if (!raw) {
|
||||
this->first_instruction = new SignalSpecial(this, PSO_FIRST);
|
||||
this->last_instruction = new SignalSpecial(this, PSO_LAST);
|
||||
SignalSpecial::link(this->first_instruction, this->last_instruction);
|
||||
}
|
||||
}
|
||||
|
||||
SignalProgram::~SignalProgram()
|
||||
{
|
||||
this->DebugPrintProgram();
|
||||
this->first_instruction->Remove();
|
||||
delete this->first_instruction;
|
||||
delete this->last_instruction;
|
||||
}
|
||||
|
||||
struct SignalVM {
|
||||
// Initial information
|
||||
uint num_exits; ///< Number of exits from block
|
||||
uint num_green; ///< Number of green exits from block
|
||||
SignalProgram *program; ///< The program being run
|
||||
|
||||
// Current state
|
||||
SignalInstruction *instruction; ///< Instruction to execute next
|
||||
|
||||
// Output state
|
||||
SignalState state;
|
||||
|
||||
void Execute()
|
||||
{
|
||||
DEBUG(misc, 6, "Begining execution of programmable signal on tile %x, track %d",
|
||||
this->program->tile, this->program->track);
|
||||
do {
|
||||
DEBUG(misc, 10, " Executing instruction %d, opcode %d", this->instruction->Id(), this->instruction->Opcode());
|
||||
this->instruction->Evaluate(*this);
|
||||
} while (this->instruction);
|
||||
|
||||
DEBUG(misc, 6, "Completed");
|
||||
}
|
||||
};
|
||||
|
||||
// -- Conditions
|
||||
|
||||
SignalCondition::~SignalCondition()
|
||||
{}
|
||||
|
||||
SignalSimpleCondition::SignalSimpleCondition(SignalConditionCode code)
|
||||
: SignalCondition(code)
|
||||
{}
|
||||
|
||||
/* virtual */ bool SignalSimpleCondition::Evaluate(SignalVM &vm)
|
||||
{
|
||||
switch (this->cond_code) {
|
||||
case PSC_ALWAYS: return true;
|
||||
case PSC_NEVER: return false;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
SignalVariableCondition::SignalVariableCondition(SignalConditionCode code)
|
||||
: SignalCondition(code)
|
||||
{
|
||||
switch (this->cond_code) {
|
||||
case PSC_NUM_GREEN: comparator = SGC_NOT_EQUALS; break;
|
||||
case PSC_NUM_RED: comparator = SGC_EQUALS; break;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
value = 0;
|
||||
}
|
||||
|
||||
/*virtual*/ bool SignalVariableCondition::Evaluate(SignalVM &vm)
|
||||
{
|
||||
uint32 var_val;
|
||||
switch (this->cond_code) {
|
||||
case PSC_NUM_GREEN: var_val = vm.num_green; break;
|
||||
case PSC_NUM_RED: var_val = vm.num_exits - vm.num_green; break;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
|
||||
switch (this->comparator) {
|
||||
case SGC_EQUALS: return var_val == this->value;
|
||||
case SGC_NOT_EQUALS: return var_val != this->value;
|
||||
case SGC_LESS_THAN: return var_val < this->value;
|
||||
case SGC_LESS_THAN_EQUALS: return var_val <= this->value;
|
||||
case SGC_MORE_THAN: return var_val > this->value;
|
||||
case SGC_MORE_THAN_EQUALS: return var_val >= this->value;
|
||||
case SGC_IS_TRUE: return var_val != 0;
|
||||
case SGC_IS_FALSE: return !var_val;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
SignalStateCondition::SignalStateCondition(SignalReference this_sig,
|
||||
TileIndex sig_tile, Trackdir sig_track)
|
||||
: SignalCondition(PSC_SIGNAL_STATE), this_sig(this_sig), sig_tile(sig_tile)
|
||||
, sig_track(sig_track)
|
||||
{
|
||||
if (this->IsSignalValid())
|
||||
AddSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
|
||||
this->this_sig);
|
||||
}
|
||||
|
||||
bool SignalStateCondition::IsSignalValid()
|
||||
{
|
||||
if (IsValidTile(this->sig_tile)) {
|
||||
if (HasSignalOnTrackdir(this->sig_tile, this->sig_track)) {
|
||||
return true;
|
||||
} else {
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SignalStateCondition::Invalidate()
|
||||
{
|
||||
this->sig_tile = INVALID_TILE;
|
||||
}
|
||||
|
||||
|
||||
void SignalStateCondition::SetSignal(TileIndex tile, Trackdir track)
|
||||
{
|
||||
if (this->IsSignalValid())
|
||||
RemoveSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
|
||||
this->this_sig);
|
||||
this->sig_tile = tile;
|
||||
this->sig_track = track;
|
||||
if (this->IsSignalValid())
|
||||
AddSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
|
||||
this->this_sig);
|
||||
}
|
||||
|
||||
/*virtual*/ SignalStateCondition::~SignalStateCondition()
|
||||
{
|
||||
if (this->IsSignalValid())
|
||||
RemoveSignalDependency(SignalReference(this->sig_tile, TrackdirToTrack(sig_track)),
|
||||
this->this_sig);
|
||||
}
|
||||
|
||||
/*virtual*/ bool SignalStateCondition::Evaluate(SignalVM& vm)
|
||||
{
|
||||
if (!this->IsSignalValid()) {
|
||||
DEBUG(misc, 1, "Signal (%x, %d) has an invalid condition", this->this_sig.tile, this->this_sig.track);
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetSignalStateByTrackdir(this->sig_tile, this->sig_track) == SIGNAL_STATE_GREEN;
|
||||
}
|
||||
|
||||
// -- Instructions
|
||||
SignalInstruction::SignalInstruction(SignalProgram *prog, SignalOpcode op)
|
||||
: opcode(op), previous(NULL), program(prog)
|
||||
{
|
||||
*program->instructions.Append() = this;
|
||||
}
|
||||
|
||||
SignalInstruction::~SignalInstruction()
|
||||
{
|
||||
SignalInstruction** pthis = program->instructions.Find(this);
|
||||
assert(pthis != program->instructions.End());
|
||||
program->instructions.Erase(pthis);
|
||||
}
|
||||
|
||||
void SignalInstruction::Insert(SignalInstruction *before_insn)
|
||||
{
|
||||
this->previous = before_insn->Previous();
|
||||
before_insn->Previous()->SetNext(this);
|
||||
before_insn->SetPrevious(this);
|
||||
this->SetNext(before_insn);
|
||||
}
|
||||
|
||||
SignalSpecial::SignalSpecial(SignalProgram *prog, SignalOpcode op)
|
||||
: SignalInstruction(prog, op)
|
||||
{
|
||||
assert(op == PSO_FIRST || op == PSO_LAST);
|
||||
this->next = NULL;
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalSpecial::Remove()
|
||||
{
|
||||
if (opcode == PSO_FIRST) {
|
||||
while (this->next->Opcode() != PSO_LAST) this->next->Remove();
|
||||
} else if (opcode == PSO_LAST) {
|
||||
} else NOT_REACHED();
|
||||
}
|
||||
|
||||
/*static*/ void SignalSpecial::link(SignalSpecial *first, SignalSpecial *last)
|
||||
{
|
||||
assert(first->opcode == PSO_FIRST && last->opcode == PSO_LAST);
|
||||
first->next = last;
|
||||
last->previous = first;
|
||||
}
|
||||
|
||||
void SignalSpecial::Evaluate(SignalVM &vm)
|
||||
{
|
||||
if (this->opcode == PSO_FIRST) {
|
||||
DEBUG(misc, 7, " Executing First");
|
||||
vm.instruction = this->next;
|
||||
} else {
|
||||
DEBUG(misc, 7, " Executing Last");
|
||||
vm.instruction = NULL;
|
||||
}
|
||||
}
|
||||
/*virtual*/ void SignalSpecial::SetNext(SignalInstruction *next_insn)
|
||||
{
|
||||
this->next = next_insn;
|
||||
}
|
||||
|
||||
SignalIf::PseudoInstruction::PseudoInstruction(SignalProgram *prog, SignalOpcode op)
|
||||
: SignalInstruction(prog, op)
|
||||
{}
|
||||
|
||||
SignalIf::PseudoInstruction::PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op)
|
||||
: SignalInstruction(prog, op)
|
||||
{
|
||||
this->block = block;
|
||||
if (op == PSO_IF_ELSE) {
|
||||
previous = block;
|
||||
} else if (op == PSO_IF_ENDIF) {
|
||||
previous = block->if_true;
|
||||
} else NOT_REACHED();
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalIf::PseudoInstruction::Remove()
|
||||
{
|
||||
if (opcode == PSO_IF_ELSE) {
|
||||
this->block->if_true = NULL;
|
||||
while(this->block->if_false) this->block->if_false->Remove();
|
||||
} else if (opcode == PSO_IF_ENDIF) {
|
||||
this->block->if_false = NULL;
|
||||
} else NOT_REACHED();
|
||||
delete this;
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalIf::PseudoInstruction::Evaluate(SignalVM &vm)
|
||||
{
|
||||
DEBUG(misc, 7, " Executing If Pseudo Instruction %s", opcode == PSO_IF_ELSE ? "Else" : "Endif");
|
||||
vm.instruction = this->block->after;
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalIf::PseudoInstruction::SetNext(SignalInstruction *next_insn)
|
||||
{
|
||||
if (this->opcode == PSO_IF_ELSE) {
|
||||
this->block->if_false = next_insn;
|
||||
} else if (this->opcode == PSO_IF_ENDIF) {
|
||||
this->block->after = next_insn;
|
||||
} else NOT_REACHED();
|
||||
}
|
||||
|
||||
SignalIf::SignalIf(SignalProgram *prog, bool raw)
|
||||
: SignalInstruction(prog, PSO_IF)
|
||||
{
|
||||
if (!raw) {
|
||||
this->condition = new SignalSimpleCondition(PSC_ALWAYS);
|
||||
this->if_true = new PseudoInstruction(prog, this, PSO_IF_ELSE);
|
||||
this->if_false = new PseudoInstruction(prog, this, PSO_IF_ENDIF);
|
||||
this->after = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalIf::Remove()
|
||||
{
|
||||
delete this->condition;
|
||||
while (this->if_true) this->if_true->Remove();
|
||||
|
||||
this->previous->SetNext(this->after);
|
||||
this->after->SetPrevious(this->previous);
|
||||
delete this;
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalIf::Insert(SignalInstruction *before_insn)
|
||||
{
|
||||
this->previous = before_insn->Previous();
|
||||
before_insn->Previous()->SetNext(this);
|
||||
before_insn->SetPrevious(this->if_false);
|
||||
this->after = before_insn;
|
||||
}
|
||||
|
||||
void SignalIf::SetCondition(SignalCondition *cond)
|
||||
{
|
||||
assert(cond != this->condition);
|
||||
delete this->condition;
|
||||
this->condition = cond;
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalIf::Evaluate(SignalVM &vm)
|
||||
{
|
||||
bool is_true = this->condition->Evaluate(vm);
|
||||
DEBUG(misc, 7, " Executing If, taking %s branch", is_true ? "then" : "else");
|
||||
if (is_true) {
|
||||
vm.instruction = this->if_true;
|
||||
} else {
|
||||
vm.instruction = this->if_false;
|
||||
}
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalIf::SetNext(SignalInstruction *next_insn)
|
||||
{
|
||||
this->if_true = next_insn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
SignalSet::SignalSet(SignalProgram *prog, SignalState state)
|
||||
: SignalInstruction(prog, PSO_SET_SIGNAL)
|
||||
{
|
||||
this->to_state = state;
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalSet::Remove()
|
||||
{
|
||||
this->next->SetPrevious(this->previous);
|
||||
this->previous->SetNext(this->next);
|
||||
delete this;
|
||||
}
|
||||
|
||||
/*virtual*/ void SignalSet::Evaluate(SignalVM &vm)
|
||||
{
|
||||
DEBUG(misc, 7, " Executing SetSignal, making %s", this->to_state? "green" : "red");
|
||||
vm.state = this->to_state;
|
||||
vm.instruction = NULL;
|
||||
}
|
||||
|
||||
|
||||
/*virtual*/ void SignalSet::SetNext(SignalInstruction *next_insn)
|
||||
{
|
||||
this->next = next_insn;
|
||||
}
|
||||
|
||||
SignalProgram *GetExistingSignalProgram(SignalReference ref)
|
||||
{
|
||||
ProgramList::iterator i = _signal_programs.find(ref);
|
||||
if (i != _signal_programs.end()) {
|
||||
assert(i->first == ref);
|
||||
return i->second;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SignalProgram *GetSignalProgram(SignalReference ref)
|
||||
{
|
||||
SignalProgram *pr = GetExistingSignalProgram(ref);
|
||||
if (!pr) {
|
||||
pr = new SignalProgram(ref.tile, ref.track);
|
||||
_signal_programs[ref] = pr;
|
||||
} else assert(pr->tile == ref.tile && pr->track == ref.track);
|
||||
return pr;
|
||||
}
|
||||
|
||||
void FreeSignalProgram(SignalReference ref)
|
||||
{
|
||||
DeleteWindowById(WC_SIGNAL_PROGRAM, (ref.tile << 3) | ref.track);
|
||||
ProgramList::iterator i = _signal_programs.find(ref);
|
||||
if (i != _signal_programs.end()) {
|
||||
delete i->second;
|
||||
_signal_programs.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void FreeSignalPrograms()
|
||||
{
|
||||
ProgramList::iterator i, e;
|
||||
for (i = _signal_programs.begin(), e = _signal_programs.end(); i != e;) {
|
||||
delete i->second;
|
||||
// Must postincrement here to avoid iterator invalidation
|
||||
_signal_programs.erase(i++);
|
||||
}
|
||||
}
|
||||
|
||||
SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green)
|
||||
{
|
||||
SignalProgram *program = GetSignalProgram(ref);
|
||||
SignalVM vm;
|
||||
vm.program = program;
|
||||
vm.num_exits = num_exits;
|
||||
vm.num_green = num_green;
|
||||
|
||||
vm.instruction = program->first_instruction;
|
||||
vm.state = SIGNAL_STATE_RED;
|
||||
|
||||
DEBUG(misc, 7, "%d exits, of which %d green", vm.num_exits, vm.num_green);
|
||||
vm.Execute();
|
||||
DEBUG(misc, 7, "Returning %s", vm.state == SIGNAL_STATE_GREEN ? "green" : "red");
|
||||
return vm.state;
|
||||
}
|
||||
|
||||
void RemoveProgramDependencies(SignalReference by, SignalReference on)
|
||||
{
|
||||
SignalProgram *prog = GetSignalProgram(by);
|
||||
for (SignalInstruction **b = prog->instructions.Begin(), **i = b, **e = prog->instructions.End();
|
||||
i != e; i++) {
|
||||
SignalInstruction *insn = *i;
|
||||
if (insn->Opcode() == PSO_IF) {
|
||||
SignalIf* ifi = static_cast<SignalIf*>(insn);
|
||||
if (ifi->condition->ConditionCode() == PSC_SIGNAL_STATE) {
|
||||
SignalStateCondition* c = static_cast<SignalStateCondition*>(ifi->condition);
|
||||
if(c->sig_tile == by.tile && TrackdirToTrack(c->sig_track) == by.track)
|
||||
c->Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddTrackToSignalBuffer(by.tile, by.track, GetTileOwner(by.tile));
|
||||
UpdateSignalsInBuffer();
|
||||
}
|
||||
|
||||
void SignalProgram::DebugPrintProgram()
|
||||
{
|
||||
DEBUG(misc, 5, "Program %p listing", this);
|
||||
for (SignalInstruction **b = this->instructions.Begin(), **i = b, **e = this->instructions.End();
|
||||
i != e; i++)
|
||||
{
|
||||
SignalInstruction *insn = *i;
|
||||
DEBUG(misc, 5, " %ld: Opcode %d, prev %d", long(i - b), int(insn->Opcode()),
|
||||
int(insn->Previous() ? insn->Previous()->Id() : -1));
|
||||
}
|
||||
}
|
||||
|
||||
/** Insert a signal instruction into the signal program.
|
||||
*
|
||||
* @param tile The Tile on which to perform the operation
|
||||
* @param p1 Flags and information
|
||||
* - Bits 0-2 Which track the signal sits on
|
||||
* - Bits 3-18 ID of instruction to insert before
|
||||
* - Bits 19-26 Which opcode to create
|
||||
* - Bits 27-31 Reserved
|
||||
* @param p2 Depends upon instruction
|
||||
* - PSO_SET_SIGNAL:
|
||||
* - Colour to set the signal to
|
||||
* @param text unused
|
||||
*/
|
||||
CommandCost CmdInsertSignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
|
||||
{
|
||||
Track track = Extract<Track, 0, 3>(p1);
|
||||
uint instruction_id = GB(p1, 3, 16);
|
||||
SignalOpcode op = Extract<SignalOpcode, 19, 8>(p1);
|
||||
|
||||
if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
|
||||
return CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!IsTileOwner(tile, _current_company))
|
||||
return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
|
||||
|
||||
SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track));
|
||||
if (!prog)
|
||||
return_cmd_error(STR_ERR_PROGSIG_NOT_THERE);
|
||||
if (instruction_id > prog->instructions.Length())
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION);
|
||||
|
||||
bool exec = (flags & DC_EXEC) != 0;
|
||||
|
||||
SignalInstruction *insert_before = prog->instructions[instruction_id];
|
||||
switch (op) {
|
||||
case PSO_IF: {
|
||||
if (!exec) return CommandCost();
|
||||
SignalIf *if_ins = new SignalIf(prog);
|
||||
if_ins->Insert(insert_before);
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_SET_SIGNAL: {
|
||||
SignalState ss = (SignalState) p2;
|
||||
if (ss > SIGNAL_STATE_MAX) return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE);
|
||||
if (!exec) return CommandCost();
|
||||
|
||||
SignalSet *set = new SignalSet(prog, ss);
|
||||
set->Insert(insert_before);
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_FIRST:
|
||||
case PSO_LAST:
|
||||
case PSO_IF_ELSE:
|
||||
case PSO_IF_ENDIF:
|
||||
default:
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE);
|
||||
}
|
||||
|
||||
if (!exec) return CommandCost();
|
||||
AddTrackToSignalBuffer(tile, track, GetTileOwner(tile));
|
||||
UpdateSignalsInBuffer();
|
||||
InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track);
|
||||
return CommandCost();
|
||||
}
|
||||
|
||||
/** Modify a singal instruction
|
||||
*
|
||||
* @param tile The Tile on which to perform the operation
|
||||
* @param p1 Flags and information
|
||||
* - Bits 0-2 Which track the signal sits on
|
||||
* - Bits 3-18 ID of instruction to insert before
|
||||
* @param p2 Depends upon instruction
|
||||
* - PSO_SET_SIGNAL:
|
||||
* - Colour to set the signal to
|
||||
* - PSO_IF:
|
||||
* - Bit 0 If 0, set the condidion code:
|
||||
* - Bit 1-8: Conditon code to change to
|
||||
* - Otherwise, if SignalVariableCondition:
|
||||
* - Bits 1-2: Which field to change (ConditionField)
|
||||
* - Bits 3-31: Value to set field to
|
||||
* - Otherwise, if SignalStateCondition:
|
||||
* - Bits 1-4: Trackdir on which signal is located
|
||||
* - Bits 5-31: Tile on which signal is located
|
||||
* @param text unused
|
||||
*/
|
||||
CommandCost CmdModifySignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
|
||||
{
|
||||
Track track = Extract<Track, 0, 3 >(p1);
|
||||
uint instruction_id = GB(p1, 3, 16);
|
||||
|
||||
if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
|
||||
return CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!IsTileOwner(tile, _current_company))
|
||||
return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
|
||||
|
||||
SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track));
|
||||
if (!prog)
|
||||
return_cmd_error(STR_ERR_PROGSIG_NOT_THERE);
|
||||
|
||||
if (instruction_id > prog->instructions.Length())
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION);
|
||||
|
||||
bool exec = (flags & DC_EXEC) != 0;
|
||||
|
||||
SignalInstruction *insn = prog->instructions[instruction_id];
|
||||
switch (insn->Opcode()) {
|
||||
case PSO_SET_SIGNAL: {
|
||||
SignalState state = (SignalState) p2;
|
||||
if (state > SIGNAL_STATE_MAX)
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_SIGNAL_STATE);
|
||||
if (!exec)
|
||||
return CommandCost();
|
||||
SignalSet *ss = static_cast<SignalSet*>(insn);
|
||||
ss->to_state = state;
|
||||
} break;
|
||||
|
||||
case PSO_IF: {
|
||||
SignalIf *si = static_cast<SignalIf*>(insn);
|
||||
byte act = GB(p2, 0, 1);
|
||||
if (act == 0) { // Set code
|
||||
SignalConditionCode code = (SignalConditionCode) GB(p2, 1, 8);
|
||||
if (code > PSC_MAX)
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_CONDITION);
|
||||
if (!exec) return CommandCost();
|
||||
|
||||
SignalCondition *cond;
|
||||
switch (code) {
|
||||
case PSC_ALWAYS:
|
||||
case PSC_NEVER:
|
||||
cond = new SignalSimpleCondition(code);
|
||||
break;
|
||||
|
||||
case PSC_NUM_GREEN:
|
||||
case PSC_NUM_RED:
|
||||
cond = new SignalVariableCondition(code);
|
||||
break;
|
||||
|
||||
case PSC_SIGNAL_STATE:
|
||||
cond = new SignalStateCondition(SignalReference(tile, track), INVALID_TILE, INVALID_TRACKDIR);
|
||||
break;
|
||||
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
si->SetCondition(cond);
|
||||
} else { // modify condition
|
||||
switch (si->condition->ConditionCode()) {
|
||||
case PSC_ALWAYS:
|
||||
case PSC_NEVER:
|
||||
return CommandCost(STR_ERR_PROGSIG_INVALID_CONDITION_FIELD);
|
||||
|
||||
case PSC_NUM_GREEN:
|
||||
case PSC_NUM_RED: {
|
||||
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(si->condition);
|
||||
SignalConditionField f = (SignalConditionField) GB(p2, 1, 2);
|
||||
uint32 val = GB(p2, 3, 27);
|
||||
if (f == SCF_COMPARATOR) {
|
||||
if (val > SGC_LAST) return_cmd_error(STR_ERR_PROGSIG_INVALID_COMPARATOR);
|
||||
if (!exec) return CommandCost();
|
||||
vc->comparator = (SignalComparator) val;
|
||||
} else if (f == SCF_VALUE) {
|
||||
if (!exec) return CommandCost();
|
||||
vc->value = val;
|
||||
} else CommandCost(STR_ERR_PROGSIG_INVALID_CONDITION_FIELD);
|
||||
} break;
|
||||
|
||||
case PSC_SIGNAL_STATE: {
|
||||
SignalStateCondition *sc = static_cast<SignalStateCondition*>(si->condition);
|
||||
Trackdir td = (Trackdir) GB(p2, 1, 4);
|
||||
TileIndex ti = (TileIndex) GB(p2, 5, 27);
|
||||
|
||||
if (!IsValidTile(ti) || !IsValidTrackdir(td) || !HasSignalOnTrackdir(ti, td)
|
||||
|| GetTileOwner(ti) != _current_company)
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_SIGNAL);
|
||||
if (!exec) return CommandCost();
|
||||
sc->SetSignal(ti, td);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case PSO_FIRST:
|
||||
case PSO_LAST:
|
||||
case PSO_IF_ELSE:
|
||||
case PSO_IF_ENDIF:
|
||||
default:
|
||||
return CommandCost(STR_ERR_PROGSIG_INVALID_OPCODE);
|
||||
}
|
||||
|
||||
if (!exec) return CommandCost();
|
||||
|
||||
AddTrackToSignalBuffer(tile, track, GetTileOwner(tile));
|
||||
UpdateSignalsInBuffer();
|
||||
InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track);
|
||||
return CommandCost();
|
||||
}
|
||||
|
||||
/** Remove an instruction from a signal program
|
||||
*
|
||||
* @param tile The Tile on which to perform the operation
|
||||
* @param p1 Flags and information
|
||||
* - Bits 0-2 Which track the signal sits on
|
||||
* - Bits 3-18 ID of instruction to insert before
|
||||
* @param p2 unused
|
||||
* @param text unused
|
||||
*/
|
||||
CommandCost CmdRemoveSignalInstruction(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
|
||||
{
|
||||
Track track = Extract<Track, 0, 3 >(p1);
|
||||
uint instruction_id = GB(p1, 3, 16);
|
||||
|
||||
if (!IsValidTrack(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
|
||||
return CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!IsTileOwner(tile, _current_company))
|
||||
return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
|
||||
|
||||
SignalProgram *prog = GetExistingSignalProgram(SignalReference(tile, track));
|
||||
if (!prog)
|
||||
return_cmd_error(STR_ERR_PROGSIG_NOT_THERE);
|
||||
|
||||
if (instruction_id > prog->instructions.Length())
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_INSTRUCTION);
|
||||
|
||||
bool exec = (flags & DC_EXEC) != 0;
|
||||
|
||||
SignalInstruction *insn = prog->instructions[instruction_id];
|
||||
switch (insn->Opcode()) {
|
||||
case PSO_SET_SIGNAL:
|
||||
case PSO_IF:
|
||||
if (!exec) return CommandCost();
|
||||
insn->Remove();
|
||||
break;
|
||||
|
||||
case PSO_FIRST:
|
||||
case PSO_LAST:
|
||||
case PSO_IF_ELSE:
|
||||
case PSO_IF_ENDIF:
|
||||
default:
|
||||
return_cmd_error(STR_ERR_PROGSIG_INVALID_OPCODE);
|
||||
}
|
||||
|
||||
if (!exec) return CommandCost();
|
||||
AddTrackToSignalBuffer(tile, track, GetTileOwner(tile));
|
||||
UpdateSignalsInBuffer();
|
||||
InvalidateWindowData(WC_SIGNAL_PROGRAM, (tile << 3) | track);
|
||||
return CommandCost();
|
||||
}
|
@ -0,0 +1,395 @@
|
||||
/* $Id$ */
|
||||
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file programmable_signals.h Programmable Signals */
|
||||
|
||||
#ifndef PROGRAMMABLE_SIGNALS_H
|
||||
#define PROGRAMMABLE_SIGNALS_H
|
||||
#include "rail_map.h"
|
||||
#include "core/smallvec_type.hpp"
|
||||
#include <map>
|
||||
|
||||
/** @defgroup progsigs Programmable Signals */
|
||||
///@{
|
||||
|
||||
/** The Programmable Signal virtual machine.
|
||||
*
|
||||
* This structure contains the state of the currently executing signal program.
|
||||
*/
|
||||
struct SignalVM;
|
||||
|
||||
class SignalInstruction;
|
||||
class SignalSpecial;
|
||||
typedef SmallVector<SignalInstruction*, 4> InstructionList;
|
||||
|
||||
/** The actual programmable signal information */
|
||||
struct SignalProgram {
|
||||
SignalProgram(TileIndex tile, Track track, bool raw = false);
|
||||
~SignalProgram();
|
||||
void DebugPrintProgram();
|
||||
|
||||
TileIndex tile;
|
||||
Track track;
|
||||
|
||||
SignalSpecial *first_instruction;
|
||||
SignalSpecial *last_instruction;
|
||||
InstructionList instructions;
|
||||
};
|
||||
|
||||
/** Programmable Signal opcode.
|
||||
*
|
||||
* Opcode types are discriminated by this enumeration. It is primarily used for
|
||||
* code which must be able to inspect the type of a signal operation, rather than
|
||||
* evaluate it (such as the programming GUI)
|
||||
*/
|
||||
enum SignalOpcode {
|
||||
PSO_FIRST = 0, ///< Start pseudo instruction
|
||||
PSO_LAST = 1, ///< End pseudo instruction
|
||||
PSO_IF = 2, ///< If instruction
|
||||
PSO_IF_ELSE = 3, ///< If Else pseudo instruction
|
||||
PSO_IF_ENDIF = 4, ///< If Endif pseudo instruction
|
||||
PSO_SET_SIGNAL = 5, ///< Set signal instruction
|
||||
|
||||
PSO_END,
|
||||
PSO_INVALID = 0xFF
|
||||
};
|
||||
template <> struct EnumPropsT<SignalOpcode> : MakeEnumPropsT<SignalOpcode, byte, PSO_FIRST, PSO_END, PSO_INVALID, 8> {};
|
||||
|
||||
/** Signal instruction base class. All instructions must derive from this. */
|
||||
class SignalInstruction {
|
||||
public:
|
||||
/// Get the instruction's opcode
|
||||
inline SignalOpcode Opcode() const { return this->opcode; }
|
||||
|
||||
/// Get the previous instruction. If this is NULL, then this is the first
|
||||
/// instruction.
|
||||
inline SignalInstruction *Previous() const { return this->previous; }
|
||||
|
||||
/// Get the Id of this instruction
|
||||
inline int Id() const
|
||||
// Const cast is safe (perculiarity of SmallVector)
|
||||
{ return program->instructions.FindIndex(const_cast<SignalInstruction*>(this)); }
|
||||
|
||||
/// Insert this instruction, placing it before @p before_insn
|
||||
virtual void Insert(SignalInstruction *before_insn);
|
||||
|
||||
/// Evaluate the instruction. The instruction should update the VM state.
|
||||
virtual void Evaluate(SignalVM &vm) = 0;
|
||||
|
||||
/// Remove the instruction. When removing itself, an instruction should
|
||||
/// <ul>
|
||||
/// <li>Set next->previous to previous
|
||||
/// <li>Set previous->next to next
|
||||
/// <li>Destroy any other children
|
||||
/// </ul>
|
||||
virtual void Remove() = 0;
|
||||
|
||||
/// Gets a reference to the previous member. This is only intended for use by
|
||||
/// the saveload code.
|
||||
inline SignalInstruction *&GetPrevHandle()
|
||||
{ return previous; }
|
||||
|
||||
/// Sets the previous instruction of this instruction. This is only intended
|
||||
/// to be used by instructions to update links during insertion and removal.
|
||||
inline void SetPrevious(SignalInstruction *prev)
|
||||
{ previous = prev; }
|
||||
/// Set the next instruction. This is only intended to be used by instructions
|
||||
/// to update links during insertion and removal
|
||||
virtual void SetNext(SignalInstruction *next_insn) = 0;
|
||||
|
||||
protected:
|
||||
/// Constructs an instruction
|
||||
/// @param prog the program to add this instruction to
|
||||
/// @param op the opcode of the instruction
|
||||
SignalInstruction(SignalProgram *prog, SignalOpcode op) ;
|
||||
virtual ~SignalInstruction();
|
||||
|
||||
const SignalOpcode opcode;
|
||||
SignalInstruction *previous;
|
||||
SignalProgram *program;
|
||||
};
|
||||
|
||||
/** Programmable Signal condition code.
|
||||
*
|
||||
* These discriminate conditions in much the same way that SignalOpcode
|
||||
* discriminates instructions.
|
||||
*/
|
||||
enum SignalConditionCode {
|
||||
PSC_ALWAYS = 0, ///< Always true
|
||||
PSC_NEVER = 1, ///< Always false
|
||||
PSC_NUM_GREEN = 2, ///< Number of green signals behind this signal
|
||||
PSC_NUM_RED = 3, ///< Number of red signals behind this signal
|
||||
PSC_SIGNAL_STATE = 4, ///< State of another signal
|
||||
|
||||
PSC_MAX = PSC_SIGNAL_STATE
|
||||
};
|
||||
|
||||
class SignalCondition {
|
||||
public:
|
||||
/// Get the condition's code
|
||||
inline SignalConditionCode ConditionCode() const { return this->cond_code; }
|
||||
|
||||
/// Evaluate the condition
|
||||
virtual bool Evaluate(SignalVM& vm) = 0;
|
||||
|
||||
/// Destroy the condition. Any children should also be destroyed
|
||||
virtual ~SignalCondition();
|
||||
|
||||
protected:
|
||||
SignalCondition(SignalConditionCode code) : cond_code(code) {}
|
||||
|
||||
const SignalConditionCode cond_code;
|
||||
};
|
||||
|
||||
// -- Condition codes --
|
||||
/** Simple condition code. These conditions have no complex inputs, and can be
|
||||
* evaluated directly from VM state and their condition code.
|
||||
*/
|
||||
class SignalSimpleCondition: public SignalCondition {
|
||||
public:
|
||||
SignalSimpleCondition(SignalConditionCode code);
|
||||
virtual bool Evaluate(SignalVM& vm);
|
||||
};
|
||||
|
||||
/** Comparator to use for variable conditions. */
|
||||
enum SignalComparator {
|
||||
SGC_EQUALS = 0, ///< the variable is equal to the specified value
|
||||
SGC_NOT_EQUALS = 1, ///< the variable is not equal to the specified value
|
||||
SGC_LESS_THAN = 2, ///< the variable is less than specified value
|
||||
SGC_LESS_THAN_EQUALS = 3, ///< the variable is less than or equal to the specified value
|
||||
SGC_MORE_THAN = 4, ///< the variable is greater than the specified value
|
||||
SGC_MORE_THAN_EQUALS = 5, ///< the variable is grater than or equal to the specified value
|
||||
SGC_IS_TRUE = 6, ///< the variable is true (non-zero)
|
||||
SGC_IS_FALSE = 7, ///< the variable is false (zero)
|
||||
|
||||
SGC_LAST = SGC_IS_FALSE
|
||||
};
|
||||
|
||||
/** Which field to modify in a condition. A parameter to CMD_MODIFY_SIGNAL_INSTRUCTION */
|
||||
enum SignalConditionField {
|
||||
SCF_COMPARATOR = 0, ///< the comparator (value from SignalComparator enum)
|
||||
SCF_VALUE = 1, ///< the value (integer value)
|
||||
};
|
||||
|
||||
/** A conditon based upon comparing a variable and a value. This condition can be
|
||||
* considered similar to the conditonal jumps in vehicle orders.
|
||||
*
|
||||
* The variable is specified by the conditon code, the comparison by @p comparator, and
|
||||
* the value to compare against by @p value. The condition returns the result of that value.
|
||||
*/
|
||||
class SignalVariableCondition: public SignalCondition {
|
||||
public:
|
||||
/// Constructs a condition refering to the value @p code refers to. Sets the
|
||||
/// comparator and value to sane defaults.
|
||||
SignalVariableCondition(SignalConditionCode code);
|
||||
|
||||
SignalComparator comparator;
|
||||
uint32 value;
|
||||
|
||||
/// Evaluates the condition
|
||||
virtual bool Evaluate(SignalVM &vm);
|
||||
};
|
||||
|
||||
/** A condition which is based upon the state of another signal. */
|
||||
class SignalStateCondition: public SignalCondition {
|
||||
public:
|
||||
SignalStateCondition(SignalReference this_sig, TileIndex sig_tile, Trackdir sig_track);
|
||||
|
||||
void SetSignal(TileIndex tile, Trackdir track);
|
||||
bool IsSignalValid();
|
||||
void Invalidate();
|
||||
|
||||
virtual bool Evaluate(SignalVM& vm);
|
||||
virtual ~SignalStateCondition();
|
||||
|
||||
SignalReference this_sig;
|
||||
TileIndex sig_tile;
|
||||
Trackdir sig_track;
|
||||
SignalState state;
|
||||
};
|
||||
|
||||
// -- Instructions
|
||||
|
||||
/** The special start and end pseudo instructions.
|
||||
*
|
||||
* These instructions serve two purposes:
|
||||
* <ol>
|
||||
* <li>They permit every other instruction to assume that there is another
|
||||
* following it. This makes the code much simpler (and by extension less
|
||||
* error prone)</li>
|
||||
* <li>Particularly in the case of the End instruction, they provide an
|
||||
* instruction in the user interface that can be clicked on to add
|
||||
* instructions at the end of a program</li>
|
||||
* </ol>
|
||||
*/
|
||||
class SignalSpecial: public SignalInstruction {
|
||||
public:
|
||||
/** Constructs a special signal of the opcode @p op in program @p prog.
|
||||
*
|
||||
* Generally you should not need to call this; it will be called by the
|
||||
* program's constructor. An exception is in the saveload code, which needs
|
||||
* to construct raw objects to deserialize into
|
||||
*/
|
||||
SignalSpecial(SignalProgram *prog, SignalOpcode op);
|
||||
|
||||
/** Evaluates the instruction. If this is an Start instruction, flow will be
|
||||
* vectored to the first instruction; if it is an End instruction, the program
|
||||
* will terminate and the signal will be left red.
|
||||
*/
|
||||
virtual void Evaluate(SignalVM &vm);
|
||||
|
||||
/** Links the first and last instructions in the program. Generally only to be
|
||||
* called from the SignalProgram constructor.
|
||||
*/
|
||||
static void link(SignalSpecial *first, SignalSpecial *last);
|
||||
|
||||
/** Removes this instruction. If this is the start instruction, then all of
|
||||
* the other instructions in the program will be successively removed,
|
||||
* (emptying it). If this is the End instruction, then it will do nothing.
|
||||
*
|
||||
* This operation, unlike when executed on most instructions, does not destroy
|
||||
* the instruction.
|
||||
*/
|
||||
virtual void Remove();
|
||||
|
||||
/** The next instruction after this one. On the End instruction, this should
|
||||
* be NULL.
|
||||
*/
|
||||
SignalInstruction *next;
|
||||
|
||||
virtual void SetNext(SignalInstruction *next_insn);
|
||||
};
|
||||
|
||||
/** If signal instruction. This is perhaps the most important, as without it,
|
||||
* programmable signals are pretty useless.
|
||||
*
|
||||
* It's also the most complex!
|
||||
*/
|
||||
class SignalIf: public SignalInstruction {
|
||||
public:
|
||||
/** The If-Else and If-Endif pseudo instructions. The Else instruction
|
||||
* follows the Then block, and the Endif instruction follows the Else block.
|
||||
*
|
||||
* These serve two purposes:
|
||||
* <ul>
|
||||
* <li>They correctly vector the execution to after the if block
|
||||
* (if needed)
|
||||
* <li>They provide an instruction for the GUI to insert other instructions
|
||||
* before.
|
||||
* </ul>
|
||||
*/
|
||||
class PseudoInstruction: public SignalInstruction {
|
||||
public:
|
||||
/** Normal constructor. The pseudo instruction will be constructed as
|
||||
* belonging to @p block.
|
||||
*/
|
||||
PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op);
|
||||
|
||||
/** Constructs an empty instruction of type @p op. This should only be used
|
||||
* by the saveload code during deserialization. The instruction must have
|
||||
* its block field set correctly before the program is run.
|
||||
*/
|
||||
PseudoInstruction(SignalProgram *prog, SignalOpcode op);
|
||||
|
||||
/** Removes the pseudo instruction. Unless you are also removing the If it
|
||||
* belongs to, this is nonsense and dangerous.
|
||||
*/
|
||||
virtual void Remove();
|
||||
|
||||
/** Evaluate the pseudo instruction. This involves vectoring execution to
|
||||
* the instruction after the if.
|
||||
*/
|
||||
virtual void Evaluate(SignalVM &vm);
|
||||
|
||||
/** The block to which this instruction belongs */
|
||||
SignalIf *block;
|
||||
virtual void SetNext(SignalInstruction *next_insn);
|
||||
};
|
||||
|
||||
public:
|
||||
/** Constructs an If instruction belonging to program @p prog. If @p raw is
|
||||
* true, then the instruction is constructed raw (in order for the
|
||||
* deserializer to be able to correctly deserialize the instruction).
|
||||
*/
|
||||
SignalIf(SignalProgram *prog, bool raw = false);
|
||||
|
||||
/** Sets the instruction's condition, and releases the old condition */
|
||||
void SetCondition(SignalCondition *cond);
|
||||
|
||||
/** Evaluates the If and takes the appropriate branch */
|
||||
virtual void Evaluate(SignalVM &vm);
|
||||
|
||||
virtual void Insert(SignalInstruction *before_insn);
|
||||
|
||||
/** Removes the If and all of its children */
|
||||
virtual void Remove();
|
||||
|
||||
SignalCondition *condition; ///< The if conditon
|
||||
SignalInstruction *if_true; ///< The branch to take if true
|
||||
SignalInstruction *if_false; ///< The branch to take if false
|
||||
SignalInstruction *after; ///< The branch to take after the If
|
||||
|
||||
virtual void SetNext(SignalInstruction *next_insn);
|
||||
};
|
||||
|
||||
/** Set signal instruction. This sets the state of the signal and terminates execution */
|
||||
class SignalSet: public SignalInstruction {
|
||||
public:
|
||||
/// Constructs the instruction and sets the state the signal is to be set to
|
||||
SignalSet(SignalProgram *prog, SignalState = SIGNAL_STATE_RED);
|
||||
|
||||
virtual void Evaluate(SignalVM &vm);
|
||||
virtual void Remove();
|
||||
|
||||
/// The state to set the signal to
|
||||
SignalState to_state;
|
||||
|
||||
/// The instruction following this one (for the editor)
|
||||
SignalInstruction *next;
|
||||
|
||||
virtual void SetNext(SignalInstruction *next_insn);
|
||||
};
|
||||
|
||||
/// The map type used for looking up signal programs
|
||||
typedef std::map<SignalReference, SignalProgram*> ProgramList;
|
||||
|
||||
/// The global signal program list
|
||||
extern ProgramList _signal_programs;
|
||||
|
||||
/// Verifies that a SignalReference refers to a signal which has a program.
|
||||
static inline bool HasProgrammableSignals(SignalReference ref)
|
||||
{
|
||||
return IsTileType(ref.tile, MP_RAILWAY) && GetRailTileType(ref.tile) == RAIL_TILE_SIGNALS
|
||||
&& IsPresignalProgrammable(ref.tile, ref.track);
|
||||
}
|
||||
|
||||
/// Shows the programming window for the signal identified by @p tile and
|
||||
/// @p track.
|
||||
void ShowSignalProgramWindow(SignalReference ref);
|
||||
|
||||
/// Gets the signal program for the tile identified by @p t and @p track.
|
||||
/// An empty program will be constructed if none is specified
|
||||
SignalProgram *GetSignalProgram(SignalReference ref);
|
||||
|
||||
SignalProgram *GetExistingSignalProgram(SignalReference ref);
|
||||
|
||||
/// Frees a signal program by tile and track
|
||||
void FreeSignalProgram(SignalReference ref);
|
||||
|
||||
/// Frees all signal programs (For use when creating a new game)
|
||||
void FreeSignalPrograms();
|
||||
|
||||
/// Runs the signal program, specifying the following parameters.
|
||||
SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green);
|
||||
|
||||
/// Remove dependencies on signal @p on from @p by
|
||||
void RemoveProgramDependencies(SignalReference by, SignalReference on);
|
||||
///@}
|
||||
|
||||
#endif
|
@ -0,0 +1,904 @@
|
||||
/* $Id$ */
|
||||
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file programmable_signals_gui.cpp GUI related to programming signals */
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "programmable_signals.h"
|
||||
#include "command_func.h"
|
||||
#include "window_func.h"
|
||||
#include "strings_func.h"
|
||||
#include "string_func.h"
|
||||
#include "viewport_func.h"
|
||||
#include "textbuf_gui.h"
|
||||
#include "company_func.h"
|
||||
#include "widgets/dropdown_func.h"
|
||||
#include "gui.h"
|
||||
#include "gfx_func.h"
|
||||
#include "tilehighlight_func.h"
|
||||
#include "rail_map.h"
|
||||
#include "tile_cmd.h"
|
||||
#include "error.h"
|
||||
|
||||
#include "table/sprites.h"
|
||||
#include "table/strings.h"
|
||||
|
||||
enum ProgramWindowWidgets {
|
||||
PROGRAM_WIDGET_CAPTION,
|
||||
PROGRAM_WIDGET_INSTRUCTION_LIST,
|
||||
PROGRAM_WIDGET_SCROLLBAR,
|
||||
|
||||
PROGRAM_WIDGET_SEL_TOP_LEFT,
|
||||
PROGRAM_WIDGET_SEL_TOP_MIDDLE,
|
||||
PROGRAM_WIDGET_SEL_TOP_RIGHT,
|
||||
|
||||
PROGRAM_WIDGET_SET_STATE,
|
||||
PROGRAM_WIDGET_COND_VARIABLE,
|
||||
PROGRAM_WIDGET_COND_COMPARATOR,
|
||||
PROGRAM_WIDGET_COND_VALUE,
|
||||
PROGRAM_WIDGET_COND_GOTO_SIGNAL,
|
||||
PROGRAM_WIDGET_COND_SET_SIGNAL,
|
||||
|
||||
PROGRAM_WIDGET_GOTO_SIGNAL,
|
||||
PROGRAM_WIDGET_INSERT,
|
||||
PROGRAM_WIDGET_REMOVE,
|
||||
|
||||
PROGRAM_WIDGET_REMOVE_PROGRAM,
|
||||
PROGRAM_WIDGET_COPY_PROGRAM,
|
||||
};
|
||||
|
||||
enum PanelWidgets {
|
||||
// Left
|
||||
DPL_COND_VARIABLE = 0,
|
||||
DPL_SET_STATE,
|
||||
|
||||
// Middle
|
||||
DPM_COND_COMPARATOR = 0,
|
||||
DPM_COND_GOTO_SIGNAL,
|
||||
|
||||
// Right
|
||||
DPR_COND_VALUE = 0,
|
||||
DPR_COND_SET_SIGNAL
|
||||
};
|
||||
|
||||
static const StringID _program_insert[] = {
|
||||
STR_PROGSIG_INSERT_IF,
|
||||
STR_PROGSIG_INSERT_SET_SIGNAL,
|
||||
INVALID_STRING_ID
|
||||
};
|
||||
|
||||
static SignalOpcode OpcodeForIndex(int index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0: return PSO_IF;
|
||||
case 1: return PSO_SET_SIGNAL;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsConditionComparator(SignalCondition *cond)
|
||||
{
|
||||
switch (cond->ConditionCode()) {
|
||||
case PSC_NUM_GREEN:
|
||||
case PSC_NUM_RED:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const StringID _program_condvar[] = {
|
||||
/* PSC_ALWAYS */ STR_PROGSIG_COND_ALWAYS,
|
||||
/* PSC_NEVER */ STR_PROGSIG_COND_NEVER,
|
||||
/* PSC_NUM_GREEN */ STR_PROGSIG_CONDVAR_NUM_GREEN,
|
||||
/* PSC_NUM_RED */ STR_PROGSIG_CONDVAR_NUM_RED,
|
||||
/* PSC_SIGNAL_STATE*/ STR_PROGSIG_COND_SIGNAL_STATE,
|
||||
INVALID_STRING_ID
|
||||
};
|
||||
|
||||
// TODO: These should probably lose the ORDER
|
||||
static const StringID _program_comparator[] = {
|
||||
/* SGC_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_EQUALS,
|
||||
/* SGC_NOT_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_NOT_EQUALS,
|
||||
/* SGC_LESS_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_THAN,
|
||||
/* SGC_LESS_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_LESS_EQUALS,
|
||||
/* SGC_MORE_THAN */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_THAN,
|
||||
/* SGC_MORE_THAN_EQUALS */ STR_ORDER_CONDITIONAL_COMPARATOR_MORE_EQUALS,
|
||||
/* SGC_IS_TRUE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_TRUE,
|
||||
/* SGC_IS_FALSE */ STR_ORDER_CONDITIONAL_COMPARATOR_IS_FALSE,
|
||||
INVALID_STRING_ID
|
||||
};
|
||||
|
||||
static const StringID _program_sigstate[] = {
|
||||
STR_COLOUR_RED,
|
||||
STR_COLOUR_GREEN,
|
||||
INVALID_STRING_ID
|
||||
};
|
||||
|
||||
/** Get the string for a condition */
|
||||
static char *GetConditionString(SignalCondition *cond, char *buf, char *buflast, bool selected)
|
||||
{
|
||||
StringID string = INVALID_STRING_ID;
|
||||
bool comparator = IsConditionComparator(cond);
|
||||
|
||||
if (comparator) {
|
||||
SignalVariableCondition *cv = static_cast<SignalVariableCondition*>(cond);
|
||||
string = STR_PROGSIG_COND_COMPARE;
|
||||
SetDParam(0, _program_condvar[cond->ConditionCode()]);
|
||||
SetDParam(1, _program_comparator[cv->comparator]);
|
||||
SetDParam(2, cv->value);
|
||||
} else {
|
||||
string = _program_condvar[cond->ConditionCode()];
|
||||
if (cond->ConditionCode() == PSC_SIGNAL_STATE) {
|
||||
string = STR_PROGSIG_CONDVAR_SIGNAL_STATE;
|
||||
SetDParam(0, static_cast<SignalStateCondition*>(cond)->IsSignalValid()
|
||||
? STR_PROGSIG_CONDVAR_SIGNAL_STATE_SPECIFIED : STR_PROGSIG_CONDVAR_SIGNAL_STATE_UNSPECIFIED);
|
||||
SetDParam(1, selected ? STR_WHITE : STR_BLACK);
|
||||
}
|
||||
}
|
||||
return GetString(buf, string, buflast);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an instruction in the programming GUI
|
||||
* @param instruction The instruction to draw
|
||||
* @param y Y position for drawing
|
||||
* @param selected True, if the order is selected
|
||||
* @param indent How many levels the instruction is indented
|
||||
* @param left Left border for text drawing
|
||||
* @param right Right border for text drawing
|
||||
*/
|
||||
static void DrawInstructionString(SignalInstruction *instruction, int y, bool selected, int indent, int left, int right)
|
||||
{
|
||||
StringID instruction_string = INVALID_STRING_ID;
|
||||
|
||||
char condstr[512];
|
||||
|
||||
switch (instruction->Opcode()) {
|
||||
case PSO_FIRST:
|
||||
instruction_string = STR_PROGSIG_FIRST;
|
||||
break;
|
||||
|
||||
case PSO_LAST:
|
||||
instruction_string = STR_PROGSIG_LAST;
|
||||
break;
|
||||
|
||||
case PSO_IF: {
|
||||
SignalIf *if_ins = static_cast<SignalIf*>(instruction);
|
||||
GetConditionString(if_ins->condition, condstr, lastof(condstr), selected);
|
||||
SetDParamStr(0, condstr);
|
||||
instruction_string = STR_PROGSIG_IF;
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_IF_ELSE:
|
||||
instruction_string = STR_PROGSIG_ELSE;
|
||||
break;
|
||||
|
||||
case PSO_IF_ENDIF:
|
||||
instruction_string = STR_PROGSIG_ENDIF;
|
||||
break;
|
||||
|
||||
case PSO_SET_SIGNAL: {
|
||||
instruction_string = STR_PROGSIG_SET_SIGNAL;
|
||||
SignalSet *set = static_cast<SignalSet*>(instruction);
|
||||
SetDParam(0, _program_sigstate[set->to_state]);
|
||||
break;
|
||||
}
|
||||
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
|
||||
DrawString(left + indent * 16, right, y, instruction_string, selected ? TC_WHITE : TC_BLACK);
|
||||
}
|
||||
|
||||
struct GuiInstruction {
|
||||
SignalInstruction *insn;
|
||||
uint indent;
|
||||
};
|
||||
|
||||
typedef SmallVector<GuiInstruction, 4> GuiInstructionList;
|
||||
|
||||
class ProgramWindow: public Window {
|
||||
public:
|
||||
ProgramWindow(WindowDesc *desc, SignalReference ref): Window(desc)
|
||||
{
|
||||
// this->InitNested(desc, (ref.tile << 3) | ref.track);
|
||||
this->tile = ref.tile;
|
||||
this->track = ref.track;
|
||||
this->selected_instruction = -1;
|
||||
|
||||
this->CreateNestedTree(desc);
|
||||
this->vscroll = this->GetScrollbar(PROGRAM_WIDGET_SCROLLBAR);
|
||||
this->FinishInitNested((ref.tile << 3) | ref.track);
|
||||
|
||||
program = GetSignalProgram(ref);
|
||||
RebuildInstructionList();
|
||||
}
|
||||
|
||||
virtual void OnClick(Point pt, int widget, int click_count) OVERRIDE
|
||||
{
|
||||
switch (widget) {
|
||||
case PROGRAM_WIDGET_INSTRUCTION_LIST: {
|
||||
int sel = this->GetInstructionFromPt(pt.y);
|
||||
|
||||
this->DeleteChildWindows();
|
||||
HideDropDownMenu(this);
|
||||
|
||||
if (sel == -1 || this->GetOwner() != _local_company) {
|
||||
// Deselect
|
||||
this->selected_instruction = -1;
|
||||
} else {
|
||||
this->selected_instruction = sel;
|
||||
}
|
||||
|
||||
this->UpdateButtonState();
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_INSERT: {
|
||||
DEBUG(misc, 5, "Selection is %d", this->selected_instruction);
|
||||
if (this->GetOwner() != _local_company || this->selected_instruction < 1)
|
||||
return;
|
||||
ShowDropDownMenu(this, _program_insert, -1, PROGRAM_WIDGET_INSERT, 0, 0, 0);
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_REMOVE: {
|
||||
SignalInstruction *ins = GetSelected();
|
||||
if (this->GetOwner() != _local_company || !ins)
|
||||
return;
|
||||
|
||||
uint32 p1 = 0;
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, ins->Id());
|
||||
|
||||
DoCommandP(this->tile, p1, 0, CMD_REMOVE_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
|
||||
this->RebuildInstructionList();
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_SET_STATE: {
|
||||
SignalInstruction *si = this->GetSelected();
|
||||
if (!si || si->Opcode() != PSO_SET_SIGNAL) return;
|
||||
SignalSet *ss = static_cast <SignalSet*>(si);
|
||||
|
||||
ShowDropDownMenu(this, _program_sigstate, ss->to_state, PROGRAM_WIDGET_SET_STATE, 0, 0, 0);
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_COND_VARIABLE: {
|
||||
SignalInstruction *si = this->GetSelected();
|
||||
if (!si || si->Opcode() != PSO_IF) return;
|
||||
SignalIf *sif = static_cast <SignalIf*>(si);
|
||||
|
||||
ShowDropDownMenu(this, _program_condvar, sif->condition->ConditionCode(), PROGRAM_WIDGET_COND_VARIABLE, 0, 0, 0);
|
||||
this->UpdateButtonState();
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_COND_COMPARATOR: {
|
||||
SignalInstruction *si = this->GetSelected();
|
||||
if (!si || si->Opcode() != PSO_IF) return;
|
||||
SignalIf *sif = static_cast <SignalIf*>(si);
|
||||
if (!IsConditionComparator(sif->condition)) return;
|
||||
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(sif->condition);
|
||||
|
||||
ShowDropDownMenu(this, _program_comparator, vc->comparator, PROGRAM_WIDGET_COND_COMPARATOR, 0, 0, 0);
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_COND_VALUE: {
|
||||
SignalInstruction *si = this->GetSelected();
|
||||
if (!si || si->Opcode() != PSO_IF) return;
|
||||
SignalIf *sif = static_cast <SignalIf*>(si);
|
||||
if (!IsConditionComparator(sif->condition)) return;
|
||||
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(sif->condition);
|
||||
|
||||
SetDParam(0, vc->value);
|
||||
//ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, 100, this, CS_NUMERAL, QSF_NONE);
|
||||
ShowQueryString(STR_JUST_INT, STR_PROGSIG_CONDITION_VALUE_CAPT, 5, this, CS_NUMERAL, QSF_NONE);
|
||||
this->UpdateButtonState();
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_COND_GOTO_SIGNAL: {
|
||||
SignalInstruction *si = this->GetSelected();
|
||||
if (!si || si->Opcode() != PSO_IF) return;
|
||||
SignalIf *sif = static_cast <SignalIf*>(si);
|
||||
if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return;
|
||||
SignalStateCondition *sc = static_cast<SignalStateCondition*>(sif->condition);
|
||||
|
||||
if (sc->IsSignalValid()) {
|
||||
ScrollMainWindowToTile(sc->sig_tile);
|
||||
} else {
|
||||
ShowErrorMessage(STR_ERROR_CAN_T_GOTO_UNDEFINED_SIGNAL, STR_EMPTY, WL_INFO);
|
||||
}
|
||||
// this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_COND_SET_SIGNAL: {
|
||||
this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COND_SET_SIGNAL);
|
||||
this->SetWidgetDirty(PROGRAM_WIDGET_COND_SET_SIGNAL);
|
||||
if (this->IsWidgetLowered(PROGRAM_WIDGET_COND_SET_SIGNAL)) {
|
||||
SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this);
|
||||
} else {
|
||||
ResetObjectToPlace();
|
||||
}
|
||||
} break;
|
||||
|
||||
case PROGRAM_WIDGET_GOTO_SIGNAL: {
|
||||
ScrollMainWindowToTile(this->tile);
|
||||
// this->RaiseWidget(PROGRAM_WIDGET_GOTO_SIGNAL);
|
||||
} break;
|
||||
case PROGRAM_WIDGET_REMOVE_PROGRAM: {
|
||||
if (this->GetOwner() != _local_company)
|
||||
return;
|
||||
program->first_instruction->Remove();
|
||||
|
||||
this->RebuildInstructionList();
|
||||
} break;
|
||||
case PROGRAM_WIDGET_COPY_PROGRAM: {
|
||||
|
||||
this->ToggleWidgetLoweredState(PROGRAM_WIDGET_COPY_PROGRAM);
|
||||
this->SetWidgetDirty(PROGRAM_WIDGET_COPY_PROGRAM);
|
||||
if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) {
|
||||
SetObjectToPlaceWnd(ANIMCURSOR_BUILDSIGNALS, PAL_NONE, HT_RECT, this);
|
||||
} else {
|
||||
ResetObjectToPlace();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void InsertInstruction(SignalInstruction *si, uint32 next)
|
||||
{
|
||||
uint64 p1 = 0;
|
||||
while(true) {
|
||||
switch(si->Opcode()) {
|
||||
case PSO_SET_SIGNAL: {
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, next);
|
||||
SB(p1, 19, 8, si->Opcode());
|
||||
|
||||
DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION));
|
||||
this->RebuildInstructionList();
|
||||
si = ((SignalSet*)si)->next;
|
||||
} break;
|
||||
case PSO_IF: {
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, next);
|
||||
SB(p1, 19, 8, si->Opcode());
|
||||
|
||||
DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION));
|
||||
this->RebuildInstructionList();
|
||||
|
||||
SignalInstruction *s = ((SignalIf*)si)->if_true;
|
||||
while(s->Opcode() != PSO_IF_ELSE) {
|
||||
if(s->Opcode() == PSO_IF) s = ((SignalIf*)s)->after;
|
||||
if(s->Opcode() == PSO_SET_SIGNAL) s = ((SignalSet*)s)->next;
|
||||
else break;
|
||||
}
|
||||
InsertInstruction(((SignalIf*)si)->if_true, s->Id());
|
||||
this->RebuildInstructionList();
|
||||
|
||||
s = ((SignalIf*)si)->if_false;
|
||||
while(s->Opcode() != PSO_IF_ENDIF) {
|
||||
if(s->Opcode() == PSO_IF) s = ((SignalIf*)s)->after;
|
||||
if(s->Opcode() == PSO_SET_SIGNAL) s = ((SignalSet*)s)->next;
|
||||
else break;
|
||||
}
|
||||
InsertInstruction(((SignalIf*)si)->if_false, s->Id());
|
||||
this->RebuildInstructionList();
|
||||
|
||||
si = ((SignalIf*)si)->after;
|
||||
} break;
|
||||
}
|
||||
if(si == NULL) break;
|
||||
if(si->Opcode() == PSO_LAST) break;
|
||||
if(si->Opcode() == PSO_IF_ELSE) break;
|
||||
if(si->Opcode() == PSO_IF_ENDIF) break;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnPlaceObject(Point pt, TileIndex tile1) OVERRIDE
|
||||
{
|
||||
if (this->IsWidgetLowered(PROGRAM_WIDGET_COPY_PROGRAM)) {
|
||||
//Copy program from another progsignal
|
||||
TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile1, TRANSPORT_RAIL, 0));
|
||||
if (trackbits & TRACK_BIT_VERT) { // N-S direction
|
||||
trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT;
|
||||
}
|
||||
if (trackbits & TRACK_BIT_HORZ) { // E-W direction
|
||||
trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER;
|
||||
}
|
||||
Track track1 = FindFirstTrack(trackbits);
|
||||
if(track1 == INVALID_TRACK) {
|
||||
return;
|
||||
}
|
||||
Trackdir td = TrackToTrackdir(track1);
|
||||
Trackdir tdr = ReverseTrackdir(td);
|
||||
if (!(HasSignalOnTrackdir(tile1, td) || HasSignalOnTrackdir(tile1, tdr)))
|
||||
return;
|
||||
|
||||
if (GetSignalType(tile1, track1) != SIGTYPE_PROG) {
|
||||
ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_PROG_SIGNAL, WL_INFO);
|
||||
return;
|
||||
}
|
||||
if(this->tile == tile1 && this->track == track1) {
|
||||
ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CANNOT_USE_SELF, WL_INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
SignalProgram *sp = GetExistingSignalProgram(SignalReference(tile1, track1));
|
||||
if (!sp) {
|
||||
ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO);
|
||||
return;
|
||||
}
|
||||
program->first_instruction->Remove();
|
||||
this->RebuildInstructionList();
|
||||
|
||||
SignalInstruction *si = ((SignalSpecial*)sp->first_instruction)->next;
|
||||
InsertInstruction(si, program->last_instruction->Id());
|
||||
ResetObjectToPlace();
|
||||
this->RaiseWidget(PROGRAM_WIDGET_COPY_PROGRAM);
|
||||
//OnPaint(); // this appears to cause visual artefacts
|
||||
return;
|
||||
}
|
||||
|
||||
SignalInstruction *si = this->GetSelected();
|
||||
if (!si || si->Opcode() != PSO_IF) return;
|
||||
SignalIf *sif = static_cast <SignalIf*>(si);
|
||||
if (sif->condition->ConditionCode() != PSC_SIGNAL_STATE) return;
|
||||
|
||||
if (!IsPlainRailTile(tile1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile1, TRANSPORT_RAIL, 0));
|
||||
if (trackbits & TRACK_BIT_VERT) { // N-S direction
|
||||
trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT;
|
||||
}
|
||||
|
||||
if (trackbits & TRACK_BIT_HORZ) { // E-W direction
|
||||
trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER;
|
||||
}
|
||||
Track track1 = FindFirstTrack(trackbits);
|
||||
if(track1 == INVALID_TRACK) {
|
||||
return;
|
||||
}
|
||||
|
||||
Trackdir td = TrackToTrackdir(track1);
|
||||
Trackdir tdr = ReverseTrackdir(td);
|
||||
|
||||
if (HasSignalOnTrackdir(tile1, td) && HasSignalOnTrackdir(tile1, tdr)) {
|
||||
ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_CAN_T_DEPEND_UPON_BIDIRECTIONAL_SIGNALS, WL_INFO);
|
||||
return;
|
||||
} else if (HasSignalOnTrackdir(tile1, tdr) && !HasSignalOnTrackdir(tile1, td)) {
|
||||
td = tdr;
|
||||
}
|
||||
|
||||
if (!HasSignalOnTrackdir(tile1, td)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//!!!!!!!!!!!!!!!
|
||||
if (!(GetSignalType(tile1, track1) == SIGTYPE_EXIT || GetSignalType(tile1, track1) == SIGTYPE_PROG)) {
|
||||
//!!!!!!!!!!!!!!!
|
||||
ShowErrorMessage(STR_ERROR_INVALID_SIGNAL, STR_ERROR_NOT_AN_EXIT_SIGNAL, WL_INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 p1 = 0, p2 = 0;
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, si->Id());
|
||||
|
||||
SB(p2, 0, 1, 1);
|
||||
SB(p2, 1, 4, td);
|
||||
SB(p2, 5, 27, tile1);
|
||||
|
||||
DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
|
||||
ResetObjectToPlace();
|
||||
this->RaiseWidget(PROGRAM_WIDGET_COND_SET_SIGNAL);
|
||||
//OnPaint(); // this appears to cause visual artefacts
|
||||
}
|
||||
|
||||
virtual void OnQueryTextFinished(char *str) OVERRIDE
|
||||
{
|
||||
if (!StrEmpty(str)) {
|
||||
SignalInstruction *si = this->GetSelected();
|
||||
if (!si || si->Opcode() != PSO_IF) return;
|
||||
SignalIf *sif = static_cast <SignalIf*>(si);
|
||||
if (!IsConditionComparator(sif->condition)) return;
|
||||
|
||||
uint value = atoi(str);
|
||||
|
||||
uint32 p1 = 0, p2 = 0;
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, si->Id());
|
||||
|
||||
SB(p2, 0, 1, 1);
|
||||
SB(p2, 1, 2, SCF_VALUE);
|
||||
SB(p2, 3, 27, value);
|
||||
|
||||
DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnDropdownSelect(int widget, int index) OVERRIDE
|
||||
{
|
||||
SignalInstruction *ins = this->GetSelected();
|
||||
if (!ins) return;
|
||||
|
||||
switch (widget) {
|
||||
case PROGRAM_WIDGET_INSERT: {
|
||||
uint64 p1 = 0;
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, ins->Id());
|
||||
SB(p1, 19, 8, OpcodeForIndex(index));
|
||||
|
||||
DoCommandP(this->tile, p1, 0, CMD_INSERT_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_INSERT_INSTRUCTION));
|
||||
this->RebuildInstructionList();
|
||||
break;
|
||||
}
|
||||
|
||||
case PROGRAM_WIDGET_SET_STATE: {
|
||||
uint64 p1 = 0;
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, ins->Id());
|
||||
|
||||
DoCommandP(this->tile, p1, index, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
|
||||
break;
|
||||
}
|
||||
|
||||
case PROGRAM_WIDGET_COND_VARIABLE: {
|
||||
uint64 p1 = 0, p2 = 0;
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, ins->Id());
|
||||
|
||||
SB(p2, 0, 1, 0);
|
||||
SB(p2, 1, 8, index);
|
||||
|
||||
DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
|
||||
break;
|
||||
}
|
||||
|
||||
case PROGRAM_WIDGET_COND_COMPARATOR: {
|
||||
uint64 p1 = 0, p2 = 0;
|
||||
SB(p1, 0, 3, this->track);
|
||||
SB(p1, 3, 16, ins->Id());
|
||||
|
||||
SB(p2, 0, 1, 1);
|
||||
SB(p2, 1, 2, SCF_COMPARATOR);
|
||||
SB(p2, 3, 27, index);
|
||||
|
||||
DoCommandP(this->tile, p1, p2, CMD_MODIFY_SIGNAL_INSTRUCTION | CMD_MSG(STR_ERROR_CAN_T_MODIFY_INSTRUCTION));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) OVERRIDE
|
||||
{
|
||||
switch (widget) {
|
||||
case PROGRAM_WIDGET_INSTRUCTION_LIST:
|
||||
resize->height = FONT_HEIGHT_NORMAL;
|
||||
size->height = 6 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnResize() OVERRIDE
|
||||
{
|
||||
/* Update the scroll bar */
|
||||
this->vscroll->SetCapacityFromWidget(this, PROGRAM_WIDGET_INSTRUCTION_LIST);
|
||||
}
|
||||
|
||||
virtual void OnPaint() OVERRIDE
|
||||
{
|
||||
this->DrawWidgets();
|
||||
}
|
||||
|
||||
virtual void DrawWidget(const Rect &r, int widget) const OVERRIDE
|
||||
{
|
||||
if (widget != PROGRAM_WIDGET_INSTRUCTION_LIST) return;
|
||||
|
||||
int y = r.top + WD_FRAMERECT_TOP;
|
||||
int line_height = this->GetWidget<NWidgetBase>(PROGRAM_WIDGET_INSTRUCTION_LIST)->resize_y;
|
||||
|
||||
int no = this->vscroll->GetPosition();
|
||||
|
||||
for (const GuiInstruction *i = instructions.Begin() + no, *e = instructions.End();
|
||||
i != e; ++i, no++) {
|
||||
/* Don't draw anything if it extends past the end of the window. */
|
||||
if (!this->vscroll->IsVisible(no)) break;
|
||||
|
||||
DrawInstructionString(i->insn, y, no == this->selected_instruction, i->indent, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT);
|
||||
y += line_height;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnInvalidateData(int data, bool gui_scope) OVERRIDE {
|
||||
if (gui_scope) {
|
||||
this->RebuildInstructionList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual void SetStringParameters(int widget) const OVERRIDE
|
||||
{
|
||||
switch (widget) {
|
||||
case PROGRAM_WIDGET_COND_VALUE: {
|
||||
SetDParam(0, 0);
|
||||
SignalInstruction *insn = this->GetSelected();
|
||||
if (!insn || insn->Opcode() != PSO_IF) return;
|
||||
SignalIf *si = static_cast<SignalIf*>(insn);
|
||||
if (!IsConditionComparator(si->condition)) return;
|
||||
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(si->condition);
|
||||
SetDParam(0, vc->value);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SignalInstruction *GetSelected() const
|
||||
{
|
||||
if (this->selected_instruction == -1
|
||||
|| this->selected_instruction >= int(this->instructions.Length()))
|
||||
return NULL;
|
||||
|
||||
return this->instructions[this->selected_instruction].insn;
|
||||
}
|
||||
|
||||
Owner GetOwner()
|
||||
{
|
||||
return GetTileOwner(this->tile);
|
||||
}
|
||||
|
||||
int GetInstructionFromPt(int y)
|
||||
{
|
||||
NWidgetBase *nwid = this->GetWidget<NWidgetBase>(PROGRAM_WIDGET_INSTRUCTION_LIST);
|
||||
int sel = (y - nwid->pos_y - WD_FRAMERECT_TOP) / nwid->resize_y; // Selected line
|
||||
|
||||
if ((uint)sel >= this->vscroll->GetCapacity()) return -1;
|
||||
|
||||
sel += this->vscroll->GetPosition();
|
||||
|
||||
return (sel <= int(this->instructions.Length()) && sel >= 0) ? sel : -1;
|
||||
}
|
||||
|
||||
void RebuildInstructionList()
|
||||
{
|
||||
uint old_len = this->instructions.Length();
|
||||
this->instructions.Clear();
|
||||
SignalInstruction *insn = program->first_instruction;
|
||||
uint indent = 0;
|
||||
|
||||
do {
|
||||
DEBUG(misc, 5, "PSig Gui: Opcode %d", insn->Opcode());
|
||||
switch (insn->Opcode()) {
|
||||
case PSO_FIRST:
|
||||
case PSO_LAST: {
|
||||
SignalSpecial *s = static_cast<SignalSpecial*>(insn);
|
||||
GuiInstruction *gi = this->instructions.Append();
|
||||
gi->insn = s;
|
||||
gi->indent = indent;
|
||||
insn = s->next;
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_IF: {
|
||||
SignalIf *i = static_cast<SignalIf*>(insn);
|
||||
GuiInstruction *gi = this->instructions.Append();
|
||||
gi->insn = i;
|
||||
gi->indent = indent++;
|
||||
insn = i->if_true;
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_IF_ELSE: {
|
||||
SignalIf::PseudoInstruction *p = static_cast<SignalIf::PseudoInstruction*>(insn);
|
||||
GuiInstruction *gi = this->instructions.Append();
|
||||
gi->insn = p;
|
||||
gi->indent = indent - 1;
|
||||
insn = p->block->if_false;
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_IF_ENDIF: {
|
||||
SignalIf::PseudoInstruction *p = static_cast<SignalIf::PseudoInstruction*>(insn);
|
||||
GuiInstruction *gi = this->instructions.Append();
|
||||
gi->insn = p;
|
||||
gi->indent = --indent;
|
||||
insn = p->block->after;
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_SET_SIGNAL: {
|
||||
SignalSet *s = static_cast<SignalSet*>(insn);
|
||||
GuiInstruction *gi = this->instructions.Append();
|
||||
gi->insn = s;
|
||||
gi->indent = indent;
|
||||
insn = s->next;
|
||||
break;
|
||||
}
|
||||
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
} while (insn);
|
||||
|
||||
this->vscroll->SetCount(this->instructions.Length());
|
||||
if (this->instructions.Length() != old_len)
|
||||
selected_instruction = -1;
|
||||
UpdateButtonState();
|
||||
}
|
||||
|
||||
void UpdateButtonState()
|
||||
{
|
||||
// Do not close the Signals GUI when opening the ProgrammableSignals GUI
|
||||
// ResetObjectToPlace();
|
||||
this->RaiseWidget(PROGRAM_WIDGET_INSERT);
|
||||
this->RaiseWidget(PROGRAM_WIDGET_REMOVE);
|
||||
this->RaiseWidget(PROGRAM_WIDGET_SET_STATE);
|
||||
this->RaiseWidget(PROGRAM_WIDGET_COND_VARIABLE);
|
||||
this->RaiseWidget(PROGRAM_WIDGET_COND_COMPARATOR);
|
||||
this->RaiseWidget(PROGRAM_WIDGET_COND_VALUE);
|
||||
this->RaiseWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
|
||||
|
||||
NWidgetStacked *left_sel = this->GetWidget<NWidgetStacked>(PROGRAM_WIDGET_SEL_TOP_LEFT);
|
||||
NWidgetStacked *middle_sel = this->GetWidget<NWidgetStacked>(PROGRAM_WIDGET_SEL_TOP_MIDDLE);
|
||||
NWidgetStacked *right_sel = this->GetWidget<NWidgetStacked>(PROGRAM_WIDGET_SEL_TOP_RIGHT);
|
||||
|
||||
// Disable all the modifier buttons - we will re-enable them if applicable
|
||||
this->DisableWidget(PROGRAM_WIDGET_SET_STATE);
|
||||
this->DisableWidget(PROGRAM_WIDGET_COND_VARIABLE);
|
||||
this->DisableWidget(PROGRAM_WIDGET_COND_COMPARATOR);
|
||||
this->DisableWidget(PROGRAM_WIDGET_COND_VALUE);
|
||||
this->DisableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL);
|
||||
this->DisableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
|
||||
|
||||
// Don't allow modifications if don't own, or have selected invalid instruction
|
||||
if (this->GetOwner() != _local_company || this->selected_instruction < 1) {
|
||||
this->DisableWidget(PROGRAM_WIDGET_INSERT);
|
||||
this->DisableWidget(PROGRAM_WIDGET_REMOVE);
|
||||
this->SetDirty();
|
||||
return;
|
||||
} else {
|
||||
this->EnableWidget(PROGRAM_WIDGET_INSERT);
|
||||
this->EnableWidget(PROGRAM_WIDGET_REMOVE);
|
||||
}
|
||||
|
||||
SignalInstruction *insn = GetSelected();
|
||||
if (!insn) return;
|
||||
|
||||
switch (insn->Opcode()) {
|
||||
case PSO_IF: {
|
||||
SignalIf *i = static_cast<SignalIf*>(insn);
|
||||
left_sel->SetDisplayedPlane(DPL_COND_VARIABLE);
|
||||
middle_sel->SetDisplayedPlane(DPM_COND_COMPARATOR);
|
||||
right_sel->SetDisplayedPlane(DPR_COND_VALUE);
|
||||
|
||||
this->EnableWidget(PROGRAM_WIDGET_COND_VARIABLE);
|
||||
this->GetWidget<NWidgetCore>(PROGRAM_WIDGET_COND_VARIABLE)->widget_data =
|
||||
_program_condvar[i->condition->ConditionCode()];
|
||||
|
||||
if (IsConditionComparator(i->condition)) {
|
||||
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(i->condition);
|
||||
this->EnableWidget(PROGRAM_WIDGET_COND_COMPARATOR);
|
||||
this->EnableWidget(PROGRAM_WIDGET_COND_VALUE);
|
||||
|
||||
this->GetWidget<NWidgetCore>(PROGRAM_WIDGET_COND_COMPARATOR)->widget_data =
|
||||
_program_comparator[vc->comparator];
|
||||
|
||||
} else if (i->condition->ConditionCode() == PSC_SIGNAL_STATE) {
|
||||
this->EnableWidget(PROGRAM_WIDGET_COND_GOTO_SIGNAL);
|
||||
this->EnableWidget(PROGRAM_WIDGET_COND_SET_SIGNAL);
|
||||
middle_sel->SetDisplayedPlane(DPM_COND_GOTO_SIGNAL);
|
||||
right_sel->SetDisplayedPlane(DPR_COND_SET_SIGNAL);
|
||||
}
|
||||
} break;
|
||||
|
||||
case PSO_SET_SIGNAL: {
|
||||
SignalSet *s = static_cast<SignalSet*>(insn);
|
||||
left_sel->SetDisplayedPlane(DPL_SET_STATE);
|
||||
this->SetWidgetDisabledState(PROGRAM_WIDGET_SET_STATE, false);
|
||||
this->GetWidget<NWidgetCore>(PROGRAM_WIDGET_SET_STATE)->widget_data =
|
||||
_program_sigstate[s->to_state];
|
||||
} break;
|
||||
|
||||
case PSO_FIRST:
|
||||
case PSO_LAST:
|
||||
case PSO_IF_ELSE:
|
||||
case PSO_IF_ENDIF:
|
||||
// All cannot be modified
|
||||
this->DisableWidget(PROGRAM_WIDGET_REMOVE);
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
this->SetDirty();
|
||||
}
|
||||
|
||||
TileIndex tile;
|
||||
Track track;
|
||||
SignalProgram *program;
|
||||
GuiInstructionList instructions;
|
||||
int selected_instruction;
|
||||
Scrollbar *vscroll;
|
||||
};
|
||||
|
||||
static const NWidgetPart _nested_program_widgets[] = {
|
||||
// Title bar
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_CLOSEBOX, COLOUR_GREY),
|
||||
NWidget(WWT_CAPTION, COLOUR_GREY, PROGRAM_WIDGET_CAPTION), SetDataTip(STR_PROGSIG_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
|
||||
NWidget(WWT_SHADEBOX, COLOUR_GREY),
|
||||
NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
|
||||
NWidget(WWT_STICKYBOX, COLOUR_GREY),
|
||||
EndContainer(),
|
||||
|
||||
// Program display
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_PANEL, COLOUR_GREY, PROGRAM_WIDGET_INSTRUCTION_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_PROGSIG_CAPTION), SetResize(1, 1), EndContainer(),
|
||||
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, PROGRAM_WIDGET_SCROLLBAR),
|
||||
EndContainer(),
|
||||
|
||||
// Button Bar
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
|
||||
NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_LEFT),
|
||||
NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_VARIABLE), SetMinimalSize(124, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_NULL, STR_PROGSIG_COND_VARIABLE_TOOLTIP), SetResize(1, 0),
|
||||
NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_SET_STATE), SetMinimalSize(124, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_NULL, STR_PROGSIG_SIGNAL_STATE_TOOLTIP), SetResize(1, 0),
|
||||
EndContainer(),
|
||||
NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_MIDDLE),
|
||||
NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_COND_COMPARATOR), SetMinimalSize(124, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_NULL, STR_PROGSIG_COND_COMPARATOR_TOOLTIP), SetResize(1, 0),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_GOTO_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_PROGSIG_GOTO_SIGNAL, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP), SetResize(1, 0),
|
||||
EndContainer(),
|
||||
NWidget(NWID_SELECTION, INVALID_COLOUR, PROGRAM_WIDGET_SEL_TOP_RIGHT),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_VALUE), SetMinimalSize(124, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_BLACK_COMMA, STR_PROGSIG_COND_VALUE_TOOLTIP), SetResize(1, 0),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COND_SET_SIGNAL), SetMinimalSize(124, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_PROGSIG_COND_SET_SIGNAL, STR_PROGSIG_COND_SET_SIGNAL_TOOLTIP), SetResize(1, 0),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
NWidget(WWT_IMGBTN, COLOUR_GREY, PROGRAM_WIDGET_GOTO_SIGNAL), SetMinimalSize(12, 12), SetDataTip(SPR_ARROW_RIGHT, STR_PROGSIG_GOTO_SIGNAL_TOOLTIP),
|
||||
EndContainer(),
|
||||
|
||||
/* Second button row. */
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
|
||||
NWidget(WWT_DROPDOWN, COLOUR_GREY, PROGRAM_WIDGET_INSERT), SetMinimalSize(124, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_PROGSIG_INSERT, STR_PROGSIG_INSERT_TOOLTIP), SetResize(1, 0),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE), SetMinimalSize(186, 12), SetFill(1, 0),
|
||||
SetDataTip(STR_PROGSIG_REMOVE, STR_PROGSIG_REMOVE_TOOLTIP), SetResize(1, 0),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
|
||||
/* Third button row*/
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_REMOVE_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_REMOVE_PROGRAM, STR_PROGSIG_REMOVE_PROGRAM_TOOLTIP), SetResize(1, 0),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, PROGRAM_WIDGET_COPY_PROGRAM), SetMinimalSize(124, 12), SetFill(1, 0), SetDataTip(STR_PROGSIG_COPY_PROGRAM, STR_PROGSIG_COPY_PROGRAM_TOOLTIP), SetResize(1, 0),
|
||||
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
|
||||
EndContainer(),
|
||||
};
|
||||
|
||||
static WindowDesc _program_desc(
|
||||
WDP_AUTO, "signal_program", 384, 100,
|
||||
WC_SIGNAL_PROGRAM, WC_BUILD_SIGNAL,
|
||||
WDF_CONSTRUCTION,
|
||||
_nested_program_widgets, lengthof(_nested_program_widgets)
|
||||
);
|
||||
|
||||
void ShowSignalProgramWindow(SignalReference ref)
|
||||
{
|
||||
uint32 window_id = (ref.tile << 3) | ref.track;
|
||||
if (BringWindowToFrontById(WC_SIGNAL_PROGRAM, window_id) != NULL) return;
|
||||
|
||||
new ProgramWindow(&_program_desc, ref);
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
/* $Id$ */
|
||||
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file signal_sl.cpp Code handling saving and loading of signals */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "../programmable_signals.h"
|
||||
#include "../core/alloc_type.hpp"
|
||||
#include "../core/bitmath_func.hpp"
|
||||
#include <vector>
|
||||
#include "saveload.h"
|
||||
|
||||
typedef std::vector<byte> Buffer;
|
||||
|
||||
// Variable length integers are stored in Variable Length Quantity
|
||||
// format (http://en.wikipedia.org/wiki/Variable-length_quantity)
|
||||
|
||||
static void WriteVLI(Buffer &b, uint i)
|
||||
{
|
||||
uint lsmask = 0x7F;
|
||||
uint msmask = ~0x7F;
|
||||
while(i & msmask) {
|
||||
byte part = (i & lsmask) | 0x80;
|
||||
b.push_back(part);
|
||||
i >>= 7;
|
||||
}
|
||||
b.push_back((byte) i);
|
||||
}
|
||||
|
||||
static uint ReadVLI()
|
||||
{
|
||||
uint shift = 0;
|
||||
uint val = 0;
|
||||
byte b;
|
||||
|
||||
b = SlReadByte();
|
||||
while(b & 0x80) {
|
||||
val |= uint(b & 0x7F) << shift;
|
||||
shift += 7;
|
||||
b = SlReadByte();
|
||||
}
|
||||
val |= uint(b) << shift;
|
||||
return val;
|
||||
}
|
||||
|
||||
static void WriteCondition(Buffer &b, SignalCondition *c)
|
||||
{
|
||||
WriteVLI(b, c->ConditionCode());
|
||||
switch(c->ConditionCode()) {
|
||||
case PSC_NUM_GREEN:
|
||||
case PSC_NUM_RED: {
|
||||
SignalVariableCondition *vc = static_cast<SignalVariableCondition*>(c);
|
||||
WriteVLI(b, vc->comparator);
|
||||
WriteVLI(b, vc->value);
|
||||
} break;
|
||||
|
||||
case PSC_SIGNAL_STATE: {
|
||||
SignalStateCondition *sc = static_cast<SignalStateCondition*>(c);
|
||||
WriteVLI(b, sc->sig_tile);
|
||||
WriteVLI(b, sc->sig_track);
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static SignalCondition *ReadCondition(SignalReference this_sig)
|
||||
{
|
||||
SignalConditionCode code = (SignalConditionCode) ReadVLI();
|
||||
switch(code) {
|
||||
case PSC_NUM_GREEN:
|
||||
case PSC_NUM_RED: {
|
||||
SignalVariableCondition *c = new SignalVariableCondition(code);
|
||||
c->comparator = (SignalComparator) ReadVLI();
|
||||
if(c->comparator > SGC_LAST) NOT_REACHED();
|
||||
c->value = ReadVLI();
|
||||
return c;
|
||||
}
|
||||
|
||||
case PSC_SIGNAL_STATE: {
|
||||
TileIndex ti = (TileIndex) ReadVLI();
|
||||
Trackdir td = (Trackdir) ReadVLI();
|
||||
return new SignalStateCondition(this_sig, ti, td);
|
||||
}
|
||||
|
||||
default:
|
||||
return new SignalSimpleCondition(code);
|
||||
}
|
||||
}
|
||||
|
||||
static void Save_SPRG()
|
||||
{
|
||||
// Check for, and dispose of, any signal information on a tile which doesn't have signals.
|
||||
// This indicates that someone removed the signals from the tile but didn't clean them up.
|
||||
// (This code is to detect bugs and limit their consquences, not to cover them up!)
|
||||
for(ProgramList::iterator i = _signal_programs.begin(), e = _signal_programs.end();
|
||||
i != e; ++i) {
|
||||
SignalReference ref = i->first;
|
||||
if(!HasProgrammableSignals(ref)) {
|
||||
DEBUG(sl, 0, "Programmable signal information for (%x, %d) has been leaked!",
|
||||
ref.tile, ref.track);
|
||||
++i;
|
||||
FreeSignalProgram(ref);
|
||||
if(i == e) break;
|
||||
}
|
||||
}
|
||||
|
||||
// OK, we can now write out our programs
|
||||
Buffer b;
|
||||
WriteVLI(b, _signal_programs.size());
|
||||
for(ProgramList::iterator i = _signal_programs.begin(), e = _signal_programs.end();
|
||||
i != e; ++i) {
|
||||
SignalReference ref = i->first;
|
||||
SignalProgram *prog = i->second;
|
||||
|
||||
prog->DebugPrintProgram();
|
||||
|
||||
WriteVLI(b, prog->tile);
|
||||
WriteVLI(b, prog->track);
|
||||
WriteVLI(b, prog->instructions.Length());
|
||||
for(SignalInstruction **j = prog->instructions.Begin(), **je = prog->instructions.End();
|
||||
j != je; ++j) {
|
||||
SignalInstruction *insn = *j;
|
||||
WriteVLI(b, insn->Opcode());
|
||||
if(insn->Opcode() != PSO_FIRST)
|
||||
WriteVLI(b, insn->Previous()->Id());
|
||||
switch(insn->Opcode()) {
|
||||
case PSO_FIRST: {
|
||||
SignalSpecial *s = static_cast<SignalSpecial*>(insn);
|
||||
WriteVLI(b, s->next->Id());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_LAST: break;
|
||||
|
||||
case PSO_IF: {
|
||||
SignalIf *i = static_cast<SignalIf*>(insn);
|
||||
WriteCondition(b, i->condition);
|
||||
WriteVLI(b, i->if_true->Id());
|
||||
WriteVLI(b, i->if_false->Id());
|
||||
WriteVLI(b, i->after->Id());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_IF_ELSE:
|
||||
case PSO_IF_ENDIF: {
|
||||
SignalIf::PseudoInstruction *p = static_cast<SignalIf::PseudoInstruction*>(insn);
|
||||
WriteVLI(b, p->block->Id());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_SET_SIGNAL: {
|
||||
SignalSet *s = static_cast<SignalSet*>(insn);
|
||||
WriteVLI(b, s->next->Id());
|
||||
WriteVLI(b, s->to_state ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint size = b.size();
|
||||
SlSetLength(size);
|
||||
for(uint i = 0; i < size; i++)
|
||||
SlWriteByte(b[i]); // TODO Gotta be a better way
|
||||
}
|
||||
|
||||
// We don't know the pointer values that need to be stored in various
|
||||
// instruction fields at load time, so we need to instead store the IDs and
|
||||
// then fix them up once all of the instructions have been loaded.
|
||||
//
|
||||
// Additionally, we store the opcode type we expect (if we expect a specific one)
|
||||
// to check for consistency (For example, an If Pseudo Instruction's block should
|
||||
// point at an If!)
|
||||
struct Fixup {
|
||||
Fixup(SignalInstruction **p, SignalOpcode type)
|
||||
: type(type), ptr(p)
|
||||
{}
|
||||
|
||||
SignalOpcode type;
|
||||
SignalInstruction **ptr;
|
||||
};
|
||||
|
||||
typedef SmallVector<Fixup, 4> FixupList;
|
||||
|
||||
template<typename T>
|
||||
static void MakeFixup(FixupList &l, T *&ir, uint id, SignalOpcode op = PSO_INVALID)
|
||||
{
|
||||
ir = reinterpret_cast<T*>(id);
|
||||
new(l.Append()) Fixup(reinterpret_cast<SignalInstruction**>(&ir), op);
|
||||
}
|
||||
|
||||
static void DoFixups(FixupList &l, InstructionList &il)
|
||||
{
|
||||
for(Fixup *i = l.Begin(), *e = l.End(); i != e; ++i) {
|
||||
uint id = reinterpret_cast<size_t>(*i->ptr);
|
||||
if(id >= il.Length())
|
||||
NOT_REACHED();
|
||||
|
||||
*i->ptr = il[id];
|
||||
|
||||
if(i->type != PSO_INVALID && (*i->ptr)->Opcode() != i->type) {
|
||||
DEBUG(sl, 0, "Expected Id %d to be %d, but was in fact %d", id, i->type, (*i->ptr)->Opcode());
|
||||
NOT_REACHED();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Load_SPRG()
|
||||
{
|
||||
uint count = ReadVLI();
|
||||
for(uint i = 0; i < count; i++) {
|
||||
FixupList l;
|
||||
TileIndex tile = ReadVLI();
|
||||
Track track = (Track) ReadVLI();
|
||||
uint instructions = ReadVLI();
|
||||
SignalReference ref(tile, track);
|
||||
|
||||
SignalProgram *sp = new SignalProgram(tile, track, true);
|
||||
_signal_programs[ref] = sp;
|
||||
|
||||
for(uint j = 0; j < instructions; j++) {
|
||||
SignalOpcode op = (SignalOpcode) ReadVLI();
|
||||
switch(op) {
|
||||
case PSO_FIRST: {
|
||||
sp->first_instruction = new SignalSpecial(sp, PSO_FIRST);
|
||||
sp->first_instruction->GetPrevHandle() = NULL;
|
||||
MakeFixup(l, sp->first_instruction->next, ReadVLI());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_LAST: {
|
||||
sp->last_instruction = new SignalSpecial(sp, PSO_LAST);
|
||||
sp->last_instruction->next = NULL;
|
||||
MakeFixup(l, sp->last_instruction->GetPrevHandle(), ReadVLI());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_IF: {
|
||||
SignalIf *i = new SignalIf(sp, true);
|
||||
MakeFixup(l, i->GetPrevHandle(), ReadVLI());
|
||||
i->condition = ReadCondition(ref);
|
||||
MakeFixup(l, i->if_true, ReadVLI());
|
||||
MakeFixup(l, i->if_false, ReadVLI());
|
||||
MakeFixup(l, i->after, ReadVLI());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_IF_ELSE:
|
||||
case PSO_IF_ENDIF: {
|
||||
SignalIf::PseudoInstruction *p = new SignalIf::PseudoInstruction(sp, op);
|
||||
MakeFixup(l, p->GetPrevHandle(), ReadVLI());
|
||||
MakeFixup(l, p->block, ReadVLI(), PSO_IF);
|
||||
break;
|
||||
}
|
||||
|
||||
case PSO_SET_SIGNAL: {
|
||||
SignalSet *s = new SignalSet(sp);
|
||||
MakeFixup(l, s->GetPrevHandle(), ReadVLI());
|
||||
MakeFixup(l, s->next, ReadVLI());
|
||||
s->to_state = (SignalState) ReadVLI();
|
||||
if(s->to_state > SIGNAL_STATE_MAX) NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
DoFixups(l, sp->instructions);
|
||||
sp->DebugPrintProgram();
|
||||
}
|
||||
}
|
||||
|
||||
extern const ChunkHandler _signal_chunk_handlers[] = {
|
||||
{ 'SPRG', Save_SPRG, Load_SPRG, NULL, NULL, CH_RIFF | CH_LAST},
|
||||
};
|
Loading…
Reference in New Issue