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

Fix crash with Psych Up, Focus Energy, and Dragon Cheer #10573

Merged
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
4 changes: 2 additions & 2 deletions data/abilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,13 +702,13 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = {
pokemon.boosts[i] = ally.boosts[i];
}
const volatilesToCopy = ['dragoncheer', 'focusenergy', 'gmaxchistrike', 'laserfocus'];
// we need to be sure to remove all the overlapping crit volatiles before trying to add any
for (const volatile of volatilesToCopy) pokemon.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
if (ally.volatiles[volatile]) {
pokemon.addVolatile(volatile);
if (volatile === 'gmaxchistrike') pokemon.volatiles[volatile].layers = ally.volatiles[volatile].layers;
if (volatile === 'dragoncheer') pokemon.volatiles[volatile].hasDragonType = ally.volatiles[volatile].hasDragonType;
} else {
pokemon.removeVolatile(volatile);
}
}
this.add('-copyboost', pokemon, ally, '[from] ability: Costar');
Expand Down
4 changes: 2 additions & 2 deletions data/mods/gen7pokebilities/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ export const Scripts: ModdedBattleScriptsData = {
this.boosts[boostName] = pokemon.boosts[boostName];
}
if (this.battle.gen >= 6) {
// we need to be sure to remove all the overlapping crit volatiles before trying to add any of them
const volatilesToCopy = ['dragoncheer', 'focusenergy', 'gmaxchistrike', 'laserfocus'];
for (const volatile of volatilesToCopy) this.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
if (pokemon.volatiles[volatile]) {
this.addVolatile(volatile);
if (volatile === 'gmaxchistrike') this.volatiles[volatile].layers = pokemon.volatiles[volatile].layers;
if (volatile === 'dragoncheer') this.volatiles[volatile].hasDragonType = pokemon.volatiles[volatile].hasDragonType;
} else {
this.removeVolatile(volatile);
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions data/mods/partnersincrime/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,13 +382,12 @@ export const Scripts: ModdedBattleScriptsData = {
}
if (this.battle.gen >= 6) {
const volatilesToCopy = ['dragoncheer', 'focusenergy', 'gmaxchistrike', 'laserfocus'];
for (const volatile of volatilesToCopy) this.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
if (pokemon.volatiles[volatile]) {
this.addVolatile(volatile);
if (volatile === 'gmaxchistrike') this.volatiles[volatile].layers = pokemon.volatiles[volatile].layers;
if (volatile === 'dragoncheer') this.volatiles[volatile].hasDragonType = pokemon.volatiles[volatile].hasDragonType;
} else {
this.removeVolatile(volatile);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions data/mods/pokebilities/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ export const Scripts: ModdedBattleScriptsData = {
this.boosts[boostName] = pokemon.boosts[boostName];
}
if (this.battle.gen >= 6) {
// we need to remove all crit volatiles before adding any crit volatiles
const volatilesToCopy = ['dragoncheer', 'focusenergy', 'gmaxchistrike', 'laserfocus'];
for (const volatile of volatilesToCopy) this.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
if (pokemon.volatiles[volatile]) {
this.addVolatile(volatile);
if (volatile === 'gmaxchistrike') this.volatiles[volatile].layers = pokemon.volatiles[volatile].layers;
if (volatile === 'dragoncheer') this.volatiles[volatile].hasDragonType = pokemon.volatiles[volatile].hasDragonType;
} else {
this.removeVolatile(volatile);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions data/mods/trademarked/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,13 @@ export const Scripts: ModdedBattleScriptsData = {
}
if (this.battle.gen >= 6) {
const volatilesToCopy = ['dragoncheer', 'focusenergy', 'gmaxchistrike', 'laserfocus'];
// we need to remove all the crit volatiles before adding any crit volatile
for (const volatile of volatilesToCopy) this.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
if (pokemon.volatiles[volatile]) {
this.addVolatile(volatile);
if (volatile === 'gmaxchistrike') this.volatiles[volatile].layers = pokemon.volatiles[volatile].layers;
if (volatile === 'dragoncheer') this.volatiles[volatile].hasDragonType = pokemon.volatiles[volatile].hasDragonType;
} else {
this.removeVolatile(volatile);
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions data/moves.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14542,14 +14542,16 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
for (i in target.boosts) {
source.boosts[i] = target.boosts[i];
}

const volatilesToCopy = ['dragoncheer', 'focusenergy', 'gmaxchistrike', 'laserfocus'];
// we need to remove all crit stage volatiles first; otherwise copying e.g. dragoncheer onto a mon with focusenergy
// will crash the server (since addVolatile fails due to overlap, leaving the source mon with no hasDragonType to set)
for (const volatile of volatilesToCopy) source.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
Comment on lines +14549 to 14550
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (const volatile of volatilesToCopy) source.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
for (const volatile of volatilesToCopy) {
source.removeVolatile(volatile);

wouldn't this functionally do the same thing with one less iteration?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is this a concern for Costar?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is that you need to remove the Focus Energy volatile in order to be able to properly copy the Dragon Cheer volatile and vice versa. Costar can presumably activate after having volatiles though use of Neutralising Gas or other methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I guess the other approach would be to avoid having codependent volatiles altogether by putting all of the logic in the Psych Up volatile which would have to work out whether it had been set by Dragon Cheer on a non-Dragon type in which case it would only add 1 to the crit ratio.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't move the removeVolatile call inside the same loop that tries to add volatiles since that would not remove focusenergy before dragoncheer is added; in this case fusing the loop changes the behavior. We could re-order the volatilesToCopy list, but I'm not sure (or tbh willing to exhaustively test) that would be robust, and it definitely wouldn't be robust against possible future additions or list re-orderings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the cartridge behavior of Costar after neutralizing gas?
e.g. roaring moon has 2 dd's and dragoncheer effect, flamigo switches in and copies all three boosts (+2 atk, +2 spe, +2 crit).
opponent switches weezing-galar (neutralizing gas) in
roaring moon switches out to hydreigon
weezing galar switches out

should flamigo after costar re-ups have no boosts? volatiles only?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the cartridge behavior of Costar after neutralizing gas? e.g. roaring moon has 2 dd's and dragoncheer effect, flamigo switches in and copies all three boosts (+2 atk, +2 spe, +2 crit). opponent switches weezing-galar (neutralizing gas) in roaring moon switches out to hydreigon weezing galar switches out

should flamigo after costar re-ups have no boosts? volatiles only?

It should have attempted to copy Hydreigon's stats, which in this case would be +0 Atk and +0 Spe. It also would copy Hydreigon's lack of crit volatile, so it should have a +0 crit rate again after Costar activates. It's identical to how Psych Up behaves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, that's the current behavior I have tested, then.

if (target.volatiles[volatile]) {
source.addVolatile(volatile);
if (volatile === 'gmaxchistrike') source.volatiles[volatile].layers = target.volatiles[volatile].layers;
if (volatile === 'dragoncheer') source.volatiles[volatile].hasDragonType = target.volatiles[volatile].hasDragonType;
} else {
source.removeVolatile(volatile);
}
}
this.add('-copyboost', source, target, '[from] move: Psych Up');
Expand Down
4 changes: 2 additions & 2 deletions sim/pokemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1274,14 +1274,14 @@ export class Pokemon {
this.boosts[boostName] = pokemon.boosts[boostName];
}
if (this.battle.gen >= 6) {
// we need to remove all of the overlapping crit volatiles before adding any of them
const volatilesToCopy = ['dragoncheer', 'focusenergy', 'gmaxchistrike', 'laserfocus'];
for (const volatile of volatilesToCopy) this.removeVolatile(volatile);
for (const volatile of volatilesToCopy) {
if (pokemon.volatiles[volatile]) {
this.addVolatile(volatile);
if (volatile === 'gmaxchistrike') this.volatiles[volatile].layers = pokemon.volatiles[volatile].layers;
if (volatile === 'dragoncheer') this.volatiles[volatile].hasDragonType = pokemon.volatiles[volatile].hasDragonType;
} else {
this.removeVolatile(volatile);
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions test/sim/abilities/costar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

const assert = require('./../../assert');
const common = require('./../../common');

let battle;

describe('Costar', function () {
afterEach(function () {
battle.destroy();
});

it('should copy the teammate\'s crit ratio on activation', function () {
battle = common.createBattle({gameType: 'doubles'});
battle.setPlayer('p1', {team: [
{species: 'Smeargle', level: 1, moves: ['sleeptalk', 'focusenergy']},
{species: 'Suicune', level: 1, moves: ['sleeptalk']},
{species: 'Flamigo', level: 1, ability: 'costar', moves: ['sleeptalk']},
]});
battle.setPlayer('p2', {team: [
{species: 'Suicune', level: 1, moves: ['sleeptalk']},
{species: 'pikachu', level: 1, moves: ['sleeptalk']},
{species: 'weezinggalar', level: 1, ability: 'neutralizinggas', moves: ['sleeptalk']},
]});

battle.makeChoices('move focusenergy, move sleeptalk', 'move sleeptalk, move sleeptalk');
battle.makeChoices('move sleeptalk, switch flamigo', 'move sleeptalk, move sleeptalk');

const flamigo = battle.p1.active[1];
assert(flamigo.volatiles['focusenergy'], "Costar should copy volatile crit modifiers.");

battle.makeChoices('switch suicune, move sleeptalk', 'switch weezinggalar, move sleeptalk');
battle.makeChoices('move sleeptalk, move sleeptalk', 'switch suicune, move sleeptalk');
assert(!flamigo.volatiles['focusenergy'], "Costar should copy having no volatile crit modifiers when re-activated.");
});

it('should copy both positive and negative stat changes', function () {
battle = common.createBattle({gameType: 'doubles'});
battle.setPlayer('p1', {team: [
{species: 'Suicune', level: 1, moves: ['sleeptalk']},
{species: 'Smeargle', level: 1, moves: ['sleeptalk', 'shellsmash']},
{species: 'Flamigo', level: 1, ability: 'costar', moves: ['sleeptalk']},
]});
battle.setPlayer('p2', {team: [
{species: 'Suicune', level: 1, moves: ['sleeptalk']},
{species: 'Suicune', level: 1, moves: ['sleeptalk']},
]});


battle.makeChoices('move sleeptalk, move shellsmash', 'move sleeptalk, move sleeptalk');
battle.makeChoices('switch flamigo, move sleeptalk', 'move sleeptalk, move sleeptalk');

const flamigo = battle.p1.active[0];
assert.statStage(flamigo, 'atk', 2, "A pokemon should copy the target's positive stat changes (atk) when switching in with Costar.");
assert.statStage(flamigo, 'spa', 2, "A pokemon should copy the target's positive stat changes (spa) when switching in with Costar.");
assert.statStage(flamigo, 'spe', 2, "A pokemon should copy the target's positive stat changes (spe) when switching in with Costar.");
assert.statStage(flamigo, 'def', -1, "A pokemon should copy the target's negative stat changes (def) when switching in with Costar.");
assert.statStage(flamigo, 'spd', -1, "A pokemon should copy the target's negative stat changes (spd) when switching in with Costar.");
});
});

20 changes: 20 additions & 0 deletions test/sim/moves/dragoncheer.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ describe('Dragon Cheer', function () {
battle.makeChoices('move bubble, move sleeptalk', 'auto');
});

it(`should be copied by Psych Up, using the target's Dragon Cheer level and replacing the user's current critical hit stage`, function () {
battle = common.createBattle({gameType: 'doubles'}, [[
{species: 'milotic', moves: ['dragoncheer', 'psychup', 'bubble', 'focusenergy']},
{species: 'comfey', moves: ['sleeptalk']},
], [
{species: 'wynaut', moves: ['sleeptalk']},
{species: 'wobbuffet', moves: ['sleeptalk']},
]]);

battle.onEvent(
'ModifyCritRatio', battle.format, -99,
(critRatio) => assert.equal(critRatio, 2)
);

battle.makeChoices('move focusenergy, move sleeptalk', 'auto');
battle.makeChoices('move dragoncheer -2, move sleeptalk', 'auto');
battle.makeChoices('move psychup -2, move sleeptalk', 'auto');
battle.makeChoices('move bubble, move sleeptalk', 'auto');
});

it(`should be copied by Transform, using the target's Dragon Cheer level`, function () {
battle = common.createBattle({gameType: 'doubles'}, [[
{species: 'milotic', moves: ['dragoncheer', 'transform']},
Expand Down
Loading