From 1b2712c4255de8cef31c1f5a16dead696d5d9145 Mon Sep 17 00:00:00 2001 From: iceBear67 Date: Tue, 21 Jul 2026 15:58:13 +0800 Subject: [PATCH] fix: move indicator farther for GpsModule and integrate GPS with chat sendloc --- CONTRIBUTING.md | 7 +-- .../io/ib67/sfcraft/config/GameConfig.java | 2 + .../chat/decorator/ChatSendLocModule.java | 12 ++-- .../sfcraft/module/command/GpsModule.java | 63 +++++++++++++++++-- .../resources/assets/sfcraft/lang/en_us.json | 6 ++ .../resources/assets/sfcraft/lang/zh_cn.json | 6 ++ 6 files changed, 79 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86c618f..a89483f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # 贡献指南 -欢迎!如果你想提交投影文件,请前往[此处](https://github.com/saltedfishclub/sfcraft-schematics) +欢迎!如果你想提交投影文件,请前往[此处](https://github.com/saltedfishclub/sfcraft-schematics). 此文件为有经验的开发者提供,如果你是新手,请查看 [NEWBIE.md](./NEWBIE.md) 以了解如何开始。 ## 贡献流程 首先,你需要新建一个有关修改内容的分支(如果你没有本仓库的修改权限,请自行 Fork),然后在里面提交代码,最后你就可以发起 Pull Request 并且等待我们审查你的代码了。 @@ -42,11 +42,6 @@ private MinecraftServerSupplier serverSupplier; 在 Mixin 代码中你可能需要拿一些在 Guice 内的对象,此时可以通过 `SFCraft.getInjector().getInstance(TYPE)` 得到对应的对象。 但是注意:如果你的 Mixin 代码是高频操作,请适当地做缓存 -#### 使用自定义实体 API - -参考 `SFEntityType#init` -自定义实体的写法大体与正常 mod 开发无异。 - #### 使用 Room API 文档施工中 diff --git a/src/main/java/io/ib67/sfcraft/config/GameConfig.java b/src/main/java/io/ib67/sfcraft/config/GameConfig.java index 4f7da18..a2d3738 100644 --- a/src/main/java/io/ib67/sfcraft/config/GameConfig.java +++ b/src/main/java/io/ib67/sfcraft/config/GameConfig.java @@ -30,6 +30,8 @@ public class GameConfig { public double toleranceDegrees = 12.0; // 光标悬浮在视线前方的距离;放远一点航向偏差在画面上更明显,也更不容易糊在脸上 public double cursorDistance = 8.0; + // 鞘翅飞行时改用的光标距离:高速飞行下虚拟实体位置插值滞后,光标会"跟不上",拉远以抵消 + public double flyingCursorDistance = 20.0; public double arriveRadius = 4.0; // 水平(X-Z)距离大于该值时进入远距离巡航:光标锁在眼睛所在水平面,只随左右转向(yaw)偏移、 // 忽略俯仰,这样用鞘翅高速飞行低头看地面时光标不会跟着往下沉;近于此值则恢复全向指示 diff --git a/src/main/java/io/ib67/sfcraft/module/chat/decorator/ChatSendLocModule.java b/src/main/java/io/ib67/sfcraft/module/chat/decorator/ChatSendLocModule.java index 2ce7931..c2c4975 100644 --- a/src/main/java/io/ib67/sfcraft/module/chat/decorator/ChatSendLocModule.java +++ b/src/main/java/io/ib67/sfcraft/module/chat/decorator/ChatSendLocModule.java @@ -8,6 +8,7 @@ import io.ib67.sfcraft.util.SFConsts; import net.minecraft.network.chat.ChatDecorator; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffectInstance; @@ -16,8 +17,6 @@ import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; -import java.net.URI; - public class ChatSendLocModule extends ServerModule implements ChatDecorator { @Inject SimpleMessageDecorator messageDecorator; @@ -50,9 +49,12 @@ public class ChatSendLocModule extends ServerModule implements ChatDecorator { .append(dimensionLabel(key)) .append(" ") .withColor(Helper.fromRgb(63, 254, 254)) - .withStyle(style -> style.withClickEvent(new ClickEvent.OpenUrl(URI.create( - "https://mc.sfclub.cc/map/?world=" + key.identifier().toString().replace(':', '_') + "&zoom=5&x=" + x + "&z=" + z - )))); + // 点击运行 /gps 直接开始导航(并附带世界,不在该世界时会被 gps 拒绝),而不是打开网页地图 + .withStyle(style -> style + .withClickEvent(new ClickEvent.RunCommand( + "/gps " + x + " " + y + " " + z + " " + key.identifier())) + .withHoverEvent(new HoverEvent.ShowText( + Component.translatable("message.sfcraft.chat.loc_navigate")))); } private static Component dimensionLabel(ResourceKey registryKey) { diff --git a/src/main/java/io/ib67/sfcraft/module/command/GpsModule.java b/src/main/java/io/ib67/sfcraft/module/command/GpsModule.java index d542fbe..36a691d 100644 --- a/src/main/java/io/ib67/sfcraft/module/command/GpsModule.java +++ b/src/main/java/io/ib67/sfcraft/module/command/GpsModule.java @@ -5,6 +5,7 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import eu.pb4.polymer.virtualentity.api.ElementHolder; import eu.pb4.polymer.virtualentity.api.attachment.ManualAttachment; import eu.pb4.polymer.virtualentity.api.elements.SimpleEntityElement; @@ -19,8 +20,10 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.DimensionArgument; import net.minecraft.commands.arguments.coordinates.Coordinates; import net.minecraft.commands.arguments.coordinates.Vec3Argument; +import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; @@ -33,6 +36,7 @@ import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -86,7 +90,27 @@ public class GpsModule extends ServerModule { .then(LiteralArgumentBuilder.literal("stop") .executes(this::stop)) .then(RequiredArgumentBuilder.argument("target", Vec3Argument.vec3()) - .executes(this::start))); + .executes(this::start) + // 可选的目标世界:玩家不在该世界时拒绝导航,便于 .xyz 跨维度分享坐标的点击导航 + .then(Commands.argument("world", DimensionArgument.dimension()) + .executes(this::startInWorld)))); + } + + // /gps :先校验玩家是否处于指定世界,不匹配则拒绝导航 + private int startInWorld(CommandContext context) throws CommandSyntaxException { + ServerLevel world = DimensionArgument.getDimension(context, "world"); + ServerPlayer player = context.getSource().getPlayer(); + if (player.level().dimension() != world.dimension()) { + var source = context.getSource(); + source.sendFailure(Component.translatable("command.sfcraft.gps.wrong_world", + dimensionName(world.dimension()))); + // 虽然无法导航,仍给一张目标世界的网页地图链接,方便玩家先看看目标在哪 + Vec3 target = Vec3Argument.getVec3(context, "target"); + source.sendSuccess(() -> previewLink(world.dimension(), + (int) Math.round(target.x), (int) Math.round(target.z)), false); + return 0; + } + return start(context); } private int start(CommandContext context) { @@ -95,11 +119,36 @@ public class GpsModule extends ServerModule { var old = sessions.remove(player.getUUID()); if (old != null) old.destroy(); sessions.put(player.getUUID(), new GpsSession(player, target)); - context.getSource().sendSuccess(() -> Component.translatable("command.sfcraft.gps.started", - Math.round(target.x), Math.round(target.y), Math.round(target.z)), false); + var source = context.getSource(); + long x = Math.round(target.x), y = Math.round(target.y), z = Math.round(target.z); + source.sendSuccess(() -> Component.translatable("command.sfcraft.gps.started", x, y, z), false); + // 出发前先给一张网页地图链接,允许玩家提前预览目标周边 + source.sendSuccess(() -> previewLink(player.level().dimension(), (int) x, (int) z), false); return 1; } + // 可点击的网页地图预览链接 + private static Component previewLink(ResourceKey world, int x, int z) { + String url = webMapUrl(world, x, z); + return Component.translatable("command.sfcraft.gps.preview") + .withStyle(style -> style + .withColor(Helper.fromRgb(63, 254, 254)) + .withUnderlined(true) + .withClickEvent(new ClickEvent.OpenUrl(URI.create(url)))); + } + + private static String webMapUrl(ResourceKey world, int x, int z) { + return "https://mc.sfclub.cc/map/?world=" + world.identifier().toString().replace(':', '_') + + "&zoom=5&x=" + x + "&z=" + z; + } + + private static Component dimensionName(ResourceKey key) { + if (key == Level.OVERWORLD) return Component.translatable("message.sfcraft.dimension.name.overworld"); + if (key == Level.NETHER) return Component.translatable("message.sfcraft.dimension.name.nether"); + if (key == Level.END) return Component.translatable("message.sfcraft.dimension.name.end"); + return Component.literal(key.identifier().toString()); + } + private int stop(CommandContext context) { var session = sessions.remove(context.getSource().getPlayer().getUUID()); if (session == null) { @@ -165,6 +214,8 @@ public class GpsModule extends ServerModule { } void tick(ServerPlayer player, GameConfig.Gps gps) { + // 鞘翅飞行时把光标拉远:高速下虚拟实体位置插值滞后,近距离光标会"跟不上",拉远抵消 + double cursorDistance = player.isFallFlying() ? gps.flyingCursorDistance : gps.cursorDistance; Vec3 eye = player.getEyePosition(); double dx = target.x - eye.x; double dz = target.z - eye.z; @@ -179,9 +230,9 @@ public class GpsModule extends ServerModule { double angle = Math.toDegrees(Math.acos(Mth.clamp(lookHoriz.dot(targetHoriz), -1.0, 1.0))); Vec3 horiz = angle <= gps.toleranceDegrees ? lookHoriz : targetHoriz; this.cursorPos = new Vec3( - eye.x + horiz.x * gps.cursorDistance, + eye.x + horiz.x * cursorDistance, eye.y - CURSOR_HALF_HEIGHT, - eye.z + horiz.z * gps.cursorDistance); + eye.z + horiz.z * cursorDistance); } else { // 近距离:恢复全向(含俯仰)指示,便于精确对准竖直方向上的目标。 // 容差内粘在准星方向(画面上纹丝不动),超出则跳到真实目标方向提示玩家回正。 @@ -190,7 +241,7 @@ public class GpsModule extends ServerModule { Vec3 look = player.getLookAngle(); double angle = Math.toDegrees(Math.acos(Mth.clamp(look.dot(targetDir), -1.0, 1.0))); Vec3 direction = angle <= gps.toleranceDegrees ? look : targetDir; - double distance = Math.min(gps.cursorDistance, toTarget.length()); + double distance = Math.min(cursorDistance, toTarget.length()); this.cursorPos = eye.add(direction.scale(distance)).subtract(0, CURSOR_HALF_HEIGHT, 0); } holder.tick(); diff --git a/src/main/resources/assets/sfcraft/lang/en_us.json b/src/main/resources/assets/sfcraft/lang/en_us.json index 2839a0a..4410127 100644 --- a/src/main/resources/assets/sfcraft/lang/en_us.json +++ b/src/main/resources/assets/sfcraft/lang/en_us.json @@ -40,9 +40,13 @@ "message.sfcraft.elytra.infinity": "ELYTRA: INFINITY", "message.sfcraft.chat.calling_you": "%s is calling you.", "message.sfcraft.chat.invalid_position": " (invalid position) ", + "message.sfcraft.chat.loc_navigate": "Click to start navigating here", "message.sfcraft.dimension.end": " (The End)", "message.sfcraft.dimension.nether": " (The Nether)", "message.sfcraft.dimension.unknown": " (Unknown)", + "message.sfcraft.dimension.name.overworld": "Overworld", + "message.sfcraft.dimension.name.nether": "The Nether", + "message.sfcraft.dimension.name.end": "The End", "message.sfcraft.playground.must_be_in": "You and target must be in playground!", "message.sfcraft.playground.gamemode_changed": "Your gamemode has been changed to %s", "message.sfcraft.playground.only_here": "You can only use this in playground!", @@ -81,5 +85,7 @@ "command.sfcraft.gps.not_active": "No navigation in progress", "command.sfcraft.gps.arrived": "You have arrived, navigation ended", "command.sfcraft.gps.dimension_changed": "You left the target's dimension, navigation ended", + "command.sfcraft.gps.wrong_world": "You are not in the target world (%s); navigation refused", + "command.sfcraft.gps.preview": "Click to preview this location on the web map", "item.sfcraft.standing_firm_totem": "Totem of Restitution" } diff --git a/src/main/resources/assets/sfcraft/lang/zh_cn.json b/src/main/resources/assets/sfcraft/lang/zh_cn.json index 9f8359e..c36e7fc 100644 --- a/src/main/resources/assets/sfcraft/lang/zh_cn.json +++ b/src/main/resources/assets/sfcraft/lang/zh_cn.json @@ -40,9 +40,13 @@ "message.sfcraft.elytra.infinity": "鞘翅: 无限", "message.sfcraft.chat.calling_you": "%s 正在叫你。", "message.sfcraft.chat.invalid_position": " (无效坐标) ", + "message.sfcraft.chat.loc_navigate": "点击开始导航到这里", "message.sfcraft.dimension.end": " (末地)", "message.sfcraft.dimension.nether": " (地狱)", "message.sfcraft.dimension.unknown": " (未知)", + "message.sfcraft.dimension.name.overworld": "主世界", + "message.sfcraft.dimension.name.nether": "地狱", + "message.sfcraft.dimension.name.end": "末地", "message.sfcraft.playground.must_be_in": "你和目标必须都在游乐园中!", "message.sfcraft.playground.gamemode_changed": "你的游戏模式已更改为 %s", "message.sfcraft.playground.only_here": "只能在游乐园中使用此功能!", @@ -81,5 +85,7 @@ "command.sfcraft.gps.not_active": "当前没有正在进行的导航", "command.sfcraft.gps.arrived": "已到达目的地,导航结束", "command.sfcraft.gps.dimension_changed": "已离开目标所在维度,导航结束", + "command.sfcraft.gps.wrong_world": "你不在目标世界(%s),已拒绝导航", + "command.sfcraft.gps.preview": "点击在网页地图上预览该位置", "item.sfcraft.standing_firm_totem": "代偿图腾" }