diff --git a/src/main/java/matsyir/pvpperformancetracker/controllers/Fighter.java b/src/main/java/matsyir/pvpperformancetracker/controllers/Fighter.java index 08ffc0b..e934d01 100644 --- a/src/main/java/matsyir/pvpperformancetracker/controllers/Fighter.java +++ b/src/main/java/matsyir/pvpperformancetracker/controllers/Fighter.java @@ -42,10 +42,13 @@ import matsyir.pvpperformancetracker.models.CombatLevels; import matsyir.pvpperformancetracker.models.EquipmentData; import matsyir.pvpperformancetracker.models.FightLogEntry; +import net.runelite.api.ActorSpotAnim; +import net.runelite.api.GraphicID; +import net.runelite.api.IterableHashTable; import net.runelite.api.Player; import net.runelite.api.PlayerComposition; +import net.runelite.api.gameval.SpotanimID; import net.runelite.api.kit.KitType; -import net.runelite.api.GraphicID; @Slf4j @Getter @@ -211,18 +214,19 @@ void addAttack(Player opponent, AnimationData animationData, int offensivePray, // Granite Maul specific handling boolean isGmaulSpec = animationData == AnimationData.MELEE_GRANITE_MAUL_SPEC; + boolean elyProc = hasTargetSpotAnim(opponent, SpotanimID.ELYSIAN_SHIELD_DEFEND_SPOTANIM); // --- Detect dark bow & dragon crossbow specials via GFX --- if (weapon == EquipmentData.DARK_BOW && animationData == AnimationData.RANGED_SHORTBOW) { - boolean spec = opponent.getGraphic() == GFX_TARGET_DBOW_SPEC; + boolean spec = hasTargetSpotAnim(opponent, GFX_TARGET_DBOW_SPEC); animationData = spec ? AnimationData.RANGED_DARK_BOW_SPEC : AnimationData.RANGED_DARK_BOW; } else if (weapon == EquipmentData.DRAGON_CROSSBOW && (animationData == AnimationData.RANGED_CROSSBOW_PVP || animationData == AnimationData.RANGED_RUNE_CROSSBOW)) { - boolean spec = opponent.getGraphic() == GFX_TARGET_DCBOW_SPEC; + boolean spec = hasTargetSpotAnim(opponent, GFX_TARGET_DCBOW_SPEC); if (spec) { @@ -251,7 +255,21 @@ else if (weapon == EquipmentData.DRAGON_CROSSBOW && animationData = animationData.isSpecial ? AnimationData.MELEE_VLS_SPEC : AnimationData.MELEE_SCIM_SLASH; } + boolean staffMeleeReduction = false; + if (animationData.attackStyle.isMelee()) + { + staffMeleeReduction = hasStaffMeleeReduction(opponent); + } + pvpDamageCalc.updateDamageStats(player, opponent, successful, animationData); + if (elyProc) + { + pvpDamageCalc.applyElysianReduction(); + } + if (staffMeleeReduction) + { + pvpDamageCalc.applyStaffMeleeReduction(); + } expectedDamage += pvpDamageCalc.getAverageHit(); if (animationData.attackStyle == AnimationData.AttackStyle.MAGIC) @@ -266,6 +284,8 @@ else if (weapon == EquipmentData.DRAGON_CROSSBOW && } FightLogEntry fightLogEntry = new FightLogEntry(player, opponent, pvpDamageCalc, offensivePray, levels, animationData); + fightLogEntry.setElyProc(elyProc); + fightLogEntry.setStaffMeleeReductionProc(staffMeleeReduction); fightLogEntry.setGmaulSpecial(isGmaulSpec); if (PvpPerformanceTrackerPlugin.CONFIG.fightLogInChat()) { @@ -364,6 +384,40 @@ void died() dead = true; } + private static boolean hasTargetSpotAnim(Player opponent, int spotAnimId) + { + if (opponent.getGraphic() == spotAnimId) + { + return true; + } + + IterableHashTable spotAnims = opponent.getSpotAnims(); + if (spotAnims == null) + { + return false; + } + + for (ActorSpotAnim spotAnim : spotAnims) + { + if (spotAnim != null && spotAnim.getId() == spotAnimId) + { + return true; + } + } + + return false; + } + + private static boolean hasStaffMeleeReduction(Player opponent) + { + return hasTargetSpotAnim(opponent, SpotanimID.SOTD_SPECIAL_START) || + hasTargetSpotAnim(opponent, SpotanimID.SOTD_SPECIAL_EXTRA) || + hasTargetSpotAnim(opponent, SpotanimID.STAFF_OF_LIGHT_SPECIAL_START) || + hasTargetSpotAnim(opponent, SpotanimID.STAFF_OF_LIGHT_SPECIAL_EXTRA) || + hasTargetSpotAnim(opponent, SpotanimID.STAFF_OF_BALANCE_SPECIAL_START) || + hasTargetSpotAnim(opponent, SpotanimID.STAFF_OF_BALANCE_SPECIAL_EXTRA); + } + AnimationData getAnimationData() { return AnimationData.fromId(player.getAnimation()); diff --git a/src/main/java/matsyir/pvpperformancetracker/controllers/PvpDamageCalc.java b/src/main/java/matsyir/pvpperformancetracker/controllers/PvpDamageCalc.java index 11482bc..1db59cd 100644 --- a/src/main/java/matsyir/pvpperformancetracker/controllers/PvpDamageCalc.java +++ b/src/main/java/matsyir/pvpperformancetracker/controllers/PvpDamageCalc.java @@ -65,6 +65,8 @@ public class PvpDamageCalc private static final int STANCE_BONUS = 0; // assume they are not in controlled or defensive private static final double UNSUCCESSFUL_PRAY_DMG_MODIFIER = 0.6; // modifier for when you unsuccessfully hit off-pray + private static final double ELYSIAN_DAMAGE_MULTIPLIER = 0.75; + private static final double STAFF_MELEE_DAMAGE_MULTIPLIER = 0.5; // Offensive pray: assume you have valid. Piety for melee, Rigour for range, Augury for mage private static final double PIETY_ATK_PRAYER_MODIFIER = 1.2; @@ -255,6 +257,37 @@ else if (attackStyle == AttackStyle.MAGIC) maxHit = (int)(maxHit * (success ? 1 : UNSUCCESSFUL_PRAY_DMG_MODIFIER)); minHit = (int)(minHit * (success ? 1 : UNSUCCESSFUL_PRAY_DMG_MODIFIER)); + + if (atkLog.isElyProc()) + { + applyElysianReduction(); + } + if (atkLog.isStaffMeleeReductionProc() && atkLog.getAnimationData().attackStyle.isMelee()) + { + applyStaffMeleeReduction(); + } + } + + public void applyElysianReduction() + { + applyDamageMultiplier(ELYSIAN_DAMAGE_MULTIPLIER); + } + + public void applyStaffMeleeReduction() + { + applyDamageMultiplier(STAFF_MELEE_DAMAGE_MULTIPLIER); + } + + private void applyDamageMultiplier(double multiplier) + { + if (multiplier == 1) + { + return; + } + + averageHit *= multiplier; + minHit = (int) Math.floor(minHit * multiplier); + maxHit = (int) Math.floor(maxHit * multiplier); } private void getAverageHit(boolean success, EquipmentData weapon, boolean usingSpec) diff --git a/src/main/java/matsyir/pvpperformancetracker/models/FightLogEntry.java b/src/main/java/matsyir/pvpperformancetracker/models/FightLogEntry.java index 32afa47..61ab664 100644 --- a/src/main/java/matsyir/pvpperformancetracker/models/FightLogEntry.java +++ b/src/main/java/matsyir/pvpperformancetracker/models/FightLogEntry.java @@ -107,6 +107,14 @@ public class FightLogEntry implements Comparable @Expose @SerializedName("s") private boolean splash; // true if it was a magic attack and it splashed + @Expose + @SerializedName("E") + @Setter + private boolean elyProc = false; + @Expose + @SerializedName("S") + @Setter + private boolean staffMeleeReductionProc = false; @Expose @SerializedName("C") @@ -257,6 +265,8 @@ public FightLogEntry(FightLogEntry e, PvpDamageCalc pvpDamageCalc) this.minHit = pvpDamageCalc.getMinHit(); this.maxHit = pvpDamageCalc.getMaxHit(); this.splash = e.splash; + this.elyProc = e.elyProc; + this.staffMeleeReductionProc = e.staffMeleeReductionProc; this.attackerLevels = e.attackerLevels; // defender data diff --git a/src/main/java/matsyir/pvpperformancetracker/views/FightLogFrame.java b/src/main/java/matsyir/pvpperformancetracker/views/FightLogFrame.java index 4256461..32ec4b1 100644 --- a/src/main/java/matsyir/pvpperformancetracker/views/FightLogFrame.java +++ b/src/main/java/matsyir/pvpperformancetracker/views/FightLogFrame.java @@ -27,6 +27,7 @@ import java.awt.BorderLayout; import java.awt.Component; +import java.awt.FlowLayout; import java.awt.Point; import java.awt.image.BufferedImage; import java.math.RoundingMode; @@ -52,6 +53,7 @@ import static matsyir.pvpperformancetracker.PvpPerformanceTrackerPlugin.PLUGIN_ICON; import matsyir.pvpperformancetracker.utils.PvpPerformanceTrackerUtils; +import net.runelite.api.ItemID; import net.runelite.api.SpriteID; @Slf4j @@ -178,15 +180,60 @@ private FightLogFrame(FightPerformance fight, ArrayList logEntrie stats[i][9] = fightEntry.success() ? "✔" : ""; // Def Prayer (Index 10) int prayIcon = PvpPerformanceTrackerUtils.getSpriteForHeadIcon(fightEntry.getDefenderOverhead()); - if (prayIcon > 0) + boolean hasPray = prayIcon > 0; + boolean hasEly = fightEntry.isElyProc(); + boolean hasStaffReduction = fightEntry.isStaffMeleeReductionProc(); + + if (!hasPray && !hasEly && !hasStaffReduction) + { + stats[i][10] = ""; + } + else if ((hasPray ? 1 : 0) + (hasEly ? 1 : 0) + (hasStaffReduction ? 1 : 0) == 1) { - JLabel defPrayLabel = new JLabel(); - PLUGIN.addSpriteToLabelIfValid(defPrayLabel, prayIcon, this::repaint); - stats[i][10] = defPrayLabel; + JLabel label = new JLabel(); + if (hasPray) + { + PLUGIN.addSpriteToLabelIfValid(label, prayIcon, this::repaint); + label.setToolTipText("Defensive Prayer"); + } + else if (hasEly) + { + PLUGIN.addItemToLabelIfValid(label, ItemID.ELYSIAN_SPIRIT_SHIELD, false, this::repaint, "Elysian proc"); + } + else + { + PLUGIN.addItemToLabelIfValid(label, ItemID.STAFF_OF_THE_DEAD, false, this::repaint, "Staff spec damage reduction"); + } + stats[i][10] = label; } else { - stats[i][10] = ""; + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0)); + panel.setOpaque(false); + + if (hasPray) + { + JLabel defPrayLabel = new JLabel(); + PLUGIN.addSpriteToLabelIfValid(defPrayLabel, prayIcon, this::repaint); + defPrayLabel.setToolTipText("Defensive Prayer"); + panel.add(defPrayLabel); + } + + if (hasEly) + { + JLabel elyLabel = new JLabel(); + PLUGIN.addItemToLabelIfValid(elyLabel, ItemID.ELYSIAN_SPIRIT_SHIELD, false, this::repaint, "Elysian proc"); + panel.add(elyLabel); + } + + if (hasStaffReduction) + { + JLabel staffLabel = new JLabel(); + PLUGIN.addItemToLabelIfValid(staffLabel, ItemID.STAFF_OF_THE_DEAD, false, this::repaint, "Staff spec damage reduction"); + panel.add(staffLabel); + } + + stats[i][10] = panel; } // Splash (Index 11) if (fightEntry.getAnimationData().attackStyle == AnimationData.AttackStyle.MAGIC) @@ -228,6 +275,7 @@ private FightLogFrame(FightPerformance fight, ArrayList logEntrie table = new JTable(stats, header); table.setRowHeight(30); table.setDefaultEditor(Object.class, null); + table.getColumnModel().getColumn(10).setPreferredWidth(96); // room for def pray + proc icons table.getColumnModel().getColumn(1).setCellRenderer(new BufferedImageCellRenderer()); // Style table.getColumnModel().getColumn(5).setCellRenderer(new BufferedImageCellRenderer()); // Actual Dmg @@ -271,7 +319,14 @@ private static class BufferedImageCellRenderer extends DefaultTableCellRenderer public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (value instanceof BufferedImage) + if (value instanceof JPanel) + { + JPanel panel = (JPanel) value; + panel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); + panel.setOpaque(true); + return panel; + } + else if (value instanceof BufferedImage) { setText(""); setIcon(new ImageIcon((BufferedImage) value));