packages feed

edge-0.8: 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

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 :: Double -- 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 :: Double
                   , 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 :: Double -> 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 :: Double -> Lance -> Lance
updateIdealTargetCenter t lance =
  lance { idealTargetCenter = Just (M.idealNewLocation (wrapMap lance)
                                                       (center lance)
                                                       (velocity lance)
                                                       t)
        }

updateVelocity :: Double -> 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