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