Files
loop/src/Dodge/Update.hs
T

418 lines
14 KiB
Haskell

{-# LANGUAGE BangPatterns #-}
{- |
Module : Dodge.Update
Description : Simulation update
-}
module Dodge.Update ( updateUniverse ) where
import Dodge.Data
import Dodge.RadarBlip
import Dodge.Flare
import Dodge.Menu
import Dodge.CullBox
--import Dodge.Block
import Dodge.Distortion
import Dodge.SoundLogic
--import Dodge.Wall.Delete
import Dodge.Update.WallDamage
import Dodge.Machine.Destroy
--import Dodge.Menu
import Dodge.Base
import Dodge.Zone
import Dodge.Hammer
import Dodge.WallCreatureCollisions
import Dodge.Update.Camera
import Dodge.Inventory
import Sound.Data
import Geometry
import LensHelp
import FoldableHelp
--import qualified Data.Text as T
--import System.Random
import Data.List
import Data.Maybe
--import Data.Function
import qualified Data.IntMap.Strict as IM
import qualified Data.Map.Strict as M
import Control.Applicative
--import qualified Data.Set as S
--import Data.Monoid
--import System.Random
import qualified Streaming.Prelude as S
import SDL
--import Streaming
{- For most menus the only way to change the world is using event handling. -}
updateUniverse :: Universe -> Universe
updateUniverse u = case _menuLayers u of
(WaitScreen s i : _)
| i < 1 -> u & over uvWorld (dbArg _worldEvents)
| otherwise -> u & menuLayers %~ ( (WaitScreen s (i-1) :) . tail )
(OptionScreen {_scOptionFlag = GameOverOptions} : _) -> u & uvWorld %~
( updateParticles
. (radarBlips .~ [])
. updateIMl _props _pjUpdate
. updateLightSources
. updateClouds )
(_ : _) -> u
[] -> over uvWorld (functionalUpdate (_config u)) u
{- | The update step. -}
functionalUpdate :: Configuration -> World -> World
functionalUpdate cfig w = checkEndGame
-- . updateRandGen
. (mouseButtons . each .~ True) -- to determine if the mouse button is held
. (worldClock +~ 1)
. updateWorldSelect
. doRewind
. (hammers . each %~ moveHammerUp)
. updateDistortions
. updateCreatureSoundPositions
. ppEvents
. updateCamera cfig
. colCrsWalls cfig
. simpleCrSprings
. zoneCreatures
. updateIMl _doors _drMech
. updateDelayedEvents
. resetWorldEvents
. dbArg _worldEvents
. updateIMl _modifications _mdUpdate
. updateParticles
. updateRadarBlips
. updateFlares
. updateBeams
. updateIMl _props _pjUpdate
. updateLightSources
. updateClouds
. updateGusts
. zoneClouds
. updateMIM magnets _mgUpdate
. updateIMl' _terminals tmUpdate
. updateIMl _machines mcChooseUpdate
. updateIMl _creatures _crUpdate
-- creatures should be updated early so that crOldPos is set before any position change
. over creatures (fmap setOldPos)
. updateCreatureGroups
-- . updateBlocks
. updateWallDamages
. updateSeenWalls
-- . (youHammerPosition %~ moveHammerUp)
. updateTerminal
. updateRBList
. updateBounds cfig -- where should this go? next to update camera?
$ updateCloseObjects w
zoneClouds :: World -> World
zoneClouds w = w
& clZoning . znObjects .~ mempty
& clZoning %~ \zn ->
foldl' (flip $ updateZoning (:)) zn (_clouds w)
--runIdentity (S.fold_ (flip $ updateZoning (:)) (zn & znObjects .~ mempty) id (_clouds w))
updateWorldSelect :: World -> World
updateWorldSelect w = f . g $ case (w ^? mouseButtons . ix ButtonLeft, w ^? mouseButtons . ix ButtonRight) of
(Nothing ,Nothing) -> w
(Just False,Nothing) -> w & lLine . _1 .~ mwp
(Just True ,Nothing) -> w & lLine . _2 .~ mwp
(Nothing ,_) -> w
(Just False,_) -> w & rLine . _1 .~ mwp
(Just True ,_) -> w & rLine . _2 .~ mwp
where
mwp = mouseWorldPos w
f | ButtonLeft `M.member` _mouseButtons w = lSelect .~ mwp
| otherwise = id
g | ButtonRight `M.member` _mouseButtons w = rSelect .~ mwp
| otherwise = id
mcChooseUpdate :: Machine -> Machine -> World -> World
mcChooseUpdate mc mc'
| _mcHP mc > 0 = _mcUpdate mc mc'
| otherwise = destroyMachine mc
-- (machines %~ IM.delete (_mcID mc))
-- . deleteWallIDs (_mcWallIDs mc)
-- . _mcDeath mc mc'
tmUpdate :: Terminal -> World -> World
tmUpdate tm w = case w ^? terminals . ix (_tmID tm) . tmFutureLines . ix 0 of
Nothing -> w
Just tl | _tlPause tl > 0 -> w & pointTermParams . tmFutureLines . ix 0 . tlPause -~ 1
Just (TerminalLineDisplay _ f) -> w & pointTermParams %~
( ( tmFutureLines %~ tail )
. ( tmDisplayedLines .:~ f w )
)
Just (TerminalLineEffect _ eff) -> w
& pointTermParams . tmFutureLines %~ tail
& eff tm
Just (TerminalLineTerminalEffect _ eff) -> w
& pointTermParams . tmFutureLines %~ tail
& pointTermParams %~ eff
where
pointTermParams = terminals . ix (_tmID tm)
setOldPos :: Creature -> Creature
setOldPos cr = cr
& crOldPos .~ _crPos cr
& crOldDir .~ _crDir cr
-- hack
--updateRandGen :: World -> World
--updateRandGen = randGen %~ (snd . (uniform :: StdGen -> (Int,StdGen)))
doRewind :: World -> World
doRewind w = case _maybeWorld w of
Just' w' -> w' & timeFlow .~ RewindingLastFrame
Nothing' -> w & timeFlow .~ NormalTimeFlow
zoneCreatures :: World -> World
zoneCreatures w = w
& crZoning . znObjects .~ mempty
& crZoning %~ \zn ->
foldl' (flip $ updateZoning (\cr -> IM.insert (_crID cr) cr))
zn (_creatures w)
updateCreatureSoundPositions :: World -> World
updateCreatureSoundPositions w = M.foldlWithKey' insertSound w
. M.mapMaybeWithKey updateSound
$ _playingSounds w
where
insertSound w' k s = w' & toPlaySounds %~ M.insertWith (const id) k s
updateSound (CrMouth cid) s = case w ^? creatures . ix cid . crPos of
Just p -> Just s {_soundPos = p, _soundAngDist = Just (soundAngle p w,0)}
Nothing -> Just s {_soundTime = Just 0}
updateSound _ _ = Nothing
--updateIMr :: (World -> IM.IntMap a) -> (a -> a -> World -> World) -> World -> World
--updateIMr fim fup w = foldr (dbArg fup) w (fim w)
updateIMl :: (World -> IM.IntMap a) -> (a -> a -> World -> World) -> World -> World
updateIMl fim fup w = foldl' (flip $ dbArg fup) w (fim w)
updateIMl' :: (World -> IM.IntMap a) -> (a -> World -> World) -> World -> World
updateIMl' fim fup w = foldl' (flip fup) w (fim w)
-- | Note the explict use of record syntax. Using lens created a space leak.
resetWorldEvents :: World -> World
resetWorldEvents w = w {_worldEvents = id}
updateCreatureGroups :: World -> World
updateCreatureGroups w = w & creatureGroups %~
IM.mapMaybe (\cgp -> _crGroupUpdate cgp w cgp)
updateDistortions :: World -> World
updateDistortions = distortions %~ mapMaybe updateDistortion
updateLightSources :: World -> World
updateLightSources w = over tempLightSources f w
where
f = mapMaybe (\b -> _tlsUpdate b w b)
updateRadarBlips :: World -> World
updateRadarBlips = radarBlips %~ mapMaybe updateRadarBlip
updateFlares :: World -> World
updateFlares = flares %~ mapMaybe updateFlare
{- Apply internal particle updates, delete 'Nothing's. -}
updateParticles :: World -> World
updateParticles w = updateInstantParticles $ set particles (catMaybes ps) w'
where
(w',ps) = mapAccumR (\a b -> _ptUpdate b a b) w $ _particles w
updateBeams :: World -> World
updateBeams w = w
& newBeams .~ WorldBeams [] [] [] []
& beams .~ thebeams
& combineBeams thebeams
where
thebeams = _newBeams w
combineBeams :: WorldBeams -> World -> World
combineBeams wbeams w = w''
& beams . positronBeams .~ pbeams
& beams . electronBeams .~ ebeams
where
(w',pbeams) = mapAccumR (combineBeamBeams (_electronBeams wbeams)) w $ _positronBeams wbeams
(w'',ebeams) = mapAccumR (combineBeamBeams (_positronBeams wbeams)) w' $ _electronBeams wbeams
combineBeamBeams :: [Beam] -> World -> Beam -> (World,Beam)
combineBeamBeams bms w bm = case intersectBeamBeams bm bms of
(ps,Nothing) -> (w, bm & bmFirstPoints .~ ps)
(ps,Just a) -> (_beamCombine (_bmType bm) a w, bm & bmFirstPoints .~ ps)
-- intersect a beam with a list of beams.
-- returns the (reversed) travel of the beam up to maybe an intersection point
intersectBeamBeams :: Beam -> [Beam]
-> ([Point2], Maybe (Point2,(Point2,Point2,Beam),(Point2,Point2,Beam)))
intersectBeamBeams bm bms = f [] $ _bmPoints bm
where
f ps (x:y:ys) = case intersectSegBeams x y bms of
Just (z,a) -> (z:x:ps, Just (z,(x,y,bm),a))
Nothing -> f (x:ps) (y:ys)
f ps [x] = (x:ps, Nothing)
f _ _ = error "made an empty beam"
intersectSegBeams :: Point2 -> Point2 -> [Beam] -> Maybe (Point2,(Point2,Point2,Beam))
intersectSegBeams sp ep (bm:bms) = case intersectSegBeam sp ep bm of
Nothing -> intersectSegBeams sp ep bms
--Just a@(z,_) -> maybe (Just a) Just $ intersectSegBeams sp z bms
Just a@(z,_) -> intersectSegBeams sp z bms <|> Just a
intersectSegBeams _ _ _ = Nothing
intersectSegBeam :: Point2 -> Point2 -> Beam -> Maybe (Point2,(Point2,Point2,Beam))
intersectSegBeam sp ep bm = case intersectSegSegs' sp ep (_bmPoints bm) of
Nothing -> Nothing
Just (z,x,y) -> Just (z,(x,y,bm))
intersectSegSegs' :: Point2 -> Point2 -> [Point2] -> Maybe (Point2,Point2,Point2)
intersectSegSegs' sp ep (x:y:ys) = case intersectSegSeg sp ep x y of
--Just z -> maybe (Just (z,x,y)) Just $ intersectSegSegs' sp z (y:ys)
Just z -> intersectSegSegs' sp z (y:ys) <|> Just (z,x,y)
Nothing -> intersectSegSegs' sp ep (y:ys)
intersectSegSegs' _ _ _ = Nothing
--intersectSegSegs :: Point2 -> Point2 -> [Point2] -> Maybe Point2
--intersectSegSegs sp ep (x:y:ys) = case intersectSegSeg sp ep x y of
-- Just z -> maybe (Just z) Just $ intersectSegSegs sp z (y:ys)
-- Nothing -> intersectSegSegs sp ep (y:ys)
--intersectSegSegs _ _ _ = Nothing
--
--intersectSegSegss :: Point2 -> Point2 -> [[Point2]] -> Maybe Point2
--intersectSegSegss sp ep (as:ass) = case intersectSegSegs sp ep as of
-- Just z -> maybe (Just z) Just $ intersectSegSegss sp z ass
-- Nothing -> intersectSegSegss sp ep ass
--intersectSegSegss _ _ _ = Nothing
--
--intersectSegsSegss :: [Point2] -> [[Point2]] -> Maybe Point2
--intersectSegsSegss (x:y:ys) ass = maybe
-- (intersectSegsSegss (y:ys) ass)
-- Just
-- (intersectSegSegss x y ass)
--intersectSegsSegss _ _ = Nothing
updateInstantParticles :: World -> World
updateInstantParticles w = case _instantParticles w of
[] -> w
ps -> let (w',ps') = mapAccumR (\a b -> _ptUpdate b a b) (w {_instantParticles=[]}) ps
in updateInstantParticles $ w' & particles .++~ catMaybes ps'
updateMIM :: ASetter' World (IM.IntMap a) -> (a -> a -> Maybe a) -> World -> World
updateMIM f up = f %~ IM.mapMaybe (dbArg up)
-- Note that this updates the randgen
--updateCreatures :: World -> World
--updateCreatures w = appEndo f $ w
-- & creatures .~ IM.mapMaybe id m
-- & randGen .~ newg
-- where
-- ((f,newg),m) = mapAccumR crUp (Endo id,_randGen w) (_creatures w)
-- crUp (f',g) cr = ((f' <> f'',g'), cr')
-- where
-- (f'',cr') = _crUpdate cr cr (w & randGen .~ g)
-- (_,g') = genWord8 g
ppEvents :: World -> World
ppEvents w = IM.foldl' (flip $ \pp -> _ppEvent pp pp) w $ _pressPlates w
updateSeenWalls :: World -> World
updateSeenWalls w = runIdentity $ S.fold_ markWallSeen w id (S.map (_wlID . snd) $ allVisibleWalls w)
-- where
-- f w' !i -- = w' & walls . ix i . wlSeen .~ True
-- = w' { _walls = IM.adjust mw i $ _walls w' }
-- mw wl = wl {_wlSeen = True}
markWallSeen :: World -> Int -> World
markWallSeen !w !i = w { _walls = IM.adjust markSeen i $ _walls w }
markSeen :: Wall -> Wall
markSeen wl = wl {_wlSeen = True}
checkEndGame :: World -> World
checkEndGame w = case _deathDelay w of
Just x | x < 0 -> w & sideEffects %~ ( . (menuLayers .~ [gameOverMenu]))
& deathDelay .~ Nothing
Just _ -> w & deathDelay . _Just -~ 1
_ | _crHP (you w) < 1 -> w & deathDelay ?~ 50
_ -> w
updateGusts :: World -> World
updateGusts w = w
& gusts %~ IM.mapMaybe (mvGust w)
mvGust :: World -> Gust -> Maybe Gust
mvGust _ gu
| _guTime gu < 0 = Nothing
| otherwise = Just $ gu
& guPos .+.+~ _guVel gu
& guTime -~ 1
updateClouds :: World -> World
updateClouds w = w' & clouds .~ catMaybes mclouds
where
-- cls = _clouds w
(w',mclouds) = mapAccumR updateCloud w (_clouds w)
updateCloud :: World -> Cloud -> (World,Maybe Cloud)
updateCloud w c
| _clTimer c < 1 = (w, Nothing)
| otherwise = (_clEffect c c w, Just $ c
& clPos .~ finalPos
& clVel .~ finalVel
& clTimer -~ 1
)
where
newVel@(V3 _ _ nvz) = (0.95 *.*.* springVels) +.+.+ V3 0 0 (0.001 * vertVel)
newVel2 = stripZ newVel
vertVel = _clAlt c - opz
springVels = runIdentity $ S.fold_ (clClSpringVel c) (_clVel c) id (clsNearPoint oldPos2 w)
oldPos@(V3 _ _ opz) = _clPos c
oldPos2 = stripZ oldPos
newPos@(V3 _ _ npz) = oldPos +.+.+ newVel
newPos2 = stripZ newPos
hitWl = bouncePoint (const True) 1 oldPos2 newPos2 w
finalPos = addZ (min 74 npz) $ maybe newPos2 fst hitWl
-- allowing clouds at/above height 75 causes graphical glitches 22.05.23
finalVel = addZ nvz $ maybe newVel2 snd hitWl
clClSpringVel :: Cloud -> Point3 -> Cloud -> Point3
clClSpringVel a v b
| dist3 pa pb < radDist = v +.+.+ 0.1 *.*.* normalizeV3 (pa -.-.- pb)
| otherwise = v
where
pa = _clPos a
pb = _clPos b
radDist = (_clRad a + _clRad b) / 2
simpleCrSprings :: World -> World
simpleCrSprings w = IM.foldl' (flip crSpring) w $ _creatures w
-- note that this may in rare cases not push creatures away from each other
crSpring :: Creature -> World -> World
crSpring c w = runIdentity $ S.fold_ (flip $ crCrSpring c) w id cs
where
cs = crsNearPoint (_crPos c) w
crCrSpring :: Creature -> Creature -> World -> World
crCrSpring c1 c2
| id1 == id2 = id
| vec == V2 0 0 = id
| diff >= comRad = id
| otherwise = creatures %~
( over (ix id1 . crPos) (+.+ overlap1)
. over (ix id2 . crPos) (-.- overlap2)
)
where
id1 = _crID c1
id2 = _crID c2
vec = _crPos c1 -.- _crPos c2
diff = magV vec
comRad = _crRad c1 + _crRad c2
overlap1 = ((comRad - diff) * _crMass c2 * 0.5 / massT) *.* errorNormalizeV 55 vec
overlap2 = ((comRad - diff) * _crMass c1 * 0.5 / massT) *.* errorNormalizeV 56 vec
massT = _crMass c1 + _crMass c2
updateDelayedEvents :: World -> World
updateDelayedEvents w = let (neww,newde) = mapAccumR f w (_delayedEvents w)
in neww & delayedEvents .~ catMaybes newde
where
f w' (i,g)
| i <= 0 = (g w', Nothing)
| otherwise = (w', Just (i-1,g))