This commit is contained in:
Felix Cravic 2020-04-05 10:15:21 +02:00
parent 4dd3bd607d
commit 3b06c5b9ef
20 changed files with 450 additions and 55 deletions

View File

@ -11,7 +11,6 @@ sourceCompatibility = 1.11
repositories {
mavenCentral()
maven { url 'https://jitpack.io'}
maven { url "https://libraries.minecraft.net"}
}
@ -36,6 +35,7 @@ dependencies {
implementation 'com.github.LynnOwens:starlite:9971b899f7'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
compile 'com.mojang:brigadier:1.0.17'
implementation 'com.github.TheMode:CommandBuilder:e70e2bd6b7'
}

View File

@ -1,13 +1,14 @@
package fr.themode.demo;
import com.mojang.brigadier.CommandDispatcher;
import fr.themode.demo.blocks.StoneBlock;
import fr.themode.demo.blocks.UpdatableBlockDemo;
import fr.themode.demo.commands.HealthCommand;
import fr.themode.demo.commands.SimpleCommand;
import fr.themode.minestom.MinecraftServer;
import fr.themode.minestom.command.CommandManager;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.item.Material;
import fr.themode.minestom.net.packet.server.play.DeclareRecipesPacket;
import fr.themode.minestom.recipe.RecipeManager;
import fr.themode.minestom.recipe.ShapelessRecipe;
@ -23,14 +24,14 @@ public class Main {
blockManager.registerCustomBlock(new UpdatableBlockDemo());
CommandManager commandManager = MinecraftServer.getCommandManager();
CommandDispatcher<Player> dispatcher = commandManager.getDispatcher();
// TODO register command
commandManager.register(new HealthCommand());
commandManager.register(new SimpleCommand());
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
ShapelessRecipe shapelessRecipe = new ShapelessRecipe("test", "groupname");
shapelessRecipe.setResult(new ItemStack(50, (byte) 1));
shapelessRecipe.setResult(new ItemStack(Material.STONE, (byte) 1));
DeclareRecipesPacket.Ingredient ingredient = new DeclareRecipesPacket.Ingredient();
ingredient.items = new ItemStack[]{new ItemStack(2, (byte) 3)};
ingredient.items = new ItemStack[]{new ItemStack(Material.STONE, (byte) 3)};
shapelessRecipe.addIngredient(ingredient);
recipeManager.addRecipe(shapelessRecipe);

View File

@ -3,6 +3,7 @@ package fr.themode.demo;
import fr.themode.demo.entity.ChickenCreature;
import fr.themode.demo.generator.ChunkGeneratorDemo;
import fr.themode.minestom.MinecraftServer;
import fr.themode.minestom.command.CommandManager;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.entity.GameMode;
@ -50,7 +51,7 @@ public class PlayerInit {
Vector velocity = player.getPosition().clone().getDirection().multiply(4);
velocity.setY(3.5f);
target.setVelocity(velocity, 150);
target.damage(2);
target.damage(1);
player.sendMessage("ATTACK");
}
});
@ -69,8 +70,13 @@ public class PlayerInit {
});
player.setEventCallback(PlayerUseItemEvent.class, event -> {
System.out.println("CALLBACK EVENT");
player.setEventCallback(PlayerChatEvent.class, event -> {
String message = event.getMessage();
System.out.println("Chat: " + message);
CommandManager commandManager = MinecraftServer.getCommandManager();
boolean result = commandManager.execute(event.getSender(), message);
if (!result)
player.sendMessage("No command found");
});
player.setEventCallback(PickupItemEvent.class, event -> {

View File

@ -0,0 +1,78 @@
package fr.themode.demo.commands;
import fr.themode.command.Arguments;
import fr.themode.command.Command;
import fr.themode.command.arguments.Argument;
import fr.themode.command.arguments.ArgumentType;
import fr.themode.command.arguments.number.ArgumentNumber;
import fr.themode.minestom.entity.Player;
public class HealthCommand extends Command<Player> {
public HealthCommand() {
super("health", "h", "healthbar");
setCondition(this::condition);
setDefaultExecutor(this::defaultExecutor);
Argument arg0 = ArgumentType.Word("mode").from("set", "add");
Argument arg1 = ArgumentType.Integer("value").between(0, 100);
addCallback(this::modeCallback, arg0);
addCallback(this::valueCallback, arg1);
addSyntax(this::execute, arg0);
addSyntax(this::execute2, arg0, arg1);
}
private boolean condition(Player player) {
// Your custom condition, called no matter the syntax used
boolean hasPerm = true;
if (!hasPerm) {
player.sendMessage("You do not have permission !");
return false;
}
return true;
}
private void defaultExecutor(Player player, Arguments args) {
player.sendMessage("Correct usage: health [set/add] [number]");
}
private void modeCallback(Player player, String value, int error) {
System.out.println("SYNTAX ERROR: '" + value + "' should be replaced by 'set' or 'add'");
}
private void valueCallback(Player player, String value, int error) {
switch (error) {
case ArgumentNumber.NOT_NUMBER_ERROR:
player.sendMessage("SYNTAX ERROR: '" + value + "' isn't a number!");
break;
case ArgumentNumber.RANGE_ERROR:
player.sendMessage("SYNTAX ERROR: " + value + " is not between 0 and 100");
break;
}
}
private void execute(Player player, Arguments args) {
player.sendMessage("/health " + args.getWord("mode") + " [Integer]");
}
private void execute2(Player player, Arguments args) {
String mode = args.getWord("mode");
int value = args.getInteger("value");
switch (mode.toLowerCase()) {
case "set":
player.setHealth(value);
break;
case "add":
player.setHealth(player.getHealth() + value);
break;
}
player.sendMessage("You have now " + player.getHealth() + " health");
}
}

View File

@ -0,0 +1,17 @@
package fr.themode.demo.commands;
import fr.themode.minestom.command.CommandProcessor;
import fr.themode.minestom.entity.Player;
public class SimpleCommand implements CommandProcessor {
@Override
public String getCommandName() {
return "test";
}
@Override
public boolean process(Player player, String command, String[] args) {
player.sendMessage("You tried the sample command!");
return true;
}
}

View File

@ -0,0 +1,170 @@
package fr.themode.minestom.command;
import fr.themode.command.Command;
import fr.themode.command.CommandDispatcher;
import fr.themode.command.CommandSyntax;
import fr.themode.command.arguments.*;
import fr.themode.command.arguments.number.ArgumentDouble;
import fr.themode.command.arguments.number.ArgumentFloat;
import fr.themode.command.arguments.number.ArgumentInteger;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.net.packet.server.play.DeclareCommandsPacket;
import fr.themode.minestom.utils.ArrayUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CommandManager {
private String commandPrefix = "/";
private CommandDispatcher<Player> dispatcher = new CommandDispatcher<>();
private Map<String, CommandProcessor> commandProcessorMap = new HashMap<>();
private DeclareCommandsPacket declareCommandsPacket = new DeclareCommandsPacket();
public void register(Command<Player> command) {
this.dispatcher.register(command);
}
public void register(CommandProcessor commandProcessor) {
this.commandProcessorMap.put(commandProcessor.getCommandName().toLowerCase(), commandProcessor);
}
public boolean execute(Player source, String command) {
if (source == null)
throw new NullPointerException("Source cannot be null");
if (command == null)
throw new NullPointerException("Command string cannot be null");
try {
this.dispatcher.execute(source, command);
return true;
} catch (NullPointerException e) {
String[] splitted = command.split(" ");
String commandName = splitted[0];
CommandProcessor commandProcessor = commandProcessorMap.get(commandName.toLowerCase());
if (commandProcessor == null)
return false;
String[] args = command.substring(command.indexOf(" ") + 1).split(" ");
return commandProcessor.process(source, commandName, args);
}
}
public String getCommandPrefix() {
return commandPrefix;
}
public void setCommandPrefix(String commandPrefix) {
this.commandPrefix = commandPrefix;
}
public DeclareCommandsPacket getDeclareCommandsPacket() {
return declareCommandsPacket;
}
private void refreshPacket() {
List<DeclareCommandsPacket.Node> nodes = new ArrayList<>();
ArrayList<Integer> rootChildren = new ArrayList<>();
for (Command<Player> command : dispatcher.getCommands()) {
ArrayList<Integer> cmdChildren = new ArrayList<>();
String name = command.getName();
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
literalNode.flags = 0b1;
literalNode.name = name;
rootChildren.add(nodes.size());
nodes.add(literalNode);
for (CommandSyntax syntax : command.getSyntaxes()) {
ArrayList<Integer> argChildren = cmdChildren;
for (Argument argument : syntax.getArguments()) {
DeclareCommandsPacket.Node argumentNode = toNode(argument);
argChildren.add(nodes.size());
nodes.add(argumentNode);
System.out.println("size: " + argChildren.size());
argumentNode.children = ArrayUtils.toArray(argChildren);
argChildren = new ArrayList<>();
}
}
System.out.println("test " + cmdChildren.size() + " : " + cmdChildren.get(0));
literalNode.children = ArrayUtils.toArray(cmdChildren);
}
DeclareCommandsPacket.Node rootNode = new DeclareCommandsPacket.Node();
rootNode.flags = 0;
rootNode.children = ArrayUtils.toArray(rootChildren);
nodes.add(rootNode);
declareCommandsPacket.nodes = nodes.toArray(new DeclareCommandsPacket.Node[nodes.size()]);
declareCommandsPacket.rootIndex = nodes.size() - 1;
}
private DeclareCommandsPacket.Node toNode(Argument argument) {
DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node();
argumentNode.flags = 0b1010;
argumentNode.name = argument.getId();
if (argument instanceof ArgumentBoolean) {
argumentNode.parser = "brigadier:bool";
argumentNode.properties = packetWriter -> packetWriter.writeByte((byte) 0);
} else if (argument instanceof ArgumentDouble) {
ArgumentDouble argumentDouble = (ArgumentDouble) argument;
argumentNode.parser = "brigadier:double";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte((byte) 0b11);
packetWriter.writeDouble(argumentDouble.min);
packetWriter.writeDouble(argumentDouble.max);
};
} else if (argument instanceof ArgumentFloat) {
ArgumentFloat argumentFloat = (ArgumentFloat) argument;
argumentNode.parser = "brigadier:float";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte((byte) 0b11);
packetWriter.writeFloat(argumentFloat.min);
packetWriter.writeFloat(argumentFloat.max);
};
} else if (argument instanceof ArgumentInteger) {
ArgumentInteger argumentInteger = (ArgumentInteger) argument;
argumentNode.parser = "brigadier:integer";
argumentNode.properties = packetWriter -> {
packetWriter.writeByte((byte) 0b11);
packetWriter.writeInt(argumentInteger.min);
packetWriter.writeInt(argumentInteger.max);
};
} else if (argument instanceof ArgumentWord) {
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(0); // Single word
};
} else if (argument instanceof ArgumentString) {
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(1); // Quotable phrase
};
} else if (argument instanceof ArgumentStringArray) {
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
packetWriter.writeVarInt(2); // Greedy phrase
};
}
return argumentNode;
}
}

