reldens
Version:
Reldens - MMORPG Platform
288 lines (270 loc) • 10.4 kB
JavaScript
/**
*
* Reldens - ObjectsManager
*
* Manages loading, generating, and lifecycle of game objects within a room.
*
*/
const { Logger, sc } = require('@reldens/utils');
/**
* @typedef {import('@reldens/utils').EventsManager} EventsManager
* @typedef {import('@reldens/storage').BaseDataServer} BaseDataServer
* @typedef {import('../../game/server/config-manager').ConfigManager} ConfigManager
*/
class ObjectsManager
{
/**
* @param {Object} props
*/
constructor(props)
{
/** @type {ConfigManager} */
this.config = props.config;
/** @type {EventsManager|false} */
this.events = sc.get(props, 'events', false);
/** @type {number|string|false} */
this.roomId = sc.get(props, 'roomId', false);
/** @type {string|false} */
this.roomName = sc.get(props, 'roomName', false);
if(!this.events){
Logger.error('EventsManager undefined in ObjectsManager.');
}
/** @type {BaseDataServer|false} */
this.dataServer = sc.get(props, 'dataServer', false);
if(!this.dataServer){
Logger.error('DataServer undefined in ObjectsManager.');
}
/** @type {Array<Object>|false} */
this.roomObjectsData = false;
/** @type {Object<string, Object>|false} */
this.roomObjects = false;
/** @type {Object<string, Object>} */
this.roomObjectsByLayer = {};
/** @type {Object<string, Object>} */
this.preloadAssets = {};
/** @type {Object<string, Object>} */
this.objectsAnimationsData = {};
/** @type {boolean} */
this.listenMessages = false;
/** @type {Object<string, Object>} */
this.listenMessagesObjects = {};
}
/**
* @param {number|string} roomId
* @returns {Promise<Array<Object>|false>}
*/
async loadObjectsByRoomId(roomId)
{
if(this.roomObjectsData){
return this.roomObjectsData;
}
let objectsRepository = this.dataServer.getEntity('objects');
objectsRepository.sortBy = 'tile_index';
this.roomObjectsData = await objectsRepository.loadByWithRelations(
'room_id',
roomId,
[
'related_rooms',
'related_objects_assets',
'related_objects_animations',
'related_objects_stats.related_stats'
]
);
}
/**
* @returns {Promise<Object<string, Object>|false>}
*/
async generateObjects()
{
if(!this.roomObjectsData || 0 === this.roomObjectsData.length){
return this.roomObjects;
}
this.roomObjects = {};
// @NOTE: allow null index for multiple objects of the same type.
for(let objectData of this.roomObjectsData){
await this.generateObjectFromObjectData(objectData);
}
}
/**
* @param {Object} objectData
* @returns {Promise<Object|false>}
*/
async generateObjectFromObjectData(objectData)
{
// @NOTE: these classes are coming from the theme/plugins/objects/server.js file.
let objClass = this.config.getWithoutLogs('server/customClasses/objects/'+objectData.object_class_key, false);
let objectClassTypes = this.config?.configList?.server?.objectsClassTypes;
if(!objClass && objectClassTypes && objectData.class_type){
let objClassData = sc.get(objectClassTypes, objectData.class_type, false);
if(objClassData){
objClass = objClassData.classInstance;
}
}
if(!objClass){
Logger.error('ObjectManager object class type not found.', objectData);
return false;
}
let objProps = Object.assign(
{config: this.config, events: this.events, dataServer: this.dataServer},
objectData
);
// @TODO - BETA - Create a memory cache for objects information and avoid the data reload and remap.
this.prepareInitialStats(objProps);
try {
let objectInstance = new objClass(objProps);
this.attachToAnimations(objectInstance);
if(objectInstance.multiple){
objectInstance.objProps = objProps;
let subObjClass = false;
if(objectClassTypes && objectInstance.childObjectType){
let objClassData = sc.get(objectClassTypes, objectInstance.childObjectType, false);
if(objClassData){
subObjClass = objClassData.classInstance;
}
}
if(objectInstance.childObjectType && !subObjClass){
Logger.error('ObjectManager sub-object class type not found.', {
objectClassKey: objectData.object_class_key,
childObjectType: objectInstance.childObjectType
});
return false;
}
objectInstance.classInstance = subObjClass;
}
this.enrichWithMultipleAnimationsData(objectData, objectInstance);
this.attachToMessagesListeners(objectInstance, objectData);
this.prepareAssetsPreload(objectData);
await this.runAdditionalSetup(objectInstance, objectData);
this.events.emit('reldens.afterRunAdditionalSetup', {
objectInstance,
objectData,
objectsManager: this
});
this.roomObjects[objectInstance.objectIndex] = objectInstance;
if(!this.roomObjectsByLayer[objectData.layer_name]){
this.roomObjectsByLayer[objectData.layer_name] = {};
}
this.roomObjectsByLayer[objectData.layer_name][objectData.id] = objectInstance;
return objectInstance;
} catch (error) {
Logger.error('Error while generating object.', error, objectData);
return false;
}
}
/**
* @param {Object} objProps
*/
prepareInitialStats(objProps)
{
//Logger.debug('Preparing initial stats:', objProps);
let stats = sc.get(objProps, 'related_objects_stats', []);
if(0 === stats.length){
return;
}
if(!objProps.initialStats){
objProps.initialStats = {};
}
for(let stat of stats){
objProps.initialStats[stat.related_stats.key] = stat.value;
}
}
/**
* @param {Object} objectInstance
* @param {Object} objectData
* @returns {Promise<boolean|undefined>}
*/
async runAdditionalSetup(objectInstance, objectData)
{
if(!sc.isObjectFunction(objectInstance, 'runAdditionalSetup')){
return false;
}
objectInstance.runAdditionalSetup({objectsManager: this, objectData});
}
/**
* @param {Object} objectData
*/
prepareAssetsPreload(objectData)
{
//Logger.debug('Preparing object assets preload:', objectData);
if(!objectData.related_objects_assets){
return false;
}
for(let asset of objectData.related_objects_assets){
// @NOTE: assets can be different types, sprite sheets, images, atlas, etc. We push them
// here to later send these to the client along with the sceneData.
this.preloadAssets[(asset.object_id || '')+(asset.object_asset_id || '')] = asset;
}
}
/**
* @param {Object} objectInstance
* @param {Object} objectData
*/
attachToMessagesListeners(objectInstance, objectData)
{
// prepare an object for room messages:
if(!sc.hasOwn(objectInstance, 'listenMessages')){
return false;
}
this.listenMessages = true;
this.listenMessagesObjects[objectData.id] = objectInstance;
}
/**
* @param {Object} objectData
* @param {Object} objectInstance
*/
enrichWithMultipleAnimationsData(objectData, objectInstance)
{
if(!objectData.related_objects_animations){
return false;
}
objectInstance.multipleAnimations = {};
for(let anim of objectData.related_objects_animations){
// @NOTE: assets can be different types, sprite sheets, images, atlas, etc.
// We push them here to later send these to the client along with the sceneData.
objectInstance.multipleAnimations[anim.animationKey] = sc.toJson(anim.animationData);
}
}
/**
* @param {Object} objectInstance
*/
attachToAnimations(objectInstance)
{
// if the result is an animation instance, then we can include it in the list to send it to the client:
if(sc.hasOwn(objectInstance, 'isAnimation') || sc.hasOwn(objectInstance, 'hasAnimation')){
this.objectsAnimationsData[objectInstance.objectIndex] = objectInstance.clientParams;
}
}
/**
* The object instance is created when the world is created since we don't need to overload the server by creating
* every object defined if it is not going to be used.
*
* @param {string} objectIndex
* @returns {Object|false}
*/
getObjectData(objectIndex)
{
if(sc.hasOwn(this.roomObjects, objectIndex)){
return this.roomObjects[objectIndex];
}
return false;
}
/**
* @param {Object} rewardObject
*/
removeObjectData(rewardObject)
{
this.removeFromPreloadAssetsArray(rewardObject.objects_assets);
delete this.roomObjects[rewardObject.objectIndex];
delete this.objectsAnimationsData[rewardObject.objectIndex];
}
/**
* @param {Array<Object>} objectAssets
*/
removeFromPreloadAssetsArray(objectAssets)
{
for(let objectAsset of objectAssets){
delete this.preloadAssets[(objectAsset.object_id || '')+(objectAsset.object_asset_id || '')];
}
}
}
module.exports.ObjectsManager = ObjectsManager;