Add boss health bars
This commit is contained in:
parent
52e3c6b730
commit
eedad7d678
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[] = [];
|
||||
|
|
|
@ -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;
|
||||
|
|
158
src/data/move.ts
158
src/data/move.ts
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
})),
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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'));
|
||||
|
||||
|
|
188
src/pokemon.ts
188
src/pokemon.ts
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue