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
14 KiB

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