packages feed

edge-0.8.18: Unit/Smart/Death.hs

module Unit.Smart.Death ( Death(..)
                       , new
                       ) where

import Data.WrapAround ( WP, WM, vectorRelation )
import Animation
    ( Audible(..), Animation(..), termSndSrc, handSndSrc )
import Graphics.Gloss.Data.Picture ()
import Graphics.Gloss.Data.Color ()
import GHC.Float ()
import Math
    ( vectorDirection, targetingA, subV, moreThanZero, isNan )
import ResourceTracker ( RT, protectedGetImage )
import Updating
    ( Transient(..), Observant(..), InternallyUpdating(..) )
import qualified Moving as M
    ( Moving(..),
      Locatable(..),
      Colliding(..),
      newVelocity,
      newLocation )
import Combat
    ( Projectile(..), Launcher(..), Damaging(..), Damageable(..) )
import qualified Projectile.BulletSII as P.BulletSII ( speed, new )
import qualified Projectile.BulletMII as P.BulletMII ( new )
import AfterEffect ( AfterEffect(AfterEffect) )
import qualified AfterEffect.SimpleExplosion as SimpleExplosion
    ( new )
import Data.Maybe ()
import Universe ( Arena(lance) )
import qualified Universe as U ( Arena(wrapMap) )
import Sound.ALUT ( Source )
import Common ( Velocity, Time, Angle )


radialVelocity = pi
maxVelocityMag = 200
kamikazeDamage = 100
maxIntegrity = 12
accelerationRate = 200
adjAngleC = pi / 8
shotDelay_sniper = 3
shotDelay_spread = 2
collisionR = 60

data Death = Death { angle :: Angle -- radians
                   , velocity :: Velocity
                   , center :: WP
                   , idealTargetLocation :: Maybe WP
                   , wmap :: WM
                   , launchTube :: [Projectile]
                   , sinceLastShot_sniper :: Time
                   , sinceLastShot_spread :: Time
                   , integrity :: Double
                   , vision :: Maybe Arena
                   , rt :: RT
                   , queueShotSnd :: Bool
                   , shotSndSrc :: Maybe Source
                   }

instance Audible Death where

  processAudio s a = handSndSrc s
                       queueShotSnd
                       shotSndSrc
                       (\a -> a { queueShotSnd = False })
                       rt
                       "energy-shot-02.wav"
                       (\a b -> a { shotSndSrc = Just b })
                       a (wmap s) (M.center)

  terminateAudio s = termSndSrc s shotSndSrc

new a b c d = Death { center = c
                    , angle = d
                    , idealTargetLocation = Nothing
                    , velocity = (0, 0)
                    , wmap = b
                    , launchTube = []
                    , sinceLastShot_sniper = 0
                    , sinceLastShot_spread = 0
                    , integrity = maxIntegrity
                    , vision = Nothing
                    , rt = a
                    , queueShotSnd = False
                    , shotSndSrc = Nothing
                    }

instance Observant Death where

  updateVision s a = s { vision = Just a }

updateAngle t s
 = case vision s of
     Nothing -> s
     Just a -> case lance a of
                Nothing -> s
                Just l -> let b = angle s in
                          let e = if isNan b then 0.1 else b in
                          let m = vectorDirection
                                       (vectorRelation
                                         (wmap s)
                                         (center s)
                                         (M.center l)) in
                          let adj
                               | m - e > adjAngleC = radialVelocity * t
                               | m - e < (-1) * adjAngleC = (-radialVelocity) * t
                               | otherwise = 0 in
                          s { angle = angle s + adj }

updateVelocity t s =
  s { velocity = b }
  where c = M.newVelocity
              (velocity s) accelerationRate (angle s) maxVelocityMag t
        b = case vision s of
              Nothing -> velocity s
              Just a ->
                case lance a of
                  Nothing -> velocity s
                  Just l -> let d = angle s in
                            let e = if isNan d then 0.1 else d in
                            let m = vectorDirection
                                         (vectorRelation
                                           (wmap s)
                                           (center s)
                                           (M.center l)) in
                            if abs (m - e) <= adjAngleC then c else velocity s

instance Animation Death where
  image s _ = protectedGetImage (rt s) "death.bmp"

instance M.Locatable Death where
  center = center

instance M.Moving Death where
  velocity = velocity

instance M.Colliding Death where
  collisionRadius _ = collisionR

instance InternallyUpdating Death where

  preUpdate s t = (updateFiringInformation t
                   . updateVelocity t
                   . updateAngle t) s

  postUpdate s t =
    s { center = M.newLocation (wmap s) (center s) (velocity s) t }

updateFiringInformation t s =
  fst ((handleSpreadFiring . handleSniperFiring) (s, t))

handleSniperFiring (self, t) =
  let sinceLastShot_sniper' = sinceLastShot_sniper self + t in
    if sinceLastShot_sniper' >= shotDelay_sniper
      then (self { sinceLastShot_sniper = 0.0
                , launchTube = projectile : launchTube self
                , queueShotSnd = True
                }, t)
      else (self { sinceLastShot_sniper = sinceLastShot_sniper' }, t)
  where projectile = Projectile
                       (P.BulletSII.new
                         (wmap self)
                         pAngle
                         (center self)
                         (velocity self))
        pSpeed = P.BulletSII.speed
        pAngle = case vision self of
                   Nothing -> angle self
                   Just arena ->
                     case lance arena of
                       Nothing -> angle self
                       Just l -> targetingA
                                   pSpeed
                                   (vectorRelation
                                     (U.wrapMap arena)
                                     (center self)
                                     (M.center l))
                                   (subV
                                     (M.velocity l)
                                     (M.velocity self))

handleSpreadFiring (self, t) =
  let sinceLastShot_spread' = sinceLastShot_spread self + t in
    if sinceLastShot_spread' >= shotDelay_spread
      then (self { sinceLastShot_spread = 0.0
                , launchTube = projectiles ++ launchTube self
                , queueShotSnd = True
                }, t)
      else (self { sinceLastShot_spread = sinceLastShot_spread' }, t)
  where projectiles = map projectile [ x * pi / 8.0 - pi / 4.0 | x <- [0..7] ]
        projectile x = Projectile
                        (P.BulletMII.new
                          (wmap self)
                          (pAngle + x)
                          (center self)
                          (velocity self))
        pAngle = case vision self of
                   Nothing -> angle self
                   Just arena ->
                     case lance arena of
                       Nothing -> angle self
                       Just l -> vectorDirection
                                  (vectorRelation
                                    (U.wrapMap arena)
                                    (center self)
                                    (M.center l))


instance Launcher Death where

  deployProjectiles s = (launchTube s, s { launchTube = [] })

instance Transient Death where

  expired' s = if moreThanZero (integrity s) then Nothing else Just [a]

    where a = AfterEffect
                (SimpleExplosion.new (rt s) (wmap s) (center s) (velocity s))

instance Damageable Death where

  inflictDamage s d = s { integrity = max 0 (integrity s - d) }

instance Damaging Death where

  damageEnergy _ = kamikazeDamage