Resolve "Implement instancing and add features to support tutorial mod" Closes #1915 See merge request Isleward/isleward!588tags/v0.11.0
@@ -60,6 +60,9 @@ define([ | |||
enabled: true, | |||
blacklistedKeys: [], | |||
whitelistedKeys: [], | |||
init: function () { | |||
$(window).on('keydown', this.events.keyboard.keyDown.bind(this)); | |||
$(window).on('keyup', this.events.keyboard.keyUp.bind(this)); | |||
@@ -78,6 +81,22 @@ define([ | |||
require(['plugins/shake.js'], this.onLoadShake.bind(this)); | |||
}, | |||
blacklistKeys: function (list) { | |||
this.blacklistedKeys.push(...list); | |||
}, | |||
unBlacklistKeys: function (list) { | |||
this.blacklistedKeys.spliceWhere(d => list.includes(d)); | |||
}, | |||
whitelistKeys: function (list) { | |||
this.whitelistedKeys.push(...list); | |||
}, | |||
unWhitelistKeys: function (list) { | |||
this.whitelistedKeys.spliceWhere(d => list.includes(d)); | |||
}, | |||
onLoadShake: function (shake) { | |||
let shaker = new shake({ | |||
threshold: 5, | |||
@@ -142,6 +161,7 @@ define([ | |||
events: { | |||
keyboard: { | |||
/* eslint-disable-next-line max-lines-per-function */ | |||
keyDown: function (e) { | |||
if (!this.enabled) | |||
return; | |||
@@ -156,6 +176,18 @@ define([ | |||
if ((e.keyCode === 9) || (e.keyCode === 8) || (e.keyCode === 122)) | |||
e.preventDefault(); | |||
const allowKey = ( | |||
key.length > 1 || | |||
this.whitelistedKeys.includes(key) || | |||
( | |||
!this.blacklistedKeys.includes(key) && | |||
!this.blacklistedKeys.includes('*') | |||
) | |||
); | |||
if (!allowKey) | |||
return; | |||
if (this.keys.has(key)) | |||
this.keys[key] = 2; | |||
else { | |||
@@ -76,6 +76,22 @@ define([ | |||
return inside; | |||
}, | |||
//Helper function to check if a point is inside an area | |||
// This function is optimized to check if the point is outside the rect first | |||
// and if it is not, we do the more expensive isInPolygon check | |||
isInArea: function (x, y, { x: ax, y: ay, width, height, area }) { | |||
//Outside rect | |||
if ( | |||
x < ax || | |||
x >= ax + width || | |||
y < ay || | |||
y >= ay + height | |||
) | |||
return false; | |||
return this.isInPolygon(x, y, area); | |||
}, | |||
distanceToPolygon: function (p, verts) { | |||
return distanceToPolygon.calculate(p, verts); | |||
} | |||
@@ -167,7 +167,7 @@ define([ | |||
events.emit('onGetPlayer', obj); | |||
window.player = obj; | |||
sound.unload(obj.zoneId); | |||
sound.unload(obj.zoneName); | |||
renderer.setPosition({ | |||
x: (obj.x - (renderer.width / (scale * 2))) * scale, | |||
@@ -313,8 +313,13 @@ define([ | |||
return 0; | |||
}); | |||
if (this.zoneId !== null) | |||
events.emit('onRezone', this.zoneId); | |||
if (this.zoneId !== null) { | |||
events.emit('onRezone', { | |||
oldZoneId: this.zoneId, | |||
newZoneId: msg.zoneId | |||
}); | |||
} | |||
this.zoneId = msg.zoneId; | |||
msg.clientObjects.forEach(c => { | |||
@@ -384,60 +389,45 @@ define([ | |||
let foundVisibleLayer = null; | |||
let foundHiddenLayer = null; | |||
const fnTileInArea = physics.isInArea.bind(physics, x, y); | |||
const fnPlayerInArea = physics.isInArea.bind(physics, px, py); | |||
hiddenRooms.forEach(h => { | |||
const { discovered, layer, interior } = h; | |||
const { x: hx, y: hy, width, height, area } = h; | |||
//Is the tile outside the hider | |||
if ( | |||
x < hx || | |||
x >= hx + width || | |||
y < hy || | |||
y >= hy + height | |||
) { | |||
//If the hider is an interior, the tile should be hidden if the player is inside the hider | |||
if (interior) { | |||
if (physics.isInPolygon(px, py, area)) | |||
foundHiddenLayer = layer; | |||
} | |||
return; | |||
} | |||
const playerInHider = fnPlayerInArea(h); | |||
const tileInHider = fnTileInArea(h); | |||
//Is the tile inside the hider | |||
if (!physics.isInPolygon(x, y, area)) | |||
return; | |||
if (playerInHider) { | |||
if (interior && !tileInHider) { | |||
foundHiddenLayer = layer; | |||
if (discovered) { | |||
foundVisibleLayer = layer; | |||
return; | |||
} | |||
} else if (tileInHider && !discovered) { | |||
foundHiddenLayer = layer; | |||
return; | |||
} | |||
//Is the player outside the hider | |||
if ( | |||
px < hx || | |||
px >= hx + width || | |||
py < hy || | |||
py >= hy + height | |||
) { | |||
foundHiddenLayer = layer; | |||
} else if (discovered) { | |||
foundVisibleLayer = layer; | |||
return; | |||
} | |||
//Is the player inside the hider | |||
if (!physics.isInPolygon(px, py, area)) { | |||
foundHiddenLayer = layer; | |||
if (!tileInHider) | |||
return; | |||
} | |||
foundVisibleLayer = layer; | |||
}); | |||
//We compare hider layers to cater for hiders inside hiders | |||
return (foundHiddenLayer > foundVisibleLayer) || (foundHiddenLayer === 0 && foundVisibleLayer === null); | |||
return ( | |||
foundHiddenLayer > foundVisibleLayer || | |||
( | |||
foundHiddenLayer === 0 && | |||
foundVisibleLayer === null | |||
) | |||
); | |||
}, | |||
updateSprites: function () { | |||
@@ -52,7 +52,7 @@ define([ | |||
}, | |||
//Fired when a character rezones | |||
// 'scope' is the new zone name | |||
// 'newScope' is the new zone name | |||
unload: function (newScope) { | |||
const { sounds } = this; | |||
@@ -337,10 +337,10 @@ | |||
} | |||
.mobile .uiEquipment { | |||
width: 100%; | |||
height: 100%; | |||
margin-left: 5px; | |||
margin-top: 5px; | |||
width: calc(100% - 10px); | |||
height: calc(100% - 10px); | |||
margin-left: -5px; | |||
margin-top: -5px; | |||
.content { | |||
flex-direction: column; | |||
@@ -39,7 +39,9 @@ define([ | |||
}, | |||
onGetConnectedPlayer: function (msg) { | |||
let party = this.party; | |||
const { party } = this; | |||
const { player: { serverId: playerId, zoneId: playerZone } } = window; | |||
if (!party) | |||
return; | |||
@@ -47,25 +49,26 @@ define([ | |||
msg = [msg]; | |||
msg.forEach(m => { | |||
const { id: mId, zoneId: mZone } = m; | |||
if (party.indexOf(m.id) === -1) | |||
return; | |||
let zone = m.zone; | |||
if (m.id === window.player.serverId) { | |||
if (mId === playerId) { | |||
party.forEach(p => { | |||
let player = globals.onlineList.find(o => o.id === p); | |||
const mObj = globals.onlineList.find(o => o.id === p); | |||
let el = this.find('.member[memberId="' + p + '"]'); | |||
el.removeClass('differentZone'); | |||
if (player.zone !== zone) | |||
if (mObj.mZone !== mZone) | |||
el.addClass('differentZone'); | |||
}); | |||
} else { | |||
let el = this.find('.member[memberId="' + m.id + '"]'); | |||
el.removeClass('differentZone'); | |||
if (m.zone !== window.player.zone) | |||
if (m.mZone !== playerZone) | |||
el.addClass('differentZone'); | |||
el.find('.txtLevel').html('level: ' + m.level); | |||
@@ -130,7 +133,7 @@ define([ | |||
.attr('memberId', p) | |||
.on('contextmenu', this.showContext.bind(this, playerName, p)); | |||
if (player.zone !== window.player.zone) | |||
if (player.zoneId !== window.player.zoneId) | |||
el.addClass('differentZone'); | |||
//Find stats | |||
@@ -56,7 +56,7 @@ define([ | |||
this.items.push.apply(this.items, blueprint.getItems || []); | |||
events.emit('onGetItems', this.items, rerender); | |||
events.emit('onGetItems', this.items, rerender, blueprint.getItems); | |||
} | |||
}, | |||
@@ -14,11 +14,11 @@ define([ | |||
init: function () { | |||
const { | |||
sound, volume, music, defaultMusic, loop = true, | |||
obj: { zoneId, x, y, width, height, area } | |||
obj: { zoneName, x, y, width, height, area } | |||
} = this; | |||
const config = { | |||
scope: zoneId, | |||
scope: zoneName, | |||
file: sound, | |||
volume, | |||
x, | |||
@@ -80,10 +80,10 @@ module.exports = { | |||
} | |||
if (this.gatheringTtl > 0) { | |||
if ((this.gatheringTtl === this.gatheringTtlMax) && (gathering.width)) { | |||
['x', 'y', 'width', 'height'].forEach(function (p) { | |||
if (this.gatheringTtl === this.gatheringTtlMax && gathering.width > 1) { | |||
['x', 'y', 'width', 'height'].forEach(p => { | |||
this.obj.syncer.set(false, 'gatherer', p, gathering[p]); | |||
}, this); | |||
}); | |||
} | |||
this.gatheringTtl--; | |||
@@ -124,7 +124,7 @@ module.exports = { | |||
return; | |||
} | |||
gatherResult.items.forEach(function (g) { | |||
gatherResult.items.forEach(g => { | |||
if (g.slot) | |||
return; | |||
@@ -150,7 +150,12 @@ module.exports = { | |||
}; | |||
g.worth = ~~(weight * 10); | |||
}, this); | |||
}); | |||
} else { | |||
gatherResult.items.forEach(g => { | |||
if (g.worth === undefined) | |||
g.worth = 1; | |||
}); | |||
} | |||
if (isFish) { | |||
@@ -126,7 +126,6 @@ module.exports = { | |||
let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y)); | |||
if (!distanceFromHome) { | |||
this.goHome = false; | |||
if (!obj.spellbook) { | |||
/* eslint-disable-next-line no-console */ | |||
console.log('MOB HAS NO SPELLBOOK BUT WANTS TO RESET ROTATION'); | |||
@@ -1,5 +1,4 @@ | |||
let classes = require('../config/spirits'); | |||
let serverConfig = require('../config/serverConfig'); | |||
const eventEmitter = require('../misc/events'); | |||
module.exports = { | |||
@@ -31,12 +30,12 @@ module.exports = { | |||
skinId: character.skinId, | |||
name: character.name, | |||
class: character.class, | |||
zoneName: character.zoneName || serverConfig.defaultZone, | |||
x: character.x, | |||
y: character.y, | |||
hidden: character.dead || null, | |||
account: character.account, | |||
instanceId: character.instanceId || null | |||
zoneName: character.zoneName || clientConfig.config.defaultZone, | |||
zoneId: character.zoneId || null | |||
}); | |||
character.components = character.components || []; | |||
@@ -125,7 +124,7 @@ module.exports = { | |||
let self = { | |||
id: obj.id, | |||
zone: obj.zone, | |||
zoneId: obj.zoneId, | |||
name: obj.name, | |||
level: obj.level, | |||
class: obj.class | |||
@@ -215,7 +214,7 @@ module.exports = { | |||
}; | |||
obj.instance.eventEmitter.emit('onBeforePlayerRespawn', obj, spawnPos); | |||
if (!spawnPos.zone) { | |||
if (!spawnPos.zoneName) { | |||
obj.x = spawnPos.x; | |||
obj.y = spawnPos.y; | |||
@@ -251,7 +250,7 @@ module.exports = { | |||
id: obj.serverId, | |||
args: { | |||
obj: simpleObj, | |||
newZone: spawnPos.zone | |||
newZone: spawnPos.zoneName | |||
} | |||
}); | |||
} | |||
@@ -275,7 +275,7 @@ module.exports = { | |||
if (oldQuantity) | |||
item.quantity = oldQuantity; | |||
let worth = ~~(item.worth * targetTrade.markup.buy); | |||
let worth = ~~((item.quantity ?? 1) * item.worth * targetTrade.markup.buy); | |||
this.gold += worth; | |||
@@ -203,7 +203,8 @@ const config = { | |||
sounds: { | |||
ui: [] | |||
}, | |||
tos | |||
tos, | |||
defaultMap: 'fjolarok' | |||
}; | |||
module.exports = { | |||
@@ -497,140 +497,21 @@ | |||
"y":672 | |||
}, | |||
{ | |||
"height":112, | |||
"height":160, | |||
"id":627, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 1, \"msg\": \"You open your eyes and cough. Saltwater burns your throat. You remember the storm, and the crash that left your ship in pieces.<br \/><br \/>You realize you need to find shelter. Use <font class='color-green'>wasd<\/font> or the <ont class='color-green'>arrow keys<\/font> to move.\"}" | |||
"value":"{ \"msg\": \"The Cape of Confusion\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":120, | |||
"x":704, | |||
"y":1096 | |||
}, | |||
{ | |||
"height":32, | |||
"id":628, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 1, \"msg\": \"You take a few steps, still weak from the ordeal. Through the glare of the sun, you see a creature to the north-east.<br \/><br \/>Press <font class='color-green'>v<\/font> to toggle nameplates.\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":48, | |||
"x":840, | |||
"y":1096 | |||
}, | |||
{ | |||
"height":80, | |||
"id":629, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 1, \"msg\": \"You take a few steps, still weak from the ordeal. Through the glare of the sun, you see a creature to the north-east.<br \/><br \/>Press <font class='color-green'>v<\/font> to toggle nameplates.\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":136, | |||
"x":824, | |||
"y":1128 | |||
}, | |||
{ | |||
"height":40, | |||
"id":630, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 1, \"msg\": \"The seagull's eyes are bloodshot and in its beak you see a glinting locket. It stole your family heirloom!<br \/><br \/>Click on it to target it then press <font class='color-green'>space<\/font> to toggle auto-attack. Remember to stand close if you are using melee attacks.\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":8, | |||
"x":888, | |||
"y":1072 | |||
}, | |||
{ | |||
"height":8, | |||
"id":631, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 1, \"msg\": \"You take a few steps, still weak from the ordeal. Through the glare of the sun, you see a creature to the north-east.<br \/><br \/>Press <font class='color-green'>v<\/font> to toggle nameplates.\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":8, | |||
"x":888, | |||
"y":1120 | |||
}, | |||
{ | |||
"height":40, | |||
"id":632, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 2, \"msg\": \"You can loot items by standing on them then open your inventory with <font class='color-green'>i<\/font>.<br \/><br \/>To equip an item, simply right click the item in your inventory.\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":144, | |||
"x":888, | |||
"y":1032 | |||
}, | |||
{ | |||
"height":32, | |||
"id":633, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 2, \"msg\": \"Far to the north, you see a small shack. Civilization!<br \/><br \/>You can read more help by pressing <font class='color-green'>h<\/font>.\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":144, | |||
"x":912, | |||
"y":1000 | |||
}, | |||
{ | |||
"height":56, | |||
"id":634, | |||
"name":"", | |||
"properties":[ | |||
{ | |||
"name":"cpnNotice", | |||
"type":"string", | |||
"value":"{\"maxLevel\": 1, \"msg\": \"The seagull's eyes are bloodshot and in its beak you see a glinting locket. It stole your family heirloom!<br \/><br \/>Click on it to target it then press <font class='color-green'>space<\/font> to toggle auto-attack. Remember to stand close if you are using melee attacks.\"}" | |||
}], | |||
"rotation":0, | |||
"type":"", | |||
"visible":true, | |||
"width":112, | |||
"x":896, | |||
"y":1072 | |||
"width":304, | |||
"x":712, | |||
"y":1048 | |||
}, | |||
{ | |||
"height":0, | |||
@@ -2839,7 +2720,7 @@ | |||
{ | |||
"name":"spawn", | |||
"type":"string", | |||
"value":"[{\"maxLevel\":1,\"x\":100,\"y\":150},{\"maxLevel\":999,\"x\":132,\"y\":82}]" | |||
"value":"[{\"x\":132,\"y\":82}]" | |||
}], | |||
"renderorder":"right-down", | |||
"tiledversion":"1.6.0", | |||
@@ -19,6 +19,6 @@ module.exports = { | |||
name: 'Green Fingers', | |||
type: 'gatherResource', | |||
subType: 'herb', | |||
quantity: [5, 10] | |||
quantity: [2, 5] | |||
}] | |||
}; |
@@ -56,27 +56,7 @@ module.exports = { | |||
}, | |||
'crazed seagull': { | |||
level: 1, | |||
rare: { | |||
count: 0 | |||
}, | |||
regular: { | |||
drops: { | |||
rolls: 1, | |||
noRandom: true, | |||
blueprints: [{ | |||
chance: 100, | |||
maxLevel: 2, | |||
name: 'Family Heirloom', | |||
quality: 1, | |||
slot: 'neck', | |||
type: 'Choker', | |||
noSalvage: true, | |||
stats: ['vit', 'regenMana'] | |||
}] | |||
} | |||
} | |||
attackable: false | |||
}, | |||
seagull: { | |||
level: 2, | |||
@@ -10,7 +10,7 @@ module.exports = { | |||
}, | |||
obtain: function (obj, template) { | |||
let zoneName = template ? template.zoneName : obj.zoneName; | |||
let zoneName = template?.zoneName ?? obj.zoneName; | |||
let zone = mapList.mapList.find(m => m.name === zoneName); | |||
//Zone doesn't exist any more. Probably been renamed | |||
@@ -33,13 +33,18 @@ module.exports = { | |||
zoneTemplate = globalQuests; | |||
let config = extend({}, zoneTemplate); | |||
this.instance.eventEmitter.emit('onBeforeGetQuests', config); | |||
this.instance.eventEmitter.emit('onBeforeGetQuests', { | |||
obj, | |||
config, | |||
zoneName, | |||
template | |||
}); | |||
if (config.infini.length === 0) | |||
return; | |||
//Only check min level of quests when physically in the zone they belong to | |||
if (obj.zoneName === zoneName) { | |||
const minPlayerLevel = ~~(obj.instance.map.zone.level[0] * 0.75); | |||
const minPlayerLevel = ~~(obj.instance.zoneConfig.level[0] * 0.75); | |||
if (obj.stats.values.level < minPlayerLevel) | |||
return; | |||
@@ -8,9 +8,11 @@ module.exports = { | |||
build: function () { | |||
if (!this.need) { | |||
this.need = 2 + ~~(Math.random() * 3); | |||
const { quantity, subType } = this; | |||
this.gatherType = ['herb', 'fish'][~~(Math.random() * 2)]; | |||
this.need = quantity[0] + ~~(Math.random() * (quantity[1] - quantity[0])); | |||
this.gatherType = subType ?? ['herb', 'fish'][~~(Math.random() * 2)]; | |||
if (this.gatherType === 'fish') { | |||
this.name = 'Lure of the Sea'; | |||
@@ -10,7 +10,7 @@ module.exports = { | |||
//If we're not in the correct zone, don't do this check, it'll just crash the server | |||
// since the mob won't be available (most likely) in the zoneFile | |||
if (this.obj.zoneName === this.zoneName) { | |||
let mobTypes = this.obj.instance.spawners.zone.mobs; | |||
let mobTypes = this.obj.instance.zoneConfig.mobs; | |||
if (this.mobName) { | |||
let mobType = mobTypes[this.mobName.toLowerCase()]; | |||
//Maybe the zoneFile changed in the meantime. If so, regenerate | |||
@@ -18,7 +18,7 @@ module.exports = { | |||
//If we're not in the correct zone, don't do this check, it'll just crash the server | |||
// since the mob won't be available (most likely) in the zoneFile | |||
if (this.obj.zoneName === this.zoneName) { | |||
let mobTypes = this.obj.instance.spawners.zone.mobs; | |||
let mobTypes = this.obj.instance.zoneConfig.mobs; | |||
if (this.mobType && this.item) { | |||
//Check if the zoneFile changed | |||
@@ -6,7 +6,7 @@ module.exports = { | |||
return false; | |||
if (!this.xp) { | |||
let level = this.obj.instance.spawners.zone.level; | |||
let level = this.obj.instance.zoneConfig.level; | |||
level = level[0]; | |||
let xp = ~~(level * 22 * this.getXpMultiplier()); | |||
this.xp = xp; | |||
@@ -2,7 +2,6 @@ module.exports = { | |||
version: '0.10.6', | |||
port: 4000, | |||
startupMessage: 'Server: ready', | |||
defaultZone: 'fjolarok', | |||
//Options: | |||
// sqlite | |||
@@ -17,8 +17,8 @@ module.exports = { | |||
this.events.on('onAfterGetZone', this.onAfterGetZone.bind(this)); | |||
}, | |||
onAfterGetZone: function (zone, config) { | |||
if (zone !== 'fjolgard') | |||
onAfterGetZone: function (zoneName, config) { | |||
if (zoneName !== 'fjolgard') | |||
return; | |||
let newRunes = [{ | |||
@@ -88,8 +88,6 @@ module.exports = { | |||
getSimple: function (self, isSave, isTransfer) { | |||
let s = this.simplify(null, self, isSave, isTransfer); | |||
if (this.instance) | |||
s.zoneId = this.instance.zoneId; | |||
if (self && !isSave && this.syncer) { | |||
this.syncer.oSelf.components | |||
@@ -104,8 +104,8 @@ module.exports = { | |||
}); | |||
//If we don't do this, the atlas will try to remove it from the thread | |||
player.zoneName = null; | |||
player.name = null; | |||
delete player.zoneName; | |||
delete player.name; | |||
//A hack to allow us to actually call methods again (like retrieve the player list) | |||
player.dead = false; | |||
@@ -151,7 +151,7 @@ module.exports = { | |||
continue; | |||
result.push({ | |||
zone: p.zone, | |||
zoneName: p.zoneName, | |||
name: p.name, | |||
level: p.level, | |||
class: p.class, | |||
@@ -164,7 +164,7 @@ module.exports = { | |||
forceSaveAll: function () { | |||
this.players | |||
.filter(p => p.zone) | |||
.filter(p => p.zoneName !== undefined) | |||
.forEach(p => { | |||
atlas.performAction(p, { | |||
cpn: 'auth', | |||
@@ -27,7 +27,7 @@ const route = function (socket, msg) { | |||
if (msg.callback) | |||
msg.data.callbackId = atlas.registerCallback(msg.callback); | |||
atlas.send(source.zone, msg); | |||
atlas.send(source.zoneId, msg); | |||
return; | |||
} | |||
@@ -45,7 +45,7 @@ module.exports = { | |||
keysCorrect: function (obj, keys) { | |||
const foundIncorrect = keys.some(({ key, dataType, optional, spec }) => { | |||
if (!obj.hasOwnProperty(key)) { | |||
if (!Object.hasOwnProperty.call(obj, key)) { | |||
if (optional) | |||
return false; | |||
@@ -127,7 +127,7 @@ module.exports = { | |||
return keysCorrect; | |||
}, | |||
isMsgValid: function (msg) { | |||
isMsgValid: function (msg, source) { | |||
let signature; | |||
if (msg.module) { | |||
@@ -152,8 +152,12 @@ module.exports = { | |||
const result = this.signatureCorrect(msg, signature); | |||
if (!result || msg.cpn !== 'player' || msg.method !== 'performAction') | |||
if (!result || msg.cpn !== 'player' || msg.method !== 'performAction') { | |||
if (result && signature.allowWhenIngame === false && source.name !== undefined) | |||
return false; | |||
return result; | |||
} | |||
const signatureThreadMsg = signatures.threadCpnMethods[msg.data.cpn]?.[msg.data.method]; | |||
@@ -49,6 +49,7 @@ const routerConfig = { | |||
auth: { | |||
login: { | |||
callback: true, | |||
allowWhenIngame: false, | |||
data: [ | |||
{ | |||
key: 'username', | |||
@@ -62,6 +63,7 @@ const routerConfig = { | |||
}, | |||
register: { | |||
callback: true, | |||
allowWhenIngame: false, | |||
data: [ | |||
{ | |||
key: 'username', | |||
@@ -75,6 +77,7 @@ const routerConfig = { | |||
}, | |||
deleteCharacter: { | |||
callback: true, | |||
allowWhenIngame: false, | |||
data: [ | |||
{ | |||
key: 'name', | |||
@@ -88,6 +91,7 @@ const routerConfig = { | |||
}, | |||
createCharacter: { | |||
callback: true, | |||
allowWhenIngame: false, | |||
data: [ | |||
{ | |||
key: 'name', | |||
@@ -109,10 +113,12 @@ const routerConfig = { | |||
}, | |||
getCharacterList: { | |||
callback: true, | |||
allowWhenIngame: false, | |||
data: [] | |||
}, | |||
getCharacter: { | |||
callback: true, | |||
allowWhenIngame: false, | |||
data: [ | |||
{ | |||
key: 'name', | |||
@@ -122,6 +128,7 @@ const routerConfig = { | |||
}, | |||
play: { | |||
callback: true, | |||
allowWhenIngame: false, | |||
data: [ | |||
{ | |||
key: 'name', | |||
@@ -16,7 +16,9 @@ const onRequest = (socket, msg, callback) => { | |||
if (!msg.data) | |||
msg.data = {}; | |||
if (!router.isMsgValid(msg)) | |||
const source = cons.players.find(p => p.socket.id === socket.id); | |||
if (!router.isMsgValid(msg, source)) | |||
return; | |||
if (msg.cpn) | |||
@@ -24,8 +26,6 @@ const onRequest = (socket, msg, callback) => { | |||
else if (msg.threadModule) | |||
cons.route(socket, msg); | |||
else { | |||
const source = cons.players.find(p => p.socket.id === socket.id); | |||
msg.socket = socket; | |||
if (source) | |||
@@ -2,7 +2,6 @@ let childProcess = require('child_process'); | |||
let objects = require('../objects/objects'); | |||
let mapList = require('../config/maps/mapList'); | |||
let connections = require('../security/connections'); | |||
let serverConfig = require('../config/serverConfig'); | |||
let events = require('../misc/events'); | |||
const listenersOnZoneIdle = []; | |||
@@ -17,79 +16,120 @@ module.exports = { | |||
this.getMapFiles(); | |||
}, | |||
addObject: function (obj, keepPos, transfer) { | |||
addObject: async function (obj, keepPos, transfer) { | |||
const serverObj = objects.objects.find(o => o.id === obj.id); | |||
if (!serverObj) | |||
return; | |||
events.emit('onBeforePlayerEnterWorld', obj); | |||
let thread = this.getThreadFromName(obj.zoneName); | |||
let thread; | |||
let map = mapList.mapList.find(m => m.name === obj.zoneName); | |||
if (!map) | |||
map = mapList.mapList.find(m => m.name === clientConfig.config.defaultZone); | |||
let instanceId = obj.instanceId; | |||
if ((!thread) || (obj.zoneName !== thread.name)) | |||
instanceId = -1; | |||
thread = this.threads.find(t => t.id === obj.zoneId && t.name === obj.zoneName); | |||
if (!thread) { | |||
thread = this.getThreadFromName(serverConfig.defaultZone); | |||
obj.zoneName = thread.name; | |||
if (map.instanced) { | |||
delete obj.x; | |||
delete obj.y; | |||
thread = this.spawnMap(map); | |||
await new Promise(res => setTimeout(res, 2000)); | |||
} else | |||
thread = this.getThreadFromName(map.name); | |||
} | |||
obj.zone = thread.id; | |||
this.send(obj.zone, { | |||
obj.zoneName = thread.name; | |||
obj.zoneId = thread.id; | |||
serverObj.zoneId = thread.id; | |||
serverObj.zoneName = thread.name; | |||
const simpleObj = obj.getSimple ? obj.getSimple(true, true) : obj; | |||
this.send(obj.zoneId, { | |||
method: 'addObject', | |||
args: { | |||
keepPos: keepPos, | |||
obj: obj.getSimple ? obj.getSimple(true, true) : obj, | |||
instanceId: instanceId, | |||
obj: simpleObj, | |||
transfer: transfer | |||
} | |||
}); | |||
}, | |||
removeObjectFromInstancedZone: async function (thread, obj, callback) { | |||
await new Promise(res => { | |||
const cb = this.registerCallback(res); | |||
thread.worker.send({ | |||
method: 'forceSavePlayer', | |||
args: { | |||
playerName: obj.name, | |||
callbackId: cb | |||
} | |||
}); | |||
}); | |||
thread.worker.kill(); | |||
this.threads.spliceWhere(t => t === thread); | |||
if (callback) | |||
callback(); | |||
}, | |||
removeObject: function (obj, skipLocal, callback) { | |||
if (!skipLocal) | |||
objects.removeObject(obj); | |||
let thread = this.getThreadFromName(obj.zoneName); | |||
if (!thread) | |||
let thread = this.findObjectThread(obj); | |||
if (!thread) | |||
return; | |||
if (thread.instanced) { | |||
this.removeObjectFromInstancedZone(thread, obj, callback); | |||
return; | |||
} | |||
let callbackId = null; | |||
if (callback) | |||
callbackId = this.registerCallback(callback); | |||
obj.zone = thread.id; | |||
this.send(obj.zone, { | |||
this.send(obj.zoneId, { | |||
method: 'removeObject', | |||
args: { | |||
obj: obj.getSimple(true), | |||
instanceId: obj.instanceId, | |||
callbackId: callbackId | |||
} | |||
}); | |||
}, | |||
updateObject: function (obj, msgObj) { | |||
this.send(obj.zone, { | |||
this.send(obj.zoneId, { | |||
method: 'updateObject', | |||
args: { | |||
id: obj.id, | |||
instanceId: obj.instanceId, | |||
obj: msgObj | |||
} | |||
}); | |||
}, | |||
queueAction: function (obj, action) { | |||
this.send(obj.zone, { | |||
this.send(obj.zoneId, { | |||
method: 'queueAction', | |||
args: { | |||
id: obj.id, | |||
instanceId: obj.instanceId, | |||
action: action | |||
} | |||
}); | |||
}, | |||
performAction: function (obj, action) { | |||
this.send(obj.zone, { | |||
this.send(obj.zoneId, { | |||
method: 'performAction', | |||
args: { | |||
id: obj.id, | |||
instanceId: obj.instanceId, | |||
action: action | |||
} | |||
}); | |||
@@ -111,29 +151,34 @@ module.exports = { | |||
callback.callback(msg.msg.result); | |||
}, | |||
send: function (zone, msg) { | |||
let thread = this.getThreadFromId(zone); | |||
send: function (threadId, msg) { | |||
const thread = this.threads.find(t => t.id === threadId); | |||
if (thread) | |||
thread.worker.send(msg); | |||
}, | |||
getThreadFromId: function (id) { | |||
return this.threads.find(t => t.id === id); | |||
findObjectThread: function ({ zoneId }) { | |||
return this.threads.find(t => t.id === zoneId); | |||
}, | |||
getThreadFromName: function (name) { | |||
return this.threads.find(t => t.name === name); | |||
}, | |||
getMapFiles: function () { | |||
mapList.mapList.filter(m => !m.disabled).forEach(m => this.spawnMap(m)); | |||
mapList.mapList | |||
.filter(m => !m.disabled && !m.instanced) | |||
.forEach(m => this.spawnMap(m)); | |||
}, | |||
spawnMap: function (map) { | |||
const worker = childProcess.fork('./world/worker', [map.name]); | |||
spawnMap: function ({ name, path, instanced }) { | |||
const worker = childProcess.fork('./world/worker', [name]); | |||
const id = instanced ? _.getGuid() : name; | |||
const thread = { | |||
id: this.nextId++, | |||
name: map.name, | |||
path: map.path, | |||
id, | |||
name, | |||
instanced, | |||
path, | |||
worker | |||
}; | |||
@@ -143,6 +188,8 @@ module.exports = { | |||
}); | |||
this.threads.push(thread); | |||
return thread; | |||
}, | |||
onMessage: function (thread, message) { | |||
if (message.module) | |||
@@ -167,9 +214,9 @@ module.exports = { | |||
thread.worker.send({ | |||
method: 'init', | |||
args: { | |||
name: thread.name, | |||
path: thread.path, | |||
zoneId: thread.id | |||
zoneName: thread.name, | |||
zoneId: thread.id, | |||
path: thread.path | |||
} | |||
}); | |||
}, | |||
@@ -209,31 +256,33 @@ module.exports = { | |||
}); | |||
}, | |||
rezone: function (thread, message) { | |||
rezone: async function (thread, message) { | |||
const { args: { obj, newZone, keepPos = true } } = message; | |||
//When messages are sent from map threads, they have an id (id of the object in the map thread) | |||
// as well as a serverId (id of the object in the main thread) | |||
const serverId = obj.serverId; | |||
obj.id = serverId; | |||
obj.destroyed = false; | |||
obj.zoneName = newZone; | |||
obj.id = obj.serverId; | |||
let serverObj = objects.objects.find(o => o.id === obj.id); | |||
serverObj.zoneName = obj.zoneName; | |||
let newThread = this.getThreadFromName(obj.zoneName); | |||
const serverObj = objects.objects.find(o => o.id === obj.id); | |||
const mapExists = mapList.mapList.some(m => m.name === newZone); | |||
if (!newThread) { | |||
newThread = this.getThreadFromName(serverConfig.defaultZone); | |||
obj.zoneName = newThread.name; | |||
serverObj.zoneName = newThread.name; | |||
if (mapExists) { | |||
serverObj.zoneName = newZone; | |||
obj.zoneName = newZone; | |||
} else { | |||
obj.zoneName = clientConfig.config.defaultZone; | |||
serverObj.zoneName = clientConfig.config.defaultZone; | |||
} | |||
serverObj.zone = newThread.id; | |||
obj.zone = newThread.id; | |||
delete serverObj.zoneId; | |||
delete obj.zoneId; | |||
serverObj.player.broadcastSelf(); | |||
const isRezone = true; | |||
this.addObject(obj, keepPos, isRezone); | |||
await this.addObject(obj, keepPos, isRezone); | |||
}, | |||
onZoneIdle: function (thread) { | |||
@@ -7,7 +7,7 @@ module.exports = { | |||
load: function (instance, objToAdd, callback) { | |||
this.instance = instance; | |||
this.ent = instance.zone.name + '-' + objToAdd.components.find(c => c.type === 'auth').username; | |||
this.ent = instance.mapName + '-' + objToAdd.components.find(c => c.type === 'auth').username; | |||
io.get({ | |||
ent: this.ent, | |||
@@ -27,7 +27,10 @@ module.exports = { | |||
lastTime: 0, | |||
init: function (args) { | |||
this.zoneId = args.zoneId; | |||
const { zoneId, zoneName } = args; | |||
this.zoneName = zoneName; | |||
this.zoneId = zoneId; | |||
spellCallbacks.init(); | |||
herbs.init(); | |||
@@ -37,15 +40,16 @@ module.exports = { | |||
objects, | |||
syncer, | |||
physics, | |||
zoneId: this.zoneId, | |||
zoneId, | |||
zoneName, | |||
spawners, | |||
questBuilder, | |||
events, | |||
zone: map.zone, | |||
map, | |||
scheduler, | |||
eventEmitter, | |||
resourceSpawner | |||
resourceSpawner, | |||
zoneConfig: map.zoneConfig | |||
}; | |||
this.instances.push(fakeInstance); | |||
@@ -68,18 +68,18 @@ module.exports = { | |||
hiddenWalls: null, | |||
hiddenTiles: null, | |||
zone: null, | |||
zoneConfig: null, | |||
init: function (args) { | |||
this.name = args.name; | |||
this.path = args.path; | |||
init: function ({ zoneName, path }) { | |||
this.name = zoneName; | |||
this.path = path; | |||
try { | |||
this.zone = require('../' + this.path + '/' + this.name + '/zone'); | |||
this.zoneConfig = require('../' + this.path + '/' + this.name + '/zone'); | |||
} catch (e) { | |||
this.zone = globalZone; | |||
this.zoneConfig = globalZone; | |||
} | |||
events.emit('onAfterGetZone', this.name, this.zone); | |||
events.emit('onAfterGetZone', this.name, this.zoneConfig); | |||
let chats = null; | |||
try { | |||
@@ -87,10 +87,10 @@ module.exports = { | |||
} catch (e) {} | |||
if (chats) { | |||
if (this.zone.chats) | |||
extend(this.zone.chats, chats); | |||
if (this.zoneConfig.chats) | |||
extend(this.zoneConfig.chats, chats); | |||
else | |||
this.zone.chats = chats; | |||
this.zoneConfig.chats = chats; | |||
} | |||
let dialogues = null; | |||
@@ -99,11 +99,11 @@ module.exports = { | |||
} catch (e) {} | |||
events.emit('onBeforeGetDialogue', this.name, dialogues); | |||
if (dialogues) | |||
this.zone.dialogues = dialogues; | |||
this.zoneConfig.dialogues = dialogues; | |||
this.zone = extend({}, globalZone, this.zone); | |||
this.zoneConfig = extend({}, globalZone, this.zoneConfig); | |||
let resources = this.zone.resources || {}; | |||
let resources = this.zoneConfig.resources || {}; | |||
for (let r in resources) | |||
resourceSpawner.register(r, resources[r]); | |||
@@ -456,11 +456,11 @@ module.exports = { | |||
if (objZoneName !== name) | |||
blueprint.objZoneName = objZoneName; | |||
if (this.zone) { | |||
if ((this.zone.objects) && (this.zone.objects[objZoneName.toLowerCase()])) | |||
extend(blueprint, this.zone.objects[objZoneName.toLowerCase()]); | |||
else if ((this.zone.objects) && (this.zone.mobs[objZoneName.toLowerCase()])) | |||
extend(blueprint, this.zone.mobs[objZoneName.toLowerCase()]); | |||
if (this.zoneConfig) { | |||
if ((this.zoneConfig.objects) && (this.zoneConfig.objects[objZoneName.toLowerCase()])) | |||
extend(blueprint, this.zoneConfig.objects[objZoneName.toLowerCase()]); | |||
else if ((this.zoneConfig.objects) && (this.zoneConfig.mobs[objZoneName.toLowerCase()])) | |||
extend(blueprint, this.zoneConfig.mobs[objZoneName.toLowerCase()]); | |||
} | |||
if (blueprint.blocking) | |||
@@ -76,15 +76,15 @@ module.exports = { | |||
mob.inventory.inventorySize = -1; | |||
mob.inventory.dailyDrops = blueprint.dailyDrops; | |||
if (this.zone) { | |||
let chats = this.zone.chats; | |||
if (this.zoneConfig) { | |||
let chats = this.zoneConfig.chats; | |||
if (chats && chats[mob.name.toLowerCase()]) { | |||
mob.addComponent('chatter', { | |||
chats: chats[mob.name.toLowerCase()] | |||
}); | |||
} | |||
let dialogues = this.zone.dialogues; | |||
let dialogues = this.zoneConfig.dialogues; | |||
if (dialogues && dialogues[mob.name.toLowerCase()]) { | |||
mob.addComponent('dialogue', { | |||
config: dialogues[mob.name.toLowerCase()] | |||
@@ -1,6 +1,7 @@ | |||
const spawners = require('../spawners'); | |||
const spawnObjects = (scope, instance, room) => { | |||
let template = room.template; | |||
let spawners = instance.spawners; | |||
let spawnCd = instance.map.mapFile.properties.spawnCd; | |||
template.objects.forEach(o => { | |||
@@ -5,7 +5,7 @@ module.exports = { | |||
objects: null, | |||
syncer: null, | |||
zone: null, | |||
zoneConfig: null, | |||
physics: null, | |||
map: null, | |||
@@ -17,7 +17,7 @@ module.exports = { | |||
syncer: instance.syncer, | |||
physics: instance.physics, | |||
map: instance.map, | |||
zone: instance.zone | |||
zoneConfig: instance.zoneConfig | |||
}); | |||
}, | |||
@@ -76,7 +76,7 @@ module.exports = { | |||
let position = null; | |||
if (blueprint.type === 'herb') { | |||
if (blueprint.type === 'herb' && !blueprint.positions) { | |||
x = ~~(Math.random() * w); | |||
y = ~~(Math.random() * h); | |||
@@ -124,7 +124,7 @@ module.exports = { | |||
if (blueprint.quantity) | |||
quantity = blueprint.quantity[0] + ~~(Math.random() * (blueprint.quantity[1] - blueprint.quantity[0])); | |||
let zoneLevel = this.zone.level; | |||
let zoneLevel = this.zoneConfig.level; | |||
zoneLevel = ~~(zoneLevel[0] + ((zoneLevel[1] - zoneLevel[0]) / 2)); | |||
let objBlueprint = extend({}, blueprint, position); | |||
@@ -35,7 +35,7 @@ const doRezone = stagedRezone => { | |||
const clientAck = msg => { | |||
const staged = stagedRezones.find(s => s.simplifiedObj.serverId === msg.sourceId); | |||
if (!staged) | |||
if (!staged) | |||
return; | |||
stagedRezones.spliceWhere(s => s === staged); | |||
@@ -9,9 +9,9 @@ module.exports = { | |||
init: function (msg) { | |||
this.objects = msg.objects; | |||
this.syncer = msg.syncer; | |||
this.zone = msg.zone; | |||
this.zoneConfig = msg.zoneConfig; | |||
this.mobBuilder = extend({ | |||
zone: this.zone | |||
zoneConfig: this.zoneConfig | |||
}, mobBuilder); | |||
}, | |||
@@ -40,7 +40,7 @@ module.exports = { | |||
else | |||
this.mobTypes[name]++; | |||
spawner.zonePrint = extend({}, this.zone.mobs.default, this.zone.mobs[name] || {}); | |||
spawner.zonePrint = extend({}, this.zoneConfig.mobs.default, this.zoneConfig.mobs[name] || {}); | |||
}, | |||
spawn: function (spawner) { | |||
@@ -162,7 +162,7 @@ module.exports = { | |||
if (l.blueprint.layerName === 'mobs') | |||
this.setupMob(mob, l.zonePrint); | |||
else { | |||
const blueprint = extend({}, this.zone.objects.default, this.zone.objects[name] || {}); | |||
const blueprint = extend({}, this.zoneConfig.objects.default, this.zoneConfig.objects[name] || {}); | |||
this.setupObj(mob, blueprint); | |||
} | |||
@@ -194,7 +194,7 @@ module.exports = { | |||
this.setupObj(mob, blueprint); | |||
this.mobBuilder.build(mob, blueprint, type, this.zone.name); | |||
this.mobBuilder.build(mob, blueprint, type, this.zoneConfig.name); | |||
}, | |||
setupObj: function (obj, blueprint) { | |||