cat [file]

마인크레프트 모드제작 #4: 커스텀 명령어

Image

이번 글에서는 명령어에 대해 다루고, 명령어를 꾸미는 법에 대해 알아보려고한다.

이 글에서 다룰 명령어는 두가지 종류이다.

전자는 “/돈”을 입력하면 잔액을 출력하는 것처럼 단순한 명령어를 말하고 후자는 /수표 1000 처럼 1000이나 100000처럼 동적으로 데이터가 변경되는 명령어를 말한다.

argument가 없는 명령어

먼저 “돈” 명령어부터 만들어보자

Image

command 패키지를 만들고 BillProviderCommand와 MyMoneyCommand를 만들어 주었다. 전자는 수표를 지급하는 명령어를 작성할 것이고 후자는 내 돈을 보여줄 명령어이다.

MyMoneyCommand 클래스부터 작성해보자

public MyMoneyCommand(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(Commands.literal("돈").executes((command) -> {
            return execute(command.getSource());
        }));
    }

클래스에 위와같은 메서드를 추가해주었다.

dipatcher에 명령어를 등록해주었다. Commands.literal("돈").executes 안에는 “/돈”을 입력 했을때 , 어떤 기능이 수행될지 코드를 작성한다. 나는 excute라는 메서드를 만들어 따로 작성해주었다.

private int execute(CommandSourceStack source) throws CommandSyntaxException {
        if (source.getEntity() instanceof ServerPlayer player) {
            String playerName = player.getName().getString();
            Integer MONEY = new PlayerData().getMoney(player);
            if (MONEY != null) {
                source.sendSuccess(() -> Component.literal(playerName + "님의 돈: " + MONEY + "원")
                        .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)    // 빨간색
                                .withBold(true)                     // 굵게
                                .withItalic(true)), false);
            }

        } else {
            source.sendFailure(Component.literal("이 명령어는 플레이어만 사용할 수 있습니다!"));
        }
        return Command.SINGLE_SUCCESS;
    }

위 코드는 명령어를 입력했을 때, 어떤 기능을 할 지를 작성한 코드이다.

playerName 은 플레이어의 아이디를 가져오고, MONEY 는 저번 글에서 만든 데이터 공급자이다.

그리고 명령에 대한 응답을 전송하는 부분에는 sendFailure과 sendSuccess 외에도 두가지가 더 있는데 다음 표를 살펴보자

메서드 목적 메시지 출력 대상 특성
sendSuccess() 명령어 실행 성공 시 출력 명령어 실행한 플레이어 - Supplier<Component>로 메시지를 지연 생성- 운영자들에게도 메시지를 브로드캐스트 가능 (broadcastToOps)
sendFailure() 명령어 실행 실패 시 출력 명령어 실행한 플레이어 Component 객체로 즉시 메시지를 전달- 실패 메시지 출력
sendSystemMessage() 시스템 관련 메시지 출력 서버의 관리자/콘솔 - 서버 관련 시스템 메시지를 출력- 관리자가 볼 수 있는 메시지로 사용
sendChatMessage() 모든 플레이어에게 채팅 형식 메시지 전송 모든 플레이어 모든 플레이어에게 메시지를 전달- 채팅 형식으로 출력

/돈 을 입력했을 때, 잔액은 그 유저에게만 보이면 되므로, sendSuccess 를 사용하자.

source.sendSuccess(() -> Component.literal(playerName + "님의 돈: " + MONEY + "원")
                        .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)    // 초록색
                                .withBold(true)                     // 굵게
                                .withItalic(true)), false);

위 코드는 답변을 전송하는 것인데, setStyle로 답변의 색깔이나 굵기, 기울기를 정할 수도 있다. 위 코드는 색깔을 초록색으로, 굵고 기울어진 메세지를 전송하는 코드이다.

이렇게 코드를 작성했다면, 명령어도 등록해주어야 한다.

@EventBusSubscriber(modid = MODID, bus = EventBusSubscriber.Bus.GAME)
    public static class ServerModEvents {
        @SubscribeEvent
        public static void onRegisterCommands(RegisterCommandsEvent event) {
            CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
            new MyMoneyCommand(dispatcher);
            LOGGER.info("[MyMod] SUCCESS: Commands registered");
        }
       }

