reldens
Version:
Reldens - MMORPG Platform
343 lines (323 loc) • 12.8 kB
JavaScript
/**
*
* Reldens - PreloaderHandler.
*
* Manages preloading and creation of animations for skills and class paths.
*
*/
const { GameConst } = require('../../game/constants');
const { Logger, sc } = require('@reldens/utils');
/**
* @typedef {import('../../game/client/game-manager').GameManager} GameManager
* @typedef {import('@reldens/utils').EventsManager} EventsManager
* @typedef {import('phaser').Scene} PhaserScene
*
* @typedef {Object} PreloaderHandlerProps
* @property {GameManager} [gameManager]
* @property {EventsManager} [events]
* @property {string} [assetsCustomActionsSpritesPath]
*
* @typedef {Object} AnimationData
* @property {string} key
* @property {string} [skillKey]
* @property {Object} animationData
* @property {Object} [classKey]
*/
class PreloaderHandler
{
/**
* @param {PreloaderHandlerProps} props
*/
constructor(props)
{
/** @type {GameManager|false} */
this.gameManager = sc.get(props, 'gameManager', false);
if(!this.gameManager){
Logger.error('Game Manager undefined in ActionsPlugin PreloaderHandler.');
}
/** @type {EventsManager|false} */
this.events = sc.get(props, 'events', false);
if(!this.events){
Logger.error('EventsManager undefined in ActionsPlugin PreloaderHandler.');
}
this.setProperties(props);
}
/**
* @param {PreloaderHandlerProps} props
* @returns {boolean|void}
*/
setProperties(props)
{
if(!this.gameManager){
return false;
}
this.gameDom = this.gameManager.gameDom;
this.initialGameData = this.gameManager.initialGameData;
this.levelsAnimConfig = this.gameManager.config.get('client/levels/animations');
this.skillsAnimConfig = this.gameManager.config.get('client/skills/animations');
this.assetsCustomActionsSpritesPath = sc.get(
props,
'assetsCustomActionsSpritesPath',
'assets/custom/actions/sprites/'
);
if(!this.gameManager.loadedAssets){
this.gameManager.loadedAssets = {};
}
if(!this.gameManager.createdAnimations){
this.gameManager.createdAnimations = {};
}
}
/**
* @param {PhaserScene} uiScene
*/
loadContents(uiScene)
{
uiScene.load.html('skillsClassPath', '/assets/features/skills/templates/ui-class-path.html');
uiScene.load.html('skillsLevel', '/assets/features/skills/templates/ui-level.html');
uiScene.load.html('skillsExperience', '/assets/features/skills/templates/ui-experience.html');
uiScene.load.html('skills', '/assets/features/skills/templates/ui-skills.html');
uiScene.load.html('skillBox', '/assets/features/skills/templates/ui-skill-box.html');
uiScene.load.html('actionBox', '/assets/html/ui-action-box.html');
this.preloadClassPaths(uiScene);
this.loopAnimationsAnd(this.levelsAnimConfig, 'preload', uiScene);
this.loopAnimationsAnd(this.skillsAnimConfig, 'preload', uiScene);
}
/**
* @param {PhaserScene} uiScene
* @returns {boolean|void}
*/
preloadClassPaths(uiScene)
{
let classesData = sc.get(this.initialGameData, 'classesData', false);
if(!classesData){
return false;
}
for(let i of Object.keys(classesData)){
let avatarKey = classesData[i].key;
let spriteSheetLoader = uiScene.load.spritesheet(
avatarKey,
'/assets/custom/sprites/'+avatarKey+GameConst.FILES.EXTENSIONS.PNG,
uiScene.playerSpriteSize
);
spriteSheetLoader.on('filecomplete', async (completedKey) => {
this.gameManager.loadedAssets[completedKey] = completedKey;
});
}
}
/**
* @param {PhaserScene} preloadScene
*/
createAnimations(preloadScene)
{
let levelsAnimations = this.levelsAnimConfig;
this.loopAnimationsAnd(levelsAnimations, 'create', preloadScene);
let skillsAnimations = this.skillsAnimConfig;
this.loopAnimationsAnd(skillsAnimations, 'create', preloadScene);
this.createAvatarsAnimations(preloadScene);
}
/**
* @param {PhaserScene} preloadScene
* @returns {Object<string, string>|boolean}
*/
createAvatarsAnimations(preloadScene)
{
let classesData = sc.get(this.initialGameData, 'classesData', false);
if(!classesData){
Logger.debug('Classes data not found. Fallback to player avatar.');
return false;
}
if(!this.gameManager.mappedAvatars){
this.gameManager.mappedAvatars = {};
}
Logger.debug({availableClassesData: classesData});
for(let i of Object.keys(classesData)){
let avatarKey = classesData[i].key;
if(!this.gameManager.loadedAssets[avatarKey]){
avatarKey = GameConst.IMAGE_PLAYER;
Logger.info('Avatar for class path "'+avatarKey+'" not found in assets. Fallback to player avatar.');
}
this.gameManager.mappedAvatars[avatarKey] = avatarKey;
preloadScene.createPlayerAnimations(avatarKey);
}
return this.gameManager.mappedAvatars;
}
/**
* @param {Object<string, AnimationData>} animations
* @param {string} command
* @param {PhaserScene} uiScene
* @returns {boolean|void}
*/
loopAnimationsAnd(animations, command, uiScene)
{
if(!animations){
Logger.warning('Animations not found.', animations);
return false;
}
for(let i of Object.keys(animations)){
let data = animations[i];
if(!data.animationData.enabled){
Logger.debug('Animation "'+i+'" not enabled, skipping.', data);
continue;
}
Logger.debug({[command+'Animation']: data});
this[command+'Animation'](data, uiScene);
}
}
/**
* @param {AnimationData} data
* @param {PhaserScene} uiScene
*/
preloadAnimation(data, uiScene)
{
// @TODO - BETA - Remove the hardcoded file extensions.
// @NOTE: here we use have two keys, the animation key and the animationData.img, this is because we could have
// a single sprite with multiple attacks, and use the start and end frame to run the required one.
if(
sc.hasOwn(data.animationData, ['type', 'img'])
&& GameConst.ANIMATIONS_TYPE.SPRITESHEET === data.animationData.type
){
this.preloadAnimationsInDirections(data, uiScene);
}
if(data.classKey && sc.isFunction(data.classKey['prepareAnimation'])){
data.classKey['prepareAnimation']({data, uiScene, pack: this});
}
}
/**
* @param {AnimationData} data
* @param {PhaserScene} uiScene
*/
preloadAnimationsInDirections(data, uiScene)
{
// try load directions:
// - 1: both (this is to include diagonals)
// - 2: up/down
// - 3: left/right
let animDir = sc.get(data.animationData, 'dir', 0);
if(0 === animDir){
uiScene.load.spritesheet(
this.getAnimationKey(data),
this.assetsCustomActionsSpritesPath + data.animationData.img+GameConst.FILES.EXTENSIONS.PNG,
data.animationData
);
return;
}
// @TODO - BETA - Refactor and implement animDir = 1 (both): up_right, up_left, down_right, down_left.
if(1 === animDir || 2 === animDir){
this.preloadSpriteInDirection(uiScene, data, GameConst.UP);
this.preloadSpriteInDirection(uiScene, data, GameConst.DOWN);
}
if(1 === animDir || 3 === animDir){
this.preloadSpriteInDirection(uiScene, data, GameConst.LEFT);
this.preloadSpriteInDirection(uiScene, data, GameConst.RIGHT);
}
}
/**
* @param {PhaserScene} uiScene
* @param {AnimationData} data
* @param {string} direction
*/
preloadSpriteInDirection(uiScene, data, direction)
{
uiScene.load.spritesheet(
this.getAnimationKey(data, direction),
this.assetsCustomActionsSpritesPath+data.animationData.img+'_'+direction+GameConst.FILES.EXTENSIONS.PNG,
data.animationData
);
}
/**
* @param {AnimationData} data
* @param {PhaserScene} uiScene
*/
createAnimation(data, uiScene)
{
if(
sc.hasOwn(data.animationData, ['type', 'img'])
&& data.animationData.type === GameConst.ANIMATIONS_TYPE.SPRITESHEET
){
let animDir = sc.get(data.animationData, 'dir', 0);
0 < animDir
? this.createWithMultipleDirections(uiScene, data, animDir)
: this.createWithDirection(data, uiScene);
}
if(data.classKey && sc.isFunction(data.classKey['createAnimation'])){
data.classKey['createAnimation']({data, uiScene, pack: this});
}
}
/**
* @param {PhaserScene} uiScene
* @param {AnimationData} data
* @param {number} animDir
*/
createWithMultipleDirections(uiScene, data, animDir)
{
// @TODO - BETA - Refactor and implement animDir = 1 (both): up_right, up_left, down_right,
// down_left.
uiScene.directionalAnimations[this.getAnimationKey(data)] = data.animationData.dir;
if(1 === animDir || 2 === animDir){
this.createWithDirection(data, uiScene, GameConst.UP);
this.createWithDirection(data, uiScene, GameConst.DOWN);
}
if(1 === animDir || 3 === animDir){
this.createWithDirection(data, uiScene, GameConst.LEFT);
this.createWithDirection(data, uiScene, GameConst.RIGHT);
}
}
/**
* @param {AnimationData} data
* @param {PhaserScene} uiScene
* @param {string} [direction]
* @returns {Object}
*/
createWithDirection(data, uiScene, direction = '')
{
let animationCreateData = this.prepareAnimationData(data, uiScene, direction);
if(this.gameManager.createdAnimations[animationCreateData.key]){
return this.gameManager.createdAnimations[animationCreateData.key];
}
let newAnimation = uiScene.anims.create(animationCreateData);
if(sc.hasOwn(data.animationData, 'destroyTime')){
newAnimation.destroyTime = data.animationData.destroyTime;
}
if(sc.hasOwn(data.animationData, 'depthByPlayer')){
newAnimation.depthByPlayer = data.animationData.depthByPlayer;
}
this.gameManager.createdAnimations[animationCreateData.key] = newAnimation;
return this.gameManager.createdAnimations[animationCreateData.key];
}
/**
* @param {AnimationData} data
* @param {PhaserScene} uiScene
* @param {string} [direction]
* @returns {Object}
*/
prepareAnimationData(data, uiScene, direction = '')
{
// @NOTE: here we use have two keys, the animation key and the animationData.img, this is because we could have
// a single sprite with multiple attacks, and use the start and end frame to run the required one.
let imageKey = this.getAnimationKey(data, direction);
let animationCreateData = {
key: imageKey,
frames: uiScene.anims.generateFrameNumbers(imageKey, data.animationData),
hideOnComplete: sc.get(data.animationData, 'hide', true),
};
if(sc.hasOwn(data.animationData, 'duration')){
animationCreateData.duration = data.animationData.duration;
} else {
animationCreateData.frameRate = sc.get(data.animationData, 'frameRate', uiScene.configuredFrameRate);
}
if(sc.hasOwn(data.animationData, 'repeat')){
animationCreateData.repeat = data.animationData.repeat;
}
return animationCreateData;
}
/**
* @param {AnimationData} data
* @param {string} [direction]
* @returns {string}
*/
getAnimationKey(data, direction = '')
{
return (data.skillKey ? data.skillKey+'_' : '')+data.key+(direction && '' !== direction ? '_'+direction : '');
}
}
module.exports.PreloaderHandler = PreloaderHandler;