-- | Ways for the client to use player input via UI to produce server
-- requests, based on the client's view (visualized for the player)
-- of the game state.
module Game.LambdaHack.Client.UI
  ( -- * Querying the human player
    queryUI
    -- * UI monad and session type
  , MonadClientUI(..), SessionUI(..)
    -- * Updating UI state wrt game state changes
  , displayRespUpdAtomicUI, displayRespSfxAtomicUI
    -- * Startup and initialization
  , CCUI(..)
  , UIOptions, applyUIOptions, uCmdline, mkUIOptions
    -- * Operations exposed for "Game.LambdaHack.Client.LoopM"
  , ChanFrontend, chanFrontend, tryRestore, clientPrintUI
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , humanCommand
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES
import qualified Data.Map.Strict as M
import qualified Data.Text as T

import           Game.LambdaHack.Client.MonadClient
import           Game.LambdaHack.Client.Request
import           Game.LambdaHack.Client.State
import           Game.LambdaHack.Client.UI.Content.Input
import           Game.LambdaHack.Client.UI.ContentClientUI
import           Game.LambdaHack.Client.UI.DisplayAtomicM
import           Game.LambdaHack.Client.UI.Frame
import           Game.LambdaHack.Client.UI.FrameM
import           Game.LambdaHack.Client.UI.Frontend
import           Game.LambdaHack.Client.UI.HandleHelperM
import           Game.LambdaHack.Client.UI.HandleHumanM
import qualified Game.LambdaHack.Client.UI.Key as K
import           Game.LambdaHack.Client.UI.MonadClientUI
import           Game.LambdaHack.Client.UI.Msg
import           Game.LambdaHack.Client.UI.MsgM
import           Game.LambdaHack.Client.UI.SessionUI
import           Game.LambdaHack.Client.UI.Slideshow
import           Game.LambdaHack.Client.UI.SlideshowM
import           Game.LambdaHack.Client.UI.UIOptions
import           Game.LambdaHack.Client.UI.UIOptionsParse
import           Game.LambdaHack.Common.Actor
import           Game.LambdaHack.Common.ActorState
import           Game.LambdaHack.Common.ClientOptions
import           Game.LambdaHack.Common.Faction
import           Game.LambdaHack.Common.MonadStateRead
import           Game.LambdaHack.Common.State
import           Game.LambdaHack.Common.Types
import           Game.LambdaHack.Content.ModeKind

-- | Handle the move of a human player.
queryUI :: (MonadClient m, MonadClientUI m) => m RequestUI
queryUI :: m RequestUI
queryUI = do
  FactionId
side <- (StateClient -> FactionId) -> m FactionId
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient StateClient -> FactionId
sside
  Faction
fact <- (State -> Faction) -> m Faction
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Faction) -> m Faction)
-> (State -> Faction) -> m Faction
forall a b. (a -> b) -> a -> b
$ (EnumMap FactionId Faction -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! FactionId
side) (EnumMap FactionId Faction -> Faction)
-> (State -> EnumMap FactionId Faction) -> State -> Faction
forall b c a. (b -> c) -> (a -> b) -> a -> c
. State -> EnumMap FactionId Faction
sfactionD
  if Faction -> Bool
isAIFact Faction
fact then do
    m ()
forall (m :: * -> *). MonadClientUI m => m ()
recordHistory
    Bool
keyPressed <- m Bool
forall (m :: * -> *). MonadClientUI m => m Bool
anyKeyPressed
    if Bool
keyPressed Bool -> Bool -> Bool
&& Player -> LeaderMode
fleaderMode (Faction -> Player
gplayer Faction
fact) LeaderMode -> LeaderMode -> Bool
forall a. Eq a => a -> a -> Bool
/= LeaderMode
LeaderNull then do
      -- Menu is entered in @displayRespUpdAtomicUI@ at @UpdAutoFaction@.
      m ()
forall (m :: * -> *). MonadClientUI m => m ()
discardPressedKey
      -- Regaining control of faction cancels some --stopAfter*.
      (StateClient -> StateClient) -> m ()
forall (m :: * -> *).
MonadClient m =>
(StateClient -> StateClient) -> m ()
modifyClient ((StateClient -> StateClient) -> m ())
-> (StateClient -> StateClient) -> m ()
forall a b. (a -> b) -> a -> b
$ \cli :: StateClient
cli ->
        StateClient
cli {soptions :: ClientOptions
soptions = (StateClient -> ClientOptions
soptions StateClient
cli) { sstopAfterSeconds :: Maybe Int
sstopAfterSeconds = Maybe Int
forall a. Maybe a
Nothing
                                       , sstopAfterFrames :: Maybe Int
sstopAfterFrames = Maybe Int
forall a. Maybe a
Nothing }}
      RequestUI -> m RequestUI
