jsge
Version:
Javascript Game Engine
1,476 lines (1,320 loc) • 367 kB
JavaScript
/******/ var __webpack_modules__ = ({
/***/ "./modules/assetsm/src/AssetsManager.js":
/*!**********************************************!*\
!*** ./modules/assetsm/src/AssetsManager.js ***!
\**********************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AssetsManager)
/* harmony export */ });
const PROGRESS_EVENT_TYPE = {
loadstart: "loadstart",
progress: "progress",
abort: "abort",
error: "error",
load: "load",
timeout: "timeout"
}
const ERROR_MESSAGES = {
// Critical
LOADER_NOT_REGISTERED: " loader is not registered.",
RECURSION_ERROR: "Too much recursion. Stop iteration.",
NOT_CORRECT_METHOD_TYPE: "uploadMethod should be instance of Promise and return upload result value",
XML_FILE_EXTENSION_INCORRECT: " AtlasXML file extension is incorrect, only .xml file supported",
TILESET_FILE_EXTENSION_INCORRECT: " tileset file extension is not correct, only .tsj, .json, .tsx, .xml files are supported",
TILEMAP_FILE_EXTENSION_INCORRECT: " tilemap file extension is not correct, only .tmj, .json, .tmx, .xml files are supported",
INPUT_PARAMS_ARE_INCORRECT: " fileKey and url should be provided",
// Non critical
ATLAS_IMAGE_LOADING_FAILED: "Error loading atlas image ",
TILESET_LOADING_FAILED: "Error loading related tileset ",
TILEMAP_LOADING_FAILED: "Error loading tilemap ",
AUDIO_LOADING_FAILED: "Error loading audio ",
IMAGE_LOADING_FAILED: "Error loading image ",
XML_FORMAT_INCORRECT: " XML format is not correct.",
}
const FILE_FORMAT = {
JSON: "JSON",
XML: "XML",
UNKNOWN: "UNKNOWN"
}
class Loader {
/**
* @type {string}
*/
#fileType;
/**
* @type { (...args: any[]) => Promise<void> }
*/
#uploadMethod;
/**
* name: url
* @type { Map<string, string[]>}
*/
#loadingQueue = new Map();
/**
* name: file
* @type { Map<string, any>}
*/
#store = new Map();
/**
*
* @param {string} name
* @param {Function} uploadMethod
*/
constructor(name, uploadMethod) {
this.#fileType = name;
this.#uploadMethod = (key, url, ...args) => {
const upload = uploadMethod(key, url, ...args);
if (upload instanceof Promise) {
return upload.then((uploadResult) => this.#processUploadResult(uploadResult, key));
} else {
throw new TypeError(ERROR_MESSAGES.NOT_CORRECT_METHOD_TYPE);
}
}
}
/**
*
* @param {null | Object} uploadResult
* @param {string} key
* @returns {Promise<void>}
*/
#processUploadResult = (uploadResult, key) => {
return new Promise((resolve, reject) => {
if ( !uploadResult && uploadResult !== null ) {
Warning("AssetsManager: uploadMethod for " + this.#fileType + " returns incorrect value");
}
this.#addUploadResultValue(key, uploadResult);
this.#removeUploadFromQueue(key);
resolve();
});
}
/**
*
* @param {string} key
* @param {*} value
*/
#addUploadResultValue(key, value) {
this.#store.set(key, value);
}
/**
*
* @param {string} key
*/
#removeUploadFromQueue(key) {
this.#loadingQueue.delete(key);
}
get filesWaitingForUpload() {
return this.#loadingQueue.size;
}
get loadingQueue() {
return this.#loadingQueue
};
get uploadMethod() {
return this.#uploadMethod;
}
/**
*
* @param {string} key
* @param {string[]} paramsArr
*/
_addFile = (key, paramsArr) => {
if (this.#loadingQueue.has(key)) {
Warning("AssetsManager: File " + this.#fileType + " with key " + key + " is already added");
}
this.#loadingQueue.set(key, paramsArr);
}
/**
*
* @param {string} key
* @returns {boolean}
*/
_isFileInQueue = (key) => {
return this.#loadingQueue.has(key);
}
/**
*
* @param {string} key
* @returns {any}
*/
_getFile = (key) => {
return this.#store.get(key);
}
}
/**
* This class is used to preload
* tilemaps, tilesets, images and audio,
* and easy access loaded files by keys
*/
class AssetsManager {
/**
* @type {number}
*/
#MAX_LOADING_CYCLES = 5;
/**
* @type {EventTarget}
*/
#emitter = new EventTarget();
/**
* @type { Map<string, Loader>}
*/
#registeredLoaders = new Map();
/**
* @type {number}
*/
#itemsLoaded = 0;
constructor() {
this.registerLoader("Audio", this._loadAudio);
this.registerLoader("Image", this._loadImage);
this.registerLoader("TileMap", this._loadTileMap);
this.registerLoader("TileSet", this._loadTileSet);
this.registerLoader("AtlasImageMap", this._loadAtlasImage);
this.registerLoader("AtlasXML", this._loadAtlasXml);
}
/**
* @returns {number}
*/
get filesWaitingForUpload() {
let files = 0;
Array.from(this.#registeredLoaders.values()).map((loader) => files += loader.filesWaitingForUpload);
return files;
}
/**
* Register a new file type to upload. Method will dynamically add new methods.
* @param {string} fileTypeName
* @param {Function=} loadMethod loadMethod should return Promise<result>
* @returns {void}
*/
registerLoader = (fileTypeName, loadMethod = this._defaultUploadMethod) => {
this["add" + fileTypeName] = (key, url, ...args) => {
this.addFile(fileTypeName, key, url, ...args);
}
this["get" + fileTypeName] = (key) => {
return this.getFile(fileTypeName, key);
}
this["is" + fileTypeName + ["InQueue"]] = (key) => {
return this.isFileInQueue(fileTypeName, key);
}
const registeredFileType = this.#registeredLoaders.get(fileTypeName) || new Loader(fileTypeName, loadMethod);
this.#registeredLoaders.set(fileTypeName, registeredFileType);
}
/**
* Execute load audio, images from tilemaps and images queues
* @returns {Promise<void>}
*/
preload() {
this.#dispatchLoadingStart();
return new Promise(async(resolve, reject) => {
this.#uploadFilesRecursive().then(() => {
this.#dispatchLoadingFinish();
resolve();
}).catch((err) => {
reject(err);
});
});
}
/**
*
* @param {number} loadCount
* @returns {Promise<void>}
*/
#uploadFilesRecursive(loadCount = 0) {
return this.#uploadFiles().then(() => {
if (this.filesWaitingForUpload === 0) {
return Promise.resolve();
} else {
loadCount++;
if (loadCount > this.#MAX_LOADING_CYCLES) {
const err = new Error(ERROR_MESSAGES.RECURSION_ERROR);
this.#dispatchLoadingError(err);
return Promise.reject(new Error(ERROR_MESSAGES.RECURSION_ERROR));
} else {
return this.#uploadFilesRecursive(loadCount);
}
}
});
}
/**
*
* @returns {Promise<void>}
*/
#uploadFiles() {
return new Promise((resolve, reject) => {
/** @type {Promise<void>[]} */
let uploadPromises = [];
Array.from(this.#registeredLoaders.values()).forEach((fileType) => {
Array.from(fileType.loadingQueue.entries()).forEach((key_value) => {
/** @type {Promise<void>} */
const p = new Promise((res, rej) => fileType.uploadMethod(key_value[0], ...key_value[1]).then(() => res()));
uploadPromises.push(p);
});
});
Promise.allSettled(uploadPromises).then((results) => {
for (const result of results) {
if (result.status === "rejected") {
const error = result.reason;
// incorrect method is a critical issue
if (this.#isUploadErrorCritical(error)) {
reject(error);
} else {
Warning("AssetsManager: " + error.message);
this.#dispatchLoadingError(error);
}
}
}
resolve();
});
});
}
addEventListener(type, fn, ...args) {
if (!PROGRESS_EVENT_TYPE[type]) {
Warning("AssetsManager: Event type should be one of the ProgressEvent.type");
} else {
this.#emitter.addEventListener(type, fn, ...args);
}
}
removeEventListener(type, fn, ...args) {
this.#emitter.removeEventListener(type, fn, ...args);
}
/**
* Loads image atlas xml
* @param {string} key
* @param {string} url
* @returns {Promise<HTMLElement | Error>}
*/
_loadAtlasXml = (key, url) => {
this.#checkXmlUrl(url);
return fetch(url)
.then(response => response.text())
.then(str => new window.DOMParser().parseFromString(str, "text/xml"))
.then(data => {
const atlas = data.documentElement || data.activeElement,
atlasImagePath = atlas.attributes.getNamedItem("imagePath"),
childrenNodes = atlas.children;
if (atlasImagePath) {
const relativePath = this.#calculateRelativePath(url);
this.addAtlasImageMap(key, relativePath + atlasImagePath.value, childrenNodes, relativePath);
return atlas;
} else {
const err = new Error(key + ERROR_MESSAGES.XML_FORMAT_INCORRECT);
this.#dispatchLoadingError(err);
return err;
// return Promise.reject(err);
}
});
}
_loadAtlasImage = (key, url, atlasChildNodes, cors = "anonymous") => {
return new Promise((resolve, reject) => {
const img = new Image(),
imageAtlas = new Map(),
tempCanvas = document.createElement("canvas"),
tempCtx = tempCanvas.getContext("2d");
img.crossOrigin = cors;
img.onload = () => {
const imageBitmapPromises = [];
let imageAtlasKeys = [];
// fix dimensions
tempCanvas.width = img.width;
tempCanvas.height = img.height;
tempCtx.drawImage(img, 0, 0);
for(let childNode of atlasChildNodes) {
const nodeAttr = childNode.attributes,
fullName = nodeAttr.getNamedItem("name").value,
name = fullName.includes(".") ? fullName.split(".")[0] : fullName, // remove name ext
x = nodeAttr.getNamedItem("x").value,
y = nodeAttr.getNamedItem("y").value,
width = nodeAttr.getNamedItem("width").value,
height = nodeAttr.getNamedItem("height").value;
// images are not cropped correctly in the mozilla@124.0, issue:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1797567
// getImageData() crop them manually before
// creating imageBitmap from atlas
imageBitmapPromises.push(createImageBitmap(tempCtx.getImageData(x, y, width, height), {premultiplyAlpha:"premultiply"}));
imageAtlasKeys.push(name);
}
this.#dispatchCurrentLoadingProgress();
Promise.all(imageBitmapPromises).then((results) => {
results.forEach((image, idx) => {
const name = imageAtlasKeys[idx];
imageAtlas.set(name, image);
this.addImage(name, "empty url", image);
});
tempCanvas.remove();
resolve(imageAtlas);
});
};
img.onerror = () => {
const err = new Error(ERROR_MESSAGES.ATLAS_IMAGE_LOADING_FAILED + url);
this.#dispatchLoadingError(err);
resolve(null);
//reject(err);
};
img.src = url;
});
}
/**
* Loads tileset
* @param {string} key
* @param {string} url
* @param {number} gid
* @param {string} relativePath
* @returns {Promise<Object>}
*/
_loadTileSet = (key, url, gid=1, relativePath) => {
const file_format = this.#checkTilesetUrl(url),
loadPath = relativePath ? relativePath + url : url;
if (file_format === FILE_FORMAT.JSON) {
return fetch(loadPath)
.then((response) => response.json())
.then((data) => this._processTilesetData(data, relativePath, gid, url))
.catch(() => {
const err = new Error(ERROR_MESSAGES.TILESET_LOADING_FAILED + url);
this.#dispatchLoadingError(err);
return Promise.resolve(null);
//return Promise.reject(err);
});
} else if (file_format === FILE_FORMAT.XML) {
return fetch(loadPath)
.then(response => response.text())
.then(str => new window.DOMParser().parseFromString(str, "text/xml"))
.then(xmlString => this._processTilesetXmlData(xmlString.documentElement))
.then((data) => this._processTilesetData(data, relativePath, gid, url))
.catch(() => {
const err = new Error(ERROR_MESSAGES.TILESET_LOADING_FAILED + url);
this.#dispatchLoadingError(err);
return Promise.resolve(null);
});
} else {
return Promise.reject(loadPath + ERROR_MESSAGES.TILEMAP_FILE_EXTENSION_INCORRECT);
}
}
/**
*
* @param {Object} doc
* @returns {Object}
*/
_processTilesetXmlData = (doc) => {
const tilesetData = {
columns: Number(doc.attributes?.columns?.value),
name: doc.attributes?.name?.value,
tilecount: Number(doc.attributes?.tilecount?.value),
tiledversion: doc.attributes?.tiledversion?.value,
tileheight: Number(doc.attributes?.tileheight?.value),
tilewidth: Number(doc.attributes?.tilewidth?.value),
version: doc.attributes?.version?.value,
margin: doc.attributes?.margin ? Number(doc.attributes.margin.value) : 0,
spacing: doc.attributes?.spacing ? Number(doc.attributes.margin.value) : 0,
type: doc.tagName
};
this._processTilesetXmlChildData(tilesetData, doc.childNodes);
return tilesetData;
}
/**
*
* @param {any} tilesetData
* @param {any} nodes
* @returns {void}
*/
_processTilesetXmlChildData(tilesetData, nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i],
name = node.nodeName;
if (name === "image") {
tilesetData.image = node?.attributes?.source?.value;
tilesetData.imagewidth = node?.attributes?.width ? Number(node.attributes.width.value) : 0;
tilesetData.imageheight = node?.attributes?.height ? Number(node.attributes.height.value) : 0;
} else if (name === "tileoffset") {
tilesetData.tileoffset = {
x: Number(node.attributes.x.value),
y: Number(node.attributes.y.value)
};
} else if (name === "tile") {
if (!tilesetData.tiles) {
tilesetData.tiles = [];
}
//add boundaries / animations
const tile = {
id: Number(node.attributes?.id?.value)
}
const childN = node.childNodes;
for (let j = 0; j < childN.length; j++) {
const child = childN[j],
childName = child.nodeName;
if (childName === "objectgroup") {
tile.objectgroup = {
type: childName
}
if (child.attributes?.id) {
tile.objectgroup.id = Number(child.attributes?.id?.value);
}
if (child.attributes?.draworder) {
tile.objectgroup.draworder = child.attributes.draworder.value;
}
if (child.attributes?.opacity) {
tile.objectgroup.opacity = child.attributes.opacity.value;
}
if (child.attributes?.x && child.attributes?.y) {
tile.objectgroup.x = child.attributes.x.value;
tile.objectgroup.y = child.attributes.y.value;
}
tile.objectgroup.objects = [];
const objects = child.childNodes;
for (let k = 0; k < objects.length; k++) {
const obj = objects[k];
if (obj.nodeName === "object") {
const objInc = {
id: Number(obj.attributes?.id?.value),
visible: obj.attributes.visible && obj.attributes.visible.value === "0" ? false : true,
x: Number(obj.attributes?.x?.value),
y: Number(obj.attributes?.y?.value),
rotation: obj.attributes?.rotation ? Number(obj.attributes.rotation.value) :0,
};
if (obj.attributes?.width) {
objInc.width = Number(obj.attributes.width.value);
}
if (obj.attributes?.height) {
objInc.height = Number(obj.attributes.height.value);
}
const childObjects = obj.childNodes;
if (childObjects && childObjects.length > 0) {
for (let n = 0; n < childObjects.length; n++) {
const childObj = childObjects[n];
if (childObj.nodeName === "ellipse") {
objInc.ellipse = true;
} else if (childObj.nodeName === "point") {
objInc.point = true;
} else if (childObj.nodeName === "polygon") {
const points = childObj.attributes?.points?.value;
if (points && points.length > 0) {
const pointsArr = points.split(" ").map((point) => {
const [x, y] = point.split(",");
return {x:Number(x), y:Number(y)};
});
objInc.polygon = pointsArr;
}
}
}
}
tile.objectgroup.objects.push(objInc);
}
}
} else if (childName === "animation") {
//
tile.animation = [];
const frames = child.childNodes;
for (let t = 0; t < frames.length; t++) {
const frame = frames[t];
if (frame.nodeName === "frame") {
const frameObject = {
tileid: Number(frame.attributes?.tileid?.value),
duration: Number(frame.attributes?.duration?.value)
}
tile.animation.push(frameObject);
}
}
}
}
tilesetData.tiles.push(tile);
}
}
}
/**
*
* @param {Object} data
* @param {string} relativePath
* @param {number=} gid
* @param {string=} source
* @returns {Promise<Object>}
*/
_processTilesetData = (data, relativePath, gid, source) => {
const {name, image } = data;
if (name && image && !this.isFileInQueue("Image", name)) {
this.addImage(name, relativePath ? relativePath + image : image);
}
if (gid) {
data.firstgid = gid;
}
// if it is an external file
if (source) {
data.source = source;
}
return Promise.resolve(data);
}
/**
*
* @param {string} key
* @param {string} url
* @returns {Promise<any>}
*/
_defaultUploadMethod = (key, url) => {
return fetch(url);
}
/**
* Loads tilemap file and related data
* @param {string} key
* @param {string} url
* @param {boolean} [attachTileSetData = true] - indicates, whenever tilesetData is attached, or will be loaded separately
* @returns {Promise}
*/
_loadTileMap = (key, url, attachTileSetData = true) => {
const file_format = this.#checkTilemapUrl(url);
let fetchData;
if (file_format === FILE_FORMAT.JSON) {
fetchData = fetch(url)
.then((response) => response.json())
.then((data) => this._processTileMapData(data, url, attachTileSetData))
.catch((err) => {
if (err.message.includes("JSON.parse:")) {
err = new Error(ERROR_MESSAGES.TILEMAP_LOADING_FAILED + url);
}
this.#dispatchLoadingError(err);
return Promise.resolve(null);
//return Promise.reject(err);
});
} else if (FILE_FORMAT.XML) {
fetchData = fetch(url)
.then((response) => response.text())
.then((rawText) => this._processTileMapXML(rawText))
.then((tilemapData) => this._processTileMapData(tilemapData, url, attachTileSetData))
.catch((err) => {
this.#dispatchLoadingError(err);
return Promise.resolve(null);
//return Promise.reject(err);
});
} else {
return Promise.reject(url + ERROR_MESSAGES.TILEMAP_FILE_EXTENSION_INCORRECT);
}
return fetchData;
}
/**
*
* @param {string} rawText
* @returns {Object}
*/
_processTileMapXML = (rawText) => {
const xmlDoc = new DOMParser().parseFromString(rawText, "text/xml");
/** @type {Object} */
const doc = xmlDoc.documentElement;
const tilemapData = {
type: doc.tagName,
width: Number(doc.attributes?.width?.value),
height: Number(doc.attributes?.height?.value),
infinite: doc.attributes.infinite && doc.attributes.infinite.value === "1" ? true : false,
nextlayerid: Number(doc.attributes?.nextlayerid?.value),
nextobjectid: Number(doc.attributes?.nextobjectid?.value),
orientation: doc.attributes?.orientation?.value,
renderorder: doc.attributes?.renderorder?.value,
tiledversion: doc.attributes?.tiledversion?.value,
tileheight: Number(doc.attributes?.tileheight?.value),
tilewidth: Number(doc.attributes?.tilewidth?.value),
version: doc.attributes?.version?.value,
/** @type {Array<Object>} */
tilesets: [],
/** @type {Array<Object>} */
layers: []
};
const nodes = xmlDoc.documentElement.childNodes;
for (let i = 0; i < nodes.length; i++) {
/** @type {Object} */
const node = nodes[i],
name = node.nodeName;
if (name === "tileset") {
const tileset = {
firstgid: Number(node.attributes?.firstgid?.value)
};
if (node.attributes?.source) { // external tileset (will be loaded later)
tileset.source = node.attributes?.source?.value;
} else {
// inline tileset
tileset.columns = Number(node.attributes?.columns?.value);
if (node.attributes?.margin) {
tileset.margin = Number(node.attributes?.margin?.value);
}
if (node.attributes?.spacing) {
tileset.spacing = node.attributes?.spacing?.value;
}
tileset.name = node.attributes?.name?.value;
tileset.tilecount = Number(node.attributes?.tilecount?.value);
tileset.tilewidth = Number(node.attributes?.tilewidth?.value);
tileset.tileheight = Number(node.attributes?.tileheight?.value);
this._processTilesetXmlChildData(tileset, node.childNodes);
}
tilemapData.tilesets.push(tileset);
} else if (name === "layer") {
const layer = {
height: Number(node.attributes?.height?.value),
id: Number(node.attributes?.id?.value),
name: node.attributes?.name?.value,
width: Number(node.attributes?.width?.value),
data: node.textContent ? node.textContent.trim().split(",").map((val) => Number(val)): null
}
tilemapData.layers.push(layer);
}
}
return tilemapData;
}
/**
*
* @param {any} data
* @param {string} url
* @param {boolean} attachTileSetData
* @returns {Promise<any>}
*/
_processTileMapData = (data, url, attachTileSetData) => {
const relativePath = this.#calculateRelativePath(url);
if (attachTileSetData === true && data.tilesets && data.tilesets.length > 0) {
const tilesetPromises = [];
// upload additional tileset data
data.tilesets.forEach((tileset, idx) => {
const { firstgid, source } = tileset;
if (source) { // external tileset
const loadTilesetPromise = this._loadTileSet("default-" + firstgid, source, firstgid, relativePath)
.then((tilesetData) => {
this.#dispatchCurrentLoadingProgress();
return Promise.resolve(tilesetData);
});
tilesetPromises.push(loadTilesetPromise);
} else { // inline tileset
const loadTilesetPromise = this._processTilesetData(tileset, relativePath)
.then((tilesetData) => {
this.#dispatchCurrentLoadingProgress();
return Promise.resolve(tilesetData);
});
tilesetPromises.push(loadTilesetPromise);
}
});
//attach additional tileset data to tilemap data
return Promise.all(tilesetPromises).then((tilesetDataArray) => {
for (let i = 0; i < tilesetDataArray.length; i++) {
const tilesetData = tilesetDataArray[i];
data.tilesets[i] = tilesetData;
// @depricated
// save backward capability with jsge@1.5.71
data.tilesets[i].data = Object.assign({}, tilesetData);
}
return Promise.resolve(data);
});
} else {
return Promise.resolve(data);
}
}
/**
* Loads audio file
* @param {string} key
* @param {string} url
* @returns {Promise}
*/
_loadAudio = (key, url) => {
return new Promise((resolve) => {
const audio = new Audio(url);
audio.addEventListener("loadeddata", () => {
this.#dispatchCurrentLoadingProgress();
resolve(audio);
});
audio.addEventListener("error", () => {
const err = new Error(ERROR_MESSAGES.AUDIO_LOADING_FAILED + url);
this.#dispatchLoadingError(err);
resolve(null);
//reject(err);
});
});
}
/**
* Loads image file.
* @param {string} key
* @param {string} url
* @param {ImageBitmap=} image - image could be add from another source
* @param {string} [cors="anonymous"] // https://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images
* @returns {Promise}
*/
_loadImage = (key, url, image, cors = "anonymous") => {
return new Promise((resolve, reject) => {
if (image) {
resolve(image);
} else {
const img = new Image();
img.crossOrigin = cors;
img.onload = () => {
// do we need a bitmap? Without creating bitmap images has not premultiplied
// transparent pixels, and in some cases it creates white ages,
// in other - multiply pixels with the background
createImageBitmap(img, {premultiplyAlpha:"premultiply"}).then((imageBitmap) => {
this.#dispatchCurrentLoadingProgress();
resolve(imageBitmap);
});
};
img.onerror = () => {
const err = new Error(ERROR_MESSAGES.IMAGE_LOADING_FAILED + url);
this.#dispatchLoadingError(err);
resolve(null);
// reject(err);
};
img.src = url;
}
});
}
#checkXmlUrl(url) {
if (url.includes(".xml")) {
return;
} else {
Exception(url + ERROR_MESSAGES.XML_FILE_EXTENSION_INCORRECT);
}
}
#checkTilesetUrl(url) {
if (url.includes(".tsj") || url.includes(".json")) {
return FILE_FORMAT.JSON;
} else if (url.includes(".tsx") || url.includes(".xml")) {
return FILE_FORMAT.XML;
} else {
return FILE_FORMAT.UNKNOWN;
}
}
#checkTilemapUrl(url) {
if (url.includes(".tmj") || url.includes(".json")) {
return FILE_FORMAT.JSON;
} else if (url.includes(".tmx") || url.includes(".xml")) {
return FILE_FORMAT.XML;
} else {
return FILE_FORMAT.UNKNOWN;
}
}
#isUploadErrorCritical(error) {
return error.message.includes(ERROR_MESSAGES.NOT_CORRECT_METHOD_TYPE)
|| error.message.includes(ERROR_MESSAGES.XML_FILE_EXTENSION_INCORRECT)
|| error.message.includes(ERROR_MESSAGES.TILESET_FILE_EXTENSION_INCORRECT)
|| error.message.includes(ERROR_MESSAGES.TILEMAP_FILE_EXTENSION_INCORRECT)
|| error.message.includes(ERROR_MESSAGES.INPUT_PARAMS_ARE_INCORRECT)
|| error.message.includes(ERROR_MESSAGES.LOADER_NOT_REGISTERED);
}
/**
* Calculate relative path for current url
* for example: /folder/images/map.xml -> /folder/images/
* @param {string} url
* @returns {string}
*/
#calculateRelativePath(url) {
let split = url.split("/"),
length = split.length,
lastEl = split[length - 1],
//prelastEl = split[length - 2],
relativePath = "/";
// url ends with .ext
if (lastEl.includes(".tmj") || lastEl.includes(".tmx") || lastEl.includes(".xml") || lastEl.includes(".json")) {
split.pop();
relativePath = split.join("/") + "/";
// url ends with /
}/* else if (prelastEl.includes(".tmj") || lastEl.includes(".tmx") || prelastEl.includes(".xml") || prelastEl.includes(".json")) {
split.splice(length - 2, 2);
relativePath = split.join("/") + "/";
}*/
return relativePath;
}
addFile(fileType, fileKey, url, ...args) {
const loader = this.#registeredLoaders.get(fileType);
if (loader) {
this.#checkInputParams(fileKey, url, fileType);
loader._addFile(fileKey, [url, ...args]);
} else {
Exception(fileType + ERROR_MESSAGES.LOADER_NOT_REGISTERED);
}
}
isFileInQueue(fileType, fileKey) {
const loader = this.#registeredLoaders.get(fileType);
if (loader) {
return loader._isFileInQueue(fileKey);
} else {
Exception("Loader for " + fileType + " is not registered!");
}
}
getFile(fileType, fileKey) {
const loader = this.#registeredLoaders.get(fileType);
if (loader) {
return loader._getFile(fileKey);
} else {
Exception("Loader for " + fileType + " is not registered!");
}
}
#checkInputParams(fileKey, url, fileType) {
const errorMessage = ERROR_MESSAGES.INPUT_PARAMS_ARE_INCORRECT;
if (!fileKey || fileKey.trim().length === 0) {
Exception("add" + fileType + "()" + errorMessage);
}
if (!url || url.trim().length === 0) {
Exception("add" + fileType + "()" + errorMessage);
}
return;
}
#dispatchLoadingStart() {
let total = this.filesWaitingForUpload;
this.#emitter.dispatchEvent(new ProgressEvent(PROGRESS_EVENT_TYPE.loadstart, { total }));
}
#dispatchLoadingFinish() {
this.#emitter.dispatchEvent(new ProgressEvent(PROGRESS_EVENT_TYPE.load));
}
#dispatchCurrentLoadingProgress() {
const total = this.filesWaitingForUpload;
this.#itemsLoaded += 1;
this.#emitter.dispatchEvent(new ProgressEvent(PROGRESS_EVENT_TYPE.progress, { lengthComputable: true, loaded: this.#itemsLoaded, total }));
}
#dispatchLoadingError(error) {
Warning("AssetsManger: " + error.message);
this.#emitter.dispatchEvent(new ErrorEvent(PROGRESS_EVENT_TYPE.error, { error }));
}
}
function Exception (message) {
throw new Error(message);
}
function Warning (message) {
console.warn(message);
}
/***/ }),
/***/ "./src/base/2d/DrawCircleObject.js":
/*!*****************************************!*\
!*** ./src/base/2d/DrawCircleObject.js ***!
\*****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "DrawCircleObject": () => (/* binding */ DrawCircleObject)
/* harmony export */ });
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../constants.js */ "./src/constants.js");
/* harmony import */ var _DrawShapeObject_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./DrawShapeObject.js */ "./src/base/2d/DrawShapeObject.js");
/**
* Circle object to draw.
* @extends DrawShapeObject
* @see {@link DrawObjectFactory} should be created with factory method
*/
class DrawCircleObject extends _DrawShapeObject_js__WEBPACK_IMPORTED_MODULE_1__.DrawShapeObject {
/**
* @type {number}
*/
#radius;
/**
* @type {Array<number>}
*/
#vertices;
/**
* @hideconstructor
*/
constructor(x, y, radius, bgColor) {
super(_constants_js__WEBPACK_IMPORTED_MODULE_0__.DRAW_TYPE.CIRCLE, x, y, bgColor);
this.#radius = radius;
this.#vertices = this._interpolateConus(radius);
}
get scale() {
return super.scale;
}
set scale(value) {
this.#vertices = this._interpolateConus(this.#radius * value);
super.scale = value;
}
/**
* Array of [x,y] cords.
* @type {Array<number>}
*/
get vertices () {
return this.#vertices;
}
set vertices(value) {
this.#vertices = value;
}
/**
* @type {number}
*/
get radius() {
return this.#radius;
}
}
/***/ }),
/***/ "./src/base/2d/DrawConusObject.js":
/*!****************************************!*\
!*** ./src/base/2d/DrawConusObject.js ***!
\****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "DrawConusObject": () => (/* binding */ DrawConusObject)
/* harmony export */ });
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../constants.js */ "./src/constants.js");
/* harmony import */ var _DrawShapeObject_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./DrawShapeObject.js */ "./src/base/2d/DrawShapeObject.js");
/**
* Conus object to draw.
* @extends DrawShapeObject
* @see {@link DrawObjectFactory} should be created with factory method
*/
class DrawConusObject extends _DrawShapeObject_js__WEBPACK_IMPORTED_MODULE_1__.DrawShapeObject {
/**
* @type {number}
*/
#radius;
/**
* @type {number}
*/
#angle;
/**
* Array of [x,y] cords.
* @type {Array<number>}
*/
#vertices;
#fade_min;
/**
* @hideconstructor
*/
constructor(x, y, radius, bgColor, angle, fade = 0) {
super(_constants_js__WEBPACK_IMPORTED_MODULE_0__.DRAW_TYPE.CONUS, x, y, bgColor);
this.#radius = radius;
this.#angle = angle;
this.#fade_min = fade;
this.#vertices = this._interpolateConus(radius, angle);
}
get scale() {
return super.scale;
}
set scale(value) {
this.#vertices = this._interpolateConus(this.#radius * value, this.#angle);
super.scale = value;
}
/**
* Array of [x,y] cords.
* @type {Array<number>}
*/
get vertices () {
return this.#vertices;
}
set vertices(value) {
this.#vertices = value;
}
/**
* @type {number}
*/
get radius() {
return this.#radius;
}
/**
* @type {number}
*/
get angle() {
return this.#angle;
}
/**
* @type {number}
*/
get fade_min() {
return this.#fade_min;
}
/**
* @param {number} value - fade start pos in px
*/
set fade_min(value) {
this.#fade_min = value;
}
}
/***/ }),
/***/ "./src/base/2d/DrawImageObject.js":
/*!****************************************!*\
!*** ./src/base/2d/DrawImageObject.js ***!
\****************************************/
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "DrawImageObject": () => (/* binding */ DrawImageObject)
/* harmony export */ });
/* harmony import */ var _AnimationEvent_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../AnimationEvent.js */ "./src/base/AnimationEvent.js");
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../constants.js */ "./src/constants.js");
/* harmony import */ var _DrawShapeObject_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./DrawShapeObject.js */ "./src/base/2d/DrawShapeObject.js");
/* harmony import */ var _Temp_ImageTempStorage_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../Temp/ImageTempStorage.js */ "./src/base/Temp/ImageTempStorage.js");
/* harmony import */ var _Exception_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../Exception.js */ "./src/base/Exception.js");
/**
* Image object to draw
* @extends DrawShapeObject
* @see {@link DrawObjectFactory} should be created with factory method
*/
class DrawImageObject extends _DrawShapeObject_js__WEBPACK_IMPORTED_MODULE_2__.DrawShapeObject {
/**
* @type {number}
*/
#w;
/**
* @type {number}
*/
#h;
/**
* Image sprite key
* @type {string}
*/
#key;
/**
* @type {ImageBitmap}
*/
#image;
/**
* @type {EventTarget}
*/
#emitter;
/**
* @type {Map<string, AnimationEvent>}
*/
#animations;
/**
* @type {null | string}
*/
#activeAnimation;
/**
* @type {number}
*/
#imageIndex;
/**
* @type {number}
*/
#spacing = 0;
/**
* @type {number}
*/
#margin = 0;
/**
* @type {Array<Array<number>>}
*/
#vertices;
/**
* @type {Object | null}
*/
#circleBoundaries;
/**
* @type {ImageTempStorage}
*/
#textureStorage;
/**
* @hideconstructor
*/
constructor(mapX, mapY, width, height, key, imageIndex = 0, boundaries, image, spacing = 0, margin = 0) {
super(_constants_js__WEBPACK_IMPORTED_MODULE_1__.DRAW_TYPE.IMAGE, mapX, mapY);
this.#key = key;
this.#emitter = new EventTarget();
this.#animations = new Map();
this.image = image;
this.#imageIndex = imageIndex;
this.#spacing = spacing;
this.#margin = margin;
this.#w = width;
this.#h = height;
this.#vertices = boundaries && !boundaries.r ? this._convertVerticesArray(boundaries) : boundaries && boundaries.r ? this._calculateConusBoundaries(boundaries.r) : this._calculateRectVertices(width, height);
this.#circleBoundaries = boundaries && typeof boundaries.r !== "undefined" ? boundaries : null;
}
/**
* @type {number}
*/
get width() {
return this.#w;
}
/**
* @type {number}
*/
get height() {
return this.#h;
}
set width(w) {
this.#w = w;
}
set height(h) {
this.#h = h;
}
/**
* A key should match an image loaded through AssetsManager
* @type {string}
*/
get key() {
return this.#key;
}
/**
* @type {ImageBitmap}
*/
get image() {
return this.#image;
}
set image(value) {
if (this.#textureStorage) {
this.#textureStorage._isTextureRecalculated = true;
}
this.#image = value;
}
get scale() {
return super.scale;
}
set scale(value) {
if (this.#circleBoundaries) {
this.#circleBoundaries.r = this.#circleBoundaries.r * value;
} else {
this.#vertices = this._calculateRectVertices(this.width * value, this.height * value);
}
super.scale = value;
}
/**
* Current image index
* @type {number}
*/
get imageIndex() {
return this.#imageIndex;
}
set imageIndex(value) {
this.#imageIndex = value;
}
/**
* Image spacing (for tilesets.spacing > 0)
* @type {number}
*/
get spacing() {
return this.#spacing;
}
/**
* Image spacing (for tilesets.margin > 0)
* @type {number}
*/
get margin() {
return this.#margin;
}
/**
* Determines if image is animated or not
* @type {boolean}
*/
get hasAnimations() {
return this.#animations.size > 0;
}
/**
* @type {null | string}
*/
get activeAnimation() {
return this.#activeAnimation;
}
/**
* @deprecated - use .vertices instead
* @type {Array<Array<number>>}
*/
get boundaries() {
return this.#vertices;
}
get vertices() {
return this.#vertices;
}
get circleBoundaries() {
return this.#circleBoundaries;
}
/**
* @ignore
*/
_processActiveAnimations() {
const activeAnimation = this.#activeAnimation;
if (activeAnimation) {
const animationEvent = this.#animations.get(activeAnimation);
if (animationEvent.isActive === false) {
this.#activeAnimation = null;
} else {
animationEvent.iterateAnimationIndex();
this.#imageIndex = animationEvent.currentSprite;
}
}
}
/**
* @ignore
*/
get _textureStorage() {
return this.#textureStorage;
}
/**
* @ignore
*/
set _textureStorage(texture) {
this.#textureStorage = texture;
}
/**
* Emit event
* @param {string} eventName
* @param {...any} eventParams
*/
emit(eventName, ...eventParams) {
const event = new Event(eventName);
event.data = [...eventParams];
this.#emitter.dispatchEvent(event);
}
/**
* Subscribe
* @param {string} eventName
* @param {*} listener
* @param {*} options
*/
addEventListener(eventName, listener, options) {
this.#emitter.addEventListener(eventName, listener, options);
}
/**
* Unsubscribe
* @param {string} eventName
* @param {*} listener
* @param {*} options
*/
removeEventListener(eventName, listener, options) {
this.#emitter.removeEventListener(eventName, listener, options);
}
/**
* Adds image animations
* @param { string } eventName -animation name
* @param { Array<number> | Array<{duration:number, id:number}> } animationSpriteIndexes - animation image indexes
* @param { boolean } [isRepeated = false] - animation is cycled or not, cycled animation could be stopped only with stopRepeatedAnimation();
*/
addAnimation (eventName, animationSpriteIndexes, isRepeated) {
if (!this.#checkAnimationParams(animationSpriteIndexes)) {
(0,_Exception_js__WEBPACK_IMPORTED_MODULE_4__.Exception)(_constants_js__WEBPACK_IMPORTED_MODULE_1__.ERROR_CODES.UNEXPECTED_INPUT_PARAMS, " animationSpriteIndexes should be Array of indexes, or Array of objects {duration:number, id:number}");
}
const animationEvent = new _AnimationEvent_js__WEBPACK_IMPORTED_MODULE_0__.AnimationEvent(eventName, animationSpriteIndexes, isRepeated);
this.#animations.set(eventName, animationEvent);
this.addEventListener(eventName, this.#activateAnimation);
}
#checkAnimationParams (animationSpriteIndexes) {
let isCorrect = true;
animationSpriteIndexes.forEach(element => {
if (typeof element !== "number") {
if (typeof element.duration !== "number" || typeof element.id !== "number") {
isCorrect = false;
}
}
});
return isCorrect;
}
#activateAnimation = (event) => {
const animationName = event.type,
animationEvent = this.#animations.get(animationName);
// only one active animation can exist at a time
if (this.#activeAnimation && this.#activeAnimation !== animationName) {
this.stopRepeatedAnimation(this.#activeAnimation);
}
animationEvent.activateAnimation();
this.#activeAnimation = animationName;
this.#imageIndex = animationEvent.currentSprite;
};
/**
*
* @param {string=} eventName - animation name, if not provided - stop current active animation event
*/
stopRepeatedAnimation (eventName) {
this.#animations.get(eventName).deactivateAnimation();
this.#activeAnimation = null;
}
/**
* Removes animations
*/
removeAllAnimations() {
for (let [eventName, animationEvent] of this.#animations.entries()) {
this.removeEventListener(eventName, animationEvent.activateAnimation);
animationEvent.deactivateAnimation();
}
this.#animations.clear();
this.#animations = undefined;
}
destroy() {
this.removeAllAnimations();
super.destroy();
}
}
/***/ }),
/***/ "./src/base/2d/DrawLineObject.js":
/*!***************************************!*\
!*** ./src/base/2d/DrawLineObject.js ***!
\********************