Skip to content

API_Command

LemonCaramel edited this page Apr 6, 2022 · 5 revisions

명령어 등록

Daydream 기존 레거시 서버에서 사용되는 명령어 체계를 사용하지 않고, Minecraft 1.13 이후 탑재된 Mojang의 명령어 분석기 "Brigadier"을 사용할 수 있습니다.

우선 기존의 명령어 등록 방식을 살펴봅시다.


기존 방식

만약 "TestPlugin"이라는 이름의 플러그인에서 명령어를 등록한다고 가정합시다.

/caramel 명령어의 정보

  • [cherry, melon]의 Aliases를 가집니다.
  • 테스트 명령어 라는 설명(Description)을 가집니다.
  • caramel.command라는 권한이 있어야 작동합니다.
  • 권한이 없는 경우 빨간색의 권한이 없습니다. 메시지가 출력됩니다.
  • 명령어의 사용법은 /<command> <string> 입니다.

위 정보를 바탕으로 명령어를 만듭니다.


plugin.yml

...

commands:
  caramel:
    aliases: [ cherry, melon ]
    description: 테스트 명령어
    permission: caramel.command
    permission-message: §c권한이 없습니다.
    usage: /<command> <string>
(이 명령어는 Bukkit에 명령어가 등록되면 다음과 같은 형태로 사용할 수 있습니다)
 - /caramel
 - /testplugin:caramel
 - /cherry
 - /testplugin:cherry
 - /melon
 - /testplugin:melon

JavaPlugin가 확장된 Main.java

...

@Override
public void onEnable() {
    this.getCommand("caramel").setExecutor(new CaramelCommand());
}

CaramelCommand.java

public class CaramelCommand implements CommandExecutor, TabExecutor {

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (args.length == 0) {
            sender.sendMessage(Component.text("사용법: /<command> <string>"));
            return true;
        }

        sender.sendMessage(Component.text("당신이 입력한 메시지: " + args[0]));
        return false;
    }

    @Override
    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
        if (args.length == 0) return Arrays.asList("안녕하세요", "Caramel", "Cherry", "Melon");
        return null;
    }
}

이렇게 명령어를 만들었습니다.

이러한 형태로 등록되는 명령어의 단점은 Argument(인수)가 많아질수록 IF 지옥이 펼쳐질 가능성이 높으며, onTabComplete와 onCommand를 따로 처리해 주어야합니다. 또한 int, double, boolean 형식의 Argument(인수)를 받아야하는 경우 명령어 개발자가 직접 예외 처리를 해주어야하는 불편함이 있습니다.

그래서 등장하게 된 것이 바로 Brigadier이라는 새로운 명령 체계 시스템인데, Bukkit API는 망할 레거시 호환성을 지키겠다고 1.13 이전의 구식 명령 체계를 상위 버전에서 다시 구현해두었습니다.

이제 편하게 알록달록 명령어를 구현할 시간입니다.


새로운 방식 (Daydream Brigadier API 사용)

여기까지 뒤로가기를 누르지 않고 읽었다면 당신은 참 개발자입니다. 👍

기존 Bukkit API에서는 명령어 입력 신호가 들어오면 CommandExecutor 구현 클래스를 찾아 onCommand(...) 메서드를 실행해주는데, 새로운 API에서 등록하게 된다면 반대로 명령어 구현 클래스가 서버 구동기에 명령어를 등록시켜줍니다.

최대한 쉽게 설명했는데 이해가 되지 않는다면 아래 설명을 참고하세요.

기존: 명령어 입력 -> 서버 구동기(분석 & 명령어 찾기) -> 등록된 명령어 실행(그러나 Bukkit API는 기존 명령 시스템을 재구현했기에...) -> CommandExecutor를 찾아 플러그인 명령어 실행(onCommand)
신규: 
  - 서버 실행 -> API를 사용한 명령어 등록 -> 서버 구동기에 등록
  - 명령어 입력 -> 서버 구동기(분석 & 명령어 찾기) -> 등록된 명령어 실행

굳이 이해할 필요는 없긴합니다. 명령어 등록 및 실행 구조에 대해 이해가 필요하다면 참고하면 됩니다.

