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 @@ -63,6 +63,7 @@
import lombok.extern.slf4j.Slf4j;
import matsyir.pvpperformancetracker.controllers.FightPerformance;
import matsyir.pvpperformancetracker.controllers.Fighter;
import matsyir.pvpperformancetracker.models.AnimationData;
import matsyir.pvpperformancetracker.models.CombatLevels;
import matsyir.pvpperformancetracker.models.FightLogEntry;
import matsyir.pvpperformancetracker.models.HitsplatInfo;
Expand Down Expand Up @@ -204,6 +205,7 @@ public class PvpPerformanceTrackerPlugin extends Plugin
// do not cache items in the same way since we could potentially cache a very large amount of them.
private final Map<Integer, List<HitsplatInfo>> hitsplatBuffer = new HashMap<>();
private final Map<Integer, List<HitsplatInfo>> incomingHitsplatsBuffer = new ConcurrentHashMap<>(); // Stores hitsplats *received* by players per tick.
private final Map<String, Integer> lastNonGmaulSpecTickByAttacker = new ConcurrentHashMap<>();
private HiscoreEndpoint hiscoreEndpoint = HiscoreEndpoint.NORMAL; // Added field

// #################################################################################################################
Expand Down Expand Up @@ -857,6 +859,27 @@ else if (opponentIsTrackedCompetitor)
// Gmaul can hit twice, others match expected hits
int hitsToFind = entry.isGmaulSpecial() ? 2 : toMatch;

// Enforce Dragon Claws 2+2 sequencing: limit phase one to two hits
if (entry.getAnimationData() == AnimationData.MELEE_DRAGON_CLAWS_SPEC && entry.getMatchedHitsCount() < 2)
{
int remainingPhase1 = Math.max(0, 2 - entry.getMatchedHitsCount());
hitsToFind = Math.min(hitsToFind, remainingPhase1);
}

// Simple double-GMaul gate: if a different special fired on the previous tick, cap to a single hit
if (entry.isGmaulSpecial())
{
String attackerName = attacker.getName();
if (attackerName != null)
{
Integer lastSpec = lastNonGmaulSpecTickByAttacker.get(attackerName);
if (lastSpec != null && lastSpec == entry.getTick() - 1)
{
hitsToFind = Math.min(hitsToFind, 1);
}
}
}

while (matchedThisCycle < hitsToFind && hitsIter.hasNext())
{
HitsplatInfo hInfo = hitsIter.next();
Expand Down Expand Up @@ -894,15 +917,59 @@ else if (opponentIsTrackedCompetitor)
// Fallback to current ratio/scale if polled is unavailable
if (ratio < 0 || scale <= 0) { ratio = opponent.getHealthRatio(); scale = opponent.getHealthScale(); }
int hpBefore = -1;
int hpBeforeThisCycle = -1;
if (ratio >= 0 && scale > 0 && maxHpToUse > 0)
{
hpBefore = PvpPerformanceTrackerUtils.calculateHpBeforeHit(ratio, scale, maxHpToUse, entry.getActualDamageSum());
hpBeforeThisCycle = PvpPerformanceTrackerUtils.calculateHpBeforeHit(ratio, scale, maxHpToUse, damageThisCycle);
}
if (hpBefore > 0)
{
entry.setEstimatedHpBeforeHit(hpBefore);
entry.setOpponentMaxHp(maxHpToUse);
}

if (entry.getAnimationData() == AnimationData.MELEE_DRAGON_CLAWS_SPEC)
{
int matched = entry.getMatchedHitsCount();
if (matched == 2 && entry.getClawsHpBeforePhase1() == null && hpBeforeThisCycle > 0)
{
entry.setClawsHpBeforePhase1(hpBeforeThisCycle);
entry.setClawsPhase1Damage(damageThisCycle);
entry.setClawsHpAfterPhase1(hpBeforeThisCycle - damageThisCycle);
}
if (matched >= entry.getExpectedHits() && entry.getClawsHpBeforePhase2() == null && hpBeforeThisCycle > 0)
{
entry.setClawsHpBeforePhase2(hpBeforeThisCycle);
}
}
else if (entry.getAnimationData() == AnimationData.RANGED_DARK_BOW ||
entry.getAnimationData() == AnimationData.RANGED_DARK_BOW_SPEC)
{
int matchedAfter = entry.getMatchedHitsCount();
int matchedBefore = matchedAfter - matchedThisCycle;

if (matchedBefore == 0 && matchedThisCycle >= 2)
{
entry.setDarkBowHitsStacked(true);
if (entry.getDarkBowHpBeforeHit1() == null && hpBeforeThisCycle > 0)
{
entry.setDarkBowHpBeforeHit1(hpBeforeThisCycle);
}
}
else
{
if (matchedAfter >= 1 && entry.getDarkBowHpBeforeHit1() == null && hpBeforeThisCycle > 0)
{
entry.setDarkBowHpBeforeHit1(hpBeforeThisCycle);
entry.setDarkBowHpAfterHit1(hpBeforeThisCycle - damageThisCycle);
}
if (matchedAfter >= entry.getExpectedHits() && entry.getDarkBowHpBeforeHit2() == null && hpBeforeThisCycle > 0)
{
entry.setDarkBowHpBeforeHit2(hpBeforeThisCycle);
}
}
}
}
}

