Skip to main content
 Warning: Beta Version
Version: Beta ⚠️

PathStreamDisplay

PathStreamDisplayEntry enables the rendering of custom path streams to players. A new display is generated for each viewer whenever a stream is recalculated, which occurs at the interval defined by refreshDuration.

When to Use It​

Create a PathStreamDisplayEntry when you want to customize the path stream visualization.

Basic Structure​

A PathStreamDisplayEntry must expose these two functions:

  • createProducer() → PathStreamProducer
  • createDisplay() → PathStreamDisplay

A PathStreamProducer is responsible for calculating the path to be displayed and tracking which paths are being displayed. Customize this if you want to modify the path stream's positioning.

A PathStreamDisplay is responsible for rendering the path to the player, receiving a position every tick and displaying it appropriately.

The entry constructs a PathStreamProducer that computes the route and returns a PathStream. Each PathStreamDisplay created for a viewer consumes that stream and renders its positions. In practice, you primarily override the methods that create the producer and display.

PathStreamDisplayEntry
├─ createDisplay() ──> PathStreamDisplay
└─ createProducer() ─> PathStreamProducer
└─manages─multiple─> PathStream
└─forwards─the─position─> PathStreamDisplay
PathStreamDisplayExample.kt
class ExamplePathStreamDisplayEntry(
override val id: String = "",
override val name: String = "",
override val children: List<Ref<out AudienceEntry>> = emptyList(),
override val refreshDuration: Duration = Duration.ofMillis(1700),
val travelSpeed: Double = 20.0,
) : PathStreamDisplayEntry {
override fun createDisplay(player: Player): PathStreamDisplay = ExampleParticleDisplay(player)

override fun createProducer(
player: Player,
roadNetwork: Ref<RoadNetworkEntry>,
startPosition: (Player) -> Position,
endPosition: (Player) -> Position,
): PathStreamProducer =
LinePathStreamProducer(
player = player,
ref = ref(),
roadNetwork = roadNetwork,
startPosition = startPosition,
endPosition = endPosition,
refreshDuration = refreshDuration,
speed = travelSpeed,
displayEntries = displays(), // A helper function that retrieves the correct display entries
)
}
PathStreamDisplayExample.kt
class ExampleParticleDisplay(private val player: Player) : PathStreamDisplay {
override fun display(position: Position) {
player.spawnParticle(Particle.FLAME, position.x, position.y, position.z, 1)
}

override fun dispose() {
// As path stream displays are stateful, you can clean up resources here if needed.
}
}

The example above uses LinePathStreamProducer, which moves from block to block towards the end position, displaying each step once. It's the simplest producer to begin with.

Custom Producers​

Implement your own PathStreamProducer when you require greater control over the path stream's positioning.

PathStreamDisplayExample.kt
class ExampleProducer(
player: Player,
id: String,
roadNetwork: Ref<RoadNetworkEntry>,
startPosition: (Player) -> Position,
endPosition: (Player) -> Position,
refreshDuration: Duration,
displayEntries: List<Ref<PathStreamDisplayEntry>>,
) : PathStreamProducer(
player,
id,
roadNetwork,
startPosition,
endPosition,
refreshDuration,
{ displayEntries.createDisplays(it) },
) {
override suspend fun refreshPath(): PathStream? {
// This is a massive helper function. There are a few more helper functions that allow you to
// separate our the different parts of the path finding.
val (_edges, _paths) = calculatePathing() ?: return null

// Here is the logic for the calculatePathing, you can use any of the helper functions
// as you like.
val (start: Position, end: Position) = points() ?: return null
val edges: List<GPSEdge> = findEdges()
val visibleEdges: List<GPSEdge> = edges.filterVisible(start, end)
val paths: List<List<Position>> = findPaths(visibleEdges)

val line = paths.flatten()
if (line.isEmpty()) return null

// Create a path stream which is used to keep track of the current position and forwards for displaying.
// Typewriter will automatically trigger the sub functions for you.
return ExamplePathStream(displaySupplier(player), line)
}
}
PathStreamDisplayExample.kt
class ExamplePathStream(
displays: List<PathStreamDisplay>,
private val points: List<Position>,
) : PathStream(displays) {
private var index = 0

// This is allowed to have state and complex logic to find the next position.
override fun forwardPath(): Position {
return points[(index++).coerceAtMost(points.size - 1)]
}

// This is to indicate whether the path stream should continue or not.
// When this returns false, the path stream will stop and no longer be displayed.
override fun shouldContinue(): Boolean {
return index < points.size
}

// Can be used to clean up resources or state when the path stream is no longer needed.
override fun dispose() {
// Make sure to keep the super.dispose() call to clean up the displays.
super.dispose()
}
}