第29批:title/tellraw报错接管恢复压制原生报错并直接打开JSON构造器(附标准示例+/json超链接) + 打包箱放置仅限领地主/授权成员且放下清除自定义名 + 封禁对账收回回填created创建时间(修复拷贝生产封禁数据后全员播报乱叫)
This commit is contained in:
@@ -223,10 +223,45 @@ public class ChestCarryManager implements Listener {
|
||||
if (data == null || data.isEmpty()) return;
|
||||
|
||||
Block placed = e.getBlockPlaced();
|
||||
|
||||
// ★ 29批第4项:打包箱仅领地主/授权成员/管理员可放置 ——
|
||||
// 防止访客/路人拿到打包箱后就地放下自动回填、窃取他人财物。
|
||||
// 无权限 → 取消放置(打包箱原封不动留在手上,绝不吞物);
|
||||
// 放置点不在任何领地内 → 无人管辖,交还原版正常回填。
|
||||
AreaProtection ap = main.areaProtection;
|
||||
if (ap != null) {
|
||||
AreaProtection.AreaConfig ac = ap.getArea(
|
||||
placed.getWorld().getName(), placed.getX(), placed.getY(), placed.getZ());
|
||||
if (ac != null) {
|
||||
AreaProtection.PermissionLevel lv = ap.getPermissionLevel(e.getPlayer(), ac);
|
||||
if (lv != AreaProtection.PermissionLevel.ADMIN
|
||||
&& lv != AreaProtection.PermissionLevel.OWNER
|
||||
&& lv != AreaProtection.PermissionLevel.MEMBER) {
|
||||
e.setCancelled(true);
|
||||
e.getPlayer().sendMessage("§c[箱子搬运] §7只有领地主/授权成员能在此放置打包箱");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1 tick 后方块状态就绪再回填(含与邻半合并成大箱子的情况)
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
BlockState st = placed.getState();
|
||||
if (!(st instanceof org.bukkit.block.Container)) return;
|
||||
// ★ 29批第2项:放下的是「原版普通箱子」——物品的自定义名可能被方块继承成
|
||||
// CustomName(准星指向时显示「打包箱」),这里清掉并落盘,再回填内容。
|
||||
// update(force=true, applyPhysics=false):强制写回世界、不触发邻块物理。
|
||||
try {
|
||||
// Paper 26:BlockState 已无 setCustomName —— 方块实体名在 Nameable 上
|
||||
//(Container ← LockableTileState ← Nameable),setter 是 adventure
|
||||
// 的 customName(Component),null 直传无歧义
|
||||
((org.bukkit.Nameable) st).customName(null);
|
||||
st.update(true, false);
|
||||
} catch (Exception ex) {
|
||||
// 个别容器不支持改名就忽略,回填照常进行
|
||||
}
|
||||
st = placed.getState(); // update 后旧快照失效,重新取
|
||||
if (!(st instanceof org.bukkit.block.Container)) return;
|
||||
try {
|
||||
ItemStack[] items = InventorySerializer.fromBase64(data);
|
||||
Inventory inv = ((org.bukkit.block.Container) st).getInventory();
|
||||
|
||||
@@ -234,12 +234,12 @@ public class JsonMessageGui implements Listener {
|
||||
/**
|
||||
* 原版 title/tellraw 报错接管(挂在 Main.onUnknownCommand)。
|
||||
*
|
||||
* <p>★ 28批(用户要求):不再直接 openInventory 强接管——运维手打 JSON 出错时
|
||||
* 瞬间被弹走面板很突兀。改为「保留原生最小化报错」(Main 侧不再 suppressUnknown),
|
||||
* 本方法只做「预填原文 + 提示 + 可点击超链接」,由玩家自己点链接唤醒构造器。
|
||||
* <p>★ 29批(用户要求,回退28批「不直接打开」的决定):Main 侧 suppressUnknown
|
||||
* 压掉原生报错后由本方法接管 ——「预填原文 + 标准最小化写法示例 + /json CLI
|
||||
* 可点击超链接 + 直接 openInventory 打开构造器」。
|
||||
*
|
||||
* @param body 已去掉开头斜杠的命令行
|
||||
* @return true = 已预填并发出提示+超链接(原生报错保留),false = 不归本构造器管
|
||||
* @return true = 已预填并接管(原生报错由 Main 压制),false = 不归本构造器管
|
||||
*/
|
||||
public boolean tryTakeover(Player p, String body) {
|
||||
if (body.isEmpty()) return false;
|
||||
@@ -281,24 +281,42 @@ public class JsonMessageGui implements Listener {
|
||||
s.styleFlags = 0;
|
||||
if (!extracted.isEmpty()) s.text.append(extracted);
|
||||
|
||||
// ★ 28批:延迟 1 tick 发提示——先让原生最小化报错落地,再看到提示+超链接
|
||||
// ★ 29批:延迟 1 tick —— 先让 suppressUnknown 压掉的原生报错彻底落地,
|
||||
// 再发「标准最小化示例 + /json CLI 连接」并直接打开构造器(回退28批不打开的决定)
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
p.sendMessage(net.kyori.adventure.text.Component
|
||||
.text("[JSON构造器] ", net.kyori.adventure.text.format.NamedTextColor.GOLD)
|
||||
.append(net.kyori.adventure.text.Component.text(
|
||||
"你的原文已预填 → ",
|
||||
"原文已预填,构造器已打开。标准写法示例:",
|
||||
net.kyori.adventure.text.format.NamedTextColor.GRAY))
|
||||
.append(net.kyori.adventure.text.Component.text(
|
||||
"[点击唤醒构造器]",
|
||||
exampleCmdOf(mode),
|
||||
net.kyori.adventure.text.format.NamedTextColor.WHITE))
|
||||
.append(net.kyori.adventure.text.Component.newline())
|
||||
.append(net.kyori.adventure.text.Component.text(
|
||||
"CLI 连接(备用入口):",
|
||||
net.kyori.adventure.text.format.NamedTextColor.GRAY))
|
||||
.append(net.kyori.adventure.text.Component.text(
|
||||
"[/json]",
|
||||
net.kyori.adventure.text.format.NamedTextColor.YELLOW,
|
||||
net.kyori.adventure.text.format.TextDecoration.UNDERLINED)
|
||||
.clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand("/json"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(
|
||||
net.kyori.adventure.text.Component.text("打开 JSON 构造器(原文已预填)")))));
|
||||
open(p);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 29批:各模式的标准最小化写法示例(压制原生报错后由本构造器给出) */
|
||||
private static String exampleCmdOf(int mode) {
|
||||
switch (mode) {
|
||||
case MODE_TITLE: return "/title @a title {\"text\":\"标题\"}";
|
||||
case MODE_SUBTITLE: return "/title @a subtitle {\"text\":\"副标题\"}";
|
||||
default: return "/tellraw @a {\"text\":\"你好\"}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 28批:从 tellraw 载荷里剥掉目标选择器——原版结构 tellraw <target> <json>,
|
||||
* @a/@p/玩家名属于指令参数,不能被构造成用户文本(用户实测 @a 被塞进了 text)。
|
||||
|
||||
@@ -4515,9 +4515,11 @@ public class Main extends JavaPlugin
|
||||
if (lowerBody.startsWith("tellraw ")
|
||||
|| lowerBody.startsWith("title ")) {
|
||||
if (jsonGui.tryTakeover((Player) sender, body)) {
|
||||
// 28批:不再 suppressUnknown——按用户要求保留原生最小化报错,
|
||||
// tryTakeover 只负责预填+发提示超链接(由玩家点链接唤醒GUI)
|
||||
getLogger().info("[无感切换] title/tellraw 报错已提示进JSON构造器: \""
|
||||
// ★ 29批(回退28批):压制原生 "<--[HERE]" 报错 —— tryTakeover 接管后
|
||||
// 自己发「标准最小化示例 + /json CLI超链接」并直接打开构造器,
|
||||
// 原生那行报错按用户要求压掉
|
||||
suppressUnknown(e);
|
||||
getLogger().info("[无感切换] title/tellraw 报错已接管进JSON构造器: \""
|
||||
+ body + "\"");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4294,11 +4294,15 @@ public class WebManager {
|
||||
new String[]{},
|
||||
alignMap(), false, false));
|
||||
// —— 封禁名单(Web 后台加的封禁要拉回游戏内生效)——
|
||||
// ★ 29批:map 增加 created_time→created —— PHP check_alignment 的 cols 已带上
|
||||
// created_time,收回行才能携带真实封禁创建时间;hashcols 故意不含 created,
|
||||
// 指纹不变(防两端 created 精度/缺失差异恒 changed 死循环)。
|
||||
ALIGN_CFGS.put("bans", new AlignCfg("bans",
|
||||
new String[]{"target", "ban_type"},
|
||||
new String[]{"target", "ban_type", "reason", "source", "expire_time"},
|
||||
new String[]{"expire_time"},
|
||||
alignMap("ban_type", "type", "expire_time", "expire"), true, false));
|
||||
alignMap("ban_type", "type", "expire_time", "expire",
|
||||
"created_time", "created"), true, false));
|
||||
// —— 领地数据(PHP web_area_lands;游戏内 area_lands 为唯一权威)——
|
||||
// pull=false:Web 后台的领地改动不回流 Java(与改造前一致)。
|
||||
// pushOnExtra=true:PHP 多出的行(游戏里已删 / 后台自建)→ 触发一次整包推,
|
||||
@@ -4763,8 +4767,16 @@ public class WebManager {
|
||||
}
|
||||
// 真实签名:addBan(target, reason, expires, source)
|
||||
// Date 必须全限定——本文件同时 import 了 java.sql.* 与 java.util.*
|
||||
banList.addBan(target, reason,
|
||||
// ★ 29批:收回行现在携带 PHP 侧创建时间(check_alignment cols 已含
|
||||
// created_time→created)。addBan 默认 created=now,若不回填,
|
||||
// 生产封禁数据拷到测试服后「收回」一轮会把所有封禁的创建时间刷成
|
||||
// 当天 → 封禁播报全员乱叫(2026-10-22 用户实测)。
|
||||
org.bukkit.BanEntry<?> pulled = banList.addBan(target, reason,
|
||||
expire > 0 ? new java.util.Date(expire) : null, source);
|
||||
long created = longOf(row.get("created"));
|
||||
if (pulled != null && created > 0) {
|
||||
pulled.setCreated(new java.util.Date(created));
|
||||
}
|
||||
plugin.getLogger().info("[对账] 已收回 Web 端封禁: " + (isIp ? "IP " : "") + target);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user