packages feed

edge-0.8.2: Lance.hs

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

import Data.WrapAround
import Animation
import Graphics.Gloss.Data.Picture
import Graphics.Gloss.Data.Color
import GHC.Float
import Trigonometry
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 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

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

type LanceInventory = [Bool]

data Lance = Lance { angle :: Angle -- radians
                   , center :: WrapPoint
                   , rotationalThrusters :: RotationDirection
                   , idealTargetCenter :: Maybe WrapPoint
                   , velocity :: (Double, Double)
                   , linearThrusters :: Bool
                   , wrapMap :: WrapMap
                   , resourceTracker :: ResourceTracker

                   -- deflector                   
                   , deflectorCharge :: Double
                   , deflector :: Bool

                   -- firing
                   , launchTube :: [Projectile]
                   , sinceLastShot :: Time
                   , fireTrigger :: Bool
                   , currentWeapon :: Int
                   , inventory :: LanceInventory

                   -- death
                   , integrity :: Double

                   -- Sound
                   , queueShotSound :: Bool
                   , shotSoundSource :: Maybe Source

                   , godMode :: Bool
                   }

new :: ResourceTracker -> WrapMap -> WrapPoint -> Lance
new rt wmap center =
  Lance { center = center
        , angle = 0.0
        , rotationalThrusters = Stable
        , idealTargetCenter = Nothing
        , velocity = (0.0, 0.0)
        , linearThrusters = False
        , wrapMap = wmap
        , resourceTracker = rt
        , 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
        }

changeCurrentWeapon self =
  let i = inventory self in
  let c = currentWeapon self in
  let c' = if c + 1 > 5
             then if i !! 0
                    then 1
                    else 0
             else c + 1 in
  if c' /= 0 && not (i !! (c' - 1))
    then changeCurrentWeapon self { currentWeapon = c' }
    else self { currentWeapon = c' }

replaceAt :: Int -> a -> [a] -> [a]
replaceAt i a as
  | i < 0 = as
  | i >= length as = as
  | otherwise = let x = take i as in
                let y = drop (i + 1) as in
                x ++ [a] ++ y

processItem :: Lance -> Item -> Lance
processItem self (Item typ _ _) =
  case typ of
    Health -> self { integrity = 3.0 }
    FourWay -> self { inventory = replaceAt 0 True (inventory self)
                    , currentWeapon = 1
                    }
    Cannon -> self { inventory = replaceAt 1 True (inventory self) 
                   , currentWeapon = 2
                   }
    Spread -> self { inventory = replaceAt 2 True (inventory self) 
                   , currentWeapon = 3
                   }
    RapidFire -> self { inventory = replaceAt 3 True (inventory self) 
                   , currentWeapon = 4
                   }
    Nuke -> self { inventory = replaceAt 4 True (inventory self) 
                 , currentWeapon = 5
                 }

instance Audible Lance where

  processAudio self lcenter =
    do self' <- if isNothing (shotSoundSource self)
                  then initializeShotSoundSource self
                  else return self
       if not (queueShotSound self)
               then return self'
               else do play [fromJust $ shotSoundSource self]
                       return self' { queueShotSound = False }

  terminateAudio self =
    if isNothing (shotSoundSource self)
      then return self
      else do stop [fromJust (shotSoundSource self)]
              return self

initializeShotSoundSource self =
  do [source] <- genObjectNames 1
     buffer source $= getSound (resourceTracker self) "simple-energy-shot.wav"
     -- ...
     return self { shotSoundSource = Just source }

shielded :: Lance -> Bool
shielded lance = deflectorCharge lance >= 1.0 && deflector lance

updateAngle :: Time -> Lance -> Lance
updateAngle t lance
  | rotationalThrusters lance == CW
      = lance { angle = angle lance - radialVelocity * t}
  | rotationalThrusters lance == CCW
      = lance { angle = angle lance + radialVelocity * t}
  | otherwise = lance

instance Animation Lance where
  image lance _ = let rt = resourceTracker lance in
                  let lancePic = fromMaybe
                        (Scale 0.20 0.20
                          (Color white
                            (Text "Error! Missing image!")))
                              (getImage rt "lance.bmp") in
                  let lanceThrustingPic =
                        fromMaybe
                          (Scale 0.20 0.20
                            (Color white
                              (Text "Error! Missing image!")))
                                (getImage rt "lance-thrusting.bmp") in
                  let pic = if linearThrusters lance then lanceThrustingPic
                                                     else lancePic in
                  let deflector' = if deflector lance
                                      && deflectorCharge lance >= 1.0
                                   then Color white (Circle 40.0)
                                   else Blank in
                  Pictures [ deflector'
                           , Rotate (radToDeg
                                      (double2Float
                                        (angle lance)) * (-1) - 90) pic
                           ]

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 lance t = (updateFiringInformation t .
                      updateIdealTargetCenter t .
                      updateVelocity t .
                      updateAngle t) lance

  postUpdate lance t =
    let center' = fromMaybe (center lance) (idealTargetCenter lance) in
    updateDeflectorCharge t
      lance { center = center'
            , idealTargetCenter = Nothing
            }

updateFiringInformation t lance =
  let sinceLastShot' = sinceLastShot lance + t in
  case currentWeapon lance of
    1 -> handleFourWayWeapon lance sinceLastShot'
    2 -> handleCannonWeapon lance sinceLastShot'
    3 -> handleSpreadWeapon lance sinceLastShot'
    4 -> handleRapidFireWeapon lance sinceLastShot'
    5 -> handleNukeWeapon lance sinceLastShot'
    otherwise -> handleDefaultWeapon lance sinceLastShot'
  
handleDefaultWeapon self ls =
 if ls >= 0.4 && fireTrigger self
   then self { sinceLastShot = 0.0
             , launchTube =
                 Projectile ( P.BulletMkI.new
                               (wrapMap 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
                                 (wrapMap self)
                                 (angle self)
                                 (center self)
                                 (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wrapMap self)
                                   (angle self + pi / 2)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wrapMap self)
                                   (angle self + pi)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wrapMap 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
                               (wrapMap 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
                                 (wrapMap self)
                                 (angle self)
                                 (center self)
                                 (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wrapMap self)
                                   (angle self + spreadAngle)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wrapMap self)
                                   (angle self + spreadAngle * 2)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wrapMap self)
                                   (angle self - spreadAngle)
                                   (center self)
                                   (velocity self) ) ]
                 ++ [ Projectile ( P.BulletMkI.new
                                   (wrapMap 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
                               (wrapMap 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
                               (wrapMap self)
                               (resourceTracker 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' }

updateIdealTargetCenter :: Time -> Lance -> Lance
updateIdealTargetCenter t lance =
  lance { idealTargetCenter = Just (M.idealNewLocation (wrapMap lance)
                                                       (center lance)
                                                       (velocity lance)
                                                       t)
        }

updateVelocity :: Time -> Lance -> Lance
updateVelocity t lance
  | linearThrusters lance =
      lance { velocity = M.calcNewVelocity
                           (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 (resourceTracker self)
                                                (wrapMap self)
                                                (Lance.center self)
                                                (Lance.velocity self))

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