Skip to content

Commit 047a3d1

Browse files
修复卡观察者
1 parent f2a2ce8 commit 047a3d1

5 files changed

Lines changed: 123 additions & 81 deletions

File tree

src/main/java/ict/minesunshineone/peek/handler/BossBarHandler.java

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class BossBarHandler {
2121

2222
private final PeekPlugin plugin;
2323
private final Map<UUID, BossBar> distanceBossBars = new HashMap<>();
24-
24+
2525
// BossBar 配置
2626
private final boolean enabled;
2727
private final String titleFormat;
@@ -36,18 +36,19 @@ public class BossBarHandler {
3636
public BossBarHandler(PeekPlugin plugin) {
3737
this.plugin = plugin;
3838
this.maxPeekDistance = plugin.getConfig().getDouble("limits.max-peek-distance", 50.0);
39-
39+
4040
// 加载 BossBar 配置
4141
this.enabled = plugin.getConfig().getBoolean("bossbar.enabled", true);
42-
this.titleFormat = plugin.getConfig().getString("bossbar.title", "&d距离 &f{target}&d: &e{distance} &7/ &e{max_distance} &d格");
42+
this.titleFormat = plugin.getConfig().getString("bossbar.title",
43+
"&d距离 &f{target}&d: &e{distance} &7/ &e{max_distance} &d格");
4344
this.style = parseBarStyle(plugin.getConfig().getString("bossbar.style", "SEGMENTED_10"));
4445
this.colorSafe = parseBarColor(plugin.getConfig().getString("bossbar.colors.safe", "GREEN"));
4546
this.colorWarning = parseBarColor(plugin.getConfig().getString("bossbar.colors.warning", "YELLOW"));
4647
this.colorDanger = parseBarColor(plugin.getConfig().getString("bossbar.colors.danger", "RED"));
4748
this.thresholdWarning = plugin.getConfig().getDouble("bossbar.thresholds.warning", 0.5);
4849
this.thresholdDanger = plugin.getConfig().getDouble("bossbar.thresholds.danger", 0.75);
4950
}
50-
51+
5152
private BarStyle parseBarStyle(String style) {
5253
try {
5354
return BarStyle.valueOf(style.toUpperCase());
@@ -56,7 +57,7 @@ private BarStyle parseBarStyle(String style) {
5657
return BarStyle.SEGMENTED_10;
5758
}
5859
}
59-
60+
6061
private BarColor parseBarColor(String color) {
6162
try {
6263
return BarColor.valueOf(color.toUpperCase());
@@ -68,20 +69,21 @@ private BarColor parseBarColor(String color) {
6869

6970
/**
7071
* 为观察者创建距离显示 BossBar
72+
*
7173
* @param peeker 观察者
7274
* @param target 目标玩家
7375
*/
7476
public void createDistanceBossBar(Player peeker, Player target) {
75-
if (!enabled) return;
76-
77+
if (!enabled)
78+
return;
79+
7780
removeDistanceBossBar(peeker); // 确保没有残留
78-
81+
7982
String title = formatBossBarTitle(target.getName(), 0.0);
8083
BossBar bar = Bukkit.createBossBar(
8184
title,
8285
colorSafe,
83-
style
84-
);
86+
style);
8587
bar.setProgress(0.0);
8688
bar.addPlayer(peeker);
8789
distanceBossBars.put(peeker.getUniqueId(), bar);
@@ -90,26 +92,27 @@ public void createDistanceBossBar(Player peeker, Player target) {
9092

9193
/**
9294
* 为自我观察模式创建距离显示 BossBar
93-
* @param peeker 观察者(同时也是目标)
95+
*
96+
* @param peeker 观察者(同时也是目标)
9497
* @param selfPeekLabel 自我观察的标签(如"原点")
9598
*/
9699
public void createSelfPeekBossBar(Player peeker, String selfPeekLabel) {
97-
if (!enabled) return;
98-
100+
if (!enabled)
101+
return;
102+
99103
removeDistanceBossBar(peeker); // 确保没有残留
100-
104+
101105
String title = formatBossBarTitle(selfPeekLabel, 0.0);
102106
BossBar bar = Bukkit.createBossBar(
103107
title,
104108
colorSafe,
105-
style
106-
);
109+
style);
107110
bar.setProgress(0.0);
108111
bar.addPlayer(peeker);
109112
distanceBossBars.put(peeker.getUniqueId(), bar);
110113
logDebug("Created self-peek BossBar for player %s", peeker.getName());
111114
}
112-
115+
113116
private String formatBossBarTitle(String targetName, double distance) {
114117
return titleFormat
115118
.replace("{target}", targetName)
@@ -120,15 +123,20 @@ private String formatBossBarTitle(String targetName, double distance) {
120123

121124
/**
122125
* 更新观察者的距离 BossBar
123-
* @param peeker 观察者
124-
* @param distance 当前距离
126+
*
127+
* @param peeker 观察者
128+
* @param distance 当前距离
125129
* @param targetName 目标玩家名称
126130
*/
127131
public void updateDistanceBossBar(Player peeker, double distance, String targetName) {
128-
if (!enabled) return;
129-
132+
if (!enabled)
133+
return;
134+
if (!peeker.isOnline())
135+
return;
136+
130137
BossBar bar = distanceBossBars.get(peeker.getUniqueId());
131-
if (bar == null) return;
138+
if (bar == null)
139+
return;
132140

133141
double progress = Math.min(distance / maxPeekDistance, 1.0);
134142
bar.setProgress(progress);
@@ -147,12 +155,14 @@ public void updateDistanceBossBar(Player peeker, double distance, String targetN
147155

148156
/**
149157
* 更新观察者的距离 BossBar(从 PeekData 获取目标信息)
150-
* @param peeker 观察者
158+
*
159+
* @param peeker 观察者
151160
* @param distance 当前距离
152-
* @param data PeekData 数据
161+
* @param data PeekData 数据
153162
*/
154163
public void updateDistanceBossBar(Player peeker, double distance, PeekData data) {
155-
if (!enabled || data == null) return;
164+
if (!enabled || data == null)
165+
return;
156166

157167
Player target = plugin.getServer().getPlayer(data.getTargetUUID());
158168
String targetName = target != null ? target.getName() : "未知";
@@ -161,6 +171,7 @@ public void updateDistanceBossBar(Player peeker, double distance, PeekData data)
161171

162172
/**
163173
* 移除观察者的距离 BossBar
174+
*
164175
* @param peeker 观察者
165176
*/
166177
public void removeDistanceBossBar(Player peeker) {
@@ -172,6 +183,7 @@ public void removeDistanceBossBar(Player peeker) {
172183

173184
/**
174185
* 安全地移除 BossBar(使用正确的线程调度)
186+
*
175187
* @param peeker 观察者
176188
*/
177189
public void safeRemoveDistanceBossBar(Player peeker) {
@@ -180,7 +192,7 @@ public void safeRemoveDistanceBossBar(Player peeker) {
180192
removeDistanceBossBar(peeker);
181193
return;
182194
}
183-
195+
184196
if (peeker.isOnline()) {
185197
peeker.getScheduler().run(plugin, scheduledTask -> removeDistanceBossBar(peeker), null);
186198
} else {
@@ -190,6 +202,7 @@ public void safeRemoveDistanceBossBar(Player peeker) {
190202

191203
/**
192204
* 检查是否启用 BossBar
205+
*
193206
* @return 是否启用
194207
*/
195208
public boolean isEnabled() {

src/main/java/ict/minesunshineone/peek/handler/PeekStateHandler.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,12 @@ private void teleportAndSetGameMode(Player peeker, Player target) {
267267
if (!success) {
268268
onFailed.run();
269269
} else {
270-
// 传送成功后,使用玩家的实体调度器确保在正确线程执行
271-
bossBarHandler.createDistanceBossBar(peeker, target);
272-
startNormalRangeChecker(peeker, target);
270+
// teleportAsync 的回调线程不确定
271+
// 使用 peeker 的实体调度器确保在正确的区域线程执行
272+
peeker.getScheduler().run(plugin, scheduledTask -> {
273+
bossBarHandler.createDistanceBossBar(peeker, target);
274+
startNormalRangeChecker(peeker, target);
275+
}, () -> logDebug("Peeker %s went offline after teleport", peeker.getName()));
273276
}
274277
});
275278
}, onFailed, 2L); // 2 tick 延迟
@@ -287,11 +290,14 @@ private void teleportAndSetGameMode(Player peeker, Player target) {
287290
private void setSelfPeekGameMode(Player peeker) {
288291
peeker.getScheduler().execute(plugin, () -> {
289292
// 这里如果玩家离线了调度器会自动退役
290-
/*if (!peeker.isOnline()) {
291-
plugin.getLogger().warning(String.format("玩家 %s 在设置自我观察模式时已离线", peeker.getName()));
292-
endPeek(peeker, false);
293-
return;
294-
}*/
293+
/*
294+
* if (!peeker.isOnline()) {
295+
* plugin.getLogger().warning(String.format("玩家 %s 在设置自我观察模式时已离线",
296+
* peeker.getName()));
297+
* endPeek(peeker, false);
298+
* return;
299+
* }
300+
*/
295301

296302
if (peeker.isDead()) {
297303
plugin.getSLF4JLogger().warn("玩家 {} 在设置自我观察模式时已死亡", peeker.getName());
@@ -326,8 +332,9 @@ private void startNormalRangeChecker(Player peeker, Player target) {
326332
PeekData data = activePeeks.get(peeker.getUniqueId());
327333
bossBarHandler.updateDistanceBossBar(peeker, distance, data);
328334
},
329-
// 不同世界时
335+
// 不同世界时 - 先停止旧的距离检查器,防止竞态重复触发
330336
() -> {
337+
rangeChecker.stopRangeChecker(peeker);
331338
plugin.getMessages().send(peeker, "target-in-different-world");
332339
teleportAndSetGameMode(peeker, target);
333340
});
@@ -360,21 +367,17 @@ private void stopRangeCheckerAndBossBar(Player peeker) {
360367
}
361368

362369
private void startRespawnWatcher(Player peeker, PeekData data) {
363-
plugin.getServer().getRegionScheduler().runAtFixedRate(plugin,
364-
peeker.getLocation(),
370+
// 使用 peeker 的实体调度器,避免 RegionScheduler 坐标过时问题
371+
peeker.getScheduler().runAtFixedRate(plugin,
365372
task -> {
366-
if (!peeker.isOnline()) {
367-
task.cancel();
368-
return;
369-
}
370-
371373
if (!peeker.isDead()) {
372374
task.cancel();
373375
stateRestorer.restorePlayerState(peeker, data);
374376
plugin.getStateManager().clearPlayerState(peeker);
375377
plugin.getMessages().send(peeker, "peek-end-respawn");
376378
}
377379
},
380+
() -> logDebug("Peeker %s went offline during respawn watch", peeker.getName()),
378381
1L, 20L);
379382
}
380383

src/main/java/ict/minesunshineone/peek/handler/PlayerStateRestorer.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public PlayerStateRestorer(PeekPlugin plugin) {
3333
public void restorePlayerState(Player peeker, PeekData data) {
3434
final Runnable onFailed = () -> handleTeleportFailure(peeker, data);
3535

36-
final boolean scheduled = peeker.getScheduler().execute(plugin, ()-> {
36+
final boolean scheduled = peeker.getScheduler().execute(plugin, () -> {
3737

3838
// 强制清理任何骑乘/附身状态,防止卡在旁观者模式
3939
PlayerStateUtil.forceExitRidingState(peeker);
@@ -43,8 +43,11 @@ public void restorePlayerState(Player peeker, PeekData data) {
4343

4444
peeker.teleportAsync(data.getOriginalLocation(), TeleportCause.PLUGIN).thenAccept(success -> {
4545
if (success) {
46-
// 传送成功后再改变游戏模式
47-
applyRestoredState(peeker, data);
46+
// teleportAsync 回调线程不确定,使用实体调度器确保线程安全
47+
peeker.getScheduler().run(plugin,
48+
scheduledTask -> applyRestoredState(peeker, data),
49+
() -> plugin.getLogger().warning(
50+
String.format("玩家 %s 在传送恢复后离线", peeker.getName())));
4851
} else {
4952
onFailed.run();
5053
}
@@ -80,7 +83,11 @@ public void handleTeleportFailure(Player peeker, PeekData data) {
8083
final boolean scheduled = peeker.getScheduler().execute(plugin, () -> {
8184
peeker.teleportAsync(spawnLoc, TeleportCause.PLUGIN).thenAccept(spawnSuccess -> {
8285
if (spawnSuccess) {
83-
applyRestoredState(peeker, data);
86+
// teleportAsync 回调线程不确定,使用实体调度器确保线程安全
87+
peeker.getScheduler().run(plugin,
88+
scheduledTask -> applyRestoredState(peeker, data),
89+
() -> plugin.getLogger().warning(
90+
String.format("玩家 %s 在重生点传送恢复后离线", peeker.getName())));
8491
} else {
8592
onFailed.run();
8693
}
@@ -121,7 +128,17 @@ public Location resolveSpawnLocation(Player peeker) {
121128
* @param data PeekData 数据
122129
*/
123130
public void forceStateRestoreWithoutTeleport(Player peeker, PeekData data) {
124-
applyRestoredState(peeker, data);
131+
// 可能从 teleportAsync 回调(线程不确定)调用
132+
// 使用实体调度器确保在正确的区域线程上执行
133+
if (peeker.isOnline()) {
134+
peeker.getScheduler().run(plugin,
135+
scheduledTask -> applyRestoredState(peeker, data),
136+
() -> plugin.getLogger().warning(
137+
String.format("玩家 %s 在强制恢复状态时离线", peeker.getName())));
138+
} else {
139+
plugin.getLogger().warning(
140+
String.format("玩家 %s 已离线,无法强制恢复状态", peeker.getName()));
141+
}
125142
}
126143

127144
/**

0 commit comments

Comments
 (0)