port to Minecraft 26.3
Source changes for 26.3 API breakage:
- block codec system removed upstream (Block has no MapCodec/codec()/
simpleCodec anymore): drop CODEC + codec() from AmethystCauldronBlock
and GravityCrystalBlock
- Entity.hurtMarked -> Entity.syncVelocity (pure rename, still public)
- LivingEntity.drop() now takes a Prediction; our drops are server-
initiated and unpredicted, so they pass SERVER_ONLY
- canSimulateMovement() became final; the ravager/hoglin mount mixins
now override getMoveSimulationType() and return
AUTHORITATIVE_SIDE_AND_SERVER, which is the same "server always
simulates" semantics the old !isClientSide branch had
- loot number providers split into providers.number.{ints,floats} and
became Holder-based: UniformGenerator.between(1f,3f) ->
ContextIntProviders.between(1,3)
- BonemealableBlock methods take a BonemealSource (INTERACTION here)
- clockManager().getTotalTicks(c) ->
clockManager().getInstance(c).totalTicks()
- EnderMan -> Enderman (capitalisation only)
Build: MC 26.3, loader 0.19.5, fabric-api 0.160.5+26.3, loom 1.18.1,
gradle wrapper 9.6.0 -> 9.7.1 (loom 1.18.x requires Gradle >= 9.7).
Polymer is 0.18.0+26.3-rc-1: upstream has not published a 26.3-final
build yet, that rc build is the only one on the 26.3 line.
server-deploy: pack.toml -> 26.3 / loader 0.19.5; fabric-api, polymer
and simple-voice-chat are the only pack mods with 26.3 builds and were
updated (index hashes regenerated). The remaining third-party mods have
no 26.3 release yet and are deliberately left untouched. Dockerfile now
pins the justbackup and fabric-carpet forks to their 26.3 branches
instead of silently building whatever the default branch holds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
08be6bc3ae
commit
2b70971b80
25 files changed
+89
-73
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
# sfcraft Agent 工作指南
|
# sfcraft Agent 工作指南
|
||||||
|
|
||||||
Fabric **纯服务端** mod,Minecraft **26.2**,Java 25,官方 Mojang 映射,自定义内容经 [Polymer](https://polymer.pb4.eu) 伪装成原版内容。整体结构见 [ARCHITECTURE.md](./ARCHITECTURE.md),先读它再动代码。
|
Fabric **纯服务端** mod,Minecraft **26.3**,Java 25,官方 Mojang 映射,自定义内容经 [Polymer](https://polymer.pb4.eu) 伪装成原版内容。整体结构见 [ARCHITECTURE.md](./ARCHITECTURE.md),先读它再动代码。
|
||||||
|
|
||||||
## ⚠️ 工作范围(最高优先级规则)
|
## ⚠️ 工作范围(最高优先级规则)
|
||||||
|
|
||||||
@@ -22,23 +22,22 @@ Agent 的主要工作范围**只有**这两个包:
|
|||||||
## 工具使用
|
## 工具使用
|
||||||
|
|
||||||
- 探索、读写代码**优先使用 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`。
|
- 探索、读写代码**优先使用 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` 仅作回退。
|
- 查 26.3 原版类签名**优先用 `read_file` 直接读 loom 缓存 jar 内的类**(会反编译,可读上下文):`~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-merged-deobf/26.3/minecraft-merged-deobf-26.3.jar!/net/minecraft/.../SomeClass.class`,`javap` 仅作回退。
|
||||||
- shell 只留给 MCP 做不了的事:gradle 编译/运行、git、文件权限操作等。
|
- shell 只留给 MCP 做不了的事:gradle 编译/运行、git、文件权限操作等。
|
||||||
|
|
||||||
## 构建与验证
|
## 构建与验证
|
||||||
|
|
||||||
仓库**没有** gradlew wrapper,使用系统 gradle + sdkman JDK:
|
仓库自带 gradlew wrapper(**9.7.1** —— loom 1.18.x 要求 Gradle ≥ 9.7,Dockerfile 也走 `./gradlew`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export JAVA_HOME=~/.sdkman/candidates/java/25.0.1-amzn
|
./gradlew compileJava --console=plain # 编译(mixin AP 会校验注入目标)
|
||||||
~/.sdkman/candidates/gradle/9.6.0/bin/gradle compileJava --console=plain # 编译(mixin AP 会校验注入目标)
|
./gradlew runServer --console=plain # dev 服务器(run/ 目录,EULA 已接受)
|
||||||
~/.sdkman/candidates/gradle/9.6.0/bin/gradle runServer --console=plain # dev 服务器(run/ 目录,EULA 已接受)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
查 26.2 真实签名(解决 cannot find symbol 的最快办法):
|
查 26.3 真实签名(解决 cannot find symbol 的最快办法):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
javap -cp ~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-merged-deobf/26.2/minecraft-merged-deobf-26.2.jar <类全名>
|
javap -cp ~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-merged-deobf/26.3/minecraft-merged-deobf-26.3.jar <类全名>
|
||||||
```
|
```
|
||||||
|
|
||||||
## 新增/修改玩法特性的套路
|
## 新增/修改玩法特性的套路
|
||||||
@@ -65,6 +64,17 @@ javap -cp ~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-me
|
|||||||
|
|
||||||
## 踩过的坑
|
## 踩过的坑
|
||||||
|
|
||||||
|
**26.3 变更**(相对 26.2,本次移植踩到的;写代码前先 javap 确认):
|
||||||
|
|
||||||
|
- **方块 codec 体系整个删了**:`Block` 不再有 `MapCodec CODEC` / `codec()`,`simpleCodec()` 也没了 —— 自定义方块直接删掉这两段即可(原版 `LayeredCauldronBlock` 同样已无 codec)
|
||||||
|
- `Entity.hurtMarked` 字段 → **`Entity.syncVelocity`**(仍是 public 字段,纯改名);`markHurt()` 照旧 protected
|
||||||
|
- `LivingEntity.drop(ItemStack, boolean)` → **`drop(ItemStack, boolean, Prediction)`**(`net.minecraft.util.Prediction`,枚举 `PREDICTED`/`SERVER_ONLY`)。服务端逻辑自己发起的掉落用 `SERVER_ONLY`,客户端没预测过
|
||||||
|
- `Entity.canSimulateMovement()` 变 **final**,改写点移到 **`getMoveSimulationType()`**(返回 `MoveSimulationType`,默认 `AUTHORITATIVE_SIDE`)。想让服务端始终模拟(纯服务端坐骑)返回 `AUTHORITATIVE_SIDE_AND_SERVER`
|
||||||
|
- 战利品数字提供器拆成 `providers.number.ints` / `.floats` 两个子包,且全部 `Holder` 化:`UniformGenerator.between(1.0F, 3.0F)` → **`ContextIntProviders.between(1, 3)`**(返回 `Holder<ContextIntProvider>`,`SetItemCountFunction.setCount` 现在收的就是它)
|
||||||
|
- `BonemealableBlock` 三个方法都多了一个 **`BonemealSource`** 参数(`INTERACTION`/`MOB`)
|
||||||
|
- `MinecraftServer.clockManager()` 的 `getTotalTicks(clock)` 没了 → **`clockManager().getInstance(clock).totalTicks()`**(`ClockInstance` 接口:`totalTicks/partialTick/rate/isPaused`);`addTicks`/`setTotalTicks` 等写操作仍在 manager 上
|
||||||
|
- `EnderMan` → **`Enderman`**(只是大小写,包没动)
|
||||||
|
|
||||||
**26.2 Mojmap 重命名**(相对 1.21.x,写代码前先 javap 确认):
|
**26.2 Mojmap 重命名**(相对 1.21.x,写代码前先 javap 确认):
|
||||||
|
|
||||||
- `ResourceLocation` → `net.minecraft.resources.Identifier`(`Identifier.fromNamespaceAndPath`)
|
- `ResourceLocation` → `net.minecraft.resources.Identifier`(`Identifier.fromNamespaceAndPath`)
|
||||||
@@ -76,7 +86,7 @@ javap -cp ~/.gradle/caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-me
|
|||||||
- `MobEffects.SLOWNESS`(不是 MOVEMENT_SLOWDOWN);`SimpleContainer` 没有 addListener 了(覆写 `setChanged()` 持久化)
|
- `MobEffects.SLOWNESS`(不是 MOVEMENT_SLOWDOWN);`SimpleContainer` 没有 addListener 了(覆写 `setChanged()` 持久化)
|
||||||
- `GameProfile` 是 record(`name()`/`id()`);`sendOverlayMessage`/`sendSystemMessage` 取代 `displayClientMessage`;命令权限 `Commands.LEVEL_GAMEMASTERS.check(source.permissions())`
|
- `GameProfile` 是 record(`name()`/`id()`);`sendOverlayMessage`/`sendSystemMessage` 取代 `displayClientMessage`;命令权限 `Commands.LEVEL_GAMEMASTERS.check(source.permissions())`
|
||||||
|
|
||||||
**Polymer(0.17.3+26.2)**:
|
**Polymer(0.18.0+26.3-rc-1 —— 上游还没出 26.3 正式版构建,先用 rc-1 那版)**:
|
||||||
|
|
||||||
- `PacketContext` 在 `net.fabricmc.fabric.api.networking.v1.context.PacketContext`
|
- `PacketContext` 在 `net.fabricmc.fabric.api.networking.v1.context.PacketContext`
|
||||||
- BE 类型必须 `PolymerBlockUtils.registerBlockEntity`,实体类型必须 `PolymerEntityUtils.registerType`(`RegistryHelper` 已封装)
|
- BE 类型必须 `PolymerBlockUtils.registerBlockEntity`,实体类型必须 `PolymerEntityUtils.registerType`(`RegistryHelper` 已封装)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# SFCraft 架构
|
# SFCraft 架构
|
||||||
|
|
||||||
Fabric **纯服务端** mod(`fabric.mod.json` 中 `environment: "server"`),Minecraft **26.2**,Java 25,**官方 Mojang 映射**。所有自定义内容通过 [Polymer](https://polymer.pb4.eu) 伪装成原版内容;玩家使用原版客户端 + 服务器发布的材质包即可游玩。
|
Fabric **纯服务端** mod(`fabric.mod.json` 中 `environment: "server"`),Minecraft **26.3**,Java 25,**官方 Mojang 映射**。所有自定义内容通过 [Polymer](https://polymer.pb4.eu) 伪装成原版内容;玩家使用原版客户端 + 服务器发布的材质包即可游玩。
|
||||||
|
|
||||||
## 分层
|
## 分层
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -14,14 +14,24 @@ RUN cd /mod && \
|
|||||||
cp /mod/build/libs/sfcraft-*-all.jar /sfcraft.jar
|
cp /mod/build/libs/sfcraft-*-all.jar /sfcraft.jar
|
||||||
|
|
||||||
FROM base AS justbackup-builder
|
FROM base AS justbackup-builder
|
||||||
RUN git clone https://github.com/saltedfishclub/justbackup /mod
|
# 跟随 MC 版本的分支。26.3 分支需先在上游仓库建好,否则此处会直接失败——
|
||||||
|
# 这是刻意的:默认分支仍是 26.2(fabric.mod.json 要求 minecraft ~26.2),
|
||||||
|
# 构建进 26.3 镜像只会在加载时才报错,不如在构建期暴露。
|
||||||
|
ARG JUSTBACKUP_REF=26.3
|
||||||
|
RUN git clone -b "$JUSTBACKUP_REF" https://github.com/saltedfishclub/justbackup /mod
|
||||||
RUN cd /mod && \
|
RUN cd /mod && \
|
||||||
./gradlew build && \
|
./gradlew build && \
|
||||||
rm -f /mod/build/libs/*sources.jar /mod/build/libs/*dev.jar && \
|
rm -f /mod/build/libs/*sources.jar /mod/build/libs/*dev.jar && \
|
||||||
cp /mod/build/libs/justbackup-1.0.0.jar /justbackup.jar
|
cp /mod/build/libs/justbackup-*.jar /justbackup.jar
|
||||||
|
|
||||||
FROM base AS carpet-builder
|
FROM base AS carpet-builder
|
||||||
RUN git clone https://github.com/saltedfishclub/fabric-carpet /mod
|
# 同上。carpet 的 depends 写的是 minecraft >26.2,能装上但 mixin 是按旧版编译的,
|
||||||
|
# 跑在 26.3 上会在 mixin apply 阶段炸,所以同样必须切到 26.3 分支。
|
||||||
|
# ⚠️ 注意:26.3 分支目前只是合了上游 master,而上游自己还停在 26.3-snapshot-9
|
||||||
|
# (对 26.3 正式版有 100+ 处编译错误)。等 gnembon 跟上 26.3 正式版、我们再合一次
|
||||||
|
# 之前,这一层产出的 carpet 装进 26.3 服务器大概率仍然会崩。
|
||||||
|
ARG CARPET_REF=26.3
|
||||||
|
RUN git clone -b "$CARPET_REF" https://github.com/saltedfishclub/fabric-carpet /mod
|
||||||
RUN cd /mod && \
|
RUN cd /mod && \
|
||||||
./gradlew build && \
|
./gradlew build && \
|
||||||
rm -f /mod/build/libs/*sources.jar /mod/build/libs/*dev.jar && \
|
rm -f /mod/build/libs/*sources.jar /mod/build/libs/*dev.jar && \
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ plugins {
|
|||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id("io.freefair.lombok") version "9.5.0"
|
id("io.freefair.lombok") version "9.5.0"
|
||||||
id("com.gradleup.shadow") version "9.3.1"
|
id("com.gradleup.shadow") version "9.3.1"
|
||||||
id 'net.fabricmc.fabric-loom' version '1.17-SNAPSHOT'
|
id 'net.fabricmc.fabric-loom' version '1.18.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
version = project.mod_version
|
version = project.mod_version
|
||||||
@@ -37,7 +37,7 @@ dependencies {
|
|||||||
shadow("org.apache.commons:commons-compress:1.28.0")
|
shadow("org.apache.commons:commons-compress:1.28.0")
|
||||||
shadow("io.ib67.kiwi:lang:1.2.3-SNAPSHOT")
|
shadow("io.ib67.kiwi:lang:1.2.3-SNAPSHOT")
|
||||||
shadow('com.github.houbb:pinyin:0.4.0')
|
shadow('com.github.houbb:pinyin:0.4.0')
|
||||||
def polymerVersion = "0.17.3+26.2"
|
def polymerVersion = "0.18.0+26.3-rc-1"
|
||||||
implementation include("eu.pb4:polymer-core:$polymerVersion")
|
implementation include("eu.pb4:polymer-core:$polymerVersion")
|
||||||
implementation include("eu.pb4:polymer-virtual-entity:$polymerVersion")
|
implementation include("eu.pb4:polymer-virtual-entity:$polymerVersion")
|
||||||
implementation include("eu.pb4:polymer-resource-pack:$polymerVersion")
|
implementation include("eu.pb4:polymer-resource-pack:$polymerVersion")
|
||||||
|
|||||||
+3
-3
@@ -3,8 +3,8 @@ org.gradle.jvmargs=-Xmx2G
|
|||||||
|
|
||||||
# Fabric Properties
|
# Fabric Properties
|
||||||
# check these on https://modmuss50.me/fabric.html
|
# check these on https://modmuss50.me/fabric.html
|
||||||
minecraft_version=26.2
|
minecraft_version=26.3
|
||||||
loader_version=0.19.3
|
loader_version=0.19.5
|
||||||
|
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
@@ -14,4 +14,4 @@ archives_base_name=sfcraft
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
# check this on https://modmuss50.me/fabric.html
|
# check this on https://modmuss50.me/fabric.html
|
||||||
fabric_version=0.155.0+26.2
|
fabric_version=0.160.5+26.3
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#Wed Jul 22 12:25:27 GMT+08:00 2026
|
#Wed Jul 22 12:25:27 GMT+08:00 2026
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
§b§lSaltedFish Club Server §r§8| §f26.2 §c§lv2.2
|
§b§lSaltedFish Club Server §r§8| §f26.3 §c§lv2.2
|
||||||
§8§lINVITED PLAYERS ONLY. 服务器启用白名单.
|
§8§lINVITED PLAYERS ONLY. 服务器启用白名单.
|
||||||
@@ -2,7 +2,7 @@ hash-format = "sha256"
|
|||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "mods/fabric-api.pw.toml"
|
file = "mods/fabric-api.pw.toml"
|
||||||
hash = "dc5490cc1a9231b0d32ec3e72f9d34279fc1d1ff6fb5a7d288b1f36faec0a6a0"
|
hash = "e3a8b560330802ec556f34a7de7a73bc712202359a657a36b39e6f85598bf8b8"
|
||||||
metafile = true
|
metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
@@ -47,12 +47,12 @@ metafile = true
|
|||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "mods/polymer.pw.toml"
|
file = "mods/polymer.pw.toml"
|
||||||
hash = "2a5626a6d9b61f968f294466e9756fbafa1c1208b4fb53c53cfc8d2afb42a857"
|
hash = "2dec8c17d9d6fdf6dd93e2dc458c8d30ead86b2efc292490142841aacd4d3e95"
|
||||||
metafile = true
|
metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "mods/simple-voice-chat.pw.toml"
|
file = "mods/simple-voice-chat.pw.toml"
|
||||||
hash = "be7369da963f5326a771bbfa360365ac6ed939b01c2c268f4dd4a922718cd4a3"
|
hash = "e55e25489644c7297a7f4cda6618437891fd0aba88e7f670a0439c3eadc14fc0"
|
||||||
metafile = true
|
metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
name = "Fabric API"
|
name = "Fabric API"
|
||||||
filename = "fabric-api-0.155.2+26.2.jar"
|
filename = "fabric-api-0.160.5+26.3.jar"
|
||||||
side = "both"
|
side = "both"
|
||||||
|
|
||||||
[download]
|
[download]
|
||||||
url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/lVXlbH4w/fabric-api-0.155.2%2B26.2.jar"
|
url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/oCYG2H4L/fabric-api-0.160.5%2B26.3.jar"
|
||||||
hash-format = "sha512"
|
hash-format = "sha512"
|
||||||
hash = "cc56984378a27c5bcd56374d6ffbb27a45c6bf3355add2ac6be9817ccac5854362249bf9d0147eb271a70fda2716129204e240d53c9aa876a2a7861f4c7f880f"
|
hash = "494378477dd9571f49c27cc1eaee3f78f140b9d8bd860c757f453bee198fd5b8a9eee444069c595b0fb6f8cb6bc86d51ccddbcec8d782fd46ef2fb1d9ac22f16"
|
||||||
|
|
||||||
[update]
|
[update]
|
||||||
[update.modrinth]
|
[update.modrinth]
|
||||||
mod-id = "P7dR8mSH"
|
mod-id = "P7dR8mSH"
|
||||||
version = "lVXlbH4w"
|
version = "oCYG2H4L"
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
name = "Polymer"
|
name = "Polymer"
|
||||||
filename = "polymer-bundled-0.17.3+26.2.jar"
|
filename = "polymer-bundled-0.18.0+26.3-rc-1.jar"
|
||||||
side = "both"
|
side = "both"
|
||||||
|
|
||||||
[download]
|
[download]
|
||||||
url = "https://cdn.modrinth.com/data/xGdtZczs/versions/w0N4I45x/polymer-bundled-0.17.3%2B26.2.jar"
|
url = "https://cdn.modrinth.com/data/xGdtZczs/versions/QBYrHLvh/polymer-bundled-0.18.0%2B26.3-rc-1.jar"
|
||||||
hash-format = "sha512"
|
hash-format = "sha512"
|
||||||
hash = "1459edf99834bbbb2eff5f7df2e7159688ad88e20e8704bc473752e81a755963bb96c511641d53c9f7b4e5867436f7b49ef839e3b857c48ca54af93627c2a110"
|
hash = "59cc2d99ead79e71b1b9df0751157f42cecf3cb0d67b4e9494ffe256e9375d9c111c7d126cc6a73161e16c39b6d5c87090aac5468ab08cd8982ed1ba55134869"
|
||||||
|
|
||||||
[update]
|
[update]
|
||||||
[update.modrinth]
|
[update.modrinth]
|
||||||
mod-id = "xGdtZczs"
|
mod-id = "xGdtZczs"
|
||||||
version = "w0N4I45x"
|
version = "QBYrHLvh"
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
name = "Simple Voice Chat"
|
name = "Simple Voice Chat"
|
||||||
filename = "voicechat-fabric-2.6.21+26.2.jar"
|
filename = "voicechat-fabric-2.6.23+26.3.jar"
|
||||||
side = "both"
|
side = "both"
|
||||||
|
|
||||||
[download]
|
[download]
|
||||||
url = "https://cdn.modrinth.com/data/9eGKb6K1/versions/3SOh5iiX/voicechat-fabric-2.6.21%2B26.2.jar"
|
url = "https://cdn.modrinth.com/data/9eGKb6K1/versions/3fRw6NfP/voicechat-fabric-2.6.23%2B26.3.jar"
|
||||||
hash-format = "sha512"
|
hash-format = "sha512"
|
||||||
hash = "0308b2e18a05913faaf461530a475d2f5debbfb4d0a46107b512ce51166ceb6cfdbea468f7e21444a3e005655e51729c3764266f81c59b6228629b4866d16b28"
|
hash = "fd62ca0b4048ab855e7f2436ecdf6c7301e546946afe2d7a3dfa3fb7c180636cbd971c0e9c5e62be4a86d67bbed54fa62ab4d4680c47ea0b366e49b9202ff42a"
|
||||||
|
|
||||||
[update]
|
[update]
|
||||||
[update.modrinth]
|
[update.modrinth]
|
||||||
mod-id = "9eGKb6K1"
|
mod-id = "9eGKb6K1"
|
||||||
version = "3SOh5iiX"
|
version = "3fRw6NfP"
|
||||||
@@ -6,11 +6,11 @@ pack-format = "packwiz:1.1.0"
|
|||||||
[index]
|
[index]
|
||||||
file = "index.toml"
|
file = "index.toml"
|
||||||
hash-format = "sha256"
|
hash-format = "sha256"
|
||||||
hash = "d7c215cba51d8e9324316d5c236c6f03727012917340b2100c868614170ba69e"
|
hash = "7524eced8051fa2ae3e84ae584afcf9863dd121db99555618b517447540daed1"
|
||||||
|
|
||||||
[versions]
|
[versions]
|
||||||
fabric = "0.19.3"
|
fabric = "0.19.5"
|
||||||
minecraft = "26.2"
|
minecraft = "26.3"
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
acceptable-game-versions = ["26.1", "26.1.2"]
|
acceptable-game-versions = ["26.2"]
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package io.ib67.sfcraft.mixin.common.fixes;
|
package io.ib67.sfcraft.mixin.common.fixes;
|
||||||
|
|
||||||
import io.ib67.sfcraft.mixin.common.MobEntityMixin;
|
import io.ib67.sfcraft.mixin.common.MobEntityMixin;
|
||||||
import net.minecraft.world.entity.monster.EnderMan;
|
import net.minecraft.world.entity.monster.Enderman;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
@Mixin(EnderMan.class)
|
@Mixin(Enderman.class)
|
||||||
public abstract class EndermanEntityMixin extends MobEntityMixin {
|
public abstract class EndermanEntityMixin extends MobEntityMixin {
|
||||||
@Inject(at = @At("HEAD"), method = "requiresCustomPersistence", cancellable = true)
|
@Inject(at = @At("HEAD"), method = "requiresCustomPersistence", cancellable = true)
|
||||||
private void sf$cannotDespawn(CallbackInfoReturnable<Boolean> cir) {
|
private void sf$cannotDespawn(CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import net.minecraft.world.level.Level;
|
|||||||
import net.minecraft.world.level.storage.ValueInput;
|
import net.minecraft.world.level.storage.ValueInput;
|
||||||
import net.minecraft.world.level.storage.ValueOutput;
|
import net.minecraft.world.level.storage.ValueOutput;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import net.minecraft.world.entity.MoveSimulationType;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
@@ -78,10 +79,12 @@ public abstract class HoglinMountMixin extends Animal implements MountAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canSimulateMovement() {
|
public MoveSimulationType getMoveSimulationType() {
|
||||||
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟
|
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟。
|
||||||
if (!level().isClientSide() && getControllingPassenger() != null) return true;
|
// 26.3 起 canSimulateMovement() 变 final,改写点移到这里:AUTHORITATIVE_SIDE_AND_SERVER
|
||||||
return super.canSimulateMovement();
|
// 的语义就是"服务端永远模拟 + 权威端模拟",等价于原来的 !isClientSide 分支。
|
||||||
|
if (getControllingPassenger() != null) return MoveSimulationType.AUTHORITATIVE_SIDE_AND_SERVER;
|
||||||
|
return super.getMoveSimulationType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import net.minecraft.world.level.Level;
|
|||||||
import net.minecraft.world.level.storage.ValueInput;
|
import net.minecraft.world.level.storage.ValueInput;
|
||||||
import net.minecraft.world.level.storage.ValueOutput;
|
import net.minecraft.world.level.storage.ValueOutput;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import net.minecraft.world.entity.MoveSimulationType;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
@@ -82,10 +83,12 @@ public abstract class RavagerMountMixin extends Raider implements MountAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canSimulateMovement() {
|
public MoveSimulationType getMoveSimulationType() {
|
||||||
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟
|
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟。
|
||||||
if (!level().isClientSide() && getControllingPassenger() != null) return true;
|
// 26.3 起 canSimulateMovement() 变 final,改写点移到这里:AUTHORITATIVE_SIDE_AND_SERVER
|
||||||
return super.canSimulateMovement();
|
// 的语义就是"服务端永远模拟 + 权威端模拟",等价于原来的 !isClientSide 分支。
|
||||||
|
if (getControllingPassenger() != null) return MoveSimulationType.AUTHORITATIVE_SIDE_AND_SERVER;
|
||||||
|
return super.getMoveSimulationType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import net.minecraft.sounds.SoundEvents;
|
|||||||
import net.minecraft.sounds.SoundSource;
|
import net.minecraft.sounds.SoundSource;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.level.block.BonemealableBlock;
|
import net.minecraft.world.level.block.BonemealableBlock;
|
||||||
|
import net.minecraft.world.level.block.BonemealSource;
|
||||||
|
|
||||||
public class FartFertilizerModule extends ServerModule {
|
public class FartFertilizerModule extends ServerModule {
|
||||||
private final Object2LongMap<Player> lastSneaked = new Object2LongOpenHashMap<>();
|
private final Object2LongMap<Player> lastSneaked = new Object2LongOpenHashMap<>();
|
||||||
@@ -63,7 +64,7 @@ public class FartFertilizerModule extends ServerModule {
|
|||||||
}
|
}
|
||||||
if (player.getRandom().nextInt(10) < 3) {
|
if (player.getRandom().nextInt(10) < 3) {
|
||||||
((BonemealableBlock) block).performBonemeal(wld,
|
((BonemealableBlock) block).performBonemeal(wld,
|
||||||
wld.getRandom(), blockPos, state);
|
wld.getRandom(), blockPos, state, BonemealSource.INTERACTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import net.minecraft.world.level.storage.loot.LootPool;
|
|||||||
import net.minecraft.world.level.storage.loot.entries.LootItem;
|
import net.minecraft.world.level.storage.loot.entries.LootItem;
|
||||||
import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction;
|
import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction;
|
||||||
import net.minecraft.world.level.storage.loot.predicates.LootItemRandomChanceCondition;
|
import net.minecraft.world.level.storage.loot.predicates.LootItemRandomChanceCondition;
|
||||||
import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator;
|
import net.minecraft.world.level.storage.loot.providers.number.ints.ContextIntProviders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 三种投掷炸弹(普通/黑曜石/烈焰)与它们的战利品注入(地牢箱、苦力怕掉落)。
|
* 三种投掷炸弹(普通/黑曜石/烈焰)与它们的战利品注入(地牢箱、苦力怕掉落)。
|
||||||
@@ -76,11 +76,11 @@ public class BombModule extends ServerModule {
|
|||||||
tableBuilder.withPool(LootPool.lootPool()
|
tableBuilder.withPool(LootPool.lootPool()
|
||||||
.when(LootItemRandomChanceCondition.randomChance(loot.dungeonBombChance))
|
.when(LootItemRandomChanceCondition.randomChance(loot.dungeonBombChance))
|
||||||
.add(LootItem.lootTableItem(bomb)
|
.add(LootItem.lootTableItem(bomb)
|
||||||
.apply(SetItemCountFunction.setCount(UniformGenerator.between(1.0F, 3.0F)))));
|
.apply(SetItemCountFunction.setCount(ContextIntProviders.between(1, 3)))));
|
||||||
tableBuilder.withPool(LootPool.lootPool()
|
tableBuilder.withPool(LootPool.lootPool()
|
||||||
.when(LootItemRandomChanceCondition.randomChance(loot.dungeonObsidianBombChance))
|
.when(LootItemRandomChanceCondition.randomChance(loot.dungeonObsidianBombChance))
|
||||||
.add(LootItem.lootTableItem(obsidianBomb)
|
.add(LootItem.lootTableItem(obsidianBomb)
|
||||||
.apply(SetItemCountFunction.setCount(UniformGenerator.between(1.0F, 2.0F)))));
|
.apply(SetItemCountFunction.setCount(ContextIntProviders.between(1, 2)))));
|
||||||
}
|
}
|
||||||
EntityTypes.CREEPER.getDefaultLootTable().ifPresent(creeperTable -> {
|
EntityTypes.CREEPER.getDefaultLootTable().ifPresent(creeperTable -> {
|
||||||
if (creeperTable.equals(key)) {
|
if (creeperTable.equals(key)) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package io.ib67.sfcraft.module.game.cauldron;
|
package io.ib67.sfcraft.module.game.cauldron;
|
||||||
|
|
||||||
import com.mojang.serialization.MapCodec;
|
|
||||||
import eu.pb4.polymer.core.api.block.PolymerBlock;
|
import eu.pb4.polymer.core.api.block.PolymerBlock;
|
||||||
import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder;
|
import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder;
|
||||||
import eu.pb4.polymer.virtualentity.api.ElementHolder;
|
import eu.pb4.polymer.virtualentity.api.ElementHolder;
|
||||||
@@ -37,7 +36,6 @@ import java.util.function.BiFunction;
|
|||||||
|
|
||||||
public class AmethystCauldronBlock extends BaseEntityBlock implements PolymerBlock, BlockWithElementHolder {
|
public class AmethystCauldronBlock extends BaseEntityBlock implements PolymerBlock, BlockWithElementHolder {
|
||||||
public static final BooleanProperty HAS_WATER = BooleanProperty.create("has_water");
|
public static final BooleanProperty HAS_WATER = BooleanProperty.create("has_water");
|
||||||
public static final MapCodec<AmethystCauldronBlock> CODEC = simpleCodec(AmethystCauldronBlock::new);
|
|
||||||
// Polymer 会把该偏移加到方块中心(Vec3.atCenterOf), 因此这里是相对中心的偏移:
|
// Polymer 会把该偏移加到方块中心(Vec3.atCenterOf), 因此这里是相对中心的偏移:
|
||||||
// 结果约为 (x+0.5, y+0.55, z+0.5), 即锅内水中、锅沿以下、水平居中。
|
// 结果约为 (x+0.5, y+0.55, z+0.5), 即锅内水中、锅沿以下、水平居中。
|
||||||
private static final Vec3 ELEMENT_HOLDER_OFFSET = new Vec3(0.0, 0.05, 0.0);
|
private static final Vec3 ELEMENT_HOLDER_OFFSET = new Vec3(0.0, 0.05, 0.0);
|
||||||
@@ -57,11 +55,6 @@ public class AmethystCauldronBlock extends BaseEntityBlock implements PolymerBlo
|
|||||||
this.blockEntityFactory = factory;
|
this.blockEntityFactory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected @NonNull MapCodec<? extends BaseEntityBlock> codec() {
|
|
||||||
return CODEC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||||
builder.add(HAS_WATER);
|
builder.add(HAS_WATER);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
|||||||
import net.minecraft.world.level.storage.ValueInput;
|
import net.minecraft.world.level.storage.ValueInput;
|
||||||
import net.minecraft.world.level.storage.ValueOutput;
|
import net.minecraft.world.level.storage.ValueOutput;
|
||||||
import net.minecraft.world.phys.AABB;
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.util.Prediction;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -124,7 +125,7 @@ public class AmethystCauldronBlockEntity extends BlockEntity {
|
|||||||
if (contents.isEmpty()) return InteractionResult.PASS;
|
if (contents.isEmpty()) return InteractionResult.PASS;
|
||||||
var stack = contents.removeLast();
|
var stack = contents.removeLast();
|
||||||
if (!player.getInventory().add(stack)) {
|
if (!player.getInventory().add(stack)) {
|
||||||
player.drop(stack, false);
|
player.drop(stack, false, Prediction.SERVER_ONLY);
|
||||||
}
|
}
|
||||||
level.playSound(null, getBlockPos(), SoundEvents.ITEM_FRAME_REMOVE_ITEM, SoundSource.BLOCKS, 0.6F, 1.0F);
|
level.playSound(null, getBlockPos(), SoundEvents.ITEM_FRAME_REMOVE_ITEM, SoundSource.BLOCKS, 0.6F, 1.0F);
|
||||||
setChanged();
|
setChanged();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package io.ib67.sfcraft.module.game.crystal;
|
package io.ib67.sfcraft.module.game.crystal;
|
||||||
|
|
||||||
import com.mojang.serialization.MapCodec;
|
|
||||||
import eu.pb4.polymer.core.api.block.PolymerBlock;
|
import eu.pb4.polymer.core.api.block.PolymerBlock;
|
||||||
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
|
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
@@ -29,7 +28,6 @@ import java.util.function.BiFunction;
|
|||||||
|
|
||||||
public class GravityCrystalBlock extends BaseEntityBlock implements PolymerBlock {
|
public class GravityCrystalBlock extends BaseEntityBlock implements PolymerBlock {
|
||||||
public static final IntegerProperty CHARGE = RespawnAnchorBlock.CHARGE;
|
public static final IntegerProperty CHARGE = RespawnAnchorBlock.CHARGE;
|
||||||
public static final MapCodec<GravityCrystalBlock> CODEC = simpleCodec(GravityCrystalBlock::new);
|
|
||||||
|
|
||||||
// GravityCrystalModule 注册方块实体类型后回填(方块与其方块实体类型互相引用,无法在构造器闭合)
|
// GravityCrystalModule 注册方块实体类型后回填(方块与其方块实体类型互相引用,无法在构造器闭合)
|
||||||
private BlockEntityType<GravityCrystalBlockEntity> blockEntityType;
|
private BlockEntityType<GravityCrystalBlockEntity> blockEntityType;
|
||||||
@@ -46,11 +44,6 @@ public class GravityCrystalBlock extends BaseEntityBlock implements PolymerBlock
|
|||||||
this.blockEntityFactory = factory;
|
this.blockEntityFactory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected MapCodec<? extends BaseEntityBlock> codec() {
|
|
||||||
return CODEC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||||
builder.add(CHARGE);
|
builder.add(CHARGE);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class GravityCrystalBlockEntity extends BlockEntity {
|
|||||||
boosted = boosted.normalize().scale(maxSpeed);
|
boosted = boosted.normalize().scale(maxSpeed);
|
||||||
}
|
}
|
||||||
entity.setDeltaMovement(boosted);
|
entity.setDeltaMovement(boosted);
|
||||||
entity.hurtMarked = true;
|
entity.syncVelocity = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import net.minecraft.world.item.Items;
|
|||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import net.minecraft.world.item.component.TooltipDisplay;
|
import net.minecraft.world.item.component.TooltipDisplay;
|
||||||
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
||||||
|
import net.minecraft.util.Prediction;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@@ -528,7 +529,7 @@ public class MapArtModule extends ServerModule {
|
|||||||
|
|
||||||
private static void give(ServerPlayer player, ItemStack stack) {
|
private static void give(ServerPlayer player, ItemStack stack) {
|
||||||
if (!player.getInventory().add(stack)) {
|
if (!player.getInventory().add(stack)) {
|
||||||
player.drop(stack, false);
|
player.drop(stack, false, Prediction.SERVER_ONLY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class ElytraPortalModule extends ServerModule {
|
|||||||
player.setDeltaMovement(recorded);
|
player.setDeltaMovement(recorded);
|
||||||
// 玩家移动是客户端权威的,而客户端同样预测到了这次撞墙。不补一个速度包的话,
|
// 玩家移动是客户端权威的,而客户端同样预测到了这次撞墙。不补一个速度包的话,
|
||||||
// 服务端这边写回去的速度会立刻被客户端的位置汇报盖掉。
|
// 服务端这边写回去的速度会立刻被客户端的位置汇报盖掉。
|
||||||
player.hurtMarked = true;
|
player.syncVelocity = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getInstantTransitionTicks() {
|
public int getInstantTransitionTicks() {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import net.minecraft.world.item.Items;
|
|||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import net.minecraft.world.item.component.ItemLore;
|
import net.minecraft.world.item.component.ItemLore;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.util.Prediction;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -93,7 +94,7 @@ public class PearlTokenItem extends Item implements PolymerItem {
|
|||||||
} else {
|
} else {
|
||||||
stack.shrink(1);
|
stack.shrink(1);
|
||||||
if (!player.getInventory().add(bound)) {
|
if (!player.getInventory().add(bound)) {
|
||||||
player.drop(bound, false);
|
player.drop(bound, false, Prediction.SERVER_ONLY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.sendOverlayMessage(Component.translatable("message.sfcraft.token.bound"));
|
player.sendOverlayMessage(Component.translatable("message.sfcraft.token.bound"));
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ public class CreativeRoomModule extends ServerModule {
|
|||||||
private int onQueryTime(CommandContext<CommandSourceStack> ctx) {
|
private int onQueryTime(CommandContext<CommandSourceStack> ctx) {
|
||||||
var player = requirePlaygroundPlayer(ctx);
|
var player = requirePlaygroundPlayer(ctx);
|
||||||
if (player == null) return Command.SINGLE_SUCCESS;
|
if (player == null) return Command.SINGLE_SUCCESS;
|
||||||
long ticks = ctx.getSource().getServer().clockManager().getTotalTicks(playgroundClock(ctx.getSource()));
|
long ticks = ctx.getSource().getServer().clockManager().getInstance(playgroundClock(ctx.getSource())).totalTicks();
|
||||||
player.sendSystemMessage(Component.translatable("message.sfcraft.playground.time_query", wrapTime(ticks)));
|
player.sendSystemMessage(Component.translatable("message.sfcraft.playground.time_query", wrapTime(ticks)));
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user