diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index e1f688c2e..583b94199 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -7,9 +7,7 @@ import net.minestom.server.advancements.FrameType; import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.benchmark.ThreadResult; import net.minestom.server.chat.ChatColor; -import net.minestom.server.chat.ChatHoverEvent; import net.minestom.server.chat.ColoredText; -import net.minestom.server.chat.RichMessage; import net.minestom.server.entity.*; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.event.entity.EntityAttackEvent; @@ -335,12 +333,6 @@ public class PlayerInit { WorldBorder worldBorder = instance.getWorldBorder(); worldBorder.setDiameter(30); - RichMessage richMessage = RichMessage.of(ColoredText.of(ChatColor.RED + "test item")); - richMessage.setHoverEvent(ChatHoverEvent.showEntity(player)); - richMessage.setInsertion("Test Insert"); - System.out.println(richMessage.toString()); - player.sendMessage(richMessage); - //EntityBoat entityBoat = new EntityBoat(player.getPosition()); //entityBoat.setInstance(player.getInstance()); //entityBoat.addPassenger(player); diff --git a/src/main/java/fr/themode/demo/commands/SimpleCommand.java b/src/main/java/fr/themode/demo/commands/SimpleCommand.java index fb15b9394..94b9facb4 100644 --- a/src/main/java/fr/themode/demo/commands/SimpleCommand.java +++ b/src/main/java/fr/themode/demo/commands/SimpleCommand.java @@ -10,8 +10,6 @@ import net.minestom.server.command.CommandSender; import net.minestom.server.entity.Player; import net.minestom.server.item.Material; -import java.util.Arrays; - public class SimpleCommand implements CommandProcessor { @Override public String getCommandName() { @@ -20,7 +18,7 @@ public class SimpleCommand implements CommandProcessor { @Override public String[] getAliases() { - return new String[0]; + return new String[]{"alias"}; } @Override @@ -66,8 +64,8 @@ public class SimpleCommand implements CommandProcessor { final Notification notification = new Notification(ColoredText.of(ChatColor.BRIGHT_GREEN + "Welcome to Minestom!"), FrameType.TASK, Material.APPLE); - NotificationCenter.send(notification, Arrays.asList(player)); - NotificationCenter.send(notification, Arrays.asList(player)); + NotificationCenter.send(notification, player); + NotificationCenter.send(notification, player); return true; } @@ -76,4 +74,9 @@ public class SimpleCommand implements CommandProcessor { public boolean hasAccess(Player player) { return true; } + + @Override + public String[] onWrite(String text) { + return new String[]{"Complete1", "Complete2"}; + } } diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 930fdd3a7..91386df48 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -27,8 +27,9 @@ import java.util.function.Consumer; public class CommandManager { + public static final String COMMAND_PREFIX = "/"; + private boolean running; - private String commandPrefix = "/"; private ConsoleSender consoleSender = new ConsoleSender(); @@ -43,9 +44,9 @@ public class CommandManager { while (running) { if (scanner.hasNext()) { String command = scanner.nextLine(); - if (!command.startsWith(commandPrefix)) + if (!command.startsWith(COMMAND_PREFIX)) continue; - command = command.replaceFirst(commandPrefix, ""); + command = command.replaceFirst(COMMAND_PREFIX, ""); execute(consoleSender, command); } } @@ -79,6 +80,23 @@ public class CommandManager { */ public void register(CommandProcessor commandProcessor) { this.commandProcessorMap.put(commandProcessor.getCommandName().toLowerCase(), commandProcessor); + // Register aliases + final String[] aliases = commandProcessor.getAliases(); + if (aliases != null && aliases.length > 0) { + for (String alias : aliases) { + this.commandProcessorMap.put(alias.toLowerCase(), commandProcessor); + } + } + } + + /** + * Get the command register by {@link #register(CommandProcessor)} + * + * @param commandName the command name + * @return the command associated with the name, null if not any + */ + public CommandProcessor getCommandProcessor(String commandName) { + return commandProcessorMap.get(commandName.toLowerCase()); } /** @@ -86,7 +104,7 @@ public class CommandManager { * * @param sender the sender of the command * @param command the raw command string (without the command prefix) - * @return + * @return true if the command hadn't been cancelled and has been successful */ public boolean execute(CommandSender sender, String command) { Check.notNull(sender, "Source cannot be null"); @@ -124,26 +142,6 @@ public class CommandManager { } } - /** - * Get the current command prefix (what should always be before the command name, ie: '/') - * - * @return the command prefix - */ - public String getCommandPrefix() { - return commandPrefix; - } - - /** - * Change the command prefix - *

- * This field can be changed half-way, but the client auto-completion still expect the '/' char - * - * @param commandPrefix the new command prefix - */ - public void setCommandPrefix(String commandPrefix) { - this.commandPrefix = commandPrefix; - } - /** * Get the console sender (which is used as a {@link CommandSender}) * @@ -219,12 +217,23 @@ public class CommandManager { } for (String simpleCommand : simpleCommands) { - // TODO server suggestion + // Server suggestion (ask_server) + { + DeclareCommandsPacket.Node tabNode = new DeclareCommandsPacket.Node(); + tabNode.flags = getFlag(NodeType.ARGUMENT, true, true, true); + tabNode.name = "tab_completion"; + tabNode.parser = "brigadier:string"; + tabNode.properties = packetWriter -> packetWriter.writeVarInt(2); // Greedy phrase + tabNode.children = new int[0]; + tabNode.suggestionsType = "minecraft:ask_server"; + + nodes.add(tabNode); + } + DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node(); literalNode.flags = getFlag(NodeType.LITERAL, true, false, false); literalNode.name = simpleCommand; - literalNode.children = new int[0]; - //literalNode.suggestionsType = "minecraft:ask_server"; + literalNode.children = new int[]{nodes.size() - 1}; rootChildren.add(nodes.size()); nodes.add(literalNode); @@ -272,7 +281,7 @@ public class CommandManager { final boolean isLast = i == arguments.length - 1; - List argumentNodes = toNodes(argument, isLast); + final List argumentNodes = toNodes(argument, isLast); for (DeclareCommandsPacket.Node node : argumentNodes) { final int childId = nodes.size(); @@ -501,11 +510,11 @@ public class CommandManager { byte result = (byte) type.mask; if (executable) { - result |= 0x4; + result |= 0x04; } if (redirect) { - result |= 0x8; + result |= 0x08; } if (suggestionType) { diff --git a/src/main/java/net/minestom/server/command/CommandProcessor.java b/src/main/java/net/minestom/server/command/CommandProcessor.java index c7caab8c2..e32e69d5a 100644 --- a/src/main/java/net/minestom/server/command/CommandProcessor.java +++ b/src/main/java/net/minestom/server/command/CommandProcessor.java @@ -43,4 +43,14 @@ public interface CommandProcessor { * @return true if the player has access to the command, false otherwise */ boolean hasAccess(Player player); + + /** + * Allow for tab auto completion, this is called everytime the player press a key in the chat + * + * @param text the whole player text + * @return the array containing all the suggestion for the current arg (split " ") + */ + default String[] onWrite(String text) { + return null; + } } diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index a66eb5f26..bafc7ba4c 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -233,7 +233,7 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { return false; } - EntityDamageEvent entityDamageEvent = new EntityDamageEvent(type, value, this); + EntityDamageEvent entityDamageEvent = new EntityDamageEvent(this, type, value); callCancellableEvent(EntityDamageEvent.class, entityDamageEvent, () -> { float damage = entityDamageEvent.getDamage(); diff --git a/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java b/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java index c675327f5..189917dff 100644 --- a/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java @@ -1,6 +1,5 @@ package net.minestom.server.event.entity; -import net.minestom.server.entity.Entity; import net.minestom.server.entity.LivingEntity; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.event.CancellableEvent; @@ -10,17 +9,28 @@ import net.minestom.server.event.CancellableEvent; */ public class EntityDamageEvent extends CancellableEvent { - private DamageType damageType; + private final LivingEntity entity; + private final DamageType damageType; private float damage; - private LivingEntity entity; - public EntityDamageEvent(DamageType damageType, float damage, LivingEntity entity) { + public EntityDamageEvent(LivingEntity entity, DamageType damageType, float damage) { + this.entity = entity; this.damageType = damageType; this.damage = damage; - this.entity = entity; } /** + * Get the damaged entity + * + * @return the damaged entity + */ + public LivingEntity getEntity() { + return entity; + } + + /** + * Get the damage type + * * @return the damage type */ public DamageType getDamageType() { @@ -28,6 +38,8 @@ public class EntityDamageEvent extends CancellableEvent { } /** + * Get the damage amount + * * @return the damage amount */ public float getDamage() { @@ -35,13 +47,11 @@ public class EntityDamageEvent extends CancellableEvent { } /** + * Change the damage amount + * * @param damage the new damage amount */ public void setDamage(float damage) { this.damage = damage; } - - public LivingEntity getEntity() { - return entity; - } } diff --git a/src/main/java/net/minestom/server/listener/ChatMessageListener.java b/src/main/java/net/minestom/server/listener/ChatMessageListener.java index 2b58fb1cf..470b09767 100644 --- a/src/main/java/net/minestom/server/listener/ChatMessageListener.java +++ b/src/main/java/net/minestom/server/listener/ChatMessageListener.java @@ -24,7 +24,7 @@ public class ChatMessageListener { public static void listener(ClientChatMessagePacket packet, Player player) { String message = packet.message; - String cmdPrefix = COMMAND_MANAGER.getCommandPrefix(); + final String cmdPrefix = CommandManager.COMMAND_PREFIX; if (message.startsWith(cmdPrefix)) { // The message is a command message = message.replaceFirst(cmdPrefix, ""); diff --git a/src/main/java/net/minestom/server/listener/TabCompleteListener.java b/src/main/java/net/minestom/server/listener/TabCompleteListener.java index 57c21e265..46856580c 100644 --- a/src/main/java/net/minestom/server/listener/TabCompleteListener.java +++ b/src/main/java/net/minestom/server/listener/TabCompleteListener.java @@ -1,13 +1,63 @@ package net.minestom.server.listener; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandManager; +import net.minestom.server.command.CommandProcessor; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.client.play.ClientTabCompletePacket; +import net.minestom.server.network.packet.server.play.TabCompletePacket; + +import java.util.regex.Pattern; public class TabCompleteListener { + private static final CommandManager COMMAND_MANAGER = MinecraftServer.getCommandManager(); + public static void listener(ClientTabCompletePacket packet, Player player) { - // TODO when is it called? - System.out.println("text: " + packet.text); + final String text = packet.text; + + final String[] split = packet.text.split(Pattern.quote(" ")); + + final String commandName = split[0].replaceFirst(CommandManager.COMMAND_PREFIX, ""); + + // Tab complete for CommandProcessor + final CommandProcessor commandProcessor = COMMAND_MANAGER.getCommandProcessor(commandName); + if (commandProcessor != null) { + final boolean endSpace = text.endsWith(" "); + + int start; + + if (endSpace) { + start = text.length(); + } else { + final String lastArg = split[split.length - 1]; + start = text.indexOf(lastArg); + } + + final String[] matches = commandProcessor.onWrite(text); + + if (matches != null && matches.length > 0) { + TabCompletePacket tabCompletePacket = new TabCompletePacket(); + tabCompletePacket.transactionId = packet.transactionId; + tabCompletePacket.start = start; + tabCompletePacket.length = 20; + + TabCompletePacket.Match[] matchesArray = new TabCompletePacket.Match[matches.length]; + for (int i = 0; i < matchesArray.length; i++) { + TabCompletePacket.Match match = new TabCompletePacket.Match(); + match.match = matches[i]; + matchesArray[i] = match; + } + + tabCompletePacket.matches = matchesArray; + + player.getPlayerConnection().sendPacket(tabCompletePacket); + } + } + + // TODO tab complete for Command (TabArgument) + + } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java index 38f808d2d..4dcc96b3c 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TabCompletePacket.java @@ -1,5 +1,6 @@ package net.minestom.server.network.packet.server.play; +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; @@ -22,7 +23,7 @@ public class TabCompletePacket implements ServerPacket { writer.writeSizedString(match.match); writer.writeBoolean(match.hasTooltip); if (match.hasTooltip) - writer.writeSizedString(match.tooltip); + writer.writeSizedString(match.tooltip.toString()); } } @@ -34,7 +35,7 @@ public class TabCompletePacket implements ServerPacket { public static class Match { public String match; public boolean hasTooltip; - public String tooltip; // Chat + public ColoredText tooltip; } }