Skip to content

Commit 7c0683e

Browse files
可以原地peek自己
1 parent 3210664 commit 7c0683e

8 files changed

Lines changed: 258 additions & 21 deletions

File tree

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"java.compile.nullAnalysis.mode": "automatic",
3-
"java.configuration.updateBuildConfiguration": "interactive"
3+
"java.configuration.updateBuildConfiguration": "automatic"
44
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>ict.minesunshineone</groupId>
88
<artifactId>peek</artifactId>
9-
<version>2.9</version>
9+
<version>3.1</version>
1010

1111
<properties>
1212
<maven.compiler.release>17</maven.compiler.release>

src/main/java/ict/minesunshineone/peek/command/PeekCommand.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
4949
handleAccept(player);
5050
case "deny" ->
5151
handleDeny(player);
52+
case "self" ->
53+
handleSelfPeek(player);
5254
default ->
5355
handlePeek(player, args[0]);
5456
};
@@ -99,6 +101,37 @@ private boolean handleDeny(Player player) {
99101
return true;
100102
}
101103

104+
private boolean handleSelfPeek(Player player) {
105+
// 检查玩家是否有权限
106+
if (!player.hasPermission("peek.self")) {
107+
plugin.getMessages().send(player, "no-permission");
108+
return true;
109+
}
110+
111+
// 检查玩家是否已经在peek状态
112+
if (plugin.getStateHandler().getActivePeeks().containsKey(player.getUniqueId())) {
113+
plugin.getMessages().send(player, "already-peeking");
114+
return true;
115+
}
116+
117+
// 检查玩家是否死亡
118+
if (player.isDead()) {
119+
plugin.getMessages().send(player, "cannot-peek-while-dead");
120+
return true;
121+
}
122+
123+
// 检查冷却时间
124+
if (plugin.getCooldownManager().isOnCooldown(player)) {
125+
plugin.getMessages().send(player, "cooldown-peek", "time",
126+
String.valueOf(plugin.getCooldownManager().getRemainingCooldown(player)));
127+
return true;
128+
}
129+
130+
// 开始self peek
131+
plugin.getStateHandler().startSelfPeek(player);
132+
return true;
133+
}
134+
102135
@Override
103136
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
104137
if (!(sender instanceof Player)) {
@@ -112,6 +145,7 @@ public List<String> onTabComplete(CommandSender sender, Command command, String
112145
completions.add("privacy");
113146
completions.add("accept");
114147
completions.add("deny");
148+
completions.add("self");
115149

116150
if (sender.hasPermission("peek.use")) {
117151
plugin.getServer().getOnlinePlayers().stream()

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

Lines changed: 202 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,83 @@ public void startPeek(Player peeker, Player target) {
7676
updateActionBar(target);
7777
}
7878

79+
public void startSelfPeek(Player peeker) {
80+
if (peeker == null) {
81+
plugin.getLogger().warning("尝试对空玩家使用自我观察功能");
82+
return;
83+
}
84+
85+
// 添加死亡检查
86+
if (peeker.isDead()) {
87+
plugin.getMessages().send(peeker, "cannot-peek-while-dead");
88+
return;
89+
}
90+
91+
// 添加在线检查
92+
if (!peeker.isOnline()) {
93+
plugin.getLogger().warning(String.format("尝试对离线玩家 %s 使用自我观察功能", peeker.getName()));
94+
return;
95+
}
96+
97+
// 防止并发修改
98+
synchronized (activePeeks) {
99+
if (activePeeks.containsKey(peeker.getUniqueId())) {
100+
plugin.getMessages().send(peeker, "already-peeking");
101+
return;
102+
}
103+
104+
try {
105+
logDebug("Starting self peek: %s", peeker.getName());
106+
107+
// 保存当前位置作为原始位置
108+
Location originalLocation = peeker.getLocation().clone();
109+
110+
// 创建特殊的 PeekData,目标设为自己
111+
PeekData data = new PeekData(
112+
originalLocation,
113+
peeker.getGameMode(),
114+
peeker.getUniqueId(), // 目标设为自己
115+
System.currentTimeMillis(),
116+
peeker.getHealth(),
117+
peeker.getFoodLevel(),
118+
peeker.getSaturation(),
119+
peeker.getActivePotionEffects()
120+
);
121+
122+
activePeeks.put(peeker.getUniqueId(), data);
123+
plugin.getStateManager().savePlayerState(peeker, data);
124+
plugin.getStatisticsManager().recordPeekStart(peeker, peeker); // 统计中目标也是自己
125+
126+
// 设置为观察者模式但不传送
127+
setSelfPeekGameMode(peeker);
128+
129+
// 发送消息
130+
plugin.getMessages().send(peeker, "self-peek-start");
131+
132+
// 播放声音
133+
playSound(peeker, "start-peek");
134+
135+
// 启动self距离检查器,确保不会超出距离限制
136+
startSelfRangeChecker(peeker, originalLocation);
137+
138+
logDebug("Self peek started successfully for player: %s", peeker.getName());
139+
140+
} catch (Exception e) {
141+
plugin.getLogger().warning(String.format("启动自我观察时发生错误,玩家: %s,错误: %s", peeker.getName(), e.getMessage()));
142+
if (plugin.getConfig().getBoolean("debug", false)) {
143+
e.printStackTrace();
144+
}
145+
146+
// 清理可能的残留状态
147+
activePeeks.remove(peeker.getUniqueId());
148+
stopRangeChecker(peeker);
149+
150+
// 通知玩家发生错误
151+
plugin.getMessages().send(peeker, "command-error");
152+
}
153+
}
154+
}
155+
79156
public void endPeek(Player peeker, boolean shouldRestore) {
80157
if (peeker == null) {
81158
plugin.getLogger().warning("尝试对空玩家结束贴贴功能");
@@ -140,16 +217,21 @@ public void endPeek(Player peeker, boolean shouldRestore) {
140217
if (peeker.isOnline()) {
141218
plugin.getMessages().send(peeker, "peek-end");
142219
}
143-
if (target != null && target.isOnline()) {
220+
221+
// 检查是否是自我观察模式(target是自己)
222+
boolean isSelfPeek = target != null && target.getUniqueId().equals(peeker.getUniqueId());
223+
224+
// 只有在非自我观察模式下才给目标发送消息
225+
if (target != null && target.isOnline() && !isSelfPeek) {
144226
plugin.getMessages().send(target, "peek-end-target", "player", peeker.getName());
145227
playSound(target, "end-peek");
146228
}
147229

148230
// 最后才移除活动peek
149231
activePeeks.remove(peeker.getUniqueId());
150232

151-
// 更新目标玩家的actionbar
152-
if (target != null && target.isOnline()) {
233+
// 更新目标玩家的actionbar(自我观察时不需要更新)
234+
if (target != null && target.isOnline() && !isSelfPeek) {
153235
updateActionBar(target);
154236
}
155237
}
@@ -168,23 +250,28 @@ private void teleportAndSetGameMode(Player peeker, Player target) {
168250
peeker.wakeup(false);
169251
}
170252

253+
// 传送之前先设置为蹲下
254+
peeker.setSneaking(true);
255+
171256
// 如果玩家在附身状态,先退出附身
172257
if (peeker.getGameMode() == GameMode.SPECTATOR && peeker.getSpectatorTarget() != null) {
173258
peeker.setSpectatorTarget(null);
174259
}
175260

261+
// 设置为旁观模式
176262
peeker.setGameMode(GameMode.SPECTATOR);
177263

178-
// 切换完成后再传送
179-
peeker.teleportAsync(target.getLocation()).thenAccept(success -> {
180-
if (!success) {
181-
plugin.getMessages().send(peeker, "teleport-failed");
182-
endPeek(peeker);
183-
} else {
184-
// 传送成功后再启动距离检查器
185-
startRangeChecker(peeker, target);
186-
}
187-
});
264+
// 等待1 tick后再传送
265+
plugin.getServer().getRegionScheduler().runDelayed(plugin, peeker.getLocation(), delayedTask -> {
266+
peeker.teleportAsync(target.getLocation()).thenAccept(success -> {
267+
if (!success) {
268+
plugin.getMessages().send(peeker, "teleport-failed");
269+
endPeek(peeker);
270+
} else {
271+
startRangeChecker(peeker, target);
272+
}
273+
});
274+
}, 2L); // 2 tick 延迟
188275
} catch (Exception e) {
189276
plugin.getLogger().warning(String.format("为玩家 %s 切换游戏模式时发生错误", peeker.getName()));
190277
endPeek(peeker);
@@ -353,4 +440,106 @@ public void updateActionBar(Player target) {
353440
"count", String.valueOf(peekerCount));
354441
}
355442
}
443+
444+
private void setSelfPeekGameMode(Player peeker) {
445+
plugin.getServer().getRegionScheduler().run(plugin, peeker.getLocation(), task -> {
446+
try {
447+
// 额外的在线检查
448+
if (!peeker.isOnline()) {
449+
plugin.getLogger().warning(String.format("玩家 %s 在设置自我观察模式时已离线", peeker.getName()));
450+
endPeek(peeker, false);
451+
return;
452+
}
453+
454+
// 死亡检查
455+
if (peeker.isDead()) {
456+
plugin.getLogger().warning(String.format("玩家 %s 在设置自我观察模式时已死亡", peeker.getName()));
457+
plugin.getMessages().send(peeker, "cannot-peek-while-dead");
458+
endPeek(peeker, false);
459+
return;
460+
}
461+
462+
// 如果玩家在睡觉,先让他离开床
463+
if (peeker.isSleeping()) {
464+
peeker.wakeup(false);
465+
}
466+
467+
// 如果玩家在附身状态,先退出附身
468+
if (peeker.getGameMode() == GameMode.SPECTATOR && peeker.getSpectatorTarget() != null) {
469+
peeker.setSpectatorTarget(null);
470+
}
471+
472+
// 设置为旁观模式,但不传送
473+
peeker.setGameMode(GameMode.SPECTATOR);
474+
475+
logDebug("Successfully set self peek game mode for player: %s", peeker.getName());
476+
} catch (Exception e) {
477+
plugin.getLogger().warning(String.format("为玩家 %s 设置自我观察模式时发生错误: %s", peeker.getName(), e.getMessage()));
478+
if (plugin.getConfig().getBoolean("debug", false)) {
479+
e.printStackTrace();
480+
}
481+
endPeek(peeker);
482+
}
483+
});
484+
}
485+
486+
private void startSelfRangeChecker(Player peeker, Location originalLocation) {
487+
synchronized (rangeCheckersLock) {
488+
// 先停止已有的检查器(如果有的话)
489+
stopRangeChecker(peeker);
490+
491+
ScheduledTask task = plugin.getServer().getRegionScheduler().runAtFixedRate(plugin,
492+
originalLocation, // 使用原始位置
493+
scheduledTask -> {
494+
// 检查玩家是否在线
495+
if (!peeker.isOnline()) {
496+
logDebug("Player %s went offline during self peek, ending peek", peeker.getName());
497+
endPeek(peeker);
498+
return;
499+
}
500+
501+
try {
502+
// 检查观察者是否死亡(与普通peek逻辑一致)
503+
if (peeker.isDead()) {
504+
logDebug("Player %s died during self peek, but continuing to monitor for respawn", peeker.getName());
505+
return; // 不立即结束,等待重生处理
506+
}
507+
508+
// 额外的状态检查
509+
PeekData data = activePeeks.get(peeker.getUniqueId());
510+
if (data == null) {
511+
logDebug("PeekData for player %s is null, stopping range checker", peeker.getName());
512+
scheduledTask.cancel();
513+
return;
514+
}
515+
516+
// 检查是否超出距离限制(相对于原始位置)
517+
if (peeker.getWorld().equals(originalLocation.getWorld())) {
518+
double distance = peeker.getLocation().distance(originalLocation);
519+
if (distance > maxPeekDistance) {
520+
logDebug("Player %s exceeded self peek distance: %.2f > %.2f",
521+
peeker.getName(), distance, maxPeekDistance);
522+
plugin.getMessages().send(peeker, "self-peek-range-exceeded");
523+
endPeek(peeker);
524+
}
525+
} else {
526+
// 如果换了世界,自动结束自我观察
527+
logDebug("Player %s changed world during self peek", peeker.getName());
528+
plugin.getMessages().send(peeker, "self-peek-world-changed");
529+
endPeek(peeker);
530+
}
531+
} catch (Exception e) {
532+
plugin.getLogger().warning(String.format("自我观察距离检查时发生错误:%s", e.getMessage()));
533+
if (plugin.getConfig().getBoolean("debug", false)) {
534+
e.printStackTrace();
535+
}
536+
endPeek(peeker);
537+
}
538+
},
539+
1L, 10L);
540+
541+
rangeCheckers.put(peeker.getUniqueId(), task);
542+
logDebug("Started self range checker for player: %s", peeker.getName());
543+
}
544+
}
356545
}

src/main/java/ict/minesunshineone/peek/listener/PeekListener.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,14 @@ public void onPlayerQuit(PlayerQuitEvent event) {
3939
}
4040

4141
// 如果是被观察者下线,结束所有观察他的玩家的观察状态
42+
// 但要排除自我观察的情况(观察者和被观察者是同一人)
4243
for (Map.Entry<UUID, PeekData> entry : new HashMap<>(plugin.getStateHandler().getActivePeeks()).entrySet()) {
43-
if (player.getUniqueId().equals(entry.getValue().getTargetUUID())) {
44-
Player peeker = plugin.getServer().getPlayer(entry.getKey());
44+
UUID peekerUUID = entry.getKey();
45+
UUID targetUUID = entry.getValue().getTargetUUID();
46+
47+
// 如果被观察者下线了,且不是自我观察
48+
if (player.getUniqueId().equals(targetUUID) && !peekerUUID.equals(targetUUID)) {
49+
Player peeker = plugin.getServer().getPlayer(peekerUUID);
4550
if (peeker != null) {
4651
plugin.getStateHandler().endPeek(peeker);
4752
}

src/main/resources/lang/en_US.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ messages:
44
prefix: ""
55
command-player-only: " &dThis feature can only be used by players!"
66
no-permission: " &dSorry, but you don't have permission to do this!"
7-
usage: " &dGuidance: \n&d➤ &f/peek <player> &7Peek player\n&d➤ &f/peek exit &7Exit Peek Mode\n&d➤ &f/peek stats &7Get the statistics\n&d➤ &f/peek privacy &7Turn on/off Privacy Mode\n&d➤ &f/peek accept &7Accept Peek request(s)\n&d➤ &f/peek deny &7Deny Peek request(s)"
7+
usage: " &dGuidance: \n&d➤ &f/peek <player> &7Peek player\n&d➤ &f/peek self &7Enable spectator mode in place\n&d➤ &f/peek exit &7Exit Peek Mode\n&d➤ &f/peek stats &7Get the statistics\n&d➤ &f/peek privacy &7Turn on/off Privacy Mode\n&d➤ &f/peek accept &7Accept Peek request(s)\n&d➤ &f/peek deny &7Deny Peek request(s)"
88
already-peeking: " &dYou're already peeking someone else,Type &f/peek exit &dto exit."
99
player-not-found: " &dCan't find the player &f{player} &d, Is there a typo in the name?"
1010
cannot-peek-self: " &dYou can't peek yourself!"
@@ -44,4 +44,7 @@ messages:
4444
request-cancelled-death: " &dYou died, and the peek request has been automatically canceled."
4545
request-cancelled-death-target: " &dThe other player has died, and the peek request has been automatically canceled."
4646
cannot-peek-while-dead: "&cYou can't use peek feature when you died."
47-
peek-end-respawn: "&aYou have respawned, your peek state has been restored~"
47+
peek-end-respawn: "&aYou have respawned, your peek state has been restored~"
48+
self-peek-start: " &d✨ Self-peek mode activated! You can now explore freely, remember to use &f/peek exit &dto exit spectator mode~"
49+
self-peek-range-exceeded: " &dYou are too far from your original position, self-peek mode has been automatically ended~"
50+
self-peek-world-changed: " &dDetected world change, self-peek mode has been automatically ended~"

src/main/resources/lang/zh_CN.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ messages:
22
prefix: ""
33
command-player-only: " &d这个魔法只能由玩家施展哦~"
44
no-permission: " &d抱歉,你还没有获得使用这个魔法的权限呢~"
5-
usage: " &d施法指南:\n&d➤ &f/peek <玩家名> &7贴贴指定玩家\n&d➤ &f/peek exit &7退出贴贴模式\n&d➤ &f/peek stats &7查看贴贴统计\n&d➤ &f/peek privacy &7切换私人模式\n&d➤ &f/peek accept &7接受贴贴请求\n&d➤ &f/peek deny &7拒绝贴贴请求"
5+
usage: " &d施法指南:\n&d➤ &f/peek <玩家名> &7贴贴指定玩家\n&d➤ &f/peek self &7在原地开启观察者模式\n&d➤ &f/peek exit &7退出贴贴模式\n&d➤ &f/peek stats &7查看贴贴统计\n&d➤ &f/peek privacy &7切换私人模式\n&d➤ &f/peek accept &7接受贴贴请求\n&d➤ &f/peek deny &7拒绝贴贴请求"
66
already-peeking: " &d你已经在贴贴别人了呢,输入 &f/peek exit &d解除魔法吧~"
77
player-not-found: " &d咦?找不到玩家 &f{player} &d呢,是不是拼错名字啦?"
88
cannot-peek-self: " &d自己贴贴自己?这可不行呢,试试贴贴别人吧!"
@@ -42,4 +42,7 @@ messages:
4242
request-cancelled-death: " &d你死亡了,贴贴请求已自动取消~"
4343
request-cancelled-death-target: " &d对方死亡了,贴贴请求已自动取消~"
4444
cannot-peek-while-dead: "&c你无法在死亡状态下使用贴贴功能!"
45-
peek-end-respawn: "&a你已重生,贴贴状态已恢复~"
45+
peek-end-respawn: "&a你已重生,贴贴状态已恢复~"
46+
self-peek-start: " &d✨ 自我观察模式已启动!你现在可以自由探索,记得用 &f/peek exit &d退出观察模式~"
47+
self-peek-range-exceeded: " &d你离原位置太远了,自我观察模式已自动结束~"
48+
self-peek-world-changed: " &d检测到你切换了世界,自我观察模式已自动结束~"

src/main/resources/plugin.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ permissions:
1818
peek.use:
1919
description: 允许使用peek命令
2020
default: op
21+
peek.self:
22+
description: 允许使用peek self命令在原地开启观察者模式
23+
default: op
2124
peek.bypass:
2225
description: 允许绕过私人模式
2326
default: op

0 commit comments

Comments
 (0)