@@ -17,7 +17,7 @@ audit: | |||||
lint-server: | lint-server: | ||||
stage: test | stage: test | ||||
script: | script: | ||||
- npm install eslint eslint-plugin-prettier prettier babel-eslint | |||||
- npm install eslint@7.32.0 eslint-plugin-prettier prettier babel-eslint eslint-plugin-requirejs | |||||
- cd src/server | - cd src/server | ||||
- ../../node_modules/.bin/eslint . | - ../../node_modules/.bin/eslint . | ||||
only: | only: | ||||
@@ -27,7 +27,7 @@ lint-server: | |||||
lint-client: | lint-client: | ||||
stage: test | stage: test | ||||
script: | script: | ||||
- npm install eslint eslint-plugin-prettier prettier babel-eslint eslint-plugin-requirejs | |||||
- npm install eslint@7.32.0 eslint-plugin-prettier prettier babel-eslint eslint-plugin-requirejs | |||||
- cd src/client | - cd src/client | ||||
- ../../node_modules/.bin/eslint . | - ../../node_modules/.bin/eslint . | ||||
only: | only: | ||||
@@ -10,6 +10,8 @@ Built with NodeJS, JS, HTML and CSS. | |||||
### Installation and Usage | ### Installation and Usage | ||||
**IMPORTANT**: The sqlite3 package has been removed for the time being due to multiple `npm audit` issues arrising. While installing this module will not cause you any security issues (due to the nature of the vulnerabilities and how Isleward actually uses the module), we have removed it to make the build process simpler for us. As a result of this, after performing an `npm install` in the `server` folder, please also run `npm i sqlite3`. | |||||
* [Windows](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(windows)) | * [Windows](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(windows)) | ||||
* [Linux](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(linux)) | * [Linux](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(linux)) | ||||
* [MacOS](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(macos)) | * [MacOS](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(macos)) | ||||
@@ -1,50 +1,15 @@ | |||||
let components = [ | |||||
'keyboardMover', | |||||
'mouseMover', | |||||
'touchMover', | |||||
'player', | |||||
'pather', | |||||
'attackAnimation', | |||||
'lightningEffect', | |||||
'moveAnimation', | |||||
'bumpAnimation', | |||||
'animation', | |||||
'light', | |||||
'lightPatch', | |||||
'projectile', | |||||
'particles', | |||||
'explosion', | |||||
'spellbook', | |||||
'inventory', | |||||
'stats', | |||||
'chest', | |||||
'effects', | |||||
'quests', | |||||
'events', | |||||
'resourceNode', | |||||
'gatherer', | |||||
'stash', | |||||
'flash', | |||||
'chatter', | |||||
'dialogue', | |||||
'trade', | |||||
'reputation', | |||||
'serverActions', | |||||
'social', | |||||
'passives', | |||||
'sound', | |||||
'whirlwind', | |||||
'fadeInOut' | |||||
].map(function (c) { | |||||
return 'js/components/' + c; | |||||
}); | |||||
define([ | define([ | ||||
...components, | |||||
'../system/events' | |||||
], function () { | |||||
const events = arguments[arguments.length - 1]; | |||||
'js/system/events', | |||||
'js/system/globals' | |||||
], function ( | |||||
events, | |||||
globals | |||||
) { | |||||
//Store templates here after loading them | |||||
const templates = []; | |||||
const extenders = []; | |||||
//Bound Methods | |||||
const hookEvent = function (e, cb) { | const hookEvent = function (e, cb) { | ||||
if (!this.eventList[e]) | if (!this.eventList[e]) | ||||
this.eventList[e] = []; | this.eventList[e] = []; | ||||
@@ -59,30 +24,54 @@ define([ | |||||
}); | }); | ||||
}; | }; | ||||
let templates = {}; | |||||
//Helpers | |||||
const loadComponent = cpn => { | |||||
return new Promise(res => { | |||||
require([cpn.path], tpl => { | |||||
if (cpn.type) | |||||
templates.push(tpl); | |||||
if (cpn.extends) | |||||
extenders.push(tpl); | |||||
res(); | |||||
}); | |||||
}); | |||||
}; | |||||
[].forEach.call(arguments, function (t, i) { | |||||
//Don't do this for the events module | |||||
if (i === arguments[2].length - 1) | |||||
return; | |||||
//Init Methods | |||||
const loadComponents = paths => { | |||||
return Promise.all( | |||||
paths.map(p => loadComponent(p)) | |||||
); | |||||
}; | |||||
const buildComponents = () => { | |||||
templates.forEach(t => { | |||||
const extensions = extenders.filter(e => e.extends === t.type); | |||||
t.eventList = {}; | |||||
t.hookEvent = hookEvent; | |||||
t.unhookEvents = unhookEvents; | |||||
extensions.forEach(e => $.extend(true, t, e)); | |||||
templates[t.type] = t; | |||||
}); | |||||
t.eventList = {}; | |||||
t.hookEvent = hookEvent; | |||||
t.unhookEvents = unhookEvents; | |||||
}); | |||||
}; | |||||
//Export | |||||
return { | return { | ||||
init: async function () { | |||||
const paths = globals.clientConfig.clientComponents; | |||||
await loadComponents(paths); | |||||
buildComponents(); | |||||
}, | |||||
getTemplate: function (type) { | getTemplate: function (type) { | ||||
if (type === 'lightpatch') | if (type === 'lightpatch') | ||||
type = 'lightPatch'; | type = 'lightPatch'; | ||||
let template = templates[type] || { | |||||
type: type | |||||
}; | |||||
return template; | |||||
return templates.find(t => t.type === type) || { type: type }; | |||||
} | } | ||||
}; | }; | ||||
}); | }); |
@@ -1,35 +0,0 @@ | |||||
define([ | |||||
'js/sound/sound' | |||||
], function ( | |||||
soundManager | |||||
) { | |||||
return { | |||||
type: 'sound', | |||||
sound: null, | |||||
volume: 0, | |||||
init: function () { | |||||
const { | |||||
sound, volume, music, defaultMusic, | |||||
obj: { zoneId, x, y, width, height, area } | |||||
} = this; | |||||
const config = { | |||||
scope: zoneId, | |||||
file: sound, | |||||
volume, | |||||
x, | |||||
y, | |||||
w: width, | |||||
h: height, | |||||
area, | |||||
music, | |||||
defaultMusic, | |||||
loop: true | |||||
}; | |||||
soundManager.addSound(config); | |||||
} | |||||
}; | |||||
}); |
@@ -10,6 +10,7 @@ define([ | |||||
'js/resources', | 'js/resources', | ||||
'js/sound/sound', | 'js/sound/sound', | ||||
'js/system/globals', | 'js/system/globals', | ||||
'js/components/components', | |||||
'ui/templates/online/online', | 'ui/templates/online/online', | ||||
'ui/templates/tooltips/tooltips' | 'ui/templates/tooltips/tooltips' | ||||
], function ( | ], function ( | ||||
@@ -23,7 +24,8 @@ define([ | |||||
events, | events, | ||||
resources, | resources, | ||||
sound, | sound, | ||||
globals | |||||
globals, | |||||
components | |||||
) { | ) { | ||||
let fnQueueTick = null; | let fnQueueTick = null; | ||||
const getQueueTick = updateMethod => { | const getQueueTick = updateMethod => { | ||||
@@ -69,6 +71,8 @@ define([ | |||||
globals.clientConfig = config; | globals.clientConfig = config; | ||||
await resources.init(); | await resources.init(); | ||||
await components.init(); | |||||
events.emit('onResourcesLoaded'); | events.emit('onResourcesLoaded'); | ||||
this.start(); | this.start(); | ||||
@@ -28,9 +28,10 @@ define([ | |||||
let options = $.extend(true, {}, particleDefaults, config); | let options = $.extend(true, {}, particleDefaults, config); | ||||
let emitter = new PIXI.particles.Emitter(this.r.layers.particles, ['images/particles.png'], options); | |||||
let emitter = new PIXI.particles.Emitter(this.stage, ['images/particles.png'], options); | |||||
emitter.obj = obj; | emitter.obj = obj; | ||||
emitter.emit = true; | emitter.emit = true; | ||||
emitter.particleEngine = this; | |||||
this.emitters.push(emitter); | this.emitters.push(emitter); | ||||
@@ -21,11 +21,15 @@ define([ | |||||
globals, | globals, | ||||
renderLoginBackground | renderLoginBackground | ||||
) { | ) { | ||||
let mRandom = Math.random.bind(Math); | |||||
const mRandom = Math.random.bind(Math); | |||||
const particleLayers = ['particlesUnder', 'particles']; | |||||
const particleEngines = {}; | |||||
return { | return { | ||||
stage: null, | stage: null, | ||||
layers: { | layers: { | ||||
particlesUnder: null, | |||||
objects: null, | objects: null, | ||||
mobs: null, | mobs: null, | ||||
characters: null, | characters: null, | ||||
@@ -115,10 +119,15 @@ define([ | |||||
this.textures[t].scaleMode = PIXI.SCALE_MODES.NEAREST; | this.textures[t].scaleMode = PIXI.SCALE_MODES.NEAREST; | ||||
}); | }); | ||||
particles.init({ | |||||
r: this, | |||||
renderer: this.renderer, | |||||
stage: this.layers.particles | |||||
particleLayers.forEach(p => { | |||||
const engine = $.extend({}, particles); | |||||
engine.init({ | |||||
r: this, | |||||
renderer: this.renderer, | |||||
stage: this.layers[p] | |||||
}); | |||||
particleEngines[p] = engine; | |||||
}); | }); | ||||
this.buildSpritesTexture(); | this.buildSpritesTexture(); | ||||
@@ -769,11 +778,16 @@ define([ | |||||
}, | }, | ||||
buildEmitter: function (config) { | buildEmitter: function (config) { | ||||
return particles.buildEmitter(config); | |||||
const { layerName = 'particles' } = config; | |||||
const particleEngine = particleEngines[layerName]; | |||||
return particleEngine.buildEmitter(config); | |||||
}, | }, | ||||
destroyEmitter: function (emitter) { | destroyEmitter: function (emitter) { | ||||
particles.destroyEmitter(emitter); | |||||
const particleEngine = emitter.particleEngine; | |||||
particleEngine.destroyEmitter(emitter); | |||||
}, | }, | ||||
setSprite: function (obj) { | setSprite: function (obj) { | ||||
@@ -887,7 +901,8 @@ define([ | |||||
return; | return; | ||||
effects.render(); | effects.render(); | ||||
particles.update(); | |||||
particleLayers.forEach(p => particleEngines[p].update()); | |||||
this.renderer.render(this.stage); | this.renderer.render(this.stage); | ||||
} | } | ||||
@@ -32,6 +32,7 @@ define([ | |||||
init: function () { | init: function () { | ||||
events.on('onToggleAudio', this.onToggleAudio.bind(this)); | events.on('onToggleAudio', this.onToggleAudio.bind(this)); | ||||
events.on('onPlaySound', this.playSound.bind(this)); | events.on('onPlaySound', this.playSound.bind(this)); | ||||
events.on('onPlaySoundAtPosition', this.onPlaySoundAtPosition.bind(this)); | |||||
events.on('onManipulateVolume', this.onManipulateVolume.bind(this)); | events.on('onManipulateVolume', this.onManipulateVolume.bind(this)); | ||||
const { clientConfig: { sounds: loadSounds } } = globals; | const { clientConfig: { sounds: loadSounds } } = globals; | ||||
@@ -67,6 +68,24 @@ define([ | |||||
} | } | ||||
}, | }, | ||||
onPlaySoundAtPosition: function ({ position: { x, y }, file, volume }) { | |||||
const { player: { x: playerX, y: playerY } } = window; | |||||
const dx = Math.abs(x - playerX); | |||||
const dy = Math.abs(y - playerY); | |||||
const distance = Math.max(dx, dy); | |||||
const useVolume = volume * (1 - (Math.pow(distance, 2) / Math.pow(minDistance, 2))); | |||||
//eslint-disable-next-line no-undef, no-unused-vars | |||||
const sound = new Howl({ | |||||
src: [file], | |||||
volume: useVolume, | |||||
loop: false, | |||||
autoplay: true, | |||||
html5: false | |||||
}); | |||||
}, | |||||
playSound: function (soundName) { | playSound: function (soundName) { | ||||
const soundEntry = this.sounds.find(s => s.name === soundName); | const soundEntry = this.sounds.find(s => s.name === soundName); | ||||
if (!soundEntry) | if (!soundEntry) | ||||
@@ -211,6 +230,13 @@ define([ | |||||
addSound: function ( | addSound: function ( | ||||
{ name: soundName, scope, file, volume = 1, x, y, w, h, area, music, defaultMusic, autoLoad, loop } | { name: soundName, scope, file, volume = 1, x, y, w, h, area, music, defaultMusic, autoLoad, loop } | ||||
) { | ) { | ||||
if (this.sounds.some(s => s.file === file)) { | |||||
if (window.player?.x !== undefined) | |||||
this.update(window.player.x, window.player.y); | |||||
return; | |||||
} | |||||
if (!area && w) { | if (!area && w) { | ||||
area = [ | area = [ | ||||
[x, y], | [x, y], | ||||
@@ -243,6 +269,11 @@ define([ | |||||
}; | }; | ||||
this.sounds.push(soundEntry); | this.sounds.push(soundEntry); | ||||
if (window.player?.x !== undefined) | |||||
this.update(window.player.x, window.player.y); | |||||
return soundEntry; | |||||
}, | }, | ||||
loadSound: function (file, loop = false, autoplay = false, volume = 1) { | loadSound: function (file, loop = false, autoplay = false, volume = 1) { | ||||
@@ -290,6 +321,10 @@ define([ | |||||
const { player: { x, y } } = window; | const { player: { x, y } } = window; | ||||
this.update(x, y); | this.update(x, y); | ||||
}, | |||||
destroySoundEntry: function (soundEntry) { | |||||
this.sounds.spliceWhere(s => s === soundEntry); | |||||
} | } | ||||
}; | }; | ||||
}); | }); |
@@ -1,11 +1,12 @@ | |||||
{ | { | ||||
"name": "isleward_client", | "name": "isleward_client", | ||||
"version": "0.10.2", | |||||
"version": "0.10.4", | |||||
"description": "isleward", | "description": "isleward", | ||||
"dependencies": {}, | |||||
"dependencies": { | |||||
}, | |||||
"devDependencies": { | "devDependencies": { | ||||
"babel-eslint": "^10.1.0", | |||||
"eslint": "^7.32.0", | "eslint": "^7.32.0", | ||||
"babel-eslint": "^10.1.0", | |||||
"eslint-plugin-prettier": "^3.4.0", | "eslint-plugin-prettier": "^3.4.0", | ||||
"eslint-plugin-requirejs": "^4.0.1" | "eslint-plugin-requirejs": "^4.0.1" | ||||
} | } | ||||
@@ -11,11 +11,11 @@ | |||||
</div> | </div> | ||||
<div class="message"></div> | <div class="message"></div> | ||||
</div> | </div> | ||||
<div class="news" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.2-Release-Notes">[ Latest Release Notes ]</div> | |||||
<div class="news" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.4-Release-Notes">[ Latest Release Notes ]</div> | |||||
<div class="extra"> | <div class="extra"> | ||||
<div class="el btn btnPatreon monetization" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div> | <div class="el btn btnPatreon monetization" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div> | ||||
<div class="el btn btnPaypal monetization" location="https://www.paypal.com/donate?hosted_button_id=NEQAV3NG9PWXA">Donate on Paypal</div> | <div class="el btn btnPaypal monetization" location="https://www.paypal.com/donate?hosted_button_id=NEQAV3NG9PWXA">Donate on Paypal</div> | ||||
<div class="el btn btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div> | <div class="el btn btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div> | ||||
</div> | </div> | ||||
<div class="version" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.2-Release-Notes">v0.10.2</div> | |||||
<div class="version" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.4-Release-Notes">v0.10.4</div> | |||||
</div> | </div> |
@@ -1,4 +1,4 @@ | |||||
{ | |||||
{ | |||||
"root": true, | "root": true, | ||||
"parser": "babel-eslint", | "parser": "babel-eslint", | ||||
@@ -0,0 +1,3 @@ | |||||
{ | |||||
"extends": "../../client/.eslintrc" | |||||
} |
@@ -0,0 +1,50 @@ | |||||
define([ | |||||
'js/sound/sound' | |||||
], function ( | |||||
soundManager | |||||
) { | |||||
return { | |||||
type: 'sound', | |||||
sound: null, | |||||
volume: 0, | |||||
soundEntry: null, | |||||
init: function () { | |||||
const { | |||||
sound, volume, music, defaultMusic, loop = true, | |||||
obj: { zoneId, x, y, width, height, area } | |||||
} = this; | |||||
const config = { | |||||
scope: zoneId, | |||||
file: sound, | |||||
volume, | |||||
x, | |||||
y, | |||||
w: width, | |||||
h: height, | |||||
area, | |||||
music, | |||||
defaultMusic, | |||||
loop | |||||
}; | |||||
this.soundEntry = soundManager.addSound(config); | |||||
}, | |||||
extend: function (bpt) { | |||||
Object.assign(this, bpt); | |||||
Object.assign(this.soundEntry, bpt); | |||||
}, | |||||
destroy: function () { | |||||
if (this.soundEntry?.sound) | |||||
this.soundEntry?.sound.stop(); | |||||
soundManager.destroySoundEntry(this.soundEntry); | |||||
} | |||||
}; | |||||
}); |
@@ -20,6 +20,11 @@ module.exports = { | |||||
this.destroyKey = blueprint.destroyKey; | this.destroyKey = blueprint.destroyKey; | ||||
this.autoClose = blueprint.autoClose; | this.autoClose = blueprint.autoClose; | ||||
if (blueprint.openSprite) | |||||
this.openSprite = blueprint.openSprite; | |||||
if (blueprint.closedSprite) | |||||
this.closedSprite = blueprint.closedSprite; | |||||
if (this.closed) { | if (this.closed) { | ||||
this.obj.instance.physics.setCollision(this.obj.x, this.obj.y, true); | this.obj.instance.physics.setCollision(this.obj.x, this.obj.y, true); | ||||
this.obj.instance.objects.notifyCollisionChange(this.obj.x, this.obj.y, true); | this.obj.instance.objects.notifyCollisionChange(this.obj.x, this.obj.y, true); | ||||
@@ -1,6 +1,7 @@ | |||||
const imageSize = require('image-size'); | const imageSize = require('image-size'); | ||||
const events = require('../misc/events'); | const events = require('../misc/events'); | ||||
const fileLister = require('../misc/fileLister'); | |||||
const tos = require('./tos'); | const tos = require('./tos'); | ||||
const config = { | const config = { | ||||
@@ -197,6 +198,7 @@ const config = { | |||||
player: [], | player: [], | ||||
npc: [] | npc: [] | ||||
}, | }, | ||||
clientComponents: [], | |||||
sounds: { | sounds: { | ||||
ui: [] | ui: [] | ||||
}, | }, | ||||
@@ -207,6 +209,18 @@ module.exports = { | |||||
config, | config, | ||||
init: async function () { | init: async function () { | ||||
fileLister.getFolder('./clientComponents').forEach(f => { | |||||
if (!f.endsWith('.js')) return; | |||||
const type = f.split('.')[0]; | |||||
const path = 'server/clientComponents/' + f; | |||||
config.clientComponents.push({ | |||||
type, | |||||
path | |||||
}); | |||||
}); | |||||
events.emit('onBeforeGetClientConfig', config); | events.emit('onBeforeGetClientConfig', config); | ||||
//Deprecated | //Deprecated | ||||
@@ -1,5 +1,5 @@ | |||||
module.exports = { | module.exports = { | ||||
version: '0.10.2', | |||||
version: '0.10.4', | |||||
port: 4000, | port: 4000, | ||||
startupMessage: 'Server: ready', | startupMessage: 'Server: ready', | ||||
defaultZone: 'fjolarok', | defaultZone: 'fjolarok', | ||||
@@ -1,23 +1,22 @@ | |||||
{ | { | ||||
"name": "isleward_server", | "name": "isleward_server", | ||||
"version": "0.10.2", | |||||
"version": "0.10.4", | |||||
"description": "isleward", | "description": "isleward", | ||||
"dependencies": { | "dependencies": { | ||||
"axios": "^0.21.1", | |||||
"axios": "^0.22.0", | |||||
"bcrypt-nodejs": "0.0.3", | "bcrypt-nodejs": "0.0.3", | ||||
"compression": "^1.7.4", | "compression": "^1.7.4", | ||||
"express": "^4.17.1", | "express": "^4.17.1", | ||||
"express-minify": "^1.0.0", | "express-minify": "^1.0.0", | ||||
"image-size": "^1.0.0", | "image-size": "^1.0.0", | ||||
"rethinkdbdash": "^2.3.31", | "rethinkdbdash": "^2.3.31", | ||||
"socket.io": "^4.1.3", | |||||
"socket.io": "^4.2.0", | |||||
"universal-analytics": "^0.4.23" | "universal-analytics": "^0.4.23" | ||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"babel-eslint": "^10.1.0", | "babel-eslint": "^10.1.0", | ||||
"eslint": "^7.32.0", | "eslint": "^7.32.0", | ||||
"eslint-plugin-prettier": "^3.4.0", | |||||
"eslint-plugin-requirejs": "^4.0.1", | |||||
"sqlite3": "^4.2.0" | |||||
"eslint-plugin-prettier": "^4.0.0", | |||||
"eslint-plugin-requirejs": "^4.0.1" | |||||
} | } | ||||
} | } |
@@ -21,6 +21,7 @@ const onRequest = (socket, msg, callback) => { | |||||
return; | return; | ||||
delete msg.threadModule; | delete msg.threadModule; | ||||
delete msg.module; | |||||
cons.route(socket, msg); | cons.route(socket, msg); | ||||
} else if (msg.threadModule) { | } else if (msg.threadModule) { | ||||
@@ -28,6 +29,7 @@ const onRequest = (socket, msg, callback) => { | |||||
return; | return; | ||||
delete msg.cpn; | delete msg.cpn; | ||||
delete msg.module; | |||||
cons.route(socket, msg); | cons.route(socket, msg); | ||||
} else { | } else { | ||||
@@ -11,13 +11,17 @@ const appFile = (req, res) => { | |||||
let file = req.params[0]; | let file = req.params[0]; | ||||
file = file.replace('/' + root + '/', ''); | file = file.replace('/' + root + '/', ''); | ||||
const validRequest = ( | const validRequest = ( | ||||
root !== 'server' || | root !== 'server' || | ||||
( | |||||
file.includes('mods/') && | |||||
validModPatterns.some(v => file.includes(v)) | |||||
) | |||||
( | |||||
root === 'server' && | |||||
file.startsWith('clientComponents/') | |||||
) || | |||||
( | |||||
file.includes('mods/') && | |||||
validModPatterns.some(v => file.includes(v)) | |||||
) | |||||
); | ); | ||||
if (!validRequest) | if (!validRequest) | ||||
@@ -259,12 +259,18 @@ module.exports = { | |||||
const layers = [...mapFile.layers.filter(l => l.objects), ...mapFile.layers.filter(l => !l.objects)]; | const layers = [...mapFile.layers.filter(l => l.objects), ...mapFile.layers.filter(l => !l.objects)]; | ||||
//Rooms need to be ahead of exits | //Rooms need to be ahead of exits | ||||
layers.rooms = (layers.rooms || []) | |||||
.sort(function (a, b) { | |||||
if ((a.exit) && (!b.exit)) | |||||
return 1; | |||||
return 0; | |||||
}); | |||||
const layerRooms = layers.find(l => l.name === 'rooms') || {}; | |||||
layerRooms.objects.sort((a, b) => { | |||||
const isExitA = a?.properties?.some(p => p.name === 'exit'); | |||||
const isExitB = b?.properties?.some(p => p.name === 'exit'); | |||||
if (isExitA && !isExitB) | |||||
return 1; | |||||
else if (!isExitA && isExitB) | |||||
return -1; | |||||
return 0; | |||||
}); | |||||
for (let i = 0; i < layers.length; i++) { | for (let i = 0; i < layers.length; i++) { | ||||
let layer = layers[i]; | let layer = layers[i]; | ||||