View File

@ -0,0 +1,10 @@
package fr.themode.minestom.command;
import fr.themode.minestom.entity.Player;
public interface CommandProcessor {
String getCommandName();
boolean process(Player player, String command, String[] args);
}

View File

@ -1,6 +1,5 @@
package fr.themode.minestom.entity;
import fr.themode.minestom.event.DeathEvent;
import fr.themode.minestom.net.packet.server.play.*;
import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.utils.ChunkUtils;
@ -83,11 +82,10 @@ public abstract class EntityCreature extends LivingEntity {
@Override
public void kill() {
this.isDead = true; // So the entity isn't killed over and over again
triggerStatus((byte) 3); // Start death animation status
scheduleRemove(1000); // Needed for proper death animation (wait for it to finish before destroying the entity)
DeathEvent deathEvent = new DeathEvent();
callEvent(DeathEvent.class, deathEvent);
super.kill();
// Needed for proper death animation (wait for it to finish before destroying the entity)
scheduleRemove(1000);
}
@Override

View File

@ -3,6 +3,7 @@ package fr.themode.minestom.entity;
import com.github.simplenet.packet.Packet;
import fr.themode.minestom.collision.BoundingBox;
import fr.themode.minestom.entity.property.Attribute;
import fr.themode.minestom.event.DeathEvent;
import fr.themode.minestom.event.PickupItemEvent;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.item.ItemStack;
@ -40,7 +41,14 @@ public abstract class LivingEntity extends Entity {
this(entityType, new Position());
}
public abstract void kill();
public void kill() {
System.out.println("KILL");
this.isDead = true; // So the entity isn't killed over and over again
setHealth(0);
triggerStatus((byte) 3); // Start death animation status
DeathEvent deathEvent = new DeathEvent();
callEvent(DeathEvent.class, deathEvent);
}
@Override
public void update() {
@ -111,7 +119,7 @@ public abstract class LivingEntity extends Entity {
health = Math.min(health, getMaxHealth());
this.health = health;
if (this.health <= 0) {
if (this.health <= 0 && !isDead) {
kill();
}
}

View File

@ -93,6 +93,8 @@ public class Player extends LivingEntity {
playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
refreshHealth();
refreshAbilities();
this.settings = new PlayerSettings();
this.inventory = new PlayerInventory(this);
@ -333,14 +335,8 @@ public class Player extends LivingEntity {
@Override
public void kill() {
this.isDead = true;
super.kill();
refreshIsDead(true);
EntityStatusPacket entityStatusPacket = new EntityStatusPacket();
entityStatusPacket.entityId = getEntityId();
entityStatusPacket.status = 3; // Death sound/animation
sendPacketToViewers(entityStatusPacket);
DeathEvent deathEvent = new DeathEvent();
callEvent(DeathEvent.class, deathEvent);
}
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {

View File

@ -0,0 +1,26 @@
package fr.themode.minestom.event;
import fr.themode.minestom.entity.Player;
public class PlayerCommandEvent extends CancellableEvent {
private Player player;
private String command;
public PlayerCommandEvent(Player player, String command) {
this.player = player;
this.command = command;
}
public Player getPlayer() {
return player;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
}

View File

@ -3,8 +3,10 @@ package fr.themode.minestom.listener;
import club.thectm.minecraft.text.*;
import fr.themode.minestom.MinecraftServer;
import fr.themode.minestom.chat.Chat;
import fr.themode.minestom.command.CommandManager;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.event.PlayerChatEvent;
import fr.themode.minestom.event.PlayerCommandEvent;
import fr.themode.minestom.net.packet.client.play.ClientChatMessagePacket;
import java.util.function.Function;
@ -14,6 +16,22 @@ public class ChatMessageListener {
public static void listener(ClientChatMessagePacket packet, Player player) {
String message = Chat.uncoloredLegacyText(packet.message);
CommandManager commandManager = MinecraftServer.getCommandManager();
String cmdPrefix = commandManager.getCommandPrefix();
if (message.startsWith(cmdPrefix)) {
// The message is a command
message = message.replaceFirst(cmdPrefix, "");
PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(player, message);
player.callCancellableEvent(PlayerCommandEvent.class, playerCommandEvent, () -> {
commandManager.execute(player, playerCommandEvent.getCommand());
});
// Do not call chat event
return;
}
PlayerChatEvent playerChatEvent = new PlayerChatEvent(player, MinecraftServer.getConnectionManager().getOnlinePlayers(), message);
// Default format

View File

@ -0,0 +1,15 @@
package fr.themode.minestom.listener;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.net.packet.client.play.ClientCraftRecipeRequest;
import fr.themode.minestom.net.packet.server.play.CraftRecipeResponse;
public class RecipeListener {
public static void listener(ClientCraftRecipeRequest packet, Player player) {
CraftRecipeResponse recipeResponse = new CraftRecipeResponse();
recipeResponse.windowId = packet.windowId;
recipeResponse.recipe = packet.recipe;
player.getPlayerConnection().sendPacket(recipeResponse);
}
}

View File

@ -33,6 +33,7 @@ public class PacketListenerManager {
addListener(ClientStatusPacket.class, StatusListener::listener);
addListener(ClientSettingsPacket.class, SettingsListener::listener);
addListener(ClientCreativeInventoryActionPacket.class, CreativeInventoryActionListener::listener);
addListener(ClientCraftRecipeRequest.class, RecipeListener::listener);
}
public <T extends ClientPlayPacket> void process(T packet, Player player) {

View File

@ -21,6 +21,7 @@ public class ClientPlayPacketsHandler extends ClientPacketsHandler {
register(0x12, ClientPlayerPositionAndLookPacket.class);
register(0x13, ClientPlayerLookPacket.class);
register(0x14, ClientPlayerPacket.class);
register(0x18, ClientCraftRecipeRequest.class);
register(0x19, ClientPlayerAbilitiesPacket.class);
register(0x1A, ClientPlayerDiggingPacket.class);
register(0x1B, ClientEntityActionPacket.class);

View File

@ -1,6 +1,5 @@
package fr.themode.minestom.net.packet.client.login;
import com.mojang.brigadier.CommandDispatcher;
import fr.themode.minestom.MinecraftServer;
import fr.themode.minestom.command.CommandManager;
import fr.themode.minestom.entity.GameMode;
@ -74,8 +73,6 @@ public class LoginStartPacket implements ClientPreplayPacket {
// TODO send server difficulty
// TODO player abilities
SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket();
spawnPositionPacket.x = 0;
@ -99,35 +96,13 @@ public class LoginStartPacket implements ClientPreplayPacket {
{
CommandManager commandManager = MinecraftServer.getCommandManager();
CommandDispatcher<Player> dispatcher = commandManager.getDispatcher();
String[] usages = dispatcher.getAllUsage(dispatcher.getRoot(), player, true);
for (String usage : usages) {
System.out.println("USAGE: " + usage);
}
DeclareCommandsPacket declareCommandsPacket = commandManager.getDeclareCommandsPacket();
// FIXME
//connection.sendPacket(declareCommandsPacket);
}
DeclareCommandsPacket declareCommandsPacket = new DeclareCommandsPacket();
DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node();
argumentNode.flags = 0b1010;
argumentNode.children = new int[0];
argumentNode.name = "arg name";
argumentNode.parser = "minecraft:nbt_path";
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
literalNode.flags = 0b1;
literalNode.children = new int[]{2};
literalNode.name = "hey";
DeclareCommandsPacket.Node rootNode = new DeclareCommandsPacket.Node();
rootNode.flags = 0;
rootNode.children = new int[]{1};
declareCommandsPacket.nodes = new DeclareCommandsPacket.Node[]{rootNode, literalNode, argumentNode};
declareCommandsPacket.rootIndex = 0;
connection.sendPacket(declareCommandsPacket);
{
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
DeclareRecipesPacket declareRecipesPacket = recipeManager.getDeclareRecipesPacket();

View File

@ -0,0 +1,21 @@
package fr.themode.minestom.net.packet.client.play;
import fr.themode.minestom.net.packet.PacketReader;
import fr.themode.minestom.net.packet.client.ClientPlayPacket;
public class ClientCraftRecipeRequest extends ClientPlayPacket {
public byte windowId;
public String recipe;
public boolean makeAll;
@Override
public void read(PacketReader reader, Runnable callback) {
reader.readByte(value -> windowId = value);
reader.readSizedString((string, length) -> recipe = string);
reader.readBoolean(value -> {
makeAll = value;
callback.run();
});
}
}

View File

@ -0,0 +1,22 @@
package fr.themode.minestom.net.packet.server.play;
import fr.themode.minestom.net.packet.PacketWriter;
import fr.themode.minestom.net.packet.server.ServerPacket;
import fr.themode.minestom.net.packet.server.ServerPacketIdentifier;
public class CraftRecipeResponse implements ServerPacket {
public byte windowId;
public String recipe;
@Override
public void write(PacketWriter writer) {
writer.writeByte(windowId);
writer.writeSizedString(recipe);
}
@Override
public int getId() {
return ServerPacketIdentifier.CRAFT_RECIPE_RESPONSE;
}
}

View File

@ -0,0 +1,22 @@
package fr.themode.minestom.net.packet.server.play;
import fr.themode.minestom.net.packet.PacketWriter;
import fr.themode.minestom.net.packet.server.ServerPacket;
import fr.themode.minestom.net.packet.server.ServerPacketIdentifier;
public class PluginMessagePacket implements ServerPacket {
public String channel;
public byte[] data;
@Override
public void write(PacketWriter writer) {
writer.writeSizedString(channel);
writer.writeBytes(data);
}
@Override
public int getId() {
return ServerPacketIdentifier.PLUGIN_MESSAGE;
}
}

View File

@ -1,5 +1,7 @@
package fr.themode.minestom.utils;
import java.util.ArrayList;
public class ArrayUtils {
public static byte[] concenateByteArrays(byte[]... arrays) {
@ -51,4 +53,12 @@ public class ArrayUtils {
return result;
}
public static int[] toArray(ArrayList<Integer> list) {
int[] array = new int[list.size()];
for (int i = 0; i < array.length; i++) {
array[i] = list.get(i);
}
return array;
}
}