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.
 
 
 

307 rivejä
7.0 KiB

  1. let combat = require('../../combat/combat');
  2. module.exports = {
  3. cd: 0,
  4. cdMax: 0,
  5. manaCost: 1,
  6. threatMult: 1,
  7. casting: false,
  8. castTime: 0,
  9. castTimeMax: 0,
  10. needLos: false,
  11. //Should damage/heals caused by this spell cause events to be fired on objects?
  12. noEvents: false,
  13. currentAction: null,
  14. pendingAttacks: [],
  15. canCast: function (target) {
  16. if (this.cd > 0)
  17. return false;
  18. else if (this.manaCost > this.obj.stats.values.mana)
  19. return false;
  20. else if (!target)
  21. return true;
  22. let inRange = true;
  23. if (this.has('range')) {
  24. let obj = this.obj;
  25. let distance = Math.max(Math.abs(target.x - obj.x), Math.abs(target.y - obj.y));
  26. inRange = (distance <= this.range);
  27. }
  28. return inRange;
  29. },
  30. castBase: function (action) {
  31. if (this.castTimeMax > 0) {
  32. if ((!this.currentAction) || (this.currentAction.target !== action.target)) {
  33. this.currentAction = action;
  34. let castTimeMax = this.castTimeMax;
  35. let speedModifier = this.obj.stats.values[this.isAttack ? 'attackSpeed' : 'castSpeed'];
  36. castTimeMax = Math.ceil(castTimeMax * (1 - (Math.min(50, speedModifier) / 100)));
  37. let castEvent = {
  38. castTimeMax: castTimeMax
  39. };
  40. this.obj.fireEvent('beforeGetSpellCastTime', castEvent);
  41. this.currentAction.castTimeMax = castEvent.castTimeMax;
  42. this.castTime = castEvent.castTimeMax;
  43. this.obj.syncer.set(false, null, 'casting', 0);
  44. }
  45. return null;
  46. }
  47. return this.cast(action);
  48. },
  49. updateBase: function () {
  50. if (this.castTime > 0) {
  51. let action = this.currentAction;
  52. if (_.getDeepProperty(action, 'target.destroyed') || !this.canCast(action.target)) {
  53. this.currentAction = null;
  54. this.castTime = 0;
  55. this.obj.syncer.set(false, null, 'casting', 0);
  56. return;
  57. }
  58. this.castTime--;
  59. this.obj.syncer.set(false, null, 'casting', (action.castTimeMax - this.castTime) / action.castTimeMax);
  60. if (!this.castTime) {
  61. if (this.cast(action)) {
  62. this.consumeMana();
  63. this.setCd();
  64. this.currentAction = null;
  65. }
  66. } else {
  67. if (this.onCastTick)
  68. this.onCastTick();
  69. this.sendBump(null, 0, -1);
  70. }
  71. return;
  72. }
  73. if (this.cd > 0) {
  74. this.cd--;
  75. if (this.cd === 0)
  76. this.obj.syncer.setArray(true, 'spellbook', 'getSpells', this.simplify());
  77. }
  78. },
  79. consumeMana: function () {
  80. let stats = this.obj.stats.values;
  81. stats.mana -= this.manaCost;
  82. if (this.obj.player)
  83. this.obj.syncer.setObject(true, 'stats', 'values', 'mana', stats.mana);
  84. },
  85. setCd: function () {
  86. let cd = {
  87. cd: this.cdMax
  88. };
  89. this.obj.fireEvent('beforeSetSpellCooldown', cd, this);
  90. this.cd = cd.cd;
  91. if (this.obj.player) {
  92. this.obj.instance.syncer.queue('onGetSpellCooldowns', {
  93. spell: this.id,
  94. cd: (this.cd * consts.tickTime)
  95. }, [this.obj.serverId]);
  96. }
  97. },
  98. setAuto: function (autoConfig) {
  99. this.autoActive = autoConfig;
  100. if (this.obj.player) {
  101. this.obj.instance.syncer.queue('onGetSpellActive', {
  102. id: this.obj.id,
  103. spell: this.id,
  104. cd: (this.cd * consts.tickTime),
  105. active: !!autoConfig
  106. }, [this.obj.serverId]);
  107. }
  108. },
  109. calcDps: function (target, noSync) {
  110. if ((!this.values) || (this.spellType === 'buff') || (this.spellType === 'aura'))
  111. return;
  112. if ((!this.damage) && (!this.healing))
  113. delete this.values.dps;
  114. else {
  115. let noMitigate = !target;
  116. let dmg = combat.getDamage({
  117. source: this.obj,
  118. target: (target || {
  119. stats: {
  120. values: {}
  121. }
  122. }),
  123. damage: (this.damage || this.healing) * (this.dmgMult || 1),
  124. cd: this.cdMax,
  125. element: this.element,
  126. statType: this.statType,
  127. statMult: this.statMult,
  128. noMitigate: noMitigate,
  129. isAttack: this.isAttack,
  130. noCrit: true
  131. }).amount;
  132. let statValues = this.obj.stats.values;
  133. let critChance = statValues.critChance + (this.isAttack ? statValues.attackCritChance : statValues.spellCritChance);
  134. let critMultiplier = statValues.critMultiplier + (this.isAttack ? statValues.attackCritMultiplier : statValues.spellCritMultiplier);
  135. let castTimeMax = this.castTimeMax;
  136. let speedModifier = this.obj.stats.values[this.isAttack ? 'attackSpeed' : 'castSpeed'];
  137. castTimeMax = Math.ceil(castTimeMax * (1 - (Math.min(50, speedModifier) / 100)));
  138. critChance = Math.min(critChance, 100);
  139. dmg = (((dmg / 100) * (100 - critChance)) + (((dmg / 100) * critChance) * (critMultiplier / 100)));
  140. let duration = this.values.duration;
  141. if (duration)
  142. dmg *= duration;
  143. const div = (this.cdMax + castTimeMax) || 1;
  144. dmg /= div;
  145. if (this.damage)
  146. this.values.dmg = ~~(dmg * 100) / 100 + '/tick';
  147. else
  148. this.values.heal = ~~(dmg * 100) / 100 + '/tick';
  149. if (!noSync)
  150. this.obj.syncer.setArray(true, 'spellbook', 'getSpells', this.simplify());
  151. }
  152. },
  153. sendAnimation: function (blueprint) {
  154. this.obj.instance.syncer.queue('onGetObject', blueprint, -1);
  155. },
  156. sendBump: function (target, deltaX, deltaY) {
  157. if (target) {
  158. let x = this.obj.x;
  159. let y = this.obj.y;
  160. let tx = target.x;
  161. let ty = target.y;
  162. if (tx < x)
  163. deltaX = -1;
  164. else if (tx > x)
  165. deltaX = 1;
  166. if (ty < y)
  167. deltaY = -1;
  168. else if (ty > y)
  169. deltaY = 1;
  170. }
  171. let components = [{
  172. type: 'bumpAnimation',
  173. deltaX: deltaX,
  174. deltaY: deltaY
  175. }];
  176. //During casting we only bump
  177. if ((target) && (this.animation)) {
  178. components.push({
  179. type: 'animation',
  180. template: this.animation
  181. });
  182. }
  183. this.obj.instance.syncer.queue('onGetObject', {
  184. id: this.obj.id,
  185. components: components
  186. }, -1);
  187. },
  188. simplify: function (self) {
  189. let values = {};
  190. for (let p in this) {
  191. let value = this[p];
  192. let type = typeof(value);
  193. if (
  194. type === 'undefined' ||
  195. type === 'function' ||
  196. (
  197. type === 'number' &&
  198. isNaN(value)
  199. ) ||
  200. ['obj', 'currentAction', 'events'].includes(p)
  201. )
  202. continue;
  203. if (p === 'autoActive')
  204. value = value !== null;
  205. values[p] = value;
  206. }
  207. if (this.animation)
  208. values.animation = this.animation.name;
  209. if (this.values)
  210. values.values = this.values;
  211. if (this.onAfterSimplify)
  212. this.onAfterSimplify(values);
  213. return values;
  214. },
  215. getDamage: function (target, noMitigate) {
  216. let config = {
  217. source: this.obj,
  218. target: target,
  219. damage: (this.damage || this.healing) * (this.dmgMult || 1),
  220. cd: this.cdMax,
  221. element: this.element,
  222. statType: this.statType,
  223. statMult: this.statMult,
  224. isAttack: this.isAttack,
  225. noScale: this.noScale,
  226. noMitigate: noMitigate
  227. };
  228. if (this.obj.mob)
  229. config.noCrit = true;
  230. this.obj.fireEvent('onBeforeCalculateDamage', config);
  231. if (this.percentDamage)
  232. config.damage = target.stats.values.hpMax * this.damage;
  233. let damage = combat.getDamage(config);
  234. damage.noEvents = this.noEvents;
  235. return damage;
  236. },
  237. queueCallback: function (callback, delay, destroyCallback, target, destroyOnRezone) {
  238. return this.obj.spellbook.registerCallback(this.obj.id, callback, delay, destroyCallback, target ? target.id : null, destroyOnRezone);
  239. },
  240. die: function () {
  241. //We unregister callbacks where we are the source OR the target
  242. this.obj.spellbook.unregisterCallback(this.obj.id);
  243. this.obj.spellbook.unregisterCallback(this.obj.id, true);
  244. }
  245. };