Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

698 righe
16 KiB

  1. let spellTemplate = require('../config/spells/spellTemplate');
  2. let animations = require('../config/animations');
  3. let playerSpells = require('../config/spells');
  4. let playerSpellsConfig = require('../config/spellsConfig');
  5. module.exports = {
  6. type: 'spellbook',
  7. spells: [],
  8. physics: null,
  9. objects: null,
  10. closestRange: -1,
  11. furthestRange: -1,
  12. callbacks: [],
  13. init: function (blueprint) {
  14. this.objects = this.obj.instance.objects;
  15. this.physics = this.obj.instance.physics;
  16. this.dmgMult = blueprint.dmgMult;
  17. (blueprint.spells || []).forEach(s => this.addSpell(s, -1));
  18. delete blueprint.spells;
  19. },
  20. transfer: function () {
  21. let spells = this.spells;
  22. this.spells = [];
  23. spells.forEach(s => this.addSpell(s, -1));
  24. },
  25. die: function () {
  26. this.stopCasting();
  27. this.spells.forEach(s => {
  28. let reserve = s.manaReserve;
  29. if (reserve && reserve.percentage && s.active) {
  30. let reserveEvent = {
  31. spell: s.name,
  32. reservePercent: reserve.percentage
  33. };
  34. this.obj.fireEvent('onBeforeReserveMana', reserveEvent);
  35. this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent);
  36. }
  37. s.die();
  38. }, this);
  39. },
  40. simplify: function (self) {
  41. if (!self)
  42. return null;
  43. let s = {
  44. type: this.type,
  45. closestRange: this.closestRange,
  46. furthestRange: this.furthestRange
  47. };
  48. let spells = this.spells;
  49. if (spells.length && spells[0].obj)
  50. spells = spells.map(f => f.simplify());
  51. s.spells = spells;
  52. return s;
  53. },
  54. addSpell: function (options, spellId) {
  55. if (!options.type) {
  56. options = {
  57. type: options
  58. };
  59. }
  60. let type = options.type[0].toUpperCase() + options.type.substr(1);
  61. let typeTemplate = {
  62. type: type,
  63. template: null
  64. };
  65. this.obj.instance.eventEmitter.emit('onBeforeGetSpellTemplate', typeTemplate);
  66. if (!typeTemplate.template)
  67. typeTemplate.template = require('../config/spells/spell' + type);
  68. let builtSpell = extend({}, spellTemplate, typeTemplate.template, options);
  69. builtSpell.obj = this.obj;
  70. builtSpell.baseDamage = builtSpell.damage || 0;
  71. builtSpell.damage += (options.damageAdd || 0);
  72. if (options.damage)
  73. builtSpell.damage = options.damage;
  74. if (builtSpell.animation) {
  75. let animation = null;
  76. let sheetName = this.obj.sheetName || '../../../images/characters.png';
  77. let animationName = builtSpell.animation;
  78. if (sheetName === 'mobs')
  79. animation = animations.mobs;
  80. else if (sheetName === 'bosses')
  81. animation = animations.bosses;
  82. else if (sheetName.indexOf('/') > -1)
  83. animation = animations.mobs[sheetName];
  84. else
  85. animation = animations.classes;
  86. if ((animation) && (animation[this.obj.cell]) && (animation[this.obj.cell][animationName])) {
  87. builtSpell.animation = extend({}, animation[this.obj.cell][animationName]);
  88. builtSpell.animation.name = animationName;
  89. } else
  90. builtSpell.animation = null;
  91. }
  92. if (!builtSpell.castOnDeath && builtSpell.range) {
  93. if (this.closestRange === -1 || builtSpell.range < this.closestRange)
  94. this.closestRange = builtSpell.range;
  95. if (this.furthestRange === -1 || builtSpell.range > this.furthestRange)
  96. this.furthestRange = builtSpell.range;
  97. }
  98. if ((!options.has('id')) && (spellId === -1)) {
  99. spellId = 0;
  100. this.spells.forEach(function (s) {
  101. if (s.id >= spellId)
  102. spellId = s.id + 1;
  103. });
  104. }
  105. builtSpell.id = !options.has('id') ? spellId : options.id;
  106. //Mobs don't get abilities put on CD when they learn them
  107. if (!this.obj.mob && builtSpell.cdMax)
  108. builtSpell.cd = builtSpell.cdMax;
  109. this.spells.push(builtSpell);
  110. this.spells.sort(function (a, b) {
  111. return (a.id - b.id);
  112. });
  113. builtSpell.calcDps(null, true);
  114. if (builtSpell.init)
  115. builtSpell.init();
  116. if (this.obj.player)
  117. this.obj.syncer.setArray(true, 'spellbook', 'getSpells', builtSpell.simplify());
  118. return builtSpell.id;
  119. },
  120. addSpellFromRune: function (runeSpell, spellId) {
  121. let type = runeSpell.type;
  122. let playerSpell = playerSpells.spells.find(s => (s.name.toLowerCase() === runeSpell.name.toLowerCase())) || playerSpells.spells.find(s => (s.type === type));
  123. let playerSpellConfig = playerSpellsConfig.spells[runeSpell.name.toLowerCase()] || playerSpellsConfig.spells[runeSpell.type];
  124. if (!playerSpellConfig)
  125. return -1;
  126. if (!runeSpell.rolls)
  127. runeSpell.rolls = {};
  128. runeSpell.values = {};
  129. let builtSpell = extend({
  130. type: runeSpell.type,
  131. values: {}
  132. }, playerSpell, playerSpellConfig, runeSpell);
  133. for (let r in builtSpell.random) {
  134. let range = builtSpell.random[r];
  135. let roll = runeSpell.rolls[r] || 0;
  136. runeSpell.rolls[r] = roll;
  137. let int = r.indexOf('i_') === 0;
  138. let val = range[0] + ((range[1] - range[0]) * roll);
  139. if (int) {
  140. val = Math.round(val);
  141. r = r.replace('i_', '');
  142. } else
  143. val = ~~(val * 100) / 100;
  144. builtSpell[r] = val;
  145. builtSpell.values[r] = val;
  146. runeSpell.values[r] = val;
  147. }
  148. if (runeSpell.properties) {
  149. for (let p in runeSpell.properties)
  150. builtSpell[p] = runeSpell.properties[p];
  151. }
  152. if (runeSpell.cdMult)
  153. builtSpell.cdMax *= runeSpell.cdMult;
  154. delete builtSpell.rolls;
  155. delete builtSpell.random;
  156. return this.addSpell(builtSpell, spellId);
  157. },
  158. calcDps: function () {
  159. this.spells.forEach(s => s.calcDps());
  160. },
  161. removeSpellById: function (id) {
  162. let exists = this.spells.spliceFirstWhere(s => (s.id === id));
  163. if (exists) {
  164. if (exists.manaReserve && exists.active) {
  165. let reserve = exists.manaReserve;
  166. if (reserve.percentage) {
  167. let reserveEvent = {
  168. spell: exists.name,
  169. reservePercent: reserve.percentage
  170. };
  171. this.obj.fireEvent('onBeforeReserveMana', reserveEvent);
  172. this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent);
  173. }
  174. }
  175. if (exists.unlearn)
  176. exists.unlearn();
  177. this.obj.syncer.setArray(true, 'spellbook', 'removeSpells', id);
  178. }
  179. },
  180. queueAuto: function (action, spell) {
  181. if (!action.auto || spell.autoActive)
  182. return true;
  183. this.spells.forEach(s => s.setAuto(null));
  184. spell.setAuto({
  185. target: action.target,
  186. spell: spell.id
  187. });
  188. },
  189. getRandomSpell: function (target) {
  190. const valid = this.spells.filter(s => {
  191. return (!s.selfCast && !s.procCast && !s.castOnDeath && s.canCast(target));
  192. });
  193. if (!valid.length)
  194. return null;
  195. return valid[~~(Math.random() * valid.length)].id;
  196. },
  197. getTarget: function (spell, action) {
  198. let target = action.target;
  199. //Cast on self?
  200. if (action.self) {
  201. if (spell.targetGround) {
  202. target = {
  203. x: this.obj.x,
  204. y: this.obj.y
  205. };
  206. } else if (spell.spellType === 'buff')
  207. target = this.obj;
  208. }
  209. if (!spell.aura && !spell.targetGround) {
  210. //Did we pass in the target id?
  211. if (target && !target.id) {
  212. target = this.objects.objects.find(o => o.id === target);
  213. if (!target)
  214. return null;
  215. }
  216. if (target === this.obj && spell.noTargetSelf)
  217. target = null;
  218. if (!target || !target.player) {
  219. if (spell.autoTargetFollower) {
  220. target = this.spells.find(s => s.minions && s.minions.length > 0);
  221. if (target)
  222. target = target.minions[0];
  223. else
  224. return null;
  225. }
  226. }
  227. if (spell.spellType === 'buff') {
  228. if (this.obj.aggro.faction !== target.aggro.faction)
  229. return;
  230. } else if (target.aggro && !this.obj.aggro.canAttack(target)) {
  231. if (this.obj.player)
  232. this.sendAnnouncement("You don't feel like attacking that target");
  233. return;
  234. }
  235. }
  236. if (!spell.targetGround && target && !target.aggro && !spell.aura) {
  237. this.sendAnnouncement("You don't feel like attacking that target");
  238. return;
  239. }
  240. if (spell.aura)
  241. target = this.obj;
  242. return target;
  243. },
  244. canCast: function (action) {
  245. if (!action.has('spell'))
  246. return false;
  247. let spell = this.spells.find(s => (s.id === action.spell));
  248. if (!spell)
  249. return false;
  250. let target = this.getTarget(spell, action);
  251. return spell.canCast(target);
  252. },
  253. cast: function (action, isAuto) {
  254. if (!action.has('spell')) {
  255. const isCasting = this.isCasting();
  256. this.stopCasting();
  257. const consumeTick = isCasting;
  258. return consumeTick;
  259. }
  260. let spell = this.spells.find(s => (s.id === action.spell));
  261. if (!spell)
  262. return false;
  263. action.target = this.getTarget(spell, action);
  264. //If a target has become nonSelectable, we need to stop attacks that are queued/auto
  265. if (!action.target || action.target.nonSelectable)
  266. return false;
  267. action.auto = spell.auto;
  268. let success = true;
  269. if (spell.cd > 0) {
  270. if (!isAuto) {
  271. let type = (spell.auto) ? 'Weapon' : 'Spell';
  272. this.sendAnnouncement(`${type} is on cooldown`);
  273. }
  274. success = false;
  275. } else if (spell.manaCost > this.obj.stats.values.mana) {
  276. if (!isAuto)
  277. this.sendAnnouncement('Insufficient mana to cast spell');
  278. success = false;
  279. } else if (spell.has('range')) {
  280. let distance = Math.max(Math.abs(action.target.x - this.obj.x), Math.abs(action.target.y - this.obj.y));
  281. let range = spell.range;
  282. if ((spell.useWeaponRange) && (this.obj.player)) {
  283. let weapon = this.obj.inventory.findItem(this.obj.equipment.eq.oneHanded) || this.obj.inventory.findItem(this.obj.equipment.eq.twoHanded);
  284. if (weapon)
  285. range = weapon.range || 1;
  286. }
  287. if (distance > range) {
  288. if (!isAuto)
  289. this.sendAnnouncement('Target out of range');
  290. success = false;
  291. }
  292. }
  293. //LoS check
  294. //Null means we don't have LoS and as such, we should move
  295. if (spell.needLos && success) {
  296. if (!this.physics.hasLos(~~this.obj.x, ~~this.obj.y, ~~action.target.x, ~~action.target.y)) {
  297. if (!isAuto)
  298. this.sendAnnouncement('Target not in line of sight');
  299. action.auto = false;
  300. success = null;
  301. }
  302. }
  303. if (!success) {
  304. this.queueAuto(action, spell);
  305. return success;
  306. } else if (!this.queueAuto(action, spell))
  307. return false;
  308. let castSuccess = {
  309. success: true
  310. };
  311. this.obj.fireEvent('beforeCastSpell', castSuccess);
  312. if (!castSuccess.success)
  313. return false;
  314. if (spell.manaReserve) {
  315. let reserve = spell.manaReserve;
  316. if (reserve.percentage) {
  317. let reserveEvent = {
  318. spell: spell.name,
  319. reservePercent: reserve.percentage
  320. };
  321. this.obj.fireEvent('onBeforeReserveMana', reserveEvent);
  322. if (!spell.active) {
  323. if (1 - this.obj.stats.values.manaReservePercent < reserve.percentage) {
  324. this.sendAnnouncement('Insufficient mana to cast spell');
  325. return;
  326. } this.obj.stats.addStat('manaReservePercent', reserveEvent.reservePercent);
  327. } else
  328. this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent);
  329. }
  330. }
  331. if (spell.targetFurthest)
  332. spell.target = this.obj.aggro.getFurthest();
  333. else if (spell.targetRandom)
  334. spell.target = this.obj.aggro.getRandom();
  335. success = spell.castBase(action);
  336. this.stopCasting(spell, true);
  337. if (success) {
  338. spell.consumeMana();
  339. spell.setCd();
  340. }
  341. this.obj.fireEvent('afterCastSpell', {
  342. castSuccess: success,
  343. spell,
  344. action
  345. });
  346. //Null means we didn't fail but are initiating casting
  347. return (success === null || success === true);
  348. },
  349. getClosestRange: function (spellNum) {
  350. if (spellNum)
  351. return this.spells[spellNum].range;
  352. return this.closestRange;
  353. },
  354. getFurthestRange: function (spellNum, checkCanCast) {
  355. if (spellNum)
  356. return this.spells[spellNum].range;
  357. let spells = this.spells;
  358. let sLen = spells.length;
  359. let furthest = 0;
  360. for (let i = 0; i < sLen; i++) {
  361. let spell = spells[i];
  362. if (spell.procCast || spell.castOnDeath)
  363. continue;
  364. if (spell.range > furthest && (!checkCanCast || spell.canCast()))
  365. furthest = spell.range;
  366. }
  367. return furthest;
  368. },
  369. getCooldowns: function () {
  370. let cds = [];
  371. this.spells.forEach(
  372. s => cds.push({
  373. cd: s.cd,
  374. cdMax: s.cdMax,
  375. canCast: ((s.manaCost <= this.obj.stats.values.mana) && (s.cd === 0))
  376. }), this);
  377. return cds;
  378. },
  379. update: function () {
  380. let didCast = false;
  381. const isCasting = this.isCasting();
  382. this.spells.forEach(s => {
  383. let auto = s.autoActive;
  384. if (auto) {
  385. if (!auto.target || auto.target.destroyed)
  386. s.setAuto(null);
  387. else if (!isCasting && this.cast(auto, true))
  388. didCast = true;
  389. }
  390. s.updateBase();
  391. if (s.update)
  392. s.update();
  393. });
  394. let callbacks = this.callbacks;
  395. let cLen = callbacks.length;
  396. for (let i = 0; i < cLen; i++) {
  397. let c = callbacks[i];
  398. //If a spellCallback kills a mob he'll unregister his callbacks
  399. if (!c) {
  400. i--;
  401. cLen--;
  402. continue;
  403. }
  404. c.time -= consts.tickTime;
  405. if (c.time <= 0) {
  406. if (c.callback)
  407. c.callback();
  408. if (c.destroyCallback)
  409. c.destroyCallback();
  410. callbacks.splice(i, 1);
  411. i--;
  412. cLen--;
  413. }
  414. }
  415. return didCast || isCasting;
  416. },
  417. registerCallback: function (sourceId, callback, time, destroyCallback, targetId, destroyOnRezone) {
  418. let obj = {
  419. sourceId: sourceId,
  420. targetId: targetId,
  421. callback: callback,
  422. destroyCallback: destroyCallback,
  423. destroyOnRezone: destroyOnRezone,
  424. time: time
  425. };
  426. this.callbacks.push(obj);
  427. return obj;
  428. },
  429. unregisterCallback: function (objId, isTarget) {
  430. let callbacks = this.callbacks;
  431. let cLen = callbacks.length;
  432. for (let i = 0; i < cLen; i++) {
  433. let c = callbacks[i];
  434. if (
  435. (
  436. isTarget &&
  437. c.targetId === objId
  438. ) ||
  439. (
  440. !isTarget &&
  441. c.sourceId === objId
  442. )
  443. ) {
  444. if (c.destroyCallback)
  445. c.destroyCallback();
  446. callbacks.splice(i, 1);
  447. i--;
  448. cLen--;
  449. }
  450. }
  451. },
  452. sendAnnouncement: function (msg) {
  453. process.send({
  454. method: 'events',
  455. data: {
  456. onGetAnnouncement: [{
  457. obj: {
  458. msg: msg
  459. },
  460. to: [this.obj.serverId]
  461. }]
  462. }
  463. });
  464. },
  465. fireEvent: function (event, args) {
  466. let spells = this.spells;
  467. let sLen = spells.length;
  468. for (let i = 0; i < sLen; i++) {
  469. let s = spells[i];
  470. let spellEvents = s.events;
  471. if (spellEvents) {
  472. let callback = spellEvents[event];
  473. if (!callback)
  474. continue;
  475. callback.apply(s, args);
  476. }
  477. if (s.castEvent === event)
  478. s.cast();
  479. }
  480. },
  481. isCasting: function () {
  482. return this.spells.some(s => s.currentAction);
  483. },
  484. stopCasting: function (ignore, skipAuto) {
  485. this.spells.forEach(s => {
  486. if (s === ignore)
  487. return;
  488. if (!skipAuto)
  489. s.setAuto(null);
  490. if (!s.currentAction)
  491. return;
  492. s.castTime = 0;
  493. s.currentAction = null;
  494. if (!ignore || !ignore.castTimeMax)
  495. this.obj.syncer.set(false, null, 'casting', 0);
  496. });
  497. },
  498. destroy: function () {
  499. this.spells.forEach(s => {
  500. if (s.destroy)
  501. s.destroy();
  502. });
  503. },
  504. events: {
  505. beforeMove: function () {
  506. this.stopCasting(null, true);
  507. },
  508. onBeforeUseItem: function () {
  509. this.stopCasting(null, true);
  510. },
  511. clearQueue: function () {
  512. this.stopCasting(null, true);
  513. },
  514. beforeDeath: function () {
  515. this.stopCasting(null, true);
  516. this.spells.forEach(function (s) {
  517. if (!s.castOnDeath)
  518. return;
  519. s.cast();
  520. });
  521. },
  522. beforeRezone: function () {
  523. this.spells.forEach(function (s) {
  524. if (s.active) {
  525. s.active = false;
  526. let reserve = s.manaReserve;
  527. if (reserve && reserve.percentage) {
  528. let reserveEvent = {
  529. spell: s.name,
  530. reservePercent: reserve.percentage
  531. };
  532. this.obj.fireEvent('onBeforeReserveMana', reserveEvent);
  533. this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent);
  534. }
  535. //Make sure to remove the buff from party members
  536. s.updateInactive();
  537. }
  538. }, this);
  539. let callbacks = this.callbacks;
  540. let cLen = callbacks.length;
  541. for (let i = 0; i < cLen; i++) {
  542. let c = callbacks[i];
  543. //If a spellCallback kills a mob he'll unregister his callbacks
  544. //Probably not needed since we aren't supposed to damage mobs in destroyCallback
  545. if (!c) {
  546. i--;
  547. cLen--;
  548. continue;
  549. }
  550. if (c.destroyOnRezone) {
  551. if (c.destroyCallback)
  552. c.destroyCallback();
  553. callbacks.splice(i, 1);
  554. i--;
  555. cLen--;
  556. }
  557. }
  558. }
  559. }
  560. };