forall (m :: * -> *) a. Monad m => a -> m a
return (ReqUI
ReqUIAutomate, Maybe ActorId
forall a. Maybe a
Nothing)  -- stop AI
    else do
      -- As long as UI faction is under AI control, check, once per move,
      -- for benchmark game stop.
      Maybe Int
stopAfterFrames <- (StateClient -> Maybe Int) -> m (Maybe Int)
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient ((StateClient -> Maybe Int) -> m (Maybe Int))
-> (StateClient -> Maybe Int) -> m (Maybe Int)
forall a b. (a -> b) -> a -> b
$ ClientOptions -> Maybe Int
sstopAfterFrames (ClientOptions -> Maybe Int)
-> (StateClient -> ClientOptions) -> StateClient -> Maybe Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StateClient -> ClientOptions
soptions
      Bool
bench <- (StateClient -> Bool) -> m Bool
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient ((StateClient -> Bool) -> m Bool)
-> (StateClient -> Bool) -> m Bool
forall a b. (a -> b) -> a -> b
$ ClientOptions -> Bool
sbenchmark (ClientOptions -> Bool)
-> (StateClient -> ClientOptions) -> StateClient -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StateClient -> ClientOptions
soptions
      let exitCmd :: ReqUI
exitCmd = if Bool
bench then ReqUI
ReqUIGameDropAndExit else ReqUI
ReqUIGameSaveAndExit
      case Maybe Int
stopAfterFrames of
        Nothing -> do
          Maybe Int
stopAfterSeconds <- (StateClient -> Maybe Int) -> m (Maybe Int)
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient ((StateClient -> Maybe Int) -> m (Maybe Int))
-> (StateClient -> Maybe Int) -> m (Maybe Int)
forall a b. (a -> b) -> a -> b
$ ClientOptions -> Maybe Int
sstopAfterSeconds (ClientOptions -> Maybe Int)
-> (StateClient -> ClientOptions) -> StateClient -> Maybe Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StateClient -> ClientOptions
soptions
          case Maybe Int
stopAfterSeconds of
            Nothing -> RequestUI -> m RequestUI
forall (m :: * -> *) a. Monad m => a -> m a
return (ReqUI
ReqUINop, Maybe ActorId
forall a. Maybe a
Nothing)
            Just stopS :: Int
stopS -> do
              Bool
exit <- Int -> m Bool
forall (m :: * -> *). MonadClientUI m => Int -> m Bool
elapsedSessionTimeGT Int
stopS
              if Bool
exit then do
                m ()
forall (m :: * -> *). MonadClientUI m => m ()
tellAllClipPS
                RequestUI -> m RequestUI
forall (m :: * -> *) a. Monad m => a -> m a
return (ReqUI
exitCmd, Maybe ActorId
forall a. Maybe a
Nothing)  -- ask server to exit
              else RequestUI -> m RequestUI
forall (m :: * -> *) a. Monad m => a -> m a
return (ReqUI
ReqUINop, Maybe ActorId
forall a. Maybe a
Nothing)
        Just stopF :: Int
stopF -> do
          Int
allNframes <- (SessionUI -> Int) -> m Int
forall (m :: * -> *) a. MonadClientUI m => (SessionUI -> a) -> m a
getsSession SessionUI -> Int
sallNframes
          Int
gnframes <- (SessionUI -> Int) -> m Int
forall (m :: * -> *) a. MonadClientUI m => (SessionUI -> a) -> m a
getsSession SessionUI -> Int
snframes
          if Int
allNframes Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
gnframes Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
stopF then do
            m ()
forall (m :: * -> *). MonadClientUI m => m ()
tellAllClipPS
            RequestUI -> m RequestUI
forall (m :: * -> *) a. Monad m => a -> m a
return (ReqUI
exitCmd, Maybe ActorId
forall a. Maybe a
Nothing)  -- ask server to exit
          else RequestUI -> m RequestUI
forall (m :: * -> *) a. Monad m => a -> m a
return (ReqUI
ReqUINop, Maybe ActorId
forall a. Maybe a
Nothing)
  else do
    let mleader :: Maybe ActorId
mleader = Faction -> Maybe ActorId
gleader Faction
fact
        !_A :: ()
_A = Bool -> () -> ()
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Maybe ActorId -> Bool
forall a. Maybe a -> Bool
isJust Maybe ActorId
mleader) ()
    ReqUI
