From 38ecdbc2e3d7470cad5b244db50a6d384604e327 Mon Sep 17 00:00:00 2001
From: izawartka <59137928+izawartka@users.noreply.github.com>
Date: Sun, 22 Jun 2025 16:30:17 +0200
Subject: [PATCH] Added SpawnPoints
+Added SpawnPoint object parser, layer and icon
*Renamed Scenery.addSpawnSignal to .addSpawnPoint
*Renamed Scenery.spawnSignals to .spawnPoints
*Minor name changes
---
public/assets/spawn.svg | 8 +++
src/components/loclist/LocList.js | 17 ++---
src/components/map/Map.css | 8 +++
src/components/map/ObjectsRenderQueue.js | 8 +++
.../object-renderers/SpawnPointRenderer.js | 33 +++++++++
src/helpers/constants.js | 9 ++-
src/model/scenery-parser.js | 3 +
src/model/scenery.js | 6 +-
src/model/signal.js | 4 +-
src/model/spawn-point.js | 72 +++++++++++++++++++
10 files changed, 153 insertions(+), 15 deletions(-)
create mode 100644 public/assets/spawn.svg
create mode 100644 src/components/map/object-renderers/SpawnPointRenderer.js
create mode 100644 src/model/spawn-point.js
diff --git a/public/assets/spawn.svg b/public/assets/spawn.svg
new file mode 100644
index 0000000..4a6c34e
--- /dev/null
+++ b/public/assets/spawn.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/src/components/loclist/LocList.js b/src/components/loclist/LocList.js
index 91a8f96..62e5fc4 100644
--- a/src/components/loclist/LocList.js
+++ b/src/components/loclist/LocList.js
@@ -17,16 +17,17 @@ export default function LocList(props) {
});
}, [scenery?.signalBoxes]);
- const spawnSignals = useMemo(() => {
- if (!scenery?.spawnSignals) return null;
- return scenery.spawnSignals.map(signal => {
- const pos = signal.pos.toSVGCoords();
- const name = signal.spawn_info.name || signal.getPrintableSignalName();
+ const spawnPoints = useMemo(() => {
+ if (!scenery?.spawnPoints) return null;
+ return scenery.spawnPoints.map(trackObject => {
+ const pos = trackObject.pos.toSVGCoords();
+ const isSignal = trackObject.type === 'Signal';
+ const name = trackObject.spawn_info.name || (isSignal ? trackObject.getPrintableSignalName() : trackObject.getPrintableSpawnPointName());
return (
-
+
)
});
- }, [scenery?.spawnSignals]);
+ }, [scenery?.spawnPoints]);
return (
@@ -34,7 +35,7 @@ export default function LocList(props) {
{ signalBoxes }
- { spawnSignals }
+ { spawnPoints }
);
diff --git a/src/components/map/Map.css b/src/components/map/Map.css
index fab5a76..c7201a4 100644
--- a/src/components/map/Map.css
+++ b/src/components/map/Map.css
@@ -82,3 +82,11 @@
.map svg g.derailer path {
fill: #eee;
}
+
+.map svg g.spawn-point path {
+ fill: #eee;
+}
+
+.map svg g.spawn-point path.background {
+ fill: #111;
+}
diff --git a/src/components/map/ObjectsRenderQueue.js b/src/components/map/ObjectsRenderQueue.js
index 2e6ac95..e6f7d8a 100644
--- a/src/components/map/ObjectsRenderQueue.js
+++ b/src/components/map/ObjectsRenderQueue.js
@@ -9,6 +9,7 @@ import SignRenderer from './object-renderers/SignRenderer';
import NEVPRenderer from './object-renderers/NEVPRenderer';
import DerailerRenderer from './object-renderers/DerailerRenderer';
import ElectrificationStatusPopup from './additional-layer-components/ElectrificationStatusPopup';
+import SpawnPointRenderer from './object-renderers/SpawnPointRenderer';
const ObjectRendererQueue = [
{
@@ -61,6 +62,13 @@ const ObjectRendererQueue = [
'renderer': SignalRenderer,
'cond': (layers) => layers['signals']
},
+ {
+ 'name': 'spawn-points',
+ 'category': 'track-objects',
+ 'type': 'SpawnPoint',
+ 'renderer': SpawnPointRenderer,
+ 'cond': (layers) => layers['spawn-points'],
+ },
{
'name': 'signalboxes',
'category': 'misc',
diff --git a/src/components/map/object-renderers/SpawnPointRenderer.js b/src/components/map/object-renderers/SpawnPointRenderer.js
new file mode 100644
index 0000000..430e882
--- /dev/null
+++ b/src/components/map/object-renderers/SpawnPointRenderer.js
@@ -0,0 +1,33 @@
+import { ReactSVG } from 'react-svg'
+import AngleHelper from '../../../helpers/angleHelper';
+
+export default function SpawnPointRenderer(props) {
+ const { object } = props;
+ const [x, y] = object.pos.toSVGCoords();
+
+ const rot = AngleHelper.normalizeDegAngle(object.rot.y);
+ const upsideDown = rot >= 180;
+ const upsideDownRot = upsideDown ? 90 : -90;
+ const anchor = upsideDown ? "start" : "end";
+
+ return (
+
+
+ {
+ svg.setAttribute('width', '1.5mm');
+ svg.setAttribute('height', '1.5mm');
+ }}
+ />
+
+
+
+ {object.getPrintableSpawnPointName()}
+
+
+
+ );
+}
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 2eb3310..e631108 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -21,7 +21,7 @@ const Constants = {
switchInvalidTrackConnection: true,
unknownObjectType: true,
unknownTrackType: true,
- signalWithoutSpawnInfo: true,
+ spawnWithoutSpawnInfo: true,
trackAliasAlreadyExists: true,
trackAliasNoTrack: true,
switchUndefinedModel: true,
@@ -50,7 +50,7 @@ const Constants = {
{
id: 'switches-names',
name: 'Switches names',
- default: true,
+ default: false,
},
{
id: 'isolations-ids',
@@ -67,6 +67,11 @@ const Constants = {
name: 'Signals',
default: true,
},
+ {
+ id: 'spawn-points',
+ name: 'SpawnPoints',
+ default: true,
+ },
{
id: 'derailers',
name: 'Derailers',
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index a5f4696..b006ed3 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -17,6 +17,7 @@ import MainCamera from './main-camera';
import ElectrificationResolver from './electrification-resolver';
import NEVP from './nevp';
import Derailer from './derailer';
+import SpawnPoint from './spawn-point';
/*
TODO: add support for WorldRotation and WorldTranslation
@@ -129,6 +130,8 @@ export default class SceneryParser {
return Derailer.fromText(text);
} else if(NEVP.isNEVP(text)) {
return NEVP.fromText(text);
+ } else if(SpawnPoint.isSpawnPoint(text)) {
+ return SpawnPoint.fromText(text);
} else {
return TrackObject.fromText(text);
}
diff --git a/src/model/scenery.js b/src/model/scenery.js
index 9231707..08737da 100644
--- a/src/model/scenery.js
+++ b/src/model/scenery.js
@@ -5,7 +5,7 @@ export default class Scenery
{
objects = {};
signalBoxes = [];
- spawnSignals = [];
+ spawnPoints = [];
bounds = { minX: Infinity, minZ: Infinity, maxX: -Infinity, maxZ: -Infinity };
trackAliases = {};
electrificationResolved = ElectrificationResolutionStatus.NOT_RESOLVED;
@@ -19,8 +19,8 @@ export default class Scenery
this.signalBoxes.push(signalBox);
}
- addSpawnSignal(signal) {
- this.spawnSignals.push(signal);
+ addSpawnPoint(trackObject) {
+ this.spawnPoints.push(trackObject);
}
addObject(object) {
diff --git a/src/model/signal.js b/src/model/signal.js
index 7408076..94357dc 100644
--- a/src/model/signal.js
+++ b/src/model/signal.js
@@ -20,7 +20,7 @@ export default class Signal extends TrackObject {
});
if (this.is_spawn && !this.spawn_info) {
- SceneryParserLog.warn('signalWithoutSpawnInfo', `Signal ${this.id} is marked as spawn but has no spawn info`);
+ SceneryParserLog.warn('spawnWithoutSpawnInfo', `Signal ${this.id} is marked as spawn but has no spawn info`);
}
}
@@ -86,7 +86,7 @@ export default class Signal extends TrackObject {
this.applied = true;
if (this.is_spawn) {
- scenery.addSpawnSignal(this);
+ scenery.addSpawnPoint(this);
}
}
diff --git a/src/model/spawn-point.js b/src/model/spawn-point.js
new file mode 100644
index 0000000..2b67667
--- /dev/null
+++ b/src/model/spawn-point.js
@@ -0,0 +1,72 @@
+import SceneryParserLog from "./scenery-parser-log";
+import SpawnInfo from "./spawn-info";
+import TrackObject from "./track-object";
+import Vector3 from "./vector3";
+
+export default class SpawnPoint extends TrackObject {
+ is_spawn;
+ spawn_info;
+ spawn_point_name;
+ type = "SpawnPoint";
+ applied = false;
+
+ constructor(id, prefab_name, pos, rot, track_id, name, is_spawn, spawn_info, signal_name) {
+ super(id, prefab_name, pos, rot, track_id, name);
+
+ Object.assign(this, {
+ is_spawn,
+ spawn_info,
+ signal_name
+ });
+
+ if (this.is_spawn && !this.spawn_info) {
+ SceneryParserLog.warn('spawnWithoutSpawnInfo', `SpawnPoint ${this.id} is marked as spawn but has no spawn info`);
+ }
+ }
+
+ getPrintableSpawnPointName() {
+ return this.spawn_point_name?.trim() || this.name?.trim() || "Spawn Point";
+ }
+
+ /*
+ Check if a TrackObject text is a SpawnPoint.
+ @param {string} text - The text representation of the TrackObject.
+ @returns {boolean} - Returns true if the text represents a SpawnPoint, false otherwise.
+ @static
+ @memberof Signal
+ */
+ static isSpawnPoint(text) {
+ const values = text.split(";");
+ if(values.length < 29) return false;
+
+ const prefabName = values[2];
+ return prefabName === "Spawn Point";
+ }
+
+ static fromText(text) {
+ const values = text.split(";");
+ const signal = new SpawnPoint(
+ values[1], // id
+ values[2], // prefab_name
+ Vector3.fromValuesArray(values, 3), // pos
+ Vector3.fromValuesArray(values, 6), // rot
+ values[9], // track_id
+ values[11], // name,
+ values[12] === "Spawn Signal", // is_spawn
+ SpawnInfo.fromText(values[15]), // spawn_info
+ values[29]
+ );
+
+ return signal;
+ }
+
+ applyObject(scenery) {
+ if(this.applied) return; // already applied
+ this.applied = true;
+
+ if (this.is_spawn) {
+ scenery.addSpawnPoint(this);
+ }
+ }
+
+}
\ No newline at end of file