Skip to content

Elements

Values are set by us; we decide what to display and when. Often it's easier to instead describe how something is calculated and let the visual keep it up to date, which is what elements are for.

A PlayerSpecificElement generates what a given player is displayed:

kotlin
// An element that returns the player's display name
val element = PlayerSpecificElement<Component> { player -> player.displayName }

Displaying Elements

Every visual has a Dynamic variant which generates its values from elements. These take the server that they belong to, since that's what the elements are generated against:

kotlin
val bossbar = DynamicVirtualBossbar(server)
bossbar.setTitle { player -> player.displayName }
bossbar.setProgress(UniversalElement { server -> (server.tickCount % 100) / 100.0F })

Each tick, every element is generated and displayed. Since only values that change are sent, an element that generates the same thing twice doesn't send anything.

The type of element decides what it generates; a UniversalElement generates the base value, and any other element generates per-player overrides. Every value has a slot for both, so we can generate a shared value for most players while generating something different for specific players:

kotlin
sidebar.setRows(UniversalElement { server -> sharedRows(server) })
sidebar.setRows { player -> rowsFor(player) }

Any value that doesn't have an element can still be set directly.

NOTE

Overrides that were generated by an element are removed when a player stops observing the visual, since they'd be generated again anyway. Overrides that you set yourself are always kept.

Types Of Element

There are other type-specific elements: LevelSpecificElement, TeamSpecificElement, and UniversalElement, these all inherit from PlayerSpecificElement:

  • LevelSpecificElement: this is an element that displays based on the world. This may be useful if you want to display information about the current world a player is in, this can then be cached on a level basis.
  • TeamSpecificElement: this is an element that displays based on a player team. Similarly to the level element, this may be useful if you want to display information about a player's team, which can then be cached on a team basis.
  • UniversalElement: this is an element that displays the same universally. This may be useful for elements that will always be displayed the same to all players.

UniversalElements can also be constant, if there is no need to constantly update its content:

kotlin
val element: UniversalElement<Component> = UniversalElement.constant(Component.literal("This is constant!"))

There's also AnimatedElement, which cycles through elements over a given duration:

kotlin
val builder = AnimatedElement.Builder<Component>()
builder.add(Component.literal("Tick..."), 1.Seconds)
builder.add(Component.literal("Tock..."), 1.Seconds)

bossbar.setTitle(builder.build())

Caching And Merging

An element is generated for every player being displayed the visual every tick, so if your generator is expensive, it may be worth considering caching your element:

kotlin
val element = PlayerSpecificElement<Component> { player ->
    /* Expensive function */
    Component.literal("Foo Bar")
}.cached()

A cached element is only generated once per player per tick, and a cached UniversalElement only once per tick.

Elements can also be merged:

kotlin
val teamElement: PlayerSpecificElement<PlayerTeam> = // ...
val messageElement: PlayerSpecificElement<String> = // ...

val componentElement: PlayerSpecificElement<Component> = teamElement.merge(messageElement) { team, message ->
    team.formattedDisplayName.append(" ").append(message)
}

Tickables

Elements should only read state, they shouldn't modify it. If we've got state that changes over time, and multiple elements which display it, we can implement a TickableElement and register it with the visual:

kotlin
val timer = TimerElement(10.Minutes)

val bossbar = DynamicVirtualBossbar(server)
bossbar.addTickable(timer)
bossbar.setProgress(timer.progress())
bossbar.setTitle(timer.remaining { duration -> Component.literal(duration.formatHHMMSS()) })

Tickables are ticked before any elements are generated, so our elements always read state that's up to date. Registering the same tickable more than once won't tick it more than once, and we can stop ticking it with removeTickable.

TimerElement is the built-in example of this, see the Bossbars Section for more information on countdowns.