Expand Down Expand Up @@ -990,9 +1057,59 @@ else if (opponentIsTrackedCompetitor)
entry.setDisplayHpBefore(hpBeforeCurrent);
entry.setDisplayHpAfter(hpAfterCurrent);

Double koChanceCurrent = (hpBeforeCurrent != null)
? PvpPerformanceTrackerUtils.calculateKoChance(entry.getAccuracy(), entry.getMinHit(), entry.getMaxHit(), hpBeforeCurrent)
: null;
Double koChanceCurrent = null;
boolean isClawsSpec = entry.getAnimationData() == AnimationData.MELEE_DRAGON_CLAWS_SPEC && entry.getExpectedHits() >= 4;
boolean isDarkBow = entry.getAnimationData() == AnimationData.RANGED_DARK_BOW ||
entry.getAnimationData() == AnimationData.RANGED_DARK_BOW_SPEC;
if (isClawsSpec)
{
if (hpBeforeCurrent != null && entry.getMatchedHitsCount() >= entry.getExpectedHits())
{
int healBetween = 0;
Integer hpAfterP1 = entry.getClawsHpAfterPhase1();
Integer hpBeforeP2 = entry.getClawsHpBeforePhase2();
if (hpAfterP1 != null && hpBeforeP2 != null)
{
healBetween = Math.max(0, hpBeforeP2 - hpAfterP1);
}
koChanceCurrent = PvpPerformanceTrackerUtils.calculateClawsTwoPhaseKo(entry.getAccuracy(), entry.getMaxHit(), hpBeforeCurrent, healBetween);
}
}
else if (isDarkBow)
{
if (hpBeforeCurrent != null && entry.getMatchedHitsCount() >= entry.getExpectedHits())
{
int healBetween = 0;
if (!entry.isDarkBowHitsStacked())
{
Integer hpAfterHit1 = entry.getDarkBowHpAfterHit1();
Integer hpBeforeHit2 = entry.getDarkBowHpBeforeHit2();
if (hpAfterHit1 != null && hpBeforeHit2 != null)
{
healBetween = Math.max(0, hpBeforeHit2 - hpAfterHit1);
}
}
koChanceCurrent = PvpPerformanceTrackerUtils.calculateDarkBowTwoPhaseKo(
entry.getAccuracy(),
entry.getMinHit(),
entry.getMaxHit(),
hpBeforeCurrent,
healBetween
);
}
}
else
{
koChanceCurrent = (hpBeforeCurrent != null)
? PvpPerformanceTrackerUtils.calculateKoChance(entry.getAccuracy(), entry.getMinHit(), entry.getMaxHit(), hpBeforeCurrent)
: null;
}

if (koChanceCurrent != null && koChanceCurrent <= 0.0)
{
koChanceCurrent = null;
}

entry.setDisplayKoChance(koChanceCurrent);
entry.setKoChance(koChanceCurrent);

Expand Down Expand Up @@ -1030,6 +1147,15 @@ public void onPlayerDespawned(PlayerDespawned event)
}
}

