가이드

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 build

API는 아직 공개 Maven 저장소에 없습니다. Crafting 소스에서 로컬 Maven에 설치하고 compileOnly로 선언하세요. 실행 시에는 Crafting이 모드에 API를 제공합니다.

터미널
cd crafting && ./gradlew :crafting-api:publishToMavenLocal
build.gradle.ktsKotlin
plugins { 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.png

src/main/kotlin/com/example/rubies/

진입 클래스는 스스로 아무것도 하지 않습니다. 공유할 것을 만들고 각 부분을 호출합니다.

src/main/kotlin/com/example/rubies/RubiesMod.ktKotlin
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()
    }
}

매니페스트

src/main/resources/crafting.mod.jsonJSON
{
  "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 강한 의존성 없이 불러오는 순서를 정합니다.

생명 주기

dev.crafting.api.mod.CraftingModKotlin
interface CraftingMod {
    fun onLoad(ctx: ModContext)
    fun onReady(ctx: ModContext) {}
    fun onShutdown(ctx: ModContext) {}
}
  1. onLoad: 콘텐츠, 이벤트, 명령어를 등록합니다.
  2. Crafting이 게임에 블록과 아이템을 만듭니다. 이 시점부터 콘텐츠를 등록하면 RegistryFrozenException이 발생합니다.
  3. onReady: 모든 모드를 불러왔습니다. Crafting.runtime.isModLoaded("other_mod")로 다른 모드가 있는지 알 수 있습니다.
  4. 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 plugin
src/main/kotlin/com/example/chatguard/ChatGuardPlugin.ktKotlin
package 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 비활성화됨")
}
src/main/kotlin/com/example/chatguard/events/ChatFilter.ktKotlin
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를 반환합니다.

src/main/java/com/example/rubies/RubiesMod.javaJava
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();
    }
}
src/main/java/com/example/rubies/events/WelcomeEvents.javaJava
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로 게임 스레드에 돌아가세요.