module Dodge.Item.Weapon.Launcher ( launcher , launcherX , remoteLauncher , fireTrackingShell ) where import Dodge.Data import Dodge.Reloading.Action --import Dodge.Default import Dodge.Default.Weapon import Dodge.Item.Location import Dodge.SoundLogic.LoadSound import Dodge.WorldEvent.Explosion import Dodge.Item.Weapon.Shell import Dodge.Item.Weapon.Remote import Dodge.Item.Weapon.TriggerType import Dodge.Movement.Turn import Dodge.Base import Dodge.Zone import Dodge.SoundLogic import Dodge.WorldEvent.SpawnParticle import Dodge.WorldEvent.Cloud import RandomHelp import Geometry import Picture import qualified IntMapHelp as IM --import Padding import ShapePicture import Shape import qualified Streaming.Prelude as S --import qualified Data.Set as S import qualified Data.Map.Strict as M import qualified SDL import Data.Maybe import Control.Lens launcher :: Item launcher = defaultWeapon { _itConsumption = defaultLoadable & laAmmoType .~ ProjectileAmmo { _amPayload = makeExplosionAt , _amString = "EXPLOSIVE SHELL" , _amPjDraw = drawShell , _amPjCreation = fireShell } & laMax .~ 1 & laLoaded .~ 1 & laCycle .~ [loadEject 30, loadInsert 30, loadPrime 10] , _itUse = ruseRate 20 usePjCreation [ hammerCheckI , ammoCheckI , useTimeCheck , withSoundStart tap4S , useAmmoAmount 1 ] & useAim . aimWeight .~ 8 & useAim . aimRange .~ 0.5 & useAim . aimStance .~ TwoHandTwist , _itParams = ShellLauncher { _shellSpinDrag = 1 , _shellSpinAmount = 2 , _shellThrustDelay = 1 } , _itTweaks = Tweakable { _tweakSel = 0 , _tweakParams = basicAmPjMoves } , _itInvSize = 3 , _itDimension = ItemDimension { _dimRad = 9 , _dimCenter = V3 10 0 0 , _dimPortage = HeldItem {_handlePos = 5 ,_muzPos = 20 } , _dimSPic = launcherPic } } & itType . iyBase .~ LAUNCHER & itType . iyModules . at ModLauncherHoming ?~ EMPTYMODULE launcherX :: Int -> Item launcherX i = launcher & itType . iyBase .~ LAUNCHERX i & itConsumption . laMax .~ i & itConsumption . laLoaded .~ i & itUse . rUse .~ usePjCreationX i & itUse . useMods .~ [ hammerCheckI , ammoCheckI , useTimeCheck , withSoundStart tap4S , useAmmoAmount i ] usePjCreation :: Item -> Creature -> World -> World usePjCreation it = _amPjCreation (_laAmmoType (_itConsumption it)) it usePjCreationX :: Int -> Item -> Creature -> World -> World usePjCreationX i it cr = foldr f (_amPjCreation (_laAmmoType (_itConsumption it)) it cr) [1..i-1] where f n = (. _amPjCreation (_laAmmoType (_itConsumption it)) it (cr & crDir +~ (2*pi*fromIntegral n / fromIntegral i))) launcherPic :: Item -> SPic launcherPic _ = ( colorSH cyan $ prismPoly (map (+.+.+ V3 20 0 5) $ polyCircx 4 5) (map (+.+.+ V3 0 0 5) $ polyCircx 4 5) , mempty ) basicAmPjMoves :: IM.IntMap TweakParam basicAmPjMoves = IM.fromList . zip [0..] $ [spinDrag ,spinStart ,thrustParam ] spinDrag :: TweakParam spinDrag = TweakParam { _doTweak = \i -> itParams . shellSpinDrag .~ i , _curTweak = 1 , _showTweak = show , _maxTweak = 5 , _nameTweak = "SPIN SLOWDOWN" } spinStart :: TweakParam spinStart = TweakParam { _doTweak = \i -> itParams . shellSpinAmount .~ i , _curTweak = 2 , _showTweak = show , _maxTweak = 5 , _nameTweak = "SPIN AMOUNT" } thrustParam :: TweakParam thrustParam = TweakParam { _doTweak = \i -> itParams . shellThrustDelay .~ i , _curTweak = 1 , _showTweak = show , _maxTweak = 5 , _nameTweak = "THRUST DELAY" } pjThrust :: Int -> Prop -> World -> World pjThrust i = pjEffTimeRange (st,et) doThrust where et | i == 0 = 36 | otherwise = 40 - (i * 20) st = et - 100 pjTrack :: Int -> Int -> Prop -> World -> World pjTrack itid i pj w = pjEffTimeRange (st,et) rotateToTarget pj w where et | i == 0 = 36 | otherwise = 40 - (i * 20) st = et - 100 rotateToTarget _ = fromMaybe id $ do tpos <- w ^? itPoint . itTargeting . tgPos . _Just return $ props . ix (_pjID pj) . pjSpin .~ turnToAmount 0.15 (_prPos pj) tpos (argV $ _pjAcc pj) itPoint = pointToItem $ _itemPositions w IM.! itid doThrust :: Prop -> World -> World doThrust pj w = w & randGen .~ g & props . ix i . pjVel %~ (\v -> accel +.+ frict *.* v) & soundContinue (ShellSound i) newPos missileLaunchS (Just 1) & makeFlamelet (oldPos -.- vel) 0 (vel +.+ rotateV (pi+sparkD) accel) Nothing 3 10 & shellTrailCloud (addZ 20 (oldPos +.+ r1 +.+ 30 *.* normalizeV (oldPos -.- newPos))) where accel = _pjAcc pj i = _pjID pj oldPos = _prPos pj vel = _pjVel pj newPos = oldPos +.+ vel (frict,g) = randomR (0.6,0.9) $ _randGen w (sparkD,_) = randomR (-0.2,0.2) $ _randGen w r1 = randInCirc 10 & evalState $ _randGen w fireShell :: Item -> Creature -> World -> World fireShell it cr = makeShell it cr $ \pj -> pjEffAtTime 35 (trySpinByCID (_crID cr) spinamount) pj . pjThrust thrustdelay pj . reduceSpinBy (1-fromIntegral spindrag*2 / 200) pj . decTimMvVel pj . moveShell pj where params = _itParams it spindrag = _shellSpinDrag params spinamount = _shellSpinAmount params thrustdelay = _shellThrustDelay params moveShell :: Prop -- ^ Projectile id -> World -> World moveShell pj w | time > 40 = if circOnSomeWall oldPos 4 w then doExplode else w | anythingHitCirc 2 oldPos newPos w = doExplode | time > -300 = w | otherwise = doExplode where time = _pjTimer pj doExplode = w & _pjPayload pj oldPos & stopSoundFrom (ShellSound i) & props %~ IM.delete i i = _pjID pj oldPos = _prPos pj vel = _pjVel pj newPos = oldPos +.+ vel reduceSpinBy :: Float -> Prop -> World -> World reduceSpinBy x pj = props . ix (_pjID pj) . pjSpin *~ x drawShell :: Prop -> SPic drawShell pj = noPic . translateSHz 18 . uncurryV translateSHf (_prPos pj) $ rotateSH (argV $ _pjAcc pj) shellShape shellShape :: Shape shellShape = colorSH black $ upperPrismPoly 4 $ map toV2 [(-6,4),(-6,-4),(6,-4),(8,0),(6,4)] remoteShellShape :: Color -> SPic remoteShellShape col = (shellShape , setLayer BloomNoZWrite . setDepth 4.5 . color col $ circleSolid 3 ) drawRemoteShell :: Prop -> SPic drawRemoteShell pj | rem (t+200) 20 < 9 = doposition $ noPic shellShape | otherwise = doposition $ remoteShellShape col where col | t > (-99) = green | otherwise = red t = _pjTimer pj doposition = translateSPz 18 . uncurryV translateSPf (_prPos pj) . rotateSP (_pjDir pj) pjEffTimeRange :: (Int,Int) -> (Prop -> World -> World) -> Prop -> World -> World pjEffTimeRange (st,et) f pj | t <= et && t >= st = f pj | otherwise = id where t = _pjTimer pj pjEffAtTime :: Int -> (Prop -> World -> World) -> Prop -> World -> World pjEffAtTime t f pj | _pjTimer pj == t = f pj | otherwise = id trySpinByCID :: Int -- ^ creature id -> Int -- ^ Spin amount -> Prop -> World -> World trySpinByCID cid i pj w = w & props . ix pjid . pjSpin .~ newSpin where pjid = _pjID pj dir = argV $ _pjVel pj newSpin = case w ^? creatures . ix cid of Just cr -> negate $ min 0.2 $ max (-0.2) $ normalizeAnglePi (dir - _crDir cr) / spinFactor _ -> 0 spinFactor = 5 * (6 - fromIntegral i) remoteLauncher :: Item remoteLauncher = launcher & itType . iyBase .~ REMOTELAUNCHER & itUse . rUse .~ fireRemoteShell & itScope .~ RemoteScope (V2 0 0) 1 True & itConsumption . laAmmoType . amPjDraw .~ drawRemoteShell -- TODO consider allowing tweaking rocket speed fireRemoteShell :: Item -> Creature -> World -> World fireRemoteShell it cr w = set (creatures . ix cid . crInv . ix j . itUse . rUse) (\_ _ -> explodeRemoteRocket itid i) -- $ set (creatures . ix cid . crInv . ix j . itName) remoteLauncherName $ addRemRocket w' where (w',itid) = getHeldItemLoc cr w i = IM.newKey $ _props w cid = _crID cr addRemRocket = makeShell it cr $ \pj -> decTimMvVel pj . setRemoteScope itid pj . moveRemoteShell cid itid pj j = crSel cr moveRemoteShell :: Int -> Int -> Prop -> World -> World moveRemoteShell cid itid pj w | time > 40 = if circOnSomeWall oldPos 4 w then doExplosion else w | anythingHitCirc 2 oldPos newPos w = doExplosion | time >= 20 = w & props . ix i . pjDir .~ newdir | time > -99 = w & set randGen g & props . ix i %~ ( ( pjVel %~ ( (accel +.+) . (frict *.*) ) ) . ( pjDir .~ newdir ) ) & soundContinue (ShellSound i) newPos missileLaunchS (Just 1) & smokeGen & makeFlamelet oldPos 20 (0.5 *.* rotateV (pi+sparkD) accel) Nothing 3 10 | time > -200 = w | otherwise = doExplosion where time = _pjTimer pj i = _pjID pj oldPos = _prPos pj vel = _pjVel pj newPos = oldPos +.+ vel newdir | SDL.ButtonRight `M.member` _mouseButtons w && w ^? creatures . ix cid . crInvSel . iselPos == w ^? itemPositions . ix itid . ipInvId = _cameraRot w + argV (_mousePos w) | otherwise = _pjDir pj accel = rotateV newdir (V2 2 0) (frict,g) = randomR (0.6,0.9) $ _randGen w (sparkD,_) = randomR (-0.5,0.5) $ _randGen w r1 = _randGen w & evalState (randInCirc 10) smokeGen = shellTrailCloud $ addZ 20 $ oldPos +.+ r1 +.+ 30 *.* normalizeV (oldPos -.- newPos) doExplosion = explodeRemoteRocket itid i $ stopSoundFrom (ShellSound i) w anythingHitCirc :: Float -> Point2 -> Point2 -> World -> Bool anythingHitCirc rad sp ep w = hitCr || isJust (sequence hitWl) where hitCr = runIdentity $ S.any_ (const True) $ overlap1SegCrs sp ep $ crsNearSeg sp ep w hitWl = collideCircWallsStream sp ep rad $ wlsNearPoint ep w -- this should probably be wallsOnLine or something explodeRemoteRocket :: Int -- ^ Item id -> Int -- ^ Projectile id -> World -> World explodeRemoteRocket itid pjid w = w & props . ix pjid . pjUpdate .~ (\_ -> retireRemoteProj fireRemoteShell itid 30 pjid) & props . ix pjid . prDraw .~ const mempty & itPoint . itUse . rUse .~ (\_ _ -> id) -- & itPoint . itName .~ "REMOTELAUNCHER" & _pjPayload thepj (_prPos thepj) where itPoint = pointToItem $ _itemPositions w IM.! itid thepj = _props w IM.! pjid fireTrackingShell :: Item -> Creature -> World -> World fireTrackingShell it cr w = addTrackRocket w' where (w',itid) = getHeldItemLoc cr w addTrackRocket = makeShell it cr $ \pj -> pjEffAtTime 35 (trySpinByCID (_crID cr) spinamount) pj . pjThrust thrustdelay pj . reduceSpinBy (1-fromIntegral spindrag*2 / 200) pj . decTimMvVel pj . pjTrack itid thrustdelay pj . moveShell pj spinamount = _shellSpinAmount params thrustdelay = _shellThrustDelay params spindrag = _shellSpinDrag params params = _itParams it makeShell :: Item -> Creature -> (Prop -> World -> World) -> World -> World makeShell it cr theupdate w = w & props %~ IM.insert i Shell { _prPos = pos , _pjZ = 20 , _pjStartPos = pos , _pjVel = rotateV dir (V2 1 0) , _prDraw = _amPjDraw $ _laAmmoType am , _pjID = i , _pjUpdate = theupdate , _pjAcc = rotateV dir (V2 3 0) , _pjDir = dir , _pjSpin = 0 , _pjPayload = _amPayload $ _laAmmoType am , _pjTimer = 50 } where i = IM.newKey $ _props w dir = _crDir cr pos = _crPos cr +.+ rotateV dir (V2 (_crRad cr + 1) 0) am = _itConsumption it