Allure-0.4.1: src/Level.hs
module Level where
import Control.Monad
import Data.Binary
import Data.Map as M
import Data.List as L
import qualified Data.IntMap as IM
import Geometry
import GeometryRnd
import Movable
import Item
import Random
import qualified Terrain
import WorldLoc
import Data.Maybe
import qualified Tile
type Party = IM.IntMap Movable
data Level = Level
{ lname :: LevelId, -- TODO: remove
lheroes :: Party, -- ^ all heroes on the level
lsize :: (Y,X),
lmonsters :: Party, -- ^ all monsters on the level
lsmell :: SMap,
lmap :: LMap,
lmeta :: String }
deriving Show
updateLMap :: (LMap -> LMap) -> Level -> Level
updateLMap f lvl = lvl { lmap = f (lmap lvl) }
updateSMap :: (SMap -> SMap) -> Level -> Level
updateSMap f lvl = lvl { lsmell = f (lsmell lvl) }
updateMonsters :: (Party -> Party) -> Level -> Level
updateMonsters f lvl = lvl { lmonsters = f (lmonsters lvl) }
updateHeroes :: (Party -> Party) -> Level -> Level
updateHeroes f lvl = lvl { lheroes = f (lheroes lvl) }
emptyParty :: Party
emptyParty = IM.empty
instance Binary Level where
put (Level nm hs sz@(sy,sx) ms lsmell lmap lmeta) =
do
put nm
put hs
put sz
put ms
put [ lsmell ! (y,x) | y <- [0..sy], x <- [0..sx] ]
put [ lmap ! (y,x) | y <- [0..sy], x <- [0..sx] ]
put lmeta
get = do
nm <- get
hs <- get
sz@(sy,sx) <- get
ms <- get
xs <- get
let lsmell = M.fromList (zip [ (y,x) | y <- [0..sy], x <- [0..sx] ] xs)
xs <- get
let lmap = M.fromList (zip [ (y,x) | y <- [0..sy], x <- [0..sx] ] xs)
lmeta <- get
return (Level nm hs sz ms lsmell lmap lmeta)
type LMap = Map (Y,X) (Tile.Tile, Tile.Tile)
type SMap = Map (Y,X) Time
at l p = fst (findWithDefault (Tile.unknownTile, Tile.unknownTile) p l)
rememberAt l p = snd (findWithDefault (Tile.unknownTile, Tile.unknownTile) p l)
-- Checks for the presence of movables. Does *not* check if the tile is open.
unoccupied :: [Movable] -> Loc -> Bool
unoccupied movables loc =
all (\ m -> mloc m /= loc) movables
-- Check whether one location is accessible from the other.
-- Precondition: the two locations are next to each other.
-- Currently only implements that the target location has to be open.
-- TODO: in the future check flying for chasms, swimming for water, etc.
accessible :: LMap -> Loc -> Loc -> Bool
accessible lmap source target =
let tgt = lmap `at` target
in Tile.open tgt
-- check whether the location contains a door of secrecy level lower than k
openable :: Int -> LMap -> Loc -> Bool
openable k lmap target =
let tgt = lmap `at` target
in case Terrain.deDoor $ Tile.tterrain tgt of
Just (Just n) -> n < k
_ -> False
findLoc :: Level -> (Loc -> Tile.Tile -> Bool) -> Rnd Loc
findLoc l@(Level { lsize = sz, lmap = lm }) p =
do
loc <- locInArea ((0,0),sz)
let tile = lm `at` loc
if p loc tile
then return loc
else findLoc l p
findLocTry :: Int -> -- try k times
Level ->
(Loc -> Tile.Tile -> Bool) -> -- loop until satisfied
(Loc -> Tile.Tile -> Bool) -> -- only try to satisfy k times
Rnd Loc
findLocTry k l@(Level { lsize = sz, lmap = lm }) p pTry =
do
loc <- locInArea ((0,0),sz)
let tile = lm `at` loc
if p loc tile && pTry loc tile
then return loc
else if k > 1
then findLocTry (k - 1) l p pTry
else findLoc l p
-- Actually, do not scatter items around, it's too much work for the player.
dropItemsAt :: [Item] -> Loc -> Level -> Level
dropItemsAt items loc lvl@(Level { lmap = lmap }) =
let joinItems items = L.foldl' (\ acc i -> snd (joinItem i acc)) items
t = lmap `at` loc
nt = t { Tile.titems = joinItems items (Tile.titems t) }
ntRemember = lmap `rememberAt` loc
in updateLMap (M.insert loc (nt, ntRemember)) lvl