가이드
Kotlin과 Java
대형 모드를 위한 선택입니다. CraftScript와 같은 API에 정적 타입, 완전한 IDE, 모든 Java 라이브러리가 더해지고, 지금은 여기에만 있는 기능도 몇 가지 있습니다. 이 가이드는 Kotlin을 사용합니다. Java도 똑같이 작동하며 마지막에 보여 드립니다.
할 수 있는 것
| 분야 | API에서 | CraftScript에서 |
|---|---|---|
| 아이템, 블록, 조합법 | ContentBuilder: item, block, shaped, shapeless, smelting |
예 |
| 몹 | ContentBuilder.creature: 몸, 텍스처, 능력치, Behaviour, TargetRule, 전리품, 자연 스폰, 길들이기, 번식, 타기, 사용자 정의 행동 |
예 |
| 소리와 입자 | ContentBuilder.sound, ContentBuilder.particle |
예 |
| 월드 생성 | ContentBuilder: ore, tree, patch, structure(풀, plan/build), biome, dimension, feature, terrain, biomes, carver, tag. 월드 생성과 코드로 생성하기 참고 |
feature, tag, salt, aboveBottom을 제외한 모두 |
| 인터페이스 | Form과 player.showForm, server.bossBar, player.setSidebar |
예 |
| 이벤트 | Crafting.events.on<E> { }, 우선순위와 취소 포함 |
예 |
| 효과, 속성, 장비 | addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot |
Yes |
| 키 | ContentBuilder.key, PlayerKeyEvent |
Yes |
| 명령어 | Crafting.commands.command(…): 하위 명령어, 타입이 지정된 인수, 권한, 별칭 |
예 |
| 월드와 플레이어 | World, Block, Entity, Player, Inventory, Server |
예 |
| 작업 | Crafting.scheduler: runLater, runRepeating, runAsync, runSync |
백그라운드 작업 없음 |
| 파일 설정 | TOML로 된 타입 있는 Config, 클라이언트와 동기화 가능 |
예(config) |
| 네트워크 | Crafting.network.channel(…), 사용자 정의 코덱 포함 |
예(listen, send) |
| 저장 데이터 | player.data["rubies.visits"] = n, data.getInt(…). 엔티티, 블록, 월드에도 |
예 |
| Java 라이브러리 | 원하는 무엇이든 | 아니요 |
API에 아직 없는 것은 어느 언어에도 없습니다. Minecraft 클래스를 호출해 우회하지 마세요. 그러면 게임이 바뀌어도 계속 작동한다는 보장을 잃게 됩니다.
프로젝트 만들기
crafting new rubies --kind kotlin
cd rubies && gradle buildAPI는 아직 공개 Maven 저장소에 없습니다. Crafting 소스에서 로컬 Maven에 설치하고 compileOnly로 선언하세요. 실행 시에는 Crafting이 모드에 API를 제공합니다.
cd crafting && ./gradlew :crafting-api:publishToMavenLocalplugins { kotlin("jvm") version "2.4.21" }
repositories { mavenCentral(); mavenLocal() }
dependencies {
compileOnly("dev.crafting:crafting-api:0.1.0")
}
kotlin { jvmToolchain(25) } // Minecraft 26.x는 Java 25에서 실행됩니다crafting new는 이 파일과 함께 책임별로 이미 나뉜 프로젝트를 생성합니다. 각 부분을 연결하기만 하는 <Name>Mod.kt, content/ModContent.kt, events/Welcome.kt, commands/GemCommand.kt.
구조
주제별 패키지, 각 부분을 연결하기만 하는 진입 클래스, 부분마다 파일 하나. 그 이유는 모드 정리하기 가이드에서 설명합니다. 전체 예제 모드는 다음과 같습니다.
rubies/
├── build.gradle.kts
├── settings.gradle.kts
└── src/main/
├── kotlin/com/example/rubies/
│ ├── RubiesMod.kt entry point: only wires the parts
│ ├── RubiesConfig.kt player settings
│ ├── content/
│ │ ├── Items.kt
│ │ ├── Blocks.kt
│ │ ├── Creatures.kt
│ │ ├── Sounds.kt
│ │ ├── Particles.kt
│ │ └── Recipes.kt
│ ├── storage/
│ │ └── Visits.kt saved data: the keys, in one place
│ ├── events/
│ │ ├── MiningEvents.kt
│ │ ├── CombatEvents.kt
│ │ └── WelcomeEvents.kt
│ ├── ui/
│ │ └── ShopForm.kt forms: what the player sees
│ └── commands/
│ ├── RubyCommand.kt
│ └── ShopCommand.kt
└── resources/
├── crafting.mod.json
└── assets/rubies/
├── sounds/ruby/chime.ogg
└── textures/
├── item/ruby.png
├── entity/ruby_golem.png
└── particle/ruby_spark_0.png, ruby_spark_1.pngsrc/main/kotlin/com/example/rubies/
진입 클래스는 스스로 아무것도 하지 않습니다. 공유할 것을 만들고 각 부분을 호출합니다.
package com.example.rubies
class RubiesMod : CraftingMod {
override fun onLoad(ctx: ModContext) {
val config = Crafting.configs.load(RubiesConfig())
val content = ContentBuilder(ctx.metadata.id, Crafting.registries)
Items.register(content)
Blocks.register(content)
Creatures.register(content)
Sounds.register(content)
Particles.register(content)
Recipes.register(content)
MiningEvents.register(config)
CombatEvents.register(config)
WelcomeEvents.register()
RubyCommand.register()
ShopCommand.register()
}
}매니페스트
{
"id": "rubies",
"name": "Rubies",
"version": "1.0.0",
"kind": "mod",
"side": "both",
"entrypoints": ["com.example.rubies.RubiesMod"],
"depends": { "crafting": ">=0.1.0" }
}| 필드 | 설명 |
|---|---|
id |
2–64자: 영문 소문자, 숫자, _, -로 이루어지고 영문자로 시작합니다. 아이템과 블록의 네임스페이스이기도 합니다. |
kind |
mod 또는 plugin(서버 전용, 다시 불러오기 가능. 아래 참고). |
side |
both, client, server. 클라이언트 모드는 전용 서버에서 조용히 무시됩니다. |
entrypoints |
CraftingMod를 구현하는 클래스. |
depends, optional, incompatible |
id → 버전 범위: >=1.2 <2, ^1.4, ~1.4.2, 1.20.x, [1.0,2.0). 문제가 있으면 Crafting이 모든 문제를 제안과 함께 한 번에 보고합니다. |
loadAfter, loadBefore |
강한 의존성 없이 불러오는 순서를 정합니다. |
생명 주기
interface CraftingMod {
fun onLoad(ctx: ModContext)
fun onReady(ctx: ModContext) {}
fun onShutdown(ctx: ModContext) {}
}onLoad: 콘텐츠, 이벤트, 명령어를 등록합니다.- Crafting이 게임에 블록과 아이템을 만듭니다. 이 시점부터 콘텐츠를 등록하면
RegistryFrozenException이 발생합니다. onReady: 모든 모드를 불러왔습니다.Crafting.runtime.isModLoaded("other_mod")로 다른 모드가 있는지 알 수 있습니다.onShutdown: 종료할 때.
서버 플러그인
플러그인은 "kind": "plugin"과 "side": "server"를 가진 CraftingMod입니다. crafting new chatguard --kind plugin은 진입 클래스, events/ChatFilter.kt, commands/HelloCommand.kt를 만듭니다. 블록이나 아이템을 추가하지 않으므로, 플러그인만 있는 서버에는 모드 없는 Minecraft 플레이어도 접속할 수 있습니다. 재시작 없이 활성화, 비활성화, 다시 불러오기를 할 수 있습니다. 활성화될 때 onLoad와 onReady가, 비활성화되거나 다시 불러올 때 onShutdown이 실행됩니다.
crafting new chatguard --kind pluginpackage com.example.chatguard
class ChatGuardPlugin : CraftingMod {
override fun onLoad(ctx: ModContext) {
ChatFilter.register()
Crafting.commands.command("guard") {
permission = "chatguard.admin"
executes { it.reply("ChatGuard가 활성화되어 있습니다") }
}
}
override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard 비활성화됨")
}package com.example.chatguard.events
object ChatFilter {
private val blocked = listOf("spam", "scam")
fun register() {
Crafting.events.on<PlayerChatEvent> { e ->
if (blocked.any { it in e.message.lowercase() }) {
e.cancel()
e.player.send("&c서로 친절하게 대해 주세요")
}
}
}
}플러그인이 등록한 모든 것은 그 플러그인에 묶입니다. /crafting reload chatguard는 onShutdown을 호출하고, 리스너, 명령어, 작업을 제거하고, jar를 다시 읽은 뒤 onLoad를 호출합니다. 개발 모드에서는 jar를 교체하면 자동으로 이렇게 됩니다.
| 모드 | 플러그인 | |
|---|---|---|
| 폴더 | mods/ |
plugins/ |
| 실행 위치 | 클라이언트 및/또는 서버 | 서버 전용 |
| 새 블록과 아이템 | 예 | 아니요 |
| 재시작 없이 다시 불러오기 | 아니요 | 예 |
| 모드 없는 클라이언트 접속 | 콘텐츠를 추가하면 불가 | 가능 |
Java
같은 API, 같은 구조입니다. Java에서는 진입점이 Crafting.INSTANCE이고, 기본값이 있는 매개변수도 모두 넘기며, 리스너는 Unit.INSTANCE를 반환합니다.
package com.example.rubies;
public final class RubiesMod implements CraftingMod {
@Override
public void onLoad(ModContext ctx) {
ContentBuilder content = new ContentBuilder(ctx.getMetadata().getId(), Crafting.INSTANCE.getRegistries());
Items.register(content);
WelcomeEvents.register();
}
}package com.example.rubies.events;
public final class WelcomeEvents {
public static void register() {
Crafting.INSTANCE.getEvents().subscribe(PlayerJoinEvent.class, EventPriority.NORMAL, false, null, e -> {
e.getPlayer().send("&aWelcome, " + e.getPlayer().getName() + "!");
return Unit.INSTANCE;
});
}
}이전 방식
여전히 작동하지만 사용 중단으로 표시되어 있으며 대체 방법이 있습니다.
| 이전 | 현재 |
|---|---|
onEnable/onDisable을 가진 class X : CraftingPlugin() |
"kind": "plugin"을 가진 CraftingMod |
@Subscribe가 붙은 메서드와 events.register(obj) |
events.on<E> { }(Java: events.subscribe(E.class, …)) |
클래스에 붙은 @Mod("id") |
매니페스트의 entrypoints에 있는 클래스 |
EntityDamageEvent.source |
damageType(minecraft:fall 같은 id) |
world.setBlockState(x, y, z, "…[open=true]") |
world.setBlock(x, y, z, type, mapOf("open" to true)) |
규칙
net.minecraft에서 아무것도 import하지 마세요. 그래야 게임이 바뀌어도 모드가 계속 작동합니다.- 콘텐츠는
onLoad에서만 등록하세요. runAsync작업은 월드를 건드리지 않습니다.runSync로 게임 스레드에 돌아가세요.