req <- m ReqUI
forall (m :: * -> *). (MonadClient m, MonadClientUI m) => m ReqUI
humanCommand
    ActorId
leader2 <- m ActorId
forall (m :: * -> *). MonadClientUI m => m ActorId
getLeaderUI
    -- Don't send the leader switch to the server with these commands,
    -- to avoid leader death at resume if his HP <= 0. That would violate
    -- the principle that save and reload doesn't change game state.
    let saveCmd :: ReqUI -> Bool
saveCmd cmd :: ReqUI
cmd = case ReqUI
cmd of
          ReqUIGameDropAndExit -> Bool
True
          ReqUIGameSaveAndExit -> Bool
True
          ReqUIGameSave -> Bool
True
          _ -> Bool
False
    RequestUI -> m RequestUI
forall (m :: * -> *) a. Monad m => a -> m a
return (ReqUI
req, if Maybe ActorId
mleader Maybe ActorId -> Maybe ActorId -> Bool
forall a. Eq a => a -> a -> Bool
/= ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
leader2 Bool -> Bool -> Bool
&& Bool -> Bool
not (ReqUI -> Bool
saveCmd ReqUI
req)
                 then ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
leader2
                 else Maybe ActorId
forall a. Maybe a
Nothing)

-- | Let the human player issue commands until any command takes time.
humanCommand :: forall m. (MonadClient m, MonadClientUI m) => m ReqUI
humanCommand :: m ReqUI
humanCommand = do
  FontSetup{DisplayFont
propFont :: FontSetup -> DisplayFont
propFont :: DisplayFont
propFont} <- m FontSetup
forall (m :: * -> *). MonadClientUI m => m FontSetup
getFontSetup
  (SessionUI -> SessionUI) -> m ()
forall (m :: * -> *).
MonadClientUI m =>
(SessionUI -> SessionUI) -> m ()
modifySession ((SessionUI -> SessionUI) -> m ())
-> (SessionUI -> SessionUI) -> m ()
forall a b. (a -> b) -> a -> b
$ \sess :: SessionUI
sess -> SessionUI
sess {slastLost :: EnumSet ActorId
slastLost = EnumSet ActorId
forall k. EnumSet k
ES.empty}
  let loop :: Maybe ActorId -> m ReqUI
      loop :: Maybe ActorId -> m ReqUI
loop mOldLeader :: Maybe ActorId
mOldLeader = do
        Bool
keyPressed <- m Bool
forall (m :: * -> *). MonadClientUI m => m Bool
anyKeyPressed
        KeyMacroFrame
macroFrame <- (SessionUI -> KeyMacroFrame) -> m KeyMacroFrame
forall (m :: * -> *) a. MonadClientUI m => (SessionUI -> a) -> m a
getsSession SessionUI -> KeyMacroFrame
smacroFrame
        -- This message, in particular, disturbs.
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
keyPressed Bool -> Bool -> Bool
&& Bool -> Bool
not ([KM] -> Bool
forall a. [a] -> Bool
null (KeyMacro -> [KM]
unKeyMacro (KeyMacroFrame -> KeyMacro
keyPending KeyMacroFrame
macroFrame)))) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
          MsgClassShowAndSave -> Text -> m ()
forall (m :: * -> *) a.
(MonadClient m, MonadClientUI m, MsgShared a) =>
a -> Text -> m ()
msgAdd MsgClassShowAndSave
MsgActionWarning "*interrupted*"
        Report
report <- (SessionUI -> Report) -> m Report
forall (m :: * -> *) a. MonadClientUI m => (SessionUI -> a) -> m a
getsSession ((SessionUI -> Report) -> m Report)
-> (SessionUI -> Report) -> m Report
forall a b. (a -> b) -> a -> b
$ History -> Report
newReport (History -> Report)
-> (SessionUI -> History) -> SessionUI -> Report
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SessionUI -> History
shistory
        (SessionUI -> SessionUI) -> m ()
forall (m :: * -> *).
MonadClientUI m =>
(SessionUI -> SessionUI) -> m ()
modifySession ((SessionUI -> SessionUI) -> m ())
-> (SessionUI -> SessionUI) -> m ()
forall a b. (a -> b) -> a -> b
$ \sess :: SessionUI
sess -> SessionUI
sess {sreportNull :: Bool
sreportNull = Report -> Bool
nullVisibleReport Report
report}
        Slideshow
slides <- Bool -> [KM] -> m Slideshow
forall (m :: * -> *).
MonadClientUI m =>
Bool -> [KM] -> m Slideshow
reportToSlideshowKeepHalt Bool
False []
        [(PointUI, AttrLine)]
