unitbuffs.lua 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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. local speed = self.unit.class.speed
  120. if not self.unit.player then return speed end
  121. local agilityLevel = self.unit.class.attributes.agility
  122. speed = speed + agilityLevel * config.attributes.agility.speed
  123. local runes = self.unit.player.deck[self.unit.class.code].runes
  124. table.each(runes, function(rune)
  125. if rune.stats and rune.stats.speed then
  126. speed = speed + rune.stats.speed
  127. end
  128. end)
  129. return speed
  130. end
  131. function UnitBuffs:getBaseAttackSpeed()
  132. local baseSpeed = self.unit.class.attackSpeed
  133. local speed = baseSpeed
  134. if not self.unit.player then return speed end
  135. local agilityLevel = self.unit.class.attributes.agility
  136. speed = speed - (agilityLevel * config.attributes.agility.attackSpeed) * baseSpeed
  137. local runes = self.unit.player.deck[self.unit.class.code].runes
  138. table.each(runes, function(rune)
  139. if rune.stats and rune.stats.attackSpeed then
  140. speed = speed - (rune.stats.attackSpeed * baseSpeed)
  141. end
  142. end)
  143. return math.max(speed, .4)
  144. end
  145. function UnitBuffs:prehurt(amount, source, kind)
  146. table.each(self.list, function(buff)
  147. if buff.prehurt then
  148. amount = buff:prehurt(amount, source, kind) or amount
  149. end
  150. end)
  151. if kind and table.has(kind, 'attack') then
  152. local armors = self:buffsWithTag('armor')
  153. local armor = 0
  154. table.each(armors, function(buff)
  155. armor = armor + (1 - armor) * math.clamp(buff.armor * (buff.armorRangedMultiplier or 1), 0, .9)
  156. end)
  157. amount = amount * (1 - armor)
  158. end
  159. return amount
  160. end
  161. function UnitBuffs:posthurt(amount, source, kind)
  162. table.with(self.list, 'posthurt', amount, source, kind)
  163. return amount
  164. end
  165. function UnitBuffs:preattack(target, damage)
  166. table.each(self.list, function(buff)
  167. if buff.preattack then
  168. damage = buff:preattack(target, damage) or damage
  169. end
  170. end)
  171. return damage
  172. end
  173. function UnitBuffs:postattack(target, damage)
  174. table.with(self.list, 'postattack', target, damage)
  175. end
  176. function UnitBuffs:die()
  177. table.with(self.list, 'die')
  178. table.with(self.list, 'deactivate')
  179. end
  180. function UnitBuffs:emitParticles()
  181. -- Frenzy particles
  182. local frenzy = self:frenzied()
  183. if frenzy and frenzy.frenzy > 0 then
  184. for _, bone in pairs({'region_lefthand', 'region_righthand'}) do
  185. bone = self.unit.animation.spine.skeleton:findBone(bone)
  186. if bone then
  187. local x, y = self.unit.animation.spine.skeleton.x + bone.worldX, self.unit.animation.spine.skeleton.y - bone.worldY
  188. ctx.particles:emit('frenzy', x, y, 1)
  189. end
  190. end
  191. end
  192. -- Haste Particles
  193. local haste = self:hasted()
  194. if haste and haste.haste > 0 and love.math.random() < .3 then
  195. ctx.particles:emit('haste', self.unit.x, ctx.map.height - ctx.map.groundHeight, 1, {direction = self.unit.animation.flipped and 0 or math.pi})
  196. end
  197. -- Slow Particles
  198. if self:slowAmount() < 1 and love.math.random() < .3 then
  199. ctx.particles:emit('slow', self.unit.x, ctx.map.height - ctx.map.groundHeight, 1, {direction = self.unit.animation.flipped and 0 or math.pi})
  200. end
  201. end
  202. function UnitBuffs:slowed()
  203. return next(self:buffsWithTag('slow'))
  204. end
  205. function UnitBuffs:slowAmount()
  206. local multiplier = 1
  207. local slows = self:buffsWithTag('slow')
  208. table.each(slows, function(slow)
  209. multiplier = multiplier * (1 - slow.slow)
  210. end)
  211. if self:feared() then multiplier = multiplier / 2 end
  212. return multiplier
  213. end
  214. function UnitBuffs:hasted()
  215. return next(self:buffsWithTag('haste'))
  216. end
  217. function UnitBuffs:taunted()
  218. local taunt = next(self:buffsWithTag('taunt'))
  219. return taunt and taunt.target
  220. end
  221. function UnitBuffs:stunned()
  222. return next(self:buffsWithTag('stun'))
  223. end
  224. function UnitBuffs:rooted()
  225. return next(self:buffsWithTag('root'))
  226. end
  227. function UnitBuffs:silenced()
  228. return next(self:buffsWithTag('silence'))
  229. end
  230. function UnitBuffs:ccImmunity()
  231. local vulnerability = 1
  232. table.each(self:buffsWithTag('ccimmune'), function(buff)
  233. vulnerability = vulnerability * (1 - buff.ccimmunity)
  234. end)
  235. return 1 - vulnerability
  236. end
  237. function UnitBuffs:feared()
  238. return next(self:buffsWithTag('fear'))
  239. end
  240. function UnitBuffs:potency()
  241. local ratio = 1
  242. table.each(self:buffsWithTag('potency'), function(potency)
  243. ratio = ratio * potency.potency
  244. end)
  245. return ratio
  246. end
  247. function UnitBuffs:frenzied()
  248. return next(self:buffsWithTag('frenzy'))
  249. end