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