over <- case Slideshow -> Maybe (Slideshow, OKX)
unsnoc Slideshow
slides of
          Nothing -> [(PointUI, AttrLine)] -> m [(PointUI, AttrLine)]
forall (m :: * -> *) a. Monad m => a -> m a
return []
          Just (allButLast :: Slideshow
allButLast, (ov :: FontOverlayMap
ov, _)) ->
            if Slideshow
allButLast Slideshow -> Slideshow -> Bool
forall a. Eq a => a -> a -> Bool
== Slideshow
emptySlideshow
            then do
              -- Display the only generated slide while waiting for next key.
              -- Strip the "--end-" prompt from it, by ignoring @MonoFont@.
              let ovProp :: [(PointUI, AttrLine)]
ovProp = FontOverlayMap
ov FontOverlayMap -> DisplayFont -> [(PointUI, AttrLine)]
forall k a. Enum k => EnumMap k a -> k -> a
EM.! DisplayFont
propFont
              [(PointUI, AttrLine)] -> m [(PointUI, AttrLine)]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(PointUI, AttrLine)] -> m [(PointUI, AttrLine)])
-> [(PointUI, AttrLine)] -> m [(PointUI, AttrLine)]
forall a b. (a -> b) -> a -> b
$! if FontOverlayMap -> Int
forall k a. EnumMap k a -> Int
EM.size FontOverlayMap
ov Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 1 then [(PointUI, AttrLine)]
ovProp else [(PointUI, AttrLine)] -> [(PointUI, AttrLine)]
forall a. [a] -> [a]
init [(PointUI, AttrLine)]
ovProp
            else do
              -- Show, one by one, all slides, awaiting confirmation for each.
              m KM -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m KM -> m ()) -> m KM -> m ()
forall a b. (a -> b) -> a -> b
$ ColorMode -> [KM] -> Slideshow -> m KM
forall (m :: * -> *).
(MonadClient m, MonadClientUI m) =>
ColorMode -> [KM] -> Slideshow -> m KM
getConfirms ColorMode
ColorFull [KM
K.spaceKM, KM
K.escKM] Slideshow
slides
              -- Indicate that report wiped out.
              (SessionUI -> SessionUI) -> m ()
forall (m :: * -> *).
MonadClientUI m =>
(SessionUI -> SessionUI) -> m ()
modifySession ((SessionUI -> SessionUI) -> m ())
-> (SessionUI -> SessionUI) -> m ()
forall a b. (a -> b) -> a -> b
$ \sess :: SessionUI
sess -> SessionUI
sess {sreportNull :: Bool
sreportNull = Bool
True}
              -- Display base frame at the end.
              [(PointUI, AttrLine)] -> m [(PointUI, AttrLine)]
forall (m :: * -> *) a. Monad m => a -> m a
return []
        ActorId
leader <- m ActorId
forall (m :: * -> *). MonadClientUI m => m ActorId
getLeaderUI
        Actor
b <- (State -> Actor) -> m Actor
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
leader
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Actor -> Int64
bhp Actor
b Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= 0 Bool -> Bool -> Bool
&& ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
leader Maybe ActorId -> Maybe ActorId -> Bool
forall a. Eq a => a -> a -> Bool
/= Maybe ActorId
mOldLeader) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ ColorMode -> Text -> m ()
forall (m :: * -> *).
(MonadClient m, MonadClientUI m) =>
ColorMode -> Text -> m ()
displayMore ColorMode
ColorBW
          "If you move, the exertion will kill you. Consider asking for first aid instead."
        let ovs :: FontOverlayMap
ovs = [(DisplayFont, [(PointUI, AttrLine)])] -> FontOverlayMap
forall k a. Enum k => [(k, a)] -> EnumMap k a
EM.fromList [(DisplayFont
propFont, [(PointUI, AttrLine)]
over)]
        KM
km <- ColorMode -> FontOverlayMap -> Bool -> [KM] -> m KM
forall (m :: * -> *).
(MonadClient m, MonadClientUI m) =>
ColorMode -> FontOverlayMap -> Bool -> [KM] -> m KM
promptGetKey ColorMode
ColorFull FontOverlayMap
ovs Bool
False []
        Either MError ReqUI
abortOrCmd <- do
          -- Look up the key.
          CCUI{coinput :: CCUI -> InputContent
coinput=InputContent{Map KM CmdTriple
bcmdMap :: InputContent -> Map KM CmdTriple
bcmdMap :: Map KM CmdTriple
bcmdMap}} <- (SessionUI -> CCUI) -> m CCUI
forall (m :: * -> *) a. MonadClientUI m => (SessionUI -> a) -> m a
getsSession SessionUI -> CCUI
sccui
          case KM
