All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.level.Level Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.level;

import cn.nukkit.Player;
import cn.nukkit.Server;
import cn.nukkit.api.*;
import cn.nukkit.block.*;
import cn.nukkit.block.customblock.CustomBlock;
import cn.nukkit.blockentity.BlockEntity;
import cn.nukkit.blockstate.BlockState;
import cn.nukkit.blockstate.BlockStateRegistry;
import cn.nukkit.blockstate.exception.InvalidBlockStateException;
import cn.nukkit.entity.Entity;
import cn.nukkit.entity.EntityAsyncPrepare;
import cn.nukkit.entity.item.EntityFirework;
import cn.nukkit.entity.item.EntityItem;
import cn.nukkit.entity.item.EntityPainting;
import cn.nukkit.entity.item.EntityXPOrb;
import cn.nukkit.entity.projectile.EntityProjectile;
import cn.nukkit.entity.weather.EntityLightning;
import cn.nukkit.event.block.BlockBreakEvent;
import cn.nukkit.event.block.BlockPlaceEvent;
import cn.nukkit.event.block.BlockUpdateEvent;
import cn.nukkit.event.level.*;
import cn.nukkit.event.player.PlayerInteractEvent;
import cn.nukkit.event.player.PlayerInteractEvent.Action;
import cn.nukkit.event.weather.LightningStrikeEvent;
import cn.nukkit.item.Item;
import cn.nukkit.item.ItemBlock;
import cn.nukkit.item.ItemBucket;
import cn.nukkit.item.enchantment.Enchantment;
import cn.nukkit.level.biome.Biome;
import cn.nukkit.level.format.*;
import cn.nukkit.level.format.generic.BaseFullChunk;
import cn.nukkit.level.format.generic.BaseLevelProvider;
import cn.nukkit.level.format.generic.EmptyChunkSection;
import cn.nukkit.level.generator.Generator;
import cn.nukkit.level.generator.PopChunkManager;
import cn.nukkit.level.generator.task.GenerationTask;
import cn.nukkit.level.generator.task.LightPopulationTask;
import cn.nukkit.level.generator.task.PopulationTask;
import cn.nukkit.level.particle.DestroyBlockParticle;
import cn.nukkit.level.particle.Particle;
import cn.nukkit.level.tickingarea.TickingArea;
import cn.nukkit.level.util.SimpleTickCachedBlockStore;
import cn.nukkit.level.util.TickCachedBlockStore;
import cn.nukkit.level.vibration.SimpleVibrationManager;
import cn.nukkit.level.vibration.VibrationEvent;
import cn.nukkit.level.vibration.VibrationManager;
import cn.nukkit.level.vibration.VibrationType;
import cn.nukkit.math.*;
import cn.nukkit.math.BlockFace.Plane;
import cn.nukkit.metadata.BlockMetadataStore;
import cn.nukkit.metadata.MetadataValue;
import cn.nukkit.metadata.Metadatable;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.*;
import cn.nukkit.network.protocol.*;
import cn.nukkit.network.protocol.types.PlayerAbility;
import cn.nukkit.plugin.Plugin;
import cn.nukkit.scheduler.AsyncTask;
import cn.nukkit.scheduler.BlockUpdateScheduler;
import cn.nukkit.timings.LevelTimings;
import cn.nukkit.utils.*;
import co.aikar.timings.Timings;
import co.aikar.timings.TimingsHistory;
import com.google.common.base.Preconditions;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nullable;
import java.awt.*;
import java.io.File;
import java.lang.ref.SoftReference;
import java.util.List;
import java.util.Queue;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static cn.nukkit.utils.Utils.dynamic;

/**
 * @author MagicDroidX (Nukkit Project)
 */
@Log4j2
public class Level implements ChunkManager, Metadatable {
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final Level[] EMPTY_ARRAY = new Level[0];
    public static final int BLOCK_UPDATE_NORMAL = 1;
    public static final int BLOCK_UPDATE_RANDOM = 2;
    public static final int BLOCK_UPDATE_SCHEDULED = 3;
    public static final int BLOCK_UPDATE_WEAK = 4;
    public static final int BLOCK_UPDATE_TOUCH = 5;
    public static final int BLOCK_UPDATE_REDSTONE = 6;
    public static final int BLOCK_UPDATE_TICK = 7;
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final int BLOCK_UPDATE_MOVED = dynamic(1_000_000);
    public static final int TIME_DAY = 0;
    public static final int TIME_NOON = 6000;
    public static final int TIME_SUNSET = 12000;
    public static final int TIME_NIGHT = 14000;
    public static final int TIME_MIDNIGHT = 18000;
    public static final int TIME_SUNRISE = 23000;
    public static final int TIME_FULL = 24000;
    public static final int DIMENSION_OVERWORLD = 0;
    public static final int DIMENSION_NETHER = 1;
    public static final int DIMENSION_THE_END = 2;
    // Lower values use less memory
    public static final int MAX_BLOCK_CACHE = 512;
    // The blocks that can randomly tick
    private static final IntOpenHashSet randomTickBlocks = new IntOpenHashSet(64);
    private static final int LCG_CONSTANT = 1013904223;
    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    private static final IntSet transparentBlockRuntimeIds = new IntOpenHashSet(256);
    private static final Entity[] ENTITY_BUFFER = new Entity[512];
    @SuppressWarnings({"java:S1444", "java:S3008"})
    public static int COMPRESSION_LEVEL = 8;
    private static int levelIdCounter = 1;
    private static int chunkLoaderCounter = 1;

    static {
        Timings.init();
    }

    static {
        randomTickBlocks.add(BlockID.GRASS);
        randomTickBlocks.add(BlockID.FARMLAND);
        randomTickBlocks.add(BlockID.MYCELIUM);
        randomTickBlocks.add(BlockID.SAPLING);
        randomTickBlocks.add(BlockID.LEAVES);
        randomTickBlocks.add(BlockID.LEAVES2);
        randomTickBlocks.add(BlockID.SNOW_LAYER);
        randomTickBlocks.add(BlockID.ICE);
        randomTickBlocks.add(BlockID.FLOWING_LAVA);
        randomTickBlocks.add(BlockID.STILL_LAVA);
        randomTickBlocks.add(BlockID.CACTUS);
        randomTickBlocks.add(BlockID.BEETROOT_BLOCK);
        randomTickBlocks.add(BlockID.CARROT_BLOCK);
        randomTickBlocks.add(BlockID.POTATO_BLOCK);
        randomTickBlocks.add(BlockID.MELON_STEM);
        randomTickBlocks.add(BlockID.PUMPKIN_STEM);
        randomTickBlocks.add(BlockID.WHEAT_BLOCK);
        randomTickBlocks.add(BlockID.REEDS);
        randomTickBlocks.add(BlockID.RED_MUSHROOM);
        randomTickBlocks.add(BlockID.BROWN_MUSHROOM);
        randomTickBlocks.add(BlockID.NETHER_WART_BLOCK);
        randomTickBlocks.add(BlockID.FIRE);
        randomTickBlocks.add(BlockID.LIT_REDSTONE_ORE);
        randomTickBlocks.add(BlockID.COCOA);
        randomTickBlocks.add(BlockID.VINE);
        randomTickBlocks.add(BlockID.CORAL_FAN);
        randomTickBlocks.add(BlockID.CORAL_FAN_DEAD);
        randomTickBlocks.add(BlockID.BLOCK_KELP);
        randomTickBlocks.add(BlockID.SWEET_BERRY_BUSH);
        randomTickBlocks.add(BlockID.TURTLE_EGG);
        randomTickBlocks.add(BlockID.BAMBOO);
        randomTickBlocks.add(BlockID.BAMBOO_SAPLING);
        randomTickBlocks.add(BlockID.CRIMSON_NYLIUM);
        randomTickBlocks.add(BlockID.WARPED_NYLIUM);
        randomTickBlocks.add(BlockID.TWISTING_VINES);
        randomTickBlocks.add(BlockID.CHORUS_FLOWER);
        randomTickBlocks.add(BlockID.COPPER_BLOCK);
        randomTickBlocks.add(BlockID.EXPOSED_COPPER);
        randomTickBlocks.add(BlockID.WEATHERED_COPPER);
        randomTickBlocks.add(BlockID.WAXED_COPPER);
        randomTickBlocks.add(BlockID.CUT_COPPER);
        randomTickBlocks.add(BlockID.EXPOSED_CUT_COPPER);
        randomTickBlocks.add(BlockID.WEATHERED_CUT_COPPER);
        randomTickBlocks.add(BlockID.CUT_COPPER_STAIRS);
        randomTickBlocks.add(BlockID.EXPOSED_CUT_COPPER_STAIRS);
        randomTickBlocks.add(BlockID.WEATHERED_CUT_COPPER_STAIRS);
        randomTickBlocks.add(BlockID.CUT_COPPER_SLAB);
        randomTickBlocks.add(BlockID.EXPOSED_CUT_COPPER_SLAB);
        randomTickBlocks.add(BlockID.WEATHERED_CUT_COPPER_SLAB);
        randomTickBlocks.add(BlockID.DOUBLE_CUT_COPPER_SLAB);
        randomTickBlocks.add(BlockID.EXPOSED_DOUBLE_CUT_COPPER_SLAB);
        randomTickBlocks.add(BlockID.WEATHERED_DOUBLE_CUT_COPPER_SLAB);
        randomTickBlocks.add(BlockID.BUDDING_AMETHYST);
        randomTickBlocks.add(BlockID.POINTED_DRIPSTONE);
        randomTickBlocks.add(BlockID.CAVE_VINES);
        randomTickBlocks.add(BlockID.CAVE_VINES_BODY_WITH_BERRIES);
        randomTickBlocks.add(BlockID.CAVE_VINES_HEAD_WITH_BERRIES);
        randomTickBlocks.add(BlockID.AZALEA_LEAVES);
        randomTickBlocks.add(BlockID.AZALEA_LEAVES_FLOWERED);
        randomTickBlocks.add(BlockID.MANGROVE_LEAVES);
        randomTickBlocks.trim();
    }

    public final Long2ObjectOpenHashMap updateEntities = new Long2ObjectOpenHashMap<>();
    private final Long2ObjectOpenHashMap blockEntities = new Long2ObjectOpenHashMap<>();
    private final Long2ObjectOpenHashMap players = new Long2ObjectOpenHashMap<>();
    private final Long2ObjectOpenHashMap entities = new Long2ObjectOpenHashMap<>();
    private final ConcurrentLinkedQueue updateBlockEntities = new ConcurrentLinkedQueue<>();
    private final Server server;

    private final int levelId;
    private final Int2ObjectOpenHashMap loaders = new Int2ObjectOpenHashMap<>();
    private final Int2IntMap loaderCounter = new Int2IntOpenHashMap();
    /*
     * >
     */
    private final Long2ObjectOpenHashMap> chunkLoaders = new Long2ObjectOpenHashMap<>();
    private final Long2ObjectOpenHashMap> playerLoaders = new Long2ObjectOpenHashMap<>();
    private final Long2ObjectOpenHashMap> chunkPackets = new Long2ObjectOpenHashMap<>();
    private final Long2LongMap unloadQueue = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
    @PowerNukkitXOnly
    @Since("1.6.0.0-PNX")
    private final ConcurrentHashMap tickCachedBlocks = new ConcurrentHashMap<>();
    @PowerNukkitXOnly
    @Since("1.6.0.0-PNX")
    private final LongSet highLightChunks = new LongOpenHashSet();
    // Avoid OOM, gc'd references result in whole chunk being sent (possibly higher cpu)
    private final Long2ObjectOpenHashMap>> changedBlocks = new Long2ObjectOpenHashMap<>();
    // Storing the vector is redundant
    private final Object changeBlocksPresent = new Object();
    // Storing extra blocks past 512 is redundant
    private final Int2ObjectOpenHashMap changeBlocksFullMap = new Int2ObjectOpenHashMap<>() {
        @Override
        public int size() {
            return Character.MAX_VALUE;
        }
    };
    private final BlockUpdateScheduler updateQueue;
    private final Queue normalUpdateQueue = new ConcurrentLinkedDeque<>();
    private final ConcurrentMap> chunkSendQueue = new ConcurrentHashMap<>();
    private final Long2ObjectOpenHashMap chunkPopulationQueue = new Long2ObjectOpenHashMap<>();
    private final Long2ObjectOpenHashMap chunkPopulationLock = new Long2ObjectOpenHashMap<>();
    private final Long2ObjectOpenHashMap chunkGenerationQueue = new Long2ObjectOpenHashMap<>();
    private final Long2IntMap chunkTickList = new Long2IntOpenHashMap();
    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    private final Int2IntMap realOreToReplacedRuntimeIds = new Int2IntOpenHashMap(24);
    //    private final TreeSet updateQueue = new TreeSet<>();
//    private final List nextTickUpdates = Lists.newArrayList();
    //private final Map updateQueueIndex = new HashMap<>();
    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    private final Int2ObjectOpenHashMap fakeOreToPutRuntimeIds = new Int2ObjectOpenHashMap<>(4);
    @PowerNukkitXOnly
    @Since("1.19.21-r3")
    private final VibrationManager vibrationManager = new SimpleVibrationManager(this);
    public boolean stopTime;
    public float skyLightSubtracted;
    public int sleepTicks = 0;
    public LevelTimings timings;
    public int tickRateTime = 0;
    public int tickRateCounter = 0;
    /**
     * 当tps过低的时候,tps优化延迟会上升,计算密集型任务应当每隔此tick才运行一次
     */
    public int tickRateOptDelay = 1;
    public GameRules gameRules;
    private boolean cacheChunks = false;
    private LevelProvider provider;
    private float time;
    private int nextTimeSendTick;
    private final String folderName;
    private Vector3 mutableBlock;
    private int chunkGenerationQueueSize = 8;
    private int chunkPopulationQueueSize = 2;
    private boolean autoSave;
    private BlockMetadataStore blockMetadata;
    private final boolean useSections;
    private Position temporalPosition;
    private final Vector3 temporalVector;
    private final int chunkTickRadius;
    private final int chunksPerTicks;
    private final boolean clearChunksOnTick;
    private final ThreadLocal generators;
    private int updateLCG = ThreadLocalRandom.current().nextInt();
    private int tickRate;
    private final Class generatorClass;
    private boolean raining = false;
    private int rainTime = 0;
    private boolean thundering = false;
    private int thunderTime = 0;
    private long levelCurrentTick = 0;
    private DimensionData dimensionData;
    /* Anti-xray related. 反矿透相关 */
    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    private boolean antiXrayEnabled = false;
    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    private int fakeOreDenominator = 16;
    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    private boolean preDeObfuscate = true;
    private final Map> lightQueue = new ConcurrentHashMap<>(8, 0.9f, 1);
    private int lastUnloadIndex;

    public Level(Server server, String name, String path, Class provider) {
        this(server, name, path,
                () -> {
                    try {
                        return (boolean) provider.getMethod("usesChunkSection").invoke(null);
                    } catch (ReflectiveOperationException e) {
                        throw new LevelException("usesChunkSection of " + provider + " failed", e);
                    }
                },
                (level, levelPath) -> {
                    try {
                        return provider.getConstructor(Level.class, String.class).newInstance(level, levelPath);
                    } catch (ReflectiveOperationException e) {
                        throw new LevelException("Constructor of " + provider + " failed", e);
                    }
                }
        );
    }

    /**
     * Easier constructor to create PowerNukkit tests.
     */
    @Since("1.4.0.0-PN")
    Level(Server server, String name, File path, boolean usesChunkSection, LevelProvider provider) {
        this(server, name, path.getAbsolutePath() + "/", () -> usesChunkSection, (lvl, p) -> provider);
    }

    /**
     * Easier constructor to create PowerNukkit tests.
     */
    @Since("1.4.0.0-PN")
    Level(Server server, String name, String path, BooleanSupplier usesChunkSection, BiFunction provider) {
        this.levelId = levelIdCounter++;
        this.blockMetadata = new BlockMetadataStore(this);
        this.server = server;
        this.autoSave = server.getAutoSave();
        this.provider = provider.apply(this, path);
        LevelProvider levelProvider = requireProvider();
        this.timings = new LevelTimings(this);
        levelProvider.updateLevelName(name);
        if (levelProvider instanceof DimensionDataProvider dimensionDataProvider) {
            this.dimensionData = dimensionDataProvider.getDimensionData();
        }

        if (server.getConfig("anti-xray." + name + ".enabled", false)) {
            this.setAntiXrayEnabled(true);
            this.reinitAntiXray(false);
            this.setFakeOreDenominator(switch (server.getConfig("anti-xray." + name + ".level", "low")) {
                case "high" -> 4;
                case "normal", "middle" -> 8;
                default -> 16;
            });
            this.setPreDeObfuscate(server.getConfig("anti-xray." + name + ".pre-deobfuscate", true));
        }

        log.info(this.server.getLanguage().tr("nukkit.level.preparing",
                TextFormat.GREEN + levelProvider.getName() + TextFormat.WHITE));

        this.generatorClass = Generator.getGenerator(levelProvider.getGenerator());
        this.generators = ThreadLocal.withInitial(() -> {
            try {
                Generator generator = generatorClass.getConstructor(Map.class).newInstance(requireProvider().getGeneratorOptions());
                NukkitRandom rand = new NukkitRandom(getSeed());
                generator.setRandom(rand);
                generator.setLevel(Level.this);
                ChunkManager manager = new PopChunkManager(getSeed());
                generator.setChunkManager(manager);

                //这里这么做的原因是为了向前兼容,实际上没必要
                if (Server.getInstance().isPrimaryThread()) {
                    generator.init(Level.this, rand);
                }
                generator.init(manager, rand);

                return generator;
            } catch (Throwable e) {
                e.printStackTrace();
                return null;
            }
        });
        this.useSections = usesChunkSection.getAsBoolean();

        this.folderName = name;
        this.time = levelProvider.getTime();

        this.raining = levelProvider.isRaining();
        this.rainTime = this.requireProvider().getRainTime();
        if (this.rainTime <= 0) {
            setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000);
        }

