No inheritance or manual registration. The framework handles registration, models, language files, and recipes.
Chasm.item()
.type(SWORD).attackDamage(10)
.name("Blazing Blade").texture("minecraft:item/diamond_sword")
.onAttack(ctx -> ctx.target().setOnFire(5))
.register();
Data components, enchantment pools, GUI windows, and durability can be configured declaratively. No need to write NBT or network packets directly.
Run runDatagen to generate models, language files, recipes, and loot tables into your resources folder.
Other mods can add behaviors through SPI, without subclassing or exposing internal APIs.
Chasm 2.0 aims to reduce boilerplate so you can focus on gameplay.
无需继承或手动注册,框架会处理注册、模型、语言文件和配方。
Chasm.item()
.type(SWORD).attackDamage(10)
.name("烈焰之刃").texture("minecraft:item/diamond_sword")
.onAttack(ctx -> ctx.target().setOnFire(5))
.register();
数据组件、附魔池、GUI 窗口、耐久消耗等都可以通过声明式配置完成,不需要直接处理 NBT 或网络包。
运行 runDatagen,模型、语言文件、配方和掉落表会自动生成到资源目录。
其他模组可以通过 SPI 追加物品行为,不需要继承或暴露内部接口。
Chasm 2.0 希望减少样板代码,让开发者把时间放在玩法设计上。
code wiki
适用版本:Chasm core 1.2.0(
gradle.properties: mod_version)· Minecraft 1.21.1 / Fabric Loader 0.19.3 / Fabric API 0.116.15+1.21.1 / Java 21 更新:2026-09-26 读者:用 Chasm 开发 Fabric 玩法模组的作者。本文签名与行为均核对自当前源码;标「已知限制」的是刻意保留或尚未实现的能力。 三条设计底线:零 Mixin(不注入任何原版类)、声明式优先(ui = f(state))、数据驱动优先(能写 JSON 就不写代码)。 第三方模组的适配/排错过程记录在内部文件docs/内部-移植笔记.md,不属于本手册(本手册到第 25 章结束)。
docs/内部-移植笔记.md,不随产品分发只想最快写出来的人看这一章就够;细节在后面的分章。
api.chasm.Chasm,全 static)| 方法 | 返回类型 | 用途 |
|---|---|---|
Chasm.item(String id) |
ItemBuilder |
声明物品 |
Chasm.block(String id) |
BlockBuilder |
声明方块 |
Chasm.gui(String id) |
GuiBuilder |
声明界面(几何 + 节点源) |
Chasm.recipe(...) / Chasm.shaped(...) |
RecipeBuilder |
声明配方 |
Chasm.register(Object holder) / ChasmRegistrar.scanContent(Class) |
void / int |
扫描 @Register 静态字段(内容容器) |
ui = f(state):每一帧由状态重算一张节点列表;列表顺序 = z 序 = 命中优先级;存在 ≡ 可见 ≡ 可命中。
因此「哪一页在画」必须由状态保证互斥,不能靠几何重叠去猜。
GuiNode 能力全表(1.2.0)| 分类 | 方法(返回 GuiNode,可链式) |
说明 |
|---|---|---|
| 建节点 | GuiNode.of(key,x,y,w,h) · sprite(key,x,y,w,h,texture,u,v,tw,th) · fill(key,x,y,w,h,argb) |
文本 / 贴图 / 色块 |
| 文本 | .text(String text,int color) · .textOutlined(String text,int color) |
后者为 8 向黑描边(真机 GuiStringOutline 语义) |
| 交互 | .action(String id,int value) · .hover(int color) · .enabled(boolean) · .hoverWhenDisabled(boolean) |
最后一项 = 不能点但仍能悬停/出提示 |
| 提示 | .tooltip(String) · .tooltipLazy(Supplier<String>) · .tooltipWhen(String key,String) · .tooltipWhenLazy(String key,Supplier<String>) |
键条件(如 GuiNode.SHIFT_KEY)在渲染时求值;惰性版把文本构造推迟到悬停那一刻 |
| 动画 | .delay(double) · .slideIn(int dx,int dy,int ms) · .alphaTimeline(tag,double from,long delay,long[] frames,double[] targets) · .opacityWhenHovered(float) / .opacityWhenHovered(float,String regionKey) |
regionKey = 悬停判定挂在别的节点上(真机页签:悬停按钮本体 → 标签淡入) |
| 图层 | .layer(GuiNode.Layer) · .behind() |
BEHIND 画在物品之前 |
build(GuiState) 里不要做全表扫描、不要每次拼 Component.translatable(...)。
→ 文本用 .tooltipLazy(...);全表查询按数据 revision() 缓存成 O(1) 查表。ChasmMenu.signature() 覆盖「全部数据键 + 全部槽位物品身份」,签名不变就复用上一帧的节点列表(最多 250ms 强制重建一次)。
→ 界面源只允许依赖菜单状态;依赖菜单之外的东西(数据包重载 / 语言切换 / 玩家经验…)最坏滞后 250ms。specialsFor 462,062 ns → ~1,000 ns;惰性 tooltip 让属性条那 53 条提示不再每帧构造。(输入身份, 数据 revision) + 一个短 TTL 兜底;不要只按可变对象引用缓存。.\gradlew.bat :chasm-example:runClient # 开发:进游戏
.\gradlew.bat :chasm-core:test # 框架单测
.\gradlew.bat build # 全量(含测试)→ 各模块 build/libs/*.jar
.\gradlew.bat :chasm-core:publishToMavenLocal # 发到本地 maven 仓库
| 路径 | 写法 | 适合 |
|---|---|---|
| ① JSON 软编码 | 把物品/界面写成 data/<modid>/chasm/items/*.json、chasm/gui/*.json 之类的数据文件(见第 16 章) |
数值/文案频繁调整、想让整合包作者改 |
| ② 内容容器 | 一个类 implements ChasmContentHolder,把一组内容写成 @Register 静态字段,ChasmRegistrar.scanContent(MyContent.class) 一次注册 |
一组相关物品/方块/配方 |
| ③ 链式声明 | Chasm.item("foo").trait(...).component(...).recipe(...)(见第 4/7/9 章) |
需要代码行为(回调、条件、动画) |
三条可以混用:同一个模组里数据文件提供默认值,代码只在需要行为时补。
框架的声明式部件(GuiNode 构建、注册表、数据加载、属性折叠、存档读写…)都能在纯 JVM 单测里跑:
SharedConstants.tryDetectVersion() → Bootstrap.bootStrap() → 注册测试数据 → 直接调你的 build(GuiState) / 注册函数断言。
chasm-core/src/test 里有现成范式;这是"改一行就能验"的最快回路(比开客户端快一个数量级)。
文件:行号)或实测数字;没有依据的猜测不写进来。chasm-example(modId=mymod),可以直接 runClient 看到效果。Chasm(api.chasm)是一套 "玩法优先"的 Minecraft Fabric 模组开发 API,把"声明一个物品/方块 → 组合玩法行为 → 绑定数据 → 自动注册 → 自动生成资源"做成一行行链式声明,目标是消灭样板代码与手写 JSON,同时保留原版语义(横扫、附魔池、挖掘等级、剥皮/耕地、容器界面、按键、存档持久化…)。
| 模块 | 内容 | 规模(当前) |
|---|---|---|
chasm-core |
API 库本体(api.chasm.*:item / item.context / block / blockentity / gui(.decl) / advancement / effect / particle / loot / net / spi / template / memo / multiblock …) |
见 chasm-core/src/main/java(随版本变化,不在此硬编码) |
chasm-core/src/test |
纯 JVM 单元测试(JUnit 5,不需要开游戏) | 同上 |
chasm-example |
演示模组(com.example.chasm,modId=mymod):魔法武器/食物/护甲套装/自定义附魔/类型/模板/GUI/按键/JSON 软编码 |
3 个文件 ≈ 572 行 |
gradle.properties 定义 minecraft_version=1.21.1、loader_version=0.19.3、loom_version=1.17-SNAPSHOT、fabric_api_version=0.116.15+1.21.1、mod_version=1.2.0(三个模块共用这一个值)。chasm-example 通过 modImplementation project(':chasm-core') 依赖 core(chasm-example/build.gradle:47),并在 fabric.mod.json 声明 "chasm-core": "*"。fabric.mod.json:main(api.chasm.ChasmInit);client(ChasmGuiClient、ChasmKeyClient、PlayerVarClient)。./gradlew :chasm-example:build(build 会自动先跑 runDatagen 再编译)./gradlew :chasm-example:runClient / runServer / runDatagen./gradlew :chasm-core:test(跑 P0 单测)./gradlew :chasm-core:publishToMavenLocal| 改了什么 | 命令顺序 |
|---|---|
只改下游模块(chasm-example / 移植模块) |
gradlew :<模块>:build → 清 .gradle\loom-cache\remapped_mods 下对应 jar → 启动客户端 |
| 改了 chasm-core | 先编 core,再清 loom remap 缓存,最后编依赖方:gradlew :chasm-core:remapJar --rerun-tasks → 删 .gradle/loom-cache/remapped_mods/remapped/api/chasm/chasm-core-<hash> → gradlew :<模块>:build |
| 想一步到位(推荐) | powershell -ExecutionPolicy Bypass -File refresh-dev.ps1(停守护进程 → 清 loom-cache → 重建) |
为什么:loom 的项目依赖读的是已重映射的 jar,普通 jar 任务只写 build/devlibs/*-dev.jar;
改动 core 后不让 loom 重新 remap,客户端加载的还是旧类 —— 表现就是「代码明明改了,游戏里没变化」。
另外:绝不要删 chasm-core/build/libs/chasm-core-<版本>.jar,:apoth-port 在配置阶段就会读它。
一个 Chasm 模组 = 1 个 @ChasmMod 入口类 + 若干 @Register 静态字段 + onInitialize 里扫描一次:
package com.example.mymod;
import api.chasm.Chasm;
import api.chasm.ChasmMod;
import api.chasm.data.DataComponent;
import api.chasm.item.ItemBuilder;
import api.chasm.registry.ChasmRegistrar;
import api.chasm.registry.Register;
import net.fabricmc.api.ModInitializer;
import net.minecraft.world.item.Item;
@ChasmMod(id = "mymod") // ★ modId 命名空间
public class MyMod implements ModInitializer {
// —— 数据组件(record + 注解,Codec 自动生成)——
@DataComponent("mana")
public static final ManaData MANA = ManaData.EMPTY;
// —— 物品:声明即注册(id = mymod:mana_sword)——
@Register("mana_sword")
public static final Item MANA_SWORD = Chasm.item()
.type(ChasmTypes.SWORD) // 成为真正的 SwordItem(横扫/剑类附魔池)
.maxStackSize(1)
.durability(1200)
.attackDamage(10.0f) // 总攻击伤害(自动换算原版修饰符)
.name("Mana Sword") // DataGen 自动生成语言文件
.texture("minecraft:item/diamond_sword") // DataGen 自动生成模型
.register(); // 只构建实例;真正注册由 scan 完成
// 给 Record 组件绑默认值见 6.3 节:.component(ManaData.class, new ManaData(50, 50))
@Override
public void onInitialize() {
ChasmRegistrar.scan(this.getClass()); // ★ 扫描本类全部 @Register / @DataComponent
Chasm.itemLoader().loadAll(); // 可选:JSON 软编码物品
}
public record ManaData(int current, int max) {
public static final ManaData EMPTY = new ManaData(0, 0);
}
}
注意:上面
.component(...)一行仅示意占位——给 Record 组件绑默认值请用.component(ManaData.class, new ManaData(50, 50))(延迟解析重载),不要传 null。 完整可运行写法见第 4 章示例。
ChasmRegistrar.scan 必须在 onInitialize(或更早静态初始化)内执行——原版注册表在 onInitialize 结束后冻结。ClientModInitializer。ExampleDataGen 同样写法,入口类实现 DataGeneratorEntrypoint 并调用 ChasmDataGen.generate(generator, MyMod.class)。声明(
@ChasmMod/@Register/@DataComponent)→ 行为(Builder)→ 数据(Codec)→ 注册(自动)→ 暴露(SPI 可扩展)→ 资源(DataGen 自动生成)
api.chasm.Chasm,全 static)| 入口 | 返回 | 说明 |
|---|---|---|
Chasm.item() |
ItemBuilder |
声明物品 |
Chasm.block() |
BlockBuilder |
声明方块 |
Chasm.recipe(String id) |
RecipeBuilder |
声明合成配方(DataGen 出 json) |
Chasm.gui(modId, name) |
ChasmGuiBuilder |
声明式界面 |
Chasm.keys() |
ChasmKeys |
自定义按键注册表 |
Chasm.traits() |
ChasmTraits |
行为特质注册表(use/attack/inventoryTick) |
Chasm.data() |
ChasmData |
数据组件类型安全读写 |
Chasm.playerVar(modId, name, codec) |
PlayerVar.Builder |
玩家持久化变量 |
Chasm.damageTypes() |
ChasmDamageTypes |
伤害类型注册表 |
Chasm.attributes() |
ChasmAttributes |
属性注册表 |
Chasm.types() |
ChasmTypes |
物品类型注册表 |
Chasm.enchantments() |
ChasmEnchantments |
自定义附魔注册表 |
Chasm.itemLoader() / Chasm.guiLoader() |
加载器单例 | JSON 软编码加载 |
Chasm.templates() |
ChasmTemplates |
玩法模板插件层:weapon / food / armorSet(第十四步) |
Chasm.mod(modId) / Chasm.mods() |
ModContext / 集合 |
按 modId 隔离的上下文(SPI 扩展点) |
| 包 | 内容 |
|---|---|
api.chasm |
门面 Chasm、注解 @ChasmMod、core 入口 ChasmInit |
registry |
ChasmRegistrar(注解扫描)、ChasmRegistration(极简注册)、@Register |
context |
ChasmContextRegistry(mod 上下文多例)、ModContext(聚合根) |
item |
ItemBuilder、ChasmItemBehavior(行为枢纽)、8 个物品子类(含 ChasmArmorItem/ChasmFoodItem)、ItemType/ChasmTypes、ChasmItemController、ChasmKeyBinding(按键句柄) |
item/context |
行为回调上下文:UseContext/AttackContext/InventoryTickContext/KeyContext(★ 已从 item/key 抽出,专用于打破包环) |
trait |
ChasmTraits 注册表 + UseTrait/AttackTrait/InventoryTickTrait/ItemTraitEvent |
data |
ChasmData、ChasmCodec(record 自动 Codec)、ChasmCodecs、@DataComponent |
block |
ChasmBlock、BlockBuilder、ChasmBlockController、LootBuilder/LootDeclaration、BlockUseContext |
recipe |
RecipeBuilder/RecipeDeclaration |
damage |
ChasmDamageTypes、ChasmDamageTypeBuilder、DamageTypeInfo、DamageSourcesResolver、DamageContext |
attribute |
ChasmAttributes、AttributeInfo |
enchantment |
ChasmEnchantments、EnchantmentDeclaration、EnchantmentConfig |
gui(+gui/client) |
ChasmGuiBuilder/ChasmGui/ChasmMenu/ChasmGuiChannel/ChasmGuiRegistry/ChasmGuiActions;客户端 ChasmGuiClient/ChasmScreen |
key(+key/client) |
ChasmKeys、ChasmKeyChannel、KeyPressC2SPayload;客户端 ChasmKeyClient |
player(+player/client) |
PlayerVar(含多人 S2C 同步)、PlayerVarChannel、PlayerVarSyncS2CPayload;客户端 PlayerVarClient |
template |
ChasmTemplates、WeaponTemplate/FoodTemplate/ArmorSetSpec/ArmorTickContext、ChasmKillEvents/ChasmKillRewards/KillReward/KillContext(第十四步) |
loader |
ChasmItemLoader/ChasmGuiLoader/ChasmResources(JSON 软编码) |
log |
ChasmLogger/ChasmFileLogger/AsyncLogWriter |
net |
RateLimiter(服务端网络限流,GUI/按键通道共用) |
datagen |
ChasmDataGen(9 个 Provider + 覆写机制) |
ChasmRegistrar.scan(class) 读 @Register 字段 → 按类型路由注册进原版注册表(BuiltInRegistries.ITEM/BLOCK/MENU/DATA_COMPONENT_TYPE/ATTRIBUTE…)→ 暴露到 ModContext。ChasmDataGen.generate 内部调用 scan(modClass, false, false) 只收集声明不注册(注册表已冻结),随后 9 个 Provider 根据各 ModContext 输出 JSON 到 src/main/generated。创建:Chasm.item()。register() 只构建物品实例(注册交给 @Register 扫描),返回 Item(实际为绑定的类型子类)。
| 方法 | 语义 |
|---|---|
type(ItemType) |
绑定物品类型(见第 5 章);null = 普通物品 |
tier(Tier) |
指定原版 Tier(仅工具类类型生效;缺省用类型内置默认铁级) |
maxStackSize(int) |
堆叠上限,默认 64 |
durability(int) |
耐久,默认无限 |
attackDamage(float total) |
总攻击伤害。自动写主手修饰符 = total − BASE(BASE = 原版 ATTACK_DAMAGE 默认值,1.21.1 为 2.0)。total == BASE 时不加修饰符。例:attackDamage(10) → 修饰符 +8 |
attackSpeed(float total) |
总攻击速度,修饰符 = total − 4.0。例:0.5 → −3.5 |
armor/armorToughness/movementSpeed/knockbackResistance(float) |
便捷属性(见第 11 章槽位组语义) |
attribute(Attribute, amount, slot) 等 3 个重载 |
任意属性修饰符(自动 id chasm:item_<attr>_<n>,ADD_VALUE) |
name(String) |
显示名(DataGen 语言文件) |
texture(String) |
贴图路径(DataGen 模型),如 minecraft:item/diamond_sword |
onRightClick(Consumer<UseContext>) |
右键行为(可多个,按声明顺序执行) |
onAttack(Consumer<AttackContext>) |
攻击行为(可多个) |
component(DataComponentType<T>, T) |
默认数据组件(写进 Item.Properties) |
component(Class<T> recordClass, T value) |
延迟绑定 record 组件默认值(解决静态初始化时序,运行时 getDefaultInstance 才解析) |
useTrait(id) / useTrait(modId, name) |
绑定 USE 特质(见第 7 章) |
attackTrait(...) |
绑定 ATTACK 特质 |
inventoryTrait(...) |
绑定 INVENTORY_TICK 特质(持续效果) |
onKeyPress(ChasmKeyBinding, Consumer<KeyContext>) / onKeyPress(ResourceLocation, ...) |
绑定自定义按键(见第 15 章) |
enchant(Holder/ResourceKey/EnchantmentDeclaration, level) |
物品自带附魔(写 ENCHANTMENTS 组件;注册键运行时解析) |
enchantable(key/decl, minLevel, maxLevel, weight[, treasureOnly]) |
把附魔加进本物品所绑类型的附魔池(必须先 .type(...)) |
food(FoodTemplate) / food(nutrition, saturation) |
绑定食物属性(类型自动为 FOOD) |
killReward(ResourceLocation rewardId) / killReward(KillReward) |
绑定击杀奖励(写入内部组件,AFTER_DEATH 触发,见第 23 章) |
template(WeaponTemplate) |
应用武器模板(伤害/攻速/命中/击杀掉落一次到位) |
armor(ArmorSetSpec, ArmorItem.Type) / armor(Holder<ArmorMaterial>, ArmorItem.Type) |
护甲部位(类型自动为 ARMOR;set 版还绑定套装 id,见第 23 章) |
register() |
构建并返回 Item |
register() 会把「类型默认属性模板 + 物品自身声明」合并,同 (属性+槽位+运算) 时物品覆盖类型默认(丢弃类型项),再统一写入 DataComponents.ATTRIBUTE_MODIFIERS。因此工具提示、整合包、附魔/重铸系统都能读到真实属性。ChasmSwordItem 等子类还暴露 getAttackDamage() 供特质计算差额。ChasmItemBehavior)| 类 | 原版父类 | 用途 |
|---|---|---|
ChasmItem |
Item |
普通物品(NONE 类型) |
ChasmSwordItem |
SwordItem |
横扫 / 剑类附魔池 |
ChasmPickaxeItem |
PickaxeItem |
挖掘等级 |
ChasmAxeItem |
AxeItem |
剥皮(useOn 原版) |
ChasmShovelItem |
ShovelItem |
铺路(useOn 原版) |
ChasmHoeItem |
HoeItem |
耕地(useOn 原版) |
五个工具/剑子类都完整覆写并委托:use、hurtEnemy、inventoryTick、getDefaultInstance、appendHoverText。右键/物品栏/攻击特质与回调在工具物品上都有效。
⚠️ 真实边界(务必阅读):工具/剑子类刻意不覆写
useOn,以保留剥皮/铺路/耕地等原版右键行为。因此右键指向一个该工具可作用的方块(如斧→原木)时,原版useOn会返回成功并短路use()——绑定的useTrait/onRightClick不会触发;右键空中或不可作用方块才进入use()。需要"点击方块也触发玩法"时请改用方块事件(第 8 章)或特质只做服务端结算。
api.chasm.item.context,回调参数)UseContext(右键/use):level()/player()/hand()/stack()、sendMessage(String)(仅服务端)、serverOnly(Runnable)、clientOnly(Runnable)、swingArm()、consumeItem()/consumeItem(int)、damageItem(int)(返回是否仍可用)。
AttackContext(攻击/hurtEnemy):stack()/target()/attacker()/level()/isServer()、serverOnly、sendMessage(仅服务端且攻击者为玩家)、damageItem(int)。
InventoryTickContext(物品栏 tick):level()/player()/stack()/slotId()/selected()、isServer()、sendMessage、serverOnly、clientOnly。
KeyContext(record):(Level, Player, ItemStack, ResourceLocation keyId),服务端主线程执行。
惯例:数值结算(扣法力、伤害、改存档)一律放
serverOnly;客户端回调只做表现。
// 字段声明顺序:数据组件/特质/附魔等要先于引用它们的物品
@DataComponent("mana")
public static final ManaData MANA = ManaData.EMPTY; // new ManaData(50,50)
public static final ItemType WAND = Chasm.types()
.register("mymod", "wand", ctx -> new ChasmItem(ctx.behavior(), ctx.properties()))
.tag("mymod:magic").build();
@Register("arcane_wand")
public static final Item ARCANE_WAND = Chasm.item()
.type(WAND)
.maxStackSize(1).durability(300)
.name("Arcane Wand").texture("minecraft:item/blaze_rod")
.component(ManaData.class, new ManaData(50, 50)) // 延迟绑定默认组件
.useTrait("mymod", "mana_use") // 绑定特质
.onKeyPress(MAGIC_KEY, ctx -> ctx.player().sendSystemMessage(Component.literal("按下自定义键!")))
.register();
类型 ≠ 继承哪个类,而是一份可复用模板:原版类映射工厂 + 默认属性 + 自动标签 + 专属附魔池。
ChasmTypes 静态字段)| 常量 | id | 实例化类 | 默认标签 | 默认属性(主手) |
|---|---|---|---|---|
NONE |
chasm:none |
ChasmItem |
— | — |
FOOD |
chasm:food |
ChasmFoodItem |
— | — |
ARMOR |
chasm:armor |
ChasmArmorItem |
— | — |
SWORD |
chasm:sword |
ChasmSwordItem |
c:swords, minecraft:swords |
攻 +1.0,攻速 −2.4 |
PICKAXE |
chasm:pickaxe |
ChasmPickaxeItem |
c:pickaxes, minecraft:pickaxes |
攻 +1.0,攻速 −2.8 |
AXE |
chasm:axe |
ChasmAxeItem |
c:axes, minecraft:axes |
攻 +5.0,攻速 −3.0 |
SHOVEL |
chasm:shovel |
ChasmShovelItem |
c:shovels, minecraft:shovels |
攻 +1.5,攻速 −3.0 |
HOE |
chasm:hoe |
ChasmHoeItem |
c:hoes, minecraft:hoes |
攻 +0.0,攻速 −3.0 |
内置默认 Tier:ChasmSwordTier/ChasmToolTier(耐久/速度 ≈ 铁级、附魔等级 15、铁锭修复、攻击加成 0——伤害全交给属性修饰符)。物品可用 .tier(Tiers.DIAMOND) 覆盖。
// 工厂决定原版类映射:可用内置类、自定义子类、或 ChasmItem 直接包装
Chasm.types().register("mymod", "staff",
ctx -> new ChasmItem(ctx.behavior(), ctx.properties()))
.tag("mymod:magic").tag("chasm:staffs") // 类型自动标签
.defaultAttribute(Attributes.ATTACK_DAMAGE.value(), 1.0f) // 默认属性模板
.enchantment(MANA_VAMPIRE, 1, 3, 10) // 专属附魔池(附魔台可刷)
.build(); // build 即登记进 ChasmTypes
ItemType.Builder 方法:defaultAttribute(a, amount[, slot])(完整重载支持自定义 modifierId+operation,同 id 覆盖)、tag/tags(...)、enchantment(decl|key, min, max, weight[, treasureOnly])(treasureOnly=true 只进战利品不进附魔台)、build()。
查询:Chasm.types().get(id) → Optional;getOrThrow;all();get("sword") 自动补 chasm: 前缀。类型 id 形如 mymod:staff,绑定 .type(WAND) 的普通物品亦可 @Register("wand_item") 复用。
数据组件(1.21 Data Component)用于给"每个物品栈"挂类型安全的附加数据,自动序列化/同步/存档。
@DataComponent("mana") record 字段(最常用,推荐):@DataComponent("mana") // id 相对 modId → mymod:mana
public static final ManaData MANA = ManaData.EMPTY; // 字段值通常是默认实例
public record ManaData(int current, int max) {
public static final ManaData EMPTY = new ManaData(50, 50);
}
扫描器(ChasmRegistrar.scan)遇到 @DataComponent 字段:ChasmCodec.codecFor(ManaData.class) 自动生成 Codec → ChasmData.register(modId, "mana", ManaData.class, codec) → 组件类型注册进原版 DATA_COMPONENT_TYPE(id mymod:mana),并按类建索引。
public static final DataComponentType<Integer> SOULS =
ChasmData.register("mymod", "souls", ChasmCodecs.INT);
// 物品里 .component(SOULS, 100)
static final Codec<T> CODEC 或静态 codec() 方法,ChasmCodec.codecFor 优先使用。name())、嵌套 record(递归)。CODEC 字段或 codec() 方法即可。Chasm.data())ManaData m = Chasm.data().get(stack, ManaData.class); // 无则 null
ManaData m2 = Chasm.data().getOrDefault(stack, ManaData.class, ManaData.EMPTY);
Chasm.data().set(stack, ManaData.class, new ManaData(5, 10));
DataComponentType<ManaData> t = Chasm.data().componentType(ManaData.class); // 未注册抛异常
索引规则(09-05 修复跨模组同名 record 覆盖):类索引已改为
(Class → modId → DataComponentType)二级表(BY_MOD_CLASS),只对register(modId, id, Class, Codec)重载建立;纯 Codec 注册(重载 2)读不出类访问器。
- 单模组注册的类:
get(stack, Class)等旧式访问仍可用;- 同一 record 类被多个模组注册:无 modId 访问会抛歧义异常,请用
Chasm.data().componentType(modId, class)/get(stack, modId, class)/set(stack, modId, class, v),或Chasm.mod(modId).data().get/set(...)(ModDataSpace 已提供类型安全访问器)。
框架自己注册了 5 个 chasm 命名空间组件:
USE_TRAIT_ID / ATTACK_TRAIT_ID / INVENTORY_TICK_TRAIT_ID(存 ResourceLocation 特质 id);KILL_REWARD_ID(击杀奖励 id,ItemBuilder.killReward 写入,第十四步);ARMOR_SET_ID(护甲套装 id,ItemBuilder.armor(set, type) 写入,ChasmArmorItem 穿戴 tick 统计用)。不要占用 chasm: 前缀。
解决什么:高频右键/攻击场景用全局注册的特质替代 List<Consumer> 遍历——物品栈只存一个 ResourceLocation 特质 id,运行时从 ChasmTraits O(1) 查回执行。
// 右键使用特质(ctx 是 UseContext)
public static final ResourceLocation MANA_USE =
Chasm.traits().registerUse("mymod", "mana_use", ctx -> {
if (ctx.player() == null) return InteractionResult.PASS;
ctx.swingArm(); // 双侧播放动画
final InteractionResult[] r = { InteractionResult.PASS };
ctx.serverOnly(() -> { // 数值结算只在服务端
PlayerMana.tryConsume(ctx.player(), 5);
r[0] = InteractionResult.SUCCESS;
});
return r[0];
});
// 攻击特质(ctx 是 AttackContext)
Chasm.traits().registerAttack("mymod", "mana_attack", ctx -> { ... });
// 物品栏 tick 特质(ctx 是 InventoryTickContext,仅 Player 持有者触发)
Chasm.traits().registerInventoryTick("mymod", "crystal_tick", ctx -> { ... });
.useTrait(id) / .attackTrait(id) / .inventoryTrait(id)(register() 时写入对应内部数据组件)。ChasmItemBehavior):特质优先,回调兜底。handleUse:特质返回 SUCCESS/CONSUME/FAIL/PASS → 分别映射 success/consume/fail/pass 结果;特质异常/未注册 → 记 error 并回退回调列表。handleAttack:特质返回 SUCCESS|CONSUME → 返回 true 短路 super.hurtEnemy(不再执行原版命中副作用/耐久损耗)。handleInventoryTick:仅当实体是 Player 才触发。ChasmTraits.INSTANCE.get(ItemTraitEvent.USE, id) 等(返回特质或 null);注册返回 modId:name 的 id。ItemTraitEvent 预留 ENTITY_INTERACT / BLOCK_INTERACT / FINISH_USING / CUSTOM_ACTION(注册表骨架已通用支持,但绑定方法/物品覆写尚未接线——目前只能用已上线的三类)。创建 Chasm.block();register() 返回 ChasmBlock(只 new 不注册,注册由 @Register 字段+扫描完成,BlockItem 自动生成同 id)。
| 方法 | 语义 |
|---|---|
strength(destroy, explosion) |
硬度/爆炸抗性(3,3 ≈ 石头) |
requiresPickaxe()/requiresAxe()/requiresShovel() |
需要对应工具(写入 mineable 标签) |
requiresTool(String tag) |
任意挖掘标签(如 mymod:mineable/magic) |
sound(SoundType) |
声音类型 |
name/texture |
显示名/贴图(DataGen 生成语言/模型) |
dropsSelf() |
掉落自身(默认) |
drops(item, count) / drops(item) / dropsRandom(item, min, max) |
简单掉落 |
loot(Consumer<LootBuilder>) |
多工具多样掉落(与 requires* 互斥) |
onUse/onPlace/onBreak/onStep(Consumer<BlockUseContext>) |
四类事件(可叠加多个) |
register() |
返回 ChasmBlock 实例 |
requiresTool 与 loot(…) 互斥:前者是"需正确工具才掉落",后者管理多工具条件掉落;不要同时声明。
Chasm.block()
.strength(3.0f, 3.0f)
.name("Mana Crystal").texture("minecraft:block/amethyst_block")
.loot(l -> l
.onTool(Items.DIAMOND_PICKAXE, r -> r.dropsSelf())
.onToolTag("minecraft:mineable/pickaxe", r -> r.dropsRandom(Items.AMETHYST_SHARD, 1, 3))
.onTool(Items.GOLDEN_HOE, r -> r.chance(0.5f).dropsSelf())
.otherwise(r -> r.nothing()))
.onUse(ctx -> ctx.sendMessage("The crystal hums!"))
.register();
掉落规则类型:SELF(自身)/ ITEM(固定数量)/ ITEM_RANDOM(随机区间)/ NOTHING;可带 chance 概率。DataGen 的 LootProvider 据此生成 loot table。
| 原版覆写 | 事件 |
|---|---|
useWithoutItem(空手右键) |
use 回调(恒返回 sidedSuccess) |
setPlacedBy |
place 回调 |
playerWillDestroy |
break 回调 |
stepOn |
step 回调 |
上下文 BlockUseContext:level()/pos()/player()/state()/hit()/entity()(entity 回退到 player)、swingArm()、sendMessage(String)(仅服务端)。player() 在踩踏等事件可为 null;hit() 仅右键事件有值。
Chasm.recipe("mana_sword") // id 不含命名空间
.shaped(" D ", " D ", " S ", // 图案(三行便捷重载)
'D', Items.DIAMOND, 'S', Items.STICK) // (字符, 材料) 成对,材料须 Item/Ingredient
.result(MANA_SWORD) // Item 或 ItemStack
.register(); // void:只写当前 mod 配方空间
shaped(List<String>, Object...)、shaped(String[], Object...);define(char, Object);材料只接受 Item 或 Ingredient。data/<ns>/recipe/*.json(解锁条件自动取第一个材料)。必须在 scan 之后 / onInitialize 中调用(需要当前 mod 上下文)。public static final DamageTypeInfo ARCANE_BURN = Chasm.damageTypes()
.register("mymod", "arcane_burn", DamageTypeKind.MAGIC) // kind=来源类别
.message("was consumed by arcane fire") // 死亡消息(需真实 DamageType 才生效)
.bypassesArmor(true)
.tag("magic").tag("fire") // 聚合标签(查询用)
.build(); // 写入 ChasmDamageTypes 内存注册表
DamageTypeKind:PLAYER_ATTACK / MOB_ATTACK / MAGIC / FIRE / LIGHTNING / INDIRECT_MAGIC。内置模板 ChasmDamageTypes.PHYSICAL、ChasmDamageTypes.MAGIC。
ChasmDamageTypeProvider 输出 data/<ns>/damage_type/<path>.json(message_id/exhaustion/scaling/effects/death_message_type);ChasmDamageTypeTagProvider 把 bypassesArmor()/isFire()/LIGHTNING 语义聚合进原版标签 minecraft:bypasses_armor / is_fire / is_lightning(replace:false 合并,运行时即被护甲/引燃/雷电机制识别)。DamageSourcesResolver.resolve(sourceEntity, info):new DamageSource(registered, source)——message()/bypassesArmor()/exhaustion() 由原版承担、全部生效;ChasmDataGen.overrideFile("<ns>/damage_type/<name>.json", json)。tag() 仅用于 hasTag/聚合查询,不是原版 damage_type 标签。float dealt = new DamageContext(ARCANE_BURN)
.base(8.0f) // 基础伤害
.multiplier(1.0f) // 倍率
.critChance(0.3f, 2.0f) // 30% 概率 ×2
.onApply(() -> { /* 音效/粒子等副作用 */ })
.apply(player, target, level.getRandom()); // 返回名义伤害(护甲等换算后的实际值由原版结算)
查询:expected()(base×multiplier)、calculate(random)、resolveSource(attacker)(只解析不施伤)、type()。
ATTACK_DAMAGE(默认值≈2.0)、ATTACK_SPEED(4.0)、ARMOR(0)、ARMOR_TOUGHNESS(0)、MOVEMENT_SPEED(0.1)、KNOCKBACK_RESISTANCE(0)。每个 AttributeInfo 持有真实原版 Attribute 引用,可用于 Entity.getAttribute。
AttributeInfo spellPower = Chasm.attributes()
.registerRanged("mymod", "spell_power", 5.0f, 0.0f, 100.0f);
// 需要注册期钩子(如挂实体属性)用 registerHook(modId, name, def, min, max, consumer)
查询:get(ResourceLocation) → Optional;get(name)(缺省命名空间 minecraft);getOrThrow;size()。
物品侧使用:ItemBuilder 的 attribute(…)/便捷方法把修饰符写进 ATTRIBUTE_MODIFIERS 组件(面板/重铸系统可读的真实属性来源)。
1.21.1 附魔是数据驱动动态注册表:必须存在 data/<ns>/enchantment/*.json 才会被加载。Chasm 用"声明 + DataGen 自动输出"闭环。
// 静态字段声明(顺序先于引用它的类型/物品)
public static final EnchantmentDeclaration MANA_VAMPIRE =
Chasm.enchantments().register("mymod", "mana_vampire")
.weight(10).maxLevel(3).anvilCost(1)
.minCost(5, 10).maxCost(15, 10) // (base, perLevelAboveFirst)
.slot("mainhand") // 可 slot/slots(...) 多组
// .treasure() // 可选:宝藏附魔(仅战利品/自带)
.build(); // 登记声明并输出日志
data/mymod/enchantment/mana_vampire.json(description/supported_items/weight/max_level/min_cost/max_cost/anvil_cost/slots)。.enchantment(decl, min, max, weight),或物品就地 .enchantable(...)(需先 .type(…))。DataGen 再聚合出 supported_items 物品标签(mymod:enchantable/mana_vampire)→ 原版附魔台天然可刷出。treasureOnly 成员不进标签。.enchant(MANA_VAMPIRE, 1)(运行时注册表冻结后解析 Holder 写入 ENCHANTMENTS 组件)。运行时解析附魔等级:Chasm.enchantments().holderFor(key) → Optional<Holder.Reference>(服务端启动后可用)。
名称/描述(09-05 已修复):构建时可
.name("魔力汲取")/.description("...")(缺省用注册名);DataGen 语言 Provider 会自动为每个附魔生成enchantment.<ns>.<name>与.desc两条语言条目,开箱即可读。声明侧可用decl.translationKey()/descTranslationKey()/displayName()/displayDescription()读取。
基于 Fabric DataAttachment:类型安全、随世界存档持久化、死亡重生保留、可选 min/max 自动钳制。所有读写只在服务端(ServerPlayer)。
public static final PlayerVar<Integer> MANA =
Chasm.playerVar("mymod", "mana", ChasmCodecs.INT)
.defaultValue(50) // 开局/未初始化值
.min(0).max(100) // 写入自动钳制(T 需 Comparable)
.build(); // build 即注册(没有 register 方法)
API:get(客户端返回默认值并 WARN)、getOrSet、set、modify(UnaryOperator)、has、clientSafeGet(客户端读只读缓存、不打 WARN,HUD 用)、cacheReadOnly、receiveSync(CompoundTag)(数据包接收侧)、location()/codec()/defaultValue()/min()/max()/isServerOnly/isClientSafe;静态 PlayerVar.byId(id) / PlayerVar.all()(全局注册表)。
多人同步(09-05 已修复,无需手写):服务端 set/modify 值实际变化时经 S2C 包(PlayerVarSyncS2CPayload,值用 NBT 包装 {"v":...},任意 Codec 通用)增量推送给该玩家;玩家加入时全量推送所有已注册变量(PlayerVarChannel 监听 JOIN)。客户端 api.chasm.player.client.PlayerVarClient 接收并解码写入只读缓存——clientSafeGet 在多人模式 HUD/UI 也能读到实时值。值未变化不落附件也不发包。
public static final ChasmGui MAGIC_BOX = Chasm.gui("mymod", "magic_box")
.title("魔法盒")
.size(9, 3) // 列 [1,18] × 行 [1,6]
.slot(0, 0, 2, 2) // 左上 2x2 存储区(槽索引 0..3,行主序)
.button("传送", 5, 0, (player, click) -> player.teleportTo(
player.getX(), player.getY() + 10.0, player.getZ())) // 回调在服务端主线程
.buttonCooldown(20) // 冷却 tick(作用于下一个按钮)
.button("治疗", 7, 0, (p, c) -> p.heal(10.0f))
.onOpen(ctx -> { // 打开即预填(服务端)
ctx.setItem(0, Items.EMERALD, 5);
ctx.player().sendSystemMessage(Component.literal("已打开"));
})
.onClose(() -> {})
.register(); // 返回 ChasmGui;MenuType 注册进原版 MENU
打开:player.openMenu(MAGIC_BOX.provider())(或 Chasm.gui("mymod","magic_box").provider(),未注册会自动 register)。
return player == null || player.isAlive()——客户端副本恒 true;服务端玩家死亡即失效;不校验距离/维度(轻量容器设计)。.background(ResourceLocation),32×32 九宫格 8px 边框;缺省灰色面板。把交互从左右键扩展到任意键(默认如 O=79、L=76 的 GLFW 码)。
// 静态声明(共享/服务端安全;请直接传键码字面量,避免在服务端加载 GLFW 类)
public static final ChasmKeyBinding MAGIC_KEY =
Chasm.keys().register("mymod", "magic_blast", 79, "Magic Blast");
@Register("wand")
public static final Item WAND = Chasm.item()
.onKeyPress(MAGIC_KEY, ctx -> ctx.player().sendSystemMessage(
Component.literal("魔法弹射出!(服务端)")))
.register();
key.chasm.<modId>.<name> 与分类 key.category.chasm。机制:把 data/<ns>/chasm/items/*.json 与 data/<ns>/chasm/gui/*.json 放在模组资源里,运行时 Chasm.itemLoader().loadAll() / Chasm.guiLoader().loadAll()(onInitialize 中)即自动构建并注册——可以不写 Java 代码加物品/界面。内部由 ChasmResources 扫描 classpath(目录与 jar 均支持)。
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| id | string | ✔ | — | 注册 id(namespace:path) |
| type | string | ✘ | 普通物品 | 类型 id:chasm:sword/pickaxe/axe/none…(未命中快捷分支仅 sword/none/pickaxe/axe;shovel/hoe 走注册表查询,注册表存在则命中) |
| tier | string | ✘ | 类型默认 | minecraft:diamond 等(Tiers.valueOf,注意大写解析) |
| attack_damage | number | ✘ | 1.0 | 总攻击伤害 |
| attack_speed | number | ✘ | — | 总攻速(内部 −4.0) |
| max_stack / durability | int | ✘ | 64 / 无限 | 堆叠/耐久 |
| name / texture | string | ✘ | — | 显示名 / 贴图 |
| traits | object | ✘ | — | { "use": "mymod:id", "attack": "mymod:id", "inventory": "mymod:id" } |
| enchant | array | ✘ | — | [{ "id": "minecraft:fire_aspect", "level": 2 }](level 缺省 1) |
| components | object | ✘ | — | 组件 id → 值(按已注册 DATA_COMPONENT_TYPE 用 Codec 从 Gson 解码) |
{
"id": "mymod:flame_sword",
"type": "chasm:sword",
"tier": "minecraft:diamond",
"attack_damage": 12.0,
"attack_speed": 1.6,
"name": "Flame Sword",
"texture": "minecraft:item/diamond_sword",
"max_stack": 1,
"durability": 1200,
"traits": { "attack": "mymod:mana_attack" },
"enchant": [ { "id": "minecraft:fire_aspect", "level": 2 } ]
}
行为:运行时真实注册进 BuiltInRegistries.ITEM(重复 id 跳过并 warn);DataGen 期只收集声明(注册表冻结);单文件失败不影响其他文件;幂等。
| 字段 | 默认 | 说明 |
|---|---|---|
| id | 必填 | namespace:path(MenuType 只能注册一次,重复跳过) |
| title / cols / rows / background | — / 9 / 1 / — | 标题/网格/背景 |
| slots | — | [{ col, row, width, height }](width/height 缺省 1) |
| buttons | — | [{ label, col, row, action, cooldown }];须有 action 或 label(都无则跳过);action 驱动经 ChasmGuiActions 反查(label 缺省用 action 的 path);纯文字按钮为空 handler |
{
"id": "mymod:simple_box",
"title": "简单宝盒",
"cols": 9, "rows": 3,
"slots": [ { "col": 0, "row": 0, "width": 2, "height": 2 } ],
"buttons": [ { "label": "传送", "col": 5, "row": 0, "action": "mymod:teleport", "cooldown": 20 } ]
}
注意:动作须先 ChasmGuiActions.register("mymod","teleport", handler) 再 loadAll;GUI 在 DataGen 期不加载(MenuType 注册表已冻结)。
ChasmLogger:按 modId 隔离的 SLF4J 增强包装——每行自动带 [modId] 前缀 + 结构化记录 + 异步会话文件落盘(<游戏目录>/chasm-logs/<时间戳>/session.log),文件 IO 全部吞异常,绝不影响游戏主流程。
| 方法 | 输出形态 |
|---|---|
info/warn/error/debug(modId, fmt, args...) |
[INFO] [mymod] ...(debug 需开调试模式) |
call(modId, cls, method, detail, args...) |
[mymod] CALL X.y: ...
|