mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-28 12:07:42 +01:00
Merge branch 'master' of https://github.com/Minestom/Minestom
Conflicts: src/main/java/net/minestom/server/benchmark/BenchmarkManager.java
This commit is contained in:
commit
296d645400
5
.github/README.md
vendored
5
.github/README.md
vendored
@ -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)
|
[![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)
|
[![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 compared to it is that our implementation of the Notchian server does not contain any features by default!
|
||||||
The main difference 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.
|
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.
|
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.
|
||||||
|
@ -143,7 +143,7 @@ dependencies {
|
|||||||
api "org.spongepowered:mixin:${mixinVersion}"
|
api "org.spongepowered:mixin:${mixinVersion}"
|
||||||
|
|
||||||
// Path finding
|
// 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"
|
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ public enum EntityType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static EntityType fromId(int id) {
|
public static EntityType fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length) {
|
||||||
return values()[id];
|
return values()[id];
|
||||||
}
|
}
|
||||||
return PIG;
|
return PIG;
|
||||||
|
@ -36,7 +36,7 @@ public enum Fluid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Fluid fromId(int id) {
|
public static Fluid fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length) {
|
||||||
return values()[id];
|
return values()[id];
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
|
@ -102,7 +102,7 @@ public enum Enchantment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Enchantment fromId(int id) {
|
public static Enchantment fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length) {
|
||||||
return values()[id];
|
return values()[id];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -170,7 +170,7 @@ public enum Particle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Particle fromId(int id) {
|
public static Particle fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length) {
|
||||||
return values()[id];
|
return values()[id];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -82,7 +82,7 @@ public enum PotionEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return ordinal();
|
return ordinal() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNamespaceID() {
|
public String getNamespaceID() {
|
||||||
@ -90,8 +90,8 @@ public enum PotionEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static PotionEffect fromId(int id) {
|
public static PotionEffect fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length + 1) {
|
||||||
return values()[id];
|
return values()[id - 1];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ public enum PotionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static PotionType fromId(int id) {
|
public static PotionType fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length) {
|
||||||
return values()[id];
|
return values()[id];
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
|
@ -2010,7 +2010,7 @@ public enum Sound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Sound fromId(int id) {
|
public static Sound fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length) {
|
||||||
return values()[id];
|
return values()[id];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -174,7 +174,7 @@ public enum StatisticType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static StatisticType fromId(int id) {
|
public static StatisticType fromId(int id) {
|
||||||
if(id >= 0 && id < values().length) {
|
if (id >= 0 && id < values().length) {
|
||||||
return values()[id];
|
return values()[id];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -20,13 +20,23 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator<BasicEnum
|
|||||||
private final boolean linear;
|
private final boolean linear;
|
||||||
private NamespaceID defaultEntry;
|
private NamespaceID defaultEntry;
|
||||||
|
|
||||||
protected BasicEnumGenerator(File targetFolder) throws IOException {
|
/**
|
||||||
this(targetFolder, true);
|
* True if the enum is linear and start by 1 instead of 0
|
||||||
|
*/
|
||||||
|
private boolean incrementOrdinal;
|
||||||
|
|
||||||
|
protected BasicEnumGenerator(File targetFolder, boolean linear, boolean incrementOrdinal) throws IOException {
|
||||||
|
this.linear = linear;
|
||||||
|
this.incrementOrdinal = incrementOrdinal;
|
||||||
|
generateTo(targetFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BasicEnumGenerator(File targetFolder, boolean linear) throws IOException {
|
protected BasicEnumGenerator(File targetFolder, boolean linear) throws IOException {
|
||||||
this.linear = linear;
|
this(targetFolder, linear, false);
|
||||||
generateTo(targetFolder);
|
}
|
||||||
|
|
||||||
|
protected BasicEnumGenerator(File targetFolder) throws IOException {
|
||||||
|
this(targetFolder, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -59,17 +69,19 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator<BasicEnum
|
|||||||
ParameterSpec idParam = ParameterSpec.builder(TypeName.INT, "id").build();
|
ParameterSpec idParam = ParameterSpec.builder(TypeName.INT, "id").build();
|
||||||
ParameterSpec[] signature = new ParameterSpec[]{idParam};
|
ParameterSpec[] signature = new ParameterSpec[]{idParam};
|
||||||
if (linear) {
|
if (linear) {
|
||||||
|
final String ordinalIncrementCondition = incrementOrdinal ? " + 1" : "";
|
||||||
|
final String ordinalIncrementIndex = incrementOrdinal ? " - 1" : "";
|
||||||
generator.addStaticMethod("fromId", signature, className, code -> {
|
generator.addStaticMethod("fromId", signature, className, code -> {
|
||||||
code.beginControlFlow("if($N >= 0 && $N < values().length)", idParam, idParam)
|
code.beginControlFlow("if ($N >= 0 && $N < values().length" + ordinalIncrementCondition + ")", idParam, idParam)
|
||||||
.addStatement("return values()[$N]", idParam)
|
.addStatement("return values()[$N" + ordinalIncrementIndex + "]", idParam)
|
||||||
.endControlFlow()
|
.endControlFlow()
|
||||||
.addStatement("return " + (defaultEntry == null ? "null" : identifier(defaultEntry)));
|
.addStatement("return " + (defaultEntry == null ? "null" : identifier(defaultEntry)));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
generator.addStaticMethod("fromId", signature, className, code -> {
|
generator.addStaticMethod("fromId", signature, className, code -> {
|
||||||
code.beginControlFlow("for($T o : values())")
|
code.beginControlFlow("for ($T o : values())")
|
||||||
.beginControlFlow("if(o.getId() == id)")
|
.beginControlFlow("if (o.getId() == id)")
|
||||||
.addStatement("return o")
|
.addStatement("return o")
|
||||||
.endControlFlow()
|
.endControlFlow()
|
||||||
.endControlFlow()
|
.endControlFlow()
|
||||||
@ -94,7 +106,7 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator<BasicEnum
|
|||||||
ClassName registriesClass = ClassName.get(Registries.class);
|
ClassName registriesClass = ClassName.get(Registries.class);
|
||||||
if (linear) {
|
if (linear) {
|
||||||
generator.setParams(ParameterSpec.builder(ClassName.get(String.class), "namespaceID").build());
|
generator.setParams(ParameterSpec.builder(ClassName.get(String.class), "namespaceID").build());
|
||||||
generator.addMethod("getId", new ParameterSpec[0], TypeName.INT, code -> code.addStatement("return ordinal()"));
|
generator.addMethod("getId", new ParameterSpec[0], TypeName.INT, code -> code.addStatement("return ordinal()" + (incrementOrdinal ? " + 1" : "")));
|
||||||
} else {
|
} else {
|
||||||
generator.setParams(ParameterSpec.builder(ClassName.get(String.class), "namespaceID").build(), ParameterSpec.builder(TypeName.INT, "id").build());
|
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"));
|
generator.addMethod("getId", new ParameterSpec[0], TypeName.INT, code -> code.addStatement("return $N", "id"));
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.minestom.codegen.potions;
|
package net.minestom.codegen.potions;
|
||||||
|
|
||||||
import net.minestom.codegen.BasicEnumGenerator;
|
import net.minestom.codegen.BasicEnumGenerator;
|
||||||
import net.minestom.codegen.stats.StatsEnumGenerator;
|
|
||||||
import net.minestom.server.registry.ResourceGatherer;
|
import net.minestom.server.registry.ResourceGatherer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -42,7 +41,7 @@ public class PotionEffectEnumGenerator extends BasicEnumGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PotionEffectEnumGenerator(File targetFolder) throws IOException {
|
private PotionEffectEnumGenerator(File targetFolder) throws IOException {
|
||||||
super(targetFolder);
|
super(targetFolder, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -99,11 +99,14 @@ public final class BenchmarkManager {
|
|||||||
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public Map<String, ThreadResult> getResultMap() {
|
public Map<String, ThreadResult> getResultMap() {
|
||||||
return Collections.unmodifiableMap(resultMap);
|
return Collections.unmodifiableMap(resultMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public String getCpuMonitoringMessage() {
|
public String getCpuMonitoringMessage() {
|
||||||
|
Check.stateCondition(!enabled, "CPU monitoring is only possible when the benchmark manager is enabled.");
|
||||||
StringBuilder benchmarkMessage = new StringBuilder();
|
StringBuilder benchmarkMessage = new StringBuilder();
|
||||||
for (Map.Entry<String, ThreadResult> resultEntry : resultMap.entrySet()) {
|
for (Map.Entry<String, ThreadResult> resultEntry : resultMap.entrySet()) {
|
||||||
final String name = resultEntry.getKey();
|
final String name = resultEntry.getKey();
|
||||||
|
@ -580,11 +580,13 @@ public final class CommandManager {
|
|||||||
} else if (argument instanceof ArgumentDynamicWord) {
|
} else if (argument instanceof ArgumentDynamicWord) {
|
||||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, true);
|
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, true);
|
||||||
|
|
||||||
|
final SuggestionType suggestionType = ((ArgumentDynamicWord) argument).getSuggestionType();
|
||||||
|
|
||||||
argumentNode.parser = "brigadier:string";
|
argumentNode.parser = "brigadier:string";
|
||||||
argumentNode.properties = packetWriter -> {
|
argumentNode.properties = packetWriter -> {
|
||||||
packetWriter.writeVarInt(0); // Single word
|
packetWriter.writeVarInt(0); // Single word
|
||||||
};
|
};
|
||||||
argumentNode.suggestionsType = "minecraft:ask_server";
|
argumentNode.suggestionsType = suggestionType.getIdentifier();
|
||||||
|
|
||||||
} else if (argument instanceof ArgumentString) {
|
} else if (argument instanceof ArgumentString) {
|
||||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||||
|
@ -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.ArgumentDynamicStringArray;
|
||||||
import net.minestom.server.command.builder.arguments.ArgumentDynamicWord;
|
import net.minestom.server.command.builder.arguments.ArgumentDynamicWord;
|
||||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
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 net.minestom.server.command.builder.condition.CommandCondition;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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
|
* 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
|
* @param text the whole player's text
|
||||||
* @return the array containing all the suggestion for the current arg (split " "), can be null
|
* @return the array containing all the suggestion for the current arg (split " "), can be null
|
||||||
|
@ -37,9 +37,9 @@ public class CommandDispatcher {
|
|||||||
this.commands.add(command);
|
this.commands.add(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregister(Command command) {
|
public void unregister(@NotNull Command command) {
|
||||||
commandMap.remove(command.getName().toLowerCase());
|
commandMap.remove(command.getName().toLowerCase());
|
||||||
for(String alias : command.getAliases()) {
|
for (String alias : command.getAliases()) {
|
||||||
this.commandMap.remove(alias.toLowerCase());
|
this.commandMap.remove(alias.toLowerCase());
|
||||||
}
|
}
|
||||||
commands.remove(command);
|
commands.remove(command);
|
||||||
@ -248,7 +248,6 @@ public class CommandDispatcher {
|
|||||||
// Get closest valid syntax
|
// Get closest valid syntax
|
||||||
if (!syntaxesSuggestions.isEmpty()) {
|
if (!syntaxesSuggestions.isEmpty()) {
|
||||||
final int max = syntaxesSuggestions.firstKey(); // number of correct arguments
|
final int max = syntaxesSuggestions.firstKey(); // number of correct arguments
|
||||||
|
|
||||||
// Check if at least 1 argument of the syntax is correct
|
// Check if at least 1 argument of the syntax is correct
|
||||||
if (max > 0) {
|
if (max > 0) {
|
||||||
// Get the data of the closest syntax
|
// Get the data of the closest syntax
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
package net.minestom.server.command.builder.arguments;
|
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 net.minestom.server.utils.callback.validator.StringValidator;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as {@link ArgumentWord} with the exception
|
* 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<String> {
|
public class ArgumentDynamicWord extends Argument<String> {
|
||||||
|
|
||||||
public static final int SPACE_ERROR = 1;
|
public static final int SPACE_ERROR = 1;
|
||||||
public static final int RESTRICTION_ERROR = 2;
|
public static final int RESTRICTION_ERROR = 2;
|
||||||
|
|
||||||
|
private SuggestionType suggestionType;
|
||||||
|
|
||||||
private StringValidator dynamicRestriction;
|
private StringValidator dynamicRestriction;
|
||||||
|
|
||||||
public ArgumentDynamicWord(String id) {
|
public ArgumentDynamicWord(@NotNull String id, @NotNull SuggestionType suggestionType) {
|
||||||
super(id);
|
super(id);
|
||||||
|
this.suggestionType = suggestionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -46,6 +51,18 @@ public class ArgumentDynamicWord extends Argument<String> {
|
|||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the suggestion type of this argument.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
* Sets the dynamic restriction of this dynamic argument.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -49,8 +49,12 @@ public class ArgumentType {
|
|||||||
return new ArgumentWord(id);
|
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) {
|
public static ArgumentDynamicWord DynamicWord(@NotNull String id) {
|
||||||
return new ArgumentDynamicWord(id);
|
return DynamicWord(id, SuggestionType.ASK_SERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArgumentStringArray StringArray(@NotNull String id) {
|
public static ArgumentStringArray StringArray(@NotNull String id) {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ import net.minestom.server.MinecraftServer;
|
|||||||
import net.minestom.server.utils.binary.BinaryReader;
|
import net.minestom.server.utils.binary.BinaryReader;
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a {@link Data} object which can be serialized and read back.
|
* 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();
|
DataManager DATA_MANAGER = MinecraftServer.getDataManager();
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> void set(@NotNull String key, @Nullable T value, @Nullable Class<T> type);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the data into an array of bytes.
|
* Serializes the data into an array of bytes.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -1122,34 +1122,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
|||||||
this.eyeHeight = eyeHeight;
|
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.
|
* Removes the entity from the server immediately.
|
||||||
* <p>
|
* <p>
|
||||||
@ -1175,17 +1147,16 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
|||||||
/**
|
/**
|
||||||
* Triggers {@link #remove()} after the specified time.
|
* 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
|
* @param timeUnit the unit of the delay
|
||||||
*/
|
*/
|
||||||
public void scheduleRemove(long delay, @NotNull TimeUnit timeUnit) {
|
public void scheduleRemove(long delay, @NotNull TimeUnit timeUnit) {
|
||||||
delay = timeUnit.toMilliseconds(delay);
|
|
||||||
|
|
||||||
if (delay == 0) { // Cancel the scheduled remove
|
if (delay == 0) { // Cancel the scheduled remove
|
||||||
this.scheduledRemoveTime = 0;
|
this.scheduledRemoveTime = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scheduledRemoveTime = System.currentTimeMillis() + delay;
|
this.scheduledRemoveTime = System.currentTimeMillis() + timeUnit.toMilliseconds(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,6 +66,7 @@ public final class EntityManager {
|
|||||||
|
|
||||||
waitingPlayer.init();
|
waitingPlayer.init();
|
||||||
|
|
||||||
|
// Spawn the player at Player#getRespawnPoint during the next instance tick
|
||||||
spawningInstance.scheduleNextTick(waitingPlayer::setInstance);
|
spawningInstance.scheduleNextTick(waitingPlayer::setInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -678,40 +678,40 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
* <p>
|
* <p>
|
||||||
* Be aware that because chunk operations are expensive,
|
* Be aware that because chunk operations are expensive,
|
||||||
* it is possible for this method to be non-blocking when retrieving chunks is required.
|
* it is possible for this method to be non-blocking when retrieving chunks is required.
|
||||||
* <p>
|
|
||||||
* 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, @Nullable Position spawnPosition) {
|
||||||
public void setInstance(@NotNull Instance instance) {
|
|
||||||
Check.notNull(instance, "instance cannot be null!");
|
Check.notNull(instance, "instance cannot be null!");
|
||||||
Check.argCondition(this.instance == instance, "Instance should be different than the current one");
|
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)
|
// 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);
|
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance);
|
||||||
|
|
||||||
if (needWorldRefresh) {
|
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)
|
// Remove all previous viewable chunks (from the previous instance)
|
||||||
for (Chunk viewableChunk : viewableChunks) {
|
for (Chunk viewableChunk : viewableChunks) {
|
||||||
viewableChunk.removeViewer(this);
|
viewableChunk.removeViewer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the new dimension
|
// Send the new dimension
|
||||||
if (this.instance != null) {
|
if (this.instance != null) {
|
||||||
final DimensionType instanceDimensionType = instance.getDimensionType();
|
final DimensionType instanceDimensionType = instance.getDimensionType();
|
||||||
if (dimensionType != instanceDimensionType)
|
if (dimensionType != instanceDimensionType)
|
||||||
sendDimension(instanceDimensionType);
|
sendDimension(instanceDimensionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load all the required chunks
|
// Load all the required chunks
|
||||||
final Position pos = firstSpawn ? getRespawnPoint() : position;
|
final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, getChunkRange());
|
||||||
final long[] visibleChunks = ChunkUtils.getChunksInRange(pos, getChunkRange());
|
|
||||||
|
|
||||||
final ChunkCallback eachCallback = chunk -> {
|
final ChunkCallback eachCallback = chunk -> {
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
final int chunkX = ChunkUtils.getChunkCoordinate((int) pos.getX());
|
final int chunkX = ChunkUtils.getChunkCoordinate((int) spawnPosition.getX());
|
||||||
final int chunkZ = ChunkUtils.getChunkCoordinate((int) pos.getZ());
|
final int chunkZ = ChunkUtils.getChunkCoordinate((int) spawnPosition.getZ());
|
||||||
if (chunk.getChunkX() == chunkX &&
|
if (chunk.getChunkX() == chunkX &&
|
||||||
chunk.getChunkZ() == chunkZ) {
|
chunk.getChunkZ() == chunkZ) {
|
||||||
updateViewPosition(chunkX, chunkZ);
|
updateViewPosition(chunkX, chunkZ);
|
||||||
@ -721,7 +721,7 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
|
|
||||||
final ChunkCallback endCallback = chunk -> {
|
final ChunkCallback endCallback = chunk -> {
|
||||||
// This is the last chunk to be loaded , spawn player
|
// 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
|
// Chunk 0;0 always needs to be loaded
|
||||||
@ -732,26 +732,42 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
} else {
|
} else {
|
||||||
// The player already has the good version of all the chunks.
|
// 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
|
// 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.
|
* Used to spawn the player once the client has all the required chunks.
|
||||||
* <p>
|
* <p>
|
||||||
* Does add the player to {@code instance}, remove all viewable entities and call {@link PlayerSpawnEvent}.
|
* Does add the player to {@code instance}, remove all viewable entities and call {@link PlayerSpawnEvent}.
|
||||||
* <p>
|
* <p>
|
||||||
* 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));
|
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
||||||
|
|
||||||
super.setInstance(instance);
|
super.setInstance(instance);
|
||||||
|
|
||||||
if (firstSpawn) {
|
if (spawnPosition != null && !position.isSimilar(spawnPosition)) {
|
||||||
teleport(getRespawnPoint());
|
teleport(spawnPosition,
|
||||||
|
position.inSameChunk(spawnPosition) ? () -> refreshVisibleChunks(getChunk()) : null);
|
||||||
|
} else {
|
||||||
|
refreshVisibleChunks(getChunk());
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
||||||
@ -1510,7 +1526,7 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
// New chunks indexes
|
// New chunks indexes
|
||||||
final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange());
|
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[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks);
|
||||||
final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks);
|
final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks);
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
@ -8,6 +8,7 @@ import net.minestom.server.entity.Player;
|
|||||||
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
|
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
|
||||||
import net.minestom.server.event.player.PlayerChunkLoadEvent;
|
import net.minestom.server.event.player.PlayerChunkLoadEvent;
|
||||||
import net.minestom.server.event.player.PlayerChunkUnloadEvent;
|
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.BlockBatch;
|
||||||
import net.minestom.server.instance.batch.ChunkBatch;
|
import net.minestom.server.instance.batch.ChunkBatch;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
@ -186,7 +187,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
* @param z the block Z
|
* @param z the block Z
|
||||||
* @param data the new data, can be null
|
* @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.
|
* 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.
|
* Creates a copy of this chunk, including blocks state id, custom block id, biomes, update data.
|
||||||
* <p>
|
* <p>
|
||||||
* 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 chunkX the chunk X of the copy
|
||||||
* @param chunkZ the new chunk Z
|
* @param chunkZ the chunk Z of the copy
|
||||||
* @return a copy of this chunk with a potentially new instance and position
|
* @return a copy of this chunk with a potentially new instance and position
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public abstract Chunk copy(int chunkX, int chunkZ);
|
public abstract Chunk copy(int chunkX, int chunkZ);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the chunk, this means clearing all the data making it empty.
|
||||||
|
* <p>
|
||||||
|
* Used for {@link BatchOption#isFullChunk()}.
|
||||||
|
*/
|
||||||
|
public abstract void reset();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link CustomBlock} at a position.
|
* Gets the {@link CustomBlock} at a position.
|
||||||
*
|
*
|
||||||
@ -314,6 +322,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
*
|
*
|
||||||
* @return the position of this chunk
|
* @return the position of this chunk
|
||||||
*/
|
*/
|
||||||
|
@NotNull
|
||||||
public Position toPosition() {
|
public Position toPosition() {
|
||||||
return new Position(CHUNK_SIZE_Z * getChunkX(), 0, CHUNK_SIZE_Z * getChunkZ());
|
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;
|
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<byte[]> temp = new ArrayList<>(14);
|
||||||
|
List<byte[]> 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.
|
* 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
|
* @param player the player
|
||||||
*/
|
*/
|
||||||
protected synchronized void sendChunk(@NotNull Player player) {
|
public synchronized void sendChunk(@NotNull Player player) {
|
||||||
// Only send loaded chunk
|
// Only send loaded chunk
|
||||||
if (!isLoaded())
|
if (!isLoaded())
|
||||||
return;
|
return;
|
||||||
@ -478,30 +518,16 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
// Retrieve & send the buffer to the connection
|
// Retrieve & send the buffer to the connection
|
||||||
playerConnection.sendPacket(getFreshFullDataPacket());
|
playerConnection.sendPacket(getFreshFullDataPacket());
|
||||||
|
|
||||||
// TODO do not hardcode light
|
playerConnection.sendPacket(getLightPacket());
|
||||||
{
|
}
|
||||||
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<byte[]> temp = new ArrayList<>(14);
|
|
||||||
List<byte[]> 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(updateLightPacket);
|
public synchronized void sendChunk() {
|
||||||
|
if (!isLoaded()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendPacketToViewers(getFreshFullDataPacket());
|
||||||
|
sendPacketToViewers(getLightPacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,7 +31,7 @@ import java.util.Set;
|
|||||||
/**
|
/**
|
||||||
* Represents a {@link Chunk} which store each individual block in memory.
|
* Represents a {@link Chunk} which store each individual block in memory.
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: not thread-safe
|
* WARNING: not thread-safe.
|
||||||
*/
|
*/
|
||||||
public class DynamicChunk extends Chunk {
|
public class DynamicChunk extends Chunk {
|
||||||
|
|
||||||
@ -408,6 +408,17 @@ public class DynamicChunk extends Chunk {
|
|||||||
return dynamicChunk;
|
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) {
|
private short getBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z) {
|
||||||
return paletteStorage.getBlockAt(x, y, z);
|
return paletteStorage.getBlockAt(x, y, z);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.UpdateManager;
|
import net.minestom.server.UpdateManager;
|
||||||
@ -81,7 +82,8 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
|||||||
protected final Set<ObjectEntity> objectEntities = new CopyOnWriteArraySet<>();
|
protected final Set<ObjectEntity> objectEntities = new CopyOnWriteArraySet<>();
|
||||||
protected final Set<ExperienceOrb> experienceOrbs = new CopyOnWriteArraySet<>();
|
protected final Set<ExperienceOrb> experienceOrbs = new CopyOnWriteArraySet<>();
|
||||||
// Entities per chunk
|
// Entities per chunk
|
||||||
protected final Long2ObjectMap<Set<Entity>> chunkEntities = new Long2ObjectOpenHashMap<>();
|
protected final Long2ObjectMap<Set<Entity>> 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
|
// the uuid of this instance
|
||||||
protected UUID uniqueId;
|
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)");
|
"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");
|
Check.argCondition(!chunk.isLoaded(), "Chunk " + chunk + " has been unloaded previously");
|
||||||
final long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
final long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
||||||
synchronized (chunkEntities) {
|
synchronized (entitiesLock) {
|
||||||
Set<Entity> entities = getEntitiesInChunk(chunkIndex);
|
Set<Entity> entities = getEntitiesInChunk(chunkIndex);
|
||||||
entities.add(entity);
|
entities.add(entity);
|
||||||
this.chunkEntities.put(chunkIndex, entities);
|
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
|
* @param chunk the chunk where the entity will be removed
|
||||||
*/
|
*/
|
||||||
public void removeEntityFromChunk(@NotNull Entity entity, @NotNull Chunk chunk) {
|
public void removeEntityFromChunk(@NotNull Entity entity, @NotNull Chunk chunk) {
|
||||||
synchronized (chunkEntities) {
|
synchronized (entitiesLock) {
|
||||||
final long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
final long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
||||||
Set<Entity> entities = getEntitiesInChunk(chunkIndex);
|
Set<Entity> entities = getEntitiesInChunk(chunkIndex);
|
||||||
entities.remove(entity);
|
entities.remove(entity);
|
||||||
@ -973,9 +975,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Set<Entity> getEntitiesInChunk(long index) {
|
private Set<Entity> getEntitiesInChunk(long index) {
|
||||||
synchronized (chunkEntities) {
|
return chunkEntities.computeIfAbsent(index, i -> new CopyOnWriteArraySet<>());
|
||||||
return chunkEntities.computeIfAbsent(index, i -> new CopyOnWriteArraySet<>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
@ -57,7 +58,7 @@ public class InstanceContainer extends Instance {
|
|||||||
// the chunk generator used, can be null
|
// the chunk generator used, can be null
|
||||||
private ChunkGenerator chunkGenerator;
|
private ChunkGenerator chunkGenerator;
|
||||||
// (chunk index -> chunk) map, contains all the chunks in the instance
|
// (chunk index -> chunk) map, contains all the chunks in the instance
|
||||||
private final Long2ObjectMap<Chunk> chunks = new Long2ObjectOpenHashMap<>();
|
private final Long2ObjectMap<Chunk> chunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
|
||||||
// contains all the chunks to remove during the next instance tick
|
// contains all the chunks to remove during the next instance tick
|
||||||
protected final Set<Chunk> scheduledChunksToRemove = new HashSet<>();
|
protected final Set<Chunk> scheduledChunksToRemove = new HashSet<>();
|
||||||
|
|
||||||
@ -312,7 +313,7 @@ public class InstanceContainer extends Instance {
|
|||||||
*
|
*
|
||||||
* @param blockPosition the position of the modified block
|
* @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 offsetX = -1; offsetX < 2; offsetX++) {
|
||||||
for (int offsetY = -1; offsetY < 2; offsetY++) {
|
for (int offsetY = -1; offsetY < 2; offsetY++) {
|
||||||
for (int offsetZ = -1; offsetZ < 2; offsetZ++) {
|
for (int offsetZ = -1; offsetZ < 2; offsetZ++) {
|
||||||
@ -451,10 +452,8 @@ public class InstanceContainer extends Instance {
|
|||||||
@Override
|
@Override
|
||||||
public Chunk getChunk(int chunkX, int chunkZ) {
|
public Chunk getChunk(int chunkX, int chunkZ) {
|
||||||
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
|
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
|
||||||
synchronized (chunks) {
|
final Chunk chunk = chunks.get(index);
|
||||||
final Chunk chunk = chunks.get(index);
|
return ChunkUtils.isLoaded(chunk) ? chunk : null;
|
||||||
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)
|
* @see #saveInstance(Runnable)
|
||||||
*/
|
*/
|
||||||
@ -495,10 +494,8 @@ public class InstanceContainer extends Instance {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveChunksToStorage(@Nullable Runnable callback) {
|
public void saveChunksToStorage(@Nullable Runnable callback) {
|
||||||
synchronized (chunks) {
|
Collection<Chunk> chunksCollection = chunks.values();
|
||||||
Collection<Chunk> chunksCollection = chunks.values();
|
this.chunkLoader.saveChunks(chunksCollection, callback);
|
||||||
this.chunkLoader.saveChunks(chunksCollection, callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -645,16 +642,14 @@ public class InstanceContainer extends Instance {
|
|||||||
copiedInstance.srcInstance = this;
|
copiedInstance.srcInstance = this;
|
||||||
copiedInstance.lastBlockChangeTime = lastBlockChangeTime;
|
copiedInstance.lastBlockChangeTime = lastBlockChangeTime;
|
||||||
|
|
||||||
synchronized (chunks) {
|
for (Chunk chunk : chunks.values()) {
|
||||||
for (Chunk chunk : chunks.values()) {
|
final int chunkX = chunk.getChunkX();
|
||||||
final int chunkX = chunk.getChunkX();
|
final int chunkZ = chunk.getChunkZ();
|
||||||
final int chunkZ = chunk.getChunkZ();
|
|
||||||
|
|
||||||
final Chunk copiedChunk = chunk.copy(chunkX, chunkZ);
|
final Chunk copiedChunk = chunk.copy(chunkX, chunkZ);
|
||||||
|
|
||||||
copiedInstance.cacheChunk(copiedChunk);
|
copiedInstance.cacheChunk(copiedChunk);
|
||||||
UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunkX, chunkZ);
|
UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunkX, chunkZ);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return copiedInstance;
|
return copiedInstance;
|
||||||
@ -701,9 +696,7 @@ public class InstanceContainer extends Instance {
|
|||||||
*/
|
*/
|
||||||
public void cacheChunk(@NotNull Chunk chunk) {
|
public void cacheChunk(@NotNull Chunk chunk) {
|
||||||
final long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
final long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
||||||
synchronized (chunks) {
|
this.chunks.put(index, chunk);
|
||||||
this.chunks.put(index, chunk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -833,12 +826,8 @@ public class InstanceContainer extends Instance {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Clear cache
|
// Clear cache
|
||||||
synchronized (chunks) {
|
this.chunks.remove(index);
|
||||||
this.chunks.remove(index);
|
this.chunkEntities.remove(index);
|
||||||
}
|
|
||||||
synchronized (chunkEntities) {
|
|
||||||
this.chunkEntities.remove(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk.unload();
|
chunk.unload();
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ import net.minestom.server.instance.Instance;
|
|||||||
import net.minestom.server.instance.InstanceContainer;
|
import net.minestom.server.instance.InstanceContainer;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
import net.minestom.server.utils.block.CustomBlockUtils;
|
import net.minestom.server.utils.block.CustomBlockUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -23,33 +25,39 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
public class BlockBatch implements InstanceBatch {
|
public class BlockBatch implements InstanceBatch {
|
||||||
|
|
||||||
private final InstanceContainer instance;
|
private final InstanceContainer instance;
|
||||||
|
private final BatchOption batchOption;
|
||||||
|
|
||||||
private final Map<Chunk, List<BlockData>> data = new HashMap<>();
|
private final Map<Chunk, List<BlockData>> data = new HashMap<>();
|
||||||
|
|
||||||
public BlockBatch(InstanceContainer instance) {
|
public BlockBatch(@NotNull InstanceContainer instance, @NotNull BatchOption batchOption) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
|
this.batchOption = batchOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockBatch(@NotNull InstanceContainer instance) {
|
||||||
|
this(instance, new BatchOption());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
final Chunk chunk = this.instance.getChunkAt(x, z);
|
||||||
addBlockData(chunk, x, y, z, blockStateId, (short) 0, data);
|
addBlockData(chunk, x, y, z, blockStateId, (short) 0, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 Chunk chunk = this.instance.getChunkAt(x, z);
|
||||||
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
||||||
addBlockData(chunk, x, y, z, customBlock.getDefaultBlockStateId(), customBlockId, data);
|
addBlockData(chunk, x, y, z, customBlock.getDefaultBlockStateId(), customBlockId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
final Chunk chunk = this.instance.getChunkAt(x, z);
|
||||||
addBlockData(chunk, x, y, z, blockStateId, customBlockId, data);
|
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<BlockData> blocksData = this.data.get(chunk);
|
List<BlockData> blocksData = this.data.get(chunk);
|
||||||
if (blocksData == null)
|
if (blocksData == null)
|
||||||
blocksData = new ArrayList<>();
|
blocksData = new ArrayList<>();
|
||||||
@ -67,7 +75,7 @@ public class BlockBatch implements InstanceBatch {
|
|||||||
this.data.put(chunk, blocksData);
|
this.data.put(chunk, blocksData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flush(Runnable callback) {
|
public void flush(@Nullable Runnable callback) {
|
||||||
synchronized (data) {
|
synchronized (data) {
|
||||||
AtomicInteger counter = new AtomicInteger();
|
AtomicInteger counter = new AtomicInteger();
|
||||||
for (Map.Entry<Chunk, List<BlockData>> entry : data.entrySet()) {
|
for (Map.Entry<Chunk, List<BlockData>> entry : data.entrySet()) {
|
||||||
@ -78,12 +86,20 @@ public class BlockBatch implements InstanceBatch {
|
|||||||
if (!chunk.isLoaded())
|
if (!chunk.isLoaded())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (batchOption.isFullChunk()) {
|
||||||
|
chunk.reset();
|
||||||
|
}
|
||||||
|
|
||||||
for (BlockData data : dataList) {
|
for (BlockData data : dataList) {
|
||||||
data.apply(chunk);
|
data.apply(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh chunk for viewers
|
// Refresh chunk for viewers
|
||||||
chunk.sendChunkUpdate();
|
if (batchOption.isFullChunk()) {
|
||||||
|
chunk.sendChunk();
|
||||||
|
} else {
|
||||||
|
chunk.sendChunkUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
final boolean isLast = counter.incrementAndGet() == data.size();
|
final boolean isLast = counter.incrementAndGet() == data.size();
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import java.util.List;
|
|||||||
* use a {@link BlockBatch} instead otherwise.
|
* use a {@link BlockBatch} instead otherwise.
|
||||||
* Can be created using {@link Instance#createChunkBatch(Chunk)}.
|
* Can be created using {@link Instance#createChunkBatch(Chunk)}.
|
||||||
* <p>
|
* <p>
|
||||||
* Use chunk coordinate (0-15) instead of world's.
|
* Uses chunk coordinate (0-15) instead of world's.
|
||||||
*
|
*
|
||||||
* @see InstanceBatch
|
* @see InstanceBatch
|
||||||
*/
|
*/
|
||||||
@ -30,6 +30,7 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
|
|
||||||
private final InstanceContainer instance;
|
private final InstanceContainer instance;
|
||||||
private final Chunk chunk;
|
private final Chunk chunk;
|
||||||
|
private final BatchOption batchOption;
|
||||||
|
|
||||||
private final boolean generationBatch;
|
private final boolean generationBatch;
|
||||||
|
|
||||||
@ -42,9 +43,11 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
private Int2ObjectMap<Data> blockDataMap;
|
private Int2ObjectMap<Data> blockDataMap;
|
||||||
|
|
||||||
public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk,
|
public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk,
|
||||||
|
@NotNull BatchOption batchOption,
|
||||||
boolean generationBatch) {
|
boolean generationBatch) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
|
this.batchOption = batchOption;
|
||||||
this.generationBatch = generationBatch;
|
this.generationBatch = generationBatch;
|
||||||
|
|
||||||
if (!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
|
@Override
|
||||||
public void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) {
|
public void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) {
|
||||||
addBlockData((byte) x, y, (byte) z, blockStateId, (short) 0, data);
|
addBlockData((byte) x, y, (byte) z, blockStateId, (short) 0, data);
|
||||||
@ -120,6 +128,10 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
|
final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
|
||||||
final boolean hasPopulator = populators != null && !populators.isEmpty();
|
final boolean hasPopulator = populators != null && !populators.isEmpty();
|
||||||
|
|
||||||
|
if (batchOption.isFullChunk()) {
|
||||||
|
this.chunk.reset();
|
||||||
|
}
|
||||||
|
|
||||||
chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ());
|
chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ());
|
||||||
|
|
||||||
if (hasPopulator) {
|
if (hasPopulator) {
|
||||||
@ -128,16 +140,7 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh chunk for viewers
|
updateChunk(callback, true);
|
||||||
this.chunk.sendChunkUpdate();
|
|
||||||
|
|
||||||
this.instance.refreshLastBlockChangeTime();
|
|
||||||
|
|
||||||
// Safe callback
|
|
||||||
instance.scheduleNextTick(inst -> {
|
|
||||||
OptionalCallback.execute(callback, chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -197,18 +200,7 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh chunk for viewers
|
updateChunk(callback, safeCallback);
|
||||||
chunk.sendChunkUpdate();
|
|
||||||
|
|
||||||
this.instance.refreshLastBlockChangeTime();
|
|
||||||
|
|
||||||
if (callback != null) {
|
|
||||||
if (safeCallback) {
|
|
||||||
this.instance.scheduleNextTick(inst -> callback.accept(chunk));
|
|
||||||
} else {
|
|
||||||
callback.accept(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,8 +226,26 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
ChunkUtils.blockIndexToChunkPositionY(index),
|
ChunkUtils.blockIndexToChunkPositionY(index),
|
||||||
ChunkUtils.blockIndexToChunkPositionZ(index),
|
ChunkUtils.blockIndexToChunkPositionZ(index),
|
||||||
blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,12 @@ public class PaletteStorage {
|
|||||||
private int valuesPerLong;
|
private int valuesPerLong;
|
||||||
private boolean hasPalette;
|
private boolean hasPalette;
|
||||||
|
|
||||||
private long[][] sectionBlocks = new long[CHUNK_SECTION_COUNT][0];
|
private long[][] sectionBlocks;
|
||||||
|
|
||||||
// chunk section - palette index = block id
|
// chunk section - palette index = block id
|
||||||
private Short2ShortLinkedOpenHashMap[] paletteBlockMaps = new Short2ShortLinkedOpenHashMap[CHUNK_SECTION_COUNT];
|
private Short2ShortLinkedOpenHashMap[] paletteBlockMaps;
|
||||||
// chunk section - block id = palette index
|
// chunk section - block id = palette index
|
||||||
private Short2ShortOpenHashMap[] blockPaletteMaps = new Short2ShortOpenHashMap[CHUNK_SECTION_COUNT];
|
private Short2ShortOpenHashMap[] blockPaletteMaps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new palette storage.
|
* Creates a new palette storage.
|
||||||
@ -72,6 +72,15 @@ public class PaletteStorage {
|
|||||||
|
|
||||||
this.valuesPerLong = Long.SIZE / bitsPerEntry;
|
this.valuesPerLong = Long.SIZE / bitsPerEntry;
|
||||||
this.hasPalette = bitsPerEntry <= PALETTE_MAXIMUM_BITS;
|
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) {
|
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() {
|
public PaletteStorage copy() {
|
||||||
PaletteStorage paletteStorage = new PaletteStorage(bitsPerEntry, bitsIncrement);
|
PaletteStorage paletteStorage = new PaletteStorage(bitsPerEntry, bitsIncrement);
|
||||||
paletteStorage.sectionBlocks = sectionBlocks.clone();
|
paletteStorage.sectionBlocks = sectionBlocks.clone();
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package net.minestom.server.utils;
|
package net.minestom.server.utils;
|
||||||
|
|
||||||
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,8 +75,8 @@ public class Position {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the distance between 2 positions.
|
* Gets the distance between 2 positions.
|
||||||
* In cases where performance matters, {@link #getDistanceSquared(Position)} should be used
|
* In cases where performance matters, {@link #getDistanceSquared(Position)} should be used
|
||||||
* as it does not perform the expensive Math.sqrt method.
|
* as it does not perform the expensive Math.sqrt method.
|
||||||
*
|
*
|
||||||
* @param position the second position
|
* @param position the second position
|
||||||
* @return the distance between {@code this} and {@code position}
|
* @return the distance between {@code this} and {@code position}
|
||||||
@ -86,15 +89,15 @@ public class Position {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the square distance to another position.
|
* Gets the square distance to another position.
|
||||||
*
|
*
|
||||||
* @param position the second position
|
* @param position the second position
|
||||||
* @return the squared distance between {@code this} and {@code position}
|
* @return the squared distance between {@code this} and {@code position}
|
||||||
*/
|
*/
|
||||||
public float getDistanceSquared(Position position) {
|
public float getDistanceSquared(Position position) {
|
||||||
return MathUtils.square(getX() - position.getX()) +
|
return MathUtils.square(getX() - position.getX()) +
|
||||||
MathUtils.square(getY() - position.getY()) +
|
MathUtils.square(getY() - position.getY()) +
|
||||||
MathUtils.square(getZ() - position.getZ());
|
MathUtils.square(getZ() - position.getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a unit-vector pointing in the direction that this Location is
|
* 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
|
* @param position the position to compare
|
||||||
* @return true if the two positions are similar
|
* @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 &&
|
return Float.compare(position.x, x) == 0 &&
|
||||||
Float.compare(position.y, y) == 0 &&
|
Float.compare(position.y, y) == 0 &&
|
||||||
Float.compare(position.z, z) == 0;
|
Float.compare(position.z, z) == 0;
|
||||||
@ -221,11 +224,27 @@ public class Position {
|
|||||||
* @param position the position to compare
|
* @param position the position to compare
|
||||||
* @return true if the two positions have the same view
|
* @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 &&
|
return Float.compare(position.yaw, yaw) == 0 &&
|
||||||
Float.compare(position.pitch, pitch) == 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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(x, y, z, yaw, pitch);
|
return Objects.hash(x, y, z, yaw, pitch);
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package demo.commands;
|
package demo.commands;
|
||||||
|
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.command.CommandSender;
|
import net.minestom.server.command.CommandSender;
|
||||||
import net.minestom.server.command.builder.Arguments;
|
import net.minestom.server.command.builder.Arguments;
|
||||||
import net.minestom.server.command.builder.Command;
|
import net.minestom.server.command.builder.Command;
|
||||||
import net.minestom.server.command.builder.arguments.Argument;
|
import net.minestom.server.command.builder.arguments.Argument;
|
||||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
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 {
|
public class TestCommand extends Command {
|
||||||
|
|
||||||
@ -13,20 +14,20 @@ public class TestCommand extends Command {
|
|||||||
super("testcmd");
|
super("testcmd");
|
||||||
setDefaultExecutor(this::usage);
|
setDefaultExecutor(this::usage);
|
||||||
|
|
||||||
Argument test = ArgumentType.ItemStack("test");
|
Argument test = ArgumentType.DynamicWord("view_distance", SuggestionType.SUMMONABLE_ENTITIES);
|
||||||
|
|
||||||
test.setCallback((source, value, error) -> {
|
test.setCallback((source, value, error) -> {
|
||||||
System.out.println("ERROR " + error);
|
System.out.println("ERROR " + error);
|
||||||
});
|
});
|
||||||
|
|
||||||
setDefaultExecutor((source, args) -> {
|
setDefaultExecutor((source, args) -> {
|
||||||
System.gc();
|
//System.gc();
|
||||||
source.sendMessage("Explicit GC executed!");
|
//source.sendMessage("Explicit GC executed!");
|
||||||
|
MinecraftServer.setChunkViewDistance(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
addSyntax((source, args) -> {
|
addSyntax((source, args) -> {
|
||||||
Player player = (Player) source;
|
|
||||||
System.out.println("ARG 1");
|
|
||||||
}, test);
|
}, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user