From c4eac723ba42636889a0baa59f8fdc367aaf1439 Mon Sep 17 00:00:00 2001 From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com> Date: Sat, 12 Jul 2025 14:00:50 +0200 Subject: [PATCH] Add route parsing --- src/helpers/constants.js | 1 + src/model/route.js | 9 ++++-- src/model/scenery-parser.js | 58 ++++++++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 1e66345..ac8e816 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -50,6 +50,7 @@ const Constants = { attachSignsOutOfBounds: true, signalElemsUnknownSign: true, signalElemsUnknownSignText: true, + routeInvalidSegment: true, }, errors: { invalidSceneryInfo: true diff --git a/src/model/route.js b/src/model/route.js index 511692a..11676af 100644 --- a/src/model/route.js +++ b/src/model/route.js @@ -9,6 +9,7 @@ export default class Route extends SceneryObject { category = "routes"; type = "Route"; points = []; + segments = []; constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) { super(route_name, pos, rot); @@ -24,7 +25,7 @@ export default class Route extends SceneryObject { this.points = this._getConnectionPoints(); } - static fromText(text) { + static fromText(text) { const values = text.split(";"); const route = new Route( values[2], // prefab_name @@ -39,6 +40,10 @@ export default class Route extends SceneryObject { return route; } + addSegment(length, radius, trackIds) { + this.segments.push({ length, radius, trackIds }); + } + _getConnectionPoints() { if (this.track_count === 1) { return [this.pos.clone()]; @@ -53,4 +58,4 @@ export default class Route extends SceneryObject { this.pos.add(leftTrackOffset) ]; } -} \ No newline at end of file +} diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js index dbeb723..0e940cd 100644 --- a/src/model/scenery-parser.js +++ b/src/model/scenery-parser.js @@ -29,6 +29,8 @@ export default class SceneryParser { const lines = scText.split("\n").map(line => line.trim()); const scenery = new Scenery(); + let currentRoute = null; + lines.forEach((line, index) => { if(index === 0) { const sceneryInfo = SceneryParser._parseSceneryInfo(line); @@ -37,9 +39,21 @@ export default class SceneryParser { } if(!line?.length) return; - const object = SceneryParser._parseObject(line); - if(!object) return; // skip null objects - scenery.addObject(object); + if (currentRoute !== null) { + const foundRouteEnd = SceneryParser._parseRouteLine(line, currentRoute); + if (foundRouteEnd) { + scenery.addObject(currentRoute); + currentRoute = null; + } + } else { + const object = SceneryParser._parseObject(line); + if(!object) return; // skip null objects + if (object.type === 'Route') { + currentRoute = object; + } else { + scenery.addObject(object); + } + } }); scenery.applyObjects(); @@ -69,7 +83,8 @@ export default class SceneryParser { static _parseObject(text) { const type = text.split(";", 2)[0]; - if(type.indexOf("Forest") !== -1 || type.indexOf("Empty") !== -1) { + if (type === '' || type === 'EndRoute' || type.indexOf("Forest") !== -1 || type.indexOf("Empty") !== -1) { + SceneryParserLog.warn('unknownObjectType', `Unexpected entry of type ${type} without a preceding Route object`); return null; } @@ -88,7 +103,6 @@ export default class SceneryParser { return CameraHome.fromText(text); case 'MainCamera': return MainCamera.fromText(text); - case 'EndRoute': case 'MiscGroup': case 'EndMiscGroup': case 'Wires': @@ -101,12 +115,11 @@ export default class SceneryParser { case 'SSPController': case 'TerrainGroup': case 'EndTerrainGroup': - case '': return null; default: SceneryParserLog.warn('unknownObjectType', `Unknown object type: ${type}`); return null; - }; + } } static _parseTrack(text) { @@ -147,4 +160,35 @@ export default class SceneryParser { return Misc.fromText(Scenery.nextMiscId++, text); } } + + // Returns true if EndRoute was found, false otherwise + static _parseRouteLine(text, route) { + if (text === 'EndRoute') return true; + const fields = text.split(';'); + if (fields.length < 4) { + SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment: "${text}", not enough fields`); + return; + } + const trackIds = fields[3] + .split(',') + .map(segment => { + const id = segment.split(':')[0]; + if (!id) return null; + return parseInt(id); + }); + if (trackIds.some(id => id === null || isNaN(id))) { + SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment track IDs: ${fields[3]}`); + return; + } + if (trackIds.length !== route.track_count) { + SceneryParserLog.warn('routeInvalidSegment', `Route segment has invalid track count: expected ${route.track_count}, got ${trackIds.length}`); + return; + } + route.addSegment( + parseInt(fields[1]), // length + parseInt(fields[2]), // radius + trackIds, + ); + return false; + } }