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}")