Add status effects

pull/1/head
Flashfyre 2023-04-11 19:08:03 -04:00
parent 3f9fbc1267
commit b0ecb9d5f5
11 changed files with 489 additions and 136 deletions

View File

@ -9,13 +9,10 @@
- Moves
- Move logic
- Can't use when PP consumed
- Multi hit moves should still show 1 time(s) when hitting once
- Abilities
- Ability logic
- Ability activation indicator (?)
- Natures
- Status effects
- Status effect animation
- EXP logic
- Fix algorithm (currently inaccurate)
- Pokemon summary screen

View File

@ -4,8 +4,8 @@
"image": "statuses.png",
"format": "RGBA8888",
"size": {
"w": 44,
"h": 30
"w": 20,
"h": 56
},
"scale": 1,
"frames": [
@ -24,8 +24,8 @@
"h": 8
},
"frame": {
"x": 1,
"y": 1,
"x": 0,
"y": 0,
"w": 20,
"h": 8
}
@ -45,8 +45,8 @@
"h": 8
},
"frame": {
"x": 1,
"y": 11,
"x": 0,
"y": 8,
"w": 20,
"h": 8
}
@ -66,8 +66,8 @@
"h": 8
},
"frame": {
"x": 1,
"y": 21,
"x": 0,
"y": 16,
"w": 20,
"h": 8
}
@ -87,8 +87,8 @@
"h": 8
},
"frame": {
"x": 23,
"y": 1,
"x": 0,
"y": 24,
"w": 20,
"h": 8
}
@ -108,8 +108,8 @@
"h": 8
},
"frame": {
"x": 23,
"y": 11,
"x": 0,
"y": 32,
"w": 20,
"h": 8
}
@ -129,8 +129,29 @@
"h": 8
},
"frame": {
"x": 23,
"y": 21,
"x": 0,
"y": 40,
"w": 20,
"h": 8
}
},
{
"filename": "toxic",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 20,
"h": 8
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 20,
"h": 8
},
"frame": {
"x": 0,
"y": 48,
"w": 20,
"h": 8
}
@ -141,6 +162,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:34eb38966145af4ef9cffbaaa8d07ea3:fba056f9a49345436b0820757c78f418:e6649238c018d3630e55681417c698ca$"
"smartupdate": "$TexturePacker:SmartUpdate:b1f59475db34388a7177fe541f95d9e8:47a06b96ac24cc4249538472b2604202:e6649238c018d3630e55681417c698ca$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

View File

