Merge remote-tracking branch 'origin/master' into block-types

This commit is contained in:
jglrxavpok 2020-06-23 18:18:12 +02:00
commit 60e1b856c7
54 changed files with 1423 additions and 311 deletions

2
.github/README.md vendored
View File

@ -62,7 +62,7 @@ It is our major concept, worlds are great for survival with friends, but when it
Being able to create instances directly on the go is a must-have, according to us it can push many more projects forward.
Instances also come with performance benefits, unlike some others which will be fully single-threaded or maybe using one thread per world we are using a set number of threads (pool) to manage all chunks independently from instances, meaning using more of CPU power.
Instances also come with performance benefits, unlike some others which will be fully single-threaded or maybe using one thread per world we are using a set number of threads (pool) to manage all chunks independently from instances, meaning using more CPU power.
## Blocks
Minestom by default does not know what is a chest, you will have to tell him that it opens an inventory.

View File

@ -60,12 +60,11 @@ 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'
// Pathfinding
implementation 'com.github.MadMartian:hydrazine-path-finding:1.02'
implementation 'com.github.MadMartian:hydrazine-path-finding:1.1.0'
}

View File

@ -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<String, ThreadResult> 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,13 +200,19 @@ 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);
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");
@ -217,27 +225,27 @@ public class PlayerInit {
scoreboard.setTitle("test");*/
});
player.addEventCallback(PlayerSkinInitEvent.class, event -> {
event.setSkin(skin);
});
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);
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 +272,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 -> {

View File

@ -23,7 +23,7 @@ public class DimensionCommand implements CommandProcessor {
@Override
public boolean process(CommandSender sender, String command, String[] args) {
if (!(sender instanceof Player))
if (!sender.isPlayer())
return false;
Player player = (Player) sender;

View File

@ -71,7 +71,7 @@ public class GamemodeCommand extends Command<CommandSender> {
}
private boolean isAllowed(CommandSender sender) {
if (!(sender instanceof Player)) {
if (!sender.isPlayer()) {
sender.sendMessage("The command is only available for player");
return false;
}

View File

@ -29,7 +29,7 @@ public class HealthCommand extends Command<CommandSender> {
}
private boolean condition(CommandSender sender) {
if (!(sender instanceof Player)) {
if (!sender.isPlayer()) {
sender.sendMessage("The command is only available for player");
return false;
}

View File

@ -1,16 +1,11 @@
package fr.themode.demo.commands;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import com.extollit.gaming.ai.path.model.PathObject;
import com.extollit.linalg.immutable.Vec3i;
import fr.themode.demo.entity.ChickenCreature;
import net.minestom.server.command.CommandProcessor;
import net.minestom.server.command.CommandSender;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.Instance;
import java.util.Iterator;
public class SimpleCommand implements CommandProcessor {
@Override
public String getCommandName() {
@ -25,7 +20,7 @@ public class SimpleCommand implements CommandProcessor {
@Override
public boolean process(CommandSender sender, String command, String[] args) {
if (!(sender instanceof Player))
if (!sender.isPlayer())
return false;
Player player = (Player) sender;

View File

@ -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;

View File

@ -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"));
}
}

View File

@ -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<Player> 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);
}

View File

@ -1,22 +1,13 @@
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 +15,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);
}
}

View File

@ -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;
}
}

View File

@ -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<String, ChatColor> 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 + "}";
}
}

View File

@ -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;
}
}

View File

@ -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<JsonObject> 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<JsonObject> getComponents() {
final List<JsonObject> 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
}
}

View File

@ -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<RichComponent> 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<JsonObject> 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<JsonObject> componentObjects = getComponentObject(component);
for (JsonObject componentObject : componentObjects) {
extraArray.add(componentObject);
}
}
mainObject.add("extra", extraArray);
return mainObject;
}
private List<JsonObject> getComponentObject(RichComponent component) {
ColoredText coloredText = component.getText();
List<JsonObject> 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;
}
}
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.command;
import net.minestom.server.entity.Player;
public interface CommandSender {
void sendMessage(String message);
@ -10,4 +12,12 @@ public interface CommandSender {
}
}
default boolean isPlayer() {
return this instanceof Player;
}
default boolean isConsole() {
return this instanceof ConsoleSender;
}
}

View File

