AI 팀으로 마크 서버 만들기#2플러그인 하나에 모듈 13개! 코어 구조와 '1000029 데미지' 사건 🧩
한눈에 보기
이 서버는 플러그인 jar 하나에 core와 13개 모듈이 들어 있어요. 모듈끼리는 서로를 직접 부르지 않고 core의 서비스로만 연결돼요. 이 구조에서 실제로 터진 버그 두 개와, 서버를 끄지 않고 설정을 다시 읽는 리로드·재시작 기능을 소개합니다.
- Paper 1.21.11 · Java · 플러그인 jar 1개 = core + 모듈 13개 · 2026-09-29~30 실제 수정
- 모듈끼리 직접 의존하지 않고 core 서비스(인터페이스)로만 연결 — 팀별 병렬 개발이 가능해진 이유
- 권한 노드 접두사 규칙을 어기면 모듈이 통째로 꺼지게 만든 건 의도된 안전장치였어요
- 데미지 1000029 = '남은 체력 29 + 사망 처리용 1,000,000'. 계산이 아니라 표시가 틀렸던 버그
목차9개 항목

안녕하세요! Mojangcam입니다 😎
지난 #1에서는 Claude를 팀장으로, 예약 작업 15개를 팀원으로 두고 일하는 구조를 소개했어요. 이번 편은 그 팀들이 만드는 플러그인 자체의 구조예요.
우리 서버에는 우리가 만든 플러그인이 jar 파일 딱 하나만 들어가요. 그 안에 레벨·전투·스킬·퀘스트·상점·몹 같은 기능이 모듈 13개로 나뉘어 있어요. 이번 편에서는
- 🧩 core + 모듈 구조를 왜 이렇게 잡았는지
- 🔒 권한 이름 하나 때문에 모듈 두 개가 통째로 꺼진 사건
- 💥 몹을 잡으면 데미지가
1000029로 뜨던 사건 - 🔄 서버를 끄지 않고 설정을 다시 읽는
/리로드, 자동으로 다시 켜지는/재시작
을 정리해 볼게요.

