Merge branch 'master' into item-api

# Conflicts:
#	src/test/java/demo/PlayerInit.java
This commit is contained in:
TheMode 2021-04-12 00:44:47 +02:00
commit 1d5262caf2
16 changed files with 260 additions and 61 deletions

View File

@ -2,7 +2,7 @@ package net.minestom.server;
import net.minestom.server.advancements.AdvancementManager;
import net.minestom.server.adventure.bossbar.BossBarManager;
import net.minestom.server.benchmark.BenchmarkManager;
import net.minestom.server.monitoring.BenchmarkManager;
import net.minestom.server.command.CommandManager;
import net.minestom.server.data.DataManager;
import net.minestom.server.data.DataType;

View File

@ -4,6 +4,7 @@ import com.google.common.collect.Queues;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.thread.PerInstanceThreadProvider;
@ -13,10 +14,8 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.LongConsumer;
/**
@ -36,6 +35,7 @@ public final class UpdateManager {
private final Queue<LongConsumer> tickStartCallbacks = Queues.newConcurrentLinkedQueue();
private final Queue<LongConsumer> tickEndCallbacks = Queues.newConcurrentLinkedQueue();
private final List<Consumer<TickMonitor>> tickMonitors = new CopyOnWriteArrayList<>();
{
// DEFAULT THREAD PROVIDER
@ -81,7 +81,14 @@ public final class UpdateManager {
final long tickTime = System.nanoTime() - currentTime;
// Tick end callbacks
doTickCallback(tickEndCallbacks, tickTime / 1000000L);
doTickCallback(tickEndCallbacks, tickTime);
// Monitoring
if (!tickMonitors.isEmpty()) {
final double tickTimeMs = tickTime / 1e6D;
final TickMonitor tickMonitor = new TickMonitor(tickTimeMs);
this.tickMonitors.forEach(consumer -> consumer.accept(tickMonitor));
}
// Flush all waiting packets
AsyncUtils.runAsync(() -> connectionManager.getOnlinePlayers().stream()
@ -246,6 +253,14 @@ public final class UpdateManager {
this.tickEndCallbacks.remove(callback);
}
public void addTickMonitor(@NotNull Consumer<TickMonitor> consumer) {
this.tickMonitors.add(consumer);
}
public void removeTickMonitor(@NotNull Consumer<TickMonitor> consumer) {
this.tickMonitors.remove(consumer);
}
/**
* Stops the server loop.
*/

View File

