mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-27 19:47:44 +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)
|
||||
[![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.
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -20,13 +20,23 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator<BasicEnum
|
||||
private final boolean linear;
|
||||
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 {
|
||||
this.linear = linear;
|
||||
generateTo(targetFolder);
|
||||
this(targetFolder, linear, false);
|
||||
}
|
||||
|
||||
protected BasicEnumGenerator(File targetFolder) throws IOException {
|
||||
this(targetFolder, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -59,17 +69,19 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator<BasicEnum
|
||||
ParameterSpec idParam = ParameterSpec.builder(TypeName.INT, "id").build();
|
||||
ParameterSpec[] signature = new ParameterSpec[]{idParam};
|
||||
if (linear) {
|
||||
final String ordinalIncrementCondition = incrementOrdinal ? " + 1" : "";
|
||||
final String ordinalIncrementIndex = incrementOrdinal ? " - 1" : "";
|
||||
generator.addStaticMethod("fromId", signature, className, code -> {
|
||||
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<BasicEnum
|
||||
ClassName registriesClass = ClassName.get(Registries.class);
|
||||
if (linear) {
|
||||
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 {
|
||||
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"));
|
||||
|
@ -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
|
||||
|
@ -99,11 +99,14 @@ public final class BenchmarkManager {
|
||||
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, ThreadResult> 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<String, ThreadResult> resultEntry : resultMap.entrySet()) {
|
||||
final String name = resultEntry.getKey();
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<String> {
|
||||
|
||||
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<String> {
|
||||
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.
|
||||
* <p>
|
||||
|
@ -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) {
|
||||
|
@ -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.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
|
||||
<T> void set(@NotNull String key, @Nullable T value, @Nullable Class<T> type);
|
||||
|
||||
/**
|
||||
* Serializes the data into an array of bytes.
|
||||
* <p>
|
||||
|
@ -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.
|
||||
* <p>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -678,40 +678,40 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* <p>
|
||||
* Be aware that because chunk operations are expensive,
|
||||
* 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) {
|
||||
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.
|
||||
* <p>
|
||||
* Does add the player to {@code instance}, remove all viewable entities and call {@link PlayerSpawnEvent}.
|
||||
* <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));
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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.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.
|
||||
* <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 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.
|
||||
* <p>
|
||||
* 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<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.
|
||||
*
|
||||
@ -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<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(getLightPacket());
|
||||
}
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
@ -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<ObjectEntity> objectEntities = new CopyOnWriteArraySet<>();
|
||||
protected final Set<ExperienceOrb> experienceOrbs = new CopyOnWriteArraySet<>();
|
||||
// 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
|
||||
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<Entity> 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<Entity> entities = getEntitiesInChunk(chunkIndex);
|
||||
entities.remove(entity);
|
||||
@ -973,9 +975,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
|
||||
@NotNull
|
||||
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;
|
||||
|
||||
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<Chunk> chunks = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectMap<Chunk> chunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
|
||||
// contains all the chunks to remove during the next instance tick
|
||||
protected final Set<Chunk> 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<Chunk> chunksCollection = chunks.values();
|
||||
this.chunkLoader.saveChunks(chunksCollection, callback);
|
||||
}
|
||||
Collection<Chunk> 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();
|
||||
|
||||
|
@ -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.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<Chunk, List<BlockData>> 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<BlockData> 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<Chunk, List<BlockData>> 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();
|
||||
|
||||
|
@ -22,7 +22,7 @@ import java.util.List;
|
||||
* use a {@link BlockBatch} instead otherwise.
|
||||
* Can be created using {@link Instance#createChunkBatch(Chunk)}.
|
||||
* <p>
|
||||
* 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<Data> 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<ChunkPopulator> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user