pigeon.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. Pigeon = class()
  2. ----------------
  3. -- Constants
  4. ----------------
  5. Pigeon.walkForce = 600
  6. Pigeon.maxSpeed = 350
  7. Pigeon.jumpForce = 3000
  8. Pigeon.flySpeed = 75
  9. Pigeon.rocketForce = 500
  10. Pigeon.maxFlySpeed = 300
  11. Pigeon.maxFuel = 25
  12. Pigeon.laserTurnSpeed = .75
  13. Pigeon.laserChargeDuration = 2
  14. Pigeon.rainbowShitThreshold = 50
  15. ----------------
  16. -- Core
  17. ----------------
  18. function Pigeon:init()
  19. self.shapeSize = 50
  20. self.body = love.physics.newBody(ctx.world, ctx.view.width / 2, ctx.map.height - ctx.map.ground.height - self.shapeSize / 2, 'dynamic')
  21. self.shape = love.physics.newRectangleShape(self.shapeSize, self.shapeSize)
  22. self.fixture = love.physics.newFixture(self.body, self.shape)
  23. self.body:setFixedRotation(true)
  24. self.body:setGravityScale(5)
  25. self.body:setUserData(self)
  26. self.fixture:setCategory(ctx.categories.pigeon)
  27. self.fixture:setMask(ctx.categories.oneWayPlatform, ctx.categories.person, ctx.categories.debris)
  28. self.phlerp = PhysicsInterpolator(self)
  29. self.fuel = self.maxFuel
  30. self.state = self.idle
  31. self.animation = data.animation.pigeon()
  32. self.animation:on('complete', function(event)
  33. if event.state.name == 'jump' then
  34. self.animation:set('walk')
  35. elseif event.state.name == 'peck' then
  36. self:changeState('walk')
  37. elseif event.state.name == 'laserStart' then
  38. self.animation:set('laserCharge')
  39. elseif event.state.name == 'laserEnd' then
  40. self:changeState('idle')
  41. self.laser.active = false
  42. elseif event.state.name == 'flyStart' then
  43. self.animation:set('fly')
  44. end
  45. end)
  46. self.animation:on('event', function(event)
  47. local name = event.data.name
  48. if name == 'rightDrop' then
  49. self.drop = 'right'
  50. elseif name == 'leftDrop' then
  51. self.drop = 'left'
  52. elseif name == 'rightStep' then
  53. self.slide = 'right'
  54. self.drop = nil
  55. if self.walk.firstShake == false then
  56. ctx.view:screenshake(10)
  57. self.stepsTaken = self.stepsTaken + 1
  58. local skeleton = self.animation.spine.skeleton
  59. local bone = skeleton:findBone('rightfoot')
  60. local x = skeleton.x + bone.worldX
  61. local y = skeleton.y + bone.worldY
  62. ctx.particles:emit('feetdust', x, y, 20)
  63. else
  64. self.walk.firstShake = false
  65. end
  66. if self.rightWhirr then
  67. self.rightWhirr:stop()
  68. end
  69. self.leftWhirr = ctx.sound:play('whirr', function(sound)
  70. sound:setVolume(.35)
  71. sound:setPitch(lume.random(.95, 1.05))
  72. end)
  73. ctx.sound:play('impact', function(sound)
  74. sound:setVolume(.5)
  75. end)
  76. elseif name == 'leftStep' then
  77. self.slide = 'left'
  78. self.drop = nil
  79. if self.walk.firstShake == false then
  80. ctx.view:screenshake(10)
  81. self.stepsTaken = self.stepsTaken + 1
  82. local skeleton = self.animation.spine.skeleton
  83. local bone = skeleton:findBone('leftfoot')
  84. local x = skeleton.x + bone.worldX
  85. local y = skeleton.y + bone.worldY
  86. ctx.particles:emit('feetdust', x, y, 20)
  87. else
  88. self.walk.firstShake = false
  89. end
  90. if self.leftWhirr then
  91. self.leftWhirr:stop()
  92. end
  93. self.rightWhirr = ctx.sound:play('whirr', function(sound)
  94. sound:setVolume(.35)
  95. sound:setPitch(lume.random(.95, 1.05))
  96. end)
  97. ctx.sound:play('impact', function(sound)
  98. sound:setVolume(.5)
  99. end)
  100. elseif name == 'leftStop' or name == 'rightStop' then
  101. self.slide = nil
  102. elseif name == 'jump' then
  103. ctx.sound:play('jump', function(sound)
  104. sound:setVolume(.35)
  105. end)
  106. self:jump()
  107. elseif name == 'laser' then
  108. self.laser.active = true
  109. elseif name == 'peck' and self.state == self.peck then
  110. self.peck.impact(self)
  111. end
  112. end)
  113. self.slide = nil
  114. self.slideSpeeds = {
  115. left = 693,
  116. right = 555
  117. }
  118. self.leftWhirr = nil
  119. self.rightWhirr = nil
  120. self.drop = nil
  121. self.downDirty = 0
  122. self.crushGrace = 0
  123. self.rainbowShitSound = nil
  124. self.stepsTaken = 0
  125. self.jumps = 0
  126. self.pecks = 0
  127. self.rainbowShits = 0
  128. self.peckDirty = false
  129. self:initBeak()
  130. self:initFeet()
  131. self.rainbowShitTimer = 0
  132. ctx.event:emit('view.register', {object = self, depth = -10})
  133. end
  134. function Pigeon:update()
  135. self.phlerp:update()
  136. self.grounded = self:getGrounded()
  137. f.exe(self.state.update, self)
  138. self:contain()
  139. --self.animation.scale = self.rainbowShitTimer > 0 and 1 or .7
  140. self.rainbowShitTimer = timer.rot(self.rainbowShitTimer, function()
  141. ctx.backgroundSound:resume()
  142. self.rainbowShitSound:stop()
  143. flux.to(self.animation, .2, {scale = .7}, 'elasticout')
  144. end)
  145. if love.keyboard.isDown('down', 's') or (joystick and (joystick:getGamepadAxis('lefty') > .5 or (joystick:isGamepadDown('dpdown')))) then
  146. self.downDirty = timer.rot(self.downDirty)
  147. else
  148. self.downDirty = .1
  149. end
  150. local skeleton = self.animation.spine.skeleton
  151. skeleton.flipY = true
  152. skeleton:updateWorldTransform()
  153. local bone = skeleton:findBone('leftwing')
  154. local x, y = skeleton.x + bone.worldX, skeleton.y + bone.worldY
  155. local dir = (-bone.worldRotation * math.pi / 180) - .2
  156. x = x + math.cos(dir) * (bone.data.length + 100) * self.animation.scale
  157. y = y + math.sin(dir) * (bone.data.length + 100) * self.animation.scale
  158. ctx.particles:emit(self.rainbowShitTimer > 0 and 'rainbowshit' or 'smoke', x, y, 3, {direction = -bone.worldRotation * math.pi / 180})
  159. skeleton.flipY = false
  160. self.crushGrace = timer.rot(self.crushGrace)
  161. if not love.keyboard.isDown(' ') then
  162. self.peckDirty = false
  163. end
  164. self:updateBeak()
  165. self:updateFeet()
  166. if self.body:getX() > ctx.goal.x then
  167. if self.state ~= self.idle then
  168. self:changeState('idle')
  169. end
  170. self.animation:set('flyLoop')
  171. if not ctx.hud.win.active then
  172. ctx.hud:activateWin()
  173. ctx.sound:play('win')
  174. if not ctx.sound.muted then
  175. ctx.backgroundSound:setVolume(.1)
  176. end
  177. end
  178. end
  179. end
  180. function Pigeon:draw()
  181. local g = love.graphics
  182. self.phlerp:lerp()
  183. g.setColor(255, 255, 255)
  184. local x, y = self.body:getPosition()
  185. self.animation:draw(x, y + self.shapeSize / 2, {noupdate = ctx.paused})
  186. if ctx.debug then
  187. local x1, y1, x2, y2 = self:getGroundRaycastPoints()
  188. g.setColor(self.grounded and {0, 255, 0} or {255, 0, 0})
  189. g.line(x1, y1, x2, y2)
  190. end
  191. if ctx.debug then
  192. g.setColor(255, 255, 255)
  193. local points = {self.feet.left.body:getWorldPoints(self.feet.left.shape:getPoints())}
  194. g.polygon('line', points)
  195. local points = {self.feet.right.body:getWorldPoints(self.feet.right.shape:getPoints())}
  196. g.polygon('line', points)
  197. local points = {self.beak.top.body:getWorldPoints(self.beak.top.shape:getPoints())}
  198. g.polygon('line', points)
  199. local points = {self.beak.bottom.body:getWorldPoints(self.beak.bottom.shape:getPoints())}
  200. g.polygon('line', points)
  201. end
  202. if self.state == self.laser then
  203. local x1, y1, x2, y2 = self:getLaserRaycastPoints()
  204. local dis, dir = math.vector(x1, y1, x2, y2)
  205. local len = dis
  206. ctx.world:rayCast(x1, y1, x2, y2, function(fixture, x, y, xn, yn, f)
  207. if lume.find({fixture:getCategory()}, ctx.categories.ground) then
  208. len = math.min(len, dis * f)
  209. return len
  210. end
  211. return 1
  212. end)
  213. g.setColor(255, 0, 0)
  214. g.setLineWidth(self.laser.active and 10 or 1)
  215. g.line(x1, y1, x1 + len * math.cos(dir), y1 + len * math.sin(dir))
  216. g.setLineWidth(1)
  217. end
  218. self.phlerp:delerp()
  219. end
  220. function Pigeon:keypressed(key)
  221. end
  222. function Pigeon:paused()
  223. self.phlerp:update()
  224. end
  225. function Pigeon:collideWith(other, myFixture)
  226. if isa(other, Person) and other.state ~= other.dead and other.invincible == 0 then
  227. if self.state == self.peck and (myFixture == self.beak.top.fixture or myFixture == self.beak.bottom.fixture) then
  228. other:changeState('dead', 'peck')
  229. elseif self.state == self.walk and self.drop and myFixture == self.feet[self.drop].fixture then
  230. other:changeState('dead', 'step')
  231. elseif self.crushGrace > 0 and (myFixture == self.feet.left.fixture or myFixture == self.feet.right.fixture) then
  232. other:changeState('dead', 'jump')
  233. end
  234. elseif isa(other, Building) and not other.destroyed and self.state == self.peck and (myFixture == self.beak.top.fixture or myFixture == self.beak.bottom.fixture) then
  235. other:destroy('peck')
  236. elseif isa(other, Building) and not other.destroyed and self.crushGrace > 0 and (myFixture == self.feet.left.fixture or myFixture == self.feet.right.fixture) then
  237. other:destroy('step')
  238. end
  239. if isa(other, Building) then return false end
  240. return true
  241. end
  242. ----------------
  243. -- Helpers
  244. ----------------
  245. function Pigeon:changeState(target)
  246. print('changing state to ' .. target)
  247. if self.state == target then return end
  248. f.exe(self.state.exit, self)
  249. self.state = self[target]
  250. f.exe(self.state.enter, self)
  251. return self.state
  252. end
  253. function Pigeon:getGroundRaycastPoints()
  254. local x, y = self.body:getPosition()
  255. local h = self.shapeSize / 2
  256. local x1, y1, x2, y2 = x, y + h, x, y + h + 1
  257. return x1, y1, x2, y2
  258. end
  259. function Pigeon:getLaserRaycastPoints()
  260. local x1, y1 = self.animation.spine.skeleton.x + self.animation.spine.skeleton:findBone('beakbottom').worldX, self.animation.spine.skeleton.y - self.animation.spine.skeleton:findBone('beakbottom').worldY
  261. local dir = self.laser.direction
  262. local x2, y2 = x1 + math.cos(dir) * 2000, y1 + math.sin(dir) * 2000
  263. return x1, y1, x2, y2
  264. end
  265. function Pigeon:getLaserLength()
  266. local x1, y1, x2, y2 = self:getLaserRaycastPoints()
  267. local minLen = 1
  268. ctx.world:rayCast(x1, y1, x2, y2, function(fixture, len)
  269. local categories = {fixture:getCategory()}
  270. if lume.find(categories, ctx.categories.ground) or lume.find(categories, ctx.categories.building) then
  271. minLen = math.min(minLen, len)
  272. end
  273. return 1
  274. end)
  275. return minLen * math.distance(x1, y1, x2, y2)
  276. end
  277. function Pigeon:getGrounded()
  278. local grounded = false
  279. local x1, y1, x2, y2 = self:getGroundRaycastPoints()
  280. ctx.world:rayCast(x1, y1, x2, y2, function(fixture)
  281. local categories = {fixture:getCategory()}
  282. if lume.find(categories, ctx.categories.ground) or lume.find(categories, ctx.categories.building) then
  283. grounded = true
  284. end
  285. return 1
  286. end)
  287. return grounded
  288. end
  289. function Pigeon:initBeak()
  290. local spine = self.animation.spine
  291. self.beak = {}
  292. table.each({'top', 'bottom'}, function(name)
  293. local beak = {}
  294. local attachment = spine.skeleton:getAttachment('beak' .. name .. '_bb', 'beak' .. name .. '_bb')
  295. local polygon = table.copy(attachment.vertices)
  296. for i = 1, #polygon, 2 do
  297. polygon[i] = polygon[i] * self.animation.scale
  298. polygon[i + 1] = polygon[i + 1] * self.animation.scale * -1
  299. end
  300. beak.shape = love.physics.newPolygonShape(unpack(polygon))
  301. beak.body = love.physics.newBody(ctx.world, 0, 0, 'kinematic')
  302. beak.fixture = love.physics.newFixture(beak.body, beak.shape)
  303. beak.fixture:setCategory(ctx.categories.pigeon)
  304. beak.fixture:setSensor(true)
  305. beak.body:setUserData(self)
  306. self.beak[name] = beak
  307. end)
  308. end
  309. function Pigeon:updateBeak()
  310. local skeleton = self.animation.spine.skeleton
  311. skeleton.flipY = true
  312. skeleton:updateWorldTransform()
  313. table.each(self.beak, function(beak, name)
  314. local slot = skeleton:findSlot('beak' .. name)
  315. slot:setAttachment(skeleton:getAttachment(slot.data.name, slot.data.name .. '_bb'))
  316. local bone = skeleton:findBone('beak' .. name)
  317. beak.body:setX(skeleton.x + bone.worldX)
  318. beak.body:setY(skeleton.y + bone.worldY)
  319. beak.body:setAngle(math.rad(bone.worldRotation * (self.animation.flipped and 1 or -1) + (self.animation.flipped and 180 or 0)))
  320. slot:setAttachment(skeleton:getAttachment(slot.data.name, slot.data.name))
  321. end)
  322. skeleton.flipY = false
  323. end
  324. function Pigeon:initFeet()
  325. local spine = self.animation.spine
  326. self.feet = {}
  327. table.each({'left', 'right'}, function(name)
  328. local foot = {}
  329. local attachment = spine.skeleton:getAttachment(name .. 'foot_bb', name .. 'foot_bb')
  330. local polygon = table.copy(attachment.vertices)
  331. for i = 1, #polygon, 2 do
  332. polygon[i] = polygon[i] * self.animation.scale
  333. polygon[i + 1] = polygon[i + 1] * self.animation.scale * -1
  334. end
  335. foot.shape = love.physics.newPolygonShape(unpack(polygon))
  336. foot.body = love.physics.newBody(ctx.world, 0, 0, 'kinematic')
  337. foot.fixture = love.physics.newFixture(foot.body, foot.shape)
  338. foot.fixture:setCategory(ctx.categories.pigeon)
  339. foot.fixture:setSensor(true)
  340. foot.body:setUserData(self)
  341. self.feet[name] = foot
  342. end)
  343. end
  344. function Pigeon:updateFeet()
  345. local skeleton = self.animation.spine.skeleton
  346. skeleton.flipY = true
  347. skeleton:updateWorldTransform()
  348. table.each(self.feet, function(foot, name)
  349. local slot = skeleton:findSlot(name .. 'foot')
  350. slot:setAttachment(skeleton:getAttachment(slot.data.name, slot.data.name .. '_bb'))
  351. local bone = skeleton:findBone(name .. 'foot')
  352. foot.body:setX(skeleton.x + bone.worldX)
  353. foot.body:setY(skeleton.y + bone.worldY)
  354. foot.body:setAngle(math.rad(bone.worldRotation * (self.animation.flipped and 1 or -1) + (self.animation.flipped and 180 or 0)))
  355. slot:setAttachment(skeleton:getAttachment(slot.data.name, slot.data.name))
  356. end)
  357. skeleton.flipY = false
  358. end
  359. function Pigeon:activateRainbowShit()
  360. self.rainbowShitTimer = 5--self.rainbowShitTimer + 5
  361. self.rainbowShits = self.rainbowShits + 1
  362. ctx.backgroundSound:pause()
  363. if not self.rainbowShitSound or self.rainbowShitSound:isStopped() then
  364. self.rainbowShitSound = ctx.sound:loop('disco', function(sound)
  365. sound:setVolume(1)
  366. end)
  367. end
  368. flux.to(self.animation, .2, {scale = 1}, 'elasticout')
  369. end
  370. ----------------
  371. -- Actions
  372. ----------------
  373. function Pigeon:move()
  374. if ctx.debug then return end
  375. local left, right = false, true
  376. if left then
  377. self.animation.flipped = true
  378. if self.state == self.walk and self.slide then
  379. self.body:setX(self.body:getX() - self.slideSpeeds[self.slide] * ls.tickrate * (self.animation.state.speed or 1) * self.animation.scale * self.animation.speed)
  380. elseif not self.grounded then
  381. self.body:applyLinearImpulse(-self.flySpeed, 0)
  382. end
  383. elseif right then
  384. self.animation.flipped = false
  385. if self.state == self.walk and self.slide then
  386. self.body:setX(self.body:getX() + self.slideSpeeds[self.slide] * ls.tickrate * (self.animation.state.speed or 1) * self.animation.scale * self.animation.speed)
  387. elseif not self.grounded then
  388. self.body:applyLinearImpulse(self.flySpeed, 0)
  389. end
  390. end
  391. local vx, vy = self.body:getLinearVelocity()
  392. self.body:setLinearVelocity(math.min(math.abs(vx), self.maxSpeed) * lume.sign(vx), vy)
  393. end
  394. function Pigeon:jump()
  395. if not self.jumped then
  396. self.body:applyLinearImpulse(0, -self.jumpForce)
  397. self.jumped = true
  398. end
  399. end
  400. function Pigeon:recoverFuel()
  401. self.fuel = math.min(self.fuel + 20 * ls.tickrate, self.maxFuel)
  402. end
  403. function Pigeon:contain()
  404. if self.body:getX() < 0 then
  405. self.body:setPosition(0, self.body:getY())
  406. end
  407. end
  408. ----------------
  409. -- States
  410. ----------------
  411. Pigeon.idle = {}
  412. function Pigeon.idle:update()
  413. self:recoverFuel()
  414. self.animation:set('idle')
  415. end
  416. Pigeon.walk = {}
  417. function Pigeon.walk:enter()
  418. self.walk.firstShake = true
  419. end
  420. function Pigeon.walk:update()
  421. local left, right = false, true
  422. self.animation:set('walk')
  423. self:recoverFuel()
  424. if love.keyboard.isDown('up', 'w', 'z') or (joystick and (joystick:getGamepadAxis('lefty') < -.5 or joystick:isGamepadDown('a', 'dpup'))) then
  425. return self:changeState('air')
  426. elseif (love.keyboard.isDown(' ', 's', 'x') or (joystick and (joystick:isGamepadDown('b', 'x', 'y')))) and not self.peckDirty then
  427. self:changeState('peck')
  428. end
  429. if not self.grounded then
  430. self.crushGrace = .1
  431. end
  432. if left or right then
  433. self:move()
  434. end
  435. end
  436. Pigeon.air = {}
  437. function Pigeon.air:enter()
  438. if self.leftWhirr then
  439. self.leftWhirr:stop()
  440. self.rightWhirr:stop()
  441. end
  442. self.jumped = false
  443. if love.keyboard.isDown('up', 'w', 'z') or (joystick and (joystick:getGamepadAxis('lefty') < -.5 or joystick:isGamepadDown('a', 'dpup'))) then
  444. self.animation:set('jump')
  445. end
  446. self.jumps = self.jumps + 1
  447. self.air.lastVelocity = 0
  448. end
  449. function Pigeon.air:exit()
  450. if self.air.lastVelocity > 800 then
  451. ctx.view:screenshake(15 + (self.air.lastVelocity / 50))
  452. local skeleton = self.animation.spine.skeleton
  453. local x = skeleton.x
  454. local y = skeleton.y
  455. ctx.particles:emit('dust', x, y, 15 + (self.air.lastVelocity / 100))
  456. end
  457. ctx.sound:play('impact', function(sound)
  458. sound:setVolume(1)
  459. end)
  460. end
  461. function Pigeon.air:update()
  462. local left, right = false, true
  463. local vx, vy = self.body:getLinearVelocity()
  464. if vy > 0 then
  465. self.crushGrace = .1
  466. end
  467. if self.jumped and self.grounded and vy >= 0 then
  468. return self:changeState('walk').update(self)
  469. end
  470. if left or right then
  471. self:move()
  472. else
  473. self.body:setLinearVelocity(0, vy)
  474. end
  475. --[[if love.keyboard.isDown(' ') then
  476. if self.fuel > 0 and not self.grounded then
  477. if (vy > 0 or math.abs(vy) < self.maxFlySpeed) then
  478. self.fuel = math.max(self.fuel - 33 * ls.tickrate, 0)
  479. local bonusBoost = vy > 0 and vy / 2 or 0
  480. self.body:applyLinearImpulse(0, -(self.rocketForce + bonusBoost))
  481. end
  482. if self.animation.state.name ~= 'fly' and self.animation.state.name ~= 'flyStart' then
  483. self.animation:set('flyStart')
  484. end
  485. self.jumped = true
  486. end
  487. end]]
  488. if (self.animation.state.name == 'fly' or self.animation.state.name == 'flyStart') and (not love.keyboard.isDown(' ') or self.fuel < 1) then
  489. self.animation:set('flyEnd')
  490. end
  491. self.air.lastVelocity = vy
  492. end
  493. Pigeon.peck = {}
  494. function Pigeon.peck:enter()
  495. if self.leftWhirr then
  496. self.leftWhirr:stop()
  497. self.rightWhirr:stop()
  498. end
  499. self.pecks = self.pecks + 1
  500. ctx.sound:play('charge')
  501. self.animation:set('peck')
  502. table.each(self.beak, function(beak)
  503. beak.fixture:setSensor(false)
  504. end)
  505. end
  506. function Pigeon.peck:exit()
  507. table.each(self.beak, function(beak)
  508. beak.fixture:setSensor(true)
  509. end)
  510. end
  511. function Pigeon.peck:impact()
  512. ctx.view:screenshake(20)
  513. ctx.sound:play('impact')
  514. ctx.sound:play('crash', function(sound)
  515. sound:setVolume(.1)
  516. end)
  517. end
  518. Pigeon.laser = {}
  519. function Pigeon.laser:enter()
  520. self.laser.active = false
  521. self.laser.direction = self.animation.flipped and 3 * math.pi / 4 or math.pi / 4
  522. self.animation:set('laserStart')
  523. end
  524. function Pigeon.laser:update()
  525. if not self.laser.active then
  526. if love.keyboard.isDown('up', 'right') then
  527. self.laser.direction = self.laser.direction - self.laserTurnSpeed * ls.tickrate * math.sign(math.pi / 2 - self.laser.direction)
  528. elseif love.keyboard.isDown('down', 'left') then
  529. self.laser.direction = self.laser.direction + self.laserTurnSpeed * ls.tickrate * math.sign(math.pi / 2 - self.laser.direction)
  530. end
  531. if not love.keyboard.isDown(' ') or self.animation.state.name == 'laserCharge' then
  532. if self.animation.state.name == 'laserCharge' or self.animation.state.name == 'laserEnd' then
  533. self.animation:set('laserEnd')
  534. else
  535. self:changeState('idle')
  536. end
  537. end
  538. else
  539. local x1, y1, x2, y2 = self:getLaserRaycastPoints()
  540. local dis, dir = math.vector(x1, y1, x2, y2)
  541. local len = dis
  542. ctx.world:rayCast(x1, y1, x2, y2, function(fixture, x, y, xn, yn, f)
  543. if lume.find({fixture:getCategory()}, ctx.categories.ground) then
  544. len = math.min(len, dis * f)
  545. return len
  546. end
  547. return 1
  548. end)
  549. ctx.world:rayCast(x1, y1, x1 + math.cos(dir) * len, y1 + math.sin(dir) * len, function(fixture)
  550. local object = fixture:getBody():getUserData()
  551. if object and isa(object, Person) and object.state ~= object.dead then
  552. object:changeState('dead')
  553. end
  554. return 1
  555. end)
  556. if love.keyboard.isDown('up', 'right') then
  557. self.laser.direction = self.laser.direction - self.laserTurnSpeed * ls.tickrate * math.sign(math.pi / 2 - self.laser.direction) * .2
  558. elseif love.keyboard.isDown('down', 'left') then
  559. self.laser.direction = self.laser.direction + self.laserTurnSpeed * ls.tickrate * math.sign(math.pi / 2 - self.laser.direction) * .2
  560. end
  561. end
  562. end