@ -32,21 +32,22 @@ public class CollisionUtils {
@NotNull Vector velocityOut) {
// TODO handle collisions with nearby entities (should it be done here?)
final Instance instance = entity.getInstance();
final Chunk originChunk = entity.getChunk();
final Position currentPosition = entity.getPosition();
final BoundingBox boundingBox = entity.getBoundingBox();
Vector intermediaryPosition = new Vector();
final boolean yCollision = stepAxis(instance, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(),
final boolean yCollision = stepAxis(instance, originChunk, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(),
intermediaryPosition,
deltaPosition.getY() > 0 ? boundingBox.getTopFace() : boundingBox.getBottomFace()
);
final boolean xCollision = stepAxis(instance, intermediaryPosition, X_AXIS, deltaPosition.getX(),
final boolean xCollision = stepAxis(instance, originChunk, intermediaryPosition, X_AXIS, deltaPosition.getX(),
intermediaryPosition,
deltaPosition.getX() < 0 ? boundingBox.getLeftFace() : boundingBox.getRightFace()
);
final boolean zCollision = stepAxis(instance, intermediaryPosition, Z_AXIS, deltaPosition.getZ(),
final boolean zCollision = stepAxis(instance, originChunk, intermediaryPosition, Z_AXIS, deltaPosition.getZ(),
intermediaryPosition,
deltaPosition.getZ() > 0 ? boundingBox.getBackFace() : boundingBox.getFrontFace()
);
@ -80,7 +81,11 @@ public class CollisionUtils {
* @param corners the corners to check against
* @return true if a collision has been found
*/
private static boolean stepAxis(Instance instance, Vector startPosition, Vector axis, double stepAmount, Vector positionOut, Vector... corners) {
private static boolean stepAxis(Instance instance,
Chunk originChunk,
Vector startPosition, Vector axis,
double stepAmount, Vector positionOut,
Vector... corners) {
positionOut.copy(startPosition);
if (corners.length == 0)
return false; // avoid degeneracy in following computations
@ -99,7 +104,7 @@ public class CollisionUtils {
// used to determine if 'remainingLength' should be used
boolean collisionFound = false;
for (int i = 0; i < Math.abs(blockLength); i++) {
if (!stepOnce(instance, axis, sign, cornersCopy, cornerPositions)) {
if (!stepOnce(instance, originChunk, axis, sign, cornersCopy, cornerPositions)) {
collisionFound = true;
}
if (collisionFound) {
@ -111,7 +116,7 @@ public class CollisionUtils {
if (!collisionFound) {
Vector direction = new Vector();
direction.copy(axis);
collisionFound = !stepOnce(instance, direction, remainingLength, cornersCopy, cornerPositions);
collisionFound = !stepOnce(instance, originChunk, direction, remainingLength, cornersCopy, cornerPositions);
}
// find the corner which moved the least
@ -138,7 +143,9 @@ public class CollisionUtils {
* @param cornerPositions the corners, converted to BlockPosition (mutable)
* @return false if this method encountered a collision
*/
private static boolean stepOnce(Instance instance, Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) {
private static boolean stepOnce(Instance instance,
Chunk originChunk,
Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) {
final double sign = Math.signum(amount);
for (int cornerIndex = 0; cornerIndex < cornersCopy.length; cornerIndex++) {
Vector corner = cornersCopy[cornerIndex];
@ -148,10 +155,13 @@ public class CollisionUtils {
blockPos.setY((int) Math.floor(corner.getY()));
blockPos.setZ((int) Math.floor(corner.getZ()));
final Chunk chunk = instance.getChunkAt(blockPos);
if (!ChunkUtils.isLoaded(chunk)) {
// Collision at chunk border
return false;
Chunk chunk = originChunk;
if (!ChunkUtils.same(originChunk, blockPos.getX(), blockPos.getZ())) {
chunk = instance.getChunkAt(blockPos);
if (!ChunkUtils.isLoaded(chunk)) {
// Collision at chunk border
return false;
}
}
final short blockStateId = chunk.getBlockStateId(blockPos.getX(), blockPos.getY(), blockPos.getZ());

View File

@ -79,10 +79,10 @@ public class ArgumentEntity extends Argument<EntityFinder> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
byte mask = 0;
if (this.isOnlySingleEntity()) {
mask += 1;
mask |= 0x01;
}
if (this.isOnlyPlayers()) {
mask += 2;
mask |= 0x02;
}
packetWriter.writeByte(mask);
});

View File

@ -64,6 +64,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
protected Instance instance;
protected Chunk currentChunk;
protected final Position position;
protected double lastX, lastY, lastZ;
protected double cacheX, cacheY, cacheZ; // Used to synchronize with #getPosition
@ -462,7 +463,6 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
}
// Check if the entity chunk is loaded
final Chunk currentChunk = getChunk();
if (!ChunkUtils.isLoaded(currentChunk)) {
// No update for entities in unloaded chunk
return;
@ -566,11 +566,14 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
// World border collision
final Position finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition);
final Chunk finalChunk = instance.getChunkAt(finalVelocityPosition);
Chunk finalChunk = currentChunk;
if (!ChunkUtils.same(position, finalVelocityPosition)) {
finalChunk = instance.getChunkAt(finalVelocityPosition);
// Entity shouldn't be updated when moving in an unloaded chunk
if (!ChunkUtils.isLoaded(finalChunk)) {
return;
// Entity shouldn't be updated when moving in an unloaded chunk
if (!ChunkUtils.isLoaded(finalChunk)) {
return;
}
}
// Apply the position if changed
@ -637,9 +640,12 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
final Chunk chunk = instance.getChunkAt(x, z);
if (!ChunkUtils.isLoaded(chunk))
continue;
Chunk chunk = currentChunk;
if (!ChunkUtils.same(currentChunk, x, z)) {
chunk = instance.getChunkAt(x, z);
if (!ChunkUtils.isLoaded(chunk))
continue;
}
final CustomBlock customBlock = chunk.getCustomBlock(x, y, z);
if (customBlock != null) {
@ -816,9 +822,8 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
*
* @return the entity chunk, can be null even if unlikely
*/
@Nullable
public Chunk getChunk() {
return instance.getChunkAt(position.getX(), position.getZ());
public @Nullable Chunk getChunk() {
return currentChunk;
}
/**
@ -826,8 +831,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
*
* @return the entity instance, can be null if the entity doesn't have an instance yet
*/
@Nullable
public Instance getInstance() {
public @Nullable Instance getInstance() {
return instance;
}
@ -855,6 +859,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
this.isActive = true;
this.instance = instance;
this.currentChunk = instance.getChunkAt(position.getX(), position.getZ());
instance.UNSAFE_addEntity(this);
spawn();
EntitySpawnEvent entitySpawnEvent = new EntitySpawnEvent(this, instance);
@ -1309,20 +1314,26 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
final Instance instance = getInstance();
if (instance != null) {
final Chunk lastChunk = instance.getChunkAt(lastX, lastZ);
final Chunk newChunk = instance.getChunkAt(x, z);
final int lastChunkX = currentChunk.getChunkX();
final int lastChunkZ = currentChunk.getChunkZ();
Check.notNull(lastChunk, "The entity {0} was in an unloaded chunk at {1};{2}", getEntityId(), lastX, lastZ);
Check.notNull(newChunk, "The entity {0} tried to move in an unloaded chunk at {1};{2}", getEntityId(), x, z);
final int newChunkX = ChunkUtils.getChunkCoordinate(x);
final int newChunkZ = ChunkUtils.getChunkCoordinate(z);
if (lastChunk != newChunk) {
instance.UNSAFE_switchEntityChunk(this, lastChunk, newChunk);
if (lastChunkX != newChunkX || lastChunkZ != newChunkZ) {
// Entity moved in a new chunk
final Chunk newChunk = instance.getChunk(newChunkX, newChunkZ);
Check.notNull(newChunk, "The entity {0} tried to move in an unloaded chunk at {1};{2}", getEntityId(), x, z);
instance.UNSAFE_switchEntityChunk(this, currentChunk, newChunk);
if (this instanceof Player) {
// Refresh player view
final Player player = (Player) this;
player.refreshVisibleChunks(newChunk);
player.refreshVisibleEntities(newChunk);
}
this.currentChunk = newChunk;
}
}

View File

@ -664,17 +664,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Load all the required chunks
final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, getChunkRange());
final ChunkCallback eachCallback = chunk -> {
if (chunk != null) {
final int chunkX = ChunkUtils.getChunkCoordinate(spawnPosition.getX());
final int chunkZ = ChunkUtils.getChunkCoordinate(spawnPosition.getZ());
if (chunk.getChunkX() == chunkX &&
chunk.getChunkZ() == chunkZ) {
updateViewPosition(chunkX, chunkZ);
}
}
};
final ChunkCallback endCallback = chunk -> {
// This is the last chunk to be loaded , spawn player
spawnPlayer(instance, spawnPosition, firstSpawn, true, dimensionChange);
@ -683,7 +672,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
// Chunk 0;0 always needs to be loaded
instance.loadChunk(0, 0, chunk ->
// Load all the required chunks
ChunkUtils.optionalLoadAll(instance, visibleChunks, eachCallback, endCallback));
ChunkUtils.optionalLoadAll(instance, visibleChunks, null, endCallback));
} else {
// The player already has the good version of all the chunks.

View File

@ -1,4 +1,4 @@
package net.minestom.server.benchmark;
package net.minestom.server.monitoring;
import it.unimi.dsi.fastutil.longs.Long2LongMap;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;

View File

@ -1,4 +1,4 @@
package net.minestom.server.benchmark;
package net.minestom.server.monitoring;
public class ThreadResult {

View File

@ -0,0 +1,14 @@
package net.minestom.server.monitoring;
public class TickMonitor {
private final double tickTime;
public TickMonitor(double tickTime) {
this.tickTime = tickTime;
}
public double getTickTime() {
return tickTime;
}
}

View File

@ -49,7 +49,7 @@ public class ClientPlayPacketsHandler extends ClientPacketsHandler {
register(0x27, ClientUpdateCommandBlockMinecartPacket::new);
register(0x28, ClientCreativeInventoryActionPacket::new);
//Update Jigsaw Block??
//Update Structure Block??
register(0x2A, ClientUpdateStructureBlockPacket::new);
register(0x2B, ClientUpdateSignPacket::new);
register(0x2C, ClientAnimationPacket::new);
register(0x2D, ClientSpectatePacket::new);

View File

@ -48,7 +48,7 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
// Velocity
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
if (data != null) {
if (data != null && data.length > 0) {
BinaryReader reader = new BinaryReader(data);
success = VelocityProxy.checkIntegrity(reader);
if (success) {
@ -103,7 +103,7 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
writer.writeVarInt(messageId);
writer.writeBoolean(successful);
if(successful) {
if (successful) {
writer.writeBytes(data);
}
}

View File

@ -0,0 +1,111 @@
package net.minestom.server.network.packet.client.play;
import net.minestom.server.network.packet.client.ClientPlayPacket;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Rotation;
import net.minestom.server.utils.binary.BinaryReader;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
public class ClientUpdateStructureBlockPacket extends ClientPlayPacket {
// Flag values
public static final byte IGNORE_ENTITIES = 0x1;
public static final byte SHOW_AIR = 0x2;
/**
* Requires the player to be in creative and have a permission level higher than 2.
*/
public static final byte SHOW_BOUNDING_BOX = 0x4;
public BlockPosition location = new BlockPosition(0, 0, 0);
public Action action = Action.UPDATE_DATA;
public Mode mode = Mode.DATA;
public String name = "";
public BlockPosition offset = new BlockPosition(0, 1, 0);
public BlockPosition size = new BlockPosition(1, 1, 1);
public Mirror mirror = Mirror.NONE;
public Rotation rotation = Rotation.NONE;
public String metadata = "";
public float integrity = 1.0f;
public long seed = 0;
public byte flags = 0x0;
@Override
public void write(@NotNull BinaryWriter writer) {
writer.writeBlockPosition(location);
writer.writeVarInt(action.ordinal());
writer.writeVarInt(mode.ordinal());
writer.writeSizedString(name);
writer.writeByte((byte) offset.getX());
writer.writeByte((byte) offset.getY());
writer.writeByte((byte) offset.getZ());
writer.writeByte((byte) size.getX());
writer.writeByte((byte) size.getY());
writer.writeByte((byte) size.getZ());
writer.writeVarInt(mirror.ordinal());
writer.writeVarInt(toRestrictedRotation(rotation));
writer.writeSizedString(metadata);
writer.writeFloat(integrity);
writer.writeVarLong(seed);
writer.writeByte(flags);
}
@Override
public void read(@NotNull BinaryReader reader) {
location = reader.readBlockPosition();
action = Action.values()[reader.readVarInt()];
mode = Mode.values()[reader.readVarInt()];
name = reader.readSizedString(Short.MAX_VALUE);
offset = new BlockPosition(
reader.readByte(),
reader.readByte(),
reader.readByte()
);
size = new BlockPosition(
reader.readByte(),
reader.readByte(),
reader.readByte()
);
mirror = Mirror.values()[reader.readVarInt()];
rotation = fromRestrictedRotation(reader.readVarInt());
metadata = reader.readSizedString(Short.MAX_VALUE);
integrity = reader.readFloat();
seed = reader.readVarLong();
flags = reader.readByte();
}
/**
* Update action, <code>UPDATE_DATA</code> indicates nothing special.
*/
public enum Action {
UPDATE_DATA, SAVE, LOAD, DETECT_SIZE
}
public enum Mode {
SAVE, LOAD, CORNER, DATA
}
public enum Mirror {
NONE, LEFT_RIGHT, FRONT_BACK
}
private int toRestrictedRotation(Rotation rotation) {
switch (rotation) {
case NONE: return 0;
case CLOCKWISE: return 1;
case FLIPPED: return 2;
case COUNTER_CLOCKWISE: return 3;
default: throw new IllegalArgumentException("ClientUpdateStructurePacket#rotation must be a valid 90-degree rotation.");
}
}
private Rotation fromRestrictedRotation(int rotation) {
switch (rotation) {
case 0: return Rotation.NONE;
case 1: return Rotation.CLOCKWISE;
case 2: return Rotation.FLIPPED;
case 3: return Rotation.COUNTER_CLOCKWISE;
default: throw new IllegalArgumentException("ClientUpdateStructurePacket#rotation must be a valid 90-degree rotation.");
}
}
}

View File

@ -22,13 +22,13 @@ public class PlayerAbilitiesPacket implements ServerPacket {
public void write(@NotNull BinaryWriter writer) {
byte flags = 0;
if (invulnerable)
flags += 1;
flags |= 0x01;
if (flying)
flags += 2;
flags |= 0x02;
if (allowFlying)
flags += 4;
flags |= 0x04;
if (instantBreak)
flags += 8;
flags |= 0x08;
writer.writeByte(flags);
writer.writeFloat(flyingSpeed);

View File

@ -4,8 +4,11 @@ import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.util.Natives;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.minestom.server.MinecraftServer;
import net.minestom.server.adventure.AdventureSerializer;
import net.minestom.server.adventure.audience.PacketGroupingAudience;
import net.minestom.server.entity.Player;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.network.netty.packet.FramedPacket;
@ -18,6 +21,7 @@ import net.minestom.server.utils.callback.validator.PlayerValidator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.util.Collection;
import java.util.zip.DataFormatException;
@ -34,6 +38,39 @@ public final class PacketUtils {
}
/**
* Sends a packet to an audience. This method performs the following steps in the
* following order:
* <ol>
* <li>If {@code audience} is a {@link Player}, send the packet to them.</li>
* <li>Otherwise, if {@code audience} is a {@link PacketGroupingAudience}, call
* {@link #sendGroupedPacket(Collection, ServerPacket)} on the players that the
* grouping audience contains.</li>
* <li>Otherwise, if {@code audience} is a {@link ForwardingAudience.Single},
* call this method on the single audience inside the forwarding audience.</li>
* <li>Otherwise, if {@code audience} is a {@link ForwardingAudience}, call this
* method for each audience member of the forwarding audience.</li>
* <li>Otherwise, do nothing.</li>
* </ol>
*
* @param audience the audience
* @param packet the packet
*/
@SuppressWarnings("OverrideOnly") // we need to access the audiences inside ForwardingAudience
public static void sendPacket(@NotNull Audience audience, @NotNull ServerPacket packet) {
if (audience instanceof Player) {
((Player) audience).getPlayerConnection().sendPacket(packet);
} else if (audience instanceof PacketGroupingAudience) {
PacketUtils.sendGroupedPacket(((PacketGroupingAudience) audience).getPlayers(), packet);
} else if (audience instanceof ForwardingAudience.Single) {
PacketUtils.sendPacket(((ForwardingAudience.Single) audience).audience(), packet);
} else if (audience instanceof ForwardingAudience) {
for (Audience member : ((ForwardingAudience) audience).audiences()) {
PacketUtils.sendPacket(member, packet);
}
}
}
/**
* Sends a {@link ServerPacket} to multiple players.
* <p>

View File

@ -84,6 +84,20 @@ public final class ChunkUtils {
return isLoaded(chunk);
}
public static boolean same(@NotNull Chunk chunk, double x, double z) {
final int chunkX = getChunkCoordinate(x);
final int chunkZ = getChunkCoordinate(z);
return chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ;
}
public static boolean same(@NotNull Position pos1, @NotNull Position pos2) {
final int x1 = getChunkCoordinate(pos1.getX());
final int z1 = getChunkCoordinate(pos1.getZ());
final int x2 = getChunkCoordinate(pos2.getX());
final int z2 = getChunkCoordinate(pos2.getZ());
return x1 == x2 && z1 == z2;
}
/**
* @param xz the instance coordinate to convert
* @return the chunk X or Z based on the argument

View File

@ -22,8 +22,6 @@ public class Main {
public static void main(String[] args) {
MinecraftServer minecraftServer = MinecraftServer.init();
// MinecraftServer.setShouldProcessNettyErrors(true);
BlockManager blockManager = MinecraftServer.getBlockManager();
blockManager.registerCustomBlock(new CustomBlockSample());
blockManager.registerCustomBlock(new UpdatableBlockDemo());