diff --git a/public/images/items.json b/public/images/items.json index 05f715ff078..dc05a39354d 100644 --- a/public/images/items.json +++ b/public/images/items.json @@ -7400,7 +7400,49 @@ "w": 16, "h": 16 } - } + }, + { + "filename": "toxic_orb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 18, + "h": 18 + }, + "frame": { + "x": 379, + "y": 274, + "w": 18, + "h": 18 + } + }, + { + "filename": "flame_orb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 18, + "h": 18 + }, + "frame": { + "x": 379, + "y": 292, + "w": 18, + "h": 18 + } + } ] } ], diff --git a/public/images/items.png b/public/images/items.png index 9af295e16ad..d19bb003b93 100644 Binary files a/public/images/items.png and b/public/images/items.png differ diff --git a/public/images/items/flame_orb.png b/public/images/items/flame_orb.png new file mode 100644 index 00000000000..32f11719a5d Binary files /dev/null and b/public/images/items/flame_orb.png differ diff --git a/public/images/items/toxic_orb.png b/public/images/items/toxic_orb.png new file mode 100644 index 00000000000..7fb36db516e Binary files /dev/null and b/public/images/items/toxic_orb.png differ diff --git a/src/locales/en/modifier-type.ts b/src/locales/en/modifier-type.ts index cd256978304..92cf45e6541 100644 --- a/src/locales/en/modifier-type.ts +++ b/src/locales/en/modifier-type.ts @@ -209,6 +209,9 @@ export const modifierType: ModifierTypeTranslationEntries = { "LEFTOVERS": { name: "Leftovers", description: "Heals 1/16 of a Pokémon's maximum HP every turn" }, "SHELL_BELL": { name: "Shell Bell", description: "Heals 1/8 of a Pokémon's dealt damage" }, + "TOXIC_ORB": { name: "Toxic Orb", description: "Badly poisons its holder at the end of the turn if they do not have a status condition already" }, + "FLAME_ORB": { name: "Flame Orb", description: "Burns its holder at the end of the turn if they do not have a status condition already" }, + "BATON": { name: "Baton", description: "Allows passing along effects when switching Pokémon, which also bypasses traps" }, "SHINY_CHARM": { name: "Shiny Charm", description: "Dramatically increases the chance of a wild Pokémon being Shiny" }, diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index a26c8f634f7..dcfdfa27113 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,6 +1,7 @@ import * as Modifiers from "./modifier"; import { AttackMove, allMoves } from "../data/move"; import { Moves } from "../data/enums/moves"; +import { Abilities } from "../data/enums/abilities"; import { PokeballType, getPokeballCatchMultiplier, getPokeballName } from "../data/pokeball"; import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "../field/pokemon"; import { EvolutionItem, pokemonEvolutions } from "../data/pokemon-evolutions"; @@ -1182,6 +1183,9 @@ export const modifierTypes = { LEFTOVERS: () => new PokemonHeldItemModifierType("modifierType:ModifierType.LEFTOVERS", "leftovers", (type, args) => new Modifiers.TurnHealModifier(type, (args[0] as Pokemon).id)), SHELL_BELL: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SHELL_BELL", "shell_bell", (type, args) => new Modifiers.HitHealModifier(type, (args[0] as Pokemon).id)), + TOXIC_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.TOXIC_ORB", "toxic_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)), + FLAME_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.FLAME_ORB", "flame_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)), + BATON: () => new PokemonHeldItemModifierType("modifierType:ModifierType.BATON", "stick", (type, args) => new Modifiers.SwitchEffectTransferModifier(type, (args[0] as Pokemon).id)), SHINY_CHARM: () => new ModifierType("modifierType:ModifierType.SHINY_CHARM", "shiny_charm", (type, _args) => new Modifiers.ShinyRateBoosterModifier(type)), @@ -1247,7 +1251,12 @@ const modifierPool: ModifierPool = { [ModifierTier.GREAT]: [ new WeightedModifierType(modifierTypes.GREAT_BALL, 6), new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => { - const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status).length, 3); + const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => { + if (i instanceof Modifiers.TurnStatusEffectModifier) { + return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status.effect; + } + return false; + })).length, 3); return statusEffectPartyMemberCount * 6; }, 18), new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => { @@ -1270,7 +1279,12 @@ const modifierPool: ModifierPool = { return thresholdPartyMemberCount; }, 3), new WeightedModifierType(modifierTypes.FULL_RESTORE, (party: Pokemon[]) => { - const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status).length, 3); + const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => { + if (i instanceof Modifiers.TurnStatusEffectModifier) { + return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status.effect; + } + return false; + })).length, 3); const thresholdPartyMemberCount = Math.floor((Math.min(party.filter(p => (p.getInverseHp() >= 150 || p.getHpRatio() <= 0.5) && !p.isFainted()).length, 3) + statusEffectPartyMemberCount) / 2); return thresholdPartyMemberCount; }, 3), @@ -1312,6 +1326,40 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.MINT, 4), new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32), new WeightedModifierType(modifierTypes.AMULET_COIN, 3), + new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { + let weight = 0; + const filteredParty = party.filter(p => (p.status?.effect === StatusEffect.TOXIC || p.canSetStatus(StatusEffect.TOXIC, true, true)) + && !p.hasAbility(Abilities.FLARE_BOOST) + && !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier)); + if (filteredParty.some(p => p.hasAbility(Abilities.TOXIC_BOOST) || p.hasAbility(Abilities.POISON_HEAL))) { + weight = 4; + } else if (filteredParty.some(p => p.hasAbility(Abilities.GUTS) || p.hasAbility(Abilities.QUICK_FEET) || p.hasAbility(Abilities.MARVEL_SCALE))) { + weight = 2; + } else { + const moveList = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; + if (filteredParty.some(p => p.getMoveset().some(m => moveList.includes(m.moveId)))) { + weight = 1; + } + } + return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * weight, 8 * weight); + }, 32), + new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => { + let weight = 0; + const filteredParty = party.filter(p => (p.status?.effect === StatusEffect.BURN || p.canSetStatus(StatusEffect.BURN, true, true)) + && !p.hasAbility(Abilities.TOXIC_BOOST) && !p.hasAbility(Abilities.POISON_HEAL) + && !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier)); + if (filteredParty.some(p => p.hasAbility(Abilities.FLARE_BOOST))) { + weight = 4; + } else if (filteredParty.some(p => p.hasAbility(Abilities.GUTS) || p.hasAbility(Abilities.QUICK_FEET) || p.hasAbility(Abilities.MARVEL_SCALE))) { + weight = 2; + } else { + const moveList = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; + if (filteredParty.some(p => p.getMoveset().some(m => moveList.includes(m.moveId)))) { + weight = 1; + } + } + return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * weight, 8 * weight); + }, 32), new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.CANDY_JAR, 5), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 9035eeda9dd..716a48cda89 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -851,6 +851,66 @@ export class TurnHealModifier extends PokemonHeldItemModifier { } } +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class TurnStatusEffectModifier extends PokemonHeldItemModifier { + /** The status effect to be applied by the held item */ + private effect: StatusEffect; + + constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) { + super(type, pokemonId, stackCount); + + switch (type.id) { + case "TOXIC_ORB": + this.effect = StatusEffect.TOXIC; + break; + case "FLAME_ORB": + this.effect = StatusEffect.BURN; + break; + } + } + + /** + * Checks if {@linkcode modifier} is an instance of this class, + * intentionally ignoring potentially different {@linkcode effect}s + * to prevent held item stockpiling since the item obtained first + * would be the only item able to {@linkcode apply} successfully. + * @override + * @param modifier {@linkcode Modifier} being type tested + * @return true if {@linkcode modifier} is an instance of + * TurnStatusEffectModifier, false otherwise + */ + matchType(modifier: Modifier): boolean { + return modifier instanceof TurnStatusEffectModifier; + } + + clone() { + return new TurnStatusEffectModifier(this.type, this.pokemonId, this.stackCount); + } + + /** + * Tries to inflicts the holder with the associated {@linkcode StatusEffect}. + * @param args [0] {@linkcode Pokemon} that holds the held item + * @returns true if the status effect was applied successfully, false if + * otherwise + */ + apply(args: any[]): boolean { + return (args[0] as Pokemon).trySetStatus(this.effect, true, undefined, undefined, this.type.name); + } + + getMaxHeldItemCount(pokemon: Pokemon): integer { + return 1; + } + + getStatusEffect(): StatusEffect { + return this.effect; + } +} + export class HitHealModifier extends PokemonHeldItemModifier { constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { super(type, pokemonId, stackCount); diff --git a/src/phases.ts b/src/phases.ts index 58bfb527e44..b23f848397d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -6,7 +6,7 @@ import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMov import { Mode } from "./ui/ui"; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier } from "./modifier/modifier"; +import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier } from "./modifier/modifier"; import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; @@ -2289,6 +2289,8 @@ export class TurnEndPhase extends FieldPhase { applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); + this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); + this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); pokemon.battleSummonData.turnCount++;