Merge branch 'beta' into danish-workspace

This commit is contained in:
Lugiad 2024-09-23 18:14:20 +02:00 committed by GitHub
commit 4ccc66ed51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 856 additions and 331 deletions

68
src/data/ability.ts Executable file → Normal file
View File

@ -1798,6 +1798,61 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
} }
} }
/**
* Base class for defining all {@linkcode Ability} Attributes after a status effect has been set.
* @see {@linkcode applyPostSetStatus()}.
*/
export class PostSetStatusAbAttr extends AbAttr {
/**
* Does nothing after a status condition is set.
* @param pokemon {@linkcode Pokemon} that status condition was set on.
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is `null` if status was not set by a Pokemon.
* @param passive Whether this ability is a passive.
* @param effect {@linkcode StatusEffect} that was set.
* @param args Set of unique arguments needed by this attribute.
* @returns `true` if application of the ability succeeds.
*/
applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]) : boolean | Promise<boolean> {
return false;
}
}
/**
* If another Pokemon burns, paralyzes, poisons, or badly poisons this Pokemon,
* that Pokemon receives the same non-volatile status condition as part of this
* ability attribute. For Synchronize ability.
*/
export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr {
/**
* If the `StatusEffect` that was set is Burn, Paralysis, Poison, or Toxic, and the status
* was set by a source Pokemon, set the source Pokemon's status to the same `StatusEffect`.
* @param pokemon {@linkcode Pokemon} that status condition was set on.
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon.
* @param passive Whether this ability is a passive.
* @param effect {@linkcode StatusEffect} that was set.
* @param args Set of unique arguments needed by this attribute.
* @returns `true` if application of the ability succeeds.
*/
override applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]): boolean {
/** Synchronizable statuses */
const syncStatuses = new Set<StatusEffect>([
StatusEffect.BURN,
StatusEffect.PARALYSIS,
StatusEffect.POISON,
StatusEffect.TOXIC
]);
if (sourcePokemon && syncStatuses.has(effect)) {
if (!simulated) {
sourcePokemon.trySetStatus(effect, true, pokemon);
}
return true;
}
return false;
}
}
export class PostVictoryAbAttr extends AbAttr { export class PostVictoryAbAttr extends AbAttr {
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> { applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
return false; return false;
@ -4241,6 +4296,10 @@ export class ReduceBerryUseThresholdAbAttr extends AbAttr {
} }
} }
/**
* Ability attribute used for abilites that change the ability owner's weight
* Used for Heavy Metal (doubling weight) and Light Metal (halving weight)
*/
export class WeightMultiplierAbAttr extends AbAttr { export class WeightMultiplierAbAttr extends AbAttr {
private multiplier: integer; private multiplier: integer;
@ -4677,6 +4736,10 @@ export function applyStatMultiplierAbAttrs(attrType: Constructor<StatMultiplierA
pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise<void> { pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<StatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), args); return applyAbAttrsInternal<StatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), args);
} }
export function applyPostSetStatusAbAttrs(attrType: Constructor<PostSetStatusAbAttr>,
pokemon: Pokemon, effect: StatusEffect, sourcePokemon?: Pokemon | null, simulated: boolean = false, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, false, simulated);
}
/** /**
* Applies a field Stat multiplier attribute * Applies a field Stat multiplier attribute
@ -4907,7 +4970,8 @@ export function initAbilities() {
.attr(EffectSporeAbAttr), .attr(EffectSporeAbAttr),
new Ability(Abilities.SYNCHRONIZE, 3) new Ability(Abilities.SYNCHRONIZE, 3)
.attr(SyncEncounterNatureAbAttr) .attr(SyncEncounterNatureAbAttr)
.unimplemented(), .attr(SynchronizeStatusAbAttr)
.partial(), // interaction with psycho shift needs work, keeping to old Gen interaction for now
new Ability(Abilities.CLEAR_BODY, 3) new Ability(Abilities.CLEAR_BODY, 3)
.attr(ProtectStatAbAttr) .attr(ProtectStatAbAttr)
.ignorable(), .ignorable(),
@ -5879,6 +5943,6 @@ export function initAbilities() {
new Ability(Abilities.POISON_PUPPETEER, 9) new Ability(Abilities.POISON_PUPPETEER, 9)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.conditionalAttr(pokemon => pokemon.species.speciesId===Species.PECHARUNT, ConfusionOnStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(ConfusionOnStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
); );
} }

View File

@ -915,12 +915,12 @@ export abstract class BattleAnim {
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ]; this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
this.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ]; this.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ];
let r = anim!.frames.length; // TODO: is this bang correct? let r = anim?.frames.length ?? 0;
let f = 0; let f = 0;
scene.tweens.addCounter({ scene.tweens.addCounter({
duration: Utils.getFrameMs(3), duration: Utils.getFrameMs(3),
repeat: anim!.frames.length, // TODO: is this bang correct? repeat: anim?.frames.length ?? 0,
onRepeat: () => { onRepeat: () => {
if (!f) { if (!f) {
userSprite.setVisible(false); userSprite.setVisible(false);
@ -1264,7 +1264,7 @@ export class CommonBattleAnim extends BattleAnim {
} }
getAnim(): AnimConfig | null { getAnim(): AnimConfig | null {
return this.commonAnim ? commonAnims.get(this.commonAnim)! : null; // TODO: is this bang correct? return this.commonAnim ? commonAnims.get(this.commonAnim) ?? null : null;
} }
isOppAnim(): boolean { isOppAnim(): boolean {
@ -1284,7 +1284,7 @@ export class MoveAnim extends BattleAnim {
getAnim(): AnimConfig { getAnim(): AnimConfig {
return moveAnims.get(this.move) instanceof AnimConfig return moveAnims.get(this.move) instanceof AnimConfig
? moveAnims.get(this.move) as AnimConfig ? moveAnims.get(this.move) as AnimConfig
: moveAnims.get(this.move)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct? : moveAnims.get(this.move)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig;
} }
isOppAnim(): boolean { isOppAnim(): boolean {
@ -1316,7 +1316,7 @@ export class MoveChargeAnim extends MoveAnim {
getAnim(): AnimConfig { getAnim(): AnimConfig {
return chargeAnims.get(this.chargeAnim) instanceof AnimConfig return chargeAnims.get(this.chargeAnim) instanceof AnimConfig
? chargeAnims.get(this.chargeAnim) as AnimConfig ? chargeAnims.get(this.chargeAnim) as AnimConfig
: chargeAnims.get(this.chargeAnim)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct? : chargeAnims.get(this.chargeAnim)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig;
} }
} }

View File

@ -2281,6 +2281,36 @@ export class TarShotTag extends BattlerTag {
} }
} }
/**
* Battler Tag that keeps track of how many times the user has Autotomized
* Each count of Autotomization reduces the weight by 100kg
*/
export class AutotomizedTag extends BattlerTag {
public autotomizeCount: number = 0;
constructor(sourceMove: Moves = Moves.AUTOTOMIZE) {
super(BattlerTagType.AUTOTOMIZED, BattlerTagLapseType.CUSTOM, 1, sourceMove);
}
/**
* Adds an autotomize count to the Pokemon. Each stack reduces weight by 100kg
* If the Pokemon is over 0.1kg it also displays a message.
* @param pokemon The Pokemon that is being autotomized
*/
onAdd(pokemon: Pokemon): void {
const minWeight = 0.1;
if (pokemon.getWeight() > minWeight) {
pokemon.scene.queueMessage(i18next.t("battlerTags:autotomizeOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)
}));
}
this.autotomizeCount += 1;
}
onOverlap(pokemon: Pokemon): void {
this.onAdd(pokemon);
}
}
export class SubstituteTag extends BattlerTag { export class SubstituteTag extends BattlerTag {
/** The substitute's remaining HP. If HP is depleted, the Substitute fades. */ /** The substitute's remaining HP. If HP is depleted, the Substitute fades. */
public hp: number; public hp: number;
@ -2568,6 +2598,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new GorillaTacticsTag(); return new GorillaTacticsTag();
case BattlerTagType.SUBSTITUTE: case BattlerTagType.SUBSTITUTE:
return new SubstituteTag(sourceMove, sourceId); return new SubstituteTag(sourceMove, sourceId);
case BattlerTagType.AUTOTOMIZED:
return new AutotomizedTag();
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
return new MysteryEncounterPostSummonTag(); return new MysteryEncounterPostSummonTag();
case BattlerTagType.HEAL_BLOCK: case BattlerTagType.HEAL_BLOCK:

View File

@ -172,11 +172,9 @@ export abstract class Challenge {
* @param overrideValue {@link integer} The value to check for. If undefined, gets the current value. * @param overrideValue {@link integer} The value to check for. If undefined, gets the current value.
* @returns {@link string} The localised name for the current value. * @returns {@link string} The localised name for the current value.
*/ */
getValue(overrideValue?: integer): string { getValue(overrideValue?: number): string {
if (overrideValue === undefined) { const value = overrideValue ?? this.value;
overrideValue = this.value; return i18next.t(`challenges:${this.geti18nKey()}.value.${value}`);
}
return i18next.t(`challenges:${this.geti18nKey()}.value.${this.value}`);
} }
/** /**
@ -184,11 +182,9 @@ export abstract class Challenge {
* @param overrideValue {@link integer} The value to check for. If undefined, gets the current value. * @param overrideValue {@link integer} The value to check for. If undefined, gets the current value.
* @returns {@link string} The localised description for the current value. * @returns {@link string} The localised description for the current value.
*/ */
getDescription(overrideValue?: integer): string { getDescription(overrideValue?: number): string {
if (overrideValue === undefined) { const value = overrideValue ?? this.value;
overrideValue = this.value; return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc`])}`;
}
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${this.value}`, `challenges:${this.geti18nKey()}.desc`])}`;
} }
/** /**
@ -511,14 +507,12 @@ export class SingleGenerationChallenge extends Challenge {
* @param {value} overrideValue The value to check for. If undefined, gets the current value. * @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised name for the current value. * @returns {string} The localised name for the current value.
*/ */
getValue(overrideValue?: integer): string { getValue(overrideValue?: number): string {
if (overrideValue === undefined) { const value = overrideValue ?? this.value;
overrideValue = this.value; if (value === 0) {
}
if (this.value === 0) {
return i18next.t("settings:off"); return i18next.t("settings:off");
} }
return i18next.t(`starterSelectUiHandler:gen${this.value}`); return i18next.t(`starterSelectUiHandler:gen${value}`);
} }
/** /**
@ -526,14 +520,12 @@ export class SingleGenerationChallenge extends Challenge {
* @param {value} overrideValue The value to check for. If undefined, gets the current value. * @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised description for the current value. * @returns {string} The localised description for the current value.
*/ */
getDescription(overrideValue?: integer): string { getDescription(overrideValue?: number): string {
if (overrideValue === undefined) { const value = overrideValue ?? this.value;
overrideValue = this.value; if (value === 0) {
}
if (this.value === 0) {
return i18next.t("challenges:singleGeneration.desc_default"); return i18next.t("challenges:singleGeneration.desc_default");
} }
return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${this.value}`) }); return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${value}`) });
} }

View File

