Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ Keep a [TabPresenceListener](../src/main/java/pit12/runtime/player/TabPresenceLi

Subscribe to name changes with `addListener(Runnable)` in `start()`, then read the current names. In `stop()`, pass the same listener to `removeListener(Runnable)`.

Use `bindDisplayNameObserver(UUID, Consumer<String>)` to watch one current Tab entry. Each entry has one observer, called on the client thread after its display name changes. Name queries can also refresh an entry and call its observer. The name keeps formatting codes and may be `null`. Pass `null` as the observer to detach it. The subscription ends when the entry leaves Tab or the connection changes.

[SidebarCache](../src/main/java/pit12/runtime/scoreboard/SidebarCache.java) provides `lines()` for the server sidebar. It returns an immutable snapshot of up to 15 lines in display order. Lines include footer text and keep formatting codes. The objective title is excluded. Each [SidebarLine](../src/main/java/pit12/runtime/scoreboard/SidebarLine.java) provides the entry ID, text, and sorting score through `entry()`, `text()`, and `score()`.

`formattedText(minecraft.fontRendererObj)` removes formatting codes and characters with a width of zero in the Minecraft font. Call it on the client thread. It computes the text on the first call and caches it for that line. Score changes retain the cache. Text changes create a new line with an empty cache.
Expand Down
4 changes: 3 additions & 1 deletion docs/zh-CN/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- Source: docs/IMPLEMENTATION.md; Based on: 4d64e449a6fbe6c418a8ae05077b7a2c30327a60 -->
<!-- Source: docs/IMPLEMENTATION.md; Based on: f12f00d52de6f34fb6cd0d9679d6ccce66bf0fbf -->

# 12pit 实现

Expand Down Expand Up @@ -296,6 +296,8 @@ Bootstrap 为功能提供查询契约。其实时查询和订阅需要客户端

在 `start()` 中通过 `addListener(Runnable)` 订阅名称变化,再读取当前名称。在 `stop()` 中通过 `removeListener(Runnable)` 注销同一个监听器。

通过 `bindDisplayNameObserver(UUID, Consumer<String>)` 监听一个当前 Tab 条目。每个条目只能绑定一个监听器,显示名变化后在客户端线程上调用。名称查询也可能刷新条目并调用监听器。名称保留格式化代码,也可能为 `null`。将监听器设为 `null` 可取消绑定。条目离开 Tab 或连接变化时,订阅结束。

[SidebarCache](../../src/main/java/pit12/runtime/scoreboard/SidebarCache.java) 通过 `lines()` 查询服务器侧栏。它返回按显示顺序排列的不可变快照,最多包含 15 行。内容保留底部文字和格式化代码,不包含目标标题。每个 [SidebarLine](../../src/main/java/pit12/runtime/scoreboard/SidebarLine.java) 通过 `entry()`、`text()` 和 `score()` 提供条目标识、文字和排序分数。

`formattedText(minecraft.fontRendererObj)` 会去掉格式化代码,以及 Minecraft 字体中宽度为 0 的字符。请在客户端线程调用。它在首次调用时计算结果,并为这一行缓存结果。分数变化时保留缓存。文本变化时创建新行,并清空缓存。
Expand Down
47 changes: 33 additions & 14 deletions src/main/java/pit12/feature/denick/DenickFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,12 @@ public void stop() {
session.removeListener(sessionChanged);
equipment.removeListener(this);
presence.removeListener(this);
targets.values().forEach(Target::cancel);
for (Target target : targets.values()) {
target.cancel();
if (target.nick) {
names.bindDisplayNameObserver(target.id, null);
}
}
targets.clear();
checkedPlayers.clear();
if (queryWorker != null) {
Expand Down Expand Up @@ -274,11 +279,12 @@ private boolean isNick(UUID id) {
return false;
}
String display = names.displayName(id);
if (display == null) {
return false;
}
return display != null && !isNpc(display);
}

private boolean isNpc(String display) {
String plain = EnumChatFormatting.getTextWithoutFormattingCodes(display).trim();
return !plain.contains("[NPC]") && !NPC_TAB_NAME.matcher(display).lookingAt();
return plain.contains("[NPC]") || NPC_TAB_NAME.matcher(display).lookingAt();
}

@Override
Expand All @@ -289,6 +295,15 @@ public void onPlayerSeen(UUID playerId, String name, boolean joined) {
if (isNick(playerId)) {
Target target = targets.computeIfAbsent(playerId, id -> new Target(id, name));
target.nick = true;
names.bindDisplayNameObserver(playerId, display -> {
if (display != null && isNpc(display)) {
// A repeated ADD packet must not mark this entry as Nick again.
Target removed = targets.remove(playerId);
removed.cancel();
names.bindDisplayNameObserver(playerId, null);
changed();
}
});
if (config.enabled() && config.automatic.get()) {
begin(target, null, false);
}
Expand All @@ -302,6 +317,9 @@ public void onPlayerLeft(UUID playerId) {
Target target = targets.remove(playerId);
if (target != null) {
target.cancel();
if (target.nick) {
names.bindDisplayNameObserver(playerId, null);
}
changed();
}
}
Expand Down Expand Up @@ -474,11 +492,12 @@ private void query(Target target) {
cachedDisplayName = api.displayName(cached, cancelled);
String name = cachedDisplayName;
client.execute(() -> {
String display = displayName(target);
if (valid(target, requestGeneration, request)) {
target.realDisplayName = name;
target.owner = cached;
reply(search.sender, Tone.SUCCESS, language.format(
"{0} §c-> {1} §c(cached)", displayName(target), name));
reply(search.sender, Tone.SUCCESS,
language.format("{0} §c-> {1} §c(cached)", display, name));
changed();
}
});
Expand All @@ -490,8 +509,9 @@ private void query(Target target) {
DenickQuery result = DenickQuery.run(api, items, search.force ? null : cached,
cachedDisplayName, cancelled);
client.execute(() -> {
String display = displayName(target);
if (valid(target, requestGeneration, request)) {
complete(target, search, items, cached, result);
complete(target, search, items, cached, result, display);
}
});
} catch (InterruptedException interrupted) {
Expand All @@ -506,7 +526,7 @@ private boolean valid(Target target, long requestGeneration, long request) {
}

private void complete(Target target, Search search, List<DenickItem> items, String cached,
DenickQuery result) {
DenickQuery result, String display) {
boolean first = search.initial;
search.initial = false;
search.busy = false;
Expand All @@ -523,11 +543,9 @@ private void complete(Target target, Search search, List<DenickItem> items, Stri
if (search.force || !Objects.equals(previousName, result.displayName)
|| !Objects.equals(previousOwner, result.owner)) {
boolean corrected = cached != null && !Objects.equals(cached, result.owner);
reply(search.sender, Tone.SUCCESS,
language.format(
corrected ? source("{0} §c-> {1} §c(updated)")
: source("{0} §c-> {1}"),
displayName(target), result.displayName));
reply(search.sender, Tone.SUCCESS, language.format(
corrected ? source("{0} §c-> {1} §c(updated)") : source("{0} §c-> {1}"),
display, result.displayName));
changed();
}
if (!Objects.equals(mappings.put(target.name.toLowerCase(Locale.ROOT), result.owner),
Expand Down Expand Up @@ -588,6 +606,7 @@ private void reply(ICommandSender sender, Tone tone, String message) {
}

private String displayName(Target target) {
// Refreshing the name can remove an NPC target before a query reply.
String displayName = names.displayName(target.id);
return displayName == null ? target.name : displayName;
}
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/pit12/runtime/player/PlayerNameCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.network.NetHandlerPlayClient;
Expand Down Expand Up @@ -126,6 +127,18 @@ public void removeListener(Runnable listener) {
listeners.remove(listener);
}

/**
* One observer per current Tab entry. Null clears it; absent entries are ignored. Name queries can refresh an entry and
* call its observer.
*/
public void bindDisplayNameObserver(UUID playerId, Consumer<String> observer) {
session.checkThread();
NameEntry entry = entries.get(playerId);
if (entry != null) {
entry.displayNameObserver = observer;
}
}

@Override
public void onPlayerSeen(UUID playerId, String name, boolean joined) {
NetHandlerPlayClient handler = session.connection();
Expand Down Expand Up @@ -266,6 +279,9 @@ private void updateName(UUID playerId) {
if (!Objects.equals(entry.displayName, name)) {
entry.displayName = name;
entry.shortName = null;
if (entry.displayNameObserver != null) {
entry.displayNameObserver.accept(name);
}
Listeners.notify(listeners, Runnable::run);
}
}
Expand All @@ -283,6 +299,7 @@ private static final class NameEntry {
private NetworkPlayerInfo info;
private String displayName;
private String shortName;
private Consumer<String> displayNameObserver;

private NameEntry(NetworkPlayerInfo info) {
this.info = info;
Expand Down
Loading