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 @@ -1158,12 +1158,41 @@ else if (isDarkBow)
);
}
}
else
{
koChanceCurrent = (hpBeforeCurrent != null)
? PvpPerformanceTrackerUtils.calculateKoChance(entry.getAccuracy(), entry.getMinHit(), entry.getMaxHit(), hpBeforeCurrent)
: null;
}
else
{
if (hpBeforeCurrent != null)
{
switch (entry.getDamageRollDistribution())
{
case CLAMPED_TO_MINIMUM:
koChanceCurrent = PvpPerformanceTrackerUtils.calculateClampedKoChance(
entry.getAccuracy(),
entry.getMinHit(),
entry.getMaxHit(),
hpBeforeCurrent
);
break;
case MULTI_HIT_CLAMPED_TO_MINIMUM:
koChanceCurrent = PvpPerformanceTrackerUtils.calculateMultiHitClampedKoChance(
entry.getAccuracy(),
entry.getMinHit(),
entry.getMaxHit(),
entry.getDamageRollHitCount(),
hpBeforeCurrent
);
break;
case STANDARD:
default:
koChanceCurrent = PvpPerformanceTrackerUtils.calculateKoChance(
entry.getAccuracy(),
entry.getMinHit(),
entry.getMaxHit(),
hpBeforeCurrent
);
break;
}
}
}

