Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

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