Add foundation for Nuzlocke Mode (WiP)

Add foundation for Nuzlocke Mode (WiP); lower berry caps; add global object for rich presence
pull/457/merge^2
Flashfyre 2024-05-04 12:15:45 -04:00
parent a7af14b8e2
commit ef6c354132
13 changed files with 198 additions and 59 deletions

View File

@ -20,7 +20,6 @@ import { initMoves } from './data/move';
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from './modifier/modifier-type'; import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from './modifier/modifier-type';
import AbilityBar from './ui/ability-bar'; import AbilityBar from './ui/ability-bar';
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability'; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability';
import { Abilities } from "./data/enums/abilities";
import { allAbilities } from "./data/ability"; import { allAbilities } from "./data/ability";
import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle'; import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle';
import { GameMode, GameModes, gameModes } from './game-mode'; import { GameMode, GameModes, gameModes } from './game-mode';
@ -53,7 +52,7 @@ import PokemonSpriteSparkleHandler from './field/pokemon-sprite-sparkle-handler'
import CharSprite from './ui/char-sprite'; import CharSprite from './ui/char-sprite';
import DamageNumberHandler from './field/damage-number-handler'; import DamageNumberHandler from './field/damage-number-handler';
import PokemonInfoContainer from './ui/pokemon-info-container'; import PokemonInfoContainer from './ui/pokemon-info-container';
import { biomeDepths } from './data/biomes'; import { biomeDepths, getBiomeName } from './data/biomes';
import { initTouchControls } from './touch-controls'; import { initTouchControls } from './touch-controls';
import { UiTheme } from './enums/ui-theme'; import { UiTheme } from './enums/ui-theme';
import { SceneBase } from './scene-base'; import { SceneBase } from './scene-base';
@ -153,6 +152,9 @@ export default class BattleScene extends SceneBase {
public arena: Arena; public arena: Arena;
public gameMode: GameMode; public gameMode: GameMode;
public score: integer; public score: integer;
public victoryCount: integer;
public faintCount: integer;
public reviveCount: integer;
public lockModifierTiers: boolean; public lockModifierTiers: boolean;
public trainer: Phaser.GameObjects.Sprite; public trainer: Phaser.GameObjects.Sprite;
public lastEnemyTrainer: Trainer; public lastEnemyTrainer: Trainer;
@ -233,6 +235,7 @@ export default class BattleScene extends SceneBase {
this.phaseQueuePrepend = []; this.phaseQueuePrepend = [];
this.phaseQueuePrependSpliceIndex = -1; this.phaseQueuePrependSpliceIndex = -1;
this.nextCommandPhaseQueue = []; this.nextCommandPhaseQueue = [];
this.updateGameInfo();
} }
loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) { loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) {
@ -837,6 +840,8 @@ export default class BattleScene extends SceneBase {
this.trainer.setPosition(406, 186); this.trainer.setPosition(406, 186);
this.trainer.setVisible(true); this.trainer.setVisible(true);
this.updateGameInfo();
if (reloadI18n) { if (reloadI18n) {
const localizable: Localizable[] = [ const localizable: Localizable[] = [
...allSpecies, ...allSpecies,
@ -960,7 +965,7 @@ export default class BattleScene extends SceneBase {
isNewBiome = !Utils.randSeedInt(6 - biomeWaves); isNewBiome = !Utils.randSeedInt(6 - biomeWaves);
}, lastBattle.waveIndex << 4); }, lastBattle.waveIndex << 4);
} }
const resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS; const resetArenaState = (isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) && !this.gameMode.hasNoReturns;
this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy()); this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy());
this.trySpreadPokerus(); this.trySpreadPokerus();
if (!isNewBiome && (newWaveIndex % 10) == 5) if (!isNewBiome && (newWaveIndex % 10) == 5)
@ -1625,10 +1630,10 @@ export default class BattleScene extends SceneBase {
} }
} }
playSoundWithoutBgm(soundName: string, pauseDuration?: integer): AnySound { playSoundWithoutBgm(soundName: string, pauseDuration?: integer, config?: object): AnySound {
this.bgmCache.add(soundName); this.bgmCache.add(soundName);
const resumeBgm = this.pauseBgm(); const resumeBgm = this.pauseBgm();
this.playSound(soundName); this.playSound(soundName, config);
const sound = this.sound.get(soundName) as AnySound; const sound = this.sound.get(soundName) as AnySound;
if (this.bgmResumeTimer) if (this.bgmResumeTimer)
this.bgmResumeTimer.destroy(); this.bgmResumeTimer.destroy();
@ -2183,4 +2188,16 @@ export default class BattleScene extends SceneBase {
return false; return false;
} }
updateGameInfo(): void {
const gameInfo = {
gameMode: this.currentBattle ? this.gameMode.getName() : 'Title',
biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : '',
wave: this.currentBattle?.waveIndex || 0,
party: this.party ? this.party.map(p => {
return { name: p.name, level: p.level };
}) : []
};
(window as any).gameInfo = gameInfo;
}
} }

