pokerogue/src/modifier.ts

893 lines
30 KiB
TypeScript
Raw Normal View History

2023-04-10 14:12:01 -04:00
import { LearnMovePhase, LevelUpPhase } from "./battle-phases";
2023-03-28 14:54:52 -04:00
import BattleScene from "./battle-scene";
2023-04-04 18:28:21 -04:00
import { getLevelTotalExp } from "./exp";
2023-04-08 00:21:44 -04:00
import { allMoves, Moves } from "./move";
2023-03-28 14:54:52 -04:00
import { getPokeballName, PokeballType } from "./pokeball";
2023-04-11 11:04:39 -04:00
import Pokemon, { PlayerPokemon, PokemonMove } from "./pokemon";
2023-03-28 14:54:52 -04:00
import { Stat, getStatName } from "./pokemon-stat";
import { addTextObject, TextStyle } from "./text";
2023-04-08 00:21:44 -04:00
import { tmSpecies } from "./tms";
import { Type } from "./type";
2023-04-11 11:04:39 -04:00
import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "./ui/party-ui-handler";
2023-03-28 14:54:52 -04:00
import * as Utils from "./utils";
export class ModifierBar extends Phaser.GameObjects.Container {
constructor(scene: BattleScene) {
super(scene, 1, 2);
this.setScale(0.5);
}
2023-04-09 19:15:21 -04:00
updateModifiers(modifiers: PersistentModifier[]) {
this.removeAll(true);
for (let modifier of modifiers) {
const icon = modifier.getIcon(this.scene as BattleScene);
this.add(icon);
this.setModifierIconPosition(icon);
2023-03-28 14:54:52 -04:00
}
}
setModifierIconPosition(icon: Phaser.GameObjects.Container) {
const x = (this.getIndex(icon) % 12) * 26;
const y = Math.floor((this.getIndex(icon) * 6) / (this.scene.game.canvas.width / 6)) * 20;
icon.setPosition(x, y);
}
}
export abstract class Modifier {
public type: ModifierType;
constructor(type: ModifierType) {
this.type = type;
}
2023-04-09 19:15:21 -04:00
match(_modifier: Modifier): boolean {
return false;
2023-03-28 14:54:52 -04:00
}
shouldApply(_args: any[]): boolean {
return true;
}
abstract apply(args: any[]): boolean;
2023-04-09 19:15:21 -04:00
}
export abstract class PersistentModifier extends Modifier {
public stackCount: integer;
public virtualStackCount: integer;
constructor(type: ModifierType) {
super(type);
this.stackCount = 1;
this.virtualStackCount = 0;
}
add(modifiers: PersistentModifier[], virtual: boolean): boolean {
for (let modifier of modifiers) {
if (this.match(modifier)) {
modifier.incrementStack(virtual);
return true;
}
}
if (virtual) {
this.virtualStackCount += this.stackCount;
this.stackCount = 0;
}
modifiers.push(this);
return true;
}
abstract clone(): PersistentModifier;
incrementStack(virtual: boolean): void {
if (this.getStackCount() < this.getMaxStackCount()) {
if (!virtual)
this.stackCount++;
else
this.virtualStackCount++;
}
}
2023-03-28 14:54:52 -04:00
2023-04-09 19:15:21 -04:00
getStackCount(): integer {
return this.stackCount + this.virtualStackCount;
2023-03-30 23:02:35 -04:00
}
getMaxStackCount(): integer {
return 99;
2023-03-28 14:54:52 -04:00
}
getIcon(scene: BattleScene): Phaser.GameObjects.Container {
const container = scene.add.container(0, 0);
const item = scene.add.sprite(0, 12, 'items');
item.setFrame(this.type.iconImage);
item.setOrigin(0, 0.5);
container.add(item);
const stackText = this.getIconStackText(scene);
if (stackText)
container.add(stackText);
2023-04-09 19:15:21 -04:00
const virtualStackText = this.getIconStackText(scene, true);
if (virtualStackText)
container.add(virtualStackText);
2023-03-28 14:54:52 -04:00
return container;
}
2023-04-09 19:15:21 -04:00
getIconStackText(scene: BattleScene, virtual?: boolean): Phaser.GameObjects.Text {
if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount))
2023-03-28 14:54:52 -04:00
return null;
2023-04-09 19:15:21 -04:00
const isStackMax = this.getStackCount() >= this.getMaxStackCount();
const maxColor = '#f89890';
const maxStrokeColor = '#984038';
if (virtual) {
const virtualText = addTextObject(scene, 1 * 11 + 16, 12, `+${this.virtualStackCount.toString()}`, TextStyle.PARTY, { fontSize: '66px', color: !isStackMax ? '#40c8f8' : maxColor });
virtualText.setShadow(0, 0, null);
virtualText.setStroke(!isStackMax ? '#006090' : maxStrokeColor, 16)
virtualText.setOrigin(1, 0);
return virtualText;
}
const text = addTextObject(scene, 8, 12, this.stackCount.toString(), TextStyle.PARTY, { fontSize: '66px', color: !isStackMax ? '#f8f8f8' : maxColor });
text.setShadow(0, 0, null);
2023-03-28 14:54:52 -04:00
text.setStroke('#424242', 16)
2023-04-09 19:15:21 -04:00
text.setOrigin(0, 0);
2023-03-28 14:54:52 -04:00
return text;
}
}
export abstract class ConsumableModifier extends Modifier {
constructor(type: ModifierType) {
super(type);
}
2023-04-09 19:15:21 -04:00
add(_modifiers: Modifier[]): boolean {
2023-03-28 14:54:52 -04:00
return true;
}
shouldApply(args: any[]): boolean {
2023-03-30 23:02:35 -04:00
return super.shouldApply(args) && args.length === 1 && args[0] instanceof BattleScene;
2023-03-28 14:54:52 -04:00
}
}
class AddPokeballModifier extends ConsumableModifier {
private pokeballType: PokeballType;
private count: integer;
constructor(type: ModifierType, pokeballType: PokeballType, count: integer) {
super(type);
this.pokeballType = pokeballType;
this.count = count;
}
apply(args: any[]): boolean {
2023-04-01 22:59:07 -04:00
const pokeballCounts = (args[0] as BattleScene).pokeballCounts;
pokeballCounts[this.pokeballType] = Math.min(pokeballCounts[this.pokeballType] + this.count, 99);
2023-03-28 14:54:52 -04:00
return true;
}
}
2023-04-09 19:15:21 -04:00
export abstract class PokemonHeldItemModifier extends PersistentModifier {
2023-03-28 14:54:52 -04:00
public pokemonId: integer;
constructor(type: ModifierType, pokemonId: integer) {
super(type);
this.pokemonId = pokemonId;
}
shouldApply(args: any[]): boolean {
2023-03-30 23:02:35 -04:00
return super.shouldApply(args) && args.length && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId);
2023-03-28 14:54:52 -04:00
}
getIcon(scene: BattleScene): Phaser.GameObjects.Container {
const container = scene.add.container(0, 0);
const pokemon = this.getPokemon(scene);
2023-04-09 19:15:21 -04:00
const pokemonIcon = scene.add.sprite(0, 8, pokemon.species.getIconAtlasKey());
pokemonIcon.play(pokemon.species.getIconKey()).stop();
2023-03-28 14:54:52 -04:00
pokemonIcon.setOrigin(0, 0.5);
container.add(pokemonIcon);
return container;
}
getPokemon(scene: BattleScene) {
return scene.getParty().find(p => p.id === this.pokemonId);
}
}
2023-04-09 19:15:21 -04:00
export class PokemonBaseStatModifier extends PokemonHeldItemModifier {
2023-03-28 14:54:52 -04:00
protected stat: Stat;
constructor(type: PokemonBaseStatBoosterModifierType, pokemonId: integer, stat: Stat) {
super(type, pokemonId);
this.stat = stat;
}
2023-04-09 19:15:21 -04:00
match(modifier: Modifier): boolean {
if (modifier instanceof PokemonBaseStatModifier) {
const pokemonStatModifier = modifier as PokemonBaseStatModifier;
return pokemonStatModifier.pokemonId === this.pokemonId && pokemonStatModifier.stat === this.stat;
2023-03-28 14:54:52 -04:00
}
2023-04-09 19:15:21 -04:00
return false;
}
2023-03-28 14:54:52 -04:00
2023-04-09 19:15:21 -04:00
clone(): PersistentModifier {
return new PokemonBaseStatModifier(this.type as PokemonBaseStatBoosterModifierType, this.pokemonId, this.stat);
2023-03-28 14:54:52 -04:00
}
shouldApply(args: any[]): boolean {
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array<integer>;
}
apply(args: any[]): boolean {
2023-04-09 19:15:21 -04:00
args[1][this.stat] = Math.min(Math.floor(args[1][this.stat] * (1 + this.getStackCount() * 0.2)), 999999);
2023-03-28 14:54:52 -04:00
return true;
}
getIcon(scene: BattleScene): Phaser.GameObjects.Container {
const container = super.getIcon(scene);
2023-04-09 19:15:21 -04:00
const item = scene.add.sprite(16, this.virtualStackCount ? 8 : 16, 'items');
2023-03-28 14:54:52 -04:00
item.setScale(0.5);
item.setOrigin(0, 0.5);
item.setTexture('items', this.type.iconImage);
container.add(item);
const stackText = this.getIconStackText(scene);
if (stackText)
container.add(stackText);
2023-04-09 19:15:21 -04:00
const virtualStackText = this.getIconStackText(scene, true);
if (virtualStackText)
container.add(virtualStackText);
2023-03-28 14:54:52 -04:00
return container;
}
}
2023-04-09 19:15:21 -04:00
export abstract class ConsumablePokemonModifier extends ConsumableModifier {
public pokemonId: integer;
2023-03-28 14:54:52 -04:00
constructor(type: ModifierType, pokemonId: integer) {
2023-04-09 19:15:21 -04:00
super(type);
this.pokemonId = pokemonId;
2023-03-28 14:54:52 -04:00
}
2023-04-09 19:15:21 -04:00
shouldApply(args: any[]): boolean {
return args.length && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId);
}
getPokemon(scene: BattleScene) {
return scene.getParty().find(p => p.id === this.pokemonId);
2023-03-28 14:54:52 -04:00
}
}
export class PokemonHpRestoreModifier extends ConsumablePokemonModifier {
2023-04-09 19:15:21 -04:00
private restorePoints: integer;
private percent: boolean;
private fainted: boolean;
2023-03-28 14:54:52 -04:00
2023-04-09 19:15:21 -04:00
constructor(type: ModifierType, pokemonId: integer, restorePoints: integer, percent: boolean, fainted?: boolean) {
2023-03-28 14:54:52 -04:00
super(type, pokemonId);
2023-04-09 19:15:21 -04:00
this.restorePoints = restorePoints;
this.percent = percent;
this.fainted = !!fainted;
2023-03-28 14:54:52 -04:00
}
2023-04-09 19:15:21 -04:00
shouldApply(args: any[]): boolean {
return super.shouldApply(args) && (this.fainted || (args.length > 1 && typeof(args[1]) === 'number'));
}
2023-03-28 14:54:52 -04:00
apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon;
2023-04-09 19:15:21 -04:00
if (!pokemon.hp === this.fainted) {
let restorePoints = this.restorePoints;
if (!this.fainted)
restorePoints = Math.floor(restorePoints * (args[1] as number));
pokemon.hp = Math.min(pokemon.hp + (this.percent ? (restorePoints * 0.01) * pokemon.getMaxHp() : restorePoints), pokemon.getMaxHp());
}
2023-03-28 14:54:52 -04:00
return true;
}
}
2023-04-11 11:04:39 -04:00
export abstract class ConsumablePokemonMoveModifier extends ConsumablePokemonModifier {
public moveIndex: integer;
2023-04-04 21:10:11 -04:00
2023-04-11 11:04:39 -04:00
constructor(type: ModifierType, pokemonId: integer, moveIndex: integer) {
2023-04-04 21:10:11 -04:00
super(type, pokemonId);
2023-04-11 11:04:39 -04:00
this.moveIndex = moveIndex;
}
}
export class PokemonPpRestoreModifier extends ConsumablePokemonMoveModifier {
private restorePoints: integer;
constructor(type: ModifierType, pokemonId: integer, moveIndex: integer, restorePoints: integer) {
super(type, pokemonId, moveIndex);
2023-04-04 21:10:11 -04:00
this.restorePoints = restorePoints;
}
2023-04-11 11:04:39 -04:00
apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon;
const move = pokemon.moveset[this.moveIndex];
console.log(move.ppUsed, this.restorePoints, this.restorePoints >= -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0);
move.ppUsed = this.restorePoints >= -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0;
return true;
}
}
export class PokemonAllMovePpRestoreModifier extends ConsumablePokemonModifier {
private restorePoints: integer;
constructor(type: ModifierType, pokemonId: integer, restorePoints: integer) {
super(type, pokemonId);
this.restorePoints = restorePoints;
2023-04-04 21:10:11 -04:00
}
apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon;
2023-04-11 11:04:39 -04:00
for (let move of pokemon.moveset)
move.ppUsed = this.restorePoints >= -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0;
2023-04-04 21:10:11 -04:00
return true;
}
}
2023-04-04 18:28:21 -04:00
export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
constructor(type: ModifierType, pokemonId: integer) {
super(type, pokemonId);
}
apply(args: any[]): boolean {
const pokemon = args[0] as PlayerPokemon;
pokemon.level++;
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
pokemon.levelExp = 0;
const scene = pokemon.scene as BattleScene;
2023-04-10 13:54:06 -04:00
scene.unshiftPhase(new LevelUpPhase(scene, scene.getParty().indexOf(pokemon), pokemon.level - 1, pokemon.level));
2023-04-04 18:28:21 -04:00
return true;
}
}
2023-04-08 00:21:44 -04:00
export class TmModifier extends ConsumablePokemonModifier {
constructor(type: TmModifierType, pokemonId: integer) {
super(type, pokemonId);
}
apply(args: any[]): boolean {
const pokemon = args[0] as PlayerPokemon;
const scene = pokemon.scene as BattleScene;
scene.unshiftPhase(new LearnMovePhase(scene, scene.getParty().indexOf(pokemon), (this.type as TmModifierType).moveId));
return true;
}
}
2023-04-09 19:15:21 -04:00
export class PartyShareModifier extends PersistentModifier {
constructor(type: ModifierType) {
super(type);
}
match(modifier: Modifier) {
return modifier instanceof PartyShareModifier;
}
clone(): PartyShareModifier {
return new PartyShareModifier(this.type);
}
shouldApply(args: any[]): boolean {
return super.shouldApply(args) && args.length === 2 && args[0] instanceof BattleScene && args[1] instanceof Array<Modifier>;
}
apply(args: any[]): boolean {
const scene = args[0] as BattleScene;
const modifiers = args[1] as Modifier[];
const party = scene.getParty();
for (let modifier of modifiers) {
if (modifier instanceof PokemonHeldItemModifier) {
const heldItemModifier = modifier as PokemonHeldItemModifier;
const extraStacks = Math.floor(modifier.stackCount / Math.max(party.length - (this.getStackCount() - 1), 1));
for (let s = 0; s < extraStacks; s++) {
for (let p of party) {
if (p.id === heldItemModifier.pokemonId)
continue;
const newHeldItemModifier = heldItemModifier.clone() as PokemonHeldItemModifier;
newHeldItemModifier.pokemonId = p.id;
scene.addModifier(newHeldItemModifier, true);
}
}
}
}
return true;
}
getMaxStackCount(): number {
return 6;
}
}
export class HealingBoosterModifier extends PersistentModifier {
private multiplier: number;
constructor(type: ModifierType, multiplier: number) {
super(type);
this.multiplier = multiplier;
}
match(modifier: Modifier): boolean {
return modifier instanceof HealingBoosterModifier;
}
clone(): HealingBoosterModifier {
return new HealingBoosterModifier(this.type, this.multiplier);
}
apply(args: any[]): boolean {
const healingMultiplier = args[0] as Utils.IntegerHolder;
for (let s = 0; s < this.getStackCount(); s++)
healingMultiplier.value *= this.multiplier;
healingMultiplier.value = Math.floor(healingMultiplier.value);
return true;
}
}
export class ExpBoosterModifier extends PersistentModifier {
private boostMultiplier: integer;
constructor(type: ModifierType, boostPercent: integer) {
2023-03-28 14:54:52 -04:00
super(type);
this.boostMultiplier = boostPercent * 0.01;
2023-03-28 14:54:52 -04:00
}
2023-04-09 19:15:21 -04:00
match(modifier: Modifier): boolean {
if (modifier instanceof ExpBoosterModifier) {
const expModifier = modifier as ExpBoosterModifier;
return expModifier.boostMultiplier === this.boostMultiplier;
2023-03-28 14:54:52 -04:00
}
2023-04-09 19:15:21 -04:00
return false;
}
2023-03-28 14:54:52 -04:00
2023-04-09 19:15:21 -04:00
clone(): ExpBoosterModifier {
return new ExpBoosterModifier(this.type, this.boostMultiplier * 100);
2023-03-28 14:54:52 -04:00
}
apply(args: any[]): boolean {
2023-04-09 19:15:21 -04:00
(args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier)));
2023-03-30 23:02:35 -04:00
return true;
}
}
2023-04-09 19:15:21 -04:00
export class ExpShareModifier extends PersistentModifier {
2023-03-30 23:02:35 -04:00
constructor(type: ModifierType) {
super(type);
}
2023-03-28 14:54:52 -04:00
2023-03-30 23:02:35 -04:00
apply(_args: any[]): boolean {
2023-03-28 14:54:52 -04:00
return true;
}
2023-03-30 23:02:35 -04:00
2023-04-09 19:15:21 -04:00
clone(): ExpShareModifier {
return new ExpShareModifier(this.type);
}
2023-03-30 23:02:35 -04:00
getMaxStackCount(): integer {
return 5;
}
2023-03-28 14:54:52 -04:00
}
2023-04-09 19:15:21 -04:00
export class ShinyRateBoosterModifier extends PersistentModifier {
2023-03-28 14:54:52 -04:00
constructor(type: ModifierType) {
super(type);
}
2023-04-09 19:15:21 -04:00
match(modifier: Modifier): boolean {
return modifier instanceof ShinyRateBoosterModifier;
}
2023-03-28 14:54:52 -04:00
2023-04-09 19:15:21 -04:00
clone(): ShinyRateBoosterModifier {
return new ShinyRateBoosterModifier(this.type);
2023-03-28 14:54:52 -04:00
}
apply(args: any[]): boolean {
2023-04-09 19:15:21 -04:00
(args[0] as Utils.IntegerHolder).value = Math.pow((args[0] as Utils.IntegerHolder).value * 0.5, this.getStackCount() + 1);
2023-03-28 14:54:52 -04:00
return true;
}
2023-03-30 23:02:35 -04:00
getMaxStackCount(): integer {
return 5;
}
2023-03-28 14:54:52 -04:00
}
2023-04-09 19:15:21 -04:00
export class ExtraModifierModifier extends PersistentModifier {
constructor(type: ModifierType) {
super(type);
}
2023-04-09 19:15:21 -04:00
clone(): ExtraModifierModifier {
return new ExtraModifierModifier(this.type);
}
apply(args: any[]): boolean {
2023-04-09 19:15:21 -04:00
(args[0] as Utils.IntegerHolder).value += this.getStackCount();
return true;
}
}
2023-03-28 14:54:52 -04:00
export enum ModifierTier {
COMMON,
GREAT,
ULTRA,
MASTER,
LUXURY
2023-03-28 14:54:52 -04:00
};
2023-04-11 11:04:39 -04:00
type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier;
2023-03-28 14:54:52 -04:00
export class ModifierType {
public name: string;
public description: string;
public iconImage: string;
public tier: ModifierTier;
2023-04-11 11:04:39 -04:00
private newModifierFunc: NewModifierFunc;
2023-03-28 14:54:52 -04:00
2023-04-11 11:04:39 -04:00
constructor(name: string, description: string, newModifierFunc: NewModifierFunc, iconImage?: string) {
2023-03-28 14:54:52 -04:00
this.name = name;
this.description = description;
2023-04-08 00:21:44 -04:00
this.iconImage = iconImage || name?.replace(/[ \-]/g, '_')?.toLowerCase();
2023-03-28 14:54:52 -04:00
this.newModifierFunc = newModifierFunc;
}
setTier(tier: ModifierTier) {
this.tier = tier;
}
newModifier(...args: any[]) {
return this.newModifierFunc(this, args);
}
}
class AddPokeballModifierType extends ModifierType {
constructor(pokeballType: PokeballType, count: integer, iconImage?: string) {
super(`${count}x ${getPokeballName(pokeballType)}`, `Receive ${getPokeballName(pokeballType)} x${count}`, (_type, _args) => new AddPokeballModifier(this, pokeballType, count), iconImage);
}
}
export abstract class PokemonModifierType extends ModifierType {
2023-04-11 11:04:39 -04:00
public selectFilter: PokemonSelectFilter;
2023-03-28 14:54:52 -04:00
2023-04-11 11:04:39 -04:00
constructor(name: string, description: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, iconImage?: string) {
2023-03-28 14:54:52 -04:00
super(name, description, newModifierFunc, iconImage);
this.selectFilter = selectFilter;
}
}
2023-03-29 00:31:25 -04:00
export class PokemonHpRestoreModifierType extends PokemonModifierType {
2023-04-09 19:15:21 -04:00
protected restorePoints: integer;
protected percent: boolean;
2023-03-28 14:54:52 -04:00
2023-04-11 11:04:39 -04:00
constructor(name: string, restorePoints: integer, percent?: boolean, newModifierFunc?: NewModifierFunc, selectFilter?: PokemonSelectFilter, iconImage?: string) {
2023-04-09 19:15:21 -04:00
super(name, `Restore ${restorePoints}${percent ? '%' : ''} HP for one POKéMON`,
newModifierFunc || ((_type, args) => new PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, this.percent, false)),
2023-04-04 18:28:21 -04:00
selectFilter || ((pokemon: PlayerPokemon) => {
if (!pokemon.hp || pokemon.hp >= pokemon.getMaxHp())
2023-03-28 14:54:52 -04:00
return PartyUiHandler.NoEffectMessage;
return null;
2023-04-04 18:28:21 -04:00
}), iconImage);
2023-03-28 14:54:52 -04:00
2023-04-09 19:15:21 -04:00
this.restorePoints = restorePoints;
this.percent = !!percent;
2023-03-28 14:54:52 -04:00
}
}
2023-03-29 00:31:25 -04:00
export class PokemonReviveModifierType extends PokemonHpRestoreModifierType {
2023-03-28 14:54:52 -04:00
constructor(name: string, restorePercent: integer, iconImage?: string) {
2023-04-09 19:15:21 -04:00
super(name, restorePercent, true, (_type, args) => new PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, true, true),
2023-04-04 18:28:21 -04:00
((pokemon: PlayerPokemon) => {
if (pokemon.hp)
return PartyUiHandler.NoEffectMessage;
return null;
}), iconImage);
2023-03-28 14:54:52 -04:00
this.description = `Revive one POKéMON and restore ${restorePercent}% HP`;
this.selectFilter = (pokemon: PlayerPokemon) => {
if (pokemon.hp)
return PartyUiHandler.NoEffectMessage;
return null;
};
}
}
2023-04-11 11:04:39 -04:00
export abstract class PokemonMoveModifierType extends PokemonModifierType {
public moveSelectFilter: PokemonMoveSelectFilter;
constructor(name: string, description: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, moveSelectFilter?: PokemonMoveSelectFilter, iconImage?: string) {
super(name, description, newModifierFunc, selectFilter, iconImage);
this.moveSelectFilter = moveSelectFilter;
2023-04-04 21:10:11 -04:00
}
}
2023-04-11 11:04:39 -04:00
export class PokemonPpRestoreModifierType extends PokemonMoveModifierType {
2023-04-04 21:10:11 -04:00
protected restorePoints: integer;
constructor(name: string, restorePoints: integer, iconImage?: string) {
2023-04-11 11:04:39 -04:00
super(name, `Restore ${restorePoints > -1 ? restorePoints : 'all'} PP for one POKéMON move`, (_type, args) => new PokemonPpRestoreModifier(this, (args[0] as PlayerPokemon).id, (args[1] as integer), this.restorePoints),
(_pokemon: PlayerPokemon) => {
return null;
}, (pokemonMove: PokemonMove) => {
if (!pokemonMove.ppUsed)
return PartyUiHandler.NoEffectMessage;
2023-04-04 21:10:11 -04:00
return null;
}, iconImage);
this.restorePoints = this.restorePoints;
}
}
2023-04-11 11:04:39 -04:00
export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType {
protected restorePoints: integer;
constructor(name: string, restorePoints: integer, iconImage?: string) {
super(name, `Restore ${restorePoints > -1 ? restorePoints : 'all'} PP for all of one POKéMON's moves`, (_type, args) => new PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints),
(pokemon: PlayerPokemon) => {
if (!pokemon.moveset.filter(m => m.ppUsed).length)
return PartyUiHandler.NoEffectMessage;
return null;
}, iconImage);
this.restorePoints = this.restorePoints;
}
}
export class PokemonLevelIncrementModifierType extends PokemonModifierType {
constructor(name: string, iconImage?: string) {
super(name, `Increase a POKéMON\'s level by 1`, (_type, args) => new PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id),
(_pokemon: PlayerPokemon) => null, iconImage);
}
}
2023-03-29 00:31:25 -04:00
export class PokemonBaseStatBoosterModifierType extends PokemonModifierType {
2023-03-28 14:54:52 -04:00
private stat: Stat;
constructor(name: string, stat: Stat, _iconImage?: string) {
2023-03-30 23:02:35 -04:00
super(name, `Increases one POKéMON's base ${getStatName(stat)} by 20%` , (_type, args) => new PokemonBaseStatModifier(this, (args[0] as PlayerPokemon).id, this.stat));
2023-03-28 14:54:52 -04:00
this.stat = stat;
}
}
2023-03-29 12:23:52 -04:00
class AllPokemonFullHpRestoreModifierType extends ModifierType {
2023-04-11 11:04:39 -04:00
constructor(name: string, description?: string, newModifierFunc?: NewModifierFunc, iconImage?: string) {
super(name, description || `Restore 100% HP for all POKéMON`, newModifierFunc || ((_type, _args) => new PokemonHpRestoreModifier(this, -1, 100, false)), iconImage);
}
}
class AllPokemonFullReviveModifierType extends AllPokemonFullHpRestoreModifierType {
2023-03-29 12:23:52 -04:00
constructor(name: string, iconImage?: string) {
super(name, `Revives all fainted POKéMON, restoring 100% HP`, (_type, _args) => new PokemonHpRestoreModifier(this, -1, 100, true), iconImage);
2023-03-28 14:54:52 -04:00
}
}
2023-03-31 20:19:57 -04:00
export class ExpBoosterModifierType extends ModifierType {
constructor(name: string, boostPercent: integer, iconImage?: string) {
super(name, `Increases gain of EXP. Points by ${boostPercent}%`, () => new ExpBoosterModifier(this, boostPercent), iconImage);
}
}
2023-04-08 00:21:44 -04:00
export class TmModifierType extends PokemonModifierType {
public moveId: Moves;
constructor(moveId: Moves) {
super(`TM${Utils.padInt(Object.keys(tmSpecies).indexOf(moveId.toString()) + 1, 3)} - ${allMoves[moveId - 1].name}`, `Teach ${allMoves[moveId - 1].name} to a POKéMON`, (_type, args) => new TmModifier(this, (args[0] as PlayerPokemon).id),
(pokemon: PlayerPokemon) => {
if (pokemon.compatibleTms.indexOf(moveId) === -1 || pokemon.moveset.filter(m => m?.moveId === moveId).length)
return PartyUiHandler.NoEffectMessage;
return null;
}, `tm_${Type[allMoves[moveId - 1].type].toLowerCase()}`);
this.moveId = moveId;
}
}
class ModifierTypeGenerator extends ModifierType {
private genTypeFunc: Function;
constructor(genTypeFunc: Function) {
super(null, null, null, null);
this.genTypeFunc = genTypeFunc;
}
generateType(party: PlayerPokemon[]) {
const ret = this.genTypeFunc(party);
ret.setTier(this.tier);
return ret;
}
}
2023-03-28 14:54:52 -04:00
class WeightedModifierType {
public modifierType: ModifierType;
2023-03-29 12:23:52 -04:00
public weight: integer | Function;
2023-03-28 14:54:52 -04:00
2023-03-29 12:23:52 -04:00
constructor(modifierType: ModifierType, weight: integer | Function) {
2023-03-28 14:54:52 -04:00
this.modifierType = modifierType;
this.weight = weight;
}
setTier(tier: ModifierTier) {
this.modifierType.setTier(tier);
}
}
const modifierPool = {
[ModifierTier.COMMON]: [
new WeightedModifierType(new AddPokeballModifierType(PokeballType.POKEBALL, 5, 'pb'), 2),
2023-04-08 00:21:44 -04:00
new WeightedModifierType(new PokemonHpRestoreModifierType('POTION', 20), (party: PlayerPokemon[]) => {
2023-04-11 19:56:26 -04:00
const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 10 || p.getHpRatio() <= 0.875).length;
2023-03-29 12:23:52 -04:00
return thresholdPartyMemberCount;
}),
2023-04-08 00:21:44 -04:00
new WeightedModifierType(new PokemonHpRestoreModifierType('SUPER POTION', 50), (party: PlayerPokemon[]) => {
2023-04-11 19:56:26 -04:00
const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 25 || p.getHpRatio() <= 0.75).length;
2023-04-11 11:04:39 -04:00
return Math.ceil(thresholdPartyMemberCount / 3);
}),
new WeightedModifierType(new PokemonPpRestoreModifierType('ETHER', 10), (party: PlayerPokemon[]) => {
const thresholdPartyMemberCount = party.filter(p => p.moveset.filter(m => m.ppUsed >= 5)).length;
2023-04-11 11:04:39 -04:00
return thresholdPartyMemberCount;
}),
2023-04-12 00:37:56 -04:00
new WeightedModifierType(new PokemonPpRestoreModifierType('MAX ETHER', -1), (party: PlayerPokemon[]) => {
2023-04-11 11:04:39 -04:00
const thresholdPartyMemberCount = party.filter(p => p.moveset.filter(m => m.ppUsed > 10)).length;
2023-03-29 12:23:52 -04:00
return Math.ceil(thresholdPartyMemberCount / 3);
2023-04-08 00:21:44 -04:00
})
2023-03-28 14:54:52 -04:00
].map(m => { m.setTier(ModifierTier.COMMON); return m; }),
[ModifierTier.GREAT]: [
2023-03-30 23:02:35 -04:00
new WeightedModifierType(new AddPokeballModifierType(PokeballType.GREAT_BALL, 5, 'gb'), 3),
2023-04-08 00:21:44 -04:00
new WeightedModifierType(new PokemonReviveModifierType('REVIVE', 50), (party: PlayerPokemon[]) => {
2023-03-30 23:02:35 -04:00
const faintedPartyMemberCount = party.filter(p => !p.hp).length;
return faintedPartyMemberCount * 3;
}),
2023-04-08 00:21:44 -04:00
new WeightedModifierType(new PokemonReviveModifierType('MAX REVIVE', 100), (party: PlayerPokemon[]) => {
2023-03-29 12:23:52 -04:00
const faintedPartyMemberCount = party.filter(p => !p.hp).length;
return faintedPartyMemberCount;
}),
2023-04-09 19:15:21 -04:00
new WeightedModifierType(new PokemonHpRestoreModifierType('HYPER POTION', 200), (party: PlayerPokemon[]) => {
2023-04-11 19:56:26 -04:00
const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length;
2023-03-30 23:02:35 -04:00
return thresholdPartyMemberCount;
}),
2023-04-11 11:04:39 -04:00
new WeightedModifierType(new PokemonHpRestoreModifierType('MAX POTION', 100, true), (party: PlayerPokemon[]) => {
2023-04-11 19:56:26 -04:00
const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 150 || p.getHpRatio() <= 0.5).length;
2023-04-11 11:04:39 -04:00
return Math.ceil(thresholdPartyMemberCount / 3);
}),
new WeightedModifierType(new PokemonAllMovePpRestoreModifierType('ELIXIR', 10), (party: PlayerPokemon[]) => {
const thresholdPartyMemberCount = party.filter(p => p.moveset.filter(m => m.ppUsed >= 5)).length;
2023-04-11 11:04:39 -04:00
return thresholdPartyMemberCount;
}),
2023-04-12 00:37:56 -04:00
new WeightedModifierType(new PokemonAllMovePpRestoreModifierType('MAX ELIXIR', -1), (party: PlayerPokemon[]) => {
2023-04-11 11:04:39 -04:00
const thresholdPartyMemberCount = party.filter(p => p.moveset.filter(m => m.ppUsed > 10)).length;
return Math.ceil(thresholdPartyMemberCount / 3);
}),
2023-04-08 00:21:44 -04:00
new WeightedModifierType(new ModifierTypeGenerator((party: PlayerPokemon[]) => {
const partyMemberCompatibleTms = party.map(p => p.compatibleTms);
const uniqueCompatibleTms = partyMemberCompatibleTms.flat().filter((tm, i, array) => array.indexOf(tm) === i);
const randTmIndex = Utils.randInt(uniqueCompatibleTms.length);
return new TmModifierType(uniqueCompatibleTms[randTmIndex]);
}), 2),
2023-04-04 18:28:21 -04:00
new PokemonLevelIncrementModifierType('RARE CANDY'),
2023-03-30 23:02:35 -04:00
new PokemonBaseStatBoosterModifierType('HP-UP', Stat.HP),
new PokemonBaseStatBoosterModifierType('PROTEIN', Stat.ATK),
new PokemonBaseStatBoosterModifierType('IRON', Stat.DEF),
new PokemonBaseStatBoosterModifierType('CALCIUM', Stat.SPATK),
new PokemonBaseStatBoosterModifierType('ZINC', Stat.SPDEF),
new PokemonBaseStatBoosterModifierType('CARBOS', Stat.SPD)
2023-03-28 14:54:52 -04:00
].map(m => { m.setTier(ModifierTier.GREAT); return m; }),
[ModifierTier.ULTRA]: [
new AddPokeballModifierType(PokeballType.ULTRA_BALL, 5, 'ub'),
2023-04-08 00:21:44 -04:00
new WeightedModifierType(new AllPokemonFullReviveModifierType('SACRED ASH'), (party: PlayerPokemon[]) => {
return party.filter(p => !p.hp).length >= Math.ceil(party.length / 2) ? 1 : 0;
}),
2023-04-09 19:15:21 -04:00
new ModifierType('OVAL CHARM', 'For every X (no. of party members) items in a POKéMON\'s held item stack, give one to each other party member',
(type, _args) => new PartyShareModifier(type), 'oval_charm'),
new ModifierType('HEALING CHARM', 'Doubles the effectiveness of HP restoring items (excludes revives)', (type, _args) => new HealingBoosterModifier(type, 2), 'healing_charm'),
2023-03-30 23:02:35 -04:00
new ExpBoosterModifierType('LUCKY EGG', 25),
new ModifierType('EXP. SHARE', 'All POKéMON in your party gain an additional 10% of a battle\'s EXP. Points', (type, _args) => new ExpShareModifier(type), 'exp_share')
].map(m => { m.setTier(ModifierTier.ULTRA); return m; }),
[ModifierTier.MASTER]: [
new AddPokeballModifierType(PokeballType.MASTER_BALL, 1, 'mb'),
new WeightedModifierType(new ModifierType('SHINY CHARM', 'Dramatically increases the chance of a wild POKéMON being shiny', (type, _args) => new ShinyRateBoosterModifier(type)), 2)
].map(m => { m.setTier(ModifierTier.MASTER); return m; }),
[ModifierTier.LUXURY]: [
new ExpBoosterModifierType('GOLDEN EGG', 100),
new ModifierType(`GOLDEN ${getPokeballName(PokeballType.POKEBALL)}`, 'Adds 1 extra ITEM option at the end of every battle', (type, _args) => new ExtraModifierModifier(type), 'pb_gold')
].map(m => { m.setTier(ModifierTier.LUXURY); return m; }),
2023-03-28 14:54:52 -04:00
};
2023-03-29 12:23:52 -04:00
let modifierPoolThresholds = {};
let ignoredPoolIndexes = {};
2023-04-08 00:21:44 -04:00
export function regenerateModifierPoolThresholds(party: PlayerPokemon[]) {
2023-03-29 12:23:52 -04:00
ignoredPoolIndexes = {};
modifierPoolThresholds = Object.fromEntries(new Map(Object.keys(modifierPool).map(t => {
ignoredPoolIndexes[t] = [];
const thresholds = new Map();
let i = 0;
modifierPool[t].reduce((total: integer, modifierType: ModifierType | WeightedModifierType) => {
if (modifierType instanceof WeightedModifierType) {
const weightedModifierType = modifierType as WeightedModifierType;
const weight = weightedModifierType.weight instanceof Function
? (weightedModifierType.weight as Function)(party)
: weightedModifierType.weight as integer;
if (weight)
total += weight;
else {
ignoredPoolIndexes[t].push(i++);
return total;
}
} else
total++;
thresholds.set(total, i++);
return total;
}, 0);
return [ t, Object.fromEntries(thresholds) ]
})));
console.log(modifierPoolThresholds)
}
2023-03-28 14:54:52 -04:00
2023-04-12 00:37:56 -04:00
export function getModifierTypesForWave(waveIndex: integer, count: integer, party: PlayerPokemon[]): ModifierType[] {
if (waveIndex % 10 === 0)
return modifierPool[ModifierTier.LUXURY];
2023-04-12 00:37:56 -04:00
return new Array(count).fill(0).map(() => getNewModifierType(party));
}
2023-04-12 00:37:56 -04:00
function getNewModifierType(party: PlayerPokemon[]): ModifierType {
2023-03-28 14:54:52 -04:00
const tierValue = Utils.randInt(256);
const tier = tierValue >= 52 ? ModifierTier.COMMON : tierValue >= 8 ? ModifierTier.GREAT : tierValue >= 1 ? ModifierTier.ULTRA : ModifierTier.MASTER;
const thresholds = Object.keys(modifierPoolThresholds[tier]);
const totalWeight = parseInt(thresholds[thresholds.length - 1]);
const value = Utils.randInt(totalWeight);
let index: integer;
for (let t of thresholds) {
let threshold = parseInt(t);
if (value < threshold) {
index = modifierPoolThresholds[tier][threshold];
break;
}
}
2023-03-29 12:23:52 -04:00
console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier])
2023-03-28 14:54:52 -04:00
let modifierType: ModifierType | WeightedModifierType = modifierPool[tier][index];
if (modifierType instanceof WeightedModifierType)
2023-04-08 00:21:44 -04:00
modifierType = (modifierType as WeightedModifierType).modifierType;
if (modifierType instanceof ModifierTypeGenerator)
modifierType = (modifierType as ModifierTypeGenerator).generateType(party);
2023-04-12 00:37:56 -04:00
return modifierType as ModifierType;
2023-03-28 14:54:52 -04:00
}