본문 바로가기
마인크래프트/플러그인 제작 강좌(자바)

[인텔리제이로 마크 플러그인 개발하기]5. 나만의 명령어 만들기 (give, help 명령어 만들어보기)

by Zepelown 2022. 9. 15.

*이 글은 Spigot 1.19.2 버전을 기준으로 하여 제작되었습니다.

 

이전화

https://zepelown.tistory.com/41

 

[인텔리제이로 마크 플러그인 개발하기]4. 나만의 아이템 제작하기 (ItemStack, ItemMeta에 관하여)

*이 글은 Spigot 1.19.2 버전을 기준으로 하여 제작되었습니다. 이전화 https://zepelown.tistory.com/38?category=745673 [인텔리제이로 마크 플러그인 개발하기] 3. 흙을 캐면 다이아가 나오게 해보자! (이벤트..

zepelown.tistory.com


BlockBreakEvent와 그리고 ItemStack, ItemMeta에 대해 강의를 진행했습니다.

 

이벤트도 플러그인을 제작할 때 많이 사용하긴 하지만

 

플러그인에서 가장 많이 쓰게 되는 것이 있죠.

 

명령어입니다.

 

오늘은 간단하게 /help 명령어와 이전에 만든 다이아를 명령어를 통해 받는 걸 만들어보겠습니다.

 


이렇게 명령어를 담당할 클래스들을 전부 commands라는 패키지에 넣을 예정입니다.

 

그리고 간단하게, 알아보기 쉽게 한 클래스당 한 명령어를 부여하는 걸로 하겠습니다.

 

help 명령어를 만들기 위해 Help라는 클래스를 만들었습니다.

 

위와 같이 작성해줍니다.

 

3강에서 말했듯이 CommandExecutor를 상속받아 사용하는 겁니다.

 

여기서 빨간색 줄에 마우스를 올려보시면

 

왜 오류가 나는지와 해결법을 알려줍니다.

 

implement methods 을 눌러줍니다.

 

오류가 나는 이유는 반드시 해당 매서드가 있어야 작동하기 때문입니다.

 

자세한 내용은 자바 상속 부분을 따로 공부하시면 됩니다.

 

implement methods 을 누르면

어떤 매서드가 필요한지 나옵니다.

 

ok를 눌러줍니다.

 

매서드가 나타났습니다.

 

여기서 자주 쓸 것은 매서드의 매개변수입니다.

 

sender는 말 그대로 명령어를 친 대상입니다. 콘솔일 수도 있고 플레이어일 수도 있습니다.

 

command 말 그대로 실행된 커맨드를 말합니다.

 

label은 명령어의 별명입니다. 쉽게 말해 test라는 명령어의 별명을 test2라고 하면 /test와 /test2는 같은 명령을 수행합니다.

 

args는 예를 들어 /go home and sleep이라고 치면 home, and, sleep 은 각각 args라는 스트링 배열에 들어가며 인덱스는 0, 1, 2입니다.

 

이번 강의에서는 sender,  args, label만 아시면 됩니다.

 


 

이번에 만들 건 이 플긴을 소개하는 help 명령어와 이전에 만든 다이아를 받는 명령어입니다.

 

먼저 help부터 해볼게요.

 

Help.java

package io.github.zepelown.testplugin.commands;

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public class Help implements CommandExecutor {


    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        sender.sendMessage("흙 블럭 부스면 다이아가 나와요 ㅎㅎ");
        return false;
    }
}

이렇게 작성해봤습니다.

 

정말 간단하지않나요?

 

그다음 이벤트와 마찬가지로 onEnable()에서 등록을 해줘야 합니다.

 

TestPlugin.java

package io.github.zepelown.testplugin;


