/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Lists * com.mojang.logging.LogUtils * org.joml.Matrix3x2f * org.joml.Matrix3x2fc * org.joml.Vector2f * org.jspecify.annotations.Nullable * org.slf4j.Logger */ package net.minecraft.client.gui.components; import com.google.common.collect.Lists; import com.mojang.logging.LogUtils; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Optional; import java.util.function.Consumer; import net.minecraft.ChatFormatting; import net.minecraft.Optionull; import net.minecraft.client.GuiMessage; import net.minecraft.client.GuiMessageTag; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ActiveTextCollector; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.TextAlignment; import net.minecraft.client.gui.screens.ChatScreen; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.MessageSignature; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.Style; import net.minecraft.resources.Identifier; import net.minecraft.util.ARGB; import net.minecraft.util.ArrayListDeque; import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.Mth; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.entity.player.ChatVisiblity; import org.joml.Matrix3x2f; import org.joml.Matrix3x2fc; import org.joml.Vector2f; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class ChatComponent { private static final Logger LOGGER = LogUtils.getLogger(); private static final int MAX_CHAT_HISTORY = 100; private static final int MESSAGE_INDENT = 4; private static final int BOTTOM_MARGIN = 40; private static final int TOOLTIP_MAX_WIDTH = 210; private static final int TIME_BEFORE_MESSAGE_DELETION = 60; private static final Component DELETED_CHAT_MESSAGE = Component.translatable("chat.deleted_marker").withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC); public static final int MESSAGE_BOTTOM_TO_MESSAGE_TOP = 8; public static final Identifier QUEUE_EXPAND_ID = Identifier.withDefaultNamespace("internal/expand_chat_queue"); private static final Style QUEUE_EXPAND_TEXT_STYLE = Style.EMPTY.withClickEvent(new ClickEvent.Custom(QUEUE_EXPAND_ID, Optional.empty())).withHoverEvent(new HoverEvent.ShowText(Component.translatable("chat.queue.tooltip"))); private final Minecraft minecraft; private final ArrayListDeque recentChat = new ArrayListDeque(100); private final List allMessages = Lists.newArrayList(); private final List trimmedMessages = Lists.newArrayList(); private int chatScrollbarPos; private boolean newMessageSinceScroll; private @Nullable Draft latestDraft; private @Nullable ChatScreen preservedScreen; private final List messageDeletionQueue = new ArrayList(); public ChatComponent(Minecraft minecraft) { this.minecraft = minecraft; this.recentChat.addAll(minecraft.commandHistory().history()); } public void tick() { if (!this.messageDeletionQueue.isEmpty()) { this.processMessageDeletionQueue(); } } private int forEachLine(AlphaCalculator alphaCalculator, LineConsumer lineConsumer) { int perPage = this.getLinesPerPage(); int count = 0; for (int i = Math.min(this.trimmedMessages.size() - this.chatScrollbarPos, perPage) - 1; i >= 0; --i) { int messageIndex = i + this.chatScrollbarPos; GuiMessage.Line message = this.trimmedMessages.get(messageIndex); float alpha = alphaCalculator.calculate(message); if (!(alpha > 1.0E-5f)) continue; ++count; lineConsumer.accept(message, i, alpha); } return count; } public void render(GuiGraphics graphics, Font font, int ticks, int mouseX, int mouseY, boolean isChatting) { graphics.pose().pushMatrix(); this.render(isChatting ? new DrawingFocusedGraphicsAccess(graphics, font, mouseX, mouseY) : new DrawingBackgroundGraphicsAccess(graphics), graphics.guiHeight(), ticks, isChatting); graphics.pose().popMatrix(); } public void captureClickableText(ActiveTextCollector activeTextCollector, int screenHeight, int ticks, boolean isChatting) { this.render(new ClickableTextOnlyGraphicsAccess(activeTextCollector), screenHeight, ticks, isChatting); } private void render(final ChatGraphicsAccess graphics, int screenHeight, int ticks, boolean isChatting) { if (this.isChatHidden()) { return; } int total = this.trimmedMessages.size(); if (total <= 0) { return; } ProfilerFiller profiler = Profiler.get(); profiler.push("chat"); float scale = (float)this.getScale(); int maxWidth = Mth.ceil((float)this.getWidth() / scale); final int chatBottom = Mth.floor((float)(screenHeight - 40) / scale); final float textOpacity = this.minecraft.options.chatOpacity().get().floatValue() * 0.9f + 0.1f; float backgroundOpacity = this.minecraft.options.textBackgroundOpacity().get().floatValue(); final int messageHeight = this.minecraft.font.lineHeight; int messageBottomToMessageTop = 8; double chatLineSpacing = this.minecraft.options.chatLineSpacing().get(); final int entryHeight = (int)((double)messageHeight * (chatLineSpacing + 1.0)); final int entryBottomToMessageY = (int)Math.round(8.0 * (chatLineSpacing + 1.0) - 4.0 * chatLineSpacing); long queueSize = this.minecraft.getChatListener().queueSize(); AlphaCalculator alphaCalculator = isChatting ? AlphaCalculator.FULLY_VISIBLE : AlphaCalculator.timeBased(ticks); graphics.updatePose(pose -> { pose.scale(scale, scale); pose.translate(4.0f, 0.0f); }); this.forEachLine(alphaCalculator, (line, lineIndex, alpha) -> { int entryBottom = chatBottom - lineIndex * entryHeight; int entryTop = entryBottom - entryHeight; graphics.fill(-4, entryTop, maxWidth + 4 + 4, entryBottom, ARGB.black(alpha * backgroundOpacity)); }); if (queueSize > 0L) { graphics.fill(-2, chatBottom, maxWidth + 4, chatBottom + messageHeight, ARGB.black(backgroundOpacity)); } int count = this.forEachLine(alphaCalculator, new LineConsumer(){ boolean hoveredOverCurrentMessage; @Override public void accept(GuiMessage.Line line, int lineIndex, float alpha) { boolean forceIconRendering; int entryBottom = chatBottom - lineIndex * entryHeight; int entryTop = entryBottom - entryHeight; int textTop = entryBottom - entryBottomToMessageY; boolean hoveredOverCurrentLine = graphics.handleMessage(textTop, alpha * textOpacity, line.content()); this.hoveredOverCurrentMessage |= hoveredOverCurrentLine; if (line.endOfEntry()) { forceIconRendering = this.hoveredOverCurrentMessage; this.hoveredOverCurrentMessage = false; } else { forceIconRendering = false; } GuiMessageTag tag = line.tag(); if (tag != null) { graphics.handleTag(-4, entryTop, -2, entryBottom, alpha * textOpacity, tag); if (tag.icon() != null) { int iconLeft = line.getTagIconLeft(ChatComponent.this.minecraft.font); int textBottom = textTop + messageHeight; graphics.handleTagIcon(iconLeft, textBottom, forceIconRendering, tag, tag.icon()); } } } }); if (queueSize > 0L) { int queueLineBottom = chatBottom + messageHeight; MutableComponent queueMessage = Component.translatable("chat.queue", queueSize).setStyle(QUEUE_EXPAND_TEXT_STYLE); graphics.handleMessage(queueLineBottom - 8, 0.5f * textOpacity, queueMessage.getVisualOrderText()); } if (isChatting) { int virtualHeight = total * entryHeight; int chatHeight = count * entryHeight; int y = this.chatScrollbarPos * chatHeight / total - chatBottom; int height = chatHeight * chatHeight / virtualHeight; if (virtualHeight != chatHeight) { int alpha2 = y > 0 ? 170 : 96; int color = this.newMessageSinceScroll ? 0xCC3333 : 0x3333AA; int scrollBarStartX = maxWidth + 4; graphics.fill(scrollBarStartX, -y, scrollBarStartX + 2, -y - height, ARGB.color(alpha2, color)); graphics.fill(scrollBarStartX + 2, -y, scrollBarStartX + 1, -y - height, ARGB.color(alpha2, 0xCCCCCC)); } } profiler.pop(); } private boolean isChatHidden() { return this.minecraft.options.chatVisibility().get() == ChatVisiblity.HIDDEN; } public void clearMessages(boolean history) { this.minecraft.getChatListener().flushQueue(); this.messageDeletionQueue.clear(); this.trimmedMessages.clear(); this.allMessages.clear(); if (history) { this.recentChat.clear(); this.recentChat.addAll(this.minecraft.commandHistory().history()); } } public void addMessage(Component message) { this.addMessage(message, null, this.minecraft.isSingleplayer() ? GuiMessageTag.systemSinglePlayer() : GuiMessageTag.system()); } public void addMessage(Component contents, @Nullable MessageSignature signature, @Nullable GuiMessageTag tag) { GuiMessage message = new GuiMessage(this.minecraft.gui.getGuiTicks(), contents, signature, tag); this.logChatMessage(message); this.addMessageToDisplayQueue(message); this.addMessageToQueue(message); } private void logChatMessage(GuiMessage message) { String messageString = message.content().getString().replaceAll("\r", "\\\\r").replaceAll("\n", "\\\\n"); String logTag = Optionull.map(message.tag(), GuiMessageTag::logTag); if (logTag != null) { LOGGER.info("[{}] [CHAT] {}", (Object)logTag, (Object)messageString); } else { LOGGER.info("[CHAT] {}", (Object)messageString); } } private void addMessageToDisplayQueue(GuiMessage message) { int maxWidth = Mth.floor((double)this.getWidth() / this.getScale()); List lines = message.splitLines(this.minecraft.font, maxWidth); boolean chatting = this.isChatFocused(); for (int i = 0; i < lines.size(); ++i) { FormattedCharSequence line = lines.get(i); if (chatting && this.chatScrollbarPos > 0) { this.newMessageSinceScroll = true; this.scrollChat(1); } boolean endOfEntry = i == lines.size() - 1; this.trimmedMessages.addFirst(new GuiMessage.Line(message.addedTime(), line, message.tag(), endOfEntry)); } while (this.trimmedMessages.size() > 100) { this.trimmedMessages.removeLast(); } } private void addMessageToQueue(GuiMessage message) { this.allMessages.addFirst(message); while (this.allMessages.size() > 100) { this.allMessages.removeLast(); } } private void processMessageDeletionQueue() { int time = this.minecraft.gui.getGuiTicks(); this.messageDeletionQueue.removeIf(entry -> { if (time >= entry.deletableAfter()) { return this.deleteMessageOrDelay(entry.signature()) == null; } return false; }); } public void deleteMessage(MessageSignature signature) { DelayedMessageDeletion delayedMessage = this.deleteMessageOrDelay(signature); if (delayedMessage != null) { this.messageDeletionQueue.add(delayedMessage); } } private @Nullable DelayedMessageDeletion deleteMessageOrDelay(MessageSignature signature) { int time = this.minecraft.gui.getGuiTicks(); ListIterator iterator = this.allMessages.listIterator(); while (iterator.hasNext()) { GuiMessage message = iterator.next(); if (!signature.equals(message.signature())) continue; int deletableAfter = message.addedTime() + 60; if (time >= deletableAfter) { iterator.set(this.createDeletedMarker(message)); this.refreshTrimmedMessages(); return null; } return new DelayedMessageDeletion(signature, deletableAfter); } return null; } private GuiMessage createDeletedMarker(GuiMessage message) { return new GuiMessage(message.addedTime(), DELETED_CHAT_MESSAGE, null, GuiMessageTag.system()); } public void rescaleChat() { this.resetChatScroll(); this.refreshTrimmedMessages(); } private void refreshTrimmedMessages() { this.trimmedMessages.clear(); for (GuiMessage message : Lists.reverse(this.allMessages)) { this.addMessageToDisplayQueue(message); } } public ArrayListDeque getRecentChat() { return this.recentChat; } public void addRecentChat(String message) { if (!message.equals(this.recentChat.peekLast())) { if (this.recentChat.size() >= 100) { this.recentChat.removeFirst(); } this.recentChat.addLast(message); } if (message.startsWith("/")) { this.minecraft.commandHistory().addCommand(message); } } public void resetChatScroll() { this.chatScrollbarPos = 0; this.newMessageSinceScroll = false; } public void scrollChat(int dir) { this.chatScrollbarPos += dir; int max = this.trimmedMessages.size(); if (this.chatScrollbarPos > max - this.getLinesPerPage()) { this.chatScrollbarPos = max - this.getLinesPerPage(); } if (this.chatScrollbarPos <= 0) { this.chatScrollbarPos = 0; this.newMessageSinceScroll = false; } } public boolean isChatFocused() { return this.minecraft.screen instanceof ChatScreen; } private int getWidth() { return ChatComponent.getWidth(this.minecraft.options.chatWidth().get()); } private int getHeight() { return ChatComponent.getHeight(this.isChatFocused() ? this.minecraft.options.chatHeightFocused().get() : this.minecraft.options.chatHeightUnfocused().get()); } private double getScale() { return this.minecraft.options.chatScale().get(); } public static int getWidth(double pct) { int max = 320; int min = 40; return Mth.floor(pct * 280.0 + 40.0); } public static int getHeight(double pct) { int max = 180; int min = 20; return Mth.floor(pct * 160.0 + 20.0); } public static double defaultUnfocusedPct() { int max = 180; int min = 20; return 70.0 / (double)(ChatComponent.getHeight(1.0) - 20); } public int getLinesPerPage() { return this.getHeight() / this.getLineHeight(); } private int getLineHeight() { return (int)((double)this.minecraft.font.lineHeight * (this.minecraft.options.chatLineSpacing().get() + 1.0)); } public void saveAsDraft(String text) { boolean isCommand = text.startsWith("/"); this.latestDraft = new Draft(text, isCommand ? ChatMethod.COMMAND : ChatMethod.MESSAGE); } public void discardDraft() { this.latestDraft = null; } public T createScreen(ChatMethod chatMethod, ChatScreen.ChatConstructor chat) { if (this.latestDraft != null && chatMethod.isDraftRestorable(this.latestDraft)) { return chat.create(this.latestDraft.text(), true); } return chat.create(chatMethod.prefix(), false); } public void openScreen(ChatMethod chatMethod, ChatScreen.ChatConstructor chat) { this.minecraft.setScreen((Screen)this.createScreen(chatMethod, chat)); } public void preserveCurrentChatScreen() { Screen screen = this.minecraft.screen; if (screen instanceof ChatScreen) { ChatScreen chatScreen; this.preservedScreen = chatScreen = (ChatScreen)screen; } } public @Nullable ChatScreen restoreChatScreen() { ChatScreen restoredScreen = this.preservedScreen; this.preservedScreen = null; return restoredScreen; } public State storeState() { return new State(List.copyOf(this.allMessages), List.copyOf(this.recentChat), List.copyOf(this.messageDeletionQueue)); } public void restoreState(State state) { this.recentChat.clear(); this.recentChat.addAll(state.history); this.messageDeletionQueue.clear(); this.messageDeletionQueue.addAll(state.delayedMessageDeletions); this.allMessages.clear(); this.allMessages.addAll(state.messages); this.refreshTrimmedMessages(); } @FunctionalInterface private static interface AlphaCalculator { public static final AlphaCalculator FULLY_VISIBLE = message -> 1.0f; public static AlphaCalculator timeBased(int currentTickTime) { return message -> { int tickDelta = currentTickTime - message.addedTime(); double t = (double)tickDelta / 200.0; t = 1.0 - t; t *= 10.0; t = Mth.clamp(t, 0.0, 1.0); t *= t; return (float)t; }; } public float calculate(GuiMessage.Line var1); } @FunctionalInterface private static interface LineConsumer { public void accept(GuiMessage.Line var1, int var2, float var3); } private static class DrawingFocusedGraphicsAccess implements ChatGraphicsAccess, Consumer