이제 새로운 방식으로 명령을 등록하는 법을 배워봅시다.

plugin.yml? 설정할 필요 없습니다. 그냥 비워두세요!

JavaPlugin가 확장된 Main.java

...
@Override
public void onLoad() { // onEnable에 등록해도 상관 없음

    /**
     * @param 첫번째 - Plugin 객체(어지간해선 this)
     * @param 두번째 - 명령어 이름
     * @param 세번째 - AbstractCommand가 확장된 클래스
     */
    this.getServer().registerCommand(this, "caramel", new CaramelCommand());
}

노란줄이 안보이니 벌써부터 싱글벙글 해집니다

CaramelCommand.java

public class CaramelCommand extends AbstractCommand {

    public CaramelCommand() {
        this.setAliases("cherry", "melon"); // Aliases 설정
        this.setDescription("테스트 명령어"); // 설명 설정
        this.setPermission("caramel.command"); // 권한 설정
        this.setPermissionMessage("§c권한이 없습니다."); // String 형식
        this.permissionMessage(Component.text("권한이 없습니다.", NamedTextColor.RED)); // Adventure Component 형식
        this.setUsage("/<command> <string>"); // Usage 설정
    }

    @Override
    public void createCommand(LiteralArgumentBuilder<BukkitBrigadierCommandSource> builder) {
        builder.then(
            this.argument("메시지", StringArgumentType.word())
                .suggests(this.suggest("안녕하세요", "Caramel", "Cherry", "Melon"))
                .executes(context -> {
                        CommandSender source = context.getSource().getBukkitSender(); // CommandSender을 가져오려면 꼭!
                        String type = context.getArgument("메시지", String.class);

                        source.sendMessage(Component.text("당신이 입력한 메시지: " + type));
                        return 0; // 현재까지 큰 의미는 없으나 0으로 고정해야 함
                    }
                )
        );
    }
}

매우 간단합니다! builder 형태이기에 마음대로 끊어서 써도 됩니다.

this.argument(@NotNull String name, @NotNull ArgumentType<T> type) // this. 생략 가능
// 위 메소드의 예시

argument("인수의 이름", /*여기는 인수의 타입*/);

/*
인수의 타입은 여러가지가 있습니다.

com.mojang.brigadier.arguments.StringArgumentType // String
com.mojang.brigadier.arguments.BoolArgumentType // Boolean
com.mojang.brigadier.arguments.DoubleArgumentType // Double
com.mojang.brigadier.arguments.FloatArgumentType // float
com.mojang.brigadier.arguments.IntegerArgumentType // Integer
com.mojang.brigadier.arguments.LongArgumentType // Long

기본 제공 타입은 위와 같으며, Minecraft 자체 타입은 더 많지만 아직 구현이 안되었습니다.
*/

// 인수 추출하는 법
this.argument("나이", IntegerArgumentType.integer()).executes(context -> {
        CommandSender source = context.getSource().getBukkitSender();
        int age = context.getArgument("나이", Integer.class); // 여기에서 인수의 이름과 타입을 대입하여 추출할 수 있습니다.

        source.sendMessage(Component.text("당신의 나이: " + age));
        return 0;
    }
)

IntegerArgumentType과 같은 숫자 타입은 범위를 지정해줄 수도 있습니다.

IntegerArgumentType.integer(10, 20)인 경우 10에서 20까지만 입력을 허용합니다.

image

image

마지막으로 Boolean을 인수로 받아봅시다.

this.argument("참/거짓", BoolArgumentType.bool()).executes(context -> {
    boolean trueOrFalse = BoolArgumentType.getBool(context, "참/거짓"); // 기본 제공 타입의 경우 get~() 메소드가 있음
                    
    source.sendMessage(Component.text("참일까 거짓일까: " + trueOrFalse));
    return 0;
})

image

image

여기까지 읽었는데도 이해가 안 될 거 다 알고 있습니다. 예제를 따라해본 후 추가 설명이 필요하다면 개인 강의 가능하니 연락 주시기 바랍니다.

Content / 목차

Clone this wiki locally