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.util;
020
021import com.google.inject.Inject;
022import com.google.inject.Singleton;
023import com.plotsquared.core.generator.AugmentedUtils;
024import com.plotsquared.core.inject.factory.ProgressSubscriberFactory;
025import com.plotsquared.core.location.Location;
026import com.plotsquared.core.location.PlotLoc;
027import com.plotsquared.core.player.PlotPlayer;
028import com.plotsquared.core.plot.Plot;
029import com.plotsquared.core.plot.PlotArea;
030import com.plotsquared.core.plot.PlotManager;
031import com.plotsquared.core.queue.GlobalBlockQueue;
032import com.plotsquared.core.queue.QueueCoordinator;
033import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator;
034import com.plotsquared.core.util.ChunkManager;
035import com.plotsquared.core.util.RegionManager;
036import com.plotsquared.core.util.WorldUtil;
037import com.plotsquared.core.util.entity.EntityCategories;
038import com.plotsquared.core.util.task.RunnableVal;
039import com.sk89q.worldedit.bukkit.BukkitAdapter;
040import com.sk89q.worldedit.bukkit.BukkitWorld;
041import com.sk89q.worldedit.regions.CuboidRegion;
042import com.sk89q.worldedit.world.block.BaseBlock;
043import com.sk89q.worldedit.world.block.BlockTypes;
044import org.bukkit.Bukkit;
045import org.bukkit.Chunk;
046import org.bukkit.World;
047import org.bukkit.entity.Entity;
048import org.bukkit.entity.Player;
049import org.checkerframework.checker.nullness.qual.NonNull;
050import org.checkerframework.checker.nullness.qual.Nullable;
051
052import java.util.ArrayList;
053import java.util.HashSet;
054import java.util.List;
055import java.util.Set;
056
057import static com.plotsquared.core.util.entity.EntityCategories.CAP_ANIMAL;
058import static com.plotsquared.core.util.entity.EntityCategories.CAP_ENTITY;
059import static com.plotsquared.core.util.entity.EntityCategories.CAP_MISC;
060import static com.plotsquared.core.util.entity.EntityCategories.CAP_MOB;
061import static com.plotsquared.core.util.entity.EntityCategories.CAP_MONSTER;
062import static com.plotsquared.core.util.entity.EntityCategories.CAP_VEHICLE;
063
064@Singleton
065public class BukkitRegionManager extends RegionManager {
066
067    private final GlobalBlockQueue blockQueue;
068
069    @Inject
070    public BukkitRegionManager(
071            @NonNull WorldUtil worldUtil, @NonNull GlobalBlockQueue blockQueue, @NonNull
072    ProgressSubscriberFactory subscriberFactory
073    ) {
074        super(worldUtil, blockQueue, subscriberFactory);
075        this.blockQueue = blockQueue;
076    }
077
078    @Override
079    public boolean handleClear(
080            @NonNull Plot plot,
081            @Nullable Runnable whenDone,
082            @NonNull PlotManager manager,
083            @Nullable PlotPlayer<?> player
084    ) {
085        return false;
086    }
087
088    @Override
089    public int[] countEntities(@NonNull Plot plot) {
090        int[] existing = (int[]) plot.getMeta("EntityCount");
091        if (existing != null && (System.currentTimeMillis() - (long) plot.getMeta("EntityCountTime") < 1000)) {
092            return existing;
093        }
094        PlotArea area = plot.getArea();
095        World world = BukkitUtil.getWorld(area.getWorldName());
096        Location bot = plot.getBottomAbs();
097        Location top = plot.getTopAbs();
098        int bx = bot.getX() >> 4;
099        int bz = bot.getZ() >> 4;
100
101        int tx = top.getX() >> 4;
102        int tz = top.getZ() >> 4;
103
104        int size = tx - bx << 4;
105
106        Set<Chunk> chunks = new HashSet<>();
107        for (int X = bx; X <= tx; X++) {
108            for (int Z = bz; Z <= tz; Z++) {
109                if (world.isChunkLoaded(X, Z)) {
110                    chunks.add(world.getChunkAt(X, Z));
111                }
112            }
113        }
114
115        boolean doWhole = false;
116        List<Entity> entities = null;
117        if (size > 200 && chunks.size() > 200) {
118            entities = world.getEntities();
119            if (entities.size() < 16 + size / 8) {
120                doWhole = true;
121            }
122        }
123
124        int[] count = new int[6];
125        if (doWhole) {
126            for (Entity entity : entities) {
127                org.bukkit.Location location = entity.getLocation();
128                PaperSupport.getChunkAtAsync(location).thenAccept(chunk -> {
129                    if (chunks.contains(chunk)) {
130                        int X = chunk.getX();
131                        int Z = chunk.getZ();
132                        if (X > bx && X < tx && Z > bz && Z < tz) {
133                            count(count, entity);
134                        } else {
135                            Plot other = area.getPlot(BukkitUtil.adapt(location));
136                            if (plot.equals(other)) {
137                                count(count, entity);
138                            }
139                        }
140                    }
141                });
142            }
143        } else {
144            for (Chunk chunk : chunks) {
145                int X = chunk.getX();
146                int Z = chunk.getZ();
147                Entity[] entities1 = chunk.getEntities();
148                for (Entity entity : entities1) {
149                    if (X == bx || X == tx || Z == bz || Z == tz) {
150                        Plot other = area.getPlot(BukkitUtil.adapt(entity.getLocation()));
151                        if (plot.equals(other)) {
152                            count(count, entity);
153                        }
154                    } else {
155                        count(count, entity);
156                    }
157                }
158            }
159        }
160        return count;
161    }
162
163    @Override
164    public boolean regenerateRegion(
165            final @NonNull Location pos1,
166            final @NonNull Location pos2,
167            final boolean ignoreAugment,
168            final @Nullable Runnable whenDone
169    ) {
170        final BukkitWorld world = (BukkitWorld) worldUtil.getWeWorld(pos1.getWorldName());
171
172        final int p1x = pos1.getX();
173        final int p1z = pos1.getZ();
174        final int p2x = pos2.getX();
175        final int p2z = pos2.getZ();
176        final int bcx = p1x >> 4;
177        final int bcz = p1z >> 4;
178        final int tcx = p2x >> 4;
179        final int tcz = p2z >> 4;
180
181        final QueueCoordinator queue = blockQueue.getNewQueue(world);
182        final QueueCoordinator regenQueue = blockQueue.getNewQueue(world);
183        queue.addReadChunks(new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()).getChunks());
184        queue.setChunkConsumer(chunk -> {
185
186            int x = chunk.getX();
187            int z = chunk.getZ();
188            int xxb = x << 4;
189            int zzb = z << 4;
190            int xxt = xxb + 15;
191            int zzt = zzb + 15;
192            if (xxb >= p1x && xxt <= p2x && zzb >= p1z && zzt <= p2z) {
193                AugmentedUtils.bypass(ignoreAugment, () -> regenQueue.regenChunk(chunk.getX(), chunk.getZ()));
194                return;
195            }
196            boolean checkX1 = false;
197
198            int xxb2;
199
200            if (x == bcx) {
201                xxb2 = p1x - 1;
202                checkX1 = true;
203            } else {
204                xxb2 = xxb;
205            }
206            boolean checkX2 = false;
207            int xxt2;
208            if (x == tcx) {
209                xxt2 = p2x + 1;
210                checkX2 = true;
211            } else {
212                xxt2 = xxt;
213            }
214            boolean checkZ1 = false;
215            int zzb2;
216            if (z == bcz) {
217                zzb2 = p1z - 1;
218                checkZ1 = true;
219            } else {
220                zzb2 = zzb;
221            }
222            boolean checkZ2 = false;
223            int zzt2;
224            if (z == tcz) {
225                zzt2 = p2z + 1;
226                checkZ2 = true;
227            } else {
228                zzt2 = zzt;
229            }
230            final ContentMap map = new ContentMap();
231            if (checkX1) {
232                map.saveRegion(world, xxb, xxb2, zzb2, zzt2); //
233            }
234            if (checkX2) {
235                map.saveRegion(world, xxt2, xxt, zzb2, zzt2); //
236            }
237            if (checkZ1) {
238                map.saveRegion(world, xxb2, xxt2, zzb, zzb2); //
239            }
240            if (checkZ2) {
241                map.saveRegion(world, xxb2, xxt2, zzt2, zzt); //
242            }
243            if (checkX1 && checkZ1) {
244                map.saveRegion(world, xxb, xxb2, zzb, zzb2); //
245            }
246            if (checkX2 && checkZ1) {
247                map.saveRegion(world, xxt2, xxt, zzb, zzb2); // ?
248            }
249            if (checkX1 && checkZ2) {
250                map.saveRegion(world, xxb, xxb2, zzt2, zzt); // ?
251            }
252            if (checkX2 && checkZ2) {
253                map.saveRegion(world, xxt2, xxt, zzt2, zzt); //
254            }
255            CuboidRegion currentPlotClear = new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3());
256            map.saveEntitiesOut(Bukkit.getWorld(world.getName()).getChunkAt(x, z), currentPlotClear);
257            AugmentedUtils.bypass(
258                    ignoreAugment,
259                    () -> ChunkManager.setChunkInPlotArea(null, new RunnableVal<ZeroedDelegateScopedQueueCoordinator>() {
260                        @Override
261                        public void run(ZeroedDelegateScopedQueueCoordinator value) {
262                            Location min = value.getMin();
263                            int bx = min.getX();
264                            int bz = min.getZ();
265                            for (int x1 = 0; x1 < 16; x1++) {
266                                for (int z1 = 0; z1 < 16; z1++) {
267                                    PlotLoc plotLoc = new PlotLoc(bx + x1, bz + z1);
268                                    BaseBlock[] ids = map.allBlocks.get(plotLoc);
269                                    if (ids != null) {
270                                        int minY = value.getMin().getY();
271                                        for (int yIndex = 0; yIndex < ids.length; yIndex++) {
272                                            int y = yIndex + minY;
273                                            BaseBlock id = ids[yIndex];
274                                            if (id != null) {
275                                                value.setBlock(x1, y, z1, id);
276                                            } else {
277                                                value.setBlock(x1, y, z1, BlockTypes.AIR.getDefaultState());
278                                            }
279                                        }
280                                    }
281                                }
282                            }
283                        }
284                    }, world.getName(), chunk)
285            );
286            //map.restoreBlocks(worldObj, 0, 0);
287            map.restoreEntities(Bukkit.getWorld(world.getName()));
288        });
289        regenQueue.setCompleteTask(whenDone);
290        queue.setCompleteTask(regenQueue::enqueue);
291        queue.enqueue();
292        return true;
293    }
294
295    @Override
296    public void clearAllEntities(@NonNull Location pos1, @NonNull Location pos2) {
297        String world = pos1.getWorldName();
298
299        final World bukkitWorld = BukkitUtil.getWorld(world);
300        final List<Entity> entities;
301        if (bukkitWorld != null) {
302            entities = new ArrayList<>(bukkitWorld.getEntities());
303        } else {
304            entities = new ArrayList<>();
305        }
306
307        int bx = pos1.getX();
308        int bz = pos1.getZ();
309        int tx = pos2.getX();
310        int tz = pos2.getZ();
311        for (Entity entity : entities) {
312            if (!(entity instanceof Player)) {
313                org.bukkit.Location location = entity.getLocation();
314                if (location.getX() >= bx && location.getX() <= tx && location.getZ() >= bz && location.getZ() <= tz) {
315                    if (entity.hasMetadata("ps-tmp-teleport")) {
316                        continue;
317                    }
318                    entity.remove();
319                }
320            }
321        }
322    }
323
324    private void count(int[] count, @NonNull Entity entity) {
325        final com.sk89q.worldedit.world.entity.EntityType entityType = BukkitAdapter.adapt(entity.getType());
326
327        if (EntityCategories.PLAYER.contains(entityType)) {
328            return;
329        } else if (EntityCategories.PROJECTILE.contains(entityType) || EntityCategories.OTHER.contains(entityType) || EntityCategories.HANGING
330                .contains(entityType)) {
331            count[CAP_MISC]++;
332        } else if (EntityCategories.ANIMAL.contains(entityType) || EntityCategories.VILLAGER.contains(entityType) || EntityCategories.TAMEABLE
333                .contains(entityType)) {
334            count[CAP_MOB]++;
335            count[CAP_ANIMAL]++;
336        } else if (EntityCategories.VEHICLE.contains(entityType)) {
337            count[CAP_VEHICLE]++;
338        } else if (EntityCategories.HOSTILE.contains(entityType)) {
339            count[CAP_MOB]++;
340            count[CAP_MONSTER]++;
341        }
342        count[CAP_ENTITY]++;
343    }
344
345}