smogon / pokemon-showdown

Pokémon battle simulator.
https://pokemonshowdown.com
MIT License
4.66k stars 2.72k forks source link

Tera Blast vs Unaware #9381

Open hreine2 opened 1 year ago

hreine2 commented 1 year ago

https://replay.pokemonshowdown.com/gen9randombattle-1790972745

On turn 53, it appears that Dondozo's Unaware is causing Tera Blast to ignore stat stages, resulting in Regidrago's Tera Blast being special (as is default since at base it has equal Atk and SpAtk in randoms) despite being at +2 Atk. I don't know how this interaction works on cartridge, but at the very least the calc believes it should be doing physical damage.

+2 Lvl 81 84 Atk Life Orb Tera Steel Regidrago Tera Blast vs. +6 Lvl 79 84 HP / 84 Def Unaware Tera Fairy Dondozo: 42-55 (11.4 - 14.9%) -- possibly the worst move ever

It using SpAtk instead lines up with the calc if it weren't +2 Atk.

Lvl 81 84 SpA Life Orb Tera Steel Regidrago Tera Blast vs. Lvl 79 84 HP / 84 SpD Tera Fairy Dondozo: 257-304 (70 - 82.8%) -- guaranteed 2HKO after Leftovers recovery

DaWoblefet commented 1 year ago

Confirming this is still a bug: https://replay.pokemonshowdown.com/gen9customgame-1902916108

Unaware shouldn't cause Tera Blast to change its category. I think there's potentially a lot more Unaware-related bugs like this out there, too, since we run the event for Unaware any time pokemon#getStat or pokemon#calculateStat are called.

vingkan commented 7 months ago

Trying to understand the consequences of changing code related to this issue:

Why are there separate functions for getStat() and calculateStat()?

As far as I can tell, the major differences between the two functions are:

  1. getStat() has the parameters unboosted and unmodified that allow optionally ignoring boosts and modifiers. The Tera Blast onModifyMove() function uses this to ignore modifiers, but keep boosts.
  2. calculateStat() has a parameter statUser to change the target Pokemon when modifying boosts. This helped solve the issue affected Foul Play and Unaware in @pyuk-bot's PR #9397.
  3. getStat() runs the Modify${statName} event, while calculateStat() does not.
  4. calculateStat() uses Battle.modify() to calculate the result of modifiers and check for truncation, while getStat() only seems to truncate if the speed stat is checked.
vingkan commented 7 months ago

I proposed a solution in #10030 that only changes the logic for Tera Blast. Let me know what you think!

vingkan commented 7 months ago

I think there's potentially a lot more Unaware-related bugs like this out there, too, since we run the event for Unaware any time pokemon#getStat or pokemon#calculateStat are called.

What other cases do you have in mind?

Maybe some of these moves have weird interactions with Unaware?

For abilities, I think Unaware is the only one that would cause this kind of interaction because no other ability (excluding mods) uses a ModifyBoost handler.

I spot-checked some stat-raising abilities (stat stage boosts only) and they used different kinds of handlers to apply their effects to the boost table before or after stat and damage calculation. I also spot-checked some stat-increasing abilities (stat/move modifiers and other methods) and these should already be ignored by Tera Blast because the unmodified parameter skips them.

urkerab commented 7 months ago

Why are there separate functions for getStat() and calculateStat()?

Because damage calculation is complicated. A few examples: