⚠️ **블로그 컨셉상 반어체가 들어가니 양해바랍니다
**
🎮 코블버스 미티컬 월드팩, 버그 잡고 애드온까지 (feat. 모바일)
지난 포스트에서 서버 구축 및 클라 접속까지 알아보았다.
오늘 포스트에서는 지금까지 서버맨이 코블버스 미티컬 월드팩을 돌려보면서 발견한 버그(?)와 해결법, 애드온 사용법, 마크 명령어, 마지막으로 모바일로 코블버스 하는 법을 알아보겠다.
이제부터 기술할 내용은 서버맨의 방식이며 다른 서버에서는 이렇게 안 할 수도 있다
1️⃣ 래디컬레드 활성화 패치 (feat. 아키라)

첫번째로 해야할 패치는 래디컬레드 활성화 패치이다.
사진에 보다시피 NPC Akira가 처음 Kanto(관동) 지방으로 워프시켜줘야하는데 아무리 해도 관동지방으로 보내주지 않는다. 포켓몬을 받고 기본으로 주어지는 트레이너 카드를 열어보면 Johto(성도), Sinnoh(신오), Hoenn(호연), Normal, Rival .. 등등 여러 이벤트를 활성화 하기 위한 카드들이 있다. 근데 이상태에서 아무리 해도 진행이 안돼서 처음에는 op SilverRuler 해서 creative 모드로 해서 가까운 지역으로 날라가서 강제로 게임을 시작했다. 근데 이렇게 해도 결국에는 배타는 곳에서 NPC가 워프 안해주고, 바위마을 체육관 웅도 선행조건이 만족하지 않았다며 배틀을 해주지 않는다.
그래서 몇번이고 게임을 초기화 시켜서 스폰지역에서 이거저거 다 해보았지만 되지 않았다.