import io.github.zepelown.testplugin.commands.GiveDia;
import io.github.zepelown.testplugin.commands.Help;
import io.github.zepelown.testplugin.event.BreakEvent;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public final class TestPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Plugin startup logic
        getLogger().info("플러그인 활성화");
        getServer().getPluginManager().registerEvents(new BreakEvent(), this);
        getServer().getPluginCommand("thelp").setExecutor(new Help());
    }

    @Override
    public void onDisable() {
        // Plugin shutdown logic
        getLogger().info("플러그인 비활성화");
    }
}

 

 

 

getServer().getPluginCommand("thelp").setExecutor(new Help());

위 코드가 새로 생겼습니다.

 

/thelp 입력하면 Help.java에서 작성한 코드가 작동하는 겁니다.

 

더보기

참고로 thelp라고 한 이유는 testhelp이니 원하시는 대로 바꾸셔도 무방합니다.

 

단 밑에 작성할 것도 바꿔주셔야 합니다.

 

이벤트라면 여기서 끝나겠지만 커맨드는 끝나지 않았습니다!

 

plugin.yml에도 해당 내용을 적어줘야 합니다.

 

plugin.yml

name: TestPlugin
version: '${project.version}'
main: io.github.zepelown.testplugin.TestPlugin
api-version: 1.19
commands:
  thelp:
    description: help command
    aliases:
      - thelp2

참고로 resources 폴더 안에 있습니다.

위에서 입력한 방식대로 그대로 해주셔야 합니다.

 

작성할 수 있는 것이 많은데

https://www.spigotmc.org/wiki/plugin-yml/

을 참고하시면 도움이 되실 겁니다.

 

 

다시 plugin.yml로 돌아와

 

description은 말 그대로 설명입니다. 마크 기본 명령어인 help를 쳤을 때 어떤 설명이 나오는가입니다.

 

 

    aliases:
      - thelp2

여기서 aliases가 뭘까요?

 

바로 위에서 설명한 label입니다. thelp2를 입력해도 thelp 같은 역할은 한다는 뜻입니다.

 

이제 확인해볼까요?

 

/help를 쳤을 때도 정상적으로 나오는 걸 볼 수 있습니다.

 


이제 다이아를 주는 명령어를 만들어보겠습니다.

 

명령어는 /tgivedia (enchant/개수) (개수)입니다.

 

/tgivedia 나 /tgivedia enchant 입력 시 각각 그냥 다이아나 인첸트 다이아 하나씩 줍니다.

 

/tgivedia 12 나 /tgivedia enchant 12 입력 시 각각 다이아랑 인첸트 다이아 12개씩 주는 겁니다.

 

조금 복잡할 수도 있지만 가보겠습니다.

 

GiveDia.java를 만들어줍니다.

 

먼저 만들 명령어는 따로 플레이어를 입력하는 게 아니라 명령어 입력한 자신에게 다이아 들어가는 겁니다.

 

따라서 명령어를 입력하는 대상을 누구인지 검증하는 것이 중요합니다.

 

public class GiveDia implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if(sender instanceof Player){
            Player player = (Player) sender;
            return true;
        } else {
            sender.sendMessage("콘솔로 치지마세요 ~ ");
        }

     }
}

이렇게 인스턴스를 검증하시면 됩니다.

 

instanceof란 쉽게 말해 해당 객체를 상속하고 있거나 일치할 경우 true를 반환합니다.

 

그러므로 player이면 if 문이 작동하는 겁니다.

 

 

 

 

자 이제 여기서 중요합니다.

 

우리가 만들 명령어는 /tgivedia (enchant/개수) (개수)입니다.

 

args[] 배열의 최대 길이는 몇일까요?

 

바로 2입니다. 당연하게도 args[]는 tgivedia 뒤에 오는 최대 두 개까지의 입력을 받는 겁니다.

 

명령어가 제대로 작동하기 위해서는 저 경우를 모두 따져줘야 한다는 겁니다.

 

쉽게 만들기 위해 저는 swtich 문을 사용했습니다. 당연히 if문으로 하셔도 무방합니다.

 

