Files
loop/src/Dodge/Update/Camera.hs
T

268 lines
9.3 KiB
Haskell

{-# LANGUAGE LambdaCase #-}
{- Functions controlling the movement of the screen camera:
'_cameraCenter', '_cameraZoom', _cameraRot';
and the position that the character sees from: '_cameraViewFrom'. -}
module Dodge.Update.Camera (
updateCamera,
) where
import Linear.V3
import Dodge.Creature.Radius
import Bound
import Control.Monad
import Data.Foldable
import qualified Data.Map.Strict as M
import Data.Maybe
import Dodge.Base
import Dodge.Creature.Test
import Dodge.Data.Config
import Dodge.Data.World
import Dodge.SmoothScroll
import Dodge.Viewpoints
import Dodge.WASD
import Dodge.Zoning.Wall
import Geometry
import LensHelp
import SDL (MouseButton (..))
{- Update the screen camera rotation and position, including any in rold scope/remote camera modifiers;
update where your avatar's view is from. -}
updateCamera :: Config -> World -> World
updateCamera cfig w = case w ^. wCam . camControl of
CamInGame{} -> updateInGameCamera cfig w
CamFloat -> updateFloatingCamera cfig w
updateFloatingCamera :: Config -> World -> World
updateFloatingCamera cfig w =
w
& updateBounds cfig
& wCam %~ setViewDistance cfig
& wCam %~ translateFloatingCamera theinput
& wCam %~ translateFloatingCameraKeys theinput
& wCam %~ zoomFloatingCamera theinput
where
theinput = w ^. input
translateFloatingCamera :: Input -> Camera -> Camera
translateFloatingCamera theinput cam = fromMaybe cam $ do
lpresstime <- theinput ^. mouseButtons . at ButtonLeft
rpresstime <- theinput ^. mouseButtons . at ButtonRight
if lpresstime > rpresstime
then do
hpos <- theinput ^? heldPos . ix ButtonLeft
let thetran =
screenToWorldPos cam hpos
-.- screenToWorldPos cam (theinput ^. mousePos)
return $
cam & camCenter +~ thetran
& camViewFrom +~ thetran
else do
a <- theinput ^. heldPos . at SDL.ButtonRight
return $ cam & camRot -~ angleBetween (theinput ^. mousePos) a
translateFloatingCameraKeys :: Input -> Camera -> Camera
translateFloatingCameraKeys theinput cam =
cam
& camCenter -~ thetran
& camViewFrom -~ thetran
where
thetran = rotateV (cam ^. camRot) $ negate 10 *.* wasdDir theinput
zoomFloatingCamera :: Input -> Camera -> Camera
zoomFloatingCamera theinput
| ButtonRight `M.member` (theinput ^. mouseButtons) =
camZoom *~ ((39 / 40) ^^ negate (getSmoothScrollValue theinput))
| otherwise = id
-- the 39/40 is taken from zoomSpeed
updateInGameCamera :: Config -> World -> World
updateInGameCamera cfig w =
w
& updateBounds cfig
& wCam %~ moveZoomCamera cfig (w ^. input) (you w) w
& rotateCamera cfig
& over wCam (setViewDistance cfig) -- I think this should be updated after the zoom?
moveZoomCamera :: Config -> Input -> Creature -> World -> Camera -> Camera
moveZoomCamera cfig theinput cr w campos =
campos
& camCenter .~ fromMaybe (_crPos cr +.+ offset) mremotepos
& camViewFrom .~ fromMaybe (_crPos cr) mremotepos
& camZoom .~ newzoom
& camDefaultZoom .~ newDefaultZoom
& camItemZoom .~ newItemZoom
where
mremotepos = do
i <- cr ^? crManipulation . manObject . imSelectedItem
itid <- cr ^? crInv . ix i
j <- w ^? cWorld . lWorld . items . ix itid . itUse . uaParams . apProjectiles . ix 0
guard $ Just REMOTESCREEN == w ^? cWorld . lWorld . items . ix itid . itType . ibtAttach
w ^? cWorld . lWorld . projectiles . ix j . pjPos . _xy
docamrot = rotateV (campos ^. camRot)
offset = fromMaybe noscopeoffset $ do
guard (SDL.ButtonRight `M.member` _mouseButtons theinput)
i <- cr ^? crManipulation . manObject . imSelectedItem
itid <- cr ^? crInv . ix i
fmap docamrot (w ^? cWorld . lWorld . items . ix itid . itUse . uScope . opticPos)
noscopeoffset =
docamrot $
((newzoom - newDefaultZoom) / (newDefaultZoom * newzoom)) *.* _mousePos theinput
newzoom = fromMaybe (newDefaultZoom * newItemZoom) $ do
i <- cr ^? crManipulation . manObject . imSelectedItem
itid <- cr ^? crInv . ix i
w ^? cWorld . lWorld . items . ix itid . itUse . uScope . opticZoom
idealDefaultZoom = clipZoom wallZoom
newDefaultZoom = fromMaybe (changeZoom (campos ^. camDefaultZoom) idealDefaultZoom) $ do
i <- cr ^? crManipulation . manObject . imSelectedItem
itid <- cr ^? crInv . ix i
w ^? cWorld . lWorld . items . ix itid . itUse . uScope . opticZoom
idealItemZoom = fromMaybe 1 $ do
guard $ crIsAiming cr
i <- cr ^? crManipulation . manObject . imSelectedItem
itid <- cr ^? crInv . ix i
getAimZoom <$> (w ^? cWorld . lWorld . items . ix itid)
newItemZoom = changeZoom (campos ^. camItemZoom) idealItemZoom
changeZoom curZoom idealZoom
| curZoom > idealZoom + 0.01 = ((zoomOutSpeed -1) * curZoom + idealZoom) / zoomOutSpeed
| curZoom < idealZoom - 0.01 = ((zoomInSpeed -1) * curZoom + idealZoom) / zoomInSpeed
| otherwise = idealZoom
wallZoom = min4 (f (campos ^. camBoundDist))
f (a,b,c,d) = (a+bordersize,b-bordersize,c+bordersize,d-bordersize)
bordersize = 20
min4 (a, b, c, d) = minimum [hh / maxd a, hh / maxd (- b), hw / maxd c, hw / maxd (- d)]
maxd = max distFromEqmnt
distFromEqmnt = viewDistanceFromItems cr
--distFromEqmnt = foldl' max 1 $ IM.mapMaybe (_eeViewDist . _uequipEffect . _itUse) $ getCrEquipment cr
hw = halfWidth cfig
hh = halfHeight cfig
-- these speeds are inverted, larger means slower
zoomInSpeed = 25
zoomOutSpeed = 15
getAimZoom :: Item -> Float
getAimZoom itm = case itm ^. itType of
HELD hit -> heldAimZoom hit
LASER -> 1.5
_ -> 1
heldAimZoom :: HeldItemType -> Float
heldAimZoom = \case
BANGSTICK{} -> 1
REWINDER -> 1
TIMESTOPPER -> 1
TIMESCROLLER -> 1
PISTOL -> 1
MACHINEPISTOL -> 1
AUTOPISTOL -> 1
SMG -> 1
BANGCONE -> 1
BLUNDERBUSS -> 1.5
GRAPECANNON{} -> 1.5
MINIGUNX{} -> 1.5
VOLLEYGUN{} -> 1.5
RIFLE -> 1.5
ALTERIFLE -> 1.5
AUTORIFLE -> 1.5
BURSTRIFLE -> 1.5
BANGROD -> 1.5
ELEPHANTGUN -> 1.5
AMR -> 1.5
AUTOAMR -> 1.5
SNIPERRIFLE -> 0.5
FLAMESPITTER -> 1
FLAMETHROWER -> 1.5
FLAMETORRENT -> 1
FLAMEWALL -> 1
BLOWTORCH -> 1
SPARKGUN -> 1
TESLAGUN -> 1.5
TRACTORGUN -> 1.5
RLAUNCHER -> 1.5
RLAUNCHERX{} -> 1.5
GLAUNCHER -> 1.5
POISONSPRAYER -> 1
SHATTERGUN -> 1
TORCH -> 1
FLATSHIELD -> 1
KEYCARD {} -> 1
BLINKER -> 1
BLINKERUNSAFE -> 1
-- for example, this should be 400 or so when you have an unsafe blink gun
-- capable of firing, should be expanded when you have a radar of some sort, etc
viewDistanceFromItems :: Creature -> Float
viewDistanceFromItems _ = 1
rotateCamera :: Config -> World -> World
rotateCamera cfig w
| MouseGameRotate <- w ^. input . mouseContext
, Just rotation <-
angleBetween (w ^. input . mousePos)
<$> (w ^. input . heldPos . at SDL.ButtonRight) =
w & wCam . camRot -~ rotation
| _gameplay_rotate_to_wall cfig
, isNothing $ w ^? input . mouseButtons . ix SDL.ButtonRight =
rotateToOverlappingWall w
| otherwise = w
rotateToOverlappingWall :: World -> World
rotateToOverlappingWall w =
maybe
id
(doWallRotate . snd)
(overlapCircWallsClosest p (crRad (cr ^. crType) + 10) (filter _wlRotateTo $ wlsNearPoint p w))
w
where
cr = you w
p = _crPos (you w)
doWallRotate :: Wall -> World -> World
doWallRotate wl w
| b - b' > 0.01 = w & wCam . camRot +~ 0.01
| b - b' < negate 0.01 = w & wCam . camRot -~ 0.01
| otherwise = w
where
a = argV (uncurry (-.-) $ _wlLine wl) - w ^. wCam . camRot
b = a * (4 / pi) -- for 8 way cardinal orientation wrt wall
b' = fromIntegral (round b :: Int)
clipZoom ::
-- | Furthest viewable distance
Float ->
Float
clipZoom = min 20 . max 0.2
setViewDistance :: Config -> Camera -> Camera
setViewDistance cfig w =
w & camViewDistance
.~ sqrt (halfWidth cfig ** 2 + halfHeight cfig ** 2) / (w ^. camZoom)
farWallDistDirection :: Point2 -> World -> Maybe (Float, Float, Float, Float)
farWallDistDirection p w = boundPoints $ map f $ getViewpoints p (_cWorld w)
where
f q = (rotateV (negate (w ^. wCam . camRot)) . (-.- p)) (foldl' findPoint q (wls q))
wls q = filter wlIsOpaque $ wlsNearSeg p q w
findPoint q = fromMaybe q . uncurry (intersectSegSeg p q) . _wlLine
findBoundDists :: Config -> World -> (Float, Float, Float, Float)
findBoundDists cfig w
| debugOn Bound_box_screen cfig = (hh, - hh, hw, - hw)
| otherwise = fromMaybe (0, 0, 0, 0) $ farWallDistDirection (w ^. wCam . camViewFrom) w
where
hw = halfWidth cfig
hh = halfHeight cfig
-- this probably doesn't need to be updated every frame,
-- though I'm not sure how often it should be updated
updateBounds :: Config -> World -> World
updateBounds cfig w = case (w ^. cWorld . cClock) `mod` 5 of
0 ->
w
& wCam . camBoundDist .~ bdists
& wCam . camBoundBox
.~ map ((+.+ w ^. wCam . camCenter) . rotateV (w ^. wCam . camRot)) (rectNSWE n s w' e)
_ -> w
where
bdists@(n, s, e, w') = findBoundDists cfig w