@ -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;
@ -539,6 +539,15 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
return uuid;
}
/**
* Change the internal entity UUID, mostly unsafe
*
* @param uuid the new entity uuid
*/
protected void setUuid(UUID uuid) {
this.uuid = uuid;
}
/**
* Return false just after instantiation, set to true after calling {@link #setInstance(Instance)}
*
@ -815,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;
}
@ -824,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);
}
@ -1218,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());
}
}

View File

@ -2,6 +2,7 @@ package net.minestom.server.entity;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.player.PlayerLoginEvent;
import net.minestom.server.event.player.PlayerPreLoginEvent;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceManager;
@ -9,8 +10,10 @@ import net.minestom.server.utils.thread.MinestomThread;
import net.minestom.server.utils.validate.Check;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
public final class EntityManager {
@ -178,8 +181,38 @@ public final class EntityManager {
}
}
/**
* Call the player initialization callbacks and the event {@link PlayerPreLoginEvent}
* If the player hasn't been kicked, add him to the waiting list
* <p>
* Can be considered as a pre-init thing
*
* @param player the player to add
*/
public void addWaitingPlayer(Player player) {
// Init player (register events)
for (Consumer<Player> playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) {
playerInitialization.accept(player);
}
// Call pre login event
PlayerPreLoginEvent playerPreLoginEvent = new PlayerPreLoginEvent(player, player.getUsername(), player.getUuid());
player.callEvent(PlayerPreLoginEvent.class, playerPreLoginEvent);
// Ignore the player if he has been disconnected (kick)
final boolean online = player.isOnline();
if (!online)
return;
// Add him to the list and change his username/uuid if changed
this.waitingPlayers.add(player);
String username = playerPreLoginEvent.getUsername();
UUID uuid = playerPreLoginEvent.getPlayerUuid();
player.setUsername(username);
player.setUuid(uuid);
}
/**

View File

@ -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<ClientPlayPacket> packets = new ConcurrentLinkedQueue<>();
private int latency;
private String displayName;
private ColoredText displayName;
private PlayerSkin skin;
private Dimension dimension;
@ -79,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 byte heldSlot;
private Position respawnPoint;
@ -163,16 +166,10 @@ 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() {
// Init player (register events)
for (Consumer<Player> playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) {
playerInitialization.accept(this);
}
// TODO complete login sequence with optionals packets
JoinGamePacket joinGamePacket = new JoinGamePacket();
joinGamePacket.entityId = getEntityId();
@ -408,21 +405,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);
@ -599,27 +598,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) {
@ -662,33 +659,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!");
}
@ -696,47 +689,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()) {
@ -845,7 +814,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;
}
@ -856,10 +825,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);
@ -950,6 +919,16 @@ public class Player extends LivingEntity implements CommandSender {
return username;
}
/**
* Change the internal player name, used for the {@link PlayerPreLoginEvent}
* mostly unsafe outside of it
*
* @param username the new player name
*/
protected void setUsername(String username) {
this.username = username;
}
private void sendChangeGameStatePacket(ChangeGameStatePacket.Reason reason, float value) {
ChangeGameStatePacket changeGameStatePacket = new ChangeGameStatePacket();
changeGameStatePacket.reason = reason;
@ -1282,13 +1261,14 @@ 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);
}
/**
@ -1297,7 +1277,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() {
@ -1310,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();
@ -1324,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;
}
@ -1359,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
*
@ -1384,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
*
@ -1418,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();
@ -1430,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
@ -1448,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
* <p>
* 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
* <p>
* 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;
}
/**
@ -1733,11 +1738,11 @@ public class Player extends LivingEntity implements CommandSender {
* Also cancel eating if {@link #isEating()} was true
* <p>
* 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);
@ -1878,7 +1883,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 =

View File

@ -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 + "}");
}
/**

View File

@ -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);
}

View File

@ -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 byte slot;
public PlayerChangeHeldSlotEvent(Player player, byte 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 byte 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(byte slot) {
Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "The held slot needs to be between 0 and 8");
this.slot = slot;
}
}

View File

@ -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<Player> recipients;
private String message;
private Function<PlayerChatEvent, TextComponent> chatFormat;
private Function<PlayerChatEvent, RichMessage> chatFormat;
public PlayerChatEvent(Player sender, Collection<Player> 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<PlayerChatEvent, TextComponent> chatFormat) {
public void setChatFormat(Function<PlayerChatEvent, RichMessage> chatFormat) {
this.chatFormat = chatFormat;
}
@ -71,7 +71,7 @@ public class PlayerChatEvent extends CancellableEvent {
*
* @return the chat format which will be used
*/
public Function<PlayerChatEvent, TextComponent> getChatFormatFunction() {
public Function<PlayerChatEvent, RichMessage> getChatFormatFunction() {
return chatFormat;
}
}

View File

