* add gitignore for codex * add image as mapart support * refine codes * fix * adjust scaling algorithm * some fix * fix
6.7 KiB
6.7 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 这些"例行越界"——请在给出方案时一并列出这些改动点,打包征得一次同意后再动手,不要做完了才说。
工具使用
- 探索、读写代码优先使用 IDE 的 MCP 工具:
list_directory_tree(代替ls/find)、read_file(代替cat/sed)、search_text/search_regex/search_symbol(代替rg/grep)、get_symbol_info、analyze_calls;编辑用apply_patch/create_new_file。 - 查 26.2 原版类签名优先用
read_file直接读 loom 缓存 jar 内的类(会反编译,可读上下文):~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-merged-deobf/26.2/minecraft-merged-deobf-26.2.jar!/net/minecraft/.../SomeClass.class,javap仅作回退。 - shell 只留给 MCP 做不了的事:gradle 编译/运行、git、文件权限操作等。
构建与验证
仓库没有 gradlew wrapper,使用系统 gradle + sdkman JDK:
export JAVA_HOME=~/.sdkman/candidates/java/25.0.1-amzn
~/.sdkman/candidates/gradle/9.6.0/bin/gradle compileJava --console=plain # 编译(mixin AP 会校验注入目标)
~/.sdkman/candidates/gradle/9.6.0/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 <类全名>
新增/修改玩法特性的套路
- 在
module.game.<feature>建包:一个ServerModule子类 + 该特性的方块/物品/实体类。 - 注册表内容在模块
onInitialize()里用RegistryHelper注册(此阶段注册表未冻结、MinecraftServer不可用),实例存模块字段(@Getter)。不要写public static void register()或静态注册表持有类。 - 依赖一律
@Inject(如GameConfigService、CauldronRecipeRegistry);方块实体/实体拿不到注入,由模块在注册时用工厂闭包把依赖塞进构造器(参考CauldronModule)。 - mixin 放
mixin.gameplay,保持轻薄:逻辑写在模块的公开方法里,mixin 用SFCraft.getInjector().getInstance(XxxModule.class)一行委托(参考MobCommanderMixin);高频调用路径可参考MountLogic的惰性缓存。能用 Fabric API 事件或SFCallbacks回调就不要写 mixin。 - 需要注册模块时在
SFCraftInitializer#registerGameFeatures加一行(⚠️ 越界,需许可)。GameExtensionModule必须保持在所有 game 模块之前——它加载 gameplay.json,后续模块注册物品时会读配置。
另外,如果你注册了新的物品,确保通过依赖注入把他注册到 ItemGroupService 中。
- 编译验证,必要时 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监听器按注册顺序执行