{-# LANGUAGE FlexibleContexts #-}
module Game.LambdaHack.Client.LoopM
( MonadClientReadResponse(..)
, loopCli
#ifdef EXPOSE_INTERNAL
, initAI, initUI, loopAI, longestDelay, loopUI
#endif
) where
import Prelude ()
import Game.LambdaHack.Core.Prelude
import qualified Data.EnumMap.Strict as EM
import Data.Time.Clock
import Data.Time.Clock.POSIX
import Game.LambdaHack.Atomic
import Game.LambdaHack.Client.HandleAtomicM
import Game.LambdaHack.Client.HandleResponseM
import Game.LambdaHack.Client.MonadClient
import Game.LambdaHack.Client.Response
import Game.LambdaHack.Client.State
import Game.LambdaHack.Client.UI
import Game.LambdaHack.Common.ClientOptions
import Game.LambdaHack.Common.Faction
import Game.LambdaHack.Common.MonadStateRead
import Game.LambdaHack.Common.State
class MonadClient m => MonadClientReadResponse m where
receiveResponse :: m Response
initAI :: MonadClient m => m ()
initAI :: forall (m :: * -> *). MonadClient m => m ()
initAI = do
side <- (StateClient -> FactionId) -> m FactionId
forall a. (StateClient -> a) -> m a
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient StateClient -> FactionId
sside
debugPossiblyPrint $ "AI client" <+> tshow side <+> "initializing."
initUI :: (MonadClient m, MonadClientUI m) => CCUI -> m ()
initUI :: forall (m :: * -> *).
(MonadClient m, MonadClientUI m) =>
CCUI -> m ()
initUI sccui :: CCUI
sccui@CCUI{ScreenContent
coscreen :: ScreenContent
coscreen :: CCUI -> ScreenContent
coscreen} = do
side <- (StateClient -> FactionId) -> m FactionId
forall a. (StateClient -> a) -> m a
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient StateClient -> FactionId
sside
soptions <- getsClient soptions
debugPossiblyPrint $ "UI client" <+> tshow side <+> "initializing."
schanF <- chanFrontend coscreen soptions
modifySession $ \SessionUI
sess -> SessionUI
sess {schanF, sccui}
loopCli :: ( MonadClientSetup m
, MonadClientUI m
, MonadClientAtomic m
, MonadClientReadResponse m
, MonadClientWriteRequest m )
=> CCUI -> UIOptions -> ClientOptions -> Bool -> m ()
loopCli :: forall (m :: * -> *).
(MonadClientSetup m, MonadClientUI m, MonadClientAtomic m,
MonadClientReadResponse m, MonadClientWriteRequest m) =>
CCUI -> UIOptions -> ClientOptions -> Bool -> m ()
loopCli CCUI
ccui UIOptions
sUIOptions ClientOptions
clientOptions Bool
startsNewGame = do
(StateClient -> StateClient) -> m ()
forall (m :: * -> *).
MonadClient m =>
(StateClient -> StateClient) -> m ()
modifyClient ((StateClient -> StateClient) -> m ())
-> (StateClient -> StateClient) -> m ()
forall a b. (a -> b) -> a -> b
$ \StateClient
cli -> StateClient
cli {soptions = clientOptions}
side <- (StateClient -> FactionId) -> m FactionId
forall a. (StateClient -> a) -> m a
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient StateClient -> FactionId
sside
hasUI <- clientHasUI
if not hasUI then initAI else initUI ccui
let cliendKindText = if Bool -> Bool
not Bool
hasUI then Text
"AI" else Text
"UI"
debugPossiblyPrint $ cliendKindText <+> "client"
<+> tshow side <+> "starting 1/4."
restored <-
if startsNewGame && not hasUI
then return False
else do
restoredG <- tryRestore
case restoredG of
Just (StateClient
cli, Maybe SessionUI
msess)-> do
case Maybe SessionUI
msess of
Just SessionUI
sess | Bool
hasUI -> do
schanF <- (SessionUI -> ChanFrontend) -> m ChanFrontend
forall a. (SessionUI -> a) -> m a
forall (m :: * -> *) a. MonadClientUI m => (SessionUI -> a) -> m a
getsSession SessionUI -> ChanFrontend
schanF
sccui <- getsSession sccui
putSession $ sess {schanF, sccui, sUIOptions}
Maybe SessionUI
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
if Bool
startsNewGame then
Bool -> m Bool
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
else do
let noAnim :: Bool
noAnim = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ ClientOptions -> Maybe Bool
snoAnim (ClientOptions -> Maybe Bool) -> ClientOptions -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ StateClient -> ClientOptions
soptions StateClient
cli
StateClient -> m ()
forall (m :: * -> *). MonadClient m => StateClient -> m ()
putClient StateClient
cli {soptions = clientOptions {snoAnim = Just noAnim}}
Bool -> m Bool
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
Maybe (StateClient, Maybe SessionUI)
Nothing -> Bool -> m Bool
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
debugPossiblyPrint $ cliendKindText <+> "client"
<+> tshow side <+> "starting 2/4."
tabA <- createTabBFS
tabB <- createTabBFS
modifyClient $ \StateClient
cli -> StateClient
cli {stabs = (tabA, tabB)}
cmd1 <- receiveResponse
debugPossiblyPrint $ cliendKindText <+> "client"
<+> tshow side <+> "starting 3/4."
case (restored, startsNewGame, cmd1) of
(Bool
True, Bool
False, RespUpdAtomic State
_ UpdResume{}) ->
() -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
(Bool
True, Bool
True, RespUpdAtomic State
_ UpdRestart{}) ->
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
hasUI (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Text -> m ()
forall (m :: * -> *). MonadClientUI m => Text -> m ()
clientPrintUI Text
"Ignoring an old savefile and starting a new game."
(Bool
False, Bool
False, RespUpdAtomic State
_ UpdResume{}) ->
String -> m ()
forall a. HasCallStack => String -> a
error (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Savefile of client " String -> String -> String
forall a. [a] -> [a] -> [a]
++ FactionId -> String
forall a. Show a => a -> String
show FactionId
side String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" not usable."
String -> () -> String
forall v. Show v => String -> v -> String
`showFailure` ()
(Bool
False, Bool
True, RespUpdAtomic State
_ UpdRestart{}) ->
() -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
(Bool
True, Bool
False, RespUpdAtomicNoState UpdResume{}) ->
m ()
forall a. HasCallStack => a
undefined
(Bool
True, Bool
True, RespUpdAtomicNoState UpdRestart{}) ->
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
hasUI (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Text -> m ()
forall (m :: * -> *). MonadClientUI m => Text -> m ()
clientPrintUI Text
"Ignoring an old savefile and starting a new game."
(Bool
False, Bool
False, RespUpdAtomicNoState UpdResume{}) ->
String -> m ()
forall a. HasCallStack => String -> a
error (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Savefile of client " String -> String -> String
forall a. [a] -> [a] -> [a]
++ FactionId -> String
forall a. Show a => a -> String
show FactionId
side String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" not usable."
String -> () -> String
forall v. Show v => String -> v -> String
`showFailure` ()
(Bool
False, Bool
True, RespUpdAtomicNoState UpdRestart{}) ->
() -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
(Bool, Bool, Response)
_ -> String -> m ()
forall a. HasCallStack => String -> a
error (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"unexpected command" String -> (FactionId, Bool, Response) -> String
forall v. Show v => String -> v -> String
`showFailure` (FactionId
side, Bool
restored, Response
cmd1)
handleResponse cmd1
debugPossiblyPrint $ cliendKindText <+> "client"
<+> tshow side <+> "starting 4/4."
if hasUI
then loopUI 0
else loopAI
side2 <- getsClient sside
debugPossiblyPrint $ cliendKindText <+> "client" <+> tshow side2
<+> "(initially" <+> tshow side <> ") stopped."
loopAI :: ( MonadClientSetup m
, MonadClientUI m
, MonadClientAtomic m
, MonadClientReadResponse m
, MonadClientWriteRequest m )
=> m ()
loopAI :: forall (m :: * -> *).
(MonadClientSetup m, MonadClientUI m, MonadClientAtomic m,
MonadClientReadResponse m, MonadClientWriteRequest m) =>
m ()
loopAI = do
cmd <- m Response
forall (m :: * -> *). MonadClientReadResponse m => m Response
receiveResponse
handleResponse cmd
quit <- getsClient squit
unless quit
loopAI
longestDelay :: POSIXTime
longestDelay :: POSIXTime
longestDelay = Pico -> POSIXTime
secondsToNominalDiffTime Pico
1
loopUI :: ( MonadClientSetup m
, MonadClientUI m
, MonadClientAtomic m
, MonadClientReadResponse m
, MonadClientWriteRequest m )
=> POSIXTime -> m ()
loopUI :: forall (m :: * -> *).
(MonadClientSetup m, MonadClientUI m, MonadClientAtomic m,
MonadClientReadResponse m, MonadClientWriteRequest m) =>
POSIXTime -> m ()
loopUI POSIXTime
timeSinceLastQuery = do
sreqPending <- (SessionUI -> Maybe RequestUI) -> m (Maybe RequestUI)
forall a. (SessionUI -> a) -> m a
forall (m :: * -> *) a. MonadClientUI m => (SessionUI -> a) -> m a
getsSession SessionUI -> Maybe RequestUI
sreqPending
sreqDelay <- getsSession sreqDelay
sregainControl <- getsSession sregainControl
keyPressed <- anyKeyPressed
let alarm = POSIXTime
timeSinceLastQuery POSIXTime -> POSIXTime -> Bool
forall a. Ord a => a -> a -> Bool
> POSIXTime
longestDelay
if | not alarm
&&
(sreqDelay == ReqDelayNot
|| sregainControl
|| (not keyPressed
&& sreqDelay /= ReqDelayHandled)) -> do
timeBefore <- liftIO getPOSIXTime
cmd <- receiveResponse
timeAfter <- liftIO getPOSIXTime
handleResponse cmd
quit <- getsClient squit
unless quit $ case cmd of
Response
RespQueryUI -> POSIXTime -> m ()
forall (m :: * -> *).
(MonadClientSetup m, MonadClientUI m, MonadClientAtomic m,
MonadClientReadResponse m, MonadClientWriteRequest m) =>
POSIXTime -> m ()
loopUI POSIXTime
0
Response
RespQueryUIunderAI ->
POSIXTime -> m ()
forall (m :: * -> *).
(MonadClientSetup m, MonadClientUI m, MonadClientAtomic m,
MonadClientReadResponse m, MonadClientWriteRequest m) =>
POSIXTime -> m ()
loopUI (POSIXTime -> m ()) -> POSIXTime -> m ()
forall a b. (a -> b) -> a -> b
$ POSIXTime -> POSIXTime
forall a. Enum a => a -> a
succ POSIXTime
longestDelay
Response
_ -> do
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Maybe RequestUI -> Bool
forall a. Maybe a -> Bool
isJust Maybe RequestUI
sreqPending) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
MsgClassShow -> Text -> m ()
forall (m :: * -> *) a.
(MonadClientUI m, MsgShared a) =>
a -> Text -> m ()
msgAdd MsgClassShow
MsgActionAlert Text
"Warning: server updated game state after current command was issued by the client but before it was received by the server."
POSIXTime -> m ()
forall (m :: * -> *).
(MonadClientSetup m, MonadClientUI m, MonadClientAtomic m,
MonadClientReadResponse m, MonadClientWriteRequest m) =>
POSIXTime -> m ()
loopUI (POSIXTime -> m ()) -> POSIXTime -> m ()
forall a b. (a -> b) -> a -> b
$ POSIXTime
timeSinceLastQuery POSIXTime -> POSIXTime -> POSIXTime
forall a. Num a => a -> a -> a
- POSIXTime
timeBefore POSIXTime -> POSIXTime -> POSIXTime
forall a. Num a => a -> a -> a
+ POSIXTime
timeAfter
| not sregainControl && (keyPressed
|| sreqDelay == ReqDelayHandled
|| isJust sreqPending) -> do
side <- getsClient sside
fact <- getsState $ (EM.! side) . sfactionD
if gunderAI fact then
modifySession $ \SessionUI
sess -> SessionUI
sess {sregainControl = True}
else do
resetPressedKeys
modifySession $ \SessionUI
sess -> SessionUI
sess {sreqDelay = ReqDelayHandled}
let msg = if Maybe RequestUI -> Bool
forall a. Maybe a -> Bool
isNothing Maybe RequestUI
sreqPending
then Text
"Server delayed asking us for a command. Regardless, UI is made accessible. Press ESC twice to listen to server some more."
else Text
"Server delayed receiving a command from us. The command is cancelled. Issue a new one."
msgAdd MsgActionAlert msg
mreqNew <- queryUI
msgAdd MsgPromptGeneric "Your client is listening to the server again."
pushReportFrame
modifySession $ \SessionUI
sess -> SessionUI
sess {sreqPending = mreqNew}
modifySession $ \SessionUI
sess -> SessionUI
sess {sreqDelay = ReqDelayNot}
loopUI 0
| otherwise -> do
modifySession $ \SessionUI
sess -> SessionUI
sess {sreqDelay = ReqDelayAlarm}
loopUI 0