From f892213e8ce5adf8783fd6babc1e2e06f23d6f79 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Mon, 22 Jun 2020 23:25:00 +0200 Subject: [PATCH 1/6] Added a new Chat API --- build.gradle | 1 - src/main/java/fr/themode/demo/PlayerInit.java | 72 ++++--- .../net/minestom/server/UpdateManager.java | 8 +- .../net/minestom/server/bossbar/BossBar.java | 14 +- .../java/net/minestom/server/chat/Chat.java | 19 -- .../minestom/server/chat/ChatClickEvent.java | 32 ++++ .../net/minestom/server/chat/ChatColor.java | 135 +++++++++++++ .../minestom/server/chat/ChatHoverEvent.java | 59 ++++++ .../net/minestom/server/chat/ColoredText.java | 180 ++++++++++++++++++ .../net/minestom/server/chat/RichMessage.java | 178 +++++++++++++++++ .../net/minestom/server/entity/Entity.java | 12 +- .../net/minestom/server/entity/Player.java | 101 ++++------ .../server/entity/damage/DamageType.java | 15 +- .../server/entity/hologram/Hologram.java | 13 +- .../server/event/player/PlayerChatEvent.java | 8 +- .../net/minestom/server/item/ItemStack.java | 15 +- .../server/listener/ChatMessageListener.java | 28 ++- .../server/listener/KeepAliveListener.java | 8 +- .../server/network/ConnectionManager.java | 18 +- .../server/play/AdvancementsPacket.java | 5 +- .../packet/server/play/BossBarPacket.java | 9 +- .../packet/server/play/ChatMessagePacket.java | 1 - .../packet/server/play/CombatEventPacket.java | 9 +- .../play/ScoreboardObjectivePacket.java | 7 +- .../packet/server/play/TeamsPacket.java | 14 +- .../scoreboard/BelowNameScoreboard.java | 4 +- .../minestom/server/scoreboard/Sidebar.java | 17 +- .../server/scoreboard/SidebarTeam.java | 26 +-- .../net/minestom/server/scoreboard/Team.java | 45 +++-- .../java/net/minestom/server/utils/Utils.java | 11 +- .../server/utils/item/NbtReaderUtils.java | 7 +- 31 files changed, 810 insertions(+), 261 deletions(-) create mode 100644 src/main/java/net/minestom/server/chat/ChatClickEvent.java create mode 100644 src/main/java/net/minestom/server/chat/ChatColor.java create mode 100644 src/main/java/net/minestom/server/chat/ChatHoverEvent.java create mode 100644 src/main/java/net/minestom/server/chat/ColoredText.java create mode 100644 src/main/java/net/minestom/server/chat/RichMessage.java diff --git a/build.gradle b/build.gradle index dbdf8df28..418048951 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,6 @@ dependencies { // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl api group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.13.2' - api 'net.kyori:text-api:3.0.3' api 'net.kyori:text-serializer-legacy:3.0.3' api 'net.kyori:text-serializer-gson:3.0.3' api 'net.kyori:text-serializer-plain:3.0.3' diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index 019e9386a..c0596a0e5 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -1,14 +1,15 @@ package fr.themode.demo; -import fr.themode.demo.entity.ChickenCreature; import fr.themode.demo.generator.ChunkGeneratorDemo; import fr.themode.demo.generator.NoiseTestGenerator; import net.minestom.server.MinecraftServer; import net.minestom.server.attribute.Attribute; import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.benchmark.ThreadResult; +import net.minestom.server.chat.*; import net.minestom.server.entity.*; import net.minestom.server.entity.damage.DamageType; +import net.minestom.server.entity.type.EntityZombie; import net.minestom.server.event.entity.EntityAttackEvent; import net.minestom.server.event.item.ItemDropEvent; import net.minestom.server.event.item.ItemUpdateStateEvent; @@ -39,19 +40,13 @@ import java.util.Map; import java.util.UUID; public class PlayerInit { - - private static String textures = "ewogICJ0aW1lc3RhbXAiIDogMTU5MDg1NTI3NjIwNCwKICAicHJvZmlsZUlkIiA6ICI0NTY2ZTY5ZmM5MDc0OGVlOGQ3MWQ3YmE1YWEwMGQyMCIsCiAgInByb2ZpbGVOYW1lIiA6ICJUaGlua29mZGVhdGgiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzRkMWUwOGIwYmI3ZTlmNTkwYWYyNzc1ODEyNWJiZWQxNzc4YWM2Y2VmNzI5YWVkZmNiOTYxM2U5OTExYWU3NSIKICAgIH0sCiAgICAiQ0FQRSIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjBjYzA4ODQwNzAwNDQ3MzIyZDk1M2EwMmI5NjVmMWQ2NWExM2E2MDNiZjY0YjE3YzgwM2MyMTQ0NmZlMTYzNSIKICAgIH0KICB9Cn0="; - private static String signature = "rCVVwVpLF9ovy+Hm4cOXOLSPOMjNo5WoBfHo9K2OcTUPqcZJ1P/1k4lAnQkChD/Ri11iJ84PejWJzDkHMXM8196Wh+jf12d2GJVhca9/SRLms0cFJjdZZjs72+82AdX0OtO3+qzwKRHzHoEYb+ZVZLfgx37ZKKo4DD3IKmaSnwEjOVJ4BOhnsXLmcNW37kdZUmv2/hlg7ZuWZaayWPhadCYEMnkpVtDIpnpzAeV9EcRfg/ysQoynO2v6WEW0RtnfFEczMN6vXtfiuC8UqyA2SK9aiLnBgpGaehDfFIq/0dpo2uFilVDS/Il6uQ1JSwq7yNT5lNF+i1AlH9SGf1VVy5mT9ShmkVmRxCXX5cSNLXZD0acsNNJb/GAuDHuXpE32GsfgKxWAXMHLw0GnbADbFDfdl5nQyQTDS7FRfUjsFpF8a8Z83muFXaty2WLFy1zxy2JEkI/q+ltLaEG6mQbWI2zhOS7ARvK0OmPz4lDYVInfrwL93AIdMUg2Re817hsapkN6Dm1ND+iirvayR90gqQ9C9J0dMMBlSyTSoKBQeLsi8qETS+7LuhvletPTDFolnTIvP8hj2bWLmQ7LfXJ2arJCUw86YEavVYuF0gYrBuKcEYTC4DA0kO4yLj63gwEgOj9dEigCgyqUcenzmZBffSZ365/QF0cGrG7HC7HmF0w="; - - private static PlayerSkin skin = new PlayerSkin(textures, signature); - private static volatile InstanceContainer instanceContainer; private static volatile InstanceContainer netherTest; private static volatile Inventory inventory; static { - //StorageFolder storageFolder = MinecraftServer.getStorageManager().getFolder("chunk_data"); + //StorageFolder storageFolder = MinecraftServer.getStorageManager().getFolder("instance_data"); ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo(); NoiseTestGenerator noiseTestGenerator = new NoiseTestGenerator(); //instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(storageFolder); @@ -96,23 +91,26 @@ public class PlayerInit { for (Map.Entry resultEntry : benchmarkManager.getResultMap().entrySet()) { String name = resultEntry.getKey(); ThreadResult result = resultEntry.getValue(); - benchmarkMessage += "&7" + name; + benchmarkMessage += ChatColor.GRAY + name; benchmarkMessage += ": "; - benchmarkMessage += "&e" + MathUtils.round(result.getCpuPercentage(), 2) + "% CPU "; - benchmarkMessage += "&c" + MathUtils.round(result.getUserPercentage(), 2) + "% USER "; - benchmarkMessage += "&d" + MathUtils.round(result.getBlockedPercentage(), 2) + "% BLOCKED "; - benchmarkMessage += "&a" + MathUtils.round(result.getWaitedPercentage(), 2) + "% WAITED "; + benchmarkMessage += ChatColor.YELLOW.toString() + MathUtils.round(result.getCpuPercentage(), 2) + "% CPU "; + benchmarkMessage += ChatColor.RED.toString() + MathUtils.round(result.getUserPercentage(), 2) + "% USER "; + benchmarkMessage += ChatColor.PINK.toString() + MathUtils.round(result.getBlockedPercentage(), 2) + "% BLOCKED "; + benchmarkMessage += ChatColor.BRIGHT_GREEN.toString() + MathUtils.round(result.getWaitedPercentage(), 2) + "% WAITED "; benchmarkMessage += "\n"; } for (Player player : connectionManager.getOnlinePlayers()) { - player.sendHeaderFooter("RAM USAGE: " + ramUsage + " MB", benchmarkMessage, '&'); + ColoredText header = ColoredText.of("RAM USAGE: " + ramUsage + " MB"); + ColoredText footer = ColoredText.of(benchmarkMessage); + player.sendHeaderFooter(header, footer); } } }, new UpdateOption(10, TimeUnit.TICK)); connectionManager.addPacketConsumer((player, packetController, packet) -> { // Listen to all received packet + //System.out.println("PACKET: "+packet.getClass().getSimpleName()); packetController.setCancel(false); }); @@ -153,9 +151,13 @@ public class PlayerInit { p.teleport(player.getPosition()); }*/ - ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); + /*ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); chickenCreature.setInstance(player.getInstance()); - chickenCreature.setAttribute(Attribute.MOVEMENT_SPEED, 0.4f); + chickenCreature.setAttribute(Attribute.MOVEMENT_SPEED, 0.4f);*/ + + EntityZombie zombie = new EntityZombie(player.getPosition()); + zombie.setAttribute(Attribute.MOVEMENT_SPEED, 0.25f); + zombie.setInstance(player.getInstance()); /*FakePlayer fakePlayer = new FakePlayer(UUID.randomUUID(), "test"); fakePlayer.addEventCallback(EntityDeathEvent.class, e -> { @@ -198,7 +200,12 @@ public class PlayerInit { }); player.addEventCallback(PlayerLoginEvent.class, event -> { + //final String url = "https://download.mc-packs.net/pack/a83a04f5d78061e0890e13519fea925550461c74.zip"; + //final String hash = "a83a04f5d78061e0890e13519fea925550461c74"; + //player.setResourcePack(new ResourcePack(url, hash)); + event.setSpawningInstance(instanceContainer); + player.setEnableRespawnScreen(false); player.setPermissionLevel(4); @@ -217,10 +224,6 @@ public class PlayerInit { scoreboard.setTitle("test");*/ }); - player.addEventCallback(PlayerSkinInitEvent.class, event -> { - event.setSkin(skin); - }); - player.addEventCallback(PlayerSpawnEvent.class, event -> { player.setGameMode(GameMode.CREATIVE); player.teleport(new Position(0, 41f, 0)); @@ -228,16 +231,18 @@ public class PlayerInit { player.setGlowing(true); ItemStack item = new ItemStack(Material.STONE_SWORD, (byte) 1); - item.setDisplayName("Item name"); - item.getLore().add("a lore line"); + item.setDisplayName(ColoredText.of(ChatColor.BLUE + "Item name")); + item.getLore().add(ColoredText.of(ChatColor.RED + "a lore line")); item.addItemFlags(ItemFlag.HIDE_ENCHANTS); item.setEnchantment(Enchantment.SHARPNESS, (short) 50); player.getInventory().addItemStack(item); - inventory.addItemStack(item.clone()); - player.openInventory(inventory); + //player.setHelmet(new ItemStack(Material.DIAMOND_HELMET, (byte) 1)); - player.getInventory().addItemStack(new ItemStack(Material.STONE, (byte) 100)); + inventory.addItemStack(item.clone()); + //player.openInventory(inventory); + + //player.getInventory().addItemStack(new ItemStack(Material.STONE, (byte) 100)); Instance instance = player.getInstance(); WorldBorder worldBorder = instance.getWorldBorder(); @@ -264,6 +269,23 @@ public class PlayerInit { setBelowNameScoreboard(belowNameScoreboard); belowNameScoreboard.updateScore(this, 50);*/ + ColoredText coloredText1 = ColoredText.of(ChatColor.BLUE, "I am colored") + .append(ChatColor.BLUE, "I am the next") + .appendFormat("I am {#blue}here"); + + ColoredText coloredText2 = + ColoredText.ofFormat( + "I am {#green}colo{#red}red {#white}{&key.jump} keybind, {@attack.fall} translatable"); + + + RichMessage richMessage1 = RichMessage.of(coloredText1) + .setClickEvent(ChatClickEvent.runCommand("/test")) + .setHoverEvent(ChatHoverEvent.showText("I'm a text")) + .append(coloredText2) + .setHoverEvent(ChatHoverEvent.showText("I'm a second text")); + + player.sendMessage(richMessage1); + }); player.addEventCallback(PlayerRespawnEvent.class, event -> { diff --git a/src/main/java/net/minestom/server/UpdateManager.java b/src/main/java/net/minestom/server/UpdateManager.java index ffc7ced98..059102a96 100644 --- a/src/main/java/net/minestom/server/UpdateManager.java +++ b/src/main/java/net/minestom/server/UpdateManager.java @@ -1,7 +1,7 @@ package net.minestom.server; -import net.kyori.text.TextComponent; -import net.kyori.text.format.TextColor; +import net.minestom.server.chat.ChatColor; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.EntityManager; import net.minestom.server.entity.Player; import net.minestom.server.instance.InstanceManager; @@ -48,9 +48,7 @@ public final class UpdateManager { player.refreshKeepAlive(time); player.getPlayerConnection().sendPacket(keepAlivePacket); } else if (lastKeepAlive >= KEEP_ALIVE_KICK) { - TextComponent textComponent = TextComponent.of("Timeout") - .color(TextColor.RED); - player.kick(textComponent); + player.kick(ColoredText.of(ChatColor.RED + "Timeout")); } } diff --git a/src/main/java/net/minestom/server/bossbar/BossBar.java b/src/main/java/net/minestom/server/bossbar/BossBar.java index 1cc022e24..ce36cce7a 100644 --- a/src/main/java/net/minestom/server/bossbar/BossBar.java +++ b/src/main/java/net/minestom/server/bossbar/BossBar.java @@ -1,7 +1,7 @@ package net.minestom.server.bossbar; import net.minestom.server.Viewable; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.play.BossBarPacket; import net.minestom.server.utils.MathUtils; @@ -20,13 +20,13 @@ public class BossBar implements Viewable { private UUID uuid = UUID.randomUUID(); private Set viewers = new CopyOnWriteArraySet<>(); - private String title; + private ColoredText title; private float progress; private BarColor color; private BarDivision division; private byte flags; - public BossBar(String title, BarColor color, BarDivision division) { + public BossBar(ColoredText title, BarColor color, BarDivision division) { this.title = title; this.color = color; this.division = division; @@ -58,7 +58,7 @@ public class BossBar implements Viewable { * * @return the current title of the bossbar */ - public String getTitle() { + public ColoredText getTitle() { return title; } @@ -67,7 +67,7 @@ public class BossBar implements Viewable { * * @param title the new title of the bossbar */ - public void setTitle(String title) { + public void setTitle(ColoredText title) { this.title = title; } @@ -146,7 +146,7 @@ public class BossBar implements Viewable { BossBarPacket bossBarPacket = new BossBarPacket(); bossBarPacket.uuid = uuid; bossBarPacket.action = BossBarPacket.Action.ADD; - bossBarPacket.title = Chat.fromLegacyText(title); + bossBarPacket.title = title; bossBarPacket.health = progress; bossBarPacket.color = color; bossBarPacket.division = division; @@ -165,7 +165,7 @@ public class BossBar implements Viewable { BossBarPacket bossBarPacket = new BossBarPacket(); bossBarPacket.uuid = uuid; bossBarPacket.action = BossBarPacket.Action.UPDATE_TITLE; - bossBarPacket.title = Chat.fromLegacyText(title); + bossBarPacket.title = title; sendPacketToViewers(bossBarPacket); } diff --git a/src/main/java/net/minestom/server/chat/Chat.java b/src/main/java/net/minestom/server/chat/Chat.java index 1fa07d3d1..7258e5d38 100644 --- a/src/main/java/net/minestom/server/chat/Chat.java +++ b/src/main/java/net/minestom/server/chat/Chat.java @@ -1,22 +1,11 @@ package net.minestom.server.chat; import net.kyori.text.Component; -import net.kyori.text.TextComponent; import net.kyori.text.serializer.gson.GsonComponentSerializer; import net.kyori.text.serializer.legacy.LegacyComponentSerializer; -/** - * Thank for the minecraft-text library made by rbrick: - * https://github.com/ctmclub/minecraft-text - */ public class Chat { - public static final char COLOR_CHAR = (char) 0xA7; // Represent the character '§' - - public static String toJsonString(Component component) { - return GsonComponentSerializer.INSTANCE.serialize(component); - } - public static Component fromJsonString(String json) { return GsonComponentSerializer.INSTANCE.deserialize(json); } @@ -24,12 +13,4 @@ public class Chat { public static String toLegacyText(Component component) { return LegacyComponentSerializer.legacyLinking().serialize(component); } - - public static TextComponent fromLegacyText(String text, char colorChar) { - return LegacyComponentSerializer.legacyLinking().deserialize(text, colorChar); - } - - public static TextComponent fromLegacyText(String text) { - return fromLegacyText(text, COLOR_CHAR); - } } diff --git a/src/main/java/net/minestom/server/chat/ChatClickEvent.java b/src/main/java/net/minestom/server/chat/ChatClickEvent.java new file mode 100644 index 000000000..c177d7965 --- /dev/null +++ b/src/main/java/net/minestom/server/chat/ChatClickEvent.java @@ -0,0 +1,32 @@ +package net.minestom.server.chat; + +public class ChatClickEvent { + + private String action; + private String value; + + private ChatClickEvent(String action, String value) { + this.action = action; + this.value = value; + } + + public static ChatClickEvent openUrl(String url) { + return new ChatClickEvent("open_url", url); + } + + public static ChatClickEvent runCommand(String command) { + return new ChatClickEvent("run_command", command); + } + + public static ChatClickEvent suggestCommand(String command) { + return new ChatClickEvent("suggest_command", command); + } + + protected String getAction() { + return action; + } + + protected String getValue() { + return value; + } +} diff --git a/src/main/java/net/minestom/server/chat/ChatColor.java b/src/main/java/net/minestom/server/chat/ChatColor.java new file mode 100644 index 000000000..d912950d7 --- /dev/null +++ b/src/main/java/net/minestom/server/chat/ChatColor.java @@ -0,0 +1,135 @@ +package net.minestom.server.chat; + +import java.util.HashMap; +import java.util.Map; + +public class ChatColor { + + public static final ChatColor NO_COLOR = new ChatColor(); + + public static final ChatColor BLACK = new ChatColor("black", 0); + public static final ChatColor DARK_BLUE = new ChatColor("dark_blue", 1); + public static final ChatColor DARK_GREEN = new ChatColor("dark_green", 2); + public static final ChatColor DARK_CYAN = new ChatColor("dark_cyan", 3); + public static final ChatColor DARK_RED = new ChatColor("dark_red", 4); + public static final ChatColor PURPLE = new ChatColor("dark_purple", 5); + public static final ChatColor GOLD = new ChatColor("gold", 6); + public static final ChatColor GRAY = new ChatColor("gray", 7); + public static final ChatColor DARK_GRAY = new ChatColor("dark_gray", 8); + public static final ChatColor BLUE = new ChatColor("blue", 9); + public static final ChatColor BRIGHT_GREEN = new ChatColor("green", 10); + public static final ChatColor CYAN = new ChatColor("cyan", 11); + public static final ChatColor RED = new ChatColor("red", 12); + public static final ChatColor PINK = new ChatColor("light_purple", 13); + public static final ChatColor YELLOW = new ChatColor("yellow", 14); + public static final ChatColor WHITE = new ChatColor("white", 15); + + /*public static final ChatColor BLACK = fromRGB(0, 0, 0); + public static final ChatColor DARK_BLUE = fromRGB(0, 0, 170); + public static final ChatColor DARK_GREEN = fromRGB(0, 170, 0); + public static final ChatColor DARK_CYAN = fromRGB(0, 170, 170); + public static final ChatColor DARK_RED = fromRGB(170, 0, 0); + public static final ChatColor PURPLE = fromRGB(170, 0, 170); + public static final ChatColor GOLD = fromRGB(255, 170, 0); + public static final ChatColor GRAY = fromRGB(170, 170, 170); + public static final ChatColor DARK_GRAY = fromRGB(85, 85, 85); + public static final ChatColor BLUE = fromRGB(85, 85, 255); + public static final ChatColor BRIGHT_GREEN = fromRGB(85, 255, 85); + public static final ChatColor CYAN = fromRGB(85, 255, 255); + public static final ChatColor RED = fromRGB(255, 85, 85); + public static final ChatColor PINK = fromRGB(255, 85, 255); + public static final ChatColor YELLOW = fromRGB(255, 255, 85); + public static final ChatColor WHITE = fromRGB(255, 255, 255);*/ + private static Map colorCode = new HashMap<>(); + + static { + colorCode.put("black", BLACK); + colorCode.put("dark_blue", DARK_BLUE); + colorCode.put("dark_green", DARK_GREEN); + colorCode.put("dark_cyan", DARK_CYAN); + colorCode.put("dark_red", DARK_RED); + colorCode.put("purple", PURPLE); + colorCode.put("gold", GOLD); + colorCode.put("gray", GRAY); + colorCode.put("dark_gray", DARK_GRAY); + colorCode.put("blue", BLUE); + colorCode.put("bright_green", BRIGHT_GREEN); + colorCode.put("cyan", CYAN); + colorCode.put("red", RED); + colorCode.put("pink", PINK); + colorCode.put("yellow", YELLOW); + colorCode.put("white", WHITE); + } + + private boolean empty; + private int red, green, blue; + private int id; + // 1.15 + private String name; + + // 1.15 + private ChatColor(String name, int id) { + this.name = name; + this.id = id; + } + + // 1.16 + private ChatColor(int r, int g, int b) { + this.empty = false; + this.red = r; + this.green = g; + this.blue = b; + } + + private ChatColor() { + this.empty = true; + } + + public static ChatColor fromRGB(int r, int g, int b) { + return new ChatColor(r, g, b); + } + + public static ChatColor fromName(String name) { + return colorCode.getOrDefault(name.toLowerCase(), NO_COLOR); + } + + public boolean isEmpty() { + return empty; + } + + public int getRed() { + return red; + } + + public int getGreen() { + return green; + } + + public int getBlue() { + return blue; + } + + public int getId() { + return id; + } + + // 1.15 + public String getName() { + return name; + } + + @Override + public String toString() { + // 1.15 + if (name != null) + return "{#" + name + "}"; + // 1.16 + if (empty) + return ""; + + String redH = Integer.toHexString(red); + String greenH = Integer.toHexString(green); + String blueH = Integer.toHexString(blue); + return "{#" + redH + greenH + blueH + "}"; + } +} diff --git a/src/main/java/net/minestom/server/chat/ChatHoverEvent.java b/src/main/java/net/minestom/server/chat/ChatHoverEvent.java new file mode 100644 index 000000000..332850175 --- /dev/null +++ b/src/main/java/net/minestom/server/chat/ChatHoverEvent.java @@ -0,0 +1,59 @@ +package net.minestom.server.chat; + +import com.google.gson.JsonObject; +import net.minestom.server.entity.Entity; +import net.minestom.server.item.ItemStack; + +public class ChatHoverEvent { + + private String action; + private String value; + private JsonObject valueObject; + private boolean isJson; + + private ChatHoverEvent(String action, String value) { + this.action = action; + this.value = value; + } + + private ChatHoverEvent(String action, JsonObject valueObject) { + this.action = action; + this.valueObject = valueObject; + this.isJson = true; + } + + public static ChatHoverEvent showText(ColoredText text) { + return new ChatHoverEvent("show_text", text.getJsonObject()); + } + + + public static ChatHoverEvent showText(String text) { + return new ChatHoverEvent("show_text", text); + } + + public static ChatHoverEvent showItem(ItemStack itemStack) { + throw new UnsupportedOperationException("Feature in progress"); + //return new ChatHoverEvent("show_item", parsedItem); + } + + public static ChatHoverEvent showEntity(Entity entity) { + throw new UnsupportedOperationException("Feature in progress"); + //return new ChatHoverEvent("show_entity", parsedEntity); + } + + protected String getAction() { + return action; + } + + protected String getValue() { + return value; + } + + protected JsonObject getValueObject() { + return valueObject; + } + + protected boolean isJson() { + return isJson; + } +} diff --git a/src/main/java/net/minestom/server/chat/ColoredText.java b/src/main/java/net/minestom/server/chat/ColoredText.java new file mode 100644 index 000000000..1a0077120 --- /dev/null +++ b/src/main/java/net/minestom/server/chat/ColoredText.java @@ -0,0 +1,180 @@ +package net.minestom.server.chat; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +public class ColoredText { + + private String message; + + private ColoredText(String message) { + this.message = message; + } + + public static ColoredText of(ChatColor color, String message) { + return new ColoredText(color + message); + } + + public static ColoredText of(String message) { + return of(ChatColor.WHITE, message); + } + + public static ColoredText ofFormat(String message) { + return new ColoredText(message); + } + + public ColoredText append(ChatColor color, String message) { + this.message += color + message; + return this; + } + + public ColoredText append(String message) { + return append(ChatColor.NO_COLOR, message); + } + + public ColoredText appendFormat(String message) { + this.message += message; + return this; + } + + public String getMessage() { + return message; + } + + @Override + public String toString() { + return getJsonObject().toString(); + } + + protected JsonObject getJsonObject() { + List components = getComponents(); + + // No message, return empty object + if (components.isEmpty()) { + return new JsonObject(); + } + + // Get the first element and remove it + JsonObject mainObject = components.remove(0); + + // Append all the components + if (!components.isEmpty()) { + JsonArray extraArray = new JsonArray(); + for (JsonObject component : components) { + extraArray.add(component); + } + mainObject.add("extra", extraArray); + } + + + return mainObject; + } + + /** + * Get the list of objects composing the message + * + * @return the list of objects composing the message + */ + protected List getComponents() { + final List objects = new ArrayList<>(); + // No message, return empty list + if (message.isEmpty()) + return objects; + + boolean inFormat = false; + int formatStart = 0; + int formatEnd = 0; + + String currentColor = ""; + + for (int i = 0; i < message.length(); i++) { + // Last char or null + Character p = i == 0 ? null : message.charAt(i - 1); + // Current char + char c = message.charAt(i); + if ((p == null || (p != '/')) && c == '{' && !inFormat) { + + formatEnd = formatEnd > 0 ? formatEnd + 1 : formatEnd; + String rawMessage = message.substring(formatEnd, i); + if (!rawMessage.isEmpty()) { + objects.add(getMessagePart(MessageType.RAW, rawMessage, currentColor)); + } + + inFormat = true; + formatStart = i; + continue; + } else if ((p == null || (p != '/')) && c == '}' && inFormat) { + // Represent the custom format between the brackets + String formatString = message.substring(formatStart + 1, i); + if (formatString.isEmpty()) + continue; + + inFormat = false; + formatStart = 0; + formatEnd = i; + + // Color component + if (formatString.startsWith("#")) { + // Remove the first # character to get code + String colorCode = formatString.substring(1); + ChatColor color = ChatColor.fromName(colorCode); + if (color == ChatColor.NO_COLOR) { + // Use rgb formatting + currentColor = colorCode; + } else { + // Use color name formatiing + currentColor = color.getName(); + } + continue; + } + // Translatable component + if (formatString.startsWith("@")) { + String translatableCode = formatString.substring(1); + objects.add(getMessagePart(MessageType.TRANSLATABLE, translatableCode, currentColor)); + continue; + } + // Keybind component + if (formatString.startsWith("&")) { + String keybindCode = formatString.substring(1); + objects.add(getMessagePart(MessageType.KEYBIND, keybindCode, currentColor)); + continue; + } + } + } + + // Add the remaining of the message as a raw message when any + if (formatEnd < message.length()) { + String lastRawMessage = message.substring(formatEnd + 1); + objects.add(getMessagePart(MessageType.RAW, lastRawMessage, currentColor)); + } + + return objects; + } + + private JsonObject getMessagePart(MessageType messageType, String message, String color) { + JsonObject object = new JsonObject(); + switch (messageType) { + case RAW: + object.addProperty("text", message); + break; + case KEYBIND: + object.addProperty("keybind", message); + break; + case TRANSLATABLE: + object.addProperty("translate", message); + break; + } + if (!color.isEmpty()) { + object.addProperty("color", color); + } + return object; + } + + private enum MessageType { + RAW, KEYBIND, TRANSLATABLE + } + +} diff --git a/src/main/java/net/minestom/server/chat/RichMessage.java b/src/main/java/net/minestom/server/chat/RichMessage.java new file mode 100644 index 000000000..21ef920b6 --- /dev/null +++ b/src/main/java/net/minestom/server/chat/RichMessage.java @@ -0,0 +1,178 @@ +package net.minestom.server.chat; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.minestom.server.utils.validate.Check; + +import java.util.ArrayList; +import java.util.List; + +public class RichMessage { + + private List components = new ArrayList<>(); + private RichComponent currentComponent; + + public static RichMessage of(ColoredText coloredText, FormatRetention formatRetention) { + Check.notNull(coloredText, "ColoredText cannot be null"); + + RichMessage richMessage = new RichMessage(); + appendText(richMessage, coloredText, formatRetention); + + return richMessage; + } + + public static RichMessage of(ColoredText coloredText) { + return of(coloredText, FormatRetention.ALL); + } + + private static void appendText(RichMessage richMessage, ColoredText coloredText, FormatRetention formatRetention) { + RichComponent component = new RichComponent(coloredText, formatRetention); + richMessage.components.add(component); + richMessage.currentComponent = component; + } + + public RichMessage setClickEvent(ChatClickEvent clickEvent) { + Check.notNull(clickEvent, "ChatClickEvent cannot be null"); + + currentComponent.setClickEvent(clickEvent); + return this; + } + + public RichMessage setHoverEvent(ChatHoverEvent hoverEvent) { + Check.notNull(hoverEvent, "ChatHoverEvent cannot be null"); + + currentComponent.setHoverEvent(hoverEvent); + return this; + } + + public RichMessage append(ColoredText coloredText, FormatRetention formatRetention) { + Check.notNull(coloredText, "ColoredText cannot be null"); + + appendText(this, coloredText, formatRetention); + return this; + } + + public RichMessage append(ColoredText coloredText) { + return append(coloredText, FormatRetention.ALL); + } + + @Override + public String toString() { + return getJsonObject().toString(); + } + + private JsonObject getJsonObject() { + // No component, return empty json object + if (components.isEmpty()) + return new JsonObject(); + + RichComponent firstComponent = components.remove(0); + List firstComponentObjects = getComponentObject(firstComponent); + JsonObject mainObject = firstComponentObjects.remove(0); + + if (components.isEmpty() && firstComponentObjects.isEmpty()) + return mainObject; + + JsonArray extraArray = new JsonArray(); + for (JsonObject firstComponentObject : firstComponentObjects) { + extraArray.add(firstComponentObject); + } + + for (RichComponent component : components) { + List componentObjects = getComponentObject(component); + for (JsonObject componentObject : componentObjects) { + extraArray.add(componentObject); + } + } + + mainObject.add("extra", extraArray); + + + return mainObject; + } + + private List getComponentObject(RichComponent component) { + ColoredText coloredText = component.getText(); + List componentObjects = coloredText.getComponents(); + + ChatClickEvent clickEvent = component.getClickEvent(); + ChatHoverEvent hoverEvent = component.getHoverEvent(); + + // Nothing to process + if (clickEvent == null && hoverEvent == null) { + return componentObjects; + } + + for (JsonObject componentObject : componentObjects) { + if (clickEvent != null) { + final JsonObject clickObject = + getEventObject(clickEvent.getAction(), clickEvent.getValue()); + componentObject.add("clickEvent", clickObject); + } + if (hoverEvent != null) { + final JsonObject hoverObject; + if (hoverEvent.isJson()) { + // The value is a JsonObject + hoverObject = new JsonObject(); + hoverObject.addProperty("action", hoverEvent.getAction()); + hoverObject.add("value", hoverEvent.getValueObject()); + } else { + // The value is a raw string + hoverObject = getEventObject(hoverEvent.getAction(), hoverEvent.getValue()); + } + componentObject.add("hoverEvent", hoverObject); + } + } + + return componentObjects; + } + + private JsonObject getEventObject(String action, String value) { + JsonObject eventObject = new JsonObject(); + eventObject.addProperty("action", action); + eventObject.addProperty("value", value); + return eventObject; + } + + public enum FormatRetention { + ALL, CLICK_EVENT, HOVER_EVENT, NONE + } + + private static class RichComponent { + + private ColoredText text; + private FormatRetention formatRetention; + private ChatClickEvent clickEvent; + private ChatHoverEvent hoverEvent; + + private RichComponent(ColoredText text, FormatRetention formatRetention) { + this.text = text; + this.formatRetention = formatRetention; + } + + public ColoredText getText() { + return text; + } + + public FormatRetention getFormatRetention() { + return formatRetention; + } + + public ChatClickEvent getClickEvent() { + return clickEvent; + } + + public void setClickEvent(ChatClickEvent clickEvent) { + this.clickEvent = clickEvent; + } + + public ChatHoverEvent getHoverEvent() { + return hoverEvent; + } + + public void setHoverEvent(ChatHoverEvent hoverEvent) { + this.hoverEvent = hoverEvent; + } + } + +} diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 7a778aec0..4bc93a2d4 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -2,7 +2,7 @@ package net.minestom.server.entity; import net.minestom.server.MinecraftServer; import net.minestom.server.Viewable; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.collision.BoundingBox; import net.minestom.server.collision.CollisionUtils; import net.minestom.server.data.Data; @@ -95,7 +95,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { protected boolean glowing; protected boolean usingElytra; protected int air = 300; - protected String customName; + protected ColoredText customName; protected boolean customNameVisible; protected boolean silent; protected boolean noGravity; @@ -824,7 +824,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { * * @return the custom name of the entity, null if there is not */ - public String getCustomName() { + public ColoredText getCustomName() { return customName; } @@ -833,7 +833,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { * * @param customName the custom name of the entity, null to remove it */ - public void setCustomName(String customName) { + public void setCustomName(ColoredText customName) { this.customName = customName; sendMetadataIndex(2); } @@ -1227,13 +1227,13 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } private void fillCustomNameMetaData(PacketWriter packet) { - boolean hasCustomName = customName != null && !customName.isEmpty(); + boolean hasCustomName = customName != null; packet.writeByte((byte) 2); packet.writeByte(METADATA_OPTCHAT); packet.writeBoolean(hasCustomName); if (hasCustomName) { - packet.writeSizedString(Chat.toJsonString(Chat.fromLegacyText(customName))); + packet.writeSizedString(customName.toString()); } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 347cd5ee1..1a4df5e4c 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1,11 +1,10 @@ package net.minestom.server.entity; -import net.kyori.text.Component; -import net.kyori.text.TextComponent; import net.minestom.server.MinecraftServer; import net.minestom.server.attribute.Attribute; import net.minestom.server.bossbar.BossBar; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; +import net.minestom.server.chat.RichMessage; import net.minestom.server.collision.BoundingBox; import net.minestom.server.command.CommandManager; import net.minestom.server.command.CommandSender; @@ -63,7 +62,7 @@ public class Player extends LivingEntity implements CommandSender { private ConcurrentLinkedQueue packets = new ConcurrentLinkedQueue<>(); private int latency; - private String displayName; + private ColoredText displayName; private PlayerSkin skin; private Dimension dimension; @@ -163,7 +162,7 @@ public class Player extends LivingEntity implements CommandSender { } /** - * Used when the player is created ({@link EntityManager#waitingPlayersTick()}) + * Used when the player is created (EntityManager#waitingPlayersTick()) * Init the player and spawn him */ protected void init() { @@ -402,21 +401,23 @@ public class Player extends LivingEntity implements CommandSender { public void kill() { if (!isDead()) { // send death message to player - Component deathMessage; + ColoredText deathMessage; if (lastDamageSource != null) { deathMessage = lastDamageSource.buildDeathScreenMessage(this); } else { // may happen if killed by the server without applying damage - deathMessage = TextComponent.of("Killed by poor programming."); + deathMessage = ColoredText.of("Killed by poor programming."); } CombatEventPacket deathPacket = CombatEventPacket.death(this, Optional.empty(), deathMessage); playerConnection.sendPacket(deathPacket); // send death message to chat - Component chatMessage; + RichMessage chatMessage; if (lastDamageSource != null) { chatMessage = lastDamageSource.buildChatMessage(this); } else { // may happen if killed by the server without applying damage - chatMessage = TextComponent.of(getUsername() + " was killed by poor programming."); + ColoredText coloredChatMessage = + ColoredText.of(getUsername() + " was killed by poor programming."); + chatMessage = RichMessage.of(coloredChatMessage); } MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player -> { player.sendMessage(chatMessage); @@ -593,27 +594,25 @@ public class Player extends LivingEntity implements CommandSender { // Use legacy color formatting @Override public void sendMessage(String message) { - sendMessage(Chat.fromLegacyText(message)); + sendMessage(ColoredText.of(message)); } /** * Send a message to the player * - * @param message the message to send - * @param colorChar the character used to represent the color + * @param coloredText the text to send */ - public void sendMessage(String message, char colorChar) { - sendMessage(Chat.fromLegacyText(message, colorChar)); + public void sendMessage(ColoredText coloredText) { + playerConnection.sendPacket(new ChatMessagePacket(coloredText.toString(), ChatMessagePacket.Position.CHAT)); } /** * Send a message to the player * - * @param component the text component + * @param richMessage the rich text to send */ - public void sendMessage(Component component) { - String json = Chat.toJsonString(component); - playerConnection.sendPacket(new ChatMessagePacket(json, ChatMessagePacket.Position.CHAT)); + public void sendMessage(RichMessage richMessage) { + playerConnection.sendPacket(new ChatMessagePacket(richMessage.toString(), ChatMessagePacket.Position.CHAT)); } public void playSound(Sound sound, SoundCategory soundCategory, int x, int y, int z, float volume, float pitch) { @@ -656,33 +655,29 @@ public class Player extends LivingEntity implements CommandSender { playerConnection.sendPacket(stopSoundPacket); } - public void sendHeaderFooter(Component header, Component footer) { + public void sendHeaderFooter(ColoredText header, ColoredText footer) { PlayerListHeaderAndFooterPacket playerListHeaderAndFooterPacket = new PlayerListHeaderAndFooterPacket(); playerListHeaderAndFooterPacket.emptyHeader = header == null; playerListHeaderAndFooterPacket.emptyFooter = footer == null; - playerListHeaderAndFooterPacket.header = Chat.toJsonString(header); - playerListHeaderAndFooterPacket.footer = Chat.toJsonString(footer); + playerListHeaderAndFooterPacket.header = header.toString(); + playerListHeaderAndFooterPacket.footer = footer.toString(); playerConnection.sendPacket(playerListHeaderAndFooterPacket); } - public void sendHeaderFooter(String header, String footer, char colorChar) { - sendHeaderFooter(Chat.fromLegacyText(header, colorChar), Chat.fromLegacyText(footer, colorChar)); - } - - private void sendTitle(Component title, TitlePacket.Action action) { + private void sendTitle(ColoredText text, TitlePacket.Action action) { TitlePacket titlePacket = new TitlePacket(); titlePacket.action = action; switch (action) { case SET_TITLE: - titlePacket.titleText = Chat.toJsonString(title); + titlePacket.titleText = text.toString(); break; case SET_SUBTITLE: - titlePacket.subtitleText = Chat.toJsonString(title); + titlePacket.subtitleText = text.toString(); break; case SET_ACTION_BAR: - titlePacket.actionBarText = Chat.toJsonString(title); + titlePacket.actionBarText = text.toString(); default: throw new UnsupportedOperationException("Invalid TitlePacket.Action type!"); } @@ -690,47 +685,23 @@ public class Player extends LivingEntity implements CommandSender { playerConnection.sendPacket(titlePacket); } - public void sendTitleSubtitleMessage(Component title, Component subtitle) { + public void sendTitleSubtitleMessage(ColoredText title, ColoredText subtitle) { sendTitle(title, TitlePacket.Action.SET_TITLE); sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE); } - public void sendTitleMessage(Component title) { + public void sendTitleMessage(ColoredText title) { sendTitle(title, TitlePacket.Action.SET_TITLE); } - public void sendTitleMessage(String title, char colorChar) { - sendTitleMessage(Chat.fromLegacyText(title, colorChar)); - } - - public void sendTitleMessage(String title) { - sendTitleMessage(title, Chat.COLOR_CHAR); - } - - public void sendSubtitleMessage(Component subtitle) { + public void sendSubtitleMessage(ColoredText subtitle) { sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE); } - public void sendSubtitleMessage(String subtitle, char colorChar) { - sendSubtitleMessage(Chat.fromLegacyText(subtitle, colorChar)); - } - - public void sendSubtitleMessage(String subtitle) { - sendSubtitleMessage(subtitle, Chat.COLOR_CHAR); - } - - public void sendActionBarMessage(Component actionBar) { + public void sendActionBarMessage(ColoredText actionBar) { sendTitle(actionBar, TitlePacket.Action.SET_ACTION_BAR); } - public void sendActionBarMessage(String message, char colorChar) { - sendActionBarMessage(Chat.fromLegacyText(message, colorChar)); - } - - public void sendActionBarMessage(String message) { - sendActionBarMessage(message, Chat.COLOR_CHAR); - } - @Override public boolean isImmune(DamageType type) { if (!getGameMode().canTakeDamage()) { @@ -839,7 +810,7 @@ public class Player extends LivingEntity implements CommandSender { * @return the player display name, * null means that {@link #getUsername()} is displayed */ - public String getDisplayName() { + public ColoredText getDisplayName() { return displayName; } @@ -850,10 +821,10 @@ public class Player extends LivingEntity implements CommandSender { * * @param displayName the display name */ - public void setDisplayName(String displayName) { + public void setDisplayName(ColoredText displayName) { this.displayName = displayName; - String jsonDisplayName = displayName != null ? Chat.toJsonString(Chat.fromLegacyText(displayName)) : null; + String jsonDisplayName = displayName != null ? displayName.toString() : null; PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME); infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateDisplayName(getUuid(), jsonDisplayName)); sendPacketToViewersAndSelf(infoPacket); @@ -1286,11 +1257,11 @@ public class Player extends LivingEntity implements CommandSender { /** * Kick the player with a reason * - * @param textComponent the kick reason + * @param text the kick reason */ - public void kick(TextComponent textComponent) { + public void kick(ColoredText text) { DisconnectPacket disconnectPacket = new DisconnectPacket(); - disconnectPacket.message = Chat.toJsonString(textComponent); + disconnectPacket.message = text.toString(); playerConnection.sendPacket(disconnectPacket); playerConnection.disconnect(); playerConnection.refreshOnline(false); @@ -1302,7 +1273,7 @@ public class Player extends LivingEntity implements CommandSender { * @param message the kick reason */ public void kick(String message) { - kick(Chat.fromLegacyText(message)); + kick(ColoredText.of(message)); } public LevelType getLevelType() { @@ -1883,7 +1854,7 @@ public class Player extends LivingEntity implements CommandSender { final String textures = skin == null ? "" : skin.getTextures(); final String signature = skin == null ? null : skin.getSignature(); - String jsonDisplayName = displayName != null ? Chat.toJsonString(Chat.fromLegacyText(displayName)) : null; + String jsonDisplayName = displayName != null ? displayName.toString() : null; PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER); PlayerInfoPacket.AddPlayer addPlayer = diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java index 74564f77b..ce6a21968 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageType.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -1,8 +1,7 @@ package net.minestom.server.entity.damage; -import net.kyori.text.Component; -import net.kyori.text.TextComponent; -import net.kyori.text.TranslatableComponent; +import net.minestom.server.chat.ColoredText; +import net.minestom.server.chat.RichMessage; import net.minestom.server.entity.Entity; import net.minestom.server.entity.LivingEntity; import net.minestom.server.entity.Player; @@ -35,8 +34,10 @@ public class DamageType { return new EntityProjectileDamage(shooter, projectile); } - public Component buildChatMessage(Player killed) { - return TranslatableComponent.of("death." + identifier, TextComponent.of(killed.getUsername())); + public RichMessage buildChatMessage(Player killed) { + RichMessage richMessage = RichMessage.of(ColoredText.ofFormat("{@death." + identifier + "}")) + .append(ColoredText.ofFormat(killed.getUsername())); + return richMessage; } public static EntityDamage fromPlayer(Player player) { @@ -47,8 +48,8 @@ public class DamageType { return new EntityDamage(entity); } - public Component buildDeathScreenMessage(Player killed) { - return buildChatMessage(killed); + public ColoredText buildDeathScreenMessage(Player killed) { + return ColoredText.ofFormat("{@death." + identifier + "}"); } /** diff --git a/src/main/java/net/minestom/server/entity/hologram/Hologram.java b/src/main/java/net/minestom/server/entity/hologram/Hologram.java index b7e0fc8ad..fa20b686d 100644 --- a/src/main/java/net/minestom/server/entity/hologram/Hologram.java +++ b/src/main/java/net/minestom/server/entity/hologram/Hologram.java @@ -1,5 +1,6 @@ package net.minestom.server.entity.hologram; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.type.EntityArmorStand; import net.minestom.server.instance.Instance; import net.minestom.server.utils.Position; @@ -12,11 +13,11 @@ public class Hologram { private HologramEntity entity; private Position position; - private String text; + private ColoredText text; private boolean removed; - public Hologram(Instance instance, Position spawnPosition, String text, boolean autoViewable) { + public Hologram(Instance instance, Position spawnPosition, ColoredText text, boolean autoViewable) { this.entity = new HologramEntity(spawnPosition.clone().add(0, OFFSET_Y, 0)); this.entity.setInstance(instance); this.entity.setAutoViewable(autoViewable); @@ -25,7 +26,7 @@ public class Hologram { setText(text); } - public Hologram(Instance instance, Position spawnPosition, String text) { + public Hologram(Instance instance, Position spawnPosition, ColoredText text) { this(instance, spawnPosition, text, true); } @@ -40,11 +41,11 @@ public class Hologram { this.entity.teleport(position); } - public String getText() { + public ColoredText getText() { return text; } - public void setText(String text) { + public void setText(ColoredText text) { checkRemoved(); this.text = text; this.entity.setCustomName(text); @@ -70,7 +71,7 @@ public class Hologram { setSmall(true); setNoGravity(true); - setCustomName(""); + setCustomName(ColoredText.of("")); setCustomNameVisible(true); setInvisible(true); } diff --git a/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java b/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java index c8182b194..35c69c903 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java @@ -1,6 +1,6 @@ package net.minestom.server.event.player; -import net.kyori.text.TextComponent; +import net.minestom.server.chat.RichMessage; import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; @@ -17,7 +17,7 @@ public class PlayerChatEvent extends CancellableEvent { private Player sender; private Collection recipients; private String message; - private Function chatFormat; + private Function chatFormat; public PlayerChatEvent(Player sender, Collection recipients, String message) { this.sender = sender; @@ -28,7 +28,7 @@ public class PlayerChatEvent extends CancellableEvent { /** * @param chatFormat the custom chat format */ - public void setChatFormat(Function chatFormat) { + public void setChatFormat(Function chatFormat) { this.chatFormat = chatFormat; } @@ -71,7 +71,7 @@ public class PlayerChatEvent extends CancellableEvent { * * @return the chat format which will be used */ - public Function getChatFormatFunction() { + public Function getChatFormatFunction() { return chatFormat; } } diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index a36149536..cbe357c61 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -1,5 +1,6 @@ package net.minestom.server.item; +import net.minestom.server.chat.ColoredText; import net.minestom.server.data.Data; import net.minestom.server.data.DataContainer; import net.minestom.server.item.attribute.ItemAttribute; @@ -24,9 +25,9 @@ public class ItemStack implements DataContainer { private byte amount; private short damage; - private String displayName; + private ColoredText displayName; private boolean unbreakable; - private ArrayList lore; + private ArrayList lore; private Map enchantmentMap; private Map storedEnchantmentMap; @@ -103,7 +104,7 @@ public class ItemStack implements DataContainer { */ public synchronized boolean isSimilar(ItemStack itemStack) { synchronized (itemStack) { - final String itemDisplayName = itemStack.getDisplayName(); + final ColoredText itemDisplayName = itemStack.getDisplayName(); final boolean displayNameCheck = (displayName == null && itemDisplayName == null) || (displayName != null && itemDisplayName != null && displayName.equals(itemDisplayName)); @@ -179,7 +180,7 @@ public class ItemStack implements DataContainer { * * @return the item display name, can be null if not present */ - public String getDisplayName() { + public ColoredText getDisplayName() { return displayName; } @@ -188,7 +189,7 @@ public class ItemStack implements DataContainer { * * @param displayName the item display name */ - public void setDisplayName(String displayName) { + public void setDisplayName(ColoredText displayName) { this.displayName = displayName; } @@ -206,7 +207,7 @@ public class ItemStack implements DataContainer { * * @return the item lore, can be null if not present */ - public ArrayList getLore() { + public ArrayList getLore() { return lore; } @@ -215,7 +216,7 @@ public class ItemStack implements DataContainer { * * @param lore the item lore, can be null to remove */ - public void setLore(ArrayList lore) { + public void setLore(ArrayList lore) { this.lore = lore; } diff --git a/src/main/java/net/minestom/server/listener/ChatMessageListener.java b/src/main/java/net/minestom/server/listener/ChatMessageListener.java index ab43e4f4b..bc854ec78 100644 --- a/src/main/java/net/minestom/server/listener/ChatMessageListener.java +++ b/src/main/java/net/minestom/server/listener/ChatMessageListener.java @@ -1,12 +1,7 @@ package net.minestom.server.listener; -import net.kyori.text.TextComponent; -import net.kyori.text.event.ClickEvent; -import net.kyori.text.event.HoverEvent; -import net.kyori.text.format.TextColor; -import net.kyori.text.serializer.plain.PlainComponentSerializer; import net.minestom.server.MinecraftServer; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.*; import net.minestom.server.command.CommandManager; import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerChatEvent; @@ -19,7 +14,7 @@ import java.util.function.Function; public class ChatMessageListener { public static void listener(ClientChatMessagePacket packet, Player player) { - String message = PlainComponentSerializer.INSTANCE.serialize(Chat.fromLegacyText(packet.message)); + String message = packet.message; CommandManager commandManager = MinecraftServer.getCommandManager(); String cmdPrefix = commandManager.getCommandPrefix(); @@ -43,9 +38,9 @@ public class ChatMessageListener { // Call the event player.callCancellableEvent(PlayerChatEvent.class, playerChatEvent, () -> { - Function formatFunction = playerChatEvent.getChatFormatFunction(); + Function formatFunction = playerChatEvent.getChatFormatFunction(); - TextComponent textObject; + RichMessage textObject; if (formatFunction != null) { // Custom format @@ -63,16 +58,17 @@ public class ChatMessageListener { } - private static TextComponent buildDefaultChatMessage(PlayerChatEvent chatEvent) { + private static RichMessage buildDefaultChatMessage(PlayerChatEvent chatEvent) { String username = chatEvent.getSender().getUsername(); - TextComponent usernameText = TextComponent.of(String.format("<%s>", username)) - .color(TextColor.WHITE) - .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Its " + username).color(TextColor.GRAY))) - .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, "/msg " + username + " ")) - .append(TextComponent.of(" " + chatEvent.getMessage())); + ColoredText usernameText = ColoredText.of(String.format("<%s>", username)); - return usernameText; + RichMessage richMessage = RichMessage.of(usernameText) + .setHoverEvent(ChatHoverEvent.showText(ColoredText.of(ChatColor.GRAY + "Its " + username))) + .setClickEvent(ChatClickEvent.suggestCommand("/msg " + username + " ")) + .append(ColoredText.of(" " + chatEvent.getMessage())); + + return richMessage; } } diff --git a/src/main/java/net/minestom/server/listener/KeepAliveListener.java b/src/main/java/net/minestom/server/listener/KeepAliveListener.java index 109e5b79f..8c543c373 100644 --- a/src/main/java/net/minestom/server/listener/KeepAliveListener.java +++ b/src/main/java/net/minestom/server/listener/KeepAliveListener.java @@ -1,7 +1,7 @@ package net.minestom.server.listener; -import net.kyori.text.TextComponent; -import net.kyori.text.format.TextColor; +import net.minestom.server.chat.ChatColor; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.client.play.ClientKeepAlivePacket; @@ -12,9 +12,7 @@ public class KeepAliveListener { final long playerId = player.getLastKeepAlive(); final boolean equals = packetId == playerId; if (!equals) { - TextComponent textComponent = TextComponent.of("Bad Keep Alive packet") - .color(TextColor.RED); - player.kick(textComponent); + player.kick(ColoredText.of(ChatColor.RED + "Bad Keep Alive packet")); return; } diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 390d842e6..5556b2659 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -1,6 +1,6 @@ package net.minestom.server.network; -import net.kyori.text.TextComponent; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.listener.manager.PacketConsumer; import net.minestom.server.network.player.PlayerConnection; @@ -50,17 +50,17 @@ public final class ConnectionManager { /** * Send a message to all online players who validate the condition {@code condition} * - * @param textComponent the message to send - * @param condition the condition to receive the message + * @param coloredText the message to send + * @param condition the condition to receive the message */ - public void broadcastMessage(TextComponent textComponent, Function condition) { + public void broadcastMessage(ColoredText coloredText, Function condition) { if (condition == null) { - getOnlinePlayers().forEach(player -> player.sendMessage(textComponent)); + getOnlinePlayers().forEach(player -> player.sendMessage(coloredText)); } else { getOnlinePlayers().forEach(player -> { boolean result = condition.apply(player); if (result) - player.sendMessage(textComponent); + player.sendMessage(coloredText); }); } } @@ -68,10 +68,10 @@ public final class ConnectionManager { /** * Send a message to all online players without exception * - * @param textComponent the message to send + * @param coloredText the message to send */ - public void broadcastMessage(TextComponent textComponent) { - broadcastMessage(textComponent, null); + public void broadcastMessage(ColoredText coloredText) { + broadcastMessage(coloredText, null); } /** 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 6ed240577..0eee0aa48 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 @@ -1,6 +1,5 @@ package net.minestom.server.network.packet.server.play; -import net.minestom.server.chat.Chat; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.ServerPacket; @@ -92,8 +91,8 @@ public class AdvancementsPacket implements ServerPacket { public float y; private void write(PacketWriter writer) { - writer.writeSizedString(Chat.toJsonString(Chat.fromLegacyText(title))); - writer.writeSizedString(Chat.toJsonString(Chat.fromLegacyText(title))); + writer.writeSizedString(title); + writer.writeSizedString(description); writer.writeItemStack(icon); writer.writeVarInt(frameType.ordinal()); writer.writeInt(flags); 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 b11d0a0f4..0639fdf9d 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 @@ -1,9 +1,8 @@ package net.minestom.server.network.packet.server.play; -import net.kyori.text.Component; import net.minestom.server.bossbar.BarColor; import net.minestom.server.bossbar.BarDivision; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -15,7 +14,7 @@ public class BossBarPacket implements ServerPacket { public UUID uuid; public Action action; - public Component title; + public ColoredText title; public float health; public BarColor color; public BarDivision division; @@ -29,7 +28,7 @@ public class BossBarPacket implements ServerPacket { switch (action) { case ADD: - writer.writeSizedString(Chat.toJsonString(title)); + writer.writeSizedString(title.toString()); writer.writeFloat(health); writer.writeVarInt(color.ordinal()); writer.writeVarInt(division.ordinal()); @@ -42,7 +41,7 @@ public class BossBarPacket implements ServerPacket { writer.writeFloat(health); break; case UPDATE_TITLE: - writer.writeSizedString(Chat.toJsonString(title)); + writer.writeSizedString(title.toString()); break; case UPDATE_STYLE: writer.writeVarInt(color.ordinal()); 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 1e2b9d59c..1d431d5d8 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 @@ -26,7 +26,6 @@ public class ChatMessagePacket implements ServerPacket { } public enum Position { - CHAT, SYSTEM_MESSAGE, GAME_INFO diff --git a/src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java index 5fef1a46d..5765d601e 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java @@ -1,7 +1,6 @@ package net.minestom.server.network.packet.server.play; -import net.kyori.text.Component; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.PacketWriter; @@ -20,7 +19,7 @@ public class CombatEventPacket implements ServerPacket { private int duration; private int opponent; private int playerID; - private Component deathMessage; + private ColoredText deathMessage; private CombatEventPacket() { } @@ -39,7 +38,7 @@ public class CombatEventPacket implements ServerPacket { return packet; } - public static CombatEventPacket death(Player player, Optional killer, Component message) { + public static CombatEventPacket death(Player player, Optional killer, ColoredText message) { CombatEventPacket packet = new CombatEventPacket(); packet.type = EventType.DEATH; packet.playerID = player.getEntityId(); @@ -64,7 +63,7 @@ public class CombatEventPacket implements ServerPacket { case DEATH: writer.writeVarInt(playerID); writer.writeInt(opponent); - writer.writeSizedString(Chat.toJsonString(deathMessage)); + writer.writeSizedString(deathMessage.toString()); break; } } 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 f3563749d..45ad1e17c 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 @@ -1,7 +1,6 @@ package net.minestom.server.network.packet.server.play; -import net.kyori.text.Component; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -10,7 +9,7 @@ public class ScoreboardObjectivePacket implements ServerPacket { public String objectiveName; public byte mode; - public Component objectiveValue; + public ColoredText objectiveValue; public int type; @Override @@ -19,7 +18,7 @@ public class ScoreboardObjectivePacket implements ServerPacket { writer.writeByte(mode); if (mode == 0 || mode == 2) { - writer.writeSizedString(Chat.toJsonString(objectiveValue)); + writer.writeSizedString(objectiveValue.toString()); writer.writeVarInt(type); } } 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 776948ce5..2c1d08a00 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 @@ -1,7 +1,5 @@ package net.minestom.server.network.packet.server.play; -import net.kyori.text.Component; -import net.minestom.server.chat.Chat; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -11,13 +9,13 @@ public class TeamsPacket implements ServerPacket { public String teamName; public Action action; - public Component teamDisplayName; + public String teamDisplayName; public byte friendlyFlags; public NameTagVisibility nameTagVisibility; public CollisionRule collisionRule; public int teamColor; - public Component teamPrefix; - public Component teamSuffix; + public String teamPrefix; + public String teamSuffix; public String[] entities; @Override @@ -28,13 +26,13 @@ public class TeamsPacket implements ServerPacket { switch (action) { case CREATE_TEAM: case UPDATE_TEAM_INFO: - writer.writeSizedString(Chat.toJsonString(teamDisplayName)); + writer.writeSizedString(teamDisplayName); writer.writeByte(friendlyFlags); writer.writeSizedString(nameTagVisibility.getIdentifier()); writer.writeSizedString(collisionRule.getIdentifier()); writer.writeVarInt(teamColor); - writer.writeSizedString(Chat.toJsonString(teamPrefix)); - writer.writeSizedString(Chat.toJsonString(teamSuffix)); + writer.writeSizedString(teamPrefix); + writer.writeSizedString(teamSuffix); break; case REMOVE_TEAM: diff --git a/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java b/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java index 8a19d1764..1894ba64b 100644 --- a/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java +++ b/src/main/java/net/minestom/server/scoreboard/BelowNameScoreboard.java @@ -1,7 +1,7 @@ package net.minestom.server.scoreboard; import net.minestom.server.Viewable; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.play.DisplayScoreboardPacket; import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; @@ -35,7 +35,7 @@ public class BelowNameScoreboard implements Viewable { scoreboardObjectivePacket = new ScoreboardObjectivePacket(); scoreboardObjectivePacket.objectiveName = objectiveName; scoreboardObjectivePacket.mode = 0; - scoreboardObjectivePacket.objectiveValue = Chat.fromLegacyText(objectiveName); + scoreboardObjectivePacket.objectiveValue = ColoredText.of(objectiveName); scoreboardObjectivePacket.type = 0; displayScoreboardPacket = new DisplayScoreboardPacket(); diff --git a/src/main/java/net/minestom/server/scoreboard/Sidebar.java b/src/main/java/net/minestom/server/scoreboard/Sidebar.java index 6e747619b..dbef51812 100644 --- a/src/main/java/net/minestom/server/scoreboard/Sidebar.java +++ b/src/main/java/net/minestom/server/scoreboard/Sidebar.java @@ -2,6 +2,7 @@ package net.minestom.server.scoreboard; import net.minestom.server.Viewable; import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.play.DisplayScoreboardPacket; import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; @@ -53,7 +54,7 @@ public class Sidebar implements Viewable { ScoreboardObjectivePacket scoreboardObjectivePacket = new ScoreboardObjectivePacket(); scoreboardObjectivePacket.objectiveName = objectiveName; scoreboardObjectivePacket.mode = 2; // Update display text - scoreboardObjectivePacket.objectiveValue = Chat.fromLegacyText(title); + scoreboardObjectivePacket.objectiveValue = ColoredText.of(title); scoreboardObjectivePacket.type = 0; sendPacketToViewers(scoreboardObjectivePacket); @@ -82,7 +83,7 @@ public class Sidebar implements Viewable { } } - public void updateLineContent(String id, String content) { + public void updateLineContent(String id, ColoredText content) { ScoreboardLine scoreboardLine = getLine(id); if (scoreboardLine != null) { scoreboardLine.refreshContent(content); @@ -131,7 +132,7 @@ public class Sidebar implements Viewable { ScoreboardObjectivePacket scoreboardObjectivePacket = new ScoreboardObjectivePacket(); scoreboardObjectivePacket.objectiveName = objectiveName; scoreboardObjectivePacket.mode = 0; // Create scoreboard - scoreboardObjectivePacket.objectiveValue = Chat.fromLegacyText(title); + scoreboardObjectivePacket.objectiveValue = ColoredText.of(title); scoreboardObjectivePacket.type = 0; // Type integer DisplayScoreboardPacket displayScoreboardPacket = new DisplayScoreboardPacket(); @@ -172,7 +173,7 @@ public class Sidebar implements Viewable { public static class ScoreboardLine { private String id; // ID used to modify the line later - private String content; + private ColoredText content; private int line; private String teamName; @@ -180,7 +181,7 @@ public class Sidebar implements Viewable { private String entityName; private SidebarTeam sidebarTeam; - public ScoreboardLine(String id, String content, int line) { + public ScoreboardLine(String id, ColoredText content, int line) { this.id = id; this.content = content; this.line = line; @@ -192,7 +193,7 @@ public class Sidebar implements Viewable { return id; } - public String getContent() { + public ColoredText getContent() { return sidebarTeam == null ? content : sidebarTeam.getPrefix(); } @@ -209,7 +210,7 @@ public class Sidebar implements Viewable { private void createTeam() { this.entityName = Chat.COLOR_CHAR + Integer.toHexString(colorName); - this.sidebarTeam = new SidebarTeam(teamName, content, "", entityName); + this.sidebarTeam = new SidebarTeam(teamName, content, ColoredText.of(""), entityName); } private void returnName(LinkedList colors) { @@ -241,7 +242,7 @@ public class Sidebar implements Viewable { return updateScorePacket; } - private void refreshContent(String content) { + private void refreshContent(ColoredText content) { this.sidebarTeam.refreshPrefix(content); } diff --git a/src/main/java/net/minestom/server/scoreboard/SidebarTeam.java b/src/main/java/net/minestom/server/scoreboard/SidebarTeam.java index 54b321a9c..87bff7cb3 100644 --- a/src/main/java/net/minestom/server/scoreboard/SidebarTeam.java +++ b/src/main/java/net/minestom/server/scoreboard/SidebarTeam.java @@ -1,22 +1,22 @@ package net.minestom.server.scoreboard; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.network.packet.server.play.TeamsPacket; public class SidebarTeam { private String teamName; - private String prefix, suffix; + private ColoredText prefix, suffix; private String entityName; - private String teamDisplayName = "displaynametest"; + private ColoredText teamDisplayName = ColoredText.of("displaynametest"); private byte friendlyFlags = 0x00; private TeamsPacket.NameTagVisibility nameTagVisibility = TeamsPacket.NameTagVisibility.NEVER; private TeamsPacket.CollisionRule collisionRule = TeamsPacket.CollisionRule.NEVER; private int teamColor = 2; - protected SidebarTeam(String teamName, String prefix, String suffix, String entityName) { + protected SidebarTeam(String teamName, ColoredText prefix, ColoredText suffix, String entityName) { this.teamName = teamName; this.prefix = prefix; this.suffix = suffix; @@ -27,13 +27,13 @@ public class SidebarTeam { TeamsPacket teamsPacket = new TeamsPacket(); teamsPacket.teamName = teamName; teamsPacket.action = TeamsPacket.Action.CREATE_TEAM; - teamsPacket.teamDisplayName = Chat.fromLegacyText(teamDisplayName); + teamsPacket.teamDisplayName = teamDisplayName.toString(); teamsPacket.friendlyFlags = friendlyFlags; teamsPacket.nameTagVisibility = nameTagVisibility; teamsPacket.collisionRule = collisionRule; teamsPacket.teamColor = teamColor; - teamsPacket.teamPrefix = Chat.fromLegacyText(prefix); - teamsPacket.teamSuffix = Chat.fromLegacyText(suffix); + teamsPacket.teamPrefix = prefix.toString(); + teamsPacket.teamSuffix = suffix.toString(); teamsPacket.entities = new String[]{entityName}; return teamsPacket; } @@ -45,17 +45,17 @@ public class SidebarTeam { return teamsPacket; } - protected TeamsPacket updatePrefix(String prefix) { + protected TeamsPacket updatePrefix(ColoredText prefix) { TeamsPacket teamsPacket = new TeamsPacket(); teamsPacket.teamName = teamName; teamsPacket.action = TeamsPacket.Action.UPDATE_TEAM_INFO; - teamsPacket.teamDisplayName = Chat.fromLegacyText(teamDisplayName); + teamsPacket.teamDisplayName = teamDisplayName.toString(); teamsPacket.friendlyFlags = friendlyFlags; teamsPacket.nameTagVisibility = nameTagVisibility; teamsPacket.collisionRule = collisionRule; teamsPacket.teamColor = teamColor; - teamsPacket.teamPrefix = Chat.fromLegacyText(prefix); - teamsPacket.teamSuffix = Chat.fromLegacyText(suffix); + teamsPacket.teamPrefix = prefix.toString(); + teamsPacket.teamSuffix = suffix.toString(); return teamsPacket; } @@ -63,11 +63,11 @@ public class SidebarTeam { return entityName; } - protected String getPrefix() { + protected ColoredText getPrefix() { return prefix; } - protected void refreshPrefix(String prefix) { + protected void refreshPrefix(ColoredText prefix) { this.prefix = prefix; } } diff --git a/src/main/java/net/minestom/server/scoreboard/Team.java b/src/main/java/net/minestom/server/scoreboard/Team.java index 56b46f323..9790addfa 100644 --- a/src/main/java/net/minestom/server/scoreboard/Team.java +++ b/src/main/java/net/minestom/server/scoreboard/Team.java @@ -1,8 +1,8 @@ package net.minestom.server.scoreboard; import io.netty.buffer.ByteBuf; -import net.kyori.text.format.TextColor; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ChatColor; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.play.TeamsPacket; import net.minestom.server.utils.PacketUtils; @@ -14,12 +14,15 @@ import java.util.concurrent.CopyOnWriteArraySet; public class Team { private String teamName; - private String teamDisplayName = ""; + private ColoredText teamDisplayName = ColoredText.of(""); private byte friendlyFlags = 0x00; private TeamsPacket.NameTagVisibility nameTagVisibility = TeamsPacket.NameTagVisibility.ALWAYS; private TeamsPacket.CollisionRule collisionRule = TeamsPacket.CollisionRule.NEVER; - private TextColor teamColor = TextColor.WHITE; - private String prefix = "", suffix = ""; + private ChatColor teamColor = ChatColor.WHITE; + + private ColoredText prefix = ColoredText.of(""); + private ColoredText suffix = ColoredText.of(""); + private String[] entities = new String[0]; private Set players = new CopyOnWriteArraySet<>(); @@ -33,13 +36,13 @@ public class Team { teamsCreationPacket = new TeamsPacket(); teamsCreationPacket.teamName = teamName; teamsCreationPacket.action = TeamsPacket.Action.CREATE_TEAM; - teamsCreationPacket.teamDisplayName = Chat.fromLegacyText(teamDisplayName); + teamsCreationPacket.teamDisplayName = teamDisplayName.toString(); teamsCreationPacket.friendlyFlags = friendlyFlags; teamsCreationPacket.nameTagVisibility = nameTagVisibility; teamsCreationPacket.collisionRule = collisionRule; - teamsCreationPacket.teamColor = teamColor.ordinal(); - teamsCreationPacket.teamPrefix = Chat.fromLegacyText(prefix); - teamsCreationPacket.teamSuffix = Chat.fromLegacyText(suffix); + teamsCreationPacket.teamColor = teamColor.getId(); + teamsCreationPacket.teamPrefix = prefix.toString(); + teamsCreationPacket.teamSuffix = suffix.toString(); teamsCreationPacket.entities = entities; TeamsPacket destroyPacket = new TeamsPacket(); @@ -90,9 +93,9 @@ public class Team { this.teamsCreationPacket.entities = entities; } - public void setTeamDisplayName(String teamDisplayName) { + public void setTeamDisplayName(ColoredText teamDisplayName) { this.teamDisplayName = teamDisplayName; - this.teamsCreationPacket.teamDisplayName = Chat.fromLegacyText(teamDisplayName); + this.teamsCreationPacket.teamDisplayName = teamDisplayName.toString(); sendUpdatePacket(); } @@ -108,21 +111,21 @@ public class Team { sendUpdatePacket(); } - public void setTeamColor(TextColor teamColor) { + public void setTeamColor(ChatColor teamColor) { this.teamColor = teamColor; - this.teamsCreationPacket.teamColor = teamColor.ordinal(); + this.teamsCreationPacket.teamColor = teamColor.getId(); sendUpdatePacket(); } - public void setPrefix(String prefix) { + public void setPrefix(ColoredText prefix) { this.prefix = prefix; - this.teamsCreationPacket.teamPrefix = Chat.fromLegacyText(prefix); + this.teamsCreationPacket.teamPrefix = prefix.toString(); sendUpdatePacket(); } - public void setSuffix(String suffix) { + public void setSuffix(ColoredText suffix) { this.suffix = suffix; - this.teamsCreationPacket.teamSuffix = Chat.fromLegacyText(suffix); + this.teamsCreationPacket.teamSuffix = suffix.toString(); sendUpdatePacket(); } @@ -149,13 +152,13 @@ public class Team { TeamsPacket updatePacket = new TeamsPacket(); updatePacket.teamName = teamName; updatePacket.action = TeamsPacket.Action.UPDATE_TEAM_INFO; - updatePacket.teamDisplayName = Chat.fromLegacyText(teamDisplayName); + updatePacket.teamDisplayName = teamDisplayName.toString(); updatePacket.friendlyFlags = friendlyFlags; updatePacket.nameTagVisibility = nameTagVisibility; updatePacket.collisionRule = collisionRule; - updatePacket.teamColor = teamColor.ordinal(); - updatePacket.teamPrefix = Chat.fromLegacyText(prefix); - updatePacket.teamSuffix = Chat.fromLegacyText(suffix); + updatePacket.teamColor = teamColor.getId(); + updatePacket.teamPrefix = prefix.toString(); + updatePacket.teamSuffix = suffix.toString(); ByteBuf buffer = PacketUtils.writePacket(updatePacket); players.forEach(p -> p.getPlayerConnection().sendPacket(buffer)); } diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java index 6e3c15246..2211d223d 100644 --- a/src/main/java/net/minestom/server/utils/Utils.java +++ b/src/main/java/net/minestom/server/utils/Utils.java @@ -1,7 +1,7 @@ package net.minestom.server.utils; import io.netty.buffer.ByteBuf; -import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.instance.Chunk; import net.minestom.server.item.Enchantment; import net.minestom.server.item.ItemStack; @@ -127,17 +127,16 @@ public class Utils { if (hasDisplayName || hasLore) { writer.writeCompound("display", displayWriter -> { if (hasDisplayName) { - final String name = Chat.toJsonString(Chat.fromLegacyText(itemStack.getDisplayName())); + final String name = itemStack.getDisplayName().toString(); displayWriter.writeString("Name", name); } if (hasLore) { - final ArrayList lore = itemStack.getLore(); + final ArrayList lore = itemStack.getLore(); displayWriter.writeList("Lore", NBT.NBT_STRING, lore.size(), () -> { - for (String line : lore) { - line = Chat.toJsonString(Chat.fromLegacyText(line)); - packet.writeShortSizedString(line); + for (ColoredText line : lore) { + packet.writeShortSizedString(line.toString()); } }); diff --git a/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java b/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java index a9138cbf5..e0cc46b58 100644 --- a/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java +++ b/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java @@ -4,6 +4,7 @@ import net.kyori.text.Component; import net.minestom.server.attribute.Attribute; import net.minestom.server.attribute.AttributeOperation; import net.minestom.server.chat.Chat; +import net.minestom.server.chat.ColoredText; import net.minestom.server.item.Enchantment; import net.minestom.server.item.ItemStack; import net.minestom.server.item.attribute.AttributeSlot; @@ -213,7 +214,7 @@ public class NbtReaderUtils { Component textObject = Chat.fromJsonString(jsonDisplayName); String displayName = Chat.toLegacyText(textObject); - item.setDisplayName(displayName); + item.setDisplayName(ColoredText.of(displayName)); readItemStackDisplayNBT(reader, item); } break; @@ -225,13 +226,13 @@ public class NbtReaderUtils { reader.readByte(); // lore type, should always be 0x08 (TAG_String) int size = reader.readInteger(); - ArrayList lore = new ArrayList<>(size); + ArrayList lore = new ArrayList<>(size); for (int i = 0; i < size; i++) { String string = reader.readShortSizedString(); Component textObject = Chat.fromJsonString(string); String line = Chat.toLegacyText(textObject); - lore.add(line); + lore.add(ColoredText.of(line)); if (lore.size() == size) { item.setLore(lore); } From 6ccd31e7feb44fcbe020ce13eaa546f47040b366 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Tue, 23 Jun 2020 00:34:49 +0200 Subject: [PATCH 2/6] Fix Chat.COLOR_CHAR --- src/main/java/net/minestom/server/chat/Chat.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/minestom/server/chat/Chat.java b/src/main/java/net/minestom/server/chat/Chat.java index 7258e5d38..864e63aa3 100644 --- a/src/main/java/net/minestom/server/chat/Chat.java +++ b/src/main/java/net/minestom/server/chat/Chat.java @@ -6,6 +6,8 @@ import net.kyori.text.serializer.legacy.LegacyComponentSerializer; public class Chat { + public static final char COLOR_CHAR = (char) 0xA7; // Represent the character '§' + public static Component fromJsonString(String json) { return GsonComponentSerializer.INSTANCE.deserialize(json); } From 9dfb9b657bf091c1f4a8a66df88325b4115516ec Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Tue, 23 Jun 2020 17:25:46 +0200 Subject: [PATCH 3/6] Added CommandSender#isPlayer and CommandSender#isConsole --- .../net/minestom/server/entity/Player.java | 51 +++++++++++++++---- .../minestom/server/inventory/Inventory.java | 26 ++++++++-- .../server/inventory/PlayerInventory.java | 10 +++- .../click/InventoryClickProcessor.java | 6 +++ .../server/listener/WindowListener.java | 10 +++- .../server/utils/item/ItemStackUtils.java | 3 ++ 6 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 1a4df5e4c..34398bff2 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -78,9 +78,13 @@ public class Player extends LivingEntity implements CommandSender { private PlayerSettings settings; private float exp; private int level; + private PlayerInventory inventory; - private short heldSlot; private Inventory openInventory; + // Used internally to allow the closing of inventory within the inventory listener + private boolean didCloseInventory; + + private short heldSlot; private Position respawnPoint; @@ -1335,15 +1339,6 @@ public class Player extends LivingEntity implements CommandSender { } } - /** - * Get the player open inventory - * - * @return the currently open inventory, null if there is not (player inventory is not detected) - */ - public Inventory getOpenInventory() { - return openInventory; - } - /** * Used to get the {@link CustomBlock} that the player is currently mining * @@ -1360,6 +1355,15 @@ public class Player extends LivingEntity implements CommandSender { return Collections.unmodifiableSet(bossBars); } + /** + * Get the player open inventory + * + * @return the currently open inventory, null if there is not (player inventory is not detected) + */ + public Inventory getOpenInventory() { + return openInventory; + } + /** * Open the specified Inventory, close the previous inventory if existing * @@ -1394,7 +1398,7 @@ public class Player extends LivingEntity implements CommandSender { /** * Close the current inventory if there is any - * It closes the player inventory if {@link #getOpenInventory()} returns null + * It closes the player inventory (when opened) if {@link #getOpenInventory()} returns null */ public void closeInventory() { Inventory openInventory = getOpenInventory(); @@ -1406,6 +1410,7 @@ public class Player extends LivingEntity implements CommandSender { getInventory().setCursorItem(ItemStack.getAirItem()); } else { cursorItem = openInventory.getCursorItem(this); + openInventory.setCursorItem(this, ItemStack.getAirItem()); } if (!cursorItem.isAir()) { // Add item to inventory if he hasn't been able to drop it @@ -1424,6 +1429,30 @@ public class Player extends LivingEntity implements CommandSender { } playerConnection.sendPacket(closeWindowPacket); inventory.update(); + this.didCloseInventory = true; + } + + /** + * Used internally to prevent an inventory click to be processed + * when the inventory listeners closed the inventory + *

+ * Should only be used within an inventory listener (event or condition) + * + * @return true if the inventory has been closed, false otherwise + */ + public boolean didCloseInventory() { + return didCloseInventory; + } + + /** + * Used internally to reset the didCloseInventory field + *

+ * Shouldn't be used externally without proper understanding of its consequence + * + * @param didCloseInventory the new didCloseInventory field + */ + public void UNSAFE_changeDidCloseInventory(boolean didCloseInventory) { + this.didCloseInventory = didCloseInventory; } /** diff --git a/src/main/java/net/minestom/server/inventory/Inventory.java b/src/main/java/net/minestom/server/inventory/Inventory.java index 122f432ea..2c7df4a8b 100644 --- a/src/main/java/net/minestom/server/inventory/Inventory.java +++ b/src/main/java/net/minestom/server/inventory/Inventory.java @@ -186,14 +186,34 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View * Get the cursor item of a viewer * * @param player the player to get the cursor item from - * @return the player cursor item - * @throws IllegalStateException if {@code player} is not in the viewer list + * @return the player cursor item, air item if the player is not a viewer */ public ItemStack getCursorItem(Player player) { - Check.stateCondition(!isViewer(player), "You can only get the cursor item of a viewer"); return cursorPlayersItem.getOrDefault(player, ItemStack.getAirItem()); } + /** + * Change the cursor item of a viewer, + * does nothing if {@param player} is not a viewer + * + * @param player the player to change the cursor item + * @param cursorItem the new player cursor item + */ + public void setCursorItem(Player player, ItemStack cursorItem) { + if (!isViewer(player)) + return; + + cursorItem = ItemStackUtils.notNull(cursorItem); + + SetSlotPacket setSlotPacket = new SetSlotPacket(); + setSlotPacket.windowId = -1; + setSlotPacket.slot = -1; + setSlotPacket.itemStack = cursorItem; + player.getPlayerConnection().sendPacket(setSlotPacket); + + this.cursorPlayersItem.put(player, cursorItem); + } + private synchronized void safeItemInsert(int slot, ItemStack itemStack) { itemStack = ItemStackUtils.notNull(itemStack); this.itemStacks[slot] = itemStack; diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index 3997cefde..1e06a4f9f 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -218,7 +218,13 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler * @param cursorItem the new cursor item */ public void setCursorItem(ItemStack cursorItem) { - this.cursorItem = ItemStackUtils.notNull(cursorItem); + cursorItem = ItemStackUtils.notNull(cursorItem); + this.cursorItem = cursorItem; + SetSlotPacket setSlotPacket = new SetSlotPacket(); + setSlotPacket.windowId = -1; + setSlotPacket.slot = -1; + setSlotPacket.itemStack = cursorItem; + player.getPlayerConnection().sendPacket(setSlotPacket); } /** @@ -233,7 +239,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()), "The slot " + slot + " does not exist for player"); Check.notNull(itemStack, "The ItemStack cannot be null, you can set air instead"); - + EntityEquipmentPacket.Slot equipmentSlot; if (slot == player.getHeldSlot()) { diff --git a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java index 8a0e9d3cc..004a79b3c 100644 --- a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java +++ b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java @@ -532,6 +532,12 @@ public class InventoryClickProcessor { } } + // Cancel the click if the inventory has been closed by Player#closeInventory within an inventory listener + if (player.didCloseInventory()) { + clickResult.setCancel(true); + player.UNSAFE_changeDidCloseInventory(false); + } + } return clickResult; diff --git a/src/main/java/net/minestom/server/listener/WindowListener.java b/src/main/java/net/minestom/server/listener/WindowListener.java index aaac6c2c4..16115ae0e 100644 --- a/src/main/java/net/minestom/server/listener/WindowListener.java +++ b/src/main/java/net/minestom/server/listener/WindowListener.java @@ -90,6 +90,10 @@ public class WindowListener { player.getPlayerConnection().sendPacket(windowConfirmationPacket); } + /** + * @param player the player to refresh the cursor item + * @param inventory the player open inventory, null if not any (could be player inventory) + */ private static void refreshCursorItem(Player player, Inventory inventory) { PlayerInventory playerInventory = player.getInventory(); @@ -100,8 +104,10 @@ public class WindowListener { cursorItem = playerInventory.getCursorItem(); } - // Setting the window id properly seems to broke +64 stack support - //byte windowId = inventory == null ? 0 : inventory.getWindowId(); + // Error occurred while retrieving the cursor item, stop here + if (cursorItem == null) { + return; + } SetSlotPacket setSlotPacket = new SetSlotPacket(); setSlotPacket.windowId = -1; diff --git a/src/main/java/net/minestom/server/utils/item/ItemStackUtils.java b/src/main/java/net/minestom/server/utils/item/ItemStackUtils.java index ec1fb3e4f..808cdc91f 100644 --- a/src/main/java/net/minestom/server/utils/item/ItemStackUtils.java +++ b/src/main/java/net/minestom/server/utils/item/ItemStackUtils.java @@ -5,6 +5,9 @@ import net.minestom.server.item.ItemStack; public class ItemStackUtils { /** + * Ensure that the returned ItemStack won't be null + * by replacing every null instance by a new Air one + * * @param itemStack the ItemStack to return if not null * @return {@code itemStack} if not null, {@link ItemStack#getAirItem()} otherwise */ From b47efa35ca1260724887fd5705ee5f1acf51c395 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Tue, 23 Jun 2020 17:50:43 +0200 Subject: [PATCH 4/6] Added PlayerChangeHeldSlotEvent --- .../player/PlayerChangeHeldSlotEvent.java | 49 +++++++++++++++++++ .../server/listener/PlayerHeldListener.java | 24 ++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java diff --git a/src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java b/src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java new file mode 100644 index 000000000..6838f65e2 --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java @@ -0,0 +1,49 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.CancellableEvent; +import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.validate.Check; + +/** + * Called when a player change his held slot (by pressing 1-9 keys) + */ +public class PlayerChangeHeldSlotEvent extends CancellableEvent { + + private Player player; + private short slot; + + public PlayerChangeHeldSlotEvent(Player player, short slot) { + this.player = player; + this.slot = slot; + } + + /** + * Get the player who changed his held slot + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the slot which the player will held + * + * @return the held slot + */ + public short getSlot() { + return slot; + } + + /** + * Change the final held slot of the player + * + * @param slot the new held slot + * @throws IllegalArgumentException if {@param slot} is not between 0 and 8 + */ + public void setSlot(short slot) { + Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "The held slot needs to be between 0 and 8"); + this.slot = slot; + } +} diff --git a/src/main/java/net/minestom/server/listener/PlayerHeldListener.java b/src/main/java/net/minestom/server/listener/PlayerHeldListener.java index 14948f841..f633d423f 100644 --- a/src/main/java/net/minestom/server/listener/PlayerHeldListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerHeldListener.java @@ -1,18 +1,38 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; +import net.minestom.server.event.player.PlayerChangeHeldSlotEvent; import net.minestom.server.network.packet.client.play.ClientHeldItemChangePacket; import net.minestom.server.utils.MathUtils; public class PlayerHeldListener { public static void heldListener(ClientHeldItemChangePacket packet, Player player) { - short slot = packet.slot; + final short slot = packet.slot; if (!MathUtils.isBetween(slot, 0, 8)) { // Incorrect packet, ignore return; } - player.refreshHeldSlot(slot); + + PlayerChangeHeldSlotEvent changeHeldSlotEvent = new PlayerChangeHeldSlotEvent(player, slot); + player.callEvent(PlayerChangeHeldSlotEvent.class, changeHeldSlotEvent); + + if (!changeHeldSlotEvent.isCancelled()) { + // Event hasn't been canceled, process it + + final short resultSlot = changeHeldSlotEvent.getSlot(); + + // If the held slot has been changed by the event, send the change to the player + if (resultSlot != slot) { + player.setHeldItemSlot(resultSlot); + } else { + // Otherwise, simply refresh the player field + player.refreshHeldSlot(resultSlot); + } + } else { + // Event has been canceled, send the last held slot to refresh the client + player.setHeldItemSlot(player.getHeldSlot()); + } } } From 19fc90e7648690df39a51ebf4f41bafa4dd20e38 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Tue, 23 Jun 2020 18:09:01 +0200 Subject: [PATCH 5/6] Fixed Held packet + replaced the short by a byte --- build.gradle | 2 +- src/main/java/fr/themode/demo/PlayerInit.java | 5 ++++- src/main/java/net/minestom/server/entity/Player.java | 10 +++++----- .../server/event/player/PlayerChangeHeldSlotEvent.java | 8 ++++---- .../minestom/server/listener/PlayerHeldListener.java | 7 ++++--- .../packet/server/play/HeldItemChangePacket.java | 4 ++-- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 418048951..6c434da26 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,6 @@ dependencies { api 'net.kyori:text-serializer-plain:3.0.3' // Pathfinding - implementation 'com.github.MadMartian:hydrazine-path-finding:1.02' + implementation 'com.github.MadMartian:hydrazine-path-finding:1.1.0' } diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index c0596a0e5..7e762abd0 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -212,6 +212,7 @@ public class PlayerInit { player.getInventory().addInventoryCondition((p, slot, clickType, inventoryConditionResult) -> { player.sendMessage("CLICK PLAYER INVENTORY"); System.out.println("slot player: " + slot); + p.closeInventory(); }); /*Sidebar scoreboard = new Sidebar("Scoreboard Title"); @@ -225,9 +226,11 @@ public class PlayerInit { }); player.addEventCallback(PlayerSpawnEvent.class, event -> { - player.setGameMode(GameMode.CREATIVE); + player.setGameMode(GameMode.SURVIVAL); player.teleport(new Position(0, 41f, 0)); + player.setHeldItemSlot((byte) 5); + player.setGlowing(true); ItemStack item = new ItemStack(Material.STONE_SWORD, (byte) 1); diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 34398bff2..96b2216e1 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -84,7 +84,7 @@ public class Player extends LivingEntity implements CommandSender { // Used internally to allow the closing of inventory within the inventory listener private boolean didCloseInventory; - private short heldSlot; + private byte heldSlot; private Position respawnPoint; @@ -1290,7 +1290,7 @@ public class Player extends LivingEntity implements CommandSender { * @param slot the slot that the player has to held * @throws IllegalArgumentException if {@code slot} is not between 0 and 8 */ - public void setHeldItemSlot(short slot) { + public void setHeldItemSlot(byte slot) { Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "Slot has to be between 0 and 8"); HeldItemChangePacket heldItemChangePacket = new HeldItemChangePacket(); @@ -1304,7 +1304,7 @@ public class Player extends LivingEntity implements CommandSender { * * @return the current held slot for the player */ - public short getHeldSlot() { + public byte getHeldSlot() { return heldSlot; } @@ -1738,11 +1738,11 @@ public class Player extends LivingEntity implements CommandSender { * Also cancel eating if {@link #isEating()} was true *

* Warning: the player will not be noticed by this chance, only his viewers, - * see instead: {@link #setHeldItemSlot(short)} + * see instead: {@link #setHeldItemSlot(byte)} * * @param slot the new held slot */ - public void refreshHeldSlot(short slot) { + public void refreshHeldSlot(byte slot) { this.heldSlot = slot; syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND); diff --git a/src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java b/src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java index 6838f65e2..6a70f608a 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerChangeHeldSlotEvent.java @@ -11,9 +11,9 @@ import net.minestom.server.utils.validate.Check; public class PlayerChangeHeldSlotEvent extends CancellableEvent { private Player player; - private short slot; + private byte slot; - public PlayerChangeHeldSlotEvent(Player player, short slot) { + public PlayerChangeHeldSlotEvent(Player player, byte slot) { this.player = player; this.slot = slot; } @@ -32,7 +32,7 @@ public class PlayerChangeHeldSlotEvent extends CancellableEvent { * * @return the held slot */ - public short getSlot() { + public byte getSlot() { return slot; } @@ -42,7 +42,7 @@ public class PlayerChangeHeldSlotEvent extends CancellableEvent { * @param slot the new held slot * @throws IllegalArgumentException if {@param slot} is not between 0 and 8 */ - public void setSlot(short slot) { + public void setSlot(byte slot) { Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "The held slot needs to be between 0 and 8"); this.slot = slot; } diff --git a/src/main/java/net/minestom/server/listener/PlayerHeldListener.java b/src/main/java/net/minestom/server/listener/PlayerHeldListener.java index f633d423f..7bd713ac1 100644 --- a/src/main/java/net/minestom/server/listener/PlayerHeldListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerHeldListener.java @@ -8,19 +8,20 @@ import net.minestom.server.utils.MathUtils; public class PlayerHeldListener { public static void heldListener(ClientHeldItemChangePacket packet, Player player) { - final short slot = packet.slot; - if (!MathUtils.isBetween(slot, 0, 8)) { + if (!MathUtils.isBetween(packet.slot, 0, 8)) { // Incorrect packet, ignore return; } + final byte slot = (byte) packet.slot; + PlayerChangeHeldSlotEvent changeHeldSlotEvent = new PlayerChangeHeldSlotEvent(player, slot); player.callEvent(PlayerChangeHeldSlotEvent.class, changeHeldSlotEvent); if (!changeHeldSlotEvent.isCancelled()) { // Event hasn't been canceled, process it - final short resultSlot = changeHeldSlotEvent.getSlot(); + final byte resultSlot = changeHeldSlotEvent.getSlot(); // If the held slot has been changed by the event, send the change to the player if (resultSlot != slot) { diff --git a/src/main/java/net/minestom/server/network/packet/server/play/HeldItemChangePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/HeldItemChangePacket.java index 44ce887b5..85e8cc204 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/HeldItemChangePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/HeldItemChangePacket.java @@ -6,11 +6,11 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier; public class HeldItemChangePacket implements ServerPacket { - public short slot; + public byte slot; @Override public void write(PacketWriter writer) { - writer.writeShort(slot); + writer.writeByte(slot); } @Override From 4111c728df768d6d87b1f4f588e6a58af77292fd Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 23 Jun 2020 18:17:02 +0200 Subject: [PATCH 6/6] Basic implementation of tags --- .../net/minestom/server/MinecraftServer.java | 7 ++ .../minestom/server/gamedata/tags/Tag.java | 73 ++++++++++++ .../server/gamedata/tags/TagContainer.java | 9 ++ .../server/gamedata/tags/TagManager.java | 102 +++++++++++++++++ src/test/java/tags/TestTags.java | 104 ++++++++++++++++++ 5 files changed, 295 insertions(+) create mode 100644 src/main/java/net/minestom/server/gamedata/tags/Tag.java create mode 100644 src/main/java/net/minestom/server/gamedata/tags/TagContainer.java create mode 100644 src/main/java/net/minestom/server/gamedata/tags/TagManager.java create mode 100644 src/test/java/tags/TestTags.java diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 17e4df539..0b954bc49 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -6,6 +6,7 @@ import net.minestom.server.data.DataManager; import net.minestom.server.entity.EntityManager; import net.minestom.server.entity.Player; import net.minestom.server.gamedata.loottables.LootTableManager; +import net.minestom.server.gamedata.tags.TagManager; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.listener.manager.PacketListenerManager; @@ -91,6 +92,7 @@ public class MinecraftServer { private static ResponseDataConsumer responseDataConsumer; private static Difficulty difficulty = Difficulty.NORMAL; private static LootTableManager lootTableManager; + private static TagManager tagManager; public static MinecraftServer init() { connectionManager = new ConnectionManager(); @@ -111,6 +113,7 @@ public class MinecraftServer { updateManager = new UpdateManager(); lootTableManager = new LootTableManager(); + tagManager = new TagManager(); nettyServer = new NettyServer(packetProcessor); @@ -208,6 +211,10 @@ public class MinecraftServer { return lootTableManager; } + public static TagManager getTagManager() { + return tagManager; + } + public void start(String address, int port, ResponseDataConsumer responseDataConsumer) { LOGGER.info("Starting Minestom server."); MinecraftServer.responseDataConsumer = responseDataConsumer; diff --git a/src/main/java/net/minestom/server/gamedata/tags/Tag.java b/src/main/java/net/minestom/server/gamedata/tags/Tag.java new file mode 100644 index 000000000..c95a2b677 --- /dev/null +++ b/src/main/java/net/minestom/server/gamedata/tags/Tag.java @@ -0,0 +1,73 @@ +package net.minestom.server.gamedata.tags; + +import net.minestom.server.utils.NamespaceID; + +import java.io.FileNotFoundException; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Represents a group of items, blocks, fluids, entity types or function. + * Immutable by design + */ +public class Tag { + + public static final Tag EMPTY = new Tag(); + + private Set values; + + /** + * Creates a new empty tag + */ + public Tag() { + values = new HashSet<>(); + lockValues(); + } + + /** + * Creates a new tag with the contents of the container + * @param manager Used to load tag contents (as tags are valid values inside 'values') + * @param lowerPriority Tag contents from lower priority data packs. If 'replace' is false in 'container', + * appends the contents of that pack to the one being constructed + * @param container + */ + public Tag(TagManager manager, String type, Tag lowerPriority, TagContainer container) throws FileNotFoundException { + values = new HashSet<>(); + if(!container.replace) { + values.addAll(lowerPriority.values); + } + Objects.requireNonNull(container.values, "Attempted to load from a TagContainer with no 'values' array"); + for(String line : container.values) { + if(line.startsWith("#")) { // pull contents from a tag + Tag subtag = manager.load(NamespaceID.from(line.substring(1)), type); + values.addAll(subtag.values); + } else { + values.add(NamespaceID.from(line)); + } + } + + lockValues(); + } + + private void lockValues() { + values = Set.copyOf(values); + } + + /** + * Checks whether the given id in inside this tag + * @param id the id to check against + * @return 'true' iif this tag contains the given id + */ + public boolean contains(NamespaceID id) { + return values.contains(id); + } + + /** + * Returns an immutable set of values present in this tag + * @return immutable set of values present in this tag + */ + public Set getValues() { + return values; + } +} diff --git a/src/main/java/net/minestom/server/gamedata/tags/TagContainer.java b/src/main/java/net/minestom/server/gamedata/tags/TagContainer.java new file mode 100644 index 000000000..7f181851a --- /dev/null +++ b/src/main/java/net/minestom/server/gamedata/tags/TagContainer.java @@ -0,0 +1,9 @@ +package net.minestom.server.gamedata.tags; + +/** + * Meant only for parsing tag JSON + */ +public class TagContainer { + boolean replace; + String[] values; +} diff --git a/src/main/java/net/minestom/server/gamedata/tags/TagManager.java b/src/main/java/net/minestom/server/gamedata/tags/TagManager.java new file mode 100644 index 000000000..28269854f --- /dev/null +++ b/src/main/java/net/minestom/server/gamedata/tags/TagManager.java @@ -0,0 +1,102 @@ +package net.minestom.server.gamedata.tags; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import net.minestom.server.registry.ResourceGatherer; +import net.minestom.server.utils.NamespaceID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +/** + * Handles loading and caching of tags + */ +public class TagManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(TagManager.class); + private final Gson gson; + private Map cache = new HashMap<>(); + + public TagManager() { + gson = new GsonBuilder() + .create(); + } + + /** + * Loads a tag with the given name. This method attempts to read from "data/<name.domain>/tags/<tagType>/<name.path>.json" if the given name is not already present in cache + * @param name + * @param tagType the type of the tag to load, used to resolve paths (blocks, items, entity_types, fluids, functions are the vanilla variants) + * @return + * @throws FileNotFoundException if the file does not exist + */ + public Tag load(NamespaceID name, String tagType) throws FileNotFoundException { + return load(name, tagType, () -> new FileReader(new File(ResourceGatherer.DATA_FOLDER, "data/"+name.getDomain()+"/tags/"+tagType+"/"+name.getPath()+".json"))); + } + + /** + * Loads a tag with the given name. This method attempts to read from 'reader' if the given name is not already present in cache + * @param name + * @param tagType the type of the tag to load, used to resolve paths (blocks, items, entity_types, fluids, functions are the vanilla variants) + * @param reader + * @return + */ + public Tag load(NamespaceID name, String tagType, Reader reader) throws FileNotFoundException { + return load(name, tagType, () -> reader); + } + + /** + * Loads a tag with the given name. This method reads from 'reader'. This will override the previous tag + * @param name + * @param tagType the type of the tag to load, used to resolve paths (blocks, items, entity_types, fluids, functions are the vanilla variants) + * @param readerSupplier + * @return + */ + public Tag forceLoad(NamespaceID name, String tagType, ReaderSupplierWithFileNotFound readerSupplier) throws FileNotFoundException { + Tag prev = cache.getOrDefault(name, Tag.EMPTY); + FileNotFoundException[] ex = new FileNotFoundException[1]; // very ugly code but Java does not let its standard interfaces throw exceptions + Tag result = create(prev, tagType, readerSupplier); + cache.put(name, result); + return result; + } + + /** + * Loads a tag with the given name. This method attempts to read from 'reader' if the given name is not already present in cache + * @param name + * @param tagType the type of the tag to load, used to resolve paths (blocks, items, entity_types, fluids, functions are the vanilla variants) + * @param readerSupplier + * @return + */ + public Tag load(NamespaceID name, String tagType, ReaderSupplierWithFileNotFound readerSupplier) throws FileNotFoundException { + Tag prev = cache.getOrDefault(name, Tag.EMPTY); + FileNotFoundException[] ex = new FileNotFoundException[1]; // very ugly code but Java does not let its standard interfaces throw exceptions + Tag result = cache.computeIfAbsent(name, _name -> { + try { + return create(prev, tagType, readerSupplier); + } catch (FileNotFoundException e) { + ex[0] = e; + return Tag.EMPTY; + } + }); + if(ex[0] != null) { + throw ex[0]; + } + return result; + } + + private Tag create(Tag prev, String tagType, ReaderSupplierWithFileNotFound reader) throws FileNotFoundException { + TagContainer container = gson.fromJson(reader.get(), TagContainer.class); + try { + return new Tag(this, tagType, prev, container); + } catch (FileNotFoundException e) { + LOGGER.error("Failed to load tag due to error", e); + return Tag.EMPTY; + } + } + + public interface ReaderSupplierWithFileNotFound { + Reader get() throws FileNotFoundException; + } +} diff --git a/src/test/java/tags/TestTags.java b/src/test/java/tags/TestTags.java new file mode 100644 index 000000000..3f59ba112 --- /dev/null +++ b/src/test/java/tags/TestTags.java @@ -0,0 +1,104 @@ +package tags; + +import net.minestom.server.gamedata.tags.Tag; +import net.minestom.server.gamedata.tags.TagManager; +import net.minestom.server.utils.NamespaceID; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.FileNotFoundException; +import java.io.StringReader; + +public class TestTags { + + private TagManager tags; + + @Before + public void init() { + tags = new TagManager(); + } + + @Test + public void testSubTag() throws FileNotFoundException { + String tag1 = "{\n" + + "\t\"replace\": false,\n" + + "\t\"values\": [\n" + + "\t\t\"minestom:an_item\"\n" + + "\t]\n" + + "}"; + + String tag2 = "{\n" + + "\t\"replace\": false,\n" + + "\t\"values\": [\n" + + "\t\t\"#minestom:test_sub\",\n" + + "\t\t\"minestom:some_other_item\"\n" + + "\t]\n" + + "}"; + Assert.assertNotEquals(Tag.EMPTY, tags.load(NamespaceID.from("minestom:test_sub"), "any", new StringReader(tag1))); + Tag loaded = tags.load(NamespaceID.from("minestom:test"), "any", new StringReader(tag2)); + NamespaceID[] values = loaded.getValues().toArray(new NamespaceID[0]); + Assert.assertEquals(2, values.length); + Assert.assertTrue(loaded.contains(NamespaceID.from("minestom:an_item"))); + Assert.assertTrue(loaded.contains(NamespaceID.from("minestom:some_other_item"))); + Assert.assertFalse(loaded.contains(NamespaceID.from("minestom:some_other_item_that_is_not_in_the_tag"))); + } + + /** + * A value of 'true' in 'replace' should replace previous contents + */ + @Test + public void testReplacement() throws FileNotFoundException { + String tag1 = "{\n" + + "\t\"replace\": false,\n" + + "\t\"values\": [\n" + + "\t\t\"minestom:an_item\"\n" + + "\t]\n" + + "}"; + + String tag2 = "{\n" + + "\t\"replace\": true,\n" + + "\t\"values\": [\n" + + "\t\t\"minestom:some_other_item\"\n" + + "\t]\n" + + "}"; + Assert.assertNotEquals(Tag.EMPTY, tags.load(NamespaceID.from("minestom:test"), "any", new StringReader(tag1))); + Tag loaded = tags.forceLoad(NamespaceID.from("minestom:test"), "any", () -> new StringReader(tag2)); + Assert.assertNotEquals(Tag.EMPTY, loaded); + Assert.assertEquals(1, loaded.getValues().size()); + Assert.assertTrue(loaded.contains(NamespaceID.from("minestom:some_other_item"))); + Assert.assertFalse(loaded.contains(NamespaceID.from("minestom:an_item"))); + } + + /** + * A value of 'false' in 'replace' should append to previous contents + */ + @Test + public void testAppend() throws FileNotFoundException { + String tag1 = "{\n" + + "\t\"replace\": false,\n" + + "\t\"values\": [\n" + + "\t\t\"minestom:an_item\"\n" + + "\t]\n" + + "}"; + + String tag2 = "{\n" + + "\t\"replace\": false,\n" + + "\t\"values\": [\n" + + "\t\t\"minestom:some_other_item\"\n" + + "\t]\n" + + "}"; + Assert.assertNotEquals(Tag.EMPTY, tags.load(NamespaceID.from("minestom:test"), "any", new StringReader(tag1))); + Tag loaded = tags.forceLoad(NamespaceID.from("minestom:test"), "any", () -> new StringReader(tag2)); + Assert.assertNotEquals(Tag.EMPTY, loaded); + Assert.assertEquals(2, loaded.getValues().size()); + Assert.assertTrue(loaded.contains(NamespaceID.from("minestom:some_other_item"))); + Assert.assertTrue(loaded.contains(NamespaceID.from("minestom:an_item"))); + } + + @After + public void cleanup() { + tags = null; + } +}