diff --git a/build.gradle b/build.gradle index 95d054a0a..1d8b2b3ec 100644 --- a/build.gradle +++ b/build.gradle @@ -35,17 +35,15 @@ dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' // https://mvnrepository.com/artifact/io.netty/netty-all - api group: 'io.netty', name: 'netty-all', version: '4.1.48.Final' + api group: 'io.netty', name: 'netty-all', version: '4.1.50.Final' - api 'com.github.jhg023:Pbbl:1.0.1' + api 'com.github.jhg023:Pbbl:1.0.2' // https://mvnrepository.com/artifact/it.unimi.dsi/fastutil api group: 'it.unimi.dsi', name: 'fastutil', version: '8.3.0' - api 'com.github.Querz:NBT:4.1' - // https://mvnrepository.com/artifact/com.google.code.gson/gson - api group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + api group: 'com.google.code.gson', name: 'gson', version: '2.8.6' api 'com.github.TheMode:CommandBuilder:f893cfbfe4' @@ -66,4 +64,8 @@ dependencies { 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' + } diff --git a/src/main/java/fr/themode/demo/MainDemo.java b/src/main/java/fr/themode/demo/MainDemo.java new file mode 100644 index 000000000..08c86e761 --- /dev/null +++ b/src/main/java/fr/themode/demo/MainDemo.java @@ -0,0 +1,71 @@ +package fr.themode.demo; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.event.player.PlayerLoginEvent; +import net.minestom.server.event.player.PlayerSpawnEvent; +import net.minestom.server.instance.*; +import net.minestom.server.instance.batch.ChunkBatch; +import net.minestom.server.instance.block.Block; +import net.minestom.server.network.ConnectionManager; +import net.minestom.server.utils.Position; + +import java.util.Arrays; +import java.util.List; + +public class MainDemo { + + public static void main(String[] args) { + // Initialization + MinecraftServer minecraftServer = MinecraftServer.init(); + + InstanceManager instanceManager = MinecraftServer.getInstanceManager(); + // Create the instance + InstanceContainer instanceContainer = instanceManager.createInstanceContainer(); + // Set the ChunkGenerator + instanceContainer.setChunkGenerator(new GeneratorDemo()); + // Enable the auto chunk loading (when players come close) + instanceContainer.enableAutoChunkLoad(true); + + // Add event listeners + ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); + connectionManager.addPlayerInitialization(player -> { + // Set the spawning instance + player.addEventCallback(PlayerLoginEvent.class, event -> { + event.setSpawningInstance(instanceContainer); + }); + + // Teleport the player at spawn + player.addEventCallback(PlayerSpawnEvent.class, event -> { + player.teleport(new Position(0, 45, 0)); + }); + }); + + // Start the server + minecraftServer.start("localhost", 55555); + } + + private static class GeneratorDemo extends ChunkGenerator { + + @Override + public void generateChunkData(ChunkBatch batch, int chunkX, int chunkZ) { + // Set chunk blocks + for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++) + for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { + for (byte y = 0; y < 40; y++) { + batch.setBlock(x, y, z, Block.STONE); + } + } + } + + @Override + public void fillBiomes(Biome[] biomes, int chunkX, int chunkZ) { + Arrays.fill(biomes, Biome.PLAINS); + } + + @Override + public List getPopulators() { + return null; + } + } + +} diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index c477ae08b..019e9386a 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -4,6 +4,7 @@ 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.entity.*; @@ -39,6 +40,11 @@ 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; @@ -95,16 +101,15 @@ public class PlayerInit { 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 += "\n"; } - // if (benchmarkMessage.length() > 0) - // System.out.println(benchmarkMessage); for (Player player : connectionManager.getOnlinePlayers()) { player.sendHeaderFooter("RAM USAGE: " + ramUsage + " MB", benchmarkMessage, '&'); } } - }, new UpdateOption(5, TimeUnit.TICK)); + }, new UpdateOption(10, TimeUnit.TICK)); connectionManager.addPacketConsumer((player, packetController, packet) -> { // Listen to all received packet @@ -137,10 +142,10 @@ public class PlayerInit { return; if (event.getBlockId() == Block.STONE.getBlockId()) { - event.setCustomBlockId((short) 2); // custom stone block + event.setCustomBlock((short) 2); // custom stone block } if (event.getBlockId() == Block.TORCH.getBlockId()) { - event.setCustomBlockId((short) 3); // custom torch block + event.setCustomBlock((short) 3); // custom torch block } /*for (Player p : player.getInstance().getPlayers()) { @@ -150,6 +155,7 @@ public class PlayerInit { ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); chickenCreature.setInstance(player.getInstance()); + chickenCreature.setAttribute(Attribute.MOVEMENT_SPEED, 0.4f); /*FakePlayer fakePlayer = new FakePlayer(UUID.randomUUID(), "test"); fakePlayer.addEventCallback(EntityDeathEvent.class, e -> { @@ -211,19 +217,24 @@ public class PlayerInit { scoreboard.setTitle("test");*/ }); + player.addEventCallback(PlayerSkinInitEvent.class, event -> { + event.setSkin(skin); + }); + player.addEventCallback(PlayerSpawnEvent.class, event -> { player.setGameMode(GameMode.CREATIVE); - player.teleport(new Position(0, 45, 0)); + player.teleport(new Position(0, 41f, 0)); player.setGlowing(true); ItemStack item = new ItemStack(Material.STONE_SWORD, (byte) 1); item.setDisplayName("Item name"); item.getLore().add("a lore line"); - item.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); - item.setEnchantment(Enchantment.SHARPNESS, (short) 2); + item.addItemFlags(ItemFlag.HIDE_ENCHANTS); + item.setEnchantment(Enchantment.SHARPNESS, (short) 50); player.getInventory().addItemStack(item); + inventory.addItemStack(item.clone()); player.openInventory(inventory); player.getInventory().addItemStack(new ItemStack(Material.STONE, (byte) 100)); @@ -256,7 +267,7 @@ public class PlayerInit { }); player.addEventCallback(PlayerRespawnEvent.class, event -> { - event.setRespawnPosition(new Position(0f, 45f, 0f)); + event.setRespawnPosition(new Position(0f, 41f, 0f)); }); player.addEventCallback(PlayerUseItemEvent.class, useEvent -> { @@ -301,7 +312,7 @@ public class PlayerInit { public static ResponseDataConsumer getResponseDataConsumer() { return (playerConnection, responseData) -> { - responseData.setMaxPlayer(100); + responseData.setMaxPlayer(0); responseData.setOnline(MinecraftServer.getConnectionManager().getOnlinePlayers().size()); responseData.addPlayer("A name", UUID.randomUUID()); responseData.addPlayer("Could be some message", UUID.randomUUID()); diff --git a/src/main/java/fr/themode/demo/commands/DimensionCommand.java b/src/main/java/fr/themode/demo/commands/DimensionCommand.java index 2cd0e973b..7eca98c89 100644 --- a/src/main/java/fr/themode/demo/commands/DimensionCommand.java +++ b/src/main/java/fr/themode/demo/commands/DimensionCommand.java @@ -2,6 +2,7 @@ package fr.themode.demo.commands; import net.minestom.server.MinecraftServer; 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 net.minestom.server.world.Dimension; @@ -20,22 +21,27 @@ public class DimensionCommand implements CommandProcessor { } @Override - public boolean process(Player player, String command, String[] args) { + public boolean process(CommandSender sender, String command, String[] args) { + + if (!(sender instanceof Player)) + return false; + Player player = (Player) sender; + Instance instance = player.getInstance(); Dimension targetDimension = Dimension.NETHER; - if(instance.getDimension() == Dimension.NETHER) { + if (instance.getDimension() == Dimension.NETHER) { targetDimension = Dimension.OVERWORLD; } Dimension finalTargetDimension = targetDimension; Optional targetInstance = MinecraftServer.getInstanceManager().getInstances().stream().filter(in -> in.getDimension() == finalTargetDimension).findFirst(); - if(targetInstance.isPresent()) { - player.sendMessage("You were in "+instance.getDimension()); + if (targetInstance.isPresent()) { + player.sendMessage("You were in " + instance.getDimension()); player.setInstance(targetInstance.get()); - player.sendMessage("You are now in "+targetDimension); + player.sendMessage("You are now in " + targetDimension); } else { - player.sendMessage("Could not find instance with dimension "+targetDimension); + player.sendMessage("Could not find instance with dimension " + targetDimension); } return true; diff --git a/src/main/java/fr/themode/demo/commands/GamemodeCommand.java b/src/main/java/fr/themode/demo/commands/GamemodeCommand.java index d90eb186f..77d8f4df4 100644 --- a/src/main/java/fr/themode/demo/commands/GamemodeCommand.java +++ b/src/main/java/fr/themode/demo/commands/GamemodeCommand.java @@ -4,6 +4,7 @@ import fr.themode.command.Arguments; import fr.themode.command.Command; import fr.themode.command.arguments.Argument; import fr.themode.command.arguments.ArgumentType; +import net.minestom.server.command.CommandSender; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Player; @@ -12,7 +13,7 @@ import java.util.Optional; /** * Command that make a player change gamemode */ -public class GamemodeCommand extends Command { +public class GamemodeCommand extends Command { public GamemodeCommand() { super("gamemode", "g", "gm"); @@ -35,11 +36,13 @@ public class GamemodeCommand extends Command { addSyntax(this::executeOnOther, player, mode); } - private void usage(Player player, Arguments arguments) { - player.sendMessage("Usage: /gamemode [player] "); + private void usage(CommandSender sender, Arguments arguments) { + sender.sendMessage("Usage: /gamemode [player] "); } - private void executeOnSelf(Player player, Arguments arguments) { + private void executeOnSelf(CommandSender sender, Arguments arguments) { + Player player = (Player) sender; + String gamemodeName = arguments.getWord("mode"); GameMode mode = GameMode.valueOf(gamemodeName.toUpperCase()); assert mode != null; // mode is not supposed to be null, because gamemodeName will be valid @@ -47,7 +50,9 @@ public class GamemodeCommand extends Command { player.sendMessage("You are now playing in " + gamemodeName); } - private void executeOnOther(Player player, Arguments arguments) { + private void executeOnOther(CommandSender sender, Arguments arguments) { + Player player = (Player) sender; + String gamemodeName = arguments.getWord("mode"); String targetName = arguments.getWord("player"); GameMode mode = GameMode.valueOf(gamemodeName.toUpperCase()); @@ -61,11 +66,15 @@ public class GamemodeCommand extends Command { } } - private void gameModeCallback(Player player, String gamemode, int error) { - player.sendMessage("'" + gamemode + "' is not a valid gamemode!"); + private void gameModeCallback(CommandSender sender, String gamemode, int error) { + sender.sendMessage("'" + gamemode + "' is not a valid gamemode!"); } - private boolean isAllowed(Player player) { - return true; // TODO: permissions + private boolean isAllowed(CommandSender sender) { + if (!(sender instanceof Player)) { + sender.sendMessage("The command is only available for player"); + return false; + } + return true; } } diff --git a/src/main/java/fr/themode/demo/commands/HealthCommand.java b/src/main/java/fr/themode/demo/commands/HealthCommand.java index be97c41a3..52dc7a26a 100644 --- a/src/main/java/fr/themode/demo/commands/HealthCommand.java +++ b/src/main/java/fr/themode/demo/commands/HealthCommand.java @@ -5,9 +5,10 @@ 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 net.minestom.server.command.CommandSender; import net.minestom.server.entity.Player; -public class HealthCommand extends Command { +public class HealthCommand extends Command { public HealthCommand() { super("health", "h", "healthbar"); @@ -27,39 +28,39 @@ public class HealthCommand extends Command { addSyntax(this::execute2, arg0, arg1); } - private boolean condition(Player player) { - boolean hasPerm = true; - if (!hasPerm) { - player.sendMessage("You do not have permission !"); + private boolean condition(CommandSender sender) { + if (!(sender instanceof Player)) { + sender.sendMessage("The command is only available for player"); return false; } return true; } - private void defaultExecutor(Player player, Arguments args) { - player.sendMessage("Correct usage: health [set/add] [number]"); + private void defaultExecutor(CommandSender sender, Arguments args) { + sender.sendMessage("Correct usage: health [set/add] [number]"); } - private void modeCallback(Player player, String value, int error) { - player.sendMessage("SYNTAX ERROR: '" + value + "' should be replaced by 'set' or 'add'"); + private void modeCallback(CommandSender sender, String value, int error) { + sender.sendMessage("SYNTAX ERROR: '" + value + "' should be replaced by 'set' or 'add'"); } - private void valueCallback(Player player, String value, int error) { + private void valueCallback(CommandSender sender, String value, int error) { switch (error) { case ArgumentNumber.NOT_NUMBER_ERROR: - player.sendMessage("SYNTAX ERROR: '" + value + "' isn't a number!"); + sender.sendMessage("SYNTAX ERROR: '" + value + "' isn't a number!"); break; case ArgumentNumber.RANGE_ERROR: - player.sendMessage("SYNTAX ERROR: " + value + " is not between 0 and 100"); + sender.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 execute(CommandSender sender, Arguments args) { + sender.sendMessage("/health " + args.getWord("mode") + " [Integer]"); } - private void execute2(Player player, Arguments args) { + private void execute2(CommandSender sender, Arguments args) { + Player player = (Player) sender; String mode = args.getWord("mode"); int value = args.getInteger("value"); diff --git a/src/main/java/fr/themode/demo/commands/SimpleCommand.java b/src/main/java/fr/themode/demo/commands/SimpleCommand.java index 4ff31d581..bc456c150 100644 --- a/src/main/java/fr/themode/demo/commands/SimpleCommand.java +++ b/src/main/java/fr/themode/demo/commands/SimpleCommand.java @@ -1,7 +1,15 @@ 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 @@ -15,7 +23,11 @@ public class SimpleCommand implements CommandProcessor { } @Override - public boolean process(Player player, String command, String[] args) { + public boolean process(CommandSender sender, String command, String[] args) { + + if (!(sender instanceof Player)) + return false; + Player player = (Player) sender; /*for (Player p : MinecraftServer.getConnectionManager().getOnlinePlayers()) { if (!(p instanceof FakePlayer)) @@ -35,9 +47,33 @@ public class SimpleCommand implements CommandProcessor { break; }*/ - System.gc(); - player.sendMessage("Garbage collector called"); + /*for (EntityCreature entityCreature : player.getInstance().getCreatures()) { + entityCreature.setPathTo(player.getPosition().clone()); + //entityCreature.jump(1); + } + System.gc(); + player.sendMessage("Garbage collector called");*/ + + Instance instance = player.getInstance(); + + ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); + chickenCreature.setInstance(instance); +/* + PFPathingEntity pathingEntity = new PFPathingEntity(chickenCreature); + PFInstanceSpace instanceSpace = new PFInstanceSpace(instance); + + final HydrazinePathFinder pathFinder = new HydrazinePathFinder(pathingEntity, instanceSpace); + + final PathObject path = pathFinder.initiatePathTo(-10, 42, -10); + + for (Iterator it = path.iterator(); it.hasNext(); ) { + Vec3i ite = it.next(); + + System.out.println("test: " + ite); + + } +*/ return true; } diff --git a/src/main/java/fr/themode/demo/entity/ChickenCreature.java b/src/main/java/fr/themode/demo/entity/ChickenCreature.java index 1b49fa324..07319ced9 100644 --- a/src/main/java/fr/themode/demo/entity/ChickenCreature.java +++ b/src/main/java/fr/themode/demo/entity/ChickenCreature.java @@ -1,19 +1,16 @@ package fr.themode.demo.entity; import net.minestom.server.entity.Entity; -import net.minestom.server.entity.EntityCreature; -import net.minestom.server.entity.EntityType; import net.minestom.server.entity.Player; +import net.minestom.server.entity.type.EntityChicken; import net.minestom.server.entity.vehicle.PlayerVehicleInformation; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; -public class ChickenCreature extends EntityCreature { +public class ChickenCreature extends EntityChicken { public ChickenCreature(Position defaultPosition) { - super(EntityType.CHICKEN, defaultPosition); - - setBoundingBox(0.4f, 0.7f, 0.4f); + super(defaultPosition); } @Override @@ -22,8 +19,8 @@ public class ChickenCreature extends EntityCreature { } @Override - public void update() { - super.update(); + public void update(long time) { + super.update(time); float speed = 0.075f; if (hasPassenger()) { @@ -80,10 +77,7 @@ public class ChickenCreature extends EntityCreature { move(x, 0, z, updateView); } } else { - //move(0.5f * speed, 0, 0.5f * speed, true); + //move(-0.5f * speed, 0, 0.5f * speed, true); } - - //Player player = MinecraftServer.getConnectionManager().getPlayer("TheMode911"); - //moveTo(player.getPosition().clone()); } } diff --git a/src/main/java/fr/themode/demo/entity/TestArrow.java b/src/main/java/fr/themode/demo/entity/TestArrow.java index ce2e76daa..4b09805d3 100644 --- a/src/main/java/fr/themode/demo/entity/TestArrow.java +++ b/src/main/java/fr/themode/demo/entity/TestArrow.java @@ -14,16 +14,6 @@ public class TestArrow extends ObjectEntity { this.shooter = shooter; } - @Override - public void update() { - - } - - @Override - public void spawn() { - - } - @Override public int getObjectData() { return shooter.getEntityId() + 1; diff --git a/src/main/java/fr/themode/demo/generator/ChunkGeneratorDemo.java b/src/main/java/fr/themode/demo/generator/ChunkGeneratorDemo.java index 80c0adc28..4beae2907 100644 --- a/src/main/java/fr/themode/demo/generator/ChunkGeneratorDemo.java +++ b/src/main/java/fr/themode/demo/generator/ChunkGeneratorDemo.java @@ -9,22 +9,15 @@ import net.minestom.server.instance.block.Block; import java.util.Arrays; import java.util.List; -import java.util.Random; public class ChunkGeneratorDemo extends ChunkGenerator { - private final Random random = new Random(); - @Override public void generateChunkData(ChunkBatch batch, int chunkX, int chunkZ) { for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++) for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { - for (byte y = 0; y < 65; y++) { - if (random.nextInt(100) > 10) { - batch.setCustomBlock(x, y, z, "custom_block"); - } else { - batch.setBlock(x, y, z, Block.DIAMOND_BLOCK); - } + for (byte y = 0; y < 40; y++) { + batch.setBlock(x, y, z, Block.STONE); } } } diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 61db0d05e..17e4df539 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -36,7 +36,6 @@ public class MinecraftServer { public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark"; public static final String THREAD_NAME_MAIN_UPDATE = "Ms-MainUpdate"; - public static final int THREAD_COUNT_MAIN_UPDATE = 1; // Keep it to 1 public static final String THREAD_NAME_PACKET_WRITER = "Ms-PacketWriterPool"; public static final int THREAD_COUNT_PACKET_WRITER = 2; diff --git a/src/main/java/net/minestom/server/UpdateManager.java b/src/main/java/net/minestom/server/UpdateManager.java index 00c4f7b58..ffc7ced98 100644 --- a/src/main/java/net/minestom/server/UpdateManager.java +++ b/src/main/java/net/minestom/server/UpdateManager.java @@ -1,5 +1,7 @@ package net.minestom.server; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; import net.minestom.server.entity.EntityManager; import net.minestom.server.entity.Player; import net.minestom.server.instance.InstanceManager; @@ -10,19 +12,27 @@ import net.minestom.server.utils.thread.MinestomThread; import java.util.concurrent.ExecutorService; -public class UpdateManager { +public final class UpdateManager { - private ExecutorService mainUpdate = new MinestomThread(MinecraftServer.THREAD_COUNT_MAIN_UPDATE, MinecraftServer.THREAD_NAME_MAIN_UPDATE); + private static final long KEEP_ALIVE_DELAY = 10_000; + private static final long KEEP_ALIVE_KICK = 30_000; + + private ExecutorService mainUpdate = new MinestomThread(1, MinecraftServer.THREAD_NAME_MAIN_UPDATE); private boolean stopRequested; + /** + * Should only be created in MinecraftServer + */ + protected UpdateManager() { + } public void start() { mainUpdate.execute(() -> { - ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); - EntityManager entityManager = MinecraftServer.getEntityManager(); - InstanceManager instanceManager = MinecraftServer.getInstanceManager(); - SchedulerManager schedulerManager = MinecraftServer.getSchedulerManager(); + final ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); + final EntityManager entityManager = MinecraftServer.getEntityManager(); + final InstanceManager instanceManager = MinecraftServer.getInstanceManager(); + final SchedulerManager schedulerManager = MinecraftServer.getSchedulerManager(); final long tickDistance = MinecraftServer.TICK_MS * 1000000; long currentTime; @@ -30,12 +40,17 @@ public class UpdateManager { currentTime = System.nanoTime(); // Keep Alive Handling + final long time = System.currentTimeMillis(); + final KeepAlivePacket keepAlivePacket = new KeepAlivePacket(time); for (Player player : connectionManager.getOnlinePlayers()) { - long time = System.currentTimeMillis(); - if (time - player.getLastKeepAlive() > 10000) { + final long lastKeepAlive = time - player.getLastKeepAlive(); + if (lastKeepAlive > KEEP_ALIVE_DELAY && player.didAnswerKeepAlive()) { player.refreshKeepAlive(time); - KeepAlivePacket keepAlivePacket = new KeepAlivePacket(time); player.getPlayerConnection().sendPacket(keepAlivePacket); + } else if (lastKeepAlive >= KEEP_ALIVE_KICK) { + TextComponent textComponent = TextComponent.of("Timeout") + .color(TextColor.RED); + player.kick(textComponent); } } diff --git a/src/main/java/net/minestom/server/entity/property/Attribute.java b/src/main/java/net/minestom/server/attribute/Attribute.java similarity index 82% rename from src/main/java/net/minestom/server/entity/property/Attribute.java rename to src/main/java/net/minestom/server/attribute/Attribute.java index db0a8ae2d..624f9769c 100644 --- a/src/main/java/net/minestom/server/entity/property/Attribute.java +++ b/src/main/java/net/minestom/server/attribute/Attribute.java @@ -1,4 +1,4 @@ -package net.minestom.server.entity.property; +package net.minestom.server.attribute; public enum Attribute { @@ -37,4 +37,12 @@ public enum Attribute { public float getMaxVanillaValue() { return maxVanillaValue; } + + public static Attribute fromKey(String key) { + for (Attribute attribute : values()) { + if (attribute.getKey().equals(key)) + return attribute; + } + return null; + } } diff --git a/src/main/java/net/minestom/server/attribute/AttributeOperation.java b/src/main/java/net/minestom/server/attribute/AttributeOperation.java new file mode 100644 index 000000000..1c84c822f --- /dev/null +++ b/src/main/java/net/minestom/server/attribute/AttributeOperation.java @@ -0,0 +1,26 @@ +package net.minestom.server.attribute; + +public enum AttributeOperation { + ADDITION(0), + MULTIPLY_BASE(1), + MULTIPLY_TOTAL(2); + + private static final AttributeOperation[] VALUES = new AttributeOperation[]{ADDITION, MULTIPLY_BASE, MULTIPLY_TOTAL}; + private final int id; + + AttributeOperation(int id) { + this.id = id; + } + + public static AttributeOperation byId(int id) { + if (id >= 0 && id < VALUES.length) { + return VALUES[id]; + } else { + throw new IllegalArgumentException("No operation with value " + id); + } + } + + public int getId() { + return this.id; + } +} diff --git a/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java b/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java index d6a91f83b..5171cdcba 100644 --- a/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java +++ b/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java @@ -26,6 +26,7 @@ public class BenchmarkManager { private Map lastCpuTimeMap = new HashMap<>(); private Map lastUserTimeMap = new HashMap<>(); + private Map lastWaitedMap = new HashMap<>(); private Map lastBlockedMap = new HashMap<>(); private Map resultMap = new ConcurrentHashMap<>(); @@ -100,26 +101,30 @@ public class BenchmarkManager { long lastCpuTime = lastCpuTimeMap.getOrDefault(id, 0L); long lastUserTime = lastUserTimeMap.getOrDefault(id, 0L); + long lastWaitedTime = lastWaitedMap.getOrDefault(id, 0L); long lastBlockedTime = lastBlockedMap.getOrDefault(id, 0L); long blockedTime = threadInfo2.getBlockedTime(); - //long waitedTime = threadInfo2.getWaitedTime(); + long waitedTime = threadInfo2.getWaitedTime(); long cpuTime = threadMXBean.getThreadCpuTime(id); long userTime = threadMXBean.getThreadUserTime(id); lastCpuTimeMap.put(id, cpuTime); lastUserTimeMap.put(id, userTime); + lastWaitedMap.put(id, waitedTime); lastBlockedMap.put(id, blockedTime); double totalCpuTime = (double) (cpuTime - lastCpuTime) / 1000000D; double totalUserTime = (double) (userTime - lastUserTime) / 1000000D; long totalBlocked = blockedTime - lastBlockedTime; + long totalWaited = waitedTime - lastWaitedTime; double cpuPercentage = totalCpuTime / (double) time * 100L; double userPercentage = totalUserTime / (double) time * 100L; + double waitedPercentage = totalWaited / (double) time * 100L; double blockedPercentage = totalBlocked / (double) time * 100L; - ThreadResult threadResult = new ThreadResult(cpuPercentage, userPercentage, blockedPercentage); + ThreadResult threadResult = new ThreadResult(cpuPercentage, userPercentage, waitedPercentage, blockedPercentage); resultMap.put(name, threadResult); } } diff --git a/src/main/java/net/minestom/server/benchmark/ThreadResult.java b/src/main/java/net/minestom/server/benchmark/ThreadResult.java index 9b08a8b62..320ee0d14 100644 --- a/src/main/java/net/minestom/server/benchmark/ThreadResult.java +++ b/src/main/java/net/minestom/server/benchmark/ThreadResult.java @@ -2,11 +2,12 @@ package net.minestom.server.benchmark; public class ThreadResult { - private double cpuPercentage, userPercentage, blockedPercentage; + private double cpuPercentage, userPercentage, waitedPercentage, blockedPercentage; - protected ThreadResult(double cpuPercentage, double userPercentage, double blockedPercentage) { + protected ThreadResult(double cpuPercentage, double userPercentage, double waitedPercentage, double blockedPercentage) { this.cpuPercentage = cpuPercentage; this.userPercentage = userPercentage; + this.waitedPercentage = waitedPercentage; this.blockedPercentage = blockedPercentage; } @@ -18,6 +19,10 @@ public class ThreadResult { return userPercentage; } + public double getWaitedPercentage() { + return waitedPercentage; + } + public double getBlockedPercentage() { return blockedPercentage; } diff --git a/src/main/java/net/minestom/server/bossbar/BossBar.java b/src/main/java/net/minestom/server/bossbar/BossBar.java index d9cfd1aa9..1cc022e24 100644 --- a/src/main/java/net/minestom/server/bossbar/BossBar.java +++ b/src/main/java/net/minestom/server/bossbar/BossBar.java @@ -4,12 +4,17 @@ import net.minestom.server.Viewable; import net.minestom.server.chat.Chat; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.play.BossBarPacket; +import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.validate.Check; import java.util.Collections; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; +/** + * Represent a bossbar which can be showed to any player {@link #addViewer(Player)} + */ public class BossBar implements Viewable { private UUID uuid = UUID.randomUUID(); @@ -48,41 +53,87 @@ public class BossBar implements Viewable { return Collections.unmodifiableSet(viewers); } + /** + * Get the bossbar title + * + * @return the current title of the bossbar + */ public String getTitle() { return title; } + /** + * Change the bossbar title + * + * @param title the new title of the bossbar + */ public void setTitle(String title) { this.title = title; } + /** + * Get the bossbar progress + * + * @return the current progress of the bossbar + */ public float getProgress() { return progress; } + /** + * Change the bossbar progress + * + * @param progress the new progress bar percentage + * @throws IllegalArgumentException if {@code progress} is not between 0 and 1 + */ public void setProgress(float progress) { + Check.argCondition(!MathUtils.isBetween(progress, 0, 1), + "BossBar progress percentage should be between 0 and 1"); this.progress = progress; updateProgress(); } + /** + * Get the bossbar color + * + * @return the current bossbar color + */ public BarColor getColor() { return color; } + /** + * Change the bossbar color + * + * @param color the new color of the bossbar + */ public void setColor(BarColor color) { this.color = color; updateStyle(); } + /** + * Get the bossbar division + * + * @return the current bossbar division + */ public BarDivision getDivision() { return division; } + /** + * Change the bossbar division + * + * @param division the new bossbar division count + */ public void setDivision(BarDivision division) { this.division = division; updateStyle(); } + /** + * Delete the boss bar and remove all of its viewers + */ public void delete() { BossBarPacket bossBarPacket = new BossBarPacket(); bossBarPacket.uuid = uuid; diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java index f1f3ce1c6..a114f2b6c 100644 --- a/src/main/java/net/minestom/server/collision/BoundingBox.java +++ b/src/main/java/net/minestom/server/collision/BoundingBox.java @@ -21,12 +21,24 @@ public class BoundingBox { this.z = z; } + /** + * Used to know if two BoundingBox intersect with each other + * + * @param boundingBox the bounding box to check + * @return true if the two BoundingBox intersect with each other, false otherwise + */ public boolean intersect(BoundingBox boundingBox) { return (getMinX() <= boundingBox.getMaxX() && getMaxX() >= boundingBox.getMinX()) && (getMinY() <= boundingBox.getMaxY() && getMaxY() >= boundingBox.getMinY()) && (getMinZ() <= boundingBox.getMaxZ() && getMaxZ() >= boundingBox.getMinZ()); } + /** + * Used to know if the bounding box intersects with a block (can be air) + * + * @param blockPosition the position to check + * @return true if the bounding box intersects with the position, false otherwise + */ public boolean intersect(BlockPosition blockPosition) { final float x = blockPosition.getX(); @@ -62,10 +74,22 @@ public class BoundingBox { return intersect(position.getX(), position.getY(), position.getZ()); } + /** + * @param x the X offset + * @param y the Y offset + * @param z the Z offset + * @return a new bounding box expanded + */ public BoundingBox expand(float x, float y, float z) { return new BoundingBox(entity, this.x + x, this.y + y, this.z + z); } + /** + * @param x the X offset + * @param y the Y offset + * @param z the Z offset + * @return a new bounding box contracted + */ public BoundingBox contract(float x, float y, float z) { return new BoundingBox(entity, this.x - x, this.y - y, this.z - z); } @@ -107,7 +131,7 @@ public class BoundingBox { } public Vector[] getBottomFace() { - return new Vector[] { + return new Vector[]{ new Vector(getMinX(), getMinY(), getMinZ()), new Vector(getMaxX(), getMinY(), getMinZ()), new Vector(getMaxX(), getMinY(), getMaxZ()), @@ -116,7 +140,7 @@ public class BoundingBox { } public Vector[] getTopFace() { - return new Vector[] { + return new Vector[]{ new Vector(getMinX(), getMaxY(), getMinZ()), new Vector(getMaxX(), getMaxY(), getMinZ()), new Vector(getMaxX(), getMaxY(), getMaxZ()), @@ -125,7 +149,7 @@ public class BoundingBox { } public Vector[] getLeftFace() { - return new Vector[] { + return new Vector[]{ new Vector(getMinX(), getMinY(), getMinZ()), new Vector(getMinX(), getMaxY(), getMinZ()), new Vector(getMinX(), getMaxY(), getMaxZ()), @@ -134,7 +158,7 @@ public class BoundingBox { } public Vector[] getRightFace() { - return new Vector[] { + return new Vector[]{ new Vector(getMaxX(), getMinY(), getMinZ()), new Vector(getMaxX(), getMaxY(), getMinZ()), new Vector(getMaxX(), getMaxY(), getMaxZ()), @@ -143,7 +167,7 @@ public class BoundingBox { } public Vector[] getFrontFace() { - return new Vector[] { + return new Vector[]{ new Vector(getMinX(), getMinY(), getMinZ()), new Vector(getMaxX(), getMinY(), getMinZ()), new Vector(getMaxX(), getMaxY(), getMinZ()), @@ -152,7 +176,7 @@ public class BoundingBox { } public Vector[] getBackFace() { - return new Vector[] { + return new Vector[]{ new Vector(getMinX(), getMinY(), getMaxZ()), new Vector(getMaxX(), getMinY(), getMaxZ()), new Vector(getMaxX(), getMaxY(), getMaxZ()), diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 420d161a7..f74b5ea9f 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -9,19 +9,33 @@ import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.validate.Check; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class CommandManager { private String commandPrefix = "/"; - private CommandDispatcher dispatcher = new CommandDispatcher<>(); + private ConsoleSender consoleSender = new ConsoleSender(); + + private CommandDispatcher dispatcher = new CommandDispatcher<>(); private Map commandProcessorMap = new HashMap<>(); - public void register(Command command) { + public CommandManager() { + // Setup console thread + new Thread(() -> { + Scanner scanner = new Scanner(System.in); + while (true) { + String command = scanner.nextLine(); + if (!command.startsWith(commandPrefix)) + continue; + command = command.replaceFirst(commandPrefix, ""); + execute(consoleSender, command); + + } + }, "ConsoleCommand-Thread").start(); + } + + public void register(Command command) { this.dispatcher.register(command); } @@ -29,20 +43,24 @@ public class CommandManager { this.commandProcessorMap.put(commandProcessor.getCommandName().toLowerCase(), commandProcessor); } - public boolean execute(Player source, String command) { - Check.notNull(source, "Source cannot be null"); + public boolean execute(CommandSender sender, String command) { + Check.notNull(sender, "Source cannot be null"); Check.notNull(command, "Command string cannot be null"); - PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(source, command); - source.callEvent(PlayerCommandEvent.class, playerCommandEvent); + if (sender instanceof Player) { + Player player = (Player) sender; - if (playerCommandEvent.isCancelled()) - return false; + PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(player, command); + player.callEvent(PlayerCommandEvent.class, playerCommandEvent); - command = playerCommandEvent.getCommand(); + if (playerCommandEvent.isCancelled()) + return false; + + command = playerCommandEvent.getCommand(); + } try { - this.dispatcher.execute(source, command); + this.dispatcher.execute(sender, command); return true; } catch (NullPointerException e) { String[] splitted = command.split(" "); @@ -53,7 +71,7 @@ public class CommandManager { String[] args = command.substring(command.indexOf(" ") + 1).split(" "); - return commandProcessor.process(source, commandName, args); + return commandProcessor.process(sender, commandName, args); } } @@ -66,6 +84,10 @@ public class CommandManager { this.commandPrefix = commandPrefix; } + public ConsoleSender getConsoleSender() { + return consoleSender; + } + public DeclareCommandsPacket createDeclareCommandsPacket(Player player) { return buildPacket(player); } @@ -74,7 +96,7 @@ public class CommandManager { DeclareCommandsPacket declareCommandsPacket = new DeclareCommandsPacket(); List commands = new ArrayList<>(); - for (Command command : dispatcher.getCommands()) { + for (Command command : dispatcher.getCommands()) { CommandCondition commandCondition = command.getCondition(); if (commandCondition != null) { // Do not show command if return false diff --git a/src/main/java/net/minestom/server/command/CommandProcessor.java b/src/main/java/net/minestom/server/command/CommandProcessor.java index 8c500313d..a899bab0c 100644 --- a/src/main/java/net/minestom/server/command/CommandProcessor.java +++ b/src/main/java/net/minestom/server/command/CommandProcessor.java @@ -8,7 +8,7 @@ public interface CommandProcessor { String[] getAliases(); - boolean process(Player player, String command, String[] args); + boolean process(CommandSender sender, String command, String[] args); boolean hasAccess(Player player); } diff --git a/src/main/java/net/minestom/server/command/CommandSender.java b/src/main/java/net/minestom/server/command/CommandSender.java new file mode 100644 index 000000000..50f485262 --- /dev/null +++ b/src/main/java/net/minestom/server/command/CommandSender.java @@ -0,0 +1,13 @@ +package net.minestom.server.command; + +public interface CommandSender { + + void sendMessage(String message); + + default void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + +} diff --git a/src/main/java/net/minestom/server/command/ConsoleSender.java b/src/main/java/net/minestom/server/command/ConsoleSender.java new file mode 100644 index 000000000..8d92332f6 --- /dev/null +++ b/src/main/java/net/minestom/server/command/ConsoleSender.java @@ -0,0 +1,8 @@ +package net.minestom.server.command; + +public class ConsoleSender implements CommandSender { + @Override + public void sendMessage(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/net/minestom/server/data/Data.java b/src/main/java/net/minestom/server/data/Data.java index 8ac12c96f..9c83ba82a 100644 --- a/src/main/java/net/minestom/server/data/Data.java +++ b/src/main/java/net/minestom/server/data/Data.java @@ -8,7 +8,8 @@ public class Data { public static final Data EMPTY = new Data() { @Override - public void set(String key, T value, Class type) {} + public void set(String key, T value, Class type) { + } @Override public T get(String key) { @@ -41,6 +42,8 @@ public class Data { } /** + * Get if the data has a key + * * @param key * @return true if the data contains the key, false otherwise */ @@ -49,6 +52,8 @@ public class Data { } /** + * Get the list of data keys + * * @return an unmodifiable set containing all keys */ public Set getKeys() { @@ -56,12 +61,19 @@ public class Data { } /** + * Get if the data is empty or not + * * @return true if the data does not contain anything, false otherwise */ public boolean isEmpty() { return data.isEmpty(); } + /** + * Clone this data + * + * @return a cloned data object + */ public Data clone() { Data data = new Data(); data.data = new ConcurrentHashMap<>(this.data); diff --git a/src/main/java/net/minestom/server/data/DataManager.java b/src/main/java/net/minestom/server/data/DataManager.java index 820c6b7ed..44183570c 100644 --- a/src/main/java/net/minestom/server/data/DataManager.java +++ b/src/main/java/net/minestom/server/data/DataManager.java @@ -6,11 +6,13 @@ import net.minestom.server.data.type.array.*; import net.minestom.server.inventory.Inventory; import net.minestom.server.item.ItemStack; import net.minestom.server.utils.PrimitiveConversion; +import net.minestom.server.utils.validate.Check; import java.util.HashMap; import java.util.Map; +import java.util.UUID; -public class DataManager { +public final class DataManager { private Map dataTypeMap = new HashMap<>(); @@ -42,6 +44,8 @@ public class DataManager { registerType(String.class, new StringData()); registerType(String[].class, new StringArrayData()); + registerType(UUID.class, new UuidType()); + registerType(SerializableData.class, new SerializableDataData()); registerType(ItemStack.class, new ItemStackData()); @@ -50,14 +54,27 @@ public class DataManager { registerType(Inventory.class, new InventoryData()); } + /** + * Register a new data type + * + * @param clazz the data class + * @param dataType the data type associated + * @param the data type + */ public void registerType(Class clazz, DataType dataType) { clazz = PrimitiveConversion.getObjectClass(clazz); - if (dataTypeMap.containsKey(clazz)) - throw new UnsupportedOperationException("Type " + clazz.getName() + " has already been registed"); + Check.stateCondition(dataTypeMap.containsKey(clazz), + "Type " + clazz.getName() + " has already been registered"); this.dataTypeMap.put(clazz, dataType); } + /** + * @param clazz the data class + * @param the data type + * @return the {@link DataType} associated to the class + * @throws NullPointerException if none is found + */ public DataType getDataType(Class clazz) { return dataTypeMap.get(PrimitiveConversion.getObjectClass(clazz)); } diff --git a/src/main/java/net/minestom/server/data/type/UuidType.java b/src/main/java/net/minestom/server/data/type/UuidType.java new file mode 100644 index 000000000..d037f8524 --- /dev/null +++ b/src/main/java/net/minestom/server/data/type/UuidType.java @@ -0,0 +1,19 @@ +package net.minestom.server.data.type; + +import net.minestom.server.data.DataType; +import net.minestom.server.network.packet.PacketReader; +import net.minestom.server.network.packet.PacketWriter; + +import java.util.UUID; + +public class UuidType extends DataType { + @Override + public void encode(PacketWriter packetWriter, UUID value) { + packetWriter.writeUuid(value); + } + + @Override + public UUID decode(PacketReader packetReader) { + return packetReader.readUuid(); + } +} diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 513ccc97c..a0a83f32a 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -14,9 +14,7 @@ import net.minestom.server.event.entity.EntitySpawnEvent; import net.minestom.server.event.entity.EntityTickEvent; import net.minestom.server.event.entity.EntityVelocityEvent; import net.minestom.server.event.handler.EventHandler; -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.WorldBorder; +import net.minestom.server.instance.*; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.play.*; @@ -50,12 +48,15 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { protected static final byte METADATA_BOOLEAN = 7; protected static final byte METADATA_ROTATION = 8; protected static final byte METADATA_POSITION = 9; + protected static final byte METADATA_PARTICLE = 15; protected static final byte METADATA_POSE = 18; protected Instance instance; protected Position position; protected float lastX, lastY, lastZ; + protected float cacheX, cacheY, cacheZ; // Used to synchronize with #getPosition protected float lastYaw, lastPitch; + protected float cacheYaw, cachePitch; private int id; private BoundingBox boundingBox; @@ -140,8 +141,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { /** * Called each tick + * + * @param time the time of update in milliseconds */ - public abstract void update(); + public abstract void update(long time); /** * Called when a new instance is set @@ -205,6 +208,39 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { teleport(position, null); } + /** + * Change the view of the entity + * + * @param yaw the new yaw + * @param pitch the new pitch + */ + public void setView(float yaw, float pitch) { + refreshView(yaw, pitch); + + EntityRotationPacket entityRotationPacket = new EntityRotationPacket(); + entityRotationPacket.entityId = getEntityId(); + entityRotationPacket.yaw = yaw; + entityRotationPacket.pitch = pitch; + entityRotationPacket.onGround = onGround; + + EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket(); + entityHeadLookPacket.entityId = getEntityId(); + entityHeadLookPacket.yaw = yaw; + + sendPacketToViewersAndSelf(entityHeadLookPacket); + sendPacketToViewersAndSelf(entityRotationPacket); + } + + /** + * Change the view of the entity + * Only the yaw and pitch is used + * + * @param position the new view + */ + public void setView(Position position) { + setView(position.getYaw(), position.getPitch()); + } + /** * When set to true, the entity will automatically get new viewers when they come too close * This can be use to complete control over which player can see it, without having to deal with @@ -231,6 +267,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { public boolean addViewer(Player player) { Check.notNull(player, "Viewer cannot be null"); boolean result = this.viewers.add(player); + if (!result) + return false; player.viewableEntities.add(this); return result; } @@ -263,6 +301,11 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { this.data = data; } + /** + * Update the entity, called every tick + * + * @param time update time in milliseconds + */ public void tick(long time) { if (instance == null) return; @@ -286,6 +329,21 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { return; } + // Synchronization with updated fields in #getPosition() + { + // X/Y/Z axis + if (cacheX != position.getX() || + cacheY != position.getY() || + cacheZ != position.getZ()) { + teleport(position); + } + // Yaw/Pitch + if (cacheYaw != position.getYaw() || + cachePitch != position.getPitch()) { + setView(position); + } + } + if (shouldUpdate(time)) { this.lastUpdate = time; @@ -393,16 +451,16 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { handleVoid(); // Call the abstract update method - update(); + update(time); ticks++; callEvent(EntityTickEvent.class, tickEvent); // reuse tickEvent to avoid recreating it each tick + } - // Scheduled synchronization - if (time - lastSynchronizationTime >= synchronizationDelay) { - lastSynchronizationTime = time; - sendSynchronization(); - } + // Scheduled synchronization + if (time - lastSynchronizationTime >= synchronizationDelay) { + lastSynchronizationTime = time; + sendSynchronization(); } if (shouldRemove()) { @@ -411,9 +469,9 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } /** - * Returns the number of ticks this entity has been active for + * Get the number of ticks this entity has been active for * - * @return + * @return the number of ticks this entity has been active for */ public long getAliveTicks() { return ticks; @@ -438,6 +496,15 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { this.eventCallbacks.put(eventClass, callbacks); } + @Override + public void removeEventCallback(Class eventClass, EventCallback eventCallback) { + Check.notNull(eventClass, "Event class cannot be null"); + Check.notNull(eventCallback, "Event callback cannot be null"); + List callbacks = getEventCallbacks(eventClass); + callbacks.remove(eventCallback); + this.eventCallbacks.put(eventClass, callbacks); + } + @Override public List getEventCallbacks(Class eventClass) { Check.notNull(eventClass, "Event class cannot be null"); @@ -454,30 +521,73 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { return id; } + /** + * Return the entity type id, can convert using {@link EntityType#fromId(int)} + * + * @return the entity type id + */ public int getEntityType() { return entityType; } + /** + * Get the entity UUID + * + * @return the entity UUID + */ public UUID getUuid() { return uuid; } + /** + * Return false just after instantiation, set to true after calling {@link #setInstance(Instance)} + * + * @return true if the entity has been linked to an instance, false otherwise + */ public boolean isActive() { return isActive; } + /** + * Is used to check collision with coordinates or other blocks/entities + * + * @return the entity bounding box + */ public BoundingBox getBoundingBox() { return boundingBox; } + /** + * Change the internal entity bounding box + *

+ * WARNING: this does not change the entity hit-box which is client-side + * + * @param x the bounding box X size + * @param y the bounding box Y size + * @param z the bounding box Z size + */ public void setBoundingBox(float x, float y, float z) { this.boundingBox = new BoundingBox(this, x, y, z); } + /** + * Get the entity current instance + * + * @return the entity instance + */ public Instance getInstance() { return instance; } + /** + * Change the entity instance + * + * @param instance the new instance of the entity + * @throws NullPointerException if {@code instance} is null + * @throws IllegalStateException if {@code instance} has not been registered in + * {@link InstanceManager#createInstanceContainer()} or + * {@link InstanceManager#createSharedInstance(InstanceContainer)} + */ public void setInstance(Instance instance) { Check.notNull(instance, "instance cannot be null!"); Check.stateCondition(!MinecraftServer.getInstanceManager().getInstances().contains(instance), @@ -495,16 +605,22 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { callEvent(EntitySpawnEvent.class, entitySpawnEvent); } + /** + * Get the entity current velocity + * + * @return the entity current velocity + */ public Vector getVelocity() { return velocity; } - public boolean hasVelocity() { - return velocity.getX() != 0 || - velocity.getY() != 0 || - velocity.getZ() != 0; - } - + /** + * Change the entity velocity and calls {@link EntityVelocityEvent}. + *

+ * The final velocity can be cancelled or modified by the event + * + * @param velocity the new entity velocity + */ public void setVelocity(Vector velocity) { EntityVelocityEvent entityVelocityEvent = new EntityVelocityEvent(this, velocity); callCancellableEvent(EntityVelocityEvent.class, entityVelocityEvent, () -> { @@ -513,19 +629,51 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { }); } + /** + * @return true if velocity is not set to 0 + */ + public boolean hasVelocity() { + return velocity.getX() != 0 || + velocity.getY() != 0 || + velocity.getZ() != 0; + } + + /** + * Change the gravity of the entity + * + * @param gravityDragPerTick + */ public void setGravity(float gravityDragPerTick) { this.gravityDragPerTick = gravityDragPerTick; } + /** + * Get the distance between two entities + * + * @param entity the entity to get the distance from + * @return the distance between this and {@code entity} + */ public float getDistance(Entity entity) { Check.notNull(entity, "Entity cannot be null"); return getPosition().getDistance(entity.getPosition()); } + /** + * Get the entity vehicle or null + * + * @return the entity vehicle, or null if there is not any + */ public Entity getVehicle() { return vehicle; } + /** + * Add a new passenger to this entity + * + * @param entity the new passenger + * @throws NullPointerException if {@code entity} is null + * @throws IllegalStateException if {@link #getInstance()} returns null + */ public void addPassenger(Entity entity) { Check.notNull(entity, "Passenger cannot be null"); Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance"); @@ -540,6 +688,13 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { sendPacketToViewersAndSelf(getPassengersPacket()); } + /** + * Remove a passenger to this entity + * + * @param entity the passenger to remove + * @throws NullPointerException if {@code entity} is null + * @throws IllegalStateException if {@link #getInstance()} returns null + */ public void removePassenger(Entity entity) { Check.notNull(entity, "Passenger cannot be null"); Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance"); @@ -550,11 +705,18 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { sendPacketToViewersAndSelf(getPassengersPacket()); } + /** + * Get if the entity has any passenger + * + * @return true if the entity has any passenger, false otherwise + */ public boolean hasPassenger() { return !passengers.isEmpty(); } /** + * Get the entity passengers + * * @return an unmodifiable list containing all the entity passengers */ public Set getPassengers() { @@ -588,6 +750,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } /** + * Get if the entity is on fire + * * @return true if the entity is in fire, false otherwise */ public boolean isOnFire() { @@ -607,40 +771,79 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { sendMetadataIndex(0); } + /** + * Get if the entity is invisible or not + * + * @return true if the entity is invisible, false otherwise + */ public boolean isInvisible() { return invisible; } + /** + * Change the internal invisible value and send a {@link EntityMetaDataPacket} + * to make visible or invisible the entity to its viewers + * + * @param invisible true to set the entity invisible, false otherwise + */ public void setInvisible(boolean invisible) { this.invisible = invisible; sendMetadataIndex(0); } - public void setGlowing(boolean glowing) { - this.glowing = glowing; - sendMetadataIndex(0); - } - /** + * Get if the entity is glowing or not + * * @return true if the entity is glowing, false otherwise */ public boolean isGlowing() { return glowing; } + /** + * Set or remove the entity glowing effect + * + * @param glowing true to make the entity glows, false otherwise + */ + public void setGlowing(boolean glowing) { + this.glowing = glowing; + sendMetadataIndex(0); + } + + /** + * Get the entity custom name + * + * @return the custom name of the entity, null if there is not + */ public String getCustomName() { return customName; } + /** + * Change the entity custom name + * + * @param customName the custom name of the entity, null to remove it + */ public void setCustomName(String customName) { this.customName = customName; sendMetadataIndex(2); } + /** + * Get the custom name visible metadata field + * + * @return true if the custom name is visible, false otherwise + */ public boolean isCustomNameVisible() { return customNameVisible; } + /** + * Change the internal custom name visible field and send a {@link EntityMetaDataPacket} + * to update the entity state to its viewers + * + * @param customNameVisible true to make the custom name visible, false otherwise + */ public void setCustomNameVisible(boolean customNameVisible) { this.customNameVisible = customNameVisible; sendMetadataIndex(3); @@ -656,6 +859,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } /** + * Change the noGravity metadata field and change the gravity behaviour accordingly + * * @param noGravity should the entity ignore gravity */ public void setNoGravity(boolean noGravity) { @@ -664,6 +869,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } /** + * Get the noGravity metadata field + * * @return true if the entity ignore gravity, false otherwise */ public boolean hasNoGravity() { @@ -689,6 +896,9 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { position.setX(x); position.setY(y); position.setZ(z); + this.cacheX = x; + this.cacheY = y; + this.cacheZ = z; if (hasPassenger()) { for (Entity passenger : getPassengers()) { @@ -710,6 +920,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } } + /** + * @param position the new position + * @see #refreshPosition(float, float, float) + */ public void refreshPosition(Position position) { refreshPosition(position.getX(), position.getY(), position.getZ()); } @@ -765,11 +979,21 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } } + /** + * Update the entity view internally + *

+ * Warning: you probably want to use {@link #setView(float, float)} + * + * @param yaw the yaw + * @param pitch the pitch + */ public void refreshView(float yaw, float pitch) { this.lastYaw = position.getYaw(); this.lastPitch = position.getPitch(); position.setYaw(yaw); position.setPitch(pitch); + this.cacheYaw = yaw; + this.cachePitch = pitch; } public void refreshSneaking(boolean sneaking) { @@ -785,21 +1009,35 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } /** + * Get the entity position + * * @return the current position of the entity */ public Position getPosition() { return position; } + /** + * Get the entity eye height + * + * @return the entity eye height + */ public float getEyeHeight() { return eyeHeight; } + /** + * Change the entity eye height + * + * @param eyeHeight the entity eye eight + */ public void setEyeHeight(float eyeHeight) { this.eyeHeight = eyeHeight; } /** + * Get if this entity is in the same chunk as the specified position + * * @param position the checked position chunk * @return true if the entity is in the same chunk as {@code position} */ @@ -815,6 +1053,12 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { return chunkX1 == chunkX2 && chunkZ1 == chunkZ2; } + /** + * Get if the entity is in the same chunk as another + * + * @param entity the entity to check + * @return true if both entities are in the same chunk, false otherwise + */ public boolean sameChunk(Entity entity) { return sameChunk(entity.getPosition()); } @@ -847,6 +1091,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } /** + * Get if the entity removal is scheduled + * * @return true if {@link #scheduleRemove(long, TimeUnit)} has been called, false otherwise */ public boolean isRemoveScheduled() { @@ -1015,7 +1261,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { } /** - * Ask for a synchronization (position) to happen during next entity update + * Ask for a synchronization (position) to happen during next entity tick */ public void askSynchronization() { this.lastSynchronizationTime = 0; @@ -1025,7 +1271,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { return (float) (time - lastUpdate) >= MinecraftServer.TICK_MS * 0.9f; // Margin of error } - public enum Pose { + private enum Pose { STANDING, FALL_FLYING, SLEEPING, diff --git a/src/main/java/net/minestom/server/entity/EntityCreature.java b/src/main/java/net/minestom/server/entity/EntityCreature.java index 77eb910c5..32ca700af 100644 --- a/src/main/java/net/minestom/server/entity/EntityCreature.java +++ b/src/main/java/net/minestom/server/entity/EntityCreature.java @@ -1,8 +1,9 @@ package net.minestom.server.entity; +import net.minestom.server.attribute.Attribute; import net.minestom.server.collision.CollisionUtils; import net.minestom.server.entity.pathfinding.EntityPathFinder; -import net.minestom.server.entity.property.Attribute; +import net.minestom.server.event.entity.EntityAttackEvent; import net.minestom.server.event.item.ArmorEquipEvent; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.*; @@ -48,24 +49,11 @@ public abstract class EntityCreature extends LivingEntity { } @Override - public void update() { - super.update(); + public void update(long time) { + super.update(time); // Path finding - if (blockPositions != null) { - if (targetPosition != null) { - float distance = getPosition().getDistance(targetPosition); - //System.out.println("test: "+distance); - if (distance < 0.7f) { - setNextPathPosition(); - //System.out.println("END TARGET"); - } else { - moveTowards(targetPosition, getAttributeValue(Attribute.MOVEMENT_SPEED)); - //System.out.println("MOVE TOWARD " + targetPosition); - } - } - } - + pathProgress(); } /** @@ -94,9 +82,9 @@ public abstract class EntityCreature extends LivingEntity { float yaw = (float) (radians * (180.0 / Math.PI)) - 90; float pitch = position.getPitch(); // TODO - short deltaX = (short) ((newX * 32 - position.getX() * 32) * 128); - short deltaY = (short) ((newY * 32 - position.getY() * 32) * 128); - short deltaZ = (short) ((newZ * 32 - position.getZ() * 32) * 128); + final short deltaX = (short) ((newX * 32 - position.getX() * 32) * 128); + final short deltaY = (short) ((newY * 32 - position.getY() * 32) * 128); + final short deltaZ = (short) ((newZ * 32 - position.getZ() * 32) * 128); if (updateView) { EntityPositionAndRotationPacket entityPositionAndRotationPacket = new EntityPositionAndRotationPacket(); @@ -119,11 +107,7 @@ public abstract class EntityCreature extends LivingEntity { } if (lastYaw != yaw) { - EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket(); - entityHeadLookPacket.entityId = getEntityId(); - entityHeadLookPacket.yaw = yaw; - sendPacketToViewers(entityHeadLookPacket); - refreshView(yaw, pitch); + setView(yaw, pitch); } refreshPosition(newX, newY, newZ); @@ -145,6 +129,9 @@ public abstract class EntityCreature extends LivingEntity { @Override public boolean addViewer(Player player) { boolean result = super.addViewer(player); + if (!result) + return false; + PlayerConnection playerConnection = player.getPlayerConnection(); EntityPacket entityPacket = new EntityPacket(); @@ -163,7 +150,7 @@ public abstract class EntityCreature extends LivingEntity { playerConnection.sendPacket(getMetadataPacket()); // Equipments synchronization - syncEquipments(); + syncEquipments(playerConnection); if (hasPassenger()) { playerConnection.sendPacket(getPassengersPacket()); @@ -238,9 +225,33 @@ public abstract class EntityCreature extends LivingEntity { syncEquipment(EntityEquipmentPacket.Slot.BOOTS); } + /** + * Call a {@link EntityAttackEvent} with this entity as the source and {@code target} as the target. + * + * @param target the entity target + * @param swingHand true to swing the entity main hand, false otherwise + */ + public void attack(Entity target, boolean swingHand) { + if (swingHand) + swingMainHand(); + EntityAttackEvent attackEvent = new EntityAttackEvent(this, target); + callEvent(EntityAttackEvent.class, attackEvent); + } + + /** + * Call a {@link EntityAttackEvent} with this entity as the source and {@code target} as the target. + *

+ * This does not trigger the hand animation + * + * @param target the entity target + */ + public void attack(Entity target) { + attack(target, false); + } + public void jump(float height) { // FIXME magic value - Vector velocity = new Vector(0, height * 10, 0); + Vector velocity = new Vector(0, height * 5, 0); setVelocity(velocity); } @@ -271,7 +282,7 @@ public abstract class EntityCreature extends LivingEntity { } /** - * Used to move the entity toward {@code direction} in the axis X and Z + * Used to move the entity toward {@code direction} in the X and Z axis * Gravity is still applied but the entity will not attempt to jump * * @param direction the targeted position @@ -294,11 +305,27 @@ public abstract class EntityCreature extends LivingEntity { } this.targetPosition = blockPosition.toPosition();//.add(0.5f, 0, 0.5f); - // FIXME: jump support if (blockPosition.getY() > getPosition().getY()) jump(1); } + private void pathProgress() { + if (blockPositions != null) { + if (targetPosition != null) { + float distance = getPosition().getDistance(targetPosition); + //System.out.println("test: "+distance); + if (distance < 1f) { + setNextPathPosition(); + pathProgress(); + //System.out.println("END TARGET"); + } else { + moveTowards(targetPosition, getAttributeValue(Attribute.MOVEMENT_SPEED)); + //System.out.println("MOVE TOWARD " + targetPosition); + } + } + } + } + private ItemStack getEquipmentItem(ItemStack itemStack, ArmorEquipEvent.ArmorSlot armorSlot) { itemStack = ItemStackUtils.notNull(itemStack); diff --git a/src/main/java/net/minestom/server/entity/EntityManager.java b/src/main/java/net/minestom/server/entity/EntityManager.java index 59626ab07..82d813e5b 100644 --- a/src/main/java/net/minestom/server/entity/EntityManager.java +++ b/src/main/java/net/minestom/server/entity/EntityManager.java @@ -12,7 +12,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; -public class EntityManager { +public final class EntityManager { private static InstanceManager instanceManager = MinecraftServer.getInstanceManager(); @@ -24,6 +24,9 @@ public class EntityManager { private ConcurrentLinkedQueue waitingPlayers = new ConcurrentLinkedQueue<>(); + /** + * Execute a whole entity server tick + */ public void update() { final long time = System.currentTimeMillis(); @@ -164,7 +167,7 @@ public class EntityManager { playersPool.execute(() -> { playerCache.init(); - PlayerLoginEvent loginEvent = new PlayerLoginEvent(); + PlayerLoginEvent loginEvent = new PlayerLoginEvent(playerCache); playerCache.callEvent(PlayerLoginEvent.class, loginEvent); Instance spawningInstance = loginEvent.getSpawningInstance(); @@ -179,10 +182,16 @@ public class EntityManager { this.waitingPlayers.add(player); } + /** + * @return the current entity update type + */ public UpdateType getUpdateType() { return updateType; } + /** + * @param updateType the new entity update type + */ public void setUpdateType(UpdateType updateType) { this.updateType = updateType; } diff --git a/src/main/java/net/minestom/server/entity/ExperienceOrb.java b/src/main/java/net/minestom/server/entity/ExperienceOrb.java index d5321bcd4..e729b12b3 100644 --- a/src/main/java/net/minestom/server/entity/ExperienceOrb.java +++ b/src/main/java/net/minestom/server/entity/ExperienceOrb.java @@ -18,7 +18,7 @@ public class ExperienceOrb extends Entity { } @Override - public void update() { + public void update(long time) { // TODO slide toward nearest player } diff --git a/src/main/java/net/minestom/server/entity/ItemEntity.java b/src/main/java/net/minestom/server/entity/ItemEntity.java index 89e8e001e..a205e2686 100644 --- a/src/main/java/net/minestom/server/entity/ItemEntity.java +++ b/src/main/java/net/minestom/server/entity/ItemEntity.java @@ -6,13 +6,28 @@ import net.minestom.server.item.ItemStack; import net.minestom.server.item.StackingRule; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.utils.Position; +import net.minestom.server.utils.time.CooldownUtils; import net.minestom.server.utils.time.TimeUnit; +import net.minestom.server.utils.time.UpdateOption; import java.util.Set; import java.util.function.Consumer; +/** + * Represent an item on the ground + */ public class ItemEntity extends ObjectEntity { + /** + * Used to slow down the merge check delay + */ + private static UpdateOption mergeUpdateOption = new UpdateOption(10, TimeUnit.TICK); + + /** + * The last time that this item has checked his neighbors for merge + */ + private long lastMergeCheck; + private ItemStack itemStack; private boolean pickable = true; @@ -26,12 +41,33 @@ public class ItemEntity extends ObjectEntity { super(EntityType.ITEM, spawnPosition); this.itemStack = itemStack; setBoundingBox(0.25f, 0.25f, 0.25f); - setGravity(0.025f); + } + + /** + * Get the update option for the merging feature + * + * @return the merge update option + */ + public static UpdateOption getMergeUpdateOption() { + return mergeUpdateOption; + } + + /** + * Change the merge update option. + * Can be set to null to entirely remove the delay + * + * @param mergeUpdateOption the new merge update option + */ + public static void setMergeUpdateOption(UpdateOption mergeUpdateOption) { + ItemEntity.mergeUpdateOption = mergeUpdateOption; } @Override - public void update() { - if (isMergeable() && isPickable()) { + public void update(long time) { + if (isMergeable() && isPickable() && + (mergeUpdateOption == null || !CooldownUtils.hasCooldown(time, lastMergeCheck, mergeUpdateOption))) { + this.lastMergeCheck = time; + Chunk chunk = instance.getChunkAt(getPosition()); Set entities = instance.getChunkEntities(chunk); for (Entity entity : entities) { @@ -49,30 +85,30 @@ public class ItemEntity extends ObjectEntity { if (getDistance(itemEntity) > mergeRange) continue; - synchronized (this) { - synchronized (itemEntity) { - ItemStack itemStackEntity = itemEntity.getItemStack(); + // Use the class as a monitor to prevent deadlock + // Shouldn't happen too often to be an issue + synchronized (ItemEntity.class) { + final ItemStack itemStackEntity = itemEntity.getItemStack(); - StackingRule stackingRule = itemStack.getStackingRule(); - boolean canStack = stackingRule.canBeStacked(itemStack, itemStackEntity); + final StackingRule stackingRule = itemStack.getStackingRule(); + final boolean canStack = stackingRule.canBeStacked(itemStack, itemStackEntity); - if (!canStack) - continue; + if (!canStack) + continue; - int totalAmount = stackingRule.getAmount(itemStack) + stackingRule.getAmount(itemStackEntity); - boolean canApply = stackingRule.canApply(itemStack, totalAmount); + final int totalAmount = stackingRule.getAmount(itemStack) + stackingRule.getAmount(itemStackEntity); + final boolean canApply = stackingRule.canApply(itemStack, totalAmount); - if (!canApply) - continue; + if (!canApply) + continue; - EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity); - callCancellableEvent(EntityItemMergeEvent.class, entityItemMergeEvent, () -> { - ItemStack result = stackingRule.apply(itemStack.clone(), totalAmount); - setItemStack(result); - itemEntity.remove(); - }); + final ItemStack result = stackingRule.apply(itemStack.clone(), totalAmount); - } + EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity, result); + callCancellableEvent(EntityItemMergeEvent.class, entityItemMergeEvent, () -> { + setItemStack(entityItemMergeEvent.getResult()); + itemEntity.remove(); + }); } } diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 73a07dc8f..733276e59 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -1,8 +1,8 @@ package net.minestom.server.entity; +import net.minestom.server.attribute.Attribute; import net.minestom.server.collision.BoundingBox; import net.minestom.server.entity.damage.DamageType; -import net.minestom.server.entity.property.Attribute; import net.minestom.server.event.entity.EntityDamageEvent; import net.minestom.server.event.entity.EntityDeathEvent; import net.minestom.server.event.entity.EntityFireEvent; @@ -12,6 +12,7 @@ import net.minestom.server.inventory.EquipmentHandler; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.play.*; +import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.sound.Sound; import net.minestom.server.sound.SoundCategory; import net.minestom.server.utils.Position; @@ -64,14 +65,14 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { } @Override - public void update() { + public void update(long time) { if (isOnFire()) { - if (System.currentTimeMillis() > fireExtinguishTime) { + if (time > fireExtinguishTime) { setOnFire(false); } else { - if (System.currentTimeMillis() - lastFireDamageTime > fireDamagePeriod) { + if (time - lastFireDamageTime > fireDamagePeriod) { damage(DamageType.ON_FIRE, 1.0f); - lastFireDamageTime = System.currentTimeMillis(); + lastFireDamageTime = time; } } } @@ -91,6 +92,7 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { ItemEntity itemEntity = (ItemEntity) entity; if (!itemEntity.isPickable()) continue; + BoundingBox itemBoundingBox = itemEntity.getBoundingBox(); if (livingBoundingBox.intersect(itemBoundingBox)) { synchronized (itemEntity) { @@ -149,10 +151,20 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { } } + /** + * Get the amount of arrows in the entity + * + * @return the arrow count + */ public int getArrowCount() { return arrowCount; } + /** + * Change the amount of arrow stuck in the entity + * + * @param arrowCount the arrow count + */ public void setArrowCount(int arrowCount) { this.arrowCount = arrowCount; sendMetadataIndex(11); @@ -214,6 +226,8 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { * @return true if damage has been applied, false if it didn't */ public boolean damage(DamageType type, float value) { + if (isDead()) + return false; if (isImmune(type)) { return false; } @@ -273,10 +287,20 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { return false; } + /** + * Get the entity health + * + * @return the entity health + */ public float getHealth() { return health; } + /** + * Change the entity health, kill it if {@code health} is <= 0 and is not dead yet + * + * @param health the new entity health + */ public void setHealth(float health) { health = Math.min(health, getMaxHealth()); @@ -287,6 +311,11 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { sendMetadataIndex(8); // Health metadata index } + /** + * Get the entity max health from {@link #getAttributeValue(Attribute)} {@link Attribute#MAX_HEALTH} + * + * @return the entity max health + */ public float getMaxHealth() { return getAttributeValue(Attribute.MAX_HEALTH); } @@ -320,6 +349,15 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { } // Equipments + public void syncEquipments(PlayerConnection connection) { + for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) { + EntityEquipmentPacket entityEquipmentPacket = getEquipmentPacket(slot); + if (entityEquipmentPacket == null) + return; + connection.sendPacket(entityEquipmentPacket); + } + } + public void syncEquipments() { for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) { syncEquipment(slot); @@ -345,6 +383,8 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { } /** + * Get if the entity is dead or not + * * @return true if the entity is dead, false otherwise */ public boolean isDead() { @@ -352,6 +392,8 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { } /** + * Get if the entity is able to pickup items + * * @return true if the entity is able to pickup items */ public boolean canPickupItem() { @@ -373,6 +415,28 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { this.expandedBoundingBox = getBoundingBox().expand(1, 0.5f, 1); } + /** + * Send a {@link EntityAnimationPacket} to swing the main hand + * (can be used for attack animation) + */ + public void swingMainHand() { + EntityAnimationPacket animationPacket = new EntityAnimationPacket(); + animationPacket.entityId = getEntityId(); + animationPacket.animation = EntityAnimationPacket.Animation.SWING_MAIN_ARM; + sendPacketToViewers(animationPacket); + } + + /** + * Send a {@link EntityAnimationPacket} to swing the off hand + * (can be used for attack animation) + */ + public void swingOffHand() { + EntityAnimationPacket animationPacket = new EntityAnimationPacket(); + animationPacket.entityId = getEntityId(); + animationPacket.animation = EntityAnimationPacket.Animation.SWING_OFF_HAND; + sendPacketToViewers(animationPacket); + } + public void refreshActiveHand(boolean isHandActive, boolean offHand, boolean riptideSpinAttack) { this.isHandActive = isHandActive; this.offHand = offHand; @@ -392,14 +456,14 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { int length = Attribute.values().length; EntityPropertiesPacket.Property[] properties = new EntityPropertiesPacket.Property[length]; for (int i = 0; i < length; i++) { - Attribute attribute = Attribute.values()[i]; EntityPropertiesPacket.Property property = new EntityPropertiesPacket.Property(); - float maxValue = attribute.getMaxVanillaValue(); - float value = getAttributeValue(attribute); - value = value > maxValue ? maxValue : value; // Bypass vanilla limit client-side if needed (by sending the max value allowed) - property.key = attribute.getKey(); + Attribute attribute = Attribute.values()[i]; + float value = getAttributeValue(attribute); + + property.attribute = attribute; property.value = value; + properties[i] = property; } @@ -421,11 +485,23 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { } } + /** + * Get the time in ms between two fire damage applications + * + * @return the time in ms + */ public long getFireDamagePeriod() { return fireDamagePeriod; } - public void setFireDamagePeriod(long fireDamagePeriod) { + /** + * Change the delay between two fire damage applications + * + * @param fireDamagePeriod the delay + * @param timeUnit the time unit + */ + public void setFireDamagePeriod(long fireDamagePeriod, TimeUnit timeUnit) { + fireDamagePeriod = timeUnit.toMilliseconds(fireDamagePeriod); this.fireDamagePeriod = fireDamagePeriod; } } diff --git a/src/main/java/net/minestom/server/entity/ObjectEntity.java b/src/main/java/net/minestom/server/entity/ObjectEntity.java index 9ad11b812..9f88082cc 100644 --- a/src/main/java/net/minestom/server/entity/ObjectEntity.java +++ b/src/main/java/net/minestom/server/entity/ObjectEntity.java @@ -19,7 +19,7 @@ public abstract class ObjectEntity extends Entity { public abstract int getObjectData(); @Override - public void update() { + public void update(long time) { } @@ -30,6 +30,10 @@ public abstract class ObjectEntity extends Entity { @Override public boolean addViewer(Player player) { + boolean result = super.addViewer(player); + if (!result) + return false; + PlayerConnection playerConnection = player.getPlayerConnection(); SpawnEntityPacket spawnEntityPacket = new SpawnEntityPacket(); @@ -46,7 +50,7 @@ public abstract class ObjectEntity extends Entity { playerConnection.sendPacket(getPassengersPacket()); } - return super.addViewer(player); // Add player to viewers list + return result; } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 8ea4f191a..e2ec8adb9 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -3,13 +3,14 @@ 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.collision.BoundingBox; import net.minestom.server.command.CommandManager; +import net.minestom.server.command.CommandSender; import net.minestom.server.effects.Effects; import net.minestom.server.entity.damage.DamageType; -import net.minestom.server.entity.property.Attribute; import net.minestom.server.entity.vehicle.PlayerVehicleInformation; import net.minestom.server.event.inventory.InventoryOpenEvent; import net.minestom.server.event.item.ItemDropEvent; @@ -31,6 +32,7 @@ import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.RecipeManager; +import net.minestom.server.resourcepack.ResourcePack; import net.minestom.server.scoreboard.BelowNameScoreboard; import net.minestom.server.scoreboard.Team; import net.minestom.server.sound.Sound; @@ -51,9 +53,10 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -public class Player extends LivingEntity { +public class Player extends LivingEntity implements CommandSender { private long lastKeepAlive; + private boolean answerKeepAlive; private String username; protected PlayerConnection playerConnection; @@ -61,6 +64,7 @@ public class Player extends LivingEntity { private int latency; private String displayName; + private PlayerSkin skin; private Dimension dimension; private GameMode gameMode; @@ -144,6 +148,9 @@ public class Player extends LivingEntity { setCanPickupItem(true); // By default + // Allow the server to send the next keep alive packet + refreshAnswerKeepAlive(true); + this.gameMode = GameMode.SURVIVAL; this.dimension = Dimension.OVERWORLD; this.levelType = LevelType.DEFAULT; @@ -161,6 +168,11 @@ public class Player extends LivingEntity { */ protected void init() { + // Init player (register events) + for (Consumer playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) { + playerInitialization.accept(this); + } + // TODO complete login sequence with optionals packets JoinGamePacket joinGamePacket = new JoinGamePacket(); joinGamePacket.entityId = getEntityId(); @@ -185,21 +197,11 @@ public class Player extends LivingEntity { spawnPositionPacket.z = 0; playerConnection.sendPacket(spawnPositionPacket); - // Add player to list - String jsonDisplayName = displayName != null ? Chat.toJsonString(Chat.fromLegacyText(displayName)) : null; - String property = ""; - PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER); - PlayerInfoPacket.AddPlayer addPlayer = new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), getGameMode(), getLatency()); - addPlayer.displayName = jsonDisplayName; - PlayerInfoPacket.AddPlayer.Property prop = new PlayerInfoPacket.AddPlayer.Property("textures", property); - addPlayer.properties.add(prop); - playerInfoPacket.playerInfos.add(addPlayer); - playerConnection.sendPacket(playerInfoPacket); - - // Init player - for (Consumer playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) { - playerInitialization.accept(this); - } + // Add player to list with spawning skin + PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent(this); + callEvent(PlayerSkinInitEvent.class, skinInitEvent); + this.skin = skinInitEvent.getSkin(); + playerConnection.sendPacket(getAddPlayerToList()); // Commands start { @@ -241,17 +243,21 @@ public class Player extends LivingEntity { playerConnection.sendPacket(getPropertiesPacket()); // Send default properties refreshHealth(); // Heal and send health packet refreshAbilities(); // Send abilities packet + getInventory().update(); } /** * Used to initialize the player connection - * mostly used by {@link net.minestom.server.entity.fakeplayer.FakePlayer} */ protected void playerConnectionInit() { + this.playerConnection.setPlayer(this); } @Override public boolean damage(DamageType type, float value) { + if (isInvulnerable()) + return false; + // Compute final heart based on health and additional hearts boolean result = super.damage(type, value); if (result) { @@ -261,7 +267,7 @@ public class Player extends LivingEntity { } @Override - public void update() { + public void update(long time) { // Flush all pending packets playerConnection.flush(); @@ -271,13 +277,14 @@ public class Player extends LivingEntity { packet.process(this); } - super.update(); // Super update (item pickup/fire management) + super.update(time); // Super update (item pickup/fire management) // Target block stage if (targetCustomBlock != null) { - final int animationCount = 10; - long since = System.currentTimeMillis() - targetBlockTime; - byte stage = (byte) (since / (blockBreakTime / animationCount) - 1); + final byte animationCount = 10; + long since = time - targetBlockTime; + byte stage = (byte) (since / (blockBreakTime / animationCount)); + stage = MathUtils.setBetween(stage, (byte) -1, animationCount); if (stage != targetLastStage) { sendBlockBreakAnimation(targetBlockPosition, stage); } @@ -311,7 +318,7 @@ public class Player extends LivingEntity { // Eating animation if (isEating()) { - if (System.currentTimeMillis() - startEatingTime >= eatingTime) { + if (time - startEatingTime >= eatingTime) { refreshEating(false); triggerStatus((byte) 9); // Mark item use as finished @@ -325,7 +332,7 @@ public class Player extends LivingEntity { boolean isFood = foodItem.getMaterial().isFood(); if (isFood) { - PlayerEatEvent playerEatEvent = new PlayerEatEvent(foodItem); + PlayerEatEvent playerEatEvent = new PlayerEatEvent(this, foodItem); callEvent(PlayerEatEvent.class, playerEatEvent); } } @@ -334,11 +341,10 @@ public class Player extends LivingEntity { // Tick event callEvent(PlayerTickEvent.class, playerTickEvent); - // Multiplayer sync - if (!getViewers().isEmpty()) { - final boolean positionChanged = position.getX() != lastX || position.getZ() != lastZ || position.getY() != lastY; - final boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch; + final boolean positionChanged = position.getX() != lastX || position.getY() != lastY || position.getZ() != lastZ; + final boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch; + if (!getViewers().isEmpty() && (positionChanged || viewChanged)) { ServerPacket updatePacket = null; ServerPacket optionalUpdatePacket = null; if (positionChanged && viewChanged) { @@ -425,6 +431,10 @@ public class Player extends LivingEntity { super.kill(); } + /** + * Respawn the player by sending a {@link RespawnPacket} to the player and teleporting him + * to {@link #getRespawnPoint()}. It also reset fire and his health + */ public void respawn() { if (!isDead()) return; @@ -437,7 +447,7 @@ public class Player extends LivingEntity { respawnPacket.gameMode = getGameMode(); respawnPacket.levelType = getLevelType(); getPlayerConnection().sendPacket(respawnPacket); - PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(getRespawnPoint()); + PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this, getRespawnPoint()); callEvent(PlayerRespawnEvent.class, respawnEvent); refreshIsDead(false); @@ -458,6 +468,7 @@ public class Player extends LivingEntity { @Override public void remove() { super.remove(); + this.packets.clear(); clearBossBars(); if (getOpenInventory() != null) getOpenInventory().removeViewer(this); @@ -467,7 +478,8 @@ public class Player extends LivingEntity { chunk.removeViewer(this); }); resetTargetBlock(); - callEvent(PlayerDisconnectEvent.class, new PlayerDisconnectEvent()); + callEvent(PlayerDisconnectEvent.class, new PlayerDisconnectEvent(this)); + playerConnection.disconnect(); } @Override @@ -476,34 +488,11 @@ public class Player extends LivingEntity { return false; boolean result = super.addViewer(player); + if (!result) + return false; + PlayerConnection viewerConnection = player.getPlayerConnection(); - String property = "eyJ0aW1lc3RhbXAiOjE1NjU0ODMwODQwOTYsInByb2ZpbGVJZCI6ImFiNzBlY2I0MjM0NjRjMTRhNTJkN2EwOTE1MDdjMjRlIiwicHJvZmlsZU5hbWUiOiJUaGVNb2RlOTExIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RkOTE2NzJiNTE0MmJhN2Y3MjA2ZTRjN2IwOTBkNzhlM2Y1ZDc2NDdiNWFmZDIyNjFhZDk4OGM0MWI2ZjcwYTEifX19"; - SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket(); - spawnPlayerPacket.entityId = getEntityId(); - spawnPlayerPacket.playerUuid = getUuid(); - spawnPlayerPacket.position = getPosition(); - - PlayerInfoPacket pInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER); - PlayerInfoPacket.AddPlayer addP = new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), getGameMode(), 10); - PlayerInfoPacket.AddPlayer.Property p = new PlayerInfoPacket.AddPlayer.Property("textures", property);//new PlayerInfoPacket.AddPlayer.Property("textures", properties.get(onlinePlayer.getUsername())); - addP.properties.add(p); - pInfoPacket.playerInfos.add(addP); - - viewerConnection.sendPacket(pInfoPacket); - viewerConnection.sendPacket(spawnPlayerPacket); - viewerConnection.sendPacket(getVelocityPacket()); - viewerConnection.sendPacket(getMetadataPacket()); - - // Equipments synchronization - syncEquipments(); - - if (hasPassenger()) { - viewerConnection.sendPacket(getPassengersPacket()); - } - - // Team - if (team != null) - viewerConnection.sendPacket(team.getTeamsCreationPacket()); + showPlayer(viewerConnection); return result; } @@ -514,9 +503,7 @@ public class Player extends LivingEntity { boolean result = super.removeViewer(player); PlayerConnection viewerConnection = player.getPlayerConnection(); - PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER); - playerInfoPacket.playerInfos.add(new PlayerInfoPacket.RemovePlayer(getUuid())); - viewerConnection.sendPacket(playerInfoPacket); + viewerConnection.sendPacket(getRemovePlayerToList()); // Team if (team != null && team.getPlayers().size() == 1) // If team only contains "this" player @@ -591,7 +578,17 @@ public class Player extends LivingEntity { } } + /** + * Send a {@link BlockBreakAnimationPacket} packet to the player and his viewers + * Setting {@code destroyStage} to -1 reset the break animation + * + * @param blockPosition the position of the block + * @param destroyStage the destroy stage + * @throws IllegalArgumentException if {@code destroyStage} is not between -1 and 10 + */ public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) { + Check.argCondition(!MathUtils.isBetween(destroyStage, -1, 10), + "The destroy stage has to be between -1 and 10"); BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket(); breakAnimationPacket.entityId = getEntityId() + 1; breakAnimationPacket.blockPosition = blockPosition; @@ -600,16 +597,28 @@ public class Player extends LivingEntity { } // Use legacy color formatting + @Override public void sendMessage(String message) { sendMessage(Chat.fromLegacyText(message)); } + /** + * Send a message to the player + * + * @param message the message to send + * @param colorChar the character used to represent the color + */ public void sendMessage(String message, char colorChar) { sendMessage(Chat.fromLegacyText(message, colorChar)); } - public void sendMessage(Component textObject) { - String json = Chat.toJsonString(textObject); + /** + * Send a message to the player + * + * @param component the text component + */ + public void sendMessage(Component component) { + String json = Chat.toJsonString(component); playerConnection.sendPacket(new ChatMessagePacket(json, ChatMessagePacket.Position.CHAT)); } @@ -644,6 +653,9 @@ public class Player extends LivingEntity { playerConnection.sendPacket(packet); } + /** + * Send a {@link StopSoundPacket} packet + */ public void stopSound() { StopSoundPacket stopSoundPacket = new StopSoundPacket(); stopSoundPacket.flags = 0x00; @@ -746,15 +758,30 @@ public class Player extends LivingEntity { sendUpdateHealthPacket(); } + /** + * Get the player additional hearts + * + * @return the player additional hearts + */ public float getAdditionalHearts() { return additionalHearts; } + /** + * Update the internal field and send the appropriate {@link EntityMetaDataPacket} + * + * @param additionalHearts the count of additional heartss + */ public void setAdditionalHearts(float additionalHearts) { this.additionalHearts = additionalHearts; sendMetadataIndex(14); } + /** + * Get the player food + * + * @return the player food + */ public int getFood() { return food; } @@ -786,14 +813,18 @@ public class Player extends LivingEntity { } /** - * @return true if the player is currently eating, false otherwise + * Get if the player is eating + * + * @return true if the player is eating, false otherwise */ public boolean isEating() { return isEating; } /** - * @return the default eating time for the player + * Get the player default eating time + * + * @return the player default eating time */ public long getDefaultEatingTime() { return defaultEatingTime; @@ -809,31 +840,21 @@ public class Player extends LivingEntity { } /** - * Used to change the player gamemode + * Get the player display name in the tab-list * - * @param gameMode the new player gamemode - */ - public void setGameMode(GameMode gameMode) { - Check.notNull(gameMode, "GameMode cannot be null"); - this.gameMode = gameMode; - sendChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.getId()); - - PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE); - infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateGamemode(getUuid(), gameMode)); - sendPacketToViewersAndSelf(infoPacket); - } - - /** - * @return the displayed name of the player in the tab-list, - * null means that {@link #getUsername()} is display + * @return the player display name, + * null means that {@link #getUsername()} is displayed */ public String getDisplayName() { return displayName; } /** - * @param displayName the new displayed name of the player in the tab-list, - * set to null to show the player username + * Change the player display name in the tab-list + *

+ * Set to null to show the player username + * + * @param displayName the display name */ public void setDisplayName(String displayName) { this.displayName = displayName; @@ -844,15 +865,91 @@ public class Player extends LivingEntity { sendPacketToViewersAndSelf(infoPacket); } + /** + * Get the player skin + * + * @return the player skin object, + * null means that the player has his {@link #getUuid()} default skin + */ + public PlayerSkin getSkin() { + return skin; + } + + /** + * Change the player skin + * + * @param skin the player skin, null to reset it to his {@link #getUuid()} default skin + */ + public synchronized void setSkin(PlayerSkin skin) { + this.skin = skin; + + DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(); + destroyEntitiesPacket.entityIds = new int[]{getEntityId()}; + + PlayerInfoPacket removePlayerPacket = getRemovePlayerToList(); + PlayerInfoPacket addPlayerPacket = getAddPlayerToList(); + + RespawnPacket respawnPacket = new RespawnPacket(); + respawnPacket.dimension = getDimension(); + respawnPacket.gameMode = getGameMode(); + respawnPacket.levelType = getLevelType(); + + playerConnection.sendPacket(removePlayerPacket); + playerConnection.sendPacket(destroyEntitiesPacket); + playerConnection.sendPacket(respawnPacket); + playerConnection.sendPacket(addPlayerPacket); + + for (Player viewer : getViewers()) { + PlayerConnection connection = viewer.getPlayerConnection(); + + connection.sendPacket(removePlayerPacket); + connection.sendPacket(destroyEntitiesPacket); + + showPlayer(connection); + } + + getInventory().update(); + teleport(getPosition()); + } + + /** + * Used to update the internal skin field + * + * @param skin the player skin + * @see #setSkin(PlayerSkin) instead + */ + protected void refreshSkin(PlayerSkin skin) { + this.skin = skin; + } + + /** + * Get if the player has the respawn screen enabled or disabled + * + * @return true if the player has the respawn screen, false if he didn't + */ public boolean isEnableRespawnScreen() { return enableRespawnScreen; } + /** + * Enable or disable the respawn screen + * + * @param enableRespawnScreen true to enable the respawn screen, false to disable it + */ public void setEnableRespawnScreen(boolean enableRespawnScreen) { this.enableRespawnScreen = enableRespawnScreen; sendChangeGameStatePacket(ChangeGameStatePacket.Reason.ENABLE_RESPAWN_SCREEN, enableRespawnScreen ? 0 : 1); } + /** + * Get the player username + * + * @return the player username + */ + public String getUsername() { + return username; + } + private void sendChangeGameStatePacket(ChangeGameStatePacket.Reason reason, float value) { ChangeGameStatePacket changeGameStatePacket = new ChangeGameStatePacket(); changeGameStatePacket.reason = reason; @@ -870,6 +967,23 @@ public class Player extends LivingEntity { return !itemDropEvent.isCancelled(); } + /** + * Set the player resource pack + * + * @param resourcePack the resource pack + */ + public void setResourcePack(ResourcePack resourcePack) { + Check.notNull(resourcePack, "The resource pack cannot be null"); + final String url = resourcePack.getUrl(); + Check.notNull(url, "The resource pack url cannot be null"); + final String hash = resourcePack.getHash(); + + ResourcePackSendPacket resourcePackSendPacket = new ResourcePackSendPacket(); + resourcePackSendPacket.url = url; + resourcePackSendPacket.hash = hash; + playerConnection.sendPacket(resourcePackSendPacket); + } + /** * Rotate the player to face {@code targetPosition} * @@ -937,13 +1051,17 @@ public class Player extends LivingEntity { /** * Change the default spawn point * - * @param respawnPoint + * @param respawnPoint the player respawn point */ public void setRespawnPoint(Position respawnPoint) { this.respawnPoint = respawnPoint; } - public void refreshAfterTeleport() { + /** + * Called after the player teleportation to refresh his position + * and send data to his new viewers + */ + protected void refreshAfterTeleport() { getInventory().update(); SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket(); @@ -951,10 +1069,14 @@ public class Player extends LivingEntity { spawnPlayerPacket.playerUuid = getUuid(); spawnPlayerPacket.position = getPosition(); sendPacketToViewers(spawnPlayerPacket); + + // Update for viewers + sendPacketToViewersAndSelf(getVelocityPacket()); sendPacketToViewersAndSelf(getMetadataPacket()); playerConnection.sendPacket(getPropertiesPacket()); - sendUpdateHealthPacket(); syncEquipments(); + + sendSynchronization(); } protected void refreshHealth() { @@ -1054,15 +1176,20 @@ public class Player extends LivingEntity { teleport(position, null); } - public String getUsername() { - return username; - } - + /** + * Get the player connection + *

+ * Used to send packets and get relatives stuff to the connection + * + * @return the player connection + */ public PlayerConnection getPlayerConnection() { return playerConnection; } /** + * Get if the player is online or not + * * @return true if the player is online, false otherwise */ public boolean isOnline() { @@ -1070,12 +1197,23 @@ public class Player extends LivingEntity { } /** + * Get the player settings + * * @return the player settings */ public PlayerSettings getSettings() { return settings; } + /** + * Get the player dimension + * + * @return the player current dimension + */ + public Dimension getDimension() { + return dimension; + } + public PlayerInventory getInventory() { return inventory; } @@ -1091,19 +1229,29 @@ public class Player extends LivingEntity { } /** - * @return the player current dimension - */ - public Dimension getDimension() { - return dimension; - } - - /** + * Get the player GameMode + * * @return the player current gamemode */ public GameMode getGameMode() { return gameMode; } + /** + * Change the player GameMode + * + * @param gameMode the new player GameMode + */ + public void setGameMode(GameMode gameMode) { + Check.notNull(gameMode, "GameMode cannot be null"); + this.gameMode = gameMode; + sendChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.getId()); + + PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE); + infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateGamemode(getUuid(), gameMode)); + sendPacketToViewersAndSelf(infoPacket); + } + /** * Returns true iff this player is in creative. Used for code readability * @@ -1131,13 +1279,23 @@ public class Player extends LivingEntity { playerConnection.sendPacket(respawnPacket); } - public void kick(Component message) { + /** + * Kick the player with a reason + * + * @param textComponent the kick reason + */ + public void kick(TextComponent textComponent) { DisconnectPacket disconnectPacket = new DisconnectPacket(); - disconnectPacket.message = Chat.toJsonString(message); + disconnectPacket.message = Chat.toJsonString(textComponent); playerConnection.sendPacket(disconnectPacket); playerConnection.disconnect(); } + /** + * Kick the player with a reason + * + * @param message the kick reason + */ public void kick(String message) { kick(Chat.fromLegacyText(message)); } @@ -1162,6 +1320,8 @@ public class Player extends LivingEntity { } /** + * Get the player held slot (0-8) + * * @return the current held slot for the player */ public short getHeldSlot() { @@ -1200,6 +1360,8 @@ public class Player extends LivingEntity { } /** + * Get the player open inventory + * * @return the currently open inventory, null if there is not (player inventory is not detected) */ public Inventory getOpenInventory() { @@ -1247,7 +1409,7 @@ public class Player extends LivingEntity { openWindowPacket.title = newInventory.getTitle(); playerConnection.sendPacket(openWindowPacket); newInventory.addViewer(this); - refreshOpenInventory(newInventory); + this.openInventory = newInventory; }); @@ -1282,13 +1444,15 @@ public class Player extends LivingEntity { } else { closeWindowPacket.windowId = openInventory.getWindowId(); openInventory.removeViewer(this); // Clear cache - refreshOpenInventory(null); + this.openInventory = null; } playerConnection.sendPacket(closeWindowPacket); inventory.update(); } /** + * Get the player viewable chunks + * * @return an unmodifiable {@link Set} containing all the chunks that the player sees */ public Set getViewableChunks() { @@ -1302,8 +1466,15 @@ public class Player extends LivingEntity { this.bossBars.forEach(bossBar -> bossBar.removeViewer(this)); } + /** + * Send a {@link UpdateViewPositionPacket} to the player + * + * @param chunk the chunk to update the view + */ public void updateViewPosition(Chunk chunk) { - UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket(chunk); + UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket(); + updateViewPositionPacket.chunkX = chunk.getChunkX(); + updateViewPositionPacket.chunkZ = chunk.getChunkZ(); playerConnection.sendPacket(updateViewPositionPacket); } @@ -1319,6 +1490,8 @@ public class Player extends LivingEntity { } /** + * Get the player permission level + * * @return the player permission level */ public int getPermissionLevel() { @@ -1340,6 +1513,11 @@ public class Player extends LivingEntity { triggerStatus(permissionLevelStatus); } + /** + * Set or remove the reduced debug screen + * + * @param reduced should the player has the reduced debug screen + */ public void setReducedDebugScreenInformation(boolean reduced) { this.reducedDebugScreenInformation = reduced; @@ -1348,20 +1526,38 @@ public class Player extends LivingEntity { triggerStatus(debugScreenStatus); } + /** + * Get if the player has the reduced debug screen + * + * @return true if the player has the reduced debug screen, false otherwise + */ public boolean hasReducedDebugScreenInformation() { return reducedDebugScreenInformation; } + /** + * The invulnerable field appear in the {@link PlayerAbilitiesPacket} packet + * + * @return true if the player is invulnerable, false otherwise + */ public boolean isInvulnerable() { return invulnerable; } + /** + * This do update the {@code invulnerable} field in the packet {@link PlayerAbilitiesPacket} + * and prevent the player from receiving damage + * + * @param invulnerable should the player be invulnerable + */ public void setInvulnerable(boolean invulnerable) { this.invulnerable = invulnerable; refreshAbilities(); } /** + * Get if the player is currently flying + * * @return true if the player if flying, false otherwise */ public boolean isFlying() { @@ -1369,18 +1565,30 @@ public class Player extends LivingEntity { } /** + * Set the player flying + * * @param flying should the player fly */ public void setFlying(boolean flying) { - refreshFlying(flying); + this.flying = flying; refreshAbilities(); } + /** + * Update the internal flying field + *

+ * Mostly unsafe since there is nothing to backup the value, used internally for creative players + * + * @param flying the new flying field + * @see #setFlying(boolean) instead + */ public void refreshFlying(boolean flying) { this.flying = flying; } /** + * Get if the player is allowed to fly + * * @return true if the player if allowed to fly, false otherwise */ public boolean isAllowFlying() { @@ -1388,6 +1596,8 @@ public class Player extends LivingEntity { } /** + * Allow or forbid the player to fly + * * @param allowFlying should the player be allowed to fly */ public void setAllowFlying(boolean allowFlying) { @@ -1412,10 +1622,20 @@ public class Player extends LivingEntity { refreshAbilities(); } + /** + * Get the player flying speed + * + * @return the flying speed of the player + */ public float getFlyingSpeed() { return flyingSpeed; } + /** + * Update the internal field and send a {@link PlayerAbilitiesPacket} with the new flying speed + * + * @param flyingSpeed the new flying speed of the player + */ public void setFlyingSpeed(float flyingSpeed) { this.flyingSpeed = flyingSpeed; refreshAbilities(); @@ -1440,6 +1660,11 @@ public class Player extends LivingEntity { return statisticValueMap; } + /** + * Get the player vehicle information + * + * @return the player vehicle information + */ public PlayerVehicleInformation getVehicleInformation() { return vehicleInformation; } @@ -1457,7 +1682,7 @@ public class Player extends LivingEntity { } /** - * All packets in the queue are executed in the {@link #update()} method + * All packets in the queue are executed in the {@link #update(long)} method * It is used internally to add all received packet from the client * Could be used to "simulate" a received packet, but to use at your own risk * @@ -1467,6 +1692,11 @@ public class Player extends LivingEntity { this.packets.add(packet); } + /** + * Change the storage player latency and update its tab value + * + * @param latency the new player latency + */ public void refreshLatency(int latency) { this.latency = latency; PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY); @@ -1478,10 +1708,35 @@ public class Player extends LivingEntity { this.onGround = onGround; } + /** + * Used to change internally the last sent last keep alive id + *

+ * Warning: could lead to have the player kicked because of a wrong keep alive packet + * + * @param lastKeepAlive the new lastKeepAlive id + */ public void refreshKeepAlive(long lastKeepAlive) { this.lastKeepAlive = lastKeepAlive; + this.answerKeepAlive = false; } + public boolean didAnswerKeepAlive() { + return answerKeepAlive; + } + + public void refreshAnswerKeepAlive(boolean answerKeepAlive) { + this.answerKeepAlive = answerKeepAlive; + } + + /** + * Change the held item for the player viewers + * Also cancel eating if {@link #isEating()} was true + *

+ * Warning: the player will not be noticed by this chance, only his viewers, + * see instead: {@link #setHeldItemSlot(short)} + * + * @param slot the new held slot + */ public void refreshHeldSlot(short slot) { this.heldSlot = slot; syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND); @@ -1489,10 +1744,6 @@ public class Player extends LivingEntity { refreshEating(false); } - protected void refreshOpenInventory(Inventory openInventory) { - this.openInventory = openInventory; - } - public void refreshEating(boolean isEating, long eatingTime) { this.isEating = isEating; if (isEating) { @@ -1507,6 +1758,14 @@ public class Player extends LivingEntity { refreshEating(isEating, defaultEatingTime); } + /** + * Used to call {@link ItemUpdateStateEvent} with the proper item + * It does check which hand to get the item to update + * + * @param allowFood true if food should be updated, false otherwise + * @return the called {@link ItemUpdateStateEvent}, + * null if there is no item to update the state + */ public ItemUpdateStateEvent callItemUpdateStateEvent(boolean allowFood) { Material mainHandMat = Material.fromId(getItemInMainHand().getMaterialId()); Material offHandMat = Material.fromId(getItemInOffHand().getMaterialId()); @@ -1529,25 +1788,55 @@ public class Player extends LivingEntity { return itemUpdateStateEvent; } - public void refreshTargetBlock(CustomBlock targetCustomBlock, BlockPosition targetBlockPosition, int breakTime) { + /** + * Make the player digging a custom block, see {@link #resetTargetBlock()} to rewind + * + * @param targetCustomBlock the custom block to dig + * @param targetBlockPosition the custom block position + * @param breakTime the time it will take to break the block in milliseconds + */ + public void setTargetBlock(CustomBlock targetCustomBlock, BlockPosition targetBlockPosition, int breakTime) { this.targetCustomBlock = targetCustomBlock; this.targetBlockPosition = targetBlockPosition; this.targetBlockTime = targetBlockPosition == null ? 0 : System.currentTimeMillis(); this.blockBreakTime = breakTime; } + /** + * Reset data from the current block the player is mining. + * If the currently mined block (or if there isn't any) is not a CustomBlock, nothing append + */ public void resetTargetBlock() { - if (targetBlockPosition != null) + if (targetBlockPosition != null) { sendBlockBreakAnimation(targetBlockPosition, (byte) -1); // Clear the break animation - this.targetCustomBlock = null; - this.targetBlockPosition = null; - this.targetBlockTime = 0; + this.targetCustomBlock = null; + this.targetBlockPosition = null; + this.targetBlockTime = 0; + + // Remove effect + RemoveEntityEffectPacket removeEntityEffectPacket = new RemoveEntityEffectPacket(); + removeEntityEffectPacket.entityId = getEntityId(); + removeEntityEffectPacket.effectId = 4; + getPlayerConnection().sendPacket(removeEntityEffectPacket); + } } + /** + * Used internally to add Bossbar in the {@link #getBossBars()} set + * You probably want to use {@link BossBar#addViewer(Player)} + * + * @param bossBar the bossbar to add internally + */ public void refreshAddBossbar(BossBar bossBar) { this.bossBars.add(bossBar); } + /** + * Used internally to remove Bossbar from the {@link #getBossBars()} set + * You probably want to use {@link BossBar#removeViewer(Player)} + * + * @param bossBar the bossbar to remove internally + */ public void refreshRemoveBossbar(BossBar bossBar) { this.bossBars.remove(bossBar); } @@ -1556,6 +1845,11 @@ public class Player extends LivingEntity { this.vehicleInformation.refresh(sideways, forward, jump, unmount); } + /** + * @return the chunk range of the viewers, + * which is {@link MinecraftServer#CHUNK_VIEW_DISTANCE} or {@link PlayerSettings#getViewDistance()} + * based on which one is the lowest + */ public int getChunkRange() { int serverRange = MinecraftServer.CHUNK_VIEW_DISTANCE; int playerRange = getSettings().viewDistance; @@ -1566,10 +1860,86 @@ public class Player extends LivingEntity { } } + /** + * Get the last sent keep alive id + * + * @return the last keep alive id sent to the player + */ public long getLastKeepAlive() { return lastKeepAlive; } + /** + * Get the packet to add the player from tab-list + * + * @return a {@link PlayerInfoPacket} to add the player + */ + protected PlayerInfoPacket getAddPlayerToList() { + 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; + PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER); + + PlayerInfoPacket.AddPlayer addPlayer = + new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), getGameMode(), getLatency()); + addPlayer.displayName = jsonDisplayName; + + PlayerInfoPacket.AddPlayer.Property prop = + new PlayerInfoPacket.AddPlayer.Property("textures", textures, signature); + addPlayer.properties.add(prop); + + playerInfoPacket.playerInfos.add(addPlayer); + return playerInfoPacket; + } + + /** + * Get the packet to remove the player from tab-list + * + * @return a {@link PlayerInfoPacket} to add the player + */ + protected PlayerInfoPacket getRemovePlayerToList() { + PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER); + + PlayerInfoPacket.RemovePlayer removePlayer = + new PlayerInfoPacket.RemovePlayer(getUuid()); + + playerInfoPacket.playerInfos.add(removePlayer); + return playerInfoPacket; + } + + /** + * Send all the related packet to have the player sent to another with related data + * (create player, spawn position, velocity, metadata, equipments, passengers, team) + *

+ * WARNING: this does not sync the player, please use {@link #addViewer(Player)} + * + * @param connection the connection to show the player to + */ + protected void showPlayer(PlayerConnection connection) { + SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket(); + spawnPlayerPacket.entityId = getEntityId(); + spawnPlayerPacket.playerUuid = getUuid(); + spawnPlayerPacket.position = getPosition(); + + connection.sendPacket(getAddPlayerToList()); + + connection.sendPacket(spawnPlayerPacket); + connection.sendPacket(getVelocityPacket()); + connection.sendPacket(getMetadataPacket()); + + // Equipments synchronization + syncEquipments(connection); + + if (hasPassenger()) { + connection.sendPacket(getPassengersPacket()); + } + + // Team + if (team != null) + connection.sendPacket(team.getTeamsCreationPacket()); + } + @Override public ItemStack getItemInMainHand() { return inventory.getItemInMainHand(); @@ -1630,13 +2000,24 @@ public class Player extends LivingEntity { inventory.setBoots(itemStack); } + /** + * Represent the main or off hand of the player + */ public enum Hand { MAIN, OFF } + public enum FacePoint { + FEET, + EYE + } + // Settings enum + /** + * Represent where is located the main hand of the player (can be changed in Minecraft option) + */ public enum MainHand { LEFT, RIGHT @@ -1648,11 +2029,6 @@ public class Player extends LivingEntity { HIDDEN } - public enum FacePoint { - FEET, - EYE - } - public class PlayerSettings { private String locale; @@ -1662,18 +2038,38 @@ public class Player extends LivingEntity { private byte displayedSkinParts; private MainHand mainHand; + /** + * The player game language + * + * @return the player locale + */ public String getLocale() { return locale; } + /** + * Get the player view distance + * + * @return the player view distance + */ public byte getViewDistance() { return viewDistance; } + /** + * Get the player chat mode + * + * @return the player chat mode + */ public ChatMode getChatMode() { return chatMode; } + /** + * Get if the player has chat colors enabled + * + * @return true if chat colors are enabled, false otherwise + */ public boolean hasChatColors() { return chatColors; } @@ -1682,10 +2078,27 @@ public class Player extends LivingEntity { return displayedSkinParts; } + /** + * Get the player main hand + * + * @return the player main hand + */ public MainHand getMainHand() { return mainHand; } + /** + * Change the player settings internally + *

+ * WARNING: the player will not be noticed by this change, probably unsafe + * + * @param locale the player locale + * @param viewDistance the player view distance + * @param chatMode the player chat mode + * @param chatColors the player chat colors + * @param displayedSkinParts the player displayed skin parts + * @param mainHand the player main handœ + */ public void refresh(String locale, byte viewDistance, ChatMode chatMode, boolean chatColors, byte displayedSkinParts, MainHand mainHand) { this.locale = locale; this.viewDistance = viewDistance; diff --git a/src/main/java/net/minestom/server/entity/PlayerSkin.java b/src/main/java/net/minestom/server/entity/PlayerSkin.java new file mode 100644 index 000000000..c5d4e788f --- /dev/null +++ b/src/main/java/net/minestom/server/entity/PlayerSkin.java @@ -0,0 +1,94 @@ +package net.minestom.server.entity; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import net.minestom.server.utils.url.URLUtils; + +import java.io.IOException; +import java.util.Iterator; + +/** + * Contains all the data required to store a skin + */ +public class PlayerSkin { + + private String textures; + private String signature; + + public PlayerSkin(String textures, String signature) { + this.textures = textures; + this.signature = signature; + } + + /** + * Get the skin textures value + * + * @return the textures value + */ + public String getTextures() { + return textures; + } + + /** + * Get the skin signature + * + * @return the skin signature + */ + public String getSignature() { + return signature; + } + + /** + * Get a skin from a Mojang UUID + * + * @param uuid Mojang UUID + * @return a player skin based on the UUID + */ + public static PlayerSkin fromUuid(String uuid) { + final String url = "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false"; + + try { + final String response = URLUtils.getText(url); + JsonObject jsonObject = (new JsonParser()).parse(response).getAsJsonObject(); + JsonArray propertiesArray = jsonObject.get("properties").getAsJsonArray(); + + Iterator iterator = propertiesArray.iterator(); + while (iterator.hasNext()) { + JsonObject propertyObject = iterator.next().getAsJsonObject(); + final String name = propertyObject.get("name").getAsString(); + if (!name.equals("textures")) + continue; + final String textureValue = propertyObject.get("value").getAsString(); + final String signatureValue = propertyObject.get("signature").getAsString(); + return new PlayerSkin(textureValue, signatureValue); + } + return null; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Get a skin from a Minecraft username + * + * @param username the Minecraft username + * @return a skin based on a Minecraft username + */ + public static PlayerSkin fromUsername(String username) { + final String url = "https://api.mojang.com/users/profiles/minecraft/" + username; + + try { + final String response = URLUtils.getText(url); + JsonObject jsonObject = (new JsonParser()).parse(response).getAsJsonObject(); + final String uuid = jsonObject.get("id").getAsString(); + return fromUuid(uuid); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java index 39df149e3..74564f77b 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageType.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -39,10 +39,14 @@ public class DamageType { return TranslatableComponent.of("death." + identifier, TextComponent.of(killed.getUsername())); } - public static DamageType fromPlayer(Player player) { + public static EntityDamage fromPlayer(Player player) { return new EntityDamage(player); } + public static EntityDamage fromEntity(Entity entity) { + return new EntityDamage(entity); + } + public Component buildDeathScreenMessage(Player killed) { return buildChatMessage(killed); } diff --git a/src/main/java/net/minestom/server/entity/damage/EntityDamage.java b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java index 76e033630..1bc785b7c 100644 --- a/src/main/java/net/minestom/server/entity/damage/EntityDamage.java +++ b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java @@ -14,6 +14,9 @@ public class EntityDamage extends DamageType { this.source = source; } + /** + * @return the source of the damage + */ public Entity getSource() { return source; } diff --git a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java index 7222a608e..f0af18071 100644 --- a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java +++ b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java @@ -22,6 +22,8 @@ public class FakePlayer extends Player { public FakePlayer(UUID uuid, String username, boolean addInCache) { super(uuid, username, new FakePlayerConnection()); + this.fakePlayerController = new FakePlayerController(this); + this.registered = addInCache; if (registered) { @@ -39,13 +41,6 @@ public class FakePlayer extends Player { this(uuid, username, false); } - @Override - protected void playerConnectionInit() { - FakePlayerConnection playerConnection = (FakePlayerConnection) getPlayerConnection(); - playerConnection.setFakePlayer(this); - this.fakePlayerController = new FakePlayerController(this); - } - public FakePlayerController getController() { return fakePlayerController; } diff --git a/src/main/java/net/minestom/server/entity/type/EntityAreaEffectCloud.java b/src/main/java/net/minestom/server/entity/type/EntityAreaEffectCloud.java new file mode 100644 index 000000000..89882b7ef --- /dev/null +++ b/src/main/java/net/minestom/server/entity/type/EntityAreaEffectCloud.java @@ -0,0 +1,118 @@ +package net.minestom.server.entity.type; + +import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.ObjectEntity; +import net.minestom.server.network.packet.PacketWriter; +import net.minestom.server.particle.Particle; +import net.minestom.server.utils.Position; + +import java.util.function.Consumer; + +public class EntityAreaEffectCloud extends ObjectEntity { + + public Consumer particleDataConsumer; + private float radius; + private int color; + private boolean ignoreRadius; + private Particle particle; + + public EntityAreaEffectCloud(Position spawnPosition) { + super(EntityType.AREA_EFFECT_CLOUD, spawnPosition); + setRadius(0.5f); + setColor(0); + setIgnoreRadius(false); + setParticle(Particle.EFFECT); + setParticleDataConsumer(packetWriter -> { + }); + } + + @Override + public Consumer getMetadataConsumer() { + return packet -> { + super.getMetadataConsumer().accept(packet); + fillMetadataIndex(packet, 7); + fillMetadataIndex(packet, 8); + fillMetadataIndex(packet, 9); + fillMetadataIndex(packet, 10); + }; + } + + @Override + protected void fillMetadataIndex(PacketWriter packet, int index) { + super.fillMetadataIndex(packet, index); + if (index == 7) { + packet.writeByte((byte) 7); + packet.writeByte(METADATA_FLOAT); + packet.writeFloat(radius); + } else if (index == 8) { + packet.writeByte((byte) 8); + packet.writeByte(METADATA_VARINT); + packet.writeVarInt(color); + } else if (index == 9) { + packet.writeByte((byte) 9); + packet.writeByte(METADATA_BOOLEAN); + packet.writeBoolean(ignoreRadius); + } else if (index == 10) { + packet.writeByte((byte) 10); + packet.writeByte(METADATA_PARTICLE); + packet.writeVarInt(particle.getId()); + particleDataConsumer.accept(packet); + } + } + + @Override + public int getObjectData() { + return 0; + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + setBoundingBox(2 * radius, 0.5f, 2 * radius); + sendMetadataIndex(7); + } + + public int getColor() { + return color; + } + + public void setColor(int color) { + this.color = color; + sendMetadataIndex(8); + } + + public boolean isIgnoreRadius() { + return ignoreRadius; + } + + public void setIgnoreRadius(boolean ignoreRadius) { + this.ignoreRadius = ignoreRadius; + sendMetadataIndex(9); + } + + public Particle getParticle() { + return particle; + } + + public void setParticle(Particle particle) { + this.particle = particle; + sendMetadataIndex(10); + } + + public Consumer getParticleDataConsumer() { + return particleDataConsumer; + } + + /** + * Used to add data to the particle + * + * @param particleDataConsumer the particle data consumer + * @see @see Particle data + */ + public void setParticleDataConsumer(Consumer particleDataConsumer) { + this.particleDataConsumer = particleDataConsumer; + } +} diff --git a/src/main/java/net/minestom/server/entity/type/EntityArmorStand.java b/src/main/java/net/minestom/server/entity/type/EntityArmorStand.java index 8687e3cdf..f17830609 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityArmorStand.java +++ b/src/main/java/net/minestom/server/entity/type/EntityArmorStand.java @@ -8,6 +8,7 @@ import net.minestom.server.inventory.EquipmentHandler; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; +import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; import net.minestom.server.utils.item.ItemStackUtils; @@ -63,7 +64,7 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { @Override public boolean addViewer(Player player) { boolean result = super.addViewer(player); - syncEquipments(); + syncEquipments(player.getPlayerConnection()); return result; } @@ -316,6 +317,15 @@ public class EntityArmorStand extends ObjectEntity implements EquipmentHandler { } // Equipments + public void syncEquipments(PlayerConnection connection) { + for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) { + EntityEquipmentPacket entityEquipmentPacket = getEquipmentPacket(slot); + if (entityEquipmentPacket == null) + return; + connection.sendPacket(entityEquipmentPacket); + } + } + public void syncEquipments() { for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) { syncEquipment(slot); diff --git a/src/main/java/net/minestom/server/entity/type/EntityBat.java b/src/main/java/net/minestom/server/entity/type/EntityBat.java index ad12dac9a..a47c5cd55 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityBat.java +++ b/src/main/java/net/minestom/server/entity/type/EntityBat.java @@ -13,6 +13,7 @@ public class EntityBat extends EntityCreature { public EntityBat(Position spawnPosition) { super(EntityType.BAT, spawnPosition); + setBoundingBox(0.5f, 0.9f, 0.5f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityBlaze.java b/src/main/java/net/minestom/server/entity/type/EntityBlaze.java index 5cf5674a9..f22ebb040 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityBlaze.java +++ b/src/main/java/net/minestom/server/entity/type/EntityBlaze.java @@ -11,6 +11,7 @@ public class EntityBlaze extends EntityCreature { public EntityBlaze(Position spawnPosition) { super(EntityType.BLAZE, spawnPosition); + setBoundingBox(0.6f, 1.8f, 0.6f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityBoat.java b/src/main/java/net/minestom/server/entity/type/EntityBoat.java index 33ffcfb35..32b6fc2b9 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityBoat.java +++ b/src/main/java/net/minestom/server/entity/type/EntityBoat.java @@ -22,16 +22,6 @@ public class EntityBoat extends ObjectEntity { return 0; } - @Override - public void update() { - - } - - @Override - public void spawn() { - - } - @Override public Consumer getMetadataConsumer() { return packet -> { diff --git a/src/main/java/net/minestom/server/entity/type/EntityCaveSpider.java b/src/main/java/net/minestom/server/entity/type/EntityCaveSpider.java new file mode 100644 index 000000000..680ed21b6 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/type/EntityCaveSpider.java @@ -0,0 +1,12 @@ +package net.minestom.server.entity.type; + +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.EntityType; +import net.minestom.server.utils.Position; + +public class EntityCaveSpider extends EntityCreature { + public EntityCaveSpider(Position spawnPosition) { + super(EntityType.CAVE_SPIDER, spawnPosition); + setBoundingBox(0.7f, 0.5f, 0.7f); + } +} diff --git a/src/main/java/net/minestom/server/entity/type/EntityChicken.java b/src/main/java/net/minestom/server/entity/type/EntityChicken.java index c9cb32965..009e4aaa7 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityChicken.java +++ b/src/main/java/net/minestom/server/entity/type/EntityChicken.java @@ -7,5 +7,6 @@ import net.minestom.server.utils.Position; public class EntityChicken extends EntityCreature { public EntityChicken(Position spawnPosition) { super(EntityType.CHICKEN, spawnPosition); + setBoundingBox(0.4f, 0.7f, 0.4f); } } diff --git a/src/main/java/net/minestom/server/entity/type/EntityCow.java b/src/main/java/net/minestom/server/entity/type/EntityCow.java index 91fea081b..a77449f45 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityCow.java +++ b/src/main/java/net/minestom/server/entity/type/EntityCow.java @@ -7,5 +7,6 @@ import net.minestom.server.utils.Position; public class EntityCow extends EntityCreature { public EntityCow(Position spawnPosition) { super(EntityType.COW, spawnPosition); + setBoundingBox(0.9f, 1.4f, 0.9f); } } diff --git a/src/main/java/net/minestom/server/entity/type/EntityCreeper.java b/src/main/java/net/minestom/server/entity/type/EntityCreeper.java index c9e570e0c..715bb413d 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityCreeper.java +++ b/src/main/java/net/minestom/server/entity/type/EntityCreeper.java @@ -15,6 +15,7 @@ public class EntityCreeper extends EntityCreature { public EntityCreeper(Position spawnPosition) { super(EntityType.CREEPER, spawnPosition); + setBoundingBox(0.6f, 1.7f, 0.6f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityEndermite.java b/src/main/java/net/minestom/server/entity/type/EntityEndermite.java index 84ea5bac5..d37ef50cf 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityEndermite.java +++ b/src/main/java/net/minestom/server/entity/type/EntityEndermite.java @@ -7,5 +7,6 @@ import net.minestom.server.utils.Position; public class EntityEndermite extends EntityCreature { public EntityEndermite(Position spawnPosition) { super(EntityType.ENDERMITE, spawnPosition); + setBoundingBox(0.4f, 0.3f, 0.4f); } } diff --git a/src/main/java/net/minestom/server/entity/type/EntityGhast.java b/src/main/java/net/minestom/server/entity/type/EntityGhast.java index 352f5352a..16a3a9166 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityGhast.java +++ b/src/main/java/net/minestom/server/entity/type/EntityGhast.java @@ -13,6 +13,7 @@ public class EntityGhast extends EntityCreature { public EntityGhast(Position spawnPosition) { super(EntityType.GHAST, spawnPosition); + setBoundingBox(4, 4, 4); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityGiant.java b/src/main/java/net/minestom/server/entity/type/EntityGiant.java index d18220f21..cb062a525 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityGiant.java +++ b/src/main/java/net/minestom/server/entity/type/EntityGiant.java @@ -7,5 +7,6 @@ import net.minestom.server.utils.Position; public class EntityGiant extends EntityCreature { public EntityGiant(Position spawnPosition) { super(EntityType.GIANT, spawnPosition); + setBoundingBox(3.6f, 10.8f, 3.6f); } } diff --git a/src/main/java/net/minestom/server/entity/type/EntityGuardian.java b/src/main/java/net/minestom/server/entity/type/EntityGuardian.java new file mode 100644 index 000000000..4c68a1f6d --- /dev/null +++ b/src/main/java/net/minestom/server/entity/type/EntityGuardian.java @@ -0,0 +1,59 @@ +package net.minestom.server.entity.type; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.EntityType; +import net.minestom.server.network.packet.PacketWriter; +import net.minestom.server.utils.Position; + +import java.util.function.Consumer; + +public class EntityGuardian extends EntityCreature { + + private boolean retractingSpikes; + private Entity target; + + public EntityGuardian(Position spawnPosition) { + super(EntityType.GUARDIAN, spawnPosition); + setBoundingBox(0.85f, 0.85f, 0.85f); + } + + @Override + public Consumer getMetadataConsumer() { + return packet -> { + super.getMetadataConsumer().accept(packet); + fillMetadataIndex(packet, 15); + fillMetadataIndex(packet, 16); + }; + } + + @Override + protected void fillMetadataIndex(PacketWriter packet, int index) { + super.fillMetadataIndex(packet, index); + if (index == 15) { + packet.writeByte((byte) 15); + packet.writeByte(METADATA_BOOLEAN); + packet.writeBoolean(retractingSpikes); + } else if (index == 16) { + packet.writeByte((byte) 16); + packet.writeByte(METADATA_VARINT); + packet.writeVarInt(target == null ? 0 : target.getEntityId()); + } + } + + public boolean isRetractingSpikes() { + return retractingSpikes; + } + + public void setRetractingSpikes(boolean retractingSpikes) { + this.retractingSpikes = retractingSpikes; + } + + public Entity getTarget() { + return target; + } + + public void setTarget(Entity target) { + this.target = target; + } +} diff --git a/src/main/java/net/minestom/server/entity/type/EntityIronGolem.java b/src/main/java/net/minestom/server/entity/type/EntityIronGolem.java index 1d39e880f..b3123ce81 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityIronGolem.java +++ b/src/main/java/net/minestom/server/entity/type/EntityIronGolem.java @@ -13,6 +13,7 @@ public class EntityIronGolem extends EntityCreature { public EntityIronGolem(Position spawnPosition) { super(EntityType.IRON_GOLEM, spawnPosition); + setBoundingBox(1.4f, 2.7f, 1.4f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityMooshroom.java b/src/main/java/net/minestom/server/entity/type/EntityMooshroom.java index 198d000c0..1e978776b 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityMooshroom.java +++ b/src/main/java/net/minestom/server/entity/type/EntityMooshroom.java @@ -13,6 +13,7 @@ public class EntityMooshroom extends EntityCreature { public EntityMooshroom(Position spawnPosition) { super(EntityType.MOOSHROOM, spawnPosition); + setBoundingBox(0.9f, 1.4f, 0.9f); setMooshroomType(MooshroomType.RED); } diff --git a/src/main/java/net/minestom/server/entity/type/EntityPhantom.java b/src/main/java/net/minestom/server/entity/type/EntityPhantom.java index cec872fd6..cf4dc0938 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityPhantom.java +++ b/src/main/java/net/minestom/server/entity/type/EntityPhantom.java @@ -13,6 +13,7 @@ public class EntityPhantom extends EntityCreature { public EntityPhantom(Position spawnPosition) { super(EntityType.PHANTOM, spawnPosition); + setBoundingBox(0.9f, 0.5f, 0.9f); // TODO change based on size } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityPig.java b/src/main/java/net/minestom/server/entity/type/EntityPig.java index ee793281e..e992205a3 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityPig.java +++ b/src/main/java/net/minestom/server/entity/type/EntityPig.java @@ -13,6 +13,7 @@ public class EntityPig extends EntityCreature { public EntityPig(Position spawnPosition) { super(EntityType.PIG, spawnPosition); + setBoundingBox(0.9f, 0.9f, 0.9f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityPigZombie.java b/src/main/java/net/minestom/server/entity/type/EntityPigZombie.java new file mode 100644 index 000000000..97abe11b0 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/type/EntityPigZombie.java @@ -0,0 +1,61 @@ +package net.minestom.server.entity.type; + +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.EntityType; +import net.minestom.server.network.packet.PacketWriter; +import net.minestom.server.utils.Position; + +import java.util.function.Consumer; + +public class EntityPigZombie extends EntityCreature { + + private boolean baby; + private boolean becomingDrowned; + + public EntityPigZombie(Position spawnPosition) { + super(EntityType.ZOMBIE_PIGMAN, spawnPosition); + setBoundingBox(0.6f, 1.95f, 0.6f); + } + + @Override + public Consumer getMetadataConsumer() { + return packet -> { + super.getMetadataConsumer().accept(packet); + fillMetadataIndex(packet, 15); + fillMetadataIndex(packet, 17); + }; + } + + @Override + protected void fillMetadataIndex(PacketWriter packet, int index) { + super.fillMetadataIndex(packet, index); + if (index == 15) { + packet.writeByte((byte) 15); + packet.writeByte(METADATA_BOOLEAN); + packet.writeBoolean(baby); + } else if (index == 17) { + packet.writeByte((byte) 17); + packet.writeByte(METADATA_BOOLEAN); + packet.writeBoolean(becomingDrowned); + } + } + + public boolean isBaby() { + return baby; + } + + public void setBaby(boolean baby) { + this.baby = baby; + sendMetadataIndex(15); + } + + public boolean isBecomingDrowned() { + return becomingDrowned; + } + + public void setBecomingDrowned(boolean becomingDrowned) { + this.becomingDrowned = becomingDrowned; + sendMetadataIndex(17); + } + +} diff --git a/src/main/java/net/minestom/server/entity/type/EntityPolarBear.java b/src/main/java/net/minestom/server/entity/type/EntityPolarBear.java index b848ce1fa..ac77a6663 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityPolarBear.java +++ b/src/main/java/net/minestom/server/entity/type/EntityPolarBear.java @@ -13,6 +13,7 @@ public class EntityPolarBear extends EntityCreature { public EntityPolarBear(Position spawnPosition) { super(EntityType.POLAR_BEAR, spawnPosition); + setBoundingBox(1.3f, 1.4f, 1.3f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityPotion.java b/src/main/java/net/minestom/server/entity/type/EntityPotion.java index a3c631904..15ee7fbf5 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityPotion.java +++ b/src/main/java/net/minestom/server/entity/type/EntityPotion.java @@ -14,6 +14,7 @@ public class EntityPotion extends ObjectEntity { public EntityPotion(Position spawnPosition, ItemStack potion) { super(EntityType.POTION, spawnPosition); + setBoundingBox(0.25f, 0.25f, 0.25f); setPotion(potion); } diff --git a/src/main/java/net/minestom/server/entity/type/EntityRabbit.java b/src/main/java/net/minestom/server/entity/type/EntityRabbit.java index aebd5c8cf..f84b2afa5 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityRabbit.java +++ b/src/main/java/net/minestom/server/entity/type/EntityRabbit.java @@ -13,6 +13,7 @@ public class EntityRabbit extends EntityCreature { public EntityRabbit(Position spawnPosition) { super(EntityType.RABBIT, spawnPosition); + setBoundingBox(0.4f, 0.5f, 0.4f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntitySilverfish.java b/src/main/java/net/minestom/server/entity/type/EntitySilverfish.java index 9f7d8d5b6..87412dc82 100644 --- a/src/main/java/net/minestom/server/entity/type/EntitySilverfish.java +++ b/src/main/java/net/minestom/server/entity/type/EntitySilverfish.java @@ -7,5 +7,6 @@ import net.minestom.server.utils.Position; public class EntitySilverfish extends EntityCreature { public EntitySilverfish(Position spawnPosition) { super(EntityType.SILVERFISH, spawnPosition); + setBoundingBox(0.4f, 0.3f, 0.4f); } } diff --git a/src/main/java/net/minestom/server/entity/type/EntitySlime.java b/src/main/java/net/minestom/server/entity/type/EntitySlime.java index 6bd92bf12..f7db3dd69 100644 --- a/src/main/java/net/minestom/server/entity/type/EntitySlime.java +++ b/src/main/java/net/minestom/server/entity/type/EntitySlime.java @@ -40,6 +40,8 @@ public class EntitySlime extends EntityCreature { public void setSize(int size) { this.size = size; + final float boxSize = 0.51000005f * size; + setBoundingBox(boxSize, boxSize, boxSize); sendMetadataIndex(15); } } diff --git a/src/main/java/net/minestom/server/entity/type/EntitySnowman.java b/src/main/java/net/minestom/server/entity/type/EntitySnowman.java index 57e74afd1..c7508e7ca 100644 --- a/src/main/java/net/minestom/server/entity/type/EntitySnowman.java +++ b/src/main/java/net/minestom/server/entity/type/EntitySnowman.java @@ -13,6 +13,7 @@ public class EntitySnowman extends EntityCreature { public EntitySnowman(Position spawnPosition) { super(EntityType.SNOW_GOLEM, spawnPosition); + setBoundingBox(0.7f, 1.9f, 0.7f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntitySpider.java b/src/main/java/net/minestom/server/entity/type/EntitySpider.java index c4de9b06a..ea8cf6d8b 100644 --- a/src/main/java/net/minestom/server/entity/type/EntitySpider.java +++ b/src/main/java/net/minestom/server/entity/type/EntitySpider.java @@ -13,6 +13,7 @@ public class EntitySpider extends EntityCreature { public EntitySpider(Position spawnPosition) { super(EntityType.SPIDER, spawnPosition); + setBoundingBox(1.4f, 0.9f, 1.4f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityWitch.java b/src/main/java/net/minestom/server/entity/type/EntityWitch.java index f497c7b34..07fab805f 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityWitch.java +++ b/src/main/java/net/minestom/server/entity/type/EntityWitch.java @@ -13,6 +13,7 @@ public class EntityWitch extends EntityCreature { public EntityWitch(Position spawnPosition) { super(EntityType.WITCH, spawnPosition); + setBoundingBox(0.6f, 1.95f, 0.6f); } @Override diff --git a/src/main/java/net/minestom/server/entity/type/EntityZombie.java b/src/main/java/net/minestom/server/entity/type/EntityZombie.java index 398f7646f..0e2995efa 100644 --- a/src/main/java/net/minestom/server/entity/type/EntityZombie.java +++ b/src/main/java/net/minestom/server/entity/type/EntityZombie.java @@ -14,6 +14,7 @@ public class EntityZombie extends EntityCreature { public EntityZombie(Position spawnPosition) { super(EntityType.ZOMBIE, spawnPosition); + setBoundingBox(0.6f, 1.95f, 0.6f); } @Override diff --git a/src/main/java/net/minestom/server/event/CancellableEvent.java b/src/main/java/net/minestom/server/event/CancellableEvent.java index c03646384..735aa3ac2 100644 --- a/src/main/java/net/minestom/server/event/CancellableEvent.java +++ b/src/main/java/net/minestom/server/event/CancellableEvent.java @@ -4,10 +4,16 @@ public class CancellableEvent extends Event { private boolean cancelled; + /** + * @return true if the event should be cancelled, false otherwise + */ public boolean isCancelled() { return cancelled; } + /** + * @param cancel true if the event should be cancelled, false otherwise + */ public void setCancelled(boolean cancel) { this.cancelled = cancel; } diff --git a/src/main/java/net/minestom/server/event/entity/EntityAttackEvent.java b/src/main/java/net/minestom/server/event/entity/EntityAttackEvent.java index b67a67232..06d6e9606 100644 --- a/src/main/java/net/minestom/server/event/entity/EntityAttackEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntityAttackEvent.java @@ -3,6 +3,10 @@ package net.minestom.server.event.entity; import net.minestom.server.entity.Entity; import net.minestom.server.event.Event; +/** + * Called when a player does a left click on an entity or with + * {@link net.minestom.server.entity.EntityCreature#attack(Entity)} + */ public class EntityAttackEvent extends Event { private Entity source; @@ -13,10 +17,16 @@ public class EntityAttackEvent extends Event { this.target = target; } + /** + * @return the source of the attack + */ public Entity getSource() { return source; } + /** + * @return the target of the attack + */ public Entity getTarget() { return target; } 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 fea3ee4ae..ff5e40e63 100644 --- a/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.entity; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.event.CancellableEvent; +/** + * Called with {@link net.minestom.server.entity.LivingEntity#damage(DamageType, float)} + */ public class EntityDamageEvent extends CancellableEvent { private DamageType damageType; @@ -13,14 +16,23 @@ public class EntityDamageEvent extends CancellableEvent { this.damage = damage; } + /** + * @return the damage type + */ public DamageType getDamageType() { return damageType; } + /** + * @return the damage amount + */ public float getDamage() { return damage; } + /** + * @param damage the new damage amount + */ public void setDamage(float damage) { this.damage = damage; } diff --git a/src/main/java/net/minestom/server/event/entity/EntityDeathEvent.java b/src/main/java/net/minestom/server/event/entity/EntityDeathEvent.java index 6c11e8ba8..923ca98ba 100644 --- a/src/main/java/net/minestom/server/event/entity/EntityDeathEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntityDeathEvent.java @@ -12,6 +12,9 @@ public class EntityDeathEvent extends Event { this.entity = entity; } + /** + * @return the entity that died + */ public Entity getEntity() { return entity; } diff --git a/src/main/java/net/minestom/server/event/entity/EntityItemMergeEvent.java b/src/main/java/net/minestom/server/event/entity/EntityItemMergeEvent.java index 023ae3cfe..ece89d028 100644 --- a/src/main/java/net/minestom/server/event/entity/EntityItemMergeEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntityItemMergeEvent.java @@ -2,22 +2,61 @@ package net.minestom.server.event.entity; import net.minestom.server.entity.ItemEntity; import net.minestom.server.event.CancellableEvent; +import net.minestom.server.item.ItemStack; +/** + * Called when two {@link ItemEntity} are merging their ItemStack together to form a sole entity + */ public class EntityItemMergeEvent extends CancellableEvent { private ItemEntity source; private ItemEntity merged; - public EntityItemMergeEvent(ItemEntity source, ItemEntity merged) { + private ItemStack result; + + public EntityItemMergeEvent(ItemEntity source, ItemEntity merged, ItemStack result) { this.source = source; this.merged = merged; + this.result = result; } + /** + * Get the entity who is receiving {@link #getMerged()} ItemStack + *

+ * This can be used to get the final ItemEntity position + * + * @return the source ItemEntity + */ public ItemEntity getSource() { return source; } + /** + * Get the entity who will be merged + *

+ * This entity will be removed after the event + * + * @return the merged ItemEntity + */ public ItemEntity getMerged() { return merged; } + + /** + * Get the final item stack on the ground + * + * @return the item stack + */ + public ItemStack getResult() { + return result; + } + + /** + * Change the item stack which will appear on the ground + * + * @param result the new item stack + */ + public void setResult(ItemStack result) { + this.result = result; + } } diff --git a/src/main/java/net/minestom/server/event/entity/EntitySpawnEvent.java b/src/main/java/net/minestom/server/event/entity/EntitySpawnEvent.java index baad9ccd8..4f849827f 100644 --- a/src/main/java/net/minestom/server/event/entity/EntitySpawnEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntitySpawnEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.entity; import net.minestom.server.event.Event; import net.minestom.server.instance.Instance; +/** + * Called when a new instance is set for an entity + */ public class EntitySpawnEvent extends Event { private Instance spawnInstance; @@ -11,6 +14,11 @@ public class EntitySpawnEvent extends Event { this.spawnInstance = spawnInstance; } + /** + * Get the entity new instance + * + * @return the instance + */ public Instance getSpawnInstance() { return spawnInstance; } diff --git a/src/main/java/net/minestom/server/event/entity/EntityVelocityEvent.java b/src/main/java/net/minestom/server/event/entity/EntityVelocityEvent.java index 46400395d..fffc36c86 100644 --- a/src/main/java/net/minestom/server/event/entity/EntityVelocityEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntityVelocityEvent.java @@ -14,14 +14,23 @@ public class EntityVelocityEvent extends CancellableEvent { this.velocity = velocity; } + /** + * @return the entity who the velocity is applied to + */ public Entity getEntity() { return entity; } + /** + * @return the velocity which will be applied + */ public Vector getVelocity() { return velocity; } + /** + * @param velocity the new velocity to applies + */ public void setVelocity(Vector velocity) { this.velocity = velocity; } diff --git a/src/main/java/net/minestom/server/event/handler/EventHandler.java b/src/main/java/net/minestom/server/event/handler/EventHandler.java index b737d0595..338ce06d1 100644 --- a/src/main/java/net/minestom/server/event/handler/EventHandler.java +++ b/src/main/java/net/minestom/server/event/handler/EventHandler.java @@ -17,6 +17,15 @@ public interface EventHandler { */ void addEventCallback(Class eventClass, EventCallback eventCallback); + /** + * Remove an event callback + * + * @param eventClass the event class + * @param eventCallback the event callback + * @param the event type + */ + void removeEventCallback(Class eventClass, EventCallback eventCallback); + /** * @param eventClass * @param diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java index bb3416b41..b44043f13 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryClickEvent.java @@ -1,5 +1,6 @@ package net.minestom.server.event.inventory; +import net.minestom.server.entity.Player; import net.minestom.server.event.Event; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.click.ClickType; @@ -7,13 +8,14 @@ import net.minestom.server.item.ItemStack; public class InventoryClickEvent extends Event { + private Player player; private Inventory inventory; private int slot; private ClickType clickType; private ItemStack clickedItem; private ItemStack cursorItem; - public InventoryClickEvent(Inventory inventory, int slot, ClickType clickType, ItemStack clicked, ItemStack cursor) { + public InventoryClickEvent(Player player, Inventory inventory, int slot, ClickType clickType, ItemStack clicked, ItemStack cursor) { this.inventory = inventory; this.slot = slot; this.clickType = clickType; @@ -21,6 +23,15 @@ public class InventoryClickEvent extends Event { this.cursorItem = cursor; } + /** + * Get the player who clicked in the inventory + * + * @return the player who clicked in the inventory + */ + public Player getPlayer() { + return player; + } + /** * Can be null if the clicked inventory is the player one * @@ -30,18 +41,38 @@ public class InventoryClickEvent extends Event { return inventory; } + /** + * Get the clicked slot number + * + * @return the clicked slot number + */ public int getSlot() { return slot; } + /** + * Get the click type + * + * @return the click type + */ public ClickType getClickType() { return clickType; } + /** + * Get the clicked item + * + * @return the clicked item + */ public ItemStack getClickedItem() { return clickedItem; } + /** + * Get the item in the player cursor + * + * @return the cursor item + */ public ItemStack getCursorItem() { return cursorItem; } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java index 6d9c195cc..088f022fa 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryCloseEvent.java @@ -1,24 +1,43 @@ package net.minestom.server.event.inventory; +import net.minestom.server.entity.Player; import net.minestom.server.event.Event; import net.minestom.server.inventory.Inventory; public class InventoryCloseEvent extends Event { + private Player player; private Inventory inventory; private Inventory newInventory; - public InventoryCloseEvent(Inventory inventory) { + public InventoryCloseEvent(Player player, Inventory inventory) { + this.player = player; this.inventory = inventory; } /** + * Get the player who closed the inventory + * + * @return the player who closed the inventory + */ + public Player getPlayer() { + return player; + } + + /** + * Get the closed inventory + * * @return the closed inventory, null if this is the player inventory */ public Inventory getInventory() { return inventory; } + /** + * Get the new inventory to open + * + * @return the new inventory to open, null if there isn't any + */ public Inventory getNewInventory() { return newInventory; } @@ -26,7 +45,7 @@ public class InventoryCloseEvent extends Event { /** * Can be used to open a new inventory after closing the previous one * - * @param newInventory the inventory to open, can be null + * @param newInventory the inventory to open, null to do not open any */ public void setNewInventory(Inventory newInventory) { this.newInventory = newInventory; diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java index bb823b4d3..dd7cb5839 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryOpenEvent.java @@ -3,7 +3,6 @@ package net.minestom.server.event.inventory; import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; import net.minestom.server.inventory.Inventory; -import net.minestom.server.utils.validate.Check; public class InventoryOpenEvent extends CancellableEvent { @@ -15,16 +14,32 @@ public class InventoryOpenEvent extends CancellableEvent { this.inventory = inventory; } + /** + * Get the player who opens the inventory + * + * @return the player who opens the inventory + */ public Player getPlayer() { return player; } + /** + * Get the inventory to open, this could have been change by the {@link #setInventory(Inventory)} + * + * @return the inventory to open + */ public Inventory getInventory() { return inventory; } + /** + * Change the inventory to open. + * to do not open any inventory see {@link #setCancelled(boolean)} + * + * @param inventory the inventory to open + * @throws NullPointerException if {@code inventory} is null + */ public void setInventory(Inventory inventory) { - Check.notNull(inventory, "Inventory cannot be null!"); this.inventory = inventory; } } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java index 99a9c4377..0f0f804c9 100644 --- a/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java +++ b/src/main/java/net/minestom/server/event/inventory/InventoryPreClickEvent.java @@ -1,5 +1,6 @@ package net.minestom.server.event.inventory; +import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.click.ClickType; @@ -8,13 +9,15 @@ import net.minestom.server.utils.item.ItemStackUtils; public class InventoryPreClickEvent extends CancellableEvent { + private Player player; private Inventory inventory; private int slot; private ClickType clickType; private ItemStack clickedItem; private ItemStack cursorItem; - public InventoryPreClickEvent(Inventory inventory, int slot, ClickType clickType, ItemStack clicked, ItemStack cursor) { + public InventoryPreClickEvent(Player player, Inventory inventory, int slot, ClickType clickType, ItemStack clicked, ItemStack cursor) { + this.player = player; this.inventory = inventory; this.slot = slot; this.clickType = clickType; @@ -22,6 +25,15 @@ public class InventoryPreClickEvent extends CancellableEvent { this.cursorItem = cursor; } + /** + * Get the player who is trying to click on the inventory + * + * @return the player who clicked + */ + public Player getPlayer() { + return player; + } + /** * Can be null if the clicked inventory is the player one * @@ -31,26 +43,56 @@ public class InventoryPreClickEvent extends CancellableEvent { return inventory; } + /** + * Get the clicked slot number + * + * @return the clicked slot number + */ public int getSlot() { return slot; } + /** + * Get the click type + * + * @return the click type + */ public ClickType getClickType() { return clickType; } + /** + * Get the item who have been clicked + * + * @return the clicked item + */ public ItemStack getClickedItem() { return clickedItem; } + /** + * Change the clicked item + * + * @param clickedItem the clicked item + */ public void setClickedItem(ItemStack clickedItem) { this.clickedItem = ItemStackUtils.notNull(clickedItem); } + /** + * Get the item who was in the player cursor + * + * @return the cursor item + */ public ItemStack getCursorItem() { return cursorItem; } + /** + * Change the cursor item + * + * @param cursorItem the cursor item + */ public void setCursorItem(ItemStack cursorItem) { this.cursorItem = ItemStackUtils.notNull(cursorItem); } diff --git a/src/main/java/net/minestom/server/event/player/PlayerAddItemStackEvent.java b/src/main/java/net/minestom/server/event/player/PlayerAddItemStackEvent.java new file mode 100644 index 000000000..04e4a1ae7 --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerAddItemStackEvent.java @@ -0,0 +1,47 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.CancellableEvent; +import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.item.ItemStackUtils; + +/** + * Called as a result of {@link net.minestom.server.inventory.PlayerInventory#addItemStack(ItemStack)} + */ +public class PlayerAddItemStackEvent extends CancellableEvent { + + private Player player; + private ItemStack itemStack; + + public PlayerAddItemStackEvent(Player player, ItemStack itemStack) { + this.player = player; + this.itemStack = itemStack; + } + + /** + * Get the player who has an item stack added to his inventory + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the item stack which will be added + * + * @return the item stack + */ + public ItemStack getItemStack() { + return itemStack; + } + + /** + * Change the item stack which will be added + * + * @param itemStack the new item stack + */ + public void setItemStack(ItemStack itemStack) { + this.itemStack = ItemStackUtils.notNull(itemStack); + } +} diff --git a/src/main/java/net/minestom/server/event/player/PlayerBlockBreakEvent.java b/src/main/java/net/minestom/server/event/player/PlayerBlockBreakEvent.java index a149f1175..d16526472 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerBlockBreakEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerBlockBreakEvent.java @@ -1,42 +1,97 @@ package net.minestom.server.event.player; import net.minestom.server.event.CancellableEvent; +import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.utils.BlockPosition; public class PlayerBlockBreakEvent extends CancellableEvent { private BlockPosition blockPosition; + private short blockId; + private CustomBlock customBlock; + private short resultBlockId; private short resultCustomBlockId; - public PlayerBlockBreakEvent(BlockPosition blockPosition, short resultBlockId, short resultCustomBlockId) { + public PlayerBlockBreakEvent(BlockPosition blockPosition, + short blockId, CustomBlock customBlock, + short resultBlockId, short resultCustomBlockId) { this.blockPosition = blockPosition; + + this.blockId = blockId; + this.customBlock = customBlock; + this.resultBlockId = resultBlockId; this.resultCustomBlockId = resultCustomBlockId; } + /** + * Get the block position + * + * @return the block position + */ public BlockPosition getBlockPosition() { return blockPosition; } + /** + * Get the broken block visual id + * + * @return the block id + */ + public short getBlockId() { + return blockId; + } + + /** + * Get the broken custom block + * + * @return the custom block, + * null if not any + */ + public CustomBlock getCustomBlock() { + return customBlock; + } + + /** + * Get the visual block id result, which will be placed after the event + * + * @return the block id that will be set at {@link #getBlockPosition()} + * set to 0 to remove + */ public short getResultBlockId() { return resultBlockId; } + /** + * Change the visual block id result + * + * @param resultBlockId the result block id + */ public void setResultBlockId(short resultBlockId) { this.resultBlockId = resultBlockId; } + /** + * Get the custom block id result, which will be placed after the event + *

+ * Warning: the visual block will not be changed, be sure to call {@link #setResultBlockId(short)} + * if you want the visual to be the same as {@link CustomBlock#getBlockId()} + * + * @return the custom block id that will be set at {@link #getBlockPosition()} + * set to 0 to remove + */ public short getResultCustomBlockId() { return resultCustomBlockId; } + /** + * Change the custom block id result, which will be placed after the event + * + * @param resultCustomBlockId the custom block id result + */ public void setResultCustomBlockId(short resultCustomBlockId) { this.resultCustomBlockId = resultCustomBlockId; } - - public boolean isResultCustomBlock() { - return resultCustomBlockId != 0; - } } diff --git a/src/main/java/net/minestom/server/event/player/PlayerBlockPlaceEvent.java b/src/main/java/net/minestom/server/event/player/PlayerBlockPlaceEvent.java index b95dadba8..05cceb6c9 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerBlockPlaceEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerBlockPlaceEvent.java @@ -1,11 +1,19 @@ package net.minestom.server.event.player; +import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; +import net.minestom.server.instance.block.BlockManager; +import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.utils.BlockPosition; +/** + * Called when a player tries placing a block + */ public class PlayerBlockPlaceEvent extends CancellableEvent { + private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); + private final Player player; private short blockId; private short customBlockId; @@ -23,38 +31,100 @@ public class PlayerBlockPlaceEvent extends CancellableEvent { this.consumeBlock = true; } - public void setCustomBlockId(short customBlockId) { - this.customBlockId = customBlockId; + /** + * Set both the blockId and customBlockId + * + * @param customBlock the custom block to place + */ + public void setCustomBlock(CustomBlock customBlock) { + setBlockId(customBlock.getBlockId()); + setCustomBlockId(customBlock.getCustomBlockId()); } + /** + * Set both the blockId and customBlockId + * + * @param customBlockId the custom block id to place + */ + public void setCustomBlock(short customBlockId) { + CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); + setCustomBlock(customBlock); + } + + /** + * Set both the blockId and customBlockId + * + * @param customBlockId the custom block id to place + */ + public void setCustomBlock(String customBlockId) { + CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); + setCustomBlock(customBlock); + } + + /** + * @return the custom block id to place + */ public short getCustomBlockId() { return customBlockId; } - public void setBlockId(short blockId) { - this.blockId = blockId; + /** + * Set the custom block id to place + *

+ * WARNING: this does not change the visual block id, see {@link #setBlockId(short)} + * or {@link #setCustomBlock(short)} + * + * @param customBlockId + */ + public void setCustomBlockId(short customBlockId) { + this.customBlockId = customBlockId; } + /** + * @return the visual block id to place + */ public short getBlockId() { return blockId; } + /** + * @param blockId the visual block id to place + */ + public void setBlockId(short blockId) { + this.blockId = blockId; + } + + /** + * @return the player who is placing the block + */ public Player getPlayer() { return player; } + /** + * @return the position of the block to place + */ public BlockPosition getBlockPosition() { return blockPosition; } + /** + * @return the hand with which the player is trying to place + */ public Player.Hand getHand() { return hand; } + /** + * @param consumeBlock true if the block should be consumer (-1 amount), false otherwise + */ public void consumeBlock(boolean consumeBlock) { this.consumeBlock = consumeBlock; } + /** + * @return true if the block will be consumed, false otherwise + */ public boolean doesConsumeBlock() { return consumeBlock; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java b/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java index 15126ed55..c8182b194 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerChatEvent.java @@ -8,6 +8,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.function.Function; +/** + * Called every time a player write and send something in the chat. + * The event can be cancelled to do not send anything, and the format can be changed + */ public class PlayerChatEvent extends CancellableEvent { private Player sender; @@ -21,26 +25,52 @@ public class PlayerChatEvent extends CancellableEvent { this.message = message; } + /** + * @param chatFormat the custom chat format + */ public void setChatFormat(Function chatFormat) { this.chatFormat = chatFormat; } + /** + * @return the sender of the message + */ public Player getSender() { return sender; } + /** + * This is all the players who will receive the message + * It can be modified to add or remove recipient + * + * @return a modifiable list of message targets + */ public Collection getRecipients() { return recipients; } + /** + * @return the sender's message + */ public String getMessage() { return message; } + /** + * Used to change the message + * + * @param message the new message + */ public void setMessage(String message) { this.message = message; } + /** + * Used to retrieve the chat format for this message. + * If null, the default format will be used + * + * @return the chat format which will be used + */ public Function getChatFormatFunction() { return chatFormat; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerChunkLoadEvent.java b/src/main/java/net/minestom/server/event/player/PlayerChunkLoadEvent.java index 94f747912..4c2b37fd2 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerChunkLoadEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerChunkLoadEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.player; import net.minestom.server.entity.Player; import net.minestom.server.event.Event; +/** + * Called when a player receive a new chunk data + */ public class PlayerChunkLoadEvent extends Event { private Player player; @@ -14,14 +17,29 @@ public class PlayerChunkLoadEvent extends Event { this.chunkZ = chunkZ; } + /** + * Get the player + * + * @return the player + */ public Player getPlayer() { return player; } + /** + * Get the chunk X + * + * @return the chunk X + */ public int getChunkX() { return chunkX; } + /** + * Get the chunk Z + * + * @return the chunk Z + */ public int getChunkZ() { return chunkZ; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerChunkUnloadEvent.java b/src/main/java/net/minestom/server/event/player/PlayerChunkUnloadEvent.java index 47df65799..dfd4fb7a5 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerChunkUnloadEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerChunkUnloadEvent.java @@ -18,14 +18,29 @@ public class PlayerChunkUnloadEvent extends Event { this.chunkZ = chunkZ; } + /** + * Get the player + * + * @return the player + */ public Player getPlayer() { return player; } + /** + * Get the chunk X + * + * @return the chunk X + */ public int getChunkX() { return chunkX; } + /** + * Get the chunk Z + * + * @return the chunk Z + */ public int getChunkZ() { return chunkZ; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerCommandEvent.java b/src/main/java/net/minestom/server/event/player/PlayerCommandEvent.java index 37d778727..f027fb2c2 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerCommandEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerCommandEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.player; import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; +/** + * Called every time a player send a message starting by '/' + */ public class PlayerCommandEvent extends CancellableEvent { private Player player; @@ -13,14 +16,25 @@ public class PlayerCommandEvent extends CancellableEvent { this.command = command; } + /** + * @return the player who want to execute the command + */ public Player getPlayer() { return player; } + /** + * @return the command that the player wants to execute + */ public String getCommand() { return command; } + /** + * Change the command to execute + * + * @param command the new command + */ public void setCommand(String command) { this.command = command; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerDisconnectEvent.java b/src/main/java/net/minestom/server/event/player/PlayerDisconnectEvent.java index 64eac1dd9..c8aba8239 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerDisconnectEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerDisconnectEvent.java @@ -1,6 +1,25 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.Event; +/** + * Called when a player disconnect + */ public class PlayerDisconnectEvent extends Event { + + private Player player; + + public PlayerDisconnectEvent(Player player) { + this.player = player; + } + + /** + * Get the player who is disconnecting + * + * @return the player + */ + public Player getPlayer() { + return player; + } } diff --git a/src/main/java/net/minestom/server/event/player/PlayerEatEvent.java b/src/main/java/net/minestom/server/event/player/PlayerEatEvent.java index 900cef153..f73a87772 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerEatEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerEatEvent.java @@ -1,16 +1,36 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.Event; import net.minestom.server.item.ItemStack; +/** + * Called when a player is finished eating + */ public class PlayerEatEvent extends Event { + private Player player; private ItemStack foodItem; - public PlayerEatEvent(ItemStack foodItem) { + public PlayerEatEvent(Player player, ItemStack foodItem) { + this.player = player; this.foodItem = foodItem; } + /** + * Get the player who is finished eating + * + * @return the concerned player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the food item that has been eaten + * + * @return the food item + */ public ItemStack getFoodItem() { return foodItem; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerInteractEvent.java b/src/main/java/net/minestom/server/event/player/PlayerInteractEvent.java index d479371da..98a31c4a6 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerInteractEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerInteractEvent.java @@ -4,20 +4,44 @@ import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; import net.minestom.server.event.Event; +/** + * Called when a player interacts (right-click) with an entity + */ public class PlayerInteractEvent extends Event { + private Player player; private Entity entityTarget; private Player.Hand hand; - public PlayerInteractEvent(Entity entityTarget, Player.Hand hand) { + public PlayerInteractEvent(Player player, Entity entityTarget, Player.Hand hand) { + this.player = player; this.entityTarget = entityTarget; this.hand = hand; } + /** + * Get the player who is interacting + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the entity with who {@link #getPlayer()} is interacting + * + * @return the entity + */ public Entity getTarget() { return entityTarget; } + /** + * Get with which hand the player interacted with the entity + * + * @return the hand + */ public Player.Hand getHand() { return hand; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerLoginEvent.java b/src/main/java/net/minestom/server/event/player/PlayerLoginEvent.java index 8428fbf87..49392cdc5 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerLoginEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerLoginEvent.java @@ -1,18 +1,49 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.Event; import net.minestom.server.instance.Instance; +/** + * Called at player login, used to define his spawn instance + *

+ * WARNING: defining the spawning instance is MANDATORY + */ public class PlayerLoginEvent extends Event { + private Player player; private Instance spawningInstance; + public PlayerLoginEvent(Player player) { + this.player = player; + } + + /** + * Get the player who is logging + * + * @return the player who is logging + */ + public Player getPlayer() { + return player; + } + + /** + * Get the spawning instance of the player + *

+ * WARNING: this must NOT be null, otherwise the player cannot spawn + * + * @return the spawning instance + */ public Instance getSpawningInstance() { return spawningInstance; } + /** + * Change the spawning instance + * + * @param instance the new spawning instance + */ public void setSpawningInstance(Instance instance) { this.spawningInstance = instance; } - } diff --git a/src/main/java/net/minestom/server/event/player/PlayerMoveEvent.java b/src/main/java/net/minestom/server/event/player/PlayerMoveEvent.java index b204110f0..d1b3d1957 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerMoveEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerMoveEvent.java @@ -1,16 +1,36 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; import net.minestom.server.utils.Position; +/** + * Called when a player is modifying his position + */ public class PlayerMoveEvent extends CancellableEvent { + private Player player; private Position newPosition; - public PlayerMoveEvent(float x, float y, float z, float yaw, float pitch) { - this.newPosition = new Position(x, y, z, yaw, pitch); + public PlayerMoveEvent(Player player, Position newPosition) { + this.player = player; + this.newPosition = newPosition; } + /** + * Get the player who is moving + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the target position + * + * @return the new position + */ public Position getNewPosition() { return newPosition; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java b/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java index cdac0e548..7ade555ce 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java @@ -1,25 +1,55 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.Event; +/** + * Called when a player send {@link net.minestom.server.network.packet.client.play.ClientPluginMessagePacket} + */ public class PlayerPluginMessageEvent extends Event { + private Player player; private String identifier; private byte[] message; - public PlayerPluginMessageEvent(String identifier, byte[] message) { + public PlayerPluginMessageEvent(Player player, String identifier, byte[] message) { + this.player = player; this.identifier = identifier; this.message = message; } + /** + * Get the player who sent the message + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the message identifier + * + * @return the identifier + */ public String getIdentifier() { return identifier; } + /** + * Get the message data as a byte array + * + * @return the message + */ public byte[] getMessage() { return message; } + /** + * Get the message data as a String + * + * @return the message + */ public String getMessageString() { return new String(message); } diff --git a/src/main/java/net/minestom/server/event/player/PlayerPreEatEvent.java b/src/main/java/net/minestom/server/event/player/PlayerPreEatEvent.java index 8b2dc31c3..85498f9c0 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerPreEatEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerPreEatEvent.java @@ -21,18 +21,40 @@ public class PlayerPreEatEvent extends CancellableEvent { this.eatingTime = eatingTime; } + /** + * The player who is trying to eat + * + * @return the concerned player + */ public Player getPlayer() { return player; } + /** + * The food item which will be eaten + * + * @return the food item + */ public ItemStack getFoodItem() { return foodItem; } + /** + * Get the food eating time + *

+ * This is by default {@link Player#getDefaultEatingTime()} + * + * @return the eating time + */ public long getEatingTime() { return eatingTime; } + /** + * Change the food eating time + * + * @param eatingTime the new eating time + */ public void setEatingTime(long eatingTime) { this.eatingTime = eatingTime; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerResourcePackStatusEvent.java b/src/main/java/net/minestom/server/event/player/PlayerResourcePackStatusEvent.java new file mode 100644 index 000000000..515815e60 --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerResourcePackStatusEvent.java @@ -0,0 +1,37 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.Event; +import net.minestom.server.resourcepack.ResourcePackStatus; + +/** + * Called when a player warns the server of a resource pack status + */ +public class PlayerResourcePackStatusEvent extends Event { + + private Player player; + private ResourcePackStatus status; + + public PlayerResourcePackStatusEvent(Player player, ResourcePackStatus status) { + this.player = player; + this.status = status; + } + + /** + * Get the player who send a resource pack status + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the resource pack status + * + * @return the resource pack status + */ + public ResourcePackStatus getStatus() { + return status; + } +} diff --git a/src/main/java/net/minestom/server/event/player/PlayerRespawnEvent.java b/src/main/java/net/minestom/server/event/player/PlayerRespawnEvent.java index 2ef4aea0e..b9d7b04f6 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerRespawnEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerRespawnEvent.java @@ -1,20 +1,46 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.Event; import net.minestom.server.utils.Position; +/** + * Called when {@link Player#respawn()} is executed (for custom respawn or as a result of + * {@link net.minestom.server.network.packet.client.play.ClientStatusPacket} + */ public class PlayerRespawnEvent extends Event { + private Player player; private Position respawnPosition; - public PlayerRespawnEvent(Position respawnPosition) { + public PlayerRespawnEvent(Player player, Position respawnPosition) { + this.player = player; this.respawnPosition = respawnPosition; } + /** + * Get the player who is respawning + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the respawn position + * + * @return the respawn position + */ public Position getRespawnPosition() { return respawnPosition; } + /** + * Change the respawn position + * + * @param respawnPosition the new respawn position + */ public void setRespawnPosition(Position respawnPosition) { this.respawnPosition = respawnPosition; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerSetItemStackEvent.java b/src/main/java/net/minestom/server/event/player/PlayerSetItemStackEvent.java new file mode 100644 index 000000000..9aadfa62b --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerSetItemStackEvent.java @@ -0,0 +1,69 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.CancellableEvent; +import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.item.ItemStackUtils; + +/** + * Called as a result of {@link net.minestom.server.inventory.PlayerInventory#setItemStack(int, ItemStack)} + * and player click in his inventory + */ +public class PlayerSetItemStackEvent extends CancellableEvent { + + private Player player; + private int slot; + private ItemStack itemStack; + + public PlayerSetItemStackEvent(Player player, int slot, ItemStack itemStack) { + this.player = player; + this.slot = slot; + this.itemStack = itemStack; + } + + /** + * Get the player who has an item stack set to his inventory + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the slot where the item will be set + * + * @return the slot + */ + public int getSlot() { + return slot; + } + + /** + * Change the slot where the item will be set + * + * @param slot the new slot + */ + public void setSlot(int slot) { + this.slot = slot; + } + + /** + * Get the item stack which will be set + * + * @return the item stack + */ + public ItemStack getItemStack() { + return itemStack; + } + + /** + * Change the item stack which will be set + * + * @param itemStack the new item stack + */ + public void setItemStack(ItemStack itemStack) { + this.itemStack = ItemStackUtils.notNull(itemStack); + } + +} diff --git a/src/main/java/net/minestom/server/event/player/PlayerSkinInitEvent.java b/src/main/java/net/minestom/server/event/player/PlayerSkinInitEvent.java new file mode 100644 index 000000000..33e8b854e --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerSkinInitEvent.java @@ -0,0 +1,45 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.Player; +import net.minestom.server.entity.PlayerSkin; +import net.minestom.server.event.Event; + +/** + * Called at the player connection to initialize his skin + */ +public class PlayerSkinInitEvent extends Event { + + private Player player; + private PlayerSkin skin; + + public PlayerSkinInitEvent(Player player) { + this.player = player; + } + + /** + * Get the player whose the skin is getting initialized + * + * @return + */ + public Player getPlayer() { + return player; + } + + /** + * Get the spawning skin of the player + * + * @return the player skin, or null if not any + */ + public PlayerSkin getSkin() { + return skin; + } + + /** + * Set the spawning skin of the player + * + * @param skin the new player skin + */ + public void setSkin(PlayerSkin skin) { + this.skin = skin; + } +} diff --git a/src/main/java/net/minestom/server/event/player/PlayerSpawnEvent.java b/src/main/java/net/minestom/server/event/player/PlayerSpawnEvent.java index dc9cd03f1..5d30d5be8 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerSpawnEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerSpawnEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.player; import net.minestom.server.event.entity.EntitySpawnEvent; import net.minestom.server.instance.Instance; +/** + * Called when a new instance is set for a player + */ public class PlayerSpawnEvent extends EntitySpawnEvent { private final boolean firstSpawn; @@ -13,7 +16,8 @@ public class PlayerSpawnEvent extends EntitySpawnEvent { /** * 'true' if the player is spawning for the first time. 'false' if this spawn event was triggered by a dimension teleport - * @return + * + * @return true if this is the first spawn, false otherwise */ public boolean isFirstSpawn() { return firstSpawn; diff --git a/src/main/java/net/minestom/server/event/player/PlayerStartDiggingEvent.java b/src/main/java/net/minestom/server/event/player/PlayerStartDiggingEvent.java index 0b6ad7536..2f8786e6b 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerStartDiggingEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerStartDiggingEvent.java @@ -1,24 +1,52 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.utils.BlockPosition; +/** + * Called when a player start digging a {@link CustomBlock}, + * can be used to forbid the player from mining a block + *

+ * WARNING: this is not called for non-custom block + */ public class PlayerStartDiggingEvent extends CancellableEvent { + private Player player; private BlockPosition blockPosition; private CustomBlock customBlock; - public PlayerStartDiggingEvent(BlockPosition blockPosition, CustomBlock customBlock) { + public PlayerStartDiggingEvent(Player player, BlockPosition blockPosition, CustomBlock customBlock) { + this.player = player; this.blockPosition = blockPosition; this.customBlock = customBlock; } + /** + * Get the player who started digging the block + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the block position + * + * @return the block position + */ public BlockPosition getBlockPosition() { return blockPosition; } - public CustomBlock getBlock() { + /** + * Get the custom block object that the player is trying to dig + * + * @return the custom block + */ + public CustomBlock getCustomBlock() { return customBlock; } } diff --git a/src/main/java/net/minestom/server/event/player/PlayerStartFlyingEvent.java b/src/main/java/net/minestom/server/event/player/PlayerStartFlyingEvent.java index f7a23dd9f..c112e237c 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerStartFlyingEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerStartFlyingEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.player; import net.minestom.server.entity.Player; import net.minestom.server.event.Event; +/** + * Called when a player start flying + */ public class PlayerStartFlyingEvent extends Event { private Player player; @@ -11,6 +14,11 @@ public class PlayerStartFlyingEvent extends Event { this.player = player; } + /** + * Get the player who started flying + * + * @return the player + */ public Player getPlayer() { return player; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerStopFlyingEvent.java b/src/main/java/net/minestom/server/event/player/PlayerStopFlyingEvent.java index 6787b18bd..391d35dba 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerStopFlyingEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerStopFlyingEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.player; import net.minestom.server.entity.Player; import net.minestom.server.event.Event; +/** + * Called when a player stop flying + */ public class PlayerStopFlyingEvent extends Event { private Player player; @@ -11,6 +14,11 @@ public class PlayerStopFlyingEvent extends Event { this.player = player; } + /** + * Get the player who stopped flying + * + * @return the player + */ public Player getPlayer() { return player; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java b/src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java index 3f099a4e5..3efd92e13 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerSwapItemEvent.java @@ -1,30 +1,65 @@ package net.minestom.server.event.player; +import net.minestom.server.entity.Player; import net.minestom.server.event.CancellableEvent; import net.minestom.server.item.ItemStack; +/** + * Called when a player is trying to swap his main and off hand item + */ public class PlayerSwapItemEvent extends CancellableEvent { + private Player player; private ItemStack mainHandItem; private ItemStack offHandItem; - public PlayerSwapItemEvent(ItemStack mainHandItem, ItemStack offHandItem) { + public PlayerSwapItemEvent(Player player, ItemStack mainHandItem, ItemStack offHandItem) { + this.player = player; this.mainHandItem = mainHandItem; this.offHandItem = offHandItem; } + /** + * Get the player who is trying to swap his hands item + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the item which will be in player main hand after the event + * + * @return the item in main hand + */ public ItemStack getMainHandItem() { return mainHandItem; } + /** + * Change the item which will be in the player main hand + * + * @param mainHandItem the main hand item + */ public void setMainHandItem(ItemStack mainHandItem) { this.mainHandItem = mainHandItem; } + /** + * Get the item which will be in player off hand after the event + * + * @return the item in off hand + */ public ItemStack getOffHandItem() { return offHandItem; } + /** + * Change the item which will be in the player off hand + * + * @param offHandItem the off hand item + */ public void setOffHandItem(ItemStack offHandItem) { this.offHandItem = offHandItem; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerTickEvent.java b/src/main/java/net/minestom/server/event/player/PlayerTickEvent.java index abbf6d90a..7aca9b4c3 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerTickEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerTickEvent.java @@ -3,6 +3,9 @@ package net.minestom.server.event.player; import net.minestom.server.entity.Player; import net.minestom.server.event.Event; +/** + * Called at each player tick + */ public class PlayerTickEvent extends Event { private Player player; @@ -11,6 +14,11 @@ public class PlayerTickEvent extends Event { this.player = player; } + /** + * Get the player + * + * @return the player + */ public Player getPlayer() { return player; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java b/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java index 20c858383..65a9af7de 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerUseItemEvent.java @@ -9,18 +9,39 @@ import net.minestom.server.item.ItemStack; */ public class PlayerUseItemEvent extends CancellableEvent { + private Player player; private Player.Hand hand; private ItemStack itemStack; - public PlayerUseItemEvent(Player.Hand hand, ItemStack itemStack) { + public PlayerUseItemEvent(Player player, Player.Hand hand, ItemStack itemStack) { + this.player = player; this.hand = hand; this.itemStack = itemStack; } + /** + * Get the player who used an item + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get which hand the player used + * + * @return the hand used + */ public Player.Hand getHand() { return hand; } + /** + * Get the item which have been used + * + * @return the item + */ public ItemStack getItemStack() { return itemStack; } diff --git a/src/main/java/net/minestom/server/event/player/PlayerUseItemOnBlockEvent.java b/src/main/java/net/minestom/server/event/player/PlayerUseItemOnBlockEvent.java index 86e417b78..4c10d3f16 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerUseItemOnBlockEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerUseItemOnBlockEvent.java @@ -11,30 +11,61 @@ import net.minestom.server.utils.Direction; */ public class PlayerUseItemOnBlockEvent extends Event { + private Player player; private Player.Hand hand; private ItemStack itemStack; private final BlockPosition position; private final Direction blockFace; - public PlayerUseItemOnBlockEvent(Player.Hand hand, ItemStack itemStack, BlockPosition position, Direction blockFace) { + public PlayerUseItemOnBlockEvent(Player player, Player.Hand hand, ItemStack itemStack, BlockPosition position, Direction blockFace) { + this.player = player; this.hand = hand; this.itemStack = itemStack; this.position = position; this.blockFace = blockFace; } + /** + * Get the player who used an item while clicking on a block + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Get the position of the interacted block + * + * @return the block position + */ public BlockPosition getPosition() { return position; } + /** + * Get which face the player has interacted with + * + * @return the block face + */ public Direction getBlockFace() { return blockFace; } + /** + * Get which hand the player used to interact with the block + * + * @return the hand + */ public Player.Hand getHand() { return hand; } + /** + * Get with which item the player has interacted with the block + * + * @return the item + */ public ItemStack getItemStack() { return itemStack; } diff --git a/src/main/java/net/minestom/server/instance/BlockModifier.java b/src/main/java/net/minestom/server/instance/BlockModifier.java index 76a79d3f3..9e8321f01 100644 --- a/src/main/java/net/minestom/server/instance/BlockModifier.java +++ b/src/main/java/net/minestom/server/instance/BlockModifier.java @@ -7,6 +7,7 @@ import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; +import net.minestom.server.utils.validate.Check; public interface BlockModifier { @@ -53,6 +54,8 @@ public interface BlockModifier { default void setCustomBlock(int x, int y, int z, String customBlockId, Data data) { CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); + Check.notNull(customBlock, "The CustomBlock " + customBlockId + " is not registered"); + setCustomBlock(x, y, z, customBlock.getCustomBlockId(), data); } diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index c529675c7..4782a265e 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -31,7 +31,7 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; // TODO light data & API -public class Chunk implements Viewable { +public final class Chunk implements Viewable { private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); @@ -223,35 +223,33 @@ public class Chunk implements Viewable { return blocksData.get(index); } - public void updateBlocks(long time, Instance instance) { + public synchronized void updateBlocks(long time, Instance instance) { if (updatableBlocks.isEmpty()) return; // Block all chunk operation during the update - synchronized (this) { - IntIterator iterator = new IntOpenHashSet(updatableBlocks).iterator(); - while (iterator.hasNext()) { - int index = iterator.nextInt(); - CustomBlock customBlock = getCustomBlock(index); + IntIterator iterator = new IntOpenHashSet(updatableBlocks).iterator(); + while (iterator.hasNext()) { + final int index = iterator.nextInt(); + final CustomBlock customBlock = getCustomBlock(index); - // Update cooldown - UpdateOption updateOption = customBlock.getUpdateOption(); - long lastUpdate = updatableBlocksLastUpdate.get(index); - boolean hasCooldown = CooldownUtils.hasCooldown(time, lastUpdate, updateOption.getTimeUnit(), updateOption.getValue()); - if (hasCooldown) - continue; + // Update cooldown + final UpdateOption updateOption = customBlock.getUpdateOption(); + final long lastUpdate = updatableBlocksLastUpdate.get(index); + final boolean hasCooldown = CooldownUtils.hasCooldown(time, lastUpdate, updateOption); + if (hasCooldown) + continue; - this.updatableBlocksLastUpdate.put(index, time); // Refresh last update time + this.updatableBlocksLastUpdate.put(index, time); // Refresh last update time - int[] blockPos = ChunkUtils.indexToPosition(index, chunkX, chunkZ); - int x = blockPos[0]; - int y = blockPos[1]; - int z = blockPos[2]; + final int[] blockPos = ChunkUtils.indexToPosition(index, chunkX, chunkZ); + int x = blockPos[0]; + int y = blockPos[1]; + int z = blockPos[2]; - BlockPosition blockPosition = new BlockPosition(x, y, z); - Data data = getData(index); - customBlock.update(instance, blockPosition, data); - } + BlockPosition blockPosition = new BlockPosition(x, y, z); + Data data = getData(index); + customBlock.update(instance, blockPosition, data); } } diff --git a/src/main/java/net/minestom/server/instance/ChunkLoader.java b/src/main/java/net/minestom/server/instance/ChunkLoader.java index 8d258815e..168e06343 100644 --- a/src/main/java/net/minestom/server/instance/ChunkLoader.java +++ b/src/main/java/net/minestom/server/instance/ChunkLoader.java @@ -9,7 +9,7 @@ import java.util.function.Consumer; public class ChunkLoader { private static String getChunkKey(int chunkX, int chunkZ) { - return "chunk_" + chunkX + "." + chunkZ; + return chunkX + "." + chunkZ; } protected void saveChunk(Chunk chunk, StorageFolder storageFolder, Runnable callback) { diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index a0b9118ff..feac75179 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -24,6 +24,7 @@ import net.minestom.server.utils.Position; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.time.TimeUnit; +import net.minestom.server.utils.validate.Check; import net.minestom.server.world.Dimension; import java.util.*; @@ -50,7 +51,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta protected Set experienceOrbs = new CopyOnWriteArraySet<>(); // Entities per chunk protected Map> chunkEntities = new ConcurrentHashMap<>(); - private UUID uniqueId; + protected UUID uniqueId; private Data data; private ExplosionSupplier explosionSupplier; @@ -62,49 +63,143 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta this.worldBorder = new WorldBorder(this); } + /** + * Used to change the id of the block in a specific position. + *

+ * In case of a CustomBlock it does not remove it but only refresh its visual + * + * @param blockPosition the block position + * @param blockId the new block id + */ public abstract void refreshBlockId(BlockPosition blockPosition, short blockId); - // Used to call BlockBreakEvent and sending particle packet if true - public abstract void breakBlock(Player player, BlockPosition blockPosition); + /** + * Does call {@link net.minestom.server.event.player.PlayerBlockBreakEvent} + * and send particle packets + * + * @param player the player who break the block + * @param blockPosition the position of the broken block + * @return true if the block has been broken, false if it has been cancelled + */ + public abstract boolean breakBlock(Player player, BlockPosition blockPosition); - // Force the generation of the chunk, even if no file and ChunkGenerator are defined + /** + * Force the generation of the chunk, even if no file and ChunkGenerator are defined + * + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @param callback consumer called after the chunk has been generated, + * the returned chunk will never be null + */ public abstract void loadChunk(int chunkX, int chunkZ, Consumer callback); - // Load only if auto chunk load is enabled + /** + * Load the chunk if the chunk is already loaded or if + * {@link #hasEnabledAutoChunkLoad()} returns true + * + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @param callback consumer called after the chunk has tried to be loaded, + * contains a chunk if it is successful, null otherwise + */ public abstract void loadOptionalChunk(int chunkX, int chunkZ, Consumer callback); + /** + * Unload a chunk + *

+ * WARNING: all entities other than {@link Player} will be removed + * + * @param chunk the chunk to unload + */ public abstract void unloadChunk(Chunk chunk); + /** + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @return the chunk at the specified position, null if not loaded + */ public abstract Chunk getChunk(int chunkX, int chunkZ); + /** + * @param chunk the chunk to save + * @param callback called when the chunk is done saving + * @throws NullPointerException if {@link #getStorageFolder()} returns null + */ public abstract void saveChunkToStorageFolder(Chunk chunk, Runnable callback); + /** + * @param callback called when the chunks are done saving + * @throws NullPointerException if {@link #getStorageFolder()} returns null + */ public abstract void saveChunksToStorageFolder(Runnable callback); + /** + * @return a BlockBatch linked to the instance + */ public abstract BlockBatch createBlockBatch(); + /** + * @param chunk the chunk to modify + * @return a ChunkBatch linked to {@code chunk} + * @throws NullPointerException if {@code chunk} is null + */ public abstract ChunkBatch createChunkBatch(Chunk chunk); + /** + * @return the chunk generator of the instance + */ + public abstract ChunkGenerator getChunkGenerator(); + + /** + * @param chunkGenerator the new chunk generator of the instance + */ public abstract void setChunkGenerator(ChunkGenerator chunkGenerator); + /** + * @return an unmodifiable containing all the loaded chunks of the instance + */ public abstract Collection getChunks(); + /** + * @return the storage folder of the instance + */ public abstract StorageFolder getStorageFolder(); + /** + * @param storageFolder the new storage folder of the instance + */ public abstract void setStorageFolder(StorageFolder storageFolder); - public abstract void sendChunkUpdate(Player player, Chunk chunk); - protected abstract void retrieveChunk(int chunkX, int chunkZ, Consumer callback); - public abstract void createChunk(int chunkX, int chunkZ, Consumer callback); + protected abstract void createChunk(int chunkX, int chunkZ, Consumer callback); + /** + * Send all chunks data to {@code player} + * + * @param player the player + */ public abstract void sendChunks(Player player); + /** + * Send a specific chunk data to {@code player} + * + * @param player the player + * @param chunk the chunk + */ public abstract void sendChunk(Player player, Chunk chunk); + /** + * When set to true, chunks will load with players moving closer + * Otherwise using {@link #loadChunk(int, int)} will be required to even spawn a player + * + * @param enable enable the auto chunk load + */ public abstract void enableAutoChunkLoad(boolean enable); + /** + * @return true if auto chunk load is enabled, false otherwise + */ public abstract boolean hasEnabledAutoChunkLoad(); /** @@ -117,6 +212,17 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta public abstract boolean isInVoid(Position position); // + + /** + * Send a full {@link ChunkDataPacket} to {@code player} + * + * @param player the player to update the chunk to + * @param chunk the chunk to send + */ + public void sendChunkUpdate(Player player, Chunk chunk) { + player.getPlayerConnection().sendPacket(chunk.getFullDataPacket()); + } + protected void sendChunkUpdate(Collection players, Chunk chunk) { ByteBuf chunkData = chunk.getFullDataPacket(); players.forEach(player -> { @@ -149,32 +255,73 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta // + /** + * Get the instance dimension + * + * @return the dimension of the instance + */ public Dimension getDimension() { return dimension; } + /** + * Get the instance world border + * + * @return the world border linked to the instance + */ public WorldBorder getWorldBorder() { return worldBorder; } + /** + * Get the players in the instance + * + * @return an unmodifiable list containing all the players in the instance + */ public Set getPlayers() { return Collections.unmodifiableSet(players); } + /** + * Get the creatures in the instance + * + * @return an unmodifiable list containing all the creatures in the instance + */ public Set getCreatures() { return Collections.unmodifiableSet(creatures); } + /** + * Get the object entities in the instance + * + * @return an unmodifiable list containing all the object entities in the instance + */ public Set getObjectEntities() { return Collections.unmodifiableSet(objectEntities); } + /** + * Get the experience orbs in the instance + * + * @return an unmodifiable list containing all the experience orbs in the instance + */ public Set getExperienceOrbs() { return Collections.unmodifiableSet(experienceOrbs); } + /** + * Get the entities located in the chunk + * + * @param chunk the chunk to get the entities from + * @return an unmodifiable set containing all the entities in a chunk, + * if {@code chunk} is null, return an empty {@link HashSet} + */ public Set getChunkEntities(Chunk chunk) { - return Collections.unmodifiableSet(getEntitiesInChunk(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()))); + if (chunk == null) + return new HashSet<>(); + + long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); + return Collections.unmodifiableSet(getEntitiesInChunk(index)); } public void refreshBlockId(int x, int y, int z, short blockId) { @@ -279,6 +426,11 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta saveChunksToStorageFolder(null); } + /** + * Get the instance unique id + * + * @return the instance unique id + */ public UUID getUniqueId() { return uniqueId; } @@ -300,6 +452,13 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta this.eventCallbacks.put(eventClass, callbacks); } + @Override + public void removeEventCallback(Class eventClass, EventCallback eventCallback) { + List callbacks = getEventCallbacks(eventClass); + callbacks.remove(eventCallback); + this.eventCallbacks.put(eventClass, callbacks); + } + @Override public List getEventCallbacks(Class eventClass) { return eventCallbacks.getOrDefault(eventClass, new CopyOnWriteArrayList<>()); @@ -307,6 +466,14 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta // UNSAFE METHODS (need most of time to be synchronized) + /** + * Used when called {@link Entity#setInstance(Instance)}, it is used to refresh viewable chunks + * and add viewers if {@code entity} is a Player + *

+ * Warning: unsafe, you probably want to use {@link Entity#setInstance(Instance)} instead + * + * @param entity the entity to add + */ public void addEntity(Entity entity) { Instance lastInstance = entity.getInstance(); if (lastInstance != null && lastInstance != this) { @@ -342,6 +509,13 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta }); } + /** + * Used when an entity is removed from the instance, it removes all of his viewers + *

+ * Warning: unsafe, you probably want to set the entity to another instance + * + * @param entity the entity to remove + */ public void removeEntity(Entity entity) { Instance entityInstance = entity.getInstance(); if (entityInstance == null || entityInstance != this) @@ -359,7 +533,18 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta }); } + /** + * Add the specified entity to the instance entities cache + *

+ * Warning: this is done automatically when the entity move out of his chunk + * + * @param entity the entity to add + * @param chunk the chunk where the entity will be added + */ public void addEntityToChunk(Entity entity, Chunk chunk) { + Check.notNull(chunk, + "The chunk " + chunk + " is not loaded, you can make it automatic by using Instance#enableAutoChunkLoad(true)"); + Check.argCondition(!chunk.isLoaded(), "Chunk " + chunk + " has been unloaded previously"); long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); synchronized (chunkEntities) { Set entities = getEntitiesInChunk(chunkIndex); @@ -378,15 +563,25 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta } } + /** + * Remove the specified entity to the instance entities cache + *

+ * Warning: this is done automatically when the entity move out of his chunk + * + * @param entity the entity to remove + * @param chunk the chunk where the entity will be removed + */ public void removeEntityFromChunk(Entity entity, Chunk chunk) { - long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); synchronized (chunkEntities) { - Set entities = getEntitiesInChunk(chunkIndex); - entities.remove(entity); - if (entities.isEmpty()) { - this.chunkEntities.remove(chunkIndex); - } else { - this.chunkEntities.put(chunkIndex, entities); + if (chunk != null) { + long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); + Set entities = getEntitiesInChunk(chunkIndex); + entities.remove(entity); + if (entities.isEmpty()) { + this.chunkEntities.remove(chunkIndex); + } else { + this.chunkEntities.put(chunkIndex, entities); + } } if (entity instanceof Player) { @@ -428,27 +623,29 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta } /** - * Creates an explosion at the given position with the given strength. The algorithm used to compute damages is provided by {@link #getExplosionSupplier()}. - * If no {@link ExplosionSupplier} was supplied, this method will throw an {@link IllegalStateException} + * Creates an explosion at the given position with the given strength. + * The algorithm used to compute damages is provided by {@link #getExplosionSupplier()}. * * @param centerX * @param centerY * @param centerZ * @param strength + * @throws IllegalStateException If no {@link ExplosionSupplier} was supplied */ public void explode(float centerX, float centerY, float centerZ, float strength) { explode(centerX, centerY, centerZ, strength, null); } /** - * Creates an explosion at the given position with the given strength. The algorithm used to compute damages is provided by {@link #getExplosionSupplier()}. - * If no {@link ExplosionSupplier} was supplied, this method will throw an {@link IllegalStateException} + * Creates an explosion at the given position with the given strength. + * The algorithm used to compute damages is provided by {@link #getExplosionSupplier()}. * * @param centerX * @param centerY * @param centerZ * @param strength * @param additionalData data to pass to the explosion supplier + * @throws IllegalStateException If no {@link ExplosionSupplier} was supplied */ public void explode(float centerX, float centerY, float centerZ, float strength, Data additionalData) { ExplosionSupplier explosionSupplier = getExplosionSupplier(); @@ -461,7 +658,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta /** * Return the registered explosion supplier, or null if none was provided * - * @return + * @return the instance explosion supplier, null if none was provided */ public ExplosionSupplier getExplosionSupplier() { return explosionSupplier; @@ -470,7 +667,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta /** * Registers the explosion supplier to use in this instance * - * @param supplier + * @param supplier the explosion supplier */ public void setExplosionSupplier(ExplosionSupplier supplier) { this.explosionSupplier = supplier; diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index ca83df0b1..9d6c9ef22 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -3,6 +3,7 @@ package net.minestom.server.instance; import io.netty.buffer.ByteBuf; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; +import net.minestom.server.data.SerializableData; import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerBlockBreakEvent; import net.minestom.server.instance.batch.BlockBatch; @@ -24,6 +25,7 @@ import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.UpdateOption; +import net.minestom.server.utils.validate.Check; import net.minestom.server.world.Dimension; import java.util.*; @@ -37,8 +39,12 @@ import java.util.function.Consumer; /** * InstanceContainer is an instance that contains chunks in contrary to SharedInstance. */ +// TODO save data + other things such as UUID public class InstanceContainer extends Instance { + private static final String UUID_KEY = "uuid"; + private static final String DATA_KEY = "data"; + private StorageFolder storageFolder; private List sharedInstances = new CopyOnWriteArrayList<>(); @@ -53,7 +59,16 @@ public class InstanceContainer extends Instance { public InstanceContainer(UUID uniqueId, Dimension dimension, StorageFolder storageFolder) { super(uniqueId, dimension); + this.storageFolder = storageFolder; + + if (storageFolder == null) + return; + // Retrieve instance data + this.uniqueId = storageFolder.getOrDefault(UUID_KEY, UUID.class, uniqueId); + + Data data = storageFolder.getOrDefault(DATA_KEY, SerializableData.class, null); + setData(data); } @Override @@ -204,25 +219,33 @@ public class InstanceContainer extends Instance { } @Override - public void breakBlock(Player player, BlockPosition blockPosition) { + public boolean breakBlock(Player player, BlockPosition blockPosition) { + player.resetTargetBlock(); + Chunk chunk = getChunkAt(blockPosition); + // Chunk unloaded, stop here + if (ChunkUtils.isChunkUnloaded(chunk)) + return false; + int x = blockPosition.getX(); int y = blockPosition.getY(); int z = blockPosition.getZ(); - short blockId = chunk.getBlockId(x, y, z); + short blockId = getBlockId(x, y, z); // The player probably have a wrong version of this chunk section, send it if (blockId == 0) { sendChunkSectionUpdate(chunk, ChunkUtils.getSectionAt(y), player); - return; + return false; } - PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent(blockPosition, (short) 0, (short) 0); - player.callEvent(PlayerBlockBreakEvent.class, blockBreakEvent); - if (!blockBreakEvent.isCancelled()) { + CustomBlock customBlock = getCustomBlock(x, y, z); + PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent(blockPosition, blockId, customBlock, (short) 0, (short) 0); + player.callEvent(PlayerBlockBreakEvent.class, blockBreakEvent); + boolean result = !blockBreakEvent.isCancelled(); + if (result) { // Break or change the broken block based on event result setSeparateBlocks(x, y, z, blockBreakEvent.getResultBlockId(), blockBreakEvent.getResultCustomBlockId()); @@ -233,11 +256,19 @@ public class InstanceContainer extends Instance { writer.writeVarInt(blockId); }); - chunk.sendPacketToViewers(particlePacket); + chunk.getViewers().forEach(p -> { + // The player who breaks the block already get particles client-side + if (customBlock != null || !(p.equals(player) && !player.isCreative())) { + p.getPlayerConnection().sendPacket(particlePacket); + } + }); + } else { // Cancelled so we need to refresh player chunk section - sendChunkSectionUpdate(chunk, ChunkUtils.getSectionAt(blockPosition.getY()), player); + int section = ChunkUtils.getSectionAt(blockPosition.getY()); + sendChunkSectionUpdate(chunk, section, player); } + return result; } @Override @@ -286,26 +317,55 @@ public class InstanceContainer extends Instance { chunk.removeViewer(viewer); } + // Remove all entities in chunk + getChunkEntities(chunk).forEach(entity -> { + if (!(entity instanceof Player)) + entity.remove(); + }); + + // Clear cache this.chunks.remove(index); + this.chunkEntities.remove(index); + chunk.unload(); } @Override public Chunk getChunk(int chunkX, int chunkZ) { Chunk chunk = chunks.get(ChunkUtils.getChunkIndex(chunkX, chunkZ)); - return ChunkUtils.isChunkUnloaded(this, chunk) ? null : chunk; + return ChunkUtils.isChunkUnloaded(chunk) ? null : chunk; + } + + /** + * Save the instance ({@link #getUniqueId()} {@link #getData()}) and call {@link #saveChunksToStorageFolder(Runnable)} + *

+ * WARNING: {@link #getData()} needs to be a {@link SerializableData} in order to be saved + * + * @param callback the callback + */ + public void saveInstance(Runnable callback) { + Check.notNull(getStorageFolder(), "You cannot save the instance if no StorageFolder has been defined"); + + this.storageFolder.set(UUID_KEY, getUniqueId(), UUID.class); + Data data = getData(); + if (data != null) { + Check.stateCondition(!(data instanceof SerializableData), + "Instance#getData needs to be a SerializableData in order to be saved"); + this.storageFolder.set(DATA_KEY, (SerializableData) getData(), SerializableData.class); + } + + saveChunksToStorageFolder(callback); } @Override public void saveChunkToStorageFolder(Chunk chunk, Runnable callback) { + Check.notNull(getStorageFolder(), "You cannot save the chunk if no StorageFolder has been defined"); CHUNK_LOADER_IO.saveChunk(chunk, getStorageFolder(), callback); } @Override public void saveChunksToStorageFolder(Runnable callback) { - if (storageFolder == null) - throw new UnsupportedOperationException("You cannot save an instance without setting a folder."); - + Check.notNull(getStorageFolder(), "You cannot save the instance if no StorageFolder has been defined"); Iterator chunks = getChunks().iterator(); while (chunks.hasNext()) { Chunk chunk = chunks.next(); @@ -321,14 +381,10 @@ public class InstanceContainer extends Instance { @Override public ChunkBatch createChunkBatch(Chunk chunk) { + Check.notNull(chunk, "The chunk of a ChunkBatch cannot be null"); return new ChunkBatch(this, chunk); } - @Override - public void sendChunkUpdate(Player player, Chunk chunk) { - player.getPlayerConnection().sendPacket(chunk.getFullDataPacket()); - } - @Override protected void retrieveChunk(int chunkX, int chunkZ, Consumer callback) { if (storageFolder != null) { @@ -345,7 +401,7 @@ public class InstanceContainer extends Instance { } @Override - public void createChunk(int chunkX, int chunkZ, Consumer callback) { + protected void createChunk(int chunkX, int chunkZ, Consumer callback) { Biome[] biomes = new Biome[Chunk.BIOME_COUNT]; if (chunkGenerator == null) { Arrays.fill(biomes, Biome.VOID); @@ -415,9 +471,16 @@ public class InstanceContainer extends Instance { } private void cacheChunk(Chunk chunk) { - this.chunks.put(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()), chunk); + long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); + this.chunks.put(index, chunk); } + @Override + public ChunkGenerator getChunkGenerator() { + return chunkGenerator; + } + + @Override public void setChunkGenerator(ChunkGenerator chunkGenerator) { this.chunkGenerator = chunkGenerator; } diff --git a/src/main/java/net/minestom/server/instance/InstanceManager.java b/src/main/java/net/minestom/server/instance/InstanceManager.java index 964746b1e..debbe063b 100644 --- a/src/main/java/net/minestom/server/instance/InstanceManager.java +++ b/src/main/java/net/minestom/server/instance/InstanceManager.java @@ -12,7 +12,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; -public class InstanceManager { +public final class InstanceManager { private ExecutorService blocksPool = new MinestomThread(MinecraftServer.THREAD_COUNT_BLOCK_UPDATE, MinecraftServer.THREAD_NAME_BLOCK_UPDATE); @@ -57,6 +57,9 @@ public class InstanceManager { return createSharedInstance(sharedInstance); } + /** + * Execute a whole block tick update for all instances + */ public void updateBlocks() { if (instances.isEmpty()) return; diff --git a/src/main/java/net/minestom/server/instance/SharedInstance.java b/src/main/java/net/minestom/server/instance/SharedInstance.java index f31e07542..85f4b9376 100644 --- a/src/main/java/net/minestom/server/instance/SharedInstance.java +++ b/src/main/java/net/minestom/server/instance/SharedInstance.java @@ -31,8 +31,8 @@ public class SharedInstance extends Instance { } @Override - public void breakBlock(Player player, BlockPosition blockPosition) { - instanceContainer.breakBlock(player, blockPosition); + public boolean breakBlock(Player player, BlockPosition blockPosition) { + return instanceContainer.breakBlock(player, blockPosition); } @Override @@ -80,6 +80,11 @@ public class SharedInstance extends Instance { instanceContainer.setChunkGenerator(chunkGenerator); } + @Override + public ChunkGenerator getChunkGenerator() { + return instanceContainer.getChunkGenerator(); + } + @Override public Collection getChunks() { return instanceContainer.getChunks(); @@ -95,11 +100,6 @@ public class SharedInstance extends Instance { instanceContainer.setStorageFolder(storageFolder); } - @Override - public void sendChunkUpdate(Player player, Chunk chunk) { - instanceContainer.sendChunkUpdate(player, chunk); - } - @Override public void sendChunkSectionUpdate(Chunk chunk, int section, Player player) { instanceContainer.sendChunkSectionUpdate(chunk, section, player); @@ -111,7 +111,7 @@ public class SharedInstance extends Instance { } @Override - public void createChunk(int chunkX, int chunkZ, Consumer callback) { + protected void createChunk(int chunkX, int chunkZ, Consumer callback) { instanceContainer.createChunk(chunkX, chunkZ, callback); } diff --git a/src/main/java/net/minestom/server/instance/WorldBorder.java b/src/main/java/net/minestom/server/instance/WorldBorder.java index ca50413d5..2441b0278 100644 --- a/src/main/java/net/minestom/server/instance/WorldBorder.java +++ b/src/main/java/net/minestom/server/instance/WorldBorder.java @@ -185,7 +185,16 @@ public class WorldBorder { } else { double diameterDelta = newDiameter - oldDiameter; long elapsedTime = System.currentTimeMillis() - lerpStartTime; - this.currentDiameter = oldDiameter + (diameterDelta * ((double) elapsedTime / (double) speed)); + double percentage = (double) elapsedTime / (double) speed; + percentage = Math.max(percentage, 1); + this.currentDiameter = oldDiameter + (diameterDelta * percentage); + + // World border finished lerp + if (percentage == 1) { + this.lerpStartTime = 0; + this.speed = 0; + this.oldDiameter = newDiameter; + } } } diff --git a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java index 42f3e6f07..215ef7cef 100644 --- a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java @@ -21,28 +21,22 @@ public class BlockBatch implements InstanceBatch { } @Override - public void setBlock(int x, int y, int z, short blockId, Data data) { - synchronized (this) { - Chunk chunk = this.instance.getChunkAt(x, z); - addBlockData(chunk, x, y, z, false, blockId, (short) 0, data); - } + public synchronized void setBlock(int x, int y, int z, short blockId, Data data) { + Chunk chunk = this.instance.getChunkAt(x, z); + addBlockData(chunk, x, y, z, false, blockId, (short) 0, data); } @Override - public void setCustomBlock(int x, int y, int z, short blockId, Data data) { - synchronized (this) { - Chunk chunk = this.instance.getChunkAt(x, z); - CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(blockId); - addBlockData(chunk, x, y, z, true, customBlock.getBlockId(), blockId, data); - } + public synchronized void setCustomBlock(int x, int y, int z, short blockId, Data data) { + Chunk chunk = this.instance.getChunkAt(x, z); + CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(blockId); + addBlockData(chunk, x, y, z, true, customBlock.getBlockId(), blockId, data); } @Override - public void setSeparateBlocks(int x, int y, int z, short blockId, short customBlockId, Data data) { - synchronized (this) { - Chunk chunk = this.instance.getChunkAt(x, z); - addBlockData(chunk, x, y, z, true, blockId, customBlockId, data); - } + public synchronized void setSeparateBlocks(int x, int y, int z, short blockId, short customBlockId, Data data) { + Chunk chunk = this.instance.getChunkAt(x, z); + addBlockData(chunk, x, y, z, true, blockId, customBlockId, data); } private void addBlockData(Chunk chunk, int x, int y, int z, boolean customBlock, short blockId, short customBlockId, Data data) { diff --git a/src/main/java/net/minestom/server/instance/block/BlockManager.java b/src/main/java/net/minestom/server/instance/block/BlockManager.java index b2bdf43a6..2fe7ea339 100644 --- a/src/main/java/net/minestom/server/instance/block/BlockManager.java +++ b/src/main/java/net/minestom/server/instance/block/BlockManager.java @@ -14,6 +14,9 @@ public class BlockManager { private Short2ObjectOpenHashMap placementRules = new Short2ObjectOpenHashMap<>(); + /** + * @param customBlock the custom block to register + */ public void registerCustomBlock(CustomBlock customBlock) { String identifier = customBlock.getIdentifier(); short id = customBlock.getCustomBlockId(); @@ -21,24 +24,43 @@ public class BlockManager { this.customBlocksId.put(identifier, customBlock); } + /** + * @param blockPlacementRule the block placement rule to register + */ public void registerBlockPlacementRule(BlockPlacementRule blockPlacementRule) { this.placementRules.put(blockPlacementRule.getBlockId(), blockPlacementRule); } + /** + * @param blockId the block id to check + * @return the block placement rule associated with the id, null if not any + */ public BlockPlacementRule getBlockPlacementRule(short blockId) { Block block = Block.fromId(blockId); // Convert block alternative blockId = block.getBlockId(); return this.placementRules.get(blockId); } + /** + * @param block the block to check + * @return the block placement rule associated with the block, null if not any + */ public BlockPlacementRule getBlockPlacementRule(Block block) { return getBlockPlacementRule(block.getBlockId()); } + /** + * @param identifier the custom block identifier + * @return the {@link CustomBlock} associated with the identifier, null if not any + */ public CustomBlock getCustomBlock(String identifier) { return customBlocksId.get(identifier); } + /** + * @param id the custom block id + * @return the {@link CustomBlock} associated with the id, null if not any + */ public CustomBlock getCustomBlock(short id) { return customBlocksInternalId.get(id); } diff --git a/src/main/java/net/minestom/server/instance/block/CustomBlock.java b/src/main/java/net/minestom/server/instance/block/CustomBlock.java index 2a8777458..b284f4ef7 100644 --- a/src/main/java/net/minestom/server/instance/block/CustomBlock.java +++ b/src/main/java/net/minestom/server/instance/block/CustomBlock.java @@ -7,8 +7,8 @@ import net.minestom.server.gamedata.loottables.LootTable; import net.minestom.server.gamedata.loottables.LootTableManager; import net.minestom.server.instance.Instance; import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.nbt.NbtWriter; import net.minestom.server.utils.time.UpdateOption; -import net.querz.nbt.CompoundTag; /** * TODO @@ -19,6 +19,10 @@ public abstract class CustomBlock { private short blockId; private String identifier; + /** + * @param blockId the visual block id + * @param identifier the custom block identifier + */ public CustomBlock(short blockId, String identifier) { this.blockId = blockId; this.identifier = identifier; @@ -28,16 +32,48 @@ public abstract class CustomBlock { this(block.getBlockId(), identifier); } + /** + * Calling delay depends on {@link #getUpdateOption()} which should be overridden + * + * @param instance the instance of the block + * @param blockPosition the position of the block + * @param data the data associated with the block + * @throws UnsupportedOperationException if {@link #getUpdateOption()} + * is not null but the update method is not overridden + */ public void update(Instance instance, BlockPosition blockPosition, Data data) { throw new UnsupportedOperationException("Update method not overridden"); } + /** + * The update option is used to define the delay between two + * {@link #update(Instance, BlockPosition, Data)} execution. + *

+ * If this is not null, {@link #update(Instance, BlockPosition, Data)} + * should be overridden or errors with occurs + * + * @return the update option of the block + */ public UpdateOption getUpdateOption() { return null; } + /** + * Called when a custom block has been placed + * + * @param instance the instance of the block + * @param blockPosition the position of the block + * @param data the data associated with the block + */ public abstract void onPlace(Instance instance, BlockPosition blockPosition, Data data); + /** + * Called when a custom block has been destroyed or replaced + * + * @param instance the instance of the block + * @param blockPosition the position of the block + * @param data the data associated with the block + */ public abstract void onDestroy(Instance instance, BlockPosition blockPosition, Data data); /** @@ -70,6 +106,9 @@ public abstract class CustomBlock { */ public abstract int getBreakDelay(Player player, BlockPosition position); + /** + * @return true if {@link #getUpdateOption()} is not null, false otherwise + */ public boolean hasUpdate() { UpdateOption updateOption = getUpdateOption(); if (updateOption == null) @@ -89,10 +128,25 @@ public abstract class CustomBlock { public void handleContact(Instance instance, BlockPosition position, Entity touching) { } + /** + * This is the default visual for the block when the custom block is set, + * it is possible to change this value per block using + * {@link net.minestom.server.instance.BlockModifier#setSeparateBlocks(int, int, int, short, short)} + *

+ * Meaning that you should not believe that your custom blocks id will always be this one. + * + * @return the default visual block id + */ public short getBlockId() { return blockId; } + /** + * The custom block identifier, used to retrieve the custom block object with + * {@link BlockManager#getCustomBlock(String)} and to set custom block in the instance + * + * @return the custom block identifier + */ public String getIdentifier() { return identifier; } @@ -139,11 +193,12 @@ public abstract class CustomBlock { * @param position position of the block * @param blockData equivalent to

instance.getBlockData(position)
*/ - public void writeBlockEntity(BlockPosition position, Data blockData, CompoundTag nbt) { + public void writeBlockEntity(BlockPosition position, Data blockData, NbtWriter nbt) { } /** * Called when an explosion wants to destroy this block. + * * @param instance * @param lootTableArguments arguments used in the loot table loot generation * @return 'true' if the explosion should happen on this block, 'false' to cancel the destruction. @@ -155,6 +210,7 @@ public abstract class CustomBlock { /** * Return the loot table associated to this block. Return null to use vanilla behavior + * * @param tableManager * @return */ diff --git a/src/main/java/net/minestom/server/inventory/EquipmentHandler.java b/src/main/java/net/minestom/server/inventory/EquipmentHandler.java index a0f74295b..c0d722608 100644 --- a/src/main/java/net/minestom/server/inventory/EquipmentHandler.java +++ b/src/main/java/net/minestom/server/inventory/EquipmentHandler.java @@ -4,16 +4,45 @@ import net.minestom.server.entity.Player; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; +/** + * Represent an entity which can have item in hand and armor slots + */ public interface EquipmentHandler { + /** + * Get the item in main hand + * + * @return the item in main hand + */ ItemStack getItemInMainHand(); + /** + * Change the main hand item + * + * @param itemStack the main hand item + */ void setItemInMainHand(ItemStack itemStack); + /** + * Get the item in off hand + * + * @return the item in off hand + */ ItemStack getItemInOffHand(); + /** + * Change the off hand item + * + * @param itemStack the off hand item + */ void setItemInOffHand(ItemStack itemStack); + /** + * Get the item in the specific hand + * + * @param hand the hand to get the item from + * @return the item in {@code hand} + */ default ItemStack getItemInHand(Player.Hand hand) { switch (hand) { case MAIN: @@ -27,6 +56,12 @@ public interface EquipmentHandler { } } + /** + * Change the item in the specific hand + * + * @param hand the hand to set the item to + * @param stack the itemstack to set + */ default void setItemInHand(Player.Hand hand, ItemStack stack) { switch (hand) { case MAIN: @@ -39,22 +74,68 @@ public interface EquipmentHandler { } } + /** + * Get the helmet + * + * @return the helmet + */ ItemStack getHelmet(); + /** + * Change the helmet + * + * @param itemStack the helmet + */ void setHelmet(ItemStack itemStack); + /** + * Get the chestplate + * + * @return the chestplate + */ ItemStack getChestplate(); + /** + * Change the chestplate + * + * @param itemStack the chestplate + */ void setChestplate(ItemStack itemStack); + /** + * Get the leggings + * + * @return the leggings + */ ItemStack getLeggings(); + /** + * Change the leggings + * + * @param itemStack the leggings + */ void setLeggings(ItemStack itemStack); + /** + * Get the boots + * + * @return the boots + */ ItemStack getBoots(); + /** + * Change the boots + * + * @param itemStack the boots + */ void setBoots(ItemStack itemStack); + /** + * Get the equipment in a specific slot + * + * @param slot the equipment to get the item from + * @return the equipment item + */ default ItemStack getEquipment(EntityEquipmentPacket.Slot slot) { switch (slot) { case MAIN_HAND: diff --git a/src/main/java/net/minestom/server/inventory/Inventory.java b/src/main/java/net/minestom/server/inventory/Inventory.java index 4d2f95f79..122f432ea 100644 --- a/src/main/java/net/minestom/server/inventory/Inventory.java +++ b/src/main/java/net/minestom/server/inventory/Inventory.java @@ -8,10 +8,12 @@ import net.minestom.server.inventory.click.InventoryClickProcessor; import net.minestom.server.inventory.click.InventoryClickResult; import net.minestom.server.inventory.condition.InventoryCondition; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.StackingRule; import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; import net.minestom.server.network.packet.server.play.WindowPropertyPacket; +import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.inventory.PlayerInventoryUtils; import net.minestom.server.utils.item.ItemStackUtils; @@ -55,9 +57,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View this.itemStacks = new ItemStack[size]; - for (int i = 0; i < size; i++) { - itemStacks[i] = ItemStack.getAirItem(); - } + ArrayUtils.fill(itemStacks, ItemStack::getAirItem); } private static byte generateId() { @@ -88,7 +88,32 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View } @Override - public boolean addItemStack(ItemStack itemStack) { + public synchronized boolean addItemStack(ItemStack itemStack) { + StackingRule stackingRule = itemStack.getStackingRule(); + for (int i = 0; i < getItemStacks().length; i++) { + ItemStack item = getItemStacks()[i]; + StackingRule itemStackingRule = item.getStackingRule(); + if (itemStackingRule.canBeStacked(itemStack, item)) { + int itemAmount = itemStackingRule.getAmount(item); + if (itemAmount == stackingRule.getMaxSize()) + continue; + int itemStackAmount = itemStackingRule.getAmount(itemStack); + int totalAmount = itemStackAmount + itemAmount; + if (!stackingRule.canApply(itemStack, totalAmount)) { + item = itemStackingRule.apply(item, itemStackingRule.getMaxSize()); + + sendSlotRefresh((short) i, item); + itemStack = stackingRule.apply(itemStack, totalAmount - stackingRule.getMaxSize()); + } else { + item.setAmount((byte) totalAmount); + sendSlotRefresh((short) i, item); + return true; + } + } else if (item.isAir()) { + setItemStack(i, itemStack); + return true; + } + } return false; } @@ -121,7 +146,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View * Refresh the inventory for all viewers */ public void update() { - PacketWriterUtils.writeAndSend(getViewers(), getWindowItemsPacket()); + PacketWriterUtils.writeAndSend(getViewers(), createWindowItemsPacket()); } /** @@ -134,7 +159,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View if (!getViewers().contains(player)) return; - PacketWriterUtils.writeAndSend(player, getWindowItemsPacket()); + PacketWriterUtils.writeAndSend(player, createWindowItemsPacket()); } @Override @@ -145,8 +170,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View @Override public boolean addViewer(Player player) { boolean result = this.viewers.add(player); - WindowItemsPacket windowItemsPacket = getWindowItemsPacket(); - player.getPlayerConnection().sendPacket(windowItemsPacket); + PacketWriterUtils.writeAndSend(player, createWindowItemsPacket()); return result; } @@ -158,23 +182,29 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View return result; } + /** + * 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 + */ 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()); } - private void safeItemInsert(int slot, ItemStack itemStack) { - synchronized (this) { - itemStack = ItemStackUtils.notNull(itemStack); - this.itemStacks[slot] = itemStack; - SetSlotPacket setSlotPacket = new SetSlotPacket(); - setSlotPacket.windowId = getWindowId(); - setSlotPacket.slot = (short) slot; - setSlotPacket.itemStack = itemStack; - sendPacketToViewers(setSlotPacket); - } + private synchronized void safeItemInsert(int slot, ItemStack itemStack) { + itemStack = ItemStackUtils.notNull(itemStack); + this.itemStacks[slot] = itemStack; + SetSlotPacket setSlotPacket = new SetSlotPacket(); + setSlotPacket.windowId = getWindowId(); + setSlotPacket.slot = (short) slot; + setSlotPacket.itemStack = itemStack; + sendPacketToViewers(setSlotPacket); } - private WindowItemsPacket getWindowItemsPacket() { + private WindowItemsPacket createWindowItemsPacket() { WindowItemsPacket windowItemsPacket = new WindowItemsPacket(); windowItemsPacket.windowId = getWindowId(); windowItemsPacket.count = (short) itemStacks.length; @@ -441,6 +471,14 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View return !clickResult.isCancel(); } + private void sendSlotRefresh(short slot, ItemStack itemStack) { + SetSlotPacket setSlotPacket = new SetSlotPacket(); + setSlotPacket.windowId = getWindowId(); + setSlotPacket.slot = slot; + setSlotPacket.itemStack = itemStack; + sendPacketToViewers(setSlotPacket); + } + /** * Used to update the inventory for a specific player in order to fix his cancelled actions * diff --git a/src/main/java/net/minestom/server/inventory/InventoryClickHandler.java b/src/main/java/net/minestom/server/inventory/InventoryClickHandler.java index 96178c1fa..06a50b3f4 100644 --- a/src/main/java/net/minestom/server/inventory/InventoryClickHandler.java +++ b/src/main/java/net/minestom/server/inventory/InventoryClickHandler.java @@ -8,28 +8,75 @@ import net.minestom.server.item.ItemStack; /** * Represent an inventory which can receive click input * all methods returning boolean returns true if the action is successful, false otherwise + *

+ * See https://wiki.vg/Protocol#Click_Window for more information */ public interface InventoryClickHandler { + /** + * Called when a player left click in the inventory. Can also be to drop the cursor item + * + * @param player the player who clicked + * @param slot the slot number + * @return true if the click hasn't been cancelled, false otherwise + */ boolean leftClick(Player player, int slot); + /** + * Called when a player right click in the inventory. Can also be to drop the cursor item + * + * @param player the player who clicked + * @param slot the slot number + * @return true if the click hasn't been cancelled, false otherwise + */ boolean rightClick(Player player, int slot); + /** + * Called when a player shift click in the inventory + * + * @param player the player who clicked + * @param slot the slot number + * @return true if the click hasn't been cancelled, false otherwise + */ boolean shiftClick(Player player, int slot); // shift + left/right click have the same behavior + /** + * Called when a player held click in the inventory + * + * @param player the player who clicked + * @param slot the slot number + * @param key the held slot (0-8) pressed + * @return true if the click hasn't been cancelled, false otherwise + */ boolean changeHeld(Player player, int slot, int key); boolean middleClick(Player player, int slot); + /** + * Called when a player press the drop button + * + * @param player the player who clicked + * @param mode + * @param slot the slot number + * @param button -999 if clicking outside, normal if he is not + * @return true if the drop hasn't been cancelled, false otherwise + */ boolean drop(Player player, int mode, int slot, int button); boolean dragging(Player player, int slot, int button); + /** + * Called when a player double click in the inventory + * + * @param player the player who clicked + * @param slot the slot number + * @return true if the click hasn't been cancelled, false otherwise + */ boolean doubleClick(Player player, int slot); default void callClickEvent(Player player, Inventory inventory, int slot, ClickType clickType, ItemStack clicked, ItemStack cursor) { - InventoryClickEvent inventoryClickEvent = new InventoryClickEvent(inventory, slot, clickType, clicked, cursor); + InventoryClickEvent inventoryClickEvent = new InventoryClickEvent(player, inventory, slot, clickType, clicked, cursor); player.callEvent(InventoryClickEvent.class, inventoryClickEvent); } diff --git a/src/main/java/net/minestom/server/inventory/InventoryProperty.java b/src/main/java/net/minestom/server/inventory/InventoryProperty.java index 8c6ac908c..dbbb2d722 100644 --- a/src/main/java/net/minestom/server/inventory/InventoryProperty.java +++ b/src/main/java/net/minestom/server/inventory/InventoryProperty.java @@ -1,5 +1,10 @@ package net.minestom.server.inventory; +/** + * List of inventory property and their ID + *

+ * See https://wiki.vg/Protocol#Window_Property for more information + */ public enum InventoryProperty { FURNACE_FIRE_ICON((short) 0), diff --git a/src/main/java/net/minestom/server/inventory/InventoryRule.java b/src/main/java/net/minestom/server/inventory/InventoryRule.java deleted file mode 100644 index 549f89a1b..000000000 --- a/src/main/java/net/minestom/server/inventory/InventoryRule.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.minestom.server.inventory; - -public class InventoryRule { -} diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index 86141ca4b..3997cefde 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -2,6 +2,8 @@ package net.minestom.server.inventory; import net.minestom.server.entity.Player; import net.minestom.server.event.item.ArmorEquipEvent; +import net.minestom.server.event.player.PlayerAddItemStackEvent; +import net.minestom.server.event.player.PlayerSetItemStackEvent; import net.minestom.server.inventory.click.ClickType; import net.minestom.server.inventory.click.InventoryClickLoopHandler; import net.minestom.server.inventory.click.InventoryClickProcessor; @@ -13,8 +15,10 @@ import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.play.EntityEquipmentPacket; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; +import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.item.ItemStackUtils; +import net.minestom.server.utils.validate.Check; import java.util.Arrays; import java.util.List; @@ -36,9 +40,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler public PlayerInventory(Player player) { this.player = player; - for (int i = 0; i < items.length; i++) { - items[i] = ItemStack.getAirItem(); - } + ArrayUtils.fill(items, ItemStack::getAirItem); } @Override @@ -68,36 +70,52 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler @Override public void setItemStack(int slot, ItemStack itemStack) { + itemStack = ItemStackUtils.notNull(itemStack); + + PlayerSetItemStackEvent setItemStackEvent = new PlayerSetItemStackEvent(player, slot, itemStack); + player.callEvent(PlayerSetItemStackEvent.class, setItemStackEvent); + if (setItemStackEvent.isCancelled()) + return; + slot = setItemStackEvent.getSlot(); + itemStack = setItemStackEvent.getItemStack(); + safeItemInsert(slot, itemStack); } @Override - public boolean addItemStack(ItemStack itemStack) { - synchronized (this) { - StackingRule stackingRule = itemStack.getStackingRule(); - for (int i = 0; i < items.length - 10; i++) { - ItemStack item = items[i]; - StackingRule itemStackingRule = item.getStackingRule(); - if (itemStackingRule.canBeStacked(itemStack, item)) { - int itemAmount = itemStackingRule.getAmount(item); - if (itemAmount == stackingRule.getMaxSize()) - continue; - int itemStackAmount = itemStackingRule.getAmount(itemStack); - int totalAmount = itemStackAmount + itemAmount; - if (!stackingRule.canApply(itemStack, totalAmount)) { - item = itemStackingRule.apply(item, itemStackingRule.getMaxSize()); + public synchronized boolean addItemStack(ItemStack itemStack) { + itemStack = ItemStackUtils.notNull(itemStack); - sendSlotRefresh((short) convertToPacketSlot(i), item); - itemStack = stackingRule.apply(itemStack, totalAmount - stackingRule.getMaxSize()); - } else { - item.setAmount((byte) totalAmount); - sendSlotRefresh((short) convertToPacketSlot(i), item); - return true; - } - } else if (item.isAir()) { - setItemStack(i, itemStack); + PlayerAddItemStackEvent addItemStackEvent = new PlayerAddItemStackEvent(player, itemStack); + player.callEvent(PlayerAddItemStackEvent.class, addItemStackEvent); + if (addItemStackEvent.isCancelled()) + return false; + + itemStack = addItemStackEvent.getItemStack(); + + StackingRule stackingRule = itemStack.getStackingRule(); + for (int i = 0; i < items.length - 10; i++) { + ItemStack item = items[i]; + StackingRule itemStackingRule = item.getStackingRule(); + if (itemStackingRule.canBeStacked(itemStack, item)) { + int itemAmount = itemStackingRule.getAmount(item); + if (itemAmount == stackingRule.getMaxSize()) + continue; + int itemStackAmount = itemStackingRule.getAmount(itemStack); + int totalAmount = itemStackAmount + itemAmount; + if (!stackingRule.canApply(itemStack, totalAmount)) { + item = itemStackingRule.apply(item, itemStackingRule.getMaxSize()); + + sendSlotRefresh((short) convertToPacketSlot(i), item); + itemStack = stackingRule.apply(itemStack, totalAmount - stackingRule.getMaxSize()); + } else { + item.setAmount((byte) totalAmount); + sendSlotRefresh((short) convertToPacketSlot(i), item); return true; } + } else if (item.isAir()) { + safeItemInsert(i, itemStack); + return true; } } return false; @@ -168,79 +186,127 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler safeItemInsert(BOOTS_SLOT, itemStack); } + /** + * Refresh the player inventory by sending a {@link WindowItemsPacket} containing all + * the inventory items + */ public void update() { PacketWriterUtils.writeAndSend(player, createWindowItemsPacket()); } + /** + * Refresh only a specific slot with the updated item stack data + * + * @param slot the slot to refresh + */ public void refreshSlot(int slot) { sendSlotRefresh((short) convertToPacketSlot(slot), getItemStack(slot)); } + /** + * Get the item in player cursor + * + * @return the cursor item + */ public ItemStack getCursorItem() { return cursorItem; } + /** + * Change the player cursor item + * + * @param cursorItem the new cursor item + */ public void setCursorItem(ItemStack cursorItem) { this.cursorItem = ItemStackUtils.notNull(cursorItem); } - private void safeItemInsert(int slot, ItemStack itemStack) { - synchronized (this) { - itemStack = ItemStackUtils.notNull(itemStack); + /** + * Insert an item safely (synchronized) in the appropriate slot + * + * @param slot an internal slot + * @param itemStack the item to insert at the slot + * @throws IllegalArgumentException if the slot {@code slot} does not exist + * @throws NullPointerException if {@code itemStack} is null + */ + private synchronized void safeItemInsert(int slot, ItemStack itemStack) { + 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; - EntityEquipmentPacket.Slot equipmentSlot; + if (slot == player.getHeldSlot()) { + equipmentSlot = EntityEquipmentPacket.Slot.MAIN_HAND; + } else if (slot == OFFHAND_SLOT) { + equipmentSlot = EntityEquipmentPacket.Slot.OFF_HAND; + } else { + ArmorEquipEvent armorEquipEvent = null; - if (slot == player.getHeldSlot()) { - equipmentSlot = EntityEquipmentPacket.Slot.MAIN_HAND; - } else if (slot == OFFHAND_SLOT) { - equipmentSlot = EntityEquipmentPacket.Slot.OFF_HAND; + if (slot == HELMET_SLOT) { + armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.HELMET); + } else if (slot == CHESTPLATE_SLOT) { + armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE); + } else if (slot == LEGGINGS_SLOT) { + armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS); + } else if (slot == BOOTS_SLOT) { + armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.BOOTS); + } + + if (armorEquipEvent != null) { + ArmorEquipEvent.ArmorSlot armorSlot = armorEquipEvent.getArmorSlot(); + equipmentSlot = EntityEquipmentPacket.Slot.fromArmorSlot(armorSlot); + player.callEvent(ArmorEquipEvent.class, armorEquipEvent); + itemStack = armorEquipEvent.getArmorItem(); } else { - ArmorEquipEvent armorEquipEvent = null; - - if (slot == HELMET_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.HELMET); - } else if (slot == CHESTPLATE_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE); - } else if (slot == LEGGINGS_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS); - } else if (slot == BOOTS_SLOT) { - armorEquipEvent = new ArmorEquipEvent(player, itemStack, ArmorEquipEvent.ArmorSlot.BOOTS); - } - - if (armorEquipEvent != null) { - ArmorEquipEvent.ArmorSlot armorSlot = armorEquipEvent.getArmorSlot(); - equipmentSlot = EntityEquipmentPacket.Slot.fromArmorSlot(armorSlot); - player.callEvent(ArmorEquipEvent.class, armorEquipEvent); - itemStack = armorEquipEvent.getArmorItem(); - } else { - equipmentSlot = null; - } + equipmentSlot = null; } - - this.items[slot] = itemStack; - - // Sync equipment - if (equipmentSlot != null) { - player.syncEquipment(equipmentSlot); - } - - // Refresh slot - update(); - //refreshSlot(slot); seems to break things concerning +64 stacks } + + this.items[slot] = itemStack; + + // Sync equipment + if (equipmentSlot != null) { + player.syncEquipment(equipmentSlot); + } + + // Refresh slot + update(); + //refreshSlot(slot); seems to break things concerning +64 stacks } + /** + * Set an item from a packet slot + * + * @param slot a packet slot + * @param offset offset (generally 9 to ignore armor and craft slots) + * @param itemStack the item stack to set + */ protected void setItemStack(int slot, int offset, ItemStack itemStack) { slot = convertSlot(slot, offset); - safeItemInsert(slot, itemStack); + setItemStack(slot, itemStack); } + /** + * Get the item from a packet slot + * + * @param slot a packet slot + * @param offset offset (generally 9 to ignore armor and craft slots) + * @return the item in the specified slot + */ protected ItemStack getItemStack(int slot, int offset) { slot = convertSlot(slot, offset); return this.items[slot]; } - private void sendSlotRefresh(short slot, ItemStack itemStack) { + /** + * Refresh an inventory slot + * + * @param slot the packet slot + * see {@link net.minestom.server.utils.inventory.PlayerInventoryUtils#convertToPacketSlot(int)} + * @param itemStack the item stack in the slot + */ + protected void sendSlotRefresh(short slot, ItemStack itemStack) { SetSlotPacket setSlotPacket = new SetSlotPacket(); setSlotPacket.windowId = (byte) (MathUtils.isBetween(slot, 35, INVENTORY_SIZE) ? 0 : -2); setSlotPacket.slot = slot; @@ -248,6 +314,11 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler player.getPlayerConnection().sendPacket(setSlotPacket); } + /** + * Get a {@link WindowItemsPacket} with all the items in the inventory + * + * @return a {@link WindowItemsPacket} with inventory items + */ private WindowItemsPacket createWindowItemsPacket() { ItemStack[] convertedSlots = new ItemStack[INVENTORY_SIZE]; diff --git a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java index 67296ea30..8a0e9d3cc 100644 --- a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java +++ b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java @@ -506,7 +506,7 @@ public class InventoryClickProcessor { player.getInventory().getInventoryConditions() : inventory.getInventoryConditions(); if (!inventoryConditions.isEmpty()) { - InventoryPreClickEvent inventoryPreClickEvent = new InventoryPreClickEvent(inventory, slot, clickType, clicked, cursor); + InventoryPreClickEvent inventoryPreClickEvent = new InventoryPreClickEvent(player, inventory, slot, clickType, clicked, cursor); player.callEvent(InventoryPreClickEvent.class, inventoryPreClickEvent); cursor = inventoryPreClickEvent.getCursorItem(); clicked = inventoryPreClickEvent.getClickedItem(); @@ -544,7 +544,7 @@ public class InventoryClickProcessor { private void callClickEvent(Player player, Inventory inventory, int slot, ClickType clickType, ItemStack clicked, ItemStack cursor) { - InventoryClickEvent inventoryClickEvent = new InventoryClickEvent(inventory, slot, clickType, clicked, cursor); + InventoryClickEvent inventoryClickEvent = new InventoryClickEvent(player, inventory, slot, clickType, clicked, cursor); player.callEvent(InventoryClickEvent.class, inventoryClickEvent); } diff --git a/src/main/java/net/minestom/server/item/ItemFlag.java b/src/main/java/net/minestom/server/item/ItemFlag.java index fa77de086..0fdfbf090 100644 --- a/src/main/java/net/minestom/server/item/ItemFlag.java +++ b/src/main/java/net/minestom/server/item/ItemFlag.java @@ -1,11 +1,10 @@ package net.minestom.server.item; public enum ItemFlag { - - HIDE_ATTRIBUTES, - HIDE_DESTROYS, HIDE_ENCHANTS, + HIDE_ATTRIBUTES, + HIDE_UNBREAKABLE, + HIDE_DESTROYS, HIDE_PLACED_ON, HIDE_POTION_EFFECTS, - HIDE_UNBREAKABLE } diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 3748365b9..a36149536 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -2,6 +2,7 @@ package net.minestom.server.item; import net.minestom.server.data.Data; import net.minestom.server.data.DataContainer; +import net.minestom.server.item.attribute.ItemAttribute; import net.minestom.server.item.rule.VanillaStackingRule; import net.minestom.server.potion.PotionType; import net.minestom.server.utils.validate.Check; @@ -19,7 +20,6 @@ public class ItemStack implements DataContainer { private static StackingRule defaultStackingRule; private short materialId; - private Set potionTypes; private byte amount; private short damage; @@ -29,17 +29,22 @@ public class ItemStack implements DataContainer { private ArrayList lore; private Map enchantmentMap; - - { - if (defaultStackingRule == null) - defaultStackingRule = DEFAULT_STACKING_RULE; - } + private Map storedEnchantmentMap; + private List attributes; + private Set potionTypes; private int hideFlag; private StackingRule stackingRule; private Data data; + { + if (defaultStackingRule == null) + defaultStackingRule = DEFAULT_STACKING_RULE; + if (this.stackingRule == null) + this.stackingRule = defaultStackingRule; + } + public ItemStack(short materialId, byte amount, short damage) { this.materialId = materialId; this.amount = amount; @@ -47,9 +52,9 @@ public class ItemStack implements DataContainer { this.lore = new ArrayList<>(); this.enchantmentMap = new HashMap<>(); + this.storedEnchantmentMap = new HashMap<>(); + this.attributes = new ArrayList<>(); this.potionTypes = new HashSet<>(); - - this.stackingRule = defaultStackingRule; } public ItemStack(short materialId, byte amount) { @@ -60,43 +65,89 @@ public class ItemStack implements DataContainer { this(material.getId(), amount); } + /** + * Get the default stacking rule for newly created ItemStack + * + * @return the default stacking rule + */ + public static StackingRule getDefaultStackingRule() { + return defaultStackingRule; + } + + /** + * Change the default stacking rule for created item stack + * + * @param defaultStackingRule the default item stack + * @throws NullPointerException if {@code defaultStackingRule} is null + */ + public static void setDefaultStackingRule(StackingRule defaultStackingRule) { + Check.notNull(defaultStackingRule, "StackingRule cannot be null!"); + ItemStack.defaultStackingRule = defaultStackingRule; + } + + /** + * Get if the item is air + * + * @return true if the material is air, false otherwise + */ public boolean isAir() { return materialId == Material.AIR.getId(); } /** - * Do not take amount in consideration + * Get if two items are similar. + * It does not take {@link #getAmount()} and {@link #getStackingRule()} in consideration * * @param itemStack The ItemStack to compare to - * @return true if both items are similar (without comparing amount) + * @return true if both items are similar */ - public boolean isSimilar(ItemStack itemStack) { - return itemStack.getMaterialId() == materialId && - itemStack.getDisplayName() == displayName && - itemStack.isUnbreakable() == unbreakable && - itemStack.getDamage() == damage && - itemStack.enchantmentMap.equals(enchantmentMap) && - itemStack.hideFlag == hideFlag && - itemStack.getStackingRule() == stackingRule && - itemStack.getData() == data; - } + public synchronized boolean isSimilar(ItemStack itemStack) { + synchronized (itemStack) { + final String itemDisplayName = itemStack.getDisplayName(); + final boolean displayNameCheck = (displayName == null && itemDisplayName == null) || + (displayName != null && itemDisplayName != null && displayName.equals(itemDisplayName)); - public byte getAmount() { - return amount; + final Data itemData = itemStack.getData(); + final boolean dataCheck = (data == null && itemData == null) || + (data != null && itemData != null && data.equals(itemData)); + + return itemStack.getMaterialId() == materialId && + displayNameCheck && + itemStack.isUnbreakable() == unbreakable && + itemStack.getDamage() == damage && + itemStack.enchantmentMap.equals(enchantmentMap) && + itemStack.storedEnchantmentMap.equals(storedEnchantmentMap) && + itemStack.attributes.equals(attributes) && + itemStack.potionTypes.equals(potionTypes) && + itemStack.hideFlag == hideFlag && + dataCheck; + } } public short getDamage() { return damage; } - public short getMaterialId() { - return materialId; - } - - public Material getMaterial() { - return Material.fromId(getMaterialId()); + /** + * Get the item amount + *

+ * WARNING: for amount computation it would be better to use {@link StackingRule#getAmount(ItemStack)} + * to support all stacking implementation + * + * @return the item amount + */ + public byte getAmount() { + return amount; } + /** + * Change the item amount + *

+ * WARNING: for amount computation it would be better to use {@link StackingRule#getAmount(ItemStack)} + * to support all stacking implementation + * + * @param amount the new item amount + */ public void setAmount(byte amount) { this.amount = amount; } @@ -105,34 +156,93 @@ public class ItemStack implements DataContainer { this.damage = damage; } + /** + * Get the item internal material id + * + * @return the item material id + */ + public short getMaterialId() { + return materialId; + } + + /** + * Get the item material + * + * @return the item material + */ + public Material getMaterial() { + return Material.fromId(getMaterialId()); + } + + /** + * Get the item display name + * + * @return the item display name, can be null if not present + */ public String getDisplayName() { return displayName; } + /** + * Set the item display name + * + * @param displayName the item display name + */ public void setDisplayName(String displayName) { this.displayName = displayName; } + /** + * Get if the item has a display name + * + * @return the item display name + */ public boolean hasDisplayName() { return displayName != null; } + /** + * Get the item lore + * + * @return the item lore, can be null if not present + */ public ArrayList getLore() { return lore; } + /** + * Set the item lore + * + * @param lore the item lore, can be null to remove + */ public void setLore(ArrayList lore) { this.lore = lore; } + /** + * Get if the item has a lore + * + * @return true if the item has lore, false otherwise + */ public boolean hasLore() { return lore != null && !lore.isEmpty(); } + /** + * Get the item enchantment map + * + * @return an unmodifiable map containing the item enchantments + */ public Map getEnchantmentMap() { return Collections.unmodifiableMap(enchantmentMap); } + /** + * Set an enchantment level + * + * @param enchantment the enchantment type + * @param level the enchantment level + */ public void setEnchantment(Enchantment enchantment, short level) { if (level < 1) { removeEnchantment(enchantment); @@ -142,38 +252,168 @@ public class ItemStack implements DataContainer { this.enchantmentMap.put(enchantment, level); } + /** + * Remove an enchantment + * + * @param enchantment the enchantment type + */ public void removeEnchantment(Enchantment enchantment) { this.enchantmentMap.remove(enchantment); } + /** + * Get an enchantment level + * + * @param enchantment the enchantment type + * @return the stored enchantment level, 0 if not present + */ public int getEnchantmentLevel(Enchantment enchantment) { return this.enchantmentMap.getOrDefault(enchantment, (short) 0); } + /** + * Get the stored enchantment map + * Stored enchantments are used on enchanted book + * + * @return an unmodifiable map containing the item stored enchantments + */ + public Map getStoredEnchantmentMap() { + return Collections.unmodifiableMap(storedEnchantmentMap); + } + + /** + * Set a stored enchantment level + * + * @param enchantment the enchantment type + * @param level the enchantment level + */ + public void setStoredEnchantment(Enchantment enchantment, short level) { + if (level < 1) { + removeStoredEnchantment(enchantment); + return; + } + + this.storedEnchantmentMap.put(enchantment, level); + } + + /** + * Remove a stored enchantment + * + * @param enchantment the enchantment type + */ + public void removeStoredEnchantment(Enchantment enchantment) { + this.storedEnchantmentMap.remove(enchantment); + } + + /** + * Get a stored enchantment level + * + * @param enchantment the enchantment type + * @return the stored enchantment level, 0 if not present + */ + public int getStoredEnchantmentLevel(Enchantment enchantment) { + return this.storedEnchantmentMap.getOrDefault(enchantment, (short) 0); + } + + /** + * Get the item attributes + * + * @return an unmodifiable {@link List} containing the item attributes + */ + public List getAttributes() { + return Collections.unmodifiableList(attributes); + } + + /** + * Add an attribute to the item + * + * @param itemAttribute the attribute to add + */ + public void addAttribute(ItemAttribute itemAttribute) { + this.attributes.add(itemAttribute); + } + + /** + * Remove an attribute to the item + * + * @param itemAttribute the attribute to remove + */ + public void removeAttribute(ItemAttribute itemAttribute) { + this.attributes.remove(itemAttribute); + } + + /** + * Get the item potion types + * + * @return an unmodifiable {@link Set} containing the item potion types + */ public Set getPotionTypes() { return Collections.unmodifiableSet(potionTypes); } + /** + * Add a potion type to the item + * + * @param potionType the potion type to add + */ public void addPotionType(PotionType potionType) { this.potionTypes.add(potionType); } + /** + * Remove a potion type to the item + * + * @param potionType the potion type to remove + */ + public void removePotionType(PotionType potionType) { + this.potionTypes.remove(potionType); + } + + /** + * Get the item hide flag + * + * @return the item hide flag + */ public int getHideFlag() { return hideFlag; } - public void addItemFlags(ItemFlag... hideFlags) { - for (ItemFlag f : hideFlags) { + /** + * Change the item hide flag. This is the integer sent when updating the item hide flag + * + * @param hideFlag the new item hide flag + */ + public void setHideFlag(int hideFlag) { + this.hideFlag = hideFlag; + } + + /** + * Add flags to the item + * + * @param flags the flags to add + */ + public void addItemFlags(ItemFlag... flags) { + for (ItemFlag f : flags) { this.hideFlag |= getBitModifier(f); } } - public void removeItemFlags(ItemFlag... hideFlags) { - for (ItemFlag f : hideFlags) { + /** + * Remove flags from the item + * + * @param flags the flags to remove + */ + public void removeItemFlags(ItemFlag... flags) { + for (ItemFlag f : flags) { this.hideFlag &= ~getBitModifier(f); } } + /** + * Get the item flags + * + * @return an unmodifiable {@link Set} containing the item flags + */ public Set getItemFlags() { Set currentFlags = EnumSet.noneOf(ItemFlag.class); @@ -183,27 +423,55 @@ public class ItemStack implements DataContainer { } } - return currentFlags; + return Collections.unmodifiableSet(currentFlags); } + /** + * Get if the item has an item flag + * + * @param flag the item flag + * @return true if the item has the flag {@code flag}, false otherwise + */ public boolean hasItemFlag(ItemFlag flag) { int bitModifier = getBitModifier(flag); return (this.hideFlag & bitModifier) == bitModifier; } + /** + * Get if the item is unbreakable + * + * @return true if the item is unbreakable, false otherwise + */ public boolean isUnbreakable() { return unbreakable; } + /** + * Make the item unbreakable + * + * @param unbreakable true to make the item unbreakable, false otherwise + */ public void setUnbreakable(boolean unbreakable) { this.unbreakable = unbreakable; } + /** + * Get if the item has any nbt tag + * + * @return true if the item has nbt tag, false otherwise + */ public boolean hasNbtTag() { - return hasDisplayName() || hasLore() || isUnbreakable() || !getEnchantmentMap().isEmpty() || !potionTypes.isEmpty(); + return hasDisplayName() || hasLore() || isUnbreakable() || + !enchantmentMap.isEmpty() || !storedEnchantmentMap.isEmpty() || + !attributes.isEmpty() || !potionTypes.isEmpty(); } - public ItemStack clone() { + /** + * Clone this item stack + * + * @return a cloned item stack + */ + public synchronized ItemStack clone() { ItemStack itemStack = new ItemStack(materialId, amount, damage); itemStack.setDisplayName(displayName); itemStack.setUnbreakable(unbreakable); @@ -211,6 +479,10 @@ public class ItemStack implements DataContainer { itemStack.setStackingRule(getStackingRule()); itemStack.enchantmentMap = new HashMap<>(enchantmentMap); + itemStack.storedEnchantmentMap = new HashMap<>(storedEnchantmentMap); + itemStack.attributes = new ArrayList<>(attributes); + itemStack.potionTypes = new HashSet<>(potionTypes); + itemStack.hideFlag = hideFlag; Data data = getData(); if (data != null) @@ -218,15 +490,6 @@ public class ItemStack implements DataContainer { return itemStack; } - public StackingRule getStackingRule() { - return stackingRule; - } - - public static void setDefaultStackingRule(StackingRule defaultStackingRule) { - Check.notNull(defaultStackingRule, "StackingRule cannot be null!"); - ItemStack.defaultStackingRule = defaultStackingRule; - } - @Override public Data getData() { return data; @@ -237,10 +500,21 @@ public class ItemStack implements DataContainer { this.data = data; } - public static StackingRule getDefaultStackingRule() { - return defaultStackingRule; + /** + * Get the item stacking rule + * + * @return the item stacking rule + */ + public StackingRule getStackingRule() { + return stackingRule; } + /** + * Change the stacking rule of the item + * + * @param stackingRule the new item stacking rule + * @throws NullPointerException if {@code stackingRule} is null + */ public void setStackingRule(StackingRule stackingRule) { Check.notNull(stackingRule, "StackingRule cannot be null!"); this.stackingRule = stackingRule; diff --git a/src/main/java/net/minestom/server/item/StackingRule.java b/src/main/java/net/minestom/server/item/StackingRule.java index 53cc3060c..a01b39350 100644 --- a/src/main/java/net/minestom/server/item/StackingRule.java +++ b/src/main/java/net/minestom/server/item/StackingRule.java @@ -1,5 +1,10 @@ package net.minestom.server.item; +/** + * Represent the stacking rule of an item + * This can be used to mimic the vanilla one (using the item amount displayed) + * or a complete new one which can be stored in lore, name, etc... + */ public abstract class StackingRule { private int maxSize; @@ -9,6 +14,8 @@ public abstract class StackingRule { } /** + * Used to know if two ItemStack can be stacked together + * * @param item1 the first item * @param item2 the second item * @return true if both item can be stacked together (do not take their amount in consideration) @@ -16,6 +23,8 @@ public abstract class StackingRule { public abstract boolean canBeStacked(ItemStack item1, ItemStack item2); /** + * Used to know if an ItemStack can have the size {@code newAmount} applied + * * @param item the item to check * @param newAmount the desired new amount * @return true if item can have its stack size set to newAmount @@ -23,10 +32,11 @@ public abstract class StackingRule { public abstract boolean canApply(ItemStack item, int newAmount); /** - * At this point we know that the item can have this stack size + * Change the size of the item to {@code newAmount} + * At this point we know that the item can have this stack size applied * - * @param item - * @param newAmount + * @param item the item stack to applies the size to + * @param newAmount the new item size * @return the new ItemStack with the new amount */ public abstract ItemStack apply(ItemStack item, int newAmount); @@ -35,7 +45,7 @@ public abstract class StackingRule { * Used to determine the current stack size of an item * It is possible to have it stored in its Data object, lore, etc... * - * @param itemStack + * @param itemStack the item stack to check the size * @return the correct size of itemStack */ public abstract int getAmount(ItemStack itemStack); diff --git a/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java b/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java new file mode 100644 index 000000000..46d3c9678 --- /dev/null +++ b/src/main/java/net/minestom/server/item/attribute/AttributeSlot.java @@ -0,0 +1,10 @@ +package net.minestom.server.item.attribute; + +public enum AttributeSlot { + MAINHAND, + OFFHAND, + FEET, + LEGS, + CHEST, + HEAD +} diff --git a/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java b/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java new file mode 100644 index 000000000..9d625b267 --- /dev/null +++ b/src/main/java/net/minestom/server/item/attribute/ItemAttribute.java @@ -0,0 +1,49 @@ +package net.minestom.server.item.attribute; + +import net.minestom.server.attribute.Attribute; +import net.minestom.server.attribute.AttributeOperation; + +import java.util.UUID; + +public class ItemAttribute { + + private UUID uuid; + private String internalName; + private Attribute attribute; + private AttributeOperation operation; + private double value; + private AttributeSlot slot; + + public ItemAttribute(UUID uuid, String internalName, Attribute attribute, AttributeOperation operation, double value, AttributeSlot slot) { + this.uuid = uuid; + this.internalName = internalName; + this.attribute = attribute; + this.operation = operation; + this.value = value; + this.slot = slot; + } + + public UUID getUuid() { + return uuid; + } + + public String getInternalName() { + return internalName; + } + + public Attribute getAttribute() { + return attribute; + } + + public AttributeOperation getOperation() { + return operation; + } + + public double getValue() { + return value; + } + + public AttributeSlot getSlot() { + return slot; + } +} diff --git a/src/main/java/net/minestom/server/listener/AbilitiesListener.java b/src/main/java/net/minestom/server/listener/AbilitiesListener.java index bf98c7ae9..01240980a 100644 --- a/src/main/java/net/minestom/server/listener/AbilitiesListener.java +++ b/src/main/java/net/minestom/server/listener/AbilitiesListener.java @@ -12,6 +12,7 @@ public class AbilitiesListener { if (canFly) { boolean isFlying = (packet.flags & 0x2) > 0; + player.refreshFlying(isFlying); if (isFlying) { diff --git a/src/main/java/net/minestom/server/listener/AnimationListener.java b/src/main/java/net/minestom/server/listener/AnimationListener.java index 3fe97240c..8e56eb321 100644 --- a/src/main/java/net/minestom/server/listener/AnimationListener.java +++ b/src/main/java/net/minestom/server/listener/AnimationListener.java @@ -3,18 +3,21 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; import net.minestom.server.event.animation.AnimationEvent; import net.minestom.server.network.packet.client.play.ClientAnimationPacket; -import net.minestom.server.network.packet.server.play.EntityAnimationPacket; public class AnimationListener { public static void animationListener(ClientAnimationPacket packet, Player player) { AnimationEvent animationEvent = new AnimationEvent(player, packet.hand); player.callCancellableEvent(AnimationEvent.class, animationEvent, () -> { - EntityAnimationPacket entityAnimationPacket = new EntityAnimationPacket(); - entityAnimationPacket.entityId = player.getEntityId(); - entityAnimationPacket.animation = animationEvent.getHand() == Player.Hand.MAIN ? - EntityAnimationPacket.Animation.SWING_MAIN_ARM : EntityAnimationPacket.Animation.SWING_OFF_HAND; - player.sendPacketToViewers(entityAnimationPacket); + Player.Hand hand = animationEvent.getHand(); + switch (hand) { + case MAIN: + player.swingMainHand(); + break; + case OFF: + player.swingOffHand(); + break; + } }); } diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 4f7146ced..670286e21 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -123,7 +123,7 @@ public class BlockPlacementListener { refreshChunk = true; } } else { - PlayerUseItemOnBlockEvent event = new PlayerUseItemOnBlockEvent(hand, usedItem, blockPosition, blockFace.toDirection()); + PlayerUseItemOnBlockEvent event = new PlayerUseItemOnBlockEvent(player, hand, usedItem, blockPosition, blockFace.toDirection()); player.callEvent(PlayerUseItemOnBlockEvent.class, event); refreshChunk = true; } diff --git a/src/main/java/net/minestom/server/listener/ChatMessageListener.java b/src/main/java/net/minestom/server/listener/ChatMessageListener.java index afb0f156a..ab43e4f4b 100644 --- a/src/main/java/net/minestom/server/listener/ChatMessageListener.java +++ b/src/main/java/net/minestom/server/listener/ChatMessageListener.java @@ -12,8 +12,8 @@ import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerChatEvent; import net.minestom.server.event.player.PlayerCommandEvent; import net.minestom.server.network.packet.client.play.ClientChatMessagePacket; -import net.minestom.server.utils.validate.Check; +import java.util.Collection; import java.util.function.Function; public class ChatMessageListener { @@ -37,28 +37,23 @@ public class ChatMessageListener { } - PlayerChatEvent playerChatEvent = new PlayerChatEvent(player, MinecraftServer.getConnectionManager().getOnlinePlayers(), message); - - // Default format - playerChatEvent.setChatFormat((event) -> { - String username = player.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(" " + event.getMessage())); - - return usernameText; - }); + Collection players = MinecraftServer.getConnectionManager().getOnlinePlayers(); + PlayerChatEvent playerChatEvent = new PlayerChatEvent(player, players, message); // Call the event player.callCancellableEvent(PlayerChatEvent.class, playerChatEvent, () -> { Function formatFunction = playerChatEvent.getChatFormatFunction(); - Check.notNull(formatFunction, "PlayerChatEvent#getChatFormatFunction cannot be null!"); - TextComponent textObject = formatFunction.apply(playerChatEvent); + TextComponent textObject; + + if (formatFunction != null) { + // Custom format + textObject = formatFunction.apply(playerChatEvent); + } else { + // Default format + textObject = buildDefaultChatMessage(playerChatEvent); + } for (Player recipient : playerChatEvent.getRecipients()) { recipient.sendMessage(textObject); @@ -68,4 +63,16 @@ public class ChatMessageListener { } + private static TextComponent 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())); + + return usernameText; + } + } diff --git a/src/main/java/net/minestom/server/listener/KeepAliveListener.java b/src/main/java/net/minestom/server/listener/KeepAliveListener.java index 6deb7c19f..109e5b79f 100644 --- a/src/main/java/net/minestom/server/listener/KeepAliveListener.java +++ b/src/main/java/net/minestom/server/listener/KeepAliveListener.java @@ -1,5 +1,6 @@ package net.minestom.server.listener; +import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.client.play.ClientKeepAlivePacket; @@ -7,11 +8,18 @@ import net.minestom.server.network.packet.client.play.ClientKeepAlivePacket; public class KeepAliveListener { public static void listener(ClientKeepAlivePacket packet, Player player) { - if (packet.id != player.getLastKeepAlive()) { - player.kick(TextColor.RED + "Bad Keep Alive packet"); + final long packetId = packet.id; + 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); return; } + player.refreshAnswerKeepAlive(true); + // Update latency int latency = (int) (System.currentTimeMillis() - packet.id); player.refreshLatency(latency); diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index 5353a31ed..fd4df97e2 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -1,6 +1,5 @@ package net.minestom.server.listener; -import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Player; import net.minestom.server.event.item.ItemUpdateStateEvent; import net.minestom.server.event.player.PlayerStartDiggingEvent; @@ -14,7 +13,6 @@ import net.minestom.server.item.StackingRule; import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket; import net.minestom.server.network.packet.server.play.AcknowledgePlayerDiggingPacket; import net.minestom.server.network.packet.server.play.EntityEffectPacket; -import net.minestom.server.network.packet.server.play.RemoveEntityEffectPacket; import net.minestom.server.utils.BlockPosition; public class PlayerDiggingListener { @@ -29,72 +27,57 @@ public class PlayerDiggingListener { Instance instance = player.getInstance(); + if (instance == null) + return; + + final short blockId = instance.getBlockId(blockPosition); + switch (status) { case STARTED_DIGGING: - if (player.getGameMode() == GameMode.CREATIVE) { - if (instance != null) { - instance.breakBlock(player, blockPosition); - } - } else if (player.getGameMode() == GameMode.SURVIVAL) { - if (instance != null) { - CustomBlock customBlock = instance.getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); - if (customBlock != null) { - int breakTime = customBlock.getBreakDelay(player, blockPosition); - if (breakTime >= 0) { - PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(blockPosition, customBlock); - player.callEvent(PlayerStartDiggingEvent.class, playerStartDiggingEvent); - if (!playerStartDiggingEvent.isCancelled()) { - player.refreshTargetBlock(customBlock, blockPosition, breakTime); - sendAcknowledgePacket(player, blockPosition, customBlock.getBlockId(), - ClientPlayerDiggingPacket.Status.STARTED_DIGGING, true); - } else { - sendAcknowledgePacket(player, blockPosition, customBlock.getBlockId(), - ClientPlayerDiggingPacket.Status.STARTED_DIGGING, false); - } - addEffect(player); - } else { - if (Block.fromId(instance.getBlockId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ())).breaksInstantaneously()) { - if (player.getCustomBlockTarget() != null) { - player.resetTargetBlock(); - removeEffect(player); - } - instance.breakBlock(player, blockPosition); + final boolean instantBreak = player.isCreative() || + player.isInstantBreak() || + Block.fromId(blockId).breaksInstantaneously(); - sendAcknowledgePacket(player, blockPosition, customBlock.getBlockId(), - ClientPlayerDiggingPacket.Status.FINISHED_DIGGING, true); - } else { - player.resetTargetBlock(); - removeEffect(player); - } + if (instantBreak) { + breakBlock(instance, player, blockPosition); + } else { + CustomBlock customBlock = instance.getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + if (customBlock != null) { + int breakTime = customBlock.getBreakDelay(player, blockPosition); + if (breakTime >= 0) { + // Custom block has a custom break time, allow for digging event + PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(player, blockPosition, customBlock); + player.callEvent(PlayerStartDiggingEvent.class, playerStartDiggingEvent); + if (!playerStartDiggingEvent.isCancelled()) { + // Start digging the block + player.setTargetBlock(customBlock, blockPosition, breakTime); + sendAcknowledgePacket(player, blockPosition, customBlock.getBlockId(), + ClientPlayerDiggingPacket.Status.STARTED_DIGGING, true); + } else { + // Unsuccessful digging + sendAcknowledgePacket(player, blockPosition, customBlock.getBlockId(), + ClientPlayerDiggingPacket.Status.STARTED_DIGGING, false); } + addEffect(player); } else { - player.resetTargetBlock(); - removeEffect(player); + // Does not have a custom break time, remove effect and keep vanilla time + breakBlock(instance, player, blockPosition); } + } else { + // Player is not mining a custom block, be sure that he doesn't have the effect + removeEffect(player); } } break; case CANCELLED_DIGGING: - player.resetTargetBlock(); + // Remove custom block target removeEffect(player); - final short blockId = instance.getBlockId(blockPosition); sendAcknowledgePacket(player, blockPosition, blockId, ClientPlayerDiggingPacket.Status.CANCELLED_DIGGING, true); break; case FINISHED_DIGGING: - if (player.getCustomBlockTarget() != null) { - player.resetTargetBlock(); - removeEffect(player); - } else { - final short id = instance.getBlockId(blockPosition); - if (instance != null) { - instance.breakBlock(player, blockPosition); - } - - sendAcknowledgePacket(player, blockPosition, id, - ClientPlayerDiggingPacket.Status.FINISHED_DIGGING, true); - } + breakBlock(instance, player, blockPosition); break; case DROP_ITEM_STACK: ItemStack droppedItemStack = player.getInventory().getItemInMainHand().clone(); @@ -124,7 +107,7 @@ public class PlayerDiggingListener { break; case SWAP_ITEM_HAND: - PlayerSwapItemEvent swapItemEvent = new PlayerSwapItemEvent(offHand.clone(), mainHand.clone()); + PlayerSwapItemEvent swapItemEvent = new PlayerSwapItemEvent(player, offHand.clone(), mainHand.clone()); player.callCancellableEvent(PlayerSwapItemEvent.class, swapItemEvent, () -> { synchronized (playerInventory) { playerInventory.setItemInMainHand(swapItemEvent.getMainHandItem()); @@ -135,6 +118,18 @@ public class PlayerDiggingListener { } } + private static void breakBlock(Instance instance, Player player, BlockPosition blockPosition) { + // Finished digging, remove effect if any + removeEffect(player); + + // Unverified block break, client is fully responsive + instance.breakBlock(player, blockPosition); + + // Send acknowledge packet to confirm the digging process + sendAcknowledgePacket(player, blockPosition, 0, + ClientPlayerDiggingPacket.Status.FINISHED_DIGGING, true); + } + private static void dropItem(Player player, ItemStack droppedItem, ItemStack handItem) { PlayerInventory playerInventory = player.getInventory(); if (player.dropItem(droppedItem)) { @@ -155,10 +150,7 @@ public class PlayerDiggingListener { } private static void removeEffect(Player player) { - RemoveEntityEffectPacket removeEntityEffectPacket = new RemoveEntityEffectPacket(); - removeEntityEffectPacket.entityId = player.getEntityId(); - removeEntityEffectPacket.effectId = 4; - player.getPlayerConnection().sendPacket(removeEntityEffectPacket); + player.resetTargetBlock(); } private static void sendAcknowledgePacket(Player player, BlockPosition blockPosition, int blockId, diff --git a/src/main/java/net/minestom/server/listener/PlayerPositionListener.java b/src/main/java/net/minestom/server/listener/PlayerPositionListener.java index f1482b75e..91d9a5d17 100644 --- a/src/main/java/net/minestom/server/listener/PlayerPositionListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerPositionListener.java @@ -67,7 +67,8 @@ public class PlayerPositionListener { return; } - PlayerMoveEvent playerMoveEvent = new PlayerMoveEvent(x, y, z, yaw, pitch); + Position newPosition = new Position(x, y, z, yaw, pitch); + PlayerMoveEvent playerMoveEvent = new PlayerMoveEvent(player, newPosition); player.callEvent(PlayerMoveEvent.class, playerMoveEvent); if (!playerMoveEvent.isCancelled()) { consumer.accept(playerMoveEvent.getNewPosition()); diff --git a/src/main/java/net/minestom/server/listener/PluginMessageListener.java b/src/main/java/net/minestom/server/listener/PluginMessageListener.java index b76a4d75d..9bd798614 100644 --- a/src/main/java/net/minestom/server/listener/PluginMessageListener.java +++ b/src/main/java/net/minestom/server/listener/PluginMessageListener.java @@ -7,7 +7,7 @@ import net.minestom.server.network.packet.client.play.ClientPluginMessagePacket; public class PluginMessageListener { public static void listener(ClientPluginMessagePacket packet, Player player) { - PlayerPluginMessageEvent pluginMessageEvent = new PlayerPluginMessageEvent(packet.channel, packet.data); + PlayerPluginMessageEvent pluginMessageEvent = new PlayerPluginMessageEvent(player, packet.channel, packet.data); player.callEvent(PlayerPluginMessageEvent.class, pluginMessageEvent); } diff --git a/src/main/java/net/minestom/server/listener/ResourcePackListener.java b/src/main/java/net/minestom/server/listener/ResourcePackListener.java new file mode 100644 index 000000000..01ce51988 --- /dev/null +++ b/src/main/java/net/minestom/server/listener/ResourcePackListener.java @@ -0,0 +1,15 @@ +package net.minestom.server.listener; + +import net.minestom.server.entity.Player; +import net.minestom.server.event.player.PlayerResourcePackStatusEvent; +import net.minestom.server.network.packet.client.play.ClientResourcePackStatusPacket; +import net.minestom.server.resourcepack.ResourcePackStatus; + +public class ResourcePackListener { + + public static void listener(ClientResourcePackStatusPacket packet, Player player) { + ResourcePackStatus result = packet.result; + PlayerResourcePackStatusEvent resourcePackStatusEvent = new PlayerResourcePackStatusEvent(player, result); + player.callEvent(PlayerResourcePackStatusEvent.class, resourcePackStatusEvent); + } +} diff --git a/src/main/java/net/minestom/server/listener/TeleportListener.java b/src/main/java/net/minestom/server/listener/TeleportListener.java new file mode 100644 index 000000000..d96b977c1 --- /dev/null +++ b/src/main/java/net/minestom/server/listener/TeleportListener.java @@ -0,0 +1,12 @@ +package net.minestom.server.listener; + +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.client.play.ClientTeleportConfirmPacket; + +public class TeleportListener { + + public static void listener(ClientTeleportConfirmPacket packet, Player player) { + // Empty + } + +} diff --git a/src/main/java/net/minestom/server/listener/UseEntityListener.java b/src/main/java/net/minestom/server/listener/UseEntityListener.java index 316e1c7f6..1a40de675 100644 --- a/src/main/java/net/minestom/server/listener/UseEntityListener.java +++ b/src/main/java/net/minestom/server/listener/UseEntityListener.java @@ -26,11 +26,11 @@ public class UseEntityListener { EntityAttackEvent entityAttackEvent = new EntityAttackEvent(player, entity); player.callEvent(EntityAttackEvent.class, entityAttackEvent); } else if (type == ClientInteractEntityPacket.Type.INTERACT) { - PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(entity, packet.hand); + PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(player, entity, packet.hand); player.callEvent(PlayerInteractEvent.class, playerInteractEvent); } else { // TODO find difference with INTERACT - PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(entity, packet.hand); + PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(player, entity, packet.hand); player.callEvent(PlayerInteractEvent.class, playerInteractEvent); } } diff --git a/src/main/java/net/minestom/server/listener/UseItemListener.java b/src/main/java/net/minestom/server/listener/UseItemListener.java index ce4370e81..3236731d3 100644 --- a/src/main/java/net/minestom/server/listener/UseItemListener.java +++ b/src/main/java/net/minestom/server/listener/UseItemListener.java @@ -16,7 +16,7 @@ public class UseItemListener { PlayerInventory inventory = player.getInventory(); Player.Hand hand = packet.hand; ItemStack itemStack = hand == Player.Hand.MAIN ? inventory.getItemInMainHand() : inventory.getItemInOffHand(); - PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(hand, itemStack); + PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(player, hand, itemStack); player.callEvent(PlayerUseItemEvent.class, useItemEvent); Material material = Material.fromId(itemStack.getMaterialId()); diff --git a/src/main/java/net/minestom/server/listener/WindowListener.java b/src/main/java/net/minestom/server/listener/WindowListener.java index 8bc0331cd..aaac6c2c4 100644 --- a/src/main/java/net/minestom/server/listener/WindowListener.java +++ b/src/main/java/net/minestom/server/listener/WindowListener.java @@ -8,6 +8,7 @@ import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.play.ClientClickWindowPacket; import net.minestom.server.network.packet.client.play.ClientCloseWindow; +import net.minestom.server.network.packet.client.play.ClientWindowConfirmationPacket; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowConfirmationPacket; @@ -112,7 +113,7 @@ public class WindowListener { public static void closeWindowListener(ClientCloseWindow packet, Player player) { // if windowId == 0 then it is player's inventory, meaning that they hadn't been any open inventory packet - InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(player.getOpenInventory()); + InventoryCloseEvent inventoryCloseEvent = new InventoryCloseEvent(player, player.getOpenInventory()); player.callEvent(InventoryCloseEvent.class, inventoryCloseEvent); player.closeInventory(); @@ -122,4 +123,8 @@ public class WindowListener { player.openInventory(newInventory); } + public static void windowConfirmationListener(ClientWindowConfirmationPacket packet, Player player) { + // Empty + } + } diff --git a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java index 99d624553..d1e19c55e 100644 --- a/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java +++ b/src/main/java/net/minestom/server/listener/manager/PacketListenerManager.java @@ -21,6 +21,7 @@ public class PacketListenerManager { addListener(ClientChatMessagePacket.class, ChatMessageListener::listener); addListener(ClientClickWindowPacket.class, WindowListener::clickWindowListener); addListener(ClientCloseWindow.class, WindowListener::closeWindowListener); + addListener(ClientWindowConfirmationPacket.class, WindowListener::windowConfirmationListener); addListener(ClientEntityActionPacket.class, EntityActionListener::listener); addListener(ClientHeldItemChangePacket.class, PlayerHeldListener::heldListener); addListener(ClientPlayerBlockPlacementPacket.class, BlockPlacementListener::listener); @@ -42,15 +43,19 @@ public class PacketListenerManager { addListener(ClientTabCompletePacket.class, TabCompleteListener::listener); addListener(ClientPluginMessagePacket.class, PluginMessageListener::listener); addListener(ClientPlayerAbilitiesPacket.class, AbilitiesListener::listener); + addListener(ClientTeleportConfirmPacket.class, TeleportListener::listener); + addListener(ClientResourcePackStatusPacket.class, ResourcePackListener::listener); } public void process(T packet, Player player) { - PacketListenerConsumer packetListenerConsumer = listeners.get(packet.getClass()); + final Class clazz = packet.getClass(); + + PacketListenerConsumer packetListenerConsumer = listeners.get(clazz); // Listener can be null if none has been set before, call PacketConsumer anyway if (packetListenerConsumer == null) { - System.err.println("Packet " + packet.getClass() + " does not have any default listener!"); + System.err.println("Packet " + clazz + " does not have any default listener!"); } diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 8188ae006..390d842e6 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -1,5 +1,6 @@ package net.minestom.server.network; +import net.kyori.text.TextComponent; import net.minestom.server.entity.Player; import net.minestom.server.listener.manager.PacketConsumer; import net.minestom.server.network.player.PlayerConnection; @@ -10,7 +11,7 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; import java.util.function.Function; -public class ConnectionManager { +public final class ConnectionManager { private Set players = new CopyOnWriteArraySet<>(); private Map connectionPlayerMap = Collections.synchronizedMap(new HashMap<>()); @@ -19,14 +20,25 @@ public class ConnectionManager { private UuidProvider uuidProvider; private List> playerInitializations = new CopyOnWriteArrayList<>(); + /** + * @param connection the player connection + * @return the {@link Player} linked to the conneciton + */ public Player getPlayer(PlayerConnection connection) { return connectionPlayerMap.get(connection); } + /** + * @return an unmodifiable collection containing all the online players + */ public Collection getOnlinePlayers() { return Collections.unmodifiableCollection(players); } + /** + * @param username the player username (ignoreCase) + * @return the first player who validate the username condition + */ public Player getPlayer(String username) { for (Player player : getOnlinePlayers()) { if (player.getUsername().equalsIgnoreCase(username)) @@ -35,26 +47,47 @@ public class ConnectionManager { return null; } - public void broadcastMessage(String message, Function condition) { + /** + * 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 + */ + public void broadcastMessage(TextComponent textComponent, Function condition) { if (condition == null) { - getOnlinePlayers().forEach(player -> player.sendMessage(message)); + getOnlinePlayers().forEach(player -> player.sendMessage(textComponent)); } else { getOnlinePlayers().forEach(player -> { boolean result = condition.apply(player); if (result) - player.sendMessage(message); + player.sendMessage(textComponent); }); } } - public void broadcastMessage(String message) { - broadcastMessage(message, null); + /** + * Send a message to all online players without exception + * + * @param textComponent the message to send + */ + public void broadcastMessage(TextComponent textComponent) { + broadcastMessage(textComponent, null); } + /** + * Those are all the listeners which are called for each packet received + * + * @return an unmodifiable list of packet's consumers + */ public List getPacketConsumers() { return Collections.unmodifiableList(packetConsumers); } + /** + * Add a new packet listener + * + * @param packetConsumer the packet consumer + */ public void addPacketConsumer(PacketConsumer packetConsumer) { this.packetConsumers.add(packetConsumer); } @@ -77,16 +110,27 @@ public class ConnectionManager { * @return the uuid based on {@code playerConnection} * return a random UUID if no UUID provider is defined see {@link #setUuidProvider(UuidProvider)} */ - public UUID getPlayerConnectionUuid(PlayerConnection playerConnection) { + public UUID getPlayerConnectionUuid(PlayerConnection playerConnection, String username) { if (uuidProvider == null) return UUID.randomUUID(); - return uuidProvider.provide(playerConnection); + return uuidProvider.provide(playerConnection, username); } + /** + * Those are all the consumers called when a new player join + * + * @return an unmodifiable list containing all the player initialization consumer + */ public List> getPlayerInitializations() { return Collections.unmodifiableList(playerInitializations); } + /** + * Add a new player initialization consumer. Those are called when a player join, + * mainly to add event callbacks to the player + * + * @param playerInitialization the player initialization consumer + */ public void addPlayerInitialization(Consumer playerInitialization) { this.playerInitializations.add(playerInitialization); } diff --git a/src/main/java/net/minestom/server/network/PacketProcessor.java b/src/main/java/net/minestom/server/network/PacketProcessor.java index e7b672eb7..149798afe 100644 --- a/src/main/java/net/minestom/server/network/PacketProcessor.java +++ b/src/main/java/net/minestom/server/network/PacketProcessor.java @@ -62,7 +62,7 @@ public class PacketProcessor { switch (connectionState) { case PLAY: - Player player = connectionManager.getPlayer(playerConnection); + Player player = playerConnection.getPlayer(); ClientPlayPacket playPacket = (ClientPlayPacket) playPacketsHandler.getPacketInstance(id); playPacket.read(packetReader); diff --git a/src/main/java/net/minestom/server/network/PacketWriterUtils.java b/src/main/java/net/minestom/server/network/PacketWriterUtils.java index 31b9f00a5..4897e8bfe 100644 --- a/src/main/java/net/minestom/server/network/PacketWriterUtils.java +++ b/src/main/java/net/minestom/server/network/PacketWriterUtils.java @@ -26,8 +26,7 @@ public class PacketWriterUtils { public static void writeAndSend(Collection players, ServerPacket serverPacket) { batchesPool.execute(() -> { - int size = players.size(); - if (size == 0) + if (players.isEmpty()) return; ByteBuf buffer = PacketUtils.writePacket(serverPacket); @@ -39,6 +38,7 @@ public class PacketWriterUtils { playerConnection.sendPacket(serverPacket); } } + buffer.release(); }); } @@ -46,7 +46,8 @@ public class PacketWriterUtils { batchesPool.execute(() -> { if (PlayerUtils.isNettyClient(playerConnection)) { ByteBuf buffer = PacketUtils.writePacket(serverPacket); - playerConnection.sendPacket(buffer); + playerConnection.writePacket(buffer); + buffer.release(); } else { playerConnection.sendPacket(serverPacket); } diff --git a/src/main/java/net/minestom/server/network/UuidProvider.java b/src/main/java/net/minestom/server/network/UuidProvider.java index 23d928215..5dbd05249 100644 --- a/src/main/java/net/minestom/server/network/UuidProvider.java +++ b/src/main/java/net/minestom/server/network/UuidProvider.java @@ -6,5 +6,5 @@ import java.util.UUID; @FunctionalInterface public interface UuidProvider { - UUID provide(PlayerConnection playerConnection); + UUID provide(PlayerConnection playerConnection, String username); } diff --git a/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java b/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java index dbb737790..62e043198 100644 --- a/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java +++ b/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java @@ -28,8 +28,9 @@ public class ClientChannel extends ChannelInboundHandlerAdapter { } @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - PacketHandler packetHandler = (PacketHandler) msg; + public void channelRead(ChannelHandlerContext ctx, Object obj) { + PacketHandler packetHandler = (PacketHandler) obj; + int packetLength = packetHandler.length; ByteBuf buffer = packetHandler.buffer; @@ -49,20 +50,14 @@ public class ClientChannel extends ChannelInboundHandlerAdapter { buffer.release(); } - @Override - public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { - super.channelReadComplete(ctx); - } - @Override public void channelInactive(ChannelHandlerContext ctx) { PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx); if (playerConnection != null) { playerConnection.refreshOnline(false); - Player player = connectionManager.getPlayer(playerConnection); + Player player = playerConnection.getPlayer(); if (player != null) { player.remove(); - connectionManager.removePlayer(playerConnection); } packetProcessor.removePlayerConnection(ctx); diff --git a/src/main/java/net/minestom/server/network/packet/PacketReader.java b/src/main/java/net/minestom/server/network/packet/PacketReader.java index 0604c2e04..0c1b516dc 100644 --- a/src/main/java/net/minestom/server/network/packet/PacketReader.java +++ b/src/main/java/net/minestom/server/network/packet/PacketReader.java @@ -7,6 +7,8 @@ import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.SerializerUtils; import net.minestom.server.utils.Utils; +import java.util.UUID; + public class PacketReader { private ByteBuf buffer; @@ -90,6 +92,12 @@ public class PacketReader { return SerializerUtils.longToBlockPosition(value); } + public UUID readUuid() { + long most = readLong(); + long least = readLong(); + return new UUID(most, least); + } + public ItemStack readSlot() { return Utils.readItemStack(this); } diff --git a/src/main/java/net/minestom/server/network/packet/client/ClientPlayPacket.java b/src/main/java/net/minestom/server/network/packet/client/ClientPlayPacket.java index 15f513969..2f5df038b 100644 --- a/src/main/java/net/minestom/server/network/packet/client/ClientPlayPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/ClientPlayPacket.java @@ -2,11 +2,14 @@ package net.minestom.server.network.packet.client; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; +import net.minestom.server.listener.manager.PacketListenerManager; public abstract class ClientPlayPacket implements ClientPacket { + private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager(); + public void process(Player player) { - MinecraftServer.getPacketListenerManager().process(this, player); + PACKET_LISTENER_MANAGER.process(this, player); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/handler/ClientPacketsHandler.java b/src/main/java/net/minestom/server/network/packet/client/handler/ClientPacketsHandler.java index c79f965df..58379c49a 100644 --- a/src/main/java/net/minestom/server/network/packet/client/handler/ClientPacketsHandler.java +++ b/src/main/java/net/minestom/server/network/packet/client/handler/ClientPacketsHandler.java @@ -16,7 +16,6 @@ public class ClientPacketsHandler { } public ClientPacket getPacketInstance(int id) { - // System.out.println("RECEIVED PACKET 0x" + Integer.toHexString(id)); if (id > SIZE) throw new IllegalStateException("Packet ID 0x" + Integer.toHexString(id) + " has been tried to be parsed, debug needed"); @@ -25,6 +24,7 @@ public class ClientPacketsHandler { throw new IllegalStateException("Packet id 0x" + Integer.toHexString(id) + " isn't registered!"); ClientPacket packet = supplier.get(); + //System.out.println("RECEIVED PACKET 0x" + Integer.toHexString(id)+" : "+packet.getClass().getSimpleName()); return packet; } diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java index e596de8b8..851e694bc 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java @@ -17,10 +17,7 @@ public class LoginStartPacket implements ClientPreplayPacket { public void process(PlayerConnection connection, ConnectionManager connectionManager) { // TODO send encryption request OR directly login success - // TODO: Skin - //UUID adam = UUID.fromString("58ffa9d8-aee1-4587-8b79-41b754f6f238"); - //UUID mode = UUID.fromString("ab70ecb4-2346-4c14-a52d-7a091507c24e"); - UUID playerUuid = connectionManager.getPlayerConnectionUuid(connection); + UUID playerUuid = connectionManager.getPlayerConnectionUuid(connection, username); LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username); connection.sendPacket(successPacket); diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientResourcePackStatusPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientResourcePackStatusPacket.java index dab9753da..2a5fb3de8 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientResourcePackStatusPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientResourcePackStatusPacket.java @@ -2,18 +2,15 @@ package net.minestom.server.network.packet.client.play; import net.minestom.server.network.packet.PacketReader; import net.minestom.server.network.packet.client.ClientPlayPacket; +import net.minestom.server.resourcepack.ResourcePackStatus; public class ClientResourcePackStatusPacket extends ClientPlayPacket { - public Result result; + public ResourcePackStatus result; @Override public void read(PacketReader reader) { - this.result = Result.values()[reader.readVarInt()]; - } - - public enum Result { - SUCCESS, DECLINED, FAILED_DOWNLOAD, ACCEPTED + this.result = ResourcePackStatus.values()[reader.readVarInt()]; } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java index e5f96ed69..caac92003 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java @@ -15,13 +15,8 @@ import net.minestom.server.utils.Utils; import net.minestom.server.utils.buffer.BufferUtils; import net.minestom.server.utils.buffer.BufferWrapper; import net.minestom.server.utils.chunk.ChunkUtils; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.DoubleTag; -import net.querz.nbt.LongArrayTag; +import net.minestom.server.utils.nbt.NbtWriter; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.Set; public class ChunkDataPacket implements ServerPacket { @@ -47,6 +42,8 @@ public class ChunkDataPacket implements ServerPacket { @Override public void write(PacketWriter writer) { + NbtWriter nbtWriter = new NbtWriter(writer); + writer.writeInt(chunkX); writer.writeInt(chunkZ); writer.writeBoolean(fullChunk); @@ -80,17 +77,10 @@ public class ChunkDataPacket implements ServerPacket { } { - CompoundTag compound = new CompoundTag(); - compound.put("MOTION_BLOCKING", new LongArrayTag(Utils.encodeBlocks(motionBlocking, 9))); - compound.put("WORLD_SURFACE", new LongArrayTag(Utils.encodeBlocks(worldSurface, 9))); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try { - compound.serialize(new DataOutputStream(outputStream), 100); - } catch (IOException e) { - e.printStackTrace(); - } - byte[] data = outputStream.toByteArray(); - writer.writeBytes(data); + nbtWriter.writeCompound("", compound -> { + compound.writeLongArray("MOTION_BLOCKING", Utils.encodeBlocks(motionBlocking, 9)); + compound.writeLongArray("WORLD_SURFACE", Utils.encodeBlocks(worldSurface, 9)); + }); } // Biome data @@ -108,25 +98,20 @@ public class ChunkDataPacket implements ServerPacket { writer.writeVarInt(blockEntities.size()); for (int index : blockEntities) { - BlockPosition blockPosition = ChunkUtils.getBlockPosition(index, chunkX, chunkZ); - CompoundTag blockEntity = new CompoundTag(); - blockEntity.put("x", new DoubleTag(blockPosition.getX())); - blockEntity.put("y", new DoubleTag(blockPosition.getY())); - blockEntity.put("z", new DoubleTag(blockPosition.getZ())); - short customBlockId = customBlocksId[index]; - CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); - if (customBlock != null) { - Data data = blocksData.get(index); - customBlock.writeBlockEntity(blockPosition, data, blockEntity); - } - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - blockEntity.serialize(new DataOutputStream(os), 100); - } catch (IOException e) { - e.printStackTrace(); - } - byte[] d = os.toByteArray(); - writer.writeBytes(d); + final BlockPosition blockPosition = ChunkUtils.getBlockPosition(index, chunkX, chunkZ); + + nbtWriter.writeCompound("", compound -> { + compound.writeDouble("x", blockPosition.getX()); + compound.writeDouble("y", blockPosition.getY()); + compound.writeDouble("z", blockPosition.getZ()); + + final short customBlockId = customBlocksId[index]; + final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); + if (customBlock != null) { + Data data = blocksData.get(index); + customBlock.writeBlockEntity(blockPosition, data, compound); + } + }); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityPropertiesPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityPropertiesPacket.java index bb881f4cd..6c964886b 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityPropertiesPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityPropertiesPacket.java @@ -1,5 +1,6 @@ package net.minestom.server.network.packet.server.play; +import net.minestom.server.attribute.Attribute; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -26,14 +27,19 @@ public class EntityPropertiesPacket implements ServerPacket { public static class Property { - public String key; + public Attribute attribute; public double value; private void write(PacketWriter writer) { - writer.writeSizedString(key); - writer.writeDouble(value); + float maxValue = attribute.getMaxVanillaValue(); - // TODO modifiers + // Bypass vanilla limit client-side if needed (by sending the max value allowed) + final double v = value > maxValue ? maxValue : value; + + writer.writeSizedString(attribute.getKey()); + writer.writeDouble(v); + + // TODO support for AttributeOperation writer.writeVarInt(0); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/UpdateViewPositionPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/UpdateViewPositionPacket.java index 6ac5c22de..e0a063445 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/UpdateViewPositionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/UpdateViewPositionPacket.java @@ -1,22 +1,17 @@ package net.minestom.server.network.packet.server.play; -import net.minestom.server.instance.Chunk; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; public class UpdateViewPositionPacket implements ServerPacket { - private Chunk chunk; - - public UpdateViewPositionPacket(Chunk chunk) { - this.chunk = chunk; - } + public int chunkX, chunkZ; @Override public void write(PacketWriter writer) { - writer.writeVarInt(chunk.getChunkX()); - writer.writeVarInt(chunk.getChunkZ()); + writer.writeVarInt(chunkX); + writer.writeVarInt(chunkZ); } @Override diff --git a/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java b/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java index 05e710ffa..f3be6ccea 100644 --- a/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java @@ -2,16 +2,16 @@ package net.minestom.server.network.player; import io.netty.buffer.ByteBuf; import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Player; import net.minestom.server.entity.fakeplayer.FakePlayer; import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.utils.validate.Check; import java.net.InetSocketAddress; import java.net.SocketAddress; public class FakePlayerConnection extends PlayerConnection { - private FakePlayer fakePlayer; - @Override public void sendPacket(ByteBuf buffer) { throw new UnsupportedOperationException("FakePlayer cannot read Bytebuf"); @@ -24,12 +24,12 @@ public class FakePlayerConnection extends PlayerConnection { @Override public void sendPacket(ServerPacket serverPacket) { - this.fakePlayer.getController().consumePacket(serverPacket); + getFakePlayer().getController().consumePacket(serverPacket); } @Override public void flush() { - + // Does nothing } @Override @@ -39,15 +39,18 @@ public class FakePlayerConnection extends PlayerConnection { @Override public void disconnect() { - if (fakePlayer.isRegistered()) + if (getFakePlayer().isRegistered()) MinecraftServer.getConnectionManager().removePlayer(this); } public FakePlayer getFakePlayer() { - return fakePlayer; + return (FakePlayer) getPlayer(); } - public void setFakePlayer(FakePlayer fakePlayer) { - this.fakePlayer = fakePlayer; + + @Override + public void setPlayer(Player player) { + Check.argCondition(!(player instanceof FakePlayer), "FakePlayerController needs a FakePlayer object"); + super.setPlayer(player); } } diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index 2f33ce6af..1392a2020 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -1,6 +1,7 @@ package net.minestom.server.network.player; import io.netty.buffer.ByteBuf; +import net.minestom.server.entity.Player; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.server.ServerPacket; @@ -12,6 +13,7 @@ import java.net.SocketAddress; */ public abstract class PlayerConnection { + private Player player; private ConnectionState connectionState; private boolean online; @@ -35,6 +37,14 @@ public abstract class PlayerConnection { */ public abstract void disconnect(); + public Player getPlayer() { + return player; + } + + public void setPlayer(Player player) { + this.player = player; + } + public boolean isOnline() { return online; } diff --git a/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java b/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java index 13450b981..7f7eec431 100644 --- a/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java +++ b/src/main/java/net/minestom/server/ping/ResponseDataConsumer.java @@ -4,7 +4,5 @@ import net.minestom.server.network.player.PlayerConnection; @FunctionalInterface public interface ResponseDataConsumer { - void accept(PlayerConnection playerConnection, ResponseData responseData); - } diff --git a/src/main/java/net/minestom/server/registry/ResourceGatherer.java b/src/main/java/net/minestom/server/registry/ResourceGatherer.java index 5236bc631..cd9c20583 100644 --- a/src/main/java/net/minestom/server/registry/ResourceGatherer.java +++ b/src/main/java/net/minestom/server/registry/ResourceGatherer.java @@ -4,18 +4,9 @@ import com.google.gson.Gson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; import java.net.URL; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; +import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; /** @@ -33,21 +24,21 @@ public class ResourceGatherer { * If it is already present, directly return */ public static void ensureResourcesArePresent(File minecraftFolderOverride) throws IOException { - if(DATA_FOLDER.exists()) { + if (DATA_FOLDER.exists()) { return; } - LOGGER.info(DATA_FOLDER +" folder does not exist. Minestom will now generate the necessary files."); + LOGGER.info(DATA_FOLDER + " folder does not exist. Minestom will now generate the necessary files."); - if(!TMP_FOLDER.exists() && !TMP_FOLDER.mkdirs()) { + if (!TMP_FOLDER.exists() && !TMP_FOLDER.mkdirs()) { throw new IOException("Failed to create tmp folder."); } final String version = "1.15.2"; // TODO: Do not hardcode - + LOGGER.info("Starting download of Minecraft server jar for version " + version + " from Mojang servers..."); File minecraftFolder = getMinecraftFolder(minecraftFolderOverride); - if(!minecraftFolder.exists()) { - throw new IOException("Could not find Minecraft installation folder, attempted location "+minecraftFolder+". If this location is not the correct one, please supply the correct one as argument of ResourceGatherer#ensureResourcesArePresent"); + if (!minecraftFolder.exists()) { + throw new IOException("Could not find Minecraft installation folder, attempted location " + minecraftFolder + ". If this location is not the correct one, please supply the correct one as argument of ResourceGatherer#ensureResourcesArePresent"); } File serverJar = downloadServerJar(minecraftFolder, version); LOGGER.info("Download complete."); @@ -70,7 +61,7 @@ public class ResourceGatherer { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path relativePath = generatedFolder.relativize(dir); - if(dir.startsWith(generatedFolder)) { // don't copy logs + if (dir.startsWith(generatedFolder)) { // don't copy logs Path resolvedPath = dataFolderPath.resolve(relativePath); LOGGER.info("> Creating sub-folder " + relativePath); Files.createDirectories(resolvedPath); @@ -88,7 +79,7 @@ public class ResourceGatherer { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path relativePath = generatedFolder.relativize(file); - if(file.startsWith(generatedFolder)) { // don't copy logs + if (file.startsWith(generatedFolder)) { // don't copy logs Path resolvedPath = dataFolderPath.resolve(relativePath); LOGGER.info("> Moving " + relativePath); Files.move(file, resolvedPath); @@ -113,11 +104,11 @@ public class ResourceGatherer { new InputStreamReader(dataGeneratorProcess.getInputStream()) ).lines().forEach(LOGGER::info); LOGGER.info(""); - + try { int resultCode = dataGeneratorProcess.waitFor(); - if(resultCode != 0) { - throw new IOException("Data generator finished with non-zero return code "+resultCode); + if (resultCode != 0) { + throw new IOException("Data generator finished with non-zero return code " + resultCode); } } catch (InterruptedException e) { throw new IOException("Data generator was interrupted.", e); @@ -126,30 +117,31 @@ public class ResourceGatherer { /** * Finds the URL for the server jar inside the versions/ folder of the game installation and download the .jar file from there + * * @param minecraftFolder * @param version * @return */ private static File downloadServerJar(File minecraftFolder, String version) throws IOException { - File versionInfoFile = new File(minecraftFolder, "versions/"+version+"/"+version+".json"); - if(!versionInfoFile.exists()) { - throw new IOException("Could not find "+version+".json in your Minecraft installation. Make sure to launch this version at least once before running Minestom"); + File versionInfoFile = new File(minecraftFolder, "versions/" + version + "/" + version + ".json"); + if (!versionInfoFile.exists()) { + throw new IOException("Could not find " + version + ".json in your Minecraft installation. Make sure to launch this version at least once before running Minestom"); } - try(FileReader fileReader = new FileReader(versionInfoFile)) { + try (FileReader fileReader = new FileReader(versionInfoFile)) { Gson gson = new Gson(); VersionInfo versionInfo = gson.fromJson(fileReader, VersionInfo.class); VersionInfo.DownloadObject serverJarInfo = versionInfo.getDownloadableFiles().get("server"); String downloadURL = serverJarInfo.getUrl(); - + LOGGER.info("Found URL, starting download from " + downloadURL + "..."); return download(version, downloadURL); } } private static File download(String version, String url) throws IOException { - File target = new File(TMP_FOLDER, "server_"+version+".jar"); - try(BufferedInputStream in = new BufferedInputStream(new URL(url).openStream())) { + File target = new File(TMP_FOLDER, "server_" + version + ".jar"); + try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream())) { Files.copy(in, target.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new IOException("Failed to download Minecraft server jar.", e); @@ -158,20 +150,21 @@ public class ResourceGatherer { } private static File getMinecraftFolder(File minecraftFolderOverride) { - if(minecraftFolderOverride != null) { + if (minecraftFolderOverride != null) { return minecraftFolderOverride; } // https://help.minecraft.net/hc/en-us/articles/360035131551-Where-are-Minecraft-files-stored- String os = System.getProperty("os.name").toLowerCase(); - if(os.contains("win")) { + if (os.contains("win")) { String user = System.getProperty("user.name"); - return new File("C:/Users/"+user+"/AppData/Roaming/.minecraft/"); + return new File("C:/Users/" + user + "/AppData/Roaming/.minecraft/"); } - if(os.contains("mac")) { - return new File("~/Library/Application Support/minecraft"); + if (os.contains("mac")) { + String user = System.getProperty("user.home"); + return new File(user + "/Library/Application Support/minecraft"); } - return new File("~/.minecraft"); + return new File(System.getProperty("user.home") + "/.minecraft"); } } diff --git a/src/main/java/net/minestom/server/resourcepack/ResourcePack.java b/src/main/java/net/minestom/server/resourcepack/ResourcePack.java new file mode 100644 index 000000000..efe7ef8ee --- /dev/null +++ b/src/main/java/net/minestom/server/resourcepack/ResourcePack.java @@ -0,0 +1,37 @@ +package net.minestom.server.resourcepack; + +/** + * Represent a resource pack which can be send to a player + */ +public class ResourcePack { + + private String url; + private String hash; + + public ResourcePack(String url, String hash) { + this.url = url; + // Optional, set to empty if null + this.hash = hash == null ? "" : hash; + } + + /** + * Get the resource pack URL + * + * @return the resource pack URL + */ + public String getUrl() { + return url; + } + + /** + * Get the resource pack hash + *

+ * WARNING: if null or empty, the player will probably waste bandwidth by re-downloading + * the resource pack + * + * @return the resource pack hash + */ + public String getHash() { + return hash; + } +} diff --git a/src/main/java/net/minestom/server/resourcepack/ResourcePackStatus.java b/src/main/java/net/minestom/server/resourcepack/ResourcePackStatus.java new file mode 100644 index 000000000..e75dd0167 --- /dev/null +++ b/src/main/java/net/minestom/server/resourcepack/ResourcePackStatus.java @@ -0,0 +1,5 @@ +package net.minestom.server.resourcepack; + +public enum ResourcePackStatus { + SUCCESS, DECLINED, FAILED_DOWNLOAD, ACCEPTED +} diff --git a/src/main/java/net/minestom/server/scoreboard/TeamManager.java b/src/main/java/net/minestom/server/scoreboard/TeamManager.java index 42feaaf38..63aac80cc 100644 --- a/src/main/java/net/minestom/server/scoreboard/TeamManager.java +++ b/src/main/java/net/minestom/server/scoreboard/TeamManager.java @@ -3,7 +3,7 @@ package net.minestom.server.scoreboard; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -public class TeamManager { +public final class TeamManager { // Represents all registered teams private Set teams = new CopyOnWriteArraySet<>(); diff --git a/src/main/java/net/minestom/server/storage/StorageFolder.java b/src/main/java/net/minestom/server/storage/StorageFolder.java index 710588007..a3360becc 100644 --- a/src/main/java/net/minestom/server/storage/StorageFolder.java +++ b/src/main/java/net/minestom/server/storage/StorageFolder.java @@ -32,7 +32,7 @@ public class StorageFolder { this.storageSystem.open(folderPath); } - + public byte[] get(String key) { return storageSystem.get(key); } diff --git a/src/main/java/net/minestom/server/utils/ArrayUtils.java b/src/main/java/net/minestom/server/utils/ArrayUtils.java index 5be324cb1..6c1034293 100644 --- a/src/main/java/net/minestom/server/utils/ArrayUtils.java +++ b/src/main/java/net/minestom/server/utils/ArrayUtils.java @@ -1,6 +1,7 @@ package net.minestom.server.utils; import java.util.ArrayList; +import java.util.function.Supplier; public class ArrayUtils { @@ -61,4 +62,17 @@ public class ArrayUtils { return array; } + /** + * Fill an array by a supplier + * + * @param array the array to fill + * @param supplier the supplier to fill the array + * @param the array type + */ + public static void fill(T[] array, Supplier supplier) { + for (int i = 0; i < array.length; i++) { + array[i] = supplier.get(); + } + } + } diff --git a/src/main/java/net/minestom/server/utils/MathUtils.java b/src/main/java/net/minestom/server/utils/MathUtils.java index af6f821e5..4068b9b5b 100644 --- a/src/main/java/net/minestom/server/utils/MathUtils.java +++ b/src/main/java/net/minestom/server/utils/MathUtils.java @@ -39,6 +39,10 @@ public class MathUtils { return Direction.HORIZONTAL[directionIndex]; } + public static boolean isBetween(byte number, byte min, byte max) { + return number >= min && number <= max; + } + public static boolean isBetween(int number, int min, int max) { return number >= min && number <= max; } @@ -47,4 +51,16 @@ public class MathUtils { return number >= min && number <= max; } + public static byte setBetween(byte number, byte min, byte max) { + return number > max ? max : number < min ? min : number; + } + + public static int setBetween(int number, int min, int max) { + return number > max ? max : number < min ? min : number; + } + + public static float setBetween(float number, float min, float max) { + return number > max ? max : number < min ? min : number; + } + } diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index 33a4e24e8..caadcf36e 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -25,7 +25,7 @@ public class PacketUtils { buffer.writeBytes(bytes); //if(!(serverPacket instanceof ChunkDataPacket) && !(serverPacket instanceof PlayerListHeaderAndFooterPacket)) - //System.out.println("WRITE PACKET: " + id + " " + serverPacket.getClass().getSimpleName()); + //System.out.println("WRITE PACKET: " + serverPacket.getClass().getSimpleName()); //Unpooled.copiedBuffer(buffer); return buffer; diff --git a/src/main/java/net/minestom/server/utils/Position.java b/src/main/java/net/minestom/server/utils/Position.java index 51600f378..e1dd9d4bd 100644 --- a/src/main/java/net/minestom/server/utils/Position.java +++ b/src/main/java/net/minestom/server/utils/Position.java @@ -36,7 +36,9 @@ public class Position { } public float getDistance(Position position) { - return (float) Math.sqrt(MathUtils.square(position.getX() - getX()) + MathUtils.square(position.getY() - getY()) + MathUtils.square(position.getZ() - getZ())); + return (float) Math.sqrt(MathUtils.square(position.getX() - getX()) + + MathUtils.square(position.getY() - getY()) + + MathUtils.square(position.getZ() - getZ())); } /** diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java index 11d9a64e6..6e3c15246 100644 --- a/src/main/java/net/minestom/server/utils/Utils.java +++ b/src/main/java/net/minestom/server/utils/Utils.java @@ -5,15 +5,16 @@ import net.minestom.server.chat.Chat; import net.minestom.server.instance.Chunk; import net.minestom.server.item.Enchantment; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.attribute.ItemAttribute; import net.minestom.server.network.packet.PacketReader; import net.minestom.server.network.packet.PacketWriter; import net.minestom.server.potion.PotionType; import net.minestom.server.utils.buffer.BufferWrapper; import net.minestom.server.utils.item.NbtReaderUtils; +import net.minestom.server.utils.nbt.NBT; +import net.minestom.server.utils.nbt.NbtWriter; -import java.util.ArrayList; -import java.util.Map; -import java.util.Set; +import java.util.*; public class Utils { @@ -105,113 +106,130 @@ public class Utils { return; } - packet.writeByte((byte) 0x0A); // Compound - packet.writeShort((short) 0); // Empty compound name + NbtWriter mainWriter = new NbtWriter(packet); - // Unbreakable - if (itemStack.isUnbreakable()) { - packet.writeByte((byte) 0x03); // Integer - packet.writeShortSizedString("Unbreakable"); - packet.writeInt(1); - } - - // Start damage - { - packet.writeByte((byte) 0x02); - packet.writeShortSizedString("Damage"); - packet.writeShort(itemStack.getDamage()); - } - // End damage - - // Display - boolean hasDisplayName = itemStack.hasDisplayName(); - boolean hasLore = itemStack.hasLore(); - - if (hasDisplayName || hasLore) { - packet.writeByte((byte) 0x0A); // Start display compound - packet.writeShortSizedString("display"); - - if (hasDisplayName) { - packet.writeByte((byte) 0x08); - packet.writeShortSizedString("Name"); - packet.writeShortSizedString(Chat.toJsonString(Chat.fromLegacyText(itemStack.getDisplayName()))); + mainWriter.writeCompound("", writer -> { + // Unbreakable + if (itemStack.isUnbreakable()) { + writer.writeInt("Unbreakable", 1); } - if (hasLore) { - ArrayList lore = itemStack.getLore(); - - packet.writeByte((byte) 0x09); - packet.writeShortSizedString("Lore"); - packet.writeByte((byte) 0x08); - packet.writeInt(lore.size()); - for (String line : lore) { - packet.writeShortSizedString(Chat.toJsonString(Chat.fromLegacyText(line))); - } + // Start damage + { + writer.writeShort("Damage", itemStack.getDamage()); } + // End damage - packet.writeByte((byte) 0); // End display compound - } - // End display + // Display + boolean hasDisplayName = itemStack.hasDisplayName(); + boolean hasLore = itemStack.hasLore(); - // Start enchantment - // FIXME: something is broken, enchants are basically ignored... - { - Map enchantmentMap = itemStack.getEnchantmentMap(); - if (!enchantmentMap.isEmpty()) { - packet.writeByte((byte) 0x09); // Type id (list) - packet.writeShortSizedString("StoredEnchantments"); + if (hasDisplayName || hasLore) { + writer.writeCompound("display", displayWriter -> { + if (hasDisplayName) { + final String name = Chat.toJsonString(Chat.fromLegacyText(itemStack.getDisplayName())); + displayWriter.writeString("Name", name); + } - packet.writeByte((byte) 0x0A); // Compound - packet.writeInt(enchantmentMap.size()); // Map size + if (hasLore) { + final ArrayList lore = itemStack.getLore(); - for (Map.Entry entry : enchantmentMap.entrySet()) { - Enchantment enchantment = entry.getKey(); - short level = entry.getValue(); + displayWriter.writeList("Lore", NBT.NBT_STRING, lore.size(), () -> { + for (String line : lore) { + line = Chat.toJsonString(Chat.fromLegacyText(line)); + packet.writeShortSizedString(line); + } + }); - packet.writeByte((byte) 0x02); // Type id (short) - packet.writeShortSizedString("lvl"); - packet.writeShort(level); - - packet.writeByte((byte) 0x08); // Type id (string) - packet.writeShortSizedString("id"); - packet.writeShortSizedString("minecraft:" + enchantment.name().toLowerCase()); + } + }); + } + // End display + // Start enchantment + { + Map enchantmentMap = itemStack.getEnchantmentMap(); + if (!enchantmentMap.isEmpty()) { + writeEnchant(writer, "Enchantments", enchantmentMap); } - packet.writeByte((byte) 0); // End enchantment compound - - } - } - // End enchantment - - // Start potion - { - Set potionTypes = itemStack.getPotionTypes(); - if (!potionTypes.isEmpty()) { - for (PotionType potionType : potionTypes) { - packet.writeByte((byte) 0x08); // type id (string) - packet.writeShortSizedString("Potion"); - packet.writeShortSizedString("minecraft:" + potionType.name().toLowerCase()); - + Map storedEnchantmentMap = itemStack.getStoredEnchantmentMap(); + if (!storedEnchantmentMap.isEmpty()) { + writeEnchant(writer, "StoredEnchantments", storedEnchantmentMap); } } - } - // End potion + // End enchantment - // Start hide flags - /*{ - int hideFlag = itemStack.getHideFlag(); - if (hideFlag != 0) { - packet.writeByte((byte) 3); // Type id (int) - packet.writeShortSizedString("HideFlags"); - packet.writeInt(hideFlag); + // Start attribute + { + List itemAttributes = itemStack.getAttributes(); + if (!itemAttributes.isEmpty()) { + packet.writeByte((byte) 0x09); // Type id (list) + packet.writeShortSizedString("AttributeModifiers"); + + packet.writeByte((byte) 0x0A); // Compound + packet.writeInt(itemAttributes.size()); + + for (ItemAttribute itemAttribute : itemAttributes) { + UUID uuid = itemAttribute.getUuid(); + + writer.writeLong("UUIDMost", uuid.getMostSignificantBits()); + + writer.writeLong("UUIDLeast", uuid.getLeastSignificantBits()); + + writer.writeDouble("Amount", itemAttribute.getValue()); + + writer.writeString("Slot", itemAttribute.getSlot().name().toLowerCase()); + + writer.writeString("itemAttribute", itemAttribute.getAttribute().getKey()); + + writer.writeInt("Operation", itemAttribute.getOperation().getId()); + + writer.writeString("Name", itemAttribute.getInternalName()); + } + packet.writeByte((byte) 0x00); // End compound + } } - }*/ + // End attribute - packet.writeByte((byte) 0); // End nbt + // Start potion + { + Set potionTypes = itemStack.getPotionTypes(); + if (!potionTypes.isEmpty()) { + for (PotionType potionType : potionTypes) { + packet.writeByte((byte) 0x08); // type id (string) + packet.writeShortSizedString("Potion"); + packet.writeShortSizedString("minecraft:" + potionType.name().toLowerCase()); + } + } + } + // End potion + + // Start hide flags + { + int hideFlag = itemStack.getHideFlag(); + if (hideFlag != 0) { + writer.writeInt("HideFlags", hideFlag); + } + } + }); } } + private static void writeEnchant(NbtWriter writer, String listName, Map enchantmentMap) { + writer.writeList(listName, NBT.NBT_COMPOUND, enchantmentMap.size(), () -> { + for (Map.Entry entry : enchantmentMap.entrySet()) { + Enchantment enchantment = entry.getKey(); + short level = entry.getValue(); + + writer.writeShort("lvl", level); + + writer.writeString("id", "minecraft:" + enchantment.name().toLowerCase()); + + } + }); + } + public static ItemStack readItemStack(PacketReader reader) { boolean present = reader.readBoolean(); diff --git a/src/main/java/net/minestom/server/utils/buffer/BufferUtils.java b/src/main/java/net/minestom/server/utils/buffer/BufferUtils.java index be03d5476..7ffd61e2c 100644 --- a/src/main/java/net/minestom/server/utils/buffer/BufferUtils.java +++ b/src/main/java/net/minestom/server/utils/buffer/BufferUtils.java @@ -1,12 +1,12 @@ package net.minestom.server.utils.buffer; -import pbbl.heap.HeapByteBufferPool; +import com.github.pbbl.heap.ByteBufferPool; import java.nio.ByteBuffer; public class BufferUtils { - private static HeapByteBufferPool pool = new HeapByteBufferPool(); + private static ByteBufferPool pool = new ByteBufferPool(); public static BufferWrapper getBuffer(int size) { return new BufferWrapper(pool.take(size)); diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index cd2780446..ee87503cd 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -9,11 +9,10 @@ import net.minestom.server.utils.Position; public class ChunkUtils { /** - * @param instance the instance where {@code chunk} is - * @param chunk the chunk to check + * @param chunk the chunk to check * @return true if the chunk is unloaded, false otherwise */ - public static boolean isChunkUnloaded(Instance instance, Chunk chunk) { + public static boolean isChunkUnloaded(Chunk chunk) { return chunk == null || !chunk.isLoaded(); } @@ -28,7 +27,7 @@ public class ChunkUtils { int chunkZ = getChunkCoordinate((int) z); Chunk chunk = instance.getChunk(chunkX, chunkZ); - return isChunkUnloaded(instance, chunk); + return isChunkUnloaded(chunk); } /** @@ -84,6 +83,12 @@ public class ChunkUtils { return visibleChunks; } + /** + * @param x the block X + * @param y the block Y + * @param z the block Z + * @return an index which can be used to store and retrieve later data linked to a block position + */ public static int getBlockIndex(int x, int y, int z) { x = x % Chunk.CHUNK_SIZE_X; z = z % Chunk.CHUNK_SIZE_Z; @@ -94,11 +99,24 @@ public class ChunkUtils { return index & 0xffff; } + /** + * @param index an index computed from {@link #getBlockIndex(int, int, int)} + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @return the instance position of the block located in {@code index} + */ public static BlockPosition getBlockPosition(int index, int chunkX, int chunkZ) { int[] pos = indexToPosition(index, chunkX, chunkZ); return new BlockPosition(pos[0], pos[1], pos[2]); } + /** + * @param index an index computed from {@link #getBlockIndex(int, int, int)} + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @return the world position of the specified index with its chunks being {@code chunkX} and {@code chunk Z} + * positions in the array are in the order X/Y/Z + */ public static int[] indexToPosition(int index, int chunkX, int chunkZ) { int z = (byte) (index >> 12 & 0xF); int y = (index >>> 4 & 0xFF); @@ -110,6 +128,11 @@ public class ChunkUtils { return new int[]{x, y, z}; } + /** + * @param index an index computed from {@link #getBlockIndex(int, int, int)} + * @return the chunk position (O-15) of the specified index, + * positions in the array are in the order X/Y/Z + */ public static int[] indexToChunkPosition(int index) { return indexToPosition(index, 0, 0); } diff --git a/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java b/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java index 234725e68..a9138cbf5 100644 --- a/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java +++ b/src/main/java/net/minestom/server/utils/item/NbtReaderUtils.java @@ -1,13 +1,18 @@ package net.minestom.server.utils.item; 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.item.Enchantment; import net.minestom.server.item.ItemStack; +import net.minestom.server.item.attribute.AttributeSlot; +import net.minestom.server.item.attribute.ItemAttribute; import net.minestom.server.network.packet.PacketReader; import net.minestom.server.potion.PotionType; import java.util.ArrayList; +import java.util.UUID; public class NbtReaderUtils { @@ -50,6 +55,13 @@ public class NbtReaderUtils { item.setUnbreakable(value == 1); readItemStackNBT(reader, item); } + + if (intName.equals("HideFlags")) { + int flag = reader.readInteger(); + item.setHideFlag(flag); + readItemStackNBT(reader, item); + } + break; case 0x04: // TAG_Long @@ -80,11 +92,13 @@ public class NbtReaderUtils { String listName = reader.readShortSizedString(); - if (listName.equals("StoredEnchantments")) { + final boolean isEnchantment = listName.equals("Enchantments"); + final boolean isStoredEnchantment = listName.equals("StoredEnchantments"); + if (isEnchantment || isStoredEnchantment) { reader.readByte(); // Should be a compound (0x0A) - int size = reader.readInteger(); // Enchants count + int size = reader.readInteger(); // Enchantments count - for (int ench = 0; ench < size; ench++) { + for (int i = 0; i < size; i++) { reader.readByte(); // Type id (short) reader.readShortSizedString(); // Constant "lvl" short lvl = reader.readShort(); @@ -95,9 +109,72 @@ public class NbtReaderUtils { // Convert id id = id.replace("minecraft:", "").toUpperCase(); - Enchantment enchantment = Enchantment.valueOf(id); - item.setEnchantment(enchantment, lvl); + + if (isEnchantment) { + item.setEnchantment(enchantment, lvl); + } else if (isStoredEnchantment) { + item.setStoredEnchantment(enchantment, lvl); + } + } + + reader.readByte(); // Compound end + + readItemStackNBT(reader, item); + + } + + if (listName.equals("AttributeModifiers")) { + reader.readByte(); // Should be a compound (0x0A); + int size = reader.readInteger(); // Attributes count + for (int i = 0; i < size; i++) { + reader.readByte(); // Type id (long) + reader.readShortSizedString(); // Constant "UUIDMost" + long uuidMost = reader.readLong(); + + reader.readByte(); // Type id (long) + reader.readShortSizedString(); // Constant "UUIDLeast" + long uuidLeast = reader.readLong(); + + final UUID uuid = new UUID(uuidMost, uuidLeast); + + reader.readByte(); // Type id (double) + reader.readShortSizedString(); // Constant "Amount" + final double value = reader.readDouble(); + + reader.readByte(); // Type id (string) + reader.readShortSizedString(); // Constant "Slot" + final String slot = reader.readShortSizedString(); + + reader.readByte(); // Type id (string) + reader.readShortSizedString(); // Constant "AttributeName" + final String attributeName = reader.readShortSizedString(); + + reader.readByte(); // Type id (int) + reader.readShortSizedString(); // Constant "Operation" + final int operation = reader.readInteger(); + + reader.readByte(); // Type id (string) + reader.readShortSizedString(); // Constant "Name" + final String name = reader.readShortSizedString(); + + final Attribute attribute = Attribute.fromKey(attributeName); + // Wrong attribute name, stop here + if (attribute == null) + break; + final AttributeOperation attributeOperation = AttributeOperation.byId(operation); + // Wrong attribute operation, stop here + if (attributeOperation == null) + break; + final AttributeSlot attributeSlot = AttributeSlot.valueOf(slot.toUpperCase()); + // Wrong attribute slot, stop here + if (attributeSlot == null) + break; + + // Add attribute + final ItemAttribute itemAttribute = + new ItemAttribute(uuid, name, attribute, attributeOperation, value, attributeSlot); + item.addAttribute(itemAttribute); } reader.readByte(); // Compound end diff --git a/src/main/java/net/minestom/server/utils/nbt/NBT.java b/src/main/java/net/minestom/server/utils/nbt/NBT.java new file mode 100644 index 000000000..a5b1b5f91 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/nbt/NBT.java @@ -0,0 +1,18 @@ +package net.minestom.server.utils.nbt; + +public class NBT { + + public static final byte NBT_BYTE = 0x01; + public static final byte NBT_SHORT = 0x02; + public static final byte NBT_INT = 0x03; + public static final byte NBT_LONG = 0x04; + public static final byte NBT_FLOAT = 0x05; + public static final byte NBT_DOUBLE = 0x06; + public static final byte NBT_BYTE_ARRAY = 0x07; + public static final byte NBT_STRING = 0x08; + public static final byte NBT_LIST = 0x09; + public static final byte NBT_COMPOUND = 0x0A; + public static final byte NBT_INT_ARRAY = 0x0B; + public static final byte NBT_LONG_ARRAY = 0x0C; + +} diff --git a/src/main/java/net/minestom/server/utils/nbt/NbtConsumer.java b/src/main/java/net/minestom/server/utils/nbt/NbtConsumer.java new file mode 100644 index 000000000..008eaacd0 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/nbt/NbtConsumer.java @@ -0,0 +1,6 @@ +package net.minestom.server.utils.nbt; + +@FunctionalInterface +public interface NbtConsumer { + void accept(NbtWriter writer); +} diff --git a/src/main/java/net/minestom/server/utils/nbt/NbtWriter.java b/src/main/java/net/minestom/server/utils/nbt/NbtWriter.java new file mode 100644 index 000000000..87f79a999 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/nbt/NbtWriter.java @@ -0,0 +1,99 @@ +package net.minestom.server.utils.nbt; + +import net.minestom.server.network.packet.PacketWriter; +import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.validate.Check; + +import static net.minestom.server.utils.nbt.NBT.*; + +public class NbtWriter { + + private PacketWriter packet; + + public NbtWriter(PacketWriter packet) { + this.packet = packet; + } + + public void writeByte(String name, byte value) { + writeHeader(NBT_BYTE, name); + packet.writeByte(value); + } + + public void writeShort(String name, short value) { + writeHeader(NBT_SHORT, name); + packet.writeShort(value); + } + + public void writeInt(String name, int value) { + writeHeader(NBT_INT, name); + packet.writeInt(value); + } + + public void writeLong(String name, long value) { + writeHeader(NBT_LONG, name); + packet.writeLong(value); + } + + public void writeFloat(String name, float value) { + writeHeader(NBT_FLOAT, name); + packet.writeFloat(value); + } + + public void writeDouble(String name, double value) { + writeHeader(NBT_DOUBLE, name); + packet.writeDouble(value); + } + + public void writeByteArray(String name, byte[] value) { + writeHeader(NBT_BYTE_ARRAY, name); + packet.writeInt(value.length); + for (byte val : value) { + packet.writeByte(val); + } + } + + public void writeString(String name, String value) { + writeHeader(NBT_STRING, name); + packet.writeShortSizedString(value); + } + + public void writeList(String name, byte type, int size, Runnable callback) { + writeHeader(NBT_LIST, name); + packet.writeByte(type); + packet.writeInt(size); + callback.run(); + if (type == NBT_COMPOUND) + packet.writeByte((byte) 0x00); // End compount + } + + public void writeCompound(String name, NbtConsumer consumer) { + writeHeader(NBT_COMPOUND, name); + consumer.accept(this); + packet.writeByte((byte) 0x00); // End compound + } + + public void writeIntArray(String name, int[] value) { + writeHeader(NBT_INT_ARRAY, name); + packet.writeInt(value.length); + for (int val : value) { + packet.writeInt(val); + } + } + + public void writeLongArray(String name, long[] value) { + writeHeader(NBT_LONG_ARRAY, name); + packet.writeInt(value.length); + for (long val : value) { + packet.writeLong(val); + } + } + + private void writeHeader(byte type, String name) { + Check.argCondition(!MathUtils.isBetween(type, NBT_BYTE, NBT_LONG_ARRAY), + "The NbtTag type " + type + " is not valid"); + Check.notNull(name, "The NbtTag name cannot be null"); + packet.writeByte(type); + packet.writeShortSizedString(name); + } + +} diff --git a/src/main/java/net/minestom/server/utils/time/CooldownUtils.java b/src/main/java/net/minestom/server/utils/time/CooldownUtils.java index b4b7fe825..0a134bae3 100644 --- a/src/main/java/net/minestom/server/utils/time/CooldownUtils.java +++ b/src/main/java/net/minestom/server/utils/time/CooldownUtils.java @@ -7,6 +7,10 @@ public class CooldownUtils { return currentTime - lastUpdate < cooldownMs; } + public static boolean hasCooldown(long currentTime, long lastUpdate, UpdateOption updateOption) { + return hasCooldown(currentTime, lastUpdate, updateOption.getTimeUnit(), updateOption.getValue()); + } + public static boolean hasCooldown(long lastUpdate, TimeUnit timeUnit, int cooldown) { return hasCooldown(System.currentTimeMillis(), lastUpdate, timeUnit, cooldown); } diff --git a/src/main/java/net/minestom/server/utils/url/URLUtils.java b/src/main/java/net/minestom/server/utils/url/URLUtils.java new file mode 100644 index 000000000..ac5682133 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/url/URLUtils.java @@ -0,0 +1,40 @@ +package net.minestom.server.utils.url; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +public class URLUtils { + + public static String getText(String url) throws IOException { + HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + //add headers to the connection, or check the status if desired.. + + // handle error response code it occurs + int responseCode = connection.getResponseCode(); + InputStream inputStream; + if (200 <= responseCode && responseCode <= 299) { + inputStream = connection.getInputStream(); + } else { + inputStream = connection.getErrorStream(); + } + + BufferedReader in = new BufferedReader( + new InputStreamReader( + inputStream)); + + StringBuilder response = new StringBuilder(); + String currentLine; + + while ((currentLine = in.readLine()) != null) + response.append(currentLine); + + in.close(); + + return response.toString(); + } + +}