diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/data/ChatNewlineRewriter.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/data/ChatNewlineRewriter.java new file mode 100644 index 000000000..fd4761aa8 --- /dev/null +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/data/ChatNewlineRewriter.java @@ -0,0 +1,149 @@ +/* + * This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind + * Copyright (C) 2018-2026 ViaVersion and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.data; + +import com.viaversion.viaversion.libs.gson.JsonArray; +import com.viaversion.viaversion.libs.gson.JsonElement; +import com.viaversion.viaversion.libs.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ChatNewlineRewriter { + public static List splitChatComponentByNewline(JsonElement element) { + return splitChatComponentByNewline(element, new JsonObject()); + } + + private static List splitChatComponentByNewline(JsonElement element, JsonObject inheritedStyle) { + List results = new ArrayList<>(); + if (element == null || element.isJsonNull()) { + return results; + } + + if (element.isJsonPrimitive()) { + String text = element.getAsString(); + for (String part : text.split("\n", -1)) { + JsonObject obj = inheritedStyle.deepCopy(); + obj.addProperty("text", part); + results.add(obj); + } + return results; + } + + if (element.isJsonArray()) { + for (JsonElement child : element.getAsJsonArray()) { + results.addAll(splitChatComponentByNewline(child, inheritedStyle)); + } + return results; + } + + if (!element.isJsonObject()) { + return results; + } + + JsonObject obj = element.getAsJsonObject(); + + // Build the style for this node: inherit first, then override with this object's style + JsonObject currentStyle = inheritedStyle.deepCopy(); + for (Map.Entry entry : obj.entrySet()) { + String key = entry.getKey(); + if (!"text".equals(key) && !"extra".equals(key)) { + currentStyle.add(key, entry.getValue()); + } + } + + // Gather components in order: base text then extra array + List ordered = new ArrayList<>(); + if (obj.has("text")) { + ordered.add(obj.get("text")); + } + if (obj.has("extra") && obj.get("extra").isJsonArray()) { + for (JsonElement extra : obj.getAsJsonArray("extra")) { + ordered.add(extra); + } + } + + // If there is no text/extra, return a single object with the style + if (ordered.isEmpty()) { + results.add(currentStyle); + return results; + } + + // Build lines by concatenating split pieces + List currentLine = new ArrayList<>(); + for (JsonElement part : ordered) { + if (part == null || part.isJsonNull()) { + continue; + } + + if (part.isJsonPrimitive()) { + String text = part.getAsString(); + String[] split = text.split("\n", -1); + for (int i = 0; i < split.length; i++) { + JsonObject piece = currentStyle.deepCopy(); + piece.addProperty("text", split[i]); + currentLine.add(piece); + + if (i < split.length - 1) { + results.add(mergeLine(currentLine)); + currentLine.clear(); + } + } + continue; + } + + if (part.isJsonObject() || part.isJsonArray()) { + List splitParts = splitChatComponentByNewline(part, currentStyle); + for (int i = 0; i < splitParts.size(); i++) { + currentLine.add(splitParts.get(i)); + if (i < splitParts.size() - 1) { + results.add(mergeLine(currentLine)); + currentLine.clear(); + } + } + } + } + + if (!currentLine.isEmpty()) { + results.add(mergeLine(currentLine)); + } + + return results; + } + + private static JsonObject mergeLine(List lineParts) { + if (lineParts.isEmpty()) { + JsonObject empty = new JsonObject(); + empty.addProperty("text", ""); + return empty; + } + + JsonObject first = lineParts.get(0); + if (lineParts.size() == 1) { + return first; + } + + JsonArray extra = new JsonArray(); + for (int i = 1; i < lineParts.size(); i++) { + extra.add(lineParts.get(i)); + } + first.add("extra", extra); + return first; + } +} diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/rewriter/PlayerPacketRewriter1_8.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/rewriter/PlayerPacketRewriter1_8.java index ef5c08199..ccfac6dd6 100644 --- a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/rewriter/PlayerPacketRewriter1_8.java +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/rewriter/PlayerPacketRewriter1_8.java @@ -30,6 +30,7 @@ import com.viaversion.viarewind.protocol.v1_7_6_10to1_7_2_5.packet.ServerboundPackets1_7_2_5; import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.Protocol1_8To1_7_6_10; import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.data.ChatItemRewriter; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.data.ChatNewlineRewriter; import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.TitleRenderProvider; import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage.EntityTracker1_8; import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.storage.GameProfileStorage; @@ -46,6 +47,7 @@ import com.viaversion.viaversion.api.rewriter.RewriterBase; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.libs.gson.JsonElement; +import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ClientboundPackets1_8; import com.viaversion.viaversion.protocols.v1_8to1_9.packet.ServerboundPackets1_8; import com.viaversion.viaversion.util.ComponentUtil; @@ -70,13 +72,27 @@ protected void registerPackets() { @Override public void register() { map(Types.COMPONENT); // Chat message + handler(wrapper -> { final JsonElement json = wrapper.get(Types.COMPONENT, 0); - ChatItemRewriter.toClient(protocol, wrapper.user(), json); - final int position = wrapper.read(Types.BYTE); + if (position == 2) { // Above hotbar wrapper.cancel(); + return; + } + + ChatItemRewriter.toClient(protocol, wrapper.user(), json); + List splitComponents = ChatNewlineRewriter.splitChatComponentByNewline(json); + + if (splitComponents.size() > 1) { + wrapper.cancel(); + + for (JsonObject split : splitComponents) { + PacketWrapper newWrapper = wrapper.create(ClientboundPackets1_7_2_5.CHAT); + newWrapper.write(Types.COMPONENT, split); + newWrapper.send(Protocol1_8To1_7_6_10.class); + } } }); }