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
This commit is contained in:
iceBear67 authored and GitHub committed 2026-07-20 18:33:15 +08:00
1 parent 19e16a575e
commit 1c4c64a63d
105 files changed
+5343 -69

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
# sfcraft Agent 工作指南
Fabric **纯服务端** mod,Minecraft **26.2**,Java 25,官方 Mojang 映射,自定义内容经 [Polymer](https://polymer.pb4.eu) 伪装成原版内容。整体结构见 [ARCHITECTURE.md](./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:
```bash
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 的最快办法):
```bash
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` 监听器按注册顺序执行