{-# LANGUAGE LambdaCase #-} --{-# OPTIONS_GHC -Wno-unused-imports #-} module Dodge.Projectile.Update ( updateProjectile, ) where import Data.Foldable import Data.List (delete, deleteBy) import Data.Maybe import Dodge.Base import Dodge.Data.World import Dodge.EnergyBall import Dodge.Item.Location import Dodge.Movement.Turn import Dodge.Payload import Dodge.SoundLogic import Dodge.WorldEvent.Cloud import Dodge.WorldEvent.Sound import Dodge.WorldEvent.ThingsHit import Geometry import qualified IntMapHelp as IM import LensHelp import NewInt import RandomHelp updateProjectile :: Projectile -> World -> World updateProjectile pj w = (cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjTimer -~ 1) . shellCollisionCheck pj . moveProjectile pj . doGravityPU pj $ foldl' (flip $ upProjectile pj) w (_pjUpdates pj) upProjectile :: Projectile -> ProjectileUpdate -> World -> World upProjectile pj = \case ThrustPU st et | act st et -> doThrust pj (pj ^? pjType . rkSmoke . _Just) | otherwise -> id StartSpinPU t cid spinamount | time == t -> trySpinByCID cid spinamount pj | otherwise -> id RemoteDirectionPU st et | act st et -> pjRemoteSetDirection (pj ^? pjType . rkHoming) pj | otherwise -> id where time = _pjTimer pj act st et = time <= st && time >= et doGravityPU :: Projectile -> World -> World doGravityPU pj | Grenade GBounce{} <- pj ^. pjType = applyGravityPU pj | Grenade GStick <- pj ^. pjType = applyGravityPU pj | otherwise = id applyGravityPU :: Projectile -> World -> World applyGravityPU pj w | newz <= 0 && abs (pj ^. pjZVel) < 1 = w & topj . pjVel %~ decvel & topj . pjSpin %~ decspin | newz < 0 = w & topj . pjZ .~ 0 & topj . pjZVel %~ bouncez & topj . pjVel %~ decvel & topj . pjSpin %~ decspin & soundStart (ShellSound (pj ^. pjID)) (pj ^. pjPos) click1S Nothing | otherwise = w & topj . pjZ .~ newz & topj . pjZVel -~ 0.5 where topj = cWorld . lWorld . projectiles . ix (pj ^. pjID) bouncez z | z < -1 = -0.5 * z | otherwise = 0 decvel v | magV v > 1 = rotateV (5 * pj ^. pjSpin) $ 0.8 * v | otherwise = 0 decspin x | abs x < 0.01 = 0 | otherwise = x * 0.9 newz = pj ^. pjZ + pj ^. pjZVel -- consider pushing any hit creature back shellCollisionCheck :: Projectile -> World -> World shellCollisionCheck pj w | RetiredProjectile <- pj ^. pjType , time <= 0 = destroyProjectile (pj ^. pjScreenID) (_pjID pj) w | time <= 0 = explodeShell pj w | RetiredProjectile <- pj ^. pjType = w | Just thit <- thingHit oldpos (oldpos + _pjVel pj) w = tryShellBounce thit pj w | otherwise = w where time = _pjTimer pj oldpos = _pjPos pj -- note this doesn't take into account moving walls, which may break the -- bouncing in some way tryShellBounce :: (Point2, Either Creature Wall) -> Projectile -> World -> World tryShellBounce (p, Right wl) pj w | Just GStuckCreature{} <- pj ^? pjType . gnHitEffect = w | Just GStuckWall{} <- pj ^? pjType . gnHitEffect = w | Just GStick <- pj ^? pjType . gnHitEffect = w & cWorld . lWorld . projectiles . ix (_pjID pj) . pjType .~ Grenade (GStuckWall (wl ^. wlStructure)) & soundOriginIDsAt (ShellSound (pj ^. pjID)) [slapS, slap1S] (pj ^. pjPos) | Just x <- pj ^? pjType . gnHitEffect . bounceTolerance , abs (dotV (pj ^. pjVel) (vNormal hitline)) < x = w & cWorld . lWorld . projectiles . ix (_pjID pj) . pjVel %~ reflectIn hitline & cWorld . lWorld . projectiles . ix (_pjID pj) . pjPos .~ p - normalizeV (pj ^. pjVel) & soundStart (ShellSound (pj ^. pjID)) (pj ^. pjPos) click1S Nothing | isJust $ pj ^? pjType . gnHitEffect . bounceTolerance = explodeShell pj w where hitline = normalizeV $ uncurry (-) $ _wlLine wl tryShellBounce (p, Left cr) pj w | Just GStuckCreature{} <- pj ^? pjType . gnHitEffect = w | Just GStuckWall{} <- pj ^? pjType . gnHitEffect = w | Just GStick <- pj ^? pjType . gnHitEffect = w & cWorld . lWorld . projectiles . ix (_pjID pj) . pjType .~ Grenade ( GStuckCreature (cr ^. crID) (rotateV (- cr ^. crDir) (p - cr ^. crPos)) (pj ^. pjDir - cr ^. crDir) ) & soundOriginIDsAt (ShellSound (pj ^. pjID)) [slapS, slap1S] (pj ^. pjPos) tryShellBounce _ pj w = explodeShell pj w destroyProjectile :: Maybe (NewInt ItmInt) -> Int -> World -> World destroyProjectile mitid pjid w = w & cWorld . lWorld . projectiles %~ IM.delete pjid & removelink where removelink = fromMaybe id $ do itid <- fmap _unNInt mitid loc <- w ^? cWorld . lWorld . itemLocations . ix itid return $ pointerToItemLocation loc . itUse . uaParams . apProjectiles %~ delete pjid doThrust :: Projectile -> Maybe RocketSmoke -> World -> World doThrust pj smoke w = w & randGen .~ g & cWorld . lWorld . projectiles . ix i . pjVel %~ (\v -> accel +.+ frict *.* v) & soundContinue (ShellSound i) newPos missileLaunchS (Just 1) -- & makeFlamelet (oldPos -.- vel) (vel + 2 * rotateV (pi + sparkD) accel) 3 10 & makeFlamelet (oldPos -.- vel) (vel +.+ rotateV (pi + sparkD) accel) 3 10 & makeCloudAt RocketSmoke lifetime (addZ 20 (oldPos +.+ r1 +.+ 30 *.* normalizeV (oldPos -.- newPos))) where lifetime = fst . randomR lt $ _randGen w lt | smoke == Just ReducedRocketSmoke = (50, 200) | otherwise = (300, 500) -- trailfadetime = fst . randomR (100, 300) $ _randGen w accel = rotateV (pj ^. pjDir) (V2 3 0) i = _pjID pj oldPos = _pjPos 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 -- there doesn't seem to be a more sensible way to filter the first occurence of -- a list than deleteBy trySpinByCID :: -- | creature id Int -> -- | Spin amount Int -> Projectile -> World -> World trySpinByCID cid i pj w = w & cWorld . lWorld . projectiles . ix pjid . pjSpin .~ newSpin & cWorld . lWorld . projectiles . ix pjid . pjUpdates %~ deleteBy f (StartSpinPU 0 0 0) where f _ StartSpinPU{} = True f _ _ = False pjid = _pjID pj dir = argV $ _pjVel pj newSpin = case w ^? cWorld . lWorld . creatures . ix cid of Just cr -> negate $ min 0.2 $ max (-0.2) $ normalizeAnglePi (dir - _crDir cr) / spinFactor _ -> 0 spinFactor = 5 * (6 - fromIntegral i) -- note this only allows YOU to remotely track projectiles pjRemoteSetDirection :: Maybe RocketHoming -> Projectile -> World -> World pjRemoteSetDirection ph pj w = case ph of Just (HomeUsingRemoteScreen screenid) | lw ^? creatures . ix 0 . crManipulation . manObject . imSelectedItem == lw ^? itemLocations . ix (_unNInt screenid) . ilInvID -> w & cWorld . lWorld . projectiles . ix (_pjID pj) . pjDir .~ (w ^. wCam . camRot) + argV (w ^. input . mousePos) & cWorld . lWorld . projectiles . ix (_pjID pj) . pjSpin .~ 0.5 * diffAngles ((w ^. wCam . camRot) + argV (w ^. input . mousePos)) (_pjDir pj) Just (HomeUsingTargeting itid) | Just tp <- w ^? pointerToItemID itid . itTargeting . itTgPos . _Just -> w & cWorld . lWorld . projectiles . ix (_pjID pj) . pjDir %~ turnTo 0.2 (_pjPos pj) tp & cWorld . lWorld . projectiles . ix (_pjID pj) . pjSpin .~ 0.5 * diffAngles (turnTo 0.2 (_pjPos pj) tp (_pjDir pj)) (_pjDir pj) _ -> w where lw = w ^. cWorld . lWorld moveProjectile :: Projectile -> World -> World moveProjectile pj w | Just GStuckWall{} <- pj ^? pjType . gnHitEffect = w | Just (GStuckCreature crid p d) <- pj ^? pjType . gnHitEffect , Just cr <- w ^? cWorld . lWorld . creatures . ix crid = let cpos = cr ^. crPos cdir = cr ^. crDir vel = cpos - cr ^. crOldPos in w & cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjPos .~ cpos + rotateV cdir p & cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjDir .~ d + cdir & cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjVel .~ vel | Just GStuckCreature{} <- pj ^? pjType . gnHitEffect = w & cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjType .~ Grenade GStick | otherwise = w & cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjSpin *~ 0.99 & cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjPos +~ _pjVel pj & cWorld . lWorld . projectiles . ix (pj ^. pjID) . pjDir +~ (_pjSpin pj * _pjSpinFactor pj) explodeShell :: Projectile -> World -> World explodeShell pj w = w & cWorld . lWorld . projectiles . ix pjid . pjUpdates .~ [] & cWorld . lWorld . projectiles . ix pjid . pjType .~ RetiredProjectile & cWorld . lWorld . projectiles . ix pjid . pjVel .~ 0 & cWorld . lWorld . projectiles . ix pjid . pjTimer .~ 30 & usePayload (_pjPayload pj) (_pjPos pj) (_pjVel pj) & stopSoundFrom (ShellSound pjid) & updatedetonator where updatedetonator = fromMaybe id $ do detid <- pj ^. pjDetonatorID return $ pointerToItemID detid . itUse . uaParams . apProjectiles %~ delete pjid pjid = pj ^. pjID