reldens
Version:
Reldens - MMORPG Platform
499 lines (478 loc) • 21 kB
JavaScript
/**
*
* Reldens - MapsImporter
*
*/
const { ExtrudeTileset } = require('./tile-extruder');
const { FileHandler } = require('../../game/server/file-handler');
const { Logger, sc } = require('@reldens/utils');
class MapsImporter
{
constructor(serverManager)
{
this.serverManager = serverManager;
this.config = this.serverManager?.configManager;
this.roomsRepository = this.serverManager?.dataServer?.getEntity('rooms');
this.roomsChangePointsRepository = this.serverManager?.dataServer?.getEntity('roomsChangePoints');
this.roomsReturnPointsRepository = this.serverManager?.dataServer?.getEntity('roomsReturnPoints');
this.importAssociationsForChangePoints = false;
this.importAssociationsRecursively = false;
this.verifyTilesetImage = true;
this.maps = {};
this.mapsJson = {};
this.mapsImages = {};
this.roomsChangePoints = {};
this.roomsReturnPoints = {};
this.createdRooms = {};
this.generatedDataPath = this.serverManager?.themeManager?.projectGeneratedDataPath;
this.errorCode = '';
}
async import(data)
{
if(!data){
Logger.critical('Import data not found.');
return false;
}
if(!this.validRepositories(['roomsRepository', 'roomsChangePointsRepository', 'roomsReturnPointsRepository'])){
return false;
}
this.maps = data?.maps;
this.importAssociationsForChangePoints = 1 === Number(sc.get(data, 'importAssociationsForChangePoints', 0));
this.importAssociationsRecursively = 1 === Number(sc.get(data, 'importAssociationsRecursively', 0));
this.verifyTilesetImage = 1 === Number(sc.get(data, 'verifyTilesetImage', 1));
this.automaticallyExtrudeMaps = 1 === Number(sc.get(data, 'automaticallyExtrudeMaps', 0));
this.setImportFilesPath(data);
if(this.maps){
if(!await this.loadValidMaps()){
return false;
}
}
if(this.mapsJson){
if(!await this.createRooms()){
return false;
}
}
return true;
}
setImportFilesPath(data)
{
let generatedDataPath = String(sc.get(data, 'generatedDataPath', ''));
if('' !== generatedDataPath){
this.generatedDataPath = generatedDataPath;
}
let relativeGeneratedDataPath = String(sc.get(data, 'relativeGeneratedDataPath', ''));
if('' !== relativeGeneratedDataPath){
this.generatedDataPath = FileHandler.joinPaths(this.serverManager.projectRoot, relativeGeneratedDataPath);
}
}
validRepositories(repositoriesKey)
{
for(let repositoryKey of repositoriesKey){
if(!this[repositoryKey]){
Logger.critical('Repository "'+repositoryKey+'" not found.');
return false;
}
}
return true;
}
async loadValidMaps()
{
for(let mapTitle of Object.keys(this.maps)){
let mapExists = await this.roomsRepository.loadOneBy('name', this.maps[mapTitle]);
if(mapExists){
Logger.error('Map with name "'+this.maps[mapTitle]+'" already exists.');
this.errorCode = 'mapExists';
return false;
}
if(!this.loadMapByTitle(mapTitle)){
return false;
}
}
return true;
}
loadMapByTitle(mapTitle, useTitleAsFileName = false)
{
let mapName = useTitleAsFileName ? mapTitle : this.maps[mapTitle];
let fullPath = FileHandler.joinPaths(this.generatedDataPath, mapName + '.json');
let fileContent = FileHandler.exists(fullPath) ? FileHandler.readFile(fullPath) : '';
if('' === fileContent){
Logger.critical('File "' + mapName + '.json" not found.', fullPath);
this.errorCode = 'mapJsonNotFound';
return false;
}
let jsonContent = sc.toJson(fileContent);
if(this.verifyTilesetImage){
let tilesets = jsonContent?.tilesets || [];
if(0 === tilesets.length){
Logger.critical('File "' + mapName + '.json" must have at least one tileset.');
this.errorCode = 'mapJsonMissingTileset';
return false;
}
for(let tileset of tilesets){
if(!tileset.image){
Logger.critical('File "' + mapName + '.json" must have at least one tileset with an image.');
this.errorCode = 'mapJsonMissingTilesetImage';
return false;
}
let checkImagePath = FileHandler.joinPaths(this.generatedDataPath, tileset.image);
if(!FileHandler.exists(checkImagePath)){
Logger.critical('File "' + checkImagePath + '" not found.');
this.errorCode = 'mapTilesetImageNotFound';
return false;
}
if(!this.mapsImages[mapName]){
this.mapsImages[mapName] = [];
}
if(-1 === this.mapsImages[mapName].indexOf(tileset.image)){
this.mapsImages[mapName].push(tileset.image);
}
}
}
this.mapsJson[mapName] = sc.deepJsonClone(jsonContent);
return true;
}
async createRooms()
{
for(let mapTitle of Object.keys(this.maps)){
if(!await this.createRoomByMapTitle(mapTitle)){
return false;
}
}
return true;
}
async copyExtrudedFiles(fileNames)
{
for(let fileName of fileNames){
let from = FileHandler.joinPaths(this.generatedDataPath, fileName);
let to = FileHandler.joinPaths(this.generatedDataPath, fileName.replace('.png', '-original.png'));
let result = await FileHandler.copyFile(from, to);
Logger.debug('Copy file "' + from + '" to "' + to + '".');
if(!result){
Logger.critical('Could not copy file "' + from + '" to "' + to + '".');
return false;
}
}
return true;
}
async createRoomByMapTitle(mapTitle, useTitleAsFileName = false)
{
if(this.createdRooms[mapTitle]){
return this.createdRooms[mapTitle];
}
let mapName = useTitleAsFileName ? mapTitle : this.maps[mapTitle];
let mapFileName = mapName + '.json';
let mapsImages = this.mapsImages[mapName] || [];
if(this.automaticallyExtrudeMaps){
let createExtrudeBackups = await this.copyExtrudedFiles(mapsImages);
if(!createExtrudeBackups){
this.errorCode = 'createExtrudeBackupsError';
return false;
}
let mapJson = this.mapsJson[mapName];
for(let image of mapsImages){
let inputPath = image.replace('.png', '-original.png');
let imageObject = await ExtrudeTileset(
mapJson.tilewidth,
mapJson.tileheight,
FileHandler.joinPaths(this.generatedDataPath, inputPath)
);
try {
await imageObject.write(FileHandler.joinPaths(this.generatedDataPath, image));
} catch (error) {
Logger.critical('Image object could not be saved as file.', image, error);
this.errorCode = 'imageObjectSaveError';
return false;
}
for(let tileset of mapJson.tilesets){
if(tileset.image !== image){
continue;
}
tileset.margin = this.config.getWithoutLogs('maps/extrude/margin', 1);
tileset.spacing = this.config.getWithoutLogs('maps/extrude/spacing', 2);
tileset.imagewidth = imageObject.bitmap.width;
tileset.imageheight = imageObject.bitmap.height;
}
}
await FileHandler.updateFileContents(
FileHandler.joinPaths(this.generatedDataPath, mapFileName),
JSON.stringify(mapJson)
);
}
let filesCopied = await this.copyFiles([mapFileName, ...mapsImages]);
if(!filesCopied){
Logger.critical('Could not copy map files for "' + mapName + '" / "' + mapFileName + '".');
this.errorCode = 'copyMapFilesError';
return false;
}
let roomCreateData = {
name: mapName,
title: this.fetchRoomTitle(mapName, mapTitle),
map_filename: mapFileName,
scene_images: mapsImages.join(',')
};
let result = false;
try {
result = await this.roomsRepository.create(roomCreateData);
} catch (error) {
Logger.critical('Map "' + mapName + '" could not be saved. Error: ' + error.message, roomCreateData);
this.errorCode = 'mapSaveError';
return false;
}
if(!result){
Logger.critical('Could not create room with title "' + roomCreateData.title + '".', roomCreateData);
this.errorCode = 'createRoomError';
return false;
}
this.createdRooms[mapName] = result;
Logger.info('Created room "'+mapName+'".');
await this.createRoomsChangePoints(mapName, result);
await this.createRoomsReturnPoints(result);
return this.createdRooms[mapName];
}
fetchRoomTitle(mapName, mapTitle)
{
let mapJson = this.mapsJson[mapName];
if(sc.isArray(mapJson.properties)){
for(let property of mapJson.properties){
if(property.name === 'mapTitle'){
return property.value;
}
}
}
return mapTitle;
}
async copyFiles(fileNames)
{
for(let fileName of fileNames){
let from = FileHandler.joinPaths(this.generatedDataPath, fileName);
let to = FileHandler.joinPaths(this.serverManager.themeManager.projectAssetsPath, 'maps', fileName);
let result = await FileHandler.copyFile(from, to);
if(!result){
Logger.critical('Could not copy file "' + from + '" to "' + to + '".');
return false;
}
let toDist = FileHandler.joinPaths(this.serverManager.themeManager.assetsDistPath, 'maps', fileName);
let resultDist = await FileHandler.copyFile(from, toDist);
if(!resultDist){
Logger.critical('Could not copy file "' + from + '" to "' + to + '".');
return false;
}
}
return true;
}
async createRoomsChangePoints(mapName, createdRoom)
{
Logger.info('Creating rooms change points for "'+mapName+'".');
let mapJson = this.mapsJson[mapName];
if(!sc.isArray(mapJson.layers)){
Logger.info('Warning Map JSON not found for "'+mapName+'".');
return false;
}
let changePointForKey = 'change-point-for-';
for(let layer of mapJson.layers){
if(!sc.isArray(layer.properties)){
Logger.info('Layer "'+layer.name+'" properties is not an array on "'+mapName+'".');
continue;
}
let roomChangePoints = [];
for(let property of layer.properties){
if(0 === property.name.indexOf(changePointForKey)){
roomChangePoints.push(property);
}
}
Logger.info(
'Found '+roomChangePoints.length+' rooms change points on "'+mapName+'".',
changePointForKey,
layer.properties
);
for(let changePointData of roomChangePoints){
let nextRoomName = changePointData.name.replace(changePointForKey, '');
let nextRoomModel = await this.provideRoomByName(nextRoomName);
if(!nextRoomModel){
Logger.error(
'Could not find room "'+nextRoomName+'" while creating change points.',
changePointData
);
continue;
}
let roomChangePointCreateData = {
room_id: createdRoom.id,
tile_index: changePointData.value,
next_room_id: nextRoomModel.id
};
let result = await this.roomsChangePointsRepository.create(roomChangePointCreateData);
if(!result){
Logger.critical(
'Could not create rooms change point for "'+nextRoomName+'".',
roomChangePointCreateData
);
continue;
}
Logger.info('Created rooms change point with ID "'+result.id+'".', roomChangePointCreateData);
}
}
}
async createRoomsReturnPoints(createdRoom)
{
Logger.info('Creating room return points for "'+createdRoom.name+'".');
let currentRoomMapJson = this.mapsJson[createdRoom.name];
if(!sc.isArray(currentRoomMapJson.layers)){
Logger.info('Warning Map JSON not found for "'+createdRoom.name+'".');
return false;
}
let returnPointForKey = 'return-point-for-';
let returnPointForDefaultKey = 'return-point-for-default-';
for(let layer of currentRoomMapJson.layers){
if(!sc.isArray(layer.properties)){
Logger.info('Layer "'+layer.name+'" properties is not an array on "'+createdRoom.name+'".');
continue;
}
let roomReturnPoints = this.fetchReturnPointsFromLayer(layer, returnPointForKey);
Logger.info(
'Found '+roomReturnPoints.length+' rooms return points on "'+createdRoom.name+'".',
layer.properties
);
for(let i = 0; i < roomReturnPoints.length; i++){
let returnPointData = roomReturnPoints[i];
let isDefault = -1 !== returnPointData.name.indexOf(returnPointForDefaultKey);
let returnPointForName = returnPointData.name.replace(
isDefault ? returnPointForDefaultKey : returnPointForKey,
''
);
let roomModel = this.createdRooms[returnPointForName] || await this.roomsRepository.loadOneBy(
'name',
returnPointForName
);
if(!roomModel){
Logger.error(
'Could not find room "'+returnPointForName+'" while creating return point.',
returnPointData
);
continue;
}
await this.saveReturnPoint(
isDefault,
createdRoom,
roomModel,
returnPointData,
currentRoomMapJson,
returnPointForName
);
}
}
}
fetchReturnPointsFromLayer(layer)
{
let key = 'return-point-';
let keyFor = key + 'for-';
let keyX = key + 'x-';
let keyY = key + 'y-';
let keyPosition = key + 'position-';
let keyIsDefault = key + 'isDefault-';
let roomReturnPoints = [];
let roomReturnPointsIndex = {};
let roomReturnPointsX = {};
let roomReturnPointsY = {};
let roomReturnPointsPosition = {};
let roomReturnPointsIsDefault = {};
// get all the data from the layer that could be in any order
for(let property of layer.properties){
let normalizedName = property.name
.replace(keyFor, '')
.replace(keyX, '')
.replace(keyY, '')
.replace(keyPosition, '')
.replace(keyIsDefault, '')
.replace('default-', '');
if(0 === property.name.indexOf(keyFor)){
roomReturnPointsIndex[normalizedName] = property;
}
if(0 === property.name.indexOf(keyX)){
roomReturnPointsX[normalizedName] = property;
}
if(0 === property.name.indexOf(keyY)){
roomReturnPointsY[normalizedName] = property;
}
if(0 === property.name.indexOf(keyPosition)){
roomReturnPointsPosition[normalizedName] = property;
}
if(0 === property.name.indexOf(keyIsDefault)){
roomReturnPointsIsDefault[normalizedName] = property;
}
}
// map only points with indexes:
for(let propertyName of Object.keys(roomReturnPointsIndex)){
let newPoint = {
name: propertyName,
value: roomReturnPointsIndex[propertyName].value
};
if(roomReturnPointsX[propertyName]){
newPoint.x = roomReturnPointsX[propertyName].value;
}
if(roomReturnPointsY[propertyName]){
newPoint.y = roomReturnPointsY[propertyName].value;
}
if(roomReturnPointsPosition[propertyName]){
newPoint.position = roomReturnPointsPosition[propertyName].value;
}
if(roomReturnPointsIsDefault[propertyName]){
newPoint.isDefault = roomReturnPointsIsDefault[propertyName].value;
}
roomReturnPoints.push(newPoint);
}
return roomReturnPoints;
}
async saveReturnPoint(isDefault, createdRoom, roomModel, returnPointData, currentRoomMapJson, returnPointForName)
{
// a valid "x" is determined by the map width in points:
let mapWidthInPoints = currentRoomMapJson.width * currentRoomMapJson.tilewidth;
let x = (returnPointData.x * currentRoomMapJson.tilewidth) + currentRoomMapJson.tilewidth;
if(mapWidthInPoints < x){
x = mapWidthInPoints - (currentRoomMapJson.tilewidth);
}
// a valid "y" is determined by the specified return position (top or down for now):
let playerY = 'down' === returnPointData.position
? currentRoomMapJson.tileheight
: -currentRoomMapJson.tileheight;
let y = (returnPointData.y * currentRoomMapJson.tileheight) + playerY;
// create the room return point:
let roomReturnPointCreateData = {
// destination room id, for example going from the map in to the house this will be the house ID:
room_id: createdRoom.id,
// display direction:
direction: returnPointData.position,
// x position in map in pixels + 1 tile because the player occupies one tile space:
x,
// y position in map in pixels + 1 tile because the player occupies one tile space:
y,
// if is the default place where the player starts when selecting the map:
is_default: Boolean(isDefault || returnPointData.isDefault),
// room id where the change point was hit, for example the town ID:
from_room_id: isDefault ? null : roomModel.id
};
let result = await this.roomsReturnPointsRepository.create(roomReturnPointCreateData);
if(!result){
Logger.critical(
'Could not create rooms return point for "' + returnPointForName + '".',
roomReturnPointCreateData
);
return false;
}
Logger.info('Created rooms return point with ID "' + result.id + '".', roomReturnPointCreateData);
return true;
}
async provideRoomByName(roomName)
{
if(this.importAssociationsForChangePoints){
if(!this.mapsJson[roomName]){
this.loadMapByTitle(roomName, true);
}
if(this.createdRooms[roomName]){
return this.createdRooms[roomName];
}
return await this.createRoomByMapTitle(roomName, true);
}
return this.roomsRepository.loadOneBy('name', roomName);
}
}
module.exports.MapsImporter = MapsImporter;