km KM -> Map KM CmdTriple -> Maybe CmdTriple
forall k a. Ord k => k -> Map k a -> Maybe a
`M.lookup` Map KM CmdTriple
bcmdMap of
            Just (_, _, cmd :: HumanCmd
cmd) -> do
              (SessionUI -> SessionUI) -> m ()
forall (m :: * -> *).
MonadClientUI m =>
(SessionUI -> SessionUI) -> m ()
modifySession ((SessionUI -> SessionUI) -> m ())
-> (SessionUI -> SessionUI) -> m ()
forall a b. (a -> b) -> a -> b
$ \sess :: SessionUI
sess -> SessionUI
sess {swaitTimes :: Int
swaitTimes = if SessionUI -> Int
swaitTimes SessionUI
sess Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0
                                                          then - SessionUI -> Int
swaitTimes SessionUI
sess
                                                          else 0}
              KM -> HumanCmd -> m (Either MError ReqUI)
forall (m :: * -> *).
(MonadClient m, MonadClientUI m) =>
KM -> HumanCmd -> m (Either MError ReqUI)
restrictedCmdSemInCxtOfKM KM
km HumanCmd
cmd
            _ -> let msgKey :: String
msgKey = "unknown command '" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> KM -> String
K.showKM KM
km String -> String -> String
forall a. Semigroup a => a -> a -> a
<> "'"
                 in FailOrCmd ReqUI -> Either MError ReqUI
forall a. FailOrCmd a -> Either MError a
weaveJust (FailOrCmd ReqUI -> Either MError ReqUI)
-> m (FailOrCmd ReqUI) -> m (Either MError ReqUI)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> m (FailOrCmd ReqUI)
forall (m :: * -> *) a. MonadClientUI m => Text -> m (FailOrCmd a)
failWith (String -> Text
T.pack String
msgKey)
        -- GC macro stack if there are no actions left to handle,
        -- removing all unnecessary macro frames at once,
        -- but leaving the last one for user's in-game macros.
        (SessionUI -> SessionUI) -> m ()
forall (m :: * -> *).
MonadClientUI m =>
(SessionUI -> SessionUI) -> m ()
modifySession ((SessionUI -> SessionUI) -> m ())
-> (SessionUI -> SessionUI) -> m ()
forall a b. (a -> b) -> a -> b
$ \sess :: SessionUI
sess ->
          let (smacroFrameNew :: KeyMacroFrame
smacroFrameNew, smacroStackMew :: [KeyMacroFrame]
smacroStackMew) =
                KeyMacroFrame
-> [KeyMacroFrame] -> (KeyMacroFrame, [KeyMacroFrame])
dropEmptyMacroFrames (SessionUI -> KeyMacroFrame
smacroFrame SessionUI
sess) (SessionUI -> [KeyMacroFrame]
smacroStack SessionUI
sess)
          in SessionUI
sess { smacroFrame :: KeyMacroFrame
smacroFrame = KeyMacroFrame
smacroFrameNew
                  , smacroStack :: [KeyMacroFrame]
smacroStack = [KeyMacroFrame]
smacroStackMew }
        -- The command was failed or successful and if the latter,
        -- possibly took some time.
        case Either MError ReqUI
abortOrCmd of
          Right cmdS :: ReqUI
cmdS ->
            -- Exit the loop and let other actors act. No next key needed
            -- and no report could have been generated.
            ReqUI -> m ReqUI
forall (m :: * -> *) a. Monad m => a -> m a
return ReqUI
cmdS
          Left Nothing -> Maybe ActorId -> m ReqUI
loop (Maybe ActorId -> m ReqUI) -> Maybe ActorId -> m ReqUI
forall a b. (a -> b) -> a -> b
$ ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
leader
          Left (Just err :: FailError
err) -> do
            MsgClassShow -> Text -> m ()
forall (m :: * -> *) a.
(MonadClient m, MonadClientUI m, MsgShared a) =>
a -> Text -> m ()
msgAdd MsgClassShow
MsgActionAlert (Text -> m ()) -> Text -> m ()
forall a b. (a -> b) -> a -> b
$ FailError -> Text
showFailError FailError
err
            Maybe ActorId -> m ReqUI
loop (Maybe ActorId -> m ReqUI) -> Maybe ActorId -> m ReqUI
forall a b. (a -> b) -> a -> b
$ ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
leader
  Maybe ActorId -> m ReqUI
loop Maybe ActorId
forall a. Maybe a
Nothing