Files
sfcraft/AGENTS.md
T
iceBear67 1c4c64a63d sfcraft 2.0 (#47)
* play with polymer

* new features checkpoint 1

* squash! new features and our own art assets!

* refactor && agents.md

* new command gps

* add new variant of undying totem

* refactor cauldron recipe handling to use stable IDs instead of indices

* add recipes

* remove irrelavant parent from blaze_bomb

* clean up

* add: anti spam module

* implement i18n for remaining texts
2026-07-20 18:33:15 +08:00

5.9 KiB

sfcraft Agent 工作指南

Fabric 纯服务端 mod,Minecraft 26.2,Java 25,官方 Mojang 映射,自定义内容经 Polymer 伪装成原版内容。整体结构见 ARCHITECTURE.md,先读它再动代码。

⚠️ 工作范围(最高优先级规则)

Agent 的主要工作范围只有这两个包:

  • io.ib67.sfcraft.module.game — 游戏玩法模块
  • io.ib67.sfcraft.mixin.gameplay — 玩法相关 mixin

其它任何位置在修改之前必须先向用户说明并获得明确许可,包括但不限于:

  • io.ib67.sfcraft 其余包(基础设施、DI、callback、registry、其他 module、其他 mixin)
  • SFCraftInitializer(即使只是给新模块加一行 registerFeature)
  • sfcraft.mixins.json(即使只是加一条 gameplay mixin 条目)
  • src/main/resources/ 下的 assets/data(lang、配方、战利品表、tag…)
  • 构建脚本、accesswidener、fabric.mod.json、文档

新特性通常必然涉及 registerFeature 注册行、mixins.json 条目、lang 键、配方 JSON 这些"例行越界"——请在给出方案时一并列出这些改动点,打包征得一次同意后再动手,不要做完了才说。

构建与验证

仓库没有 gradlew wrapper,使用系统 gradle + sdkman JDK:

export JAVA_HOME=~/.sdkman/candidates/java/25.0.1-amzn
~/.sdkman/candidates/gradle/9.5.1/bin/gradle compileJava --console=plain   # 编译(mixin AP 会校验注入目标)
~/.sdkman/candidates/gradle/9.5.1/bin/gradle runServer --console=plain    # dev 服务器(run/ 目录,EULA 已接受)

查 26.2 真实签名(解决 cannot find symbol 的最快办法):

javap -cp ~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-merged-deobf/26.2/minecraft-merged-deobf-26.2.jar <类全名>

新增/修改玩法特性的套路

  1. 在 module.game.<feature> 建包:一个 ServerModule 子类 + 该特性的方块/物品/实体类。
  2. 注册表内容在模块 onInitialize() 里用 RegistryHelper 注册(此阶段注册表未冻结、MinecraftServer 不可用),实例存模块字段(@Getter)。不要写 public static void register() 或静态注册表持有类。
  3. 依赖一律 @Inject(如 GameConfigService、CauldronRecipeRegistry);方块实体/实体拿不到注入,由模块在注册时用工厂闭包把依赖塞进构造器(参考 CauldronModule)。
  4. mixin 放 mixin.gameplay,保持轻薄:逻辑写在模块的公开方法里,mixin 用 SFCraft.getInjector().getInstance(XxxModule.class) 一行委托(参考 MobCommanderMixin);高频调用路径可参考 MountLogic 的惰性缓存。能用 Fabric API 事件或 SFCallbacks 回调就不要写 mixin。
  5. 需要注册模块时在 SFCraftInitializer#registerGameFeatures 加一行(⚠️ 越界,需许可)。GameExtensionModule 必须保持在所有 game 模块之前——它加载 gameplay.json,后续模块注册物品时会读配置。
  6. 编译验证,必要时 runServer 冒烟。

编码约定

  • 配置:数值参数进 GameConfig(io.ib67.sfcraft.config)对应特性的嵌套类,运行期通过注入的 GameConfigService#get() 每次现读,不要缓存(否则 /sfcraft reload 不生效)。注册时烘焙进组件的值(如耐久)天然不可热重载,属已知限制。需要在重载时重建的东西(如炼药锅配方)监听 callback.SFConfigReload。
  • i18n:玩家可见文本一律 Component.translatable,键加进 assets/sfcraft/lang/zh_cn.json 和 en_us.json(⚠️ 越界,需许可)。禁止硬编码中文/英文 literal。不要引入 server-translations 依赖——玩家装服务器材质包,lang 由客户端解析。物品显示名 = ITEM_NAME 组件挂 translatable。
  • 纯服务端:假设代码总在服务端运行;玩家移动是客户端权威,服务端改玩家速度矢量要置 entity.hurtMarked = true。

踩过的坑

26.2 Mojmap 重命名(相对 1.21.x,写代码前先 javap 确认):

  • ResourceLocation → net.minecraft.resources.Identifier(Identifier.fromNamespaceAndPath)
  • EntityType 常量在 net.minecraft.world.entity.EntityTypes(EntityTypes.SNOWBALL)
  • Level.isClientSide 字段私有化 → 用 isClientSide();Level.random → getRandom()
  • BE 存档:saveAdditional(ValueOutput) / loadAdditional(ValueInput),列表用 out.list(name, Codec) / in.listOrEmpty(...)
  • BlockBehaviour.onRemove 已删 → 覆写 BlockEntity.preRemoveSideEffects
  • 类移包:Zombie → entity.monster.zombie,ThrowableItemProjectile → entity.projectile.throwableitemprojectile,VillagerProfession → entity.npc.villager
  • MobEffects.SLOWNESS(不是 MOVEMENT_SLOWDOWN);SimpleContainer 没有 addListener 了(覆写 setChanged() 持久化)
  • GameProfile 是 record(name()/id());sendOverlayMessage/sendSystemMessage 取代 displayClientMessage;命令权限 Commands.LEVEL_GAMEMASTERS.check(source.permissions())

Polymer(0.17.3+26.2):

  • PacketContext 在 net.fabricmc.fabric.api.networking.v1.context.PacketContext
  • BE 类型必须 PolymerBlockUtils.registerBlockEntity,实体类型必须 PolymerEntityUtils.registerType(RegistryHelper 已封装)
  • 伪装物品尽量选非方块、无自定义 use 行为的原版物品:方块物品有放置鬼影;有 use 覆写的物品会吃掉客户端 consumable 预测(进食动画不显示)
  • 要让客户端拿到额外组件(如 food/consumable),覆写 getPolymerItemStack 显式复制
  • 自定义 MobEffect 必须实现 PolymerMobEffect 并返回原版替身(静态非同步注册表)

加载时序:

  • 初始数据包加载早于物品组件初始化,自定义 Codec 里不能用 ItemStack.CODEC(报 "Item ... does not have components yet")——只存物品 id(BuiltInRegistries.ITEM.holderByNameCodec()),用时再 new ItemStack(holder)
  • 注册必须在 onInitialize()(ModInit);onEnable 阶段注册表已冻结
  • 模块按 registerFeature 顺序初始化;SFConfigReload 监听器按注册顺序执行