        this.thundering = levelProvider.isThundering();
        this.thunderTime = levelProvider.getThunderTime();
        if (this.thunderTime <= 0) {
            setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000);
        }

        this.levelCurrentTick = levelProvider.getCurrentTick();
        this.updateQueue = new BlockUpdateScheduler(this, levelCurrentTick);

        this.chunkTickRadius = Math.min(this.server.getViewDistance(),
                Math.max(1, this.server.getConfig("chunk-ticking.tick-radius", 4)));
        this.chunksPerTicks = this.server.getConfig("chunk-ticking.per-tick", 40);
        this.chunkGenerationQueueSize = this.server.getConfig("chunk-generation.queue-size", 8);
        this.chunkPopulationQueueSize = this.server.getConfig("chunk-generation.population-queue-size", 2);
        this.chunkTickList.clear();
        this.clearChunksOnTick = this.server.getConfig("chunk-ticking.clear-tick-list", true);
        this.cacheChunks = this.server.getConfig("chunk-sending.cache-chunks", false);
        this.temporalPosition = new Position(0, 0, 0, this);
        this.temporalVector = new Vector3(0, 0, 0);
        this.tickRate = 1;

        this.skyLightSubtracted = this.calculateSkylightSubtracted(1);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static boolean canRandomTick(int blockId) {
        return blockId < randomTickBlocks.size() && randomTickBlocks.contains(blockId);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static void setCanRandomTick(int blockId, boolean newValue) {
        if (newValue) {
            randomTickBlocks.add(blockId);
        } else {
            randomTickBlocks.remove(blockId);
        }
    }

    public static long chunkHash(int x, int z) {
        return (((long) x) << 32) | (z & 0xffffffffL);
    }

    @Deprecated(since = "1.6.0.0-PNX")
    @Since("1.6.0.0-PNX")
    public static long blockHash(int x, int y, int z) {
        if (y < 0 || y >= 256) {
            throw new IllegalArgumentException("Y coordinate y is out of range!");
        }
        return (((long) x & (long) 0xFFFFFFF) << 36) | (((long) y & (long) 0xFF) << 28) | ((long) z & (long) 0xFFFFFFF);
    }

    @PowerNukkitXOnly
    @Since("1.6.0.0-PNX")
    public static long blockHash(int x, int y, int z, Level level) {
        if (!level.isYInRange(y)) {
            throw new IllegalArgumentException("Y coordinate y is out of range!");
        }
        return (((long) x & (long) 0b111111111111111111111111111) << 37) | ((long) (level.ensureY(y) + 64) << 28) | ((long) z & (long) 0xFFFFFFF);
    }

    public static int localBlockHash(double x, double y, double z, Level level) {
        byte hi = (byte) (((int) x & 15) + (((int) z & 15) << 4));
        short lo = (short) (level.ensureY((int) y) + 64);
        return (hi & 0xFF) << 16 | lo;
    }

    @PowerNukkitXOnly
    @Since("1.6.0.0-PNX")
    public static int localBlockHash(int x, int y, int z, int layer, Level level) {
        byte hi = (byte) ((x & 15) + ((z & 15) << 4));
        short lo = (short) (level.ensureY(y) + 64);
        return ((layer & 127) << 24) | (hi & 0xFF) << 16 | lo;
    }

    public static Vector3 getBlockXYZ(long chunkHash, int blockHash, Level level) {
        int hi = (byte) (blockHash >>> 16);
        int lo = (short) blockHash;
        int y = level.ensureY(lo - 64);
        int x = (hi & 0xF) + (getHashX(chunkHash) << 4);
        int z = ((hi >> 4) & 0xF) + (getHashZ(chunkHash) << 4);
        return new Vector3(x, y, z);
    }

    @PowerNukkitXOnly
    @Since("1.6.0.0-PNX")
    public static int chunkBlockHash(int x, int y, int z) {
        return (x << 13) | (z << 9) | (y + 64); // 为适配384世界,y需要额外的1bit来存储
    }

    /**
     * 获取chunkX从chunk hash
     * 

* Get chunkX from chunk hash * * @param hash the hash * @return the hash x */ public static int getHashX(long hash) { return (int) (hash >> 32); } /** * 获取chunkZ从chunk hash *

* Get chunkZ from chunk hash * * @param hash the hash * @return the hash x */ public static int getHashZ(long hash) { return (int) hash; } public static Vector3 getBlockXYZ(BlockVector3 hash) { return new Vector3(hash.x, hash.y, hash.z); } public static Chunk.Entry getChunkXZ(long hash) { return new Chunk.Entry(getHashX(hash), getHashZ(hash)); } public static int generateChunkLoaderId(ChunkLoader loader) { if (loader.getLoaderId() == 0) { return chunkLoaderCounter++; } else { throw new IllegalStateException("ChunkLoader has a loader id already assigned: " + loader.getLoaderId()); } } @PowerNukkitXOnly @Since("1.19.21-r1") public synchronized static void addAntiXrayTransparentBlock(@NotNull Block block) { transparentBlockRuntimeIds.add(block.getRuntimeId()); } @PowerNukkitXOnly @Since("1.19.21-r1") public static IntSet getRawTransparentBlockRuntimeIds() { return transparentBlockRuntimeIds; } public int getTickRate() { return tickRate; } public void setTickRate(int tickRate) { this.tickRate = tickRate; } public int getTickRateTime() { return tickRateTime; } public int recalcTickOptDelay() { if (tickRateTime > 40) { return Math.min(tickRateOptDelay << 1, 8); } else if (tickRateOptDelay == 1) { return 1; } else { return tickRateOptDelay >> 1; } } @PowerNukkitXOnly @Since("1.19.21-r1") public boolean isAntiXrayEnabled() { return antiXrayEnabled; } @PowerNukkitXOnly @Since("1.19.21-r1") public void setAntiXrayEnabled(boolean antiXrayEnabled) { this.antiXrayEnabled = antiXrayEnabled; } @PowerNukkitXOnly @Since("1.19.21-r1") public int getFakeOreDenominator() { return fakeOreDenominator; } @PowerNukkitXOnly @Since("1.19.21-r1") public void setFakeOreDenominator(int fakeOreDenominator) { this.fakeOreDenominator = fakeOreDenominator; } @PowerNukkitXOnly @Since("1.19.21-r1") public boolean isPreDeObfuscate() { return preDeObfuscate; } @PowerNukkitXOnly @Since("1.19.21-r1") public void setPreDeObfuscate(boolean preDeObfuscate) { this.preDeObfuscate = preDeObfuscate; } @PowerNukkitXOnly @Since("1.19.21-r1") public void addAntiXrayOreBlock(@NotNull Block oreBlock, @NotNull Block replaceWith) { this.realOreToReplacedRuntimeIds.put(oreBlock.getRuntimeId(), replaceWith.getRuntimeId()); } @PowerNukkitXOnly @Since("1.19.21-r1") public void removeAntiXrayOreBlock(@NotNull Block oreBlock, @NotNull Block replaceWith) { this.realOreToReplacedRuntimeIds.remove(oreBlock.getRuntimeId(), replaceWith.getRuntimeId()); } @PowerNukkitXInternal @PowerNukkitXOnly @Since("1.19.21-r1") public Int2IntMap getRawRealOreToReplacedRuntimeIdMap() { return this.realOreToReplacedRuntimeIds; } @PowerNukkitXOnly @Since("1.19.21-r1") public void addAntiXrayFakeBlock(@NotNull Block originBlock, @NotNull Collection fakeBlocks) { var rid = originBlock.getRuntimeId(); var list = this.fakeOreToPutRuntimeIds.get(rid); if (list == null) { this.fakeOreToPutRuntimeIds.put(rid, list = new IntArrayList(8)); } for (var each : fakeBlocks) { list.add(each.getRuntimeId()); } } @PowerNukkitXOnly @Since("1.19.21-r1") public void removeAntiXrayFakeBlock(@NotNull Block originBlock, @NotNull Collection fakeBlocks) { var rid = originBlock.getRuntimeId(); var list = this.fakeOreToPutRuntimeIds.get(rid); if (list != null) { for (var each : fakeBlocks) { var tmp = each.getRuntimeId(); list.removeIf(i -> i == tmp); } } } @PowerNukkitXInternal @PowerNukkitXOnly @Since("1.19.21-r1") public Int2ObjectMap getRawFakeOreToPutRuntimeIdMap() { return this.fakeOreToPutRuntimeIds; } @PowerNukkitXOnly @Since("1.19.21-r1") public void reinitAntiXray(boolean global) { var stone = new BlockStone(); var netherRack = new BlockNetherrack(); var deepSlate = new BlockDeepslate(); { getRawFakeOreToPutRuntimeIdMap().clear(); getRawRealOreToReplacedRuntimeIdMap().clear(); } { addAntiXrayOreBlock(new BlockOreCoal(), stone); addAntiXrayOreBlock(new BlockOreDiamond(), stone); addAntiXrayOreBlock(new BlockOreEmerald(), stone); addAntiXrayOreBlock(new BlockOreGold(), stone); addAntiXrayOreBlock(new BlockOreIron(), stone); addAntiXrayOreBlock(new BlockOreLapis(), stone); addAntiXrayOreBlock(new BlockOreRedstone(), stone); addAntiXrayOreBlock(new BlockOreQuartz(), netherRack); addAntiXrayOreBlock(new BlockOreGoldNether(), netherRack); addAntiXrayOreBlock(new BlockAncientDebris(), netherRack); addAntiXrayOreBlock(new BlockOreCoalDeepslate(), deepSlate); addAntiXrayOreBlock(new BlockOreDiamondDeepslate(), deepSlate); addAntiXrayOreBlock(new BlockOreEmeraldDeepslate(), deepSlate); addAntiXrayOreBlock(new BlockOreGoldDeepslate(), deepSlate); addAntiXrayOreBlock(new BlockOreIronDeepslate(), deepSlate); addAntiXrayOreBlock(new BlockOreLapisDeepslate(), deepSlate); addAntiXrayOreBlock(new BlockOreRedstoneDeepslate(), deepSlate); } { addAntiXrayFakeBlock(stone, List.of(new BlockOreCoal(), new BlockOreDiamond(), new BlockOreEmerald(), new BlockOreGold(), new BlockOreIron(), new BlockOreLapis(), new BlockOreRedstone())); addAntiXrayFakeBlock(netherRack, List.of(new BlockOreQuartz(), new BlockOreGoldNether(), new BlockAncientDebris())); addAntiXrayFakeBlock(deepSlate, List.of(new BlockOreCoalDeepslate(), new BlockOreDiamondDeepslate(), new BlockOreEmeraldDeepslate(), new BlockOreGoldDeepslate(), new BlockOreIronDeepslate(), new BlockOreLapisDeepslate(), new BlockOreRedstoneDeepslate())); } if (global || transparentBlockRuntimeIds.isEmpty()) { transparentBlockRuntimeIds.clear(); for (var each : BlockStateRegistry.getPersistenceNames()) { try { var block = BlockState.of(each); if (BlockState.of(each).getBlock().isTransparent()) { transparentBlockRuntimeIds.add(block.getRuntimeId()); } } catch (Exception ignore) { } } ((IntOpenHashSet) transparentBlockRuntimeIds).trim(); } } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public boolean isHighLightChunk(int chunkX, int chunkZ) { return highLightChunks.contains(Level.chunkHash(chunkX, chunkZ)); } @PowerNukkitXOnly @Since("1.19.20-r3") public void initLevel() { this.initLevel(null); } @PowerNukkitXDifference(since = "1.19.20-r3") public void initLevel(@Nullable DimensionData givenDimensionData) { Generator generator = generators.get(); if (dimensionData == null || givenDimensionData != null) { this.dimensionData = givenDimensionData == null ? generator.getDimensionData() : givenDimensionData; if (this.requireProvider() instanceof DimensionDataProvider dimensionDataProvider) { dimensionDataProvider.setDimensionData(dimensionData); } } this.gameRules = this.requireProvider().getGamerules(); log.info("Preparing start region for level \"{}\"", this.getFolderName()); Position spawn = this.getSpawnLocation(); this.populateChunk(spawn.getChunkX(), spawn.getChunkZ(), true); } public Generator getGenerator() { return generators.get(); } public BlockMetadataStore getBlockMetadata() { return this.blockMetadata; } public Server getServer() { return server; } public final LevelProvider getProvider() { return provider; } /** * Returns the level provider if it exists. Tries to close and unregister the level and then throw an exception if it doesn't. * * @throws LevelException If the level is already closed */ @PowerNukkitOnly @Since("1.4.0.0-PN") @NotNull public final LevelProvider requireProvider() { LevelProvider levelProvider = getProvider(); if (levelProvider == null) { LevelException levelException = new LevelException("The level \"" + getFolderName() + "\" is already closed (have no providers)"); try { close(); } catch (Exception e) { levelException.addSuppressed(e); } throw levelException; } return levelProvider; } public final int getId() { return this.levelId; } public void close() { LevelProvider levelProvider = this.provider; if (levelProvider != null) { if (this.getAutoSave()) { this.save(true); } levelProvider.close(); } this.provider = null; this.blockMetadata = null; this.temporalPosition = null; this.server.getLevels().remove(this.levelId); this.generators.remove(); } public void addSound(Vector3 pos, Sound sound) { this.addSound(pos, sound, 1, 1, (Player[]) null); } public void addSound(Vector3 pos, Sound sound, float volume, float pitch) { this.addSound(pos, sound, volume, pitch, (Player[]) null); } public void addSound(Vector3 pos, Sound sound, float volume, float pitch, Collection players) { this.addSound(pos, sound, volume, pitch, players.toArray(Player.EMPTY_ARRAY)); } public void addSound(Vector3 pos, Sound sound, float volume, float pitch, Player... players) { Preconditions.checkArgument(volume >= 0 && volume <= 1, "Sound volume must be between 0 and 1"); Preconditions.checkArgument(pitch >= 0, "Sound pitch must be higher than 0"); PlaySoundPacket packet = new PlaySoundPacket(); packet.name = sound.getSound(); packet.volume = volume; packet.pitch = pitch; packet.x = pos.getFloorX(); packet.y = pos.getFloorY(); packet.z = pos.getFloorZ(); if (players == null || players.length == 0) { addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, packet); } else { Server.broadcastPacket(players, packet); } } @PowerNukkitOnly public void addLevelEvent(int type, int data) { addLevelEvent(type, data, null); } @PowerNukkitOnly public void addLevelEvent(int type, int data, Vector3 pos) { if (pos == null) { addLevelEvent(type, data, 0, 0, 0); } else { addLevelEvent(type, data, (float) pos.x, (float) pos.y, (float) pos.z); } } @PowerNukkitOnly public void addLevelEvent(int type, int data, float x, float y, float z) { LevelEventPacket packet = new LevelEventPacket(); packet.evid = type; packet.x = x; packet.y = y; packet.z = z; packet.data = data; this.addChunkPacket(NukkitMath.floorFloat(x) >> 4, NukkitMath.floorFloat(z) >> 4, packet); } public void addLevelEvent(Vector3 pos, int event) { this.addLevelEvent(pos, event, 0); } @Since("1.4.0.0-PN") public void addLevelEvent(Vector3 pos, int event, int data) { LevelEventPacket pk = new LevelEventPacket(); pk.evid = event; pk.x = (float) pos.x; pk.y = (float) pos.y; pk.z = (float) pos.z; pk.data = data; addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); } @PowerNukkitXOnly @Since("1.19.60-r1") public void addLevelEvent(Vector3 pos, int event, CompoundTag data) { LevelEventGenericPacket pk = new LevelEventGenericPacket(); pk.eventId = event; pk.tag = data; this.addChunkPacket(pos.getChunkX(), pos.getChunkZ(), pk); } public void addLevelSoundEvent(Vector3 pos, int type, int data, int entityType) { addLevelSoundEvent(pos, type, data, entityType, false, false); } public void addLevelSoundEvent(Vector3 pos, int type, int data, int entityType, boolean isBaby, boolean isGlobal) { String identifier = AddEntityPacket.LEGACY_IDS.getOrDefault(entityType, ":"); addLevelSoundEvent(pos, type, data, identifier, isBaby, isGlobal); } public void addLevelSoundEvent(Vector3 pos, int type) { this.addLevelSoundEvent(pos, type, -1); } /** * Broadcasts sound to players * * @param pos position where sound should be played * @param type ID of the sound from {@link cn.nukkit.network.protocol.LevelSoundEventPacket} * @param data generic data that can affect sound */ public void addLevelSoundEvent(Vector3 pos, int type, int data) { this.addLevelSoundEvent(pos, type, data, ":", false, false); } /** * Broadcasts a LevelSound to players,use LevelSoundEventPacket * * @param pos the pos * @param type the sound type id,get from{@link LevelSoundEventPacket} * @param data the extra data,default -1 * @param identifier the identifier,default ":" * @param isBaby the is baby,default false * @param isGlobal the is global,default false */ public void addLevelSoundEvent(Vector3 pos, int type, int data, String identifier, boolean isBaby, boolean isGlobal) { LevelSoundEventPacket pk = new LevelSoundEventPacket(); pk.sound = type; pk.extraData = data; pk.entityIdentifier = identifier; pk.x = (float) pos.x; pk.y = (float) pos.y; pk.z = (float) pos.z; pk.isBabyMob = isBaby; pk.isGlobal = isGlobal; this.addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); } public void addParticle(Particle particle) { this.addParticle(particle, (Player[]) null); } public void addParticle(Particle particle, Player player) { this.addParticle(particle, new Player[]{player}); } public void addParticle(Particle particle, Player[] players) { DataPacket[] packets = particle.encode(); if (players == null) { if (packets != null) { for (DataPacket packet : packets) { this.addChunkPacket((int) particle.x >> 4, (int) particle.z >> 4, packet); } } } else { if (packets != null) { if (packets.length == 1) { Server.broadcastPacket(players, packets[0]); } else { this.server.batchPackets(players, packets, false); } } } } public void addParticle(Particle particle, Collection players) { this.addParticle(particle, players.toArray(Player.EMPTY_ARRAY)); } public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect) { this.addParticleEffect(pos, particleEffect, -1, this.getDimension(), (Player[]) null); } public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect, long uniqueEntityId) { this.addParticleEffect(pos, particleEffect, uniqueEntityId, this.getDimension(), (Player[]) null); } public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect, long uniqueEntityId, int dimensionId) { this.addParticleEffect(pos, particleEffect, uniqueEntityId, dimensionId, (Player[]) null); } public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect, long uniqueEntityId, int dimensionId, Collection players) { this.addParticleEffect(pos, particleEffect, uniqueEntityId, dimensionId, players.toArray(Player.EMPTY_ARRAY)); } public void addParticleEffect(Vector3 pos, ParticleEffect particleEffect, long uniqueEntityId, int dimensionId, Player... players) { this.addParticleEffect(pos.asVector3f(), particleEffect.getIdentifier(), uniqueEntityId, dimensionId, players); } public void addParticleEffect(Vector3f pos, String identifier, long uniqueEntityId, int dimensionId, Player... players) { SpawnParticleEffectPacket pk = new SpawnParticleEffectPacket(); pk.identifier = identifier; pk.uniqueEntityId = uniqueEntityId; pk.dimensionId = dimensionId; pk.position = pos; if (players == null || players.length == 0) { addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); } else { Server.broadcastPacket(players, pk); } } public boolean getAutoSave() { return this.autoSave; } public void setAutoSave(boolean autoSave) { this.autoSave = autoSave; } public boolean unload() { return this.unload(false); } public boolean unload(boolean force) { LevelUnloadEvent ev = new LevelUnloadEvent(this); if (this == this.server.getDefaultLevel() && !force) { ev.setCancelled(); } this.server.getPluginManager().callEvent(ev); if (!force && ev.isCancelled()) { return false; } log.info(this.server.getLanguage().tr("nukkit.level.unloading", TextFormat.GREEN + this.getName() + TextFormat.WHITE)); Level defaultLevel = this.server.getDefaultLevel(); for (Player player : this.getPlayers().values().toArray(Player.EMPTY_ARRAY)) { if (this == defaultLevel || defaultLevel == null) { player.close(player.getLeaveMessage(), "Forced default level unload"); } else { player.teleport(this.server.getDefaultLevel().getSafeSpawn()); } } if (this == defaultLevel) { this.server.setDefaultLevel(null); } this.close(); return true; } public Map getChunkPlayers(int chunkX, int chunkZ) { long index = Level.chunkHash(chunkX, chunkZ); if (this.playerLoaders.containsKey(index)) { return new HashMap<>(this.playerLoaders.get(index)); } else { return new HashMap<>(); } } public ChunkLoader[] getChunkLoaders(int chunkX, int chunkZ) { long index = Level.chunkHash(chunkX, chunkZ); if (this.chunkLoaders.containsKey(index)) { return this.chunkLoaders.get(index).values().toArray(ChunkLoader.EMPTY_ARRAY); } else { return ChunkLoader.EMPTY_ARRAY; } } public void addChunkPacket(int chunkX, int chunkZ, DataPacket packet) { long index = Level.chunkHash(chunkX, chunkZ); synchronized (chunkPackets) { Deque packets = chunkPackets.computeIfAbsent(index, i -> new ArrayDeque<>()); packets.add(packet); } } public void registerChunkLoader(ChunkLoader loader, int chunkX, int chunkZ) { this.registerChunkLoader(loader, chunkX, chunkZ, true); } public void registerChunkLoader(ChunkLoader loader, int chunkX, int chunkZ, boolean autoLoad) { int hash = loader.getLoaderId(); long index = Level.chunkHash(chunkX, chunkZ); if (!this.chunkLoaders.containsKey(index)) { this.chunkLoaders.put(index, new HashMap<>()); this.playerLoaders.put(index, new HashMap<>()); } else if (this.chunkLoaders.get(index).containsKey(hash)) { return; } this.chunkLoaders.get(index).put(hash, loader); if (loader instanceof Player) { this.playerLoaders.get(index).put(hash, (Player) loader); } if (!this.loaders.containsKey(hash)) { this.loaderCounter.put(hash, 1); this.loaders.put(hash, loader); } else { this.loaderCounter.put(hash, this.loaderCounter.get(hash) + 1); } this.cancelUnloadChunkRequest(hash); if (autoLoad) { this.loadChunk(chunkX, chunkZ); } } public void unregisterChunkLoader(ChunkLoader loader, int chunkX, int chunkZ) { int hash = loader.getLoaderId(); long index = Level.chunkHash(chunkX, chunkZ); Map chunkLoadersIndex = this.chunkLoaders.get(index); if (chunkLoadersIndex != null) { ChunkLoader oldLoader = chunkLoadersIndex.remove(hash); if (oldLoader != null) { if (chunkLoadersIndex.isEmpty()) { this.chunkLoaders.remove(index); this.playerLoaders.remove(index); this.unloadChunkRequest(chunkX, chunkZ, true); } else { Map playerLoadersIndex = this.playerLoaders.get(index); playerLoadersIndex.remove(hash); } int count = this.loaderCounter.get(hash); if (--count == 0) { this.loaderCounter.remove(hash); this.loaders.remove(hash); } else { this.loaderCounter.put(hash, count); } } } } public void checkTime() { if (!this.stopTime && this.gameRules.getBoolean(GameRule.DO_DAYLIGHT_CYCLE)) { this.time += tickRate; } } public void sendTime(Player... players) { SetTimePacket pk = new SetTimePacket(); pk.time = (int) this.time; Server.broadcastPacket(players, pk); } public void sendTime() { this.sendTime(this.players.values().toArray(Player.EMPTY_ARRAY)); } public GameRules getGameRules() { return gameRules; } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public void releaseTickCachedBlocks() { synchronized (this.tickCachedBlocks) { for (var each : tickCachedBlocks.values()) { each.clearCachedStore(); } } } public void doTick(int currentTick) { this.timings.doTick.startTiming(); requireProvider(); try { updateBlockLight(lightQueue); this.checkTime(); if (currentTick >= nextTimeSendTick) { // Send time to client every 30 seconds to make sure it this.sendTime(); nextTimeSendTick = currentTick + 30 * 20; } // 检查突出区块(玩家附近3x3区块) if ((currentTick & 127) == 0) { // 每127刻检查一次是比较合理的 highLightChunks.clear(); for (var player : this.players.values()) { if (player.isOnline()) { int chunkX = player.getChunkX(); int chunkZ = player.getChunkZ(); for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { highLightChunks.add(Level.chunkHash(chunkX + dx, chunkZ + dz)); } } } } } // Tick Weather if (this.getDimension() != DIMENSION_NETHER && this.getDimension() != DIMENSION_THE_END && gameRules.getBoolean(GameRule.DO_WEATHER_CYCLE)) { this.rainTime--; if (this.rainTime <= 0) { if (!this.setRaining(!this.raining)) { if (this.raining) { setRainTime(ThreadLocalRandom.current().nextInt(12000) + 12000); } else { setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } } } this.thunderTime--; if (this.thunderTime <= 0) { if (!this.setThundering(!this.thundering)) { if (this.thundering) { setThunderTime(ThreadLocalRandom.current().nextInt(12000) + 3600); } else { setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } } } if (this.isThundering()) { Map chunks = getChunks(); if (chunks instanceof Long2ObjectOpenHashMap) { Long2ObjectOpenHashMap fastChunks = (Long2ObjectOpenHashMap) chunks; ObjectIterator> iter = fastChunks.long2ObjectEntrySet().fastIterator(); while (iter.hasNext()) { Long2ObjectMap.Entry entry = iter.next(); performThunder(entry.getLongKey(), entry.getValue()); } } else { for (Map.Entry entry : getChunks().entrySet()) { performThunder(entry.getKey(), entry.getValue()); } } } } this.skyLightSubtracted = this.calculateSkylightSubtracted(1); this.levelCurrentTick++; this.timings.doTickPending.startTiming(); this.updateQueue.tick(this.getCurrentTick()); this.timings.doTickPending.stopTiming(); while (!this.normalUpdateQueue.isEmpty()) { QueuedUpdate queuedUpdate = this.normalUpdateQueue.poll(); Block block = getBlock(queuedUpdate.block, queuedUpdate.block.layer); BlockUpdateEvent event = new BlockUpdateEvent(block); this.server.getPluginManager().callEvent(event); if (!event.isCancelled()) { block.onUpdate(BLOCK_UPDATE_NORMAL); if (queuedUpdate.neighbor != null) { block.onNeighborChange(queuedUpdate.neighbor.getOpposite()); } } } TimingsHistory.entityTicks += this.updateEntities.size(); this.timings.entityTick.startTiming(); if (!this.updateEntities.isEmpty()) { CompletableFuture.runAsync(() -> updateEntities.keySet() .longParallelStream().mapToObj(id -> { var entity = this.updateEntities.get(id); if (entity instanceof EntityAsyncPrepare entityAsyncPrepare) { return entityAsyncPrepare; } else { return null; } }).forEach(entityAsyncPrepare -> { if (entityAsyncPrepare != null) entityAsyncPrepare.asyncPrepare(currentTick); }), Server.getInstance().computeThreadPool).join(); for (long id : new ArrayList<>(this.updateEntities.keySet())) { Entity entity = this.updateEntities.get(id); if (entity == null) { this.updateEntities.remove(id); continue; } if (entity.closed || !entity.onUpdate(currentTick)) { this.updateEntities.remove(id); } } } this.timings.entityTick.stopTiming(); TimingsHistory.tileEntityTicks += this.updateBlockEntities.size(); this.timings.blockEntityTick.startTiming(); this.updateBlockEntities.removeIf(blockEntity -> !blockEntity.isValid() || !blockEntity.onUpdate()); this.timings.blockEntityTick.stopTiming(); this.timings.tickChunks.startTiming(); this.tickChunks(); this.timings.tickChunks.stopTiming(); synchronized (changedBlocks) { if (!this.changedBlocks.isEmpty()) { if (!this.players.isEmpty()) { var iter = changedBlocks.long2ObjectEntrySet().fastIterator(); while (iter.hasNext()) { var entry = iter.next(); long index = entry.getLongKey(); var blocks = entry.getValue().get(); int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); if (blocks == null || blocks.size() > MAX_BLOCK_CACHE) { FullChunk chunk = this.getChunk(chunkX, chunkZ); chunk.reObfuscateChunk(); for (Player p : this.getChunkPlayers(chunkX, chunkZ).values()) { p.onChunkChanged(chunk); } } else { Collection toSend = this.getChunkPlayers(chunkX, chunkZ).values(); Player[] playerArray = toSend.toArray(Player.EMPTY_ARRAY); var size = blocks.size(); if (antiXrayEnabled) { var vectorSet = new IntOpenHashSet(size * 6); var vRidList = new ArrayList(size * 7); Vector3WithRuntimeId tmpV3Rid; for (int blockHash : blocks.keySet()) { Vector3 hash = getBlockXYZ(index, blockHash, this); var x = hash.getFloorX(); var y = hash.getFloorY(); var z = hash.getFloorZ(); if (!vectorSet.contains(blockHash)) { vectorSet.add(blockHash); try { tmpV3Rid = new Vector3WithRuntimeId(x, y, z, getBlockRuntimeId(x, y, z, 0), getBlockRuntimeId(x, y, z, 1)); vRidList.add(tmpV3Rid); if (!transparentBlockRuntimeIds.contains(tmpV3Rid.getRuntimeIdLayer0())) { continue; } } catch (Exception ignore) { } } x++; blockHash = localBlockHash(x, y, z, 0, this); if (!vectorSet.contains(blockHash)) { vectorSet.add(blockHash); try { vRidList.add(new Vector3WithRuntimeId(x, y, z, getBlockRuntimeId(x, y, z, 0), getBlockRuntimeId(x, y, z, 1))); } catch (Exception ignore) { } } x -= 2; blockHash = localBlockHash(x, y, z, 0, this); if (!vectorSet.contains(blockHash)) { vectorSet.add(blockHash); try { vRidList.add(new Vector3WithRuntimeId(x, y, z, getBlockRuntimeId(x, y, z, 0), getBlockRuntimeId(x, y, z, 1))); } catch (Exception ignore) { } } x++; y++; blockHash = localBlockHash(x, y, z, 0, this); if (!vectorSet.contains(blockHash)) { vectorSet.add(blockHash); try { vRidList.add(new Vector3WithRuntimeId(x, y, z, getBlockRuntimeId(x, y, z, 0), getBlockRuntimeId(x, y, z, 1))); } catch (Exception ignore) { } } y -= 2; blockHash = localBlockHash(x, y, z, 0, this); if (!vectorSet.contains(blockHash)) { vectorSet.add(blockHash); try { vRidList.add(new Vector3WithRuntimeId(x, y, z, getBlockRuntimeId(x, y, z, 0), getBlockRuntimeId(x, y, z, 1))); } catch (Exception ignore) { } } y++; z++; blockHash = localBlockHash(x, y, z, 0, this); if (!vectorSet.contains(blockHash)) { vectorSet.add(blockHash); try { vRidList.add(new Vector3WithRuntimeId(x, y, z, getBlockRuntimeId(x, y, z, 0), getBlockRuntimeId(x, y, z, 1))); } catch (Exception ignore) { } } z -= 2; blockHash = localBlockHash(x, y, z, 0, this); if (!vectorSet.contains(blockHash)) { vectorSet.add(blockHash); try { vRidList.add(new Vector3WithRuntimeId(x, y, z, getBlockRuntimeId(x, y, z, 0), getBlockRuntimeId(x, y, z, 1))); } catch (Exception ignore) { } } } this.sendBlocks(playerArray, vRidList.toArray(Vector3[]::new), UpdateBlockPacket.FLAG_ALL); } else { var blocksArray = new Vector3[size]; int i = 0; for (int blockHash : blocks.keySet()) { Vector3 hash = getBlockXYZ(index, blockHash, this); blocksArray[i++] = hash; } this.sendBlocks(playerArray, blocksArray, UpdateBlockPacket.FLAG_ALL); } } } } this.changedBlocks.clear(); } } this.processChunkRequest(); if (this.sleepTicks > 0 && --this.sleepTicks <= 0) { this.checkSleep(); } synchronized (chunkPackets) { for (long index : this.chunkPackets.keySet()) { int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); Player[] chunkPlayers = this.getChunkPlayers(chunkX, chunkZ).values().toArray(Player.EMPTY_ARRAY); if (chunkPlayers.length > 0) { for (DataPacket pk : this.chunkPackets.get(index)) { Server.broadcastPacket(chunkPlayers, pk); } } } this.chunkPackets.clear(); } if (gameRules.isStale()) { GameRulesChangedPacket packet = new GameRulesChangedPacket(); packet.gameRules = gameRules; Server.broadcastPacket(players.values().toArray(Player.EMPTY_ARRAY), packet); gameRules.refresh(); } } finally { // 清除所有tick缓存的方块 releaseTickCachedBlocks(); } this.timings.doTick.stopTiming(); } private void performThunder(long index, FullChunk chunk) { if (areNeighboringChunksLoaded(index)) return; if (ThreadLocalRandom.current().nextInt(10000) == 0) { int LCG = this.getUpdateLCG() >> 2; int chunkX = chunk.getX() * 16; int chunkZ = chunk.getZ() * 16; Vector3 vector = this.adjustPosToNearbyEntity(new Vector3(chunkX + (LCG & 0xf), 0, chunkZ + (LCG >> 8 & 0xf))); Biome biome = Biome.getBiome(this.getBiomeId(vector.getFloorX(), vector.getFloorZ())); if (!biome.canRain()) { return; } int bId = this.getBlockIdAt(vector.getFloorX(), vector.getFloorY(), vector.getFloorZ()); if (bId != Block.TALL_GRASS && bId != Block.FLOWING_WATER) vector.y += 1; CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos").add(new DoubleTag("", vector.x)) .add(new DoubleTag("", vector.y)).add(new DoubleTag("", vector.z))) .putList(new ListTag("Motion").add(new DoubleTag("", 0)) .add(new DoubleTag("", 0)).add(new DoubleTag("", 0))) .putList(new ListTag("Rotation").add(new FloatTag("", 0)) .add(new FloatTag("", 0))); EntityLightning bolt = new EntityLightning(chunk, nbt); LightningStrikeEvent ev = new LightningStrikeEvent(this, bolt); getServer().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { bolt.spawnToAll(); } else { bolt.setEffect(false); } this.addLevelSoundEvent(vector, LevelSoundEventPacket.SOUND_THUNDER, -1, EntityLightning.NETWORK_ID); this.addLevelSoundEvent(vector, LevelSoundEventPacket.SOUND_EXPLODE, -1, EntityLightning.NETWORK_ID); } } public Vector3 adjustPosToNearbyEntity(Vector3 pos) { pos.y = this.getHighestBlockAt(pos.getFloorX(), pos.getFloorZ()); AxisAlignedBB axisalignedbb = new SimpleAxisAlignedBB(pos.x, pos.y, pos.z, pos.getX(), isOverWorld() ? 320 : 255, pos.getZ()).expand(3, 3, 3); List list = new ArrayList<>(); for (Entity entity : this.getCollidingEntities(axisalignedbb)) { if (entity.isAlive() && canBlockSeeSky(entity)) { list.add(entity); } } if (!list.isEmpty()) { return list.get(ThreadLocalRandom.current().nextInt(list.size())).getPosition(); } else { if (pos.getY() == -1) { pos = pos.up(2); } return pos; } } public void checkSleep() { if (this.players.isEmpty()) { return; } boolean resetTime = true; for (Player p : this.getPlayers().values()) { if (!p.isSleeping()) { resetTime = false; break; } } if (resetTime) { int time = this.getTime() % Level.TIME_FULL; if (time >= Level.TIME_NIGHT && time < Level.TIME_SUNRISE) { this.setTime(this.getTime() + Level.TIME_FULL - time); for (Player p : this.getPlayers().values()) { p.stopSleep(); } } } } public void sendBlockExtraData(int x, int y, int z, int id, int data) { this.sendBlockExtraData(x, y, z, id, data, this.getChunkPlayers(x >> 4, z >> 4).values()); } public void sendBlockExtraData(int x, int y, int z, int id, int data, Collection players) { sendBlockExtraData(x, y, z, id, data, players.toArray(Player.EMPTY_ARRAY)); } public void sendBlockExtraData(int x, int y, int z, int id, int data, Player[] players) { LevelEventPacket pk = new LevelEventPacket(); pk.evid = LevelEventPacket.EVENT_SET_DATA; pk.x = x + 0.5f; pk.y = y + 0.5f; pk.z = z + 0.5f; pk.data = (data << 8) | id; Server.broadcastPacket(players, pk); } public void sendBlocks(Player[] target, Vector3[] blocks) { this.sendBlocks(target, blocks, UpdateBlockPacket.FLAG_NONE, 0); this.sendBlocks(target, blocks, UpdateBlockPacket.FLAG_NONE, 1); } public void sendBlocks(Player[] target, Vector3[] blocks, int flags) { this.sendBlocks(target, blocks, flags, 0); this.sendBlocks(target, blocks, flags, 1); } public void sendBlocks(Player[] target, Vector3[] blocks, int flags, boolean optimizeRebuilds) { this.sendBlocks(target, blocks, flags, 0, optimizeRebuilds); this.sendBlocks(target, blocks, flags, 1, optimizeRebuilds); } public void sendBlocks(Player[] target, Vector3[] blocks, int flags, int dataLayer) { this.sendBlocks(target, blocks, flags, dataLayer, false); } public void sendBlocks(Player[] target, Vector3[] blocks, int flags, int dataLayer, boolean optimizeRebuilds) { int size = 0; for (Vector3 block : blocks) { if (block != null) size++; } int packetIndex = 0; var packets = new ArrayList(size); LongSet chunks = null; if (optimizeRebuilds) { chunks = new LongOpenHashSet(); } for (Vector3 b : blocks) { if (b == null) { continue; } boolean first = !optimizeRebuilds; if (optimizeRebuilds) { long index = Level.chunkHash((int) b.x >> 4, (int) b.z >> 4); if (!chunks.contains(index)) { chunks.add(index); first = true; } } UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.x = (int) b.x; updateBlockPacket.y = (int) b.y; updateBlockPacket.z = (int) b.z; updateBlockPacket.flags = first ? flags : UpdateBlockPacket.FLAG_NONE; updateBlockPacket.dataLayer = dataLayer; int runtimeId; if (b instanceof Block) { runtimeId = ((Block) b).getRuntimeId(); } else if (b instanceof Vector3WithRuntimeId vRid) { if (dataLayer == 0) { runtimeId = vRid.getRuntimeIdLayer0(); } else { runtimeId = vRid.getRuntimeIdLayer1(); } } else { runtimeId = getBlockRuntimeId((int) b.x, (int) b.y, (int) b.z, dataLayer); if (runtimeId == Integer.MIN_VALUE) { continue; } } try { updateBlockPacket.blockRuntimeId = runtimeId; } catch (NoSuchElementException e) { throw new IllegalStateException("Unable to create BlockUpdatePacket at (" + b.x + ", " + b.y + ", " + b.z + ") in " + getName(), e); } packets.add(updateBlockPacket); } this.server.batchPackets(target, packets.toArray(DataPacket.EMPTY_ARRAY)); } private void tickChunks() { if (this.chunksPerTicks <= 0 || this.loaders.isEmpty()) { this.chunkTickList.clear(); return; } int chunksPerLoader = Math.min(200, Math.max(1, (int) (((double) (this.chunksPerTicks - this.loaders.size()) / this.loaders.size() + 0.5)))); int randRange = 3 + chunksPerLoader / 30; randRange = Math.min(randRange, this.chunkTickRadius); ThreadLocalRandom random = ThreadLocalRandom.current(); if (!this.loaders.isEmpty()) { for (ChunkLoader loader : this.loaders.values()) { int chunkX = (int) loader.getX() >> 4; int chunkZ = (int) loader.getZ() >> 4; long index = Level.chunkHash(chunkX, chunkZ); int existingLoaders = Math.max(0, this.chunkTickList.getOrDefault(index, 0)); this.chunkTickList.put(index, existingLoaders + 1); for (int chunk = 0; chunk < chunksPerLoader; ++chunk) { int dx = random.nextInt(2 * randRange) - randRange; int dz = random.nextInt(2 * randRange) - randRange; long hash = Level.chunkHash(dx + chunkX, dz + chunkZ); if (!this.chunkTickList.containsKey(hash) && requireProvider().isChunkLoaded(hash)) { this.chunkTickList.put(hash, -1); } } } } boolean blockTest = false; if (!chunkTickList.isEmpty()) { ObjectIterator iter = chunkTickList.long2IntEntrySet().iterator(); while (iter.hasNext()) { Long2IntMap.Entry entry = iter.next(); long index = entry.getLongKey(); if (!areNeighboringChunksLoaded(index)) { iter.remove(); continue; } int loaders = entry.getIntValue(); int chunkX = getHashX(index); int chunkZ = getHashZ(index); FullChunk chunk; if ((chunk = this.getChunk(chunkX, chunkZ, false)) == null) { iter.remove(); continue; } else if (loaders <= 0) { iter.remove(); } for (Entity entity : chunk.getEntities().values()) { entity.scheduleUpdate(); } int tickSpeed = gameRules.getInteger(GameRule.RANDOM_TICK_SPEED); if (tickSpeed > 0) { if (this.useSections) { for (ChunkSection section : ((Chunk) chunk).getSections()) { if (!(section instanceof EmptyChunkSection)) { int Y = section.getY(); for (int i = 0; i < tickSpeed; ++i) { int lcg = this.getUpdateLCG(); int x = lcg & 0x0f; int y = lcg >>> 8 & 0x0f; int z = lcg >>> 16 & 0x0f; BlockState state = section.getBlockState(x, y, z); if (randomTickBlocks.contains(state.getBlockId())) { Block block = state.getBlockRepairing(this, chunkX * 16 + x, ((Y - (isOverWorld() ? 4 : 0)) << 4) + y, chunkZ * 16 + z); block.onUpdate(BLOCK_UPDATE_RANDOM); } } } } } else { for (int Y = 0; Y < 8 && (Y < 3 || blockTest); ++Y) { blockTest = false; for (int i = 0; i < tickSpeed; ++i) { int lcg = this.getUpdateLCG(); int x = lcg & 0x0f; int y = lcg >>> 8 & 0x0f; int z = lcg >>> 16 & 0x0f; BlockState state = chunk.getBlockState(x, y + (Y << 4), z); blockTest = blockTest || !state.equals(BlockState.AIR); if (Level.randomTickBlocks.contains(state.getBlockId())) { Block block = state.getBlockRepairing(this, x, y + ((Y - (isOverWorld() ? 4 : 0)) << 4), z); block.onUpdate(BLOCK_UPDATE_RANDOM); } } } } } } } if (this.clearChunksOnTick) { this.chunkTickList.clear(); } } public boolean save() { return this.save(false); } public boolean save(boolean force) { if (!this.getAutoSave() && !force) { return false; } this.server.getPluginManager().callEvent(new LevelSaveEvent(this)); LevelProvider levelProvider = this.requireProvider(); levelProvider.setTime((int) this.time); levelProvider.setRaining(this.raining); levelProvider.setRainTime(this.rainTime); levelProvider.setThundering(this.thundering); levelProvider.setThunderTime(this.thunderTime); levelProvider.setCurrentTick(this.levelCurrentTick); levelProvider.setGameRules(this.gameRules); this.saveChunks(); if (levelProvider instanceof BaseLevelProvider) { levelProvider.saveLevelData(); } return true; } public void saveChunks() { requireProvider().saveChunks(); } @Deprecated @DeprecationDetails(reason = "Was moved to RedstoneComponent", since = "1.4.0.0-PN", replaceWith = "RedstoneComponent#updateAroundRedstone", by = "PowerNukkit") public void updateAroundRedstone(Vector3 pos, BlockFace face) { Location loc = new Location(pos.x, pos.y, pos.z, this); RedstoneComponent.updateAroundRedstone(loc, face); } public void updateComparatorOutputLevel(Vector3 v) { updateComparatorOutputLevelSelective(v, true); } @PowerNukkitOnly @Since("1.4.0.0-PN") public void updateComparatorOutputLevelSelective(Vector3 v, boolean observer) { for (BlockFace face : Plane.HORIZONTAL) { temporalVector.setComponentsAdding(v, face); if (this.isChunkLoaded((int) temporalVector.x >> 4, (int) temporalVector.z >> 4)) { Block block1 = this.getBlock(temporalVector); if (block1.getId() == BlockID.OBSERVER) { if (observer) { block1.onNeighborChange(face.getOpposite()); } } else if (BlockRedstoneDiode.isDiode(block1)) { block1.onUpdate(BLOCK_UPDATE_REDSTONE); } else if (block1.isNormalBlock()) { block1 = this.getBlock(temporalVector.setComponentsAdding(temporalVector, face)); if (BlockRedstoneDiode.isDiode(block1)) { block1.onUpdate(BLOCK_UPDATE_REDSTONE); } } } } if (!observer) { return; } for (BlockFace face : Plane.VERTICAL) { Block block1 = this.getBlock(temporalVector.setComponentsAdding(v, face)); if (block1.getId() == BlockID.OBSERVER) { block1.onNeighborChange(face.getOpposite()); } } } public void updateAround(Vector3 pos) { Block block = getBlock(pos); for (BlockFace face : BlockFace.values()) { final Block side = block.getSideAtLayer(0, face); normalUpdateQueue.add(new QueuedUpdate(side, face)); normalUpdateQueue.add(new QueuedUpdate(side.getLevelBlockAtLayer(1), face)); } } @PowerNukkitXOnly @Since("1.19.60-r1") public void neighborChangeAroundImmediately(int x, int y, int z) { neighborChangeAroundImmediately(new Vector3(x, y, z)); } /** * 立即对围绕指定位置的方块发送neighborChange更新 * * @param pos 指定位置 */ @PowerNukkitXOnly @Since("1.19.60-r1") public void neighborChangeAroundImmediately(Vector3 pos) { for (var face : BlockFace.values()) { var neighborBlock = getBlock(pos.getSide(face)); neighborBlock.onNeighborChange(face.getOpposite()); } } @PowerNukkitXOnly @Since("1.19.60-r1") public void updateAroundObserver(Vector3 pos) { for (var face : BlockFace.values()) { var neighborBlock = getBlock(pos.getSide(face)); if (neighborBlock.getId() == BlockID.OBSERVER) neighborBlock.onNeighborChange(face.getOpposite()); } } public void updateAround(int x, int y, int z) { updateAround(new Vector3(x, y, z)); } public void scheduleUpdate(Block pos, int delay) { this.scheduleUpdate(pos, pos, delay, 0, true); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public void scheduleUpdate(Block pos, int delay, boolean checkBlockWhenUpdate) { this.scheduleUpdate(pos, pos, delay, 0, true, checkBlockWhenUpdate); } public void scheduleUpdate(Block block, Vector3 pos, int delay) { this.scheduleUpdate(block, pos, delay, 0, true); } public void scheduleUpdate(Block block, Vector3 pos, int delay, int priority) { this.scheduleUpdate(block, pos, delay, priority, true); } public void scheduleUpdate(Block block, Vector3 pos, int delay, int priority, boolean checkArea) { this.scheduleUpdate(block, pos, delay, priority, checkArea, true); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public void scheduleUpdate(Block block, Vector3 pos, int delay, int priority, boolean checkArea, boolean checkBlockWhenUpdate) { if (block.getId() == 0 || (checkArea && !this.isChunkLoaded(block.getFloorX() >> 4, block.getFloorZ() >> 4))) { return; } BlockUpdateEntry entry = new BlockUpdateEntry(pos.floor(), block, ((long) delay) + getCurrentTick(), priority, checkBlockWhenUpdate); if (!this.updateQueue.contains(entry)) { this.updateQueue.add(entry); } } public boolean cancelSheduledUpdate(Vector3 pos, Block block) { return this.updateQueue.remove(new BlockUpdateEntry(pos, block)); } public boolean isUpdateScheduled(Vector3 pos, Block block) { return this.updateQueue.contains(new BlockUpdateEntry(pos, block)); } public boolean isBlockTickPending(Vector3 pos, Block block) { return this.updateQueue.isBlockTickPending(pos, block); } public Set getPendingBlockUpdates(FullChunk chunk) { int minX = (chunk.getX() << 4) - 2; int maxX = minX + 16 + 2; int minZ = (chunk.getZ() << 4) - 2; int maxZ = minZ + 16 + 2; return this.getPendingBlockUpdates(new SimpleAxisAlignedBB(minX, isOverWorld() ? -64 : 0, minZ, maxX, isOverWorld() ? 320 : 256, maxZ)); } public Set getPendingBlockUpdates(AxisAlignedBB boundingBox) { return updateQueue.getPendingBlockUpdates(boundingBox); } @PowerNukkitOnly @Since("1.4.0.0-PN") public List scanBlocks(@NotNull AxisAlignedBB bb, @NotNull BiPredicate condition) { BlockVector3 min = new BlockVector3(NukkitMath.floorDouble(bb.getMinX()), NukkitMath.floorDouble(bb.getMinY()), NukkitMath.floorDouble(bb.getMinZ())); BlockVector3 max = new BlockVector3(NukkitMath.floorDouble(bb.getMaxX()), NukkitMath.floorDouble(bb.getMaxY()), NukkitMath.floorDouble(bb.getMaxZ())); ChunkVector2 minChunk = min.getChunkVector(); ChunkVector2 maxChunk = max.getChunkVector(); return IntStream.rangeClosed(minChunk.getX(), maxChunk.getX()) .mapToObj(x -> IntStream.rangeClosed(minChunk.getZ(), maxChunk.getZ()).mapToObj(z -> new ChunkVector2(x, z))) .flatMap(Function.identity()).parallel() .map(this::getChunk).filter(Objects::nonNull) .flatMap(chunk -> chunk.scanBlocks(min, max, condition)) .collect(Collectors.toList()); } public Block[] getCollisionBlocks(AxisAlignedBB bb) { return this.getCollisionBlocks(bb, false); } public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst) { return getCollisionBlocks(bb, targetFirst, false); } @PowerNukkitOnly public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst, boolean ignoreCollidesCheck) { return getCollisionBlocks(bb, targetFirst, ignoreCollidesCheck, block -> block.getId() != 0); } @PowerNukkitOnly public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst, boolean ignoreCollidesCheck, Predicate condition) { int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); int maxX = NukkitMath.ceilDouble(bb.getMaxX()); int maxY = NukkitMath.ceilDouble(bb.getMaxY()); int maxZ = NukkitMath.ceilDouble(bb.getMaxZ()); List collides = new ArrayList<>(); if (targetFirst) { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getBlock(this.temporalVector.setComponents(x, y, z), false); if (block != null && condition.test(block) && (ignoreCollidesCheck || block.collidesWithBB(bb))) { return new Block[]{block}; } } } } } else { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getBlock(this.temporalVector.setComponents(x, y, z), false); if (block != null && condition.test(block) && (ignoreCollidesCheck || block.collidesWithBB(bb))) { collides.add(block); } } } } } return collides.toArray(Block.EMPTY_ARRAY); } public boolean isFullBlock(Vector3 pos) { AxisAlignedBB bb; if (pos instanceof Block) { if (((Block) pos).isSolid()) { return true; } bb = ((Block) pos).getBoundingBox(); } else { bb = this.getBlock(pos).getBoundingBox(); } return bb != null && bb.getAverageEdgeLength() >= 1; } @PowerNukkitXOnly @Since("1.19.21-r3") public Block[] getTickCachedCollisionBlocks(AxisAlignedBB bb) { return this.getTickCachedCollisionBlocks(bb, false); } @PowerNukkitXOnly @Since("1.19.21-r3") public Block[] getTickCachedCollisionBlocks(AxisAlignedBB bb, boolean targetFirst) { return getTickCachedCollisionBlocks(bb, targetFirst, false); } @PowerNukkitXOnly @Since("1.19.21-r3") public Block[] getTickCachedCollisionBlocks(AxisAlignedBB bb, boolean targetFirst, boolean ignoreCollidesCheck) { return getTickCachedCollisionBlocks(bb, targetFirst, ignoreCollidesCheck, block -> block.getId() != 0); } @PowerNukkitXOnly @Since("1.19.21-r3") public Block[] getTickCachedCollisionBlocks(AxisAlignedBB bb, boolean targetFirst, boolean ignoreCollidesCheck, Predicate condition) { int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); int maxX = NukkitMath.ceilDouble(bb.getMaxX()); int maxY = NukkitMath.ceilDouble(bb.getMaxY()); int maxZ = NukkitMath.ceilDouble(bb.getMaxZ()); List collides = new ArrayList<>(); if (targetFirst) { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getTickCachedBlock(this.temporalVector.setComponents(x, y, z), false); if (block != null && condition.test(block) && (ignoreCollidesCheck || block.collidesWithBB(bb))) { return new Block[]{block}; } } } } } else { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getTickCachedBlock(this.temporalVector.setComponents(x, y, z), false); if (block != null && condition.test(block) && (ignoreCollidesCheck || block.collidesWithBB(bb))) { collides.add(block); } } } } } return collides.toArray(Block.EMPTY_ARRAY); } public AxisAlignedBB[] getCollisionCubes(Entity entity, AxisAlignedBB bb) { return this.getCollisionCubes(entity, bb, true); } public AxisAlignedBB[] getCollisionCubes(Entity entity, AxisAlignedBB bb, boolean entities) { return getCollisionCubes(entity, bb, entities, false); } public AxisAlignedBB[] getCollisionCubes(Entity entity, AxisAlignedBB bb, boolean entities, boolean solidEntities) { int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); int maxX = NukkitMath.ceilDouble(bb.getMaxX()); int maxY = NukkitMath.ceilDouble(bb.getMaxY()); int maxZ = NukkitMath.ceilDouble(bb.getMaxZ()); List collides = new ArrayList<>(); for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getBlock(this.temporalVector.setComponents(x, y, z), false); if (!block.canPassThrough() && block.collidesWithBB(bb)) { collides.add(block.getBoundingBox()); } } } } if (entities || solidEntities) { for (Entity ent : this.getCollidingEntities(bb.grow(0.25f, 0.25f, 0.25f), entity)) { if (solidEntities && !ent.canPassThrough()) { collides.add(ent.boundingBox.clone()); } } } return collides.toArray(AxisAlignedBB.EMPTY_ARRAY); } public List fastCollisionCubes(Entity entity, AxisAlignedBB bb) { return this.fastCollisionCubes(entity, bb, true); } public List fastCollisionCubes(Entity entity, AxisAlignedBB bb, boolean entities) { return fastCollisionCubes(entity, bb, entities, false); } public List fastCollisionCubes(Entity entity, AxisAlignedBB bb, boolean entities, boolean solidEntities) { int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); int maxX = NukkitMath.ceilDouble(bb.getMaxX()); int maxY = NukkitMath.ceilDouble(bb.getMaxY()); int maxZ = NukkitMath.ceilDouble(bb.getMaxZ()); List collides = new ArrayList<>(); for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getBlock(this.temporalVector.setComponents(x, y, z), false); if (!block.canPassThrough() && block.collidesWithBB(bb)) { collides.add(block.getBoundingBox()); } } } } if (entities || solidEntities) { var grownBB = bb.grow(0.25f, 0.25f, 0.25f); collides.addAll(this.streamCollidingEntities(grownBB, entity) .filter(ent -> solidEntities && !ent.canPassThrough()).map(ent -> ent.boundingBox.clone()).toList()); } return collides; } @PowerNukkitDifference(since = "1.4.0.0-PN", info = "Rounds the AABB to have precision 4 before checking for collision, fix PowerNukkit#506") public boolean hasCollision(Entity entity, AxisAlignedBB bb, boolean entities) { int minX = NukkitMath.floorDouble(NukkitMath.round(bb.getMinX(), 4)); int minY = NukkitMath.floorDouble(NukkitMath.round(bb.getMinY(), 4)); int minZ = NukkitMath.floorDouble(NukkitMath.round(bb.getMinZ(), 4)); int maxX = NukkitMath.ceilDouble(NukkitMath.round(bb.getMaxX(), 4) - 0.00001); int maxY = NukkitMath.ceilDouble(NukkitMath.round(bb.getMaxY(), 4) - 0.00001); int maxZ = NukkitMath.ceilDouble(NukkitMath.round(bb.getMaxZ(), 4) - 0.00001); for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getBlock(this.temporalVector.setComponents(x, y, z)); if (!block.canPassThrough() && block.collidesWithBB(bb)) { return true; } } } } if (entities) { return this.fastCollidingEntities(bb.grow(0.25f, 0.25f, 0.25f), entity).size() > 0; } return false; } public int getFullLight(Vector3 pos) { FullChunk chunk = this.getChunk((int) pos.x >> 4, (int) pos.z >> 4, false); int level = 0; if (chunk != null) { level = chunk.getBlockSkyLight((int) pos.x & 0x0f, ensureY((int) pos.y), (int) pos.z & 0x0f); level -= this.skyLightSubtracted; if (level < 15) { level = Math.max(chunk.getBlockLight((int) pos.x & 0x0f, ensureY((int) pos.y), (int) pos.z & 0x0f), level); } } return level; } @PowerNukkitXDifference(since = "1.19.20-r3") public int calculateSkylightSubtracted(float tickDiff) { float d = 1.0F - (this.getRainStrength(tickDiff) * 5.0F) / 16.0F; float e = 1.0F - (this.getThunderStrength(tickDiff) * 5.0F) / 16.0F; float f = 0.5F + 2.0F * MathHelper.clamp(MathHelper.cos(this.getCelestialAngle(tickDiff) * 6.2831855F), -0.25F, 0.25F); return (int) ((1.0F - f * d * e) * 11.0F); /* Old NukkitX Code float angle = this.getCelestialAngle(tickDiff); float light = 1.0F - (MathHelper.cos(angle * ((float) Math.PI * 2F)) * 2.0F + 0.5F); light = MathHelper.clamp(light, 0.0F, 1.0F); light = 1.0F - light; light = (float) ((double) light * (1.0D - (double) (this.getRainStrength(tickDiff) * 5.0F) / 16.0D)); light = (float) ((double) light * (1.0D - (double) (this.getThunderStrength(tickDiff) * 5.0F) / 16.0D)); light = 1.0F - light; return (int) (light * 11.0F); */ } @PowerNukkitOnly public float getRainStrength(float tickDiff) { return isRaining() ? 1 : 0; // TODO: real implementation } @PowerNukkitOnly public float getThunderStrength(float tickDiff) { return isThundering() ? 1 : 0; // TODO: real implementation } @PowerNukkitOnly public float getCelestialAngle(float tickDiff) { return calculateCelestialAngle(getTime(), tickDiff); } public float calculateCelestialAngle(int time, float tickDiff) { int i = (int) (time % 24000L); float angle = ((float) i + tickDiff) / 24000.0F - 0.25F; if (angle < 0.0F) { ++angle; } if (angle > 1.0F) { --angle; } float f1 = 1.0F - (float) ((Math.cos((double) angle * Math.PI) + 1.0D) / 2.0D); angle = angle + (f1 - angle) / 3.0F; return angle; } public int getMoonPhase(long worldTime) { return (int) (worldTime / 24000 % 8 + 8) % 8; } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN") public int getFullBlock(int x, int y, int z) { return getFullBlock(x, y, z, 0); } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN") @PowerNukkitOnly public int getFullBlock(int x, int y, int z, int layer) { return this.getChunk(x >> 4, z >> 4, false).getFullBlock(x & 0x0f, ensureY(y), z & 0x0f, layer); } @PowerNukkitOnly @Since("1.3.0.0-PN") public int getBlockRuntimeId(int x, int y, int z) { return getBlockRuntimeId(x, y, z, 0); } @PowerNukkitOnly @Since("1.3.0.0-PN") public int getBlockRuntimeId(int x, int y, int z, int layer) { final var tmp = this.getChunk(x >> 4, z >> 4, false); if (tmp == null) { return Integer.MIN_VALUE; } return tmp.getBlockRuntimeId(x & 0x0f, ensureY(y), z & 0x0f, layer); } @PowerNukkitXDifference(since = "1.19.20-r3", info = "Allow parallel gets.") public Set getBlockAround(Vector3 pos) { Set around = new HashSet<>(); Block block = getBlock(pos); for (BlockFace face : BlockFace.values()) { Block side = block.getSideAtLayer(0, face); around.add(side); } return around; } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(Vector3 pos) { return getTickCachedBlock(pos, 0); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(Vector3 pos, int layer) { return this.getTickCachedBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), layer); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(Vector3 pos, boolean load) { return getTickCachedBlock(pos, 0, load); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(Vector3 pos, int layer, boolean load) { return this.getTickCachedBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), layer, load); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(int x, int y, int z) { return getTickCachedBlock(x, y, z, 0); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(int x, int y, int z, int layer) { return getTickCachedBlock(x, y, z, layer, true); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(int x, int y, int z, boolean load) { return getTickCachedBlock(x, y, z, 0, load); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public Block getTickCachedBlock(int x, int y, int z, int layer, boolean load) { return tickCachedBlocks.computeIfAbsent(Level.chunkHash(x >> 4, z >> 4), key -> new SimpleTickCachedBlockStore(this)) .computeFromCachedStore(x, y, z, layer, () -> getBlock(x, y, z, layer, load)); } public Block getBlock(Vector3 pos) { return getBlock(pos, 0); } @PowerNukkitOnly public Block getBlock(Vector3 pos, int layer) { return this.getBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), layer); } public Block getBlock(Vector3 pos, boolean load) { return getBlock(pos, 0, load); } @PowerNukkitOnly public Block getBlock(Vector3 pos, int layer, boolean load) { return this.getBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), layer, load); } public Block getBlock(int x, int y, int z) { return getBlock(x, y, z, 0); } @PowerNukkitOnly public Block getBlock(int x, int y, int z, int layer) { return getBlock(x, y, z, layer, true); } public Block getBlock(int x, int y, int z, boolean load) { return getBlock(x, y, z, 0, load); } @PowerNukkitOnly public Block getBlock(int x, int y, int z, int layer, boolean load) { BlockState fullState; if (isYInRange(y)) { int cx = x >> 4; int cz = z >> 4; BaseFullChunk chunk; if (load) { chunk = getChunk(cx, cz); } else { chunk = getChunkIfLoaded(cx, cz); } if (chunk != null) { fullState = chunk.getBlockState(x & 0xF, y, z & 0xF, layer); } else { fullState = BlockState.AIR; } } else { fullState = BlockState.AIR; } OptionalBoolean valid = fullState.getCachedValidation(); if (valid == OptionalBoolean.TRUE) { return fullState.getBlock(this, x, y, z, layer); } if (valid == OptionalBoolean.EMPTY) { try { return fullState.getBlock(this, x, y, z, layer); } catch (InvalidBlockStateException ignored) { } } Block block = fullState.getBlockRepairing(this, x, y, z, layer); setBlock(x, y, z, layer, block, false, false); // Update set to false to fix PowerNukkit#650 return block; } public synchronized void updateAllLight(Vector3 pos) { this.updateBlockSkyLight((int) pos.x, (int) pos.y, (int) pos.z); this.addLightUpdate((int) pos.x, (int) pos.y, (int) pos.z); } public void updateBlockSkyLight(int x, int y, int z) { BaseFullChunk chunk = getChunkIfLoaded(x >> 4, z >> 4); if (chunk == null) return; int oldHeightMap = chunk.getHeightMap(x & 0xf, z & 0xf); int sourceId = getBlockIdAt(x, y, z); int yPlusOne = y + 1; int newHeightMap; if (yPlusOne == oldHeightMap) { // Block changed directly beneath the heightmap. Check if a block was removed or changed to a different light-filter newHeightMap = chunk.recalculateHeightMapColumn(x & 0x0f, z & 0x0f); } else if (yPlusOne > oldHeightMap) { // Block changed above the heightmap if (Block.getLightFilter(sourceId) > 1 || Block.diffusesSkyLight(sourceId)) { chunk.setHeightMap(x & 0xf, z & 0xf, yPlusOne); newHeightMap = yPlusOne; } else { // Block changed which has no effect on direct sky light, for example placing or removing glass. return; } } else { // Block changed below heightmap newHeightMap = oldHeightMap; } if (newHeightMap > oldHeightMap) { // Heightmap increase, block placed, remove sky light for (int i = y; i >= oldHeightMap; --i) { setBlockSkyLightAt(x, i, z, 0); } } else if (newHeightMap < oldHeightMap) { // Heightmap decrease, block changed or removed, add sky light for (int i = y; i >= newHeightMap; --i) { setBlockSkyLightAt(x, i, z, 15); } } else { // No heightmap change, block changed "underground" setBlockSkyLightAt(x, y, z, Math.max(0, getHighestAdjacentBlockSkyLight(x, y, z) - Block.getLightFilter(sourceId))); } } /** * Returns the highest block skylight level available in the positions adjacent to the specified block coordinates. */ @PowerNukkitOnly public int getHighestAdjacentBlockSkyLight(int x, int y, int z) { int[] lightLevels = new int[]{ getBlockSkyLightAt(x + 1, y, z), getBlockSkyLightAt(x - 1, y, z), getBlockSkyLightAt(x, y + 1, z), getBlockSkyLightAt(x, y - 1, z), getBlockSkyLightAt(x, y, z + 1), getBlockSkyLightAt(x, y, z - 1), }; int maxValue = lightLevels[0]; for (int i = 1; i < lightLevels.length; i++) { if (lightLevels[i] > maxValue) { maxValue = lightLevels[i]; } } return maxValue; } public void updateBlockLight(Map> map) { int size = map.size(); if (size == 0) { return; } Queue lightPropagationQueue = new ConcurrentLinkedQueue<>(); Queue lightRemovalQueue = new ConcurrentLinkedQueue<>(); Long2ObjectOpenHashMap visited = new Long2ObjectOpenHashMap<>(); Long2ObjectOpenHashMap removalVisited = new Long2ObjectOpenHashMap<>(); var iter = map.entrySet().iterator(); while (iter.hasNext() && size-- > 0) { var entry = iter.next(); iter.remove(); long index = entry.getKey(); var blocks = entry.getValue(); int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); int bx = chunkX << 4; int bz = chunkZ << 4; for (int blockHash : blocks.keySet()) { int hi = (byte) (blockHash >>> 16); int lo = (short) blockHash; int y = ensureY(lo - 64); int x = (hi & 0xF) + bx; int z = ((hi >> 4) & 0xF) + bz; BaseFullChunk chunk = getChunk(x >> 4, z >> 4, false); if (chunk != null) { int lcx = x & 0xF; int lcz = z & 0xF; int oldLevel = chunk.getBlockLight(lcx, y, lcz); int newLevel = chunk.getBlockState(lcx, y, lcz).getBlock(this, x, y, z, 0, true).getLightLevel(); if (oldLevel != newLevel) { this.setBlockLightAt(x, y, z, newLevel); if (newLevel < oldLevel) { removalVisited.put(Hash.hashBlock(x, y, z), changeBlocksPresent); lightRemovalQueue.add(new Object[]{Hash.hashBlock(x, y, z), oldLevel}); } else { visited.put(Hash.hashBlock(x, y, z), changeBlocksPresent); lightPropagationQueue.add(Hash.hashBlock(x, y, z)); } } } } } while (!lightRemovalQueue.isEmpty()) { Object[] val = lightRemovalQueue.poll(); long node = (long) val[0]; int x = Hash.hashBlockX(node); int y = Hash.hashBlockY(node); int z = Hash.hashBlockZ(node); int lightLevel = (int) val[1]; this.computeRemoveBlockLight(x - 1, y, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); this.computeRemoveBlockLight(x + 1, y, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); this.computeRemoveBlockLight(x, y - 1, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); this.computeRemoveBlockLight(x, y + 1, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); this.computeRemoveBlockLight(x, y, z - 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); this.computeRemoveBlockLight(x, y, z + 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); } while (!lightPropagationQueue.isEmpty()) { long node = lightPropagationQueue.poll(); int x = Hash.hashBlockX(node); int y = Hash.hashBlockY(node); int z = Hash.hashBlockZ(node); int lightLevel = this.getBlockLightAt(x, y, z) - Block.getLightFilter(this.getBlockIdAt(x, y, z)); if (lightLevel >= 1) { this.computeSpreadBlockLight(x - 1, y, z, lightLevel, lightPropagationQueue, visited); this.computeSpreadBlockLight(x + 1, y, z, lightLevel, lightPropagationQueue, visited); this.computeSpreadBlockLight(x, y - 1, z, lightLevel, lightPropagationQueue, visited); this.computeSpreadBlockLight(x, y + 1, z, lightLevel, lightPropagationQueue, visited); this.computeSpreadBlockLight(x, y, z - 1, lightLevel, lightPropagationQueue, visited); this.computeSpreadBlockLight(x, y, z + 1, lightLevel, lightPropagationQueue, visited); } } } private void computeRemoveBlockLight(int x, int y, int z, int currentLight, Queue queue, Queue spreadQueue, Map visited, Map spreadVisited) { int current = this.getBlockLightAt(x, y, z); long index = Hash.hashBlock(x, y, z); if (current != 0 && current < currentLight) { this.setBlockLightAt(x, y, z, 0); if (current > 1) { if (!visited.containsKey(index)) { visited.put(index, changeBlocksPresent); queue.add(new Object[]{Hash.hashBlock(x, y, z), current}); } } } else if (current >= currentLight) { if (!spreadVisited.containsKey(index)) { spreadVisited.put(index, changeBlocksPresent); spreadQueue.add(Hash.hashBlock(x, y, z)); } } } private void computeSpreadBlockLight(int x, int y, int z, int currentLight, Queue queue, Map visited) { int current = this.getBlockLightAt(x, y, z); long index = Hash.hashBlock(x, y, z); if (current < currentLight - 1) { this.setBlockLightAt(x, y, z, currentLight); if (!visited.containsKey(index)) { visited.put(index, changeBlocksPresent); if (currentLight > 1) { queue.add(Hash.hashBlock(x, y, z)); } } } } public void addLightUpdate(int x, int y, int z) { long index = chunkHash(x >> 4, z >> 4); var currentMap = lightQueue.get(index); if (currentMap == null) { currentMap = new ConcurrentHashMap<>(8, 0.9f, 1); this.lightQueue.put(index, currentMap); } currentMap.put(Level.localBlockHash(x, y, z, this), changeBlocksPresent); } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @Override public void setBlockFullIdAt(int x, int y, int z, int fullId) { setBlockFullIdAt(x, y, z, 0, fullId); } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @PowerNukkitOnly @Override public void setBlockFullIdAt(int x, int y, int z, int layer, int fullId) { setBlock(x, y, z, layer, Block.fullList[fullId], false, false); } public boolean setBlock(Vector3 pos, Block block) { return setBlock(pos, 0, block); } @PowerNukkitOnly public boolean setBlock(Vector3 pos, int layer, Block block) { return this.setBlock(pos, layer, block, false); } public boolean setBlock(Vector3 pos, Block block, boolean direct) { return this.setBlock(pos, 0, block, direct); } @PowerNukkitOnly public boolean setBlock(Vector3 pos, int layer, Block block, boolean direct) { return this.setBlock(pos, layer, block, direct, true); } public boolean setBlock(Vector3 pos, Block block, boolean direct, boolean update) { return setBlock(pos, 0, block, direct, update); } @PowerNukkitOnly public boolean setBlock(Vector3 pos, int layer, Block block, boolean direct, boolean update) { return setBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), layer, block, direct, update); } public boolean setBlock(int x, int y, int z, Block block, boolean direct, boolean update) { return setBlock(x, y, z, 0, block, direct, update); } /** * 设置一个方块 * * @param x 方块的x坐标 * @param y 方块的y坐标 * @param z 方块的z坐标 * @param layer 设置的方块层级。例如含水方块位置上的layer1对应的方块为水 * @param block 方块 * @param direct 是否立即同步方块变更到客户端。 * @param update 是否进行方块更新 * @return 是否设置成功 */ @PowerNukkitOnly public boolean setBlock(int x, int y, int z, int layer, Block block, boolean direct, boolean update) { if (!isYInRange(y) || layer < 0 || layer > this.requireProvider().getMaximumLayer()) { return false; } BlockState state = block.getCurrentState(); BaseFullChunk chunk = this.getChunk(x >> 4, z >> 4, true); BlockState statePrevious = chunk.getAndSetBlockState(x & 0xF, y, z & 0xF, layer, state); if (state.equals(statePrevious)) { return false; } block.x = x; block.y = y; block.z = z; block.level = this; block.layer = layer; Block blockPrevious = statePrevious.getBlockRepairing(this, x, y, z, layer); int cx = x >> 4; int cz = z >> 4; long index = Level.chunkHash(cx, cz); if (direct) { if (antiXrayEnabled && block.isTransparent()) { this.sendBlocks(this.getChunkPlayers(cx, cz).values().toArray(Player.EMPTY_ARRAY), new Vector3[]{block.add(-1), block.add(1), block.add(0, -1), block.add(0, 1), block.add(0, 0, 1), block.add(0, 0, -1)} , UpdateBlockPacket.FLAG_ALL_PRIORITY); } this.sendBlocks(this.getChunkPlayers(cx, cz).values().toArray(Player.EMPTY_ARRAY), new Block[]{block}, UpdateBlockPacket.FLAG_ALL_PRIORITY, block.layer); //this.sendBlocks(this.getChunkPlayers(cx, cz).values().toArray(new Player[0]), new Block[]{block.getLevelBlockAtLayer(0)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 0); //this.sendBlocks(this.getChunkPlayers(cx, cz).values().toArray(new Player[0]), new Block[]{block.getLevelBlockAtLayer(1)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); } else { addBlockChange(index, x, y, z); } for (ChunkLoader loader : this.getChunkLoaders(cx, cz)) { loader.onBlockChanged(block); } if (update) { updateAllLight(block); /*if (blockPrevious.isTransparent() != block.isTransparent() || blockPrevious.getLightLevel() != block.getLightLevel()) { addLightUpdate(x, y, z); }*/ BlockUpdateEvent ev = new BlockUpdateEvent(block); this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { for (Entity entity : this.getNearbyEntities(new SimpleAxisAlignedBB(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1))) { entity.scheduleUpdate(); } block = ev.getBlock(); block.onUpdate(BLOCK_UPDATE_NORMAL); block.getLevelBlockAtLayer(layer == 0 ? 1 : 0).onUpdate(BLOCK_UPDATE_NORMAL); this.updateAround(x, y, z); if (block.hasComparatorInputOverride()) { this.updateComparatorOutputLevel(block); } } } blockPrevious.afterRemoval(block, update); return true; } private void addBlockChange(int x, int y, int z) { long index = Level.chunkHash(x >> 4, z >> 4); addBlockChange(index, x, y, z); } private void addBlockChange(long index, int x, int y, int z) { synchronized (changedBlocks) { SoftReference> current = changedBlocks.computeIfAbsent(index, k -> new SoftReference<>(new Int2ObjectOpenHashMap<>())); var currentMap = current.get(); if (currentMap != changeBlocksFullMap && currentMap != null) { if (currentMap.size() > MAX_BLOCK_CACHE) { this.changedBlocks.put(index, new SoftReference<>(changeBlocksFullMap)); } else { currentMap.put(Level.localBlockHash(x, y, z, this), changeBlocksPresent); } } } } public void dropItem(Vector3 source, Item item) { this.dropItem(source, item, null); } public void dropItem(Vector3 source, Item item, Vector3 motion) { this.dropItem(source, item, motion, 10); } public void dropItem(Vector3 source, Item item, Vector3 motion, int delay) { this.dropItem(source, item, motion, false, delay); } public void dropItem(Vector3 source, Item item, Vector3 motion, boolean dropAround, int delay) { if (motion == null) { if (dropAround) { float f = ThreadLocalRandom.current().nextFloat() * 0.5f; float f1 = ThreadLocalRandom.current().nextFloat() * ((float) Math.PI * 2); motion = new Vector3(-MathHelper.sin(f1) * f, 0.20000000298023224, MathHelper.cos(f1) * f); } else { motion = new Vector3(new java.util.Random().nextDouble() * 0.2 - 0.1, 0.2, new java.util.Random().nextDouble() * 0.2 - 0.1); } } if (item.getId() != 0 && item.getCount() > 0) { EntityItem itemEntity = (EntityItem) Entity.createEntity("Item", this.getChunk((int) source.getX() >> 4, (int) source.getZ() >> 4, true), Entity.getDefaultNBT(source, motion, new Random().nextFloat() * 360, 0) .putShort("Health", 5) .putCompound("Item", NBTIO.putItemHelper(item)) .putShort("PickupDelay", delay)); if (itemEntity != null) { itemEntity.spawnToAll(); } } } @Since("1.4.0.0-PN") @Nullable public EntityItem dropAndGetItem(@NotNull Vector3 source, @NotNull Item item) { return this.dropAndGetItem(source, item, null); } @Since("1.4.0.0-PN") @Nullable public EntityItem dropAndGetItem(@NotNull Vector3 source, @NotNull Item item, @Nullable Vector3 motion) { return this.dropAndGetItem(source, item, motion, 10); } @Since("1.4.0.0-PN") @Nullable public EntityItem dropAndGetItem(@NotNull Vector3 source, @NotNull Item item, @Nullable Vector3 motion, int delay) { return this.dropAndGetItem(source, item, motion, false, delay); } @Since("1.4.0.0-PN") @Nullable public EntityItem dropAndGetItem(@NotNull Vector3 source, @NotNull Item item, @Nullable Vector3 motion, boolean dropAround, int delay) { EntityItem itemEntity = null; if (motion == null) { if (dropAround) { float f = ThreadLocalRandom.current().nextFloat() * 0.5f; float f1 = ThreadLocalRandom.current().nextFloat() * ((float) Math.PI * 2); motion = new Vector3(-MathHelper.sin(f1) * f, 0.20000000298023224, MathHelper.cos(f1) * f); } else { motion = new Vector3(new java.util.Random().nextDouble() * 0.2 - 0.1, 0.2, new java.util.Random().nextDouble() * 0.2 - 0.1); } } CompoundTag itemTag = NBTIO.putItemHelper(item); itemTag.setName("Item"); if (item.getId() != 0 && item.getCount() > 0) { itemEntity = (EntityItem) Entity.createEntity("Item", this.getChunk((int) source.getX() >> 4, (int) source.getZ() >> 4, true), new CompoundTag().putList(new ListTag("Pos").add(new DoubleTag("", source.getX())) .add(new DoubleTag("", source.getY())).add(new DoubleTag("", source.getZ()))) .putList(new ListTag("Motion").add(new DoubleTag("", motion.x)) .add(new DoubleTag("", motion.y)).add(new DoubleTag("", motion.z))) .putList(new ListTag("Rotation") .add(new FloatTag("", ThreadLocalRandom.current().nextFloat() * 360)) .add(new FloatTag("", 0))) .putShort("Health", 5).putCompound("Item", itemTag).putShort("PickupDelay", delay)); if (itemEntity != null) { itemEntity.spawnToAll(); } } return itemEntity; } public Item useBreakOn(Vector3 vector) { return this.useBreakOn(vector, null); } public Item useBreakOn(Vector3 vector, Item item) { return this.useBreakOn(vector, item, null); } public Item useBreakOn(Vector3 vector, Item item, Player player) { return this.useBreakOn(vector, item, player, false); } public Item useBreakOn(Vector3 vector, Item item, Player player, boolean createParticles) { return useBreakOn(vector, null, item, player, createParticles); } public Item useBreakOn(Vector3 vector, BlockFace face, Item item, Player player, boolean createParticles) { return useBreakOn(vector, face, item, player, createParticles, false); } @PowerNukkitOnly public Item useBreakOn(Vector3 vector, BlockFace face, Item item, Player player, boolean createParticles, boolean setBlockDestroy) { if (vector instanceof Block) { return useBreakOn(vector, ((Block) vector).layer, face, item, player, createParticles, setBlockDestroy); } else { return useBreakOn(vector, 0, face, item, player, createParticles, setBlockDestroy); } } @PowerNukkitOnly public Item useBreakOn(Vector3 vector, int layer, BlockFace face, Item item, Player player, boolean createParticles, boolean setBlockDestroy) { if (player != null && player.getGamemode() > 2) { return null; } Block target = this.getBlock(vector, layer); if (player != null && !target.isBlockChangeAllowed(player)) { return null; } Item[] drops; int dropExp = target.getDropExp(); if (item == null) { item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } if (!target.isBreakable(vector, layer, face, item, player, setBlockDestroy)) { return null; } boolean mustDrop = target.mustDrop(vector, layer, face, item, player); boolean mustSilkTouch = target.mustSilkTouch(vector, layer, face, item, player); boolean isSilkTouch = mustSilkTouch || (item.getEnchantment(Enchantment.ID_SILK_TOUCH) != null && item.applyEnchantments()); if (player != null) { if (player.getGamemode() == 2) { Tag tag = item.getNamedTagEntry("CanDestroy"); boolean canBreak = false; if (tag instanceof ListTag) { for (Tag v : ((ListTag) tag).getAll()) { if (v instanceof StringTag) { Item entry = Item.fromString(((StringTag) v).data); if (entry.getId() > 0 && entry.getBlock() != null && entry.getBlock().getId() == target.getId()) { canBreak = true; break; } } } } if (!canBreak) { return null; } } Item[] eventDrops; if (!mustDrop && !setBlockDestroy && !player.isSurvival() && !player.isAdventure()) { eventDrops = Item.EMPTY_ARRAY; } else if (mustSilkTouch || isSilkTouch && target.canSilkTouch()) { eventDrops = new Item[]{target.toItem()}; } else { eventDrops = target.getDrops(item); } if (!setBlockDestroy) { if (!player.getAdventureSettings().get(PlayerAbility.MINE)) return null; //使用calculateBreakTimeNotInAir目的是获取玩家在陆地上的挖掘时间,如果挖掘时间小于这个时间才认为玩家作弊。 double breakTime = target.calculateBreakTimeNotInAir(item, player); //对于自定义方块,由于用户可以自由设置客户端侧的挖掘时间,拿服务端硬度计算出来的挖掘时间来判断是否为fastBreak是不准确的。 if (target instanceof CustomBlock customBlock) { var comp = customBlock.getDefinition().nbt().getCompound("components"); if (comp.containsCompound("minecraft:destructible_by_mining")) { var clientBreakTime = comp.getCompound("minecraft:destructible_by_mining").getFloat("value"); breakTime = Math.min(breakTime, clientBreakTime); } } if (player.isCreative() && breakTime > 0.15) { breakTime = 0.15; } breakTime -= 0.15; //thisBreak-lastBreak < breakTime-1000ms = the player is hacker (fastBreak) boolean fastBreak = Long.sum(player.lastBreak, (long) breakTime * 1000) > Long.sum(System.currentTimeMillis(), 1000); BlockBreakEvent ev = new BlockBreakEvent(player, target, face, item, eventDrops, player.isCreative(), fastBreak); if (player.isSurvival() && !target.isBreakable(item)) { ev.setCancelled(); } else if (!player.isOp() && isInSpawnRadius(target)) { ev.setCancelled(); } else if (!ev.getInstaBreak() && ev.isFastBreak()) { ev.setCancelled(); } this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return null; } if (!ev.getInstaBreak() && ev.isFastBreak()) { return null; } player.lastBreak = System.currentTimeMillis(); drops = ev.getDrops(); dropExp = ev.getDropExp(); } else { drops = eventDrops; } } else if (!target.isBreakable(item)) { return null; } else if (isSilkTouch) { drops = new Item[]{target.toItem()}; } else { drops = target.getDrops(item); } Block above = this.getBlock(new Vector3(target.x, target.y + 1, target.z), 0); if (above != null) { if (above.getId() == Item.FIRE) { this.setBlock(above, Block.get(BlockID.AIR), true); } } if (createParticles) { Map players = this.getChunkPlayers((int) target.x >> 4, (int) target.z >> 4); this.addParticle(new DestroyBlockParticle(target.add(0.5), target), players.values()); if (player != null && !setBlockDestroy) { players.remove(player.getLoaderId()); } } // Close BlockEntity before we check onBreak if (layer == 0) { BlockEntity blockEntity = this.getBlockEntity(target); if (blockEntity != null) { blockEntity.onBreak(isSilkTouch); blockEntity.close(); this.updateComparatorOutputLevel(target); } } target.onBreak(item); this.getVibrationManager().callVibrationEvent(new VibrationEvent(player, target.add(0.5, 0.5, 0.5), VibrationType.BLOCK_DESTROY)); item.useOn(target); if (item.isTool() && item.getDamage() >= item.getMaxDurability()) { if (player != null) { addSound(player, Sound.RANDOM_BREAK); } item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } if (this.gameRules.getBoolean(GameRule.DO_TILE_DROPS)) { if (!isSilkTouch && (mustDrop || player != null && ((player.isSurvival() || player.isAdventure()) || setBlockDestroy)) && dropExp > 0 && drops.length != 0) { this.dropExpOrb(vector.add(0.5, 0.5, 0.5), dropExp); } if (mustDrop || player == null || setBlockDestroy || (player.isSurvival() || player.isAdventure())) { for (Item drop : drops) { if (drop.getCount() > 0) { this.dropItem(vector.add(0.5, 0.5, 0.5), drop); } } } } return item; } public void dropExpOrb(Vector3 source, int exp) { dropExpOrb(source, exp, null); } public void dropExpOrb(Vector3 source, int exp, Vector3 motion) { dropExpOrb(source, exp, motion, 10); } public void dropExpOrb(Vector3 source, int exp, Vector3 motion, int delay) { dropExpOrbAndGetEntities(source, exp, motion, delay); } @PowerNukkitOnly @Since("1.4.0.0-PN") public List dropExpOrbAndGetEntities(Vector3 source, int exp) { return dropExpOrbAndGetEntities(source, exp, null); } @PowerNukkitOnly @Since("1.4.0.0-PN") public List dropExpOrbAndGetEntities(Vector3 source, int exp, Vector3 motion) { return dropExpOrbAndGetEntities(source, exp, motion, 10); } @PowerNukkitOnly @Since("1.4.0.0-PN") public List dropExpOrbAndGetEntities(Vector3 source, int exp, Vector3 motion, int delay) { Random rand = ThreadLocalRandom.current(); List drops = EntityXPOrb.splitIntoOrbSizes(exp); List entities = new ArrayList<>(drops.size()); for (int split : drops) { CompoundTag nbt = Entity.getDefaultNBT(source, motion == null ? new Vector3( (rand.nextDouble() * 0.2 - 0.1) * 2, rand.nextDouble() * 0.4, (rand.nextDouble() * 0.2 - 0.1) * 2) : motion, rand.nextFloat() * 360f, 0); nbt.putShort("Value", split); nbt.putShort("PickupDelay", delay); EntityXPOrb entity = (EntityXPOrb) Entity.createEntity("XpOrb", this.getChunk(source.getChunkX(), source.getChunkZ()), nbt); if (entity != null) { entities.add(entity); entity.spawnToAll(); } } return entities; } public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float fy, float fz) { return this.useItemOn(vector, item, face, fx, fy, fz, null); } public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float fy, float fz, Player player) { return this.useItemOn(vector, item, face, fx, fy, fz, player, true); } @PowerNukkitXDifference(info = "Fixed issue#790", since = "1.19.50-r1") @PowerNukkitDifference(info = "PowerNukkit#403", since = "1.3.1.2-PN") @PowerNukkitDifference(info = "Fixed PowerNukkit#716, block stops placing when towering up", since = "1.4.0.0-PN") public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float fy, float fz, Player player, boolean playSound) { Block target = this.getBlock(vector); Block block = target.getSide(face); if (item.getBlock() instanceof BlockScaffolding && face == BlockFace.UP && block.getId() == BlockID.SCAFFOLDING) { while (block instanceof BlockScaffolding) { block = block.up(); } } //handle height limit if (!isYInRange((int) block.y)) { return null; } //handle height limit in nether if (block.y > 127 && this.getDimension() == DIMENSION_NETHER) { return null; } if (target.getId() == Item.AIR) { return null; } if (player != null) { PlayerInteractEvent ev = new PlayerInteractEvent(player, item, target, face, target.getId() == 0 ? Action.RIGHT_CLICK_AIR : Action.RIGHT_CLICK_BLOCK); if (player.getGamemode() > 2) { ev.setCancelled(); } //handle spawn protect if (!player.isOp() && isInSpawnRadius(target)) { ev.setCancelled(); } this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { target.onTouch(player, ev.getAction(), face); if ((!player.isSneaking() || player.getInventory().getItemInHand().isNull()) && target.canBeActivated() && target.onActivate(item, player)) { if (item.isTool() && item.getDamage() >= item.getMaxDurability()) { addSound(player, Sound.RANDOM_BREAK); item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } return item; } if (item.canBeActivated() && item.onActivate(this, player, block, target, face, fx, fy, fz)) { if (item.getCount() <= 0) { item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); return item; } } } else { if ((item instanceof ItemBucket) && ((ItemBucket) item).isWater()) { player.getLevel().sendBlocks(new Player[]{player}, new Block[]{Block.get(Block.AIR, 0, target.getLevelBlockAtLayer(1))}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); } return null; } if ((item instanceof ItemBucket) && ((ItemBucket) item).isWater()) { player.getLevel().sendBlocks(new Player[]{player}, new Block[]{target.getLevelBlockAtLayer(1)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); } } else if (target.canBeActivated() && target.onActivate(item, player)) { if (item.isTool() && item.getDamage() >= item.getMaxDurability()) { item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } return item; } Block hand; if (item.canBePlaced()) { hand = item.getBlock(); hand.position(block); } else { return null; } if (!(block.canBeReplaced() || (hand instanceof BlockSlab && hand.getId() == block.getId()))) { return null; } //cause bug (eg: frog_spawn) (and I don't know what this is for) if (!(hand instanceof BlockFrogSpawn) && target.canBeReplaced()) { block = target; hand.position(block); } //处理放置梯子,我们应该提前给hand设置方向,这样后面计算是否碰撞实体才准确 if (hand instanceof BlockLadder) { if (target instanceof BlockLadder) { hand.setDamage(face.getOpposite().getIndex()); } else hand.setDamage(face.getIndex()); } if (!hand.canPassThrough() && hand.getBoundingBox() != null) { int realCount = 0; Entity[] entities = this.getCollidingEntities(hand.getBoundingBox()); for (Entity e : entities) { if (e instanceof EntityProjectile || e instanceof EntityItem || e instanceof EntityXPOrb || e instanceof EntityFirework || e instanceof EntityPainting || e == player || (e instanceof Player p && p.isSpectator())) { continue; } ++realCount; } if (player != null) { var diff = player.getNextPosition().subtract(player.getPosition()); var aabb = player.getBoundingBox().getOffsetBoundingBox(diff.x, diff.y <= 0 ? 0 : diff.y, diff.z); if (aabb.offset(0, 0.01, 0).intersectsWith(hand.getBoundingBox())) { ++realCount; } } if (realCount > 0) { return null; // Entity in block } } if (player != null) { if (!player.getAdventureSettings().get(PlayerAbility.BUILD) || !block.isBlockChangeAllowed(player)) return null; BlockPlaceEvent event = new BlockPlaceEvent(player, hand, block, target, item); if (player.getGamemode() == Player.ADVENTURE) { Tag tag = item.getNamedTagEntry("CanPlaceOn"); boolean canPlace = false; if (tag instanceof ListTag) { for (Tag v : ((ListTag) tag).getAll()) { if (v instanceof StringTag) { Item entry = Item.fromString(((StringTag) v).data); if (entry.getId() > 0 && entry.getBlock() != null && entry.getBlock().getId() == target.getId()) { canPlace = true; break; } } } } if (!canPlace) { event.setCancelled(); } } if (!player.isOp() && isInSpawnRadius(target)) { event.setCancelled(); } this.server.getPluginManager().callEvent(event); if (event.isCancelled()) { return null; } } if (hand.getWaterloggingLevel() == 0 && hand.canBeFlowedInto() && (block instanceof BlockLiquid || block.getLevelBlockAtLayer(1) instanceof BlockLiquid)) { return null; } boolean liquidMoved = false; if ((block instanceof BlockLiquid) && ((BlockLiquid) block).usesWaterLogging()) { liquidMoved = true; this.setBlock(block, 1, block, false, false); this.setBlock(block, 0, Block.get(BlockID.AIR), false, false); this.scheduleUpdate(block, 1); } if (!hand.place(item, block, target, face, fx, fy, fz, player)) { if (liquidMoved) { this.setBlock(block, 0, block, false, false); this.setBlock(block, 1, Block.get(BlockID.AIR), false, false); } return null; } if (player != null) { if (!player.isCreative()) { item.setCount(item.getCount() - 1); } } if (playSound) { this.addLevelSoundEvent(hand, LevelSoundEventPacket.SOUND_PLACE, GlobalBlockPalette.getOrCreateRuntimeId(hand.getId(), hand.getDamage())); } if (item.getCount() <= 0) { item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } this.getVibrationManager().callVibrationEvent(new VibrationEvent(player, block.add(0.5, 0.5, 0.5), VibrationType.BLOCK_PLACE)); return item; } public boolean isInSpawnRadius(Vector3 vector3) { int distance = this.server.getSpawnRadius(); if (distance > -1) { Vector2 t = new Vector2(vector3.x, vector3.z); Vector2 s = new Vector2(this.getSpawnLocation().x, this.getSpawnLocation().z); return t.distance(s) <= distance; } return false; } public Entity getEntity(long entityId) { return this.entities.containsKey(entityId) ? this.entities.get(entityId) : null; } public Entity[] getEntities() { return entities.values().toArray(Entity.EMPTY_ARRAY); } public Entity[] getCollidingEntities(AxisAlignedBB bb) { return this.getCollidingEntities(bb, null); } public Entity[] getCollidingEntities(AxisAlignedBB bb, Entity entity) { int index = 0; ArrayList overflow = null; if (entity == null || entity.canCollide()) { int minX = NukkitMath.floorDouble((bb.getMinX() - 2) / 16); int maxX = NukkitMath.ceilDouble((bb.getMaxX() + 2) / 16); int minZ = NukkitMath.floorDouble((bb.getMinZ() - 2) / 16); int maxZ = NukkitMath.ceilDouble((bb.getMaxZ() + 2) / 16); for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { for (Entity ent : this.getChunkEntities(x, z, false).values()) { if ((entity == null || (ent != entity && entity.canCollideWith(ent))) && ent.boundingBox.intersectsWith(bb)) { overflow = addEntityToBuffer(index, overflow, ent); index++; } } } } } return getEntitiesFromBuffer(index, overflow); } public List fastCollidingEntities(AxisAlignedBB bb) { return this.fastCollidingEntities(bb, null); } public List fastCollidingEntities(AxisAlignedBB bb, Entity entity) { var result = new ArrayList(); if (entity == null || entity.canCollide()) { int minX = NukkitMath.floorDouble((bb.getMinX() - 2) / 16); int maxX = NukkitMath.ceilDouble((bb.getMaxX() + 2) / 16); int minZ = NukkitMath.floorDouble((bb.getMinZ() - 2) / 16); int maxZ = NukkitMath.ceilDouble((bb.getMaxZ() + 2) / 16); for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { for (var each : this.getChunkEntities(x, z, false).values()) { if ((entity == null || (each != entity && entity.canCollideWith(each))) && each.boundingBox.intersectsWith(bb)) { result.add(each); } } } } } return result; } public Stream streamCollidingEntities(AxisAlignedBB bb, Entity entity) { if (entity == null || entity.canCollide()) { int minX = NukkitMath.floorDouble((bb.getMinX() - 2) / 16); int maxX = NukkitMath.ceilDouble((bb.getMaxX() + 2) / 16); int minZ = NukkitMath.floorDouble((bb.getMinZ() - 2) / 16); int maxZ = NukkitMath.ceilDouble((bb.getMaxZ() + 2) / 16); var allEntities = new ArrayList(); for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { allEntities.addAll(this.getChunkEntities(x, z, false).values()); } } return allEntities.stream().filter(each -> (entity == null || (each != entity && entity.canCollideWith(each))) && each.boundingBox.intersectsWith(bb)); } else { return Stream.empty(); } } public Entity[] getNearbyEntities(AxisAlignedBB bb) { return this.getNearbyEntities(bb, null); } public Entity[] getNearbyEntities(AxisAlignedBB bb, Entity entity) { return getNearbyEntities(bb, entity, false); } public Entity[] getNearbyEntities(AxisAlignedBB bb, Entity entity, boolean loadChunks) { int index = 0; int minX = NukkitMath.floorDouble((bb.getMinX() - 2) * 0.0625); int maxX = NukkitMath.ceilDouble((bb.getMaxX() + 2) * 0.0625); int minZ = NukkitMath.floorDouble((bb.getMinZ() - 2) * 0.0625); int maxZ = NukkitMath.ceilDouble((bb.getMaxZ() + 2) * 0.0625); ArrayList overflow = null; for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { for (Entity ent : this.getChunkEntities(x, z, loadChunks).values()) { if (ent != entity && ent.boundingBox.intersectsWith(bb)) { overflow = addEntityToBuffer(index, overflow, ent); index++; } } } } return getEntitiesFromBuffer(index, overflow); } public List fastNearbyEntities(AxisAlignedBB bb) { return this.fastNearbyEntities(bb, null); } public List fastNearbyEntities(AxisAlignedBB bb, Entity entity) { return fastNearbyEntities(bb, entity, false); } public List fastNearbyEntities(AxisAlignedBB bb, Entity entity, boolean loadChunks) { int minX = NukkitMath.floorDouble((bb.getMinX() - 2) * 0.0625); int maxX = NukkitMath.ceilDouble((bb.getMaxX() + 2) * 0.0625); int minZ = NukkitMath.floorDouble((bb.getMinZ() - 2) * 0.0625); int maxZ = NukkitMath.ceilDouble((bb.getMaxZ() + 2) * 0.0625); var result = new ArrayList(); for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { for (var ent : this.getChunkEntities(x, z, loadChunks).values()) { if (ent != entity && ent.boundingBox.intersectsWith(bb)) { result.add(ent); } } } } return result; } private ArrayList addEntityToBuffer(int index, ArrayList overflow, Entity ent) { if (index < ENTITY_BUFFER.length) { ENTITY_BUFFER[index] = ent; } else { if (overflow == null) overflow = new ArrayList<>(1024); overflow.add(ent); } return overflow; } private Entity[] getEntitiesFromBuffer(int index, ArrayList overflow) { if (index == 0) return Entity.EMPTY_ARRAY; Entity[] copy; if (overflow == null) { copy = Arrays.copyOfRange(ENTITY_BUFFER, 0, index); Arrays.fill(ENTITY_BUFFER, 0, index, null); } else { copy = new Entity[ENTITY_BUFFER.length + overflow.size()]; System.arraycopy(ENTITY_BUFFER, 0, copy, 0, ENTITY_BUFFER.length); for (int i = 0; i < overflow.size(); i++) { copy[ENTITY_BUFFER.length + i] = overflow.get(i); } } return copy; } public Map getBlockEntities() { return blockEntities; } public BlockEntity getBlockEntityById(long blockEntityId) { return this.blockEntities.containsKey(blockEntityId) ? this.blockEntities.get(blockEntityId) : null; } public Map getPlayers() { return players; } public Map getLoaders() { return loaders; } public BlockEntity getBlockEntity(Vector3 pos) { return getBlockEntity(pos.asBlockVector3()); } @PowerNukkitOnly public BlockEntity getBlockEntity(BlockVector3 pos) { FullChunk chunk = this.getChunk(pos.x >> 4, pos.z >> 4, false); if (chunk != null) { return chunk.getTile(pos.x & 0x0f, ensureY(pos.y), pos.z & 0x0f); } return null; } public BlockEntity getBlockEntityIfLoaded(Vector3 pos) { FullChunk chunk = this.getChunkIfLoaded((int) pos.x >> 4, (int) pos.z >> 4); if (chunk != null) { return chunk.getTile((int) pos.x & 0x0f, ensureY((int) pos.y), (int) pos.z & 0x0f); } return null; } public Map getChunkEntities(int X, int Z) { return getChunkEntities(X, Z, true); } public Map getChunkEntities(int X, int Z, boolean loadChunks) { FullChunk chunk = loadChunks ? this.getChunk(X, Z) : this.getChunkIfLoaded(X, Z); return chunk != null ? chunk.getEntities() : Collections.emptyMap(); } public Map getChunkBlockEntities(int X, int Z) { FullChunk chunk; return (chunk = this.getChunk(X, Z)) != null ? chunk.getBlockEntities() : Collections.emptyMap(); } @PowerNukkitOnly @Override public BlockState getBlockStateAt(int x, int y, int z, int layer) { return getChunk(x >> 4, z >> 4, true).getBlockStateAt(x & 0x0f, ensureY(y), z & 0x0f, layer); } @Override public int getBlockIdAt(int x, int y, int z) { return getBlockIdAt(x, y, z, 0); } @PowerNukkitOnly @Override public int getBlockIdAt(int x, int y, int z, int layer) { return this.getChunk(x >> 4, z >> 4, true).getBlockId(x & 0x0f, ensureY(y), z & 0x0f, layer); } @Override public void setBlockIdAt(int x, int y, int z, int id) { setBlockIdAt(x, y, z, 0, id); } @PowerNukkitOnly @Override public void setBlockIdAt(int x, int y, int z, int layer, int id) { this.getChunk(x >> 4, z >> 4, true).setBlockId(x & 0x0f, ensureY(y), z & 0x0f, layer, id & 0xfff); addBlockChange(x, y, z); temporalVector.setComponents(x, y, z); for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { loader.onBlockChanged(temporalVector); } } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @Override public void setBlockAt(int x, int y, int z, int id, int data) { setBlockAtLayer(x, y, z, 0, id, data); } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @PowerNukkitOnly @Override public boolean setBlockAtLayer(int x, int y, int z, int layer, int id, int data) { return setBlockStateAt(x, y, z, layer, BlockState.of(id, data)); } @Override @PowerNukkitOnly @Since("1.4.0.0-PN") public boolean setBlockStateAt(int x, int y, int z, int layer, BlockState state) { BaseFullChunk chunk = this.getChunk(x >> 4, z >> 4, true); boolean changed = chunk.setBlockStateAtLayer(x & 0x0f, ensureY(y), z & 0x0f, layer, state); addBlockChange(x, y, z); temporalVector.setComponents(x, y, z); for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { loader.onBlockChanged(temporalVector); } return changed; } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @Override public int getBlockDataAt(int x, int y, int z) { return getBlockDataAt(x, y, z, 0); } public int getBlockExtraDataAt(int x, int y, int z) { return this.getChunk(x >> 4, z >> 4, true).getBlockExtraData(x & 0x0f, ensureY(y), z & 0x0f); } public void setBlockExtraDataAt(int x, int y, int z, int id, int data) { this.getChunk(x >> 4, z >> 4, true).setBlockExtraData(x & 0x0f, ensureY(y), z & 0x0f, (data << 8) | id); this.sendBlockExtraData(x, y, z, id, data); } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @PowerNukkitOnly @Override public int getBlockDataAt(int x, int y, int z, int layer) { return this.getChunk(x >> 4, z >> 4, true).getBlockData(x & 0x0f, ensureY(y), z & 0x0f, layer); } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @Override public void setBlockDataAt(int x, int y, int z, int data) { setBlockDataAt(x, y, z, 0, data); } @Deprecated @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN") @PowerNukkitOnly @Override public void setBlockDataAt(int x, int y, int z, int layer, int data) { this.getChunk(x >> 4, z >> 4, true).setBlockData(x & 0x0f, ensureY(y), z & 0x0f, layer, data); addBlockChange(x, y, z); temporalVector.setComponents(x, y, z); for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { loader.onBlockChanged(temporalVector); } } public synchronized int getBlockSkyLightAt(int x, int y, int z) { return this.getChunk(x >> 4, z >> 4, true).getBlockSkyLight(x & 0x0f, ensureY(y), z & 0x0f); } public synchronized void setBlockSkyLightAt(int x, int y, int z, int level) { this.getChunk(x >> 4, z >> 4, true).setBlockSkyLight(x & 0x0f, ensureY(y), z & 0x0f, level & 0x0f); } public synchronized int getBlockLightAt(int x, int y, int z) { return this.getChunk(x >> 4, z >> 4, true).getBlockLight(x & 0x0f, ensureY(y), z & 0x0f); } public synchronized void setBlockLightAt(int x, int y, int z, int level) { this.getChunk(x >> 4, z >> 4, true).setBlockLight(x & 0x0f, ensureY(y), z & 0x0f, level & 0x0f); } public int getBiomeId(int x, int z) { return this.getChunk(x >> 4, z >> 4, true).getBiomeId(x & 0x0f, z & 0x0f); } public void setBiomeId(int x, int z, byte biomeId) { this.getChunk(x >> 4, z >> 4, true).setBiomeId(x & 0x0f, z & 0x0f, biomeId); } @PowerNukkitXOnly @Since("1.19.20-r3") public int getBiomeId(int x, int y, int z) { return this.getChunk(x >> 4, z >> 4, true).getBiomeId(x & 0x0f, y, z & 0x0f); } @PowerNukkitXOnly @Since("1.19.20-r3") public void setBiomeId(int x, int y, int z, byte biomeId) { this.getChunk(x >> 4, z >> 4, true).setBiomeId(x & 0x0f, y, z & 0x0f, biomeId); } public int getHeightMap(int x, int z) { return this.getChunk(x >> 4, z >> 4, true).getHeightMap(x & 0x0f, z & 0x0f); } public void setHeightMap(int x, int z, int value) { this.getChunk(x >> 4, z >> 4, true).setHeightMap(x & 0x0f, z & 0x0f, value & 0x0f); } public Map getChunks() { return requireProvider().getLoadedChunks(); } @Override public BaseFullChunk getChunk(int chunkX, int chunkZ) { return this.getChunk(chunkX, chunkZ, false); } public BaseFullChunk getChunk(int chunkX, int chunkZ, boolean create) { long index = Level.chunkHash(chunkX, chunkZ); BaseFullChunk chunk = this.requireProvider().getLoadedChunk(index); if (chunk == null) { chunk = this.forceLoadChunk(index, chunkX, chunkZ, create); } return chunk; } public BaseFullChunk getChunkIfLoaded(int chunkX, int chunkZ) { long index = Level.chunkHash(chunkX, chunkZ); return this.requireProvider().getLoadedChunk(index); } public void generateChunkCallback(int x, int z, BaseFullChunk chunk) { generateChunkCallback(x, z, chunk, true); } public void generateChunkCallback(int x, int z, BaseFullChunk chunk, boolean isPopulated) { Timings.generationCallbackTimer.startTiming(); long index = Level.chunkHash(x, z); LevelProvider levelProvider = this.requireProvider(); if (this.chunkPopulationQueue.containsKey(index)) { FullChunk oldChunk = this.getChunk(x, z, false); for (int xx = -1; xx <= 1; ++xx) { for (int zz = -1; zz <= 1; ++zz) { this.chunkPopulationLock.remove(Level.chunkHash(x + xx, z + zz)); } } this.chunkPopulationQueue.remove(index); chunk.setProvider(levelProvider); this.setChunk(x, z, chunk, false); chunk = this.getChunk(x, z, false); if (chunk != null && (oldChunk == null || !isPopulated) && chunk.isPopulated() && chunk.getProvider() != null) { this.server.getPluginManager().callEvent(new ChunkPopulateEvent(chunk)); for (ChunkLoader loader : this.getChunkLoaders(x, z)) { loader.onChunkPopulated(chunk); } } } else if (this.chunkGenerationQueue.containsKey(index) || this.chunkPopulationLock.containsKey(index)) { this.chunkGenerationQueue.remove(index); this.chunkPopulationLock.remove(index); chunk.setProvider(levelProvider); this.setChunk(x, z, chunk, false); } else { chunk.setProvider(levelProvider); this.setChunk(x, z, chunk, false); } Timings.generationCallbackTimer.stopTiming(); } @Override public void setChunk(int chunkX, int chunkZ) { this.setChunk(chunkX, chunkZ, null); } @Override public void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk) { this.setChunk(chunkX, chunkZ, chunk, true); } public void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk, boolean unload) { if (chunk == null) { return; } long index = Level.chunkHash(chunkX, chunkZ); FullChunk oldChunk = this.getChunk(chunkX, chunkZ, false); if (oldChunk != chunk) { if (unload && oldChunk != null) { this.unloadChunk(chunkX, chunkZ, false, false); this.requireProvider().setChunk(chunkX, chunkZ, chunk); } else { Map oldEntities = oldChunk != null ? oldChunk.getEntities() : Collections.emptyMap(); Map oldBlockEntities = oldChunk != null ? oldChunk.getBlockEntities() : Collections.emptyMap(); if (!oldEntities.isEmpty()) { Iterator> iter = oldEntities.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); Entity entity = entry.getValue(); chunk.addEntity(entity); if (oldChunk != null) { iter.remove(); oldChunk.removeEntity(entity); entity.chunk = chunk; } } } if (!oldBlockEntities.isEmpty()) { Iterator> iter = oldBlockEntities.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); BlockEntity blockEntity = entry.getValue(); chunk.addBlockEntity(blockEntity); if (oldChunk != null) { iter.remove(); oldChunk.removeBlockEntity(blockEntity); blockEntity.chunk = chunk; } } } this.requireProvider().setChunk(chunkX, chunkZ, chunk); } } chunk.setChanged(); if (!this.isChunkInUse(index)) { this.unloadChunkRequest(chunkX, chunkZ); } else { for (ChunkLoader loader : this.getChunkLoaders(chunkX, chunkZ)) { loader.onChunkChanged(chunk); } } } public int getHighestBlockAt(int x, int z) { return this.getChunk(x >> 4, z >> 4, true).getHighestBlockAt(x & 0x0f, z & 0x0f); } protected static final BlockColor VOID_BLOCK_COLOR = BlockColor.VOID_BLOCK_COLOR; protected static final BlockColor WATER_BLOCK_COLOR = BlockColor.WATER_BLOCK_COLOR; @PowerNukkitXDifference(info = "使用新的颜色算法", since = "1.19.80-r3") public BlockColor getMapColorAt(int x, int z) { var color = VOID_BLOCK_COLOR.toAwtColor(); var block = getMapColoredBlockAt(x, z); if (block == null) return VOID_BLOCK_COLOR; //在z轴存在高度差的地方,颜色变深或变浅 var nzy = getMapColoredBlockAt(x, z - 1); if (nzy == null) return block.getColor(); color = block.getColor().toAwtColor(); if (nzy.getFloorY() > block.getFloorY()) { color = darker(color, 0.875 - Math.min(5, nzy.getFloorY() - block.getFloorY()) * 0.05); } else if (nzy.getFloorY() < block.getFloorY()) { color = brighter(color, 0.875 - Math.min(5, block.getFloorY() - nzy.getFloorY()) * 0.05); } var deltaY = block.y - 128; if (deltaY > 0) { color = brighter(color, 1 - deltaY / (192 * 3)); } else if (deltaY < 0) { color = darker(color, 1 - (-deltaY) / (192 * 3)); } var up = block.getSide(BlockFace.UP); var up1 = block.getSideAtLayer(1, BlockFace.UP); if (up instanceof BlockWater || up1 instanceof BlockWater) { var r1 = color.getRed(); var g1 = color.getGreen(); var b1 = color.getBlue(); //在水下 if (block.y < 62) { //在海平面下 //海平面为62格。离海平面越远颜色越接近海洋颜色 var depth = 62 - block.y; if (depth > 96) return WATER_BLOCK_COLOR; b1 = WATER_BLOCK_COLOR.getBlue(); var radio = (depth / 96.0); if (radio < 0.5) radio = 0.5; r1 += (WATER_BLOCK_COLOR.getRed() - r1) * radio; g1 += (WATER_BLOCK_COLOR.getGreen() - g1) * radio; } else { //湖泊 or 河流 b1 = WATER_BLOCK_COLOR.getBlue(); r1 += (WATER_BLOCK_COLOR.getRed() - r1) * 0.5; g1 += (WATER_BLOCK_COLOR.getGreen() - g1) * 0.5; } color = new Color(r1, g1, b1); } return new BlockColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); } protected Color brighter(Color source, double factor) { int r = source.getRed(); int g = source.getGreen(); int b = source.getBlue(); int alpha = source.getAlpha(); int i = (int)(1.0/(1.0-factor)); if ( r == 0 && g == 0 && b == 0) { return new Color(i, i, i, alpha); } if ( r > 0 && r < i ) r = i; if ( g > 0 && g < i ) g = i; if ( b > 0 && b < i ) b = i; return new Color(Math.min((int)(r/factor), 255), Math.min((int)(g/factor), 255), Math.min((int)(b/factor), 255), alpha); } protected Color darker(Color source, double factor) { return new Color(Math.max((int)(source.getRed()*factor), 0), Math.max((int)(source.getGreen()*factor), 0), Math.max((int)(source.getBlue()*factor), 0), source.getAlpha()); } protected Block getMapColoredBlockAt(int x, int z) { var chunk = getChunk(x >> 4, z >> 4); if (chunk == null) return null; var chunkX = x & 0xF; var chunkZ = z & 0xF; //TODO: 地形生成器不会更新heightMap,按理说这边的cache应该是true。需要等待heightMap的384适配以及地形生成器的heightMap修复 int y = chunk.getHighestBlockAt(chunkX, chunkZ, false); while (y > getMinHeight()) { Block block = getBlock(x, y, z); if (block.getColor() == null) return null; if (block.getColor().getAlpha() < 255 || block instanceof BlockWater) { y--; } else { return block; } } return null; } public boolean isChunkLoaded(int x, int z) { return this.requireProvider().isChunkLoaded(x, z); } private boolean areNeighboringChunksLoaded(long hash) { LevelProvider levelProvider = this.requireProvider(); return levelProvider.isChunkLoaded(hash + 1) && levelProvider.isChunkLoaded(hash - 1) && levelProvider.isChunkLoaded(hash + (1L << 32)) && levelProvider.isChunkLoaded(hash - (1L << 32)); } public boolean isChunkGenerated(int x, int z) { FullChunk chunk = this.getChunk(x, z); return chunk != null && chunk.isGenerated(); } public boolean isChunkPopulated(int x, int z) { FullChunk chunk = this.getChunk(x, z); return chunk != null && chunk.isPopulated(); } public Position getSpawnLocation() { return Position.fromObject(this.requireProvider().getSpawn(), this); } public void setSpawnLocation(Vector3 pos) { Position previousSpawn = this.getSpawnLocation(); this.requireProvider().setSpawn(pos); this.server.getPluginManager().callEvent(new SpawnChangeEvent(this, previousSpawn)); SetSpawnPositionPacket pk = new SetSpawnPositionPacket(); pk.spawnType = SetSpawnPositionPacket.TYPE_WORLD_SPAWN; pk.x = pos.getFloorX(); pk.y = pos.getFloorY(); pk.z = pos.getFloorZ(); pk.dimension = getDimension(); for (Player p : getPlayers().values()) { p.dataPacket(pk); } } @PowerNukkitOnly public Position getFuzzySpawnLocation() { Position spawn = getSpawnLocation(); int radius = gameRules.getInteger(GameRule.SPAWN_RADIUS); if (radius > 0) { ThreadLocalRandom random = ThreadLocalRandom.current(); int negativeFlags = random.nextInt(4); spawn = spawn.add( radius * random.nextDouble() * ((negativeFlags & 1) > 0 ? -1 : 1), 0, radius * random.nextDouble() * ((negativeFlags & 2) > 0 ? -1 : 1) ); } return spawn; } public void requestChunk(int x, int z, Player player) { Preconditions.checkState(player.getLoaderId() > 0, player.getName() + " has no chunk loader"); long index = Level.chunkHash(x, z); Int2ObjectMap playerInt2ObjectMap = this.chunkSendQueue.computeIfAbsent(index, (key) -> new Int2ObjectOpenHashMap<>()); playerInt2ObjectMap.put(player.getLoaderId(), player); } private void sendChunk(int x, int z, long index, DataPacket packet) { for (Player player : this.chunkSendQueue.get(index).values()) { if (player.isConnected() && player.usedChunks.containsKey(index)) { player.sendChunk(x, z, packet); } } this.chunkSendQueue.remove(index); } @PowerNukkitXOnly @Since("1.19.70-r2") private final ArrayList> allChunkRequestTask = new ArrayList<>( Server.getInstance().getConfig("chunk-sending.per-tick", 8) * Server.getInstance().getMaxPlayers() ); private void processChunkRequest() { this.timings.syncChunkSendTimer.startTiming(); this.timings.syncChunkSendPrepareTimer.startTiming(); for (long index : this.chunkSendQueue.keySet()) { int x = getHashX(index); int z = getHashZ(index); BaseFullChunk chunk = getChunk(x, z); if (chunk != null) { BatchPacket packet = chunk.getChunkPacket(); if (packet != null) { this.sendChunk(x, z, index, packet); continue; } } AsyncTask task = this.requireProvider().requestChunkTask(x, z); if (task != null) { allChunkRequestTask.add(CompletableFuture.runAsync(task, server.computeThreadPool)); } } this.timings.syncChunkSendPrepareTimer.stopTiming(); if (!allChunkRequestTask.isEmpty()) { CompletableFuture.allOf(allChunkRequestTask.toArray(CompletableFuture[]::new)).join(); allChunkRequestTask.clear(); } this.timings.syncChunkSendTimer.stopTiming(); } public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount, byte[] payload) { this.timings.syncChunkSendTimer.startTiming(); long index = Level.chunkHash(x, z); if (this.cacheChunks) { BatchPacket data = Player.getChunkCacheFromData(x, z, subChunkCount, payload); BaseFullChunk chunk = getChunk(x, z, false); if (chunk != null && chunk.getChanges() <= timestamp) { chunk.setChunkPacket(data); } this.sendChunk(x, z, index, data); this.timings.syncChunkSendTimer.stopTiming(); return; } for (Player player : this.chunkSendQueue.get(index).values()) { if (player.isConnected() && player.usedChunks.containsKey(index)) { player.sendChunk(x, z, subChunkCount, payload); } } this.chunkSendQueue.remove(index); this.timings.syncChunkSendTimer.stopTiming(); } public void removeEntity(Entity entity) { if (entity.getLevel() != this) { throw new LevelException("Invalid Entity level"); } if (entity instanceof Player) { this.players.remove(entity.getId()); this.checkSleep(); } else { entity.close(); } this.entities.remove(entity.getId()); this.updateEntities.remove(entity.getId()); } public void addEntity(Entity entity) { if (entity.getLevel() != this) { throw new LevelException("Invalid Entity level"); } if (entity instanceof Player) { this.players.put(entity.getId(), (Player) entity); } this.entities.put(entity.getId(), entity); } public void addBlockEntity(BlockEntity blockEntity) { if (blockEntity.getLevel() != this) { throw new LevelException("Invalid Block Entity level"); } blockEntities.put(blockEntity.getId(), blockEntity); } public void scheduleBlockEntityUpdate(BlockEntity entity) { Preconditions.checkNotNull(entity, "entity"); Preconditions.checkArgument(entity.getLevel() == this, "BlockEntity is not in this level"); if (!updateBlockEntities.contains(entity)) { updateBlockEntities.add(entity); } } public void removeBlockEntity(BlockEntity entity) { Preconditions.checkNotNull(entity, "entity"); Preconditions.checkArgument(entity.getLevel() == this, "BlockEntity is not in this level"); blockEntities.remove(entity.getId()); updateBlockEntities.remove(entity); } /** * 该区块是否在使用中,出生点区块,tick区域中的区块,以及存在{@link ChunkLoader}的区块都被看做正在使用 *

* Whether the chunk is in use, spawn chunks, chunks in the tick area, and chunks with {@link ChunkLoader} are considered in use * * @param x the chunk x * @param z the chunk z * @return the boolean */ public boolean isChunkInUse(int x, int z) { if (isSpawnChunk(x, z)) { return true; } var tickingAreaManager = getServer().getTickingAreaManager(); if (tickingAreaManager != null && tickingAreaManager.getTickingAreaByChunk(this.getName(), new TickingArea.ChunkPos(x, z)) != null) { return true; } return isChunkInUse(Level.chunkHash(x, z)); } /** * 该区块是否在使用中,出生点区块,tick区域中的区块,以及存在{@link ChunkLoader}的区块都被看做正在使用 *

* Whether the chunk is in use, spawn chunks, chunks in the tick area, and chunks with {@link ChunkLoader} are considered in use * * @param hash chunk hash value from {@link #chunkHash(int, int)} * @return the boolean */ public boolean isChunkInUse(long hash) { if (isSpawnChunk(getHashX(hash), getHashZ(hash))) { return true; } var tickingAreaManager = getServer().getTickingAreaManager(); if (tickingAreaManager != null && tickingAreaManager.getTickingAreaByChunk(this.getName(), new TickingArea.ChunkPos(getHashX(hash), getHashZ(hash))) != null) { return true; } return this.chunkLoaders.containsKey(hash) && !this.chunkLoaders.get(hash).isEmpty(); } public boolean loadChunk(int x, int z) { return this.loadChunk(x, z, true); } public boolean loadChunk(int x, int z, boolean generate) { long index = Level.chunkHash(x, z); if (this.requireProvider().isChunkLoaded(index)) { return true; } return forceLoadChunk(index, x, z, generate) != null; } private synchronized BaseFullChunk forceLoadChunk(long index, int x, int z, boolean generate) { this.timings.syncChunkLoadTimer.startTiming(); BaseFullChunk chunk = this.requireProvider().getChunk(x, z, generate); if (chunk == null) { if (generate) { throw new IllegalStateException("Could not create new Chunk"); } this.timings.syncChunkLoadTimer.stopTiming(); return null; } if (chunk.getProvider() != null) { this.server.getPluginManager().callEvent(new ChunkLoadEvent(chunk, !chunk.isGenerated())); } else { this.unloadChunk(x, z, false); this.timings.syncChunkLoadTimer.stopTiming(); return chunk; } chunk.backwardCompatibilityUpdate(this); chunk.initChunk(); if (!chunk.isLightPopulated() && chunk.isPopulated() && this.getServer().getConfig("chunk-ticking.light-updates", false)) { this.getServer().computeThreadPool.submit(new LightPopulationTask(this, chunk)); } if (this.isChunkInUse(index)) { this.unloadQueue.remove(index); for (ChunkLoader loader : this.getChunkLoaders(x, z)) { loader.onChunkLoaded(chunk); } } else { this.unloadQueue.put(index, System.currentTimeMillis()); } this.timings.syncChunkLoadTimer.stopTiming(); return chunk; } private void queueUnloadChunk(int x, int z) { long index = Level.chunkHash(x, z); this.unloadQueue.put(index, System.currentTimeMillis()); } public boolean unloadChunkRequest(int x, int z) { return this.unloadChunkRequest(x, z, true); } public boolean unloadChunkRequest(int x, int z, boolean safe) { if ((safe && this.isChunkInUse(x, z))) { return false; } this.queueUnloadChunk(x, z); return true; } public void cancelUnloadChunkRequest(int x, int z) { this.cancelUnloadChunkRequest(Level.chunkHash(x, z)); } public void cancelUnloadChunkRequest(long hash) { this.unloadQueue.remove(hash); } public boolean unloadChunk(int x, int z) { return this.unloadChunk(x, z, true); } public boolean unloadChunk(int x, int z, boolean safe) { return this.unloadChunk(x, z, safe, true); } public synchronized boolean unloadChunk(int x, int z, boolean safe, boolean trySave) { if (safe && this.isChunkInUse(x, z)) { return false; } if (!this.isChunkLoaded(x, z)) { return true; } this.timings.doChunkUnload.startTiming(); BaseFullChunk chunk = this.getChunk(x, z); if (chunk != null && chunk.getProvider() != null) { ChunkUnloadEvent ev = new ChunkUnloadEvent(chunk); this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { this.timings.doChunkUnload.stopTiming(); return false; } } try { LevelProvider levelProvider = this.requireProvider(); if (chunk != null) { if (trySave && this.getAutoSave()) { int entities = 0; for (Entity e : chunk.getEntities().values()) { if (e instanceof Player) { continue; } ++entities; } if (chunk.hasChanged() || !chunk.getBlockEntities().isEmpty() || entities > 0) { levelProvider.setChunk(x, z, chunk); levelProvider.saveChunk(x, z); } } for (ChunkLoader loader : this.getChunkLoaders(x, z)) { loader.onChunkUnloaded(chunk); } } levelProvider.unloadChunk(x, z, safe); } catch (Exception e) { log.error(this.server.getLanguage().tr("nukkit.level.chunkUnloadError", e.toString()), e); } this.timings.doChunkUnload.stopTiming(); return true; } public boolean isSpawnChunk(int X, int Z) { Vector3 spawn = this.requireProvider().getSpawn(); return Math.abs(X - (spawn.getFloorX() >> 4)) <= 1 && Math.abs(Z - (spawn.getFloorZ() >> 4)) <= 1; } public Position getSafeSpawn() { return getSafeSpawn(null); } public Position getSafeSpawn(Vector3 spawn) { return getSafeSpawn(spawn, 16); } public Position getSafeSpawn(Vector3 spawn, int horizontalMaxOffset) { return getSafeSpawn(spawn, horizontalMaxOffset, true); } public Position getSafeSpawn(Vector3 spawn, int horizontalMaxOffset, boolean allowWaterUnder) { if (spawn == null) spawn = this.getFuzzySpawnLocation(); if (spawn == null) return null; if (standable(spawn, true)) return Position.fromObject(spawn, this); int maxY = isNether() ? 127 : (isOverWorld() ? 319 : 255); int minY = isOverWorld() ? -64 : 0; for (int horizontalOffset = 0; horizontalOffset <= horizontalMaxOffset; horizontalOffset++) { for (int y = maxY; y > minY; y--) { Position pos = Position.fromObject(spawn, this); pos.setY(y); Position newSpawn; if (standable(newSpawn = pos.add(horizontalOffset, 0, horizontalOffset), allowWaterUnder)) return newSpawn; if (standable(newSpawn = pos.add(horizontalOffset, 0, -horizontalOffset), allowWaterUnder)) return newSpawn; if (standable(newSpawn = pos.add(-horizontalOffset, 0, horizontalOffset), allowWaterUnder)) return newSpawn; if (standable(newSpawn = pos.add(-horizontalOffset, 0, -horizontalOffset), allowWaterUnder)) return newSpawn; } } log.warn("cannot find a safe spawn around " + spawn.asBlockVector3() + "!"); return Position.fromObject(spawn, this); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public boolean standable(Vector3 vec) { return standable(vec, false); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public boolean standable(Vector3 vec, boolean allowWaterUnder) { Position pos = Position.fromObject(vec, this); Block blockUnder = pos.add(0, -1, 0).getLevelBlock(0, true); Block block = pos.getLevelBlock(0, true); Block blockUpper = pos.add(0, 1, 0).getLevelBlock(0, true); if (!allowWaterUnder) return !blockUnder.canPassThrough() && (block.getId() == BlockID.AIR || block.canPassThrough()) && (blockUpper.getId() == BlockID.AIR || block.canPassThrough()); else return (!blockUnder.canPassThrough() || blockUnder.getId() == BlockID.WATER || blockUnder.getId() == BlockID.WATER_LILY || blockUnder.getId() == BlockID.STILL_WATER) && (block.getId() == BlockID.AIR || block.canPassThrough()) && (blockUpper.getId() == BlockID.AIR || block.canPassThrough()); } /** * 获取这个地图经历的时间(一直会累加) *

* Get the elapsed time for this level */ public int getTime() { return (int) time; } /** * 设置这个地图经历的时间 *

* Set the elapsed time for this level */ public void setTime(int time) { this.time = time; this.sendTime(); } public boolean isDaytime() { return this.skyLightSubtracted < 4; } public long getCurrentTick() { return this.levelCurrentTick; } public String getName() { return this.requireProvider().getName(); } public String getFolderName() { return this.folderName; } public void stopTime() { this.stopTime = true; this.sendTime(); } public void startTime() { this.stopTime = false; this.sendTime(); } @Override public long getSeed() { return this.requireProvider().getSeed(); } public void setSeed(int seed) { this.requireProvider().setSeed(seed); } public boolean populateChunk(int x, int z) { return this.populateChunk(x, z, false); } public boolean populateChunk(int x, int z, boolean force) { long index = Level.chunkHash(x, z); if (this.chunkPopulationQueue.containsKey(index) || this.chunkPopulationQueue.size() >= this.chunkPopulationQueueSize && !force) { return false; } BaseFullChunk chunk = this.getChunk(x, z, true); boolean populate; if (!chunk.isPopulated()) { Timings.populationTimer.startTiming(); populate = true; for (int xx = -1; xx <= 1; ++xx) { for (int zz = -1; zz <= 1; ++zz) { if (this.chunkPopulationLock.containsKey(Level.chunkHash(x + xx, z + zz))) { populate = false; break; } } } if (populate) { if (!this.chunkPopulationQueue.containsKey(index)) { this.chunkPopulationQueue.put(index, Boolean.TRUE); for (int xx = -1; xx <= 1; ++xx) { for (int zz = -1; zz <= 1; ++zz) { this.chunkPopulationLock.put(Level.chunkHash(x + xx, z + zz), Boolean.TRUE); } } PopulationTask task = new PopulationTask(this, chunk); getGenerator().handleAsyncChunkPopTask(task); } } Timings.populationTimer.stopTiming(); return false; } return true; } public void generateChunk(int x, int z) { this.generateChunk(x, z, false); } public void generateChunk(int x, int z, boolean force) { if (this.chunkGenerationQueue.size() >= this.chunkGenerationQueueSize && !force) { return; } long index = Level.chunkHash(x, z); if (!this.chunkGenerationQueue.containsKey(index)) { Timings.generationTimer.startTiming(); this.chunkGenerationQueue.put(index, Boolean.TRUE); GenerationTask task = new GenerationTask(this, this.getChunk(x, z, true)); getGenerator().handleAsyncChunkPopTask(task); Timings.generationTimer.stopTiming(); } } public void regenerateChunk(int x, int z) { this.unloadChunk(x, z, false); this.cancelUnloadChunkRequest(x, z); LevelProvider levelProvider = requireProvider(); BaseFullChunk chunk = levelProvider.getEmptyChunk(x, z); levelProvider.setChunk(x, z, chunk); this.generateChunk(x, z); } /** * 异步执行服务器内存垃圾收集 *

* Run server memory garbage collection asynchronously * * @return the list */ @PowerNukkitXInternal public List> asyncChunkGarbageCollection() { this.timings.doChunkGC.startTiming(); var gcBlockEntities = CompletableFuture.runAsync(() -> { // remove all invaild block entities. if (!blockEntities.isEmpty()) { ObjectIterator iter = blockEntities.values().iterator(); while (iter.hasNext()) { BlockEntity blockEntity = iter.next(); if (blockEntity != null) { if (!blockEntity.isValid()) { iter.remove(); blockEntity.close(); } } else { iter.remove(); } } } }); var gcDeadChunks = CompletableFuture.runAsync(() -> { for (Map.Entry entry : requireProvider().getLoadedChunks().entrySet()) { long index = entry.getKey(); if (!this.unloadQueue.containsKey(index)) { FullChunk chunk = entry.getValue(); int X = chunk.getX(); int Z = chunk.getZ(); this.unloadChunkRequest(X, Z, true); } } this.unloadChunks(); }); var gcSuper = CompletableFuture.runAsync(() -> this.requireProvider().doGarbageCollection()); this.timings.doChunkGC.stopTiming(); return List.of(gcBlockEntities, gcDeadChunks, gcSuper); } /** * 异步执行服务器内存垃圾收集 *

* Run server memory garbage collection synchronously */ @PowerNukkitXInternal public void doChunkGarbageCollection() { doChunkGarbageCollection(false); } /** * 同步执行服务器内存垃圾收集 *

* Run server memory garbage collection synchronously * * @param force the force */ @PowerNukkitXOnly @PowerNukkitXInternal public void doChunkGarbageCollection(boolean force) { this.timings.doChunkGC.startTiming(); // remove all invaild block entities. if (!blockEntities.isEmpty()) { ObjectIterator iter = blockEntities.values().iterator(); while (iter.hasNext()) { BlockEntity blockEntity = iter.next(); if (blockEntity != null) { if (!blockEntity.isValid()) { iter.remove(); blockEntity.close(); } } else { iter.remove(); } } } for (Map.Entry entry : requireProvider().getLoadedChunks().entrySet()) { long index = entry.getKey(); if (!this.unloadQueue.containsKey(index)) { FullChunk chunk = entry.getValue(); int X = chunk.getX(); int Z = chunk.getZ(); this.unloadChunkRequest(X, Z, true); } } this.unloadChunks(force); this.requireProvider().doGarbageCollection(); this.timings.doChunkGC.stopTiming(); } /** * 在一些空闲的时间片对Level进行内存垃圾收集 *

* Run memory garbage collection on Level in some free time slices * * @param allocatedTime free time slices */ @PowerNukkitXInternal public void doGarbageCollection(long allocatedTime) { long start = System.currentTimeMillis(); if (unloadChunks(start, allocatedTime, false)) { allocatedTime = allocatedTime - (System.currentTimeMillis() - start); requireProvider().doGarbageCollection(allocatedTime); } } public void unloadChunks() { this.unloadChunks(false); } public void unloadChunks(boolean force) { unloadChunks(96, force); } public void unloadChunks(int maxUnload, boolean force) { if (!this.unloadQueue.isEmpty()) { long now = System.currentTimeMillis(); LongList toRemove = null; for (Long2LongMap.Entry entry : unloadQueue.long2LongEntrySet()) { long index = entry.getLongKey(); if (isChunkInUse(index)) { continue; } if (!force) { long time = entry.getLongValue(); if (maxUnload <= 0) { break; } else if (time > (now - 30000)) { continue; } } if (toRemove == null) toRemove = new LongArrayList(); toRemove.add(index); } if (toRemove != null) { int size = toRemove.size(); for (int i = 0; i < size; i++) { long index = toRemove.getLong(i); int X = getHashX(index); int Z = getHashZ(index); if (this.unloadChunk(X, Z, true)) { this.unloadQueue.remove(index); --maxUnload; } } } } } /** * @param now * @param allocatedTime * @param force * @return true if there is allocated time remaining */ private boolean unloadChunks(long now, long allocatedTime, boolean force) { if (!this.unloadQueue.isEmpty()) { boolean result = true; int maxIterations = this.unloadQueue.size(); if (lastUnloadIndex > maxIterations) lastUnloadIndex = 0; ObjectIterator iter = this.unloadQueue.long2LongEntrySet().iterator(); if (lastUnloadIndex != 0) iter.skip(lastUnloadIndex); LongList toUnload = null; for (int i = 0; i < maxIterations; i++) { if (!iter.hasNext()) { iter = this.unloadQueue.long2LongEntrySet().iterator(); } Long2LongMap.Entry entry = iter.next(); long index = entry.getLongKey(); if (isChunkInUse(index)) { continue; } if (!force) { long time = entry.getLongValue(); if (time > (now - 30000)) { continue; } } if (toUnload == null) toUnload = new LongArrayList(); toUnload.add(index); } if (toUnload != null) { long[] arr = toUnload.toLongArray(); for (long index : arr) { int X = getHashX(index); int Z = getHashZ(index); if (this.unloadChunk(X, Z, true)) { this.unloadQueue.remove(index); if (System.currentTimeMillis() - now >= allocatedTime) { result = false; break; } } } } return result; } else { return true; } } @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) throws Exception { this.server.getLevelMetadata().setMetadata(this, metadataKey, newMetadataValue); } @Override public List getMetadata(String metadataKey) throws Exception { return this.server.getLevelMetadata().getMetadata(this, metadataKey); } @Override public boolean hasMetadata(String metadataKey) throws Exception { return this.server.getLevelMetadata().hasMetadata(this, metadataKey); } @Override public void removeMetadata(String metadataKey, Plugin owningPlugin) throws Exception { this.server.getLevelMetadata().removeMetadata(this, metadataKey, owningPlugin); } public void addPlayerMovement(Entity entity, double x, double y, double z, double yaw, double pitch, double headYaw) { MovePlayerPacket pk = new MovePlayerPacket(); pk.eid = entity.getId(); pk.x = (float) x; pk.y = (float) y; pk.z = (float) z; pk.yaw = (float) yaw; pk.headYaw = (float) headYaw; pk.pitch = (float) pitch; if (entity.riding != null) { pk.ridingEid = entity.riding.getId(); pk.mode = MovePlayerPacket.MODE_PITCH; } Server.broadcastPacket(entity.getViewers().values(), pk); } @PowerNukkitDifference(since = "1.6.0.0-PNX", info = "use MoveEntityDeltaPacket instead of MoveEntityAbsolutePacket to implement headYaw") public void addEntityMovement(Entity entity, double x, double y, double z, double yaw, double pitch, double headYaw) { // MoveEntityAbsolutePacket pk = new MoveEntityAbsolutePacket(); // pk.eid = entity.getId(); // pk.x = (float) x; // pk.y = (float) y; // pk.z = (float) z; // pk.yaw = (float) yaw; // pk.headYaw = (float) headYaw; // pk.pitch = (float) pitch; // pk.onGround = entity.onGround; MoveEntityDeltaPacket pk = new MoveEntityDeltaPacket(); pk.runtimeEntityId = entity.getId(); if (entity.lastX != x) { pk.x = (float) x; pk.flags |= MoveEntityDeltaPacket.FLAG_HAS_X; } if (entity.lastY != y) { pk.y = (float) y; pk.flags |= MoveEntityDeltaPacket.FLAG_HAS_Y; } if (entity.lastZ != z) { pk.z = (float) z; pk.flags |= MoveEntityDeltaPacket.FLAG_HAS_Z; } if (entity.lastPitch != pitch) { pk.pitch = (float) pitch; pk.flags |= MoveEntityDeltaPacket.FLAG_HAS_PITCH; } if (entity.lastYaw != yaw) { pk.yaw = (float) yaw; pk.flags |= MoveEntityDeltaPacket.FLAG_HAS_YAW; } if (entity.lastHeadYaw != headYaw) { pk.headYaw = (float) headYaw; pk.flags |= MoveEntityDeltaPacket.FLAG_HAS_HEAD_YAW; } if (entity.onGround) { pk.flags |= MoveEntityDeltaPacket.FLAG_ON_GROUND; } Server.broadcastPacket(entity.getViewers().values(), pk); } public boolean isRaining() { return this.raining; } public boolean setRaining(boolean raining) { WeatherChangeEvent ev = new WeatherChangeEvent(this, raining); this.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { return false; } this.raining = raining; LevelEventPacket pk = new LevelEventPacket(); // These numbers are from Minecraft if (raining) { pk.evid = LevelEventPacket.EVENT_START_RAIN; int time = ThreadLocalRandom.current().nextInt(12000) + 12000; pk.data = time; setRainTime(time); } else { pk.evid = LevelEventPacket.EVENT_STOP_RAIN; setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } Server.broadcastPacket(this.getPlayers().values(), pk); return true; } public int getRainTime() { return this.rainTime; } public void setRainTime(int rainTime) { this.rainTime = rainTime; } public boolean isThundering() { return isRaining() && this.thundering; } public boolean setThundering(boolean thundering) { ThunderChangeEvent ev = new ThunderChangeEvent(this, thundering); this.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { return false; } if (thundering && !isRaining()) { setRaining(true); } this.thundering = thundering; LevelEventPacket pk = new LevelEventPacket(); // These numbers are from Minecraft if (thundering) { pk.evid = LevelEventPacket.EVENT_START_THUNDER; int time = ThreadLocalRandom.current().nextInt(12000) + 3600; pk.data = time; setThunderTime(time); } else { pk.evid = LevelEventPacket.EVENT_STOP_THUNDER; setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } Server.broadcastPacket(this.getPlayers().values(), pk); return true; } public int getThunderTime() { return this.thunderTime; } public void setThunderTime(int thunderTime) { this.thunderTime = thunderTime; } public void sendWeather(Player[] players) { if (players == null) { players = this.getPlayers().values().toArray(Player.EMPTY_ARRAY); } LevelEventPacket pk = new LevelEventPacket(); if (this.isRaining()) { pk.evid = LevelEventPacket.EVENT_START_RAIN; pk.data = this.rainTime; } else { pk.evid = LevelEventPacket.EVENT_STOP_RAIN; } Server.broadcastPacket(players, pk); if (this.isThundering()) { pk.evid = LevelEventPacket.EVENT_START_THUNDER; pk.data = this.thunderTime; } else { pk.evid = LevelEventPacket.EVENT_STOP_THUNDER; } Server.broadcastPacket(players, pk); } public void sendWeather(Player player) { if (player != null) { this.sendWeather(new Player[]{player}); } } public void sendWeather(Collection players) { if (players == null) { players = this.getPlayers().values(); } this.sendWeather(players.toArray(Player.EMPTY_ARRAY)); } public DimensionData getDimensionData() { return this.dimensionData; } public int getDimension() { if (dimensionData == null) { return 0; } else return dimensionData.getDimensionId(); } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public final boolean isOverWorld() { return getDimension() == 0; } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public final boolean isNether() { return getDimension() == 1; } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public final boolean isTheEnd() { return getDimension() == 2; } @PowerNukkitXOnly @Since("1.6.0.0-PNX") public final boolean isYInRange(int y) { return y >= getMinHeight() && y < getMaxHeight(); } public boolean canBlockSeeSky(Vector3 pos) { return this.getHighestBlockAt(pos.getFloorX(), pos.getFloorZ()) < pos.getY(); } /** * @return 最低高度 */ @PowerNukkitXOnly @Since("1.6.0.0-PNX") public int getMinHeight() { if (dimensionData != null) return dimensionData.getMinHeight(); return isOverWorld() ? -64 : 0; } /** * 最大高度,请注意此y值不能放置方块 * * @return 最大高度 */ @PowerNukkitXOnly @Since("1.6.0.0-PNX") public int getMaxHeight() { if (dimensionData != null) return dimensionData.getMaxHeight() + 1; return isOverWorld() ? 320 : 256; } public int getStrongPower(Vector3 pos, BlockFace direction) { return this.getBlock(pos).getStrongPower(direction); } @PowerNukkitDifference(info = "Check if the block to check is a piston, then return 0.", since = "1.4.0.0-PN") public int getStrongPower(Vector3 pos) { if (pos instanceof BlockPistonBase || this.getBlock(pos) instanceof BlockPistonBase) return 0; int i = 0; for (BlockFace face : BlockFace.values()) { i = Math.max(i, this.getStrongPower(temporalVector.setComponentsAdding(pos, face), face)); if (i >= 15) { return i; } } return i; // i = Math.max(i, this.getStrongPower(pos.down(), BlockFace.DOWN)); // // if (i >= 15) { // return i; // } else { // i = Math.max(i, this.getStrongPower(pos.up(), BlockFace.UP)); // // if (i >= 15) { // return i; // } else { // i = Math.max(i, this.getStrongPower(pos.north(), BlockFace.NORTH)); // // if (i >= 15) { // return i; // } else { // i = Math.max(i, this.getStrongPower(pos.south(), BlockFace.SOUTH)); // // if (i >= 15) { // return i; // } else { // i = Math.max(i, this.getStrongPower(pos.west(), BlockFace.WEST)); // // if (i >= 15) { // return i; // } else { // i = Math.max(i, this.getStrongPower(pos.east(), BlockFace.EAST)); // return i >= 15 ? i : i; // } // } // } // } // } } public boolean isSidePowered(Vector3 pos, BlockFace face) { return this.getRedstonePower(pos, face) > 0; } public int getRedstonePower(Vector3 pos, BlockFace face) { Block block; if (pos instanceof Block) { block = (Block) pos; pos = pos.add(0); } else { block = this.getBlock(pos); } return block.isNormalBlock() ? this.getStrongPower(pos) : block.getWeakPower(face); } public boolean isBlockPowered(Vector3 pos) { for (BlockFace face : BlockFace.values()) { if (this.getRedstonePower(temporalVector.setComponentsAdding(pos, face), face) > 0) { return true; } } return false; } public int isBlockIndirectlyGettingPowered(Vector3 pos) { int power = 0; for (BlockFace face : BlockFace.values()) { int blockPower = this.getRedstonePower(temporalVector.setComponentsAdding(pos, face), face); if (blockPower >= 15) { return 15; } if (blockPower > power) { power = blockPower; } } return power; } public boolean isAreaLoaded(AxisAlignedBB bb) { if (bb.getMaxY() < (isOverWorld() ? -64 : 0) || bb.getMinY() >= (isOverWorld() ? 320 : 256)) { return false; } int minX = NukkitMath.floorDouble(bb.getMinX()) >> 4; int minZ = NukkitMath.floorDouble(bb.getMinZ()) >> 4; int maxX = NukkitMath.floorDouble(bb.getMaxX()) >> 4; int maxZ = NukkitMath.floorDouble(bb.getMaxZ()) >> 4; for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { if (!this.isChunkLoaded(x, z)) { return false; } } } return true; } public int getUpdateLCG() { return (this.updateLCG = (this.updateLCG * 3) ^ LCG_CONSTANT); } @PowerNukkitDifference(info = "Using new method to play sounds", since = "1.4.0.0-PN") public boolean createPortal(Block target) { if (this.getDimension() == DIMENSION_THE_END) return false; int maxPortalSize = 23; final int targX = target.getFloorX(); final int targY = target.getFloorY(); final int targZ = target.getFloorZ(); //check if there's air above (at least 3 blocks) for (int i = 1; i < 4; i++) { if (this.getBlockIdAt(targX, targY + i, targZ) != BlockID.AIR) { return false; } } int sizePosX = 0; int sizeNegX = 0; int sizePosZ = 0; int sizeNegZ = 0; for (int i = 1; i < maxPortalSize; i++) { if (this.getBlockIdAt(targX + i, targY, targZ) == BlockID.OBSIDIAN) { sizePosX++; } else { break; } } for (int i = 1; i < maxPortalSize; i++) { if (this.getBlockIdAt(targX - i, targY, targZ) == BlockID.OBSIDIAN) { sizeNegX++; } else { break; } } for (int i = 1; i < maxPortalSize; i++) { if (this.getBlockIdAt(targX, targY, targZ + i) == BlockID.OBSIDIAN) { sizePosZ++; } else { break; } } for (int i = 1; i < maxPortalSize; i++) { if (this.getBlockIdAt(targX, targY, targZ - i) == BlockID.OBSIDIAN) { sizeNegZ++; } else { break; } } //plus one for target block int sizeX = sizePosX + sizeNegX + 1; int sizeZ = sizePosZ + sizeNegZ + 1; if (sizeX >= 2 && sizeX <= maxPortalSize) { //start scan from 1 block above base //find pillar or end of portal to start scan int scanX = targX; int scanY = targY + 1; int scanZ = targZ; for (int i = 0; i < sizePosX + 1; i++) { //this must be air if (this.getBlockIdAt(scanX + i, scanY, scanZ) != BlockID.AIR) { return false; } if (this.getBlockIdAt(scanX + i + 1, scanY, scanZ) == BlockID.OBSIDIAN) { scanX += i; break; } } //make sure that the above loop finished if (this.getBlockIdAt(scanX + 1, scanY, scanZ) != BlockID.OBSIDIAN) { return false; } int innerWidth = 0; LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { int id = this.getBlockIdAt(scanX - i, scanY, scanZ); switch (id) { case BlockID.AIR: innerWidth++; break; case BlockID.OBSIDIAN: break LOOP; default: return false; } } int innerHeight = 0; LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { int id = this.getBlockIdAt(scanX, scanY + i, scanZ); switch (id) { case BlockID.AIR: innerHeight++; break; case BlockID.OBSIDIAN: break LOOP; default: return false; } } if (!(innerWidth <= maxPortalSize - 2 && innerWidth >= 2 && innerHeight <= maxPortalSize - 2 && innerHeight >= 3)) { return false; } for (int height = 0; height < innerHeight + 1; height++) { if (height == innerHeight) { for (int width = 0; width < innerWidth; width++) { if (this.getBlockIdAt(scanX - width, scanY + height, scanZ) != BlockID.OBSIDIAN) { return false; } } } else { if (this.getBlockIdAt(scanX + 1, scanY + height, scanZ) != BlockID.OBSIDIAN || this.getBlockIdAt(scanX - innerWidth, scanY + height, scanZ) != BlockID.OBSIDIAN) { return false; } for (int width = 0; width < innerWidth; width++) { if (this.getBlockIdAt(scanX - width, scanY + height, scanZ) != BlockID.AIR) { return false; } } } } for (int height = 0; height < innerHeight; height++) { for (int width = 0; width < innerWidth; width++) { this.setBlock(new Vector3(scanX - width, scanY + height, scanZ), Block.get(BlockID.NETHER_PORTAL)); } } this.addSound(target, Sound.FIRE_IGNITE); return true; } else if (sizeZ >= 2 && sizeZ <= maxPortalSize) { //start scan from 1 block above base //find pillar or end of portal to start scan int scanX = targX; int scanY = targY + 1; int scanZ = targZ; for (int i = 0; i < sizePosZ + 1; i++) { //this must be air if (this.getBlockIdAt(scanX, scanY, scanZ + i) != BlockID.AIR) { return false; } if (this.getBlockIdAt(scanX, scanY, scanZ + i + 1) == BlockID.OBSIDIAN) { scanZ += i; break; } } //make sure that the above loop finished if (this.getBlockIdAt(scanX, scanY, scanZ + 1) != BlockID.OBSIDIAN) { return false; } int innerWidth = 0; LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { int id = this.getBlockIdAt(scanX, scanY, scanZ - i); switch (id) { case BlockID.AIR: innerWidth++; break; case BlockID.OBSIDIAN: break LOOP; default: return false; } } int innerHeight = 0; LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { int id = this.getBlockIdAt(scanX, scanY + i, scanZ); switch (id) { case BlockID.AIR: innerHeight++; break; case BlockID.OBSIDIAN: break LOOP; default: return false; } } if (!(innerWidth <= maxPortalSize - 2 && innerWidth >= 2 && innerHeight <= maxPortalSize - 2 && innerHeight >= 3)) { return false; } for (int height = 0; height < innerHeight + 1; height++) { if (height == innerHeight) { for (int width = 0; width < innerWidth; width++) { if (this.getBlockIdAt(scanX, scanY + height, scanZ - width) != BlockID.OBSIDIAN) { return false; } } } else { if (this.getBlockIdAt(scanX, scanY + height, scanZ + 1) != BlockID.OBSIDIAN || this.getBlockIdAt(scanX, scanY + height, scanZ - innerWidth) != BlockID.OBSIDIAN) { return false; } for (int width = 0; width < innerWidth; width++) { if (this.getBlockIdAt(scanX, scanY + height, scanZ - width) != BlockID.AIR) { return false; } } } } for (int height = 0; height < innerHeight; height++) { for (int width = 0; width < innerWidth; width++) { this.setBlock(new Vector3(scanX, scanY + height, scanZ - width), Block.get(BlockID.NETHER_PORTAL)); } } this.addSound(target, Sound.FIRE_IGNITE); return true; } return false; } @PowerNukkitXOnly @Since("1.19.21-r3") public VibrationManager getVibrationManager() { return this.vibrationManager; } @PowerNukkitXOnly @Since("1.6.0.0-PNX") private int ensureY(final int y) { if (getDimension() == DIMENSION_OVERWORLD) { return Math.max(Math.min(y, 319), -64); } else { return y & 0xff; } } @Override public String toString() { return "Level{" + "folderName='" + folderName + '\'' + ", dimension=" + getDimension() + '}'; } @AllArgsConstructor @Data private static class QueuedUpdate { @NotNull private Block block; private BlockFace neighbor; } }