module Game.Entities.Common ( toSpriteSet, frameLimit, collision, hitPlayer, collectedBattery, updateFrame, updateGravity, ) where import Data.IORef import Game.Entities.Const import Game.Entities.Types import qualified Game.Sprites as S import qualified Game.State as GS import SDL (($~)) -- | Convert direction into a sprite set. toSpriteSet :: Dir -> Int toSpriteSet DirRight = 0 toSpriteSet DirLeft = 1 -- | Return the number of frames available on the entity's sprite for current direction. frameLimit :: Entity -> Int frameLimit e = S.frameCount e.sprite (toSpriteSet e.dir) -- | Collision detection of player vs entity. -- -- The player's head won't register, this is necessary to avoid hitting things on a platform above when jumping. collision :: IORef Entity -> Int -> Collision collision playerRef otherHeight other = do player <- readIORef playerRef pure $ player.x + 4 < other.x + 12 && other.x + 4 < player.x + 12 && player.y + otherHeight - 4 < other.y + otherHeight && other.y + 4 < player.y + 24 -- | Update game state to reflect that the player was hit by an enemy. hitPlayer :: IORef GS.State -> IO () hitPlayer stateRef = stateRef $~ updatePlayerHit where updatePlayerHit :: GS.State -> GS.State updatePlayerHit s -- if we run out of lives, trigger the game over | s.lives == 1 = s {GS.lives = 0, GS.gameOverDelay = gameOverDelay} | otherwise = s {GS.lives = s.lives - 1, GS.hitDelay = hitDelay} -- | Update game state to reflect that the player picked up a battery. collectedBattery :: IORef GS.State -> IO () collectedBattery stateRef = stateRef $~ (\s -> s {GS.batteries = s.batteries + 1}) -- | Update frame animation for entities that have direction. updateFrame :: Bool -> Entity -> Entity updateFrame updated e | isGravityOn = e | e.delay > 0 = e {delay = e.delay - 1} | e.frame + 1 < frameLimit e = e {delay = frameDelay, frame = if updated then e.frame + 1 else 0} | otherwise = e {delay = frameDelay, frame = 0} where isGravityOn = e.gravity > gravityOff applyGravity :: IsBlocked -> Int -> Entity -> Entity applyGravity isBlocked v e | v == 0 = e -- hit the floor | isGoingDown && (isBlocked (e.x + 4) (e.y + 24) || isBlocked (e.x + 10) (e.y + 24)) && not (isBlocked (e.x + 4) (e.y + 23)) && not (isBlocked (e.x + 10) (e.y + 23)) = e {jumping = False, gravity = gravityOff, delay = 0} | otherwise = applyGravity isBlocked (v - 1) e {y = e.y + change} where isGoingDown = e.gravity >= gravityDown change = if isGoingDown then 1 else -1 -- XXX: hardcoded to 16x24 pixels sprite. updateGravity :: IsBlocked -> Entity -> Entity updateGravity isBlocked e | current > gravityOff = applyGravity isBlocked (gravityTable !! current) e {gravity = new} | not (isBlocked (e.x + 4) (e.y + 24) || isBlocked (e.x + 10) (e.y + 24)) = e {gravity = gravityDown, frame = jumpFrame} | otherwise = e where current = e.gravity new = if current > gravityOff && current < length gravityTable - 1 then current + 1 else current