Merge branch 'pagefaultgames:beta' into beta

This commit is contained in:
pom-eranian 2024-10-01 12:09:00 -04:00 committed by GitHub
commit 3626c8906f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 360 additions and 215 deletions

View File

@ -123,6 +123,7 @@ export class Ability implements Localizable {
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>; type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>;
type AbAttrCondition = (pokemon: Pokemon) => boolean; type AbAttrCondition = (pokemon: Pokemon) => boolean;
// TODO: Can this be improved?
type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean; type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
@ -2432,7 +2433,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.setStatStage(s, target.getStatStage(s)); pokemon.setStatStage(s, target.getStatStage(s));
} }
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m!.moveId, m!.ppUsed, m!.ppUp)); // TODO: are those bangs correct? pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m?.moveId ?? Moves.NONE, m?.ppUsed, m?.ppUp));
pokemon.summonData.types = target.getTypes(); pokemon.summonData.types = target.getTypes();
@ -3152,12 +3153,12 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
} else if (move?.getMove().power === -1) { } else if (move?.getMove().power === -1) {
movePower = 80; movePower = 80;
} else { } else {
movePower = move!.getMove().power; // TODO: is this bang correct? movePower = move?.getMove().power ?? 0;
} }
if (movePower > maxPowerSeen) { if (movePower > maxPowerSeen) {
maxPowerSeen = movePower; maxPowerSeen = movePower;
maxMove = move!.getName(); // TODO: is this bang correct? maxMove = move?.getName() ?? "";
} }
} }
} }
@ -5175,8 +5176,7 @@ export function initAbilities() {
.attr(IgnoreOpponentStatStagesAbAttr) .attr(IgnoreOpponentStatStagesAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.TINTED_LENS, 4) new Ability(Abilities.TINTED_LENS, 4)
//@ts-ignore .attr(DamageBoostAbAttr, 2, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) <= 0.5),
.attr(DamageBoostAbAttr, 2, (user, target, move) => target?.getMoveEffectiveness(user, move) <= 0.5), // TODO: fix TS issues
new Ability(Abilities.FILTER, 4) new Ability(Abilities.FILTER, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
.ignorable(), .ignorable(),
@ -5294,8 +5294,9 @@ export function initAbilities() {
.attr(WonderSkinAbAttr) .attr(WonderSkinAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.ANALYTIC, 5) new Ability(Abilities.ANALYTIC, 5)
//@ts-ignore .attr(MovePowerBoostAbAttr, (user, target, move) =>
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3), // TODO fix TS issues !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn)
|| user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command !== Command.FIGHT, 1.3),
new Ability(Abilities.ILLUSION, 5) new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
@ -5460,8 +5461,7 @@ export function initAbilities() {
.bypassFaint() .bypassFaint()
.partial(), .partial(),
new Ability(Abilities.STAKEOUT, 7) new Ability(Abilities.STAKEOUT, 7)
//@ts-ignore .attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2),
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2), // TODO: fix TS issues
new Ability(Abilities.WATER_BUBBLE, 7) new Ability(Abilities.WATER_BUBBLE, 7)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
@ -5599,8 +5599,7 @@ export function initAbilities() {
new Ability(Abilities.PRISM_ARMOR, 7) new Ability(Abilities.PRISM_ARMOR, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75), .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75),
new Ability(Abilities.NEUROFORCE, 7) new Ability(Abilities.NEUROFORCE, 7)
//@ts-ignore .attr(MovePowerBoostAbAttr, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) >= 2, 1.25),
.attr(MovePowerBoostAbAttr, (user, target, move) => target?.getMoveEffectiveness(user, move) >= 2, 1.25), // TODO: fix TS issues
new Ability(Abilities.INTREPID_SWORD, 8) new Ability(Abilities.INTREPID_SWORD, 8)
.attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true) .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)), .condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
@ -5698,10 +5697,8 @@ export function initAbilities() {
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(), .ignorable(),
new Ability(Abilities.HUNGER_SWITCH, 8) new Ability(Abilities.HUNGER_SWITCH, 8)
//@ts-ignore .attr(PostTurnFormChangeAbAttr, p => p.getFormKey() ? 0 : 1)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) // TODO: fix ts-ignore .attr(PostTurnFormChangeAbAttr, p => p.getFormKey() ? 1 : 0)
//@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0) // TODO: fix ts-ignore
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)

View File

