diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f117615e5..a67c6777f 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 { UiTheme } from './enums/ui-theme'; import { SceneBase } from './scene-base'; import CandyBar from './ui/candy-bar'; @@ -136,6 +135,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; @@ -188,6 +190,7 @@ export default class BattleScene extends SceneBase { this.phaseQueuePrepend = []; this.phaseQueuePrependSpliceIndex = -1; this.nextCommandPhaseQueue = []; + this.updateGameInfo(); } loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) { @@ -756,6 +759,8 @@ export default class BattleScene extends SceneBase { this.trainer.setPosition(406, 186); this.trainer.setVisible(true); + this.updateGameInfo(); + if (reloadI18n) { const localizable: Localizable[] = [ ...allSpecies, @@ -882,7 +887,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) @@ -1376,10 +1381,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(); @@ -1936,4 +1941,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 88329a2c2..21099157e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1732,17 +1732,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; @@ -1751,11 +1753,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) { @@ -1766,7 +1770,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(); @@ -1789,13 +1793,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; @@ -1812,12 +1820,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; @@ -1826,11 +1833,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) { @@ -1845,7 +1854,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 81db82425..253c8c226 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 a7d68ef98..608e2e230 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -252,6 +252,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 2457a705b..e7b77e12a 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; @@ -434,7 +435,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); } } @@ -1002,14 +1007,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; }, 27), 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[]) => { @@ -1060,7 +1071,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), @@ -1338,7 +1349,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 []; @@ -1346,7 +1357,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), @@ -1358,7 +1369,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), @@ -1368,10 +1379,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 c119ba9c4..6178f12fe 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -884,7 +884,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; } } @@ -986,6 +993,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 25b0c3b75..0c4748c89 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 = []; @@ -1407,7 +1434,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 ? @@ -3202,12 +3232,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); @@ -3221,7 +3254,7 @@ export class FaintPhase extends PokemonPhase { this.end(); } }); - }); + }, pokemon.isPlayer() && this.scene.gameMode.isNuzlocke); } tryOverrideForBattleSpec(): boolean { @@ -3254,6 +3287,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(); @@ -3609,6 +3643,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]) @@ -4225,6 +4261,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, () => { @@ -4386,7 +4429,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 e342f337a..447a3b986 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; @@ -537,6 +540,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 4c3f40e5d..8581357f3 100644 --- a/src/system/game-stats.ts +++ b/src/system/game-stats.ts @@ -9,6 +9,8 @@ export class GameStats { public ribbonsOwned: integer; public dailyRunSessionsPlayed: integer; public dailyRunSessionsWon: integer; + public nuzlockeSessionsPlayed: integer; + public nuzlockeSessionsWon: integer; public endlessSessionsPlayed: integer; public highestEndlessWave: integer; public highestLevel: integer; @@ -47,6 +49,8 @@ export class GameStats { this.ribbonsOwned = source?.ribbonsOwned || 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 a186b2bec..6ad80c8be 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 c053d5700..6e9917097 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -56,9 +56,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 8af13d8f6..124c4b1a7 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -108,7 +108,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 8b497655a..462f1b02f 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -476,6 +476,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; @@ -973,6 +977,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 {