packages feed

nyx-game-0.1.0.0: app/ShootingBox.hs

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE DuplicateRecordFields  #-}
{-# LANGUAGE FlexibleInstances  #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}

module ShootingBox where

import qualified SDL
import qualified Play.Engine.MySDL.MySDL as MySDL

import Play.Engine

import Data.Maybe
import Control.Monad.Except
import Control.Lens
import Control.DeepSeq
import qualified Control.Monad.State as SM
import qualified Data.DList as DL
import qualified Data.Map as M

import qualified Play.Engine.Movement as MV
import qualified Play.Engine.Sprite as Spr
import Bullet

-- import Debug.Trace



data Dir
  = DirRight
  | DirLeft
  deriving (Eq, Show)

data MainChar
  = MainChar
  { _pos :: {-# UNPACK #-} !IPoint
  , _size :: {-# UNPACK #-} !Size
  , _movement :: {-# UNPACK #-} !MV.Movement
  , _sprite :: !Spr.Sprite
  , _bullet :: !SDL.Texture
  , _hitTimer :: {-# UNPACK #-} !Int
  , _bulletsTimer :: {-# UNPACK #-} !Int
  , _health :: {-# UNPACK #-} !Int
  , _lastDir :: !Dir
  , _hitbox :: !Hitbox
  }

makeFieldsNoPrefix ''MainChar

instance NFData MainChar where
  rnf (MainChar {_pos, _size, _hitTimer, _movement, _health, _bulletsTimer}) =
    rnf _pos
    `seq` rnf _size
    `seq` rnf _direction
    `seq` rnf _movement
    `seq` rnf _hitTimer
    `seq` rnf _bulletsTimer
    `seq` rnf _health
    `seq` rnf _transparency

instance Eq MainChar where
  mc1 == mc2 =
    mc1 ^. pos == mc2 ^. pos
    && mc1 ^. size == mc2 ^. size

instance Ord MainChar where
  mc1 <= mc2 =
    mc1 ^. pos <= mc2 ^. pos
    && mc1 ^. size <= mc2 ^. size

wantedAssets :: [(String, MySDL.ResourceType FilePath)]
wantedAssets =
  [ ("nyx-sprites", MySDL.Texture "nyx-sprites-animated.png")
  , ("nyx-bullet", MySDL.Texture "nyx-bullet.png")
  ]


mkMainChar :: M.Map String SDL.Texture -> Result MainChar
mkMainChar ts = do
  case mapM ((`M.lookup` ts) . fst) wantedAssets of
    Just [nyxSprites, nyxBullet] ->
      pure $
        MainChar
          { _pos = Point 380 800
          , _size = charSize
          , _sprite =
            fromJust
              $ Spr.make
              $ Spr.MakeArgs
              { mkActionmap = ["normal", "side-right", "side-left"]
              , mkAction = "normal"
              , mkTexture = nyxSprites
              , mkSize = Point 180 380
              , mkMaxPos = 4
              , mkSpeed = 5
              }
          , _bullet = nyxBullet
          , _hitTimer = -1
          , _bulletsTimer = 5
          , _health = 1
          , _movement = MV.make $ MV.defArgs
            { MV.maxspeed = Point 5 5
            , MV.accel = Point 3.5 3.5
            }
          , _lastDir = DirRight
          , _hitbox = Hitbox
            { _alignment = Point (charSize ^. x `div` 4) (charSize ^. y `div` 4)
            , _size = charSize
              & over x (`div` 2)
              & over y (`div` 2)
            }
          }
    _ ->
      throwError ["Texture not found: nyx-sprites"]

charSize :: Size
charSize = Point 60 108

fixHitpos :: MainChar -> MainChar
fixHitpos mc = mc
  & set (hitbox . alignment . x) (charSize ^. x `div` 4)
  & set (hitbox . alignment . y) (charSize ^. y `div` 4)
  & set (hitbox . size . x) (charSize ^. x `div` 2)
  & set (hitbox . size . y) (charSize ^. y `div` 3)

halfHitbox :: MainChar -> MainChar
halfHitbox mc = mc
  & set (hitbox . alignment . x) (charSize ^. x `div` 3)
  & set (hitbox . alignment . y) (charSize ^. y `div` 3)
  & set (hitbox . size . x) (charSize ^. x `div` 3)
  & set (hitbox . size . y) (charSize ^. y `div` 3)


update :: Input -> MainChar -> Result (MainChar, DL.DList Bullet -> DL.DList Bullet)
update input mc = do
  wsize <- _windowSize <$> SM.get
  let
    dir = keysToMovement 1 input
    (mv, move) =
      MV.update dir
        . set MV.maxSpeed (if keyPressed KeyB input then Point 1.2 1.2 else Point 4 4)
        $ (mc ^. movement)

    addBullets
      | keyPressed KeyA input
      , mc ^. bulletsTimer == 0 =
        DL.append $ DL.fromList (newBullet mc)
      | otherwise = id

    newDir
      | keyPressed KeyLeft  input = DirLeft
      | keyPressed KeyRight input = DirRight
      | otherwise = mc ^. lastDir

    newMC =
      mc
      & over pos (`addPoint` move)
      & fixPos wsize
      & fixHitpos
      & set (size . x) (if keyPressed KeyB input then charSize ^. x `div` 2 else charSize ^. x)
      & (if keyPressed KeyB input then halfHitbox else id)
      & set movement mv
      & set lastDir newDir
      & over sprite
        (flip Spr.update False $
           if
             | keyPressed KeyB input && newDir == DirRight -> Just "side-left"
             | keyPressed KeyB input -> Just "side-right"
             | otherwise -> Just "normal"
        )
      & over hitTimer (\t -> if t <= 0 then -1 else t - 1)
      & over bulletsTimer (\t -> if t > 0 then t - 1 else if keyPressed KeyA input then 5 else 0)

    result =
      if mc ^. health <= 0 && mc ^. hitTimer < 0
        then (set size (Point 0 0) mc, id)
        else (newMC, addBullets)

  pure result

newBullet :: MainChar -> [Bullet]
newBullet mc
  | mc ^. size . x == charSize ^. x =
    [ mkBullet (mc ^. bullet) (Point 0 (-1)) mv 2 70 ((mc ^. pos) `addPoint` Point (mc ^. size . x `div` 4) 0)
    , mkBullet (mc ^. bullet) (Point 0 (-1)) mv 2 70 ((mc ^. pos) `addPoint` Point ((mc ^. size . x `div` 4) * 3) 0)
    ]
  | otherwise =
    [ mkBullet (mc ^. bullet) (Point 0 (-1)) mv 5 140 ((mc ^. pos) `addPoint` Point (charSize ^. x `div` 2) 0)
    ]

  where
    mv = MV.make $ MV.defArgs
      { MV.maxspeed = Point 0 10
      , MV.accel = Point 0 10
      }

checkHit :: [Bullet] -> MainChar -> MainChar
checkHit bullets mc
  | not (null bullets) && mc ^. health > 0
  = mc
    & over health (flip (-) (maximum $ (0:) $ map (^. damage) bullets))
    & \mc' -> set hitTimer (if mc' ^. health <= 0 then hitTimeout * 4 else hitTimeout) mc'
  | otherwise
  = mc

hitTimeout = 20

render :: SDL.Renderer -> Camera -> MainChar -> IO ()
render renderer cam mc =
  unless (mc ^. health < 0 && mc ^. hitTimer < 0) $ do
    let
      isHit = mc ^. hitTimer > 0 && mc ^. hitTimer `mod` 8 < 4
    Spr.render renderer cam (mc ^. pos) charSize (if isHit then 100 else 255) (mc ^. sprite)

get mc l
  | mc ^. health <= 0 && mc ^. hitTimer < 0 = Nothing
  | otherwise = pure $ mc ^. l