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.
 
 
 

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