edge-0.8: Unit/Smart/Saucer.hs
module Unit.Smart.Saucer ( Saucer(..)
, new
) where
import Data.WrapAround
import Animation
import Graphics.Gloss.Data.Picture
import Graphics.Gloss.Data.Color
import GHC.Float
import Trigonometry
import ResourceTracker
import Updating
import qualified Moving as M
import Combat
import qualified Projectile.Pellet as P.Pellet
import AfterEffect
import qualified AfterEffect.SimpleExplosion as SimpleExplosion
import Data.Maybe
import Universe hiding (wrapMap, resourceTracker)
import qualified Universe as U
import Sound.ALUT
radialVelocity = pi -- radians per second
maxVelocityMag = 300
kamikazeDamage = 6.0
maxIntegrity = 1
accelerationRate = 300
adjAngle = pi / 8
shotDelay = 0.10
data Saucer = Saucer { angle :: Double -- radians
, velocity :: (Double, Double)
, center :: WrapPoint
, idealTargetLocation :: Maybe WrapPoint
, wrapMap :: WrapMap
, launchTube :: [Projectile]
, sinceLastShot :: Double
, integrity :: Double
, vision :: Maybe Arena
, resourceTracker :: ResourceTracker
, firingAnglesIndex :: Int
-- Sound
, queueShotSound :: Bool
, shotSoundSource :: Maybe Source
}
instance Audible Saucer 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 let (x, y) = vectorRelation
(wrapMap self)
(lcenter)
(center self)
let s = fromJust $ shotSoundSource self
sourcePosition s $= (Vertex3 (double2Float x)
(double2Float (-y))
0)
play [s]
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) "energy-shot-02.wav"
-- ...
sourceRelative source $= Listener
referenceDistance source $= audioReferenceDistance
-- maxDistance source $= audioMaxDistance
rolloffFactor source $= audioRolloffFactor
return self { shotSoundSource = Just source }
new :: ResourceTracker
-> WrapMap
-> WrapPoint
-> Double
-> Saucer
new rt wmap center' angle' =
Saucer { center = center'
, angle = angle'
, idealTargetLocation = Nothing
, velocity = (0.0, 0.0)
, wrapMap = wmap
, launchTube = []
, sinceLastShot = 0.0
, integrity = maxIntegrity
, vision = Nothing
, resourceTracker = rt
, firingAnglesIndex = 0
, queueShotSound = False
, shotSoundSource = Nothing
}
instance Observant Saucer where
updateVision self arena = self { vision = Just arena }
updateAngle t self
= case vision self of
Nothing -> self
Just a -> case lance a of
Nothing -> self
Just l -> let sDir = angle self in
let sDir' = if sDir == 0.0 / 0.0
then 0.1
else sDir in
let tDir = vectorDirection
(vectorRelation
(wrapMap self)
(center self)
(M.center l)) in
let adj
| tDir - sDir' > adjAngle = radialVelocity * t
| tDir - sDir' < (-1) * adjAngle = (-radialVelocity) * t
| otherwise = 0.0 in
self { angle = angle self + adj }
updateVelocity :: Double -> Saucer -> Saucer
updateVelocity t self =
let thrustingVelocity = M.calcNewVelocity
(velocity self)
accelerationRate
(angle self)
maxVelocityMag
t in
let velocity' = case vision self of
Nothing -> velocity self
Just a ->
case lance a of
Nothing -> velocity self
Just l -> let sDir = angle self in
let sDir' = if sDir == 0.0 / 0.0
then 0.1
else sDir in
let tDir = vectorDirection
(vectorRelation
(wrapMap self)
(center self)
(M.center l)) in
if abs (tDir - sDir') <= adjAngle
then thrustingVelocity
else velocity self in
self { velocity = velocity' }
instance Animation Saucer where
image self _ = fromMaybe
(Scale 0.20 0.20
(Color white
(Text "Error! Missing image!")))
(getImage rt "saucer.bmp")
where rt = resourceTracker self
instance M.Locatable Saucer where
center = center
instance M.Moving Saucer where
velocity = velocity
instance M.Colliding Saucer where
collisionRadius _ = 20.0
instance InternallyUpdating Saucer where
preUpdate self t = (updateFiringInformation t
. updateIdealTargetLocation t
. updateVelocity t
. updateAngle t) self
postUpdate self t =
let center' = fromMaybe (center self) (idealTargetLocation self) in
self { center = center'
, idealTargetLocation = Nothing
}
updateFiringInformation t self =
let inRange = case vision self of
Nothing -> False
Just a -> case lance a of
Nothing -> False
Just l -> Data.WrapAround.distance
(wrapMap self)
(center self)
(M.center l) <= P.Pellet.range in
let sinceLastShot' = sinceLastShot self + t in
if sinceLastShot' >= shotDelay && inRange
then self { sinceLastShot = 0.0
, launchTube = projectile : launchTube self
, queueShotSound = True
, firingAnglesIndex =
if firingAnglesIndex self >= length firingAngles - 1
then 0
else firingAnglesIndex self + 1
}
else self { sinceLastShot = sinceLastShot' }
where projectile = Projectile
(P.Pellet.new
(wrapMap self)
pAngle
(center self)
(velocity self))
pAngle = firingAngles !! firingAnglesIndex self
firingAngles = [ 0.0
, 3.142
, 0.785
, 3.927
, 1.571
, 4.712
, 2.356
, 5.498
]
updateIdealTargetLocation :: Double -> Saucer -> Saucer
updateIdealTargetLocation t self =
self { idealTargetLocation = Just (M.idealNewLocation (wrapMap self)
(center self)
(velocity self)
t)
}
instance Launcher Saucer where
deployProjectiles self = (launchTube self, self { launchTube = [] })
instance Transient Saucer where
expired' self = if integrity self > 0.0
then Nothing
else Just [aeffect]
where aeffect = AfterEffect (SimpleExplosion.new (resourceTracker self)
(wrapMap self)
(center self)
(velocity self))
instance Damageable Saucer where
inflictDamage self d = self { integrity = max 0.0 (integrity self - d) }
instance Damaging Saucer where
damageEnergy self = kamikazeDamage