add fiery storm animation for fiery fallout

This commit is contained in:
ImperialSympathizer 2024-07-16 22:51:40 -04:00
parent e10a9caddc
commit 09a3167bac
9 changed files with 1916 additions and 45 deletions

View File

@ -0,0 +1,70 @@
{
"graphic": "Encounter Magma Bg",
"frames": [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
],
"frameTimedEvents": {
"0": [
{
"frameIndex": 0,
"resourceName": "PRAS- Fire BG",
"bgX": 0,
"bgY": 0,
"opacity": 0,
"duration": 35,
"eventType": "AnimTimedAddBgEvent"
},
{
"frameIndex": 0,
"resourceName": "",
"bgX": 0,
"bgY": 0,
"opacity": 255,
"duration": 4,
"eventType": "AnimTimedUpdateBgEvent"
}
],
"25": [
{
"frameIndex": 25,
"resourceName": "",
"bgX": 0,
"bgY": 0,
"opacity": 0,
"duration": 7,
"eventType": "AnimTimedUpdateBgEvent"
}
]
},
"position": 1,
"hue": 0
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import {Constructor, isNullOrUndefined} from "#app/utils";
import * as Utils from "./utils";
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims";
import { initCommonAnims, initEncounterAnims, initMoveAnim, loadCommonAnimAssets, loadEncounterAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims";
import { Phase } from "./phase";
import { initGameSpeed } from "./system/game-speed";
import { Arena, ArenaBase } from "./field/arena";
@ -555,6 +555,7 @@ export default class BattleScene extends SceneBase {
Promise.all([
Promise.all(loadPokemonAssets),
initCommonAnims(this).then(() => loadCommonAnimAssets(this, true)),
initEncounterAnims(this).then(() => loadEncounterAnimAssets(this, true)),
Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(this, m))).then(() => loadMoveAnimAssets(this, defaultMoves, true)),
this.initStarterColors()
]).then(() => {

View File

@ -102,6 +102,11 @@ export enum CommonAnim {
LOCK_ON = 2120
}
export enum EncounterAnim {
MAGMA_BG,
MAGMA_SPOUT
}
export class AnimConfig {
public id: integer;
public graphic: string;
@ -444,6 +449,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig]>();
export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig]>();
export const commonAnims = new Map<CommonAnim, AnimConfig>();
export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
export function initCommonAnims(scene: BattleScene): Promise<void> {
return new Promise(resolve => {
@ -511,6 +517,21 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
});
}
export function initEncounterAnims(scene: BattleScene): Promise<void> {
return new Promise(resolve => {
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
const encounterAnimIds = Utils.getEnumValues(EncounterAnim);
const encounterAnimFetches = [];
for (let ea = 0; ea < encounterAnimIds.length; ea++) {
const encounterAnimId = encounterAnimIds[ea];
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[ea].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json())
.then(cas => encounterAnims.set(encounterAnimId, new AnimConfig(cas))));
}
Promise.allSettled(encounterAnimFetches).then(() => resolve());
});
}
export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): Promise<void> {
return new Promise(resolve => {
if (chargeAnims.has(chargeAnim)) {
@ -565,6 +586,12 @@ export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): P
});
}
export function loadEncounterAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
loadAnimAssets(scene, Array.from(encounterAnims.values()), startLoad).then(() => resolve());
});
}
export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise<void> {
return new Promise(resolve => {
const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat();
@ -977,6 +1004,185 @@ export abstract class BattleAnim {
}
});
}
private getGraphicFrameDataWithoutTarget(scene: BattleScene, frames: AnimFrame[], targetInitialX: number, targetInitialY: number): Map<integer, Map<AnimFrameTarget, GraphicFrameData>> {
const ret: Map<integer, Map<AnimFrameTarget, GraphicFrameData>> = new Map([
[AnimFrameTarget.GRAPHIC, new Map<AnimFrameTarget, GraphicFrameData>() ],
[AnimFrameTarget.USER, new Map<AnimFrameTarget, GraphicFrameData>() ],
[AnimFrameTarget.TARGET, new Map<AnimFrameTarget, GraphicFrameData>() ]
]);
const userInitialX = 0;
const userInitialY = 0;
const userHalfHeight = 30;
const targetHalfHeight = 30;
let g = 0;
let u = 0;
let t = 0;
for (const frame of frames) {
let x = frame.x;
let y = frame.y;
let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1);
const scaleY = (frame.zoomY / 100);
switch (frame.focus) {
case AnimFocus.TARGET:
x += targetInitialX - targetFocusX;
y += (targetInitialY - targetHalfHeight) - targetFocusY;
break;
case AnimFocus.USER:
x += userInitialX - userFocusX;
y += (userInitialY - userHalfHeight) - userFocusY;
break;
case AnimFocus.USER_TARGET:
const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3],
this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y);
x = point[0];
y = point[1];
if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) {
scaleX = scaleX * -1;
}
break;
}
const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
ret.get(frame.target).set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle });
}
return ret;
}
playWithoutTargets(scene: BattleScene, targetInitialX: number, targetInitialY: number, frameTimeMult: number, callback?: Function) {
const spriteCache: SpriteCache = {
[AnimFrameTarget.GRAPHIC]: [],
[AnimFrameTarget.USER]: [],
[AnimFrameTarget.TARGET]: []
};
const spritePriorities: integer[] = [];
const cleanUpAndComplete = () => {
for (const ms of Object.values(spriteCache).flat()) {
if (ms) {
ms.destroy();
}
}
if (this.bgSprite) {
this.bgSprite.destroy();
}
if (callback) {
callback();
}
};
if (!scene.moveAnimations) {
return cleanUpAndComplete();
}
const anim = this.getAnim();
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
this.dstLine = [ 150, 75, targetInitialX, targetInitialY ];
let r = anim.frames.length;
let f = 0;
scene.tweens.addCounter({
duration: Utils.getFrameMs(3) * frameTimeMult,
repeat: anim.frames.length,
onRepeat: () => {
const spriteFrames = anim.frames[f];
const frameData = this.getGraphicFrameDataWithoutTarget(scene, anim.frames[f], targetInitialX, targetInitialY);
const u = 0;
const t = 0;
let g = 0;
for (const frame of spriteFrames) {
if (frame.target !== AnimFrameTarget.GRAPHIC) {
console.log("Encounter animations do not support targets");
continue;
}
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (g === sprites.length) {
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim.graphic, 1);
sprites.push(newSprite);
scene.field.add(newSprite);
spritePriorities.push(1);
}
const graphicIndex = g++;
const moveSprite = sprites[graphicIndex];
if (spritePriorities[graphicIndex] !== frame.priority) {
spritePriorities[graphicIndex] = frame.priority;
const setSpritePriority = (priority: integer) => {
if (priority < 0) {
// Move to top of scene
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
} else if (priority < scene.field.getAll().length) {
// Indexes of field:
// 0 is scene background
// 1 is enemy field
scene.field.moveTo(moveSprite, priority);
} else {
setSpritePriority(-1);
}
};
setSpritePriority(frame.priority);
}
moveSprite.setFrame(frame.graphicFrame);
//console.log(AnimFocus[frame.focus]);
const graphicFrameData = frameData.get(frame.target).get(graphicIndex);
moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y);
moveSprite.setAngle(graphicFrameData.angle);
moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY);
moveSprite.setAlpha(frame.opacity / 255);
moveSprite.setVisible(frame.visible);
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
}
if (anim.frameTimedEvents.has(f)) {
for (const event of anim.frameTimedEvents.get(f)) {
r = Math.max((anim.frames.length - f) + event.execute(scene, this), r);
}
}
const targets = Utils.getEnumValues(AnimFrameTarget);
for (const i of targets) {
const count = i === AnimFrameTarget.GRAPHIC ? g : i === AnimFrameTarget.USER ? u : t;
if (count < spriteCache[i].length) {
const spritesToRemove = spriteCache[i].slice(count, spriteCache[i].length);
for (const rs of spritesToRemove) {
if (!rs.getData("locked") as boolean) {
const spriteCacheIndex = spriteCache[i].indexOf(rs);
spriteCache[i].splice(spriteCacheIndex, 1);
if (i === AnimFrameTarget.GRAPHIC) {
spritePriorities.splice(spriteCacheIndex, 1);
}
rs.destroy();
}
}
}
}
f++;
r--;
},
onComplete: () => {
for (const ms of Object.values(spriteCache).flat()) {
if (ms && !ms.getData("locked")) {
ms.destroy();
}
}
if (r) {
scene.tweens.addCounter({
duration: Utils.getFrameMs(r),
onComplete: () => cleanUpAndComplete()
});
} else {
cleanUpAndComplete();
}
}
});
}
}
export class CommonBattleAnim extends BattleAnim {
@ -1045,6 +1251,24 @@ export class MoveChargeAnim extends MoveAnim {
}
}
export class EncounterBattleAnim extends BattleAnim {
public encounterAnim: EncounterAnim;
constructor(encounterAnim: EncounterAnim, user: Pokemon, target?: Pokemon) {
super(user, target || user);
this.encounterAnim = encounterAnim;
}
getAnim(): AnimConfig {
return encounterAnims.get(this.encounterAnim);
}
isOppAnim(): boolean {
return false;
}
}
export async function populateAnims() {
const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase());
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, ""));

