From 48bf79055aaa526f2a85a412dd101726138ba622 Mon Sep 17 00:00:00 2001 From: nossr50 Date: Fri, 2 Apr 2021 11:00:11 -0700 Subject: [PATCH] Rupture has been reworked --- Changelog.txt | 15 + .../com/gmail/nossr50/api/AbilityAPI.java | 10 +- .../datatypes/meta/RuptureTaskMeta.java | 25 + .../nossr50/datatypes/player/McMMOPlayer.java | 12 +- src/main/java/com/gmail/nossr50/mcMMO.java | 5 +- .../runnables/skills/BleedTimerTask.java | 428 +++++++++--------- .../nossr50/runnables/skills/RuptureTask.java | 142 ++++++ .../skills/herbalism/HerbalismManager.java | 1 - .../nossr50/skills/swords/SwordsManager.java | 65 ++- .../nossr50/skills/taming/TamingManager.java | 21 +- .../nossr50/util/TransientMetadataTools.java | 5 + .../nossr50/util/skills/CombatUtils.java | 6 +- .../util/skills/ParticleEffectUtils.java | 43 +- src/main/resources/advanced.yml | 6 - 14 files changed, 507 insertions(+), 277 deletions(-) create mode 100644 src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java create mode 100644 src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java diff --git a/Changelog.txt b/Changelog.txt index f6dfd2839..526ae90e8 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,4 +1,19 @@ Version 2.1.186 + Rupture has been reworked to solve a few outstanding issues (see notes) + Removed 'Skills.Swords.Rupture.MaxTicks' from advanced.yml + Removed 'Skills.Swords.Rupture.BaseTicks' from advanced.yml + Gore no longer applies Rupture + Gore no longer sends a message to the Wolf owner when it triggers + Gore no longer sends a message to players that are hit by it + Rupture no longer sends a message telling you that your target is bleeding + + NOTES: + The old Rupture would constantly interfere with your ability to do a Sweep Attack/Swipe with swords, the new one solves this problem + Targets will bleed and take "pure" damage while bleeding, this never kills the target. It will reduce them to 0.01 HP. + After 5 seconds since your last attack on the target have transpired Rupture "explodes" dealing a large amount of damage, this damage is not pure and is affected by armor etc. + Rupture no longer tells you that you that you applied it to the target, it should be obvious from the sounds/particle effects + The new Rupture no longer constantly interferes with the vanilla Swipe (the AOE attack built into Minecraft) + The new Rupture has not had a fine tuned balance pass, I will be balancing it frequently after this patch, it may be too weak or too strong in its current form Version 2.1.185 diff --git a/src/main/java/com/gmail/nossr50/api/AbilityAPI.java b/src/main/java/com/gmail/nossr50/api/AbilityAPI.java index 5922ecbe6..ad68b9d7e 100644 --- a/src/main/java/com/gmail/nossr50/api/AbilityAPI.java +++ b/src/main/java/com/gmail/nossr50/api/AbilityAPI.java @@ -2,7 +2,7 @@ package com.gmail.nossr50.api; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.SuperAbilityType; -import com.gmail.nossr50.runnables.skills.BleedTimerTask; +import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -83,6 +83,12 @@ public final class AbilityAPI { } public static boolean isBleeding(LivingEntity entity) { - return BleedTimerTask.isBleeding(entity); + if(entity.isValid()) { + if(entity.hasMetadata(mcMMO.RUPTURE_META_KEY)) { + return true; + } + } + + return false; } } diff --git a/src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java b/src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java new file mode 100644 index 000000000..b484ce8cd --- /dev/null +++ b/src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java @@ -0,0 +1,25 @@ +package com.gmail.nossr50.datatypes.meta; + +import com.gmail.nossr50.runnables.skills.RuptureTask; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +public class RuptureTaskMeta extends FixedMetadataValue { + + private final @NotNull RuptureTask ruptureTask; + /** + * Initializes a FixedMetadataValue with an Object + * + * @param owningPlugin the {@link Plugin} that created this metadata value + * @param ruptureTask the value assigned to this metadata value + */ + public RuptureTaskMeta(@NotNull Plugin owningPlugin, @NotNull RuptureTask ruptureTask) { + super(owningPlugin, ruptureTask); + this.ruptureTask = ruptureTask; + } + + public @NotNull RuptureTask getRuptureTimerTask() { + return ruptureTask; + } +} diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java index cbcbbdba5..94face6c1 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java @@ -10,6 +10,7 @@ import com.gmail.nossr50.datatypes.chat.ChatChannel; import com.gmail.nossr50.datatypes.experience.XPGainReason; import com.gmail.nossr50.datatypes.experience.XPGainSource; import com.gmail.nossr50.datatypes.interactions.NotificationType; +import com.gmail.nossr50.datatypes.meta.RuptureTaskMeta; import com.gmail.nossr50.datatypes.mods.CustomTool; import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.party.PartyTeleportRecord; @@ -23,7 +24,6 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.party.ShareHandler; import com.gmail.nossr50.runnables.skills.AbilityDisableTask; -import com.gmail.nossr50.runnables.skills.BleedTimerTask; import com.gmail.nossr50.runnables.skills.ToolLowerTask; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager; @@ -1080,7 +1080,15 @@ public class McMMOPlayer implements Identified { */ public void logout(boolean syncSave) { Player thisPlayer = getPlayer(); - BleedTimerTask.bleedOut(getPlayer()); + if(getPlayer().hasMetadata(mcMMO.RUPTURE_META_KEY)) { + RuptureTaskMeta ruptureTaskMeta = (RuptureTaskMeta) getPlayer().getMetadata(mcMMO.RUPTURE_META_KEY); + + //Punish a logout + ruptureTaskMeta.getRuptureTimerTask().explode(); + ruptureTaskMeta.getRuptureTimerTask().explode(); + ruptureTaskMeta.getRuptureTimerTask().explode(); + } + cleanup(); if (syncSave) { diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index f24170690..9e442355b 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -27,7 +27,6 @@ import com.gmail.nossr50.runnables.party.PartyAutoKickTask; import com.gmail.nossr50.runnables.player.ClearRegisteredXPGainTask; import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask; import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask; -import com.gmail.nossr50.runnables.skills.BleedTimerTask; import com.gmail.nossr50.skills.alchemy.Alchemy; import com.gmail.nossr50.skills.child.ChildConfig; import com.gmail.nossr50.skills.repair.repairables.Repairable; @@ -126,6 +125,7 @@ public class mcMMO extends JavaPlugin { /* Metadata Values */ public static final String REPLANT_META_KEY = "mcMMO: Recently Replanted"; + public static final String RUPTURE_META_KEY = "mcMMO: RuptureTask"; public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker"; public static final String DODGE_TRACKER = "mcMMO: Dodge Tracker"; public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage"; @@ -641,9 +641,6 @@ public class mcMMO extends JavaPlugin { // Cleanup the backups folder new CleanBackupsTask().runTaskAsynchronously(mcMMO.p); - // Bleed timer (Runs every 0.5 seconds) - new BleedTimerTask().runTaskTimer(this, Misc.TICK_CONVERSION_FACTOR, (Misc.TICK_CONVERSION_FACTOR / 2)); - // Old & Powerless User remover long purgeIntervalTicks = Config.getInstance().getPurgeInterval() * 60L * 60L * Misc.TICK_CONVERSION_FACTOR; diff --git a/src/main/java/com/gmail/nossr50/runnables/skills/BleedTimerTask.java b/src/main/java/com/gmail/nossr50/runnables/skills/BleedTimerTask.java index aee720282..49298c2bb 100644 --- a/src/main/java/com/gmail/nossr50/runnables/skills/BleedTimerTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/skills/BleedTimerTask.java @@ -1,214 +1,214 @@ -package com.gmail.nossr50.runnables.skills; - -import com.gmail.nossr50.config.AdvancedConfig; -import com.gmail.nossr50.datatypes.interactions.NotificationType; -import com.gmail.nossr50.events.fake.FakeEntityDamageByEntityEvent; -import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.MobHealthbarUtils; -import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.skills.CombatUtils; -import com.gmail.nossr50.util.skills.ParticleEffectUtils; -import com.gmail.nossr50.util.sounds.SoundManager; -import com.gmail.nossr50.util.sounds.SoundType; -import org.bukkit.Bukkit; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; - -public class BleedTimerTask extends BukkitRunnable { - private static final @NotNull Map bleedList = new HashMap<>(); - private static boolean isIterating = false; - - @Override - public void run() { - isIterating = true; - Iterator> bleedIterator = bleedList.entrySet().iterator(); - - while (bleedIterator.hasNext()) { - Entry containerEntry = bleedIterator.next(); - LivingEntity target = containerEntry.getKey(); - int toolTier = containerEntry.getValue().toolTier; - -// String debugMessage = ""; -// debugMessage += ChatColor.GOLD + "Target ["+target.getName()+"]: " + ChatColor.RESET; - -// debugMessage+="RemainingTicks=["+containerEntry.getValue().bleedTicks+"], "; - - if (containerEntry.getValue().bleedTicks <= 0 || !target.isValid()) { - if(target instanceof Player) - { - NotificationManager.sendPlayerInformation((Player) target, NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Bleeding.Stopped"); - } - - bleedIterator.remove(); - continue; - } - - int armorCount = 0; - - double damage; - - if (target instanceof Player) { - damage = AdvancedConfig.getInstance().getRuptureDamagePlayer(); - - //Above Bleed Rank 3 deals 50% more damage - if (containerEntry.getValue().toolTier >= 4 && containerEntry.getValue().bleedRank >= 3) - damage = damage * 1.5; - - Player player = (Player) target; - - if (!player.isOnline()) { - continue; - } - - //Count Armor - for (ItemStack armorPiece : ((Player) target).getInventory().getArmorContents()) { - //We only want to count slots that contain armor. - if (armorPiece != null) { - armorCount++; - } - } - - } else { - damage = AdvancedConfig.getInstance().getRuptureDamageMobs(); - -// debugMessage+="BaseDMG=["+damage+"], "; - - //Above Bleed Rank 3 deals 50% more damage - if (containerEntry.getValue().bleedRank >= 3) - { - damage = damage * 1.5; - } - -// debugMessage+="Rank4Bonus=["+String.valueOf(containerEntry.getValue().bleedRank >= 3)+"], "; - - - MobHealthbarUtils.handleMobHealthbars(target, damage, mcMMO.p); //Update health bars - } - -// debugMessage+="FullArmor=["+String.valueOf(armorCount > 3)+"], "; - - if(armorCount > 3) - { - damage = damage * .75; - } - -// debugMessage+="AfterRankAndArmorChecks["+damage+"], "; - - //Weapons below Diamond get damage cut in half - if(toolTier < 4) - damage = damage / 2; - -// debugMessage+="AfterDiamondCheck=["+String.valueOf(damage)+"], "; - - //Wood weapons get damage cut in half again - if(toolTier < 2) - damage = damage / 2; - -// debugMessage+="AfterWoodenCheck=["+String.valueOf(damage)+"], "; - - double victimHealth = target.getHealth(); - -// debugMessage+="TargetHealthBeforeDMG=["+String.valueOf(target.getHealth())+"], "; - - //Fire a fake event - FakeEntityDamageByEntityEvent fakeEntityDamageByEntityEvent = (FakeEntityDamageByEntityEvent) CombatUtils.sendEntityDamageEvent(containerEntry.getValue().damageSource, target, EntityDamageEvent.DamageCause.CUSTOM, damage); - Bukkit.getPluginManager().callEvent(fakeEntityDamageByEntityEvent); - - CombatUtils.dealNoInvulnerabilityTickDamageRupture(target, damage, containerEntry.getValue().damageSource, toolTier); - - double victimHealthAftermath = target.getHealth(); - -// debugMessage+="TargetHealthAfterDMG=["+String.valueOf(target.getHealth())+"], "; - - if(victimHealthAftermath <= 0 || victimHealth != victimHealthAftermath) - { - //Play Bleed Sound - SoundManager.worldSendSound(target.getWorld(), target.getLocation(), SoundType.BLEED); - - ParticleEffectUtils.playBleedEffect(target); - } - - //Lower Bleed Ticks - BleedContainer loweredBleedContainer = copyContainer(containerEntry.getValue()); - loweredBleedContainer.bleedTicks -= 1; - -// debugMessage+="RemainingTicks=["+loweredBleedContainer.bleedTicks+"]"; - containerEntry.setValue(loweredBleedContainer); - -// Bukkit.broadcastMessage(debugMessage); - } - isIterating = false; - } - - public static @NotNull BleedContainer copyContainer(@NotNull BleedContainer container) - { - LivingEntity target = container.target; - LivingEntity source = container.damageSource; - int bleedTicks = container.bleedTicks; - int bleedRank = container.bleedRank; - int toolTier = container.toolTier; - - return new BleedContainer(target, bleedTicks, bleedRank, toolTier, source); - } - - /** - * Instantly Bleed out a LivingEntity - * - * @param entity LivingEntity to bleed out - */ - public static void bleedOut(@NotNull LivingEntity entity) { - /* - * Don't remove anything from the list outside of run() - */ - - if (bleedList.containsKey(entity)) { - CombatUtils.dealNoInvulnerabilityTickDamage(entity, bleedList.get(entity).bleedTicks * 2, bleedList.get(entity).damageSource); - } - } - - /** - * Add a LivingEntity to the bleedList if it is not in it. - * - * @param entity LivingEntity to add - * @param attacker source of the bleed/rupture - * @param ticks Number of bleeding ticks - */ - public static void add(@NotNull LivingEntity entity, @NotNull LivingEntity attacker, int ticks, int bleedRank, int toolTier) { - if (!Bukkit.isPrimaryThread()) { - throw new IllegalStateException("Cannot add bleed task async!"); - } - - if(isIterating) { - //Used to throw an error here, but in reality all we are really doing is preventing concurrency issues from other plugins being naughty and its not really needed - //I'm not really a fan of silent errors, but I'm sick of seeing people using crazy enchantments come in and report this "bug" - return; - } - -// if (isIterating) throw new IllegalStateException("Cannot add task while iterating timers!"); - - if(toolTier < 4) - ticks = Math.max(1, (ticks / 3)); - - ticks+=1; - - BleedContainer newBleedContainer = new BleedContainer(entity, ticks, bleedRank, toolTier, attacker); - bleedList.put(entity, newBleedContainer); - } - - public static boolean isBleedOperationAllowed() { - return !isIterating && Bukkit.isPrimaryThread(); - } - - public static boolean isBleeding(@NotNull LivingEntity entity) { - return bleedList.containsKey(entity); - } -} +//package com.gmail.nossr50.runnables.skills; +// +//import com.gmail.nossr50.config.AdvancedConfig; +//import com.gmail.nossr50.datatypes.interactions.NotificationType; +//import com.gmail.nossr50.events.fake.FakeEntityDamageByEntityEvent; +//import com.gmail.nossr50.mcMMO; +//import com.gmail.nossr50.util.MobHealthbarUtils; +//import com.gmail.nossr50.util.player.NotificationManager; +//import com.gmail.nossr50.util.skills.CombatUtils; +//import com.gmail.nossr50.util.skills.ParticleEffectUtils; +//import com.gmail.nossr50.util.sounds.SoundManager; +//import com.gmail.nossr50.util.sounds.SoundType; +//import org.bukkit.Bukkit; +//import org.bukkit.entity.LivingEntity; +//import org.bukkit.entity.Player; +//import org.bukkit.event.entity.EntityDamageEvent; +//import org.bukkit.inventory.ItemStack; +//import org.bukkit.scheduler.BukkitRunnable; +//import org.jetbrains.annotations.NotNull; +// +//import java.util.HashMap; +//import java.util.Iterator; +//import java.util.Map; +//import java.util.Map.Entry; +// +//public class BleedTimerTask extends BukkitRunnable { +// private static final @NotNull Map bleedList = new HashMap<>(); +// private static boolean isIterating = false; +// +// @Override +// public void run() { +// isIterating = true; +// Iterator> bleedIterator = bleedList.entrySet().iterator(); +// +// while (bleedIterator.hasNext()) { +// Entry containerEntry = bleedIterator.next(); +// LivingEntity target = containerEntry.getKey(); +// int toolTier = containerEntry.getValue().toolTier; +// +//// String debugMessage = ""; +//// debugMessage += ChatColor.GOLD + "Target ["+target.getName()+"]: " + ChatColor.RESET; +// +//// debugMessage+="RemainingTicks=["+containerEntry.getValue().bleedTicks+"], "; +// +// if (containerEntry.getValue().bleedTicks <= 0 || !target.isValid()) { +// if(target instanceof Player) +// { +// NotificationManager.sendPlayerInformation((Player) target, NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Bleeding.Stopped"); +// } +// +// bleedIterator.remove(); +// continue; +// } +// +// int armorCount = 0; +// +// double damage; +// +// if (target instanceof Player) { +// damage = AdvancedConfig.getInstance().getRuptureDamagePlayer(); +// +// //Above Bleed Rank 3 deals 50% more damage +// if (containerEntry.getValue().toolTier >= 4 && containerEntry.getValue().bleedRank >= 3) +// damage = damage * 1.5; +// +// Player player = (Player) target; +// +// if (!player.isOnline()) { +// continue; +// } +// +// //Count Armor +// for (ItemStack armorPiece : ((Player) target).getInventory().getArmorContents()) { +// //We only want to count slots that contain armor. +// if (armorPiece != null) { +// armorCount++; +// } +// } +// +// } else { +// damage = AdvancedConfig.getInstance().getRuptureDamageMobs(); +// +//// debugMessage+="BaseDMG=["+damage+"], "; +// +// //Above Bleed Rank 3 deals 50% more damage +// if (containerEntry.getValue().bleedRank >= 3) +// { +// damage = damage * 1.5; +// } +// +//// debugMessage+="Rank4Bonus=["+String.valueOf(containerEntry.getValue().bleedRank >= 3)+"], "; +// +// +// MobHealthbarUtils.handleMobHealthbars(target, damage, mcMMO.p); //Update health bars +// } +// +//// debugMessage+="FullArmor=["+String.valueOf(armorCount > 3)+"], "; +// +// if(armorCount > 3) +// { +// damage = damage * .75; +// } +// +//// debugMessage+="AfterRankAndArmorChecks["+damage+"], "; +// +// //Weapons below Diamond get damage cut in half +// if(toolTier < 4) +// damage = damage / 2; +// +//// debugMessage+="AfterDiamondCheck=["+String.valueOf(damage)+"], "; +// +// //Wood weapons get damage cut in half again +// if(toolTier < 2) +// damage = damage / 2; +// +//// debugMessage+="AfterWoodenCheck=["+String.valueOf(damage)+"], "; +// +// double victimHealth = target.getHealth(); +// +//// debugMessage+="TargetHealthBeforeDMG=["+String.valueOf(target.getHealth())+"], "; +// +// //Fire a fake event +// FakeEntityDamageByEntityEvent fakeEntityDamageByEntityEvent = (FakeEntityDamageByEntityEvent) CombatUtils.sendEntityDamageEvent(containerEntry.getValue().damageSource, target, EntityDamageEvent.DamageCause.CUSTOM, damage); +// Bukkit.getPluginManager().callEvent(fakeEntityDamageByEntityEvent); +// +// CombatUtils.dealNoInvulnerabilityTickDamageRupture(target, damage, containerEntry.getValue().damageSource, toolTier); +// +// double victimHealthAftermath = target.getHealth(); +// +//// debugMessage+="TargetHealthAfterDMG=["+String.valueOf(target.getHealth())+"], "; +// +// if(victimHealthAftermath <= 0 || victimHealth != victimHealthAftermath) +// { +// //Play Bleed Sound +// SoundManager.worldSendSound(target.getWorld(), target.getLocation(), SoundType.BLEED); +// +// ParticleEffectUtils.playBleedEffect(target); +// } +// +// //Lower Bleed Ticks +// BleedContainer loweredBleedContainer = copyContainer(containerEntry.getValue()); +// loweredBleedContainer.bleedTicks -= 1; +// +//// debugMessage+="RemainingTicks=["+loweredBleedContainer.bleedTicks+"]"; +// containerEntry.setValue(loweredBleedContainer); +// +//// Bukkit.broadcastMessage(debugMessage); +// } +// isIterating = false; +// } +// +// public static @NotNull BleedContainer copyContainer(@NotNull BleedContainer container) +// { +// LivingEntity target = container.target; +// LivingEntity source = container.damageSource; +// int bleedTicks = container.bleedTicks; +// int bleedRank = container.bleedRank; +// int toolTier = container.toolTier; +// +// return new BleedContainer(target, bleedTicks, bleedRank, toolTier, source); +// } +// +// /** +// * Instantly Bleed out a LivingEntity +// * +// * @param entity LivingEntity to bleed out +// */ +// public static void bleedOut(@NotNull LivingEntity entity) { +// /* +// * Don't remove anything from the list outside of run() +// */ +// +// if (bleedList.containsKey(entity)) { +// CombatUtils.dealNoInvulnerabilityTickDamage(entity, bleedList.get(entity).bleedTicks * 2, bleedList.get(entity).damageSource); +// } +// } +// +// /** +// * Add a LivingEntity to the bleedList if it is not in it. +// * +// * @param entity LivingEntity to add +// * @param attacker source of the bleed/rupture +// * @param ticks Number of bleeding ticks +// */ +// public static void add(@NotNull LivingEntity entity, @NotNull LivingEntity attacker, int ticks, int bleedRank, int toolTier) { +// if (!Bukkit.isPrimaryThread()) { +// throw new IllegalStateException("Cannot add bleed task async!"); +// } +// +// if(isIterating) { +// //Used to throw an error here, but in reality all we are really doing is preventing concurrency issues from other plugins being naughty and its not really needed +// //I'm not really a fan of silent errors, but I'm sick of seeing people using crazy enchantments come in and report this "bug" +// return; +// } +// +//// if (isIterating) throw new IllegalStateException("Cannot add task while iterating timers!"); +// +// if(toolTier < 4) +// ticks = Math.max(1, (ticks / 3)); +// +// ticks+=1; +// +// BleedContainer newBleedContainer = new BleedContainer(entity, ticks, bleedRank, toolTier, attacker); +// bleedList.put(entity, newBleedContainer); +// } +// +// public static boolean isBleedOperationAllowed() { +// return !isIterating && Bukkit.isPrimaryThread(); +// } +// +// public static boolean isBleeding(@NotNull LivingEntity entity) { +// return bleedList.containsKey(entity); +// } +//} diff --git a/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java b/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java new file mode 100644 index 000000000..681578577 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java @@ -0,0 +1,142 @@ +package com.gmail.nossr50.runnables.skills; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.MobHealthbarUtils; +import com.gmail.nossr50.util.skills.ParticleEffectUtils; +import com.google.common.base.Objects; +import org.bukkit.entity.LivingEntity; +import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; + +public class RuptureTask extends BukkitRunnable { + + public static final int FIVE_SECOND_DURATION = 20 * 5; + public static final int DAMAGE_TICK_INTERVAL = 10; + + private final @NotNull McMMOPlayer ruptureSource; + private final @NotNull LivingEntity targetEntity; + private final int ruptureRank; + private final int expireTick; + + private int ruptureTick; + private int damageTickTracker; + private final double damageValue; //TODO: Make configurable + + public RuptureTask(@NotNull McMMOPlayer ruptureSource, @NotNull LivingEntity targetEntity, int ruptureRank, double damageValue) { + this.ruptureSource = ruptureSource; + this.targetEntity = targetEntity; + this.ruptureRank = ruptureRank; + this.expireTick = FIVE_SECOND_DURATION; + this.damageValue = damageValue; + + this.ruptureTick = 0; + this.damageTickTracker = 0; + } + + @Override + public void run() { + //Check validity + if(targetEntity.isValid()) { + ruptureTick += 1; //Advance rupture tick by 1. + damageTickTracker += 1; //Increment damage tick tracker + + //Rupture hasn't ended yet + if(ruptureTick < expireTick) { + + //Is it time to damage? + if(damageTickTracker >= DAMAGE_TICK_INTERVAL) { + damageTickTracker = 0; //Reset + ParticleEffectUtils.playBleedEffect(targetEntity); //Animate + + if(targetEntity.getHealth() > 0.01) { + double healthBeforeRuptureIsApplied = targetEntity.getHealth(); + double damagedHealth = healthBeforeRuptureIsApplied - getTickDamage(); + + if(damagedHealth <= 0) { + mcMMO.p.getLogger().severe("DEBUG: Miscalculating Rupture tick damage"); + } else { + targetEntity.setHealth(damagedHealth); //Hurt entity without the unwanted side effects of damage() + + //TODO: Do we need to set last damage? Double check + double finalDamage = healthBeforeRuptureIsApplied - targetEntity.getHealth(); + + //Update health bars + MobHealthbarUtils.handleMobHealthbars(targetEntity, finalDamage, mcMMO.p); + + if(finalDamage <= 0) { + mcMMO.p.getLogger().severe("DEBUG: Miscalculating final damage for Rupture"); + } else { + //Actually should this even be done? + targetEntity.setLastDamage(finalDamage); + } + } + } + } + } else { + explode(); + } + } else { + targetEntity.removeMetadata(mcMMO.RUPTURE_META_KEY, mcMMO.p); + this.cancel(); //Task no longer needed + } + } + + public void explode() { + ParticleEffectUtils.playBleedEffect(targetEntity); //Animate + + if(ruptureSource.getPlayer() != null && ruptureSource.getPlayer().isValid()) { + targetEntity.damage(getExplosionDamage(), ruptureSource.getPlayer()); + } else { + targetEntity.damage(getExplosionDamage(), null); + } + + targetEntity.removeMetadata(mcMMO.RUPTURE_META_KEY, mcMMO.p); + this.cancel(); //Task no longer needed + } + + private double getTickDamage() { + double tickDamage = damageValue; + + if(targetEntity.getHealth() <= tickDamage) { + tickDamage = targetEntity.getHealth() - 0.01; + + if(tickDamage <= 0) { + tickDamage = 0; + } + } + + return tickDamage; + } + + private int getExplosionDamage() { + //TODO: Balance pass + return ruptureRank * 10; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RuptureTask that = (RuptureTask) o; + return ruptureRank == that.ruptureRank && expireTick == that.expireTick && ruptureTick == that.ruptureTick && damageTickTracker == that.damageTickTracker && Double.compare(that.damageValue, damageValue) == 0 && Objects.equal(ruptureSource, that.ruptureSource) && Objects.equal(targetEntity, that.targetEntity); + } + + @Override + public int hashCode() { + return Objects.hashCode(ruptureSource, targetEntity, ruptureRank, expireTick, ruptureTick, damageTickTracker, damageValue); + } + + @Override + public String toString() { + return "RuptureTimerTask{" + + "ruptureSource=" + ruptureSource + + ", targetEntity=" + targetEntity + + ", ruptureRank=" + ruptureRank + + ", expireTick=" + expireTick + + ", ruptureTick=" + ruptureTick + + ", damageTickTracker=" + damageTickTracker + + ", damageValue=" + damageValue + + '}'; + } +} diff --git a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java index c4d4b6a32..85f87ed51 100644 --- a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java +++ b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java @@ -29,7 +29,6 @@ import com.gmail.nossr50.util.skills.SkillUtils; import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; import com.gmail.nossr50.util.text.StringUtils; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; diff --git a/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java b/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java index 1c3fa3766..87efaffda 100644 --- a/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java +++ b/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java @@ -1,12 +1,15 @@ package com.gmail.nossr50.skills.swords; +import com.gmail.nossr50.config.AdvancedConfig; import com.gmail.nossr50.datatypes.interactions.NotificationType; +import com.gmail.nossr50.datatypes.meta.RuptureTaskMeta; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.SuperAbilityType; import com.gmail.nossr50.datatypes.skills.ToolType; -import com.gmail.nossr50.runnables.skills.BleedTimerTask; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.runnables.skills.RuptureTask; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.ItemUtils; import com.gmail.nossr50.util.Permissions; @@ -60,32 +63,50 @@ public class SwordsManager extends SkillManager { * * @param target The defending entity */ - public void ruptureCheck(@NotNull LivingEntity target) throws IllegalStateException { - if(BleedTimerTask.isBleedOperationAllowed()) { - if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SWORDS_RUPTURE, getPlayer())) { + public void processRupture(@NotNull LivingEntity target) throws IllegalStateException { + if(target.hasMetadata(mcMMO.REPLANT_META_KEY)) { + if(mmoPlayer.isDebugMode()) { + mmoPlayer.getPlayer().sendMessage("Rupture task ongoing for target " + target.toString()); + RuptureTaskMeta ruptureTaskMeta = (RuptureTaskMeta) target.getMetadata(mcMMO.RUPTURE_META_KEY); + RuptureTask ruptureTask = (RuptureTask) target.getMetadata(mcMMO.RUPTURE_META_KEY); + mmoPlayer.getPlayer().sendMessage(ruptureTask.toString()); + } - if (target instanceof Player) { - Player defender = (Player) target; + return; //Don't apply bleed + } - //Don't start or add to a bleed if they are blocking - if(defender.isBlocking()) - return; + if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SWORDS_RUPTURE, getPlayer())) { - if (NotificationManager.doesPlayerUseNotifications(defender)) { - if(!BleedTimerTask.isBleeding(defender)) - NotificationManager.sendPlayerInformation(defender, NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Bleeding.Started"); - } - } + if (target instanceof Player) { + Player defender = (Player) target; - BleedTimerTask.add(target, getPlayer(), getRuptureBleedTicks(), RankUtils.getRank(getPlayer(), SubSkillType.SWORDS_RUPTURE), getToolTier(getPlayer().getInventory().getItemInMainHand())); + //Don't start or add to a bleed if they are blocking + if(defender.isBlocking()) + return; - if (mmoPlayer.useChatNotifications()) { - NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Bleeding"); + if (NotificationManager.doesPlayerUseNotifications(defender)) { + NotificationManager.sendPlayerInformation(defender, NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Bleeding.Started"); } } + + double tickDamageValue = target instanceof Player ? AdvancedConfig.getInstance().getRuptureDamagePlayer() : AdvancedConfig.getInstance().getRuptureDamageMobs(); + + RuptureTask ruptureTask = new RuptureTask(mmoPlayer, target, RankUtils.getRank(mmoPlayer.getPlayer(), SubSkillType.SWORDS_RUPTURE), tickDamageValue); + RuptureTaskMeta ruptureTaskMeta = new RuptureTaskMeta(mcMMO.p, ruptureTask); + + ruptureTask.runTaskTimer(mcMMO.p, 0, 1); + target.setMetadata(mcMMO.RUPTURE_META_KEY, ruptureTaskMeta); + +// if (mmoPlayer.useChatNotifications()) { +// NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Bleeding"); +// } } } + private int getRuptureRank() { + return RankUtils.getRank(getPlayer(), SubSkillType.SWORDS_RUPTURE); + } + public double getStabDamage() { int rank = RankUtils.getRank(getPlayer(), SubSkillType.SWORDS_STAB); @@ -112,14 +133,8 @@ public class SwordsManager extends SkillManager { return 1; } - public int getRuptureBleedTicks() - { - int bleedTicks = 2 * RankUtils.getRank(getPlayer(), SubSkillType.SWORDS_RUPTURE); - - if(bleedTicks > Swords.bleedMaxTicks) - bleedTicks = Swords.bleedMaxTicks; - - return bleedTicks; + public int getRuptureBleedTicks() { + return RuptureTask.FIVE_SECOND_DURATION / RuptureTask.DAMAGE_TICK_INTERVAL; } /** diff --git a/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java b/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java index 859892c20..23a12081b 100644 --- a/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java @@ -12,7 +12,6 @@ import com.gmail.nossr50.datatypes.skills.subskills.taming.CallOfTheWildType; import com.gmail.nossr50.datatypes.skills.subskills.taming.TamingSummon; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.runnables.skills.BleedTimerTask; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; @@ -168,21 +167,13 @@ public class TamingManager extends SkillManager { * @param damage The initial damage */ public double gore(@NotNull LivingEntity target, double damage) { - if(BleedTimerTask.isBleedOperationAllowed()) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.TAMING_GORE, getPlayer())) { - return 0; - } +// if (target instanceof Player) { +// NotificationManager.sendPlayerInformation((Player)target, NotificationType.SUBSKILL_MESSAGE, "Combat.StruckByGore"); +// } +// +// NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Combat.Gore"); - BleedTimerTask.add(target, getPlayer(), Taming.goreBleedTicks, 1, 2); - - if (target instanceof Player) { - NotificationManager.sendPlayerInformation((Player)target, NotificationType.SUBSKILL_MESSAGE, "Combat.StruckByGore"); - } - - NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Combat.Gore"); - - damage = (damage * Taming.goreModifier) - damage; - } + damage = (damage * Taming.goreModifier) - damage; return damage; } diff --git a/src/main/java/com/gmail/nossr50/util/TransientMetadataTools.java b/src/main/java/com/gmail/nossr50/util/TransientMetadataTools.java index b84e8c60e..6a55c5a43 100644 --- a/src/main/java/com/gmail/nossr50/util/TransientMetadataTools.java +++ b/src/main/java/com/gmail/nossr50/util/TransientMetadataTools.java @@ -33,6 +33,11 @@ public class TransientMetadataTools { livingEntity.removeMetadata(mcMMO.travelingBlock, pluginRef); } + if(livingEntity.hasMetadata(mcMMO.REPLANT_META_KEY)) { + livingEntity.removeMetadata(mcMMO.REPLANT_META_KEY, pluginRef); + } + + //Cleanup mob metadata mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity); } 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 f2ab26669..f28f6762e 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java +++ b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java @@ -12,7 +12,6 @@ import com.gmail.nossr50.events.fake.FakeEntityDamageByEntityEvent; import com.gmail.nossr50.events.fake.FakeEntityDamageEvent; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.party.PartyManager; -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; @@ -41,6 +40,7 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.projectiles.ProjectileSource; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import com.gmail.nossr50.runnables.skills.AwardCombatXpTask; import java.util.EnumMap; import java.util.HashMap; @@ -96,7 +96,7 @@ public final class CombatUtils { if(target.getHealth() - event.getFinalDamage() >= 1) { if (swordsManager.canUseRupture()) { - swordsManager.ruptureCheck(target); + swordsManager.processRupture(target); } } @@ -714,7 +714,7 @@ public final class CombatUtils { NotificationManager.sendPlayerInformation((Player)entity, NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.SS.Struck"); } - UserManager.getPlayer(attacker).getSwordsManager().ruptureCheck(target); + UserManager.getPlayer(attacker).getSwordsManager().processRupture(target); break; case AXES: diff --git a/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java b/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java index 40fad662b..8d0f75937 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java +++ b/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java @@ -1,17 +1,19 @@ package com.gmail.nossr50.util.skills; import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; -import org.bukkit.Effect; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; +import org.apache.commons.lang.math.RandomUtils; +import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import java.util.ArrayList; +import java.util.Arrays; + public final class ParticleEffectUtils { private ParticleEffectUtils() {} @@ -27,9 +29,40 @@ public final class ParticleEffectUtils { return; } - livingEntity.getWorld().playEffect(livingEntity.getEyeLocation(), Effect.STEP_SOUND, Material.REDSTONE_WIRE); + Location origin = livingEntity.getEyeLocation().clone(); + World world = origin.getWorld(); + + double x = origin.getX(); + double y = origin.getY(); + double z = origin.getZ(); + + double offSetVal = 0.3D; + + Location locA = new Location(world, x - offSetVal, y, z); + Location locB = new Location(world, x + offSetVal, y, z); + Location locC = new Location(world, x, y + offSetVal, z); + Location locD = new Location(world, x, y - offSetVal, z); + Location locE = new Location(world, x, y, z + offSetVal); + Location locF = new Location(world, x, y, z - offSetVal); + + Location locG = new Location(world, x + offSetVal, y, z + offSetVal); + Location locH = new Location(world, x - offSetVal, y, z - offSetVal); + Location locI = new Location(world, x - offSetVal, y - offSetVal, z - offSetVal); + Location locJ = new Location(world, x + offSetVal, y - offSetVal, z + offSetVal); + Location locK = new Location(world, x - offSetVal, y + offSetVal, z - offSetVal); + Location locL = new Location(world, x - offSetVal, y + offSetVal, z - offSetVal); + + Location[] particleLocations = new Location[]{ locA, locB, locC, locD, locE, locF, locG, locH, locI, locJ, locK, locL}; + + for(Location location : particleLocations) { + if(RandomUtils.nextInt(100) > 30) { + //TODO: Change + livingEntity.getWorld().playEffect(location, Effect.STEP_SOUND, Material.REDSTONE_WIRE); + } + } } + public static void playDodgeEffect(Player player) { if (!Config.getInstance().getDodgeEffectEnabled()) { return; diff --git a/src/main/resources/advanced.yml b/src/main/resources/advanced.yml index e9979909c..90a783a72 100644 --- a/src/main/resources/advanced.yml +++ b/src/main/resources/advanced.yml @@ -465,13 +465,7 @@ Skills: # DamageMobs: Bleeding damage dealt to mobs DamagePlayer: 2.0 DamageMobs: 3.0 - - # These settings determine how long the Bleeding effect lasts - MaxTicks: 8 - BaseTicks: 2 - CounterAttack: - # ChanceMax: Maximum chance of triggering a counter attack # MaxBonusLevel: On this level, the chance to Counter will be ChanceMax: 30.0