mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-30 21:17:53 +01:00
Merge branch 'master' into viewable-broadcast
# Conflicts: # src/main/java/net/minestom/server/network/player/PlayerSocketConnection.java
This commit is contained in:
commit
4b2bda15f5
12
.github/workflows/trigger-jitpack-build.yml
vendored
Normal file
12
.github/workflows/trigger-jitpack-build.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
name: Trigger Jitpack Build
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Jitpack Build
|
||||
run: curl "https://jitpack.io/com/github/Minestom/Minestom/${GITHUB_SHA:0:10}/build.log"
|
@ -4,6 +4,7 @@ import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
import net.minestom.server.timer.Task;
|
||||
import net.minestom.server.utils.thread.ThreadBindingExecutor;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
@ -27,6 +28,8 @@ public abstract class GLFWCapableBuffer {
|
||||
private final ByteBuffer colorsBuffer;
|
||||
private boolean onlyMapColors;
|
||||
|
||||
private static ThreadBindingExecutor threadBindingPool;
|
||||
|
||||
protected GLFWCapableBuffer(int width, int height) {
|
||||
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||||
}
|
||||
@ -60,6 +63,12 @@ public abstract class GLFWCapableBuffer {
|
||||
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(GLFWCapableBuffer.class) {
|
||||
if(threadBindingPool == null) {
|
||||
threadBindingPool = new ThreadBindingExecutor(MinecraftServer.THREAD_COUNT_SCHEDULER, MinecraftServer.THREAD_NAME_SCHEDULER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GLFWCapableBuffer unbindContextFromThread() {
|
||||
@ -80,14 +89,17 @@ public abstract class GLFWCapableBuffer {
|
||||
return MinecraftServer.getSchedulerManager()
|
||||
.buildTask(new Runnable() {
|
||||
private boolean first = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
private final Runnable subAction = () -> {
|
||||
if(first) {
|
||||
changeRenderingThreadToCurrent();
|
||||
first = false;
|
||||
}
|
||||
render(rendering);
|
||||
};
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
threadBindingPool.execute(subAction);
|
||||
}
|
||||
})
|
||||
.repeat(period)
|
||||
|
@ -1,10 +1,8 @@
|
||||
package net.minestom.server.attribute;
|
||||
|
||||
import net.minestom.server.utils.UniqueIdUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Represent an attribute modifier.
|
||||
@ -24,7 +22,7 @@ public class AttributeModifier {
|
||||
* @param operation the operation to apply this modifier with
|
||||
*/
|
||||
public AttributeModifier(@NotNull String name, float amount, @NotNull AttributeOperation operation) {
|
||||
this(UniqueIdUtils.createRandomUUID(ThreadLocalRandom.current()), name, amount, operation);
|
||||
this(UUID.randomUUID(), name, amount, operation);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,9 @@ import net.minestom.server.entity.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@ -18,13 +20,42 @@ public class BoundingBox {
|
||||
private final Entity entity;
|
||||
private final double x, y, z;
|
||||
|
||||
private volatile Pos lastPosition;
|
||||
private List<Vec> bottomFace;
|
||||
private List<Vec> topFace;
|
||||
private List<Vec> leftFace;
|
||||
private List<Vec> rightFace;
|
||||
private List<Vec> frontFace;
|
||||
private List<Vec> backFace;
|
||||
private final CachedFace bottomFace = new CachedFace(() -> List.of(
|
||||
new Vec(getMinX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMinY(), getMaxZ())
|
||||
));
|
||||
private final CachedFace topFace = new CachedFace(() -> List.of(
|
||||
new Vec(getMinX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMaxZ())
|
||||
));
|
||||
private final CachedFace leftFace = new CachedFace(() -> List.of(
|
||||
new Vec(getMinX(), getMinY(), getMinZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMinY(), getMaxZ())
|
||||
));
|
||||
private final CachedFace rightFace = new CachedFace(() -> List.of(
|
||||
new Vec(getMaxX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMaxZ())
|
||||
));
|
||||
private final CachedFace frontFace = new CachedFace(() -> List.of(
|
||||
new Vec(getMinX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMinZ())
|
||||
));
|
||||
private final CachedFace backFace = new CachedFace(() -> List.of(
|
||||
new Vec(getMinX(), getMinY(), getMaxZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMaxZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMaxZ())
|
||||
));
|
||||
|
||||
/**
|
||||
* Creates a {@link BoundingBox} linked to an {@link Entity} and with a specific size.
|
||||
@ -320,12 +351,7 @@ public class BoundingBox {
|
||||
* @return the points at the bottom of the {@link BoundingBox}
|
||||
*/
|
||||
public @NotNull List<Vec> getBottomFace() {
|
||||
this.bottomFace = get(bottomFace, () ->
|
||||
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMinY(), getMaxZ())));
|
||||
return bottomFace;
|
||||
return bottomFace.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -334,12 +360,7 @@ public class BoundingBox {
|
||||
* @return the points at the top of the {@link BoundingBox}
|
||||
*/
|
||||
public @NotNull List<Vec> getTopFace() {
|
||||
this.topFace = get(topFace, () ->
|
||||
List.of(new Vec(getMinX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMaxZ())));
|
||||
return topFace;
|
||||
return topFace.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -348,12 +369,7 @@ public class BoundingBox {
|
||||
* @return the points on the left face of the {@link BoundingBox}
|
||||
*/
|
||||
public @NotNull List<Vec> getLeftFace() {
|
||||
this.leftFace = get(leftFace, () ->
|
||||
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMinY(), getMaxZ())));
|
||||
return leftFace;
|
||||
return leftFace.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -362,12 +378,7 @@ public class BoundingBox {
|
||||
* @return the points on the right face of the {@link BoundingBox}
|
||||
*/
|
||||
public @NotNull List<Vec> getRightFace() {
|
||||
this.rightFace = get(rightFace, () ->
|
||||
List.of(new Vec(getMaxX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMaxZ())));
|
||||
return rightFace;
|
||||
return rightFace.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -376,12 +387,7 @@ public class BoundingBox {
|
||||
* @return the points at the front of the {@link BoundingBox}
|
||||
*/
|
||||
public @NotNull List<Vec> getFrontFace() {
|
||||
this.frontFace = get(frontFace, () ->
|
||||
List.of(new Vec(getMinX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMinZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMinZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMinZ())));
|
||||
return frontFace;
|
||||
return frontFace.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -390,12 +396,7 @@ public class BoundingBox {
|
||||
* @return the points at the back of the {@link BoundingBox}
|
||||
*/
|
||||
public @NotNull List<Vec> getBackFace() {
|
||||
this.backFace = get(backFace, () -> List.of(
|
||||
new Vec(getMinX(), getMinY(), getMaxZ()),
|
||||
new Vec(getMaxX(), getMinY(), getMaxZ()),
|
||||
new Vec(getMaxX(), getMaxY(), getMaxZ()),
|
||||
new Vec(getMinX(), getMaxY(), getMaxZ())));
|
||||
return backFace;
|
||||
return backFace.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -410,18 +411,42 @@ public class BoundingBox {
|
||||
return result;
|
||||
}
|
||||
|
||||
private @NotNull List<Vec> get(@Nullable List<Vec> face, Supplier<? extends List<Vec>> vecSupplier) {
|
||||
final var lastPos = this.lastPosition;
|
||||
final var entityPos = entity.getPosition();
|
||||
if (face != null && lastPos != null && lastPos.samePoint(entityPos)) {
|
||||
return face;
|
||||
}
|
||||
this.lastPosition = entityPos;
|
||||
return vecSupplier.get();
|
||||
}
|
||||
|
||||
private enum Axis {
|
||||
X, Y, Z
|
||||
}
|
||||
|
||||
private class CachedFace {
|
||||
|
||||
private final AtomicReference<@Nullable PositionedPoints> reference = new AtomicReference<>(null);
|
||||
private final Supplier<@NotNull List<Vec>> faceProducer;
|
||||
|
||||
private CachedFace(Supplier<@NotNull List<Vec>> faceProducer) {
|
||||
this.faceProducer = faceProducer;
|
||||
}
|
||||
|
||||
@NotNull List<Vec> get() {
|
||||
//noinspection ConstantConditions
|
||||
return reference.updateAndGet(value -> {
|
||||
Pos entityPosition = entity.getPosition();
|
||||
if (value == null || !value.lastPosition.samePoint(entityPosition)) {
|
||||
return new PositionedPoints(entityPosition, faceProducer.get());
|
||||
}
|
||||
return value;
|
||||
}).points;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class PositionedPoints {
|
||||
|
||||
private final @NotNull Pos lastPosition;
|
||||
private final @NotNull List<Vec> points;
|
||||
|
||||
private PositionedPoints(@NotNull Pos lastPosition, @NotNull List<Vec> points) {
|
||||
this.lastPosition = lastPosition;
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ public class ArgumentEntityType extends ArgumentRegistry<EntityType> {
|
||||
@Override
|
||||
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, true);
|
||||
argumentNode.parser = "minecraft:entity_summon";
|
||||
argumentNode.parser = "minecraft:resource_location";
|
||||
argumentNode.suggestionsType = SuggestionType.SUMMONABLE_ENTITIES.getIdentifier();
|
||||
|
||||
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
|
||||
|
@ -450,7 +450,7 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
update(time);
|
||||
|
||||
ticks++;
|
||||
EventDispatcher.call(new EntityTickEvent(this), GlobalHandles.ENTITY_TICK);
|
||||
GlobalHandles.ENTITY_TICK.call(new EntityTickEvent(this));
|
||||
|
||||
// remove expired effects
|
||||
effectTick(time);
|
||||
@ -883,16 +883,15 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
public void addPassenger(@NotNull Entity entity) {
|
||||
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
|
||||
Check.stateCondition(entity == getVehicle(), "Cannot add the entity vehicle as a passenger");
|
||||
|
||||
final Entity vehicle = entity.getVehicle();
|
||||
if (vehicle != null) {
|
||||
vehicle.removePassenger(entity);
|
||||
}
|
||||
|
||||
this.passengers.add(entity);
|
||||
entity.vehicle = this;
|
||||
|
||||
sendPacketToViewersAndSelf(getPassengersPacket());
|
||||
entity.refreshPosition(position);
|
||||
entity.synchronizePosition(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -904,11 +903,14 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
*/
|
||||
public void removePassenger(@NotNull Entity entity) {
|
||||
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
|
||||
|
||||
if (!passengers.remove(entity))
|
||||
return;
|
||||
if (!passengers.remove(entity)) return;
|
||||
entity.vehicle = null;
|
||||
sendPacketToViewersAndSelf(getPassengersPacket());
|
||||
if (entity instanceof Player) {
|
||||
Player player = (Player) entity;
|
||||
player.getPlayerConnection().sendPacket(new PlayerPositionAndLookPacket(player.getPosition(),
|
||||
(byte) 0x00, player.getNextTeleportId(), true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,7 @@ import net.minestom.server.entity.metadata.monster.skeleton.SkeletonMeta;
|
||||
import net.minestom.server.entity.metadata.monster.skeleton.StrayMeta;
|
||||
import net.minestom.server.entity.metadata.monster.skeleton.WitherSkeletonMeta;
|
||||
import net.minestom.server.entity.metadata.monster.zombie.DrownedMeta;
|
||||
import net.minestom.server.entity.metadata.monster.zombie.HuskMeta;
|
||||
import net.minestom.server.entity.metadata.monster.zombie.ZombieMeta;
|
||||
import net.minestom.server.entity.metadata.monster.zombie.ZombieVillagerMeta;
|
||||
import net.minestom.server.entity.metadata.monster.zombie.ZombifiedPiglinMeta;
|
||||
@ -119,6 +120,7 @@ final class EntityTypeImpl implements EntityType {
|
||||
supplier.put("minecraft:guardian", GuardianMeta::new);
|
||||
supplier.put("minecraft:hoglin", HoglinMeta::new);
|
||||
supplier.put("minecraft:horse", HorseMeta::new);
|
||||
supplier.put("minecraft:husk", HuskMeta::new);
|
||||
supplier.put("minecraft:illusioner", IllusionerMeta::new);
|
||||
supplier.put("minecraft:iron_golem", IronGolemMeta::new);
|
||||
supplier.put("minecraft:item", ItemEntityMeta::new);
|
||||
|
@ -351,7 +351,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
}
|
||||
|
||||
// Tick event
|
||||
EventDispatcher.call(new PlayerTickEvent(this), GlobalHandles.PLAYER_TICK);
|
||||
GlobalHandles.PLAYER_TICK.call(new PlayerTickEvent(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -418,6 +418,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
getPlayerConnection().sendPacket(respawnPacket);
|
||||
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this);
|
||||
EventDispatcher.call(respawnEvent);
|
||||
triggerStatus((byte) (24 + permissionLevel)); // Set permission level
|
||||
refreshIsDead(false);
|
||||
|
||||
// Runnable called when teleportation is successful (after loading and sending necessary chunk)
|
||||
@ -1195,7 +1196,11 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
// Cannot load chunk (auto load is not enabled)
|
||||
return;
|
||||
}
|
||||
chunk.addViewer(this);
|
||||
try {
|
||||
chunk.addViewer(this);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1548,6 +1553,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
playerConnection.sendPacket(new UpdateViewPositionPacket(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
public int getNextTeleportId() {
|
||||
return teleportId.incrementAndGet();
|
||||
}
|
||||
|
||||
public int getLastSentTeleportId() {
|
||||
return teleportId.get();
|
||||
}
|
||||
@ -1567,7 +1576,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
@ApiStatus.Internal
|
||||
protected void synchronizePosition(boolean includeSelf) {
|
||||
if (includeSelf) {
|
||||
playerConnection.sendPacket(new PlayerPositionAndLookPacket(position, (byte) 0x00, teleportId.incrementAndGet(), false));
|
||||
playerConnection.sendPacket(new PlayerPositionAndLookPacket(position, (byte) 0x00, getNextTeleportId(), false));
|
||||
}
|
||||
super.synchronizePosition(includeSelf);
|
||||
}
|
||||
|
@ -10,10 +10,6 @@ public final class EventDispatcher {
|
||||
MinecraftServer.getGlobalEventHandler().call(event);
|
||||
}
|
||||
|
||||
public static <E extends Event> void call(@NotNull E event, @NotNull ListenerHandle<E> handle) {
|
||||
MinecraftServer.getGlobalEventHandler().call(event, handle);
|
||||
}
|
||||
|
||||
public static <E extends Event> ListenerHandle<E> getHandle(@NotNull Class<E> handleType) {
|
||||
return MinecraftServer.getGlobalEventHandler().getHandle(handleType);
|
||||
}
|
||||
|
@ -188,22 +188,9 @@ public interface EventNode<T extends Event> {
|
||||
*/
|
||||
default void call(@NotNull T event) {
|
||||
//noinspection unchecked
|
||||
call(event, getHandle((Class<T>) event.getClass()));
|
||||
getHandle((Class<T>) event.getClass()).call(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls an event starting from this node.
|
||||
* <p>
|
||||
* The event handle can be retrieved using {@link #getHandle(Class)}
|
||||
* and is useful to avoid map lookups for high-frequency events.
|
||||
*
|
||||
* @param event the event to call
|
||||
* @param handle the event handle linked to this node
|
||||
* @param <E> the event type
|
||||
* @throws IllegalArgumentException if {@code handle}'s owner is not {@code this}
|
||||
*/
|
||||
<E extends T> void call(@NotNull E event, @NotNull ListenerHandle<E> handle);
|
||||
|
||||
/**
|
||||
* Gets the handle of an event type.
|
||||
*
|
||||
@ -211,21 +198,9 @@ public interface EventNode<T extends Event> {
|
||||
* @param <E> the event type
|
||||
* @return the handle linked to {@code handleType}
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
<E extends T> @NotNull ListenerHandle<E> getHandle(@NotNull Class<E> handleType);
|
||||
|
||||
/**
|
||||
* Gets if any listener has been registered for the given handle.
|
||||
* May trigger an update if the cached data is not correct.
|
||||
* <p>
|
||||
* Useful if you are able to avoid expensive computation in the case where
|
||||
* the event is unused. Be aware that {@link #call(Event, ListenerHandle)}
|
||||
* has similar optimization built-in.
|
||||
*
|
||||
* @param handle the listener handle
|
||||
* @return true if the event has 1 or more listeners
|
||||
*/
|
||||
boolean hasListener(@NotNull ListenerHandle<? extends T> handle);
|
||||
|
||||
/**
|
||||
* Execute a cancellable event with a callback to execute if the event is successful.
|
||||
* Event conditions and propagation is the same as {@link #call(Event)}.
|
||||
|
@ -39,18 +39,6 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||
this.eventType = filter.eventType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends T> void call(@NotNull E event, @NotNull ListenerHandle<E> handle) {
|
||||
final Handle<T> castedHandle = (Handle<T>) handle;
|
||||
Check.argCondition(castedHandle.node != this, "Invalid handle owner");
|
||||
if (!castedHandle.updated) castedHandle.update();
|
||||
final List<Consumer<T>> listeners = castedHandle.listeners;
|
||||
if (listeners.isEmpty()) return;
|
||||
for (Consumer<T> listener : listeners) {
|
||||
listener.accept(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends T> @NotNull ListenerHandle<E> getHandle(@NotNull Class<E> handleType) {
|
||||
//noinspection unchecked
|
||||
@ -58,13 +46,6 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||
aClass -> new Handle<>(this, (Class<T>) aClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasListener(@NotNull ListenerHandle<? extends T> handle) {
|
||||
final Handle<T> castedHandle = (Handle<T>) handle;
|
||||
if (!castedHandle.updated) castedHandle.update();
|
||||
return !castedHandle.listeners.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends T> @NotNull List<EventNode<E>> findChildren(@NotNull String name, Class<E> eventType) {
|
||||
synchronized (GLOBAL_CHILD_LOCK) {
|
||||
@ -239,6 +220,10 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||
for (Class<? extends T> eventType : listenerMap.keySet()) {
|
||||
node.invalidateEvent(eventType);
|
||||
}
|
||||
// TODO bindings?
|
||||
for (EventNodeImpl<T> child : children) {
|
||||
child.invalidateEventsFor(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateEvent(Class<? extends T> eventClass) {
|
||||
@ -277,7 +262,7 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||
private static final class Handle<E extends Event> implements ListenerHandle<E> {
|
||||
private final EventNodeImpl<E> node;
|
||||
private final Class<E> eventType;
|
||||
private final List<Consumer<E>> listeners = new CopyOnWriteArrayList<>();
|
||||
private Consumer<E> listener = null;
|
||||
private volatile boolean updated;
|
||||
|
||||
Handle(EventNodeImpl<E> node, Class<E> eventType) {
|
||||
@ -285,145 +270,170 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
void update() {
|
||||
synchronized (GLOBAL_CHILD_LOCK) {
|
||||
this.listeners.clear();
|
||||
recursiveUpdate(node);
|
||||
this.updated = true;
|
||||
@Override
|
||||
public void call(@NotNull E event) {
|
||||
final Consumer<E> listener = updatedListener();
|
||||
if (listener == null) return;
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void recursiveUpdate(EventNodeImpl<E> targetNode) {
|
||||
@Override
|
||||
public boolean hasListener() {
|
||||
return updatedListener() != null;
|
||||
}
|
||||
|
||||
@Nullable Consumer<E> updatedListener() {
|
||||
if (updated) return listener;
|
||||
synchronized (GLOBAL_CHILD_LOCK) {
|
||||
if (updated) return listener;
|
||||
final Consumer<E> listener = createConsumer();
|
||||
this.listener = listener;
|
||||
this.updated = true;
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Consumer<E> createConsumer() {
|
||||
// Standalone listeners
|
||||
List<Consumer<E>> listeners = new ArrayList<>();
|
||||
forTargetEvents(eventType, type -> {
|
||||
final ListenerEntry<E> entry = targetNode.listenerMap.get(type);
|
||||
if (entry != null) appendEntries(entry, targetNode);
|
||||
final ListenerEntry<E> entry = node.listenerMap.get(type);
|
||||
if (entry != null) {
|
||||
final Consumer<E> result = listenersConsumer(entry);
|
||||
if (result != null) listeners.add(result);
|
||||
}
|
||||
});
|
||||
// Mapped nodes
|
||||
handleMappedNode(targetNode);
|
||||
// Add children
|
||||
final Set<EventNodeImpl<E>> children = targetNode.children;
|
||||
if (children.isEmpty()) return;
|
||||
children.stream()
|
||||
final Consumer<E>[] listenersArray = listeners.toArray(Consumer[]::new);
|
||||
// Mapped
|
||||
final Consumer<E> mappedListener = mappedConsumer();
|
||||
// Children
|
||||
final Consumer<E>[] childrenListeners = node.children.stream()
|
||||
.filter(child -> child.eventType.isAssignableFrom(eventType)) // Invalid event type
|
||||
.sorted(Comparator.comparing(EventNode::getPriority))
|
||||
.forEach(this::recursiveUpdate);
|
||||
.map(child -> ((Handle<E>) child.getHandle(eventType)).updatedListener())
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(Consumer[]::new);
|
||||
// Empty check
|
||||
final BiPredicate<E, Object> predicate = node.predicate;
|
||||
final EventFilter<E, ?> filter = node.filter;
|
||||
final boolean hasPredicate = predicate != null;
|
||||
final boolean hasListeners = listenersArray.length > 0;
|
||||
final boolean hasMap = mappedListener != null;
|
||||
final boolean hasChildren = childrenListeners.length > 0;
|
||||
if (listenersArray.length == 0 && mappedListener == null && childrenListeners.length == 0) {
|
||||
// No listener
|
||||
return null;
|
||||
}
|
||||
return e -> {
|
||||
// Filtering
|
||||
if (hasPredicate) {
|
||||
final Object value = filter.getHandler(e);
|
||||
if (!predicate.test(e, value)) return;
|
||||
}
|
||||
// Normal listeners
|
||||
if (hasListeners) {
|
||||
for (Consumer<E> listener : listenersArray) {
|
||||
listener.accept(e);
|
||||
}
|
||||
}
|
||||
// Mapped nodes
|
||||
if (hasMap) mappedListener.accept(e);
|
||||
// Children
|
||||
if (hasChildren) {
|
||||
for (Consumer<E> childHandle : childrenListeners) {
|
||||
childHandle.accept(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the node's listeners from {@link EventNode#map(EventNode, Object)}.
|
||||
* Create a consumer calling all listeners from {@link EventNode#addListener(EventListener)} and
|
||||
* {@link EventNode#register(EventBinding)}.
|
||||
* <p>
|
||||
* Most computation should ideally be done outside the consumers as a one-time cost.
|
||||
*/
|
||||
private @Nullable Consumer<E> listenersConsumer(@NotNull ListenerEntry<E> entry) {
|
||||
final EventListener<E>[] listenersCopy = entry.listeners.toArray(EventListener[]::new);
|
||||
final Consumer<E>[] bindingsCopy = entry.bindingConsumers.toArray(Consumer[]::new);
|
||||
final boolean listenersEmpty = listenersCopy.length == 0;
|
||||
final boolean bindingsEmpty = bindingsCopy.length == 0;
|
||||
if (listenersEmpty && bindingsEmpty) return null;
|
||||
if (bindingsEmpty && listenersCopy.length == 1) {
|
||||
// Only one normal listener
|
||||
final EventListener<E> listener = listenersCopy[0];
|
||||
return e -> callListener(listener, e);
|
||||
}
|
||||
// Worse case scenario, try to run everything
|
||||
return e -> {
|
||||
if (!listenersEmpty) {
|
||||
for (EventListener<E> listener : listenersCopy) {
|
||||
callListener(listener, e);
|
||||
}
|
||||
}
|
||||
if (!bindingsEmpty) {
|
||||
for (Consumer<E> eConsumer : bindingsCopy) {
|
||||
eConsumer.accept(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a consumer handling {@link EventNode#map(EventNode, Object)}.
|
||||
* The goal is to limit the amount of map lookup.
|
||||
*/
|
||||
private void handleMappedNode(EventNodeImpl<E> targetNode) {
|
||||
final var mappedNodeCache = targetNode.mappedNodeCache;
|
||||
if (mappedNodeCache.isEmpty()) return;
|
||||
private @Nullable Consumer<E> mappedConsumer() {
|
||||
final var mappedNodeCache = node.mappedNodeCache;
|
||||
if (mappedNodeCache.isEmpty()) return null;
|
||||
Set<EventFilter<E, ?>> filters = new HashSet<>(mappedNodeCache.size());
|
||||
Map<Object, Handle<E>> handlers = new HashMap<>(mappedNodeCache.size());
|
||||
// Retrieve all filters used to retrieve potential handlers
|
||||
for (var mappedEntry : mappedNodeCache.entrySet()) {
|
||||
final EventNodeImpl<E> mappedNode = mappedEntry.getValue();
|
||||
final Handle<E> handle = (Handle<E>) mappedNode.getHandle(eventType);
|
||||
if (!mappedNode.hasListener(handle)) continue; // Implicit update
|
||||
if (!handle.hasListener()) continue; // Implicit update
|
||||
filters.add(mappedNode.filter);
|
||||
handlers.put(mappedEntry.getKey(), handle);
|
||||
}
|
||||
// If at least one mapped node listen to this handle type,
|
||||
// loop through them and forward to mapped node if there is a match
|
||||
if (!filters.isEmpty()) {
|
||||
final var filterList = List.copyOf(filters);
|
||||
final int size = filterList.size();
|
||||
final BiConsumer<EventFilter<E, ?>, E> mapper = (filter, event) -> {
|
||||
final Object handler = filter.castHandler(event);
|
||||
final Handle<E> handle = handlers.get(handler);
|
||||
if (handle != null) { // Run the listeners of the mapped node
|
||||
if (!handle.updated) handle.update();
|
||||
for (Consumer<E> listener : handle.listeners) {
|
||||
listener.accept(event);
|
||||
}
|
||||
if (filters.isEmpty()) return null;
|
||||
final EventFilter<E, ?>[] filterList = filters.toArray(EventFilter[]::new);
|
||||
final BiConsumer<EventFilter<E, ?>, E> mapper = (filter, event) -> {
|
||||
final Object handler = filter.castHandler(event);
|
||||
final Handle<E> handle = handlers.get(handler);
|
||||
if (handle != null) handle.call(event);
|
||||
};
|
||||
if (filterList.length == 1) {
|
||||
final var firstFilter = filterList[0];
|
||||
// Common case where there is only one filter
|
||||
return event -> mapper.accept(firstFilter, event);
|
||||
} else if (filterList.length == 2) {
|
||||
final var firstFilter = filterList[0];
|
||||
final var secondFilter = filterList[1];
|
||||
return event -> {
|
||||
mapper.accept(firstFilter, event);
|
||||
mapper.accept(secondFilter, event);
|
||||
};
|
||||
} else {
|
||||
return event -> {
|
||||
for (var filter : filterList) {
|
||||
mapper.accept(filter, event);
|
||||
}
|
||||
};
|
||||
if (size == 1) {
|
||||
final var firstFilter = filterList.get(0);
|
||||
// Common case where there is only one filter
|
||||
this.listeners.add(event -> mapper.accept(firstFilter, event));
|
||||
} else if (size == 2) {
|
||||
final var firstFilter = filterList.get(0);
|
||||
final var secondFilter = filterList.get(1);
|
||||
this.listeners.add(event -> {
|
||||
mapper.accept(firstFilter, event);
|
||||
mapper.accept(secondFilter, event);
|
||||
});
|
||||
} else {
|
||||
this.listeners.add(event -> {
|
||||
for (var filter : filterList) {
|
||||
mapper.accept(filter, event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners from {@link EventNode#addListener(EventListener)} and
|
||||
* {@link EventNode#register(EventBinding)} to the handle list.
|
||||
* <p>
|
||||
* Most computation should ideally be done outside the consumers as a one-time cost.
|
||||
*/
|
||||
private void appendEntries(ListenerEntry<E> entry, EventNodeImpl<E> targetNode) {
|
||||
final var filter = targetNode.filter;
|
||||
final var predicate = targetNode.predicate;
|
||||
|
||||
final boolean hasPredicate = predicate != null;
|
||||
final List<EventListener<E>> listenersCopy = List.copyOf(entry.listeners);
|
||||
final List<Consumer<E>> bindingsCopy = List.copyOf(entry.bindingConsumers);
|
||||
if (!hasPredicate && listenersCopy.isEmpty() && bindingsCopy.isEmpty())
|
||||
return; // Nothing to run
|
||||
|
||||
if (!hasPredicate && bindingsCopy.isEmpty() && listenersCopy.size() == 1) {
|
||||
// Only one normal listener
|
||||
final EventListener<E> listener = listenersCopy.get(0);
|
||||
this.listeners.add(e -> callListener(targetNode, listener, e));
|
||||
return;
|
||||
}
|
||||
|
||||
// Worse case scenario, try to run everything
|
||||
this.listeners.add(e -> {
|
||||
if (hasPredicate) {
|
||||
final Object value = filter.getHandler(e);
|
||||
try {
|
||||
if (!predicate.test(e, value)) return;
|
||||
} catch (Throwable t) {
|
||||
MinecraftServer.getExceptionManager().handleException(t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!listenersCopy.isEmpty()) {
|
||||
for (EventListener<E> listener : listenersCopy) {
|
||||
callListener(targetNode, listener, e);
|
||||
}
|
||||
}
|
||||
if (!bindingsCopy.isEmpty()) {
|
||||
for (Consumer<E> eConsumer : bindingsCopy) {
|
||||
try {
|
||||
eConsumer.accept(e);
|
||||
} catch (Throwable t) {
|
||||
MinecraftServer.getExceptionManager().handleException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static <E extends Event> void callListener(EventNodeImpl<E> targetNode, EventListener<E> listener, E event) {
|
||||
EventListener.Result result;
|
||||
try {
|
||||
result = listener.run(event);
|
||||
} catch (Throwable t) {
|
||||
result = EventListener.Result.EXCEPTION;
|
||||
MinecraftServer.getExceptionManager().handleException(t);
|
||||
}
|
||||
void callListener(@NotNull EventListener<E> listener, E event) {
|
||||
EventListener.Result result = listener.run(event);
|
||||
if (result == EventListener.Result.EXPIRED) {
|
||||
targetNode.removeListener(listener);
|
||||
node.removeListener(listener);
|
||||
this.updated = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,38 @@
|
||||
package net.minestom.server.event;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a key to access an {@link EventNode} listeners.
|
||||
* Represents a key to a listenable event, retrievable from {@link EventNode#getHandle(Class)}.
|
||||
* Useful to avoid map lookups.
|
||||
* <p>
|
||||
* It is recommended to store instances of this class in {@code static final} fields.
|
||||
*
|
||||
* @param <E> the event type
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@ApiStatus.NonExtendable
|
||||
public interface ListenerHandle<E extends Event> {
|
||||
/**
|
||||
* Calls the given event.
|
||||
* Will try to fast exit the execution when possible if {@link #hasListener()} return {@code false}.
|
||||
* <p>
|
||||
* Anonymous and subclasses are not supported, events must have the exact type {@code E}.
|
||||
*
|
||||
* @param event the event to call
|
||||
*/
|
||||
void call(@NotNull E event);
|
||||
|
||||
/**
|
||||
* Gets if any listener has been registered for the given handle.
|
||||
* May trigger an update if the cached data is not correct.
|
||||
* <p>
|
||||
* Useful if you are able to avoid expensive computation in the case where
|
||||
* the event is unused. Be aware that {@link #call(Event)}
|
||||
* has similar optimization built-in.
|
||||
*
|
||||
* @return true if the event has 1 or more listeners
|
||||
*/
|
||||
boolean hasListener();
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ public final class ExceptionManager {
|
||||
* @param e the occurred exception
|
||||
*/
|
||||
public void handleException(Throwable e) {
|
||||
// TODO handle OOM exceptions
|
||||
this.getExceptionHandler().handleException(e);
|
||||
}
|
||||
|
||||
|
@ -280,7 +280,7 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka
|
||||
|
||||
if (result) {
|
||||
PlayerChunkLoadEvent playerChunkLoadEvent = new PlayerChunkLoadEvent(player, chunkX, chunkZ);
|
||||
EventDispatcher.call(playerChunkLoadEvent, GlobalHandles.PLAYER_CHUNK_LOAD);
|
||||
GlobalHandles.PLAYER_CHUNK_LOAD.call(playerChunkLoadEvent);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -9,13 +9,13 @@ import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.entity.pathfinding.PFBlock;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.network.packet.CachedPacket;
|
||||
import net.minestom.server.network.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -40,10 +40,8 @@ public class DynamicChunk extends Chunk {
|
||||
protected final Int2ObjectOpenHashMap<Block> tickableMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private volatile long lastChangeTime;
|
||||
|
||||
private FramedPacket cachedChunkBuffer;
|
||||
private FramedPacket cachedLightBuffer;
|
||||
private long cachedPacketTime;
|
||||
private final CachedPacket chunkCache = new CachedPacket(this::createChunkPacket);
|
||||
private final CachedPacket lightCache = new CachedPacket(this::createLightPacket);
|
||||
|
||||
public DynamicChunk(@NotNull Instance instance, @Nullable Biome[] biomes, int chunkX, int chunkZ) {
|
||||
super(instance, biomes, chunkX, chunkZ, true);
|
||||
@ -126,34 +124,31 @@ public class DynamicChunk extends Chunk {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void sendChunk(@NotNull Player player) {
|
||||
public void sendChunk(@NotNull Player player) {
|
||||
if (!isLoaded()) return;
|
||||
final PlayerConnection connection = player.getPlayerConnection();
|
||||
final long lastChange = getLastChangeTime();
|
||||
final FramedPacket lightPacket = lightCache.retrieveFramedPacket(lastChange);
|
||||
final FramedPacket chunkPacket = chunkCache.retrieveFramedPacket(lastChange);
|
||||
if (connection instanceof PlayerSocketConnection) {
|
||||
final long lastChange = getLastChangeTime();
|
||||
var chunkPacket = cachedChunkBuffer;
|
||||
var lightPacket = cachedLightBuffer;
|
||||
if (lastChange > cachedPacketTime || (chunkPacket == null || lightPacket == null)) {
|
||||
chunkPacket = PacketUtils.allocateTrimmedPacket(createChunkPacket());
|
||||
lightPacket = PacketUtils.allocateTrimmedPacket(createLightPacket());
|
||||
this.cachedChunkBuffer = chunkPacket;
|
||||
this.cachedLightBuffer = lightPacket;
|
||||
this.cachedPacketTime = lastChange;
|
||||
}
|
||||
PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
socketConnection.write(lightPacket);
|
||||
socketConnection.write(chunkPacket);
|
||||
socketConnection.write(lightPacket.body());
|
||||
socketConnection.write(chunkPacket.body());
|
||||
} else {
|
||||
connection.sendPacket(createLightPacket());
|
||||
connection.sendPacket(createChunkPacket());
|
||||
connection.sendPacket(lightPacket.packet());
|
||||
connection.sendPacket(chunkPacket.packet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void sendChunk() {
|
||||
public void sendChunk() {
|
||||
if (!isLoaded()) return;
|
||||
sendPacketToViewers(createLightPacket());
|
||||
sendPacketToViewers(createChunkPacket());
|
||||
if (getViewers().isEmpty()) return;
|
||||
final long lastChange = getLastChangeTime();
|
||||
final FramedPacket lightPacket = lightCache.retrieveFramedPacket(lastChange);
|
||||
final FramedPacket chunkPacket = chunkCache.retrieveFramedPacket(lastChange);
|
||||
sendPacketToViewers(lightPacket.packet());
|
||||
sendPacketToViewers(chunkPacket.packet());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -139,8 +139,7 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public abstract boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull Point blockPosition,
|
||||
@NotNull BlockFace blockFace, float cursorX, float cursorY, float cursorZ);
|
||||
public abstract boolean placeBlock(@NotNull BlockHandler.Placement placement);
|
||||
|
||||
/**
|
||||
* Does call {@link net.minestom.server.event.player.PlayerBlockBreakEvent}
|
||||
@ -732,7 +731,7 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
|
||||
// Tick event
|
||||
{
|
||||
// Process tick events
|
||||
EventDispatcher.call(new InstanceTickEvent(this, time, lastTickAge), GlobalHandles.INSTANCE_TICK);
|
||||
GlobalHandles.INSTANCE_TICK.call(new InstanceTickEvent(this, time, lastTickAge));
|
||||
// Set last tick age
|
||||
this.lastTickAge = time;
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ import net.minestom.server.event.instance.InstanceChunkUnloadEvent;
|
||||
import net.minestom.server.event.player.PlayerBlockBreakEvent;
|
||||
import net.minestom.server.instance.batch.ChunkGenerationBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockFace;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.instance.block.rule.BlockPlacementRule;
|
||||
import net.minestom.server.network.packet.server.play.BlockChangePacket;
|
||||
@ -27,6 +26,7 @@ import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import net.minestom.server.world.DimensionType;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -68,21 +68,18 @@ public class InstanceContainer extends Instance {
|
||||
protected InstanceContainer srcInstance; // only present if this instance has been created using a copy
|
||||
private long lastBlockChangeTime; // Time at which the last block change happened (#setBlock)
|
||||
|
||||
/**
|
||||
* Creates an {@link InstanceContainer}.
|
||||
*
|
||||
* @param uniqueId the unique id of the instance
|
||||
* @param dimensionType the dimension type of the instance
|
||||
*/
|
||||
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType) {
|
||||
@ApiStatus.Experimental
|
||||
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType, @Nullable IChunkLoader loader) {
|
||||
super(uniqueId, dimensionType);
|
||||
// Set the default chunk supplier using DynamicChunk
|
||||
setChunkSupplier(DynamicChunk::new);
|
||||
// Set the default chunk loader which use the Anvil format
|
||||
setChunkLoader(new AnvilLoader("world"));
|
||||
setChunkLoader(Objects.requireNonNullElseGet(loader, () -> new AnvilLoader("world")));
|
||||
this.chunkLoader.loadInstance(this);
|
||||
}
|
||||
|
||||
public InstanceContainer(@NotNull UUID uniqueId, @NotNull DimensionType dimensionType) {
|
||||
this(uniqueId, dimensionType, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
final Chunk chunk = getChunkAt(x, z);
|
||||
@ -156,12 +153,12 @@ public class InstanceContainer extends Instance {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull Point blockPosition,
|
||||
@NotNull BlockFace blockFace, float cursorX, float cursorY, float cursorZ) {
|
||||
public boolean placeBlock(@NotNull BlockHandler.Placement placement) {
|
||||
final Point blockPosition = placement.getBlockPosition();
|
||||
final Chunk chunk = getChunkAt(blockPosition);
|
||||
if (!ChunkUtils.isLoaded(chunk)) return false;
|
||||
UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), block,
|
||||
new BlockHandler.PlayerPlacement(block, this, blockPosition, player, blockFace, cursorX, cursorY, cursorZ), null);
|
||||
UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(),
|
||||
placement.getBlock(), placement, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -275,7 +272,7 @@ public class InstanceContainer extends Instance {
|
||||
.whenComplete((chunk, throwable) -> {
|
||||
// TODO run in the instance thread?
|
||||
cacheChunk(chunk);
|
||||
EventDispatcher.call(new InstanceChunkLoadEvent(this, chunkX, chunkZ), GlobalHandles.INSTANCE_CHUNK_LOAD);
|
||||
GlobalHandles.INSTANCE_CHUNK_LOAD.call(new InstanceChunkLoadEvent(this, chunkX, chunkZ));
|
||||
synchronized (loadingChunks) {
|
||||
this.loadingChunks.remove(ChunkUtils.getChunkIndex(chunk));
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.storage.StorageLocation;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import net.minestom.server.world.DimensionType;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -39,21 +40,32 @@ public final class InstanceManager {
|
||||
* with the specified {@link DimensionType} and {@link StorageLocation}.
|
||||
*
|
||||
* @param dimensionType the {@link DimensionType} of the instance
|
||||
* @param loader the chunk loader
|
||||
* @return the created {@link InstanceContainer}
|
||||
*/
|
||||
public @NotNull InstanceContainer createInstanceContainer(@NotNull DimensionType dimensionType) {
|
||||
final InstanceContainer instanceContainer = new InstanceContainer(UUID.randomUUID(), dimensionType);
|
||||
@ApiStatus.Experimental
|
||||
public @NotNull InstanceContainer createInstanceContainer(@NotNull DimensionType dimensionType, @Nullable IChunkLoader loader) {
|
||||
final InstanceContainer instanceContainer = new InstanceContainer(UUID.randomUUID(), dimensionType, loader);
|
||||
registerInstance(instanceContainer);
|
||||
return instanceContainer;
|
||||
}
|
||||
|
||||
public @NotNull InstanceContainer createInstanceContainer(@NotNull DimensionType dimensionType) {
|
||||
return createInstanceContainer(dimensionType, null);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public @NotNull InstanceContainer createInstanceContainer(@Nullable IChunkLoader loader) {
|
||||
return createInstanceContainer(DimensionType.OVERWORLD, loader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and register an {@link InstanceContainer}.
|
||||
*
|
||||
* @return the created {@link InstanceContainer}
|
||||
*/
|
||||
public @NotNull InstanceContainer createInstanceContainer() {
|
||||
return createInstanceContainer(DimensionType.OVERWORLD);
|
||||
return createInstanceContainer(DimensionType.OVERWORLD, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@ package net.minestom.server.instance;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockFace;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -28,9 +28,8 @@ public class SharedInstance extends Instance {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull Point blockPosition,
|
||||
@NotNull BlockFace blockFace, float cursorX, float cursorY, float cursorZ) {
|
||||
return instanceContainer.placeBlock(player, block, blockPosition, blockFace, cursorX, cursorY, cursorZ);
|
||||
public boolean placeBlock(@NotNull BlockHandler.Placement placement) {
|
||||
return instanceContainer.placeBlock(placement);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -114,14 +114,16 @@ public interface BlockHandler {
|
||||
|
||||
final class PlayerPlacement extends Placement {
|
||||
private final Player player;
|
||||
private final Player.Hand hand;
|
||||
private final BlockFace blockFace;
|
||||
private final float cursorX, cursorY, cursorZ;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public PlayerPlacement(Block block, Instance instance, Point blockPosition,
|
||||
Player player, BlockFace blockFace, float cursorX, float cursorY, float cursorZ) {
|
||||
Player player, Player.Hand hand, BlockFace blockFace, float cursorX, float cursorY, float cursorZ) {
|
||||
super(block, instance, blockPosition);
|
||||
this.player = player;
|
||||
this.hand = hand;
|
||||
this.blockFace = blockFace;
|
||||
this.cursorX = cursorX;
|
||||
this.cursorY = cursorY;
|
||||
@ -132,6 +134,10 @@ public interface BlockHandler {
|
||||
return player;
|
||||
}
|
||||
|
||||
public @NotNull Player.Hand getHand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
public @NotNull BlockFace getBlockFace() {
|
||||
return blockFace;
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ final class BlockImpl implements Block {
|
||||
private Block compute(Map<String, String> properties) {
|
||||
Block block = propertyEntry.get(properties);
|
||||
if (block == null)
|
||||
throw new IllegalArgumentException("Invalid properties: " + properties);
|
||||
throw new IllegalArgumentException("Invalid properties: " + properties + " for block " + this);
|
||||
return nbt == null && handler == null ? block :
|
||||
new BlockImpl(block.registry(), propertyEntry, block.properties(), nbt, handler);
|
||||
}
|
||||
|
@ -401,18 +401,7 @@ public class Inventory extends AbstractInventory implements Viewable {
|
||||
final InventoryClickResult clickResult = clickProcessor.dragging(player,
|
||||
slot != -999 ? (isInWindow ? this : playerInventory) : null,
|
||||
clickSlot, button,
|
||||
clicked, cursor,
|
||||
|
||||
s -> isClickInWindow(s) ? getItemStack(s) :
|
||||
playerInventory.getItemStack(PlayerInventoryUtils.convertSlot(s, offset)),
|
||||
|
||||
(s, item) -> {
|
||||
if (isClickInWindow(s)) {
|
||||
setItemStack(s, item);
|
||||
} else {
|
||||
playerInventory.setItemStack(PlayerInventoryUtils.convertSlot(s, offset), item);
|
||||
}
|
||||
});
|
||||
clicked, cursor);
|
||||
if (clickResult == null || clickResult.isCancel()) {
|
||||
updateAll(player);
|
||||
return false;
|
||||
|
@ -317,9 +317,7 @@ public class PlayerInventory extends AbstractInventory implements EquipmentHandl
|
||||
final ItemStack cursor = getCursorItem();
|
||||
final ItemStack clicked = slot != -999 ? getItemStackFromPacketSlot(slot) : ItemStack.AIR;
|
||||
final InventoryClickResult clickResult = clickProcessor.dragging(player, this,
|
||||
slot, button,
|
||||
clicked, cursor, this::getItemStackFromPacketSlot,
|
||||
this::setItemStackFromPacketSlot);
|
||||
convertPlayerInventorySlot(slot, OFFSET), button, clicked, cursor);
|
||||
if (clickResult == null || clickResult.isCancel()) {
|
||||
update();
|
||||
return false;
|
||||
|
@ -1,8 +1,5 @@
|
||||
package net.minestom.server.inventory.click;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.minestom.server.entity.EquipmentSlot;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
@ -18,18 +15,18 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class InventoryClickProcessor {
|
||||
// Dragging maps
|
||||
private final Map<Player, IntSet> leftDraggingMap = new ConcurrentHashMap<>();
|
||||
private final Map<Player, IntSet> rightDraggingMap = new ConcurrentHashMap<>();
|
||||
private final Map<Player, List<DragData>> leftDraggingMap = new ConcurrentHashMap<>();
|
||||
private final Map<Player, List<DragData>> rightDraggingMap = new ConcurrentHashMap<>();
|
||||
|
||||
public @NotNull InventoryClickResult leftClick(@NotNull Player player, @NotNull AbstractInventory inventory,
|
||||
int slot,
|
||||
@ -162,23 +159,21 @@ public final class InventoryClickProcessor {
|
||||
|
||||
public @Nullable InventoryClickResult dragging(@NotNull Player player, @Nullable AbstractInventory inventory,
|
||||
int slot, int button,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor,
|
||||
@NotNull Int2ObjectFunction<ItemStack> itemGetter,
|
||||
@NotNull BiConsumer<Integer, ItemStack> itemSetter) {
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
InventoryClickResult clickResult = null;
|
||||
final StackingRule stackingRule = cursor.getStackingRule();
|
||||
if (slot != -999) {
|
||||
// Add slot
|
||||
if (button == 1) {
|
||||
// Add left
|
||||
IntSet left = leftDraggingMap.get(player);
|
||||
List<DragData> left = leftDraggingMap.get(player);
|
||||
if (left == null) return null;
|
||||
left.add(slot);
|
||||
left.add(new DragData(slot, inventory));
|
||||
} else if (button == 5) {
|
||||
// Add right
|
||||
IntSet right = rightDraggingMap.get(player);
|
||||
List<DragData> right = rightDraggingMap.get(player);
|
||||
if (right == null) return null;
|
||||
right.add(slot);
|
||||
right.add(new DragData(slot, inventory));
|
||||
} else if (button == 9) {
|
||||
// Add middle
|
||||
// TODO
|
||||
@ -188,18 +183,18 @@ public final class InventoryClickProcessor {
|
||||
if (button == 0) {
|
||||
// Start left
|
||||
clickResult = startCondition(player, inventory, slot, ClickType.START_LEFT_DRAGGING, clicked, cursor);
|
||||
if (!clickResult.isCancel()) this.leftDraggingMap.put(player, new IntOpenHashSet());
|
||||
if (!clickResult.isCancel()) this.leftDraggingMap.put(player, new ArrayList<>());
|
||||
} else if (button == 2) {
|
||||
// End left
|
||||
final IntSet slots = leftDraggingMap.remove(player);
|
||||
final List<DragData> slots = leftDraggingMap.remove(player);
|
||||
if (slots == null) return null;
|
||||
final int slotCount = slots.size();
|
||||
final int cursorAmount = stackingRule.getAmount(cursor);
|
||||
if (slotCount > cursorAmount) return null;
|
||||
for (int s : slots) {
|
||||
for (DragData s : slots) {
|
||||
// Apply each drag element
|
||||
final ItemStack slotItem = itemGetter.apply(s);
|
||||
clickResult = startCondition(player, inventory, s, ClickType.LEFT_DRAGGING, slotItem, cursor);
|
||||
final ItemStack slotItem = s.inventory.getItemStack(s.slot);
|
||||
clickResult = startCondition(player, s.inventory, s.slot, ClickType.LEFT_DRAGGING, slotItem, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
return clickResult;
|
||||
}
|
||||
@ -210,8 +205,10 @@ public final class InventoryClickProcessor {
|
||||
final int slotSize = (int) ((float) cursorAmount / (float) slotCount);
|
||||
// Place all waiting drag action
|
||||
int finalCursorAmount = cursorAmount;
|
||||
for (int s : slots) {
|
||||
ItemStack slotItem = itemGetter.apply(s);
|
||||
for (DragData dragData : slots) {
|
||||
final var inv = dragData.inventory;
|
||||
final int s = dragData.slot;
|
||||
ItemStack slotItem = inv.getItemStack(s);
|
||||
final StackingRule slotItemRule = slotItem.getStackingRule();
|
||||
final int amount = slotItemRule.getAmount(slotItem);
|
||||
if (stackingRule.canBeStacked(cursor, slotItem)) {
|
||||
@ -231,7 +228,7 @@ public final class InventoryClickProcessor {
|
||||
slotItem = stackingRule.apply(cursor, slotSize);
|
||||
finalCursorAmount -= slotSize;
|
||||
}
|
||||
itemSetter.accept(s, slotItem);
|
||||
inv.setItemStack(s, slotItem);
|
||||
callClickEvent(player, inventory, s, ClickType.LEFT_DRAGGING, slotItem, cursor);
|
||||
}
|
||||
// Update the cursor
|
||||
@ -239,18 +236,18 @@ public final class InventoryClickProcessor {
|
||||
} else if (button == 4) {
|
||||
// Start right
|
||||
clickResult = startCondition(player, inventory, slot, ClickType.START_RIGHT_DRAGGING, clicked, cursor);
|
||||
if (!clickResult.isCancel()) this.rightDraggingMap.put(player, new IntOpenHashSet());
|
||||
if (!clickResult.isCancel()) this.rightDraggingMap.put(player, new ArrayList<>());
|
||||
} else if (button == 6) {
|
||||
// End right
|
||||
final IntSet slots = rightDraggingMap.remove(player);
|
||||
final List<DragData> slots = rightDraggingMap.remove(player);
|
||||
if (slots == null) return null;
|
||||
final int size = slots.size();
|
||||
int cursorAmount = stackingRule.getAmount(cursor);
|
||||
if (size > cursorAmount) return null;
|
||||
// Verify if each slot can be modified (or cancel the whole drag)
|
||||
for (int s : slots) {
|
||||
ItemStack slotItem = itemGetter.apply(s);
|
||||
clickResult = startCondition(player, inventory, s, ClickType.RIGHT_DRAGGING, slotItem, cursor);
|
||||
for (DragData s : slots) {
|
||||
final ItemStack slotItem = s.inventory.getItemStack(s.slot);
|
||||
clickResult = startCondition(player, s.inventory, s.slot, ClickType.RIGHT_DRAGGING, slotItem, cursor);
|
||||
if (clickResult.isCancel()) {
|
||||
return clickResult;
|
||||
}
|
||||
@ -259,8 +256,10 @@ public final class InventoryClickProcessor {
|
||||
if (clickResult.isCancel()) return clickResult;
|
||||
// Place all waiting drag action
|
||||
int finalCursorAmount = cursorAmount;
|
||||
for (int s : slots) {
|
||||
ItemStack slotItem = itemGetter.apply(s);
|
||||
for (DragData dragData : slots) {
|
||||
final var inv = dragData.inventory;
|
||||
final int s = dragData.slot;
|
||||
ItemStack slotItem = inv.getItemStack(s);
|
||||
StackingRule slotItemRule = slotItem.getStackingRule();
|
||||
if (stackingRule.canBeStacked(cursor, slotItem)) {
|
||||
// Compatible item in the slot, increment by 1
|
||||
@ -274,7 +273,7 @@ public final class InventoryClickProcessor {
|
||||
slotItem = stackingRule.apply(cursor, 1);
|
||||
finalCursorAmount -= 1;
|
||||
}
|
||||
itemSetter.accept(s, slotItem);
|
||||
inv.setItemStack(s, slotItem);
|
||||
callClickEvent(player, inventory, s, ClickType.RIGHT_DRAGGING, slotItem, cursor);
|
||||
}
|
||||
// Update the cursor
|
||||
@ -462,4 +461,14 @@ public final class InventoryClickProcessor {
|
||||
this.leftDraggingMap.remove(player);
|
||||
this.rightDraggingMap.remove(player);
|
||||
}
|
||||
|
||||
private static final class DragData {
|
||||
private final int slot;
|
||||
private final AbstractInventory inventory;
|
||||
|
||||
public DragData(int slot, AbstractInventory inventory) {
|
||||
this.slot = slot;
|
||||
this.inventory = inventory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import net.minestom.server.item.rule.VanillaStackingRule;
|
||||
import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.tag.TagReadable;
|
||||
import net.minestom.server.utils.NBTUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -79,6 +80,24 @@ public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent
|
||||
return fromNBT(material, nbtCompound, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this item to an NBT tag containing the id (material), count (amount), and tag (meta).
|
||||
*
|
||||
* @param nbtCompound The nbt representation of the item
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public static @NotNull ItemStack fromItemNBT(@NotNull NBTCompound nbtCompound) {
|
||||
String id = nbtCompound.getString("id");
|
||||
Check.notNull(id, "Item NBT must contain an id field.");
|
||||
Material material = Material.fromNamespaceId(id);
|
||||
Check.notNull(material, "Unknown material: {0}", id);
|
||||
|
||||
Byte amount = nbtCompound.getByte("Count");
|
||||
return fromNBT(material,
|
||||
nbtCompound.getCompound("tag"),
|
||||
amount == null ? 1 : amount);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public @NotNull Material getMaterial() {
|
||||
return material;
|
||||
@ -228,6 +247,20 @@ public final class ItemStack implements TagReadable, HoverEventSource<HoverEvent
|
||||
NBTUtils.asBinaryTagHolder(this.meta.toNBT().getCompound("tag")))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this item to an NBT tag containing the id (material), count (amount), and tag (meta)
|
||||
*
|
||||
* @return The nbt representation of the item
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public @NotNull NBTCompound toItemNBT() {
|
||||
final NBTCompound nbtCompound = new NBTCompound();
|
||||
nbtCompound.setString("id", getMaterial().name());
|
||||
nbtCompound.setByte("Count", (byte) getAmount());
|
||||
nbtCompound.set("tag", getMeta().toNBT());
|
||||
return nbtCompound;
|
||||
}
|
||||
|
||||
@Contract(value = "-> new", pure = true)
|
||||
private @NotNull ItemStackBuilder builder() {
|
||||
return new ItemStackBuilder(material, meta.builder())
|
||||
|
@ -46,6 +46,7 @@ public class ItemStackBuilder {
|
||||
MATERIAL_SUPPLIER_MAP.put(Material.FIREWORK_STAR, FireworkEffectMeta.Builder::new);
|
||||
MATERIAL_SUPPLIER_MAP.put(Material.FIREWORK_ROCKET, FireworkMeta.Builder::new);
|
||||
MATERIAL_SUPPLIER_MAP.put(Material.PLAYER_HEAD, PlayerHeadMeta.Builder::new);
|
||||
MATERIAL_SUPPLIER_MAP.put(Material.BUNDLE, BundleMeta.Builder::new);
|
||||
|
||||
MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_HELMET, LeatherArmorMeta.Builder::new);
|
||||
MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_CHESTPLATE, LeatherArmorMeta.Builder::new);
|
||||
|
@ -0,0 +1,84 @@
|
||||
package net.minestom.server.item.metadata;
|
||||
|
||||
import net.minestom.server.item.ItemMeta;
|
||||
import net.minestom.server.item.ItemMetaBuilder;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTList;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTTypes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public class BundleMeta extends ItemMeta implements ItemMetaBuilder.Provider<BundleMeta.Builder> {
|
||||
|
||||
private final List<ItemStack> items;
|
||||
|
||||
protected BundleMeta(ItemMetaBuilder metaBuilder,
|
||||
@NotNull List<ItemStack> items) {
|
||||
super(metaBuilder);
|
||||
this.items = List.copyOf(items);
|
||||
}
|
||||
|
||||
public @NotNull List<ItemStack> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public static class Builder extends ItemMetaBuilder {
|
||||
private List<ItemStack> items = new ArrayList<>();
|
||||
|
||||
public Builder items(@NotNull List<ItemStack> items) {
|
||||
this.items = new ArrayList<>(items); // defensive copy
|
||||
updateItems();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public Builder addItem(@NotNull ItemStack item) {
|
||||
items.add(item);
|
||||
updateItems();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public Builder removeItem(@NotNull ItemStack item) {
|
||||
items.remove(item);
|
||||
updateItems();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BundleMeta build() {
|
||||
return new BundleMeta(this, items);
|
||||
}
|
||||
|
||||
private void updateItems() {
|
||||
mutateNbt(compound -> {
|
||||
NBTList<NBTCompound> itemList = new NBTList<>(NBTTypes.TAG_Compound);
|
||||
for (ItemStack item : items) {
|
||||
itemList.add(item.toItemNBT());
|
||||
}
|
||||
compound.set("Items", itemList);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(@NotNull NBTCompound nbtCompound) {
|
||||
if (nbtCompound.containsKey("Items")) {
|
||||
final NBTList<NBTCompound> items = nbtCompound.getList("Items");
|
||||
for (NBTCompound item : items) {
|
||||
this.items.add(ItemStack.fromItemNBT(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull Supplier<@NotNull ItemMetaBuilder> getSupplier() {
|
||||
return Builder::new;
|
||||
}
|
||||
}
|
||||
}
|
@ -157,8 +157,8 @@ public class BlockPlacementListener {
|
||||
return;
|
||||
}
|
||||
// Place the block
|
||||
instance.placeBlock(player, resultBlock, placementPosition,
|
||||
blockFace, packet.cursorPositionX, packet.cursorPositionY, packet.cursorPositionZ);
|
||||
instance.placeBlock(new BlockHandler.PlayerPlacement(resultBlock, instance, placementPosition, player, hand, blockFace,
|
||||
packet.cursorPositionX, packet.cursorPositionY, packet.cursorPositionZ));
|
||||
// Block consuming
|
||||
if (playerBlockPlaceEvent.doesConsumeBlock()) {
|
||||
// Consume the block in the player's hand
|
||||
|
@ -2,11 +2,12 @@ package net.minestom.server.listener;
|
||||
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.GlobalHandles;
|
||||
import net.minestom.server.event.player.PlayerMoveEvent;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.network.packet.client.play.*;
|
||||
import net.minestom.server.network.packet.server.play.PlayerPositionAndLookPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -32,9 +33,9 @@ public class PlayerPositionListener {
|
||||
player.refreshReceivedTeleportId(packet.teleportId);
|
||||
}
|
||||
|
||||
private static void processMovement(@NotNull Player player, @NotNull Pos newPosition, boolean onGround) {
|
||||
private static void processMovement(@NotNull Player player, @NotNull Pos packetPosition, boolean onGround) {
|
||||
final var currentPosition = player.getPosition();
|
||||
if (currentPosition.equals(newPosition)) {
|
||||
if (currentPosition.equals(packetPosition)) {
|
||||
// For some reason, the position is the same
|
||||
return;
|
||||
}
|
||||
@ -48,21 +49,37 @@ public class PlayerPositionListener {
|
||||
return;
|
||||
}
|
||||
// Try to move in an unloaded chunk, prevent it
|
||||
if (!currentPosition.sameChunk(newPosition) && !ChunkUtils.isLoaded(instance, newPosition)) {
|
||||
if (!currentPosition.sameChunk(packetPosition) && !ChunkUtils.isLoaded(instance, packetPosition)) {
|
||||
player.teleport(currentPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerMoveEvent playerMoveEvent = new PlayerMoveEvent(player, newPosition);
|
||||
EventDispatcher.call(playerMoveEvent, GlobalHandles.PLAYER_MOVE);
|
||||
// True if the event call changed the player position (possibly a teleport)
|
||||
if (!playerMoveEvent.isCancelled() && currentPosition.equals(player.getPosition())) {
|
||||
// Move the player
|
||||
player.refreshPosition(playerMoveEvent.getNewPosition());
|
||||
PlayerMoveEvent playerMoveEvent = new PlayerMoveEvent(player, packetPosition);
|
||||
GlobalHandles.PLAYER_MOVE.call(playerMoveEvent);
|
||||
if (!currentPosition.equals(player.getPosition())) {
|
||||
// Player has been teleported in the event
|
||||
return;
|
||||
}
|
||||
if (playerMoveEvent.isCancelled()) {
|
||||
// Teleport to previous position
|
||||
PlayerConnection connection = player.getPlayerConnection();
|
||||
connection.sendPacket(new PlayerPositionAndLookPacket(currentPosition, (byte) 0x00, player.getNextTeleportId(), false));
|
||||
return;
|
||||
}
|
||||
final Pos eventPosition = playerMoveEvent.getNewPosition();
|
||||
if (packetPosition.equals(eventPosition)) {
|
||||
// Event didn't change the position
|
||||
player.refreshPosition(eventPosition);
|
||||
player.refreshOnGround(onGround);
|
||||
} else {
|
||||
// Cancelled, teleport to previous position
|
||||
player.teleport(player.getPosition());
|
||||
// Position modified by the event
|
||||
if (packetPosition.samePoint(eventPosition)) {
|
||||
player.refreshPosition(eventPosition, true);
|
||||
player.refreshOnGround(onGround);
|
||||
player.setView(eventPosition.yaw(), eventPosition.pitch());
|
||||
} else {
|
||||
player.teleport(eventPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,8 +66,6 @@ public class WindowListener {
|
||||
successful = inventory.doubleClick(player, slot);
|
||||
}
|
||||
|
||||
// Prevent the player from picking a ghost item in cursor
|
||||
refreshCursorItem(player, inventory);
|
||||
// Prevent ghost item when the click is cancelled
|
||||
if (!successful) {
|
||||
player.getInventory().update();
|
||||
@ -75,6 +73,10 @@ public class WindowListener {
|
||||
((Inventory) inventory).update(player);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent the player from picking a ghost item in cursor
|
||||
refreshCursorItem(player, inventory);
|
||||
|
||||
// (Why is the ping packet necessary?)
|
||||
PingPacket pingPacket = new PingPacket();
|
||||
pingPacket.id = (1 << 30) | (windowId << 16);
|
||||
|
@ -2,7 +2,6 @@ package net.minestom.server.listener.manager;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.GlobalHandles;
|
||||
import net.minestom.server.event.player.PlayerPacketEvent;
|
||||
import net.minestom.server.listener.*;
|
||||
@ -90,7 +89,7 @@ public final class PacketListenerManager {
|
||||
|
||||
// Event
|
||||
PlayerPacketEvent playerPacketEvent = new PlayerPacketEvent(player, packet);
|
||||
EventDispatcher.call(playerPacketEvent, GlobalHandles.PLAYER_PACKET);
|
||||
GlobalHandles.PLAYER_PACKET.call(playerPacketEvent);
|
||||
if (playerPacketEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package net.minestom.server.network.packet;
|
||||
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class CachedPacket {
|
||||
private final Supplier<ServerPacket> supplier;
|
||||
private volatile long packetTimestamp;
|
||||
private SoftReference<FramedPacket> packet;
|
||||
|
||||
public CachedPacket(Supplier<ServerPacket> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public FramedPacket retrieveFramedPacket(long lastChange) {
|
||||
final long timestamp = packetTimestamp;
|
||||
final var ref = packet;
|
||||
FramedPacket cache = ref != null ? ref.get() : null;
|
||||
if (cache == null || lastChange > timestamp) {
|
||||
cache = PacketUtils.allocateTrimmedPacket(supplier.get());
|
||||
this.packet = new SoftReference<>(cache);
|
||||
this.packetTimestamp = lastChange;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
@ -12,7 +12,6 @@ import net.minestom.server.network.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
import net.minestom.server.network.socket.Server;
|
||||
import net.minestom.server.network.socket.Worker;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
@ -32,10 +31,9 @@ import java.net.SocketAddress;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
/**
|
||||
@ -46,6 +44,8 @@ import java.util.zip.DataFormatException;
|
||||
@ApiStatus.Internal
|
||||
public class PlayerSocketConnection extends PlayerConnection {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(PlayerSocketConnection.class);
|
||||
private final static Queue<BinaryBuffer> POOLED_BUFFERS = new ConcurrentLinkedQueue<>();
|
||||
private final static int BUFFER_SIZE = 262_143;
|
||||
|
||||
private final Worker worker;
|
||||
private final SocketChannel channel;
|
||||
@ -73,7 +73,9 @@ public class PlayerSocketConnection extends PlayerConnection {
|
||||
private UUID bungeeUuid;
|
||||
private PlayerSkin bungeeSkin;
|
||||
|
||||
private final BinaryBuffer tickBuffer = BinaryBuffer.ofSize(Server.SOCKET_BUFFER_SIZE);
|
||||
private final Object bufferLock = new Object();
|
||||
private final List<BinaryBuffer> waitingBuffers = new ArrayList<>();
|
||||
private BinaryBuffer tickBuffer = BinaryBuffer.ofSize(BUFFER_SIZE);
|
||||
private volatile BinaryBuffer cacheBuffer;
|
||||
|
||||
public PlayerSocketConnection(@NotNull Worker worker, @NotNull SocketChannel channel, SocketAddress remoteAddress) {
|
||||
@ -227,7 +229,7 @@ public class PlayerSocketConnection extends PlayerConnection {
|
||||
}
|
||||
|
||||
public void write(@NotNull ByteBuffer buffer) {
|
||||
synchronized (tickBuffer) {
|
||||
synchronized (bufferLock) {
|
||||
if (!tickBuffer.canWrite(buffer.position())) {
|
||||
// Tick buffer is full, flush before appending
|
||||
flush();
|
||||
@ -246,7 +248,7 @@ public class PlayerSocketConnection extends PlayerConnection {
|
||||
}
|
||||
|
||||
public void writeAndFlush(@NotNull ServerPacket packet) {
|
||||
synchronized (tickBuffer) {
|
||||
synchronized (bufferLock) {
|
||||
write(packet);
|
||||
flush();
|
||||
}
|
||||
@ -255,31 +257,45 @@ public class PlayerSocketConnection extends PlayerConnection {
|
||||
@Override
|
||||
public void flush() {
|
||||
if (!channel.isOpen()) return;
|
||||
synchronized (tickBuffer) {
|
||||
if (tickBuffer.readableBytes() == 0) return;
|
||||
try {
|
||||
if (encrypted) {
|
||||
final Cipher cipher = encryptCipher;
|
||||
// Encrypt data first
|
||||
final int remainingBytes = tickBuffer.readableBytes();
|
||||
final byte[] bytes = tickBuffer.readRemainingBytes();
|
||||
byte[] outTempArray = new byte[cipher.getOutputSize(remainingBytes)];
|
||||
if (tickBuffer.readableBytes() == 0 && waitingBuffers.isEmpty()) return;
|
||||
synchronized (bufferLock) {
|
||||
if (tickBuffer.readableBytes() == 0 && waitingBuffers.isEmpty()) return;
|
||||
if (encrypted) {
|
||||
final Cipher cipher = encryptCipher;
|
||||
// Encrypt data first
|
||||
final int remainingBytes = tickBuffer.readableBytes();
|
||||
final byte[] bytes = tickBuffer.readRemainingBytes();
|
||||
byte[] outTempArray = new byte[cipher.getOutputSize(remainingBytes)];
|
||||
try {
|
||||
cipher.update(bytes, 0, remainingBytes, outTempArray);
|
||||
this.tickBuffer.clear();
|
||||
this.tickBuffer.writeBytes(outTempArray);
|
||||
}
|
||||
this.tickBuffer.writeChannel(channel);
|
||||
} catch (IOException e) {
|
||||
final String message = e.getMessage();
|
||||
if (message == null ||
|
||||
(!message.equals("Broken pipe") && !message.equals("Connection reset by peer"))) {
|
||||
} catch (ShortBufferException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
} catch (ShortBufferException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
this.tickBuffer.clear();
|
||||
this.tickBuffer.writeBytes(outTempArray);
|
||||
}
|
||||
|
||||
this.waitingBuffers.add(tickBuffer);
|
||||
Iterator<BinaryBuffer> iterator = waitingBuffers.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
BinaryBuffer waitingBuffer = iterator.next();
|
||||
try {
|
||||
if (!waitingBuffer.writeChannel(channel)) break;
|
||||
iterator.remove();
|
||||
waitingBuffer.clear();
|
||||
POOLED_BUFFERS.add(waitingBuffer);
|
||||
} catch (IOException e) {
|
||||
final String message = e.getMessage();
|
||||
if (message == null ||
|
||||
(!message.equals("Broken pipe") && !message.equals("Connection reset by peer"))) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
} }
|
||||
}
|
||||
// Update tick buffer
|
||||
BinaryBuffer newBuffer = POOLED_BUFFERS.poll();
|
||||
if (newBuffer == null) newBuffer = BinaryBuffer.ofSize(BUFFER_SIZE);
|
||||
newBuffer.clear();
|
||||
this.tickBuffer = newBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,6 +319,9 @@ public class PlayerSocketConnection extends PlayerConnection {
|
||||
@Override
|
||||
public void disconnect() {
|
||||
this.worker.disconnect(this, channel);
|
||||
synchronized (bufferLock) {
|
||||
POOLED_BUFFERS.addAll(waitingBuffers);
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull SocketChannel getChannel() {
|
||||
|
@ -18,8 +18,8 @@ public final class Server {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(Server.class);
|
||||
public static final int WORKER_COUNT = Integer.getInteger("minestom.workers",
|
||||
Runtime.getRuntime().availableProcessors());
|
||||
public static final int SOCKET_BUFFER_SIZE = Integer.getInteger("minestom.buffer-size", 1_048_575);
|
||||
public static final int MAX_PACKET_SIZE = 2_097_151; // 3 bytes var-int
|
||||
public static final int SOCKET_BUFFER_SIZE = Integer.getInteger("minestom.buffer-size", MAX_PACKET_SIZE);
|
||||
public static final boolean NO_DELAY = true;
|
||||
|
||||
private volatile boolean stop;
|
||||
|
@ -84,6 +84,7 @@ public final class Worker extends Thread {
|
||||
socket.setSendBufferSize(Server.SOCKET_BUFFER_SIZE);
|
||||
socket.setReceiveBufferSize(Server.SOCKET_BUFFER_SIZE);
|
||||
socket.setTcpNoDelay(Server.NO_DELAY);
|
||||
socket.setSoTimeout(30 * 1000); // 30 seconds
|
||||
this.selector.wakeup();
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ public abstract class ThreadProvider {
|
||||
return;
|
||||
try {
|
||||
chunk.tick(time);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
final var entities = chunkEntry.entities;
|
||||
@ -148,7 +148,7 @@ public abstract class ThreadProvider {
|
||||
}
|
||||
try {
|
||||
entity.tick(time);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
|
@ -100,12 +100,26 @@ public class Task implements Runnable {
|
||||
* Sets up the task for correct execution.
|
||||
*/
|
||||
public void schedule() {
|
||||
if(owningExtension != null) {
|
||||
this.schedulerManager.onScheduleFromExtension(owningExtension, this);
|
||||
if(this.shutdown) {
|
||||
Int2ObjectMap<Task> shutdownTasks = this.schedulerManager.shutdownTasks;
|
||||
synchronized (shutdownTasks) {
|
||||
shutdownTasks.put(getId(), this);
|
||||
}
|
||||
if (owningExtension != null) {
|
||||
this.schedulerManager.onScheduleShutdownFromExtension(owningExtension, this);
|
||||
}
|
||||
} else {
|
||||
Int2ObjectMap<Task> tasks = this.schedulerManager.tasks;
|
||||
synchronized (tasks) {
|
||||
tasks.put(getId(), this);
|
||||
}
|
||||
if (owningExtension != null) {
|
||||
this.schedulerManager.onScheduleFromExtension(owningExtension, this);
|
||||
}
|
||||
this.future = this.repeat == 0L ?
|
||||
this.schedulerManager.getTimerExecutionService().schedule(this, this.delay, TimeUnit.MILLISECONDS) :
|
||||
this.schedulerManager.getTimerExecutionService().scheduleAtFixedRate(this, this.delay, this.repeat, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
this.future = this.repeat == 0L ?
|
||||
this.schedulerManager.getTimerExecutionService().schedule(this, this.delay, TimeUnit.MILLISECONDS) :
|
||||
this.schedulerManager.getTimerExecutionService().scheduleAtFixedRate(this, this.delay, this.repeat, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,13 +138,13 @@ public class TaskBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules this {@link Task} for execution.
|
||||
* Builds a {@link Task}.
|
||||
*
|
||||
* @return the built {@link Task}
|
||||
*/
|
||||
@NotNull
|
||||
public Task schedule() {
|
||||
Task task = new Task(
|
||||
public Task build() {
|
||||
return new Task(
|
||||
this.schedulerManager,
|
||||
this.runnable,
|
||||
this.shutdown,
|
||||
@ -152,21 +152,17 @@ public class TaskBuilder {
|
||||
this.repeat,
|
||||
this.isTransient,
|
||||
this.owningExtension);
|
||||
if (this.shutdown) {
|
||||
Int2ObjectMap<Task> shutdownTasks = this.schedulerManager.shutdownTasks;
|
||||
synchronized (shutdownTasks) {
|
||||
shutdownTasks.put(task.getId(), task);
|
||||
}
|
||||
if (owningExtension != null) {
|
||||
this.schedulerManager.onScheduleShutdownFromExtension(owningExtension, task);
|
||||
}
|
||||
} else {
|
||||
Int2ObjectMap<Task> tasks = this.schedulerManager.tasks;
|
||||
synchronized (tasks) {
|
||||
tasks.put(task.getId(), task);
|
||||
}
|
||||
task.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules this {@link Task} for execution.
|
||||
*
|
||||
* @return the scheduled {@link Task}
|
||||
*/
|
||||
@NotNull
|
||||
public Task schedule() {
|
||||
Task task = build();
|
||||
task.schedule();
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import java.util.Random;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* An utilities class for {@link UUID}.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public final class UniqueIdUtils {
|
||||
|
||||
public static final Pattern UNIQUE_ID_PATTERN = Pattern.compile("\\b[0-9a-f]{8}\\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\\b[0-9a-f]{12}\\b");
|
||||
|
||||
/**
|
||||
@ -20,12 +21,4 @@ public final class UniqueIdUtils {
|
||||
public static boolean isUniqueId(String input) {
|
||||
return input.matches(UNIQUE_ID_PATTERN.pattern());
|
||||
}
|
||||
|
||||
public static UUID createRandomUUID(Random random) {
|
||||
long most = random.nextLong() & -61441L | 16384L;
|
||||
long least = random.nextLong() & 4611686018427387903L | Long.MAX_VALUE;
|
||||
|
||||
return new UUID(most, least);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -123,16 +123,15 @@ public final class BinaryBuffer {
|
||||
return nioBuffer.position(reader).slice().limit(writer);
|
||||
}
|
||||
|
||||
public void writeChannel(WritableByteChannel channel) throws IOException {
|
||||
var writeBuffer = asByteBuffer(readerOffset, writerOffset);
|
||||
while (writeBuffer.position() != writeBuffer.limit()) {
|
||||
final int count = channel.write(writeBuffer);
|
||||
if (count == -1) {
|
||||
// EOS
|
||||
throw new IOException("Disconnected");
|
||||
}
|
||||
this.readerOffset += count;
|
||||
public boolean writeChannel(WritableByteChannel channel) throws IOException {
|
||||
var writeBuffer = asByteBuffer(readerOffset, writerOffset - readerOffset);
|
||||
final int count = channel.write(writeBuffer);
|
||||
if (count == -1) {
|
||||
// EOS
|
||||
throw new IOException("Disconnected");
|
||||
}
|
||||
this.readerOffset += count;
|
||||
return writeBuffer.limit() == writeBuffer.position();
|
||||
}
|
||||
|
||||
public void readChannel(ReadableByteChannel channel) throws IOException {
|
||||
|
@ -0,0 +1,92 @@
|
||||
package net.minestom.server.utils.thread;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.AbstractExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Executor service which will always give the same thread to a given Runnable.
|
||||
* Uses <pre>Runnable#hashCode()</pre> to determine the thread to assign.
|
||||
*/
|
||||
public class ThreadBindingExecutor extends AbstractExecutorService {
|
||||
|
||||
private MinestomThread[] threadExecutors;
|
||||
|
||||
/**
|
||||
* Creates a non-local thread-binding executor
|
||||
*
|
||||
* @param nThreads the number of threads
|
||||
* @param name the name of the thread pool
|
||||
*/
|
||||
public ThreadBindingExecutor(int nThreads, String name) {
|
||||
this(nThreads, name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nThreads the number of threads
|
||||
* @param name the name of the thread pool
|
||||
* @param local set to true if this executor is only used inside a method and should *not* be kept in the internal list of executors
|
||||
*/
|
||||
public ThreadBindingExecutor(int nThreads, String name, boolean local) {
|
||||
threadExecutors = new MinestomThread[nThreads];
|
||||
for (int i = 0; i < nThreads; i++) {
|
||||
threadExecutors[i] = new MinestomThread(1, name, local);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
for (MinestomThread t : threadExecutors) {
|
||||
t.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
List<Runnable> allTasks = new LinkedList<>();
|
||||
for (MinestomThread t : threadExecutors) {
|
||||
allTasks.addAll(t.shutdownNow());
|
||||
}
|
||||
return allTasks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
for (MinestomThread t : threadExecutors) {
|
||||
if(!t.isShutdown())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
for (MinestomThread t : threadExecutors) {
|
||||
if(!t.isShutdown())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
|
||||
boolean terminated = true;
|
||||
for (MinestomThread t : threadExecutors) {
|
||||
terminated &= t.awaitTermination(timeout, unit);
|
||||
}
|
||||
return terminated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Runnable command) {
|
||||
int hash = command.hashCode();
|
||||
if(hash < 0) hash = -hash;
|
||||
int bucket = hash % threadExecutors.length;
|
||||
|
||||
threadExecutors[bucket].execute(command);
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.metadata.BundleMeta;
|
||||
import net.minestom.server.monitoring.BenchmarkManager;
|
||||
import net.minestom.server.monitoring.TickMonitor;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
@ -104,6 +105,14 @@ public class PlayerInit {
|
||||
.canDestroy(Set.of(Block.DIAMOND_ORE)))
|
||||
.build();
|
||||
player.getInventory().addItemStack(itemStack);
|
||||
|
||||
ItemStack bundle = ItemStack.builder(Material.BUNDLE)
|
||||
.meta(BundleMeta.class, bundleMetaBuilder -> {
|
||||
bundleMetaBuilder.addItem(ItemStack.of(Material.DIAMOND, 5));
|
||||
bundleMetaBuilder.addItem(ItemStack.of(Material.RABBIT_FOOT, 5));
|
||||
})
|
||||
.build();
|
||||
player.getInventory().addItemStack(bundle);
|
||||
});
|
||||
|
||||
static {
|
||||
|
@ -3,32 +3,39 @@ package demo.commands;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentEnum;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentEntityType;
|
||||
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec3;
|
||||
import net.minestom.server.command.builder.condition.Conditions;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.LivingEntity;
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SummonCommand extends Command {
|
||||
|
||||
private final ArgumentEntityType entity;
|
||||
private final ArgumentRelativeVec3 pos;
|
||||
private final ArgumentEnum<EntityClass> entityClass;
|
||||
private final Argument<RelativeVec> pos;
|
||||
private final Argument<EntityClass> entityClass;
|
||||
|
||||
public SummonCommand() {
|
||||
super("summon");
|
||||
setCondition(Conditions::playerOnly);
|
||||
|
||||
entity = ArgumentType.EntityType("entity type");
|
||||
pos = ArgumentType.RelativeVec3("pos");
|
||||
entityClass = ArgumentType.Enum("class", EntityClass.class);
|
||||
entityClass.setFormat(ArgumentEnum.Format.LOWER_CASED);
|
||||
entityClass.setDefaultValue(EntityClass.CREATURE);
|
||||
pos = ArgumentType.RelativeVec3("pos").setDefaultValue(() -> new RelativeVec(
|
||||
new Vec(0, 0, 0),
|
||||
RelativeVec.CoordinateType.RELATIVE,
|
||||
true, true, true
|
||||
));
|
||||
entityClass = ArgumentType.Enum("class", EntityClass.class)
|
||||
.setFormat(ArgumentEnum.Format.LOWER_CASED)
|
||||
.setDefaultValue(EntityClass.CREATURE);;
|
||||
addSyntax(this::execute, entity, pos, entityClass);
|
||||
setDefaultExecutor((sender, context) -> sender.sendMessage("Usage: /summon <type> <x> <y> <z> <class>"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user