25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

323 lines
7.4 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. spell: this,
  39. castTimeMax: castTimeMax
  40. };
  41. this.obj.fireEvent('beforeGetSpellCastTime', castEvent);
  42. this.currentAction.castTimeMax = castEvent.castTimeMax;
  43. this.castTime = castEvent.castTimeMax;
  44. this.obj.syncer.set(false, null, 'casting', 0);
  45. }
  46. return null;
  47. }
  48. return this.cast(action);
  49. },
  50. updateBase: function () {
  51. //It's possible that we rezoned midway through casting (map regen)
  52. // We'll have a hanging cast bar but at least we won't crash
  53. if (this.castTime > 0 && !this.currentAction)
  54. this.castTime = 0;
  55. if (this.castTime > 0) {
  56. let action = this.currentAction;
  57. if (_.getDeepProperty(action, 'target.destroyed') || !this.canCast(action.target)) {
  58. this.currentAction = null;
  59. this.castTime = 0;
  60. this.obj.syncer.set(false, null, 'casting', 0);
  61. return;
  62. }
  63. this.castTime--;
  64. this.obj.syncer.set(false, null, 'casting', (action.castTimeMax - this.castTime) / action.castTimeMax);
  65. if (!this.castTime) {
  66. this.currentAction = null;
  67. if (this.cast(action)) {
  68. this.consumeMana();
  69. this.setCd();
  70. this.obj.fireEvent('afterCastSpell', {
  71. castSuccess: true,
  72. spell: this,
  73. action
  74. });
  75. }
  76. } else {
  77. if (this.onCastTick)
  78. this.onCastTick();
  79. this.sendBump(null, 0, -1);
  80. }
  81. return;
  82. }
  83. if (this.cd > 0) {
  84. this.cd--;
  85. if (this.cd === 0)
  86. this.obj.syncer.setArray(true, 'spellbook', 'getSpells', this.simplify());
  87. }
  88. },
  89. consumeMana: function () {
  90. let stats = this.obj.stats.values;
  91. stats.mana -= this.manaCost;
  92. if (this.obj.player)
  93. this.obj.syncer.setObject(true, 'stats', 'values', 'mana', stats.mana);
  94. },
  95. setCd: function () {
  96. let cd = {
  97. cd: this.cdMax
  98. };
  99. this.obj.fireEvent('beforeSetSpellCooldown', cd, this);
  100. this.cd = cd.cd;
  101. if (this.obj.player) {
  102. this.obj.instance.syncer.queue('onGetSpellCooldowns', {
  103. spell: this.id,
  104. cd: (this.cd * consts.tickTime)
  105. }, [this.obj.serverId]);
  106. }
  107. },
  108. setAuto: function (autoConfig) {
  109. this.autoActive = autoConfig;
  110. if (this.obj.player) {
  111. this.obj.instance.syncer.queue('onGetSpellActive', {
  112. id: this.obj.id,
  113. spell: this.id,
  114. cd: (this.cd * consts.tickTime),
  115. active: !!autoConfig
  116. }, [this.obj.serverId]);
  117. }
  118. },
  119. calcDps: function (target, noSync) {
  120. if ((!this.values) || (this.spellType === 'buff') || (this.spellType === 'aura'))
  121. return;
  122. if ((!this.damage) && (!this.healing))
  123. delete this.values.dps;
  124. else {
  125. let noMitigate = !target;
  126. let dmg = combat.getDamage({
  127. source: this.obj,
  128. target: (target || {
  129. stats: {
  130. values: {}
  131. }
  132. }),
  133. damage: (this.damage || this.healing) * (this.dmgMult || 1),
  134. cd: this.cdMax,
  135. element: this.element,
  136. statType: this.statType,
  137. statMult: this.statMult,
  138. noMitigate: noMitigate,
  139. isAttack: this.isAttack,
  140. noCrit: true
  141. }).amount;
  142. let statValues = this.obj.stats.values;
  143. let critChance = statValues.critChance + (this.isAttack ? statValues.attackCritChance : statValues.spellCritChance);
  144. let critMultiplier = statValues.critMultiplier + (this.isAttack ? statValues.attackCritMultiplier : statValues.spellCritMultiplier);
  145. let castTimeMax = this.castTimeMax;
  146. let speedModifier = this.obj.stats.values[this.isAttack ? 'attackSpeed' : 'castSpeed'];
  147. castTimeMax = Math.ceil(castTimeMax * (1 - (Math.min(50, speedModifier) / 100)));
  148. critChance = Math.min(critChance, 100);
  149. dmg = (((dmg / 100) * (100 - critChance)) + (((dmg / 100) * critChance) * (critMultiplier / 100)));
  150. let duration = this.values.duration;
  151. if (duration)
  152. dmg *= duration;
  153. const div = (this.cdMax + castTimeMax) || 1;
  154. dmg /= div;
  155. if (this.damage)
  156. this.values.dmg = ~~(dmg * 100) / 100 + '/tick';
  157. else
  158. this.values.heal = ~~(dmg * 100) / 100 + '/tick';
  159. if (!noSync)
  160. this.obj.syncer.setArray(true, 'spellbook', 'getSpells', this.simplify());
  161. }
  162. },
  163. sendAnimation: function (blueprint) {
  164. this.obj.instance.syncer.queue('onGetObject', blueprint, -1);
  165. },
  166. sendBump: function (target, deltaX, deltaY) {
  167. if (target) {
  168. let x = this.obj.x;
  169. let y = this.obj.y;
  170. let tx = target.x;
  171. let ty = target.y;
  172. if (tx < x)
  173. deltaX = -1;
  174. else if (tx > x)
  175. deltaX = 1;
  176. if (ty < y)
  177. deltaY = -1;
  178. else if (ty > y)
  179. deltaY = 1;
  180. }
  181. let components = [{
  182. type: 'bumpAnimation',
  183. deltaX: deltaX,
  184. deltaY: deltaY
  185. }];
  186. //During casting we only bump
  187. if ((target) && (this.animation)) {
  188. components.push({
  189. type: 'animation',
  190. template: this.animation
  191. });
  192. }
  193. this.obj.instance.syncer.queue('onGetObject', {
  194. id: this.obj.id,
  195. components: components
  196. }, -1);
  197. },
  198. simplify: function (self) {
  199. let values = {};
  200. for (let p in this) {
  201. let value = this[p];
  202. let type = typeof(value);
  203. if (
  204. type === 'undefined' ||
  205. type === 'function' ||
  206. (
  207. type === 'number' &&
  208. isNaN(value)
  209. ) ||
  210. ['obj', 'currentAction', 'events'].includes(p)
  211. )
  212. continue;
  213. if (p === 'autoActive')
  214. value = value !== null;
  215. values[p] = value;
  216. }
  217. if (this.animation)
  218. values.animation = this.animation.name;
  219. if (this.values)
  220. values.values = this.values;
  221. if (this.onAfterSimplify)
  222. this.onAfterSimplify(values);
  223. return values;
  224. },
  225. getDamage: function (target, noMitigate) {
  226. let config = {
  227. source: this.obj,
  228. target: target,
  229. damage: (this.damage || this.healing) * (this.dmgMult || 1),
  230. cd: this.cdMax,
  231. element: this.element,
  232. statType: this.statType,
  233. statMult: this.statMult,
  234. isAttack: this.isAttack,
  235. noScale: this.noScale,
  236. noMitigate: noMitigate,
  237. spell: this,
  238. scaleConfig: this.scaleConfig
  239. };
  240. if (this.obj.mob)
  241. config.noCrit = true;
  242. this.obj.fireEvent('onBeforeCalculateDamage', config);
  243. if (this.percentDamage)
  244. config.damage = target.stats.values.hpMax * this.damage;
  245. let damage = combat.getDamage(config);
  246. damage.noEvents = this.noEvents;
  247. return damage;
  248. },
  249. queueCallback: function (callback, delay, destroyCallback, target, destroyOnRezone) {
  250. return this.obj.spellbook.registerCallback(this.obj.id, callback, delay, destroyCallback, target ? target.id : null, destroyOnRezone);
  251. },
  252. die: function () {
  253. //We unregister callbacks where we are the source OR the target
  254. this.obj.spellbook.unregisterCallback(this.obj.id);
  255. this.obj.spellbook.unregisterCallback(this.obj.id, true);
  256. }
  257. };