View File

@ -1702,17 +1702,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return cry; return cry;
} }
faintCry(callback: Function): void { faintCry(callback: Function, forPermadeath: boolean = false): void {
if (this.fusionSpecies) if (this.fusionSpecies)
return this.fusionFaintCry(callback); return this.fusionFaintCry(callback, forPermadeath);
const key = this.getSpeciesForm().getCryKey(this.formIndex); const key = this.getSpeciesForm().getCryKey(this.formIndex);
let i = 0;
let rate = 0.85; let rate = 0.85;
const cry = this.scene.playSound(key, { rate: rate }) as AnySound; const cry = (!forPermadeath
? this.scene.playSound(key, { rate: rate })
: this.scene.playSoundWithoutBgm(key, Utils.fixedInt(2500), { rate: rate })) as AnySound;
const sprite = this.getSprite(); const sprite = this.getSprite();
const tintSprite = this.getTintSprite(); const tintSprite = this.getTintSprite();
const decayRate = !forPermadeath ? 0.99 : 0.98;
const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25); const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25);
let frameProgress = 0; let frameProgress = 0;
@ -1721,11 +1723,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
sprite.anims.pause(); sprite.anims.pause();
tintSprite.anims.pause(); tintSprite.anims.pause();
if (forPermadeath)
this.tint(0, 0.325, Utils.fixedInt(1500), 'Sine.easeIn');
let faintCryTimer = this.scene.time.addEvent({ let faintCryTimer = this.scene.time.addEvent({
delay: Utils.fixedInt(delay), delay: Utils.fixedInt(delay),
repeat: -1, repeat: -1,
callback: () => { callback: () => {
++i;
frameThreshold = sprite.anims.msPerFrame / rate; frameThreshold = sprite.anims.msPerFrame / rate;
frameProgress += delay; frameProgress += delay;
while (frameProgress > frameThreshold) { while (frameProgress > frameThreshold) {
@ -1736,7 +1740,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
frameProgress -= frameThreshold; frameProgress -= frameThreshold;
} }
if (cry && !cry.pendingRemove) { if (cry && !cry.pendingRemove) {
rate *= 0.99; rate *= decayRate;
cry.setRate(rate); cry.setRate(rate);
} else { } else {
faintCryTimer.destroy(); faintCryTimer.destroy();
@ -1759,13 +1763,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}); });
} }
private fusionFaintCry(callback: Function): void { private fusionFaintCry(callback: Function, forPermadeath: boolean = false): void {
const key = this.getSpeciesForm().getCryKey(this.formIndex); const key = this.getSpeciesForm().getCryKey(this.formIndex);
let i = 0; let i = 0;
let rate = 0.85; let rate = 0.85;
let cry = this.scene.playSound(key, { rate: rate }) as AnySound; let cry = (!forPermadeath
? this.scene.playSound(key, { rate: rate })
: this.scene.playSoundWithoutBgm(key, Utils.fixedInt(2000), { rate: rate })) as AnySound;
const sprite = this.getSprite(); const sprite = this.getSprite();
const tintSprite = this.getTintSprite(); const tintSprite = this.getTintSprite();
const decayRate = !forPermadeath ? 0.99 : 0.98;
let duration = cry.totalDuration * 1000; let duration = cry.totalDuration * 1000;
let fusionCry = this.scene.playSound(this.getFusionSpeciesForm().getCryKey(this.fusionFormIndex), { rate: rate }) as AnySound; let fusionCry = this.scene.playSound(this.getFusionSpeciesForm().getCryKey(this.fusionFormIndex), { rate: rate }) as AnySound;
@ -1782,12 +1790,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
while (durationProgress < transitionThreshold) { while (durationProgress < transitionThreshold) {
++i; ++i;
durationProgress += delay * rate; durationProgress += delay * rate;
rate *= 0.99; rate *= decayRate;
} }
transitionIndex = i; transitionIndex = i;
i = 0;
rate = 0.85; rate = 0.85;
let frameProgress = 0; let frameProgress = 0;
@ -1796,11 +1803,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
sprite.anims.pause(); sprite.anims.pause();
tintSprite.anims.pause(); tintSprite.anims.pause();
if (forPermadeath)
this.tint(0, 0.325, Utils.fixedInt(1500), 'Sine.easeIn');
let faintCryTimer = this.scene.time.addEvent({ let faintCryTimer = this.scene.time.addEvent({
delay: Utils.fixedInt(delay), delay: Utils.fixedInt(delay),
repeat: -1, repeat: -1,
callback: () => { callback: () => {
++i;
frameThreshold = sprite.anims.msPerFrame / rate; frameThreshold = sprite.anims.msPerFrame / rate;
frameProgress += delay; frameProgress += delay;
while (frameProgress > frameThreshold) { while (frameProgress > frameThreshold) {
@ -1815,7 +1824,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
fusionCry = this.scene.playSound(this.getFusionSpeciesForm().getCryKey(this.fusionFormIndex), Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0), rate: rate })); fusionCry = this.scene.playSound(this.getFusionSpeciesForm().getCryKey(this.fusionFormIndex), Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0), rate: rate }));
SoundFade.fadeIn(this.scene, fusionCry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), this.scene.masterVolume * this.scene.seVolume, 0); SoundFade.fadeIn(this.scene, fusionCry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), this.scene.masterVolume * this.scene.seVolume, 0);
} }
rate *= 0.99; rate *= decayRate;
if (cry && !cry.pendingRemove) if (cry && !cry.pendingRemove)
cry.setRate(rate); cry.setRate(rate);
if (fusionCry && !fusionCry.pendingRemove) if (fusionCry && !fusionCry.pendingRemove)

View File

@ -11,16 +11,23 @@ export enum GameModes {
CLASSIC, CLASSIC,
ENDLESS, ENDLESS,
SPLICED_ENDLESS, SPLICED_ENDLESS,
DAILY DAILY,
NUZLOCKE,
GAUNTLET
} }
interface GameModeConfig { interface GameModeConfig {
isClassic?: boolean; isClassic?: boolean;
isEndless?: boolean; isEndless?: boolean;
isDaily?: boolean; isDaily?: boolean;
isNuzlocke?: boolean;
hasNoHeals?: boolean;
hasNoReturns?: boolean;
hasTrainers?: boolean; hasTrainers?: boolean;
hasFixedBattles?: boolean; hasFixedBattles?: boolean;
hasNoShop?: boolean; hasNoShop?: boolean;
hasStrictLevelCap?: boolean;
hasPassiveHeals?: boolean;
hasShortBiomes?: boolean; hasShortBiomes?: boolean;
hasRandomBiomes?: boolean; hasRandomBiomes?: boolean;
hasRandomBosses?: boolean; hasRandomBosses?: boolean;
@ -32,9 +39,14 @@ export class GameMode implements GameModeConfig {
public isClassic: boolean; public isClassic: boolean;
public isEndless: boolean; public isEndless: boolean;
public isDaily: boolean; public isDaily: boolean;
public isNuzlocke: boolean;
public hasNoHeals: boolean;
public hasNoReturns: boolean;
public hasTrainers: boolean; public hasTrainers: boolean;
public hasFixedBattles: boolean; public hasFixedBattles: boolean;
public hasNoShop: boolean; public hasNoShop: boolean;
public hasStrictLevelCap: boolean;
public hasPassiveHeals: boolean;
public hasShortBiomes: boolean; public hasShortBiomes: boolean;
public hasRandomBiomes: boolean; public hasRandomBiomes: boolean;
public hasRandomBosses: boolean; public hasRandomBosses: boolean;
@ -132,9 +144,11 @@ export class GameMode implements GameModeConfig {
isWaveFinal(waveIndex: integer): boolean { isWaveFinal(waveIndex: integer): boolean {
switch (this.modeId) { switch (this.modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
case GameModes.NUZLOCKE:
return waveIndex === 200; return waveIndex === 200;
case GameModes.ENDLESS: case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
case GameModes.GAUNTLET:
return !(waveIndex % 250); return !(waveIndex % 250);
case GameModes.DAILY: case GameModes.DAILY:
return waveIndex === 50; return waveIndex === 50;
@ -154,9 +168,11 @@ export class GameMode implements GameModeConfig {
switch (this.modeId) { switch (this.modeId) {
case GameModes.CLASSIC: case GameModes.CLASSIC:
case GameModes.DAILY: case GameModes.DAILY:
case GameModes.NUZLOCKE:
return !isBoss ? 18 : 6; return !isBoss ? 18 : 6;
case GameModes.ENDLESS: case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
case GameModes.GAUNTLET:
return !isBoss ? 12 : 4; return !isBoss ? 12 : 4;
} }
} }
@ -171,6 +187,10 @@ export class GameMode implements GameModeConfig {
return 'Endless (Spliced)'; return 'Endless (Spliced)';
case GameModes.DAILY: case GameModes.DAILY:
return 'Daily Run'; return 'Daily Run';
case GameModes.NUZLOCKE:
return 'Nuzlocke';
case GameModes.GAUNTLET:
return 'Gauntlet';
} }
} }
} }
@ -179,5 +199,7 @@ export const gameModes = Object.freeze({
[GameModes.CLASSIC]: new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasFixedBattles: true }), [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.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.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 }) [GameModes.DAILY]: new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }),
[GameModes.NUZLOCKE]: new GameMode(GameModes.NUZLOCKE, { isClassic: true, isNuzlocke: true, hasStrictLevelCap: true, hasTrainers: true, hasFixedBattles: true }),
[GameModes.GAUNTLET]: new GameMode(GameModes.GAUNTLET, { isEndless: true, hasNoHeals: true, hasNoReturns: true, hasPassiveHeals: true, hasShortBiomes: true, hasRandomBosses: true })
}); });

