From 7f279bf35d31c6679bc01645ac46c5e191d3b501 Mon Sep 17 00:00:00 2001 From: izawartka <59137928+izawartka@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:45:18 +0200 Subject: [PATCH] Added SignalBoxes, fixed bugs +Added Misc objects parser +Added SignalBoxRenderer *Fixed Switch and TrackObject double apply protection *Updated scp.py to not exclude Signalboxes --- public/assets/signalbox.svg | 7 ++++ src/components/map/Map.css | 9 +++++ src/components/map/ObjectsRenderQueue.js | 7 ++++ .../map/object-renderers/SignalBoxRenderer.js | 34 ++++++++++++++++++ src/helpers/constants.js | 5 +++ src/model/misc.js | 35 +++++++++++++++++++ src/model/scenery.js | 4 +++ src/model/signal.js | 5 +++ src/model/switch.js | 1 + src/model/track-object.js | 4 +++ tools/scp.py | 16 +++++++-- 11 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 public/assets/signalbox.svg create mode 100644 src/components/map/object-renderers/SignalBoxRenderer.js create mode 100644 src/model/misc.js diff --git a/public/assets/signalbox.svg b/public/assets/signalbox.svg new file mode 100644 index 0000000..643c9b4 --- /dev/null +++ b/public/assets/signalbox.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/map/Map.css b/src/components/map/Map.css index 66fe93d..ae6dd19 100644 --- a/src/components/map/Map.css +++ b/src/components/map/Map.css @@ -45,4 +45,13 @@ .map svg g.route text { fill: #eee; font-size: 72mm; +} + +.map svg g.signalbox { + fill: #eee; +} + +.map svg g.signalbox text { + fill: #eee; + font-size: 4px; } \ No newline at end of file diff --git a/src/components/map/ObjectsRenderQueue.js b/src/components/map/ObjectsRenderQueue.js index e0a2b5d..a32522a 100644 --- a/src/components/map/ObjectsRenderQueue.js +++ b/src/components/map/ObjectsRenderQueue.js @@ -4,6 +4,7 @@ import IsolationIdRenderer from './object-renderers/IsolationIdRenderer'; import TrackObjectRenderer from './object-renderers/TrackObjectRenderer'; import SignalRenderer from './object-renderers/SignalRenderer'; import RouteRenderer from './object-renderers/RouteRenderer'; +import SignalBoxRenderer from './object-renderers/SignalBoxRenderer'; const ObjectRendererQueue = [ { @@ -38,6 +39,12 @@ const ObjectRendererQueue = [ 'renderer': SignalRenderer, 'cond': (settings) => settings.layers['signals'] }, + { + 'name': 'signalboxes', + 'category': 'misc', + 'renderer': SignalBoxRenderer, + 'cond': (settings) => settings.layers['signalboxes'] + }, { 'name': 'routes', 'category': 'routes', diff --git a/src/components/map/object-renderers/SignalBoxRenderer.js b/src/components/map/object-renderers/SignalBoxRenderer.js new file mode 100644 index 0000000..17dfef3 --- /dev/null +++ b/src/components/map/object-renderers/SignalBoxRenderer.js @@ -0,0 +1,34 @@ +import { ReactSVG } from 'react-svg'; +import AngleHelper from '../../../helpers/angleHelper'; + +export default function SignalBoxRenderer(props) { + const { object } = props; + if (!object.isSignalBox) return null; + const [x, y] = object.pos.toSVGCoords(); + const text = `"${object.name}"`; + + const rot = AngleHelper.normalizeDegAngle(object.rot.y); + const upsideDown = rot >= 90 && rot < 270; + const upsideDownRot = upsideDown ? 180 : 0; + + return ( + + + { + svg.setAttribute('width', '2.5mm'); + svg.setAttribute('height', '2.5mm'); + }} + /> + + + + {text} + + + + ); +} diff --git a/src/helpers/constants.js b/src/helpers/constants.js index da022e1..5b248a7 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -36,6 +36,11 @@ const Constants = { name: 'Signals', default: true, }, + { + id: 'signalboxes', + name: 'Signal boxes', + default: true, + }, { id: 'routes', name: 'Routes', diff --git a/src/model/misc.js b/src/model/misc.js new file mode 100644 index 0000000..621d179 --- /dev/null +++ b/src/model/misc.js @@ -0,0 +1,35 @@ +import SceneryObject from "./scenery-object"; +import Vector3 from "./vector3"; + +export default class Misc extends SceneryObject { + prefab_name; + name; + category = "misc"; + type = "Misc"; + isSignalBox; + + constructor(id, misc_id, prefab_name, pos, rot, name) { + super(id, pos, rot); + Object.assign(this, { + misc_id, + prefab_name, + name, + }); + + this.isSignalBox = prefab_name.startsWith("SignalBox"); + } + + static fromText(id, text) { + const values = text.split(";"); + const object = new Misc( + id, // id + values[1], // misc_id + values[2], // prefab_name + Vector3.fromValuesArray(values, 3), // pos + Vector3.fromValuesArray(values, 6), // rot + values[9] // name + ); + + return object; + } +} diff --git a/src/model/scenery.js b/src/model/scenery.js index bb6dd76..b40f84d 100644 --- a/src/model/scenery.js +++ b/src/model/scenery.js @@ -4,12 +4,14 @@ import BezierTrack from './bezier-track'; import TrackObject from './track-object'; import Signal from './signal'; import Route from './route'; +import Misc from './misc'; import SceneryParserLog from './scenery-parser-log'; export default class Scenery { objects = {}; bounds = { minX: Infinity, minZ: Infinity, maxX: -Infinity, maxZ: -Infinity }; + static nextMiscId = 1; getBounds () { return { ...this.bounds }; @@ -71,6 +73,8 @@ export default class Scenery return Scenery._parseTrackObject(text); case 'Route': return Route.fromText(text); + case 'Misc': + return Misc.fromText(Scenery.nextMiscId++, text); case 'Empty': case 'EndRoute': return null; diff --git a/src/model/signal.js b/src/model/signal.js index f0adf2e..c100cd2 100644 --- a/src/model/signal.js +++ b/src/model/signal.js @@ -1,3 +1,4 @@ +import SceneryParserLog from "./scenery-parser-log"; import SpawnInfo from "./spawn-info"; import TrackObject from "./track-object"; import Vector3 from "./vector3"; @@ -16,6 +17,10 @@ export default class Signal extends TrackObject { spawn_info: is_spawn ? spawn_info : null, signal_name }); + + if (this.is_spawn && !this.spawn_info) { + SceneryParserLog.warn('signalWithoutSpawnInfo', `Signal ${this.id} is marked as spawn but has no spawn info`); + } } getPrintableSignalName() { diff --git a/src/model/switch.js b/src/model/switch.js index dd83370..353305b 100644 --- a/src/model/switch.js +++ b/src/model/switch.js @@ -47,6 +47,7 @@ export default class Switch extends SceneryObject { applyObject(scenery) { if(this.applied) return; // already applied + this.applied = true; this.outs = Switch.getOutsFromData(this.data); const r = Switch.getRadiusFromModel(this.model); diff --git a/src/model/track-object.js b/src/model/track-object.js index 947e1f5..4e5f87b 100644 --- a/src/model/track-object.js +++ b/src/model/track-object.js @@ -9,6 +9,7 @@ export default class TrackObject extends SceneryObject { category = "track-objects"; type = "TrackObject"; track; + applied = false; constructor(id, prefab_name, pos, rot, track_id, name) { super(id, pos, rot); @@ -34,6 +35,9 @@ export default class TrackObject extends SceneryObject { } applyObject(scenery) { + if(this.applied) return; // already applied + this.applied = true; + const track = scenery.getObject('tracks', this.track_id); if (!track) { SceneryParserLog.warn('trackObjectCannotBeApplied', `TrackObject ${this.id} cannot be applied: track ${this.track_id} not found`) diff --git a/tools/scp.py b/tools/scp.py index a3b4f9a..e39c403 100644 --- a/tools/scp.py +++ b/tools/scp.py @@ -4,18 +4,28 @@ import sys # # [ TD2 SCENERY PROCESSOR ] # by masuo -# v1.2 +# v1.3 # -BAD_WORDS = ['Misc', 'Fence', 'TerrainPoint', 'Wires'] +BAD_WORDS = ['Empty,Empty', 'Forest Start,Forest Start', 'EndMiscGroup', 'Fence', 'TerrainPoint', 'Wires'] def should_exclude(line: str) -> bool: - return any(bad_word in line for bad_word in BAD_WORDS) + for word in BAD_WORDS: + if line.startswith(word): + return True + + if line.startswith("Misc"): + values = line.split(';') + return len(values[9].strip()) == 0 or not values[2].startswith("SignalBox") def process_file(file_path: str): if not file_path.endswith(".sc"): print(f"Skipping non-.sc file: {file_path}") return + + if file_path.endswith(".lite.sc"): + print(f"Skipping already processed file: {file_path}") + return output_path = file_path[:-3] + ".lite.sc" print(f"Processing file: {file_path} -> {output_path}")