Add Challenges (#1459)

* Initial challenge framework

* Add type localisation

* Change how challenges are tracked

Also fixes the difficulty total text

* MVP

Renames challenge types, temporarily hides difficulty, and implements challenge saving.

* Attempt to fix one legal pokemon in a double battle

* Make monotype ignore type changing effects

* Make isOfType correctly detect normal types

* Try to fix double battles again

* Make challenge function more like classic

* Add helper function for fainted or not allowed

* Add framework for fresh start challenge and improve comments

* Try to fix evolution issues

* Make form changing items only usable from rewards screen

* Update localisation

* Additional localisation change

* Add achievements for completing challenges

* Fix initialisation bug with challenge achievements

* Add support for gamemode specific fixed battles

Also make monogen challenges face the e4 of their generation

* Add better support for mobile in challenges

* Localise illegal evolution/form change message

* Update achievement names

* Make alternate forms count for monogen

* Update monotype achievement icons

* Add more comments

* Improve comments

* Fix mid battle form changes

* Reorder mode list

* Remove currently unused localisation entry

* Add type overrides for monotype challenges

Meloetta always counts for psychic and castform always counts for normal

* Change how form changes are handled

Now attempts a switch at the start of each turn instead of immediately

* Add start button to challenge select screen

* Make starter select back out to challenge screen if using challenges

* Fix daily runs

* Update tests to new game mode logic
This commit is contained in:
Xavion3 2024-06-08 15:07:23 +10:00 committed by GitHub
parent 9c97e37c27
commit 696ff6eae3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 2115 additions and 134 deletions

View File

@ -20,8 +20,8 @@ import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesF
import AbilityBar from "./ui/ability-bar";
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
import { allAbilities } from "./data/ability";
import Battle, { BattleType, FixedBattleConfig, fixedBattles } from "./battle";
import { GameMode, GameModes, gameModes } from "./game-mode";
import Battle, { BattleType, FixedBattleConfig } from "./battle";
import { GameMode, GameModes, getGameMode } from "./game-mode";
import FieldSpritePipeline from "./pipelines/field-sprite";
import SpritePipeline from "./pipelines/sprite";
import PartyExpBar from "./ui/party-exp-bar";
@ -853,7 +853,7 @@ export default class BattleScene extends SceneBase {
this.gameData = new GameData(this);
}
this.gameMode = gameModes[GameModes.CLASSIC];
this.gameMode = getGameMode(GameModes.CLASSIC);
this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24));
console.log("Seed:", this.seed);
@ -961,8 +961,8 @@ export default class BattleScene extends SceneBase {
const playerField = this.getPlayerField();
if (this.gameMode.hasFixedBattles && fixedBattles.hasOwnProperty(newWaveIndex) && trainerData === undefined) {
battleConfig = fixedBattles[newWaveIndex];
if (this.gameMode.isFixedBattle(newWaveIndex) && trainerData === undefined) {
battleConfig = this.gameMode.getFixedBattle(newWaveIndex);
newDouble = battleConfig.double;
newBattleType = battleConfig.battleType;
this.executeWithSeedOffset(() => newTrainer = battleConfig.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8);

View File

@ -416,11 +416,11 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[]): Get
};
}
interface FixedBattleConfigs {
export interface FixedBattleConfigs {
[key: integer]: FixedBattleConfig
}
export const fixedBattles: FixedBattleConfigs = {
export const classicFixedBattles: FixedBattleConfigs = {
[5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)
.setGetTrainerFunc(scene => new Trainer(scene, TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)),
[8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER)

568
src/data/challenge.ts Normal file
View File

@ -0,0 +1,568 @@
import * as Utils from "../utils";
import { Challenges } from "./enums/challenges";
import i18next from "#app/plugins/i18n.js";
import { GameData } from "#app/system/game-data.js";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
import Pokemon from "#app/field/pokemon.js";
import { BattleType, FixedBattleConfig } from "#app/battle.js";
import { TrainerType } from "./enums/trainer-type";
import Trainer, { TrainerVariant } from "#app/field/trainer.js";
import { GameMode } from "#app/game-mode.js";
import { Species } from "./enums/species";
import { Type } from "./type";
/**
* An enum for all the challenge types. The parameter entries on these describe the
* parameters to use when calling the applyChallenges function.
*/
export enum ChallengeType {
/**
* Challenges which modify what starters you can choose
* @param args [0] {@link PokemonSpecies} The species to check
* [1] {@link Utils.BooleanHolder} Sets to false if illegal, pass in true.
*/
STARTER_CHOICE,
/**
* Challenges which modify how many starter points you have
* @param args [0] {@link Utils.NumberHolder} The amount of starter points you have
*/
STARTER_POINTS,
/**
* Challenges which modify your starters in some way
* Not Fully Implemented
*/
STARTER_MODIFY,
/**
* Challenges which limit which pokemon you can have in battle.
* @param args [0] {@link Pokemon} The pokemon to check
* [1] {@link Utils.BooleanHolder} Sets to false if illegal, pass in true.
*/
POKEMON_IN_BATTLE,
/**
* Adds or modifies the fixed battles in a run
* @param args [0] integer The wave to get a battle for
* [1] {@link FixedBattleConfig} A new fixed battle. It'll be modified if a battle exists.
*/
FIXED_BATTLES,
}
/**
* A challenge object. Exists only to serve as a base class.
*/
export abstract class Challenge {
public id: Challenges; // The id of the challenge
public value: integer; // The "strength" of the challenge, all challenges have a numerical value.
public maxValue: integer; // The maximum strength of the challenge.
public severity: integer; // The current severity of the challenge. Some challenges have multiple severities in addition to strength.
public maxSeverity: integer; // The maximum severity of the challenge.
public conditions: ChallengeCondition[];
public challengeTypes: ChallengeType[];
/**
* @param {Challenges} id The enum value for the challenge
*/
constructor(id: Challenges, maxValue: integer = Number.MAX_SAFE_INTEGER) {
this.id = id;
this.value = 0;
this.maxValue = maxValue;
this.severity = 0;
this.maxSeverity = 0;
this.conditions = [];
this.challengeTypes = [];
}
/**
* Reset the challenge to a base state.
*/
reset(): void {
this.value = 0;
this.severity = 0;
}
/**
* Gets the localisation key for the challenge
* @returns The i18n key for this challenge
*/
geti18nKey(): string {
return Challenges[this.id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("");
}
/**
* Used for unlockable challenges to check if they're unlocked.
* @param {GameData} data The save data.
* @returns {boolean} Whether this challenge is unlocked.
*/
isUnlocked(data: GameData): boolean {
return this.conditions.every(f => f(data));
}
/**
* Adds an unlock condition to this challenge.
* @param {ChallengeCondition} condition The condition to add.
* @returns {Challenge} This challenge
*/
condition(condition: ChallengeCondition): Challenge {
this.conditions.push(condition);
return this;
}
/**
* If this challenge is of a particular type
* @param {ChallengeType} challengeType The challenge type to check.
* @returns {Challenge} This challenge
*/
isOfType(challengeType: ChallengeType): boolean {
return this.challengeTypes.some(c => c === challengeType);
}
/**
* Adds a challenge type to this challenge.
* @param {ChallengeType} challengeType The challenge type to add.
* @returns {Challenge} This challenge
*/
addChallengeType(challengeType: ChallengeType): Challenge {
this.challengeTypes.push(challengeType);
return this;
}
/**
* @returns {string} The localised name of this challenge.
*/
getName(): string {
return i18next.t(`challenges:${this.geti18nKey()}.name`);
}
/**
* Returns the textual representation of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised name for the current value.
*/
getValue(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return i18next.t(`challenges:${this.geti18nKey()}.value.${this.value}`);
}
/**
* Returns the description of a challenge's current value.
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
* @returns {string} The localised description for the current value.
*/
getDescription(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return i18next.t(`challenges:${this.geti18nKey()}.desc.${this.value}`);
}
/**
* Increase the value of the challenge
* @returns {boolean} Returns true if the value changed
*/
increaseValue(): boolean {
if (this.value < this.maxValue) {
this.value = Math.min(this.value + 1, this.maxValue);
return true;
}
return false;
}
/**
* Decrease the value of the challenge
* @returns {boolean} Returns true if the value changed
*/
decreaseValue(): boolean {
if (this.value > 0) {
this.value = Math.max(this.value - 1, 0);
return true;
}
return false;
}
/**
* Whether to allow choosing this challenge's severity.
*/
hasSeverity(): boolean {
return this.value !== 0 && this.maxSeverity > 0;
}
/**
* Decrease the severity of the challenge
* @returns {boolean} Returns true if the value changed
*/
decreaseSeverity(): boolean {
if (this.severity > 0) {
this.severity = Math.max(this.severity - 1, 0);
return true;
}
return false;
}
/**
* Increase the severity of the challenge
* @returns {boolean} Returns true if the value changed
*/
increaseSeverity(): boolean {
if (this.severity < this.maxSeverity) {
this.severity = Math.min(this.severity + 1, this.maxSeverity);
return true;
}
return false;
}
/**
* Gets the "difficulty" value of this challenge.
* @returns {integer} The difficulty value.
*/
getDifficulty(): integer {
return this.value;
}
/**
* Gets the minimum difficulty added by this challenge.
* @returns {integer} The difficulty value.
*/
getMinDifficulty(): integer {
return 0;
}
/**
* Modifies the data or game state in some way to apply the challenge.
* @param {ChallengeType} challengeType Which challenge type this is being applied for.
* @param args Irrelevant. See the specific challenge's apply function for additional information.
*/
abstract apply(challengeType: ChallengeType, args: any[]): boolean;
/**
* Clones a challenge, either from another challenge or json. Chainable.
* @param {Challenge | any} source The source challenge of json.
* @returns {Challenge} This challenge.
*/
static loadChallenge(source: Challenge | any): Challenge {
throw new Error("Method not implemented! Use derived class");
}
}
type ChallengeCondition = (data: GameData) => boolean;
/**
* Implements a mono generation challenge.
*/
export class SingleGenerationChallenge extends Challenge {
constructor() {
super(Challenges.SINGLE_GENERATION, 9);
this.addChallengeType(ChallengeType.STARTER_CHOICE);
this.addChallengeType(ChallengeType.POKEMON_IN_BATTLE);
this.addChallengeType(ChallengeType.FIXED_BATTLES);
}
apply(challengeType: ChallengeType, args: any[]): boolean {
if (this.value === 0) {
return false;
}
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
const species = args[0] as PokemonSpecies;
const isValidStarter = args[1] as Utils.BooleanHolder;
if (species.generation !== this.value) {
isValidStarter.value = false;
return true;
}
break;
case ChallengeType.POKEMON_IN_BATTLE:
const pokemon = args[0] as Pokemon;
const isValidPokemon = args[1] as Utils.BooleanHolder;
if (pokemon.isPlayer() && ((pokemon.species.formIndex === 0 ? pokemon.species : getPokemonSpecies(pokemon.species.speciesId)).generation !== this.value || (pokemon.isFusion() && (pokemon.fusionSpecies.formIndex === 0 ? pokemon.fusionSpecies : getPokemonSpecies(pokemon.fusionSpecies.speciesId)).generation !== this.value))) {
isValidPokemon.value = false;
return true;
}
break;
case ChallengeType.FIXED_BATTLES:
const waveIndex = args[0] as integer;
const battleConfig = args[1] as FixedBattleConfig;
let trainerTypes: TrainerType[] = [];
switch (waveIndex) {
case 182:
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]),TrainerType.MARNIE_ELITE, TrainerType.RIKA ];
break;
case 184:
trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ];
break;
case 186:
trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([TrainerType.BEA_ELITE,TrainerType.ALLISTER_ELITE]), TrainerType.LARRY_ELITE ];
break;
case 188:
trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ];
break;
case 190:
trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ];
break;
}
if (trainerTypes.length === 0) {
return false;
} else {
battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(scene => new Trainer(scene, trainerTypes[this.value - 1], TrainerVariant.DEFAULT));
return true;
}
}
return false;
}
/**
* @overrides
*/
getDifficulty(): number {
return this.value > 0 ? 1 : 0;
}
static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge {
const newChallenge = new SingleGenerationChallenge();
newChallenge.value = source.value;
newChallenge.severity = source.severity;
return newChallenge;
}
}
interface monotypeOverride {
/** The species to override */
species: Species;
/** The type to count as */
type: Type;
/** If part of a fusion, should we check the fused species instead of the base species? */
fusion: boolean;
}
/**
* Implements a mono type challenge.
*/
export class SingleTypeChallenge extends Challenge {
private static TYPE_OVERRIDES: monotypeOverride[] = [
{species: Species.MELOETTA, type: Type.PSYCHIC, fusion: true},
{species: Species.CASTFORM, type: Type.NORMAL, fusion: false},
];
constructor() {
super(Challenges.SINGLE_TYPE, 18);
this.addChallengeType(ChallengeType.STARTER_CHOICE);
this.addChallengeType(ChallengeType.POKEMON_IN_BATTLE);
}
apply(challengeType: ChallengeType, args: any[]): boolean {
if (this.value === 0) {
return false;
}
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
const species = args[0] as PokemonSpecies;
const isValidStarter = args[1] as Utils.BooleanHolder;
if (!species.isOfType(this.value - 1)) {
isValidStarter.value = false;
return true;
}
break;
case ChallengeType.POKEMON_IN_BATTLE:
const pokemon = args[0] as Pokemon;
const isValidPokemon = args[1] as Utils.BooleanHolder;
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true)
&& !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies : pokemon.species).speciesId === o.species)) {
isValidPokemon.value = false;
return true;
}
break;
}
return false;
}
/**
* @overrides
*/
getDifficulty(): number {
return this.value > 0 ? 1 : 0;
}
static loadChallenge(source: SingleTypeChallenge | any): SingleTypeChallenge {
const newChallenge = new SingleTypeChallenge();
newChallenge.value = source.value;
newChallenge.severity = source.severity;
return newChallenge;
}
}
/**
* Implements a fresh start challenge.
*/
export class FreshStartChallenge extends Challenge {
constructor() {
super(Challenges.FRESH_START, 1);
this.addChallengeType(ChallengeType.STARTER_CHOICE);
this.addChallengeType(ChallengeType.STARTER_MODIFY);
}
apply(challengeType: ChallengeType, args: any[]): boolean {
if (this.value === 0) {
return false;
}
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
const species = args[0] as PokemonSpecies;
const isValidStarter = args[1] as Utils.BooleanHolder;
if (species) {
isValidStarter.value = false;
return true;
}
break;
}
return false;
}
/**
* @overrides
*/
getDifficulty(): number {
return 0;
}
static loadChallenge(source: FreshStartChallenge | any): FreshStartChallenge {
const newChallenge = new FreshStartChallenge();
newChallenge.value = source.value;
newChallenge.severity = source.severity;
return newChallenge;
}
}
/**
* Lowers the amount of starter points available.
*/
export class LowerStarterMaxCostChallenge extends Challenge {
constructor() {
super(Challenges.LOWER_MAX_STARTER_COST, 9);
this.addChallengeType(ChallengeType.STARTER_CHOICE);
}
/**
* @override
*/
getValue(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return (10 - overrideValue).toString();
}
apply(challengeType: ChallengeType, args: any[]): boolean {
if (this.value === 0) {
return false;
}
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
const species = args[0] as PokemonSpecies;
const isValid = args[1] as Utils.BooleanHolder;
if (speciesStarters[species.speciesId] > 10 - this.value) {
isValid.value = false;
return true;
}
}
return false;
}
static loadChallenge(source: LowerStarterMaxCostChallenge | any): LowerStarterMaxCostChallenge {
const newChallenge = new LowerStarterMaxCostChallenge();
newChallenge.value = source.value;
newChallenge.severity = source.severity;
return newChallenge;
}
}
/**
* Lowers the maximum cost of starters available.
*/
export class LowerStarterPointsChallenge extends Challenge {
constructor() {
super(Challenges.LOWER_STARTER_POINTS, 9);
this.addChallengeType(ChallengeType.STARTER_POINTS);
}
/**
* @override
*/
getValue(overrideValue?: integer): string {
if (overrideValue === undefined) {
overrideValue = this.value;
}
return (10 - overrideValue).toString();
}
apply(challengeType: ChallengeType, args: any[]): boolean {
if (this.value === 0) {
return false;
}
switch (challengeType) {
case ChallengeType.STARTER_POINTS:
const points = args[0] as Utils.NumberHolder;
points.value -= this.value;
return true;
}
return false;
}
static loadChallenge(source: LowerStarterPointsChallenge | any): LowerStarterPointsChallenge {
const newChallenge = new LowerStarterPointsChallenge();
newChallenge.value = source.value;
newChallenge.severity = source.severity;
return newChallenge;
}
}
/**
* Apply all challenges of a given challenge type.
* @param {BattleScene} scene The current scene
* @param {ChallengeType} challengeType What challenge type to apply
* @param {any[]} args Any args for that challenge type
* @returns {boolean} True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false;
gameMode.challenges.forEach(v => {
if (v.isOfType(challengeType)) {
ret ||= v.apply(challengeType, args);
}
});
return ret;
}
export function copyChallenge(source: Challenge | any): Challenge {
switch (source.id) {
case Challenges.SINGLE_GENERATION:
return SingleGenerationChallenge.loadChallenge(source);
case Challenges.SINGLE_TYPE:
return SingleTypeChallenge.loadChallenge(source);
case Challenges.LOWER_MAX_STARTER_COST:
return LowerStarterMaxCostChallenge.loadChallenge(source);
case Challenges.LOWER_STARTER_POINTS:
return LowerStarterPointsChallenge.loadChallenge(source);
}
throw new Error("Unknown challenge copied");
}
export const allChallenges: Challenge[] = [];
export function initChallenges() {
allChallenges.push(
new SingleGenerationChallenge(),
new SingleTypeChallenge(),
// new LowerStarterMaxCostChallenge(),
// new LowerStarterPointsChallenge(),
// new FreshStartChallenge()
);
}

View File

@ -1,6 +1,5 @@
import BattleScene from "../battle-scene";
import { PlayerPokemon } from "../field/pokemon";
import { GameModes, gameModes } from "../game-mode";
import { Starter } from "../ui/starter-select-ui-handler";
import * as Utils from "../utils";
import { Species } from "./enums/species";
@ -29,7 +28,7 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[]
const starters: Starter[] = [];
scene.executeWithSeedOffset(() => {
const startingLevel = gameModes[GameModes.DAILY].getStartingLevel();
const startingLevel = scene.gameMode.getStartingLevel();
if (/\d{18}$/.test(seed)) {
for (let s = 0; s < 3; s++) {

View File

@ -0,0 +1,7 @@
export enum Challenges {
SINGLE_GENERATION,
SINGLE_TYPE,
LOWER_MAX_STARTER_COST,
LOWER_STARTER_POINTS,
FRESH_START
}

View File

@ -531,6 +531,11 @@ export class EvolutionPhase extends Phase {
}
export class EndEvolutionPhase extends Phase {
constructor(scene: BattleScene) {
super(scene);
}
start() {
super.start();

View File

@ -48,6 +48,7 @@ import { BerryType } from "../data/enums/berry-type";
import i18next from "../plugins/i18n";
import { speciesEggMoves } from "../data/egg-moves";
import { ModifierTier } from "../modifier/modifier-tier";
import { applyChallenges, ChallengeType } from "#app/data/challenge.js";
export enum FieldPosition {
CENTER,
@ -266,11 +267,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return !this.hp && (!checkStatus || this.status?.effect === StatusEffect.FAINT);
}
/**
* Check if this pokemon is both not fainted and allowed to be in battle.
* This is frequently a better alternative to {@link isFainted}
* @returns {boolean} True if pokemon is allowed in battle
*/
isAllowedInBattle(): boolean {
const challengeAllowed = new Utils.BooleanHolder(true);
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed);
return !this.isFainted() && challengeAllowed.value;
}
isActive(onField?: boolean): boolean {
if (!this.scene) {
return false;
}
return !this.isFainted() && !!this.scene && (!onField || this.isOnField());
return this.isAllowedInBattle() && !!this.scene && (!onField || this.isOnField());
}
getDexAttr(): bigint {
@ -885,8 +897,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return types;
}
isOfType(type: Type, forDefend: boolean = false): boolean {
return !!this.getTypes(true, forDefend).find(t => t === type);
isOfType(type: Type, includeTeraType: boolean = true, forDefend: boolean = false, ignoreOverride?: boolean): boolean {
return !!this.getTypes(includeTeraType, forDefend, ignoreOverride).some(t => t === type);
}
/**
@ -1054,7 +1066,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
isGrounded(): boolean {
return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE);
return !this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE);
}
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): TypeDamageMultiplier {

View File

@ -1,4 +1,4 @@
import { fixedBattles } from "./battle";
import { classicFixedBattles, FixedBattleConfig, FixedBattleConfigs } from "./battle";
import BattleScene from "./battle-scene";
import { Biome } from "./data/enums/biome";
import { Species } from "./data/enums/species";
@ -6,12 +6,14 @@ import PokemonSpecies, { allSpecies } from "./data/pokemon-species";
import { Arena } from "./field/arena";
import * as Utils from "./utils";
import * as Overrides from "./overrides";
import { allChallenges, applyChallenges, Challenge, ChallengeType, copyChallenge } from "./data/challenge";
export enum GameModes {
CLASSIC,
ENDLESS,
SPLICED_ENDLESS,
DAILY
DAILY,
CHALLENGE
}
interface GameModeConfig {
@ -19,12 +21,12 @@ interface GameModeConfig {
isEndless?: boolean;
isDaily?: boolean;
hasTrainers?: boolean;
hasFixedBattles?: boolean;
hasNoShop?: boolean;
hasShortBiomes?: boolean;
hasRandomBiomes?: boolean;
hasRandomBosses?: boolean;
isSplicedOnly?: boolean;
isChallenge?: boolean;
}
export class GameMode implements GameModeConfig {
@ -33,16 +35,23 @@ export class GameMode implements GameModeConfig {
public isEndless: boolean;
public isDaily: boolean;
public hasTrainers: boolean;
public hasFixedBattles: boolean;
public hasNoShop: boolean;
public hasShortBiomes: boolean;
public hasRandomBiomes: boolean;
public hasRandomBosses: boolean;
public isSplicedOnly: boolean;
public isChallenge: boolean;
public challenges: Challenge[];
public battleConfig: FixedBattleConfigs;
constructor(modeId: GameModes, config: GameModeConfig) {
constructor(modeId: GameModes, config: GameModeConfig, battleConfig?: FixedBattleConfigs) {
this.modeId = modeId;
this.challenges = [];
Object.assign(this, config);
if (this.isChallenge) {
this.challenges = allChallenges.map(c => copyChallenge(c));
}
this.battleConfig = battleConfig || {};
}
/**
@ -112,7 +121,7 @@ export class GameMode implements GameModeConfig {
if (w === waveIndex) {
continue;
}
if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || fixedBattles.hasOwnProperty(w)) {
if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(waveIndex)) {
allowTrainerBattle = false;
break;
} else if (w < waveIndex) {
@ -161,6 +170,7 @@ export class GameMode implements GameModeConfig {
isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean {
switch (modeId) {
case GameModes.CLASSIC:
case GameModes.CHALLENGE:
return waveIndex === 200;
case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS:
@ -208,10 +218,36 @@ export class GameMode implements GameModeConfig {
(this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
}
/**
* Checks whether there is a fixed battle on this gamemode on a given wave.
* @param {integer} waveIndex The wave to check.
* @returns {boolean} If this game mode has a fixed battle on this wave
*/
isFixedBattle(waveIndex: integer): boolean {
const dummyConfig = new FixedBattleConfig();
return this.battleConfig.hasOwnProperty(waveIndex) || applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, dummyConfig);
}
/**
* Returns the config for the fixed battle for a particular wave.
* @param {integer} waveIndex The wave to check.
* @returns {boolean} The fixed battle for this wave.
*/
getFixedBattle(waveIndex: integer): FixedBattleConfig {
const challengeConfig = new FixedBattleConfig();
if (applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, challengeConfig)) {
return challengeConfig;
} else {
return this.battleConfig[waveIndex];
}
}
getClearScoreBonus(): integer {
switch (this.modeId) {
case GameModes.CLASSIC:
case GameModes.CHALLENGE:
return 5000;
case GameModes.DAILY:
return 2500;
@ -221,6 +257,7 @@ export class GameMode implements GameModeConfig {
getEnemyModifierChance(isBoss: boolean): integer {
switch (this.modeId) {
case GameModes.CLASSIC:
case GameModes.CHALLENGE:
case GameModes.DAILY:
return !isBoss ? 18 : 6;
case GameModes.ENDLESS:
@ -239,13 +276,38 @@ export class GameMode implements GameModeConfig {
return "Endless (Spliced)";
case GameModes.DAILY:
return "Daily Run";
case GameModes.CHALLENGE:
return "Challenge";
}
}
static getModeName(modeId: GameModes): string {
switch (modeId) {
case GameModes.CLASSIC:
return "Classic";
case GameModes.ENDLESS:
return "Endless";
case GameModes.SPLICED_ENDLESS:
return "Endless (Spliced)";
case GameModes.DAILY:
return "Daily Run";
case GameModes.CHALLENGE:
return "Challenge";
}
}
}
export const gameModes = Object.freeze({
[GameModes.CLASSIC]: new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasFixedBattles: true }),
[GameModes.ENDLESS]: new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }),
[GameModes.SPLICED_ENDLESS]: new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }),
[GameModes.DAILY]: new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true })
});
export function getGameMode(gameMode: GameModes): GameMode {
switch (gameMode) {
case GameModes.CLASSIC:
return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true }, classicFixedBattles);
case GameModes.ENDLESS:
return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true });
case GameModes.SPLICED_ENDLESS:
return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true });
case GameModes.DAILY:
return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true });
case GameModes.CHALLENGE:
return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true }, classicFixedBattles);
}
}

