diff --git a/data/abilities.ts b/data/abilities.ts index 829ffccc20..72ae5fdbde 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -487,11 +487,11 @@ export const Abilities: {[abilityid: string]: AbilityData} = { num: 172, }, compoundeyes: { - onSourceModifyAccuracyPriority: 9, + onSourceModifyAccuracyPriority: -1, onSourceModifyAccuracy(accuracy) { if (typeof accuracy !== 'number') return; this.debug('compoundeyes - enhancing accuracy'); - return accuracy * 1.3; + return this.chainModify([0x14CD, 0x1000]); }, name: "Compound Eyes", rating: 3, @@ -1430,10 +1430,10 @@ export const Abilities: {[abilityid: string]: AbilityData} = { onModifyAtk(atk) { return this.modify(atk, 1.5); }, - onSourceModifyAccuracyPriority: 7, + onSourceModifyAccuracyPriority: -1, onSourceModifyAccuracy(accuracy, target, source, move) { if (move.category === 'Physical' && typeof accuracy === 'number') { - return accuracy * 0.8; + return this.chainModify([0x0CCD, 0x1000]); } }, name: "Hustle", @@ -3076,12 +3076,12 @@ export const Abilities: {[abilityid: string]: AbilityData} = { onImmunity(type, pokemon) { if (type === 'sandstorm') return false; }, - onModifyAccuracyPriority: 8, + onModifyAccuracyPriority: -1, onModifyAccuracy(accuracy) { if (typeof accuracy !== 'number') return; if (this.field.isWeather('sandstorm')) { this.debug('Sand Veil - decreasing accuracy'); - return accuracy * 0.8; + return this.chainModify([0x0CCD, 0x1000]); } }, name: "Sand Veil", @@ -3400,12 +3400,12 @@ export const Abilities: {[abilityid: string]: AbilityData} = { onImmunity(type, pokemon) { if (type === 'hail') return false; }, - onModifyAccuracyPriority: 8, + onModifyAccuracyPriority: -1, onModifyAccuracy(accuracy) { if (typeof accuracy !== 'number') return; if (this.field.isWeather('hail')) { this.debug('Snow Cloak - decreasing accuracy'); - return accuracy * 0.8; + return this.chainModify([0x0CCD, 0x1000]); } }, name: "Snow Cloak", @@ -3799,12 +3799,12 @@ export const Abilities: {[abilityid: string]: AbilityData} = { num: 28, }, tangledfeet: { - onModifyAccuracyPriority: 6, + onModifyAccuracyPriority: -1, onModifyAccuracy(accuracy, target) { if (typeof accuracy !== 'number') return; if (target?.volatiles['confusion']) { this.debug('Tangled Feet - decreasing accuracy'); - return accuracy * 0.5; + return this.chainModify(0.5); } }, name: "Tangled Feet", @@ -4080,9 +4080,10 @@ export const Abilities: {[abilityid: string]: AbilityData} = { num: 260, }, victorystar: { - onAllyModifyMove(move) { - if (typeof move.accuracy === 'number') { - move.accuracy *= 1.1; + onAnyModifyAccuracyPriority: -1, + onAnyModifyAccuracy(accuracy, target, source) { + if (source.side === this.effectData.target.side && typeof accuracy === 'number') { + return this.chainModify([0x119A, 0x1000]); } }, name: "Victory Star", diff --git a/data/items.ts b/data/items.ts index 6a9aa65213..5997f7011e 100644 --- a/data/items.ts +++ b/data/items.ts @@ -562,11 +562,11 @@ export const Items: {[itemid: string]: ItemData} = { fling: { basePower: 10, }, - onModifyAccuracyPriority: 5, + onModifyAccuracyPriority: -2, onModifyAccuracy(accuracy) { if (typeof accuracy !== 'number') return; this.debug('brightpowder - decreasing accuracy'); - return accuracy * 0.9; + return this.chainModify([0x0F1C, 0x1000]); }, num: 213, gen: 2, @@ -2794,11 +2794,11 @@ export const Items: {[itemid: string]: ItemData} = { fling: { basePower: 10, }, - onModifyAccuracyPriority: 5, + onModifyAccuracyPriority: -2, onModifyAccuracy(accuracy) { if (typeof accuracy !== 'number') return; this.debug('lax incense - decreasing accuracy'); - return accuracy * 0.9; + return this.chainModify([0x0F1C, 0x1000]); }, num: 255, gen: 3, @@ -3463,12 +3463,13 @@ export const Items: {[itemid: string]: ItemData} = { }, condition: { duration: 2, - onSourceModifyAccuracyPriority: 3, - onSourceModifyAccuracy(accuracy, target, source) { - this.add('-enditem', source, 'Micle Berry'); - source.removeVolatile('micleberry'); - if (typeof accuracy === 'number') { - return accuracy * 1.2; + onSourceAccuracy(accuracy, target, source, move) { + if (!move.ohko) { + this.add('-enditem', source, 'Micle Berry'); + source.removeVolatile('micleberry'); + if (typeof accuracy === 'number') { + return this.chainModify([0x1333, 0x1000]); + } } }, }, @@ -6737,10 +6738,10 @@ export const Items: {[itemid: string]: ItemData} = { fling: { basePower: 10, }, - onSourceModifyAccuracyPriority: 4, + onSourceModifyAccuracyPriority: -2, onSourceModifyAccuracy(accuracy) { if (typeof accuracy === 'number') { - return accuracy * 1.1; + return this.chainModify([0x1199, 0x1000]); } }, num: 265, @@ -6837,11 +6838,11 @@ export const Items: {[itemid: string]: ItemData} = { fling: { basePower: 10, }, - onSourceModifyAccuracyPriority: 4, + onSourceModifyAccuracyPriority: -2, onSourceModifyAccuracy(accuracy, target) { if (typeof accuracy === 'number' && !this.queue.willMove(target)) { this.debug('Zoom Lens boosting accuracy'); - return accuracy * 1.2; + return this.chainModify([0x1333, 0x1000]); } }, num: 276, diff --git a/data/mods/gen4/abilities.ts b/data/mods/gen4/abilities.ts index 94ed38a7bc..0c3ad9d1b1 100644 --- a/data/mods/gen4/abilities.ts +++ b/data/mods/gen4/abilities.ts @@ -44,6 +44,15 @@ export const Abilities: {[k: string]: ModdedAbilityData} = { }, onAfterMoveSecondary() {}, }, + compoundeyes: { + onSourceModifyAccuracyPriority: 9, + onSourceModifyAccuracy(accuracy) { + if (typeof accuracy !== 'number') return; + this.debug('compoundeyes - enhancing accuracy'); + return accuracy * 1.3; + }, + inherit: true, + }, cutecharm: { inherit: true, onDamagingHit(damage, target, source, move) { @@ -147,6 +156,15 @@ export const Abilities: {[k: string]: ModdedAbilityData} = { this.add('-activate', pokemon, 'ability: Forewarn', warnMove); }, }, + hustle: { + inherit: true, + onSourceModifyAccuracyPriority: 7, + onSourceModifyAccuracy(accuracy, target, source, move) { + if (move.category === 'Physical' && typeof accuracy === 'number') { + return accuracy * 0.8; + } + }, + }, insomnia: { inherit: true, rating: 2.5, @@ -313,6 +331,17 @@ export const Abilities: {[k: string]: ModdedAbilityData} = { } }, }, + sandveil: { + inherit: true, + onModifyAccuracyPriority: 8, + onModifyAccuracy(accuracy) { + if (typeof accuracy !== 'number') return; + if (this.field.isWeather('sandstorm')) { + this.debug('Sand Veil - decreasing accuracy'); + return accuracy * 0.8; + } + }, + }, serenegrace: { inherit: true, onModifyMove(move) { @@ -335,8 +364,16 @@ export const Abilities: {[k: string]: ModdedAbilityData} = { rating: 4, num: 86, }, - soundproof: { + snowcloak: { inherit: true, + onModifyAccuracyPriority: 8, + onModifyAccuracy(accuracy) { + if (typeof accuracy !== 'number') return; + if (this.field.isWeather('hail')) { + this.debug('Snow Cloak - decreasing accuracy'); + return accuracy * 0.8; + } + }, }, static: { inherit: true, @@ -396,6 +433,17 @@ export const Abilities: {[k: string]: ModdedAbilityData} = { source.trySetStatus(id, target); }, }, + tangledfeet: { + inherit: true, + onModifyAccuracyPriority: 6, + onModifyAccuracy(accuracy, target) { + if (typeof accuracy !== 'number') return; + if (target?.volatiles['confusion']) { + this.debug('Tangled Feet - decreasing accuracy'); + return accuracy * 0.5; + } + }, + }, thickfat: { onSourceBasePowerPriority: 1, onSourceBasePower(basePower, attacker, defender, move) { diff --git a/data/mods/gen4/items.ts b/data/mods/gen4/items.ts index e052b550ee..f44834ae04 100644 --- a/data/mods/gen4/items.ts +++ b/data/mods/gen4/items.ts @@ -16,6 +16,15 @@ export const Items: {[k: string]: ModdedItemData} = { } }, }, + brightpowder: { + inherit: true, + onModifyAccuracyPriority: 5, + onModifyAccuracy(accuracy) { + if (typeof accuracy !== 'number') return; + this.debug('brightpowder - decreasing accuracy'); + return accuracy * 0.9; + }, + }, choiceband: { inherit: true, onStart() { }, @@ -139,6 +148,15 @@ export const Items: {[k: string]: ModdedItemData} = { } }, }, + laxincense: { + inherit: true, + onModifyAccuracyPriority: 5, + onModifyAccuracy(accuracy) { + if (typeof accuracy !== 'number') return; + this.debug('lax incense - decreasing accuracy'); + return accuracy * 0.9; + }, + }, lifeorb: { inherit: true, onModifyDamage() {}, @@ -235,6 +253,20 @@ export const Items: {[k: string]: ModdedItemData} = { }, }, }, + micleberry: { + inherit: true, + condition: { + duration: 2, + onSourceModifyAccuracyPriority: 3, + onSourceModifyAccuracy(accuracy, target, source) { + this.add('-enditem', source, 'Micle Berry'); + source.removeVolatile('micleberry'); + if (typeof accuracy === 'number') { + return accuracy * 1.2; + } + }, + }, + }, razorfang: { inherit: true, onModifyMove(move) { @@ -266,4 +298,23 @@ export const Items: {[k: string]: ModdedItemData} = { } }, }, + widelens: { + inherit: true, + onSourceModifyAccuracyPriority: 4, + onSourceModifyAccuracy(accuracy) { + if (typeof accuracy === 'number') { + return accuracy * 1.1; + } + }, + }, + zoomlens: { + inherit: true, + onSourceModifyAccuracyPriority: 4, + onSourceModifyAccuracy(accuracy, target) { + if (typeof accuracy === 'number' && !this.queue.willMove(target)) { + this.debug('Zoom Lens boosting accuracy'); + return accuracy * 1.2; + } + }, + }, }; diff --git a/data/mods/gen4/scripts.ts b/data/mods/gen4/scripts.ts index 1f7fc07ab7..1f48c38900 100644 --- a/data/mods/gen4/scripts.ts +++ b/data/mods/gen4/scripts.ts @@ -97,6 +97,63 @@ export const Scripts: ModdedBattleScriptsData = { } return hitResults; }, + hitStepAccuracy(targets, pokemon, move) { + const hitResults = []; + for (const [i, target] of targets.entries()) { + this.activeTarget = target; + // calculate true accuracy + let accuracy = move.accuracy; + if (move.ohko) { // bypasses accuracy modifiers + if (!target.isSemiInvulnerable()) { + if (pokemon.level < target.level) { + this.add('-immune', target, '[ohko]'); + hitResults[i] = false; + continue; + } + accuracy = 30 + pokemon.level - target.level; + } + } else { + const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3]; + + let boosts; + let boost!: number; + if (accuracy !== true) { + if (!move.ignoreAccuracy) { + boosts = this.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts}); + boost = this.clampIntRange(boosts['accuracy'], -6, 6); + if (boost > 0) { + accuracy *= boostTable[boost]; + } else { + accuracy /= boostTable[-boost]; + } + } + if (!move.ignoreEvasion) { + boosts = this.runEvent('ModifyBoost', target, null, null, {...target.boosts}); + boost = this.clampIntRange(boosts['evasion'], -6, 6); + if (boost > 0) { + accuracy /= boostTable[boost]; + } else if (boost < 0) { + accuracy *= boostTable[-boost]; + } + } + } + accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy); + } + if (move.alwaysHit) { + accuracy = true; // bypasses ohko accuracy modifiers + } else { + accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy); + } + if (accuracy !== true && !this.randomChance(accuracy, 100)) { + if (!move.spreadHit) this.attrLastMove('[miss]'); + this.add('-miss', pokemon, target); + hitResults[i] = false; + continue; + } + hitResults[i] = true; + } + return hitResults; + }, calcRecoilDamage(damageDealt, move) { return this.clampIntRange(Math.floor(damageDealt * move.recoil![0] / move.recoil![1]), 1); diff --git a/data/moves.ts b/data/moves.ts index aff0422ed7..e64484f6fe 100644 --- a/data/moves.ts +++ b/data/moves.ts @@ -7279,7 +7279,7 @@ export const Moves: {[moveid: string]: MoveData} = { }, onModifyAccuracy(accuracy) { if (typeof accuracy !== 'number') return; - return accuracy * 5 / 3; + return this.chainModify([0x1AB8, 0x1000]); }, onDisableMove(pokemon) { for (const moveSlot of pokemon.moveSlots) { diff --git a/data/scripts.ts b/data/scripts.ts index f3704b9e1e..abe99ba103 100644 --- a/data/scripts.ts +++ b/data/scripts.ts @@ -452,31 +452,23 @@ export const Scripts: BattleScriptsData = { } } } else { - const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3]; - - let boosts; - let boost!: number; + accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy); if (accuracy !== true) { + let boost = 0; if (!move.ignoreAccuracy) { - boosts = this.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts}); + const boosts = this.runEvent('ModifyBoost', pokemon, null, null, {...pokemon.boosts}); boost = this.clampIntRange(boosts['accuracy'], -6, 6); - if (boost > 0) { - accuracy *= boostTable[boost]; - } else { - accuracy /= boostTable[-boost]; - } } if (!move.ignoreEvasion) { - boosts = this.runEvent('ModifyBoost', target, null, null, {...target.boosts}); - boost = this.clampIntRange(boosts['evasion'], -6, 6); - if (boost > 0) { - accuracy /= boostTable[boost]; - } else if (boost < 0) { - accuracy *= boostTable[-boost]; - } + const boosts = this.runEvent('ModifyBoost', target, null, null, {...target.boosts}); + boost = this.clampIntRange(boost - boosts['evasion'], -6, 6); + } + if (boost > 0) { + accuracy = this.trunc(accuracy * (3 + boost) / 3); + } else if (boost < 0) { + accuracy = this.trunc(accuracy * 3 / (3 - boost)); } } - accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy); } if (move.alwaysHit || (move.id === 'toxic' && this.gen >= 6 && pokemon.hasType('Poison'))) { accuracy = true; // bypasses ohko accuracy modifiers diff --git a/sim/dex-conditions.ts b/sim/dex-conditions.ts index 598e12c0ac..def4cc1b12 100644 --- a/sim/dex-conditions.ts +++ b/sim/dex-conditions.ts @@ -524,6 +524,7 @@ export interface EventMethods { onAfterMoveSelfPriority?: number; onAnyBasePowerPriority?: number; onAnyInvulnerabilityPriority?: number; + onAnyModifyAccuracyPriority?: number; onAnyFaintPriority?: number; onAnyPrepareHitPriority?: number; onAllyBasePowerPriority?: number; diff --git a/test/sim/misc/accuracy.js b/test/sim/misc/accuracy.js new file mode 100644 index 0000000000..c52e5dd1a8 --- /dev/null +++ b/test/sim/misc/accuracy.js @@ -0,0 +1,86 @@ +'use strict'; + +const assert = require('./../../assert'); +const common = require('./../../common'); + +let battle; + +describe("Accuracy", function () { + afterEach(function () { + battle.destroy(); + }); + + it(`should round half down when applying a modifier`, function () { + battle = common.createBattle([ + [{species: 'Butterfree', ability: 'compoundeyes', moves: ['sleeppowder']}], + [{species: 'Beldum', moves: ['poltergeist']}], + ]); + + battle.onEvent('Accuracy', battle.format, function (accuracy) { + assert.equal(accuracy, 98, 'CompoundEyes Sleep Powder should be 98% accurate'); + }); + + battle.makeChoices(); + + battle = common.createBattle([ + [{species: 'Butterfree', ability: 'victorystar', moves: ['fireblast']}], + [{species: 'Regirock', moves: ['sleeptalk']}], + ]); + + battle.onEvent('Accuracy', battle.format, function (accuracy) { + assert.equal(accuracy, 94, 'Victory Star Fire Blast should be 94% accurate'); + }); + + battle.makeChoices(); + + battle = common.createBattle([ + [{species: 'Butterfree', item: 'widelens', moves: ['fireblast']}], + [{species: 'Regirock', moves: ['sleeptalk']}], + ]); + + battle.onEvent('Accuracy', battle.format, function (accuracy) { + assert.equal(accuracy, 93, 'Wide Lens Fire Blast should be 93% accurate'); + }); + + battle.makeChoices(); + }); + + it(`should chain modifiers in order of the Pokemon's raw speed`, function () { + battle = common.createBattle({gameType: 'doubles'}, [[ + {species: 'Mewtwo', ability: 'victorystar', moves: ['gravity', 'sleeptalk', 'sandattack']}, + {species: 'Charizard', ability: 'compoundeyes', moves: ['sleeptalk', 'fireblast']}, + ], [ + {species: 'Bonsly', ability: 'tangledfeet', moves: ['doubleteam', 'sleeptalk']}, + {species: 'Pyukumuku', ability: 'noguard', moves: ['confuseray', 'sandattack', 'sleeptalk']}, + ]]); + + battle.makeChoices('move sandattack -2, move sleeptalk', 'move doubleteam, move sandattack 2'); + battle.makeChoices('auto', 'move sleeptalk, move confuseray -1'); + + battle.onEvent('Accuracy', battle.format, function (accuracy, target, source, move) { + if (move.id !== 'fireblast') return; + assert.equal(accuracy, 51); + }); + + battle.makeChoices('move gravity, move fire blast 1', 'move sleeptalk, move sleeptalk'); + + // Changing the Pokemon's Speeds around changes the chaining order, which affects the result + battle = common.createBattle({gameType: 'doubles'}, [[ + {species: 'Bonsly', ability: 'victorystar', moves: ['gravity', 'sleeptalk', 'sandattack']}, + {species: 'Charizard', ability: 'compoundeyes', moves: ['sleeptalk', 'fireblast']}, + ], [ + {species: 'Mewtwo', ability: 'tangledfeet', moves: ['doubleteam', 'sleeptalk']}, + {species: 'Pyukumuku', ability: 'noguard', moves: ['confuseray', 'sandattack', 'sleeptalk']}, + ]]); + + battle.makeChoices('move sandattack -2, move sleeptalk', 'move doubleteam, move sandattack 2'); + battle.makeChoices('auto', 'move sleeptalk, move confuseray -1'); + + battle.onEvent('Accuracy', battle.format, function (accuracy, target, source, move) { + if (move.id !== 'fireblast') return; + assert.equal(accuracy, 50); + }); + + battle.makeChoices('move gravity, move fire blast 1', 'move sleeptalk, move sleeptalk'); + }); +});