packages feed

edge-0.8.16: Lance.hs

module Lance ( Lance ( rotationalThrusters
                     , linearThrusters
                     , deflector
                     , fireTrigger
                     , deflectorCharge
                     , center
                     , angle
                     , velocity
                     , godMode
                     , integrity
                     , inventory
                     , currentWeapon
                     , swClock
                     )
             , new
             , RotationDirection (..)
             , shielded
             , processItem
             , changeCurrentWeapon
             , swTimeLimit
             ) where

import Data.WrapAround
import Animation
import Graphics.Gloss.Data.Picture
import Graphics.Gloss.Data.Color
import GHC.Float
import Math
import ResourceTracker
import Data.Maybe
import Updating
import qualified Moving as M
import Combat
import qualified Projectile.BulletMkI as P.BulletMkI
import qualified Projectile.Cannon as P.Cannon
import qualified Projectile.Nuke as P.Nuke
import qualified Projectile.SWSide as P.SWSide
import qualified Projectile.SWForward as P.SWForward
import AfterEffect
import qualified AfterEffect.SimpleExplosion as SimpleExplosion
import Sound.ALUT
import Item
import Common

radialVelocity = pi -- radians per second

accelerationRate = 200 -- points per second

maxVelocity = 500 -- points per second

kamikazeDamage = 8.0

deflectorChargeLossFactor = 0.8

swTimeLimit = 10.0

data RotationDirection = Stable | CW | CCW
  deriving (Eq)

type LanceInventory = [Bool]

data Lance = Lance { angle :: Angle
                   , center :: WP
                   , wmap :: WM
                   , rotationalThrusters :: RotationDirection
                   , velocity :: Velocity
                   , linearThrusters :: Bool
                   , queueShotSound :: Bool
                   , godMode :: Bool
                   , deflector :: Bool
                   , fireTrigger :: Bool
                   , rt :: RT
                   , launchTube :: [Projectile]
                   , currentWeapon :: Int
                   , inventory :: LanceInventory
                   , swClock :: Time
                   , sinceLastShot :: Time
                   , integrity :: Double
                   , deflectorCharge :: Double
                   , shotSoundSource :: Maybe Source
                   }

new r w c =
  Lance { center = c
        , angle = 0.0
        , rotationalThrusters = Stable
        , velocity = (0, 0)
        , linearThrusters = False
        , wmap = w
        , rt = r
        , deflector = False
        , deflectorCharge = 2.0
        , launchTube = []
        , sinceLastShot = 0.0
        , fireTrigger = False
        , currentWeapon = 0
        , inventory = [False, False, False, False, False]
        , queueShotSound = False
        , shotSoundSource = Nothing
        , godMode = False
        , integrity = 3.0
        , swClock = 0.0
        }

changeCurrentWeapon s =
  if neither (isZero c) (a !! dec c) then changeCurrentWeapon d else d
  where a = inventory s
        b = currentWeapon s
        c = if inc b > 5
               then if a !! 0 then 1 else 0
               else inc b
        d = s { currentWeapon = c }

processItem s (Item a _ _) =
  if a == Health then b else b { swClock = 0 }
  where b = case a of
             Health -> s { integrity = 3.0 }
             FourWay -> f 0
             Cannon -> f 1
             Spread -> f 2
             RapidFire -> f 3
             Nuke -> f 4
        f v = s { inventory = replaceAt v True (inventory s)
                , currentWeapon = inc v
                }

instance Audible Lance where

  processAudio s _ = handSndSrc s
                       queueShotSound
                       shotSoundSource
                       (\a -> a { queueShotSound = False })
                       rt
                       "simple-energy-shot.wav"
                       (\a b -> a { shotSoundSource = Just b })

  terminateAudio s = termSndSrc s shotSoundSource

shielded s = deflectorCharge s >= 1.0 && deflector s

updateAngle :: Time -> Lance -> Lance
updateAngle t s =
  case rotationalThrusters s of CW -> f (-); CCW -> f (+); Stable -> s
  where f a = s { angle = a (angle s) (radialVelocity * t) }

instance Animation Lance where
  image s _ = Pictures [ b, reorient (angle s) a ]
    where a = if linearThrusters s
                then protectedGetImage (rt s) "lance-thrusting.bmp"
                else protectedGetImage (rt s) "lance.bmp"
          b = if deflector s && deflectorCharge s >= 1.0
                then Color white (Circle 40.0)
                else Blank

instance M.Locatable Lance where
  center = Lance.center

instance M.Moving Lance where
  velocity = velocity

instance M.Colliding Lance where
  collisionRadius _ = 20.0

instance InternallyUpdating Lance where

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

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

updateFiringInformation t s =
  b { swClock = (swClock b) + t }
  where a = sinceLastShot s + t
        b = if allTrue (inventory s) && swClock s < swTimeLimit
                then handleSuperWeapon s a
                else case currentWeapon s of
                       1 -> handleFourWayWeapon s a
                       2 -> handleCannonWeapon s a
                       3 -> handleSpreadWeapon s a
                       4 -> handleRapidFireWeapon s a
                       5 -> handleNukeWeapon s a
                       otherwise -> handleDefaultWeapon s a

handleSuperWeapon s a = if a >= 0.2 && fireTrigger s
                          then s { sinceLastShot = 0
                                 , launchTube = b ++ c ++ [d] ++ launchTube s
                                 , queueShotSound = True
                                 }
                          else s { sinceLastShot = a }
  where f u v = Projectile ( u (wmap s) (angle s + v) (center s) (velocity s) )
        g = f P.BulletMkI.new
        h = f P.SWSide.new
        b = map g [ pi / 2, 3 * pi / 4, pi, 5 * pi / 4, 3 * pi / 2 ]
        c = map h [ pi / 10, pi / 5, (-pi) / 10,  (-pi) / 5 ]
        d = f P.SWForward.new 0
  
handleDefaultWeapon self ls =
 if ls >= 0.4 && fireTrigger self
   then self { sinceLastShot = 0.0
             , launchTube =
                 Projectile ( P.BulletMkI.new
                               (wmap self)
                               (angle self)
                               (center self)
                               (velocity self) ) : launchTube self
             , queueShotSound = True
             }
   else self { sinceLastShot = ls }

handleFourWayWeapon self ls =
 if ls >= 0.4 && fireTrigger self
   then self { sinceLastShot = 0.0
             , launchTube =
                 [ Projectile ( P.BulletMkI.new
                                 (wmap self)
                                 (angle self)
                                 (center self)
                                 (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wmap self)
                                   (angle self + pi / 2)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wmap self)
                                   (angle self + pi)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wmap self)
                                   (angle self + 3 * pi / 2)
                                   (center self)
                                   (velocity self) ) ]
                 ++ launchTube self
             , queueShotSound = True
             }
   else self { sinceLastShot = ls }

handleCannonWeapon self ls =
 if ls >= 0.7 && fireTrigger self
   then self { sinceLastShot = 0.0
             , launchTube =
                 Projectile ( P.Cannon.new
                               (wmap self)
                               (angle self)
                               (center self)
                               (velocity self) ) : launchTube self
             , queueShotSound = True
             }
   else self { sinceLastShot = ls }

handleSpreadWeapon self ls =
 let spreadAngle = pi / 10 in
 if ls >= 0.4 && fireTrigger self
   then self { sinceLastShot = 0.0
             , launchTube =
                 [ Projectile ( P.BulletMkI.new
                                 (wmap self)
                                 (angle self)
                                 (center self)
                                 (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wmap self)
                                   (angle self + spreadAngle)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wmap self)
                                   (angle self + spreadAngle * 2)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wmap self)
                                   (angle self - spreadAngle)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wmap self)
                                   (angle self - spreadAngle * 2)
                                   (center self)
                                   (velocity self) ) ]
                 ++ launchTube self
             , queueShotSound = True
             }
   else self { sinceLastShot = ls }

handleRapidFireWeapon self ls =
 if ls >= 0.2 && fireTrigger self
   then self { sinceLastShot = 0.0
             , launchTube =
                 Projectile ( P.BulletMkI.new
                               (wmap self)
                               (angle self)
                               (center self)
                               (velocity self) ) : launchTube self
             , queueShotSound = True
             }
   else self { sinceLastShot = ls }

handleNukeWeapon self ls =
 if ls >= 3.0 && fireTrigger self
   then self { sinceLastShot = 0.0
             , launchTube =
                 Projectile ( P.Nuke.new
                               (wmap self)
                               (rt self)
                               (angle self)
                               (center self)
                               (velocity self) ) : launchTube self
             , queueShotSound = True
             }
   else self { sinceLastShot = ls }

updateDeflectorCharge t lance =
  let charge = deflectorCharge lance in
  let charge' = if deflector lance then max 0.8 (charge - t
                                                  * deflectorChargeLossFactor)
                                   else min 2.0 (charge + t * 0.05) in
  lance { deflectorCharge = charge' }

updateVelocity :: Time -> Lance -> Lance
updateVelocity t lance
  | linearThrusters lance =
      lance { velocity = M.newVelocity
                           (velocity lance)
                           accelerationRate
                           (angle lance)
                           maxVelocity
                           t                               
            }
  | otherwise = lance

instance Launcher Lance where

  deployProjectiles self = (launchTube self, self { launchTube = []
                                                  -- , sinceLastShot = 0.0
                                                  })

instance Damaging Lance where

  damageEnergy self =
    if not (deflector self) || deflectorCharge self < 1.0
      then kamikazeDamage
    else 0.0

instance Damageable Lance where

  inflictDamage self d =
    let d' = if godMode self then 0 else d in
    if d' > 0 && (not (deflector self) || deflectorCharge self < 1.0)
       then self { integrity = integrity self - d }
       else self

instance Transient Lance where

  expired' self = if not (integrity self <= 0.0)
                     then Nothing
                     else Just [ef]
    where ef = AfterEffect (SimpleExplosion.new (rt self)
                                                (wmap self)
                                                (Lance.center self)
                                                (Lance.velocity self))

-- instance Audible Lance where
--   processAudio self = queueShotFX