Pvty Games Extensible API

Welcome! This guide shows how to integrate an external mini-game with Pvty Games: register your game, respond to lifecycle callbacks, and surface optional mods the lobby can enable prior to a match.

Quick Start

  1. Add the pvtyGamesExtensibleAPI jar to your plugin's dependencies (compileOnly is sufficient).
  2. Implement ExternalGame, returning descriptive GameMetadata and creating ExternalGameSession instances.
  3. During your plugin's enable phase, acquire PvtyGamesAPI and call registerGame with a GameRegistration.
  4. Inside your session implementation respond to lifecycle events (onInitialize, onStart, onPlayerEliminated, etc.).
public final class SkyLootGame implements ExternalGame {
    private final GameMetadata metadata = GameMetadata.builder("skyloot", "Sky Loot Rush")
            .description("Collect loot on floating islands before the timer expires.")
            .minPlayers(4)
            .maxPlayers(16)
            .supportsSpectators(true)
            .build();

    @Override
    public @NotNull GameMetadata metadata() {
        return metadata;
    }

    @Override
    public @NotNull ExternalGameSession createSession(@NotNull GameSessionCreationContext ctx) {
        return new SkyLootSession();
    }
}

Registration Lifecycle

Games are registered via PvtyGamesAPI. The API is typically obtained from the Pvty Games plugin:

public void onEnable() {
    PvtyGamesAPI api = getServer()
            .getServicesManager()
            .load(PvtyGamesAPI.class);
    if (api == null) {
        getLogger().warning("Pvty Games API not available.");
        return;
    }

    GameRegistration registration = GameRegistration.builder("skyloot", SkyLootGame::new)
            .metadata(GameMetadata.builder("skyloot", "Sky Loot Rush")
                    .description("Collect loot on floating islands before the timer expires.")
                    .build())
            .addTag("pve")
            .addTag("parkour")
            .build();

    api.registerGame(registration);
}

Required:

  • metadata() — must describe the game with unique id.
  • createSession() — must return a new ExternalGameSession each time.
  • GameRegistration.metadata(...) — must be populated when you call registerGame.

Optional, but recommended:

  • ExternalGame.availableMods() to expose toggles and numeric modifiers.
  • ExternalGame.onRegistered() for setup after the platform loads.
  • GameRegistration.addTag() to help the lobby categorise the game.

Session Lifecycle Callbacks

The host drives each session through a predictable set of callbacks on ExternalGameSession:

  • onInitialize: Allocate resources, load worlds, place players in waiting lobbies.
  • onCountdownTick: Update countdown scoreboards or play sounds.
  • onStart: Begin the actual gameplay (start timers, spawn mobs, enable damage).
  • onPlayerJoin / onPlayerLeave: Handle reconnects or voluntary departures.
  • onPlayerEliminated: Update internal state when Pvty Games removes a player.
  • onPlayerCompleted: Mark success for time-trial scenarios.
  • onTick: Perform lightweight per-tick logic (keep work small to avoid lag).
  • onGameStopping & onGameStopped: Clean up resources and reset worlds.

You can interact with players through GameSessionContext, which exposes helper methods to broadcast messages, play sounds, award points, and update game modes. Use the session scheduler() to schedule repeated tasks that automatically cancel when the game ends.

Mods & Rule Variations

Mods are defined via GameModDescriptor. They enable lobby hosts to customise your game before it runs. At runtime, the chosen values are available through GameSessionContext.selectedMods().

GameModDescriptor hardcore = GameModDescriptor.builder("hardcore", "Hardcore Mode")
        .description("Players have one life and cannot regenerate health.")
        .kind(GameModKind.TOGGLE)
        .defaultValue("false")
        .build();

collection.add(hardcore);

Parity helpers such as GameModSelection.isEnabled(), intValue(), and doubleValue() simplify reading selections.

Best Practices

  • Use GameSessionContext.scheduler() instead of Bukkit's global scheduler to ensure tasks end with the session.
  • Keep expensive work off the main thread to avoid TPS drops.
  • Always handle onGameStopping to roll back world changes or cancel outstanding tasks.
  • Call GameSessionContext.endGame() when your game reaches a natural conclusion.