public class GiveDia implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (sender instanceof Player) {
            Player player = (Player) sender;
            switch (args.length) {
                case 0:
                    sender.sendMessage("다이아가 지급되었습니다.");
                    player.getInventory().addItem(ItemManager.dia);
                    break;
            }
        }  else {
            sender.sendMessage("콘솔로 치지마세요 ~ ");
        }
        return false;
    }
}

이렇게 하면 어떻게 될까요?

 

간단합니다. args[] 배열의 길이가 0인 경우를 상정했으므로 /tgivedia 만 입력한 경우입니다.

 

그래서 인첸트 안된 다이아 하나를 주는 겁니다.

 

public class GiveDia implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (sender instanceof Player) {
            Player player = (Player) sender;
            switch (args.length) {
                case 0:
                    sender.sendMessage("다이아가 지급되었습니다.");
                    player.getInventory().addItem(ItemManager.dia);
                    break;
                case 1:
                    if(args[0].equalsIgnoreCase("enchant")) {
                        player.getInventory().addItem(ItemManager.enchantDia);
                        sender.sendMessage("다이아가 지급되었습니다.");
                        break;
                    }
            }
        } else {
            sender.sendMessage("콘솔로 치지마세요 ~ ");
        }
        return false;
    }
}

이렇게 하면 어떻게 작동할까요?

 

/tgivedia enchant를 입력하면 인첸트 된 다이아 하나를 주는 겁니다.

 

놀랍게도 벌써 두 가지의 명령어를 만들었습니다.

 

하지만 저희는 숫자도 입력을 받아야 합니다.

 

플레이어에게 명령어에 입력한 개수를 줄려면 당연히 숫자를 입력받아야 합니다.

 

하지만 args[] 배열은 매서드 매개변수를 보시면 알겠지만 string입니다.

 

string에서 int를 변환 및 검증하는 방법은 많겠지만

 

저는 Integer  클래스 내 parseInt 메서드를 사용하겠습니다.

 

쉽게 말해 string을 int로 변환해주는 메서드입니다.

 

public class GiveDia implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (sender instanceof Player) {
            Player player = (Player) sender;
            switch (args.length) {
                case 0:
                    sender.sendMessage("다이아가 지급되었습니다.");
                    player.getInventory().addItem(ItemManager.dia);
                    break;
                case 1:
                    if(args[0].equalsIgnoreCase("enchant")) {
                        player.getInventory().addItem(ItemManager.enchantDia);
                        sender.sendMessage("다이아가 지급되었습니다.");
                        break;
                    } else {
                        try{
                            int amount = Integer.parseInt(args[0]);
                            ItemStack dia = new ItemStack(ItemManager.dia);
                            dia.setAmount(amount);
                            player.getInventory().addItem(dia);
                            break;
                        } catch (NumberFormatException e){
                            player.sendMessage("명령어를 제대로 입력해주세요.");
                            return false;
                        } catch (Exception e){
                            player.sendMessage("명령어를 제대로 입력해주세요.");
                            return false;
                        }
                    }
            }
        } else {
            sender.sendMessage("콘솔로 치지마세요 ~ ");
        }
        return false;
    }
}

이제 /tgivedia 12를 입력할 수 있게 됩니다.

 

또한 당연하게도 오류를 검사해야 합니다. 왜냐하면

 

만약 오류 검사를 안 하고 유저가 /tgivedia 12a라고 입력하면 어떻게 될까요?

 

int형으로 변환이 불가능하기 때문에

 

오류가 발생하고 원하는 대로 작동하지 않을 겁니다.

 

그래서 try 문을 사용해 오류를 미리 검출하는 겁니다.

 

GiveDia.java

package io.github.zepelown.testplugin.commands;

import io.github.zepelown.testplugin.ItemManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

