From a344603176fb06e94e5d59789a103c87b6004000 Mon Sep 17 00:00:00 2001 From: atauber27 Date: Mon, 5 Feb 2024 01:34:47 -0500 Subject: [PATCH 1/2] Implemented Gen 6 Symbiosis Eject Button glitch --- data/items.ts | 1 + data/mods/gen6/items.ts | 105 ++++++++++++++++++++++++++++++++ sim/battle-queue.ts | 3 +- sim/battle.ts | 14 ++++- sim/pokemon.ts | 34 ++++++++++- test/sim/abilities/symbiosis.js | 2 +- 6 files changed, 152 insertions(+), 7 deletions(-) diff --git a/data/items.ts b/data/items.ts index f9a4bdeeb563..f895021fc6d5 100644 --- a/data/items.ts +++ b/data/items.ts @@ -1555,6 +1555,7 @@ export const Items: {[itemid: string]: ItemData} = { } target.switchFlag = true; if (target.useItem()) { + if (this.gen === 6) target.lastTurnEjected = this.turn; source.switchFlag = false; } else { target.switchFlag = false; diff --git a/data/mods/gen6/items.ts b/data/mods/gen6/items.ts index 6b822fe8d5b2..a0c3eaa3c593 100644 --- a/data/mods/gen6/items.ts +++ b/data/mods/gen6/items.ts @@ -13,10 +13,33 @@ export const Items: {[k: string]: ModdedItemData} = { } }, }, + airballoon: { + inherit: true, + onStart(target) { + if (!target.ignoringItem() && !this.field.getPseudoWeather('gravity')) { + this.add('-item', target, 'Air Balloon'); + if (target.lastTurnEjected) this.add('-item', target, 'Air Balloon'); + } + }, + }, belueberry: { inherit: true, isNonstandard: null, }, + berryjuice: { + inherit: true, + onUpdate(pokemon) { + if (pokemon.hp <= pokemon.maxhp / 2) { + if (this.runEvent('TryHeal', pokemon, null, this.effect, 20) && pokemon.useItem()) { + this.heal(20); + if (pokemon.lastTurnEjected && !pokemon.newlySwitched) { + this.heal(20); + pokemon.lastTurnEjected = null; + } + } + } + }, + }, cornnberry: { inherit: true, isNonstandard: null, @@ -71,6 +94,13 @@ export const Items: {[k: string]: ModdedItemData} = { } }, }, + leftovers: { + inherit: true, + onResidual(pokemon) { + this.heal(pokemon.baseMaxhp / 16); + if (pokemon.lastTurnEjected) this.heal(pokemon.baseMaxhp / 16); + }, + }, levelball: { inherit: true, isNonstandard: "Unobtainable", @@ -80,6 +110,7 @@ export const Items: {[k: string]: ModdedItemData} = { onAfterMoveSecondarySelf(source, target, move) { if (source && source !== target && move && move.category !== 'Status' && !move.ohko) { this.damage(source.baseMaxhp / 10, source, source, this.dex.items.get('lifeorb')); + if (source.lastTurnEjected) this.damage(source.baseMaxhp / 10, source, source, this.dex.items.get('lifeorb')); } }, }, @@ -113,6 +144,33 @@ export const Items: {[k: string]: ModdedItemData} = { inherit: true, isNonstandard: null, }, + mentalherb: { + inherit: true, + onUpdate(pokemon) { + const conditions = ['attract', 'taunt', 'encore', 'torment', 'disable', 'healblock']; + for (const firstCondition of conditions) { + if (pokemon.volatiles[firstCondition]) { + if (!pokemon.useItem()) return; + let foundSource; + for (const secondCondition of conditions) { + let secondSource; + if (pokemon.volatiles[secondCondition] && pokemon.volatiles[secondCondition].source) { + secondSource = pokemon.volatiles[secondCondition].source; + } + pokemon.removeVolatile(secondCondition); + if (firstCondition === 'attract' && secondCondition === 'attract') { + this.add('-end', pokemon, 'move: Attract', '[from] item: Mental Herb'); + if (secondSource.item && secondSource.item === 'destinyknot' && secondSource.lastTurnEjected) { + foundSource = secondSource; + } + } + } + this.add('-start', pokemon, 'Attract', '[from] item: Destiny Knot', '[of] ' + foundSource); + return; + } + } + }, + }, moonball: { inherit: true, isNonstandard: "Unobtainable", @@ -133,10 +191,40 @@ export const Items: {[k: string]: ModdedItemData} = { inherit: true, isNonstandard: null, }, + powerherb: { + inherit: true, + onChargeMove(pokemon, target, move) { + if (pokemon.useItem()) { + this.debug('power herb - remove charge turn for ' + move.id); + this.attrLastMove('[still]'); + this.addMove('-anim', pokemon, move.name, target); + if (pokemon.lastTurnEjected) this.addMove('-anim', pokemon, move.name, target); + return false; // skip charge turn + } + }, + }, + quickclaw: { + inherit: true, + onFractionalPriority(priority, pokemon, target, move) { + if (move.category === "Status" && pokemon.hasAbility("myceliummight")) return; + if (priority <= 0 && this.randomChance(1, 5)) { + this.add('-activate', pokemon, 'item: Quick Claw'); + if (pokemon.lastTurnEjected) this.add('-activate', pokemon, 'item: Quick Claw'); + return 0.1; + } + }, + }, rabutaberry: { inherit: true, isNonstandard: null, }, + razorclaw: { + inherit: true, + onModifyCritRatio(critRatio) { + if (this.event.target.lastTurnEjected) return critRatio + 2; + return critRatio + 1; + }, + }, razzberry: { inherit: true, isNonstandard: null, @@ -146,6 +234,7 @@ export const Items: {[k: string]: ModdedItemData} = { onDamagingHit(damage, target, source, move) { if (move.flags['contact']) { this.damage(source.baseMaxhp / 6, source, target, null, true); + if (target.lastTurnEjected) this.damage(source.baseMaxhp / 6, source, target, null, true); } }, }, @@ -159,6 +248,22 @@ export const Items: {[k: string]: ModdedItemData} = { } }, }, + scopelens: { + inherit: true, + onModifyCritRatio(critRatio) { + if (this.event.target.lastTurnEjected) return critRatio + 2; + return critRatio + 1; + }, + }, + shellbell: { + inherit: true, + onAfterMoveSecondarySelf(pokemon, target, move) { + if (move.totalDamage && !pokemon.forceSwitchFlag) { + this.heal(move.totalDamage / 8, pokemon); + if (pokemon.lastTurnEjected) this.heal(move.totalDamage / 8, pokemon); + } + }, + }, spelonberry: { inherit: true, isNonstandard: null, diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 9f503ae10686..6b1b7a4543cc 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -234,7 +234,8 @@ export class BattleQueue { if (typeof action.pokemon.switchFlag === 'string') { action.sourceEffect = this.battle.dex.moves.get(action.pokemon.switchFlag as ID) as any; } - action.pokemon.switchFlag = false; + if (this.battle.turn !== action.pokemon.lastTurnEjected || !action.pokemon.item) action.pokemon.lastTurnEjected = null; + action.pokemon.switchFlag = null; } } diff --git a/sim/battle.ts b/sim/battle.ts index 2370d9deab0e..a11bb66c684c 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -17,7 +17,7 @@ import {Dex, toID} from './dex'; import {Teams} from './teams'; import {Field} from './field'; -import {Pokemon, EffectState, RESTORATIVE_BERRIES} from './pokemon'; +import {Pokemon, EffectState, RESTORATIVE_BERRIES, DMG_REDUCING_BERRIES} from './pokemon'; import {PRNG, PRNGSeed} from './prng'; import {Side} from './side'; import {State} from './state'; @@ -509,8 +509,10 @@ export class Battle { if ((handler.effectHolder as Field).pseudoWeather) handlerEventid = `Field${eventid}`; if (handler.callback) { this.singleEvent(handlerEventid, effect, handler.state, handler.effectHolder, null, null, relayVar, handler.callback); + if (handler.effectHolder instanceof Pokemon && handler.effectHolder.lastTurnEjected && effect.effectType === 'Item') { + this.singleEvent(handlerEventid, effect, handler.state, handler.effectHolder, null, null, relayVar, handler.callback); + } } - this.faintMessages(); if (this.ended) return; } @@ -2138,13 +2140,19 @@ export class Battle { chainModify(numerator: number | number[], denominator?: number) { const previousMod = this.trunc(this.event.modifier * 4096); - if (Array.isArray(numerator)) { denominator = numerator[1]; numerator = numerator[0]; } const nextMod = this.trunc(numerator * 4096 / (denominator || 1)); this.event.modifier = ((previousMod * nextMod + 2048) >> 12) / 4096; + if (this.event.target.lastTurnEjected && (this.effect.effectType === 'Item' || this.effect.id === 'metronome' || + DMG_REDUCING_BERRIES.has(this.effect.id))) { + this.event.modifier *= ((previousMod * nextMod + 2048) >> 12) / 4096; + } + if (DMG_REDUCING_BERRIES.has(this.effect.id)) { + this.event.target.lastTurnEjected = null; + } } modify(value: number, numerator: number | number[], denominator?: number) { diff --git a/sim/pokemon.ts b/sim/pokemon.ts index 12f75590ac4e..d875dc3da3cb 100644 --- a/sim/pokemon.ts +++ b/sim/pokemon.ts @@ -42,6 +42,11 @@ export const RESTORATIVE_BERRIES = new Set([ 'leppaberry', 'aguavberry', 'enigmaberry', 'figyberry', 'iapapaberry', 'magoberry', 'sitrusberry', 'wikiberry', 'oranberry', ] as ID[]); +export const DMG_REDUCING_BERRIES = new Set([ + 'babiriberry', 'chartiberry', 'chilanberry', 'chopleberry', 'cobaberry', 'colburberry', 'habanberry', 'kasibberry', 'kebiaberry', + 'occaberry', 'passhoberry', 'payapaberry', 'rindoberry', 'roseliberry', 'shucaberry', 'tangaberry', 'wacanberry', 'yacheberry', +] as ID[]); + export class Pokemon { readonly side: Side; readonly battle: Battle; @@ -149,6 +154,9 @@ export class Pokemon { newlySwitched: boolean; beingCalledBack: boolean; + // Gen 6 only + lastTurnEjected: number | null; + lastMove: ActiveMove | null; // Gen 2 only lastMoveEncore?: ActiveMove | null; @@ -433,10 +441,17 @@ export class Pokemon { this.newlySwitched = false; this.beingCalledBack = false; + /** + * Tracks the last turn the pokemon was ejected + * as a result of the eject button. Relevant in + * gen 6 to detect the validity of the symbiosis + * eject button glitch. + */ + this.lastTurnEjected = null; + this.lastMove = null; // This is used in gen 2 only, here to avoid code repetition. // Only declared if gen 2 to avoid declaring an object we aren't going to need. - if (this.battle.gen === 2) this.lastMoveEncore = null; this.lastMoveUsed = null; this.moveThisTurn = ''; this.statsRaisedThisTurn = false; @@ -646,7 +661,10 @@ export class Pokemon { */ getWeight() { - const weighthg = this.battle.runEvent('ModifyWeight', this, null, null, this.weighthg); + let weighthg = this.battle.runEvent('ModifyWeight', this, null, null, this.weighthg); + if ((this.item === 'floatstone' || this.item === 'ironball') && this.lastTurnEjected) { + weighthg = this.battle.runEvent('ModifyWeight', this, null, null, weighthg); + } return Math.max(1, weighthg); } @@ -1695,6 +1713,13 @@ export class Pokemon { this.battle.singleEvent('Eat', item, this.itemState, this, source, sourceEffect); this.battle.runEvent('EatItem', this, null, null, item); + if (this.lastTurnEjected && this.battle.turn !== this.lastTurnEjected && !this.newlySwitched) { + this.battle.singleEvent('Eat', item, this.itemState, this, source, sourceEffect); + this.battle.runEvent('EatItem', this, null, null, item); + if (!DMG_REDUCING_BERRIES.has(item.id)) { + this.lastTurnEjected = null; + } + } if (RESTORATIVE_BERRIES.has(item.id)) { switch (this.pendingStaleness) { @@ -1741,6 +1766,10 @@ export class Pokemon { } if (item.boosts) { this.battle.boost(item.boosts, this, source, item); + if (this.lastTurnEjected) { + this.battle.boost(item.boosts, this, source, item); + this.lastTurnEjected = null; + } } this.battle.singleEvent('Use', item, this.itemState, this, source, sourceEffect); @@ -1769,6 +1798,7 @@ export class Pokemon { const oldItemState = this.itemState; this.itemState = {id: '', target: this}; this.pendingStaleness = undefined; + source.lastTurnEjected = null; this.battle.singleEvent('End', item, oldItemState, this); this.battle.runEvent('AfterTakeItem', this, null, null, item); return item; diff --git a/test/sim/abilities/symbiosis.js b/test/sim/abilities/symbiosis.js index 6f0181458072..591917af47b2 100644 --- a/test/sim/abilities/symbiosis.js +++ b/test/sim/abilities/symbiosis.js @@ -64,7 +64,7 @@ describe('Symbiosis', function () { }); // See Marty's research for many more examples: https://www.smogon.com/forums/threads/battle-mechanics-research.3489239/post-6401506 - describe.skip('Symbiosis Eject Button Glitch (Gen 6 only)', function () { + describe('Symbiosis Eject Button Glitch (Gen 6 only)', function () { it('should cause Leftovers to restore HP 4 times', function () { battle = common.gen(6).createBattle({gameType: 'doubles'}, [[ {species: 'florges', ability: 'symbiosis', item: 'leftovers', moves: ['sleeptalk']}, From 81dbedf6079994c86e3c69e17cecbce9d28be9d1 Mon Sep 17 00:00:00 2001 From: atauber27 Date: Mon, 5 Feb 2024 02:18:41 -0500 Subject: [PATCH 2/2] Removed powerherb hardcoding --- data/mods/gen6/items.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/data/mods/gen6/items.ts b/data/mods/gen6/items.ts index a0c3eaa3c593..26746e183183 100644 --- a/data/mods/gen6/items.ts +++ b/data/mods/gen6/items.ts @@ -191,18 +191,6 @@ export const Items: {[k: string]: ModdedItemData} = { inherit: true, isNonstandard: null, }, - powerherb: { - inherit: true, - onChargeMove(pokemon, target, move) { - if (pokemon.useItem()) { - this.debug('power herb - remove charge turn for ' + move.id); - this.attrLastMove('[still]'); - this.addMove('-anim', pokemon, move.name, target); - if (pokemon.lastTurnEjected) this.addMove('-anim', pokemon, move.name, target); - return false; // skip charge turn - } - }, - }, quickclaw: { inherit: true, onFractionalPriority(priority, pokemon, target, move) {