힌트는 스폰지역의 Instructor Arts 라는 NPC에게서 얻을 수 있었는데
... don't forget to select Radical Red mode ...
라는 대사를 한다. 근데 이것도 도저히 방법이 없었다. 진짜 할 수 있는건 다 눌러봤지만 래디컬레드 모드를 선택할 방법을 찾지 못했다. 그러면 어쩌겠는가. 당연히 코드를 수정해야지 ㅋ 이부분은 그냥 파이썬 스크립트를 통으로 공유한다.
📌 A. RCT 데이터팩 내부의 선행 강제 락 해제
📄 코드 보기 (클릭)
cat << 'EOF' > fix_radicalred_lock.py
import zipfile
import json
import os
import shutil
zip_path = "/root/cobblemon-server/datapacks/COBBLEVERSE-RCT-DP-v19.zip"
temp_dir = "/tmp/rct_extract"
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
os.makedirs(temp_dir)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
json_path = os.path.join(temp_dir, "data/rctmod/series/radicalred.json")
if os.path.exists(json_path):
with open(json_path, 'r') as f:
data = json.load(f)
data["requiredSeries"] = []
with open(json_path, 'w') as f:
json.dump(data, f, indent=2)
os.remove(zip_path)
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_write:
for root, dirs, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, temp_dir)
zip_write.write(file_path, arcname)
shutil.rmtree(temp_dir)
print("Datapack lock patched successfully!")
EOF
python3 fix_radicalred_lock.py
📌 B. 최초 스타터 지급 시 kanto가 아닌 radicalred가 활성화되도록 코블버스 데이터팩 수정
📄 코드 보기 (클릭)
cat << 'EOF' > fix_starter_pack.py
import zipfile
import os
import shutil
zip_path = "/root/cobblemon-server/datapacks/COBBLEVERSE-DP-v29.zip"
temp_dir = "/tmp/cv_extract"
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
os.makedirs(temp_dir)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
func_path = os.path.join(temp_dir, "data/cobbleverse/function/starter_pack.mcfunction")
if os.path.exists(func_path):
with open(func_path, 'r') as f:
content = f.read()
target = "rctmod player set series kanto @s"
replacement = "rctmod player set series radicalred @s"
content = content.replace(target, replacement)
with open(func_path, 'w') as f:
f.write(content)
os.remove(zip_path)
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_write:
for root, dirs, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, temp_dir)
zip_write.write(file_path, arcname)
shutil.rmtree(temp_dir)
print("Datapack starter pack patched successfully!")
EOF
python3 fix_starter_pack.py
그리고 다 하고 /root/cobblemon-server/config/rctmod-server.toml 설정 파일을 vi로 열어 156번째 줄의 initialSeries 값을 "radicalred"로 변경해야 한다.
2️⃣ 체육관 NPC 배틀 가능 패치
다음 스크립트는 체육관 NPC 배틀 가능 패치이다.
📄 코드 보기 (클릭)
cat << 'EOF' > patch_kanto_advancements.py
import zipfile
import json
import os
import shutil
zip_path = "/root/cobblemon-server/datapacks/COBBLEVERSE-DP-v29.zip"
temp_dir = "/tmp/cv_extract_adv"
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
os.makedirs(temp_dir)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
mapping = {
"kanto_brock": ["kanto_brock", "leader_brock_019e"],
"kanto_misty": ["kanto_misty", "leader_misty_019f"],
"kanto_ltsurge": ["kanto_ltsurge", "leader_lt_surge_01a0"],
"kanto_erika": ["kanto_erika", "leader_erika_01a1"],
"kanto_koga": ["kanto_koga", "leader_koga_01a2"],
"kanto_sabrina": ["kanto_sabrina", "leader_sabrina_01a4"],
"kanto_blaine": ["kanto_blaine", "leader_blaine_01a3"],
"kanto_giovanni": ["kanto_giovanni", "leader_giovanni_015e"],
"kanto_league_lorelei": ["kanto_league_lorelei", "elite_four_lorelei_004d", "elite_four_lorelei_004e"],
"kanto_league_bruno": ["kanto_league_bruno", "elite_four_bruno_0050", "elite_four_bruno_0051"],
"kanto_league_agatha": ["kanto_league_agatha", "elite_four_agatha_0053", "elite_four_agatha_0054"],
"kanto_league_lance": ["kanto_league_lance", "elite_four_lance_0056", "elite_four_lance_0057", "elite_four_lance_019d", "elite_four_lance_02e2"],
"kanto_champion_blue": ["kanto_champion_blue", "champion_terry_01b6", "champion_terry_01b7", "champion_terry_01b8"]
}
adv_dir = os.path.join(temp_dir, "data/cobbleverse/advancement/trainer/kanto")
if os.path.exists(adv_dir):
for f_name in os.listdir(adv_dir):
if f_name.endswith(".json"):
file_path = os.path.join(adv_dir, f_name)
with open(file_path, 'r') as f:
data = json.load(f)
modified = False
criteria = data.get("criteria", {})
for crit_name, crit_data in criteria.items():
conds = crit_data.get("conditions", {})
if "trainer_ids" in conds:
original_ids = conds["trainer_ids"]
new_ids = []
for t_id in original_ids:
if t_id in mapping:
new_ids.extend(mapping[t_id])
else:
new_ids.append(t_id)
new_ids = list(dict.fromkeys(new_ids))
conds["trainer_ids"] = new_ids
modified = True
if modified:
with open(file_path, 'w') as f:
json.dump(data, f, indent=2)
os.remove(zip_path)
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_write:
for root, dirs, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, temp_dir)
zip_write.write(file_path, arcname)
shutil.rmtree(temp_dir)
print("Datapack advancements patched successfully!")
EOF
python3 patch_kanto_advancements.py
여기까지하면 이제 아키라가 Kanto(관동) 지방으로 워프시켜주고 기본적인 NPC와의 배틀이 활성화된다.
이 현상은 코블버스의 Kanto와 미티컬 월드팩 제작자의 RadicalRed가 충돌이 일어나는 것으로 예상된다..
3️⃣ 전설/환상 포켓몬 포획 발전과제 버그 패치

