@@ -403,7 +403,44 @@ module.exports = { | |||||
try { | try { | ||||
let effectModule = require('../' + effectUrl); | let effectModule = require('../' + effectUrl); | ||||
e.events = effectModule.events; | e.events = effectModule.events; | ||||
if (effectModule.events.onGetText) | |||||
const { rolls } = e; | |||||
if (rolls.textTemplate) { | |||||
let text = rolls.textTemplate; | |||||
while (text.includes('((')) { | |||||
Object.entries(rolls).forEach(([k, v]) => { | |||||
text = text.replaceAll(`((${k}))`, v); | |||||
}); | |||||
if (rolls.applyEffect) { | |||||
Object.entries(rolls.applyEffect).forEach(([k, v]) => { | |||||
text = text.replaceAll(`((applyEffect.${k}))`, v); | |||||
}); | |||||
} | |||||
if (rolls.castSpell) { | |||||
Object.entries(rolls.castSpell).forEach(([k, v]) => { | |||||
text = text.replaceAll(`((castSpell.${k}))`, v); | |||||
}); | |||||
} | |||||
if (rolls.applyEffect?.scaleDamage) { | |||||
Object.entries(rolls.applyEffect.scaleDamage).forEach(([k, v]) => { | |||||
text = text.replaceAll(`((applyEffect.scaleDamage.${k}))`, v); | |||||
}); | |||||
} | |||||
if (rolls.castSpell?.scaleDamage) { | |||||
Object.entries(rolls.castSpell.scaleDamage).forEach(([k, v]) => { | |||||
text = text.replaceAll(`((castSpell.scaleDamage.${k}))`, v); | |||||
}); | |||||
} | |||||
} | |||||
e.text = text; | |||||
} else if (effectModule.events.onGetText) | |||||
e.text = effectModule.events.onGetText(item, e); | e.text = effectModule.events.onGetText(item, e); | ||||
} catch (error) { | } catch (error) { | ||||
_.log(`Effect not found: ${e.type}`); | _.log(`Effect not found: ${e.type}`); | ||||
@@ -536,7 +536,9 @@ module.exports = { | |||||
target: Target object (heal.target) | target: Target object (heal.target) | ||||
spell: Optional spell object that caused this event | spell: Optional spell object that caused this event | ||||
*/ | */ | ||||
getHp: function (heal, source, event) { | |||||
getHp: function (event) { | |||||
const { heal, source } = event; | |||||
let amount = heal.amount; | let amount = heal.amount; | ||||
if (amount === 0) | if (amount === 0) | ||||
return; | return; | ||||
@@ -548,48 +550,45 @@ module.exports = { | |||||
let values = this.values; | let values = this.values; | ||||
let hpMax = values.hpMax; | let hpMax = values.hpMax; | ||||
if (values.hp >= hpMax) | |||||
return; | |||||
if (hpMax - values.hp < amount) | |||||
amount = hpMax - values.hp; | |||||
values.hp += amount; | |||||
if (values.hp > hpMax) | |||||
values.hp = hpMax; | |||||
let recipients = []; | |||||
if (this.obj.serverId) | |||||
recipients.push(this.obj.serverId); | |||||
if (source.serverId) | |||||
recipients.push(source.serverId); | |||||
if (recipients.length > 0) { | |||||
this.syncer.queue('onGetDamage', { | |||||
id: this.obj.id, | |||||
source: source.id, | |||||
heal: true, | |||||
amount: amount, | |||||
crit: heal.crit, | |||||
element: heal.element | |||||
}, recipients); | |||||
} | |||||
if (values.hp < hpMax) { | |||||
if (hpMax - values.hp < amount) | |||||
amount = hpMax - values.hp; | |||||
values.hp += amount; | |||||
if (values.hp > hpMax) | |||||
values.hp = hpMax; | |||||
let recipients = []; | |||||
if (this.obj.serverId) | |||||
recipients.push(this.obj.serverId); | |||||
if (source.serverId) | |||||
recipients.push(source.serverId); | |||||
if (recipients.length > 0) { | |||||
this.syncer.queue('onGetDamage', { | |||||
id: this.obj.id, | |||||
source: source.id, | |||||
heal: true, | |||||
amount: amount, | |||||
crit: heal.crit, | |||||
element: heal.element | |||||
}, recipients); | |||||
} | |||||
//Add aggro to all our attackers | |||||
let threat = amount * 0.4 * threatMult; | |||||
if (threat !== 0) { | |||||
let aggroList = this.obj.aggro.list; | |||||
let aLen = aggroList.length; | |||||
for (let i = 0; i < aLen; i++) { | |||||
let a = aggroList[i].obj; | |||||
a.aggro.tryEngage(source, threat); | |||||
//Add aggro to all our attackers | |||||
let threat = amount * 0.4 * threatMult; | |||||
if (threat !== 0) { | |||||
let aggroList = this.obj.aggro.list; | |||||
let aLen = aggroList.length; | |||||
for (let i = 0; i < aLen; i++) { | |||||
let a = aggroList[i].obj; | |||||
a.aggro.tryEngage(source, threat); | |||||
} | |||||
} | } | ||||
} | |||||
this.obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp); | |||||
this.obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp); | |||||
} | |||||
//We want to eventually replace the first two args with the event object | |||||
// For now, only fire the event when the event object is specified. | |||||
if (!heal.noEvents && event) | |||||
if (!heal.noEvents) | |||||
source.fireEvent('afterGiveHp', event); | source.fireEvent('afterGiveHp', event); | ||||
}, | }, | ||||
@@ -730,7 +729,11 @@ module.exports = { | |||||
if (target === obj || !lifeOnHit) | if (target === obj || !lifeOnHit) | ||||
return; | return; | ||||
this.getHp({ amount: lifeOnHit }, obj); | |||||
this.getHp({ | |||||
event: { amount: lifeOnHit }, | |||||
source: obj, | |||||
target: obj | |||||
}); | |||||
} | } | ||||
} | } | ||||
}; | }; |
@@ -6,7 +6,11 @@ module.exports = { | |||||
events: { | events: { | ||||
afterDealDamage: function ({ damage, target }) { | afterDealDamage: function ({ damage, target }) { | ||||
damage.dealt *= 0.5; | damage.dealt *= 0.5; | ||||
this.obj.stats.getHp(damage, this.obj); | |||||
this.obj.stats.getHp({ | |||||
event: damage, | |||||
source: this.obj, | |||||
target: this.obj | |||||
}); | |||||
} | } | ||||
} | } | ||||
}; | }; |
@@ -1,37 +1,95 @@ | |||||
//Imports | |||||
const spellBaseTemplate = require('../spells/spellTemplate'); | const spellBaseTemplate = require('../spells/spellTemplate'); | ||||
module.exports = { | |||||
events: { | |||||
onGetText: function (item) { | |||||
const { rolls: { chance, spell, damage = 1 } } = item.effects.find(e => (e.type === 'castSpellOnHit')); | |||||
//Helpers | |||||
const getItemEffect = item => { | |||||
return item.effects.find(e => (e.type === 'castSpellOnHit')); | |||||
}; | |||||
return `${chance}% chance to cast a ${damage} damage ${spell} on hit`; | |||||
}, | |||||
const doesEventMatch = (firedEvent, eventCondition) => { | |||||
if ( | |||||
!firedEvent || | |||||
( | |||||
!eventCondition.targetNotSelf && | |||||
firedEvent.target === firedEvent.source | |||||
) | |||||
) | |||||
return false; | |||||
const foundNonMatch = Object.entries(eventCondition).some(([k, v]) => { | |||||
if (v !== null && typeof(v) === 'object') { | |||||
if (!doesEventMatch(firedEvent[k], v)) | |||||
return true; | |||||
return false; | |||||
} | |||||
return firedEvent[k] !== v; | |||||
}); | |||||
return !foundNonMatch; | |||||
}; | |||||
const shouldApplyEffect = (itemEffect, firedEvent, firedEventName) => { | |||||
const { rolls: { chance, combatEvent: { [firedEventName]: eventCondition } } } = itemEffect; | |||||
if (!eventCondition || !doesEventMatch(firedEvent, eventCondition)) | |||||
return false; | |||||
if (chance !== undefined && Math.random() * 100 >= chance) | |||||
return false; | |||||
return true; | |||||
}; | |||||
const handler = (obj, item, event, firedEventName) => { | |||||
const itemEffect = getItemEffect(item); | |||||
afterDealDamage: function (item, { damage, target }) { | |||||
//Should only proc for attacks...this is kind of a hack | |||||
const { element } = damage; | |||||
if (element) | |||||
return; | |||||
const { rolls: { chance, spell, statType = 'dex', damage: spellDamage = 1 } } = item.effects.find(e => (e.type === 'castSpellOnHit')); | |||||
const chanceRoll = Math.random() * 100; | |||||
if (chanceRoll >= chance) | |||||
return; | |||||
const spellName = 'spell' + spell.replace(/./, spell.toUpperCase()[0]); | |||||
const spellTemplate = require(`../spells/${spellName}`); | |||||
const builtSpell = extend({ obj: this }, spellBaseTemplate, spellTemplate, { | |||||
name: spellName, | |||||
noEvents: true, | |||||
statType, | |||||
damage: spellDamage, | |||||
duration: 5, | |||||
radius: 1 | |||||
if (!shouldApplyEffect(itemEffect, event, firedEventName)) | |||||
return; | |||||
const { rolls: { castSpell, castTarget } } = itemEffect; | |||||
const spellConfig = extend({}, castSpell); | |||||
spellConfig.noEvents = true; | |||||
const scaleDamage = spellConfig.scaleDamage; | |||||
delete spellConfig.scaleDamage; | |||||
if (scaleDamage) { | |||||
if (scaleDamage.s_useOriginal) { | |||||
scaleDamage.s_useOriginal.forEach(s => { | |||||
spellConfig[s] = event.spell[s]; | |||||
}); | }); | ||||
} | |||||
if (scaleDamage.percentage) | |||||
spellConfig.damage *= (scaleDamage.percentage / 100); | |||||
} | |||||
const spellName = 'spell' + spellConfig.type.replace(/./, spellConfig.type.toUpperCase()[0]); | |||||
const spellTemplate = require(`../spells/${spellName}`); | |||||
const builtSpell = extend({ obj }, spellBaseTemplate, spellTemplate, spellConfig); | |||||
let target = event.target; | |||||
if (castTarget === 'self') | |||||
target = obj; | |||||
else if (castTarget === 'none') | |||||
target = undefined; | |||||
builtSpell.cast({ target }); | |||||
}; | |||||
//Effect | |||||
module.exports = { | |||||
events: { | |||||
afterGiveHp: function (item, event) { | |||||
handler(this, item, event, 'afterGiveHp'); | |||||
}, | |||||
builtSpell.cast(); | |||||
afterDealDamage: function (item, event) { | |||||
handler(this, item, event, 'afterDealDamage'); | |||||
} | } | ||||
} | } | ||||
}; | }; |
@@ -18,9 +18,13 @@ module.exports = { | |||||
amount = (cpnStats.values.hpMax / 100) * ~~amount.replace('%', ''); | amount = (cpnStats.values.hpMax / 100) * ~~amount.replace('%', ''); | ||||
cpnStats.getHp({ | cpnStats.getHp({ | ||||
amount: amount, | |||||
threatMult: 0 | |||||
}, item); | |||||
event: { | |||||
amount: amount, | |||||
threatMult: 0 | |||||
}, | |||||
source: cpnStats.obj, | |||||
target: cpnStats.obj | |||||
}); | |||||
} else | } else | ||||
cpnStats.addStat(stat, amount); | cpnStats.addStat(stat, amount); | ||||
}, | }, | ||||
@@ -34,8 +34,12 @@ module.exports = { | |||||
let amount = rolls.amount || ((damage.dealt / 100) * rolls.percentage); | let amount = rolls.amount || ((damage.dealt / 100) * rolls.percentage); | ||||
this.stats.getHp({ | this.stats.getHp({ | ||||
amount: amount | |||||
}, this); | |||||
event: { | |||||
amount: amount | |||||
}, | |||||
source: this, | |||||
target: this | |||||
}); | |||||
} | } | ||||
} | } | ||||
}; | }; |
@@ -39,7 +39,11 @@ let cpnArcanePatch = { | |||||
let c = contents[i]; | let c = contents[i]; | ||||
let amount = this.spell.getDamage(c, true); | let amount = this.spell.getDamage(c, true); | ||||
c.stats.getHp(amount, this.caster); | |||||
c.stats.getHp({ | |||||
event: amount, | |||||
source: this.caster, | |||||
target: c | |||||
}); | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
@@ -9,7 +9,11 @@ let cpnHealPatch = { | |||||
}, | }, | ||||
applyHeal: function (o, amount) { | applyHeal: function (o, amount) { | ||||
o.stats.getHp(amount, this.caster); | |||||
o.stats.getHp({ | |||||
event: amount, | |||||
source: this.caster, | |||||
target: o | |||||
}); | |||||
}, | }, | ||||
collisionEnter: function (o) { | collisionEnter: function (o) { | ||||
@@ -17,14 +17,18 @@ module.exports = { | |||||
const target = action.target; | const target = action.target; | ||||
const { x, y } = target; | const { x, y } = target; | ||||
const amount = this.getDamage(target, true); | |||||
const heal = this.getDamage(target, true); | |||||
heal.noEvents = this.noEvents; | |||||
const event = { | const event = { | ||||
heal: amount, | |||||
heal, | |||||
source: this.obj, | source: this.obj, | ||||
target, | target, | ||||
spellName: 'singleTargetHeal', | |||||
spell: this | spell: this | ||||
}; | }; | ||||
target.stats.getHp(amount, this.obj, event); | |||||
target.stats.getHp(event); | |||||
const effect = { | const effect = { | ||||
x, | x, | ||||
@@ -96,9 +96,13 @@ module.exports = { | |||||
mLen--; | mLen--; | ||||
} else if ((Math.abs(x - m.x) <= 1) && (Math.abs(y - m.y) <= 1)) { | } else if ((Math.abs(x - m.x) <= 1) && (Math.abs(y - m.y) <= 1)) { | ||||
m.destroyed = true; | m.destroyed = true; | ||||
this.obj.stats.getHp({ | |||||
amount: obj.stats.values.hpMax / 10 | |||||
}, obj); | |||||
obj.stats.getHp({ | |||||
event: { | |||||
amount: obj.stats.values.hpMax / 10 | |||||
}, | |||||
source: obj, | |||||
target: obj | |||||
}); | |||||
obj.instance.syncer.queue('onGetObject', { | obj.instance.syncer.queue('onGetObject', { | ||||
x: m.x, | x: m.x, | ||||
@@ -1,3 +1,5 @@ | |||||
/* eslint-disable max-lines-per-function */ | |||||
module.exports = { | module.exports = { | ||||
fixDb: async function () { | fixDb: async function () { | ||||
await io.deleteAsync({ | await io.deleteAsync({ | ||||
@@ -116,6 +118,34 @@ module.exports = { | |||||
effect.properties.element = 'poison'; | effect.properties.element = 'poison'; | ||||
}); | }); | ||||
items | |||||
.filter(i => i.name === 'Gourdhowl') | |||||
.forEach(i => { | |||||
const effect = i.effects[0]; | |||||
if (!effect.rolls.castSpell) { | |||||
effect.rolls = { | |||||
castSpell: { | |||||
type: 'whirlwind', | |||||
damage: effect.rolls.damage, | |||||
range: 1, | |||||
statType: 'str', | |||||
statMult: 1, | |||||
isAttack: true | |||||
}, | |||||
castTarget: 'none', | |||||
chance: effect.rolls.chance, | |||||
textTemplate: 'Grants you a ((chance))% chance to cast a ((castSpell.damage)) damage whirlwind on hit', | |||||
combatEvent: { | |||||
name: 'afterDealDamage', | |||||
afterDealDamage: { | |||||
spellName: 'melee' | |||||
} | |||||
} | |||||
}; | |||||
} | |||||
}); | |||||
items | items | ||||
.filter(f => f.effects?.[0]?.factionId === 'akarei' && !f.effects[0].properties) | .filter(f => f.effects?.[0]?.factionId === 'akarei' && !f.effects[0].properties) | ||||
.forEach(function (i) { | .forEach(function (i) { | ||||
@@ -16,7 +16,7 @@ const rollValues = (rollsDefinition, result) => { | |||||
const isInt = (p.indexOf('i_') === 0); | const isInt = (p.indexOf('i_') === 0); | ||||
const fieldName = p.replace('i_', ''); | const fieldName = p.replace('i_', ''); | ||||
if (!entry.push) { | |||||
if (!Array.isArray(entry) || p.indexOf('s_') === 0) { | |||||
result[fieldName] = range; | result[fieldName] = range; | ||||
continue; | continue; | ||||
@@ -45,7 +45,11 @@ module.exports = { | |||||
amount = amount * this.shieldMultiplier; | amount = amount * this.shieldMultiplier; | ||||
const heal = { amount }; | const heal = { amount }; | ||||
target.stats.getHp(heal, obj); | |||||
target.stats.getHp({ | |||||
event: heal, | |||||
source: obj, | |||||
target | |||||
}); | |||||
//Only reset the first spell's cooldown if it's an auto attack and not a spell | //Only reset the first spell's cooldown if it's an auto attack and not a spell | ||||
const firstSpell = target.spellbook.spells[0]; | const firstSpell = target.spellbook.spells[0]; | ||||
@@ -54,7 +54,11 @@ module.exports = { | |||||
let healAmount = damage.amount * (this.healPercent / 100); | let healAmount = damage.amount * (this.healPercent / 100); | ||||
obj.stats.getHp({ | obj.stats.getHp({ | ||||
amount: healAmount | |||||
}, obj); | |||||
event: { | |||||
amount: healAmount | |||||
}, | |||||
source: obj, | |||||
target: obj | |||||
}); | |||||
} | } | ||||
}; | }; |