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:
iceBear67andClaude Opus 5 committed 2026-09-15 15:54:54 +00:00
1 parent 08be6bc3ae
commit 2b70971b80
25 files changed
+89 -73

No files matched your search

+19 -9
View File
@@ -1,6 +1,6 @@
# 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`。
- 查 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、文件权限操作等。
## 构建与验证
仓库**没有** gradlew wrapper,使用系统 gradle + sdkman JDK:
仓库自带 gradlew wrapper(**9.7.1** —— loom 1.18.x 要求 Gradle ≥ 9.7,Dockerfile 也走 `./gradlew`):
```bash
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 已接受)
./gradlew compileJava --console=plain # 编译(mixin AP 会校验注入目标)
./gradlew runServer --console=plain # dev 服务器(run/ 目录,EULA 已接受)
```
查 26.2 真实签名(解决 cannot find symbol 的最快办法):
查 26.3 真实签名(解决 cannot find symbol 的最快办法):
```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 确认):
- `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()` 持久化)
- `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`
- BE 类型必须 `PolymerBlockUtils.registerBlockEntity`,实体类型必须 `PolymerEntityUtils.registerType`(`RegistryHelper` 已封装)
+1 -1
View File
@@ -1,6 +1,6 @@
# 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
View File
@@ -14,14 +14,24 @@ RUN cd /mod && \
cp /mod/build/libs/sfcraft-*-all.jar /sfcraft.jar
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 && \
./gradlew build && \
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
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 && \
./gradlew build && \
rm -f /mod/build/libs/*sources.jar /mod/build/libs/*dev.jar && \
+2 -2
View File
@@ -2,7 +2,7 @@ plugins {
id 'maven-publish'
id("io.freefair.lombok") version "9.5.0"
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
@@ -37,7 +37,7 @@ dependencies {
shadow("org.apache.commons:commons-compress:1.28.0")
shadow("io.ib67.kiwi:lang:1.2.3-SNAPSHOT")
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-virtual-entity:$polymerVersion")
implementation include("eu.pb4:polymer-resource-pack:$polymerVersion")
+3 -3
View File
@@ -3,8 +3,8 @@ org.gradle.jvmargs=-Xmx2G
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=26.2
loader_version=0.19.3
minecraft_version=26.3
loader_version=0.19.5
# Mod Properties
@@ -14,4 +14,4 @@ archives_base_name=sfcraft
# Dependencies
# check this on https://modmuss50.me/fabric.html
fabric_version=0.155.0+26.2
fabric_version=0.160.5+26.3
+1 -1
View File
@@ -1,6 +1,6 @@
#Wed Jul 22 12:25:27 GMT+08:00 2026
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists
+1 -1
View File
@@ -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. 服务器启用白名单.
+3 -3
View File
@@ -2,7 +2,7 @@ hash-format = "sha256"
[[files]]
file = "mods/fabric-api.pw.toml"
hash = "dc5490cc1a9231b0d32ec3e72f9d34279fc1d1ff6fb5a7d288b1f36faec0a6a0"
hash = "e3a8b560330802ec556f34a7de7a73bc712202359a657a36b39e6f85598bf8b8"
metafile = true
[[files]]
@@ -47,12 +47,12 @@ metafile = true
[[files]]
file = "mods/polymer.pw.toml"
hash = "2a5626a6d9b61f968f294466e9756fbafa1c1208b4fb53c53cfc8d2afb42a857"
hash = "2dec8c17d9d6fdf6dd93e2dc458c8d30ead86b2efc292490142841aacd4d3e95"
metafile = true
[[files]]
file = "mods/simple-voice-chat.pw.toml"
hash = "be7369da963f5326a771bbfa360365ac6ed939b01c2c268f4dd4a922718cd4a3"
hash = "e55e25489644c7297a7f4cda6618437891fd0aba88e7f670a0439c3eadc14fc0"
metafile = true
[[files]]
+4 -4
View File
@@ -1,13 +1,13 @@
name = "Fabric API"
filename = "fabric-api-0.155.2+26.2.jar"
filename = "fabric-api-0.160.5+26.3.jar"
side = "both"
[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 = "cc56984378a27c5bcd56374d6ffbb27a45c6bf3355add2ac6be9817ccac5854362249bf9d0147eb271a70fda2716129204e240d53c9aa876a2a7861f4c7f880f"
hash = "494378477dd9571f49c27cc1eaee3f78f140b9d8bd860c757f453bee198fd5b8a9eee444069c595b0fb6f8cb6bc86d51ccddbcec8d782fd46ef2fb1d9ac22f16"
[update]
[update.modrinth]
mod-id = "P7dR8mSH"
version = "lVXlbH4w"
version = "oCYG2H4L"
+4 -4
View File
@@ -1,13 +1,13 @@
name = "Polymer"
filename = "polymer-bundled-0.17.3+26.2.jar"
filename = "polymer-bundled-0.18.0+26.3-rc-1.jar"
side = "both"
[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 = "1459edf99834bbbb2eff5f7df2e7159688ad88e20e8704bc473752e81a755963bb96c511641d53c9f7b4e5867436f7b49ef839e3b857c48ca54af93627c2a110"
hash = "59cc2d99ead79e71b1b9df0751157f42cecf3cb0d67b4e9494ffe256e9375d9c111c7d126cc6a73161e16c39b6d5c87090aac5468ab08cd8982ed1ba55134869"
[update]
[update.modrinth]
mod-id = "xGdtZczs"
version = "w0N4I45x"
version = "QBYrHLvh"
@@ -1,13 +1,13 @@
name = "Simple Voice Chat"
filename = "voicechat-fabric-2.6.21+26.2.jar"
filename = "voicechat-fabric-2.6.23+26.3.jar"
side = "both"
[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 = "0308b2e18a05913faaf461530a475d2f5debbfb4d0a46107b512ce51166ceb6cfdbea468f7e21444a3e005655e51729c3764266f81c59b6228629b4866d16b28"
hash = "fd62ca0b4048ab855e7f2436ecdf6c7301e546946afe2d7a3dfa3fb7c180636cbd971c0e9c5e62be4a86d67bbed54fa62ab4d4680c47ea0b366e49b9202ff42a"
[update]
[update.modrinth]
mod-id = "9eGKb6K1"
version = "3SOh5iiX"
version = "3fRw6NfP"
+4 -4
View File
@@ -6,11 +6,11 @@ pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "d7c215cba51d8e9324316d5c236c6f03727012917340b2100c868614170ba69e"
hash = "7524eced8051fa2ae3e84ae584afcf9863dd121db99555618b517447540daed1"
[versions]
fabric = "0.19.3"
minecraft = "26.2"
fabric = "0.19.5"
minecraft = "26.3"
[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;
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.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(EnderMan.class)
@Mixin(Enderman.class)
public abstract class EndermanEntityMixin extends MobEntityMixin {
@Inject(at = @At("HEAD"), method = "requiresCustomPersistence", cancellable = true)
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.ValueOutput;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.entity.MoveSimulationType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -78,10 +79,12 @@ public abstract class HoglinMountMixin extends Animal implements MountAccess {
}
@Override
public boolean canSimulateMovement() {
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟
if (!level().isClientSide() && getControllingPassenger() != null) return true;
return super.canSimulateMovement();
public MoveSimulationType getMoveSimulationType() {
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟。
// 26.3 起 canSimulateMovement() 变 final,改写点移到这里:AUTHORITATIVE_SIDE_AND_SERVER
// 的语义就是"服务端永远模拟 + 权威端模拟",等价于原来的 !isClientSide 分支。
if (getControllingPassenger() != null) return MoveSimulationType.AUTHORITATIVE_SIDE_AND_SERVER;
return super.getMoveSimulationType();
}
@Override
@@ -12,6 +12,7 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.entity.MoveSimulationType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -82,10 +83,12 @@ public abstract class RavagerMountMixin extends Raider implements MountAccess {
}
@Override
public boolean canSimulateMovement() {
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟
if (!level().isClientSide() && getControllingPassenger() != null) return true;
return super.canSimulateMovement();
public MoveSimulationType getMoveSimulationType() {
// 骑手是 Player 时会被判为客户端权威,但纯服务端坐骑客户端不会模拟 → 强制服务端模拟。
// 26.3 起 canSimulateMovement() 变 final,改写点移到这里:AUTHORITATIVE_SIDE_AND_SERVER
// 的语义就是"服务端永远模拟 + 权威端模拟",等价于原来的 !isClientSide 分支。
if (getControllingPassenger() != null) return MoveSimulationType.AUTHORITATIVE_SIDE_AND_SERVER;
return super.getMoveSimulationType();
}
@Override
@@ -16,6 +16,7 @@ import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BonemealSource;
public class FartFertilizerModule extends ServerModule {
private final Object2LongMap<Player> lastSneaked = new Object2LongOpenHashMap<>();
@@ -63,7 +64,7 @@ public class FartFertilizerModule extends ServerModule {
}
if (player.getRandom().nextInt(10) < 3) {
((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.functions.SetItemCountFunction;
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()
.when(LootItemRandomChanceCondition.randomChance(loot.dungeonBombChance))
.add(LootItem.lootTableItem(bomb)
.apply(SetItemCountFunction.setCount(UniformGenerator.between(1.0F, 3.0F)))));
.apply(SetItemCountFunction.setCount(ContextIntProviders.between(1, 3)))));
tableBuilder.withPool(LootPool.lootPool()
.when(LootItemRandomChanceCondition.randomChance(loot.dungeonObsidianBombChance))
.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 -> {
if (creeperTable.equals(key)) {
@@ -1,6 +1,5 @@
package io.ib67.sfcraft.module.game.cauldron;
import com.mojang.serialization.MapCodec;
import eu.pb4.polymer.core.api.block.PolymerBlock;
import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder;
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 static final BooleanProperty HAS_WATER = BooleanProperty.create("has_water");
public static final MapCodec<AmethystCauldronBlock> CODEC = simpleCodec(AmethystCauldronBlock::new);
// Polymer 会把该偏移加到方块中心(Vec3.atCenterOf), 因此这里是相对中心的偏移:
// 结果约为 (x+0.5, y+0.55, z+0.5), 即锅内水中、锅沿以下、水平居中。
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;
}
@Override
protected @NonNull MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
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.ValueOutput;
import net.minecraft.world.phys.AABB;
import net.minecraft.util.Prediction;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@@ -124,7 +125,7 @@ public class AmethystCauldronBlockEntity extends BlockEntity {
if (contents.isEmpty()) return InteractionResult.PASS;
var stack = contents.removeLast();
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);
setChanged();
@@ -1,6 +1,5 @@
package io.ib67.sfcraft.module.game.crystal;
import com.mojang.serialization.MapCodec;
import eu.pb4.polymer.core.api.block.PolymerBlock;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.core.BlockPos;
@@ -29,7 +28,6 @@ import java.util.function.BiFunction;
public class GravityCrystalBlock extends BaseEntityBlock implements PolymerBlock {
public static final IntegerProperty CHARGE = RespawnAnchorBlock.CHARGE;
public static final MapCodec<GravityCrystalBlock> CODEC = simpleCodec(GravityCrystalBlock::new);
// GravityCrystalModule 注册方块实体类型后回填(方块与其方块实体类型互相引用,无法在构造器闭合)
private BlockEntityType<GravityCrystalBlockEntity> blockEntityType;
@@ -46,11 +44,6 @@ public class GravityCrystalBlock extends BaseEntityBlock implements PolymerBlock
this.blockEntityFactory = factory;
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(CHARGE);
@@ -56,7 +56,7 @@ public class GravityCrystalBlockEntity extends BlockEntity {
boosted = boosted.normalize().scale(maxSpeed);
}
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.TooltipDisplay;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import net.minecraft.util.Prediction;
import org.jspecify.annotations.Nullable;
import javax.imageio.ImageIO;
@@ -528,7 +529,7 @@ public class MapArtModule extends ServerModule {
private static void give(ServerPlayer player, ItemStack 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.hurtMarked = true;
player.syncVelocity = true;
}
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.ItemLore;
import net.minecraft.world.level.Level;
import net.minecraft.util.Prediction;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -93,7 +94,7 @@ public class PearlTokenItem extends Item implements PolymerItem {
} else {
stack.shrink(1);
if (!player.getInventory().add(bound)) {
player.drop(bound, false);
player.drop(bound, false, Prediction.SERVER_ONLY);
}
}
player.sendOverlayMessage(Component.translatable("message.sfcraft.token.bound"));
@@ -211,7 +211,7 @@ public class CreativeRoomModule extends ServerModule {
private int onQueryTime(CommandContext<CommandSourceStack> ctx) {
var player = requirePlaygroundPlayer(ctx);
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)));
return Command.SINGLE_SUCCESS;
}