base.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. local Player = class()
  2. Player.radius = 28
  3. Player.collision = {
  4. shape = 'circle',
  5. tag = 'player',
  6. with = {
  7. wall = function(self, other, dx, dy)
  8. if self.z == 0 then
  9. self.x, self.y = self.x + dx, self.y + dy
  10. end
  11. end,
  12. shortwall = function(self, other, dx, dy)
  13. if self.z == 0 then
  14. self.x, self.y = self.x + dx, self.y + dy
  15. end
  16. end,
  17. teamwall = function(self, other, dx, dy)
  18. if other.team ~= self.team and self.z == 0 then
  19. self.x, self.y = self.x + dx, self.y + dy
  20. end
  21. end,
  22. player = function(self, other, dx, dy)
  23. if other.team ~= self.team then
  24. if self.moving then
  25. self.x, self.y = self.x + dx, self.y + dy
  26. end
  27. end
  28. end
  29. }
  30. }
  31. ----------------
  32. -- Core
  33. ----------------
  34. function Player:init()
  35. self.meta = {__index = self}
  36. self.id = nil
  37. self.username = ''
  38. self.class = nil
  39. self.team = nil
  40. self.active = false
  41. self.kills = 0
  42. self.deaths = 0
  43. self.x = 0
  44. self.y = 0
  45. self.z = 0
  46. self.angle = 0
  47. self.drawX = 0
  48. self.drawY = 0
  49. self.drawAngle = 0
  50. self.drawScale = 0
  51. self.slots = {}
  52. self.weapon = 1
  53. self.skill = 1
  54. self.weaponDirty = false
  55. self.skillDirty = false
  56. self.health = 0
  57. self.maxHealth = 0
  58. self.shield = 0
  59. self.lastHurt = -math.huge
  60. self.lastDamageDealt = -math.huge
  61. self.ded = false
  62. self.killer = nil
  63. self.lifesteal = 0
  64. self.haste = 0
  65. self.cloak = 0
  66. self.stun = 0
  67. self.disarm = 0
  68. self.silence = 0
  69. self.damageOutMultiplier = 1
  70. self.damageInMultiplier = 1
  71. self.depth = 0
  72. self.recoil = 0
  73. self.alpha = 0
  74. self.canvasDirty = true
  75. end
  76. function Player:activate()
  77. self.active = true
  78. self.x = ctx.map.spawn[self.team].x
  79. self.y = ctx.map.spawn[self.team].y
  80. self.z = 0
  81. self.weapon = 1
  82. self.skill = 1
  83. table.each(self.slots, function(slot)
  84. f.exe(slot.deactivate, slot, self)
  85. end)
  86. table.clear(self.slots)
  87. for i = 1, 5 do
  88. self.slots[i] = setmetatable({}, {__index = self.class.slots[i]})
  89. f.exe(self.slots[i].activate, self.slots[i], self)
  90. if self.slots[i].type == 'skill' and self.skill == self.weapon then self.skill = i end
  91. end
  92. self.maxHealth = self.class.health
  93. self.health = self.maxHealth
  94. self.killer = nil
  95. ctx.buffs:removeAll(self)
  96. self.lifesteal = 0
  97. self.haste = 0
  98. self.cloak = 0
  99. self.stun = 0
  100. self.damageOutMultiplier = 1
  101. self.damageInMultiplier = 1
  102. self.depth = -self.id
  103. self.canvas = nil
  104. self.canvasDirty = true
  105. end
  106. function Player:deactivate()
  107. self.active = false
  108. self.username = ''
  109. end
  110. function Player:update()
  111. if self.recoil > 0 then self.recoil = math.lerp(self.recoil, 0, math.min(5 * tickRate, 1)) end
  112. self.cloak = timer.rot(self.cloak)
  113. self.stun = timer.rot(self.stun)
  114. self.disarm = timer.rot(self.disarm)
  115. self.silence = timer.rot(self.silence)
  116. if self.ded then
  117. self.x, self.y = 0, 0
  118. ctx.event:emit('collision.move', {object = self, resolve = true})
  119. end
  120. if ctx.view then
  121. self.depth = ctx.view:threeDepth(self.x, self.y, self.z)
  122. if self.z > 0 then self.depth = self.depth - 100 end
  123. end
  124. end
  125. function Player:draw()
  126. local g, c, x, y, a, s = love.graphics, self.class, self.drawX, self.drawY, self.drawAngle, self.drawScale * self.class.scale
  127. local alpha = self.alpha * (1 - (self.cloak / ((self.team == ctx.players:get(ctx.id).team or (ctx.players:get(ctx.id).killer and ctx.players:get(ctx.id).killer.id == self.id)) and 2 or 1)))
  128. if self.canvasDirty then self:refreshCanvas() end
  129. g.setColor(0, 0, 0, alpha * 50)
  130. g.draw(c.sprite, self.x + 4, self.y + 4, a, s, s, c.anchorx, c.anchory)
  131. g.setColor(255, 255, 255, alpha * 255)
  132. g.draw(self.canvas, x, y, a, s, s, self.canvasAnchorX, self.canvasAnchorY)
  133. g.setColor(self.team == purple and {222, 200, 230, alpha * 255} or {250, 210, 200, alpha * 255})
  134. g.draw(c.sprite, x, y, a, s, s, c.anchorx, c.anchory)
  135. f.exe(self.slots[self.weapon].draw, self.slots[self.weapon], self)
  136. end
  137. ----------------
  138. -- Behavior
  139. ----------------
  140. function Player:move(input)
  141. if self.stun > 0 then return end
  142. if input.reposition then
  143. self.x, self.y = input.reposition.x, input.reposition.y
  144. end
  145. local w, a, s, d = input.w, input.a, input.s, input.d
  146. if not (w or a or s or d) then return end
  147. local up, down, left, right, dx, dy = 1.5 * math.pi, .5 * math.pi, math.pi, 2.0 * math.pi
  148. if a and not d then dx = left elseif d then dx = right end
  149. if w and not s then dy = up elseif s then dy = down end
  150. if not dx then dx = dy end
  151. if not dy then dy = dx end
  152. if dx == right and dy == down then dx = 0 end
  153. local dir = (dx + dy) / 2
  154. local len = math.max((self.class.speed + self.haste) * tickRate, 0)
  155. self.x, self.y = self.x + math.dx(len, dir), self.y + math.dy(len, dir)
  156. self.x = math.clamp(self.x, 0, ctx.map.width)
  157. self.y = math.clamp(self.y, 0, ctx.map.height)
  158. self.moving = true
  159. ctx.event:emit('collision.move', {object = self, resolve = true})
  160. self.moving = nil
  161. end
  162. function Player:turn(input)
  163. if self.stun > 0 then return end
  164. local d = math.direction(self.x, self.y, input.x, input.y)
  165. self.angle = math.anglerp(self.angle, d, math.min(25 * tickRate, 1))
  166. end
  167. function Player:slot(input, prev)
  168. if self.stun > 0 then return end
  169. input, prev = input or {}, prev or {}
  170. local ldown, lpress, lrelease = input.l, input.l and not prev.l, not input.l and prev.l
  171. local rdown, rpress, rrelease = input.r, input.r and not prev.r, not input.r and prev.r
  172. if input.slot then
  173. local k = self.slots[input.slot].type
  174. if self[k] ~= input.slot and (not self.slots[self[k]] or not self.slots[self[k]].targeting) then
  175. self[k] = input.slot
  176. local slot = self.slots[input.slot]
  177. f.exe(slot.select, slot, self)
  178. self.canvasDirty = true
  179. end
  180. end
  181. local weapon = self.slots[self.weapon]
  182. local skill = self.slots[self.skill]
  183. local function fire(slot)
  184. local obj = self.slots[slot]
  185. local msg = {id = self.id, slot = slot, mx = obj.needsMouse and input.x, my = obj.needsMouse and input.y}
  186. obj:fire(self, input.x, input.y)
  187. if obj.targeted then obj.targeting = false end
  188. ctx.net:emit(app.net.events.fire, msg)
  189. end
  190. for i = 1, 5 do
  191. if self.slots[i].type ~= 'weapon' or self.weapon == i then
  192. f.exe(self.slots[i].update, self.slots[i], self)
  193. end
  194. end
  195. if lpress and skill.targeting then
  196. skill.targeting = false
  197. self.weaponDirty = true
  198. end
  199. if rpress and weapon.targeting then
  200. weapon.targeting = false
  201. self.skillDirty = true
  202. end
  203. if self.disarm == 0 and weapon:canFire(self) then
  204. if not ldown then self.weaponDirty = false end
  205. if not self.weaponDirty then
  206. if lpress then weapon.targeting = weapon.targeted end
  207. if (weapon.targeted and weapon.targeting and lrelease) or (not weapon.targeted and ldown) then
  208. fire(self.weapon)
  209. end
  210. end
  211. end
  212. if self.silence == 0 and skill:canFire(self) then
  213. if not rdown then self.skillDirty = false end
  214. if not self.skillDirty then
  215. if rpress then skill.targeting = skill.targeted end
  216. if (skill.targeted and skill.targeting and rrelease) or (not skill.targeted and rdown) then
  217. fire(self.skill)
  218. end
  219. end
  220. end
  221. if input.reload then weapon:reload(self) end
  222. end
  223. Player.hurt = f.empty
  224. Player.heal = f.empty
  225. function Player:die()
  226. self.ded = 5
  227. ctx.event:emit('particle.create', {
  228. kind = 'skull',
  229. vars = {x = self.x, y = self.y}
  230. })
  231. ctx.event:emit('particle.create', {
  232. kind = 'gib',
  233. count = 64,
  234. vars = {x = self.x, y = self.y}
  235. })
  236. ctx.event:emit('sound.play', {sound = 'die', x = self.x, y = self.y})
  237. ctx.buffs:removeAll(self)
  238. self.alpha = 0
  239. self.x, self.y = 0, 0
  240. ctx.event:emit('collision.move', {object = self, resolve = true})
  241. end
  242. function Player:spawn()
  243. self:activate()
  244. end
  245. function Player:refreshCanvas()
  246. local g, c, wep = love.graphics, self.class, self.slots[self.weapon]
  247. local margin = 16
  248. local w, h = 0, 0
  249. self = ctx.players:get(self.id)
  250. if not wep.image then
  251. self.canvasAnchorX, self.canvasAnchorY = c.anchorx + margin, c.anchory + margin
  252. w, h = (2 * margin) + c.sprite:getWidth(), (2 * margin) + c.sprite:getHeight()
  253. else
  254. local ws = wep.scale / c.scale
  255. local ax, ay = c.anchorx, c.anchory
  256. local px1, py1, px2, py2 = 0, 0, c.sprite:getWidth(), c.sprite:getHeight()
  257. local dx, dy = c.handx - self.recoil, c.handy
  258. local wx1 = ax + math.dx(dx, 0) - math.dy(dy, 0) - (wep.anchorx * ws)
  259. local wy1 = ay + math.dy(dx, 0) + math.dx(dy, 0) - (wep.anchory * ws)
  260. local wx2 = wx1 + wep.image:getWidth() * ws
  261. local wy2 = wy1 + wep.image:getHeight() * ws
  262. if wx1 < 0 then
  263. ax = ax + wx1
  264. px1, px2 = px1 + wx1, px2 + wx1
  265. wx1, wx2 = wx1 + wx1, wx2 + wx1
  266. end
  267. if wy1 < 0 then
  268. ay = ay + wy1
  269. py1, py2 = py1 + wy1, py2 + wy1
  270. wy1, wy2 = wy1 + wy1, wy2 + wy2
  271. end
  272. px1, py1, px2, py2 = px1 + margin, py1 + margin, px2 + margin, py2 + margin
  273. wx1, wy1, wx2, wy2 = wx1 + margin, wy1 + margin, wx2 + margin, wy2 + margin
  274. ax, ay = ax + margin, ay + margin
  275. w, h = math.max(px2, wx2) + margin, math.max(py2, wy2) + margin
  276. self.canvasAnchorX, self.canvasAnchorY = ax, ay
  277. end
  278. if not self.canvas or self.canvas:getWidth() < w or self.canvas:getHeight() < h then
  279. self.canvas = g.newCanvas(w, h)
  280. self.backCanvas = g.newCanvas(w, h)
  281. end
  282. g.pop()
  283. if self.team == purple then
  284. self.canvas:clear(190, 160, 220, 0)
  285. self.backCanvas:clear(190, 160, 220, 0)
  286. g.setColor(190, 160, 220)
  287. else
  288. self.canvas:clear(240, 160, 140, 0)
  289. self.backCanvas:clear(240, 160, 140, 0)
  290. g.setColor(240, 160, 140)
  291. end
  292. self.canvas:renderTo(function()
  293. g.setShader(data.media.shaders.colorize)
  294. g.draw(c.sprite, self.canvasAnchorX, self.canvasAnchorY, 0, 1, 1, c.anchorx, c.anchory)
  295. if wep.image then
  296. local dx, dy = c.handx - self.recoil, c.handy
  297. local wx = self.canvasAnchorX + math.dx(dx, 0) - math.dy(dy, 0)
  298. local wy = self.canvasAnchorY + math.dy(dx, 0) + math.dx(dy, 0)
  299. g.draw(wep.image, wx, wy, 0, wep.scale / c.scale, wep.scale / c.scale, wep.anchorx, wep.anchory)
  300. end
  301. g.setShader()
  302. end)
  303. data.media.shaders.horizontalBlur:send('amount', .008)
  304. data.media.shaders.verticalBlur:send('amount', .008)
  305. g.setColor(255, 255, 255)
  306. for i = 1, 3 do
  307. g.setShader(data.media.shaders.horizontalBlur)
  308. self.backCanvas:renderTo(function()
  309. g.draw(self.canvas)
  310. end)
  311. g.setShader(data.media.shaders.verticalBlur)
  312. self.canvas:renderTo(function()
  313. g.draw(self.backCanvas)
  314. end)
  315. end
  316. g.setShader()
  317. ctx.view:worldPush()
  318. self.canvasDirty = false
  319. end
  320. return Player