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