두번째로 해야할 것은 관동지방의 전설/환상 포켓몬 포획 발전과제 버그 패치이다.
이 부분은 간혹가다가 바위 마을에서 프리져가 출몰하길래 잡았는데 진행이 안되어서 찾다보니 이 발전과제는 코드상에서 Shiny(이로치)를 잡아야만 다음 퀘스트가 잠금 해제되는 구조를 가지고 있었다.
일단 이것은 실제 이로치 프리져를 만날 확률도 극히 적으며, 강제로 이로치 프리져를 spawn 시킬 방법도 모르겠고 해서 발전과제의 레벨을 낮췄다라고 보면 되겠다.
다음의 스크립트는 발전과제의 Shiny를 Normal로 경감시키는 스크립트이다.
📄 코드 보기 (클릭)
cat << 'EOF' > patch_pokemon_advancements.py
import zipfile
import json
import os
import shutil
zip_path = "/root/cobblemon-server/datapacks/COBBLEVERSE-DP-v29.zip"
temp_dir = "/tmp/cv_extract_poke"
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
os.makedirs(temp_dir)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
poke_adv_dir = os.path.join(temp_dir, "data/cobbleverse/advancement/pokemon")
if os.path.exists(poke_adv_dir):
for f_name in os.listdir(poke_adv_dir):
if f_name.endswith(".json"):
file_path = os.path.join(poke_adv_dir, f_name)
with open(file_path, 'r') as f:
data = json.load(f)
modified = False
# 1. 극악의 이로치 잠금 해제 (일반 전설몬들끼리 1선 연계)
if f_name == "catch_zapdos.json":
data["parent"] = "cobbleverse:pokemon/catch_articuno"
modified = True
elif f_name == "catch_moltres.json":
data["parent"] = "cobbleverse:pokemon/catch_zapdos"
modified = True
# 2. 코블몬 aspects_collected 빈 배열 오작동 우회 (genderless 속성 적용)
criteria = data.get("criteria", {})
for crit_name, crit_data in criteria.items():
trigger = crit_data.get("trigger")
if trigger == "cobblemon:catch_pokemon" or trigger == "cobblemon:aspects_collected":
crit_data["trigger"] = "cobblemon:aspects_collected"
conds = crit_data.get("conditions", {})
if f_name.endswith("_shiny.json"):
original_aspects = conds.get("aspects", [])
new_aspects = [a for a in original_aspects if a == "shiny"]
if "shiny" not in new_aspects:
new_aspects.append("shiny")
conds["aspects"] = new_aspects
modified = True
else:
conds["aspects"] = ["genderless"]
modified = True
if modified:
with open(file_path, 'w') as f:
json.dump(data, f, indent=2)
os.remove(zip_path)
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_write:
for root, dirs, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, temp_dir)
zip_write.write(file_path, arcname)
shutil.rmtree(temp_dir)
print("Datapack pokemon advancements patched successfully!")
EOF
python3 patch_pokemon_advancements.py
4️⃣ 포켓몬 센터 간호순 NPC 치료 패치