📌 준비물 (환경)
- 서버: Paper 1.21.11, Java 21 이상
- 빌드: Gradle 멀티 프로젝트 (모듈마다 하위 프로젝트 → 최종 jar 하나로 묶기)
- DB: MariaDB (진행 상황 저장)
- 외부 플러그인은 최소한만: LuckPerms(권한) · EssentialsX · WorldEdit
📌 1단계 - 왜 "jar 하나 + 모듈 13개"인가 🧠
처음엔 기능마다 플러그인을 따로 만들까 했어요. 그런데 레벨·전투·스킬·아이템은 서로 숫자를 계속 주고받아요. 플러그인이 따로면 로딩 순서, 버전 맞추기, API 공유가 전부 골칫거리예요.
그래서 이렇게 정했어요.
| 층 | 역할 |
|---|---|
| core | 설정 파일·메시지·DB 연결·권한 등록·명령어 등록·서비스 레지스트리·가상 체력·HUD |
| 모듈 13개 | economy · quest · job · level · combat · skill · item · companion · world · mob · log · storage 등 |
| 규칙 | 모듈은 다른 모듈 클래스를 직접 import하지 않음. 필요한 기능은 core에 있는 인터페이스(서비스) 로만 |
예를 들어 전투 모듈이 무기 공격력을 알고 싶으면 아이템 모듈을 부르지 않고, core에 등록된 ItemService를 찾아서 써요. 아이템 모듈이 꺼져 있으면 서비스가 없을 뿐, 전투 모듈은 기본값으로 돌아가요.
이 규칙 덕분에 팀 13개가 동시에 개발할 수 있었어요. 각 팀은 인터페이스만 믿고 일하면 되니까요.
📌 2단계 - 권한 이름 하나로 모듈이 통째로 꺼진 사건 🔒
서버를 켰더니 로그에 이런 줄이 떴어요 (요약).
[MyServer] quest 모듈 켜기 실패: 권한 노드는 'myserver.quest.' 로 시작해야 합니다 — myserver.qnpc.console
[MyServer] world 모듈 켜기 실패: 권한 노드는 'myserver.world.' 로 시작해야 합니다 — myserver.region.admincore의 권한 등록 함수는 "권한 이름은 myserver.<모듈>.로 시작해야 한다" 는 규칙을 검사하고, 어기면 예외를 던져요. 예외가 나면 그 모듈은 켜지지 않아요.
퀘스트 팀은 NPC 명령에 myserver.qnpc.…, 월드 팀은 구역 보호에 myserver.region.…을 썼어요. 사람이 봐도 그럴듯한 이름이라 리뷰에서 놓쳤죠.
하나씩 뜯어볼게요 💡
- 왜 이렇게 엄격하게? : 모듈이 13개면 권한 이름이 겹치기 쉬워요. 접두사를 강제하면 LuckPerms에서
myserver.quest.*한 줄로 모듈 권한을 통째로 줄 수 있어요. - 왜 경고가 아니라 모듈을 꺼 버리나? : 권한이 잘못 등록된 채 돌아가면, 운영자 전용 명령이 일반 유저에게 열리는 사고가 날 수 있어요. 안전장치는 확실히 멈추는 쪽이 나아요.
수정은 이름 두 개를 바꾸는 걸로 끝났어요. 대신 같은 사고를 막으려고 합치기 전에 자동으로 검사하는 한 줄을 팀장 절차에 넣었어요.
# module/<팀> 브랜치에서 접두사 규칙을 어긴 권한 노드 찾기 (결과가 비어 있어야 통과)
m=quest
git grep -hoE 'registerPermission\([^;]*' module/$m -- "$m/src/main" \
| grep -oE '"myserver\.[a-z_]+\.' | awk -F. -v m=$m '$2!=m'📌 3단계 - 데미지 1000029 사건 💥
게임에서 몹을 잡는 순간, 머리 위에 1000029 같은 말도 안 되는 숫자가 떴어요. 그런데 몹이 한 방에 죽은 건 아니었어요. 앞의 공격들은 정상 숫자였고, 마지막 한 방만 이상했어요.
원인: "가상 체력"과 "확실히 죽이기"
이 서버는 체력이 수천~수만까지 올라가요. 바닐라 체력(최대 1024)으로는 부족해서, core가 가상 체력을 따로 관리해요. 가상 체력이 0이 되면 core가 바닐라 쪽에서도 몹을 확실히 죽여야 하는데, 이때 이렇게 했어요.
// core: 가상 체력이 0이 되면 바닐라에서도 확실히 죽인다
e.damage(e.getHealth() + 1_000_000, damageSource); // 사망 처리용 내부 피해바닐라 입장에서는 "남은 체력 + 1,000,000"만큼 맞은 거예요. 그리고 데미지 숫자를 띄우는 전투 모듈이 이 내부 피해 이벤트까지 받아서 그대로 표시한 거죠. 남은 바닐라 체력 29 + 1,000,000 = 1000029. 숫자가 정확히 맞아떨어져요 😂
수정: 사망 처리용 피해는 표시하지 않기
이 내부 피해는 피해 종류가 GENERIC_KILL이에요. 전투 모듈에 한 줄만 넣었어요.
// combat: 데미지 숫자 표시 리스너 (MONITOR)
if (event.getDamageSource().getDamageType() == DamageType.GENERIC_KILL) return; // 사망 처리용 내부 피해는 숫자로 안 띄움
double shown = event.getFinalDamage();실제 피해 계산은 처음부터 정상이었고, 표시만 틀렸던 버그였어요. 이런 버그는 게임 안에서 한 번 보면 바로 알지만, 단위 테스트로는 잡기 어려워요. 1편에서 말한 "사람은 게임 들어가서 보고 말하기" 역할이 여기서 빛났어요.
📌 4단계 - 서버 안 끄고 설정 바꾸기: /리로드 · /재시작 🔄
AI 팀이 하루에도 몇 번씩 설정과 jar를 고치니까, 매번 서버를 껐다 켜는 게 너무 번거로웠어요. 그래서 core에 두 가지를 넣었어요.
| 명령 | 하는 일 |
|---|---|
/리로드 (/myserver reload) |
core → 켜진 모듈 전부의 설정 파일을 다시 읽고, 모듈별 ✓/✗ 요약. 설정이 틀리면 그 모듈만 실패하고 이전 설정 유지 |
/리로드 level |
그 모듈만 다시 읽기 |
/재시작 60 |
60초 카운트다운(채팅 + 보스바) 후 서버 종료 → 자동으로 다시 켜짐 |
| (자동) | 설정 파일이 바뀌면 알아서 다시 읽음, plugins/update에 새 jar가 들어오면 운영자에게 알림 |
"자동으로 다시 켜짐"은 서버 쪽 코드만으로는 안 돼요. 플러그인이 종료 직전에 표시 파일을 하나 만들고, 서버를 켜는 배치 파일이 그 파일을 보고 다시 켜요.
@echo off
chcp 65001 > nul
cd /d "%~dp0"
:loop
if exist "plugins\MyServer\.restart" del /q "plugins\MyServer\.restart"
java -Xmx8G -jar server.jar nogui
if exist "plugins\MyServer\.restart" (
echo 재시작 요청 확인 - 5초 뒤 다시 켭니다. 중지하려면 Ctrl+C
timeout /t 5
goto loop
)
echo 서버가 종료되었습니다.
pause하나씩 뜯어볼게요 💡
.restart파일 :/재시작이 만들어요. 그냥/stop으로 끄면 파일이 없으니 다시 안 켜져요.- 루프 시작에서 지우기 : 이전 재시작의 흔적이 남아서 무한 재시작되는 걸 막아요.
- 메모리(
-Xmx) : 서버 사양에 맞게 바꾸세요.
📌 자주 나는 에러
- 모듈이 켜지지 않음 — "권한 노드는 … 로 시작해야 합니다"
원인: 권한 이름 접두사 규칙 위반 → 해결:
myserver.<모듈>.…으로 이름 변경 /리로드후 특정 모듈만 ✗ 원인: 그 모듈 설정 JSON 문법 오류 → 해결: 로그의 줄 번호를 보고 고친 뒤 다시/리로드. 그동안 이전 설정으로 계속 돌아가요- 재시작 후 서버가 안 켜짐
원인: 배치 파일 대신
java -jar로 직접 켰음 → 해결: 루프 배치 파일로 켜기
📌 마무리
오늘은 플러그인 구조와 실제 사건 두 개를 정리했어요.
- jar 하나 + 모듈 13개, 모듈끼리는 core 서비스로만 연결
- 권한 접두사 규칙은 모듈을 꺼 버리는 안전장치 + 합치기 전 자동 검사
1000029는 사망 처리용 내부 피해가 숫자로 보인 것/리로드·/재시작으로 AI 팀이 자주 고쳐도 서버 운영이 편해졌어요
다음 편에서는 숫자 밸런스 이야기예요. 체력·데미지를 한꺼번에 10배로 키운 방법, "30레벨까지 12시간" 경험치 곡선, 그리고 기획팀 AI가 클래스 6종을 시뮬레이션해서 찾아낸 의외의 원인을 다룰게요. 기대해주세요 🙌
이 글은 제가 방향을 정하고 Claude와 함께 정리했어요. 이 서버는 개인 프로젝트이며, 마인크래프트 공식 콘텐츠가 아니고 Mojang·Microsoft와 관계없습니다.
연재 · 5편 중 2번째
AI 팀으로 마크 서버 만들기
- #1AI 팀장 1명 + 팀원 15명으로 MMORPG 플러그인 만드는 구조! 🤖
- #2플러그인 하나에 모듈 13개! 코어 구조와 '1000029 데미지' 사건 🧩지금 읽는 글
- #3숫자 ×10, 12시간 경험치 곡선, 클래스 6종 시뮬레이션! 📊
- #4ChatGPT가 그리고 Claude가 검수하는 리소스팩 파이프라인! 🎨
- #5디스코드 봇으로 개발팀 운영하기! 결정 포럼·명령어 목록·리소스팩 채널 🤝
