diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 8bbd8e7c583..925d574e96a 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -1,11 +1,11 @@ import BattleScene, { startingLevel, startingWave } from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult } from "./pokemon"; import * as Utils from './utils'; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr } from "./data/move"; +import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove } from "./data/move"; import { Mode } from './ui/ui'; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyInstantReviveChanceModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyInstantReviveChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier } 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"; @@ -1266,6 +1266,11 @@ export class TurnEndPhase extends FieldPhase { this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + if (!pokemon.isPlayer()) { + this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); + this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + } + applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); @@ -1563,7 +1568,7 @@ class MoveEffectPhase extends PokemonPhase { user, target, this.move.getMove()); if (!target.isFainted()) { applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult); - if (!user.isPlayer()) + if (!user.isPlayer() && this.move instanceof AttackMove) user.scene.applyModifiers(EnemyAttackStatusEffectChanceModifier, false, target); } if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 3995593102b..0b96933415f 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -528,7 +528,7 @@ export class TurnHeldItemTransferModifierType extends PokemonHeldItemModifierTyp export class EnemyAttackStatusEffectChanceModifierType extends ModifierType { constructor(name: string, chancePercent: integer, effect: StatusEffect, iconImage?: string) { - super(name, `Adds a ${chancePercent}% chance on hit to inflict ${getStatusEffectDescriptor(effect)}`, (type, args) => new Modifiers.EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent), iconImage, 'enemy_status_chance') + super(name, `Adds a ${chancePercent}% chance to inflict ${getStatusEffectDescriptor(effect)} with attack moves`, (type, args) => new Modifiers.EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent), iconImage, 'enemy_status_chance') } } @@ -675,15 +675,17 @@ export const modifierTypes = { GOLDEN_POKEBALL: () => new ModifierType(`Golden ${getPokeballName(PokeballType.POKEBALL)}`, 'Adds 1 extra item option at the end of every battle', (type, _args) => new Modifiers.ExtraModifierModifier(type), 'pb_gold', null, 'pb_bounce_1'), - ENEMY_DAMAGE_BOOSTER: () => new ModifierType('Damage Booster', 'Increases damage by 20%', (type, _args) => new Modifiers.EnemyDamageBoosterModifier(type), 'wl_item_drop'), - ENEMY_DAMAGE_REDUCTION: () => new ModifierType('Damage Reducer', 'Reduces incoming damage by 10%', (type, _args) => new Modifiers.EnemyDamageReducerModifier(type), 'wl_guard_spec'), - ENEMY_ATTACK_POISON_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Poison Hit', 10, StatusEffect.POISON, 'wl_antidote'), - ENEMY_ATTACK_PARALYZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Paralyze Hit', 10, StatusEffect.PARALYSIS, 'wl_paralyze_heal'), - ENEMY_ATTACK_SLEEP_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Sleep Hit', 10, StatusEffect.SLEEP, 'wl_awakening'), - ENEMY_ATTACK_FREEZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Freeze Hit', 10, StatusEffect.FREEZE, 'wl_ice_heal'), - ENEMY_ATTACK_BURN_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Burn Hit', 10, StatusEffect.BURN, 'wl_burn_heal'), - ENEMY_INSTANT_REVIVE_CHANCE: () => new EnemyInstantReviveChanceModifierType('Reviver', 5, false, 'wl_revive'), - ENEMY_INSTANT_MAX_REVIVE_CHANCE: () => new EnemyInstantReviveChanceModifierType('Reviver', 2, true, 'wl_max_revive'), + ENEMY_DAMAGE_BOOSTER: () => new ModifierType('Damage Token', 'Increases damage by 20%', (type, _args) => new Modifiers.EnemyDamageBoosterModifier(type, 20), 'wl_item_drop'), + ENEMY_DAMAGE_REDUCTION: () => new ModifierType('Protection Token', 'Reduces incoming damage by 10%', (type, _args) => new Modifiers.EnemyDamageReducerModifier(type, 10), 'wl_guard_spec'), + ENEMY_HEAL: () => new ModifierType('Recovery Token', 'Heals 10% of max HP every turn', (type, _args) => new Modifiers.EnemyTurnHealModifier(type, 10), 'wl_potion'), + ENEMY_ATTACK_POISON_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Poison Token', 10, StatusEffect.POISON, 'wl_antidote'), + ENEMY_ATTACK_PARALYZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Paralyze Token', 10, StatusEffect.PARALYSIS, 'wl_paralyze_heal'), + ENEMY_ATTACK_SLEEP_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Sleep Token', 10, StatusEffect.SLEEP, 'wl_awakening'), + ENEMY_ATTACK_FREEZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Freeze Token', 10, StatusEffect.FREEZE, 'wl_ice_heal'), + ENEMY_ATTACK_BURN_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType('Burn Token', 10, StatusEffect.BURN, 'wl_burn_heal'), + ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new ModifierType('Full Heal Token', 'Adds a 10% chance every turn to heal a status condition', (type, _args) => new Modifiers.EnemyStatusEffectHealChanceModifier(type, 10), 'wl_full_heal'), + ENEMY_INSTANT_REVIVE_CHANCE: () => new EnemyInstantReviveChanceModifierType('Revive Token', 5, false, 'wl_revive'), + ENEMY_INSTANT_MAX_REVIVE_CHANCE: () => new EnemyInstantReviveChanceModifierType('Max Revive Token', 2, true, 'wl_max_revive'), }; const modifierPool = { @@ -823,15 +825,17 @@ const trainerModifierPool = { const enemyBuffModifierPool = { [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 8), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 8), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_SLEEP_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_FREEZE_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_INSTANT_REVIVE_CHANCE, 8), - new WeightedModifierType(modifierTypes.ENEMY_INSTANT_MAX_REVIVE_CHANCE, 8) + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5), + new WeightedModifierType(modifierTypes.ENEMY_HEAL, 5), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 1), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 1), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_SLEEP_CHANCE, 1), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_FREEZE_CHANCE, 1), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 1), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5), + new WeightedModifierType(modifierTypes.ENEMY_INSTANT_REVIVE_CHANCE, 5), + new WeightedModifierType(modifierTypes.ENEMY_INSTANT_MAX_REVIVE_CHANCE, 3) ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), [ModifierTier.GREAT]: [ ].map(m => { m.setTier(ModifierTier.GREAT); return m; }), [ModifierTier.ULTRA]: [ ].map(m => { m.setTier(ModifierTier.ULTRA); return m; }), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index d4697b151ed..69754f8b630 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -15,7 +15,7 @@ import { TempBattleStat } from '../data/temp-battle-stat'; import { BerryType, getBerryEffectFunc, getBerryPredicate } from '../data/berry'; import { Species } from '../data/species'; import { BattleType } from '../battle'; -import { StatusEffect } from '../data/status-effect'; +import { StatusEffect, getStatusEffectDescriptor } from '../data/status-effect'; type ModifierType = ModifierTypes.ModifierType; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -487,9 +487,10 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { surviveDamage.value = true; pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` hung on\nusing its ${this.type.name}!`)); + return true; } - return true; + return false; } getMaxStackCount(): integer { @@ -517,10 +518,12 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier { apply(args: any[]): boolean { const flinched = args[1] as Utils.BooleanHolder; - if (!flinched.value && Utils.randInt(10) < this.getStackCount()) + if (!flinched.value && Utils.randInt(10) < this.getStackCount()) { flinched.value = true; + return true; + } - return true; + return false; } getMaxStackCount(): integer { @@ -548,9 +551,10 @@ export class TurnHealModifier extends PokemonHeldItemModifier { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); + return true; } - return true; + return false; } getMaxStackCount(): integer { @@ -762,7 +766,6 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { } export class PokemonStatusHealModifier extends ConsumablePokemonModifier { - constructor(type: ModifierType, pokemonId: integer) { super(type, pokemonId); } @@ -1233,50 +1236,89 @@ export abstract class EnemyPersistentModifer extends PersistentModifier { } getMaxStackCount(): number { - return 5; + return 1; } } export class EnemyDamageBoosterModifier extends EnemyPersistentModifer { - constructor(type: ModifierType, stackCount?: integer) { + private damageMultiplier: number; + + constructor(type: ModifierType, boostPercent: integer, stackCount?: integer) { super(type, stackCount); + + this.damageMultiplier = 1 + (boostPercent * 0.01); } match(modifier: Modifier): boolean { - return modifier instanceof EnemyDamageBoosterModifier; + return modifier instanceof EnemyDamageBoosterModifier && modifier.damageMultiplier === this.damageMultiplier; } clone(): EnemyDamageBoosterModifier { - return new EnemyDamageBoosterModifier(this.type, this.stackCount); + return new EnemyDamageBoosterModifier(this.type, (this.damageMultiplier - 1) * 100, this.stackCount); } apply(args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (1 + 0.2 * this.getStackCount())); + (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (this.damageMultiplier * this.getStackCount())); return true; } } export class EnemyDamageReducerModifier extends EnemyPersistentModifer { - constructor(type: ModifierType, stackCount?: integer) { + private damageMultiplier: number; + + constructor(type: ModifierType, reductionPercent: integer, stackCount?: integer) { super(type, stackCount); + + this.damageMultiplier = 1 - (reductionPercent * 0.01); } match(modifier: Modifier): boolean { - return modifier instanceof EnemyDamageReducerModifier; + return modifier instanceof EnemyDamageReducerModifier && modifier.damageMultiplier === this.damageMultiplier; } clone(): EnemyDamageReducerModifier { - return new EnemyDamageReducerModifier(this.type, this.stackCount); + return new EnemyDamageReducerModifier(this.type, (1 - this.damageMultiplier) * 100, this.stackCount); } apply(args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (1 - 0.2 * this.getStackCount())); + (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (this.damageMultiplier * this.getStackCount())); return true; } } +export class EnemyTurnHealModifier extends EnemyPersistentModifer { + private healPercent: integer; + + constructor(type: ModifierType, healPercent: integer, stackCount?: integer) { + super(type, stackCount); + + this.healPercent = healPercent; + } + + match(modifier: Modifier): boolean { + return modifier instanceof EnemyTurnHealModifier && modifier.healPercent === this.healPercent; + } + + clone(): EnemyTurnHealModifier { + return new EnemyTurnHealModifier(this.type, this.healPercent, this.stackCount); + } + + apply(args: any[]): boolean { + const pokemon = args[0] as Pokemon; + + if (pokemon.getHpRatio() < 1) { + const scene = pokemon.scene; + scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + Math.max(Math.floor(pokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), getPokemonMessage(pokemon, `\nrestored some HP!`), true)); + return true; + } + + return false; + } +} + export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifer { public effect: StatusEffect; private chance: number; @@ -1289,11 +1331,11 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModife } match(modifier: Modifier): boolean { - return modifier instanceof EnemyAttackStatusEffectChanceModifier && modifier.effect === this.effect; + return modifier instanceof EnemyAttackStatusEffectChanceModifier && modifier.effect === this.effect && modifier.chance === this.chance; } - clone(): EnemyDamageReducerModifier { - return new EnemyAttackStatusEffectChanceModifier(this.type, this.effect, this.stackCount); + clone(): EnemyAttackStatusEffectChanceModifier { + return new EnemyAttackStatusEffectChanceModifier(this.type, this.effect, this.chance * 100, this.stackCount); } apply(args: any[]): boolean { @@ -1307,6 +1349,36 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModife } } +export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifer { + private chance: number; + + constructor(type: ModifierType, chancePercent: integer, stackCount?: integer) { + super(type, stackCount); + + this.chance = chancePercent / 100; + } + + match(modifier: Modifier): boolean { + return modifier instanceof EnemyStatusEffectHealChanceModifier && modifier.chance === this.chance; + } + + clone(): EnemyStatusEffectHealChanceModifier { + return new EnemyStatusEffectHealChanceModifier(this.type, this.chance * 100, this.stackCount); + } + + apply(args: any[]): boolean { + const target = (args[0] as Pokemon); + if (target.status && Utils.randIntRange(0, 1) < this.chance * this.getStackCount()) { + target.scene.queueMessage(getPokemonMessage(target, ` was cured of its\n${getStatusEffectDescriptor(target.status.effect)}!`)); + target.resetStatus(); + target.updateInfo(); + return true; + } + + return false; + } +} + export class EnemyInstantReviveChanceModifier extends EnemyPersistentModifer { public fullHeal: boolean; private chance: number; @@ -1319,11 +1391,11 @@ export class EnemyInstantReviveChanceModifier extends EnemyPersistentModifer { } matchType(modifier: Modifier) { - return modifier instanceof EnemyInstantReviveChanceModifier && modifier.fullHeal === this.fullHeal; + return modifier instanceof EnemyInstantReviveChanceModifier && modifier.fullHeal === this.fullHeal && modifier.chance === this.chance; } clone() { - return new EnemyInstantReviveChanceModifier(this.type, this.fullHeal, this.chance, this.stackCount); + return new EnemyInstantReviveChanceModifier(this.type, this.fullHeal, this.chance * 100, this.stackCount); } apply(args: any[]): boolean {