Files
loop/src/Dodge/Update.hs
T
2021-12-07 00:39:51 +00:00

281 lines
9.0 KiB
Haskell

{- |
Module : Dodge.Update
Description : Simulation update
-}
module Dodge.Update ( updateUniverse ) where
import Dodge.Data
import Dodge.Menu
import Dodge.Hammer
import Dodge.Block
import Dodge.Distortion
import Dodge.SoundLogic
--import Dodge.Menu
import Dodge.Base
import Dodge.Zone
import Dodge.WallCreatureCollisions
import Dodge.Update.Camera
import Dodge.Inventory
import Sound.Data
import Geometry
import Data.List
import Data.Maybe
import Data.Function
import qualified Data.IntMap.Strict as IM
import qualified Data.Map.Strict as M
--import qualified Data.Set as S
import Control.Lens
import Data.Monoid
import System.Random
{- 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
. updateIMl _props _pjUpdate
. updateLightSources
. updateClouds )
(_ : _) -> u
[] -> over uvWorld (functionalUpdate (_config u)) u
{- | The update step. -}
functionalUpdate :: Configuration -> World -> World
functionalUpdate cfig w = checkEndGame
. (worldClock +~ 1)
. updateDistortions
. updateCreatureSoundPositions
. ppEvents
. updateCamera cfig
. colCrsWalls cfig
. simpleCrSprings
. zoneCreatures
. updateIMl _doors _drMech
. updateDelayedEvents
. resetWorldEvents
. dbArg _worldEvents
. updateIMl _modifications _mdUpdate
. updateParticles
. updateIMl _props _pjUpdate
. updateLightSources
. updateClouds
. zoneClouds
. updateIMl _machines _mcUpdate
. updateCreatures
. updateCreatureGroups
. updateBlocks
. updateSeenWalls
. (lSelHammerPosition %~ moveHammerUp)
$ updateCloseObjects w
where
--updatedLightSources = mapMaybe (\b -> _tlsUpdate b w b) $ _tempLightSources w
zoneClouds = set (cloudsZone . znObjects) (foldl' (flip cloudInZone) IM.empty (_clouds w))
cloudInZone cl = insertInZoneWith x y (++) [cl]
where
(x,y) = cloudZoneOfPoint $ stripZ $ _clPos cl
zoneCreatures :: World -> World
zoneCreatures w = w
& creaturesZone . znObjects .~
IM.foldl' (flip creatureInZone) IM.empty (_creatures w)
where
creatureInZone cr = insertIMInZone x y cid cr
where
(x,y) = crZoneOfPoint $ _crPos cr
cid = _crID cr
updateCreatureSoundPositions :: World -> World
updateCreatureSoundPositions w = M.foldrWithKey insertSound w
. M.mapMaybeWithKey updateSound
$ _playingSounds w
where
insertSound k s = 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)
updateBlocks :: World -> World
updateBlocks w = foldr f w $ _blocks w
where
f bl w' = case _blHPs bl of
(x:_:_) | x < 1 -> splinterBlock bl w'
[x] | x < 1 -> destroyBlock bl w'
_ -> 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)
{- 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
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' ++)
updateMachines :: World -> World
updateMachines w = foldr f w (_machines w)
where
f mc = _mcUpdate mc mc
-- 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
updateDoors :: World -> World
updateDoors w = IM.foldl' (\w' dr -> _drMech dr dr w') w (_doors w)
ppEvents :: World -> World
ppEvents w = IM.foldl' (flip $ \pp -> _ppEvent pp pp) w $ _pressPlates w
updateSeenWalls :: World -> World
updateSeenWalls w = foldl' markWallSeen w wallsToUpdate
where
vPos = _cameraViewFrom w
wallsToUpdate = concatMap (\p -> visibleWalls vPos (vPos +.+p) $ wallsAlongLine vPos (vPos +.+ p) w)
$ nRays 20
markWallSeen :: World -> Wall -> World
markWallSeen w wl = w { _walls = IM.adjust mw (_wlID wl) $ _walls w }
where
mw wl' = wl' {_wlSeen = True}
checkEndGame :: World -> World
checkEndGame w
| _crHP (you w) < 1 = w & sideEffects %~ ( . (menuLayers .~ [gameOverMenu]))
| otherwise = w
updateClouds :: World -> World
updateClouds w = w' & clouds .~ catMaybes mclouds
where
(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 = foldl' (clClSpringVel c) (_clVel c) (cloudsNearPoint oldPos2 w)
oldPos@(V3 _ _ opz) = _clPos c
oldPos2 = stripZ oldPos
newPos@(V3 _ _ npz) = oldPos +.+.+ newVel
newPos2 = stripZ newPos
-- the following only tests for the first collision with a wall
hitWl = collidePointAnyWallsReflect oldPos2 newPos2 $ wallsNearPoint newPos2 w
finalPos = addZ npz $ maybe newPos2 fst hitWl
finalVel = addZ nvz $ maybe newVel2 snd hitWl
--updateCloud :: World -> Cloud -> Maybe Cloud
--updateCloud w c
-- | _clTimer c < 1 = Nothing
-- | otherwise = Just $ c
-- & clPos .~ finalPos
-- & clVel .~ finalVel
-- & clTimer -~ 1
-- where
-- newVel = 0.95 *.* springVels
-- springVels = foldl' (clClSpringVel c) (_clVel c) (cloudsNearPoint oldPos w)
-- oldPos = _clPos c
-- newPos = oldPos +.+ newVel
-- hitWl = collideCircWalls' oldPos newPos 5 $ wallsNearPoint newPos w
-- finalPos = maybe newPos fst hitWl
-- finalVel = maybe newVel 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
crSpring :: Creature -> World -> World
crSpring c w = IM.foldl' (flip $ crCrSpring c) w cs
where
cs = creaturesNearPoint (_crPos c) w
crCrSpring :: Creature -> Creature -> World -> World
crCrSpring c1 c2 w
| id1 == id2 = w
| vec == V2 0 0 = w
| diff >= comRad = w
| otherwise = over creatures
( over (ix id1 . crPos) (+.+ overlap1)
. over (ix id2 . crPos) (-.- overlap2)
) w
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
{- Finds the visible walls from a point to another point. -}
visibleWalls :: Point2 -> Point2 -> IM.IntMap Wall -> [Wall]
visibleWalls p1 p2 ws
= takeUntil theTest
. map snd
. sortOn (dist p1 . fromJust . fst)
. filter (isJust . fst)
. map f
$ IM.elems ws
where
f wl = (uncurry intersectSegSeg (_wlLine wl) p1 p2, wl)
theTest wl = _wlOpacity wl /= Opaque
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))