@ -341,7 +341,8 @@ export default class Move implements Localizable {
* @returns `true` if the move can bypass the target's Substitute; `false` otherwise. * @returns `true` if the move can bypass the target's Substitute; `false` otherwise.
*/ */
hitsSubstitute(user: Pokemon, target: Pokemon | null): boolean { hitsSubstitute(user: Pokemon, target: Pokemon | null): boolean {
if (this.moveTarget === MoveTarget.USER || !target?.getTag(BattlerTagType.SUBSTITUTE)) { if ([MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.moveTarget)
|| !target?.getTag(BattlerTagType.SUBSTITUTE)) {
return false; return false;
} }
@ -398,220 +399,202 @@ export default class Move implements Localizable {
/** /**
* Sets the {@linkcode MoveFlags.MAKES_CONTACT} flag for the calling Move * Sets the {@linkcode MoveFlags.MAKES_CONTACT} flag for the calling Move
* @param makesContact The value (boolean) to set the flag to * @param setFlag Default `true`, set to `false` if the move doesn't make contact
* @see {@linkcode Abilities.STATIC}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
makesContact(makesContact: boolean = true): this { makesContact(setFlag: boolean = true): this {
this.setFlag(MoveFlags.MAKES_CONTACT, makesContact); this.setFlag(MoveFlags.MAKES_CONTACT, setFlag);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_PROTECT} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_PROTECT} flag for the calling Move
* @param ignoresProtect The value (boolean) to set the flag to * @see {@linkcode Moves.CURSE}
* example: @see {@linkcode Moves.CURSE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresProtect(ignoresProtect: boolean = true): this { ignoresProtect(): this {
this.setFlag(MoveFlags.IGNORE_PROTECT, ignoresProtect); this.setFlag(MoveFlags.IGNORE_PROTECT, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move
* @param ignoresVirtual The value (boolean) to set the flag to * @see {@linkcode Moves.NATURE_POWER}
* example: @see {@linkcode Moves.NATURE_POWER}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresVirtual(ignoresVirtual: boolean = true): this { ignoresVirtual(): this {
this.setFlag(MoveFlags.IGNORE_VIRTUAL, ignoresVirtual); this.setFlag(MoveFlags.IGNORE_VIRTUAL, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move * Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move
* @param soundBased The value (boolean) to set the flag to * @see {@linkcode Moves.UPROAR}
* example: @see {@linkcode Moves.UPROAR}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
soundBased(soundBased: boolean = true): this { soundBased(): this {
this.setFlag(MoveFlags.SOUND_BASED, soundBased); this.setFlag(MoveFlags.SOUND_BASED, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.HIDE_USER} flag for the calling Move * Sets the {@linkcode MoveFlags.HIDE_USER} flag for the calling Move
* @param hidesUser The value (boolean) to set the flag to * @see {@linkcode Moves.TELEPORT}
* example: @see {@linkcode Moves.TELEPORT}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
hidesUser(hidesUser: boolean = true): this { hidesUser(): this {
this.setFlag(MoveFlags.HIDE_USER, hidesUser); this.setFlag(MoveFlags.HIDE_USER, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.HIDE_TARGET} flag for the calling Move * Sets the {@linkcode MoveFlags.HIDE_TARGET} flag for the calling Move
* @param hidesTarget The value (boolean) to set the flag to * @see {@linkcode Moves.WHIRLWIND}
* example: @see {@linkcode Moves.WHIRLWIND}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
hidesTarget(hidesTarget: boolean = true): this { hidesTarget(): this {
this.setFlag(MoveFlags.HIDE_TARGET, hidesTarget); this.setFlag(MoveFlags.HIDE_TARGET, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.BITING_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.BITING_MOVE} flag for the calling Move
* @param bitingMove The value (boolean) to set the flag to * @see {@linkcode Moves.BITE}
* example: @see {@linkcode Moves.BITE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
bitingMove(bitingMove: boolean = true): this { bitingMove(): this {
this.setFlag(MoveFlags.BITING_MOVE, bitingMove); this.setFlag(MoveFlags.BITING_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.PULSE_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.PULSE_MOVE} flag for the calling Move
* @param pulseMove The value (boolean) to set the flag to * @see {@linkcode Moves.WATER_PULSE}
* example: @see {@linkcode Moves.WATER_PULSE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
pulseMove(pulseMove: boolean = true): this { pulseMove(): this {
this.setFlag(MoveFlags.PULSE_MOVE, pulseMove); this.setFlag(MoveFlags.PULSE_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.PUNCHING_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.PUNCHING_MOVE} flag for the calling Move
* @param punchingMove The value (boolean) to set the flag to * @see {@linkcode Moves.DRAIN_PUNCH}
* example: @see {@linkcode Moves.DRAIN_PUNCH}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
punchingMove(punchingMove: boolean = true): this { punchingMove(): this {
this.setFlag(MoveFlags.PUNCHING_MOVE, punchingMove); this.setFlag(MoveFlags.PUNCHING_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.SLICING_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.SLICING_MOVE} flag for the calling Move
* @param slicingMove The value (boolean) to set the flag to * @see {@linkcode Moves.X_SCISSOR}
* example: @see {@linkcode Moves.X_SCISSOR}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
slicingMove(slicingMove: boolean = true): this { slicingMove(): this {
this.setFlag(MoveFlags.SLICING_MOVE, slicingMove); this.setFlag(MoveFlags.SLICING_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.RECKLESS_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.RECKLESS_MOVE} flag for the calling Move
* @see {@linkcode Abilities.RECKLESS} * @see {@linkcode Abilities.RECKLESS}
* @param recklessMove The value to set the flag to
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
recklessMove(recklessMove: boolean = true): this { recklessMove(): this {
this.setFlag(MoveFlags.RECKLESS_MOVE, recklessMove); this.setFlag(MoveFlags.RECKLESS_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.BALLBOMB_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.BALLBOMB_MOVE} flag for the calling Move
* @param ballBombMove The value (boolean) to set the flag to * @see {@linkcode Moves.ELECTRO_BALL}
* example: @see {@linkcode Moves.ELECTRO_BALL}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ballBombMove(ballBombMove: boolean = true): this { ballBombMove(): this {
this.setFlag(MoveFlags.BALLBOMB_MOVE, ballBombMove); this.setFlag(MoveFlags.BALLBOMB_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.POWDER_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.POWDER_MOVE} flag for the calling Move
* @param powderMove The value (boolean) to set the flag to * @see {@linkcode Moves.STUN_SPORE}
* example: @see {@linkcode Moves.STUN_SPORE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
powderMove(powderMove: boolean = true): this { powderMove(): this {
this.setFlag(MoveFlags.POWDER_MOVE, powderMove); this.setFlag(MoveFlags.POWDER_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.DANCE_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.DANCE_MOVE} flag for the calling Move
* @param danceMove The value (boolean) to set the flag to * @see {@linkcode Moves.PETAL_DANCE}
* example: @see {@linkcode Moves.PETAL_DANCE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
danceMove(danceMove: boolean = true): this { danceMove(): this {
this.setFlag(MoveFlags.DANCE_MOVE, danceMove); this.setFlag(MoveFlags.DANCE_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.WIND_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.WIND_MOVE} flag for the calling Move
* @param windMove The value (boolean) to set the flag to * @see {@linkcode Moves.HURRICANE}
* example: @see {@linkcode Moves.HURRICANE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
windMove(windMove: boolean = true): this { windMove(): this {
this.setFlag(MoveFlags.WIND_MOVE, windMove); this.setFlag(MoveFlags.WIND_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.TRIAGE_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.TRIAGE_MOVE} flag for the calling Move
* @param triageMove The value (boolean) to set the flag to * @see {@linkcode Moves.ABSORB}
* example: @see {@linkcode Moves.ABSORB}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
triageMove(triageMove: boolean = true): this { triageMove(): this {
this.setFlag(MoveFlags.TRIAGE_MOVE, triageMove); this.setFlag(MoveFlags.TRIAGE_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_ABILITIES} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_ABILITIES} flag for the calling Move
* @param ignoresAbilities sThe value (boolean) to set the flag to * @see {@linkcode Moves.SUNSTEEL_STRIKE}
* example: @see {@linkcode Moves.SUNSTEEL_STRIKE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresAbilities(ignoresAbilities: boolean = true): this { ignoresAbilities(): this {
this.setFlag(MoveFlags.IGNORE_ABILITIES, ignoresAbilities); this.setFlag(MoveFlags.IGNORE_ABILITIES, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.CHECK_ALL_HITS} flag for the calling Move * Sets the {@linkcode MoveFlags.CHECK_ALL_HITS} flag for the calling Move
* @param checkAllHits The value (boolean) to set the flag to * @see {@linkcode Moves.TRIPLE_AXEL}
* example: @see {@linkcode Moves.TRIPLE_AXEL}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
checkAllHits(checkAllHits: boolean = true): this { checkAllHits(): this {
this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits); this.setFlag(MoveFlags.CHECK_ALL_HITS, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_SUBSTITUTE} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_SUBSTITUTE} flag for the calling Move
* @param ignoresSubstitute The value (boolean) to set the flag to * @see {@linkcode Moves.WHIRLWIND}
* example: @see {@linkcode Moves.WHIRLWIND}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresSubstitute(ignoresSubstitute: boolean = true): this { ignoresSubstitute(): this {
this.setFlag(MoveFlags.IGNORE_SUBSTITUTE, ignoresSubstitute); this.setFlag(MoveFlags.IGNORE_SUBSTITUTE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.REDIRECT_COUNTER} flag for the calling Move * Sets the {@linkcode MoveFlags.REDIRECT_COUNTER} flag for the calling Move
* @param redirectCounter The value (boolean) to set the flag to * @see {@linkcode Moves.METAL_BURST}
* example: @see {@linkcode Moves.METAL_BURST}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
redirectCounter(redirectCounter: boolean = true): this { redirectCounter(): this {
this.setFlag(MoveFlags.REDIRECT_COUNTER, redirectCounter); this.setFlag(MoveFlags.REDIRECT_COUNTER, true);
return this; return this;
} }
@ -4544,6 +4527,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.DROWSY: case BattlerTagType.DROWSY:
case BattlerTagType.DISABLED: case BattlerTagType.DISABLED:
case BattlerTagType.HEAL_BLOCK: case BattlerTagType.HEAL_BLOCK:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return -5; return -5;
case BattlerTagType.SEEDED: case BattlerTagType.SEEDED:
case BattlerTagType.SALT_CURED: case BattlerTagType.SALT_CURED:
@ -4564,6 +4548,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.ENCORE: case BattlerTagType.ENCORE:
return -2; return -2;
case BattlerTagType.MINIMIZED: case BattlerTagType.MINIMIZED:
case BattlerTagType.ALWAYS_GET_HIT:
return 0; return 0;
case BattlerTagType.INGRAIN: case BattlerTagType.INGRAIN:
case BattlerTagType.IGNORE_ACCURACY: case BattlerTagType.IGNORE_ACCURACY:
@ -5348,7 +5333,7 @@ export class ChillyReceptionAttr extends ForceSwitchOutAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
// chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail // chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail
return (user, target, move) => user.scene.arena.trySetWeather(WeatherType.SNOW, true) || super.getSwitchOutCondition()(user, target, move); return (user, target, move) => user.scene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move);
} }
} }
export class RemoveTypeAttr extends MoveEffectAttr { export class RemoveTypeAttr extends MoveEffectAttr {
@ -6589,7 +6574,7 @@ export class MoveCondition {
export class FirstMoveCondition extends MoveCondition { export class FirstMoveCondition extends MoveCondition {
constructor() { constructor() {
super((user, target, move) => user.battleSummonData?.turnCount === 1); super((user, target, move) => user.battleSummonData?.waveTurnCount === 1);
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
@ -7266,7 +7251,7 @@ export function initMoves() {
new StatusMove(Moves.CURSE, Type.GHOST, -1, 10, -1, 0, 2) new StatusMove(Moves.CURSE, Type.GHOST, -1, 10, -1, 0, 2)
.attr(CurseAttr) .attr(CurseAttr)
.ignoresSubstitute() .ignoresSubstitute()
.ignoresProtect(true) .ignoresProtect()
.target(MoveTarget.CURSE), .target(MoveTarget.CURSE),
new AttackMove(Moves.FLAIL, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2) new AttackMove(Moves.FLAIL, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2)
.attr(LowHpPowerAttr), .attr(LowHpPowerAttr),

View File

@ -4,7 +4,7 @@ import { SpeciesFormKey } from "./pokemon-species";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import { MoveCategory, allMoves } from "./move"; import { MoveCategory, allMoves } from "./move";
import { Type } from "./type"; import { Type } from "./type";
import { Constructor } from "#app/utils"; import { Constructor, nil } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -185,7 +185,7 @@ export class SpeciesFormChange {
return true; return true;
} }
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | null { findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | nil {
if (!this.trigger.hasTriggerType(triggerType)) { if (!this.trigger.hasTriggerType(triggerType)) {
return null; return null;
} }
@ -193,7 +193,7 @@ export class SpeciesFormChange {
const trigger = this.trigger; const trigger = this.trigger;
if (trigger instanceof SpeciesFormChangeCompoundTrigger) { if (trigger instanceof SpeciesFormChangeCompoundTrigger) {
return trigger.triggers.find(t => t.hasTriggerType(triggerType))!; // TODO: is this bang correct? return trigger.triggers.find(t => t.hasTriggerType(triggerType));
} }
return trigger; return trigger;
@ -202,11 +202,11 @@ export class SpeciesFormChange {
export class SpeciesFormChangeCondition { export class SpeciesFormChangeCondition {
public predicate: SpeciesFormChangeConditionPredicate; public predicate: SpeciesFormChangeConditionPredicate;
public enforceFunc: SpeciesFormChangeConditionEnforceFunc | null; public enforceFunc: SpeciesFormChangeConditionEnforceFunc | nil;
constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) { constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) {
this.predicate = predicate; this.predicate = predicate;
this.enforceFunc = enforceFunc!; // TODO: is this bang correct? this.enforceFunc = enforceFunc;
} }
} }

View File

@ -18,6 +18,11 @@ import {Species} from "#enums/species";
import {TrainerType} from "#enums/trainer-type"; import {TrainerType} from "#enums/trainer-type";
import {Gender} from "./gender"; import {Gender} from "./gender";
/** Minimum BST for Pokemon generated onto the Elite Four's teams */
const ELITE_FOUR_MINIMUM_BST = 460;
/** Minimum BST for Pokemon generated onto the E4 Champion's team */
const CHAMPION_MINIMUM_BST = 508;
export enum TrainerPoolTier { export enum TrainerPoolTier {
COMMON, COMMON,
UNCOMMON, UNCOMMON,
@ -860,10 +865,10 @@ export class TrainerConfig {
// Set species filter and specialty types if provided, otherwise filter by base total. // Set species filter and specialty types if provided, otherwise filter by base total.
if (specialtyTypes.length) { if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.some(t => p.isOfType(t)) && p.baseTotal >= 450); this.setSpeciesFilter(p => specialtyTypes.some(t => p.isOfType(t)) && p.baseTotal >= ELITE_FOUR_MINIMUM_BST);
this.setSpecialtyTypes(...specialtyTypes); this.setSpecialtyTypes(...specialtyTypes);
} else { } else {
this.setSpeciesFilter(p => p.baseTotal >= 450); this.setSpeciesFilter(p => p.baseTotal >= ELITE_FOUR_MINIMUM_BST);
} }
// Localize the trainer's name by converting it to lowercase and replacing spaces with underscores. // Localize the trainer's name by converting it to lowercase and replacing spaces with underscores.
@ -913,8 +918,7 @@ export class TrainerConfig {
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
// Set species filter to only include species with a base total of 470 or higher. this.setSpeciesFilter(p => p.baseTotal >= CHAMPION_MINIMUM_BST);
this.setSpeciesFilter(p => p.baseTotal >= 470);
// Localize the trainer's name by converting it to lowercase and replacing spaces with underscores. // Localize the trainer's name by converting it to lowercase and replacing spaces with underscores.
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");

View File

@ -5000,6 +5000,8 @@ export class PokemonBattleData {
export class PokemonBattleSummonData { export class PokemonBattleSummonData {
/** The number of turns the pokemon has passed since entering the battle */ /** The number of turns the pokemon has passed since entering the battle */
public turnCount: number = 1; public turnCount: number = 1;
/** The number of turns the pokemon has passed since the start of the wave */
public waveTurnCount: number = 1;
/** The list of moves the pokemon has used since entering the battle */ /** The list of moves the pokemon has used since entering the battle */
public moveHistory: TurnMove[] = []; public moveHistory: TurnMove[] = [];
} }

View File

@ -25,7 +25,7 @@
"outro": "Schau wie glücklich dein {{chosenPokemon}} nun ist!$Hier, diese Pokémon-Eier kannst du auch haben.", "outro": "Schau wie glücklich dein {{chosenPokemon}} nun ist!$Hier, diese Pokémon-Eier kannst du auch haben.",
"outro_failed": "Wie enttäuschend...$Es sieht so aus, als hättest du noch einen langen Weg vor dir, um das Vertrauen deines Pokémon zu gewinnen!", "outro_failed": "Wie enttäuschend...$Es sieht so aus, als hättest du noch einen langen Weg vor dir, um das Vertrauen deines Pokémon zu gewinnen!",
"gained_eggs": "@s{item_fanfare}Du erhählst {{numEggs}}!", "gained_eggs": "@s{item_fanfare}Du erhählst {{numEggs}}!",
"eggs_tooltip": "\n(+) Erhalte @[TOOLTIP_TITLE]{{{eggs}}}", "eggs_tooltip": "\n(+) Erhalte {{eggs}}",
"numEggs_one": "{{count}} Ei der Stufe {{rarity}}", "numEggs_one": "{{count}} Ei der Stufe {{rarity}}",
"numEggs_other": "{{count}} Eier der Stufe {{rarity}}" "numEggs_other": "{{count}} Eier der Stufe {{rarity}}"
} }

View File

@ -25,7 +25,7 @@
"outro": "Ton {{chosenPokemon}} et toi avez\nlair très heureux !$Tiens, prends ça aussi.", "outro": "Ton {{chosenPokemon}} et toi avez\nlair très heureux !$Tiens, prends ça aussi.",
"outro_failed": "Voilà qui est bien décevant…$Tas encore visiblement bien du chemin à faire\npour acquérir la confiance de tes Pokémon !", "outro_failed": "Voilà qui est bien décevant…$Tas encore visiblement bien du chemin à faire\npour acquérir la confiance de tes Pokémon !",
"gained_eggs": "@s{item_fanfare}Vous recevez\n{{numEggs}} !", "gained_eggs": "@s{item_fanfare}Vous recevez\n{{numEggs}} !",
"eggs_tooltip": "\n(+) Recevez @[TOOLTIP_TITLE]{{{eggs}}}", "eggs_tooltip": "\n(+) Recevez {{eggs}}",
"numEggs_one": "{{count}} Œuf {{rarity}}", "numEggs_one": "{{count}} Œuf {{rarity}}",
"numEggs_other": "{{count}} Œufs {{rarity}}s" "numEggs_other": "{{count}} Œufs {{rarity}}s"
} }

View File

@ -25,7 +25,7 @@
"outro": "ねえ、 {{chosenPokemon}} すごく 嬉しくなったわ!$そして、 これらも どうぞ!", "outro": "ねえ、 {{chosenPokemon}} すごく 嬉しくなったわ!$そして、 これらも どうぞ!",
"outro_failed": "なんか ガッカリ ね……$手持ち ポケモンの 信用を 得るまで\nまだまだ みたいんだわ", "outro_failed": "なんか ガッカリ ね……$手持ち ポケモンの 信用を 得るまで\nまだまだ みたいんだわ",
"gained_eggs": "@s{item_fanfare}{{numEggs}}を もらいました!", "gained_eggs": "@s{item_fanfare}{{numEggs}}を もらいました!",
"eggs_tooltip": "\n(+) @[TOOLTIP_TITLE]{{{eggs}}}を得る", "eggs_tooltip": "\n(+) {{eggs}}を得る",
"numEggs_one": "{{count}} {{rarity}} タマゴ", "numEggs_one": "{{count}} {{rarity}} タマゴ",
"numEggs_other": "{{count}} {{rarity}} タマゴ" "numEggs_other": "{{count}} {{rarity}} タマゴ"
} }

View File

@ -26,7 +26,7 @@
"outro": "{{chosenPokemon}}[[가]] 정말 행복해 보이네요!$여기, 이것도 드릴게요.", "outro": "{{chosenPokemon}}[[가]] 정말 행복해 보이네요!$여기, 이것도 드릴게요.",
"outro_failed": "실망이네요….$포켓몬의 신뢰를 얻으려면 아직 멀었어요!", "outro_failed": "실망이네요….$포켓몬의 신뢰를 얻으려면 아직 멀었어요!",
"gained_eggs": "@s{item_fanfare}{{numEggs}}[[를]] 받았습니다!", "gained_eggs": "@s{item_fanfare}{{numEggs}}[[를]] 받았습니다!",
"eggs_tooltip": "\n(+) @[TOOLTIP_TITLE]{{{eggs}}} 획득", "eggs_tooltip": "\n(+) {{eggs}} 획득",
"numEggs_one": "{{rarity}}알 {{count}}개", "numEggs_one": "{{rarity}}알 {{count}}개",
"numEggs_other": "{{rarity}}알 {{count}}개" "numEggs_other": "{{rarity}}알 {{count}}개"
} }

