diff --git a/src/main/java/net/minestom/server/collision/BlockCollision.java b/src/main/java/net/minestom/server/collision/BlockCollision.java index 3d3595421fe..87171d3110a 100644 --- a/src/main/java/net/minestom/server/collision/BlockCollision.java +++ b/src/main/java/net/minestom/server/collision/BlockCollision.java @@ -43,7 +43,7 @@ static PhysicsResult handlePhysics(@NotNull BoundingBox boundingBox, static Entity canPlaceBlockAt(Instance instance, Point blockPos, Block b) { for (Entity entity : instance.getNearbyEntities(blockPos, 3)) { final EntityType type = entity.getEntityType(); - if (type == EntityType.ITEM || type == EntityType.ARROW) + if (!entity.hasCollision() || type == EntityType.ITEM || type == EntityType.ARROW) continue; // Marker Armor Stands should not prevent block placement if (entity.getEntityMeta() instanceof ArmorStandMeta armorStandMeta && armorStandMeta.isMarker()) diff --git a/src/main/java/net/minestom/server/command/builder/condition/Conditions.java b/src/main/java/net/minestom/server/command/builder/condition/Conditions.java index f185ed0bff6..14de416cc30 100644 --- a/src/main/java/net/minestom/server/command/builder/condition/Conditions.java +++ b/src/main/java/net/minestom/server/command/builder/condition/Conditions.java @@ -1,7 +1,5 @@ package net.minestom.server.command.builder.condition; - -import net.kyori.adventure.text.Component; import net.minestom.server.command.CommandSender; import net.minestom.server.command.ConsoleSender; import net.minestom.server.entity.Player; @@ -10,19 +8,47 @@ * Common command conditions */ public class Conditions { - public static boolean playerOnly(CommandSender sender, String commandString) { - if (!(sender instanceof Player)) { - sender.sendMessage(Component.text("The command is only available for players")); + /** + * Will only execute if all command conditions succeed. + */ + public static CommandCondition all(CommandCondition... conditions) { + return (sender, commandString) -> { + for (CommandCondition condition : conditions) { + if (!condition.canUse(sender, commandString)) { + return false; + } + } + + return true; + }; + } + + /** + * Will execute if one or more command conditions succeed. + */ + public static CommandCondition any(CommandCondition... conditions) { + return (sender, commandString) -> { + for (CommandCondition condition : conditions) { + if (condition.canUse(sender, commandString)) { + return true; + } + } + return false; - } - return true; + }; } + /** + * Will succeed if the command sender is a player. + */ + public static boolean playerOnly(CommandSender sender, String commandString) { + return sender instanceof Player; + } + + /** + * Will succeed if the command sender is the server console. + */ public static boolean consoleOnly(CommandSender sender, String commandString) { - if (!(sender instanceof ConsoleSender)) { - sender.sendMessage(Component.text("The command is only available from the console")); - return false; - } - return true; + return sender instanceof ConsoleSender; } } diff --git a/src/main/java/net/minestom/server/coordinate/Vec.java b/src/main/java/net/minestom/server/coordinate/Vec.java index ae34febd1ca..d3ba7ce6b89 100644 --- a/src/main/java/net/minestom/server/coordinate/Vec.java +++ b/src/main/java/net/minestom/server/coordinate/Vec.java @@ -487,6 +487,12 @@ public interface Operator { Math.floor(z) ); + Operator SIGNUM = (x, y, z) -> new Vec( + Math.signum(x), + Math.signum(y), + Math.signum(z) + ); + @NotNull Vec apply(double x, double y, double z); } diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index eb3c5c5216a..a4229377672 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -1453,11 +1453,11 @@ public boolean hasEffect(@NotNull PotionEffect effect) { * Gets the level of the specified effect. * * @param effect the effect type - * @return the effect level, 0 if not found + * @return the effect level, -1 if not found */ public int getEffectLevel(@NotNull PotionEffect effect) { TimedPotion timedPotion = getEffect(effect); - return timedPotion == null ? 0 : timedPotion.potion().amplifier(); + return timedPotion == null ? -1 : timedPotion.potion().amplifier(); } /** diff --git a/src/main/java/net/minestom/server/entity/PlayerProjectile.java b/src/main/java/net/minestom/server/entity/PlayerProjectile.java index 79230ffeb04..9b49a96e7e8 100644 --- a/src/main/java/net/minestom/server/entity/PlayerProjectile.java +++ b/src/main/java/net/minestom/server/entity/PlayerProjectile.java @@ -20,7 +20,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; -public class PlayerProjectile extends LivingEntity { +public class PlayerProjectile extends Entity { private final Entity shooter; private long cooldown = 0; diff --git a/src/main/java/net/minestom/server/instance/LightingChunk.java b/src/main/java/net/minestom/server/instance/LightingChunk.java index 8e4801a8c6f..8d3f4f7d053 100644 --- a/src/main/java/net/minestom/server/instance/LightingChunk.java +++ b/src/main/java/net/minestom/server/instance/LightingChunk.java @@ -1,6 +1,5 @@ package net.minestom.server.instance; -import net.minestom.server.MinecraftServer; import net.minestom.server.ServerFlag; import net.minestom.server.collision.Shape; import net.minestom.server.coordinate.Point; @@ -41,8 +40,8 @@ public class LightingChunk extends DynamicChunk { final CachedPacket lightCache = new CachedPacket(this::createLightPacket); private LightData lightData; - boolean chunkLoaded = false; private int highestBlock; + private boolean freezeInvalidation = false; private final ReentrantLock packetGenerationLock = new ReentrantLock(); private final AtomicInteger resendTimer = new AtomicInteger(-1); @@ -107,7 +106,15 @@ private boolean checkSkyOcclusion(Block block) { return occludesBottom || occludesTop; } - private void invalidateSection(int coordinate) { + public void setFreezeInvalidation(boolean freezeInvalidation) { + this.freezeInvalidation = freezeInvalidation; + } + + public void invalidateNeighborsSection(int coordinate) { + if (freezeInvalidation) { + return; + } + for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { Chunk neighborChunk = instance.getChunk(chunkX + i, chunkZ + j); @@ -126,6 +133,21 @@ private void invalidateSection(int coordinate) { } } + public void invalidateResendDelay() { + if (!doneInit || freezeInvalidation) { + return; + } + + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + Chunk neighborChunk = instance.getChunk(chunkX + i, chunkZ + j); + if (neighborChunk instanceof LightingChunk light) { + light.resendTimer.set(resendDelay); + } + } + } + } + @Override public void setBlock(int x, int y, int z, @NotNull Block block, @Nullable BlockHandler.Placement placement, @@ -135,22 +157,10 @@ public void setBlock(int x, int y, int z, @NotNull Block block, // Invalidate neighbor chunks, since they can be updated by this block change int coordinate = ChunkUtils.getChunkCoordinate(y); - if (chunkLoaded) { - invalidateSection(coordinate); + if (doneInit && !freezeInvalidation) { + invalidateNeighborsSection(coordinate); + invalidateResendDelay(); this.lightCache.invalidate(); - - if (doneInit) { - for (int i = -1; i <= 1; i++) { - for (int j = -1; j <= 1; j++) { - Chunk neighborChunk = instance.getChunk(chunkX + i, chunkZ + j); - if (neighborChunk == null) continue; - - if (neighborChunk instanceof LightingChunk light) { - light.resendTimer.set(resendDelay); - } - } - } - } } } @@ -161,7 +171,6 @@ public void sendLighting() { @Override protected void onLoad() { - chunkLoaded = true; doneInit = true; } @@ -176,27 +185,24 @@ public void onGenerate() { invalidate(); - MinecraftServer.getSchedulerManager().scheduleNextTick(() -> { - for (int i = -1; i <= 1; i++) { - for (int j = -1; j <= 1; j++) { - Chunk neighborChunk = instance.getChunk(chunkX + i, chunkZ + j); - if (neighborChunk == null) continue; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + Chunk neighborChunk = instance.getChunk(chunkX + i, chunkZ + j); + if (neighborChunk == null) continue; + + if (neighborChunk instanceof LightingChunk light) { + if (light.doneInit) { + light.resendTimer.set(20); + light.invalidate(); - if (neighborChunk instanceof LightingChunk light) { - for (int section = light.minSection; section < light.maxSection; section++) { + for (int section = minSection; section < maxSection; section++) { light.getSection(section).blockLight().invalidate(); light.getSection(section).skyLight().invalidate(); } - - light.invalidate(); - - light.resendTimer.set(20); } } } - }); - - doneInit = true; + } } // Lazy compute occlusion map @@ -337,11 +343,16 @@ private static Set flushQueue(Instance instance, Set queue, LightT var section = chunk.getSection(point.blockY()); responseChunks.add(chunk); - var light = type == LightType.BLOCK ? section.blockLight() : section.skyLight(); + Light light = switch(type) { + case BLOCK -> section.blockLight(); + case SKY -> section.skyLight(); + }; CompletableFuture task = CompletableFuture.runAsync(() -> { - if (queueType == QueueType.INTERNAL) light.calculateInternal(instance, chunk.getChunkX(), point.blockY(), chunk.getChunkZ()); - else light.calculateExternal(instance, chunk, point.blockY()); + switch (queueType) { + case INTERNAL -> light.calculateInternal(instance, chunk.getChunkX(), point.blockY(), chunk.getChunkZ()); + case EXTERNAL -> light.calculateExternal(instance, chunk, point.blockY()); + } sections.add(light); @@ -449,7 +460,8 @@ private static Set getNearbyRequired(Instance instance, Point point, Ligh if (sectionPosition.blockY() < chunkCheck.getMaxSection() && sectionPosition.blockY() >= chunkCheck.getMinSection()) { Section s = chunkCheck.getSection(sectionPosition.blockY()); - if (!s.blockLight().requiresUpdate() && !s.skyLight().requiresUpdate()) continue; + if (type == LightType.BLOCK && !s.blockLight().requiresUpdate()) continue; + if (type == LightType.SKY && !s.skyLight().requiresUpdate()) continue; collected.add(sectionPosition); } @@ -509,4 +521,9 @@ private static Set relight(Instance instance, Set queue, LightType lightingChunk.entries.putAll(entries); return lightingChunk; } + + @Override + public boolean isLoaded() { + return super.isLoaded() && doneInit; + } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/instance/light/BlockLight.java b/src/main/java/net/minestom/server/instance/light/BlockLight.java index 19a3dd2d430..1a8caa67b92 100644 --- a/src/main/java/net/minestom/server/instance/light/BlockLight.java +++ b/src/main/java/net/minestom/server/instance/light/BlockLight.java @@ -10,10 +10,10 @@ import net.minestom.server.instance.block.BlockFace; import net.minestom.server.instance.palette.Palette; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import static net.minestom.server.instance.light.LightCompute.*; @@ -24,8 +24,8 @@ final class BlockLight implements Light { private byte[] contentPropagation; private byte[] contentPropagationSwap; - private boolean isValidBorders = true; - private boolean needsSend = false; + private final AtomicBoolean isValidBorders = new AtomicBoolean(true); + private final AtomicBoolean needsSend = new AtomicBoolean(false); private Set toUpdateSet = new HashSet<>(); private final Section[] neighborSections = new Section[BlockFace.values().length]; @@ -83,7 +83,7 @@ private ShortArrayFIFOQueue buildExternalQueue(Instance instance, Palette blockP neighborSections[face.ordinal()] = otherSection; } - var otherLight = otherSection.blockLight(); + Light otherLight = otherSection.blockLight(); for (int bx = 0; bx < 16; bx++) { for (int by = 0; by < 16; by++) { @@ -145,14 +145,14 @@ private ShortArrayFIFOQueue buildExternalQueue(Instance instance, Palette blockP @Override public Light calculateInternal(Instance instance, int chunkX, int sectionY, int chunkZ) { + this.isValidBorders.set(true); + Chunk chunk = instance.getChunk(chunkX, chunkZ); if (chunk == null) { this.toUpdateSet = Set.of(); return this; } - this.isValidBorders = true; - Set toUpdate = new HashSet<>(); // Update single section with base lighting changes @@ -171,8 +171,8 @@ public Light calculateInternal(Instance instance, int chunkX, int sectionY, int Vec neighborPos = new Vec(chunkX + i, sectionY + k, chunkZ + j); if (neighborPos.blockY() >= neighborChunk.getMinSection() && neighborPos.blockY() < neighborChunk.getMaxSection()) { - toUpdate.add(new Vec(neighborChunk.getChunkX(), neighborPos.blockY(), neighborChunk.getChunkZ())); - neighborChunk.getSection(neighborPos.blockY()).blockLight().invalidatePropagation(); + if (neighborChunk.getSection(neighborPos.blockY()).blockLight() instanceof BlockLight blockLight) + blockLight.contentPropagation = null; } } } @@ -186,12 +186,14 @@ public Light calculateInternal(Instance instance, int chunkX, int sectionY, int @Override public void invalidate() { - invalidatePropagation(); + this.needsSend.set(true); + this.isValidBorders.set(false); + this.contentPropagation = null; } @Override public boolean requiresUpdate() { - return !isValidBorders; + return !isValidBorders.get(); } @Override @@ -199,21 +201,13 @@ public boolean requiresUpdate() { public void set(byte[] copyArray) { this.content = copyArray.clone(); this.contentPropagation = this.content; - this.isValidBorders = true; - this.needsSend = true; + this.isValidBorders.set(true); + this.needsSend.set(true); } @Override public boolean requiresSend() { - boolean res = needsSend; - needsSend = false; - return res; - } - - private void clearCache() { - this.contentPropagation = null; - isValidBorders = true; - needsSend = true; + return needsSend.getAndSet(false); } @Override @@ -227,7 +221,10 @@ public byte[] array() { @Override public Light calculateExternal(Instance instance, Chunk chunk, int sectionY) { - if (!isValidBorders) clearCache(); + if (!isValidBorders.get()) { + this.toUpdateSet = Set.of(); + return this; + } Point[] neighbors = Light.getNeighbors(chunk, sectionY); @@ -280,13 +277,6 @@ private byte[] bake(byte[] content1, byte[] content2) { return lightMax; } - @Override - public void invalidatePropagation() { - this.isValidBorders = false; - this.needsSend = false; - this.contentPropagation = null; - } - @Override public int getLevel(int x, int y, int z) { if (content == null) return 0; diff --git a/src/main/java/net/minestom/server/instance/light/Light.java b/src/main/java/net/minestom/server/instance/light/Light.java index 540aa7ca054..9e119f5a9ca 100644 --- a/src/main/java/net/minestom/server/instance/light/Light.java +++ b/src/main/java/net/minestom/server/instance/light/Light.java @@ -10,8 +10,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import static net.minestom.server.instance.light.LightCompute.SECTION_SIZE; @@ -36,9 +34,6 @@ static Light block(@NotNull Palette blockPalette) { @ApiStatus.Internal Light calculateExternal(Instance instance, Chunk chunk, int sectionY); - @ApiStatus.Internal - void invalidatePropagation(); - int getLevel(int x, int y, int z); @ApiStatus.Internal diff --git a/src/main/java/net/minestom/server/instance/light/SkyLight.java b/src/main/java/net/minestom/server/instance/light/SkyLight.java index 1fa0448ea92..f4529483bde 100644 --- a/src/main/java/net/minestom/server/instance/light/SkyLight.java +++ b/src/main/java/net/minestom/server/instance/light/SkyLight.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import static net.minestom.server.instance.light.LightCompute.*; @@ -24,8 +25,8 @@ final class SkyLight implements Light { private byte[] contentPropagation; private byte[] contentPropagationSwap; - private boolean isValidBorders = true; - private boolean needsSend = false; + private final AtomicBoolean isValidBorders = new AtomicBoolean(true); + private final AtomicBoolean needsSend = new AtomicBoolean(false); private Set toUpdateSet = new HashSet<>(); private final Section[] neighborSections = new Section[BlockFace.values().length]; @@ -160,7 +161,7 @@ public Light calculateInternal(Instance instance, int chunkX, int sectionY, int this.toUpdateSet = Set.of(); return this; } - this.isValidBorders = true; + this.isValidBorders.set(true); // Update single section with base lighting changes int queueSize = SECTION_SIZE * SECTION_SIZE * SECTION_SIZE; @@ -190,8 +191,10 @@ public Light calculateInternal(Instance instance, int chunkX, int sectionY, int Vec neighborPos = new Vec(chunkX + i, sectionY + k, chunkZ + j); if (neighborPos.blockY() >= neighborChunk.getMinSection() && neighborPos.blockY() < neighborChunk.getMaxSection()) { - toUpdate.add(new Vec(neighborChunk.getChunkX(), neighborPos.blockY(), neighborChunk.getChunkZ())); - neighborChunk.getSection(neighborPos.blockY()).skyLight().invalidatePropagation(); + if (neighborChunk.getSection(neighborPos.blockY()).skyLight() instanceof SkyLight skyLight) { + skyLight.contentPropagation = null; + toUpdate.add(new Vec(neighborChunk.getChunkX(), neighborPos.blockY(), neighborChunk.getChunkZ())); + } } } } @@ -205,12 +208,14 @@ public Light calculateInternal(Instance instance, int chunkX, int sectionY, int @Override public void invalidate() { - invalidatePropagation(); + this.needsSend.set(true); + this.isValidBorders.set(false); + this.contentPropagation = null; } @Override public boolean requiresUpdate() { - return !isValidBorders; + return !isValidBorders.get(); } @Override @@ -218,22 +223,13 @@ public boolean requiresUpdate() { public void set(byte[] copyArray) { this.content = copyArray.clone(); this.contentPropagation = this.content; - this.isValidBorders = true; - this.needsSend = true; + this.isValidBorders.set(true); + this.needsSend.set(true); } @Override public boolean requiresSend() { - boolean res = needsSend; - needsSend = false; - return res; - } - - private void clearCache() { - this.contentPropagation = null; - isValidBorders = true; - needsSend = true; - fullyLit = false; + return needsSend.getAndSet(false); } @Override @@ -247,7 +243,10 @@ public byte[] array() { @Override public Light calculateExternal(Instance instance, Chunk chunk, int sectionY) { - if (!isValidBorders) clearCache(); + if (!isValidBorders.get()) { + this.toUpdateSet = Set.of(); + return this; + } Point[] neighbors = Light.getNeighbors(chunk, sectionY); Set toUpdate = new HashSet<>(); @@ -307,13 +306,6 @@ private byte[] bake(byte[] content1, byte[] content2) { return lightMax; } - @Override - public void invalidatePropagation() { - this.isValidBorders = false; - this.needsSend = false; - this.contentPropagation = null; - } - @Override public int getLevel(int x, int y, int z) { if (content == null) return 0;