Added a frame for extended signals parsing

+Added SignalElements class parsing and storing extended information about signals
+Added support for standard and dwarf EHA/ERI signals extended parsing (excluding eha_top_l, eha_top_r, eha_ps_k and eri_ps_k prefabs) (signs are not being parsed yet)
+Added SignalElementsEnums const that will help to translate different signal types into one unified structure
This commit is contained in:
izawartka 2025-06-26 16:55:06 +02:00
parent 480d838e61
commit 422c038cfa
9 changed files with 256 additions and 3 deletions

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100mm" height="330mm" version="1.1" viewBox="0 0 100 330" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="28" r="16" fill="none" stroke="#808080" stroke-linejoin="round" stroke-width="8"/>
<circle cx="50" cy="60" r="16" fill="none" stroke="#808080" stroke-linejoin="round" stroke-width="8"/>
<circle cx="50" cy="92" r="16" fill="none" stroke="#808080" stroke-linejoin="round" stroke-width="8"/>
<circle cx="50" cy="124" r="16" fill="none" stroke="#808080" stroke-linejoin="round" stroke-width="8"/>
<circle cx="50" cy="156" r="16" fill="none" stroke="#808080" stroke-linejoin="round" stroke-width="8"/>
<path d="m50 170.67v143.33" fill="none" stroke="#808080" stroke-linejoin="round" stroke-width="8"/>
<path d="m38 318h24" fill="none" stroke="#808080" stroke-linecap="round" stroke-linejoin="round" stroke-width="8"/>
</svg>

After

Width:  |  Height:  |  Size: 892 B

View File

