*이 글은 Spigot 1.19.2 버전을 기준으로 하여 제작되었습니다.
이전화
https://zepelown.tistory.com/41
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
위에서 입력한 방식대로 그대로 해주셔야 합니다.
작성할 수 있는 것이 많은데
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
'마인크래프트 > 플러그인 제작 강좌(자바)' 카테고리의 다른 글
[인텔리제이로 마크 플러그인 개발하기]7. 나만의 인벤토리 GUI 만들기 2편 (인벤토리와 관련된 이벤트 처리하기) (5) | 2022.10.10 |
---|---|
[인텔리제이로 마크 플러그인 개발하기]6. 나만의 인벤토리 GUI 만들기 1편 (기본적인 인벤토리 작성법) (3) | 2022.09.18 |
[인텔리제이로 마크 플러그인 개발하기]4. 나만의 아이템 제작하기 (ItemStack, ItemMeta에 관하여) (6) | 2022.08.31 |
[인텔리제이로 마크 플러그인 개발하기] 3. 흙을 캐면 다이아가 나오게 해보자! (이벤트와 리스너에 관하여) (13) | 2022.08.18 |
[인텔리제이로 마크 플러그인 개발하기] 2. 인텔리제이 한글인코딩 (6) | 2022.08.12 |
댓글