nyx-game-0.1.0.0: app/Enemy.hs
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module Enemy where
import qualified SDL
import Data.Maybe
import Play.Engine
import Control.Lens hiding (parts)
import Control.DeepSeq
import qualified Control.Monad.State as SM
import qualified Data.DList as DL
import Bullet
import qualified Attack as A
import qualified DecorationObject as DO
import qualified DecObj.SplitTexture as ST
import qualified Play.Engine.Movement as MV
import qualified Play.Engine.Sprite as Spr
data Enemy
= Enemy
{ _pos :: {-# UNPACK #-} !IPoint
, _size :: {-# UNPACK #-} !Size
, _sprite :: {-# UNPACK #-} !Spr.Sprite
, _hitbox :: {-# UNPACK #-} !Hitbox
, _movement :: {-# UNPACK #-} !MV.Movement
, _direction :: {-# UNPACK #-} !FPoint
, _directionChanger :: Size -> Enemy -> FPoint
, _attack :: {-# UNPACK #-} !A.Attack
, _attackId :: {-# UNPACK #-} !Int
, _attackChanger :: Enemy -> Int -> Maybe A.Attack
, _health :: {-# UNPACK #-} !Int
, _deathTime :: {-# UNPACK #-} !Int
, _deathParts :: {-# UNPACK #-} !IPoint
, _deathTexture :: {-# UNPACK #-} !SDL.Texture
, _timers :: {-# UNPACK #-} !EnemyTimers
}
data EnemyTimers
= EnemyTimers
{ _hitTimer :: {-# UNPACK #-} !Int
, _gracePeriodTimer :: {-# UNPACK #-} !Int
}
instance NFData Enemy where
rnf (Enemy {_pos, _size, _movement, _health, _timers}) =
rnf _pos
`seq` rnf _size
`seq` rnf _movement
`seq` rnf _timers
`seq` rnf _health
`seq` rnf _attack
`seq` rnf _deathTime
`seq` rnf _deathParts
instance NFData EnemyTimers where
rnf (EnemyTimers {_hitTimer}) =
rnf _hitTimer
`seq` rnf _gracePeriodTimer
makeFieldsNoPrefix ''Enemy
makeFieldsNoPrefix ''EnemyTimers
instance Eq Enemy where
e1 == e2 =
e1 ^. pos == e2 ^. pos
&& e1 ^. size == e2 ^. size
instance Ord Enemy where
e1 <= e2 =
e1 ^. pos <= e2 ^. pos
&& e1 ^. size <= e2 ^. size
data MakeEnemy
= MakeEnemy
{ mkePos :: {-# UNPACK #-} !IPoint
, mkeSize :: {-# UNPACK #-} !Size
, mkeSprite :: {-# UNPACK #-} !Spr.Sprite
, mkeHitbox :: {-# UNPACK #-} !Hitbox
, mkeMov :: {-# UNPACK #-} !MV.Movement
, mkeHealth :: {-# UNPACK #-} !Int
, mkeDirChanger :: Size -> Enemy -> FPoint
, mkeAtk :: {-# UNPACK #-} !A.Attack
, mkeAtkChanger :: Enemy -> Int -> Maybe A.Attack
, mkeDeathTime :: {-# UNPACK #-} !Int
, mkeDeathParts :: {-# UNPACK #-} !IPoint
, mkeDeathTexture :: {-# UNPACK #-} !SDL.Texture
}
mkEnemy :: MakeEnemy -> Enemy
mkEnemy MakeEnemy{..} =
Enemy
{ _pos = mkePos
, _size = mkeSize
, _sprite = mkeSprite
, _hitbox = mkeHitbox
, _direction = Point 0 0
, _movement = mkeMov
, _directionChanger = mkeDirChanger
, _attack = mkeAtk
, _attackId = 0
, _attackChanger = mkeAtkChanger
, _health = mkeHealth
, _deathTime = mkeDeathTime
, _deathParts = mkeDeathParts
, _deathTexture = mkeDeathTexture
, _timers = initEnemyTimers
}
initEnemyTimers :: EnemyTimers
initEnemyTimers = EnemyTimers
{ _hitTimer = -1
, _gracePeriodTimer = 60 * 5
}
update :: Input -> Enemy -> Result ([Enemy], DL.DList Bullet -> DL.DList Bullet, [DO.DecorationObject])
update _ enemy = do
wsize <- _windowSize <$> SM.get
let
dir = (enemy ^. directionChanger) wsize enemy
(mv, move) =
MV.update dir
$ (enemy ^. movement)
(newBullets, attack') =
(enemy ^. attack . A.attackUpdate) (enemy ^. pos) (enemy ^. size) (enemy ^. attack)
changedAttack =
(enemy ^. attackChanger) enemy (enemy ^. attackId)
enemy' =
enemy
& over pos (if isAlive enemy then (`addPoint` move) else id)
& over sprite (Spr.update Nothing False)
& set movement mv
& over timers updateTimers
& set direction dir
& set attack (fromMaybe attack' changedAttack)
& over attackId (maybe id (const (+1)) changedAttack)
parts <-
if isDead enemy'
then
ST.make $ ST.MakeSplitTexture
{ mkPos = enemy' ^. pos
, mkSize = enemy' ^. sprite . size
, mkSplit = enemy' ^. deathParts
, mkTexture = enemy' ^. deathTexture
, mkTexturePos = Point 0 0
, mkDeathTime = enemy' ^. deathTime
}
else pure []
pure
( if isDead enemy'
|| not (isInWindow wsize (enemy ^. pos) (enemy ^. size))
&& (enemy' ^. timers . gracePeriodTimer) < 0
then []
else pure enemy'
, if isDead enemy'
|| not (isInWindow wsize (enemy ^. pos) (Point 0 0))
then id
else DL.append newBullets
, parts
)
updateTimers :: EnemyTimers -> EnemyTimers
updateTimers et =
et
& over hitTimer (\t -> if t <= 0 then -1 else t - 1)
& over gracePeriodTimer (\t -> if t <= 0 then -1 else t - 1)
checkHit :: DL.DList Bullet -> Enemy -> Enemy
checkHit bullets enemy
| any (isJust . flip isTouchingCircleRect enemy) bullets && enemy ^. health > 0
= enemy
& over health (flip (-) (DL.head bullets ^. damage))
& \enemy' ->
enemy'
& set
(timers . hitTimer)
( if
| enemy' ^. health <= 0 ->
hitTimeout * 4
| enemy' ^. timers . hitTimer < 0 ->
hitTimeout
| otherwise ->
enemy' ^. timers . hitTimer
)
| otherwise
= enemy
hitTimeout = 20
isDead :: Enemy -> Bool
isDead enemy =
enemy ^. health <= 0
&& enemy ^. timers . hitTimer < 0
isAlive :: Enemy -> Bool
isAlive enemy =
enemy ^. health > 0
render :: SDL.Renderer -> Camera -> Enemy -> IO ()
render renderer cam enemy = do
let
-- h = fromIntegral $ 255 - max 0 (enemy ^. health * 2)
isHit = enemy ^. timers . hitTimer > 0 && enemy ^. timers . hitTimer `mod` 8 < 4
Spr.render renderer cam (enemy ^. pos) (enemy ^. size) (if isHit then 100 else 255) (enemy ^. sprite)
-- when isHit $ do
-- let
-- colour = Vect.V4 255 (255 - h) (255 - h) 150
-- radius = fromIntegral $ enemy ^. size . x `div` 2
-- center =
-- Vect.V2
-- (fromIntegral (cam (enemy ^. pos) ^. x) + radius)
-- (fromIntegral (cam (enemy ^. pos) ^. y) + radius)
-- SDL.circle renderer center radius colour
-- SDL.fillCircle renderer center radius colour