mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-27 09:49:30 +02:00
Merge branch 'beta' into doc/translation-tool-pontoon
This commit is contained in:
commit
90fdca4202
@ -4296,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;
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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}`) });
|
||||
}
|
||||
|
||||
|
||||
|
@ -5174,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) {
|
||||
@ -5207,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 {
|
||||
@ -5256,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();
|
||||
};
|
||||
}
|
||||
|
||||
@ -5286,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));
|
||||
}
|
||||
@ -8105,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),
|
||||
|
@ -2067,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();
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ 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, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "../data/ability";
|
||||
@ -1427,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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!"
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
|
98
src/test/moves/autotomize.test.ts
Normal file
98
src/test/moves/autotomize.test.ts
Normal 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);
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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[]>([
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user