Pathway
A module that will enable pathfinding functionality in the Vylocity Game Engine
+
Pathway Module
The Pathway module smoothly integrates pathfinding into the Vylocity Game Engine, allowing map instances to navigate environments more efficiently.
Uses easystar under the hood.
-
ES Module
// Importing as an ES module
-import { Pathway } from './pathway.mjs';
+Installation
ES Module
import { Pathway } from './pathway.mjs';
-<!-- Including the IIFE bundle in an HTML file -->
-<script src="pathway.js"></script>
-
+<script src="pathway.js"></script>;
// ...
-window.PathwayBundle.Pathway
+window.PathwayBundle.Pathway;
-CommonJS (CJS) Module
// Importing as a CommonJS module (Node.js)
-const { Pathway } = require('./pathway.cjs.js');
+CommonJS (CJS) Module
const { Pathway } = require('./pathway.cjs.js');
-API
instance.pathwayWeight
-type
: number
-desc
: The weight of this instance in the pathfinder system, higher values will try to make the pathfinder generate paths that do not include this instance. A weight of 0
is converying that is is passable. A weight of -1
means it is impassable. Weights are optional!
+API
MapInstance Properties
pathwayWeight
+- Type:
number
+- Description: Represents the importance of an element in pathfinding. Higher values indicate that paths should avoid this element. A weight of
0
means it's easy to traverse, while -1
indicates an impassable obstacle. This property is optional.
-Pathway.to(pInstance, pDestination, pOptions)
-pInstance
: The instance to move. object
-pDestination.x
: The xCoordinate to move to integer
-pDestination.y
: The yCoordinate to move to integer
-pOptions.diagonal
: Whether or not the pathfinder allows diagonal moves boolean
-pOptions.mode
: How this instance will move. collision
for moving with collisions in mind (movePos). position
for moving with no collisions in mind (setPos). string
-pOptions.pixelsPerSecond
: The speed in pixels this instance moves per second. This setting only works when pOptions.mode
is set to position
.number
-pOptions.exclude
: An array of diobs that will be excluded when calculating the path array
-pOptions.minDistance
: The minimum distance this pathway system will use to calculate if you have reached the (next) node. number
-pOptions.maxStuckCounter
: The maximum amount of ticks of pInstance being in the same position as the last tick before its considered stuck. number
diff --git a/docs/index.json b/docs/index.json
index a455832..c7ba581 100644
--- a/docs/index.json
+++ b/docs/index.json
@@ -567,7 +567,7 @@
"__docId__": 48,
"kind": "file",
"name": "src/pathway.mjs",
- "content": "import { Utils } from './vendor/utils.min.mjs';\r\nimport { Logger } from './vendor/logger.min.mjs';\r\nimport { EasyStar } from './vendor/easystar-0.4.4.min.js';\r\n\r\n/**\r\n * @todo Test on server\r\n * @todo Make debugging class\r\n * @private\r\n */\r\nclass PathwaySingleton {\r\n\t/**\r\n\t * The maximum amount of ticks an instance can be in the same position before the pathfinder deems it \"stuck\". The user will be able to tweak values up to this max value.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic MAX_STUCK_COUNTER = 100;\r\n\t/**\r\n\t * The max amount of delta time between ticks. If this limit is passed, it will be clamped.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic MAX_DELTA_TIME = 0.03333333333;\r\n\t/**\r\n\t * The weight that indicates that this tile is walkable. This is used as the default weight of every instance unless otherwise stated.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic PASSABLE_WEIGHT = 0;\r\n\t/**\r\n\t * A static weight to be applied when a tile should be not be traveled to at all.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic IMPASSABLE_WEIGHT = -1;\r\n\t/**\r\n\t * The default amount of pixels per second to move the instance when using `position` mode.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic DEFAULT_PIXELS_PER_SECOND = 120;\r\n\t/**\r\n\t * The minimum distance away from a node before this system determines it has made it to that node.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic DEFAULT_MINIMUM_DISTANCE = 2;\r\n\t/**\r\n\t * The default mode this pathway system uses.\r\n\t * @private\r\n\t * @type {string}\r\n\t */\r\n\tstatic DEFAULT_MODE = 'collision';\r\n\t/**\r\n\t * An object that stores the map tiles in normal format and in 2D format.\r\n\t * @private\r\n\t * @type {Object}\r\n\t */\r\n\tstatic storedMapTiles = {};\r\n\t/**\r\n\t * The tile size to use if no tile size has been assigned.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic DEFAULT_TILE_SIZE = { width: 32, height: 32 };\r\n\t/**\r\n\t * The tile size to use for this system.\r\n\t * @private\r\n\t * @type {Object}\r\n\t */\r\n\ttileSize = { ...PathwaySingleton.DEFAULT_TILE_SIZE };\r\n\t/**\r\n\t * The version of the module.\r\n\t */\r\n\tversion = \"VERSION_REPLACE_ME\";\r\n\t/**\r\n\t * A weakmap storing the data of instances used in this pathfinder.\r\n\t * @private\r\n\t * @type {WeakMap}\r\n\t */\r\n\tinstanceWeakMap = new WeakMap();\r\n\t/**\r\n\t * The last tracked time in the ticker.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tlastTime = 0;\r\n\t/**\r\n\t * The delta time between the current and last tick.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tdeltaTime = 0;\r\n\t/**\r\n\t * The time in ms between the current and last tick.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\telapsedMS = 0;\r\n\t/**\r\n\t * An array of active instances that are currently pathfinding.\r\n\t * @private\r\n\t * @type {Array}\r\n\t */\r\n\tactiveInstances = [];\r\n\t/**\r\n\t * @private\r\n\t */\r\n\tconstructor() {\r\n // Create a logger\r\n /** The logger module this module uses to log errors / logs\r\n * @private\r\n * @type {Object}\r\n */\r\n this.logger = new Logger();\r\n this.logger.registerType('Pathway-Module', '#ff6600');\r\n\t}\r\n\t/**\r\n\t * Moves pInstance to the destination position with pOptions in mind.\r\n\t * @param {Object} pInstance - The instance to move to the destination. The origin position will be retrived from this instance as well.\r\n\t * @param {Object} pDestination - The end position to travel to.\r\n\t * @property {number} pDestination.x - The end x coordinate.\r\n\t * @property {number} pDestination.y - The end y coordinate.\r\n\t * @param {Object} pOptions - An object of settings on how to move pInstance to pDestination.\r\n\t * @property {boolean} [pOptions.diagonal = false] - Whether or not the pathfinder allows diagonal moves.\r\n\t * @property {Array} pOptions.exclude - An array of diobs that will be excluded when calculating the path.\r\n\t * @property {number} [pOptions.minDistance = 2] = The minimum distance this pathway system will use to calculate if you have reached the (next) node. \r\n\t * @property {number} [pOptions.maxStuckCounter = 100] - The maximum amount of ticks of pInstance being in the same position as the last tick before its considered stuck.\r\n\t * @property {string} [pOptions.mode = 'collision'] - How this instance will move. `collision` for moving with collisions in mind (movePos). `position` for moving with no collisions in mind (setPos) Must use pOptions.pixelsPerSecond when using `position` mode. \r\n\t * @property {string} [pOptions.pixelsPerSecond = 120] - The speed in pixels this instance moves per second. This setting only works when pOptions.mode is set to `position`. \r\n\t * @property {Function} pOptions.onPathComplete - Callback for when pInstance makes it to the destination node.\r\n\t * @property {Function} pOptions.onPathFound - Callback for when pInstance finds a path.\r\n\t * @property {Function} pOptions.onPathStuck - Callback for when pInstance gets stuck on a path.\r\n\t * @property {Function} pOptions.onPathNotFound - Callback for when no path is found.\r\n\t */\r\n\tto(pInstance, pDestination, pOptions) {\r\n\t\tif (typeof(pInstance) === 'object') {\r\n\t\t\t// If this instance is not on a mapname.\r\n\t\t\tif (!pInstance.mapName) {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Cannot generate a path. pInstance is not on a map.');\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// If there is no destination object passed return.\r\n\t\t\tif (typeof(pDestination) !== 'object') {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type passed for pDestination. Expecting an object.');\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// Get the instance data for this instance\r\n\t\t\tlet instanceData = this.instanceWeakMap.get(pInstance);\r\n\r\n\t\t\tif (!instanceData) {\r\n\t\t\t\t// Set the instance data\r\n\t\t\t\tinstanceData = {\r\n\t\t\t\t\ttrajectory: { \r\n\t\t\t\t\t\tangle: 0, \r\n\t\t\t\t\t\tx: 0, \r\n\t\t\t\t\t\ty: 0, \r\n\t\t\t\t\t\tnextNodePos: null,\r\n\t\t\t\t\t},\r\n\t\t\t\t\t// The current position of the instance.\r\n\t\t\t\t\tcurrentPosition: { x: 0, y: 0 },\r\n\t\t\t\t\t// The previous position of the instance in the tick before.\r\n\t\t\t\t\tpreviousPosition: { x: 0, y: 0 },\r\n\t\t\t\t\t// The stuck counter of this instance. When this instance is in the same position for multiple ticks, this value is added onto up until -\r\n\t\t\t\t\t// the max stuck counter is reached and the `stuck` event is called.\r\n\t\t\t\t\tstuckCounter: 0,\r\n\t\t\t\t\tmaxStuckCounter: PathwaySingleton.MAX_STUCK_COUNTER,\r\n\t\t\t\t\tpathID: null, // ID of the path that was generated. Used to cancel the path.\r\n\t\t\t\t\tpath: [],\r\n\t\t\t\t\tmoving: null,\r\n\t\t\t\t\tmode: PathwaySingleton.DEFAULT_MODE,\r\n\t\t\t\t\tpixelsPerSecond: PathwaySingleton.DEFAULT_PIXELS_PER_SECOND,\r\n\t\t\t\t\tminDistance: PathwaySingleton.DEFAULT_MINIMUM_DISTANCE,\r\n\t\t\t\t\tevents: {\r\n\t\t\t\t\t\tonPathStuck: null,\r\n\t\t\t\t\t\tonPathComplete: null,\r\n\t\t\t\t\t\tonPathFound: null,\r\n\t\t\t\t\t\tonPathNotFound: null,\r\n\t\t\t\t\t},\r\n\t\t\t\t\teasystar: new EasyStar.js()\r\n\t\t\t\t};\r\n\t\t\t\t// If you have a large grid, then it is possible that these calculations could slow down the browser. \r\n\t\t\t\t// For this reason, it might be a good idea to give EasyStar a smaller iterationsPerCalculation\r\n\t\t\t\t// https://github.com/prettymuchbryce/easystarjs\r\n\t\t\t\tinstanceData.easystar.setIterationsPerCalculation(1000);\r\n\t\t\t\t// Assign the instance data\r\n\t\t\t\tthis.instanceWeakMap.set(pInstance, instanceData);\r\n\t\t\t} else {\r\n\t\t\t\t// If this instance has data already, we reset it\r\n\t\t\t\tthis.end(pInstance);\r\n\t\t\t}\r\n\r\n\t\t\t/**\r\n\t\t\t * An exclusion list of tiles.\r\n\t\t\t * @type {Array}\r\n\t\t\t */\r\n\t\t\tlet excludeList = [];\r\n\r\n\t\t\t// If there are options passed. Parse them.\r\n\t\t\tif (typeof(pOptions) === 'object') {\r\n\t\t\t\t// If max stuck counter is found in options, set it.\r\n\t\t\t\tif (typeof(pOptions.maxStuckCounter) === 'number') {\r\n\t\t\t\t\tinstanceData.maxStuckCounter = pOptions.maxStuckCounter;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Enable diagonals if found in passed options.\r\n\t\t\t\t// This can cause some \"issues\" such as trying to cut through corners.\r\n\t\t\t\tif (pOptions.diagonal) {\r\n\t\t\t\t\tinstanceData.easystar.enableDiagonals();\r\n\t\t\t\t\tinstanceData.easystar.enableCornerCutting();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Set the positioning mode\r\n\t\t\t\tif (pOptions.mode) {\r\n\t\t\t\t\t// Get the mode, if an invalid mode is passed, we default to the default mode.\r\n\t\t\t\t\tconst mode = (pOptions.mode === 'collision' || pOptions.mode === 'position') ? pOptions.mode : PathwaySingleton.DEFAULT_MODE;\r\n\t\t\t\t\tinstanceData.mode = mode;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Assign pixels per second \r\n\t\t\t\tif (typeof(pOptions.pixelsPerSecond) === 'number') {\r\n\t\t\t\t\tinstanceData.pixelsPerSecond = pOptions.pixelsPerSecond;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Assign the min distance\r\n\t\t\t\tif (typeof(pOptions.minDistance) === 'number') {\r\n\t\t\t\t\tinstanceData.minDistance = pOptions.minDistance;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Assign events\r\n\t\t\t\tif (typeof(pOptions.onPathComplete) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathComplete = pOptions.onPathComplete;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (typeof(pOptions.onPathFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathFound = pOptions.onPathFound;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (typeof(pOptions.onPathNotFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathNotFound = pOptions.onPathNotFound;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (typeof(pOptions.onPathStuck) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathStuck = pOptions.onPathStuck;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Copy the contents of the exclude array to the exclude list we manage.\r\n\t\t\t\tif (Array.isArray(pOptions.exclude)) {\r\n\t\t\t\t\texcludeList.push(...pOptions.exclude);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// We add the instance to the exclude list so that it is excluded.\r\n\t\t\tif (!excludeList.includes(pInstance)) {\r\n\t\t\t\texcludeList.push(pInstance);\r\n\t\t\t}\r\n\r\n\t\t\t// Build the 2D array grid that represents the map\r\n\t\t\tconst gridInfo = this.mapTilesToGrid(pInstance.mapName, excludeList);\r\n\t\t\t\r\n\t\t\t// Assign the grid to easystar\r\n\t\t\tinstanceData.easystar.setGrid(gridInfo.grid);\r\n\t\t\t\r\n\t\t\t// Assign the weight of each tile\r\n\t\t\tgridInfo.weights.forEach((pWeight) => {\r\n\t\t\t\tinstanceData.easystar.setTileCost(pWeight, pWeight);\r\n\t\t\t});\r\n\r\n\t\t\t// Assign what tiles can be used\r\n\t\t\tinstanceData.easystar.setAcceptableTiles(gridInfo.acceptedTiles);\r\n\r\n\t\t\t// Grab the pos of the instance so we can locate the starting tile its on.\r\n\t\t\t// This is also used as the startingNode position.\r\n\t\t\tconst instancePosition = this.getPositionFromInstance(pInstance);\r\n\t\t\t// Get the origin tile the instance is on.\r\n\t\t\tconst originTile = VYLO.Map.getLocByPos(instancePosition.x, instancePosition.y, pInstance.mapName);\r\n\t\t\t// Get the dimensions of the map that was passed.\r\n\t\t\tconst mapSize = VYLO.Map.getMapSize(pInstance.mapName);\r\n\r\n\t\t\t// Get the end nodes position so we can get the destinationTile\r\n\t\t\tconst endNodeX = Utils.clamp(Utils.clamp(pDestination.x, 0, mapSize.x) * this.tileSize.width + this.tileSize.width / 2, 0, mapSize.xPos - this.tileSize.width);\r\n\t\t\tconst endNodeY = Utils.clamp(Utils.clamp(pDestination.y, 0, mapSize.y) * this.tileSize.height + this.tileSize.height / 2, 0, mapSize.yPos - this.tileSize.height);\r\n\t\t\t// Get the end time tile\r\n\t\t\tconst destinationTile = VYLO.Map.getLocByPos(endNodeX, endNodeY, pInstance.mapName);\r\n\t\t\t\r\n\t\t\t// Make sure these have resolved to actual tiles.\r\n\t\t\tif (originTile && destinationTile) {\r\n\t\t\t\tif (this.isTileAccessible(originTile, excludeList) && this.isTileAccessible(destinationTile, excludeList)) {\r\n\t\t\t\t\t// Get the start node from the originTile\r\n\t\t\t\t\tlet startNode = this.tileToNode(originTile);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Get the end node from the destinationTile\r\n\t\t\t\t\tlet endNode = this.tileToNode(destinationTile);\r\n\r\n\t\t\t\t\t// Generate the path for the player\r\n\t\t\t\t\tthis.getPath(pInstance, { x: startNode.x, y: startNode.y }, { x: endNode.x, y: endNode.y });\t\t\t\t\r\n\t\t\t\t// If the origin tile or end tile is not accessible to be walked on then return no path found.\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// So fire the path not found event.\t\r\n\t\t\t\t\tif (typeof(instanceData.events.onPathNotFound) === 'function') {\r\n\t\t\t\t\t\tinstanceData.events.onPathNotFound();\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.end(pInstance);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Origin tile or destination tile cannot be found.');\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type passed for pInstance. Expecting an object.');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Tracks this instance as active.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to track.\r\n\t */\r\n\ttrack(pInstance) {\r\n\t\t// Add this instance to being tracked.\r\n\t\tif (!this.activeInstances.includes(pInstance)) {\r\n\t\t\tthis.activeInstances.push(pInstance);\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Untracks this instance. It is no longer considered active.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to untrack.\r\n\t */\r\n\tuntrack(pInstance) {\r\n\t\t// Remove this instance from being tracked.\r\n\t\tif (this.activeInstances.includes(pInstance)) {\r\n\t\t\tthis.activeInstances.splice(this.activeInstances.indexOf(pInstance), 1);\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Ends the current pathfinding for pInstance.\r\n\t * @param {Object} pInstance - The instance to terminate pathfinding on.\r\n\t */\r\n\tend(pInstance) {\r\n\t\t// Get the instance data for this instance\r\n\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\tif (instanceData) {\r\n\t\t\t// We are ending the pathfinding. So we get the path ID so we can cancel calculations being made for this path.\r\n\t\t\tif (instanceData.pathID) {\r\n\t\t\t\tinstanceData.easystar.cancelPath(instanceData.pathID);\r\n\t\t\t\tinstanceData.pathID = null;\r\n\t\t\t}\r\n\t\t\t// Disable diagonals in the event they were enabled in a previous call\r\n\t\t\tinstanceData.easystar.disableDiagonals();\r\n\t\t\t// Disable corner cutting in the event it was enabled in a previous call\r\n\t\t\tinstanceData.easystar.disableCornerCutting();\r\n\t\t\t// Reset trajectory data\r\n\t\t\tinstanceData.trajectory.x = 0;\r\n\t\t\tinstanceData.trajectory.y = 0;\r\n\t\t\tinstanceData.trajectory.angle = 0;\r\n\t\t\tinstanceData.trajectory.nextNodePos = null;\r\n\t\t\t// Reset events\r\n\t\t\tinstanceData.events.onPathStuck = null;\r\n\t\t\tinstanceData.events.onPathComplete = null;\r\n\t\t\tinstanceData.events.onPathFound = null;\r\n\t\t\tinstanceData.events.onPathNotFound = null;\r\n\t\t\t// Reset stuck counter\r\n\t\t\tinstanceData.stuckCounter = 0;\r\n\t\t\t// Reset the max stuck counter\r\n\t\t\tinstanceData.maxStuckCounter = PathwaySingleton.MAX_STUCK_COUNTER;\r\n\t\t\t// Empty path(s) array\r\n\t\t\tinstanceData.path.length = 0;\r\n\t\t\t// Reset it to not being moved.\r\n\t\t\tinstanceData.moving = false;\r\n\t\t\t// Reset the mode\r\n\t\t\tinstanceData.mode = 'collision';\r\n\t\t\t// Reset the pixels per second.\r\n\t\t\tinstanceData.pixelsPerSecond = PathwaySingleton.DEFAULT_PIXELS_PER_SECOND;\r\n\t\t\t// Reset the min distance\r\n\t\t\tinstanceData.minDistance = PathwaySingleton.DEFAULT_MINIMUM_DISTANCE;\r\n\t\t\t// Stop instance from moving via VYLO API.\r\n\t\t\tpInstance.move();\r\n\t\t\t// Untrack pInstance as an active instance.\r\n\t\t\tthis.untrack(pInstance);\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('No instance data found from pInstance. This instance is not engaged in pathfinding.');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Gets the position from the instance based on the pathfinding info. Centered position from the geometrical.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to get the position from.\r\n\t * @returns {Object} - The position of the instance.\r\n\t */\r\n\tgetPositionFromInstance(pInstance) {\r\n\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\tif (instanceData) {\r\n\t\t\tinstanceData.currentPosition.x = Math.floor(pInstance.x + pInstance.xOrigin + pInstance.width / 2);\r\n\t\t\tinstanceData.currentPosition.y = Math.floor(pInstance.y + pInstance.yOrigin + pInstance.height / 2);\r\n\t\t\treturn instanceData.currentPosition;\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Updates active instances on the pathfinder.\r\n\t * @private\r\n\t */\r\n\tupdate() {\r\n\t\t// Get current timestamp\r\n\t\tconst now = Date.now();\r\n\t\t// Get the elapsed ms from the last tick\r\n\t\tthis.elapsedMS = now - this.lastTime;\r\n\t\t// Get the delta time between the last tick\r\n\t\tthis.deltaTime = (this.elapsedMS / 1000);\r\n\t\t// If the delta time grows too large, we clamp it\r\n\t\tif (this.deltaTime >= PathwaySingleton.MAX_DELTA_TIME) {\r\n\t\t\tthis.deltaTime = PathwaySingleton.MAX_DELTA_TIME;\r\n\t\t}\r\n\t\t// Loop active instances and update.\r\n\t\tthis.activeInstances.forEach((pInstance) => {\r\n\t\t\t// Get the instance data for this instance\r\n\t\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\t\tif (instanceData) {\r\n\t\t\t\t// Calculate the path\r\n\t\t\t\tinstanceData.easystar.calculate();\r\n\r\n\t\t\t\t// If this instance is being moved\r\n\t\t\t\tif (Array.isArray(instanceData.path) && (instanceData.path.length || instanceData.moving)) {\r\n\t\t\t\t\t// Get the position of the instance\r\n\t\t\t\t\tconst instancePosition = this.getPositionFromInstance(pInstance);\r\n\t\t\t\t\t// If the instance is not moving\r\n\t\t\t\t\tif (!instanceData.moving) {\r\n\t\t\t\t\t\t// Get the next node to travel to.\r\n\t\t\t\t\t\tconst node = instanceData.path.shift();\r\n\t\t\t\t\t\t// Get the position of that node in real world coordinates. We subtract half of the tileSize to get the center of the node's posiiton.\r\n\t\t\t\t\t\tconst nodePos = { \r\n\t\t\t\t\t\t\tx: (node.x * this.tileSize.width) - this.tileSize.width / 2, \r\n\t\t\t\t\t\t\ty: (node.y * this.tileSize.height) - this.tileSize.height / 2 \r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\t// Store the next node position\r\n\t\t\t\t\t\tinstanceData.trajectory.nextNodePos = nodePos;\r\n\t\t\t\t\t\t// Get the angle from the instance's position to the next node\r\n\t\t\t\t\t\tinstanceData.trajectory.angle = Utils.getAngle2(instancePosition, instanceData.trajectory.nextNodePos);\r\n\t\t\t\t\t\t// Get the trajectory of where to move the instance based on the angle\r\n\t\t\t\t\t\tinstanceData.trajectory.x = Math.cos(instanceData.trajectory.angle);\r\n\t\t\t\t\t\tinstanceData.trajectory.y = -Math.sin(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t// Update the direction of the instance based on the angle to the next node\r\n\t\t\t\t\t\tpInstance.dir = Utils.getDirection(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t// Move the instance with collision mode or positional mode\r\n\t\t\t\t\t\tif (instanceData.mode === 'collision') {\r\n\t\t\t\t\t\t\tpInstance.movePos(instanceData.trajectory.x, instanceData.trajectory.y);\r\n\t\t\t\t\t\t} else if (instanceData.mode === 'position') {\r\n\t\t\t\t\t\t\tconst speed = instanceData.pixelsPerSecond * this.deltaTime;\r\n\t\t\t\t\t\t\tpInstance.setPos(pInstance.x + speed * instanceData.trajectory.x, pInstance.y + speed * instanceData.trajectory.y, pInstance.mapName);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tinstanceData.moving = true;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Get the distance from the instance's position to the next node's position.\r\n\t\t\t\t\t\tconst distance = Utils.getDistance(instancePosition, instanceData.trajectory.nextNodePos);\r\n\t\t\t\t\t\t// Stop moving when you are this close distance.\r\n\t\t\t\t\t\tif (distance <= instanceData.minDistance) {\r\n\t\t\t\t\t\t\t// Stop moving\r\n\t\t\t\t\t\t\tinstanceData.moving = false;\r\n\t\t\t\t\t\t\t// Reset stuck counter when moving has \"stopped\".\r\n\t\t\t\t\t\t\tinstanceData.stuckCounter = 0;\r\n\t\t\t\t\t\t\t// If there is no more nodes left in the path\r\n\t\t\t\t\t\t\tif (!instanceData.path.length) {\r\n\t\t\t\t\t\t\t\t// You have completed the path. Call the event function if supplied.\r\n\t\t\t\t\t\t\t\tif (typeof(instanceData.events.onPathComplete) === 'function') {\r\n\t\t\t\t\t\t\t\t\tinstanceData.events.onPathComplete();\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tthis.end(pInstance);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tinstanceData.trajectory.angle = Utils.getAngle2(instancePosition, instanceData.trajectory.nextNodePos);\r\n\t\t\t\t\t\t\tinstanceData.trajectory.x = Math.cos(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t\tinstanceData.trajectory.y = -Math.sin(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t\tpInstance.dir = Utils.getDirection(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t\t// Move the instance with collision mode or positional mode\r\n\t\t\t\t\t\t\tif (instanceData.mode === 'collision') {\r\n\t\t\t\t\t\t\t\tpInstance.movePos(instanceData.trajectory.x, instanceData.trajectory.y);\r\n\t\t\t\t\t\t\t} else if (instanceData.mode === 'position') {\r\n\t\t\t\t\t\t\t\tconst speed = instanceData.pixelsPerSecond * this.deltaTime;\r\n\t\t\t\t\t\t\t\tpInstance.setPos(pInstance.x + speed * instanceData.trajectory.x, pInstance.y + speed * instanceData.trajectory.y, pInstance.mapName);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tinstanceData.moving = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// If the instance's position is in the same spot it was in the last tick\r\n\t\t\t\t\tif (instancePosition.x === instanceData.previousPosition.x && instancePosition.y === instanceData.previousPosition.y) {\r\n\t\t\t\t\t\t// Increment the stuck counter\r\n\t\t\t\t\t\tinstanceData.stuckCounter++;\r\n\t\t\t\t\t\t// Chekck if the stuck counter is greater or equal to the max stuck counter\r\n\t\t\t\t\t\tif (instanceData.stuckCounter >= instanceData.maxStuckCounter) {\r\n\t\t\t\t\t\t\t// Call the stuck event if defined.\r\n\t\t\t\t\t\t\tif (typeof(instanceData.events.onPathStuck) === 'function') {\r\n\t\t\t\t\t\t\t\tinstanceData.events.onPathStuck();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// End this pathfinding.\r\n\t\t\t\t\t\t\tthis.end(pInstance);\r\n\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// Store the previous position as the position of this tick\r\n\t\t\t\t\tinstanceData.previousPosition = instancePosition;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t\t// Store this tick's time\r\n\t\tthis.lastTime = now;\r\n\t}\r\n\t/**\r\n\t * Sets the tilesize of this system.\r\n\t * @param {number} pTileSize - The tilesize of the game.\r\n\t */\r\n\tsetTileSize(pTileSize) {\r\n\t\tif (typeof(pTileSize) === 'number') {\r\n\t\t\tthis.tileSize = { width: pTileSize, height: pTileSize };\r\n\t\t} else if(typeof(pTileSize) === 'object') {\r\n\t\t\tconst width = pTileSize.width;\r\n\t\t\tconst height = pTileSize.height;\r\n\t\t\t// Assign the tilesize width\r\n\t\t\tif (typeof(width) === 'number') {\r\n\t\t\t\tthis.tileSize.width = width;\r\n\t\t\t}\r\n\t\t\t// Assign the tilesize height\r\n\t\t\tif (typeof(height) === 'number') {\r\n\t\t\t\tthis.tileSize.height = height;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type used for pTileSize');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Checks to see if pTile is accessible for movement.\r\n\t * @private\r\n\t * @param {Object} pTile - The tile to check the validity of.\r\n\t * @param {Array} pExclusionList - The list of excluded tiles/instances.\r\n\t * @returns {boolean} - If this tile is accessible.\r\n\t */\r\n\tisTileAccessible(pTile, pExclusionList) {\r\n\t\t// If the tile is in the exclude list, we simply say it is accessible. This prevents the tile's contents from being searched. \r\n\t\t// There is a possibility it could actually be an obstacle on this tile blocking movement from being completed. Use with caution.\r\n\t\tconst isExcluded = (pInstance) => pExclusionList.includes(pInstance);\r\n\t\t// This instance is impassible because it is dense and has no pathwayWeight, or it was explicitely set to be impassable.\r\n\t\tconst isImpassable = (pInstance) => (pInstance.pathwayWeight === PathwaySingleton.IMPASSABLE_WEIGHT) || pInstance.density && (!pInstance.pathwayWeight && pInstance.pathwayWeight !== PathwaySingleton.PASSABLE_WEIGHT);\r\n\t\t// If this tile has dense instances that are not being excluded, doesn't have a pathwayWeight set, or are explicitely set to be impassable.\r\n\t\tconst hasImpassableContent = (pInstance) => pInstance.getContents().some((pInstance) => {\r\n\t\t\treturn isImpassable(pInstance) && !isExcluded(pInstance);\r\n\t\t});\r\n\t\treturn isExcluded(pTile) || (!isImpassable(pTile) && !hasImpassableContent(pTile));\r\n\t}\r\n\t/**\r\n\t * Generates a path from the origin point to the end point with obstacles in mind.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to grab data from.\r\n\t * @param {Object} pOrigin - An object containing the start x and y position. \r\n\t * @property {number} pOrigin.x - The start x coordinate.\r\n\t * @property {number} pOrigin.y -The start y coordinate.\r\n\t * @param {Object} pDestination - An object containing the end x and y position to travel to.\r\n\t * @property {number} pDestination.x - The end x coordinate.\r\n\t * @property {number} pDestination.y - The end y coordinate.\r\n\t */\r\n\tgetPath(pInstance, pOrigin, pDestination) {\r\n\t\t// Get the instance data\r\n\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\tif (!instanceData) {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Instance data not found!');\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\t// Find the path\r\n\t\tconst pathID = instanceData.easystar.findPath(pOrigin.x, pOrigin.y, pDestination.x, pDestination.y, (pPath) => {\r\n\t\t\t// Check if the path is valid.\r\n\t\t\tif (Array.isArray(pPath) && pPath.length) {\r\n\t\t\t\t/**\r\n\t\t\t\t * The path generated.\r\n\t\t\t\t * @private\r\n\t\t\t\t * @type {Array}\r\n\t\t\t\t */\r\n\t\t\t\tlet path;\r\n\t\t\t\t// Offset the nodes by 1, because VYLO xCoord and yCoord start at 1.\r\n\t\t\t\tpath = pPath.map((pElement) => ({\r\n\t\t\t\t\tx: ++pElement.x,\r\n\t\t\t\t\ty: ++pElement.y\r\n\t\t\t\t}));\r\n\t\t\t\t// Remove the node you start on.\r\n\t\t\t\tpath.shift();\r\n\t\t\t\t// Store the path\r\n\t\t\t\tinstanceData.path = path;\r\n\t\t\t\t// Store the pathID\r\n\t\t\t\tinstanceData.pathID = pathID;\r\n\t\t\t\t// Call event when path is found\r\n\t\t\t\tif (typeof(instanceData.events.onPathFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathFound([...path]);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// Call event when no path is found\r\n\t\t\t\tif (typeof(instanceData.events.onPathNotFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathNotFound();\r\n\t\t\t\t}\r\n\t\t\t\t// If no path is found then we end the pathfinding on this instance.\r\n\t\t\t\tthis.end(pInstance);\r\n\t\t\t}\r\n\t\t});\r\n\t\t// Track pInstance as an active instance.\r\n\t\tthis.track(pInstance);\r\n\t}\r\n\t/**\r\n\t * Converts an array to an 2D array.\r\n\t * @private\r\n\t * @param {Array} pArray - The array to convert to a 2D array.\r\n\t * @param {number} pLengthOfSubArray - The length of the subarray.\r\n\t * @returns {Array} The 2D array.\r\n\t */\r\n\ttoTwoDimensionalArray(pArray, pLengthOfSubArray) {\r\n\t\tlet i = 0;\r\n\t\tconst result = [];\r\n\t\twhile (i < pArray.length) {\r\n\t\t\tresult.push(pArray.slice(i, i+= pLengthOfSubArray));\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t/**\r\n\t * Converts a tile to a node position.\r\n\t * @private\r\n\t * @param {Object}} pTile - The tile to convert into a node position.\r\n\t * @returns {Object} The node.\r\n\t */\r\n\ttileToNode(pTile) {\r\n\t\tif (typeof(pTile.mapName) === 'string') {\r\n\t\t\tif (PathwaySingleton.storedMapTiles[pTile.mapName]) {\r\n\t\t\t\tconst index = this.getIndexOf2DArray(PathwaySingleton.storedMapTiles[pTile.mapName].tiles2d, pTile);\r\n\t\t\t\tconst node = { x: index[1], y: index[0] };\r\n\t\t\t\treturn node;\r\n\t\t\t} else {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('There is no stored grid for the map this tile belongs to.');\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid mapname found on pTile');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Finds the index of a value in a 2D array.\r\n\t * @private\r\n\t * @param {Array} pArray - The 2D array to search in.\r\n\t * @param {any} pValue - The value to find in the 2D array.\r\n\t * @returns {Array
} Returns an array containing the row and column indices of the found value, or undefined if not found.\r\n\t */\r\n\tgetIndexOf2DArray(pArray, pValue) {\r\n\t\tfor (let i = 0; i < pArray.length; i++) {\r\n\t\t\tlet index = pArray[i].indexOf(pValue);\r\n\t\t\tif (index > -1) {\r\n\t\t\t\treturn [i, index];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Converts map tiles to grids.\r\n\t * @private\r\n\t * @param {string} pMapName - The mapname where the tile should come from.\r\n\t * @param {Array} pExclusionList - The exclude list to use for this grid.\r\n\t * @returns {Object|undefined} An object containing the grid created, an array of tiles that are to be accepted in the pathfinding system, and the weights of each tile.\r\n\t */\r\n\tmapTilesToGrid(pMapName, pExclusionList) {\r\n\t\tif (typeof(pMapName) === 'string') {\r\n\t\t\tif (Array.isArray(pExclusionList)) {\r\n\t\t\t\t// We check if this is a valid mapname found in VYLO.\r\n\t\t\t\tif (VYLO.Map.getMaps().includes(pMapName)) {\r\n\t\t\t\t\t// An array of tiles that we get from the map\r\n\t\t\t\t\tlet tilesArray;\r\n\t\t\t\t\t// An array of accepted tiles to be walked on.\r\n\t\t\t\t\tconst acceptedTiles = [0];\r\n\t\t\t\t\t// An array holding the weights of tiles.\r\n\t\t\t\t\tconst weights = [];\r\n\t\t\t\t\t// Get the dimensions of the map that was passed.\r\n\t\t\t\t\tconst mapSize = VYLO.Map.getMapSize(pMapName);\r\n\r\n\t\t\t\t\t// We check if we have stored tiles from this map before. If so we cache them.\r\n\t\t\t\t\tif (PathwaySingleton.storedMapTiles[pMapName]) {\r\n\t\t\t\t\t\t// We get the tile array from memory.\r\n\t\t\t\t\t\ttilesArray = PathwaySingleton.storedMapTiles[pMapName].tiles;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttilesArray = VYLO.Map.getTiles(pMapName);\r\n\t\t\t\t\t\t// We store this tiles array\r\n\t\t\t\t\t\tPathwaySingleton.storedMapTiles[pMapName] = { tiles: tilesArray, tiles2d: this.toTwoDimensionalArray(tilesArray, mapSize.x) };\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// This instance is impassible because it is dense and has no pathwayWeight, or it was explicitely set to be impassable.\r\n\t\t\t\t\tconst isImpassable = (pInstance) => (pInstance.pathwayWeight === PathwaySingleton.IMPASSABLE_WEIGHT) || pInstance.density && (!pInstance.pathwayWeight && pInstance.pathwayWeight !== PathwaySingleton.PASSABLE_WEIGHT);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Loop through the tiles array to build weights and accepted tile lists.\r\n\t\t\t\t\tconst grid = tilesArray.map((pTile) => {\r\n\t\t\t\t\t\t// If the tile is in the exclude list, we simply say it is passable. This prevents the tile's contents from being searched. \r\n\t\t\t\t\t\t// There is a possibility it could actually be an obstacle on this tile blocking movement from being completed. Use with caution.\r\n\t\t\t\t\t\tif (pExclusionList.includes(pTile)) return PathwaySingleton.PASSABLE_WEIGHT;\r\n\r\n\t\t\t\t\t\t// A weight of PathwaySingleton.PASSABLE_WEIGHT indicates no weight.\r\n\t\t\t\t\t\tlet weight = typeof(pTile.pathwayWeight) === 'number' ? pTile.pathwayWeight : PathwaySingleton.PASSABLE_WEIGHT;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// If this tile is not accessible, we cannot pass it, so we skip this tile.\r\n\t\t\t\t\t\tif (!this.isTileAccessible(pTile, pExclusionList)) {\r\n\t\t\t\t\t\t\treturn PathwaySingleton.IMPASSABLE_WEIGHT;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Accumulate weights of instances on the tile\r\n\t\t\t\t\t\tfor (const instance of pTile.getContents()) {\r\n\t\t\t\t\t\t\t// If this instance is to be excluded. We don't calculate its weight.\r\n\t\t\t\t\t\t\tif (pExclusionList.includes(instance)) continue;\r\n\r\n\t\t\t\t\t\t\t// If this instance is impassable we skip this tile.\r\n\t\t\t\t\t\t\tif (isImpassable(instance)) {\r\n\t\t\t\t\t\t\t\treturn PathwaySingleton.IMPASSABLE_WEIGHT;\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t// We accumulate the weight of instances\r\n\t\t\t\t\t\t\t\tif (typeof(instance.pathwayWeight) === 'number') {\r\n\t\t\t\t\t\t\t\t\tweight += instance.pathwayWeight;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Add weight to acceptedTiles if not already present\r\n\t\t\t\t\t\tif (weight !== PathwaySingleton.IMPASSABLE_WEIGHT && weight !== PathwaySingleton.PASSABLE_WEIGHT) {\r\n\t\t\t\t\t\t\tif (!acceptedTiles.includes(weight)) acceptedTiles.push(weight);\r\n\t\t\t\t\t\t\tif (!weights.includes(weight)) weights.push(weight);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\treturn weight;\r\n\t\t\t\t\t});\r\n\r\n\t\t\t\t\treturn { \r\n\t\t\t\t\t\t'acceptedTiles': acceptedTiles, \r\n\t\t\t\t\t\t'grid': this.toTwoDimensionalArray(grid, mapSize.x), \r\n\t\t\t\t\t\t'weights': weights \r\n\t\t\t\t\t};\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.logger.prefix('Pathway-Module').error('pMapName was not found in VYLO.');\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type for pExclusionList.');\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type for pMapName.');\r\n\t\t}\r\n\t}\r\n}\r\n/**\r\n * The module instantiated for use.\r\n * @type {PathwaySingleton}\r\n */\r\nconst Pathway = new PathwaySingleton();\r\n/**\r\n * Check if this is a server environment\r\n * @ignore\r\n */\r\nconst server = (typeof(window) === 'undefined');\r\n/**\r\n * Update API bound to Pathway\r\n * @ignore\r\n */\r\nconst update = Pathway.update.bind(Pathway);\r\n\r\n// If on the server we use an interval\r\nif (server) {\r\n\t// Update interval\r\n\tconst updateInterval = setInterval(update, 16);\r\n// Otherwise we use raf\r\n} else {\r\n\tconst updateLoop = () => {\r\n\t\tupdate();\r\n\t\trequestAnimationFrame(updateLoop);\r\n\t}\r\n\trequestAnimationFrame(updateLoop);\r\n}\r\n\r\nexport { Pathway };",
+ "content": "import { Utils } from './vendor/utils.min.mjs';\r\nimport { Logger } from './vendor/logger.min.mjs';\r\nimport { EasyStar } from './vendor/easystar-0.4.4.min.js';\r\n\r\n/**\r\n * @todo Test on server\r\n * @todo Make debugging class\r\n * @private\r\n */\r\nclass PathwaySingleton {\r\n\t/**\r\n\t * The maximum amount of ticks an instance can be in the same position before the pathfinder deems it \"stuck\". The user will be able to tweak values up to this max value.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic MAX_STUCK_COUNTER = 100;\r\n\t/**\r\n\t * The max amount of delta time between ticks. If this limit is passed, it will be clamped.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic MAX_DELTA_TIME = 0.03333333333;\r\n\t/**\r\n\t * The weight that indicates that this tile is walkable. This is used as the default weight of every instance unless otherwise stated.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic PASSABLE_WEIGHT = 0;\r\n\t/**\r\n\t * A static weight to be applied when a tile should be not be traveled to at all.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic IMPASSABLE_WEIGHT = -1;\r\n\t/**\r\n\t * The default amount of pixels per second to move the instance when using `position` mode.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic DEFAULT_PIXELS_PER_SECOND = 120;\r\n\t/**\r\n\t * The minimum distance away from a node before this system determines it has made it to that node.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic DEFAULT_MINIMUM_DISTANCE = 2;\r\n\t/**\r\n\t * The default mode this pathway system uses.\r\n\t * @private\r\n\t * @type {string}\r\n\t */\r\n\tstatic DEFAULT_MODE = 'collision';\r\n\t/**\r\n\t * An object that stores the map tiles in normal format and in 2D format.\r\n\t * @private\r\n\t * @type {Object}\r\n\t */\r\n\tstatic storedMapTiles = {};\r\n\t/**\r\n\t * The tile size to use if no tile size has been assigned.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tstatic DEFAULT_TILE_SIZE = { width: 32, height: 32 };\r\n\t/**\r\n\t * The tile size to use for this system.\r\n\t * @private\r\n\t * @type {Object}\r\n\t */\r\n\ttileSize = { ...PathwaySingleton.DEFAULT_TILE_SIZE };\r\n\t/**\r\n\t * The version of the module.\r\n\t */\r\n\tversion = \"VERSION_REPLACE_ME\";\r\n\t/**\r\n\t * A weakmap storing the data of instances used in this pathfinder.\r\n\t * @private\r\n\t * @type {WeakMap}\r\n\t */\r\n\tinstanceWeakMap = new WeakMap();\r\n\t/**\r\n\t * The last tracked time in the ticker.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tlastTime = 0;\r\n\t/**\r\n\t * The delta time between the current and last tick.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\tdeltaTime = 0;\r\n\t/**\r\n\t * The time in ms between the current and last tick.\r\n\t * @private\r\n\t * @type {number}\r\n\t */\r\n\telapsedMS = 0;\r\n\t/**\r\n\t * An array of active instances that are currently pathfinding.\r\n\t * @private\r\n\t * @type {Array}\r\n\t */\r\n\tactiveInstances = [];\r\n\t/**\r\n\t * @private\r\n\t */\r\n\tconstructor() {\r\n // Create a logger\r\n /** The logger module this module uses to log errors / logs\r\n * @private\r\n * @type {Object}\r\n */\r\n this.logger = new Logger();\r\n this.logger.registerType('Pathway-Module', '#ff6600');\r\n\t}\r\n\t/**\r\n\t * Moves pInstance to the destination position with pOptions in mind.\r\n\t * @param {Object} pInstance - The instance to move to the destination. The origin position will be retrived from this instance as well.\r\n\t * @param {Object} pDestination - The end position to travel to.\r\n\t * @property {number} pDestination.x - The end x coordinate.\r\n\t * @property {number} pDestination.y - The end y coordinate.\r\n\t * @param {Object} pOptions - An object of settings on how to move pInstance to pDestination.\r\n\t * @property {boolean} [pOptions.diagonal = false] - Whether or not the pathfinder allows diagonal moves.\r\n\t * @property {Array} pOptions.exclude - An array of diobs that will be excluded when calculating the path.\r\n\t * @property {number} [pOptions.minDistance = 2] = The minimum distance this pathway system will use to calculate if you have reached the (next) node. \r\n\t * @property {number} [pOptions.maxStuckCounter = 100] - The maximum amount of ticks of pInstance being in the same position as the last tick before its considered stuck.\r\n\t * @property {string} [pOptions.mode = 'collision'] - How this instance will move. `collision` for moving with collisions in mind (movePos). `position` for moving with no collisions in mind (setPos) Must use pOptions.pixelsPerSecond when using `position` mode. \r\n\t * @property {string} [pOptions.pixelsPerSecond = 120] - The speed in pixels this instance moves per second. This setting only works when pOptions.mode is set to `position`. \r\n\t * @property {Function} pOptions.onPathComplete - Callback for when pInstance makes it to the destination node.\r\n\t * @property {Function} pOptions.onPathFound - Callback for when pInstance finds a path.\r\n\t * @property {Function} pOptions.onPathStuck - Callback for when pInstance gets stuck on a path.\r\n\t * @property {Function} pOptions.onPathNotFound - Callback for when no path is found.\r\n\t */\r\n\tto(pInstance, pDestination, pOptions) {\r\n\t\tif (typeof(pInstance) === 'object') {\r\n\t\t\t// If this instance is not on a map.\r\n\t\t\tif (!pInstance.mapName) {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Cannot generate a path. pInstance is not on a map.');\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// If there is no destination object passed return.\r\n\t\t\tif (typeof(pDestination) !== 'object') {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type passed for pDestination. Expecting an object.');\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// Get the instance data for this instance\r\n\t\t\tlet instanceData = this.instanceWeakMap.get(pInstance);\r\n\r\n\t\t\tif (!instanceData) {\r\n\t\t\t\t// Set the instance data\r\n\t\t\t\tinstanceData = {\r\n\t\t\t\t\ttrajectory: { \r\n\t\t\t\t\t\tangle: 0, \r\n\t\t\t\t\t\tx: 0, \r\n\t\t\t\t\t\ty: 0, \r\n\t\t\t\t\t\tnextNodePos: null,\r\n\t\t\t\t\t},\r\n\t\t\t\t\t// The current position of the instance.\r\n\t\t\t\t\tcurrentPosition: { x: 0, y: 0 },\r\n\t\t\t\t\t// The previous position of the instance in the tick before.\r\n\t\t\t\t\tpreviousPosition: { x: 0, y: 0 },\r\n\t\t\t\t\t// The stuck counter of this instance. When this instance is in the same position for multiple ticks, this value is added onto up until -\r\n\t\t\t\t\t// the max stuck counter is reached and the `stuck` event is called.\r\n\t\t\t\t\tstuckCounter: 0,\r\n\t\t\t\t\tmaxStuckCounter: PathwaySingleton.MAX_STUCK_COUNTER,\r\n\t\t\t\t\tpathID: null, // ID of the path that was generated. Used to cancel the path.\r\n\t\t\t\t\tpath: [],\r\n\t\t\t\t\tmoving: null,\r\n\t\t\t\t\tmode: PathwaySingleton.DEFAULT_MODE,\r\n\t\t\t\t\tpixelsPerSecond: PathwaySingleton.DEFAULT_PIXELS_PER_SECOND,\r\n\t\t\t\t\tminDistance: PathwaySingleton.DEFAULT_MINIMUM_DISTANCE,\r\n\t\t\t\t\tevents: {\r\n\t\t\t\t\t\tonPathStuck: null,\r\n\t\t\t\t\t\tonPathComplete: null,\r\n\t\t\t\t\t\tonPathFound: null,\r\n\t\t\t\t\t\tonPathNotFound: null,\r\n\t\t\t\t\t},\r\n\t\t\t\t\teasystar: new EasyStar.js()\r\n\t\t\t\t};\r\n\t\t\t\t// If you have a large grid, then it is possible that these calculations could slow down the browser. \r\n\t\t\t\t// For this reason, it might be a good idea to give EasyStar a smaller iterationsPerCalculation\r\n\t\t\t\t// https://github.com/prettymuchbryce/easystarjs\r\n\t\t\t\tinstanceData.easystar.setIterationsPerCalculation(1000);\r\n\t\t\t\t// Assign the instance data\r\n\t\t\t\tthis.instanceWeakMap.set(pInstance, instanceData);\r\n\t\t\t} else {\r\n\t\t\t\t// If this instance has data already, we reset it\r\n\t\t\t\tthis.end(pInstance);\r\n\t\t\t}\r\n\r\n\t\t\t/**\r\n\t\t\t * An exclusion list of instances and tiles.\r\n\t\t\t * @type {Array}\r\n\t\t\t */\r\n\t\t\tlet excludeList = [];\r\n\r\n\t\t\t// If there are options passed. Parse them.\r\n\t\t\tif (typeof(pOptions) === 'object') {\r\n\t\t\t\t// If max stuck counter is found in options, set it.\r\n\t\t\t\tif (typeof(pOptions.maxStuckCounter) === 'number') {\r\n\t\t\t\t\tinstanceData.maxStuckCounter = pOptions.maxStuckCounter;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Enable diagonals if found in passed options.\r\n\t\t\t\t// This can cause some \"issues\" such as trying to cut through corners.\r\n\t\t\t\tif (pOptions.diagonal) {\r\n\t\t\t\t\tinstanceData.easystar.enableDiagonals();\r\n\t\t\t\t\tinstanceData.easystar.enableCornerCutting();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Set the positioning mode\r\n\t\t\t\tif (pOptions.mode) {\r\n\t\t\t\t\t// Get the mode, if an invalid mode is passed, we default to the default mode.\r\n\t\t\t\t\tconst mode = (pOptions.mode === 'collision' || pOptions.mode === 'position') ? pOptions.mode : PathwaySingleton.DEFAULT_MODE;\r\n\t\t\t\t\tinstanceData.mode = mode;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Assign pixels per second \r\n\t\t\t\tif (typeof(pOptions.pixelsPerSecond) === 'number') {\r\n\t\t\t\t\tinstanceData.pixelsPerSecond = pOptions.pixelsPerSecond;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Assign the min distance\r\n\t\t\t\tif (typeof(pOptions.minDistance) === 'number') {\r\n\t\t\t\t\tinstanceData.minDistance = pOptions.minDistance;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Assign events\r\n\t\t\t\tif (typeof(pOptions.onPathComplete) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathComplete = pOptions.onPathComplete;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (typeof(pOptions.onPathFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathFound = pOptions.onPathFound;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (typeof(pOptions.onPathNotFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathNotFound = pOptions.onPathNotFound;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (typeof(pOptions.onPathStuck) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathStuck = pOptions.onPathStuck;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Copy the contents of the exclude array to the exclude array we manage.\r\n\t\t\t\tif (Array.isArray(pOptions.exclude)) {\r\n\t\t\t\t\texcludeList.push(...pOptions.exclude);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// We add the instance to the exclude array so that it is excluded.\r\n\t\t\tif (!excludeList.includes(pInstance)) {\r\n\t\t\t\texcludeList.push(pInstance);\r\n\t\t\t}\r\n\r\n\t\t\t// Build the 2D array grid that represents the map\r\n\t\t\tconst gridInfo = this.mapTilesToGrid(pInstance.mapName, excludeList);\r\n\t\t\t\r\n\t\t\t// Assign the grid to easystar\r\n\t\t\tinstanceData.easystar.setGrid(gridInfo.grid);\r\n\t\t\t\r\n\t\t\t// Assign the weight of each tile\r\n\t\t\tgridInfo.weights.forEach((pWeight) => {\r\n\t\t\t\tinstanceData.easystar.setTileCost(pWeight, pWeight);\r\n\t\t\t});\r\n\r\n\t\t\t// Assign what tiles can be used\r\n\t\t\tinstanceData.easystar.setAcceptableTiles(gridInfo.acceptedTiles);\r\n\r\n\t\t\t// Grab the pos of the instance so we can locate the starting tile its on.\r\n\t\t\t// This is also used as the startingNode position.\r\n\t\t\tconst instancePosition = this.getPositionFromInstance(pInstance);\r\n\t\t\t// Get the origin tile the instance is on.\r\n\t\t\tconst originTile = VYLO.Map.getLocByPos(instancePosition.x, instancePosition.y, pInstance.mapName);\r\n\t\t\t// Get the dimensions of the map that was passed.\r\n\t\t\tconst mapSize = VYLO.Map.getMapSize(pInstance.mapName);\r\n\r\n\t\t\t// Get the end nodes position so we can get the destinationTile\r\n\t\t\tconst endNodeX = Utils.clamp(Utils.clamp(pDestination.x, 0, mapSize.x) * this.tileSize.width + this.tileSize.width / 2, 0, mapSize.xPos - this.tileSize.width);\r\n\t\t\tconst endNodeY = Utils.clamp(Utils.clamp(pDestination.y, 0, mapSize.y) * this.tileSize.height + this.tileSize.height / 2, 0, mapSize.yPos - this.tileSize.height);\r\n\t\t\t// Get the end tile\r\n\t\t\tconst destinationTile = VYLO.Map.getLocByPos(endNodeX, endNodeY, pInstance.mapName);\r\n\t\t\t\r\n\t\t\t// Make sure these have resolved to actual tiles.\r\n\t\t\tif (originTile && destinationTile) {\r\n\t\t\t\t// Check if the origin and end tile are accessible\r\n\t\t\t\tif (this.isTileAccessible(originTile, excludeList) && this.isTileAccessible(destinationTile, excludeList)) {\r\n\t\t\t\t\t// Get the start node from the originTile\r\n\t\t\t\t\tlet startNode = this.tileToNode(originTile);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Get the end node from the destinationTile\r\n\t\t\t\t\tlet endNode = this.tileToNode(destinationTile);\r\n\r\n\t\t\t\t\t// Generate the path for the player\r\n\t\t\t\t\tthis.getPath(pInstance, { x: startNode.x, y: startNode.y }, { x: endNode.x, y: endNode.y });\t\t\t\t\r\n\t\t\t\t// If the origin tile or end tile is not accessible to be walked on then return no path found.\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// Fire the path not found event.\t\r\n\t\t\t\t\tif (typeof(instanceData.events.onPathNotFound) === 'function') {\r\n\t\t\t\t\t\tinstanceData.events.onPathNotFound();\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.end(pInstance);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Origin tile or destination tile cannot be found.');\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type passed for pInstance. Expecting an object.');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Tracks this instance as active.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to track.\r\n\t */\r\n\ttrack(pInstance) {\r\n\t\t// Add this instance to being tracked.\r\n\t\tif (!this.activeInstances.includes(pInstance)) {\r\n\t\t\tthis.activeInstances.push(pInstance);\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Untracks this instance. It is no longer considered active.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to untrack.\r\n\t */\r\n\tuntrack(pInstance) {\r\n\t\t// Remove this instance from being tracked.\r\n\t\tif (this.activeInstances.includes(pInstance)) {\r\n\t\t\tthis.activeInstances.splice(this.activeInstances.indexOf(pInstance), 1);\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Ends the current pathfinding for pInstance.\r\n\t * @param {Object} pInstance - The instance to terminate pathfinding on.\r\n\t */\r\n\tend(pInstance) {\r\n\t\t// Get the instance data for this instance\r\n\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\tif (instanceData) {\r\n\t\t\t// We are ending the pathfinding. So we get the path ID so we can cancel calculations being made for this path.\r\n\t\t\tif (instanceData.pathID) {\r\n\t\t\t\tinstanceData.easystar.cancelPath(instanceData.pathID);\r\n\t\t\t\tinstanceData.pathID = null;\r\n\t\t\t}\r\n\t\t\t// Disable diagonals in the event they were enabled in a previous call\r\n\t\t\tinstanceData.easystar.disableDiagonals();\r\n\t\t\t// Disable corner cutting in the event it was enabled in a previous call\r\n\t\t\tinstanceData.easystar.disableCornerCutting();\r\n\t\t\t// Reset trajectory data\r\n\t\t\tinstanceData.trajectory.x = 0;\r\n\t\t\tinstanceData.trajectory.y = 0;\r\n\t\t\tinstanceData.trajectory.angle = 0;\r\n\t\t\tinstanceData.trajectory.nextNodePos = null;\r\n\t\t\t// Reset events\r\n\t\t\tinstanceData.events.onPathStuck = null;\r\n\t\t\tinstanceData.events.onPathComplete = null;\r\n\t\t\tinstanceData.events.onPathFound = null;\r\n\t\t\tinstanceData.events.onPathNotFound = null;\r\n\t\t\t// Reset stuck counter\r\n\t\t\tinstanceData.stuckCounter = 0;\r\n\t\t\t// Reset the max stuck counter\r\n\t\t\tinstanceData.maxStuckCounter = PathwaySingleton.MAX_STUCK_COUNTER;\r\n\t\t\t// Empty path array\r\n\t\t\tinstanceData.path.length = 0;\r\n\t\t\t// Reset it to not being moved.\r\n\t\t\tinstanceData.moving = false;\r\n\t\t\t// Reset the mode\r\n\t\t\tinstanceData.mode = 'collision';\r\n\t\t\t// Reset the pixels per second.\r\n\t\t\tinstanceData.pixelsPerSecond = PathwaySingleton.DEFAULT_PIXELS_PER_SECOND;\r\n\t\t\t// Reset the min distance\r\n\t\t\tinstanceData.minDistance = PathwaySingleton.DEFAULT_MINIMUM_DISTANCE;\r\n\t\t\t// Stop instance from moving via VYLO API.\r\n\t\t\tpInstance.move();\r\n\t\t\t// Untrack pInstance as an active instance.\r\n\t\t\tthis.untrack(pInstance);\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('No instance data found from pInstance. This instance is not engaged in pathfinding.');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Gets the position from the instance based on the pathfinding info. Centered position from the geometrical.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to get the position from.\r\n\t * @returns {Object} - The position of the instance.\r\n\t */\r\n\tgetPositionFromInstance(pInstance) {\r\n\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\tif (instanceData) {\r\n\t\t\tinstanceData.currentPosition.x = Math.floor(pInstance.x + pInstance.xOrigin + pInstance.width / 2);\r\n\t\t\tinstanceData.currentPosition.y = Math.floor(pInstance.y + pInstance.yOrigin + pInstance.height / 2);\r\n\t\t\treturn instanceData.currentPosition;\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Updates active instances on the pathfinder.\r\n\t * @private\r\n\t */\r\n\tupdate() {\r\n\t\t// Get current timestamp\r\n\t\tconst now = Date.now();\r\n\t\t// Get the elapsed ms from the last tick\r\n\t\tthis.elapsedMS = now - this.lastTime;\r\n\t\t// Get the delta time between the last tick\r\n\t\tthis.deltaTime = (this.elapsedMS / 1000);\r\n\t\t// If the delta time grows too large, we clamp it\r\n\t\tif (this.deltaTime >= PathwaySingleton.MAX_DELTA_TIME) {\r\n\t\t\tthis.deltaTime = PathwaySingleton.MAX_DELTA_TIME;\r\n\t\t}\r\n\t\t// Loop active instances and update.\r\n\t\tthis.activeInstances.forEach((pInstance) => {\r\n\t\t\t// Get the instance data for this instance\r\n\t\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\t\tif (instanceData) {\r\n\t\t\t\t// Calculate the path\r\n\t\t\t\tinstanceData.easystar.calculate();\r\n\r\n\t\t\t\t// If this instance is being moved\r\n\t\t\t\tif (Array.isArray(instanceData.path) && (instanceData.path.length || instanceData.moving)) {\r\n\t\t\t\t\t// Get the position of the instance\r\n\t\t\t\t\tconst instancePosition = this.getPositionFromInstance(pInstance);\r\n\t\t\t\t\t// If the instance is not moving\r\n\t\t\t\t\tif (!instanceData.moving) {\r\n\t\t\t\t\t\t// Get the next node to travel to.\r\n\t\t\t\t\t\tconst node = instanceData.path.shift();\r\n\t\t\t\t\t\t// Get the position of that node in real world coordinates. We subtract half of the tileSize to get the center of the node's posiiton.\r\n\t\t\t\t\t\tconst nodePos = { \r\n\t\t\t\t\t\t\tx: (node.x * this.tileSize.width) - this.tileSize.width / 2, \r\n\t\t\t\t\t\t\ty: (node.y * this.tileSize.height) - this.tileSize.height / 2 \r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\t// Store the next node position\r\n\t\t\t\t\t\tinstanceData.trajectory.nextNodePos = nodePos;\r\n\t\t\t\t\t\t// Get the angle from the instance's position to the next node\r\n\t\t\t\t\t\tinstanceData.trajectory.angle = Utils.getAngle2(instancePosition, instanceData.trajectory.nextNodePos);\r\n\t\t\t\t\t\t// Get the trajectory of where to move the instance based on the angle\r\n\t\t\t\t\t\tinstanceData.trajectory.x = Math.cos(instanceData.trajectory.angle);\r\n\t\t\t\t\t\tinstanceData.trajectory.y = -Math.sin(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t// Update the direction of the instance based on the angle to the next node\r\n\t\t\t\t\t\tpInstance.dir = Utils.getDirection(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t// Move the instance with collision mode or positional mode\r\n\t\t\t\t\t\tif (instanceData.mode === 'collision') {\r\n\t\t\t\t\t\t\tpInstance.movePos(instanceData.trajectory.x, instanceData.trajectory.y);\r\n\t\t\t\t\t\t} else if (instanceData.mode === 'position') {\r\n\t\t\t\t\t\t\tconst speed = instanceData.pixelsPerSecond * this.deltaTime;\r\n\t\t\t\t\t\t\tpInstance.setPos(pInstance.x + speed * instanceData.trajectory.x, pInstance.y + speed * instanceData.trajectory.y, pInstance.mapName);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tinstanceData.moving = true;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Get the distance from the instance's position to the next node's position.\r\n\t\t\t\t\t\tconst distance = Utils.getDistance(instancePosition, instanceData.trajectory.nextNodePos);\r\n\t\t\t\t\t\t// Stop moving when you are this close distance.\r\n\t\t\t\t\t\tif (distance <= instanceData.minDistance) {\r\n\t\t\t\t\t\t\t// Stop moving\r\n\t\t\t\t\t\t\tinstanceData.moving = false;\r\n\t\t\t\t\t\t\t// Reset stuck counter when moving has \"stopped\".\r\n\t\t\t\t\t\t\tinstanceData.stuckCounter = 0;\r\n\t\t\t\t\t\t\t// If there is no more nodes left in the path\r\n\t\t\t\t\t\t\tif (!instanceData.path.length) {\r\n\t\t\t\t\t\t\t\t// You have completed the path. Call the event function if supplied.\r\n\t\t\t\t\t\t\t\tif (typeof(instanceData.events.onPathComplete) === 'function') {\r\n\t\t\t\t\t\t\t\t\tinstanceData.events.onPathComplete();\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tthis.end(pInstance);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tinstanceData.trajectory.angle = Utils.getAngle2(instancePosition, instanceData.trajectory.nextNodePos);\r\n\t\t\t\t\t\t\tinstanceData.trajectory.x = Math.cos(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t\tinstanceData.trajectory.y = -Math.sin(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t\tpInstance.dir = Utils.getDirection(instanceData.trajectory.angle);\r\n\t\t\t\t\t\t\t// Move the instance with collision mode or positional mode\r\n\t\t\t\t\t\t\tif (instanceData.mode === 'collision') {\r\n\t\t\t\t\t\t\t\tpInstance.movePos(instanceData.trajectory.x, instanceData.trajectory.y);\r\n\t\t\t\t\t\t\t} else if (instanceData.mode === 'position') {\r\n\t\t\t\t\t\t\t\tconst speed = instanceData.pixelsPerSecond * this.deltaTime;\r\n\t\t\t\t\t\t\t\tpInstance.setPos(pInstance.x + speed * instanceData.trajectory.x, pInstance.y + speed * instanceData.trajectory.y, pInstance.mapName);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tinstanceData.moving = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// If the instance's position is in the same spot it was in the last tick\r\n\t\t\t\t\tif (instancePosition.x === instanceData.previousPosition.x && instancePosition.y === instanceData.previousPosition.y) {\r\n\t\t\t\t\t\t// Increment the stuck counter\r\n\t\t\t\t\t\tinstanceData.stuckCounter++;\r\n\t\t\t\t\t\t// Chekck if the stuck counter is greater or equal to the max stuck counter\r\n\t\t\t\t\t\tif (instanceData.stuckCounter >= instanceData.maxStuckCounter) {\r\n\t\t\t\t\t\t\t// Call the stuck event if defined.\r\n\t\t\t\t\t\t\tif (typeof(instanceData.events.onPathStuck) === 'function') {\r\n\t\t\t\t\t\t\t\tinstanceData.events.onPathStuck();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// End this pathfinding.\r\n\t\t\t\t\t\t\tthis.end(pInstance);\r\n\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// Store the previous position as the position of this tick\r\n\t\t\t\t\tinstanceData.previousPosition = instancePosition;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t\t// Store this tick's time\r\n\t\tthis.lastTime = now;\r\n\t}\r\n\t/**\r\n\t * Sets the tilesize of this system.\r\n\t * @param {number} pTileSize - The tilesize of the game.\r\n\t */\r\n\tsetTileSize(pTileSize) {\r\n\t\tif (typeof(pTileSize) === 'number') {\r\n\t\t\tthis.tileSize = { width: pTileSize, height: pTileSize };\r\n\t\t} else if(typeof(pTileSize) === 'object') {\r\n\t\t\tconst width = pTileSize.width;\r\n\t\t\tconst height = pTileSize.height;\r\n\t\t\t// Assign the tilesize width\r\n\t\t\tif (typeof(width) === 'number') {\r\n\t\t\t\tthis.tileSize.width = width;\r\n\t\t\t}\r\n\t\t\t// Assign the tilesize height\r\n\t\t\tif (typeof(height) === 'number') {\r\n\t\t\t\tthis.tileSize.height = height;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type used for pTileSize');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Checks to see if pTile is accessible for movement.\r\n\t * @private\r\n\t * @param {Object} pTile - The tile to check the validity of.\r\n\t * @param {Array} pExclusionList - The list of excluded tiles/instances.\r\n\t * @returns {boolean} - If this tile is accessible.\r\n\t */\r\n\tisTileAccessible(pTile, pExclusionList) {\r\n\t\t// If the tile is in the exclude list, we simply say it is accessible. This prevents the tile's contents from being searched. \r\n\t\t// There is a possibility it could actually be an obstacle on this tile blocking movement from being completed. Use with caution.\r\n\t\tconst isExcluded = (pInstance) => pExclusionList.includes(pInstance);\r\n\t\t// This instance is impassible because it is dense and has no pathwayWeight, or it was explicitely set to be impassable.\r\n\t\tconst isImpassable = (pInstance) => (pInstance.pathwayWeight === PathwaySingleton.IMPASSABLE_WEIGHT) || pInstance.density && (!pInstance.pathwayWeight && pInstance.pathwayWeight !== PathwaySingleton.PASSABLE_WEIGHT);\r\n\t\t// If this tile has dense instances that are not being excluded, doesn't have a pathwayWeight set, or are explicitely set to be impassable.\r\n\t\tconst hasImpassableContent = (pInstance) => pInstance.getContents().some((pInstance) => {\r\n\t\t\treturn isImpassable(pInstance) && !isExcluded(pInstance);\r\n\t\t});\r\n\t\treturn isExcluded(pTile) || (!isImpassable(pTile) && !hasImpassableContent(pTile));\r\n\t}\r\n\t/**\r\n\t * Generates a path from the origin point to the end point with obstacles in mind.\r\n\t * @private\r\n\t * @param {Object} pInstance - The instance to grab data from.\r\n\t * @param {Object} pOrigin - An object containing the start x and y position. \r\n\t * @property {number} pOrigin.x - The start x coordinate.\r\n\t * @property {number} pOrigin.y -The start y coordinate.\r\n\t * @param {Object} pDestination - An object containing the end x and y position to travel to.\r\n\t * @property {number} pDestination.x - The end x coordinate.\r\n\t * @property {number} pDestination.y - The end y coordinate.\r\n\t */\r\n\tgetPath(pInstance, pOrigin, pDestination) {\r\n\t\t// Get the instance data\r\n\t\tconst instanceData = this.instanceWeakMap.get(pInstance);\r\n\t\tif (!instanceData) {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Instance data not found!');\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\t// Find the path\r\n\t\tconst pathID = instanceData.easystar.findPath(pOrigin.x, pOrigin.y, pDestination.x, pDestination.y, (pPath) => {\r\n\t\t\t// Check if the path is valid.\r\n\t\t\tif (Array.isArray(pPath) && pPath.length) {\r\n\t\t\t\t/**\r\n\t\t\t\t * The path generated.\r\n\t\t\t\t * @private\r\n\t\t\t\t * @type {Array}\r\n\t\t\t\t */\r\n\t\t\t\tlet path;\r\n\t\t\t\t// Offset the nodes by 1, because VYLO xCoord and yCoord start at 1.\r\n\t\t\t\tpath = pPath.map((pElement) => ({\r\n\t\t\t\t\tx: ++pElement.x,\r\n\t\t\t\t\ty: ++pElement.y\r\n\t\t\t\t}));\r\n\t\t\t\t// Remove the node you start on.\r\n\t\t\t\tpath.shift();\r\n\t\t\t\t// Store the path\r\n\t\t\t\tinstanceData.path = path;\r\n\t\t\t\t// Store the pathID\r\n\t\t\t\tinstanceData.pathID = pathID;\r\n\t\t\t\t// Call event when path is found\r\n\t\t\t\tif (typeof(instanceData.events.onPathFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathFound([...path]);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// Call event when no path is found\r\n\t\t\t\tif (typeof(instanceData.events.onPathNotFound) === 'function') {\r\n\t\t\t\t\tinstanceData.events.onPathNotFound();\r\n\t\t\t\t}\r\n\t\t\t\t// If no path is found then we end the pathfinding on this instance.\r\n\t\t\t\tthis.end(pInstance);\r\n\t\t\t}\r\n\t\t});\r\n\t\t// Track pInstance as an active instance.\r\n\t\tthis.track(pInstance);\r\n\t}\r\n\t/**\r\n\t * Converts an array to an 2D array.\r\n\t * @private\r\n\t * @param {Array} pArray - The array to convert to a 2D array.\r\n\t * @param {number} pLengthOfSubArray - The length of the subarray.\r\n\t * @returns {Array} The 2D array.\r\n\t */\r\n\ttoTwoDimensionalArray(pArray, pLengthOfSubArray) {\r\n\t\tlet i = 0;\r\n\t\tconst result = [];\r\n\t\twhile (i < pArray.length) {\r\n\t\t\tresult.push(pArray.slice(i, i+= pLengthOfSubArray));\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t/**\r\n\t * Converts a tile to a node position.\r\n\t * @private\r\n\t * @param {Object}} pTile - The tile to convert into a node position.\r\n\t * @returns {Object} The node.\r\n\t */\r\n\ttileToNode(pTile) {\r\n\t\tif (typeof(pTile.mapName) === 'string') {\r\n\t\t\tif (PathwaySingleton.storedMapTiles[pTile.mapName]) {\r\n\t\t\t\tconst index = this.getIndexOf2DArray(PathwaySingleton.storedMapTiles[pTile.mapName].tiles2d, pTile);\r\n\t\t\t\tconst node = { x: index[1], y: index[0] };\r\n\t\t\t\treturn node;\r\n\t\t\t} else {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('There is no stored grid for the map this tile belongs to.');\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid mapname found on pTile');\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Finds the index of a value in a 2D array.\r\n\t * @private\r\n\t * @param {Array} pArray - The 2D array to search in.\r\n\t * @param {any} pValue - The value to find in the 2D array.\r\n\t * @returns {Array} Returns an array containing the row and column indices of the found value, or undefined if not found.\r\n\t */\r\n\tgetIndexOf2DArray(pArray, pValue) {\r\n\t\tfor (let i = 0; i < pArray.length; i++) {\r\n\t\t\tlet index = pArray[i].indexOf(pValue);\r\n\t\t\tif (index > -1) {\r\n\t\t\t\treturn [i, index];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * Converts map tiles to grids.\r\n\t * @private\r\n\t * @param {string} pMapName - The mapname where the tile should come from.\r\n\t * @param {Array} pExclusionList - The exclude list to use for this grid.\r\n\t * @returns {Object|undefined} An object containing the grid created, an array of tiles that are to be accepted in the pathfinding system, and the weights of each tile.\r\n\t */\r\n\tmapTilesToGrid(pMapName, pExclusionList) {\r\n\t\tif (typeof(pMapName) === 'string') {\r\n\t\t\tif (Array.isArray(pExclusionList)) {\r\n\t\t\t\t// We check if this is a valid mapname found in VYLO.\r\n\t\t\t\tif (VYLO.Map.getMaps().includes(pMapName)) {\r\n\t\t\t\t\t// An array of tiles that we get from the map\r\n\t\t\t\t\tlet tilesArray;\r\n\t\t\t\t\t// An array of accepted tiles to be walked on.\r\n\t\t\t\t\tconst acceptedTiles = [0];\r\n\t\t\t\t\t// An array holding the weights of tiles.\r\n\t\t\t\t\tconst weights = [];\r\n\t\t\t\t\t// Get the dimensions of the map that was passed.\r\n\t\t\t\t\tconst mapSize = VYLO.Map.getMapSize(pMapName);\r\n\r\n\t\t\t\t\t// We check if we have stored tiles from this map before. If so we cache them.\r\n\t\t\t\t\tif (PathwaySingleton.storedMapTiles[pMapName]) {\r\n\t\t\t\t\t\t// We get the tile array from memory.\r\n\t\t\t\t\t\ttilesArray = PathwaySingleton.storedMapTiles[pMapName].tiles;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttilesArray = VYLO.Map.getTiles(pMapName);\r\n\t\t\t\t\t\t// We store this tiles array\r\n\t\t\t\t\t\tPathwaySingleton.storedMapTiles[pMapName] = { tiles: tilesArray, tiles2d: this.toTwoDimensionalArray(tilesArray, mapSize.x) };\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// This instance is impassible because it is dense and has no pathwayWeight, or it was explicitely set to be impassable.\r\n\t\t\t\t\tconst isImpassable = (pInstance) => (pInstance.pathwayWeight === PathwaySingleton.IMPASSABLE_WEIGHT) || pInstance.density && (!pInstance.pathwayWeight && pInstance.pathwayWeight !== PathwaySingleton.PASSABLE_WEIGHT);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Loop through the tiles array to build weights and accepted tile lists.\r\n\t\t\t\t\tconst grid = tilesArray.map((pTile) => {\r\n\t\t\t\t\t\t// If the tile is in the exclude list, we simply say it is passable. This prevents the tile's contents from being searched. \r\n\t\t\t\t\t\t// There is a possibility it could actually be an obstacle on this tile blocking movement from being completed. Use with caution.\r\n\t\t\t\t\t\tif (pExclusionList.includes(pTile)) return PathwaySingleton.PASSABLE_WEIGHT;\r\n\r\n\t\t\t\t\t\t// A weight of PathwaySingleton.PASSABLE_WEIGHT indicates no weight.\r\n\t\t\t\t\t\tlet weight = typeof(pTile.pathwayWeight) === 'number' ? pTile.pathwayWeight : PathwaySingleton.PASSABLE_WEIGHT;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// If this tile is not accessible, we cannot pass it, so we skip this tile.\r\n\t\t\t\t\t\tif (!this.isTileAccessible(pTile, pExclusionList)) {\r\n\t\t\t\t\t\t\treturn PathwaySingleton.IMPASSABLE_WEIGHT;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Accumulate weights of instances on the tile\r\n\t\t\t\t\t\tfor (const instance of pTile.getContents()) {\r\n\t\t\t\t\t\t\t// If this instance is to be excluded. We don't calculate its weight.\r\n\t\t\t\t\t\t\tif (pExclusionList.includes(instance)) continue;\r\n\r\n\t\t\t\t\t\t\t// If this instance is impassable we skip this tile.\r\n\t\t\t\t\t\t\tif (isImpassable(instance)) {\r\n\t\t\t\t\t\t\t\treturn PathwaySingleton.IMPASSABLE_WEIGHT;\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t// We accumulate the weight of instances\r\n\t\t\t\t\t\t\t\tif (typeof(instance.pathwayWeight) === 'number') {\r\n\t\t\t\t\t\t\t\t\tweight += instance.pathwayWeight;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Add weight to acceptedTiles if not already present\r\n\t\t\t\t\t\tif (weight !== PathwaySingleton.IMPASSABLE_WEIGHT && weight !== PathwaySingleton.PASSABLE_WEIGHT) {\r\n\t\t\t\t\t\t\tif (!acceptedTiles.includes(weight)) acceptedTiles.push(weight);\r\n\t\t\t\t\t\t\tif (!weights.includes(weight)) weights.push(weight);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\treturn weight;\r\n\t\t\t\t\t});\r\n\r\n\t\t\t\t\treturn { \r\n\t\t\t\t\t\t'acceptedTiles': acceptedTiles, \r\n\t\t\t\t\t\t'grid': this.toTwoDimensionalArray(grid, mapSize.x), \r\n\t\t\t\t\t\t'weights': weights \r\n\t\t\t\t\t};\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.logger.prefix('Pathway-Module').error('pMapName was not found in VYLO.');\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type for pExclusionList.');\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis.logger.prefix('Pathway-Module').error('Invalid type for pMapName.');\r\n\t\t}\r\n\t}\r\n}\r\n/**\r\n * The module instantiated for use.\r\n * @type {PathwaySingleton}\r\n */\r\nconst Pathway = new PathwaySingleton();\r\n/**\r\n * Check if this is a server environment\r\n * @ignore\r\n */\r\nconst server = (typeof(window) === 'undefined');\r\n/**\r\n * Update API bound to Pathway\r\n * @ignore\r\n */\r\nconst update = Pathway.update.bind(Pathway);\r\n\r\n// If on the server we use an interval\r\nif (server) {\r\n\t// Update interval\r\n\tconst updateInterval = setInterval(update, 16);\r\n// Otherwise we use raf\r\n} else {\r\n\tconst updateLoop = () => {\r\n\t\tupdate();\r\n\t\trequestAnimationFrame(updateLoop);\r\n\t}\r\n\trequestAnimationFrame(updateLoop);\r\n}\r\n\r\nexport { Pathway };",
"static": true,
"longname": "C:/Users/jared/Documents/Github/Pathway/src/pathway.mjs",
"access": "public",
@@ -1651,7 +1651,7 @@
},
{
"kind": "index",
- "content": "# Pathway\r\nA module that will enable pathfinding functionality in the Vylocity Game Engine \r\n\r\nUses [easystar](https://github.com/prettymuchbryce/easystarjs) under the hood.\r\n\r\n\r\n# ES Module\r\n```js\r\n// Importing as an ES module\r\nimport { Pathway } from './pathway.mjs';\r\n```\r\n\r\n# IIFE (Immediately Invoked Function Expression)\r\n```js\r\n\r\n\r\n\r\n// ...\r\nwindow.PathwayBundle.Pathway\r\n```\r\n\r\n# CommonJS (CJS) Module\r\n\r\n```js\r\n// Importing as a CommonJS module (Node.js)\r\nconst { Pathway } = require('./pathway.cjs.js');\r\n```\r\n\r\n## API \r\n\r\n### instance.pathwayWeight \r\n - `type`: `number` \r\n - `desc`: The weight of this instance in the pathfinder system, higher values will try to make the pathfinder generate paths that do not include this instance. A weight of `0` is converying that is is passable. A weight of `-1` means it is impassable. Weights are optional! \r\n\r\n### Pathway.to(pInstance, pDestination, pOptions) \r\n - `pInstance`: The instance to move. `object`\r\n - `pDestination.x`: The xCoordinate to move to `integer` \r\n - `pDestination.y`: The yCoordinate to move to `integer` \r\n - `pOptions.diagonal`: Whether or not the pathfinder allows diagonal moves `boolean` \r\n - `pOptions.mode`: How this instance will move. `collision` for moving with collisions in mind (movePos). `position` for moving with no collisions in mind (setPos). `string` \r\n - `pOptions.pixelsPerSecond`: The speed in pixels this instance moves per second. This setting only works when `pOptions.mode` is set to `position`.`number` \r\n - `pOptions.exclude`: An array of diobs that will be excluded when calculating the path `array` \r\n - `pOptions.minDistance`: The minimum distance this pathway system will use to calculate if you have reached the (next) node. `number` \r\n - `pOptions.maxStuckCounter`: The maximum amount of ticks of pInstance being in the same position as the last tick before its considered stuck. `number` \r\n\t- `pOptions.onPathComplete`: Callback for when pInstance makes it to the `function` \r\n\t- `pOptions.onPathFound`: Callback for when pInstance finds a path. The first parameter is the path that was generated. `function` \r\n\t- `pOptions.onPathStuck`: Callback for when pInstance gets stuck on a path. `function` \r\n\t- `pOptions.onPathNotFound`: Callback for when no path is found. `function` \r\n - `desc`: Moves `pInstance` to the provided coordinates by walking along a generated path free of obstacles.\r\n\r\n### Pathway.end(pInstance) \r\n - `pInstance`: The instance to end the pathfinding on.\r\n - `desc`: Cancels the current path if there is one and stops this instance from moving \r\n \r\n### Pathway.setTileSize(pTileSize)\r\n - `pTileSize`: The size of the tileset. `number` | `object` `pTileSize.width` and `pTileSize.height` when using an object. \r\n - `desc`: Sets the tile size internally for this pathway system to reference. This is how pathway will determine node positions.\r\n\r\nThis module expects the `VYLO` variable to be exposed globally.",
+ "content": "# Pathway Module\r\n\r\nThe Pathway module smoothly integrates pathfinding into the Vylocity Game Engine, allowing map instances to navigate environments more efficiently.\r\n\r\nUses [easystar](https://github.com/prettymuchbryce/easystarjs) under the hood.\r\n\r\n## Installation\r\n\r\n### ES Module\r\n\r\n```js\r\nimport { Pathway } from './pathway.mjs';\r\n```\r\n\r\n### IIFE (Immediately Invoked Function Expression)\r\n\r\n```js\r\n;\r\n// ...\r\nwindow.PathwayBundle.Pathway;\r\n```\r\n\r\n### CommonJS (CJS) Module\r\n\r\n```js\r\nconst { Pathway } = require('./pathway.cjs.js');\r\n```\r\n\r\n## API\r\n\r\n### MapInstance Properties\r\n\r\n#### `pathwayWeight`\r\n\r\n- **Type**: `number`\r\n- **Description**: Represents the importance of an element in pathfinding. Higher values indicate that paths should avoid this element. A weight of `0` means it's easy to traverse, while `-1` indicates an impassable obstacle. This property is optional.\r\n\r\n### Methods\r\n\r\n#### `Pathway.to(pInstance, pDestination, pOptions)`\r\n\r\n- **Parameters**:\r\n - `pInstance`: The moving element.\r\n - `pDestination.x`: The destination's x-coordinate.\r\n - `pDestination.y`: The destination's y-coordinate.\r\n - `pOptions.diagonal`: Whether diagonal movement is allowed.\r\n - `pOptions.mode`: Movement style (`collision` considers obstacles, `position` ignores obstacles).\r\n - `pOptions.pixelsPerSecond`: Speed of movement in pixels per second (applies only in `position` mode).\r\n - `pOptions.exclude`: An array of obstacles to avoid when planning the path.\r\n - `pOptions.minDistance`: Minimum distance to determine node proximity.\r\n - `pOptions.maxStuckCounter`: Maximum consecutive ticks without movement before considering the instance stuck.\r\n - `pOptions.onPathComplete`: Callback executed when the element reaches its destination.\r\n - `pOptions.onPathFound`: Callback executed when a viable path is found.\r\n - `pOptions.onPathStuck`: Callback executed when an element gets stuck on its path.\r\n - `pOptions.onPathNotFound`: Callback executed when no path is found.\r\n- **Description**: Guides an element to a destination along a clear path, avoiding obstacles as necessary.\r\n\r\n#### `Pathway.end(pInstance)`\r\n\r\n- **Parameters**:\r\n - `pInstance`: The element to stop pathfinding for.\r\n- **Description**: Halts the current path and stops the element's movement.\r\n\r\n#### `Pathway.setTileSize(pTileSize)`\r\n\r\n- **Parameters**:\r\n - `pTileSize`: The dimensions of the tileset.\r\n- **Description**: Sets the size of tiles for the pathway system to reference.\r\n\r\n### Global Dependency\r\n\r\nPathway relies on the `VYLO` variable being globally accessible.\r\n",
"longname": "C:\\Users\\jared\\Documents\\Github\\Pathway\\README.md",
"name": "./README.md",
"static": true,
diff --git a/docs/source.html b/docs/source.html
index b191531..377a4d0 100644
--- a/docs/source.html
+++ b/docs/source.html
@@ -55,9 +55,9 @@
Pathway
PathwaySingleton |
- |
- 31169 byte |
+ 31212 byte |
772 |
- 2024-04-04 22:50:57 (UTC) |
+ 2024-04-05 14:03:11 (UTC) |
diff --git a/src/pathway.mjs b/src/pathway.mjs
index 64547e4..6add158 100644
--- a/src/pathway.mjs
+++ b/src/pathway.mjs
@@ -134,7 +134,7 @@ class PathwaySingleton {
*/
to(pInstance, pDestination, pOptions) {
if (typeof(pInstance) === 'object') {
- // If this instance is not on a mapname.
+ // If this instance is not on a map.
if (!pInstance.mapName) {
this.logger.prefix('Pathway-Module').error('Cannot generate a path. pInstance is not on a map.');
return;
@@ -192,7 +192,7 @@ class PathwaySingleton {
}
/**
- * An exclusion list of tiles.
+ * An exclusion list of instances and tiles.
* @type {Array}
*/
let excludeList = [];
@@ -245,13 +245,13 @@ class PathwaySingleton {
instanceData.events.onPathStuck = pOptions.onPathStuck;
}
- // Copy the contents of the exclude array to the exclude list we manage.
+ // Copy the contents of the exclude array to the exclude array we manage.
if (Array.isArray(pOptions.exclude)) {
excludeList.push(...pOptions.exclude);
}
}
- // We add the instance to the exclude list so that it is excluded.
+ // We add the instance to the exclude array so that it is excluded.
if (!excludeList.includes(pInstance)) {
excludeList.push(pInstance);
}
@@ -281,11 +281,12 @@ class PathwaySingleton {
// Get the end nodes position so we can get the destinationTile
const endNodeX = Utils.clamp(Utils.clamp(pDestination.x, 0, mapSize.x) * this.tileSize.width + this.tileSize.width / 2, 0, mapSize.xPos - this.tileSize.width);
const endNodeY = Utils.clamp(Utils.clamp(pDestination.y, 0, mapSize.y) * this.tileSize.height + this.tileSize.height / 2, 0, mapSize.yPos - this.tileSize.height);
- // Get the end time tile
+ // Get the end tile
const destinationTile = VYLO.Map.getLocByPos(endNodeX, endNodeY, pInstance.mapName);
// Make sure these have resolved to actual tiles.
if (originTile && destinationTile) {
+ // Check if the origin and end tile are accessible
if (this.isTileAccessible(originTile, excludeList) && this.isTileAccessible(destinationTile, excludeList)) {
// Get the start node from the originTile
let startNode = this.tileToNode(originTile);
@@ -297,12 +298,11 @@ class PathwaySingleton {
this.getPath(pInstance, { x: startNode.x, y: startNode.y }, { x: endNode.x, y: endNode.y });
// If the origin tile or end tile is not accessible to be walked on then return no path found.
} else {
- // So fire the path not found event.
+ // Fire the path not found event.
if (typeof(instanceData.events.onPathNotFound) === 'function') {
instanceData.events.onPathNotFound();
}
this.end(pInstance);
- return;
}
} else {
this.logger.prefix('Pathway-Module').error('Origin tile or destination tile cannot be found.');
@@ -365,7 +365,7 @@ class PathwaySingleton {
instanceData.stuckCounter = 0;
// Reset the max stuck counter
instanceData.maxStuckCounter = PathwaySingleton.MAX_STUCK_COUNTER;
- // Empty path(s) array
+ // Empty path array
instanceData.path.length = 0;
// Reset it to not being moved.
instanceData.moving = false;