View File

@ -25,5 +25,6 @@
"unlinkGoogle": "Desconectar Google", "unlinkGoogle": "Desconectar Google",
"cancel": "Cancelar", "cancel": "Cancelar",
"losingProgressionWarning": "Você vai perder todo o progresso desde o início da batalha. Confirmar?", "losingProgressionWarning": "Você vai perder todo o progresso desde o início da batalha. Confirmar?",
"noEggs": "Você não está chocando nenhum ovo\nno momento!" "noEggs": "Você não está chocando nenhum ovo\nno momento!",
"donate": "Contribuir"
} }

View File

@ -25,7 +25,7 @@
"outro": "Veja como seu {{chosenPokemon}} está feliz agora!$Aqui, você também pode ficar com isso.", "outro": "Veja como seu {{chosenPokemon}} está feliz agora!$Aqui, você também pode ficar com isso.",
"outro_failed": "Que decepção...$Parece que você ainda tem um longo caminho\na percorrer para ganhar a confiança dos seus Pokémon!", "outro_failed": "Que decepção...$Parece que você ainda tem um longo caminho\na percorrer para ganhar a confiança dos seus Pokémon!",
"gained_eggs": "@s{item_fanfare}Você recebeu {{numEggs}}!", "gained_eggs": "@s{item_fanfare}Você recebeu {{numEggs}}!",
"eggs_tooltip": "\n(+) Ganhe @[TOOLTIP_TITLE]{{{eggs}}}", "eggs_tooltip": "\n(+) Ganhe {{eggs}}",
"numEggs_one": "{{count}} Ovo {{rarity}}", "numEggs_one": "{{count}} Ovo {{rarity}}",
"numEggs_other": "{{count}} Ovos {{rarity}}" "numEggs_other": "{{count}} Ovos {{rarity}}"
} }

