{-# LANGUAGE RankNTypes, FlexibleContexts, ConstraintKinds #-} {- This module is part of Antisplice. Copyleft (c) 2014 Marvin Cohrs All wrongs reversed. Sharing is an act of love, not crime. Please share Antisplice with everyone you like. Antisplice is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Antisplice is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Antisplice. If not, see . -} -- | A huge pile of utility functions for building our dungeon. module Game.Antisplice.Rooms ( -- * Room modification modifyRoomState, getRoomDesc, getRoomTitle, setRoomTitle, markRoom, -- * Moving around enterRoom, reenterCurrentRoom, enterAndAnnounce, changeRoom, -- * Room construction constructRoom, establishWay, addRoomObject, removeRoomObject, insertRoomObject, -- * Object construction constructObject, modifyObjectState, setObjectDesc, setObjectTitle, addObjectName, addObjectAttr, setObjectIsMob, setObjectIsAcquirable, addFeature, addDescSeg, addEquipSlot, -- * Object forms registerForm, instanciateForm, registerKind, setObjectKind, -- * Object investigation getObjectTitle, getObjectDesc, getObjectNames, matchObjectName, getObjectIsMob, getObjectIsAcquirable, roomOfObject, -- * Object actions setMobRoute, continueMobRoute, -- * Scheduling schedule, -- * Players subscribePlayer, setPlayerRoom, acquireObject, dropObject, equipObject, equipObjectAt, getEquipment, getCooldown, setCooldown, consumeAlcohol, -- * Currencies registerCurrency, getCurrency, modifyCurrency, -- * Fight damage, focusOpponent, dealDamage, -- * Masquerades withRoom, withPlayer, withObject, -- * Guardians guardRoom, guardObject, guardObjectInRoom, guardObjectNotInRoom, guardKindInRoom, guardKindNotInRoom, -- * In-/Output drunken ) where import Text.Chatty.Printer import Text.Chatty.Scanner import Text.Chatty.Expansion import System.Chatty.Misc import Game.Antisplice.Monad.Dungeon import Game.Antisplice.Monad import Data.Chatty.Graph import Data.Chatty.Counter import Data.Chatty.BST import Data.Chatty.AVL import Data.Chatty.Atoms import Data.Chatty.None import Game.Antisplice.Errors import Control.Arrow import Control.Monad import Control.Monad.Error import Control.Monad.Trans.Class import Data.Text (pack,unpack) import Data.Maybe import Data.List import Data.Char import Data.Time.Clock import Text.Printf -- | Modify the room state. modifyRoomState :: MonadRoom m => (RoomState -> RoomState) -> m () modifyRoomState f = do s <- getRoomState putRoomState (f s) -- | Get the current room's description getRoomDesc :: (MonadRoom m,ChAtoms m) => m String getRoomDesc = do s <- getRoomState --return (fromText $ roomDescOf s) let descseg (Described a) = getAtom a descseg _ = return none is <- liftM (filter $ not.null) $ mapM descseg $ concatMap (avlInorder.objectFeaturesOf) $ avlInorder $ roomObjectsOf s return $ concat $ intersperse " " is -- | Get the current room's title getRoomTitle :: (MonadRoom m,IsText t) => m t getRoomTitle = do s <- getRoomState return (fromText $ roomTitleOf s) {- | Set the current room's description setRoomDesc :: (MonadRoom m,IsText t) => t -> m () setRoomDesc t = modifyRoomState $ \s -> s{roomDescOf=toText t}-} -- | Set the current room's title setRoomTitle :: (MonadRoom m,IsText t) => t -> m () setRoomTitle t = modifyRoomState $ \s -> s{roomTitleOf=toText t} -- | Mark the current room as visited markRoom :: MonadDungeon m => m () markRoom = do s <- getDungeonState case currentRoomOf s of Just r -> putDungeonState s{roomsOf=markNode r $ roomsOf s} -- | Enter the given room and trigger most events, but don't announce it. Result tells whether this room is visited the first time. enterRoom :: NodeId -> ChattyDungeonM Bool enterRoom n = do rs0 <- getRoomState roomTriggerOnLeaveOf rs0 s <- getDungeonState modifyPlayerState $ \p -> p{playerRoomOf=n,playerOpponentOf=FalseObject} let marked = nodeMarked $ getNode' n $ roomsOf s rs <- getRoomState unless marked $ do markRoom roomTriggerOnFirstEnterOf rs roomTriggerOnEachEnterOf rs sequence_ $ avlInorder $ flip fmap (roomObjectsOf rs) $ \os -> do unless (objectOnceSeenOf os) $ objectTriggerOnFirstSightOf os objectTriggerOnEachSightOf os putRoomState rs{roomObjectsOf=fmap (\os -> os{objectOnceSeenOf=True}) $ roomObjectsOf rs} return $ not marked -- | Reenter the current room and trigger all events (but don't announce it). reenterCurrentRoom :: ChattyDungeonM () reenterCurrentRoom = do s <- getDungeonState case currentRoomOf s of Just r -> void $ enterRoom r -- | Enter the given room, trigger all events and announce it. On the first visit, look around. enterAndAnnounce :: NodeId -> ChattyDungeonM () enterAndAnnounce n = do r <- enterRoom n rs <- getRoomState roomTriggerOnAnnounceOf rs --when r $ id $ roomTriggerOnLookOf rs -- | Construct a new room using the room monad. constructRoom :: (MonadDungeon m) => RoomT m a -> m NodeId constructRoom m = do (_,rs) <- runRoomT m $ RoomState none none noneM noneM noneM noneM noneM s <- getDungeonState let (nid,g) = addNode' rs $ roomsOf s putDungeonState s{roomsOf=g} when (isNothing $ currentRoomOf s) $ modifyPlayerState $ \p -> p{playerRoomOf=nid} return nid -- | Run a function in the context of the given room. withRoom :: MonadDungeon m => NodeId -> RoomT m a -> m a withRoom n m = do s <- getDungeonState let rs = getNode n $ roomsOf s (a,rs') <- runRoomT m rs putDungeonState s{roomsOf=setNode n rs' $ roomsOf s} return a -- | Establish a path from one room to another one (one-way only). establishWay :: MonadDungeon m => NodeId -> NodeId -> Direction -> PathState -> m () establishWay f t d c = do s <- getDungeonState let g = addEdge f t 0 d c $ roomsOf s putDungeonState s{roomsOf=g} -- | Enter a neighbouring room by its direction. changeRoom :: Direction -> ChattyDungeonM () changeRoom d = do s <- getDungeonState case currentRoomOf s of Just r -> case queryEdge r d (roomsOf s) of Just c -> do b <- pathPrerequisiteOf c unless b $ throwError CantWalkThereError case followEdge r d (roomsOf s) of Just n -> do pathTriggerBeforeWalkOf c enterAndAnnounce n pathTriggerAfterWalkOf c Nothing -> throwError CantWalkThereError -- | Add a new object to the current room. It is contructed using the object monad. addRoomObject :: (ChCounter m,MonadRoom m) => ObjectT m a -> m ObjectId addRoomObject m = do i <- liftM ObjectId countOn k <- liftM KindId countOn o <- constructObject m (Just i) k insertRoomObject o return i -- | Construct a room object (but don't add it) constructObject :: Monad m => ObjectT m a -> Maybe ObjectId -> KindId -> m ObjectState constructObject m j k = do (_,o) <- runObjectT m $ ObjectState (if isJust j then (\(Just j) -> j) j else none) -- id k -- kid (pack "Something") -- title (pack "I don't know what this is.") -- desc none none False False False False -- names, attr, 1seen?, 1acq?, 1insp?, 1eq? 100 100 -- maxhp, curhp none none -- route, feat none -- faction noneM -- on 1 sight noneM -- on sight noneM -- on 1 acq noneM -- on acq noneM -- on 1 insp noneM -- on insp (mprintLn "There is nothing special about this.") -- on look at (mprintLn "There is nothing inside this.") -- on look in (mprintLn "There is nothing written on this.") -- on read (mprintLn "You cannot enter this.") -- on enter noneM -- on enter room noneM -- on leave room noneM -- on announce noneM -- on drop noneM -- on 1 eq noneM -- on eq noneM -- on uneq noneM -- on die noneM -- on take damage noneM -- on use noneM -- on eat noneM -- on drink return o -- | Remove an object from the current room and return its state. removeRoomObject :: MonadRoom m => ObjectId -> m ObjectState removeRoomObject i = do s <- getRoomState let Just o = avlLookup i $ roomObjectsOf s putRoomState s{roomObjectsOf=avlRemove i $ roomObjectsOf s} return o -- | Insert an already constructed object to the current room. insertRoomObject :: MonadRoom m => ObjectState -> m () insertRoomObject o = modifyRoomState $ \s -> s{roomObjectsOf=avlInsert o $ roomObjectsOf s} -- | Modify the object state modifyObjectState :: MonadObject m => (ObjectState -> ObjectState) -> m () modifyObjectState f = do s <- getObjectState putObjectState $ f s -- | Get the object's title getObjectTitle :: (MonadObject m,IsText t) => m t getObjectTitle = do s <- getObjectState return (fromText $ objectTitleOf s) -- | Set the object's title setObjectTitle :: (MonadObject m,IsText t) => t -> m () setObjectTitle t = modifyObjectState $ \s -> s{objectTitleOf=toText t} -- | Get the object's description getObjectDesc :: (MonadObject m,IsText t) => m t getObjectDesc = do s <- getObjectState return (fromText $ objectDescOf s) -- | Set the object's description setObjectDesc :: (MonadObject m,IsText t) => t -> m () setObjectDesc t = modifyObjectState $ \s -> s{objectDescOf=toText t} -- | Get the object's names getObjectNames :: MonadObject m => m [String] getObjectNames = do s <- getObjectState return $ objectNamesOf s -- | Add a name for the current object addObjectName :: MonadObject m => String -> m () addObjectName t = modifyObjectState $ \s -> s{objectNamesOf=t:objectNamesOf s} -- | Check if the given name matches our current object matchObjectName :: MonadObject m => String -> m Bool matchObjectName t = do s <- getObjectState return $ elem t $ objectNamesOf s -- | Add an attribute for the current object addObjectAttr :: MonadObject m => String -> m () addObjectAttr t = modifyObjectState $ \s -> s{objectAttributesOf=t:objectAttributesOf s} -- | Create a new player using the player monad subscribePlayer :: (MonadDungeon m,ChCounter m) => PlayerT m a -> m () subscribePlayer m = do s <- getDungeonState i <- liftM PlayerId countOn let r = rootNode $ roomsOf s (_,a) <- runPlayerT m $ PlayerState i r 100 none none none none none (avlInsert (Health,100) none) none none none 0 False putDungeonState s{playersOf=anyBstInsert a $ playersOf s} -- | Move the current player to the given room, but don't trigger anything. setPlayerRoom :: MonadPlayer m => NodeId -> m () setPlayerRoom r = modifyPlayerState $ \p -> p{playerRoomOf=r} -- | Check if the current object is a mob. getObjectIsMob :: (MonadObject m,Functor m) => m Bool getObjectIsMob = fmap (isJust . avlLookup Mobile . objectFeaturesOf) getObjectState -- | Set whether the current object is a mob. setObjectIsMob :: MonadObject m => Bool -> m () setObjectIsMob b = modifyObjectState $ \o -> o{objectFeaturesOf=a b Mobile $ objectFeaturesOf o} where a True = avlInsert a False = avlRemove -- | Set object kind. setObjectKind :: MonadObject m => KindId -> m () setObjectKind k = modifyObjectState $ \o -> o{objectKindOf=k} -- | Register an object kind. registerKind :: ChCounter m => m KindId registerKind = liftM KindId countOn -- | Check if the current object is acquirable. getObjectIsAcquirable :: (MonadObject m,Functor m) => m Bool getObjectIsAcquirable = fmap (isJust . avlLookup Acquirable . objectFeaturesOf) getObjectState -- | Set whether the current object is acquirable. setObjectIsAcquirable :: MonadObject m => Bool -> m () setObjectIsAcquirable b = modifyObjectState $ \o -> o{objectFeaturesOf=a b $ objectFeaturesOf o} where a True = avlInsert Acquirable a False = avlRemove Acquirable -- | Schedule an event for a given time offset (in milliseconds). schedule :: (MonadDungeon m,ChClock m) => Integer -> Handler -> m () schedule ms t = do now <- mgetstamp let t' = now + (realToFrac ms / 1000) s <- getDungeonState putDungeonState s{timeTriggersOf=avlInsert (t',Handler t) $ timeTriggersOf s} -- | Set the current mob's route setMobRoute :: MonadObject m => [NodeId] -> m () setMobRoute rs = modifyObjectState $ \s -> s{objectRouteOf=rs} -- | The given object may continue its route continueMobRoute :: ObjectId -> Handler continueMobRoute i = do rs <- roomOfObject i case rs of [r] -> do o <- withRoom r $ removeRoomObject i guardRoom r $ objectTriggerOnRoomLeaveOf o let (n:rs) = objectRouteOf o withRoom n $ insertRoomObject o{objectRouteOf=rs++[n]} guardRoom n $ objectTriggerOnRoomEnterOf o modifyPlayerState $ \s -> if playerOpponentOf s == i then s{playerOpponentOf=FalseObject} else s return () -- | Only run the given function if the player is inside the also given room. guardRoom :: MonadDungeon m => NodeId -> m () -> m () guardRoom n m = do s <- getDungeonState when (currentRoomOf s == Just n) m -- | Only run the given function if the given object exists guardObject :: MonadDungeon m => ObjectId -> m () -> m () guardObject o m = do rs <- roomOfObject o case rs of [] -> return () _ -> m -- | Only run the given function if the given object is in the also given room guardObjectInRoom :: MonadDungeon m => ObjectId -> NodeId -> m () -> m () guardObjectInRoom o r m = do rs <- roomOfObject o case rs of [r1] | r1 == r -> m _ -> return () -- | Only run the given function if the given object is not in the also given room guardObjectNotInRoom :: MonadDungeon m => ObjectId -> NodeId -> m () -> m () guardObjectNotInRoom o r m = do rs <- roomOfObject o case rs of [r1] | r1 == r -> return () _ -> m -- | Only run the given function if an object of the given kind is in the also given room guardKindInRoom :: MonadDungeon m => KindId -> NodeId -> m () -> m () guardKindInRoom k r m = do rs <- withRoom r getRoomState let b = null $ filter ((==k).objectKindOf) $ avlPreorder $ roomObjectsOf rs unless b m -- | Only run the given function if no object of the given kind is in the also given room guardKindNotInRoom :: MonadDungeon m => KindId -> NodeId -> m () -> m () guardKindNotInRoom k r m = do rs <- withRoom r getRoomState let b = null $ filter ((==k).objectKindOf) $ avlPreorder $ roomObjectsOf rs when b m -- | Determine which rooms contain the given object (won't be more than one, but we're careful) roomOfObject :: MonadDungeon m => ObjectId -> m [NodeId] roomOfObject o = (return . map nodeId . filter (isJust . avlLookup o . roomObjectsOf . nodeContent) . allNodes . roomsOf) =<< getDungeonState -- | Acquire the given object and put it in the player's inventory acquireObject :: ObjectId -> ChattyDungeonM () acquireObject i = do rs <- roomOfObject i case rs of [r] -> do s <- withObject i getObjectState if isJust $ avlLookup Acquirable $ objectFeaturesOf s then do o <- withRoom r $ removeRoomObject i modifyPlayerState $ \s -> s{playerInventoryOf=avlInsert o $ playerInventoryOf s} objectTriggerOnEachAcquireOf o else throwError CantAcquireThatError [] -> throwError CantSeeOneError _ -> throwError WhichOneError -- | Drop the given object and remove it from the player's inventory dropObject :: ObjectId -> ChattyDungeonM () dropObject i = do Just r <- (return . currentRoomOf) =<< getDungeonState ps <- getPlayerState let Just o = avlLookup i $ playerInventoryOf ps putPlayerState ps{playerInventoryOf=avlRemove i $ playerInventoryOf ps} insertRoomObject o -- | Add an object feature to the current object addFeature :: MonadObject m => Feature -> m () addFeature f = modifyObjectState $ \s -> s{objectFeaturesOf=avlInsert f $ objectFeaturesOf s} -- | Add a room description segment to the current object addDescSeg :: (MonadObject m,ChAtoms m) => String -> m () addDescSeg s = do a <- newAtom putAtom a s addFeature $ Described a -- | Register an object form and return its atom registerForm :: (ChAtoms m) => ObjectT m () -> m (Atom ObjectState) registerForm m = do a <- newAtom k <- liftM KindId countOn o <- constructObject m Nothing k putAtom a o return a -- | Instanciate a registered form instanciateForm :: (ChAtoms m,MonadRoom m) => Atom ObjectState -> m ObjectId instanciateForm a = do i <- countOn o <- getAtom a insertRoomObject o{objectIdOf=ObjectId i} return $ ObjectId i -- | Equip the given object at a given key equipObjectAt :: (MonadPlayer m,MonadError SplErr m) => EquipKey -> ObjectState -> m (Maybe ObjectState) equipObjectAt k o | isJust (avlLookup (Equipable k) $ objectFeaturesOf o) = do p <- getPlayerState let o1 = avlLookup k $ playerEquipOf p putPlayerState p{playerEquipOf=avlInsert (k,o) $ playerEquipOf p, playerInventoryOf=avlRemove (indexOf o) $ playerInventoryOf p} return o1 | otherwise = throwError CantEquipThatThereError -- | Equip the given object somewhere equipObject :: (MonadPlayer m,MonadError SplErr m) => ObjectState -> m (Maybe ObjectState) equipObject o = let equ (Equipable k) = [k] equ _ = none in case concatMap equ $ avlInorder $ objectFeaturesOf o of [] -> throwError CantEquipThatError [k] -> equipObjectAt k o ks -> do p <- getPlayerState case filter (isJust.flip avlLookup (playerEquipOf p)) ks of [] -> throwError WhereToEquipError [k] -> equipObjectAt k o _ -> throwError WhereToEquipError -- | Get equipped object getEquipment :: MonadPlayer m => EquipKey -> m (Maybe ObjectState) getEquipment k = liftM (avlLookup k.playerEquipOf) getPlayerState -- | Add equippable slot addEquipSlot :: MonadObject m => EquipKey -> m () addEquipSlot = addFeature . Equipable -- | Set/unset cooldown setCooldown :: MonadPlayer m => CooldownId -> Bool -> m () setCooldown c b = modifyPlayerState $ \p -> p{playerCooldownsOf=(if b then avlInsert else avlRemove) c $ playerCooldownsOf p} -- | Get cooldown state getCooldown :: MonadPlayer m => CooldownId -> m Bool getCooldown c = liftM (isJust . avlLookup c . playerCooldownsOf) getPlayerState -- | Get currency state getCurrency :: MonadPlayer m => CurrencyId -> m Int getCurrency c = liftM (joinMaybe . avlLookup c . playerCurrenciesOf) getPlayerState -- | Modify currency state modifyCurrency :: MonadPlayer m => CurrencyId -> (Int -> Int) -> m () modifyCurrency c f = modifyPlayerState $ \p -> let c1 = joinMaybe $ avlLookup c $ playerCurrenciesOf p in p{playerCurrenciesOf=avlInsert (c,f c1) $ playerCurrenciesOf p} -- | Register a currency registerCurrency :: (ChCounter m,MonadDungeon m) => String -> String -> m CurrencyId registerCurrency n d = do s <- getDungeonState i <- liftM CurrencyId countOn putDungeonState s{currenciesOf=avlInsert (Currency i d n) $ currenciesOf s} return i -- | Run a function in the context of a given player withPlayer :: MonadDungeon m => PlayerId -> PlayerT (RoomT m) a -> m a withPlayer i m = do d <- getDungeonState case anyBstLookup i $ playersOf d of Just p -> do (a,p') <- withRoom (playerRoomOf p) $ runPlayerT m p d <- getDungeonState putDungeonState d{playersOf=anyBstInsert p' $ playersOf d} return a -- | Run a function in the context of a given object withObject :: MonadDungeon m => ObjectId -> ObjectT (RoomT m) a -> m a withObject i m = do rs <- roomOfObject i ps <- getPlayerState case rs of [] -> case avlLookup i $ playerInventoryOf ps of Nothing -> error "Wtf, that object does not exist. Neither in a room nor in your inventory." Just o -> withRoom (playerRoomOf ps) $ do (a,o') <- runObjectT m o lift $ modifyPlayerState $ \p -> p{playerInventoryOf=avlInsert o' $ playerInventoryOf p} return a [r] -> withRoom r $ do rs <- getRoomState case anyBstLookup i $ roomObjectsOf rs of Just o -> do (a,o') <- runObjectT m o modifyRoomState $ \r -> r{roomObjectsOf=anyBstInsert o' $ roomObjectsOf r} return a -- | Damage a target (no matter whether player or mob) without setting focus damage :: DamageTarget -> Int -> ChattyDungeonM () damage (TargetPlayer p) v = withPlayer p $ modifyCurrency Health $ subtract v damage (TargetObject o) v = do d <- withObject o $ do modifyObjectState $ \o -> o{objectCurHealthOf=objectCurHealthOf o - v} liftM ((<=0).objectCurHealthOf) getObjectState when d $ do objectTriggerOnDieOf =<< withObject o getObjectState s <- getPlayerState when (playerOpponentOf s == o) $ putPlayerState s{playerOpponentOf=FalseObject} void $ removeRoomObject o -- | Focus an opponent focusOpponent :: ObjectId -> ChattyDungeonM () focusOpponent o = do os <- withObject o getObjectState if (isJust $ avlLookup Damagable $ objectFeaturesOf os) then modifyPlayerState $ \p -> p{playerOpponentOf=o} else throwError WontHitThatError -- | Deal damage to an opponent. Real damage is influenced by random. dealDamage :: Int -> ChattyDungeonM () dealDamage d = do let dmin = truncate (fromIntegral d * 0.8) dmax = truncate (fromIntegral d * 1.2) r <- mrandomR (dmin,dmax) o <- liftM playerOpponentOf getPlayerState damage (TargetObject o) r instance (Functor m,ChExpand m) => ChExpand (DungeonT m) where expand = lift . expand <=< liftM (replay.snd) . runRecorderT . expandDun expandDun :: (ChPrinter m,MonadDungeon m) => String -> m () expandDun [] = return () expandDun ('\\':'#':ss) = do mprint "#" expandDun ss expandDun ('#':'?':'{':ss) = let nm = takeBrace 0 ss rm = drop (length nm + 1) ss takeBrace 0 ('}':ss) = "" takeBrace n ('}':ss) = '}' : takeBrace (n-1) ss takeBrace n ('{':ss) = '{' : takeBrace (n+1) ss takeBrace n (s:ss) = s : takeBrace n ss in do o <- liftM playerOpponentOf getPlayerState case o of FalseObject -> expandDun rm _ -> do expandDun nm expandDun rm expandDun ('#':'{':ss) = let (nm,rm) = (takeWhile (/='}') &&& tail.dropWhile (/='}')) ss prnum Nothing Nothing i = show i prnum (Just d) Nothing i = show (i `div` d) prnum Nothing (Just m) i = let exp s l = replicate (l-length s) '0' ++ s in exp (show (i `mod` m)) (length $ show (m-1)) prnum (Just d) (Just m) i = prnum (Just d) Nothing i ++ "." ++ prnum Nothing (Just m) i prstr Nothing Nothing s = s prstr (Just d) _ s = take d s prstr Nothing (Just m) s = drop m s numfun (Just "inc") i = i + 1 numfun (Just "dec") i = i - 1 numfun Nothing i = i numfun _ _ = 0 strfun (Just "up") s = map toUpper s strfun (Just "down") s = map toLower s strfun (Just "capital") "" = "" strfun (Just "capital") (s:ss) = toUpper s : ss strfun Nothing s = s strfun _ _ = "" replace :: (ChPrinter m,MonadDungeon m) => String -> Maybe Int -> Maybe Int -> Maybe String -> m () replace "health" d m f = liftM (prnum d m . numfun f . joinMaybe . avlLookup Health . playerCurrenciesOf) getPlayerState >>= mprint replace "ohealth" d m f = do i <- liftM playerOpponentOf getPlayerState o <- withObject i getObjectState mprint $ prnum d m $ numfun f $ objectCurHealthOf o replace "otitle" d m f = do i <- liftM playerOpponentOf getPlayerState o <- withObject i getObjectState mprint $ prstr d m $ strfun f $ unpack $ objectTitleOf o replace s d m f = do cs <- liftM (avlInorder . currenciesOf) getDungeonState case filter ((==s).currencyNameOf) cs of [] -> return () [c] -> liftM (prnum d m . numfun f . joinMaybe . avlLookup (currencyIdOf c) . playerCurrenciesOf) getPlayerState >>= mprint cs -> mprintLn $ concat $ map currencyNameOf cs (cm,nmc) | '!' `elem` nm = (Just $ takeWhile (/='!') nm, tail $ dropWhile (/='!') nm) | otherwise = (Nothing, nm) (nm1,dm1,mm1) | '/' `elem` nmc = (takeWhile (/='/') nmc, Just $ read $ tail $ dropWhile (/='/') nmc, Nothing) | '%' `elem` nmc = (takeWhile (/='%') nmc, Nothing, Just $ read $ tail $ dropWhile (/='%') nmc) | '.' `elem` nmc = let xz = Just $ read $ tail $ dropWhile (/='.') nmc in (takeWhile (/='.') nmc, xz, xz) | otherwise = (nmc,Nothing,Nothing) in do replace nm1 dm1 mm1 cm expandDun rm expandDun ('#':ss) = let (nm,rm) = (takeWhile isAnum &&& dropWhile isAnum) ss isAnum = flip elem (['A'..'Z']++['a'..'z']++['0'..'9']) in expandDun ("#{"++nm++"}"++rm) expandDun (s:ss) = do mprint [s] expandDun ss -- | Consider the player's alcohol rate and mask the string with random underscores. drunken :: (ChRandom m,MonadPlayer m) => String -> m String drunken [] = return [] drunken ('}':cs) = do cx <- drunken cs return ('}':cx) drunken ('\n':cs) = do cx <- drunken cs return ('\n':cx) drunken ('%':'{':a:b:';':cs) | (a `elem` "DV") && (b `elem` ['0'..'7']) = do cx <- drunken cs return ('%':'{':a:b:';':cx) drunken (c:cs) = do p <- liftM playerAlcoholOf getPlayerState r <- mrandomR (0,100) cx <- drunken cs return $ if r < p then '_' : cx else c : cx -- | Consume alcohol. consumeAlcohol :: (ChPrinter m,ChRandom m,MonadPlayer m,MonadDungeon m,ChClock m) => Int -> m () consumeAlcohol r = do modifyPlayerState $ \p -> p{playerAlcoholOf=r+playerAlcoholOf p} p <- getPlayerState case playerAlcoholOf p of a | a > 50 -> mprintLn =<< drunken "You really feel very, very drunk." | a > 30 -> mprintLn =<< drunken "You definitely feel drunk." | a > 15 -> mprintLn =<< drunken "You feel kind of drunk." _ -> return () unless (playerSoberingActiveOf p) $ do modifyPlayerState $ \p -> p{playerSoberingActiveOf=True} let sobering :: Handler sobering = do modifyPlayerState $ \p -> p{playerAlcoholOf=max 0 (playerAlcoholOf p - 5)} schedule 120000 sobering schedule 120000 sobering