그다음 마지막 패치는 포켓몬 센터의 간호순이 치료를 안해주는 걸 패치해야한다.
이부분은 미티컬 월드팩에 기본적으로 배치되어 있는 포켓몬 간호순이 치료를 안해주고, spawnnpc nurse_joy로 한 간호순 NPC는 치료를 해준다. 두 간호순 NPC는 NBT(Named Binary Tag)가 다르다.
웬만하면 손을 들고 있고, 플레이어를 응시하는 기본 nurse joy로 하고 싶은데 도저히 해결 방법을 찾지 못해서 모든 지역의 포켓몬 센터에 있는 기본 간호순 joy를 spawnnpc joy로 교체하는 스크립트이다.
📄 코드 보기 (클릭)
cat << 'EOF' > patch_nurse_joys.py
import os
import zlib
import struct
import io
import nbtlib
entities_dir = "/root/cobblemon-server/world/entities"
# 간호사 조이 NBT 데이터를 새로운 포켓몬 회복 대사 및 기능으로 변경하는 함수
def patch_nurse_nbt(entity):
entity["CustomName"] = nbtlib.String('{"text":"간호사 조이","color":"#FF69B4"}')
entity["DialogData"] = nbtlib.Compound({
"Type": nbtlib.String("BASIC"),
"DialogDataSet": nbtlib.List[nbtlib.Compound]([
nbtlib.Compound({
"Texts": nbtlib.List[nbtlib.Compound]([
nbtlib.Compound({
"Text": nbtlib.String("안녕하세요! 포켓몬 센터입니다. 당신의 포켓몬들을 치료해 드릴게요.")
})
]),
"Name": nbtlib.String("기본 대사"),
"Label": nbtlib.String("default")
})
])
})
entity["ActionData"] = nbtlib.Compound({
"ActionEventSet": nbtlib.Compound({
"ON_INTERACTION": nbtlib.List[nbtlib.Compound]([
nbtlib.Compound({
"Type": nbtlib.String("EXECUTE_COMMAND"),
"Command": nbtlib.String("execute as @p run pokeheal")
}),
nbtlib.Compound({
"Type": nbtlib.String("OPEN_DEFAULT_DIALOG"),
"Id": nbtlib.IntArray([-1312345678, 1122334455, -1556677889, 998877665])
})
])
})
})
def patch_mca_file(file_path):
modified = False
with open(file_path, "rb") as f:
data = f.read()
offsets = list(struct.unpack(">1024I", data[:4096]))
out_data = bytearray(data)
modified_chunks = 0
for idx, offset in enumerate(offsets):
if offset == 0:
continue
sector_offset = (offset >> 8) * 4096
sector_count = offset & 0xFF
f_in = io.BytesIO(data[sector_offset : sector_offset + sector_count * 4096])
length = struct.unpack(">I", f_in.read(4))[0]
compr_type = f_in.read(1)[0]
compressed_data = f_in.read(length - 1)
try:
decompressed = zlib.decompress(compressed_data)
nbt_file = nbtlib.File.from_fileobj(io.BytesIO(decompressed))
except Exception:
continue
chunk_modified = False
entities = nbt_file.get("Entities", [])
for ent in entities:
# 간호사 조이 혹은 힐러 기능을 가진 NPC 식별
if ent.get("id") in ["easy_npc:humanoid", "easy_npc:humanoid_slim"]:
dialog = str(ent.get("DialogData", ""))
action = str(ent.get("ActionData", ""))
if "nurse" in dialog.lower() or "heal" in action.lower() or "nurse" in action.lower():
patch_nurse_nbt(ent)
chunk_modified = True
print(f"-> Patched Nurse Joy NPC at Pos: {list(ent.get('Pos', []))}")
if chunk_modified:
modified = True
f_out = io.BytesIO()
nbt_file.write_to(f_out)
new_decompressed = f_out.getvalue()
new_compressed = zlib.compress(new_decompressed)
new_length = len(new_compressed) + 1
chunk_buffer = bytearray()
chunk_buffer += struct.pack(">I", new_length)
chunk_buffer += struct.pack(">B", compr_type)
chunk_buffer += new_compressed
# 4096 바이트 배수 맞추기 패딩
padding = 4096 - (len(chunk_buffer) % 4096)
if padding < 4096:
chunk_buffer += bytes(padding)
new_sectors = len(chunk_buffer) // 4096
if new_sectors == sector_count:
out_data[sector_offset : sector_offset + len(chunk_buffer)] = chunk_buffer
else:
# 섹터 크기가 커질 시 슬롯 재조정 간편 처리
pass
modified_chunks += 1
if modified:
with open(file_path, "wb") as f:
f.write(out_data)
print(f"Successfully patched {modified_chunks} chunks in {os.path.basename(file_path)}")
for file_name in os.listdir(entities_dir):
if file_name.endswith(".mca"):
path = os.path.join(entities_dir, file_name)
if os.path.getsize(path) > 0:
patch_mca_file(path)
print("간호사 NPC 치료 기능 전역 복구 완료.")
EOF
python3 patch_nurse_joys.py
의문인 점은 상점에 있는 NPC나 다른 NPC들은 이런 패치 없이 정상적으로 작동한다.
아마 이것도 기본 코블버스의 Kanto와 미티컬 월드팩의 래디컬레드가 충돌이 일어나는 것으로 파악된다.
여기까지 왔다면 이제 플레이는 정상적으로 할 수 있다(서버맨도 미티컬 월드팩의 모든 컨텐츠를 다 플레이하진 못했다).
이후에 발생하는 문제가 있다면 그냥 AI에게 물어보자
5️⃣ 코블버스에 없는 애드온 사용법
이 Cobblemon Simple Pokecenter는 서버/클라 양측에 모두 설치되어 있어야 하며, 이전 포스트에서 서버맨의 가이드를 그대로 따라왔다면 client.zip 파일에 들어 있을 것이다.

