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.ScopedQueueCoordinator;
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@DoNotUse
048public class GenChunk extends ScopedQueueCoordinator {
049
050    public final Biome[] biomes;
051    public BlockState[][] result;
052    public BiomeGrid biomeGrid;
053    public Chunk chunk;
054    public String world;
055    public int chunkX;
056    public int chunkZ;
057    private ChunkData chunkData = null;
058
059    /**
060     * @param minY minimum world Y, inclusive
061     * @param maxY maximum world Y, inclusive
062     *
063     * @since 6.6.0
064     */
065    public GenChunk(int minY, int maxY) {
066        super(null, Location.at("", 0, minY, 0), Location.at("", 15, maxY, 15));
067        this.biomes = Biome.values();
068    }
069
070    public @Nullable ChunkData getChunkData() {
071        return this.chunkData;
072    }
073
074    /**
075     * Set the internal Bukkit chunk data
076     *
077     * @param chunkData Bukkit ChunkData
078     */
079    public void setChunkData(@NonNull ChunkData chunkData) {
080        this.chunkData = chunkData;
081    }
082
083    public @NonNull Chunk getChunk() {
084        if (chunk == null) {
085            World worldObj = BukkitUtil.getWorld(world);
086            if (worldObj != null) {
087                this.chunk = worldObj.getChunkAt(chunkX, chunkZ);
088            }
089        }
090        return chunk;
091    }
092
093    /**
094     * Set the chunk being represented
095     *
096     * @param chunk Bukkit Chunk
097     */
098    public void setChunk(@NonNull Chunk chunk) {
099        this.chunk = chunk;
100    }
101
102
103    /**
104     * Set the world and XZ of the chunk being represented via {@link ChunkWrapper}
105     *
106     * @param wrap PlotSquared ChunkWrapper
107     */
108    public void setChunk(@NonNull ChunkWrapper wrap) {
109        chunk = null;
110        world = wrap.world;
111        chunkX = wrap.x;
112        chunkZ = wrap.z;
113    }
114
115    @Override
116    public void fillBiome(@NonNull BiomeType biomeType) {
117        if (biomeGrid == null) {
118            return;
119        }
120        Biome biome = BukkitAdapter.adapt(biomeType);
121        for (int y = getMin().getY(); y <= getMax().getY(); y++) {
122            for (int x = 0; x < 16; x++) {
123                for (int z = 0; z < 16; z++) {
124                    this.biomeGrid.setBiome(x, y, z, biome);
125                }
126            }
127        }
128    }
129
130    @Override
131    public void setCuboid(@NonNull Location pos1, @NonNull Location pos2, @NonNull BlockState block) {
132        if (result != null && pos1.getX() == 0 && pos1.getZ() == 0 && pos2.getX() == 15 && pos2.getZ() == 15) {
133            for (int y = pos1.getY(); y <= pos2.getY(); y++) {
134                int layer = getLayerIndex(y);
135                BlockState[] data = result[layer];
136                if (data == null) {
137                    result[layer] = data = new BlockState[4096];
138                }
139                int start = y << 8;
140                int end = start + 256;
141                Arrays.fill(data, start, end, block);
142            }
143        }
144        int minX = Math.min(pos1.getX(), pos2.getX());
145        int minY = Math.min(pos1.getY(), pos2.getY());
146        int minZ = Math.min(pos1.getZ(), pos2.getZ());
147        int maxX = Math.max(pos1.getX(), pos2.getX());
148        int maxY = Math.max(pos1.getY(), pos2.getY());
149        int maxZ = Math.max(pos1.getZ(), pos2.getZ());
150        chunkData.setRegion(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1, BukkitAdapter.adapt(block));
151    }
152
153    @Override
154    public boolean setBiome(int x, int z, @NonNull BiomeType biomeType) {
155        return setBiome(x, z, BukkitAdapter.adapt(biomeType));
156    }
157
158    /**
159     * Set the in the whole column of XZ
160     *
161     * @param x     Relative x location within the chunk (0 - 15)
162     * @param z     Relative z location within the chunk (0 - 15)
163     * @param biome Bukkit biome to set
164     * @return if successful
165     */
166    public boolean setBiome(int x, int z, @NonNull Biome biome) {
167        if (this.biomeGrid != null) {
168            for (int y = getMin().getY(); y <= getMax().getY(); y++) {
169                this.setBiome(x, y, z, biome);
170            }
171            return true;
172        }
173        return false;
174    }
175
176    public boolean setBiome(int x, int y, int z, @NonNull Biome biome) {
177        if (this.biomeGrid != null) {
178            this.biomeGrid.setBiome(x, y, z, biome);
179            return true;
180        }
181        return false;
182    }
183
184    @Override
185    public boolean setBlock(int x, int y, int z, @NonNull Pattern pattern) {
186        final BaseBlock block = PatternUtil.apply(Preconditions.checkNotNull(
187                pattern,
188                "Pattern may not be null"
189        ), x + (chunkX << 4), y, z + (chunkZ << 4));
190        return setBlock(x, y, z, block);
191    }
192
193    @Override
194    public boolean setBlock(int x, int y, int z, @NonNull BlockState id) {
195        if (this.result == null) {
196            this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
197            return true;
198        }
199        this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
200        this.storeCache(x, y, z, id);
201        return true;
202    }
203
204    private void storeCache(final int x, final int y, final int z, final @NonNull BlockState id) {
205        int i = getLayerIndex(y);
206        BlockState[] v = this.result[i];
207        if (v == null) {
208            this.result[i] = v = new BlockState[4096];
209        }
210        int j = ChunkUtil.getJ(x, y, z);
211        v[j] = id;
212    }
213
214    @Override
215    public boolean setBlock(int x, int y, int z, @NonNull BaseBlock id) {
216        if (this.result == null) {
217            this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
218            return true;
219        }
220        this.chunkData.setBlock(x, y, z, BukkitAdapter.adapt(id));
221        this.storeCache(x, y, z, id.toImmutableState());
222        return true;
223    }
224
225    @Override
226    public @Nullable BlockState getBlock(int x, int y, int z) {
227        int i = getLayerIndex(y);
228        if (result == null) {
229            return BukkitBlockUtil.get(chunkData.getType(x, y, z));
230        }
231        BlockState[] array = result[i];
232        if (array == null) {
233            return BlockTypes.AIR.getDefaultState();
234        }
235        int j = ChunkUtil.getJ(x, y, z);
236        return array[j];
237    }
238
239    public int getX() {
240        return chunk == null ? chunkX : chunk.getX();
241    }
242
243    public int getZ() {
244        return chunk == null ? chunkZ : chunk.getZ();
245    }
246
247    @Override
248    public com.sk89q.worldedit.world.@NonNull World getWorld() {
249        return chunk == null ? BukkitAdapter.adapt(Bukkit.getWorld(world)) : BukkitAdapter.adapt(chunk.getWorld());
250    }
251
252    @Override
253    public @NonNull Location getMax() {
254        return Location.at(getWorld().getName(), 15 + (getX() << 4), super.getMax().getY(), 15 + (getZ() << 4));
255    }
256
257    @Override
258    public @NonNull Location getMin() {
259        return Location.at(getWorld().getName(), getX() << 4, super.getMin().getY(), getZ() << 4);
260    }
261
262    public @NonNull GenChunk clone() {
263        GenChunk toReturn = new GenChunk(getMin().getY(), getMax().getY());
264        if (this.result != null) {
265            for (int i = 0; i < this.result.length; i++) {
266                BlockState[] matrix = this.result[i];
267                if (matrix != null) {
268                    toReturn.result[i] = new BlockState[matrix.length];
269                    System.arraycopy(matrix, 0, toReturn.result[i], 0, matrix.length);
270                }
271            }
272        }
273        toReturn.chunkData = this.chunkData;
274        return toReturn;
275    }
276
277    private int getLayerIndex(int y) {
278        return (y - getMin().getY()) >> 4;
279    }
280
281}