Added permadeath gamemode modifier

- add enable/disable modifier to starter UI
- Modifiers get saved/loaded
- permadeath:
 - potions don't show up in shop/rewards
 - every 10th wave does not heal dead pokemon
 - achievement for winning classic with permadeath enabled
This commit is contained in:
EnslavedTuna 2024-04-10 17:11:21 +02:00
parent ca778e07d5
commit 411786bf20
8 changed files with 98 additions and 17 deletions

View File

@ -22,6 +22,9 @@ import AbilityBar from './ui/ability-bar';
import { Abilities, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } from './data/ability'; import { Abilities, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, applyAbAttrs, initAbilities } 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';
import GameModeModifiers from './system/game-mode-modifiers';
import FieldSpritePipeline from './pipelines/field-sprite'; import FieldSpritePipeline from './pipelines/field-sprite';
import SpritePipeline from './pipelines/sprite'; import SpritePipeline from './pipelines/sprite';
import PartyExpBar from './ui/party-exp-bar'; import PartyExpBar from './ui/party-exp-bar';
@ -149,6 +152,9 @@ export default class BattleScene extends SceneBase {
public arenaNextEnemy: ArenaBase; public arenaNextEnemy: ArenaBase;
public arena: Arena; public arena: Arena;
public gameMode: GameMode; public gameMode: GameMode;
public gameModeModifiers: GameModeModifiers;
public score: integer; public score: integer;
public lockModifierTiers: boolean; public lockModifierTiers: boolean;
public trainer: Phaser.GameObjects.Sprite; public trainer: Phaser.GameObjects.Sprite;

View File

@ -959,14 +959,14 @@ const modifierPool: ModifierPool = {
}, 18), }, 18),
new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => {
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 party[0].scene.gameModeModifiers.isPermaDeath? 0 : faintedPartyMemberCount * 9;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => {
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 party[0].scene.gameModeModifiers.isPermaDeath ? 0 : faintedPartyMemberCount * 3;
}, 9), }, 9),
new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => {
return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; return party[0].scene.gameModeModifiers.isPermaDeath ? 0 : 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[]) => {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3);
@ -1286,7 +1286,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, permadeath:boolean = false): ModifierTypeOption[] {
if (!(waveIndex % 10)) if (!(waveIndex % 10))
return []; return [];
@ -1294,7 +1294,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) ... !permadeath ? [new ModifierTypeOption(modifierTypes.REVIVE(), 0, baseCost * 2)] : [],
], ],
[ [
new ModifierTypeOption(modifierTypes.SUPER_POTION(), 0, baseCost * 0.45), new ModifierTypeOption(modifierTypes.SUPER_POTION(), 0, baseCost * 0.45),
@ -1306,7 +1306,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) ... !permadeath ? [new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75)] : [],
], ],
[ [
new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5), new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5),
@ -1316,7 +1316,7 @@ 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 * 12) ... !permadeath ? [new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 12)] : []
] ]
]; ];
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();

View File

@ -3293,6 +3293,9 @@ export class GameOverPhase extends BattlePhase {
if (this.scene.gameMode.isClassic) { if (this.scene.gameMode.isClassic) {
firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY); firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY);
this.scene.gameData.gameStats.sessionsWon++; this.scene.gameData.gameStats.sessionsWon++;
if (this.scene.gameModeModifiers.isPermaDeath){
this.scene.validateAchv(achvs.PERMADEATH_VICTORY);
}
} else if (this.scene.gameMode.isDaily && success[1]) } else if (this.scene.gameMode.isDaily && success[1])
this.scene.gameData.gameStats.dailyRunSessionsWon++; this.scene.gameData.gameStats.dailyRunSessionsWon++;
} }
@ -4065,7 +4068,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.gameModeModifiers.isPermaDeath);
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;
@ -4228,6 +4231,7 @@ export class PartyHealPhase extends BattlePhase {
this.scene.fadeOutBgm(1000, false); this.scene.fadeOutBgm(1000, false);
this.scene.ui.fadeOut(1000).then(() => { this.scene.ui.fadeOut(1000).then(() => {
for (let pokemon of this.scene.getParty()) { for (let pokemon of this.scene.getParty()) {
if (!this.scene.gameModeModifiers.isPermaDeath && pokemon.hp == 0){
pokemon.hp = pokemon.getMaxHp(); pokemon.hp = pokemon.getMaxHp();
pokemon.resetStatus(); pokemon.resetStatus();
for (let move of pokemon.moveset) for (let move of pokemon.moveset)
@ -4241,7 +4245,7 @@ export class PartyHealPhase extends BattlePhase {
this.scene.playBgm(); this.scene.playBgm();
this.scene.ui.fadeIn(500).then(() => this.end()); this.scene.ui.fadeIn(500).then(() => this.end());
}); });
}); }});
} }
} }

View File

