From ef6c3541328986983b2659bba63c1cd4371b5046 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sat, 4 May 2024 12:15:45 -0400 Subject: [PATCH] Add foundation for Nuzlocke Mode (WiP) Add foundation for Nuzlocke Mode (WiP); lower berry caps; add global object for rich presence --- src/battle-scene.ts | 27 ++++++-- src/field/pokemon.ts | 33 ++++++---- src/game-mode.ts | 26 +++++++- src/loading-scene.ts | 3 + src/modifier/modifier-type.ts | 27 +++++--- src/modifier/modifier.ts | 11 +++- src/phases.ts | 99 ++++++++++++++++++++-------- src/system/game-data.ts | 6 ++ src/system/game-stats.ts | 4 ++ src/system/unlockables.ts | 8 ++- src/ui/game-stats-ui-handler.ts | 4 +- src/ui/modifier-select-ui-handler.ts | 2 +- src/ui/party-ui-handler.ts | 7 ++ 13 files changed, 198 insertions(+), 59 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cbf363f689a..ed30dcee79f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -20,7 +20,6 @@ import { initMoves } from './data/move'; import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from './modifier/modifier-type'; import AbilityBar from './ui/ability-bar'; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability'; -import { Abilities } from "./data/enums/abilities"; import { allAbilities } from "./data/ability"; import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle'; 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 DamageNumberHandler from './field/damage-number-handler'; import PokemonInfoContainer from './ui/pokemon-info-container'; -import { biomeDepths } from './data/biomes'; +import { biomeDepths, getBiomeName } from './data/biomes'; import { initTouchControls } from './touch-controls'; import { UiTheme } from './enums/ui-theme'; import { SceneBase } from './scene-base'; @@ -153,6 +152,9 @@ export default class BattleScene extends SceneBase { public arena: Arena; public gameMode: GameMode; public score: integer; + public victoryCount: integer; + public faintCount: integer; + public reviveCount: integer; public lockModifierTiers: boolean; public trainer: Phaser.GameObjects.Sprite; public lastEnemyTrainer: Trainer; @@ -233,6 +235,7 @@ export default class BattleScene extends SceneBase { this.phaseQueuePrepend = []; this.phaseQueuePrependSpliceIndex = -1; this.nextCommandPhaseQueue = []; + this.updateGameInfo(); } loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) { @@ -837,6 +840,8 @@ export default class BattleScene extends SceneBase { this.trainer.setPosition(406, 186); this.trainer.setVisible(true); + this.updateGameInfo(); + if (reloadI18n) { const localizable: Localizable[] = [ ...allSpecies, @@ -960,7 +965,7 @@ export default class BattleScene extends SceneBase { isNewBiome = !Utils.randSeedInt(6 - biomeWaves); }, 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.trySpreadPokerus(); 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); const resumeBgm = this.pauseBgm(); - this.playSound(soundName); + this.playSound(soundName, config); const sound = this.sound.get(soundName) as AnySound; if (this.bgmResumeTimer) this.bgmResumeTimer.destroy(); @@ -2183,4 +2188,16 @@ export default class BattleScene extends SceneBase { 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; + } } \ No newline at end of file diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index adbe8a8d893..e01088152f0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1702,17 +1702,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return cry; } - faintCry(callback: Function): void { + faintCry(callback: Function, forPermadeath: boolean = false): void { if (this.fusionSpecies) - return this.fusionFaintCry(callback); + return this.fusionFaintCry(callback, forPermadeath); const key = this.getSpeciesForm().getCryKey(this.formIndex); - let i = 0; 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 tintSprite = this.getTintSprite(); + const decayRate = !forPermadeath ? 0.99 : 0.98; const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25); let frameProgress = 0; @@ -1721,11 +1723,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite.anims.pause(); + if (forPermadeath) + this.tint(0, 0.325, Utils.fixedInt(1500), 'Sine.easeIn'); + let faintCryTimer = this.scene.time.addEvent({ delay: Utils.fixedInt(delay), repeat: -1, callback: () => { - ++i; frameThreshold = sprite.anims.msPerFrame / rate; frameProgress += delay; while (frameProgress > frameThreshold) { @@ -1736,7 +1740,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { frameProgress -= frameThreshold; } if (cry && !cry.pendingRemove) { - rate *= 0.99; + rate *= decayRate; cry.setRate(rate); } else { 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); let i = 0; 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 tintSprite = this.getTintSprite(); + + const decayRate = !forPermadeath ? 0.99 : 0.98; let duration = cry.totalDuration * 1000; 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) { ++i; durationProgress += delay * rate; - rate *= 0.99; + rate *= decayRate; } transitionIndex = i; - i = 0; rate = 0.85; let frameProgress = 0; @@ -1796,11 +1803,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite.anims.pause(); + if (forPermadeath) + this.tint(0, 0.325, Utils.fixedInt(1500), 'Sine.easeIn'); + let faintCryTimer = this.scene.time.addEvent({ delay: Utils.fixedInt(delay), repeat: -1, callback: () => { - ++i; frameThreshold = sprite.anims.msPerFrame / rate; frameProgress += delay; 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 })); 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) cry.setRate(rate); if (fusionCry && !fusionCry.pendingRemove) diff --git a/src/game-mode.ts b/src/game-mode.ts index 76c97b4c9ac..319f608b52f 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -11,16 +11,23 @@ export enum GameModes { CLASSIC, ENDLESS, SPLICED_ENDLESS, - DAILY + DAILY, + NUZLOCKE, + GAUNTLET } interface GameModeConfig { isClassic?: boolean; isEndless?: boolean; isDaily?: boolean; + isNuzlocke?: boolean; + hasNoHeals?: boolean; + hasNoReturns?: boolean; hasTrainers?: boolean; hasFixedBattles?: boolean; hasNoShop?: boolean; + hasStrictLevelCap?: boolean; + hasPassiveHeals?: boolean; hasShortBiomes?: boolean; hasRandomBiomes?: boolean; hasRandomBosses?: boolean; @@ -32,9 +39,14 @@ export class GameMode implements GameModeConfig { public isClassic: boolean; public isEndless: boolean; public isDaily: boolean; + public isNuzlocke: boolean; + public hasNoHeals: boolean; + public hasNoReturns: boolean; public hasTrainers: boolean; public hasFixedBattles: boolean; public hasNoShop: boolean; + public hasStrictLevelCap: boolean; + public hasPassiveHeals: boolean; public hasShortBiomes: boolean; public hasRandomBiomes: boolean; public hasRandomBosses: boolean; @@ -132,9 +144,11 @@ export class GameMode implements GameModeConfig { isWaveFinal(waveIndex: integer): boolean { switch (this.modeId) { case GameModes.CLASSIC: + case GameModes.NUZLOCKE: return waveIndex === 200; case GameModes.ENDLESS: case GameModes.SPLICED_ENDLESS: + case GameModes.GAUNTLET: return !(waveIndex % 250); case GameModes.DAILY: return waveIndex === 50; @@ -154,9 +168,11 @@ export class GameMode implements GameModeConfig { switch (this.modeId) { case GameModes.CLASSIC: case GameModes.DAILY: + case GameModes.NUZLOCKE: return !isBoss ? 18 : 6; case GameModes.ENDLESS: case GameModes.SPLICED_ENDLESS: + case GameModes.GAUNTLET: return !isBoss ? 12 : 4; } } @@ -171,6 +187,10 @@ export class GameMode implements GameModeConfig { return 'Endless (Spliced)'; case GameModes.DAILY: 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.ENDLESS]: new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }), [GameModes.SPLICED_ENDLESS]: new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }), - [GameModes.DAILY]: new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }) + [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 }) }); \ No newline at end of file diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 05b6c9f50b1..4cac3e81777 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -249,6 +249,9 @@ export class LoadingScene extends SceneBase { this.loadSe('gacha_running'); this.loadSe('gacha_dispense'); + // Nuzleaf + this.loadSe('274', '', '../cry/274.m4a'); + this.loadSe('PRSFX- Transform', 'battle_anims'); this.loadBgm('menu'); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 2e18cd91759..72ed16eaafd 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -3,7 +3,7 @@ import { AttackMove, allMoves } from '../data/move'; import { Moves } from "../data/enums/moves"; import { PokeballType, getPokeballCatchMultiplier, getPokeballName } from '../data/pokeball'; 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 { tmPoolTiers, tmSpecies } from '../data/tms'; import { Type } from '../data/type'; @@ -21,6 +21,7 @@ import { ModifierTier } from './modifier-tier'; import { Nature, getNatureName, getNatureStatMultiplier } from '#app/data/nature'; import { Localizable } from '#app/plugins/i18n'; import { getModifierTierTextTint } from '#app/ui/text'; +import { GameMode } from '#app/game-mode'; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -427,7 +428,11 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i export class PokemonLevelIncrementModifierType extends PokemonModifierType { 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), - (_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; }, 18), 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); return faintedPartyMemberCount * 9; }, 3), 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); return faintedPartyMemberCount * 3; }, 9), 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; }, 1), new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => { @@ -1053,7 +1064,7 @@ const modifierPool: ModifierPool = { 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.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.ATTACK_TYPE_BOOSTER, 10), new WeightedModifierType(modifierTypes.TM_ULTRA, 8), @@ -1331,7 +1342,7 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo return options; } -export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer): ModifierTypeOption[] { +export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer, gameMode: GameMode): ModifierTypeOption[] { if (!(waveIndex % 10)) return []; @@ -1339,7 +1350,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base [ new ModifierTypeOption(modifierTypes.POTION(), 0, baseCost * 0.2), 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), @@ -1351,7 +1362,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base ], [ 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), @@ -1361,10 +1372,10 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base 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 { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 88b009d3a48..d817966cba3 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -885,7 +885,14 @@ export class BerryModifier extends PokemonHeldItemModifier { } 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 { const pokemon = args[0] as Pokemon; if (!pokemon.hp === this.fainted) { + if (this.fainted) + pokemon.scene.reviveCount++; let restorePoints = this.restorePoints; if (!this.fainted) restorePoints = Math.floor(restorePoints * (args[1] as number)); diff --git a/src/phases.ts b/src/phases.ts index bfe309878e5..48446923ba2 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -192,32 +192,53 @@ export class TitlePhase extends Phase { this.scene.ui.clearText(); this.end(); }; - if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - const options: OptionSelectItem[] = [ - { - label: gameModes[GameModes.CLASSIC].getName(), - handler: () => { - setModeAndEnd(GameModes.CLASSIC); - return true; - } - }, - { - label: gameModes[GameModes.ENDLESS].getName(), - handler: () => { - setModeAndEnd(GameModes.ENDLESS); - return true; - } + const options: OptionSelectItem[] = [ + { + label: gameModes[GameModes.CLASSIC].getName(), + handler: () => { + setModeAndEnd(GameModes.CLASSIC); + return true; } - ]; - if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { - options.push({ - label: gameModes[GameModes.SPLICED_ENDLESS].getName(), - handler: () => { - setModeAndEnd(GameModes.SPLICED_ENDLESS); - 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(), + handler: () => { + setModeAndEnd(GameModes.ENDLESS); + return true; + } + }); + } + if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { + options.push({ + label: gameModes[GameModes.SPLICED_ENDLESS].getName(), + handler: () => { + setModeAndEnd(GameModes.SPLICED_ENDLESS); + return true; + } + }); + } + 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({ label: i18next.t('menu:cancel'), handler: () => { @@ -513,7 +534,11 @@ export class SelectStarterPhase extends Phase { Promise.all(loadPokemonAssets).then(() => { SoundFade.fadeOut(this.scene, this.scene.sound.get('menu'), 500, true); 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++; else this.scene.gameData.gameStats.endlessSessionsPlayed++; @@ -678,6 +703,8 @@ export class EncounterPhase extends BattlePhase { start() { super.start(); + this.scene.updateGameInfo(); + this.scene.initSession(); const loadEnemyAssets = []; @@ -1395,7 +1422,10 @@ export class SwitchSummonPhase extends SummonPhase { } } if (switchedPokemon) { - party[this.slotIndex] = this.lastPokemon; + if (!this.scene.gameMode.isNuzlocke || this.doReturn) + party[this.slotIndex] = this.lastPokemon; + else + party.splice(this.slotIndex, 1); party[this.fieldIndex] = switchedPokemon; const showTextAndSummon = () => { this.scene.ui.showText(this.player ? @@ -3162,12 +3192,15 @@ export class FaintPhase extends PokemonPhase { pokemon.addFriendship(-10); pokemon.hideInfo(); this.scene.playSound('faint'); + this.scene.tweens.add({ targets: pokemon, duration: 500, y: pokemon.y + 150, ease: 'Sine.easeIn', onComplete: () => { + this.scene.faintCount++; + pokemon.setVisible(false); pokemon.y -= 150; pokemon.trySetStatus(StatusEffect.FAINT); @@ -3181,7 +3214,7 @@ export class FaintPhase extends PokemonPhase { this.end(); } }); - }); + }, pokemon.isPlayer() && this.scene.gameMode.isNuzlocke); } tryOverrideForBattleSpec(): boolean { @@ -3214,6 +3247,7 @@ export class VictoryPhase extends PokemonPhase { super.start(); this.scene.gameData.gameStats.pokemonDefeated++; + this.scene.victoryCount++; const participantIds = this.scene.currentBattle.playerParticipantIds; const party = this.scene.getParty(); @@ -3526,6 +3560,8 @@ export class GameOverPhase extends BattlePhase { handleUnlocks(): void { 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]) 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]) @@ -4130,6 +4166,13 @@ export class AttemptCapturePhase extends PokemonPhase { }); }; 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) { const promptRelease = () => { 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; break; 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]; modifierType = shopOption.type; cost = shopOption.cost; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 6410c52a2b8..76d6f5da407 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -94,6 +94,9 @@ export interface SessionSaveData { pokeballCounts: PokeballCounts; money: integer; score: integer; + victoryCount: integer; + faintCount: integer; + reviveCount: integer; waveIndex: integer; battleType: BattleType; trainer: TrainerData; @@ -538,6 +541,9 @@ export class GameData { pokeballCounts: scene.pokeballCounts, money: scene.money, score: scene.score, + victoryCount: scene.victoryCount, + faintCount: scene.faintCount, + reviveCount: scene.reviveCount, waveIndex: scene.currentBattle.waveIndex, battleType: scene.currentBattle.battleType, trainer: scene.currentBattle.battleType == BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null, diff --git a/src/system/game-stats.ts b/src/system/game-stats.ts index 9a564b3c3b8..d74577838af 100644 --- a/src/system/game-stats.ts +++ b/src/system/game-stats.ts @@ -8,6 +8,8 @@ export class GameStats { public sessionsWon: integer; public dailyRunSessionsPlayed: integer; public dailyRunSessionsWon: integer; + public nuzlockeSessionsPlayed: integer; + public nuzlockeSessionsWon: integer; public endlessSessionsPlayed: integer; public highestEndlessWave: integer; public highestLevel: integer; @@ -42,6 +44,8 @@ export class GameStats { this.sessionsWon = source?.sessionsWon || 0; this.dailyRunSessionsPlayed = source?.dailyRunSessionsPlayed || 0; this.dailyRunSessionsWon = source?.dailyRunSessionsWon || 0; + this.nuzlockeSessionsPlayed = source?.nuzlockeSessionsPlayed || 0; + this.nuzlockeSessionsWon = source?.nuzlockeSessionsWon || 0; this.endlessSessionsPlayed = source?.endlessSessionsPlayed || 0; this.highestEndlessWave = source?.highestEndlessWave || 0; this.highestLevel = source?.highestLevel || 0; diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index a186b2bec02..6ad80c8be36 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -3,7 +3,9 @@ import { GameModes, gameModes } from "../game-mode"; export enum Unlockables { ENDLESS_MODE, MINI_BLACK_HOLE, - SPLICED_ENDLESS_MODE + SPLICED_ENDLESS_MODE, + NUZLOCKE_MODE, + GAUNTLET_MODE } export function getUnlockableName(unlockable: Unlockables) { @@ -14,5 +16,9 @@ export function getUnlockableName(unlockable: Unlockables) { return 'Mini Black Hole'; case Unlockables.SPLICED_ENDLESS_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`; } } \ No newline at end of file diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 00e358ff8e4..fbac5a3bc8d 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -54,9 +54,11 @@ const displayStats: DisplayStats = { sessionsWon: 'Classic Wins', dailyRunSessionsPlayed: 'Daily Run Attempts', dailyRunSessionsWon: 'Daily Run Wins', + nuzlockeSessionsPlayed: 'Nuzlocke Attempts?', + nuzlockeSessionsWon: 'Nuzlocke Wins?', endlessSessionsPlayed: 'Endless Runs?', highestEndlessWave: 'Highest Wave (Endless)?', - highestMoney: 'Highest Money', + highestMoney: 'Most Money', highestDamage: 'Highest Damage', highestHeal: 'Highest HP Healed', pokemonSeen: 'Pokémon Encountered', diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index e5252e02a8f..f3b6ed7c001 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -107,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const typeOptions = args[1] as ModifierTypeOption[]; 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; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 35014fa7027..d1f49d0b55e 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -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))); } else { + if (cursor < this.scene.currentBattle.getBattlerCount()) { + while (cursor < 6 && !this.partySlots[cursor].visible) + cursor++; + } changed = this.cursor !== cursor; if (changed) { this.lastCursor = this.cursor; @@ -972,6 +976,9 @@ class PartySlot extends Phaser.GameObjects.Container { slotInfoContainer.add(slotTmLabel); } + + if ((this.scene as BattleScene).gameMode.isNuzlocke && this.pokemon.isFainted()) + this.setVisible(false); } select(): void {