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) | if (cpn.type) | ||||
templates.push(tpl); | templates.push(tpl); | ||||
if (cpn.extends) | if (cpn.extends) | ||||
extenders.push(tpl); | |||||
extenders.push({ extends: cpn.extends, tpl }); | |||||
res(); | res(); | ||||
}); | }); | ||||
@@ -49,7 +49,7 @@ define([ | |||||
templates.forEach(t => { | templates.forEach(t => { | ||||
const extensions = extenders.filter(e => e.extends === t.type); | 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.eventList = {}; | ||||
t.hookEvent = hookEvent; | t.hookEvent = hookEvent; | ||||
@@ -1,7 +1,9 @@ | |||||
define([ | define([ | ||||
'js/misc/distanceToPolygon' | |||||
'js/misc/distanceToPolygon', | |||||
'js/system/events' | |||||
], function ( | ], function ( | ||||
distanceToPolygon | |||||
distanceToPolygon, | |||||
events | |||||
) { | ) { | ||||
return { | return { | ||||
grid: null, | grid: null, | ||||
@@ -10,6 +12,8 @@ define([ | |||||
height: 0, | height: 0, | ||||
init: function (collisionMap) { | init: function (collisionMap) { | ||||
events.on('resetPhysics', this.reset.bind(this)); | |||||
this.width = collisionMap.length; | this.width = collisionMap.length; | ||||
this.height = collisionMap[0].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) { | isTileBlocking: function (x, y, mob, obj) { | ||||
if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height)) | if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height)) | ||||
return true; | return true; | ||||
@@ -52,7 +52,7 @@ define([ | |||||
attackSpeed: 'attack speed', | attackSpeed: 'attack speed', | ||||
castSpeed: 'cast speed', | castSpeed: 'cast speed', | ||||
lifeOnHit: 'life gained on hit', | |||||
lifeOnHit: 'life gained on dealing physical damage', | |||||
auraReserveMultiplier: 'aura mana reservation multiplier', | auraReserveMultiplier: 'aura mana reservation multiplier', | ||||
@@ -174,7 +174,7 @@ define([ | |||||
offEvents: function () { | offEvents: function () { | ||||
if (this.pather) | if (this.pather) | ||||
this.pather.onDeath(); | |||||
this.pather.resetPath(); | |||||
for (let e in this.eventCallbacks) | for (let e in this.eventCallbacks) | ||||
this.eventCallbacks[e].forEach(c => events.off(e, c)); | this.eventCallbacks[e].forEach(c => events.off(e, c)); | ||||
@@ -15,11 +15,15 @@ define([ | |||||
objects: [], | objects: [], | ||||
init: function () { | 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('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) { | getLocation: function (x, y) { | ||||
@@ -87,20 +91,14 @@ define([ | |||||
return list[fromIndex]; | 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) { | 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/shaders/outline', | ||||
'js/rendering/spritePool', | 'js/rendering/spritePool', | ||||
'js/system/globals', | 'js/system/globals', | ||||
'js/rendering/renderLoginBackground' | |||||
'js/rendering/renderLoginBackground', | |||||
'js/rendering/helpers/resetRenderer' | |||||
], function ( | ], function ( | ||||
resources, | resources, | ||||
events, | events, | ||||
@@ -19,7 +20,8 @@ define([ | |||||
shaderOutline, | shaderOutline, | ||||
spritePool, | spritePool, | ||||
globals, | globals, | ||||
renderLoginBackground | |||||
renderLoginBackground, | |||||
resetRenderer | |||||
) { | ) { | ||||
const mRandom = Math.random.bind(Math); | const mRandom = Math.random.bind(Math); | ||||
@@ -84,6 +86,7 @@ define([ | |||||
events.on('onGetMap', this.onGetMap.bind(this)); | events.on('onGetMap', this.onGetMap.bind(this)); | ||||
events.on('onToggleFullscreen', this.toggleScreen.bind(this)); | events.on('onToggleFullscreen', this.toggleScreen.bind(this)); | ||||
events.on('onMoveSpeedChange', this.adaptCameraMoveSpeed.bind(this)); | events.on('onMoveSpeedChange', this.adaptCameraMoveSpeed.bind(this)); | ||||
events.on('resetRenderer', resetRenderer.bind(this)); | |||||
this.width = $('body').width(); | this.width = $('body').width(); | ||||
this.height = $('body').height(); | this.height = $('body').height(); | ||||
@@ -18,7 +18,38 @@ define([ | |||||
this.socket.on('event', this.onEvent.bind(this)); | this.socket.on('event', this.onEvent.bind(this)); | ||||
this.socket.on('events', this.onEvents.bind(this)); | this.socket.on('events', this.onEvents.bind(this)); | ||||
this.socket.on('dc', this.onDisconnect.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) { | onConnected: function (onReady) { | ||||
if (this.doneConnect) | if (this.doneConnect) | ||||
this.onDisconnect(); | this.onDisconnect(); | ||||
@@ -28,45 +59,67 @@ define([ | |||||
if (onReady) | if (onReady) | ||||
onReady(); | onReady(); | ||||
}, | }, | ||||
onDisconnect: function () { | onDisconnect: function () { | ||||
window.location = window.location; | window.location = window.location; | ||||
}, | }, | ||||
onHandshake: function () { | onHandshake: function () { | ||||
events.emit('onHandshake'); | events.emit('onHandshake'); | ||||
this.socket.emit('handshake'); | this.socket.emit('handshake'); | ||||
}, | }, | ||||
request: function (msg) { | request: function (msg) { | ||||
this.socket.emit('request', msg, msg.callback); | 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", | "name": "isleward_client", | ||||
"version": "0.10.5", | |||||
"version": "0.10.6", | |||||
"description": "isleward", | "description": "isleward", | ||||
"dependencies": { | "dependencies": { | ||||
}, | }, | ||||
@@ -14,12 +14,13 @@ define([ | |||||
return { | return { | ||||
uis: [], | uis: [], | ||||
root: '', | root: '', | ||||
ingameUisBuilt: false, | |||||
init: function (root) { | init: function (root) { | ||||
if (root) | if (root) | ||||
this.root = 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('onUiKeyDown', this.onUiKeyDown.bind(this)); | ||||
events.on('onResize', this.onResize.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({ | 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) { | getUi: function (type) { | ||||
return this.uis.find(u => u.type === 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 () { | onPlay: function () { | ||||
this.el.removeClass('disabled'); | this.el.removeClass('disabled'); | ||||
this.destroy(); | this.destroy(); | ||||
events.emit('onEnterGame'); | |||||
}, | }, | ||||
onNewClick: function () { | onNewClick: function () { | ||||
@@ -151,7 +151,6 @@ define([ | |||||
if (!result) { | if (!result) { | ||||
this.clear(); | this.clear(); | ||||
this.destroy(); | this.destroy(); | ||||
events.emit('onEnterGame'); | |||||
} else | } else | ||||
this.el.find('.message').html(result); | this.el.find('.message').html(result); | ||||
}, | }, | ||||
@@ -20,14 +20,14 @@ define([ | |||||
this.onEvent('onPermadeath', this.onPermadeath.bind(this)); | this.onEvent('onPermadeath', this.onPermadeath.bind(this)); | ||||
this.find('.btn-logout').on('click', this.onLogout.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 () { | onLogout: function () { | ||||
$('.uiMainMenu').data('ui').charSelect(); | $('.uiMainMenu').data('ui').charSelect(); | ||||
}, | }, | ||||
onRespawn: function () { | |||||
performRespawn: function () { | |||||
events.emit('onHideOverlay', this.el); | events.emit('onHideOverlay', this.el); | ||||
this.hide(true); | 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"; | @import "../../../css/colors.less"; | ||||
.uiBuffs { | |||||
.uiEffects { | |||||
position: absolute; | position: absolute; | ||||
left: 16px; | left: 16px; | ||||
top: 104px; | top: 104px; | ||||
@@ -23,7 +23,7 @@ | |||||
} | } | ||||
.mobile .uiBuffs { | |||||
.mobile .uiEffects { | |||||
left: 316px; | left: 316px; | ||||
top: 10px; | 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 class="inner"></div> | ||||
</div> | </div> |
@@ -26,7 +26,7 @@ define([ | |||||
this.find('.btnCollapse').on('click', this.toggleButtons.bind(this)); | 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('onObtainEvent', this.onObtainEvent.bind(this)); | ||||
this.onEvent('onRemoveEvent', this.onRemoveEvent.bind(this)); | this.onEvent('onRemoveEvent', this.onRemoveEvent.bind(this)); | ||||
@@ -37,7 +37,7 @@ define([ | |||||
this.onToggleEventsVisibility(config.showEvents); | this.onToggleEventsVisibility(config.showEvents); | ||||
}, | }, | ||||
onRezone: function () { | |||||
clear: function () { | |||||
this.list = []; | this.list = []; | ||||
this.el.find('.list').empty(); | this.el.find('.list').empty(); | ||||
}, | }, | ||||
@@ -328,17 +328,17 @@ define([ | |||||
ctxConfig.push(menuItems.divider); | 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) | if (item.quantity > 1 && !item.quest) | ||||
@@ -11,11 +11,11 @@ | |||||
</div> | </div> | ||||
<div class="message"></div> | <div class="message"></div> | ||||
</div> | </div> | ||||
<div class="news" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.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="extra"> | ||||
<div class="el btn btnPatreon monetization" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div> | <div class="el btn btnPatreon monetization" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div> | ||||
<div class="el btn btnPaypal monetization" location="https://www.paypal.com/donate?hosted_button_id=NEQAV3NG9PWXA">Donate on Paypal</div> | <div class="el btn btnPaypal monetization" location="https://www.paypal.com/donate?hosted_button_id=NEQAV3NG9PWXA">Donate on Paypal</div> | ||||
<div class="el btn btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div> | <div class="el btn btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div> | ||||
</div> | </div> | ||||
<div class="version" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.10.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> | </div> |
@@ -58,17 +58,17 @@ define([ | |||||
}, | }, | ||||
onCharSelect: function () { | onCharSelect: function () { | ||||
renderer.clean(); | |||||
objects.onRezone(); | |||||
events.emit('destroyAllObjects'); | |||||
events.emit('resetRenderer'); | |||||
events.emit('resetPhysics'); | |||||
renderer.buildTitleScreen(); | renderer.buildTitleScreen(); | ||||
sound.unload(); | sound.unload(); | ||||
events.emit('onShowCharacterSelect'); | 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', {}); | factory.build('characters', {}); | ||||
}, | }, | ||||
@@ -25,7 +25,7 @@ define([ | |||||
this.find('.btnCollapse').on('click', this.toggleButtons.bind(this)); | 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('onObtainQuest', this.onObtainQuest.bind(this)); | ||||
this.onEvent('onUpdateQuest', this.onUpdateQuest.bind(this)); | this.onEvent('onUpdateQuest', this.onUpdateQuest.bind(this)); | ||||
@@ -35,7 +35,7 @@ define([ | |||||
this.onToggleQuestsVisibility(config.showQuests); | this.onToggleQuestsVisibility(config.showQuests); | ||||
}, | }, | ||||
onRezone: function () { | |||||
clear: function () { | |||||
this.quests = []; | this.quests = []; | ||||
this.el.find('.list').empty(); | this.el.find('.list').empty(); | ||||
}, | }, | ||||
@@ -22,33 +22,38 @@ define([ | |||||
hoverItem: null, | hoverItem: null, | ||||
items: [], | items: [], | ||||
maxItems: null, | |||||
modal: true, | modal: true, | ||||
hasClose: true, | hasClose: true, | ||||
postRender: function () { | 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 () { | 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) | if (!item) | ||||
continue; | continue; | ||||
@@ -126,14 +131,28 @@ define([ | |||||
this.build(); | 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) | if (item === this.hoverItem) | ||||
this.hideTooltip(); | this.hideTooltip(); | ||||
this.items.spliceWhere(i => i.id === id); | |||||
}, this); | |||||
items.spliceWhere(i => i.id === id); | |||||
}); | |||||
if (this.shown) | if (this.shown) | ||||
this.build(); | this.build(); | ||||
@@ -155,8 +174,12 @@ define([ | |||||
events.emit('onHideContextMenu'); | events.emit('onHideContextMenu'); | ||||
}, | }, | ||||
onOpenStash: function () { | |||||
this.build(); | |||||
onOpenStash: function ({ items, maxItems }) { | |||||
this.maxItems = maxItems; | |||||
this.show(); | |||||
this.onGetStashItems(items); | |||||
}, | }, | ||||
beforeDestroy: function () { | beforeDestroy: function () { | ||||
@@ -18,11 +18,7 @@ define([ | |||||
postRender: function () { | postRender: function () { | ||||
this.onEvent('onGetTalk', this.onGetTalk.bind(this)); | 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) { | onGetTalk: function (dialogue) { | ||||
@@ -58,7 +58,9 @@ | |||||
"leaderboard": false, | "leaderboard": false, | ||||
"clientConfig": false, | "clientConfig": false, | ||||
"random": false, | "random": false, | ||||
"consts": false | |||||
"consts": false, | |||||
"rezoneManager": false, | |||||
"eventManager": false | |||||
}, | }, | ||||
"rules": { | "rules": { | ||||
@@ -1,135 +1,127 @@ | |||||
define([ | define([ | ||||
'js/rendering/renderer' | |||||
'js/system/events', | |||||
'js/rendering/numbers' | |||||
], function ( | ], 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: [], | effects: [], | ||||
templates: { | |||||
}, | |||||
init: function (blueprint) { | 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) { | extend: function (blueprint) { | ||||
if (blueprint.addEffects) { | 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 || []); | this.effects.push.apply(this.effects, blueprint.addEffects || []); | ||||
} | } | ||||
if (blueprint.removeEffects) { | 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) | if (!effect) | ||||
return; | 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 => { | 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) { | setVisible: function (visible) { | ||||
this.effects.forEach(e => { | this.effects.forEach(e => { | ||||
e.sprite.visible = visible; | |||||
if (e.setVisible) | |||||
e.setVisible(visible); | |||||
}); | }); | ||||
}, | }, | ||||
destroy: function () { | destroy: function () { | ||||
this.effects.forEach(e => { | 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 () { | init: function () { | ||||
this.obj.on('onKeyDown', this.onKeyDown.bind(this)); | this.obj.on('onKeyDown', this.onKeyDown.bind(this)); | ||||
this.hookEvent('onRezone', this.onRezone.bind(this)); | |||||
}, | }, | ||||
extend: function (msg) { | extend: function (msg) { | ||||
@@ -53,13 +52,6 @@ define([ | |||||
} | } | ||||
}, | }, | ||||
onRezone: function () { | |||||
this.extend({ | |||||
progress: 100, | |||||
action: 'Fishing' | |||||
}); | |||||
}, | |||||
onKeyDown: function (key) { | onKeyDown: function (key) { | ||||
if (key !== 'g') | if (key !== 'g') | ||||
return; | return; | ||||
@@ -27,9 +27,9 @@ define([ | |||||
lastY: 0, | lastY: 0, | ||||
init: function () { | 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.x = round(this.obj.x); | ||||
this.pathPos.y = round(this.obj.y); | this.pathPos.y = round(this.obj.y); | ||||
@@ -46,7 +46,7 @@ define([ | |||||
this.path = []; | this.path = []; | ||||
}, | }, | ||||
onDeath: function () { | |||||
resetPath: function () { | |||||
this.clearPath(); | this.clearPath(); | ||||
this.pathPos.x = round(this.obj.x); | this.pathPos.x = round(this.obj.x); | ||||
@@ -29,7 +29,7 @@ define([ | |||||
obj.addComponent('serverActions'); | obj.addComponent('serverActions'); | ||||
obj.addComponent('pather'); | obj.addComponent('pather'); | ||||
this.hookEvent('onRespawn', this.onRespawn.bind(this)); | |||||
this.hookEvent('teleportToPosition', this.teleportToPosition.bind(this)); | |||||
events.emit('onGetPortrait', obj.portrait); | events.emit('onGetPortrait', obj.portrait); | ||||
}, | }, | ||||
@@ -74,7 +74,7 @@ define([ | |||||
}, instant); | }, instant); | ||||
}, | }, | ||||
onRespawn: function ({ x, y }) { | |||||
teleportToPosition: function ({ x, y }) { | |||||
this.positionCamera(x, y, true); | this.positionCamera(x, y, true); | ||||
sound.update(x, y); | sound.update(x, y); | ||||
}, | }, | ||||
@@ -14,6 +14,10 @@ define([ | |||||
this.hookEvent('onKeyUp', this.onKeyUp.bind(this)); | this.hookEvent('onKeyUp', this.onKeyUp.bind(this)); | ||||
}, | }, | ||||
hasAction: function (actionId) { | |||||
return this.actions.some(a => a.id === actionId); | |||||
}, | |||||
onKeyUp: function (key) { | onKeyUp: function (key) { | ||||
this.actions.forEach(function (a) { | this.actions.forEach(function (a) { | ||||
if (a.key !== key) | if (a.key !== key) | ||||
@@ -1,49 +1,7 @@ | |||||
define([ | |||||
'js/system/events' | |||||
], function ( | |||||
events | |||||
) { | |||||
define([], function () { | |||||
return { | return { | ||||
type: 'stash', | 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 () { | clearIgnoreList: function () { | ||||
this.ignoreList = []; | this.ignoreList = []; | ||||
}, | |||||
isInCombat: function () { | |||||
return this.list.length > 0; | |||||
} | } | ||||
}; | }; |
@@ -93,6 +93,9 @@ module.exports = { | |||||
doSave: async function (callback, saveStash = true) { | doSave: async function (callback, saveStash = true) { | ||||
const simple = this.obj.getSimple(true, true); | const simple = this.obj.getSimple(true, true); | ||||
delete simple.destroyed; | |||||
delete simple.forceDestroy; | |||||
simple.components.spliceWhere(f => (f.type === 'stash')); | simple.components.spliceWhere(f => (f.type === 'stash')); | ||||
await io.setAsync({ | await io.setAsync({ | ||||
@@ -111,10 +114,15 @@ module.exports = { | |||||
}, | }, | ||||
doSaveStash: async function () { | doSaveStash: async function () { | ||||
const { username, obj: { stash } } = this; | |||||
if (!stash.changed) | |||||
return; | |||||
await io.setAsync({ | await io.setAsync({ | ||||
key: this.username, | |||||
key: username, | |||||
table: 'stash', | table: 'stash', | ||||
value: this.obj.stash.serialize(), | |||||
value: stash.serialize(), | |||||
clean: true, | clean: true, | ||||
serialize: true | serialize: true | ||||
}); | }); | ||||
@@ -174,7 +182,6 @@ module.exports = { | |||||
this.characters[charName] = character; | this.characters[charName] = character; | ||||
await this.getCustomChannels(character); | await this.getCustomChannels(character); | ||||
await this.getStash(); | |||||
await this.verifySkin(character); | await this.verifySkin(character); | ||||
@@ -341,8 +348,13 @@ module.exports = { | |||||
register: async function (msg) { | register: async function (msg) { | ||||
let credentials = msg.data; | let credentials = msg.data; | ||||
if ((credentials.username === '') || (credentials.password === '')) { | |||||
if (credentials.username === '' || credentials.password === '') { | |||||
msg.callback(messages.login.allFields); | msg.callback(messages.login.allFields); | ||||
return; | |||||
} else if (credentials.username.length > 32) { | |||||
msg.callback(messages.login.maxUsernameLength); | |||||
return; | return; | ||||
} | } | ||||
@@ -110,14 +110,14 @@ module.exports = { | |||||
let effect = effects[i]; | let effect = effects[i]; | ||||
if (!forceDestroy) { | if (!forceDestroy) { | ||||
if (effect.persist) { | if (effect.persist) { | ||||
this.syncRemove(effect.id, effect.type); | |||||
this.syncRemove(effect.id); | |||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
this.destroyEffect(effect); | this.destroyEffect(effect); | ||||
this.syncRemove(effect.id, effect.type); | |||||
this.syncRemove(effect.id); | |||||
effects.splice(i, 1); | effects.splice(i, 1); | ||||
eLen--; | eLen--; | ||||
i--; | i--; | ||||
@@ -137,36 +137,40 @@ module.exports = { | |||||
}, | }, | ||||
addEffect: function (options, source) { | addEffect: function (options, source) { | ||||
//Skip 0-duration effects | |||||
if ((options.has('ttl')) && (options.ttl === 0)) | if ((options.has('ttl')) && (options.ttl === 0)) | ||||
return; | return; | ||||
options.caster = options.caster || source; | options.caster = options.caster || source; | ||||
//"X of Y in Z" cc resist check | |||||
if (!options.force && !this.canApplyEffect(options.type)) | if (!options.force && !this.canApplyEffect(options.type)) | ||||
return; | 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; | 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 = { | let result = { | ||||
type: type, | type: type, | ||||
url: 'config/effects/effect' + type + '.js' | |||||
url: 'config/effects/effect' + capitalizedType + '.js' | |||||
}; | }; | ||||
this.obj.instance.eventEmitter.emit('onBeforeGetEffect', result); | this.obj.instance.eventEmitter.emit('onBeforeGetEffect', result); | ||||
@@ -174,84 +178,77 @@ module.exports = { | |||||
} | } | ||||
let builtEffect = extend({}, effectTemplate, typeTemplate); | let builtEffect = extend({}, effectTemplate, typeTemplate); | ||||
return builtEffect; | |||||
}, | |||||
buildEffect: function (options) { | |||||
let builtEffect = this.getTypeTemplate(options.type); | |||||
for (let p in options) | for (let p in options) | ||||
builtEffect[p] = options[p]; | builtEffect[p] = options[p]; | ||||
builtEffect.obj = this.obj; | builtEffect.obj = this.obj; | ||||
builtEffect.id = this.nextId++; | builtEffect.id = this.nextId++; | ||||
builtEffect.noMsg = options.noMsg; | |||||
builtEffect.silent = options.silent; | |||||
if (builtEffect.init) | if (builtEffect.init) | ||||
builtEffect.init(options.source); | builtEffect.init(options.source); | ||||
this.effects.push(builtEffect); | 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); | this.obj.instance.eventEmitter.emit('onAddEffect', this.obj, builtEffect); | ||||
return 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; | 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) { | getEffectByType: function (effectType) { | ||||
@@ -293,23 +290,22 @@ module.exports = { | |||||
for (let i = 0; i < eLen; i++) { | for (let i = 0; i < eLen; i++) { | ||||
let e = effects[i]; | let e = effects[i]; | ||||
if (e.ttl > 0) { | |||||
if (e.ttl > 0) | |||||
e.ttl--; | e.ttl--; | ||||
if (e.ttl === 0) | |||||
e.destroyed = true; | |||||
} | |||||
else if (e.ttl === 0) | |||||
e.destroyed = true; | |||||
if (e.update) | if (e.update) | ||||
e.update(); | e.update(); | ||||
if (e.destroyed) { | if (e.destroyed) { | ||||
this.destroyEffect(e); | |||||
this.syncRemove(e.id); | |||||
effects.splice(i, 1); | effects.splice(i, 1); | ||||
eLen--; | eLen--; | ||||
i--; | i--; | ||||
this.destroyEffect(e); | |||||
this.syncRemove(e.id, e.type, e.noMsg); | |||||
} | } | ||||
} | } | ||||
@@ -549,7 +549,7 @@ module.exports = { | |||||
die: function () { | die: function () { | ||||
this.obj.stats.takeDamage({ | this.obj.stats.takeDamage({ | ||||
amount: 99999 | |||||
amount: 20000000 | |||||
}, 1, this.obj); | }, 1, this.obj); | ||||
}, | }, | ||||
@@ -272,19 +272,16 @@ module.exports = { | |||||
this.obj.syncer.setArray(true, 'inventory', 'getItems', item); | 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) | if (!item || item.quest || item.noStash) | ||||
return; | return; | ||||
delete item.pos; | 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) | if (!success) | ||||
return; | return; | ||||
@@ -98,6 +98,8 @@ module.exports = { | |||||
patrol: null, | patrol: null, | ||||
patrolTargetNode: 0, | patrolTargetNode: 0, | ||||
needLos: null, | |||||
init: function (blueprint) { | init: function (blueprint) { | ||||
this.physics = this.obj.instance.physics; | this.physics = this.obj.instance.physics; | ||||
@@ -111,6 +113,7 @@ module.exports = { | |||||
this.maxChaseDistance = blueprint.maxChaseDistance; | this.maxChaseDistance = blueprint.maxChaseDistance; | ||||
}, | }, | ||||
/* eslint-disable-next-line max-lines-per-function */ | |||||
update: function () { | update: function () { | ||||
let obj = this.obj; | let obj = this.obj; | ||||
@@ -121,20 +124,22 @@ module.exports = { | |||||
//Have we reached home? | //Have we reached home? | ||||
if (this.goHome) { | if (this.goHome) { | ||||
let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y)); | let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y)); | ||||
if (!distanceFromHome) | |||||
if (!distanceFromHome) { | |||||
this.goHome = false; | 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) { | 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 ((target) && (target !== obj) && ((!obj.follower) || (obj.follower.master !== target))) { | ||||
//If we just started attacking, patrols need to know where home is | //If we just started attacking, patrols need to know where home is | ||||
if (!this.target && this.patrol) { | if (!this.target && this.patrol) { | ||||
@@ -145,10 +150,11 @@ module.exports = { | |||||
//Are we in fight mode? | //Are we in fight mode? | ||||
this.fight(target); | this.fight(target); | ||||
return; | return; | ||||
} else if ((!target) && (this.target)) { | |||||
} else if (!target && this.target) { | |||||
//Is fight mode over? | //Is fight mode over? | ||||
this.target = null; | this.target = null; | ||||
obj.clearQueue(); | obj.clearQueue(); | ||||
obj.spellbook.resetRotation(); | |||||
if (canPathHome(this)) | if (canPathHome(this)) | ||||
this.goHome = true; | this.goHome = true; | ||||
@@ -251,8 +257,8 @@ module.exports = { | |||||
let ty = ~~target.y; | let ty = ~~target.y; | ||||
let distance = max(abs(x - tx), abs(y - ty)); | 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 doesCollide = null; | ||||
let hasLos = null; | let hasLos = null; | ||||
@@ -263,18 +269,20 @@ module.exports = { | |||||
hasLos = this.physics.hasLos(x, y, tx, ty); | hasLos = this.physics.hasLos(x, y, tx, ty); | ||||
//Maybe we don't care if the mob has LoS | //Maybe we don't care if the mob has LoS | ||||
if (hasLos || this.needLos === false) { | 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; | 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) { | } else if (furthestAttackRange === 0) { | ||||
@@ -70,9 +70,7 @@ module.exports = { | |||||
faction: 'players' | faction: 'players' | ||||
}); | }); | ||||
obj.addComponent('gatherer'); | obj.addComponent('gatherer'); | ||||
obj.addComponent('stash', { | |||||
items: character.stash | |||||
}); | |||||
obj.addComponent('stash'); | |||||
let blueprintEffects = character.components.find(c => c.type === 'effects') || {}; | let blueprintEffects = character.components.find(c => c.type === 'effects') || {}; | ||||
if (blueprintEffects.effects) { | if (blueprintEffects.effects) { | ||||
@@ -235,7 +233,7 @@ module.exports = { | |||||
obj.instance.physics.addObject(obj, obj.x, obj.y); | obj.instance.physics.addObject(obj, obj.x, obj.y); | ||||
obj.instance.syncer.queue('onRespawn', { | |||||
obj.instance.syncer.queue('teleportToPosition', { | |||||
x: obj.x, | x: obj.x, | ||||
y: obj.y | y: obj.y | ||||
}, [obj.serverId]); | }, [obj.serverId]); | ||||
@@ -276,11 +274,5 @@ module.exports = { | |||||
msg.data.data.callbackId = atlas.registerCallback(msg.callback); | msg.data.data.callbackId = atlas.registerCallback(msg.callback); | ||||
atlas.performAction(this.obj, msg.data); | 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; | this.patronLevel = ~~blueprint.patron; | ||||
}, | }, | ||||
collisionEnter: async function (obj) { | |||||
collisionEnter: function (obj) { | |||||
if (!obj.player) | if (!obj.player) | ||||
return; | 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) { | if (obj.zoneName === zoneName) { | ||||
globalSyncer.flushForTarget(serverId); | |||||
physics.removeObject(obj, obj.x, obj.y); | physics.removeObject(obj, obj.x, obj.y); | ||||
if (toRelativePos) { | if (toRelativePos) { | ||||
@@ -18,7 +28,7 @@ const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos | |||||
physics.addObject(obj, obj.x, obj.y); | physics.addObject(obj, obj.x, obj.y); | ||||
globalSyncer.queue('onRespawn', { | |||||
globalSyncer.queue('teleportToPosition', { | |||||
x: obj.x, | x: obj.x, | ||||
y: obj.y | y: obj.y | ||||
}, [obj.serverId]); | }, [obj.serverId]); | ||||
@@ -26,28 +36,41 @@ const sendObjToZone = async ({ obj, invokingObj, zoneName, toPos, toRelativePos | |||||
return; | 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(); | 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); | 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({ | 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; | return; | ||||
let source = cons.players.find(c => c.id === sourceId); | let source = cons.players.find(c => c.id === sourceId); | ||||
if (!source) | |||||
if (!source || !source.social) | |||||
return; | return; | ||||
source.social.sendMessage('invite sent', 'color-yellowB'); | source.social.sendMessage('invite sent', 'color-yellowB'); | ||||
@@ -3,6 +3,9 @@ const sendObjToZone = require('../portal/sendObjToZone'); | |||||
module.exports = (cpnSocial, targetZone) => { | module.exports = (cpnSocial, targetZone) => { | ||||
const { obj } = cpnSocial; | const { obj } = cpnSocial; | ||||
if (obj.zoneName === targetZone) | |||||
return; | |||||
sendObjToZone({ | sendObjToZone({ | ||||
obj, | obj, | ||||
zoneName: targetZone | zoneName: targetZone | ||||
@@ -3,6 +3,11 @@ let animations = require('../config/animations'); | |||||
let playerSpells = require('../config/spells'); | let playerSpells = require('../config/spells'); | ||||
let playerSpellsConfig = require('../config/spellsConfig'); | let playerSpellsConfig = require('../config/spellsConfig'); | ||||
//Helpers | |||||
const rotationManager = require('./spellbook/rotationManager'); | |||||
//Component | |||||
module.exports = { | module.exports = { | ||||
type: 'spellbook', | type: 'spellbook', | ||||
@@ -16,6 +21,8 @@ module.exports = { | |||||
callbacks: [], | callbacks: [], | ||||
rotation: null, | |||||
init: function (blueprint) { | init: function (blueprint) { | ||||
this.objects = this.obj.instance.objects; | this.objects = this.obj.instance.objects; | ||||
this.physics = this.obj.instance.physics; | this.physics = this.obj.instance.physics; | ||||
@@ -24,7 +31,22 @@ module.exports = { | |||||
(blueprint.spells || []).forEach(s => this.addSpell(s, -1)); | (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; | 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 () { | 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) { | getTarget: function (spell, action) { | ||||
let target = action.target; | 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) | if (this.obj.aggro.faction !== target.aggro.faction) | ||||
return; | return; | ||||
} else if (target.aggro && !this.obj.aggro.canAttack(target)) { | } else if (target.aggro && !this.obj.aggro.canAttack(target)) { | ||||
@@ -339,6 +350,7 @@ module.exports = { | |||||
return false; | return false; | ||||
action.target = this.getTarget(spell, action); | action.target = this.getTarget(spell, action); | ||||
//If a target has become nonSelectable, we need to stop attacks that are queued/auto | //If a target has become nonSelectable, we need to stop attacks that are queued/auto | ||||
if (!action.target || action.target.nonSelectable) | if (!action.target || action.target.nonSelectable) | ||||
return false; | return false; | ||||
@@ -445,25 +457,6 @@ module.exports = { | |||||
return this.closestRange; | 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 () { | getCooldowns: function () { | ||||
let cds = []; | let cds = []; | ||||
this.spells.forEach( | this.spells.forEach( | ||||
@@ -480,6 +473,9 @@ module.exports = { | |||||
let didCast = false; | let didCast = false; | ||||
const isCasting = this.isCasting(); | const isCasting = this.isCasting(); | ||||
if (this.rotation) | |||||
rotationManager.tick(this); | |||||
this.spells.forEach(s => { | this.spells.forEach(s => { | ||||
let auto = s.autoActive; | let auto = s.autoActive; | ||||
if (auto) { | 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 cpnInventory = require('./inventory'); | ||||
const { isItemStackable } = require('./inventory/helpers'); | const { isItemStackable } = require('./inventory/helpers'); | ||||
//Config | |||||
const maxItemsBase = 50; | const maxItemsBase = 50; | ||||
//Component | |||||
module.exports = { | module.exports = { | ||||
type: 'stash', | type: 'stash', | ||||
active: false, | active: false, | ||||
items: [], | |||||
items: null, | |||||
changed: false, | changed: false, | ||||
maxItems: maxItemsBase, | 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) { | getItem: function (item) { | ||||
//Material? | |||||
let exists = false; | |||||
const { items } = this; | |||||
if (isItemStackable(item)) { | if (isItemStackable(item)) { | ||||
let existItem = this.items.find(i => i.name === item.name); | |||||
const existItem = items.find(i => i.name === item.name); | |||||
if (existItem) { | if (existItem) { | ||||
exists = true; | |||||
if (!existItem.quantity) | if (!existItem.quantity) | ||||
existItem.quantity = 1; | 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 | //We modify the old object because it gets sent to the client | ||||
item.id = existItem.id; | item.id = existItem.id; | ||||
item.quantity = existItem.quantity; | item.quantity = existItem.quantity; | ||||
item = existItem; | item = existItem; | ||||
return; | |||||
} | } | ||||
} | } | ||||
//Get next id | //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; | 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) { | if (!isStackable) { | ||||
const message = 'You do not have room in your stash to deposit that item'; | const message = 'You do not have room in your stash to deposit that item'; | ||||
this.obj.social.notifySelf({ message }); | |||||
obj.social.notifySelf({ message }); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
this.getItem(item); | |||||
this.obj.syncer.setArray(true, 'stash', 'getItems', item); | |||||
this.changed = true; | 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) { | withdraw: function (id) { | ||||
if (!this.active) | |||||
const { active, items, obj } = this; | |||||
if (!active) | |||||
return; | return; | ||||
let item = this.items.find(i => i.id === id); | |||||
let item = items.find(i => i.id === id); | |||||
if (!item) | if (!item) | ||||
return; | 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'; | const message = 'You do not have room in your inventory to withdraw that item'; | ||||
this.obj.social.notifySelf({ message }); | |||||
obj.social.notifySelf({ message }); | |||||
return; | 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) { | setActive: function (active) { | ||||
let obj = this.obj; | |||||
const { obj } = this; | |||||
this.active = active; | this.active = active; | ||||
obj.syncer.set(true, 'stash', 'active', this.active); | |||||
const actionType = active ? 'addActions' : 'removeActions'; | const actionType = active ? 'addActions' : 'removeActions'; | ||||
obj.syncer.setArray(true, 'serverActions', actionType, { | obj.syncer.setArray(true, 'serverActions', actionType, { | ||||
id: 'openStash', | |||||
key: 'u', | key: 'u', | ||||
action: { | action: { | ||||
targetId: obj.id, | targetId: obj.id, | ||||
@@ -130,34 +141,53 @@ module.exports = { | |||||
} | } | ||||
}); | }); | ||||
if (!this.active) | |||||
return; | |||||
let msg = 'Press U to access your Shared Stash'; | 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 | msg: msg | ||||
}, [obj.serverId]); | }, [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) { | simplify: function (self) { | ||||
if (!self) | if (!self) | ||||
return null; | return null; | ||||
return { type: 'stash' }; | |||||
}, | |||||
simplifyTransfer: function () { | |||||
const { type, items } = this; | |||||
return { | return { | ||||
type: 'stash', | |||||
active: this.active, | |||||
items: this.items, | |||||
maxItems: this.maxItems | |||||
type, | |||||
items | |||||
}; | }; | ||||
}, | }, | ||||
@@ -629,7 +629,8 @@ module.exports = { | |||||
source: source.id, | source: source.id, | ||||
heal: true, | heal: true, | ||||
amount: amount, | amount: amount, | ||||
crit: heal.crit | |||||
crit: heal.crit, | |||||
element: heal.element | |||||
}, recipients); | }, recipients); | ||||
} | } | ||||
@@ -776,6 +777,9 @@ module.exports = { | |||||
}, | }, | ||||
afterDealDamage: function (damageEvent, target) { | afterDealDamage: function (damageEvent, target) { | ||||
if (damageEvent.element) | |||||
return; | |||||
const { obj, values: { lifeOnHit } } = this; | const { obj, values: { lifeOnHit } } = this; | ||||
if (target === obj || !lifeOnHit) | if (target === obj || !lifeOnHit) | ||||
@@ -44,7 +44,8 @@ const config = { | |||||
'images/tiles.png': 8, | 'images/tiles.png': 8, | ||||
'images/walls.png': 8, | 'images/walls.png': 8, | ||||
'images/objects.png': 8, | 'images/objects.png': 8, | ||||
'images/mobs.png': 8 | |||||
'images/mobs.png': 8, | |||||
'images/characters.png': 8 | |||||
}, | }, | ||||
blockingTileIndices: [ | blockingTileIndices: [ | ||||
6, 7, 54, 55, 62, 63, 154, 189, 190, 192, 193, 194, 195, 196, 197 | 6, 7, 54, 55, 62, 63, 154, 189, 190, 192, 193, 194, 195, 196, 197 | ||||
@@ -147,7 +148,7 @@ const config = { | |||||
//Table Sides | //Table Sides | ||||
103, 110, 118, 126, | 103, 110, 118, 126, | ||||
//Wall-mounted plants | //Wall-mounted plants | ||||
120, 122, 140, | |||||
120, 121, | |||||
//Ship oars | //Ship oars | ||||
140, 143, | 140, 143, | ||||
//Ship Cannons | //Ship Cannons | ||||
@@ -173,7 +174,7 @@ const config = { | |||||
'party', | 'party', | ||||
'help', | 'help', | ||||
'dialogue', | 'dialogue', | ||||
'buffs', | |||||
'effects', | |||||
'tooltips', | 'tooltips', | ||||
'tooltipInfo', | 'tooltipInfo', | ||||
'tooltipItem', | 'tooltipItem', | ||||
@@ -221,6 +222,11 @@ module.exports = { | |||||
}); | }); | ||||
}); | }); | ||||
config.clientComponents.push({ | |||||
extends: 'effects', | |||||
path: 'server/clientComponents/effects/auras.js' | |||||
}); | |||||
events.emit('onBeforeGetClientConfig', config); | events.emit('onBeforeGetClientConfig', config); | ||||
//Deprecated | //Deprecated | ||||
@@ -2,7 +2,8 @@ module.exports = { | |||||
type: 'stunned', | type: 'stunned', | ||||
init: function () { | init: function () { | ||||
this.obj.spellbook.stopCasting(); | |||||
if (this.obj.spellbook) | |||||
this.obj.spellbook.stopCasting(); | |||||
}, | }, | ||||
events: { | events: { | ||||
@@ -1,4 +1,15 @@ | |||||
module.exports = { | 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 () { | save: function () { | ||||
if (!this.persist) | if (!this.persist) | ||||
return null; | return null; | ||||
@@ -19,6 +30,9 @@ module.exports = { | |||||
}, | }, | ||||
simplify: function () { | simplify: function () { | ||||
return this.type; | |||||
return { | |||||
id: this.id, | |||||
type: this.type | |||||
}; | |||||
} | } | ||||
}; | }; |
@@ -1,5 +1,5 @@ | |||||
module.exports = { | module.exports = { | ||||
version: '0.10.5', | |||||
version: '0.10.6', | |||||
port: 4000, | port: 4000, | ||||
startupMessage: 'Server: ready', | startupMessage: 'Server: ready', | ||||
defaultZone: 'fjolarok', | defaultZone: 'fjolarok', | ||||
@@ -18,11 +18,6 @@ const config = { | |||||
sprite: [2, 0], | sprite: [2, 0], | ||||
defaultSpirit: 'bear', | defaultSpirit: 'bear', | ||||
default: true | default: true | ||||
}, | |||||
//Faction Skins | |||||
'gaekatlan-druid': { | |||||
name: 'Gaekatlan Druid', | |||||
sprite: [0, 1] | |||||
} | } | ||||
}; | }; | ||||
@@ -212,6 +212,57 @@ let spells = [{ | |||||
randomSpeed: true, | randomSpeed: true, | ||||
chance: 0.02 | 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', | name: 'Holy Vengeance', | ||||
description: 'Grants holy vengeance to a friendly target. For the duration of the effect, dealing damage will also heal the attacker.', | 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 (distance > range) { | ||||
if (effect) { | if (effect) { | ||||
delete effects[m]; | delete effects[m]; | ||||
obj.effects.removeEffect(effect); | |||||
obj.effects.removeEffect(effect.id); | |||||
} | } | ||||
return; | return; | ||||
@@ -85,8 +85,7 @@ module.exports = { | |||||
type: this.effect, | type: this.effect, | ||||
amount: amount, | amount: amount, | ||||
caster: this.obj, | caster: this.obj, | ||||
ttl: -1, | |||||
new: true | |||||
ttl: -1 | |||||
}); | }); | ||||
}); | }); | ||||
@@ -96,7 +95,7 @@ module.exports = { | |||||
delete effects[serverId]; | delete effects[serverId]; | ||||
const obj = objects.find(f => ~~f.serverId === ~~serverId); | const obj = objects.find(f => ~~f.serverId === ~~serverId); | ||||
if (obj) | if (obj) | ||||
obj.effects.removeEffect(effect); | |||||
obj.effects.removeEffect(effect.id); | |||||
} | } | ||||
}); | }); | ||||
}, | }, | ||||
@@ -117,7 +116,7 @@ module.exports = { | |||||
return; | return; | ||||
} | } | ||||
obj.effects.removeEffect(effect); | |||||
obj.effects.removeEffect(effect.id); | |||||
delete effects[m]; | delete effects[m]; | ||||
}, this); | }, this); | ||||
} | } | ||||
@@ -55,19 +55,10 @@ module.exports = { | |||||
type: 'stunned' | type: 'stunned' | ||||
}); | }); | ||||
if (targetEffect) { | |||||
this.obj.instance.syncer.queue('onGetDamage', { | |||||
id: target.id, | |||||
event: true, | |||||
text: 'stunned' | |||||
}, -1); | |||||
} | |||||
let selfEffect = this.obj.effects.addEffect({ | let selfEffect = this.obj.effects.addEffect({ | ||||
type: 'stunned', | type: 'stunned', | ||||
noMsg: true, | |||||
force: true, | |||||
new: true | |||||
silent: true, | |||||
force: true | |||||
}); | }); | ||||
const moveAnimationEffect = { | const moveAnimationEffect = { | ||||
@@ -115,7 +106,7 @@ module.exports = { | |||||
obj.instance.physics.addObject(obj, obj.x, obj.y); | obj.instance.physics.addObject(obj, obj.x, obj.y); | ||||
obj.effects.removeEffect(selfEffect, true); | |||||
obj.effects.removeEffect(selfEffect.id); | |||||
this.obj.aggro.move(); | this.obj.aggro.move(); | ||||
@@ -115,8 +115,7 @@ module.exports = { | |||||
const targetEffect = m.effects.addEffect({ | const targetEffect = m.effects.addEffect({ | ||||
type: 'stunned', | type: 'stunned', | ||||
noMsg: true, | |||||
new: true | |||||
silent: true | |||||
}); | }); | ||||
//If targetEffect is undefined, it means that the target has become resistant | //If targetEffect is undefined, it means that the target has become resistant | ||||
@@ -147,7 +146,7 @@ module.exports = { | |||||
}, | }, | ||||
endEffect: function (target, targetPos, targetEffect) { | endEffect: function (target, targetPos, targetEffect) { | ||||
target.effects.removeEffect(targetEffect, true); | |||||
target.effects.removeEffect(targetEffect.id); | |||||
target.instance.physics.removeObject(target, target.x, target.y); | target.instance.physics.removeObject(target, target.x, target.y); | ||||
@@ -54,19 +54,11 @@ module.exports = { | |||||
if (this.obj.destroyed) | if (this.obj.destroyed) | ||||
return; | return; | ||||
let targetEffect = target.effects.addEffect({ | |||||
target.effects.addEffect({ | |||||
type: 'slowed', | type: 'slowed', | ||||
ttl: this.freezeDuration | ttl: this.freezeDuration | ||||
}); | }); | ||||
if (targetEffect) { | |||||
this.obj.instance.syncer.queue('onGetDamage', { | |||||
id: target.id, | |||||
event: true, | |||||
text: 'slowed' | |||||
}, -1); | |||||
} | |||||
let damage = this.getDamage(target); | let damage = this.getDamage(target); | ||||
target.stats.takeDamage(damage, this.threatMult, this.obj); | target.stats.takeDamage(damage, this.threatMult, this.obj); | ||||
} | } | ||||
@@ -36,6 +36,6 @@ module.exports = { | |||||
let obj = this.obj; | 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, | targetGround: true, | ||||
effect: null, | |||||
cast: function (action) { | cast: function (action) { | ||||
//Clear Aggro | //Clear Aggro | ||||
this.obj.aggro.die(); | this.obj.aggro.die(); | ||||
@@ -15,10 +17,10 @@ module.exports = { | |||||
let ttl = this.duration * consts.tickTime; | let ttl = this.duration * consts.tickTime; | ||||
let endCallback = this.queueCallback(this.endEffect.bind(this), ttl - 50); | let endCallback = this.queueCallback(this.endEffect.bind(this), ttl - 50); | ||||
this.obj.effects.addEffect({ | |||||
this.effect = this.obj.effects.addEffect({ | |||||
type: 'stealth', | type: 'stealth', | ||||
endCallback: endCallback | endCallback: endCallback | ||||
}); | |||||
}); | |||||
return true; | return true; | ||||
}, | }, | ||||
@@ -28,7 +30,7 @@ module.exports = { | |||||
let obj = this.obj; | let obj = this.obj; | ||||
obj.effects.removeEffectByName('stealth'); | |||||
obj.effects.removeEffect(this.effect.id); | |||||
this.obj.aggro.move(); | this.obj.aggro.move(); | ||||
} | } | ||||
}; | }; |
@@ -164,7 +164,7 @@ module.exports = { | |||||
this.castingEffect = this.obj.effects.addEffect({ | this.castingEffect = this.obj.effects.addEffect({ | ||||
type: 'casting', | type: 'casting', | ||||
noMsg: true | |||||
silent: true | |||||
}); | }); | ||||
this.casting = true; | this.casting = true; | ||||
@@ -76,9 +76,9 @@ let spells = { | |||||
statType: 'int', | statType: 'int', | ||||
statMult: 1, | statMult: 1, | ||||
element: 'holy', | element: 'holy', | ||||
cdMax: 5, | |||||
cdMax: 15, | |||||
castTimeMax: 4, | castTimeMax: 4, | ||||
manaCost: 8, | |||||
manaCost: 12, | |||||
range: 9, | range: 9, | ||||
radius: 3, | radius: 3, | ||||
random: { | 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: { | slash: { | ||||
statType: 'str', | statType: 'str', | ||||
statMult: 1, | statMult: 1, | ||||
@@ -130,7 +143,6 @@ let spells = { | |||||
cdMax: 12, | cdMax: 12, | ||||
castTimeMax: 2, | castTimeMax: 2, | ||||
manaCost: 7, | manaCost: 7, | ||||
noDrop: true, | |||||
random: { | random: { | ||||
i_range: [1, 2.5], | i_range: [1, 2.5], | ||||
damage: [4, 18] | damage: [4, 18] | ||||
@@ -156,7 +168,6 @@ let spells = { | |||||
castTimeMax: 3, | castTimeMax: 3, | ||||
range: 10, | range: 10, | ||||
manaCost: 7, | manaCost: 7, | ||||
noDrop: true, | |||||
random: { | random: { | ||||
damage: [8, 35], | damage: [8, 35], | ||||
i_stunDuration: [4, 7] | i_stunDuration: [4, 7] | ||||
@@ -167,11 +178,12 @@ let spells = { | |||||
statMult: 1, | statMult: 1, | ||||
manaCost: 14, | manaCost: 14, | ||||
needLos: true, | needLos: true, | ||||
cdMax: 20, | |||||
cdMax: 15, | |||||
castTimeMax: 0, | castTimeMax: 0, | ||||
range: 9, | range: 9, | ||||
isAttack: true, | |||||
random: { | random: { | ||||
damage: [3, 16], | |||||
damage: [3, 18], | |||||
i_delay: [1, 4] | i_delay: [1, 4] | ||||
}, | }, | ||||
negativeStats: [ | negativeStats: [ | ||||
@@ -219,7 +231,7 @@ let spells = { | |||||
auraRange: 9, | auraRange: 9, | ||||
effect: 'swiftness', | effect: 'swiftness', | ||||
random: { | random: { | ||||
chance: [5, 10] | |||||
chance: [8, 20] | |||||
} | } | ||||
} | } | ||||
@@ -1,6 +1,6 @@ | |||||
module.exports = { | module.exports = { | ||||
init: function (event) { | init: function (event) { | ||||
const { config, rewards, eventManager } = event; | |||||
const { config, rewards, eventManager: eManager } = event; | |||||
const { name: eventName, rewardSenderName } = config; | const { name: eventName, rewardSenderName } = config; | ||||
@@ -26,7 +26,7 @@ module.exports = { | |||||
}); | }); | ||||
if ((config.events) && (config.events.afterGiveRewards)) | if ((config.events) && (config.events.afterGiveRewards)) | ||||
config.events.afterGiveRewards(eventManager, config); | |||||
config.events.afterGiveRewards(eManager, config); | |||||
this.end = true; | this.end = true; | ||||
} | } | ||||
@@ -5,7 +5,8 @@ module.exports = { | |||||
allFields: 'please complete all fields', | allFields: 'please complete all fields', | ||||
illegal: 'illegal characters in username', | illegal: 'illegal characters in username', | ||||
incorrect: 'invalid username and password', | 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: { | createCharacter: { | ||||
nameLength: 'name must be between 3 and 12 characters' | nameLength: 'name must be between 3 and 12 characters' | ||||
@@ -126,7 +126,7 @@ module.exports = { | |||||
range: 1, | range: 1, | ||||
random: { | random: { | ||||
damage: [4, 14], | damage: [4, 14], | ||||
healPercent: [10, 30] | |||||
healPercent: [2, 15] | |||||
} | } | ||||
}; | }; | ||||
@@ -277,7 +277,7 @@ module.exports = { | |||||
}, | }, | ||||
performMove: function (action) { | 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 { maxDistance = 1, force, data } = action; | ||||
const { x: xNew, y: yNew } = data; | const { x: xNew, y: yNew } = data; | ||||
@@ -309,26 +309,16 @@ module.exports = { | |||||
return false; | 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); | 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) | //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; | 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; | return; | ||||
player.socket.emit('event', { | 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) { | 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++) { | 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) { | updateObject: async function (msg) { | ||||
let player = this.objects.find(p => p.id === msg.serverId); | let player = this.objects.find(p => p.id === msg.serverId); | ||||
if (!player) | if (!player) | ||||
@@ -324,6 +347,13 @@ module.exports = { | |||||
if ((o.update) && (!o.destroyed)) | if ((o.update) && (!o.destroyed)) | ||||
o.update(); | o.update(); | ||||
//When objects are sent to other zones, we destroy them immediately (thhrough sendObjToZone) | |||||
if (o.forceDestroy) { | |||||
i--; | |||||
len--; | |||||
continue; | |||||
} | |||||
if (o.ttl) { | if (o.ttl) { | ||||
o.ttl--; | o.ttl--; | ||||
if (!o.ttl) | if (!o.ttl) | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"name": "isleward_server", | "name": "isleward_server", | ||||
"version": "0.10.5", | |||||
"version": "0.10.6", | |||||
"description": "isleward", | "description": "isleward", | ||||
"dependencies": { | "dependencies": { | ||||
"axios": "^0.22.0", | "axios": "^0.22.0", | ||||
@@ -11,7 +11,7 @@ | |||||
"image-size": "^1.0.0", | "image-size": "^1.0.0", | ||||
"rethinkdbdash": "^2.3.31", | "rethinkdbdash": "^2.3.31", | ||||
"socket.io": "^4.2.0", | "socket.io": "^4.2.0", | ||||
"universal-analytics": "^0.4.23" | |||||
"universal-analytics": "^0.5.1" | |||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"babel-eslint": "^10.1.0", | "babel-eslint": "^10.1.0", | ||||
@@ -77,8 +77,11 @@ module.exports = { | |||||
(['getCharacterList', 'getCharacter', 'deleteCharacter'].indexOf(msg.method) === -1) | (['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; | return; | ||||
@@ -18,13 +18,14 @@ const routerConfig = { | |||||
wardrobe: ['open', 'apply'], | wardrobe: ['open', 'apply'], | ||||
stats: ['respawn'], | stats: ['respawn'], | ||||
passives: ['tickNode', 'untickNode'], | passives: ['tickNode', 'untickNode'], | ||||
workbench: ['open', 'craft', 'getRecipe'], | |||||
player: ['notifyServerUiReady'] | |||||
workbench: ['open', 'craft', 'getRecipe'] | |||||
}, | }, | ||||
globalAllowed: { | globalAllowed: { | ||||
clientConfig: ['getClientConfig'], | clientConfig: ['getClientConfig'], | ||||
leaderboard: ['requestList'], | leaderboard: ['requestList'], | ||||
cons: ['unzone'] | |||||
cons: ['unzone'], | |||||
rezoneManager: ['clientAck'], | |||||
instancer: ['clientAck'] | |||||
}, | }, | ||||
allowTargetId: { | allowTargetId: { | ||||
door: ['lock', 'unlock'], | door: ['lock', 'unlock'], | ||||
@@ -128,15 +128,16 @@ module.exports = { | |||||
mapList.mapList.filter(m => !m.disabled).forEach(m => this.spawnMap(m)); | mapList.mapList.filter(m => !m.disabled).forEach(m => this.spawnMap(m)); | ||||
}, | }, | ||||
spawnMap: function (map) { | spawnMap: function (map) { | ||||
let worker = childProcess.fork('./world/worker'); | |||||
let thread = { | |||||
const worker = childProcess.fork('./world/worker', [map.name]); | |||||
const thread = { | |||||
id: this.nextId++, | id: this.nextId++, | ||||
name: map.name, | name: map.name, | ||||
path: map.path, | 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) { | worker.on('message', function (m) { | ||||
onMessage(m); | onMessage(m); | ||||
}); | }); | ||||
@@ -174,11 +175,11 @@ module.exports = { | |||||
}, | }, | ||||
event: function (thread, message) { | event: function (thread, message) { | ||||
objects.sendEvent(message); | |||||
objects.sendEvent(message, thread); | |||||
}, | }, | ||||
events: function (thread, message) { | events: function (thread, message) { | ||||
objects.sendEvents(message); | |||||
objects.sendEvents(message, thread); | |||||
}, | }, | ||||
object: function (thread, message) { | object: function (thread, message) { | ||||
@@ -13,6 +13,9 @@ let eventEmitter = require('../misc/events'); | |||||
const mods = require('../misc/mods'); | const mods = require('../misc/mods'); | ||||
const transactions = require('../security/transactions'); | const transactions = require('../security/transactions'); | ||||
//Own helpers | |||||
const { stageZoneIn, unstageZoneIn, clientAck } = require('./instancer/handshakes'); | |||||
module.exports = { | module.exports = { | ||||
instances: [], | instances: [], | ||||
zoneId: -1, | zoneId: -1, | ||||
@@ -65,6 +68,9 @@ module.exports = { | |||||
[resourceSpawner, syncer, objects, questBuilder, events].forEach(i => i.init(fakeInstance)); | [resourceSpawner, syncer, objects, questBuilder, events].forEach(i => i.init(fakeInstance)); | ||||
this.tick(); | this.tick(); | ||||
this.clientAck = clientAck; | |||||
eventEmitter.on('removeObject', unstageZoneIn); | |||||
}, | }, | ||||
startRegen: function (respawnMap, respawnPos) { | startRegen: function (respawnMap, respawnPos) { | ||||
@@ -211,21 +217,24 @@ module.exports = { | |||||
obj.spawn = map.spawn; | 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) { | onAddObject: function (obj) { | ||||
if (obj.player) { | if (obj.player) { | ||||
obj.stats.onLogin(); | obj.stats.onLogin(); | ||||
eventEmitter.emit('onAfterPlayerEnterZone', obj); | |||||
eventEmitter.emit('onAfterPlayerEnterZone', obj, { isTransfer: false }); | |||||
} | } | ||||
questBuilder.obtain(obj); | questBuilder.obtain(obj); | ||||
@@ -302,6 +311,10 @@ module.exports = { | |||||
return; | 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; | let obj = msg.obj; | ||||
obj = objects.find(o => o.serverId === obj.id); | obj = objects.find(o => o.serverId === obj.id); | ||||
if (!obj) { | 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 { | try { | ||||
chats = require('../' + this.path + '/' + this.name + '/chats'); | chats = require('../' + this.path + '/' + this.name + '/chats'); | ||||
} catch (e) {} | } 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; | let dialogues = null; | ||||
try { | try { | ||||
@@ -50,6 +50,8 @@ module.exports = { | |||||
if (cpnMob.patrol) | if (cpnMob.patrol) | ||||
cpnMob.walkDistance = 1; | cpnMob.walkDistance = 1; | ||||
cpnMob.needLos = blueprint.needLos; | |||||
let spells = extend([], blueprint.spells); | let spells = extend([], blueprint.spells); | ||||
spells.forEach(s => { | spells.forEach(s => { | ||||
if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell]) | if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell]) | ||||
@@ -179,21 +181,6 @@ module.exports = { | |||||
s.dmgMult = s.name ? dmgMult / 3 : dmgMult; | s.dmgMult = s.name ? dmgMult / 3 : dmgMult; | ||||
s.statType = preferStat; | s.statType = preferStat; | ||||
s.manaCost = 0; | 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 | //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', { | this.syncer.queue('onGetObject', { | ||||
id: obj.id, | id: obj.id, | ||||
performLast: true, | |||||
components: [spawnAnimation] | components: [spawnAnimation] | ||||
}, -1); | }, -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 () { | send: function () { | ||||
if (!this.dirty) | if (!this.dirty) | ||||
return; | return; | ||||
@@ -5,6 +5,7 @@ global.consts = require('../config/consts'); | |||||
global.instancer = require('./instancer'); | global.instancer = require('./instancer'); | ||||
global.eventManager = require('../events/events'); | global.eventManager = require('../events/events'); | ||||
global.clientConfig = require('../config/clientConfig'); | global.clientConfig = require('../config/clientConfig'); | ||||
global.rezoneManager = require('./rezoneManager'); | |||||
const components = require('../components/components'); | const components = require('../components/components'); | ||||
const mods = require('../misc/mods'); | const mods = require('../misc/mods'); | ||||
@@ -21,6 +22,8 @@ const itemEffects = require('../items/itemEffects'); | |||||
const profanities = require('../misc/profanities'); | const profanities = require('../misc/profanities'); | ||||
const eventEmitter = require('../misc/events'); | const eventEmitter = require('../misc/events'); | ||||
instancer.mapName = process.argv[2]; | |||||
let onCpnsReady = async function () { | let onCpnsReady = async function () { | ||||
factions.init(); | factions.init(); | ||||
skins.init(); | skins.init(); | ||||
@@ -33,6 +36,8 @@ let onCpnsReady = async function () { | |||||
recipes.init(); | recipes.init(); | ||||
itemEffects.init(); | itemEffects.init(); | ||||
profanities.init(); | profanities.init(); | ||||
rezoneManager.init(); | |||||
await clientConfig.init(); | await clientConfig.init(); | ||||
process.send({ | process.send({ | ||||