Skip to content
Steank edited this page Oct 10, 2023 · 7 revisions

This page is meant to serve as a high-level description of the Scene API, which is used to create custom minigames and lobbies.

Scene API

All scenes, at minimum, implement the Scene interface. Scenes at their core are just containers of players that can be "shut down", upon which their players are removed (possibly sent to another scene, or kicked from the server).

Scene implementations are maintained by SceneManager, through which nearly all operations related to scenes must be performed. There is only one SceneManager instance, which can be obtained using the following code snippet:

SceneManager manager = SceneManager.Global.instance();

Consult SceneManager's javadoc to see what methods it has and how they must be used.

PlayerView

Critical to the understanding of the Scene API is the PlayerView. A PlayerView is a representation of a specific Player — a Minestom class — who may or may not be online. One can convert a PlayerView into a Player using its getPlayer method, which returns an Optional which will be empty if the player is offline. PlayerView instances can be stored long-term, and can be obtained from a PlayerViewProvider.

As with SceneManager, there is only one instance of PlayerViewProvider.

PlayerViewProvider provider = PlayerViewProvider.Global.instance();

//get a PlayerView from a UUID
PlayerView player = provider.fromUUID(UUID.randomUUID());

Optional<Player> playerOptional = player.getPlayer();
if (playerOptional.isPresent()) {
    Player minestomPlayer = playerOptional.get();
    
    //operations can now be performed on 'player'
    minestomPlayer.teleport(new Vec(0, 0, 0)).join();

    //you can also get a PlayerView from an already-existing Minestom Player
    PlayerView theView = provider.fromPlayer(player);
}

Getting a player's current scene

Sometimes, it is necessary to get the current scene that a player belongs to. You can do this as follows:

PlayerViewProvider provider = PlayerViewProvider.Global.instance();
PlayerView player = provider.fromUUID(UUID.randomUUID());
Optional<Scene> sceneOptional = SceneManager.Global.instance().currentScene(player);
if (scene.isPresent()) { //'scene' may be empty if the player is not currently in a scene
    Scene current = sceneOptional.get();
    //perform actions on 'current'
}

//or, get a scene of a certain type:
Optional<Lobby> lobbyOptional = SceneManager.Global.instance().currentScene(player, Lobby.class);
if (lobbyOptional.isPresent()) {
    Lobby lobby = lobbyOptional.get();
    //perform actions on the lobby here
}

Login

When a player logs into the server, it is often desirable to immediately send them to a Scene. In Phantazm, this scene is a Lobby instance.

However, in contrast to "regular" joining, this procedure has to occur in two steps. First, the method Lobby#join(Set, boolean) is called, with the login parameter set to true. When logging in, the join method will still add the player to the scene, but it will not teleport them. Teleporting right away, during the first phase of login, would actually result in an error as the player has not been added to an Instance (Minecraft 'world') yet.

At some point later, once the player is added to an instance, Lobby#postLogin(Set) is called. This method will teleport the player to the lobby's spawn point.

Proper Synchronization

Because scenes may be ticked in parallel, on different threads from the rest of Minestom, it is necessary to properly synchronize access to scenes unless you are operating from within the scene itself. For example, methods on a given Scene implementation do not need to sync access to themselves (but would need to synchronize on other instances of Scene).

The earlier examples did not demonstrate how to synchronize on a scene. The following demonstrates what this might look like:

PlayerViewProvider provider = PlayerViewProvider.Global.instance();
PlayerView player = provider.fromUUID(UUID.randomUUID());

Optional<Lobby> lobbyOptional = SceneManager.Global.instance().currentScene(player, Lobby.class);
if (lobbyOptional.isPresent()) {
    lobbyOptional.get().getAcquirable().sync(self -> { //'self' is the instance of Lobby returned by lobbyOptional.get()
        //anything in this block is synchronized and it is safe to call methods on the scene!
        self.sendMessage(Component.text("This message would be sent to all players currently in the lobby"));
    });
}

Things not to do

All methods that are annotated with @ApiStatus.Internal should not be directly called. They are called when appropriate by the SceneManager.