{-# LANGUAGE DeriveGeneric #-}
module Game.LambdaHack.Content.TileKind
( TileKind(..), Feature(..)
, validateSingleTileKind, validateAllTileKind, actionFeatures
, TileSpeedup(..), Tab(..), isUknownSpace, unknownId
, isSuspectKind, isOpenableKind, isClosableKind, talterForStairs, floorSymbol
) where
import Prelude ()
import Game.LambdaHack.Common.Prelude
import Control.DeepSeq
import Data.Binary
import qualified Data.Char as Char
import Data.Hashable
import qualified Data.IntSet as IS
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Vector.Unboxed as U
import GHC.Generics (Generic)
import Game.LambdaHack.Common.Color
import qualified Game.LambdaHack.Common.KindOps as KindOps
import Game.LambdaHack.Common.Misc
import Game.LambdaHack.Content.ItemKind (ItemKind)
data TileKind = TileKind
{ tsymbol :: !Char
, tname :: !Text
, tfreq :: !(Freqs TileKind)
, tcolor :: !Color
, tcolor2 :: !Color
, talter :: !Word8
, tfeature :: ![Feature]
}
deriving Show
data Feature =
Embed !(GroupName ItemKind)
| OpenTo !(GroupName TileKind)
| CloseTo !(GroupName TileKind)
| ChangeTo !(GroupName TileKind)
| HideAs !(GroupName TileKind)
| BuildAs !(GroupName TileKind)
| RevealAs !(GroupName TileKind)
| ObscureAs !(GroupName TileKind)
| Walkable
| Clear
| Dark
| OftenItem
| OftenActor
| NoItem
| NoActor
| Indistinct
| ConsideredByAI
| Trail
| Spice
deriving (Show, Eq, Ord, Generic)
instance Binary Feature
instance Hashable Feature
instance NFData Feature
data TileSpeedup = TileSpeedup
{ isClearTab :: !(Tab Bool)
, isLitTab :: !(Tab Bool)
, isWalkableTab :: !(Tab Bool)
, isDoorTab :: !(Tab Bool)
, isChangableTab :: !(Tab Bool)
, isSuspectTab :: !(Tab Bool)
, isHideAsTab :: !(Tab Bool)
, consideredByAITab :: !(Tab Bool)
, isOftenItemTab :: !(Tab Bool)
, isOftenActorTab :: !(Tab Bool)
, isNoItemTab :: !(Tab Bool)
, isNoActorTab :: !(Tab Bool)
, isEasyOpenTab :: !(Tab Bool)
, alterMinSkillTab :: !(Tab Word8)
, alterMinWalkTab :: !(Tab Word8)
}
newtype Tab a = Tab (U.Vector a)
isUknownSpace :: KindOps.Id TileKind -> Bool
{-# INLINE isUknownSpace #-}
isUknownSpace tt = KindOps.Id 0 == tt
unknownId :: KindOps.Id TileKind
{-# INLINE unknownId #-}
unknownId = KindOps.Id 0
validateSingleTileKind :: TileKind -> [Text]
validateSingleTileKind t@TileKind{..} =
[ "suspect tile is walkable" | Walkable `elem` tfeature
&& isSuspectKind t ]
++ [ "openable tile is open" | Walkable `elem` tfeature
&& isOpenableKind t ]
++ [ "closable tile is closed" | Walkable `notElem` tfeature
&& isClosableKind t ]
++ [ "walkable tile is considered for triggering by AI"
| Walkable `elem` tfeature
&& ConsideredByAI `elem` tfeature ]
++ [ "trail tile not walkable" | Walkable `notElem` tfeature
&& Trail `elem` tfeature ]
++ [ "OftenItem and NoItem on a tile" | OftenItem `elem` tfeature
&& NoItem `elem` tfeature ]
++ [ "OftenActor and NoActor on a tile" | OftenItem `elem` tfeature
&& NoItem `elem` tfeature ]
++ (let f :: Feature -> Bool
f OpenTo{} = True
f CloseTo{} = True
f ChangeTo{} = True
f _ = False
ts = filter f tfeature
in [ "more than one OpenTo, CloseTo and ChangeTo specification"
| length ts > 1 ])
++ (let f :: Feature -> Bool
f HideAs{} = True
f _ = False
ts = filter f tfeature
in ["more than one HideAs specification" | length ts > 1])
++ (let f :: Feature -> Bool
f BuildAs{} = True
f _ = False
ts = filter f tfeature
in ["more than one BuildAs specification" | length ts > 1])
++ concatMap (validateDups t)
[ Walkable, Clear, Dark, OftenItem, OftenActor, NoItem, NoActor
, Indistinct, ConsideredByAI, Trail, Spice ]
validateDups :: TileKind -> Feature -> [Text]
validateDups TileKind{..} feat =
let ts = filter (== feat) tfeature
in ["more than one" <+> tshow feat <+> "specification" | length ts > 1]
isSuspectKind :: TileKind -> Bool
isSuspectKind t =
let getTo RevealAs{} = True
getTo ObscureAs{} = True
getTo _ = False
in any getTo $ tfeature t
isOpenableKind ::TileKind -> Bool
isOpenableKind t =
let getTo OpenTo{} = True
getTo _ = False
in any getTo $ tfeature t
isClosableKind :: TileKind -> Bool
isClosableKind t =
let getTo CloseTo{} = True
getTo _ = False
in any getTo $ tfeature t
validateAllTileKind :: [TileKind] -> [Text]
validateAllTileKind lt =
let kindFreq :: S.Set (GroupName TileKind)
kindFreq = let tuples = [ cgroup
| k <- lt
, (cgroup, n) <- tfreq k
, n > 0 ]
in S.fromList tuples
listVis f = map (\kt -> ( (tsymbol kt, f kt)
, [(kt, actionFeatures True kt)] )) lt
mapVis :: (TileKind -> Color)
-> M.Map (Char, Color) [(TileKind, IS.IntSet)]
mapVis f = M.fromListWith (++) $ listVis f
isConfused [] = assert `failure` lt
isConfused [_] = False
isConfused (hd : tl) =
any ((Indistinct `notElem`) . tfeature . fst) (hd : tl)
&& any ((/= snd hd) . snd) tl
confusions f = filter isConfused $ M.elems $ mapVis f
hardwiredAbsent = filter (`S.notMember` kindFreq) hardwiredTileGroups
in [ "first tile should be the unknown one"
| talter (head lt) /= 1 || tname (head lt) /= "unknown space" ]
++ [ "only unknown tile may have talter 1"
| any ((== 1) . talter) $ tail lt ]
++ case confusions tcolor ++ confusions tcolor2 of
[] -> []
cfs -> ["tile confusions detected:" <+> tshow cfs]
++ [ "Hardwired groups not in content:" <+> tshow hardwiredAbsent
| not $ null hardwiredAbsent ]
hardwiredTileGroups :: [GroupName TileKind]
hardwiredTileGroups =
[ "unknown space", "legendLit", "legendDark", "basic outer fence"
, "stair terminal" ]
actionFeatures :: Bool -> TileKind -> IS.IntSet
actionFeatures markSuspect t =
let stripLight grp = maybe grp toGroupName
$ maybe (T.stripSuffix "Dark" $ tshow grp) Just
$ T.stripSuffix "Lit" $ tshow grp
f feat = case feat of
Embed{} -> Just feat
OpenTo grp -> Just $ OpenTo $ stripLight grp
CloseTo grp -> Just $ CloseTo $ stripLight grp
ChangeTo grp -> Just $ ChangeTo $ stripLight grp
Walkable -> Just feat
Clear -> Just feat
HideAs{} -> Nothing
BuildAs{} -> Nothing
RevealAs{} -> if markSuspect then Just feat else Nothing
ObscureAs{} -> if markSuspect then Just feat else Nothing
Dark -> Nothing
OftenItem -> Nothing
OftenActor -> Nothing
NoItem -> Nothing
NoActor -> Nothing
Indistinct -> Nothing
ConsideredByAI -> Nothing
Trail -> Just feat
Spice -> Nothing
in IS.fromList $ map hash $ mapMaybe f $ tfeature t
talterForStairs :: Word8
talterForStairs = 3
floorSymbol :: Char.Char
floorSymbol = Char.chr 183