diff --git a/scripts/generateEquipment.py b/scripts/generateEquipment.py index 792ac4fa..e7393e4d 100644 --- a/scripts/generateEquipment.py +++ b/scripts/generateEquipment.py @@ -41,6 +41,21 @@ 'Combat style' ] +ITEMS_TO_SKIP = [ + 'The dogsword', + 'Drygore blowpipe', + 'Amulet of the monarchs', + 'Emperor ring', + 'Devil\'s element', + 'Nature\'s reprisal', + 'Gloves of the damned', + 'Crystal blessing', + 'Sunlight spear', + 'Sunlit bracers', + 'Thunder khopesh', + 'Thousand-dragon ward' +] + def getEquipmentData(): equipment = {} offset = 0 @@ -147,6 +162,9 @@ def main(): if "(Last Man Standing)" in equipment['name']: continue + if equipment['name'] in ITEMS_TO_SKIP: + continue + # Append the current equipment item to the calc's equipment list data.append(equipment) diff --git a/src/app/components/generic/GridItem.tsx b/src/app/components/generic/GridItem.tsx index 8b29b19d..5b608eaf 100644 --- a/src/app/components/generic/GridItem.tsx +++ b/src/app/components/generic/GridItem.tsx @@ -9,9 +9,6 @@ interface IGridItemProps { image: string | StaticImageData; onClick: (item: T) => void; active: boolean; - width?: number; - height?: number; - className?: string; } /** @@ -19,7 +16,7 @@ interface IGridItemProps { */ const GridItem: React.FC> = observer((props: IGridItemProps) => { const { - item, name, image, active, onClick, width, height, className, + item, name, image, active, onClick, } = props; return ( @@ -28,7 +25,7 @@ const GridItem: React.FC> = observer((p data-tooltip-id="tooltip" data-tooltip-content={name} onClick={() => onClick(item)} - className={`cursor-pointer flex justify-center items-center ${className}`} + className="cursor-pointer w-[28px] h-[23px] flex justify-center items-center" >
{active && ( @@ -36,7 +33,7 @@ const GridItem: React.FC> = observer((p className="filter drop-shadow absolute top-[-10px] left-[-12px] text-green-400 dark:text-green-200 w-5" /> )} - {name} + {name}
); diff --git a/src/app/components/player/EchoesLeague.tsx b/src/app/components/player/EchoesLeague.tsx deleted file mode 100644 index 854f075a..00000000 --- a/src/app/components/player/EchoesLeague.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import { observer } from 'mobx-react-lite'; -import { useStore } from '@/state'; -import GridItem from '@/app/components/generic/GridItem'; -import { - MAGIC_MASTERIES, - MELEE_MASTERIES, - RANGED_MASTERIES, - MasteryStyle, - MasteryUiData, -} from '@/lib/LeaguesV'; -import NumberInput from '@/app/components/generic/NumberInput'; -import magic_2 from '@/public/img/league/magic_2.png'; - -const MasteryButton: React.FC> = observer((props) => { - const { - masteryStyle, - mastery, - name, - image, - } = props; - - const store = useStore(); - const { five: data } = store.player.leagues; - - let ix = 0; - if (masteryStyle === 'ranged') { - ix += 6; - } else if (masteryStyle === 'magic') { - ix += 12; - } - - const onClick = () => { - if (store.player.leagues.five[masteryStyle] === mastery) { - store.updatePlayer({ leagues: { five: { [masteryStyle]: 0 } } }); - } else { - store.updatePlayer({ leagues: { five: { [masteryStyle]: mastery } } }); - } - }; - - return ( - = mastery} - width={40} - height={40} - /> - ); -}); - -const EchoesLeague: React.FC = observer(() => { - const store = useStore(); - const { five: data } = store.player.leagues; - - const passives = []; - const maxMastery = Math.max(data.melee, data.ranged, data.magic); - if (maxMastery >= 3) { - passives.push('+100% accuracy'); - } - if (maxMastery >= 6) { - passives.push('+60% prayer penetration'); - } - - return ( -
-

- Masteries -

- -
- {MELEE_MASTERIES.map((r) => ())} - {RANGED_MASTERIES.map((r) => ())} - {MAGIC_MASTERIES.map((r) => ())} -
- - {data.magic >= 2 && ( -
-
- store.updatePlayer({ leagues: { five: { ticksDelayed: v } } })} - /> - - - {' '} - Extra delay ticks - {' '} - - ? - - -
-
- )} - - {(passives.length > 0) && ( -
- Passives: -
    - {passives.map((passive, i) => ( - // each index is always the same string - // eslint-disable-next-line react/no-array-index-key -
  • {passive}
  • - ))} -
-
- )} -
- ); -}); - -export default EchoesLeague; diff --git a/src/app/components/player/PlayerInnerContainer.tsx b/src/app/components/player/PlayerInnerContainer.tsx index e3b93f6d..1beb64a2 100644 --- a/src/app/components/player/PlayerInnerContainer.tsx +++ b/src/app/components/player/PlayerInnerContainer.tsx @@ -5,8 +5,6 @@ import options from '@/public/img/tabs/options.webp'; import prayer from '@/public/img/tabs/prayer.png'; import React, { useState } from 'react'; import PlayerTab from '@/app/components/player/PlayerTab'; -import league from '@/public/img/tabs/league.png'; -import EchoesLeague from '@/app/components/player/EchoesLeague'; import Equipment from './Equipment'; import Combat from './Combat'; import Skills from './Skills'; @@ -30,8 +28,6 @@ const PlayerInnerContainer: React.FC = () => { return ; case 'options': return ; - case 'league': - return ; default: break; } @@ -47,7 +43,6 @@ const PlayerInnerContainer: React.FC = () => { setSelected('equipment')} /> setSelected('prayer')} /> setSelected('options')} /> - setSelected('league')} /> {renderSelected()} diff --git a/src/app/components/results/HitDistribution.tsx b/src/app/components/results/HitDistribution.tsx index a2ddea4f..4e0678c0 100644 --- a/src/app/components/results/HitDistribution.tsx +++ b/src/app/components/results/HitDistribution.tsx @@ -14,7 +14,6 @@ import { observer } from 'mobx-react-lite'; import { max } from 'd3-array'; import { toJS } from 'mobx'; import { isDefined } from '@/utils'; -import { MeleeMastery } from '@/lib/LeaguesV'; const CustomTooltip: React.FC> = ({ active, payload, label }) => { if (active && payload && payload.length) { @@ -138,14 +137,6 @@ const HitDistribution: React.FC = observer(() => { - {store.loadouts[selectedLoadout].leagues.five.melee >= MeleeMastery.MELEE_2 && ( -

- - Echoes are intentionally omitted from the above graph for clarity, - but are included in all other calculations. - -

- )} ); diff --git a/src/enums/EquipmentCategory.ts b/src/enums/EquipmentCategory.ts index 8f7c45b4..78b5f700 100644 --- a/src/enums/EquipmentCategory.ts +++ b/src/enums/EquipmentCategory.ts @@ -18,7 +18,6 @@ export enum EquipmentCategory { CROSSBOW = 'Crossbow', DAGGER = 'Dagger', GUN = 'Gun', - MULTISTYLE = 'Multi-Style', PARTISAN = 'Partisan', PICKAXE = 'Pickaxe', POLEARM = 'Polearm', diff --git a/src/lib/BaseCalc.ts b/src/lib/BaseCalc.ts index d2bc4902..99a67a0a 100644 --- a/src/lib/BaseCalc.ts +++ b/src/lib/BaseCalc.ts @@ -331,7 +331,7 @@ export default class BaseCalc { } protected isWearingGodsword(): boolean { - return this.wearing(['Ancient godsword', 'Armadyl godsword', 'Bandos godsword', 'Saradomin godsword', 'Zamorak godsword', 'The dogsword']); + return this.wearing(['Ancient godsword', 'Armadyl godsword', 'Bandos godsword', 'Saradomin godsword', 'Zamorak godsword']); } /** @@ -382,7 +382,7 @@ export default class BaseCalc { * @see https://oldschool.runescape.wiki/w/Karil_the_Tainted%27s_equipment */ protected isWearingKarils(): boolean { - return this.wearingAll(["Karil's coif", "Karil's leathertop", "Karil's leatherskirt", "Karil's crossbow"]) && this.isWearingAnyDamnedItems(); + return this.wearingAll(["Karil's coif", "Karil's leathertop", "Karil's leatherskirt", "Karil's crossbow", 'Amulet of the damned']); } /** @@ -391,7 +391,7 @@ export default class BaseCalc { */ protected isWearingAhrims(): boolean { - return this.wearingAll(["Ahrim's staff", "Ahrim's hood", "Ahrim's robetop", "Ahrim's robeskirt"]) && this.isWearingAnyDamnedItems(); + return this.wearingAll(["Ahrim's staff", "Ahrim's hood", "Ahrim's robetop", "Ahrim's robeskirt", 'Amulet of the damned']); } /** @@ -399,11 +399,7 @@ export default class BaseCalc { * @see https://oldschool.runescape.wiki/w/Torag_the_Corrupted%27s_equipment */ protected isWearingTorags(): boolean { - return this.wearingAll(["Torag's helm", "Torag's platebody", "Torag's platelegs", "Torag's hammers"]) && this.isWearingAnyDamnedItems(); - } - - protected isWearingAnyDamnedItems(): boolean { - return this.wearing('Amulet of the damned') || this.wearing('Gloves of the damned'); + return this.wearingAll(["Torag's helm", "Torag's platebody", "Torag's platelegs", "Torag's hammers", 'Amulet of the damned']); } protected isWearingBloodMoonSet(): boolean { @@ -523,10 +519,6 @@ export default class BaseCalc { return true; } - if (this.wearing('Thunder khopesh')) { - return true; - } - return false; } diff --git a/src/lib/CalcDetails.ts b/src/lib/CalcDetails.ts index 06c532b2..3b580ff9 100644 --- a/src/lib/CalcDetails.ts +++ b/src/lib/CalcDetails.ts @@ -95,11 +95,6 @@ export enum DetailKey { NPC_ACCURACY_ROLL_BONUS = 'NPC accuracy bonus', NPC_ACCURACY_ROLL_FINAL = 'NPC accuracy roll', - // leagues relics - PLAYER_ACCURACY_LEAGUES_5_PASSIVE = 'Leagues V passive accuracy', - MAX_HIT_FOCUS_BLASTS = 'Focus blasts max hit', - MAX_HIT_ADRENALINE_CHARGES = 'Adrenaline charges max hit', - MAX_HIT_REPEAT_SHOOTER = 'Repeat shooter max hit', } export interface DetailEntry { diff --git a/src/lib/Equipment.ts b/src/lib/Equipment.ts index 75694d25..a93ddee1 100644 --- a/src/lib/Equipment.ts +++ b/src/lib/Equipment.ts @@ -254,29 +254,6 @@ export const calculateAttackSpeed = (player: Player, monster: Monster): number = } } - let activeRelic: number; - if (player.style.type === 'ranged') { - activeRelic = player.leagues.five.ranged; - } else if (player.style.type === 'magic') { - activeRelic = player.leagues.five.magic; - } else { - activeRelic = player.leagues.five.melee; - } - - if (activeRelic >= 5) { - if (attackSpeed >= 5) { - attackSpeed = Math.trunc(attackSpeed / 2); - } else { - attackSpeed = Math.ceil(attackSpeed / 2); - } - } else if (activeRelic >= 3) { - attackSpeed = Math.trunc(attackSpeed * 4 / 5); - } - - if (player.style.type === 'magic' && activeRelic >= 2) { - attackSpeed += player.leagues.five.ticksDelayed; - } - return Math.max(attackSpeed, 1); }; @@ -422,8 +399,6 @@ export const WEAPON_SPEC_COSTS: { [canonicalName: string]: number } = { 'Armadyl godsword': 50, 'Zamorak godsword': 50, 'Abyssal bludgeon': 50, - 'The dogsword': 50, - 'Thunder khopesh': 50, 'Magic shortbow': 55, 'Dark bow': 55, diff --git a/src/lib/LeaguesV.ts b/src/lib/LeaguesV.ts deleted file mode 100644 index 38ef24cb..00000000 --- a/src/lib/LeaguesV.ts +++ /dev/null @@ -1,195 +0,0 @@ -import melee_1 from '@/public/img/league/melee_1.png'; -import melee_2 from '@/public/img/league/melee_2.png'; -import melee_3 from '@/public/img/league/melee_3.png'; -import melee_4 from '@/public/img/league/melee_4.png'; -import melee_5 from '@/public/img/league/melee_5.png'; -import melee_6 from '@/public/img/league/melee_6.png'; -import ranged_1 from '@/public/img/league/ranged_1.png'; -import ranged_2 from '@/public/img/league/ranged_2.png'; -import ranged_3 from '@/public/img/league/ranged_3.png'; -import ranged_4 from '@/public/img/league/ranged_4.png'; -import ranged_5 from '@/public/img/league/ranged_5.png'; -import ranged_6 from '@/public/img/league/ranged_6.png'; -import magic_1 from '@/public/img/league/magic_1.png'; -import magic_2 from '@/public/img/league/magic_2.png'; -import magic_3 from '@/public/img/league/magic_3.png'; -import magic_4 from '@/public/img/league/magic_4.png'; -import magic_5 from '@/public/img/league/magic_5.png'; -import magic_6 from '@/public/img/league/magic_6.png'; -import { StaticImageData } from 'next/image'; - -export enum MeleeMastery { - NONE = 0, - MELEE_1, - MELEE_2, - MELEE_3, - MELEE_4, - MELEE_5, - MELEE_6, -} - -export enum RangedMastery { - NONE = 0, - RANGED_1, - RANGED_2, - RANGED_3, - RANGED_4, - RANGED_5, - RANGED_6, -} - -export enum MagicMastery { - NONE = 0, - MAGIC_1, - MAGIC_2, - MAGIC_3, - MAGIC_4, - MAGIC_5, - MAGIC_6, -} - -export interface LeaguesState { - melee: MeleeMastery; - ranged: RangedMastery; - magic: MagicMastery; - - /** for {@link MagicMastery.MAGIC_2} */ - ticksDelayed: number; - - /** for {@link RangedMastery.RANGED_2}. not shown in ui anymore but still stateful in calculations */ - attackCount: number; -} - -export type MasteryStyle = keyof Pick; - -export const defaultLeaguesState = (): LeaguesState => ({ - melee: MeleeMastery.NONE, - ranged: RangedMastery.NONE, - magic: MagicMastery.NONE, - ticksDelayed: 0, - attackCount: 2, // average, -1 relative to ui so this is 3/5 -}); - -export interface MasteryUiData { - masteryStyle: S; - mastery: LeaguesState[S]; - name: string; - image: StaticImageData; -} - -export const MELEE_MASTERIES: MasteryUiData<'melee'>[] = [ - { - name: 'Melee I', - mastery: MeleeMastery.MELEE_1, - masteryStyle: 'melee', - image: melee_1, - }, - { - name: 'Melee II', - mastery: MeleeMastery.MELEE_2, - masteryStyle: 'melee', - image: melee_2, - }, - { - name: 'Melee III', - mastery: MeleeMastery.MELEE_3, - masteryStyle: 'melee', - image: melee_3, - }, - { - name: 'Melee IV', - mastery: MeleeMastery.MELEE_4, - masteryStyle: 'melee', - image: melee_4, - }, - { - name: 'Melee V', - mastery: MeleeMastery.MELEE_5, - masteryStyle: 'melee', - image: melee_5, - }, - { - name: 'Melee VI', - mastery: MeleeMastery.MELEE_6, - masteryStyle: 'melee', - image: melee_6, - }, -]; - -export const RANGED_MASTERIES: MasteryUiData<'ranged'>[] = [ - { - name: 'Ranged I', - mastery: RangedMastery.RANGED_1, - masteryStyle: 'ranged', - image: ranged_1, - }, - { - name: 'Ranged II', - mastery: RangedMastery.RANGED_2, - masteryStyle: 'ranged', - image: ranged_2, - }, - { - name: 'Ranged III', - mastery: RangedMastery.RANGED_3, - masteryStyle: 'ranged', - image: ranged_3, - }, - { - name: 'Ranged IV', - mastery: RangedMastery.RANGED_4, - masteryStyle: 'ranged', - image: ranged_4, - }, - { - name: 'Ranged V', - mastery: RangedMastery.RANGED_5, - masteryStyle: 'ranged', - image: ranged_5, - }, - { - name: 'Ranged VI', - mastery: RangedMastery.RANGED_6, - masteryStyle: 'ranged', - image: ranged_6, - }, -]; - -export const MAGIC_MASTERIES: MasteryUiData<'magic'>[] = [ - { - name: 'Magic I', - mastery: MagicMastery.MAGIC_1, - masteryStyle: 'magic', - image: magic_1, - }, - { - name: 'Magic II', - mastery: MagicMastery.MAGIC_2, - masteryStyle: 'magic', - image: magic_2, - }, - { - name: 'Magic III', - mastery: MagicMastery.MAGIC_3, - masteryStyle: 'magic', - image: magic_3, - }, - { - name: 'Magic IV', - mastery: MagicMastery.MAGIC_4, - masteryStyle: 'magic', - image: magic_4, - }, - { - name: 'Magic V', - mastery: MagicMastery.MAGIC_5, - masteryStyle: 'magic', - image: magic_5, - }, - { - name: 'Magic VI', - mastery: MagicMastery.MAGIC_6, - masteryStyle: 'magic', - image: magic_6, - }, -]; diff --git a/src/lib/NPCVsPlayerCalc.ts b/src/lib/NPCVsPlayerCalc.ts index 922634af..0695e18a 100644 --- a/src/lib/NPCVsPlayerCalc.ts +++ b/src/lib/NPCVsPlayerCalc.ts @@ -144,8 +144,7 @@ export default class NPCVsPlayerCalc extends BaseCalc { if (this.isWearingTorags()) { const currentHealth = skills.hp + boosts.hp; const missingHealth = ((Math.round((skills.hp - currentHealth) / skills.hp * 100) / 100) * 100); - const factor = this.wearing('Gloves of the damned') ? 50 : 100; - effectiveLevel = this.trackFactor(DetailKey.PLAYER_DEFENCE_ROLL_LEVEL_TORAGS, effectiveLevel, [factor + missingHealth, factor]); + effectiveLevel = this.trackFactor(DetailKey.PLAYER_DEFENCE_ROLL_LEVEL_TORAGS, effectiveLevel, [1 + missingHealth, 100]); } if (style === 'magic') { diff --git a/src/lib/PlayerVsNPCCalc.ts b/src/lib/PlayerVsNPCCalc.ts index 00350c30..04b543fb 100644 --- a/src/lib/PlayerVsNPCCalc.ts +++ b/src/lib/PlayerVsNPCCalc.ts @@ -40,7 +40,6 @@ import { TTK_DIST_MAX_ITER_ROUNDS, USES_DEFENCE_LEVEL_FOR_MAGIC_DEFENCE_NPC_IDS, VERZIK_P1_IDS, - ZULRAH_IDS, } from '@/lib/constants'; import { EquipmentCategory } from '@/enums/EquipmentCategory'; import { DetailKey } from '@/lib/CalcDetails'; @@ -64,13 +63,9 @@ import { rubyBolts, } from '@/lib/dists/bolts'; import { burningClawDoT, burningClawSpec, dClawDist } from '@/lib/dists/claws'; -import { - LeaguesState, MagicMastery, MeleeMastery, RangedMastery, -} from '@/lib/LeaguesV'; const PARTIALLY_IMPLEMENTED_SPECS: string[] = [ 'Ancient godsword', - 'The dogsword', ]; // https://oldschool.runescape.wiki/w/Category:Weapons_with_Special_attacks @@ -217,11 +212,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { const mattrs = this.monster.attributes; const { buffs } = this.player; - if (this.wearing('Crystal blessing')) { - const crystalPieces = (this.wearing('Crystal helm') ? 1 : 0) + (this.wearing('Crystal legs') ? 2 : 0) + (this.wearing('Crystal body') ? 3 : 0); - attackRoll = Math.trunc(attackRoll * (20 + crystalPieces) / 20); - } - // These bonuses do not stack with each other if (this.wearing('Amulet of avarice') && this.monster.name.startsWith('Revenant')) { const factor = [buffs.forinthrySurge ? 27 : 24, 20]; @@ -358,11 +348,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { // Specific bonuses that are applied from equipment const mattrs = this.monster.attributes; - if (this.wearing('Crystal blessing')) { - const crystalPieces = (this.wearing('Crystal helm') ? 1 : 0) + (this.wearing('Crystal legs') ? 2 : 0) + (this.wearing('Crystal body') ? 3 : 0); - maxHit = Math.trunc(maxHit * (40 + crystalPieces) / 40); - } - // These bonuses do not stack with each other if (this.wearing('Amulet of avarice') && this.monster.name.startsWith('Revenant')) { const factor = [buffs.forinthrySurge ? 27 : 24, 20]; @@ -451,7 +436,7 @@ export default class PlayerVsNPCCalc extends BaseCalc { if (this.wearing('Bandos godsword')) { maxHit = this.trackFactor(DetailKey.MAX_HIT_SPEC, maxHit, [11, 10]); - } else if (this.wearing(['Armadyl godsword', 'The dogsword'])) { + } else if (this.wearing('Armadyl godsword')) { maxHit = this.trackFactor(DetailKey.MAX_HIT_SPEC, maxHit, [5, 4]); } else if (this.wearing('Dragon warhammer')) { maxHit = this.trackFactor(DetailKey.MAX_HIT_SPEC, maxHit, [3, 2]); @@ -707,11 +692,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { } } - if (this.hasLeaguesMastery('ranged', RangedMastery.RANGED_2)) { - const factor = Math.max(0, Math.min(4, this.player.leagues.five.attackCount)); - maxHit = this.trackFactor(DetailKey.MAX_HIT_REPEAT_SHOOTER, maxHit, [20 + factor, 20]); - } - return [minHit, maxHit]; } @@ -807,10 +787,7 @@ export default class PlayerVsNPCCalc extends BaseCalc { const spellement = this.player.spell?.element; if (this.monster.weakness && spellement) { if (spellement === this.monster.weakness.element) { - let severity = this.monster.weakness.severity; - if (this.wearing("Devil's element")) { - severity *= 2; - } + const severity = this.monster.weakness.severity; const bonus = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPELLEMENT_BONUS, baseRoll, [severity, 100]); attackRoll = this.trackAdd(DetailKey.PLAYER_ACCURACY_SPELLEMENT, attackRoll, bonus); } @@ -885,8 +862,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { maxHit = Math.trunc((magicLevel * (92 + 64) + 320) / 640); } else if (this.wearing('Tecu salamander')) { maxHit = Math.trunc((magicLevel * (104 + 64) + 320) / 640); - } else if (this.wearing("Nature's reprisal")) { - maxHit = Math.max(0, Math.trunc(magicLevel / 3) - 2); } if (maxHit === 0) { @@ -955,11 +930,7 @@ export default class PlayerVsNPCCalc extends BaseCalc { const spellement = this.player.spell?.element; if (this.monster.weakness && spellement) { if (spellement === this.monster.weakness.element) { - let severity = this.monster.weakness.severity; - if (this.wearing("Devil's element")) { - severity *= 2; - } - maxHit += Math.trunc(baseMax * (severity / 100)); + maxHit += Math.trunc(baseMax * (this.monster.weakness.severity / 100)); } } @@ -974,17 +945,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { maxHit = this.trackFactor(DetailKey.MAX_HIT_TOME, maxHit, [11, 10]); } - if (this.hasLeaguesMastery('magic', MagicMastery.MAGIC_2)) { - const delay = this.getAttackSpeed(); - const factor = Math.max(0, Math.min(8, delay)); - maxHit = this.trackFactor(DetailKey.MAX_HIT_FOCUS_BLASTS, maxHit, [20 + factor, 20]); - } - - if (this.hasLeaguesMastery('magic', MagicMastery.MAGIC_6)) { - const factor = Math.min(10, Math.max(0, Math.trunc(this.monster.inputs.monsterCurrentHp / 100))); - maxHit = this.trackFactor(DetailKey.MAX_HIT_ADRENALINE_CHARGES, maxHit, [100 + factor, 100]); - } - return [minHit, maxHit]; } @@ -1045,62 +1005,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { return minMax; } - public getDisplayMax(): number { - if (this.hasLeaguesMastery('melee', MeleeMastery.MELEE_2)) { - return this.noInitSubCalc( - { - ...this.player, - leagues: { - ...this.player.leagues, - five: { - ...this.player.leagues.five, - melee: MeleeMastery.MELEE_1, - }, - }, - }, - this.monster, - ).getDisplayMax(); - } - - if (this.hasLeaguesMastery('ranged', RangedMastery.RANGED_2)) { - return this.noInitSubCalc( - { - ...this.player, - leagues: { - ...this.player.leagues, - five: { - ...this.player.leagues.five, - attackCount: 4, - }, - }, - }, - this.monster, - ).getDistribution().getMax(); - } - - return this.getDistribution().getMax(); - } - - public getHistogram(hideMisses: boolean = false): ReturnType { - if (this.hasLeaguesMastery('melee', MeleeMastery.MELEE_2)) { - return this.noInitSubCalc( - { - ...this.player, - leagues: { - ...this.player.leagues, - five: { - ...this.player.leagues.five, - melee: MeleeMastery.MELEE_1, - }, - }, - }, - this.monster, - ).getHistogram(hideMisses); - } - - return this.getDistribution().asHistogram(hideMisses); - } - /** * Get the max attack roll for this loadout, which is based on the player's current combat style */ @@ -1129,12 +1033,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { atkRoll = this.getPlayerMaxMagicAttackRoll(); } - const leagueData = this.player.leagues.five; - const maxMastery = Math.max(leagueData.melee, leagueData.ranged, leagueData.magic); - if (maxMastery >= 3) { - atkRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_LEAGUES_5_PASSIVE, atkRoll, [2, 1]); - } - return this.track(DetailKey.PLAYER_ACCURACY_ROLL_FINAL, atkRoll); } @@ -1143,10 +1041,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { return this.track(DetailKey.PLAYER_ACCURACY_FINAL, this.opts.overrides.accuracy); } - if (this.hasLeaguesMastery('ranged', RangedMastery.RANGED_6)) { - return this.track(DetailKey.PLAYER_ACCURACY_FINAL, 1.0); - } - if (VERZIK_P1_IDS.includes(this.monster.id) && this.wearing('Dawnbringer')) { this.track(DetailKey.PLAYER_ACCURACY_DAWNBRINGER, 1.0); return this.track(DetailKey.PLAYER_ACCURACY_FINAL, 1.0); @@ -1182,10 +1076,8 @@ export default class PlayerVsNPCCalc extends BaseCalc { hitChance = this.track(DetailKey.PLAYER_ACCURACY_BRIMSTONE, (0.75 * hitChance) + (0.25 * effectHitChance)); } - const fangAccuracy = this.isWearingFang() && this.player.style.type === 'stab'; - const drygoreAccuracy = this.wearing('Drygore blowpipe') && this.player.style.stance !== 'Manual Cast'; - if (fangAccuracy || drygoreAccuracy) { - if (fangAccuracy && TOMBS_OF_AMASCUT_MONSTER_IDS.includes(this.monster.id)) { + if (this.isWearingFang() && this.player.style.type === 'stab') { + if (TOMBS_OF_AMASCUT_MONSTER_IDS.includes(this.monster.id)) { hitChance = this.track(DetailKey.PLAYER_ACCURACY_FANG_TOA, 1 - (1 - hitChance) ** 2); } else { hitChance = this.track( @@ -1256,78 +1148,8 @@ export default class PlayerVsNPCCalc extends BaseCalc { const style = this.player.style.type; // standard linear - /* eslint-disable @typescript-eslint/no-shadow */ - const generateStandardDist = (acc: number, min: number, max: number): HitDistribution => { - // wrap this so we can nest it in echo dists - const rollDamageTwice = (acc: number, min: number, max: number): HitDistribution => { - // 75% chance to roll damage twice, take larger of two - const d = new HitDistribution([]); - const n = max - min + 1; - const hitProb = acc / n; - for (let i = min; i <= max; i++) { - d.addHit(new WeightedHit(hitProb * 0.75, [new Hitsplat(i)])); - for (let j = min; j <= max; j++) { - d.addHit(new WeightedHit((hitProb * 0.25) / n, [new Hitsplat(Math.max(i, j))])); - } - } - d.addHit(new WeightedHit(1 - acc, [Hitsplat.INACCURATE])); - return d.flatten(); - }; - - // const echo = (chance: number, chainN: number, maxChain: number): HitTransformer => (h) => { - // if (!h.accurate || chainN >= maxChain) { - // return HitDistribution.single(1.0, [h]); - // } - // - // const r = new HitDistribution([ - // new WeightedHit(1 - chance, [h]), - // ...HitDistribution.single(1.0, [h]) - // .scaleProbability(chance) - // .zip(rollDamageTwice(acc, min, max) - // .transform(echo(0.1, chainN + 1, maxChain))) - // .cumulative() - // .hits, - // ]); - // - // console.log(`Completed echo ${chainN} at ${global.performance.now()}`); - // return r; - // }; - if (this.hasLeaguesMastery('melee', MeleeMastery.MELEE_6)) { - const echoMax = Math.trunc(max / 2); - let echoDist = rollDamageTwice(acc * 0.2, min, echoMax); - for (let i = 1; i < 8; i++) { - echoDist = echoDist - .zip(rollDamageTwice(0.2 ** (i + 1), min, echoMax)) - .cumulative(); - } - return rollDamageTwice(acc, min, max) - .zip(echoDist) - .cumulative(); - } if (this.hasLeaguesMastery('melee', MeleeMastery.MELEE_2)) { - const echoDistSingle = rollDamageTwice(acc * 0.1, min, Math.trunc(max / 2)); - return rollDamageTwice(acc, min, max) - .zip(echoDistSingle) - .cumulative(); - } if (this.hasLeaguesMastery('melee', MeleeMastery.MELEE_1)) { - return rollDamageTwice(acc, min, max); - } - - if (this.hasLeaguesMastery('ranged', RangedMastery.RANGED_1)) { - // raise rolls below 30% to 30%, this is NOT the same as a min hit since it applies post-roll - const floor = Math.trunc(max * 3 / 10); - return HitDistribution.linear(acc, min, max).transform((h) => { - if (h.accurate && h.damage < floor) { - return HitDistribution.single(1.0, [new Hitsplat(floor, h.accurate)]); - } - return HitDistribution.single(1.0, [h]); - }); - } - - return HitDistribution.linear(acc, min, max); - }; - /* eslint-enable @typescript-eslint/no-shadow */ - - let dist = new AttackDistribution([generateStandardDist(acc, min, max)]); + const standardHitDist = HitDistribution.linear(acc, min, max); + let dist = new AttackDistribution([standardHitDist]); // Monsters that always die in one hit no matter what if (ONE_HIT_MONSTERS.includes(this.monster.id)) { @@ -1336,31 +1158,17 @@ export default class PlayerVsNPCCalc extends BaseCalc { ]); } - const maxHitMonster = (this.player.style.type === 'magic' && ALWAYS_MAX_HIT_MONSTERS.magic.includes(this.monster.id)) + // monsters that are always max hit no matter what + if ((this.player.style.type === 'magic' && ALWAYS_MAX_HIT_MONSTERS.magic.includes(this.monster.id)) || (this.isUsingMeleeStyle() && ALWAYS_MAX_HIT_MONSTERS.melee.includes(this.monster.id)) - || (this.player.style.type === 'ranged' && ALWAYS_MAX_HIT_MONSTERS.ranged.includes(this.monster.id)); - - if (this.hasLeaguesMastery('magic', MagicMastery.MAGIC_1)) { - const hasMagic6 = this.hasLeaguesMastery('magic', MagicMastery.MAGIC_6); - const critThreshold = Math.trunc(max * 9 / 10); - const critMax = Math.trunc(max * 3 / 2); - dist = dist.transform((h) => { - // magic 1 & 6 have weird complications with forced max hits - const newDmg = h.damage >= critThreshold ? Math.trunc(h.damage * 3 / 2) : h.damage; - const newMax = h.damage >= critThreshold ? critMax : max; - if (maxHitMonster || (hasMagic6 && newMax >= this.monster.inputs.monsterCurrentHp)) { - return HitDistribution.single(1.0, [new Hitsplat(newMax)]); - } - return HitDistribution.single(1.0, [new Hitsplat(newDmg)]); - }, { transformInaccurate: maxHitMonster }); - } else if (maxHitMonster) { + || (this.player.style.type === 'ranged' && ALWAYS_MAX_HIT_MONSTERS.ranged.includes(this.monster.id))) { return new AttackDistribution([HitDistribution.single(1.0, [new Hitsplat(max)])]); } if (style === 'ranged' && this.wearing('Tonalztics of ralos') && this.player.equipment.weapon?.version === 'Charged') { // roll two independent hits if (!this.opts.usingSpecialAttack) { - dist = new AttackDistribution([generateStandardDist(acc, min, max), generateStandardDist(acc, min, max)]); + dist = new AttackDistribution([standardHitDist, standardHitDist]); } else { // the defence reduction from the first hit applies to the second hit, // so we need a full subcalc with the new defence value to determine the dist @@ -1375,10 +1183,10 @@ export default class PlayerVsNPCCalc extends BaseCalc { }, })).getHitChance(); - const loweredDefHitDist = generateStandardDist(loweredDefHitAccuracy, min, max); + const loweredDefHitDist = HitDistribution.linear(loweredDefHitAccuracy, min, max); dist = dist.transform((firstHit) => { const firstHitDist = HitDistribution.single(1.0, [firstHit]); - const secondHitDist = firstHit.accurate ? loweredDefHitDist : generateStandardDist(acc, min, max); + const secondHitDist = firstHit.accurate ? loweredDefHitDist : standardHitDist; return firstHitDist.zip(secondHitDist); }); } @@ -1387,14 +1195,14 @@ export default class PlayerVsNPCCalc extends BaseCalc { if (this.isUsingMeleeStyle() && this.wearing('Gadderhammer') && mattrs.includes(MonsterAttribute.SHADE)) { dist = new AttackDistribution([ new HitDistribution([ - ...generateStandardDist(acc, min, max).scaleProbability(0.95).scaleDamage(5, 4).hits, - ...generateStandardDist(acc, min, max).scaleProbability(0.05).scaleDamage(2).hits, + ...standardHitDist.scaleProbability(0.95).scaleDamage(5, 4).hits, + ...standardHitDist.scaleProbability(0.05).scaleDamage(2).hits, ]), ]); } if (style === 'ranged' && this.wearing('Dark bow')) { - dist = new AttackDistribution([generateStandardDist(acc, min, max), generateStandardDist(acc, min, max)]); + dist = new AttackDistribution([standardHitDist, standardHitDist]); if (this.opts.usingSpecialAttack) { dist = dist.transform(flatLimitTransformer(48, min)); } @@ -1411,32 +1219,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { } } - if (this.wearing('Thunder khopesh')) { - if (this.opts.usingSpecialAttack) { - // two melee hits and iff at least one hits, add a bolt hit - dist = new AttackDistribution([ - generateStandardDist(acc, min, max), - generateStandardDist(acc, min, max), - HitDistribution.linear(1 - ((1 - acc) ** 2), min, max), - ]); - } else { - const boltDist = HitDistribution.linear(1.0, 0, Math.trunc(max / 2)); - dist = dist.transform((khopeshSplat) => new HitDistribution([ - new WeightedHit(0.8, [khopeshSplat]), - ...HitDistribution.single(1.0, [khopeshSplat]) - .scaleProbability(0.2) - .zip(boltDist) - .hits, - ])); - } - } - - if (this.wearing('Sunlight spear') && this.opts.usingSpecialAttack) { - // todo I assumed this was post-roll, is it? - const factor = this.player.bonuses.prayer * 3; - dist = dist.transform(multiplyTransformer(100 + factor, 100)); - } - if (this.opts.usingSpecialAttack && this.wearing(['Dragon halberd', 'Crystal halberd']) && this.monster.size > 1) { const secondHitAttackRoll = Math.trunc(this.getMaxAttackRoll() * 3 / 4); const secondHitAcc = this.noInitSubCalc( @@ -1445,7 +1227,7 @@ export default class PlayerVsNPCCalc extends BaseCalc { { overrides: { attackRoll: secondHitAttackRoll } }, ).getHitChance(); - dist = new AttackDistribution([generateStandardDist(acc, min, max), generateStandardDist(secondHitAcc, min, max)]); + dist = new AttackDistribution([standardHitDist, HitDistribution.linear(secondHitAcc, min, max)]); } // simple multi-hit specs @@ -1458,7 +1240,7 @@ export default class PlayerVsNPCCalc extends BaseCalc { } if (hitCount !== 1) { - dist = new AttackDistribution(Array(hitCount).fill(generateStandardDist(acc, min, max))); + dist = new AttackDistribution(Array(hitCount).fill(standardHitDist)); } } @@ -1467,22 +1249,20 @@ export default class PlayerVsNPCCalc extends BaseCalc { } if (this.isUsingMeleeStyle() && this.isWearingVeracs()) { - const effectChance = this.wearing('Gloves of the damned') ? 0.5 : 0.25; dist = new AttackDistribution([ new HitDistribution([ - ...generateStandardDist(acc, min, max).scaleProbability(1 - effectChance).hits, - ...generateStandardDist(1.0, 1, max + 1).scaleProbability(effectChance).hits, + ...standardHitDist.scaleProbability(0.75).hits, + ...HitDistribution.linear(1.0, 1, max + 1).scaleProbability(0.25).hits, ]), ]); } if (style === 'ranged' && this.isWearingKarils()) { // 25% chance to deal a second hitsplat at half the damage of the first (flat, not rolled) - const effectChance = this.wearing('Gloves of the damned') ? 0.5 : 0.25; dist = dist.transform( (h) => new HitDistribution([ - new WeightedHit(1 - effectChance, [h]), - new WeightedHit(effectChance, [h, new Hitsplat(Math.trunc(h.damage / 2))]), + new WeightedHit(0.75, [h]), + new WeightedHit(0.25, [h, new Hitsplat(Math.trunc(h.damage / 2))]), ]), { transformInaccurate: false }, ); @@ -1492,14 +1272,14 @@ export default class PlayerVsNPCCalc extends BaseCalc { const hits: HitDistribution[] = []; for (let i = 0; i < Math.min(Math.max(this.monster.size, 1), 3); i++) { const splatMax = Math.trunc(max / (2 ** i)); - hits.push(generateStandardDist(acc, Math.min(min, splatMax), splatMax)); + hits.push(HitDistribution.linear(acc, Math.min(min, splatMax), splatMax)); } dist = new AttackDistribution(hits); } if (this.isUsingMeleeStyle() && this.wearing('Dual macuahuitl')) { - const secondHit = generateStandardDist(acc, min, max - Math.trunc(max / 2)); - const firstHit = new AttackDistribution([generateStandardDist(acc, min, Math.trunc(max / 2))]); + const secondHit = HitDistribution.linear(acc, 0, max - Math.trunc(max / 2)); + const firstHit = new AttackDistribution([HitDistribution.linear(acc, 0, Math.trunc(max / 2))]); dist = firstHit.transform( (h) => { if (h.accurate) { @@ -1512,16 +1292,16 @@ export default class PlayerVsNPCCalc extends BaseCalc { if (this.isUsingMeleeStyle() && this.isWearingTwoHitWeapon()) { dist = new AttackDistribution([ - generateStandardDist(acc, min, Math.trunc(max / 2)), - generateStandardDist(acc, min, max - Math.trunc(max / 2)), + HitDistribution.linear(acc, 0, Math.trunc(max / 2)), + HitDistribution.linear(acc, 0, max - Math.trunc(max / 2)), ]); } if (this.isUsingMeleeStyle() && this.isWearingKeris() && mattrs.includes(MonsterAttribute.KALPHITE)) { dist = new AttackDistribution([ new HitDistribution([ - ...generateStandardDist(acc, min, max).scaleProbability(50.0 / 51.0).hits, - ...generateStandardDist(acc, min, max).scaleProbability(1.0 / 51.0).scaleDamage(3).hits, + ...standardHitDist.scaleProbability(50.0 / 51.0).hits, + ...standardHitDist.scaleProbability(1.0 / 51.0).scaleDamage(3).hits, ]), ]); } @@ -1555,11 +1335,10 @@ export default class PlayerVsNPCCalc extends BaseCalc { } if (this.player.style.type === 'magic' && this.isWearingAhrims()) { - const effectChance = this.wearing('Gloves of the damned') ? 0.5 : 0.25; dist = dist.transform( (h) => new HitDistribution([ - new WeightedHit(1 - effectChance, [h]), - new WeightedHit(effectChance, [new Hitsplat(Math.trunc(h.damage * 13 / 10), h.accurate)]), + new WeightedHit(0.75, [h]), + new WeightedHit(0.25, [new Hitsplat(Math.trunc(h.damage * 13 / 10), h.accurate)]), ]), ); } @@ -1573,13 +1352,9 @@ export default class PlayerVsNPCCalc extends BaseCalc { } if (this.isUsingMeleeStyle() && this.isWearingDharok()) { - const baseHp = this.player.skills.hp; - const currHp = this.player.skills.hp + this.player.boosts.hp; - let factor = (baseHp - currHp) * baseHp; - if (this.wearing('Gloves of the damned')) { - factor *= 2; - } - dist = dist.scaleDamage(10000 + factor, 10000); + const newMax = this.player.skills.hp; + const curr = this.player.skills.hp + this.player.boosts.hp; + dist = dist.scaleDamage(10000 + (newMax - curr) * newMax, 10000); } if (this.isUsingMeleeStyle() && this.isWearingBerserkerNecklace() && this.isWearingTzhaarWeapon()) { @@ -1618,14 +1393,12 @@ export default class PlayerVsNPCCalc extends BaseCalc { // bolt effects const boltContext: BoltContext = { - minHit: min, maxHit: max, rangedLvl: this.player.skills.ranged + this.player.boosts.ranged, zcb: this.wearing('Zaryte crossbow'), spec: this.opts.usingSpecialAttack, kandarinDiary: this.player.buffs.kandarinDiary, monster: this.monster, - generateStandardDist, }; if (this.player.style.type === 'ranged' && this.player.equipment.weapon?.name.includes('rossbow')) { if (this.wearing(['Opal bolts (e)', 'Opal dragon bolts (e)'])) { @@ -1696,13 +1469,8 @@ export default class PlayerVsNPCCalc extends BaseCalc { } if (this.monster.name === 'Zulrah') { - // during leagues this cap has been removed. - // we're just using the presence of any league mastery at all to check - const maxMastery = Math.max(this.player.leagues.five.melee, this.player.leagues.five.ranged, this.player.leagues.five.magic); - if (maxMastery === 0) { - // https://twitter.com/JagexAsh/status/1745852774607183888 - dist = dist.transform(cappedRerollTransformer(50, 5, 45)); - } + // https://twitter.com/JagexAsh/status/1745852774607183888 + dist = dist.transform(cappedRerollTransformer(50, 5, 45)); } if (this.monster.name === 'Fragment of Seren') { // https://twitter.com/JagexAsh/status/1375037874559721474 @@ -1808,27 +1576,15 @@ export default class PlayerVsNPCCalc extends BaseCalc { if (IMMUNE_TO_MAGIC_DAMAGE_NPC_IDS.includes(monsterId) && styleType === 'magic') { return true; } - if (IMMUNE_TO_RANGED_DAMAGE_NPC_IDS.includes(monsterId) && styleType === 'ranged' - && this.monster.name !== 'Dusk (Echo)') { + if (IMMUNE_TO_RANGED_DAMAGE_NPC_IDS.includes(monsterId) && styleType === 'ranged') { return true; } if (IMMUNE_TO_MELEE_DAMAGE_NPC_IDS.includes(monsterId) && this.isUsingMeleeStyle()) { - if (ZULRAH_IDS.includes(monsterId)) { - // during leagues this immunity has been removed. - // we're just using the presence of any league mastery at all to check - // but not bothering with a range >= 2 check since we don't track that - const maxMastery = Math.max(this.player.leagues.five.melee, this.player.leagues.five.ranged, this.player.leagues.five.magic); - if (maxMastery === 0) { - return true; - } - } else { - return true; - } + return true; } if (IMMUNE_TO_NON_SALAMANDER_MELEE_DAMAGE_NPC_IDS.includes(monsterId) && this.isUsingMeleeStyle() - && this.player.equipment.weapon?.category !== EquipmentCategory.SALAMANDER - && this.player.equipment.weapon?.category !== EquipmentCategory.MULTISTYLE) { + && this.player.equipment.weapon?.category !== EquipmentCategory.SALAMANDER) { return true; } if (mattrs.includes(MonsterAttribute.VAMPYRE_3) && !this.wearingVampyrebane(MonsterAttribute.VAMPYRE_3)) { @@ -1888,30 +1644,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { * Returns the expected damage per tick, based on the player's attack speed. */ public getDpt() { - if (this.hasLeaguesMastery('ranged', RangedMastery.RANGED_2)) { - const subCalc = (n: number) => this.noInitSubCalc( - { - ...this.player, - leagues: { - ...this.player.leagues, - five: { - ...this.player.leagues.five, - attackCount: n, - }, - }, - }, - this.monster, - ); - - return ( - subCalc(0).getExpectedDamage() - + subCalc(1).getExpectedDamage() - + subCalc(2).getExpectedDamage() - + subCalc(3).getExpectedDamage() - + subCalc(4).getExpectedDamage() - ) / (5 * this.getExpectedAttackSpeed()); - } - return this.getExpectedDamage() / this.getExpectedAttackSpeed(); } @@ -1977,23 +1709,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { * Returns the average time-to-kill (in seconds) calculation. */ public getTtk() { - if (this.hasLeaguesMastery('magic', MagicMastery.MAGIC_6) || this.hasLeaguesMastery('ranged', RangedMastery.RANGED_2)) { - // Use slower TTK calculation - const calc = this.noInitSubCalc(this.player, this.monster); - const ttkDist = calc.getTtkDistribution(); - let accum = 0.0; - let probAccum = 0.0; - for (const [k, v] of ttkDist.entries()) { - probAccum += v; - accum += k * v; - } - - if (probAccum < (1 - TTK_DIST_EPSILON)) { - return undefined; - } - return (accum + this.getExpectedAttackSpeed() - 1) * SECONDS_PER_TICK; - } - return this.getHtk() * this.getExpectedAttackSpeed() * SECONDS_PER_TICK; } @@ -2003,11 +1718,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { const ticksPerSpec = this.getAttackSpeed() * this.player.buffs.soulreaperStacks; return this.getDps() * this.getExpectedAttackSpeed() / ticksPerSpec; } - if (this.wearing('Sunlight spear')) { - // assumes using spec every time you reach 7 stacks - const ticksPerSpec = this.getAttackSpeed() * 7; - return this.getDps() * this.getExpectedAttackSpeed() / ticksPerSpec; - } const specCost = this.getSpecCost(); if (!specCost) { @@ -2065,31 +1775,7 @@ export default class PlayerVsNPCCalc extends BaseCalc { // dist attack-on-specific-tick probabilities // todo thralls, append here - const dists: DelayedHit[][] = [playerDist]; - - const hasRepeatShooter = this.hasLeaguesMastery('ranged', RangedMastery.RANGED_2); - const repeatShooterDists: DelayedHit[][] = [playerDist]; - if (hasRepeatShooter) { - // todo(leagues): this depends on getWeaponDelayProvider offsetting the attack timings - // since the implementation does not support same-tick attacks - for (let i = 1; i < 5; i++) { - repeatShooterDists.push( - this.noInitSubCalc({ - ...this.player, - leagues: { - ...this.player.leagues, - five: { - ...this.player.leagues.five, - attackCount: (this.player.leagues.five.attackCount + i) % 5, - }, - }, - }, this.monster) - .getDistribution() - .zipped - .withProbabilisticDelays(this.getWeaponDelayProvider()), - ); - } - } + const dists = [playerDist]; const attackOnTick = dists.map(() => new Float64Array(iterMax + 1)); attackOnTick.forEach((arr) => { @@ -2136,20 +1822,12 @@ export default class PlayerVsNPCCalc extends BaseCalc { // 3. for each possible hp value, const hps = tickHps[tick]; for (const [hp, hpProb] of hps.entries()) { + // this is a bit of a hack, but idk if there's a better way + const currDist: DelayedHit[] = recalcDistOnHp ? hpHitDists[hp] : dist; if (hpProb === 0) { continue; } - // this is a bit of a hack, but idk if there's a better way - let currDist: DelayedHit[]; // todo(leagues): support dynamic hp AND repeat shooter? - if (recalcDistOnHp) { - currDist = hpHitDists[hp]; - } else if (hasRepeatShooter) { - currDist = repeatShooterDists[Math.trunc(tick / speed) % 5]; - } else { - currDist = dist; - } - // 4. for each damage amount possible, for (const [wh, delay] of currDist) { const dmgProb = wh.probability; @@ -2185,15 +1863,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { return baseDist; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [baseMin, baseMax] = this.getMinAndMax(); - if (this.hasLeaguesMastery('magic', MagicMastery.MAGIC_6)) { - const critMax = Math.trunc(baseMax * 3 / 2); - if (baseMax && hp > critMax) { - return baseDist; - } - } - // a special case for optimization, ruby bolts only change dps under 500 hp // so for high health targets, avoid recomputing dist until then if (this.player.style.type === 'ranged' @@ -2254,9 +1923,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { if (this.wearing('Keris partisan of the sun') && TOMBS_OF_AMASCUT_MONSTER_IDS.includes(monster.id)) { return true; } - if (this.hasLeaguesMastery('magic', MagicMastery.MAGIC_6)) { - return true; - } return false; } @@ -2295,10 +1961,6 @@ export default class PlayerVsNPCCalc extends BaseCalc { ? FeatureStatus.NOT_APPLICABLE : FeatureStatus.IMPLEMENTED; } - if (this.wearing('Sunlight spear')) { - // has no spec cost since it uses stacks - return FeatureStatus.IMPLEMENTED; - } if (PARTIALLY_IMPLEMENTED_SPECS.includes(weaponName)) { return FeatureStatus.PARTIALLY_IMPLEMENTED; @@ -2328,20 +1990,4 @@ export default class PlayerVsNPCCalc extends BaseCalc { return null; } } - - private hasLeaguesMastery< - U extends keyof Pick, - >(key: U, mastery: LeaguesState[U]) { - if (key === 'melee' && !this.isUsingMeleeStyle()) { - return false; - } - if (key === 'ranged' && this.player.style.type !== 'ranged') { - return false; - } - if (key === 'magic' && this.player.style.type !== 'magic') { - return false; - } - - return this.player.leagues.five[key] >= mastery; - } } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index eaecfefe..a83f5d2b 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -3,7 +3,6 @@ import { CombatStyleStance } from '@/types/PlayerCombatStyle'; export const BLOWPIPE_IDS: number[] = [ 12926, // regular 28688, // blazing - 30374, // drygore ]; // The maximum number of loadouts that users can have. Do not lower it, else it will cause share link issues. @@ -423,19 +422,3 @@ export const HUEYCOATL_IDS = [ export const HUEYCOATL_PHASES = ['Without Pillar', 'With Pillar']; export const HUEYCOATL_PHASE_IDS = [...HUEYCOATL_HEAD_IDS, ...HUEYCOATL_TAIL_IDS]; // body can't receive pillar buff HUEYCOATL_PHASE_IDS.forEach((id) => { MONSTER_PHASES_BY_ID[id] = HUEYCOATL_PHASES; }); - -export const LEAGUES_FIVE_MOCK_ID_MAPPINGS: { [k: number]: number } = { - 1000000: 30367, // the dogsword - 1000001: 30369, // sunlight spear - 1000002: 30371, // devil's element - 1000003: 30373, // drygore blowpipe#empty - 1000004: 30374, // drygore blowpipe#charged - 1000005: 30376, // amulet of the monarchs - 1000006: 30378, // emperor ring - 1000007: 30380, // gloves of the damned - 1000008: 30382, // thousand-dragon ward - 1000009: 30384, // crystal blessing - 1000010: 30386, // sunlit bracers - 1000012: 30390, // nature's reprisal#charged - 1000013: 30392, // nature's reprisal#uncharged -}; diff --git a/src/lib/dists/bolts.ts b/src/lib/dists/bolts.ts index e3b90e3e..263171ec 100644 --- a/src/lib/dists/bolts.ts +++ b/src/lib/dists/bolts.ts @@ -7,13 +7,11 @@ import { Monster } from '@/types/Monster'; export interface BoltContext { rangedLvl: number; - minHit: number; maxHit: number; zcb: boolean; spec: boolean; kandarinDiary: boolean; monster: Monster; - generateStandardDist: (acc: number, min: number, max: number) => HitDistribution; } export type BoltTransformer = (ctx: BoltContext) => HitTransformer; @@ -54,7 +52,7 @@ export const diamondBolts: BoltTransformer = (ctx) => { const chance = 0.1 * kandarinFactor(ctx); const effectMax = Math.trunc(maxHit * (zcb ? 126 : 115) / 100); - const effectDist = ctx.generateStandardDist(1.0, ctx.minHit, effectMax); + const effectDist = HitDistribution.linear(1.0, 0, effectMax); return (h) => { if (h.accurate && zcb && spec) { return effectDist; @@ -93,7 +91,7 @@ export const onyxBolts: BoltTransformer = (ctx) => { const chance = 0.11 * kandarinFactor(ctx); const effectMax = Math.trunc(maxHit * (zcb ? 132 : 120) / 100); - const effectDist = ctx.generateStandardDist(1.0, ctx.minHit, effectMax); + const effectDist = HitDistribution.linear(1.0, 0, effectMax); return (h) => { if (!h.accurate) { return new HitDistribution([new WeightedHit(1.0, [h])]); diff --git a/src/public/img/league/magic_1.png b/src/public/img/league/magic_1.png deleted file mode 100644 index b254fd46..00000000 Binary files a/src/public/img/league/magic_1.png and /dev/null differ diff --git a/src/public/img/league/magic_2.png b/src/public/img/league/magic_2.png deleted file mode 100644 index d2f9c4b6..00000000 Binary files a/src/public/img/league/magic_2.png and /dev/null differ diff --git a/src/public/img/league/magic_3.png b/src/public/img/league/magic_3.png deleted file mode 100644 index 5d707213..00000000 Binary files a/src/public/img/league/magic_3.png and /dev/null differ diff --git a/src/public/img/league/magic_4.png b/src/public/img/league/magic_4.png deleted file mode 100644 index 05ff8c79..00000000 Binary files a/src/public/img/league/magic_4.png and /dev/null differ diff --git a/src/public/img/league/magic_5.png b/src/public/img/league/magic_5.png deleted file mode 100644 index 0ee87065..00000000 Binary files a/src/public/img/league/magic_5.png and /dev/null differ diff --git a/src/public/img/league/magic_6.png b/src/public/img/league/magic_6.png deleted file mode 100644 index 761eb21d..00000000 Binary files a/src/public/img/league/magic_6.png and /dev/null differ diff --git a/src/public/img/league/melee_1.png b/src/public/img/league/melee_1.png deleted file mode 100644 index fede2cab..00000000 Binary files a/src/public/img/league/melee_1.png and /dev/null differ diff --git a/src/public/img/league/melee_2.png b/src/public/img/league/melee_2.png deleted file mode 100644 index d778fc5d..00000000 Binary files a/src/public/img/league/melee_2.png and /dev/null differ diff --git a/src/public/img/league/melee_3.png b/src/public/img/league/melee_3.png deleted file mode 100644 index bdecc34d..00000000 Binary files a/src/public/img/league/melee_3.png and /dev/null differ diff --git a/src/public/img/league/melee_4.png b/src/public/img/league/melee_4.png deleted file mode 100644 index 6de6dcf0..00000000 Binary files a/src/public/img/league/melee_4.png and /dev/null differ diff --git a/src/public/img/league/melee_5.png b/src/public/img/league/melee_5.png deleted file mode 100644 index 23147bc8..00000000 Binary files a/src/public/img/league/melee_5.png and /dev/null differ diff --git a/src/public/img/league/melee_6.png b/src/public/img/league/melee_6.png deleted file mode 100644 index a551db0e..00000000 Binary files a/src/public/img/league/melee_6.png and /dev/null differ diff --git a/src/public/img/league/ranged_1.png b/src/public/img/league/ranged_1.png deleted file mode 100644 index 534b0d5e..00000000 Binary files a/src/public/img/league/ranged_1.png and /dev/null differ diff --git a/src/public/img/league/ranged_2.png b/src/public/img/league/ranged_2.png deleted file mode 100644 index e7bd080e..00000000 Binary files a/src/public/img/league/ranged_2.png and /dev/null differ diff --git a/src/public/img/league/ranged_3.png b/src/public/img/league/ranged_3.png deleted file mode 100644 index b126cdb8..00000000 Binary files a/src/public/img/league/ranged_3.png and /dev/null differ diff --git a/src/public/img/league/ranged_4.png b/src/public/img/league/ranged_4.png deleted file mode 100644 index f75a71e1..00000000 Binary files a/src/public/img/league/ranged_4.png and /dev/null differ diff --git a/src/public/img/league/ranged_5.png b/src/public/img/league/ranged_5.png deleted file mode 100644 index 00c5bffe..00000000 Binary files a/src/public/img/league/ranged_5.png and /dev/null differ diff --git a/src/public/img/league/ranged_6.png b/src/public/img/league/ranged_6.png deleted file mode 100644 index 4ebac19a..00000000 Binary files a/src/public/img/league/ranged_6.png and /dev/null differ diff --git a/src/state.tsx b/src/state.tsx index c4b8905a..81afe9af 100644 --- a/src/state.tsx +++ b/src/state.tsx @@ -1,3 +1,5 @@ +// noinspection FallThroughInSwitchStatementJS + import { autorun, IReactionDisposer, IReactionPublic, makeAutoObservable, reaction, toJS, } from 'mobx'; @@ -26,7 +28,6 @@ import { fetchPlayerSkills, fetchShortlinkData, getCombatStylesForCategory, - keys, PotionMap, } from '@/utils'; import { ComputeBasicRequest, ComputeReverseRequest, WorkerRequestType } from '@/worker/CalcWorkerTypes'; @@ -36,10 +37,8 @@ import { CalcWorker } from '@/worker/CalcWorker'; import { spellByName } from '@/types/Spell'; import { DEFAULT_ATTACK_SPEED, - LEAGUES_FIVE_MOCK_ID_MAPPINGS, NUMBER_OF_LOADOUTS, } from '@/lib/constants'; -import { defaultLeaguesState } from '@/lib/LeaguesV'; import { EquipmentCategory } from './enums/EquipmentCategory'; import { ARM_PRAYERS, @@ -133,9 +132,6 @@ export const generateEmptyPlayer = (name?: string): Player => ({ usingSunfireRunes: false, }, spell: null, - leagues: { - five: defaultLeaguesState(), - }, }); export const parseLoadoutsFromImportedData = (data: ImportableData) => data.loadouts.map((loadout, i) => { @@ -470,46 +466,24 @@ class GlobalState implements State { case 1: data.monster.inputs.phase = data.monster.inputs.tormentedDemonPhase; - case 2: + case 2: // reserved: used during leagues 5 + case 3: // reserved: used during leagues 5 + case 4: // reserved: used during leagues 5 + case 5: data.loadouts.forEach((l) => { - if (l.equipment?.weapon?.id === 1000012) { // old mock version of Nature's reprisal - l.equipment.weapon.category = EquipmentCategory.MULTISTYLE; - } - }); - - case 3: - data.loadouts.forEach((l) => { - if (!l.leagues?.five) { - l.leagues = { five: defaultLeaguesState() }; - } - }); - - case 4: - data.loadouts.forEach((l, ix) => { - if (l.equipment) { - for (const slot of keys(l.equipment)) { - const eq = l.equipment[slot]; - if (!eq?.id) { - continue; - } - - const newId = LEAGUES_FIVE_MOCK_ID_MAPPINGS[eq.id]; - if (newId) { - eq.id = newId; - console.info('mock id migration', { - ix, - slot, - oldId: eq.id, - newId, - }); - } - } + /* eslint-disable @typescript-eslint/dot-notation */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + if ((l as any)['leagues']) { + delete (l as any)['leagues']; } + /* eslint-enable @typescript-eslint/dot-notation */ + /* eslint-enable @typescript-eslint/no-explicit-any */ }); default: } /* eslint-enable no-fallthrough */ + console.debug('IMPORT | ', data); if (data.monster) { let newMonster: PartialDeep = {}; diff --git a/src/types/Player.ts b/src/types/Player.ts index c29d24e3..fdbf184a 100644 --- a/src/types/Player.ts +++ b/src/types/Player.ts @@ -3,7 +3,6 @@ import { Prayer } from '@/enums/Prayer'; import Potion from '@/enums/Potion'; import { Spell } from '@/types/Spell'; import { PlayerCombatStyle } from '@/types/PlayerCombatStyle'; -import { LeaguesState } from '@/lib/LeaguesV'; export interface PlayerSkills { atk: number; @@ -144,8 +143,4 @@ export interface Player extends EquipmentStats { usingSunfireRunes: boolean; }; spell: Spell | null; - - leagues: { - five: LeaguesState, - }; } diff --git a/src/types/PlayerCombatStyle.ts b/src/types/PlayerCombatStyle.ts index f8b489ca..fcd0897b 100644 --- a/src/types/PlayerCombatStyle.ts +++ b/src/types/PlayerCombatStyle.ts @@ -37,7 +37,6 @@ export function getRangedDamageType(category: EquipmentCategory): RangedDamageTy return 'heavy'; case EquipmentCategory.SALAMANDER: - case EquipmentCategory.MULTISTYLE: return 'mixed'; default: diff --git a/src/types/State.ts b/src/types/State.ts index cf90da3c..ae59de81 100644 --- a/src/types/State.ts +++ b/src/types/State.ts @@ -99,7 +99,7 @@ export interface Calculator { * or any of its subproperties, are updated in a non-backwards-compatible manner, * or also in any manner that could affect the migrations required on load. */ -export const IMPORT_VERSION = 5 as const; +export const IMPORT_VERSION = 6 as const; /** * This is the state that can be exported and imported (through shortlinks). diff --git a/src/utils.ts b/src/utils.ts index b7b7cb01..1f7a7f41 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -552,11 +552,6 @@ export const CombatStyleMap: { [k in EquipmentCategory]: { [k: string]: { image: Flare: { image: '290' }, Blaze: { image: '291' }, }, - [EquipmentCategory.MULTISTYLE]: { - Melee: { image: '289' }, - Ranged: { image: '290' }, - Magic: { image: '291' }, - }, [EquipmentCategory.DAGGER]: {}, [EquipmentCategory.NONE]: { Punch: { image: '247' }, @@ -621,13 +616,6 @@ export const getCombatStylesForCategory = (style: EquipmentCategory): PlayerComb { name: 'Block', type: null, stance: null }, ]; break; - case EquipmentCategory.MULTISTYLE: - ret = [ - { name: 'Melee', type: 'stab', stance: 'Aggressive' }, - { name: 'Ranged', type: 'ranged', stance: 'Rapid' }, - { name: 'Magic', type: 'magic', stance: 'Defensive' }, - ]; - break; case EquipmentCategory.PARTISAN: ret = [ { name: 'Stab', type: 'stab', stance: 'Accurate' }, diff --git a/src/worker/worker.ts b/src/worker/worker.ts index b2b9f723..b29cfbca 100644 --- a/src/worker/worker.ts +++ b/src/worker/worker.ts @@ -36,22 +36,22 @@ const computePvMValues: Handler = async (data) res.push({ npcDefRoll: calc.getNPCDefenceRoll(), - maxHit: calc.getDisplayMax(), + maxHit: calc.getDistribution().getMax(), expectedHit: calc.getDistribution().getExpectedDamage(), maxAttackRoll: calc.getMaxAttackRoll(), accuracy: calc.getHitChance(), dps: calc.getDps(), ttk: calc.getTtk(), - hitDist: calc.getHistogram(calcOpts.hitDistHideMisses), + hitDist: calc.getDistribution().asHistogram(calcOpts.hitDistHideMisses), details: calc.details, userIssues: calc.userIssues, specAccuracy: specCalc?.getHitChance(), - specMaxHit: specCalc?.getDisplayMax(), + specMaxHit: specCalc?.getMax(), specExpected: specCalc?.getExpectedDamage(), specMomentDps: specCalc?.getDps(), specFullDps: specCalc?.getSpecDps(), - specHitDist: specCalc?.getHistogram(calcOpts.hitDistHideMisses), + specHitDist: specCalc?.getDistribution().asHistogram(calcOpts.hitDistHideMisses), specDetails: specCalc?.details, });