@ -36,6 +36,8 @@ const Constants = {
electrificationMissingRouteTracks: true,
electrificationResolverError: true,
electrificationResolverWarnings: true,
signalElemsUnknownPrefab: true,
signalElemsRecognizedUnknownPrefab: true,
},
errors: {
invalidSceneryInfo: true

View File

@ -0,0 +1,39 @@
const SignalElementsEnums = {
Type: {
UNKNOWN: 0,
STANDARD: 1,
DWARF: 2,
TOP: 4,
},
HeadPosition: {
UNKNOWN: 0,
STANDARD: 1,
RIGHT: 2,
LEFT: 3,
},
UnitType: {
UNKNOWN: 0,
WHITE: 1,
RED: 2,
BLUE: 3,
YELLOW_UPPER: 4,
YELLOW_LOWER: 5,
DWARF_YELLOW_S13: 6,
INNER: 7,
DWARF_W24: 8,
GREEN: 9,
OLD_GREEN: 10,
UNUSED: 11,
BROKEN: 12,
NONE: 13,
},
BarType: {
UNKNOWN: 0,
NONE: 1,
YELLOW: 2,
GREEN: 3,
YELLOW_GREEN: 4,
}
}
export default SignalElementsEnums;

View File

@ -0,0 +1,15 @@
import SignalElementsParserEhaEriDwarf from "./parsers/eha-eri-dwarf";
import SignalElementsParserEhaEriStandard from "./parsers/eha-eri-standard";
const SignalElementsParsersList = {
'eha-eri': {
name: 'EHA/ERI',
regex: /^(eha_|eri_)/,
subtypes: [
SignalElementsParserEhaEriStandard,
SignalElementsParserEhaEriDwarf
]
}
};
export default SignalElementsParsersList;

View File

@ -0,0 +1,94 @@
import SignalElementsEnums from '../enums';
export default class SignalElementsParserCommon {
static getAfterLastUnderscoreNoVersion(entry) {
entry = entry?.trim();
entry = entry?.replace(/_(old|new)$/, '');
const afterLastUnderscore = entry?.lastIndexOf('_') + 1;
return entry?.substring(afterLastUnderscore) || null;
}
static getHeadPosition(poleString) {
const headPosition = this.getAfterLastUnderscoreNoVersion(poleString);
if (!headPosition) return SignalElementsEnums.HeadPosition.UNKNOWN;
switch (headPosition) {
case 'p':
return SignalElementsEnums.HeadPosition.STANDARD;
case 'r':
return SignalElementsEnums.HeadPosition.RIGHT;
case 'l':
return SignalElementsEnums.HeadPosition.LEFT;
default:
return SignalElementsEnums.HeadPosition.UNKNOWN;
}
}
static getUnitTypeFromTypeString(typeString) {
typeString = typeString?.trim();
if(!typeString) return SignalElementsEnums.UnitType.NONE;
switch (typeString) {
case 'b':
return SignalElementsEnums.UnitType.WHITE;
case 'c':
return SignalElementsEnums.UnitType.RED;
case 'n':
return SignalElementsEnums.UnitType.BLUE;
case 'p1':
return SignalElementsEnums.UnitType.YELLOW_UPPER;
case 'p2':
return SignalElementsEnums.UnitType.YELLOW_LOWER;
case 'p3':
return SignalElementsEnums.UnitType.DWARF_YELLOW_S13;
case 'wew':
return SignalElementsEnums.UnitType.INNER;
case 'w24':
return SignalElementsEnums.UnitType.DWARF_W24;
case 'z':
return SignalElementsEnums.UnitType.GREEN;
case 'z2':
return SignalElementsEnums.UnitType.OLD_GREEN;
case 'zas':
return SignalElementsEnums.UnitType.UNUSED;
case 'zb':
return SignalElementsEnums.UnitType.BROKEN;
default:
return SignalElementsEnums.UnitType.UNKNOWN;
}
}
static getUnits(entries, start, count, step) {
const units = [];
for (let i = 0; i < count; i++) {
const index = start + i * step;
if (index >= entries.length) break;
const typeString = this.getAfterLastUnderscoreNoVersion(entries[index]);
const type = this.getUnitTypeFromTypeString(typeString);
units[i] = type;
}
while (units.length > 0 && units[units.length - 1] === SignalElementsEnums.UnitType.NONE) {
units.pop();
}
return units;
}
static getBarType(barString) {
const typeString = this.getAfterLastUnderscoreNoVersion(barString);
switch (typeString) {
case 'p':
return SignalElementsEnums.BarType.YELLOW;
case 'z':
return SignalElementsEnums.BarType.GREEN;
case 'pz':
return SignalElementsEnums.BarType.YELLOW_GREEN;
default:
return SignalElementsEnums.BarType.UNKNOWN;
}
}
}

View File

@ -0,0 +1,24 @@
import SignalElementsEnums from "../enums";
import SignalElements from "../signal-elements";
import SignalElementsParserCommon from "./common";
export default class SignalElementsParserEhaEriDwarf {
static parsers = [];
static isAppliable(prefabName) {
const regex = /^(eha|eri)_(sp|tm|to)_k$/;
return regex.test(prefabName);
}
static fromPrefabText(id, text) {
const entries = text.split(",");
return new SignalElements(
SignalElementsEnums.Type.DWARF,
SignalElementsEnums.HeadPosition.STANDARD,
SignalElementsParserCommon.getUnits(entries, 2, 3, 2),
SignalElementsEnums.BarType.NONE,
[]
);
}
}

View File

@ -0,0 +1,24 @@
import SignalElementsEnums from "../enums";
import SignalElements from "../signal-elements";
import SignalElementsParserCommon from "./common";
export default class SignalElementsParserEhaEriStandard {
static parsers = [];
static isAppliable(prefabName) {
const regex = /^(eha|eri)_(ps|sbl|sp|tm|to)_(p|r|l)$/;
return regex.test(prefabName);
}
static fromPrefabText(id, text) {
const entries = text.split(",");
return new SignalElements(
SignalElementsEnums.Type.STANDARD,
SignalElementsParserCommon.getHeadPosition(entries[1]),
SignalElementsParserCommon.getUnits(entries, 6, 6, 1),
SignalElementsParserCommon.getBarType(entries[12]),
[]
);
}
}

View File

@ -0,0 +1,41 @@
import SceneryParserLog from "../scenery-parser-log";
import SignalElementsEnums from "./enums";
import SignalElementsParsersList from "./parsers-list";
export default class SignalElements {
type = SignalElementsEnums.Type.UNKNOWN;
headPosition = SignalElementsEnums.HeadPosition.UNKNOWN;
units = [];
bar;
signs = [];
constructor(type, headPosition, units = [], bar = null, signs = []) {
Object.assign(this, {
type,
headPosition,
units,
bar,
signs
});
}
static fromPrefabText(id, text) {
const prefabName = text.split(",", 2)[0].trim();
for(const parser of Object.values(SignalElementsParsersList)) {
if (!parser.regex.test(prefabName)) continue;
for(const subtype of parser.subtypes) {
if (subtype.isAppliable(prefabName)) {
return subtype.fromPrefabText(id, text);
}
}
SceneryParserLog.warn('signalElemsRecognizedUnknownPrefab', `Signal ${id} recognized as ${parser.name} but has unknown prefab name: ${prefabName}`);
return null;
}
SceneryParserLog.warn('signalElemsUnknownPrefab', `Signal ${id} has unknown prefab name: ${prefabName}`);
return null;
}
}

View File

@ -2,21 +2,24 @@ import SceneryParserLog from "../scenery-parser-log";
import SpawnInfo from "../spawn-info";
import TrackObject from "./track-object";
import Vector3 from "../vector3";
import SignalElements from "../signal_elements/signal-elements";
export default class Signal extends TrackObject {
is_spawn;
spawn_info;
signal_name;
signal_elements;
type = "Signal";
applied = false;
constructor(id, prefab_name, pos, rot, track_id, name, is_spawn, spawn_info, signal_name) {
constructor(id, prefab_name, pos, rot, track_id, name, is_spawn, spawn_info, signal_name, signal_elements) {
super(id, prefab_name, pos, rot, track_id, name);
Object.assign(this, {
is_spawn,
spawn_info: is_spawn ? spawn_info : null,
signal_name
signal_name,
signal_elements
});
if (this.is_spawn && !this.spawn_info) {
@ -75,7 +78,8 @@ export default class Signal extends TrackObject {
values[11], // name,
values[12] === "Spawn Signal", // is_spawn
SpawnInfo.fromText(values[15]), // spawn_info
values[29]
values[29],
SignalElements.fromPrefabText(values[1], values[2]) // signal_elements
);
return signal;