package tc.oc.pgm.projectile; import java.util.logging.Level; import javax.inject.Inject; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.util.Vector; import tc.oc.commons.bukkit.event.targeted.TargetedEventHandler; import tc.oc.commons.bukkit.logging.MapdevLogger; import tc.oc.commons.core.util.CheckedCloseable; import tc.oc.commons.core.util.TimeUtils; import tc.oc.pgm.PGM; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchPlayerFacet; import tc.oc.pgm.match.MatchScope; import tc.oc.pgm.match.ParticipantState; import static com.google.common.base.Preconditions.checkState; @ListenerScope(MatchScope.RUNNING) public class ProjectilePlayerFacet implements MatchPlayerFacet, Listener { private final Match match; private final Player player; private final MapdevLogger mapdevLogger; @Inject ProjectilePlayerFacet(Match match, Player player, MapdevLogger mapdevLogger) { this.match = match; this.player = player; this.mapdevLogger = mapdevLogger; } @TargetedEventHandler(priority = EventPriority.HIGH) public void onClickEvent(PlayerInteractEvent event) { if(event.getAction() == Action.PHYSICAL) return; final ParticipantState playerState = match.getParticipantState(player); if(playerState == null) return; final ItemStack item = event.getItem(); final ProjectileDefinition definition = Projectiles.getProjectileDefinition(match.featureDefinitions(), item); if(definition == null || !isValidProjectileAction(event.getAction(), definition.clickAction())) return; // Prevent the original projectile from being fired event.setCancelled(true); if(definition.cooldown() != null && player.getRemainingItemCooldown(item.getType()) > 0F) return; final boolean realProjectile = Projectile.class.isAssignableFrom(definition.projectile()); final Vector velocity = player.getEyeLocation().getDirection().multiply(definition.velocity()); final Entity projectile; checkState(Projectiles.launchingDefinition.get() == null, "nested projectile launch"); try(CheckedCloseable x = Projectiles.launchingDefinition.let(definition)) { try { if(realProjectile) { projectile = player.launchProjectile(definition.projectile().asSubclass(Projectile.class), velocity); } else { projectile = player.getWorld().spawn(player.getEyeLocation(), definition.projectile()); projectile.setVelocity(velocity); } } catch(Exception e) { mapdevLogger.log(Level.SEVERE, "Failed to spawn custom projectile type " + definition.projectile().getSimpleName(), e); return; } projectile.setMetadata(Projectiles.METADATA_KEY, new FixedMetadataValue(PGM.get(), definition)); } // If the entity implements Projectile, it will have already generated a ProjectileLaunchEvent. // Otherwise, we fire our custom event. if(!realProjectile) { EntityLaunchEvent launchEvent = new EntityLaunchEvent(projectile, event.getPlayer()); match.callEvent(launchEvent); if(launchEvent.isCancelled()) { projectile.remove(); return; } } if(definition.throwable()) { if(item.getAmount() > 1) { item.setAmount(item.getAmount() - 1); } else { player.getInventory().setItem(event.getHand(), null); } } if(definition.cooldown() != null) { player.startItemCooldown(item.getType(), (int) TimeUtils.toTicks(definition.cooldown())); } } private static boolean isValidProjectileAction(Action action, ClickAction clickAction) { switch(clickAction) { case RIGHT: return action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK; case LEFT: return action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK; case BOTH: return action != Action.PHYSICAL; } return false; } }