Merge branch 'beta' into fix-me-input-blocking

This commit is contained in:
ImperialSympathizer 2024-10-17 14:41:21 -04:00 committed by GitHub
commit d21fdccbf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 371 additions and 263 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "pokemon-rogue-battle",
"version": "1.0.4",
"version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pokemon-rogue-battle",
"version": "1.0.4",
"version": "1.1.0",
"hasInstallScript": true,
"dependencies": {
"@material/material-color-utilities": "^0.2.7",

View File

@ -1,7 +1,7 @@
{
"name": "pokemon-rogue-battle",
"private": true,
"version": "1.0.4",
"version": "1.1.0",
"type": "module",
"scripts": {
"start": "vite",
@ -20,7 +20,7 @@
"depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"create-test": "node ./create-test-boilerplate.js",
"postinstall": "npx lefthook install && npx lefthook run post-merge"
"postinstall": "npx lefthook install && npx lefthook run post-merge"
},
"devDependencies": {
"@eslint/js": "^9.3.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -95,6 +95,7 @@ import { ExpPhase } from "#app/phases/exp-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -3054,7 +3055,7 @@ export default class BattleScene extends SceneBase {
const pId = partyMember.id;
const participated = participantIds.has(pId);
if (participated && pokemonDefeated) {
partyMember.addFriendship(2);
partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE);
const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier);
if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) {
machoBraceModifier.stackCount++;

View File

@ -4913,8 +4913,7 @@ export function initAbilities() {
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1)
.ignorable(),
new Ability(Abilities.SHIELD_DUST, 3)
.attr(IgnoreMoveEffectsAbAttr)
.edgeCase(), // Does not work with secret power (unimplemented)
.attr(IgnoreMoveEffectsAbAttr),
new Ability(Abilities.OWN_TEMPO, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
.attr(IntimidateImmunityAbAttr)
@ -4958,8 +4957,7 @@ export function initAbilities() {
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1)
.ignorable(),
new Ability(Abilities.SERENE_GRACE, 3)
.attr(MoveEffectChanceMultiplierAbAttr, 2)
.edgeCase(), // does not work with secret power (unimplemented)
.attr(MoveEffectChanceMultiplierAbAttr, 2),
new Ability(Abilities.SWIFT_SWIM, 3)
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),

View File

@ -2,6 +2,12 @@ import { Species } from "#enums/species";
export const POKERUS_STARTER_COUNT = 5;
// #region Friendship constants
export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2;
export const FRIENDSHIP_GAIN_FROM_BATTLE = 2;
export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5;
export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
/**
* Function to get the cumulative friendship threshold at which a candy is earned
* @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts}

View File

@ -2891,8 +2891,6 @@ export class SecretPowerAttr extends MoveEffectAttr {
this.effectChanceOverride = move.chance;
const moveChance = this.getMoveChance(user, target, move, this.selfTarget);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
// effectChanceOverride used in the application of the actual secondary effect
this.effectChanceOverride = 100;
return true;
} else {
return false;
@ -2915,6 +2913,8 @@ export class SecretPowerAttr extends MoveEffectAttr {
const biome = user.scene.arena.biomeType;
secondaryEffect = this.determineBiomeEffect(biome);
}
// effectChanceOverride used in the application of the actual secondary effect
secondaryEffect.effectChanceOverride = 100;
return secondaryEffect.apply(user, target, move, []);
}

View File

@ -304,6 +304,11 @@ async function summonSafariPokemon(scene: BattleScene) {
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
// TODO: If we await showEncounterText here, then the text will display without
// the wild Pokemon on screen, but if we don't await it, then the text never
// shows up and the IV scanner breaks. For now, we place the IV scanner code
// separately so that at least the IV scanner works.
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));

View File

@ -61,7 +61,7 @@ const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.JYNX ],
[ Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE) ],

View File

@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { starterPassiveAbilities } from "#app/data/balance/passives";
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "#app/utils";
@ -4082,7 +4082,7 @@ export class PlayerPokemon extends Pokemon {
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
].filter(d => !!d);
const amount = new Utils.IntegerHolder(friendship);
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1)));
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1)));
if (amount.value > 0) {
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount);

View File

@ -30,6 +30,7 @@ import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next";
import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type";
import { Color, ShadowColor } from "#enums/color";
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
export type ModifierPredicate = (modifier: Modifier) => boolean;
@ -2213,7 +2214,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
playerPokemon.levelExp = 0;
}
playerPokemon.addFriendship(5);
playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY);
playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level));

View File

@ -20,6 +20,7 @@ import { VictoryPhase } from "./victory-phase";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { SwitchType } from "#enums/switch-type";
import { isNullOrUndefined } from "#app/utils";
import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters";
export class FaintPhase extends PokemonPhase {
/**
@ -147,7 +148,7 @@ export class FaintPhase extends PokemonPhase {
pokemon.faintCry(() => {
if (pokemon instanceof PlayerPokemon) {
pokemon.addFriendship(-10);
pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT);
}
pokemon.hideInfo();
this.scene.playSound("se/faint");

View File

@ -1,13 +0,0 @@
import BattleScene from "#app/battle-scene";
import { Phase } from "#app/phase";
import { Mode } from "#app/ui/ui";
export class OutdatedPhase extends Phase {
constructor(scene: BattleScene) {
super(scene);
}
start(): void {
this.scene.ui.setMode(Mode.OUTDATED);
}
}

View File

@ -43,10 +43,9 @@ import { Species } from "#enums/species";
import { applyChallenges, ChallengeType } from "#app/data/challenge";
import { WeatherType } from "#enums/weather-type";
import { TerrainType } from "#app/data/terrain";
import { OutdatedPhase } from "#app/phases/outdated-phase";
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "#app/system/version-converter";
import { applySessionVersionMigration, applySystemVersionMigration, applySettingsVersionMigration } from "./version_migration/version_converter";
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
@ -403,10 +402,7 @@ export class GameData {
.then(error => {
this.scene.ui.savingIcon.hide();
if (error) {
if (error.startsWith("client version out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
} else if (error.startsWith("session out of date")) {
if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}
@ -482,7 +478,7 @@ export class GameData {
localStorage.setItem(lsItemKey, "");
}
applySystemDataPatches(systemData);
applySystemVersionMigration(systemData);
this.trainerId = systemData.trainerId;
this.secretId = systemData.secretId;
@ -857,7 +853,7 @@ export class GameData {
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
applySettingsDataPatches(settings);
applySettingsVersionMigration(settings);
for (const setting of Object.keys(settings)) {
setSetting(this.scene, setting, settings[setting]);
@ -1313,7 +1309,7 @@ export class GameData {
return v;
}) as SessionSaveData;
applySessionDataPatches(sessionData);
applySessionVersionMigration(sessionData);
return sessionData;
}
@ -1354,10 +1350,7 @@ export class GameData {
this.scene.ui.savingIcon.hide();
}
if (error) {
if (error.startsWith("client version out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
} else if (error.startsWith("session out of date")) {
if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}

View File

@ -1,157 +0,0 @@
import { allSpecies } from "#app/data/pokemon-species";
import { AbilityAttr, defaultStarterSpecies, DexAttr, SessionSaveData, SystemSaveData } from "./game-data";
import { SettingKeys } from "./settings/settings";
const LATEST_VERSION = "1.0.5";
export function applySessionDataPatches(data: SessionSaveData) {
const curVersion = data.gameVersion;
// Always sanitize money as a safeguard
data.money = Math.floor(data.money);
if (curVersion !== LATEST_VERSION) {
switch (curVersion) {
case "1.0.0":
case "1.0.1":
case "1.0.2":
case "1.0.3":
case "1.0.4":
// --- PATCHES ---
// Fix Battle Items, Vitamins, and Lures
data.modifiers.forEach((m) => {
if (m.className === "PokemonBaseStatModifier") {
m.className = "BaseStatModifier";
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
m.className = "ResetNegativeStatStageModifier";
} else if (m.className === "TempBattleStatBoosterModifier") {
// Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator
if (m.typeId !== "DIRE_HIT") {
m.className = "TempStatStageBoosterModifier";
m.typeId = "TEMP_STAT_STAGE_BOOSTER";
// Migration from TempBattleStat to Stat
const newStat = m.typePregenArgs[0] + 1;
m.typePregenArgs[0] = newStat;
// From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
m.args = [ newStat, 5, m.args[1] ];
} else {
m.className = "TempCritBoosterModifier";
m.typePregenArgs = [];
// From [ stat, battlesLeft ] to [ maxBattles, battleCount ]
m.args = [ 5, m.args[1] ];
}
} else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
let maxBattles: number;
switch (m.typeId) {
case "MAX_LURE":
maxBattles = 30;
break;
case "SUPER_LURE":
maxBattles = 15;
break;
default:
maxBattles = 10;
break;
}
// From [ battlesLeft ] to [ maxBattles, battleCount ]
m.args = [ maxBattles, m.args[0] ];
}
});
data.enemyModifiers.forEach((m) => {
if (m.className === "PokemonBaseStatModifier") {
m.className = "BaseStatModifier";
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
m.className = "ResetNegativeStatStageModifier";
}
});
}
data.gameVersion = LATEST_VERSION;
}
}
export function applySystemDataPatches(data: SystemSaveData) {
const curVersion = data.gameVersion;
if (curVersion !== LATEST_VERSION) {
switch (curVersion) {
case "1.0.0":
case "1.0.1":
case "1.0.2":
case "1.0.3":
case "1.0.4":
// --- LEGACY PATCHES ---
if (data.starterData && data.dexData) {
// Migrate ability starter data if empty for caught species
Object.keys(data.starterData).forEach(sd => {
if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
data.starterData[sd].abilityAttr = 1;
}
});
}
// Fix Legendary Stats
if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) {
data.gameStats.subLegendaryPokemonSeen = 0;
data.gameStats.subLegendaryPokemonCaught = 0;
data.gameStats.subLegendaryPokemonHatched = 0;
allSpecies.filter(s => s.subLegendary).forEach(s => {
const dexEntry = data.dexData[s.speciesId];
data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount;
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0);
data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount;
data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0);
data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount;
data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0);
});
data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught);
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught);
data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught);
}
// --- PATCHES ---
// Fix Starter Data
if (data.starterData && data.dexData) {
for (const starterId of defaultStarterSpecies) {
if (data.starterData[starterId]?.abilityAttr) {
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
}
if (data.dexData[starterId]?.caughtAttr) {
data.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
}
}
}
}
data.gameVersion = LATEST_VERSION;
}
}
export function applySettingsDataPatches(settings: Object) {
const curVersion = settings.hasOwnProperty("gameVersion") ? settings["gameVersion"] : "1.0.0";
if (curVersion !== LATEST_VERSION) {
switch (curVersion) {
case "1.0.0":
case "1.0.1":
case "1.0.2":
case "1.0.3":
case "1.0.4":
// --- PATCHES ---
// Fix Reward Cursor Target
if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"];
delete settings["REROLL_TARGET"];
localStorage.setItem("settings", JSON.stringify(settings));
}
}
// Note that the current game version will be written at `saveSettings`
}
}

View File

@ -0,0 +1,182 @@
import { SessionSaveData, SystemSaveData } from "../game-data";
import { version } from "../../../package.json";
// --- v1.0.4 (and below) PATCHES --- //
import * as v1_0_4 from "./versions/v1_0_4";
const LATEST_VERSION = version.split(".").map(value => parseInt(value));
/**
* Converts incoming {@linkcode SystemSaveData} that has a version below the
* current version number listed in `package.json`.
*
* Note that no transforms act on the {@linkcode data} if its version matches
* the current version or if there are no migrations made between its version up
* to the current version.
* @param data {@linkcode SystemSaveData}
* @see {@link SystemVersionConverter}
*/
export function applySystemVersionMigration(data: SystemSaveData) {
const curVersion = data.gameVersion.split(".").map(value => parseInt(value));
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
const converter = new SystemVersionConverter();
converter.applyStaticPreprocessors(data);
converter.applyMigration(data, curVersion);
}
}
/**
* Converts incoming {@linkcode SessionSavaData} that has a version below the
* current version number listed in `package.json`.
*
* Note that no transforms act on the {@linkcode data} if its version matches
* the current version or if there are no migrations made between its version up
* to the current version.
* @param data {@linkcode SessionSaveData}
* @see {@link SessionVersionConverter}
*/
export function applySessionVersionMigration(data: SessionSaveData) {
const curVersion = data.gameVersion.split(".").map(value => parseInt(value));
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
const converter = new SessionVersionConverter();
converter.applyStaticPreprocessors(data);
converter.applyMigration(data, curVersion);
}
}
/**
* Converts incoming settings data that has a version below the
* current version number listed in `package.json`.
*
* Note that no transforms act on the {@linkcode data} if its version matches
* the current version or if there are no migrations made between its version up
* to the current version.
* @param data Settings data object
* @see {@link SettingsVersionConverter}
*/
export function applySettingsVersionMigration(data: Object) {
const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0";
const curVersion = gameVersion.split(".").map(value => parseInt(value));
if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
const converter = new SettingsVersionConverter();
converter.applyStaticPreprocessors(data);
converter.applyMigration(data, curVersion);
}
}
/**
* Abstract class encapsulating the logic for migrating data from a given version up to
* the current version listed in `package.json`.
*
* Note that, for any version converter, the corresponding `applyMigration`
* function would only need to be changed once when the first migration for a
* given version is introduced. Similarly, a version file (within the `versions`
* folder) would only need to be created for a version once with the appropriate
* array nomenclature.
*/
abstract class VersionConverter {
/**
* Iterates through an array of designated migration functions that are each
* called one by one to transform the data.
* @param data The data to be operated on
* @param migrationArr An array of functions that will transform the incoming data
*/
callMigrators(data: any, migrationArr: readonly any[]) {
for (const migrate of migrationArr) {
migrate(data);
}
}
/**
* Applies any version-agnostic data sanitation as defined within the function
* body.
* @param data The data to be operated on
*/
applyStaticPreprocessors(_data: any): void {
}
/**
* Uses the current version the incoming data to determine the starting point
* of the migration which will cascade up to the latest version, calling the
* necessary migration functions in the process.
* @param data The data to be operated on
* @param curVersion [0] Current major version
* [1] Current minor version
* [2] Current patch version
*/
abstract applyMigration(data: any, curVersion: number[]): void;
}
/**
* Class encapsulating the logic for migrating {@linkcode SessionSaveData} from
* a given version up to the current version listed in `package.json`.
* @extends VersionConverter
*/
class SessionVersionConverter extends VersionConverter {
override applyStaticPreprocessors(data: SessionSaveData): void {
// Always sanitize money as a safeguard
data.money = Math.floor(data.money);
}
override applyMigration(data: SessionSaveData, curVersion: number[]): void {
const [ curMajor, curMinor, curPatch ] = curVersion;
if (curMajor === 1) {
if (curMinor === 0) {
if (curPatch <= 4) {
console.log("Applying v1.0.4 session data migration!");
this.callMigrators(data, v1_0_4.sessionMigrators);
}
}
}
console.log(`Session data successfully migrated to v${version}!`);
}
}
/**
* Class encapsulating the logic for migrating {@linkcode SystemSaveData} from
* a given version up to the current version listed in `package.json`.
* @extends VersionConverter
*/
class SystemVersionConverter extends VersionConverter {
override applyMigration(data: SystemSaveData, curVersion: number[]): void {
const [ curMajor, curMinor, curPatch ] = curVersion;
if (curMajor === 1) {
if (curMinor === 0) {
if (curPatch <= 4) {
console.log("Applying v1.0.4 system data migraton!");
this.callMigrators(data, v1_0_4.systemMigrators);
}
}
}
console.log(`System data successfully migrated to v${version}!`);
}
}
/**
* Class encapsulating the logic for migrating settings data from
* a given version up to the current version listed in `package.json`.
* @extends VersionConverter
*/
class SettingsVersionConverter extends VersionConverter {
override applyMigration(data: Object, curVersion: number[]): void {
const [ curMajor, curMinor, curPatch ] = curVersion;
if (curMajor === 1) {
if (curMinor === 0) {
if (curPatch <= 4) {
console.log("Applying v1.0.4 settings data migraton!");
this.callMigrators(data, v1_0_4.settingsMigrators);
}
}
}
console.log(`System data successfully migrated to v${version}!`);
}
}

