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() ?: return null
val visibleEdges: List<GPSEdge> = edges.filterVisible(start, end)
val paths: List<List<Position>> = findPaths(visibleEdges) ?: return null

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