[Paper 플러그인 실전 제작기 #02] 명령어 시스템 실전 (/heal 권한, 인자 파싱, 탭 완성)
2026. 2. 21. 12:32ㆍJava/마인크래프트
1편에서 플러그인 뼈대를 만들었다면,
2편에서는 실제로 많이 쓰는 명령어 시스템을 가장 쉬운 예제로 익혀보겠습니다.
오늘 목표는 딱 3개입니다.
/heal만들기- 권한 분리하기 (
자기 자신/다른 사람) - 탭 자동완성 붙이기
기준 환경: Java 21, Maven, Paper API 1.21.11-R0.1-SNAPSHOT, api-version 1.21
1) plugin.yml에서 명령어/권한 등록
name: PaperPractice
version: 0.2.0
main: com.example.paperpractice.PaperPracticePlugin
api-version: "1.21"
commands:
heal:
description: Heal yourself or another player
usage: /heal [player] [amount]
permissions:
paperpractice.heal.self:
description: Allows healing yourself
default: true
paperpractice.heal.others:
description: Allows healing others
default: op
쉽게 말하면:
paperpractice.heal.self- 내 자신 회복 권한
default: true= 기본적으로 모든 유저 허용
paperpractice.heal.others- 다른 사람 회복 권한
default: op= 기본적으로 OP만 허용
즉, 기본 상태에서 일반 유저는 /heal만 가능, OP는 /heal 다른사람도 가능입니다.
2) 메인 클래스에서 명령 연결
@Override
public void onEnable() {
HealCommand healCommand = new HealCommand();
PluginCommand command = getCommand("heal");
if (command == null) {
getLogger().severe("Command 'heal' is not defined in plugin.yml");
getServer().getPluginManager().disablePlugin(this);
return;
}
command.setExecutor(healCommand);
command.setTabCompleter(healCommand);
}
왜 2줄이 필요할까요?
setExecutor(...)= 명령 실행(/heal) 담당 등록setTabCompleter(...)= 탭 키 자동완성 담당 등록
같은 객체를 넣어도, 실행 기능과 자동완성 기능은 Paper 내부에서 따로 등록해야 합니다.
3) HealCommand 구현 (핵심만)
public final class HealCommand implements TabExecutor {
private static final String PERM_SELF = "paperpractice.heal.self";
private static final String PERM_OTHERS = "paperpractice.heal.others";
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// /heal (자기 자신)
if (args.length == 0) {
if (!(sender instanceof Player player)) {
sender.sendMessage("콘솔에서는 /heal <player> 로 사용하세요.");
return true;
}
if (!player.hasPermission(PERM_SELF)) {
player.sendMessage("권한이 없습니다. (" + PERM_SELF + ")");
return true;
}
healFull(player);
player.sendMessage("체력/허기 회복 완료");
return true;
}
// /heal <player> ...
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null) {
sender.sendMessage("플레이어를 찾을 수 없습니다.");
return true;
}
// 타인 회복 권한 체크
if (!sender.hasPermission(PERM_OTHERS)) {
sender.sendMessage("권한이 없습니다. (" + PERM_OTHERS + ")");
return true;
}
// /heal <player>
if (args.length == 1) {
healFull(target);
sender.sendMessage(target.getName() + " 회복 완료");
return true;
}
// /heal <player> <amount>
int amount;
try {
amount = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
sender.sendMessage("숫자를 입력하세요. 예: /heal Steve 8");
return true;
}
if (amount <= 0) {
sender.sendMessage("회복량은 1 이상이어야 합니다.");
return true;
}
double maxHealth = target.getAttribute(Attribute.MAX_HEALTH).getValue();
target.setHealth(Math.min(maxHealth, target.getHealth() + amount));
sender.sendMessage(target.getName() + " 체력 " + amount + " 회복");
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
String prefix = args[0].toLowerCase(Locale.ROOT);
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix))
.sorted()
.toList();
}
if (args.length == 2) {
List<String> amounts = List.of("4", "8", "12", "20");
String prefix = args[1];
return amounts.stream().filter(a -> a.startsWith(prefix)).toList();
}
return List.of();
}
private void healFull(Player player) {
double maxHealth = player.getAttribute(Attribute.MAX_HEALTH).getValue();
player.setHealth(maxHealth);
player.setFoodLevel(20);
player.setSaturation(20f);
player.setFireTicks(0);
}
}
4) 이 코드에서 꼭 이해할 4줄
hasPermission(...)- 이 유저가 권한 있는지 확인
Integer.parseInt(...)- 숫자 인자 변환 (실패하면 예외 처리)
onTabComplete(...)- 탭 누를 때 추천 목록 반환
Math.min(maxHealth, 현재체력 + amount)- 최대 체력 초과 방지
5) 빠른 테스트
- 일반 유저로
/heal - 일반 유저로
/heal 다른유저(막혀야 정상) - OP로
/heal 다른유저 - OP로
/heal 다른유저 8 /heal 다른유저 abc(숫자 안내 문구 확인)- 탭으로 플레이어 이름/수치 추천 확인
마무리
이번 편 핵심은 “명령어 실행”보다 운영 가능한 명령어를 만드는 방법입니다.
- 권한 분리
- 인자 검증
- 탭 자동완성
이 3가지만 습관화해도, 플러그인 완성도가 확 올라갑니다.
'Java > 마인크래프트' 카테고리의 다른 글
| [Paper 플러그인 실전 제작기 #05] 데이터 저장 1: YAML로 유저별 설정 저장 (0) | 2026.02.26 |
|---|---|
| [Paper 플러그인 실전 제작기 #04] config.yml로 기능 on/off + 메시지 커스터마이징 (2) | 2026.02.25 |
| [Paper 플러그인 실전 제작기 #03] 이벤트 리스너 실전 (스폰 보호구역에서 블록 설치/파괴 막기) (0) | 2026.02.23 |
| [Paper 플러그인 실전 제작기 #01] Java 21 + Maven으로 첫 플러그인 만들기 (0) | 2026.02.20 |
| Paper 플러그인 실전 제작기 (0) | 2026.02.20 |