Add boss health bars

This commit is contained in:
Flashfyre 2024-01-07 23:17:24 -05:00
parent 52e3c6b730
commit eedad7d678
17 changed files with 496 additions and 115 deletions

View File

@ -0,0 +1,83 @@
{
"textures": [
{
"image": "overlay_hp_boss.png",
"format": "RGBA8888",
"size": {
"w": 96,
"h": 12
},
"scale": 1,
"frames": [
{
"filename": "high",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 86,
"h": 4
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 86,
"h": 4
},
"frame": {
"x": 0,
"y": 0,
"w": 86,
"h": 4
}
},
{
"filename": "medium",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 86,
"h": 4
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 98,
"h": 4
},
"frame": {
"x": 0,
"y": 4,
"w": 86,
"h": 4
}
},
{
"filename": "low",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 86,
"h": 4
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 86,
"h": 4
},
"frame": {
"x": 0,
"y": 8,
"w": 86,
"h": 4
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:0ab3d30defc8fe4d0802d006063b09c5:9cc1fb380aa2908ff6c9e92f310fde62:e6c4614fcfcf040f918551c90d4448f7$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

View File

@ -55,7 +55,7 @@ export class Arena {
}
randomSpecies(waveIndex: integer, level: integer, attempt?: integer): PokemonSpecies {
const isBoss = (waveIndex % 10 === 0 || (this.scene.gameMode !== GameMode.CLASSIC && Utils.randSeedInt(100) < Math.min(Math.max(Math.ceil((waveIndex - 250) / 50), 0) * 2, 30))) && !!this.pokemonPool[BiomePoolTier.BOSS].length
const isBoss = !!this.scene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length
&& (this.biomeType !== Biome.END || this.scene.gameMode === GameMode.CLASSIC || waveIndex % 250 === 0);
const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64);
let tier = !isBoss

View File

@ -1,7 +1,7 @@
import BattleScene, { bypassLogin, startingLevel, startingWave } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./pokemon";
import * as Utils from './utils';
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr } from "./data/move";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr, RechargeAttr } from "./data/move";
import { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat";
@ -39,7 +39,6 @@ import { vouchers } from "./system/voucher";
import { loggedInUser, updateUserInfo } from "./account";
import { GameDataType } from "./system/game-data";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./anims";
import { Nature } from "./data/nature";
export class LoginPhase extends BattlePhase {
private showText: boolean;
@ -229,7 +228,7 @@ export class SelectStarterPhase extends BattlePhase {
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = new PlayerPokemon(this.scene, starter.species, startingLevel, starterProps.abilityIndex, starterProps.formIndex, starterGender, starterProps.shiny, starterIvs, starter.nature);
const starterPokemon = this.scene.addPlayerPokemon(starter.species, startingLevel, starterProps.abilityIndex, starterProps.formIndex, starterGender, starterProps.shiny, starterIvs, starter.nature);
if (starter.pokerus)
starterPokemon.pokerus = true;
if (this.scene.gameMode === GameMode.SPLICED_ENDLESS)
@ -372,7 +371,7 @@ export class EncounterPhase extends BattlePhase {
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
else {
const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true);
battle.enemyParty[e] = new EnemyPokemon(this.scene, enemySpecies, level, false);
battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, false, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies));
this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => {
applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]);
});
@ -386,8 +385,10 @@ export class EncounterPhase extends BattlePhase {
this.scene.gameData.setPokemonSeen(enemyPokemon);
}
if (this.scene.gameMode === GameMode.CLASSIC && (battle.waveIndex === 200 || !(battle.waveIndex % 250)) && enemyPokemon.species.speciesId === Species.ETERNATUS)
if (this.scene.gameMode === GameMode.CLASSIC && (battle.waveIndex === 200 || !(battle.waveIndex % 250)) && enemyPokemon.species.speciesId === Species.ETERNATUS) {
enemyPokemon.formIndex = 1;
enemyPokemon.setBoss();
}
loadEnemyAssets.push(enemyPokemon.loadAssets());
@ -1246,15 +1247,21 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else if (cursor < 4) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor };
if (targets.length > 1)
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
else {
const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true));
if (targetPokemon.isBoss() && targetPokemon.getBossSegmentIndex()) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(`The target Pokémon is too strong to be caught!\nYou need to weaken it first!`, null, () => {
this.scene.ui.showText(null, 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor };
this.scene.currentBattle.turnCommands[this.fieldIndex].targets = targets;
if (this.fieldIndex)
this.scene.currentBattle.turnCommands[this.fieldIndex - 1].skip = true;
success = true;
}
success = true;
}
}
break;
@ -1741,6 +1748,9 @@ export class MovePhase extends BattlePhase {
if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER)
return;
}
if (this.pokemon.getTag(BattlerTagType.RECHARGING))
return;
this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500);
}
@ -2951,7 +2961,7 @@ export class PokemonHealPhase extends CommonAnimPhase {
if (!this.revive)
this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
const healAmount = new Utils.NumberHolder(this.hpHealed * hpRestoreMultiplier.value);
pokemon.heal(healAmount.value);
healAmount.value = pokemon.heal(healAmount.value);
this.scene.validateAchvs(HealAchv, healAmount);
pokemon.updateInfo().then(() => super.end());
} else if (this.showFullHpMessage)
@ -2979,7 +2989,7 @@ export class AttemptCapturePhase extends PokemonPhase {
start() {
super.start();
const pokemon = this.getPokemon();
const pokemon = this.getPokemon() as EnemyPokemon;
if (!pokemon?.hp)
return this.end();
@ -2988,7 +2998,11 @@ export class AttemptCapturePhase extends PokemonPhase {
this.originalY = pokemon.y;
const _3m = 3 * pokemon.getMaxHp();
const relMaxHp = !pokemon.isBoss()
? pokemon.getMaxHp()
: Math.round(pokemon.getMaxHp() / pokemon.bossSegments);
const _3m = 3 * relMaxHp;
const _2h = 2 * pokemon.hp;
const catchRate = pokemon.species.catchRate;
const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType);

View File

@ -41,6 +41,8 @@ import { Voucher, vouchers } from './system/voucher';
import { Gender } from './data/gender';
import UIPlugin from 'phaser3-rex-plugins/templates/ui/ui-plugin';
import { WindowVariant, getWindowVariantSuffix } from './ui/window';
import PokemonData from './system/pokemon-data';
import { Nature } from './data/nature';
const enableAuto = true;
const quickStart = false;
@ -207,10 +209,12 @@ export default class BattleScene extends Phaser.Scene {
this.loadImage('pbinfo_player', 'ui');
this.loadImage('pbinfo_player_mini', 'ui');
this.loadImage('pbinfo_enemy_mini', 'ui');
this.loadImage('pbinfo_enemy_boss', 'ui');
this.loadImage('overlay_lv', 'ui');
this.loadAtlas('numbers', 'ui');
this.loadAtlas('numbers_red', 'ui');
this.loadAtlas('overlay_hp', 'ui');
this.loadAtlas('overlay_hp_boss', 'ui');
this.loadImage('overlay_exp', 'ui');
this.loadImage('icon_owned', 'ui');
this.loadImage('ability_bar', 'ui');
@ -526,7 +530,7 @@ export default class BattleScene extends Phaser.Scene {
for (let s = 0; s < 3; s++) {
const playerSpecies = this.randomSpecies(startingWave, startingLevel);
const playerPokemon = new PlayerPokemon(this, playerSpecies, startingLevel, 0, 0);
const playerPokemon = this.addPlayerPokemon(playerSpecies, startingLevel, 0, 0);
playerPokemon.setVisible(false);
this.party.push(playerPokemon);
@ -637,6 +641,22 @@ export default class BattleScene extends Phaser.Scene {
return findInParty(this.getParty()) || findInParty(this.getEnemyParty());
}
addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon {
const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, ivs, nature, dataSource);
if (postProcess)
postProcess(pokemon);
pokemon.init();
return pokemon;
}
addEnemyPokemon(species: PokemonSpecies, level: integer, trainer: boolean, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
const pokemon = new EnemyPokemon(this, species, level, trainer, boss, dataSource);
if (postProcess)
postProcess(pokemon);
pokemon.init();
return pokemon;
}
reset(clearScene?: boolean): void {
this.seed = Utils.randomString(16);
console.log('Seed:', this.seed);
@ -715,7 +735,7 @@ export default class BattleScene extends Phaser.Scene {
if (this.gameMode !== GameMode.CLASSIC)
newBattleType = BattleType.WILD;
else if (battleType === undefined) {
if ((newWaveIndex % 30) === 20)
if ((newWaveIndex % 30) === 20 && newWaveIndex !== 200)
newBattleType = BattleType.TRAINER;
else if (newWaveIndex % 10 !== 1 && newWaveIndex % 10) {
const trainerChance = this.arena.getTrainerChance();
@ -860,6 +880,33 @@ export default class BattleScene extends Phaser.Scene {
return ret;
}
getEncounterBossSegments(waveIndex: integer, level: integer, species?: PokemonSpecies, forceBoss: boolean = false): integer {
let isBoss: boolean;
if (forceBoss || (species && (species.pseudoLegendary || species.legendary || species.mythical)))
isBoss = true;
else {
this.executeWithSeedOffset(() => {
isBoss = waveIndex % 10 === 0 || (this.gameMode !== GameMode.CLASSIC && Utils.randSeedInt(100) < Math.min(Math.max(Math.ceil((waveIndex - 250) / 50), 0) * 2, 30));
}, waveIndex << 2);
}
if (!isBoss)
return 0;
let ret: integer = 2;
if (level >= 100)
ret++;
if (species) {
if (species.baseTotal >= 670)
ret++;
if (species.legendary)
ret++;
}
ret += Math.floor(waveIndex / 250);
return ret;
}
trySpreadPokerus(): void {
const party = this.getParty();
const infectedIndexes: integer[] = [];

View File

@ -610,7 +610,7 @@ export class PerishSongTag extends BattlerTag {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, `\'s perish count fell to ${this.turnCount}.`));
else {
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.ONE_HIT_KO));
pokemon.damage(pokemon.hp);
pokemon.damage(pokemon.hp, true, true);
}
return ret;

View File

@ -58,8 +58,8 @@ export enum MoveFlags {
WIND_MOVE = 8192
}
type MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveCondition = (user: Pokemon, move: Move) => boolean;
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
export default class Move {
public id: Moves;
@ -111,18 +111,24 @@ export default class Move {
attr<T extends new (...args: any[]) => MoveAttr>(AttrType: T, ...args: ConstructorParameters<T>): this {
const attr = new AttrType(...args);
this.attrs.push(attr);
const attrCondition = attr.getCondition();
if (attrCondition)
let attrCondition = attr.getCondition();
if (attrCondition) {
if (typeof attrCondition === 'function')
attrCondition = new MoveCondition(attrCondition);
this.conditions.push(attrCondition);
}
return this;
}
addAttr(attr: MoveAttr): this {
this.attrs.push(attr);
const attrCondition = attr.getCondition();
if (attrCondition)
let attrCondition = attr.getCondition();
if (attrCondition) {
if (typeof attrCondition === 'function')
attrCondition = new MoveCondition(attrCondition);
this.conditions.push(attrCondition);
}
return this;
}
@ -136,7 +142,9 @@ export default class Move {
return !!(this.flags & flag);
}
condition(condition: MoveCondition): this {
condition(condition: MoveCondition | MoveConditionFunc): this {
if (typeof condition === 'function')
condition = new MoveCondition(condition as MoveConditionFunc);
this.conditions.push(condition);
return this;
@ -232,7 +240,7 @@ export default class Move {
applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean {
for (let condition of this.conditions) {
if (!condition(user, target, move))
if (!condition.apply(user, target, move))
return false;
}
@ -245,6 +253,9 @@ export default class Move {
for (let attr of this.attrs)
score += attr.getUserBenefitScore(user, target, move);
for (let condition of this.conditions)
score += condition.getUserBenefitScore(user, target, move);
return score;
}
@ -252,7 +263,7 @@ export default class Move {
let score = 0;
for (let attr of this.attrs)
score += attr.getTargetBenefitScore(user, target, move);
score += attr.getTargetBenefitScore(user, !attr.selfTarget ? target : user, move) * (target !== user && attr.selfTarget ? -1 : 1);
return score;
}
@ -1237,11 +1248,17 @@ export enum Moves {
};
export abstract class MoveAttr {
public selfTarget: boolean;
constructor(selfTarget: boolean = false) {
this.selfTarget = selfTarget;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveCondition | MoveConditionFunc {
return null;
}
@ -1261,13 +1278,10 @@ export enum MoveEffectTrigger {
}
export class MoveEffectAttr extends MoveAttr {
public selfTarget: boolean;
public trigger: MoveEffectTrigger;
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger) {
super();
this.selfTarget = !!selfTarget;
super(selfTarget);
this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY;
}
@ -1360,7 +1374,7 @@ export class MatchHpAttr extends FixedDamageAttr {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => user.hp <= target.hp;
}
@ -1388,7 +1402,7 @@ export class CounterDamageAttr extends FixedDamageAttr {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => !!user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).length;
}
}
@ -1440,7 +1454,7 @@ export class RecoilAttr extends MoveEffectAttr {
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
user.damage(recoilDamage);
user.damage(recoilDamage, true);
return true;
}
@ -1460,7 +1474,7 @@ export class SacrificialAttr extends MoveEffectAttr {
return false;
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
user.damage(user.getMaxHp());
user.damage(user.getMaxHp(), true, true);
return true;
}
@ -1500,7 +1514,7 @@ export class HealAttr extends MoveEffectAttr {
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return (1 - (this.selfTarget ? user : target).getHpRatio()) * 20;
return Math.round((1 - (this.selfTarget ? user : target).getHpRatio()) * 20);
}
}
@ -1772,7 +1786,7 @@ export class WeatherChangeAttr extends MoveEffectAttr {
return user.scene.arena.trySetWeather(this.weatherType, true);
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => !user.scene.arena.weather || (user.scene.arena.weather.weatherType !== this.weatherType && !user.scene.arena.weather.isImmutable());
}
}
@ -1804,7 +1818,7 @@ export class OneHitKOAttr extends MoveAttr {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => user.level >= target.level;
}
}
@ -1918,9 +1932,9 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
export class StatChangeAttr extends MoveEffectAttr {
public stats: BattleStat[];
public levels: integer;
private condition: MoveCondition;
private condition: MoveConditionFunc;
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveCondition) {
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc) {
super(selfTarget, MoveEffectTrigger.HIT);
this.stats = typeof(stats) === 'number'
? [ stats as BattleStat ]
@ -1980,13 +1994,13 @@ export class HpSplitAttr extends MoveEffectAttr {
if (user.hp < hpValue)
user.heal(hpValue - user.hp);
else if (user.hp > hpValue)
user.damage(user.hp - hpValue);
user.damage(user.hp - hpValue, true);
infoUpdates.push(user.updateInfo());
if (target.hp < hpValue)
target.heal(hpValue - target.hp);
else if (target.hp > hpValue)
target.damage(target.hp - hpValue);
target.damage(target.hp - hpValue, true);
infoUpdates.push(target.updateInfo());
return Promise.all(infoUpdates).then(() => resolve(true));
@ -2232,9 +2246,9 @@ export class OneHitKOAccuracyAttr extends MoveAttr {
}
export class MissEffectAttr extends MoveAttr {
private missEffectFunc: UserMoveCondition;
private missEffectFunc: UserMoveConditionFunc;
constructor(missEffectFunc: UserMoveCondition) {
constructor(missEffectFunc: UserMoveConditionFunc) {
super();
this.missEffectFunc = missEffectFunc;
@ -2280,7 +2294,7 @@ export class DisableMoveAttr extends MoveEffectAttr {
return false;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => {
if (target.summonData.disabledMove)
return false;
@ -2335,7 +2349,7 @@ export class FrenzyAttr extends MoveEffectAttr {
}
}
export const frenzyMissFunc: UserMoveCondition = (user: Pokemon, move: Move) => {
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id)
user.getMoveQueue().shift();
user.lapseTag(BattlerTagType.FRENZY);
@ -2375,7 +2389,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
return move.chance;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return this.failOnOverlap
? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType)
: null;
@ -2383,6 +2397,8 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
switch (this.tagType) {
case BattlerTagType.RECHARGING:
return -16;
case BattlerTagType.FLINCHED:
return -5;
case BattlerTagType.CONFUSED:
@ -2394,7 +2410,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.NIGHTMARE:
return -5;
case BattlerTagType.FRENZY:
return -2;
return -3;
case BattlerTagType.ENCORE:
return -2;
case BattlerTagType.INGRAIN:
@ -2415,7 +2431,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.PROTECTED:
return 5;
case BattlerTagType.PERISH_SONG:
return -8;
return -16;
case BattlerTagType.FLYING:
return 5;
case BattlerTagType.CRIT_BOOST:
@ -2460,6 +2476,12 @@ export class ConfuseAttr extends AddBattlerTagAttr {
}
}
export class RechargeAttr extends AddBattlerTagAttr {
constructor() {
super(BattlerTagType.RECHARGING, true);
}
}
export class TrapAttr extends AddBattlerTagAttr {
constructor(tagType: BattlerTagType) {
super(tagType, false, false, 3, 6);
@ -2471,7 +2493,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
super(BattlerTagType.PROTECTED, true);
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return ((user, target, move): boolean => {
let timesUsed = 0;
const moveHistory = user.getLastXMoves();
@ -2514,7 +2536,7 @@ export class FaintCountdownAttr extends AddBattlerTagAttr {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => super.getCondition()(user, target, move) && !target.isBossImmune();
}
}
@ -2560,7 +2582,7 @@ export class AddArenaTagAttr extends MoveEffectAttr {
}
export class AddArenaTrapTagAttr extends AddArenaTagAttr {
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => {
if (move.category !== MoveCategory.STATUS || !user.scene.arena.getTag(this.tagType))
return true;
@ -2619,11 +2641,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
});
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move);
}
getSwitchOutCondition(): MoveCondition {
getSwitchOutCondition(): MoveConditionFunc {
return (user, target, move) => {
const switchOutTarget = (this.user ? user : target);
const player = switchOutTarget instanceof PlayerPokemon;
@ -2736,7 +2758,7 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
}
}
const lastMoveCopiableCondition: MoveCondition = (user, target, move) => {
const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
const copiableMove = user.scene.currentBattle.lastMove;
if (!copiableMove)
@ -2770,13 +2792,13 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return lastMoveCopiableCondition;
}
}
// TODO: Review this
const targetMoveCopiableCondition: MoveCondition = (user, target, move) => {
const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
const targetMoves = target.getMoveHistory().filter(m => !m.virtual);
if (!targetMoves.length)
return false;
@ -2815,7 +2837,7 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return targetMoveCopiableCondition;
}
}
@ -2847,7 +2869,7 @@ export class SketchAttr extends MoveEffectAttr {
return true;
}
getCondition(): MoveCondition {
getCondition(): MoveConditionFunc {
return (user, target, move) => {
if (!targetMoveCopiableCondition(user, target, move))
return false;
@ -2891,7 +2913,7 @@ export class TransformAttr extends MoveEffectAttr {
}
}
const failOnGravityCondition: MoveCondition = (user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY);
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY);
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
@ -2916,6 +2938,32 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
return applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export class MoveCondition {
protected func: MoveConditionFunc;
constructor(func: MoveConditionFunc) {
this.func = func;
}
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
return this.func(user, target, move);
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 0;
}
}
export class FirstMoveCondition extends MoveCondition {
constructor() {
super((user, target, move) => !user.getMoveHistory().length);
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return this.apply(user, target, move) ? 10 : -20;
}
}
export type MoveTargetSet = {
targets: BattlerIndex[];
multiple: boolean;
@ -3127,7 +3175,7 @@ export function initMoves() {
new AttackMove(Moves.AURORA_BEAM, "Aurora Beam", Type.ICE, MoveCategory.SPECIAL, 65, 100, 20, -1, "The target is hit with a rainbow-colored beam. This may also lower the target's Attack stat.", 10, 0, 1)
.attr(StatChangeAttr, BattleStat.ATK, -1),
new AttackMove(Moves.HYPER_BEAM, "Hyper Beam", Type.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, 163, "The target is attacked with a powerful beam. The user can't move on the next turn.", -1, 0, 1)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new AttackMove(Moves.PECK, "Peck", Type.FLYING, MoveCategory.PHYSICAL, 35, 100, 35, -1, "The target is jabbed with a sharply pointed beak or horn.", -1, 0, 1),
new AttackMove(Moves.DRILL_PECK, "Drill Peck", Type.FLYING, MoveCategory.PHYSICAL, 80, 100, 20, -1, "A corkscrewing attack that strikes the target with a sharp beak acting as a drill.", -1, 0, 1),
new AttackMove(Moves.SUBMISSION, "Submission", Type.FIGHTING, MoveCategory.PHYSICAL, 80, 80, 20, -1, "The user grabs the target and recklessly dives for the ground. This also damages the user a little.", -1, 0, 1)
@ -3597,7 +3645,7 @@ export function initMoves() {
.makesContact(false),
new AttackMove(Moves.FAKE_OUT, "Fake Out", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 10, -1, "This attack hits first and makes the target flinch. It only works the first turn each time the user enters battle.", 100, 3, 3)
.attr(FlinchAttr)
.condition((user, target, move) => !user.getMoveHistory().length),
.condition(new FirstMoveCondition()),
new AttackMove(Moves.UPROAR, "Uproar (N)", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, "The user attacks in an uproar for three turns. During that time, no Pokémon can fall asleep.", -1, 0, 3)
.ignoresVirtual()
.soundBased()
@ -3712,9 +3760,9 @@ export function initMoves() {
new AttackMove(Moves.CRUSH_CLAW, "Crush Claw", Type.NORMAL, MoveCategory.PHYSICAL, 75, 95, 10, -1, "The user slashes the target with hard and sharp claws. This may also lower the target's Defense stat.", 50, 0, 3)
.attr(StatChangeAttr, BattleStat.DEF, -1),
new AttackMove(Moves.BLAST_BURN, "Blast Burn", Type.FIRE, MoveCategory.SPECIAL, 150, 90, 5, 153, "The target is razed by a fiery explosion. The user can't move on the next turn.", -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new AttackMove(Moves.HYDRO_CANNON, "Hydro Cannon", Type.WATER, MoveCategory.SPECIAL, 150, 90, 5, 154, "The target is hit with a watery blast. The user can't move on the next turn.", -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new AttackMove(Moves.METEOR_MASH, "Meteor Mash", Type.STEEL, MoveCategory.PHYSICAL, 90, 90, 10, -1, "The target is hit with a hard punch fired like a meteor. This may also raise the user's Attack stat.", 20, 0, 3)
.attr(StatChangeAttr, BattleStat.ATK, 1, true)
.punchingMove(),
@ -3791,7 +3839,7 @@ export function initMoves() {
.target(MoveTarget.USER_AND_ALLIES),
new AttackMove(Moves.DRAGON_CLAW, "Dragon Claw", Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 15, 78, "The user slashes the target with huge sharp claws.", -1, 0, 3),
new AttackMove(Moves.FRENZY_PLANT, "Frenzy Plant", Type.GRASS, MoveCategory.SPECIAL, 150, 90, 5, 155, "The user slams the target with the roots of an enormous tree. The user can't move on the next turn.", -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new SelfStatusMove(Moves.BULK_UP, "Bulk Up", Type.FIGHTING, -1, 20, 64, "The user tenses its muscles to bulk up its body, raising both its Attack and Defense stats.", -1, 0, 3)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], 1, true),
new AttackMove(Moves.BOUNCE, "Bounce", Type.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, -1, "The user bounces up high, then drops on the target on the second turn. This may also leave the target with paralysis.", 30, 0, 3)
@ -3960,7 +4008,7 @@ export function initMoves() {
.attr(StatChangeAttr, BattleStat.SPDEF, -1),
new StatusMove(Moves.SWITCHEROO, "Switcheroo (N)", Type.DARK, 100, 10, -1, "The user trades held items with the target faster than the eye can follow.", -1, 0, 4),
new AttackMove(Moves.GIGA_IMPACT, "Giga Impact", Type.NORMAL, MoveCategory.PHYSICAL, 150, 90, 5, 152, "The user charges at the target using every bit of its power. The user can't move on the next turn.", -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new SelfStatusMove(Moves.NASTY_PLOT, "Nasty Plot", Type.DARK, -1, 20, 140, "The user stimulates its brain by thinking bad thoughts. This sharply raises the user's Sp. Atk stat.", -1, 0, 4)
.attr(StatChangeAttr, BattleStat.SPATK, 2, true),
new AttackMove(Moves.BULLET_PUNCH, "Bullet Punch", Type.STEEL, MoveCategory.PHYSICAL, 40, 100, 30, -1, "The user strikes the target with tough punches as fast as bullets. This move always goes first.", -1, 1, 4)
@ -4018,7 +4066,7 @@ export function initMoves() {
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.POWER_WHIP, "Power Whip", Type.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, "The user violently whirls its vines, tentacles, or the like to harshly lash the target.", -1, 0, 4),
new AttackMove(Moves.ROCK_WRECKER, "Rock Wrecker", Type.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, "The user launches a huge boulder at the target to attack. The user can't move on the next turn.", -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true)
.attr(RechargeAttr)
.makesContact(false)
.ballBombMove(),
new AttackMove(Moves.CROSS_POISON, "Cross Poison", Type.POISON, MoveCategory.PHYSICAL, 70, 100, 20, -1, "A slashing attack with a poisonous blade that may also poison the target. Critical hits land more easily.", 10, 0, 4)
@ -4068,7 +4116,7 @@ export function initMoves() {
new AttackMove(Moves.DOUBLE_HIT, "Double Hit", Type.NORMAL, MoveCategory.PHYSICAL, 35, 90, 10, -1, "The user slams the target with a long tail, vines, or a tentacle. The target is hit twice in a row.", -1, 0, 4)
.attr(MultiHitAttr, MultiHitType._2),
new AttackMove(Moves.ROAR_OF_TIME, "Roar of Time", Type.DRAGON, MoveCategory.SPECIAL, 150, 90, 5, -1, "The user blasts the target with power that distorts even time. The user can't move on the next turn.", -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new AttackMove(Moves.SPACIAL_REND, "Spacial Rend", Type.DRAGON, MoveCategory.SPECIAL, 100, 95, 5, -1, "The user tears the target along with the space around it. Critical hits land more easily.", -1, 0, 4)
.attr(HighCritAttr),
new SelfStatusMove(Moves.LUNAR_DANCE, "Lunar Dance (N)", Type.PSYCHIC, -1, 10, -1, "The user faints. In return, the Pokémon taking its place will have its status and HP fully restored.", -1, 0, 4)
@ -4465,7 +4513,7 @@ export function initMoves() {
new SelfStatusMove(Moves.SHORE_UP, "Shore Up", Type.GROUND, -1, 10, -1, "The user regains up to half of its max HP. It restores more HP in a sandstorm.", -1, 0, 7)
.attr(SandHealAttr),
new AttackMove(Moves.FIRST_IMPRESSION, "First Impression", Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, "Although this move has great power, it only works the first turn each time the user enters battle.", -1, 2, 7)
.condition((user, target, move) => !user.getMoveHistory().length),
.condition(new FirstMoveCondition()),
new SelfStatusMove(Moves.BANEFUL_BUNKER, "Baneful Bunker (N)", Type.POISON, -1, 10, -1, "In addition to protecting the user from attacks, this move also poisons any attacker that makes direct contact.", -1, 4, 7),
new AttackMove(Moves.SPIRIT_SHACKLE, "Spirit Shackle (N)", Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, -1, "The user attacks while simultaneously stitching the target's shadow to the ground to prevent the target from escaping.", -1, 0, 7),
new AttackMove(Moves.DARKEST_LARIAT, "Darkest Lariat (N)", Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, "The user swings both arms and hits the target. The target's stat changes don't affect this attack's damage.", -1, 0, 7),
@ -4551,7 +4599,7 @@ export function initMoves() {
new AttackMove(Moves.LIQUIDATION, "Liquidation", Type.WATER, MoveCategory.PHYSICAL, 85, 100, 10, -1, "The user slams into the target using a full-force blast of water. This may also lower the target's Defense stat.", 20, 0, 7)
.attr(StatChangeAttr, BattleStat.DEF, -1),
new AttackMove(Moves.PRISMATIC_LASER, "Prismatic Laser", Type.PSYCHIC, MoveCategory.SPECIAL, 160, 100, 10, -1, "The user shoots powerful lasers using the power of a prism. The user can't move on the next turn.", -1, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new AttackMove(Moves.SPECTRAL_THIEF, "Spectral Thief (N)", Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, "The user hides in the target's shadow, steals the target's stat boosts, and then attacks.", -1, 0, 7),
new AttackMove(Moves.SUNSTEEL_STRIKE, "Sunsteel Strike (N)", Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, "The user slams into the target with the force of a meteor. This move can be used on the target regardless of its Abilities.", -1, 0, 7),
new AttackMove(Moves.MOONGEIST_BEAM, "Moongeist Beam (N)", Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, "The user emits a sinister ray to attack the target. This move can be used on the target regardless of its Abilities.", -1, 0, 7),
@ -4702,9 +4750,9 @@ export function initMoves() {
.attr(ProtectAttr),
new AttackMove(Moves.FALSE_SURRENDER, "False Surrender", Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, "The user pretends to bow its head, but then it stabs the target with its disheveled hair. This attack never misses.", -1, 0, 8),
new AttackMove(Moves.METEOR_ASSAULT, "Meteor Assault", Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, "The user attacks wildly with its thick leek. The user can't move on the next turn, because the force of this move makes it stagger.", -1, 0, 8)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new AttackMove(Moves.ETERNABEAM, "Eternabeam", Type.DRAGON, MoveCategory.SPECIAL, 160, 90, 5, -1, "This is Eternatus's most powerful attack in its original form. The user can't move on the next turn.", -1, 0, 8)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
.attr(RechargeAttr),
new AttackMove(Moves.STEEL_BEAM, "Steel Beam (N)", Type.STEEL, MoveCategory.SPECIAL, 140, 95, 5, -1, "The user fires a beam of steel that it collected from its entire body. This also damages the user.", -1, 0, 8),
new AttackMove(Moves.EXPANDING_FORCE, "Expanding Force (N)", Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, "The user attacks the target with its psychic power. This move's power goes up and damages all opposing Pokémon on Psychic Terrain.", -1, 0, 8),
new AttackMove(Moves.STEEL_ROLLER, "Steel Roller (N)", Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, "The user attacks while destroying the terrain. This move fails when the ground hasn't turned into a terrain.", -1, 0, 8),

View File

@ -3,6 +3,7 @@ import { ModifierTypeFunc, modifierTypes } from "../modifier/modifier-type";
import { EnemyPokemon } from "../pokemon";
import * as Utils from "../utils";
import { Moves } from "./move";
import { PokeballType } from "./pokeball";
import { pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions";
import PokemonSpecies, { PokemonSpeciesFilter, getPokemonSpecies } from "./pokemon-species";
import { Species } from "./species";
@ -630,10 +631,7 @@ function getGymLeaderPartyTemplate(scene: BattleScene) {
function getRandomPartyMemberFunc(speciesPool: Species[], postProcess?: (enemyPokemon: EnemyPokemon) => void): PartyMemberFunc {
return (scene: BattleScene, level: integer) => {
const species = getPokemonSpecies(Phaser.Math.RND.pick(speciesPool)).getSpeciesForLevel(level, true, true, scene.currentBattle.trainer.config.isBoss);
const ret = new EnemyPokemon(scene, getPokemonSpecies(species), level, true);
if (postProcess)
postProcess(ret);
return ret;
return scene.addEnemyPokemon(getPokemonSpecies(species), level, true, undefined, undefined, postProcess);
};
}
@ -641,9 +639,7 @@ function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilt
const originalSpeciesFilter = speciesFilter;
speciesFilter = (species: PokemonSpecies) => allowLegendaries || (!species.legendary && !species.pseudoLegendary && !species.mythical) && originalSpeciesFilter(species);
return (scene: BattleScene, level: integer) => {
const ret = new EnemyPokemon(scene, getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getSpeciesForLevel(level, true, true, scene.currentBattle.trainer.config.isBoss)), level, true);
if (postProcess)
postProcess(ret);
const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getSpeciesForLevel(level, true, true, scene.currentBattle.trainer.config.isBoss)), level, true, undefined, undefined, postProcess);
return ret;
};
}
@ -1111,11 +1107,18 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT ]))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540)
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ])),
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ], p => {
p.setBoss();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm('final').setBattleBgm('battle_rival_3').setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT ]))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540)
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ], p => p.formIndex = 1)),
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ], p => {
p.setBoss();
p.pokeball = PokeballType.MASTER_BALL;
p.formIndex = 1;
})),
}

View File

@ -350,7 +350,7 @@ export class EggHatchPhase extends BattlePhase {
if (speciesOverride) {
this.scene.executeWithSeedOffset(() => {
const pokemonSpecies = getPokemonSpecies(speciesOverride);
ret = new PlayerPokemon(this.scene, pokemonSpecies, 5, undefined, undefined, undefined, false);
ret = this.scene.addPlayerPokemon(pokemonSpecies, 5, undefined, undefined, undefined, false);
}, this.egg.id, EGG_SEED.toString());
} else {
let minStarterValue: integer;
@ -422,7 +422,7 @@ export class EggHatchPhase extends BattlePhase {
const pokemonSpecies = getPokemonSpecies(species);
ret = new PlayerPokemon(this.scene, pokemonSpecies, 5, undefined, undefined, undefined, false);
ret = this.scene.addPlayerPokemon(pokemonSpecies, 5, undefined, undefined, undefined, false);
}, this.egg.id, EGG_SEED.toString());
}

View File

@ -62,6 +62,7 @@ Phaser.GameObjects.Sprite.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Image.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative;
document.fonts.load('16px emerald').then(() => document.fonts.load('10px pkmnems'));

View File

@ -14,7 +14,7 @@ import { initMoveAnim, loadMoveAnimAssets } from './data/battle-anims';
import { Status, StatusEffect } from './data/status-effect';
import { reverseCompatibleTms, tmSpecies } from './data/tms';
import { pokemonEvolutions, pokemonPrevolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './data/pokemon-evolutions';
import { DamagePhase, FaintPhase, SwitchSummonPhase } from './battle-phases';
import { DamagePhase, FaintPhase, StatChangePhase, SwitchSummonPhase } from './battle-phases';
import { BattleStat } from './data/battle-stat';
import { BattlerTag, BattlerTagLapseType, BattlerTagType, EncoreTag, TypeBoostTag, getBattlerTag } from './data/battler-tag';
import { Species } from './data/species';
@ -104,9 +104,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const randAbilityIndex = Utils.randSeedInt(2);
this.species = species;
this.battleInfo = this.isPlayer()
? new PlayerBattleInfo(scene)
: new EnemyBattleInfo(scene);
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level;
this.abilityIndex = abilityIndex !== undefined
@ -190,12 +187,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.shiny = false;
this.calculateStats();
}
init(): void {
this.fieldPosition = FieldPosition.CENTER;
scene.fieldUI.addAt(this.battleInfo, 0);
this.battleInfo.initInfo(this);
this.initBattleInfo();
this.scene.fieldUI.addAt(this.battleInfo, 0);
const getSprite = (hasShadow?: boolean) => {
const ret = this.scene.addFieldSprite(0, 0, `pkmn__${this.isPlayer() ? 'back__' : ''}sub`);
@ -216,6 +215,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.initShinySparkle();
}
abstract initBattleInfo(): void;
isOnField(): boolean {
if (!this.scene)
return false;
@ -570,6 +571,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.shiny || (this.fusionSpecies && this.fusionShiny);
}
abstract isBoss(): boolean;
getMoveset(ignoreOverride?: boolean): PokemonMove[] {
const ret = !ignoreOverride && this.summonData?.moveset
? this.summonData.moveset
@ -852,18 +855,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return move?.isUsable(this, ignorePp);
}
showInfo() {
showInfo(): void {
if (!this.battleInfo.visible) {
const otherBattleInfo = this.scene.fieldUI.getAll().slice(0, 4).filter(ui => ui instanceof BattleInfo && ((ui as BattleInfo) instanceof PlayerBattleInfo) === this.isPlayer()).find(() => true);
if (!otherBattleInfo || !this.getFieldIndex())
this.scene.fieldUI.sendToBack(this.battleInfo);
else
this.scene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo);
this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : -150));
this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198));
this.battleInfo.setVisible(true);
this.scene.tweens.add({
targets: this.battleInfo,
x: this.isPlayer() ? '-=150' : '+=150',
x: this.isPlayer() ? '-=150' : `+=${!this.isBoss() ? 150 : 246}`,
duration: 1000,
ease: 'Sine.easeOut'
});
@ -875,12 +878,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.battleInfo.visible) {
this.scene.tweens.add({
targets: this.battleInfo,
x: this.isPlayer() ? '+=150' : '-=150',
x: this.isPlayer() ? '+=150' : `-=${!this.isBoss() ? 150 : 198}`,
duration: 500,
ease: 'Sine.easeIn',
onComplete: () => {
this.battleInfo.setVisible(false);
this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : -150));
this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198));
resolve();
}
});
@ -1039,8 +1042,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (isCritical)
this.scene.queueMessage('A critical hit!');
this.scene.setPhaseQueueSplice();
damage.value = Math.min(damage.value, this.hp);
this.damage(damage.value);
damage.value = this.damage(damage.value);
if (source.isPlayer())
this.scene.validateAchvs(DamageAchv, damage);
source.turnData.damageDealt += damage.value;
@ -1083,9 +1085,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return result;
}
damage(damage: integer, preventEndure?: boolean): void {
damage(damage: integer, ignoreSegments: boolean = false, preventEndure: boolean = false): integer {
if (this.isFainted())
return;
return 0;
if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) {
const surviveDamage = new Utils.BooleanHolder(false);
@ -1094,19 +1096,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
damage = this.hp - 1;
}
this.hp = Math.max(this.hp - damage, 0);
damage = Math.min(damage, this.hp);
this.hp = this.hp - damage;
if (this.isFainted()) {
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), preventEndure));
this.resetSummonData();
}
return damage;
}
heal(amount: integer): void {
this.hp = Math.min(this.hp + amount, this.getMaxHp());
heal(amount: integer): integer {
const healAmount = Math.min(amount, this.getMaxHp() - this.hp);
this.hp += healAmount;
return healAmount;
}
isBossImmune(): boolean {
return this.species.speciesId === Species.ETERNATUS && this.formIndex === 1;
return this.isBoss();
}
addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean {
@ -1742,12 +1750,17 @@ export default interface Pokemon {
export class PlayerPokemon extends Pokemon {
public compatibleTms: Moves[];
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) {
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender: Gender, shiny: boolean, ivs: integer[], nature: Nature, dataSource: Pokemon | PokemonData) {
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, ivs, nature, dataSource);
this.generateCompatibleTms();
}
initBattleInfo(): void {
this.battleInfo = new PlayerBattleInfo(this.scene);
this.battleInfo.initInfo(this);
}
isPlayer(): boolean {
return true;
}
@ -1756,6 +1769,10 @@ export class PlayerPokemon extends Pokemon {
return true;
}
isBoss(): boolean {
return false;
}
getFieldIndex(): integer {
return this.scene.getPlayerField().indexOf(this);
}
@ -1808,7 +1825,7 @@ export class PlayerPokemon extends Pokemon {
return new Promise(resolve => {
const species = getPokemonSpecies(evolution.speciesId);
const formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0);
const ret = new PlayerPokemon(this.scene, species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.ivs, this.nature, this);
const ret = this.scene.addPlayerPokemon(species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.ivs, this.nature, this);
ret.loadAssets().then(() => resolve(ret));
});
}
@ -1839,7 +1856,7 @@ export class PlayerPokemon extends Pokemon {
if (this.species.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) {
const newEvolution = pokemonEvolutions[this.species.speciesId][1];
if (newEvolution.condition.predicate(this)) {
const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature);
const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature);
this.scene.getParty().push(newPokemon);
newPokemon.evolve(newEvolution);
const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
@ -1911,12 +1928,16 @@ export class PlayerPokemon extends Pokemon {
export class EnemyPokemon extends Pokemon {
public trainer: boolean;
public aiType: AiType;
public bossSegments: integer;
public bossSegmentIndex: integer;
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainer: boolean, dataSource?: PokemonData) {
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainer: boolean, boss: boolean, dataSource: PokemonData) {
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex,
dataSource?.gender, dataSource ? dataSource.shiny : false, null, dataSource ? dataSource.nature : undefined, dataSource);
this.trainer = trainer;
if (boss)
this.setBoss();
if (!dataSource) {
this.trySetShiny();
@ -1934,6 +1955,22 @@ export class EnemyPokemon extends Pokemon {
this.aiType = AiType.SMART_RANDOM;
}
initBattleInfo(): void {
this.battleInfo = new EnemyBattleInfo(this.scene);
this.battleInfo.updateBossSegments(this);
this.battleInfo.initInfo(this);
}
setBoss(boss: boolean = true): void {
if (boss) {
this.bossSegments = this.scene.getEncounterBossSegments(this.scene.currentBattle.waveIndex, this.level, this.species, true);
this.bossSegmentIndex = this.bossSegments - 1;
} else {
this.bossSegments = 0;
this.bossSegmentIndex = 0;
}
}
generateAndPopulateMoveset(): void {
switch (true) {
case (this.species.speciesId === Species.SMEARGLE):
@ -2097,6 +2134,107 @@ export class EnemyPokemon extends Pokemon {
return this.trainer;
}
isBoss(): boolean {
return !!this.bossSegments;
}
getBossSegmentIndex(): integer {
const segments = (this as EnemyPokemon).bossSegments;
const segmentSize = this.getMaxHp() / segments;
for (let s = segments - 1; s > 0; s--) {
const hpThreshold = Math.round(segmentSize * s);
if (this.hp > hpThreshold) {
return s;
}
}
return 0;
}
damage(damage: integer, ignoreSegments: boolean = false, preventEndure: boolean = false): integer {
if (this.isFainted())
return 0;
let clearedSegment = false;
if (!ignoreSegments && this.isBoss()) {
const segmentSize = this.getMaxHp() / this.bossSegments;
for (let s = this.bossSegments - 1; s > 0; s--) {
const hpThreshold = Math.round(segmentSize * s);
if (this.hp > hpThreshold) {
if (this.hp - damage < hpThreshold) {
damage = this.hp - hpThreshold;
clearedSegment = true;
this.handleBossSegmentCleared(s);
}
break;
}
}
}
return super.damage(damage, ignoreSegments, preventEndure);
}
handleBossSegmentCleared(segmentIndex: integer): void {
while (segmentIndex - 1 < this.bossSegmentIndex) {
let boostedStat = BattleStat.RAND;
const battleStats = Utils.getEnumValues(BattleStat).slice(0, -2);
const statWeights = new Array().fill(battleStats.length).filter((bs: BattleStat) => this.summonData.battleStats[bs] < 6).map((bs: BattleStat) => this.getStat(bs + 1));
const statThresholds: integer[] = [];
let totalWeight = 0;
for (let bs of battleStats) {
totalWeight += statWeights[bs];
statThresholds.push(totalWeight);
}
const randInt = Utils.randSeedInt(totalWeight);
for (let bs of battleStats) {
if (randInt < statThresholds[bs]) {
boostedStat = bs;
break;
}
}
let statLevels = 1;
switch (segmentIndex) {
case 1:
if (this.bossSegments >= 3)
statLevels++;
break;
case 2:
if (this.bossSegments >= 5)
statLevels++;
break;
}
this.scene.unshiftPhase(new StatChangePhase(this.scene, this.getBattlerIndex(), true, [ boostedStat ], statLevels));
this.bossSegmentIndex--;
}
}
heal(amount: integer): integer {
if (this.isBoss()) {
let amountRatio = amount / this.getMaxHp();
let segmentBypassCount = Math.floor(amountRatio / (1 / this.bossSegments));
const segmentSize = this.getMaxHp() / this.bossSegments;
for (let s = 1; s < this.bossSegments; s++) {
const hpThreshold = Math.round(segmentSize * s);
if (this.hp <= hpThreshold) {
const healAmount = Math.min(amount, this.getMaxHp() - this.hp, Math.round(hpThreshold + (segmentSize * segmentBypassCount) - this.hp));
this.hp += healAmount;
return healAmount;
}
}
}
return super.heal(amount);
}
getFieldIndex(): integer {
return this.scene.getEnemyField().indexOf(this);
}
@ -2113,7 +2251,7 @@ export class EnemyPokemon extends Pokemon {
this.pokeball = pokeballType;
this.metLevel = this.level;
this.metBiome = this.scene.arena.biomeType;
const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this);
const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this);
party.push(newPokemon);
ret = newPokemon;
}

View File

@ -39,6 +39,8 @@ export default class PokemonData {
public fusionShiny: boolean;
public fusionGender: Gender;
public boss: boolean;
public summonData: PokemonSummonData;
constructor(source: Pokemon | any) {
@ -70,6 +72,8 @@ export default class PokemonData {
this.fusionShiny = source.fusionShiny;
this.fusionGender = source.fusionGender;
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
if (sourcePokemon) {
this.moveset = sourcePokemon.moveset;
this.status = sourcePokemon.status;
@ -95,7 +99,7 @@ export default class PokemonData {
toPokemon(scene: BattleScene, battleType?: BattleType): Pokemon {
const species = getPokemonSpecies(this.species);
if (this.player)
return new PlayerPokemon(scene, species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this);
return new EnemyPokemon(scene, species, this.level, battleType === BattleType.TRAINER, this);
return scene.addPlayerPokemon(species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this);
return scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER, this.boss, this);
}
}

View File

@ -137,7 +137,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
? getPokemonSpecies(battle.enemyParty[offset].species.getSpeciesForLevel(level, false, true, this.config.isBoss))
: this.genNewPartyMemberSpecies(level);
ret = new EnemyPokemon(this.scene, species, level, true);
ret = this.scene.addEnemyPokemon(species, level, true);
}, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8));
return ret;

View File

@ -1,4 +1,4 @@
import { default as Pokemon } from '../pokemon';
import { EnemyPokemon, default as Pokemon } from '../pokemon';
import { getLevelTotalExp, getLevelRelExp } from '../data/exp';
import * as Utils from '../utils';
import { addTextObject, TextStyle } from './text';
@ -9,6 +9,8 @@ import BattleScene from '../battle-scene';
export default class BattleInfo extends Phaser.GameObjects.Container {
private player: boolean;
private mini: boolean;
private boss: boolean;
private bossSegments: integer;
private offset: boolean;
private lastName: string;
private lastStatus: StatusEffect;
@ -29,6 +31,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private statusIndicator: Phaser.GameObjects.Sprite;
private levelContainer: Phaser.GameObjects.Container;
private hpBar: Phaser.GameObjects.Image;
private hpBarSegmentDividers: Phaser.GameObjects.Rectangle[];
private levelNumbersContainer: Phaser.GameObjects.Container;
private hpNumbersContainer: Phaser.GameObjects.Container;
private expBar: Phaser.GameObjects.Image;
@ -37,6 +40,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
super(scene, x, y);
this.player = player;
this.mini = !player;
this.boss = false;
this.offset = false;
this.lastName = null;
this.lastStatus = StatusEffect.NONE;
@ -100,6 +104,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.hpBar.setOrigin(0);
this.add(this.hpBar);
this.hpBarSegmentDividers = [];
this.levelNumbersContainer = this.scene.add.container(9.5, 0);
this.levelContainer.add(this.levelNumbersContainer);
@ -137,6 +143,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const dexAttr = pokemon.getDexAttr();
if ((dexEntry.caughtAttr & dexAttr) < dexAttr)
this.ownedIcon.setTint(0x808080);
if (this.boss)
this.updateBossSegmentDividers(pokemon.getMaxHp());
}
this.hpBar.setScale(pokemon.getHpRatio(), 1);
@ -158,7 +167,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
}
getTextureName(): string {
return `pbinfo_${this.player ? 'player' : 'enemy'}${this.mini ? '_mini' : ''}`;
return `pbinfo_${this.player ? 'player' : 'enemy'}${!this.player && this.boss ? '_boss' : this.mini ? '_mini' : ''}`;
}
setMini(mini: boolean): void {
@ -181,6 +190,40 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const toggledElements = [ this.hpNumbersContainer, this.expBar ];
toggledElements.forEach(el => el.setVisible(!mini));
}
updateBossSegments(pokemon: EnemyPokemon): void {
const boss = !!pokemon.bossSegments;
if (boss !== this.boss) {
this.boss = boss;
[ this.nameText, this.genderText, this.splicedIcon, this.ownedIcon, this.statusIndicator, this.levelContainer ].map(e => e.x += 48 * (boss ? -1 : 1));
this.hpBar.x += 38 * (boss ? -1 : 1);
this.hpBar.y += 2 * (this.boss ? -1 : 1);
this.hpBar.setTexture(`overlay_hp${boss ? '_boss' : ''}`);
this.box.setTexture(this.getTextureName());
}
this.bossSegments = boss ? pokemon.bossSegments : 0;
this.updateBossSegmentDividers(pokemon.hp);
}
updateBossSegmentDividers(hp: number): void {
while (this.hpBarSegmentDividers.length)
this.hpBarSegmentDividers.pop().destroy();
if (this.boss && this.bossSegments > 1) {
for (let s = 1; s < this.bossSegments; s++) {
const dividerX = (Math.round((hp / this.bossSegments) * s) / hp) * this.hpBar.width;
const divider = this.scene.add.rectangle(0, 0, 1, this.hpBar.height, 0xffffff);
divider.setOrigin(0.5, 0);
this.add(divider);
divider.setPositionRelative(this.hpBar, dividerX, 0);
this.hpBarSegmentDividers.push(divider);
}
}
}
setOffset(offset: boolean): void {
if (this.offset === offset)

View File

@ -109,7 +109,7 @@ function getTextStyleOptions(style: TextStyle, extraStyleOptions?: Phaser.Types.
}
export function getBBCodeFrag(content: string, textStyle: TextStyle): string {
return `[color=${getTextColor(textStyle)}][shadow=${getTextColor(textStyle, true)}]${content}[/shadow][/color]`;
return `[color=${getTextColor(textStyle)}][shadow=${getTextColor(textStyle, true)}]${content}`;
}
export function getTextColor(textStyle: TextStyle, shadow?: boolean): string {

View File

@ -63,7 +63,7 @@ export function randInt(range: integer, min: integer = 0): integer {
}
export function randSeedInt(range: integer, min: integer = 0): integer {
if (range === 1)
if (range <= 1)
return min;
return Phaser.Math.RND.integerInRange(min, (range - 1) + min);
}