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.
 
 
 

522 lines
12 KiB

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