View File

@ -24,7 +24,7 @@
"tooManyItems": "{{pokemonName}} já tem\nmuitos desse item!", "tooManyItems": "{{pokemonName}} já tem\nmuitos desse item!",
"anyEffect": "Isso não terá nenhum efeito.", "anyEffect": "Isso não terá nenhum efeito.",
"unpausedEvolutions": "Evoluções foram despausadas para {{pokemonName}}.", "unpausedEvolutions": "Evoluções foram despausadas para {{pokemonName}}.",
"unspliceConfirmation": "Você realmente deseja desfazer a fusão de {{fusionName}}\ncom {{pokemonName}}? {{fusionName}} será perdido.", "unspliceConfirmation": "Você realmente deseja desfazer a fusão de\n{{fusionName}} com {{pokemonName}}? {{fusionName}} será perdido.",
"wasReverted": "{{fusionName}} foi revertido para {{pokemonName}}.", "wasReverted": "{{fusionName}} foi revertido para {{pokemonName}}.",
"releaseConfirmation": "Você realmente deseja soltar {{pokemonName}}?", "releaseConfirmation": "Você realmente deseja soltar {{pokemonName}}?",
"releaseInBattle": "Você não pode soltar um Pokémon que está em batalha!", "releaseInBattle": "Você não pode soltar um Pokémon que está em batalha!",

View File

@ -11,6 +11,10 @@
"expGainsSpeed": "Velocidade do Ganho de EXP", "expGainsSpeed": "Velocidade do Ganho de EXP",
"expPartyDisplay": "Exibição de EXP da Equipe", "expPartyDisplay": "Exibição de EXP da Equipe",
"skipSeenDialogues": "Pular Diálogos Vistos", "skipSeenDialogues": "Pular Diálogos Vistos",
"eggSkip": "Pular Eclosões de Ovos",
"never": "Nunca",
"always": "Sempre",
"ask": "Perguntar",
"battleStyle": "Estilo de Batalha", "battleStyle": "Estilo de Batalha",
"enableRetries": "Habilitar Novas Tentativas", "enableRetries": "Habilitar Novas Tentativas",
"hideIvs": "Esconder scanner de IV", "hideIvs": "Esconder scanner de IV",

View File

@ -88,6 +88,14 @@
"statHarshlyFell_other": "{{pokemonNameWithAffix}}的{{stats}}大幅降低了!", "statHarshlyFell_other": "{{pokemonNameWithAffix}}的{{stats}}大幅降低了!",
"statSeverelyFell_other": "{{pokemonNameWithAffix}}的{{stats}}极大幅降低了!", "statSeverelyFell_other": "{{pokemonNameWithAffix}}的{{stats}}极大幅降低了!",
"statWontGoAnyLower_other": "{{pokemonNameWithAffix}}的{{stats}}已经无法再降低了!", "statWontGoAnyLower_other": "{{pokemonNameWithAffix}}的{{stats}}已经无法再降低了!",
"statRose_one": "{{pokemonNameWithAffix}}的{{stats}}提高了!",
"statSharplyRose_one": "{{pokemonNameWithAffix}}的{{stats}}大幅提高了!",
"statRoseDrastically_one": "{{pokemonNameWithAffix}}的{{stats}}极大幅提高了!",
"statWontGoAnyHigher_one": "{{pokemonNameWithAffix}}的{{stats}}已经无法再提高了!",
"statFell_one": "{{pokemonNameWithAffix}}的{{stats}}降低了!",
"statHarshlyFell_one": "{{pokemonNameWithAffix}}的{{stats}}大幅降低了!",
"statSeverelyFell_one": "{{pokemonNameWithAffix}}的{{stats}}极大幅降低了!",
"statWontGoAnyLower_one": "{{pokemonNameWithAffix}}的{{stats}}已经无法再降低了!",
"transformedIntoType": "{{pokemonName}}变成了\n{{type}}属性!", "transformedIntoType": "{{pokemonName}}变成了\n{{type}}属性!",
"ppReduced": "降低了{{targetName}}的\n{{moveName}}的PP{{reduction}}点!", "ppReduced": "降低了{{targetName}}的\n{{moveName}}的PP{{reduction}}点!",
"retryBattle": "你要从对战开始时重试么?", "retryBattle": "你要从对战开始时重试么?",

View File

