Skip to content

Create an offline player command

This example shows how to execute a command for both online and offline players.

Offline player commands are useful when you need to access player data even if the player is not currently online.

Deep dive: [[DataLoader|Core-API/Player/DataLoader]]


Steps

  1. Try to get the player (online)
  2. If not found, load the player using a DataLoader
  3. Execute logic after loading (sync)
  4. Handle errors properly

Implementation

public class InfoCommand extends PixelCommand<ExamplePlugin> {

    @CommandDefinition(
            label = "info",
            usage = "(Player)",
            rank = PixelRank.SUPPORTER
    )
    public InfoCommand(ExamplePlugin plugin) {
        super(plugin);
    }

    @Override
    public void onPlayerCommand(BukkitPixelPlayer player, String label, String[] args) {
        if (args.length != 1) {
            String playerUsage = getPlayerUsage();
            player.sendMessage(playerUsage);
            return;
        }

        String targetName = args[0];

        PlayerHandler playerHandler = BukkitCoreLibrary.getPlayerHandler();

        // try to get online player first
        BukkitPixelPlayer target = playerHandler.getPlayer(targetName);
        if (target != null) {
            sendInformation(player, target);
            return;
        }

        ExamplePlugin plugin = getPlugin();

        DataLoader dataLoader = DataLoaderBuilder.create(DataLoaderBuilderType.SINGLE_PLAYER)
                .name("InfoCommand")
                .addRequireds(
                        // base player data is ALWAYS needed!
                        // rank data for colored name display
                        BasePlayerData.class,
                        RankPlayerData.class,
                        ExamplePlayerData.class
                )
                .build();

        Logger logger = plugin.getLogger();

        // loading player asynchronously
        BukkitExecutor.runAsync(plugin, () -> {
            BukkitPixelPlayer loadedTarget;

            try {
                loadedTarget = playerHandler.getPlayer(targetName, dataLoader);
            } catch (IOException | ClassNotFoundException | SQLException exception) {
                logger.log(Level.WARNING, "Failed to load player " + targetName + "!", exception);

                BukkitExecutor.runSync(plugin, () -> {
                    if (player.isOnline()) player.sendMessage(DATA_LOAD_FAILED);
                });
                return;
            }

            // switch back to main thread
            BukkitExecutor.runSync(plugin, () -> {
                if (!player.isOnline()) return;

                // can be null if required data is missing
                if (loadedTarget == null) {
                    player.sendMessage(PLAYER_IS_NOT_REGISTERED);
                    return;
                }

                sendInformation(player, loadedTarget);
            });
        });
    }

    private void sendInformation(@NotNull BukkitPixelPlayer player, @NotNull BukkitPixelPlayer target) {
        String coloredName = target.getColoredName();

        ExamplePlayerData data = target.getData(ExamplePlayerData.class);
        Timestamp firstJoin = data.getFirstJoin();

        player.sendMessage(coloredName + " first join: " + TimeUtil.getTimeMoment(firstJoin));
    }

}

Tab completion

@Override
public Collection<String> onPlayerTabComplete(BukkitPixelPlayer player, String label, String[] args) {
    if (args.length != 1) return null;

    // returning all online players across the network
    ServerHandler serverHandler = BukkitCoreLibrary.getServerHandler();
    return serverHandler.getOnlinePlayers(player);
}

Explanation

  • getPlayer(name) tries to get an online player
  • DataLoader is used to load offline player data
  • .addRequireds(...) defines which data must be loaded
  • Loading must happen asynchronously
  • Logic must run back on the main thread

Notes

  • Always try online lookup first (faster)
  • Always use async loading for offline players
  • Always switch back to the main thread before interacting with Bukkit
  • loadedTarget can be null if required data is missing
  • Only request the data you actually need

Next step

[[Create a jedis packet & listener|Core-API/Build-your-first-feature/Systems/Create-a-jedis-packet-&-listener]]