View File

@ -0,0 +1,135 @@
import { SettingKeys } from "../../settings/settings";
import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "../../game-data";
import { allSpecies } from "../../../data/pokemon-species";
export const systemMigrators = [
/**
* Migrate ability starter data if empty for caught species.
* @param data {@linkcode SystemSaveData}
*/
function migrateAbilityData(data: SystemSaveData) {
if (data.starterData && data.dexData) {
Object.keys(data.starterData).forEach(sd => {
if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
data.starterData[sd].abilityAttr = 1;
}
});
}
},
/**
* Populate legendary Pokémon statistics if they are missing.
* @param data {@linkcode SystemSaveData}
*/
function fixLegendaryStats(data: SystemSaveData) {
if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) {
data.gameStats.subLegendaryPokemonSeen = 0;
data.gameStats.subLegendaryPokemonCaught = 0;
data.gameStats.subLegendaryPokemonHatched = 0;
allSpecies.filter(s => s.subLegendary).forEach(s => {
const dexEntry = data.dexData[s.speciesId];
data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount;
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0);
data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount;
data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0);
data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount;
data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0);
});
data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught);
data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught);
data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught);
}
},
/**
* Unlock all starters' first ability and female gender option.
* @param data {@linkcode SystemSaveData}
*/
function fixStarterData(data: SystemSaveData) {
for (const starterId of defaultStarterSpecies) {
if (data.starterData[starterId]?.abilityAttr) {
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
}
if (data.dexData[starterId]?.caughtAttr) {
data.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
}
}
}
] as const;
export const settingsMigrators = [
/**
* Migrate from "REROLL_TARGET" property to {@linkcode
* SettingKeys.Shop_Cursor_Target}.
* @param data the `settings` object
*/
function fixRerollTarget(data: Object) {
if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"];
delete data["REROLL_TARGET"];
localStorage.setItem("settings", JSON.stringify(data));
}
}
] as const;
export const sessionMigrators = [
/**
* Converts old lapsing modifiers (battle items, lures, and Dire Hit) and
* other miscellaneous modifiers (vitamins, White Herb) to any new class
* names and/or change in reload arguments.
* @param data {@linkcode SessionSaveData}
*/
function migrateModifiers(data: SessionSaveData) {
data.modifiers.forEach((m) => {
if (m.className === "PokemonBaseStatModifier") {
m.className = "BaseStatModifier";
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
m.className = "ResetNegativeStatStageModifier";
} else if (m.className === "TempBattleStatBoosterModifier") {
const maxBattles = 5;
// Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator
if (m.typeId !== "DIRE_HIT") {
m.className = "TempStatStageBoosterModifier";
m.typeId = "TEMP_STAT_STAGE_BOOSTER";
// Migration from TempBattleStat to Stat
const newStat = m.typePregenArgs[0] + 1;
m.typePregenArgs[0] = newStat;
// From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
m.args = [ newStat, maxBattles, Math.min(m.args[1], maxBattles) ];
} else {
m.className = "TempCritBoosterModifier";
m.typePregenArgs = [];
// From [ stat, battlesLeft ] to [ maxBattles, battleCount ]
m.args = [ maxBattles, Math.min(m.args[1], maxBattles) ];
}
} else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
let maxBattles: number;
switch (m.typeId) {
case "MAX_LURE":
maxBattles = 30;
break;
case "SUPER_LURE":
maxBattles = 15;
break;
default:
maxBattles = 10;
break;
}
// From [ battlesLeft ] to [ maxBattles, battleCount ]
m.args = [ maxBattles, Math.min(m.args[0], maxBattles) ];
}
});
data.enemyModifiers.forEach((m) => {
if (m.className === "PokemonBaseStatModifier") {
m.className = "BaseStatModifier";
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
m.className = "ResetNegativeStatStageModifier";
}
});
}
] as const;