서버맨의 모드린스처럼 코블버스 인스턴스를 눌러서 밑에 내렸을때 사진과 같이 모드팩들이 설치되어있어야 한다.
기본적으로 한/영 문자 자동인식 모드팩 / Jade, Neat (마크에서의 기본적인 오브젝트 인식 뷰 모드팩) / Cobblemon Detail Viewer (상단에 포켓몬 상세정보표시) / Simple Voice Chat이 들어 있다.
생각해보니 저번 포스팅에서 서버측에 Simple Voice Chat을 설치 안했는데 다음 스크립트를 서버측에서 실행한다.
📄 코드 보기 (클릭)
cat > voice.py <<'EOF'
import os
import sys
import json
import urllib.request
SERVER_DIR = "/root/cobblemon-server"
MOD_SLUG = "simple-voice-chat"
MOD_FILENAME = "voicechat-fabric-1.21.1-2.6.20.jar"
headers = {"User-Agent": "Mozilla/5.0 Antigravity Voice Setup"}
def api_get(url):
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except Exception as e:
print(f"Error fetching {url}: {e}", file=sys.stderr)
return None
def download_voice_chat():
print(f"--- [Voice Chat] Downloading {MOD_FILENAME} ---")
proj_data = api_get(f"https://api.modrinth.com/v2/project/{MOD_SLUG}")
if not proj_data:
print(" ERROR: Could not get project data for simple-voice-chat.")
return False
project_id = proj_data["id"]
url = f"https://api.modrinth.com/v2/project/{project_id}/version"
versions = api_get(url)
if not versions:
print(" ERROR: No versions found for Simple Voice Chat.")
return False
target_url = None
for v in versions:
for f in v.get("files", []):
if f.get("filename") == MOD_FILENAME:
target_url = f["url"]
break
if target_url:
break
if not target_url:
print(f" WARNING: Could not find exact filename '{MOD_FILENAME}' on Modrinth. Downloading latest fallback...")
target_url = versions[0]["files"][0]["url"]
filename = versions[0]["files"][0]["filename"]
else:
filename = MOD_FILENAME
mods_dir = os.path.join(SERVER_DIR, "mods")
os.makedirs(mods_dir, exist_ok=True)
target_path = os.path.join(mods_dir, filename)
if os.path.exists(target_path):
print(f" {filename} is already installed in {mods_dir}.")
else:
print(f" Downloading {filename} from {target_url}...")
urllib.request.urlretrieve(target_url, target_path)
print(f" Successfully installed {filename}!")
return True
def show_activation_guide():
config_path = os.path.join(SERVER_DIR, "config/voicechat/voicechat-server.properties")
print("\n" + "=" * 60)
print(" [Voice Chat 활성화 및 설정 안내]")
print("=" * 60)
print(" 1. 포트 설정 (UDP):")
print(" - Simple Voice Chat은 마인크래프트 포트(TCP 25565)와 별개로 UDP 포트를 사용합니다.")
print(" - 기본 포트는 '24454' (UDP) 입니다.")
print(f" - 현재 설정 파일 경로: {config_path}")
if os.path.exists(config_path):
print(" - 현재 설정된 포트:")
with open(config_path, "r") as f:
for line in f:
if line.strip().startswith("port="):
print(f" -> {line.strip()}")
else:
print(" - (아직 서버가 실행되지 않아 설정 파일이 없습니다. 첫 실행 시 생성됩니다.)")
print("\n 2. 방화벽 허용 (클라우드/서버 방화벽 설정 필수!):")
print(" - Voice Chat을 정상적으로 이용하려면 서버 호스팅사 콘솔(예: GCP, AWS, Oracle Cloud 등)에서")
print(" '24454 포트'를 'UDP 프로토콜'로 인바운드 허용해야 합니다.")
print(" - 리눅스 내부 방화벽(ufw)을 사용 중인 경우:")
print(" sudo ufw allow 24454/udp")
print("=" * 60)
def main():
print("=" * 50)
print(" Simple Voice Chat 서버 설치 도구")
print("=" * 50)
if download_voice_chat():
show_activation_guide()
if __name__ == "__main__":
main()
EOF
python3 voice.py
클라이언트의 모드팩 내용물 확인은 이만하면 됐다고 치고, 다시 서버로 돌아가서 모드린스 discover 페이지나 커스포지의 공유 사이트에서 직접 필요한 모드팩들을 검색 후 다운 받는 방법도 있지만 지금은 ferium이란걸 사용해 볼거다. 모든 모드팩이 페리엄에 올라와 있는건 아니지만 연습용으로 쓸만한 건 대부분 올라와 있다.
먼저 설치부터
curl -LO https://github.com/gorilla-devs/ferium/releases/latest/download/ferium-linux-nogui.zip
그후 폴더 이동하고
cd ~/cobblemon-server
페리엄 프로필을 생성한다
ferium profile create
정상적으로 잡혔나보고
ferium profile list
add하고 upgrade 해주면 된다.
ferium add cobblemon-simple-pokecenters
ferium upgrade
근데 이때 페리엄은 자신의 패키지 서버에서 다운하지 않은 모든 모드들을 .old라는 곳으로 보내는 특징이 있어서 페리엄으로 포켓센터 설치 했다면 반드시 우리 모드팩들을 원래의 디렉터리로 옮겨줘야한다.
mv -n /root/cobblemon-server/mods/.old/* /root/cobblemon-server/mods/
rm -rf /root/cobblemon-server/mods/.old/
애드온으로 쓸 포켓센터는 준비되었고 포켓센터 소환 명령어를 할려면 op 모드가 있어야 하므로 마크 명령어 설명을 하면서 동시에 진행하겠다.
6️⃣ 마인크래프트 명령어(관리자) 총정리

이제 마크의 명령어(관리자)를 설명한다. 마크 서버가 돌아가고 있는 tmux 세션으로 들어가서
op 유저명을 입력하면 그 유저는 operator(권한 4)가 된다.
op가 되었다면 인게임에서 채팅으로 모든 명령어를 입력할 수 있다. 또한 미니맵에서 우클릭으로 어디든지 텔레포트가 가능하다.
먼저 채팅창을 열고
gamemode creative 유저명
라고 한다. (이때 tab을 활용해서 치면 빨리 칠 수 있다)
이러면 이때부터 스페이를 두번 치면 (점프를 두번) 비행을 시작할 수 있다.
또한 크리에이티브 모드는 밤의 야생 몬스터로 부터 공격을 받지 않는다.

그리고 e를 눌러서 아이템 검색을 하고 마음대로 아이템을 소지할 수 있다.

채팅창에
/spawnpokemon charizard
하면 리자몽이 소환된다. (마스터볼로 잡자)
포켓몬 소환은 영어로 해야하는데 영어명칭은 구글링을 통해서 알아내자. 망나뇽, 리자몽, 갸라도스 같은 일부 탑승할 수 있는 포켓몬은 포켓몬을 꺼낸 뒤에 컨트롤(앉기)+우클릭 하면 탑승을 할 수 있다. 혹은 아이템을 줄 수 있다(대표적으로 메가 스톤줄때).
※ 추가 명령어 기록
도전과제 전설포켓몬 잡기 (썬더) 강제 clear 방법
/advancement grant <player> only cobbleverse:pokemon/catch_zapdos
지역락 해제 명령어. freedom은 모든 지역 (예시. kanto, hoenn .. )
/rctmod player set series freeroam <player>
특정 NPC 소환 (예시. misty : 이슬이)
/rctmod trainer summon_persistent leader_misty_0030
특정 NPC 처치 횟수 1 설정
/rctmod player set defeats leader_misty_0030 <player> 1
강제 특정 스토리 진행 명령어 (예시. 웅이 클리어 직후)
/rctmod player set progress after leader_brock_0038 <player>
레벨 제한 풀기 ( 아닐수도)
/rctmod player set series radicalred <player> completed 1
🕐 시간
개인적으로 밤 되면 어두워서 돌아다니기가 힘들다.
/time set day
/gamerule doDaylightCycle false
첫번째 명령어로 시간을 낮으로 세팅하고, 두번째 명령어로 시간을 고정시킬 수 있다.
🌦️ 날씨
가끔 비오고 눈오고 그러는데 그것도 매우 귀찮다..
/weather clear
/gamerule doWeatherCycle false
위와 똑같은 원리의 명령어이다.
📍 텔레포트(tp)

/locate structure cobbleverse:brock
를 치면 가장 가까운 웅이의 위치를 알려준다. 이때 채팅창에서 클릭을 누르면 알아서 밑에 tp 명령어가 생긴다. 그렇게 이동해도 되고 명령어로 위치를 정확하게 조절할 수 있다.
보통 640, ~, 2832 이렇게 나올때 ~는 플레이어의 높낮이 표시라서 대충 100 정도 하면 된다.(그냥 클릭하자)

/tp @s 640 100 2832
🏥 포켓센터 소환
이제 아까 설치한 ferium의 Cobblemon Simple Pokecenter를 사용해보자. 방법은 간단하다 place 명령어이다. 대신 이거 하기 전에 숲 같은 곳에 가서 기존의 만들어진 월드들이 뭉개지지 않도록 하자.

/place structure cobblemon_simple_center:pokecenter
이 한줄이면 보다시피 펑 하고 포켓몬 센터가 튀어나온다.
7️⃣ 유저 데이터 초기화 스크립트
그리고 마지막으로 여러모로 혼자 테스트를 하면서 이 코블버스에서 나의 데이터를 초기화 해야할 일이 많았는데 그럴때마다 서버 자체를 지우는게 아니라 월드팩의 아래 데이터만 지우면 된다.
| 구분 | 위치 |
|---|---|
| 1. 마인크래프트 순정(Vanilla) 플레이어 데이터 | world/playerdata/[UUID].dat |
| 2. 어드밴스먼트 | world/advancements/[UUID].json |
| 2. 플레이 시간/처치 통계 | world/stats/[UUID].json |
| 3. 코블몬 모드 전용 포켓몬 데이터 | world/cobblemon/players/[UUID].json |
| 4. 기타 모드 데이터 (RCT, Sophisticated Backpacks 등) | world/rctmod/ 등 모드 폴더 하위 UUID 매핑 파일 |
을 지워야 하는데 (개귀찮으니까) 다음의 스크립트를 통해 한번에 초기화 하자.
사용법 : python3 resetUser.py 유저명
📄 코드 보기 (클릭)
import os
import sys
import json
import subprocess
SERVER_DIR = "/root/cobblemon-server"
USERCACHE_PATH = os.path.join(SERVER_DIR, "usercache.json")
def main():
if len(sys.argv) < 2:
username = input("Enter the Minecraft username to reset: ").strip()
else:
username = sys.argv[1].strip()
if not username:
print("Error: Username cannot be empty.")
sys.exit(1)
uuid = None
# 1. Lookup UUID from usercache.json
if os.path.exists(USERCACHE_PATH):
try:
with open(USERCACHE_PATH, 'r') as f:
cache = json.load(f)
for entry in cache:
if entry.get("name", "").lower() == username.lower():
uuid = entry.get("uuid")
username = entry.get("name") # normalize casing
break
except Exception as e:
print(f"Error reading usercache.json: {e}")
# 2. If not found in cache, check if input is a valid UUID format
if not uuid:
# Check if username itself is a UUID
clean_name = username.replace("-", "")
if len(clean_name) == 32:
uuid = username
else:
print(f"Error: Could not find UUID for user '{username}' in usercache.json.")
sys.exit(1)
print(f"Target player: {username} (UUID: {uuid})")
# Check if run non-interactively
if len(sys.argv) < 2:
confirm = input(f"Are you sure you want to completely wipe player data for {username}? (y/N): ").strip().lower()
if confirm != 'y':
print("Aborted.")
sys.exit(0)
# 3. Stop server
print("Stopping Minecraft server to prevent data caching issues...")
subprocess.run(["tmux", "kill-session", "-t", "cobblemon"])
subprocess.run(["pkill", "-9", "-f", "fabric-server-launch.jar"])
subprocess.run(["pkill", "-9", "-f", "server.jar"])
subprocess.run(["pkill", "-9", "-f", "java"])
# 4. Find and delete player files
print(f"Wiping files matching UUID: {uuid}")
world_dir = os.path.join(SERVER_DIR, "world")
deleted_count = 0
if os.path.exists(world_dir):
for root, dirs, files in os.walk(world_dir):
for file in files:
if uuid in file:
file_path = os.path.join(root, file)
try:
os.remove(file_path)
print(f" Removed: {os.path.relpath(file_path, world_dir)}")
deleted_count += 1
except Exception as e:
print(f" Failed to remove {file_path}: {e}")
print(f"Successfully deleted {deleted_count} files.")
# 5. Restart server
print("Restarting Minecraft server in tmux...")
subprocess.run(["tmux", "new-session", "-d", "-s", "cobblemon", "./start.sh"], cwd=SERVER_DIR)
print("Done! Server is starting up again.")
if __name__ == "__main__":
main()
8️⃣ 📱 모바일에서 코블버스(모드팩) 하는 법
다음은 모두가 이 포스트에서 가장 관심 가질 법한(?) 모바일에서 마크(모드팩) 하는 방법이다.
플레이 스토어에서 마인크래프트를 팔고 있지만 이건 베드락 에디션이다. 베드락 에디션은 쉽게 말해 다양한 디바이스 플랫폼에서 플레이할 수 있도록 나온 자바에디션(JAVA)의 차세대 버전(C++)이라고 생각하면 되겠다. 플스 / XBOX / 스위치 / iOS / 안드로이드 / 윈도우 등과 같이 거의 모든 플랫폼에서 즐길 수 있다.
하지만 이유는 모르겠지만 베드락 에디션 하는 사람을 거의 본 적이 없다 난..
아무튼 간에 자바에디션을 모바일에서 하는 것을 포자브런쳐 팀이 만들었고 근데 25년 9월 24일에 중단하게 되었고,
그것을 포크하여 엔젤아우라MC팀이 아메시스트로 이름 바꿔서 지속적으로 개발중이다.
그밖에 모조런처 라는 이상한 구데기 앱이 있는데 설치해보니 시스템 UI 런쳐를 강제로 바꾸게 하는 황당한 요구를 하길래 바로 칼삭했다.
일단 포자브런쳐가 근본인거 같아서 설치를 하고 테스트해본 결과 코블버스는 실행이 불가능했다.
다행히 이후 포크 개발된 아메시스트가 지원한다.
➡️ Amethyst-Android 1.1.5 다운로드
여기에 가서 Amethyst.apk를 다운받는다. debug 버전 말고!

설치하고 MS 로그인을 한후에 위로 화살표 모양 버튼을 눌러서 Create new profile을 한다. (이게 모드린스의 인스턴스 설치이다)
그러면 밑에 '모드팩 프로필 생성'이란게 있는데 그걸 눌러주고

cobbleverse라고 (여기선 소문자로 검색해도 검색된다) 검색해준다.
그리고 포스트 작성일 260706 기준으로 그냥 설치 누르면 알아서
패브릭 로더 0.18.4에 마크는 1.21.1 코블버스의 버전은 1.731로 설치되는데,
이후에 이 글을 따라하는 사람이라면 일단 이 버전에 맞게 설치한다.
다른 버전으로 설치해보니 실행이 안되거나 튕긴다

그러고 일단 실행을 하자. 로딩이 상당히 걸린다. (폰의 스펙이 되는 사람은 아메시스트 설정에서 램 사이즈를 조절해도 된다)
대략 서버맨의 플립7 기준으로 위에 보는 사진의 로딩까지 다 끝나고 메인화면 뜨는데 까지 약 40초~60초 남짓 걸린다.

메인화면에서 상당히 버벅이므로 언어 설정 같은걸 만질 수가 없다.
일단은 싱글플레이로 시작을 해서 온전히 게임안에 들어가지는지 확인부터한다. 확인 되었으면

그러면 이제 우리가 만든 서버로 들어가기 위해선 클라이언트 측인 이 아메시스트 앱에도 모드팩들을 설치해줘야한다.
근데 이때 아메시스트의 data폴더의 내부로 들어가서 인스턴스 폴더의 mods 폴더에 PART2 포스트에서 받은 client.zip을 넣어줘야하는데 기본적인 갤럭시의 내 파일 앱은 보안상의 이유로 /android/data 폴더 내부를 볼 수 없다.
아마 실수로 컴맹유저가 그 안의 폴더를 지우고 OS가 꼬이는 일을 방지하기 위해서
어느순간부터 구글의 정책과 원UI 정책에서 data 폴더 내부를 볼 수 없게 만들었는데,
이때 사용해야할 개념이 [Android] Storage Access Framework(SAF) 이다.
구글 플레이 스토어에 files라고 검색하면 두번째 쯤에 뜨는 marc apps & software 개발팀이 만든 Files 앱이 있다. 이 파일은 아무것도 아니다. 그냥 원래 안드로이드 폰이라면 내장되어있는(하지만 숨겨둔) Files 앱을 그냥 소프트 링크형식으로 들어갈 수 있게 Wrapper 역할만 해주는 앱이다.
아무튼 이걸 실행해서 client.zip의 jar 파일들을 전부다
amethyst/custom_instances/cobbleverse_-_pokemon_adveture.../mods
에 풀어준다. 마운트된 아메시스트말고 정확하게 위치를 말하자면
/Android/data/org.angelauramc.amethyst/files/custom_instances/이하동일
하나 더 해줘야하는게 있는데 인스턴스폴더에 options.txt가 있다. 이것을 클릭해서 열고 (폰에서 안열리면 스토어에서 텍스트 편집기 라는 앱을 받아라) 좀 내리다보면 226번째 줄에 lang이라는 부분이 있는데 이걸 lang:ko_kr로 바꿔주고 저장한다. 그러면 앱을 켜서 자동으로 한글로 바뀌어있다.
그리고 다시 인스턴스(아메시스트 프로필)를 켜서 실행후에 멀티플레이어 누르고 우리가 만든 서버로 접속하면 다음과 같이 화면이 잘 나온다.

처음에는 좀 버벅이지만 한 1분만 지나면 로딩이 끝나서 플립7 기준 아주 준수하게 게임이 돌아간다.
참고로 말하면 안드로이드 자체가 JVM 환경이라서 일반적인 LD플레이어 같은 앱플레이어로 실행하는거보다 당연히 더 원활하게 돌아가는 원리이다.
마인크래프트 코블몬(코블버스) 설치 주제는 이걸로 끝이다.
다음에 또 재밌는 모드팩이 나온다면 웬만하면 패브릭은 했으니 Forge 위주로 해볼 생각이다.
## 언제까지 개발하고 운영할진 모르겠지만 서버맨의 코블버스 미티컬 팩 서버 주소 공개!! ##
(언제든지 파기하고 언제든지 섭종 될 수 있습니다)
devmc.silverruler.xyz
질문은 언제나 환영 🙌
🛰️ 서버맨의 공간들
'내가 아는 것들.. > 마인크래프트' 카테고리의 다른 글
| [마크] 친구와 같이 마크를 하자 PART 2 (하마치 & 제로티어 편) (0) | 2026.08.05 |
|---|---|
| [마크] 친구와 같이 마크를 하자 PART1 (토스편) (0) | 2026.07.27 |
| [마크] 마인크래프트 코블몬 구축 PART2 (실제 구축) (0) | 2026.07.03 |
| [마크] 마인크래프트 코블몬 구축 PART1 (개념 설명) (0) | 2026.06.30 |