Allow selecting and ordering starter moves

This commit is contained in:
Flashfyre 2024-02-21 01:03:34 -05:00
parent c3983fccb4
commit b054992ffa
6 changed files with 188 additions and 54 deletions

View File

@ -290,6 +290,7 @@ export class SelectStarterPhase extends BattlePhase {
: Gender.GENDERLESS;
const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = this.scene.addPlayerPokemon(starter.species, startingLevel, starterProps.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterIvs, starter.nature);
starterPokemon.tryPopulateMoveset(starter.moveset);
if (starter.pokerus)
starterPokemon.pokerus = true;
if (this.scene.gameMode === GameMode.SPLICED_ENDLESS)

View File

@ -1,7 +1,7 @@
import { Moves } from "./enums/moves";
import { Species } from "./enums/species";
export type LevelMoves = (integer | Moves)[][];
export type LevelMoves = ([integer, Moves])[];
interface PokemonSpeciesLevelMoves {
[key: integer]: LevelMoves

View File

@ -4,9 +4,10 @@ import { GrowthRate } from './exp';
import { SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from './pokemon-evolutions';
import { Species } from './enums/species';
import { Type } from './type';
import { LevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from './pokemon-level-moves';
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from './pokemon-level-moves';
import { uncatchableSpecies } from './biomes';
import * as Utils from '../utils';
import { StarterMoveset } from '../system/game-data';
export enum Region {
NORMAL,
@ -259,6 +260,18 @@ export abstract class PokemonSpeciesForm {
return ret;
}
validateStarterMoveset(moveset: StarterMoveset): boolean {
for (let moveId of moveset) {
if (pokemonFormLevelMoves.hasOwnProperty(this.speciesId) && pokemonFormLevelMoves[this.speciesId].hasOwnProperty(this.formIndex)) {
if (!pokemonFormLevelMoves[this.speciesId][this.formIndex].find(lm => lm[0] <= 5 && lm[1] === moveId))
return false;
} else if (!pokemonSpeciesLevelMoves[this.speciesId].find(lm => lm[0] <= 5 && lm[1] === moveId))
return false;
}
return true;
}
loadAssets(scene: BattleScene, female: boolean, formIndex?: integer, shiny?: boolean, startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
scene.load.audio(this.getCryKey(formIndex), `audio/cry/${this.getCryKey(formIndex)}.ogg`);

View File

@ -35,7 +35,7 @@ import SoundFade from 'phaser3-rex-plugins/plugins/soundfade';
import { GameMode } from './game-mode';
import { LevelMoves } from './data/pokemon-level-moves';
import { DamageAchv, achvs } from './system/achv';
import { DexAttr } from './system/game-data';
import { DexAttr, StarterMoveset } from './system/game-data';
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from '@material/material-color-utilities';
import { Nature, getNatureStatMultiplier } from './data/nature';
import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveUsedTrigger, SpeciesFormChangeStatusEffectTrigger } from './data/pokemon-forms';
@ -1924,6 +1924,15 @@ export class PlayerPokemon extends Pokemon {
}
}
tryPopulateMoveset(moveset: StarterMoveset): boolean {
if (!this.getSpeciesForm().validateStarterMoveset(moveset))
return false;
this.moveset = moveset.map(m => new PokemonMove(m));
return true;
}
switchOut(batonPass: boolean): Promise<void> {
return new Promise(resolve => {
this.resetTurnData();

View File

@ -24,6 +24,7 @@ import { Nature } from "../data/nature";
import { GameStats } from "./game-stats";
import { Tutorial } from "../tutorial";
import { BattleSpec } from "../enums/battle-spec";
import { Moves } from "../data/enums/moves";
const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary
@ -58,6 +59,7 @@ interface SystemSaveData {
secretId: integer;
gender: PlayerGender;
dexData: DexData;
starterMoveData: StarterMoveData;
gameStats: GameStats;
unlocks: Unlocks;
achvUnlocks: AchvUnlocks;
@ -134,6 +136,16 @@ export interface DexAttrProps {
formIndex: integer;
}
export type StarterMoveset = [ Moves ] | [ Moves, Moves ] | [ Moves, Moves, Moves ] | [ Moves, Moves, Moves, Moves ];
export interface StarterMoveData {
[key: integer]: StarterMoveset | StarterFormMoveData
}
export interface StarterFormMoveData {
[key: integer]: StarterMoveset
}
export interface TutorialFlags {
[key: string]: boolean
}
@ -158,6 +170,8 @@ export class GameData {
public dexData: DexData;
private defaultDexData: DexData;
public starterMoveData: StarterMoveData;
public gameStats: GameStats;
public unlocks: Unlocks;
@ -173,6 +187,7 @@ export class GameData {
this.loadSettings();
this.trainerId = Utils.randSeedInt(65536);
this.secretId = Utils.randSeedInt(65536);
this.starterMoveData = {};
this.gameStats = new GameStats();
this.unlocks = {
[Unlockables.ENDLESS_MODE]: false,
@ -204,6 +219,7 @@ export class GameData {
secretId: this.secretId,
gender: this.gender,
dexData: this.dexData,
starterMoveData: this.starterMoveData,
gameStats: this.gameStats,
unlocks: this.unlocks,
achvUnlocks: this.achvUnlocks,
@ -262,6 +278,8 @@ export class GameData {
this.saveSetting(Setting.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0);
this.starterMoveData = systemData.starterMoveData || {};
if (systemData.gameStats)
this.gameStats = systemData.gameStats;

View File

@ -9,7 +9,7 @@ import { allAbilities } from "../data/ability";
import { GameMode, gameModeNames } from "../game-mode";
import { Unlockables } from "../system/unlockables";
import { GrowthRate, getGrowthRateColor } from "../data/exp";
import { DexAttr, DexEntry } from "../system/game-data";
import { DexAttr, DexEntry, StarterFormMoveData, StarterMoveset } from "../system/game-data";
import * as Utils from "../utils";
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "../sprite/pokemon-icon-anim-handler";
import { StatsContainer } from "./stats-container";
@ -18,9 +18,10 @@ import { Nature, getNatureName } from "../data/nature";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import { pokemonFormChanges } from "../data/pokemon-forms";
import { Tutorial, handleTutorial } from "../tutorial";
import { pokemonSpeciesLevelMoves } from "../data/pokemon-level-moves";
import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "../data/pokemon-level-moves";
import { allMoves } from "../data/move";
import { Type } from "../data/type";
import { Moves } from "../data/enums/moves";
export type StarterSelectCallback = (starters: Starter[]) => void;
@ -28,6 +29,7 @@ export interface Starter {
species: PokemonSpecies;
dexAttr: bigint;
nature: Nature;
moveset: StarterMoveset;
pokerus: boolean;
}
@ -64,6 +66,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private natureCursor: integer = 0;
private genCursor: integer = 0;
private genScrollCursor: integer = 0;
private starterMoveset: StarterMoveset;
private genSpecies: PokemonSpecies[][] = [];
private lastSpecies: PokemonSpecies;
@ -74,7 +77,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private pokerusCursors: integer[] = [];
private starterAttr: bigint[] = [];
private starterNatures: Nature[] = [];
private starterMovesets: StarterMoveset[] = [];
private speciesStarterDexEntry: DexEntry;
private speciesStarterMoves: Moves[];
private canCycleShiny: boolean;
private canCycleForm: boolean;
private canCycleGender: boolean;
@ -481,56 +486,125 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (!this.speciesStarterDexEntry?.caughtAttr)
error = true;
else if (this.starterCursors.length < 6) {
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: [
{
label: 'Add to Party',
handler: () => {
ui.setMode(Mode.STARTER_SELECT);
let isDupe = false;
for (let s = 0; s < this.starterCursors.length; s++) {
if (this.starterGens[s] === this.getGenCursorWithScroll() && this.starterCursors[s] === this.cursor) {
isDupe = true;
break;
}
const options = [
{
label: 'Add to Party',
handler: () => {
ui.setMode(Mode.STARTER_SELECT);
let isDupe = false;
for (let s = 0; s < this.starterCursors.length; s++) {
if (this.starterGens[s] === this.getGenCursorWithScroll() && this.starterCursors[s] === this.cursor) {
isDupe = true;
break;
}
const species = this.genSpecies[this.getGenCursorWithScroll()][this.cursor];
if (!isDupe && 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);
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor);
this.starterIcons[this.starterCursors.length].setTexture(species.getIconAtlasKey(props.formIndex));
this.starterIcons[this.starterCursors.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny));
this.starterGens.push(this.getGenCursorWithScroll());
this.starterCursors.push(this.cursor);
this.starterAttr.push(this.dexAttrCursor);
this.starterNatures.push(this.natureCursor as unknown as Nature);
if (this.speciesLoaded.get(species.speciesId))
species.cry(this.scene);
if (this.starterCursors.length === 6 || this.value === 10)
this.tryStart();
this.updateInstructions();
ui.playSelect();
} else
ui.playError();
},
overrideSound: true
},
{
label: 'Toggle IVs',
handler: () => {
this.toggleStatsMode();
ui.setMode(Mode.STARTER_SELECT);
}
const species = this.genSpecies[this.getGenCursorWithScroll()][this.cursor];
if (!isDupe && 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);
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor);
this.starterIcons[this.starterCursors.length].setTexture(species.getIconAtlasKey(props.formIndex));
this.starterIcons[this.starterCursors.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny));
this.starterGens.push(this.getGenCursorWithScroll());
this.starterCursors.push(this.cursor);
this.starterAttr.push(this.dexAttrCursor);
this.starterNatures.push(this.natureCursor as unknown as Nature);
this.starterMovesets.push(this.starterMoveset.slice(0) as StarterMoveset);
if (this.speciesLoaded.get(species.speciesId))
species.cry(this.scene);
if (this.starterCursors.length === 6 || this.value === 10)
this.tryStart();
this.updateInstructions();
ui.playSelect();
} else
ui.playError();
},
{
label: 'Cancel',
handler: () => {
ui.setMode(Mode.STARTER_SELECT);
}
overrideSound: true
},
{
label: 'Toggle IVs',
handler: () => {
this.toggleStatsMode();
ui.setMode(Mode.STARTER_SELECT);
}
],
}
];
if (this.speciesStarterMoves.length > 1) {
const showSwapOptions = (moveset: StarterMoveset) => {
ui.setMode(Mode.STARTER_SELECT).then(() => {
ui.showText('Select a move to swap out.', null, () => {
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: moveset.map((m, i) => {
return {
label: allMoves[m].name,
handler: () => {
ui.setMode(Mode.STARTER_SELECT).then(() => {
ui.showText(`Select a move to swap with ${allMoves[m].name}.`, null, () => {
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: this.speciesStarterMoves.filter(sm => sm !== m).map(sm => {
return {
label: allMoves[sm].name,
handler: () => {
const speciesId = this.lastSpecies.speciesId;
const existingMoveIndex = this.starterMoveset.indexOf(sm);
this.starterMoveset[i] = sm;
if (existingMoveIndex > -1)
this.starterMoveset[existingMoveIndex] = m;
const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.dexAttrCursor);
if (!this.scene.gameData.starterMoveData.hasOwnProperty(speciesId) && pokemonFormLevelMoves.hasOwnProperty(speciesId)) {
this.scene.gameData.starterMoveData[speciesId] = pokemonFormLevelMoves.hasOwnProperty(speciesId)
? {}
: this.starterMoveset.slice(0) as StarterMoveset;
}
if (pokemonFormLevelMoves.hasOwnProperty(speciesId)) {
if (!this.scene.gameData.starterMoveData[speciesId].hasOwnProperty(props.formIndex))
this.scene.gameData.starterMoveData[speciesId][props.formIndex] = this.starterMoveset.slice(0) as StarterMoveset;
} else
this.scene.gameData.starterMoveData[speciesId] = this.starterMoveset.slice(0) as StarterMoveset;
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, false);
showSwapOptions(this.starterMoveset);
}
};
}).concat({
label: 'Cancel',
handler: () => {
showSwapOptions(this.starterMoveset);
}
}),
yOffset: 19
});
});
});
}
}
}).concat({
label: 'Cancel',
handler: () => {
this.clearText();
ui.setMode(Mode.STARTER_SELECT);
}
}),
yOffset: 19
});
});
});
};
options.push({
label: 'Manage Moves',
handler: () => {
showSwapOptions(this.starterMoveset);
}
});
}
options.push({
label: 'Cancel',
handler: () => {
ui.setMode(Mode.STARTER_SELECT);
}
});
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: options,
yOffset: 47
});
success = true;
@ -898,7 +972,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.assetLoadCancelled = null;
}
const starterMoves = [];
this.starterMoveset = null;
this.speciesStarterMoves = [];
if (species) {
const dexEntry = this.scene.gameData.dexData[species.speciesId];
@ -974,7 +1049,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonNatureText.setText(getNatureName(natureIndex as unknown as Nature, true, true));
starterMoves.push(...pokemonSpeciesLevelMoves[species.speciesId].slice(0, 4).filter(lm => lm[0] <= 5).map(lm => lm[1]));
let levelMoves: LevelMoves;
if (pokemonFormLevelMoves.hasOwnProperty(species.speciesId) && pokemonFormLevelMoves[species.speciesId].hasOwnProperty(formIndex))
levelMoves = pokemonFormLevelMoves[species.speciesId][formIndex];
else
levelMoves = pokemonSpeciesLevelMoves[species.speciesId];
this.speciesStarterMoves.push(...levelMoves.filter(lm => lm[0] <= 5).map(lm => lm[1]));
const speciesMoveData = this.scene.gameData.starterMoveData[species.speciesId];
let moveData: StarterMoveset = speciesMoveData
? Array.isArray(speciesMoveData)
? speciesMoveData as StarterMoveset
: (speciesMoveData as StarterFormMoveData)[formIndex]
: null;
this.starterMoveset = moveData || (this.speciesStarterMoves.slice(0, 4) as StarterMoveset);
} else {
this.pokemonAbilityText.setText('');
this.pokemonNatureText.setText('');
@ -985,8 +1073,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonNatureText.setText('');
}
if (!this.starterMoveset)
this.starterMoveset = this.speciesStarterMoves.slice(0, 4) as StarterMoveset;
for (let m = 0; m < 4; m++) {
const move = m < starterMoves.length ? allMoves[starterMoves[m]] : null;
const move = m < this.starterMoveset.length ? allMoves[this.starterMoveset[m]] : null;
this.pokemonMoveBgs[m].setFrame(Type[move ? move.type : Type.UNKNOWN].toString().toLowerCase());
this.pokemonMoveLabels[m].setText(move ? move.name : '-');
this.pokemonMoveContainers[m].setVisible(!!move);
@ -1000,6 +1091,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterCursors.pop();
this.starterAttr.pop();
this.starterNatures.pop();
this.starterMovesets.pop();
this.starterCursorObjs[this.starterCursors.length].setVisible(false);
this.starterIcons[this.starterCursors.length].setTexture('pokemon_icons_0');
this.starterIcons[this.starterCursors.length].setFrame('unknown');
@ -1051,6 +1143,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
species: starterSpecies,
dexAttr: thisObj.starterAttr[i],
nature: thisObj.starterNatures[i] as Nature,
moveset: thisObj.starterMovesets[i],
pokerus: !![ 0, 1, 2 ].filter(n => thisObj.pokerusGens[n] === starterSpecies.generation - 1 && thisObj.pokerusCursors[n] === thisObj.genSpecies[starterSpecies.generation - 1].indexOf(starterSpecies)).length
};
}));