Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand All @@ -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())
{
Expand Down Expand Up @@ -364,6 +384,40 @@ void died()
dead = true;
}

private static boolean hasTargetSpotAnim(Player opponent, int spotAnimId)
{
if (opponent.getGraphic() == spotAnimId)
{
return true;
}

IterableHashTable<ActorSpotAnim> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ public class FightLogEntry implements Comparable<FightLogEntry>
@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")
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -178,15 +180,60 @@ private FightLogFrame(FightPerformance fight, ArrayList<FightLogEntry> 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)
Expand Down Expand Up @@ -228,6 +275,7 @@ private FightLogFrame(FightPerformance fight, ArrayList<FightLogEntry> 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
Expand Down Expand Up @@ -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));
Expand Down