View File

@ -18,6 +18,7 @@ import {initMoves} from "#app/data/move";
import {initAbilities} from "#app/data/ability";
import {initAchievements} from "#app/system/achv";
import {initTrainerTypeDialogue} from "#app/data/dialogue";
import { initChallenges } from "./data/challenge";
import i18next from "i18next";
import { initStatsKeys } from "./ui/game-stats-ui-handler";
import { initVouchers } from "./system/voucher";
@ -341,6 +342,7 @@ export class LoadingScene extends SceneBase {
initSpecies();
initMoves();
initAbilities();
initChallenges();
}
loadLoadingScreen() {

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const deConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -168,4 +168,99 @@ export const achv: AchievementTranslationEntries = {
name: "Undefeated",
description: "Beat the game in classic mode",
},
"MONO_GEN_ONE": {
name: "The Original Rival",
description: "Complete the generation one only challenge.",
},
"MONO_GEN_TWO": {
name: "Generation 1.5",
description: "Complete the generation two only challenge.",
},
"MONO_GEN_THREE": {
name: "Too much water?",
description: "Complete the generation three only challenge.",
},
"MONO_GEN_FOUR": {
name: "Is she really the hardest?",
description: "Complete the generation four only challenge.",
},
"MONO_GEN_FIVE": {
name: "All Original",
description: "Complete the generation five only challenge.",
},
"MONO_GEN_SIX": {
name: "Almost Royalty",
description: "Complete the generation six only challenge.",
},
"MONO_GEN_SEVEN": {
name: "Only Technically",
description: "Complete the generation seven only challenge.",
},
"MONO_GEN_EIGHT": {
name: "A Champion Time!",
description: "Complete the generation eight only challenge.",
},
"MONO_GEN_NINE": {
name: "She was going easy on you",
description: "Complete the generation nine only challenge.",
},
"MonoType": {
description: "Complete the {{type}} monotype challenge.",
},
"MONO_NORMAL": {
name: "Mono NORMAL",
},
"MONO_FIGHTING": {
name: "I Know Kung Fu",
},
"MONO_FLYING": {
name: "Mono FLYING",
},
"MONO_POISON": {
name: "Kanto's Favourite",
},
"MONO_GROUND": {
name: "Mono GROUND",
},
"MONO_ROCK": {
name: "Brock Hard",
},
"MONO_BUG": {
name: "Sting Like A Beedrill",
},
"MONO_GHOST": {
name: "Who you gonna call?",
},
"MONO_STEEL": {
name: "Mono STEEL",
},
"MONO_FIRE": {
name: "Mono FIRE",
},
"MONO_WATER": {
name: "When It Rains, It Pours",
},
"MONO_GRASS": {
name: "Mono GRASS",
},
"MONO_ELECTRIC": {
name: "Mono ELECTRIC",
},
"MONO_PSYCHIC": {
name: "Mono PSYCHIC",
},
"MONO_ICE": {
name: "Mono ICE",
},
"MONO_DRAGON": {
name: "Mono DRAGON",
},
"MONO_DARK": {
name: "It's just a phase",
},
"MONO_FAIRY": {
name: "Mono FAIRY",
},
} as const;

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"start": "Start",
"illegalEvolution": "{{pokemon}} changed into an ineligble pokemon\nfor this challenge!",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const enConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const esConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const frConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const itConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const koConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const ptBrConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const zhCnConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -0,0 +1,67 @@
import { SimpleTranslationEntries } from "#app/plugins/i18n";
export const challenges: SimpleTranslationEntries = {
"title": "Challenge Modifiers",
"points": "Bad Ideas",
"confirm_start": "Proceed with these challenges?",
"singleGeneration.name": "Mono Gen",
"singleGeneration.value.0": "Off",
"singleGeneration.desc.0": "You can only use pokemon from the chosen generation.",
"singleGeneration.value.1": "Gen 1",
"singleGeneration.desc.1": "You can only use pokemon from generation one.",
"singleGeneration.value.2": "Gen 2",
"singleGeneration.desc.2": "You can only use pokemon from generation two.",
"singleGeneration.value.3": "Gen 3",
"singleGeneration.desc.3": "You can only use pokemon from generation three.",
"singleGeneration.value.4": "Gen 4",
"singleGeneration.desc.4": "You can only use pokemon from generation four.",
"singleGeneration.value.5": "Gen 5",
"singleGeneration.desc.5": "You can only use pokemon from generation five.",
"singleGeneration.value.6": "Gen 6",
"singleGeneration.desc.6": "You can only use pokemon from generation six.",
"singleGeneration.value.7": "Gen 7",
"singleGeneration.desc.7": "You can only use pokemon from generation seven.",
"singleGeneration.value.8": "Gen 8",
"singleGeneration.desc.8": "You can only use pokemon from generation eight.",
"singleGeneration.value.9": "Gen 9",
"singleGeneration.desc.9": "You can only use pokemon from generation nine.",
"singleType.name": "Mono Type",
"singleType.value.0": "Off",
"singleType.desc.0": "You can only use pokemon of the chosen type.",
"singleType.value.1": "Normal",
"singleType.desc.1": "You can only use pokemon with the Normal type.",
"singleType.value.2": "Fighting",
"singleType.desc.2": "You can only use pokemon with the Fighting type.",
"singleType.value.3": "Flying",
"singleType.desc.3": "You can only use pokemon with the Flying type.",
"singleType.value.4": "Poison",
"singleType.desc.4": "You can only use pokemon with the Poison type.",
"singleType.value.5": "Ground",
"singleType.desc.5": "You can only use pokemon with the Ground type.",
"singleType.value.6": "Rock",
"singleType.desc.6": "You can only use pokemon with the Rock type.",
"singleType.value.7": "Bug",
"singleType.desc.7": "You can only use pokemon with the Bug type.",
"singleType.value.8": "Ghost",
"singleType.desc.8": "You can only use pokemon with the Ghost type.",
"singleType.value.9": "Steel",
"singleType.desc.9": "You can only use pokemon with the Steel type.",
"singleType.value.10": "Fire",
"singleType.desc.10": "You can only use pokemon with the Fire type.",
"singleType.value.11": "Water",
"singleType.desc.11": "You can only use pokemon with the Water type.",
"singleType.value.12": "Grass",
"singleType.desc.12": "You can only use pokemon with the Grass type.",
"singleType.value.13": "Electric",
"singleType.desc.13": "You can only use pokemon with the Electric type.",
"singleType.value.14": "Psychic",
"singleType.desc.14": "You can only use pokemon with the Psychic type.",
"singleType.value.15": "Ice",
"singleType.desc.15": "You can only use pokemon with the Ice type.",
"singleType.value.16": "Dragon",
"singleType.desc.16": "You can only use pokemon with the Dragon type.",
"singleType.value.17": "Dark",
"singleType.desc.17": "You can only use pokemon with the Dark type.",
"singleType.value.18": "Fairy",
"singleType.desc.18": "You can only use pokemon with the Fairy type.",
} as const;

