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