mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-28 17:56:09 +00:00
Merge branch 'beta' into instruct
This commit is contained in:
commit
0221b3eb2a
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pokemon-rogue-battle",
|
"name": "pokemon-rogue-battle",
|
||||||
"version": "1.1.6",
|
"version": "1.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pokemon-rogue-battle",
|
"name": "pokemon-rogue-battle",
|
||||||
"version": "1.1.6",
|
"version": "1.2.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material/material-color-utilities": "^0.2.7",
|
"@material/material-color-utilities": "^0.2.7",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "pokemon-rogue-battle",
|
"name": "pokemon-rogue-battle",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.6",
|
"version": "1.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 5775faa6b3184082df73f6cdb96b253ea7dae3fe
|
Subproject commit ed1b1df4776ccd4330e8ac1d2f44de611d04c2bc
|
@ -4951,9 +4951,10 @@ class ForceSwitchOutHelper {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* For wild Pokémon battles, the Pokémon will flee if the conditions are met (waveIndex and double battles).
|
* For wild Pokémon battles, the Pokémon will flee if the conditions are met (waveIndex and double battles).
|
||||||
|
* It will not flee if it is a Mystery Encounter with fleeing disabled (checked in `getSwitchOutCondition()`) or if it is a wave 10x wild boss
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
if (!pokemon.scene.currentBattle.waveIndex && pokemon.scene.currentBattle.waveIndex % 10 === 0) {
|
if (!pokemon.scene.currentBattle.waveIndex || pokemon.scene.currentBattle.waveIndex % 10 === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||||||
newNature = randSeedInt(25) as Nature;
|
newNature = randSeedInt(25) as Nature;
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenPokemon.customPokemonData.nature = newNature;
|
chosenPokemon.setCustomNature(newNature);
|
||||||
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
encounter.setDialogueToken("newNature", getNatureName(newNature));
|
||||||
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
|
queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
|
||||||
setEncounterExp(scene, [ chosenPokemon.id ], 100);
|
setEncounterExp(scene, [ chosenPokemon.id ], 100);
|
||||||
|
@ -226,8 +226,8 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||||||
const onBeforeRewardsPhase = () => {
|
const onBeforeRewardsPhase = () => {
|
||||||
queueEncounterMessage(scene, `${namespace}:option.2.finished`);
|
queueEncounterMessage(scene, `${namespace}:option.2.finished`);
|
||||||
// Add the pokemon back to party with Nature change
|
// Add the pokemon back to party with Nature change
|
||||||
playerPokemon.setNature(encounter.misc.chosenNature);
|
playerPokemon.setCustomNature(encounter.misc.chosenNature);
|
||||||
scene.gameData.setPokemonCaught(playerPokemon, false);
|
scene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature);
|
||||||
|
|
||||||
// Add pokemon and modifiers back
|
// Add pokemon and modifiers back
|
||||||
scene.getPlayerParty().push(playerPokemon);
|
scene.getPlayerParty().push(playerPokemon);
|
||||||
|
@ -7,22 +7,22 @@ import i18next from "i18next";
|
|||||||
/**
|
/**
|
||||||
* Will inject all relevant dialogue tokens that exist in the {@linkcode BattleScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text.
|
* Will inject all relevant dialogue tokens that exist in the {@linkcode BattleScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text.
|
||||||
* Also adds BBCodeText fragments for colored text, if applicable
|
* Also adds BBCodeText fragments for colored text, if applicable
|
||||||
* @param scene
|
|
||||||
* @param keyOrString
|
* @param keyOrString
|
||||||
* @param primaryStyle Can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly
|
* @param primaryStyle Can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly
|
||||||
* @param uiTheme
|
|
||||||
*/
|
*/
|
||||||
export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string | null {
|
export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle): string | null {
|
||||||
if (isNullOrUndefined(keyOrString)) {
|
if (isNullOrUndefined(keyOrString)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uiTheme = scene.uiTheme ?? UiTheme.DEFAULT;
|
||||||
|
|
||||||
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString);
|
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString);
|
||||||
|
|
||||||
// Can only color the text if a Primary Style is defined
|
// Can only color the text if a Primary Style is defined
|
||||||
// primaryStyle is applied to all text that does not have its own specified style
|
// primaryStyle is applied to all text that does not have its own specified style
|
||||||
if (primaryStyle && textString) {
|
if (primaryStyle && textString) {
|
||||||
textString = getTextWithColors(textString, primaryStyle, uiTheme);
|
textString = getTextWithColors(textString, primaryStyle, uiTheme, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return textString;
|
return textString;
|
||||||
|
@ -1072,6 +1072,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.calculateStats();
|
this.calculateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCustomNature(nature: Nature): void {
|
||||||
|
this.customPokemonData.nature = nature;
|
||||||
|
this.calculateStats();
|
||||||
|
}
|
||||||
|
|
||||||
generateNature(naturePool?: Nature[]): void {
|
generateNature(naturePool?: Nature[]): void {
|
||||||
if (naturePool === undefined) {
|
if (naturePool === undefined) {
|
||||||
naturePool = Utils.getEnumValues(Nature);
|
naturePool = Utils.getEnumValues(Nature);
|
||||||
@ -2617,6 +2622,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const fixedDamage = new Utils.IntegerHolder(0);
|
const fixedDamage = new Utils.IntegerHolder(0);
|
||||||
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
|
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
|
||||||
if (fixedDamage.value) {
|
if (fixedDamage.value) {
|
||||||
|
const multiLensMultiplier = new Utils.NumberHolder(1);
|
||||||
|
source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiLensMultiplier);
|
||||||
|
fixedDamage.value = Utils.toDmgValue(fixedDamage.value * multiLensMultiplier.value);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
result: HitResult.EFFECTIVE,
|
result: HitResult.EFFECTIVE,
|
||||||
@ -3092,10 +3101,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lapseTag(tagType: BattlerTagType): boolean {
|
lapseTag(tagType: BattlerTagType): boolean {
|
||||||
const tags = this.summonData?.tags;
|
if (!this.summonData) {
|
||||||
if (isNullOrUndefined(tags)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const tags = this.summonData.tags;
|
||||||
const tag = tags.find(t => t.tagType === tagType);
|
const tag = tags.find(t => t.tagType === tagType);
|
||||||
if (tag && !(tag.lapse(this, BattlerTagLapseType.CUSTOM))) {
|
if (tag && !(tag.lapse(this, BattlerTagLapseType.CUSTOM))) {
|
||||||
tag.onRemove(this);
|
tag.onRemove(this);
|
||||||
@ -3105,6 +3114,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lapseTags(lapseType: BattlerTagLapseType): void {
|
lapseTags(lapseType: BattlerTagLapseType): void {
|
||||||
|
if (!this.summonData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const tags = this.summonData.tags;
|
const tags = this.summonData.tags;
|
||||||
tags.filter(t => lapseType === BattlerTagLapseType.FAINT || ((t.lapseTypes.some(lType => lType === lapseType)) && !(t.lapse(this, lapseType)))).forEach(t => {
|
tags.filter(t => lapseType === BattlerTagLapseType.FAINT || ((t.lapseTypes.some(lType => lType === lapseType)) && !(t.lapse(this, lapseType)))).forEach(t => {
|
||||||
t.onRemove(this);
|
t.onRemove(this);
|
||||||
@ -3113,6 +3125,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeTag(tagType: BattlerTagType): boolean {
|
removeTag(tagType: BattlerTagType): boolean {
|
||||||
|
if (!this.summonData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const tags = this.summonData.tags;
|
const tags = this.summonData.tags;
|
||||||
const tag = tags.find(t => t.tagType === tagType);
|
const tag = tags.find(t => t.tagType === tagType);
|
||||||
if (tag) {
|
if (tag) {
|
||||||
|
@ -230,6 +230,13 @@ export class GameMode implements GameModeConfig {
|
|||||||
return waveIndex % 10 === 0;
|
return waveIndex % 10 === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns `true` if the current battle is against classic mode's final boss
|
||||||
|
*/
|
||||||
|
isBattleClassicFinalBoss(waveIndex: number): boolean {
|
||||||
|
return (this.modeId === GameModes.CLASSIC || this.modeId === GameModes.CHALLENGE) && this.isWaveFinal(waveIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every 50 waves of an Endless mode is a boss
|
* Every 50 waves of an Endless mode is a boss
|
||||||
* At this time it is paradox pokemon
|
* At this time it is paradox pokemon
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type BattleScene from "#app/battle-scene";
|
import type BattleScene from "#app/battle-scene";
|
||||||
import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
||||||
import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
|
import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
|
||||||
import { getLevelTotalExp } from "#app/data/exp";
|
import { getLevelTotalExp } from "#app/data/exp";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
@ -2197,14 +2197,8 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
override apply(playerPokemon: PlayerPokemon): boolean {
|
override apply(playerPokemon: PlayerPokemon): boolean {
|
||||||
playerPokemon.customPokemonData.nature = this.nature;
|
playerPokemon.setCustomNature(this.nature);
|
||||||
let speciesId = playerPokemon.species.speciesId;
|
playerPokemon.scene.gameData.unlockSpeciesNature(playerPokemon.species, this.nature);
|
||||||
playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
|
||||||
|
|
||||||
while (pokemonPrevolutions.hasOwnProperty(speciesId)) {
|
|
||||||
speciesId = pokemonPrevolutions[speciesId];
|
|
||||||
playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2733,10 +2727,18 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
|||||||
* Additional strikes beyond that are given a 0.25x damage multiplier
|
* Additional strikes beyond that are given a 0.25x damage multiplier
|
||||||
*/
|
*/
|
||||||
private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean {
|
private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean {
|
||||||
damageMultiplier.value = (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount)
|
if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) {
|
||||||
? (1 - (0.25 * this.getStackCount()))
|
// Reduce first hit by 25% for each stack count
|
||||||
: 0.25;
|
damageMultiplier.value *= 1 - 0.25 * this.getStackCount();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) {
|
||||||
|
// Deal 25% damage for each remaining Multi Lens hit
|
||||||
|
damageMultiplier.value *= 0.25;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// An extra hit not caused by Multi Lens -- assume it is Parental Bond
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxHeldItemCount(pokemon: Pokemon): number {
|
getMaxHeldItemCount(pokemon: Pokemon): number {
|
||||||
|
@ -141,10 +141,6 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
} else if (!(battle.waveIndex % 1000)) {
|
} else if (!(battle.waveIndex % 1000)) {
|
||||||
enemyPokemon.formIndex = 1;
|
enemyPokemon.formIndex = 1;
|
||||||
enemyPokemon.updateScale();
|
enemyPokemon.updateScale();
|
||||||
const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier;
|
|
||||||
this.scene.removeModifier(bossMBH!);
|
|
||||||
bossMBH?.setTransferrableFalse();
|
|
||||||
this.scene.addEnemyModifier(bossMBH!);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +221,8 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
|
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
if (!this.loaded) {
|
if (!this.loaded) {
|
||||||
this.trySetWeatherIfNewBiome(); // Set weather before session gets saved
|
this.trySetWeatherIfNewBiome(); // Set weather before session gets saved
|
||||||
this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || (this.scene.lastSavePlayTime ?? 0) >= 300).then(success => {
|
// Game syncs to server on waves X1 and X6 (As of 1.2.0)
|
||||||
|
this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 5 === 1 || (this.scene.lastSavePlayTime ?? 0) >= 300).then(success => {
|
||||||
this.scene.disableMenu = false;
|
this.scene.disableMenu = false;
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return this.scene.reset(true);
|
return this.scene.reset(true);
|
||||||
@ -442,6 +439,15 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
if (enemyPokemon.isShiny()) {
|
if (enemyPokemon.isShiny()) {
|
||||||
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e));
|
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e));
|
||||||
}
|
}
|
||||||
|
/** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */
|
||||||
|
if (enemyPokemon.species.speciesId === Species.ETERNATUS && (this.scene.gameMode.isBattleClassicFinalBoss(this.scene.currentBattle.waveIndex) || this.scene.gameMode.isEndlessMajorBoss(this.scene.currentBattle.waveIndex))) {
|
||||||
|
const enemyMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier, false) as TurnHeldItemTransferModifier;
|
||||||
|
if (enemyMBH) {
|
||||||
|
this.scene.removeModifier(enemyMBH, true);
|
||||||
|
enemyMBH.setTransferrableFalse();
|
||||||
|
this.scene.addEnemyModifier(enemyMBH);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (![ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(this.scene.currentBattle.battleType)) {
|
if (![ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(this.scene.currentBattle.battleType)) {
|
||||||
|
@ -506,9 +506,9 @@ export class GameData {
|
|||||||
|
|
||||||
const starterIds = Object.keys(this.starterData).map(s => parseInt(s) as Species);
|
const starterIds = Object.keys(this.starterData).map(s => parseInt(s) as Species);
|
||||||
for (const s of starterIds) {
|
for (const s of starterIds) {
|
||||||
this.starterData[s].candyCount += this.dexData[s].caughtCount;
|
this.starterData[s].candyCount += systemData.dexData[s].caughtCount;
|
||||||
this.starterData[s].candyCount += this.dexData[s].hatchedCount * 2;
|
this.starterData[s].candyCount += systemData.dexData[s].hatchedCount * 2;
|
||||||
if (this.dexData[s].caughtAttr & DexAttr.SHINY) {
|
if (systemData.dexData[s].caughtAttr & DexAttr.SHINY) {
|
||||||
this.starterData[s].candyCount += 4;
|
this.starterData[s].candyCount += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1791,6 +1791,32 @@ export class GameData {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the root species of a given {@PokemonSpecies} has been unlocked in the dex
|
||||||
|
*/
|
||||||
|
isRootSpeciesUnlocked(species: PokemonSpecies): boolean {
|
||||||
|
return !!this.dexData[species.getRootSpeciesId()]?.caughtAttr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlocks the given {@linkcode Nature} for a {@linkcode PokemonSpecies} and its prevolutions.
|
||||||
|
* Will fail silently if root species has not been unlocked
|
||||||
|
*/
|
||||||
|
unlockSpeciesNature(species: PokemonSpecies, nature: Nature): void {
|
||||||
|
if (!this.isRootSpeciesUnlocked(species)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//recursively unlock nature for species and prevolutions
|
||||||
|
const _unlockSpeciesNature = (speciesId: Species) => {
|
||||||
|
this.dexData[speciesId].natureAttr |= 1 << (nature + 1);
|
||||||
|
if (pokemonPrevolutions.hasOwnProperty(speciesId)) {
|
||||||
|
_unlockSpeciesNature(pokemonPrevolutions[speciesId]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_unlockSpeciesNature(species.speciesId);
|
||||||
|
}
|
||||||
|
|
||||||
updateSpeciesDexIvs(speciesId: Species, ivs: integer[]): void {
|
updateSpeciesDexIvs(speciesId: Species, ivs: integer[]): void {
|
||||||
let dexEntry: DexEntry;
|
let dexEntry: DexEntry;
|
||||||
do {
|
do {
|
||||||
|
@ -129,7 +129,7 @@ class SessionVersionConverter extends VersionConverter {
|
|||||||
|
|
||||||
if (curMajor === 1) {
|
if (curMajor === 1) {
|
||||||
if (curMinor === 0) {
|
if (curMinor === 0) {
|
||||||
if (curPatch <= 4) {
|
if (curPatch <= 5) {
|
||||||
console.log("Applying v1.0.4 session data migration!");
|
console.log("Applying v1.0.4 session data migration!");
|
||||||
this.callMigrators(data, v1_0_4.sessionMigrators);
|
this.callMigrators(data, v1_0_4.sessionMigrators);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { SettingKeys } from "../../settings/settings";
|
import { SettingKeys } from "#app/system/settings/settings";
|
||||||
import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "../../game-data";
|
import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "#app/system/game-data";
|
||||||
import { allSpecies } from "../../../data/pokemon-species";
|
import { allSpecies } from "#app/data/pokemon-species";
|
||||||
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
|
||||||
export const systemMigrators = [
|
export const systemMigrators = [
|
||||||
/**
|
/**
|
||||||
@ -46,6 +48,7 @@ export const systemMigrators = [
|
|||||||
* @param data {@linkcode SystemSaveData}
|
* @param data {@linkcode SystemSaveData}
|
||||||
*/
|
*/
|
||||||
function fixStarterData(data: SystemSaveData) {
|
function fixStarterData(data: SystemSaveData) {
|
||||||
|
if (!isNullOrUndefined(data.starterData)) {
|
||||||
for (const starterId of defaultStarterSpecies) {
|
for (const starterId of defaultStarterSpecies) {
|
||||||
if (data.starterData[starterId]?.abilityAttr) {
|
if (data.starterData[starterId]?.abilityAttr) {
|
||||||
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
|
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
|
||||||
@ -55,6 +58,7 @@ export const systemMigrators = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const settingsMigrators = [
|
export const settingsMigrators = [
|
||||||
@ -131,5 +135,28 @@ export const sessionMigrators = [
|
|||||||
m.className = "ResetNegativeStatStageModifier";
|
m.className = "ResetNegativeStatStageModifier";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Converts old Pokemon natureOverride and mysteryEncounterData
|
||||||
|
* to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead.
|
||||||
|
* @param data {@linkcode SessionSaveData}
|
||||||
|
*/
|
||||||
|
function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) {
|
||||||
|
// Fix Pokemon nature overrides and custom data migration
|
||||||
|
data.party.forEach(pokemon => {
|
||||||
|
if (pokemon["mysteryEncounterPokemonData"]) {
|
||||||
|
pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]);
|
||||||
|
pokemon["mysteryEncounterPokemonData"] = null;
|
||||||
|
}
|
||||||
|
if (pokemon["fusionMysteryEncounterPokemonData"]) {
|
||||||
|
pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]);
|
||||||
|
pokemon["fusionMysteryEncounterPokemonData"] = null;
|
||||||
|
}
|
||||||
|
pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData();
|
||||||
|
if (!isNullOrUndefined(pokemon["natureOverride"]) && pokemon["natureOverride"] >= 0) {
|
||||||
|
pokemon.customPokemonData.nature = pokemon["natureOverride"];
|
||||||
|
pokemon["natureOverride"] = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
] as const;
|
] as const;
|
||||||
|
@ -1,32 +1,5 @@
|
|||||||
import { SessionSaveData } from "../../game-data";
|
|
||||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
|
||||||
|
|
||||||
export const systemMigrators = [] as const;
|
export const systemMigrators = [] as const;
|
||||||
|
|
||||||
export const settingsMigrators = [] as const;
|
export const settingsMigrators = [] as const;
|
||||||
|
|
||||||
export const sessionMigrators = [
|
export const sessionMigrators = [] as const;
|
||||||
/**
|
|
||||||
* Converts old Pokemon natureOverride and mysteryEncounterData
|
|
||||||
* to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead.
|
|
||||||
* @param data {@linkcode SessionSaveData}
|
|
||||||
*/
|
|
||||||
function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) {
|
|
||||||
// Fix Pokemon nature overrides and custom data migration
|
|
||||||
data.party.forEach(pokemon => {
|
|
||||||
if (pokemon["mysteryEncounterPokemonData"]) {
|
|
||||||
pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]);
|
|
||||||
pokemon["mysteryEncounterPokemonData"] = null;
|
|
||||||
}
|
|
||||||
if (pokemon["fusionMysteryEncounterPokemonData"]) {
|
|
||||||
pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]);
|
|
||||||
pokemon["fusionMysteryEncounterPokemonData"] = null;
|
|
||||||
}
|
|
||||||
pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData();
|
|
||||||
if (pokemon["natureOverride"] && pokemon["natureOverride"] >= 0) {
|
|
||||||
pokemon.customPokemonData.nature = pokemon["natureOverride"];
|
|
||||||
pokemon["natureOverride"] = -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
|
@ -313,7 +313,7 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to("MoveEndPhase", false);
|
await game.phaseInterceptor.to("MoveEndPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.hp).toBe(enemyStartingHp - 300);
|
expect(enemyPokemon.hp).toBe(enemyStartingHp - 200);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -613,4 +613,23 @@ describe("Abilities - Wimp Out", () => {
|
|||||||
|
|
||||||
confirmNoSwitch();
|
confirmNoSwitch();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not activate on wave X0 bosses", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.WIMP_OUT)
|
||||||
|
.startingLevel(5850)
|
||||||
|
.startingWave(10);
|
||||||
|
await game.classicMode.startBattle([ Species.GOLISOPOD ]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
// Use 2 turns of False Swipe due to opponent's health bar shield
|
||||||
|
game.move.select(Moves.FALSE_SWIPE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.move.select(Moves.FALSE_SWIPE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
const isVisible = enemyPokemon.visible;
|
||||||
|
const hasFled = enemyPokemon.switchOutStatus;
|
||||||
|
expect(isVisible && !hasFled).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -32,8 +32,8 @@ describe("Items - Multi Lens", () => {
|
|||||||
.enemySpecies(Species.SNORLAX)
|
.enemySpecies(Species.SNORLAX)
|
||||||
.enemyAbility(Abilities.BALL_FETCH)
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
.enemyMoveset(Moves.SPLASH)
|
.enemyMoveset(Moves.SPLASH)
|
||||||
.startingLevel(100)
|
.startingLevel(99) // Check for proper rounding on Seismic Toss damage reduction
|
||||||
.enemyLevel(100);
|
.enemyLevel(99);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -114,4 +114,25 @@ describe("Items - Multi Lens", () => {
|
|||||||
|
|
||||||
expect(magikarp.turnData.hitCount).toBe(2);
|
expect(magikarp.turnData.hitCount).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should enhance fixed-damage moves while also applying damage reduction", async () => {
|
||||||
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }])
|
||||||
|
.moveset(Moves.SEISMIC_TOSS);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
const spy = vi.spyOn(enemyPokemon, "getAttackDamage");
|
||||||
|
|
||||||
|
game.move.select(Moves.SEISMIC_TOSS);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
const damageResults = spy.mock.results.map(result => result.value?.damage);
|
||||||
|
|
||||||
|
expect(damageResults).toHaveLength(2);
|
||||||
|
expect(damageResults[0]).toBe(Math.floor(playerPokemon.level * 0.75));
|
||||||
|
expect(damageResults[1]).toBe(Math.floor(playerPokemon.level * 0.25));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -369,9 +369,9 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||||||
let text: string | null;
|
let text: string | null;
|
||||||
if (option.hasRequirements() && this.optionsMeetsReqs[i] && (option.optionMode === MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL || option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) {
|
if (option.hasRequirements() && this.optionsMeetsReqs[i] && (option.optionMode === MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL || option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) {
|
||||||
// Options with special requirements that are met are automatically colored green
|
// Options with special requirements that are met are automatically colored green
|
||||||
text = getEncounterText(this.scene, label, TextStyle.SUMMARY_GREEN);
|
text = getEncounterText(this.scene, label, TextStyle.ME_OPTION_SPECIAL);
|
||||||
} else {
|
} else {
|
||||||
text = getEncounterText(this.scene, label, optionDialogue.style ? optionDialogue.style : TextStyle.WINDOW);
|
text = getEncounterText(this.scene, label, optionDialogue.style ? optionDialogue.style : TextStyle.ME_OPTION_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
|
@ -518,7 +518,8 @@ export default class RunInfoUiHandler extends UiHandler {
|
|||||||
const runTime = Utils.getPlayTimeString(this.runInfo.playTime);
|
const runTime = Utils.getPlayTimeString(this.runInfo.playTime);
|
||||||
runInfoText.appendText(`${i18next.t("runHistory:runLength")}: ${runTime}`, false);
|
runInfoText.appendText(`${i18next.t("runHistory:runLength")}: ${runTime}`, false);
|
||||||
const runMoney = Utils.formatMoney(this.scene.moneyFormat, this.runInfo.money);
|
const runMoney = Utils.formatMoney(this.scene.moneyFormat, this.runInfo.money);
|
||||||
runInfoText.appendText(`[color=${getTextColor(TextStyle.MONEY)}]${i18next.t("battleScene:moneyOwned", { formattedMoney : runMoney })}[/color]`);
|
const moneyTextColor = getTextColor(TextStyle.MONEY_WINDOW, false, this.scene.uiTheme);
|
||||||
|
runInfoText.appendText(`[color=${moneyTextColor}]${i18next.t("battleScene:moneyOwned", { formattedMoney : runMoney })}[/color]`);
|
||||||
runInfoText.setPosition(7, 70);
|
runInfoText.setPosition(7, 70);
|
||||||
runInfoTextContainer.add(runInfoText);
|
runInfoTextContainer.add(runInfoText);
|
||||||
// Luck
|
// Luck
|
||||||
|
@ -22,7 +22,8 @@ export enum TextStyle {
|
|||||||
SUMMARY_GOLD,
|
SUMMARY_GOLD,
|
||||||
SUMMARY_GRAY,
|
SUMMARY_GRAY,
|
||||||
SUMMARY_GREEN,
|
SUMMARY_GREEN,
|
||||||
MONEY,
|
MONEY, // Money default styling (pale yellow)
|
||||||
|
MONEY_WINDOW, // Money displayed in Windows (needs different colors based on theme)
|
||||||
STATS_LABEL,
|
STATS_LABEL,
|
||||||
STATS_VALUE,
|
STATS_VALUE,
|
||||||
SETTINGS_VALUE,
|
SETTINGS_VALUE,
|
||||||
@ -38,7 +39,9 @@ export enum TextStyle {
|
|||||||
MOVE_PP_EMPTY,
|
MOVE_PP_EMPTY,
|
||||||
SMALLER_WINDOW_ALT,
|
SMALLER_WINDOW_ALT,
|
||||||
BGM_BAR,
|
BGM_BAR,
|
||||||
PERFECT_IV
|
PERFECT_IV,
|
||||||
|
ME_OPTION_DEFAULT, // Default style for choices in ME
|
||||||
|
ME_OPTION_SPECIAL, // Style for choices with special requirements in ME
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextStyleOptions {
|
export interface TextStyleOptions {
|
||||||
@ -139,6 +142,8 @@ export function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraSty
|
|||||||
case TextStyle.SUMMARY_GREEN:
|
case TextStyle.SUMMARY_GREEN:
|
||||||
case TextStyle.WINDOW:
|
case TextStyle.WINDOW:
|
||||||
case TextStyle.WINDOW_ALT:
|
case TextStyle.WINDOW_ALT:
|
||||||
|
case TextStyle.ME_OPTION_DEFAULT:
|
||||||
|
case TextStyle.ME_OPTION_SPECIAL:
|
||||||
shadowXpos = 3;
|
shadowXpos = 3;
|
||||||
shadowYpos = 3;
|
shadowYpos = 3;
|
||||||
break;
|
break;
|
||||||
@ -177,6 +182,7 @@ export function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraSty
|
|||||||
break;
|
break;
|
||||||
case TextStyle.BATTLE_INFO:
|
case TextStyle.BATTLE_INFO:
|
||||||
case TextStyle.MONEY:
|
case TextStyle.MONEY:
|
||||||
|
case TextStyle.MONEY_WINDOW:
|
||||||
case TextStyle.TOOLTIP_TITLE:
|
case TextStyle.TOOLTIP_TITLE:
|
||||||
styleOptions.fontSize = defaultFontSize - 24;
|
styleOptions.fontSize = defaultFontSize - 24;
|
||||||
shadowXpos = 3.5;
|
shadowXpos = 3.5;
|
||||||
@ -238,13 +244,22 @@ export function getBBCodeFrag(content: string, textStyle: TextStyle, uiTheme: Ui
|
|||||||
* - "red text" with TextStyle.SUMMARY_RED applied
|
* - "red text" with TextStyle.SUMMARY_RED applied
|
||||||
* @param content string with styling that need to be applied for BBCodeTextObject
|
* @param content string with styling that need to be applied for BBCodeTextObject
|
||||||
* @param primaryStyle Primary style is required in order to escape BBCode styling properly.
|
* @param primaryStyle Primary style is required in order to escape BBCode styling properly.
|
||||||
* @param uiTheme
|
* @param uiTheme the {@linkcode UiTheme} to get TextStyle for
|
||||||
|
* @param forWindow set to `true` if the text is to be displayed in a window ({@linkcode BattleScene.addWindow})
|
||||||
|
* it will replace all instances of the default MONEY TextStyle by {@linkcode TextStyle.MONEY_WINDOW}
|
||||||
*/
|
*/
|
||||||
export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
|
export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme, forWindow?: boolean): string {
|
||||||
// Apply primary styling before anything else
|
// Apply primary styling before anything else
|
||||||
let text = getBBCodeFrag(content, primaryStyle, uiTheme) + "[/color][/shadow]";
|
let text = getBBCodeFrag(content, primaryStyle, uiTheme) + "[/color][/shadow]";
|
||||||
const primaryStyleString = [ ...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))! ][0];
|
const primaryStyleString = [ ...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))! ][0];
|
||||||
|
|
||||||
|
/* For money text displayed in game windows, we can't use the default {@linkcode TextStyle.MONEY}
|
||||||
|
* or it will look wrong in legacy mode because of the different window background color
|
||||||
|
* So, for text to be displayed in windows replace all "@[MONEY]" with "@[MONEY_WINDOW]" */
|
||||||
|
if (forWindow) {
|
||||||
|
text = text.replace(/@\[MONEY\]/g, (_substring: string) => "@[MONEY_WINDOW]");
|
||||||
|
}
|
||||||
|
|
||||||
// Set custom colors
|
// Set custom colors
|
||||||
text = text.replace(/@\[([^{]*)\]{([^}]*)}/gi, (substring, textStyle: string, textToColor: string) => {
|
text = text.replace(/@\[([^{]*)\]{([^}]*)}/gi, (substring, textStyle: string, textToColor: string) => {
|
||||||
return "[/color][/shadow]" + getBBCodeFrag(textToColor, TextStyle[textStyle], uiTheme) + "[/color][/shadow]" + primaryStyleString;
|
return "[/color][/shadow]" + getBBCodeFrag(textToColor, TextStyle[textStyle], uiTheme) + "[/color][/shadow]" + primaryStyleString;
|
||||||
@ -310,7 +325,12 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
|
|||||||
return !shadow ? "#f89890" : "#984038";
|
return !shadow ? "#f89890" : "#984038";
|
||||||
case TextStyle.SUMMARY_GOLD:
|
case TextStyle.SUMMARY_GOLD:
|
||||||
case TextStyle.MONEY:
|
case TextStyle.MONEY:
|
||||||
return !shadow ? "#e8e8a8" : "#a0a060";
|
return !shadow ? "#e8e8a8" : "#a0a060"; // Pale Yellow/Gold
|
||||||
|
case TextStyle.MONEY_WINDOW:
|
||||||
|
if (isLegacyTheme) {
|
||||||
|
return !shadow ? "#f8b050" : "#c07800"; // Gold
|
||||||
|
}
|
||||||
|
return !shadow ? "#e8e8a8" : "#a0a060"; // Pale Yellow/Gold
|
||||||
case TextStyle.SETTINGS_LOCKED:
|
case TextStyle.SETTINGS_LOCKED:
|
||||||
case TextStyle.SUMMARY_GRAY:
|
case TextStyle.SUMMARY_GRAY:
|
||||||
return !shadow ? "#a0a0a0" : "#636363";
|
return !shadow ? "#a0a0a0" : "#636363";
|
||||||
@ -332,6 +352,13 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui
|
|||||||
return !shadow ? "#484848" : "#d0d0c8";
|
return !shadow ? "#484848" : "#d0d0c8";
|
||||||
case TextStyle.BGM_BAR:
|
case TextStyle.BGM_BAR:
|
||||||
return !shadow ? "#f8f8f8" : "#6b5a73";
|
return !shadow ? "#f8f8f8" : "#6b5a73";
|
||||||
|
case TextStyle.ME_OPTION_DEFAULT:
|
||||||
|
return !shadow ? "#f8f8f8" : "#6b5a73"; // White
|
||||||
|
case TextStyle.ME_OPTION_SPECIAL:
|
||||||
|
if (isLegacyTheme) {
|
||||||
|
return !shadow ? "#f8b050" : "#c07800"; // Gold
|
||||||
|
}
|
||||||
|
return !shadow ? "#78c850" : "#306850"; // Green
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user