@ -139,7 +139,8 @@ export const achvs = {
HATCH_SHINY: new Achv('Shiny Egg', 'Hatch a shiny Pokémon from an egg', 'golden_mystic_ticket', 100).setSecret(), HATCH_SHINY: new Achv('Shiny Egg', 'Hatch a shiny Pokémon from an egg', 'golden_mystic_ticket', 100).setSecret(),
HIDDEN_ABILITY: new Achv('Hidden Potential', 'Catch a Pokémon with a hidden ability', 'ability_charm', 75), HIDDEN_ABILITY: new Achv('Hidden Potential', 'Catch a Pokémon with a hidden ability', 'ability_charm', 75),
PERFECT_IVS: new Achv('Certificate of Authenticity', 'Get perfect IVs on a Pokémon', 'blunder_policy', 100), PERFECT_IVS: new Achv('Certificate of Authenticity', 'Get perfect IVs on a Pokémon', 'blunder_policy', 100),
CLASSIC_VICTORY: new Achv('Undefeated', 'Beat the game in classic mode', 'relic_crown', 150) CLASSIC_VICTORY: new Achv('Undefeated', 'Beat the game in classic mode', 'relic_crown', 150),
PERMADEATH_VICTORY: new Achv('Mr. Fuji', 'Beat the game using the permadeath modifier', 'silph_scope', 50)
}; };
{ {

View File

@ -9,6 +9,7 @@ import PersistentModifierData from "./modifier-data";
import ArenaData from "./arena-data"; import ArenaData from "./arena-data";
import { Unlockables } from "./unlockables"; import { Unlockables } from "./unlockables";
import { GameModes, gameModes } from "../game-mode"; import { GameModes, gameModes } from "../game-mode";
import GameModeModifiers from "./game-mode-modifiers";
import { BattleType } from "../battle"; import { BattleType } from "../battle";
import TrainerData from "./trainer-data"; import TrainerData from "./trainer-data";
import { trainerConfigs } from "../data/trainer-config"; import { trainerConfigs } from "../data/trainer-config";
@ -80,6 +81,7 @@ export interface SessionSaveData {
seed: string; seed: string;
playTime: integer; playTime: integer;
gameMode: GameModes; gameMode: GameModes;
gameModeModiefiers: GameModeModifiers
party: PokemonData[]; party: PokemonData[];
enemyParty: PokemonData[]; enemyParty: PokemonData[];
modifiers: PersistentModifierData[]; modifiers: PersistentModifierData[];
@ -468,6 +470,7 @@ export class GameData {
seed: scene.seed, seed: scene.seed,
playTime: scene.sessionPlayTime, playTime: scene.sessionPlayTime,
gameMode: scene.gameMode.modeId, gameMode: scene.gameMode.modeId,
gameModeModiefiers: scene.gameModeModifiers,
party: scene.getParty().map(p => new PokemonData(p)), party: scene.getParty().map(p => new PokemonData(p)),
enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)), enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)),
modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
@ -554,6 +557,7 @@ export class GameData {
console.debug(sessionData); console.debug(sessionData);
scene.gameMode = gameModes[sessionData.gameMode || GameModes.CLASSIC]; scene.gameMode = gameModes[sessionData.gameMode || GameModes.CLASSIC];
scene.gameModeModifiers = sessionData.gameModeModiefiers || new GameModeModifiers({})
scene.setSeed(sessionData.seed || scene.game.config.seed[0]); scene.setSeed(sessionData.seed || scene.game.config.seed[0]);
scene.resetSeed(); scene.resetSeed();

View File

@ -0,0 +1,17 @@
import { Type } from "../data/type";
interface GameModeModifiersConfig {
isPermaDeath?: boolean;
isMonotype?: boolean;
monoTypeType?: Type;
}
export default class GameModeModifiers {
public isPermaDeath: boolean;
public isMonotype: boolean;
public monoTypeType: Type;
constructor(config: GameModeModifiersConfig) {
Object.assign(this, config);
}
}

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.gameModeModifiers.isPermaDeath)
: []; : [];
const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24; const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;

View File

@ -24,6 +24,7 @@ import { Type } from "../data/type";
import { Moves } from "../data/enums/moves"; import { Moves } from "../data/enums/moves";
import { speciesEggMoves } from "../data/egg-moves"; import { speciesEggMoves } from "../data/egg-moves";
import { TitlePhase } from "../phases"; import { TitlePhase } from "../phases";
import GameModeModifiers from "../system/game-mode-modifiers";
export type StarterSelectCallback = (starters: Starter[]) => void; export type StarterSelectCallback = (starters: Starter[]) => void;
@ -112,6 +113,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private starterSelectCallback: StarterSelectCallback; private starterSelectCallback: StarterSelectCallback;
private gameMode: GameModes; private gameMode: GameModes;
private gameModeModifierConfig = {isPermaDeath: false}
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
super(scene, Mode.STARTER_SELECT); super(scene, Mode.STARTER_SELECT);
@ -1264,7 +1266,54 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return true; return true;
} }
showRunMods(manualTrigger: boolean = false, permadeath: boolean = false): boolean {
const ui = this.getUi();
let options = [];
options.push(
{
label: permadeath ? 'Permadeath (enabled)' : 'Permadeath (disabled)',
handler: () => {
ui.setMode(Mode.STARTER_SELECT);
this.gameModeModifierConfig.isPermaDeath = !permadeath
this.showRunMods(manualTrigger,!permadeath)
}
})
options.push(
{
label: 'Done',
handler: () => {
this.scene.gameModeModifiers = new GameModeModifiers(this.gameModeModifierConfig)
ui.setMode(Mode.STARTER_SELECT);
this.tryStartRun(manualTrigger)
}
})
ui.showText('Select desired run modifiers', null, () => {
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: options,
yOffset: 20
});
});
return true
}
tryStart(manualTrigger: boolean = false): boolean{ tryStart(manualTrigger: boolean = false): boolean{
const cancel = () => {
this.clearText();
ui.setMode(Mode.STARTER_SELECT);
this.tryStartRun(manualTrigger)
};
const ui = this.getUi();
ui.showText('Enable custom game modifiers?', null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => {
ui.setMode(Mode.STARTER_SELECT);
this.showRunMods(manualTrigger)
}, cancel, null, null, 19);
});
return true
}
tryStartRun(manualTrigger: boolean = false): boolean {
if (!this.starterGens.length) if (!this.starterGens.length)
return false; return false;