Add boss health bars

pull/14/head
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 { 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); && (this.biomeType !== Biome.END || this.scene.gameMode === GameMode.CLASSIC || waveIndex % 250 === 0);
const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64); const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64);
let tier = !isBoss let tier = !isBoss

View File

@ -1,7 +1,7 @@
import BattleScene, { bypassLogin, startingLevel, startingWave } from "./battle-scene"; import BattleScene, { bypassLogin, startingLevel, startingWave } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./pokemon"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./pokemon";
import * as Utils from './utils'; 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 { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat"; import { Stat } from "./data/pokemon-stat";
@ -39,7 +39,6 @@ import { vouchers } from "./system/voucher";
import { loggedInUser, updateUserInfo } from "./account"; import { loggedInUser, updateUserInfo } from "./account";
import { GameDataType } from "./system/game-data"; import { GameDataType } from "./system/game-data";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./anims"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./anims";
import { Nature } from "./data/nature";
export class LoginPhase extends BattlePhase { export class LoginPhase extends BattlePhase {
private showText: boolean; private showText: boolean;
@ -229,7 +228,7 @@ export class SelectStarterPhase extends BattlePhase {
? !starterProps.female ? Gender.MALE : Gender.FEMALE ? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS; : Gender.GENDERLESS;
const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); 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) if (starter.pokerus)
starterPokemon.pokerus = true; starterPokemon.pokerus = true;
if (this.scene.gameMode === GameMode.SPLICED_ENDLESS) if (this.scene.gameMode === GameMode.SPLICED_ENDLESS)
@ -372,7 +371,7 @@ export class EncounterPhase extends BattlePhase {
battle.enemyParty[e] = battle.trainer.genPartyMember(e); battle.enemyParty[e] = battle.trainer.genPartyMember(e);
else { else {
const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); 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 => { this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => {
applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]);
}); });
@ -386,8 +385,10 @@ export class EncounterPhase extends BattlePhase {
this.scene.gameData.setPokemonSeen(enemyPokemon); 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.formIndex = 1;
enemyPokemon.setBoss();
}
loadEnemyAssets.push(enemyPokemon.loadAssets()); loadEnemyAssets.push(enemyPokemon.loadAssets());
@ -1246,15 +1247,21 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true); }, null, true);
} else if (cursor < 4) { } else if (cursor < 4) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true));
if (targets.length > 1) if (targetPokemon.isBoss() && targetPokemon.getBossSegmentIndex()) {
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
else { 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; this.scene.currentBattle.turnCommands[this.fieldIndex].targets = targets;
if (this.fieldIndex) if (this.fieldIndex)
this.scene.currentBattle.turnCommands[this.fieldIndex - 1].skip = true; this.scene.currentBattle.turnCommands[this.fieldIndex - 1].skip = true;
success = true;
} }
success = true;
} }
} }
break; 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) if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER)
return; return;
} }
if (this.pokemon.getTag(BattlerTagType.RECHARGING))
return;
this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500); this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500);
} }
@ -2951,7 +2961,7 @@ export class PokemonHealPhase extends CommonAnimPhase {
if (!this.revive) if (!this.revive)
this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
const healAmount = new Utils.NumberHolder(this.hpHealed * hpRestoreMultiplier.value); const healAmount = new Utils.NumberHolder(this.hpHealed * hpRestoreMultiplier.value);
pokemon.heal(healAmount.value); healAmount.value = pokemon.heal(healAmount.value);
this.scene.validateAchvs(HealAchv, healAmount); this.scene.validateAchvs(HealAchv, healAmount);
pokemon.updateInfo().then(() => super.end()); pokemon.updateInfo().then(() => super.end());
} else if (this.showFullHpMessage) } else if (this.showFullHpMessage)
@ -2979,7 +2989,7 @@ export class AttemptCapturePhase extends PokemonPhase {
start() { start() {
super.start(); super.start();
const pokemon = this.getPokemon(); const pokemon = this.getPokemon() as EnemyPokemon;
if (!pokemon?.hp) if (!pokemon?.hp)
return this.end(); return this.end();
@ -2988,7 +2998,11 @@ export class AttemptCapturePhase extends PokemonPhase {
this.originalY = pokemon.y; 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 _2h = 2 * pokemon.hp;
const catchRate = pokemon.species.catchRate; const catchRate = pokemon.species.catchRate;
const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType); const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType);

View File

@ -41,6 +41,8 @@ import { Voucher, vouchers } from './system/voucher';
import { Gender } from './data/gender'; import { Gender } from './data/gender';
import UIPlugin from 'phaser3-rex-plugins/templates/ui/ui-plugin'; import UIPlugin from 'phaser3-rex-plugins/templates/ui/ui-plugin';
import { WindowVariant, getWindowVariantSuffix } from './ui/window'; import { WindowVariant, getWindowVariantSuffix } from './ui/window';
import PokemonData from './system/pokemon-data';
import { Nature } from './data/nature';
const enableAuto = true; const enableAuto = true;
const quickStart = false; const quickStart = false;
@ -207,10 +209,12 @@ export default class BattleScene extends Phaser.Scene {
this.loadImage('pbinfo_player', 'ui'); this.loadImage('pbinfo_player', 'ui');
this.loadImage('pbinfo_player_mini', 'ui'); this.loadImage('pbinfo_player_mini', 'ui');
this.loadImage('pbinfo_enemy_mini', 'ui'); this.loadImage('pbinfo_enemy_mini', 'ui');
this.loadImage('pbinfo_enemy_boss', 'ui');
this.loadImage('overlay_lv', 'ui'); this.loadImage('overlay_lv', 'ui');
this.loadAtlas('numbers', 'ui'); this.loadAtlas('numbers', 'ui');
this.loadAtlas('numbers_red', 'ui'); this.loadAtlas('numbers_red', 'ui');
this.loadAtlas('overlay_hp', 'ui'); this.loadAtlas('overlay_hp', 'ui');
this.loadAtlas('overlay_hp_boss', 'ui');
this.loadImage('overlay_exp', 'ui'); this.loadImage('overlay_exp', 'ui');
this.loadImage('icon_owned', 'ui'); this.loadImage('icon_owned', 'ui');
this.loadImage('ability_bar', 'ui'); this.loadImage('ability_bar', 'ui');
@ -526,7 +530,7 @@ export default class BattleScene extends Phaser.Scene {
for (let s = 0; s < 3; s++) { for (let s = 0; s < 3; s++) {
const playerSpecies = this.randomSpecies(startingWave, startingLevel); 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); playerPokemon.setVisible(false);
this.party.push(playerPokemon); this.party.push(playerPokemon);
@ -637,6 +641,22 @@ export default class BattleScene extends Phaser.Scene {
return findInParty(this.getParty()) || findInParty(this.getEnemyParty()); 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 { reset(clearScene?: boolean): void {
this.seed = Utils.randomString(16); this.seed = Utils.randomString(16);
console.log('Seed:', this.seed); console.log('Seed:', this.seed);
@ -715,7 +735,7 @@ export default class BattleScene extends Phaser.Scene {
if (this.gameMode !== GameMode.CLASSIC) if (this.gameMode !== GameMode.CLASSIC)
newBattleType = BattleType.WILD; newBattleType = BattleType.WILD;
else if (battleType === undefined) { else if (battleType === undefined) {
if ((newWaveIndex % 30) === 20) if ((newWaveIndex % 30) === 20 && newWaveIndex !== 200)
newBattleType = BattleType.TRAINER; newBattleType = BattleType.TRAINER;
else if (newWaveIndex % 10 !== 1 && newWaveIndex % 10) { else if (newWaveIndex % 10 !== 1 && newWaveIndex % 10) {
const trainerChance = this.arena.getTrainerChance(); const trainerChance = this.arena.getTrainerChance();
@ -860,6 +880,33 @@ export default class BattleScene extends Phaser.Scene {
return ret; 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 { trySpreadPokerus(): void {
const party = this.getParty(); const party = this.getParty();
const infectedIndexes: integer[] = []; 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}.`)); pokemon.scene.queueMessage(getPokemonMessage(pokemon, `\'s perish count fell to ${this.turnCount}.`));
else { else {
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.ONE_HIT_KO)); 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; return ret;

View File

@ -58,8 +58,8 @@ export enum MoveFlags {
WIND_MOVE = 8192 WIND_MOVE = 8192
} }
type MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveCondition = (user: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
export default class Move { export default class Move {
public id: Moves; public id: Moves;
@ -111,18 +111,24 @@ export default class Move {
attr<T extends new (...args: any[]) => MoveAttr>(AttrType: T, ...args: ConstructorParameters<T>): this { attr<T extends new (...args: any[]) => MoveAttr>(AttrType: T, ...args: ConstructorParameters<T>): this {
const attr = new AttrType(...args); const attr = new AttrType(...args);
this.attrs.push(attr); this.attrs.push(attr);
const attrCondition = attr.getCondition(); let attrCondition = attr.getCondition();
if (attrCondition) if (attrCondition) {
if (typeof attrCondition === 'function')
attrCondition = new MoveCondition(attrCondition);
this.conditions.push(attrCondition); this.conditions.push(attrCondition);
}
return this; return this;
} }
addAttr(attr: MoveAttr): this { addAttr(attr: MoveAttr): this {
this.attrs.push(attr); this.attrs.push(attr);
const attrCondition = attr.getCondition(); let attrCondition = attr.getCondition();
if (attrCondition) if (attrCondition) {
if (typeof attrCondition === 'function')
attrCondition = new MoveCondition(attrCondition);
this.conditions.push(attrCondition); this.conditions.push(attrCondition);
}
return this; return this;
} }
@ -136,7 +142,9 @@ export default class Move {
return !!(this.flags & flag); 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); this.conditions.push(condition);
return this; return this;
@ -232,7 +240,7 @@ export default class Move {
applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean { applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean {
for (let condition of this.conditions) { for (let condition of this.conditions) {
if (!condition(user, target, move)) if (!condition.apply(user, target, move))
return false; return false;
} }
@ -245,6 +253,9 @@ export default class Move {
for (let attr of this.attrs) for (let attr of this.attrs)
score += attr.getUserBenefitScore(user, target, move); score += attr.getUserBenefitScore(user, target, move);
for (let condition of this.conditions)
score += condition.getUserBenefitScore(user, target, move);
return score; return score;
} }
@ -252,7 +263,7 @@ export default class Move {
let score = 0; let score = 0;
for (let attr of this.attrs) 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; return score;
} }
@ -1237,11 +1248,17 @@ export enum Moves {
}; };
export abstract class MoveAttr { 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> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
return true; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveCondition | MoveConditionFunc {
return null; return null;
} }
@ -1261,13 +1278,10 @@ export enum MoveEffectTrigger {
} }
export class MoveEffectAttr extends MoveAttr { export class MoveEffectAttr extends MoveAttr {
public selfTarget: boolean;
public trigger: MoveEffectTrigger; public trigger: MoveEffectTrigger;
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger) { constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger) {
super(); super(selfTarget);
this.selfTarget = !!selfTarget;
this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY; this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY;
} }
@ -1360,7 +1374,7 @@ export class MatchHpAttr extends FixedDamageAttr {
return true; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return (user, target, move) => user.hp <= target.hp; return (user, target, move) => user.hp <= target.hp;
} }
@ -1388,7 +1402,7 @@ export class CounterDamageAttr extends FixedDamageAttr {
return true; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return (user, target, move) => !!user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).length; 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.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!')); user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
user.damage(recoilDamage); user.damage(recoilDamage, true);
return true; return true;
} }
@ -1460,7 +1474,7 @@ export class SacrificialAttr extends MoveEffectAttr {
return false; return false;
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER)); user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
user.damage(user.getMaxHp()); user.damage(user.getMaxHp(), true, true);
return true; return true;
} }
@ -1500,7 +1514,7 @@ export class HealAttr extends MoveEffectAttr {
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { 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); 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()); 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; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return (user, target, move) => user.level >= target.level; return (user, target, move) => user.level >= target.level;
} }
} }
@ -1918,9 +1932,9 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
export class StatChangeAttr extends MoveEffectAttr { export class StatChangeAttr extends MoveEffectAttr {
public stats: BattleStat[]; public stats: BattleStat[];
public levels: integer; 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); super(selfTarget, MoveEffectTrigger.HIT);
this.stats = typeof(stats) === 'number' this.stats = typeof(stats) === 'number'
? [ stats as BattleStat ] ? [ stats as BattleStat ]
@ -1980,13 +1994,13 @@ export class HpSplitAttr extends MoveEffectAttr {
if (user.hp < hpValue) if (user.hp < hpValue)
user.heal(hpValue - user.hp); user.heal(hpValue - user.hp);
else if (user.hp > hpValue) else if (user.hp > hpValue)
user.damage(user.hp - hpValue); user.damage(user.hp - hpValue, true);
infoUpdates.push(user.updateInfo()); infoUpdates.push(user.updateInfo());
if (target.hp < hpValue) if (target.hp < hpValue)
target.heal(hpValue - target.hp); target.heal(hpValue - target.hp);
else if (target.hp > hpValue) else if (target.hp > hpValue)
target.damage(target.hp - hpValue); target.damage(target.hp - hpValue, true);
infoUpdates.push(target.updateInfo()); infoUpdates.push(target.updateInfo());
return Promise.all(infoUpdates).then(() => resolve(true)); return Promise.all(infoUpdates).then(() => resolve(true));
@ -2232,9 +2246,9 @@ export class OneHitKOAccuracyAttr extends MoveAttr {
} }
export class MissEffectAttr extends MoveAttr { export class MissEffectAttr extends MoveAttr {
private missEffectFunc: UserMoveCondition; private missEffectFunc: UserMoveConditionFunc;
constructor(missEffectFunc: UserMoveCondition) { constructor(missEffectFunc: UserMoveConditionFunc) {
super(); super();
this.missEffectFunc = missEffectFunc; this.missEffectFunc = missEffectFunc;
@ -2280,7 +2294,7 @@ export class DisableMoveAttr extends MoveEffectAttr {
return false; return false;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
if (target.summonData.disabledMove) if (target.summonData.disabledMove)
return false; 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) while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id)
user.getMoveQueue().shift(); user.getMoveQueue().shift();
user.lapseTag(BattlerTagType.FRENZY); user.lapseTag(BattlerTagType.FRENZY);
@ -2375,7 +2389,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
return move.chance; return move.chance;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return this.failOnOverlap return this.failOnOverlap
? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType) ? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType)
: null; : null;
@ -2383,6 +2397,8 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
switch (this.tagType) { switch (this.tagType) {
case BattlerTagType.RECHARGING:
return -16;
case BattlerTagType.FLINCHED: case BattlerTagType.FLINCHED:
return -5; return -5;
case BattlerTagType.CONFUSED: case BattlerTagType.CONFUSED:
@ -2394,7 +2410,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.NIGHTMARE: case BattlerTagType.NIGHTMARE:
return -5; return -5;
case BattlerTagType.FRENZY: case BattlerTagType.FRENZY:
return -2; return -3;
case BattlerTagType.ENCORE: case BattlerTagType.ENCORE:
return -2; return -2;
case BattlerTagType.INGRAIN: case BattlerTagType.INGRAIN:
@ -2415,7 +2431,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.PROTECTED: case BattlerTagType.PROTECTED:
return 5; return 5;
case BattlerTagType.PERISH_SONG: case BattlerTagType.PERISH_SONG:
return -8; return -16;
case BattlerTagType.FLYING: case BattlerTagType.FLYING:
return 5; return 5;
case BattlerTagType.CRIT_BOOST: 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 { export class TrapAttr extends AddBattlerTagAttr {
constructor(tagType: BattlerTagType) { constructor(tagType: BattlerTagType) {
super(tagType, false, false, 3, 6); super(tagType, false, false, 3, 6);
@ -2471,7 +2493,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
super(BattlerTagType.PROTECTED, true); super(BattlerTagType.PROTECTED, true);
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return ((user, target, move): boolean => { return ((user, target, move): boolean => {
let timesUsed = 0; let timesUsed = 0;
const moveHistory = user.getLastXMoves(); const moveHistory = user.getLastXMoves();
@ -2514,7 +2536,7 @@ export class FaintCountdownAttr extends AddBattlerTagAttr {
return true; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return (user, target, move) => super.getCondition()(user, target, move) && !target.isBossImmune(); 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 { export class AddArenaTrapTagAttr extends AddArenaTagAttr {
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
if (move.category !== MoveCategory.STATUS || !user.scene.arena.getTag(this.tagType)) if (move.category !== MoveCategory.STATUS || !user.scene.arena.getTag(this.tagType))
return true; 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); return (user, target, move) => move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move);
} }
getSwitchOutCondition(): MoveCondition { getSwitchOutCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const switchOutTarget = (this.user ? user : target); const switchOutTarget = (this.user ? user : target);
const player = switchOutTarget instanceof PlayerPokemon; 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; const copiableMove = user.scene.currentBattle.lastMove;
if (!copiableMove) if (!copiableMove)
@ -2770,13 +2792,13 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
return true; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return lastMoveCopiableCondition; return lastMoveCopiableCondition;
} }
} }
// TODO: Review this // TODO: Review this
const targetMoveCopiableCondition: MoveCondition = (user, target, move) => { const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
const targetMoves = target.getMoveHistory().filter(m => !m.virtual); const targetMoves = target.getMoveHistory().filter(m => !m.virtual);
if (!targetMoves.length) if (!targetMoves.length)
return false; return false;
@ -2815,7 +2837,7 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr {
return true; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return targetMoveCopiableCondition; return targetMoveCopiableCondition;
} }
} }
@ -2847,7 +2869,7 @@ export class SketchAttr extends MoveEffectAttr {
return true; return true;
} }
getCondition(): MoveCondition { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
if (!targetMoveCopiableCondition(user, target, move)) if (!targetMoveCopiableCondition(user, target, move))
return false; 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; export type MoveAttrFilter = (attr: MoveAttr) => boolean;
@ -2916,6 +2938,32 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
return applyMoveAttrsInternal(attrFilter, user, target, move, args); 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 = { export type MoveTargetSet = {
targets: BattlerIndex[]; targets: BattlerIndex[];
multiple: boolean; 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) 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), .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) 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.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.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) 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), .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) 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) .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) 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() .ignoresVirtual()
.soundBased() .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) 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), .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) 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) 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) 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) .attr(StatChangeAttr, BattleStat.ATK, 1, true)
.punchingMove(), .punchingMove(),
@ -3791,7 +3839,7 @@ export function initMoves() {
.target(MoveTarget.USER_AND_ALLIES), .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.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) 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) 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), .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) 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), .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 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) 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) 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), .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) 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), .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.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) 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) .makesContact(false)
.ballBombMove(), .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) 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) 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), .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) 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) 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), .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) 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) 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), .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) 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 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.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), 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) 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), .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) 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.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.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), 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), .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.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) 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) 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.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.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), 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 { EnemyPokemon } from "../pokemon";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { Moves } from "./move"; import { Moves } from "./move";
import { PokeballType } from "./pokeball";
import { pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions";
import PokemonSpecies, { PokemonSpeciesFilter, getPokemonSpecies } from "./pokemon-species"; import PokemonSpecies, { PokemonSpeciesFilter, getPokemonSpecies } from "./pokemon-species";
import { Species } from "./species"; import { Species } from "./species";
@ -630,10 +631,7 @@ function getGymLeaderPartyTemplate(scene: BattleScene) {
function getRandomPartyMemberFunc(speciesPool: Species[], postProcess?: (enemyPokemon: EnemyPokemon) => void): PartyMemberFunc { function getRandomPartyMemberFunc(speciesPool: Species[], postProcess?: (enemyPokemon: EnemyPokemon) => void): PartyMemberFunc {
return (scene: BattleScene, level: integer) => { return (scene: BattleScene, level: integer) => {
const species = getPokemonSpecies(Phaser.Math.RND.pick(speciesPool)).getSpeciesForLevel(level, true, true, scene.currentBattle.trainer.config.isBoss); 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); return scene.addEnemyPokemon(getPokemonSpecies(species), level, true, undefined, undefined, postProcess);
if (postProcess)
postProcess(ret);
return ret;
}; };
} }
@ -641,9 +639,7 @@ function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilt
const originalSpeciesFilter = speciesFilter; const originalSpeciesFilter = speciesFilter;
speciesFilter = (species: PokemonSpecies) => allowLegendaries || (!species.legendary && !species.pseudoLegendary && !species.mythical) && originalSpeciesFilter(species); speciesFilter = (species: PokemonSpecies) => allowLegendaries || (!species.legendary && !species.pseudoLegendary && !species.mythical) && originalSpeciesFilter(species);
return (scene: BattleScene, level: integer) => { 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); 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);
if (postProcess)
postProcess(ret);
return ret; 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(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)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540) .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) [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(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(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)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540) .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) { if (speciesOverride) {
this.scene.executeWithSeedOffset(() => { this.scene.executeWithSeedOffset(() => {
const pokemonSpecies = getPokemonSpecies(speciesOverride); 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()); }, this.egg.id, EGG_SEED.toString());
} else { } else {
let minStarterValue: integer; let minStarterValue: integer;
@ -422,7 +422,7 @@ export class EggHatchPhase extends BattlePhase {
const pokemonSpecies = getPokemonSpecies(species); 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()); }, 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.Image.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative; Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative;
Phaser.GameObjects.Text.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')); 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 { Status, StatusEffect } from './data/status-effect';
import { reverseCompatibleTms, tmSpecies } from './data/tms'; import { reverseCompatibleTms, tmSpecies } from './data/tms';
import { pokemonEvolutions, pokemonPrevolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './data/pokemon-evolutions'; 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 { BattleStat } from './data/battle-stat';
import { BattlerTag, BattlerTagLapseType, BattlerTagType, EncoreTag, TypeBoostTag, getBattlerTag } from './data/battler-tag'; import { BattlerTag, BattlerTagLapseType, BattlerTagType, EncoreTag, TypeBoostTag, getBattlerTag } from './data/battler-tag';
import { Species } from './data/species'; import { Species } from './data/species';
@ -104,9 +104,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const randAbilityIndex = Utils.randSeedInt(2); const randAbilityIndex = Utils.randSeedInt(2);
this.species = species; this.species = species;
this.battleInfo = this.isPlayer()
? new PlayerBattleInfo(scene)
: new EnemyBattleInfo(scene);
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL; this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level; this.level = level;
this.abilityIndex = abilityIndex !== undefined this.abilityIndex = abilityIndex !== undefined
@ -190,12 +187,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.shiny = false; this.shiny = false;
this.calculateStats(); this.calculateStats();
}
init(): void {
this.fieldPosition = FieldPosition.CENTER; this.fieldPosition = FieldPosition.CENTER;
scene.fieldUI.addAt(this.battleInfo, 0); this.initBattleInfo();
this.battleInfo.initInfo(this); this.scene.fieldUI.addAt(this.battleInfo, 0);
const getSprite = (hasShadow?: boolean) => { const getSprite = (hasShadow?: boolean) => {
const ret = this.scene.addFieldSprite(0, 0, `pkmn__${this.isPlayer() ? 'back__' : ''}sub`); 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(); this.initShinySparkle();
} }
abstract initBattleInfo(): void;
isOnField(): boolean { isOnField(): boolean {
if (!this.scene) if (!this.scene)
return false; return false;
@ -570,6 +571,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.shiny || (this.fusionSpecies && this.fusionShiny); return this.shiny || (this.fusionSpecies && this.fusionShiny);
} }
abstract isBoss(): boolean;
getMoveset(ignoreOverride?: boolean): PokemonMove[] { getMoveset(ignoreOverride?: boolean): PokemonMove[] {
const ret = !ignoreOverride && this.summonData?.moveset const ret = !ignoreOverride && this.summonData?.moveset
? this.summonData.moveset ? this.summonData.moveset
@ -852,18 +855,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return move?.isUsable(this, ignorePp); return move?.isUsable(this, ignorePp);
} }
showInfo() { showInfo(): void {
if (!this.battleInfo.visible) { 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); 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()) if (!otherBattleInfo || !this.getFieldIndex())
this.scene.fieldUI.sendToBack(this.battleInfo); this.scene.fieldUI.sendToBack(this.battleInfo);
else else
this.scene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo); 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.battleInfo.setVisible(true);
this.scene.tweens.add({ this.scene.tweens.add({
targets: this.battleInfo, targets: this.battleInfo,
x: this.isPlayer() ? '-=150' : '+=150', x: this.isPlayer() ? '-=150' : `+=${!this.isBoss() ? 150 : 246}`,
duration: 1000, duration: 1000,
ease: 'Sine.easeOut' ease: 'Sine.easeOut'
}); });
@ -875,12 +878,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.battleInfo.visible) { if (this.battleInfo.visible) {
this.scene.tweens.add({ this.scene.tweens.add({
targets: this.battleInfo, targets: this.battleInfo,
x: this.isPlayer() ? '+=150' : '-=150', x: this.isPlayer() ? '+=150' : `-=${!this.isBoss() ? 150 : 198}`,
duration: 500, duration: 500,
ease: 'Sine.easeIn', ease: 'Sine.easeIn',
onComplete: () => { onComplete: () => {
this.battleInfo.setVisible(false); 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(); resolve();
} }
}); });
@ -1039,8 +1042,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (isCritical) if (isCritical)
this.scene.queueMessage('A critical hit!'); this.scene.queueMessage('A critical hit!');
this.scene.setPhaseQueueSplice(); this.scene.setPhaseQueueSplice();
damage.value = Math.min(damage.value, this.hp); damage.value = this.damage(damage.value);
this.damage(damage.value);
if (source.isPlayer()) if (source.isPlayer())
this.scene.validateAchvs(DamageAchv, damage); this.scene.validateAchvs(DamageAchv, damage);
source.turnData.damageDealt += damage.value; source.turnData.damageDealt += damage.value;
@ -1083,9 +1085,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return result; return result;
} }
damage(damage: integer, preventEndure?: boolean): void { damage(damage: integer, ignoreSegments: boolean = false, preventEndure: boolean = false): integer {
if (this.isFainted()) if (this.isFainted())
return; return 0;
if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) { if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) {
const surviveDamage = new Utils.BooleanHolder(false); const surviveDamage = new Utils.BooleanHolder(false);
@ -1094,19 +1096,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
damage = this.hp - 1; 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()) { if (this.isFainted()) {
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), preventEndure)); this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), preventEndure));
this.resetSummonData(); this.resetSummonData();
} }
return damage;
} }
heal(amount: integer): void { heal(amount: integer): integer {
this.hp = Math.min(this.hp + amount, this.getMaxHp()); const healAmount = Math.min(amount, this.getMaxHp() - this.hp);
this.hp += healAmount;
return healAmount;
} }
isBossImmune(): boolean { 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 { addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean {
@ -1742,12 +1750,17 @@ export default interface Pokemon {
export class PlayerPokemon extends Pokemon { export class PlayerPokemon extends Pokemon {
public compatibleTms: Moves[]; 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); super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, ivs, nature, dataSource);
this.generateCompatibleTms(); this.generateCompatibleTms();
} }
initBattleInfo(): void {
this.battleInfo = new PlayerBattleInfo(this.scene);
this.battleInfo.initInfo(this);
}
isPlayer(): boolean { isPlayer(): boolean {
return true; return true;
} }
@ -1756,6 +1769,10 @@ export class PlayerPokemon extends Pokemon {
return true; return true;
} }
isBoss(): boolean {
return false;
}
getFieldIndex(): integer { getFieldIndex(): integer {
return this.scene.getPlayerField().indexOf(this); return this.scene.getPlayerField().indexOf(this);
} }
@ -1808,7 +1825,7 @@ export class PlayerPokemon extends Pokemon {
return new Promise(resolve => { return new Promise(resolve => {
const species = getPokemonSpecies(evolution.speciesId); const species = getPokemonSpecies(evolution.speciesId);
const formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0); 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)); ret.loadAssets().then(() => resolve(ret));
}); });
} }
@ -1839,7 +1856,7 @@ export class PlayerPokemon extends Pokemon {
if (this.species.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) { if (this.species.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) {
const newEvolution = pokemonEvolutions[this.species.speciesId][1]; const newEvolution = pokemonEvolutions[this.species.speciesId][1];
if (newEvolution.condition.predicate(this)) { 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); this.scene.getParty().push(newPokemon);
newPokemon.evolve(newEvolution); newPokemon.evolve(newEvolution);
const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
@ -1911,12 +1928,16 @@ export class PlayerPokemon extends Pokemon {
export class EnemyPokemon extends Pokemon { export class EnemyPokemon extends Pokemon {
public trainer: boolean; public trainer: boolean;
public aiType: AiType; 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, super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex,
dataSource?.gender, dataSource ? dataSource.shiny : false, null, dataSource ? dataSource.nature : undefined, dataSource); dataSource?.gender, dataSource ? dataSource.shiny : false, null, dataSource ? dataSource.nature : undefined, dataSource);
this.trainer = trainer; this.trainer = trainer;
if (boss)
this.setBoss();
if (!dataSource) { if (!dataSource) {
this.trySetShiny(); this.trySetShiny();
@ -1934,6 +1955,22 @@ export class EnemyPokemon extends Pokemon {
this.aiType = AiType.SMART_RANDOM; 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 { generateAndPopulateMoveset(): void {
switch (true) { switch (true) {
case (this.species.speciesId === Species.SMEARGLE): case (this.species.speciesId === Species.SMEARGLE):
@ -2097,6 +2134,107 @@ export class EnemyPokemon extends Pokemon {
return this.trainer; 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 { getFieldIndex(): integer {
return this.scene.getEnemyField().indexOf(this); return this.scene.getEnemyField().indexOf(this);
} }
@ -2113,7 +2251,7 @@ export class EnemyPokemon extends Pokemon {
this.pokeball = pokeballType; this.pokeball = pokeballType;
this.metLevel = this.level; this.metLevel = this.level;
this.metBiome = this.scene.arena.biomeType; 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); party.push(newPokemon);
ret = newPokemon; ret = newPokemon;
} }

View File

@ -39,6 +39,8 @@ export default class PokemonData {
public fusionShiny: boolean; public fusionShiny: boolean;
public fusionGender: Gender; public fusionGender: Gender;
public boss: boolean;
public summonData: PokemonSummonData; public summonData: PokemonSummonData;
constructor(source: Pokemon | any) { constructor(source: Pokemon | any) {
@ -70,6 +72,8 @@ export default class PokemonData {
this.fusionShiny = source.fusionShiny; this.fusionShiny = source.fusionShiny;
this.fusionGender = source.fusionGender; this.fusionGender = source.fusionGender;
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
if (sourcePokemon) { if (sourcePokemon) {
this.moveset = sourcePokemon.moveset; this.moveset = sourcePokemon.moveset;
this.status = sourcePokemon.status; this.status = sourcePokemon.status;
@ -95,7 +99,7 @@ export default class PokemonData {
toPokemon(scene: BattleScene, battleType?: BattleType): Pokemon { toPokemon(scene: BattleScene, battleType?: BattleType): Pokemon {
const species = getPokemonSpecies(this.species); const species = getPokemonSpecies(this.species);
if (this.player) if (this.player)
return new PlayerPokemon(scene, species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this); return scene.addPlayerPokemon(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.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)) ? getPokemonSpecies(battle.enemyParty[offset].species.getSpeciesForLevel(level, false, true, this.config.isBoss))
: this.genNewPartyMemberSpecies(level); : 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)); }, 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; 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 { getLevelTotalExp, getLevelRelExp } from '../data/exp';
import * as Utils from '../utils'; import * as Utils from '../utils';
import { addTextObject, TextStyle } from './text'; import { addTextObject, TextStyle } from './text';
@ -9,6 +9,8 @@ import BattleScene from '../battle-scene';
export default class BattleInfo extends Phaser.GameObjects.Container { export default class BattleInfo extends Phaser.GameObjects.Container {
private player: boolean; private player: boolean;
private mini: boolean; private mini: boolean;
private boss: boolean;
private bossSegments: integer;
private offset: boolean; private offset: boolean;
private lastName: string; private lastName: string;
private lastStatus: StatusEffect; private lastStatus: StatusEffect;
@ -29,6 +31,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private statusIndicator: Phaser.GameObjects.Sprite; private statusIndicator: Phaser.GameObjects.Sprite;
private levelContainer: Phaser.GameObjects.Container; private levelContainer: Phaser.GameObjects.Container;
private hpBar: Phaser.GameObjects.Image; private hpBar: Phaser.GameObjects.Image;
private hpBarSegmentDividers: Phaser.GameObjects.Rectangle[];
private levelNumbersContainer: Phaser.GameObjects.Container; private levelNumbersContainer: Phaser.GameObjects.Container;
private hpNumbersContainer: Phaser.GameObjects.Container; private hpNumbersContainer: Phaser.GameObjects.Container;
private expBar: Phaser.GameObjects.Image; private expBar: Phaser.GameObjects.Image;
@ -37,6 +40,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
super(scene, x, y); super(scene, x, y);
this.player = player; this.player = player;
this.mini = !player; this.mini = !player;
this.boss = false;
this.offset = false; this.offset = false;
this.lastName = null; this.lastName = null;
this.lastStatus = StatusEffect.NONE; this.lastStatus = StatusEffect.NONE;
@ -100,6 +104,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.hpBar.setOrigin(0); this.hpBar.setOrigin(0);
this.add(this.hpBar); this.add(this.hpBar);
this.hpBarSegmentDividers = [];
this.levelNumbersContainer = this.scene.add.container(9.5, 0); this.levelNumbersContainer = this.scene.add.container(9.5, 0);
this.levelContainer.add(this.levelNumbersContainer); this.levelContainer.add(this.levelNumbersContainer);
@ -137,6 +143,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const dexAttr = pokemon.getDexAttr(); const dexAttr = pokemon.getDexAttr();
if ((dexEntry.caughtAttr & dexAttr) < dexAttr) if ((dexEntry.caughtAttr & dexAttr) < dexAttr)
this.ownedIcon.setTint(0x808080); this.ownedIcon.setTint(0x808080);
if (this.boss)
this.updateBossSegmentDividers(pokemon.getMaxHp());
} }
this.hpBar.setScale(pokemon.getHpRatio(), 1); this.hpBar.setScale(pokemon.getHpRatio(), 1);
@ -158,7 +167,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
} }
getTextureName(): string { 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 { setMini(mini: boolean): void {
@ -181,6 +190,40 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const toggledElements = [ this.hpNumbersContainer, this.expBar ]; const toggledElements = [ this.hpNumbersContainer, this.expBar ];
toggledElements.forEach(el => el.setVisible(!mini)); 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 { setOffset(offset: boolean): void {
if (this.offset === offset) 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 { 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 { 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 { export function randSeedInt(range: integer, min: integer = 0): integer {
if (range === 1) if (range <= 1)
return min; return min;
return Phaser.Math.RND.integerInRange(min, (range - 1) + min); return Phaser.Math.RND.integerInRange(min, (range - 1) + min);
} }