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.util.BukkitEntityUtil; 023import com.plotsquared.bukkit.util.BukkitUtil; 024import com.plotsquared.core.configuration.caption.TranslatableCaption; 025import com.plotsquared.core.location.Location; 026import com.plotsquared.core.permissions.Permission; 027import com.plotsquared.core.player.PlotPlayer; 028import com.plotsquared.core.plot.Plot; 029import com.plotsquared.core.plot.PlotArea; 030import com.plotsquared.core.plot.PlotHandler; 031import com.plotsquared.core.plot.flag.implementations.FishingFlag; 032import com.plotsquared.core.plot.flag.implementations.ProjectilesFlag; 033import com.plotsquared.core.plot.world.PlotAreaManager; 034import com.plotsquared.core.util.PlotFlagUtil; 035import net.kyori.adventure.text.minimessage.tag.Tag; 036import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 037import org.bukkit.entity.Entity; 038import org.bukkit.entity.FishHook; 039import org.bukkit.entity.LivingEntity; 040import org.bukkit.entity.Player; 041import org.bukkit.entity.Projectile; 042import org.bukkit.entity.ThrownPotion; 043import org.bukkit.event.EventHandler; 044import org.bukkit.event.EventPriority; 045import org.bukkit.event.Listener; 046import org.bukkit.event.entity.LingeringPotionSplashEvent; 047import org.bukkit.event.entity.PotionSplashEvent; 048import org.bukkit.event.entity.ProjectileHitEvent; 049import org.bukkit.event.entity.ProjectileLaunchEvent; 050import org.bukkit.event.player.PlayerEggThrowEvent; 051import org.bukkit.projectiles.BlockProjectileSource; 052import org.bukkit.projectiles.ProjectileSource; 053import org.checkerframework.checker.nullness.qual.NonNull; 054 055@SuppressWarnings("unused") 056public class ProjectileEventListener implements Listener { 057 058 private final PlotAreaManager plotAreaManager; 059 060 @Inject 061 public ProjectileEventListener(final @NonNull PlotAreaManager plotAreaManager) { 062 this.plotAreaManager = plotAreaManager; 063 } 064 065 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 066 public void onLingeringPotionSplash(LingeringPotionSplashEvent event) { 067 // Cancelling projectile hit events still results in area effect clouds. 068 // We need to cancel the splash events to get rid of those. 069 onProjectileHit(event); 070 } 071 072 @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 073 public void onPotionSplash(PotionSplashEvent event) { 074 ThrownPotion damager = event.getPotion(); 075 Location location = BukkitUtil.adapt(damager.getLocation()); 076 if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) { 077 return; 078 } 079 int count = 0; 080 for (LivingEntity victim : event.getAffectedEntities()) { 081 if (!BukkitEntityUtil.entityDamage(damager, victim)) { 082 event.setIntensity(victim, 0); 083 count++; 084 } 085 } 086 if (count > 0 && count == event.getAffectedEntities().size()) { 087 event.setCancelled(true); 088 } else { 089 // Cancelling projectile hit events still results in potions 090 // splashing in the world. We need to cancel the splash events to 091 // avoid that. 092 onProjectileHit(event); 093 } 094 } 095 096 @EventHandler(ignoreCancelled = true) 097 public void onProjectileLaunch(ProjectileLaunchEvent event) { 098 Projectile entity = event.getEntity(); 099 ProjectileSource shooter = entity.getShooter(); 100 if (!(shooter instanceof Player)) { 101 return; 102 } 103 Location location = BukkitUtil.adapt(entity.getLocation()); 104 PlotArea area = location.getPlotArea(); 105 if (area == null) { 106 return; 107 } 108 PlotPlayer<Player> pp = BukkitUtil.adapt((Player) shooter); 109 Plot plot = location.getOwnedPlot(); 110 111 if (plot == null) { 112 if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, ProjectilesFlag.class, true) && !pp.hasPermission( 113 Permission.PERMISSION_ADMIN_PROJECTILE_ROAD 114 )) { 115 pp.sendMessage( 116 TranslatableCaption.of("permission.no_permission_event"), 117 TagResolver.resolver( 118 "node", 119 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_ROAD) 120 ) 121 ); 122 entity.remove(); 123 event.setCancelled(true); 124 } 125 } else if (!plot.hasOwner()) { 126 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED)) { 127 pp.sendMessage( 128 TranslatableCaption.of("permission.no_permission_event"), 129 TagResolver.resolver( 130 "node", 131 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED) 132 ) 133 ); 134 entity.remove(); 135 event.setCancelled(true); 136 } 137 } else if (!plot.isAdded(pp.getUUID())) { 138 if (entity instanceof FishHook) { 139 if (plot.getFlag(FishingFlag.class)) { 140 return; 141 } 142 } 143 if (!plot.getFlag(ProjectilesFlag.class)) { 144 if (!pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER)) { 145 pp.sendMessage( 146 TranslatableCaption.of("permission.no_permission_event"), 147 TagResolver.resolver( 148 "node", 149 Tag.inserting(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER) 150 ) 151 ); 152 entity.remove(); 153 event.setCancelled(true); 154 } 155 } 156 } 157 } 158 159 @EventHandler 160 public void onProjectileHit(ProjectileHitEvent event) { 161 if (cancelProjectileHit(event.getEntity())) { 162 event.setCancelled(true); 163 } 164 } 165 166 @EventHandler 167 public void onPlayerEggThrow(PlayerEggThrowEvent event) { 168 if (cancelProjectileHit(event.getEgg())) { 169 event.setHatching(false); 170 } 171 } 172 173 private boolean cancelProjectileHit(Projectile entity) { 174 Location location = BukkitUtil.adapt(entity.getLocation()); 175 if (!this.plotAreaManager.hasPlotArea(location.getWorldName())) { 176 return false; 177 } 178 PlotArea area = location.getPlotArea(); 179 if (area == null) { 180 return false; 181 } 182 Plot plot = area.getPlot(location); 183 ProjectileSource shooter = entity.getShooter(); 184 if (shooter instanceof Player) { 185 if (!((Player) shooter).isOnline()) { 186 if (plot != null) { 187 if (plot.isAdded(((Player) shooter).getUniqueId()) || plot.getFlag(ProjectilesFlag.class)) { 188 return false; 189 } 190 } else if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, ProjectilesFlag.class, true)) { 191 return false; 192 } 193 194 entity.remove(); 195 return true; 196 } 197 198 PlotPlayer<?> pp = BukkitUtil.adapt((Player) shooter); 199 if (plot == null) { 200 if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, ProjectilesFlag.class, true) && !pp.hasPermission( 201 Permission.PERMISSION_ADMIN_PROJECTILE_UNOWNED 202 )) { 203 entity.remove(); 204 return true; 205 } 206 return false; 207 } 208 if (plot.isAdded(pp.getUUID()) || pp.hasPermission(Permission.PERMISSION_ADMIN_PROJECTILE_OTHER) || plot.getFlag( 209 ProjectilesFlag.class) || (entity instanceof FishHook && plot.getFlag( 210 FishingFlag.class))) { 211 return false; 212 } 213 entity.remove(); 214 return true; 215 } 216 if (!(shooter instanceof Entity) && shooter != null) { 217 if (plot == null) { 218 entity.remove(); 219 return true; 220 } 221 Location sLoc = 222 BukkitUtil.adapt(((BlockProjectileSource) shooter).getBlock().getLocation()); 223 if (!area.contains(sLoc.getX(), sLoc.getZ())) { 224 entity.remove(); 225 return true; 226 } 227 Plot sPlot = area.getOwnedPlotAbs(sLoc); 228 if (sPlot == null || !PlotHandler.sameOwners(plot, sPlot)) { 229 entity.remove(); 230 return true; 231 } 232 } 233 return false; 234 } 235 236}