-- | Basic client monad and related operations. module Game.LambdaHack.Client.MonadClient ( -- * Basic client monads MonadClientRead ( getsClient , liftIO -- exposed only to be implemented, not used ) , MonadClient(modifyClient) -- * Assorted primitives , getClient, putClient , debugPossiblyPrint, createTabBFS, dumpTextFile, rndToAction , condInMeleeM, insertInMeleeM ) where import Prelude () import Game.LambdaHack.Core.Prelude import qualified Control.Exception as Ex import Control.Monad.ST.Strict (stToIO) import qualified Control.Monad.Trans.State.Strict as St import qualified Data.EnumSet as ES import qualified Data.Primitive.PrimArray as PA import qualified Data.Text.IO as T import System.Directory import System.FilePath import System.IO (hFlush, stdout) import Game.LambdaHack.Client.State import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.ClientOptions import Game.LambdaHack.Common.File import Game.LambdaHack.Common.Kind import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.MonadStateRead import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.State import Game.LambdaHack.Common.Types import Game.LambdaHack.Content.RuleKind import Game.LambdaHack.Core.Random -- | Monad for reading client state. class MonadStateRead m => MonadClientRead m where getsClient :: (StateClient -> a) -> m a -- We do not provide a MonadIO instance, so that outside -- nobody can subvert the action monads by invoking arbitrary IO. liftIO :: IO a -> m a -- | Monad for writing to client state. class MonadClientRead m => MonadClient m where modifyClient :: (StateClient -> StateClient) -> m () getClient :: MonadClientRead m => m StateClient getClient = getsClient id putClient :: MonadClient m => StateClient -> m () putClient s = modifyClient (const s) debugPossiblyPrint :: MonadClient m => Text -> m () debugPossiblyPrint t = do sdbgMsgCli <- getsClient $ sdbgMsgCli . soptions when sdbgMsgCli $ liftIO $ do T.hPutStr stdout $! t <> "\n" -- hPutStrLn not atomic enough hFlush stdout createTabBFS :: MonadClient m => m (PA.PrimArray PointI) createTabBFS = do COps{corule=RuleContent{rWidthMax, rHeightMax}} <- getsState scops liftIO $ stToIO $ do tabAMutable <- PA.newPrimArray (rWidthMax * rHeightMax) -- always enough PA.unsafeFreezePrimArray tabAMutable dumpTextFile :: MonadClientRead m => Text -> FilePath -> m FilePath dumpTextFile t filename = liftIO $ do dataDir <- appDataDir tryCreateDir dataDir let path = dataDir filename Ex.handle (\(_ :: Ex.IOException) -> return ()) $ removeFile path tryWriteFile path t return path -- | Invoke pseudo-random computation with the generator kept in the state. rndToAction :: MonadClient m => Rnd a -> m a rndToAction r = do gen1 <- getsClient srandom let (a, gen2) = St.runState r gen1 modifyClient $ \cli -> cli {srandom = gen2} return a condInMeleeM :: MonadClientRead m => LevelId -> m Bool condInMeleeM lid = do condInMelee <- getsClient scondInMelee return $! lid `ES.member` condInMelee insertInMeleeM :: MonadClient m => LevelId -> m () insertInMeleeM lid = do side <- getsClient sside actorMaxSkills <- getsState sactorMaxSkills inM <- getsState $ inMelee actorMaxSkills side lid modifyClient $ \cli -> -- cli {scondInMelee = ES.alterF (const inM) lid $ scondInMelee cli} cli {scondInMelee = if inM then ES.insert lid $ scondInMelee cli else ES.delete lid $ scondInMelee cli}