diff --git a/.github/README.md b/.github/README.md index 93cb8a974..b4858e1c5 100644 --- a/.github/README.md +++ b/.github/README.md @@ -5,10 +5,9 @@ [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) [![discord-banner](https://discordapp.com/api/guilds/706185253441634317/widget.png?style=banner2)](https://discord.gg/pkFRvqB) -Minestom is an alternative to the popular Minecraft server API called Bukkit. +Minestom is a complete rewrite of Minecraft server software, open-source and without any code from Mojang. - -The main difference is that our implementation of the Notchian server does not contain any features by default! +The main difference compared to it is that our implementation of the Notchian server does not contain any features by default! However, we have a complete API which allows you to make anything possible with current spigot plugins. This is a developer API not meant to be used by the end-users. Replacing Spigot/Paper with this will **not** work since we do not implement the Bukkit API. diff --git a/build.gradle b/build.gradle index 5ea60abf9..c5c564cd4 100644 --- a/build.gradle +++ b/build.gradle @@ -143,7 +143,7 @@ dependencies { api "org.spongepowered:mixin:${mixinVersion}" // Path finding - api 'com.github.MadMartian:hydrazine-path-finding:1.4.2' + api 'com.github.MadMartian:hydrazine-path-finding:1.5.0' api "org.jetbrains.kotlin:kotlin-stdlib-jdk8" diff --git a/src/autogenerated/java/net/minestom/server/entity/EntityType.java b/src/autogenerated/java/net/minestom/server/entity/EntityType.java index e3d806f38..a92bbdd83 100644 --- a/src/autogenerated/java/net/minestom/server/entity/EntityType.java +++ b/src/autogenerated/java/net/minestom/server/entity/EntityType.java @@ -242,7 +242,7 @@ public enum EntityType { } public static EntityType fromId(int id) { - if(id >= 0 && id < values().length) { + if (id >= 0 && id < values().length) { return values()[id]; } return PIG; diff --git a/src/autogenerated/java/net/minestom/server/fluids/Fluid.java b/src/autogenerated/java/net/minestom/server/fluids/Fluid.java index ae4126612..2c3715c3c 100644 --- a/src/autogenerated/java/net/minestom/server/fluids/Fluid.java +++ b/src/autogenerated/java/net/minestom/server/fluids/Fluid.java @@ -36,7 +36,7 @@ public enum Fluid { } public static Fluid fromId(int id) { - if(id >= 0 && id < values().length) { + if (id >= 0 && id < values().length) { return values()[id]; } return EMPTY; diff --git a/src/autogenerated/java/net/minestom/server/item/Enchantment.java b/src/autogenerated/java/net/minestom/server/item/Enchantment.java index c5382d0bb..ff92bf36d 100644 --- a/src/autogenerated/java/net/minestom/server/item/Enchantment.java +++ b/src/autogenerated/java/net/minestom/server/item/Enchantment.java @@ -102,7 +102,7 @@ public enum Enchantment { } public static Enchantment fromId(int id) { - if(id >= 0 && id < values().length) { + if (id >= 0 && id < values().length) { return values()[id]; } return null; diff --git a/src/autogenerated/java/net/minestom/server/particle/Particle.java b/src/autogenerated/java/net/minestom/server/particle/Particle.java index d22377fa7..4e5e15805 100644 --- a/src/autogenerated/java/net/minestom/server/particle/Particle.java +++ b/src/autogenerated/java/net/minestom/server/particle/Particle.java @@ -170,7 +170,7 @@ public enum Particle { } public static Particle fromId(int id) { - if(id >= 0 && id < values().length) { + if (id >= 0 && id < values().length) { return values()[id]; } return null; diff --git a/src/autogenerated/java/net/minestom/server/potion/PotionEffect.java b/src/autogenerated/java/net/minestom/server/potion/PotionEffect.java index 6fb8ffe37..60480a89a 100644 --- a/src/autogenerated/java/net/minestom/server/potion/PotionEffect.java +++ b/src/autogenerated/java/net/minestom/server/potion/PotionEffect.java @@ -82,7 +82,7 @@ public enum PotionEffect { } public int getId() { - return ordinal(); + return ordinal() + 1; } public String getNamespaceID() { @@ -90,8 +90,8 @@ public enum PotionEffect { } public static PotionEffect fromId(int id) { - if(id >= 0 && id < values().length) { - return values()[id]; + if (id >= 0 && id < values().length + 1) { + return values()[id - 1]; } return null; } diff --git a/src/autogenerated/java/net/minestom/server/potion/PotionType.java b/src/autogenerated/java/net/minestom/server/potion/PotionType.java index 2f6259b33..984dbedcf 100644 --- a/src/autogenerated/java/net/minestom/server/potion/PotionType.java +++ b/src/autogenerated/java/net/minestom/server/potion/PotionType.java @@ -112,7 +112,7 @@ public enum PotionType { } public static PotionType fromId(int id) { - if(id >= 0 && id < values().length) { + if (id >= 0 && id < values().length) { return values()[id]; } return EMPTY; diff --git a/src/autogenerated/java/net/minestom/server/sound/Sound.java b/src/autogenerated/java/net/minestom/server/sound/Sound.java index a0efa1a99..2497d1452 100644 --- a/src/autogenerated/java/net/minestom/server/sound/Sound.java +++ b/src/autogenerated/java/net/minestom/server/sound/Sound.java @@ -2010,7 +2010,7 @@ public enum Sound { } public static Sound fromId(int id) { - if(id >= 0 && id < values().length) { + if (id >= 0 && id < values().length) { return values()[id]; } return null; diff --git a/src/autogenerated/java/net/minestom/server/stat/StatisticType.java b/src/autogenerated/java/net/minestom/server/stat/StatisticType.java index 8beef7f5f..8b0f6dd0a 100644 --- a/src/autogenerated/java/net/minestom/server/stat/StatisticType.java +++ b/src/autogenerated/java/net/minestom/server/stat/StatisticType.java @@ -174,7 +174,7 @@ public enum StatisticType { } public static StatisticType fromId(int id) { - if(id >= 0 && id < values().length) { + if (id >= 0 && id < values().length) { return values()[id]; } return null; diff --git a/src/generators/java/net/minestom/codegen/BasicEnumGenerator.java b/src/generators/java/net/minestom/codegen/BasicEnumGenerator.java index b3141f7c9..71f6b82fe 100644 --- a/src/generators/java/net/minestom/codegen/BasicEnumGenerator.java +++ b/src/generators/java/net/minestom/codegen/BasicEnumGenerator.java @@ -20,13 +20,23 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator { - code.beginControlFlow("if($N >= 0 && $N < values().length)", idParam, idParam) - .addStatement("return values()[$N]", idParam) + code.beginControlFlow("if ($N >= 0 && $N < values().length" + ordinalIncrementCondition + ")", idParam, idParam) + .addStatement("return values()[$N" + ordinalIncrementIndex + "]", idParam) .endControlFlow() .addStatement("return " + (defaultEntry == null ? "null" : identifier(defaultEntry))); } ); } else { generator.addStaticMethod("fromId", signature, className, code -> { - code.beginControlFlow("for($T o : values())") - .beginControlFlow("if(o.getId() == id)") + code.beginControlFlow("for ($T o : values())") + .beginControlFlow("if (o.getId() == id)") .addStatement("return o") .endControlFlow() .endControlFlow() @@ -94,7 +106,7 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator code.addStatement("return ordinal()")); + generator.addMethod("getId", new ParameterSpec[0], TypeName.INT, code -> code.addStatement("return ordinal()" + (incrementOrdinal ? " + 1" : ""))); } else { generator.setParams(ParameterSpec.builder(ClassName.get(String.class), "namespaceID").build(), ParameterSpec.builder(TypeName.INT, "id").build()); generator.addMethod("getId", new ParameterSpec[0], TypeName.INT, code -> code.addStatement("return $N", "id")); diff --git a/src/generators/java/net/minestom/codegen/potions/PotionEffectEnumGenerator.java b/src/generators/java/net/minestom/codegen/potions/PotionEffectEnumGenerator.java index 9ff9d7180..77807d2c9 100644 --- a/src/generators/java/net/minestom/codegen/potions/PotionEffectEnumGenerator.java +++ b/src/generators/java/net/minestom/codegen/potions/PotionEffectEnumGenerator.java @@ -1,7 +1,6 @@ package net.minestom.codegen.potions; import net.minestom.codegen.BasicEnumGenerator; -import net.minestom.codegen.stats.StatsEnumGenerator; import net.minestom.server.registry.ResourceGatherer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +41,7 @@ public class PotionEffectEnumGenerator extends BasicEnumGenerator { } private PotionEffectEnumGenerator(File targetFolder) throws IOException { - super(targetFolder); + super(targetFolder, true, true); } @Override diff --git a/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java b/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java index 67e2078ec..6b067170f 100644 --- a/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java +++ b/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java @@ -99,11 +99,14 @@ public final class BenchmarkManager { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } + @NotNull public Map getResultMap() { return Collections.unmodifiableMap(resultMap); } + @NotNull public String getCpuMonitoringMessage() { + Check.stateCondition(!enabled, "CPU monitoring is only possible when the benchmark manager is enabled."); StringBuilder benchmarkMessage = new StringBuilder(); for (Map.Entry resultEntry : resultMap.entrySet()) { final String name = resultEntry.getKey(); diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 667cd3413..c0e003a86 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -580,11 +580,13 @@ public final class CommandManager { } else if (argument instanceof ArgumentDynamicWord) { DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, true); + final SuggestionType suggestionType = ((ArgumentDynamicWord) argument).getSuggestionType(); + argumentNode.parser = "brigadier:string"; argumentNode.properties = packetWriter -> { packetWriter.writeVarInt(0); // Single word }; - argumentNode.suggestionsType = "minecraft:ask_server"; + argumentNode.suggestionsType = suggestionType.getIdentifier(); } else if (argument instanceof ArgumentString) { DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false); diff --git a/src/main/java/net/minestom/server/command/builder/Command.java b/src/main/java/net/minestom/server/command/builder/Command.java index 0f373796a..c101ed3e7 100644 --- a/src/main/java/net/minestom/server/command/builder/Command.java +++ b/src/main/java/net/minestom/server/command/builder/Command.java @@ -5,6 +5,7 @@ import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.arguments.ArgumentDynamicStringArray; import net.minestom.server.command.builder.arguments.ArgumentDynamicWord; import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.arguments.minecraft.SuggestionType; import net.minestom.server.command.builder.condition.CommandCondition; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -187,7 +188,8 @@ public class Command { /** * Allows for tab auto completion, this is called everytime the player press a key in the chat - * when in a dynamic argument ({@link ArgumentDynamicWord} and {@link ArgumentDynamicStringArray}). + * when in a dynamic argument ({@link ArgumentDynamicWord} (when {@link SuggestionType#ASK_SERVER} is used) + * and {@link ArgumentDynamicStringArray}). * * @param text the whole player's text * @return the array containing all the suggestion for the current arg (split " "), can be null diff --git a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java index d8d2d51d9..8a46cf015 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -37,9 +37,9 @@ public class CommandDispatcher { this.commands.add(command); } - public void unregister(Command command) { + public void unregister(@NotNull Command command) { commandMap.remove(command.getName().toLowerCase()); - for(String alias : command.getAliases()) { + for (String alias : command.getAliases()) { this.commandMap.remove(alias.toLowerCase()); } commands.remove(command); @@ -248,7 +248,6 @@ public class CommandDispatcher { // Get closest valid syntax if (!syntaxesSuggestions.isEmpty()) { final int max = syntaxesSuggestions.firstKey(); // number of correct arguments - // Check if at least 1 argument of the syntax is correct if (max > 0) { // Get the data of the closest syntax diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java index 331ca68b6..a84ff80d8 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentDynamicWord.java @@ -1,22 +1,27 @@ package net.minestom.server.command.builder.arguments; +import net.minestom.server.command.builder.arguments.minecraft.SuggestionType; import net.minestom.server.utils.callback.validator.StringValidator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Same as {@link ArgumentWord} with the exception - * that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(String)}. + * that this argument can trigger {@link net.minestom.server.command.builder.Command#onDynamicWrite(String)} + * when the suggestion type is {@link SuggestionType#ASK_SERVER}, or any other suggestions available in the enum. */ public class ArgumentDynamicWord extends Argument { public static final int SPACE_ERROR = 1; public static final int RESTRICTION_ERROR = 2; + private SuggestionType suggestionType; + private StringValidator dynamicRestriction; - public ArgumentDynamicWord(String id) { + public ArgumentDynamicWord(@NotNull String id, @NotNull SuggestionType suggestionType) { super(id); + this.suggestionType = suggestionType; } @Override @@ -46,6 +51,18 @@ public class ArgumentDynamicWord extends Argument { return SUCCESS; } + /** + * Gets the suggestion type of this argument. + *

+ * Suggestions are only suggestion, this means that the end value could not be the expected one. + * + * @return this argument suggestion type + */ + @NotNull + public SuggestionType getSuggestionType() { + return suggestionType; + } + /** * Sets the dynamic restriction of this dynamic argument. *

diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java index ff46ff741..ea59cce95 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentType.java @@ -49,8 +49,12 @@ public class ArgumentType { return new ArgumentWord(id); } + public static ArgumentDynamicWord DynamicWord(@NotNull String id, @NotNull SuggestionType suggestionType) { + return new ArgumentDynamicWord(id, suggestionType); + } + public static ArgumentDynamicWord DynamicWord(@NotNull String id) { - return new ArgumentDynamicWord(id); + return DynamicWord(id, SuggestionType.ASK_SERVER); } public static ArgumentStringArray StringArray(@NotNull String id) { diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/SuggestionType.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/SuggestionType.java new file mode 100644 index 000000000..413a6f201 --- /dev/null +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/SuggestionType.java @@ -0,0 +1,22 @@ +package net.minestom.server.command.builder.arguments.minecraft; + +import org.jetbrains.annotations.NotNull; + +public enum SuggestionType { + + ASK_SERVER("minecraft:ask_server"), + ALL_RECIPES("minecraft:all_recipes"), + AVAILABLE_SOUNDS("minecraft:available_sounds"), + SUMMONABLE_ENTITIES("minecraft:summonable_entities"); + + private String identifier; + + SuggestionType(@NotNull String identifier) { + this.identifier = identifier; + } + + @NotNull + public String getIdentifier() { + return identifier; + } +} diff --git a/src/main/java/net/minestom/server/data/SerializableData.java b/src/main/java/net/minestom/server/data/SerializableData.java index 54e63cc82..bec836d19 100644 --- a/src/main/java/net/minestom/server/data/SerializableData.java +++ b/src/main/java/net/minestom/server/data/SerializableData.java @@ -6,7 +6,6 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Represents a {@link Data} object which can be serialized and read back. @@ -17,9 +16,6 @@ public interface SerializableData extends Data { DataManager DATA_MANAGER = MinecraftServer.getDataManager(); - @Override - void set(@NotNull String key, @Nullable T value, @Nullable Class type); - /** * Serializes the data into an array of bytes. *

diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 2bd8b971f..539344bfa 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -1122,34 +1122,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P this.eyeHeight = eyeHeight; } - /** - * Gets 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} - */ - public boolean sameChunk(@NotNull Position position) { - Check.notNull(position, "Position cannot be null"); - final Position pos = getPosition(); - final int chunkX1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getX())); - final int chunkZ1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getZ())); - - final int chunkX2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getX())); - final int chunkZ2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getZ())); - - return chunkX1 == chunkX2 && chunkZ1 == chunkZ2; - } - - /** - * Gets 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(@NotNull Entity entity) { - return sameChunk(entity.getPosition()); - } - /** * Removes the entity from the server immediately. *

@@ -1175,17 +1147,16 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P /** * Triggers {@link #remove()} after the specified time. * - * @param delay the time before removing the entity + * @param delay the time before removing the entity, + * 0 to cancel the removing * @param timeUnit the unit of the delay */ public void scheduleRemove(long delay, @NotNull TimeUnit timeUnit) { - delay = timeUnit.toMilliseconds(delay); - if (delay == 0) { // Cancel the scheduled remove this.scheduledRemoveTime = 0; return; } - this.scheduledRemoveTime = System.currentTimeMillis() + delay; + this.scheduledRemoveTime = System.currentTimeMillis() + timeUnit.toMilliseconds(delay); } /** diff --git a/src/main/java/net/minestom/server/entity/EntityManager.java b/src/main/java/net/minestom/server/entity/EntityManager.java index 05af5b7a8..97b48c88c 100644 --- a/src/main/java/net/minestom/server/entity/EntityManager.java +++ b/src/main/java/net/minestom/server/entity/EntityManager.java @@ -66,6 +66,7 @@ public final class EntityManager { waitingPlayer.init(); + // Spawn the player at Player#getRespawnPoint during the next instance tick spawningInstance.scheduleNextTick(waitingPlayer::setInstance); } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 2a21c93be..6e197567b 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -678,40 +678,40 @@ public class Player extends LivingEntity implements CommandSender { *

* Be aware that because chunk operations are expensive, * it is possible for this method to be non-blocking when retrieving chunks is required. - *

- * When this method is called for the first time (during player login), the player will be teleport at {@link #getRespawnPoint()}. * - * @param instance the new instance of the player + * @param instance the new player instance + * @param spawnPosition the new position of the player, + * can be null or {@link #getPosition()} if you do not want to teleport the player */ - @Override - public void setInstance(@NotNull Instance instance) { + public void setInstance(@NotNull Instance instance, @Nullable Position spawnPosition) { Check.notNull(instance, "instance cannot be null!"); Check.argCondition(this.instance == instance, "Instance should be different than the current one"); - final boolean firstSpawn = this.instance == null; // TODO: Handle player reconnections, must be false in that case too - // true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance) final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance); if (needWorldRefresh) { + final boolean firstSpawn = this.instance == null; // TODO: Handle player reconnections, must be false in that case too + // Remove all previous viewable chunks (from the previous instance) for (Chunk viewableChunk : viewableChunks) { viewableChunk.removeViewer(this); } + // Send the new dimension if (this.instance != null) { final DimensionType instanceDimensionType = instance.getDimensionType(); if (dimensionType != instanceDimensionType) sendDimension(instanceDimensionType); } + // Load all the required chunks - final Position pos = firstSpawn ? getRespawnPoint() : position; - final long[] visibleChunks = ChunkUtils.getChunksInRange(pos, getChunkRange()); + final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, getChunkRange()); final ChunkCallback eachCallback = chunk -> { if (chunk != null) { - final int chunkX = ChunkUtils.getChunkCoordinate((int) pos.getX()); - final int chunkZ = ChunkUtils.getChunkCoordinate((int) pos.getZ()); + final int chunkX = ChunkUtils.getChunkCoordinate((int) spawnPosition.getX()); + final int chunkZ = ChunkUtils.getChunkCoordinate((int) spawnPosition.getZ()); if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ) { updateViewPosition(chunkX, chunkZ); @@ -721,7 +721,7 @@ public class Player extends LivingEntity implements CommandSender { final ChunkCallback endCallback = chunk -> { // This is the last chunk to be loaded , spawn player - spawnPlayer(instance, firstSpawn); + spawnPlayer(instance, spawnPosition, firstSpawn); }; // Chunk 0;0 always needs to be loaded @@ -732,26 +732,42 @@ public class Player extends LivingEntity implements CommandSender { } else { // The player already has the good version of all the chunks. // We just need to refresh his entity viewing list and add him to the instance - spawnPlayer(instance, false); + spawnPlayer(instance, spawnPosition, false); } } + /** + * Changes the player instance without changing its position (defaulted to {@link #getRespawnPoint()} + * if the player is not in any instance. + * + * @param instance the new player instance + * @see #setInstance(Instance, Position) + */ + @Override + public void setInstance(@NotNull Instance instance) { + setInstance(instance, this.instance != null ? getPosition() : getRespawnPoint()); + } + /** * Used to spawn the player once the client has all the required chunks. *

* Does add the player to {@code instance}, remove all viewable entities and call {@link PlayerSpawnEvent}. *

- * UNSAFE: only called with {@link #setInstance(Instance)}. + * UNSAFE: only called with {@link #setInstance(Instance, Position)}. * - * @param firstSpawn true if this is the player first spawn + * @param spawnPosition the position to teleport the player + * @param firstSpawn true if this is the player first spawn */ - private void spawnPlayer(@NotNull Instance instance, boolean firstSpawn) { + private void spawnPlayer(@NotNull Instance instance, @Nullable Position spawnPosition, boolean firstSpawn) { this.viewableEntities.forEach(entity -> entity.removeViewer(this)); super.setInstance(instance); - if (firstSpawn) { - teleport(getRespawnPoint()); + if (spawnPosition != null && !position.isSimilar(spawnPosition)) { + teleport(spawnPosition, + position.inSameChunk(spawnPosition) ? () -> refreshVisibleChunks(getChunk()) : null); + } else { + refreshVisibleChunks(getChunk()); } PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn); @@ -1510,7 +1526,7 @@ public class Player extends LivingEntity implements CommandSender { // New chunks indexes final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange()); - // Find the difference between the two arrays¬ + // Find the difference between the two arrays final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks); final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks); diff --git a/src/main/java/net/minestom/server/entity/task/EntityTask.java b/src/main/java/net/minestom/server/entity/task/EntityTask.java deleted file mode 100644 index 263aaffa0..000000000 --- a/src/main/java/net/minestom/server/entity/task/EntityTask.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.minestom.server.entity.task; - -import net.minestom.server.entity.LivingEntity; - -public abstract class EntityTask { - - /** - * Whether the task should begin executing for this entity. - * - * @param entity the entity in question. - * @return true if the task should start, false otherwise. - */ - public abstract boolean shouldStart(LivingEntity entity); - - /** - * Invoked when this task is about to start for this entity. - * - * @param entity the entity in question. - */ - public abstract void start(LivingEntity entity); - - /** - * Invoked when this task is being ended for this entity. - * - * @param entity the entity in question. - */ - public abstract void end(LivingEntity entity); - - /** - * Invoked each tick when this task is being executed for this entity. - * - * @param entity the entity in question. - */ - public abstract void execute(LivingEntity entity); - -} diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 2bceca8c2..c67a53844 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -8,6 +8,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.entity.pathfinding.PFColumnarSpace; import net.minestom.server.event.player.PlayerChunkLoadEvent; import net.minestom.server.event.player.PlayerChunkUnloadEvent; +import net.minestom.server.instance.batch.BatchOption; import net.minestom.server.instance.batch.BlockBatch; import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.instance.block.Block; @@ -186,7 +187,7 @@ public abstract class Chunk implements Viewable, DataContainer { * @param z the block Z * @param data the new data, can be null */ - public abstract void setBlockData(int x, int y, int z, Data data); + public abstract void setBlockData(int x, int y, int z, @Nullable Data data); /** * Gets all the block entities in this chunk. @@ -240,15 +241,22 @@ public abstract class Chunk implements Viewable, DataContainer { /** * Creates a copy of this chunk, including blocks state id, custom block id, biomes, update data. *

- * The instance and chunk position (X/Z) can be modified using the given arguments. + * The chunk position (X/Z) can be modified using the given arguments. * - * @param chunkX the new chunk X - * @param chunkZ the new chunk Z + * @param chunkX the chunk X of the copy + * @param chunkZ the chunk Z of the copy * @return a copy of this chunk with a potentially new instance and position */ @NotNull public abstract Chunk copy(int chunkX, int chunkZ); + /** + * Resets the chunk, this means clearing all the data making it empty. + *

+ * Used for {@link BatchOption#isFullChunk()}. + */ + public abstract void reset(); + /** * Gets the {@link CustomBlock} at a position. * @@ -314,6 +322,7 @@ public abstract class Chunk implements Viewable, DataContainer { * * @return the position of this chunk */ + @NotNull public Position toPosition() { return new Position(CHUNK_SIZE_Z * getChunkX(), 0, CHUNK_SIZE_Z * getChunkZ()); } @@ -385,6 +394,37 @@ public abstract class Chunk implements Viewable, DataContainer { return fullDataPacket; } + /** + * Gets the light packet of this chunk. + * + * @return the light packet + */ + @NotNull + public UpdateLightPacket getLightPacket() { + // TODO do not hardcode light + UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime()); + updateLightPacket.chunkX = getChunkX(); + updateLightPacket.chunkZ = getChunkZ(); + updateLightPacket.skyLightMask = 0x3FFF0; + updateLightPacket.blockLightMask = 0x3F; + updateLightPacket.emptySkyLightMask = 0x0F; + updateLightPacket.emptyBlockLightMask = 0x3FFC0; + byte[] bytes = new byte[2048]; + Arrays.fill(bytes, (byte) 0xFF); + List temp = new ArrayList<>(14); + List temp2 = new ArrayList<>(6); + for (int i = 0; i < 14; ++i) { + temp.add(bytes); + } + for (int i = 0; i < 6; ++i) { + temp2.add(bytes); + } + updateLightPacket.skyLight = temp; + updateLightPacket.blockLight = temp2; + + return updateLightPacket; + } + /** * Used to verify if the chunk should still be kept in memory. * @@ -468,7 +508,7 @@ public abstract class Chunk implements Viewable, DataContainer { * * @param player the player */ - protected synchronized void sendChunk(@NotNull Player player) { + public synchronized void sendChunk(@NotNull Player player) { // Only send loaded chunk if (!isLoaded()) return; @@ -478,30 +518,16 @@ public abstract class Chunk implements Viewable, DataContainer { // Retrieve & send the buffer to the connection playerConnection.sendPacket(getFreshFullDataPacket()); - // TODO do not hardcode light - { - UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime()); - updateLightPacket.chunkX = getChunkX(); - updateLightPacket.chunkZ = getChunkZ(); - updateLightPacket.skyLightMask = 0x3FFF0; - updateLightPacket.blockLightMask = 0x3F; - updateLightPacket.emptySkyLightMask = 0x0F; - updateLightPacket.emptyBlockLightMask = 0x3FFC0; - byte[] bytes = new byte[2048]; - Arrays.fill(bytes, (byte) 0xFF); - List temp = new ArrayList<>(14); - List temp2 = new ArrayList<>(6); - for (int i = 0; i < 14; ++i) { - temp.add(bytes); - } - for (int i = 0; i < 6; ++i) { - temp2.add(bytes); - } - updateLightPacket.skyLight = temp; - updateLightPacket.blockLight = temp2; + playerConnection.sendPacket(getLightPacket()); + } - playerConnection.sendPacket(updateLightPacket); + public synchronized void sendChunk() { + if (!isLoaded()) { + return; } + + sendPacketToViewers(getFreshFullDataPacket()); + sendPacketToViewers(getLightPacket()); } /** diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 30c71f310..b630772ce 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -31,7 +31,7 @@ import java.util.Set; /** * Represents a {@link Chunk} which store each individual block in memory. *

- * WARNING: not thread-safe + * WARNING: not thread-safe. */ public class DynamicChunk extends Chunk { @@ -408,6 +408,17 @@ public class DynamicChunk extends Chunk { return dynamicChunk; } + @Override + public void reset() { + this.blockPalette.clear(); + this.customBlockPalette.clear(); + + this.blocksData.clear(); + this.updatableBlocks.clear(); + this.updatableBlocksLastUpdate.clear(); + this.blockEntities.clear(); + } + private short getBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z) { return paletteStorage.getBlockAt(x, y, z); } diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 19560c993..f501cf626 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -1,6 +1,7 @@ package net.minestom.server.instance; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minestom.server.MinecraftServer; import net.minestom.server.UpdateManager; @@ -81,7 +82,8 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta protected final Set objectEntities = new CopyOnWriteArraySet<>(); protected final Set experienceOrbs = new CopyOnWriteArraySet<>(); // Entities per chunk - protected final Long2ObjectMap> chunkEntities = new Long2ObjectOpenHashMap<>(); + protected final Long2ObjectMap> chunkEntities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + private Object entitiesLock = new Object(); // Lock used to prevent the entities Set and Map to be subject to race condition // the uuid of this instance protected UUID uniqueId; @@ -921,7 +923,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta "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"); final long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); - synchronized (chunkEntities) { + synchronized (entitiesLock) { Set entities = getEntitiesInChunk(chunkIndex); entities.add(entity); this.chunkEntities.put(chunkIndex, entities); @@ -948,7 +950,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta * @param chunk the chunk where the entity will be removed */ public void removeEntityFromChunk(@NotNull Entity entity, @NotNull Chunk chunk) { - synchronized (chunkEntities) { + synchronized (entitiesLock) { final long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); Set entities = getEntitiesInChunk(chunkIndex); entities.remove(entity); @@ -973,9 +975,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta @NotNull private Set getEntitiesInChunk(long index) { - synchronized (chunkEntities) { - return chunkEntities.computeIfAbsent(index, i -> new CopyOnWriteArraySet<>()); - } + return chunkEntities.computeIfAbsent(index, i -> new CopyOnWriteArraySet<>()); } /** diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index ae816699f..9e10afe86 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -1,6 +1,7 @@ package net.minestom.server.instance; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; @@ -57,7 +58,7 @@ public class InstanceContainer extends Instance { // the chunk generator used, can be null private ChunkGenerator chunkGenerator; // (chunk index -> chunk) map, contains all the chunks in the instance - private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectMap chunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); // contains all the chunks to remove during the next instance tick protected final Set scheduledChunksToRemove = new HashSet<>(); @@ -312,7 +313,7 @@ public class InstanceContainer extends Instance { * * @param blockPosition the position of the modified block */ - private void executeNeighboursBlockPlacementRule(BlockPosition blockPosition) { + private void executeNeighboursBlockPlacementRule(@NotNull BlockPosition blockPosition) { for (int offsetX = -1; offsetX < 2; offsetX++) { for (int offsetY = -1; offsetY < 2; offsetY++) { for (int offsetZ = -1; offsetZ < 2; offsetZ++) { @@ -451,10 +452,8 @@ public class InstanceContainer extends Instance { @Override public Chunk getChunk(int chunkX, int chunkZ) { final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ); - synchronized (chunks) { - final Chunk chunk = chunks.get(index); - return ChunkUtils.isLoaded(chunk) ? chunk : null; - } + final Chunk chunk = chunks.get(index); + return ChunkUtils.isLoaded(chunk) ? chunk : null; } /** @@ -480,7 +479,7 @@ public class InstanceContainer extends Instance { } /** - * Save the instance without callback. + * Saves the instance without callback. * * @see #saveInstance(Runnable) */ @@ -495,10 +494,8 @@ public class InstanceContainer extends Instance { @Override public void saveChunksToStorage(@Nullable Runnable callback) { - synchronized (chunks) { - Collection chunksCollection = chunks.values(); - this.chunkLoader.saveChunks(chunksCollection, callback); - } + Collection chunksCollection = chunks.values(); + this.chunkLoader.saveChunks(chunksCollection, callback); } @Override @@ -645,16 +642,14 @@ public class InstanceContainer extends Instance { copiedInstance.srcInstance = this; copiedInstance.lastBlockChangeTime = lastBlockChangeTime; - synchronized (chunks) { - for (Chunk chunk : chunks.values()) { - final int chunkX = chunk.getChunkX(); - final int chunkZ = chunk.getChunkZ(); + for (Chunk chunk : chunks.values()) { + final int chunkX = chunk.getChunkX(); + final int chunkZ = chunk.getChunkZ(); - final Chunk copiedChunk = chunk.copy(chunkX, chunkZ); + final Chunk copiedChunk = chunk.copy(chunkX, chunkZ); - copiedInstance.cacheChunk(copiedChunk); - UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunkX, chunkZ); - } + copiedInstance.cacheChunk(copiedChunk); + UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunkX, chunkZ); } return copiedInstance; @@ -701,9 +696,7 @@ public class InstanceContainer extends Instance { */ public void cacheChunk(@NotNull Chunk chunk) { final long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); - synchronized (chunks) { - this.chunks.put(index, chunk); - } + this.chunks.put(index, chunk); } @Override @@ -833,12 +826,8 @@ public class InstanceContainer extends Instance { }); // Clear cache - synchronized (chunks) { - this.chunks.remove(index); - } - synchronized (chunkEntities) { - this.chunkEntities.remove(index); - } + this.chunks.remove(index); + this.chunkEntities.remove(index); chunk.unload(); diff --git a/src/main/java/net/minestom/server/instance/batch/BatchOption.java b/src/main/java/net/minestom/server/instance/batch/BatchOption.java new file mode 100644 index 000000000..eb63e87c4 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/batch/BatchOption.java @@ -0,0 +1,33 @@ +package net.minestom.server.instance.batch; + +import org.jetbrains.annotations.NotNull; + +public class BatchOption { + + private boolean fullChunk = false; + + public BatchOption() { + } + + /** + * Gets if the batch is responsible for composing the whole chunk. + *

+ * Having it to true means that the batch will clear the chunk data before placing the blocks. + * + * @return true if the batch is responsible for all the chunk + */ + public boolean isFullChunk() { + return fullChunk; + } + + /** + * @param fullChunk true to make this batch composes the whole chunk + * @return 'this' for chaining + * @see #isFullChunk() + */ + @NotNull + public BatchOption setFullChunk(boolean fullChunk) { + this.fullChunk = fullChunk; + return this; + } +} 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 c03baf90c..81b46bbe5 100644 --- a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java @@ -6,6 +6,8 @@ import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.utils.block.CustomBlockUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashMap; @@ -23,33 +25,39 @@ import java.util.concurrent.atomic.AtomicInteger; public class BlockBatch implements InstanceBatch { private final InstanceContainer instance; + private final BatchOption batchOption; private final Map> data = new HashMap<>(); - public BlockBatch(InstanceContainer instance) { + public BlockBatch(@NotNull InstanceContainer instance, @NotNull BatchOption batchOption) { this.instance = instance; + this.batchOption = batchOption; + } + + public BlockBatch(@NotNull InstanceContainer instance) { + this(instance, new BatchOption()); } @Override - public synchronized void setBlockStateId(int x, int y, int z, short blockStateId, Data data) { + public synchronized void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) { final Chunk chunk = this.instance.getChunkAt(x, z); addBlockData(chunk, x, y, z, blockStateId, (short) 0, data); } @Override - public void setCustomBlock(int x, int y, int z, short customBlockId, Data data) { + public void setCustomBlock(int x, int y, int z, short customBlockId, @Nullable Data data) { final Chunk chunk = this.instance.getChunkAt(x, z); final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); addBlockData(chunk, x, y, z, customBlock.getDefaultBlockStateId(), customBlockId, data); } @Override - public synchronized void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, Data data) { + public synchronized void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { final Chunk chunk = this.instance.getChunkAt(x, z); addBlockData(chunk, x, y, z, blockStateId, customBlockId, data); } - private void addBlockData(Chunk chunk, int x, int y, int z, short blockStateId, short customBlockId, Data data) { + private void addBlockData(@NotNull Chunk chunk, int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { List blocksData = this.data.get(chunk); if (blocksData == null) blocksData = new ArrayList<>(); @@ -67,7 +75,7 @@ public class BlockBatch implements InstanceBatch { this.data.put(chunk, blocksData); } - public void flush(Runnable callback) { + public void flush(@Nullable Runnable callback) { synchronized (data) { AtomicInteger counter = new AtomicInteger(); for (Map.Entry> entry : data.entrySet()) { @@ -78,12 +86,20 @@ public class BlockBatch implements InstanceBatch { if (!chunk.isLoaded()) return; + if (batchOption.isFullChunk()) { + chunk.reset(); + } + for (BlockData data : dataList) { data.apply(chunk); } // Refresh chunk for viewers - chunk.sendChunkUpdate(); + if (batchOption.isFullChunk()) { + chunk.sendChunk(); + } else { + chunk.sendChunkUpdate(); + } final boolean isLast = counter.incrementAndGet() == data.size(); diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java index f2afafe8a..2226a69ff 100644 --- a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java @@ -22,7 +22,7 @@ import java.util.List; * use a {@link BlockBatch} instead otherwise. * Can be created using {@link Instance#createChunkBatch(Chunk)}. *

- * Use chunk coordinate (0-15) instead of world's. + * Uses chunk coordinate (0-15) instead of world's. * * @see InstanceBatch */ @@ -30,6 +30,7 @@ public class ChunkBatch implements InstanceBatch { private final InstanceContainer instance; private final Chunk chunk; + private final BatchOption batchOption; private final boolean generationBatch; @@ -42,9 +43,11 @@ public class ChunkBatch implements InstanceBatch { private Int2ObjectMap blockDataMap; public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk, + @NotNull BatchOption batchOption, boolean generationBatch) { this.instance = instance; this.chunk = chunk; + this.batchOption = batchOption; this.generationBatch = generationBatch; if (!generationBatch) { @@ -53,6 +56,11 @@ public class ChunkBatch implements InstanceBatch { } } + public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk, + boolean generationBatch) { + this(instance, chunk, new BatchOption(), generationBatch); + } + @Override public void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) { addBlockData((byte) x, y, (byte) z, blockStateId, (short) 0, data); @@ -120,6 +128,10 @@ public class ChunkBatch implements InstanceBatch { final List populators = chunkGenerator.getPopulators(); final boolean hasPopulator = populators != null && !populators.isEmpty(); + if (batchOption.isFullChunk()) { + this.chunk.reset(); + } + chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ()); if (hasPopulator) { @@ -128,16 +140,7 @@ public class ChunkBatch implements InstanceBatch { } } - // Refresh chunk for viewers - this.chunk.sendChunkUpdate(); - - this.instance.refreshLastBlockChangeTime(); - - // Safe callback - instance.scheduleNextTick(inst -> { - OptionalCallback.execute(callback, chunk); - }); - + updateChunk(callback, true); } }); } @@ -197,18 +200,7 @@ public class ChunkBatch implements InstanceBatch { } } - // Refresh chunk for viewers - chunk.sendChunkUpdate(); - - this.instance.refreshLastBlockChangeTime(); - - if (callback != null) { - if (safeCallback) { - this.instance.scheduleNextTick(inst -> callback.accept(chunk)); - } else { - callback.accept(chunk); - } - } + updateChunk(callback, safeCallback); } } @@ -234,8 +226,26 @@ public class ChunkBatch implements InstanceBatch { ChunkUtils.blockIndexToChunkPositionY(index), ChunkUtils.blockIndexToChunkPositionZ(index), blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId)); + } + private void updateChunk(@Nullable ChunkCallback callback, boolean safeCallback) { + // Refresh chunk for viewers + if (batchOption.isFullChunk()) { + chunk.sendChunk(); + } else { + chunk.sendChunkUpdate(); + } + + this.instance.refreshLastBlockChangeTime(); + + if (callback != null) { + if (safeCallback) { + this.instance.scheduleNextTick(inst -> callback.accept(chunk)); + } else { + callback.accept(chunk); + } + } } } diff --git a/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java b/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java index 2951de1ea..bd4951aef 100644 --- a/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java +++ b/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java @@ -45,12 +45,12 @@ public class PaletteStorage { private int valuesPerLong; private boolean hasPalette; - private long[][] sectionBlocks = new long[CHUNK_SECTION_COUNT][0]; + private long[][] sectionBlocks; // chunk section - palette index = block id - private Short2ShortLinkedOpenHashMap[] paletteBlockMaps = new Short2ShortLinkedOpenHashMap[CHUNK_SECTION_COUNT]; + private Short2ShortLinkedOpenHashMap[] paletteBlockMaps; // chunk section - block id = palette index - private Short2ShortOpenHashMap[] blockPaletteMaps = new Short2ShortOpenHashMap[CHUNK_SECTION_COUNT]; + private Short2ShortOpenHashMap[] blockPaletteMaps; /** * Creates a new palette storage. @@ -72,6 +72,15 @@ public class PaletteStorage { this.valuesPerLong = Long.SIZE / bitsPerEntry; this.hasPalette = bitsPerEntry <= PALETTE_MAXIMUM_BITS; + + init(); + } + + private void init() { + this.sectionBlocks = new long[CHUNK_SECTION_COUNT][0]; + + this.paletteBlockMaps = new Short2ShortLinkedOpenHashMap[CHUNK_SECTION_COUNT]; + this.blockPaletteMaps = new Short2ShortOpenHashMap[CHUNK_SECTION_COUNT]; } public void setBlockAt(int x, int y, int z, short blockId) { @@ -139,6 +148,14 @@ public class PaletteStorage { } } + /** + * Clears all the data in the palette and data array. + */ + public void clear() { + init(); + } + + @NotNull public PaletteStorage copy() { PaletteStorage paletteStorage = new PaletteStorage(bitsPerEntry, bitsIncrement); paletteStorage.sectionBlocks = sectionBlocks.clone(); diff --git a/src/main/java/net/minestom/server/utils/Position.java b/src/main/java/net/minestom/server/utils/Position.java index 66b1d9978..4abc78674 100644 --- a/src/main/java/net/minestom/server/utils/Position.java +++ b/src/main/java/net/minestom/server/utils/Position.java @@ -1,5 +1,8 @@ package net.minestom.server.utils; +import net.minestom.server.utils.chunk.ChunkUtils; +import org.jetbrains.annotations.NotNull; + import java.util.Objects; /** @@ -72,8 +75,8 @@ public class Position { /** * Gets the distance between 2 positions. - * In cases where performance matters, {@link #getDistanceSquared(Position)} should be used - * as it does not perform the expensive Math.sqrt method. + * In cases where performance matters, {@link #getDistanceSquared(Position)} should be used + * as it does not perform the expensive Math.sqrt method. * * @param position the second position * @return the distance between {@code this} and {@code position} @@ -86,15 +89,15 @@ public class Position { /** * Gets the square distance to another position. - * - * @param position the second position - * @return the squared distance between {@code this} and {@code position} - */ - public float getDistanceSquared(Position position) { - return MathUtils.square(getX() - position.getX()) + - MathUtils.square(getY() - position.getY()) + - MathUtils.square(getZ() - position.getZ()); - } + * + * @param position the second position + * @return the squared distance between {@code this} and {@code position} + */ + public float getDistanceSquared(Position position) { + return MathUtils.square(getX() - position.getX()) + + MathUtils.square(getY() - position.getY()) + + MathUtils.square(getZ() - position.getZ()); + } /** * Gets a unit-vector pointing in the direction that this Location is @@ -209,7 +212,7 @@ public class Position { * @param position the position to compare * @return true if the two positions are similar */ - public boolean isSimilar(Position position) { + public boolean isSimilar(@NotNull Position position) { return Float.compare(position.x, x) == 0 && Float.compare(position.y, y) == 0 && Float.compare(position.z, z) == 0; @@ -221,11 +224,27 @@ public class Position { * @param position the position to compare * @return true if the two positions have the same view */ - public boolean hasSimilarView(Position position) { + public boolean hasSimilarView(@NotNull Position position) { return Float.compare(position.yaw, yaw) == 0 && Float.compare(position.pitch, pitch) == 0; } + /** + * Gets if two positions are in the same chunk. + * + * @param position the checked position chunk + * @return true if 'this' is in the same chunk as {@code position} + */ + public boolean inSameChunk(@NotNull Position position) { + final int chunkX1 = ChunkUtils.getChunkCoordinate((int) Math.floor(getX())); + final int chunkZ1 = ChunkUtils.getChunkCoordinate((int) Math.floor(getZ())); + + final int chunkX2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getX())); + final int chunkZ2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getZ())); + + return chunkX1 == chunkX2 && chunkZ1 == chunkZ2; + } + @Override public int hashCode() { return Objects.hash(x, y, z, yaw, pitch); diff --git a/src/test/java/demo/commands/TestCommand.java b/src/test/java/demo/commands/TestCommand.java index e58c80e82..9d845d8e9 100644 --- a/src/test/java/demo/commands/TestCommand.java +++ b/src/test/java/demo/commands/TestCommand.java @@ -1,11 +1,12 @@ package demo.commands; +import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Arguments; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.arguments.ArgumentType; -import net.minestom.server.entity.Player; +import net.minestom.server.command.builder.arguments.minecraft.SuggestionType; public class TestCommand extends Command { @@ -13,20 +14,20 @@ public class TestCommand extends Command { super("testcmd"); setDefaultExecutor(this::usage); - Argument test = ArgumentType.ItemStack("test"); + Argument test = ArgumentType.DynamicWord("view_distance", SuggestionType.SUMMONABLE_ENTITIES); test.setCallback((source, value, error) -> { System.out.println("ERROR " + error); }); setDefaultExecutor((source, args) -> { - System.gc(); - source.sendMessage("Explicit GC executed!"); + //System.gc(); + //source.sendMessage("Explicit GC executed!"); + MinecraftServer.setChunkViewDistance(2); }); addSyntax((source, args) -> { - Player player = (Player) source; - System.out.println("ARG 1"); + }, test); }