Files
loop/src/Dodge/Room/Procedural.hs
T

346 lines
13 KiB
Haskell

-- | Procedural creation of rooms and subroom parts.
module Dodge.Room.Procedural (
roomRect,
roomRectAutoLinks,
randomFourCornerRoom,
randomFourCornerRoomCrsIts,
centerVaultRoom,
combineRooms,
-- linksAndPath,
-- makeGrid,
) where
import Grid
import Dodge.Door.PutSlideDoor
import Dodge.Default.Wall
import Control.Monad
import Tile
import qualified Data.Set as S
import Data.Tile
import Dodge.Creature
import Dodge.Data.GenWorld
import Dodge.Default.Door
import Dodge.Default.Room
import Dodge.LevelGen.PlacementHelper
import Dodge.LevelGen.Switch
import Dodge.Placement.Instance
import Dodge.Placement.Shift
import Dodge.PlacementSpot
import Dodge.Room.Link
import Dodge.Room.Path
import Dodge.RoomLink
import Geometry
import LensHelp
import Picture
import RandomHelp
--import Control.Lens
-- This will need a cleanup, but it might change a bit first
{- A simple rectangular room with a light in the center.
Creates links and pathfinding graph. -}
roomRect :: Float -> Float -> Int -> Int -> Room
roomRect x y xn yn =
defaultRoom
{ _rmPolys = [rectNSWE y 0 0 x]
, _rmLinks = lnks
, _rmName = "rect"
, _rmPath = pth
, _rmPos = map makeonpos posps ++ map makeoffpos interposps
, _rmPmnts = []
, _rmBound = [rectNSWE (y + 5) (-5) (-5) (x + 5)]
, _rmFloor =
Tiled
[ Tile
{ _tilePoly = rectNSWE y 0 0 x
, _tileZero = V2 0 0
, _tileTangentPos = V2 baseFloorTileSize 0
--, _tileArrayZ = 5
, _tileArrayZ = 16
}
]
, _rmRandPSs = [psRandRanges (10, x -10) (10, y -10) (0, 2 * pi)]
, _rmType =
RectRoomType
{ _numLinkEW = xn
, _numLinkNS = yn
, _linkGapEW = xd
, _linkGapNS = yd
, _rmWidth = x
, _rmHeight = y
}
}
where
xs = take (xn + 1) [0,xd..] & each +~ 20
ys = take (yn + 1) [0,yd..] & each +~ 20
yd = (y - 40) / fromIntegral yn
xd = (x - 40) / fromIntegral xn
pth = linksDAGToPath lnks $ latticeXsYs xs ys
hls y' a e = xs <&> \x' -> RoomLink (rlt e) (V2 x' y') a
vls x' a e = ys <&> \y' -> RoomLink (rlt e) (V2 x' y') a
rlt e = S.fromList [OnEdge e, InLink, OutLink]
somelnks poffset ps a = zip (map (+.+ poffset) ps) (repeat a)
wlnks = vls 0 (pi/2) West
elnks = vls x (-pi/2) East
nlnks = hls y 0 North --somelnks (V2 20 y) (gridPoints xd (xn + 1) 0 1) 0
slnks = hls 0 pi South --somelnks (V2 20 0) (gridPoints xd (xn + 1) 0 1) pi
lnks = zipWith g nlnks [0..]
<> zipWith g slnks [0..]
<> zipWith g' elnks [0..]
<> zipWith g' wlnks [0..]
g ln xi = ln & rlType <>~ S.fromList [FromEdge West xi,FromEdge East (xn - xi)]
g' ln xi = ln & rlType <>~ S.fromList [FromEdge South xi,FromEdge North (xn - xi)]
-- lnks= m North (FromEdge West) (FromEdge East) nlnks
-- ++ m East (FromEdge South) (FromEdge North) elnks
-- ++ m West (FromEdge South) (FromEdge North) wlnks
-- ++ m South (FromEdge West) (FromEdge East) slnks
--wlnks = somelnks (V2 0 20) (gridPoints 0 1 yd (yn + 1)) (pi / 2)
--elnks = somelnks (V2 x 20) (gridPoints 0 1 yd (yn + 1)) (- pi / 2)
--nlnks = somelnks (V2 20 y) (gridPoints xd (xn + 1) 0 1) 0
--slnks = somelnks (V2 20 0) (gridPoints xd (xn + 1) 0 1) pi
--lnks =
-- m North (FromEdge West) (FromEdge East) nlnks
-- ++ m East (FromEdge South) (FromEdge North) elnks
-- ++ m West (FromEdge South) (FromEdge North) wlnks
-- ++ m South (FromEdge West) (FromEdge East) slnks
m edge edgefrom1 edgefrom2 =
zipWith (lnkBothAnd (OnEdge edge) edgefrom1 edgefrom2) [0 ..]
. zipCountDown
makeonpos (p, a) = RoomPos p 0 (S.singleton $ RoomPosOnGrid $ makerpedges a)
(NotLink True) mempty
makerpedges (a, b) =
S.fromList
[ PathFromEdge South b
, PathFromEdge North (yn - b)
, PathFromEdge East (xn - a)
, PathFromEdge West a
]
posps = map (over _1 (+.+ V2 20 20)) $ gridPoints'' xd (xn + 1) yd (yn + 1)
interposps = map (over _1 (+.+ V2 (20 + xd / 2) (20 + yd / 2))) $ gridPoints'' xd xn yd yn
makeoffpos (p, a) = RoomPos
p 0 (S.singleton $ RoomPosOffGrid $ makerpedges' a) (NotLink False) mempty
makerpedges' (a, b) =
S.fromList
[ PathFromEdge South b
, PathFromEdge North (yn - (b + 1))
, PathFromEdge East (xn - (a + 1))
, PathFromEdge West a
]
zipCountDown :: [a] -> [(Int, a)]
zipCountDown xs = zip [length xs - 1, length xs - 2 ..] xs
lnkBothAnd ::
RoomLinkType ->
(Int -> RoomLinkType) ->
(Int -> RoomLinkType) ->
Int ->
(Int, (Point2, Float)) ->
RoomLink
lnkBothAnd rlt ltcon ltcon2 i (j, (p, a)) =
RoomLink
{ _rlType = S.fromList [OutLink, InLink, rlt, ltcon i, ltcon2 j]
, _rlPos = p
, _rlDir = a
}
{- Creates a rectangular room, automatically creates links and pathfinding graph at a sensible size. -}
-- it is not clear to me that this works for very small rooms (but it does seem
-- to do so)
roomRectAutoLinks :: RandomGen g => Float -> Float -> State g Room
roomRectAutoLinks x y = do
l1 <- mntLightLnkCond $ resetPLUse $ rprBool $ \rp _ -> isInLnk rp
l2 <- mntLightLnkCond $ resetPLUse $ rprBool $ \rp _ -> isOutLnk rp
return $ roomRect x y (f x) (f y)
& rmName .~ "autoRect"
& rmPmnts .~ [ l1 , l2 ]
where
f z = max 1 $ (ceiling z - 40) `div` 60
{- Combines two rooms into one room.
- will have to work out exactly what to do with combining links
Mostly involves concatenation. -}
combineRooms :: Room -> Room -> Room
combineRooms r r' =
defaultRoom
{ _rmPolys = _rmPolys r ++ _rmPolys r'
, _rmLinks = _rmLinks r ++ _rmLinks r'
, _rmPath = S.map clampPath $ _rmPath r <> _rmPath r'
, _rmPmnts = _rmPmnts r ++ _rmPmnts r'
, _rmBound = _rmBound r ++ _rmBound r'
, _rmPos = _rmPos r ++ _rmPos r'
, _rmFloor = combineFloors (_rmFloor r) (_rmFloor r')
, _rmShift = (V2 0 0, 0)
}
-- not that this assumes that any link paths are integral
clampPath :: (Point2, Point2) -> (Point2, Point2)
clampPath = bimap f f
where
f (V2 x y) = V2 (g x) (g y)
g = (fromIntegral :: Int -> Float) . floor
combineFloors :: Floor -> Floor -> Floor
combineFloors = const
{- Randomly generate a top fourth of a room possibly with a wall.
Add a light and a 'PutNothing' placement. -}
quarterRoomTri :: RandomGen g => Float -> State g Room
quarterRoomTri w = do
b <-
takeOne
[
[ mntLS vShape (V2 0 w) (V3 0 (w -20) 70)
, blockLine (V2 (w / 2) (w / 2)) (V2 (w / 2) w)
]
, [blockLine (V2 (w / 2) (w / 2)) (V2 (negate $ w / 2) (w / 2))]
,
[ blockLine (V2 (w / 2) (w / 2)) (V2 0 (w / 2))
, blockLine (V2 (-29) w) (V2 0 (w / 2)) & plType . putWall . wlRotateTo .~ False
]
]
pure $
defaultRoom
{ _rmPolys = [[V2 0 0, V2 w w, V2 (- w) w]]
, _rmLinks = [toBothLnk (V2 0 w, 0)]
, _rmPath =
foldMap
doublePairSet
[ (V2 0 w, V2 0 (w -20))
, (V2 0 (w -20), V2 (w -20) (w -20))
, (V2 0 (w -20), V2 (55 - w) (w -20))
, (V2 (55 - w) (w -20), V2 (20 - w) (w -20))
, (V2 0 (w -20), V2 0 0)
, (V2 (55 - w) (w -20), V2 0 0)
]
, _rmPmnts = b ++ [mntLS iShape (V2 (w -20) w) (V3 (w -20) (w -20) 70)]
, _rmPos =
[ RoomPos (V2 (w -20) (w -20)) pi S.empty (NotLink False) mempty
, RoomPos (V2 (w -15) (w -25)) pi S.empty (NotLink False) mempty
]
, _rmBound = [[V2 0 0, V2 w w, V2 (- w) w]]
}
quarterRoomSquare :: RandomGen g => Float -> State g Room
quarterRoomSquare w = do
b <-
takeOne
[
[ mntLS vShape (V2 0 (2 * w -20)) (V3 0 (2 * w -40) 70)
, blockLine (V2 (negate $ w / 2) (w / 2)) (V2 0 w)
]
,
[ mntLS vShape (V2 0 (2 * w -20)) (V3 0 (2 * w -40) 70)
, blockLine (V2 (negate w) w) (V2 0 w)
]
,
[ mntLS iShape (V2 (0.7 * w) (1.3 * w)) (V3 (0.7 * w -20) (1.3 * w -20) 70)
, blockLine (V2 0 w) (V2 0 (w * 2))
]
]
let thepoly = [map toV2 [(0, 0), (w, w), (0, 2 * w), (- w, w)]]
pure $
defaultRoom
{ _rmPolys = thepoly
, _rmLinks =
map
toBothLnk
[ (V2 (w / 2) (3 * w / 2), negate $ pi / 4)
, (V2 (negate $ w / 2) (3 * w / 2), pi / 4)
]
, _rmPath =
foldMap
doublePairSet
[ (V2 (0.5 * w) (1.5 * w), V2 (0.5 * w -20) (1.5 * w -20))
, (V2 (-0.5 * w) (1.5 * w), V2 (-0.5 * w + 20) (1.5 * w -20))
, (V2 0 (2 * w -40), V2 (-0.5 * w + 20) (1.5 * w -20))
, (V2 0 (2 * w -40), V2 (0.5 * w -20) (1.5 * w -20))
, (V2 (w -20) (w -20), V2 (0.5 * w -20) (1.5 * w -20))
, (V2 (20 - w) (w -20), V2 (-0.5 * w + 20) (1.5 * w -20))
, --,(V2 (40-w) (w),V2 0 (w-40))
(V2 (20 - w) (w -20), V2 0 (w -40))
, (V2 0 0, V2 0 (w -40))
-- ,(V2 (20-w) (w-20),V2 (40-w) (w))
]
, _rmPmnts = b ++ [blockLine (V2 (w / 2) (w / 2)) (V2 0 w)]
, _rmPos =
[ RoomPos p pi S.empty (NotLink False) mempty
| p <- [V2 20 (2 * w -40), V2 25 (2 * w -45)]
]
, --, _rmBound = [map toV2 [(w,w),(0,2*w),(-w,w)]]
_rmBound = [map toV2 [(0, 0), (w, w), (0, 2 * w), (- w, w)]]
}
{- | A randomly generate room based on four randomly generated corners.
Tight corridors, random placements.
-}
randomFourCornerRoom :: RandomGen g => [Item] -> State g Room
randomFourCornerRoom its = do
nCrits <- state $ randomR (1, 3)
crits <-
takeN nCrits <=< shuffle $
[spreadGunCrit, autoCrit, armourChaseCrit]
++ replicate 20 chaseCrit
randomFourCornerRoomCrsIts crits its
{- | A randomly generate room based on four randomly generated corners.
Tight corridors.
-}
randomFourCornerRoomCrsIts :: RandomGen g => [Creature] -> [Item] -> State g Room
randomFourCornerRoomCrsIts crits its = do
corners <- replicateM 4 . join $ takeOne [quarterRoomTri 100, quarterRoomSquare 100]
let putitms = map (\it -> sps0 (PutFlIt it) & plSpot .~ anyUnusedSpot) its
putcrits = map (\cr -> sps0 (PutCrit cr) & plSpot .~ unusedSpotAwayFromLink 50) crits
shuffleLinks
. (rmPmnts .++~ (sps0 putLamp : putitms ++ putcrits))
. foldr1 combineRooms
$ zipWith (\r a -> moveRoomBy (V2 0 0, a) r) corners [0, pi / 2, pi, 3 * pi / 2]
-- | Creates room with a central vault with doors around it.
centerVaultRoom ::
-- | Width
Float ->
-- | Height
Float ->
-- | Vault dimensions
Float ->
State g Room
centerVaultRoom w h d =
return $
defaultRoom
{ _rmPolys = [rectNSWE h (- h) (- w) w]
, _rmLinks =
[ outLink (V2 0 h) 0
, outLink (V2 w 0) (- pi / 2)
, outLink (V2 (- w) 0) (pi / 2)
, inLink (V2 0 (- h)) pi
]
, _rmPath = mempty
, _rmPmnts =
[ sps0 $ PutWall (reverse $ rectNSWE d (d - 30) (d - 30) d) defaultWall
, sps0 $ PutWall (reverse $ rectNSWE d (d - 30) (30 - d) (- d)) defaultWall
, sps0 $ PutWall (reverse $ rectNSWE (- d) (30 - d) (d - 30) d) defaultWall
, sps0 $ PutWall (reverse $ rectNSWE (- d) (30 - d) (30 - d) (- d)) defaultWall
]
++ map
(\a -> mntLS vShape (rotateV a $ V2 0 d) (rotate3z a $ V3 0 (d + 30) 70))
[0, 0.5 * pi, pi, 1.5 * pi]
++ concatMap
(\r -> map (shiftPlacement (V2 0 0, r)) theDoor)
[0, pi / 2, pi, 3 * pi / 2]
, _rmBound = [rectNSWE h (- h) (- w) w]
, _rmName = "cenVault"
}
where
col = dim $ dim $ bright red
theDoor =
[ pContID (PS (V2 35 (d + 4)) 0) (PutButton $ makeSwitch col red NoWorldEffect NoWorldEffect) $
\btid ->
jspsJ (V2 0 (d -10)) 0 (putSlideDr (thedoor btid) thewall 1 (V2 (-21) 0) (V2 0 0)) $
sPS (V2 0 (d -10)) 0 (putSlideDr (thedoor btid) thewall 1 (V2 21 0) (V2 0 0))
]
thewall = defaultDoorWall
thedoor btid =
defaultDoor
& drTrigger .~ WdBlBtOn btid
& drUpdate . drLerpSpeed .~ 2