@ -299,6 +299,28 @@
"1": "真奇怪…怎么会这样…我不应该被打败的。" "1": "真奇怪…怎么会这样…我不应该被打败的。"
} }
}, },
"snow_worker": {
"encounter": {
"1": "天冷了,便要添衣。\n只有人类会有这么聪明的想法"
},
"victory": {
"1": "即使是徒劳的挣扎,\n到头来也还是展示了人类的智慧啊"
},
"defeat": {
"1": "我来告诉你一个小技巧。\n如果你对着冻住的宝可梦用火系技能冰就化啦"
}
},
"snow_worker_double": {
"encounter": {
"1": "……\n……准……准备好战……战斗了吗"
},
"victory": {
"1": "……\n……我冻得直直打哆嗦"
},
"defeat": {
"1": "……\n……知……知道吗\n...这……这地方蛮冷。"
}
},
"hex_maniac": { "hex_maniac": {
"encounter": { "encounter": {
"1": "我通常只听古典音乐,但如果我输了,$我想我应该试试新时代的音乐!", "1": "我通常只听古典音乐,但如果我输了,$我想我应该试试新时代的音乐!",

View File

@ -11,7 +11,7 @@
"gachaTypeLegendary": "传说概率上升", "gachaTypeLegendary": "传说概率上升",
"gachaTypeMove": "稀有概率上升", "gachaTypeMove": "稀有概率上升",
"gachaTypeShiny": "闪光概率上升", "gachaTypeShiny": "闪光概率上升",
"eventType": "Mystery Event", "eventType": "神秘事件",
"selectMachine": "选择一个机器。", "selectMachine": "选择一个机器。",
"notEnoughVouchers": "你没有足够的兑换券!", "notEnoughVouchers": "你没有足够的兑换券!",
"tooManyEggs": "你的蛋太多啦!", "tooManyEggs": "你的蛋太多啦!",

View File

@ -2,7 +2,7 @@
"ModifierType": { "ModifierType": {
"AddPokeballModifierType": { "AddPokeballModifierType": {
"name": "{{modifierCount}}x {{pokeballName}}", "name": "{{modifierCount}}x {{pokeballName}}",
"description": "获得 {{pokeballName}} x{{modifierCount}} (已有:{{pokeballAmount}}) \n捕捉倍率{{catchRate}}。" "description": "获得 {{pokeballName}}x {{modifierCount}} (已有:{{pokeballAmount}}) \n捕捉倍率{{catchRate}}。"
}, },
"AddVoucherModifierType": { "AddVoucherModifierType": {
"name": "{{modifierCount}}x {{voucherTypeName}}", "name": "{{modifierCount}}x {{voucherTypeName}}",
@ -50,7 +50,7 @@
"description": "遭遇双打概率提升四倍,持续{{battleCount}}场战斗。" "description": "遭遇双打概率提升四倍,持续{{battleCount}}场战斗。"
}, },
"TempStatStageBoosterModifierType": { "TempStatStageBoosterModifierType": {
"description": "提升全队的{{stat}}{{amount}}持续5场战斗。", "description": "提升全队的{{stat}}{{amount}}持续5场战斗。",
"extra": { "extra": {
"stage": "1阶", "stage": "1阶",
"percentage": "30%" "percentage": "30%"
@ -63,24 +63,24 @@
"description": "使一只宝可梦的等级提升{{levels}}级。" "description": "使一只宝可梦的等级提升{{levels}}级。"
}, },
"AllPokemonLevelIncrementModifierType": { "AllPokemonLevelIncrementModifierType": {
"description": "使一只寶可夢的等級提升{{levels}}級。" "description": "使所有宝可梦的等级提升{{levels}}级。"
}, },
"BaseStatBoosterModifierType": { "BaseStatBoosterModifierType": {
"description": "增加10%持有者的{{stat}}\n个体值越高堆叠上限越高。" "description": "增加10%持有者的{{stat}}\n个体值越高堆叠上限越高。"
}, },
"PokemonBaseStatTotalModifierType": { "PokemonBaseStatTotalModifierType": {
"name": "Shuckle Juice", "name": "壶壶果汁",
"description": "{{increaseDecrease}} all of the holder's base stats by {{statValue}}. You were {{blessCurse}} by the Shuckle.", "description": "{{increaseDecrease}}持有者的所有基础属性{{statValue}}级。你被壶壶{{blessCurse}}了。",
"extra": { "extra": {
"increase": "Increases", "increase": "提升",
"decrease": "Decreases", "decrease": "降低",
"blessed": "blessed", "blessed": "强化",
"cursed": "cursed" "cursed": "诅咒"
} }
}, },
"PokemonBaseStatFlatModifierType": { "PokemonBaseStatFlatModifierType": {
"name": "Old Gateau", "name": "森之羊羹",
"description": "Increases the holder's {{stats}} base stats by {{statValue}}. Found after a strange dream." "description": "增加持有者{{stats}}的基础属性{{statValue}},来自于一场怪梦。"
}, },
"AllPokemonFullHpRestoreModifierType": { "AllPokemonFullHpRestoreModifierType": {
"description": "所有宝可梦完全回复HP。" "description": "所有宝可梦完全回复HP。"
@ -109,7 +109,7 @@
"description": "招式命中率增加{{accuracyAmount}}(最大100)。" "description": "招式命中率增加{{accuracyAmount}}(最大100)。"
}, },
"PokemonMultiHitModifierType": { "PokemonMultiHitModifierType": {
"description": "攻击以60/75/82.5%的伤害造成2/3/4次伤害。" "description": "伤害降低60/75/82.5%的同时造成2/3/4次伤害。"
}, },
"TmModifierType": { "TmModifierType": {
"name": "招式学习器\n{{moveId}} - {{moveName}}", "name": "招式学习器\n{{moveId}} - {{moveName}}",
@ -126,7 +126,7 @@
"description": "使某些宝可梦更改形态。" "description": "使某些宝可梦更改形态。"
}, },
"FusePokemonModifierType": { "FusePokemonModifierType": {
"description": "融合两只宝可梦 (改变特性, 平分基础点数\n和属性, 共享招式池)。" "description": "融合两只宝可梦(改变特性, 平分基础点数\n和属性, 共享招式池)。"
}, },
"TerastallizeModifierType": { "TerastallizeModifierType": {
"name": "{{teraType}}太晶碎块", "name": "{{teraType}}太晶碎块",
@ -359,7 +359,7 @@
"description": "触碰后会放出热量的神奇宝珠。\n携带后在战斗时会变成灼伤状态。" "description": "触碰后会放出热量的神奇宝珠。\n携带后在战斗时会变成灼伤状态。"
}, },
"EVOLUTION_TRACKER_GIMMIGHOUL": { "EVOLUTION_TRACKER_GIMMIGHOUL": {
"name": "金子宝物", "name": "宝藏金币",
"description": "这个宝可梦最爱金币!多收集点金币的话会发生什么呢?" "description": "这个宝可梦最爱金币!多收集点金币的话会发生什么呢?"
}, },
"BATON": { "BATON": {
@ -421,11 +421,11 @@
"description": "增加1%野生融合宝可梦出现概率。" "description": "增加1%野生融合宝可梦出现概率。"
}, },
"MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { "name": "Shuckle Juice" }, "MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { "name": "壶壶果汁" },
"MYSTERY_ENCOUNTER_BLACK_SLUDGE": { "name": "Black Sludge", "description": "The stench is so powerful that shops will only sell you items at a steep cost increase." }, "MYSTERY_ENCOUNTER_BLACK_SLUDGE": { "name": "黑色污泥", "description": "由于恶臭扑鼻,商店会以非常高昂的价格向您出售商品。" },
"MYSTERY_ENCOUNTER_MACHO_BRACE": { "name": "Macho Brace", "description": "Defeating a Pokémon grants the holder a Macho Brace stack. Each stack slightly boosts stats, with an extra bonus at max stacks." }, "MYSTERY_ENCOUNTER_MACHO_BRACE": { "name": "强制锻炼器", "description": "击败对手后获得一层锻炼等级。每层会略微提升属性,\n达到最大层数时还会获得额外奖励。" },
"MYSTERY_ENCOUNTER_OLD_GATEAU": { "name": "Old Gateau", "description": "Increases the holder's {{stats}} stats by {{statValue}}." }, "MYSTERY_ENCOUNTER_OLD_GATEAU": { "name": "森之羊羔", "description": "提升持有者的{{stats}}的{{statValue}}。" },
"MYSTERY_ENCOUNTER_GOLDEN_BUG_NET": { "name": "Golden Bug Net", "description": "Imbues the owner with luck to find Bug Type Pokémon more often. Has a strange heft to it." } "MYSTERY_ENCOUNTER_GOLDEN_BUG_NET": { "name": "金捕虫网", "description": "赋予主人好运,使其更容易\n找到虫属性宝可梦手感很奇妙。" }
}, },
"SpeciesBoosterItem": { "SpeciesBoosterItem": {
"LIGHT_BALL": { "LIGHT_BALL": {

View File

@ -3,7 +3,7 @@
{ {
"intro": "一伙{{delibirdName}}出现了!", "intro": "一伙{{delibirdName}}出现了!",
"title": "信使鸟快递", "title": "信使鸟快递",
"description": "{{delibirdName}}满怀期待地看着你,\n它们好像想要什么东西。\n\n也许给它们一件道具或一些钱能让它们满意", "description": "{{delibirdName}}满怀期待地看着你,\n它们好像想要什么东西。\n\n给它们一件道具或一些钱的话\n会不会让它们满意呢",
"query": "你要给它们什么?", "query": "你要给它们什么?",
"invalid_selection": "宝可梦身上并没有那种道具。", "invalid_selection": "宝可梦身上并没有那种道具。",
"option": { "option": {

View File

@ -1,9 +1,9 @@
{ {
"intro": "一位提着一大堆购物袋的女士。", "intro": "一位提着一大堆购物袋的女士。",
"speaker": "大促销", "speaker": "大促销",
"intro_dialogue": "你好!你也是来参加促销活动的吗?\n在促销期间可以领取一张特别优惠券,用于购买免费商品\n我这有一张多余的优惠券。送你了", "intro_dialogue": "你好!你也是来参加促销活动的吗?\n在促销期间可以领取一张特别优惠券\n可以用于免费兑换商品哦\n我这有一张多余的优惠券。送你了",
"title": "百货公司促销", "title": "百货公司促销",
"description": "到处都是商品!\n好像有4个柜台可以让你用优惠券兑换商品。\n无尽可能", "description": "到处都是商品!\n共有4个柜台可以用优惠券兑换商品。\n好礼多多",
"query": "你要去哪里?", "query": "你要去哪里?",
"option": { "option": {
"1": { "1": {

View File

@ -1,7 +1,7 @@
{ {
"intro": "是GTS系统的交互面板", "intro": "是GTS系统的交互面板",
"title": "全球交换系统GTS", "title": "全球交换系统GTS",
"description": "啊GTS科学的奇迹\\nn你可以与世界各地的任何人联系\n与他们交换宝可梦\n今天你的交换会有好运吗", "description": "啊GTS科学的奇迹\n你可以与世界各地的任何人联系\n并与他们交换宝可梦\n今天你的交换运会如何呢",
"query": "你要怎么做?", "query": "你要怎么做?",
"option": { "option": {
"1": { "1": {
@ -11,13 +11,13 @@
}, },
"2": { "2": {
"label": "奇迹交换", "label": "奇迹交换",
"tooltip": "(+)将一只宝可梦送至GTS\n并获得一只随机宝可梦" "tooltip": "(+)发送一只宝可梦\n并获得一只随机宝可梦"
}, },
"3": { "3": {
"label": "道具交换", "label": "道具交换",
"trade_options_prompt": "选择要发送的道具", "trade_options_prompt": "选择要发送的道具",
"invalid_selection": "这只宝可梦没有合法道具\n可以被交换。", "invalid_selection": "这只宝可梦没有合法道具\n可以被交换。",
"tooltip": "(+)将你的一件道具发送到GTS并获得一件随机新道具" "tooltip": "(+)发送一件道具\n并获得一件随机新道具"
}, },
"4": { "4": {
"label": "离开", "label": "离开",

View File

@ -8,19 +8,19 @@
"1": { "1": {
"label": "轻量训练", "label": "轻量训练",
"tooltip": "(-)轻松战斗\n(+)提升随机2只\n宝可梦的个体", "tooltip": "(-)轻松战斗\n(+)提升随机2只\n宝可梦的个体",
"finished": "{{selectedPokemon}}回来了,感觉\n虽然疲惫不堪,但很有成就!$它的{{stat1}}和{{stat2}}的个体得到了改善!" "finished": "{{selectedPokemon}}回来了,感觉虽然疲惫不堪,\n但很有成就$它的{{stat1}}和{{stat2}}的个体得到了改善!"
}, },
"2": { "2": {
"label": "适度训练", "label": "适度训练",
"tooltip": "(-)适中的战斗\n(+)改变宝可梦的性格", "tooltip": "(-)适中的战斗\n(+)改变宝可梦的性格",
"select_prompt": "选择一种想要的性格\n来训练你的神奇宝贝。", "select_prompt": "选择一种想要的性格\n来训练你的神奇宝贝。",
"finished": "{{selectedPokemon}}回来了,感觉\n虽然疲惫不堪,但很有成就!$它的性格变成了{{nature}}" "finished": "{{selectedPokemon}}回来了,感觉虽然疲惫不堪,\n但很有成就$它的性格变成了{{nature}}"
}, },
"3": { "3": {
"label": "重磅训练", "label": "重磅训练",
"tooltip": "(-)艰苦的战斗\n(+)改变宝可梦的特性", "tooltip": "(-)艰苦的战斗\n(+)改变宝可梦的特性",
"select_prompt": "选择一种想要的特性\n来训练你的神奇宝贝。", "select_prompt": "选择一种想要的特性\n来训练你的神奇宝贝。",
"finished": "{{selectedPokemon}}回来了,感觉\n虽然疲惫不堪但很有成就$它的特性变成了{{nature}}" "finished": "{{selectedPokemon}}回来了,感觉虽然疲惫不堪,\n但很有成就感$它的特性变成了{{ability}}"
}, },
"4": { "4": {
"label": "离开", "label": "离开",

View File

@ -12,7 +12,7 @@
"2": { "2": {
"label": "探究来源", "label": "探究来源",
"tooltip": "(?)找到垃圾的来源", "tooltip": "(?)找到垃圾的来源",
"selected": "你在这堆东西周围徘徊,寻找表明这东西可能出现在这里的任何迹象……", "selected": "你在这堆东西周围徘徊,\n寻找表明这东西可能出现在这里的任何迹象……",
"selected_2": "突然,垃圾动了!那不是垃圾,是一只宝可梦!" "selected_2": "突然,垃圾动了!那不是垃圾,是一只宝可梦!"
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"intro": "这可不是一只普通的宝可梦!", "intro": "这可不是一只普通的宝可梦!",
"title": "罕见物种", "title": "罕见物种",
"description": "{{enemyPokemon}}看起来与其他同种宝可梦大相径庭。\n@[TOOLTIP_TITLE]{也许它会特殊招式?}\n你可以直接进行战斗并尝试捕获它\n但可能也有办法和它交朋友。", "description": "{{enemyPokemon}}看起来\n与其他同种宝可梦大相径庭。\n@[TOOLTIP_TITLE]{也许它会特殊招式?}\n你可以直接进行战斗并尝试捕获它\n但可能也有办法和它交朋友。",
"query": "你要怎么做?", "query": "你要怎么做?",
"option": { "option": {
"1": { "1": {

View File

@ -13,6 +13,7 @@
"ALL": "全部道具", "ALL": "全部道具",
"PASS_BATON": "接棒", "PASS_BATON": "接棒",
"UNPAUSE_EVOLUTION": "解除进化暂停", "UNPAUSE_EVOLUTION": "解除进化暂停",
"PAUSE_EVOLUTION": "暂停进化",
"REVIVE": "复活", "REVIVE": "复活",
"RENAME": "起名", "RENAME": "起名",
"SELECT": "选择", "SELECT": "选择",

View File

@ -11,6 +11,10 @@
"expGainsSpeed": "经验值获取动画速度", "expGainsSpeed": "经验值获取动画速度",
"expPartyDisplay": "显示队伍经验", "expPartyDisplay": "显示队伍经验",
"skipSeenDialogues": "跳过已读对话", "skipSeenDialogues": "跳过已读对话",
"eggSkip": "孵蛋跳过",
"never": "从不",
"always": "永久",
"ask": "询问",
"battleStyle": "对战模式", "battleStyle": "对战模式",
"enableRetries": "允许重试", "enableRetries": "允许重试",
"hideIvs": "禁用个体值探测器信息", "hideIvs": "禁用个体值探测器信息",
@ -60,9 +64,10 @@
"playerGender": "玩家性别", "playerGender": "玩家性别",
"typeHints": "属性提示", "typeHints": "属性提示",
"masterVolume": "主音量", "masterVolume": "主音量",
"bgmVolume": "音乐", "bgmVolume": "音乐音量",
"fieldVolume": "场景音量", "fieldVolume": "场景音量",
"seVolume": "音效", "seVolume": "音效音量",
"uiVolume": "界面音量",
"musicPreference": "音乐偏好", "musicPreference": "音乐偏好",
"mixed": "全曲混合", "mixed": "全曲混合",
"gamepadPleasePlug": "请链接手柄或按任意键", "gamepadPleasePlug": "请链接手柄或按任意键",

View File

@ -35,6 +35,12 @@ export class BattleEndPhase extends BattlePhase {
this.scene.unshiftPhase(new GameOverPhase(this.scene, true)); this.scene.unshiftPhase(new GameOverPhase(this.scene, true));
} }
for (const pokemon of this.scene.getField()) {
if (pokemon && pokemon.battleSummonData) {
pokemon.battleSummonData.waveTurnCount = 1;
}
}
for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) { for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) {
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); applyPostBattleAbAttrs(PostBattleAbAttr, pokemon);
} }

View File

@ -1,34 +1,28 @@
import { clientSessionId } from "#app/account"; import { clientSessionId } from "#app/account";
import BattleScene from "#app/battle-scene";
import { BattleType } from "#app/battle"; import { BattleType } from "#app/battle";
import BattleScene from "#app/battle-scene";
import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getCharVariantFromDialogue } from "#app/data/dialogue";
import { pokemonEvolutions } from "#app/data/pokemon-evolutions"; import { pokemonEvolutions } from "#app/data/pokemon-evolutions";
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainer-config";
import { PlayerGender } from "#app/enums/player-gender";
import { TrainerType } from "#app/enums/trainer-type";
import Pokemon from "#app/field/pokemon"; import Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { BattlePhase } from "#app/phases/battle-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { EncounterPhase } from "#app/phases/encounter-phase";
import { EndCardPhase } from "#app/phases/end-card-phase";
import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase";
import { PostGameOverPhase } from "#app/phases/post-game-over-phase";
import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { UnlockPhase } from "#app/phases/unlock-phase";
import { achvs, ChallengeAchv } from "#app/system/achv"; import { achvs, ChallengeAchv } from "#app/system/achv";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import i18next from "i18next";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import { BattlePhase } from "./battle-phase"; import { PlayerGender } from "#enums/player-gender";
import { CheckSwitchPhase } from "./check-switch-phase"; import { TrainerType } from "#enums/trainer-type";
import { EncounterPhase } from "./encounter-phase"; import i18next from "i18next";
import { GameOverModifierRewardPhase } from "./game-over-modifier-reward-phase";
import { RibbonModifierRewardPhase } from "./ribbon-modifier-reward-phase";
import { SummonPhase } from "./summon-phase";
import { EndCardPhase } from "./end-card-phase";
import { PostGameOverPhase } from "./post-game-over-phase";
import { UnlockPhase } from "./unlock-phase";
import { SessionSaveData } from "../system/game-data";
import TrainerData from "../system/trainer-data";
import PokemonData from "../system/pokemon-data";
import PersistentModifierData from "../system/modifier-data";
import ChallengeData from "../system/challenge-data";
import ArenaData from "../system/arena-data";
export class GameOverPhase extends BattlePhase { export class GameOverPhase extends BattlePhase {
private victory: boolean; private victory: boolean;
@ -114,7 +108,7 @@ export class GameOverPhase extends BattlePhase {
this.scene.gameData.gameStats.dailyRunSessionsWon++; this.scene.gameData.gameStats.dailyRunSessionsWon++;
} }
} }
this.scene.gameData.saveRunHistory(this.scene, this.getFinalSessionData(), this.victory); this.scene.gameData.saveRunHistory(this.scene, this.scene.gameData.getSessionSaveData(this.scene), this.victory);
const fadeDuration = this.victory ? 10000 : 5000; const fadeDuration = this.victory ? 10000 : 5000;
this.scene.fadeOutBgm(fadeDuration, true); this.scene.fadeOutBgm(fadeDuration, true);
const activeBattlers = this.scene.getField().filter(p => p?.isActive(true)); const activeBattlers = this.scene.getField().filter(p => p?.isActive(true));
@ -221,34 +215,5 @@ export class GameOverPhase extends BattlePhase {
this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter))); this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter)));
} }
} }
/**
* This function mirrors game-data.ts' getSessionSaveData() to update the session data to reflect any changes that occurred within the last wave
* This means that level ups, item usage, evolutions, etc. will all be accurately reflected.
* @returns {@linkCode SessionSaveData} an updated version of the wave's SessionSaveData that accurately reflects the events of the wave
*/
private getFinalSessionData(): SessionSaveData {
return {
seed: this.scene.seed,
playTime: this.scene.sessionPlayTime,
gameMode: this.scene.gameMode.modeId,
party: this.scene.getParty().map(p => new PokemonData(p)),
enemyParty: this.scene.getEnemyParty().map(p => new PokemonData(p)),
modifiers: this.scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
enemyModifiers: this.scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)),
arena: new ArenaData(this.scene.arena),
pokeballCounts: this.scene.pokeballCounts,
money: Math.floor(this.scene.money),
score: this.scene.score,
waveIndex: this.scene.currentBattle.waveIndex,
battleType: this.scene.currentBattle.battleType,
trainer: this.scene.currentBattle.battleType === BattleType.TRAINER ? new TrainerData(this.scene.currentBattle.trainer) : null,
gameVersion: this.scene.game.config.gameVersion,
timestamp: new Date().getTime(),
challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)),
mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1,
mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData
} as SessionSaveData;
}
} }

View File

@ -173,6 +173,7 @@ export class SwitchSummonPhase extends SummonPhase {
// Or compensate for force switch move if switched out pokemon is not fainted // Or compensate for force switch move if switched out pokemon is not fainted
if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) { if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) {
pokemon.battleSummonData.turnCount--; pokemon.battleSummonData.turnCount--;
pokemon.battleSummonData.waveTurnCount--;
} }
if (this.switchType === SwitchType.BATON_PASS && pokemon) { if (this.switchType === SwitchType.BATON_PASS && pokemon) {

View File

@ -44,6 +44,7 @@ export class TurnEndPhase extends FieldPhase {
this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon);
pokemon.battleSummonData.turnCount++; pokemon.battleSummonData.turnCount++;
pokemon.battleSummonData.waveTurnCount++;
}; };
this.executeForAll(handlePokemon); this.executeForAll(handlePokemon);

View File

@ -4,6 +4,7 @@ import { Species } from "#enums/species";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
//import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Chilly Reception", () => { describe("Moves - Chilly Reception", () => {
@ -66,4 +67,58 @@ describe("Moves - Chilly Reception", () => {
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH); expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH);
}); });
// enemy uses another move and weather doesn't change
it("check case - enemy not selecting chilly reception doesn't change weather ", async () => {
game.override.battleType("single")
.enemyMoveset([Moves.CHILLY_RECEPTION, Moves.TACKLE])
.enemyAbility(Abilities.NONE)
.moveset(Array(4).fill(Moves.SPLASH));
await game.classicMode.startBattle([Species.SLOWKING, Species.MEOWTH]);
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.TACKLE);
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(undefined);
});
it("enemy trainer - expected behavior ", async () => {
game.override.battleType("single")
.startingWave(8)
.enemyMoveset(Array(4).fill(Moves.CHILLY_RECEPTION))
.enemyAbility(Abilities.NONE)
.enemySpecies(Species.MAGIKARP)
.moveset([Moves.SPLASH, Moves.THUNDERBOLT]);
await game.classicMode.startBattle([Species.JOLTEON]);
const RIVAL_MAGIKARP1 = game.scene.getEnemyPokemon()?.id;
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
expect(game.scene.getEnemyPokemon()?.id !== RIVAL_MAGIKARP1);
await game.phaseInterceptor.to("TurnInitPhase", false);
game.move.select(Moves.SPLASH);
// second chilly reception should still switch out
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
await game.phaseInterceptor.to("TurnInitPhase", false);
expect(game.scene.getEnemyPokemon()?.id === RIVAL_MAGIKARP1);
game.move.select(Moves.THUNDERBOLT);
// enemy chilly recep move should fail: it's snowing and no option to switch out
// no crashing
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
await game.phaseInterceptor.to("TurnInitPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
});
}); });

View File

@ -23,14 +23,15 @@ describe("Moves - Fake Out", () => {
game.override game.override
.battleType("single") .battleType("single")
.enemySpecies(Species.CORVIKNIGHT) .enemySpecies(Species.CORVIKNIGHT)
.starterSpecies(Species.FEEBAS)
.moveset([Moves.FAKE_OUT, Moves.SPLASH]) .moveset([Moves.FAKE_OUT, Moves.SPLASH])
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
.enemyLevel(10)
.startingLevel(10) // prevent LevelUpPhase from happening
.disableCrits(); .disableCrits();
}); });
it("can only be used on the first turn a pokemon is sent out", async() => { it("can only be used on the first turn a pokemon is sent out in a battle", async() => {
await game.classicMode.startBattle(); await game.classicMode.startBattle([Species.FEEBAS]);
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
@ -44,22 +45,27 @@ describe("Moves - Fake Out", () => {
await game.toNextTurn(); await game.toNextTurn();
expect(enemy.hp).toBe(postTurnOneHp); expect(enemy.hp).toBe(postTurnOneHp);
}, 20000);
game.move.select(Moves.SPLASH); // This is a PokeRogue buff to Fake Out
await game.doKillOpponents(); it("can be used at the start of every wave even if the pokemon wasn't recalled", async() => {
await game.classicMode.startBattle([Species.FEEBAS]);
const enemy = game.scene.getEnemyPokemon()!;
enemy.damageAndUpdate(enemy.getMaxHp() - 1);
game.move.select(Moves.FAKE_OUT);
await game.toNextWave(); await game.toNextWave();
const newEnemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.FAKE_OUT); game.move.select(Moves.FAKE_OUT);
await game.toNextTurn(); await game.toNextTurn();
expect(newEnemy.hp).toBe(newEnemy.getMaxHp()); expect(game.scene.getEnemyPokemon()!.isFullHp()).toBe(false);
}, 20000); }, 20000);
it("can be used again if recalled and sent back out", async() => { it("can be used again if recalled and sent back out", async() => {
game.override.startingWave(4); game.override.startingWave(4);
await game.classicMode.startBattle(); await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]);
const enemy1 = game.scene.getEnemyPokemon()!; const enemy1 = game.scene.getEnemyPokemon()!;
@ -76,6 +82,18 @@ describe("Moves - Fake Out", () => {
const enemy2 = game.scene.getEnemyPokemon()!; const enemy2 = game.scene.getEnemyPokemon()!;
expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp());
enemy2.hp = enemy2.getMaxHp();
game.doSwitchPokemon(1);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
game.move.select(Moves.FAKE_OUT);
await game.toNextTurn();
expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp()); expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp());
}, 20000); }, 20000);
}); });

