Files
loop/src/Dodge/WorldEvent/SpawnParticle.hs
T

231 lines
7.9 KiB
Haskell

{- | Creation of particles in the world. -}
module Dodge.WorldEvent.SpawnParticle
( makeGasCloud
, aFlameParticle
, makeFlamelet
) where
import Dodge.Data
import Dodge.Base
import Dodge.WorldEvent.HitEffect
import Dodge.WorldEvent.ThingsHit
import Dodge.WorldEvent.Cloud
import Dodge.WorldEvent.Flash
import Dodge.SoundLogic
import Dodge.RandomHelp
import Dodge.Wall.Damage
import Picture
import Geometry
import qualified IntMapHelp as IM
import LensHelp
import System.Random
import Control.Monad.State
import Data.Tuple
aFlameParticle
:: Int -- ^ Timer
-> Point2 -- ^ Position
-> Point2 -- ^ Velocity
-> Maybe Int -- ^ Creature id
-> Particle
aFlameParticle t pos vel maycid = PtZ
{ _ptDraw = drawFlame vel
, _ptUpdate = moveFlame vel
, _ptVel = vel
, _ptColor = red
, _ptPos = pos
, _ptCrIgnore = maycid
, _ptWidth = 4
, _ptTimer = t
, _ptHitEff = destroyOnImpact (doFlameDam 1)
(\_ p -> damageWall (Damage Flaming 19 p p p NoDamageEffect))
, _ptZ = 20
}
drawFlame
:: Point2 -- ^ Rotate direction
-> Particle
-> Picture
drawFlame rotd pt = pictures
[ glow
, aPic 3 prot2 25 (V2 (scaleChange + 1) 2 ) $ V4 2 (-1) (-1) 0.5
, aPic 3 prot 22 (V2 (scaleChange + 0.5) 1 ) $ V4 1 0.5 0 2
, aPic 1 prot3 20 (V2 scaleChange 0.5) $ V4 1 1 1 1
]
where
ep = _ptPos pt
aPic :: Int -> (Point2 -> Point2) -> Float -> Point2 -> Color -> Picture
aPic lay offset depth (V2 scalex scaley) col
= setLayer lay
. setDepth depth
. uncurryV translate (offset ep)
. rotate (pi * 0.5 + argV rotd)
. scale scalex scaley
. color col
$ circleSolid 5
glow = setLayer 1 $ setDepth 0.3 $ uncurryV translate ep
$ circleSolidCol (withAlpha 0 orange) (withAlpha 0.02 orange) 50
time = _ptTimer pt
scaleChange
| time < 80 = 3
| otherwise = 3 - (fromIntegral time - 80) * 0.2
prot p' = p' +.+ rotateV (fromIntegral time) (V2 0 1)
prot2 p' = p' +.+ rotateV (negate $ fromIntegral time) (V2 0 1)
prot3 p' = p' +.+ rotateV (2 + fromIntegral time * 0.1) (V2 0 2)
{- TODO: add generalised area damage particles/hiteffects. -}
moveFlame :: Point2 -- ^ Rotation direction
-> World
-> Particle
-> (World, Maybe Particle)
moveFlame rotd w pt
| time <= 0 = (makeFlamerSmokeAt (addZ 20 ep) w, Nothing)
| otherwise = case thingsHitExceptCr (_ptCrIgnore pt) sp ep w of
((_,Left _):_) -> (doSound damcrs , mvPt 0.7)
(th@(p,Right wl):_) -> (doSound . fst $ hiteff [th] damcrs , rfl wl p)
_ -> (flameFlicker pt $ doSound damcrs , mvPt 0.98)
where
time = _ptTimer pt
doSound = soundContinue Flame (V2 x y) fireLoudS (Just 2)
sp@(V2 x y) = _ptPos pt
vel = _ptVel pt
ep = sp +.+ vel
mvPt speed = Just $ pt
{ _ptTimer = time - 1
, _ptPos = ep
, _ptCrIgnore = Nothing
, _ptVel = speed *.* vel }
damcrs = foldr (\cr -> fst . hiteff [(ep,Left cr)]) w $ IM.filter closeCrs $ _creatures w
closeCrs cr = dist ep (_crPos cr)
< _crRad cr + 10 - min 9 (max 0 (fromIntegral time - 80))
+ 5 * angleCoeff (angleVV (ep -.- _crPos cr) rotd)
angleCoeff x' = abs $ 1 - abs ( (x' * 2 - pi) / pi )
hiteff = _ptHitEff pt pt
rfl wl p = Just $ pt
{ _ptTimer = time - 1
, _ptPos = pOut p
, _ptVel = reflV wl
}
pOut p = p +.+ squashNormalizeV (sp -.- p)
reflV wall = (0.3 *.* reflectIn (uncurry (-.-) . swap $ _wlLine wall) vel )
+.+ (0.2 *.* vel)
makeFlamelet
:: Point2 -- ^ Position
-> Float -- ^ z position
-> Point2 -- ^ Velocity
-> Maybe Int -- ^ Ignore creature id
-> Float -- ^ Size
-> Int -- ^ Timer
-> World
-> World
makeFlamelet (V2 x y) z vel maycid size time w = w
& randGen .~ g
& instantParticles .:~ PtZ
{ _ptDraw = drawFlameletZ rot
, _ptUpdate = moveFlamelet
, _ptVel = vel
, _ptColor = red
, _ptPos = V2 x y
, _ptCrIgnore = maycid
, _ptWidth = size
, _ptTimer = time
, _ptHitEff = destroyOnImpact (doFlameDam 1) noEff
, _ptZ = z
}
where
(rot ,g) = randomR (0,3) $ _randGen w
drawFlameletZ
:: Float -- ^ Rotation
-> Particle
-> Picture
drawFlameletZ rot pt = pictures
[ setLayer 1 pic
, setLayer 3 piu
, setLayer 3 pi2
]
where
z = _ptZ pt
sp = _ptPos pt
vel = _ptVel pt
ep = sp +.+ vel
size = _ptWidth pt
siz2 = size + 0.2
time = _ptTimer pt
piu = setDepth (z + 25)
. uncurryV translate ep
. color (mixColors (fromIntegral (max 0 (time-2))) (10-fromIntegral time)
(V4 2 0 0 1) (V4 2 0 0 0)
)
. rotate (negate (rot - 0.1 * fromIntegral time))
. scale s1 s1
$ polygon (rectNSWE siz2 (-siz2) (-siz2) siz2)
pi2 = setDepth (z + 25)
. uncurryV translate ep
. color (mixColors (fromIntegral (max 0 (time-2))) (10-fromIntegral time)
(V4 2 1 0 0.5) (V4 0 0 0 0)
)
. rotate (negate (rot + 0.2 * fromIntegral time))
. scale s2 s2
$ polygon (rectNSWE siz2 (-siz2) (-siz2) siz2)
pic = setDepth (z + 20)
. uncurryV translate ep
. color (mixColors (fromIntegral (max 0 (time-2))) (10-fromIntegral time)
(V4 1 1 1 3) (V4 1 0 0 1)
)
. rotate (negate ( 0.1 * fromIntegral time + rot))
. scale (0.5* sc) (0.5 *sc)
$ polygon $ map toV2 [(-size,-size),(size,-size),(size,size),(-size,size)]
sc = (*) 2 $ log $ 1 + fromIntegral time / 20
s1 = (*) 2 $ log $ 2 + fromIntegral time / 40
s2 = 0.5 * (sc + s1)
{- Update of a flamelet.
Applies movement and attaches damage to nearby creatures. -}
moveFlamelet :: World -> Particle -> (World, Maybe Particle)
moveFlamelet w pt
| _ptTimer pt <= 0 = ( w, Nothing)
| otherwise = (flameFlicker pt damcrs, mvPt)
where
sp = _ptPos pt
vel = _ptVel pt
ep = sp +.+ vel
size = _ptWidth pt
mvPt = Just $ pt & ptTimer -~ 1
& ptPos .~ ep
& ptCrIgnore .~ Nothing
& ptVel .~ 0.8 *.* vel
damcrs = w & creatures %~ IM.map damifclose
isClose cr = dist ep (_crPos cr) < _crRad cr + size
damifclose cr
| isClose cr = cr & crState . crDamage .:~ Damage Flaming 3 sp ep ep NoDamageEffect
| otherwise = cr
-- | At writing the radius is half the size of the effect area
makeGasCloud
:: Point2 -- ^ Position
-> Point2 -- ^ Velocity
-> World
-> World
makeGasCloud pos vel w = w
& clouds .:~ theCloud
& randGen .~ g
where
theCloud = Cloud
{ _clPos = addZ 20 pos
, _clVel = addZ 0 vel
, _clPict = \_ -> setLayer 2 . setDepth 30 $ color (withAlpha 0.05 col)
$ circleSolid 20
, _clRad = 10
, _clAlt = 25
, _clTimer = 400
, _clType = GasCloud
, _clEffect = cloudPoisonDamage
}
(col, g) = runState (takeOne [green,yellow]) $ _randGen w
{- Attach poison cloud damage to creatures near cloud. -}
cloudPoisonDamage :: Cloud -> World -> World
cloudPoisonDamage c w = w & creatures %~ flip (foldr (IM.adjust doDam)) damagedCrs
where
damagedCrs = IM.keys $ IM.filter f $ creaturesNearPoint clpos w
f cr = dist3 (addZ 20 $ _crPos cr) (_clPos c) < _crRad cr + _clRad c + 10
doDam cr = cr & crState . crDamage .:~ Damage PoisonDam 1 clpos clpos clpos NoDamageEffect
clpos = stripZ $ _clPos c