프로젝트에서 메인메서드가 실행되는 클래스에 위와같은 코드를 작성하여 명령어를 등록해준다.

그 후 runClient를 실행하여 잘 작동이 되나 확인해보자.

Image

Image

위와 같이 초기 값인 100000원이 잘 나온다.

argument가 있는 명령어

이제 “/수표 10000”처럼 특정 argument가 있는 명령어를 제작해보자.

이 명령어의 기능은 일정한 금액의 수표를 발급하는 것이다.

“BillProviderCommand” 클래스에 다음과 같은 소스를 작성하자.

public class BillProviderCommand {
    public BillProviderCommand(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(Commands.literal("수표")
                .then(Commands.argument("amount", IntegerArgumentType.integer()) // 'amount'는 액수 입력 받기
                .executes((command) -> {
                    int amount = IntegerArgumentType.getInteger(command, "amount"); // 액수 얻기
                    return execute(command.getSource(),amount);
                })));
    }

    private int execute(CommandSourceStack source,Integer amount) throws CommandSyntaxException {
        if (source.getEntity() instanceof ServerPlayer player) {
            PlayerData playerData = new PlayerData();
            int MONEY = playerData.getMoney(player);
        } else {
            source.sendFailure(Component.literal("이 명령어는 플레이어만 사용할 수 있습니다!"));
        }
        return Command.SINGLE_SUCCESS;
    }
}

Command.argument부분에 argument의 이름과 타입을 정해준다. /수표 는 숫자값을 받으므로 Integer로 정했다. 그리고 명령어 실행 부분인 excute에는 금액인 amount를 매개변수로 받는다.

if (MONEY >= amount) {
        source.sendSuccess(() -> Component.literal("수표 " + amount + "원이 발급되었습니다. 현재 잔액: " + (MONEY - amount) +"원")
            setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)
                 .withBold(true)                     // 굵게
                 .withItalic(true)), false);
    playerData.addMoney(player, -1*amount);

    ItemStack BILL = new ItemStack(ModItems.BILL_ITEM.get());
    player.addItem(BILL);
}else{
        source.sendSuccess(() -> Component.literal("잔액이 부족합니다. 현재 잔액: " + MONEY +"원")
                        .setStyle(Style.EMPTY.withColor(ChatFormatting.RED)), false);
}

위 부분은 수표를 지급하는 코드이다. 만약 현재 잔액이 수표액수보다 많다면 지급을 허용하고 유저의 인벤토리에 아이템을 넣어준다. 하지만 부족하면 지급을 거절한다.

전체코드는 다음과 같다.

public class BillProviderCommand {
    public BillProviderCommand(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(Commands.literal("수표")
                .then(Commands.argument("amount", IntegerArgumentType.integer()) // 'amount'는 액수 입력 받기
                .executes((command) -> {
                    int amount = IntegerArgumentType.getInteger(command, "amount"); // 액수 얻기
                    return execute(command.getSource(),amount);
                })));
    }

    private int execute(CommandSourceStack source,Integer amount) throws CommandSyntaxException {
        if (source.getEntity() instanceof ServerPlayer player) {
            PlayerData playerData = new PlayerData();
            int MONEY = playerData.getMoney(player);

            if (MONEY >= amount) {
                source.sendSuccess(() -> Component.literal("수표 " + amount + "원이 발급되었습니다. 현재 잔액: " + (MONEY - amount) +"원")
                        .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)
                                .withBold(true)                     // 굵게
                                .withItalic(true)), false);
                playerData.addMoney(player, -1*amount);

                ItemStack BILL = new ItemStack(ModItems.BILL_ITEM.get());
                BILL.set(BillDataComponent.VALUE,amount);
                player.addItem(BILL);
            }else{
                source.sendSuccess(() -> Component.literal("잔액이 부족합니다. 현재 잔액: " + MONEY +"원")
                        .setStyle(Style.EMPTY.withColor(ChatFormatting.RED)), false);
            }

        } else {
            source.sendFailure(Component.literal("이 명령어는 플레이어만 사용할 수 있습니다!"));
        }
        return Command.SINGLE_SUCCESS;
    }
}

이것도 argument가 없는 명령어에서 한 것처럼 메인 클래스에 등록한 후 테스트해보자

위와 같이 잘 된다.