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.listener;
020
021import com.google.inject.Inject;
022import com.plotsquared.bukkit.player.BukkitPlayer;
023import com.plotsquared.bukkit.util.BukkitEntityUtil;
024import com.plotsquared.bukkit.util.BukkitUtil;
025import com.plotsquared.core.PlotSquared;
026import com.plotsquared.core.configuration.Settings;
027import com.plotsquared.core.listener.PlayerBlockEventType;
028import com.plotsquared.core.location.Location;
029import com.plotsquared.core.permissions.Permission;
030import com.plotsquared.core.player.PlotPlayer;
031import com.plotsquared.core.plot.Plot;
032import com.plotsquared.core.plot.PlotArea;
033import com.plotsquared.core.plot.PlotHandler;
034import com.plotsquared.core.plot.flag.implementations.DisablePhysicsFlag;
035import com.plotsquared.core.plot.flag.implementations.EntityChangeBlockFlag;
036import com.plotsquared.core.plot.flag.implementations.ExplosionFlag;
037import com.plotsquared.core.plot.flag.implementations.InvincibleFlag;
038import com.plotsquared.core.plot.world.PlotAreaManager;
039import com.plotsquared.core.util.EventDispatcher;
040import com.plotsquared.core.util.PlotFlagUtil;
041import com.sk89q.worldedit.bukkit.BukkitAdapter;
042import com.sk89q.worldedit.world.block.BlockType;
043import org.bukkit.Material;
044import org.bukkit.Particle;
045import org.bukkit.World;
046import org.bukkit.block.Block;
047import org.bukkit.entity.Ageable;
048import org.bukkit.entity.Boat;
049import org.bukkit.entity.Entity;
050import org.bukkit.entity.EntityType;
051import org.bukkit.entity.FallingBlock;
052import org.bukkit.entity.Player;
053import org.bukkit.entity.Projectile;
054import org.bukkit.entity.TNTPrimed;
055import org.bukkit.entity.Vehicle;
056import org.bukkit.event.EventHandler;
057import org.bukkit.event.EventPriority;
058import org.bukkit.event.Listener;
059import org.bukkit.event.entity.CreatureSpawnEvent;
060import org.bukkit.event.entity.EntityChangeBlockEvent;
061import org.bukkit.event.entity.EntityCombustByEntityEvent;
062import org.bukkit.event.entity.EntityDamageByEntityEvent;
063import org.bukkit.event.entity.EntityDamageEvent;
064import org.bukkit.event.entity.EntityExplodeEvent;
065import org.bukkit.event.entity.ExplosionPrimeEvent;
066import org.bukkit.event.vehicle.VehicleCreateEvent;
067import org.bukkit.metadata.FixedMetadataValue;
068import org.bukkit.metadata.MetadataValue;
069import org.bukkit.plugin.Plugin;
070import org.bukkit.projectiles.BlockProjectileSource;
071import org.bukkit.projectiles.ProjectileSource;
072import org.checkerframework.checker.nullness.qual.NonNull;
073
074import java.util.Iterator;
075import java.util.List;
076
077@SuppressWarnings("unused")
078public class EntityEventListener implements Listener {
079
080    private final PlotAreaManager plotAreaManager;
081    private final EventDispatcher eventDispatcher;
082    private float lastRadius;
083
084    @Inject
085    public EntityEventListener(
086            final @NonNull PlotAreaManager plotAreaManager,
087            final @NonNull EventDispatcher eventDispatcher
088    ) {
089        this.plotAreaManager = plotAreaManager;
090        this.eventDispatcher = eventDispatcher;
091    }
092
093    @EventHandler(priority = EventPriority.HIGHEST)
094    public void onEntityCombustByEntity(EntityCombustByEntityEvent event) {
095        EntityDamageByEntityEvent eventChange =
096                new EntityDamageByEntityEvent(
097                        event.getCombuster(),
098                        event.getEntity(),
099                        EntityDamageEvent.DamageCause.FIRE_TICK,
100                        event.getDuration()
101                );
102        onEntityDamageByEntityEvent(eventChange);
103        if (eventChange.isCancelled()) {
104            event.setCancelled(true);
105        }
106    }
107
108    @EventHandler(priority = EventPriority.HIGHEST)
109    public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent event) {
110        Entity damager = event.getDamager();
111        Location location = BukkitUtil.adapt(damager.getLocation());
112        if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) {
113            return;
114        }
115        Entity victim = event.getEntity();
116/*
117        if (victim.getType().equals(EntityType.ITEM_FRAME)) {
118            Plot plot = BukkitUtil.getLocation(victim).getPlot();
119            if (plot != null && !plot.isAdded(damager.getUniqueId())) {
120                event.setCancelled(true);
121                return;
122            }
123        }
124*/
125        if (!BukkitEntityUtil.entityDamage(damager, victim, event.getCause())) {
126            if (event.isCancelled()) {
127                if (victim instanceof Ageable ageable) {
128                    if (ageable.getAge() == -24000) {
129                        ageable.setAge(0);
130                        ageable.setAdult();
131                    }
132                }
133            }
134            event.setCancelled(true);
135        }
136    }
137
138    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
139    public void creatureSpawnEvent(CreatureSpawnEvent event) {
140        Entity entity = event.getEntity();
141        Location location = BukkitUtil.adapt(entity.getLocation());
142        PlotArea area = location.getPlotArea();
143        if (area == null) {
144            return;
145        }
146        CreatureSpawnEvent.SpawnReason reason = event.getSpawnReason();
147        switch (reason.toString()) {
148            case "DISPENSE_EGG":
149            case "EGG":
150            case "OCELOT_BABY":
151            case "SPAWNER_EGG":
152                if (!area.isSpawnEggs()) {
153                    event.setCancelled(true);
154                    return;
155                }
156                break;
157            case "REINFORCEMENTS":
158            case "NATURAL":
159            case "MOUNT":
160            case "PATROL":
161            case "RAID":
162            case "SHEARED":
163            case "SILVERFISH_BLOCK":
164            case "ENDER_PEARL":
165            case "TRAP":
166            case "VILLAGE_DEFENSE":
167            case "VILLAGE_INVASION":
168            case "BEEHIVE":
169            case "CHUNK_GEN":
170                if (!area.isMobSpawning()) {
171                    event.setCancelled(true);
172                    return;
173                }
174                break;
175            case "BREEDING":
176                if (!area.isSpawnBreeding()) {
177                    event.setCancelled(true);
178                    return;
179                }
180                break;
181            case "BUILD_IRONGOLEM":
182            case "BUILD_SNOWMAN":
183            case "BUILD_WITHER":
184            case "CUSTOM":
185                if (!area.isSpawnCustom() && entity.getType() != EntityType.ARMOR_STAND) {
186                    event.setCancelled(true);
187                    return;
188                }
189                break;
190            case "SPAWNER":
191                if (!area.isMobSpawnerSpawning()) {
192                    event.setCancelled(true);
193                    return;
194                }
195                break;
196        }
197        Plot plot = area.getOwnedPlotAbs(location);
198        if (plot == null) {
199            if (!area.isMobSpawning()) {
200                event.setCancelled(true);
201            }
202            return;
203        }
204        if (BukkitEntityUtil.checkEntity(entity, plot.getBasePlot(false))) {
205            event.setCancelled(true);
206        }
207    }
208
209    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
210    public void onEntityFall(EntityChangeBlockEvent event) {
211        if (event.getEntityType() != EntityType.FALLING_BLOCK) {
212            return;
213        }
214        Block block = event.getBlock();
215        World world = block.getWorld();
216        String worldName = world.getName();
217        if (!this.plotAreaManager.hasPlotArea(worldName)) {
218            return;
219        }
220        Location location = BukkitUtil.adapt(block.getLocation());
221        PlotArea area = location.getPlotArea();
222        if (area == null) {
223            return;
224        }
225        Plot plot = area.getOwnedPlotAbs(location);
226        if (plot == null || plot.getFlag(DisablePhysicsFlag.class)) {
227            event.setCancelled(true);
228            if (plot != null) {
229                if (block.getType().hasGravity()) {
230                    BlockEventListener.sendBlockChange(block.getLocation(), block.getBlockData());
231                }
232                plot.debug("Falling block event was cancelled because disable-physics = true");
233            }
234            return;
235        }
236        if (event.getTo().hasGravity()) {
237            Entity entity = event.getEntity();
238            List<MetadataValue> meta = entity.getMetadata("plot");
239            if (meta.isEmpty()) {
240                return;
241            }
242            Plot origin = (Plot) meta.get(0).value();
243            if (origin != null && !origin.equals(plot)) {
244                event.setCancelled(true);
245                entity.remove();
246            }
247        } else if (event.getTo() == Material.AIR) {
248            event.getEntity().setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot));
249        }
250    }
251
252    @EventHandler(priority = EventPriority.HIGH)
253    public void onDamage(EntityDamageEvent event) {
254        if (event.getEntityType() != EntityType.PLAYER) {
255            return;
256        }
257        Location location = BukkitUtil.adapt(event.getEntity().getLocation());
258        PlotArea area = location.getPlotArea();
259        if (area == null) {
260            return;
261        }
262        Plot plot = location.getOwnedPlot();
263        if (plot == null) {
264            if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, InvincibleFlag.class, true)) {
265                event.setCancelled(true);
266            }
267            return;
268        }
269        if (plot.getFlag(InvincibleFlag.class)) {
270            plot.debug(event.getEntity().getName() + " could not take damage because invincible = true");
271            event.setCancelled(true);
272        }
273    }
274
275    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
276    public void onBigBoom(EntityExplodeEvent event) {
277        Location location = BukkitUtil.adapt(event.getLocation());
278        PlotArea area = location.getPlotArea();
279        boolean plotArea = location.isPlotArea();
280        if (!plotArea) {
281            if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) {
282                return;
283            }
284            return;
285        }
286        Plot plot = area.getOwnedPlot(location);
287        if (plot != null) {
288            if (plot.getFlag(ExplosionFlag.class)) {
289                List<MetadataValue> meta = event.getEntity().getMetadata("plot");
290                Plot origin;
291                if (meta.isEmpty()) {
292                    origin = plot;
293                } else {
294                    origin = (Plot) meta.get(0).value();
295                }
296                if (this.lastRadius != 0) {
297                    List<Entity> nearby = event.getEntity().getNearbyEntities(this.lastRadius, this.lastRadius, this.lastRadius);
298                    for (Entity near : nearby) {
299                        if (near instanceof TNTPrimed || near.getType().equals(EntityType.MINECART_TNT)) {
300                            if (!near.hasMetadata("plot")) {
301                                near.setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot));
302                            }
303                        }
304                    }
305                    this.lastRadius = 0;
306                }
307                Iterator<Block> iterator = event.blockList().iterator();
308                while (iterator.hasNext()) {
309                    Block block = iterator.next();
310                    location = BukkitUtil.adapt(block.getLocation());
311                    if (!area.contains(location.getX(), location.getZ()) || !origin.equals(area.getOwnedPlot(location))) {
312                        iterator.remove();
313                    }
314                }
315                return;
316            } else {
317                plot.debug("Explosion was cancelled because explosion = false");
318            }
319        }
320        event.setCancelled(true);
321        //Spawn Explosion Particles when enabled in settings
322        if (Settings.General.ALWAYS_SHOW_EXPLOSIONS) {
323            event.getLocation().getWorld().spawnParticle(Particle.EXPLOSION_HUGE, event.getLocation(), 0);
324        }
325    }
326
327    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
328    public void onPeskyMobsChangeTheWorldLikeWTFEvent(EntityChangeBlockEvent event) {
329        Entity e = event.getEntity();
330        Material type = event.getBlock().getType();
331        Location location = BukkitUtil.adapt(event.getBlock().getLocation());
332        PlotArea area = location.getPlotArea();
333        if (area == null) {
334            return;
335        }
336        if (e instanceof FallingBlock) {
337            // allow falling blocks converting to blocks and vice versa
338            return;
339        } else if (e instanceof Boat) {
340            // allow boats destroying lily pads
341            if (type == Material.LILY_PAD) {
342                return;
343            }
344        } else if (e instanceof Player player) {
345            BukkitPlayer pp = BukkitUtil.adapt(player);
346            if (type.toString().equals("POWDER_SNOW")) {
347                // Burning player evaporating powder snow. Use same checks as
348                // trampling farmland
349                BlockType blockType = BukkitAdapter.asBlockType(type);
350                if (!this.eventDispatcher.checkPlayerBlockEvent(pp,
351                        PlayerBlockEventType.TRIGGER_PHYSICAL, location, blockType, true
352                )) {
353                    event.setCancelled(true);
354                }
355                return;
356            } else {
357                // already handled by other flags (mainly the 'use' flag):
358                // - player tilting big dripleaf by standing on it
359                // - player picking glow berries from cave vine
360                // - player trampling farmland
361                // - player standing on or clicking redstone ore
362                return;
363            }
364        } else if (e instanceof Projectile entity) {
365            // Exact same as the ProjectileHitEvent listener, except that we let
366            // the entity-change-block determine what to do with shooters that
367            // aren't players and aren't blocks
368            Plot plot = area.getPlot(location);
369            ProjectileSource shooter = entity.getShooter();
370            if (shooter instanceof Player) {
371                PlotPlayer<?> pp = BukkitUtil.adapt((Player) shooter);
372                if (plot == null) {
373                    if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED)) {
374                        entity.remove();
375                        event.setCancelled(true);
376                    }
377                    return;
378                }
379                if (plot.isAdded(pp.getUUID()) || pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER)) {
380                    return;
381                }
382                entity.remove();
383                event.setCancelled(true);
384                return;
385            }
386            if (!(shooter instanceof Entity) && shooter != null) {
387                if (plot == null) {
388                    entity.remove();
389                    event.setCancelled(true);
390                    return;
391                }
392                Location sLoc =
393                        BukkitUtil.adapt(((BlockProjectileSource) shooter).getBlock().getLocation());
394                if (!area.contains(sLoc.getX(), sLoc.getZ())) {
395                    entity.remove();
396                    event.setCancelled(true);
397                    return;
398                }
399                Plot sPlot = area.getOwnedPlotAbs(sLoc);
400                if (sPlot == null || !PlotHandler.sameOwners(plot, sPlot)) {
401                    entity.remove();
402                    event.setCancelled(true);
403                }
404                return;
405            }
406            // fall back to entity-change-block flag
407        }
408
409        Plot plot = area.getOwnedPlot(location);
410        if (plot != null && !plot.getFlag(EntityChangeBlockFlag.class)) {
411            plot.debug(e.getType() + " could not change block because entity-change-block = false");
412            event.setCancelled(true);
413        }
414    }
415
416    @EventHandler
417    public void onPrime(ExplosionPrimeEvent event) {
418        this.lastRadius = event.getRadius() + 1;
419    }
420
421    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
422    public void onVehicleCreate(VehicleCreateEvent event) {
423        Vehicle entity = event.getVehicle();
424        Location location = BukkitUtil.adapt(entity.getLocation());
425        PlotArea area = location.getPlotArea();
426        if (area == null) {
427            return;
428        }
429        Plot plot = area.getOwnedPlotAbs(location);
430        if (plot == null || BukkitEntityUtil.checkEntity(entity, plot)) {
431            entity.remove();
432            return;
433        }
434        if (Settings.Enabled_Components.KILL_ROAD_VEHICLES) {
435            entity.setMetadata("plot", new FixedMetadataValue((Plugin) PlotSquared.platform(), plot));
436        }
437    }
438
439}