module Game.Entities.Common ( toSpriteSet, frameLimit, collision, inLine, facingLower, updateFrame, updateGravity, turn, ) where import Data.IORef import Game.Entities.Const import Game.Entities.Types import Game.Sprites qualified as S -- | Return the opposite direction. turn :: Dir -> Dir turn DirRight = DirLeft turn DirLeft = DirRight -- | 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 e.set -- | 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.typ == TypePlayer && 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 -- | Check if the player is in line with the entity and the entity is facing the player. inLine :: IORef Entity -> Int -> Collision inLine playerRef otherHeight other = do player <- readIORef playerRef pure $ player.typ == TypePlayer && player.y + otherHeight - 4 < other.y + otherHeight && other.y + 4 < player.y + 24 -- XXX: adjust perhaps? so the enemies don't shoot too close to the player && ( (other.dir == DirLeft && player.x < other.x) || (other.dir == DirRight && player.x > other.x) ) -- | Check if the entity is facing the player and it is in a lower plarform. facingLower :: IORef Entity -> Collision facingLower playerRef other = do player <- readIORef playerRef pure $ player.typ == TypePlayer && player.gravity == gravityOff && player.y < other.y && ( (other.dir == DirLeft && player.x < other.x) || (other.dir == DirRight && player.x > other.x) ) -- | 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