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