unitbuffs.lua 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. UnitBuffs = class()
  2. function UnitBuffs:init(unit)
  3. self.unit = unit
  4. self.list = {}
  5. table.merge(table.only(self.unit.class, Unit.classStats), self.unit)
  6. self:applyRunes('health')
  7. self:applyRunes('damage')
  8. self:applyRunes('spirit')
  9. self:applyRunes('haste')
  10. end
  11. function UnitBuffs:update()
  12. table.with(self.list, 'rot')
  13. table.with(self.list, 'update')
  14. self.unit.speed = self:getBaseSpeed()
  15. local speed = self.unit.speed
  16. -- Apply Hastes
  17. local hastes = self:buffsWithTag('haste')
  18. table.each(hastes, function(haste)
  19. speed = speed + (self.unit.class.speed * haste.haste)
  20. end)
  21. -- Apply Slows
  22. self.unit.speed = speed * self:slowAmount()
  23. -- Apply Roots and Stuns
  24. if self:rooted() or self:stunned() then self.unit.speed = 0 end
  25. -- Apply Attack Speed Increases
  26. local attackSpeed = self:getBaseAttackSpeed()
  27. local frenzies = self:buffsWithTag('frenzy')
  28. table.each(frenzies, function(frenzy)
  29. attackSpeed = attackSpeed * (1 - frenzy.frenzy)
  30. end)
  31. -- Apply Attack Speed Decreases
  32. local exhausts = self:buffsWithTag('exhaust')
  33. table.each(exhausts, function(exhaust)
  34. attackSpeed = attackSpeed + attackSpeed * (1 - exhaust.exhaust)
  35. end)
  36. self.unit.attackSpeed = math.max(attackSpeed, .4)
  37. -- Apply DoTs
  38. local dots = self:buffsWithTag('dot')
  39. table.each(dots, function(dot)
  40. self.unit:hurt(dot.dot * ls.tickrate, nil, {'dot'})
  41. end)
  42. -- Apply Knockups
  43. self.unit.prev.knockup = self.unit.knockup
  44. self.unit.knockup = 0
  45. local knockups = self:buffsWithTag('knockup')
  46. table.each(knockups, function(knockup)
  47. self.unit.knockup = self.unit.knockup + knockup.knockup
  48. end)
  49. end
  50. function UnitBuffs:add(code, vars)
  51. if not self:shouldApply(code) then return end
  52. if self:isCrowdControl(code) and self:ccImmunity() == 1 then return end
  53. if self:get(code) then return self:reapply(code, vars) end
  54. local buff = data.buff[code]()
  55. buff.unit = self.unit
  56. self.list[buff] = buff
  57. table.merge(vars, buff, true)
  58. if buff.stack then buff.stacks = 1 end
  59. f.exe(buff.activate, buff)
  60. if buff.timer and self:isCrowdControl(code) then
  61. local ccimmunity = self:ccImmunity()
  62. buff.timer = buff.timer * (1 - ccimmunity)
  63. end
  64. return buff
  65. end
  66. function UnitBuffs:remove(buff)
  67. if type(buff) == 'string' then
  68. buff = self:get(buff)
  69. end
  70. if buff then
  71. f.exe(buff and buff.deactivate, buff, self.unit)
  72. self.list[buff] = nil
  73. end
  74. end
  75. function UnitBuffs:get(code)
  76. return next(table.filter(self.list, function(buff) return buff.code == code end))
  77. end
  78. function UnitBuffs:reapply(code, vars)
  79. if self:isCrowdControl(code) and self:ccImmunity() == 1 then return end
  80. local buff = self:get(code)
  81. if buff then
  82. table.merge(vars, buff, true)
  83. if buff.stacks then
  84. buff.stacks = math.min((buff.stacks or 1) + 1, buff.maxStacks or 1)
  85. end
  86. return buff
  87. else
  88. return self:add(code, vars)
  89. end
  90. end
  91. function UnitBuffs:buffsWithTag(tag)
  92. return table.filter(self.list, function(buff) return table.has(buff.tags, tag) end)
  93. end
  94. function UnitBuffs:isCrowdControl(buff)
  95. if type(buff) == 'string' then buff = data.buff[buff] end
  96. local tags = buff.tags
  97. local function t(s) return table.has(tags, s) end
  98. return t('slow') or t('root') or t('stun') or t('silence') or t('knockback') or t('taunt') or t('fear')
  99. end
  100. function UnitBuffs:shouldApply(code)
  101. for buff in pairs(self.list) do
  102. if buff.shouldApplyBuff and buff:shouldApplyBuff(code) == false then
  103. return false
  104. end
  105. end
  106. return true
  107. end
  108. function UnitBuffs:applyRunes(stat)
  109. if not self.unit.player or not self.unit:hasRunes() then return end
  110. local runes = self.unit.player.deck[self.unit.class.code].runes
  111. table.each(runes, function(rune)
  112. if rune.stats and rune.stats[stat] then
  113. self.unit[stat] = self.unit[stat] + rune.stats[stat]
  114. end
  115. end)
  116. end
  117. function UnitBuffs:getBaseSpeed()
  118. local speed = math.max(self.unit.class.speed * (self.unit.health / self.unit.maxHealth) ^ .25, 20)
  119. if not self.unit.player then return speed end
  120. local agilityLevel = self.unit.class.attributes.agility
  121. speed = speed + agilityLevel * config.attributes.agility.speed
  122. local runes = self.unit.player.deck[self.unit.class.code].runes
  123. table.each(runes, function(rune)
  124. if rune.stats and rune.stats.speed then
  125. speed = speed + rune.stats.speed
  126. end
  127. end)
  128. return speed
  129. end
  130. function UnitBuffs:getBaseAttackSpeed()
  131. local baseSpeed = self.unit.class.attackSpeed
  132. local speed = baseSpeed
  133. if not self.unit.player then return speed end
  134. local agilityLevel = self.unit.class.attributes.agility
  135. speed = speed - (agilityLevel * config.attributes.agility.attackSpeed) * baseSpeed
  136. local runes = self.unit.player.deck[self.unit.class.code].runes
  137. table.each(runes, function(rune)
  138. if rune.stats and rune.stats.attackSpeed then
  139. speed = speed - (rune.stats.attackSpeed * baseSpeed)
  140. end
  141. end)
  142. return math.max(speed, .4)
  143. end
  144. function UnitBuffs:prehurt(amount, source, kind)
  145. table.each(self.list, function(buff)
  146. if buff.prehurt then
  147. amount = buff:prehurt(amount, source, kind) or amount
  148. end
  149. end)
  150. if kind and table.has(kind, 'attack') then
  151. local armors = self:buffsWithTag('armor')
  152. local armor = 0
  153. table.each(armors, function(buff)
  154. armor = armor + (1 - armor) * math.clamp(buff.armor * (buff.armorRangedMultiplier or 1), 0, .9)
  155. end)
  156. amount = amount * (1 - armor)
  157. end
  158. return amount
  159. end
  160. function UnitBuffs:posthurt(amount, source, kind)
  161. table.with(self.list, 'posthurt', amount, source, kind)
  162. return amount
  163. end
  164. function UnitBuffs:preattack(target, damage)
  165. table.each(self.list, function(buff)
  166. if buff.preattack then
  167. damage = buff:preattack(target, damage) or damage
  168. end
  169. end)
  170. return damage
  171. end
  172. function UnitBuffs:postattack(target, damage)
  173. table.with(self.list, 'postattack', target, damage)
  174. end
  175. function UnitBuffs:die()
  176. table.with(self.list, 'die')
  177. table.with(self.list, 'deactivate')
  178. end
  179. function UnitBuffs:emitParticles()
  180. -- Frenzy particles
  181. local frenzy = self:frenzied()
  182. if frenzy and frenzy.frenzy > 0 then
  183. for _, bone in pairs({'region_lefthand', 'region_righthand'}) do
  184. bone = self.unit.animation.spine.skeleton:findBone(bone)
  185. if bone then
  186. local x, y = self.unit.animation.spine.skeleton.x + bone.worldX, self.unit.animation.spine.skeleton.y - bone.worldY
  187. ctx.particles:emit('frenzy', x, y, 1)
  188. end
  189. end
  190. end
  191. -- Haste Particles
  192. local haste = self:hasted()
  193. if haste and haste.haste > 0 and love.math.random() < .3 then
  194. ctx.particles:emit('haste', self.unit.x, ctx.map.height - ctx.map.groundHeight, 1, {direction = self.unit.animation.flipped and 0 or math.pi})
  195. end
  196. -- Slow Particles
  197. if self:slowAmount() < 1 and love.math.random() < .3 then
  198. ctx.particles:emit('slow', self.unit.x, ctx.map.height - ctx.map.groundHeight, 1, {direction = self.unit.animation.flipped and 0 or math.pi})
  199. end
  200. end
  201. function UnitBuffs:slowed()
  202. return next(self:buffsWithTag('slow'))
  203. end
  204. function UnitBuffs:slowAmount()
  205. local multiplier = 1
  206. local slows = self:buffsWithTag('slow')
  207. table.each(slows, function(slow)
  208. multiplier = multiplier * (1 - slow.slow)
  209. end)
  210. if self:feared() then multiplier = multiplier / 2 end
  211. return multiplier
  212. end
  213. function UnitBuffs:hasted()
  214. return next(self:buffsWithTag('haste'))
  215. end
  216. function UnitBuffs:taunted()
  217. local taunt = next(self:buffsWithTag('taunt'))
  218. return taunt and taunt.target
  219. end
  220. function UnitBuffs:stunned()
  221. return next(self:buffsWithTag('stun'))
  222. end
  223. function UnitBuffs:rooted()
  224. return next(self:buffsWithTag('root'))
  225. end
  226. function UnitBuffs:silenced()
  227. return next(self:buffsWithTag('silence'))
  228. end
  229. function UnitBuffs:ccImmunity()
  230. local vulnerability = 1
  231. table.each(self:buffsWithTag('ccimmune'), function(buff)
  232. vulnerability = vulnerability * (1 - buff.ccimmunity)
  233. end)
  234. return 1 - vulnerability
  235. end
  236. function UnitBuffs:feared()
  237. return next(self:buffsWithTag('fear'))
  238. end
  239. function UnitBuffs:potency()
  240. local ratio = 1
  241. table.each(self:buffsWithTag('potency'), function(potency)
  242. ratio = ratio * potency.potency
  243. end)
  244. return ratio
  245. end
  246. function UnitBuffs:frenzied()
  247. return next(self:buffsWithTag('frenzy'))
  248. end