@ -35,12 +35,12 @@ export enum ChargeAnim {
export enum CommonAnim {
HEALTH_UP = 2000,
SLEEP,
POISON,
TOXIC,
BURN,
PARALYSIS,
SLEEP,
FROZEN,
BURN,
CONFUSION,
ATTRACT,
BIND,
@ -209,7 +209,7 @@ abstract class AnimTimedEvent {
this.resourceName = resourceName;
}
abstract execute(scene: BattleScene, moveAnim: MoveAnim): void;
abstract execute(scene: BattleScene, battleAnim: BattleAnim): integer;
abstract getEventType(): string;
}
@ -228,12 +228,13 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
this.pitch = 100;
}
execute(scene: BattleScene, moveAnim: MoveAnim): void {
execute(scene: BattleScene, battleAnim: BattleAnim): integer {
const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) };
if (this.resourceName)
if (this.resourceName) {
scene.sound.play(this.resourceName, soundConfig);
else
moveAnim.user.cry(soundConfig);
return Math.ceil((scene.sound.get(this.resourceName).totalDuration * 1000) / 33.33);
} else
return Math.ceil(battleAnim.user.cry(soundConfig) / 33.33);
}
getEventType(): string {
@ -284,7 +285,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
super(frameIndex, resourceName, source);
}
execute(scene: BattleScene, moveAnim: MoveAnim): void {
execute(scene: BattleScene, moveAnim: MoveAnim): integer {
const tweenProps = {};
if (this.bgX !== undefined)
tweenProps['x'] = (this.bgX * 0.5) - 256;
@ -299,6 +300,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
useFrames: true
}, tweenProps))
}
return this.duration * 2;
}
getEventType(): string {
@ -311,7 +313,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
super(frameIndex, resourceName, source);
}
execute(scene: BattleScene, moveAnim: MoveAnim): void {
execute(scene: BattleScene, moveAnim: MoveAnim): integer {
moveAnim.bgSprite = scene.add.tileSprite(this.bgX - 256, this.bgY - 284, 768, 576, this.resourceName);
moveAnim.bgSprite.setOrigin(0, 0);
moveAnim.bgSprite.setScale(1.25);
@ -325,6 +327,8 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
duration: this.duration * 2,
useFrames: true
});
return this.duration * 2;
}
getEventType(): string {
@ -336,7 +340,22 @@ export const moveAnims = new Map<Moves, Anim | [Anim, Anim]>();
export const chargeAnims = new Map<ChargeAnim, Anim>();
export const commonAnims = new Map<CommonAnim, Anim>();
export function initAnim(move: Moves): Promise<void> {
export function initCommonAnims(): Promise<void> {
return new Promise(resolve => {
const commonAnimNames = Utils.getEnumKeys(CommonAnim);
const commonAnimIds = Utils.getEnumValues(CommonAnim);
const commonAnimFetches = [];
for (let ca = 0; ca < commonAnimIds.length; ca++) {
const commonAnimId = commonAnimIds[ca];
commonAnimFetches.push(fetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, '-')}.json`)
.then(response => response.json())
.then(cas => commonAnims.set(commonAnimId, new Anim(cas))));
}
Promise.allSettled(commonAnimFetches).then(() => resolve());
});
}
export function initMoveAnim(move: Moves): Promise<void> {
return new Promise(resolve => {
if (moveAnims.has(move)) {
if (moveAnims.get(move) !== null)
@ -374,6 +393,12 @@ function populateMoveAnim(move: Moves, animSource: any) {
moveAnims.set(move, [ moveAnims.get(move) as Anim, moveAnim ]);
}
export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
loadAnimAssets(scene, Array.from(commonAnims.values()), startLoad).then(() => resolve());
});
}
export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
const moveAnimations = moveIds.map(m => {
@ -382,17 +407,23 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
return anims as Anim;
return anims[0] as Anim;
});
loadAnimAssets(scene, moveAnimations, startLoad).then(() => resolve());
});
}
function loadAnimAssets(scene: BattleScene, anims: Anim[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
const backgrounds = new Set<string>();
const sounds = new Set<string>();
for (let ma of moveAnimations) {
const moveSounds = ma.getSoundResourceNames();
for (let ms of moveSounds)
sounds.add(ms);
const moveBackgrounds = ma.getBackgroundResourceNames();
for (let mbg of moveBackgrounds)
backgrounds.add(mbg);
if (ma.graphic)
scene.loadSpritesheet(ma.graphic, 'battle_anims', 96);
for (let a of anims) {
const animSounds = a.getSoundResourceNames();
for (let ms of animSounds)
sounds.add(ms);
const animBackgrounds = a.getBackgroundResourceNames();
for (let abg of animBackgrounds)
backgrounds.add(abg);
if (a.graphic)
scene.loadSpritesheet(a.graphic, 'battle_anims', 96);
}
for (let bg of backgrounds)
scene.loadImage(bg, 'battle_anims');
@ -404,25 +435,26 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
});
}
export class MoveAnim {
public move: Moves;
export abstract class BattleAnim {
public user: Pokemon;
public target: Pokemon;
public moveSprites: Phaser.GameObjects.Sprite[];
public sprites: Phaser.GameObjects.Sprite[];
public bgSprite: Phaser.GameObjects.TileSprite;
constructor(move: Moves, user: Pokemon, target: Pokemon) {
this.move = move;
constructor(user: Pokemon, target: Pokemon) {
this.user = user;
this.target = target;
this.moveSprites = [];
this.sprites = [];
}
abstract getAnim(): Anim;
abstract isOppAnim(): boolean;
abstract isReverseCoords(): boolean;
play(scene: BattleScene, callback?: Function) {
const anim = moveAnims.get(this.move) instanceof Anim
? moveAnims.get(this.move) as Anim
: moveAnims.get(this.move)[this.user instanceof PlayerPokemon ? 0 : 1] as Anim;
const isOppMove = Array.isArray(moveAnims.get(this.move)) && this.user instanceof EnemyPokemon;
const anim = this.getAnim();
const userInitialX = this.user.x;
const userInitialY = this.user.y;
@ -431,11 +463,12 @@ export class MoveAnim {
const targetInitialY = this.target.y;
const targetHalfHeight = this.target.getSprite().displayHeight / 2;
const coordMultiplayer = this.user instanceof PlayerPokemon || isOppMove ? 1 : -1;
const coordMultiplier = this.isReverseCoords() ? -1 : 1;
let r = anim.frames.length;
let f = 0;
const moveSprites: Phaser.GameObjects.Sprite[] = [];
const sprites: Phaser.GameObjects.Sprite[] = [];
scene.tweens.addCounter({
useFrames: true,
@ -447,24 +480,25 @@ export class MoveAnim {
for (let frame of spriteFrames) {
switch (frame.target) {
case AnimFrameTarget.USER:
this.user.setPosition(userInitialX + frame.x * coordMultiplayer, userInitialY + frame.y * coordMultiplayer);
this.user.setPosition(userInitialX + frame.x * coordMultiplier, userInitialY + frame.y * coordMultiplier);
break;
case AnimFrameTarget.TARGET:
this.target.setPosition(targetInitialX + frame.x * coordMultiplayer, targetInitialY + frame.y * coordMultiplayer);
this.target.setPosition(targetInitialX + frame.x * coordMultiplier, targetInitialY + frame.y * coordMultiplier);
break;
case AnimFrameTarget.GRAPHIC:
if (g === moveSprites.length) {
if (g === sprites.length) {
const newSprite = scene.add.sprite(0, 0, anim.graphic, 1);
scene.field.add(newSprite);
moveSprites.push(newSprite);
sprites.push(newSprite);
}
const moveSprite = moveSprites[g++];
const moveSprite = sprites[g++];
moveSprite.setFrame(frame.graphicFrame);
const xProgress = Math.min(Math.max(frame.x, 0) / 128, 1);
const yOffset = ((userHalfHeight * (1 - xProgress)) + (targetHalfHeight * xProgress)) * -1;
moveSprite.setPosition((!isOppMove ? userInitialX : targetInitialX) + frame.x * coordMultiplayer, (!isOppMove ? userInitialY : targetInitialY) + yOffset + frame.y * coordMultiplayer);
const isOppAnim = this.isOppAnim();
moveSprite.setPosition((!isOppAnim ? userInitialX : targetInitialX) + frame.x * coordMultiplier, (!isOppAnim ? userInitialY : targetInitialY) + yOffset + frame.y * coordMultiplier);
moveSprite.setAlpha(frame.opacity);
moveSprite.setAngle(-frame.angle * coordMultiplayer);
moveSprite.setAngle(-frame.angle * coordMultiplier);
break;
}
if (frame.target !== AnimFrameTarget.GRAPHIC) {
@ -472,7 +506,7 @@ export class MoveAnim {
? this.user
: this.target;
pokemon.setAlpha(frame.opacity);
pokemon.setAngle(-frame.angle * coordMultiplayer);
pokemon.setAngle(-frame.angle * coordMultiplier);
const zoomScaleX = frame.zoomX / 100;
const zoomScaleY = frame.zoomY / 100;
const zoomSprite = pokemon.getZoomSprite();
@ -482,14 +516,15 @@ export class MoveAnim {
}
if (anim.frameTimedEvents.has(f)) {
for (let event of anim.frameTimedEvents.get(f))
event.execute(scene, this);
r = Math.max((anim.frames.length - f) + event.execute(scene, this), r);
}
if (g < moveSprites.length) {
const removedSprites = moveSprites.splice(g, moveSprites.length - g);
if (g < sprites.length) {
const removedSprites = sprites.splice(g, sprites.length - g);
for (let rs of removedSprites)
rs.destroy();
}
f++;
r--;
},
onComplete: () => {
this.user.setPosition(userInitialX, userInitialY);
@ -498,15 +533,67 @@ export class MoveAnim {
this.target.setPosition(targetInitialX, targetInitialY);
this.target.setAlpha(1);
this.target.setAngle(0);
for (let ms of moveSprites)
for (let ms of sprites)
ms.destroy();
if (callback)
if (r && callback) {
scene.tweens.addCounter({
duration: r,
useFrames: true,
onComplete: () => callback()
});
} else if (callback)
callback();
}
});
}
}
export class CommonBattleAnim extends BattleAnim {
public commonAnim: CommonAnim;
constructor(commonAnim: CommonAnim, user: Pokemon, target?: Pokemon) {
super(user, target || user);
this.commonAnim = commonAnim;
}
getAnim(): Anim {
return commonAnims.get(this.commonAnim);
}
isOppAnim(): boolean {
return false;
}
isReverseCoords(): boolean {
return false;
}
}
export class MoveAnim extends BattleAnim {
public move: Moves;
constructor(move: Moves, user: Pokemon, target: Pokemon) {
super(user, target);
this.move = move;
}
getAnim(): Anim {
return moveAnims.get(this.move) instanceof Anim
? moveAnims.get(this.move) as Anim
: moveAnims.get(this.move)[this.user instanceof PlayerPokemon ? 0 : 1] as Anim;
}
isOppAnim(): boolean {
return this.user instanceof EnemyPokemon && Array.isArray(moveAnims.get(this.move));
}
isReverseCoords(): boolean {
return this.user instanceof EnemyPokemon && !this.isOppAnim();
}
}
export function populateAnims() {
return;
const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase());

View File

@ -3,10 +3,12 @@ import { getLevelTotalExp, getLevelRelExp } from './exp';
import * as Utils from './utils';
import { addTextObject, TextStyle } from './text';
import { getGenderSymbol, getGenderColor } from './gender';
import { StatusEffect } from './status-effect';
export default class BattleInfo extends Phaser.GameObjects.Container {
private player: boolean;
private lastName: string;
private lastStatus: StatusEffect;
private lastHp: integer;
private lastMaxHp: integer;
private lastHpFrame: string;
@ -16,6 +18,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private nameText: Phaser.GameObjects.Text;
private genderText: Phaser.GameObjects.Text;
private statusIndicator: Phaser.GameObjects.Sprite;
private levelContainer: Phaser.GameObjects.Container;
private hpBar: Phaser.GameObjects.Image;
private levelNumbersContainer: Phaser.GameObjects.Container;
@ -25,6 +28,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
super(scene, x, y);
this.player = player;
this.lastName = null;
this.lastStatus = StatusEffect.NONE;
this.lastHp = -1;
this.lastMaxHp = -1;
this.lastHpFrame = null;
@ -48,6 +53,12 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.genderText.setPositionRelative(this.nameText, 0, 2);
this.add(this.genderText);
this.statusIndicator = this.scene.add.sprite(0, 0, 'statuses');
this.statusIndicator.setVisible(false);
this.statusIndicator.setOrigin(0, 0);
this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5);
this.add(this.statusIndicator);
this.levelContainer = this.scene.add.container(player ? -41 : -50, player ? -10 : -5);
this.add(this.levelContainer);
@ -121,6 +132,14 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.genderText.setPositionRelative(this.nameText, nameTextWidth, 0);
}
if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) {
this.lastStatus = pokemon.status?.effect || StatusEffect.NONE;
if (this.lastStatus !== StatusEffect.NONE)
this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase());
this.statusIndicator.setVisible(!!this.lastStatus);
}
const updatePokemonHp = () => {
const duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0;
this.scene.tweens.add({

View File

@ -8,8 +8,8 @@ import { Stat } from "./pokemon-stat";
import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, getModifierTypesForWave, ModifierType, PokemonModifierType, PokemonMoveModifierType, regenerateModifierPoolThresholds } from "./modifier";
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./pokeball";
import { MoveAnim, initAnim, loadMoveAnimAssets } from "./battle-anims";
import { StatusEffect } from "./status-effect";
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { StatusEffect, getStatusEffectActivationText, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./status-effect";
import { SummaryUiMode } from "./ui/summary-ui-handler";
import EvolutionSceneHandler from "./ui/evolution-scene-handler";
import { EvolutionPhase } from "./evolution-phase";
@ -351,19 +351,20 @@ export class CommandPhase extends BattlePhase {
});
}
handleCommand(command: Command, cursor: integer): boolean{
handleCommand(command: Command, cursor: integer): boolean {
const playerPokemon = this.scene.getPlayerPokemon();
const enemyPokemon = this.scene.getEnemyPokemon();
let success: boolean;
let delayed: boolean;
const playerSpeed = playerPokemon.getBattleStat(Stat.SPD);
const enemySpeed = enemyPokemon.getBattleStat(Stat.SPD);
const isDelayed = () => playerSpeed < enemySpeed || (playerSpeed === enemySpeed && Utils.randInt(2) === 1);
switch (command) {
case Command.FIGHT:
if (playerPokemon.trySelectMove(cursor)) {
const playerPhase = new PlayerMovePhase(this.scene, playerPokemon, playerPokemon.moveset[cursor]);
const playerSpeed = playerPokemon.getBattleStat(Stat.SPD);
const enemySpeed = enemyPokemon.getBattleStat(Stat.SPD);
delayed = playerSpeed < enemySpeed || (playerSpeed === enemySpeed && Utils.randInt(2) === 1)
this.scene.pushPhase(playerPhase);
success = true;
}
@ -382,10 +383,24 @@ export class CommandPhase extends BattlePhase {
if (success) {
const enemyMove = enemyPokemon.getNextMove();
const enemyPhase = new EnemyMovePhase(this.scene, enemyPokemon, enemyMove);
if (delayed)
if (isDelayed())
this.scene.unshiftPhase(enemyPhase);
else
this.scene.pushPhase(enemyPhase);
const statusEffectPhases: PostTurnStatusEffectPhase[] = [];
if (playerPokemon.status && playerPokemon.status.isPostTurn())
statusEffectPhases.push(new PostTurnStatusEffectPhase(this.scene, true));
if (enemyPokemon.status && enemyPokemon.status.isPostTurn()) {
const enemyStatusEffectPhase = new PostTurnStatusEffectPhase(this.scene, false);
if (isDelayed())
statusEffectPhases.unshift(enemyStatusEffectPhase);
else
statusEffectPhases.push(enemyStatusEffectPhase);
}
for (let sef of statusEffectPhases)
this.scene.pushPhase(sef);
this.end();
}
@ -428,12 +443,14 @@ export abstract class PartyMemberPokemonPhase extends PokemonPhase {
abstract class MovePhase extends BattlePhase {
protected pokemon: Pokemon;
protected move: PokemonMove;
protected cancelled: boolean;
constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove) {
super(scene);
this.pokemon = pokemon;
this.move = move;
this.cancelled = false;
}
abstract getEffectPhase(): MoveEffectPhase;
@ -445,17 +462,61 @@ abstract class MovePhase extends BattlePhase {
start() {
super.start();
const doMove = () => {
if (this.cancelled) {
this.end();
return;
}
if (!this.move)
console.log(this.pokemon.moveset);
this.move.ppUsed++;
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.pokemon.name} used\n${this.move.getName()}!`, 500));
this.scene.unshiftPhase(this.getEffectPhase());
this.end();
};
if (!this.canMove()) {
this.end();
return;
}
if (!this.move)
console.log(this.pokemon.moveset);
this.scene.ui.showText(`${this.pokemon.name} used\n${this.move.getName()}!`, null, () => {
this.move.ppUsed++;
this.scene.unshiftPhase(this.getEffectPhase());
this.end();
}, 500);
if (this.pokemon.status && !this.pokemon.status.isPostTurn()) {
this.pokemon.status.incrementTurn();
let activated = false;
let healed = false;
switch (this.pokemon.status.effect) {
case StatusEffect.PARALYSIS:
if (Utils.randInt(4) === 0) {
activated = true;
this.cancelled = true;
}
break;
case StatusEffect.SLEEP:
healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn;
activated = !healed;
this.cancelled = activated;
break;
case StatusEffect.FREEZE:
healed = Utils.randInt(5) === 0;
activated = !healed;
this.cancelled = activated;
break;
}
if (activated) {
this.scene.unshiftPhase(new MessagePhase(this.scene,
`${this.pokemon instanceof PlayerPokemon ? '' : 'Foe '}${this.pokemon.name}${getStatusEffectActivationText(this.pokemon.status.effect)}`));
new CommonBattleAnim(CommonAnim.POISON + (this.pokemon.status.effect - 1), this.pokemon).play(this.scene, () => doMove());
} else {
if (healed) {
this.scene.unshiftPhase(new MessagePhase(this.scene,
`${this.pokemon instanceof PlayerPokemon ? '' : 'Foe '}${this.pokemon.name}${getStatusEffectHealText(this.pokemon.status.effect)}`));
this.pokemon.resetStatus();
this.pokemon.updateInfo(true);
}
doMove();
}
} else
doMove();
}
}
@ -498,7 +559,7 @@ abstract class MoveEffectPhase extends PokemonPhase {
const hitCount = new Utils.IntegerHolder(1);
applyMoveAttrs(MultiHitAttr, this.scene, user, target, this.move.getMove(), hitCount);
user.turnData.hitCount = 0;
user.turnData.hitsLeft = hitCount.value;
user.turnData.hitsLeft = user.turnData.hitsTotal = hitCount.value;
}
if (!this.hitCheck()) {
@ -511,7 +572,8 @@ abstract class MoveEffectPhase extends PokemonPhase {
new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () => {
this.getTargetPokemon().apply(this.getUserPokemon(), this.move, () => {
++user.turnData.hitCount;
applyMoveAttrs(MoveHitEffectAttr, this.scene, user, target, this.move.getMove());
if (this.getTargetPokemon().hp)
applyMoveAttrs(MoveHitEffectAttr, this.scene, user, target, this.move.getMove());
this.end();
});
if (this.getUserPokemon().hp <= 0) {
@ -529,7 +591,7 @@ abstract class MoveEffectPhase extends PokemonPhase {
const user = this.getUserPokemon();
if (--user.turnData.hitsLeft && this.getTargetPokemon().hp)
this.scene.unshiftPhase(this.getNewHitPhase());
else if (user.turnData.hitCount > 1)
else if (user.turnData.hitsTotal > 1)
this.scene.unshiftPhase(new MessagePhase(this.scene, `Hit ${user.turnData.hitCount} time(s)!`));
super.end();
@ -683,14 +745,56 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, statusEffect: StatusEffect) {
super(scene, player);
this.statusEffect = statusEffect;
}
start() {
const pokemon = this.getPokemon();
if (pokemon.status === StatusEffect.NONE) {
pokemon.status = this.statusEffect;
// Show message and animation
this.end();
if (!pokemon.status) {
if (pokemon.trySetStatus(this.statusEffect)) {
pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => {
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.player ? '' : 'Foe '}${pokemon.name}${getStatusEffectObtainText(this.statusEffect)}`));
if (pokemon.status.isPostTurn())
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.player));
this.end();
});
return;
}
} else if (pokemon.status.effect === this.statusEffect)
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.player ? '' : 'Foe '}${pokemon.name}${getStatusEffectOverlapText(this.statusEffect)}`));
this.end();
}
}
export class PostTurnStatusEffectPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean) {
super(scene, player);
}
start() {
const pokemon = this.getPokemon();
if (pokemon.hp && pokemon.status && pokemon.status.isPostTurn()) {
pokemon.status.incrementTurn();
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => {
this.scene.unshiftPhase(new MessagePhase(this.scene,
`${pokemon instanceof PlayerPokemon ? '' : 'Foe '}${pokemon.name}${getStatusEffectActivationText(pokemon.status.effect)}`));
switch (pokemon.status.effect) {
case StatusEffect.POISON:
case StatusEffect.BURN:
pokemon.hp = Math.max(pokemon.hp - Math.max(pokemon.getMaxHp() >> 3, 1), 0);
break;
case StatusEffect.TOXIC:
pokemon.hp = Math.max(pokemon.hp - Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1), 0);
break;
}
if (pokemon.hp <= 0) {
this.scene.pushPhase(new FaintPhase(this.scene, this.player));
(this.player ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).resetBattleSummonData();
}
pokemon.updateInfo().then(() => this.end());
});
} else
this.end();
}
@ -698,19 +802,21 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
export class MessagePhase extends BattlePhase {
private text: string;
private callbackDelay: integer;
private prompt: boolean;
constructor(scene: BattleScene, text: string, prompt?: boolean) {
constructor(scene: BattleScene, text: string, callbackDelay?: integer, prompt?: boolean) {
super(scene);
this.text = text;
this.callbackDelay = callbackDelay;
this.prompt = prompt;
}
start() {
super.start();
this.scene.ui.showText(this.text, null, () => this.end(), this.prompt ? 0 : 1500, this.prompt);
this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt);
}
}
@ -723,10 +829,10 @@ export class FaintPhase extends PokemonPhase {
super.start();
if (this.player) {
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.getPokemon().name} fainted!`, true));
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.getPokemon().name} fainted!`, null, true));
this.scene.unshiftPhase(new SwitchPhase(this.scene, true, false));
} else {
this.scene.unshiftPhase(new MessagePhase(this.scene, `Foe ${this.getPokemon().name} fainted!`, true));
this.scene.unshiftPhase(new MessagePhase(this.scene, `Foe ${this.getPokemon().name} fainted!`, null, true));
this.scene.unshiftPhase(new VictoryPhase(this.scene));
}
@ -904,7 +1010,7 @@ export class LearnMovePhase extends PartyMemberPokemonPhase {
if (emptyMoveIndex > -1) {
pokemon.moveset[emptyMoveIndex] = new PokemonMove(this.moveId, 0, 0);
initAnim(this.moveId).then(() => {
initMoveAnim(this.moveId).then(() => {
loadMoveAnimAssets(this.scene, [ this.moveId ], true)
.then(() => {
this.scene.ui.setMode(messageMode).then(() => {

View File

@ -10,7 +10,7 @@ import { PokeballType } from './pokeball';
import { Species } from './species';
import { initAutoPlay } from './auto-play';
import { Battle } from './battle';
import { populateAnims } from './battle-anims';
import { initCommonAnims, loadCommonAnimAssets, populateAnims } from './battle-anims';
import { BattlePhase } from './battle-phase';
const enableAuto = true;
@ -321,7 +321,7 @@ export default class BattleScene extends Phaser.Scene {
ui.setup();
Promise.all(loadPokemonAssets).then(() => {
Promise.all([ Promise.all(loadPokemonAssets), initCommonAnims().then(() => loadCommonAnimAssets(this, true)) ]).then(() => {
if (enableAuto)
initAutoPlay.apply(this);

View File

@ -2,8 +2,7 @@ import { MessagePhase, ObtainStatusEffectPhase, StatChangePhase } from "./battle
import BattleScene from "./battle-scene";
import { BattleStat } from "./battle-stat";
import Pokemon, { PlayerPokemon } from "./pokemon";
import { Stat } from "./pokemon-stat";
import { StatusEffect } from "./status-effect";
import { StatusEffect, getStatusEffectOverlapText } from "./status-effect";
import { Type } from "./type";
import * as Utils from "./utils";
@ -609,6 +608,10 @@ export enum Moves {
FUSION_BOLT
};
const enum MoveEffectText {
BUT_IT_FAILED = 'But it failed!'
}
type MoveAttrFunc = (scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) => void;
export abstract class MoveAttr {
@ -624,7 +627,7 @@ export class HighCritAttr extends MoveAttr {
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const critChance = args[0] as Utils.IntegerHolder;
critChance.value /= 2;
return true;
}
}
@ -681,8 +684,10 @@ class StatusEffectAttr extends MoveHitEffectAttr {
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const statusCheck = move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance;
if (statusCheck)
scene.unshiftPhase(new ObtainStatusEffectPhase(scene, target instanceof PlayerPokemon, this.effect));
if (statusCheck) {
if (!target.status || (target.status.effect === this.effect && move.chance < 0))
scene.unshiftPhase(new ObtainStatusEffectPhase(scene, target instanceof PlayerPokemon, this.effect));
}
return true;
}
}
@ -794,32 +799,32 @@ export const allMoves = [
new Move(Moves.FURY_ATTACK, "Fury Attack", Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, "Hits 2-5 times in one turn.", -1, 1, new MultiHitAttr()),
new Move(Moves.HORN_DRILL, "Horn Drill", Type.NORMAL, MoveCategory.PHYSICAL, -1, 30, 5, -1, "One-Hit-KO, if it hits.", -1, 1, new OneHitKOAttr()),
new Move(Moves.TACKLE, "Tackle", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, "", -1, 1),
new Move(Moves.BODY_SLAM, "Body Slam", Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 66, "May paralyze opponent.", 30, 1),
new Move(Moves.BODY_SLAM, "Body Slam", Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 66, "May paralyze opponent.", 30, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.WRAP, "Wrap", Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1),
new Move(Moves.TAKE_DOWN, "Take Down", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 1, "User receives recoil damage.", -1, 1),
new Move(Moves.THRASH, "Thrash", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1),
new Move(Moves.DOUBLE_EDGE, "Double-Edge", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, "User receives recoil damage.", -1, 1),
new Move(Moves.TAIL_WHIP, "Tail Whip", Type.NORMAL, MoveCategory.STATUS, -1, 100, 30, -1, "Lowers opponent's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, -1)),
new Move(Moves.POISON_STING, "Poison Sting", Type.POISON, MoveCategory.PHYSICAL, 15, 100, 35, -1, "May poison the opponent.", 30, 1),
new Move(Moves.TWINEEDLE, "Twineedle", Type.BUG, MoveCategory.PHYSICAL, 25, 100, 20, -1, "Hits twice in one turn. May poison opponent.", 20, 1, new MultiHitAttr(MultiHitType._2)),
new Move(Moves.POISON_STING, "Poison Sting", Type.POISON, MoveCategory.PHYSICAL, 15, 100, 35, -1, "May poison the opponent.", 30, 1, new StatusEffectAttr(StatusEffect.POISON)),
new Move(Moves.TWINEEDLE, "Twineedle", Type.BUG, MoveCategory.PHYSICAL, 25, 100, 20, -1, "Hits twice in one turn. May poison opponent.", 20, 1, new MultiHitAttr(MultiHitType._2), new StatusEffectAttr(StatusEffect.POISON)),
new Move(Moves.PIN_MISSILE, "Pin Missile", Type.BUG, MoveCategory.PHYSICAL, 25, 95, 20, -1, "Hits 2-5 times in one turn.", -1, 1,new MultiHitAttr()),
new Move(Moves.LEER, "Leer", Type.NORMAL, MoveCategory.STATUS, -1, 100, 30, -1, "Lowers opponent's Defense.", 100, 1, new StatChangeAttr(BattleStat.DEF, -1)),
new Move(Moves.BITE, "Bite", Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, "May cause flinching.", 30, 1, new FlinchAttr()),
new Move(Moves.GROWL, "Growl", Type.NORMAL, MoveCategory.STATUS, -1, 100, 40, -1, "Lowers opponent's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, -1)),
new Move(Moves.ROAR, "Roar", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 1),
new Move(Moves.SING, "Sing", Type.NORMAL, MoveCategory.STATUS, -1, 55, 15, -1, "Puts opponent to sleep.", -1, 1),
new Move(Moves.SING, "Sing", Type.NORMAL, MoveCategory.STATUS, -1, 55, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
new Move(Moves.SUPERSONIC, "Supersonic", Type.NORMAL, MoveCategory.STATUS, -1, 55, 20, -1, "Confuses opponent.", -1, 1),
new Move(Moves.SONIC_BOOM, "Sonic Boom", Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, "Always inflicts 20 HP.", -1, 1),
new Move(Moves.DISABLE, "Disable", Type.NORMAL, MoveCategory.STATUS, -1, 100, 20, -1, "Opponent can't use its last attack for a few turns.", -1, 1),
new Move(Moves.ACID, "Acid", Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)),
new Move(Moves.EMBER, "Ember", Type.FIRE, MoveCategory.SPECIAL, 40, 100, 25, -1, "May burn opponent.", 10, 1),
new Move(Moves.FLAMETHROWER, "Flamethrower", Type.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 125, "May burn opponent.", 10, 1),
new Move(Moves.EMBER, "Ember", Type.FIRE, MoveCategory.SPECIAL, 40, 100, 25, -1, "May burn opponent.", 10, 1, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.FLAMETHROWER, "Flamethrower", Type.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 125, "May burn opponent.", 10, 1, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.MIST, "Mist", Type.ICE, MoveCategory.STATUS, -1, -1, 30, -1, "User's stats cannot be changed for a period of time.", -1, 1),
new Move(Moves.WATER_GUN, "Water Gun", Type.WATER, MoveCategory.SPECIAL, 40, 100, 25, -1, "", -1, 1),
new Move(Moves.HYDRO_PUMP, "Hydro Pump", Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, 142, "", -1, 1),
new Move(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 1),
new Move(Moves.ICE_BEAM, "Ice Beam", Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 135, "May freeze opponent.", 10, 1),
new Move(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 1),
new Move(Moves.ICE_BEAM, "Ice Beam", Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 135, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)),
new Move(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)),
new Move(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 1),
new Move(Moves.BUBBLE_BEAM, "Bubble Beam", Type.WATER, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)),
new Move(Moves.AURORA_BEAM, "Aurora Beam", Type.ICE, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Attack.", 10, 1, new StatChangeAttr(BattleStat.ATK, -1)),
@ -838,9 +843,9 @@ export const allMoves = [
new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], 1, true)),
new Move(Moves.RAZOR_LEAF, "Razor Leaf", Type.GRASS, MoveCategory.PHYSICAL, 55, 95, 25, -1, "High critical hit ratio.", -1, 1),
new Move(Moves.SOLAR_BEAM, "Solar Beam", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, 168, "Charges on first turn, attacks on second.", -1, 1),
new Move(Moves.POISON_POWDER, "Poison Powder", Type.POISON, MoveCategory.STATUS, -1, 75, 35, -1, "Poisons opponent.", -1, 1),
new Move(Moves.STUN_SPORE, "Stun Spore", Type.GRASS, MoveCategory.STATUS, -1, 75, 30, -1, "Paralyzes opponent.", -1, 1),
new Move(Moves.SLEEP_POWDER, "Sleep Powder", Type.GRASS, MoveCategory.STATUS, -1, 75, 15, -1, "Puts opponent to sleep.", -1, 1),
new Move(Moves.POISON_POWDER, "Poison Powder", Type.POISON, MoveCategory.STATUS, -1, 75, 35, -1, "Poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.POISON)),
new Move(Moves.STUN_SPORE, "Stun Spore", Type.GRASS, MoveCategory.STATUS, -1, 75, 30, -1, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.SLEEP_POWDER, "Sleep Powder", Type.GRASS, MoveCategory.STATUS, -1, 75, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
new Move(Moves.PETAL_DANCE, "Petal Dance", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1),
new Move(Moves.STRING_SHOT, "String Shot", Type.BUG, MoveCategory.STATUS, -1, 95, 40, -1, "Sharply lowers opponent's Speed.", -1, 1, new StatChangeAttr(BattleStat.SPD, -2)),
new Move(Moves.DRAGON_RAGE, "Dragon Rage", Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, "Always inflicts 40 HP.", -1, 1),
@ -944,7 +949,7 @@ export const allMoves = [
new Move(Moves.COTTON_SPORE, "Cotton Spore", Type.GRASS, MoveCategory.STATUS, -1, 100, 40, -1, "Sharply lowers opponent's Speed.", -1, 2, new StatChangeAttr(BattleStat.SPD, -2)),
new Move(Moves.REVERSAL, "Reversal", Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 15, 134, "The lower the user's HP, the higher the power.", -1, 2),
new Move(Moves.SPITE, "Spite", Type.GHOST, MoveCategory.STATUS, -1, 100, 10, -1, "The opponent's last move loses 2-5 PP.", -1, 2),
new Move(Moves.POWDER_SNOW, "Powder Snow", Type.ICE, MoveCategory.SPECIAL, 40, 100, 25, -1, "May freeze opponent.", 10, 2),
new Move(Moves.POWDER_SNOW, "Powder Snow", Type.ICE, MoveCategory.SPECIAL, 40, 100, 25, -1, "May freeze opponent.", 10, 2, new StatusEffectAttr(StatusEffect.FREEZE)),
new Move(Moves.PROTECT, "Protect", Type.NORMAL, MoveCategory.STATUS, -1, -1, 10, 7, "Protects the user, but may fail if used consecutively.", -1, 2),
new Move(Moves.MACH_PUNCH, "Mach Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, "User attacks first.", -1, 2),
new Move(Moves.SCARY_FACE, "Scary Face", Type.NORMAL, MoveCategory.STATUS, -1, 100, 10, 6, "Sharply lowers opponent's Speed.", -1, 2, new StatChangeAttr(BattleStat.SPD, -2)),
@ -1047,7 +1052,7 @@ export const allMoves = [
new Move(Moves.RECYCLE, "Recycle", Type.NORMAL, MoveCategory.STATUS, -1, -1, 10, -1, "User's used hold item is restored.", -1, 3),
new Move(Moves.REVENGE, "Revenge", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "Power increases if user was hit first.", -1, 3),
new Move(Moves.BRICK_BREAK, "Brick Break", Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, 58, "Breaks through Reflect and Light Screen barriers.", -1, 3),
new Move(Moves.YAWN, "Yawn", Type.NORMAL, MoveCategory.STATUS, -1, -1, 10, -1, "Puts opponent to sleep in the next turn.", -1, 3),
new Move(Moves.YAWN, "Yawn", Type.NORMAL, MoveCategory.STATUS, -1, -1, 10, -1, "Puts opponent to sleep in the next turn.", -1, 3), // TODO
new Move(Moves.KNOCK_OFF, "Knock Off", Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, "Removes opponent's held item for the rest of the battle.", -1, 3),
new Move(Moves.ENDEAVOR, "Endeavor", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, "Reduces opponent's HP to same as user's.", -1, 3),
new Move(Moves.ERUPTION, "Eruption", Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, "Stronger when the user's HP is higher.", -1, 3),
@ -1168,11 +1173,11 @@ export const allMoves = [
new Move(Moves.HEART_SWAP, "Heart Swap", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 10, -1, "Stat changes are swapped with the opponent.", -1, 4),
new Move(Moves.AQUA_RING, "Aqua Ring", Type.WATER, MoveCategory.STATUS, -1, -1, 20, -1, "Restores a little HP each turn.", -1, 4),
new Move(Moves.MAGNET_RISE, "Magnet Rise", Type.ELECTRIC, MoveCategory.STATUS, -1, -1, 10, -1, "User becomes immune to Ground-type moves for 5 turns.", -1, 4),
new Move(Moves.FLARE_BLITZ, "Flare Blitz", Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 165, "User receives recoil damage. May burn opponent.", 10, 4),
new Move(Moves.FORCE_PALM, "Force Palm", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "May paralyze opponent.", 30, 4),
new Move(Moves.FLARE_BLITZ, "Flare Blitz", Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 165, "User receives recoil damage. May burn opponent.", 10, 4, new StatusEffectAttr(StatusEffect.BURN)), // TODO
new Move(Moves.FORCE_PALM, "Force Palm", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "May paralyze opponent.", 30, 4, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.AURA_SPHERE, "Aura Sphere", Type.FIGHTING, MoveCategory.SPECIAL, 80, 999, 20, 112, "Ignores Accuracy and Evasiveness.", -1, 4),
new Move(Moves.ROCK_POLISH, "Rock Polish", Type.ROCK, MoveCategory.STATUS, -1, -1, 20, -1, "Sharply raises user's Speed.", -1, 4, new StatChangeAttr(BattleStat.SPD, 2, true)),
new Move(Moves.POISON_JAB, "Poison Jab", Type.POISON, MoveCategory.PHYSICAL, 80, 100, 20, 83, "May poison the opponent.", 30, 4),
new Move(Moves.POISON_JAB, "Poison Jab", Type.POISON, MoveCategory.PHYSICAL, 80, 100, 20, 83, "May poison the opponent.", 30, 4, new StatusEffectAttr(StatusEffect.POISON)),
new Move(Moves.DARK_PULSE, "Dark Pulse", Type.DARK, MoveCategory.SPECIAL, 80, 100, 15, 94, "May cause flinching.", 20, 4, new FlinchAttr()),
new Move(Moves.NIGHT_SLASH, "Night Slash", Type.DARK, MoveCategory.PHYSICAL, 70, 100, 15, -1, "High critical hit ratio.", -1, 4),
new Move(Moves.AQUA_TAIL, "Aqua Tail", Type.WATER, MoveCategory.PHYSICAL, 90, 90, 10, -1, "", -1, 4),
@ -1209,8 +1214,8 @@ export const allMoves = [
new Move(Moves.DEFOG, "Defog", Type.FLYING, MoveCategory.STATUS, -1, -1, 15, -1, "Lowers opponent's Evasiveness and clears fog.", -1, 4, new StatChangeAttr(BattleStat.EVA, -1)), // TODO
new Move(Moves.TRICK_ROOM, "Trick Room", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 5, 161, "Slower Pokémon move first in the turn for 5 turns.", -1, 4),
new Move(Moves.DRACO_METEOR, "Draco Meteor", Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, 169, "Sharply lowers user's Special Attack.", 100, 4, new StatChangeAttr(BattleStat.SPATK, -2, true)),
new Move(Moves.DISCHARGE, "Discharge", Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, -1, "May paralyze opponent.", 30, 4),
new Move(Moves.LAVA_PLUME, "Lava Plume", Type.FIRE, MoveCategory.SPECIAL, 80, 100, 15, -1, "May burn opponent.", 30, 4),
new Move(Moves.DISCHARGE, "Discharge", Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, -1, "May paralyze opponent.", 30, 4, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.LAVA_PLUME, "Lava Plume", Type.FIRE, MoveCategory.SPECIAL, 80, 100, 15, -1, "May burn opponent.", 30, 4, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.LEAF_STORM, "Leaf Storm", Type.GRASS, MoveCategory.SPECIAL, 130, 90, 5, 159, "Sharply lowers user's Special Attack.", 100, 4, new StatChangeAttr(BattleStat.SPATK, -2, true)),
new Move(Moves.POWER_WHIP, "Power Whip", Type.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, "", -1, 4),
new Move(Moves.ROCK_WRECKER, "Rock Wrecker", Type.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, "User must recharge next turn.", -1, 4),
@ -1240,7 +1245,7 @@ export const allMoves = [
new Move(Moves.LUNAR_DANCE, "Lunar Dance", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 10, -1, "The user faints but the next Pokémon released is fully healed.", -1, 4),
new Move(Moves.CRUSH_GRIP, "Crush Grip", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, "More powerful when opponent has higher HP.", -1, 4),
new Move(Moves.MAGMA_STORM, "Magma Storm", Type.FIRE, MoveCategory.SPECIAL, 100, 75, 5, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 4),
new Move(Moves.DARK_VOID, "Dark Void", Type.DARK, MoveCategory.STATUS, -1, 50, 10, -1, "Puts all adjacent opponents to sleep.", -1, 4),
new Move(Moves.DARK_VOID, "Dark Void", Type.DARK, MoveCategory.STATUS, -1, 50, 10, -1, "Puts all adjacent opponents to sleep.", -1, 4, new StatusEffectAttr(StatusEffect.SLEEP)),
new Move(Moves.SEED_FLARE, "Seed Flare", Type.GRASS, MoveCategory.SPECIAL, 120, 85, 5, -1, "May lower opponent's Special Defense.", 40, 4, new StatChangeAttr(BattleStat.SPDEF, -1)),
new Move(Moves.OMINOUS_WIND, "Ominous Wind", Type.GHOST, MoveCategory.SPECIAL, 60, 100, 5, -1, "May raise all user's stats at once.", 10, 4,
new StatChangeAttr([ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)),
@ -1283,7 +1288,7 @@ export const allMoves = [
new Move(Moves.STORED_POWER, "Stored Power", Type.PSYCHIC, MoveCategory.SPECIAL, 20, 100, 10, 41, "Power increases when user's stats have been raised.", -1, 5),
new Move(Moves.QUICK_GUARD, "Quick Guard", Type.FIGHTING, MoveCategory.STATUS, -1, -1, 15, -1, "Protects the user's team from high-priority moves.", -1, 5),
new Move(Moves.ALLY_SWITCH, "Ally Switch", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 15, -1, "User switches with opposite teammate.", -1, 5),
new Move(Moves.SCALD, "Scald", Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, "May burn opponent.", 30, 5),
new Move(Moves.SCALD, "Scald", Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, "May burn opponent.", 30, 5, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.SHELL_SMASH, "Shell Smash", Type.NORMAL, MoveCategory.STATUS, -1, -1, 15, -1, "Sharply raises user's Attack, Special Attack and Speed but lowers Defense and Special Defense.", -1, 5,
new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], 2, true), new StatChangeAttr([ BattleStat.DEF, BattleStat.SPDEF ], -1, true)),
new Move(Moves.HEAL_PULSE, "Heal Pulse", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 10, -1, "Restores half the target's max HP.", -1, 5),
@ -1330,14 +1335,14 @@ export const allMoves = [
new Move(Moves.GEAR_GRIND, "Gear Grind", Type.STEEL, MoveCategory.PHYSICAL, 50, 85, 15, -1, "Hits twice in one turn.", -1, 5, new MultiHitAttr(MultiHitType._2)),
new Move(Moves.SEARING_SHOT, "Searing Shot", Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, "May burn opponent.", 30, 5, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.TECHNO_BLAST, "Techno Blast", Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, "Type depends on the Drive being held.", -1, 5),
new Move(Moves.RELIC_SONG, "Relic Song", Type.NORMAL, MoveCategory.SPECIAL, 75, 100, 10, -1, "May put the target to sleep.", 10, 5),
new Move(Moves.RELIC_SONG, "Relic Song", Type.NORMAL, MoveCategory.SPECIAL, 75, 100, 10, -1, "May put the target to sleep.", 10, 5, new StatusEffectAttr(StatusEffect.SLEEP)),
new Move(Moves.SECRET_SWORD, "Secret Sword", Type.FIGHTING, MoveCategory.SPECIAL, 85, 100, 10, -1, "Inflicts damage based on the target's Defense, not Special Defense.", -1, 5),
new Move(Moves.GLACIATE, "Glaciate", Type.ICE, MoveCategory.SPECIAL, 65, 95, 10, -1, "Lowers opponent's Speed.", 100, 5, new StatChangeAttr(BattleStat.SPD, -1)),
new Move(Moves.BOLT_STRIKE, "Bolt Strike", Type.ELECTRIC, MoveCategory.PHYSICAL, 130, 85, 5, -1, "May paralyze opponent.", 20, 5, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.BLUE_FLARE, "Blue Flare", Type.FIRE, MoveCategory.SPECIAL, 130, 85, 5, -1, "May burn opponent.", 20, 5, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.FIERY_DANCE, "Fiery Dance", Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, -1, "May raise user's Special Attack.", 50, 5, new StatChangeAttr(BattleStat.SPATK, 1, true)),
new Move(Moves.FREEZE_SHOCK, "Freeze Shock", Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May paralyze opponent.", 30, 5),
new Move(Moves.ICE_BURN, "Ice Burn", Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May burn opponent.", 30, 5),
new Move(Moves.FREEZE_SHOCK, "Freeze Shock", Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May paralyze opponent.", 30, 5, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.ICE_BURN, "Ice Burn", Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May burn opponent.", 30, 5, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.SNARL, "Snarl", Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 30, "Lowers opponent's Special Attack.", 100, 5, new StatChangeAttr(BattleStat.SPATK, -1)),
new Move(Moves.ICICLE_CRASH, "Icicle Crash", Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, -1, "May cause flinching.", 30, 5, new FlinchAttr()),
new Move(Moves.V_CREATE, "V-create", Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, "Lowers user's Defense, Special Defense and Speed.", 100, 5,

View File

@ -5,14 +5,14 @@ import { default as Move, allMoves, MoveCategory, Moves, StatChangeAttr, applyMo
import { pokemonLevelMoves } from './pokemon-level-moves';
import { default as PokemonSpecies, getPokemonSpecies } from './pokemon-species';
import * as Utils from './utils';
import { getTypeDamageMultiplier } from './type';
import { Type, getTypeDamageMultiplier } from './type';
import { getLevelTotalExp } from './exp';
import { Stat } from './pokemon-stat';
import { PokemonBaseStatModifier as PokemonBaseStatBoosterModifier, ShinyRateBoosterModifier } from './modifier';
import { PokeballType } from './pokeball';
import { Gender } from './gender';
import { initAnim, loadMoveAnimAssets } from './battle-anims';
import { StatusEffect } from './status-effect';
import { initMoveAnim, loadMoveAnimAssets } from './battle-anims';
import { Status, StatusEffect } from './status-effect';
import { tmSpecies } from './tms';
import { pokemonEvolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './pokemon-evolutions';
import { MessagePhase, StatChangePhase } from './battle-phases';
@ -33,7 +33,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public stats: integer[];
public ivs: integer[];
public moveset: PokemonMove[];
public status: StatusEffect;
public status: Status;
public winCount: integer;
public summonData: PokemonSummonData;
@ -109,8 +109,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/*else
this.shiny = Utils.randInt(16) === 0;*/
this.status = StatusEffect.NONE;
this.winCount = 0;
}
@ -162,7 +160,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
loadAssets(): Promise<void> {
return new Promise(resolve => {
const moveIds = this.moveset.map(m => m.getMove().id);
Promise.allSettled(moveIds.map(m => initAnim(m)))
Promise.allSettled(moveIds.map(m => initMoveAnim(m)))
.then(() => {
loadMoveAnimAssets(this.scene as BattleScene, moveIds);
(this.scene as BattleScene).loadAtlas(this.getSpriteKey(), 'pokemon', this.getSpriteAtlasPath());
@ -258,7 +256,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (stat === Stat.HP)
return this.stats[Stat.HP];
const statLevel = this.summonData.battleStats[(stat + 1) as BattleStat];
return this.stats[stat] * (Math.max(2, 2 + statLevel) / Math.max(2, 2 - statLevel));
let ret = this.stats[stat] * (Math.max(2, 2 + statLevel) / Math.max(2, 2 - statLevel));
if (this.status && this.status.effect === StatusEffect.PARALYSIS)
ret >>= 2;
return ret;
}
calculateStats(): void {
@ -436,6 +437,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const typeMultiplier = getTypeDamageMultiplier(move.type, this.species.type1) * (this.species.type2 > -1 ? getTypeDamageMultiplier(move.type, this.species.type2) : 1);
const criticalMultiplier = isCritical ? 2 : 1;
damage = Math.ceil(((((2 * source.level / 5 + 2) * move.power * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier;
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN)
damage = Math.floor(damage / 2);
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
if (damage) {
this.hp = Math.max(this.hp - damage, 0);
@ -503,8 +506,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
callback();
}
cry(soundConfig?: Phaser.Types.Sound.SoundConfig) {
cry(soundConfig?: Phaser.Types.Sound.SoundConfig): integer {
this.scene.sound.play(this.species.speciesId.toString(), soundConfig);
return this.scene.sound.get(this.species.speciesId.toString()).totalDuration * 1000;
}
faintCry(callback: Function) {
@ -555,7 +559,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
});
// Failsafe
this.scene.time.delayedCall(5000, () => {
this.scene.time.delayedCall(3000, () => {
if (!faintCryTimer || !this.scene)
return;
const crySound = this.scene.sound.get(key);
@ -567,16 +571,42 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
});
}
resetSummonData() {
trySetStatus(effect: StatusEffect): boolean {
if (this.status)
return false;
switch (effect) {
case StatusEffect.POISON:
case StatusEffect.TOXIC:
if (this.species.isOfType(Type.POISON) || this.species.isOfType(Type.STEEL))
return false;
break;
case StatusEffect.FREEZE:
if (this.species.isOfType(Type.ICE))
return false;
break;
case StatusEffect.BURN:
if (this.species.isOfType(Type.FIRE))
return false;
break;
}
this.status = new Status(effect);
return true;
}
resetStatus(): void {
this.status = undefined;
}
resetSummonData(): void {
this.summonData = new PokemonSummonData();
this.resetBattleSummonData();
}
resetBattleSummonData() {
resetBattleSummonData(): void {
this.battleSummonData = new PokemonBattleSummonData();
}
resetTurnData() {
resetTurnData(): void {
this.turnData = new PokemonTurnData();
}
@ -815,6 +845,7 @@ export class PokemonTurnData {
public flinched: boolean;
public hitCount: integer;
public hitsLeft: integer;
public hitsTotal: integer;
}
export enum AiType {

View File

@ -1,3 +1,5 @@
import * as Utils from "./utils";
export enum StatusEffect {
NONE,
POISON,
@ -9,10 +11,95 @@ export enum StatusEffect {
}
export class Status {
public statusType: StatusEffect;
public effect: StatusEffect;
public turnCount: integer;
public cureTurn: integer;
constructor(statusType: StatusEffect) {
this.statusType = statusType;
constructor(effect: StatusEffect) {
this.effect = effect;
this.turnCount = 0;
if (effect === StatusEffect.SLEEP)
this.cureTurn = Utils.randInt(3, 1);
}
incrementTurn(): void {
this.turnCount++;
}
isPostTurn(): boolean {
return this.effect === StatusEffect.POISON || this.effect === StatusEffect.TOXIC || this.effect === StatusEffect.BURN;
}
}
export function getStatusEffectObtainText(statusEffect: StatusEffect): string {
switch (statusEffect) {
case StatusEffect.POISON:
return '\nwas poisoned!';
case StatusEffect.TOXIC:
return '\nwas badly poisoned!';
case StatusEffect.PARALYSIS:
return ' is paralyzed!\nIt may be unable to move!';
case StatusEffect.SLEEP:
return '\nfell asleep!';
case StatusEffect.FREEZE:
return '\nwas frozen solid!';
case StatusEffect.BURN:
return '\nwas burned!';
}
return '';
}
export function getStatusEffectActivationText(statusEffect: StatusEffect): string {
switch (statusEffect) {
case StatusEffect.POISON:
case StatusEffect.TOXIC:
return ' is hurt\nby poison!';
case StatusEffect.PARALYSIS:
return ' is paralyzed!\nIt can\'t move!';
case StatusEffect.SLEEP:
return ' is fast asleep.';
case StatusEffect.FREEZE:
return ' is\nfrozen solid!';
case StatusEffect.BURN:
return ' is hurt\nby its burn!';
}
return '';
}
export function getStatusEffectOverlapText(statusEffect: StatusEffect): string {
switch (statusEffect) {
case StatusEffect.POISON:
case StatusEffect.TOXIC:
return ' is\nalready poisoned!';
case StatusEffect.PARALYSIS:
return ' is\nalready paralyzed!';
case StatusEffect.SLEEP:
return ' is\nalready asleep!';
case StatusEffect.FREEZE:
return ' is\nalready frozen!';
case StatusEffect.BURN:
return ' is\nalready burned!';
}
return '';
}
export function getStatusEffectHealText(statusEffect: StatusEffect) {
switch (statusEffect) {
case StatusEffect.POISON:
case StatusEffect.TOXIC:
return ' was\ncured of its poison!';
case StatusEffect.PARALYSIS:
return ' was\nhealed of paralysis!';
case StatusEffect.SLEEP:
return ' woke up!';
case StatusEffect.FREEZE:
return ' was\ndefrosted!';
case StatusEffect.BURN:
return ' is hurt\nby its burn!';
}
return '';
}