# A full match HUD Source: https://tehoclient.dev/recipes/match-hud SDK: 1.0.0 By the end of this page your server runs a capture-the-point match that feels like it was built into the game. Players get a countdown title when it starts, a live scoreboard, a beam and a waypoint on the point they need to hold, a progress bar while they capture, a kill feed, and a victory screen. The winning moment is even saved as a clip for anyone with the replay buffer on. None of it uses armor stands, scoreboard tricks or a resource pack. It is about 120 lines of plugin code, and every piece degrades gracefully: players without Teho still get a working match, they just do not get the extras. **Before you start** You need TehoAPI installed and a plugin that depends on it. If you have not done that yet, the [Quickstart](https://tehoclient.dev/quickstart) takes five minutes. ## The shape of a match A match goes through four moments, and each one maps onto a handful of Teho calls: | Moment | What players see | Modules | |---|---|---| | Start | Countdown title, match timer, scoreboard, the point marked in the world | [HUD](https://tehoclient.dev/modules/hud), [Waypoints](https://tehoclient.dev/modules/waypoints), [World](https://tehoclient.dev/modules/world) | | Capture | A bar filling up while someone stands on the point | [HUD](https://tehoclient.dev/modules/hud) | | Kills | Kill feed entries, scoreboard updates | [HUD](https://tehoclient.dev/modules/hud) | | End | Victory title, the moment saved as a clip | [HUD](https://tehoclient.dev/modules/hud), [Clips](https://tehoclient.dev/modules/clips) | We will build them in that order. Every snippet lives in one class, `Match`, that your game logic calls into. ### Start: tell everyone the match is on When the match starts, send each Teho player the whole HUD at once. Timers tick on the client, so you send the countdown once and never touch it again. ```java public final class Match { private static final int POINT_X = 0, POINT_Y = 64, POINT_Z = 0; private final Map kills = new HashMap<>(); private int red, blue; public void start(Collection players) { for (Player player : players) { BukkitTeho.player(player).ifPresent(teho -> { teho.hud().title(Title.of("Capture the point") .subtitle("First team to 3 wins") .animation(Title.Animation.POP) .times(200, 2500, 400) .build()); teho.hud().timer(Timer.countdown("match", "Match ends", 10 * 60_000).color(0xFFC24D)); teho.hud().sidebar(scoreboard()); markThePoint(teho, player.getWorld()); }); } } } ``` Players who are not on Teho are simply skipped by `ifPresent`, so the rest of your match code does not need to care. ### Build a scoreboard worth looking at The Teho sidebar replaces the vanilla one, so you are not stuck with 15 grey lines. Rows have a label, a value, an optional item icon and an optional progress bar. ```java private Sidebar scoreboard() { return Sidebar.replace("Capture") .accent(0xF472B6) .row(Sidebar.Row.of("red", "Red").value(red + " / 3").color(0xFF5555)) .row(Sidebar.Row.of("blue", "Blue").value(blue + " / 3").color(0x4DA9FF)) .row(Sidebar.Row.of("point", "Point").value("Neutral").item("white_banner")) .footer("play.skyline.gg") .build(); } ``` Later, when only one row changes, send an update instead of the whole board. The client patches just that row, which is both cheaper and smoother. ```java private void updatePoint(TehoPlayer teho, String owner, int color) { teho.hud().sidebar(Sidebar.update() .row(Sidebar.Row.of("point", "Point").value(owner).color(color).item("white_banner")) .build()); } ``` ### Mark the point so nobody has to ask where it is A waypoint tells players how far away the point is from anywhere on the map, and a beam makes it visible over hills and through fog. Both are drawn by the client, so they cost your server nothing. ```java private void markThePoint(TehoPlayer teho, World world) { String dimension = BukkitTeho.dimensionOf(world); teho.waypoints().set(Waypoint.builder("point") .name("The point") .at(POINT_X, POINT_Y, POINT_Z) .dimension(dimension) .color(0xFFC24D) .focus(true) .build()); teho.world().beam(Beam.at("point", POINT_X + 0.5, POINT_Y, POINT_Z + 0.5) .color(0xFFC24D) .height(200) .pulse(true) .dimension(dimension) .build()); } ``` `focus(true)` makes it the waypoint the player's HUD readout tracks, so the distance is always on screen. ### Show the capture filling up While a team stands on the point, call this every second with the current progress. Sending the same bar id again updates it in place. ```java public void capturing(Player player, String team, double progress) { BukkitTeho.player(player).ifPresent(teho -> teho.hud().bar( Bar.builder("capture", team + " is capturing", progress) .color(team.equals("Red") ? 0xFF5555 : 0x4DA9FF) .style(Bar.Style.SEGMENTED) .icon("white_banner") .ttl(2000) .build())); } ``` The two-second `ttl` means that when capturing stops, the bar disappears by itself. You never have to remember to remove it. ### Feed the kills On every kill, add an entry to the kill feed for everyone and bump the killer's count. ```java public void kill(Player killer, Player victim, String weapon) { kills.merge(killer.getUniqueId(), 1, Integer::sum); for (Player player : Bukkit.getOnlinePlayers()) { BukkitTeho.player(player).ifPresent(teho -> teho.hud().kill( killer.getName(), killer.getUniqueId(), victim.getName(), victim.getUniqueId(), weapon, 0xFF5555)); } } ``` ### End: make the win feel like one When a team reaches three captures, everyone gets a big title, and the last few seconds are saved as a highlight for players who turned the replay buffer on. ```java public void end(String winner, int color) { for (Player player : Bukkit.getOnlinePlayers()) { BukkitTeho.player(player).ifPresent(teho -> { teho.hud().removeTimer("match"); teho.hud().title(Title.of(winner + " wins") .subtitle("GG, back to the lobby in 10 seconds") .animation(Title.Animation.POP) .color(color) .scale(1.6) .build()); teho.clips().mark("win-" + System.currentTimeMillis(), winner + " wins", 10_000, 3_000); }); } } ``` ## Clean up between matches Everything you sent is remembered per player and re-sent if their client reconnects, so clear it when the match is over. One call wipes everything your server applied: ```java BukkitTeho.player(player).ifPresent(TehoPlayer::reset); ``` If you would rather keep things like the tab list or keybinds across matches, remove just the match pieces instead: `removeTimer`, `clearSidebar`, `waypoints().remove("point")` and `world().removeBeams("point")`. ## Where to go from here - [Everything the HUD can do](https://tehoclient.dev/modules/hud) — Tints, kill feeds, TNT fuses and more title animations. - [Add a map fly-over](https://tehoclient.dev/modules/camera) — A keyframed cutscene before the countdown. - [Make it ranked](https://tehoclient.dev/recipes/ranked-queue) — Only admit verified clients and lock the mods.