View File

@ -1,13 +1,15 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { ArenaTagSide } from "#app/data/arena-tag";
import { SubstituteTag, TrappedTag } from "#app/data/battler-tags"; import { SubstituteTag, TrappedTag } from "#app/data/battler-tags";
import { allMoves, StealHeldItemChanceAttr } from "#app/data/move"; import { allMoves, StealHeldItemChanceAttr } from "#app/data/move";
import { StatusEffect } from "#app/data/status-effect"; import { StatusEffect } from "#app/data/status-effect";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#app/enums/battler-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BerryType } from "#app/enums/berry-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#app/enums/moves"; import { BerryType } from "#enums/berry-type";
import { Species } from "#app/enums/species"; import { Moves } from "#enums/moves";
import { Stat } from "#app/enums/stat"; import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import { MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
@ -194,6 +196,71 @@ describe("Moves - Substitute", () => {
} }
); );
it(
"shouldn't block moves that target the user's side of the field",
async () => {
game.override.moveset(Moves.LIGHT_SCREEN);
await game.classicMode.startBattle([Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(leadPokemon, "getMoveEffectiveness");
leadPokemon.addTag(BattlerTagType.SUBSTITUTE, 0, Moves.NONE, leadPokemon.id);
game.move.select(Moves.LIGHT_SCREEN);
await game.toNextTurn();
expect(leadPokemon.getMoveEffectiveness).not.toHaveReturnedWith(0);
expect(game.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.PLAYER)).toBeDefined();
}
);
it(
"shouldn't block the opponent from setting hazards",
async () => {
game.override.enemyMoveset(Moves.STEALTH_ROCK);
await game.classicMode.startBattle([Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon()!;
vi.spyOn(leadPokemon, "getMoveEffectiveness");
game.move.select(Moves.SUBSTITUTE);
await game.toNextTurn();
expect(leadPokemon.getMoveEffectiveness).not.toHaveReturnedWith(0);
expect(game.scene.arena.getTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.PLAYER)).toBeDefined();
}
);
it(
"shouldn't block moves that target both sides of the field",
async () => {
game.override
.moveset(Moves.TRICK_ROOM)
.enemyMoveset(Moves.GRAVITY);
await game.classicMode.startBattle([Species.BLASTOISE]);
const pokemon = game.scene.getField(true);
pokemon.forEach(p => {
vi.spyOn(p, "getMoveEffectiveness");
p.addTag(BattlerTagType.SUBSTITUTE, 0, Moves.NONE, p.id);
});
game.move.select(Moves.TRICK_ROOM);
await game.toNextTurn();
pokemon.forEach(p => expect(p.getMoveEffectiveness).not.toHaveReturnedWith(0));
expect(game.scene.arena.getTag(ArenaTagType.TRICK_ROOM)).toBeDefined();
expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined();
}
);
it( it(
"should protect the user from flinching", "should protect the user from flinching",
async () => { async () => {

View File

@ -1,5 +1,6 @@
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { TextStyle, addTextObject } from "#app/ui/text"; import { TextStyle, addTextObject } from "#app/ui/text";
import { nil } from "#app/utils";
import i18next from "i18next"; import i18next from "i18next";
export enum EventType { export enum EventType {
@ -64,19 +65,19 @@ export class TimedEventManager {
let multiplier = 1; let multiplier = 1;
const shinyEvents = timedEvents.filter((te) => te.eventType === EventType.SHINY && this.isActive(te)); const shinyEvents = timedEvents.filter((te) => te.eventType === EventType.SHINY && this.isActive(te));
shinyEvents.forEach((se) => { shinyEvents.forEach((se) => {
multiplier *= se.shinyMultiplier!; // TODO: is this bang correct? multiplier *= se.shinyMultiplier ?? 1;
}); });
return multiplier; return multiplier;
} }
getEventBannerFilename(): string { getEventBannerFilename(): string {
return timedEvents.find((te: TimedEvent) => this.isActive(te))?.bannerKey!; // TODO: is this bang correct? return timedEvents.find((te: TimedEvent) => this.isActive(te))?.bannerKey ?? "";
} }
} }
export class TimedEventDisplay extends Phaser.GameObjects.Container { export class TimedEventDisplay extends Phaser.GameObjects.Container {
private event: TimedEvent | null; private event: TimedEvent | nil;
private eventTimerText: Phaser.GameObjects.Text; private eventTimerText: Phaser.GameObjects.Text;
private banner: Phaser.GameObjects.Image; private banner: Phaser.GameObjects.Image;
private bannerShadow: Phaser.GameObjects.Rectangle; private bannerShadow: Phaser.GameObjects.Rectangle;
@ -84,7 +85,7 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
constructor(scene: BattleScene, x: number, y: number, event?: TimedEvent) { constructor(scene: BattleScene, x: number, y: number, event?: TimedEvent) {
super(scene, x, y); super(scene, x, y);
this.event = event!; // TODO: is this bang correct? this.event = event;
this.setVisible(false); this.setVisible(false);
} }

View File

@ -2,6 +2,8 @@ import { MoneyFormat } from "#enums/money-format";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import i18next from "i18next"; import i18next from "i18next";
export type nil = null | undefined;
export const MissingTextureKey = "__MISSING"; export const MissingTextureKey = "__MISSING";
export function toReadableString(str: string): string { export function toReadableString(str: string): string {
@ -443,7 +445,7 @@ export function deltaRgb(rgb1: integer[], rgb2: integer[]): integer {
} }
export function rgbHexToRgba(hex: string) { export function rgbHexToRgba(hex: string) {
const color = hex.match(/^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i)!; // TODO: is this bang correct? const color = hex.match(/^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i) ?? ["000000", "00", "00", "00"];
return { return {
r: parseInt(color[1], 16), r: parseInt(color[1], 16),
g: parseInt(color[2], 16), g: parseInt(color[2], 16),