import t from './translation'; import _ from 'lodash'; import { NotAuthorized } from '../api-v3/errors'; /* --------------------------------------------------------------- Spells --------------------------------------------------------------- Text, notes, and mana are obvious. The rest: * {target}: one of [task, self, party, user]. This is very important, because if the cast() function is expecting one thing and receives another, it will cause errors. `self` is used for self buffs, multi-task debuffs, AOEs (eg, meteor-shower), etc. Basically, use self for anything that's not [task, party, user] and is an instant-cast * {cast}: the function that's run to perform the ability's action. This is pretty slick - because this is exported to the web, this function can be performed on the client and on the server. `user` param is self (needed for determining your own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` spells, you act on `user` instead of `target`. You can trust these are the correct objects, as long as the `target` attr of the spell is correct. Take a look at habitrpg/src/models/user.js and habitrpg/src/models/task.js for what attributes are available on each model. Note `task.value` is its "redness". If party is passed in, it's an array of users, so you'll want to iterate over them like: `_.each(target,function(member){...})` Note, user.stats.mp is docked after automatically (it's appended to functions automatically down below in an _.each) */ function diminishingReturns (bonus, max, halfway) { if (!halfway) halfway = max / 2; return max * (bonus / (bonus + halfway)); } function calculateBonus (value, stat, crit = 1, statScale = 0.5) { return (value < 0 ? 1 : value + 1) + stat * statScale * crit; } let spells = {}; spells.wizard = { fireball: { text: t('spellWizardFireballText'), mana: 10, lvl: 11, target: 'task', notes: t('spellWizardFireballNotes'), cast (user, target, req) { let bonus = user._statsComputed.int * user.fns.crit('per'); bonus *= Math.ceil((target.value < 0 ? 1 : target.value + 1) * 0.075); user.stats.exp += diminishingReturns(bonus, 75); if (!user.party.quest.progress) user.party.quest.progress = 0; user.party.quest.progress.up += Math.ceil(user._statsComputed.int * 9.1); user.fns.updateStats(user.stats, req); }, }, mpheal: { text: t('spellWizardMPHealText'), mana: 30, lvl: 12, target: 'party', notes: t('spellWizardMPHealNotes'), cast (user, target) { _.each(target, (member) => { let bonus = user._statsComputed.int; if (user._id !== member._id) { member.stats.mp += Math.ceil(diminishingReturns(bonus, 25, 125)); } }); }, }, earth: { text: t('spellWizardEarthText'), mana: 35, lvl: 13, target: 'party', notes: t('spellWizardEarthNotes'), cast (user, target) { _.each(target, (member) => { let bonus = user._statsComputed.int - user.stats.buffs.int; if (!member.stats.buffs.int) member.stats.buffs.int = 0; member.stats.buffs.int += Math.ceil(diminishingReturns(bonus, 30, 200)); }); }, }, frost: { text: t('spellWizardFrostText'), mana: 40, lvl: 14, target: 'self', notes: t('spellWizardFrostNotes'), cast (user) { user.stats.buffs.streaks = true; }, }, }; spells.warrior = { smash: { text: t('spellWarriorSmashText'), mana: 10, lvl: 11, target: 'task', notes: t('spellWarriorSmashNotes'), cast (user, target) { let bonus = user._statsComputed.str * user.fns.crit('con'); target.value += diminishingReturns(bonus, 2.5, 35); if (!user.party.quest.progress.up) user.party.quest.progress.base = 0; user.party.quest.progress.up += diminishingReturns(bonus, 55, 70); }, }, defensiveStance: { text: t('spellWarriorDefensiveStanceText'), mana: 25, lvl: 12, target: 'self', notes: t('spellWarriorDefensiveStanceNotes'), cast (user) { let bonus = user._statsComputed.con - user.stats.buffs.con; if (!user.stats.buffs.con) user.stats.buffs.con = 0; user.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 40, 200)); }, }, valorousPresence: { text: t('spellWarriorValorousPresenceText'), mana: 20, lvl: 13, target: 'party', notes: t('spellWarriorValorousPresenceNotes'), cast (user, target) { _.each(target, (member) => { let bonus = user._statsComputed.str - user.stats.buffs.str; if (!member.stats.buffs.str) member.stats.buffs.str = 0; member.stats.buffs.str += Math.ceil(diminishingReturns(bonus, 20, 200)); }); }, }, intimidate: { text: t('spellWarriorIntimidateText'), mana: 15, lvl: 14, target: 'party', notes: t('spellWarriorIntimidateNotes'), cast (user, target) { _.each(target, (member) => { let bonus = user._statsComputed.con - user.stats.buffs.con; if (!member.stats.buffs.con) member.stats.buffs.con = 0; member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 24, 200)); }); }, }, }; spells.rogue = { pickPocket: { text: t('spellRoguePickPocketText'), mana: 10, lvl: 11, target: 'task', notes: t('spellRoguePickPocketNotes'), cast (user, target) { let bonus = calculateBonus(target.value, user._statsComputed.per); user.stats.gp += diminishingReturns(bonus, 25, 75); }, }, backStab: { text: t('spellRogueBackStabText'), mana: 15, lvl: 12, target: 'task', notes: t('spellRogueBackStabNotes'), cast (user, target, req) { let _crit = user.fns.crit('str', 0.3); let bonus = calculateBonus(target.value, user._statsComputed.str, _crit); user.stats.exp += diminishingReturns(bonus, 75, 50); user.stats.gp += diminishingReturns(bonus, 18, 75); user.fns.updateStats(user.stats, req); }, }, toolsOfTrade: { text: t('spellRogueToolsOfTradeText'), mana: 25, lvl: 13, target: 'party', notes: t('spellRogueToolsOfTradeNotes'), cast (user, target) { _.each(target, (member) => { let bonus = user._statsComputed.per - user.stats.buffs.per; if (!member.stats.buffs.per) member.stats.buffs.per = 0; member.stats.buffs.per += Math.ceil(diminishingReturns(bonus, 100, 50)); }); }, }, stealth: { text: t('spellRogueStealthText'), mana: 45, lvl: 14, target: 'self', notes: t('spellRogueStealthNotes'), cast (user) { if (!user.stats.buffs.stealth) user.stats.buffs.stealth = 0; user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.dailys.length * 0.64, 55)); }, }, }; spells.healer = { heal: { text: t('spellHealerHealText'), mana: 15, lvl: 11, target: 'self', notes: t('spellHealerHealNotes'), cast (user) { user.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * 0.075; if (user.stats.hp > 50) user.stats.hp = 50; }, }, brightness: { text: t('spellHealerBrightnessText'), mana: 15, lvl: 12, target: 'tasks', notes: t('spellHealerBrightnessNotes'), cast (user, tasks) { _.each(tasks, (task) => { if (task.type !== 'reward') { task.value += 4 * (user._statsComputed.int / (user._statsComputed.int + 40)); } }); }, }, protectAura: { text: t('spellHealerProtectAuraText'), mana: 30, lvl: 13, target: 'party', notes: t('spellHealerProtectAuraNotes'), cast (user, target) { _.each(target, (member) => { let bonus = user._statsComputed.con - user.stats.buffs.con; if (!member.stats.buffs.con) member.stats.buffs.con = 0; member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 200, 200)); }); }, }, heallAll: { text: t('spellHealerHealAllText'), mana: 25, lvl: 14, target: 'party', notes: t('spellHealerHealAllNotes'), cast (user, target) { _.each(target, (member) => { member.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * 0.04; if (member.stats.hp > 50) member.stats.hp = 50; }); }, }, }; spells.special = { snowball: { text: t('spellSpecialSnowballAuraText'), mana: 0, value: 15, previousPurchase: true, target: 'user', notes: t('spellSpecialSnowballAuraNotes'), cast (user, target, req) { if (!user.items.special.snowball) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = true; target.stats.buffs.spookDust = false; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = false; if (!target.achievements.snowball) target.achievements.snowball = 0; target.achievements.snowball++; user.items.special.snowball--; }, }, salt: { text: t('spellSpecialSaltText'), mana: 0, value: 5, immediateUse: true, target: 'self', notes: t('spellSpecialSaltNotes'), cast (user) { user.stats.buffs.snowball = false; user.stats.gp -= 5; }, }, spookDust: { text: t('spellSpecialSpookDustText'), mana: 0, value: 15, previousPurchase: true, target: 'user', notes: t('spellSpecialSpookDustNotes'), cast (user, target, req) { if (!user.items.special.spookDust) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = false; target.stats.buffs.spookDust = true; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = false; if (!target.achievements.spookDust) target.achievements.spookDust = 0; target.achievements.spookDust++; user.items.special.spookDust--; }, }, opaquePotion: { text: t('spellSpecialOpaquePotionText'), mana: 0, value: 5, immediateUse: true, target: 'self', notes: t('spellSpecialOpaquePotionNotes'), cast (user) { user.stats.buffs.spookDust = false; user.stats.gp -= 5; }, }, shinySeed: { text: t('spellSpecialShinySeedText'), mana: 0, value: 15, previousPurchase: true, target: 'user', notes: t('spellSpecialShinySeedNotes'), cast (user, target, req) { if (!user.items.special.shinySeed) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = false; target.stats.buffs.spookDust = false; target.stats.buffs.shinySeed = true; target.stats.buffs.seafoam = false; if (!target.achievements.shinySeed) target.achievements.shinySeed = 0; target.achievements.shinySeed++; user.items.special.shinySeed--; }, }, petalFreePotion: { text: t('spellSpecialPetalFreePotionText'), mana: 0, value: 5, immediateUse: true, target: 'self', notes: t('spellSpecialPetalFreePotionNotes'), cast (user) { user.stats.buffs.shinySeed = false; user.stats.gp -= 5; }, }, seafoam: { text: t('spellSpecialSeafoamText'), mana: 0, value: 15, previousPurchase: true, target: 'user', notes: t('spellSpecialSeafoamNotes'), cast (user, target, req) { if (!user.items.special.seafoam) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = false; target.stats.buffs.spookDust = false; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = true; if (!target.achievements.seafoam) target.achievements.seafoam = 0; target.achievements.seafoam++; user.items.special.seafoam--; }, }, sand: { text: t('spellSpecialSandText'), mana: 0, value: 5, immediateUse: true, target: 'self', notes: t('spellSpecialSandNotes'), cast (user) { user.stats.buffs.seafoam = false; user.stats.gp -= 5; }, }, nye: { text: t('nyeCard'), mana: 0, value: 10, immediateUse: true, silent: true, target: 'user', notes: t('nyeCardNotes'), cast (user, target) { if (user === target) { if (!user.achievements.nye) user.achievements.nye = 0; user.achievements.nye++; } else { _.each([user, target], (u) => { if (!u.achievements.nye) u.achievements.nye = 0; u.achievements.nye++; }); } if (!target.items.special.nyeReceived) target.items.special.nyeReceived = []; target.items.special.nyeReceived.push(user.profile.name); target.flags.cardReceived = true; user.stats.gp -= 10; }, }, valentine: { text: t('valentineCard'), mana: 0, value: 10, immediateUse: true, silent: true, target: 'user', notes: t('valentineCardNotes'), cast (user, target) { if (user === target) { if (!user.achievements.valentine) user.achievements.valentine = 0; user.achievements.valentine++; } else { _.each([user, target], (u) => { if (!u.achievements.valentine) u.achievements.valentine = 0; u.achievements.valentine++; }); } if (!target.items.special.valentineReceived) target.items.special.valentineReceived = []; target.items.special.valentineReceived.push(user.profile.name); target.flags.cardReceived = true; user.stats.gp -= 10; }, }, greeting: { text: t('greetingCard'), mana: 0, value: 10, immediateUse: true, silent: true, target: 'user', notes: t('greetingCardNotes'), cast (user, target) { if (user === target) { user.achievements.greeting++; } else { _.each([user, target], (u) => { if (!u.achievements.greeting) u.achievements.greeting = 0; u.achievements.greeting++; }); } if (!target.items.special.greetingReceived) target.items.special.greetingReceived = []; target.items.special.greetingReceived.push(user.profile.name); target.flags.cardReceived = true; user.stats.gp -= 10; }, }, thankyou: { text: t('thankyouCard'), mana: 0, value: 10, immediateUse: true, silent: true, target: 'user', notes: t('thankyouCardNotes'), cast (user, target) { if (user === target) { if (!user.achievements.thankyou) user.achievements.thankyou = 0; user.achievements.thankyou++; } else { _.each([user, target], (u) => { if (!u.achievements.thankyou) u.achievements.thankyou = 0; u.achievements.thankyou++; }); } if (!target.items.special.thankyouReceived) target.items.special.thankyouReceived = []; target.items.special.thankyouReceived.push(user.profile.name); target.flags.cardReceived = true; user.stats.gp -= 10; }, }, birthday: { text: t('birthdayCard'), mana: 0, value: 10, immediateUse: true, silent: true, target: 'user', notes: t('birthdayCardNotes'), cast (user, target) { if (user === target) { if (!user.achievements.birthday) user.achievements.birthday = 0; user.achievements.birthday++; } else { _.each([user, target], (u) => { if (!u.achievements.birthday) u.achievements.birthday = 0; u.achievements.birthday++; }); } if (!target.items.special.birthdayReceived) target.items.special.birthdayReceived = []; target.items.special.birthdayReceived.push(user.profile.name); target.flags.cardReceived = true; user.stats.gp -= 10; }, }, }; _.each(spells, (spellClass) => { _.each(spellClass, (spell, key) => { spell.key = key; let _cast = spell.cast; spell.cast = function castSpell (user, target, req) { _cast(user, target, req); user.stats.mp -= spell.mana; }; }); }); export default spells;