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