diff --git a/api/src/main/java/com/github/retrooper/packetevents/protocol/score/BlankScoreFormat.java b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/BlankScoreFormat.java new file mode 100644 index 0000000000..3cd073ddd0 --- /dev/null +++ b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/BlankScoreFormat.java @@ -0,0 +1,39 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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.github.retrooper.packetevents.protocol.score; + +import net.kyori.adventure.text.Component; + +public final class BlankScoreFormat implements ScoreFormat { + + public static final BlankScoreFormat INSTANCE = new BlankScoreFormat(); + + private BlankScoreFormat() { + } + + @Override + public Component format(int score) { + return Component.empty(); + } + + @Override + public ScoreFormatType getType() { + return ScoreFormatTypes.BLANK; + } +} diff --git a/api/src/main/java/com/github/retrooper/packetevents/protocol/score/FixedScoreFormat.java b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/FixedScoreFormat.java new file mode 100644 index 0000000000..7c70f8e3e5 --- /dev/null +++ b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/FixedScoreFormat.java @@ -0,0 +1,44 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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.github.retrooper.packetevents.protocol.score; + +import net.kyori.adventure.text.Component; + +public final class FixedScoreFormat implements ScoreFormat { + + private final Component value; + + public FixedScoreFormat(Component value) { + this.value = value; + } + + @Override + public ScoreFormatType getType() { + return ScoreFormatTypes.FIXED; + } + + @Override + public Component format(int score) { + return this.value; + } + + public Component getValue() { + return this.value; + } +} diff --git a/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormat.java b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormat.java new file mode 100644 index 0000000000..35e0000af0 --- /dev/null +++ b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormat.java @@ -0,0 +1,43 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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.github.retrooper.packetevents.protocol.score; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.NonExtendable +public interface ScoreFormat { + + static BlankScoreFormat blankScore() { + return BlankScoreFormat.INSTANCE; + } + + static StyledScoreFormat styledScore(Style style) { + return new StyledScoreFormat(style); + } + + static FixedScoreFormat fixedScore(Component value) { + return new FixedScoreFormat(value); + } + + Component format(int score); + + ScoreFormatType getType(); +} diff --git a/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormatType.java b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormatType.java new file mode 100644 index 0000000000..8e542118ad --- /dev/null +++ b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormatType.java @@ -0,0 +1,29 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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.github.retrooper.packetevents.protocol.score; + +import com.github.retrooper.packetevents.protocol.mapper.StaticMappedEntity; +import com.github.retrooper.packetevents.wrapper.PacketWrapper; + +public interface ScoreFormatType extends StaticMappedEntity { + + ScoreFormat read(PacketWrapper wrapper); + + void write(PacketWrapper wrapper, ScoreFormat format); +} diff --git a/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormatTypes.java b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormatTypes.java new file mode 100644 index 0000000000..3bf6fd9fd5 --- /dev/null +++ b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/ScoreFormatTypes.java @@ -0,0 +1,106 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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.github.retrooper.packetevents.protocol.score; + +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.resources.ResourceLocation; +import com.github.retrooper.packetevents.wrapper.PacketWrapper; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public final class ScoreFormatTypes { + + private static final Map SCORE_FORMAT_TYPE_MAP = new HashMap<>(); + private static final Map SCORE_FORMAT_TYPE_ID_MAP = new HashMap<>(); + + public static final ScoreFormatType BLANK = define(0, "blank", BlankScoreFormat.class, + wrapper -> ScoreFormat.blankScore(), + (wrapper, format) -> { /**/ }); + public static final ScoreFormatType STYLED = define(1, "styled", StyledScoreFormat.class, + wrapper -> ScoreFormat.styledScore(wrapper.readStyle()), + (wrapper, format) -> wrapper.writeStyle(format.getStyle())); + public static final ScoreFormatType FIXED = define(2, "fixed", FixedScoreFormat.class, + wrapper -> ScoreFormat.fixedScore(wrapper.readComponent()), + (wrapper, format) -> wrapper.writeComponent(format.getValue())); + + private ScoreFormatTypes() { + } + + public static ScoreFormat read(PacketWrapper wrapper) { + int formatTypeId = wrapper.readVarInt(); + ScoreFormatType formatType = getById(wrapper.getServerVersion().toClientVersion(), formatTypeId); + if (formatType == null) { + throw new NullPointerException("Can't resolve format type " + formatTypeId); + } + return formatType.read(wrapper); + } + + public static void write(PacketWrapper wrapper, ScoreFormat format) { + int formatTypeId = format.getType().getId(wrapper.getServerVersion().toClientVersion()); + wrapper.writeVarInt(formatTypeId); + format.getType().write(wrapper, format); + } + + public static ScoreFormatType define(int id, String name, + Class formatClass, + Function, T> reader, + BiConsumer, T> writer) { + ResourceLocation location = new ResourceLocation(name); + ScoreFormatType type = new ScoreFormatType() { + @Override + public ScoreFormat read(PacketWrapper wrapper) { + return reader.apply(wrapper); + } + + @Override + public void write(PacketWrapper wrapper, ScoreFormat format) { + writer.accept(wrapper, formatClass.cast(format)); + } + + @Override + public ResourceLocation getName() { + return location; + } + + @Override + public int getId() { + return id; + } + }; + SCORE_FORMAT_TYPE_MAP.put(location.toString(), type); + SCORE_FORMAT_TYPE_ID_MAP.put((byte) id, type); + return type; + } + + public static @Nullable ScoreFormatType getById(ClientVersion version, int id) { + return SCORE_FORMAT_TYPE_ID_MAP.get((byte) id); + } + + public static @Nullable ScoreFormatType getByName(String name) { + return getByName(new ResourceLocation(name)); + } + + public static @Nullable ScoreFormatType getByName(ResourceLocation name) { + return SCORE_FORMAT_TYPE_MAP.get(name.toString()); + } +} diff --git a/api/src/main/java/com/github/retrooper/packetevents/protocol/score/StyledScoreFormat.java b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/StyledScoreFormat.java new file mode 100644 index 0000000000..27746f5e27 --- /dev/null +++ b/api/src/main/java/com/github/retrooper/packetevents/protocol/score/StyledScoreFormat.java @@ -0,0 +1,48 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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.github.retrooper.packetevents.protocol.score; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; + +public final class StyledScoreFormat implements ScoreFormat { + + private final Style style; + + public StyledScoreFormat(Style style) { + this.style = style; + } + + @Override + public Component format(int score) { + return Component.text() + .content(Integer.toString(score)) + .style(this.style) + .build(); + } + + @Override + public ScoreFormatType getType() { + return ScoreFormatTypes.STYLED; + } + + public Style getStyle() { + return this.style; + } +} diff --git a/api/src/main/java/com/github/retrooper/packetevents/util/adventure/AdventureNBTSerialization.java b/api/src/main/java/com/github/retrooper/packetevents/util/adventure/AdventureNBTSerialization.java index d836ef344a..d8684520ea 100644 --- a/api/src/main/java/com/github/retrooper/packetevents/util/adventure/AdventureNBTSerialization.java +++ b/api/src/main/java/com/github/retrooper/packetevents/util/adventure/AdventureNBTSerialization.java @@ -1,6 +1,6 @@ /* * This file is part of packetevents - https://github.com/retrooper/packetevents - * Copyright (C) 2023 retrooper and contributors + * Copyright (C) 2024 retrooper 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 @@ -19,6 +19,7 @@ package com.github.retrooper.packetevents.util.adventure; import com.github.retrooper.packetevents.netty.buffer.ByteBufInputStream; +import com.github.retrooper.packetevents.netty.buffer.ByteBufOutputStream; import net.kyori.adventure.key.Key; import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.BlockNBTComponent; @@ -35,8 +36,10 @@ import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.format.TextDecoration.State; import net.kyori.adventure.text.serializer.json.JSONComponentConstants; import java.io.DataInput; @@ -56,6 +59,7 @@ import static net.kyori.adventure.text.Component.storageNBT; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.Component.translatable; +import static net.kyori.adventure.text.event.ClickEvent.clickEvent; import static net.kyori.adventure.text.event.HoverEvent.ShowEntity.showEntity; import static net.kyori.adventure.text.event.HoverEvent.ShowItem.showItem; import static net.kyori.adventure.text.serializer.json.JSONComponentConstants.CLICK_EVENT; @@ -148,6 +152,445 @@ private static void requireState(boolean state) { } } + public static Style readStyle(Object byteBuf) throws IOException { + return readStyle(new ByteBufInputStream(byteBuf)); + } + + public static Style readStyle(DataInput input) throws IOException { + TagType type = resolveNbtType(input.readByte()); + return readStyle(input, type); + } + + private static Style readStyle(DataInput input, TagType rootType) throws IOException { + return readStyle(input, rootType, 0); + } + + private static Style readStyle(DataInput input, TagType rootType, int depth) throws IOException { + if (depth > DEPTH_LIMIT) { + throw new RuntimeException("Depth limit reached while decoding style: " + depth + " > " + DEPTH_LIMIT); + } + if (rootType != TagType.COMPOUND) { + throw new RuntimeException("Unsupported nbt tag type for style: " + rootType); + } + + Style.Builder style = null; + + // read until end + TagType type; + while ((type = resolveNbtType(input.readByte())) != TagType.END) { + String key = input.readUTF(); + + if (style == null) { + style = Style.style(); + } + readStyle(style, key, type, input, depth); + } + + return style == null ? Style.empty() : style.build(); + } + + @SuppressWarnings({"PatternValidation", "unchecked"}) // Key and HoverEvent + private static void readStyle(Style.Builder style, String key, TagType type, + DataInput input, int depth) throws IOException { + switch (key) { + case FONT: + requireType(type, TagType.STRING); + style.font(key(input.readUTF())); + break; + case COLOR: + requireType(type, TagType.STRING); + style.color(parseColor(input.readUTF())); + break; + case BOLD: + requireType(type, TagType.BYTE); + style.decoration(TextDecoration.BOLD, State.byBoolean(input.readBoolean())); + break; + case ITALIC: + requireType(type, TagType.BYTE); + style.decoration(TextDecoration.ITALIC, State.byBoolean(input.readBoolean())); + break; + case UNDERLINED: + requireType(type, TagType.BYTE); + style.decoration(TextDecoration.UNDERLINED, State.byBoolean(input.readBoolean())); + break; + case STRIKETHROUGH: + requireType(type, TagType.BYTE); + style.decoration(TextDecoration.STRIKETHROUGH, State.byBoolean(input.readBoolean())); + break; + case OBFUSCATED: + requireType(type, TagType.BYTE); + style.decoration(TextDecoration.OBFUSCATED, State.byBoolean(input.readBoolean())); + break; + case INSERTION: + requireType(type, TagType.STRING); + style.insertion(input.readUTF()); + break; + case CLICK_EVENT: + requireType(type, TagType.COMPOUND); + + ClickEvent.Action clickEventAction = null; + String clickEventValue = null; + + TagType clickType; + while ((clickType = resolveNbtType(input.readByte())) != TagType.END) { + String clickKey = input.readUTF(); + switch (clickKey) { + case CLICK_EVENT_ACTION: + requireType(clickType, TagType.STRING); + requireState(clickEventAction == null); + + String actionId = input.readUTF(); + switch (actionId) { + case OPEN_URL: + clickEventAction = ClickEvent.Action.OPEN_URL; + break; + case RUN_COMMAND: + clickEventAction = ClickEvent.Action.RUN_COMMAND; + break; + case SUGGEST_COMMAND: + clickEventAction = ClickEvent.Action.SUGGEST_COMMAND; + break; + case CHANGE_PAGE: + clickEventAction = ClickEvent.Action.CHANGE_PAGE; + break; + case COPY_TO_CLIPBOARD: + clickEventAction = ClickEvent.Action.COPY_TO_CLIPBOARD; + break; + default: + throw new IllegalStateException("Illegal click event action read: '" + actionId + "'"); + } + break; + case CLICK_EVENT_VALUE: + requireType(clickType, TagType.STRING); + requireState(clickEventValue == null); + clickEventValue = input.readUTF(); + break; + default: + throw new IllegalStateException("Illegal click event nbt key read: '" + clickKey + "'"); + } + } + requireState(clickEventAction != null && clickEventValue != null); + style.clickEvent(clickEvent(clickEventAction, clickEventValue)); + break; + case HOVER_EVENT: + requireType(type, TagType.COMPOUND); + + HoverEvent.Action hoverEventAction = null; + Object hoverEventContents = null; + + TagType hoverType; + while ((hoverType = resolveNbtType(input.readByte())) != TagType.END) { + String hoverKey = input.readUTF(); + switch (hoverKey) { + case HOVER_EVENT_ACTION: + requireType(hoverType, TagType.STRING); + requireState(hoverEventAction == null); + + String actionId = input.readUTF(); + switch (actionId) { + case SHOW_TEXT: + hoverEventAction = HoverEvent.Action.SHOW_TEXT; + break; + case SHOW_ITEM: + hoverEventAction = HoverEvent.Action.SHOW_ITEM; + break; + case SHOW_ENTITY: + hoverEventAction = HoverEvent.Action.SHOW_ENTITY; + break; + default: + throw new IllegalStateException("Illegal hover event action read: '" + actionId + "'"); + } + break; + case HOVER_EVENT_CONTENTS: + requireState(hoverEventContents == null); + requireState(hoverEventAction != null); + + switch (hoverEventAction.toString()) { + case SHOW_TEXT: + requireComponentType(hoverType); + hoverEventContents = readComponent(input, hoverType, depth + 1); + break; + case SHOW_ITEM: + if (hoverType == TagType.STRING) { + String itemId = input.readUTF(); + hoverEventContents = showItem(key(itemId), 1); + } else { + requireType(hoverType, TagType.COMPOUND); + + String itemId = null; + int count = 1; + String tag = null; + + TagType itemType; + while ((itemType = resolveNbtType(input.readByte())) != TagType.END) { + String itemKey = input.readUTF(); + switch (itemKey) { + case ITEM_ID: + requireType(itemType, TagType.STRING); + requireState(itemId == null); + itemId = input.readUTF(); + break; + case ITEM_COUNT: + requireType(itemType, TagType.INT); + count = input.readInt(); + break; + case ITEM_TAG: + requireType(itemType, TagType.STRING); + tag = input.readUTF(); + break; + } + } + + requireState(itemId != null); + hoverEventContents = showItem(key(itemId), count, + tag == null ? null : binaryTagHolder(tag)); + } + break; + case SHOW_ENTITY: + requireType(hoverType, TagType.COMPOUND); + + String entityType = null; + UUID entityId = null; + Component entityName = null; + + TagType itemType; + while ((itemType = resolveNbtType(input.readByte())) != TagType.END) { + String itemKey = input.readUTF(); + switch (itemKey) { + case ENTITY_TYPE: + requireType(itemType, TagType.STRING); + requireState(entityType == null); + entityType = input.readUTF(); + break; + case ENTITY_ID: + requireType(itemType, TagType.INT_ARRAY); + requireState(entityId == null); + entityId = readUniqueId(input); + break; + case ENTITY_NAME: + requireComponentType(itemType); + requireState(entityName == null); + entityName = readComponent(input, itemType, depth + 1); + break; + } + } + + requireState(entityType != null && entityId != null); + hoverEventContents = showEntity(key(entityType), entityId, entityName); + break; + } + break; + default: + throw new IllegalStateException("Illegal hover event nbt key read: '" + hoverKey + "'"); + } + } + requireState(hoverEventContents != null); + + // this is not unchecked, as it will 100% never fail - validated while reading + HoverEvent.Action unsafeAction = (HoverEvent.Action) hoverEventAction; + style.hoverEvent(HoverEvent.hoverEvent(unsafeAction, hoverEventContents)); + break; + default: + throw new IllegalStateException("Illegal component nbt key read: '" + key + "'"); + } + } + + public static void writeStyle(Object byteBuf, Style style) throws IOException { + writeStyle(new ByteBufOutputStream(byteBuf), style); + } + + public static void writeStyle(DataOutput output, Style style) throws IOException { + TagType tagType = TagType.COMPOUND; + output.writeByte(tagType.getId()); + writeStyle(output, style, tagType); + output.writeByte(TagType.END.getId()); // ends style tag + } + + private static void writeStyle(DataOutput output, Style style, TagType rootType) throws IOException { + if (rootType != TagType.COMPOUND) { + throw new UnsupportedEncodingException(); + } + if (style.isEmpty()) { + return; + } + + // font + Key font = style.font(); + if (font != null) { + output.writeByte(TagType.STRING.getId()); + output.writeUTF(FONT); + output.writeUTF(font.asString()); + } + + // color + TextColor color = style.color(); + if (color != null) { + output.writeByte(TagType.STRING.getId()); + output.writeUTF(COLOR); + output.writeUTF(stringifyColor(color)); + } + + // bold + State bold = style.decoration(TextDecoration.BOLD); + if (bold != State.NOT_SET) { + output.writeByte(TagType.BYTE.getId()); + output.writeUTF(BOLD); + output.writeBoolean(bold == State.TRUE); + } + // italic + State italic = style.decoration(TextDecoration.ITALIC); + if (italic != State.NOT_SET) { + output.writeByte(TagType.BYTE.getId()); + output.writeUTF(ITALIC); + output.writeBoolean(italic == State.TRUE); + } + // underlined + State underlined = style.decoration(TextDecoration.UNDERLINED); + if (underlined != State.NOT_SET) { + output.writeByte(TagType.BYTE.getId()); + output.writeUTF(UNDERLINED); + output.writeBoolean(underlined == State.TRUE); + } + // strikethrough + State strikethrough = style.decoration(TextDecoration.STRIKETHROUGH); + if (strikethrough != State.NOT_SET) { + output.writeByte(TagType.BYTE.getId()); + output.writeUTF(STRIKETHROUGH); + output.writeBoolean(strikethrough == State.TRUE); + } + // obfuscated + State obfuscated = style.decoration(TextDecoration.OBFUSCATED); + if (obfuscated != State.NOT_SET) { + output.writeByte(TagType.BYTE.getId()); + output.writeUTF(OBFUSCATED); + output.writeBoolean(obfuscated == State.TRUE); + } + + // insertion + String insertion = style.insertion(); + if (insertion != null) { + output.writeByte(TagType.STRING.getId()); + output.writeUTF(INSERTION); + output.writeUTF(insertion); + } + + // click event + ClickEvent clickEvent = style.clickEvent(); + if (clickEvent != null) { + // nested compound + output.writeByte(TagType.COMPOUND.getId()); + output.writeUTF(CLICK_EVENT); + + // click event action + ClickEvent.Action action = clickEvent.action(); + output.writeByte(TagType.STRING.getId()); + output.writeUTF(CLICK_EVENT_ACTION); + output.writeUTF(action.toString()); + + // click event value + String value = clickEvent.value(); + output.writeByte(TagType.STRING.getId()); + output.writeUTF(CLICK_EVENT_VALUE); + output.writeUTF(value); + + output.writeByte(TagType.END.getId()); // ends compound + } + + // hover event + HoverEvent hoverEvent = style.hoverEvent(); + if (hoverEvent != null) { + // nested compound + output.writeByte(TagType.COMPOUND.getId()); + output.writeUTF(HOVER_EVENT); + + // hover event action + HoverEvent.Action action = hoverEvent.action(); + output.writeByte(TagType.STRING.getId()); + output.writeUTF(HOVER_EVENT_ACTION); + output.writeUTF(action.toString()); + + // hover event contents + switch (action.toString()) { + case SHOW_TEXT: + Component text = (Component) hoverEvent.value(); + TagType textTagType = getComponentTagType(text); + output.writeByte(textTagType.getId()); + output.writeUTF(HOVER_EVENT_CONTENTS); + writeComponent(output, text, textTagType); + break; + case SHOW_ITEM: + HoverEvent.ShowItem item = (HoverEvent.ShowItem) hoverEvent.value(); + Key itemId = item.item(); + int count = item.count(); + BinaryTagHolder nbt = item.nbt(); + + if (count == 1 && nbt == null) { + output.writeByte(TagType.STRING.getId()); + output.writeUTF(HOVER_EVENT_CONTENTS); + output.writeUTF(itemId.asString()); + } else { + // nested compound + output.writeByte(TagType.COMPOUND.getId()); + output.writeUTF(HOVER_EVENT_CONTENTS); + + // item id + output.writeByte(TagType.STRING.getId()); + output.writeUTF(ITEM_ID); + output.writeUTF(itemId.asString()); + + // item count + if (count != 1) { + output.writeByte(TagType.INT.getId()); + output.writeUTF(ITEM_COUNT); + output.writeInt(count); + } + + // item nbt + if (nbt != null) { + output.writeByte(TagType.STRING.getId()); + output.writeUTF(ITEM_TAG); + output.writeUTF(nbt.string()); + } + + output.writeByte(TagType.END.getId()); // ends compound + } + break; + case SHOW_ENTITY: + HoverEvent.ShowEntity entity = (HoverEvent.ShowEntity) hoverEvent.value(); + Key entityType = entity.type(); + UUID entityId = entity.id(); + Component entityName = entity.name(); + + // nested compound + output.writeByte(TagType.COMPOUND.getId()); + output.writeUTF(HOVER_EVENT_CONTENTS); + + // entity type + output.writeByte(TagType.STRING.getId()); + output.writeUTF(ENTITY_TYPE); + output.writeUTF(entityType.asString()); + + // entity uuid + output.writeByte(TagType.INT_ARRAY.getId()); + output.writeUTF(ENTITY_ID); + writeUniqueId(output, entityId); + + // entity name + if (entityName != null) { + TagType nameTagType = getComponentTagType(entityName); + output.writeByte(nameTagType.getId()); + output.writeUTF(ENTITY_NAME); + writeComponent(output, entityName, nameTagType); + } + + output.writeByte(TagType.END.getId()); // ends compound + break; + } + + output.writeByte(TagType.END.getId()); // ends compound + } + } + public static Component readComponent(Object byteBuf) throws IOException { return readComponent(new ByteBufInputStream(byteBuf)); } @@ -161,7 +604,7 @@ private static Component readComponent(DataInput input, TagType rootType) throws return readComponent(input, rootType, 0); } - @SuppressWarnings({"PatternValidation", "unchecked"}) // Key and HoverEvent + @SuppressWarnings({"PatternValidation"}) // Key private static Component readComponent(DataInput input, TagType rootType, int depth) throws IOException { if (depth > DEPTH_LIMIT) { throw new RuntimeException("Depth limit reached while decoding component: " + depth + " > " + DEPTH_LIMIT); @@ -191,19 +634,7 @@ private static Component readComponent(DataInput input, TagType rootType, int de String nbtStorage = null; Component separator = null; - // style - String font = null; - String color = null; - TextDecoration.State obfuscated = TextDecoration.State.NOT_SET; - TextDecoration.State bold = TextDecoration.State.NOT_SET; - TextDecoration.State strikethrough = TextDecoration.State.NOT_SET; - TextDecoration.State underlined = TextDecoration.State.NOT_SET; - TextDecoration.State italic = TextDecoration.State.NOT_SET; - String insertion = null; - ClickEvent.Action clickEventAction = null; - String clickEventValue = null; - HoverEvent.Action hoverEventAction = null; - Object hoverEventContents = null; + Style.Builder style = null; // read until end TagType type; @@ -301,204 +732,12 @@ private static Component readComponent(DataInput input, TagType rootType, int de requireState(separator == null); separator = readComponent(input, type, depth + 1); break; - case FONT: - requireType(type, TagType.STRING); - requireState(font == null); - font = input.readUTF(); - break; - case COLOR: - requireType(type, TagType.STRING); - requireState(color == null); - color = input.readUTF(); - break; - case BOLD: - requireType(type, TagType.BYTE); - requireState(bold == TextDecoration.State.NOT_SET); - bold = TextDecoration.State.byBoolean(input.readBoolean()); - break; - case ITALIC: - requireType(type, TagType.BYTE); - requireState(italic == TextDecoration.State.NOT_SET); - italic = TextDecoration.State.byBoolean(input.readBoolean()); - break; - case UNDERLINED: - requireType(type, TagType.BYTE); - requireState(underlined == TextDecoration.State.NOT_SET); - underlined = TextDecoration.State.byBoolean(input.readBoolean()); - break; - case STRIKETHROUGH: - requireType(type, TagType.BYTE); - requireState(strikethrough == TextDecoration.State.NOT_SET); - strikethrough = TextDecoration.State.byBoolean(input.readBoolean()); - break; - case OBFUSCATED: - requireType(type, TagType.BYTE); - requireState(obfuscated == TextDecoration.State.NOT_SET); - obfuscated = TextDecoration.State.byBoolean(input.readBoolean()); - break; - case INSERTION: - requireType(type, TagType.STRING); - requireState(insertion == null); - insertion = input.readUTF(); - break; - case CLICK_EVENT: - requireType(type, TagType.COMPOUND); - requireState(clickEventAction == null && clickEventValue == null); - - TagType clickType; - while ((clickType = resolveNbtType(input.readByte())) != TagType.END) { - String clickKey = input.readUTF(); - switch (clickKey) { - case CLICK_EVENT_ACTION: - requireType(clickType, TagType.STRING); - requireState(clickEventAction == null); - - String actionId = input.readUTF(); - switch (actionId) { - case OPEN_URL: - clickEventAction = ClickEvent.Action.OPEN_URL; - break; - case RUN_COMMAND: - clickEventAction = ClickEvent.Action.RUN_COMMAND; - break; - case SUGGEST_COMMAND: - clickEventAction = ClickEvent.Action.SUGGEST_COMMAND; - break; - case CHANGE_PAGE: - clickEventAction = ClickEvent.Action.CHANGE_PAGE; - break; - case COPY_TO_CLIPBOARD: - clickEventAction = ClickEvent.Action.COPY_TO_CLIPBOARD; - break; - default: - throw new IllegalStateException("Illegal click event action read: '" + actionId + "'"); - } - break; - case CLICK_EVENT_VALUE: - requireType(clickType, TagType.STRING); - requireState(clickEventValue == null); - clickEventValue = input.readUTF(); - break; - default: - throw new IllegalStateException("Illegal click event nbt key read: '" + clickKey + "'"); - } - } - requireState(clickEventAction != null && clickEventValue != null); - break; - case HOVER_EVENT: - requireType(type, TagType.COMPOUND); - requireState(hoverEventAction == null); - - TagType hoverType; - while ((hoverType = resolveNbtType(input.readByte())) != TagType.END) { - String hoverKey = input.readUTF(); - switch (hoverKey) { - case HOVER_EVENT_ACTION: - requireType(hoverType, TagType.STRING); - requireState(hoverEventAction == null); - - String actionId = input.readUTF(); - switch (actionId) { - case SHOW_TEXT: - hoverEventAction = HoverEvent.Action.SHOW_TEXT; - break; - case SHOW_ITEM: - hoverEventAction = HoverEvent.Action.SHOW_ITEM; - break; - case SHOW_ENTITY: - hoverEventAction = HoverEvent.Action.SHOW_ENTITY; - break; - default: - throw new IllegalStateException("Illegal hover event action read: '" + actionId + "'"); - } - break; - case HOVER_EVENT_CONTENTS: - requireState(hoverEventContents == null); - requireState(hoverEventAction != null); - - switch (hoverEventAction.toString()) { - case SHOW_TEXT: - requireComponentType(hoverType); - hoverEventContents = readComponent(input, hoverType, depth + 1); - break; - case SHOW_ITEM: - if (hoverType == TagType.STRING) { - String itemId = input.readUTF(); - hoverEventContents = showItem(key(itemId), 1); - } else { - requireType(hoverType, TagType.COMPOUND); - - String itemId = null; - int count = 1; - String tag = null; - - TagType itemType; - while ((itemType = resolveNbtType(input.readByte())) != TagType.END) { - String itemKey = input.readUTF(); - switch (itemKey) { - case ITEM_ID: - requireType(itemType, TagType.STRING); - requireState(itemId == null); - itemId = input.readUTF(); - break; - case ITEM_COUNT: - requireType(itemType, TagType.INT); - count = input.readInt(); - break; - case ITEM_TAG: - requireType(itemType, TagType.STRING); - tag = input.readUTF(); - break; - } - } - - requireState(itemId != null); - hoverEventContents = showItem(key(itemId), count, - tag == null ? null : binaryTagHolder(tag)); - } - break; - case SHOW_ENTITY: - requireType(hoverType, TagType.COMPOUND); - - String entityType = null; - UUID entityId = null; - Component entityName = null; - - TagType itemType; - while ((itemType = resolveNbtType(input.readByte())) != TagType.END) { - String itemKey = input.readUTF(); - switch (itemKey) { - case ENTITY_TYPE: - requireType(itemType, TagType.STRING); - requireState(entityType == null); - entityType = input.readUTF(); - break; - case ENTITY_ID: - requireType(itemType, TagType.INT_ARRAY); - requireState(entityId == null); - entityId = readUniqueId(input); - break; - case ENTITY_NAME: - requireComponentType(itemType); - requireState(entityName == null); - entityName = readComponent(input, itemType, depth + 1); - break; - } - } - - requireState(entityType != null && entityId != null); - hoverEventContents = showEntity(key(entityType), entityId, entityName); - break; - } - break; - default: - throw new IllegalStateException("Illegal hover event nbt key read: '" + hoverKey + "'"); - } + default: + if (style == null) { + style = Style.style(); } - requireState(hoverEventContents != null); + readStyle(style, key, type, input, depth); break; - default: - throw new IllegalStateException("Illegal component nbt key read: '" + key + "'"); } } @@ -538,37 +777,8 @@ private static Component readComponent(DataInput input, TagType rootType, int de throw new IllegalStateException("Illegal nbt component, component type could not be determined"); } - if (font != null) { - builder.font(key(font)); - } - if (color != null) { - builder.color(parseColor(color)); - } - if (obfuscated != TextDecoration.State.NOT_SET) { - builder.decoration(TextDecoration.OBFUSCATED, obfuscated); - } - if (bold != TextDecoration.State.NOT_SET) { - builder.decoration(TextDecoration.BOLD, bold); - } - if (strikethrough != TextDecoration.State.NOT_SET) { - builder.decoration(TextDecoration.STRIKETHROUGH, strikethrough); - } - if (underlined != TextDecoration.State.NOT_SET) { - builder.decoration(TextDecoration.UNDERLINED, underlined); - } - if (italic != TextDecoration.State.NOT_SET) { - builder.decoration(TextDecoration.ITALIC, italic); - } - if (insertion != null) { - builder.insertion(insertion); - } - if (clickEventAction != null) { - builder.clickEvent(ClickEvent.clickEvent(clickEventAction, clickEventValue)); - } - if (hoverEventAction != null) { - // this is not unchecked, as it will 100% never fail - validated while reading - HoverEvent.Action unsafeAction = (HoverEvent.Action) hoverEventAction; - builder.hoverEvent(HoverEvent.hoverEvent(unsafeAction, hoverEventContents)); + if (style != null) { + builder.style(style.build()); } if (extra != null) { @@ -577,6 +787,10 @@ private static Component readComponent(DataInput input, TagType rootType, int de return builder.build(); } + public static void writeComponent(Object byteBuf, Component component) throws IOException { + writeComponent(new ByteBufOutputStream(byteBuf), component); + } + public static void writeComponent(DataOutput output, Component component) throws IOException { TagType tagType = getComponentTagType(component); output.writeByte(tagType.getId()); @@ -706,181 +920,7 @@ private static void writeComponent(DataOutput output, Component component, TagTy } if (component.hasStyling()) { - // font - Key font = component.font(); - if (font != null) { - output.writeByte(TagType.STRING.getId()); - output.writeUTF(FONT); - output.writeUTF(font.asString()); - } - - // color - TextColor color = component.color(); - if (color != null) { - output.writeByte(TagType.STRING.getId()); - output.writeUTF(COLOR); - output.writeUTF(stringifyColor(color)); - } - - // bold - TextDecoration.State bold = component.decoration(TextDecoration.BOLD); - if (bold != TextDecoration.State.NOT_SET) { - output.writeByte(TagType.BYTE.getId()); - output.writeUTF(BOLD); - output.writeBoolean(bold == TextDecoration.State.TRUE); - } - // italic - TextDecoration.State italic = component.decoration(TextDecoration.ITALIC); - if (italic != TextDecoration.State.NOT_SET) { - output.writeByte(TagType.BYTE.getId()); - output.writeUTF(ITALIC); - output.writeBoolean(italic == TextDecoration.State.TRUE); - } - // underlined - TextDecoration.State underlined = component.decoration(TextDecoration.UNDERLINED); - if (underlined != TextDecoration.State.NOT_SET) { - output.writeByte(TagType.BYTE.getId()); - output.writeUTF(UNDERLINED); - output.writeBoolean(underlined == TextDecoration.State.TRUE); - } - // strikethrough - TextDecoration.State strikethrough = component.decoration(TextDecoration.STRIKETHROUGH); - if (strikethrough != TextDecoration.State.NOT_SET) { - output.writeByte(TagType.BYTE.getId()); - output.writeUTF(STRIKETHROUGH); - output.writeBoolean(strikethrough == TextDecoration.State.TRUE); - } - // obfuscated - TextDecoration.State obfuscated = component.decoration(TextDecoration.OBFUSCATED); - if (obfuscated != TextDecoration.State.NOT_SET) { - output.writeByte(TagType.BYTE.getId()); - output.writeUTF(OBFUSCATED); - output.writeBoolean(obfuscated == TextDecoration.State.TRUE); - } - - // insertion - String insertion = component.insertion(); - if (insertion != null) { - output.writeByte(TagType.STRING.getId()); - output.writeUTF(INSERTION); - output.writeUTF(insertion); - } - - // click event - ClickEvent clickEvent = component.clickEvent(); - if (clickEvent != null) { - // nested compound - output.writeByte(TagType.COMPOUND.getId()); - output.writeUTF(CLICK_EVENT); - - // click event action - ClickEvent.Action action = clickEvent.action(); - output.writeByte(TagType.STRING.getId()); - output.writeUTF(CLICK_EVENT_ACTION); - output.writeUTF(action.toString()); - - // click event value - String value = clickEvent.value(); - output.writeByte(TagType.STRING.getId()); - output.writeUTF(CLICK_EVENT_VALUE); - output.writeUTF(value); - - output.writeByte(TagType.END.getId()); // ends compound - } - - // hover event - HoverEvent hoverEvent = component.hoverEvent(); - if (hoverEvent != null) { - // nested compound - output.writeByte(TagType.COMPOUND.getId()); - output.writeUTF(HOVER_EVENT); - - // hover event action - HoverEvent.Action action = hoverEvent.action(); - output.writeByte(TagType.STRING.getId()); - output.writeUTF(HOVER_EVENT_ACTION); - output.writeUTF(action.toString()); - - // hover event contents - switch (action.toString()) { - case SHOW_TEXT: - Component text = (Component) hoverEvent.value(); - TagType textTagType = getComponentTagType(text); - output.writeByte(textTagType.getId()); - output.writeUTF(HOVER_EVENT_CONTENTS); - writeComponent(output, text, textTagType); - break; - case SHOW_ITEM: - HoverEvent.ShowItem item = (HoverEvent.ShowItem) hoverEvent.value(); - Key itemId = item.item(); - int count = item.count(); - BinaryTagHolder nbt = item.nbt(); - - if (count == 1 && nbt == null) { - output.writeByte(TagType.STRING.getId()); - output.writeUTF(HOVER_EVENT_CONTENTS); - output.writeUTF(itemId.asString()); - } else { - // nested compound - output.writeByte(TagType.COMPOUND.getId()); - output.writeUTF(HOVER_EVENT_CONTENTS); - - // item id - output.writeByte(TagType.STRING.getId()); - output.writeUTF(ITEM_ID); - output.writeUTF(itemId.asString()); - - // item count - if (count != 1) { - output.writeByte(TagType.INT.getId()); - output.writeUTF(ITEM_COUNT); - output.writeInt(count); - } - - // item nbt - if (nbt != null) { - output.writeByte(TagType.STRING.getId()); - output.writeUTF(ITEM_TAG); - output.writeUTF(nbt.string()); - } - - output.writeByte(TagType.END.getId()); // ends compound - } - break; - case SHOW_ENTITY: - HoverEvent.ShowEntity entity = (HoverEvent.ShowEntity) hoverEvent.value(); - Key entityType = entity.type(); - UUID entityId = entity.id(); - Component entityName = entity.name(); - - // nested compound - output.writeByte(TagType.COMPOUND.getId()); - output.writeUTF(HOVER_EVENT_CONTENTS); - - // entity type - output.writeByte(TagType.STRING.getId()); - output.writeUTF(ENTITY_TYPE); - output.writeUTF(entityType.asString()); - - // entity uuid - output.writeByte(TagType.INT_ARRAY.getId()); - output.writeUTF(ENTITY_ID); - writeUniqueId(output, entityId); - - // entity name - if (entityName != null) { - TagType nameTagType = getComponentTagType(entityName); - output.writeByte(nameTagType.getId()); - output.writeUTF(ENTITY_NAME); - writeComponent(output, entityName, nameTagType); - } - - output.writeByte(TagType.END.getId()); // ends compound - break; - } - - output.writeByte(TagType.END.getId()); // ends compound - } + writeStyle(output, component.style(), TagType.COMPOUND); } // component children diff --git a/api/src/main/java/com/github/retrooper/packetevents/wrapper/PacketWrapper.java b/api/src/main/java/com/github/retrooper/packetevents/wrapper/PacketWrapper.java index fea4b1ae75..61f99e8d10 100644 --- a/api/src/main/java/com/github/retrooper/packetevents/wrapper/PacketWrapper.java +++ b/api/src/main/java/com/github/retrooper/packetevents/wrapper/PacketWrapper.java @@ -25,7 +25,6 @@ import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.manager.server.VersionComparison; import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper; -import com.github.retrooper.packetevents.netty.buffer.ByteBufOutputStream; import com.github.retrooper.packetevents.netty.channel.ChannelHelper; import com.github.retrooper.packetevents.protocol.PacketSide; import com.github.retrooper.packetevents.protocol.chat.ChatType; @@ -68,6 +67,7 @@ import com.github.retrooper.packetevents.util.crypto.SaltSignature; import com.github.retrooper.packetevents.util.crypto.SignatureData; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.NotNull; @@ -542,7 +542,7 @@ public void writeComponent(Component component) { public void writeComponentAsNBT(Component component) { try { - AdventureNBTSerialization.writeComponent(new ByteBufOutputStream(this.buffer), component); + AdventureNBTSerialization.writeComponent(this.buffer, component); } catch (IOException exception) { throw new IllegalStateException(exception); } @@ -553,6 +553,22 @@ public void writeComponentAsJSON(Component component) { this.writeString(jsonString, this.getMaxMessageLength()); } + public Style readStyle() { + try { + return AdventureNBTSerialization.readStyle(this.buffer); + } catch (IOException exception) { + throw new IllegalStateException(exception); + } + } + + public void writeStyle(Style style) { + try { + AdventureNBTSerialization.writeStyle(this.buffer, style); + } catch (IOException exception) { + throw new IllegalStateException(exception); + } + } + public ResourceLocation readIdentifier(int maxLen) { return new ResourceLocation(readString(maxLen)); } diff --git a/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerDisplayScoreboard.java b/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerDisplayScoreboard.java index ef13aff126..db45857c71 100644 --- a/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerDisplayScoreboard.java +++ b/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerDisplayScoreboard.java @@ -44,7 +44,11 @@ public void read() { } else { position = readByte(); } - scoreName = readString(16); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + scoreName = readString(); + } else { + scoreName = readString(16); + } } @Override @@ -54,7 +58,11 @@ public void write() { } else { writeByte(position); } - writeString(scoreName, 16); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + writeString(scoreName); // length limit removed + } else { + writeString(scoreName, 16); + } } @Override diff --git a/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerScoreboardObjective.java b/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerScoreboardObjective.java index 60dc955b14..f7d298a102 100644 --- a/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerScoreboardObjective.java +++ b/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerScoreboardObjective.java @@ -1,6 +1,6 @@ /* * This file is part of packetevents - https://github.com/retrooper/packetevents - * Copyright (C) 2022 retrooper and contributors + * Copyright (C) 2024 retrooper 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 @@ -21,22 +21,27 @@ import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.score.ScoreFormat; +import com.github.retrooper.packetevents.protocol.score.ScoreFormatTypes; import com.github.retrooper.packetevents.util.adventure.AdventureSerializer; import com.github.retrooper.packetevents.wrapper.PacketWrapper; import net.kyori.adventure.text.Component; import org.jetbrains.annotations.Nullable; public class WrapperPlayServerScoreboardObjective extends PacketWrapper { + private String name; private ObjectiveMode mode; private Component displayName; private @Nullable RenderType renderType; + private @Nullable ScoreFormat scoreFormat; public WrapperPlayServerScoreboardObjective(PacketSendEvent event) { super(event); } - public WrapperPlayServerScoreboardObjective(String name, ObjectiveMode mode, Component displayName, @Nullable RenderType renderType) { + public WrapperPlayServerScoreboardObjective(String name, ObjectiveMode mode, Component displayName, + @Nullable RenderType renderType) { super(PacketType.Play.Server.SCOREBOARD_OBJECTIVE); this.name = name; this.mode = mode; @@ -46,11 +51,16 @@ public WrapperPlayServerScoreboardObjective(String name, ObjectiveMode mode, Com @Override public void read() { - name = readString(); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + name = readString(); + } else { + name = readString(16); + } mode = ObjectiveMode.getById(readByte()); if (mode != ObjectiveMode.CREATE && mode != ObjectiveMode.UPDATE) { displayName = Component.empty(); renderType = RenderType.INTEGER; + scoreFormat = null; } else { if (serverVersion.isOlderThan(ServerVersion.V_1_13)) { displayName = AdventureSerializer.fromLegacyFormat(readString(32)); @@ -58,13 +68,18 @@ public void read() { } else { displayName = readComponent(); renderType = RenderType.getById(readVarInt()); + scoreFormat = readOptional(ScoreFormatTypes::read); } } } @Override public void write() { - writeString(name); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + writeString(name); + } else { + writeString(name, 16); + } writeByte((byte) mode.ordinal()); if (this.mode == ObjectiveMode.CREATE || this.mode == ObjectiveMode.UPDATE) { if (serverVersion.isOlderThan(ServerVersion.V_1_13)) { @@ -81,6 +96,7 @@ public void write() { } else { writeVarInt(RenderType.INTEGER.ordinal()); } + writeOptional(scoreFormat, ScoreFormatTypes::write); } } } @@ -91,6 +107,7 @@ public void copy(WrapperPlayServerScoreboardObjective wrapper) { mode = wrapper.mode; displayName = wrapper.displayName; renderType = wrapper.renderType; + scoreFormat = wrapper.scoreFormat; } public String getName() { @@ -125,6 +142,14 @@ public void setRenderType(@Nullable RenderType renderType) { this.renderType = renderType; } + public @Nullable ScoreFormat getScoreFormat() { + return this.scoreFormat; + } + + public void setScoreFormat(@Nullable ScoreFormat scoreFormat) { + this.scoreFormat = scoreFormat; + } + public enum ObjectiveMode { CREATE, REMOVE, @@ -168,4 +193,4 @@ public static RenderType getById(int id) { return VALUES[id]; } } -} \ No newline at end of file +} diff --git a/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerUpdateScore.java b/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerUpdateScore.java index 47555559a5..bac5ba733c 100644 --- a/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerUpdateScore.java +++ b/api/src/main/java/com/github/retrooper/packetevents/wrapper/play/server/WrapperPlayServerUpdateScore.java @@ -1,6 +1,6 @@ /* * This file is part of packetevents - https://github.com/retrooper/packetevents - * Copyright (C) 2022 retrooper and contributors + * Copyright (C) 2024 retrooper 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 @@ -21,22 +21,22 @@ import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.score.ScoreFormat; +import com.github.retrooper.packetevents.protocol.score.ScoreFormatTypes; import com.github.retrooper.packetevents.wrapper.PacketWrapper; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Nullable; import java.util.Optional; public class WrapperPlayServerUpdateScore extends PacketWrapper { + private String entityName; private Action action; private String objectiveName; private Optional value; - - public enum Action { - CREATE_OR_UPDATE_ITEM, - REMOVE_ITEM; - - public static final Action[] VALUES = values(); - } + private @Nullable Component entityDisplayName; + private @Nullable ScoreFormat scoreFormat; public WrapperPlayServerUpdateScore(PacketSendEvent event) { super(event); @@ -50,9 +50,26 @@ public WrapperPlayServerUpdateScore(String entityName, Action action, String obj this.value = value; } + public WrapperPlayServerUpdateScore(String entityName, Action action, String objectiveName, int value, + @Nullable Component entityDisplayName, @Nullable ScoreFormat scoreFormat) { + super(PacketType.Play.Server.UPDATE_SCORE); + this.entityName = entityName; + this.action = action; + this.objectiveName = objectiveName; + this.value = Optional.of(value); + this.entityDisplayName = entityDisplayName; + this.scoreFormat = scoreFormat; + } + @Override public void read() { - if (serverVersion == ServerVersion.V_1_7_10) { + if (this.serverVersion.isNewerThanOrEquals(ServerVersion.V_1_20_3)) { + this.entityName = this.readString(); + this.objectiveName = this.readString(); + this.value = Optional.of(this.readVarInt()); + this.entityDisplayName = this.readOptional(PacketWrapper::readComponent); + this.scoreFormat = this.readOptional(ScoreFormatTypes::read); + } else if (this.serverVersion == ServerVersion.V_1_7_10) { entityName = readString(16); action = Action.VALUES[readByte()]; if (action != Action.REMOVE_ITEM) { @@ -63,9 +80,17 @@ public void read() { value = Optional.empty(); } } else { - entityName = readString(40); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + entityName = readString(); + } else { + entityName = readString(40); + } action = Action.VALUES[readByte()]; - objectiveName = readString(16); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + objectiveName = readString(); + } else { + objectiveName = readString(16); + } if (action != Action.REMOVE_ITEM) { value = Optional.of(readVarInt()); } else { @@ -77,7 +102,13 @@ public void read() { @Override public void write() { - if (serverVersion == ServerVersion.V_1_7_10) { + if (this.serverVersion.isNewerThanOrEquals(ServerVersion.V_1_20_3)) { + this.writeString(this.entityName); + this.writeString(this.objectiveName); + this.writeVarInt(this.value.orElse(0)); + this.writeOptional(this.entityDisplayName, PacketWrapper::writeComponent); + this.writeOptional(this.scoreFormat, ScoreFormatTypes::write); + } else if (this.serverVersion == ServerVersion.V_1_7_10) { writeString(entityName, 16); writeByte(action.ordinal()); if (action != Action.REMOVE_ITEM) { @@ -88,9 +119,17 @@ public void write() { value = Optional.empty(); } } else { - writeString(entityName, 40); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + writeString(entityName); + } else { + writeString(entityName, 40); + } writeByte(action.ordinal()); - writeString(objectiveName, 16); + if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_18)) { + writeString(objectiveName); + } else { + writeString(objectiveName, 16); + } if (action != Action.REMOVE_ITEM) { writeVarInt(value.orElse(-1)); } @@ -104,6 +143,8 @@ public void copy(WrapperPlayServerUpdateScore wrapper) { action = wrapper.action; objectiveName = wrapper.objectiveName; value = wrapper.value; + entityDisplayName = wrapper.entityDisplayName; + scoreFormat = wrapper.scoreFormat; } public String getEntityName() { @@ -130,11 +171,41 @@ public void setObjectiveName(String objectiveName) { this.objectiveName = objectiveName; } + /** + * Always present for >=1.20.3 + */ public Optional getValue() { return value; } + /** + * Always present for >=1.20.3 + */ public void setValue(Optional value) { this.value = value; } + + public @Nullable Component getEntityDisplayName() { + return this.entityDisplayName; + } + + public void setEntityDisplayName(@Nullable Component entityDisplayName) { + this.entityDisplayName = entityDisplayName; + } + + public @Nullable ScoreFormat getScoreFormat() { + return this.scoreFormat; + } + + public void setScoreFormat(@Nullable ScoreFormat scoreFormat) { + this.scoreFormat = scoreFormat; + } + + public enum Action { + + CREATE_OR_UPDATE_ITEM, + REMOVE_ITEM; + + public static final Action[] VALUES = values(); + } }