From 60ac4e096c263b82da8f11c85502699be09fc09e Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Thu, 20 Apr 2023 19:44:56 -0400 Subject: [PATCH] Add enemy modifiers functionality --- src/battle-phases.ts | 78 ++++----- src/battle-scene.ts | 128 +++++++++++---- src/data/berry.ts | 2 +- src/data/status-effect.ts | 6 +- src/modifier/modifier-type.ts | 289 +++++++++++++++++++++++----------- src/modifier/modifier.ts | 29 ++-- src/pokemon.ts | 13 +- 7 files changed, 362 insertions(+), 183 deletions(-) diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 5a25eb275c0..04598e65ac9 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -5,7 +5,7 @@ import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, ConditionalMoveA import { Mode } from './ui/ui'; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, HealingBoosterModifier, HitHealModifier, PokemonExpBoosterModifier, TempBattleStatBoosterModifier } from "./modifier/modifier"; +import { BerryModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, HealingBoosterModifier, HitHealModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, TempBattleStatBoosterModifier } from "./modifier/modifier"; import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; @@ -16,7 +16,7 @@ import { EvolutionPhase } from "./evolution-phase"; import { BattlePhase } from "./battle-phase"; import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat"; import { Biome, biomeLinks } from "./data/biome"; -import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; +import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getPlayerModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { BattleTagLapseType, BattleTagType, HideSpriteTag as HiddenTag } from "./data/battle-tag"; import { getPokemonMessage } from "./messages"; @@ -67,7 +67,7 @@ export class EncounterPhase extends BattlePhase { start() { super.start(); - this.scene.updateWaveText(); + this.scene.updateWaveCountText(); const battle = this.scene.currentBattle; const enemySpecies = this.scene.arena.randomSpecies(battle.waveIndex, battle.enemyLevel); @@ -85,6 +85,9 @@ export class EncounterPhase extends BattlePhase { this.scene.field.moveBelow(enemyPokemon, this.scene.getPlayerPokemon()); enemyPokemon.tint(0, 0.5); + regenerateModifierPoolThresholds(this.scene.getEnemyParty(), false); + this.scene.generateEnemyModifiers(); + this.scene.ui.setMode(Mode.MESSAGE).then(() => this.doEncounter()); }); } @@ -92,7 +95,7 @@ export class EncounterPhase extends BattlePhase { doEncounter() { if (startingWave > 10) { for (let m = 0; m < Math.floor(startingWave / 10); m++) - this.scene.addModifier(getModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getParty())[0].type.newModifier()); + this.scene.addModifier(getPlayerModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getParty())[0].type.newModifier()); } this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena.biomeType), false); @@ -594,7 +597,7 @@ export class TurnEndPhase extends BattlePhase { this.scene.pushPhase(new MessagePhase(this.scene, `${dm.getName()} is disabled\nno more!`)); } - const hasUsableBerry = pokemon.isPlayer() && !!this.scene.findModifier(m => m instanceof BerryModifier && m.shouldApply([ pokemon ])); + const hasUsableBerry = !!this.scene.findModifier(m => m instanceof BerryModifier && m.shouldApply([ pokemon ]), pokemon.isPlayer()); if (hasUsableBerry) this.scene.pushPhase(new BerryPhase(this.scene, pokemon.isPlayer())); @@ -627,6 +630,8 @@ export class BattleEndPhase extends BattlePhase { start() { super.start(); + this.scene.clearEnemyModifiers(); + const tempBattleStatBoosterModifiers = this.scene.getModifiers(TempBattleStatBoosterModifier) as TempBattleStatBoosterModifier[]; for (let m of tempBattleStatBoosterModifiers) { if (!m.lapse()) @@ -675,7 +680,7 @@ export class CommonAnimPhase extends PokemonPhase { } start() { - new CommonBattleAnim(this.anim, this.getPokemon(), this.getPokemon().isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).play(this.scene, () => { + new CommonBattleAnim(this.anim, this.getPokemon(), this.player ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).play(this.scene, () => { this.end(); }); } @@ -881,8 +886,7 @@ abstract class MoveEffectPhase extends PokemonPhase { else { if (user.turnData.hitsTotal > 1) this.scene.unshiftPhase(new MessagePhase(this.scene, `Hit ${user.turnData.hitCount} time(s)!`)); - if (this.player) - this.scene.applyModifiers(HitHealModifier, user); + this.scene.applyModifiers(HitHealModifier, this.player, user); } super.end(); @@ -907,8 +911,7 @@ abstract class MoveEffectPhase extends PokemonPhase { if (this.move.getMove().category !== MoveCategory.STATUS) { const userAccuracyLevel = new Utils.IntegerHolder(this.getUserPokemon().summonData.battleStats[BattleStat.ACC]); const targetEvasionLevel = new Utils.IntegerHolder(this.getTargetPokemon().summonData.battleStats[BattleStat.EVA]); - if (this.getUserPokemon().isPlayer()) - this.scene.applyModifiers(TempBattleStatBoosterModifier, TempBattleStat.ACC, userAccuracyLevel); + this.scene.applyModifiers(TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel); const rand = Utils.randInt(100, 1); let accuracyMultiplier = 1; if (userAccuracyLevel.value !== targetEvasionLevel.value) { @@ -1324,7 +1327,7 @@ export class VictoryPhase extends PokemonPhase { if (expShareModifier) expMultiplier += expShareModifier.stackCount * 0.1; const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); - this.scene.applyModifiers(PokemonExpBoosterModifier, partyMember, pokemonExp); + this.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); partyMemberExp.push(Math.floor(pokemonExp.value)); } @@ -1404,7 +1407,7 @@ export class ExpPhase extends PartyMemberPokemonPhase { const pokemon = this.getPokemon(); let exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, exp); + this.scene.applyModifiers(ExpBoosterModifier, true, exp); exp.value = Math.floor(exp.value); this.scene.ui.showText(`${pokemon.name} gained\n${exp.value} EXP. Points!`, null, () => { const lastLevel = pokemon.level; @@ -1549,18 +1552,16 @@ export class BerryPhase extends CommonAnimPhase { start() { let berryModifier: BerryModifier; - if (this.player) { - if ((berryModifier = this.scene.applyModifier(BerryModifier, this.getPokemon()) as BerryModifier)) { - if (berryModifier.consumed) { - if (!--berryModifier.stackCount) - this.scene.removeModifier(berryModifier); - else - berryModifier.consumed = false; - this.scene.updateModifiers(); - } - super.start(); - return; + if ((berryModifier = this.scene.applyModifier(BerryModifier, this.player, this.getPokemon()) as BerryModifier)) { + if (berryModifier.consumed) { + if (!--berryModifier.stackCount) + this.scene.removeModifier(berryModifier); + else + berryModifier.consumed = false; + this.scene.updateModifiers(this.player); } + super.start(); + return; } this.end(); @@ -1596,8 +1597,7 @@ export class PokemonHealPhase extends CommonAnimPhase { if (!fullHp) { const hpRestoreMultiplier = new Utils.IntegerHolder(1); - if (this.player) - this.scene.applyModifiers(HealingBoosterModifier, hpRestoreMultiplier); + this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); pokemon.hp = Math.min(pokemon.hp + this.hpHealed * hpRestoreMultiplier.value, pokemon.getMaxHp()); pokemon.updateInfo().then(() => super.end()); } else if (this.showFullHpMessage) @@ -1699,9 +1699,7 @@ export class AttemptCapturePhase extends BattlePhase { } else this.scene.sound.play('pb_lock') }, - onComplete: () => { - this.catch(); - } + onComplete: () => this.catch() }); } : () => this.catch(); @@ -1747,12 +1745,16 @@ export class AttemptCapturePhase extends BattlePhase { }; const addToParty = () => { const newPokemon = pokemon.addToParty(); - pokemon.hp = 0; - this.scene.field.remove(pokemon, true); - if (newPokemon) - newPokemon.loadAssets().then(end); - else - end(); + const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + Promise.all(modifiers.map(m => this.scene.addModifier(m))).then(() => { + pokemon.hp = 0; + this.scene.clearEnemyModifiers(); + this.scene.field.remove(pokemon, true); + if (newPokemon) + newPokemon.loadAssets().then(end); + else + end(); + }); }; Promise.all([ pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon) ]).then(() => { if (this.scene.getParty().length === 6) { @@ -1804,8 +1806,8 @@ export class SelectModifierPhase extends BattlePhase { const party = this.scene.getParty(); regenerateModifierPoolThresholds(party); const modifierCount = new Utils.IntegerHolder(3); - this.scene.applyModifiers(ExtraModifierModifier, modifierCount); - const typeOptions: Array = getModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex - 1, modifierCount.value, party); + this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); + const typeOptions: Array = getPlayerModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex - 1, modifierCount.value, party); const modifierSelectCallback = (cursor: integer) => { if (cursor < 0) { @@ -1825,14 +1827,14 @@ export class SelectModifierPhase extends BattlePhase { const modifier = !isMoveModifier ? modifierType.newModifier(party[slotIndex]) : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); - this.scene.addModifier(modifier).then(() => super.end()); + this.scene.addModifier(modifier, true).then(() => super.end()); this.scene.ui.clearText(); this.scene.ui.setMode(Mode.MESSAGE); } else this.scene.ui.setMode(Mode.MODIFIER_SELECT, typeOptions, modifierSelectCallback); }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined); } else { - this.scene.addModifier(typeOptions[cursor].type.newModifier()).then(() => super.end()); + this.scene.addModifier(typeOptions[cursor].type.newModifier(), true).then(() => super.end()); this.scene.ui.clearText(); this.scene.ui.setMode(Mode.MESSAGE); } diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 4f425020f8e..af074009669 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2,7 +2,7 @@ import Phaser from 'phaser'; import { Biome } from './data/biome'; import UI from './ui/ui'; import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, SelectStarterPhase, MessagePhase } from './battle-phases'; -import { PlayerPokemon, EnemyPokemon } from './pokemon'; +import Pokemon, { PlayerPokemon, EnemyPokemon } from './pokemon'; import PokemonSpecies, { allSpecies, getPokemonSpecies } from './data/pokemon-species'; import * as Utils from './utils'; import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PartyShareModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ConsumablePokemonMoveModifier, ModifierPredicate } from './modifier/modifier'; @@ -18,7 +18,7 @@ import { GameData } from './system/game-data'; import StarterSelectUiHandler from './ui/starter-select-ui-handler'; import { TextStyle, addTextObject } from './ui/text'; import { Moves } from './data/move'; -import { getDefaultModifierTypeForTier } from './modifier/modifier-type'; +import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getPlayerModifierTypeOptionsForWave } from './modifier/modifier-type'; const enableAuto = true; export const startingLevel = 5; @@ -45,7 +45,7 @@ export enum Button { export default class BattleScene extends Phaser.Scene { public auto: boolean; public gameSpeed: integer = 1; - public quickStart: boolean; + public quickStart: boolean = true; public gameData: GameData; @@ -68,7 +68,9 @@ export default class BattleScene extends Phaser.Scene { private party: PlayerPokemon[]; private waveCountText: Phaser.GameObjects.Text; private modifierBar: ModifierBar; + private enemyModifierBar: ModifierBar; private modifiers: PersistentModifier[]; + private enemyModifiers: PokemonHeldItemModifier[]; public uiContainer: Phaser.GameObjects.Container; public ui: UI; @@ -285,13 +287,19 @@ export default class BattleScene extends Phaser.Scene { this.uiContainer = uiContainer; this.modifiers = []; + this.enemyModifiers = []; this.modifierBar = new ModifierBar(this); this.add.existing(this.modifierBar); uiContainer.add(this.modifierBar); - this.waveCountText = addTextObject(this, (this.game.canvas.width / 6) - 2, -(this.game.canvas.height / 6), '1', TextStyle.BATTLE_INFO); + this.enemyModifierBar = new ModifierBar(this, true); + this.add.existing(this.enemyModifierBar); + uiContainer.add(this.enemyModifierBar); + + this.waveCountText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, '1', TextStyle.BATTLE_INFO); this.waveCountText.setOrigin(1, 0); + this.updateWaveCountPosition(); this.fieldUI.add(this.waveCountText); this.party = []; @@ -414,6 +422,10 @@ export default class BattleScene extends Phaser.Scene { return this.party; } + getEnemyParty(): EnemyPokemon[] { + return this.getEnemyPokemon() ? [ this.getEnemyPokemon() ] : []; + } + getPlayerPokemon(): PlayerPokemon { return this.getParty()[0]; } @@ -451,13 +463,17 @@ export default class BattleScene extends Phaser.Scene { return this.arena; } - updateWaveText(): void { + updateWaveCountText(): void { const isBoss = !(this.currentBattle.waveIndex % 10); this.waveCountText.setText(this.currentBattle.waveIndex.toString()); this.waveCountText.setColor(!isBoss ? '#404040' : '#f89890'); this.waveCountText.setShadowColor(!isBoss ? '#ded6b5' : '#984038'); } + updateWaveCountPosition(): void { + this.waveCountText.setY(-(this.game.canvas.height / 6) + (this.enemyModifiers.length ? 15 : 0)); + } + randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean): PokemonSpecies { return fromArenaPool ? this.arena.randomSpecies(waveIndex, level) @@ -591,16 +607,16 @@ export default class BattleScene extends Phaser.Scene { this.phaseQueue.push(new CommandPhase(this)); } - addModifier(modifier: Modifier, virtual?: boolean): Promise { + addModifier(modifier: Modifier, playSound?: boolean, virtual?: boolean): Promise { return new Promise(resolve => { const soundName = modifier.type.soundName; if (modifier instanceof PersistentModifier) { if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { - if (!virtual && !this.sound.get(soundName)) + if (playSound && !this.sound.get(soundName)) this.sound.play(soundName); } else if (!virtual) { const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); - this.addModifier(defaultModifierType.newModifier()).then(() => resolve()); + this.addModifier(defaultModifierType.newModifier(), playSound).then(() => resolve()); this.unshiftPhase(new MessagePhase(this, `The stack for this item is full.\n You will receive ${defaultModifierType.name} instead.`, null, true)); return; } @@ -608,7 +624,7 @@ export default class BattleScene extends Phaser.Scene { if (!virtual) this.updateModifiers().then(() => resolve()); } else if (modifier instanceof ConsumableModifier) { - if (!this.sound.get(soundName)) + if (playSound && !this.sound.get(soundName)) this.sound.play(soundName); if (modifier instanceof ConsumablePokemonModifier) { @@ -619,7 +635,7 @@ export default class BattleScene extends Phaser.Scene { if (modifier instanceof PokemonHpRestoreModifier) { if (!(modifier as PokemonHpRestoreModifier).fainted) { const hpRestoreMultiplier = new Utils.IntegerHolder(1); - this.applyModifiers(HealingBoosterModifier, hpRestoreMultiplier); + this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier); args.push(hpRestoreMultiplier.value); } else args.push(1); @@ -651,68 +667,114 @@ export default class BattleScene extends Phaser.Scene { }); } - updateModifiers(): Promise { + generateEnemyModifiers(): Promise { return new Promise(resolve => { - for (let modifier of this.modifiers) { + const waveIndex = this.currentBattle.waveIndex; + const chances = Math.ceil(waveIndex / 20); + const isBoss = waveIndex >= 100 || !(waveIndex % 10); + let count = 0; + for (let c = 0; c < chances; c++) { + if (!Utils.randInt(!isBoss ? 8 : 2)) + count++; + if (count === 12) + break; + } + if (isBoss) + count = Math.max(count, Math.ceil(chances / 2)); + getEnemyModifierTypesForWave(waveIndex, count, this.getEnemyParty()).map(mt => mt.newModifier(this.getEnemyPokemon()).add(this.enemyModifiers, false)); + + this.updateModifiers(false).then(() => resolve()); + }); + } + + clearEnemyModifiers(): void { + this.enemyModifiers.splice(0, this.enemyModifiers.length); + this.updateModifiers(false).then(() => this.updateWaveCountPosition()); + } + + updateModifiers(player?: boolean): Promise { + if (player === undefined) + player = true; + return new Promise(resolve => { + const modifiers = player ? this.modifiers : this.enemyModifiers; + for (let modifier of modifiers) { if (modifier instanceof PersistentModifier) (modifier as PersistentModifier).virtualStackCount = 0; } - this.applyModifiers(PartyShareModifier, this, this.modifiers); + if (player) + this.applyModifiers(PartyShareModifier, true, this, modifiers); - const modifiers = this.modifiers.slice(0); - for (let modifier of modifiers) { + const modifiersClone = modifiers.slice(0); + for (let modifier of modifiersClone) { if (!modifier.getStackCount()) - this.modifiers.splice(this.modifiers.indexOf(modifier), 1); + modifiers.splice(modifiers.indexOf(modifier), 1); } - this.updatePartyForModifiers().then(() => { - this.modifierBar.updateModifiers(this.modifiers); + this.updatePartyForModifiers(player ? this.getParty() : this.getEnemyParty()).then(() => { + (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); + if (!player) + this.updateWaveCountPosition(); resolve(); }); }); } - updatePartyForModifiers(): Promise { + updatePartyForModifiers(party: Pokemon[]): Promise { return new Promise(resolve => { - Promise.allSettled(this.party.map(p => { + Promise.allSettled(party.map(p => { p.calculateStats(); return p.updateInfo(); })).then(() => resolve()); }); } - removeModifier(modifier: PersistentModifier): boolean { - const modifierIndex = this.modifiers.indexOf(modifier); + removeModifier(modifier: PersistentModifier, enemy?: boolean): boolean { + const modifiers = !enemy ? this.modifiers : this.enemyModifiers; + const modifierIndex = modifiers.indexOf(modifier); if (modifierIndex > -1) { - this.modifiers.splice(modifierIndex, 1); + modifiers.splice(modifierIndex, 1); return true; } return false; } - getModifiers(modifierType: { new(...args: any[]): Modifier }): Modifier[] { - return this.modifiers.filter(m => m instanceof modifierType); + getModifiers(modifierType: { new(...args: any[]): Modifier }, player?: boolean): Modifier[] { + if (player === undefined) + player = true; + return (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType); } - findModifier(modifierFilter: ModifierPredicate): Modifier { - return this.modifiers.find(m => (modifierFilter as ModifierPredicate)(m)); + findModifiers(modifierFilter: ModifierPredicate, player?: boolean): Modifier[] { + if (player === undefined) + player = true; + return (player ? this.modifiers : this.enemyModifiers).filter(m => (modifierFilter as ModifierPredicate)(m)); } - applyModifiers(modifierType: { new(...args: any[]): Modifier }, ...args: any[]): void { - const modifiers = this.modifiers.filter(m => m instanceof modifierType && m.shouldApply(args)); + findModifier(modifierFilter: ModifierPredicate, player?: boolean): Modifier { + if (player === undefined) + player = true; + return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m)); + } + + applyModifiers(modifierType: { new(...args: any[]): Modifier }, player?: boolean, ...args: any[]): void { + if (player === undefined) + player = true; + const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); for (let modifier of modifiers) { if (modifier.apply(args)) - console.log('Applied', modifier.type.name); + console.log('Applied', modifier.type.name, !player ? '(enemy)' : ''); } } - applyModifier(modifierType: { new(...args: any[]): Modifier }, ...args: any[]): PersistentModifier { - const modifiers = this.modifiers.filter(m => m instanceof modifierType && m.shouldApply(args)); + applyModifier(modifierType: { new(...args: any[]): Modifier }, player?: boolean, ...args: any[]): PersistentModifier { + if (player === undefined) + player = true; + const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); for (let modifier of modifiers) { if (modifier.apply(args)) { - console.log('Applied', modifier.type.name); + console.log('Applied', modifier.type.name, !player ? '(enemy)' : ''); return modifier; } } diff --git a/src/data/berry.ts b/src/data/berry.ts index c4408ff64c0..37e7744f3f3 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -44,7 +44,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { switch (berryType) { case BerryType.SITRUS: return (pokemon: Pokemon) => { - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, true, Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true)); + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true)); }; case BerryType.LUM: return (pokemon: Pokemon) => { diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index d8d4636bccb..c8bfafedbf9 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -87,7 +87,7 @@ export function getStatusEffectOverlapText(statusEffect: StatusEffect): string { return ''; } -export function getStatusEffectHealText(statusEffect: StatusEffect) { +export function getStatusEffectHealText(statusEffect: StatusEffect): string { switch (statusEffect) { case StatusEffect.POISON: case StatusEffect.TOXIC: @@ -105,7 +105,7 @@ export function getStatusEffectHealText(statusEffect: StatusEffect) { return ''; } -export function getStatusEffectDescriptor(statusEffect: StatusEffect) { +export function getStatusEffectDescriptor(statusEffect: StatusEffect): string { switch (statusEffect) { case StatusEffect.POISON: case StatusEffect.TOXIC: @@ -121,7 +121,7 @@ export function getStatusEffectDescriptor(statusEffect: StatusEffect) { } } -export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect) { +export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): number { switch (statusEffect) { case StatusEffect.POISON: case StatusEffect.TOXIC: diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 038c6f1db4b..de94ed49ce0 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,8 +1,7 @@ -import { BattleStat, getBattleStatName } from '../data/battle-stat'; import * as Modifiers from './modifier'; import { AttackMove, Moves, allMoves } from '../data/move'; import { PokeballType, getPokeballName } from '../data/pokeball'; -import { PlayerPokemon, PokemonMove } from '../pokemon'; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from '../pokemon'; import { EvolutionItem, pokemonEvolutions } from '../data/pokemon-evolutions'; import { Stat, getStatName } from '../data/pokemon-stat'; import { tmSpecies } from '../data/tms'; @@ -31,7 +30,7 @@ export class ModifierType { public group: string; public soundName: string; public tier: ModifierTier; - private newModifierFunc: NewModifierFunc; + protected newModifierFunc: NewModifierFunc; constructor(name: string, description: string, newModifierFunc: NewModifierFunc, iconImage?: string, group?: string, soundName?: string) { this.name = name; @@ -42,11 +41,11 @@ export class ModifierType { this.newModifierFunc = newModifierFunc; } - setTier(tier: ModifierTier) { + setTier(tier: ModifierTier): void { this.tier = tier; } - newModifier(...args: any[]) { + newModifier(...args: any[]): Modifier { return this.newModifierFunc(this, args); } } @@ -69,8 +68,12 @@ export class PokemonModifierType extends ModifierType { } export class PokemonHeldItemModifierType extends PokemonModifierType { - constructor(name: string, description: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, iconImage?: string, group?: string, soundName?: string) { - super(name, description, newModifierFunc, selectFilter, iconImage, group, soundName); + constructor(name: string, description: string, newModifierFunc: NewModifierFunc, iconImage?: string, group?: string, soundName?: string) { + super(name, description, newModifierFunc, undefined, iconImage, group, soundName); + } + + newModifier(...args: any[]): Modifiers.PokemonHeldItemModifier { + return super.newModifier(...args) as Modifiers.PokemonHeldItemModifier; } } @@ -225,8 +228,8 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType { constructor(moveType: Type, boostPercent: integer) { super(Utils.toPokemonUpperCase(getAttackTypeBoosterItemName(moveType)), `Inceases the power of a POKéMON's ${Type[moveType]}-type moves by 20%`, - (_type, args) => new Modifiers.AttackTypeBoosterModifier(this, (args[0] as PlayerPokemon).id, moveType, boostPercent), - null, `${getAttackTypeBoosterItemName(moveType).replace(/[ \-]/g, '_').toLowerCase()}`); + (_type, args) => new Modifiers.AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), + `${getAttackTypeBoosterItemName(moveType).replace(/[ \-]/g, '_').toLowerCase()}`); this.moveType = moveType; this.boostPercent = boostPercent; @@ -261,7 +264,7 @@ export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierT private stat: Stat; constructor(name: string, stat: Stat, _iconImage?: string) { - super(name, `Increases the holder's base ${getStatName(stat)} by 20%` , (_type, args) => new Modifiers.PokemonBaseStatModifier(this, (args[0] as PlayerPokemon).id, this.stat)); + super(name, `Increases the holder's base ${getStatName(stat)} by 20%`, (_type, args) => new Modifiers.PokemonBaseStatModifier(this, (args[0] as Pokemon).id, this.stat)); this.stat = stat; } @@ -287,8 +290,8 @@ export class ExpBoosterModifierType extends ModifierType { export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { constructor(name: string, boostPercent: integer, iconImage?: string) { - super(name, `Increases the holder's gain of EXP. Points by ${boostPercent}%`, (_type, args) => new Modifiers.PokemonExpBoosterModifier(this, (args[0] as PlayerPokemon).id, boostPercent), - (_pokemon: PlayerPokemon) => null, iconImage); + super(name, `Increases the holder's gain of EXP. Points by ${boostPercent}%`, (_type, args) => new Modifiers.PokemonExpBoosterModifier(this, (args[0] as Pokemon).id, boostPercent), + iconImage); } } @@ -359,7 +362,7 @@ class ModifierTypeGenerator extends ModifierType { this.genTypeFunc = genTypeFunc; } - generateType(party: PlayerPokemon[]) { + generateType(party: Pokemon[]) { const ret = this.genTypeFunc(party); if (ret) ret.setTier(this.tier); @@ -369,7 +372,7 @@ class ModifierTypeGenerator extends ModifierType { class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { constructor() { - super((party: PlayerPokemon[]) => { + super((party: Pokemon[]) => { const attackMoveTypes = party.map(p => p.moveset.map(m => m.getMove()).filter(m => m instanceof AttackMove).map(m => m.type)).flat(); const attackMoveTypeWeights = new Map(); let totalWeight = 0; @@ -408,7 +411,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { constructor() { - super((party: PlayerPokemon[]) => { + super((party: Pokemon[]) => { const evolutionItemPool = party.filter(p => pokemonEvolutions.hasOwnProperty(p.species.speciesId)).map(p => { const evolutions = pokemonEvolutions[p.species.speciesId] return evolutions.filter(e => e.item !== EvolutionItem.NONE && (!e.condition || e.condition.predicate(p))); @@ -436,119 +439,205 @@ class WeightedModifierType { } } +const modifierTypes = { + POKEBALL: new AddPokeballModifierType(PokeballType.POKEBALL, 5, 'pb'), + GREAT_BALL: new AddPokeballModifierType(PokeballType.GREAT_BALL, 5, 'gb'), + ULTRA_BALL: new AddPokeballModifierType(PokeballType.ULTRA_BALL, 5, 'ub'), + MASTER_BALL: new AddPokeballModifierType(PokeballType.MASTER_BALL, 1, 'mb'), + + RARE_CANDY: new PokemonLevelIncrementModifierType('RARE CANDY'), + + EVOLUTION_ITEM: new EvolutionItemModifierTypeGenerator(), + + POTION: new PokemonHpRestoreModifierType('POTION', 20), + SUPER_POTION: new PokemonHpRestoreModifierType('SUPER POTION', 50), + HYPER_POTION: new PokemonHpRestoreModifierType('HYPER POTION', 200), + MAX_POTION: new PokemonHpRestoreModifierType('MAX POTION', 100, true), + + REVIVE: new PokemonReviveModifierType('REVIVE', 50), + MAX_REVIVE: new PokemonReviveModifierType('MAX REVIVE', 100), + + FULL_HEAL: new PokemonStatusHealModifierType('FULL HEAL'), + + SACRED_ASH: new AllPokemonFullReviveModifierType('SACRED ASH'), + + ETHER: new PokemonPpRestoreModifierType('ETHER', 10), + MAX_ETHER: new PokemonPpRestoreModifierType('MAX ETHER', -1), + + ELIXIR: new PokemonAllMovePpRestoreModifierType('ELIXIR', 10), + MAX_ELIXIR: new PokemonAllMovePpRestoreModifierType('MAX ELIXIR', -1), + + TEMP_STAT_BOOSTER: new ModifierTypeGenerator((party: Pokemon[]) => { + const randTempBattleStat = Utils.randInt(7) as TempBattleStat; + return new TempBattleStatBoosterModifierType(randTempBattleStat); + }), + + BASE_STAT_BOOSTER: new ModifierTypeGenerator((party: Pokemon[]) => { + const randStat = Utils.randInt(6) as Stat; + return new PokemonBaseStatBoosterModifierType(getBaseStatBoosterItemName(randStat), randStat); + }), + + ATTACK_TYPE_BOOSTER: new AttackTypeBoosterModifierTypeGenerator(), + + BERRY: new ModifierTypeGenerator((party: Pokemon[]) => { + const berryTypes = Utils.getEnumValues(BerryType); + const randBerryType = berryTypes[Utils.randInt(berryTypes.length)]; + return new PokemonHeldItemModifierType(getBerryName(randBerryType), getBerryEffectDescription(randBerryType), + (type, args) => new Modifiers.BerryModifier(type, (args[0] as Pokemon).id, randBerryType), + null, 'berry'); + }), + + TM: new ModifierTypeGenerator((party: Pokemon[]) => { + const partyMemberCompatibleTms = party.map(p => (p as PlayerPokemon).compatibleTms); + const uniqueCompatibleTms = partyMemberCompatibleTms.flat().filter((tm, i, array) => array.indexOf(tm) === i); + if (!uniqueCompatibleTms.length) + return null; + const randTmIndex = Utils.randInt(uniqueCompatibleTms.length); + return new TmModifierType(uniqueCompatibleTms[randTmIndex]); + }), + + EXP_SHARE: new ModifierType('EXP. SHARE', 'All POKéMON in your party gain an additional 10% of a battle\'s EXP. Points', + (type, _args) => new Modifiers.ExpShareModifier(type), 'exp_share'), + EXP_BALANCE: new ModifierType('EXP. BALANCE', 'All EXP. Points received from battles is split among the lower leveled party members', + (type, _args) => new Modifiers.ExpBalanceModifier(type), 'exp_balance'), + + EXP_CHARM: new ExpBoosterModifierType('EXP CHARM', 25), + GOLDEN_EXP_CHARM: new ExpBoosterModifierType('GOLDEN EXP CHARM', 100), + + LUCKY_EGG: new PokemonExpBoosterModifierType('LUCKY EGG', 50), + + HEALING_CHARM: new ModifierType('HEALING CHARM', 'Doubles the effectiveness of HP restoring moves and items (excludes revives)', + (type, _args) => new Modifiers.HealingBoosterModifier(type, 2), 'healing_charm'), + + OVAL_CHARM: 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 Modifiers.PartyShareModifier(type), 'oval_charm'), + + BERRY_POUCH: new ModifierType('BERRY POUCH', 'Adds a 25% chance that a used berry will not be consumed', + (type, _args) => new Modifiers.PreserveBerryModifier(type)), + + SHELL_BELL: new PokemonHeldItemModifierType('SHELL BELL', 'Heals 1/8 of a POKéMON\'s dealt damage', + (type, args) => new Modifiers.HitHealModifier(type, (args[0] as Pokemon).id)), + + SHINY_CHARM: new ModifierType('SHINY CHARM', 'Dramatically increases the chance of a wild POKéMON being shiny', (type, _args) => new Modifiers.ShinyRateBoosterModifier(type)), + + GOLDEN_POKEBALL: new ModifierType(`GOLDEN ${getPokeballName(PokeballType.POKEBALL)}`, 'Adds 1 extra item option at the end of every battle', + (type, _args) => new Modifiers.ExtraModifierModifier(type), 'pb_gold', null, 'pb_bounce_1'), +}; + const modifierPool = { [ModifierTier.COMMON]: [ - new WeightedModifierType(new AddPokeballModifierType(PokeballType.POKEBALL, 5, 'pb'), 6), - new WeightedModifierType(new PokemonLevelIncrementModifierType('RARE CANDY'), 2), - new WeightedModifierType(new PokemonHpRestoreModifierType('POTION', 20), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.POKEBALL, 6), + new WeightedModifierType(modifierTypes.RARE_CANDY, 2), + new WeightedModifierType(modifierTypes.POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 10 || p.getHpRatio() <= 0.875).length; return thresholdPartyMemberCount * 3; }), - new WeightedModifierType(new PokemonHpRestoreModifierType('SUPER POTION', 50), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.SUPER_POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 25 || p.getHpRatio() <= 0.75).length; return thresholdPartyMemberCount; }), - new WeightedModifierType(new PokemonPpRestoreModifierType('ETHER', 10), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.ETHER, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length; return thresholdPartyMemberCount * 3; }), - new WeightedModifierType(new PokemonPpRestoreModifierType('MAX ETHER', -1), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.MAX_ETHER, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length; return thresholdPartyMemberCount; }), - new WeightedModifierType(new ModifierTypeGenerator((party: PlayerPokemon[]) => { - const randTempBattleStat = Utils.randInt(7) as TempBattleStat; - return new TempBattleStatBoosterModifierType(randTempBattleStat); - }), 4), - new WeightedModifierType(new ModifierTypeGenerator((party: PlayerPokemon[]) => { - const berryTypes = Utils.getEnumValues(BerryType); - const randBerryType = berryTypes[Utils.randInt(berryTypes.length)]; - return new PokemonHeldItemModifierType(getBerryName(randBerryType), getBerryEffectDescription(randBerryType), - (type, args) => new Modifiers.BerryModifier(type, (args[0] as PlayerPokemon).id, randBerryType), - () => null, null, 'berry'); - }), 2) + new WeightedModifierType(modifierTypes.TEMP_STAT_BOOSTER, 4), + new WeightedModifierType(modifierTypes.BERRY, 2) ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), [ModifierTier.GREAT]: [ - new WeightedModifierType(new AddPokeballModifierType(PokeballType.GREAT_BALL, 5, 'gb'), 12), - new WeightedModifierType(new PokemonStatusHealModifierType('FULL HEAL'), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.GREAT_BALL, 12), + new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => { const statusEffectPartyMemberCount = party.filter(p => p.hp && !!p.status).length; return statusEffectPartyMemberCount * 8; }), - new WeightedModifierType(new PokemonReviveModifierType('REVIVE', 50), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => { const faintedPartyMemberCount = party.filter(p => !p.hp).length; return faintedPartyMemberCount * 6; }), - new WeightedModifierType(new PokemonReviveModifierType('MAX REVIVE', 100), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => { const faintedPartyMemberCount = party.filter(p => !p.hp).length; return faintedPartyMemberCount * 2; }), - new WeightedModifierType(new AllPokemonFullReviveModifierType('SACRED ASH'), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => { return party.filter(p => !p.hp).length >= Math.ceil(party.length / 2) ? 1 : 0; }), - new WeightedModifierType(new PokemonHpRestoreModifierType('HYPER POTION', 200), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length; return thresholdPartyMemberCount * 2; }), - new WeightedModifierType(new PokemonHpRestoreModifierType('MAX POTION', 100, true), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.MAX_POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.getInverseHp() >= 150 || p.getHpRatio() <= 0.5).length; return Math.ceil(thresholdPartyMemberCount / 1.5); }), - new WeightedModifierType(new PokemonAllMovePpRestoreModifierType('ELIXIR', 10), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.ELIXIR, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length; return thresholdPartyMemberCount * 2; }), - new WeightedModifierType(new PokemonAllMovePpRestoreModifierType('MAX ELIXIR', -1), (party: PlayerPokemon[]) => { + new WeightedModifierType(modifierTypes.MAX_ELIXIR, (party: Pokemon[]) => { const thresholdPartyMemberCount = party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length; return Math.ceil(thresholdPartyMemberCount / 1.5); }), - 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); - if (!uniqueCompatibleTms.length) - return null; - const randTmIndex = Utils.randInt(uniqueCompatibleTms.length); - return new TmModifierType(uniqueCompatibleTms[randTmIndex]); - }), 4), - new WeightedModifierType(new ModifierType('EXP. SHARE', 'All POKéMON in your party gain an additional 10% of a battle\'s EXP. Points', (type, _args) => new Modifiers.ExpShareModifier(type), 'exp_share'), 2), - new WeightedModifierType(new ModifierTypeGenerator((party: PlayerPokemon[]) => { - const randStat = Utils.randInt(6) as Stat; - return new PokemonBaseStatBoosterModifierType(getBaseStatBoosterItemName(randStat), randStat); - }), 4) + new WeightedModifierType(modifierTypes.TEMP_STAT_BOOSTER, 4), + new WeightedModifierType(modifierTypes.EXP_SHARE, 2), + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 4) ].map(m => { m.setTier(ModifierTier.GREAT); return m; }), [ModifierTier.ULTRA]: [ - new WeightedModifierType(new AddPokeballModifierType(PokeballType.ULTRA_BALL, 5, 'ub'), 8), - new WeightedModifierType(new EvolutionItemModifierTypeGenerator(), 12), - new WeightedModifierType(new AttackTypeBoosterModifierTypeGenerator(), 5), - 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 Modifiers.PartyShareModifier(type), 'oval_charm'), - new ModifierType('HEALING CHARM', 'Doubles the effectiveness of HP restoring moves and items (excludes revives)', (type, _args) => new Modifiers.HealingBoosterModifier(type, 2), 'healing_charm'), - new WeightedModifierType(new PokemonHeldItemModifierType('SHELL BELL', 'Heals 1/8 of a POKéMON\'s dealt damage', (type, args) => new Modifiers.HitHealModifier(type, (args[0] as PlayerPokemon).id)), 2), - new WeightedModifierType(new ExpBoosterModifierType('EXP CHARM', 25), 4), - new WeightedModifierType(new PokemonExpBoosterModifierType('LUCKY EGG', 50), 3), - new WeightedModifierType(new ModifierType('BERRY POUCH', 'Adds a 25% chance that a used berry will not be consumed', - (type, _args) => new Modifiers.PreserveBerryModifier(type)), 3), - new WeightedModifierType(new ModifierType('EXP. BALANCE', 'All EXP. Points received from battles is split among the lower leveled party members', (type, _args) => new Modifiers.ExpBalanceModifier(type), 'exp_balance'), 1) + new WeightedModifierType(modifierTypes.ULTRA_BALL, 8), + new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, 12), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5), + modifierTypes.OVAL_CHARM, + modifierTypes.HEALING_CHARM, + new WeightedModifierType(modifierTypes.SHELL_BELL, 2), + new WeightedModifierType(modifierTypes.EXP_CHARM, 4), + new WeightedModifierType(modifierTypes.LUCKY_EGG, 3), + new WeightedModifierType(modifierTypes.BERRY_POUCH, 3), + modifierTypes.EXP_BALANCE ].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 Modifiers.ShinyRateBoosterModifier(type)), 2) + modifierTypes.MASTER_BALL, + new WeightedModifierType(modifierTypes.SHINY_CHARM, 2) ].map(m => { m.setTier(ModifierTier.MASTER); return m; }), [ModifierTier.LUXURY]: [ - new ModifierType(`GOLDEN ${getPokeballName(PokeballType.POKEBALL)}`, 'Adds 1 extra item option at the end of every battle', (type, _args) => new Modifiers.ExtraModifierModifier(type), 'pb_gold', null, 'pb_bounce_1'), - new ExpBoosterModifierType('GOLDEN EXP CHARM', 100) + modifierTypes.GOLDEN_POKEBALL, + modifierTypes.GOLDEN_EXP_CHARM ].map(m => { m.setTier(ModifierTier.LUXURY); return m; }), }; +const enemyModifierPool = { + [ModifierTier.COMMON]: [ + modifierTypes.BERRY + ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), + [ModifierTier.GREAT]: [ + modifierTypes.BASE_STAT_BOOSTER + ].map(m => { m.setTier(ModifierTier.GREAT); return m; }), + [ModifierTier.ULTRA]: [ + new WeightedModifierType(new AttackTypeBoosterModifierTypeGenerator(), 5), + new WeightedModifierType(modifierTypes.LUCKY_EGG, 2), + ].map(m => { m.setTier(ModifierTier.ULTRA); return m; }), + [ModifierTier.MASTER]: [ + modifierTypes.SHELL_BELL + ].map(m => { m.setTier(ModifierTier.MASTER); return m; }) +}; + let modifierPoolThresholds = {}; let ignoredPoolIndexes = {}; -export function regenerateModifierPoolThresholds(party: PlayerPokemon[]) { - ignoredPoolIndexes = {}; - modifierPoolThresholds = Object.fromEntries(new Map(Object.keys(modifierPool).map(t => { - ignoredPoolIndexes[t] = []; +let enemyModifierPoolThresholds = {}; +let enemyIgnoredPoolIndexes = {}; + +export function regenerateModifierPoolThresholds(party: Pokemon[], player?: boolean) { + if (player === undefined) + player = true; + const pool = player ? modifierPool : enemyModifierPool; + const ignoredIndexes = {}; + const thresholds = Object.fromEntries(new Map(Object.keys(pool).map(t => { + ignoredIndexes[t] = []; const thresholds = new Map(); let i = 0; - modifierPool[t].reduce((total: integer, modifierType: ModifierType | WeightedModifierType) => { + pool[t].reduce((total: integer, modifierType: ModifierType | WeightedModifierType) => { if (modifierType instanceof WeightedModifierType) { const weightedModifierType = modifierType as WeightedModifierType; const weight = weightedModifierType.weight instanceof Function @@ -557,7 +646,7 @@ export function regenerateModifierPoolThresholds(party: PlayerPokemon[]) { if (weight) total += weight; else { - ignoredPoolIndexes[t].push(i++); + ignoredIndexes[t].push(i++); return total; } } else @@ -567,9 +656,16 @@ export function regenerateModifierPoolThresholds(party: PlayerPokemon[]) { }, 0); return [ t, Object.fromEntries(thresholds) ] }))); + if (player) { + modifierPoolThresholds = thresholds; + ignoredPoolIndexes = ignoredIndexes; + } else { + enemyModifierPoolThresholds = thresholds; + enemyIgnoredPoolIndexes = ignoredIndexes; + } } -export function getModifierTypeOptionsForWave(waveIndex: integer, count: integer, party: PlayerPokemon[]): ModifierTypeOption[] { +export function getPlayerModifierTypeOptionsForWave(waveIndex: integer, count: integer, party: PlayerPokemon[]): ModifierTypeOption[] { if (waveIndex % 10 === 0) return modifierPool[ModifierTier.LUXURY].map(m => new ModifierTypeOption(m, false)); const options: ModifierTypeOption[] = []; @@ -578,43 +674,58 @@ export function getModifierTypeOptionsForWave(waveIndex: integer, count: integer let candidate = getNewModifierTypeOption(party); let r = 0; while (options.length && ++r < retryCount && options.filter(o => o.type.name === candidate.type.name || o.type.group === candidate.type.group).length) - candidate = getNewModifierTypeOption(party, candidate.type.tier, candidate.upgraded); + candidate = getNewModifierTypeOption(party, true, candidate.type.tier, candidate.upgraded); options.push(candidate); }); return options; } -function getNewModifierTypeOption(party: PlayerPokemon[], tier?: ModifierTier, upgrade?: boolean): ModifierTypeOption { +export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[]): PokemonHeldItemModifierType[] { + return new Array(count).fill(0).map(() => getNewModifierTypeOption(party, false).type as PokemonHeldItemModifierType); +} + +function getNewModifierTypeOption(party: Pokemon[], player?: boolean, tier?: ModifierTier, upgrade?: boolean): ModifierTypeOption { + if (player === undefined) + player = true; const tierValue = Utils.randInt(256); if (tier === undefined) { - const partyShinyCount = party.filter(p => p.shiny).length; - const upgradeOdds = Math.floor(32 / Math.max((partyShinyCount * 2), 1)); - upgrade = !Utils.randInt(upgradeOdds); + if (player) { + const partyShinyCount = party.filter(p => p.shiny).length; + const upgradeOdds = Math.floor(32 / Math.max((partyShinyCount * 2), 1)); + upgrade = !Utils.randInt(upgradeOdds); + } else + upgrade = false; tier = (tierValue >= 52 ? ModifierTier.COMMON : tierValue >= 8 ? ModifierTier.GREAT : tierValue >= 1 ? ModifierTier.ULTRA : ModifierTier.MASTER) + (upgrade ? 1 : 0); } - const thresholds = Object.keys(modifierPoolThresholds[tier]); + + const thresholds = Object.keys((player ? modifierPoolThresholds : enemyModifierPoolThresholds)[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]; + index = (player ? modifierPoolThresholds : enemyModifierPoolThresholds)[tier][threshold]; break; } } - console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]) - let modifierType: ModifierType | WeightedModifierType = modifierPool[tier][index]; + + if (player) + console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]) + let modifierType: ModifierType | WeightedModifierType = (player ? modifierPool : enemyModifierPool)[tier][index]; if (modifierType instanceof WeightedModifierType) modifierType = (modifierType as WeightedModifierType).modifierType; if (modifierType instanceof ModifierTypeGenerator) { modifierType = (modifierType as ModifierTypeGenerator).generateType(party); if (modifierType === null) { - console.log(ModifierTier[tier], upgrade); - return getNewModifierTypeOption(party, tier, upgrade); + if (player) + console.log(ModifierTier[tier], upgrade); + return getNewModifierTypeOption(party, player, tier, upgrade); } } - console.log(modifierType); + + console.log(modifierType, !player ? '(enemy)' : ''); + return new ModifierTypeOption(modifierType as ModifierType, upgrade); } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 61ec181e36f..a12a5814f25 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,9 +1,9 @@ import * as ModifierTypes from './modifier-type'; -import { CommonAnimPhase, LearnMovePhase, LevelUpPhase, PokemonHealPhase } from "../battle-phases"; +import { LearnMovePhase, LevelUpPhase, PokemonHealPhase } from "../battle-phases"; import BattleScene from "../battle-scene"; import { getLevelTotalExp } from "../data/exp"; import { PokeballType } from "../data/pokeball"; -import Pokemon, { PlayerPokemon } from "../pokemon"; +import Pokemon, { EnemyPokemon, PlayerPokemon } from "../pokemon"; import { Stat } from "../data/pokemon-stat"; import { addTextObject, TextStyle } from "../ui/text"; import { Type } from '../data/type'; @@ -13,15 +13,17 @@ import { getPokemonMessage } from '../messages'; import * as Utils from "../utils"; import { TempBattleStat } from '../data/temp-battle-stat'; import { BerryType, getBerryEffectFunc, getBerryPredicate } from '../data/berry'; -import { CommonAnim } from '../data/battle-anims'; type ModifierType = ModifierTypes.ModifierType; export type ModifierPredicate = (modifier: Modifier) => boolean; export class ModifierBar extends Phaser.GameObjects.Container { - constructor(scene: BattleScene) { - super(scene, 1, 2); + private player: boolean; + constructor(scene: BattleScene, enemy?: boolean) { + super(scene, 1 + (enemy ? 302 : 0), 2); + + this.player = !enemy; this.setScale(0.5); } @@ -41,7 +43,7 @@ export class ModifierBar extends Phaser.GameObjects.Container { const x = (this.getIndex(icon) % rowIcons) * 26 / (rowIcons / 12); const y = Math.floor(this.getIndex(icon) / rowIcons) * 20; - icon.setPosition(x, y); + icon.setPosition(this.player ? x : -x, y); } } @@ -272,7 +274,8 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { } getPokemon(scene: BattleScene) { - return scene.getParty().find(p => p.id === this.pokemonId); + const findInParty = (party: Pokemon[]) => party.find(p => p.id === this.pokemonId); + return findInParty(scene.getParty()) || findInParty(scene.getEnemyParty()); } } @@ -351,7 +354,7 @@ export class HitHealModifier extends PokemonHeldItemModifier { } apply(args: any[]): boolean { - const pokemon = args[0] as PlayerPokemon; + const pokemon = args[0] as Pokemon; if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) { const scene = pokemon.scene; @@ -375,7 +378,7 @@ export class BerryModifier extends PokemonHeldItemModifier { } match(modifier: Modifier) { - return modifier instanceof BerryModifier && (modifier as BerryModifier).berryType === this.berryType; + return modifier instanceof BerryModifier && (modifier as BerryModifier).berryType === this.berryType && modifier.pokemonId === this.pokemonId; } clone() { @@ -390,7 +393,9 @@ export class BerryModifier extends PokemonHeldItemModifier { const pokemon = args[0] as Pokemon; const preserve = new Utils.BooleanHolder(false); - pokemon.scene.applyModifiers(PreserveBerryModifier, preserve); + pokemon.scene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), preserve); + + console.log(pokemon.isPlayer()); getBerryEffectFunc(this.berryType)(pokemon); if (!preserve.value) @@ -439,7 +444,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier { } shouldApply(args: any[]): boolean { - return args.length && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId); + return args.length && args[0] instanceof PlayerPokemon && (this.pokemonId === -1 || (args[0] as PlayerPokemon).id === this.pokemonId); } getPokemon(scene: BattleScene) { @@ -623,7 +628,7 @@ export class PartyShareModifier extends PersistentModifier { continue; const newHeldItemModifier = heldItemModifier.clone() as PokemonHeldItemModifier; newHeldItemModifier.pokemonId = p.id; - scene.addModifier(newHeldItemModifier, true); + scene.addModifier(newHeldItemModifier, false, true); } } } diff --git a/src/pokemon.ts b/src/pokemon.ts index 13eefa6b7eb..3226ab37323 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -109,7 +109,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.shiny === undefined) { let shinyThreshold = new Utils.IntegerHolder(32); - this.scene.applyModifiers(ShinyRateBoosterModifier, shinyThreshold); + this.scene.applyModifiers(ShinyRateBoosterModifier, this.isPlayer(), shinyThreshold); console.log(shinyThreshold.value); this.shiny = (E ^ F) < shinyThreshold.value; @@ -267,7 +267,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const battleStat = (stat - 1) as BattleStat; const statLevel = new Utils.IntegerHolder(this.summonData.battleStats[battleStat]); if (this.isPlayer()) - this.scene.applyModifiers(TempBattleStatBoosterModifier, battleStat as integer as TempBattleStat, statLevel); + this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), battleStat as integer as TempBattleStat, statLevel); let ret = this.stats[stat] * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value)); if (stat === Stat.SPDEF && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) ret *= 1.5; @@ -280,7 +280,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.stats) this.stats = [ 0, 0, 0, 0, 0, 0 ]; const baseStats = this.getSpeciesForm().baseStats.slice(0); - this.scene.applyModifiers(PokemonBaseStatModifier, this, baseStats); + this.scene.applyModifiers(PokemonBaseStatModifier, this.isPlayer(), this, baseStats); const stats = Utils.getEnumValues(Stat); for (let s of stats) { const isHp = s === Stat.HP; @@ -464,11 +464,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const typeMultiplier = getTypeDamageMultiplier(move.type, this.getSpeciesForm().type1) * (this.getSpeciesForm().type2 !== null ? getTypeDamageMultiplier(move.type, this.getSpeciesForm().type2) : 1); const weatherTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(move.type); applyMoveAttrs(VariablePowerAttr, source, this, move, power); - this.scene.applyModifiers(AttackTypeBoosterModifier, source, power); + this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, power); const critLevel = new Utils.IntegerHolder(0); applyMoveAttrs(HighCritAttr, source, this, move, critLevel); - if (source.isPlayer()) - this.scene.applyModifiers(TempBattleStatBoosterModifier, TempBattleStat.CRIT, critLevel); + this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel); const critChance = Math.ceil(16 / Math.pow(2, critLevel.value)); let isCritical = !source.getTag(BattleTagType.NO_CRIT) && (critChance === 1 || !Utils.randInt(critChance)); const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK); @@ -968,7 +967,7 @@ export class EnemyPokemon extends Pokemon { let ret: PlayerPokemon = null; if (party.length < 6) { - const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.formIndex, this.gender, this.shiny); + const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.formIndex, this.gender, this.shiny, this); party.push(newPokemon); ret = newPokemon; }