diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ActionBarPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ActionBarPacket.java index 786749a3f..68c3ccae9 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ActionBarPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ActionBarPacket.java @@ -1,13 +1,18 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; -public record ActionBarPacket(@NotNull Component text) implements ServerPacket { +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; + +public record ActionBarPacket(@NotNull Component text) implements ComponentHoldingServerPacket { public ActionBarPacket(BinaryReader reader) { this(reader.readComponent()); } @@ -21,4 +26,14 @@ public record ActionBarPacket(@NotNull Component text) implements ServerPacket { public int getId() { return ServerPacketIdentifier.ACTION_BAR; } + + @Override + public @NotNull Collection components() { + return List.of(this.text); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new ActionBarPacket(operator.apply(this.text)); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java index baf735301..ec5713625 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/AdvancementsPacket.java @@ -2,7 +2,9 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; import net.minestom.server.advancements.FrameType; +import net.minestom.server.adventure.ComponentHolder; import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; @@ -11,11 +13,13 @@ import net.minestom.server.utils.binary.Writeable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; +import java.util.*; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; public record AdvancementsPacket(boolean reset, @NotNull List advancementMappings, @NotNull List identifiersToRemove, - @NotNull List progressMappings) implements ServerPacket { + @NotNull List progressMappings) implements ComponentHoldingServerPacket { public AdvancementsPacket { advancementMappings = List.copyOf(advancementMappings); identifiersToRemove = List.copyOf(identifiersToRemove); @@ -41,10 +45,35 @@ public record AdvancementsPacket(boolean reset, @NotNull List components() { + final var displayData = this.advancementMappings.stream().map(AdvancementMapping::value).map(Advancement::displayData).filter(Objects::nonNull).toList(); + final var titles = displayData.stream().map(DisplayData::title).toList(); + final var descriptions = displayData.stream().map(DisplayData::description).toList(); + + final var list = new ArrayList(); + + list.addAll(titles); + list.addAll(descriptions); + + return List.copyOf(list); + } + + @Override + public @NotNull ServerPacket copyWithOperator(final @NotNull UnaryOperator operator) { + return new AdvancementsPacket( + this.reset, + this.advancementMappings.stream().map(mapping -> mapping.copyWithOperator(operator)).toList(), + this.identifiersToRemove, + this.progressMappings + ); + } + /** * AdvancementMapping maps the namespaced ID to the Advancement. */ - public record AdvancementMapping(@NotNull String key, @NotNull Advancement value) implements Writeable { + public record AdvancementMapping(@NotNull String key, @NotNull Advancement value) implements Writeable, ComponentHolder { public AdvancementMapping(BinaryReader reader) { this(reader.readSizedString(), new Advancement(reader)); } @@ -54,11 +83,21 @@ public record AdvancementsPacket(boolean reset, @NotNull List components() { + return this.value.components(); + } + + @Override + public @NotNull AdvancementMapping copyWithOperator(@NotNull UnaryOperator operator) { + return this.value.displayData == null ? this : new AdvancementMapping(this.key, this.value.copyWithOperator(operator)); + } } public record Advancement(@Nullable String parentIdentifier, @Nullable DisplayData displayData, @NotNull List criteria, - @NotNull List requirements) implements Writeable { + @NotNull List requirements) implements Writeable, ComponentHolder { public Advancement { criteria = List.copyOf(criteria); requirements = List.copyOf(requirements); @@ -80,6 +119,16 @@ public record AdvancementsPacket(boolean reset, @NotNull List components() { + return this.displayData != null ? this.displayData.components() : List.of(); + } + + @Override + public @NotNull Advancement copyWithOperator(@NotNull UnaryOperator operator) { + return this.displayData == null ? this : new Advancement(this.parentIdentifier, this.displayData.copyWithOperator(operator), this.criteria, this.requirements); + } } public record Requirement(@NotNull List requirements) implements Writeable { @@ -100,7 +149,7 @@ public record AdvancementsPacket(boolean reset, @NotNull List { public DisplayData(BinaryReader reader) { this(read(reader)); } @@ -141,6 +190,16 @@ public record AdvancementsPacket(boolean reset, @NotNull List components() { + return List.of(this.title, this.description); + } + + @Override + public @NotNull DisplayData copyWithOperator(@NotNull UnaryOperator operator) { + return new DisplayData(operator.apply(this.title), operator.apply(this.description), this.icon, this.frameType, this.flags, this.backgroundTexture, this.x, this.y); + } } public record ProgressMapping(@NotNull String key, @NotNull AdvancementProgress progress) implements Writeable { diff --git a/src/main/java/net/minestom/server/network/packet/server/play/BossBarPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/BossBarPacket.java index 90206752b..2acd63bd0 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/BossBarPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/BossBarPacket.java @@ -3,6 +3,8 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.text.Component; import net.minestom.server.adventure.AdventurePacketConvertor; +import net.minestom.server.adventure.ComponentHolder; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; @@ -10,9 +12,12 @@ import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.binary.Writeable; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; import java.util.UUID; +import java.util.function.UnaryOperator; -public record BossBarPacket(@NotNull UUID uuid, @NotNull Action action) implements ServerPacket { +public record BossBarPacket(@NotNull UUID uuid, @NotNull Action action) implements ComponentHoldingServerPacket { public BossBarPacket(BinaryReader reader) { this(reader.readUuid(), switch (reader.readVarInt()) { case 0 -> new AddAction(reader); @@ -32,13 +37,27 @@ public record BossBarPacket(@NotNull UUID uuid, @NotNull Action action) implemen writer.write(action); } + @Override + public @NotNull Collection components() { + return this.action instanceof ComponentHolder holder + ? holder.components() + : List.of(); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return this.action instanceof ComponentHolder holder + ? new BossBarPacket(this.uuid, (Action) holder.copyWithOperator(operator)) + : this; + } + public sealed interface Action extends Writeable permits AddAction, RemoveAction, UpdateHealthAction, UpdateTitleAction, UpdateStyleAction, UpdateFlagsAction { int id(); } public record AddAction(@NotNull Component title, float health, @NotNull BossBar.Color color, - @NotNull BossBar.Overlay overlay, byte flags) implements Action { + @NotNull BossBar.Overlay overlay, byte flags) implements Action, ComponentHolder { public AddAction(@NotNull BossBar bar) { this(bar.name(), bar.progress(), bar.color(), bar.overlay(), AdventurePacketConvertor.getBossBarFlagValue(bar.flags())); @@ -63,6 +82,16 @@ public record BossBarPacket(@NotNull UUID uuid, @NotNull Action action) implemen public int id() { return 0; } + + @Override + public @NotNull Collection components() { + return List.of(this.title); + } + + @Override + public @NotNull AddAction copyWithOperator(@NotNull UnaryOperator operator) { + return new AddAction(operator.apply(this.title), this.health, this.color, this.overlay, this.flags); + } } public record RemoveAction() implements Action { @@ -96,7 +125,7 @@ public record BossBarPacket(@NotNull UUID uuid, @NotNull Action action) implemen } } - public record UpdateTitleAction(@NotNull Component title) implements Action { + public record UpdateTitleAction(@NotNull Component title) implements Action, ComponentHolder { public UpdateTitleAction(@NotNull BossBar bar) { this(bar.name()); } @@ -114,6 +143,16 @@ public record BossBarPacket(@NotNull UUID uuid, @NotNull Action action) implemen public int id() { return 3; } + + @Override + public @NotNull Collection components() { + return List.of(this.title); + } + + @Override + public @NotNull UpdateTitleAction copyWithOperator(@NotNull UnaryOperator operator) { + return new UpdateTitleAction(operator.apply(this.title)); + } } public record UpdateStyleAction(@NotNull BossBar.Color color, diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java index efe6fad4d..1521ce7b6 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ChatMessagePacket.java @@ -10,7 +10,7 @@ import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; import java.util.Collection; -import java.util.Collections; +import java.util.List; import java.util.UUID; import java.util.function.UnaryOperator; @@ -38,7 +38,7 @@ public record ChatMessagePacket(@NotNull Component message, @NotNull ChatPositio @Override public @NotNull Collection components() { - return Collections.singleton(message); + return List.of(message); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/DeathCombatEventPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/DeathCombatEventPacket.java index b54af7cb1..4c39a021d 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/DeathCombatEventPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/DeathCombatEventPacket.java @@ -1,14 +1,19 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; + public record DeathCombatEventPacket(int playerId, int entityId, - @NotNull Component message) implements ServerPacket { + @NotNull Component message) implements ComponentHoldingServerPacket { public DeathCombatEventPacket(BinaryReader reader) { this(reader.readVarInt(), reader.readInt(), reader.readComponent()); } @@ -24,4 +29,14 @@ public record DeathCombatEventPacket(int playerId, int entityId, public int getId() { return ServerPacketIdentifier.DEATH_COMBAT_EVENT; } + + @Override + public @NotNull Collection components() { + return List.of(this.message); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new DeathCombatEventPacket(this.playerId, this.entityId, operator.apply(this.message)); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java index 8a6b273e5..35232ba85 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/DisconnectPacket.java @@ -9,7 +9,7 @@ import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; import java.util.Collection; -import java.util.Collections; +import java.util.List; import java.util.function.UnaryOperator; public record DisconnectPacket(@NotNull Component message) implements ComponentHoldingServerPacket { @@ -29,7 +29,7 @@ public record DisconnectPacket(@NotNull Component message) implements ComponentH @Override public @NotNull Collection components() { - return Collections.singleton(message); + return List.of(message); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java index 6fc841d38..e433392f3 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityEquipmentPacket.java @@ -1,18 +1,24 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.text.Component; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.EnumMap; import java.util.Map; +import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; public record EntityEquipmentPacket(int entityId, - @NotNull Map equipments) implements ServerPacket { + @NotNull Map equipments) implements ComponentHoldingServerPacket { public EntityEquipmentPacket { equipments = Map.copyOf(equipments); if (equipments.isEmpty()) @@ -41,6 +47,23 @@ public record EntityEquipmentPacket(int entityId, return ServerPacketIdentifier.ENTITY_EQUIPMENT; } + @Override + public @NotNull Collection components() { + return this.equipments.values() + .stream() + .map(ItemStack::getDisplayName) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + final var map = new EnumMap(EquipmentSlot.class); + this.equipments.forEach((key, value) -> map.put(key, value.withDisplayName(operator))); + + return new EntityEquipmentPacket(this.entityId, map); + } + private static Map readEquipments(BinaryReader reader) { Map equipments = new EnumMap<>(EquipmentSlot.class); byte slot; diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java index 62c80903e..1d70eac73 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityMetaDataPacket.java @@ -1,17 +1,21 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.text.Component; import net.minestom.server.entity.Metadata; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.function.UnaryOperator; public record EntityMetaDataPacket(int entityId, - @NotNull Map> entries) implements ServerPacket { + @NotNull Map> entries) implements ComponentHoldingServerPacket { public EntityMetaDataPacket { entries = Map.copyOf(entries); } @@ -47,4 +51,27 @@ public record EntityMetaDataPacket(int entityId, public int getId() { return ServerPacketIdentifier.ENTITY_METADATA; } + + @Override + public @NotNull Collection components() { + return this.entries.values() + .stream() + .map(Metadata.Entry::value) + .filter(entry -> entry instanceof Component) + .map(entry -> (Component) entry) + .toList(); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + final var entries = new HashMap>(); + + this.entries.forEach((key, value) -> { + final var v = value.value(); + + entries.put(key, v instanceof Component c ? Metadata.OptChat(operator.apply(c)) : value); + }); + + return new EntityMetaDataPacket(this.entityId, entries); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java index 9e4cf36b6..71c35a83d 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/OpenWindowPacket.java @@ -1,14 +1,19 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; + public record OpenWindowPacket(int windowId, int windowType, - @NotNull Component title) implements ServerPacket { + @NotNull Component title) implements ComponentHoldingServerPacket { public OpenWindowPacket(BinaryReader reader) { this(reader.readVarInt(), reader.readVarInt(), reader.readComponent()); } @@ -24,4 +29,14 @@ public record OpenWindowPacket(int windowId, int windowType, public int getId() { return ServerPacketIdentifier.OPEN_WINDOW; } + + @Override + public @NotNull Collection components() { + return List.of(this.title); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new OpenWindowPacket(this.windowId, this.windowType, operator.apply(this.title)); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java index 85e406b37..1918e9abf 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java @@ -81,7 +81,7 @@ public record PlayerInfoPacket(@NotNull Action action, } return components; default: - return Collections.emptyList(); + return List.of(); } } @@ -152,7 +152,7 @@ public record PlayerInfoPacket(@NotNull Action action, @Override public @NotNull Collection components() { - return displayName != null ? Collections.singleton(displayName) : Collections.emptyList(); + return displayName != null ? List.of(displayName) : List.of(); } @Override @@ -218,7 +218,7 @@ public record PlayerInfoPacket(@NotNull Action action, @Override public @NotNull Collection components() { - return displayName != null ? Collections.singleton(displayName) : Collections.emptyList(); + return displayName != null ? List.of(displayName) : List.of(); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ResourcePackSendPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ResourcePackSendPacket.java index ff591e411..c73f604c8 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ResourcePackSendPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ResourcePackSendPacket.java @@ -1,6 +1,7 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.resourcepack.ResourcePack; @@ -8,8 +9,12 @@ import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; + public record ResourcePackSendPacket(String url, String hash, boolean forced, - Component prompt) implements ServerPacket { + Component prompt) implements ComponentHoldingServerPacket { public ResourcePackSendPacket(BinaryReader reader) { this(reader.readSizedString(), reader.readSizedString(), reader.readBoolean(), reader.readBoolean() ? reader.readComponent() : null); @@ -37,4 +42,14 @@ public record ResourcePackSendPacket(String url, String hash, boolean forced, public int getId() { return ServerPacketIdentifier.RESOURCE_PACK_SEND; } + + @Override + public @NotNull Collection components() { + return List.of(this.prompt); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new ResourcePackSendPacket(this.url, this.hash, this.forced, operator.apply(this.prompt)); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java index 6a26c4253..416c7247a 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ScoreboardObjectivePacket.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.function.UnaryOperator; public record ScoreboardObjectivePacket(@NotNull String objectiveName, byte mode, @@ -55,8 +56,8 @@ public record ScoreboardObjectivePacket(@NotNull String objectiveName, byte mode @Override public @NotNull Collection components() { - return mode == 0 || mode == 2 ? Collections.singleton(objectiveValue) : - Collections.emptyList(); + return mode == 0 || mode == 2 ? List.of(objectiveValue) : + List.of(); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java index 3541eea70..6170ecd15 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SetSlotPacket.java @@ -1,14 +1,22 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.text.Component; import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.UnaryOperator; + public record SetSlotPacket(byte windowId, int stateId, short slot, - @NotNull ItemStack itemStack) implements ServerPacket { + @NotNull ItemStack itemStack) implements ComponentHoldingServerPacket { public SetSlotPacket(BinaryReader reader) { this(reader.readByte(), reader.readVarInt(), reader.readShort(), reader.readItemStack()); @@ -27,6 +35,24 @@ public record SetSlotPacket(byte windowId, int stateId, short slot, return ServerPacketIdentifier.SET_SLOT; } + @Override + public @NotNull Collection components() { + final var components = new ArrayList<>(this.itemStack.getLore()); + final var displayname = this.itemStack.getDisplayName(); + if (displayname != null) components.add(displayname); + + return List.copyOf(components); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new SetSlotPacket(this.windowId, this.stateId, this.slot, this.itemStack.withDisplayName(operator).withLore(lines -> { + final var translatedComponents = new ArrayList(); + lines.forEach(component -> translatedComponents.add(operator.apply(component))); + return translatedComponents; + })); + } + /** * Returns a {@link SetSlotPacket} used to change a player cursor item. * diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SetTitleSubTitlePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SetTitleSubTitlePacket.java index c2964f894..9f6720fc9 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SetTitleSubTitlePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SetTitleSubTitlePacket.java @@ -1,13 +1,18 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; -public record SetTitleSubTitlePacket(@NotNull Component subtitle) implements ServerPacket { +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; + +public record SetTitleSubTitlePacket(@NotNull Component subtitle) implements ComponentHoldingServerPacket { public SetTitleSubTitlePacket(BinaryReader reader) { this(reader.readComponent()); } @@ -21,4 +26,14 @@ public record SetTitleSubTitlePacket(@NotNull Component subtitle) implements Ser public int getId() { return ServerPacketIdentifier.SET_TITLE_SUBTITLE; } + + @Override + public @NotNull Collection components() { + return List.of(this.subtitle); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new SetTitleSubTitlePacket(operator.apply(this.subtitle)); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SetTitleTextPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SetTitleTextPacket.java index b7ac73c4d..ed4a844c2 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SetTitleTextPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SetTitleTextPacket.java @@ -1,13 +1,18 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; -public record SetTitleTextPacket(@NotNull Component title) implements ServerPacket { +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; + +public record SetTitleTextPacket(@NotNull Component title) implements ComponentHoldingServerPacket { public SetTitleTextPacket(BinaryReader reader) { this(reader.readComponent()); } @@ -21,4 +26,14 @@ public record SetTitleTextPacket(@NotNull Component title) implements ServerPack public int getId() { return ServerPacketIdentifier.SET_TITLE_TEXT; } + + @Override + public @NotNull Collection components() { + return List.of(this.title); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new SetTitleTextPacket(operator.apply(this.title)); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java index ccb89cbc1..ed67e9f82 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java @@ -42,7 +42,7 @@ public record TabCompletePacket(int transactionId, int start, int length, @Override public @NotNull Collection components() { - if (matches.isEmpty()) return Collections.emptyList(); + if (matches.isEmpty()) return List.of(); List components = new ArrayList<>(matches.size()); for (Match match : matches) { if (match.tooltip != null) { @@ -75,7 +75,7 @@ public record TabCompletePacket(int transactionId, int start, int length, @Override public @NotNull Collection components() { - return tooltip != null ? Collections.singletonList(tooltip) : Collections.emptyList(); + return tooltip != null ? Collections.singletonList(tooltip) : List.of(); } @Override diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java index 778285b31..b571b6193 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TeamsPacket.java @@ -3,6 +3,8 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.adventure.AdventurePacketConvertor; +import net.minestom.server.adventure.ComponentHolder; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; @@ -13,11 +15,12 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.List; +import java.util.function.UnaryOperator; /** * The packet creates or updates teams */ -public record TeamsPacket(String teamName, Action action) implements ServerPacket { +public record TeamsPacket(String teamName, Action action) implements ComponentHoldingServerPacket { public TeamsPacket(BinaryReader reader) { this(reader.readSizedString(), switch (reader.readByte()) { case 0 -> new CreateTeamAction(reader); @@ -36,6 +39,21 @@ public record TeamsPacket(String teamName, Action action) implements ServerPacke writer.write(action); } + @Override + public @NotNull Collection components() { + return this.action instanceof ComponentHolder holder ? holder.components() : List.of(); + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new TeamsPacket( + this.teamName, + this.action instanceof ComponentHolder holder + ? (Action) holder.copyWithOperator(operator) + : this.action + ); + } + public sealed interface Action extends Writeable permits CreateTeamAction, RemoveTeamAction, UpdateTeamAction, AddEntitiesToTeamAction, RemoveEntitiesToTeamAction { int id(); @@ -44,7 +62,7 @@ public record TeamsPacket(String teamName, Action action) implements ServerPacke public record CreateTeamAction(Component displayName, byte friendlyFlags, NameTagVisibility nameTagVisibility, CollisionRule collisionRule, NamedTextColor teamColor, Component teamPrefix, Component teamSuffix, - Collection entities) implements Action { + Collection entities) implements Action, ComponentHolder { public CreateTeamAction { entities = List.copyOf(entities); } @@ -72,6 +90,25 @@ public record TeamsPacket(String teamName, Action action) implements ServerPacke public int id() { return 0; } + + @Override + public @NotNull Collection components() { + return List.of(this.displayName, this.teamPrefix, this.teamSuffix); + } + + @Override + public @NotNull CreateTeamAction copyWithOperator(@NotNull UnaryOperator operator) { + return new CreateTeamAction( + operator.apply(this.displayName), + this.friendlyFlags, + this.nameTagVisibility, + this.collisionRule, + this.teamColor, + operator.apply(this.teamPrefix), + operator.apply(this.teamSuffix), + entities + ); + } } public record RemoveTeamAction() implements Action { @@ -88,7 +125,7 @@ public record TeamsPacket(String teamName, Action action) implements ServerPacke public record UpdateTeamAction(Component displayName, byte friendlyFlags, NameTagVisibility nameTagVisibility, CollisionRule collisionRule, NamedTextColor teamColor, - Component teamPrefix, Component teamSuffix) implements Action { + Component teamPrefix, Component teamSuffix) implements Action, ComponentHolder { public UpdateTeamAction(BinaryReader reader) { this(reader.readComponent(), reader.readByte(), @@ -112,6 +149,24 @@ public record TeamsPacket(String teamName, Action action) implements ServerPacke public int id() { return 2; } + + @Override + public @NotNull Collection components() { + return List.of(this.displayName, this.teamPrefix, this.teamSuffix); + } + + @Override + public @NotNull UpdateTeamAction copyWithOperator(@NotNull UnaryOperator operator) { + return new UpdateTeamAction( + operator.apply(this.displayName), + this.friendlyFlags, + this.nameTagVisibility, + this.collisionRule, + this.teamColor, + operator.apply(this.teamPrefix), + operator.apply(this.teamSuffix) + ); + } } public record AddEntitiesToTeamAction(Collection entities) implements Action { diff --git a/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java index 4dc5d5145..e296364eb 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/WindowItemsPacket.java @@ -1,16 +1,23 @@ package net.minestom.server.network.packet.server.play; +import net.kyori.adventure.text.Component; import net.minestom.server.item.ItemStack; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; public record WindowItemsPacket(byte windowId, int stateId, @NotNull List items, - @NotNull ItemStack carriedItem) implements ServerPacket { + @NotNull ItemStack carriedItem) implements ComponentHoldingServerPacket { public WindowItemsPacket { items = List.copyOf(items); } @@ -32,4 +39,41 @@ public record WindowItemsPacket(byte windowId, int stateId, @NotNull List components() { + final var list = new ArrayList<>(this.items); + list.add(this.carriedItem); + + final var components = new ArrayList(); + + list.forEach(itemStack -> { + components.addAll(itemStack.getLore()); + + final var displayName = itemStack.getDisplayName(); + if (displayName == null) return; + + components.add(displayName); + }); + + return components; + } + + @Override + public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { + return new WindowItemsPacket( + this.windowId, + this.stateId, + this.items.stream().map(stack -> stack.withDisplayName(operator).withLore(lines -> { + final var translatedComponents = new ArrayList(); + lines.forEach(component -> translatedComponents.add(operator.apply(component))); + return translatedComponents; + })).toList(), + this.carriedItem.withDisplayName(operator).withLore(lines -> { + final var translatedComponents = new ArrayList(); + lines.forEach(component -> translatedComponents.add(operator.apply(component))); + return translatedComponents; + }) + ); + } } diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index 12f72b697..eb65f8a5e 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -8,15 +8,16 @@ import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import net.minestom.server.MinecraftServer; import net.minestom.server.Viewable; +import net.minestom.server.adventure.ComponentHolder; +import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; -import net.minestom.server.network.packet.server.CachedPacket; -import net.minestom.server.network.packet.server.FramedPacket; -import net.minestom.server.network.packet.server.SendablePacket; -import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.*; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.utils.binary.BinaryBuffer; @@ -101,12 +102,47 @@ public final class PacketUtils { */ public static void sendGroupedPacket(@NotNull Collection players, @NotNull ServerPacket packet, @NotNull Predicate predicate) { - final SendablePacket sendablePacket = GROUPED_PACKET ? new CachedPacket(packet) : packet; + final var sendablePacket = shouldUseCachePacket(packet) ? new CachedPacket(packet) : packet; + players.forEach(player -> { if (predicate.test(player)) player.sendPacket(sendablePacket); }); } + /** + * Checks if the {@link ServerPacket} is suitable to be wrapped into a {@link CachedPacket}. + * Note: {@link ComponentHoldingServerPacket}s are not translated inside a {@link CachedPacket}. + * + * @see CachedPacket#body() + * @see PlayerSocketConnection#writePacketSync(SendablePacket, boolean) + */ + static boolean shouldUseCachePacket(final @NotNull ServerPacket packet) { + if (!MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION) return GROUPED_PACKET; + if (!(packet instanceof ComponentHoldingServerPacket holder)) return GROUPED_PACKET; + return !containsTranslatableComponents(holder); + } + + private static boolean containsTranslatableComponents(final @NotNull ComponentHolder holder) { + for (final Component component : holder.components()) { + if (isTranslatable(component)) return true; + } + + return false; + } + + private static boolean isTranslatable(final @NotNull Component component) { + if (component instanceof TranslatableComponent) return true; + + final var children = component.children(); + if (children.isEmpty()) return false; + + for (final Component child : children) { + if (isTranslatable(child)) return true; + } + + return false; + } + /** * Same as {@link #sendGroupedPacket(Collection, ServerPacket, Predicate)} * but with the player validator sets to null. diff --git a/src/test/java/net/minestom/server/api/TestConnectionImpl.java b/src/test/java/net/minestom/server/api/TestConnectionImpl.java index 286563bf0..5292c2798 100644 --- a/src/test/java/net/minestom/server/api/TestConnectionImpl.java +++ b/src/test/java/net/minestom/server/api/TestConnectionImpl.java @@ -1,10 +1,13 @@ package net.minestom.server.api; +import net.kyori.adventure.translation.GlobalTranslator; import net.minestom.server.ServerProcess; +import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerLoginEvent; import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.player.PlayerConnection; @@ -13,6 +16,7 @@ import org.jetbrains.annotations.NotNull; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; @@ -54,12 +58,26 @@ final class TestConnectionImpl implements TestConnection { final class PlayerConnectionImpl extends PlayerConnection { @Override public void sendPacket(@NotNull SendablePacket packet) { + final var serverPacket = this.extractPacket(packet); for (var tracker : incomingTrackers) { - final var serverPacket = SendablePacket.extractServerPacket(packet); if (tracker.type.isAssignableFrom(serverPacket.getClass())) tracker.packets.add(serverPacket); } } + private ServerPacket extractPacket(final SendablePacket packet) { + if (!(packet instanceof ServerPacket serverPacket)) return SendablePacket.extractServerPacket(packet); + + final Player player = getPlayer(); + if (player == null) return serverPacket; + + if (MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && serverPacket instanceof ComponentHoldingServerPacket) { + serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component -> + GlobalTranslator.render(component, Objects.requireNonNullElseGet(player.getLocale(), MinestomAdventure::getDefaultLocale))); + } + + return serverPacket; + } + @Override public @NotNull SocketAddress getRemoteAddress() { return new InetSocketAddress("localhost", 25565); diff --git a/src/test/java/net/minestom/server/utils/TranslationIntegrationTest.java b/src/test/java/net/minestom/server/utils/TranslationIntegrationTest.java new file mode 100644 index 000000000..333221a9f --- /dev/null +++ b/src/test/java/net/minestom/server/utils/TranslationIntegrationTest.java @@ -0,0 +1,69 @@ +package net.minestom.server.utils; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.translation.GlobalTranslator; +import net.kyori.adventure.translation.TranslationRegistry; +import net.minestom.server.adventure.MinestomAdventure; +import net.minestom.server.api.Env; +import net.minestom.server.api.EnvTest; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.message.ChatPosition; +import net.minestom.server.network.packet.server.play.ChatMessagePacket; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.text.MessageFormat; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@EnvTest +public class TranslationIntegrationTest { + + @BeforeAll + static void translator() { + final var translator = TranslationRegistry.create(Key.key("test.reg")); + translator.register("test.key", MinestomAdventure.getDefaultLocale(), new MessageFormat("This is a test message", MinestomAdventure.getDefaultLocale())); + + GlobalTranslator.translator().addSource(translator); + } + + @Test + public void testTranslationEnabled(final Env env) { + final var instance = env.createFlatInstance(); + final var connection = env.createConnection(); + final var player = connection.connect(instance, new Pos(0, 40, 0)).join(); + final var collector = connection.trackIncoming(ChatMessagePacket.class); + + MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION = true; + final var message = Component.translatable("test.key"); + final var packet = new ChatMessagePacket(message, ChatPosition.CHAT, UUID.randomUUID()); + PacketUtils.sendGroupedPacket(List.of(player), packet); + + // the message should not be changed if translations are enabled. + // the translation of the message itself will be proceeded in PlayerConnectionImpl class + collector.assertSingle(received -> { + assertNotEquals(message, received.message()); + }); + } + + @Test + public void testTranslationDisabled(final Env env) { + final var instance = env.createFlatInstance(); + final var connection = env.createConnection(); + final var player = connection.connect(instance, new Pos(0, 40, 0)).join(); + final var collector = connection.trackIncoming(ChatMessagePacket.class); + + MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION = false; + final var message = Component.translatable("test.key"); + final var packet = new ChatMessagePacket(message, ChatPosition.CHAT, UUID.randomUUID()); + PacketUtils.sendGroupedPacket(List.of(player), packet); + + collector.assertSingle(received -> { + assertEquals(message, received.message()); + }); + } + +}