@ -7,13 +7,13 @@ import net.minestom.server.event.Event;
/**
* Called when a player interacts (right-click) with an entity
*/
public class PlayerInteractEvent extends Event {
public class PlayerEntityInteractEvent extends Event {
private Player player;
private Entity entityTarget;
private Player.Hand hand;
public PlayerInteractEvent(Player player, Entity entityTarget, Player.Hand hand) {
public PlayerEntityInteractEvent(Player player, Entity entityTarget, Player.Hand hand) {
this.player = player;
this.entityTarget = entityTarget;
this.hand = hand;

View File

@ -0,0 +1,71 @@
package net.minestom.server.event.player;
import net.minestom.server.entity.Player;
import net.minestom.server.event.Event;
import net.minestom.server.utils.validate.Check;
import java.util.UUID;
/**
* Called before the player initialization, it can be used to kick the player before any connection
* or to change his final username/uuid
*/
public class PlayerPreLoginEvent extends Event {
private Player player;
private String username;
private UUID playerUuid;
public PlayerPreLoginEvent(Player player, String username, UUID playerUuid) {
this.player = player;
this.username = username;
this.playerUuid = playerUuid;
}
/**
* Get the player who is trying to connect
*
* @return the player
*/
public Player getPlayer() {
return player;
}
/**
* Get the player username
*
* @return the player username
*/
public String getUsername() {
return username;
}
/**
* Change the player username
*
* @param username the new player username
*/
public void setUsername(String username) {
Check.notNull(username, "The player username cannot be null");
this.username = username;
}
/**
* Get the player uuid
*
* @return the player uuid
*/
public UUID getPlayerUuid() {
return playerUuid;
}
/**
* Change the player uuid
*
* @param playerUuid the new player uuid
*/
public void setPlayerUuid(UUID playerUuid) {
Check.notNull(playerUuid, "The player uuid cannot be null");
this.playerUuid = playerUuid;
}
}

View File

@ -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<NamespaceID> 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<NamespaceID> getValues() {
return values;
}
}

View File

@ -0,0 +1,9 @@
package net.minestom.server.gamedata.tags;
/**
* Meant only for parsing tag JSON
*/
public class TagContainer {
boolean replace;
String[] values;
}

View File

@ -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<NamespaceID, Tag> cache = new HashMap<>();
public TagManager() {
gson = new GsonBuilder()
.create();
}
/**
* Loads a tag with the given name. This method attempts to read from "data/&lt;name.domain&gt;/tags/&lt;tagType&gt;/&lt;name.path&gt;.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;
}
}

View File

@ -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;

View File

@ -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()) {

View File

@ -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;

View File

@ -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<String> lore;
private ArrayList<ColoredText> lore;
private Map<Enchantment, Short> enchantmentMap;
private Map<Enchantment, Short> 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<String> getLore() {
public ArrayList<ColoredText> 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<String> lore) {
public void setLore(ArrayList<ColoredText> lore) {
this.lore = lore;
}

View File

@ -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<PlayerChatEvent, TextComponent> formatFunction = playerChatEvent.getChatFormatFunction();
Function<PlayerChatEvent, RichMessage> 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;
}
}

View File

@ -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;
}

View File

@ -1,18 +1,39 @@
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;
if (!MathUtils.isBetween(slot, 0, 8)) {
if (!MathUtils.isBetween(packet.slot, 0, 8)) {
// Incorrect packet, ignore
return;
}
player.refreshHeldSlot(slot);
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 byte 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());
}
}
}

View File

@ -4,7 +4,7 @@ import net.minestom.server.entity.Entity;
import net.minestom.server.entity.LivingEntity;
import net.minestom.server.entity.Player;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.player.PlayerInteractEvent;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import net.minestom.server.network.packet.client.play.ClientInteractEntityPacket;
public class UseEntityListener {
@ -26,12 +26,12 @@ public class UseEntityListener {
EntityAttackEvent entityAttackEvent = new EntityAttackEvent(player, entity);
player.callEvent(EntityAttackEvent.class, entityAttackEvent);
} else if (type == ClientInteractEntityPacket.Type.INTERACT) {
PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(player, entity, packet.hand);
player.callEvent(PlayerInteractEvent.class, playerInteractEvent);
PlayerEntityInteractEvent playerEntityInteractEvent = new PlayerEntityInteractEvent(player, entity, packet.hand);
player.callEvent(PlayerEntityInteractEvent.class, playerEntityInteractEvent);
} else {
// TODO find difference with INTERACT
PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(player, entity, packet.hand);
player.callEvent(PlayerInteractEvent.class, playerInteractEvent);
PlayerEntityInteractEvent playerEntityInteractEvent = new PlayerEntityInteractEvent(player, entity, packet.hand);
player.callEvent(PlayerEntityInteractEvent.class, playerEntityInteractEvent);
}
}

View File

@ -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;

View File

@ -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<Player, Boolean> condition) {
public void broadcastMessage(ColoredText coloredText, Function<Player, Boolean> 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);
}
/**

View File

@ -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);

View File

@ -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());

View File

@ -26,7 +26,6 @@ public class ChatMessagePacket implements ServerPacket {
}
public enum Position {
CHAT,
SYSTEM_MESSAGE,
GAME_INFO

View File

@ -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<Entity> killer, Component message) {
public static CombatEventPacket death(Player player, Optional<Entity> 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;
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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();

View File

@ -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<Integer> 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);
}

View File

@ -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;
}
}

View File

@ -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<Player> 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));
}

View File

@ -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<String> lore = itemStack.getLore();
final ArrayList<ColoredText> 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());
}
});

View File

@ -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
*/

View File

@ -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<String> lore = new ArrayList<>(size);
ArrayList<ColoredText> 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);
}

View File

@ -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;
}
}