{- | Weapon effects when pulling the trigger. -} module Dodge.Item.Weapon.TriggerType where import Dodge.Data import Dodge.SoundLogic import Dodge.Creature.Action (startReloadingWeapon) import Dodge.WorldEvent (muzzleFlashAt,tempLightForAt) import Dodge.WorldEvent.Cloud import Dodge.RandomHelp import Dodge.Particle.Bullet.Spawn import Dodge.Item.Attachment.Data import Dodge.Item.Data import Geometry import System.Random import Control.Lens import Control.Monad.State import Data.Maybe import qualified Data.IntMap.Strict as IM import Data.Foldable withThinSmoke :: (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World withThinSmoke eff cr w = eff cr $ foldl' (flip $ makeThinSmokeAt . (+.+ pos)) w ps where dir = _crDir cr pos = _crPos cr +.+ (_crRad cr +0.5) *.* unitVectorAtAngle dir ps = (replicateM 5 . randInCirc) 8 & evalState $ _randGen w withThickSmoke :: (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World withThickSmoke eff cr w = eff cr $ foldl' (flip $ makeThickSmokeAt . (+.+ pos)) w ps where dir = _crDir cr pos = _crPos cr +.+ (_crRad cr + 15) *.* unitVectorAtAngle dir ps = (replicateM 20 . randInCirc) 8 & evalState $ _randGen w {- | Fires at an increasing rate. Has different effect after first fire. Applies ammo check and use cooldown check. -} rateIncAB :: Int -- ^ Start rate -> Int -- ^ End rate -> (Creature -> World -> World) -- ^ Effect on first fire -> (Creature -> World -> World) -- ^ Effect on continued fire -> Creature -- ^ Creature id -> World -> World rateIncAB startRate fastRate shooteff1 shooteff2 cr w | repeatFire = w & pointItem %~ ( (itUseRate .~ max fastRate (currentRate - 1)) . (wpLoadedAmmo -~ 1) . (itUseTime .~ currentRate) ) & shooteff2 cr | firstFire = w & pointItem %~ ( (itUseRate .~ startRate - 1) . (wpLoadedAmmo -~ 1) . (itUseTime .~ startRate) ) & shooteff1 cr | reloadCondition = fromMaybe w $ startReloadingWeapon cr w | otherwise = w where cid = _crID cr itRef = _crInvSel cr item = _crInv cr IM.! itRef pointItem = creatures . ix cid . crInv . ix itRef currentRate = _itUseRate item repeatFire = _wpReloadState item == 0 && _itUseTime item == 1 && _wpLoadedAmmo item > 0 firstFire = _wpReloadState item == 0 && _itUseTime item == 0 && _wpLoadedAmmo item > 0 reloadCondition = _wpLoadedAmmo item == 0 {- | Shoot a weapon rapidly after a warm up. Applies ammo check as well. -} withWarmUp :: Int -- ^ Warm up time (in frames) -> (Creature -> World -> World) -- ^ Shoot effect -> Creature -> World -> World withWarmUp t f cr w | reloadCondition = fromMaybe w $ startReloadingWeapon cr w | _wpReloadState item /= 0 = w | fState == 0 = w & pointerToItem %~ ( (itUse .~ (\_ -> withWarmUp 100 f)) . (itUseTime .~ 2) ) | t > 2 = w & pointerToItem %~ ( (itUse .~ (\_ -> withWarmUp (t-1) f)) . (itUseTime .~ 2) ) & soundFrom (CrWeaponSound cid) 26 2 0 | t > 0 = w & pointerToItem %~ ( (itUse .~ (\_ -> withWarmUp (t-1) f)) . (itUseTime .~ 2) ) | otherwise = w & pointerToItem %~ ( (itUse .~ (\_ -> withWarmUp 1 f)) . (wpLoadedAmmo -~ 1) . (itUseTime .~ 2) ) & f cr & soundFrom (CrWeaponSound cid) 28 2 0 where cid = _crID cr itRef = _crInvSel cr item = _crInv cr IM.! itRef pointerToItem = creatures . ix cid . crInv . ix itRef fState = _itUseTime item reloadCondition = _wpLoadedAmmo item == 0 {- | Adds a sound to a creature based world effect. The sound is emitted from the creature's position. -} withSound :: Int -- ^ Sound id -> (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World withSound soundid f cr = soundOncePos soundid (_crPos cr) . f cr withRecoil :: Float -- ^ Recoil amount -> (Creature -> World -> World) -- ^ Underlying world effect, takes creature id as input -> Creature -> World -> World withRecoil recoilAmount eff cr = eff cr . over (creatures . ix cid) pushback where cid = _crID cr pushback = over crPos (+.+ rotateV (_crDir cr) (V2 ((-recoilAmount) / _crMass cr ) 0)) {- | Pushes a creature sideways by a random amount. Applied before the underlying effect. -} withSidePush :: Float -- ^ Maximal possible side push amount -> (Creature -> World -> World) -- ^ Underlying world effect, takes creature id as input -> Creature -> World -> World withSidePush maxSide eff cr w = eff cr . over (creatures . ix cid) push $ w where cid = _crID cr push = over crPos (+.+ rotateV (_crDir cr) (V2 0 (pushAmount / _crMass cr))) (pushAmount, _) = randomR (-maxSide,maxSide) $ _randGen w -- consider unifying the pushes using a direction vector {- | Pushes a creature sideways by a random amount. Applied after the underlying effect. -} withSidePushAfter :: Float -- ^ Maximal possible side push amount -> (Creature -> World -> World) -- ^ Underlying world effect, takes creature id as input -> Creature -- ^ Creature id -> World -> World withSidePushAfter maxSide eff cr w = over (creatures . ix cid) push . eff cr $ w where cid = _crID cr push = over crPos (+.+ rotateV (_crDir cr) (V2 0 (pushAmount / _crMass cr))) (pushAmount, _) = randomR (-maxSide,maxSide) $ _randGen w {- | Applies a world effect and sound effect after an ammo check. -} shootWithSound :: Int -- ^ Sound identifier -> (Creature -> World -> World) -- ^ Shoot effect, takes creature id as input -> Creature -> World -> World shootWithSound soundid f cr w | fireCondition = over (pointerToItem . wpLoadedAmmo) (\ammo -> ammo-1) $ soundOncePos soundid (_crPos cr) $ set (pointerToItem . itUseTime) (_itUseRate item) $ f cr w | reloadCondition = fromMaybe w $ startReloadingWeapon cr w | otherwise = w where cid = _crID cr itRef = _crInvSel cr item = _crInv cr IM.! itRef pointerToItem = creatures . ix cid . crInv . ix itRef fireCondition = _wpReloadState item == 0 && _itUseTime item == 0 && _wpLoadedAmmo item > 0 reloadCondition = _wpLoadedAmmo item == 0 {- | Applies a world effect and sound effect after an ammo check. -} shootWithSoundI :: Int -- ^ Sound identifier -> (Item -> Creature -> World -> World) -- ^ Shoot effect, takes creature id as input -> Item -> Creature -> World -> World shootWithSoundI soundid f item cr w | fireCondition = over (pointerToItem . wpLoadedAmmo) (\ammo -> ammo-1) $ soundOncePos soundid (_crPos cr) $ set (pointerToItem . itUseTime) (_itUseRate item) $ f item cr w | reloadCondition = fromMaybe w $ startReloadingWeapon cr w | otherwise = w where cid = _crID cr itRef = _crInvSel cr pointerToItem = creatures . ix cid . crInv . ix itRef fireCondition = _wpReloadState item == 0 && _itUseTime item == 0 && _wpLoadedAmmo item > 0 reloadCondition = _wpLoadedAmmo item == 0 {- | Applies a world effect after an item use cooldown check. -} useTimeCheck :: (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World useTimeCheck f cr w = case theItem ^? itUseTime of Just 0 -> f cr $ setUseTime w _ -> w where cid = _crID cr setUseTime = creatures . ix cid . crInv . ix (_crInvSel cr) . itUseTime +~ useRate theItem = _crInv cr IM.! _crInvSel cr useRate = fromMaybe 0 $ theItem ^? itUseRate {- | Applies a world effect after a hammer position check. -} hammerCheck :: (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World hammerCheck f cr w = case (_crInv cr IM.! _crInvSel cr) ^? itHammer of Just HammerUp -> f cr $ setHammerDown w _ -> setHammerDown w where cid = _crID cr setHammerDown = creatures . ix cid . crInv . ix (_crInvSel cr) . itHammer .~ HammerDown {- | Applies a world effect after a hammer position check. -} hammerCheckI :: (Item -> Creature -> World -> World) -- ^ Underlying effect -> Item -> Creature -> World -> World hammerCheckI f it cr w = case it ^? itHammer of Just HammerUp -> f it cr $ setHammerDown w _ -> setHammerDown w where cid = _crID cr setHammerDown = creatures . ix cid . crInv . ix (_crInvSel cr) . itHammer .~ HammerDown {- | Applies a world effect after an ammo check. -} shoot :: (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World shoot f cr w | fireCondition = f cr w & pointerToItem %~ ( (wpLoadedAmmo -~ 1) . (itUseTime .~ _itUseRate item) ) | reloadCondition = fromMaybe w $ startReloadingWeapon cr w | otherwise = w where cid = _crID cr itRef = _crInvSel cr item = _crInv cr IM.! itRef pointerToItem = creatures . ix cid . crInv . ix itRef fireCondition = _wpReloadState item == 0 && _itUseTime item == 0 && _wpLoadedAmmo item > 0 reloadCondition = _wpLoadedAmmo item == 0 {- | Applies a world effect after a hammer position check. Arbitrary inventory position. -} hammerCheckL :: (Creature -> Int -> World -> World) -- ^ Underlying effect -> Creature -> Int -> World -> World hammerCheckL f cr invid w = case (_crInv cr IM.! invid) ^? itHammer of Just HammerUp -> f cr invid $ setHammerDown w _ -> setHammerDown w where cid = _crID cr setHammerDown = creatures . ix cid . crInv . ix invid . itHammer .~ HammerDown {- | Applies a world effect after an ammo check. Arbitrary inventory position. -} shootL :: (Creature -> Int -> World -> World) -- ^ Underlying effect -> Creature -> Int -- ^ Inventory position -> World -> World shootL f cr invid w | fireCondition = f cr invid w & pointerToItem %~ ( (wpLoadedAmmo -~ 1) . (itUseTime .~ _itUseRate item) ) | reloadCondition = fromMaybe w $ startReloadingWeapon cr w | otherwise = w where cid = _crID cr item = _crInv cr IM.! invid pointerToItem = creatures . ix cid . crInv . ix invid fireCondition = _wpReloadState item == 0 && _itUseTime item == 0 && _wpLoadedAmmo item > 0 reloadCondition = _wpLoadedAmmo item == 0 withMuzFlare :: (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World withMuzFlare f cr w = tempLightForAt 3 pos . muzzleFlashAt pos2 $ f cr w where pos = _crPos cr +.+ _crRad cr *.* unitVectorAtAngle (_crDir cr) pos2 = _crPos cr +.+ (2 * _crRad cr) *.* unitVectorAtAngle (_crDir cr) withMuzFlareI :: (Item -> Creature -> World -> World) -- ^ Underlying effect -> Item -> Creature -> World -> World withMuzFlareI f it cr w = tempLightForAt 3 pos . muzzleFlashAt pos2 $ f it cr w where pos = _crPos cr +.+ _crRad cr *.* unitVectorAtAngle (_crDir cr) pos2 = _crPos cr +.+ (2 * _crRad cr) *.* unitVectorAtAngle (_crDir cr) {- | Applies the effect to a randomly rotated creature. -} withRandomDir :: Float -- ^ Max possible rotation -> (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World withRandomDir acc f cr w = f (cr & crDir +~ a) $ set randGen g w where (a, g) = randomR (-acc,acc) $ _randGen w {- | Applies the effect to a randomly rotated creature. -} withRandomDirI :: Float -- ^ Max possible rotation -> (Item -> Creature -> World -> World) -- ^ Underlying effect -> Item -> Creature -> World -> World withRandomDirI acc f it cr w = f it (cr & crDir +~ a) $ set randGen g w where (a, g) = randomR (-acc,acc) $ _randGen w {- | Creates a bullet with a given velocity, width, and 'HitEffect' -} withVelWthHiteff :: Point2 -- ^ Velocity, x direction is forward with respect to the creature -> Float -- ^ Bullet width -> HitEffect -- ^ Bullet effect when hitting creature, wall etc -> Creature -- ^ Creature id -> World -> World withVelWthHiteff vel width hiteff cr = over particles (newbul : ) where cid = _crID cr newbul = aGenBulAt (Just cid) pos (rotateV dir vel) hiteff width dir = _crDir cr pos = _crPos cr +.+ _crRad cr *.* unitVectorAtAngle (_crDir cr) {- | Apply the effect to a translated creature. -} withRandomOffset :: Float -- ^ Max possible translate -> (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World withRandomOffset offsetAmount f cr w = f (cr & crPos %~ (+.+ offV)) $ set randGen g w where (offsetVal , g) = randomR (-offsetAmount,offsetAmount) $ _randGen w offV = rotateV (_crDir cr) (V2 0 offsetVal) -- | Rotates a creature with minimum rotation at least 0.1. -- Rotates the player creature before applying the effect, other creatures after. torqueBeforeForced :: Float -- ^ Max possible rotation (less the 0.1 forced rotation) -> (Creature -> World -> World) -- ^ Underlying effect -> Creature -> World -> World torqueBeforeForced torque feff cr w | cid == 0 = feff (cr & crDir +~ rot') $ w & randGen .~ g & creatures . ix cid . crDir +~ rot' & cameraRot +~ rot' | otherwise = feff cr $ set randGen g $ over (creatures . ix cid . crDir) (+rot') w where cid = _crID cr (rot, g) = randomR (-torque,torque) $ _randGen w rot' | rot < 0 = rot - 0.1 | otherwise = rot + 0.1 -- | Rotates the player creature before applying an effect. -- Note this currently (29/4/2021) rotates other creatures /after/ applying the effect. torqueBefore :: Float -- ^ Max possible rotation -> (Creature -> World -> World) -- ^ Underlying effect -> Creature -- ^ Creature id -> World -> World torqueBefore torque feff cr w | cid == 0 = feff (cr & crDir +~ rot) $ w & randGen .~ g & creatures . ix cid . crDir +~ rot & cameraRot +~ rot | otherwise = set randGen g $ over (creatures . ix cid . crDir) (+rot) $ feff cr w where cid = _crID cr (rot, g) = randomR (-torque,torque) $ _randGen w -- | Rotate a randomly creature after applying an effect. torqueAfter :: Float -- ^ Max possible rotation -> (Creature -> World -> World) -- ^ Underlying effect -> Creature -- ^ Creature id -> World -> World torqueAfter torque feff cr w | cid == 0 = rotateScope $ set randGen g $ over cameraRot (+rot) $ feff cr w | otherwise = set randGen g $ over (creatures . ix cid . crDir) (+rot) $ feff cr w where cid = _crID cr (rot, g) = randomR (-torque,torque) $ _randGen w rotateScope = creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0)) . itAttachment . _Just . scopePos %~ rotateV rot {- | Create multiple bullets with a given spread, a given amount, given velocity, given width and given 'HitEffect'. -} spreadNumVelWthHiteff :: Float -- ^ Spread -> Int -- ^ Number of bullets -> Point2 -- ^ Velocity, x is direction of creature -> Float -- ^ Bullet width -> HitEffect -- ^ Effect when hitting creature, wall etc -> Creature -- ^ Creature id -> World -> World spreadNumVelWthHiteff spread num vel wth eff cr w = over particles (newbuls ++) w where cid = _crID cr newbuls = zipWith (\pos d -> aGenBulAt (Just cid) pos (rotateV d vel) eff wth) poss dirs poss = map ((+.+) $ _crPos cr +.+ _crRad cr *.* unitVectorAtAngle (_crDir cr)) $ (replicateM num . randInCirc) 5 & evalState $ _randGen w dirs = map (_crDir cr +) $ zipWith (+) [-spread,-spread+(2*spread/fromIntegral num)..] (randomRs (0,spread/5) (_randGen w)) {- | Create a number of bullets side by side with a given velocity, given width and given 'HitEffect'. -} numVelWthHitEff :: Int -- ^ Amount of bullets -> Point2 -- ^ Velocity, x axis is direction of creature -> Float -- ^ Bullet width -> HitEffect -- ^ Effect when hitting creature, wall etc -> Creature -> World -> World numVelWthHitEff num vel wth eff cr = over particles (newbuls ++) where cid = _crID cr newbuls = map (\p -> aGenBulAt (Just cid) p (rotateV d vel) eff wth) poss d = _crDir cr poss = map (+.+ pos) $ take num offsets maxOffset = fromIntegral num * 2.5 - 2.5 offsets = map (rotateV d . V2 0) [-maxOffset,5-maxOffset..] pos = _crPos cr +.+ _crRad cr *.* unitVectorAtAngle d {- | Uses '_wpSpread' as a parameter for the current offset angle. -} randWalkAngle :: Float -- ^ Max offset angle -> Float -- ^ Walk speed -> (Creature -> World -> World) -- ^ Underlying effect -> Creature -- ^ Creature id -> World -> World randWalkAngle ma aspeed f cr w = w & f (cr & crDir +~ a) & creatures . ix cid . crInv . ix i . wpSpread .~ newa where cid = _crID cr a = _wpSpread $ _crInv (_creatures w IM.! cid) IM.! i (walka, _) = randomR (-aspeed,aspeed) (_randGen w) newa = min ma $ max (negate ma) (a + walka) i = _crInvSel $ _creatures w IM.! cid