View File

@ -12,8 +12,9 @@ import { Type } from "#app/data/type";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { Moves } from "#enums/moves";
import { initMoveAnim } from "#app/data/battle-anims";
import { EncounterAnim, EncounterBattleAnim, initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { WeatherType } from "#app/data/weather";
import { randSeedInt } from "#app/utils";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fiery_fallout";
@ -34,7 +35,7 @@ export const FieryFalloutEncounter: IMysteryEncounter =
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Calculate boss mon
// Calculate boss mons
const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA);
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 0.25,
@ -55,19 +56,28 @@ export const FieryFalloutEncounter: IMysteryEncounter =
};
encounter.enemyPartyConfigs = [config];
const spriteKey = volcaronaSpecies.getSpriteId(false, null, false, null);
encounter.spriteConfigs = [
{
spriteKey: spriteKey,
fileRoot: "pokemon",
tint: 0.9,
repeat: true
}
];
// Sets weather for 5 turns
scene.arena.trySetWeather(WeatherType.SUNNY, true);
// Load animations/sfx for Volcarona moves
Promise.all([initMoveAnim(scene, Moves.QUIVER_DANCE), initMoveAnim(scene, Moves.FIRE_SPIN)])
.then(() => loadMoveAnimAssets(scene, [Moves.QUIVER_DANCE, Moves.FIRE_SPIN]));
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, scene.getPlayerPokemon(), scene.getPlayerPokemon());
background.playWithoutTargets(scene, 200, 70, 2);
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, scene.getPlayerPokemon(), scene.getPlayerPokemon());
animation.playWithoutTargets(scene, 200, 70, 2);
const increment = 600;
for (let i = 3; i < 6; i++) {
scene.time.delayedCall((increment) * (i - 2), () => {
animation.playWithoutTargets(scene, 100 + randSeedInt(12) * 20, 110 - randSeedInt(10) * 15, 2);
});
}
return true;
})
.withTitle(`${namespace}_title`)
@ -86,22 +96,19 @@ export const FieryFalloutEncounter: IMysteryEncounter =
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
// TODO: play heat wave animation for weather effect
// await initMoveAnim(scene, Moves.HEAT_WAVE);
// await loadMoveAnimAssets(scene, [ Moves.HEAT_WAVE ], true);
// const heatWave = new MoveAnim(Moves.HEAT_WAVE, scene.getPlayerPokemon(), 0);
// heatWave.play(scene);
await initMoveAnim(scene, Moves.QUIVER_DANCE);
await initMoveAnim(scene, Moves.FIRE_SPIN);
await initMoveAnim(scene, Moves.HEAT_WAVE);
const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [charcoal], fillRemaining: true });
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER, BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.HEAT_WAVE),
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
},
{
@ -115,18 +122,6 @@ export const FieryFalloutEncounter: IMysteryEncounter =
targets: [BattlerIndex.ENEMY_2],
move: new PokemonMove(Moves.QUIVER_DANCE),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(Moves.FIRE_SPIN),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
}