View File

@ -249,6 +249,9 @@ export class LoadingScene extends SceneBase {
this.loadSe('gacha_running'); this.loadSe('gacha_running');
this.loadSe('gacha_dispense'); this.loadSe('gacha_dispense');
// Nuzleaf
this.loadSe('274', '', '../cry/274.m4a');
this.loadSe('PRSFX- Transform', 'battle_anims'); this.loadSe('PRSFX- Transform', 'battle_anims');
this.loadBgm('menu'); this.loadBgm('menu');

View File

@ -3,7 +3,7 @@ import { AttackMove, allMoves } from '../data/move';
import { Moves } from "../data/enums/moves"; import { Moves } from "../data/enums/moves";
import { PokeballType, getPokeballCatchMultiplier, getPokeballName } from '../data/pokeball'; import { PokeballType, getPokeballCatchMultiplier, getPokeballName } from '../data/pokeball';
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from '../field/pokemon'; import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from '../field/pokemon';
import { EvolutionItem, SpeciesFriendshipEvolutionCondition, pokemonEvolutions } from '../data/pokemon-evolutions'; import { EvolutionItem, pokemonEvolutions } from '../data/pokemon-evolutions';
import { Stat, getStatName } from '../data/pokemon-stat'; import { Stat, getStatName } from '../data/pokemon-stat';
import { tmPoolTiers, tmSpecies } from '../data/tms'; import { tmPoolTiers, tmSpecies } from '../data/tms';
import { Type } from '../data/type'; import { Type } from '../data/type';
@ -21,6 +21,7 @@ import { ModifierTier } from './modifier-tier';
import { Nature, getNatureName, getNatureStatMultiplier } from '#app/data/nature'; import { Nature, getNatureName, getNatureStatMultiplier } from '#app/data/nature';
import { Localizable } from '#app/plugins/i18n'; import { Localizable } from '#app/plugins/i18n';
import { getModifierTierTextTint } from '#app/ui/text'; import { getModifierTierTextTint } from '#app/ui/text';
import { GameMode } from '#app/game-mode';
const outputModifierData = false; const outputModifierData = false;
const useMaxWeightForOutput = false; const useMaxWeightForOutput = false;
@ -427,7 +428,11 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i
export class PokemonLevelIncrementModifierType extends PokemonModifierType { export class PokemonLevelIncrementModifierType extends PokemonModifierType {
constructor(name: string, iconImage?: string) { constructor(name: string, iconImage?: string) {
super(name, `Increases a Pokémon\'s level by 1`, (_type, args) => new Modifiers.PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), super(name, `Increases a Pokémon\'s level by 1`, (_type, args) => new Modifiers.PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id),
(_pokemon: PlayerPokemon) => null, iconImage); (pokemon: PlayerPokemon) => {
if (pokemon.scene.gameMode.hasStrictLevelCap && pokemon.level >= pokemon.scene.getMaxExpLevel())
return PartyUiHandler.NoEffectMessage;
return null;
}, iconImage);
} }
} }
@ -995,14 +1000,20 @@ const modifierPool: ModifierPool = {
return statusEffectPartyMemberCount * 6; return statusEffectPartyMemberCount * 6;
}, 18), }, 18),
new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => {
if (party[0].scene.gameMode.isNuzlocke)
return 0;
const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
return faintedPartyMemberCount * 9; return faintedPartyMemberCount * 9;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => {
if (party[0].scene.gameMode.isNuzlocke)
return 0;
const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
return faintedPartyMemberCount * 3; return faintedPartyMemberCount * 3;
}, 9), }, 9),
new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => {
if (party[0].scene.gameMode.isNuzlocke)
return 0;
return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0;
}, 1), }, 1),
new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => {
@ -1053,7 +1064,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.MINT, 4), new WeightedModifierType(modifierTypes.MINT, 4),
new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32), new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32),
new WeightedModifierType(modifierTypes.AMULET_COIN, 3), new WeightedModifierType(modifierTypes.AMULET_COIN, 3),
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.REVIVER_SEED, (party: Pokemon[]) => party[0].scene.gameMode.isNuzlocke ? 8 : 4, 4),
new WeightedModifierType(modifierTypes.CANDY_JAR, 5), new WeightedModifierType(modifierTypes.CANDY_JAR, 5),
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10),
new WeightedModifierType(modifierTypes.TM_ULTRA, 8), new WeightedModifierType(modifierTypes.TM_ULTRA, 8),
@ -1331,7 +1342,7 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
return options; return options;
} }
export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer): ModifierTypeOption[] { export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer, gameMode: GameMode): ModifierTypeOption[] {
if (!(waveIndex % 10)) if (!(waveIndex % 10))
return []; return [];
@ -1339,7 +1350,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
[ [
new ModifierTypeOption(modifierTypes.POTION(), 0, baseCost * 0.2), new ModifierTypeOption(modifierTypes.POTION(), 0, baseCost * 0.2),
new ModifierTypeOption(modifierTypes.ETHER(), 0, baseCost * 0.4), new ModifierTypeOption(modifierTypes.ETHER(), 0, baseCost * 0.4),
new ModifierTypeOption(modifierTypes.REVIVE(), 0, baseCost * 2) !gameMode.isNuzlocke ? new ModifierTypeOption(modifierTypes.REVIVE(), 0, baseCost * 2) : null
], ],
[ [
new ModifierTypeOption(modifierTypes.SUPER_POTION(), 0, baseCost * 0.45), new ModifierTypeOption(modifierTypes.SUPER_POTION(), 0, baseCost * 0.45),
@ -1351,7 +1362,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
], ],
[ [
new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8), new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8),
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75) !gameMode.isNuzlocke ? new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75) : null
], ],
[ [
new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5), new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5),
@ -1361,10 +1372,10 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25) new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25)
], ],
[ [
new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10) !gameMode.isNuzlocke ? new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10) : null
] ]
]; ];
return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat().filter(o => o);
} }
export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier { export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier {

View File

@ -885,7 +885,14 @@ export class BerryModifier extends PokemonHeldItemModifier {
} }
getMaxHeldItemCount(pokemon: Pokemon): integer { getMaxHeldItemCount(pokemon: Pokemon): integer {
return 10; switch (this.berryType) {
case BerryType.LUM:
case BerryType.LEPPA:
case BerryType.SITRUS:
case BerryType.ENIGMA:
return 2;
}
return 3;
} }
} }
@ -987,6 +994,8 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier {
apply(args: any[]): boolean { apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon; const pokemon = args[0] as Pokemon;
if (!pokemon.hp === this.fainted) { if (!pokemon.hp === this.fainted) {
if (this.fainted)
pokemon.scene.reviveCount++;
let restorePoints = this.restorePoints; let restorePoints = this.restorePoints;
if (!this.fainted) if (!this.fainted)
restorePoints = Math.floor(restorePoints * (args[1] as number)); restorePoints = Math.floor(restorePoints * (args[1] as number));

View File

@ -192,7 +192,6 @@ export class TitlePhase extends Phase {
this.scene.ui.clearText(); this.scene.ui.clearText();
this.end(); this.end();
}; };
if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) {
const options: OptionSelectItem[] = [ const options: OptionSelectItem[] = [
{ {
label: gameModes[GameModes.CLASSIC].getName(), label: gameModes[GameModes.CLASSIC].getName(),
@ -200,15 +199,27 @@ export class TitlePhase extends Phase {
setModeAndEnd(GameModes.CLASSIC); setModeAndEnd(GameModes.CLASSIC);
return true; return true;
} }
}, }
{ ];
if (this.scene.gameData.unlocks[Unlockables.NUZLOCKE_MODE] || true) {
options.push({
label: gameModes[GameModes.NUZLOCKE].getName(),
handler: () => {
this.scene.playSound('274');
setModeAndEnd(GameModes.NUZLOCKE);
return true;
}
});
}
if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) {
options.push({
label: gameModes[GameModes.ENDLESS].getName(), label: gameModes[GameModes.ENDLESS].getName(),
handler: () => { handler: () => {
setModeAndEnd(GameModes.ENDLESS); setModeAndEnd(GameModes.ENDLESS);
return true; return true;
} }
});
} }
];
if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) {
options.push({ options.push({
label: gameModes[GameModes.SPLICED_ENDLESS].getName(), label: gameModes[GameModes.SPLICED_ENDLESS].getName(),
@ -218,6 +229,16 @@ export class TitlePhase extends Phase {
} }
}); });
} }
if (this.scene.gameData.unlocks[Unlockables.GAUNTLET_MODE]) {
options.push({
label: gameModes[GameModes.GAUNTLET].getName(),
handler: () => {
setModeAndEnd(GameModes.GAUNTLET);
return true;
}
});
}
if (options.length > 1) {
options.push({ options.push({
label: i18next.t('menu:cancel'), label: i18next.t('menu:cancel'),
handler: () => { handler: () => {
@ -513,7 +534,11 @@ export class SelectStarterPhase extends Phase {
Promise.all(loadPokemonAssets).then(() => { Promise.all(loadPokemonAssets).then(() => {
SoundFade.fadeOut(this.scene, this.scene.sound.get('menu'), 500, true); SoundFade.fadeOut(this.scene, this.scene.sound.get('menu'), 500, true);
this.scene.time.delayedCall(500, () => this.scene.playBgm()); this.scene.time.delayedCall(500, () => this.scene.playBgm());
if (this.scene.gameMode.isClassic) if (this.scene.gameMode.isNuzlocke) {
this.scene.gameData.gameStats.nuzlockeSessionsPlayed++;
this.scene.addModifier(modifierTypes.MAP().withIdFromFunc(modifierTypes.MAP).newModifier(), true, false, false, true);
this.scene.updateModifiers(true, true);
} else if (this.scene.gameMode.isClassic)
this.scene.gameData.gameStats.classicSessionsPlayed++; this.scene.gameData.gameStats.classicSessionsPlayed++;
else else
this.scene.gameData.gameStats.endlessSessionsPlayed++; this.scene.gameData.gameStats.endlessSessionsPlayed++;
@ -678,6 +703,8 @@ export class EncounterPhase extends BattlePhase {
start() { start() {
super.start(); super.start();
this.scene.updateGameInfo();
this.scene.initSession(); this.scene.initSession();
const loadEnemyAssets = []; const loadEnemyAssets = [];
@ -1395,7 +1422,10 @@ export class SwitchSummonPhase extends SummonPhase {
} }
} }
if (switchedPokemon) { if (switchedPokemon) {
if (!this.scene.gameMode.isNuzlocke || this.doReturn)
party[this.slotIndex] = this.lastPokemon; party[this.slotIndex] = this.lastPokemon;
else
party.splice(this.slotIndex, 1);
party[this.fieldIndex] = switchedPokemon; party[this.fieldIndex] = switchedPokemon;
const showTextAndSummon = () => { const showTextAndSummon = () => {
this.scene.ui.showText(this.player ? this.scene.ui.showText(this.player ?
@ -3162,12 +3192,15 @@ export class FaintPhase extends PokemonPhase {
pokemon.addFriendship(-10); pokemon.addFriendship(-10);
pokemon.hideInfo(); pokemon.hideInfo();
this.scene.playSound('faint'); this.scene.playSound('faint');
this.scene.tweens.add({ this.scene.tweens.add({
targets: pokemon, targets: pokemon,
duration: 500, duration: 500,
y: pokemon.y + 150, y: pokemon.y + 150,
ease: 'Sine.easeIn', ease: 'Sine.easeIn',
onComplete: () => { onComplete: () => {
this.scene.faintCount++;
pokemon.setVisible(false); pokemon.setVisible(false);
pokemon.y -= 150; pokemon.y -= 150;
pokemon.trySetStatus(StatusEffect.FAINT); pokemon.trySetStatus(StatusEffect.FAINT);
@ -3181,7 +3214,7 @@ export class FaintPhase extends PokemonPhase {
this.end(); this.end();
} }
}); });
}); }, pokemon.isPlayer() && this.scene.gameMode.isNuzlocke);
} }
tryOverrideForBattleSpec(): boolean { tryOverrideForBattleSpec(): boolean {
@ -3214,6 +3247,7 @@ export class VictoryPhase extends PokemonPhase {
super.start(); super.start();
this.scene.gameData.gameStats.pokemonDefeated++; this.scene.gameData.gameStats.pokemonDefeated++;
this.scene.victoryCount++;
const participantIds = this.scene.currentBattle.playerParticipantIds; const participantIds = this.scene.currentBattle.playerParticipantIds;
const party = this.scene.getParty(); const party = this.scene.getParty();
@ -3526,6 +3560,8 @@ export class GameOverPhase extends BattlePhase {
handleUnlocks(): void { handleUnlocks(): void {
if (this.victory && this.scene.gameMode.isClassic) { if (this.victory && this.scene.gameMode.isClassic) {
if (!this.scene.gameData.unlocks[Unlockables.NUZLOCKE_MODE] && this.scene.victoryCount >= 250 && !this.scene.reviveCount)
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.NUZLOCKE_MODE));
if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE])
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE)); this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE));
if (this.scene.getParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) if (this.scene.getParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE])
@ -4130,6 +4166,13 @@ export class AttemptCapturePhase extends PokemonPhase {
}); });
}; };
Promise.all([ pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon) ]).then(() => { Promise.all([ pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon) ]).then(() => {
if (this.scene.gameMode.isNuzlocke && this.scene.currentBattle.waveIndex % 10 !== 1) {
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
removePokemon();
end();
});
return;
}
if (this.scene.getParty().length === 6) { if (this.scene.getParty().length === 6) {
const promptRelease = () => { const promptRelease = () => {
this.scene.ui.showText(`Your party is full.\nRelease a Pokémon to make room for ${pokemon.name}?`, null, () => { this.scene.ui.showText(`Your party is full.\nRelease a Pokémon to make room for ${pokemon.name}?`, null, () => {
@ -4291,7 +4334,7 @@ export class SelectModifierPhase extends BattlePhase {
modifierType = typeOptions[cursor].type; modifierType = typeOptions[cursor].type;
break; break;
default: default:
const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1), this.scene.gameMode);
const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT];
modifierType = shopOption.type; modifierType = shopOption.type;
cost = shopOption.cost; cost = shopOption.cost;

View File

@ -94,6 +94,9 @@ export interface SessionSaveData {
pokeballCounts: PokeballCounts; pokeballCounts: PokeballCounts;
money: integer; money: integer;
score: integer; score: integer;
victoryCount: integer;
faintCount: integer;
reviveCount: integer;
waveIndex: integer; waveIndex: integer;
battleType: BattleType; battleType: BattleType;
trainer: TrainerData; trainer: TrainerData;
@ -538,6 +541,9 @@ export class GameData {
pokeballCounts: scene.pokeballCounts, pokeballCounts: scene.pokeballCounts,
money: scene.money, money: scene.money,
score: scene.score, score: scene.score,
victoryCount: scene.victoryCount,
faintCount: scene.faintCount,
reviveCount: scene.reviveCount,
waveIndex: scene.currentBattle.waveIndex, waveIndex: scene.currentBattle.waveIndex,
battleType: scene.currentBattle.battleType, battleType: scene.currentBattle.battleType,
trainer: scene.currentBattle.battleType == BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null, trainer: scene.currentBattle.battleType == BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null,

View File

@ -8,6 +8,8 @@ export class GameStats {
public sessionsWon: integer; public sessionsWon: integer;
public dailyRunSessionsPlayed: integer; public dailyRunSessionsPlayed: integer;
public dailyRunSessionsWon: integer; public dailyRunSessionsWon: integer;
public nuzlockeSessionsPlayed: integer;
public nuzlockeSessionsWon: integer;
public endlessSessionsPlayed: integer; public endlessSessionsPlayed: integer;
public highestEndlessWave: integer; public highestEndlessWave: integer;
public highestLevel: integer; public highestLevel: integer;
@ -42,6 +44,8 @@ export class GameStats {
this.sessionsWon = source?.sessionsWon || 0; this.sessionsWon = source?.sessionsWon || 0;
this.dailyRunSessionsPlayed = source?.dailyRunSessionsPlayed || 0; this.dailyRunSessionsPlayed = source?.dailyRunSessionsPlayed || 0;
this.dailyRunSessionsWon = source?.dailyRunSessionsWon || 0; this.dailyRunSessionsWon = source?.dailyRunSessionsWon || 0;
this.nuzlockeSessionsPlayed = source?.nuzlockeSessionsPlayed || 0;
this.nuzlockeSessionsWon = source?.nuzlockeSessionsWon || 0;
this.endlessSessionsPlayed = source?.endlessSessionsPlayed || 0; this.endlessSessionsPlayed = source?.endlessSessionsPlayed || 0;
this.highestEndlessWave = source?.highestEndlessWave || 0; this.highestEndlessWave = source?.highestEndlessWave || 0;
this.highestLevel = source?.highestLevel || 0; this.highestLevel = source?.highestLevel || 0;

View File

@ -3,7 +3,9 @@ import { GameModes, gameModes } from "../game-mode";
export enum Unlockables { export enum Unlockables {
ENDLESS_MODE, ENDLESS_MODE,
MINI_BLACK_HOLE, MINI_BLACK_HOLE,
SPLICED_ENDLESS_MODE SPLICED_ENDLESS_MODE,
NUZLOCKE_MODE,
GAUNTLET_MODE
} }
export function getUnlockableName(unlockable: Unlockables) { export function getUnlockableName(unlockable: Unlockables) {
@ -14,5 +16,9 @@ export function getUnlockableName(unlockable: Unlockables) {
return 'Mini Black Hole'; return 'Mini Black Hole';
case Unlockables.SPLICED_ENDLESS_MODE: case Unlockables.SPLICED_ENDLESS_MODE:
return `${gameModes[GameModes.SPLICED_ENDLESS].getName()} Mode`; return `${gameModes[GameModes.SPLICED_ENDLESS].getName()} Mode`;
case Unlockables.NUZLOCKE_MODE:
return `${gameModes[GameModes.NUZLOCKE].getName()} Mode`;
case Unlockables.GAUNTLET_MODE:
return `${gameModes[GameModes.GAUNTLET].getName()} Mode`;
} }
} }

View File

@ -54,9 +54,11 @@ const displayStats: DisplayStats = {
sessionsWon: 'Classic Wins', sessionsWon: 'Classic Wins',
dailyRunSessionsPlayed: 'Daily Run Attempts', dailyRunSessionsPlayed: 'Daily Run Attempts',
dailyRunSessionsWon: 'Daily Run Wins', dailyRunSessionsWon: 'Daily Run Wins',
nuzlockeSessionsPlayed: 'Nuzlocke Attempts?',
nuzlockeSessionsWon: 'Nuzlocke Wins?',
endlessSessionsPlayed: 'Endless Runs?', endlessSessionsPlayed: 'Endless Runs?',
highestEndlessWave: 'Highest Wave (Endless)?', highestEndlessWave: 'Highest Wave (Endless)?',
highestMoney: 'Highest Money', highestMoney: 'Most Money',
highestDamage: 'Highest Damage', highestDamage: 'Highest Damage',
highestHeal: 'Highest HP Healed', highestHeal: 'Highest HP Healed',
pokemonSeen: 'Pokémon Encountered', pokemonSeen: 'Pokémon Encountered',

View File

@ -107,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
const typeOptions = args[1] as ModifierTypeOption[]; const typeOptions = args[1] as ModifierTypeOption[];
const shopTypeOptions = !this.scene.gameMode.hasNoShop const shopTypeOptions = !this.scene.gameMode.hasNoShop
? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)) ? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1), this.scene.gameMode)
: []; : [];
const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24; const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;

View File

@ -475,6 +475,10 @@ export default class PartyUiHandler extends MessageUiHandler {
} }
this.optionsCursorObj.setPosition(8 - this.optionsBg.displayWidth, -19 - (16 * ((this.options.length - 1) - this.optionsCursor))); this.optionsCursorObj.setPosition(8 - this.optionsBg.displayWidth, -19 - (16 * ((this.options.length - 1) - this.optionsCursor)));
} else { } else {
if (cursor < this.scene.currentBattle.getBattlerCount()) {
while (cursor < 6 && !this.partySlots[cursor].visible)
cursor++;
}
changed = this.cursor !== cursor; changed = this.cursor !== cursor;
if (changed) { if (changed) {
this.lastCursor = this.cursor; this.lastCursor = this.cursor;
@ -972,6 +976,9 @@ class PartySlot extends Phaser.GameObjects.Container {
slotInfoContainer.add(slotTmLabel); slotInfoContainer.add(slotTmLabel);
} }
if ((this.scene as BattleScene).gameMode.isNuzlocke && this.pokemon.isFainted())
this.setVisible(false);
} }
select(): void { select(): void {