@ -650,7 +650,7 @@ export default class Move implements Localizable {
} }
/** /**
* Applies each {@linkcode MoveCondition} of this move to the params * Applies each {@linkcode MoveCondition} function of this move to the params, determines if the move can be used prior to calling each attribute's apply()
* @param user {@linkcode Pokemon} to apply conditions to * @param user {@linkcode Pokemon} to apply conditions to
* @param target {@linkcode Pokemon} to apply conditions to * @param target {@linkcode Pokemon} to apply conditions to
* @param move {@linkcode Move} to apply conditions to * @param move {@linkcode Move} to apply conditions to
@ -2091,21 +2091,20 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
if (target.status) { if (target.status) {
return false; return false;
} } else {
//@ts-ignore - how can target.status.effect be checked when we return `false` before when it's defined? const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { // TODO: resolve ts-ignore
const statusAfflictResult = target.trySetStatus(statusToApply, true, user); if (canSetStatus) {
if (statusAfflictResult) {
if (user.status) { if (user.status) {
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user))); user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
} }
user.resetStatus(); user.resetStatus();
user.updateInfo(); user.updateInfo();
} target.trySetStatus(statusToApply, true, user);
return statusAfflictResult;
} }
return false; return canSetStatus;
}
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
@ -5175,30 +5174,28 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
} }
export class ForceSwitchOutAttr extends MoveEffectAttr { export class ForceSwitchOutAttr extends MoveEffectAttr {
private user: boolean; constructor(
private batonPass: boolean; private selfSwitch: boolean = false,
private batonPass: boolean = false
constructor(user?: boolean, batonPass?: boolean) { ) {
super(false, MoveEffectTrigger.POST_APPLY, false, true); super(false, MoveEffectTrigger.POST_APPLY, false, true);
this.user = !!user;
this.batonPass = !!batonPass;
} }
isBatonPass() { isBatonPass() {
return this.batonPass; return this.batonPass;
} }
// TODO: Why is this a Promise?
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return new Promise(resolve => { return new Promise(resolve => {
// Check if the move category is not STATUS or if the switch out condition is not met
if (!this.getSwitchOutCondition()(user, target, move)) { if (!this.getSwitchOutCondition()(user, target, move)) {
return resolve(false); return resolve(false);
} }
// Move the switch out logic inside the conditional block // Move the switch out logic inside the conditional block
// This ensures that the switch out only happens when the conditions are met // This ensures that the switch out only happens when the conditions are met
const switchOutTarget = this.user ? user : target; const switchOutTarget = this.selfSwitch ? user : target;
if (switchOutTarget instanceof PlayerPokemon) { if (switchOutTarget instanceof PlayerPokemon) {
switchOutTarget.leaveField(!this.batonPass); switchOutTarget.leaveField(!this.batonPass);
@ -5215,7 +5212,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
if (switchOutTarget.hp > 0) { if (switchOutTarget.hp > 0) {
// for opponent switching out // for opponent switching out
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(), (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, this.batonPass, false), MoveEndPhase); user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(),
(user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
false, this.batonPass, false), MoveEndPhase);
} }
} else { } else {
// Switch out logic for everything else (eg: WILD battles) // Switch out logic for everything else (eg: WILD battles)
@ -5257,29 +5256,33 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
getSwitchOutCondition(): MoveConditionFunc { getSwitchOutCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const switchOutTarget = (this.user ? user : target); const switchOutTarget = (this.selfSwitch ? user : target);
const player = switchOutTarget instanceof PlayerPokemon; const player = switchOutTarget instanceof PlayerPokemon;
if (!this.user && move.hitsSubstitute(user, target)) { if (!this.selfSwitch) {
if (move.hitsSubstitute(user, target)) {
return false; return false;
} }
if (!this.user && move.category === MoveCategory.STATUS && (target.hasAbilityWithAttr(ForceSwitchOutImmunityAbAttr))) { const blockedByAbility = new Utils.BooleanHolder(false);
return false; applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility);
return !blockedByAbility.value;
} }
if (!player && !user.scene.currentBattle.battleType) { if (!player && user.scene.currentBattle.battleType === BattleType.WILD) {
if (this.batonPass) { if (this.batonPass) {
return false; return false;
} }
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on // Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
if (!(user.scene.currentBattle.waveIndex % 10)) { if (user.scene.currentBattle.waveIndex % 10 === 0) {
return false; return false;
} }
} }
const party = player ? user.scene.getParty() : user.scene.getEnemyParty(); const party = player ? user.scene.getParty() : user.scene.getEnemyParty();
return (!player && !user.scene.currentBattle.battleType) || party.filter(p => p.isAllowedInBattle() && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount(); return (!player && !user.scene.currentBattle.battleType)
|| party.filter(p => p.isAllowedInBattle()
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount();
}; };
} }
@ -5287,8 +5290,8 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
if (!user.scene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) { if (!user.scene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) {
return -20; return -20;
} }
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move); let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
if (this.user && this.batonPass) { if (this.selfSwitch && this.batonPass) {
const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0); const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0);
ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10)); ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10));
} }
@ -5296,6 +5299,21 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
} }
export class ChillyReceptionAttr extends ForceSwitchOutAttr {
// using inherited constructor
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
user.scene.arena.trySetWeather(WeatherType.SNOW, true);
return super.apply(user, target, move, args);
}
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
return (user, target, move) => user.scene.arena.trySetWeather(WeatherType.SNOW, true) || super.getSwitchOutCondition()(user, target, move);
}
}
export class RemoveTypeAttr extends MoveEffectAttr { export class RemoveTypeAttr extends MoveEffectAttr {
private removedType: Type; private removedType: Type;
@ -8091,7 +8109,7 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1), .attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1),
new SelfStatusMove(Moves.AUTOTOMIZE, Type.STEEL, -1, 15, -1, 0, 5) new SelfStatusMove(Moves.AUTOTOMIZE, Type.STEEL, -1, 15, -1, 0, 5)
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true) .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true)
.partial(), .attr(AddBattlerTagAttr, BattlerTagType.AUTOTOMIZED, true),
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5) new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
.powderMove() .powderMove()
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true), .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
@ -9073,8 +9091,7 @@ export function initMoves() {
new AttackMove(Moves.AURA_WHEEL, Type.ELECTRIC, MoveCategory.PHYSICAL, 110, 100, 10, 100, 0, 8) new AttackMove(Moves.AURA_WHEEL, Type.ELECTRIC, MoveCategory.PHYSICAL, 110, 100, 10, 100, 0, 8)
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true) .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
.makesContact(false) .makesContact(false)
.attr(AuraWheelTypeAttr) .attr(AuraWheelTypeAttr),
.condition((user, target, move) => [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)), // Missing custom fail message
new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8) new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8)
.target(MoveTarget.ALL_NEAR_ENEMIES) .target(MoveTarget.ALL_NEAR_ENEMIES)
.attr(StatStageChangeAttr, [ Stat.ATK ], -1), .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
@ -9486,10 +9503,9 @@ export function initMoves() {
.makesContact(), .makesContact(),
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9) new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
.unimplemented(), .unimplemented(),
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9) new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
.attr(WeatherChangeAttr, WeatherType.SNOW) .attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", {pokemonName: getPokemonNameWithAffix(user)}))
.attr(ForceSwitchOutAttr, true, false) .attr(ChillyReceptionAttr, true, false),
.target(MoveTarget.BOTH_SIDES),
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9) new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
.attr(RemoveArenaTrapAttr, true) .attr(RemoveArenaTrapAttr, true)

View File

@ -597,7 +597,7 @@ export class TrainerConfig {
case "flare": { case "flare": {
return { return {
[TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY], [TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY],
[TrainerPoolTier.UNCOMMON]: [Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.BERGMITE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO], [TrainerPoolTier.UNCOMMON]: [Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO],
[TrainerPoolTier.RARE]: [Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO] [TrainerPoolTier.RARE]: [Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO]
}; };
} }
@ -640,14 +640,14 @@ export class TrainerConfig {
return { return {
[TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ],
[TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ],
[TrainerPoolTier.RARE]: [ Species.BULBASAUR, Species.GLIMMET ] [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ]
}; };
} }
case "star_4": { case "star_4": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ],
[TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ],
[TrainerPoolTier.RARE]: [ Species.POPPLIO, Species.GALAR_PONYTA ] [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ]
}; };
} }
case "star_5": { case "star_5": {
@ -1509,7 +1509,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools({ .setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH, Species.SPHEAL, Species.REMORAID, Species.QWILFISH, Species.BARBOACH], [TrainerPoolTier.COMMON]: [Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH, Species.SPHEAL, Species.REMORAID, Species.QWILFISH, Species.BARBOACH],
[TrainerPoolTier.UNCOMMON]: [Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.CLOBBOPUS, Species.HORSEA], [TrainerPoolTier.UNCOMMON]: [Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.CLOBBOPUS, Species.HORSEA],
[TrainerPoolTier.RARE]: [Species.MANTINE, Species.DHELMISE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.PALDEA_WOOPER, Species.SKRELP], [TrainerPoolTier.RARE]: [Species.MANTYKE, Species.DHELMISE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.PALDEA_WOOPER, Species.SKRELP],
[TrainerPoolTier.SUPER_RARE]: [Species.DONDOZO, Species.BASCULEGION] [TrainerPoolTier.SUPER_RARE]: [Species.DONDOZO, Species.BASCULEGION]
}), }),
[TrainerType.MATT]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin", "aqua", [Species.SHARPEDO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.MATT]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin", "aqua", [Species.SHARPEDO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1527,8 +1527,8 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) [TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({ .setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE, Species.SANDILE, Species.TRUBBISH, Species.TYMPOLE], [TrainerPoolTier.COMMON]: [Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE, Species.SANDILE, Species.TRUBBISH, Species.TYMPOLE],
[TrainerPoolTier.UNCOMMON]: [Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.JOLTIK], [TrainerPoolTier.UNCOMMON]: [Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.JOLTIK, Species.CUBCHOO, Species.KLINK],
[TrainerPoolTier.RARE]: [Species.PAWNIARD, Species.RUFFLET, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.CUBCHOO, Species.MIENFOO, Species.DURANT, Species.BOUFFALANT], [TrainerPoolTier.RARE]: [Species.PAWNIARD, Species.RUFFLET, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.MIENFOO, Species.DURANT, Species.BOUFFALANT],
[TrainerPoolTier.SUPER_RARE]: [Species.DRUDDIGON, Species.HISUI_ZORUA, Species.AXEW, Species.DEINO] [TrainerPoolTier.SUPER_RARE]: [Species.DRUDDIGON, Species.HISUI_ZORUA, Species.AXEW, Species.DEINO]
}), }),
[TrainerType.ZINZOLIN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [Species.CRYOGONAL]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.ZINZOLIN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [Species.CRYOGONAL]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1537,7 +1537,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools({ .setSpeciesPools({
[TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR], [TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR],
[TrainerPoolTier.UNCOMMON]: [Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP], [TrainerPoolTier.UNCOMMON]: [Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP],
[TrainerPoolTier.RARE]: [Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.BERGMITE, Species.SLIGGOO], [TrainerPoolTier.RARE]: [Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO],
[TrainerPoolTier.SUPER_RARE]: [Species.NOIVERN, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG] [TrainerPoolTier.SUPER_RARE]: [Species.NOIVERN, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG]
}), }),
[TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [Species.LIEPARD]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [Species.LIEPARD]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1545,15 +1545,15 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.AETHER_GRUNT]: new TrainerConfig(++t).setHasGenders("Aether Grunt Female").setHasDouble("Aether Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) [TrainerType.AETHER_GRUNT]: new TrainerConfig(++t).setHasGenders("Aether Grunt Female").setHasDouble("Aether Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({ .setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.PIKIPEK, Species.ROCKRUFF, Species.ALOLA_DIGLETT, Species.ALOLA_EXEGGUTOR, Species.YUNGOOS, Species.CORSOLA, Species.ALOLA_GEODUDE, Species.ALOLA_RAICHU, Species.BOUNSWEET, Species.LILLIPUP, Species.KOMALA, Species.MORELULL, Species.COMFEY, Species.TOGEDEMARU], [TrainerPoolTier.COMMON]: [ Species.PIKIPEK, Species.ROCKRUFF, Species.ALOLA_DIGLETT, Species.ALOLA_EXEGGUTOR, Species.YUNGOOS, Species.CORSOLA, Species.ALOLA_GEODUDE, Species.ALOLA_RAICHU, Species.BOUNSWEET, Species.LILLIPUP, Species.KOMALA, Species.MORELULL, Species.COMFEY, Species.TOGEDEMARU],
[TrainerPoolTier.UNCOMMON]: [ Species.POLIWAG, Species.STUFFUL, Species.ORANGURU, Species.PASSIMIAN, Species.BRUXISH, Species.MINIOR, Species.WISHIWASHI, Species.CRABRAWLER, Species.CUTIEFLY, Species.ORICORIO, Species.MUDBRAY, Species.PYUKUMUKU, Species.ALOLA_MAROWAK], [TrainerPoolTier.UNCOMMON]: [ Species.POLIWAG, Species.STUFFUL, Species.ORANGURU, Species.PASSIMIAN, Species.BRUXISH, Species.MINIOR, Species.WISHIWASHI, Species.ALOLA_SANDSHREW, Species.ALOLA_VULPIX, Species.CRABRAWLER, Species.CUTIEFLY, Species.ORICORIO, Species.MUDBRAY, Species.PYUKUMUKU, Species.ALOLA_MAROWAK],
[TrainerPoolTier.RARE]: [ Species.GALAR_CORSOLA, Species.ALOLA_SANDSHREW, Species.ALOLA_VULPIX, Species.TURTONATOR, Species.DRAMPA], [TrainerPoolTier.RARE]: [ Species.GALAR_CORSOLA, Species.TURTONATOR, Species.MIMIKYU, Species.MAGNEMITE, Species.DRAMPA],
[TrainerPoolTier.SUPER_RARE]: [Species.JANGMO_O, Species.PORYGON] [TrainerPoolTier.SUPER_RARE]: [Species.JANGMO_O, Species.PORYGON]
}), }),
[TrainerType.FABA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aether_admin", "aether", [Species.HYPNO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.FABA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aether_admin", "aether", [Species.HYPNO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.SKULL_GRUNT]: new TrainerConfig(++t).setHasGenders("Skull Grunt Female").setHasDouble("Skull Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_skull_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) [TrainerType.SKULL_GRUNT]: new TrainerConfig(++t).setHasGenders("Skull Grunt Female").setHasDouble("Skull Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_skull_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({ .setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.SALANDIT, Species.ALOLA_RATTATA, Species.EKANS, Species.ALOLA_MEOWTH, Species.SCRAGGY, Species.KOFFING, Species.ALOLA_GRIMER, Species.MAREANIE, Species.SPINARAK, Species.TRUBBISH], [TrainerPoolTier.COMMON]: [ Species.SALANDIT, Species.ALOLA_RATTATA, Species.EKANS, Species.ALOLA_MEOWTH, Species.SCRAGGY, Species.KOFFING, Species.ALOLA_GRIMER, Species.MAREANIE, Species.SPINARAK, Species.TRUBBISH, Species.DROWZEE],
[TrainerPoolTier.UNCOMMON]: [ Species.FOMANTIS, Species.SABLEYE, Species.SANDILE, Species.HOUNDOUR, Species.ALOLA_MAROWAK, Species.GASTLY, Species.PANCHAM, Species.DROWZEE, Species.ZUBAT, Species.VENIPEDE, Species.VULLABY], [TrainerPoolTier.UNCOMMON]: [ Species.FOMANTIS, Species.SABLEYE, Species.SANDILE, Species.HOUNDOUR, Species.ALOLA_MAROWAK, Species.GASTLY, Species.PANCHAM, Species.ZUBAT, Species.VENIPEDE, Species.VULLABY],
[TrainerPoolTier.RARE]: [Species.SANDYGAST, Species.PAWNIARD, Species.MIMIKYU, Species.DHELMISE, Species.WISHIWASHI, Species.NYMBLE], [TrainerPoolTier.RARE]: [Species.SANDYGAST, Species.PAWNIARD, Species.MIMIKYU, Species.DHELMISE, Species.WISHIWASHI, Species.NYMBLE],
[TrainerPoolTier.SUPER_RARE]: [Species.GRUBBIN, Species.DEWPIDER] [TrainerPoolTier.SUPER_RARE]: [Species.GRUBBIN, Species.DEWPIDER]
}), }),
@ -1916,7 +1916,14 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // Mega Kangaskhan p.formIndex = 1; // Mega Kangaskhan
p.generateName(); p.generateName();
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([Species.GASTRODON, Species.SEISMITOAD])) .setPartyMemberFunc(4, getRandomPartyMemberFunc([Species.GASTRODON, Species.SEISMITOAD], TrainerSlot.TRAINER, true, p => {
//Storm Drain Gastrodon, Water Absorb Seismitoad
if (p.species.speciesId === Species.GASTRODON) {
p.abilityIndex = 0;
} else if (p.species.speciesId === Species.SEISMITOAD) {
p.abilityIndex = 2;
}
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([Species.MEWTWO], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([Species.MEWTWO], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2060,7 +2067,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = Utils.randSeedInt(5, 1); // Shock, Burn, Chill, or Douse Drive p.formIndex = Utils.randSeedInt(4, 1); // Shock, Burn, Chill, or Douse Drive
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BASCULEGION, Species.JELLICENT ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BASCULEGION, Species.JELLICENT ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2153,9 +2160,23 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
})), })),
[TrainerType.GUZMA]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", []).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma") [TrainerType.GUZMA]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", []).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LOKIX, Species.YANMEGA ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LOKIX, Species.YANMEGA ], TrainerSlot.TRAINER, true, p => {
//Tinted Lens Lokix, Tinted Lens Yanmega
if (p.species.speciesId === Species.LOKIX) {
p.abilityIndex = 2;
} else if (p.species.speciesId === Species.YANMEGA) {
p.abilityIndex = 1;
}
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HERACROSS ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HERACROSS ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => {
//Technician Scizor, Sharpness Kleavor
if (p.species.speciesId === Species.SCIZOR) {
p.abilityIndex = 1;
} else if (p.species.speciesId === Species.KLEAVOR) {
p.abilityIndex = 2;
}
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALVANTULA, Species.VIKAVOLT])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALVANTULA, Species.VIKAVOLT]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
@ -2175,25 +2196,32 @@ export const trainerConfigs: TrainerConfigs = {
p.abilityIndex = 2; //Anticipation p.abilityIndex = 2; //Anticipation
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HISUI_SAMUROTT, Species.CRAWDAUNT ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => {
//Technician Scizor, Sharpness Kleavor
if (p.species.speciesId === Species.SCIZOR) {
p.abilityIndex = 1;
} else if (p.species.speciesId === Species.KLEAVOR) {
p.abilityIndex = 2;
}
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HISUI_SAMUROTT, Species.CRAWDAUNT ], TrainerSlot.TRAINER, true, p => {
p.abilityIndex = 2; //Sharpness Hisui Samurott, Adaptability Crawdaunt p.abilityIndex = 2; //Sharpness Hisui Samurott, Adaptability Crawdaunt
})) }))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.BUZZWOLE ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ROGUE_BALL;
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.XURKITREE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ROGUE_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.formIndex = 1; p.formIndex = 1;
p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.BUZZWOLE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ROGUE_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.XURKITREE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ROGUE_BALL;
})), })),
[TrainerType.ROSE]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", []).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma") [TrainerType.ROSE]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", []).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ]))
@ -2209,17 +2237,16 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})), })),
[TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma") [TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.MELMETAL ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AEGISLASH, Species.GHOLDENGO ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AEGISLASH, Species.GHOLDENGO ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.DRACOVISH, Species.DRACOZOLT ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.DRACOVISH, Species.DRACOZOLT ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.abilityIndex = 1; //Strong Jaw Dracovish, Hustle Dracozolt p.abilityIndex = 1; //Strong Jaw Dracovish, Hustle Dracozolt
})) }))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.ARCHALUDON ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.MELMETAL ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GALAR_ARTICUNO, Species.GALAR_ZAPDOS, Species.GALAR_MOLTRES ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GALAR_ARTICUNO, Species.GALAR_ZAPDOS, Species.GALAR_MOLTRES ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();

View File

@ -79,6 +79,7 @@ export enum BattlerTagType {
TAR_SHOT = "TAR_SHOT", TAR_SHOT = "TAR_SHOT",
BURNED_UP = "BURNED_UP", BURNED_UP = "BURNED_UP",
DOUBLE_SHOCKED = "DOUBLE_SHOCKED", DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
AUTOTOMIZED = "AUTOTOMIZED",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
HEAL_BLOCK = "HEAL_BLOCK", HEAL_BLOCK = "HEAL_BLOCK",
} }

View File

@ -33,6 +33,7 @@ export class Arena {
public tags: ArenaTag[]; public tags: ArenaTag[];
public bgm: string; public bgm: string;
public ignoreAbilities: boolean; public ignoreAbilities: boolean;
public ignoringEffectSource: BattlerIndex | null;
private lastTimeOfDay: TimeOfDay; private lastTimeOfDay: TimeOfDay;
@ -569,8 +570,9 @@ export class Arena {
} }
} }
setIgnoreAbilities(ignoreAbilities: boolean = true): void { setIgnoreAbilities(ignoreAbilities: boolean, ignoringEffectSource: BattlerIndex | null = null): void {
this.ignoreAbilities = ignoreAbilities; this.ignoreAbilities = ignoreAbilities;
this.ignoringEffectSource = ignoreAbilities ? ignoringEffectSource : null;
} }
/** /**

View File

@ -17,10 +17,10 @@ import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag } from "../data/battler-tags"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags";
import { WeatherType } from "../data/weather"; import { WeatherType } from "../data/weather";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability"; import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "../data/ability";
import PokemonData from "../system/pokemon-data"; import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Mode } from "../ui/ui"; import { Mode } from "../ui/ui";
@ -1364,7 +1364,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) { if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
return false; return false;
} }
if (this.scene?.arena.ignoreAbilities && ability.isIgnorable) { const arena = this.scene?.arena;
if (arena.ignoreAbilities && arena.ignoringEffectSource !== this.getBattlerIndex() && ability.isIgnorable) {
return false; return false;
} }
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) { if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
@ -1426,11 +1427,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
/**
* Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal)
* @returns the kg of the Pokemon (minimum of 0.1)
*/
getWeight(): number { getWeight(): number {
const weight = new Utils.NumberHolder(this.species.weight); const autotomizedTag = this.getTag(AutotomizedTag);
let weightRemoved = 0;
if (!Utils.isNullOrUndefined(autotomizedTag)) {
weightRemoved = 100 * autotomizedTag!.autotomizeCount;
}
const minWeight = 0.1;
const weight = new Utils.NumberHolder(this.species.weight - weightRemoved);
// This will trigger the ability overlay so only call this function when necessary // This will trigger the ability overlay so only call this function when necessary
applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight); applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight);
return weight.value; return Math.max(minWeight, weight.value);
} }
/** /**
@ -3365,7 +3378,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (asPhase) { if (asPhase) {
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText!, sourcePokemon!)); // TODO: are these bangs correct? this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon));
return true; return true;
} }
@ -3399,6 +3412,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (effect !== StatusEffect.FAINT) { if (effect !== StatusEffect.FAINT) {
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true); this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, this, effect, sourcePokemon);
} }
return true; return true;

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "Giftpuppenspiel", "name": "Giftpuppenspiel",
"description": "Wenn Infamomo ein Ziel mit einer Attacke vergiftet, so wird dieses auch verwirrt." "description": "Wenn das Pokémon ein Ziel mit einer Attacke vergiftet, so wird dieses auch verwirrt."
} }
} }

View File

@ -111,17 +111,17 @@
"forest": "PMD Erkundungsteam Himmel Düsterwald", "forest": "PMD Erkundungsteam Himmel Düsterwald",
"grass": "PMD Erkundungsteam Himmel Apfelwald", "grass": "PMD Erkundungsteam Himmel Apfelwald",
"graveyard": "PMD Erkundungsteam Himmel Verwirrwald", "graveyard": "PMD Erkundungsteam Himmel Verwirrwald",
"ice_cave": "PMD Erkundungsteam Himmel Rieseneisberg", "ice_cave": "Firel - -50°C",
"island": "PMD Erkundungsteam Himmel Schroffküste", "island": "PMD Erkundungsteam Himmel Schroffküste",
"jungle": "Lmz - Jungle", "jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory", "laboratory": "Firel - Laboratory",
"lake": "PMD Erkundungsteam Himmel Kristallhöhle", "lake": "Lmz - Lake",
"meadow": "PMD Erkundungsteam Himmel Himmelsgipfel-Wald", "meadow": "PMD Erkundungsteam Himmel Himmelsgipfel-Wald",
"metropolis": "Firel - Metropolis", "metropolis": "Firel - Metropolis",
"mountain": "PMD Erkundungsteam Himmel Hornberg", "mountain": "PMD Erkundungsteam Himmel Hornberg",
"plains": "PMD Erkundungsteam Himmel Himmelsgipfel-Prärie", "plains": "Firel - Route 888",
"power_plant": "PMD Erkundungsteam Himmel Weite Ampere-Ebene", "power_plant": "Firel - The Klink",
"ruins": "PMD Erkundungsteam Himmel Tiefes Ruinenverlies", "ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - Marine Mystique", "sea": "Andr06 - Marine Mystique",
"seabed": "Firel - Seabed", "seabed": "Firel - Seabed",
"slum": "Andr06 - Sneaky Snom", "slum": "Andr06 - Sneaky Snom",
@ -131,7 +131,7 @@
"tall_grass": "PMD Erkundungsteam Himmel Nebelwald", "tall_grass": "PMD Erkundungsteam Himmel Nebelwald",
"temple": "PMD Erkundungsteam Himmel Ägishöhle", "temple": "PMD Erkundungsteam Himmel Ägishöhle",
"town": "PMD Erkundungsteam Himmel Zufälliges Dungeon-Theme 3", "town": "PMD Erkundungsteam Himmel Zufälliges Dungeon-Theme 3",
"volcano": "PMD Erkundungsteam Himmel Dunsthöhle", "volcano": "Firel - Twisturn Volcano",
"wasteland": "PMD Erkundungsteam Himmel Verborgenes Hochland", "wasteland": "PMD Erkundungsteam Himmel Verborgenes Hochland",
"encounter_ace_trainer": "SW Trainerblicke treffen sich (Ass-Trainer)", "encounter_ace_trainer": "SW Trainerblicke treffen sich (Ass-Trainer)",
"encounter_backpacker": "SW Trainerblicke treffen sich (Backpacker)", "encounter_backpacker": "SW Trainerblicke treffen sich (Backpacker)",

View File

@ -65,6 +65,7 @@
"suppressAbilities": "Die Fähigkeit von {{pokemonName}} wirkt nicht mehr!", "suppressAbilities": "Die Fähigkeit von {{pokemonName}} wirkt nicht mehr!",
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!", "revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
"swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!", "swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!",
"chillyReception": "{{pokemonName}} erzählt einen schlechten Witz, der nicht besonders gut ankommt...",
"exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!", "exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!",
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!", "safeguard": "{{targetName}} wird durch Bodyguard geschützt!",
"afterYou": "{{targetName}} lässt sich auf Galanterie ein!" "afterYou": "{{targetName}} lässt sich auf Galanterie ein!"

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "Aura-Rad", "name": "Aura-Rad",
"effect": "Mithilfe der in den Backentaschen gespeicherten Energie greift der Anwender an und erhöht seine Initiative. Der Typ der Attacke hängt von Morpekos Form ab." "effect": "Mithilfe der in den Backentaschen gespeicherten Energie greift der Anwender an und erhöht seine Initiative. Wenn dies von Morpeko verwendet wird hängt der Typ der Attacke von dessen Form ab."
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "Breitseite", "name": "Breitseite",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "Poison Puppeteer", "name": "Poison Puppeteer",
"description": "Pokémon poisoned by Pecharunt's moves will also become confused." "description": "Pokémon poisoned by this Pokémon's moves will also become confused."
} }
} }

View File

@ -73,5 +73,6 @@
"tarShotOnAdd": "{{pokemonNameWithAffix}} became weaker to fire!", "tarShotOnAdd": "{{pokemonNameWithAffix}} became weaker to fire!",
"substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!", "substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!",
"substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!", "substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!",
"substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!" "substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!",
"autotomizeOnAdd": "{{pokemonNameWIthAffix}} became nimble!"
} }

View File

@ -111,7 +111,7 @@
"forest": "PMD EoS Dusk Forest", "forest": "PMD EoS Dusk Forest",
"grass": "PMD EoS Apple Woods", "grass": "PMD EoS Apple Woods",
"graveyard": "PMD EoS Mystifying Forest", "graveyard": "PMD EoS Mystifying Forest",
"ice_cave": "Firel - -60F", "ice_cave": "Firel - -50°C",
"island": "PMD EoS Craggy Coast", "island": "PMD EoS Craggy Coast",
"jungle": "Lmz - Jungle", "jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory", "laboratory": "Firel - Laboratory",

View File

@ -66,6 +66,7 @@
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!", "suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
"revivalBlessing": "{{pokemonName}} was revived!", "revivalBlessing": "{{pokemonName}} was revived!",
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!", "swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
"chillyReception": "{{pokemonName}} is preparing to tell a chillingly bad joke!",
"exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!", "exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!",
"safeguard": "{{targetName}} is protected by Safeguard!", "safeguard": "{{targetName}} is protected by Safeguard!",
"substituteOnOverlap": "{{pokemonName}} already\nhas a substitute!", "substituteOnOverlap": "{{pokemonName}} already\nhas a substitute!",

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "Aura Wheel", "name": "Aura Wheel",
"effect": "Morpeko attacks and raises its Speed with the energy stored in its cheeks. This move's type changes depending on the user's form." "effect": "The user attacks and raises its Speed with the energy stored in its cheeks. If used by Morpeko, this move's type changes depending on the user's form."
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "Breaking Swipe", "name": "Breaking Swipe",

View File

@ -4,7 +4,7 @@
"costar": "¡{{pokemonName}} copió los cambios de características de {{allyName}}!", "costar": "¡{{pokemonName}} copió los cambios de características de {{allyName}}!",
"iceFaceAvoidedDamage": "¡{{pokemonNameWithAffix}} evitó\ndaño con {{abilityName}}!", "iceFaceAvoidedDamage": "¡{{pokemonNameWithAffix}} evitó\ndaño con {{abilityName}}!",
"perishBody": "¡{{abilityName}} de {{pokemonName}} debilitará a ambos Pokémon en 3 turnos!", "perishBody": "¡{{abilityName}} de {{pokemonName}} debilitará a ambos Pokémon en 3 turnos!",
"poisonHeal": "¡{{pokemonNameWithAffix}} restauró algunos de sus PS gracias a {{abilityName}}!", "poisonHeal": "¡{{pokemonName}} restauró algunos de sus PS gracias a {{abilityName}}!",
"trace": "¡{{pokemonName}} ha copiado la habilidad {{abilityName}} \nde {{targetName}}!", "trace": "¡{{pokemonName}} ha copiado la habilidad {{abilityName}} \nde {{targetName}}!",
"windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!", "windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!",
"quickDraw": "¡{{pokemonName}} ataca primero gracias a la habilidad Mano Rápida!", "quickDraw": "¡{{pokemonName}} ataca primero gracias a la habilidad Mano Rápida!",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "Títere Tóxico", "name": "Títere Tóxico",
"description": "Los rivales que Pecharunt envenene con sus movimientos también sufrirán confusión." "description": "Los rivales que el usuario envenene con sus movimientos también sufrirán confusión."
} }
} }

View File

@ -107,17 +107,17 @@
"forest": "PMD EoS - Bosque Sombrío", "forest": "PMD EoS - Bosque Sombrío",
"grass": "PMD EoS - Manzanar", "grass": "PMD EoS - Manzanar",
"graveyard": "PMD EoS - Bosque Misterio", "graveyard": "PMD EoS - Bosque Misterio",
"ice_cave": "PMD EoS - Gran Iceberg", "ice_cave": "Firel - -50°C",
"island": "PMD EoS - Costa Escarpada", "island": "PMD EoS - Costa Escarpada",
"jungle": "Lmz - Jungla", "jungle": "Lmz - Jungla",
"laboratory": "Firel - Laboratorio", "laboratory": "Firel - Laboratorio",
"lake": "PMD EoS - Cueva Cristal", "lake": "Lmz - Lake",
"meadow": "PMD EoS - Bosque de la Cumbre del Cielo", "meadow": "PMD EoS - Bosque de la Cumbre del Cielo",
"metropolis": "Firel - Metrópolis", "metropolis": "Firel - Metrópolis",
"mountain": "PMD EoS - Monte Cuerno", "mountain": "PMD EoS - Monte Cuerno",
"plains": "PMD EoS - Pradera de la Cumbre del Cielo", "plains": "Firel - Route 888",
"power_plant": "PMD EoS - Pradera Destello", "power_plant": "Firel - The Klink",
"ruins": "PMD EoS - Sima Hermética", "ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - Misticismo marino", "sea": "Andr06 - Misticismo marino",
"seabed": "Firel - Lecho del mar", "seabed": "Firel - Lecho del mar",
"slum": "Andr06 - Snom sigiloso", "slum": "Andr06 - Snom sigiloso",
@ -127,7 +127,7 @@
"tall_grass": "PMD EoS - Bosque Niebla", "tall_grass": "PMD EoS - Bosque Niebla",
"temple": "PMD EoS - Cueva Regia", "temple": "PMD EoS - Cueva Regia",
"town": "PMD EoS - Tema del territorio aleatorio 3", "town": "PMD EoS - Tema del territorio aleatorio 3",
"volcano": "PMD EoS - Cueva Vapor", "volcano": "Firel - Twisturn Volcano",
"wasteland": "PMD EoS - Corazón Tierra Oculta", "wasteland": "PMD EoS - Corazón Tierra Oculta",
"encounter_ace_trainer": "BW - ¡Vs. entrenador guay!", "encounter_ace_trainer": "BW - ¡Vs. entrenador guay!",
"encounter_backpacker": "BW - ¡Vs. mochilero!", "encounter_backpacker": "BW - ¡Vs. mochilero!",

View File

@ -2913,7 +2913,7 @@
}, },
"zippyZap": { "zippyZap": {
"name": "Pikaturbo", "name": "Pikaturbo",
"effect": "The user attacks the target with bursts of electricity at high speed. This move always goes first and raises the user's evasiveness." "effect": "Ataque eléctrico a la velocidad del rayo. Este movimiento tiene prioridad alta y aumenta la Evasión del usuario."
}, },
"splishySplash": { "splishySplash": {
"name": "Salpikasurf", "name": "Salpikasurf",
@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "Rueda Aural", "name": "Rueda Aural",
"effect": "La energía que acumula en las mejillas le sirve para atacar y aumentar su Velocidad. Este movimiento cambia de tipo según la forma que adopte Morpeko." "effect": "La energía que acumula en las mejillas le sirve para atacar y aumentar su Velocidad. Si es utilizado por Morpeko, este movimiento cambia de tipo según la forma que adopte."
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "Vasto Impacto", "name": "Vasto Impacto",

View File

@ -1077,7 +1077,7 @@
}, },
"thermalExchange": { "thermalExchange": {
"name": "Thermodynamique", "name": "Thermodynamique",
"description": "Lorsque le Pokémon est touché par une capacité de type Feu, il ne subit aucun dégât et son Attaque augmente." "description": "Lorsque le Pokémon est touché par une capacité de type Feu, son Attaque augmente. Il ne peut pas être brulé."
}, },
"angerShell": { "angerShell": {
"name": "Courroupace", "name": "Courroupace",
@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "Emprise Toxique", "name": "Emprise Toxique",
"description": "Lorsque Pêchaminus empoisonne un Pokémon grâce à lune de ses capacités, ce dernier devient également confus." "description": "Lorsque le Pokémon en empoisonne un autre grâce à lune de ses capacités, ce dernier devient également confus."
} }
} }

View File

@ -14,6 +14,10 @@
"moneyWon": "Vous remportez\n{{moneyAmount}} ₽ !", "moneyWon": "Vous remportez\n{{moneyAmount}} ₽ !",
"moneyPickedUp": "Vous obtenez {{moneyAmount}} ₽ !", "moneyPickedUp": "Vous obtenez {{moneyAmount}} ₽ !",
"pokemonCaught": "Vous avez attrapé\n{{pokemonName}} !", "pokemonCaught": "Vous avez attrapé\n{{pokemonName}} !",
"pokemonObtained": "Vous obtenez\nun {{pokemonName}} !",
"pokemonBrokeFree": "Oh non !\nLe Pokémon sest libéré !",
"pokemonFled": "Le {{pokemonName}} sauvage\nprend la fuite !",
"playerFled": "Vous fuyez le {{pokemonName}} !",
"addedAsAStarter": "{{pokemonName}} est ajouté\ncomme starter !", "addedAsAStarter": "{{pokemonName}} est ajouté\ncomme starter !",
"partyFull": "Votre équipe est pleine.\nRelâcher un Pokémon pour {{pokemonName}} ?", "partyFull": "Votre équipe est pleine.\nRelâcher un Pokémon pour {{pokemonName}} ?",
"pokemon": "de Pokémon", "pokemon": "de Pokémon",
@ -65,6 +69,7 @@
"skipItemQuestion": "Êtes-vous sûr·e de ne pas vouloir prendre dobjet ?", "skipItemQuestion": "Êtes-vous sûr·e de ne pas vouloir prendre dobjet ?",
"itemStackFull": "Quantité maximale de {{fullItemName}} atteinte.\nVous recevez {{itemName}} à la place.", "itemStackFull": "Quantité maximale de {{fullItemName}} atteinte.\nVous recevez {{itemName}} à la place.",
"eggHatching": "Hein ?", "eggHatching": "Hein ?",
"eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?",
"ivScannerUseQuestion": "Utiliser le Scanner dIV\nsur {{pokemonName}} ?", "ivScannerUseQuestion": "Utiliser le Scanner dIV\nsur {{pokemonName}} ?",
"wildPokemonWithAffix": "{{pokemonName}} sauvage", "wildPokemonWithAffix": "{{pokemonName}} sauvage",
"foePokemonWithAffix": "{{pokemonName}} ennemi", "foePokemonWithAffix": "{{pokemonName}} ennemi",
@ -99,7 +104,7 @@
"unlockedSomething": "{{unlockedThing}}\na été débloqué.", "unlockedSomething": "{{unlockedThing}}\na été débloqué.",
"congratulations": "Félicitations !", "congratulations": "Félicitations !",
"beatModeFirstTime": "{{speciesName}} a battu le mode {{gameMode}} pour la première fois !\nVous avez reçu {{newModifier}} !", "beatModeFirstTime": "{{speciesName}} a battu le mode {{gameMode}} pour la première fois !\nVous avez reçu {{newModifier}} !",
"eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?", "ppReduced": "Les PP de la capacité {{moveName}}\nde{{targetName}} baissent de {{reduction}} !",
"battlerTagsHealBlock": "{{pokemonNameWithAffix}} ne peut pas guérir !", "battlerTagsHealBlock": "{{pokemonNameWithAffix}} ne peut pas guérir !",
"battlerTagsHealBlockOnRemove": "Le blocage de soins qui affectait\n{{pokemonNameWithAffix}} sest dissipé !" "battlerTagsHealBlockOnRemove": "Le blocage de soins qui affectait\n{{pokemonNameWithAffix}} sest dissipé !"
} }

View File

@ -111,17 +111,17 @@
"forest": "PDM EdC - Forêt Crépuscule", "forest": "PDM EdC - Forêt Crépuscule",
"grass": "PDM EdC - Bois aux Pommes", "grass": "PDM EdC - Bois aux Pommes",
"graveyard": "PDM EdC - Forêt Trompeuse", "graveyard": "PDM EdC - Forêt Trompeuse",
"ice_cave": "PDM EdC - Montagne Glacier", "ice_cave": "Firel - -50°C",
"island": "PDM EdC - Côte Escarpée", "island": "PDM EdC - Côte Escarpée",
"jungle": "Lmz - Jungle", "jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory", "laboratory": "Firel - Laboratory",
"lake": "PDM EdC - Caverne Cristal", "lake": "Lmz - Lake",
"meadow": "PDM EdC - Pic Céleste (forêt)", "meadow": "PDM EdC - Pic Céleste (forêt)",
"metropolis": "Firel - Metropolis", "metropolis": "Firel - Metropolis",
"mountain": "PDM EdC - Mont Corne", "mountain": "PDM EdC - Mont Corne",
"plains": "PDM EdC - Pic Céleste (prairie)", "plains": "Firel - Route 888",
"power_plant": "PDM EdC - Plaines Élek", "power_plant": "Firel - The Klink",
"ruins": "PDM EdC - Ruine Scellée", "ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - Marine Mystique", "sea": "Andr06 - Marine Mystique",
"seabed": "Firel - Seabed", "seabed": "Firel - Seabed",
"slum": "Andr06 - Sneaky Snom", "slum": "Andr06 - Sneaky Snom",
@ -131,7 +131,7 @@
"tall_grass": "PDM EdC - Forêt Brumeuse", "tall_grass": "PDM EdC - Forêt Brumeuse",
"temple": "PDM EdC - Grotte Égide", "temple": "PDM EdC - Grotte Égide",
"town": "PDM EdC - Donjon aléatoire - Thème 3", "town": "PDM EdC - Donjon aléatoire - Thème 3",
"volcano": "PDM EdC - Grotte Étuve", "volcano": "Firel - Twisturn Volcano",
"wasteland": "PDM EdC - Terres Illusoires", "wasteland": "PDM EdC - Terres Illusoires",
"encounter_ace_trainer": "NB - Regards croisés (Topdresseur·euse)", "encounter_ace_trainer": "NB - Regards croisés (Topdresseur·euse)",
"encounter_backpacker": "NB - Regards croisés (Randonneur·euse)", "encounter_backpacker": "NB - Regards croisés (Randonneur·euse)",

View File

@ -66,6 +66,7 @@
"suppressAbilities": "Le talent de {{pokemonName}}\na été rendu inactif !", "suppressAbilities": "Le talent de {{pokemonName}}\na été rendu inactif !",
"revivalBlessing": "{{pokemonName}} a repris connaissance\net est prêt à se battre de nouveau !", "revivalBlessing": "{{pokemonName}} a repris connaissance\net est prêt à se battre de nouveau !",
"swapArenaTags": "Les effets affectant chaque côté du terrain\nont été échangés par {{pokemonName}} !", "swapArenaTags": "Les effets affectant chaque côté du terrain\nont été échangés par {{pokemonName}} !",
"chillyReception": "{{pokemonName}} sapprête\nà faire un mauvais jeu de mots…",
"exposedMove": "{{targetPokemonName}} est identifié\npar {{pokemonName}} !", "exposedMove": "{{targetPokemonName}} est identifié\npar {{pokemonName}} !",
"safeguard": "{{targetName}} est protégé\npar la capacité Rune Protect !", "safeguard": "{{targetName}} est protégé\npar la capacité Rune Protect !",
"substituteOnOverlap": "{{pokemonName}} a déjà\nun clone !", "substituteOnOverlap": "{{pokemonName}} a déjà\nun clone !",

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "Roue Libre", "name": "Roue Libre",
"effect": "Inflige et change en type Ténèbres" "effect": "Le Pokémon libère lénergie stockée dans ses joues pour attaquer et augmenter sa Vitesse. Le type de cette capacité change en fonction de la forme du lanceur."
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "Abattage", "name": "Abattage",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "\tMalia Tossica", "name": "\tMalia Tossica",
"description": "I Pokémon avvelenati dalle mosse di Pecharunt entreranno anche in stato di confusione." "description": "I Pokémon avvelenati dalle mosse di questo Pokémon entreranno anche in stato di confusione."
} }
} }

View File

@ -66,6 +66,7 @@
"revivalBlessing": "{{pokemonName}} torna in forze!", "revivalBlessing": "{{pokemonName}} torna in forze!",
"swapArenaTags": "{{pokemonName}} ha invertito gli effetti attivi\nnelle due metà del campo!", "swapArenaTags": "{{pokemonName}} ha invertito gli effetti attivi\nnelle due metà del campo!",
"exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!", "exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!",
"chillyReception": "{{pokemonName}} sta per fare una battuta!",
"safeguard": "Salvaguardia protegge {{targetName}}!", "safeguard": "Salvaguardia protegge {{targetName}}!",
"afterYou": "{{pokemonName}} approfitta della cortesia!" "afterYou": "{{pokemonName}} approfitta della cortesia!"
} }

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "Ruota d'Aura", "name": "Ruota d'Aura",
"effect": "Il Pokémon emette l'energia accumulata nelle guance per attaccare e aumentare la Velocità. Il tipo della mossa cambia in base alla forma assunta da Morpeko." "effect": "Il Pokémon emette l'energia accumulata nelle guance per attaccare e aumentare la Velocità. Se usata da Morpeko, il tipo della mossa cambia in base alla forma assunta."
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "Vastoimpatto", "name": "Vastoimpatto",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "どくくぐつ", "name": "どくくぐつ",
"description": "モモワロウの 技によって どく状態に なった 相手は こんらん状態にも なってしまう。" "description": "このポケモンの 技によって どく状態に なった 相手は こんらん状態にも なってしまう。"
} }
} }

View File

@ -111,17 +111,17 @@
"forest": "ポケダン空 くろのもり", "forest": "ポケダン空 くろのもり",
"grass": "ポケダン空 リンゴのもり", "grass": "ポケダン空 リンゴのもり",
"graveyard": "ポケダン空 しんぴのもり", "graveyard": "ポケダン空 しんぴのもり",
"ice_cave": "ポケダン空 だいひょうざん", "ice_cave": "Firel - -50°C",
"island": "ポケダン空 えんがんのいわば", "island": "ポケダン空 えんがんのいわば",
"jungle": "Lmz - Jungle(ジャングル)", "jungle": "Lmz - Jungle(ジャングル)",
"laboratory": "Firel - Laboratory(ラボラトリー)", "laboratory": "Firel - Laboratory(ラボラトリー)",
"lake": "ポケダン空 すいしょうのどうくつ", "lake": "Lmz - Lake(湖)",
"meadow": "ポケダン空 そらのいただき(もり)", "meadow": "ポケダン空 そらのいただき(もり)",
"metropolis": "Firel - Metropolis(大都市)", "metropolis": "Firel - Metropolis(大都市)",
"mountain": "ポケダン空 ツノやま", "mountain": "ポケダン空 ツノやま",
"plains": "ポケダン空 そらのいただき(そうげん)", "plains": "Firel - Route 888(888ばんどうろ)",
"power_plant": "ポケダン空 エレキへいげん", "power_plant": "Firel - The Klink(ザ・ギアル)",
"ruins": "ポケダン空 ふういんのいわば", "ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - Marine Mystique(海の神秘性)", "sea": "Andr06 - Marine Mystique(海の神秘性)",
"seabed": "Firel - Seabed(海底)", "seabed": "Firel - Seabed(海底)",
"slum": "Andr06 - Sneaky Snom(ずるいユキハミ)", "slum": "Andr06 - Sneaky Snom(ずるいユキハミ)",
@ -131,7 +131,7 @@
"tall_grass": "ポケダン空 のうむのもり", "tall_grass": "ポケダン空 のうむのもり",
"temple": "ポケダン空 ばんにんのどうくつ", "temple": "ポケダン空 ばんにんのどうくつ",
"town": "ポケダン空 ランダムダンジョン3", "town": "ポケダン空 ランダムダンジョン3",
"volcano": "ポケダン空 ねっすいのどうくつ", "volcano": "Firel - Twisturn Volcano(曲がる折れる火山)",
"wasteland": "ポケダン空 まぼろしのだいち", "wasteland": "ポケダン空 まぼろしのだいち",
"encounter_ace_trainer": "BW 視線!エリートトレーナー", "encounter_ace_trainer": "BW 視線!エリートトレーナー",
"encounter_backpacker": "BW 視線!バックパッカー", "encounter_backpacker": "BW 視線!バックパッカー",

View File

@ -64,6 +64,8 @@
"copyType": "{{pokemonName}}は {{targetPokemonName}}と\n同じタイプに なった", "copyType": "{{pokemonName}}は {{targetPokemonName}}と\n同じタイプに なった",
"suppressAbilities": "{{pokemonName}}の 特性が 効かなくなった!", "suppressAbilities": "{{pokemonName}}の 特性が 効かなくなった!",
"revivalBlessing": "{{pokemonName}}は\n復活して 戦えるようになった", "revivalBlessing": "{{pokemonName}}は\n復活して 戦えるようになった",
"swapArenaTags": "{{pokemonName}}は\nお互いの 場の効果を 入れ替えた",
"chillyReception": "{{pokemonName}}は\n寒い ギャグを かました",
"swapArenaTags": "{{pokemonName}}は\nお互いの 場の 効果を 入れ替えた", "swapArenaTags": "{{pokemonName}}は\nお互いの 場の 効果を 入れ替えた",
"exposedMove": "{{pokemonName}}は {{targetPokemonName}}の\n正体を 見破った", "exposedMove": "{{pokemonName}}は {{targetPokemonName}}の\n正体を 見破った",
"afterYou": "{{pokemonName}}は\nお言葉に 甘えることにした" "afterYou": "{{pokemonName}}は\nお言葉に 甘えることにした"

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "オーラぐるま", "name": "オーラぐるま",
"effect": "ほほぶくろに 溜めた エネルギーで 攻撃し 自分の 素早さを あげる。 モルペコの 姿で タイプが 変わる。" "effect": "ほほぶくろに 溜めた エネルギーで 攻撃し 自分の 素早さを あげる。 モルペコが こ技を 使う場合 姿で 技の タイプが 変わる。"
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "ワイドブレイカー", "name": "ワイドブレイカー",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "독조종", "name": "독조종",
"description": "복숭악동의 기술에 의해 독 상태가 된 상대는 혼란 상태도 되어 버린다." "description": " 기술에 의해 독 상태가 된 상대는 혼란 상태도 되어 버린다."
} }
} }

View File

@ -110,17 +110,17 @@
"forest": "불가사의 던전 하늘의 탐험대 검은 숲", "forest": "불가사의 던전 하늘의 탐험대 검은 숲",
"grass": "불가사의 던전 하늘의 탐험대 사과의 숲", "grass": "불가사의 던전 하늘의 탐험대 사과의 숲",
"graveyard": "불가사의 던전 하늘의 탐험대 신비의 숲", "graveyard": "불가사의 던전 하늘의 탐험대 신비의 숲",
"ice_cave": "불가사의 던전 하늘의 탐험대 광대한 얼음산", "ice_cave": "Firel - -50°C",
"island": "불가사의 던전 하늘의 탐험대 연안의 암반", "island": "불가사의 던전 하늘의 탐험대 연안의 암반",
"jungle": "Lmz - Jungle", "jungle": "Lmz - 정글",
"laboratory": "Firel - Laboratory", "laboratory": "Firel - 연구소",
"lake": "불가사의 던전 하늘의 탐험대 수정 동굴", "lake": "Lmz - 호수",
"meadow": "불가사의 던전 하늘의 탐험대 하늘 꼭대기 숲", "meadow": "불가사의 던전 하늘의 탐험대 하늘 꼭대기 숲",
"metropolis": "Firel - Metropolis", "metropolis": "Firel - Metropolis",
"mountain": "불가사의 던전 하늘의 탐험대 뿔산", "mountain": "불가사의 던전 하늘의 탐험대 뿔산",
"plains": "불가사의 던전 하늘의 탐험대 하늘 꼭대기 초원", "plains": "Firel - Route 888",
"power_plant": "불가사의 던전 하늘의 탐험대 일렉트릭 평원", "power_plant": "Firel - 기어르",
"ruins": "불가사의 던전 하늘의 탐험대 봉인의 암반", "ruins": "Lmz - 고대 유적",
"sea": "Andr06 - Marine Mystique", "sea": "Andr06 - Marine Mystique",
"seabed": "Firel - Seabed", "seabed": "Firel - Seabed",
"slum": "Andr06 - Sneaky Snom", "slum": "Andr06 - Sneaky Snom",
@ -130,7 +130,7 @@
"tall_grass": "불가사의 던전 하늘의 탐험대 짙은 안개의 숲", "tall_grass": "불가사의 던전 하늘의 탐험대 짙은 안개의 숲",
"temple": "불가사의 던전 하늘의 탐험대 파수꾼의 동굴", "temple": "불가사의 던전 하늘의 탐험대 파수꾼의 동굴",
"town": "불가사의 던전 하늘의 탐험대 랜덤 던전 테마 3", "town": "불가사의 던전 하늘의 탐험대 랜덤 던전 테마 3",
"volcano": "불가사의 던전 하늘의 탐험대 열수의 동굴", "volcano": "Firel - Twisturn Volcano",
"wasteland": "불가사의 던전 하늘의 탐험대 환상의 대지", "wasteland": "불가사의 던전 하늘의 탐험대 환상의 대지",
"encounter_ace_trainer": "BW 눈이 마주치면 승부! (엘리트 트레이너)", "encounter_ace_trainer": "BW 눈이 마주치면 승부! (엘리트 트레이너)",
"encounter_backpacker": "BW 눈이 마주치면 승부! (등산가)", "encounter_backpacker": "BW 눈이 마주치면 승부! (등산가)",

View File

@ -66,6 +66,7 @@
"suppressAbilities": "{{pokemonName}}의\n특성이 효과를 발휘하지 못하게 되었다!", "suppressAbilities": "{{pokemonName}}의\n특성이 효과를 발휘하지 못하게 되었다!",
"revivalBlessing": "{{pokemonName}}[[는]]\n정신을 차려 싸울 수 있게 되었다!", "revivalBlessing": "{{pokemonName}}[[는]]\n정신을 차려 싸울 수 있게 되었다!",
"swapArenaTags": "{{pokemonName}}[[는]]\n서로의 필드 효과를 교체했다!", "swapArenaTags": "{{pokemonName}}[[는]]\n서로의 필드 효과를 교체했다!",
"chillyReception": "{{pokemonName}}[[는]] 썰렁한 개그를 선보였다!",
"exposedMove": "{{pokemonName}}[[는]]\n{{targetPokemonName}}의 정체를 꿰뚫어 보았다!", "exposedMove": "{{pokemonName}}[[는]]\n{{targetPokemonName}}의 정체를 꿰뚫어 보았다!",
"safeguard": "{{targetName}}[[는]] 신비의 베일이 지켜 주고 있다!", "safeguard": "{{targetName}}[[는]] 신비의 베일이 지켜 주고 있다!",
"afterYou": "{{pokemonName}}[[는]]\n배려를 받아들이기로 했다!" "afterYou": "{{pokemonName}}[[는]]\n배려를 받아들이기로 했다!"

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "오라휠", "name": "오라휠",
"effect": "볼주머니에 저장해둔 에너지로 공격하고 자신의 스피드를 올린다. 모르페코 모습에 따라 타입이 바뀐다." "effect": "볼주머니에 저장해둔 에너지로 공격하고 자신의 스피드를 올린다. 모르페코가 사용할 경우 모습에 따라 타입이 바뀐다."
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "와이드브레이커", "name": "와이드브레이커",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "Poison Puppeteer", "name": "Poison Puppeteer",
"description": "Pokémon envenenados pelos movimentos de Pecharunt também ficarão confusos." "description": "Pokémon envenenados pelos movimentos deste Pokémon também ficarão confusos."
} }
} }

View File

@ -111,17 +111,17 @@
"forest": "PMD EoS Dusk Forest", "forest": "PMD EoS Dusk Forest",
"grass": "PMD EoS Apple Woods", "grass": "PMD EoS Apple Woods",
"graveyard": "PMD EoS Mystifying Forest", "graveyard": "PMD EoS Mystifying Forest",
"ice_cave": "PMD EoS Vast Ice Mountain", "ice_cave": "Firel - -50°C",
"island": "PMD EoS Craggy Coast", "island": "PMD EoS Craggy Coast",
"jungle": "Lmz - Jungle", "jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory", "laboratory": "Firel - Laboratory",
"lake": "PMD EoS Crystal Cave", "lake": "Lmz - Lake",
"meadow": "PMD EoS Sky Peak Forest", "meadow": "PMD EoS Sky Peak Forest",
"metropolis": "Firel - Metropolis", "metropolis": "Firel - Metropolis",
"mountain": "PMD EoS Mt. Horn", "mountain": "PMD EoS Mt. Horn",
"plains": "PMD EoS Sky Peak Prairie", "plains": "Firel - Route 888",
"power_plant": "PMD EoS Far Amp Plains", "power_plant": "Firel - The Klink",
"ruins": "PMD EoS Deep Sealed Ruin", "ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - Marine Mystique", "sea": "Andr06 - Marine Mystique",
"seabed": "Firel - Seabed", "seabed": "Firel - Seabed",
"slum": "Andr06 - Sneaky Snom", "slum": "Andr06 - Sneaky Snom",
@ -131,7 +131,7 @@
"tall_grass": "PMD EoS Foggy Forest", "tall_grass": "PMD EoS Foggy Forest",
"temple": "PMD EoS Aegis Cave", "temple": "PMD EoS Aegis Cave",
"town": "PMD EoS Random Dungeon Theme 3", "town": "PMD EoS Random Dungeon Theme 3",
"volcano": "PMD EoS Steam Cave", "volcano": "Firel - Twisturn Volcano",
"wasteland": "PMD EoS Hidden Highland", "wasteland": "PMD EoS Hidden Highland",
"encounter_ace_trainer": "BW Encontro com Treinador (Treinador Ás)", "encounter_ace_trainer": "BW Encontro com Treinador (Treinador Ás)",
"encounter_backpacker": "BW Encontro com Treinador (Mochileiro)", "encounter_backpacker": "BW Encontro com Treinador (Mochileiro)",

View File

@ -61,6 +61,7 @@
"suppressAbilities": "A habilidade de {{pokemonName}}\nfoi suprimida!", "suppressAbilities": "A habilidade de {{pokemonName}}\nfoi suprimida!",
"revivalBlessing": "{{pokemonName}} foi reanimado!", "revivalBlessing": "{{pokemonName}} foi reanimado!",
"swapArenaTags": "{{pokemonName}} trocou os efeitos de batalha que afetam cada lado do campo!", "swapArenaTags": "{{pokemonName}} trocou os efeitos de batalha que afetam cada lado do campo!",
"chillyReception": "{{pokemonName}} está prestes a contar uma piada gelada!",
"exposedMove": "{{pokemonName}} identificou\n{{targetPokemonName}}!", "exposedMove": "{{pokemonName}} identificou\n{{targetPokemonName}}!",
"safeguard": "{{targetName}} está protegido por Safeguard!", "safeguard": "{{targetName}} está protegido por Safeguard!",
"afterYou": "{{pokemonName}} aceitou a gentil oferta!" "afterYou": "{{pokemonName}} aceitou a gentil oferta!"

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "Aura Wheel", "name": "Aura Wheel",
"effect": "Morpeko ataca e aumenta sua Velocidade com a energia armazenada em suas bochechas. O tipo deste movimento muda dependendo da forma do usuário." "effect": "O usuário ataca e aumenta sua Velocidade com a energia armazenada em suas bochechas. Se usado por Morpeko, o tipo deste movimento muda dependendo da forma do usuário."
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "Breaking Swipe", "name": "Breaking Swipe",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "毒傀儡", "name": "毒傀儡",
"description": "因桃歹郎的招式而陷入中毒状态的\n对手同时也会陷入混乱状态。" "description": "因此宝可梦的招式而陷入中毒状态的对手\n同时也会陷入混乱状态。"
} }
} }

View File

@ -109,17 +109,17 @@
"forest": "空之探险队「黑暗森林」", "forest": "空之探险队「黑暗森林」",
"grass": "空之探险队「苹果森林」", "grass": "空之探险队「苹果森林」",
"graveyard": "空之探险队「神秘森林」", "graveyard": "空之探险队「神秘森林」",
"ice_cave": "空之探险队「大冰山」", "ice_cave": "Firel - -50°C",
"island": "空之探险队「沿岸岩地」", "island": "空之探险队「沿岸岩地」",
"jungle": "Lmz - 丛林", "jungle": "Lmz - 丛林",
"laboratory": "Firel - 研究所", "laboratory": "Firel - 研究所",
"lake": "空之探险队「水晶洞窟」", "lake": "Lmz - Lake",
"meadow": "空之探险队「天空顶端(森林)」", "meadow": "空之探险队「天空顶端(森林)」",
"metropolis": "Firel - 城市", "metropolis": "Firel - 城市",
"mountain": "空之探险队「角山」", "mountain": "空之探险队「角山」",
"plains": "空之探险队「天空顶端(草原)」", "plains": "Firel - Route 888",
"power_plant": "空之探险队「电气平原 深处」", "power_plant": "Firel - The Klink",
"ruins": "空之探险队「封印岩地 深处」", "ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - 海洋之秘", "sea": "Andr06 - 海洋之秘",
"seabed": "Firel - 海底", "seabed": "Firel - 海底",
"slum": "Andr06 - 狡猾的雪吞虫", "slum": "Andr06 - 狡猾的雪吞虫",
@ -129,7 +129,7 @@
"tall_grass": "空之探险队「浓雾森林」", "tall_grass": "空之探险队「浓雾森林」",
"temple": "空之探险队「守护洞穴」", "temple": "空之探险队「守护洞穴」",
"town": "空之探险队「随机迷宫3」", "town": "空之探险队「随机迷宫3」",
"volcano": "空之探险队「热水洞窟」", "volcano": "Firel - Twisturn Volcano",
"wasteland": "空之探险队「梦幻高原」", "wasteland": "空之探险队「梦幻高原」",
"encounter_ace_trainer": "黑白 「视线!精英训练师」", "encounter_ace_trainer": "黑白 「视线!精英训练师」",
"encounter_backpacker": "黑白 「视线!背包客」", "encounter_backpacker": "黑白 「视线!背包客」",

View File

@ -65,6 +65,7 @@
"suppressAbilities": "{{pokemonName}}的特性\n变得无效了", "suppressAbilities": "{{pokemonName}}的特性\n变得无效了",
"revivalBlessing": "{{pokemonName}}复活了!", "revivalBlessing": "{{pokemonName}}复活了!",
"swapArenaTags": "{{pokemonName}}\n交换了双方的场地效果", "swapArenaTags": "{{pokemonName}}\n交换了双方的场地效果",
"chillyReception": "{{pokemonName}}\n说出了冷笑话",
"exposedMove": "{{pokemonName}}识破了\n{{targetPokemonName}}的原型!", "exposedMove": "{{pokemonName}}识破了\n{{targetPokemonName}}的原型!",
"safeguard": "{{targetName}}\n正受到神秘之幕的保护", "safeguard": "{{targetName}}\n正受到神秘之幕的保护",
"afterYou": "{{pokemonName}}\n接受了对手的好意" "afterYou": "{{pokemonName}}\n接受了对手的好意"

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "气场轮", "name": "气场轮",
"effect": "用储存在颊囊里的能量进行攻击,\n并提高自己的速度。其属性会随着\n莫鲁贝可的样子而改变" "effect": "用储存在颊囊里的能量进行攻击,\n并提高自己的速度。如果由莫鲁贝可使用,\n其属性会随着它的样子而改变"
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "广域破坏", "name": "广域破坏",

View File

@ -1237,6 +1237,6 @@
}, },
"poisonPuppeteer": { "poisonPuppeteer": {
"name": "毒傀儡", "name": "毒傀儡",
"description": "因為桃歹郎的招式而陷入中\n毒狀態的對手同時也會陷入\n混亂狀態。" "description": "因為此寶可夢的招式而陷入中毒狀態的對手\n同時也會陷入混亂狀態。"
} }
} }

View File

@ -100,17 +100,17 @@
"forest": "空之探險隊「黑暗森林」", "forest": "空之探險隊「黑暗森林」",
"grass": "空之探險隊「蘋果森林」", "grass": "空之探險隊「蘋果森林」",
"graveyard": "空之探險隊「神秘森林」", "graveyard": "空之探險隊「神秘森林」",
"ice_cave": "空之探險隊「大冰山」", "ice_cave": "Firel - -50°C",
"island": "空之探險隊「沿岸岩地」", "island": "空之探險隊「沿岸岩地」",
"jungle": "Lmz - 叢林", "jungle": "Lmz - 叢林",
"laboratory": "Firel - 研究所", "laboratory": "Firel - 研究所",
"lake": "空之探險隊「水晶洞窟」", "lake": "Lmz - Lake",
"meadow": "空之探險隊「天空頂端(森林)」", "meadow": "空之探險隊「天空頂端(森林)」",
"metropolis": "Firel - 城市", "metropolis": "Firel - 城市",
"mountain": "空之探險隊「角山」", "mountain": "空之探險隊「角山」",
"plains": "空之探險隊「天空頂端(草原)」", "plains": "Firel - Route 888",
"power_plant": "空之探險隊「電氣平原 深處」", "power_plant": "Firel - The Klink",
"ruins": "空之探險隊「封印岩地 深處」", "ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - 海洋之秘", "sea": "Andr06 - 海洋之秘",
"seabed": "Firel - 海底", "seabed": "Firel - 海底",
"slum": "Andr06 - 狡猾的雪吞蟲", "slum": "Andr06 - 狡猾的雪吞蟲",
@ -120,7 +120,7 @@
"tall_grass": "空之探險隊「濃霧森林」", "tall_grass": "空之探險隊「濃霧森林」",
"temple": "空之探險隊「守護洞穴」", "temple": "空之探險隊「守護洞穴」",
"town": "空之探險隊「隨機迷宮3」", "town": "空之探險隊「隨機迷宮3」",
"volcano": "空之探險隊「熱水洞窟」", "volcano": "Firel - Twisturn Volcano",
"wasteland": "空之探險隊「夢幻高原」", "wasteland": "空之探險隊「夢幻高原」",
"encounter_ace_trainer": "黑白 「視線!精英訓練師」", "encounter_ace_trainer": "黑白 「視線!精英訓練師」",

View File

@ -65,6 +65,7 @@
"suppressAbilities": "{{pokemonName}}的特性\n變得無效了", "suppressAbilities": "{{pokemonName}}的特性\n變得無效了",
"revivalBlessing": "{{pokemonName}}復活了!", "revivalBlessing": "{{pokemonName}}復活了!",
"swapArenaTags": "{{pokemonName}}\n交換了雙方的場地效果", "swapArenaTags": "{{pokemonName}}\n交換了雙方的場地效果",
"chillyReception": "{{pokemonName}}\n說了冷笑話",
"exposedMove": "{{pokemonName}}識破了\n{{targetPokemonName}}的原形!", "exposedMove": "{{pokemonName}}識破了\n{{targetPokemonName}}的原形!",
"safeguard": "{{targetName}}\n正受到神秘之幕的保護", "safeguard": "{{targetName}}\n正受到神秘之幕的保護",
"afterYou": "{{pokemonName}}\n接受了對手的好意" "afterYou": "{{pokemonName}}\n接受了對手的好意"

View File

@ -3129,7 +3129,7 @@
}, },
"auraWheel": { "auraWheel": {
"name": "氣場輪", "name": "氣場輪",
"effect": "用儲存在頰囊裏的能量進行\n攻擊並提高自己的速度。\n其屬性會隨着莫魯貝可的樣\n子而改變" "effect": "用儲存在頰囊裏的能量進行\n攻擊並提高自己的速度。\n如果由莫魯貝可使用,\n其屬性會隨着它的樣子而改變"
}, },
"breakingSwipe": { "breakingSwipe": {
"name": "廣域破壞", "name": "廣域破壞",

View File

@ -1288,6 +1288,21 @@ function skipInClassicAfterWave(wave: integer, defaultWeight: integer): Weighted
function skipInLastClassicWaveOrDefault(defaultWeight: integer) : WeightedModifierTypeWeightFunc { function skipInLastClassicWaveOrDefault(defaultWeight: integer) : WeightedModifierTypeWeightFunc {
return skipInClassicAfterWave(199, defaultWeight); return skipInClassicAfterWave(199, defaultWeight);
} }
/**
* High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199
* or if the lure still has over 60% of its duration left
* @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max
* @param weight The desired weight for the lure when it does spawn
* @returns A WeightedModifierTypeWeightFunc
*/
function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc {
return (party: Pokemon[]) => {
const lures = party[0].scene.getModifiers(Modifiers.DoubleBattleChanceBoosterModifier);
return !(party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex === 199) && (lures.length === 0 || lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) ? weight : 0;
};
}
class WeightedModifierType { class WeightedModifierType {
public modifierType: ModifierType; public modifierType: ModifierType;
public weight: integer | WeightedModifierTypeWeightFunc; public weight: integer | WeightedModifierTypeWeightFunc;
@ -1611,7 +1626,7 @@ const modifierPool: ModifierPool = {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed >= Math.floor(m.getMovePp() / 2)).length).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed >= Math.floor(m.getMovePp() / 2)).length).length, 3);
return thresholdPartyMemberCount; return thresholdPartyMemberCount;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.LURE, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)),
new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4),
new WeightedModifierType(modifierTypes.BERRY, 2), new WeightedModifierType(modifierTypes.BERRY, 2),
new WeightedModifierType(modifierTypes.TM_COMMON, 2), new WeightedModifierType(modifierTypes.TM_COMMON, 2),
@ -1668,7 +1683,7 @@ const modifierPool: ModifierPool = {
return thresholdPartyMemberCount; return thresholdPartyMemberCount;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.DIRE_HIT, 4), new WeightedModifierType(modifierTypes.DIRE_HIT, 4),
new WeightedModifierType(modifierTypes.SUPER_LURE, skipInLastClassicWaveOrDefault(4)), new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)),
new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)),
new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => {
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8); return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8);
@ -1691,7 +1706,7 @@ const modifierPool: ModifierPool = {
}), }),
[ModifierTier.ULTRA]: [ [ModifierTier.ULTRA]: [
new WeightedModifierType(modifierTypes.ULTRA_BALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.ULTRA_BALL)) ? 0 : 15, 15), new WeightedModifierType(modifierTypes.ULTRA_BALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.ULTRA_BALL)) ? 0 : 15, 15),
new WeightedModifierType(modifierTypes.MAX_LURE, skipInLastClassicWaveOrDefault(4)), new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)),
new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)),
new WeightedModifierType(modifierTypes.PP_MAX, 3), new WeightedModifierType(modifierTypes.PP_MAX, 3),
new WeightedModifierType(modifierTypes.MINT, 4), new WeightedModifierType(modifierTypes.MINT, 4),

View File

@ -9,6 +9,7 @@ import PartyUiHandler from "../ui/party-ui-handler";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import { EndEvolutionPhase } from "./end-evolution-phase"; import { EndEvolutionPhase } from "./end-evolution-phase";
import { EvolutionPhase } from "./evolution-phase"; import { EvolutionPhase } from "./evolution-phase";
import { BattlerTagType } from "#app/enums/battler-tag-type";
export class FormChangePhase extends EvolutionPhase { export class FormChangePhase extends EvolutionPhase {
private formChange: SpeciesFormChange; private formChange: SpeciesFormChange;
@ -157,6 +158,7 @@ export class FormChangePhase extends EvolutionPhase {
} }
end(): void { end(): void {
this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED);
if (this.modal) { if (this.modal) {
this.scene.ui.revertMode().then(() => { this.scene.ui.revertMode().then(() => {
if (this.scene.ui.getMode() === Mode.PARTY) { if (this.scene.ui.getMode() === Mode.PARTY) {

View File

@ -74,7 +74,7 @@ export class MovePhase extends BattlePhase {
if (!this.followUp) { if (!this.followUp) {
if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) { if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) {
this.scene.arena.setIgnoreAbilities(); this.scene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex());
} }
} else { } else {
this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct? this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct?

View File

@ -9,24 +9,24 @@ import { PokemonPhase } from "./pokemon-phase";
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
export class ObtainStatusEffectPhase extends PokemonPhase { export class ObtainStatusEffectPhase extends PokemonPhase {
private statusEffect: StatusEffect | undefined; private statusEffect?: StatusEffect | undefined;
private cureTurn: integer | null; private cureTurn?: integer | null;
private sourceText: string | null; private sourceText?: string | null;
private sourcePokemon: Pokemon | null; private sourcePokemon?: Pokemon | null;
constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string, sourcePokemon?: Pokemon) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string | null, sourcePokemon?: Pokemon | null) {
super(scene, battlerIndex); super(scene, battlerIndex);
this.statusEffect = statusEffect; this.statusEffect = statusEffect;
this.cureTurn = cureTurn!; // TODO: is this bang correct? this.cureTurn = cureTurn;
this.sourceText = sourceText!; // TODO: is this bang correct? this.sourceText = sourceText;
this.sourcePokemon = sourcePokemon!; // For tracking which Pokemon caused the status effect // TODO: is this bang correct? this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect
} }
start() { start() {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (!pokemon?.status) { if (pokemon && !pokemon.status) {
if (pokemon?.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
if (this.cureTurn) { if (this.cureTurn) {
pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct?
} }
@ -40,8 +40,8 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
}); });
return; return;
} }
} else if (pokemon.status.effect === this.statusEffect) { } else if (pokemon.status?.effect === this.statusEffect) {
this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon))); this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)));
} }
this.end(); this.end();
} }

View File

@ -3,6 +3,7 @@ import { SemiInvulnerableTag } from "#app/data/battler-tags";
import { SpeciesFormChange, getSpeciesFormChangeMessage } from "#app/data/pokemon-forms"; import { SpeciesFormChange, getSpeciesFormChangeMessage } from "#app/data/pokemon-forms";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { BattleSpec } from "#app/enums/battle-spec"; import { BattleSpec } from "#app/enums/battle-spec";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import Pokemon, { EnemyPokemon } from "#app/field/pokemon"; import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
@ -113,6 +114,7 @@ export class QuietFormChangePhase extends BattlePhase {
} }
end(): void { end(): void {
this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED);
if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) {
this.scene.playBgm(); this.scene.playBgm();
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true)); this.scene.unshiftPhase(new PokemonHealPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true));

View File

@ -0,0 +1,109 @@
import { StatusEffect } from "#app/data/status-effect";
import GameManager from "#app/test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Synchronize", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.startingLevel(100)
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.SYNCHRONIZE)
.moveset([Moves.SPLASH, Moves.THUNDER_WAVE, Moves.SPORE, Moves.PSYCHO_SHIFT])
.ability(Abilities.NO_GUARD);
}, 20000);
it("does not trigger when no status is applied by opponent Pokemon", async () => {
await game.classicMode.startBattle([Species.FEEBAS]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getParty()[0].status).toBeUndefined();
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
}, 20000);
it("sets the status of the source pokemon to Paralysis when paralyzed by it", async () => {
await game.classicMode.startBattle([Species.FEEBAS]);
game.move.select(Moves.THUNDER_WAVE);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
}, 20000);
it("does not trigger on Sleep", async () => {
await game.classicMode.startBattle();
game.move.select(Moves.SPORE);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getParty()[0].status?.effect).toBeUndefined();
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.SLEEP);
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
}, 20000);
it("does not trigger when Pokemon is statused by Toxic Spikes", async () => {
game.override
.ability(Abilities.SYNCHRONIZE)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Array(4).fill(Moves.TOXIC_SPIKES));
await game.classicMode.startBattle([Species.FEEBAS, Species.MILOTIC]);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.POISON);
expect(game.scene.getEnemyParty()[0].status?.effect).toBeUndefined();
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
}, 20000);
it("shows ability even if it fails to set the status of the opponent Pokemon", async () => {
await game.classicMode.startBattle([Species.PIKACHU]);
game.move.select(Moves.THUNDER_WAVE);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getParty()[0].status?.effect).toBeUndefined();
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
}, 20000);
it("should activate with Psycho Shift after the move clears the status", async () => {
game.override.statusEffect(StatusEffect.PARALYSIS);
await game.classicMode.startBattle();
game.move.select(Moves.PSYCHO_SHIFT);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS); // keeping old gen < V impl for now since it's buggy otherwise
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
}, 20000);
});

View File

@ -0,0 +1,98 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
describe("Moves - Autotomize", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const TIMEOUT = 20 * 1000;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([Moves.AUTOTOMIZE, Moves.KINGS_SHIELD, Moves.FALSE_SWIPE])
.battleType("single")
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("Autotomize should reduce weight", async () => {
const baseDracozoltWeight = 190;
const oneAutotomizeDracozoltWeight = 90;
const twoAutotomizeDracozoltWeight = 0.1;
const threeAutotomizeDracozoltWeight = 0.1;
await game.classicMode.startBattle([Species.DRACOZOLT]);
const playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getWeight()).toBe(baseDracozoltWeight);
game.move.select(Moves.AUTOTOMIZE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(oneAutotomizeDracozoltWeight);
game.move.select(Moves.AUTOTOMIZE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(twoAutotomizeDracozoltWeight);
game.move.select(Moves.AUTOTOMIZE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(threeAutotomizeDracozoltWeight);
}, TIMEOUT);
it("Changing forms should revert weight", async () => {
const baseAegislashWeight = 53;
const autotomizeAegislashWeight = 0.1;
await game.classicMode.startBattle([Species.AEGISLASH]);
const playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getWeight()).toBe(baseAegislashWeight);
game.move.select(Moves.AUTOTOMIZE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(autotomizeAegislashWeight);
// Transform to sword form
game.move.select(Moves.FALSE_SWIPE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(baseAegislashWeight);
game.move.select(Moves.AUTOTOMIZE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(autotomizeAegislashWeight);
// Transform to shield form
game.move.select(Moves.KINGS_SHIELD);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(baseAegislashWeight);
game.move.select(Moves.AUTOTOMIZE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(autotomizeAegislashWeight);
}, TIMEOUT);
it("Autotomize should interact with light metal correctly", async () => {
const baseLightGroudonWeight = 475;
const autotomizeLightGroudonWeight = 425;
game.override.ability(Abilities.LIGHT_METAL);
await game.classicMode.startBattle([Species.GROUDON]);
const playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getWeight()).toBe(baseLightGroudonWeight);
game.move.select(Moves.AUTOTOMIZE);
await game.toNextTurn();
expect(playerPokemon.getWeight()).toBe(autotomizeLightGroudonWeight);
}, TIMEOUT);
});

View File

@ -0,0 +1,71 @@
import { Abilities } from "#app/enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { WeatherType } from "#enums/weather-type";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
const TIMEOUT = 20 * 1000;
describe("Moves - Chilly Reception", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleType("single")
.moveset([Moves.CHILLY_RECEPTION, Moves.SNOWSCAPE])
.enemyMoveset(Array(4).fill(Moves.SPLASH))
.enemyAbility(Abilities.NONE)
.ability(Abilities.NONE);
});
it("should still change the weather if user can't switch out", async () => {
await game.classicMode.startBattle([Species.SLOWKING]);
game.move.select(Moves.CHILLY_RECEPTION);
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
}, TIMEOUT);
it("should switch out even if it's snowing", async () => {
await game.classicMode.startBattle([Species.SLOWKING, Species.MEOWTH]);
// first turn set up snow with snowscape, try chilly reception on second turn
game.move.select(Moves.SNOWSCAPE);
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
await game.phaseInterceptor.to("TurnInitPhase", false);
game.move.select(Moves.CHILLY_RECEPTION);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH);
}, TIMEOUT);
it("happy case - switch out and weather changes", async () => {
await game.classicMode.startBattle([Species.SLOWKING, Species.MEOWTH]);
game.move.select(Moves.CHILLY_RECEPTION);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to("BerryPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH);
}, TIMEOUT);
});

View File

@ -1,16 +1,11 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { allMoves } from "#app/data/move"; import { allMoves } from "#app/data/move";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { BerryPhase } from "#app/phases/berry-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
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";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import GameManager from "../utils/gameManager";
describe("Moves - Dragon Tail", () => { describe("Moves - Dragon Tail", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -29,7 +24,7 @@ describe("Moves - Dragon Tail", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleType("single") game.override.battleType("single")
.moveset([Moves.DRAGON_TAIL, Moves.SPLASH]) .moveset([Moves.DRAGON_TAIL, Moves.SPLASH, Moves.FLAMETHROWER])
.enemySpecies(Species.WAILORD) .enemySpecies(Species.WAILORD)
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
.startingLevel(5) .startingLevel(5)
@ -38,53 +33,45 @@ describe("Moves - Dragon Tail", () => {
vi.spyOn(allMoves[Moves.DRAGON_TAIL], "accuracy", "get").mockReturnValue(100); vi.spyOn(allMoves[Moves.DRAGON_TAIL], "accuracy", "get").mockReturnValue(100);
}); });
test( it("should cause opponent to flee, and not crash", async () => {
"Single battle should cause opponent to flee, and not crash", await game.classicMode.startBattle([Species.DRATINI]);
async () => {
await game.startBattle([Species.DRATINI]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.DRAGON_TAIL); game.move.select(Moves.DRAGON_TAIL);
await game.phaseInterceptor.to(BerryPhase); await game.phaseInterceptor.to("BerryPhase");
const isVisible = enemyPokemon.visible; const isVisible = enemyPokemon.visible;
const hasFled = enemyPokemon.switchOutStatus; const hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true); expect(!isVisible && hasFled).toBe(true);
// simply want to test that the game makes it this far without crashing // simply want to test that the game makes it this far without crashing
await game.phaseInterceptor.to(BattleEndPhase); await game.phaseInterceptor.to("BattleEndPhase");
} });
);
test( it("should cause opponent to flee, display ability, and not crash", async () => {
"Single battle should cause opponent to flee, display ability, and not crash",
async () => {
game.override.enemyAbility(Abilities.ROUGH_SKIN); game.override.enemyAbility(Abilities.ROUGH_SKIN);
await game.startBattle([Species.DRATINI]); await game.classicMode.startBattle([Species.DRATINI]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.DRAGON_TAIL); game.move.select(Moves.DRAGON_TAIL);
await game.phaseInterceptor.to(BerryPhase); await game.phaseInterceptor.to("BerryPhase");
const isVisible = enemyPokemon.visible; const isVisible = enemyPokemon.visible;
const hasFled = enemyPokemon.switchOutStatus; const hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true); expect(!isVisible && hasFled).toBe(true);
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
} });
);
test( it("should proceed without crashing in a double battle", async () => {
"Double battles should proceed without crashing", game.override
async () => { .battleType("double").enemyMoveset(Moves.SPLASH)
game.override.battleType("double").enemyMoveset(Moves.SPLASH);
game.override.moveset([Moves.DRAGON_TAIL, Moves.SPLASH, Moves.FLAMETHROWER])
.enemyAbility(Abilities.ROUGH_SKIN); .enemyAbility(Abilities.ROUGH_SKIN);
await game.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]); await game.classicMode.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]);
const leadPokemon = game.scene.getParty()[0]!; const leadPokemon = game.scene.getParty()[0]!;
@ -94,7 +81,7 @@ describe("Moves - Dragon Tail", () => {
game.move.select(Moves.DRAGON_TAIL, 0, BattlerIndex.ENEMY); game.move.select(Moves.DRAGON_TAIL, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SPLASH, 1); game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to("TurnEndPhase");
const isVisibleLead = enemyLeadPokemon.visible; const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus; const hasFledLead = enemyLeadPokemon.switchOutStatus;
@ -107,18 +94,16 @@ describe("Moves - Dragon Tail", () => {
game.move.select(Moves.FLAMETHROWER, 0, BattlerIndex.ENEMY_2); game.move.select(Moves.FLAMETHROWER, 0, BattlerIndex.ENEMY_2);
game.move.select(Moves.SPLASH, 1); game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(BerryPhase); await game.phaseInterceptor.to("BerryPhase");
expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp()); expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp());
} });
);
test( it("should redirect targets upon opponent flee", async () => {
"Flee move redirection works", game.override
async () => { .battleType("double")
game.override.battleType("double").enemyMoveset(Moves.SPLASH); .enemyMoveset(Moves.SPLASH)
game.override.moveset([Moves.DRAGON_TAIL, Moves.SPLASH, Moves.FLAMETHROWER]); .enemyAbility(Abilities.ROUGH_SKIN);
game.override.enemyAbility(Abilities.ROUGH_SKIN); await game.classicMode.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]);
await game.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]);
const leadPokemon = game.scene.getParty()[0]!; const leadPokemon = game.scene.getParty()[0]!;
const secPokemon = game.scene.getParty()[1]!; const secPokemon = game.scene.getParty()[1]!;
@ -130,7 +115,7 @@ describe("Moves - Dragon Tail", () => {
// target the same pokemon, second move should be redirected after first flees // target the same pokemon, second move should be redirected after first flees
game.move.select(Moves.DRAGON_TAIL, 1, BattlerIndex.ENEMY); game.move.select(Moves.DRAGON_TAIL, 1, BattlerIndex.ENEMY);
await game.phaseInterceptor.to(BerryPhase); await game.phaseInterceptor.to("BerryPhase");
const isVisibleLead = enemyLeadPokemon.visible; const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus; const hasFledLead = enemyLeadPokemon.switchOutStatus;
@ -141,6 +126,17 @@ describe("Moves - Dragon Tail", () => {
expect(secPokemon.hp).toBeLessThan(secPokemon.getMaxHp()); expect(secPokemon.hp).toBeLessThan(secPokemon.getMaxHp());
expect(enemyLeadPokemon.hp).toBeLessThan(enemyLeadPokemon.getMaxHp()); expect(enemyLeadPokemon.hp).toBeLessThan(enemyLeadPokemon.getMaxHp());
expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp()); expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp());
} });
);
it("doesn't switch out if the target has suction cups", async () => {
game.override.enemyAbility(Abilities.SUCTION_CUPS);
await game.classicMode.startBattle([Species.REGIELEKI]);
const enemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.DRAGON_TAIL);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy.isFullHp()).toBe(false);
});
}); });

