From 8865e480bf0f79b5655c3d2b456ca186a3ba587d Mon Sep 17 00:00:00 2001 From: ikawe Date: Tue, 23 Apr 2024 20:05:11 +0200 Subject: [PATCH 01/25] Added schedule It will be displayed with the tooltip when hovering over a train. --- .../kotlin/littlechasiu/ctm/Extensions.kt | 48 +++++++++++++++++++ .../kotlin/littlechasiu/ctm/model/Network.kt | 32 +++++++++++++ .../static/assets/css/create-track-map.css | 3 ++ .../ctm/static/assets/js/create-track-map.js | 19 +++++++- 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 571889d..ee8117f 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -6,6 +6,12 @@ import com.simibubi.create.content.trains.entity.TravellingPoint import com.simibubi.create.content.trains.graph.TrackEdge import com.simibubi.create.content.trains.graph.TrackNode import com.simibubi.create.content.trains.graph.TrackNodeLocation +import com.simibubi.create.content.trains.schedule.Schedule +import com.simibubi.create.content.trains.schedule.ScheduleEntry +import com.simibubi.create.content.trains.schedule.ScheduleRuntime +import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction +import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction +import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction import com.simibubi.create.foundation.utility.Couple import littlechasiu.ctm.model.* import net.minecraft.resources.ResourceKey @@ -75,6 +81,46 @@ val Carriage.sendable }, ) +fun getInstructions(instructions: List): ArrayList { + val result: ArrayList = ArrayList() + + for(entry in instructions){ + if(entry.instruction is DestinationInstruction){ + result.add( + ScheduleInstructionDestination( + stationName = (entry.instruction as DestinationInstruction).summary.second.string, + ) + ) + } + if(entry.instruction is ChangeTitleInstruction){ + result.add( + ScheduleInstructionNameChange( + newName = (entry.instruction as ChangeTitleInstruction).scheduleTitle, + ) + ) + } + if(entry.instruction is ChangeThrottleInstruction){ + result.add( + ScheduleInstructionThrottleChange( + throttle = (entry.instruction as ChangeThrottleInstruction).summary.second.string, + ) + ) + } + + } + return result +} + +val ScheduleRuntime.sendable + get() = schedule?.let { + CreateSchedule( + cycling = it.cyclic, + instructions = getInstructions(it.entries), + paused = paused, + currentEntry = currentEntry, + ) + } + val Train.sendable get() = CreateTrain( @@ -82,6 +128,8 @@ val Train.sendable name = name.string, owner = null, cars = carriages.map { it.sendable }.toList(), + speed = speed, backwards = speed < 0, stopped = speed == 0.0, + schedule = runtime.sendable, ) diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 7735c51..f974400 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -1,11 +1,14 @@ package littlechasiu.ctm.model +import com.simibubi.create.content.trains.entity.Navigation +import com.simibubi.create.content.trains.schedule.Schedule import com.simibubi.create.content.trains.signal.SignalBlock.SignalType import com.simibubi.create.content.trains.signal.SignalBlockEntity.SignalState import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import java.util.* @@ -120,6 +123,33 @@ data class TrainCar( val portal: Portal? = null, ) +@Serializable +sealed class ScheduleInstruction( + val instructionType: String, +) +@Serializable +data class ScheduleInstructionDestination( + val stationName : String, +) : ScheduleInstruction(instructionType = "Destination") + +@Serializable +data class ScheduleInstructionThrottleChange( + val throttle : String, +) : ScheduleInstruction(instructionType = "ThrottleChange") + +@Serializable +data class ScheduleInstructionNameChange( + val newName : String, +) : ScheduleInstruction(instructionType = "NameChange") + +@Serializable +data class CreateSchedule( + val instructions: List, + val cycling: Boolean, + val paused: Boolean, + val currentEntry: Int, +) + @Serializable data class CreateTrain( @Serializable(with = UUIDSerializer::class) @@ -129,6 +159,8 @@ data class CreateTrain( val cars: List, val backwards: Boolean, val stopped: Boolean, + val speed: Double, + val schedule: CreateSchedule?, ) @Serializable diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css index 21434b3..96f8c6e 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css @@ -41,6 +41,9 @@ body { background-color: #444; color: white; } +.on-schedule { + text-decoration: underline; +} .station-icon .fill { fill: var(--station-color); diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 8566466..9f27789 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -189,6 +189,21 @@ function startMapUpdates() { } } + let schedule = "" + console.log(train.schedule); + if(train.schedule != null){ + schedule = "
On schedule
" + train.schedule.instructions.forEach((instruction, i) => { + if(instruction.instructionType == "Destination"){ + schedule += ""; + if(i == schedule.currentEntry){ + schedule += "=> " + } + schedule += instruction.stationName + "
"; + } + }) + } + train.cars.forEach((car, i) => { let parts = car.portal ? [ @@ -205,9 +220,9 @@ function startMapUpdates() { pane: "trains", }) .bindTooltip( - train.cars.length === 1 + (train.cars.length === 1 ? train.name - : `${train.name} ${i + 1}`, + : `${train.name} ${i + 1}`) + schedule, { className: "train-name", direction: "right", From 30d9f9e1a907f0cb3258eefde9e1520eafed279c Mon Sep 17 00:00:00 2001 From: ikawe Date: Tue, 23 Apr 2024 20:15:34 +0200 Subject: [PATCH 02/25] Added arrow pointing to current target on schedule --- .../littlechasiu/ctm/static/assets/js/create-track-map.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 9f27789..03df509 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -190,13 +190,12 @@ function startMapUpdates() { } let schedule = "" - console.log(train.schedule); if(train.schedule != null){ schedule = "
On schedule
" train.schedule.instructions.forEach((instruction, i) => { if(instruction.instructionType == "Destination"){ schedule += ""; - if(i == schedule.currentEntry){ + if(i == train.schedule.currentEntry){ schedule += "=> " } schedule += instruction.stationName + "
"; From b833cac5043bd2d1af2ea57d0ef00cfff0994073 Mon Sep 17 00:00:00 2001 From: ikawe Date: Tue, 23 Apr 2024 20:19:38 +0200 Subject: [PATCH 03/25] added reminder cuz i forget easily :) --- src/main/kotlin/littlechasiu/ctm/model/Network.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index f974400..45c31a7 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -144,6 +144,7 @@ data class ScheduleInstructionNameChange( @Serializable data class CreateSchedule( + /*TODO store instructions as a queue => next instruction at first place*/ val instructions: List, val cycling: Boolean, val paused: Boolean, From d6d52f2f47ee32d0aaeeb3b72147bdfb69a5dbc2 Mon Sep 17 00:00:00 2001 From: ikawe Date: Wed, 24 Apr 2024 17:49:36 +0200 Subject: [PATCH 04/25] added currentPath of train to api/trains (Autopilot path) --- .../kotlin/littlechasiu/ctm/Extensions.kt | 45 ++++++++++++++++++- .../kotlin/littlechasiu/ctm/model/Network.kt | 5 +-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index ee8117f..73a93f2 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -1,22 +1,26 @@ package littlechasiu.ctm import com.simibubi.create.content.trains.entity.Carriage +import com.simibubi.create.content.trains.entity.Navigation import com.simibubi.create.content.trains.entity.Train import com.simibubi.create.content.trains.entity.TravellingPoint import com.simibubi.create.content.trains.graph.TrackEdge import com.simibubi.create.content.trains.graph.TrackNode import com.simibubi.create.content.trains.graph.TrackNodeLocation -import com.simibubi.create.content.trains.schedule.Schedule import com.simibubi.create.content.trains.schedule.ScheduleEntry import com.simibubi.create.content.trains.schedule.ScheduleRuntime import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction +import com.simibubi.create.content.trains.station.GlobalStation import com.simibubi.create.foundation.utility.Couple import littlechasiu.ctm.model.* +import net.minecraft.core.Vec3i import net.minecraft.resources.ResourceKey import net.minecraft.world.level.Level import net.minecraft.world.phys.Vec3 +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.javaField fun MutableSet.replaceWith(other: Collection) { this.retainAll { other.contains(it) } @@ -30,6 +34,10 @@ val Vec3.sendable: Point get() = Point(x = x, y = y, z = z) +val Vec3i.sendable: Point + get() = + Point(x = x.toDouble(), y = y.toDouble(), z = z.toDouble()) + val ResourceKey.string: String get() { val loc = location() @@ -121,6 +129,40 @@ val ScheduleRuntime.sendable ) } +private inline fun Navigation.getCurrentPath() : List> { + return Navigation::class.java.getDeclaredField("currentPath").let{ + it.isAccessible = true + val value = it.get(this) + @Suppress("UNCHECKED_CAST") + return@let value as List> + } +} + +fun getCurrentTrainPath(navigation: Navigation?) : List{ + val result : ArrayList = ArrayList() + if(navigation == null){ + return result + } + val field = Navigation::class.java.getDeclaredField("currentPath") + field.isAccessible = true + @Suppress("UNCHECKED_CAST") + val currentPath = field.get(navigation) as List> + + //val currentPath = navigation.getCurrentPath() + println("NAVIGATION:") + println(currentPath.size) + + currentPath.forEach{ + result.add(Path( + start = it.first.location.sendable, + end = it.second.location.sendable, + firstControlPoint = it.first.normal.sendable, + secondControlPoint = it.first.normal.sendable, + )) + } + return result +} + val Train.sendable get() = CreateTrain( @@ -132,4 +174,5 @@ val Train.sendable backwards = speed < 0, stopped = speed == 0.0, schedule = runtime.sendable, + currentPath = getCurrentTrainPath(navigation), ) diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 45c31a7..0adafa1 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -1,14 +1,11 @@ package littlechasiu.ctm.model -import com.simibubi.create.content.trains.entity.Navigation -import com.simibubi.create.content.trains.schedule.Schedule import com.simibubi.create.content.trains.signal.SignalBlock.SignalType import com.simibubi.create.content.trains.signal.SignalBlockEntity.SignalState import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import java.util.* @@ -144,7 +141,6 @@ data class ScheduleInstructionNameChange( @Serializable data class CreateSchedule( - /*TODO store instructions as a queue => next instruction at first place*/ val instructions: List, val cycling: Boolean, val paused: Boolean, @@ -162,6 +158,7 @@ data class CreateTrain( val stopped: Boolean, val speed: Double, val schedule: CreateSchedule?, + val currentPath: List, ) @Serializable From c33497000daeea119039370bed0be23b8fdeef7d Mon Sep 17 00:00:00 2001 From: ikawe Date: Wed, 24 Apr 2024 18:19:36 +0200 Subject: [PATCH 05/25] Paths now somehow get visualised, turns out the data is only about the turns the train needs to make. Which makes the visualisation more challenging. --- src/main/kotlin/littlechasiu/ctm/Extensions.kt | 8 ++------ src/main/kotlin/littlechasiu/ctm/model/Network.kt | 4 ++-- .../ctm/static/assets/css/create-track-map.css | 3 +++ .../ctm/static/assets/js/create-track-map.js | 9 +++++++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 73a93f2..ebc6e7e 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -148,14 +148,10 @@ fun getCurrentTrainPath(navigation: Navigation?) : List{ @Suppress("UNCHECKED_CAST") val currentPath = field.get(navigation) as List> - //val currentPath = navigation.getCurrentPath() - println("NAVIGATION:") - println(currentPath.size) - currentPath.forEach{ result.add(Path( - start = it.first.location.sendable, - end = it.second.location.sendable, + start = it.first.dimensionLocation, + end = it.second.dimensionLocation, firstControlPoint = it.first.normal.sendable, secondControlPoint = it.first.normal.sendable, )) diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 0adafa1..236c000 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -32,10 +32,10 @@ data class Point( @Serializable data class Path( - val start: Point, + val start: DimensionLocation, val firstControlPoint: Point, val secondControlPoint: Point, - val end: Point, + val end: DimensionLocation, ) @Serializable diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css index 96f8c6e..67eff1c 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css @@ -25,6 +25,9 @@ body { .track { stroke: var(--track-free); } +.track.path { + stroke: #0000ff; +} .train-name, .station-name { diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 03df509..7f25ae1 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -244,6 +244,15 @@ function startMapUpdates() { }).addTo(lmgr.layer(dim, "trains")) } }) + + train.currentPath.forEach((path, i) => { + L.polyline([xz(path.start.location), xz(path.end.location)], { + className: "track path", + interactive: false, + pane: "trains", + }).addTo(lmgr.layer(path.start.dimension, "trains")) + }) }) + }) } From c410242b99d0b2c6963f0f727147d150a9fe99c7 Mon Sep 17 00:00:00 2001 From: ikawe Date: Wed, 24 Apr 2024 19:24:33 +0200 Subject: [PATCH 06/25] Path turns are now curves on the network TODO: Fill in track pieces inbetween --- .../kotlin/littlechasiu/ctm/Extensions.kt | 30 ++++--------------- .../kotlin/littlechasiu/ctm/model/Network.kt | 2 +- .../ctm/static/assets/js/create-track-map.js | 26 +++++++++++----- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index ebc6e7e..5a4c80e 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -12,15 +12,12 @@ import com.simibubi.create.content.trains.schedule.ScheduleRuntime import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction -import com.simibubi.create.content.trains.station.GlobalStation import com.simibubi.create.foundation.utility.Couple import littlechasiu.ctm.model.* import net.minecraft.core.Vec3i import net.minecraft.resources.ResourceKey import net.minecraft.world.level.Level import net.minecraft.world.phys.Vec3 -import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.javaField fun MutableSet.replaceWith(other: Collection) { this.retainAll { other.contains(it) } @@ -34,10 +31,6 @@ val Vec3.sendable: Point get() = Point(x = x, y = y, z = z) -val Vec3i.sendable: Point - get() = - Point(x = x.toDouble(), y = y.toDouble(), z = z.toDouble()) - val ResourceKey.string: String get() { val loc = location() @@ -129,17 +122,8 @@ val ScheduleRuntime.sendable ) } -private inline fun Navigation.getCurrentPath() : List> { - return Navigation::class.java.getDeclaredField("currentPath").let{ - it.isAccessible = true - val value = it.get(this) - @Suppress("UNCHECKED_CAST") - return@let value as List> - } -} - -fun getCurrentTrainPath(navigation: Navigation?) : List{ - val result : ArrayList = ArrayList() +fun getCurrentTrainPath(navigation: Navigation?) : List{ + val result : ArrayList = ArrayList() if(navigation == null){ return result } @@ -149,12 +133,10 @@ fun getCurrentTrainPath(navigation: Navigation?) : List{ val currentPath = field.get(navigation) as List> currentPath.forEach{ - result.add(Path( - start = it.first.dimensionLocation, - end = it.second.dimensionLocation, - firstControlPoint = it.first.normal.sendable, - secondControlPoint = it.first.normal.sendable, - )) + val trackEdge : TrackEdge = navigation.train.graph.getConnectionsFrom(it.first).get(it.second) ?: return@forEach + val edge = trackEdge.sendable + if(edge is Edge) + result.add(edge) } return result } diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 236c000..634dc30 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -158,7 +158,7 @@ data class CreateTrain( val stopped: Boolean, val speed: Double, val schedule: CreateSchedule?, - val currentPath: List, + val currentPath: List, ) @Serializable diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 7f25ae1..1f46875 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -191,6 +191,24 @@ function startMapUpdates() { let schedule = "" if(train.schedule != null){ + + train.currentPath.forEach((trk) => { + const path = trk.path + if (path.length === 4) { + L.curve(["M", xz(path[0]), "C", xz(path[1]), xz(path[2]), xz(path[3])], { + className: "track path", + interactive: false, + pane: "trains", + }).addTo(lmgr.layer(trk.dimension, "trains")) + } else if (path.length === 2) { + L.polyline([xz(path[0]), xz(path[1])], { + className: "track path", + interactive: false, + pane: "trains", + }).addTo(lmgr.layer(trk.dimension, "trains")) + } + }) + schedule = "
On schedule
" train.schedule.instructions.forEach((instruction, i) => { if(instruction.instructionType == "Destination"){ @@ -244,14 +262,6 @@ function startMapUpdates() { }).addTo(lmgr.layer(dim, "trains")) } }) - - train.currentPath.forEach((path, i) => { - L.polyline([xz(path.start.location), xz(path.end.location)], { - className: "track path", - interactive: false, - pane: "trains", - }).addTo(lmgr.layer(path.start.dimension, "trains")) - }) }) }) From f4446dd41232523127417fe40ac140824bcab541 Mon Sep 17 00:00:00 2001 From: ikawe Date: Fri, 26 Apr 2024 11:16:27 +0200 Subject: [PATCH 07/25] Most of the Path is displayed now Missing: From train to first node / Last node to station --- .../kotlin/littlechasiu/ctm/Extensions.kt | 85 +++++++++++++++---- .../kotlin/littlechasiu/ctm/model/Network.kt | 2 + .../static/assets/css/create-track-map.css | 4 + .../ctm/static/assets/js/create-track-map.js | 17 ++++ 4 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 5a4c80e..032c397 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -5,6 +5,7 @@ import com.simibubi.create.content.trains.entity.Navigation import com.simibubi.create.content.trains.entity.Train import com.simibubi.create.content.trains.entity.TravellingPoint import com.simibubi.create.content.trains.graph.TrackEdge +import com.simibubi.create.content.trains.graph.TrackGraph import com.simibubi.create.content.trains.graph.TrackNode import com.simibubi.create.content.trains.graph.TrackNodeLocation import com.simibubi.create.content.trains.schedule.ScheduleEntry @@ -12,8 +13,11 @@ import com.simibubi.create.content.trains.schedule.ScheduleRuntime import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction +import com.simibubi.create.content.trains.track.BezierConnection import com.simibubi.create.foundation.utility.Couple +import kotlinx.css.li import littlechasiu.ctm.model.* +import net.minecraft.core.Direction import net.minecraft.core.Vec3i import net.minecraft.resources.ResourceKey import net.minecraft.world.level.Level @@ -119,38 +123,83 @@ val ScheduleRuntime.sendable instructions = getInstructions(it.entries), paused = paused, currentEntry = currentEntry, + arrivalCountdown = 0.0 ) } -fun getCurrentTrainPath(navigation: Navigation?) : List{ +fun getNextEdge(graph: TrackGraph, trackNode: TrackNode, trackEdge: TrackEdge, direction: Vec3) : TrackEdge?{ + var result : TrackEdge? = null + var biggest = Double.MIN_VALUE + graph.getConnectionsFrom(trackNode).forEach { key, value -> + if (key == trackEdge) { + return@forEach + } + val dot = value.getDirection(true).dot(direction) + if(dot > biggest && dot > 0){ + biggest = dot + result = value + } + } + return result +} +fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, List >{ val result : ArrayList = ArrayList() + val debug : ArrayList = ArrayList() if(navigation == null){ - return result + return Pair(result, debug) } val field = Navigation::class.java.getDeclaredField("currentPath") field.isAccessible = true @Suppress("UNCHECKED_CAST") val currentPath = field.get(navigation) as List> - currentPath.forEach{ - val trackEdge : TrackEdge = navigation.train.graph.getConnectionsFrom(it.first).get(it.second) ?: return@forEach + currentPath.forEachIndexed{i, obj -> + val trackEdge : TrackEdge = navigation.train.graph.getConnectionsFrom(obj.first).get(obj.second) ?: return@forEachIndexed val edge = trackEdge.sendable - if(edge is Edge) + if(edge !is Edge) {return@forEachIndexed} result.add(edge) + + if(i < currentPath.size - 1){ + var tEdge : TrackEdge = trackEdge + + var direction: Vec3 = Vec3(0.0,0.0,0.0) + var reachedEnd: Boolean = false + val MAX_PREDICTIONS: Int = 50; + + var j = 0 + while(!reachedEnd) { + direction = tEdge.getDirection(false) + tEdge = getNextEdge(navigation.train.graph, tEdge.node2, tEdge, direction) ?: return@forEachIndexed + + j++ + if(tEdge.node1 == currentPath[i + 1].first){ + reachedEnd = true // incase tEdge is the next scheduled edge + }else{ + debug.add(tEdge.sendable as Edge) + } + + if(tEdge.node2 == currentPath[i + 1].first || j > MAX_PREDICTIONS){ + reachedEnd = true + } + } + } } - return result + return Pair(result, debug) } -val Train.sendable - get() = - CreateTrain( - id = id, - name = name.string, - owner = null, - cars = carriages.map { it.sendable }.toList(), - speed = speed, - backwards = speed < 0, - stopped = speed == 0.0, - schedule = runtime.sendable, - currentPath = getCurrentTrainPath(navigation), +val Train.sendable: CreateTrain + get() { + val (currentPath, debug) = getCurrentTrainPath(navigation) + return CreateTrain( + id = id, + name = name.string, + owner = null, + cars = carriages.map { it.sendable }.toList(), + speed = speed, + backwards = speed < 0, + stopped = speed == 0.0, + schedule = runtime.sendable, + currentPath = currentPath, + debug = debug ) + } diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 634dc30..5f4d5d6 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -145,6 +145,7 @@ data class CreateSchedule( val cycling: Boolean, val paused: Boolean, val currentEntry: Int, + val arrivalCountdown: Double, ) @Serializable @@ -159,6 +160,7 @@ data class CreateTrain( val speed: Double, val schedule: CreateSchedule?, val currentPath: List, + val debug: List ) @Serializable diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css index 67eff1c..67bea15 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css @@ -29,6 +29,10 @@ body { stroke: #0000ff; } +.track.debug { + stroke: #00ff00; +} + .train-name, .station-name { font-family: var(--ui-font); diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 1f46875..f89b522 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -209,6 +209,23 @@ function startMapUpdates() { } }) + train.debug.forEach((trk) => { + const path = trk.path + if (path.length === 4) { + L.curve(["M", xz(path[0]), "C", xz(path[1]), xz(path[2]), xz(path[3])], { + className: "track debug", + interactive: false, + pane: "trains", + }).addTo(lmgr.layer(trk.dimension, "trains")) + } else if (path.length === 2) { + L.polyline([xz(path[0]), xz(path[1])], { + className: "track debug", + interactive: false, + pane: "trains", + }).addTo(lmgr.layer(trk.dimension, "trains")) + } + }) + schedule = "
On schedule
" train.schedule.instructions.forEach((instruction, i) => { if(instruction.instructionType == "Destination"){ From 64b64956a930866cc492b6ab91126f3bb36ce588 Mon Sep 17 00:00:00 2001 From: ikawe Date: Fri, 26 Apr 2024 12:39:34 +0200 Subject: [PATCH 08/25] Train to first node connected! --- .../kotlin/littlechasiu/ctm/Extensions.kt | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 032c397..ee5d93f 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -13,12 +13,8 @@ import com.simibubi.create.content.trains.schedule.ScheduleRuntime import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction -import com.simibubi.create.content.trains.track.BezierConnection import com.simibubi.create.foundation.utility.Couple -import kotlinx.css.li import littlechasiu.ctm.model.* -import net.minecraft.core.Direction -import net.minecraft.core.Vec3i import net.minecraft.resources.ResourceKey import net.minecraft.world.level.Level import net.minecraft.world.phys.Vec3 @@ -127,7 +123,7 @@ val ScheduleRuntime.sendable ) } -fun getNextEdge(graph: TrackGraph, trackNode: TrackNode, trackEdge: TrackEdge, direction: Vec3) : TrackEdge?{ +private fun getNextEdge(graph: TrackGraph, trackNode: TrackNode, trackEdge: TrackEdge, direction: Vec3) : TrackEdge?{ var result : TrackEdge? = null var biggest = Double.MIN_VALUE graph.getConnectionsFrom(trackNode).forEach { key, value -> @@ -142,10 +138,40 @@ fun getNextEdge(graph: TrackGraph, trackNode: TrackNode, trackEdge: TrackEdge, d } return result } -fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, List >{ + +private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNode) : ArrayList { + val result = ArrayList() + + var tEdge: TrackEdge = startEdge + + var direction = Vec3(0.0, 0.0, 0.0) + var reachedEnd = false + val MAX_PREDICTIONS = 50; + + var j = 0 + while (!reachedEnd) { + direction = tEdge.getDirection(false) + tEdge = getNextEdge(graph, tEdge.node2, tEdge, direction) ?: return result + + j++ + if (tEdge.node1.netId == endNode.netId) { + reachedEnd = true // incase tEdge is the next scheduled edge + } else { + result.add(tEdge.sendable as Edge) + } + + if (tEdge.node2.netId == endNode.netId || j > MAX_PREDICTIONS) { + reachedEnd = true + } + } + return result +} + +private fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, List >{ + val graph = navigation?.train?.graph val result : ArrayList = ArrayList() val debug : ArrayList = ArrayList() - if(navigation == null){ + if(navigation == null || graph == null){ return Pair(result, debug) } val field = Navigation::class.java.getDeclaredField("currentPath") @@ -153,35 +179,22 @@ fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, List @Suppress("UNCHECKED_CAST") val currentPath = field.get(navigation) as List> + val firstEdge: TrackEdge = graph.getConnection(navigation.train.endpointEdges.first) + result.add(firstEdge.sendable as Edge) + if(currentPath.size > 0){ + result.addAll(pathFromTo(firstEdge, graph, currentPath[0].first)) + }else{ + + } + currentPath.forEachIndexed{i, obj -> - val trackEdge : TrackEdge = navigation.train.graph.getConnectionsFrom(obj.first).get(obj.second) ?: return@forEachIndexed + val trackEdge : TrackEdge = graph.getConnectionsFrom(obj.first).get(obj.second) ?: return@forEachIndexed val edge = trackEdge.sendable if(edge !is Edge) {return@forEachIndexed} result.add(edge) if(i < currentPath.size - 1){ - var tEdge : TrackEdge = trackEdge - - var direction: Vec3 = Vec3(0.0,0.0,0.0) - var reachedEnd: Boolean = false - val MAX_PREDICTIONS: Int = 50; - - var j = 0 - while(!reachedEnd) { - direction = tEdge.getDirection(false) - tEdge = getNextEdge(navigation.train.graph, tEdge.node2, tEdge, direction) ?: return@forEachIndexed - - j++ - if(tEdge.node1 == currentPath[i + 1].first){ - reachedEnd = true // incase tEdge is the next scheduled edge - }else{ - debug.add(tEdge.sendable as Edge) - } - - if(tEdge.node2 == currentPath[i + 1].first || j > MAX_PREDICTIONS){ - reachedEnd = true - } - } + result.addAll(pathFromTo(trackEdge, graph, currentPath[i + 1].first)) } } return Pair(result, debug) From abc94345e87733638ed4c94bc7b26d8820417097 Mon Sep 17 00:00:00 2001 From: ikawe Date: Fri, 26 Apr 2024 13:12:43 +0200 Subject: [PATCH 09/25] Current train path now gets completely displayed. Also added nullcheck for carriage data in create-track-map.js to avoid the missing dimension error that can happen sometimes. --- .../kotlin/littlechasiu/ctm/Extensions.kt | 18 ++++- .../static/assets/css/create-track-map.css | 4 +- .../ctm/static/assets/js/create-track-map.js | 76 ++++++++++--------- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index ee5d93f..3b62559 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -13,6 +13,7 @@ import com.simibubi.create.content.trains.schedule.ScheduleRuntime import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction +import com.simibubi.create.content.trains.station.GlobalStation import com.simibubi.create.foundation.utility.Couple import littlechasiu.ctm.model.* import net.minecraft.resources.ResourceKey @@ -167,11 +168,17 @@ private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNo return result } +private fun getEdgeFromStation(station: GlobalStation, graph: TrackGraph) : TrackEdge { + val firstNode = graph.locateNode(station.edgeLocation.first) + val secondNode = graph.locateNode(station.edgeLocation.second) + return graph.getConnection(Couple.create(firstNode, secondNode)) +} + private fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, List >{ val graph = navigation?.train?.graph val result : ArrayList = ArrayList() val debug : ArrayList = ArrayList() - if(navigation == null || graph == null){ + if(navigation == null || graph == null || navigation.destination == null){ return Pair(result, debug) } val field = Navigation::class.java.getDeclaredField("currentPath") @@ -180,11 +187,18 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, Lis val currentPath = field.get(navigation) as List> val firstEdge: TrackEdge = graph.getConnection(navigation.train.endpointEdges.first) + val lastEdge: TrackEdge = getEdgeFromStation(navigation.destination, graph) + if(firstEdge == lastEdge){ + return Pair(result, debug) + } + result.add(firstEdge.sendable as Edge) + result.add(lastEdge.sendable as Edge) if(currentPath.size > 0){ result.addAll(pathFromTo(firstEdge, graph, currentPath[0].first)) + result.addAll(pathFromTo(graph.getConnection(currentPath[currentPath.size - 1]), graph, lastEdge.node1)) }else{ - + result.addAll(pathFromTo(firstEdge, graph, lastEdge.node1)) } currentPath.forEachIndexed{i, obj -> diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css index 67bea15..e205d8b 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css @@ -26,11 +26,11 @@ body { stroke: var(--track-free); } .track.path { - stroke: #0000ff; + stroke: #0000ff33; } .track.debug { - stroke: #00ff00; + stroke: #00ff0033; } .train-name, diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index f89b522..682a578 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -239,44 +239,46 @@ function startMapUpdates() { } train.cars.forEach((car, i) => { - let parts = car.portal - ? [ - [car.leading.dimension, [xz(car.leading.location), xz(car.portal.from.location)]], - [car.trailing.dimension, [xz(car.portal.to.location), xz(car.trailing.location)]], - ] - : [[car.leading.dimension, [xz(car.leading.location), xz(car.trailing.location)]]] - - parts.map(([dim, part]) => - L.polyline(part, { - weight: 12, - lineCap: "square", - className: "train" + (leadCar === i ? " lead-car" : ""), - pane: "trains", - }) - .bindTooltip( - (train.cars.length === 1 - ? train.name - : `${train.name} ${i + 1}`) + schedule, - { - className: "train-name", - direction: "right", - offset: L.point(12, 0), - opacity: 0.7, - } + if(car.leading){ // lazily solves the missing carriage data (ignore the problem) + let parts = car.portal + ? [ + [car.leading.dimension, [xz(car.leading.location), xz(car.portal.from.location)]], + [car.trailing.dimension, [xz(car.portal.to.location), xz(car.trailing.location)]], + ] + : [[car.leading.dimension, [xz(car.leading.location), xz(car.trailing.location)]]] + + parts.map(([dim, part]) => + L.polyline(part, { + weight: 12, + lineCap: "square", + className: "train" + (leadCar === i ? " lead-car" : ""), + pane: "trains", + }) + .bindTooltip( + (train.cars.length === 1 + ? train.name + : `${train.name} ${i + 1}`) + schedule, + { + className: "train-name", + direction: "right", + offset: L.point(12, 0), + opacity: 0.7, + } + ) + .addTo(lmgr.layer(dim, "trains")) ) - .addTo(lmgr.layer(dim, "trains")) - ) - - if (leadCar === i) { - let [dim, edge] = train.backwards ? parts[parts.length - 1] : parts[0] - let [head, tail] = train.backwards ? [edge[1], edge[0]] : [edge[0], edge[1]] - let angle = 180 + (Math.atan2(tail[0] - head[0], tail[1] - head[1]) * 180) / Math.PI - - L.marker(head, { - icon: headIcon, - rotationAngle: angle, - pane: "trains", - }).addTo(lmgr.layer(dim, "trains")) + + if (leadCar === i) { + let [dim, edge] = train.backwards ? parts[parts.length - 1] : parts[0] + let [head, tail] = train.backwards ? [edge[1], edge[0]] : [edge[0], edge[1]] + let angle = 180 + (Math.atan2(tail[0] - head[0], tail[1] - head[1]) * 180) / Math.PI + + L.marker(head, { + icon: headIcon, + rotationAngle: angle, + pane: "trains", + }).addTo(lmgr.layer(dim, "trains")) + } } }) }) From 1f824bb63fbbec823ec6573f8c81a30a97377e29 Mon Sep 17 00:00:00 2001 From: ikawe Date: Fri, 26 Apr 2024 13:17:13 +0200 Subject: [PATCH 10/25] Removed debug path from api/train and all its usages --- src/main/kotlin/littlechasiu/ctm/Extensions.kt | 15 ++++++--------- .../kotlin/littlechasiu/ctm/model/Network.kt | 1 - .../ctm/static/assets/css/create-track-map.css | 6 +----- .../ctm/static/assets/js/create-track-map.js | 17 ----------------- 4 files changed, 7 insertions(+), 32 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 3b62559..558b5c6 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -174,12 +174,11 @@ private fun getEdgeFromStation(station: GlobalStation, graph: TrackGraph) : Trac return graph.getConnection(Couple.create(firstNode, secondNode)) } -private fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, List >{ +private fun getCurrentTrainPath(navigation: Navigation?) : List{ val graph = navigation?.train?.graph val result : ArrayList = ArrayList() - val debug : ArrayList = ArrayList() if(navigation == null || graph == null || navigation.destination == null){ - return Pair(result, debug) + return result } val field = Navigation::class.java.getDeclaredField("currentPath") field.isAccessible = true @@ -189,12 +188,12 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, Lis val firstEdge: TrackEdge = graph.getConnection(navigation.train.endpointEdges.first) val lastEdge: TrackEdge = getEdgeFromStation(navigation.destination, graph) if(firstEdge == lastEdge){ - return Pair(result, debug) + return result } result.add(firstEdge.sendable as Edge) result.add(lastEdge.sendable as Edge) - if(currentPath.size > 0){ + if(currentPath.isNotEmpty()){ result.addAll(pathFromTo(firstEdge, graph, currentPath[0].first)) result.addAll(pathFromTo(graph.getConnection(currentPath[currentPath.size - 1]), graph, lastEdge.node1)) }else{ @@ -211,12 +210,11 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Pair< List, Lis result.addAll(pathFromTo(trackEdge, graph, currentPath[i + 1].first)) } } - return Pair(result, debug) + return result } val Train.sendable: CreateTrain get() { - val (currentPath, debug) = getCurrentTrainPath(navigation) return CreateTrain( id = id, name = name.string, @@ -226,7 +224,6 @@ val Train.sendable: CreateTrain backwards = speed < 0, stopped = speed == 0.0, schedule = runtime.sendable, - currentPath = currentPath, - debug = debug + currentPath = getCurrentTrainPath(navigation), ) } diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 5f4d5d6..152426d 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -160,7 +160,6 @@ data class CreateTrain( val speed: Double, val schedule: CreateSchedule?, val currentPath: List, - val debug: List ) @Serializable diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css index e205d8b..01a7a47 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css @@ -26,11 +26,7 @@ body { stroke: var(--track-free); } .track.path { - stroke: #0000ff33; -} - -.track.debug { - stroke: #00ff0033; + stroke: #0000ff99; } .train-name, diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 682a578..4965752 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -209,23 +209,6 @@ function startMapUpdates() { } }) - train.debug.forEach((trk) => { - const path = trk.path - if (path.length === 4) { - L.curve(["M", xz(path[0]), "C", xz(path[1]), xz(path[2]), xz(path[3])], { - className: "track debug", - interactive: false, - pane: "trains", - }).addTo(lmgr.layer(trk.dimension, "trains")) - } else if (path.length === 2) { - L.polyline([xz(path[0]), xz(path[1])], { - className: "track debug", - interactive: false, - pane: "trains", - }).addTo(lmgr.layer(trk.dimension, "trains")) - } - }) - schedule = "
On schedule
" train.schedule.instructions.forEach((instruction, i) => { if(instruction.instructionType == "Destination"){ From bdaccf19fa1f7941a4dc2ef698a035f0a6877270 Mon Sep 17 00:00:00 2001 From: ikawe Date: Sat, 27 Apr 2024 13:20:59 +0200 Subject: [PATCH 11/25] Portal support for current path --- src/main/kotlin/littlechasiu/ctm/Extensions.kt | 5 ++++- src/main/kotlin/littlechasiu/ctm/Server.kt | 1 + src/main/kotlin/littlechasiu/ctm/model/Config.kt | 2 ++ .../littlechasiu/ctm/static/assets/css/create-track-map.css | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 558b5c6..d855eda 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -158,7 +158,10 @@ private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNo if (tEdge.node1.netId == endNode.netId) { reachedEnd = true // incase tEdge is the next scheduled edge } else { - result.add(tEdge.sendable as Edge) + val edge = tEdge.sendable + if(edge is Edge) { // incase edge is portal, then ignore it + result.add(edge) + } } if (tEdge.node2.netId == endNode.netId || j > MAX_PREDICTIONS) { diff --git a/src/main/kotlin/littlechasiu/ctm/Server.kt b/src/main/kotlin/littlechasiu/ctm/Server.kt index 2f9f42d..b24de34 100644 --- a/src/main/kotlin/littlechasiu/ctm/Server.kt +++ b/src/main/kotlin/littlechasiu/ctm/Server.kt @@ -126,6 +126,7 @@ class Server { "track-occupied" to Color(mapStyle.colors.track.occupied), "track-reserved" to Color(mapStyle.colors.track.reserved), "track-free" to Color(mapStyle.colors.track.free), + "track-path" to Color(mapStyle.colors.track.path), "signal-green" to Color(mapStyle.colors.signal.green), "signal-yellow" to Color(mapStyle.colors.signal.yellow), "signal-red" to Color(mapStyle.colors.signal.red), diff --git a/src/main/kotlin/littlechasiu/ctm/model/Config.kt b/src/main/kotlin/littlechasiu/ctm/model/Config.kt index 45d3166..3e053c7 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Config.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Config.kt @@ -27,6 +27,8 @@ data class TrackColors( val reserved: String = "pink", @EncodeDefault val free: String = "white", + @EncodeDefault + val path: String = "#2244FF", ) @Serializable diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css index 01a7a47..5ef7ef5 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/create-track-map.css @@ -26,7 +26,7 @@ body { stroke: var(--track-free); } .track.path { - stroke: #0000ff99; + stroke: var(--track-path); } .train-name, From de29b241a10147c429f97280d6dda70f6422aef9 Mon Sep 17 00:00:00 2001 From: ikawe Date: Sat, 27 Apr 2024 13:50:29 +0200 Subject: [PATCH 12/25] Added tripDistance and distanceToDrive to api/trains -> currentPath TODO: Arrival time --- .../kotlin/littlechasiu/ctm/Extensions.kt | 9 ++++----- .../kotlin/littlechasiu/ctm/model/Network.kt | 19 +++++++++---------- .../ctm/static/assets/js/create-track-map.js | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index d855eda..7110000 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -120,7 +120,6 @@ val ScheduleRuntime.sendable instructions = getInstructions(it.entries), paused = paused, currentEntry = currentEntry, - arrivalCountdown = 0.0 ) } @@ -177,11 +176,11 @@ private fun getEdgeFromStation(station: GlobalStation, graph: TrackGraph) : Trac return graph.getConnection(Couple.create(firstNode, secondNode)) } -private fun getCurrentTrainPath(navigation: Navigation?) : List{ +private fun getCurrentTrainPath(navigation: Navigation?) : Path{ val graph = navigation?.train?.graph val result : ArrayList = ArrayList() if(navigation == null || graph == null || navigation.destination == null){ - return result + return Path(result, -1,0.0,0.0) } val field = Navigation::class.java.getDeclaredField("currentPath") field.isAccessible = true @@ -191,7 +190,7 @@ private fun getCurrentTrainPath(navigation: Navigation?) : List{ val firstEdge: TrackEdge = graph.getConnection(navigation.train.endpointEdges.first) val lastEdge: TrackEdge = getEdgeFromStation(navigation.destination, graph) if(firstEdge == lastEdge){ - return result + return Path(result, -1,0.0,0.0) } result.add(firstEdge.sendable as Edge) @@ -213,7 +212,7 @@ private fun getCurrentTrainPath(navigation: Navigation?) : List{ result.addAll(pathFromTo(trackEdge, graph, currentPath[i + 1].first)) } } - return result + return Path(result, -1,navigation.distanceStartedAt,navigation.distanceToDestination) } val Train.sendable: CreateTrain diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 152426d..77080f3 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -30,14 +30,6 @@ data class Point( val z: Double, ) -@Serializable -data class Path( - val start: DimensionLocation, - val firstControlPoint: Point, - val secondControlPoint: Point, - val end: DimensionLocation, -) - @Serializable data class Edge( val dimension: String, @@ -145,7 +137,14 @@ data class CreateSchedule( val cycling: Boolean, val paused: Boolean, val currentEntry: Int, - val arrivalCountdown: Double, +) + +@Serializable +data class Path( + val path : List, + val arrivingInSeconds : Int, + val tripDistance : Double, + val distanceToDrive : Double ) @Serializable @@ -159,7 +158,7 @@ data class CreateTrain( val stopped: Boolean, val speed: Double, val schedule: CreateSchedule?, - val currentPath: List, + val currentPath: Path, ) @Serializable diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 4965752..539b660 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -192,7 +192,7 @@ function startMapUpdates() { let schedule = "" if(train.schedule != null){ - train.currentPath.forEach((trk) => { + train.currentPath.path.forEach((trk) => { const path = trk.path if (path.length === 4) { L.curve(["M", xz(path[0]), "C", xz(path[1]), xz(path[2]), xz(path[3])], { From b2c382b5d3caad15f0ecd1ad4489715e8a3f55d2 Mon Sep 17 00:00:00 2001 From: ikawe Date: Sun, 28 Apr 2024 13:48:50 +0200 Subject: [PATCH 13/25] Trains don't get redrawn every train update, they get moved instead. Making them clickable. --- .../ctm/static/assets/js/create-track-map.js | 71 ++++++++++++------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 539b660..1cafe49 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -176,9 +176,16 @@ function startMapUpdates() { }) dmgr.onTrainStatus(({ trains }) => { - lmgr.clearTrains() + //lmgr.clearTrains() tmgr.update(trains) + let layerGroup = lmgr.dimension("minecraft:overworld")["trains"] + layerGroup.eachLayer(function (layer) { + if(layer.options.className === "track path" || layer.options.className === "train-head"){ + layerGroup.removeLayer(layer) + } + }); + trains.forEach((train) => { let leadCar = null if (!train.stopped) { @@ -222,7 +229,7 @@ function startMapUpdates() { } train.cars.forEach((car, i) => { - if(car.leading){ // lazily solves the missing carriage data (ignore the problem) + if(car.leading !== undefined){ // lazily solves the missing carriage data that sometimes happen for derailed trains (ignore the problem) let parts = car.portal ? [ [car.leading.dimension, [xz(car.leading.location), xz(car.portal.from.location)]], @@ -230,26 +237,42 @@ function startMapUpdates() { ] : [[car.leading.dimension, [xz(car.leading.location), xz(car.trailing.location)]]] - parts.map(([dim, part]) => - L.polyline(part, { - weight: 12, - lineCap: "square", - className: "train" + (leadCar === i ? " lead-car" : ""), - pane: "trains", - }) - .bindTooltip( - (train.cars.length === 1 - ? train.name - : `${train.name} ${i + 1}`) + schedule, - { - className: "train-name", - direction: "right", - offset: L.point(12, 0), - opacity: 0.7, + + + parts.map(([dim, part]) => { + + let layerGroup = lmgr.dimension(dim)["trains"] + let className = "train" + (leadCar === i ? " lead-car" : " carriage-" + i) + " " + train.id + let foundCar = false + + layerGroup.eachLayer(function(layer) { + if (layer.options.className === className) { + layer.setLatLngs(part) + foundCar = true } - ) - .addTo(lmgr.layer(dim, "trains")) - ) + }); + + if (!foundCar) { + L.polyline(part, { + weight: 12, + lineCap: "square", + className: "train" + (leadCar === i ? " lead-car" : " carriage-" + i) + " " + train.id, + pane: "trains", + }) + .bindTooltip( + (train.cars.length === 1 + ? train.name + : `${train.name} ${i + 1}`) + schedule, + { + className: "train-name", + direction: "right", + offset: L.point(12, 0), + opacity: 0.7, + } + ) + .addTo(lmgr.layer(dim, "trains")) + } + }) if (leadCar === i) { let [dim, edge] = train.backwards ? parts[parts.length - 1] : parts[0] @@ -258,13 +281,13 @@ function startMapUpdates() { L.marker(head, { icon: headIcon, + className: "train-head", rotationAngle: angle, pane: "trains", }).addTo(lmgr.layer(dim, "trains")) } - } + } + }) }) }) - - }) } From a0ef28344c17f54430e22df3a9910ca289ba7131 Mon Sep 17 00:00:00 2001 From: ikawe Date: Sun, 28 Apr 2024 17:29:13 +0200 Subject: [PATCH 14/25] Train paths will now be stored in "trainPaths" layer which can be turned on and off in UI (Train navigation) --- .../kotlin/littlechasiu/ctm/model/Config.kt | 1 + .../ctm/static/assets/js/create-track-map.js | 39 +++++++++++-------- .../ctm/static/assets/js/ctm.layer-manager.js | 7 +++- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/model/Config.kt b/src/main/kotlin/littlechasiu/ctm/model/Config.kt index 3e053c7..6876714 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Config.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Config.kt @@ -163,5 +163,6 @@ data class Config @OptIn(ExperimentalSerializationApi::class) constructor( "portals" to LayerConfig(label = "Portals"), "stations" to LayerConfig(label = "Stations"), "trains" to LayerConfig(label = "Trains"), + "trainPaths" to LayerConfig(label = "Train navigation"), ), ) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 1cafe49..11e5e71 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -7,12 +7,14 @@ let map = L.map("map", { map.createPane("tracks") map.createPane("blocks") map.createPane("signals") +map.createPane("trainPaths") map.createPane("trains") map.createPane("portals") map.createPane("stations") map.getPane("tracks").style.zIndex = 300 map.getPane("blocks").style.zIndex = 500 map.getPane("signals").style.zIndex = 600 +map.getPane("trainPaths").style.zIndex = 650 map.getPane("trains").style.zIndex = 700 map.getPane("portals").style.zIndex = 800 map.getPane("stations").style.zIndex = 800 @@ -177,15 +179,9 @@ function startMapUpdates() { dmgr.onTrainStatus(({ trains }) => { //lmgr.clearTrains() + lmgr.clearTrainPaths() tmgr.update(trains) - - let layerGroup = lmgr.dimension("minecraft:overworld")["trains"] - layerGroup.eachLayer(function (layer) { - if(layer.options.className === "track path" || layer.options.className === "train-head"){ - layerGroup.removeLayer(layer) - } - }); - + let whitelist = [] trains.forEach((train) => { let leadCar = null if (!train.stopped) { @@ -205,22 +201,22 @@ function startMapUpdates() { L.curve(["M", xz(path[0]), "C", xz(path[1]), xz(path[2]), xz(path[3])], { className: "track path", interactive: false, - pane: "trains", - }).addTo(lmgr.layer(trk.dimension, "trains")) + pane: "trainPaths", + }).addTo(lmgr.layer(trk.dimension, "trainPaths")) } else if (path.length === 2) { L.polyline([xz(path[0]), xz(path[1])], { className: "track path", interactive: false, - pane: "trains", - }).addTo(lmgr.layer(trk.dimension, "trains")) + pane: "trainPaths", + }).addTo(lmgr.layer(trk.dimension, "trainPaths")) } }) schedule = "
On schedule
" train.schedule.instructions.forEach((instruction, i) => { - if(instruction.instructionType == "Destination"){ + if(instruction.instructionType === "Destination"){ schedule += ""; - if(i == train.schedule.currentEntry){ + if(i === train.schedule.currentEntry){ schedule += "=> " } schedule += instruction.stationName + "
"; @@ -238,7 +234,6 @@ function startMapUpdates() { : [[car.leading.dimension, [xz(car.leading.location), xz(car.trailing.location)]]] - parts.map(([dim, part]) => { let layerGroup = lmgr.dimension(dim)["trains"] @@ -248,12 +243,13 @@ function startMapUpdates() { layerGroup.eachLayer(function(layer) { if (layer.options.className === className) { layer.setLatLngs(part) + whitelist.push(layer) foundCar = true } }); if (!foundCar) { - L.polyline(part, { + let layer = L.polyline(part, { weight: 12, lineCap: "square", className: "train" + (leadCar === i ? " lead-car" : " carriage-" + i) + " " + train.id, @@ -271,6 +267,7 @@ function startMapUpdates() { } ) .addTo(lmgr.layer(dim, "trains")) + whitelist.push(layer) } }) @@ -279,15 +276,23 @@ function startMapUpdates() { let [head, tail] = train.backwards ? [edge[1], edge[0]] : [edge[0], edge[1]] let angle = 180 + (Math.atan2(tail[0] - head[0], tail[1] - head[1]) * 180) / Math.PI - L.marker(head, { + let layer = L.marker(head, { icon: headIcon, className: "train-head", rotationAngle: angle, pane: "trains", }).addTo(lmgr.layer(dim, "trains")) + whitelist.push(layer) } } }) }) + Array.from(Object.values(lmgr.actualLayers)).forEach((obj) => { + obj.trains.eachLayer(function(layer) { + if (!whitelist.includes(layer)) { + obj.trains.removeLayer(layer) + } + }); + }) }) } diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.layer-manager.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.layer-manager.js index f46853f..0942d62 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.layer-manager.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.layer-manager.js @@ -13,6 +13,7 @@ class LayerManager { portals: L.layerGroup([]).addTo(map), stations: L.layerGroup([]).addTo(map), trains: L.layerGroup([]).addTo(map), + trainPaths: L.layerGroup([]).addTo(map), } this.actualLayers = {} @@ -55,6 +56,7 @@ class LayerManager { portals: L.layerGroup([]), stations: L.layerGroup([]), trains: L.layerGroup([]), + trainPaths: L.layerGroup([]), } let layer = (this.dimensionLayers[name] = L.layerGroup([])) layer.name = name @@ -134,7 +136,10 @@ class LayerManager { _clearLayers(key) { Array.from(Object.values(this.actualLayers)).forEach((obj) => obj[key].clearLayers()) } - + + clearTrainPaths() { + this._clearLayers("trainPaths") + } clearTracks() { this._clearLayers("tracks") } From 4d50170d655d6d9e8907307aed6a33a4d8a2b5a0 Mon Sep 17 00:00:00 2001 From: ikawe Date: Mon, 29 Apr 2024 17:52:46 +0200 Subject: [PATCH 15/25] Added train info, window opens when train is clicked. Used leaflet-control-window --- LICENSE_leaflet_control_window.txt | 22 ++ .../kotlin/littlechasiu/ctm/Extensions.kt | 36 +-- .../static/assets/css/L.Control.Window.css | 129 ++++++++ .../ctm/static/assets/js/L.Control.Window.js | 293 ++++++++++++++++++ .../ctm/static/assets/js/create-track-map.js | 147 +++++---- .../assets/littlechasiu/ctm/static/index.html | 2 + 6 files changed, 552 insertions(+), 77 deletions(-) create mode 100644 LICENSE_leaflet_control_window.txt create mode 100644 src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css create mode 100644 src/main/resources/assets/littlechasiu/ctm/static/assets/js/L.Control.Window.js diff --git a/LICENSE_leaflet_control_window.txt b/LICENSE_leaflet_control_window.txt new file mode 100644 index 0000000..a9aa88a --- /dev/null +++ b/LICENSE_leaflet_control_window.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015, Filip Zavadil +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 7110000..314cd6e 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -86,29 +86,21 @@ val Carriage.sendable fun getInstructions(instructions: List): ArrayList { val result: ArrayList = ArrayList() - for(entry in instructions){ - if(entry.instruction is DestinationInstruction){ - result.add( - ScheduleInstructionDestination( - stationName = (entry.instruction as DestinationInstruction).summary.second.string, - ) - ) - } - if(entry.instruction is ChangeTitleInstruction){ - result.add( - ScheduleInstructionNameChange( - newName = (entry.instruction as ChangeTitleInstruction).scheduleTitle, - ) - ) - } - if(entry.instruction is ChangeThrottleInstruction){ - result.add( - ScheduleInstructionThrottleChange( - throttle = (entry.instruction as ChangeThrottleInstruction).summary.second.string, - ) - ) + for (entry in instructions) { + when (val instruction = entry.instruction) { + is DestinationInstruction -> { + val stationName = instruction.summary.second.string + result.add(ScheduleInstructionDestination(stationName = stationName)) + } + is ChangeTitleInstruction -> { + val newName = instruction.scheduleTitle + result.add(ScheduleInstructionNameChange(newName = newName)) + } + is ChangeThrottleInstruction -> { + val throttle = instruction.summary.second.string + result.add(ScheduleInstructionThrottleChange(throttle = throttle)) + } } - } return result } diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css new file mode 100644 index 0000000..e34332c --- /dev/null +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css @@ -0,0 +1,129 @@ +.leaflet-control-window-wrapper{ + display: none; + opacity: 0; + -webkit-overflow-scrolling: touch; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.nonmodal{ + z-index: 6000; +} + +.modal{ + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 7000; + background-color: rgba(0, 0, 0, 0.7); +} + +.visible { + display: block; + opacity: 1; +} + +.leaflet-control-window{ + position: absolute; + z-index: 2000; + border-radius: 2px; + margin: 8px; + + /** BOX SHADOW **/ + -webkit-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.75); + -moz-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.75); + box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.75); +} + +.control-window{ + background-color: #ffffff; + color: #353535; + font: 14px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; +} + + +.leaflet-control-window .titlebar{ + min-height: 38px; + cursor: grab; + cursor: -webkit-grab; + cursor: -moz-grab; + padding: 10px 45px 10px 10px; +} + +.leaflet-control-window .close { + position: absolute; + top: 8px; + right: 8px; + width: 28px; + height: 28px; + border-radius: 100%; + font: 16px/14px Tahoma, Verdana, sans-serif; + cursor: pointer; + z-index:30; + + background-color: rgba(0, 0, 0, 0.40); + transition-property: background; + transition-duration: 0.2s; + transition-timing-function: linear; + + + color: #e4e4e4; + font-size: 22pt; + text-align: center; + line-height: 0.9em; +} + +.leaflet-control-window .close:hover { + background-color: rgba(0, 0, 0, 0.65); +} + +.leaflet-control-window .content{ + padding: 8px; + margin-top: -10px; + z-index:29; + overflow: auto; +} + +.leaflet-control-window .promptButtons{ + text-align: right; + padding: 16px; +} + +.leaflet-control-window button{ + position: relative; + display: inline-block; + background-color: transparent; + color: inherit; + + opacity: 0.5; + transition-property: opacity; + transition-duration: 0.2s; + transition-timing-function: linear; + + cursor:pointer; + font-size: medium; + font-weight: bold; + text-decoration:none; + text-align: center; + vertical-align: middle; + border: 0; + -webkit-border-radius: 4px; + border-radius: 4px; + padding: 8px; + margin: 12px 8px 0 8px; +} + +.leaflet-control-window button:focus { + outline:0; +} + +.leaflet-control-window button:hover { + opacity: 1; +} +.disabled{ + opacity: .5; + pointer-events:none; +} diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/L.Control.Window.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/L.Control.Window.js new file mode 100644 index 0000000..45b2b76 --- /dev/null +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/L.Control.Window.js @@ -0,0 +1,293 @@ +L.Control.Window = L.Control.extend({ + + includes: L.Evented.prototype || L.Mixin.Events, + + options: { + element: 'map', + className: 'control-window', + visible: false, + title: undefined, + closeButton: true, + content: undefined, + prompt: undefined, + maxWidth: 600, + modal: false, + position: 'center' + }, + initialize: function (container, options) { + var self = this; + + if (container.hasOwnProperty('options')) { container = container.getContainer(); } + + options.element = container; + L.setOptions(this, options); + + var modality = 'nonmodal'; + + if (this.options.modal){ + modality = 'modal' + } + + // Create popup window container + this._wrapper = L.DomUtil.create('div',modality+' leaflet-control-window-wrapper', L.DomUtil.get(this.options.element)); + + this._container = L.DomUtil.create('div', 'leaflet-control leaflet-control-window '+this.options.className,this._wrapper); + this._container.setAttribute('style','max-width:'+this.options.maxWidth+'px'); + + this._containerTitleBar = L.DomUtil.create('div', 'titlebar',this._container); + this.titleContent = L.DomUtil.create('h2', 'title',this._containerTitleBar); + this._containerContent = L.DomUtil.create('div', 'content' ,this._container); + this._containerPromptButtons = L.DomUtil.create('div', 'promptButtons' ,this._container); + + if (this.options.closeButton) { + this._closeButton = L.DomUtil.create('a', 'close',this._containerTitleBar); + this._closeButton.innerHTML = '×'; + } + + // Make sure we don't drag the map when we interact with the content + var stop = L.DomEvent.stopPropagation; + L.DomEvent + .on(this._wrapper, 'contextmenu', stop) + .on(this._wrapper, 'click', stop) + .on(this._wrapper, 'mousedown', stop) + .on(this._wrapper, 'touchstart', stop) + .on(this._wrapper, 'dblclick', stop) + .on(this._wrapper, 'mousewheel', stop) + .on(this._wrapper, 'MozMousePixelScroll', stop) + + // Attach event to close button + if (this.options.closeButton) { + var close = this._closeButton; + L.DomEvent.on(close, 'click', this.hide, this); + } + if (this.options.title){ + this.title(this.options.title); + } + if (this.options.content) { + this.content(this.options.content); + } + if (typeof(this.options.prompt)=='object') { + this.prompt(this.options.prompt); + } + if (this.options.visible){ + this.show(); + } + + //map.on('resize',function(){self.mapResized()}); + }, + disableBtn: function(){ + this._btnOK.disabled=true; + this._btnOK.className='disabled'; + }, + enableBtn: function(){ + this._btnOK.disabled=false; + this._btnOK.className=''; + }, + title: function(titleContent){ + if (titleContent==undefined){ + return this.options.title + } + + this.options.title = titleContent; + var title = titleContent || ''; + this.titleContent.innerHTML = title; + return this; + }, + remove: function () { + + L.DomUtil.get(this.options.element).removeChild(this._wrapper); + + // Unregister events to prevent memory leak + var stop = L.DomEvent.stopPropagation; + L.DomEvent + .off(this._wrapper, 'contextmenu', stop) + .off(this._wrapper, 'click', stop) + .off(this._wrapper, 'mousedown', stop) + .off(this._wrapper, 'touchstart', stop) + .off(this._wrapper, 'dblclick', stop) + .off(this._wrapper, 'mousewheel', stop) + .off(this._wrapper, 'MozMousePixelScroll', stop); + + // map.off('resize',self.mapResized); + + if (this._closeButton && this._close) { + var close = this._closeButton; + L.DomEvent.off(close, 'click', this.close, this); + } + return this; + }, + mapResized : function(){ + // this.show() + }, + show: function (position) { + + if (position){ + this.options.position = position + } + + L.DomUtil.addClass(this._wrapper, 'visible'); + + + this.setContentMaxHeight(); + var thisWidth = this._container.offsetWidth; + var thisHeight = this._container.offsetHeight; + var margin = 8; + + var el = L.DomUtil.get(this.options.element); + var rect = el.getBoundingClientRect(); + var width = rect.right -rect.left || Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + var height = rect.bottom -rect.top || Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + + var top = rect.top; + var left = rect.left; + var offset =0; + + // SET POSITION OF WINDOW + if (this.options.position == 'topLeft'){ + this.showOn([left,top+offset]) + } else if (this.options.position == 'left') { + this.showOn([left, top+height/2-thisHeight/2-margin+offset]) + } else if (this.options.position == 'bottomLeft') { + this.showOn([left, top+height-thisHeight-margin*2-offset]) + } else if (this.options.position == 'top') { + this.showOn([left+width/2-thisWidth/2-margin,top+offset]) + } else if (this.options.position == 'topRight') { + this.showOn([left+width-thisWidth-margin*2,top+ offset]) + } else if (this.options.position == 'right') { + this.showOn([left+width-thisWidth-margin*2, top+height/2-thisHeight/2-margin+offset]) + } else if (this.options.position == 'bottomRight') { + this.showOn([left+width-thisWidth-margin*2,top+ height-thisHeight-margin*2-offset]) + } else if (this.options.position == 'bottom') { + this.showOn([left+width/2-thisWidth/2-margin,top+ height-thisHeight-margin*2-offset]) + } else { + this.showOn([left+width/2-thisWidth/2-margin, top+top+height/2-thisHeight/2-margin+offset]) + } + + return this; + }, + showOn: function(point){ + + this.setContentMaxHeight(); + L.DomUtil.setPosition(this._container, L.point(Math.round(point[0]),Math.round(point[1]),true)); + + var draggable = new L.Draggable(this._container,this._containerTitleBar); + draggable.enable(); + + L.DomUtil.addClass(this._wrapper, 'visible'); + this.fire('show'); + return this; + }, + hide: function (e) { + + L.DomUtil.removeClass(this._wrapper, 'visible'); + this.fire('hide'); + return this; + }, + + getContainer: function () { + return this._containerContent; + }, + content: function (content) { + if (content==undefined){ + return this.options.content + } + this.options.content = content; + this.getContainer().innerHTML = content; + return this; + }, + prompt : function(promptObject){ + + if (promptObject==undefined){ + return this.options.prompt + } + + this.options.prompt = promptObject; + + this.setPromptCallback(promptObject.callback); + + this.setActionCallback(promptObject.action); + + var cancel = this.options.prompt.buttonCancel || undefined; + + var ok = this.options.prompt.buttonOK || 'OK'; + + var action = this.options.prompt.buttonAction || undefined; + + if (action != undefined) { + var btnAction = L.DomUtil.create('button','',this._containerPromptButtons); + L.DomEvent.on(btnAction, 'click',this.action, this); + btnAction.innerHTML=action; + } + + var btnOK= L.DomUtil.create('button','',this._containerPromptButtons); + L.DomEvent.on(btnOK, 'click',this.promptCallback, this); + btnOK.innerHTML=ok; + + this._btnOK=btnOK; + + if (cancel != undefined) { + var btnCancel= L.DomUtil.create('button','',this._containerPromptButtons); + L.DomEvent.on(btnCancel, 'click', this.close, this); + btnCancel.innerHTML=cancel + } + + return this; + }, + container : function(containerContent){ + if (containerContent==undefined){ + return this._container.innerHTML + } + + this._container.innerHTML = containerContent; + + if (this.options.closeButton) { + this._closeButton = L.DomUtil.create('a', 'close',this._container); + this._closeButton.innerHTML = '×'; + L.DomEvent.on(this._closeButton, 'click', this.close, this); + } + return this; + + }, + setPromptCallback : function(callback){ + var self = this; + if (typeof(callback)!= 'function') { callback = function() {console.warn('No callback function specified!');}} + var cb = function() { self.close();callback();}; + this.promptCallback = cb; + return this; + }, + setActionCallback : function(callback){ + var self = this; + if (typeof(callback)!= 'function') { callback = function() {console.warn('No callback function specified!');}} + var cb = function() { self.hide();callback();}; + this.action = cb; + return this; + }, + + setContentMaxHeight : function(){ + var margin = 68; + + if (this.options.title){ + margin += this._containerTitleBar.offsetHeight-36; + } + if (typeof(this.options.prompt) == 'object'){ + margin += this._containerPromptButtons.offsetHeight-20 + } + + var el = L.DomUtil.get(this.options.element) + var rect = el.getBoundingClientRect(); + var height = rect.bottom -rect.top; + + var maxHeight = height - margin; + this._containerContent.setAttribute('style','max-height:'+maxHeight+'px') + }, + close : function(){ + this.hide(); + this.remove(); + this.fire('close'); + return undefined; + } +}); + +L.control.window = function (container,options) { + return new L.Control.Window(container,options); +}; diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 11e5e71..f334233 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -192,9 +192,7 @@ function startMapUpdates() { } } - let schedule = "" if(train.schedule != null){ - train.currentPath.path.forEach((trk) => { const path = trk.path if (path.length === 4) { @@ -211,19 +209,10 @@ function startMapUpdates() { }).addTo(lmgr.layer(trk.dimension, "trainPaths")) } }) - - schedule = "
On schedule
" - train.schedule.instructions.forEach((instruction, i) => { - if(instruction.instructionType === "Destination"){ - schedule += ""; - if(i === train.schedule.currentEntry){ - schedule += "=> " - } - schedule += instruction.stationName + "
"; - } - }) } - + if(openTrainInfos[train.id] != null){ + openTrainInfos[train.id].content(getTrainInfoHTML(train)) + } train.cars.forEach((car, i) => { if(car.leading !== undefined){ // lazily solves the missing carriage data that sometimes happen for derailed trains (ignore the problem) let parts = car.portal @@ -233,43 +222,44 @@ function startMapUpdates() { ] : [[car.leading.dimension, [xz(car.leading.location), xz(car.trailing.location)]]] + parts.map(([dim, part]) => { + let layerGroup = lmgr.dimension(dim)["trains"] + let className = "train" + (leadCar === i ? " lead-car" : " carriage-" + i) + " " + train.id + let foundCar = false - parts.map(([dim, part]) => { - - let layerGroup = lmgr.dimension(dim)["trains"] - let className = "train" + (leadCar === i ? " lead-car" : " carriage-" + i) + " " + train.id - let foundCar = false - - layerGroup.eachLayer(function(layer) { - if (layer.options.className === className) { - layer.setLatLngs(part) - whitelist.push(layer) - foundCar = true - } - }); - - if (!foundCar) { - let layer = L.polyline(part, { - weight: 12, - lineCap: "square", - className: "train" + (leadCar === i ? " lead-car" : " carriage-" + i) + " " + train.id, - pane: "trains", - }) - .bindTooltip( - (train.cars.length === 1 - ? train.name - : `${train.name} ${i + 1}`) + schedule, - { - className: "train-name", - direction: "right", - offset: L.point(12, 0), - opacity: 0.7, - } - ) - .addTo(lmgr.layer(dim, "trains")) + layerGroup.eachLayer(function(layer) { + if (layer.options.className === className) { + layer.setLatLngs(part) whitelist.push(layer) + foundCar = true } - }) + }); + + if (!foundCar) { + let layer = L.polyline(part, { + weight: 12, + lineCap: "square", + className: "train" + (leadCar === i ? " lead-car" : " carriage-" + i) + " " + train.id, + pane: "trains", + }).addEventListener("click",function(event){ + if(!openTrainInfos[train.id]) { + openTrainInfo(train, dim) + } + },true).bindTooltip( + (train.cars.length === 1 + ? train.name + : `${train.name} ${i + 1}`), + { + className: "train-name", + direction: "right", + offset: L.point(12, 0), + opacity: 0.7, + } + ) + .addTo(lmgr.layer(dim, "trains")) + whitelist.push(layer) + } + }) if (leadCar === i) { let [dim, edge] = train.backwards ? parts[parts.length - 1] : parts[0] @@ -281,18 +271,65 @@ function startMapUpdates() { className: "train-head", rotationAngle: angle, pane: "trains", - }).addTo(lmgr.layer(dim, "trains")) + }).addTo(lmgr.layer(dim,"trains")) whitelist.push(layer) } } }) }) - Array.from(Object.values(lmgr.actualLayers)).forEach((obj) => { - obj.trains.eachLayer(function(layer) { - if (!whitelist.includes(layer)) { - obj.trains.removeLayer(layer) - } - }); + Array.from(Object.values(lmgr.actualLayers)).forEach((obj) => { + obj.trains.eachLayer(function(layer) { + if (!whitelist.includes(layer)) { + obj.trains.removeLayer(layer) + } + }); + }) }) +} + +function getTrainInfoHTML(train){ + let schedule = "

" + train.name + "


" + + if(train.schedule) { + let currentInstruction = train.schedule.currentEntry + let instructions = train.schedule.instructions + schedule += "On schedule
" + if(instructions[currentInstruction].instructionType === "Destination") { + schedule += "Next destination: " + instructions[currentInstruction].stationName + "" + }else{ + schedule += "Next destination: Unknown" + } + schedule += "
" + schedule += "Time until arrival: Unknown
" + + if(train.currentPath.tripDistance === 0){ + schedule += "Distance to destination: Arrived" + }else { + schedule += "Distance to destination: " + Math.floor(train.currentPath.distanceToDrive) + "/" + Math.floor(train.currentPath.tripDistance) + " blocks" + } + schedule += "
" + + train.schedule.instructions.forEach((instruction, i) => { + if (instruction.instructionType === "Destination") { + schedule += "" + instruction.stationName; + if (i === train.schedule.currentEntry) { + schedule += "←" + } + schedule += "
"; + } }) + + } + schedule += "
" + return schedule +} + +let openTrainInfos = {} +function openTrainInfo(train){ + var win = L.control.window(map,{content:getTrainInfoHTML(train)}).show() + win._closeButton.addEventListener("click", function(event){ + delete openTrainInfos[train.id] + }) + + openTrainInfos[train.id] = win } diff --git a/src/main/resources/assets/littlechasiu/ctm/static/index.html b/src/main/resources/assets/littlechasiu/ctm/static/index.html index 5a92a28..a445376 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/index.html +++ b/src/main/resources/assets/littlechasiu/ctm/static/index.html @@ -5,6 +5,7 @@ + @@ -16,6 +17,7 @@ + Date: Mon, 29 Apr 2024 17:57:14 +0200 Subject: [PATCH 16/25] Train path is only displayed when the same trains train info window is opened --- .../ctm/static/assets/js/create-track-map.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index f334233..bc75078 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -192,8 +192,11 @@ function startMapUpdates() { } } - if(train.schedule != null){ - train.currentPath.path.forEach((trk) => { + if(openTrainInfos[train.id] != null){ + openTrainInfos[train.id].content(getTrainInfoHTML(train)) + + if(train.schedule != null){ + train.currentPath.path.forEach((trk) => { const path = trk.path if (path.length === 4) { L.curve(["M", xz(path[0]), "C", xz(path[1]), xz(path[2]), xz(path[3])], { @@ -209,9 +212,8 @@ function startMapUpdates() { }).addTo(lmgr.layer(trk.dimension, "trainPaths")) } }) - } - if(openTrainInfos[train.id] != null){ - openTrainInfos[train.id].content(getTrainInfoHTML(train)) + } + } train.cars.forEach((car, i) => { if(car.leading !== undefined){ // lazily solves the missing carriage data that sometimes happen for derailed trains (ignore the problem) From 1c6c130c3e45b1f92e7217d6bd4c5093787cbe87 Mon Sep 17 00:00:00 2001 From: ikawe Date: Tue, 30 Apr 2024 18:07:14 +0200 Subject: [PATCH 17/25] Schedule times are now displayed in train info window (not accounting wait times) --- .../kotlin/littlechasiu/ctm/Extensions.kt | 39 +++++++++----- .../kotlin/littlechasiu/ctm/model/Network.kt | 3 +- .../static/assets/css/L.Control.Window.css | 28 ++++++++-- .../ctm/static/assets/js/create-track-map.js | 52 +++++++++++++++---- 4 files changed, 96 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 314cd6e..75d6057 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -8,8 +8,11 @@ import com.simibubi.create.content.trains.graph.TrackEdge import com.simibubi.create.content.trains.graph.TrackGraph import com.simibubi.create.content.trains.graph.TrackNode import com.simibubi.create.content.trains.graph.TrackNodeLocation +import com.simibubi.create.content.trains.schedule.Schedule import com.simibubi.create.content.trains.schedule.ScheduleEntry import com.simibubi.create.content.trains.schedule.ScheduleRuntime +import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition +import com.simibubi.create.content.trains.schedule.condition.ScheduledDelay import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction @@ -83,14 +86,20 @@ val Carriage.sendable }, ) -fun getInstructions(instructions: List): ArrayList { +fun getInstructions(scheduleRuntime: ScheduleRuntime): ArrayList { val result: ArrayList = ArrayList() + val instructions = scheduleRuntime.schedule.entries - for (entry in instructions) { + val field = ScheduleRuntime::class.java.getDeclaredField("predictionTicks") + field.isAccessible = true + @Suppress("UNCHECKED_CAST") + val predictionTicks = field.get(scheduleRuntime) as List + + instructions.forEachIndexed { i, entry -> when (val instruction = entry.instruction) { is DestinationInstruction -> { - val stationName = instruction.summary.second.string - result.add(ScheduleInstructionDestination(stationName = stationName)) + var stationName = instruction.summary.second.string + result.add(ScheduleInstructionDestination(stationName = stationName, ticksToComplete = predictionTicks[i])) } is ChangeTitleInstruction -> { val newName = instruction.scheduleTitle @@ -107,11 +116,17 @@ fun getInstructions(instructions: List): ArrayList = ArrayList() if(navigation == null || graph == null || navigation.destination == null){ - return Path(result, -1,0.0,0.0) + return Path(result,0.0,0.0) } val field = Navigation::class.java.getDeclaredField("currentPath") field.isAccessible = true @@ -182,7 +197,7 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ val firstEdge: TrackEdge = graph.getConnection(navigation.train.endpointEdges.first) val lastEdge: TrackEdge = getEdgeFromStation(navigation.destination, graph) if(firstEdge == lastEdge){ - return Path(result, -1,0.0,0.0) + return Path(result,0.0,0.0) } result.add(firstEdge.sendable as Edge) @@ -204,7 +219,7 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ result.addAll(pathFromTo(trackEdge, graph, currentPath[i + 1].first)) } } - return Path(result, -1,navigation.distanceStartedAt,navigation.distanceToDestination) + return Path(result,navigation.distanceStartedAt,navigation.distanceToDestination) } val Train.sendable: CreateTrain diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 77080f3..05b1dcd 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -119,6 +119,7 @@ sealed class ScheduleInstruction( @Serializable data class ScheduleInstructionDestination( val stationName : String, + val ticksToComplete : Int, ) : ScheduleInstruction(instructionType = "Destination") @Serializable @@ -137,12 +138,12 @@ data class CreateSchedule( val cycling: Boolean, val paused: Boolean, val currentEntry: Int, + val ticksInTransit: Int, ) @Serializable data class Path( val path : List, - val arrivingInSeconds : Int, val tripDistance : Double, val distanceToDrive : Double ) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css index e34332c..3046a9c 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/css/L.Control.Window.css @@ -39,18 +39,39 @@ } .control-window{ + min-width: 300px; background-color: #ffffff; color: #353535; - font: 14px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + font-family: var(--ui-font); + font-weight: bold; + font-size: 15px; } +.control-window .destination{ + padding-left: 8px; + padding-right: 8px; + width: 100%; + border-radius: 8px; + + display: flex; + justify-content: space-between; + align-items: center; +} +.control-window .marked{ + background-color: #353535; + color: #ffffff; + width: 100%; +} .leaflet-control-window .titlebar{ - min-height: 38px; cursor: grab; cursor: -webkit-grab; cursor: -moz-grab; - padding: 10px 45px 10px 10px; + padding: 10px 10px 10px 10px; +} + +.leaflet-control-window .title{ + max-height: 0px; } .leaflet-control-window .close { @@ -82,7 +103,6 @@ .leaflet-control-window .content{ padding: 8px; - margin-top: -10px; z-index:29; overflow: auto; } diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index bc75078..060030b 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -290,8 +290,7 @@ function startMapUpdates() { } function getTrainInfoHTML(train){ - let schedule = "

" + train.name + "


" - + let schedule = "
" if(train.schedule) { let currentInstruction = train.schedule.currentEntry let instructions = train.schedule.instructions @@ -302,22 +301,27 @@ function getTrainInfoHTML(train){ schedule += "Next destination: Unknown" } schedule += "
" - schedule += "Time until arrival: Unknown
" + + schedule += "Time until arrival: " + ticksToMMSS(calculateRemainingTicks(train, train.schedule.currentEntry)) + "
" + if(train.currentPath.tripDistance === 0){ - schedule += "Distance to destination: Arrived" + schedule += "Distance: Arrived" }else { - schedule += "Distance to destination: " + Math.floor(train.currentPath.distanceToDrive) + "/" + Math.floor(train.currentPath.tripDistance) + " blocks" + schedule += "Distance: " + Math.floor(train.currentPath.distanceToDrive) + "/" + Math.floor(train.currentPath.tripDistance) + " blocks" } schedule += "
" train.schedule.instructions.forEach((instruction, i) => { if (instruction.instructionType === "Destination") { - schedule += "" + instruction.stationName; + let className = "destination" if (i === train.schedule.currentEntry) { - schedule += "←" + className += " marked" } - schedule += "
"; + schedule += "
" + schedule += "" + instruction.stationName + "" + schedule += "" + ticksToMMSS(calculateRemainingTicks(train, i)) + schedule += "
"; } }) @@ -328,10 +332,40 @@ function getTrainInfoHTML(train){ let openTrainInfos = {} function openTrainInfo(train){ - var win = L.control.window(map,{content:getTrainInfoHTML(train)}).show() + var win = L.control.window(map,{title:train.name,content:getTrainInfoHTML(train)}).show() win._closeButton.addEventListener("click", function(event){ delete openTrainInfos[train.id] }) openTrainInfos[train.id] = win } + +function calculateRemainingTicks(train, instructionIndex){ + let instructions = train.schedule.instructions + let currentIndex = train.schedule.currentEntry + + let totalTicks = 0 + for (let i = 0; i < instructions.length; i++) { + let index = (currentIndex + i)%instructions.length + if(instructions[index].instructionType === "Destination"){ + if(instructions[index].ticksToComplete === -1){ + return "Unknown" + } + totalTicks += instructions[index].ticksToComplete + } + if(index === instructionIndex){ + break + } + } + return totalTicks - train.schedule.ticksInTransit +} + +function ticksToMMSS(ticks) { + if(ticks === "Unknown"){ + return "Unknown" + } + let seconds = Math.floor(ticks / 20) + let minutes = Math.floor(seconds / 60); + let remainingSeconds = seconds % 60; + return (minutes < 10 ? '0' : '') + minutes + ':' + (remainingSeconds < 10 ? '0' : '') + remainingSeconds; +} From 69c1765dc5bd72c2e9ff56f7f487fe2432dfd1a4 Mon Sep 17 00:00:00 2001 From: ikawe Date: Tue, 30 Apr 2024 20:48:14 +0200 Subject: [PATCH 18/25] added enable_navigation_tracks to config --- src/main/kotlin/littlechasiu/ctm/Extensions.kt | 8 +++----- src/main/kotlin/littlechasiu/ctm/TrackMap.kt | 3 +++ src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt | 1 + src/main/kotlin/littlechasiu/ctm/model/Config.kt | 4 ++++ src/main/kotlin/littlechasiu/ctm/model/Network.kt | 8 +++++++- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 75d6057..eb07cc6 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -8,11 +8,7 @@ import com.simibubi.create.content.trains.graph.TrackEdge import com.simibubi.create.content.trains.graph.TrackGraph import com.simibubi.create.content.trains.graph.TrackNode import com.simibubi.create.content.trains.graph.TrackNodeLocation -import com.simibubi.create.content.trains.schedule.Schedule -import com.simibubi.create.content.trains.schedule.ScheduleEntry import com.simibubi.create.content.trains.schedule.ScheduleRuntime -import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition -import com.simibubi.create.content.trains.schedule.condition.ScheduledDelay import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction @@ -147,6 +143,7 @@ private fun getNextEdge(graph: TrackGraph, trackNode: TrackNode, trackEdge: Trac } private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNode) : ArrayList { + //BEWARE! this method only goes straight this is not a pathfinder. It's used to get from last turn to the station val result = ArrayList() var tEdge: TrackEdge = startEdge @@ -183,10 +180,11 @@ private fun getEdgeFromStation(station: GlobalStation, graph: TrackGraph) : Trac return graph.getConnection(Couple.create(firstNode, secondNode)) } + private fun getCurrentTrainPath(navigation: Navigation?) : Path{ val graph = navigation?.train?.graph val result : ArrayList = ArrayList() - if(navigation == null || graph == null || navigation.destination == null){ + if(!CreateTrain.enableNavigationTracks || navigation == null || graph == null || navigation.destination == null){ return Path(result,0.0,0.0) } val field = Navigation::class.java.getDeclaredField("currentPath") diff --git a/src/main/kotlin/littlechasiu/ctm/TrackMap.kt b/src/main/kotlin/littlechasiu/ctm/TrackMap.kt index e032808..65eccfa 100644 --- a/src/main/kotlin/littlechasiu/ctm/TrackMap.kt +++ b/src/main/kotlin/littlechasiu/ctm/TrackMap.kt @@ -1,5 +1,6 @@ package littlechasiu.ctm +import com.ibm.icu.impl.locale.Extension import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel @@ -9,6 +10,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.encodeToStream import littlechasiu.ctm.model.Config +import littlechasiu.ctm.model.CreateTrain import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents import net.fabricmc.loader.api.FabricLoader @@ -77,6 +79,7 @@ object TrackMap { watcher.enable = config.enable server.enable = config.enable watcher.watchInterval = config.watchIntervalSeconds.seconds + CreateTrain.enableNavigationTracks = config.enableNavigationTracks server.port = config.serverPort server.mapStyle = config.mapStyle server.mapView = config.mapView diff --git a/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt b/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt index 2dbb0ca..3cbdebc 100644 --- a/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt +++ b/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt @@ -24,6 +24,7 @@ import kotlin.time.Duration.Companion.seconds class TrackWatcher() { var enable: Boolean = true var watchInterval: Duration = 0.5.seconds + var enableNavigationTracks: Boolean = true private var stopping: Boolean = false private var thread: Thread? = null diff --git a/src/main/kotlin/littlechasiu/ctm/model/Config.kt b/src/main/kotlin/littlechasiu/ctm/model/Config.kt index 6876714..1a08066 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Config.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Config.kt @@ -142,6 +142,10 @@ data class Config @OptIn(ExperimentalSerializationApi::class) constructor( @SerialName("server_port") @EncodeDefault val serverPort: Int = 3876, + @SerialName("enable_navigation_tracks") + @EncodeDefault + val enableNavigationTracks: Boolean = true, + @SerialName("map_style") @EncodeDefault diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 05b1dcd..678e1ec 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -1,9 +1,11 @@ package littlechasiu.ctm.model +import com.simibubi.create.content.trains.entity.Navigation import com.simibubi.create.content.trains.signal.SignalBlock.SignalType import com.simibubi.create.content.trains.signal.SignalBlockEntity.SignalState import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder @@ -160,7 +162,11 @@ data class CreateTrain( val speed: Double, val schedule: CreateSchedule?, val currentPath: Path, -) +){ + companion object { + var enableNavigationTracks: Boolean = true + } +} @Serializable data class TrainStatus( From 06513a0b35848ba4081124cea30ad6dc4c54b814 Mon Sep 17 00:00:00 2001 From: ikawe Date: Tue, 30 Apr 2024 21:07:26 +0200 Subject: [PATCH 19/25] cleaned up a little --- src/main/kotlin/littlechasiu/ctm/Extensions.kt | 5 +++-- src/main/kotlin/littlechasiu/ctm/TrackMap.kt | 1 - src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt | 1 - src/main/kotlin/littlechasiu/ctm/model/Network.kt | 2 -- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index eb07cc6..ba09d74 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -51,6 +51,7 @@ val TrackNode.dimensionLocation: DimensionLocation get() = DimensionLocation(location.dimension.string, location.sendable) +@Suppress("IMPLICIT_CAST_TO_ANY") val TrackEdge.sendable get() = if (isInterDimensional) @@ -94,7 +95,7 @@ fun getInstructions(scheduleRuntime: ScheduleRuntime): ArrayList when (val instruction = entry.instruction) { is DestinationInstruction -> { - var stationName = instruction.summary.second.string + val stationName = instruction.summary.second.string result.add(ScheduleInstructionDestination(stationName = stationName, ticksToComplete = predictionTicks[i])) } is ChangeTitleInstruction -> { @@ -150,7 +151,7 @@ private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNo var direction: Vec3 var reachedEnd = false - val MAX_PREDICTIONS = 50; + val MAX_PREDICTIONS = 50 var j = 0 while (!reachedEnd) { diff --git a/src/main/kotlin/littlechasiu/ctm/TrackMap.kt b/src/main/kotlin/littlechasiu/ctm/TrackMap.kt index 65eccfa..9a3a7af 100644 --- a/src/main/kotlin/littlechasiu/ctm/TrackMap.kt +++ b/src/main/kotlin/littlechasiu/ctm/TrackMap.kt @@ -1,6 +1,5 @@ package littlechasiu.ctm -import com.ibm.icu.impl.locale.Extension import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel diff --git a/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt b/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt index 3cbdebc..2dbb0ca 100644 --- a/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt +++ b/src/main/kotlin/littlechasiu/ctm/TrackWatcher.kt @@ -24,7 +24,6 @@ import kotlin.time.Duration.Companion.seconds class TrackWatcher() { var enable: Boolean = true var watchInterval: Duration = 0.5.seconds - var enableNavigationTracks: Boolean = true private var stopping: Boolean = false private var thread: Thread? = null diff --git a/src/main/kotlin/littlechasiu/ctm/model/Network.kt b/src/main/kotlin/littlechasiu/ctm/model/Network.kt index 678e1ec..59d4973 100644 --- a/src/main/kotlin/littlechasiu/ctm/model/Network.kt +++ b/src/main/kotlin/littlechasiu/ctm/model/Network.kt @@ -1,11 +1,9 @@ package littlechasiu.ctm.model -import com.simibubi.create.content.trains.entity.Navigation import com.simibubi.create.content.trains.signal.SignalBlock.SignalType import com.simibubi.create.content.trains.signal.SignalBlockEntity.SignalState import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.encoding.Decoder From 3bd744769536440d5fb6ef37fa8487445d57c87a Mon Sep 17 00:00:00 2001 From: ikawe Date: Fri, 3 May 2024 12:06:03 +0200 Subject: [PATCH 20/25] Clicking on a train in trainlist now opens the train info window. Trainlist button only update when the train moved 10 blocks (was unclickable at 20 updates/sec) --- .../ctm/static/assets/js/create-track-map.js | 7 +++- .../ctm/static/assets/js/ctm.control.list.js | 5 ++- .../ctm/static/assets/js/ctm.train-manager.js | 36 +++++++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 060030b..88ede9a 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -180,7 +180,9 @@ function startMapUpdates() { dmgr.onTrainStatus(({ trains }) => { //lmgr.clearTrains() lmgr.clearTrainPaths() + tmgr.update(trains) + let whitelist = [] trains.forEach((train) => { let leadCar = null @@ -332,7 +334,7 @@ function getTrainInfoHTML(train){ let openTrainInfos = {} function openTrainInfo(train){ - var win = L.control.window(map,{title:train.name,content:getTrainInfoHTML(train)}).show() + var win = L.control.window(map,{title:train.name,content:getTrainInfoHTML(train)}).showOn([100,100]) win._closeButton.addEventListener("click", function(event){ delete openTrainInfos[train.id] }) @@ -364,6 +366,9 @@ function ticksToMMSS(ticks) { if(ticks === "Unknown"){ return "Unknown" } + if(ticks < 0){ + ticks = 0 + } let seconds = Math.floor(ticks / 20) let minutes = Math.floor(seconds / 60); let remainingSeconds = seconds % 60; diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js index 05e7c57..8dd4616 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js @@ -78,6 +78,9 @@ L.Control.List = L.Control.extend({ el.addEventListener("click", (e) => { let [dimension, x, _, z] = e.target.dataset.coords.split(";") this.options.layerManager.switchToDimension(dimension) + if(this.options.itemClassName === "train" && !openTrainInfos[info.id]){ + openTrainInfo(info) + } this._map.panTo([parseFloat(z), parseFloat(x)]) }) @@ -91,7 +94,7 @@ L.Control.List = L.Control.extend({ let el = Array.from(this._list.children).filter((e) => e.dataset.id === id)[0] if (!!el) { - el.textContent = info.name + //el.textContent = info.name el.dataset.coords = this.options.coordsFunction(info).join(";") } }, diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.train-manager.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.train-manager.js index 13708b0..5dddd05 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.train-manager.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.train-manager.js @@ -1,30 +1,46 @@ class TrainManager { constructor(map, layerManager) { - this.trains = new Set() + this.trains = new Map() this.map = map this.control = L.control.trainList(layerManager).addTo(map) } update(trains) { - const thisTrains = new Set() + const thisTrains = new Map() + let changed = false trains.forEach((t) => { - thisTrains.add(t.id) + thisTrains.set(t.id, t) if (this.trains.has(t.id)) { - this.control.update(t.id, t) + if(distance(this.trains.get(t.id).cars[0].leading.location, t.cars[0].leading.location) > 10){ + this.trains.set(t.id, t) + this.control.update(t.id, t) + } } else { - this.trains.add(t.id) + this.trains.set(t.id, t) this.control.add(t.id, t) + changed = true } }) - this.trains.forEach((t) => { - if (!thisTrains.has(t)) { - this.trains.delete(t) - this.control.remove(t.id) + this.trains.forEach((train, id) => { + if (!thisTrains.has(id)) { + this.trains.delete(id) + this.control.remove(id) + changed = true } }) - this.control.reorder() + if(changed) { + this.control.reorder() + } } } + +function distance(vector1, vector2) { + const dx = vector1.x - vector2.x; + const dy = vector1.y - vector2.y; + const dz = vector1.z - vector2.z; + + return Math.sqrt(dx * dx + dy * dy + dz * dz); +} \ No newline at end of file From ee722435d6b9f33783043fcedca3c0cde3aa2f08 Mon Sep 17 00:00:00 2001 From: ikawe Date: Sun, 5 May 2024 12:48:03 +0200 Subject: [PATCH 21/25] Refactor and docs --- .../kotlin/littlechasiu/ctm/Extensions.kt | 106 ++++++++++++------ 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index ba09d74..66ad016 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -83,6 +83,27 @@ val Carriage.sendable }, ) +val ScheduleRuntime.sendable + get() = schedule?.let { + val field = ScheduleRuntime::class.java.getDeclaredField("ticksInTransit") + field.isAccessible = true + val currentTime = field.get(this) as Int + + + CreateSchedule( + cycling = it.cyclic, + instructions = getInstructions(this), + paused = paused, + currentEntry = currentEntry, + ticksInTransit = currentTime, + ) + } + +/** + * gets schedule instructions for a train + * @param scheduleRuntime ScheduleRuntime object associated with the Train + * @return ArrayList result + */ fun getInstructions(scheduleRuntime: ScheduleRuntime): ArrayList { val result: ArrayList = ArrayList() val instructions = scheduleRuntime.schedule.entries @@ -111,23 +132,32 @@ fun getInstructions(scheduleRuntime: ScheduleRuntime): ArrayList @@ -143,7 +173,16 @@ private fun getNextEdge(graph: TrackGraph, trackNode: TrackNode, trackEdge: Trac return result } -private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNode) : ArrayList { +/** + * Calculates path when the train keeps going without turning. + * This is not a pathfinder endNode must be reachable with no turns. + * Method stops after 50 itterations by itself + * @param startEdge TrackEdge start point + * @param endNode TrackEdge end point + * @param graph TrackGraph the train is currently on + * @return ArrayList result + */ +private fun pathFromTo(startEdge: TrackEdge, endNode: TrackNode, graph: TrackGraph) : ArrayList { //BEWARE! this method only goes straight this is not a pathfinder. It's used to get from last turn to the station val result = ArrayList() @@ -156,7 +195,7 @@ private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNo var j = 0 while (!reachedEnd) { direction = tEdge.getDirection(false) - tEdge = getNextEdge(graph, tEdge.node2, tEdge, direction) ?: return result + tEdge = getNextEdge(graph, tEdge, tEdge.node2, direction) ?: return result j++ if (tEdge.node1.netId == endNode.netId) { @@ -175,13 +214,23 @@ private fun pathFromTo(startEdge: TrackEdge, graph: TrackGraph, endNode: TrackNo return result } +/** + * Gets the TrackEdge a train station + * @param station GlobalStation train station + * @param graph TrackGraph the train station is on + * @return TrackEdge result + */ private fun getEdgeFromStation(station: GlobalStation, graph: TrackGraph) : TrackEdge { val firstNode = graph.locateNode(station.edgeLocation.first) val secondNode = graph.locateNode(station.edgeLocation.second) return graph.getConnection(Couple.create(firstNode, secondNode)) } - +/** + * Calculates the current path the train is taking + * @param navigation Navigation object of Train + * @return Path information object + */ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ val graph = navigation?.train?.graph val result : ArrayList = ArrayList() @@ -202,10 +251,10 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ result.add(firstEdge.sendable as Edge) result.add(lastEdge.sendable as Edge) if(currentPath.isNotEmpty()){ - result.addAll(pathFromTo(firstEdge, graph, currentPath[0].first)) - result.addAll(pathFromTo(graph.getConnection(currentPath[currentPath.size - 1]), graph, lastEdge.node1)) + result.addAll(pathFromTo(firstEdge, currentPath[0].first, graph)) + result.addAll(pathFromTo(graph.getConnection(currentPath[currentPath.size - 1]), lastEdge.node1, graph)) }else{ - result.addAll(pathFromTo(firstEdge, graph, lastEdge.node1)) + result.addAll(pathFromTo(firstEdge, lastEdge.node1, graph)) } currentPath.forEachIndexed{i, obj -> @@ -215,23 +264,8 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ result.add(edge) if(i < currentPath.size - 1){ - result.addAll(pathFromTo(trackEdge, graph, currentPath[i + 1].first)) + result.addAll(pathFromTo(trackEdge, currentPath[i + 1].first, graph)) } } return Path(result,navigation.distanceStartedAt,navigation.distanceToDestination) } - -val Train.sendable: CreateTrain - get() { - return CreateTrain( - id = id, - name = name.string, - owner = null, - cars = carriages.map { it.sendable }.toList(), - speed = speed, - backwards = speed < 0, - stopped = speed == 0.0, - schedule = runtime.sendable, - currentPath = getCurrentTrainPath(navigation), - ) - } From 98db912359dbc43aba52cb00b908259af01a7ca6 Mon Sep 17 00:00:00 2001 From: ikawe Date: Mon, 20 May 2024 20:28:01 +0200 Subject: [PATCH 22/25] added lazy fix to clicking a train when it has no leading data for the cars (just ignore the problem) Added more data to train info card --- .../ctm/static/assets/js/create-track-map.js | 43 ++++++++++++------- .../ctm/static/assets/js/ctm.control.list.js | 8 +++- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 88ede9a..80acd4b 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -292,27 +292,40 @@ function startMapUpdates() { } function getTrainInfoHTML(train){ - let schedule = "
" + let htmlData = "
" + htmlData += "Speed: " + Math.floor(train.speed * 100) + "%
" + if(train.stopped){ + htmlData += "Status: Stopped
" + }else{ + htmlData += "Status: Moving
" + } + if(train.schedule){ + htmlData += "Mode: Schedule" + }else{ + htmlData += "Mode: Manual" + return htmlData + } + htmlData += "
" if(train.schedule) { let currentInstruction = train.schedule.currentEntry let instructions = train.schedule.instructions - schedule += "On schedule
" + htmlData += "On schedule
" if(instructions[currentInstruction].instructionType === "Destination") { - schedule += "Next destination: " + instructions[currentInstruction].stationName + "" + htmlData += "Next destination: " + instructions[currentInstruction].stationName + "" }else{ - schedule += "Next destination: Unknown" + htmlData += "Next destination: Unknown" } - schedule += "
" + htmlData += "
" - schedule += "Time until arrival: " + ticksToMMSS(calculateRemainingTicks(train, train.schedule.currentEntry)) + "
" + htmlData += "Time until arrival: " + ticksToMMSS(calculateRemainingTicks(train, train.schedule.currentEntry)) + "
" if(train.currentPath.tripDistance === 0){ - schedule += "Distance: Arrived" + htmlData += "Distance: Arrived" }else { - schedule += "Distance: " + Math.floor(train.currentPath.distanceToDrive) + "/" + Math.floor(train.currentPath.tripDistance) + " blocks" + htmlData += "Distance: " + Math.floor(train.currentPath.distanceToDrive) + "/" + Math.floor(train.currentPath.tripDistance) + " blocks" } - schedule += "
" + htmlData += "
" train.schedule.instructions.forEach((instruction, i) => { if (instruction.instructionType === "Destination") { @@ -320,16 +333,16 @@ function getTrainInfoHTML(train){ if (i === train.schedule.currentEntry) { className += " marked" } - schedule += "
" - schedule += "" + instruction.stationName + "" - schedule += "" + ticksToMMSS(calculateRemainingTicks(train, i)) - schedule += "
"; + htmlData += "
" + htmlData += "" + instruction.stationName + "" + htmlData += "" + ticksToMMSS(calculateRemainingTicks(train, i)) + htmlData += "
"; } }) } - schedule += "
" - return schedule + htmlData += "" + return htmlData } let openTrainInfos = {} diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js index 8dd4616..d43bf09 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/ctm.control.list.js @@ -149,8 +149,12 @@ L.control.trainList = (layerManager) => itemClassName: "train", tooltip: "Trains", coordsFunction: (t) => { - const c = t.cars[0].leading - return [c.dimension, c.location.x, c.location.y, c.location.z] + if(t.cars[0].leading) { + const c = t.cars[0].leading + return [c.dimension, c.location.x, c.location.y, c.location.z] + }else{ + return ["minecraft:overworld",0,0,0] + } }, layerManager, }) From 92265666afbd2b8ab321cf78dafa4d32f7805edc Mon Sep 17 00:00:00 2001 From: DerPole Date: Sun, 16 Jun 2024 13:25:49 +0200 Subject: [PATCH 23/25] Fixed faulty path when scheduled train drives backwards Fixed speed being negative (on UI) when driving backwards Changed displayed speed unit to Blocks/s --- .../kotlin/littlechasiu/ctm/Extensions.kt | 23 +++++++++++-------- .../ctm/static/assets/js/create-track-map.js | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 66ad016..c31dcf5 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -176,21 +176,20 @@ private fun getNextEdge(graph: TrackGraph, trackEdge: TrackEdge, trackNode: Trac /** * Calculates path when the train keeps going without turning. * This is not a pathfinder endNode must be reachable with no turns. - * Method stops after 50 itterations by itself + * Method stops after 100 iterations by itself * @param startEdge TrackEdge start point * @param endNode TrackEdge end point * @param graph TrackGraph the train is currently on * @return ArrayList result */ -private fun pathFromTo(startEdge: TrackEdge, endNode: TrackNode, graph: TrackGraph) : ArrayList { - //BEWARE! this method only goes straight this is not a pathfinder. It's used to get from last turn to the station +private fun straightLinePathToEndNode(startEdge: TrackEdge, endNode: TrackNode, graph: TrackGraph) : ArrayList { val result = ArrayList() var tEdge: TrackEdge = startEdge var direction: Vec3 var reachedEnd = false - val MAX_PREDICTIONS = 50 + val MAX_PREDICTIONS = 100 var j = 0 while (!reachedEnd) { @@ -242,7 +241,13 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ @Suppress("UNCHECKED_CAST") val currentPath = field.get(navigation) as List> - val firstEdge: TrackEdge = graph.getConnection(navigation.train.endpointEdges.first) + val firstNode = navigation.train.endpointEdges.first.first + val secondNode = navigation.train.endpointEdges.first.second + val firstEdge: TrackEdge = if(navigation.train.speed > 0) + graph.getConnection(Couple.create(firstNode, secondNode)) + else + graph.getConnection(Couple.create(secondNode, firstNode)) // get the "inverted" edge when driving backwards + val lastEdge: TrackEdge = getEdgeFromStation(navigation.destination, graph) if(firstEdge == lastEdge){ return Path(result,0.0,0.0) @@ -251,10 +256,10 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ result.add(firstEdge.sendable as Edge) result.add(lastEdge.sendable as Edge) if(currentPath.isNotEmpty()){ - result.addAll(pathFromTo(firstEdge, currentPath[0].first, graph)) - result.addAll(pathFromTo(graph.getConnection(currentPath[currentPath.size - 1]), lastEdge.node1, graph)) + result.addAll(straightLinePathToEndNode(firstEdge, currentPath[0].first, graph)) + result.addAll(straightLinePathToEndNode(graph.getConnection(currentPath[currentPath.size - 1]), lastEdge.node1, graph)) }else{ - result.addAll(pathFromTo(firstEdge, lastEdge.node1, graph)) + result.addAll(straightLinePathToEndNode(firstEdge, lastEdge.node1, graph)) } currentPath.forEachIndexed{i, obj -> @@ -264,7 +269,7 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ result.add(edge) if(i < currentPath.size - 1){ - result.addAll(pathFromTo(trackEdge, currentPath[i + 1].first, graph)) + result.addAll(straightLinePathToEndNode(trackEdge, currentPath[i + 1].first, graph)) } } return Path(result,navigation.distanceStartedAt,navigation.distanceToDestination) diff --git a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js index 80acd4b..d9c81be 100644 --- a/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js +++ b/src/main/resources/assets/littlechasiu/ctm/static/assets/js/create-track-map.js @@ -293,7 +293,7 @@ function startMapUpdates() { function getTrainInfoHTML(train){ let htmlData = "
" - htmlData += "Speed: " + Math.floor(train.speed * 100) + "%
" + htmlData += "Speed: " + Math.abs(train.speed * 20).toFixed(1) + " Blocks/s
" if(train.stopped){ htmlData += "Status: Stopped
" }else{ From 3d2b4627f4d4ebe65137044ef29dd631ff673c29 Mon Sep 17 00:00:00 2001 From: DerPole Date: Mon, 17 Jun 2024 12:05:24 +0200 Subject: [PATCH 24/25] Fixed path displaying wrong when backwards driving train stopped (at signal) on its way to a station --- src/main/kotlin/littlechasiu/ctm/Extensions.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index c31dcf5..644c0b2 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -243,10 +243,10 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ val firstNode = navigation.train.endpointEdges.first.first val secondNode = navigation.train.endpointEdges.first.second - val firstEdge: TrackEdge = if(navigation.train.speed > 0) - graph.getConnection(Couple.create(firstNode, secondNode)) - else + val firstEdge: TrackEdge = if(navigation.train.currentlyBackwards) graph.getConnection(Couple.create(secondNode, firstNode)) // get the "inverted" edge when driving backwards + else + graph.getConnection(Couple.create(firstNode, secondNode)) val lastEdge: TrackEdge = getEdgeFromStation(navigation.destination, graph) if(firstEdge == lastEdge){ From 1b51c958781d62a58221a09132df7db9f427729a Mon Sep 17 00:00:00 2001 From: DerPole Date: Mon, 17 Jun 2024 13:35:28 +0200 Subject: [PATCH 25/25] Fixed path retrieval using wrong end of the train as a start point when driving backwards --- src/main/kotlin/littlechasiu/ctm/Extensions.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/littlechasiu/ctm/Extensions.kt b/src/main/kotlin/littlechasiu/ctm/Extensions.kt index 644c0b2..56ce537 100644 --- a/src/main/kotlin/littlechasiu/ctm/Extensions.kt +++ b/src/main/kotlin/littlechasiu/ctm/Extensions.kt @@ -241,8 +241,9 @@ private fun getCurrentTrainPath(navigation: Navigation?) : Path{ @Suppress("UNCHECKED_CAST") val currentPath = field.get(navigation) as List> - val firstNode = navigation.train.endpointEdges.first.first - val secondNode = navigation.train.endpointEdges.first.second + val endPointEdge = if(navigation.train.currentlyBackwards) navigation.train.endpointEdges.second else navigation.train.endpointEdges.first + val firstNode = endPointEdge.first + val secondNode = endPointEdge.second val firstEdge: TrackEdge = if(navigation.train.currentlyBackwards) graph.getConnection(Couple.create(secondNode, firstNode)) // get the "inverted" edge when driving backwards else