-- | The type of cave kinds. Every level in the game is an instantiated -- cave kind. module Game.LambdaHack.Content.CaveKind ( pattern DEFAULT_RANDOM , CaveKind(..), InitSleep(..), makeData #ifdef EXPOSE_INTERNAL -- * Internal operations , validateSingle, validateAll, mandatoryGroups #endif ) where import Prelude () import Game.LambdaHack.Core.Prelude import qualified Data.Text as T import Game.LambdaHack.Content.ItemKind (ItemKind) import Game.LambdaHack.Content.PlaceKind (PlaceKind) import qualified Game.LambdaHack.Content.RuleKind as RK import Game.LambdaHack.Content.TileKind (TileKind) import qualified Game.LambdaHack.Core.Dice as Dice import Game.LambdaHack.Core.Random import Game.LambdaHack.Definition.ContentData import Game.LambdaHack.Definition.Defs import Game.LambdaHack.Definition.DefsInternal -- | Parameters for the generation of dungeon levels. -- Warning: for efficiency, avoid embedded items in any of the common tiles. data CaveKind = CaveKind { cname :: Text -- ^ short description , cfreq :: Freqs CaveKind -- ^ frequency within groups , cXminSize :: X -- ^ minimal X size of the whole cave , cYminSize :: Y -- ^ minimal Y size of the whole cave , ccellSize :: Dice.DiceXY -- ^ size of a map cell holding a place , cminPlaceSize :: Dice.DiceXY -- ^ minimal size of places; for merging , cmaxPlaceSize :: Dice.DiceXY -- ^ maximal size of places; for growing , cdarkOdds :: Dice.Dice -- ^ the odds a place is dark -- (level-scaled dice roll > 50) , cnightOdds :: Dice.Dice -- ^ the odds the cave is dark -- (level-scaled dice roll > 50) , cauxConnects :: Rational -- ^ a proportion of extra connections , cmaxVoid :: Rational -- ^ at most this proportion of rooms may be void , cdoorChance :: Chance -- ^ the chance of a door in an opening , copenChance :: Chance -- ^ if there's a door, is it open? , chidden :: Int -- ^ if not open, hidden one in n times , cactorCoeff :: Int -- ^ the lower, the more monsters spawn , cactorFreq :: Freqs ItemKind -- ^ actor groups to consider , citemNum :: Dice.Dice -- ^ number of initial items in the cave , citemFreq :: Freqs ItemKind -- ^ item groups to consider; -- note that the groups are flattened; e.g., if an item is moved -- to another included group with the same weight, the outcome -- doesn't change , cplaceFreq :: Freqs PlaceKind -- ^ place groups to consider , cpassable :: Bool -- ^ are passable default tiles permitted , clabyrinth :: Bool -- ^ waste of time for AI to explore , cdefTile :: GroupName TileKind -- ^ the default cave tile , cdarkCorTile :: GroupName TileKind -- ^ the dark cave corridor tile , clitCorTile :: GroupName TileKind -- ^ the lit cave corridor tile , cwallTile :: GroupName TileKind -- ^ the tile used for @FWall@ fence , ccornerTile :: GroupName TileKind -- ^ tile used for the fence corners , cfenceTileN :: GroupName TileKind -- ^ the outer fence N wall , cfenceTileE :: GroupName TileKind -- ^ the outer fence E wall , cfenceTileS :: GroupName TileKind -- ^ the outer fence S wall , cfenceTileW :: GroupName TileKind -- ^ the outer fence W wall , cfenceApart :: Bool -- ^ are places touching fence banned , cminStairDist :: Int -- ^ minimal distance between stairs , cmaxStairsNum :: Dice.Dice -- ^ maximum number of stairs , cescapeFreq :: Freqs PlaceKind -- ^ escape groups, if any , cstairFreq :: Freqs PlaceKind -- ^ place groups for created stairs , cstairAllowed :: Freqs PlaceKind -- ^ extra groups for inherited , cskip :: [Int] -- ^ which faction starting positions to skip , cinitSleep :: InitSleep -- ^ whether actors spawn sleeping , cdesc :: Text -- ^ full cave description } deriving Show -- No Eq and Ord to make extending logically sound data InitSleep = InitSleepAlways | InitSleepPermitted | InitSleepBanned deriving (Show, Eq) -- | Catch caves with not enough space for all the places. Check the size -- of the cave descriptions to make sure they fit on screen. Etc. validateSingle :: RK.RuleContent -> CaveKind -> [Text] validateSingle corule CaveKind{..} = let (minCellSizeX, minCellSizeY) = Dice.infDiceXY ccellSize (maxCellSizeX, maxCellSizeY) = Dice.supDiceXY ccellSize (minMinSizeX, minMinSizeY) = Dice.infDiceXY cminPlaceSize (maxMinSizeX, maxMinSizeY) = Dice.supDiceXY cminPlaceSize (minMaxSizeX, minMaxSizeY) = Dice.infDiceXY cmaxPlaceSize in [ "cname longer than 25" | T.length cname > 25 ] ++ [ "cXminSize > RK.rWidthMax" | cXminSize > RK.rWidthMax corule ] ++ [ "cYminSize > RK.rHeightMax" | cYminSize > RK.rHeightMax corule ] ++ [ "cXminSize < 8" | cXminSize < 8 ] ++ [ "cYminSize < 8" | cYminSize < 8 ] -- see @focusArea@ ++ [ "cXminSize - 2 < maxCellSizeX" | cXminSize - 2 < maxCellSizeX ] ++ [ "cYminSize - 2 < maxCellSizeY" | cYminSize - 2 < maxCellSizeY ] ++ [ "minCellSizeX < 2" | minCellSizeX < 2 ] ++ [ "minCellSizeY < 2" | minCellSizeY < 2 ] ++ [ "minCellSizeX < 4 and stairs" | minCellSizeX < 4 && not (null cstairFreq) ] ++ [ "minCellSizeY < 4 and stairs" | minCellSizeY < 4 && not (null cstairFreq) ] -- The following four are heuristics, so not too restrictive: ++ [ "minCellSizeX < 6 && non-trivial stairs" | minCellSizeX < 6 && not (length cstairFreq <= 1 && null cescapeFreq) ] ++ [ "minCellSizeY < 4 && non-trivial stairs" | minCellSizeY < 4 && not (length cstairFreq <= 1 && null cescapeFreq) ] ++ [ "minMinSizeX < 5 && non-trivial stairs" | minMinSizeX < 5 && not (length cstairFreq <= 1 && null cescapeFreq) ] ++ [ "minMinSizeY < 3 && non-trivial stairs" | minMinSizeY < 3 && not (length cstairFreq <= 1 && null cescapeFreq) ] ++ [ "minMinSizeX < 1" | minMinSizeX < 1 ] ++ [ "minMinSizeY < 1" | minMinSizeY < 1 ] ++ [ "minMaxSizeX < maxMinSizeX" | minMaxSizeX < maxMinSizeX ] ++ [ "minMaxSizeY < maxMinSizeY" | minMaxSizeY < maxMinSizeY ] ++ [ "chidden < 0" | chidden < 0 ] ++ [ "cactorCoeff < 0" | cactorCoeff < 0 ] ++ [ "citemNum < 0" | Dice.infDice citemNum < 0 ] ++ [ "cmaxStairsNum < 0" | Dice.infDice cmaxStairsNum < 0 ] ++ [ "stairs suggested, but not defined" | Dice.supDice cmaxStairsNum > 0 && null cstairFreq ] -- | Validate all cave kinds. -- Note that names don't have to be unique: we can have several variants -- of a cave with a given name. validateAll :: [CaveKind] -> ContentData CaveKind -> [Text] validateAll _ _ = [] -- so far, always valid -- * Mandatory item groups mandatoryGroups :: [GroupName CaveKind] mandatoryGroups = [DEFAULT_RANDOM] pattern DEFAULT_RANDOM :: GroupName CaveKind pattern DEFAULT_RANDOM = GroupName "default random" makeData :: RK.RuleContent -> [CaveKind] -> [GroupName CaveKind] -> [GroupName CaveKind] -> ContentData CaveKind makeData corule content groupNamesSingleton groupNames = makeContentData "CaveKind" cname cfreq (validateSingle corule) validateAll content groupNamesSingleton (mandatoryGroups ++ groupNames)