View File

@ -64,6 +64,7 @@ export default interface IMysteryEncounter {
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterRewards?: (scene: BattleScene) => boolean;
onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean;
/**
* Requirements
@ -384,6 +385,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterRewards?: (scene: BattleScene) => boolean;
onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean;
hideBattleIntroMessage?: boolean;
hideIntroVisuals?: boolean;
enemyPartyConfigs?: EnemyPartyConfig[] = [];
@ -598,6 +600,16 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
return Object.assign(this, { onInit: onInit });
}
/**
* Can be used to perform some extra logic (usually animations) when the enemy field is finished sliding in
*
* @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in
* @returns
*/
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onVisualsStart">> {
return Object.assign(this, { onVisualsStart: onVisualsStart });
}
/**
* Defines any enemies to use for a battle from the mystery encounter
* @param enemyPartyConfig

View File

@ -202,6 +202,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
}
enemyPokemon.initBattleInfo();
enemyPokemon.getBattleInfo().initInfo(enemyPokemon);
}
loadEnemyAssets.push(enemyPokemon.loadAssets());

View File

@ -891,11 +891,6 @@ export class EncounterPhase extends BattlePhase {
if (battle.battleType === BattleType.TRAINER) {
loadEnemyAssets.push(battle.trainer.loadAssets().then(() => battle.trainer.initSprite()));
} else if (battle.battleType === BattleType.MYSTERY_ENCOUNTER) {
// this.scene.getEnemyParty().forEach(p => {
// this.scene.field.remove(p);
// p.destroy();
// });
// this.scene.currentBattle.enemyParty = [];
if (!battle.mysteryEncounter) {
const newEncounter = this.scene.getMysteryEncounter(mysteryEncounter);
battle.mysteryEncounter = newEncounter;
@ -1081,6 +1076,10 @@ export class EncounterPhase extends BattlePhase {
const introVisuals = this.scene.currentBattle.mysteryEncounter.introVisuals;
introVisuals.playAnim();
if (this.scene.currentBattle.mysteryEncounter.onVisualsStart) {
this.scene.currentBattle.mysteryEncounter.onVisualsStart(this.scene);
}
const doEncounter = () => {
this.scene.playBgm(undefined);

View File

@ -108,9 +108,9 @@ export class MysteryEncounterPhase extends Phase {
}
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 750 : 0);
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 300 : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 0 ? 750 : 0, true);
this.scene.ui.showText(text, null, nextAction, i === 0 ? 300 : 0, true);
}
i++;
};
@ -222,7 +222,7 @@ export class MysteryEncounterBattlePhase extends Phase {
}
if (!scene.currentBattle.mysteryEncounter.hideBattleIntroMessage) {
scene.ui.showText(this.getBattleMessage(scene), null, () => this.endBattleSetup(scene), 500);
scene.ui.showText(this.getBattleMessage(scene), null, () => this.endBattleSetup(scene), 0);
} else {
this.endBattleSetup(scene);
}
@ -243,7 +243,7 @@ export class MysteryEncounterBattlePhase extends Phase {
this.endBattleSetup(scene);
};
if (!scene.currentBattle.mysteryEncounter.hideBattleIntroMessage) {
scene.ui.showText(this.getBattleMessage(scene), null, doTrainerSummon, 1500, true);
scene.ui.showText(this.getBattleMessage(scene), null, doTrainerSummon, 1000, true);
} else {
doTrainerSummon();
}