View File

@ -5,6 +5,7 @@ import { battle } from "./battle";
import { battleMessageUiHandler } from "./battle-message-ui-handler";
import { berry } from "./berry";
import { biome } from "./biome";
import { challenges } from "./challenges";
import { commandUiHandler } from "./command-ui-handler";
import {
PGFbattleSpecDialogue,
@ -45,6 +46,7 @@ export const zhTwConfig = {
battleMessageUiHandler: battleMessageUiHandler,
berry: berry,
biome: biome,
challenges: challenges,
commandUiHandler: commandUiHandler,
PGMdialogue: PGMdialogue,
PGFdialogue: PGFdialogue,

View File

@ -36,7 +36,7 @@ import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
import { BattleSpec } from "./enums/battle-spec";
import { Species } from "./data/enums/species";
import { HealAchv, LevelAchv, achvs } from "./system/achv";
import { ChallengeAchv, HealAchv, LevelAchv, achvs } from "./system/achv";
import { TrainerSlot, trainerConfigs } from "./data/trainer-config";
import { TrainerType } from "./data/enums/trainer-type";
import { EggHatchPhase } from "./egg-hatch-phase";
@ -55,7 +55,7 @@ import { TerrainType } from "./data/terrain";
import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select-ui-handler";
import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler";
import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run";
import { GameModes, gameModes } from "./game-mode";
import { GameMode, GameModes, getGameMode } from "./game-mode";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species";
import i18next from "./plugins/i18n";
import { Abilities } from "./data/enums/abilities";
@ -202,14 +202,21 @@ export class TitlePhase extends Phase {
if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) {
const options: OptionSelectItem[] = [
{
label: gameModes[GameModes.CLASSIC].getName(),
label: GameMode.getModeName(GameModes.CLASSIC),
handler: () => {
setModeAndEnd(GameModes.CLASSIC);
return true;
}
},
{
label: gameModes[GameModes.ENDLESS].getName(),
label: GameMode.getModeName(GameModes.CHALLENGE),
handler: () => {
setModeAndEnd(GameModes.CHALLENGE);
return true;
}
},
{
label: GameMode.getModeName(GameModes.ENDLESS),
handler: () => {
setModeAndEnd(GameModes.ENDLESS);
return true;
@ -218,7 +225,7 @@ export class TitlePhase extends Phase {
];
if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) {
options.push({
label: gameModes[GameModes.SPLICED_ENDLESS].getName(),
label: GameMode.getModeName(GameModes.SPLICED_ENDLESS),
handler: () => {
setModeAndEnd(GameModes.SPLICED_ENDLESS);
return true;
@ -307,7 +314,7 @@ export class TitlePhase extends Phase {
this.scene.sessionSlotId = slotId;
const generateDaily = (seed: string) => {
this.scene.gameMode = gameModes[GameModes.DAILY];
this.scene.gameMode = getGameMode(GameModes.DAILY);
this.scene.setSeed(seed);
this.scene.resetSeed(1);
@ -369,7 +376,12 @@ export class TitlePhase extends Phase {
end(): void {
if (!this.loaded && !this.scene.gameMode.isDaily) {
this.scene.arena.preloadBgm();
this.scene.pushPhase(new SelectStarterPhase(this.scene, this.gameMode));
this.scene.gameMode = getGameMode(this.gameMode);
if (this.gameMode === GameModes.CHALLENGE) {
this.scene.pushPhase(new SelectChallengePhase(this.scene));
} else {
this.scene.pushPhase(new SelectStarterPhase(this.scene));
}
this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene));
} else {
this.scene.playBgm();
@ -378,7 +390,7 @@ export class TitlePhase extends Phase {
this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded));
if (this.loaded) {
const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()).length;
const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length;
this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true));
if (this.scene.currentBattle.double && availablePartyMembers > 1) {
@ -504,13 +516,24 @@ export class SelectGenderPhase extends Phase {
}
}
export class SelectStarterPhase extends Phase {
private gameMode: GameModes;
constructor(scene: BattleScene, gameMode: GameModes) {
export class SelectChallengePhase extends Phase {
constructor(scene: BattleScene) {
super(scene);
}
this.gameMode = gameMode;
start() {
super.start();
this.scene.playBgm("menu");
this.scene.ui.setMode(Mode.CHALLENGE_SELECT);
}
}
export class SelectStarterPhase extends Phase {
constructor(scene: BattleScene) {
super(scene);
}
start() {
@ -529,7 +552,7 @@ export class SelectStarterPhase extends Phase {
this.scene.sessionSlotId = slotId;
this.initBattle(starters);
});
}, this.gameMode);
});
}
initBattle(starters: Starter[]) {
@ -1002,7 +1025,7 @@ export class EncounterPhase extends BattlePhase {
}
if (!this.loaded) {
const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted());
const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle());
if (!availablePartyMembers[0].isOnField()) {
this.scene.pushPhase(new SummonPhase(this.scene, 0));
@ -1295,20 +1318,29 @@ export class SummonPhase extends PartyMemberPokemonPhase {
*/
preSummon(): void {
const partyMember = this.getPokemon();
// If the Pokemon about to be sent out is fainted, switch to the first non-fainted Pokemon
if (partyMember.isFainted()) {
console.warn("The Pokemon about to be sent out is fainted. Attempting to resolve...");
// If the Pokemon about to be sent out is fainted or illegal under a challenge, switch to the first non-fainted legal Pokemon
if (!partyMember.isAllowedInBattle()) {
console.warn("The Pokemon about to be sent out is fainted or illegal under a challenge. Attempting to resolve...");
// First check if they're somehow still in play, if so remove them.
if (partyMember.isOnField()) {
partyMember.hideInfo();
partyMember.setVisible(false);
this.scene.field.remove(partyMember);
this.scene.triggerPokemonFormChange(partyMember, SpeciesFormChangeActiveTrigger, true);
}
const party = this.getParty();
// Find the first non-fainted Pokemon index above the current one
const nonFaintedIndex = party.findIndex((p, i) => i > this.partyMemberIndex && !p.isFainted());
if (nonFaintedIndex === -1) {
const legalIndex = party.findIndex((p, i) => i > this.partyMemberIndex && p.isAllowedInBattle());
if (legalIndex === -1) {
console.error("Party Details:\n", party);
throw new Error("All available Pokemon were fainted!");
throw new Error("All available Pokemon were fainted or illegal!");
}
// Swaps the fainted Pokemon and the first non-fainted Pokemon in the party
[party[this.partyMemberIndex], party[nonFaintedIndex]] = [party[nonFaintedIndex], party[this.partyMemberIndex]];
// Swaps the fainted Pokemon and the first non-fainted legal Pokemon in the party
[party[this.partyMemberIndex], party[legalIndex]] = [party[legalIndex], party[this.partyMemberIndex]];
console.warn("Swapped %s %O with %s %O", partyMember?.name, partyMember, party[0]?.name, party[0]);
}
@ -1352,7 +1384,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
if (this.fieldIndex === 1) {
pokemon.setFieldPosition(FieldPosition.RIGHT, 0);
} else {
const availablePartyMembers = this.getParty().filter(p => !p.isFainted()).length;
const availablePartyMembers = this.getParty().filter(p => p.isAllowedInBattle()).length;
pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT);
}
@ -1642,7 +1674,7 @@ export class ToggleDoublePositionPhase extends BattlePhase {
const playerPokemon = this.scene.getPlayerField().find(p => p.isActive(true));
if (playerPokemon) {
playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => !p.isFainted()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => {
playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => {
if (playerPokemon.getFieldIndex() === 1) {
const party = this.scene.getParty();
party[1] = party[0];
@ -1742,6 +1774,34 @@ export class TurnInitPhase extends FieldPhase {
start() {
super.start();
this.scene.getPlayerField().forEach(p => {
// If this pokemon is in play and evolved into something illegal under the current challenge, force a switch
if (p.isOnField() && !p.isAllowedInBattle()) {
this.scene.queueMessage(i18next.t("challenges:illegalEvolution", {"pokemon": p.name}), null, true);
const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle());
if (!allowedPokemon.length) {
// If there are no longer any legal pokemon in the party, game over.
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new GameOverPhase(this.scene));
} else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) {
// If there is at least one pokemon in the back that is legal to switch in, force a switch.
p.switchOut(false, true);
} else {
// If there are no pokemon in the back but we're not game overing, just hide the pokemon.
// This should only happen in double battles.
p.hideInfo();
p.setVisible(false);
this.scene.field.remove(p);
this.scene.triggerPokemonFormChange(p, SpeciesFormChangeActiveTrigger, true);
}
if (allowedPokemon.length === 1 && this.scene.currentBattle.double) {
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
}
}
});
//this.scene.pushPhase(new MoveAnimTestPhase(this.scene));
this.scene.eventTarget.dispatchEvent(new TurnInitEvent());
@ -1776,9 +1836,15 @@ export class CommandPhase extends FieldPhase {
super.start();
if (this.fieldIndex) {
const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1];
if (allyCommand.command === Command.BALL || allyCommand.command === Command.RUN) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand.command, skip: true };
// If we somehow are attempting to check the right pokemon but there's only one pokemon out
// Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching
if (this.scene.getPlayerField().filter(p => p.isActive()).length === 1) {
this.fieldIndex = FieldPosition.CENTER;
} else {
const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1];
if (allyCommand.command === Command.BALL || allyCommand.command === Command.RUN) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand.command, skip: true };
}
}
}
@ -2353,7 +2419,7 @@ export class BattleEndPhase extends BattlePhase {
}
}
for (const pokemon of this.scene.getParty().filter(p => !p.isFainted())) {
for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) {
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon);
}
@ -3486,7 +3552,7 @@ export class DamagePhase extends PokemonPhase {
this.scene.setFieldScale(0.75);
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
this.scene.currentBattle.double = true;
const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted());
const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle());
if (availablePartyMembers.length > 1) {
this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true));
if (!availablePartyMembers[1].isOnField()) {
@ -3568,11 +3634,11 @@ export class FaintPhase extends PokemonPhase {
}
if (this.player) {
const nonFaintedPartyMembers = this.scene.getParty().filter(p => !p.isFainted());
const nonFaintedPartyMemberCount = nonFaintedPartyMembers.length;
const nonFaintedLegalPartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle());
const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length;
if (!nonFaintedPartyMemberCount) {
this.scene.unshiftPhase(new GameOverPhase(this.scene));
} else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedPartyMembers[0].isActive(true))) {
} else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedLegalPartyMembers[0].isActive(true))) {
this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false));
}
if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) {
@ -3966,7 +4032,7 @@ export class GameOverPhase extends BattlePhase {
this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => {
this.scene.pushPhase(new EncounterPhase(this.scene, true));
const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()).length;
const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length;
this.scene.pushPhase(new SummonPhase(this.scene, 0));
if (this.scene.currentBattle.double && availablePartyMembers > 1) {
@ -4018,6 +4084,10 @@ export class GameOverPhase extends BattlePhase {
this.scene.clearPhaseQueue();
this.scene.ui.clearText();
if (this.victory && this.scene.gameMode.isChallenge) {
this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c));
}
const clear = (endCardPhase?: EndCardPhase) => {
if (newClear) {
this.handleUnlocks();
@ -4221,17 +4291,17 @@ export class SwitchPhase extends BattlePhase {
super.start();
// Skip modal switch if impossible
if (this.isModal && !this.scene.getParty().filter(p => !p.isFainted() && !p.isActive(true)).length) {
if (this.isModal && !this.scene.getParty().filter(p => !p.isAllowedInBattle() && !p.isActive(true)).length) {
return super.end();
}
// Check if there is any space still in field
if (this.isModal && this.scene.getPlayerField().filter(p => !p.isFainted() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) {
if (this.isModal && this.scene.getPlayerField().filter(p => !p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) {
return super.end();
}
// Override field index to 0 in case of double battle where 2/3 remaining party members fainted at once
const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => !p.isFainted()).length > 1 ? this.fieldIndex : 0;
// Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once
const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0;
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
@ -4917,7 +4987,8 @@ export class SelectModifierPhase extends BattlePhase {
let cost: integer;
switch (rowCursor) {
case 0:
if (!cursor) {
switch (cursor) {
case 0:
const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers);
if (this.scene.money < rerollCost) {
this.scene.ui.playError();
@ -4932,18 +5003,25 @@ export class SelectModifierPhase extends BattlePhase {
this.scene.animateMoneyChanged(false);
this.scene.playSound("buy");
}
} else if (cursor === 1) {
break;
case 1:
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => {
if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) {
const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
&& (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex];
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
} else {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
}
}, PartyUiHandler.FilterItemMaxStacks);
} else {
break;
case 2:
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
});
break;
case 3:
this.scene.lockModifierTiers = !this.scene.lockModifierTiers;
const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers));

View File

@ -222,6 +222,7 @@ declare module "i18next" {
berry: BerryTranslationEntries;
achv: AchievementTranslationEntries;
gameStatsUiHandler: SimpleTranslationEntries;
challenges: SimpleTranslationEntries;
voucher: SimpleTranslationEntries;
biome: SimpleTranslationEntries;
pokemonInfoContainer: SimpleTranslationEntries;

View File

@ -3,6 +3,7 @@ import BattleScene from "../battle-scene";
import { TurnHeldItemTransferModifier } from "../modifier/modifier";
import i18next from "../plugins/i18n";
import * as Utils from "../utils";
import { Challenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js";
export enum AchvTier {
COMMON,
@ -126,6 +127,12 @@ export class ModifierAchv extends Achv {
}
}
export class ChallengeAchv extends Achv {
constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, challengeFunc: (challenge: Challenge) => boolean) {
super(localizationKey, name, description, iconImage, score, (_scene: BattleScene, args: any[]) => challengeFunc((args[0] as Challenge)));
}
}
/**
* Get the description of an achievement from the localization file with all the necessary variables filled in
@ -214,6 +221,43 @@ export function getAchievementDescription(localizationKey: string): string {
return i18next.t("achv:PERFECT_IVS.description");
case "CLASSIC_VICTORY":
return i18next.t("achv:CLASSIC_VICTORY.description");
case "MONO_GEN_ONE":
return i18next.t("achv:MONO_GEN_ONE.description");
case "MONO_GEN_TWO":
return i18next.t("achv:MONO_GEN_TWO.description");
case "MONO_GEN_THREE":
return i18next.t("achv:MONO_GEN_THREE.description");
case "MONO_GEN_FOUR":
return i18next.t("achv:MONO_GEN_FOUR.description");
case "MONO_GEN_FIVE":
return i18next.t("achv:MONO_GEN_FIVE.description");
case "MONO_GEN_SIX":
return i18next.t("achv:MONO_GEN_SIX.description");
case "MONO_GEN_SEVEN":
return i18next.t("achv:MONO_GEN_SEVEN.description");
case "MONO_GEN_EIGHT":
return i18next.t("achv:MONO_GEN_EIGHT.description");
case "MONO_GEN_NINE":
return i18next.t("achv:MONO_GEN_NINE.description");
case "MONO_NORMAL":
case "MONO_FIGHTING":
case "MONO_FLYING":
case "MONO_POISON":
case "MONO_GROUND":
case "MONO_ROCK":
case "MONO_BUG":
case "MONO_GHOST":
case "MONO_STEEL":
case "MONO_FIRE":
case "MONO_WATER":
case "MONO_GRASS":
case "MONO_ELECTRIC":
case "MONO_PSYCHIC":
case "MONO_ICE":
case "MONO_DRAGON":
case "MONO_DARK":
case "MONO_FAIRY":
return i18next.t("achv:MonoType.description", {"type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`)});
default:
return "";
}
@ -261,6 +305,33 @@ export const achvs = {
HIDDEN_ABILITY: new Achv("HIDDEN_ABILITY","", "HIDDEN_ABILITY.description", "ability_charm", 75),
PERFECT_IVS: new Achv("PERFECT_IVS","", "PERFECT_IVS.description", "blunder_policy", 100),
CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY","", "CLASSIC_VICTORY.description", "relic_crown", 150),
MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE","", "MONO_GEN_ONE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 1),
MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO","", "MONO_GEN_TWO.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 2),
MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE","", "MONO_GEN_THREE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 3),
MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR","", "MONO_GEN_FOUR.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 4),
MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE","", "MONO_GEN_FIVE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 5),
MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX","", "MONO_GEN_SIX.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 6),
MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN","", "MONO_GEN_SEVEN.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 7),
MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT","", "MONO_GEN_EIGHT.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 8),
MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE","", "MONO_GEN_NINE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 9),
MONO_NORMAL: new ChallengeAchv("MONO_NORMAL","", "MONO_NORMAL.description", "silk_scarf", 100, c => c instanceof SingleTypeChallenge && c.value === 1),
MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING","", "MONO_FIGHTING.description", "black_belt", 100, c => c instanceof SingleTypeChallenge && c.value === 2),
MONO_FLYING: new ChallengeAchv("MONO_FLYING","", "MONO_FLYING.description", "sharp_beak", 100, c => c instanceof SingleTypeChallenge && c.value === 3),
MONO_POISON: new ChallengeAchv("MONO_POISON","", "MONO_POISON.description", "poison_barb", 100, c => c instanceof SingleTypeChallenge && c.value === 4),
MONO_GROUND: new ChallengeAchv("MONO_GROUND","", "MONO_GROUND.description", "soft_sand", 100, c => c instanceof SingleTypeChallenge && c.value === 5),
MONO_ROCK: new ChallengeAchv("MONO_ROCK","", "MONO_ROCK.description", "hard_stone", 100, c => c instanceof SingleTypeChallenge && c.value === 6),
MONO_BUG: new ChallengeAchv("MONO_BUG","", "MONO_BUG.description", "silver_powder", 100, c => c instanceof SingleTypeChallenge && c.value === 7),
MONO_GHOST: new ChallengeAchv("MONO_GHOST","", "MONO_GHOST.description", "spell_tag", 100, c => c instanceof SingleTypeChallenge && c.value === 8),
MONO_STEEL: new ChallengeAchv("MONO_STEEL","", "MONO_STEEL.description", "metal_coat", 100, c => c instanceof SingleTypeChallenge && c.value === 9),
MONO_FIRE: new ChallengeAchv("MONO_FIRE","", "MONO_FIRE.description", "charcoal", 100, c => c instanceof SingleTypeChallenge && c.value === 10),
MONO_WATER: new ChallengeAchv("MONO_WATER","", "MONO_WATER.description", "mystic_water", 100, c => c instanceof SingleTypeChallenge && c.value === 11),
MONO_GRASS: new ChallengeAchv("MONO_GRASS","", "MONO_GRASS.description", "miracle_seed", 100, c => c instanceof SingleTypeChallenge && c.value === 12),
MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC","", "MONO_ELECTRIC.description", "magnet", 100, c => c instanceof SingleTypeChallenge && c.value === 13),
MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC","", "MONO_PSYCHIC.description", "twisted_spoon", 100, c => c instanceof SingleTypeChallenge && c.value === 14),
MONO_ICE: new ChallengeAchv("MONO_ICE","", "MONO_ICE.description", "never_melt_ice", 100, c => c instanceof SingleTypeChallenge && c.value === 15),
MONO_DRAGON: new ChallengeAchv("MONO_DRAGON","", "MONO_DRAGON.description", "dragon_fang", 100, c => c instanceof SingleTypeChallenge && c.value === 16),
MONO_DARK: new ChallengeAchv("MONO_DARK","", "MONO_DARK.description", "black_glasses", 100, c => c instanceof SingleTypeChallenge && c.value === 17),
MONO_FAIRY: new ChallengeAchv("MONO_FAIRY","", "MONO_FAIRY.description", "fairy_feather", 100, c => c instanceof SingleTypeChallenge && c.value === 18),
};
export function initAchievements() {

View File

@ -0,0 +1,17 @@
import { Challenge, copyChallenge } from "#app/data/challenge.js";
export default class ChallengeData {
public id: integer;
public value: integer;
public severity: integer;
constructor(source: Challenge | any) {
this.id = source.id;
this.value = source.value;
this.severity = source.severity;
}
toChallenge(): Challenge {
return copyChallenge(this);
}
}

View File

@ -9,7 +9,7 @@ import PokemonData from "./pokemon-data";
import PersistentModifierData from "./modifier-data";
import ArenaData from "./arena-data";
import { Unlockables } from "./unlockables";
import { GameModes, gameModes } from "../game-mode";
import { GameModes, getGameMode } from "../game-mode";
import { BattleType } from "../battle";
import TrainerData from "./trainer-data";
import { trainerConfigs } from "../data/trainer-config";
@ -38,6 +38,7 @@ import { EnemyAttackStatusEffectChanceModifier } from "../modifier/modifier";
import { StatusEffect } from "#app/data/status-effect.js";
import { PlayerGender } from "#app/data/enums/player-gender";
import { GameDataType } from "#app/data/enums/game-data-type";
import ChallengeData from "./challenge-data";
const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
@ -107,6 +108,7 @@ export interface SessionSaveData {
trainer: TrainerData;
gameVersion: string;
timestamp: integer;
challenges: ChallengeData[];
}
interface Unlocks {
@ -780,7 +782,8 @@ export class GameData {
battleType: scene.currentBattle.battleType,
trainer: scene.currentBattle.battleType === BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null,
gameVersion: scene.game.config.gameVersion,
timestamp: new Date().getTime()
timestamp: new Date().getTime(),
challenges: scene.gameMode.challenges.map(c => new ChallengeData(c))
} as SessionSaveData;
}
@ -829,7 +832,10 @@ export class GameData {
const initSessionFromData = async sessionData => {
console.debug(sessionData);
scene.gameMode = gameModes[sessionData.gameMode || GameModes.CLASSIC];
scene.gameMode = getGameMode(sessionData.gameMode || GameModes.CLASSIC);
if (sessionData.challenges) {
scene.gameMode.challenges = sessionData.challenges.map(c => c.toChallenge());
}
scene.setSeed(sessionData.seed || scene.game.config.seed[0]);
scene.resetSeed();
@ -1075,6 +1081,17 @@ export class GameData {
return new ArenaData(v);
}
if (k === "challenges") {
const ret: ChallengeData[] = [];
if (v === null) {
v = [];
}
for (const c of v) {
ret.push(new ChallengeData(c));
}
return ret;
}
return v;
}) as SessionSaveData;
}

View File

@ -1,4 +1,4 @@
import { GameModes, gameModes } from "../game-mode";
import { GameMode, GameModes } from "../game-mode";
export enum Unlockables {
ENDLESS_MODE,
@ -9,10 +9,10 @@ export enum Unlockables {
export function getUnlockableName(unlockable: Unlockables) {
switch (unlockable) {
case Unlockables.ENDLESS_MODE:
return `${gameModes[GameModes.ENDLESS].getName()} Mode`;
return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`;
case Unlockables.MINI_BLACK_HOLE:
return "Mini Black Hole";
case Unlockables.SPLICED_ENDLESS_MODE:
return `${gameModes[GameModes.SPLICED_ENDLESS].getName()} Mode`;
return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`;
}
}

View File

@ -35,6 +35,7 @@ import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {allSpecies} from "#app/data/pokemon-species";
import {PlayerGender} from "#app/data/enums/player-gender";
import { getGameMode } from "#app/game-mode.js";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;
@ -229,8 +230,9 @@ describe("Test Battle Phase", () => {
await game.phaseInterceptor.mustRun(SelectGenderPhase).catch((error) => expect(error).toBe(SelectGenderPhase));
await game.phaseInterceptor.mustRun(TitlePhase).catch((error) => expect(error).toBe(TitlePhase));
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(game.scene);
const selectStarterPhase = new SelectStarterPhase(game.scene, GameModes.CLASSIC);
const selectStarterPhase = new SelectStarterPhase(game.scene);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
});

View File

@ -17,7 +17,7 @@ import BattleScene from "#app/battle-scene.js";
import PhaseInterceptor from "#app/test/utils/phaseInterceptor";
import TextInterceptor from "#app/test/utils/TextInterceptor";
import {expect} from "vitest";
import {GameModes} from "#app/game-mode";
import {GameModes, getGameMode} from "#app/game-mode";
import fs from "fs";
import { AES, enc } from "crypto-js";
import {updateUserInfo} from "#app/account";
@ -121,8 +121,9 @@ export default class GameManager {
return new Promise(async(resolve) => {
await this.runToTitle();
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
this.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(this.scene, species);
const selectStarterPhase = new SelectStarterPhase(this.scene, GameModes.CLASSIC);
const selectStarterPhase = new SelectStarterPhase(this.scene);
this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters);
});

View File

@ -3,7 +3,7 @@ import {getDailyRunStarters} from "#app/data/daily-run";
import {Gender} from "#app/data/gender";
import {Species} from "#app/data/enums/species";
import {Starter} from "#app/ui/starter-select-ui-handler";
import {GameModes, gameModes} from "#app/game-mode";
import {GameModes, getGameMode} from "#app/game-mode";
import {getPokemonSpecies, getPokemonSpeciesForm} from "#app/data/pokemon-species";
import {PlayerPokemon} from "#app/field/pokemon";
@ -49,7 +49,7 @@ function getTestRunStarters(scene, seed, species) {
return getDailyRunStarters(scene, seed);
}
const starters: Starter[] = [];
const startingLevel = gameModes[GameModes.CLASSIC].getStartingLevel();
const startingLevel = getGameMode(GameModes.CLASSIC).getStartingLevel();
for (const specie of species) {
const starterSpeciesForm = getPokemonSpeciesForm(specie, 0);

View File

@ -0,0 +1,330 @@
import BattleScene from "../battle-scene";
import { TextStyle, addTextObject } from "./text";
import { Mode } from "./ui";
import UiHandler from "./ui-handler";
import { addWindow } from "./ui-theme";
import {Button} from "../enums/buttons";
import i18next from "#app/plugins/i18n.js";
import { SelectStarterPhase, TitlePhase } from "#app/phases.js";
import { Challenge } from "#app/data/challenge.js";
/**
* Handles all the UI for choosing optional challenges.
*/
export default class GameChallengesUiHandler extends UiHandler {
private challengesContainer: Phaser.GameObjects.Container;
private valuesContainer: Phaser.GameObjects.Container;
private scrollCursor: integer;
private optionsBg: Phaser.GameObjects.NineSlice;
// private difficultyText: Phaser.GameObjects.Text;
private descriptionText: Phaser.GameObjects.Text;
private challengeLabels: Phaser.GameObjects.Text[];
private challengeValueLabels: Phaser.GameObjects.Text[];
private cursorObj: Phaser.GameObjects.NineSlice;
private startCursor: Phaser.GameObjects.NineSlice;
constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode);
}
setup() {
const ui = this.getUi();
this.challengesContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1);
this.challengesContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
// TODO: Change this back to /9 when adding in difficulty
const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6), 24);
headerBg.setOrigin(0, 0);
const headerText = addTextObject(this.scene, 0, 0, i18next.t("challenges:title"), TextStyle.SETTINGS_LABEL);
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
// const difficultyBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 18) - 2, 24);
// difficultyBg.setOrigin(0, 0);
// difficultyBg.setPositionRelative(headerBg, headerBg.width, 0);
// this.difficultyText = addTextObject(this.scene, 0, 0, "0", TextStyle.SETTINGS_LABEL);
// this.difficultyText.setOrigin(0, 0);
// this.difficultyText.setPositionRelative(difficultyBg, 8, 4);
// const difficultyName = addTextObject(this.scene, 0, 0, i18next.t("challenges:points"), TextStyle.SETTINGS_LABEL);
// difficultyName.setOrigin(0, 0);
// difficultyName.setPositionRelative(difficultyBg, difficultyBg.width - difficultyName.displayWidth - 8, 4);
this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 9), (this.scene.game.canvas.height / 6) - headerBg.height - 2);
this.optionsBg.setOrigin(0, 0);
const descriptionBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 18) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 26);
descriptionBg.setOrigin(0, 0);
descriptionBg.setPositionRelative(this.optionsBg, this.optionsBg.width, 0);
this.descriptionText = addTextObject(this.scene, 0, 0, "", TextStyle.SETTINGS_LABEL);
this.descriptionText.setOrigin(0, 0);
this.descriptionText.setWordWrapWidth(500, true);
this.descriptionText.setPositionRelative(descriptionBg, 6, 4);
const startBg = addWindow(this.scene, 0, 0, descriptionBg.width, 24);
startBg.setOrigin(0, 0);
startBg.setPositionRelative(descriptionBg, 0, descriptionBg.height);
const startText = addTextObject(this.scene, 0, 0, i18next.t("challenges:start"), TextStyle.SETTINGS_LABEL);
startText.setOrigin(0, 0);
startText.setPositionRelative(startBg, 8, 4);
this.startCursor = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 18) - 10, 16, 1, 1, 1, 1);
this.startCursor.setOrigin(0, 0);
this.startCursor.setPositionRelative(startBg, 4, 4);
this.startCursor.setVisible(false);
this.valuesContainer = this.scene.add.container(0, 0);
this.challengeLabels = [];
this.challengeValueLabels = [];
for (let i = 0; i < 9; i++) {
this.challengeLabels[i] = addTextObject(this.scene, 8, 28 + i * 16, "", TextStyle.SETTINGS_LABEL);
this.challengeLabels[i].setOrigin(0, 0);
this.valuesContainer.add(this.challengeLabels[i]);
this.challengeValueLabels[i] = addTextObject(this.scene, 0, 28 + i * 16, "", TextStyle.SETTINGS_LABEL);
this.challengeValueLabels[i].setPositionRelative(this.challengeLabels[i], 100, 0);
this.valuesContainer.add(this.challengeValueLabels[i]);
}
this.challengesContainer.add(headerBg);
this.challengesContainer.add(headerText);
// this.challengesContainer.add(difficultyBg);
// this.challengesContainer.add(this.difficultyText);
// this.challengesContainer.add(difficultyName);
this.challengesContainer.add(this.optionsBg);
this.challengesContainer.add(descriptionBg);
this.challengesContainer.add(this.descriptionText);
this.challengesContainer.add(startBg);
this.challengesContainer.add(startText);
this.challengesContainer.add(this.startCursor);
this.challengesContainer.add(this.valuesContainer);
ui.add(this.challengesContainer);
this.setCursor(0);
this.setScrollCursor(0);
this.challengesContainer.setVisible(false);
}
updateText(): void {
if (this.scene.gameMode.challenges.length > 0) {
this.descriptionText.text = this.getActiveChallenge().getDescription();
this.descriptionText.updateText();
}
// const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0);
// const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0);
// this.difficultyText.text = `${totalDifficulty}` + (totalMinDifficulty ? `/${totalMinDifficulty}` : "");
// this.difficultyText.updateText();
for (let i = 0; i < this.challengeLabels.length; i++) {
if (i + this.scrollCursor < this.scene.gameMode.challenges.length) {
this.challengeLabels[i].setVisible(true);
this.challengeValueLabels[i].setVisible(true);
this.challengeLabels[i].text = this.scene.gameMode.challenges[i + this.scrollCursor].getName();
this.challengeValueLabels[i].text = this.scene.gameMode.challenges[i + this.scrollCursor].getValue();
this.challengeLabels[i].updateText();
this.challengeValueLabels[i].updateText();
} else {
this.challengeLabels[i].setVisible(false);
this.challengeValueLabels[i].setVisible(false);
}
}
}
show(args: any[]): boolean {
super.show(args);
this.startCursor.setVisible(false);
this.challengesContainer.setVisible(true);
this.setCursor(0);
this.updateText();
this.getUi().moveTo(this.challengesContainer, this.getUi().length - 1);
this.getUi().hideTooltip();
return true;
}
/**
* Processes input from a specified button.
* This method handles navigation through a UI menu, including movement through menu items
* and handling special actions like cancellation. Each button press may adjust the cursor
* position or the menu scroll, and plays a sound effect if the action was successful.
*
* @param button - The button pressed by the user.
* @returns `true` if the action associated with the button was successfully processed, `false` otherwise.
*/
processInput(button: Button): boolean {
const ui = this.getUi();
// Defines the maximum number of rows that can be displayed on the screen.
const rowsToDisplay = 9;
let success = false;
if (button === Button.CANCEL) {
if (this.startCursor.visible) {
this.startCursor.setVisible(false);
this.cursorObj?.setVisible(true);
} else {
this.scene.clearPhaseQueue();
this.scene.pushPhase(new TitlePhase(this.scene));
this.scene.getCurrentPhase().end();
}
success = true;
} else if (button === Button.SUBMIT || button === Button.ACTION) {
if (this.startCursor.visible) {
const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0);
const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0);
if (totalDifficulty >= totalMinDifficulty) {
this.scene.unshiftPhase(new SelectStarterPhase(this.scene));
this.scene.getCurrentPhase().end();
success = true;
} else {
success = false;
}
} else {
this.startCursor.setVisible(true);
this.cursorObj?.setVisible(false);
success = true;
}
} else {
switch (button) {
case Button.UP:
if (this.cursor === 0) {
if (this.scrollCursor === 0) {
// When at the top of the menu and pressing UP, move to the bottommost item.
if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom
// First, set the cursor to the last visible element, preparing for the scroll to the end.
const successA = this.setCursor(rowsToDisplay - 1);
// Then, adjust the scroll to display the bottommost elements of the menu.
const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay);
success = successA && successB; // success is just there to play the little validation sound effect
} else { // If there are 9 or less challenges, just move to the bottom one
success = this.setCursor(this.scene.gameMode.challenges.length - 1);
}
} else {
success = this.setScrollCursor(this.scrollCursor - 1);
}
} else {
success = this.setCursor(this.cursor - 1);
}
if (success) {
this.updateText();
}
break;
case Button.DOWN:
if (this.cursor === rowsToDisplay - 1) {
if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) {
// When at the bottom and pressing DOWN, scroll if possible.
success = this.setScrollCursor(this.scrollCursor + 1);
} else {
// When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item.
// First, set the cursor to the first visible element, preparing for the scroll to the top.
const successA = this.setCursor(0);
// Then, adjust the scroll to display the topmost elements of the menu.
const successB = this.setScrollCursor(0);
success = successA && successB; // success is just there to play the little validation sound effect
}
} else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) {
// When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item.
success = this.setCursor(0);
} else {
success = this.setCursor(this.cursor + 1);
}
if (success) {
this.updateText();
}
break;
case Button.LEFT:
// Moves the option cursor left, if possible.
success = this.getActiveChallenge().decreaseValue();
if (success) {
this.updateText();
}
break;
case Button.RIGHT:
// Moves the option cursor right, if possible.
success = this.getActiveChallenge().increaseValue();
if (success) {
this.updateText();
}
break;
}
}
// Plays a select sound effect if an action was successfully processed.
if (success) {
ui.playSelect();
}
return success;
}
setCursor(cursor: integer): boolean {
let ret = super.setCursor(cursor);
if (!this.cursorObj) {
this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 9) - 10, 16, 1, 1, 1, 1);
this.cursorObj.setOrigin(0, 0);
this.valuesContainer.add(this.cursorObj);
}
ret ||= !this.cursorObj.visible;
this.cursorObj.setVisible(true);
this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16);
return ret;
}
setScrollCursor(scrollCursor: integer): boolean {
if (scrollCursor === this.scrollCursor) {
return false;
}
this.scrollCursor = scrollCursor;
this.setCursor(this.cursor);
return true;
}
getActiveChallenge(): Challenge {
return this.scene.gameMode.challenges[this.cursor + this.scrollCursor];
}
clear() {
super.clear();
this.challengesContainer.setVisible(false);
this.eraseCursor();
}
eraseCursor() {
if (this.cursorObj) {
this.cursorObj.destroy();
}
this.cursorObj = null;
}
}

