Skip to content

warps

Named warps: /warp set <name> saves where you stand, /warp <name> teleports you there, and /warps lists them. The example shows Paper commands: one Brigadier tree and one BasicCommand, both registered by the bundled paper:commands module. The full source lives in examples/paper/warps.

The plugin main

WarpsPlugin is just the registry hook. Its paper-plugin.yml declares no commands.

package org.weftkit.examples.warps;

import org.weftkit.wiring.Registry;
import org.weftkit.wiring.bukkit.WeftPlugin;
import org.weftkit.wiring.registry.ComponentRegistry;

@Registry
public final class WarpsPlugin extends WeftPlugin {

    @Override
    protected ComponentRegistry registry() {
        return WeftWiring.INSTANCE;
    }
}
name: Warps
version: 1.0.0
main: org.weftkit.examples.warps.WarpsPlugin
api-version: "1.21"
description: Named warps with /warp and /warps, registered as Paper commands by weftkit.

The store

WarpStore holds the warps in memory, sorted by name.

package org.weftkit.examples.warps.storage;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.bukkit.Location;
import org.weftkit.wiring.Singleton;
import org.weftkit.wiring.Wired;

@Wired
@Singleton
public final class WarpStore {

    private final Map<String, Location> warps = new TreeMap<>();

    public WarpStore() {}

    public void set(String name, Location location) {
        warps.put(name, location.clone());
    }

    public Location find(String name) {
        Location warp = warps.get(name);
        return warp == null ? null : warp.clone();
    }

    public Set<String> names() {
        return new TreeSet<>(warps.keySet());
    }
}

The tree command

WarpCommand is a CommandTree: a set subcommand behind a permission, and a name argument that suggests the existing warps. Brigadier prefers a literal over an argument at the same level, so /warp set could never teleport to a warp named set, and the subcommand refuses that name. The root requires a player, so the console never sees it.

package org.weftkit.examples.warps.commands;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.Commands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.weftkit.examples.warps.storage.WarpStore;
import org.weftkit.wiring.Singleton;
import org.weftkit.wiring.Wired;
import org.weftkit.wiring.paper.commands.CommandTree;
import org.weftkit.wiring.paper.commands.PaperCommand;

@Wired
@Singleton
@PaperCommand(value = "warp", description = "Teleport to or set a named warp")
public final class WarpCommand implements CommandTree {

    private final WarpStore warps;

    public WarpCommand(WarpStore warps) {
        this.warps = warps;
    }

    @Override
    public LiteralArgumentBuilder<CommandSourceStack> tree(String label) {
        return Commands.literal(label)
                .requires(source -> source.getExecutor() instanceof Player)
                .then(Commands.literal("set")
                        .requires(source -> source.getSender().hasPermission("warps.set"))
                        .then(Commands.argument("name", StringArgumentType.word())
                                .executes(this::set)))
                .then(Commands.argument("name", StringArgumentType.word())
                        .suggests((context, builder) -> {
                            warps.names().forEach(builder::suggest);
                            return builder.buildFuture();
                        })
                        .executes(this::teleport));
    }

    private int set(CommandContext<CommandSourceStack> context) {
        Player player = (Player) context.getSource().getExecutor();
        String name = StringArgumentType.getString(context, "name");
        if (name.equals("set")) {
            player.sendMessage(Component.text("A warp cannot be named set.", NamedTextColor.RED));
            return 0;
        }
        warps.set(name, player.getLocation());
        player.sendMessage(Component.text("Warp " + name + " set.", NamedTextColor.YELLOW));
        return Command.SINGLE_SUCCESS;
    }

    private int teleport(CommandContext<CommandSourceStack> context) {
        Player player = (Player) context.getSource().getExecutor();
        String name = StringArgumentType.getString(context, "name");
        Location warp = warps.find(name);
        if (warp == null) {
            player.sendMessage(Component.text("No warp named " + name + ".", NamedTextColor.RED));
            return 0;
        }
        player.teleport(warp);
        return Command.SINGLE_SUCCESS;
    }
}

The basic command

WarpsCommand is a plain BasicCommand with an alias.

package org.weftkit.examples.warps.commands;

import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import java.util.Set;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.weftkit.examples.warps.storage.WarpStore;
import org.weftkit.wiring.Singleton;
import org.weftkit.wiring.Wired;
import org.weftkit.wiring.paper.commands.PaperCommand;

@Wired
@Singleton
@PaperCommand(value = "warps", description = "List every warp", aliases = "warplist")
public final class WarpsCommand implements BasicCommand {

    private final WarpStore warps;

    public WarpsCommand(WarpStore warps) {
        this.warps = warps;
    }

    @Override
    public void execute(CommandSourceStack source, String[] args) {
        Set<String> names = warps.names();
        if (names.isEmpty()) {
            source.getSender().sendMessage(Component.text("No warps yet.", NamedTextColor.GRAY));
            return;
        }
        source.getSender().sendMessage(Component.text("Warps: " + String.join(", ", names), NamedTextColor.YELLOW));
    }
}

Run it

Build the example as described in the overview, drop the jar into the plugins/ folder of a Paper 1.21.1 or newer server, run /warp set spawn, wander off, and /warp spawn brings you back.