{- | Module : Dodge.Update Description : Simulation update -} module Dodge.Update (update) where import Dodge.Data 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 Geometry.Vector3D 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.Lens import Data.Monoid import System.Random update :: World -> World update = (frameClock +~ 1) . functionalUpdate {- | The update step. For most menus the only way to change the world is using event handling. -} functionalUpdate :: World -> World functionalUpdate w = case _menuLayers w of (TerminalScreen t xs:mls) -> w & menuLayers .~ (TerminalScreen (max (t-1) 0) xs:mls) (WaitScreen s i : _) | i < 1 -> w & dbArg _worldEvents | otherwise -> w & menuLayers %~ ( (WaitScreen s (i-1) :) . tail ) (OptionScreen {_scOptionFlag = GameOverOptions} : _) -> updateParticles . updateProjectiles . updateLightSources $ updateClouds w (_ : _) -> w [] -> checkEndGame . updateDistortions . updateCreatureSoundPositions . ppEvents . updateCamera . colCrsWalls -- . ifConfigWallRotate . simpleCrSprings . zoneCreatures . updateDoors . resetWorldEvents . dbArg _worldEvents . updateModifications . updateParticles . updateProjectiles . updateLightSources . updateClouds . zoneClouds . updateMachines . updateCreatures . updateCreatureGroups . updateBlocks . updateSeenWalls $ updateCloseObjects w where --updatedLightSources = mapMaybe (\b -> _tlsUpdate b w b) $ _tempLightSources w zoneCreatures = set (creaturesZone . znObjects) (IM.foldl' (flip creatureInZone) IM.empty (_creatures w)) creatureInZone cr = insertIMInZone x y cid cr where (x,y) = crZoneOfPoint $ _crPos cr cid = _crID cr zoneClouds = set (cloudsZone . znObjects) (foldl' (flip cloudInZone) IM.empty (_clouds w)) cloudInZone cl = insertInZoneWith x y (++) [cl] where (x,y) = cloudZoneOfPoint $ stripZ $ _clPos cl 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 updateModifications :: World -> World updateModifications w = foldr (dbArg _mdUpdate) w (_modifications 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) updateProjectiles :: World -> World updateProjectiles w = IM.foldl' (flip $ dbArg _pjUpdate) w $ _props 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 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 = haltSound $ w {_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