View File

@ -23,6 +23,7 @@ import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin;
import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin;
import EventEmitter = Phaser.Events.EventEmitter;
import UpdateList = Phaser.GameObjects.UpdateList;
import { version } from "../../../package.json";
Object.defineProperty(window, "localStorage", {
value: mockLocalStorage(),
@ -101,6 +102,7 @@ export default class GameWrapper {
injectMandatory() {
this.game.config = {
seed: ["test"],
gameVersion: version
};
this.scene.game = this.game;
this.game.renderer = {

View File

@ -17,6 +17,9 @@ import { IntegerHolder } from "./../utils";
import Phaser from "phaser";
export const SHOP_OPTIONS_ROW_LIMIT = 7;
const SINGLE_SHOP_ROW_YOFFSET = 12;
const DOUBLE_SHOP_ROW_YOFFSET = 24;
const OPTION_BUTTON_YPOSITION = -62;
export default class ModifierSelectUiHandler extends AwaitableUiHandler {
private modifierContainer: Phaser.GameObjects.Container;
@ -68,7 +71,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width;
}
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, -64);
this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION);
this.transferButtonContainer.setName("transfer-btn");
this.transferButtonContainer.setVisible(false);
ui.add(this.transferButtonContainer);
@ -78,7 +81,7 @@ 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 = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION);
this.checkButtonContainer.setName("use-btn");
this.checkButtonContainer.setVisible(false);
ui.add(this.checkButtonContainer);
@ -88,7 +91,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
checkButtonText.setOrigin(1, 0);
this.checkButtonContainer.add(checkButtonText);
this.rerollButtonContainer = this.scene.add.container(16, -64);
this.rerollButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
this.rerollButtonContainer.setName("reroll-brn");
this.rerollButtonContainer.setVisible(false);
ui.add(this.rerollButtonContainer);
@ -104,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1);
this.rerollButtonContainer.add(this.rerollCostText);
this.lockRarityButtonContainer = this.scene.add.container(16, -64);
this.lockRarityButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
this.lockRarityButtonContainer.setVisible(false);
ui.add(this.lockRarityButtonContainer);
@ -191,7 +194,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
const shopTypeOptions = !removeHealShop
? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value)
: [];
const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;
const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET;
for (let m = 0; m < typeOptions.length; m++) {
const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2);
@ -212,7 +215,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT;
const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT);
const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2);
const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (40 - (28 * row - 1))), shopTypeOptions[m]);
const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (42 - (28 * row - 1))), shopTypeOptions[m]);
option.setScale(0.375);
this.scene.add.existing(option);
this.modifierContainer.add(option);
@ -456,16 +459,18 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
if (this.rowCursor === 1 && options.length === 0) {
// Continue button when no shop items
this.cursorObj.setScale(1.25);
this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22));
this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2));
ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription"));
return ret;
}
const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2);
if (this.rowCursor < 2) {
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22));
// Cursor on free items
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2));
} else {
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
// Cursor on paying items
this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
}
const type = options[this.cursor].modifierTypeOption.type;
@ -475,16 +480,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.moveInfoOverlay.show(allMoves[type.moveId]);
}
} else if (cursor === 0) {
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60);
this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? OPTION_BUTTON_YPOSITION - 8 : OPTION_BUTTON_YPOSITION + 4);
ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc"));
} else if (cursor === 1) {
this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, -60);
this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4);
ui.showText(i18next.t("modifierSelectUiHandler:transferDesc"));
} else if (cursor === 2) {
this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, -60);
this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4);
ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc"));
} else {
this.cursorObj.setPosition(6, -60);
this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4);
ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc"));
}

