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.
 
 
 

597 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. //Callbacks to be called when this object is destroyed
  324. registerDestroyCallback: function (callback) {
  325. this.callbacks.push({
  326. cbOnSelfDestroyed: callback
  327. });
  328. },
  329. registerCallback: function (sourceId, callback, time, destroyCallback, targetId, destroyOnRezone) {
  330. let obj = {
  331. sourceId: sourceId,
  332. targetId: targetId,
  333. callback: callback,
  334. destroyCallback: destroyCallback,
  335. destroyOnRezone: destroyOnRezone,
  336. time: time
  337. };
  338. this.callbacks.push(obj);
  339. return obj;
  340. },
  341. unregisterCallback: function (objId, isTarget) {
  342. let callbacks = this.callbacks;
  343. let cLen = callbacks.length;
  344. for (let i = 0; i < cLen; i++) {
  345. let c = callbacks[i];
  346. if (
  347. (
  348. isTarget &&
  349. c.targetId === objId
  350. ) ||
  351. (
  352. !isTarget &&
  353. c.sourceId === objId
  354. )
  355. ) {
  356. if (c.destroyCallback)
  357. c.destroyCallback();
  358. callbacks.splice(i, 1);
  359. i--;
  360. cLen--;
  361. }
  362. }
  363. },
  364. sendAnnouncement: function (msg) {
  365. process.send({
  366. method: 'events',
  367. data: {
  368. onGetAnnouncement: [{
  369. obj: {
  370. msg: msg
  371. },
  372. to: [this.obj.serverId]
  373. }]
  374. }
  375. });
  376. },
  377. fireEvent: function (event, args) {
  378. let spells = this.spells;
  379. let sLen = spells.length;
  380. for (let i = 0; i < sLen; i++) {
  381. let s = spells[i];
  382. let spellEvents = s.events;
  383. if (spellEvents) {
  384. let callback = spellEvents[event];
  385. if (!callback)
  386. continue;
  387. callback.apply(s, args);
  388. }
  389. if (s.castEvent === event)
  390. s.cast();
  391. }
  392. },
  393. isCasting: function () {
  394. return this.spells.some(s => s.currentAction);
  395. },
  396. stopCasting: function (ignore, skipAuto) {
  397. this.spells.forEach(s => {
  398. if (s === ignore)
  399. return;
  400. if (!skipAuto)
  401. s.setAuto(null);
  402. if (!s.currentAction)
  403. return;
  404. s.castTime = 0;
  405. s.currentAction = null;
  406. if (!ignore || !ignore.castTimeMax)
  407. this.obj.syncer.set(false, null, 'casting', 0);
  408. });
  409. },
  410. destroy: function () {
  411. this.callbacks.forEach(c => {
  412. if (c.cbOnSelfDestroyed)
  413. c.cbOnSelfDestroyed();
  414. });
  415. this.spells.forEach(s => {
  416. if (s.destroy)
  417. s.destroy();
  418. });
  419. },
  420. events: {
  421. beforeMove: function () {
  422. this.stopCasting(null, true);
  423. },
  424. onBeforeUseItem: function () {
  425. this.stopCasting(null, true);
  426. },
  427. clearQueue: function () {
  428. this.stopCasting(null, true);
  429. },
  430. beforeDeath: function () {
  431. this.stopCasting(null, true);
  432. this.spells.forEach(function (s) {
  433. if (!s.castOnDeath)
  434. return;
  435. s.cast();
  436. });
  437. },
  438. beforeRezone: function () {
  439. this.spells.forEach(function (s) {
  440. if (s.active) {
  441. s.active = false;
  442. let reserve = s.manaReserve;
  443. if (reserve && reserve.percentage) {
  444. let reserveEvent = {
  445. spell: s.name,
  446. reservePercent: reserve.percentage
  447. };
  448. this.obj.fireEvent('onBeforeReserveMana', reserveEvent);
  449. this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent);
  450. }
  451. //Make sure to remove the buff from party members
  452. s.updateInactive();
  453. }
  454. }, this);
  455. let callbacks = this.callbacks;
  456. let cLen = callbacks.length;
  457. for (let i = 0; i < cLen; i++) {
  458. let c = callbacks[i];
  459. //If a spellCallback kills a mob he'll unregister his callbacks
  460. //Probably not needed since we aren't supposed to damage mobs in destroyCallback
  461. if (!c) {
  462. i--;
  463. cLen--;
  464. continue;
  465. }
  466. if (c.destroyOnRezone) {
  467. if (c.destroyCallback)
  468. c.destroyCallback();
  469. callbacks.splice(i, 1);
  470. i--;
  471. cLen--;
  472. }
  473. }
  474. }
  475. }
  476. };