Scoreboards
Track scores and show per-player sidebars, name tags, and list panels.
A scoreboard holds named objectives, and each objective holds a score per entry - a player, an entity, or any plain string you choose. Display an objective in a slot and the client draws it: a sidebar on the right, a number under every player's name tag, or a column in the pause-screen player list.
Display an objective
The server keeps one primary scoreboard at self.server.scoreboard. Add an objective to it, drop the objective into a slot, and set scores - here a "blocks mined" tally in the sidebar that climbs as players dig:
from endstone.event import event_handler, BlockBreakEvent
from endstone.plugin import Plugin
from endstone.scoreboard import Criteria, DisplaySlot, ObjectiveSortOrder
class MyPlugin(Plugin):
api_version = "0.11"
def on_enable(self) -> None:
self.objective = self.server.scoreboard.add_objective(
"mined", Criteria.DUMMY, "Blocks Mined"
)
self.objective.set_display(DisplaySlot.SIDE_BAR, ObjectiveSortOrder.DESCENDING)
self.register_events(self)
@event_handler
def on_block_break(self, event: BlockBreakEvent) -> None:
score = self.objective.get_score(event.player)
score.value += 1
def on_disable(self) -> None:
self.objective.unregister()The pieces:
Criteria.DUMMYmeans the server never touches the score - your plugin owns it. It's the criteria you want for anything you drive yourself.get_score(entry)returns aScorewhosevalueyou read and write. The entry can be aPlayer, anActor, or astr, soscore.value += 1reads the current number and stores the new one.set_displayputs the objective in a slot -SIDE_BAR,BELOW_NAME, orPLAYER_LIST- with an optional sort order.unregister()removes the objective entirely; do it inon_disableso a reload starts clean.
If you've used the in-game /scoreboard command, the API maps onto it one-to-one - each call above is the programmatic form of a command you could type:
| Python API | /scoreboard command |
|---|---|
add_objective("mined", Criteria.DUMMY, "Blocks Mined") | /scoreboard objectives add mined dummy "Blocks Mined" |
set_display(DisplaySlot.SIDE_BAR, ObjectiveSortOrder.DESCENDING) | /scoreboard objectives setdisplay sidebar mined descending |
get_score(player).value += 1 | /scoreboard players add @s mined 1 |
objective.unregister() | /scoreboard objectives remove mined |
Players, entities, and text rows
An entry is whatever a score hangs off. get_score accepts three kinds, and the type you pass decides what gets tracked:
objective.get_score(player).value = 10 # a real player, by identity
objective.get_score(zombie).value = 3 # any Actor (mob, entity)
objective.get_score("Server Total").value = 999 # plain text - a free-standing label row- A
Playeris tracked by the player's identity, so the score follows them across reconnects and renames, and shows under their name tag in theBELOW_NAMEslot. - An
Actorworks the same way for entities - useful for per-mob counters. - A
stris just text: a standalone named row tied to nobody. These are how you draw arbitrary text lines, like the"Coins"and"Level"rows in the next section.
Pass the Player object, not player.name. get_score(player) tracks the actual player; get_score(player.name) quietly creates a plain text row that merely shares the same characters - a separate entry that won't follow the real player, won't appear under their name tag, and drifts away from their real score the moment they rename. Only pass a string when you genuinely want a text row.
A different scoreboard for each player
This is where Endstone goes beyond what you can do from add-ons. The vanilla script API scoreboard and the /scoreboard command share one world scoreboard - every player necessarily sees the same objective and the same values. Endstone instead lets you build a scoreboard per player and assign it individually:
board = self.server.create_scoreboard() # a fresh, independent scoreboard
objective = board.add_objective("hud", Criteria.DUMMY, "Your Stats")
objective.set_display(DisplaySlot.SIDE_BAR)
objective.get_score("Coins").value = coins # plain strings make label rows
objective.get_score("Level").value = level
player.scoreboard = board # only this player sees itcreate_scoreboard() hands back a standalone scoreboard, and player.scoreboard = board swaps which one that single player renders - so two players can be looking at completely different sidebars at the same moment. Because score entries can be arbitrary strings, each row is really a line of text you control, and the number on the right is just another field to drive.
Per-player scoreboards are the backbone of advanced JSON UI work. The sidebar is one of the few server-driven surfaces a resource pack can restyle, so pairing per-player objectives (the live data) with custom JSON UI (the look) is how server-side plugins build rich, personalised HUDs - health bars, quest trackers, shop menus - without a client mod. Drive the values from Python; let the resource pack render them. See the Bedrock Wiki's Intro to JSON UI for how the resource-pack side works.
To send everyone back to the shared view, assign the primary scoreboard again:
player.scoreboard = self.server.scoreboard