Add Pokemon Reborn move animations

This commit is contained in:
Flashfyre committed 2023-04-03 20:47:41 -04:00
1 parent 5f845839af
commit 1cf81869d0
2497 files changed
+3777463 -114

No files matched your search

-9
View File
@@ -1,9 +0,0 @@
import BattleScene from "./battle-scene";
export class BattleAnimHandler {
private scene: BattleScene;
constructor(scene: BattleScene) {
this.scene = scene;
}
}
+578
View File
@@ -0,0 +1,578 @@
//import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "./battle-scene";
import { Moves } from "./move";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./pokemon";
import * as Utils from "./utils";
import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget {
USER,
TARGET,
GRAPHIC
}
enum AnimFocus {
USER = 1,
TARGET,
USER_TARGET,
SCREEN
}
export class Anim {
public id: integer;
public graphic: string;
public frames: AnimFrame[][];
public frameTimedEvents: Map<integer, AnimTimedEvent[]>;
public position: integer;
public hue: integer;
constructor(source?: any) {
this.frameTimedEvents = new Map<integer, AnimTimedEvent[]>;
if (source) {
this.id = source.id;
this.graphic = source.graphic;
this.frames = source.frames;
const frameTimedEvents = source.frameTimedEvents;
for (let fte of Object.keys(frameTimedEvents)) {
const timedEvents: AnimTimedEvent[] = [];
for (let te of frameTimedEvents[fte]) {
let timedEvent: AnimTimedEvent;
switch (te.eventType) {
case 'AnimTimedSoundEvent':
timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te);
break;
case 'AnimTimedAddBgEvent':
timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te);
break;
case 'AnimTimedUpdateBgEvent':
timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te);
break;
}
timedEvents.push(timedEvent);
}
this.frameTimedEvents.set(parseInt(fte), timedEvents);
}
this.position = source.position;
this.hue = source.hue;
} else
this.frames = [];
}
getSoundResourceNames(): string[] {
const sounds = new Set<string>();
for (let ftes of this.frameTimedEvents.values()) {
for (let fte of ftes) {
if (fte instanceof AnimTimedSoundEvent && fte.resourceName)
sounds.add(fte.resourceName);
}
}
return Array.from(sounds.values());
}
getBackgroundResourceNames(): string[] {
const backgrounds = new Set<string>();
for (let ftes of this.frameTimedEvents.values()) {
for (let fte of ftes) {
if (fte instanceof AnimTimedAddBgEvent && fte.resourceName)
backgrounds.add(fte.resourceName);
}
}
return Array.from(backgrounds.values());
}
}
class AnimFrame {
public x: number;
public y: number;
public zoomX: number;
public zoomY: number;
public angle: number;
public mirror: boolean;
public visible: boolean;
public blendType: integer;
public target: AnimFrameTarget;
public graphicFrame: integer;
public opacity: integer;
public color: integer[];
public tone: integer[];
public flash: integer[];
public locked: boolean;
public priority: integer;
public focus: AnimFocus;
constructor(x: number, y: number, zoomX: number, zoomY: number, angle: number, mirror: boolean, visible: boolean, blendType: integer, pattern: integer,
opacity: integer, colorR: integer, colorG: integer, colorB: integer, colorA: integer, toneR: integer, toneG: integer, toneB: integer, toneA: integer,
flashR: integer, flashG: integer, flashB: integer, flashA: integer, locked: boolean, priority: integer, focus: AnimFocus) {
this.x = x;
this.y = y;
this.zoomX = zoomX;
this.zoomY = zoomY;
this.angle = angle;
this.mirror = mirror;
this.visible = visible;
this.blendType = blendType;
let target = AnimFrameTarget.GRAPHIC;
switch (pattern) {
case -2:
target = AnimFrameTarget.TARGET;
this.x -= 384;
this.y -= 96;
break;
case -1:
target = AnimFrameTarget.USER;
this.x -= 128;
this.y -= 224;
break;
default:
this.x = (this.x - 128) * 0.5;
this.y = (this.y - 224) * 0.5;
}
this.target = target;
this.graphicFrame = pattern >= 0 ? pattern : 0;
this.opacity = opacity;
this.color = [ colorR, colorG, colorB, colorA ];
this.tone = [ toneR, toneG, toneB, toneA ];
this.flash = [ flashR, flashG, flashB, flashA ];
this.locked = locked;
this.priority = priority;
this.focus = focus;
}
}
abstract class AnimTimedEvent {
public frameIndex: integer;
public resourceName: string;
constructor(frameIndex: integer, resourceName: string) {
this.frameIndex = frameIndex;
this.resourceName = resourceName;
}
abstract execute(scene: BattleScene, moveAnim: MoveAnim): void;
abstract getEventType(): string;
}
class AnimTimedSoundEvent extends AnimTimedEvent {
public volume: number;
public pitch: number;
constructor(frameIndex: integer, resourceName: string, source?: any) {
super(frameIndex, resourceName);
if (source) {
this.volume = source.volume;
this.pitch = source.pitch;
} else
this.pitch = 100;
}
execute(scene: BattleScene, moveAnim: MoveAnim): void {
const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) };
if (this.resourceName)
scene.sound.play(this.resourceName, soundConfig);
else
moveAnim.user.cry(soundConfig);
}
getEventType(): string {
return 'AnimTimedSoundEvent';
}
}
abstract class AnimTimedBgEvent extends AnimTimedEvent {
public bgX: number;
public bgY: number;
public opacity: integer;
public colorRed: integer;
public colorGreen: integer;
public colorBlue: integer;
public colorAlpha: integer;
public duration: integer;
public flashScope: integer;
public flashRed: integer;
public flashGreen: integer;
public flashBlue: integer;
public flashAlpha: integer;
public flashDuration: integer;
constructor(frameIndex: integer, resourceName: string, source: any) {
super(frameIndex, resourceName);
if (source) {
this.bgX = source.bgX;
this.bgY = source.bgY;
this.opacity = source.opacity;
this.colorRed = source.colorRed;
this.colorGreen = source.colorGreen;
this.colorBlue = source.colorBlue;
this.colorAlpha = source.colorAlpha;
this.duration = source.duration;
this.flashScope = source.flashScope;
this.flashRed = source.flashRed;
this.flashGreen = source.flashGreen;
this.flashBlue = source.flashBlue;
this.flashAlpha = source.flashAlpha;
this.flashDuration = source.flashDuration;
}
}
}
class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
constructor(frameIndex: integer, resourceName: string, source?: any) {
super(frameIndex, resourceName, source);
}
execute(scene: BattleScene, moveAnim: MoveAnim): void {
const tweenProps = {};
if (this.bgX !== undefined)
tweenProps['x'] = (this.bgX * 0.5) - 256;
if (this.bgY !== undefined)
tweenProps['y'] = (this.bgY * 0.5) - 284;
if (this.opacity !== undefined)
tweenProps['alpha'] = this.opacity / 255;
if (Object.keys(tweenProps).length) {
scene.tweens.add(Object.assign({
targets: moveAnim.bgSprite,
duration: this.duration * 2,
useFrames: true
}, tweenProps))
}
}
getEventType(): string {
return 'AnimTimedUpdateBgEvent';
}
}
class AnimTimedAddBgEvent extends AnimTimedBgEvent {
constructor(frameIndex: integer, resourceName: string, source?: any) {
super(frameIndex, resourceName, source);
}
execute(scene: BattleScene, moveAnim: MoveAnim): void {
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);
moveAnim.bgSprite.setAlpha(0);
scene.field.add(moveAnim.bgSprite);
scene.field.moveBelow(moveAnim.bgSprite, scene.getEnemyPokemon());
scene.tweens.add({
targets: moveAnim.bgSprite,
alpha: 1,
duration: this.duration * 2,
useFrames: true
});
}
getEventType(): string {
return 'AnimTimedAddBgEvent';
}
}
export const moveAnims = new Map<Moves, Anim | [Anim, Anim]>();
export function initAnim(move: Moves): Promise<void> {
return new Promise(resolve => {
if (moveAnims.has(move)) {
if (moveAnims.get(move) !== null)
resolve();
else {
let loadedCheckTimer = setInterval(() => {
if (moveAnims.get(move) !== null) {
clearInterval(loadedCheckTimer);
resolve();
}
}, 50);
}
} else {
moveAnims.set(move, null);
fetch(`./battle-anims/${Moves[move].toLowerCase().replace(/\_/g, '-')}.json`)
.then(response => response.json())
.then(ba => {
if (Array.isArray(ba)) {
populateMoveAnim(move, ba[0]);
populateMoveAnim(move, ba[1]);
} else
populateMoveAnim(move, ba);
resolve();
});
}
});
}
function populateMoveAnim(move: Moves, animSource: any) {
const moveAnim = new Anim(animSource);
if (moveAnims.get(move) === null) {
moveAnims.set(move, moveAnim);
return;
}
moveAnims.set(move, [ moveAnims.get(move) as Anim, moveAnim ]);
}
export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
const moveAnimations = moveIds.map(m => {
const anims = moveAnims.get(m);
if (anims instanceof Anim)
return anims as Anim;
return anims[0] as Anim;
});
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 bg of backgrounds)
scene.loadImage(bg, 'battle_anims');
for (let s of sounds)
scene.loadSe(s, 'battle_anims', s);
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve());
if (startLoad && !scene.load.isLoading())
scene.load.start();
});
}
export class MoveAnim {
public move: Moves;
public user: Pokemon;
public target: Pokemon;
public moveSprites: Phaser.GameObjects.Sprite[];
public bgSprite: Phaser.GameObjects.TileSprite;
constructor(move: Moves, user: Pokemon, target: Pokemon) {
this.move = move;
this.user = user;
this.target = target;
this.moveSprites = [];
}
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 userInitialX = this.user.x;
const userInitialY = this.user.y;
const userHalfHeight = this.user.getSprite().displayHeight / 2;
const targetInitialX = this.target.x;
const targetInitialY = this.target.y;
const targetHalfHeight = this.target.getSprite().displayHeight / 2;
const coordMultiplayer = this.user instanceof PlayerPokemon || isOppMove ? 1 : -1;
let f = 0;
const moveSprites: Phaser.GameObjects.Sprite[] = [];
scene.tweens.addCounter({
useFrames: true,
duration: 2,
repeat: anim.frames.length,
onRepeat: () => {
const spriteFrames = anim.frames[f];
let g = 0;
for (let frame of spriteFrames) {
switch (frame.target) {
case AnimFrameTarget.USER:
this.user.setPosition(userInitialX + frame.x * coordMultiplayer, userInitialY + frame.y * coordMultiplayer);
break;
case AnimFrameTarget.TARGET:
this.target.setPosition(targetInitialX + frame.x * coordMultiplayer, targetInitialY + frame.y * coordMultiplayer);
break;
case AnimFrameTarget.GRAPHIC:
if (g === moveSprites.length) {
const newSprite = scene.add.sprite(0, 0, anim.graphic, 1);
scene.field.add(newSprite);
moveSprites.push(newSprite);
}
const moveSprite = moveSprites[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);
moveSprite.setAlpha(frame.opacity);
moveSprite.setAngle(-frame.angle * coordMultiplayer);
break;
}
if (frame.target !== AnimFrameTarget.GRAPHIC) {
const pokemon = frame.target === AnimFrameTarget.USER
? this.user
: this.target;
pokemon.setAlpha(frame.opacity);
pokemon.setAngle(-frame.angle * coordMultiplayer);
const zoomScaleX = frame.zoomX / 100;
const zoomScaleY = frame.zoomY / 100;
const zoomSprite = pokemon.getZoomSprite();
zoomSprite.setY(zoomSprite.displayHeight * (zoomScaleY - 1) * 0.5);
zoomSprite.setScale(zoomScaleX, zoomScaleY);
}
}
if (anim.frameTimedEvents.has(f)) {
for (let event of anim.frameTimedEvents.get(f))
event.execute(scene, this);
}
if (g < moveSprites.length) {
const removedSprites = moveSprites.splice(g, moveSprites.length - g);
for (let rs of removedSprites)
rs.destroy();
}
f++;
},
onComplete: () => {
this.user.setPosition(userInitialX, userInitialY);
this.user.setAlpha(1);
this.user.setAngle(0);
this.target.setPosition(targetInitialX, targetInitialY);
this.target.setAlpha(1);
this.target.setAngle(0);
for (let ms of moveSprites)
ms.destroy();
if (callback)
callback();
}
});
}
}
export function populateAnims() {
return;
const moveNameToId = {};
for (let move of Utils.getEnumValues(Moves)) {
const moveName = Moves[move].toUpperCase().replace(/\_/g, '');
moveNameToId[moveName] = move;
}
const animsData = [];//battleAnimRawData.split('!ruby/array:PBAnimation').slice(1);
for (let a = 0; a < animsData.length; a++) {
const fields = animsData[a].split('@').slice(1);
let isOppMove: boolean;
if (!fields[1].startsWith('name: Move:') && !(isOppMove = fields[1].startsWith('name: OppMove:')))
continue;
const nameIndex = fields[1].indexOf(':', 5) + 1;
const moveName = fields[1].slice(nameIndex, fields[1].indexOf('\n', nameIndex));
if (!moveNameToId.hasOwnProperty(moveName))
continue;
let anim = new Anim();
anim.id = moveNameToId[moveName];
moveAnims.set(moveNameToId[moveName], !isOppMove ? anim : [ moveAnims.get(moveNameToId[moveName]) as Anim, anim ]);
for (let f = 0; f < fields.length; f++) {
const field = fields[f];
const fieldName = field.slice(0, field.indexOf(':'));
const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf('\n')).trim();
switch (fieldName) {
case 'array':
const framesData = fieldData.split(' - - - ').slice(1);
for (let fd = 0; fd < framesData.length; fd++) {
anim.frames.push([]);
const frameData = framesData[fd];
const focusFramesData = frameData.split(' - - ');
for (let tf = 0; tf < focusFramesData.length; tf++) {
const values = focusFramesData[tf].replace(/ \- /g, '').split('\n');
const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]),
values[4] === '1', values[6] === '1', parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]),
parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]),
parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), values[20] === '1', parseInt(values[25]), parseInt(values[26]) as AnimFocus);
anim.frames[fd].push(targetFrame);
}
}
break;
case 'graphic':
anim.graphic = fieldData !== "''" ? fieldData.slice(0, fieldData.indexOf('.')) : '';
break;
case 'timing':
const timingEntries = fieldData.split('- !ruby/object:PBAnimTiming ').slice(1);
for (let t = 0; t < timingEntries.length; t++) {
const timingData = timingEntries[t].replace(/\n/g, ' ').replace(/[ ]{2,}/g, ' ').replace(/[a-z]+: ! '', /ig, '').replace(/name: (.*?),/, 'name: "$1",')
.replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, 'flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1');
const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)[1]);
const resourceName = /name: "(.*?)"/.exec(timingData)[1].replace("''", '');
const timingType = parseInt(/timingType: (\d)/.exec(timingData)[1]);
let timedEvent: AnimTimedEvent;
switch (timingType) {
case 0:
timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName);
break;
case 1:
timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf('.')));
break;
case 2:
timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf('.')));
break;
}
if (!timedEvent)
continue;
const propPattern = /([a-z]+): (.*?)(?:,|\})/ig;
let propMatch: RegExpExecArray;
while ((propMatch = propPattern.exec(timingData))) {
const prop = propMatch[1];
let value: any = propMatch[2];
switch (prop) {
case 'bgX':
case 'bgY':
value = parseFloat(value);
break;
case 'volume':
case 'pitch':
case 'opacity':
case 'colorRed':
case 'colorGreen':
case 'colorBlue':
case 'colorAlpha':
case 'duration':
case 'flashScope':
case 'flashRed':
case 'flashGreen':
case 'flashBlue':
case 'flashAlpha':
case 'flashDuration':
value = parseInt(value);
break;
}
if (timedEvent.hasOwnProperty(prop))
timedEvent[prop] = value;
}
if (!anim.frameTimedEvents.has(frameIndex))
anim.frameTimedEvents.set(frameIndex, []);
anim.frameTimedEvents.get(frameIndex).push(timedEvent);
}
break;
case 'position':
anim.position = parseInt(fieldData);
break;
case 'hue':
anim.hue = parseInt(fieldData);
break;
}
}
}
for (let ma of moveAnims.keys()) {
const data = moveAnims.get(ma);
(async () => {
await fs.writeFile(`./public/battle-anims/${Moves[ma].toLowerCase().replace(/\_/g, '-')}.json`, JSON.stringify(data, (k, v) => {
if (v instanceof Map)
return Object.fromEntries(v);
if (v instanceof AnimTimedEvent)
v['eventType'] = v.getEventType();
return v;
}, ' '));
})();
}
}
-2
View File
@@ -12,7 +12,6 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private lastExp: integer;
private lastLevelExp: integer;
private lastLevel: integer;
private lastName: string;
private nameText: Phaser.GameObjects.Text;
private genderText: Phaser.GameObjects.Text;
@@ -31,7 +30,6 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.lastExp = -1;
this.lastLevelExp = -1;
this.lastLevel = -1;
this.lastName = null;
// Initially invisible and shown via Pokemon.showInfo
this.setVisible(false);
+48 -43
View File
@@ -1,15 +1,15 @@
import BattleScene from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove } from "./pokemon";
import * as Utils from './utils';
import { allMoves, Moves as Move, MoveCategory } from "./move";
import { allMoves, Moves as Move, MoveCategory, Moves } from "./move";
import { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler";
import { interp } from "./temp_interpreter";
import { Stat } from "./pokemon-stat";
import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, getModifierTypesForWave, ModifierType, PokemonModifierType, regenerateModifierPoolThresholds } from "./modifier";
import PartyUiHandler from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor as getPokeballTintColor, PokeballType } from "./pokeball";
import { pokemonLevelMoves } from "./pokemon-level-moves";
import { MoveAnim, loadMoveAnimAssets } from "./battle-anims";
export class BattlePhase {
protected scene: BattleScene;
@@ -49,7 +49,6 @@ export class EncounterPhase extends BattlePhase {
enemyPokemon.tint(0, 0.5);
this.doEncounter();
});
this.scene.load.start();
}
doEncounter() {
@@ -257,7 +256,7 @@ export class SummonPhase extends BattlePhase {
}
end() {
if (this.scene.getEnemyPokemon().shiny)
if (this.scene.getPlayerPokemon().shiny)
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, true));
super.end();
@@ -336,10 +335,7 @@ export class CommandPhase extends BattlePhase {
start() {
super.start();
//interp(this.scene);
this.scene.ui.setMode(Mode.COMMAND);
console.log('add participant', this.scene.getPlayerPokemon().name)
this.scene.currentBattle.addParticipant(this.scene.getPlayerPokemon());
}
@@ -443,11 +439,35 @@ abstract class MovePhase extends BattlePhase {
}
if (!this.move)
console.log(this.pokemon.moveset);
this.scene.ui.showText(`${this.pokemon.name} used\n${this.move.getName()}!`, null, () => this.end(), 500);
this.move.ppUsed++;
if (this.move.getMove().category !== MoveCategory.STATUS)
this.scene.unshiftPhase(this.getEffectPhase());
this.scene.ui.showText(`${this.pokemon.name} used\n${this.move.getName()}!`, null, () => {
this.move.ppUsed++;
if (this.move.getMove().category !== MoveCategory.STATUS) {
if (this.hitCheck())
this.scene.unshiftPhase(this.getEffectPhase());
else
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.pokemon instanceof EnemyPokemon ? 'Foe ' : ''}${this.pokemon.name}'s\nattack missed!`));
} else
this.scene.unshiftPhase(this.getEffectPhase());
this.end();
}, 500);
}
hitCheck(): boolean {
if (this.move.getMove().category !== MoveCategory.STATUS) {
const userAccuracyLevel = 0;
const targetEvasionLevel = 0;
const rand = Utils.randInt(100, 1);
let accuracyMultiplier = 1;
if (userAccuracyLevel !== targetEvasionLevel) {
accuracyMultiplier = userAccuracyLevel > targetEvasionLevel
? (3 + Math.min(userAccuracyLevel - targetEvasionLevel, 6)) / 3
: 3 / (3 + Math.min(targetEvasionLevel - userAccuracyLevel, 6));
}
return rand <= this.move.getMove().accuracy * accuracyMultiplier;
}
return true;
}
}
export class PlayerMovePhase extends MovePhase {
@@ -482,31 +502,14 @@ abstract class MoveEffectPhase extends PokemonPhase {
start() {
super.start();
if (this.hitCheck()) {
this.getTargetPokemon().apply(this.getUserPokemon(), this.move, () => this.end());
if (this.getTargetPokemon().hp <= 0) {
this.scene.pushPhase(new FaintPhase(this.scene, !this.player));
}
} else {
this.scene.unshiftPhase(new MessagePhase(this.scene, `${!this.player ? 'Foe ' : ''}${this.getPokemon().name}'s\nattack missed!`));
this.end();
}
}
const user = this.getUserPokemon();
const target = this.getTargetPokemon();
hitCheck(): boolean {
if (this.move.getMove().category !== MoveCategory.STATUS) {
const userAccuracyLevel = 0;
const targetEvasionLevel = 0;
const rand = Utils.randInt(100, 1);
let accuracyMultiplier = 1;
if (userAccuracyLevel !== targetEvasionLevel) {
accuracyMultiplier = userAccuracyLevel > targetEvasionLevel
? (3 + Math.min(userAccuracyLevel - targetEvasionLevel, 6)) / 3
: 3 / (3 + Math.min(targetEvasionLevel - userAccuracyLevel, 6));
}
return rand <= this.move.getMove().accuracy * accuracyMultiplier;
}
return true;
new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () =>{
this.getTargetPokemon().apply(this.getUserPokemon(), this.move, () => this.end());
if (this.getTargetPokemon().hp <= 0)
this.scene.pushPhase(new FaintPhase(this.scene, !this.player));
});
}
abstract getUserPokemon(): Pokemon;
@@ -524,8 +527,8 @@ export class PlayerMoveEffectPhase extends MoveEffectPhase {
}
getTargetPokemon(): Pokemon {
if (this.move.getMove().category === MoveCategory.STATUS)
return this.getUserPokemon();
/*if (this.move.getMove().category === MoveCategory.STATUS)
return this.getUserPokemon();*/
return this.scene.getEnemyPokemon();
}
}
@@ -540,8 +543,8 @@ export class EnemyMoveEffectPhase extends MoveEffectPhase {
}
getTargetPokemon(): Pokemon {
if (this.move.getMove().category === MoveCategory.STATUS)
return this.getUserPokemon();
/*if (this.move.getMove().category === MoveCategory.STATUS)
return this.getUserPokemon();*/
return this.scene.getPlayerPokemon();
}
}
@@ -743,8 +746,11 @@ export class LearnMovePhase extends PartyMemberPokemonPhase {
if (pokemon.moveset.length < 4) {
pokemon.moveset.push(new PokemonMove(this.moveId, 0, 0));
this.scene.sound.play('level_up_fanfare');
this.scene.ui.showText(`${pokemon.name} learned\n${Utils.toPokemonUpperCase(move.name)}!`, null, () => this.end(), null, true);
loadMoveAnimAssets(this.scene, [ this.moveId ])
.then(() => {
this.scene.sound.play('level_up_fanfare');
this.scene.ui.showText(`${pokemon.name} learned\n${Utils.toPokemonUpperCase(move.name)}!`, null, () => this.end(), null, true);
});
} else
this.end();
}
@@ -827,7 +833,7 @@ export class AttemptCapturePhase extends BattlePhase {
this.pokeball.setAngle(value * 27.5 * directionMultiplier);
}
},
onRepeat: t => {
onRepeat: () => {
if (shakeCount++ < 3) {
if (Utils.randInt(65536) < y)
this.scene.sound.play('pb_move');
@@ -888,7 +894,6 @@ export class AttemptCapturePhase extends BattlePhase {
this.removePb();
this.end();
});
this.scene.load.start();
}, 0, true);
}
+15 -6
View File
@@ -10,6 +10,7 @@ import { PokeballType } from './pokeball';
import { Species } from './species';
import { initAutoPlay } from './auto-play';
import { Battle } from './battle';
import { populateAnims } from './battle-anims';
export default class BattleScene extends Phaser.Scene {
private auto: boolean;
@@ -37,6 +38,8 @@ export default class BattleScene extends Phaser.Scene {
public uiContainer: Phaser.GameObjects.Container;
public ui: UI;
//public spritePipeline: SpritePipeline;
private bgm: Phaser.Sound.BaseSound;
private upKey: Phaser.Input.Keyboard.Key;
@@ -72,6 +75,12 @@ export default class BattleScene extends Phaser.Scene {
this.load.atlas(key, `images/${folder}${filenameRoot}.png`, `images/${folder}/${filenameRoot}.json`)
}
loadSpritesheet(key: string, folder: string, size: integer, filename?: string) {
if (!filename)
filename = `${key}.png`;
this.load.spritesheet(key, `images/${folder}/${filename}`, { frameWidth: size, frameHeight: size });
}
loadSe(key: string, folder?: string, filenames?: string | string[]) {
if (!filenames)
filenames = `${key}.wav`;
@@ -103,7 +112,6 @@ export default class BattleScene extends Phaser.Scene {
this.loadImage('pbinfo_enemy', 'ui');
this.loadImage('overlay_lv', 'ui');
this.loadAtlas('numbers', 'ui');
this.loadAtlas('gender', 'ui');
this.loadAtlas('overlay_hp', 'ui');
this.loadImage('overlay_exp', 'ui');
this.loadImage('level_up_stats', 'ui');
@@ -168,16 +176,18 @@ export default class BattleScene extends Phaser.Scene {
this.loadSe('pb_catch');
this.loadSe('pb_lock');
this.loadSe('m_bubble');
this.loadSe('m_bubble3');
this.loadSe('m_crabhammer');
this.loadBgm('level_up_fanfare');
this.load.glsl('sprite', 'shaders/sprite.frag');
populateAnims();
}
create() {
this.load.setBaseURL();
//this.spritePipeline = (this.renderer as Phaser.Renderer.WebGL.WebGLRenderer).pipelines.get('Sprite') as SpritePipeline;
const field = this.add.container(0, 0);
field.setScale(6);
@@ -279,7 +289,6 @@ export default class BattleScene extends Phaser.Scene {
this.shiftPhase();
});
this.load.start();
}
update() {
+3 -1
View File
@@ -1,8 +1,9 @@
import Phaser from 'phaser';
import BattleScene from './battle-scene';
//import SpritePipeline from './pipelines/sprite';
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
type: Phaser.WEBGL,
parent: 'app',
scale: {
width: 1920,
@@ -10,6 +11,7 @@ const config: Phaser.Types.Core.GameConfig = {
mode: Phaser.Scale.FIT
},
pixelArt: true,
//pipeline: { 'Sprite': SpritePipeline },
scene: [ BattleScene ]
};
+2 -2
View File
@@ -7,9 +7,9 @@ export enum MoveCategory {
};
export default class Move {
public id: integer;
public id: Moves;
public name: string;
public type: integer;
public type: Type;
public category: integer;
public power: integer;
public accuracy: integer;
+46 -21
View File
@@ -12,6 +12,7 @@ import { Stat } from './pokemon-stat';
import { PokemonBaseStatModifier as PokemonBaseStatBoosterModifier, ShinyRateBoosterModifier } from './modifier';
import { PokeballType } from './pokeball';
import { Gender } from './gender';
import { Anim, initAnim, loadMoveAnimAssets, moveAnims } from './battle-anims';
export default abstract class Pokemon extends Phaser.GameObjects.Container {
public id: integer;
@@ -98,6 +99,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.winCount = 0;
}
//this.setPipeline((this.scene as BattleScene).spritePipeline);
this.calculateStats();
(scene as BattleScene).fieldUI.add(this.battleInfo);
@@ -112,11 +115,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const sprite = getSprite();
const tintSprite = getSprite();
const zoomSprite = getSprite();
tintSprite.setVisible(false);
zoomSprite.setAlpha(0.5);
this.add(sprite);
this.add(tintSprite);
this.add(zoomSprite);
if (this.shiny) {
const shinySparkle = this.scene.add.sprite(0, 0, 'shiny');
@@ -140,24 +146,32 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
loadAssets(): Promise<void> {
return new Promise(resolve => {
(this.scene as BattleScene).loadAtlas(this.getSpriteKey(), 'pokemon', this.getAtlasPath());
this.scene.load.audio(this.species.speciesId.toString(), `audio/cry/${this.species.speciesId}.mp3`);
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
const originalWarn = console.warn;
// Ignore warnings for missing frames, because there will be a lot
console.warn = () => {};
const frameNames = this.scene.anims.generateFrameNames(this.getSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 256 });
console.warn = originalWarn;
this.scene.anims.create({
key: this.getSpriteKey(),
frames: frameNames,
frameRate: 12,
repeat: -1
const moveIds = this.moveset.map(m => m.getMove().id);
Promise.allSettled(moveIds.map(m => initAnim(m)))
.then(() => {
loadMoveAnimAssets(this.scene as BattleScene, moveIds);
(this.scene as BattleScene).loadAtlas(this.getSpriteKey(), 'pokemon', this.getAtlasPath());
this.scene.load.audio(this.species.speciesId.toString(), `audio/cry/${this.species.speciesId}.mp3`);
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
const originalWarn = console.warn;
// Ignore warnings for missing frames, because there will be a lot
console.warn = () => {};
const frameNames = this.scene.anims.generateFrameNames(this.getSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 256 });
console.warn = originalWarn;
this.scene.anims.create({
key: this.getSpriteKey(),
frames: frameNames,
frameRate: 12,
repeat: -1
});
this.getSprite().play(this.getSpriteKey());
this.getTintSprite().play(this.getSpriteKey());
this.getZoomSprite().play(this.getSpriteKey());
resolve();
});
if (!this.scene.load.isLoading())
this.scene.load.start();
});
this.getSprite().play(this.getSpriteKey());
this.getTintSprite().play(this.getSpriteKey());
resolve();
});
});
}
@@ -193,6 +207,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.getAt(1) as Phaser.GameObjects.Sprite;
}
getZoomSprite(): Phaser.GameObjects.Sprite {
return this.getAt(2) as Phaser.GameObjects.Sprite;
}
calculateStats() {
if (!this.stats)
this.stats = [ 0, 0, 0, 0, 0, 0 ];
@@ -394,8 +412,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
callback();
}
cry() {
this.scene.sound.play(this.species.speciesId.toString());
cry(soundConfig?: Phaser.Types.Sound.SoundConfig) {
this.scene.sound.play(this.species.speciesId.toString(), soundConfig);
}
faintCry(callback: Function) {
@@ -406,10 +424,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
rate: rate
});
const sprite = this.getSprite();
const tintSprite = this.getTintSprite();
const zoomSprite = this.getZoomSprite();
const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25);
let frameProgress = 0;
let frameThreshold: number;
sprite.anims.pause();
tintSprite.anims.pause();
zoomSprite.anims.pause();
let faintCryTimer = this.scene.time.addEvent({
delay: delay,
repeat: -1,
@@ -418,8 +440,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
frameThreshold = sprite.anims.msPerFrame / rate;
frameProgress += delay;
while (frameProgress > frameThreshold) {
if (sprite.anims.duration)
if (sprite.anims.duration) {
sprite.anims.nextFrame();
tintSprite.anims.nextFrame();
zoomSprite.anims.nextFrame();
}
frameProgress -= frameThreshold;
}
const crySound = this.scene.sound.get(key);
@@ -522,7 +547,7 @@ export class EnemyPokemon extends Pokemon {
public aiType: AiType;
constructor(scene: BattleScene, species: PokemonSpecies, level: integer) {
super(scene, -63, 86, species, level);
super(scene, -66, 84, species, level);
this.aiType = AiType.SMART_RANDOM;
}
+150 -19
View File
@@ -4,6 +4,7 @@ import Pokemon, { PlayerPokemon } from "./pokemon";
var moveAnimsContent;
let funcNames = [];
let templateNames = [];
let animFuncsContents = '';
let animTemplateContents = '';
@@ -14,6 +15,7 @@ var gBattleAnimArgs = [ 0, 0, 0, 0, 0, 0, 0 ];
var gSprites: Sprite[] = [];
var gTasks: Function[] = [];
var gAnimVisualTaskCount: integer = 0;
var onAnimsComplete: Function;
const MAX_BATTLERS_COUNT = 4;
const ANIM_PLAYER_LEFT = 4;
@@ -40,6 +42,8 @@ class SpriteTemplate {
}
class Sprite {
public spriteObj: Phaser.GameObjects.Sprite;
public template: SpriteTemplate;
public callback: Function;
@@ -61,7 +65,9 @@ class Sprite {
public subpriority: integer;
constructor() {
constructor(scene: BattleScene, x: integer, y: integer, image: string) {
this.spriteObj = scene.add.sprite(x, y, image, 0);
scene.field.add(this.spriteObj);
}
};
@@ -71,8 +77,7 @@ class Sprite {
callback(sprite);
}*/
function CreateSprite(template: SpriteTemplate, target: AnimTarget, subpriorityOffset: integer, args: any[])
{
function CreateSprite(template: SpriteTemplate, target: AnimTarget, subpriorityOffset: integer, args: any[]) {
/*s32 i;
const struct SpriteTemplate *template;
u8 argVar;
@@ -134,15 +139,16 @@ function CreateSprite(template: SpriteTemplate, target: AnimTarget, subpriorityO
gAnimVisualTaskCount++;
}
function CreateNewSprite(index: integer, template: SpriteTemplate, x: integer, y: integer, subpriority: integer): integer {
const sprite = new Sprite();
function CreateNewSprite(index: integer, template: SpriteTemplate, x: integer, y: integer, subpriority: integer): Sprite {
const sprite = new Sprite(scene, x, y, template.imageName.toLowerCase());
sprite.subpriority = subpriority;
sprite.template = template;
sprite.callback = template.callback;
sprite.x = x;
sprite.y = y;
sprite.callback();
gSprites[index] = sprite;
return sprite;
/*
struct Sprite *sprite = &gSprites[index];
@@ -194,6 +200,33 @@ function CreateNewSprite(index: integer, template: SpriteTemplate, x: integer, y
}
function CreateSpriteAndAnimate(template: SpriteTemplate, x: integer, y: integer, subpriority: integer) {
const sprite = CreateNewSprite(gSprites.length, template, x, y, subpriority);
console.log(template.imageName.toLowerCase());
const animateSpriteFrame = (sprite: Sprite) => {
scene.time.delayedCall(16, () => {
sprite.callback(sprite);
if (!sprite.data[0]) {
const originalOnAnimsComplete = onAnimsComplete;
if (originalOnAnimsComplete) {
onAnimsComplete = null;
originalOnAnimsComplete();
}
return;
}
console.log(sprite.data)
sprite.spriteObj.setPosition(sprite.x, sprite.y);
animateSpriteFrame(sprite);
});
};
sprite.callback(sprite);
animateSpriteFrame(sprite);
return gSprites.length - 1;
//gSprites.push(gSprites.length);
/*u8 i;
for (i = 0; i < MAX_SPRITES; i++)
@@ -248,12 +281,33 @@ function CreateVisualTask(taskFunc: Function, taskPriority: integer, args: any[]
gAnimVisualTaskCount++;
}
function DestroyAnimSprite(sprite: Sprite) {
//FreeSpriteOamMatrix(sprite);
DestroySprite(sprite);
gAnimVisualTaskCount--;
}
function DestroyAnimVisualTask(taskId: integer)
{
DestroyTask(taskId);
gAnimVisualTaskCount--;
}
function DestroySprite(sprite:Sprite) {
/*if (sprite->inUse)
{
if (!sprite->usingSheet)
{
u16 i;
u16 tileEnd = (sprite->images->size / TILE_SIZE_4BPP) + sprite->oam.tileNum;
for (i = sprite->oam.tileNum; i < tileEnd; i++)
FREE_SPRITE_TILE(i);
}
ResetSprite(sprite);
}*/
}
function CreateTask(func: Function, priority: integer): integer
{
const taskId = gTasks.length;
@@ -273,7 +327,7 @@ function DestroyTask(taskId: integer) {
}
else
{
if (gTasks[taskId].next == TAIL_SENTINEL)
if (gdTasks[taskId].next == TAIL_SENTINEL)
{
gTasks[gTasks[taskId].prev].next = TAIL_SENTINEL;
}
@@ -372,18 +426,32 @@ enum BattleSide {
ENEMY
}
/*function AnimateSprite(sprite: Sprite) {
sAnimFuncs[sprite.animBeginning](sprite);
if (!gAffineAnimsDisabled)
sAffineAnimFuncs[sprite->affineAnimBeginning](sprite);
}*/
function StartSpriteAffineAnim(sprite: Sprite, animNum: integer) {
/*u8 matrixNum = GetSpriteMatrixNum(sprite);
AffineAnimStateStartAnim(matrixNum, animNum);
sprite->affineAnimBeginning = TRUE;
sprite->affineAnimEnded = FALSE;*/
}
function GetBattlerSpriteCoord(target: Pokemon, coordType: BattlerCoord): integer {
console.log('TEST1');
console.log('TEST1', target.y, target.getSprite().height);
switch (coordType) {
case BattlerCoord.X:
case BattlerCoord.X_2:
return target.x;
case BattlerCoord.Y:
return target.y;
return target.y - target.getSprite().height / 2;
case BattlerCoord.Y_PIC_OFFSET:
case BattlerCoord.Y_PIC_OFFSET_DEFAULT:
return target.y;
return target.y - target.getSprite().height / 2;
}
}
@@ -412,9 +480,9 @@ function GetBattlerSpriteCoord2(target: Pokemon, coordType: BattlerCoord): integ
case BattlerCoord.X:
return target.x;
case BattlerCoord.Y:
return target.y;
return target.y - target.getSprite().height / 2;
case BattlerCoord.Y_PIC_OFFSET:
return target.y;
return target.y - target.getSprite().height / 2;
}
}
@@ -446,6 +514,51 @@ function SetCallbackToStoredInData6(sprite: Sprite)
//sprite->callback = (void (*)(struct Sprite *))callback;
}
function AnimTranslateLinear(sprite: Sprite): boolean
{
let v1: integer;
let v2: integer;
let x: integer
let y: integer;
if (!sprite.data[0])
return true;
v1 = sprite.data[1];
v2 = sprite.data[2];
x = sprite.data[3];
y = sprite.data[4];
x += v1;
y += v2;
console.log('XY', x, y)
if (v1 & 1)
sprite.x2 = -(x >> 8);
else
sprite.x2 = x >> 8;
if (v2 & 1)
sprite.y2 = -(y >> 8);
else
sprite.y2 = y >> 8;
sprite.data[3] = x;
sprite.data[4] = y;
sprite.data[0]--;
return false;
}
function Sin(index: integer, amplitude: integer): integer
{
return (amplitude * Math.sin(index * (Math.PI / 128))) >> 8;
}
function Cos(index: integer, amplitude: integer): integer
{
return (amplitude * Math.cos(index * (Math.PI / 128))) >> 8;
}
function GetBattlerSide(pokemon: Pokemon): BattleSide
{
return pokemon instanceof PlayerPokemon ? BattleSide.PLAYER : BattleSide.ENEMY;
@@ -463,7 +576,6 @@ const linePatterns = [
} ],
[ /([\t ]+)createsprite (.*?), (.*?), (.*?)(?:, (.*?))?$/, res => {
let ret = res[0];
const args = res[5].split(', ');
let search = `const struct SpriteTemplate ${res[2]}`;
let i = animTemplates.indexOf(search);
@@ -478,10 +590,14 @@ const linePatterns = [
search = '.callback = ';
i = template.indexOf(search);
const animFuncName = template.slice(i + search.length, template.indexOf(',', i));
console.log(imageName, animFuncName)
console.log(imageName, animFuncName);
if (templateNames.indexOf(res[2]) === -1) {
animTemplateContents += `const ${res[2]} = new SpriteTemplate('${imageName}', ${affineAnimName !== 'gDummySpriteAffineAnimTable' && false ? affineAnimName : 'null'}, ${animFuncName});\n`;
templateNames.push(res[2]);
}
if (funcNames.indexOf(animFuncName) === -1) {
animTemplateContents += `const ${res[2]} = new SpriteTemplate('${imageName}', ${affineAnimName !== 'gDummySpriteAffineAnimTable' && false ? affineAnimName : 'null'}, ${animFuncName});\n`
search = `void ${animFuncName}(struct Sprite *sprite`;
i = animTemplates.indexOf(search);
@@ -517,6 +633,7 @@ const linePatterns = [
[ /([\t ]+)delay (\d+)/, res => {
return `${res[1]}delay ${Math.round(16.6666666667 * parseInt(res[2]))}`;
} ],
[ /([\t ]+waitforvisualfinish)/, '$1' ],
[ /splitbgprio.*?$/, '' ],
[ /ANIM\_TARGET/g, 'AnimTarget.TARGET' ],
[ /ANIM\_ATTACKER/g, 'AnimTarget.ATTACKER' ]
@@ -550,16 +667,19 @@ const funcLinePatterns = [
[ /struct Sprite \*sprite/, 'sprite: Sprite' ],
[ /&sprite/g, 'sprite' ],
[ /^u8 ([A-Z][A-Za-z\_0-9]+)\((.*?)\)/, 'function $1($2): integer' ],
[ /^bool8 ([A-Z][A-Za-z\_0-9]+)\((.*?)\)/, 'function $1($2): boolean' ],
[ /^(?:static )?void /, 'function ' ],
[ /TRUE/g, 'true' ],
[ /FALSE/g, 'false' ],
[ /ANIM_(ATTACKER|TARGET)/g, 'AnimTarget.$1' ],
[ /BATTLER\_COORD\_/g, 'BattlerCoord.' ],
[ /B\_SIDE\_PLAYER/g, 'BattleSide.PLAYER' ],
[ /B\_SIDE\_OPPONENT/g, 'BattleSide.ENEMY' ],
[ /abs\(/g, 'Math.abs(' ],
[ /\(integer\)/g, '' ],
[ /^([\t ]+)\*([a-zA-Z\_]+) =/, '$1$2 =' ],
[ /([\t ]+)([a-zA-Z\_]+) = (.*?);/, '$1$2 = $3;' ],
[ /^[\t ]+([^ ]+ =|return|if \() ([A-Z][a-zA-Z0-9\_]+)(?:\((.*?)\))?(\))?;$/, res => {
[ /^[\t ]+([^ ]+ =|return|if \() ?([A-Z][a-zA-Z0-9\_]+)(?:\((.*?)\))?(\))?;?$/, res => {
const funcName = res[2];
if (funcNames.indexOf(funcName) === -1) {
const searchRes = new RegExp(`\n\n(?:static )?([^ ]+) ${funcName}\\((.*?)\\)\n\{`).exec(animTemplates);
@@ -656,10 +776,21 @@ export function interp(sceneIn) {
console.log(contIndex)
if (!contMatch)
console.log(output.slice(delayMatch.index))
console.log(delayMatch)
output = output.slice(0, delayMatch.index) + delayMatch[1] + `scene.time.delayedCall(${delayMatch[2]}, () => {` + output.slice(contIndex, contMatch.index + contIndex).replace(/\n/g, '\n\t') + '\n' + delayMatch[1] + '});' + output.slice(contMatch.index + contIndex);
}
const visualWaitPattern = /([\t ]+)waitforvisualfinish/;
let visualWaitMatch;
while ((visualWaitMatch = visualWaitPattern.exec(output))) {
const contIndex = visualWaitMatch.index + visualWaitMatch[1].length + 19;
const contMatch = /\n\}/.exec(output.slice(contIndex));
console.log(contIndex)
if (!contMatch)
console.log(output.slice(visualWaitMatch.index))
console.log(visualWaitMatch)
output = output.slice(0, visualWaitMatch.index) + visualWaitMatch[1] + `onAnimsComplete = () => {` + output.slice(contIndex, contMatch.index + contIndex).replace(/\n/g, '\n\t') + '\n' + visualWaitMatch[1] + '};' + output.slice(contMatch.index + contIndex);
}
output += '\ndoMoveAnim_WATER_GUN();\n\n';
console.log(output, animTemplateContents, animFuncsContents)