PlayerTabOverlay.java
net.minecraft.client.gui.components.PlayerTabOverlay
信息
- 全限定名:net.minecraft.client.gui.components.PlayerTabOverlay
- 类型:public class
- 包:net.minecraft.client.gui.components
- 源码路径:src/main/java/net/minecraft/client/gui/components/PlayerTabOverlay.java
- 起始行号:L42
- 职责:
TODO
字段/常量
-
PING_UNKNOWN_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L43 - 说明:
TODO
- 类型:
-
PING_1_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L44 - 说明:
TODO
- 类型:
-
PING_2_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L45 - 说明:
TODO
- 类型:
-
PING_3_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L46 - 说明:
TODO
- 类型:
-
PING_4_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L47 - 说明:
TODO
- 类型:
-
PING_5_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L48 - 说明:
TODO
- 类型:
-
HEART_CONTAINER_BLINKING_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L49 - 说明:
TODO
- 类型:
-
HEART_CONTAINER_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L50 - 说明:
TODO
- 类型:
-
HEART_FULL_BLINKING_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L51 - 说明:
TODO
- 类型:
-
HEART_HALF_BLINKING_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L52 - 说明:
TODO
- 类型:
-
HEART_ABSORBING_FULL_BLINKING_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L53 - 说明:
TODO
- 类型:
-
HEART_FULL_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L54 - 说明:
TODO
- 类型:
-
HEART_ABSORBING_HALF_BLINKING_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L55 - 说明:
TODO
- 类型:
-
HEART_HALF_SPRITE- 类型:
Identifier - 修饰符:
private static final - 源码定位:
L56 - 说明:
TODO
- 类型:
-
PLAYER_COMPARATOR- 类型:
Comparator<PlayerInfo> - 修饰符:
private static final - 源码定位:
L57 - 说明:
TODO
- 类型:
-
MAX_ROWS_PER_COL- 类型:
int - 修饰符:
public static final - 源码定位:
L61 - 说明:
TODO
- 类型:
-
minecraft- 类型:
Minecraft - 修饰符:
private final - 源码定位:
L62 - 说明:
TODO
- 类型:
-
gui- 类型:
Gui - 修饰符:
private final - 源码定位:
L63 - 说明:
TODO
- 类型:
-
footer- 类型:
Component - 修饰符:
private - 源码定位:
L64 - 说明:
TODO
- 类型:
-
header- 类型:
Component - 修饰符:
private - 源码定位:
L65 - 说明:
TODO
- 类型:
-
visible- 类型:
boolean - 修饰符:
private - 源码定位:
L66 - 说明:
TODO
- 类型:
-
healthStates- 类型:
Map<UUID,PlayerTabOverlay.HealthState> - 修饰符:
private final - 源码定位:
L67 - 说明:
TODO
- 类型:
内部类/嵌套类型
-
net.minecraft.client.gui.components.PlayerTabOverlay.HealthState- 类型:
class - 修饰符:
private static - 源码定位:
L347 - 说明:
TODO
- 类型:
-
net.minecraft.client.gui.components.PlayerTabOverlay.ScoreDisplayEntry- 类型:
record - 修饰符:
private - 源码定位:
L384 - 说明:
TODO
- 类型:
构造器
public PlayerTabOverlay(Minecraft minecraft, Gui gui) @ L69
- 构造器名:PlayerTabOverlay
- 源码定位:L69
- 修饰符:public
参数:
- minecraft: Minecraft
- gui: Gui
说明:
TODO
方法
下面的方法块按源码顺序生成。
public Component getNameForDisplay(PlayerInfo info) @ L74
- 方法名:getNameForDisplay
- 源码定位:L74
- 返回类型:Component
- 修饰符:public
参数:
- info: PlayerInfo
说明:
TODO
private Component decorateName(PlayerInfo info, MutableComponent name) @ L80
- 方法名:decorateName
- 源码定位:L80
- 返回类型:Component
- 修饰符:private
参数:
- info: PlayerInfo
- name: MutableComponent
说明:
TODO
public void setVisible(boolean visible) @ L84
- 方法名:setVisible
- 源码定位:L84
- 返回类型:void
- 修饰符:public
参数:
- visible: boolean
说明:
TODO
private List<PlayerInfo> getPlayerInfos() @ L95
- 方法名:getPlayerInfos
- 源码定位:L95
- 返回类型:List
- 修饰符:private
参数:
- 无
说明:
TODO
public void extractRenderState(GuiGraphicsExtractor graphics, int screenWidth, Scoreboard scoreboard, Objective displayObjective) @ L99
- 方法名:extractRenderState
- 源码定位:L99
- 返回类型:void
- 修饰符:public
参数:
- graphics: GuiGraphicsExtractor
- screenWidth: int
- scoreboard: Scoreboard
- displayObjective: Objective
说明:
TODO
protected void extractPingIcon(GuiGraphicsExtractor graphics, int slotWidth, int xo, int yo, PlayerInfo info) @ L238
- 方法名:extractPingIcon
- 源码定位:L238
- 返回类型:void
- 修饰符:protected
参数:
- graphics: GuiGraphicsExtractor
- slotWidth: int
- xo: int
- yo: int
- info: PlayerInfo
说明:
TODO
private void extractTablistScore(Objective displayObjective, int yo, PlayerTabOverlay.ScoreDisplayEntry entry, int left, int right, UUID profileId, GuiGraphicsExtractor graphics) @ L257
- 方法名:extractTablistScore
- 源码定位:L257
- 返回类型:void
- 修饰符:private
参数:
- displayObjective: Objective
- yo: int
- entry: PlayerTabOverlay.ScoreDisplayEntry
- left: int
- right: int
- profileId: UUID
- graphics: GuiGraphicsExtractor
说明:
TODO
private void extractTablistHearts(int yo, int left, int right, UUID profileId, GuiGraphicsExtractor graphics, int score) @ L267
- 方法名:extractTablistHearts
- 源码定位:L267
- 返回类型:void
- 修饰符:private
参数:
- yo: int
- left: int
- right: int
- profileId: UUID
- graphics: GuiGraphicsExtractor
- score: int
说明:
TODO
public void setFooter(Component footer) @ L333
- 方法名:setFooter
- 源码定位:L333
- 返回类型:void
- 修饰符:public
参数:
- footer: Component
说明:
TODO
public void setHeader(Component header) @ L337
- 方法名:setHeader
- 源码定位:L337
- 返回类型:void
- 修饰符:public
参数:
- header: Component
说明:
TODO
public void reset() @ L341
- 方法名:reset
- 源码定位:L341
- 返回类型:void
- 修饰符:public
参数:
- 无
说明:
TODO
代码
@OnlyIn(Dist.CLIENT)
public class PlayerTabOverlay {
private static final Identifier PING_UNKNOWN_SPRITE = Identifier.withDefaultNamespace("icon/ping_unknown");
private static final Identifier PING_1_SPRITE = Identifier.withDefaultNamespace("icon/ping_1");
private static final Identifier PING_2_SPRITE = Identifier.withDefaultNamespace("icon/ping_2");
private static final Identifier PING_3_SPRITE = Identifier.withDefaultNamespace("icon/ping_3");
private static final Identifier PING_4_SPRITE = Identifier.withDefaultNamespace("icon/ping_4");
private static final Identifier PING_5_SPRITE = Identifier.withDefaultNamespace("icon/ping_5");
private static final Identifier HEART_CONTAINER_BLINKING_SPRITE = Identifier.withDefaultNamespace("hud/heart/container_blinking");
private static final Identifier HEART_CONTAINER_SPRITE = Identifier.withDefaultNamespace("hud/heart/container");
private static final Identifier HEART_FULL_BLINKING_SPRITE = Identifier.withDefaultNamespace("hud/heart/full_blinking");
private static final Identifier HEART_HALF_BLINKING_SPRITE = Identifier.withDefaultNamespace("hud/heart/half_blinking");
private static final Identifier HEART_ABSORBING_FULL_BLINKING_SPRITE = Identifier.withDefaultNamespace("hud/heart/absorbing_full_blinking");
private static final Identifier HEART_FULL_SPRITE = Identifier.withDefaultNamespace("hud/heart/full");
private static final Identifier HEART_ABSORBING_HALF_BLINKING_SPRITE = Identifier.withDefaultNamespace("hud/heart/absorbing_half_blinking");
private static final Identifier HEART_HALF_SPRITE = Identifier.withDefaultNamespace("hud/heart/half");
private static final Comparator<PlayerInfo> PLAYER_COMPARATOR = Comparator.<PlayerInfo>comparingInt(p -> -p.getTabListOrder())
.thenComparingInt(p -> p.getGameMode() == GameType.SPECTATOR ? 1 : 0)
.thenComparing(p -> Optionull.mapOrDefault(p.getTeam(), PlayerTeam::getName, ""))
.thenComparing(p -> p.getProfile().name(), String::compareToIgnoreCase);
public static final int MAX_ROWS_PER_COL = 20;
private final Minecraft minecraft;
private final Gui gui;
private @Nullable Component footer;
private @Nullable Component header;
private boolean visible;
private final Map<UUID, PlayerTabOverlay.HealthState> healthStates = new Object2ObjectOpenHashMap<>();
public PlayerTabOverlay(Minecraft minecraft, Gui gui) {
this.minecraft = minecraft;
this.gui = gui;
}
public Component getNameForDisplay(PlayerInfo info) {
return info.getTabListDisplayName() != null
? this.decorateName(info, info.getTabListDisplayName().copy())
: this.decorateName(info, PlayerTeam.formatNameForTeam(info.getTeam(), Component.literal(info.getProfile().name())));
}
private Component decorateName(PlayerInfo info, MutableComponent name) {
return info.getGameMode() == GameType.SPECTATOR ? name.withStyle(ChatFormatting.ITALIC) : name;
}
public void setVisible(boolean visible) {
if (this.visible != visible) {
this.healthStates.clear();
this.visible = visible;
if (visible) {
Component players = ComponentUtils.formatList(this.getPlayerInfos(), Component.literal(", "), this::getNameForDisplay);
this.minecraft.getNarrator().saySystemNow(Component.translatable("multiplayer.player.list.narration", players));
}
}
}
private List<PlayerInfo> getPlayerInfos() {
return this.minecraft.player.connection.getListedOnlinePlayers().stream().sorted(PLAYER_COMPARATOR).limit(80L).toList();
}
public void extractRenderState(GuiGraphicsExtractor graphics, int screenWidth, Scoreboard scoreboard, @Nullable Objective displayObjective) {
List<PlayerInfo> playerInfos = this.getPlayerInfos();
List<PlayerTabOverlay.ScoreDisplayEntry> entriesToDisplay = new ArrayList<>(playerInfos.size());
int spacerWidth = this.minecraft.font.width(" ");
int maxNameWidth = 0;
int maxScoreWidth = 0;
for (PlayerInfo info : playerInfos) {
Component playerName = this.getNameForDisplay(info);
maxNameWidth = Math.max(maxNameWidth, this.minecraft.font.width(playerName));
int playerScore = 0;
Component formattedPlayerScore = null;
int playerScoreWidth = 0;
if (displayObjective != null) {
ScoreHolder scoreHolder = ScoreHolder.fromGameProfile(info.getProfile());
ReadOnlyScoreInfo scoreInfo = scoreboard.getPlayerScoreInfo(scoreHolder, displayObjective);
if (scoreInfo != null) {
playerScore = scoreInfo.value();
}
if (displayObjective.getRenderType() != ObjectiveCriteria.RenderType.HEARTS) {
NumberFormat objectiveDefaultFormat = displayObjective.numberFormatOrDefault(StyledFormat.PLAYER_LIST_DEFAULT);
formattedPlayerScore = ReadOnlyScoreInfo.safeFormatValue(scoreInfo, objectiveDefaultFormat);
playerScoreWidth = this.minecraft.font.width(formattedPlayerScore);
maxScoreWidth = Math.max(maxScoreWidth, playerScoreWidth > 0 ? spacerWidth + playerScoreWidth : 0);
}
}
entriesToDisplay.add(new PlayerTabOverlay.ScoreDisplayEntry(playerName, playerScore, formattedPlayerScore, playerScoreWidth));
}
if (!this.healthStates.isEmpty()) {
Set<UUID> playerIds = playerInfos.stream().map(player -> player.getProfile().id()).collect(Collectors.toSet());
this.healthStates.keySet().removeIf(id -> !playerIds.contains(id));
}
int slots = playerInfos.size();
int rows = slots;
int cols;
for (cols = 1; rows > 20; rows = (slots + cols - 1) / cols) {
cols++;
}
boolean showHead = this.minecraft.isLocalServer() || this.minecraft.getConnection().getConnection().isEncrypted();
int widthForScore;
if (displayObjective != null) {
if (displayObjective.getRenderType() == ObjectiveCriteria.RenderType.HEARTS) {
widthForScore = 90;
} else {
widthForScore = maxScoreWidth;
}
} else {
widthForScore = 0;
}
int slotWidth = Math.min(cols * ((showHead ? 9 : 0) + maxNameWidth + widthForScore + 13), screenWidth - 50) / cols;
int xxo = screenWidth / 2 - (slotWidth * cols + (cols - 1) * 5) / 2;
int yyo = 10;
int maxLineWidth = slotWidth * cols + (cols - 1) * 5;
List<FormattedCharSequence> headerLines = null;
if (this.header != null) {
headerLines = this.minecraft.font.split(this.header, screenWidth - 50);
for (FormattedCharSequence line : headerLines) {
maxLineWidth = Math.max(maxLineWidth, this.minecraft.font.width(line));
}
}
List<FormattedCharSequence> footerLines = null;
if (this.footer != null) {
footerLines = this.minecraft.font.split(this.footer, screenWidth - 50);
for (FormattedCharSequence line : footerLines) {
maxLineWidth = Math.max(maxLineWidth, this.minecraft.font.width(line));
}
}
if (headerLines != null) {
graphics.fill(
screenWidth / 2 - maxLineWidth / 2 - 1, yyo - 1, screenWidth / 2 + maxLineWidth / 2 + 1, yyo + headerLines.size() * 9, Integer.MIN_VALUE
);
for (FormattedCharSequence line : headerLines) {
int lineWidth = this.minecraft.font.width(line);
graphics.text(this.minecraft.font, line, screenWidth / 2 - lineWidth / 2, yyo, -1);
yyo += 9;
}
yyo++;
}
graphics.fill(screenWidth / 2 - maxLineWidth / 2 - 1, yyo - 1, screenWidth / 2 + maxLineWidth / 2 + 1, yyo + rows * 9, Integer.MIN_VALUE);
int background = this.minecraft.options.getBackgroundColor(553648127);
for (int i = 0; i < slots; i++) {
int col = i / rows;
int row = i % rows;
int xo = xxo + col * slotWidth + col * 5;
int yo = yyo + row * 9;
graphics.fill(xo, yo, xo + slotWidth, yo + 8, background);
if (i < playerInfos.size()) {
PlayerInfo info = playerInfos.get(i);
PlayerTabOverlay.ScoreDisplayEntry displayInfo = entriesToDisplay.get(i);
GameProfile profile = info.getProfile();
if (showHead) {
Player playerByUUID = this.minecraft.level.getPlayerByUUID(profile.id());
boolean flip = playerByUUID != null && AvatarRenderer.isPlayerUpsideDown(playerByUUID);
PlayerFaceExtractor.extractRenderState(graphics, info.getSkin().body().texturePath(), xo, yo, 8, info.showHat(), flip, -1);
xo += 9;
}
graphics.text(this.minecraft.font, displayInfo.name, xo, yo, info.getGameMode() == GameType.SPECTATOR ? -1862270977 : -1);
if (displayObjective != null && info.getGameMode() != GameType.SPECTATOR) {
int left = xo + maxNameWidth + 1;
int right = left + widthForScore;
if (right - left > 5) {
this.extractTablistScore(displayObjective, yo, displayInfo, left, right, profile.id(), graphics);
}
}
this.extractPingIcon(graphics, slotWidth, xo - (showHead ? 9 : 0), yo, info);
}
}
if (footerLines != null) {
yyo += rows * 9 + 1;
graphics.fill(
screenWidth / 2 - maxLineWidth / 2 - 1, yyo - 1, screenWidth / 2 + maxLineWidth / 2 + 1, yyo + footerLines.size() * 9, Integer.MIN_VALUE
);
for (FormattedCharSequence line : footerLines) {
int lineWidth = this.minecraft.font.width(line);
graphics.text(this.minecraft.font, line, screenWidth / 2 - lineWidth / 2, yyo, -1);
yyo += 9;
}
}
}
protected void extractPingIcon(GuiGraphicsExtractor graphics, int slotWidth, int xo, int yo, PlayerInfo info) {
Identifier sprite;
if (info.getLatency() < 0) {
sprite = PING_UNKNOWN_SPRITE;
} else if (info.getLatency() < 150) {
sprite = PING_5_SPRITE;
} else if (info.getLatency() < 300) {
sprite = PING_4_SPRITE;
} else if (info.getLatency() < 600) {
sprite = PING_3_SPRITE;
} else if (info.getLatency() < 1000) {
sprite = PING_2_SPRITE;
} else {
sprite = PING_1_SPRITE;
}
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, xo + slotWidth - 11, yo, 10, 8);
}
private void extractTablistScore(
Objective displayObjective, int yo, PlayerTabOverlay.ScoreDisplayEntry entry, int left, int right, UUID profileId, GuiGraphicsExtractor graphics
) {
if (displayObjective.getRenderType() == ObjectiveCriteria.RenderType.HEARTS) {
this.extractTablistHearts(yo, left, right, profileId, graphics, entry.score);
} else if (entry.formattedScore != null) {
graphics.text(this.minecraft.font, entry.formattedScore, right - entry.scoreWidth, yo, -1);
}
}
private void extractTablistHearts(int yo, int left, int right, UUID profileId, GuiGraphicsExtractor graphics, int score) {
PlayerTabOverlay.HealthState health = this.healthStates.computeIfAbsent(profileId, id -> new PlayerTabOverlay.HealthState(score));
health.update(score, this.gui.getGuiTicks());
int fullHearts = Mth.positiveCeilDiv(Math.max(score, health.displayedValue()), 2);
int heartsToRender = Math.max(score, Math.max(health.displayedValue(), 20)) / 2;
boolean blink = health.isBlinking(this.gui.getGuiTicks());
if (fullHearts > 0) {
int widthPerHeart = Mth.floor(Math.min((float)(right - left - 4) / heartsToRender, 9.0F));
if (widthPerHeart <= 3) {
float pct = Mth.clamp(score / 20.0F, 0.0F, 1.0F);
int color = (int)((1.0F - pct) * 255.0F) << 16 | (int)(pct * 255.0F) << 8;
float hearts = score / 2.0F;
Component hpText = Component.translatable("multiplayer.player.list.hp", hearts);
Component text;
if (right - this.minecraft.font.width(hpText) >= left) {
text = hpText;
} else {
text = Component.literal(Float.toString(hearts));
}
graphics.text(this.minecraft.font, text, (right + left - this.minecraft.font.width(text)) / 2, yo, ARGB.opaque(color));
} else {
Identifier sprite = blink ? HEART_CONTAINER_BLINKING_SPRITE : HEART_CONTAINER_SPRITE;
for (int heart = fullHearts; heart < heartsToRender; heart++) {
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, left + heart * widthPerHeart, yo, 9, 9);
}
for (int heart = 0; heart < fullHearts; heart++) {
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, sprite, left + heart * widthPerHeart, yo, 9, 9);
if (blink) {
if (heart * 2 + 1 < health.displayedValue()) {
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, HEART_FULL_BLINKING_SPRITE, left + heart * widthPerHeart, yo, 9, 9);
}
if (heart * 2 + 1 == health.displayedValue()) {
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, HEART_HALF_BLINKING_SPRITE, left + heart * widthPerHeart, yo, 9, 9);
}
}
if (heart * 2 + 1 < score) {
graphics.blitSprite(
RenderPipelines.GUI_TEXTURED,
heart >= 10 ? HEART_ABSORBING_FULL_BLINKING_SPRITE : HEART_FULL_SPRITE,
left + heart * widthPerHeart,
yo,
9,
9
);
}
if (heart * 2 + 1 == score) {
graphics.blitSprite(
RenderPipelines.GUI_TEXTURED,
heart >= 10 ? HEART_ABSORBING_HALF_BLINKING_SPRITE : HEART_HALF_SPRITE,
left + heart * widthPerHeart,
yo,
9,
9
);
}
}
}
}
}
public void setFooter(@Nullable Component footer) {
this.footer = footer;
}
public void setHeader(@Nullable Component header) {
this.header = header;
}
public void reset() {
this.header = null;
this.footer = null;
}
@OnlyIn(Dist.CLIENT)
private static class HealthState {
private static final long DISPLAY_UPDATE_DELAY = 20L;
private static final long DECREASE_BLINK_DURATION = 20L;
private static final long INCREASE_BLINK_DURATION = 10L;
private int lastValue;
private int displayedValue;
private long lastUpdateTick;
private long blinkUntilTick;
public HealthState(int value) {
this.displayedValue = value;
this.lastValue = value;
}
public void update(int value, long tick) {
if (value != this.lastValue) {
long blinkDuration = value < this.lastValue ? 20L : 10L;
this.blinkUntilTick = tick + blinkDuration;
this.lastValue = value;
this.lastUpdateTick = tick;
}
if (tick - this.lastUpdateTick > 20L) {
this.displayedValue = value;
}
}
public int displayedValue() {
return this.displayedValue;
}
public boolean isBlinking(long tick) {
return this.blinkUntilTick > tick && (this.blinkUntilTick - tick) % 6L >= 3L;
}
}
@OnlyIn(Dist.CLIENT)
private record ScoreDisplayEntry(Component name, int score, @Nullable Component formattedScore, int scoreWidth) {
}
}引用的其他类
-
- 引用位置:
方法调用 - 关联成员:
Optionull.mapOrDefault()
- 引用位置:
-
- 引用位置:
参数/字段
- 引用位置:
-
- 引用位置:
参数/字段
- 引用位置:
-
- 引用位置:
参数
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
PlayerFaceExtractor.extractRenderState()
- 引用位置:
-
- 引用位置:
参数/字段/返回值
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
AvatarRenderer.isPlayerUpsideDown()
- 引用位置:
-
- 引用位置:
参数/字段/方法调用/返回值 - 关联成员:
Component.literal(), Component.translatable()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
ComponentUtils.formatList()
- 引用位置:
-
- 引用位置:
参数
- 引用位置:
-
- 引用位置:
字段/方法调用 - 关联成员:
Identifier.withDefaultNamespace()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
ARGB.opaque()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
Mth.clamp(), Mth.floor(), Mth.positiveCeilDiv()
- 引用位置:
-
- 引用位置:
参数
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
PlayerTeam.formatNameForTeam()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
ReadOnlyScoreInfo.safeFormatValue()
- 引用位置:
-
- 引用位置:
方法调用 - 关联成员:
ScoreHolder.fromGameProfile()
- 引用位置:
-
- 引用位置:
参数
- 引用位置: