From c25cb50b31ab15df0292a82ceb709754734be510 Mon Sep 17 00:00:00 2001 From: cornfish <166959318+poppedcornfish@users.noreply.github.com> Date: Sun, 14 Apr 2024 11:13:17 -0600 Subject: [PATCH 01/23] Implement toxic chain (#126) * implement toxic chain * fix for self target like roost --- src/data/ability.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index e122a4bd76b..396f703b472 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -788,19 +788,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { } } -export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackAbAttr { +export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { + private contactRequired: boolean; private chance: integer; private effects: StatusEffect[]; - constructor(chance: integer, ...effects: StatusEffect[]) { + constructor(contactRequired: boolean, chance: integer, ...effects: StatusEffect[]) { super(); + this.contactRequired = contactRequired; this.chance = chance; this.effects = effects; } applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { - if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { + if (pokemon != attacker && (!this.contactRequired || move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; return attacker.trySetStatus(effect, true); } @@ -809,6 +811,12 @@ export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackAbAttr { } } +export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackApplyStatusEffectAbAttr { + constructor(chance: integer, ...effects: StatusEffect[]) { + super(true, chance, ...effects); + } +} + export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { private condition: PokemonDefendCondition; @@ -2808,7 +2816,8 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.SUPERSWEET_SYRUP, "Supersweet Syrup (N)", "A sickly sweet scent spreads across the field the first time the Pokémon enters a battle, lowering the evasiveness of opposing Pokémon.", 9), new Ability(Abilities.HOSPITALITY, "Hospitality (N)", "When the Pokémon enters a battle, it showers its ally with hospitality, restoring a small amount of the ally's HP.", 9), - new Ability(Abilities.TOXIC_CHAIN, "Toxic Chain (N)", "The power of the Pokémon's toxic chain may badly poison any target the Pokémon hits with a move.", 9), + new Ability(Abilities.TOXIC_CHAIN, "Toxic Chain", "The power of the Pokémon's toxic chain may badly poison any target the Pokémon hits with a move.", 9) + .attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC), new Ability(Abilities.EMBODY_ASPECT_TEAL, "Embody Aspect", "The Pokémon's heart fills with memories, causing the Teal Mask to shine and the Pokémon's Speed stat to be boosted.", 9) .attr(PostBattleInitStatChangeAbAttr, BattleStat.SPD, 1, true) .attr(UncopiableAbilityAbAttr) From 946e6d8c5c41f6e78ae9caaf696884f23d35500f Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+TempsRay@users.noreply.github.com> Date: Sun, 14 Apr 2024 13:15:01 -0400 Subject: [PATCH 02/23] Implement Rivalry, Quick Feet, Liquid Voice, and Normalize (#108) * Implement Rivalry * Implement Quick Feet, Liquid Voice, and Normalize * Forgot paralysis is half speed instead of a quarter * Remove log statements * Fix minor edge case in rivalry for gendered vs genderless --- src/data/ability.ts | 40 ++++++++++++++++++++++++++++++++++++---- src/field/pokemon.ts | 3 ++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 396f703b472..ded9d0a438e 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -8,6 +8,7 @@ import { Weather, WeatherType } from "./weather"; import { BattlerTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { StatusEffect, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; +import { Gender } from "./gender"; import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, RecoilAttr, StatusMoveTypeImmunityAttr, allMoves } from "./move"; import { ArenaTagType } from "./enums/arena-tag-type"; import { Stat } from "./pokemon-stat"; @@ -657,6 +658,30 @@ export class MoveTypeChangePowerMultiplierAbAttr extends VariableMoveTypeAbAttr } } +export class MoveTypeChangeAttr extends PreAttackAbAttr { + private newType: Type; + private powerMultiplier: number; + private condition: PokemonAttackCondition; + + constructor(newType: Type, powerMultiplier: number, condition: PokemonAttackCondition){ + super(true); + this.newType = newType; + this.powerMultiplier = powerMultiplier; + this.condition = condition; + } + + applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean { + if (this.condition(pokemon, defender, move.getMove())) { + const type = (args[0] as Utils.IntegerHolder); + type.value = this.newType; + (args[1] as Utils.NumberHolder).value *= this.powerMultiplier; + return true; + } + + return false; + } +} + export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { private condition: PokemonAttackCondition; private powerMultiplier: number; @@ -2278,7 +2303,9 @@ export function initAbilities() { new Ability(Abilities.MOTOR_DRIVE, "Motor Drive", "Boosts its Speed stat if hit by an Electric-type move instead of taking damage.", 4) .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1) .ignorable(), - new Ability(Abilities.RIVALRY, "Rivalry (N)", "Becomes competitive and deals more damage to Pokémon of the same gender, but deals less to Pokémon of the opposite gender.", 4), + new Ability(Abilities.RIVALRY, "Rivalry", "Becomes competitive and deals more damage to Pokémon of the same gender, but deals less to Pokémon of the opposite gender.", 4) + .attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender === target.gender, 1.25) + .attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender !== target.gender, 0.75), new Ability(Abilities.STEADFAST, "Steadfast", "The Pokémon's determination boosts the Speed stat each time the Pokémon flinches.", 4) .attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1), new Ability(Abilities.SNOW_CLOAK, "Snow Cloak", "Boosts evasiveness in a hailstorm.", 4) @@ -2319,8 +2346,12 @@ export function initAbilities() { .attr(PostWeatherLapseDamageAbAttr, 2, WeatherType.SUNNY, WeatherType.HARSH_SUN) .attr(BattleStatMultiplierAbAttr, BattleStat.SPATK, 1.5) .condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)), - new Ability(Abilities.QUICK_FEET, "Quick Feet (N)", "Boosts the Speed stat if the Pokémon has a status condition.", 4), - new Ability(Abilities.NORMALIZE, "Normalize (N)", "All the Pokémon's moves become Normal type. The power of those moves is boosted a little.", 4), + new Ability(Abilities.QUICK_FEET, "Quick Feet", "Boosts the Speed stat if the Pokémon has a status condition.", 4) + .conditionalAttr(pokemon => pokemon.status.effect === StatusEffect.PARALYSIS, BattleStatMultiplierAbAttr, BattleStat.SPD, 2) + .conditionalAttr(pokemon => !!pokemon.status, BattleStatMultiplierAbAttr, BattleStat.SPD, 1.5), + new Ability(Abilities.NORMALIZE, "Normalize", "All the Pokémon's moves become Normal type. The power of those moves is boosted a little.", 4) + .attr(MoveTypeChangeAttr, Type.NORMAL, 1.2, (user, target, move) => move.id !== Moves.HIDDEN_POWER && move.id !== Moves.WEATHER_BALL && + move.id !== Moves.NATURAL_GIFT && move.id !== Moves.JUDGMENT && move.id !== Moves.TECHNO_BLAST), new Ability(Abilities.SNIPER, "Sniper (N)", "Powers up moves if they become critical hits when attacking.", 4), new Ability(Abilities.MAGIC_GUARD, "Magic Guard", "The Pokémon only takes damage from attacks.", 4) .attr(BlockNonDirectDamageAbAttr), @@ -2566,7 +2597,8 @@ export function initAbilities() { .condition(getWeatherCondition(WeatherType.HAIL)), new Ability(Abilities.LONG_REACH, "Long Reach", "The Pokémon uses its moves without making contact with the target.", 7) .attr(IgnoreContactAbAttr), - new Ability(Abilities.LIQUID_VOICE, "Liquid Voice (N)", "All sound-based moves become Water-type moves.", 7), + new Ability(Abilities.LIQUID_VOICE, "Liquid Voice", "All sound-based moves become Water-type moves.", 7) + .attr(MoveTypeChangeAttr, Type.WATER, 1, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED)), new Ability(Abilities.TRIAGE, "Triage", "Gives priority to a healing move.", 7) .attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3), new Ability(Abilities.GALVANIZE, "Galvanize", "Normal-type moves become Electric-type moves. The power of those moves is boosted a little.", 7) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 86355b17a9e..c396ca37bcc 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -25,7 +25,7 @@ import { TempBattleStat } from '../data/temp-battle-stat'; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from '../data/arena-tag'; import { ArenaTagType } from "../data/enums/arena-tag-type"; import { Biome } from "../data/enums/biome"; -import { Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from '../data/ability'; +import { Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; import { BattlerIndex } from '../battle'; @@ -1124,6 +1124,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(VariableMoveTypeAttr, source, this, move, variableType); // 2nd argument is for MoveTypeChangePowerMultiplierAbAttr applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier); + applyPreAttackAbAttrs(MoveTypeChangeAttr, source, this, battlerMove, variableType, typeChangeMovePowerMultiplier); const type = variableType.value as Type; const types = this.getTypes(true, true); From 605e16fe35fc279eca2bad51acf777a1f1e868e1 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Mon, 15 Apr 2024 03:11:22 +1000 Subject: [PATCH 03/23] Fix a bunch of moves --- src/data/move.ts | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index e2a7e3cd39c..50fcbaa3786 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3867,9 +3867,10 @@ export function initMoves() { new AttackMove(Moves.FACADE, "Facade", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, "This attack move doubles its power if the user is poisoned, burned, or paralyzed.", -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => user.status && (user.status.effect === StatusEffect.BURN || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.PARALYSIS) ? 2 : 1), - new AttackMove(Moves.FOCUS_PUNCH, "Focus Punch (P)", Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, "The user focuses its mind before launching a punch. This move fails if the user is hit before it is used.", -1, -3, 3) + new AttackMove(Moves.FOCUS_PUNCH, "Focus Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, "The user focuses its mind before launching a punch. This move fails if the user is hit before it is used.", -1, -3, 3) .punchingMove() - .ignoresVirtual(), + .ignoresVirtual() + .condition((user, target, move) => !user.turnData.attacksReceived.find(r => r.damage)), new AttackMove(Moves.SMELLING_SALTS, "Smelling Salts", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, "This attack's power is doubled when used on a target with paralysis. This also cures the target's paralysis, however.", -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS), @@ -3895,7 +3896,8 @@ export function initMoves() { .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1, true), new SelfStatusMove(Moves.MAGIC_COAT, "Magic Coat (N)", Type.PSYCHIC, -1, 15, "Moves like Leech Seed and moves that inflict status conditions are blocked by a barrier and reflected back to the user of those moves.", -1, 4, 3), new SelfStatusMove(Moves.RECYCLE, "Recycle (N)", Type.NORMAL, -1, 10, "The user recycles a held item that has been used in battle so it can be used again.", -1, 0, 3), - new AttackMove(Moves.REVENGE, "Revenge (P)", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, "This attack move's power is doubled if the user has been hurt by the opponent in the same turn.", -1, -4, 3), + new AttackMove(Moves.REVENGE, "Revenge", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, "This attack move's power is doubled if the user has been hurt by the opponent in the same turn.", -1, -4, 3) + .attr(TurnDamagedDoublePowerAttr), new AttackMove(Moves.BRICK_BREAK, "Brick Break (P)", Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, "The user attacks with a swift chop. It can also break barriers, such as Light Screen and Reflect.", -1, 0, 3), new StatusMove(Moves.YAWN, "Yawn", Type.NORMAL, -1, 10, "The user lets loose a huge yawn that lulls the target into falling asleep on the next turn.", -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) @@ -4863,7 +4865,8 @@ export function initMoves() { new AttackMove(Moves.PULVERIZING_PANCAKE, "Pulverizing Pancake (P)", Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, "Z-Power brings out the true capabilities of the user, Snorlax. The Pokémon moves its enormous body energetically and attacks the target with full force.", -1, 0, 7), new SelfStatusMove(Moves.EXTREME_EVOBOOST, "Extreme Evoboost", Type.NORMAL, -1, 1, "After obtaining Z-Power, the user, Eevee, gets energy from its evolved friends and boosts its stats sharply.", 100, 0, 7) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true), - new AttackMove(Moves.GENESIS_SUPERNOVA, "Genesis Supernova (P)", Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, "After obtaining Z-Power, the user, Mew, attacks the target with full force. The terrain will be charged with psychic energy.", -1, 0, 7), + new AttackMove(Moves.GENESIS_SUPERNOVA, "Genesis Supernova", Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, "After obtaining Z-Power, the user, Mew, attacks the target with full force. The terrain will be charged with psychic energy.", -1, 0, 7) + .attr(TerrainChangeAttr, TerrainType.PSYCHIC), /* End Unused */ new AttackMove(Moves.SHELL_TRAP, "Shell Trap (P)", Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, "The user sets a shell trap. If the user is hit by a physical move, the trap will explode and inflict damage on opposing Pokémon.", -1, -3, 7) .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -4911,6 +4914,7 @@ export function initMoves() { .attr(ClearTerrainAttr) .makesContact(false), new AttackMove(Moves.CLANGOROUS_SOULBLAZE, "Clangorous Soulblaze (P)", Type.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, "After obtaining Z-Power, the user, Kommo-o, attacks the opposing Pokémon with full force. This move boosts the user's stats.", 100, 0, 7) + .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), /* End Unused */ @@ -5051,8 +5055,9 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.APPLE_ACID, "Apple Acid", Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, "The user attacks the target with an acidic liquid created from tart apples. This also lowers the target's Sp. Def stat.", 100, 0, 8) .attr(StatChangeAttr, BattleStat.SPDEF, -1), - new AttackMove(Moves.GRAV_APPLE, "Grav Apple (P)", Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, "The user inflicts damage by dropping an apple from high above. This also lowers the target's Defense stat.", 100, 0, 8) + new AttackMove(Moves.GRAV_APPLE, "Grav Apple", Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, "The user inflicts damage by dropping an apple from high above. This also lowers the target's Defense stat.", 100, 0, 8) .attr(StatChangeAttr, BattleStat.DEF, -1) + .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTag(ArenaTagType.GRAVITY) ? 1.5 : 1) .makesContact(false), new AttackMove(Moves.SPIRIT_BREAK, "Spirit Break", Type.FAIRY, MoveCategory.PHYSICAL, 75, 100, 15, "The user attacks the target with so much force that it could break the target's spirit. This also lowers the target's Sp. Atk stat.", 100, 0, 8) .attr(StatChangeAttr, BattleStat.SPATK, -1), @@ -5357,8 +5362,10 @@ export function initMoves() { new AttackMove(Moves.HYDRO_STEAM, "Hydro Steam (P)", Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, "The user blasts the target with boiling-hot water. This move's power is not lowered in harsh sunlight but rather boosted by 50 percent.", -1, 0, 9), new AttackMove(Moves.RUINATION, "Ruination", Type.DARK, MoveCategory.SPECIAL, 1, 90, 10, "The user summons a ruinous disaster. This cuts the target's HP in half.", -1, 0, 9) .attr(TargetHalfHpDamageAttr), - new AttackMove(Moves.COLLISION_COURSE, "Collision Course (P)", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, "The user transforms and crashes to the ground, causing a massive prehistoric explosion. This move's power is boosted more than usual if it's a supereffective hit.", -1, 0, 9), - new AttackMove(Moves.ELECTRO_DRIFT, "Electro Drift (P)", Type.ELECTRIC, MoveCategory.SPECIAL, 100, 100, 5, "The user races forward at ultrafast speeds, piercing its target with futuristic electricity. This move's power is boosted more than usual if it's a supereffective hit.", -1, 0, 9) + new AttackMove(Moves.COLLISION_COURSE, "Collision Course", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, "The user transforms and crashes to the ground, causing a massive prehistoric explosion. This move's power is boosted more than usual if it's a supereffective hit.", -1, 0, 9) + .attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type) >= 2 ? 5461/4096 : 1), + new AttackMove(Moves.ELECTRO_DRIFT, "Electro Drift", Type.ELECTRIC, MoveCategory.SPECIAL, 100, 100, 5, "The user races forward at ultrafast speeds, piercing its target with futuristic electricity. This move's power is boosted more than usual if it's a supereffective hit.", -1, 0, 9) + .attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type) >= 2 ? 5461/4096 : 1) .makesContact(), new SelfStatusMove(Moves.SHED_TAIL, "Shed Tail (N)", Type.NORMAL, -1, 10, "The user creates a substitute for itself using its own HP before switching places with a party Pokémon in waiting.", -1, 0, 9), new StatusMove(Moves.CHILLY_RECEPTION, "Chilly Reception", Type.ICE, -1, 10, "The user tells a chillingly bad joke before switching places with a party Pokémon in waiting. This summons a snowstorm lasting five turns.", -1, 0, 9) @@ -5391,7 +5398,11 @@ export function initMoves() { .triageMove(), new AttackMove(Moves.DOUBLE_SHOCK, "Double Shock (P)", Type.ELECTRIC, MoveCategory.PHYSICAL, 120, 100, 5, "The user discharges all the electricity from its body to perform a high-damage attack. After using this move, the user will no longer be Electric type.", -1, 0, 9), new AttackMove(Moves.GIGATON_HAMMER, "Gigaton Hammer (P)", Type.STEEL, MoveCategory.PHYSICAL, 160, 100, 5, "The user swings its whole body around to attack with its huge hammer. This move can't be used twice in a row.", -1, 0, 9) - .makesContact(false), + .makesContact(false) + .condition((user, target, move) => { + const turnMove = user.getLastXMoves(1); + return !turnMove.length || turnMove[0].move !== move.id || turnMove[0].result !== MoveResult.SUCCESS; + }), // TODO Add Instruct/Encore interaction new AttackMove(Moves.COMEUPPANCE, "Comeuppance", Type.DARK, MoveCategory.PHYSICAL, 1, 100, 10, "The user retaliates with much greater force against the opponent that last inflicted damage on it.", -1, 0, 9) .attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5) .target(MoveTarget.ATTACKER), @@ -5414,7 +5425,11 @@ export function initMoves() { new AttackMove(Moves.MAGICAL_TORQUE, "Magical Torque", Type.FAIRY, MoveCategory.PHYSICAL, 100, 100, 10, "The user revs their fae-like engine into the target. This may also confuse the target.", 30, 0, 9) .attr(ConfuseAttr) .makesContact(false), - new AttackMove(Moves.BLOOD_MOON, "Blood Moon (P)", Type.NORMAL, MoveCategory.SPECIAL, 140, 100, 5, "The user unleashes the full brunt of its spirit from a full moon that shines as red as blood. This move can't be used twice in a row.", -1, 0, 9), + new AttackMove(Moves.BLOOD_MOON, "Blood Moon (P)", Type.NORMAL, MoveCategory.SPECIAL, 140, 100, 5, "The user unleashes the full brunt of its spirit from a full moon that shines as red as blood. This move can't be used twice in a row.", -1, 0, 9) + .condition((user, target, move) => { + const turnMove = user.getLastXMoves(1); + return !turnMove.length || turnMove[0].move !== move.id || turnMove[0].result !== MoveResult.SUCCESS; + }), // TODO Add Instruct/Encore interaction new AttackMove(Moves.MATCHA_GOTCHA, "Matcha Gotcha", Type.GRASS, MoveCategory.SPECIAL, 80, 90, 15, "The user fires a blast of tea that it mixed. The user's HP is restored by up to half the damage taken by the target. This may also leave the target with a burn.", 20, 0, 9) .attr(HitHealAttr) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) @@ -5442,7 +5457,8 @@ export function initMoves() { new AttackMove(Moves.MIGHTY_CLEAVE, "Mighty Cleave", Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, "The user wields the light that has accumulated atop its head to cleave the target. This move hits even if the target protects itself.", -1, 0, 9) .ignoresProtect(), new AttackMove(Moves.TACHYON_CUTTER, "Tachyon Cutter", Type.STEEL, MoveCategory.SPECIAL, 50, -1, 10, "The user attacks by launching particle blades at the target twice in a row. This attack never misses.", -1, 0, 9) - .attr(MultiHitAttr, MultiHitType._2), + .attr(MultiHitAttr, MultiHitType._2) + .slicingMove(), new AttackMove(Moves.HARD_PRESS, "Hard Press", Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, "The target is crushed with an arm, a claw, or the like to inflict damage. The more HP the target has left, the greater the move's power.", -1, 0, 9) .attr(OpponentHighHpPowerAttr), new StatusMove(Moves.DRAGON_CHEER, "Dragon Cheer (P)", Type.DRAGON, -1, 15, "The user raises its allies' morale with a draconic cry so that their future attacks have a heightened chance of landing critical hits. This rouses Dragon types more.", 100, 0, 9) From 73cf4e9f5c4341bbc0b96bcf54f7501f1ccbee67 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Sun, 14 Apr 2024 12:52:59 -0500 Subject: [PATCH 04/23] Implement Thunderclap It's just a copy of Sucker Punch, so no additional work needed. --- src/data/move.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index 50fcbaa3786..e5ff01c638f 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -5453,7 +5453,8 @@ export function initMoves() { .attr(DoublePowerChanceAttr), new StatusMove(Moves.BURNING_BULWARK, "Burning Bulwark", Type.FIRE, -1, 10, "The user's intensely hot fur protects it from attacks and also burns any attacker that makes direct contact with it.", 100, 4, 9) .attr(ProtectAttr, BattlerTagType.BURNING_BULWARK), - new AttackMove(Moves.THUNDERCLAP, "Thunderclap (P)", Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, "This move enables the user to attack first with a jolt of electricity. This move fails if the target is not readying an attack.", -1, 1, 9), + new AttackMove(Moves.THUNDERCLAP, "Thunderclap", Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, "This move enables the user to attack first with a jolt of electricity. This move fails if the target is not readying an attack.", -1, 1, 9) + .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS), new AttackMove(Moves.MIGHTY_CLEAVE, "Mighty Cleave", Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, "The user wields the light that has accumulated atop its head to cleave the target. This move hits even if the target protects itself.", -1, 0, 9) .ignoresProtect(), new AttackMove(Moves.TACHYON_CUTTER, "Tachyon Cutter", Type.STEEL, MoveCategory.SPECIAL, 50, -1, 10, "The user attacks by launching particle blades at the target twice in a row. This attack never misses.", -1, 0, 9) From 90ef58d7a4144fb50dab62716df82f77e015b969 Mon Sep 17 00:00:00 2001 From: shayebeadling Date: Sun, 14 Apr 2024 14:20:00 -0400 Subject: [PATCH 05/23] Defiant implementation (#128) * Defiant works, but self inflicted stat changes still proc the stat raise. * Prevents proc from self-targeted stat reductions, like superpower --- src/data/ability.ts | 38 +++++++++++++++++++++++++++++++++++++- src/phases.ts | 3 ++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index ded9d0a438e..2ad6959cbd7 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -82,6 +82,7 @@ type AbAttrCondition = (pokemon: Pokemon) => boolean; type PokemonAttackCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean; type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; +type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean; export abstract class AbAttr { public showAbility: boolean; @@ -385,6 +386,12 @@ export class PostDefendAbAttr extends AbAttr { } } +export class PostStatChangeAbAttr extends AbAttr { + applyPostStatChange(pokemon: Pokemon, statsChanged: BattleStat[], levelChanged: integer, selfTarget: boolean, args: any[]): boolean | Promise { + return false; + } +} + export class MoveImmunityAbAttr extends PreDefendAbAttr { private immuneCondition: PreDefendAbAttrCondition; @@ -614,6 +621,29 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { } } +export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr { + private condition: PokemonStatChangeCondition; + private statsToChange: BattleStat[]; + private levels: integer; + + constructor(condition: PokemonStatChangeCondition, statsToChange: BattleStat[], levels: integer) { + super(true); + + this.condition = condition; + this.statsToChange = statsToChange; + this.levels = levels; + } + + applyPostStatChange(pokemon: Pokemon, statsChanged: BattleStat[], levelsChanged: integer, selfTarget: boolean, args: any[]): boolean { + if (this.condition(pokemon, statsChanged, levelsChanged) && !selfTarget) { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (pokemon).getBattlerIndex(), true, this.statsToChange, this.levels)); + return true; + } + + return false; + } +} + export class PreAttackAbAttr extends AbAttr { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: PokemonMove, args: any[]): boolean | Promise { return false; @@ -2036,6 +2066,11 @@ export function applyPreStatChangeAbAttrs(attrType: { new(...args: any[]): PreSt return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, stat, cancelled, args), args); } +export function applyPostStatChangeAbAttrs(attrType: { new(...args: any[]): PostStatChangeAbAttr }, + pokemon: Pokemon, stats: BattleStat[], levels: integer, selfTarget: boolean, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostStatChange(pokemon, stats, levels, selfTarget, args), args); +} + export function applyPreSetStatusAbAttrs(attrType: { new(...args: any[]): PreSetStatusAbAttr }, pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { const simulated = args.length > 1 && args[1]; @@ -2419,7 +2454,8 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.UNNERVE, "Unnerve", "Unnerves opposing Pokémon and makes them unable to eat Berries.", 5) .attr(PreventBerryUseAbAttr), - new Ability(Abilities.DEFIANT, "Defiant (N)", "Boosts the Pokémon's Attack stat sharply when its stats are lowered.", 5), + new Ability(Abilities.DEFIANT, "Defiant", "Boosts the Pokémon's Attack stat sharply when its stats are lowered.", 5) + .attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.ATK], 2), new Ability(Abilities.DEFEATIST, "Defeatist", "Halves the Pokémon's Attack and Sp. Atk stats when its HP becomes half or less.", 5) .attr(BattleStatMultiplierAbAttr, BattleStat.ATK, 0.5) .attr(BattleStatMultiplierAbAttr, BattleStat.SPATK, 0.5) diff --git a/src/phases.ts b/src/phases.ts index 659e30df9a7..7e5a3aab1d6 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr } from "./data/ability"; +import { CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs } from "./data/ability"; import { Abilities } from "./data/enums/abilities"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; @@ -2632,6 +2632,7 @@ export class StatChangePhase extends PokemonPhase { for (let stat of filteredStats) pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); + applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget) this.end(); }; From f358e302e9a9a2659615f100a71f05c05ff10d13 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 14 Apr 2024 14:36:44 -0400 Subject: [PATCH 06/23] Give Ferroseed duplicate ability as hidden --- src/data/pokemon-species.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index f1fc1827dbb..e582fdfce1d 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1617,7 +1617,7 @@ export function initSpecies() { new PokemonSpecies(Species.ALOMOMOLA, "Alomomola", 5, false, false, false, "Caring Pokémon", Type.WATER, null, 1.2, 31.6, Abilities.HEALER, Abilities.HYDRATION, Abilities.REGENERATOR, 470, 165, 75, 80, 40, 45, 65, 75, 70, 165, GrowthRate.FAST, 50, false), new PokemonSpecies(Species.JOLTIK, "Joltik", 5, false, false, false, "Attaching Pokémon", Type.BUG, Type.ELECTRIC, 0.1, 0.6, Abilities.COMPOUND_EYES, Abilities.UNNERVE, Abilities.SWARM, 319, 50, 47, 50, 57, 50, 65, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GALVANTULA, "Galvantula", 5, false, false, false, "EleSpider Pokémon", Type.BUG, Type.ELECTRIC, 0.8, 14.3, Abilities.COMPOUND_EYES, Abilities.UNNERVE, Abilities.SWARM, 472, 70, 77, 60, 97, 60, 108, 75, 50, 165, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.FERROSEED, "Ferroseed", 5, false, false, false, "Thorn Seed Pokémon", Type.GRASS, Type.STEEL, 0.6, 18.8, Abilities.IRON_BARBS, Abilities.NONE, Abilities.NONE, 305, 44, 50, 91, 24, 86, 10, 255, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(Species.FERROSEED, "Ferroseed", 5, false, false, false, "Thorn Seed Pokémon", Type.GRASS, Type.STEEL, 0.6, 18.8, Abilities.IRON_BARBS, Abilities.NONE, Abilities.IRON_BARBS, 305, 44, 50, 91, 24, 86, 10, 255, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.FERROTHORN, "Ferrothorn", 5, false, false, false, "Thorn Pod Pokémon", Type.GRASS, Type.STEEL, 1, 110, Abilities.IRON_BARBS, Abilities.NONE, Abilities.ANTICIPATION, 489, 74, 94, 131, 54, 116, 20, 90, 50, 171, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.KLINK, "Klink", 5, false, false, false, "Gear Pokémon", Type.STEEL, null, 0.3, 21, Abilities.PLUS, Abilities.MINUS, Abilities.CLEAR_BODY, 300, 40, 55, 70, 45, 60, 30, 130, 50, 60, GrowthRate.MEDIUM_SLOW, null, false), new PokemonSpecies(Species.KLANG, "Klang", 5, false, false, false, "Gear Pokémon", Type.STEEL, null, 0.6, 51, Abilities.PLUS, Abilities.MINUS, Abilities.CLEAR_BODY, 440, 60, 80, 95, 70, 85, 50, 60, 50, 154, GrowthRate.MEDIUM_SLOW, null, false), From 8ccdf6d55463961a836ff49b8c3f0aaffb11d1a1 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 14 Apr 2024 15:47:00 -0400 Subject: [PATCH 07/23] Fix cachebusting --- src/battle-scene.ts | 3 --- src/loading-scene.ts | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 35c7951c344..0efc4dc8ddb 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -55,7 +55,6 @@ import PokemonInfoContainer from './ui/pokemon-info-container'; import { biomeDepths } from './data/biomes'; import { initTouchControls } from './touch-controls'; import { UiTheme } from './enums/ui-theme'; -import CacheBustedLoaderPlugin from './plugins/cache-busted-loader-plugin'; import { SceneBase } from './scene-base'; import CandyBar from './ui/candy-bar'; @@ -214,8 +213,6 @@ export default class BattleScene extends SceneBase { this.phaseQueuePrepend = []; this.phaseQueuePrependSpliceIndex = -1; this.nextCommandPhaseQueue = []; - - Phaser.Plugins.PluginCache.register('Loader', CacheBustedLoaderPlugin, 'load'); } loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) { diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 9a3b7fdd5bd..a3b92112990 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -3,6 +3,7 @@ import { Biome } from "./data/enums/biome"; import { TrainerType } from "./data/enums/trainer-type"; import { trainerConfigs } from "./data/trainer-config"; import { getBiomeHasProps } from "./field/arena"; +import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin"; import { SceneBase } from "./scene-base"; import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme"; import * as Utils from "./utils"; @@ -10,6 +11,8 @@ import * as Utils from "./utils"; export class LoadingScene extends SceneBase { constructor() { super('loading'); + + Phaser.Plugins.PluginCache.register('Loader', CacheBustedLoaderPlugin, 'load'); } preload() { From c8445ba8dd5dfca18807e5e2f2226bb4ea53c9ba Mon Sep 17 00:00:00 2001 From: Appo <72869654+AppleOfTheDark@users.noreply.github.com> Date: Sun, 14 Apr 2024 21:50:26 +0100 Subject: [PATCH 08/23] Implement Competitive Since Competitive is just Defiant but for SPATK, the code for Defiant was reused with the buff changed to special attack. This should mean the ability works the exact same way as Defiant for it's respective stat. --- src/data/ability.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 2ad6959cbd7..a0645d4af9c 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2563,7 +2563,8 @@ export function initAbilities() { new Ability(Abilities.BULLETPROOF, "Bulletproof", "Protects the Pokémon from some ball and bomb moves.", 6) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.getMove().hasFlag(MoveFlags.BALLBOMB_MOVE)) .ignorable(), - new Ability(Abilities.COMPETITIVE, "Competitive (N)", "Boosts the Sp. Atk stat sharply when a stat is lowered.", 6), + new Ability(Abilities.COMPETITIVE, "Competitive", "Boosts the Sp. Atk stat sharply when a stat is lowered.", 6) + .attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2), new Ability(Abilities.STRONG_JAW, "Strong Jaw", "The Pokémon's strong jaw boosts the power of its biting moves.", 6) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), new Ability(Abilities.REFRIGERATE, "Refrigerate", "Normal-type moves become Ice-type moves. The power of those moves is boosted a little.", 6) From 8932a0b6f9e08ea4825eec7d058ae638ef93c195 Mon Sep 17 00:00:00 2001 From: shayebeadlingkl Date: Sun, 14 Apr 2024 17:43:03 -0400 Subject: [PATCH 09/23] Implements Nature Power --- src/data/move.ts | 107 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index e5ff01c638f..37f63abc2cf 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -23,6 +23,7 @@ import { SpeciesFormChangeActiveTrigger } from "./pokemon-forms"; import { Species } from "./enums/species"; import { ModifierPoolType } from "#app/modifier/modifier-type"; import { Command } from "../ui/command-ui-handler"; +import { Biome } from "./enums/biome"; export enum MoveCategory { PHYSICAL, @@ -2745,6 +2746,108 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { } } +export class NaturePowerAttr extends OverrideMoveEffectAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + return new Promise(resolve => { + var moveId; + switch (user.scene.arena.getTerrainType()) { + // this allows terrains to 'override' the biome move + case TerrainType.NONE: + switch (user.scene.arena.biomeType) { + case Biome.TOWN: + moveId = Moves.TRI_ATTACK; + break; + case Biome.PLAINS + || Biome.GRASS + || Biome.TALL_GRASS + || Biome.MEADOW + || Biome.FOREST + || Biome.JUNGLE: + moveId = Moves.ENERGY_BALL; + break; + case Biome.SEA + || Biome.SWAMP + || Biome.BEACH + || Biome.LAKE + || Biome.SEABED + || Biome.ISLAND: + moveId = Moves.HYDRO_PUMP; + break; + case Biome.MOUNTAIN: + moveId = Moves.AIR_SLASH; + break; + case Biome.BADLANDS + || Biome.DESERT + || Biome.WASTELAND + || Biome.CONSTRUCTION_SITE: + moveId = Moves.EARTH_POWER; + break; + case Biome.CAVE: + moveId = Moves.POWER_GEM; + break; + case Biome.ICE_CAVE + || Biome.SNOWY_FOREST: + moveId = Moves.ICE_BEAM; + break; + case Biome.VOLCANO: + moveId = Moves.LAVA_PLUME; + break; + case Biome.GRAVEYARD + || Biome.RUINS + || Biome.TEMPLE: + moveId = Moves.SHADOW_BALL; + break; + case Biome.DOJO: + moveId = Moves.AURA_SPHERE; + break; + case Biome.FAIRY_CAVE: + moveId = Moves.MOONBLAST; + break; + case Biome.ABYSS + || Biome.SPACE + || Biome.END: + moveId = Moves.DARK_PULSE; + break; + } + break; + case TerrainType.MISTY: + moveId = Moves.MOONBLAST; + break; + case TerrainType.ELECTRIC: + moveId = Moves.THUNDERBOLT; + break; + case TerrainType.GRASSY: + moveId = Moves.ENERGY_BALL; + break; + case TerrainType.PSYCHIC: + moveId = Moves.PSYCHIC; + break; + default: + // Just in case there's no match + moveId = Moves.TRI_ATTACK; + break; + } + + const moveTargets = getMoveTargets(user, moveId); + if (!moveTargets.targets.length) { + resolve(false); + return; + } + const targets = moveTargets.multiple || moveTargets.targets.length === 1 + ? moveTargets.targets + : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 + ? [ target.getBattlerIndex() ] + : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; + user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); + user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true)); + initMoveAnim(moveId).then(() => { + loadMoveAnimAssets(user.scene, [ moveId ], true) + .then(() => resolve(true)); + }); + }); + } +} + const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { const copiableMove = user.scene.currentBattle.lastMove; @@ -3875,7 +3978,9 @@ export function initMoves() { .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS), new SelfStatusMove(Moves.FOLLOW_ME, "Follow Me (N)", Type.NORMAL, -1, 20, "The user draws attention to itself, making all targets take aim only at the user.", -1, 2, 3), - new StatusMove(Moves.NATURE_POWER, "Nature Power (N)", Type.NORMAL, -1, 20, "This attack makes use of nature's power. Its effects vary depending on the user's environment.", -1, 0, 3), + new StatusMove(Moves.NATURE_POWER, "Nature Power", Type.NORMAL, -1, 20, "This attack makes use of nature's power. Its effects vary depending on the user's environment.", -1, 0, 3) + .attr(NaturePowerAttr) + .ignoresVirtual(), new SelfStatusMove(Moves.CHARGE, "Charge (P)", Type.ELECTRIC, -1, 20, "The user boosts the power of the Electric move it uses on the next turn. This also raises the user's Sp. Def stat.", -1, 0, 3) .attr(StatChangeAttr, BattleStat.SPDEF, 1, true), new StatusMove(Moves.TAUNT, "Taunt (N)", Type.DARK, 100, 20, "The target is taunted into a rage that allows it to use only attack moves for three turns.", -1, 0, 3), From 669bc367e5d003919616e242e78290e8e532fbbf Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 14 Apr 2024 18:19:35 -0400 Subject: [PATCH 10/23] Fix Super Luck working on the wrong side --- src/field/pokemon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index c396ca37bcc..f7832d612d0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1186,7 +1186,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(HighCritAttr, source, this, move, critLevel); this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel); const bonusCrit = new Utils.BooleanHolder(false); - if (applyAbAttrs(BonusCritAbAttr, this, null, bonusCrit)) { + if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) { if (bonusCrit.value) critLevel.value += 1; } From be9d07ab1a2bb43585395da30e94629e2ac5daca Mon Sep 17 00:00:00 2001 From: shayebeadlingkl Date: Sun, 14 Apr 2024 18:25:15 -0400 Subject: [PATCH 11/23] fixes targeting --- src/data/move.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 37f63abc2cf..e4eed04b8ae 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2790,7 +2790,7 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { moveId = Moves.ICE_BEAM; break; case Biome.VOLCANO: - moveId = Moves.LAVA_PLUME; + moveId = Moves.FLAMETHROWER; break; case Biome.GRAVEYARD || Biome.RUINS @@ -2828,18 +2828,8 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { break; } - const moveTargets = getMoveTargets(user, moveId); - if (!moveTargets.targets.length) { - resolve(false); - return; - } - const targets = moveTargets.multiple || moveTargets.targets.length === 1 - ? moveTargets.targets - : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 - ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); - user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true)); + user.getMoveQueue().push({ move: moveId, targets: [target.getBattlerIndex()], ignorePP: true }); + user.scene.unshiftPhase(new MovePhase(user.scene, user, [target.getBattlerIndex()], new PokemonMove(moveId, 0, 0, true), true)); initMoveAnim(moveId).then(() => { loadMoveAnimAssets(user.scene, [ moveId ], true) .then(() => resolve(true)); From 208279ce772a8dd43feddf221b04feb925f8424e Mon Sep 17 00:00:00 2001 From: surniki Date: Sun, 14 Apr 2024 17:00:50 -0500 Subject: [PATCH 12/23] Copies the moveset of the evolving Nincada to the newly generated pokemon for Shedinja. --- src/field/pokemon.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f7832d612d0..9fbe01f53e1 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2260,6 +2260,7 @@ export class PlayerPokemon extends Pokemon { if (newEvolution.condition.predicate(this)) { const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature); newPokemon.natureOverride = this.natureOverride; + newPokemon.moveset = this.moveset.slice(); newPokemon.fusionSpecies = this.fusionSpecies; newPokemon.fusionFormIndex = this.fusionFormIndex; newPokemon.fusionAbilityIndex = this.fusionAbilityIndex; From 99aa7854c0bdceca7fb07d9bd42b96c164c366de Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 14 Apr 2024 20:23:51 -0400 Subject: [PATCH 13/23] Prevent replacing learnset when species is overridden --- src/field/pokemon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 9fbe01f53e1..16e695769e7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -850,10 +850,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return true; }); } else - levelMoves = this.getSpeciesForm().getLevelMoves(); + levelMoves = this.getSpeciesForm(true).getLevelMoves(); if (this.fusionSpecies) { const evolutionLevelMoves = levelMoves.slice(0, Math.max(levelMoves.findIndex(lm => !!lm[0]), 0)); - const fusionLevelMoves = this.getFusionSpeciesForm().getLevelMoves(); + const fusionLevelMoves = this.getFusionSpeciesForm(true).getLevelMoves(); const newLevelMoves: LevelMoves = []; while (levelMoves.length && levelMoves[0][0] < startingLevel) levelMoves.shift(); From e0dd7d6d48fe2f59c875cf0edfb671b6f1b43267 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Sun, 14 Apr 2024 20:30:54 -0500 Subject: [PATCH 14/23] Implement Shields Down's form change; Fix wild Rotom forms not appearing Shields Down is only partially implemented; the form changing aspect is implemented, but the immunity to statuses is not yet. Minior's alternate colored forms can now be encountered in the wild. Rotom's appliance forms should *finally* appear in the wild correctly now. And a minor fix for Zen Mode activating incorrectly. --- src/battle-scene.ts | 2 ++ src/data/ability.ts | 11 +++++++---- src/data/pokemon-forms.ts | 16 ++++++++++++++++ src/field/arena.ts | 14 ++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 0efc4dc8ddb..30c6c560368 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -936,6 +936,8 @@ export default class BattleScene extends SceneBase { case Species.TATSUGIRI: case Species.PALDEA_TAUROS: return Utils.randSeedInt(species.forms.length); + case Species.MINIOR: + return Utils.randSeedInt(6); case Species.MEOWSTIC: case Species.INDEEDEE: case Species.BASCULEGION: diff --git a/src/data/ability.ts b/src/data/ability.ts index a0645d4af9c..7cb03d7e7e3 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2536,9 +2536,9 @@ export function initAbilities() { .attr(PostDefendContactDamageAbAttr, 8) .bypassFaint(), new Ability(Abilities.ZEN_MODE, "Zen Mode", "Changes the Pokémon's shape when HP is half or less.", 5) - .attr(PostBattleInitFormChangeAbAttr, p => p.getHpRatio() >= 0.5 ? 0 : 1) - .attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() >= 0.5 ? 0 : 1) - .attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() >= 0.5 ? 0 : 1) + .attr(PostBattleInitFormChangeAbAttr, p => p.getHpRatio() <= 0.5 ? 1 : 0) + .attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 ? 1 : 0) + .attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 ? 1 : 0) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr), @@ -2616,7 +2616,10 @@ export function initAbilities() { new Ability(Abilities.WATER_COMPACTION, "Water Compaction", "Boosts the Pokémon's Defense stat sharply when hit by a Water-type move.", 7) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER, BattleStat.DEF, 2), new Ability(Abilities.MERCILESS, "Merciless (N)", "The Pokémon's attacks become critical hits if the target is poisoned.", 7), - new Ability(Abilities.SHIELDS_DOWN, "Shields Down (N)", "When its HP becomes half or less, the Pokémon's shell breaks and it becomes aggressive.", 7) + new Ability(Abilities.SHIELDS_DOWN, "Shields Down (P)", "When its HP becomes half or less, the Pokémon's shell breaks and it becomes aggressive.", 7) + .attr(PostBattleInitFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) + .attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) + .attr(PostTurnFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr), diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index e5bcf25a4fe..2d2d491bf31 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -549,6 +549,22 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.WISHIWASHI, '', 'school', new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.WISHIWASHI, 'school', '', new SpeciesFormChangeManualTrigger(), true) ], + [Species.MINIOR]: [ + new SpeciesFormChange(Species.MINIOR, 'red-meteor', 'red', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'red', 'red-meteor', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'orange-meteor', 'orange', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'orange', 'orange-meteor', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'yellow-meteor', 'yellow', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'yellow', 'yellow-meteor', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'green-meteor', 'green', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'green', 'green-meteor', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'blue-meteor', 'blue', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'blue', 'blue-meteor', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'indigo-meteor', 'indigo', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'indigo', 'indigo-meteor', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'violet-meteor', 'violet', new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.MINIOR, 'violet', 'violet-meteor', new SpeciesFormChangeManualTrigger(), true) + ], [Species.NECROZMA]: [ new SpeciesFormChange(Species.NECROZMA, '', 'dawn-wings', new SpeciesFormChangeItemTrigger(FormChangeItem.N_LUNARIZER)), new SpeciesFormChange(Species.NECROZMA, '', 'dusk-mane', new SpeciesFormChangeItemTrigger(FormChangeItem.N_SOLARIZER)) diff --git a/src/field/arena.ts b/src/field/arena.ts index 11677dc490e..a3ba8cb69ce 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -166,6 +166,20 @@ export class Arena { return 2; } break; + case Species.ROTOM: + switch (this.biomeType) { + case Biome.VOLCANO: + return 1; + case Biome.SEA: + return 2; + case Biome.ICE_CAVE: + return 3; + case Biome.MOUNTAIN: + return 4; + case Biome.TALL_GRASS: + return 5; + } + break; case Species.SCATTERBUG: case Species.SPEWPA: case Species.VIVILLON: From d699a0f7d4f2422495733fd7ea2b45c08e263cb5 Mon Sep 17 00:00:00 2001 From: lucfd <83493765+lucfd@users.noreply.github.com> Date: Sun, 14 Apr 2024 22:26:58 -0400 Subject: [PATCH 15/23] Implemented barrier-breaking moves (#130) * Implemented barrier-breaking moves * removed (P) from move names * refactored to support defog --- src/data/move.ts | 50 ++++++++++++++++++++++++++++++++++++++++------ src/field/arena.ts | 10 ++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index e4eed04b8ae..db563c9f425 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2531,6 +2531,40 @@ export class AddArenaTrapTagAttr extends AddArenaTagAttr { } } +export class RemoveScreensAttr extends MoveEffectAttr { + + private targetBothSides: boolean; + + constructor(targetBothSides: boolean = false) { + super(true, MoveEffectTrigger.PRE_APPLY); + this.targetBothSides = targetBothSides; + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + + if (!super.apply(user, target, move, args)) + return false; + + if(this.targetBothSides){ + user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.PLAYER); + user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.PLAYER); + user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.PLAYER); + + user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.ENEMY); + user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.ENEMY); + user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.ENEMY); + } + else{ + user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + } + + return true; + + } +} + export class ForceSwitchOutAttr extends MoveEffectAttr { private user: boolean; private batonPass: boolean; @@ -3993,7 +4027,8 @@ export function initMoves() { new SelfStatusMove(Moves.RECYCLE, "Recycle (N)", Type.NORMAL, -1, 10, "The user recycles a held item that has been used in battle so it can be used again.", -1, 0, 3), new AttackMove(Moves.REVENGE, "Revenge", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, "This attack move's power is doubled if the user has been hurt by the opponent in the same turn.", -1, -4, 3) .attr(TurnDamagedDoublePowerAttr), - new AttackMove(Moves.BRICK_BREAK, "Brick Break (P)", Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, "The user attacks with a swift chop. It can also break barriers, such as Light Screen and Reflect.", -1, 0, 3), + new AttackMove(Moves.BRICK_BREAK, "Brick Break", Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, "The user attacks with a swift chop. It can also break barriers, such as Light Screen and Reflect.", -1, 0, 3) + .attr(RemoveScreensAttr), new StatusMove(Moves.YAWN, "Yawn", Type.NORMAL, -1, 10, "The user lets loose a huge yawn that lulls the target into falling asleep on the next turn.", -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) .condition((user, target, move) => !target.status), @@ -4369,7 +4404,8 @@ export function initMoves() { new StatusMove(Moves.DEFOG, "Defog", Type.FLYING, -1, 15, "A strong wind blows away the target's barriers such as Reflect or Light Screen. This also lowers the target's evasiveness.", -1, 0, 4) .attr(StatChangeAttr, BattleStat.EVA, -1) .attr(ClearWeatherAttr, WeatherType.FOG) - .attr(ClearTerrainAttr), + .attr(ClearTerrainAttr) + .attr(RemoveScreensAttr, true), new StatusMove(Moves.TRICK_ROOM, "Trick Room", Type.PSYCHIC, -1, 5, "The user creates a bizarre area in which slower Pokémon get to move first for five turns.", -1, -7, 4) .attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5) .ignoresProtect() @@ -4967,8 +5003,9 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.FLEUR_CANNON, "Fleur Cannon", Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, "The user unleashes a strong beam. The attack's recoil harshly lowers the user's Sp. Atk stat.", 100, 0, 7) .attr(StatChangeAttr, BattleStat.SPATK, -2, true), - new AttackMove(Moves.PSYCHIC_FANGS, "Psychic Fangs (P)", Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, "The user bites the target with its psychic capabilities. This can also destroy Light Screen and Reflect.", -1, 0, 7) - .bitingMove(), + new AttackMove(Moves.PSYCHIC_FANGS, "Psychic Fangs", Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, "The user bites the target with its psychic capabilities. This can also destroy Light Screen and Reflect.", -1, 0, 7) + .bitingMove() + .attr(RemoveScreensAttr), new AttackMove(Moves.STOMPING_TANTRUM, "Stomping Tantrum (P)", Type.GROUND, MoveCategory.PHYSICAL, 75, 100, 10, "Driven by frustration, the user attacks the target. If the user's previous move has failed, the power of this move doubles.", -1, 0, 7), new AttackMove(Moves.SHADOW_BONE, "Shadow Bone", Type.GHOST, MoveCategory.PHYSICAL, 85, 100, 10, "The user attacks by beating the target with a bone that contains a spirit. This may also lower the target's Defense stat.", 20, 0, 7) .attr(StatChangeAttr, BattleStat.DEF, -1) @@ -5445,8 +5482,9 @@ export function initMoves() { new AttackMove(Moves.AQUA_STEP, "Aqua Step", Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, "The user toys with the target and attacks it using light and fluid dance steps. This also boosts the user's Speed stat.", 100, 0, 9) .attr(StatChangeAttr, BattleStat.SPD, 1, true) .danceMove(), - new AttackMove(Moves.RAGING_BULL, "Raging Bull (P)", Type.NORMAL, MoveCategory.PHYSICAL, 90, 100, 10, "The user performs a tackle like a raging bull. This move's type depends on the user's form. It can also break barriers, such as Light Screen and Reflect.", -1, 0, 9) - .attr(RagingBullTypeAttr), + new AttackMove(Moves.RAGING_BULL, "Raging Bull", Type.NORMAL, MoveCategory.PHYSICAL, 90, 100, 10, "The user performs a tackle like a raging bull. This move's type depends on the user's form. It can also break barriers, such as Light Screen and Reflect.", -1, 0, 9) + .attr(RagingBullTypeAttr) + .attr(RemoveScreensAttr), new AttackMove(Moves.MAKE_IT_RAIN, "Make It Rain", Type.STEEL, MoveCategory.SPECIAL, 120, 100, 5, "The user attacks by throwing out a mass of coins. This also lowers the user's Sp. Atk stat. Money is earned after the battle.", -1, 0, 9) .attr(MoneyAttr) .attr(StatChangeAttr, BattleStat.SPATK, -1, true, null, true, true) diff --git a/src/field/arena.ts b/src/field/arena.ts index a3ba8cb69ce..910d998d68e 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -541,6 +541,16 @@ export class Arena { return !!tag; } + removeTagOnSide(tagType: ArenaTagType, side: ArenaTagSide): boolean { + const tag = this.getTagOnSide(tagType, side); + if (tag) { + tag.onRemove(this); + this.tags.splice(this.tags.indexOf(tag), 1); + } + return !!tag; + } + + removeAllTags(): void { while (this.tags.length) { this.tags[0].onRemove(this); From 032ab95756d96fb43a0f358085068f209f2b5115 Mon Sep 17 00:00:00 2001 From: Matthew Ross Date: Sun, 14 Apr 2024 19:26:35 -0700 Subject: [PATCH 16/23] Additional check for can apply ability after move used --- src/phases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases.ts b/src/phases.ts index 7e5a3aab1d6..ff2f62c3eb9 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2389,7 +2389,7 @@ export class MoveEffectPhase extends PokemonPhase { } Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => { - return Utils.executeIf(!target.isFainted(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult).then(() => { + return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult).then(() => { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) user.scene.applyModifiers(EnemyAttackStatusEffectChanceModifier, false, target); })).then(() => { From e7f1969099d36887766f6051db18307e6cc8d7b7 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Sun, 14 Apr 2024 21:41:12 -0500 Subject: [PATCH 17/23] Implement Ivy Cudgel's type changing property Basically a copy/paste of Raging Bull's code. --- src/data/move.ts | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index db563c9f425..841327b42f3 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2017,6 +2017,45 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr { } } +export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) { + const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex; + const type = (args[0] as Utils.IntegerHolder); + + switch (form) { + case 1: // Wellspring Mask + type.value = Type.WATER; + break; + case 2: // Hearthflame Mask + type.value = Type.FIRE; + break; + case 3: // Cornerstone Mask + type.value = Type.ROCK; + break; + case 4: // Teal Mask Tera + type.value = Type.GRASS; + break; + case 5: // Wellspring Mask Tera + type.value = Type.WATER; + break; + case 6: // Hearthflame Mask Tera + type.value = Type.FIRE; + break; + case 7: // Cornerstone Mask Tera + type.value = Type.ROCK; + break; + default: + type.value = Type.GRASS; + break; + } + return true; + } + + return false; + } +} + export class WeatherBallTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { @@ -5572,7 +5611,8 @@ export function initMoves() { new AttackMove(Moves.SYRUP_BOMB, "Syrup Bomb (P)", Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, "The user sets off an explosion of sticky candy syrup, which coats the target and causes the target's Speed stat to drop each turn for three turns.", -1, 0, 9) .attr(StatChangeAttr, BattleStat.SPD, -1) //Temporary .ballBombMove(), - new AttackMove(Moves.IVY_CUDGEL, "Ivy Cudgel (P)", Type.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, "The user strikes with an ivy-wrapped cudgel. This move's type changes depending on the mask worn by the user, and it has a heightened chance of landing a critical hit.", -1, 0, 9) + new AttackMove(Moves.IVY_CUDGEL, "Ivy Cudgel", Type.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, "The user strikes with an ivy-wrapped cudgel. This move's type changes depending on the mask worn by the user, and it has a heightened chance of landing a critical hit.", -1, 0, 9) + .attr(IvyCudgelTypeAttr) .attr(HighCritAttr) .makesContact(false), new AttackMove(Moves.ELECTRO_SHOT, "Electro Shot", Type.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, "The user gathers electricity on the first turn, boosting its Sp. Atk stat, then fires a high-voltage shot on the next turn. The shot will be fired immediately in rain.", 100, 0, 9) From 8dd0aa538435ab97fe5585cf1cf09f4c8b342338 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 14 Apr 2024 22:56:25 -0400 Subject: [PATCH 18/23] Fix some female breeder names showing as male names --- src/data/trainer-names.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/trainer-names.ts b/src/data/trainer-names.ts index 8d7f2e4fdce..a1c83af0779 100644 --- a/src/data/trainer-names.ts +++ b/src/data/trainer-names.ts @@ -79,7 +79,7 @@ export const trainerNamePools = { [TrainerType.BEAUTY]: ["Cassie","Julia","Olivia","Samantha","Valerie","Victoria","Bridget","Connie","Jessica","Johanna","Melissa","Sheila","Shirley","Tiffany","Namiko","Thalia","Grace","Lola","Lori","Maura","Tamia","Cyndy","Devon","Gabriella","Harley","Lindsay","Nicola","Callie","Charlotte","Kassandra","December","Fleming","Nikola","Aimee","Anais","Brigitte","Cassandra","Andrea","Brittney","Carolyn","Krystal","Alexis","Alice","Aina","Anya","Arianna","Aubrey","Beverly","Camille","Beauty","Evette","Hansol","Haruka","Jill","Jo","Lana","Lois","Lucy","Mai","Nickie","Nicole","Prita","Rose","Shelly","Suzy","Tessa","Anita","Alissa","Rita","Cudsy","Eloff","Miru","Minot","Nevah","Niven","Ogoin"], [TrainerType.BIKER]: ["Charles","Dwayne","Glenn","Harris","Joel","Riley","Zeke","Alex","Billy","Ernest","Gerald","Hideo","Isaac","Jared","Jaren","Jaxon","Jordy","Lao","Lukas","Malik","Nikolas","Ricardo","Ruben","Virgil","William","Aiden","Dale","Dan","Jacob","Markey","Reese","Teddy","Theron","Jeremy","Morgann","Phillip","Philip","Stanley","Dillon"], [TrainerType.BLACK_BELT]: [["Kenji","Lao","Lung","Nob","Wai","Yoshi","Atsushi","Daisuke","Hideki","Hitoshi","Kiyo","Koichi","Koji","Yuji","Cristian","Rhett","Takao","Theodore","Zander","Aaron","Hugh","Mike","Nicolas","Shea","Takashi","Adam","Carl","Colby","Darren","David","Davon","Derek","Eddie","Gregory","Griffin","Jarrett","Jeffery","Kendal","Kyle","Luke","Miles","Nathaniel","Philip","Rafael","Ray","Ricky","Sean","Willie","Ander","Manford","Benjamin","Corey","Edward","Grant","Jay","Kendrew","Kentaro","Ryder","Teppei","Thomas","Tyrone","Andrey","Donny","Drago","Gordon","Grigor","Jeriel","Kenneth","Martell","Mathis","Rich","Rocky","Rodrigo","Wesley","Zachery","Alonzo","Cadoc","Gunnar","Igor","Killian","Markus","Ricardo","Yanis","Banting","Clayton","Duane","Earl","Greg","Roy","Terry","Tracy","Walter","Alvaro","Curtis","Francis","Ross","Brice","Cheng","Dudley","Eric","Kano","Masahiro","Randy","Ryuji","Steve","Tadashi","Wong","Yuen","Brian","Carter","Reece","Nick","Yang"],["Cora","Cyndy","Jill","Laura","Sadie","Tessa","Vivian","Aisha","Callie","Danielle","Helene","Jocelyn","Lilith","Paula","Reyna","Helen","Kelsey","Tyler","Amy","Chandra","Hillary","Janie","Lee","Maggie","Mikiko","Miriam","Sharon","Susie","Xiao","Alize","Azra","Brenda","Chalina","Chan","Glinda","Maki","Tia","Tiffany","Wendy","Andrea","Gabrielle","Gerardine","Hailey","Hedvig","Justine","Kinsey","Sigrid","Veronique","Tess"]], - [TrainerType.BREEDER]: [["Isaac","Myles","Salvadore","Allison","Alize","Bethany","Lily","Albert","Kahlil","Eustace","Galen","Owen","Addison","Marcus","Foster","Cory","Glenn","Jay","Wesley","William","Adrian","Bradley","Jaime"],["Lydia","Gabrielle","Jayden","Pat","Veronica","Amber","Jennifer","Kaylee","Adelaide","Brooke","Ethel","April","Irene","Magnolia","Amala","Mercy","Amanda","Ikue","Savannah","Yuka","Chloe","Debra","Denise","Elena"]], + [TrainerType.BREEDER]: [["Isaac","Myles","Salvadore","Albert","Kahlil","Eustace","Galen","Owen","Addison","Marcus","Foster","Cory","Glenn","Jay","Wesley","William","Adrian","Bradley","Jaime"],["Allison","Alize","Bethany","Lily","Lydia","Gabrielle","Jayden","Pat","Veronica","Amber","Jennifer","Kaylee","Adelaide","Brooke","Ethel","April","Irene","Magnolia","Amala","Mercy","Amanda","Ikue","Savannah","Yuka","Chloe","Debra","Denise","Elena"]], [TrainerType.CLERK]: [["Chaz","Clemens","Doug","Fredric","Ivan","Isaac","Nelson","Wade","Warren","Augustin","Gilligan","Cody","Jeremy","Shane","Dugal","Royce","Ronald"],["Alberta","Ingrid","Katie","Piper","Trisha","Wren","Britney","Lana","Jessica","Kristen","Michelle","Gabrielle"]], [TrainerType.CYCLIST]: [["Axel","James","John","Ryan","Hector","Jeremiah"],["Kayla","Megan","Nicole","Rachel","Krissa","Adelaide"]], [TrainerType.DANCER]: ["Brian","Davey","Dirk","Edmond","Mickey","Raymond","Cara","Julia","Maika","Mireille","Ronda","Zoe"], From 8819473dced5a9c3d6c1e471e520143c573e5578 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 14 Apr 2024 23:46:20 -0400 Subject: [PATCH 19/23] Lower price of Sacred Ash --- src/modifier/modifier-type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 258e79e3651..8078a7c06b9 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1341,7 +1341,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25) ], [ - new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 12) + new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10) ] ]; return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); From 6881246d17c352a4201eb3ec4b9469eee7774efb Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Mon, 15 Apr 2024 15:03:50 +1000 Subject: [PATCH 20/23] Implement check functions for abilities --- src/data/battler-tags.ts | 4 ++-- src/data/move.ts | 10 +++++----- src/data/pokemon-forms.ts | 4 ++-- src/data/weather.ts | 2 +- src/field/pokemon.ts | 24 ++++++++++++++++++++---- src/phases.ts | 2 +- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 20946c784cb..a45522b5e1e 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -799,7 +799,7 @@ export class TruantTag extends AbilityBattlerTag { } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - if ((!pokemon.canApplyAbility() || pokemon.getAbility().id !== Abilities.TRUANT) && (!pokemon.canApplyAbility(true) || pokemon.getPassiveAbility().id !== Abilities.TRUANT)) + if (!pokemon.hasAbility(Abilities.TRUANT)) return super.lapse(pokemon, lapseType); const passive = pokemon.getAbility().id !== Abilities.TRUANT; @@ -827,7 +827,7 @@ export class SlowStartTag extends AbilityBattlerTag { } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - if ((!pokemon.canApplyAbility() || pokemon.getAbility().id !== this.ability) && (!pokemon.canApplyAbility(true) || pokemon.getPassiveAbility().id !== this.ability)) + if (!pokemon.hasAbility(this.ability)) this.turnCount = 1; return super.lapse(pokemon, lapseType); diff --git a/src/data/move.ts b/src/data/move.ts index 841327b42f3..de749ba3ad8 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -275,7 +275,7 @@ export default class Move { checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon): boolean { switch (flag) { case MoveFlags.MAKES_CONTACT: - if ((user.canApplyAbility() && user.getAbility().hasAttr(IgnoreContactAbAttr)) || (user.canApplyAbility(true) && user.getPassiveAbility().hasAttr(IgnoreContactAbAttr))) + if (user.hasAbilityWithAttr(IgnoreContactAbAttr)) return false; break; } @@ -4849,9 +4849,9 @@ export function initMoves() { .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true) .ignoresVirtual(), new StatusMove(Moves.MAGNETIC_FLUX, "Magnetic Flux", Type.ELECTRIC, -1, 20, "The user manipulates magnetic fields, which raises the Defense and Sp. Def stats of ally Pokémon with the Plus or Minus Ability.", -1, 0, 6) - .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => a === user.getAbility().id || (user.canApplyPassive() && a === user.getPassiveAbility().id))) + .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false))) .target(MoveTarget.USER_AND_ALLIES) - .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => a === p.getAbility().id || (user.canApplyPassive() && a === user.getPassiveAbility().id)))), + .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))), new StatusMove(Moves.HAPPY_HOUR, "Happy Hour (N)", Type.NORMAL, -1, 30, "Using Happy Hour doubles the amount of prize money received after battle.", -1, 0, 6) // No animation .target(MoveTarget.USER_SIDE), new StatusMove(Moves.ELECTRIC_TERRAIN, "Electric Terrain", Type.ELECTRIC, -1, 10, "The user electrifies the ground for five turns, powering up Electric-type moves. Pokémon on the ground no longer fall asleep.", -1, 0, 6) @@ -4979,9 +4979,9 @@ export function initMoves() { new SelfStatusMove(Moves.LASER_FOCUS, "Laser Focus", Type.NORMAL, -1, 30, "The user concentrates intensely. The attack on the next turn always results in a critical hit.", -1, 0, 7) .attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false), new StatusMove(Moves.GEAR_UP, "Gear Up", Type.STEEL, -1, 20, "The user engages its gears to raise the Attack and Sp. Atk stats of ally Pokémon with the Plus or Minus Ability.", -1, 0, 7) - .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 1, false, (user, target, move) => [ Abilities.PLUS, Abilities.MINUS ].includes(target.getAbility().id) || (target.canApplyPassive() && [ Abilities.PLUS, Abilities.MINUS ].includes(target.getPassiveAbility().id))) + .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false))) .target(MoveTarget.USER_AND_ALLIES) - .condition((user, target, move) => !![ user, user.getAlly() ].find(p => p && [ Abilities.PLUS, Abilities.MINUS ].includes(p.getAbility().id) || (target.canApplyPassive() && [ Abilities.PLUS, Abilities.MINUS ].includes(target.getPassiveAbility().id)))), + .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))), new AttackMove(Moves.THROAT_CHOP, "Throat Chop (P)", Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, "The user attacks the target's throat, and the resultant suffering prevents the target from using moves that emit sound for two turns.", 100, 0, 7), new AttackMove(Moves.POLLEN_PUFF, "Pollen Puff (P)", Type.BUG, MoveCategory.SPECIAL, 90, 100, 15, "The user attacks the enemy with a pollen puff that explodes. If the target is an ally, it gives the ally a pollen puff that restores its HP instead.", -1, 0, 7) .ballBombMove(), diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 2d2d491bf31..bb22f985976 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -535,8 +535,8 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.MELOETTA, 'pirouette', 'aria', new SpeciesFormChangeActiveTrigger(false), true) ], [Species.AEGISLASH]: [ - new SpeciesFormChange(Species.AEGISLASH, 'blade', 'shield', new SpeciesFormChangePreMoveTrigger(Moves.KINGS_SHIELD), true, new SpeciesFormChangeCondition(p => p.getAbility().id === Abilities.STANCE_CHANGE)), - new SpeciesFormChange(Species.AEGISLASH, 'shield', 'blade', new SpeciesFormChangePreMoveTrigger(m => allMoves[m].category !== MoveCategory.STATUS), true, new SpeciesFormChangeCondition(p => p.getAbility().id === Abilities.STANCE_CHANGE)), + new SpeciesFormChange(Species.AEGISLASH, 'blade', 'shield', new SpeciesFormChangePreMoveTrigger(Moves.KINGS_SHIELD), true, new SpeciesFormChangeCondition(p => p.hasAbility(Abilities.STANCE_CHANGE))), + new SpeciesFormChange(Species.AEGISLASH, 'shield', 'blade', new SpeciesFormChangePreMoveTrigger(m => allMoves[m].category !== MoveCategory.STATUS), true, new SpeciesFormChangeCondition(p => p.hasAbility(Abilities.STANCE_CHANGE))), new SpeciesFormChange(Species.AEGISLASH, 'blade', 'shield', new SpeciesFormChangeActiveTrigger(false), true) ], [Species.DIANCIE]: [ diff --git a/src/data/weather.ts b/src/data/weather.ts index b3a59f34b98..b2881598450 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -108,7 +108,7 @@ export class Weather { for (let pokemon of field) { let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr; if (!suppressWeatherEffectAbAttr) - suppressWeatherEffectAbAttr = pokemon.canApplyPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr : null; + suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr : null; if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) return true; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 16e695769e7..9181be78edf 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -25,7 +25,7 @@ import { TempBattleStat } from '../data/temp-battle-stat'; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from '../data/arena-tag'; import { ArenaTagType } from "../data/enums/arena-tag-type"; import { Biome } from "../data/enums/biome"; -import { Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from '../data/ability'; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; import { BattlerIndex } from '../battle'; @@ -561,7 +561,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let value = Math.floor(((2 * baseStat + this.ivs[s]) * this.level) * 0.01); if (isHp) { value = value + this.level + 10; - if ((this.canApplyAbility() && this.getAbility().hasAttr(NonSuperEffectiveImmunityAbAttr)) || (this.canApplyAbility(true) && this.getPassiveAbility().hasAttr(NonSuperEffectiveImmunityAbAttr))) + if (this.hasAbility(Abilities.WONDER_GUARD, false, true)) value = 1; if (this.hp > value || this.hp === undefined) this.hp = value; @@ -730,12 +730,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[starterPassiveAbilities[starterSpeciesId]]; } - canApplyPassive(): boolean { + hasPassive(): boolean { return this.passive || this.isBoss(); } canApplyAbility(passive: boolean = false): boolean { - if (passive && !this.canApplyPassive()) + if (passive && !this.hasPassive()) return false; const ability = (!passive ? this.getAbility() : this.getPassiveAbility()); if (ability.isIgnorable && this.scene.arena.ignoreAbilities) @@ -743,6 +743,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return (this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this)); } + hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean { + if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) + return true; + if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) + return true; + return false; + } + + hasAbilityWithAttr(attrType: { new(...args: any[]): AbAttr }, canApply: boolean = true, ignoreOverride?: boolean): boolean { + if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) + return true; + if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType)) + return true; + return false; + } + getWeight(): number { const weight = new Utils.NumberHolder(this.species.weight); // This will trigger the ability overlay so only call this function when necessary diff --git a/src/phases.ts b/src/phases.ts index ff2f62c3eb9..58490e24cc8 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2190,7 +2190,7 @@ export class MovePhase extends BattlePhase { for (let opponent of targetedOpponents) { if (this.move.ppUsed === this.move.getMove().pp) break; - if ((opponent.canApplyAbility() && opponent.getAbility().hasAttr(IncreasePpAbAttr)) || (opponent.canApplyAbility(true) && opponent.getPassiveAbility().hasAttr(IncreasePpAbAttr))) + if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) this.move.ppUsed = Math.min(this.move.ppUsed + 1, this.move.getMovePp()); } } From 6e59b4dd77e6e1451d80de8c002083032f0307e3 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Mon, 15 Apr 2024 10:09:51 -0400 Subject: [PATCH 21/23] Prevent saving on an outdated client --- package.json | 2 +- src/phases.ts | 10 ++++++ src/system/game-data.ts | 5 +++ src/ui/outdated-modal-ui-handler.ts | 47 +++++++++++++++++++++++++++++ src/ui/ui.ts | 10 ++++-- 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/ui/outdated-modal-ui-handler.ts diff --git a/package.json b/package.json index fd189fd4d89..0765a180c7e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.0.0", + "version": "1.0.1", "type": "module", "scripts": { "start": "vite", diff --git a/src/phases.ts b/src/phases.ts index 58490e24cc8..f668278d273 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -379,6 +379,16 @@ export class UnavailablePhase extends Phase { } } +export class OutdatedPhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + this.scene.ui.setMode(Mode.OUTDATED); + } +} + export class SelectGenderPhase extends Phase { constructor(scene: BattleScene) { super(scene); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 78ed699e954..e267608e8f8 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -27,6 +27,7 @@ import { Moves } from "../data/enums/moves"; import { speciesEggMoves } from "../data/egg-moves"; import { allMoves } from "../data/move"; import { TrainerVariant } from "../field/trainer"; +import { OutdatedPhase, UnavailablePhase } from "#app/phases"; const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary @@ -269,6 +270,10 @@ export class GameData { .then(error => { this.scene.ui.savingIcon.hide(); if (error) { + if (error.startsWith('client version out of date')) { + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new OutdatedPhase(this.scene)); + } console.error(error); return resolve(false); } diff --git a/src/ui/outdated-modal-ui-handler.ts b/src/ui/outdated-modal-ui-handler.ts new file mode 100644 index 00000000000..53243c42bbe --- /dev/null +++ b/src/ui/outdated-modal-ui-handler.ts @@ -0,0 +1,47 @@ +import BattleScene from "../battle-scene"; +import { ModalConfig, ModalUiHandler } from "./modal-ui-handler"; +import { addTextObject, TextStyle } from "./text"; +import { Mode } from "./ui"; + +export default class OutdatedModalUiHandler extends ModalUiHandler { + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + } + + getModalTitle(): string { + return ''; + } + + getWidth(): number { + return 160; + } + + getHeight(): number { + return 64; + } + + getMargin(): [number, number, number, number] { + return [ 0, 0, 48, 0 ]; + } + + getButtonLabels(): string[] { + return [ ]; + } + + setup(): void { + super.setup(); + + const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, 'Your client is currently outdated.\nPlease reload to update the game.\n\nIf this error persists, please clear your browser cache.', TextStyle.WINDOW, { fontSize: '48px', align: 'center' }); + label.setOrigin(0.5, 0.5); + + this.modalContainer.add(label); + } + + show(args: any[]): boolean { + const config: ModalConfig = { + buttonActions: [] + }; + + return super.show([ config ]); + } +} \ No newline at end of file diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 422a0ab3e10..82eead45eb4 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -34,6 +34,7 @@ import SaveSlotSelectUiHandler from './save-slot-select-ui-handler'; import TitleUiHandler from './title-ui-handler'; import SavingIconHandler from './saving-icon-handler'; import UnavailableModalUiHandler from './unavailable-modal-ui-handler'; +import OutdatedModalUiHandler from './outdated-modal-ui-handler'; export enum Mode { MESSAGE, @@ -63,7 +64,8 @@ export enum Mode { LOGIN_FORM, REGISTRATION_FORM, LOADING, - UNAVAILABLE + UNAVAILABLE, + OUTDATED }; const transitionModes = [ @@ -90,7 +92,8 @@ const noTransitionModes = [ Mode.LOGIN_FORM, Mode.REGISTRATION_FORM, Mode.LOADING, - Mode.UNAVAILABLE + Mode.UNAVAILABLE, + Mode.OUTDATED ]; export default class UI extends Phaser.GameObjects.Container { @@ -141,7 +144,8 @@ export default class UI extends Phaser.GameObjects.Container { new LoginFormUiHandler(scene), new RegistrationFormUiHandler(scene), new LoadingModalUiHandler(scene), - new UnavailableModalUiHandler(scene) + new UnavailableModalUiHandler(scene), + new OutdatedModalUiHandler(scene) ]; } From 398f2c5be067a903429401b40d191302b226155f Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Mon, 15 Apr 2024 10:35:57 -0400 Subject: [PATCH 22/23] Fix being able to overwrite data without a confirmation --- src/ui/save-slot-select-ui-handler.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 054bb1ab9f9..21865045141 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -114,8 +114,10 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { ui.showText(null, 0); }, false, 0, 19, 2000); }); - } else + } else if (this.sessionSlots[this.cursor].hasData === false) saveAndCallback(); + else + return false; break; } success = true; @@ -210,7 +212,6 @@ class SessionSlot extends Phaser.GameObjects.Container { super(scene, 0, slotId * 56); this.slotId = slotId; - this.hasData = false; this.setup(); } @@ -282,6 +283,7 @@ class SessionSlot extends Phaser.GameObjects.Container { return new Promise(resolve => { this.scene.gameData.getSession(this.slotId).then(async sessionData => { if (!sessionData) { + this.hasData = false; this.loadingLabel.setText('Empty'); resolve(false); return; From 9d92ec2a72661e7e6b22826185b6b68637c70527 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Mon, 15 Apr 2024 10:45:40 -0400 Subject: [PATCH 23/23] Fix nature power using wrong syntax for switch case --- src/data/move.ts | 50 +++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index de749ba3ad8..db0c062e2e8 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2830,44 +2830,44 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { case Biome.TOWN: moveId = Moves.TRI_ATTACK; break; - case Biome.PLAINS - || Biome.GRASS - || Biome.TALL_GRASS - || Biome.MEADOW - || Biome.FOREST - || Biome.JUNGLE: + case Biome.PLAINS: + case Biome.GRASS: + case Biome.TALL_GRASS: + case Biome.MEADOW: + case Biome.FOREST: + case Biome.JUNGLE: moveId = Moves.ENERGY_BALL; break; - case Biome.SEA - || Biome.SWAMP - || Biome.BEACH - || Biome.LAKE - || Biome.SEABED - || Biome.ISLAND: + case Biome.SEA: + case Biome.SWAMP: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + case Biome.ISLAND: moveId = Moves.HYDRO_PUMP; break; case Biome.MOUNTAIN: moveId = Moves.AIR_SLASH; break; - case Biome.BADLANDS - || Biome.DESERT - || Biome.WASTELAND - || Biome.CONSTRUCTION_SITE: + case Biome.BADLANDS: + case Biome.DESERT: + case Biome.WASTELAND: + case Biome.CONSTRUCTION_SITE: moveId = Moves.EARTH_POWER; break; case Biome.CAVE: moveId = Moves.POWER_GEM; break; - case Biome.ICE_CAVE - || Biome.SNOWY_FOREST: + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: moveId = Moves.ICE_BEAM; break; case Biome.VOLCANO: moveId = Moves.FLAMETHROWER; break; - case Biome.GRAVEYARD - || Biome.RUINS - || Biome.TEMPLE: + case Biome.GRAVEYARD: + case Biome.RUINS: + case Biome.TEMPLE: moveId = Moves.SHADOW_BALL; break; case Biome.DOJO: @@ -2876,11 +2876,13 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { case Biome.FAIRY_CAVE: moveId = Moves.MOONBLAST; break; - case Biome.ABYSS - || Biome.SPACE - || Biome.END: + case Biome.ABYSS: + case Biome.SPACE: moveId = Moves.DARK_PULSE; break; + case Biome.END: + moveId = Moves.ETERNABEAM; + break; } break; case TerrainType.MISTY: