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 {
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
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 {
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> {
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
@ -4907,7 +4970,8 @@ export function initAbilities() {
.attr(EffectSporeAbAttr),
new Ability(Abilities.SYNCHRONIZE, 3)
.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)
.attr(ProtectStatAbAttr)
.ignorable(),
@ -5879,6 +5943,6 @@ export function initAbilities() {
new Ability(Abilities.POISON_PUPPETEER, 9)
.attr(UncopiableAbilityAbAttr)
.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.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ];
let r = anim!.frames.length; // TODO: is this bang correct?
let r = anim?.frames.length ?? 0;
let f = 0;
scene.tweens.addCounter({
duration: Utils.getFrameMs(3),
repeat: anim!.frames.length, // TODO: is this bang correct?
repeat: anim?.frames.length ?? 0,
onRepeat: () => {
if (!f) {
userSprite.setVisible(false);
@ -1264,7 +1264,7 @@ export class CommonBattleAnim extends BattleAnim {
}
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 {
@ -1284,7 +1284,7 @@ export class MoveAnim extends BattleAnim {
getAnim(): AnimConfig {
return moveAnims.get(this.move) instanceof 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 {
@ -1316,7 +1316,7 @@ export class MoveChargeAnim extends MoveAnim {
getAnim(): AnimConfig {
return chargeAnims.get(this.chargeAnim) instanceof 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 {
/** The substitute's remaining HP. If HP is depleted, the Substitute fades. */
public hp: number;
@ -2568,6 +2598,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new GorillaTacticsTag();
case BattlerTagType.SUBSTITUTE:
return new SubstituteTag(sourceMove, sourceId);
case BattlerTagType.AUTOTOMIZED:
return new AutotomizedTag();
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
return new MysteryEncounterPostSummonTag();
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.
* @returns {@link string} The localised name for the current value.
*/
getValue(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return i18next.t(`challenges:${this.geti18nKey()}.value.${this.value}`);
getValue(overrideValue?: number): string {
const value = overrideValue ?? this.value;
return i18next.t(`challenges:${this.geti18nKey()}.value.${value}`);
}
/**
@ -184,11 +182,9 @@ export abstract class Challenge {
* @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.
*/
getDescription(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${this.value}`, `challenges:${this.geti18nKey()}.desc`])}`;
getDescription(overrideValue?: number): string {
const value = overrideValue ?? this.value;
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${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.
* @returns {string} The localised name for the current value.
*/
getValue(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
if (this.value === 0) {
getValue(overrideValue?: number): string {
const value = overrideValue ?? this.value;
if (value === 0) {
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.
* @returns {string} The localised description for the current value.
*/
getDescription(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
if (this.value === 0) {
getDescription(overrideValue?: number): string {
const value = overrideValue ?? this.value;
if (value === 0) {
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 target {@linkcode Pokemon} to apply conditions to
* @param move {@linkcode Move} to apply conditions to
@ -2091,21 +2091,20 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
if (target.status) {
return false;
}
//@ts-ignore - how can target.status.effect be checked when we return `false` before when it's defined?
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { // TODO: resolve ts-ignore
const statusAfflictResult = target.trySetStatus(statusToApply, true, user);
if (statusAfflictResult) {
} else {
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
if (canSetStatus) {
if (user.status) {
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
}
user.resetStatus();
user.updateInfo();
target.trySetStatus(statusToApply, true, user);
}
return statusAfflictResult;
}
return false;
return canSetStatus;
}
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
@ -5175,31 +5174,29 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
}
export class ForceSwitchOutAttr extends MoveEffectAttr {
private user: boolean;
private batonPass: boolean;
constructor(user?: boolean, batonPass?: boolean) {
constructor(
private selfSwitch: boolean = false,
private batonPass: boolean = false
) {
super(false, MoveEffectTrigger.POST_APPLY, false, true);
this.user = !!user;
this.batonPass = !!batonPass;
}
isBatonPass() {
return this.batonPass;
}
// TODO: Why is this a Promise?
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
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)) {
return resolve(false);
}
// Move the switch out logic inside the conditional block
// This ensures that the switch out only happens when the conditions are met
const switchOutTarget = this.user ? user : target;
if (switchOutTarget instanceof PlayerPokemon) {
// Move the switch out logic inside the conditional block
// This ensures that the switch out only happens when the conditions are met
const switchOutTarget = this.selfSwitch ? user : target;
if (switchOutTarget instanceof PlayerPokemon) {
switchOutTarget.leaveField(!this.batonPass);
if (switchOutTarget.hp > 0) {
@ -5208,41 +5205,43 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} else {
resolve(false);
}
return;
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
// Switch out logic for trainer battles
return;
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
// Switch out logic for trainer battles
switchOutTarget.leaveField(!this.batonPass);
if (switchOutTarget.hp > 0) {
// 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);
if (switchOutTarget.hp > 0) {
// 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);
}
} else {
// Switch out logic for everything else (eg: WILD battles)
switchOutTarget.leaveField(false);
} else {
// Switch out logic for everything else (eg: WILD battles)
switchOutTarget.leaveField(false);
if (switchOutTarget.hp) {
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
if (switchOutTarget.hp) {
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
// in double battles redirect potential moves off fled pokemon
if (switchOutTarget.scene.currentBattle.double) {
const allyPokemon = switchOutTarget.getAlly();
switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon);
}
}
}
if (!switchOutTarget.getAlly()?.isActive(true)) {
user.scene.clearEnemyHeldItemModifiers();
if (!switchOutTarget.getAlly()?.isActive(true)) {
user.scene.clearEnemyHeldItemModifiers();
if (switchOutTarget.hp) {
user.scene.pushPhase(new BattleEndPhase(user.scene));
user.scene.pushPhase(new NewBattlePhase(user.scene));
}
}
}
if (switchOutTarget.hp) {
user.scene.pushPhase(new BattleEndPhase(user.scene));
user.scene.pushPhase(new NewBattlePhase(user.scene));
}
}
}
resolve(true);
});
resolve(true);
});
}
getCondition(): MoveConditionFunc {
@ -5257,29 +5256,33 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
getSwitchOutCondition(): MoveConditionFunc {
return (user, target, move) => {
const switchOutTarget = (this.user ? user : target);
const switchOutTarget = (this.selfSwitch ? user : target);
const player = switchOutTarget instanceof PlayerPokemon;
if (!this.user && move.hitsSubstitute(user, target)) {
return false;
if (!this.selfSwitch) {
if (move.hitsSubstitute(user, target)) {
return false;
}
const blockedByAbility = new Utils.BooleanHolder(false);
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility);
return !blockedByAbility.value;
}
if (!this.user && move.category === MoveCategory.STATUS && (target.hasAbilityWithAttr(ForceSwitchOutImmunityAbAttr))) {
return false;
}
if (!player && !user.scene.currentBattle.battleType) {
if (!player && user.scene.currentBattle.battleType === BattleType.WILD) {
if (this.batonPass) {
return false;
}
// 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;
}
}
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())) {
return -20;
}
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
if (this.user && this.batonPass) {
let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
if (this.selfSwitch && this.batonPass) {
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));
}
@ -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 {
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),
new SelfStatusMove(Moves.AUTOTOMIZE, Type.STEEL, -1, 15, -1, 0, 5)
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true)
.partial(),
.attr(AddBattlerTagAttr, BattlerTagType.AUTOTOMIZED, true),
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
.powderMove()
.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)
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
.makesContact(false)
.attr(AuraWheelTypeAttr)
.condition((user, target, move) => [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)), // Missing custom fail message
.attr(AuraWheelTypeAttr),
new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
@ -9486,10 +9503,9 @@ export function initMoves() {
.makesContact(),
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
.unimplemented(),
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
.attr(WeatherChangeAttr, WeatherType.SNOW)
.attr(ForceSwitchOutAttr, true, false)
.target(MoveTarget.BOTH_SIDES),
new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", {pokemonName: getPokemonNameWithAffix(user)}))
.attr(ChillyReceptionAttr, true, false),
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
.attr(RemoveArenaTrapAttr, true)

View File

@ -597,7 +597,7 @@ export class TrainerConfig {
case "flare": {
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.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]
};
}
@ -640,14 +640,14 @@ export class TrainerConfig {
return {
[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.RARE]: [ Species.BULBASAUR, Species.GLIMMET ]
[TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ]
};
}
case "star_4": {
return {
[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.RARE]: [ Species.POPPLIO, Species.GALAR_PONYTA ]
[TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ]
};
}
case "star_5": {
@ -1509,7 +1509,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools({
[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.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]
}),
[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))
.setSpeciesPools({
[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.RARE]: [Species.PAWNIARD, Species.RUFFLET, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.CUBCHOO, Species.MIENFOO, Species.DURANT, Species.BOUFFALANT],
[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.MIENFOO, Species.DURANT, Species.BOUFFALANT],
[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)),
@ -1537,7 +1537,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools({
[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.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]
}),
[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))
.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.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.RARE]: [ Species.GALAR_CORSOLA, Species.ALOLA_SANDSHREW, Species.ALOLA_VULPIX, Species.TURTONATOR, Species.DRAMPA],
[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.TURTONATOR, Species.MIMIKYU, Species.MAGNEMITE, Species.DRAMPA],
[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.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({
[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.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.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.ZUBAT, Species.VENIPEDE, Species.VULLABY],
[TrainerPoolTier.RARE]: [Species.SANDYGAST, Species.PAWNIARD, Species.MIMIKYU, Species.DHELMISE, Species.WISHIWASHI, Species.NYMBLE],
[TrainerPoolTier.SUPER_RARE]: [Species.GRUBBIN, Species.DEWPIDER]
}),
@ -1916,7 +1916,14 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // Mega Kangaskhan
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 => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
@ -2060,7 +2067,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
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 => {
p.generateAndPopulateMoveset();
@ -2153,9 +2160,23 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL;
})),
[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(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(4, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -2175,25 +2196,32 @@ export const trainerConfigs: TrainerConfigs = {
p.abilityIndex = 2; //Anticipation
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
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.BUZZWOLE ], TrainerSlot.TRAINER, true, p => {
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.generateAndPopulateMoveset();
p.generateName();
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")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ]))
@ -2209,17 +2237,16 @@ export const trainerConfigs: TrainerConfigs = {
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")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.MELMETAL ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AEGISLASH, Species.GHOLDENGO ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.DRACOVISH, Species.DRACOZOLT ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
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 => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();

View File

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

View File

@ -33,6 +33,7 @@ export class Arena {
public tags: ArenaTag[];
public bgm: string;
public ignoreAbilities: boolean;
public ignoringEffectSource: BattlerIndex | null;
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.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 { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
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 { 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 { BattlerIndex } from "../battle";
import { Mode } from "../ui/ui";
@ -1364,7 +1364,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
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;
}
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
@ -1426,11 +1427,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
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 {
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
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) {
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;
}
@ -3399,6 +3412,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (effect !== StatusEffect.FAINT) {
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, this, effect, sourcePokemon);
}
return true;

View File

@ -1237,6 +1237,6 @@
},
"poisonPuppeteer": {
"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",
"grass": "PMD Erkundungsteam Himmel Apfelwald",
"graveyard": "PMD Erkundungsteam Himmel Verwirrwald",
"ice_cave": "PMD Erkundungsteam Himmel Rieseneisberg",
"ice_cave": "Firel - -50°C",
"island": "PMD Erkundungsteam Himmel Schroffküste",
"jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory",
"lake": "PMD Erkundungsteam Himmel Kristallhöhle",
"lake": "Lmz - Lake",
"meadow": "PMD Erkundungsteam Himmel Himmelsgipfel-Wald",
"metropolis": "Firel - Metropolis",
"mountain": "PMD Erkundungsteam Himmel Hornberg",
"plains": "PMD Erkundungsteam Himmel Himmelsgipfel-Prärie",
"power_plant": "PMD Erkundungsteam Himmel Weite Ampere-Ebene",
"ruins": "PMD Erkundungsteam Himmel Tiefes Ruinenverlies",
"plains": "Firel - Route 888",
"power_plant": "Firel - The Klink",
"ruins": "Lmz - Ancient Ruins",
"sea": "Andr06 - Marine Mystique",
"seabed": "Firel - Seabed",
"slum": "Andr06 - Sneaky Snom",
@ -131,7 +131,7 @@
"tall_grass": "PMD Erkundungsteam Himmel Nebelwald",
"temple": "PMD Erkundungsteam Himmel Ägishöhle",
"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",
"encounter_ace_trainer": "SW Trainerblicke treffen sich (Ass-Trainer)",
"encounter_backpacker": "SW Trainerblicke treffen sich (Backpacker)",

View File

@ -65,6 +65,7 @@
"suppressAbilities": "Die Fähigkeit von {{pokemonName}} wirkt nicht mehr!",
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
"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}}!",
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!",
"afterYou": "{{targetName}} lässt sich auf Galanterie ein!"

View File

@ -3129,7 +3129,7 @@
},
"auraWheel": {
"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": {
"name": "Breitseite",

View File

@ -1237,6 +1237,6 @@
},
"poisonPuppeteer": {
"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!",
"substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!",
"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",
"grass": "PMD EoS Apple Woods",
"graveyard": "PMD EoS Mystifying Forest",
"ice_cave": "Firel - -60F",
"ice_cave": "Firel - -50°C",
"island": "PMD EoS Craggy Coast",
"jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory",

View File

@ -66,6 +66,7 @@
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
"revivalBlessing": "{{pokemonName}} was revived!",
"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}}!",
"safeguard": "{{targetName}} is protected by Safeguard!",
"substituteOnOverlap": "{{pokemonName}} already\nhas a substitute!",

View File

@ -3129,7 +3129,7 @@
},
"auraWheel": {
"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": {
"name": "Breaking Swipe",

View File

@ -4,7 +4,7 @@
"costar": "¡{{pokemonName}} copió los cambios de características de {{allyName}}!",
"iceFaceAvoidedDamage": "¡{{pokemonNameWithAffix}} evitó\ndaño con {{abilityName}}!",
"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}}!",
"windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!",
"quickDraw": "¡{{pokemonName}} ataca primero gracias a la habilidad Mano Rápida!",

View File

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

View File

@ -2913,7 +2913,7 @@
},
"zippyZap": {
"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": {
"name": "Salpikasurf",
@ -3129,7 +3129,7 @@
},
"auraWheel": {
"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": {
"name": "Vasto Impacto",

View File

@ -1077,7 +1077,7 @@
},
"thermalExchange": {
"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": {
"name": "Courroupace",
@ -1237,6 +1237,6 @@
},
"poisonPuppeteer": {
"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}} ₽ !",
"moneyPickedUp": "Vous obtenez {{moneyAmount}} ₽ !",
"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 !",
"partyFull": "Votre équipe est pleine.\nRelâcher un Pokémon pour {{pokemonName}} ?",
"pokemon": "de Pokémon",
@ -65,6 +69,7 @@
"skipItemQuestion": "Êtes-vous sûr·e de ne pas vouloir prendre dobjet ?",
"itemStackFull": "Quantité maximale de {{fullItemName}} atteinte.\nVous recevez {{itemName}} à la place.",
"eggHatching": "Hein ?",
"eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?",
"ivScannerUseQuestion": "Utiliser le Scanner dIV\nsur {{pokemonName}} ?",
"wildPokemonWithAffix": "{{pokemonName}} sauvage",
"foePokemonWithAffix": "{{pokemonName}} ennemi",
@ -99,7 +104,7 @@
"unlockedSomething": "{{unlockedThing}}\na été débloqué.",
"congratulations": "Félicitations !",
"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 !",
"battlerTagsHealBlockOnRemove": "Le blocage de soins qui affectait\n{{pokemonNameWithAffix}} sest dissipé !"
}

View File

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

View File

@ -66,6 +66,7 @@
"suppressAbilities": "Le talent de {{pokemonName}}\na été rendu inactif !",
"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}} !",
"chillyReception": "{{pokemonName}} sapprête\nà faire un mauvais jeu de mots…",
"exposedMove": "{{targetPokemonName}} est identifié\npar {{pokemonName}} !",
"safeguard": "{{targetName}} est protégé\npar la capacité Rune Protect !",
"substituteOnOverlap": "{{pokemonName}} a déjà\nun clone !",

View File

@ -3129,7 +3129,7 @@
},
"auraWheel": {
"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": {
"name": "Abattage",

View File

@ -1237,6 +1237,6 @@
},
"poisonPuppeteer": {
"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!",
"swapArenaTags": "{{pokemonName}} ha invertito gli effetti attivi\nnelle due metà del campo!",
"exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!",
"chillyReception": "{{pokemonName}} sta per fare una battuta!",
"safeguard": "Salvaguardia protegge {{targetName}}!",
"afterYou": "{{pokemonName}} approfitta della cortesia!"
}

View File

@ -3129,7 +3129,7 @@
},
"auraWheel": {
"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": {
"name": "Vastoimpatto",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -61,6 +61,7 @@
"suppressAbilities": "A habilidade de {{pokemonName}}\nfoi suprimida!",
"revivalBlessing": "{{pokemonName}} foi reanimado!",
"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}}!",
"safeguard": "{{targetName}} está protegido por Safeguard!",
"afterYou": "{{pokemonName}} aceitou a gentil oferta!"

View File

@ -3129,7 +3129,7 @@
},
"auraWheel": {
"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": {
"name": "Breaking Swipe",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1288,6 +1288,21 @@ function skipInClassicAfterWave(wave: integer, defaultWeight: integer): Weighted
function skipInLastClassicWaveOrDefault(defaultWeight: integer) : WeightedModifierTypeWeightFunc {
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 {
public modifierType: ModifierType;
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);
return thresholdPartyMemberCount;
}, 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.BERRY, 2),
new WeightedModifierType(modifierTypes.TM_COMMON, 2),
@ -1668,7 +1683,7 @@ const modifierPool: ModifierPool = {
return thresholdPartyMemberCount;
}, 3),
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.EVOLUTION_ITEM, (party: Pokemon[]) => {
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8);
@ -1691,7 +1706,7 @@ const modifierPool: ModifierPool = {
}),
[ModifierTier.ULTRA]: [
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.PP_MAX, 3),
new WeightedModifierType(modifierTypes.MINT, 4),

View File

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

View File

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

View File

@ -9,26 +9,26 @@ import { PokemonPhase } from "./pokemon-phase";
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
export class ObtainStatusEffectPhase extends PokemonPhase {
private statusEffect: StatusEffect | undefined;
private cureTurn: integer | null;
private sourceText: string | null;
private sourcePokemon: Pokemon | null;
private statusEffect?: StatusEffect | undefined;
private cureTurn?: integer | null;
private sourceText?: string | 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);
this.statusEffect = statusEffect;
this.cureTurn = cureTurn!; // TODO: is this bang correct?
this.sourceText = sourceText!; // TODO: is this bang correct?
this.sourcePokemon = sourcePokemon!; // For tracking which Pokemon caused the status effect // TODO: is this bang correct?
this.cureTurn = cureTurn;
this.sourceText = sourceText;
this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect
}
start() {
const pokemon = this.getPokemon();
if (!pokemon?.status) {
if (pokemon?.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
if (pokemon && !pokemon.status) {
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
if (this.cureTurn) {
pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct?
pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct?
}
pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
@ -40,8 +40,8 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
});
return;
}
} else if (pokemon.status.effect === this.statusEffect) {
this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon)));
} else if (pokemon.status?.effect === this.statusEffect) {
this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)));
}
this.end();
}

View File

@ -3,6 +3,7 @@ import { SemiInvulnerableTag } from "#app/data/battler-tags";
import { SpeciesFormChange, getSpeciesFormChangeMessage } from "#app/data/pokemon-forms";
import { getTypeRgb } from "#app/data/type";
import { BattleSpec } from "#app/enums/battle-spec";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { BattlePhase } from "./battle-phase";
@ -113,6 +114,7 @@ export class QuietFormChangePhase extends BattlePhase {
}
end(): void {
this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED);
if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) {
this.scene.playBgm();
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 { 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 { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "../utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Dragon Tail", () => {
let phaserGame: Phaser.Game;
@ -29,7 +24,7 @@ describe("Moves - Dragon Tail", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleType("single")
.moveset([Moves.DRAGON_TAIL, Moves.SPLASH])
.moveset([Moves.DRAGON_TAIL, Moves.SPLASH, Moves.FLAMETHROWER])
.enemySpecies(Species.WAILORD)
.enemyMoveset(Moves.SPLASH)
.startingLevel(5)
@ -38,109 +33,110 @@ describe("Moves - Dragon Tail", () => {
vi.spyOn(allMoves[Moves.DRAGON_TAIL], "accuracy", "get").mockReturnValue(100);
});
test(
"Single battle should cause opponent to flee, and not crash",
async () => {
await game.startBattle([Species.DRATINI]);
it("should cause opponent to flee, and not crash", async () => {
await game.classicMode.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 hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true);
const isVisible = enemyPokemon.visible;
const hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true);
// simply want to test that the game makes it this far without crashing
await game.phaseInterceptor.to(BattleEndPhase);
}
);
// simply want to test that the game makes it this far without crashing
await game.phaseInterceptor.to("BattleEndPhase");
});
test(
"Single battle should cause opponent to flee, display ability, and not crash",
async () => {
game.override.enemyAbility(Abilities.ROUGH_SKIN);
await game.startBattle([Species.DRATINI]);
it("should cause opponent to flee, display ability, and not crash", async () => {
game.override.enemyAbility(Abilities.ROUGH_SKIN);
await game.classicMode.startBattle([Species.DRATINI]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
const leadPokemon = game.scene.getPlayerPokemon()!;
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 hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true);
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
}
);
const isVisible = enemyPokemon.visible;
const hasFled = enemyPokemon.switchOutStatus;
expect(!isVisible && hasFled).toBe(true);
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
});
test(
"Double battles should proceed without crashing",
async () => {
game.override.battleType("double").enemyMoveset(Moves.SPLASH);
game.override.moveset([Moves.DRAGON_TAIL, Moves.SPLASH, Moves.FLAMETHROWER])
.enemyAbility(Abilities.ROUGH_SKIN);
await game.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]);
it("should proceed without crashing in a double battle", async () => {
game.override
.battleType("double").enemyMoveset(Moves.SPLASH)
.enemyAbility(Abilities.ROUGH_SKIN);
await game.classicMode.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]);
const leadPokemon = game.scene.getParty()[0]!;
const leadPokemon = game.scene.getParty()[0]!;
const enemyLeadPokemon = game.scene.getEnemyParty()[0]!;
const enemySecPokemon = game.scene.getEnemyParty()[1]!;
const enemyLeadPokemon = game.scene.getEnemyParty()[0]!;
const enemySecPokemon = game.scene.getEnemyParty()[1]!;
game.move.select(Moves.DRAGON_TAIL, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SPLASH, 1);
game.move.select(Moves.DRAGON_TAIL, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus;
const isVisibleSec = enemySecPokemon.visible;
const hasFledSec = enemySecPokemon.switchOutStatus;
expect(!isVisibleLead && hasFledLead && isVisibleSec && !hasFledSec).toBe(true);
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus;
const isVisibleSec = enemySecPokemon.visible;
const hasFledSec = enemySecPokemon.switchOutStatus;
expect(!isVisibleLead && hasFledLead && isVisibleSec && !hasFledSec).toBe(true);
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
// second turn
game.move.select(Moves.FLAMETHROWER, 0, BattlerIndex.ENEMY_2);
game.move.select(Moves.SPLASH, 1);
// second turn
game.move.select(Moves.FLAMETHROWER, 0, BattlerIndex.ENEMY_2);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(BerryPhase);
expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp());
}
);
await game.phaseInterceptor.to("BerryPhase");
expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp());
});
test(
"Flee move redirection works",
async () => {
game.override.battleType("double").enemyMoveset(Moves.SPLASH);
game.override.moveset([Moves.DRAGON_TAIL, Moves.SPLASH, Moves.FLAMETHROWER]);
game.override.enemyAbility(Abilities.ROUGH_SKIN);
await game.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]);
it("should redirect targets upon opponent flee", async () => {
game.override
.battleType("double")
.enemyMoveset(Moves.SPLASH)
.enemyAbility(Abilities.ROUGH_SKIN);
await game.classicMode.startBattle([Species.DRATINI, Species.DRATINI, Species.WAILORD, Species.WAILORD]);
const leadPokemon = game.scene.getParty()[0]!;
const secPokemon = game.scene.getParty()[1]!;
const leadPokemon = game.scene.getParty()[0]!;
const secPokemon = game.scene.getParty()[1]!;
const enemyLeadPokemon = game.scene.getEnemyParty()[0]!;
const enemySecPokemon = game.scene.getEnemyParty()[1]!;
const enemyLeadPokemon = game.scene.getEnemyParty()[0]!;
const enemySecPokemon = game.scene.getEnemyParty()[1]!;
game.move.select(Moves.DRAGON_TAIL, 0, BattlerIndex.ENEMY);
// 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, 0, BattlerIndex.ENEMY);
// target the same pokemon, second move should be redirected after first flees
game.move.select(Moves.DRAGON_TAIL, 1, BattlerIndex.ENEMY);
await game.phaseInterceptor.to(BerryPhase);
await game.phaseInterceptor.to("BerryPhase");
const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus;
const isVisibleSec = enemySecPokemon.visible;
const hasFledSec = enemySecPokemon.switchOutStatus;
expect(!isVisibleLead && hasFledLead && !isVisibleSec && hasFledSec).toBe(true);
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
expect(secPokemon.hp).toBeLessThan(secPokemon.getMaxHp());
expect(enemyLeadPokemon.hp).toBeLessThan(enemyLeadPokemon.getMaxHp());
expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp());
}
);
const isVisibleLead = enemyLeadPokemon.visible;
const hasFledLead = enemyLeadPokemon.switchOutStatus;
const isVisibleSec = enemySecPokemon.visible;
const hasFledSec = enemySecPokemon.switchOutStatus;
expect(!isVisibleLead && hasFledLead && !isVisibleSec && hasFledSec).toBe(true);
expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp());
expect(secPokemon.hp).toBeLessThan(secPokemon.getMaxHp());
expect(enemyLeadPokemon.hp).toBeLessThan(enemyLeadPokemon.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 GameManager from "#test/utils/gameManager";
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", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
beforeAll(() => {
phaserGame = new Phaser.Game({
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 () => {
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");
game.move.select(Moves.SHELL_SIDE_ARM);
await game.phaseInterceptor.to("MoveEffectPhase");
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 () => {
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");
game.move.select(Moves.SHELL_SIDE_ARM);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);
@ -70,14 +67,10 @@ describe("Moves - Shell Side Arm", () => {
await game.classicMode.startBattle([Species.MANAPHY]);
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
vi.spyOn(shellSideArmAttr, "apply");
game.move.select(Moves.SHELL_SIDE_ARM);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);

View File

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

View File

@ -28,7 +28,7 @@ export default class GameChallengesUiHandler extends UiHandler {
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 cursorObj: Phaser.GameObjects.NineSlice | null;
@ -40,6 +40,11 @@ export default class GameChallengesUiHandler extends UiHandler {
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) {
super(scene, mode);
}
@ -47,6 +52,8 @@ export default class GameChallengesUiHandler extends UiHandler {
setup() {
const ui = this.getUi();
this.widestTextBox = 0;
this.challengesContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1);
this.challengesContainer.setName("challenges");
@ -135,6 +142,20 @@ export default class GameChallengesUiHandler extends UiHandler {
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);
value.setName(`challenge-value-text-${i}`);
value.setPositionRelative(label, 100, 0);
@ -142,7 +163,9 @@ export default class GameChallengesUiHandler extends UiHandler {
this.challengeLabels[i] = {
label: label,
value: value
value: value,
leftArrow: leftArrow,
rightArrow: rightArrow
};
}
@ -187,10 +210,26 @@ export default class GameChallengesUiHandler extends UiHandler {
*/
initLabels(): void {
this.setDescription(this.scene.gameMode.challenges[0].getDescription());
this.widestTextBox = 0;
for (let i = 0; i < 9; i++) {
if (i < this.scene.gameMode.challenges.length) {
this.challengeLabels[i].label.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;
for (let i = 0; i < Math.min(9, this.scene.gameMode.challenges.length); 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) {
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.setVisible(true);
this.challengeLabels[i].value.setVisible(false);
challengeLabel.value.setVisible(false);
monoTypeVisible = true;
} else {
this.challengeLabels[i].value.setText(challenge.getValue());
this.challengeLabels[i].value.setVisible(true);
challengeLabel.value.setText(challenge.getValue());
challengeLabel.value.setX(xLocation);
challengeLabel.value.setOrigin(0.5, 0);
challengeLabel.value.setVisible(true);
}
}
if (!monoTypeVisible) {
@ -244,6 +300,7 @@ export default class GameChallengesUiHandler extends UiHandler {
super.show(args);
this.startCursor.setVisible(false);
this.updateChallengeArrows(false);
this.challengesContainer.setVisible(true);
// Should always be false at the start
this.hasSelectedChallenge = this.scene.gameMode.challenges.some(c => c.value !== 0);
@ -259,6 +316,21 @@ export default class GameChallengesUiHandler extends UiHandler {
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.
* 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
this.startCursor.setVisible(false);
this.cursorObj?.setVisible(true);
this.updateChallengeArrows(this.startCursor.visible);
} else {
this.scene.clearPhaseQueue();
this.scene.pushPhase(new TitlePhase(this.scene));
@ -294,6 +367,7 @@ export default class GameChallengesUiHandler extends UiHandler {
} else {
this.startCursor.setVisible(true);
this.cursorObj?.setVisible(false);
this.updateChallengeArrows(this.startCursor.visible);
}
success = true;
} else {