시스템 교체
Poly 모드 로더의 시스템은 교체할 수 있는 게임 기능입니다. 시스템마다 활성 구현은 정확히 하나이며, Poly 모드 로더의 기본 구현이거나 한 모드가 명시적으로 선언한 교체 구현입니다.
모드는 Minecraft 클래스를 참조하지 않고 계약 인터페이스만 구현합니다. 게임과의 연결은 Poly 모드 로더의 버전 어댑터가 맡기 때문에, 게임 코드는 어느 모드가 시스템을 교체했는지 알지 못합니다.
현재 시스템은 poly:game_brand(게임이 보고하는 브랜드 문자열) 하나뿐이며, API는 모두 PREVIEW 등급입니다.
개념
타입(dev.polymodloader.api.system.…) | 내용 |
|---|---|
SystemId | 시스템 ID. namespace:path 형식입니다(예: poly:game_brand) |
PolySystem | 시스템 계약 인터페이스임을 나타내는 표식. 모든 계약이 이것을 확장합니다 |
SystemProvider<T> | 구현을 만드는 클래스. T create(SystemContext context) 하나만 구현하면 됩니다 |
SystemContext | create가 받는 정보로 systemId(), side(), providerModId()를 제공합니다(기본 구현이면 poly) |
SystemLookup | 활성 구현을 조회합니다. get(Class<T> contract) |
brand.GameBrandSystem | 첫 시스템 poly:game_brand의 계약 |
선언하는 방법
교체는 poly.mod.json에서만 선언할 수 있고, 실행 중에 코드로 교체하는 API는 없습니다. 어느 모드가 무엇을 바꾸는지 모드 코드가 실행되기 전에 알 수 있어야 하기 때문입니다.
{
"capabilities": ["system.provider.replace"],
"replacesSystems": [
{ "system": "poly:game_brand", "provider": "com.example.poly.brand.ExampleBrandProvider" }
]
}
| 필드 | 내용 |
|---|---|
system | 교체할 시스템 ID |
provider | SystemProvider를 구현한 클래스 이름. 인수 없는 public 생성자가 필요합니다 |
override | 선택 항목이며 기본값은 false입니다. 충돌에서 설명합니다 |
replacesSystems를 쓰려면 기능 권한system.provider.replace가 필요하며, 없으면POLY-METADATA-012오류가 납니다.- 코드에서는 활성 구현을 조회만 할 수 있습니다. 엔트리포인트의
ModContext.systems()가SystemLookup을 돌려줍니다.
GameBrandSystem brand = mod.systems().get(GameBrandSystem.class);
LOGGER.info("brand on this side: {}", brand.brand(mod.side()));
전체 필드는 poly.mod.json 스키마에서 확인하세요.
언제 실행되나
| 단계 | 하는 일 | 오류 |
|---|---|---|
| 충돌 해결 | 모든 모드의 replacesSystems를 보고 시스템마다 활성 제공자를 정합니다. 이때까지 모드 코드는 실행되지 않습니다 | POLY-SYSTEM-001, 002, 004, 005 |
| 시스템 등록 | 제공자를 만들고 create를 메인 스레드에서 한 번 호출합니다. 그 결과를 계약과 시스템별 규칙에 따라 검증합니다 | POLY-SYSTEM-003 |
| 엔트리포인트 | 이 시점부터 mod.systems()로 활성 구현을 조회할 수 있습니다 | — |
| 게임 실행 | 게임이 값을 물을 때마다 버전 어댑터의 훅이 활성 구현을 호출합니다 | POLY-SYSTEM-006 |
create는 모든 모드의 엔트리포인트보다 먼저 실행됩니다. 따라서 제공자는 엔트리포인트에서 준비한 상태에 의존할 수 없습니다. 전체 순서는 생명주기와 스레드에서 설명합니다.
검증에 실패해도 기본 구현으로 조용히 되돌아가지 않고, 시작을 멈추고 문제를 보고합니다.
GameBrandSystem 계약
poly:game_brand는 게임이 스스로를 무엇이라고 보고할지 정합니다. 이 값은 창 제목의 "모드 적용" 표시, 크래시 리포트, 클라이언트에 보내는 서버 브랜드에 나타납니다.
public interface GameBrandSystem extends PolySystem {
SystemId ID = new SystemId("poly:game_brand");
String brand(GameSide side);
}
| 항목 | 규칙 |
|---|---|
| 기본 구현 | poly를 돌려줍니다 |
| 교체 | 전체 교체(REPLACE)만 가능하며, 확장 지점은 없습니다 |
| 스레드 | 아무 스레드에서나 여러 번 호출될 수 있습니다 |
| 상태 | 상태를 두지 말고 빠르게 동작해야 합니다. 이 시스템이 소유하는 데이터는 없습니다 |
| 결과 | 출력 가능한 ASCII 1–64자이며, 앞뒤 공백은 허용되지 않습니다 |
| 검증 | Poly 모드 로더가 시작 전에 CLIENT와 DEDICATED_SERVER 양쪽 값을 확인하며, 규칙에 어긋나면 POLY-SYSTEM-003 오류가 납니다 |
brand는 side를 받으므로 클라이언트와 서버에 서로 다른 값을 줄 수 있습니다. 지금 어느 쪽에서 실행 중인지와 관계없이 두 값 모두 규칙을 지켜야 합니다.
따라 하기: 게임 브랜드 바꾸기
Poly 모드 로더 저장소의 examples/custom-system-mod는 브랜드를 poly/example로 바꿉니다. 이 모드에는 엔트리포인트가 없고, 교체 선언과 제공자 클래스만으로 동작합니다.
1. 의존성
모드 프로젝트의 compileOnly 의존성 poly-lifecycle-api에 시스템 API(poly-system-api)가 이미 들어 있으므로 따로 추가할 것이 없습니다. 저장소 안의 예제는 compileOnly(project(":poly-system-api")) 하나만 사용합니다.
2. 제공자
package com.example.poly.brand;
import dev.polymodloader.api.system.SystemContext;
import dev.polymodloader.api.system.SystemProvider;
import dev.polymodloader.api.system.brand.GameBrandSystem;
/**
* Replaces Poly Mod Loader's {@code poly:game_brand} system. Minecraft then reports {@code poly/example} as its
* brand, without this mod referencing any Minecraft class.
*/
public final class ExampleBrandProvider implements SystemProvider<GameBrandSystem> {
@Override
public GameBrandSystem create(SystemContext context) {
return side -> "poly/example";
}
}
GameBrandSystem은 메서드가 하나뿐이어서 람다로 돌려줄 수 있습니다.- 이 람다는 상태가 없고 항상 같은 값을 돌려주므로, 어느 스레드에서 호출해도 안전합니다.
- 클래스는 public이며 인수 없는 생성자(기본 생성자)를 갖습니다.
3. 선언
{
"schemaVersion": 1,
"id": "custom_system_mod",
"version": "0.1.0",
"name": "Custom System Mod",
"environment": "both",
"minecraft": [
"26.*"
],
"depends": {
"poly": ">=0.1.0-alpha <0.2.0"
},
"capabilities": ["system.provider.replace"],
"replacesSystems": [
{ "system": "poly:game_brand", "provider": "com.example.poly.brand.ExampleBrandProvider" }
]
}
4. 확인
게임을 실행하면 시작 로그에 활성 시스템과 제공자가 표시됩니다.
[Poly Mod Loader] System poly:game_brand: com.example.poly.brand.ExampleBrandProvider from custom_system_mod
게임이 처음 브랜드를 물으면 서버와 클라이언트 각각 한 번씩 다음 줄을 남깁니다.
[Poly Mod Loader] poly:game_brand reported 'poly/example' to Minecraft (CLIENT)
활성 시스템과 제공자는 .poly/startup-report.json에도 기록됩니다. 교체가 없으면 시작 로그가 … from poly (default)로 끝납니다.
Poly 모드 로더 저장소에서는 clientWorldReloadTest가 이 클라이언트 로그를 확인합니다.
충돌
두 모드가 같은 시스템을 교체해도 Poly 모드 로더는 추측하지 않고, 모드 코드가 실행되기 전에 다음 규칙으로 결정합니다.
| 상황 | 결과 |
|---|---|
| 교체 선언 0개 | 기본 구현 |
| 교체 선언 1개 | 그 제공자 |
2개 이상이며, 한 모드가 override: true를 선언하고 다른 교체 모드 모두에 (전이적으로) 의존 | 그 모드가 선택됩니다. 시작 로그에 overriding [...]로 덮어쓴 모드를 남깁니다 |
| 그 밖의 2개 이상 | POLY-SYSTEM-001로 시작이 중단됩니다. 관련 모드 ID와 해결 방법을 함께 보고합니다 |
선택되는 모드가 없는데, override: true를 적었지만 조건을 채우지 못한 모드가 있음 | 위의 POLY-SYSTEM-001과 함께 해당 모드마다 POLY-SYSTEM-005 |
override로 선택되려면 depends에 상대 모드를 적어야 합니다.
{
"id": "my_brand_mod",
"depends": {
"poly": ">=0.1.0-alpha <0.2.0",
"custom_system_mod": "*"
},
"capabilities": ["system.provider.replace"],
"replacesSystems": [
{ "system": "poly:game_brand", "provider": "com.example.mybrand.MyBrandProvider", "override": true }
]
}
POLY-SYSTEM-001이 나면 다음 중 하나로 해결합니다.
- 교체 모드를 하나만 남깁니다.
- 한 모드가 다른 교체 모드 모두에 의존하도록 하고, 그 모드의
replacesSystems항목에"override": true를 적습니다.
오류 코드
| 코드 | 언제 | 결과 |
|---|---|---|
POLY-METADATA-012 | replacesSystems를 썼는데 권한 system.provider.replace가 없음 | 시작 중단 |
POLY-SYSTEM-001 | 같은 시스템을 둘 이상의 모드가 교체하는데 어느 모드를 쓸지 정할 수 없음 | 시작 중단 |
POLY-SYSTEM-002 | 알 수 없는 시스템 ID | 시작 중단 |
POLY-SYSTEM-003 | 제공자 클래스를 만들 수 없음(SystemProvider 미구현, 인수 없는 public 생성자 없음), create 예외, null 반환, 계약 미구현, 시스템별 검증 실패 | 시작 중단 |
POLY-SYSTEM-004 | 교체할 수 없는 시스템임 | 시작 중단 |
POLY-SYSTEM-005 | override가 잘못됨 | 시작 중단 |
POLY-SYSTEM-006 | Poly 모드 로더가 시스템을 연결하기 전에 게임이 브랜드를 물어봄 | IllegalStateException |
전체 목록은 오류 코드에서 볼 수 있습니다.
지금의 제한
- 시스템은
poly:game_brand하나뿐입니다. - 시스템 전체를 교체하는 것만 가능합니다. 기본 구현에 덧붙이는 확장(
EXTEND)은 없습니다. - 교체 지점이 여러 개인 시스템(예: 레시피의 등록, 조회, 동기화를 따로 교체)은 아직 없습니다.
- 시스템 사이의 의존성과 초기화·종료 순서는 아직 지원하지 않습니다.
- 브랜드를 게임에 연결하는 변환 규칙은 선택 기능입니다. 게임 jar가 예상과 다르면 시작을 멈추지 않고 그 규칙만 끄며, 이때 게임은 원래 브랜드를 보고합니다.
- 개발 실행(
runClient등)에는custom-system-mod가 항상 포함되므로, Poly 모드 로더 저장소에서 개발할 때는 게임 브랜드가poly/example로 보입니다.
교체할 수 있는 시스템의 종류는 Poly 모드 로더가 정합니다. 지금은 poly:game_brand 하나이며, 시스템이 늘어나면 이 페이지에 추가합니다.