Venusanticheat docs

Worked examples

Four complete patterns: safe startup, a /venus subcommand, a violation feed, and a session report.

Every example assumes compileOnly on the API artifact from the overview, and depend: [VenusLoader] in plugin.yml.

1. Safe startup

Venus loads asynchronously relative to your plugin. The async provider form cannot throw and cannot race — use it as the only entry point you call from onEnable.

VenusBridge.java — kept out of the main class
public final class VenusBridge {

    private static VenusApi api;

    public static void init(JavaPlugin plugin) {
        VenusAPIProvider.getAsync().thenAccept(loaded -> {
            api = loaded;
            plugin.getLogger().info("Venus " + api.version() + " hooked");

            loaded.events().repository().register(UserJoinEvent.class, event ->
                    plugin.getLogger().info(event.user().username() + " joined"));
        });
    }

    public static Optional<VenusApi> get() {
        return Optional.ofNullable(api);
    }
}

2. A subcommand under /venus

Register a label, implement execute and tab completion. The dispatcher handles fallback permissions and errors; arity is your job.

registering /venus watch
VenusBridge.init(this);

VenusAPIProvider.getAsync().thenAccept(api ->
        api.subcommands().register(new Subcommand("watch", false, "venus.alerts") {

            @Override
            public void execute(Player player, String label, String[] args) {
                if (args.length != 1) {
                    player.sendMessage("Usage: /venus watch <player>");
                    return;
                }
                api.users().queryUserByName(args[0]).ifPresentOrElse(target -> {
                    api.alerts().setAlertsEnabled(target, true, false);
                    player.sendMessage("Now watching " + target.username());
                }, () -> player.sendMessage("Venus is not tracking " + args[0]));
            }

            @Override
            public List<String> onTabComplete(Player player, String[] args) {
                if (args.length != 1) return List.of();
                String prefix = args[0].toLowerCase(Locale.ROOT);
                return Bukkit.getOnlinePlayers().stream()
                        .map(Player::getName)
                        .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix))
                        .toList();
            }
        }));

3. A violation feed with filtering

Listeners run on the thread that observed the user. Keep them short; cancel events you do not want counted.

logging detections, suppressing one check
api.events().repository().register(ViolationEvent.class, event -> {
    User user = event.user();

    // Suppress an experimental check entirely for review players:
    if (reviewRoster.contains(user.uuid()) && event.check().stableKey().startsWith("experimental.")) {
        event.setCancelled(true);
        return;
    }

    feed.submit(user.username(), event.check().name(), event.vl());
}, EventPriority.NORMAL, false);

For alert text instead of raw flags, listen to DetectionAlertEvent; for the punishment branch, PunishmentEvent. The full hierarchy is on event classes.

4. Session report

History is asynchronous — chain the futures rather than blocking. This produces the same numbers a staff history view would show.

reporting a player’s last session
public void report(VenusApi api, User user, Consumer<String> out) {
    api.history().sessions(user, 1).thenAccept(mostRecent -> {
        if (mostRecent.isEmpty()) {
            out.accept("No recorded sessions.");
            return;
        }
        HistoryService.Session session = mostRecent.get(0);
        out.accept("Session #" + session.ordinal()
                + " · " + session.durationMs() / 1000 + "s"
                + " · " + session.violationCount() + " flags across "
                + session.uniqueCheckCount() + " checks");

        api.history().session(user, session.sessionId()).thenAccept(detail ->
                detail.ifPresent(d -> d.violations().forEach(v ->
                        out.accept("  +" + v.offsetMs() + "ms " + v.stableKey()
                                + " vl=" + String.format("%.1f", v.vl()))))));
    });
}

Threading rules, in one box

  • Register listeners and read services from any thread.
  • User events arrive on that user's Netty thread; cloud events on the cloud thread.
  • Hop to the main thread yourself before touching Bukkit objects.
  • Never block a Netty thread waiting on a history() future — chain it or thenAcceptAsync(..., Bukkit.getScheduler()::runTask).