View File

@ -5,11 +5,14 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Shell Side Arm", () => { describe("Moves - Shell Side Arm", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
@ -34,14 +37,11 @@ describe("Moves - Shell Side Arm", () => {
it("becomes a physical attack if forecasted to deal more damage as physical", async () => { it("becomes a physical attack if forecasted to deal more damage as physical", async () => {
game.override.enemySpecies(Species.SNORLAX); game.override.enemySpecies(Species.SNORLAX);
await game.classicMode.startBattle([Species.MANAPHY]); await game.classicMode.startBattle([Species.RAMPARDOS]);
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
vi.spyOn(shellSideArmAttr, "apply"); vi.spyOn(shellSideArmAttr, "apply");
game.move.select(Moves.SHELL_SIDE_ARM); game.move.select(Moves.SHELL_SIDE_ARM);
await game.phaseInterceptor.to("MoveEffectPhase"); await game.phaseInterceptor.to("MoveEffectPhase");
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(true); expect(shellSideArmAttr.apply).toHaveLastReturnedWith(true);
@ -50,14 +50,11 @@ describe("Moves - Shell Side Arm", () => {
it("remains a special attack if forecasted to deal more damage as special", async () => { it("remains a special attack if forecasted to deal more damage as special", async () => {
game.override.enemySpecies(Species.SLOWBRO); game.override.enemySpecies(Species.SLOWBRO);
await game.classicMode.startBattle([Species.MANAPHY]); await game.classicMode.startBattle([Species.XURKITREE]);
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
vi.spyOn(shellSideArmAttr, "apply"); vi.spyOn(shellSideArmAttr, "apply");
game.move.select(Moves.SHELL_SIDE_ARM); game.move.select(Moves.SHELL_SIDE_ARM);
await game.phaseInterceptor.to("MoveEffectPhase"); await game.phaseInterceptor.to("MoveEffectPhase");
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false); expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);
@ -70,14 +67,10 @@ describe("Moves - Shell Side Arm", () => {
await game.classicMode.startBattle([Species.MANAPHY]); await game.classicMode.startBattle([Species.MANAPHY]);
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
vi.spyOn(shellSideArmAttr, "apply"); vi.spyOn(shellSideArmAttr, "apply");
game.move.select(Moves.SHELL_SIDE_ARM); game.move.select(Moves.SHELL_SIDE_ARM);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase", false); await game.phaseInterceptor.to("BerryPhase", false);
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false); expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);

View File

@ -42,7 +42,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
.startingWave(defaultWave) .startingWave(defaultWave)
.startingBiome(defaultBiome) .startingBiome(defaultBiome)
.disableTrainerWaves() .disableTrainerWaves()
.enemyPassiveAbility(Abilities.BALL_FETCH); .enemyPassiveAbility(Abilities.BALL_FETCH)
.enemyAbility(Abilities.BALL_FETCH);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([ new Map<Biome, MysteryEncounterType[]>([

View File

@ -28,7 +28,7 @@ export default class GameChallengesUiHandler extends UiHandler {
private descriptionText: BBCodeText; private descriptionText: BBCodeText;
private challengeLabels: Array<{ label: Phaser.GameObjects.Text, value: Phaser.GameObjects.Text }>; private challengeLabels: Array<{ label: Phaser.GameObjects.Text, value: Phaser.GameObjects.Text, leftArrow: Phaser.GameObjects.Image, rightArrow: Phaser.GameObjects.Image }>;
private monoTypeValue: Phaser.GameObjects.Sprite; private monoTypeValue: Phaser.GameObjects.Sprite;
private cursorObj: Phaser.GameObjects.NineSlice | null; private cursorObj: Phaser.GameObjects.NineSlice | null;
@ -40,6 +40,11 @@ export default class GameChallengesUiHandler extends UiHandler {
private optionsWidth: number; private optionsWidth: number;
private widestTextBox: number;
private readonly leftArrowGap: number = 90; // distance from the label to the left arrow
private readonly arrowSpacing: number = 3; // distance between the arrows and the value area
constructor(scene: BattleScene, mode: Mode | null = null) { constructor(scene: BattleScene, mode: Mode | null = null) {
super(scene, mode); super(scene, mode);
} }
@ -47,6 +52,8 @@ export default class GameChallengesUiHandler extends UiHandler {
setup() { setup() {
const ui = this.getUi(); const ui = this.getUi();
this.widestTextBox = 0;
this.challengesContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); this.challengesContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1);
this.challengesContainer.setName("challenges"); this.challengesContainer.setName("challenges");
@ -135,6 +142,20 @@ export default class GameChallengesUiHandler extends UiHandler {
this.valuesContainer.add(label); this.valuesContainer.add(label);
const leftArrow = this.scene.add.image(0, 0, "cursor_reverse");
leftArrow.setName(`challenge-left-arrow-${i}`);
leftArrow.setOrigin(0, 0);
leftArrow.setVisible(false);
leftArrow.setScale(0.75);
this.valuesContainer.add(leftArrow);
const rightArrow = this.scene.add.image(0, 0, "cursor");
rightArrow.setName(`challenge-right-arrow-${i}`);
rightArrow.setOrigin(0, 0);
rightArrow.setScale(0.75);
rightArrow.setVisible(false);
this.valuesContainer.add(rightArrow);
const value = addTextObject(this.scene, 0, 28 + i * 16, "", TextStyle.SETTINGS_LABEL); const value = addTextObject(this.scene, 0, 28 + i * 16, "", TextStyle.SETTINGS_LABEL);
value.setName(`challenge-value-text-${i}`); value.setName(`challenge-value-text-${i}`);
value.setPositionRelative(label, 100, 0); value.setPositionRelative(label, 100, 0);
@ -142,7 +163,9 @@ export default class GameChallengesUiHandler extends UiHandler {
this.challengeLabels[i] = { this.challengeLabels[i] = {
label: label, label: label,
value: value value: value,
leftArrow: leftArrow,
rightArrow: rightArrow
}; };
} }
@ -187,10 +210,26 @@ export default class GameChallengesUiHandler extends UiHandler {
*/ */
initLabels(): void { initLabels(): void {
this.setDescription(this.scene.gameMode.challenges[0].getDescription()); this.setDescription(this.scene.gameMode.challenges[0].getDescription());
this.widestTextBox = 0;
for (let i = 0; i < 9; i++) { for (let i = 0; i < 9; i++) {
if (i < this.scene.gameMode.challenges.length) { if (i < this.scene.gameMode.challenges.length) {
this.challengeLabels[i].label.setVisible(true); this.challengeLabels[i].label.setVisible(true);
this.challengeLabels[i].value.setVisible(true); this.challengeLabels[i].value.setVisible(true);
this.challengeLabels[i].leftArrow.setVisible(true);
this.challengeLabels[i].rightArrow.setVisible(true);
const tempText = addTextObject(this.scene, 0, 0, "", TextStyle.SETTINGS_LABEL); // this is added here to get the widest text object for this language, which will be used for the arrow placement
for (let j = 0; j <= this.scene.gameMode.challenges[i].maxValue; j++) { // this goes through each challenge's value to find out what the max width will be
if (this.scene.gameMode.challenges[i].id !== Challenges.SINGLE_TYPE) {
tempText.setText(this.scene.gameMode.challenges[i].getValue(j));
if (tempText.displayWidth > this.widestTextBox) {
this.widestTextBox = tempText.displayWidth;
}
}
}
tempText.destroy();
} }
} }
} }
@ -203,16 +242,33 @@ export default class GameChallengesUiHandler extends UiHandler {
let monoTypeVisible = false; let monoTypeVisible = false;
for (let i = 0; i < Math.min(9, this.scene.gameMode.challenges.length); i++) { for (let i = 0; i < Math.min(9, this.scene.gameMode.challenges.length); i++) {
const challenge = this.scene.gameMode.challenges[this.scrollCursor + i]; const challenge = this.scene.gameMode.challenges[this.scrollCursor + i];
this.challengeLabels[i].label.setText(challenge.getName()); const challengeLabel = this.challengeLabels[i];
challengeLabel.label.setText(challenge.getName());
challengeLabel.leftArrow.setPositionRelative(challengeLabel.label, this.leftArrowGap, 4.5);
challengeLabel.leftArrow.setVisible(challenge.value !== 0);
challengeLabel.rightArrow.setPositionRelative(challengeLabel.leftArrow, Math.max(this.monoTypeValue.width, this.widestTextBox) + challengeLabel.leftArrow.displayWidth + 2 * this.arrowSpacing, 0);
challengeLabel.rightArrow.setVisible(challenge.value !== challenge.maxValue);
// this check looks to make sure that the arrows and value textbox don't take up too much space that they'll clip the right edge of the options background
if (challengeLabel.rightArrow.x + challengeLabel.rightArrow.width + this.optionsBg.rightWidth + this.arrowSpacing > this.optionsWidth) {
// if we go out of bounds of the box, set the x position as far right as we can without going past the box, with this.arrowSpacing to allow a small gap between the arrow and border
challengeLabel.rightArrow.setX(this.optionsWidth - this.arrowSpacing - this.optionsBg.rightWidth);
}
// this line of code gets the center point between the left and right arrows from their left side (Arrow.x gives middle point), taking into account the width of the arrows
const xLocation = Math.round((challengeLabel.leftArrow.x + challengeLabel.rightArrow.x + challengeLabel.leftArrow.displayWidth) / 2);
if (challenge.id === Challenges.SINGLE_TYPE) { if (challenge.id === Challenges.SINGLE_TYPE) {
this.monoTypeValue.setPositionRelative(this.challengeLabels[i].label, 113, 8); this.monoTypeValue.setX(xLocation);
this.monoTypeValue.setY(challengeLabel.label.y + 8);
this.monoTypeValue.setFrame(challenge.getValue()); this.monoTypeValue.setFrame(challenge.getValue());
this.monoTypeValue.setVisible(true); this.monoTypeValue.setVisible(true);
this.challengeLabels[i].value.setVisible(false); challengeLabel.value.setVisible(false);
monoTypeVisible = true; monoTypeVisible = true;
} else { } else {
this.challengeLabels[i].value.setText(challenge.getValue()); challengeLabel.value.setText(challenge.getValue());
this.challengeLabels[i].value.setVisible(true); challengeLabel.value.setX(xLocation);
challengeLabel.value.setOrigin(0.5, 0);
challengeLabel.value.setVisible(true);
} }
} }
if (!monoTypeVisible) { if (!monoTypeVisible) {
@ -244,6 +300,7 @@ export default class GameChallengesUiHandler extends UiHandler {
super.show(args); super.show(args);
this.startCursor.setVisible(false); this.startCursor.setVisible(false);
this.updateChallengeArrows(false);
this.challengesContainer.setVisible(true); this.challengesContainer.setVisible(true);
// Should always be false at the start // Should always be false at the start
this.hasSelectedChallenge = this.scene.gameMode.challenges.some(c => c.value !== 0); this.hasSelectedChallenge = this.scene.gameMode.challenges.some(c => c.value !== 0);
@ -259,6 +316,21 @@ export default class GameChallengesUiHandler extends UiHandler {
return true; return true;
} }
/* This code updates the challenge starter arrows to be tinted/not tinted when the start button is selected to show they can't be changed
*/
updateChallengeArrows(tinted: boolean) {
for (let i = 0; i < Math.min(9, this.scene.gameMode.challenges.length); i++) {
const challengeLabel = this.challengeLabels[i];
if (tinted) {
challengeLabel.leftArrow.setTint(0x808080);
challengeLabel.rightArrow.setTint(0x808080);
} else {
challengeLabel.leftArrow.clearTint();
challengeLabel.rightArrow.clearTint();
}
}
}
/** /**
* Processes input from a specified button. * Processes input from a specified button.
* This method handles navigation through a UI menu, including movement through menu items * This method handles navigation through a UI menu, including movement through menu items
@ -280,6 +352,7 @@ export default class GameChallengesUiHandler extends UiHandler {
// If the user presses cancel when the start cursor has been activated, the game deactivates the start cursor and allows typical challenge selection behavior // If the user presses cancel when the start cursor has been activated, the game deactivates the start cursor and allows typical challenge selection behavior
this.startCursor.setVisible(false); this.startCursor.setVisible(false);
this.cursorObj?.setVisible(true); this.cursorObj?.setVisible(true);
this.updateChallengeArrows(this.startCursor.visible);
} else { } else {
this.scene.clearPhaseQueue(); this.scene.clearPhaseQueue();
this.scene.pushPhase(new TitlePhase(this.scene)); this.scene.pushPhase(new TitlePhase(this.scene));
@ -294,6 +367,7 @@ export default class GameChallengesUiHandler extends UiHandler {
} else { } else {
this.startCursor.setVisible(true); this.startCursor.setVisible(true);
this.cursorObj?.setVisible(false); this.cursorObj?.setVisible(false);
this.updateChallengeArrows(this.startCursor.visible);
} }
success = true; success = true;
} else { } else {