Match scoring
The me.zlex.conduit.match package gives a minigame everything it needs to score across rounds and persist the outcome:
Match, a pure-data best-of-N holder (round number + per-player point tally). No Minecraft references, so it’s trivially testable.MatchResult, an immutable, persistable snapshot of a finishedMatch, with acommitTo(server)bridge into the coreProfileStore.MatchScoreboard, drives an fxScoreBoardBindingsidebar straight from aMatch.MatchHistorySavedData, appends results to a queryable recent-matches feed (capMAX = 50).Podium, builds a 1st/2nd/3rd podium, fires celebration fireworks, and shows a winner card.MatchCompletedEvent, fired on the coreConduitEventsbus when a match finishes.
When to use it
Section titled “When to use it”- Your game runs multiple rounds and you want per-player scoring with a stable leaderboard.
- You want wins / games-played / points folded into durable player profiles (
ProfileStore). - You want a ready-made standings sidebar and end-of-match podium.
- You’re writing a tournament runner that advances games when each
Matchfinishes.
package me.zlex.conduit.match;
public final class Match { public Match(int totalRounds); // totalRounds must be >= 1
public int totalRounds(); public int currentRound(); // 1-based public boolean advanceRound(); // false once no rounds remain public boolean isOver(); // currentRound >= totalRounds
public void award(UUID player, int delta); public void register(UUID player); public int pointsOf(UUID player); public Map<UUID, Integer> points(); // unmodifiable view
public List<UUID> standings(); // best first; ties keep insertion order public Optional<UUID> leader(); public Optional<UUID> winner(); // == leader(), named for end-of-match
public MatchResult finish(String gameId); // snapshots + fires MatchCompletedEvent}
public record MatchResult(String gameId, Optional<UUID> winner, List<UUID> standings, Map<UUID, Integer> points, int rounds, long endedAt) { public static final Codec<MatchResult> CODEC; public static MatchResult of(String gameId, Match m); public void commitTo(MinecraftServer server); // wins / games_played / points_total}
public final class MatchScoreboard { public static ScoreBoardBinding attach(String title, Match match, Function<UUID, String> nameOf, Collection<ServerPlayer> audience); public static List<String> rows(Match match, Function<UUID, String> nameOf);}
public final class MatchHistorySavedData extends GameStateSavedData<MatchHistorySavedData> { public static MatchHistorySavedData get(ServerLevel level); public void record(MatchResult result); public List<MatchResult> recent(); public List<MatchResult> recentFor(String gameId);}
public final class Podium { public static void celebrate(MinecraftServer server, ServerLevel level, BlockPos at, List<UUID> top3, Function<UUID, String> nameOf); public static void celebrate(MinecraftServer server, ServerLevel level, BlockPos at, List<UUID> top3, Function<UUID, String> nameOf, Collection<ServerPlayer> audience);}
public record MatchCompletedEvent(String gameId, Optional<UUID> winner, List<UUID> standings, MatchResult result) implements ConduitEvent {}Example
Section titled “Example”import me.zlex.conduit.match.Match;import me.zlex.conduit.match.MatchResult;import me.zlex.conduit.match.MatchScoreboard;import me.zlex.conduit.match.Podium;import net.minecraft.core.BlockPos;import net.minecraft.server.MinecraftServer;import net.minecraft.server.level.ServerLevel;import net.minecraft.server.level.ServerPlayer;
import java.util.Collection;import java.util.UUID;import java.util.function.Function;
public final class MyMatchFlow {
private final Match match = new Match(5); // best-of-5
void startMatch(Collection<ServerPlayer> players) { for (ServerPlayer p : players) match.register(p.getUUID()); MatchScoreboard.attach("§6Standings", match, this::nameOf, players); }
void onRoundWin(UUID winner) { match.award(winner, 1); if (!match.advanceRound()) endMatch(); // false → no rounds remain }
void endMatch() { MinecraftServer server = inst.level().getServer(); MatchResult result = match.finish("hot-potato"); // also fires MatchCompletedEvent result.commitTo(server); // fold into player profiles
Podium.celebrate(server, inst.level(), new BlockPos(0, 65, 0), match.standings(), this::nameOf, lobbyMembers); }
private String nameOf(UUID id) { ServerPlayer p = server.getPlayerList().getPlayer(id); return p != null ? p.getScoreboardName() : "?"; }}Matchholds no Minecraft references: it keys players byUUID. The lobby maps those back to its ownServerPlayers (via yournameOf).isOver()is true while the final round is still being played: it meanscurrentRound >= totalRounds, not “the match is finished”. The canonical end-of-match signal isadvanceRound()returningfalse.match.finish(gameId)both snapshots aMatchResultand fires aMatchCompletedEventon the bus. Listen for that event to drive a tournament / season runner.MatchResult.commitTo(server)incrementsgames_playedfor every participant, adds each score topoints_total, and bumps the winner’swins: call it once at end of match, on the server thread.Podium.celebratehas a 6-arg overload taking anaudiencecollection: the winner title card + fanfare are shown only to those players (the podium structure + fireworks stay visible to anyone nearby). Use it on multi-lobby servers so one lobby’s celebration doesn’t interrupt the rest.