feat #1866: Moved save to after delete and ensured that deleted objects aren't... See merge request Isleward/isleward!580tags/v0.10.6
@@ -31,7 +31,7 @@ define([ | |||
if (cpn.type) | |||
templates.push(tpl); | |||
if (cpn.extends) | |||
extenders.push(tpl); | |||
extenders.push({ extends: cpn.extends, tpl }); | |||
res(); | |||
}); | |||
@@ -49,7 +49,7 @@ define([ | |||
templates.forEach(t => { | |||
const extensions = extenders.filter(e => e.extends === t.type); | |||
extensions.forEach(e => $.extend(true, t, e)); | |||
extensions.forEach(e => $.extend(true, t, e.tpl)); | |||
t.eventList = {}; | |||
t.hookEvent = hookEvent; | |||
@@ -1,7 +1,9 @@ | |||
define([ | |||
'js/misc/distanceToPolygon' | |||
'js/misc/distanceToPolygon', | |||
'js/system/events' | |||
], function ( | |||
distanceToPolygon | |||
distanceToPolygon, | |||
events | |||
) { | |||
return { | |||
grid: null, | |||
@@ -10,6 +12,8 @@ define([ | |||
height: 0, | |||
init: function (collisionMap) { | |||
events.on('resetPhysics', this.reset.bind(this)); | |||
this.width = collisionMap.length; | |||
this.height = collisionMap[0].length; | |||
@@ -22,6 +26,13 @@ define([ | |||
} | |||
}, | |||
reset: function () { | |||
this.width = 0; | |||
this.height = 0; | |||
this.grid = []; | |||
}, | |||
isTileBlocking: function (x, y, mob, obj) { | |||
if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height)) | |||
return true; | |||
@@ -52,7 +52,7 @@ define([ | |||
attackSpeed: 'attack speed', | |||
castSpeed: 'cast speed', | |||
lifeOnHit: 'life gained on hit', | |||
lifeOnHit: 'life gained on dealing physical damage', | |||
auraReserveMultiplier: 'aura mana reservation multiplier', | |||
@@ -174,7 +174,7 @@ define([ | |||
offEvents: function () { | |||
if (this.pather) | |||
this.pather.onDeath(); | |||
this.pather.resetPath(); | |||
for (let e in this.eventCallbacks) | |||
this.eventCallbacks[e].forEach(c => events.off(e, c)); | |||
@@ -15,11 +15,15 @@ define([ | |||
objects: [], | |||
init: function () { | |||
events.on('onGetObject', this.onGetObject.bind(this)); | |||
events.on('onRezone', this.onRezone.bind(this)); | |||
events.on('onChangeHoverTile', this.getLocation.bind(this)); | |||
events.on('onTilesVisible', this.onTilesVisible.bind(this)); | |||
events.on('onToggleNameplates', this.onToggleNameplates.bind(this)); | |||
[ | |||
'onGetObject', | |||
'onTilesVisible', | |||
'onToggleNameplates', | |||
'destroyAllObjects' | |||
] | |||
.forEach(e => events.on(e, this[e].bind(this))); | |||
}, | |||
getLocation: function (x, y) { | |||
@@ -87,20 +91,14 @@ define([ | |||
return list[fromIndex]; | |||
}, | |||
onRezone: function (oldZone) { | |||
let objects = this.objects; | |||
let oLen = objects.length; | |||
for (let i = 0; i < oLen; i++) { | |||
let o = objects[i]; | |||
destroyAllObjects: function () { | |||
this.objects.forEach(o => { | |||
o.destroy(); | |||
}); | |||
if (oldZone === null) | |||
o.destroy(); | |||
else if (o.zoneId === oldZone) | |||
o.destroy(); | |||
} | |||
this.objects.length = 0; | |||
if (window.player) | |||
window.player.offEvents(); | |||
window?.player?.offEvents(); | |||
}, | |||
onGetObject: function (obj) { | |||
@@ -0,0 +1,45 @@ | |||
define([ | |||
'js/rendering/spritePool' | |||
], function ( | |||
spritePool | |||
) { | |||
return function () { | |||
let map = this.map; | |||
let w = this.w = map.length; | |||
let h = this.h = map[0].length; | |||
this.stage.removeChild(this.layers.hiders); | |||
this.layers.hiders = new PIXI.Container(); | |||
this.layers.hiders.layer = 'hiders'; | |||
this.stage.addChild(this.layers.hiders); | |||
let container = this.layers.tileSprites; | |||
this.stage.removeChild(container); | |||
this.layers.tileSprites = container = new PIXI.Container(); | |||
container.layer = 'tiles'; | |||
this.stage.addChild(container); | |||
this.stage.children.sort((a, b) => { | |||
if (a.layer === 'hiders') | |||
return 1; | |||
else if (b.layer === 'hiders') | |||
return -1; | |||
else if (a.layer === 'tiles') | |||
return -1; | |||
else if (b.layer === 'tiles') | |||
return 1; | |||
return 0; | |||
}); | |||
spritePool.clean(); | |||
this.sprites = _.get2dArray(w, h, 'array'); | |||
this.map = []; | |||
this.w = 0; | |||
this.h = 0; | |||
delete this.moveTo; | |||
}; | |||
}); |
@@ -8,7 +8,8 @@ define([ | |||
'js/rendering/shaders/outline', | |||
'js/rendering/spritePool', | |||
'js/system/globals', | |||
'js/rendering/renderLoginBackground' | |||
'js/rendering/renderLoginBackground', | |||
'js/rendering/helpers/resetRenderer' | |||
], function ( | |||
resources, | |||
events, | |||
@@ -19,7 +20,8 @@ define([ | |||
shaderOutline, | |||
spritePool, | |||
globals, | |||
renderLoginBackground | |||
renderLoginBackground, | |||
resetRenderer | |||
) { | |||
const mRandom = Math.random.bind(Math); | |||
@@ -84,6 +86,7 @@ define([ | |||
events.on('onGetMap', this.onGetMap.bind(this)); | |||
events.on('onToggleFullscreen', this.toggleScreen.bind(this)); | |||
events.on('onMoveSpeedChange', this.adaptCameraMoveSpeed.bind(this)); | |||
events.on('resetRenderer', resetRenderer.bind(this)); | |||
this.width = $('body').width(); | |||
this.height = $('body').height(); | |||
@@ -18,7 +18,38 @@ define([ | |||
this.socket.on('event', this.onEvent.bind(this)); | |||
this.socket.on('events', this.onEvents.bind(this)); | |||
this.socket.on('dc', this.onDisconnect.bind(this)); | |||
Object.entries(this.processAction).forEach(([k, v]) => { | |||
this.processAction[k] = v.bind(this); | |||
}); | |||
}, | |||
onRezoneStart: function () { | |||
//Fired for mods to listen to | |||
events.emit('rezoneStart'); | |||
events.emit('destroyAllObjects'); | |||
events.emit('resetRenderer'); | |||
events.emit('resetPhysics'); | |||
events.emit('clearUis'); | |||
client.request({ | |||
threadModule: 'rezoneManager', | |||
method: 'clientAck', | |||
data: {} | |||
}); | |||
}, | |||
onGetMap: function ([msg]) { | |||
events.emit('onGetMap', msg); | |||
client.request({ | |||
threadModule: 'instancer', | |||
method: 'clientAck', | |||
data: {} | |||
}); | |||
}, | |||
onConnected: function (onReady) { | |||
if (this.doneConnect) | |||
this.onDisconnect(); | |||
@@ -28,45 +59,67 @@ define([ | |||
if (onReady) | |||
onReady(); | |||
}, | |||
onDisconnect: function () { | |||
window.location = window.location; | |||
}, | |||
onHandshake: function () { | |||
events.emit('onHandshake'); | |||
this.socket.emit('handshake'); | |||
}, | |||
request: function (msg) { | |||
this.socket.emit('request', msg, msg.callback); | |||
}, | |||
onEvent: function (response) { | |||
events.emit(response.event, response.data); | |||
}, | |||
onEvents: function (response) { | |||
//If we get objects, self needs to be first | |||
// otherwise we might create the object (setting his position or attack animation) | |||
// before instantiating it | |||
let oList = response.onGetObject; | |||
if (oList) { | |||
let prepend = oList.filter(o => o.self); | |||
oList.spliceWhere(o => prepend.some(p => p === o)); | |||
oList.unshift.apply(oList, prepend); | |||
} | |||
for (let e in response) { | |||
let r = response[e]; | |||
processAction: { | |||
default: function (eventName, msgs) { | |||
msgs.forEach(m => events.emit(eventName, m)); | |||
}, | |||
//Certain messages expect to be performed last (because the object they act on hasn't been created when they get queued) | |||
r.sort(function (a, b) { | |||
if (a.performLast) | |||
return 1; | |||
else if (b.performLast) | |||
return -1; | |||
return 0; | |||
}); | |||
rezoneStart: function (eventName, msgs) { | |||
events.emit('rezoneStart'); | |||
events.emit('destroyAllObjects'); | |||
events.emit('resetRenderer'); | |||
events.emit('resetPhysics'); | |||
events.emit('clearUis'); | |||
r.forEach(function (o) { | |||
events.emit(e, o); | |||
client.request({ | |||
threadModule: 'rezoneManager', | |||
method: 'clientAck', | |||
data: {} | |||
}); | |||
}, | |||
getMap: function (eventName, msgs) { | |||
events.emit('onBuildIngameUis'); | |||
events.emit('onGetMap', msgs[0]); | |||
}, | |||
onGetObject: function (eventName, msgs) { | |||
const prepend = msgs.filter(o => o.self); | |||
msgs.spliceWhere(o => prepend.some(p => p === o)); | |||
msgs.unshift.apply(msgs, prepend); | |||
this.processAction.default(eventName, msgs); | |||
} | |||
}, | |||
onEvent: function ({ event: eventName, data: eventData }) { | |||
const handler = this.processAction[eventName] || this.processAction.default; | |||
handler(eventName, [eventData]); | |||
}, | |||
onEvents: function (response) { | |||
for (let eventName in response) { | |||
const eventMsgs = response[eventName]; | |||
const handler = this.processAction[eventName] || this.processAction.default; | |||
handler(eventName, eventMsgs); | |||
} | |||
} | |||
}; | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "isleward_client", | |||
"version": "0.10.5", | |||
"version": "0.10.6", | |||
"description": "isleward", | |||
"dependencies": { | |||
}, | |||
@@ -14,12 +14,13 @@ define([ | |||
return { | |||
uis: [], | |||
root: '', | |||
ingameUisBuilt: false, | |||
init: function (root) { | |||
if (root) | |||
this.root = root + '/'; | |||
events.on('onEnterGame', this.onEnterGame.bind(this)); | |||
events.on('onBuildIngameUis', this.onBuildIngameUis.bind(this)); | |||
events.on('onUiKeyDown', this.onUiKeyDown.bind(this)); | |||
events.on('onResize', this.onResize.bind(this)); | |||
@@ -31,39 +32,40 @@ define([ | |||
}); | |||
}, | |||
onEnterGame: async function () { | |||
events.clearQueue(); | |||
onBuildIngameUis: async function () { | |||
if (!this.ingameUisBuilt) { | |||
events.clearQueue(); | |||
await Promise.all( | |||
globals.clientConfig.uiList.map(u => { | |||
const uiType = u.path ? u.path.split('/').pop() : u; | |||
await Promise.all( | |||
globals.clientConfig.uiList.map(u => { | |||
const uiType = u.path ? u.path.split('/').pop() : u; | |||
return new Promise(res => { | |||
const doneCheck = () => { | |||
const isDone = this.uis.some(ui => ui.type === uiType); | |||
if (isDone) { | |||
res(); | |||
return new Promise(res => { | |||
const doneCheck = () => { | |||
const isDone = this.uis.some(ui => ui.type === uiType); | |||
if (isDone) { | |||
res(); | |||
return; | |||
} | |||
return; | |||
} | |||
setTimeout(doneCheck, 100); | |||
}; | |||
setTimeout(doneCheck, 100); | |||
}; | |||
this.build(uiType, { path: u.path }); | |||
this.build(uiType, { path: u.path }); | |||
doneCheck(); | |||
}); | |||
}) | |||
); | |||
doneCheck(); | |||
}); | |||
}) | |||
); | |||
this.ingameUisBuilt = true; | |||
} | |||
client.request({ | |||
cpn: 'player', | |||
method: 'performAction', | |||
data: { | |||
cpn: 'player', | |||
method: 'notifyServerUiReady' | |||
} | |||
threadModule: 'instancer', | |||
method: 'clientAck', | |||
data: {} | |||
}); | |||
}, | |||
@@ -169,6 +171,16 @@ define([ | |||
} | |||
}, | |||
exitGame: function () { | |||
$('[class^="ui"]:not(.ui-container)').toArray().forEach(el => { | |||
let ui = $(el).data('ui'); | |||
if (ui && ui.destroy) | |||
ui.destroy(); | |||
}); | |||
this.ingameUisBuilt = false; | |||
}, | |||
getUi: function (type) { | |||
return this.uis.find(u => u.type === type); | |||
} | |||
@@ -1,59 +0,0 @@ | |||
define([ | |||
'js/system/events', | |||
'html!ui/templates/buffs/template', | |||
'css!ui/templates/buffs/styles', | |||
'html!ui/templates/buffs/templateBuff' | |||
], function ( | |||
events, | |||
template, | |||
styles, | |||
templateBuff | |||
) { | |||
let icons = { | |||
stunned: [4, 0], | |||
regenHp: [3, 1], | |||
regenMana: [4, 1], | |||
swiftness: [5, 1], | |||
stealth: [7, 0], | |||
reflectDamage: [2, 1], | |||
holyVengeance: [4, 0] | |||
}; | |||
return { | |||
tpl: template, | |||
icons: {}, | |||
postRender: function () { | |||
this.onEvent('onGetBuff', this.onGetBuff.bind(this)); | |||
this.onEvent('onRemoveBuff', this.onRemoveBuff.bind(this)); | |||
}, | |||
onGetBuff: function (buff) { | |||
let icon = icons[buff.type]; | |||
if (!icon) | |||
return; | |||
let imgX = icon[0] * -32; | |||
let imgY = icon[1] * -32; | |||
let html = templateBuff; | |||
let el = $(html).appendTo(this.el) | |||
.find('.inner') | |||
.css({ | |||
background: 'url(../../../images/statusIcons.png) ' + imgX + 'px ' + imgY + 'px' | |||
}); | |||
this.icons[buff.id] = el.parent(); | |||
}, | |||
onRemoveBuff: function (buff) { | |||
let el = this.icons[buff.id]; | |||
if (!el) | |||
return; | |||
el.remove(); | |||
delete this.icons[buff.id]; | |||
} | |||
}; | |||
}); |
@@ -1 +0,0 @@ | |||
.uiBuffs{position:absolute;left:16px;top:104px}.uiBuffs .icon{width:40px;height:40px;padding:4px;background-color:rgba(49,33,54,.75);margin-right:16px;float:left}.uiBuffs .icon .inner{width:32px;height:32px;background:url(../../../images/statusIcons.png) 0 0}.mobile .uiBuffs{left:316px;top:10px} |
@@ -1 +0,0 @@ | |||
<div class="uiBuffs"></div> |
@@ -97,7 +97,6 @@ define([ | |||
onPlay: function () { | |||
this.el.removeClass('disabled'); | |||
this.destroy(); | |||
events.emit('onEnterGame'); | |||
}, | |||
onNewClick: function () { | |||
@@ -151,7 +151,6 @@ define([ | |||
if (!result) { | |||
this.clear(); | |||
this.destroy(); | |||
events.emit('onEnterGame'); | |||
} else | |||
this.el.find('.message').html(result); | |||
}, | |||
@@ -20,14 +20,14 @@ define([ | |||
this.onEvent('onPermadeath', this.onPermadeath.bind(this)); | |||
this.find('.btn-logout').on('click', this.onLogout.bind(this)); | |||
this.find('.btn-respawn').on('click', this.onRespawn.bind(this)); | |||
this.find('.btn-respawn').on('click', this.performRespawn.bind(this)); | |||
}, | |||
onLogout: function () { | |||
$('.uiMainMenu').data('ui').charSelect(); | |||
}, | |||
onRespawn: function () { | |||
performRespawn: function () { | |||
events.emit('onHideOverlay', this.el); | |||
this.hide(true); | |||
@@ -0,0 +1,54 @@ | |||
define([ | |||
'html!ui/templates/effects/template', | |||
'css!ui/templates/effects/styles', | |||
'html!ui/templates/effects/templateEffect' | |||
], function ( | |||
template, | |||
styles, | |||
templateEffect | |||
) { | |||
return { | |||
tpl: template, | |||
icons: {}, | |||
postRender: function () { | |||
this.onEvent('onGetEffectIcon', this.onGetEffectIcon.bind(this)); | |||
this.onEvent('onRemoveEffectIcon', this.onRemoveEffectIcon.bind(this)); | |||
}, | |||
buildIcon: function (config) { | |||
let { icon, url } = config; | |||
if (!url) | |||
url = '../../../images/statusIcons.png'; | |||
let imgX = icon[0] * -32; | |||
let imgY = icon[1] * -32; | |||
let html = templateEffect; | |||
let el = $(html).appendTo(this.el) | |||
.find('.inner') | |||
.css({ | |||
background: `url(${url}) ${imgX}px ${imgY}px` | |||
}); | |||
return el.parent(); | |||
}, | |||
onGetEffectIcon: function (config) { | |||
let el = this.buildIcon(config); | |||
this.icons[config.id] = el; | |||
}, | |||
onRemoveEffectIcon: function (config) { | |||
let el = this.icons[config.id]; | |||
if (!el) | |||
return; | |||
el.remove(); | |||
delete this.icons[config.id]; | |||
} | |||
}; | |||
}); |
@@ -0,0 +1 @@ | |||
.uiEffects{position:absolute;left:16px;top:104px}.uiEffects .icon{width:40px;height:40px;padding:4px;background-color:rgba(49,33,54,.75);margin-right:16px;float:left}.uiEffects .icon .inner{width:32px;height:32px;background:url(../../../images/statusIcons.png) 0 0}.mobile .uiEffects{left:316px;top:10px} |
@@ -1,6 +1,6 @@ | |||
@import "../../../css/colors.less"; | |||
.uiBuffs { | |||
.uiEffects { | |||
position: absolute; | |||
left: 16px; | |||
top: 104px; | |||
@@ -23,7 +23,7 @@ | |||
} | |||
.mobile .uiBuffs { | |||
.mobile .uiEffects { | |||
left: 316px; | |||
top: 10px; | |||
} |
@@ -0,0 +1 @@ | |||
<div class="uiEffects"></div> |
@@ -1,3 +1,3 @@ | |||
<div class="icon buff"> | |||
<div class="icon effect"> | |||
<div class="inner"></div> | |||
</div> |
@@ -26,7 +26,7 @@ define([ | |||
this.find('.btnCollapse').on('click', this.toggleButtons.bind(this)); | |||
} | |||
this.onEvent('onRezone', this.onRezone.bind(this)); | |||
this.onEvent('clearUis', this.clear.bind(this)); | |||
this.onEvent('onObtainEvent', this.onObtainEvent.bind(this)); | |||
this.onEvent('onRemoveEvent', this.onRemoveEvent.bind(this)); | |||
@@ -37,7 +37,7 @@ define([ | |||
this.onToggleEventsVisibility(config.showEvents); | |||
}, | |||
onRezone: function () { | |||
clear: function () { | |||
this.list = []; | |||
this.el.find('.list').empty(); | |||
}, | |||
@@ -328,17 +328,17 @@ define([ | |||
ctxConfig.push(menuItems.divider); | |||
} | |||
if ((!item.eq) && (!item.active)) { | |||
if (!item.quest) { | |||
if ((window.player.stash.active) && (!item.noStash)) | |||
ctxConfig.push(menuItems.stash); | |||
if (!item.eq && !item.active && !item.quest) { | |||
const isAtStash = window.player.serverActions.hasAction('openStash'); | |||
if (!item.noDrop) | |||
ctxConfig.push(menuItems.drop); | |||
if (isAtStash && !item.noStash) | |||
ctxConfig.push(menuItems.stash); | |||
if ((!item.material) && (!item.noSalvage)) | |||
ctxConfig.push(menuItems.salvage); | |||
} | |||
if (!item.noDrop) | |||
ctxConfig.push(menuItems.drop); | |||
if (!item.material && !item.noSalvage) | |||
ctxConfig.push(menuItems.salvage); | |||
} | |||
if (item.quantity > 1 && !item.quest) | |||
@@ -11,11 +11,11 @@ | |||
</div> | |||
<div class="message"></div> | |||
</div> | |||
<div class="news" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.5-Release-Notes">[ Latest Release Notes ]</div> | |||
<div class="news" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.6-Release-Notes">[ Latest Release Notes ]</div> | |||
<div class="extra"> | |||
<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 btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div> | |||
</div> | |||
<div class="version" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.5-Release-Notes">v0.10.5</div> | |||
<div class="version" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.5-Release-Notes">v0.10.6</div> | |||
</div> |
@@ -58,17 +58,17 @@ define([ | |||
}, | |||
onCharSelect: function () { | |||
renderer.clean(); | |||
objects.onRezone(); | |||
events.emit('destroyAllObjects'); | |||
events.emit('resetRenderer'); | |||
events.emit('resetPhysics'); | |||
renderer.buildTitleScreen(); | |||
sound.unload(); | |||
events.emit('onShowCharacterSelect'); | |||
$('[class^="ui"]:not(.ui-container)').toArray().forEach(el => { | |||
let ui = $(el).data('ui'); | |||
if (ui && ui.destroy) | |||
ui.destroy(); | |||
}); | |||
factory.exitGame(); | |||
factory.build('characters', {}); | |||
}, | |||
@@ -25,7 +25,7 @@ define([ | |||
this.find('.btnCollapse').on('click', this.toggleButtons.bind(this)); | |||
} | |||
this.onEvent('onRezone', this.onRezone.bind(this)); | |||
this.onEvent('clearUis', this.clear.bind(this)); | |||
this.onEvent('onObtainQuest', this.onObtainQuest.bind(this)); | |||
this.onEvent('onUpdateQuest', this.onUpdateQuest.bind(this)); | |||
@@ -35,7 +35,7 @@ define([ | |||
this.onToggleQuestsVisibility(config.showQuests); | |||
}, | |||
onRezone: function () { | |||
clear: function () { | |||
this.quests = []; | |||
this.el.find('.list').empty(); | |||
}, | |||
@@ -22,33 +22,38 @@ define([ | |||
hoverItem: null, | |||
items: [], | |||
maxItems: null, | |||
modal: true, | |||
hasClose: true, | |||
postRender: function () { | |||
this.onEvent('onGetStashItems', this.onGetStashItems.bind(this)); | |||
this.onEvent('onDestroyStashItems', this.onDestroyStashItems.bind(this)); | |||
this.onEvent('onKeyDown', this.onKeyDown.bind(this)); | |||
this.onEvent('onKeyUp', this.onKeyUp.bind(this)); | |||
this.onEvent('onOpenStash', this.toggle.bind(this)); | |||
[ | |||
'onKeyUp', | |||
'onKeyDown', | |||
'onOpenStash', | |||
'onAddStashItems', | |||
'onRemoveStashItems' | |||
] | |||
.forEach(e => { | |||
this.onEvent(e, this[e].bind(this)); | |||
}); | |||
}, | |||
build: function () { | |||
this.el.removeClass('scrolls'); | |||
if (window.player.stash.maxItems > 50) | |||
this.el.addClass('scrolls'); | |||
const { el, maxItems, items } = this; | |||
let container = this.el.find('.grid') | |||
.empty(); | |||
el.removeClass('scrolls'); | |||
if (maxItems > 50) | |||
el.addClass('scrolls'); | |||
let items = this.items; | |||
let iLen = Math.max(items.length, window.player.stash.maxItems); | |||
const container = this.el.find('.grid').empty(); | |||
for (let i = 0; i < iLen; i++) { | |||
let item = items[i]; | |||
const renderItemCount = Math.max(items.length, maxItems); | |||
let itemEl = renderItem(container, item); | |||
for (let i = 0; i < renderItemCount; i++) { | |||
const item = items[i]; | |||
const itemEl = renderItem(container, item); | |||
if (!item) | |||
continue; | |||
@@ -126,14 +131,28 @@ define([ | |||
this.build(); | |||
}, | |||
onDestroyStashItems: function (itemIds) { | |||
itemIds.forEach(function (id) { | |||
let item = this.items.find(i => i.id === id); | |||
onAddStashItems: function (addItems) { | |||
const { items } = this; | |||
addItems.forEach(newItem => { | |||
const existIndex = items.findIndex(i => i.id === newItem.id); | |||
if (existIndex !== -1) | |||
items.splice(existIndex, 1, newItem); | |||
else | |||
items.push(newItem); | |||
}); | |||
}, | |||
onRemoveStashItems: function (removeItemIds) { | |||
const { items } = this; | |||
removeItemIds.forEach(id => { | |||
const item = items.find(i => i.id === id); | |||
if (item === this.hoverItem) | |||
this.hideTooltip(); | |||
this.items.spliceWhere(i => i.id === id); | |||
}, this); | |||
items.spliceWhere(i => i.id === id); | |||
}); | |||
if (this.shown) | |||
this.build(); | |||
@@ -155,8 +174,12 @@ define([ | |||
events.emit('onHideContextMenu'); | |||
}, | |||
onOpenStash: function () { | |||
this.build(); | |||
onOpenStash: function ({ items, maxItems }) { | |||
this.maxItems = maxItems; | |||
this.show(); | |||
this.onGetStashItems(items); | |||
}, | |||
beforeDestroy: function () { | |||
@@ -18,11 +18,7 @@ define([ | |||
postRender: function () { | |||
this.onEvent('onGetTalk', this.onGetTalk.bind(this)); | |||
this.onEvent('onRezone', this.onRezone.bind(this)); | |||
}, | |||
onRezone: function () { | |||
this.hide(); | |||
this.onEvent('clearUis', this.hide.bind(this)); | |||
}, | |||
onGetTalk: function (dialogue) { | |||
@@ -58,7 +58,9 @@ | |||
"leaderboard": false, | |||
"clientConfig": false, | |||
"random": false, | |||
"consts": false | |||
"consts": false, | |||
"rezoneManager": false, | |||
"eventManager": false | |||
}, | |||
"rules": { | |||
@@ -1,135 +1,127 @@ | |||
define([ | |||
'js/rendering/renderer' | |||
'js/system/events', | |||
'js/rendering/numbers' | |||
], function ( | |||
renderer | |||
events, | |||
numbers | |||
) { | |||
let auras = { | |||
reflectDamage: 0, | |||
stealth: 1, | |||
regenHp: 9, | |||
regenMana: 10, | |||
swiftness: 11, | |||
holyVengeance: 8, | |||
rare: 16 | |||
const defaultBuffIcons = { | |||
stunned: [4, 0] | |||
}; | |||
return { | |||
type: 'effects', | |||
const effectBase = { | |||
init: function () { | |||
this.defaultDamageText(false); | |||
alpha: 0, | |||
alphaDir: 0.0025, | |||
if (this.self && defaultBuffIcons[this.type]) { | |||
events.emit('onGetEffectIcon', { | |||
id: this.id, | |||
icon: defaultBuffIcons[this.type] | |||
}); | |||
} | |||
}, | |||
alphaMax: 0.6, | |||
alphaMin: 0.35, | |||
destroy: function () { | |||
if (!this.obj.destroyed) | |||
this.defaultDamageText(true); | |||
alphaCutoff: 0.4, | |||
if (this.self && defaultBuffIcons[this.type]) { | |||
events.emit('onRemoveEffectIcon', { | |||
id: this.id | |||
}); | |||
} | |||
}, | |||
defaultDamageText: function (removing) { | |||
numbers.onGetDamage({ | |||
id: this.obj.id, | |||
event: true, | |||
text: (removing ? '-' : '+') + this.type | |||
}); | |||
} | |||
}; | |||
return { | |||
type: 'effects', | |||
effects: [], | |||
templates: { | |||
}, | |||
init: function (blueprint) { | |||
this.effects = this.effects | |||
.filter(e => auras[e] !== null) | |||
.map(e => { | |||
return { | |||
name: e, | |||
sprite: renderer.buildObject({ | |||
layerName: 'effects', | |||
sheetName: 'auras', | |||
x: this.obj.x, | |||
y: this.obj.y + 1, | |||
w: scale * 3, | |||
h: scale * 3, | |||
cell: auras[e] | |||
}) | |||
}; | |||
}); | |||
this.effects = this.effects.map(e => this.buildEffect(e)); | |||
}, | |||
buildEffect: function (data) { | |||
let template = this.templates[data.type] || {}; | |||
let effect = $.extend(true, {}, effectBase, template, data); | |||
effect.self = !!this.obj.self; | |||
effect.obj = this.obj; | |||
if (effect.init) | |||
effect.init(); | |||
return effect; | |||
}, | |||
extend: function (blueprint) { | |||
if (blueprint.addEffects) { | |||
blueprint.addEffects = blueprint.addEffects | |||
.filter(e => { | |||
return (auras[e] !== null); | |||
}) | |||
.map(e => { | |||
return { | |||
name: e, | |||
sprite: renderer.buildObject({ | |||
layerName: 'effects', | |||
sheetName: 'auras', | |||
x: this.obj.x, | |||
y: this.obj.y + 1, | |||
w: scale * 3, | |||
h: scale * 3, | |||
cell: auras[e] | |||
}) | |||
}; | |||
}); | |||
blueprint.addEffects = blueprint.addEffects.map(e => this.buildEffect(e)); | |||
this.effects.push.apply(this.effects, blueprint.addEffects || []); | |||
} | |||
if (blueprint.removeEffects) { | |||
blueprint.removeEffects.forEach(r => { | |||
let effect = this.effects.find(e => e.name === r); | |||
blueprint.removeEffects.forEach(removeId => { | |||
let effect = this.effects.find(e => e.id === removeId); | |||
if (!effect) | |||
return; | |||
renderer.destroyObject({ | |||
layerName: 'effects', | |||
sprite: effect.sprite | |||
}); | |||
if (effect.destroy) | |||
effect.destroy(); | |||
this.effects.spliceFirstWhere(e => e.name === r); | |||
this.effects.spliceFirstWhere(e => e.id === removeId); | |||
}); | |||
} | |||
}, | |||
update: function () { | |||
this.alpha += this.alphaDir; | |||
if ((this.alphaDir > 0) && (this.alpha >= this.alphaMax)) { | |||
this.alpha = this.alphaMax; | |||
this.alphaDir *= -1; | |||
} else if ((this.alphaDir < 0) && (this.alpha <= this.alphaMin)) { | |||
this.alpha = this.alphaMin; | |||
this.alphaDir *= -1; | |||
} | |||
if (blueprint.extendEffects) { | |||
blueprint.extendEffects.forEach(u => { | |||
let effect = this.effects.find(e => e.id === u.id); | |||
let x = this.obj.x; | |||
let y = this.obj.y; | |||
if (!effect) | |||
return; | |||
let useAlpha = this.alpha; | |||
if (useAlpha < this.alphaCutoff) | |||
useAlpha = 0; | |||
else { | |||
useAlpha -= this.alphaCutoff; | |||
useAlpha /= (this.alphaMax - this.alphaCutoff); | |||
if (effect.extend) | |||
effect.extend(u.data); | |||
else { | |||
for (let p in u.data) | |||
effect[p] = u.data[p]; | |||
} | |||
}); | |||
} | |||
}, | |||
update: function () { | |||
this.effects.forEach(e => { | |||
renderer.setSpritePosition({ | |||
x, | |||
y: y + 1, | |||
sprite: e.sprite | |||
}); | |||
e.sprite.alpha = useAlpha; | |||
e.sprite.visible = this.obj.isVisible; | |||
if (e.update) | |||
e.update(); | |||
}); | |||
}, | |||
setVisible: function (visible) { | |||
this.effects.forEach(e => { | |||
e.sprite.visible = visible; | |||
if (e.setVisible) | |||
e.setVisible(visible); | |||
}); | |||
}, | |||
destroy: function () { | |||
this.effects.forEach(e => { | |||
renderer.destroyObject({ | |||
layerName: 'effects', | |||
sprite: e.sprite | |||
}); | |||
if (e.destroy) | |||
e.destroy(); | |||
}); | |||
} | |||
}; | |||
@@ -0,0 +1,136 @@ | |||
define([ | |||
'js/rendering/renderer', | |||
'js/system/events' | |||
], function ( | |||
renderer, | |||
events | |||
) { | |||
const auras = { | |||
reflectDamage: 0, | |||
stealth: 1, | |||
regenHp: 9, | |||
regenMana: 10, | |||
swiftness: 11, | |||
holyVengeance: 8, | |||
rare: 16 | |||
}; | |||
const buffIcons = { | |||
regenHp: [3, 1], | |||
regenMana: [4, 1], | |||
swiftness: [5, 1], | |||
stealth: [7, 0], | |||
reflectDamage: [2, 1], | |||
holyVengeance: [4, 0] | |||
}; | |||
let templates = {}; | |||
Object.keys(auras).forEach(type => { | |||
let cell = auras[type]; | |||
templates[type] = { | |||
sprite: null, | |||
alpha: 0, | |||
alphaDir: 0.0025, | |||
alphaMax: 0.6, | |||
alphaMin: 0.35, | |||
alphaCutoff: 0.4, | |||
init: function () { | |||
this.sprite = renderer.buildObject({ | |||
layerName: 'effects', | |||
sheetName: 'auras', | |||
x: this.obj.x, | |||
y: this.obj.y + 1, | |||
w: scale * 3, | |||
h: scale * 3, | |||
cell: cell | |||
}); | |||
this.defaultDamageText(); | |||
if (this.self && buffIcons[type]) { | |||
events.emit('onGetEffectIcon', { | |||
id: this.id, | |||
icon: buffIcons[type] | |||
}); | |||
} | |||
}, | |||
getAlpha: function () { | |||
let listAuras = this.obj.effects.effects.filter(e => auras[e.type]); | |||
let first = listAuras[0]; | |||
// The first aura in the list should do all the updating so that all auras pulse together | |||
if (first === this) { | |||
this.alpha += this.alphaDir; | |||
if ((this.alphaDir > 0) && (this.alpha >= this.alphaMax)) { | |||
this.alpha = this.alphaMax; | |||
this.alphaDir *= -1; | |||
} else if ((this.alphaDir < 0) && (this.alpha <= this.alphaMin)) { | |||
this.alpha = this.alphaMin; | |||
this.alphaDir *= -1; | |||
} | |||
} else { | |||
this.alpha = first.alpha; | |||
this.alphaDir = first.alphaDir; | |||
} | |||
let useAlpha = this.alpha; | |||
if (useAlpha < this.alphaCutoff) | |||
useAlpha = 0; | |||
else { | |||
useAlpha -= this.alphaCutoff; | |||
useAlpha /= (this.alphaMax - this.alphaCutoff); | |||
} | |||
return useAlpha; | |||
}, | |||
update: function () { | |||
let useAlpha = this.getAlpha(); | |||
let x = this.obj.x; | |||
let y = this.obj.y; | |||
renderer.setSpritePosition({ | |||
x, | |||
y: y + 1, | |||
sprite: this.sprite | |||
}); | |||
this.sprite.alpha = useAlpha; | |||
this.sprite.visible = this.obj.isVisible; | |||
}, | |||
destroy: function () { | |||
renderer.destroyObject({ | |||
layerName: 'effects', | |||
sprite: this.sprite | |||
}); | |||
this.defaultDamageText(true); | |||
if (this.self && buffIcons[type]) { | |||
events.emit('onRemoveEffectIcon', { | |||
id: this.id | |||
}); | |||
} | |||
}, | |||
setVisible: function (visible) { | |||
this.sprite.visible = visible; | |||
} | |||
}; | |||
}); | |||
return { | |||
templates: { | |||
...templates | |||
} | |||
}; | |||
}); |
@@ -13,7 +13,6 @@ define([ | |||
init: function () { | |||
this.obj.on('onKeyDown', this.onKeyDown.bind(this)); | |||
this.hookEvent('onRezone', this.onRezone.bind(this)); | |||
}, | |||
extend: function (msg) { | |||
@@ -53,13 +52,6 @@ define([ | |||
} | |||
}, | |||
onRezone: function () { | |||
this.extend({ | |||
progress: 100, | |||
action: 'Fishing' | |||
}); | |||
}, | |||
onKeyDown: function (key) { | |||
if (key !== 'g') | |||
return; | |||
@@ -27,9 +27,9 @@ define([ | |||
lastY: 0, | |||
init: function () { | |||
events.on('onRespawn', this.onDeath.bind(this)); | |||
events.on('onDeath', this.onDeath.bind(this)); | |||
events.on('onClearQueue', this.onDeath.bind(this)); | |||
events.on('teleportToPosition', this.resetPath.bind(this)); | |||
events.on('onDeath', this.resetPath.bind(this)); | |||
events.on('onClearQueue', this.resetPath.bind(this)); | |||
this.pathPos.x = round(this.obj.x); | |||
this.pathPos.y = round(this.obj.y); | |||
@@ -46,7 +46,7 @@ define([ | |||
this.path = []; | |||
}, | |||
onDeath: function () { | |||
resetPath: function () { | |||
this.clearPath(); | |||
this.pathPos.x = round(this.obj.x); | |||
@@ -29,7 +29,7 @@ define([ | |||
obj.addComponent('serverActions'); | |||
obj.addComponent('pather'); | |||
this.hookEvent('onRespawn', this.onRespawn.bind(this)); | |||
this.hookEvent('teleportToPosition', this.teleportToPosition.bind(this)); | |||
events.emit('onGetPortrait', obj.portrait); | |||
}, | |||
@@ -74,7 +74,7 @@ define([ | |||
}, instant); | |||
}, | |||
onRespawn: function ({ x, y }) { | |||
teleportToPosition: function ({ x, y }) { | |||
this.positionCamera(x, y, true); | |||
sound.update(x, y); | |||
}, | |||
@@ -14,6 +14,10 @@ define([ | |||
this.hookEvent('onKeyUp', this.onKeyUp.bind(this)); | |||
}, | |||
hasAction: function (actionId) { | |||
return this.actions.some(a => a.id === actionId); | |||
}, | |||
onKeyUp: function (key) { | |||
this.actions.forEach(function (a) { | |||
if (a.key !== key) | |||
@@ -1,49 +1,7 @@ | |||
define([ | |||
'js/system/events' | |||
], function ( | |||
events | |||
) { | |||
define([], function () { | |||
return { | |||
type: 'stash', | |||
active: false, | |||
items: null, | |||
init: function () { | |||
events.emit('onGetStashItems', this.items); | |||
}, | |||
extend: function (blueprint) { | |||
if (blueprint.has('active')) | |||
this.active = blueprint.active; | |||
if (blueprint.getItems) { | |||
let items = this.items; | |||
let newItems = blueprint.getItems || []; | |||
let nLen = newItems.length; | |||
for (let i = 0; i < nLen; i++) { | |||
let nItem = newItems[i]; | |||
let nId = nItem.id; | |||
let findItem = items.find(f => f.id === nId); | |||
if (findItem) { | |||
$.extend(true, findItem, nItem); | |||
newItems.splice(i, 1); | |||
i--; | |||
nLen--; | |||
} | |||
} | |||
this.items.push.apply(this.items, blueprint.getItems || []); | |||
events.emit('onGetStashItems', this.items); | |||
} | |||
if (blueprint.destroyItems) | |||
events.emit('onDestroyStashItems', blueprint.destroyItems); | |||
} | |||
init: function () {} | |||
}; | |||
}); |
@@ -403,5 +403,9 @@ module.exports = { | |||
clearIgnoreList: function () { | |||
this.ignoreList = []; | |||
}, | |||
isInCombat: function () { | |||
return this.list.length > 0; | |||
} | |||
}; |
@@ -93,6 +93,9 @@ module.exports = { | |||
doSave: async function (callback, saveStash = true) { | |||
const simple = this.obj.getSimple(true, true); | |||
delete simple.destroyed; | |||
delete simple.forceDestroy; | |||
simple.components.spliceWhere(f => (f.type === 'stash')); | |||
await io.setAsync({ | |||
@@ -111,10 +114,15 @@ module.exports = { | |||
}, | |||
doSaveStash: async function () { | |||
const { username, obj: { stash } } = this; | |||
if (!stash.changed) | |||
return; | |||
await io.setAsync({ | |||
key: this.username, | |||
key: username, | |||
table: 'stash', | |||
value: this.obj.stash.serialize(), | |||
value: stash.serialize(), | |||
clean: true, | |||
serialize: true | |||
}); | |||
@@ -174,7 +182,6 @@ module.exports = { | |||
this.characters[charName] = character; | |||
await this.getCustomChannels(character); | |||
await this.getStash(); | |||
await this.verifySkin(character); | |||
@@ -341,8 +348,13 @@ module.exports = { | |||
register: async function (msg) { | |||
let credentials = msg.data; | |||
if ((credentials.username === '') || (credentials.password === '')) { | |||
if (credentials.username === '' || credentials.password === '') { | |||
msg.callback(messages.login.allFields); | |||
return; | |||
} else if (credentials.username.length > 32) { | |||
msg.callback(messages.login.maxUsernameLength); | |||
return; | |||
} | |||
@@ -110,14 +110,14 @@ module.exports = { | |||
let effect = effects[i]; | |||
if (!forceDestroy) { | |||
if (effect.persist) { | |||
this.syncRemove(effect.id, effect.type); | |||
this.syncRemove(effect.id); | |||
continue; | |||
} | |||
} | |||
this.destroyEffect(effect); | |||
this.syncRemove(effect.id, effect.type); | |||
this.syncRemove(effect.id); | |||
effects.splice(i, 1); | |||
eLen--; | |||
i--; | |||
@@ -137,36 +137,40 @@ module.exports = { | |||
}, | |||
addEffect: function (options, source) { | |||
//Skip 0-duration effects | |||
if ((options.has('ttl')) && (options.ttl === 0)) | |||
return; | |||
options.caster = options.caster || source; | |||
//"X of Y in Z" cc resist check | |||
if (!options.force && !this.canApplyEffect(options.type)) | |||
return; | |||
if (!options.new) { | |||
let exists = this.effects.find(e => e.type === options.type); | |||
if (exists) { | |||
exists.ttl += options.ttl; | |||
let oldEffect = this.effects.find(e => e.type === options.type); | |||
for (let p in options) { | |||
if (p === 'ttl') | |||
continue; | |||
exists[p] = options[p]; | |||
} | |||
return exists; | |||
} | |||
//If there is no existing effect or the effect is not stackable, make a new effect | |||
if (!oldEffect || !oldEffect.shouldStack) | |||
return this.buildEffect(options); | |||
//If the effect is stackable and the new effect should stack, stack with the old effect | |||
let shouldStack = oldEffect.shouldStack(options); | |||
if (shouldStack && oldEffect.incrementStack) { | |||
oldEffect.incrementStack(options); | |||
return oldEffect; | |||
} | |||
//Otherwise make a new effect | |||
return this.buildEffect(options); | |||
}, | |||
getTypeTemplate: function (type) { | |||
let typeTemplate = null; | |||
if (options.type) { | |||
let type = options.type[0].toUpperCase() + options.type.substr(1); | |||
if (type) { | |||
let capitalizedType = type[0].toUpperCase() + type.substr(1); | |||
let result = { | |||
type: type, | |||
url: 'config/effects/effect' + type + '.js' | |||
url: 'config/effects/effect' + capitalizedType + '.js' | |||
}; | |||
this.obj.instance.eventEmitter.emit('onBeforeGetEffect', result); | |||
@@ -174,84 +178,77 @@ module.exports = { | |||
} | |||
let builtEffect = extend({}, effectTemplate, typeTemplate); | |||
return builtEffect; | |||
}, | |||
buildEffect: function (options) { | |||
let builtEffect = this.getTypeTemplate(options.type); | |||
for (let p in options) | |||
builtEffect[p] = options[p]; | |||
builtEffect.obj = this.obj; | |||
builtEffect.id = this.nextId++; | |||
builtEffect.noMsg = options.noMsg; | |||
builtEffect.silent = options.silent; | |||
if (builtEffect.init) | |||
builtEffect.init(options.source); | |||
this.effects.push(builtEffect); | |||
if (!options.noMsg) { | |||
this.obj.instance.syncer.queue('onGetBuff', { | |||
type: options.type, | |||
id: builtEffect.id | |||
}, [this.obj.serverId]); | |||
this.obj.instance.syncer.queue('onGetDamage', { | |||
id: this.obj.id, | |||
event: true, | |||
text: '+' + options.type | |||
}, -1); | |||
this.obj.syncer.setArray(false, 'effects', 'addEffects', options.type); | |||
} | |||
if (!options.silent) | |||
this.obj.syncer.setArray(false, 'effects', 'addEffects', builtEffect.simplify()); | |||
this.obj.instance.eventEmitter.emit('onAddEffect', this.obj, builtEffect); | |||
return builtEffect; | |||
}, | |||
syncRemove: function (id, type, noMsg) { | |||
if ((noMsg) || (!type)) | |||
syncExtend: function (id, data) { | |||
let effect = this.effects.find(e => e.id === id); | |||
if (!effect) | |||
return; | |||
//Never sync silent effects | |||
if (effect.silent) | |||
return; | |||
this.obj.instance.syncer.queue('onRemoveBuff', { | |||
id: id | |||
}, [this.obj.serverId]); | |||
this.obj.syncer.setArray(true, 'effects', 'extendEffects', { | |||
id, | |||
data | |||
}); | |||
}, | |||
syncRemove: function (id) { | |||
let effect = this.effects.find(e => e.id === id); | |||
if (!effect) | |||
return; | |||
this.obj.instance.syncer.queue('onGetDamage', { | |||
id: this.obj.id, | |||
event: true, | |||
text: '-' + type | |||
}, -1); | |||
if (effect.silent) | |||
return; | |||
this.obj.syncer.setArray(false, 'effects', 'removeEffects', type); | |||
this.obj.syncer.setArray(false, 'effects', 'removeEffects', id); | |||
}, | |||
removeEffect: function (checkEffect, noMsg) { | |||
let effects = this.effects; | |||
let eLen = effects.length; | |||
for (let i = 0; i < eLen; i++) { | |||
let effect = effects[i]; | |||
if (effect === checkEffect) { | |||
this.destroyEffect(effect); | |||
removeEffect: function (id) { | |||
const effect = this.effects.find(e => e.id === id); | |||
this.syncRemove(effect.id, effect.type, noMsg || effect.noMsg); | |||
effects.splice(i, 1); | |||
//It's possible that something else has removed the effect | |||
if (!effect) | |||
return; | |||
return; | |||
} | |||
} | |||
this.destroyEffect(effect); | |||
this.syncRemove(effect.id); | |||
this.effects.spliceWhere(e => e.id === id); | |||
}, | |||
removeEffectByName: function (effectName, noMsg) { | |||
let effects = this.effects; | |||
let eLen = effects.length; | |||
for (let i = 0; i < eLen; i++) { | |||
let effect = effects[i]; | |||
if (effect.type === effectName) { | |||
this.destroyEffect(effect); | |||
this.syncRemove(effect.id, effect.type, noMsg || effects.noMsg); | |||
effects.splice(i, 1); | |||
return effect; | |||
} | |||
} | |||
removeEffectByType: function (type) { | |||
const effects = this.effects.filter(e => e.type === type); | |||
effects.forEach(e => this.removeEffect(e.id)); | |||
}, | |||
getEffectByType: function (effectType) { | |||
@@ -293,23 +290,22 @@ module.exports = { | |||
for (let i = 0; i < eLen; i++) { | |||
let e = effects[i]; | |||
if (e.ttl > 0) { | |||
if (e.ttl > 0) | |||
e.ttl--; | |||
if (e.ttl === 0) | |||
e.destroyed = true; | |||
} | |||
else if (e.ttl === 0) | |||
e.destroyed = true; | |||
if (e.update) | |||
e.update(); | |||
if (e.destroyed) { | |||
this.destroyEffect(e); | |||
this.syncRemove(e.id); | |||
effects.splice(i, 1); | |||
eLen--; | |||
i--; | |||
this.destroyEffect(e); | |||
this.syncRemove(e.id, e.type, e.noMsg); | |||
} | |||
} | |||
@@ -549,7 +549,7 @@ module.exports = { | |||
die: function () { | |||
this.obj.stats.takeDamage({ | |||
amount: 99999 | |||
amount: 20000000 | |||
}, 1, this.obj); | |||
}, | |||
@@ -272,19 +272,16 @@ module.exports = { | |||
this.obj.syncer.setArray(true, 'inventory', 'getItems', item); | |||
}, | |||
stashItem: function (id) { | |||
let item = this.findItem(id); | |||
stashItem: async function (id) { | |||
const item = this.findItem(id); | |||
if (!item || item.quest || item.noStash) | |||
return; | |||
delete item.pos; | |||
let stash = this.obj.stash; | |||
if (!stash.active) | |||
return; | |||
let clonedItem = extend({}, item); | |||
const success = stash.deposit(clonedItem); | |||
const stash = this.obj.stash; | |||
const clonedItem = extend({}, item); | |||
const success = await stash.deposit(clonedItem); | |||
if (!success) | |||
return; | |||
@@ -98,6 +98,8 @@ module.exports = { | |||
patrol: null, | |||
patrolTargetNode: 0, | |||
needLos: null, | |||
init: function (blueprint) { | |||
this.physics = this.obj.instance.physics; | |||
@@ -111,6 +113,7 @@ module.exports = { | |||
this.maxChaseDistance = blueprint.maxChaseDistance; | |||
}, | |||
/* eslint-disable-next-line max-lines-per-function */ | |||
update: function () { | |||
let obj = this.obj; | |||
@@ -121,20 +124,22 @@ module.exports = { | |||
//Have we reached home? | |||
if (this.goHome) { | |||
let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y)); | |||
if (!distanceFromHome) | |||
if (!distanceFromHome) { | |||
this.goHome = false; | |||
} | |||
//Are we too far from home? | |||
if ((!this.goHome) && (!obj.follower) && (target)) { | |||
if (!this.canChase(target)) { | |||
obj.clearQueue(); | |||
obj.aggro.unAggro(target); | |||
target = obj.aggro.getHighest(); | |||
obj.spellbook.resetRotation(); | |||
} | |||
} | |||
if (!this.goHome) { | |||
//Are we too far from home? | |||
if (!obj.follower && target) { | |||
if (!this.canChase(target)) { | |||
obj.clearQueue(); | |||
obj.aggro.unAggro(target); | |||
target = obj.aggro.getHighest(); | |||
} | |||
} | |||
if ((target) && (target !== obj) && ((!obj.follower) || (obj.follower.master !== target))) { | |||
//If we just started attacking, patrols need to know where home is | |||
if (!this.target && this.patrol) { | |||
@@ -145,10 +150,11 @@ module.exports = { | |||
//Are we in fight mode? | |||
this.fight(target); | |||
return; | |||
} else if ((!target) && (this.target)) { | |||
} else if (!target && this.target) { | |||
//Is fight mode over? | |||
this.target = null; | |||
obj.clearQueue(); | |||
obj.spellbook.resetRotation(); | |||
if (canPathHome(this)) | |||
this.goHome = true; | |||
@@ -251,8 +257,8 @@ module.exports = { | |||
let ty = ~~target.y; | |||
let distance = max(abs(x - tx), abs(y - ty)); | |||
let furthestAttackRange = obj.spellbook.getFurthestRange(null, true); | |||
let furthestStayRange = obj.spellbook.getFurthestRange(null, false); | |||
let furthestAttackRange = obj.spellbook.getFurthestRange(target, true); | |||
let furthestStayRange = obj.spellbook.getFurthestRange(target, false); | |||
let doesCollide = null; | |||
let hasLos = null; | |||
@@ -263,18 +269,20 @@ module.exports = { | |||
hasLos = this.physics.hasLos(x, y, tx, ty); | |||
//Maybe we don't care if the mob has LoS | |||
if (hasLos || this.needLos === false) { | |||
if (((obj.follower) && (obj.follower.master.player)) || (rnd() < 0.65)) { | |||
let spell = obj.spellbook.getRandomSpell(target); | |||
let success = obj.spellbook.cast({ | |||
spell: spell, | |||
target: target | |||
}); | |||
//null means we don't have LoS | |||
if (success !== null) | |||
return; | |||
hasLos = false; | |||
} else | |||
let spell = obj.spellbook.getSpellToCast(target); | |||
if (!spell) | |||
return; | |||
let success = obj.spellbook.cast({ | |||
spell: spell.id, | |||
target | |||
}); | |||
//null means we don't have LoS | |||
if (success !== null) | |||
return; | |||
hasLos = false; | |||
} | |||
} | |||
} else if (furthestAttackRange === 0) { | |||
@@ -70,9 +70,7 @@ module.exports = { | |||
faction: 'players' | |||
}); | |||
obj.addComponent('gatherer'); | |||
obj.addComponent('stash', { | |||
items: character.stash | |||
}); | |||
obj.addComponent('stash'); | |||
let blueprintEffects = character.components.find(c => c.type === 'effects') || {}; | |||
if (blueprintEffects.effects) { | |||
@@ -235,7 +233,7 @@ module.exports = { | |||
obj.instance.physics.addObject(obj, obj.x, obj.y); | |||
obj.instance.syncer.queue('onRespawn', { | |||
obj.instance.syncer.queue('teleportToPosition', { | |||
x: obj.x, | |||
y: obj.y | |||
}, [obj.serverId]); | |||
@@ -276,11 +274,5 @@ module.exports = { | |||
msg.data.data.callbackId = atlas.registerCallback(msg.callback); | |||
atlas.performAction(this.obj, msg.data); | |||
}, | |||
notifyServerUiReady: function () { | |||
this.obj.instance.eventEmitter.emit('onPlayerUiReady', { | |||
obj: this.obj | |||
}); | |||
} | |||
}; |
@@ -16,18 +16,20 @@ module.exports = { | |||
this.patronLevel = ~~blueprint.patron; | |||
}, | |||
collisionEnter: async function (obj) { | |||
collisionEnter: function (obj) { | |||
if (!obj.player) | |||
return; | |||
const { toZone: zoneName, toPos, toRelativePos } = this; | |||
(async () => { | |||
const { toZone: zoneName, toPos, toRelativePos } = this; | |||
await sendObjToZone({ | |||
obj, | |||
invokingObj: this, | |||
zoneName, | |||
toPos, | |||
toRelativePos | |||
}); | |||
await sendObjToZone({ | |||
obj, | |||
invokingObj: this, | |||
zoneName, | |||
toPos, | |||
toRelativePos | |||
}); | |||
})(); | |||
} | |||
}; |
@@ -1,9 +1,19 @@ | |||
const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos }) => { | |||
const { serverId, instance: { physics, syncer: globalSyncer } } = obj; | |||
const fixPosition = (obj, toPos, toRelativePos, invokingObj) => { | |||
if (toPos) { | |||
obj.x = toPos.x; | |||
obj.y = toPos.y; | |||
} else if (toRelativePos) { | |||
obj.x = invokingObj.obj.x + toRelativePos.x; | |||
obj.y = invokingObj.obj.y + toRelativePos.y; | |||
} | |||
}; | |||
globalSyncer.flushForTarget(serverId); | |||
const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos }) => { | |||
const { serverId, instance: { syncer: globalSyncer, physics } } = obj; | |||
if (obj.zoneName === zoneName) { | |||
globalSyncer.flushForTarget(serverId); | |||
physics.removeObject(obj, obj.x, obj.y); | |||
if (toRelativePos) { | |||
@@ -18,7 +28,7 @@ const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos | |||
physics.addObject(obj, obj.x, obj.y); | |||
globalSyncer.queue('onRespawn', { | |||
globalSyncer.queue('teleportToPosition', { | |||
x: obj.x, | |||
y: obj.y | |||
}, [obj.serverId]); | |||
@@ -26,28 +36,41 @@ const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos | |||
return; | |||
} | |||
obj.fireEvent('beforeRezone'); | |||
obj.destroyed = true; | |||
//We set this before saving so that objects aren't saved ON portals | |||
obj.zoneName = zoneName; | |||
fixPosition(obj, toPos, toRelativePos, invokingObj); | |||
//Destroy, flush events and notify other objects | |||
globalSyncer.processDestroyedObject(obj); | |||
await obj.auth.doSave(); | |||
//We have to do this again. This is because onCollisionEnter in portal is not blocking (even though it is async) | |||
// So physics will carry on and allow the obj to move onto the next tile (changing the position while we save above) | |||
fixPosition(obj, toPos, toRelativePos, invokingObj); | |||
//Test code, remove later | |||
Object.entries(globalSyncer.buffer).forEach(([k, v]) => { | |||
v.forEach(e => { | |||
if (e.to.includes(serverId)) { | |||
/* eslint-disable-next-line */ | |||
console.log('Found event', k, 'for rezoning object'); | |||
} | |||
}); | |||
}); | |||
const simpleObj = obj.getSimple(true, false, true); | |||
simpleObj.destroyed = false; | |||
simpleObj.forceDestroy = false; | |||
if (toPos) { | |||
simpleObj.x = toPos.x; | |||
simpleObj.y = toPos.y; | |||
} else if (toRelativePos) { | |||
simpleObj.x = invokingObj.obj.x + toRelativePos.x; | |||
simpleObj.y = invokingObj.obj.y + toRelativePos.y; | |||
} | |||
rezoneManager.stageRezone(simpleObj, zoneName); | |||
process.send({ | |||
method: 'rezone', | |||
id: obj.serverId, | |||
args: { | |||
obj: simpleObj, | |||
newZone: zoneName | |||
method: 'events', | |||
data: { | |||
rezoneStart: [{ | |||
obj: { msg: {} }, | |||
to: [serverId] | |||
}] | |||
} | |||
}); | |||
}; | |||
@@ -86,7 +86,7 @@ module.exports = { | |||
return; | |||
let source = cons.players.find(c => c.id === sourceId); | |||
if (!source) | |||
if (!source || !source.social) | |||
return; | |||
source.social.sendMessage('invite sent', 'color-yellowB'); | |||
@@ -3,6 +3,9 @@ const sendObjToZone = require('../portal/sendObjToZone'); | |||
module.exports = (cpnSocial, targetZone) => { | |||
const { obj } = cpnSocial; | |||
if (obj.zoneName === targetZone) | |||
return; | |||
sendObjToZone({ | |||
obj, | |||
zoneName: targetZone | |||
@@ -3,6 +3,11 @@ let animations = require('../config/animations'); | |||
let playerSpells = require('../config/spells'); | |||
let playerSpellsConfig = require('../config/spellsConfig'); | |||
//Helpers | |||
const rotationManager = require('./spellbook/rotationManager'); | |||
//Component | |||
module.exports = { | |||
type: 'spellbook', | |||
@@ -16,6 +21,8 @@ module.exports = { | |||
callbacks: [], | |||
rotation: null, | |||
init: function (blueprint) { | |||
this.objects = this.obj.instance.objects; | |||
this.physics = this.obj.instance.physics; | |||
@@ -24,7 +31,22 @@ module.exports = { | |||
(blueprint.spells || []).forEach(s => this.addSpell(s, -1)); | |||
if (blueprint.rotation) { | |||
const { duration, spells } = blueprint.rotation; | |||
this.rotation = { | |||
currentTick: 0, | |||
duration, | |||
spells | |||
}; | |||
} | |||
delete blueprint.spells; | |||
//External helpers that should form part of the component | |||
this.getSpellToCast = rotationManager.getSpellToCast.bind(null, this); | |||
this.getFurthestRange = rotationManager.getFurthestRange.bind(null, this); | |||
this.resetRotation = rotationManager.resetRotation.bind(null, this); | |||
}, | |||
transfer: function () { | |||
@@ -243,17 +265,6 @@ module.exports = { | |||
}); | |||
}, | |||
getRandomSpell: function (target) { | |||
const valid = this.spells.filter(s => { | |||
return (!s.selfCast && !s.procCast && !s.castOnDeath && s.canCast(target)); | |||
}); | |||
if (!valid.length) | |||
return null; | |||
return valid[~~(Math.random() * valid.length)].id; | |||
}, | |||
getTarget: function (spell, action) { | |||
let target = action.target; | |||
@@ -289,7 +300,7 @@ module.exports = { | |||
} | |||
} | |||
if (spell.spellType === 'buff') { | |||
if (spell.spellType === 'buff' || spell.spellType === 'heal') { | |||
if (this.obj.aggro.faction !== target.aggro.faction) | |||
return; | |||
} else if (target.aggro && !this.obj.aggro.canAttack(target)) { | |||
@@ -339,6 +350,7 @@ module.exports = { | |||
return false; | |||
action.target = this.getTarget(spell, action); | |||
//If a target has become nonSelectable, we need to stop attacks that are queued/auto | |||
if (!action.target || action.target.nonSelectable) | |||
return false; | |||
@@ -445,25 +457,6 @@ module.exports = { | |||
return this.closestRange; | |||
}, | |||
getFurthestRange: function (spellNum, checkCanCast) { | |||
if (spellNum) | |||
return this.spells[spellNum].range; | |||
let spells = this.spells; | |||
let sLen = spells.length; | |||
let furthest = 0; | |||
for (let i = 0; i < sLen; i++) { | |||
let spell = spells[i]; | |||
if (spell.procCast || spell.castOnDeath) | |||
continue; | |||
if (spell.range > furthest && (!checkCanCast || spell.canCast())) | |||
furthest = spell.range; | |||
} | |||
return furthest; | |||
}, | |||
getCooldowns: function () { | |||
let cds = []; | |||
this.spells.forEach( | |||
@@ -480,6 +473,9 @@ module.exports = { | |||
let didCast = false; | |||
const isCasting = this.isCasting(); | |||
if (this.rotation) | |||
rotationManager.tick(this); | |||
this.spells.forEach(s => { | |||
let auto = s.autoActive; | |||
if (auto) { | |||
@@ -0,0 +1,131 @@ | |||
const getDefaultRotationSpell = rotationSpells => { | |||
const spells = rotationSpells.filter(s => !s.atRotationTicks); | |||
if (!spells.length) | |||
return; | |||
if (spells.length === 1) | |||
return spells[0]; | |||
const randomSpell = spells[~~(Math.random() * spells.length)]; | |||
return randomSpell; | |||
}; | |||
//Mobs that define rotations (normally bosses) use this method to determine their spell choices | |||
const getRotationSpell = (source, target) => { | |||
const { spells, rotation: { currentTick, spells: rotationSpells } } = source; | |||
//Find spell matching current tick | |||
let rotationEntry = rotationSpells.find(s => s.atRotationTicks?.includes(currentTick)); | |||
if (!rotationEntry) | |||
rotationEntry = getDefaultRotationSpell(rotationSpells); | |||
if (!rotationEntry) | |||
return; | |||
//Don't cast anything | |||
if (rotationEntry.spellIndex === -1) | |||
return; | |||
const useSpell = spells[rotationEntry.spellIndex]; | |||
//Todo: We should set cdMax and manaCost to 0 of rotation spells (unless there's a mana drain mechanic) | |||
// later and we want to allow that on bosses | |||
useSpell.cd = 0; | |||
useSpell.manaCost = 0; | |||
if (!useSpell.selfCast && !useSpell.canCast(target)) | |||
return getDefaultRotationSpell(rotationSpells); | |||
return useSpell; | |||
}; | |||
//Mobs without rune rotations (normally the case) simple select any random spell that is valid | |||
const getRandomSpell = (source, target) => { | |||
const valid = source.spells.filter(s => { | |||
return (!s.selfCast && !s.procCast && !s.castOnDeath && s.canCast(target)); | |||
}); | |||
if (!valid.length) | |||
return null; | |||
return valid[~~(Math.random() * valid.length)]; | |||
}; | |||
const getSpellToCast = (source, target) => { | |||
if (source.rotation) | |||
return getRotationSpell(source, target); | |||
const { obj: { follower } } = source; | |||
//Mobs don't cast all the time but player followers do | |||
if (!follower?.master?.player && Math.random() >= 0.65) | |||
return; | |||
return getRandomSpell(source, target); | |||
}; | |||
const tick = source => { | |||
if (!source.obj.aggro.isInCombat()) | |||
return; | |||
const { rotation } = source; | |||
rotation.currentTick++; | |||
if (rotation.currentTick === rotation.duration) | |||
rotation.currentTick = 1; | |||
}; | |||
//Gets the range we need to be at to cast a specific rotation spell | |||
const getFurthestRangeRotation = (source, target, checkCanCast) => { | |||
const spell = getRotationSpell(source, target); | |||
if (!spell) | |||
return 0; | |||
return spell.range; | |||
}; | |||
/* | |||
This is used by mobs when in combat mode, | |||
* When checkCanCast is true, we want to see if we can cast right now | |||
* When checkCanCast is false, we want to see if there is a spell we could cast in the near future | |||
-> This could be a spell that is currently on cooldown, or that the mob has insufficient mana for | |||
---> Even though mobs don't need mana for spells at the moment | |||
-> Ultimately, this means the mob should not move, just wait | |||
*/ | |||
const getFurthestRange = (source, target, checkCanCast) => { | |||
const { spells, rotation } = source; | |||
if (rotation) | |||
return getFurthestRangeRotation(source, target, checkCanCast); | |||
let sLen = spells.length; | |||
let furthest = 0; | |||
for (let i = 0; i < sLen; i++) { | |||
let spell = spells[i]; | |||
if (spell.procCast || spell.castOnDeath) | |||
continue; | |||
if (spell.range > furthest && (!checkCanCast || spell.canCast())) | |||
furthest = spell.range; | |||
} | |||
return furthest; | |||
}; | |||
const resetRotation = source => { | |||
if (!source.rotation) | |||
return; | |||
source.rotation.currentTick = 0; | |||
}; | |||
module.exports = { | |||
tick, | |||
resetRotation, | |||
getSpellToCast, | |||
getFurthestRange | |||
}; |
@@ -1,127 +1,138 @@ | |||
//System | |||
const eventEmitter = require('../misc/events'); | |||
//Helpers | |||
const fixes = require('../fixes/fixes'); | |||
const cpnInventory = require('./inventory'); | |||
const { isItemStackable } = require('./inventory/helpers'); | |||
//Config | |||
const maxItemsBase = 50; | |||
//Component | |||
module.exports = { | |||
type: 'stash', | |||
active: false, | |||
items: [], | |||
items: null, | |||
changed: false, | |||
maxItems: maxItemsBase, | |||
init: function (blueprint) { | |||
let items = blueprint.items || []; | |||
let iLen = items.length; | |||
for (let i = 0; i < iLen; i++) | |||
this.getItem(items[i]); | |||
init: function (blueprint) {}, | |||
getItemsFromDb: async function () { | |||
const { obj } = this; | |||
this.items = await io.getAsync({ | |||
key: obj.account, | |||
table: 'stash', | |||
isArray: true, | |||
clean: true | |||
}); | |||
delete blueprint.items; | |||
fixes.fixStash(this.items); | |||
this.blueprint = blueprint; | |||
await eventEmitter.emit('onAfterGetStash', { | |||
obj: obj, | |||
stash: this.items | |||
}); | |||
}, | |||
getItem: function (item) { | |||
//Material? | |||
let exists = false; | |||
const { items } = this; | |||
if (isItemStackable(item)) { | |||
let existItem = this.items.find(i => i.name === item.name); | |||
const existItem = items.find(i => i.name === item.name); | |||
if (existItem) { | |||
exists = true; | |||
if (!existItem.quantity) | |||
existItem.quantity = 1; | |||
existItem.quantity += (item.quantity || 1); | |||
existItem.quantity += (+item.quantity || 1); | |||
//We modify the old object because it gets sent to the client | |||
item.id = existItem.id; | |||
item.quantity = existItem.quantity; | |||
item = existItem; | |||
return; | |||
} | |||
} | |||
//Get next id | |||
if (!exists) { | |||
let id = 0; | |||
let items = this.items; | |||
let iLen = items.length; | |||
for (let i = 0; i < iLen; i++) { | |||
let fItem = items[i]; | |||
if (fItem.id >= id) | |||
id = fItem.id + 1; | |||
} | |||
item.id = id; | |||
} | |||
let id = 0; | |||
items.forEach(i => { | |||
if (i.id >= id) | |||
id = i.id + 1; | |||
}); | |||
item.id = id; | |||
if (!exists) | |||
this.items.push(item); | |||
items.push(item); | |||
}, | |||
deposit: function (item) { | |||
if (!this.active) | |||
deposit: async function (item) { | |||
if (!this.items) | |||
await this.getItemsFromDb(); | |||
const { active, items, maxItems, obj } = this; | |||
if (!active) | |||
return; | |||
else if (this.items.length >= this.maxItems) { | |||
let isStackable = this.items.some(stashedItem => item.name === stashedItem.name && (isItemStackable(stashedItem))); | |||
else if (items.length >= maxItems) { | |||
const isStackable = items.some(stashedItem => item.name === stashedItem.name && isItemStackable(stashedItem)); | |||
if (!isStackable) { | |||
const message = 'You do not have room in your stash to deposit that item'; | |||
this.obj.social.notifySelf({ message }); | |||
obj.social.notifySelf({ message }); | |||
return; | |||
} | |||
} | |||
this.getItem(item); | |||
this.obj.syncer.setArray(true, 'stash', 'getItems', item); | |||
this.changed = true; | |||
return true; | |||
}, | |||
destroyItem: function (id) { | |||
let item = this.items.find(i => i.id === id); | |||
if (!item) | |||
return; | |||
this.getItem(item); | |||
this.items.spliceWhere(i => i === item); | |||
const sendItem = cpnInventory.simplifyItem.call({ obj: {} }, item); | |||
this.obj.syncer.setArray(true, 'stash', 'destroyItems', id); | |||
obj.instance.syncer.queue('onAddStashItems', [sendItem], [obj.serverId]); | |||
this.changed = true; | |||
return true; | |||
}, | |||
withdraw: function (id) { | |||
if (!this.active) | |||
const { active, items, obj } = this; | |||
if (!active) | |||
return; | |||
let item = this.items.find(i => i.id === id); | |||
let item = items.find(i => i.id === id); | |||
if (!item) | |||
return; | |||
else if (!this.obj.inventory.hasSpace(item)) { | |||
else if (!obj.inventory.hasSpace(item)) { | |||
const message = 'You do not have room in your inventory to withdraw that item'; | |||
this.obj.social.notifySelf({ message }); | |||
obj.social.notifySelf({ message }); | |||
return; | |||
} | |||
this.obj.inventory.getItem(item); | |||
this.items.spliceWhere(i => i === item); | |||
this.changed = true; | |||
this.obj.syncer.setArray(true, 'stash', 'destroyItems', id); | |||
obj.inventory.getItem(item); | |||
items.spliceWhere(i => i === item); | |||
this.changed = true; | |||
obj.instance.syncer.queue('onRemoveStashItems', [id], [obj.serverId]); | |||
}, | |||
setActive: function (active) { | |||
let obj = this.obj; | |||
const { obj } = this; | |||
this.active = active; | |||
obj.syncer.set(true, 'stash', 'active', this.active); | |||
const actionType = active ? 'addActions' : 'removeActions'; | |||
obj.syncer.setArray(true, 'serverActions', actionType, { | |||
id: 'openStash', | |||
key: 'u', | |||
action: { | |||
targetId: obj.id, | |||
@@ -130,34 +141,53 @@ module.exports = { | |||
} | |||
}); | |||
if (!this.active) | |||
return; | |||
let msg = 'Press U to access your Shared Stash'; | |||
this.obj.instance.syncer.queue('onGetAnnouncement', { | |||
src: this.obj.id, | |||
obj.instance.syncer.queue('onGetAnnouncement', { | |||
src: obj.id, | |||
msg: msg | |||
}, [obj.serverId]); | |||
if (this.active && this.items.length > this.maxItems) { | |||
const message = `You have more than ${this.maxItems} items in your stash. In the future, these items will be lost.`; | |||
obj.social.notifySelf({ message }); | |||
} | |||
}, | |||
open: function () { | |||
const { active, obj } = this; | |||
open: async function () { | |||
if (!this.items) | |||
await this.getItemsFromDb(); | |||
if (active) | |||
obj.instance.syncer.queue('onOpenStash', {}, [obj.serverId]); | |||
const { obj, active, maxItems, items } = this; | |||
if (!active) | |||
return; | |||
const sendItems = items.map(i => cpnInventory.simplifyItem.call({ obj: {} }, i)); | |||
const msg = { | |||
maxItems, | |||
items: sendItems | |||
}; | |||
obj.instance.syncer.queue('onOpenStash', msg, [obj.serverId]); | |||
if (items.length > maxItems) { | |||
const message = `You have more than ${maxItems} items in your stash. In the future, these items will be lost.`; | |||
obj.social.notifySelf({ message }); | |||
} | |||
}, | |||
simplify: function (self) { | |||
if (!self) | |||
return null; | |||
return { type: 'stash' }; | |||
}, | |||
simplifyTransfer: function () { | |||
const { type, items } = this; | |||
return { | |||
type: 'stash', | |||
active: this.active, | |||
items: this.items, | |||
maxItems: this.maxItems | |||
type, | |||
items | |||
}; | |||
}, | |||
@@ -629,7 +629,8 @@ module.exports = { | |||
source: source.id, | |||
heal: true, | |||
amount: amount, | |||
crit: heal.crit | |||
crit: heal.crit, | |||
element: heal.element | |||
}, recipients); | |||
} | |||
@@ -776,6 +777,9 @@ module.exports = { | |||
}, | |||
afterDealDamage: function (damageEvent, target) { | |||
if (damageEvent.element) | |||
return; | |||
const { obj, values: { lifeOnHit } } = this; | |||
if (target === obj || !lifeOnHit) | |||
@@ -44,7 +44,8 @@ const config = { | |||
'images/tiles.png': 8, | |||
'images/walls.png': 8, | |||
'images/objects.png': 8, | |||
'images/mobs.png': 8 | |||
'images/mobs.png': 8, | |||
'images/characters.png': 8 | |||
}, | |||
blockingTileIndices: [ | |||
6, 7, 54, 55, 62, 63, 154, 189, 190, 192, 193, 194, 195, 196, 197 | |||
@@ -147,7 +148,7 @@ const config = { | |||
//Table Sides | |||
103, 110, 118, 126, | |||
//Wall-mounted plants | |||
120, 122, 140, | |||
120, 121, | |||
//Ship oars | |||
140, 143, | |||
//Ship Cannons | |||
@@ -173,7 +174,7 @@ const config = { | |||
'party', | |||
'help', | |||
'dialogue', | |||
'buffs', | |||
'effects', | |||
'tooltips', | |||
'tooltipInfo', | |||
'tooltipItem', | |||
@@ -221,6 +222,11 @@ module.exports = { | |||
}); | |||
}); | |||
config.clientComponents.push({ | |||
extends: 'effects', | |||
path: 'server/clientComponents/effects/auras.js' | |||
}); | |||
events.emit('onBeforeGetClientConfig', config); | |||
//Deprecated | |||
@@ -2,7 +2,8 @@ module.exports = { | |||
type: 'stunned', | |||
init: function () { | |||
this.obj.spellbook.stopCasting(); | |||
if (this.obj.spellbook) | |||
this.obj.spellbook.stopCasting(); | |||
}, | |||
events: { | |||
@@ -1,4 +1,15 @@ | |||
module.exports = { | |||
syncExtend: function (data) { | |||
let effects = this.obj.effects; | |||
effects.syncExtend(this.id, data); | |||
}, | |||
isFirstOfType: function () { | |||
let effects = this.obj.effects; | |||
let firstOfType = effects.find(f => f.type === this.type); | |||
return (firstOfType.id === this); | |||
}, | |||
save: function () { | |||
if (!this.persist) | |||
return null; | |||
@@ -19,6 +30,9 @@ module.exports = { | |||
}, | |||
simplify: function () { | |||
return this.type; | |||
return { | |||
id: this.id, | |||
type: this.type | |||
}; | |||
} | |||
}; |
@@ -1,5 +1,5 @@ | |||
module.exports = { | |||
version: '0.10.5', | |||
version: '0.10.6', | |||
port: 4000, | |||
startupMessage: 'Server: ready', | |||
defaultZone: 'fjolarok', | |||
@@ -18,11 +18,6 @@ const config = { | |||
sprite: [2, 0], | |||
defaultSpirit: 'bear', | |||
default: true | |||
}, | |||
//Faction Skins | |||
'gaekatlan-druid': { | |||
name: 'Gaekatlan Druid', | |||
sprite: [0, 1] | |||
} | |||
}; | |||
@@ -212,6 +212,57 @@ let spells = [{ | |||
randomSpeed: true, | |||
chance: 0.02 | |||
} | |||
}, { | |||
name: 'Healing Touch', | |||
description: 'Restore health to a friendly target.', | |||
type: 'singleTargetHeal', | |||
spellType: 'heal', | |||
icon: [0, 3], | |||
animation: 'raiseStaff', | |||
particles: { | |||
scale: { | |||
start: { | |||
min: 6, | |||
max: 16 | |||
}, | |||
end: { | |||
min: 0, | |||
max: 4 | |||
} | |||
}, | |||
speed: { | |||
start: { | |||
min: 2, | |||
max: 12 | |||
}, | |||
end: { | |||
min: 0, | |||
max: 4 | |||
} | |||
}, | |||
lifetime: { | |||
min: 1, | |||
max: 3 | |||
}, | |||
alpha: { | |||
start: 0.45, | |||
end: 0 | |||
}, | |||
color: { | |||
start: ['ffeb38', 'fcfcfc'], | |||
end: ['fcfcfc', 'faac45'] | |||
}, | |||
spawnType: 'circle', | |||
spawnCircle: { | |||
x: 0, | |||
y: 0, | |||
r: 12 | |||
}, | |||
randomScale: true, | |||
randomColor: true, | |||
randomSpeed: true, | |||
chance: 0.02 | |||
} | |||
}, { | |||
name: 'Holy Vengeance', | |||
description: 'Grants holy vengeance to a friendly target. For the duration of the effect, dealing damage will also heal the attacker.', | |||
@@ -69,7 +69,7 @@ module.exports = { | |||
if (distance > range) { | |||
if (effect) { | |||
delete effects[m]; | |||
obj.effects.removeEffect(effect); | |||
obj.effects.removeEffect(effect.id); | |||
} | |||
return; | |||
@@ -85,8 +85,7 @@ module.exports = { | |||
type: this.effect, | |||
amount: amount, | |||
caster: this.obj, | |||
ttl: -1, | |||
new: true | |||
ttl: -1 | |||
}); | |||
}); | |||
@@ -96,7 +95,7 @@ module.exports = { | |||
delete effects[serverId]; | |||
const obj = objects.find(f => ~~f.serverId === ~~serverId); | |||
if (obj) | |||
obj.effects.removeEffect(effect); | |||
obj.effects.removeEffect(effect.id); | |||
} | |||
}); | |||
}, | |||
@@ -117,7 +116,7 @@ module.exports = { | |||
return; | |||
} | |||
obj.effects.removeEffect(effect); | |||
obj.effects.removeEffect(effect.id); | |||
delete effects[m]; | |||
}, this); | |||
} | |||
@@ -55,19 +55,10 @@ module.exports = { | |||
type: 'stunned' | |||
}); | |||
if (targetEffect) { | |||
this.obj.instance.syncer.queue('onGetDamage', { | |||
id: target.id, | |||
event: true, | |||
text: 'stunned' | |||
}, -1); | |||
} | |||
let selfEffect = this.obj.effects.addEffect({ | |||
type: 'stunned', | |||
noMsg: true, | |||
force: true, | |||
new: true | |||
silent: true, | |||
force: true | |||
}); | |||
const moveAnimationEffect = { | |||
@@ -115,7 +106,7 @@ module.exports = { | |||
obj.instance.physics.addObject(obj, obj.x, obj.y); | |||
obj.effects.removeEffect(selfEffect, true); | |||
obj.effects.removeEffect(selfEffect.id); | |||
this.obj.aggro.move(); | |||
@@ -115,8 +115,7 @@ module.exports = { | |||
const targetEffect = m.effects.addEffect({ | |||
type: 'stunned', | |||
noMsg: true, | |||
new: true | |||
silent: true | |||
}); | |||
//If targetEffect is undefined, it means that the target has become resistant | |||
@@ -147,7 +146,7 @@ module.exports = { | |||
}, | |||
endEffect: function (target, targetPos, targetEffect) { | |||
target.effects.removeEffect(targetEffect, true); | |||
target.effects.removeEffect(targetEffect.id); | |||
target.instance.physics.removeObject(target, target.x, target.y); | |||
@@ -54,19 +54,11 @@ module.exports = { | |||
if (this.obj.destroyed) | |||
return; | |||
let targetEffect = target.effects.addEffect({ | |||
target.effects.addEffect({ | |||
type: 'slowed', | |||
ttl: this.freezeDuration | |||
}); | |||
if (targetEffect) { | |||
this.obj.instance.syncer.queue('onGetDamage', { | |||
id: target.id, | |||
event: true, | |||
text: 'slowed' | |||
}, -1); | |||
} | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, this.obj); | |||
} | |||
@@ -36,6 +36,6 @@ module.exports = { | |||
let obj = this.obj; | |||
obj.effects.removeEffect(selfEffect); | |||
obj.effects.removeEffect(selfEffect.id); | |||
} | |||
}; |
@@ -0,0 +1,41 @@ | |||
module.exports = { | |||
type: 'singleTargetHeal', | |||
cdMax: 20, | |||
manaCost: 0, | |||
range: 9, | |||
healing: 1, | |||
needLos: true, | |||
targetFriendly: true, | |||
spellType: 'heal', | |||
particleDuration: 10, | |||
cast: function (action) { | |||
const target = action.target; | |||
const { x, y } = target; | |||
const amount = this.getDamage(target, true); | |||
target.stats.getHp(amount, this.obj); | |||
const effect = { | |||
x, | |||
y, | |||
components: [{ | |||
type: 'particles', | |||
//This ttl is in frames (each frame is roughly 1000 / 60 ms) | |||
ttl: (1000 / 60) * this.particleDuration, | |||
destroyObject: true, | |||
blueprint: this.particles | |||
}] | |||
}; | |||
this.obj.instance.syncer.queue('onGetObject', effect, -1); | |||
this.sendBump(target); | |||
return true; | |||
} | |||
}; |
@@ -8,6 +8,8 @@ module.exports = { | |||
targetGround: true, | |||
effect: null, | |||
cast: function (action) { | |||
//Clear Aggro | |||
this.obj.aggro.die(); | |||
@@ -15,10 +17,10 @@ module.exports = { | |||
let ttl = this.duration * consts.tickTime; | |||
let endCallback = this.queueCallback(this.endEffect.bind(this), ttl - 50); | |||
this.obj.effects.addEffect({ | |||
this.effect = this.obj.effects.addEffect({ | |||
type: 'stealth', | |||
endCallback: endCallback | |||
}); | |||
}); | |||
return true; | |||
}, | |||
@@ -28,7 +30,7 @@ module.exports = { | |||
let obj = this.obj; | |||
obj.effects.removeEffectByName('stealth'); | |||
obj.effects.removeEffect(this.effect.id); | |||
this.obj.aggro.move(); | |||
} | |||
}; |
@@ -164,7 +164,7 @@ module.exports = { | |||
this.castingEffect = this.obj.effects.addEffect({ | |||
type: 'casting', | |||
noMsg: true | |||
silent: true | |||
}); | |||
this.casting = true; | |||
@@ -76,9 +76,9 @@ let spells = { | |||
statType: 'int', | |||
statMult: 1, | |||
element: 'holy', | |||
cdMax: 5, | |||
cdMax: 15, | |||
castTimeMax: 4, | |||
manaCost: 8, | |||
manaCost: 12, | |||
range: 9, | |||
radius: 3, | |||
random: { | |||
@@ -87,6 +87,19 @@ let spells = { | |||
} | |||
}, | |||
'healing touch': { | |||
statType: 'int', | |||
statMult: 1, | |||
element: 'holy', | |||
cdMax: 5, | |||
castTimeMax: 3, | |||
manaCost: 8, | |||
range: 9, | |||
random: { | |||
healing: [1, 3] | |||
} | |||
}, | |||
slash: { | |||
statType: 'str', | |||
statMult: 1, | |||
@@ -130,7 +143,6 @@ let spells = { | |||
cdMax: 12, | |||
castTimeMax: 2, | |||
manaCost: 7, | |||
noDrop: true, | |||
random: { | |||
i_range: [1, 2.5], | |||
damage: [4, 18] | |||
@@ -156,7 +168,6 @@ let spells = { | |||
castTimeMax: 3, | |||
range: 10, | |||
manaCost: 7, | |||
noDrop: true, | |||
random: { | |||
damage: [8, 35], | |||
i_stunDuration: [4, 7] | |||
@@ -167,11 +178,12 @@ let spells = { | |||
statMult: 1, | |||
manaCost: 14, | |||
needLos: true, | |||
cdMax: 20, | |||
cdMax: 15, | |||
castTimeMax: 0, | |||
range: 9, | |||
isAttack: true, | |||
random: { | |||
damage: [3, 16], | |||
damage: [3, 18], | |||
i_delay: [1, 4] | |||
}, | |||
negativeStats: [ | |||
@@ -219,7 +231,7 @@ let spells = { | |||
auraRange: 9, | |||
effect: 'swiftness', | |||
random: { | |||
chance: [5, 10] | |||
chance: [8, 20] | |||
} | |||
} | |||
@@ -1,6 +1,6 @@ | |||
module.exports = { | |||
init: function (event) { | |||
const { config, rewards, eventManager } = event; | |||
const { config, rewards, eventManager: eManager } = event; | |||
const { name: eventName, rewardSenderName } = config; | |||
@@ -26,7 +26,7 @@ module.exports = { | |||
}); | |||
if ((config.events) && (config.events.afterGiveRewards)) | |||
config.events.afterGiveRewards(eventManager, config); | |||
config.events.afterGiveRewards(eManager, config); | |||
this.end = true; | |||
} | |||
@@ -5,7 +5,8 @@ module.exports = { | |||
allFields: 'please complete all fields', | |||
illegal: 'illegal characters in username', | |||
incorrect: 'invalid username and password', | |||
charExists: 'character name is taken' | |||
charExists: 'character name is taken', | |||
maxUsernameLength: 'username may not be longer than 32 characters' | |||
}, | |||
createCharacter: { | |||
nameLength: 'name must be between 3 and 12 characters' | |||
@@ -126,7 +126,7 @@ module.exports = { | |||
range: 1, | |||
random: { | |||
damage: [4, 14], | |||
healPercent: [10, 30] | |||
healPercent: [2, 15] | |||
} | |||
}; | |||
@@ -277,7 +277,7 @@ module.exports = { | |||
}, | |||
performMove: function (action) { | |||
const { x: xOld, y: yOld, syncer, aggro, mob, instance: { physics } } = this; | |||
const { x: xOld, y: yOld, syncer, aggro, instance: { physics } } = this; | |||
const { maxDistance = 1, force, data } = action; | |||
const { x: xNew, y: yNew } = data; | |||
@@ -309,26 +309,16 @@ module.exports = { | |||
return false; | |||
} | |||
//Don't allow mob overlap during combat | |||
if (mob && mob.target) { | |||
this.x = xNew; | |||
this.y = yNew; | |||
this.x = xNew; | |||
this.y = yNew; | |||
if (physics.addObject(this, xNew, yNew)) | |||
physics.removeObject(this, xOld, yOld); | |||
else { | |||
this.x = xOld; | |||
this.y = yOld; | |||
return false; | |||
} | |||
} else { | |||
if (physics.addObject(this, xNew, yNew, xOld, yOld)) | |||
physics.removeObject(this, xOld, yOld, xNew, yNew); | |||
else { | |||
this.x = xOld; | |||
this.y = yOld; | |||
this.x = xNew; | |||
this.y = yNew; | |||
physics.addObject(this, xNew, yNew, xOld, yOld); | |||
return false; | |||
} | |||
//We can't use xNew and yNew because addObject could have changed the position (like entering a building interior with stairs) | |||
@@ -203,64 +203,87 @@ module.exports = { | |||
return newO; | |||
}, | |||
sendEvent: function (msg) { | |||
let player = this.objects.find(p => p.id === msg.id); | |||
if (!player) | |||
sendEvent: function (msg, { name: sourceZone }) { | |||
const { id, data } = msg; | |||
const player = this.objects.find(p => p.id === id); | |||
if (!player || player.zoneName !== sourceZone) | |||
return; | |||
player.socket.emit('event', { | |||
event: msg.data.event, | |||
data: msg.data.data | |||
event: data.event, | |||
data: data.data | |||
}); | |||
}, | |||
sendEvents: function (msg) { | |||
let players = {}; | |||
let objects = this.objects; | |||
let data = msg.data; | |||
sendEvents: function ({ data }, { name: sourceZone }) { | |||
const { objects } = this; | |||
//Store will contain all events to be sent to players | |||
const store = {}; | |||
for (let e in data) { | |||
let event = data[e]; | |||
let eLen = event.length; | |||
const event = data[e]; | |||
const eLen = event.length; | |||
for (let j = 0; j < eLen; j++) { | |||
let eventEntry = event[j]; | |||
let obj = eventEntry.obj; | |||
if (e !== 'serverModule') { | |||
let to = eventEntry.to; | |||
let toLen = to.length; | |||
for (let i = 0; i < toLen; i++) { | |||
let toId = to[i]; | |||
let player = players[toId]; | |||
if (!player) { | |||
let findPlayer = objects.find(o => o.id === toId); | |||
if (!findPlayer) | |||
continue; | |||
else { | |||
player = (players[toId] = { | |||
socket: findPlayer.socket, | |||
events: {} | |||
}); | |||
} | |||
const eventEntry = event[j]; | |||
const { obj: eventObj, to } = eventEntry; | |||
if (e === 'serverModule') { | |||
const { method, msg } = eventObj; | |||
if (Array.isArray(msg)) | |||
global[eventObj.module][method](...msg); | |||
else | |||
global[eventObj.module][method](msg); | |||
continue; | |||
} | |||
const toLen = to.length; | |||
for (let i = 0; i < toLen; i++) { | |||
const toId = to[i]; | |||
let storeEntry = store[toId]; | |||
if (!storeEntry) { | |||
const playerObj = objects.find(o => o.id === toId); | |||
if (!playerObj || playerObj.zoneName !== sourceZone) { | |||
io.setAsync({ | |||
key: new Date(), | |||
table: 'error', | |||
value: `ignoring ${e}` | |||
}); | |||
continue; | |||
} | |||
let eventList = player.events[e] || (player.events[e] = []); | |||
eventList.push(obj); | |||
store[toId] = { | |||
obj: playerObj, | |||
events: { [e]: [eventObj] } | |||
}; | |||
continue; | |||
} | |||
} else if (obj.msg instanceof Array) | |||
global[obj.module][obj.method](...obj.msg); | |||
else | |||
global[obj.module][obj.method](obj.msg); | |||
if (!storeEntry.events[e]) | |||
storeEntry.events[e] = []; | |||
storeEntry.events[e].push(eventObj); | |||
} | |||
} | |||
} | |||
for (let p in players) { | |||
let player = players[p]; | |||
player.socket.emit('events', player.events); | |||
for (let p in store) { | |||
const { obj: { socket }, events } = store[p]; | |||
socket.emit('events', events); | |||
} | |||
}, | |||
updateObject: async function (msg) { | |||
let player = this.objects.find(p => p.id === msg.serverId); | |||
if (!player) | |||
@@ -324,6 +347,13 @@ module.exports = { | |||
if ((o.update) && (!o.destroyed)) | |||
o.update(); | |||
//When objects are sent to other zones, we destroy them immediately (thhrough sendObjToZone) | |||
if (o.forceDestroy) { | |||
i--; | |||
len--; | |||
continue; | |||
} | |||
if (o.ttl) { | |||
o.ttl--; | |||
if (!o.ttl) | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "isleward_server", | |||
"version": "0.10.5", | |||
"version": "0.10.6", | |||
"description": "isleward", | |||
"dependencies": { | |||
"axios": "^0.22.0", | |||
@@ -11,7 +11,7 @@ | |||
"image-size": "^1.0.0", | |||
"rethinkdbdash": "^2.3.31", | |||
"socket.io": "^4.2.0", | |||
"universal-analytics": "^0.4.23" | |||
"universal-analytics": "^0.5.1" | |||
}, | |||
"devDependencies": { | |||
"babel-eslint": "^10.1.0", | |||
@@ -77,8 +77,11 @@ module.exports = { | |||
(['getCharacterList', 'getCharacter', 'deleteCharacter'].indexOf(msg.method) === -1) | |||
) || | |||
( | |||
(player.dead) && | |||
(msg.data.method !== 'respawn') | |||
player.dead && | |||
!( | |||
(msg.method === 'performAction' && ['respawn'].includes(msg.data.method)) || | |||
(msg.method === 'clientAck') | |||
) | |||
) | |||
) | |||
return; | |||
@@ -18,13 +18,14 @@ const routerConfig = { | |||
wardrobe: ['open', 'apply'], | |||
stats: ['respawn'], | |||
passives: ['tickNode', 'untickNode'], | |||
workbench: ['open', 'craft', 'getRecipe'], | |||
player: ['notifyServerUiReady'] | |||
workbench: ['open', 'craft', 'getRecipe'] | |||
}, | |||
globalAllowed: { | |||
clientConfig: ['getClientConfig'], | |||
leaderboard: ['requestList'], | |||
cons: ['unzone'] | |||
cons: ['unzone'], | |||
rezoneManager: ['clientAck'], | |||
instancer: ['clientAck'] | |||
}, | |||
allowTargetId: { | |||
door: ['lock', 'unlock'], | |||
@@ -128,15 +128,16 @@ module.exports = { | |||
mapList.mapList.filter(m => !m.disabled).forEach(m => this.spawnMap(m)); | |||
}, | |||
spawnMap: function (map) { | |||
let worker = childProcess.fork('./world/worker'); | |||
let thread = { | |||
const worker = childProcess.fork('./world/worker', [map.name]); | |||
const thread = { | |||
id: this.nextId++, | |||
name: map.name, | |||
path: map.path, | |||
worker: worker | |||
worker | |||
}; | |||
let onMessage = this.onMessage.bind(this, thread); | |||
const onMessage = this.onMessage.bind(this, thread); | |||
worker.on('message', function (m) { | |||
onMessage(m); | |||
}); | |||
@@ -174,11 +175,11 @@ module.exports = { | |||
}, | |||
event: function (thread, message) { | |||
objects.sendEvent(message); | |||
objects.sendEvent(message, thread); | |||
}, | |||
events: function (thread, message) { | |||
objects.sendEvents(message); | |||
objects.sendEvents(message, thread); | |||
}, | |||
object: function (thread, message) { | |||
@@ -13,6 +13,9 @@ let eventEmitter = require('../misc/events'); | |||
const mods = require('../misc/mods'); | |||
const transactions = require('../security/transactions'); | |||
//Own helpers | |||
const { stageZoneIn, unstageZoneIn, clientAck } = require('./instancer/handshakes'); | |||
module.exports = { | |||
instances: [], | |||
zoneId: -1, | |||
@@ -65,6 +68,9 @@ module.exports = { | |||
[resourceSpawner, syncer, objects, questBuilder, events].forEach(i => i.init(fakeInstance)); | |||
this.tick(); | |||
this.clientAck = clientAck; | |||
eventEmitter.on('removeObject', unstageZoneIn); | |||
}, | |||
startRegen: function (respawnMap, respawnPos) { | |||
@@ -211,21 +217,24 @@ module.exports = { | |||
obj.spawn = map.spawn; | |||
syncer.queue('onGetMap', map.clientMap, [obj.serverId]); | |||
stageZoneIn(msg); | |||
if (!msg.transfer) | |||
objects.addObject(obj, this.onAddObject.bind(this)); | |||
else { | |||
let o = objects.transferObject(obj); | |||
questBuilder.obtain(o); | |||
eventEmitter.emit('onAfterPlayerEnterZone', o); | |||
} | |||
process.send({ | |||
method: 'events', | |||
data: { | |||
getMap: [{ | |||
obj: map.clientMap, | |||
to: [obj.serverId] | |||
}] | |||
} | |||
}); | |||
}, | |||
//This function fires when the player logs in the first time, not upon rezone | |||
onAddObject: function (obj) { | |||
if (obj.player) { | |||
obj.stats.onLogin(); | |||
eventEmitter.emit('onAfterPlayerEnterZone', obj); | |||
eventEmitter.emit('onAfterPlayerEnterZone', obj, { isTransfer: false }); | |||
} | |||
questBuilder.obtain(obj); | |||
@@ -302,6 +311,10 @@ module.exports = { | |||
return; | |||
} | |||
//We fire this event because even though an object might be destroyed already, | |||
// mods and modules might have staged events/actions we need to clear | |||
eventEmitter.emit('removeObject', { obj: msg.obj }); | |||
let obj = msg.obj; | |||
obj = objects.find(o => o.serverId === obj.id); | |||
if (!obj) { | |||
@@ -0,0 +1,49 @@ | |||
//Local State | |||
const stagedZoneIns = []; | |||
//Methods | |||
//Fired when an object is removed through a socket dc | |||
// We do this because a client might DC during rezone handshake | |||
const unstageZoneIn = msg => { | |||
stagedZoneIns.spliceWhere(s => s.obj.serverId === msg.obj.id); | |||
}; | |||
const stageZoneIn = msg => { | |||
const { serverId } = msg.obj; | |||
stagedZoneIns.spliceWhere(o => o.obj.serverId === serverId); | |||
stagedZoneIns.push(msg); | |||
}; | |||
const doZoneIn = function (staged) { | |||
const { onAddObject, instances: [ { objects, questBuilder, eventEmitter } ] } = instancer; | |||
const { transfer: isTransfer, obj } = staged; | |||
if (!isTransfer) | |||
objects.addObject(obj, onAddObject.bind(instancer)); | |||
else { | |||
let o = objects.transferObject(obj); | |||
questBuilder.obtain(o); | |||
eventEmitter.emit('onAfterPlayerEnterZone', o, { isTransfer }); | |||
} | |||
}; | |||
const clientAck = msg => { | |||
const staged = stagedZoneIns.find(s => s.obj.serverId === msg.sourceId); | |||
if (!staged) | |||
return; | |||
stagedZoneIns.spliceWhere(s => s === staged); | |||
doZoneIn(staged); | |||
}; | |||
//Exports | |||
module.exports = { | |||
unstageZoneIn, | |||
stageZoneIn, | |||
clientAck | |||
}; |
@@ -85,8 +85,13 @@ module.exports = { | |||
try { | |||
chats = require('../' + this.path + '/' + this.name + '/chats'); | |||
} catch (e) {} | |||
if (chats) | |||
this.zone.chats = chats; | |||
if (chats) { | |||
if (this.zone.chats) | |||
extend(this.zone.chats, chats); | |||
else | |||
this.zone.chats = chats; | |||
} | |||
let dialogues = null; | |||
try { | |||
@@ -50,6 +50,8 @@ module.exports = { | |||
if (cpnMob.patrol) | |||
cpnMob.walkDistance = 1; | |||
cpnMob.needLos = blueprint.needLos; | |||
let spells = extend([], blueprint.spells); | |||
spells.forEach(s => { | |||
if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell]) | |||
@@ -179,21 +181,6 @@ module.exports = { | |||
s.dmgMult = s.name ? dmgMult / 3 : dmgMult; | |||
s.statType = preferStat; | |||
s.manaCost = 0; | |||
/*if (mob.name.toLowerCase().includes('stinktooth')) { | |||
mob.stats.values.critChance = 0; | |||
mob.stats.values.attackCritChance = 0; | |||
mob.stats.values.spellCritChance = 0; | |||
const n = mob.name + '-' + s.type; | |||
if (!track[n]) | |||
track[n] = []; | |||
track[n].push(~~s.getDamage(mob, true).amount); | |||
track[n].sort((a, b) => a - b); | |||
console.log(track); | |||
console.log(''); | |||
}*/ | |||
}); | |||
//Hack to disallow low level mobs from having any lifeOnHit | |||
@@ -0,0 +1,55 @@ | |||
//Imports | |||
const eventEmitter = require('../misc/events'); | |||
//Local State | |||
const stagedRezones = []; | |||
//Methods | |||
//Fired when an object is removed through a socket dc | |||
// We do this because a client might DC during rezone handshake | |||
const unstageRezone = msg => { | |||
stagedRezones.spliceWhere(s => s.simplifiedObj.serverId === msg.obj.id); | |||
}; | |||
const stageRezone = (simplifiedObj, targetZone) => { | |||
const { serverId } = simplifiedObj; | |||
stagedRezones.spliceWhere(o => o.simplifiedObj.serverId === serverId); | |||
stagedRezones.push({ simplifiedObj, targetZone }); | |||
}; | |||
const doRezone = stagedRezone => { | |||
const { simplifiedObj, targetZone } = stagedRezone; | |||
process.send({ | |||
method: 'rezone', | |||
id: simplifiedObj.serverId, | |||
args: { | |||
obj: simplifiedObj, | |||
newZone: targetZone | |||
} | |||
}); | |||
}; | |||
const clientAck = msg => { | |||
const staged = stagedRezones.find(s => s.simplifiedObj.serverId === msg.sourceId); | |||
if (!staged) | |||
return; | |||
stagedRezones.spliceWhere(s => s === staged); | |||
doRezone(staged); | |||
}; | |||
const init = () => { | |||
eventEmitter.on('removeObject', unstageRezone); | |||
}; | |||
//Exports | |||
module.exports = { | |||
init, | |||
stageRezone, | |||
clientAck | |||
}; |
@@ -60,7 +60,6 @@ module.exports = { | |||
this.syncer.queue('onGetObject', { | |||
id: obj.id, | |||
performLast: true, | |||
components: [spawnAnimation] | |||
}, -1); | |||
} | |||
@@ -180,6 +180,35 @@ module.exports = { | |||
} | |||
}, | |||
processDestroyedObject: function (obj) { | |||
const { objects, queue } = this; | |||
const { id, serverId } = obj; | |||
obj.destroyed = true; | |||
//We mark forceDestroy to tell objects that we're destroying an object outside of the | |||
// syncer's update method | |||
obj.forceDestroy = true; | |||
const msg = { | |||
id: id, | |||
destroyed: true | |||
}; | |||
objects.removeObject(obj); | |||
this.flushForTarget(serverId); | |||
const fnQueueMsg = queue.bind(this, 'onGetObject'); | |||
//Find any players that have seen this obj | |||
objects | |||
.filter(o => !o.destroyed && o?.player?.hasSeen(id)) | |||
.forEach(o => { | |||
fnQueueMsg(msg, [o.serverId]); | |||
}); | |||
}, | |||
send: function () { | |||
if (!this.dirty) | |||
return; | |||
@@ -5,6 +5,7 @@ global.consts = require('../config/consts'); | |||
global.instancer = require('./instancer'); | |||
global.eventManager = require('../events/events'); | |||
global.clientConfig = require('../config/clientConfig'); | |||
global.rezoneManager = require('./rezoneManager'); | |||
const components = require('../components/components'); | |||
const mods = require('../misc/mods'); | |||
@@ -21,6 +22,8 @@ const itemEffects = require('../items/itemEffects'); | |||
const profanities = require('../misc/profanities'); | |||
const eventEmitter = require('../misc/events'); | |||
instancer.mapName = process.argv[2]; | |||
let onCpnsReady = async function () { | |||
factions.init(); | |||
skins.init(); | |||
@@ -33,6 +36,8 @@ let onCpnsReady = async function () { | |||
recipes.init(); | |||
itemEffects.init(); | |||
profanities.init(); | |||
rezoneManager.init(); | |||
await clientConfig.init(); | |||
process.send({ | |||