package tc.oc.pgm.modules; import org.bukkit.World; import org.bukkit.entity.Arrow; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.plugin.Plugin; import org.bukkit.potion.PotionEffect; import org.bukkit.util.Vector; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchScope; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.projectile.EntityLaunchEvent; import java.util.Random; import java.util.Set; @ListenerScope(MatchScope.RUNNING) public class ModifyBowProjectileMatchModule extends MatchModule implements Listener { protected final Class<? extends Entity> projectile; protected final float velocityMod; protected final Set<PotionEffect> potionEffects; protected final Random random = new Random(); public ModifyBowProjectileMatchModule(Match match, Class<? extends Entity> projectile, float velocityMod, Set<PotionEffect> effects) { super(match); this.projectile = projectile; this.velocityMod = velocityMod; potionEffects = effects; } @EventHandler(ignoreCancelled = true) public void changeBowProjectile(EntityShootBowEvent event) { Plugin plugin = this.getMatch().getPlugin(); Entity newProjectile; if(this.projectile == Arrow.class && event.getProjectile() instanceof Arrow) { // Don't change the projectile if it's an Arrow and the custom entity type is also Arrow newProjectile = event.getProjectile(); } else { // Replace the projectile World world = event.getEntity().getWorld(); Projectile oldEntity = (Projectile) event.getProjectile(); if(Projectile.class.isAssignableFrom(projectile)) { // Ender Pearls need to be soawned this way, otherwise they will hit the shooter sometimes newProjectile = event.getEntity().launchProjectile(projectile.asSubclass(Projectile.class), oldEntity.getVelocity()); } else { newProjectile = world.spawn(oldEntity.getLocation(), projectile); newProjectile.setVelocity(oldEntity.getVelocity()); } event.setProjectile(newProjectile); // Copy some things from the old projectile newProjectile.setFallDistance(oldEntity.getFallDistance()); newProjectile.setFireTicks(oldEntity.getFireTicks()); if(newProjectile instanceof Projectile) { ((Projectile) newProjectile).setShooter(oldEntity.getShooter()); ((Projectile) newProjectile).setBounce(oldEntity.doesBounce()); } // Save some special properties of Arrows if(oldEntity instanceof Arrow) { Arrow arrow = (Arrow) oldEntity; newProjectile.setMetadata("critical", new FixedMetadataValue(plugin, arrow.isCritical())); newProjectile.setMetadata("knockback", new FixedMetadataValue(plugin, arrow.getKnockbackStrength())); newProjectile.setMetadata("damage", new FixedMetadataValue(plugin, arrow.getDamage())); } } // Tag the projectile as custom newProjectile.setMetadata("customProjectile", new FixedMetadataValue(plugin, true)); getMatch().callEvent(new EntityLaunchEvent(newProjectile, event.getEntity())); } @EventHandler(ignoreCancelled = true) public void fixEntityDamage(EntityDamageByEntityEvent event) { Entity projectile = event.getDamager(); if(projectile.hasMetadata("customProjectile")) { // If the custom projectile replaced an arrow, recreate some effects specific to arrows if(projectile.hasMetadata("damage")) { boolean critical = projectile.getMetadata("critical").get(0).asBoolean(); int knockback = projectile.getMetadata("knockback").get(0).asInt(); double damage = projectile.getMetadata("damage").get(0).asDouble(); double speed = projectile.getVelocity().length(); // Reproduce the damage calculation from nms.EntityArrow with the addition of our modifier int finalDamage = (int) Math.ceil(speed * damage * this.velocityMod); if(critical) { finalDamage += random.nextInt(finalDamage / 2 + 2); } event.setDamage(finalDamage); // Flame arrows - target burns for 5 seconds always if(projectile.getFireTicks() > 0) { event.getEntity().setFireTicks(100); } // Reproduce the knockback calculation for punch bows if(knockback > 0) { Vector projectileVelocity = projectile.getVelocity(); double horizontalSpeed = Math.sqrt(projectileVelocity.getX() * projectileVelocity.getX() + projectileVelocity.getZ() * projectileVelocity.getZ()); Vector velocity = event.getEntity().getVelocity(); velocity.setX(velocity.getX() + projectileVelocity.getX() * knockback * 0.6 / horizontalSpeed); velocity.setY(velocity.getY() + 0.1); velocity.setZ(velocity.getZ() + projectileVelocity.getZ() * knockback * 0.6 / horizontalSpeed); event.getEntity().setVelocity(velocity); } } // Apply any potion effects attached to the projectile if(event.getEntity() instanceof LivingEntity) { for(PotionEffect potionEffect : this.potionEffects) { ((LivingEntity) event.getEntity()).addPotionEffect(potionEffect); } } } } }