if (koChanceCurrent != null && koChanceCurrent <= 0.0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import static matsyir.pvpperformancetracker.PvpPerformanceTrackerPlugin.PLUGIN;
import static matsyir.pvpperformancetracker.utils.NumberFormatter.nf1;
import static matsyir.pvpperformancetracker.utils.PvpPerformanceTrackerUtils.fixItemId;
import static matsyir.pvpperformancetracker.utils.PvpPerformanceTrackerUtils.getExpectedHits;
import matsyir.pvpperformancetracker.models.EquipmentData;
import matsyir.pvpperformancetracker.models.EquipmentData.VoidStyle;
import matsyir.pvpperformancetracker.models.CombatLevels;
Expand All @@ -58,6 +59,13 @@
@Slf4j
public class PvpDamageCalc
{
public enum DamageRollDistribution
{
STANDARD,
CLAMPED_TO_MINIMUM,
MULTI_HIT_CLAMPED_TO_MINIMUM
}

private static final int STAB_ATTACK = 0, SLASH_ATTACK = 1, CRUSH_ATTACK = 2, MAGIC_ATTACK = 3,
RANGE_ATTACK = 4, STAB_DEF = 5, SLASH_DEF = 6, CRUSH_DEF = 7, MAGIC_DEF = 8;
public static final int RANGE_DEF = 9;
Expand Down Expand Up @@ -136,7 +144,12 @@ public class PvpDamageCalc
private int minHit = 0;
@Getter
private int maxHit = 0;
@Getter
private DamageRollDistribution damageRollDistribution = DamageRollDistribution.STANDARD;
@Getter
private int damageRollHitCount = 1;
private double rangedExpectedProcDamage = 0;
private int seekingArrowMinHit = 0;

private CombatLevels attackerLevels;
private CombatLevels defenderLevels;
Expand All @@ -163,7 +176,10 @@ public void updateDamageStats(Player attacker, Player defender, boolean success,
accuracy = 0;
minHit = 0;
maxHit = 0;
damageRollDistribution = DamageRollDistribution.STANDARD;
damageRollHitCount = 1;
rangedExpectedProcDamage = 0;
seekingArrowMinHit = 0;

int[] attackerItems = attacker.getPlayerComposition().getEquipmentIds();
int[] defenderItems = defender.getPlayerComposition().getEquipmentIds();
Expand All @@ -187,7 +203,7 @@ public void updateDamageStats(Player attacker, Player defender, boolean success,
}
else if (attackStyle == AttackStyle.RANGED)
{
getRangedMaxHit(playerStats[RANGE_STRENGTH], isSpecial, weapon, voidStyle, true, attackerItems);
getRangedMaxHit(playerStats[RANGE_STRENGTH], isSpecial, weapon, voidStyle, true, attackerItems, animationData);
getRangeAccuracy(playerStats[RANGE_ATTACK], opponentStats[RANGE_DEF], isSpecial, weapon, voidStyle, true, attackerItems);
}
// this should always be true at this point, but just in case. unknown animation styles won't
Expand Down Expand Up @@ -224,7 +240,10 @@ public void updateDamageStats(FightLogEntry atkLog, FightLogEntry defenderLog)
accuracy = 0;
minHit = 0;
maxHit = 0;
damageRollDistribution = DamageRollDistribution.STANDARD;
damageRollHitCount = 1;
rangedExpectedProcDamage = 0;
seekingArrowMinHit = 0;

EquipmentData weapon = EquipmentData.fromId(fixItemId(attackerItems[KitType.WEAPON.getIndex()]));

Expand All @@ -245,7 +264,7 @@ public void updateDamageStats(FightLogEntry atkLog, FightLogEntry defenderLog)
}
else if (attackStyle == AttackStyle.RANGED)
{
getRangedMaxHit(playerStats[RANGE_STRENGTH], isSpecial, weapon, voidStyle, successfulOffensive, attackerItems);
getRangedMaxHit(playerStats[RANGE_STRENGTH], isSpecial, weapon, voidStyle, successfulOffensive, attackerItems, animationData);
getRangeAccuracy(playerStats[RANGE_ATTACK], opponentStats[RANGE_DEF], isSpecial, weapon, voidStyle, successfulOffensive, attackerItems);
}
// this should always be true at this point, but just in case. unknown animation styles won't
Expand Down Expand Up @@ -329,6 +348,13 @@ private void getAverageHit(boolean success, EquipmentData weapon, boolean usingS

averageSuccessfulHit = (double) total / maxHit;
}
else if (seekingArrowMinHit > 0)
{
minHit = seekingArrowMinHit;
averageSuccessfulHit = damageRollDistribution == DamageRollDistribution.MULTI_HIT_CLAMPED_TO_MINIMUM ?
getAverageSuccessfulMultiHitWithMinimum(minHit, damageRollHitCount) :
getAverageSuccessfulHitWithMinimum(minHit);
}
else if (usingSpec && claws)
{
// if first 1-2 claws miss, it's a 150% dmg multiplier because when the 3rd att hits, the last
Expand Down Expand Up @@ -441,6 +467,34 @@ else if (usingSpec && voidwaker)
}
}

private double getAverageSuccessfulHitWithMinimum(int minimumHit)
{
minimumHit = Math.max(0, minimumHit);
maxHit = Math.max(maxHit, minimumHit);
return getAverageSuccessfulHitWithMinimum(minimumHit, maxHit);
}

private double getAverageSuccessfulMultiHitWithMinimum(int minimumHit, int hitCount)
{
hitCount = Math.max(1, hitCount);
int perHitMinimum = Math.max(0, minimumHit / hitCount);
int perHitMaximum = Math.max(perHitMinimum, maxHit / hitCount);
maxHit = Math.max(maxHit, perHitMaximum * hitCount);
minHit = perHitMinimum * hitCount;
return hitCount * getAverageSuccessfulHitWithMinimum(perHitMinimum, perHitMaximum);
}

private double getAverageSuccessfulHitWithMinimum(int minimumHit, int maximumHit)
{
int total = 0;
for (int i = 0; i <= maximumHit; i++)
{
total += Math.max(i, minimumHit);
}

return (double) total / (maximumHit + 1);
}

private int calculateBurningClawTotalDamage(int D)
{
return (int)Math.floor(0.25 * D) + (int)Math.floor(0.25 * D) + (int)Math.floor(0.5 * D);
Expand Down Expand Up @@ -493,7 +547,7 @@ private void getMeleeMaxHit(int meleeStrength, boolean usingSpec, EquipmentData
maxHit = (int) (damageModifier * baseDamage);
}

private void getRangedMaxHit(int rangeStrength, boolean usingSpec, EquipmentData weapon, VoidStyle voidStyle, boolean successfulOffensive, int[] attackerComposition)
private void getRangedMaxHit(int rangeStrength, boolean usingSpec, EquipmentData weapon, VoidStyle voidStyle, boolean successfulOffensive, int[] attackerComposition, AnimationData animationData)
{
RangeAmmoData weaponAmmo = EquipmentData.getWeaponAmmo(weapon, isLmsFight);
EquipmentData head = EquipmentData.fromId(fixItemId(attackerComposition[KitType.HEAD.getIndex()]));
Expand All @@ -502,12 +556,24 @@ private void getRangedMaxHit(int rangeStrength, boolean usingSpec, EquipmentData

boolean ballista = weapon == EquipmentData.HEAVY_BALLISTA;
boolean dbow = weapon == EquipmentData.DARK_BOW;
boolean magicShortbow = weapon == EquipmentData.MAGIC_SHORTBOW || weapon == EquipmentData.MAGIC_SHORTBOW_I;
boolean dragonCbow = weapon == EquipmentData.DRAGON_CROSSBOW;
boolean diamonds = ArrayUtils.contains(RangeAmmoData.DIAMOND_BOLTS, weaponAmmo);
boolean opals = ArrayUtils.contains(RangeAmmoData.OPAL_BOLTS, weaponAmmo);
boolean skipBoltProcEffects = dragonCbow && usingSpec;
boolean darkBowSpecial = dbow && usingSpec;
int expectedHits = Math.max(1, getExpectedHits(animationData));
boolean seekingArrowAttack = !darkBowSpecial && RangeAmmoData.usesSeekingArrowUpgrade(weaponAmmo, isLmsFight);

int ammoStrength = weaponAmmo == null ? 0 : weaponAmmo.getRangeStr();
seekingArrowMinHit = seekingArrowAttack ? RangeAmmoData.getSeekingArrowMinimumHit(weaponAmmo, isLmsFight) * expectedHits : 0;
if (seekingArrowMinHit > 0)
{
damageRollHitCount = expectedHits;
damageRollDistribution = expectedHits > 1 ?
DamageRollDistribution.MULTI_HIT_CLAMPED_TO_MINIMUM :
DamageRollDistribution.CLAMPED_TO_MINIMUM;
}

rangeStrength += ammoStrength;

Expand Down Expand Up @@ -583,6 +649,10 @@ else if (!skipBoltProcEffects && opals)
maxHit = cap;
}
}
else if (magicShortbow && usingSpec && expectedHits > 1)
{
maxHit *= expectedHits;
}
}

private void getMagicMaxHit(EquipmentData shield, int mageDamageBonus, AnimationData animationData, EquipmentData weapon, VoidStyle voidStyle, boolean successfulOffensive)
Expand Down Expand Up @@ -713,6 +783,7 @@ private void getMeleeAccuracy(int[] playerStats, int[] opponentStats, AttackStyl
private void getRangeAccuracy(int playerRangeAtt, int opponentRangeDef, boolean usingSpec, EquipmentData weapon, VoidStyle voidStyle, boolean successfulOffensive, int[] attackerComposition)
{
RangeAmmoData weaponAmmo = EquipmentData.getWeaponAmmo(weapon, this.isLmsFight);
playerRangeAtt += RangeAmmoData.getSeekingArrowAccuracyBonus(weaponAmmo, this.isLmsFight);

boolean diamonds = ArrayUtils.contains(RangeAmmoData.DIAMOND_BOLTS, weaponAmmo);
boolean opals = ArrayUtils.contains(RangeAmmoData.OPAL_BOLTS, weaponAmmo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public enum AnimationData
// RANGED
RANGED_SHORTBOW(426, AttackStyle.RANGED), // Confirmed same w/ 3 types of arrows, w/ maple, magic, & hunter's shortbow, scorching bow, craw's bow, dbow, dbow spec
RANGED_RUNE_KNIFE_PVP(929, AttackStyle.RANGED), // 1 tick animation, has 1 tick delay between attacks. likely same for all knives. Same for morrigan's javelins, both spec & normal attack.
RANGED_MAGIC_SHORTBOW_SPEC(1074, AttackStyle.RANGED, true),
RANGED_MAGIC_SHORTBOW_SPEC(1074, AttackStyle.RANGED, true, 2),
RANGED_CROSSBOW_PVP(4230, AttackStyle.RANGED), // Tested RCB & ACB w/ dragonstone bolts (e) & diamond bolts (e)
RANGED_BLOWPIPE(5061, AttackStyle.RANGED), // tested in PvP with all styles. Has 1 tick delay between animations in pvp.
RANGED_DARTS(6600, AttackStyle.RANGED), // tested w/ addy darts. Seems to be constant animation but sometimes stalls and doesn't animate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ public class FightLogEntry implements Comparable<FightLogEntry>
@SerializedName("l") // l for lowest hit
private int minHit;
@Expose
@SerializedName("dD") // damage distribution
private PvpDamageCalc.DamageRollDistribution damageRollDistribution = PvpDamageCalc.DamageRollDistribution.STANDARD;
@Expose
@SerializedName("dHC") // damage hit count
private int damageRollHitCount = 1;
@Expose
@SerializedName("s")
private boolean splash; // true if it was a magic attack and it splashed
@Expose
Expand Down Expand Up @@ -256,6 +262,8 @@ public FightLogEntry(Player attacker, Player defender, PvpDamageCalc pvpDamageCa
this.accuracy = pvpDamageCalc.getAccuracy();
this.minHit = pvpDamageCalc.getMinHit();
this.maxHit = pvpDamageCalc.getMaxHit();
this.damageRollDistribution = pvpDamageCalc.getDamageRollDistribution();
this.damageRollHitCount = pvpDamageCalc.getDamageRollHitCount();
this.splash = animationData.attackStyle == AnimationData.AttackStyle.MAGIC && defender.getGraphic() == GraphicID.SPLASH;
this.attackerLevels = levels; // CAN BE NULL
this.attackerRingItemId = getLocalPlayerRingItemId(attacker);
Expand Down Expand Up @@ -310,6 +318,8 @@ public FightLogEntry(FightLogEntry e, PvpDamageCalc pvpDamageCalc)
this.accuracy = pvpDamageCalc.getAccuracy();
this.minHit = pvpDamageCalc.getMinHit();
this.maxHit = pvpDamageCalc.getMaxHit();
this.damageRollDistribution = pvpDamageCalc.getDamageRollDistribution();
this.damageRollHitCount = pvpDamageCalc.getDamageRollHitCount();
this.splash = e.splash;
this.defenderElyProc = e.defenderElyProc;
this.defenderSotdMeleeReductionProc = e.defenderSotdMeleeReductionProc;
Expand Down Expand Up @@ -419,6 +429,16 @@ public boolean success()
return animationData.attackStyle.getProtection() != defenderOverhead;
}

public PvpDamageCalc.DamageRollDistribution getDamageRollDistribution()
{
return damageRollDistribution != null ? damageRollDistribution : PvpDamageCalc.DamageRollDistribution.STANDARD;
}

public int getDamageRollHitCount()
{
return Math.max(1, damageRollHitCount);
}

public String toChatMessage()
{
Color darkRed = new Color(127, 0, 0); // same color as default clan chat color
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@

public interface RangeAmmoData
{
int SEEKING_ARROW_ACCURACY_BONUS = 20;
int SEEKING_ARROW_MIN_HIT = 3;

RangeAmmoData[] DIAMOND_BOLTS = {
BoltAmmo.DIAMOND_BOLTS_E,
StrongBoltAmmo.DIAMOND_BOLTS_E,
Expand All @@ -41,6 +44,11 @@ public interface RangeAmmoData
StrongBoltAmmo.OPAL_DRAGON_BOLTS_E
};

RangeAmmoData[] SEEKING_ARROW_AMMO = {
OtherAmmo.AMETHYST_ARROWS,
OtherAmmo.DRAGON_ARROW
};

static RangeAmmoData fromId(int itemId)
{
for (BoltAmmo ammo : BoltAmmo.values())
Expand Down Expand Up @@ -74,6 +82,21 @@ static RangeAmmoData fromId(int itemId)
return null;
}

static boolean usesSeekingArrowUpgrade(RangeAmmoData ammo, boolean isLmsFight)
{
return !isLmsFight && Arrays.stream(SEEKING_ARROW_AMMO).anyMatch(seekingArrowAmmo -> seekingArrowAmmo == ammo);
}

static int getSeekingArrowAccuracyBonus(RangeAmmoData ammo, boolean isLmsFight)
{
return usesSeekingArrowUpgrade(ammo, isLmsFight) ? SEEKING_ARROW_ACCURACY_BONUS : 0;
}

static int getSeekingArrowMinimumHit(RangeAmmoData ammo, boolean isLmsFight)
{
return usesSeekingArrowUpgrade(ammo, isLmsFight) ? SEEKING_ARROW_MIN_HIT : 0;
}

int getItemId(); // itemIDs used for DISPLAYING bolts, not getting them.
int getRangeStr();
double getBonusMaxHit(int rangeLevel); // damage bonus from bolt specs.
Expand Down Expand Up @@ -262,6 +285,3 @@ public String toString()
}
}




Loading