You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

585 lines
13 KiB

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