public class GiveDia implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (sender instanceof Player) {
            Player player = (Player) sender;
            switch (args.length) {
                case 0:
                    sender.sendMessage("다이아가 지급되었습니다.");
                    player.getInventory().addItem(ItemManager.dia);
                    break;
                case 1:
                    if(args[0].equalsIgnoreCase("enchant")) {
                        player.getInventory().addItem(ItemManager.enchantDia);
                        sender.sendMessage("다이아가 지급되었습니다.");
                        break;
                    } else {
                        try{
                            int amount = Integer.parseInt(args[0]);
                            ItemStack dia = new ItemStack(ItemManager.dia);
                            dia.setAmount(amount);
                            player.getInventory().addItem(dia);
                            break;
                        } catch (NumberFormatException e){
                            player.sendMessage("명령어를 제대로 입력해주세요.");
                            return false;
                        } catch (Exception e){
                            player.sendMessage("명령어를 제대로 입력해주세요.");
                            return false;
                        }
                    }
                case 2:
                    try{
                        int amount = Integer.parseInt(args[1]);
                        ItemStack dia = new ItemStack(ItemManager.enchantDia);
                        dia.setAmount(amount);
                        player.getInventory().addItem(dia);
                        break;
                    } catch (NumberFormatException e){
                        player.sendMessage("명령어를 제대로 입력해주세요.");
                        return false;
                    } catch (Exception e){
                        player.sendMessage("명령어를 제대로 입력해주세요.");
                        return false;
                    }
            }
        } else {
            sender.sendMessage("콘솔로 치지마세요 ~ ");
        }
        return false;
    }
}

/tgivedia enchant 12도 이제 작동하게 됩니다.

 

다만

int amount = Integer.parseInt(args[1]);
ItemStack dia = new ItemStack(ItemManager.enchantDia);
dia.setAmount(amount);
player.getInventory().addItem(dia);
break;

try문이 조금 다릅니다.

 

첫 번째 줄은 args[1] , 즉 /tgivedia enchant 2 에서 2를 추출하는 겁니다.

 

두 번째 줄은 기존에 작성한 ItemManager.enchantDia는 상수이기 때문에 새로운 ItemStack을 만들어

 

ItemManager.enchantDia를 깊은 복사를 하고 개수 부분을 수정하여 플레이어에게 전달하는 겁니다.

 

깊은 복사란?

더보기

쉽게 말하면

 

새로운 메모리, 즉 공간을 부여하여 복사하는 것을 말합니다.

 

반대로 얕은 복사가 있으며 주소만 복사하는 것을 말합니다.

 

즉 A -> B 얕은 복사를 하게 되면 A와 B 서로 연결되어 있습니다.

(주소 값이 같으므로)

 

따라서 B를 수정시 두 개의 값 모두 변경될 수도 있으므로

 

얕은 복사를 여기선 사용하지 않은 겁니다.

 

궁금하시면

 

Itemstack dia = ItemManager.enchantDia 로 한번 실행시켜보시길 바랍니다.

 

 


정말 먼 길을 왔습니다.

 

TestPlugin.java

package io.github.zepelown.testplugin;


import io.github.zepelown.testplugin.commands.GiveDia;
import io.github.zepelown.testplugin.commands.Help;
import io.github.zepelown.testplugin.event.BreakEvent;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public final class TestPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Plugin startup logic
        getLogger().info("플러그인 활성화");
        getServer().getPluginManager().registerEvents(new BreakEvent(), this);
        getServer().getPluginCommand("thelp").setExecutor(new Help());
        getServer().getPluginCommand("tgivedia").setExecutor(new GiveDia());
    }

    @Override
    public void onDisable() {
        // Plugin shutdown logic
        getLogger().info("플러그인 비활성화");
    }
}

plugin.yml

name: TestPlugin
version: '${project.version}'
main: io.github.zepelown.testplugin.TestPlugin
api-version: 1.19
commands:
  thelp:
    description: help command
    aliases:
      - thelp2
  tgivedia:
    description: givedia

 

 

댓글