-- | Item slots for UI and AI item collections. -- TODO: document module Game.LambdaHack.Client.ItemSlot ( ItemSlots, SlotChar(..) , allSlots, slotLabel, slotRange, assignSlot ) where import Control.Exception.Assert.Sugar import Data.Binary import Data.Bits (shiftL, shiftR) import Data.Char import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import Data.List import Data.Monoid import Data.Ord (comparing) import Data.Text (Text) import qualified Data.Text as T import qualified NLP.Miniutter.English as MU import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.State data SlotChar = SlotChar {slotPrefix :: Int, slotChar :: Char} deriving (Show, Eq) instance Ord SlotChar where compare = comparing fromEnum instance Binary SlotChar where put = put . fromEnum get = fmap toEnum get instance Enum SlotChar where fromEnum (SlotChar n c) = ord c + (if isUpper c then 100 else 0) + shiftL n 8 toEnum e = let n = shiftR e 8 c0 = e - shiftL n 8 c100 = c0 - if c0 > 150 then 100 else 0 in SlotChar n (chr c100) type ItemSlots = ( EM.EnumMap SlotChar ItemId , EM.EnumMap SlotChar ItemId ) slotRange :: [SlotChar] -> Text slotRange ls = sectionBy (sort ls) Nothing where succSlot c d = ord (slotChar d) - ord (slotChar c) == 1 succ2Slot c d = ord (slotChar d) - ord (slotChar c) == 2 sectionBy [] Nothing = T.empty sectionBy [] (Just (c, d)) = finish (c,d) sectionBy (x:xs) Nothing = sectionBy xs (Just (x, x)) sectionBy (x:xs) (Just (c, d)) | succSlot d x = sectionBy xs (Just (c, x)) | otherwise = finish (c,d) <> sectionBy xs (Just (x, x)) finish (c, d) | c == d = T.pack [slotChar c] | succSlot c d = T.pack [slotChar c, slotChar d] | succ2Slot c d = T.pack [ slotChar c , chr (1 + ord (slotChar c)) , slotChar d ] | otherwise = T.pack [slotChar c, '-', slotChar d] allSlots :: Int -> [SlotChar] allSlots n = map (SlotChar n) $ ['a'..'z'] ++ ['A'..'Z'] allZeroSlots :: [SlotChar] allZeroSlots = allSlots 0 -- | Assigns a slot to an item, for inclusion in the inventory or equipment -- of a hero. Tries to to use the requested slot, if any. assignSlot :: CStore -> Item -> FactionId -> Maybe Actor -> ItemSlots -> SlotChar -> State -> SlotChar assignSlot store item fid mbody (itemSlots, organSlots) lastSlot s = assert (maybe True (\b -> bfid b == fid) mbody) $ if jsymbol item == '$' then SlotChar 0 '$' else head $ fresh ++ free where offset = maybe 0 (+1) (elemIndex lastSlot allZeroSlots) onlyOrgans = store == COrgan len0 = length allZeroSlots candidatesZero = take len0 $ drop offset $ cycle allZeroSlots candidates = candidatesZero ++ concat [allSlots n | n <- [1..]] onPerson = sharedAllOwnedFid onlyOrgans fid s onGround = maybe EM.empty -- consider floor only under the acting actor (\b -> getCBag (CFloor (blid b) (bpos b)) s) mbody inBags = ES.unions $ map EM.keysSet $ onPerson : [ onGround | not onlyOrgans] lSlots = if onlyOrgans then organSlots else itemSlots f l = maybe True (`ES.notMember` inBags) $ EM.lookup l lSlots free = filter f candidates g l = l `EM.notMember` lSlots fresh = filter g $ take ((slotPrefix lastSlot + 1) * len0) candidates slotLabel :: SlotChar -> MU.Part slotLabel x = MU.String $ (if slotPrefix x == 0 then [] else show $ slotPrefix x) ++ [slotChar x]