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.plotsquared.bukkit.player.BukkitPlayer;
022import com.plotsquared.core.configuration.Settings;
023import com.plotsquared.core.configuration.caption.TranslatableCaption;
024import com.plotsquared.core.location.Location;
025import com.plotsquared.core.permissions.Permission;
026import com.plotsquared.core.plot.Plot;
027import com.plotsquared.core.plot.PlotArea;
028import com.plotsquared.core.plot.flag.implementations.AnimalAttackFlag;
029import com.plotsquared.core.plot.flag.implementations.AnimalCapFlag;
030import com.plotsquared.core.plot.flag.implementations.DoneFlag;
031import com.plotsquared.core.plot.flag.implementations.EntityCapFlag;
032import com.plotsquared.core.plot.flag.implementations.HangingBreakFlag;
033import com.plotsquared.core.plot.flag.implementations.HostileAttackFlag;
034import com.plotsquared.core.plot.flag.implementations.HostileCapFlag;
035import com.plotsquared.core.plot.flag.implementations.MiscBreakFlag;
036import com.plotsquared.core.plot.flag.implementations.MiscCapFlag;
037import com.plotsquared.core.plot.flag.implementations.MobCapFlag;
038import com.plotsquared.core.plot.flag.implementations.PveFlag;
039import com.plotsquared.core.plot.flag.implementations.PvpFlag;
040import com.plotsquared.core.plot.flag.implementations.TamedAttackFlag;
041import com.plotsquared.core.plot.flag.implementations.VehicleBreakFlag;
042import com.plotsquared.core.plot.flag.implementations.VehicleCapFlag;
043import com.plotsquared.core.util.EntityUtil;
044import com.plotsquared.core.util.PlotFlagUtil;
045import com.plotsquared.core.util.entity.EntityCategories;
046import com.sk89q.worldedit.bukkit.BukkitAdapter;
047import net.kyori.adventure.text.Component;
048import net.kyori.adventure.text.minimessage.tag.Tag;
049import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
050import org.bukkit.entity.Arrow;
051import org.bukkit.entity.Creature;
052import org.bukkit.entity.Entity;
053import org.bukkit.entity.EntityType;
054import org.bukkit.entity.Firework;
055import org.bukkit.entity.Player;
056import org.bukkit.entity.Projectile;
057import org.bukkit.event.entity.EntityDamageEvent;
058import org.bukkit.projectiles.BlockProjectileSource;
059import org.bukkit.projectiles.ProjectileSource;
060
061import java.util.Objects;
062
063public class BukkitEntityUtil {
064
065    public static final com.sk89q.worldedit.world.entity.EntityType FAKE_ENTITY_TYPE =
066            new com.sk89q.worldedit.world.entity.EntityType("plotsquared:fake");
067
068    public static boolean entityDamage(Entity damager, Entity victim) {
069        return entityDamage(damager, victim, null);
070    }
071
072    public static boolean entityDamage(Entity damager, Entity victim, EntityDamageEvent.DamageCause cause) {
073        Location dloc = BukkitUtil.adapt(damager.getLocation());
074        Location vloc = BukkitUtil.adapt(victim.getLocation());
075        PlotArea dArea = dloc.getPlotArea();
076        PlotArea vArea;
077        if (dArea != null && dArea.contains(vloc.getX(), vloc.getZ())) {
078            vArea = dArea;
079        } else {
080            vArea = vloc.getPlotArea();
081        }
082        if (dArea == null && vArea == null) {
083            return true;
084        }
085
086        Plot dplot;
087        if (dArea != null) {
088            dplot = dArea.getPlot(dloc);
089        } else {
090            dplot = null;
091        }
092        Plot vplot;
093        if (vArea != null) {
094            vplot = vArea.getPlot(vloc);
095        } else {
096            vplot = null;
097        }
098
099        Plot plot;
100        String stub;
101        boolean isPlot = true;
102        if (dplot == null && vplot == null) {
103            if (dArea == null) {
104                return true;
105            }
106            plot = null;
107            stub = "road";
108            isPlot = false;
109        } else {
110            // Prioritize plots for close to seamless pvp zones
111            if (victim.getTicksLived() > damager.getTicksLived()) {
112                if (dplot == null || !(victim instanceof Player)) {
113                    if (vplot == null) {
114                        plot = dplot;
115                    } else {
116                        plot = vplot;
117                    }
118                } else {
119                    plot = dplot;
120                }
121            } else if (dplot == null || !(victim instanceof Player)) {
122                if (vplot == null) {
123                    plot = dplot;
124                } else {
125                    plot = vplot;
126                }
127            } else if (vplot == null) {
128                plot = dplot;
129            } else {
130                plot = vplot;
131            }
132            if (plot.hasOwner()) {
133                stub = "other";
134            } else {
135                stub = "unowned";
136            }
137        }
138        boolean roadFlags = vArea != null ? vArea.isRoadFlags() : dArea.isRoadFlags();
139        PlotArea area = vArea != null ? vArea : dArea;
140
141        Player player;
142        if (damager instanceof Player) { // attacker is player
143            player = (Player) damager;
144        } else if (damager instanceof Projectile projectile) {
145            ProjectileSource shooter = projectile.getShooter();
146            if (shooter instanceof Player) { // shooter is player
147                player = (Player) shooter;
148            } else { // shooter is not player
149                if (shooter instanceof BlockProjectileSource) {
150                    Location sLoc = BukkitUtil
151                            .adapt(((BlockProjectileSource) shooter).getBlock().getLocation());
152                    dplot = dArea.getPlot(sLoc);
153                }
154                player = null;
155            }
156        } else { // Attacker is not player
157            player = null;
158        }
159        if (player != null) {
160            BukkitPlayer plotPlayer = BukkitUtil.adapt(player);
161
162            final com.sk89q.worldedit.world.entity.EntityType entityType;
163
164            // Create a fake entity type if the type does not have a name
165            if (victim.getType().getName() == null) {
166                entityType = FAKE_ENTITY_TYPE;
167            } else {
168                entityType = BukkitAdapter.adapt(victim.getType());
169            }
170
171            if (EntityCategories.HANGING.contains(entityType)) { // hanging
172                if (plot != null && (plot.getFlag(HangingBreakFlag.class) || plot
173                        .isAdded(plotPlayer.getUUID()))) {
174                    if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) {
175                        if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER)) {
176                            plotPlayer.sendMessage(
177                                    TranslatableCaption.of("done.building_restricted")
178                            );
179                            return false;
180                        }
181                    }
182                    return true;
183                }
184                if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY + "." + stub)) {
185                    plotPlayer.sendMessage(
186                            TranslatableCaption.of("permission.no_permission_event"),
187                            TagResolver.resolver(
188                                    "node",
189                                    Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_DESTROY + "." + stub))
190                            )
191                    );
192                    return false;
193                }
194            } else if (victim.getType() == EntityType.ARMOR_STAND) {
195                if (plot != null && (plot.getFlag(MiscBreakFlag.class) || plot
196                        .isAdded(plotPlayer.getUUID()))) {
197                    return true;
198                }
199                if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY + "." + stub)) {
200                    plotPlayer.sendMessage(
201                            TranslatableCaption.of("permission.no_permission_event"),
202                            TagResolver.resolver(
203                                    "node",
204                                    Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_DESTROY + "." + stub))
205                            )
206                    );
207                    if (plot != null) {
208                        plot.debug(player.getName()
209                                + " could not break armor stand because misc-break = false");
210                    }
211                    return false;
212                }
213            } else if (EntityCategories.HOSTILE.contains(entityType)) {
214                if (isPlot) {
215                    if (plot.getFlag(HostileAttackFlag.class) || plot.getFlag(PveFlag.class) || plot
216                            .isAdded(plotPlayer.getUUID())) {
217                        return true;
218                    }
219                } else if (roadFlags && (area.getRoadFlag(HostileAttackFlag.class) || area
220                        .getFlag(PveFlag.class))) {
221                    return true;
222                }
223                if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_PVE + "." + stub)) {
224                    plotPlayer.sendMessage(
225                            TranslatableCaption.of("permission.no_permission_event"),
226                            TagResolver.resolver(
227                                    "node",
228                                    Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_PVE + "." + stub))
229                            )
230                    );
231                    if (plot != null) {
232                        plot.debug(player.getName() + " could not attack " + entityType
233                                + " because pve = false OR hostile-attack = false");
234                    }
235                    return false;
236                }
237            } else if (EntityCategories.TAMEABLE.contains(entityType)) { // victim is tameable
238                if (isPlot) {
239                    if (plot.getFlag(TamedAttackFlag.class) || plot.getFlag(PveFlag.class) || plot
240                            .isAdded(plotPlayer.getUUID())) {
241                        return true;
242                    }
243                } else if (roadFlags && (area.getRoadFlag(TamedAttackFlag.class) || area
244                        .getFlag(PveFlag.class))) {
245                    return true;
246                }
247                if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_PVE + "." + stub)) {
248                    plotPlayer.sendMessage(
249                            TranslatableCaption.of("permission.no_permission_event"),
250                            TagResolver.resolver(
251                                    "node",
252                                    Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_PVE + "." + stub))
253                            )
254                    );
255                    if (plot != null) {
256                        plot.debug(player.getName() + " could not attack " + entityType
257                                + " because pve = false OR tamed-attack = false");
258                    }
259                    return false;
260                }
261            } else if (EntityCategories.PLAYER.contains(entityType)) {
262                if (isPlot) {
263                    if (!plot.getFlag(PvpFlag.class) && !plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_PVP + "." + stub)) {
264                        plotPlayer.sendMessage(
265                                TranslatableCaption.of("permission.no_permission_event"),
266                                TagResolver.resolver(
267                                        "node",
268                                        Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_PVP + "." + stub))
269                                )
270                        );
271                        plot.debug(player.getName() + " could not attack " + entityType
272                                + " because pve = false");
273                        return false;
274                    } else {
275                        return true;
276                    }
277                } else if (roadFlags && area.getRoadFlag(PvpFlag.class)) {
278                    return true;
279                }
280                if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_PVP + "." + stub)) {
281                    plotPlayer.sendMessage(
282                            TranslatableCaption.of("permission.no_permission_event"),
283                            TagResolver.resolver(
284                                    "node",
285                                    Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_PVP + "." + stub))
286                            )
287                    );
288                    return false;
289                }
290            } else if (EntityCategories.ANIMAL.contains(entityType)) { // victim is animal
291                if (isPlot) {
292                    if (plot.getFlag(AnimalAttackFlag.class) || plot.getFlag(PveFlag.class) || plot
293                            .isAdded(plotPlayer.getUUID())) {
294                        return true;
295                    }
296                } else if (roadFlags && (area.getRoadFlag(AnimalAttackFlag.class) || area
297                        .getFlag(PveFlag.class))) {
298                    return true;
299                }
300                if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_PVE + "." + stub)) {
301                    plotPlayer.sendMessage(
302                            TranslatableCaption.of("permission.no_permission_event"),
303                            TagResolver.resolver(
304                                    "node",
305                                    Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_PVE + "." + stub))
306                            )
307                    );
308                    if (plot != null) {
309                        plot.debug(player.getName() + " could not attack " + entityType
310                                + " because pve = false OR animal-attack = false");
311                    }
312                    return false;
313                }
314            } else if (EntityCategories.VEHICLE
315                    .contains(entityType)) { // Vehicles are managed in vehicle destroy event
316                // Fire damage (e.g. from Fire Aspect swords) needs to be checked here as it bypasses VehicleDestroyEvent
317                if (cause == EntityDamageEvent.DamageCause.FIRE_TICK) {
318                    if (isPlot) {
319                        if (!plot.hasOwner()) {
320                            if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_UNOWNED)) {
321                                plotPlayer.sendMessage(
322                                        TranslatableCaption.of("permission.no_permission_event"),
323                                        TagResolver.resolver(
324                                                "node",
325                                                Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_UNOWNED)
326                                        )
327                                );
328                                return false;
329                            }
330                            return true;
331                        }
332                        if (plot.getFlag(VehicleBreakFlag.class) || plot.isAdded(plotPlayer.getUUID())) {
333                            return true;
334                        }
335                        if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_OTHER)) {
336                            plotPlayer.sendMessage(
337                                    TranslatableCaption.of("permission.no_permission_event"),
338                                    TagResolver.resolver(
339                                            "node",
340                                            Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_OTHER)
341                                    )
342                            );
343                            plot.debug(player.getName() + " could not set vehicle on fire because vehicle-break = false");
344                            return false;
345                        }
346                    } else {
347                        // Road
348                        if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, VehicleBreakFlag.class, true)
349                                && !plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_ROAD)) {
350                            plotPlayer.sendMessage(
351                                    TranslatableCaption.of("permission.no_permission_event"),
352                                    TagResolver.resolver(
353                                            "node",
354                                            Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_ROAD)
355                                    )
356                            );
357                            return false;
358                        }
359                    }
360                }
361                return true;
362            } else { // victim is something else
363                if (isPlot) {
364                    if (plot.getFlag(PveFlag.class) || plot.isAdded(plotPlayer.getUUID())) {
365                        return true;
366                    }
367                } else if (roadFlags && area.getRoadFlag(PveFlag.class)) {
368                    return true;
369                }
370                if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_PVE + "." + stub)) {
371                    plotPlayer.sendMessage(
372                            TranslatableCaption.of("permission.no_permission_event"),
373                            TagResolver.resolver(
374                                    "node",
375                                    Tag.inserting(Component.text(Permission.PERMISSION_ADMIN_PVE + "." + stub))
376                            )
377                    );
378                    if (plot != null) {
379                        plot.debug(player.getName() + " could not attack " + entityType
380                                + " because pve = false");
381                    }
382                    return false;
383                }
384            }
385            return true;
386        } else if (dplot != null && (!dplot.equals(vplot) || Objects
387                .equals(dplot.getOwnerAbs(), vplot.getOwnerAbs()))) {
388            return vplot != null && vplot.getFlag(PveFlag.class);
389        }
390        //disable the firework damage. too much of a headache to support at the moment.
391        if (vplot != null) {
392            if (EntityDamageEvent.DamageCause.ENTITY_EXPLOSION == cause && damager instanceof Firework) {
393                return false;
394            }
395        }
396        if (vplot == null && roadFlags && area.getRoadFlag(PveFlag.class)) {
397            return true;
398        }
399        return ((vplot != null && vplot.getFlag(PveFlag.class)) || !(damager instanceof Arrow
400                && !(victim instanceof Creature)));
401    }
402
403    public static boolean checkEntity(Entity entity, Plot plot) {
404        return checkEntity(entity.getType(), plot);
405    }
406
407    public static boolean checkEntity(EntityType type, Plot plot) {
408        if (plot == null || !plot.hasOwner() || plot.getFlags().isEmpty() && plot.getArea()
409                .getFlagContainer().getFlagMap().isEmpty()) {
410            return false;
411        }
412
413        final com.sk89q.worldedit.world.entity.EntityType entityType =
414                BukkitAdapter.adapt(type);
415
416        if (EntityCategories.PLAYER.contains(entityType)) {
417            return false;
418        }
419
420        if (EntityCategories.PROJECTILE.contains(entityType) || EntityCategories.OTHER
421                .contains(entityType) || EntityCategories.HANGING.contains(entityType)) {
422            return EntityUtil.checkEntity(
423                    plot, EntityCapFlag.ENTITY_CAP_UNLIMITED,
424                    MiscCapFlag.MISC_CAP_UNLIMITED
425            );
426        }
427
428        // Has to go go before vehicle as horses are both
429        // animals and vehicles
430        if (EntityCategories.ANIMAL.contains(entityType) || EntityCategories.VILLAGER
431                .contains(entityType) || EntityCategories.TAMEABLE.contains(entityType)) {
432            return EntityUtil
433                    .checkEntity(
434                            plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, MobCapFlag.MOB_CAP_UNLIMITED,
435                            AnimalCapFlag.ANIMAL_CAP_UNLIMITED
436                    );
437        }
438
439        if (EntityCategories.HOSTILE.contains(entityType)) {
440            return EntityUtil
441                    .checkEntity(
442                            plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, MobCapFlag.MOB_CAP_UNLIMITED,
443                            HostileCapFlag.HOSTILE_CAP_UNLIMITED
444                    );
445        }
446
447        if (EntityCategories.VEHICLE.contains(entityType)) {
448            return EntityUtil.checkEntity(
449                    plot, EntityCapFlag.ENTITY_CAP_UNLIMITED,
450                    VehicleCapFlag.VEHICLE_CAP_UNLIMITED
451            );
452        }
453
454        return EntityUtil.checkEntity(plot, EntityCapFlag.ENTITY_CAP_UNLIMITED);
455    }
456
457}