55import java .util .Map ;
66import java .util .UUID ;
77
8+ import org .bukkit .Bukkit ;
89import org .bukkit .GameMode ;
910import org .bukkit .Location ;
1011import org .bukkit .Sound ;
1112import org .bukkit .attribute .Attribute ;
1213import org .bukkit .attribute .AttributeInstance ;
14+ import org .bukkit .boss .BarColor ;
15+ import org .bukkit .boss .BarStyle ;
16+ import org .bukkit .boss .BossBar ;
1317import org .bukkit .entity .Player ;
1418import org .bukkit .event .player .PlayerTeleportEvent .TeleportCause ;
1519import org .bukkit .util .Vector ;
@@ -23,6 +27,7 @@ public class PeekStateHandler {
2327 private final PeekPlugin plugin ;
2428 private final Map <UUID , PeekData > activePeeks = new HashMap <>();
2529 private final Map <UUID , ScheduledTask > rangeCheckers = new HashMap <>();
30+ private final Map <UUID , BossBar > distanceBossBars = new HashMap <>();
2631 private final Object rangeCheckersLock = new Object ();
2732 private final double maxPeekDistance ;
2833
@@ -69,14 +74,19 @@ public void startPeek(Player peeker, Player target) {
6974 teleportAndSetGameMode (peeker , target );
7075 }
7176
72- // 发送消息
73- plugin .getMessages ().send (peeker , "peek-start" , "player" , target .getName ());
74- plugin .getMessages ().send (target , "being-peeked" , "player" , peeker .getName ());
75-
76- // 播放声音
77- playSound (target , "start-peek" );
77+ // 检查是否静默 peek(有 bypass 权限)
78+ boolean silentPeek = plugin .getTargetHandler ().shouldSilentPeek (peeker );
7879
79- updateActionBar (target );
80+ // 发送消息给观察者
81+ plugin .getMessages ().send (peeker , "peek-start" , "player" , target .getName ());
82+
83+ // 只有非静默模式才通知目标
84+ if (!silentPeek ) {
85+ plugin .getMessages ().send (target , "being-peeked" , "player" , peeker .getName ());
86+ // 播放声音
87+ playSound (target , "start-peek" );
88+ updateActionBar (target );
89+ }
8090 }
8191
8292 public void startSelfPeek (Player peeker ) {
@@ -223,18 +233,20 @@ public void endPeek(Player peeker, boolean shouldRestore) {
223233
224234 // 检查是否是自我观察模式(target是自己)
225235 boolean isSelfPeek = target != null && target .getUniqueId ().equals (peeker .getUniqueId ());
236+ // 检查是否静默 peek(有 bypass 权限)
237+ boolean silentPeek = peeker .isOnline () && plugin .getTargetHandler ().shouldSilentPeek (peeker );
226238
227- // 只有在非自我观察模式下才给目标发送消息
228- if (target != null && target .isOnline () && !isSelfPeek ) {
239+ // 只有在非自我观察模式且非静默模式下才给目标发送消息
240+ if (target != null && target .isOnline () && !isSelfPeek && ! silentPeek ) {
229241 plugin .getMessages ().send (target , "peek-end-target" , "player" , peeker .getName ());
230242 playSound (target , "end-peek" );
231243 }
232244
233245 // 最后才移除活动peek
234246 activePeeks .remove (peeker .getUniqueId ());
235247
236- // 更新目标玩家的actionbar(自我观察时不需要更新)
237- if (target != null && target .isOnline () && !isSelfPeek ) {
248+ // 更新目标玩家的actionbar(自我观察时不需要更新,静默模式也不更新 )
249+ if (target != null && target .isOnline () && !isSelfPeek && ! silentPeek ) {
238250 updateActionBar (target );
239251 }
240252 }
@@ -271,7 +283,11 @@ private void teleportAndSetGameMode(Player peeker, Player target) {
271283 plugin .getMessages ().send (peeker , "teleport-failed" );
272284 endPeek (peeker );
273285 } else {
274- startRangeChecker (peeker , target );
286+ // 传送成功后,使用玩家的实体调度器确保在正确线程执行
287+ peeker .getScheduler ().run (plugin , scheduledTask -> {
288+ startRangeChecker (peeker , target );
289+ createDistanceBossBar (peeker , target );
290+ }, null );
275291 }
276292 });
277293 }, 2L ); // 2 tick 延迟
@@ -432,6 +448,7 @@ private void startRangeChecker(Player peeker, Player target) {
432448
433449 if (peeker .getWorld ().equals (target .getWorld ())) {
434450 double distance = peeker .getLocation ().distance (target .getLocation ());
451+ updateDistanceBossBar (peeker , distance );
435452 if (distance > maxPeekDistance ) {
436453 plugin .getMessages ().send (peeker , "range-exceeded" );
437454 endPeek (peeker );
@@ -459,6 +476,12 @@ public void stopRangeChecker(Player peeker) {
459476 task .cancel ();
460477 }
461478 }
479+ // 使用玩家的实体调度器确保在正确的线程上移除 BossBar
480+ if (peeker .isOnline ()) {
481+ peeker .getScheduler ().run (plugin , scheduledTask -> removeDistanceBossBar (peeker ), null );
482+ } else {
483+ removeDistanceBossBar (peeker );
484+ }
462485 }
463486
464487 public void cleanup () {
@@ -468,6 +491,58 @@ public void cleanup() {
468491 rangeCheckers .remove (peeker );
469492 });
470493 }
494+ // 清理所有 BossBar
495+ new HashMap <>(distanceBossBars ).forEach ((uuid , bar ) -> {
496+ bar .removeAll ();
497+ distanceBossBars .remove (uuid );
498+ });
499+ }
500+
501+ private void createDistanceBossBar (Player peeker , Player target ) {
502+ removeDistanceBossBar (peeker ); // 确保没有残留
503+ BossBar bar = Bukkit .createBossBar (
504+ String .format ("§d距离 §f%s§d: §e0.0 §7/ §e%.1f §d格" , target .getName (), maxPeekDistance ),
505+ BarColor .PINK ,
506+ BarStyle .SEGMENTED_10
507+ );
508+ bar .setProgress (0.0 );
509+ bar .addPlayer (peeker );
510+ distanceBossBars .put (peeker .getUniqueId (), bar );
511+ logDebug ("Created BossBar for player %s, target: %s" , peeker .getName (), target .getName ());
512+ }
513+
514+ private void updateDistanceBossBar (Player peeker , double distance ) {
515+ BossBar bar = distanceBossBars .get (peeker .getUniqueId ());
516+ if (bar == null ) return ;
517+
518+ PeekData data = activePeeks .get (peeker .getUniqueId ());
519+ if (data == null ) return ;
520+
521+ Player target = plugin .getServer ().getPlayer (data .getTargetUUID ());
522+ String targetName = target != null ? target .getName () : "未知" ;
523+
524+ double progress = Math .min (distance / maxPeekDistance , 1.0 );
525+ bar .setProgress (progress );
526+
527+ // 根据距离比例改变颜色
528+ if (progress < 0.5 ) {
529+ bar .setColor (BarColor .GREEN );
530+ } else if (progress < 0.75 ) {
531+ bar .setColor (BarColor .YELLOW );
532+ } else if (progress < 0.9 ) {
533+ bar .setColor (BarColor .RED );
534+ } else {
535+ bar .setColor (BarColor .RED );
536+ }
537+
538+ bar .setTitle (String .format ("§d距离 §f%s§d: §e%.1f §7/ §e%.1f §d格" , targetName , distance , maxPeekDistance ));
539+ }
540+
541+ private void removeDistanceBossBar (Player peeker ) {
542+ BossBar bar = distanceBossBars .remove (peeker .getUniqueId ());
543+ if (bar != null ) {
544+ bar .removeAll ();
545+ }
471546 }
472547
473548 public void removeActivePeek (Player player ) {
0 commit comments