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.
 
 
 

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