001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.bukkit.queue;
020
021import com.google.common.base.Preconditions;
022import com.intellectualsites.annotations.DoNotUse;
023import com.plotsquared.bukkit.util.BukkitBlockUtil;
024import com.plotsquared.bukkit.util.BukkitUtil;
025import com.plotsquared.core.location.ChunkWrapper;
026import com.plotsquared.core.location.Location;
027import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator;
028import com.plotsquared.core.util.ChunkUtil;
029import com.plotsquared.core.util.PatternUtil;
030import com.sk89q.worldedit.bukkit.BukkitAdapter;
031import com.sk89q.worldedit.function.pattern.Pattern;
032import com.sk89q.worldedit.world.biome.BiomeType;
033import com.sk89q.worldedit.world.block.BaseBlock;
034import com.sk89q.worldedit.world.block.BlockState;
035import com.sk89q.worldedit.world.block.BlockTypes;
036import org.bukkit.Bukkit;
037import org.bukkit.Chunk;
038import org.bukkit.World;
039import org.bukkit.block.Biome;
040import org.bukkit.generator.ChunkGenerator.BiomeGrid;
041import org.bukkit.generator.ChunkGenerator.ChunkData;
042import org.checkerframework.checker.nullness.qual.NonNull;
043import org.checkerframework.checker.nullness.qual.Nullable;
044
045import java.util.Arrays;
046
047/**
048 * Internal use only. Subject to changes at any time.
049 */
050@DoNotUse
051public class GenChunk extends ZeroedDelegateScopedQueueCoordinator {
052
053    public final Biome[] biomes;
054    public BlockState[][] result;
055    public BiomeGrid biomeGrid;
056    public Chunk chunk;
057    public String world;
058    public int chunkX;
059    public int chunkZ;
060    private ChunkData chunkData = null;
061
062    /**
063     * @param minY minimum world Y, inclusive
064     * @param maxY maximum world Y, inclusive
065     * @since 6.6.0
066     */
067    public GenChunk(int minY, int maxY) {
068        super(null, Location.at("", 0, minY, 0), Location.at("", 15, maxY, 15));
069        this.biomes = Biome.values();
070    }
071
072    public @Nullable ChunkData getChunkData() {
073        return this.chunkData;
074    }
075
076    /**
077     * Set the internal Bukkit chunk data
078     *
079     * @param chunkData Bukkit ChunkData
080     */
081    public void setChunkData(@NonNull ChunkData chunkData) {
082        this.chunkData = chunkData;
083    }
084
085    public @NonNull Chunk getChunk() {
086        if (chunk == null) {
087            World worldObj = BukkitUtil.getWorld(world);
088            if (worldObj != null) {
089                this.chunk = worldObj.getChunkAt(chunkX, chunkZ);
090            }
091        }
092        return chunk;
093    }
094
095    /**
096     * Set the chunk being represented
097     *
098     * @param chunk Bukkit Chunk
099     */
100    public void setChunk(@NonNull Chunk chunk) {
101        this.chunk = chunk;
102    }
103
104
105    /**
106     * Set the world and XZ of the chunk being represented via {@link ChunkWrapper}
107     *
108     * @param wrap PlotSquared ChunkWrapper
109     */
110    public void setChunk(@NonNull ChunkWrapper wrap) {
111        chunk = null;
112        world = wrap.world();
113        chunkX = wrap.x();
114        chunkZ = wrap.z();
115    }
116
117    @Override
118    public void fillBiome(@NonNull BiomeType biomeType) {
119        if (biomeGrid == null) {
120            return;
121        }
122        Biome biome = BukkitAdapter.adapt(biomeType);
123        for (int y = getMin().getY(); y <= getMax().getY(); y++) {
124            for (int x = 0; x < 16; x++) {
125                for (int z = 0; z < 16; z++) {
126                    this.biomeGrid.setBiome(x, y, z, biome);
127                }
128            }
129        }
130    }
131
132    @Override
133    public void setCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull BlockState block) {
134        if (result != null && pos1.getX() == 0 && pos1.getZ() == 0 && pos2.getX() == 15 && pos2.getZ() == 15) {
135            for (int y = pos1.getY(); y <= pos2.getY(); y++) {
136                int layer = getLayerIndex(y);
137                BlockState[] data = result[layer];
138                if (data == null) {
139                    result[layer] = data = new BlockState[4096];
140                }
141                int start = y << 8;
142                int end = start + 256;
143                Arrays.fill(data, start, end, block);
144            }
145        }
146        int minX = Math.min(pos1.getX(), pos2.getX());
147        int minY = Math.min(pos1.getY(), pos2.getY());
148        int minZ = Math.min(pos1.getZ(), pos2.getZ());
149        int maxX = Math.max(pos1.getX(), pos2.getX());
150        int maxY = Math.max(pos1.getY(), pos2.getY());
151        int maxZ = Math.max(pos1.getZ(), pos2.getZ());
152        chunkData.setRegion(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1, BukkitAdapter.adapt(block));
153    }
154
155    @Override
156    public boolean setBiome(int x, int z, @NonNull BiomeType biomeType) {
157        return setBiome(x, z, BukkitAdapter.adapt(biomeType));
158    }
159
160    /**
161     * Set the in the whole column of XZ
162     *
163     * @param x     Relative x location within the chunk (0 - 15)
164     * @param z     Relative z location within the chunk (0 - 15)
165     * @param biome Bukkit biome to set
166     * @return if successful
167     */
168    public boolean setBiome(int x, int z, @NonNull Biome biome) {
169        if (this.biomeGrid != null) {
170            for (int y = getMin().getY(); y <= getMax().getY(); y++) {
171                this.setBiome(x, y, z, biome);
172            }
173            return true;
174        }
175        return false;
176    }
177
178    public boolean setBiome(int x, int y, int z, @NonNull Biome biome) {
179        if (this.biomeGrid != null) {
180            this.biomeGrid.setBiome(x, y, z, biome);
181            return true;
182        }
183        return false;
184    }
185
186    @Override
187    public boolean setBlock(int x, int y, int z, @NonNull Pattern pattern) {
188        final BaseBlock block = PatternUtil.apply(Preconditions.checkNotNull(
189                pattern,
190                "Pattern may not be null"
191        ), x + (chunkX << 4), y, z + (chunkZ << 4));
192        return setBlock(x, y, z, block);
193    }
194
195    @Override
196    public boolean setBlock(int x, int y, int z, @NonNull BlockState id) {
197        if (this.result == null) {
198            this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
199            return true;
200        }
201        this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
202        this.storeCache(x, y, z, id);
203        return true;
204    }
205
206    private void storeCache(final int x, final int y, final int z, final @NonNull BlockState id) {
207        int i = getLayerIndex(y);
208        BlockState[] v = this.result[i];
209        if (v == null) {
210            this.result[i] = v = new BlockState[4096];
211        }
212        int j = ChunkUtil.getJ(x, y, z);
213        v[j] = id;
214    }
215
216    @Override
217    public boolean setBlock(int x, int y, int z, @NonNull BaseBlock id) {
218        if (this.result == null) {
219            this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
220            return true;
221        }
222        this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
223        this.storeCache(x, y, z, id.toImmutableState());
224        return true;
225    }
226
227    @Override
228    public @Nullable BlockState getBlock(int x, int y, int z) {
229        int i = getLayerIndex(y);
230        if (result == null) {
231            return BukkitBlockUtil.get(chunkData.getType(x, y, z));
232        }
233        BlockState[] array = result[i];
234        if (array == null) {
235            return BlockTypes.AIR.getDefaultState();
236        }
237        int j = ChunkUtil.getJ(x, y, z);
238        return array[j];
239    }
240
241    public int getX() {
242        return chunk == null ? chunkX : chunk.getX();
243    }
244
245    public int getZ() {
246        return chunk == null ? chunkZ : chunk.getZ();
247    }
248
249    @Override
250    public com.sk89q.worldedit.world.@NonNull World getWorld() {
251        return chunk == null ? BukkitAdapter.adapt(Bukkit.getWorld(world)) : BukkitAdapter.adapt(chunk.getWorld());
252    }
253
254    @Override
255    public @NonNull Location getMax() {
256        return Location.at(getWorld().getName(), 15 + (getX() << 4), super.getMax().getY(), 15 + (getZ() << 4));
257    }
258
259    @Override
260    public @NonNull Location getMin() {
261        return Location.at(getWorld().getName(), getX() << 4, super.getMin().getY(), getZ() << 4);
262    }
263
264    public @NonNull GenChunk clone() {
265        GenChunk toReturn = new GenChunk(getMin().getY(), getMax().getY());
266        if (this.result != null) {
267            for (int i = 0; i < this.result.length; i++) {
268                BlockState[] matrix = this.result[i];
269                if (matrix != null) {
270                    toReturn.result[i] = new BlockState[matrix.length];
271                    System.arraycopy(matrix, 0, toReturn.result[i], 0, matrix.length);
272                }
273            }
274        }
275        toReturn.chunkData = this.chunkData;
276        return toReturn;
277    }
278
279    private int getLayerIndex(int y) {
280        return (y - getMin().getY()) >> 4;
281    }
282
283}