public void recordNonGmaulSpecial(String attackerName, int tick)
{
if (attackerName == null)
{
return;
}
lastNonGmaulSpecTickByAttacker.put(attackerName, tick);
}

// #################################################################################################################
// ################################## Plugin-specific functions & global helpers ###################################
// #################################################################################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ else if (weapon == EquipmentData.DRAGON_CROSSBOW &&

FightLogEntry fightLogEntry = new FightLogEntry(player, opponent, pvpDamageCalc, offensivePray, levels, animationData);
fightLogEntry.setGmaulSpecial(isGmaulSpec);
if (animationData.isSpecial && animationData != AnimationData.MELEE_GRANITE_MAUL_SPEC)
{
PvpPerformanceTrackerPlugin.PLUGIN.recordNonGmaulSpecial(player.getName(), fightLogEntry.getTick());
}
if (PvpPerformanceTrackerPlugin.CONFIG.fightLogInChat())
{
PvpPerformanceTrackerPlugin.PLUGIN.sendTradeChatMessage(fightLogEntry.toChatMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public class PvpDamageCalc
private static final int DBOW_DMG_MODIFIER = 2;
private static final int DBOW_SPEC_DMG_MODIFIER = 3;
private static final int DBOW_SPEC_MIN_HIT = 16;
private static final int DBOW_SPEC_MAX_HIT_PER_ARROW = 48;
private static final double DRAGON_CBOW_SPEC_DMG_MODIFIER = 1.2;

private static final double DDS_SPEC_ACCURACY_MODIFIER = 1.25;
Expand Down Expand Up @@ -526,6 +527,15 @@ private void getRangedMaxHit(int rangeStrength, boolean usingSpec, EquipmentData

maxHit *= dmgModifier;
}

if (dbow && usingSpec)
{
int cap = DBOW_SPEC_MAX_HIT_PER_ARROW * 2;
if (maxHit > cap)
{
maxHit = cap;
}
}
}

private void getMagicMaxHit(EquipmentData shield, int mageDamageBonus, AnimationData animationData, EquipmentData weapon, VoidStyle voidStyle, boolean successfulOffensive)
Expand Down Expand Up @@ -674,7 +684,12 @@ private void getRangeAccuracy(int playerRangeAtt, int opponentRangeDef, boolean
/**
* Attacker Chance
*/
effectiveLevelPlayer = Math.floor(((attackerLevels.range * (successfulOffensive ? RIGOUR_OFFENSIVE_PRAYER_ATTACK_MODIFIER : 1)) + STANCE_BONUS) + 8);
int stanceBonus = STANCE_BONUS;
if (weapon == EquipmentData.DARK_BOW && usingSpec)
{
stanceBonus += 3; // dark bow spec is assumed to be on accurate
}
effectiveLevelPlayer = Math.floor(((attackerLevels.range * (successfulOffensive ? RIGOUR_OFFENSIVE_PRAYER_ATTACK_MODIFIER : 1)) + stanceBonus) + 8);
// apply void bonus if applicable
if (voidStyle == VoidStyle.VOID_ELITE_RANGE || voidStyle == VoidStyle.VOID_RANGE)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,33 @@ public class FightLogEntry implements Comparable<FightLogEntry>
@Getter @Setter
private boolean isPartOfTickGroup = false;

// Transient fields for handling multi-tick Dragon Claws special attacks
@Getter
@Setter
private transient Integer clawsPhase1Damage = null;
@Getter
@Setter
private transient Integer clawsHpBeforePhase1 = null;
@Getter
@Setter
private transient Integer clawsHpAfterPhase1 = null;
@Getter
@Setter
private transient Integer clawsHpBeforePhase2 = null;
// Transient fields for handling Dark Bow double-hit sequencing
@Getter
@Setter
private transient Integer darkBowHpBeforeHit1 = null;
@Getter
@Setter
private transient Integer darkBowHpAfterHit1 = null;
@Getter
@Setter
private transient Integer darkBowHpBeforeHit2 = null;
@Getter
@Setter
private transient boolean darkBowHitsStacked = false;

public FightLogEntry(Player attacker, Player defender, PvpDamageCalc pvpDamageCalc, int attackerOffensivePray, CombatLevels levels, AnimationData animationData)
{
this.isFullEntry = true;
Expand Down
Loading