View File

@ -18,6 +18,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
private rerollButtonContainer: Phaser.GameObjects.Container;
private lockRarityButtonContainer: Phaser.GameObjects.Container;
private transferButtonContainer: Phaser.GameObjects.Container;
private checkButtonContainer: Phaser.GameObjects.Container;
private rerollCostText: Phaser.GameObjects.Text;
private lockRarityButtonText: Phaser.GameObjects.Text;
private moveInfoOverlay : MoveInfoOverlay;
@ -45,7 +46,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.modifierContainer = this.scene.add.container(0, 0);
ui.add(this.modifierContainer);
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -64);
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 71, -64);
this.transferButtonContainer.setName("container-transfer-btn");
this.transferButtonContainer.setVisible(false);
ui.add(this.transferButtonContainer);
@ -55,6 +56,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
transferButtonText.setOrigin(1, 0);
this.transferButtonContainer.add(transferButtonText);
this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -64);
this.checkButtonContainer.setName("container-use-btn");
this.checkButtonContainer.setVisible(false);
ui.add(this.checkButtonContainer);
const checkButtonText = addTextObject(this.scene, -4, -2, "Check Team", TextStyle.PARTY);
checkButtonText.setName("text-use-btn");
checkButtonText.setOrigin(1, 0);
this.checkButtonContainer.add(checkButtonText);
this.rerollButtonContainer = this.scene.add.container(16, -64);
this.rerollButtonContainer.setName("container-reroll-brn");
this.rerollButtonContainer.setVisible(false);
@ -121,6 +132,9 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.transferButtonContainer.setVisible(false);
this.transferButtonContainer.setAlpha(0);
this.checkButtonContainer.setVisible(false);
this.checkButtonContainer.setAlpha(0);
this.rerollButtonContainer.setVisible(false);
this.rerollButtonContainer.setAlpha(0);
@ -204,12 +218,14 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
}
this.rerollButtonContainer.setAlpha(0);
this.checkButtonContainer.setAlpha(0);
this.lockRarityButtonContainer.setAlpha(0);
this.rerollButtonContainer.setVisible(true);
this.checkButtonContainer.setVisible(true);
this.lockRarityButtonContainer.setVisible(canLockRarities);
this.scene.tweens.add({
targets: [ this.rerollButtonContainer, this.lockRarityButtonContainer ],
targets: [ this.rerollButtonContainer, this.lockRarityButtonContainer, this.checkButtonContainer ],
alpha: 1,
duration: 250
});
@ -267,7 +283,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
} else {
switch (button) {
case Button.UP:
if (!this.rowCursor && this.cursor === 2) {
if (this.rowCursor === 0 && this.cursor === 3) {
success = this.setCursor(0);
} else if (this.rowCursor < this.shopOptionsRows.length + 1) {
success = this.setRowCursor(this.rowCursor + 1);
@ -276,13 +292,29 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
case Button.DOWN:
if (this.rowCursor) {
success = this.setRowCursor(this.rowCursor - 1);
} else if (this.lockRarityButtonContainer.visible && !this.cursor) {
success = this.setCursor(2);
} else if (this.lockRarityButtonContainer.visible && this.cursor === 0) {
success = this.setCursor(3);
}
break;
case Button.LEFT:
if (!this.rowCursor) {
success = this.cursor === 1 && this.rerollButtonContainer.visible && this.setCursor(0);
switch (this.cursor) {
case 0:
success = false;
break;
case 1:
success = this.rerollButtonContainer.visible && this.setCursor(0);
break;
case 2:
if (this.transferButtonContainer.visible) {
success = this.setCursor(1);
} else if (this.rerollButtonContainer.visible) {
success = this.setCursor(0);
} else {
success = false;
}
break;
}
} else if (this.cursor) {
success = this.setCursor(this.cursor - 1);
} else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) {
@ -291,7 +323,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
break;
case Button.RIGHT:
if (!this.rowCursor) {
success = this.cursor !== 1 && this.transferButtonContainer.visible && this.setCursor(1);
switch (this.cursor) {
case 0:
if (this.transferButtonContainer.visible) {
success = this.setCursor(1);
} else {
success = this.setCursor(2);
}
break;
case 1:
success = this.setCursor(2);
break;
case 2:
success = false;
break;
}
} else if (this.cursor < this.getRowItems(this.rowCursor) - 1) {
success = this.setCursor(this.cursor + 1);
} else if (this.rowCursor === 1 && this.transferButtonContainer.visible) {
@ -337,12 +383,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
// prepare the move overlay to be shown with the toggle
this.moveInfoOverlay.show(allMoves[type.moveId]);
}
} else if (!cursor) {
} else if (cursor === 0) {
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60);
ui.showText("Spend money to reroll your item options.");
} else if (cursor === 1) {
this.cursorObj.setPosition((this.scene.game.canvas.width / 6) - 50, -60);
this.cursorObj.setPosition((this.scene.game.canvas.width / 6) - 120, -60);
ui.showText("Transfer a held item from one Pokémon to another.");
} else if (cursor === 2) {
this.cursorObj.setPosition((this.scene.game.canvas.width / 6) - 60, -60);
ui.showText("Check your team or use a form changing item.");
} else {
this.cursorObj.setPosition(6, -60);
ui.showText("Lock item rarities on reroll (affects reroll cost).");
@ -354,14 +403,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
setRowCursor(rowCursor: integer): boolean {
const lastRowCursor = this.rowCursor;
if (rowCursor !== lastRowCursor && (rowCursor || this.rerollButtonContainer.visible || this.transferButtonContainer.visible)) {
if (rowCursor !== lastRowCursor) {
this.rowCursor = rowCursor;
let newCursor = Math.round(this.cursor / Math.max(this.getRowItems(lastRowCursor) - 1, 1) * (this.getRowItems(rowCursor) - 1));
if (!rowCursor) {
if (!newCursor && !this.rerollButtonContainer.visible) {
if (rowCursor === 0) {
if (newCursor === 0 && !this.rerollButtonContainer.visible) {
newCursor = 1;
} else if (newCursor && !this.transferButtonContainer.visible) {
newCursor = 0;
}
if (newCursor === 1 && !this.transferButtonContainer.visible) {
newCursor = 2;
}
}
this.cursor = -1;
@ -375,7 +425,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
private getRowItems(rowCursor: integer): integer {
switch (rowCursor) {
case 0:
return 2;
return 3;
case 1:
return this.options.length;
default:
@ -437,7 +487,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
onComplete: () => options.forEach(o => o.destroy())
});
[ this.rerollButtonContainer, this.transferButtonContainer, this.lockRarityButtonContainer ].forEach(container => {
[ this.rerollButtonContainer, this.checkButtonContainer, this.transferButtonContainer, this.lockRarityButtonContainer ].forEach(container => {
if (container.visible) {
this.scene.tweens.add({
targets: container,

View File

@ -1,4 +1,4 @@
import { CommandPhase } from "../phases";
import { CommandPhase, SelectModifierPhase } from "../phases";
import BattleScene from "../battle-scene";
import { PlayerPokemon, PokemonMove } from "../field/pokemon";
import { addTextObject, TextStyle } from "./text";
@ -17,6 +17,7 @@ import { addWindow } from "./ui-theme";
import { SpeciesFormChangeItemTrigger } from "../data/pokemon-forms";
import { getVariantTint } from "#app/data/variant";
import {Button} from "../enums/buttons";
import { applyChallenges, ChallengeType } from "#app/data/challenge.js";
import MoveInfoOverlay from "./move-info-overlay";
import i18next from "i18next";
@ -33,7 +34,8 @@ export enum PartyUiMode {
REMEMBER_MOVE_MODIFIER,
MODIFIER_TRANSFER,
SPLICE,
RELEASE
RELEASE,
CHECK
}
export enum PartyOption {
@ -120,6 +122,20 @@ export default class PartyUiHandler extends MessageUiHandler {
return null;
};
/**
* For consistency reasons, this looks like the above filters. However this is used only internally and is always enforced for switching.
* @param pokemon The pokemon to check.
* @returns
*/
private FilterChallengeLegal = (pokemon: PlayerPokemon) => {
const challengeAllowed = new Utils.BooleanHolder(true);
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed);
if (!challengeAllowed.value) {
return `${pokemon.name} can't be used in\nthis challenge!`;
}
return null;
};
private static FilterAllMoves = (_pokemonMove: PokemonMove) => null;
public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => {
@ -280,6 +296,9 @@ export default class PartyUiHandler extends MessageUiHandler {
let filterResult: string;
if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) {
filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon);
if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) {
filterResult = this.FilterChallengeLegal(pokemon);
}
if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) {
filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]);
}
@ -293,7 +312,7 @@ export default class PartyUiHandler extends MessageUiHandler {
if (this.partyUiMode !== PartyUiMode.SPLICE) {
this.clearOptions();
}
if (this.selectCallback) {
if (this.selectCallback && this.partyUiMode !== PartyUiMode.CHECK) {
if (option === PartyOption.TRANSFER) {
if (this.transferCursor !== this.cursor) {
(this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor);
@ -315,11 +334,8 @@ export default class PartyUiHandler extends MessageUiHandler {
selectCallback(this.cursor, option);
}
} else {
if (option >= PartyOption.FORM_CHANGE_ITEM && this.scene.getCurrentPhase() instanceof CommandPhase) {
switch (this.partyUiMode) {
case PartyUiMode.SWITCH:
case PartyUiMode.FAINT_SWITCH:
case PartyUiMode.POST_BATTLE_SWITCH:
if (option >= PartyOption.FORM_CHANGE_ITEM && this.scene.getCurrentPhase() instanceof SelectModifierPhase) {
if (this.partyUiMode === PartyUiMode.CHECK) {
let formChangeItemModifiers = this.scene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[];
if (formChangeItemModifiers.find(m => m.active)) {
formChangeItemModifiers = formChangeItemModifiers.filter(m => m.active);
@ -327,7 +343,6 @@ export default class PartyUiHandler extends MessageUiHandler {
const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM];
modifier.active = !modifier.active;
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true);
break;
}
} else if (this.cursor) {
(this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, this.cursor, option === PartyOption.PASS_BATON);
@ -690,15 +705,6 @@ export default class PartyUiHandler extends MessageUiHandler {
this.options.push(PartyOption.PASS_BATON);
}
}
if (this.scene.getCurrentPhase() instanceof CommandPhase) {
formChangeItemModifiers = this.scene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[];
if (formChangeItemModifiers.find(m => m.active)) {
formChangeItemModifiers = formChangeItemModifiers.filter(m => m.active);
}
for (let i = 0; i < formChangeItemModifiers.length; i++) {
this.options.push(PartyOption.FORM_CHANGE_ITEM + i);
}
}
break;
case PartyUiMode.REVIVAL_BLESSING:
this.options.push(PartyOption.REVIVE);
@ -724,6 +730,17 @@ export default class PartyUiHandler extends MessageUiHandler {
case PartyUiMode.RELEASE:
this.options.push(PartyOption.RELEASE);
break;
case PartyUiMode.CHECK:
if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) {
formChangeItemModifiers = this.scene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[];
if (formChangeItemModifiers.find(m => m.active)) {
formChangeItemModifiers = formChangeItemModifiers.filter(m => m.active);
}
for (let i = 0; i < formChangeItemModifiers.length; i++) {
this.options.push(PartyOption.FORM_CHANGE_ITEM + i);
}
}
break;
}
this.options.push(PartyOption.SUMMARY);

View File

@ -1,5 +1,5 @@
import BattleScene from "../battle-scene";
import { gameModes } from "../game-mode";
import { GameMode } from "../game-mode";
import { SessionSaveData } from "../system/game-data";
import { TextStyle, addTextObject } from "./text";
import { Mode } from "./ui";
@ -266,7 +266,7 @@ class SessionSlot extends Phaser.GameObjects.Container {
async setupWithData(data: SessionSaveData) {
this.remove(this.loadingLabel, true);
const gameModeLabel = addTextObject(this.scene, 8, 5, `${gameModes[data.gameMode]?.getName() || "Unknown"} - Wave ${data.waveIndex}`, TextStyle.WINDOW);
const gameModeLabel = addTextObject(this.scene, 8, 5, `${GameMode.getModeName(data.gameMode) || "Unknown"} - Wave ${data.waveIndex}`, TextStyle.WINDOW);
this.add(gameModeLabel);
const timestampLabel = addTextObject(this.scene, 8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW);

View File

@ -18,8 +18,8 @@ import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "../
import PokemonSpecies, { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import { Type } from "../data/type";
import { Button } from "../enums/buttons";
import { GameModes, gameModes } from "../game-mode";
import { TitlePhase } from "../phases";
import { GameModes } from "../game-mode";
import { SelectChallengePhase, TitlePhase } from "../phases";
import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterFormMoveData, StarterMoveset } from "../system/game-data";
import { Passive as PassiveAttr } from "#app/data/enums/passive";
import { Tutorial, handleTutorial } from "../tutorial";
@ -31,6 +31,7 @@ import { StatsContainer } from "./stats-container";
import { TextStyle, addBBCodeTextObject, addTextObject } from "./text";
import { Mode } from "./ui";
import { addWindow } from "./ui-theme";
import * as Challenge from "../data/challenge";
import MoveInfoOverlay from "./move-info-overlay";
export type StarterSelectCallback = (starters: Starter[]) => void;
@ -245,7 +246,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private iconAnimHandler: PokemonIconAnimHandler;
private starterSelectCallback: StarterSelectCallback;
private gameMode: GameModes;
protected blockInput: boolean = false;
@ -724,14 +724,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
show(args: any[]): boolean {
this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers
if (args.length >= 2 && args[0] instanceof Function && typeof args[1] === "number") {
if (args.length >= 1 && args[0] instanceof Function) {
super.show(args);
this.starterSelectCallback = args[0] as StarterSelectCallback;
this.starterSelectContainer.setVisible(true);
this.gameMode = args[1];
this.setGenMode(false);
this.setCursor(0);
this.setGenMode(true);
@ -957,7 +955,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} else {
this.blockInput = true;
this.scene.clearPhaseQueue();
this.scene.pushPhase(new TitlePhase(this.scene));
if (this.scene.gameMode.isChallenge) {
this.scene.pushPhase(new SelectChallengePhase(this.scene));
} else {
this.scene.pushPhase(new TitlePhase(this.scene));
}
this.scene.getCurrentPhase().end();
success = true;
}
@ -1029,7 +1031,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
}
const species = this.genSpecies[this.getGenCursorWithScroll()][this.cursor];
if (!isDupe && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId))) {
const isValidForChallenge = new Utils.BooleanHolder(true);
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge);
if (!isDupe && isValidForChallenge.value && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId))) {
const cursorObj = this.starterCursorObjs[this.starterCursors.length];
cursorObj.setVisible(true);
cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y);
@ -1521,13 +1527,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
getValueLimit(): integer {
switch (this.gameMode) {
const valueLimit = new Utils.IntegerHolder(0);
switch (this.scene.gameMode.modeId) {
case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS:
return 15;
valueLimit.value = 15;
default:
return 10;
valueLimit.value = 10;
}
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit);
return valueLimit.value;
}
setCursor(cursor: integer): boolean {
@ -2160,20 +2171,25 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const speciesSprite = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite;
/**
* If remainValue greater than or equal pokemon species, the user can select.
* If remainValue greater than or equal pokemon species and the pokemon is legal for this challenge, the user can select.
* so that the alpha value of pokemon sprite set 1.
*
* If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater.
* we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too.
*/
if (remainValue >= speciesStarterValue) {
const isValidForChallenge = new Utils.BooleanHolder(true);
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[g][s], isValidForChallenge);
const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge.value;
if (canBeChosen) {
speciesSprite.setAlpha(1);
if (speciesStarterDexEntry?.caughtAttr) {
this.canAddParty = true;
}
} else {
/**
* If remainValue less than pokemon, the use can't select.
* If it can't be chosen, the user can't select.
* so that the alpha value of pokemon sprite set 0.375.
*/
speciesSprite.setAlpha(0.375);
@ -2202,8 +2218,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
ui.showText(i18next.t("starterSelectUiHandler:confirmStartTeam"), null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => {
const startRun = (gameMode: GameModes) => {
this.scene.gameMode = gameModes[gameMode];
const startRun = () => {
this.scene.money = this.scene.gameMode.getStartingMoney();
ui.setMode(Mode.STARTER_SELECT);
const thisObj = this;
@ -2222,7 +2237,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
};
}));
};
startRun(this.gameMode);
startRun();
}, cancel, null, null, 19);
});

View File

@ -3,12 +3,19 @@ import { TextStyle, getTextColor } from "./text";
import { Mode } from "./ui";
import {Button} from "../enums/buttons";
/**
* A basic abstract class to act as a holder and processor for UI elements.
*/
export default abstract class UiHandler {
protected scene: BattleScene;
protected mode: integer;
protected cursor: integer = 0;
public active: boolean = false;
/**
* @param {BattleScene} scene The same scene as everything else.
* @param {Mode} mode The mode of the UI element. These should be unique.
*/
constructor(scene: BattleScene, mode: Mode) {
this.scene = scene;
this.mode = mode;

View File

@ -14,6 +14,7 @@ import EvolutionSceneHandler from "./evolution-scene-handler";
import TargetSelectUiHandler from "./target-select-ui-handler";
import SettingsUiHandler from "./settings/settings-ui-handler";
import SettingsGamepadUiHandler from "./settings/settings-gamepad-ui-handler";
import GameChallengesUiHandler from "./challenges-select-ui-handler";
import { TextStyle, addTextObject } from "./text";
import AchvBar from "./achv-bar";
import MenuUiHandler from "./menu-ui-handler";
@ -80,7 +81,8 @@ export enum Mode {
LOADING,
SESSION_RELOAD,
UNAVAILABLE,
OUTDATED
OUTDATED,
CHALLENGE_SELECT
}
const transitionModes = [
@ -91,7 +93,8 @@ const transitionModes = [
Mode.EVOLUTION_SCENE,
Mode.EGG_HATCH_SCENE,
Mode.EGG_LIST,
Mode.EGG_GACHA
Mode.EGG_GACHA,
Mode.CHALLENGE_SELECT
];
const noTransitionModes = [
@ -173,7 +176,8 @@ export default class UI extends Phaser.GameObjects.Container {
new LoadingModalUiHandler(scene),
new SessionReloadModalUiHandler(scene),
new UnavailableModalUiHandler(scene),
new OutdatedModalUiHandler(scene)
new OutdatedModalUiHandler(scene),
new GameChallengesUiHandler(scene)
];
}