View File

@ -1,47 +0,0 @@
import BattleScene from "../battle-scene";
import { ModalConfig, ModalUiHandler } from "./modal-ui-handler";
import { addTextObject, TextStyle } from "./text";
import { Mode } from "./ui";
export default class OutdatedModalUiHandler extends ModalUiHandler {
constructor(scene: BattleScene, mode: Mode | null = null) {
super(scene, mode);
}
getModalTitle(): string {
return "";
}
getWidth(): number {
return 160;
}
getHeight(): number {
return 64;
}
getMargin(): [number, number, number, number] {
return [ 0, 0, 48, 0 ];
}
getButtonLabels(): string[] {
return [ ];
}
setup(): void {
super.setup();
const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, "Your client is currently outdated.\nPlease reload to update the game.\n\nIf this error persists, please clear your browser cache.", TextStyle.WINDOW, { fontSize: "48px", align: "center" });
label.setOrigin(0.5, 0.5);
this.modalContainer.add(label);
}
show(args: any[]): boolean {
const config: ModalConfig = {
buttonActions: []
};
return super.show([ config ]);
}
}

View File

@ -34,7 +34,6 @@ import SaveSlotSelectUiHandler from "./save-slot-select-ui-handler";
import TitleUiHandler from "./title-ui-handler";
import SavingIconHandler from "./saving-icon-handler";
import UnavailableModalUiHandler from "./unavailable-modal-ui-handler";
import OutdatedModalUiHandler from "./outdated-modal-ui-handler";
import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler";
import { Button } from "#enums/buttons";
import i18next from "i18next";
@ -90,7 +89,6 @@ export enum Mode {
LOADING,
SESSION_RELOAD,
UNAVAILABLE,
OUTDATED,
CHALLENGE_SELECT,
RENAME_POKEMON,
RUN_HISTORY,
@ -134,7 +132,6 @@ const noTransitionModes = [
Mode.LOADING,
Mode.SESSION_RELOAD,
Mode.UNAVAILABLE,
Mode.OUTDATED,
Mode.RENAME_POKEMON,
Mode.TEST_DIALOGUE,
Mode.AUTO_COMPLETE,
@ -200,7 +197,6 @@ export default class UI extends Phaser.GameObjects.Container {
new LoadingModalUiHandler(scene),
new SessionReloadModalUiHandler(scene),
new UnavailableModalUiHandler(scene),
new OutdatedModalUiHandler(scene),
new GameChallengesUiHandler(scene),
new RenameFormUiHandler(scene),
new RunHistoryUiHandler(scene),