Notify the client when entering/exiting combat Closes #1935 See merge request Isleward/isleward!599merge-requests/599/merge
@@ -4,7 +4,7 @@ define([ | |||
'js/rendering/renderer', | |||
'js/objects/objects', | |||
'js/rendering/effects', | |||
'js/rendering/numbers', | |||
'js/rendering/floatingText', | |||
'js/input', | |||
'js/system/events', | |||
'js/resources', | |||
@@ -19,7 +19,7 @@ define([ | |||
renderer, | |||
objects, | |||
effects, | |||
numbers, | |||
floatingText, | |||
input, | |||
events, | |||
resources, | |||
@@ -72,7 +72,7 @@ define([ | |||
await resources.init(); | |||
await components.init(); | |||
events.emit('onResourcesLoaded'); | |||
this.start(); | |||
@@ -90,7 +90,7 @@ define([ | |||
renderer.init(); | |||
input.init(); | |||
numbers.init(); | |||
floatingText.init(); | |||
uiFactory.init(null); | |||
@@ -127,7 +127,7 @@ define([ | |||
objects.update(); | |||
renderer.update(); | |||
uiFactory.update(); | |||
numbers.update(); | |||
floatingText.update(); | |||
renderer.render(); | |||
@@ -0,0 +1,183 @@ | |||
define([ | |||
'js/system/events', | |||
'js/objects/objects', | |||
'js/rendering/renderer', | |||
'js/config' | |||
], function ( | |||
events, | |||
objects, | |||
renderer, | |||
config | |||
) { | |||
//Create an object of the form: { elementName: elementIntegerColor, ... } from corresponding variable values. | |||
// These variables are defiend in main.less and take the form: var(--color-element-elementName) | |||
const elementColors = Object.fromEntries( | |||
['default', 'arcane', 'frost', 'fire', 'holy', 'poison'].map(e => { | |||
const variableName = `--color-element-${e}`; | |||
const variableValue = getComputedStyle(document.documentElement).getPropertyValue(variableName); | |||
const integerColor = `0x${variableValue.replace('#', '')}`; | |||
return [e, integerColor]; | |||
}) | |||
); | |||
const shouldShowEvent = msg => { | |||
const { target, source, targetMaster, sourceMaster } = msg; | |||
const { player: { id } } = window; | |||
const shouldShow = ( | |||
(id === target) || | |||
(id === source) || | |||
(id === targetMaster) || | |||
(id === sourceMaster) | |||
); | |||
return shouldShow; | |||
}; | |||
const getObjectPosition = options => { | |||
let { targetId } = options; | |||
if (typeof targetId === 'undefined') | |||
targetId = window.player.id; | |||
//Find target from id | |||
const target = objects.objects.find(o => o.id === targetId); | |||
if (!target || !target.isVisible) | |||
return { success: false }; | |||
return { x: target.x, y: target.y, success: true }; | |||
}; | |||
return { | |||
list: [], | |||
init: function () { | |||
events.on('onGetDamage', this.onGetDamage.bind(this)); | |||
events.on('onCombatEvent', this.onCombatEvent.bind(this)); | |||
}, | |||
onGetDamage: function (msg) { | |||
if (config.damageNumbers === 'off' || !shouldShowEvent(msg)) | |||
return; | |||
const { target, amount, crit, heal, element = 'default' } = msg; | |||
const div = ((~~(amount * 10) / 10) > 0) ? 10 : 100; | |||
const text = (heal ? '+' : '') + (~~(amount * div) / div); | |||
const colorVariableName = config.damageNumbers === 'element' ? element : 'default'; | |||
const textConfig = { | |||
targetId: target, | |||
text, | |||
color: elementColors[colorVariableName], | |||
fontSize: crit ? 22 : 18, | |||
tileOffsetX: heal ? -1 : 1, | |||
tileOffsetY: 0.75, | |||
floatDirection: -1 | |||
}; | |||
this.build(textConfig); | |||
}, | |||
onCombatEvent: function (msg) { | |||
if (!msg.floatingText) | |||
return; | |||
if (msg.type === 'damageAvoided' && !shouldShowEvent(msg)) | |||
return; | |||
const textConfig = { | |||
targetId: msg.target, | |||
text: msg.floatingText, | |||
tileOffsetX: 0, | |||
tileOffsetY: 1.25, | |||
floatDirection: 1 | |||
}; | |||
this.build(textConfig); | |||
}, | |||
//Options: | |||
// text | |||
// color -- Color string in the form of "0xFFFFFF". Defaults to some gray | |||
// fontSize -- Defaults to 18 | |||
// x, y -- Position to create the text at | |||
// targetId -- Create the text at this object if no x or y is provided. Defaults to the current player | |||
// tileOffsetX -- Offset in tiles. Defaults to (1, 0.75) | |||
// tileOffsetY | |||
// ttl -- Defaults to 35 | |||
// floatDirection -- Defaults to -1 (up) | |||
build: function (options) { | |||
let { x, y } = options; | |||
if (typeof x === 'undefined' || typeof y === 'undefined') { | |||
let success; | |||
({ x, y, success } = getObjectPosition(options)); | |||
if (!success) | |||
return; | |||
} | |||
const { | |||
text, | |||
tileOffsetX = 1, | |||
tileOffsetY = 0.75, | |||
ttl = 35, | |||
fontSize = 18, | |||
floatDirection = -1, | |||
color = elementColors.default | |||
} = options; | |||
const textObj = { | |||
text, | |||
x: (x * scale) + (tileOffsetX * scale), | |||
y: (y * scale) + (tileOffsetY * scale), | |||
ttl, | |||
ttlMax: ttl, | |||
floatDirection | |||
}; | |||
textObj.sprite = renderer.buildText({ | |||
fontSize, | |||
layerName: 'effects', | |||
x: textObj.x, | |||
y: textObj.y, | |||
text, | |||
color | |||
}); | |||
this.list.push(textObj); | |||
}, | |||
update: function () { | |||
const list = this.list; | |||
let lLen = list.length; | |||
for (let i = 0; i < lLen; i++) { | |||
const l = list[i]; | |||
l.ttl--; | |||
if (l.ttl === 0) { | |||
renderer.destroyObject({ | |||
layerName: 'effects', | |||
sprite: l.sprite | |||
}); | |||
list.splice(i, 1); | |||
i--; | |||
lLen--; | |||
continue; | |||
} | |||
l.y += l.floatDirection; | |||
const alpha = l.ttl / l.ttlMax; | |||
l.sprite.x = ~~(l.x / scaleMult) * scaleMult; | |||
l.sprite.y = ~~(l.y / scaleMult) * scaleMult; | |||
l.sprite.alpha = alpha; | |||
} | |||
} | |||
}; | |||
}); |
@@ -1,118 +0,0 @@ | |||
define([ | |||
'js/system/events', | |||
'js/objects/objects', | |||
'js/rendering/renderer', | |||
'js/config' | |||
], function ( | |||
events, | |||
objects, | |||
renderer, | |||
config | |||
) { | |||
//Create an object of the form: { elementName: elementIntegerColor, ... } from corresponding variable values. | |||
// These variables are defiend in main.less and take the form: var(--color-element-elementName) | |||
const elementColors = Object.fromEntries( | |||
['default', 'arcane', 'frost', 'fire', 'holy', 'poison'].map(e => { | |||
const variableName = `--color-element-${e}`; | |||
const variableValue = getComputedStyle(document.documentElement).getPropertyValue(variableName); | |||
const integerColor = `0x${variableValue.replace('#', '')}`; | |||
return [e, integerColor]; | |||
}) | |||
); | |||
return { | |||
list: [], | |||
init: function () { | |||
events.on('onGetDamage', this.onGetDamage.bind(this)); | |||
}, | |||
onGetDamage: function (msg) { | |||
if (config.damageNumbers === 'off') | |||
return; | |||
let target = objects.objects.find(function (o) { | |||
return (o.id === msg.id); | |||
}); | |||
if (!target || !target.isVisible) | |||
return; | |||
let ttl = 35; | |||
let numberObj = { | |||
obj: target, | |||
amount: msg.amount, | |||
x: (target.x * scale), | |||
y: (target.y * scale) + scale - (scale / 4), | |||
ttl: ttl, | |||
ttlMax: ttl, | |||
event: msg.event, | |||
text: msg.text, | |||
crit: msg.crit, | |||
heal: msg.heal, | |||
element: msg.element | |||
}; | |||
if (numberObj.event) | |||
numberObj.y += (scale / 2); | |||
else if (numberObj.heal) | |||
numberObj.x -= scale; | |||
else | |||
numberObj.x += scale; | |||
let text = numberObj.text; | |||
if (!numberObj.event) { | |||
let amount = numberObj.amount; | |||
let div = ((~~(amount * 10) / 10) > 0) ? 10 : 100; | |||
text = (numberObj.heal ? '+' : '') + (~~(amount * div) / div); | |||
} | |||
const colorVariableName = config.damageNumbers === 'element' ? numberObj.element : 'default'; | |||
numberObj.sprite = renderer.buildText({ | |||
fontSize: numberObj.crit ? 22 : 18, | |||
layerName: 'effects', | |||
x: numberObj.x, | |||
y: numberObj.y, | |||
text: text, | |||
color: elementColors[colorVariableName] | |||
}); | |||
this.list.push(numberObj); | |||
}, | |||
update: function () { | |||
let list = this.list; | |||
let lLen = list.length; | |||
for (let i = 0; i < lLen; i++) { | |||
let l = list[i]; | |||
l.ttl--; | |||
if (l.ttl === 0) { | |||
renderer.destroyObject({ | |||
layerName: 'effects', | |||
sprite: l.sprite | |||
}); | |||
list.splice(i, 1); | |||
i--; | |||
lLen--; | |||
continue; | |||
} | |||
if (l.event) | |||
l.y += 1; | |||
else | |||
l.y -= 1; | |||
let alpha = l.ttl / l.ttlMax; | |||
l.sprite.x = ~~(l.x / scaleMult) * scaleMult; | |||
l.sprite.y = ~~(l.y / scaleMult) * scaleMult; | |||
l.sprite.alpha = alpha; | |||
} | |||
} | |||
}; | |||
}); |
@@ -1,9 +1,9 @@ | |||
define([ | |||
'js/system/events', | |||
'js/rendering/numbers' | |||
'js/rendering/floatingText' | |||
], function ( | |||
events, | |||
numbers | |||
floatingText | |||
) { | |||
const defaultBuffIcons = { | |||
stunned: [4, 0] | |||
@@ -11,7 +11,7 @@ define([ | |||
const effectBase = { | |||
init: function () { | |||
this.defaultDamageText(false); | |||
this.showDefaultCombatEvent(false); | |||
if (this.self && defaultBuffIcons[this.type]) { | |||
events.emit('onGetEffectIcon', { | |||
@@ -23,7 +23,7 @@ define([ | |||
destroy: function () { | |||
if (!this.obj.destroyed) | |||
this.defaultDamageText(true); | |||
this.showDefaultCombatEvent(true); | |||
if (this.self && defaultBuffIcons[this.type]) { | |||
events.emit('onRemoveEffectIcon', { | |||
@@ -32,11 +32,11 @@ define([ | |||
} | |||
}, | |||
defaultDamageText: function (removing) { | |||
numbers.onGetDamage({ | |||
id: this.obj.id, | |||
event: true, | |||
text: (removing ? '-' : '+') + this.type | |||
showDefaultCombatEvent: function (removing) { | |||
floatingText.onCombatEvent({ | |||
target: this.obj.id, | |||
type: 'effect', | |||
floatingText: (removing ? '-' : '+') + this.type | |||
}); | |||
} | |||
}; | |||
@@ -47,7 +47,7 @@ define([ | |||
effects: [], | |||
templates: { | |||
}, | |||
init: function (blueprint) { | |||
@@ -64,7 +64,7 @@ define([ | |||
if (effect.init) | |||
effect.init(); | |||
return effect; | |||
}, | |||
@@ -97,7 +97,7 @@ define([ | |||
if (effect.extend) | |||
effect.extend(u.data); | |||
else { | |||
for (let p in u.data) | |||
for (let p in u.data) | |||
effect[p] = u.data[p]; | |||
} | |||
}); | |||
@@ -50,7 +50,7 @@ define([ | |||
cell: cell | |||
}); | |||
this.defaultDamageText(); | |||
this.showDefaultCombatEvent(); | |||
if (this.self && buffIcons[type]) { | |||
events.emit('onGetEffectIcon', { | |||
@@ -113,7 +113,7 @@ define([ | |||
sprite: this.sprite | |||
}); | |||
this.defaultDamageText(true); | |||
this.showDefaultCombatEvent(true); | |||
if (this.self && buffIcons[type]) { | |||
events.emit('onRemoveEffectIcon', { | |||
@@ -1,3 +1,5 @@ | |||
const combatEvents = require('../config/combatEvents'); | |||
const configThreatCeiling = { | |||
regular: 1, | |||
rare: 0.5 | |||
@@ -21,6 +23,8 @@ module.exports = { | |||
//Certain summoned minions need to despawn when they lose their last target | |||
dieOnAggroClear: false, | |||
inCombat: false, | |||
init: function (blueprint) { | |||
this.physics = this.obj.instance.physics; | |||
@@ -227,6 +231,8 @@ module.exports = { | |||
if (exists.threat > this.threatCeiling) | |||
exists.threat = this.threatCeiling; | |||
this.updateInCombat(); | |||
return true; | |||
}, | |||
@@ -294,6 +300,8 @@ module.exports = { | |||
if ((this.list.length === 0) && (this.obj.mob) && (!this.obj.follower)) | |||
this.obj.stats.resetHp(); | |||
this.updateInCombat(); | |||
}, | |||
sortThreat: function () { | |||
@@ -406,5 +414,21 @@ module.exports = { | |||
isInCombat: function () { | |||
return this.list.length > 0; | |||
}, | |||
updateInCombat: function () { | |||
const prev = this.inCombat; | |||
this.inCombat = this.isInCombat(); | |||
if (!this.obj.player) | |||
return; | |||
if (prev !== this.inCombat) { | |||
this.obj.instance.syncer.queueInRange('onCombatEvent', { | |||
type: combatEvents.aggro, | |||
id: this.obj.id, | |||
inCombat: this.inCombat | |||
}, this.obj.id); | |||
} | |||
} | |||
}; |
@@ -1,5 +1,6 @@ | |||
const events = require('../../misc/events'); | |||
const { isItemStackable } = require('./helpers'); | |||
const combatEvents = require('../../config/combatEvents'); | |||
const getNextId = items => { | |||
let id = 0; | |||
@@ -101,11 +102,11 @@ module.exports = (cpnInv, item, hideMessage, noStack, hideAlert, createBagIfFull | |||
}]; | |||
if (!hideAlert) { | |||
obj.instance.syncer.queue('onGetDamage', { | |||
id: obj.id, | |||
event: true, | |||
text: 'loot' | |||
}, -1); | |||
obj.instance.syncer.queue('onCombatEvent', { | |||
target: obj.id, | |||
type: combatEvents.loot, | |||
floatingText: 'loot' | |||
}, [obj.serverId]); | |||
} | |||
if (!hideMessage) { | |||
@@ -137,7 +137,7 @@ module.exports = { | |||
}, | |||
hasSeen: function (id) { | |||
return (this.seen.indexOf(id) > -1); | |||
return this.seen.includes(id); | |||
}, | |||
see: function (id) { | |||
@@ -4,6 +4,7 @@ const die = require('./stats/die'); | |||
let animations = require('../config/animations'); | |||
let spirits = require('../config/spirits'); | |||
let scheduler = require('../misc/scheduler'); | |||
const combatEvents = require('../config/combatEvents'); | |||
let baseStats = { | |||
mana: 20, | |||
@@ -278,11 +279,11 @@ module.exports = { | |||
obj.syncer.setObject(true, 'stats', 'values', 'xp', values.xp); | |||
this.syncer.queue('onGetDamage', { | |||
id: obj.id, | |||
event: true, | |||
text: '+' + amount + ' xp' | |||
}, -1); | |||
this.syncer.queue('onCombatEvent', { | |||
target: obj.id, | |||
type: combatEvents.xp, | |||
floatingText: '+' + amount + ' xp' | |||
}, [obj.serverId]); | |||
let syncO = {}; | |||
let didLevelUp = false; | |||
@@ -306,11 +307,11 @@ module.exports = { | |||
obj.spellbook.calcDps(); | |||
this.syncer.queue('onGetDamage', { | |||
id: obj.id, | |||
event: true, | |||
text: 'level up' | |||
}, -1); | |||
this.syncer.queue('onCombatEvent', { | |||
target: obj.id, | |||
type: combatEvents.levelUp, | |||
floatingText: 'level up' | |||
}, [obj.serverId]); | |||
syncO.level = values.level; | |||
@@ -537,7 +538,7 @@ module.exports = { | |||
damage.dealt = amount; | |||
let msg = { | |||
id: obj.id, | |||
target: obj.id, | |||
source: source.id, | |||
crit: damage.crit, | |||
amount: amount, | |||
@@ -545,34 +546,22 @@ module.exports = { | |||
}; | |||
this.values.hp -= amount; | |||
let recipients = []; | |||
if (obj.serverId) | |||
recipients.push(obj.serverId); | |||
if (source.serverId) | |||
recipients.push(source.serverId); | |||
if (source.follower && source.follower.master.serverId) { | |||
recipients.push(source.follower.master.serverId); | |||
msg.masterSource = source.follower.master.id; | |||
} | |||
if (obj.follower && obj.follower.master.serverId) { | |||
recipients.push(obj.follower.master.serverId); | |||
msg.masterId = obj.follower.master.id; | |||
} | |||
if (source.follower) | |||
msg.sourceMaster = source.follower.master.id; | |||
if (recipients.length) { | |||
if (!damage.blocked && !damage.dodged) | |||
this.syncer.queue('onGetDamage', msg, recipients); | |||
else { | |||
this.syncer.queue('onGetDamage', { | |||
id: obj.id, | |||
source: source.id, | |||
event: true, | |||
text: damage.blocked ? 'blocked' : 'dodged' | |||
}, recipients); | |||
} | |||
} | |||
if (obj.follower) | |||
msg.targetMaster = obj.follower.master.id; | |||
if (damage.blocked || damage.dodged) { | |||
this.syncer.queueInRange('onCombatEvent', { | |||
source: source.id, | |||
target: obj.id, | |||
type: combatEvents.damageAvoided, | |||
floatingText: damage.blocked ? 'blocked' : 'dodged' | |||
}, obj.id, source.id); | |||
} else | |||
this.syncer.queueInRange('onGetDamage', msg, obj.id, source.id); | |||
obj.aggro.tryEngage(source, amount, threatMult); | |||
@@ -624,14 +613,14 @@ module.exports = { | |||
if (source.serverId) | |||
recipients.push(source.serverId); | |||
if (recipients.length > 0) { | |||
this.syncer.queue('onGetDamage', { | |||
this.syncer.queueInRange('onGetDamage', { | |||
id: this.obj.id, | |||
source: source.id, | |||
heal: true, | |||
amount: amount, | |||
crit: heal.crit, | |||
element: heal.element | |||
}, recipients); | |||
}, this.obj.id, source.id); | |||
} | |||
//Add aggro to all our attackers | |||
@@ -26,12 +26,6 @@ const die = (cpnStats, deathSource) => { | |||
const { obj, syncer: syncerGlobal } = cpnStats; | |||
const { x, y, serverId, syncer } = obj; | |||
syncerGlobal.queue('onGetDamage', { | |||
id: obj.id, | |||
event: true, | |||
text: 'death' | |||
}, -1); | |||
syncer.set(true, null, 'dead', true); | |||
const syncO = syncer.o; | |||
@@ -0,0 +1,9 @@ | |||
const types = { | |||
damageAvoided: 'damageAvoided', | |||
loot: 'loot', | |||
xp: 'xp', | |||
levelUp: 'levelUp', | |||
aggro: 'aggro' | |||
}; | |||
module.exports = types; |
@@ -1,83 +0,0 @@ | |||
module.exports = { | |||
type: 'cocoon', | |||
cocoon: null, | |||
persist: true, | |||
init: function (source) { | |||
let obj = this.obj; | |||
let syncO = obj.syncer.o; | |||
obj.hidden = true; | |||
obj.nonSelectable = true; | |||
syncO.hidden = true; | |||
syncO.nonSelectable = true; | |||
this.cocoon = obj.instance.objects.buildObjects([{ | |||
name: 'cocoon', | |||
sheetName: 'objects', | |||
cell: 54, | |||
x: obj.x, | |||
y: obj.y, | |||
properties: { | |||
cpnAggro: { | |||
faction: source.aggro.faction | |||
}, | |||
cpnStats: { | |||
values: { | |||
hpMax: 10, | |||
hp: 10 | |||
} | |||
}, | |||
cpnEffects: {} | |||
} | |||
}]); | |||
this.cocoon.effects.addEffect({ | |||
events: { | |||
afterDeath: this.onDestroyCocoon.bind(this) | |||
} | |||
}); | |||
}, | |||
onDestroyCocoon: function () { | |||
this.destroyed = true; | |||
}, | |||
destroy: function () { | |||
let obj = this.obj; | |||
let syncO = obj.syncer.o; | |||
obj.hidden = false; | |||
obj.nonSelectable = false; | |||
syncO.hidden = false; | |||
syncO.nonSelectable = false; | |||
this.cocoon.destroyed = true; | |||
}, | |||
simplify: function () { | |||
return { | |||
type: 'cocoon', | |||
ttl: this.ttl | |||
}; | |||
}, | |||
events: { | |||
beforeMove: function (targetPos) { | |||
let obj = this.obj; | |||
targetPos.x = obj.x; | |||
targetPos.y = obj.y; | |||
}, | |||
beforeDealDamage: function (damage) { | |||
if (damage) | |||
damage.failed = true; | |||
}, | |||
beforeCastSpell: function (successObj) { | |||
successObj.success = false; | |||
} | |||
} | |||
}; |
@@ -1,18 +0,0 @@ | |||
module.exports = { | |||
type: 'reflectDamage', | |||
events: { | |||
beforeTakeDamage: function (damage, source) { | |||
damage.amount *= 0.5; | |||
source.stats.takeDamage(damage, this.threatMult, this.obj); | |||
damage.failed = true; | |||
this.obj.instance.syncer.queue('onGetDamage', { | |||
id: this.obj.id, | |||
event: true, | |||
text: 'reflect' | |||
}, -1); | |||
} | |||
} | |||
}; |
@@ -283,12 +283,6 @@ let spells = [{ | |||
description: 'Charges at a foe, dealing damage and stunning them for a short period.', | |||
icon: [3, 1], | |||
animation: 'raiseShield' | |||
}, { | |||
name: 'Reflect Damage', | |||
type: 'reflectdamage', | |||
description: 'Gain an ethereal shield that reflects damage until the buff wears off.', | |||
icon: [3, 2], | |||
animation: 'raiseShield' | |||
}, { | |||
name: 'Flurry', | |||
type: 'flurry', | |||
@@ -103,19 +103,11 @@ module.exports = { | |||
targetPos.y += offsetY; | |||
} | |||
let targetEffect = target.effects.addEffect({ | |||
target.effects.addEffect({ | |||
type: 'stunned', | |||
ttl: this.stunDuration | |||
}); | |||
if (targetEffect) { | |||
this.obj.instance.syncer.queue('onGetDamage', { | |||
id: target.id, | |||
event: true, | |||
text: 'stunned' | |||
}, -1); | |||
} | |||
if (this.animation) { | |||
this.obj.instance.syncer.queue('onGetObject', { | |||
id: this.obj.id, | |||
@@ -1,63 +0,0 @@ | |||
module.exports = { | |||
type: 'cocoon', | |||
cdMax: 7, | |||
manaCost: 0, | |||
range: 9, | |||
speed: 200, | |||
damage: 0, | |||
needLos: true, | |||
ttl: 75, | |||
cast: function (action) { | |||
let obj = this.obj; | |||
let target = action.target; | |||
let ttl = Math.sqrt(Math.pow(target.x - obj.x, 2) + Math.pow(target.y - obj.y, 2)) * this.speed; | |||
this.sendAnimation({ | |||
caster: this.obj.id, | |||
components: [{ | |||
idSource: this.obj.id, | |||
idTarget: target.id, | |||
type: 'projectile', | |||
ttl: ttl, | |||
particles: this.particles | |||
}, { | |||
type: 'attackAnimation', | |||
layer: 'projectiles', | |||
loop: -1, | |||
row: this.row, | |||
col: this.col | |||
}] | |||
}); | |||
this.sendBump(target); | |||
this.queueCallback(this.explode.bind(this, target), ttl); | |||
return true; | |||
}, | |||
explode: function (target) { | |||
if (this.obj.destroyed) | |||
return; | |||
target.effects.addEffect({ | |||
type: 'cocoon', | |||
ttl: this.ttl, | |||
source: this.obj | |||
}); | |||
this.obj.instance.syncer.queue('onGetDamage', { | |||
id: target.id, | |||
event: true, | |||
text: 'cocooned' | |||
}, -1); | |||
target.aggro.tryEngage(this.obj, this.damage, this.threatMult); | |||
} | |||
}; |
@@ -1,41 +0,0 @@ | |||
module.exports = { | |||
type: 'reflectDamage', | |||
cdMax: 0, | |||
manaCost: 0, | |||
duration: 10, | |||
targetGround: true, | |||
cast: function (action) { | |||
let selfEffect = this.obj.effects.addEffect({ | |||
type: 'reflectDamage', | |||
threatMult: this.threatMult | |||
}); | |||
let ttl = this.duration * consts.tickTime; | |||
if (this.animation) { | |||
this.obj.instance.syncer.queue('onGetObject', { | |||
id: this.obj.id, | |||
components: [{ | |||
type: 'animation', | |||
template: this.animation | |||
}] | |||
}, -1); | |||
} | |||
this.queueCallback(this.endEffect.bind(this, selfEffect), ttl - 50); | |||
return true; | |||
}, | |||
endEffect: function (selfEffect) { | |||
if (this.obj.destroyed) | |||
return; | |||
let obj = this.obj; | |||
obj.effects.removeEffect(selfEffect.id); | |||
} | |||
}; |
@@ -155,6 +155,9 @@ module.exports = { | |||
this.send(); | |||
}, | |||
//Queues an event of type `event` with data `obj` to a list of players. | |||
// `to` is an array of the serverIds of players to send the event to. | |||
// If `to` is -1, the event will be sent to all players in the zone. | |||
queue: function (event, obj, to) { | |||
//Send to all players in zone? | |||
if (to === -1) { | |||
@@ -175,6 +178,38 @@ module.exports = { | |||
}); | |||
}, | |||
//Queues an event to all players who are close enough. | |||
// `eventTarget` and `eventSource` are optional object IDs. | |||
// If a player has seen either object, they are sent the event. | |||
// We pass object IDs instead of serverIds because we check | |||
// player.hasSeen which checks a list of object IDs. | |||
queueInRange: function (event, obj, eventTarget, eventSource) { | |||
const to = []; | |||
const oLen = this.objects.objects.length; | |||
for (let i = 0; i < oLen; i++) { | |||
const o = this.objects.objects[i]; | |||
const isMatch = ( | |||
o.player && | |||
( | |||
( | |||
eventTarget && | |||
o.player.hasSeen(eventTarget) | |||
) || | |||
( | |||
eventSource && | |||
o.player.hasSeen(eventSource) | |||
) | |||
) | |||
); | |||
if (isMatch) | |||
to.push(o.serverId); | |||
} | |||
this.queue(event, obj, to); | |||
}, | |||
flushForTarget: function (targetServerId) { | |||
const buffer = this.buffer; | |||