/* eslint-disable-next-line max-lines-per-function */ const cast = (cpnSpellbook, action, isAuto) => { const { obj, physics, spells } = cpnSpellbook; //Stop casting if (!action.has('spell')) { const wasCasting = cpnSpellbook.isCasting(); cpnSpellbook.stopCasting(); //Consume a tick if we were casting return wasCasting; } const spell = spells.find(s => (s.id === action.spell)); if (!spell) return false; action.target = cpnSpellbook.getTarget(spell, action); action.auto = spell.auto; //If a target has become nonSelectable, we need to stop attacks that are queued/auto if (!action.target || action.target.nonSelectable) return false; let success = true; if (spell.cd > 0) { if (!isAuto) { const type = (spell.auto) ? 'Weapon' : 'Spell'; cpnSpellbook.sendAnnouncement(`${type} is on cooldown`); } success = false; } else if (spell.manaCost > obj.stats.values.mana) { if (!isAuto) cpnSpellbook.sendAnnouncement('Insufficient mana to cast spell'); success = false; } else if (spell.has('range')) { const distance = Math.max(Math.abs(action.target.x - obj.x), Math.abs(action.target.y - obj.y)); let range = spell.range; if ((spell.useWeaponRange) && (obj.player)) { const weapon = obj.inventory.findItem(obj.equipment.eq.oneHanded) || obj.inventory.findItem(obj.equipment.eq.twoHanded); if (weapon) range = weapon.range || 1; } if (distance > range) { if (!isAuto) cpnSpellbook.sendAnnouncement('Target out of range'); success = false; } } //LoS check //Null means we don't have LoS and as such, we should move if (spell.needLos && success) { if (!physics.hasLos(~~obj.x, ~~obj.y, ~~action.target.x, ~~action.target.y)) { if (!isAuto) cpnSpellbook.sendAnnouncement('Target not in line of sight'); action.auto = false; success = null; } } if (!success) { cpnSpellbook.queueAuto(action, spell); return success; } else if (!cpnSpellbook.queueAuto(action, spell)) return false; const eventBeforeCastSpell = { success: true, action }; obj.fireEvent('beforeCastSpell', eventBeforeCastSpell); if (!eventBeforeCastSpell.success) return false; if (spell.manaReserve) { const reserve = spell.manaReserve; if (reserve.percentage) { const reserveEvent = { spell: spell.name, reservePercent: reserve.percentage }; obj.fireEvent('onBeforeReserveMana', reserveEvent); if (!spell.active) { if (1 - obj.stats.values.manaReservePercent < reserve.percentage) { cpnSpellbook.sendAnnouncement('Insufficient mana to cast spell'); return; } obj.stats.addStat('manaReservePercent', reserveEvent.reservePercent); } else obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent); } } if (spell.targetFurthest) spell.target = obj.aggro.getFurthest(); else if (spell.targetRandom) spell.target = obj.aggro.getRandom(); if (!!eventBeforeCastSpell.action.target?.effects) { const eventBeforeIsSpellTarget = { source: obj, spell, target: eventBeforeCastSpell.action.target }; eventBeforeIsSpellTarget.target.fireEvent('beforeIsSpellTarget', eventBeforeIsSpellTarget); eventBeforeCastSpell.action.target = eventBeforeIsSpellTarget.target; } success = spell.castBase(eventBeforeCastSpell.action); cpnSpellbook.stopCasting(spell, true); if (success) { spell.consumeMana(); spell.setCd(); } obj.fireEvent('afterCastSpell', { castSuccess: success, spell, action: eventBeforeCastSpell.action }); //Null means we didn't fail but are initiating casting return (success === null || success === true); }; module.exports = cast;