From 3c5621b9b981b3eaace9fb5c9640be1d83ba11dc Mon Sep 17 00:00:00 2001 From: database-mysql Date: Mon, 19 Oct 2026 13:26:41 +0800 Subject: [PATCH] fix: persist flight state and fly speed per player (JSON config), restore on join/respawn --- README.md | 25 ++-- gradle.properties | 2 +- .../java/mods/openfly/Commands/fly.java | 131 ++++++++++++++++-- .../top/gtb520/java/mods/openfly/OpenFly.java | 18 ++- 4 files changed, 148 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 8abe346..1ba0bc5 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ ## 功能特性 - 🕊️ **自由飞行** — 通过命令开启/关闭创造模式飞行能力 -- ⚡ **飞行速度调节** — 通过命令在 0.1–2.0 范围内自定义飞行速度 +- ⚡ **飞行速度调节** — 通过命令在 0.01–0.15 范围内自定义飞行速度 - 🛡️ **摔落保护** — 飞行状态下免疫摔落伤害 -- 💾 **状态持久化** — 重新加入服务器时自动恢复飞行状态 +- 💾 **状态持久化** — 飞行状态与速度保存到配置文件,退出重进或服务器重启后自动恢复 - 👥 **多玩家支持** — 管理员可为其他玩家切换飞行状态 ## 环境要求 @@ -37,16 +37,17 @@ - 如果当前未飞行,执行后**开启飞行** - 如果当前正在飞行,执行后**关闭飞行** -### `/fly speed <0.1-2.0>` +### `/fly speed <0.01-0.15>` -设置自己的飞行速度,范围为 0.1 到 2.0(默认 0.05)。所有玩家均可执行。 +设置自己的飞行速度,范围为 0.01 到 0.15(默认 0.05)。所有玩家均可执行。 ``` -/fly speed 1.5 +/fly speed 0.10 ``` -- 数值越大飞行越快,2.0 为允许的最大速度 +- 数值越大飞行越快,0.15 为允许的最大速度 - 设置后立即生效,无需重新开启飞行 +- 速度会与飞行状态一起保存,退出重进后仍然保留 ### `/fly player ` @@ -73,11 +74,11 @@ ### 飞行速度 -使用 `/fly speed <数值>` 调整飞行速度(0.1–2.0): +使用 `/fly speed <数值>` 调整飞行速度(0.01–0.15): -- `/fly speed 0.5` — 较慢的飞行速度 -- `/fly speed 1.0` — 中速 -- `/fly speed 2.0` — 最快速度 +- `/fly speed 0.03` — 较慢的飞行速度 +- `/fly speed 0.05` — 默认速度 +- `/fly speed 0.15` — 最快速度 ### 管理员操作 @@ -97,13 +98,13 @@ ### 重连恢复 -如果玩家在飞行状态下断开连接,重新加入服务器时飞行状态会自动恢复。 +玩家的飞行状态(是否飞行)与飞行速度会持久化保存。退出游戏后重新加入,甚至服务器重启,都会自动恢复上次的飞行状态与速度。 ## FAQ ### Q: 飞行状态会一直保留吗? -A: 飞行状态在服务器运行期间持续有效。如果你关闭了飞行、或者服务器重启,飞行状态会被重置。但如果你是断开重连,飞行状态会自动恢复。 +A: 会。飞行状态(是否飞行)与飞行速度会保存到服务端的 `config/openfly_flight_data.json` 文件。玩家退出重进、服务器重启后都会自动恢复上次的状态。 ### Q: 飞行时还会受到摔落伤害吗? diff --git a/gradle.properties b/gradle.properties index 32dd267..b713729 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ loader_version=0.19.5 loom_version=1.18-SNAPSHOT # Mod Properties -version=1.1.0-1.21.8 +version=1.2.0-1.21.8 group=top.gtb520.java.mods.openfly # Dependencies diff --git a/src/main/java/top/gtb520/java/mods/openfly/Commands/fly.java b/src/main/java/top/gtb520/java/mods/openfly/Commands/fly.java index 53b63d0..73001f5 100644 --- a/src/main/java/top/gtb520/java/mods/openfly/Commands/fly.java +++ b/src/main/java/top/gtb520/java/mods/openfly/Commands/fly.java @@ -1,28 +1,47 @@ package top.gtb520.java.mods.openfly.Commands; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.FloatArgumentType; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; +import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.entity.damage.DamageTypes; import net.minecraft.server.command.CommandManager; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; +import top.gtb520.java.mods.openfly.OpenFly; -import java.util.HashSet; -import java.util.Set; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; public class fly { - // 记录开启了飞行的玩家UUID - private static final Set flyingPlayers = new HashSet<>(); + // 默认飞行速度(与原版一致) + public static final float DEFAULT_FLY_SPEED = 0.05F; // 飞行速度范围(/fly speed 参数范围) public static final float MIN_FLY_SPEED = 0.01F; public static final float MAX_FLY_SPEED = 0.15F; + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + // 记录每个玩家的飞行状态(是否飞行 + 飞行速度),内存缓存 + JSON 持久化 + private static final Map flightStates = new HashMap<>(); + private static boolean loaded = false; + public static void register() { + loadStates(); + // 注册命令 CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { dispatcher.register( @@ -36,7 +55,7 @@ public class fly { } UUID playerId = player.getUuid(); - if (flyingPlayers.contains(playerId)) { + if (isFlying(playerId)) { disableFly(player); context.getSource().sendFeedback(() -> Text.literal("已禁用飞行"), false); } else { @@ -45,7 +64,7 @@ public class fly { } return Command.SINGLE_SUCCESS; }) - // /fly speed <0.1-2.0> - 设置自己的飞行速度(所有玩家可用) + // /fly speed <0.01-0.15> - 设置自己的飞行速度(所有玩家可用) .then(CommandManager.literal("speed") .then(CommandManager.argument("speed", FloatArgumentType.floatArg(MIN_FLY_SPEED, MAX_FLY_SPEED)) .executes(context -> { @@ -56,8 +75,11 @@ public class fly { } float speed = FloatArgumentType.getFloat(context, "speed"); + PlayerFlightState state = getOrCreateState(player.getUuid()); + state.flySpeed = speed; player.getAbilities().setFlySpeed(speed); player.sendAbilitiesUpdate(); + saveStates(); context.getSource().sendFeedback( () -> Text.literal("飞行速度已设置为 " + String.format("%.2f", speed)), false); return Command.SINGLE_SUCCESS; @@ -70,7 +92,7 @@ public class fly { ServerPlayerEntity targetPlayer = EntityArgumentType.getPlayer(context, "player"); UUID playerId = targetPlayer.getUuid(); - if (flyingPlayers.contains(playerId)) { + if (isFlying(playerId)) { disableFly(targetPlayer); context.getSource().sendFeedback( () -> Text.literal("已禁用玩家 " + targetPlayer.getName().getString() + " 的飞行"), true); @@ -87,7 +109,7 @@ public class fly { // 注册摔落伤害事件,取消飞行玩家的摔落伤害 ServerLivingEntityEvents.ALLOW_DAMAGE.register((entity, damageSource, amount) -> { if (entity instanceof ServerPlayerEntity player) { - if (flyingPlayers.contains(player.getUuid())) { + if (isFlying(player.getUuid())) { // 如果是摔落伤害,取消它 if (damageSource.isOf(DamageTypes.FALL)) { return false; // 返回false取消伤害 @@ -96,28 +118,111 @@ public class fly { } return true; // 其他情况正常受到伤害 }); + + // 死亡重生后重新应用飞行状态(原版重生流程会重置能力) + ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> { + restoreFlyState(newPlayer); + }); + } + + private static boolean isFlying(UUID playerId) { + PlayerFlightState state = flightStates.get(playerId); + return state != null && state.flying; + } + + private static PlayerFlightState getOrCreateState(UUID playerId) { + return flightStates.computeIfAbsent(playerId, id -> new PlayerFlightState()); } private static void enableFly(ServerPlayerEntity player) { - flyingPlayers.add(player.getUuid()); + PlayerFlightState state = getOrCreateState(player.getUuid()); + state.flying = true; player.getAbilities().allowFlying = true; player.getAbilities().flying = true; + player.getAbilities().setFlySpeed(state.flySpeed); player.sendAbilitiesUpdate(); + saveStates(); } private static void disableFly(ServerPlayerEntity player) { - flyingPlayers.remove(player.getUuid()); + PlayerFlightState state = getOrCreateState(player.getUuid()); + state.flying = false; player.getAbilities().allowFlying = false; player.getAbilities().flying = false; player.sendAbilitiesUpdate(); + saveStates(); } - // 玩家加入时恢复飞行状态 + // 玩家加入/重生时恢复飞行状态(是否飞行 + 飞行速度) public static void restoreFlyState(ServerPlayerEntity player) { - if (flyingPlayers.contains(player.getUuid())) { + PlayerFlightState state = flightStates.get(player.getUuid()); + if (state == null) { + return; + } + // 飞行速度始终恢复,是否飞行按保存的状态恢复 + player.getAbilities().setFlySpeed(state.flySpeed); + if (state.flying) { player.getAbilities().allowFlying = true; player.getAbilities().flying = true; - player.sendAbilitiesUpdate(); + } + player.sendAbilitiesUpdate(); + } + + // ---------- 持久化 ---------- + + private static Path dataFile() { + return FabricLoader.getInstance().getConfigDir().resolve("openfly_flight_data.json"); + } + + private static void loadStates() { + if (loaded) { + return; + } + loaded = true; + try { + Path path = dataFile(); + if (!Files.exists(path)) { + return; + } + try (Reader reader = Files.newBufferedReader(path)) { + Map data = GSON.fromJson(reader, new TypeToken>() { + }.getType()); + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + try { + flightStates.put(UUID.fromString(entry.getKey()), entry.getValue()); + } catch (IllegalArgumentException ignored) { + // 忽略无效的 UUID + } + } + } + } + OpenFly.LOGGER.info("OpenFly 已加载 {} 名玩家的飞行状态", flightStates.size()); + } catch (IOException e) { + OpenFly.LOGGER.error("OpenFly 读取飞行状态文件失败", e); } } + + private static void saveStates() { + try { + Path path = dataFile(); + Files.createDirectories(path.getParent()); + // 以 UUID 字符串作为 key 序列化,避免依赖 Gson 的 UUID 适配器 + Map data = new HashMap<>(); + for (Map.Entry entry : flightStates.entrySet()) { + data.put(entry.getKey().toString(), entry.getValue()); + } + try (Writer writer = Files.newBufferedWriter(path)) { + GSON.toJson(data, writer); + } + } catch (IOException e) { + OpenFly.LOGGER.error("OpenFly 保存飞行状态文件失败", e); + } + } + + // 单个玩家的飞行状态 + public static class PlayerFlightState { + public boolean flying = false; + public float flySpeed = DEFAULT_FLY_SPEED; + } } diff --git a/src/main/java/top/gtb520/java/mods/openfly/OpenFly.java b/src/main/java/top/gtb520/java/mods/openfly/OpenFly.java index 9d95d51..7c3efcb 100644 --- a/src/main/java/top/gtb520/java/mods/openfly/OpenFly.java +++ b/src/main/java/top/gtb520/java/mods/openfly/OpenFly.java @@ -1,7 +1,9 @@ package top.gtb520.java.mods.openfly; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; import org.slf4j.Logger; @@ -26,9 +28,21 @@ public class OpenFly implements ModInitializer { // 注册飞行命令和摔落伤害事件 fly.register(); - // 玩家加入时恢复飞行状态 + // 玩家加入时恢复飞行状态(是否飞行 + 飞行速度) ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { - fly.restoreFlyState(handler.getPlayer()); + ServerPlayerEntity player = handler.getPlayer(); + fly.restoreFlyState(player); + // 延迟一 tick 再应用一次,避免登录流程后续覆盖玩家能力 + server.execute(() -> { + if (!player.isDisconnected()) { + fly.restoreFlyState(player); + } + }); + }); + + // 死亡重生后重新应用飞行状态(原版重生流程会重置能力) + ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> { + fly.restoreFlyState(newPlayer); }); LOGGER.info("OpenFly Mod 加载完成!");