Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some of the unimplemented specs #609

Merged
merged 1 commit into from
Feb 19, 2025
Merged
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
6 changes: 5 additions & 1 deletion src/lib/BaseCalc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,10 @@ export default class BaseCalc {
}
}

protected isAmmoInvalid(): boolean {
return ammoApplicability(this.player.equipment.weapon?.id, this.player.equipment.ammo?.id) === AmmoApplicability.INVALID;
}

protected addIssue(type: UserIssueType, message: string) {
this.userIssues.push({ type, message, loadout: this.opts.loadoutName });
}
Expand Down Expand Up @@ -664,7 +668,7 @@ export default class BaseCalc {
};
}

if (this.player.style.stance !== 'Manual Cast' && ammoApplicability(eq.weapon?.id, eq.ammo?.id) === AmmoApplicability.INVALID) {
if (this.player.style.stance !== 'Manual Cast' && this.isAmmoInvalid()) {
if (eq.ammo?.name) {
this.addIssue(UserIssueType.EQUIPMENT_WRONG_AMMO, 'This ammo does not work with your current weapon.');
} else {
Expand Down
14 changes: 14 additions & 0 deletions src/lib/Equipment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,13 @@ export const calculateEquipmentBonusesFromGear = (player: Player, monster: Monst
export const WEAPON_SPEC_COSTS: { [canonicalName: string]: number } = {
'Abyssal dagger': 25,
'Dragon dagger': 25,
'Dragon longsword': 25,
'Dragon mace': 25,
"Osmumten's fang": 25,
"Osmumten's fang (or)": 25,
'Dual macuahuitl': 25,
'Scorching bow': 25,
'Dragon knife': 25,
'Purging staff': 25,

'Dawnbringer': 30,
Expand All @@ -382,6 +385,8 @@ export const WEAPON_SPEC_COSTS: { [canonicalName: string]: number } = {
'Magic longbow': 35,
'Magic comp bow': 35,

'Dragon sword': 40,

'Elder maul': 50,
'Dragon warhammer': 50,
'Bandos godsword': 50,
Expand All @@ -401,12 +406,21 @@ export const WEAPON_SPEC_COSTS: { [canonicalName: string]: number } = {
'Armadyl godsword': 50,
'Zamorak godsword': 50,
'Abyssal bludgeon': 50,
'Abyssal whip': 50,

'Magic shortbow': 55,
'Dark bow': 55,
'Eldritch nightmare staff': 55,
'Volatile nightmare staff': 55,
'Dragon scimitar': 55,

'Heavy ballista': 65,
'Light ballista': 65,
"Saradomin's blessed sword": 65,

'Zaryte crossbow': 75,

'Saradomin sword': 100,
'Seercull': 100,
};
/* eslint-enable quote-props */
79 changes: 43 additions & 36 deletions src/lib/PlayerVsNPCCalc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ import {
import { EquipmentCategory } from '@/enums/EquipmentCategory';
import { DetailKey } from '@/lib/CalcDetails';
import { Factor, MinMax } from '@/lib/Math';
import {
AmmoApplicability, ammoApplicability, calculateAttackSpeed, WEAPON_SPEC_COSTS,
} from '@/lib/Equipment';
import { calculateAttackSpeed, WEAPON_SPEC_COSTS } from '@/lib/Equipment';
import BaseCalc, { CalcOpts, InternalOpts } from '@/lib/BaseCalc';
import { scaleMonster, scaleMonsterHpOnly } from '@/lib/MonsterScaling';
import { CombatStyleType, getRangedDamageType } from '@/types/PlayerCombatStyle';
Expand All @@ -72,7 +70,6 @@ const PARTIALLY_IMPLEMENTED_SPECS: string[] = [
// Some entries are intentionally omitted as they are not dps-related (e.g. dragon skilling tools, ivandis flail, dbaxe)
const UNIMPLEMENTED_SPECS: string[] = [
'Abyssal tentacle',
'Abyssal whip',
'Ancient mace',
'Armadyl crossbow',
'Barrelchest anchor',
Expand All @@ -85,23 +82,13 @@ const UNIMPLEMENTED_SPECS: string[] = [
'Dragon 2h sword',
'Dragon crossbow',
'Dragon hasta',
'Dragon knife',
'Dragon longsword',
'Dragon mace',
'Dragon scimitar',
'Dragon spear',
'Dragon sword',
'Dragon thrownaxe',
'Eclipse atlatl',
'Excalibur',
'Granite hammer',
'Granite maul',
'Heavy ballista',
'Light ballista',
'Rune claws',
'Saradomin sword',
"Saradomin's blessed sword",
'Seercull',
'Staff of balance',
'Staff of light',
'Staff of the dead',
Expand Down Expand Up @@ -139,15 +126,20 @@ export default class PlayerVsNPCCalc extends BaseCalc {
'Dragon claws',
'Dragon dagger',
'Dragon halberd',
'Dragon longsword',
'Dragon scimitar',
'Crystal halberd',
'Abyssal dagger',
'Saradomin sword',
]) || this.isWearingGodsword()) {
defenceStyle = 'slash';
} else if (this.wearing(['Arclight', 'Emberlight'])) {
} else if (this.wearing(['Arclight', 'Emberlight', 'Dragon sword'])) {
defenceStyle = 'stab';
} else if (this.wearing('Voidwaker')) {
// doesn't really matter since it's 100% accuracy but eh
} else if (this.wearing(['Voidwaker', "Saradomin's blessed sword"])) {
// doesn't really matter for voidwaker since it's 100% accuracy but eh
defenceStyle = 'magic';
} else if (this.wearing('Dragon mace')) {
defenceStyle = 'crush';
}
}

Expand Down Expand Up @@ -287,7 +279,7 @@ export default class PlayerVsNPCCalc extends BaseCalc {
attackRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPEC, attackRoll, [2, 1]);
} else if (this.isWearingFang()) {
attackRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPEC, attackRoll, [3, 2]);
} else if (this.wearing('Elder maul')) {
} else if (this.wearing(['Elder maul', 'Dragon mace', 'Dragon sword', 'Dragon scimitar', 'Abyssal whip'])) {
attackRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPEC, attackRoll, [5, 4]);
} else if (this.wearing('Dragon dagger')) {
attackRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPEC, attackRoll, [23, 20]);
Expand Down Expand Up @@ -434,11 +426,11 @@ export default class PlayerVsNPCCalc extends BaseCalc {
maxHit = this.trackFactor(DetailKey.MAX_HIT_GODSWORD_SPEC, maxHit, [11, 10]);
}

if (this.wearing('Bandos godsword')) {
if (this.wearing(['Bandos godsword', 'Saradomin sword'])) {
maxHit = this.trackFactor(DetailKey.MAX_HIT_SPEC, maxHit, [11, 10]);
} else if (this.wearing('Armadyl godsword')) {
} else if (this.wearing(['Armadyl godsword', 'Dragon sword', 'Dragon longsword', "Saradomin's blessed sword"])) {
maxHit = this.trackFactor(DetailKey.MAX_HIT_SPEC, maxHit, [5, 4]);
} else if (this.wearing('Dragon warhammer')) {
} else if (this.wearing(['Dragon mace', 'Dragon warhammer'])) {
maxHit = this.trackFactor(DetailKey.MAX_HIT_SPEC, maxHit, [3, 2]);
} else if (this.wearing('Voidwaker')) {
minHit = this.trackFactor(DetailKey.MIN_HIT_SPEC, maxHit, [1, 2]);
Expand Down Expand Up @@ -550,6 +542,8 @@ export default class PlayerVsNPCCalc extends BaseCalc {
attackRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPEC, attackRoll, [2, 1]);
} else if (this.isWearingMsb()) {
attackRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPEC, attackRoll, [10, 7]);
} else if (this.wearing(['Heavy ballista', 'Light ballista'])) {
attackRoll = this.trackFactor(DetailKey.PLAYER_ACCURACY_SPEC, attackRoll, [5, 4]);
}
}

Expand All @@ -570,7 +564,7 @@ export default class PlayerVsNPCCalc extends BaseCalc {
}
this.track(DetailKey.DAMAGE_LEVEL, effectiveLevel);

if (this.opts.usingSpecialAttack && (this.isWearingMsb() || this.isWearingMlb())) {
if (this.opts.usingSpecialAttack && (this.isWearingMsb() || this.isWearingMlb() || this.wearing('Seercull'))) {
// why +10 when that's not used anywhere else? who knows
effectiveLevel += 10;

Expand Down Expand Up @@ -680,6 +674,8 @@ export default class PlayerVsNPCCalc extends BaseCalc {
} else if (this.wearing('Webweaver bow')) {
const maxReduction = Math.trunc(maxHit * 6 / 10);
maxHit = this.trackAdd(DetailKey.MAX_HIT_SPEC, maxHit, -maxReduction);
} else if (this.wearing(['Heavy ballista', 'Light ballista'])) {
maxHit = this.trackFactor(DetailKey.MAX_HIT_SPEC, maxHit, [5, 4]);
}
}

Expand Down Expand Up @@ -971,12 +967,8 @@ export default class PlayerVsNPCCalc extends BaseCalc {
* Don't use this for player-facing values! Use `getMax()`
*/
getMinAndMax(): MinMax {
if (this.player.style.stance !== 'Manual Cast') {
const weaponId = this.player.equipment.weapon?.id;
const ammoId = this.player.equipment.ammo?.id;
if (ammoApplicability(weaponId, ammoId) === AmmoApplicability.INVALID) {
return [0, 0];
}
if (this.player.style.stance !== 'Manual Cast' && this.isAmmoInvalid()) {
return [0, 0];
}

const style = this.player.style.type;
Expand Down Expand Up @@ -1013,12 +1005,8 @@ export default class PlayerVsNPCCalc extends BaseCalc {
return this.track(DetailKey.PLAYER_ACCURACY_ROLL_FINAL, this.opts.overrides?.attackRoll);
}

if (this.player.style.stance !== 'Manual Cast') {
const weaponId = this.player.equipment.weapon?.id;
const ammoId = this.player.equipment.ammo?.id;
if (ammoApplicability(weaponId, ammoId) === AmmoApplicability.INVALID) {
return this.track(DetailKey.PLAYER_ACCURACY_ROLL_FINAL, 0.0);
}
if (this.player.style.stance !== 'Manual Cast' && this.isAmmoInvalid()) {
return this.track(DetailKey.PLAYER_ACCURACY_ROLL_FINAL, 0.0);
}

const style = this.player.style.type;
Expand Down Expand Up @@ -1067,10 +1055,17 @@ export default class PlayerVsNPCCalc extends BaseCalc {
return this.track(DetailKey.PLAYER_ACCURACY_FINAL, accuracy);
}

if (this.opts.usingSpecialAttack && (this.wearing(['Voidwaker', 'Dawnbringer']) || this.isWearingMlb())) {
if (this.opts.usingSpecialAttack && this.wearing(['Voidwaker', 'Dawnbringer'])) {
return 1.0;
}

if (this.opts.usingSpecialAttack && (this.wearing('Seercull') || this.isWearingMlb())) {
if (this.isAmmoInvalid()) {
return this.track(DetailKey.PLAYER_ACCURACY_FINAL, 0.0);
}
return this.track(DetailKey.PLAYER_ACCURACY_FINAL, 1.0);
}

const atk = this.getMaxAttackRoll();
const def = this.getNPCDefenceRoll();

Expand Down Expand Up @@ -1243,7 +1238,7 @@ export default class PlayerVsNPCCalc extends BaseCalc {
// simple multi-hit specs
if (this.opts.usingSpecialAttack) {
let hitCount = 1;
if (this.wearing(['Dragon dagger', 'Abyssal dagger']) || this.isWearingMsb()) {
if (this.wearing(['Dragon dagger', 'Abyssal dagger', 'Dragon knife']) || this.isWearingMsb()) {
hitCount = 2;
} else if (this.wearing('Webweaver bow')) {
hitCount = 4;
Expand All @@ -1254,6 +1249,18 @@ export default class PlayerVsNPCCalc extends BaseCalc {
}
}

if (this.opts.usingSpecialAttack && this.wearing('Saradomin sword')) {
const magicHit = HitDistribution.linear(1.0, 1, 16);
dist = dist.transform(
(h) => {
if (h.accurate && !IMMUNE_TO_MAGIC_DAMAGE_NPC_IDS.includes(this.monster.id)) {
return new HitDistribution([new WeightedHit(1.0, [h])]).zip(magicHit);
}
return new HitDistribution([new WeightedHit(1.0, [h, Hitsplat.INACCURATE])]);
},
);
}

if (this.opts.usingSpecialAttack && this.wearing('Purging staff')) {
// todo(wgs): does this require the correct runes or only the level of each demonbane spell?
}
Expand Down