edge-0.9.1.0: Lance.hs
{-|
Module : Lance
Description : Defines and manipulates the user\'s space ship
Copyright : (c) Christopher Howard, 2016
License : GPL-3
Maintainer : ch.howard@zoho.com
-}
module Lance ( Lance ( rotationalThrusters
, linearThrusters
, deflector
, fireTrigger
, deflectorCharge
, center
, angle
, velocity
, godMode
, integrity
, inventory
, currentWeapon
, swClock
)
, new
, RotationDirection (..)
, shielded
, processItem
, changeCurrentWeapon
, swTimeLimit
) where
import Prelude (not, (||), (<), (-), (<=), Maybe(..), (*), min, (+), (*), otherwise, (>), (&&), Bool(..), max, (++), (>=), pi, (/), map, (.), (!!), (==), Eq, Int, Double)
import Data.WrapAround ( WP, WM )
import Graphics.Gloss.Data.Picture (Picture(..))
import Graphics.Gloss.Data.Color (white)
import GHC.Float (double2Float)
import Data.Maybe (fromMaybe)
import Sound.ALUT (Source(..))
import Animation
import Math
import ResourceTracker
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 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
, clock :: Time
}
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
, clock = 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 l = handSndSrc s
queueShotSound
shotSoundSource
(\a -> a { queueShotSound = False })
rt
"simple-energy-shot.wav"
(\a b -> a { shotSoundSource = Just b })
l (wmap s) center
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 [ reorient (angle s) a, b ]
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 if remF (clock s) 0.1 <= 0.05
then f "deflector-1.bmp"
else f "deflector-2.bmp"
else Blank
f x = fromMaybe Blank (getImage (rt s) x)
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
, clock = clock 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 = firing s a 0.2 (b ++ c ++ [d])
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
projectile u w v = Projectile ( u (wmap w) (angle w + v) (center w) (velocity w) )
bmki = projectile P.BulletMkI.new
handleDefaultWeapon s a = firing s a 0.4 [bmki s 0]
handleFourWayWeapon s a = firing s a 0.4 (map f [ 0, pi / 2, pi, 3 * pi / 2 ])
where f = bmki s
cann = projectile P.Cannon.new
handleCannonWeapon s a = firing s a 0.7 [cann s 0]
handleSpreadWeapon s a = firing s a 0.4 (map f [ 0, b, b * 2, (-b), (-b) * 2])
where f = bmki s
b = pi / 10
handleRapidFireWeapon s a = firing s a 0.2 [bmki s 0]
firing :: Lance
-> Time -- ^ since last shot
-> Time -- ^ intended firing delay
-> [Projectile] -- ^ the new projectiles
-> Lance
firing a b c d =
if b >= c && fireTrigger a
then a { sinceLastShot = 0.0
, launchTube = d ++ (launchTube a)
, queueShotSound = True
}
else a { sinceLastShot = b }
handleNukeWeapon s a = firing s a 3.0 [b]
where b = Projectile ( P.Nuke.new
(wmap s)
(rt s)
(angle s)
(center s)
(velocity s) )
updateDeflectorCharge t s =
s { deflectorCharge = if deflector s
then max 0.8 (c - t * deflectorChargeLossFactor)
else min 2.0 (c + t * 0.05)
}
where c = deflectorCharge s
updateVelocity t s
| linearThrusters s =
s { velocity =
M.newVelocity (velocity s) accelerationRate (angle s) maxVelocity t
}
| otherwise = s
instance Launcher Lance where
deployProjectiles s = (launchTube s, s { launchTube = [] })
instance Damaging Lance where
damageEnergy s = if not (deflector s) || deflectorCharge s < 1.0
then kamikazeDamage
else 0
instance Damageable Lance where
inflictDamage s d =
let e = if godMode s then 0 else d in
if e > 0 && (not (deflector s) || deflectorCharge s < 1.0)
then s { integrity = integrity s - d }
else s
instance Transient Lance where
expired' s = if integrity s <= 0.0 then Just [e] else Nothing
where e = AfterEffect (SimpleExplosion.new (rt s)
(wmap s)
(Lance.center s)
(Lance.velocity s))