Files
loop/src/Dodge/Update.hs
T
2022-07-25 00:56:55 +01:00

509 lines
18 KiB
Haskell

{-# LANGUAGE BangPatterns #-}
{- |
Module : Dodge.Update
Description : Simulation update
-}
module Dodge.Update ( updateUniverse ) where
import Dodge.TmTm
import Color
import Dodge.DrWdWd
import Dodge.TractorBeam.Update
import Dodge.Prop.Update
import Dodge.LinearShockwave.Update
import Dodge.Shockwave.Update
import Dodge.Tesla.Arc
import Dodge.Laser.Update
import Dodge.PressPlate
import Dodge.ModificationEffect
import Dodge.CrGroupUpdate
import Dodge.Zoning.Cloud
import Dodge.Zoning.Creature
import Dodge.WorldEffect
import Dodge.Data
import Dodge.Beam
import Dodge.LightSource.Update
import Dodge.Projectile.Update
import Dodge.Creature.Update
import Dodge.RadarSweep
import Dodge.PosEvent
import Dodge.Spark
import Dodge.EnergyBall
import Dodge.Flame
import Dodge.Bullet
import Dodge.Update.Cloud
import Dodge.Machine.Update
import Dodge.RadarBlip
import Dodge.Flare
import Dodge.Menu
import Dodge.CullBox
import Dodge.Distortion
import Dodge.SoundLogic
import Dodge.Update.WallDamage
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 Data.List
import Data.Maybe
import qualified IntMapHelp as IM
import qualified Data.Map.Strict as M
import Control.Applicative
--import qualified Streaming.Prelude as S
import SDL
{- 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 doWorldEvents
| otherwise -> u & menuLayers %~ ( (WaitScreen s (i-1) :) . tail )
(OptionScreen {_scOptionFlag = GameOverOptions} : _) -> u & uvWorld %~
( --updateParticles
(radarBlips .~ [])
-- . updateIMl _props _pjUpdate
. updateLightSources
. updateClouds )
(_ : _) -> u
[] -> functionalUpdate u
{- | The update step. -}
functionalUpdate :: Universe -> Universe
functionalUpdate w = checkEndGame
-- . updateRandGen
. over uvWorld (mouseButtons . each .~ True) -- to determine if the mouse button is held
. over uvWorld (worldClock +~ 1)
. over uvWorld updateWorldSelect
. over uvWorld doRewind
. over uvWorld (hammers . each %~ moveHammerUp)
. over uvWorld updateDistortions
. over uvWorld updateCreatureSoundPositions
. over uvWorld ppEvents
. updateCamera
. colCrsWalls
. over uvWorld simpleCrSprings
. over uvWorld zoneCreatures
. over uvWorld (updateIMl _doors (doDrWdWd . _drMech) )
. over uvWorld doWorldEvents
. over uvWorld updateDelayedEvents
. over uvWorld (updateIMl _modifications (doModificationEffect . _mdUpdate) )
. over uvWorld updateSparks
. over uvWorld updateRadarSweeps
. over uvWorld updatePosEvents
. over uvWorld updateFlames
. over uvWorld updateShockwaves
. over uvWorld updateEnergyBalls
-- . over uvWorld updateParticles
. over uvWorld updateBullets
. over uvWorld updateRadarBlips
. over uvWorld updateFlares
. over uvWorld updateBeams
. over uvWorld updateLasers
. over uvWorld updateTeslaArcs
. over uvWorld updateTractorBeams
. over uvWorld (updateIMl' _linearShockwaves updateLinearShockwave)
. over uvWorld (updateIMl' _props updateProp )
. over uvWorld (updateIMl' _projectiles updateProjectile)
. over uvWorld updateLightSources
. over uvWorld updateClouds
. over uvWorld updateGusts
. over uvWorld zoneClouds
. over uvWorld (updateMIM magnets _mgUpdate )
. over uvWorld (updateIMl' _terminals tmUpdate )
-- . updateIMl _machines mcChooseUpdate
. over uvWorld (updateIMl' _machines updateMachine )
. over uvWorld (updateIMl' _creatures updateCreature )
-- creatures should be updated early so that crOldPos is set before any position change
. over (uvWorld . creatures) (fmap setOldPos)
. over uvWorld updateCreatureGroups
. over uvWorld updateWallDamages
. over uvWorld updateSeenWalls
. over uvWorld updateTerminal
. over uvWorld updateRBList
. updateBounds -- where should this go? next to update camera?
$ over uvWorld updateCloseObjects w
doWorldEvents :: World -> World
doWorldEvents w = foldr doWorldEffect (w & worldEvents .~ []) (_worldEvents w)
updateLasers :: World -> World
updateLasers w = w'
& lasers .~ []
& lasersToDraw .~ ls
where
(w',ls) = mapAccumR updateLaser w (_lasers w)
zoneClouds :: World -> World
zoneClouds w = w
& clZoning .~ mempty
& clZoning %~ \zn ->
foldl' (flip zoneCloud) 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
displayTerminalLineString :: TerminalLineString -> World -> (String,Color)
displayTerminalLineString tls = case tls of
TerminalLineConst str col -> const (str,col)
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 .:~ displayTerminalLineString f w )
)
Just (TerminalLineEffect _ eff) -> w
& pointTermParams . tmFutureLines %~ tail
& doTmWdWd eff tm
Just (TerminalLineTerminalEffect _ eff) -> w
& pointTermParams . tmFutureLines %~ tail
& pointTermParams %~ doTmTm 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 .~ mempty
& crZoning %~ \zn -> foldl' (flip zoneCreature) zn (_creatures w)
-- 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)
updateCreatureGroups :: World -> World
updateCreatureGroups w = w & creatureGroups %~
IM.mapMaybe (\cgp -> doCrGroupUpdate (_crGroupUpdate cgp) w cgp)
updateDistortions :: World -> World
updateDistortions = distortions %~ mapMaybe updateDistortion
updateLightSources :: World -> World
updateLightSources = tempLightSources %~ f
where
f = mapMaybe (\b -> updateTempLightSource (_tlsUpdate b) b)
updateRadarBlips :: World -> World
updateRadarBlips = radarBlips %~ mapMaybe updateRadarBlip
updateFlares :: World -> World
updateFlares = flares %~ mapMaybe updateFlare
updateTeslaArcs :: World -> World
updateTeslaArcs w = w' & teslaArcs .~ catMaybes newtas
where
(w',newtas) = mapAccumR moveTeslaArc w $ _teslaArcs w
updateTractorBeams :: World -> World
updateTractorBeams w = w' & tractorBeams .~ catMaybes newtas
where
(w',newtas) = mapAccumR updateTractorBeam w $ _tractorBeams w
{- Apply internal particle updates, delete 'Nothing's. -}
updateBullets :: World -> World
updateBullets w = updateInstantBullets $ set bullets (catMaybes ps) w'
where
(w',ps) = mapAccumR updateBullet w $ _bullets w
updateShockwaves :: World -> World
updateShockwaves w = w' & shockwaves .~ catMaybes newflames
where
(w',newflames) = mapAccumR updateShockwave w $ _shockwaves w
updateFlames :: World -> World
updateFlames w = w' & flames .~ catMaybes newflames
where
(w',newflames) = mapAccumR moveFlame w $ _flames w
updateEnergyBalls :: World -> World
updateEnergyBalls w = w' & energyBalls .~ catMaybes newebs
where
(w',newebs) = mapAccumR moveEnergyBall w $ _energyBalls w
updateRadarSweeps :: World -> World
updateRadarSweeps w = w' & radarSweeps .~ catMaybes newradarSweeps
where
(w',newradarSweeps) = mapAccumR updateRadarSweep w $ _radarSweeps w
updateSparks :: World -> World
updateSparks w = w' & sparks .~ catMaybes newsparks
where
(w',newsparks) = mapAccumR moveSpark w $ _sparks w
updatePosEvents :: World -> World
updatePosEvents w = w' & posEvents .~ catMaybes newposEvents
where
(w',newposEvents) = mapAccumR updatePosEvent w $ _posEvents w
{- 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) -> (doBeamCombine (_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
updateInstantBullets :: World -> World
updateInstantBullets w = case _instantBullets w of
[] -> w
ps -> let (w',ps') = mapAccumR updateBullet (w {_instantBullets=[]}) ps
in updateInstantBullets $ w' & bullets .++~ catMaybes ps'
--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 -> doPressPlateEvent (_ppEvent pp) pp) w $ _pressPlates w
updateSeenWalls :: World -> World
updateSeenWalls w = foldl' markWallSeen w (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 :: Universe -> Universe
checkEndGame uv = case _deathDelay w of
Just x | x < 0 -> uv & menuLayers .~ [gameOverMenu]
& uvWorld . deathDelay .~ Nothing
Just _ -> uv & uvWorld . deathDelay . _Just -~ 1
_ | _crHP (you w) < 1 -> uv & uvWorld . deathDelay ?~ 50
_ -> uv
where
w = _uvWorld uv
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)
cloudEffect :: Cloud -> World -> World
cloudEffect cl = case _clType cl of
GasCloud -> cloudPoisonDamage cl
SmokeCloud -> id
updateCloud :: World -> Cloud -> (World,Maybe Cloud)
updateCloud w c
| _clTimer c < 1 = (w, Nothing)
| otherwise = (cloudEffect 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) (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 = foldl' (flip $ crCrSpring c) w 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 = (w' & worldEvents .:~ g, Nothing)
| otherwise = (w', Just (i-1,g))