diff --git a/Changelog.txt b/Changelog.txt index de88bc2c5..57b8943f5 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -17,6 +17,7 @@ Version 2.1.133 Smelting now has a Bonus Drops section in config.yml Second Smelt now only doubles smelting results for items which have bonus drop entries in the config Fixed an array out of index bug for inventory click events + mcMMO will now register arrows shot from the offhand as being from either Archery or Crossbows (before mcMMO ignored offhand Archery) (These permissions are all included in the mcmmo.defaults node) New permission node 'mcmmo.commands.tridents' diff --git a/src/main/java/com/gmail/nossr50/datatypes/meta/ProjectileOriginMeta.java b/src/main/java/com/gmail/nossr50/datatypes/meta/ProjectileOriginMeta.java new file mode 100644 index 000000000..41f70c0a7 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/datatypes/meta/ProjectileOriginMeta.java @@ -0,0 +1,17 @@ +package com.gmail.nossr50.datatypes.meta; + +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +public class ProjectileOriginMeta extends FixedMetadataValue { + /** + * Initializes a FixedMetadataValue with an Object + * + * @param owningPlugin the {@link Plugin} that created this metadata value + * @param value the value assigned to this metadata value + */ + public ProjectileOriginMeta(@NotNull Plugin owningPlugin, int value) { + super(owningPlugin, value); + } +} diff --git a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java index 5b79227ba..f68fe1cb2 100644 --- a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java @@ -4,6 +4,7 @@ import com.gmail.nossr50.config.AdvancedConfig; import com.gmail.nossr50.config.Config; import com.gmail.nossr50.config.WorldBlacklist; import com.gmail.nossr50.config.experience.ExperienceConfig; +import com.gmail.nossr50.datatypes.meta.ProjectileOriginMeta; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType; @@ -19,6 +20,7 @@ import com.gmail.nossr50.skills.taming.Taming; import com.gmail.nossr50.skills.taming.TamingManager; import com.gmail.nossr50.skills.unarmed.UnarmedManager; import com.gmail.nossr50.util.BlockUtils; +import com.gmail.nossr50.util.ItemUtils; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; @@ -122,7 +124,7 @@ public class EntityListener implements Listener { projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(plugin, projectile.getLocation())); } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onProjectileLaunch(ProjectileLaunchEvent event) { /* WORLD BLACKLIST CHECK */ if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) @@ -144,9 +146,19 @@ public class EntityListener implements Listener { if(!(projectile instanceof Arrow)) return; - projectile.setMetadata(mcMMO.bowForceKey, new FixedMetadataValue(plugin, 1.0)); projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(plugin, projectile.getLocation())); + //Track origin of projectile + if(ItemUtils.hasItemInMainHand(player, "bow")) { + markProjectileOriginAsBow(projectile); + } else if(ItemUtils.hasItemInMainHand(player, "crossbow")) { + markProjectileOriginAsCrossbow(projectile); + } else if(ItemUtils.hasItemInOffHand(player, "bow")) { + markProjectileOriginAsBow(projectile); + } else if(ItemUtils.hasItemInOffHand(player, "crossbow")) { + markProjectileOriginAsCrossbow(projectile); + } + for(Enchantment enchantment : player.getInventory().getItemInMainHand().getEnchantments().keySet()) { if(enchantment.getName().equalsIgnoreCase("piercing")) return; @@ -158,6 +170,14 @@ public class EntityListener implements Listener { } } + private void markProjectileOriginAsCrossbow(Projectile projectile) { + projectile.setMetadata(mcMMO.PROJECTILE_ORIGIN_METAKEY, new ProjectileOriginMeta(plugin, 2)); + } + + private void markProjectileOriginAsBow(Projectile projectile) { + projectile.setMetadata(mcMMO.PROJECTILE_ORIGIN_METAKEY, new ProjectileOriginMeta(plugin, 1)); + } + /** * Monitor EntityChangeBlock events. * diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index ec7f5df41..3fb5fe8ad 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -134,6 +134,7 @@ public class mcMMO extends JavaPlugin { public final static String greenThumbDataKey = "mcMMO: Green Thumb"; public final static String databaseCommandKey = "mcMMO: Processing Database Command"; public final static String bredMetadataKey = "mcMMO: Bred Animal"; + public final static String PROJECTILE_ORIGIN_METAKEY = "mcMMO: Projectile Origin"; public static FixedMetadataValue metadataValue; diff --git a/src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowManager.java b/src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowManager.java index a6f220ee1..fbfbcef4c 100644 --- a/src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowManager.java +++ b/src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowManager.java @@ -2,10 +2,38 @@ package com.gmail.nossr50.skills.crossbows; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.skills.SkillManager; +import com.gmail.nossr50.skills.archery.Archery; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; public class CrossbowManager extends SkillManager { public CrossbowManager(McMMOPlayer mcMMOPlayer) { super(mcMMOPlayer, PrimarySkillType.CROSSBOWS); } + + /** + * Calculate bonus XP awarded for Archery when hitting a far-away target. + * + * @param target The {@link LivingEntity} damaged by the arrow + * @param damager The {@link Entity} who shot the arrow + */ + public double distanceXpBonusMultiplier(LivingEntity target, Entity damager) { + //Hacky Fix - some plugins spawn arrows and assign them to players after the ProjectileLaunchEvent fires + if(!damager.hasMetadata(mcMMO.arrowDistanceKey)) + return damager.getLocation().distance(target.getLocation()); + + Location firedLocation = (Location) damager.getMetadata(mcMMO.arrowDistanceKey).get(0).value(); + Location targetLocation = target.getLocation(); + + if (firedLocation.getWorld() != targetLocation.getWorld()) { + return 1; + } + + //TODO: Should use its own variable + return 1 + Math.min(firedLocation.distance(targetLocation), 50) * Archery.DISTANCE_XP_MULTIPLIER; + } + } diff --git a/src/main/java/com/gmail/nossr50/util/ItemUtils.java b/src/main/java/com/gmail/nossr50/util/ItemUtils.java index 2eb5dfe28..48707a109 100644 --- a/src/main/java/com/gmail/nossr50/util/ItemUtils.java +++ b/src/main/java/com/gmail/nossr50/util/ItemUtils.java @@ -31,9 +31,23 @@ public final class ItemUtils { public static boolean hasItemInEitherHand(Player player, Material material) { - return player.getInventory().getItemInMainHand().getType() == material || player.getInventory().getItemInOffHand().getType() == material; + return hasItemInEitherHand(player, material.getKey().getKey()); } + public static boolean hasItemInEitherHand(Player player, String id) { + return player.getInventory().getItemInMainHand().getType().getKey().getKey().equalsIgnoreCase(id) + || player.getInventory().getItemInOffHand().getType().getKey().getKey().equalsIgnoreCase(id); + } + + public static boolean hasItemInMainHand(Player player, String id) { + return player.getInventory().getItemInMainHand().getType().getKey().getKey().equalsIgnoreCase(id); + } + + public static boolean hasItemInOffHand(Player player, String id) { + return player.getInventory().getItemInOffHand().getType().getKey().getKey().equalsIgnoreCase(id); + } + + /** * Checks if the item is a sword. * diff --git a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java index 6c9931b73..52e20b526 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java +++ b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java @@ -16,6 +16,7 @@ import com.gmail.nossr50.runnables.skills.AwardCombatXpTask; import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager; import com.gmail.nossr50.skills.archery.ArcheryManager; import com.gmail.nossr50.skills.axes.AxesManager; +import com.gmail.nossr50.skills.crossbows.CrossbowManager; import com.gmail.nossr50.skills.swords.SwordsManager; import com.gmail.nossr50.skills.taming.TamingManager; import com.gmail.nossr50.skills.tridents.TridentManager; @@ -308,13 +309,49 @@ public final class CombatUtils { } double distanceMultiplier = archeryManager.distanceXpBonusMultiplier(target, arrow); - double forceMultiplier = 1.0; //Hacky Fix - some plugins spawn arrows and assign them to players after the ProjectileLaunchEvent fires +// double forceMultiplier = 1.0; //Hacky Fix - some plugins spawn arrows and assign them to players after the ProjectileLaunchEvent fires - if(arrow.hasMetadata(mcMMO.bowForceKey)) - forceMultiplier = arrow.getMetadata(mcMMO.bowForceKey).get(0).asDouble(); +// if(arrow.hasMetadata(mcMMO.bowForceKey)) +// forceMultiplier = arrow.getMetadata(mcMMO.bowForceKey).get(0).asDouble(); applyScaledModifiers(initialDamage, finalDamage, event); - processCombatXP(mcMMOPlayer, target, PrimarySkillType.ARCHERY, forceMultiplier * distanceMultiplier); + processCombatXP(mcMMOPlayer, target, PrimarySkillType.ARCHERY, distanceMultiplier); + } + + private static void processCrossbowCombat(LivingEntity target, Player player, EntityDamageByEntityEvent event, Projectile arrow) { + double initialDamage = event.getDamage(); + + McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); + + //Make sure the profiles been loaded + if(mcMMOPlayer == null) { + return; + } + + CrossbowManager crossbowManager = mcMMOPlayer.getCrossbowManager(); + + double finalDamage = event.getDamage(); + + if (target instanceof Player && PrimarySkillType.UNARMED.getPVPEnabled()) { + UnarmedManager unarmedManager = UserManager.getPlayer((Player) target).getUnarmedManager(); + + if (unarmedManager.canDeflect()) { + event.setCancelled(unarmedManager.deflectCheck()); + + if (event.isCancelled()) { + return; + } + } + } + + if(canUseLimitBreak(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK)) + { + finalDamage+=getLimitBreakDamage(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK); + } + + double distanceMultiplier = crossbowManager.distanceXpBonusMultiplier(target, arrow); + applyScaledModifiers(initialDamage, finalDamage, event); + processCombatXP(mcMMOPlayer, target, PrimarySkillType.CROSSBOWS, distanceMultiplier); } /** @@ -433,18 +470,32 @@ public final class CombatUtils { } else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) { Projectile arrow = (Projectile) painSource; - ProjectileSource projectileSource = arrow.getShooter(); + ProjectileSource projectileShooter = arrow.getShooter(); //Determine if the arrow belongs to a bow or xbow - if (projectileSource instanceof Player && PrimarySkillType.ARCHERY.shouldProcess(target)) { - Player player = (Player) projectileSource; + if (projectileShooter instanceof Player) { + Player player = (Player) projectileShooter; - if (!Misc.isNPCEntityExcludingVillagers(player) && PrimarySkillType.ARCHERY.getPermissions(player)) { - processArcheryCombat(target, player, event, arrow); + //Has metadata + if(arrow.getMetadata(mcMMO.PROJECTILE_ORIGIN_METAKEY).size() > 0) { + if(isProjectileFromBow(arrow)) { + if(PrimarySkillType.ARCHERY.shouldProcess(target)) { + if (!Misc.isNPCEntityExcludingVillagers(player) && PrimarySkillType.ARCHERY.getPermissions(player)) { + processArcheryCombat(target, player, event, arrow); + } + } + } else if(isProjectileFromCrossbow(arrow)) { + if(PrimarySkillType.CROSSBOWS.shouldProcess(target)) { + if (!Misc.isNPCEntityExcludingVillagers(player) && PrimarySkillType.CROSSBOWS.getPermissions(player)) { + processCrossbowCombat(target, player, event, arrow); + } + } + } } + if (target.getType() != EntityType.CREEPER && !Misc.isNPCEntityExcludingVillagers(player) && PrimarySkillType.TAMING.getPermissions(player)) { McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); TamingManager tamingManager = mcMMOPlayer.getTamingManager(); @@ -454,6 +505,14 @@ public final class CombatUtils { } } + private static boolean isProjectileFromCrossbow(Projectile arrow) { + return arrow.getMetadata(mcMMO.PROJECTILE_ORIGIN_METAKEY).get(0).asInt() == 2; + } + + private static boolean isProjectileFromBow(Projectile arrow) { + return arrow.getMetadata(mcMMO.PROJECTILE_ORIGIN_METAKEY).get(0).asInt() == 1; + } + /** * This cleans up names from displaying in chat as hearts * @param entity target entity