1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
|
{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}
-- | The client UI session state.
module Game.LambdaHack.Client.UI.SessionUI
( SessionUI(..), ReqDelay(..), ItemDictUI, ItemRoles(..), AimMode(..)
, KeyMacro(..), KeyMacroFrame(..), RunParams(..), ChosenLore(..)
, emptySessionUI, emptyMacroFrame
, cycleMarkVision, toggleMarkSmell, cycleOverrideTut, getActorUI
) where
import Prelude ()
import Game.LambdaHack.Core.Prelude
import Data.Binary
import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Data.Time.Clock.POSIX
import GHC.Generics (Generic)
import qualified System.Random.SplitMix32 as SM
import Game.LambdaHack.Client.Request
import Game.LambdaHack.Client.State
import Game.LambdaHack.Client.UI.ActorUI
import Game.LambdaHack.Client.UI.ContentClientUI
import Game.LambdaHack.Client.UI.EffectDescription (DetailLevel (..))
import Game.LambdaHack.Client.UI.Frontend
import qualified Game.LambdaHack.Client.UI.Key as K
import Game.LambdaHack.Client.UI.Msg
import Game.LambdaHack.Client.UI.PointUI
import Game.LambdaHack.Client.UI.UIOptions
import Game.LambdaHack.Common.Actor
import Game.LambdaHack.Common.Faction
import Game.LambdaHack.Common.Item
import Game.LambdaHack.Common.Time
import Game.LambdaHack.Common.Types
import Game.LambdaHack.Content.ModeKind (ModeKind)
import Game.LambdaHack.Definition.Defs
-- | The information that is used across a human player playing session,
-- including many consecutive games in a single session,
-- including playing different teams. Some of it is saved, some is reset
-- when a new playing session starts. Nothing is tied to a faction/team,
-- but instead all to UI configuration and UI input and display history.
-- An important component is the frontend session.
data SessionUI = SessionUI
{ sreqPending :: Maybe RequestUI
-- ^ request created by a UI query
-- but not yet sent to the server
, sreqDelay :: ReqDelay -- ^ server delayed sending query to client
-- or receiving request from client
, sreqQueried :: Bool -- ^ player is now queried for a command
, sregainControl :: Bool -- ^ player requested to regain control
-- from AI ASAP
, sxhair :: Maybe Target -- ^ the common xhair
, sxhairGoTo :: Maybe Target -- ^ xhair set for last GoTo
, sactorUI :: ActorDictUI -- ^ assigned actor UI presentations
, sitemUI :: ItemDictUI -- ^ assigned item first seen level
, sroles :: ItemRoles -- ^ assignment of roles to items
, slastItemMove :: Maybe (CStore, CStore)
-- ^ last item move stores
, schanF :: ChanFrontend -- ^ connection with the frontend
, sccui :: CCUI -- ^ UI client content
, sUIOptions :: UIOptions -- ^ UI options as set by the player
, saimMode :: Maybe AimMode -- ^ aiming mode
, sxhairMoused :: Bool -- ^ last mouse aiming not vacuus
, sitemSel :: Maybe (ItemId, CStore, Bool)
-- ^ selected item, if any, it's store and
-- whether to override suitability check
, sselected :: ES.EnumSet ActorId
-- ^ the set of currently selected actors
, srunning :: Maybe RunParams
-- ^ parameters of the current run, if any
, shistory :: History -- ^ history of messages
, svictories :: EM.EnumMap (ContentId ModeKind) (M.Map Challenge Int)
-- ^ the number of games won by the UI faction per game mode
-- and per difficulty level
, scampings :: ES.EnumSet (ContentId ModeKind) -- ^ camped games
, srestarts :: ES.EnumSet (ContentId ModeKind) -- ^ restarted games
, spointer :: PointUI -- ^ mouse pointer position
, sautoYes :: Bool -- ^ whether to auto-clear prompts
, smacroFrame :: KeyMacroFrame -- ^ the head of the key macro stack
, smacroStack :: [KeyMacroFrame]
-- ^ the tail of the key macro stack
, slastLost :: ES.EnumSet ActorId
-- ^ actors that just got out of sight
, swaitTimes :: Int -- ^ player just waited this many times
, swasAutomated :: Bool -- ^ the player just exited AI automation
, smarkVision :: Int -- ^ mark leader and party FOV
, smarkSmell :: Bool -- ^ mark smell, if the leader can smell
, snxtScenario :: Int -- ^ next game scenario number
, scurTutorial :: Bool -- ^ whether current game is a tutorial
, snxtTutorial :: Bool -- ^ whether next game is to be tutorial
, soverrideTut :: Maybe Bool -- ^ override display of tutorial hints
, susedHints :: S.Set Msg -- ^ tutorial hints already shown this game
, smuteMessages :: Bool -- ^ whether to mute all new messages
, smenuIxMap :: M.Map String Int
-- ^ indices of last used menu items
, schosenLore :: ChosenLore -- ^ last lore chosen to display
, sdisplayNeeded :: Bool -- ^ current level needs displaying
, sturnDisplayed :: Bool -- ^ a frame was already displayed this turn
, sreportNull :: Bool -- ^ whether no visible report created
-- last UI faction turn or the report
-- wiped out from screen since
, sstart :: POSIXTime -- ^ this session start time
, sgstart :: POSIXTime -- ^ this game start time
, sallTime :: Time -- ^ clips from start of session
-- to current game start
, snframes :: Int -- ^ this game current frame count
, sallNframes :: Int -- ^ frame count from start of session
-- to current game start
, srandomUI :: SM.SMGen -- ^ current random generator for UI
}
data ReqDelay = ReqDelayNot | ReqDelayHandled | ReqDelayAlarm
deriving Eq
-- | Local macro buffer frame. Predefined macros have their own in-game macro
-- buffer, allowing them to record in-game macro, queue actions and repeat
-- the last macro's action.
-- Running predefined macro pushes new @KeyMacroFrame@ onto the stack. We pop
-- buffers from the stack if locally there are no actions pending to be handled.
data KeyMacroFrame = KeyMacroFrame
{ keyMacroBuffer :: Either [K.KM] KeyMacro -- ^ record keystrokes in Left;
-- repeat from Right
, keyPending :: KeyMacro -- ^ actions pending to be handled
, keyLast :: Maybe K.KM -- ^ last pressed key
} deriving Show
-- This can stay a map forever, not a vector, because it's added to often,
-- but never read from, except when the user requests item details.
type ItemDictUI = EM.EnumMap ItemId LevelId
-- | A collection of item identifier sets indicating what roles (possibly many)
-- an item has assigned.
newtype ItemRoles = ItemRoles (EM.EnumMap SLore (ES.EnumSet ItemId))
deriving (Show, Binary)
-- | Current aiming mode of a client.
data AimMode = AimMode
{ aimLevelId :: LevelId
, detailLevel :: DetailLevel
}
deriving (Show, Eq, Generic)
instance Binary AimMode
-- | In-game macros. We record menu navigation keystrokes and keystrokes
-- bound to commands with one exception --- we exclude keys that invoke
-- the @Record@ command, to avoid surprises.
-- Keys are kept in the same order in which they're meant to be replayed,
-- i.e. the first element of the list is replayed also as the first one.
newtype KeyMacro = KeyMacro {unKeyMacro :: [K.KM]}
deriving (Show, Eq, Binary, Semigroup, Monoid)
-- | Parameters of the current run.
data RunParams = RunParams
{ runLeader :: ActorId -- ^ the original leader from run start
, runMembers :: [ActorId] -- ^ the list of actors that take part
, runInitial :: Bool -- ^ initial run continuation by any
-- run participant, including run leader
, runStopMsg :: Maybe Text -- ^ message with the next stop reason
, runWaiting :: Int -- ^ waiting for others to move out of the way
}
deriving Show
-- | Last lore being aimed at.
data ChosenLore =
ChosenLore [(ActorId, Actor)] [(ItemId, ItemQuant)]
| ChosenNothing
emptySessionUI :: UIOptions -> SessionUI
emptySessionUI sUIOptions =
SessionUI
{ sreqPending = Nothing
, sreqDelay = ReqDelayNot
, sreqQueried = False
, sregainControl = False
, sxhair = Nothing
, sxhairGoTo = Nothing
, sactorUI = EM.empty
, sitemUI = EM.empty
, sroles = ItemRoles $ EM.fromDistinctAscList
$ zip [minBound..maxBound] (repeat ES.empty)
, slastItemMove = Nothing
, schanF = ChanFrontend $ const $
error $ "emptySessionUI: ChanFrontend" `showFailure` ()
, sccui = emptyCCUI
, sUIOptions
, saimMode = Nothing
, sxhairMoused = True
, sitemSel = Nothing
, sselected = ES.empty
, srunning = Nothing
, shistory = emptyHistory 0
, svictories = EM.empty
, scampings = ES.empty
, srestarts = ES.empty
, spointer = PointUI 0 0
, sautoYes = False
, smacroFrame = emptyMacroFrame
, smacroStack = []
, slastLost = ES.empty
, swaitTimes = 0
, swasAutomated = False
, smarkVision = 1
, smarkSmell = True
, snxtScenario = 0
, scurTutorial = False
, snxtTutorial = True -- matches @snxtScenario = 0@
, soverrideTut = Nothing
, susedHints = S.empty
, smuteMessages = False
, smenuIxMap = M.empty
, schosenLore = ChosenNothing
, sdisplayNeeded = False
, sturnDisplayed = False
, sreportNull = True
, sstart = 0
, sgstart = 0
, sallTime = timeZero
, snframes = 0
, sallNframes = 0
, srandomUI = SM.mkSMGen 0
}
emptyMacroFrame :: KeyMacroFrame
emptyMacroFrame = KeyMacroFrame (Right mempty) mempty Nothing
cycleMarkVision :: Int -> SessionUI -> SessionUI
cycleMarkVision delta sess =
sess {smarkVision = (smarkVision sess + delta) `mod` 3}
toggleMarkSmell :: SessionUI -> SessionUI
toggleMarkSmell sess = sess {smarkSmell = not (smarkSmell sess)}
cycleOverrideTut :: Int -> SessionUI -> SessionUI
cycleOverrideTut delta sess =
let ordering = cycle [Nothing, Just False, Just True]
in sess {soverrideTut =
let ix = fromJust $ elemIndex (soverrideTut sess) ordering
in ordering !! (ix + delta)}
getActorUI :: ActorId -> SessionUI -> ActorUI
getActorUI aid sess =
EM.findWithDefault (error $ "" `showFailure` (aid, sactorUI sess)) aid
$ sactorUI sess
instance Binary SessionUI where
put SessionUI{..} = do
put sxhair
put sactorUI
put sitemUI
put sroles
put sUIOptions
put saimMode
put sitemSel
put sselected
put srunning
put $ archiveReport shistory
-- avoid displaying ending messages again at game start
put svictories
put scampings
put srestarts
put smarkVision
put smarkSmell
put snxtScenario
put scurTutorial
put snxtTutorial
put soverrideTut
put susedHints
put (show srandomUI)
get = do
sxhair <- get
sactorUI <- get
sitemUI <- get
sroles <- get
sUIOptions <- get -- is overwritten ASAP, but useful for, e.g., crash debug
saimMode <- get
sitemSel <- get
sselected <- get
srunning <- get
shistory <- get
svictories <- get
scampings <- get
srestarts <- get
smarkVision <- get
smarkSmell <- get
snxtScenario <- get
scurTutorial <- get
snxtTutorial <- get
soverrideTut <- get
susedHints <- get
g <- get
let sreqPending = Nothing
sreqDelay = ReqDelayNot
sreqQueried = False
sregainControl = False
sxhairGoTo = Nothing
slastItemMove = Nothing
schanF = ChanFrontend $ const $
error $ "Binary: ChanFrontend" `showFailure` ()
sccui = emptyCCUI
sxhairMoused = True
spointer = PointUI 0 0
sautoYes = False
smacroFrame = emptyMacroFrame
smacroStack = []
slastLost = ES.empty
swaitTimes = 0
swasAutomated = False
smuteMessages = False
smenuIxMap = M.empty
schosenLore = ChosenNothing
sdisplayNeeded = False -- displayed regardless
sturnDisplayed = False
sreportNull = True
sstart = 0
sgstart = 0
sallTime = timeZero
snframes = 0
sallNframes = 0
srandomUI = read g
return $! SessionUI{..}
instance Binary RunParams where
put RunParams{..} = do
put runLeader
put runMembers
put runInitial
put runStopMsg
put runWaiting
get = do
runLeader <- get
runMembers <- get
runInitial <- get
runStopMsg <- get
runWaiting <- get
return $! RunParams{..}
|