LambdaHack 0.6.2.0 → 0.7.0.0
raw patch · 150 files changed
+7434/−6658 lines, 150 filesdep +optparse-applicativePVP ok
version bump matches the API change (PVP)
Dependencies added: optparse-applicative
API changes (from Hackage documentation)
- Game.LambdaHack.Atomic: UpdMsgAll :: Text -> UpdAtomic
- Game.LambdaHack.Atomic: class MonadStateRead m => MonadAtomic m
- Game.LambdaHack.Atomic: execSendPer :: MonadAtomic m => FactionId -> LevelId -> Perception -> Perception -> Perception -> m ()
- Game.LambdaHack.Atomic: execSfxAtomic :: MonadAtomic m => SfxAtomic -> m ()
- Game.LambdaHack.Atomic: execUpdAtomic :: MonadAtomic m => UpdAtomic -> m ()
- Game.LambdaHack.Atomic: generalMoveItem :: MonadStateRead m => Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
- Game.LambdaHack.Atomic: posProjBody :: Actor -> PosAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdMsgAll :: Text -> UpdAtomic
- Game.LambdaHack.Atomic.MonadAtomic: class MonadStateRead m => MonadAtomic m
- Game.LambdaHack.Atomic.MonadAtomic: execSendPer :: MonadAtomic m => FactionId -> LevelId -> Perception -> Perception -> Perception -> m ()
- Game.LambdaHack.Atomic.MonadAtomic: execSfxAtomic :: MonadAtomic m => SfxAtomic -> m ()
- Game.LambdaHack.Atomic.MonadAtomic: execUpdAtomic :: MonadAtomic m => UpdAtomic -> m ()
- Game.LambdaHack.Atomic.MonadStateWrite: updateActorMap :: (ActorMap -> ActorMap) -> Level -> Level
- Game.LambdaHack.Atomic.MonadStateWrite: updateFloor :: (ItemFloor -> ItemFloor) -> Level -> Level
- Game.LambdaHack.Atomic.MonadStateWrite: updateSmell :: (SmellMap -> SmellMap) -> Level -> Level
- Game.LambdaHack.Atomic.MonadStateWrite: updateTile :: (TileMap -> TileMap) -> Level -> Level
- Game.LambdaHack.Atomic.PosAtomicRead: generalMoveItem :: MonadStateRead m => Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
- Game.LambdaHack.Client: applyConfigToDebug :: COps -> Config -> DebugModeCli -> DebugModeCli
- Game.LambdaHack.Client: configCmdline :: Config -> [String]
- Game.LambdaHack.Client: data Config
- Game.LambdaHack.Client: data SessionUI
- Game.LambdaHack.Client: emptySessionUI :: Config -> SessionUI
- Game.LambdaHack.Client: mkConfig :: COps -> Bool -> IO Config
- Game.LambdaHack.Client.AI: condInMeleeM :: MonadClient m => Actor -> m Bool
- Game.LambdaHack.Client.AI: pickAI :: MonadClient m => Maybe (ActorId, RequestAnyAbility) -> ActorId -> m (ActorId, RequestAnyAbility)
- Game.LambdaHack.Client.AI: pickAction :: MonadClient m => ActorId -> Bool -> m RequestAnyAbility
- Game.LambdaHack.Client.AI.PickActorM: useTactics :: MonadClient m => ActorId -> m ()
- Game.LambdaHack.Client.CommonM: aspectRecordFromActorClient :: MonadClient m => Actor -> [(ItemId, Item)] -> m AspectRecord
- Game.LambdaHack.Client.CommonM: aspectRecordFromItemClient :: MonadClient m => ItemId -> Item -> m AspectRecord
- Game.LambdaHack.Client.CommonM: createSactorAspect :: MonadClient m => State -> m ActorAspect
- Game.LambdaHack.Client.CommonM: fullAssocsClient :: MonadClient m => ActorId -> [CStore] -> m [(ItemId, ItemFull)]
- Game.LambdaHack.Client.CommonM: itemToFullClient :: MonadClient m => m (ItemId -> ItemQuant -> ItemFull)
- Game.LambdaHack.Client.HandleAtomicM: cmdAtomicFilterCli :: MonadClient m => UpdAtomic -> m [UpdAtomic]
- Game.LambdaHack.Client.HandleResponseM: class MonadClient m => MonadClientReadResponse m
- Game.LambdaHack.Client.HandleResponseM: receiveResponse :: MonadClientReadResponse m => m Response
- Game.LambdaHack.Client.MonadClient: class MonadClient m => MonadClientSetup m
- Game.LambdaHack.Client.MonadClient: restartClient :: MonadClientSetup m => m ()
- Game.LambdaHack.Client.MonadClient: saveClient :: MonadClientSetup m => m ()
- Game.LambdaHack.Client.State: [sactorAspect] :: StateClient -> ActorAspect
- Game.LambdaHack.Client.State: [sdebugCli] :: StateClient -> DebugModeCli
- Game.LambdaHack.Client.State: [sdiscoAspect] :: StateClient -> DiscoveryAspect
- Game.LambdaHack.Client.State: [sdiscoKind] :: StateClient -> DiscoveryKind
- Game.LambdaHack.Client.UI: ColorBW :: ColorMode
- Game.LambdaHack.Client.UI: ColorFull :: ColorMode
- Game.LambdaHack.Client.UI: [sconfig] :: SessionUI -> Config
- Game.LambdaHack.Client.UI: addPressedEsc :: MonadClientUI m => m ()
- Game.LambdaHack.Client.UI: applyConfigToDebug :: COps -> Config -> DebugModeCli -> DebugModeCli
- Game.LambdaHack.Client.UI: configCmdline :: Config -> [String]
- Game.LambdaHack.Client.UI: data ColorMode
- Game.LambdaHack.Client.UI: data Config
- Game.LambdaHack.Client.UI: emptySessionUI :: Config -> SessionUI
- Game.LambdaHack.Client.UI: frontendShutdown :: MonadClientUI m => m ()
- Game.LambdaHack.Client.UI: getConfirms :: MonadClientUI m => ColorMode -> [KM] -> Slideshow -> m KM
- Game.LambdaHack.Client.UI: mkConfig :: COps -> Bool -> IO Config
- Game.LambdaHack.Client.UI: promptAdd :: MonadClientUI m => Text -> m ()
- Game.LambdaHack.Client.UI: putSession :: MonadClientUI m => SessionUI -> m ()
- Game.LambdaHack.Client.UI: reportToSlideshow :: MonadClientUI m => [KM] -> m Slideshow
- Game.LambdaHack.Client.UI.Config: Config :: [(KM, CmdTriple)] -> [(Int, (Text, Text))] -> Bool -> Bool -> Text -> Text -> Int -> Int -> Int -> Bool -> Int -> Int -> Bool -> Bool -> [String] -> Config
- Game.LambdaHack.Client.UI.Config: [configCmdline] :: Config -> [String]
- Game.LambdaHack.Client.UI.Config: [configColorIsBold] :: Config -> Bool
- Game.LambdaHack.Client.UI.Config: [configCommands] :: Config -> [(KM, CmdTriple)]
- Game.LambdaHack.Client.UI.Config: [configFontSize] :: Config -> Int
- Game.LambdaHack.Client.UI.Config: [configGtkFontFamily] :: Config -> Text
- Game.LambdaHack.Client.UI.Config: [configHeroNames] :: Config -> [(Int, (Text, Text))]
- Game.LambdaHack.Client.UI.Config: [configHistoryMax] :: Config -> Int
- Game.LambdaHack.Client.UI.Config: [configLaptop] :: Config -> Bool
- Game.LambdaHack.Client.UI.Config: [configMaxFps] :: Config -> Int
- Game.LambdaHack.Client.UI.Config: [configNoAnim] :: Config -> Bool
- Game.LambdaHack.Client.UI.Config: [configRunStopMsgs] :: Config -> Bool
- Game.LambdaHack.Client.UI.Config: [configSdlFonSizeAdd] :: Config -> Int
- Game.LambdaHack.Client.UI.Config: [configSdlFontFile] :: Config -> Text
- Game.LambdaHack.Client.UI.Config: [configSdlTtfSizeAdd] :: Config -> Int
- Game.LambdaHack.Client.UI.Config: [configVi] :: Config -> Bool
- Game.LambdaHack.Client.UI.Config: applyConfigToDebug :: COps -> Config -> DebugModeCli -> DebugModeCli
- Game.LambdaHack.Client.UI.Config: data Config
- Game.LambdaHack.Client.UI.Config: instance Control.DeepSeq.NFData Game.LambdaHack.Client.UI.Config.Config
- Game.LambdaHack.Client.UI.Config: instance Data.Binary.Class.Binary Game.LambdaHack.Client.UI.Config.Config
- Game.LambdaHack.Client.UI.Config: instance GHC.Generics.Generic Game.LambdaHack.Client.UI.Config.Config
- Game.LambdaHack.Client.UI.Config: instance GHC.Show.Show Game.LambdaHack.Client.UI.Config.Config
- Game.LambdaHack.Client.UI.Config: mkConfig :: COps -> Bool -> IO Config
- Game.LambdaHack.Client.UI.Content.KeyKind: [rhumanCommands] :: KeyKind -> [(KM, CmdTriple)]
- Game.LambdaHack.Client.UI.Frontend: KMP :: KM -> Point -> KMP
- Game.LambdaHack.Client.UI.Frontend: [kmpKeyMod] :: KMP -> KM
- Game.LambdaHack.Client.UI.Frontend: [kmpPointer] :: KMP -> Point
- Game.LambdaHack.Client.UI.Frontend: data FSession
- Game.LambdaHack.Client.UI.Frontend: data KMP
- Game.LambdaHack.Client.UI.Frontend.Common: KMP :: KM -> Point -> KMP
- Game.LambdaHack.Client.UI.Frontend.Common: [kmpKeyMod] :: KMP -> KM
- Game.LambdaHack.Client.UI.Frontend.Common: [kmpPointer] :: KMP -> Point
- Game.LambdaHack.Client.UI.Frontend.Common: data KMP
- Game.LambdaHack.Client.UI.Frontend.Teletype: display :: SingleFrame -> IO ()
- Game.LambdaHack.Client.UI.Frontend.Teletype: shutdown :: IO ()
- Game.LambdaHack.Client.UI.HumanCmd: noRemoteHumanCmd :: HumanCmd -> Bool
- Game.LambdaHack.Client.UI.Overlay: FrameForall :: (forall s. FrameST s) -> FrameForall
- Game.LambdaHack.Client.UI.Overlay: [unFrameForall] :: FrameForall -> forall s. FrameST s
- Game.LambdaHack.Client.UI.Overlay: itemDesc :: FactionId -> FactionDict -> Int -> CStore -> Time -> ItemFull -> AttrLine
- Game.LambdaHack.Client.UI.Overlay: newtype FrameForall
- Game.LambdaHack.Client.UI.Overlay: type FrameST s = Mutable Vector s Word32 -> ST s ()
- Game.LambdaHack.Client.UI.Overlay: writeLine :: Int -> AttrLine -> FrameForall
- Game.LambdaHack.Client.UI.OverlayM: describeMainKeys :: MonadClientUI m => m Text
- Game.LambdaHack.Client.UI.OverlayM: lookAt :: MonadClientUI m => Bool -> Text -> Bool -> Point -> ActorId -> Text -> m Text
- Game.LambdaHack.Client.UI.SessionUI: [sconfig] :: SessionUI -> Config
- Game.LambdaHack.Client.UI.SessionUI: type LastRecord = ([KM], [KM], Int)
- Game.LambdaHack.Common.ActorState: getItemBody :: ItemId -> State -> Item
- Game.LambdaHack.Common.ClientOptions: DebugModeCli :: Maybe Text -> Maybe Text -> Maybe Int -> Maybe Int -> Maybe Int -> Maybe Bool -> Maybe Int -> Bool -> Maybe Bool -> Bool -> Bool -> Maybe Text -> Maybe FilePath -> String -> Bool -> Bool -> Bool -> Bool -> Maybe Int -> Maybe Int -> DebugModeCli
- Game.LambdaHack.Common.ClientOptions: [sbenchmark] :: DebugModeCli -> Bool
- Game.LambdaHack.Common.ClientOptions: [scolorIsBold] :: DebugModeCli -> Maybe Bool
- Game.LambdaHack.Common.ClientOptions: [sdbgMsgCli] :: DebugModeCli -> Bool
- Game.LambdaHack.Common.ClientOptions: [sdisableAutoYes] :: DebugModeCli -> Bool
- Game.LambdaHack.Common.ClientOptions: [sdlFonSizeAdd] :: DebugModeCli -> Maybe Int
- Game.LambdaHack.Common.ClientOptions: [sdlFontFile] :: DebugModeCli -> Maybe Text
- Game.LambdaHack.Common.ClientOptions: [sdlTtfSizeAdd] :: DebugModeCli -> Maybe Int
- Game.LambdaHack.Common.ClientOptions: [sfontDir] :: DebugModeCli -> Maybe FilePath
- Game.LambdaHack.Common.ClientOptions: [sfontSize] :: DebugModeCli -> Maybe Int
- Game.LambdaHack.Common.ClientOptions: [sfrontendLazy] :: DebugModeCli -> Bool
- Game.LambdaHack.Common.ClientOptions: [sfrontendNull] :: DebugModeCli -> Bool
- Game.LambdaHack.Common.ClientOptions: [sfrontendTeletype] :: DebugModeCli -> Bool
- Game.LambdaHack.Common.ClientOptions: [sgtkFontFamily] :: DebugModeCli -> Maybe Text
- Game.LambdaHack.Common.ClientOptions: [smaxFps] :: DebugModeCli -> Maybe Int
- Game.LambdaHack.Common.ClientOptions: [snewGameCli] :: DebugModeCli -> Bool
- Game.LambdaHack.Common.ClientOptions: [snoAnim] :: DebugModeCli -> Maybe Bool
- Game.LambdaHack.Common.ClientOptions: [ssavePrefixCli] :: DebugModeCli -> String
- Game.LambdaHack.Common.ClientOptions: [sstopAfterFrames] :: DebugModeCli -> Maybe Int
- Game.LambdaHack.Common.ClientOptions: [sstopAfterSeconds] :: DebugModeCli -> Maybe Int
- Game.LambdaHack.Common.ClientOptions: [stitle] :: DebugModeCli -> Maybe Text
- Game.LambdaHack.Common.ClientOptions: data DebugModeCli
- Game.LambdaHack.Common.ClientOptions: defDebugModeCli :: DebugModeCli
- Game.LambdaHack.Common.ClientOptions: instance Data.Binary.Class.Binary Game.LambdaHack.Common.ClientOptions.DebugModeCli
- Game.LambdaHack.Common.ClientOptions: instance GHC.Classes.Eq Game.LambdaHack.Common.ClientOptions.DebugModeCli
- Game.LambdaHack.Common.ClientOptions: instance GHC.Generics.Generic Game.LambdaHack.Common.ClientOptions.DebugModeCli
- Game.LambdaHack.Common.ClientOptions: instance GHC.Show.Show Game.LambdaHack.Common.ClientOptions.DebugModeCli
- Game.LambdaHack.Common.Dice: (|*|) :: Dice -> Int -> Dice
- Game.LambdaHack.Common.Dice: diceConst :: Dice -> SimpleDice
- Game.LambdaHack.Common.Dice: diceLevel :: Dice -> SimpleDice
- Game.LambdaHack.Common.Dice: diceMult :: Dice -> Int
- Game.LambdaHack.Common.Dice: infixl 5 |*|
- Game.LambdaHack.Common.Dice: instance GHC.Classes.Eq Game.LambdaHack.Common.Dice.DiceXY
- Game.LambdaHack.Common.Dice: instance GHC.Classes.Ord Game.LambdaHack.Common.Dice.DiceXY
- Game.LambdaHack.Common.Dice: instance GHC.Num.Num Game.LambdaHack.Common.Dice.SimpleDice
- Game.LambdaHack.Common.Dice: type SimpleDice = Frequency Int
- Game.LambdaHack.Common.Faction: instance GHC.Classes.Ord Game.LambdaHack.Common.Faction.Faction
- Game.LambdaHack.Common.Item: ItemSourceFaction :: FactionId -> ItemSource
- Game.LambdaHack.Common.Item: ItemSourceLevel :: LevelId -> ItemSource
- Game.LambdaHack.Common.Item: data ItemSource
- Game.LambdaHack.Common.Item: instance Data.Binary.Class.Binary Game.LambdaHack.Common.Item.ItemSource
- Game.LambdaHack.Common.Item: instance Data.Hashable.Class.Hashable Game.LambdaHack.Common.Item.ItemSource
- Game.LambdaHack.Common.Item: instance GHC.Classes.Eq Game.LambdaHack.Common.Item.ItemSource
- Game.LambdaHack.Common.Item: instance GHC.Generics.Generic Game.LambdaHack.Common.Item.ItemSource
- Game.LambdaHack.Common.Item: instance GHC.Show.Show Game.LambdaHack.Common.Item.ItemSource
- Game.LambdaHack.Common.Item: itemToFull :: COps -> DiscoveryKind -> DiscoveryAspect -> ItemId -> Item -> ItemQuant -> ItemFull
- Game.LambdaHack.Common.ItemStrongest: unknownMelee :: [ItemFull] -> Bool
- Game.LambdaHack.Common.Request: AlterBlockActor :: ReqFailure
- Game.LambdaHack.Common.Request: AlterBlockItem :: ReqFailure
- Game.LambdaHack.Common.Request: AlterDistant :: ReqFailure
- Game.LambdaHack.Common.Request: AlterNothing :: ReqFailure
- Game.LambdaHack.Common.Request: AlterUnskilled :: ReqFailure
- Game.LambdaHack.Common.Request: AlterUnwalked :: ReqFailure
- Game.LambdaHack.Common.Request: ApplyCharging :: ReqFailure
- Game.LambdaHack.Common.Request: ApplyNoEffects :: ReqFailure
- Game.LambdaHack.Common.Request: ApplyOutOfReach :: ReqFailure
- Game.LambdaHack.Common.Request: ApplyRead :: ReqFailure
- Game.LambdaHack.Common.Request: ApplyUnskilled :: ReqFailure
- Game.LambdaHack.Common.Request: DisplaceAccess :: ReqFailure
- Game.LambdaHack.Common.Request: DisplaceBraced :: ReqFailure
- Game.LambdaHack.Common.Request: DisplaceDistant :: ReqFailure
- Game.LambdaHack.Common.Request: DisplaceDying :: ReqFailure
- Game.LambdaHack.Common.Request: DisplaceImmobile :: ReqFailure
- Game.LambdaHack.Common.Request: DisplaceProjectiles :: ReqFailure
- Game.LambdaHack.Common.Request: DisplaceSupported :: ReqFailure
- Game.LambdaHack.Common.Request: EqpOverfull :: ReqFailure
- Game.LambdaHack.Common.Request: EqpStackFull :: ReqFailure
- Game.LambdaHack.Common.Request: ItemNotCalm :: ReqFailure
- Game.LambdaHack.Common.Request: ItemNothing :: ReqFailure
- Game.LambdaHack.Common.Request: MeleeDistant :: ReqFailure
- Game.LambdaHack.Common.Request: MeleeSelf :: ReqFailure
- Game.LambdaHack.Common.Request: MoveNothing :: ReqFailure
- Game.LambdaHack.Common.Request: NoChangeDunLeader :: ReqFailure
- Game.LambdaHack.Common.Request: NotCalmPrecious :: ReqFailure
- Game.LambdaHack.Common.Request: ProjectAimOnself :: ReqFailure
- Game.LambdaHack.Common.Request: ProjectBlockActor :: ReqFailure
- Game.LambdaHack.Common.Request: ProjectBlockTerrain :: ReqFailure
- Game.LambdaHack.Common.Request: ProjectLobable :: ReqFailure
- Game.LambdaHack.Common.Request: ProjectOutOfReach :: ReqFailure
- Game.LambdaHack.Common.Request: ProjectUnskilled :: ReqFailure
- Game.LambdaHack.Common.Request: ReqAINop :: ReqAI
- Game.LambdaHack.Common.Request: ReqAITimed :: RequestAnyAbility -> ReqAI
- Game.LambdaHack.Common.Request: ReqUIAutomate :: ReqUI
- Game.LambdaHack.Common.Request: ReqUIGameExit :: ReqUI
- Game.LambdaHack.Common.Request: ReqUIGameRestart :: (GroupName ModeKind) -> Challenge -> ReqUI
- Game.LambdaHack.Common.Request: ReqUIGameSave :: ReqUI
- Game.LambdaHack.Common.Request: ReqUINop :: ReqUI
- Game.LambdaHack.Common.Request: ReqUITactic :: Tactic -> ReqUI
- Game.LambdaHack.Common.Request: ReqUITimed :: RequestAnyAbility -> ReqUI
- Game.LambdaHack.Common.Request: RequestAnyAbility :: (RequestTimed a) -> RequestAnyAbility
- Game.LambdaHack.Common.Request: TriggerNothing :: ReqFailure
- Game.LambdaHack.Common.Request: [ReqAlter] :: Point -> RequestTimed 'AbAlter
- Game.LambdaHack.Common.Request: [ReqApply] :: ItemId -> CStore -> RequestTimed 'AbApply
- Game.LambdaHack.Common.Request: [ReqDisplace] :: ActorId -> RequestTimed 'AbDisplace
- Game.LambdaHack.Common.Request: [ReqMelee] :: ActorId -> ItemId -> CStore -> RequestTimed 'AbMelee
- Game.LambdaHack.Common.Request: [ReqMoveItems] :: [(ItemId, Int, CStore, CStore)] -> RequestTimed 'AbMoveItem
- Game.LambdaHack.Common.Request: [ReqMove] :: Vector -> RequestTimed 'AbMove
- Game.LambdaHack.Common.Request: [ReqProject] :: Point -> Int -> ItemId -> CStore -> RequestTimed 'AbProject
- Game.LambdaHack.Common.Request: [ReqWait10] :: RequestTimed 'AbWait
- Game.LambdaHack.Common.Request: [ReqWait] :: RequestTimed 'AbWait
- Game.LambdaHack.Common.Request: data ReqAI
- Game.LambdaHack.Common.Request: data ReqFailure
- Game.LambdaHack.Common.Request: data ReqUI
- Game.LambdaHack.Common.Request: data RequestAnyAbility
- Game.LambdaHack.Common.Request: data RequestTimed :: Ability -> *
- Game.LambdaHack.Common.Request: impossibleReqFailure :: ReqFailure -> Bool
- Game.LambdaHack.Common.Request: instance Data.Binary.Class.Binary Game.LambdaHack.Common.Request.ReqFailure
- Game.LambdaHack.Common.Request: instance GHC.Classes.Eq Game.LambdaHack.Common.Request.ReqFailure
- Game.LambdaHack.Common.Request: instance GHC.Generics.Generic Game.LambdaHack.Common.Request.ReqFailure
- Game.LambdaHack.Common.Request: instance GHC.Show.Show (Game.LambdaHack.Common.Request.RequestTimed a)
- Game.LambdaHack.Common.Request: instance GHC.Show.Show Game.LambdaHack.Common.Request.ReqAI
- Game.LambdaHack.Common.Request: instance GHC.Show.Show Game.LambdaHack.Common.Request.ReqFailure
- Game.LambdaHack.Common.Request: instance GHC.Show.Show Game.LambdaHack.Common.Request.ReqUI
- Game.LambdaHack.Common.Request: instance GHC.Show.Show Game.LambdaHack.Common.Request.RequestAnyAbility
- Game.LambdaHack.Common.Request: permittedApply :: Time -> Int -> Bool -> [Char] -> ItemFull -> Either ReqFailure Bool
- Game.LambdaHack.Common.Request: permittedPrecious :: Bool -> Bool -> ItemFull -> Either ReqFailure Bool
- Game.LambdaHack.Common.Request: permittedProject :: Bool -> Int -> Bool -> [Char] -> ItemFull -> Either ReqFailure Bool
- Game.LambdaHack.Common.Request: permittedProjectAI :: Int -> Bool -> ItemFull -> Bool
- Game.LambdaHack.Common.Request: showReqFailure :: ReqFailure -> Text
- Game.LambdaHack.Common.Request: timedToUI :: RequestTimed a -> ReqUI
- Game.LambdaHack.Common.Request: type RequestAI = (ReqAI, Maybe ActorId)
- Game.LambdaHack.Common.Request: type RequestUI = (ReqUI, Maybe ActorId)
- Game.LambdaHack.Common.Response: ChanServer :: CliSerQueue Response -> CliSerQueue RequestAI -> Maybe (CliSerQueue RequestUI) -> ChanServer
- Game.LambdaHack.Common.Response: RespQueryAI :: ActorId -> Response
- Game.LambdaHack.Common.Response: RespQueryUI :: Response
- Game.LambdaHack.Common.Response: RespSfxAtomic :: SfxAtomic -> Response
- Game.LambdaHack.Common.Response: RespUpdAtomic :: UpdAtomic -> Response
- Game.LambdaHack.Common.Response: [requestAIS] :: ChanServer -> CliSerQueue RequestAI
- Game.LambdaHack.Common.Response: [requestUIS] :: ChanServer -> Maybe (CliSerQueue RequestUI)
- Game.LambdaHack.Common.Response: [responseS] :: ChanServer -> CliSerQueue Response
- Game.LambdaHack.Common.Response: data ChanServer
- Game.LambdaHack.Common.Response: data Response
- Game.LambdaHack.Common.Response: instance GHC.Show.Show Game.LambdaHack.Common.Response.Response
- Game.LambdaHack.Common.Response: type CliSerQueue = MVar
- Game.LambdaHack.Content.ItemKind: instance GHC.Classes.Ord Game.LambdaHack.Content.ItemKind.Effect
- Game.LambdaHack.Content.ItemKind: instance GHC.Classes.Ord Game.LambdaHack.Content.ItemKind.TimerDice
- Game.LambdaHack.Content.ModeKind: instance GHC.Classes.Eq Game.LambdaHack.Content.ModeKind.Roster
- Game.LambdaHack.Content.ModeKind: instance GHC.Classes.Ord Game.LambdaHack.Content.ModeKind.Player
- Game.LambdaHack.SampleImplementation.SampleMonadClient: instance GHC.Generics.Generic Game.LambdaHack.SampleImplementation.SampleMonadClient.CliState
- Game.LambdaHack.SampleImplementation.SampleMonadClient: instance Game.LambdaHack.Atomic.MonadAtomic.MonadAtomic Game.LambdaHack.SampleImplementation.SampleMonadClient.CliImplementation
- Game.LambdaHack.SampleImplementation.SampleMonadClient: instance Game.LambdaHack.Client.HandleResponseM.MonadClientReadResponse Game.LambdaHack.SampleImplementation.SampleMonadClient.CliImplementation
- Game.LambdaHack.SampleImplementation.SampleMonadClient: instance Game.LambdaHack.Client.MonadClient.MonadClientSetup Game.LambdaHack.SampleImplementation.SampleMonadClient.CliImplementation
- Game.LambdaHack.SampleImplementation.SampleMonadServer: instance Game.LambdaHack.Atomic.MonadAtomic.MonadAtomic Game.LambdaHack.SampleImplementation.SampleMonadServer.SerImplementation
- Game.LambdaHack.Server: DebugModeSer :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe (GroupName ModeKind) -> Bool -> Bool -> Maybe StdGen -> Maybe StdGen -> Bool -> Challenge -> Bool -> String -> Bool -> DebugModeCli -> DebugModeSer
- Game.LambdaHack.Server: [sdebugCli] :: DebugModeSer -> DebugModeCli
- Game.LambdaHack.Server: [sniffIn] :: DebugModeSer -> Bool
- Game.LambdaHack.Server: [sniffOut] :: DebugModeSer -> Bool
- Game.LambdaHack.Server: data DebugModeSer
- Game.LambdaHack.Server: debugArgs :: [String] -> DebugModeSer
- Game.LambdaHack.Server.Commandline: debugArgs :: [String] -> DebugModeSer
- Game.LambdaHack.Server.CommonM: getPerFid :: MonadServer m => FactionId -> LevelId -> m Perception
- Game.LambdaHack.Server.DebugM: instance GHC.Show.Show a => GHC.Show.Show (Game.LambdaHack.Server.DebugM.DebugAid a)
- Game.LambdaHack.Server.DungeonGen.AreaRnd: mkFixed :: (X, Y) -> Area -> Point -> Area
- Game.LambdaHack.Server.Fov: aspectRecordFromActorServer :: DiscoveryAspect -> Actor -> AspectRecord
- Game.LambdaHack.Server.HandleAtomicM: addItemToActor :: MonadServer m => ItemId -> Int -> ActorId -> m ()
- Game.LambdaHack.Server.ItemM: fullAssocsServer :: MonadServer m => ActorId -> [CStore] -> m [(ItemId, ItemFull)]
- Game.LambdaHack.Server.ItemM: itemToFullServer :: MonadServer m => m (ItemId -> ItemQuant -> ItemFull)
- Game.LambdaHack.Server.LoopM: gameExit :: (MonadAtomic m, MonadServerReadRequest m) => m ()
- Game.LambdaHack.Server.LoopM: writeSaveAll :: (MonadAtomic m, MonadServer m) => Bool -> m ()
- Game.LambdaHack.Server.MonadServer: saveChanServer :: MonadServer m => m (ChanSave (State, StateServer))
- Game.LambdaHack.Server.StartM: updatePer :: (MonadAtomic m, MonadServer m) => FactionId -> LevelId -> m ()
- Game.LambdaHack.Server.State: DebugModeSer :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe (GroupName ModeKind) -> Bool -> Bool -> Maybe StdGen -> Maybe StdGen -> Bool -> Challenge -> Bool -> String -> Bool -> DebugModeCli -> DebugModeSer
- Game.LambdaHack.Server.State: RNGs :: Maybe StdGen -> Maybe StdGen -> RNGs
- Game.LambdaHack.Server.State: [dungeonRandomGenerator] :: RNGs -> Maybe StdGen
- Game.LambdaHack.Server.State: [sactorAspect] :: StateServer -> ActorAspect
- Game.LambdaHack.Server.State: [sallClear] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sautomateAll] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sboostRandomItem] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [scurChalSer] :: DebugModeSer -> Challenge
- Game.LambdaHack.Server.State: [sdbgMsgSer] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sdebugCli] :: DebugModeSer -> DebugModeCli
- Game.LambdaHack.Server.State: [sdebugNxt] :: StateServer -> DebugModeSer
- Game.LambdaHack.Server.State: [sdebugSer] :: StateServer -> DebugModeSer
- Game.LambdaHack.Server.State: [sdiscoAspect] :: StateServer -> DiscoveryAspect
- Game.LambdaHack.Server.State: [sdiscoKind] :: StateServer -> DiscoveryKind
- Game.LambdaHack.Server.State: [sdumpInitRngs] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sdungeonRng] :: DebugModeSer -> Maybe StdGen
- Game.LambdaHack.Server.State: [sgameMode] :: DebugModeSer -> Maybe (GroupName ModeKind)
- Game.LambdaHack.Server.State: [skeepAutomated] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sknowEvents] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sknowItems] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sknowMap] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [smainRng] :: DebugModeSer -> Maybe StdGen
- Game.LambdaHack.Server.State: [snewGameSer] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sniffIn] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [sniffOut] :: DebugModeSer -> Bool
- Game.LambdaHack.Server.State: [ssavePrefixSer] :: DebugModeSer -> String
- Game.LambdaHack.Server.State: [startingRandomGenerator] :: RNGs -> Maybe StdGen
- Game.LambdaHack.Server.State: data DebugModeSer
- Game.LambdaHack.Server.State: data RNGs
- Game.LambdaHack.Server.State: defDebugModeSer :: DebugModeSer
- Game.LambdaHack.Server.State: instance Data.Binary.Class.Binary Game.LambdaHack.Server.State.DebugModeSer
- Game.LambdaHack.Server.State: instance Data.Binary.Class.Binary Game.LambdaHack.Server.State.RNGs
- Game.LambdaHack.Server.State: instance GHC.Show.Show Game.LambdaHack.Server.State.DebugModeSer
- Game.LambdaHack.Server.State: instance GHC.Show.Show Game.LambdaHack.Server.State.RNGs
+ Game.LambdaHack.Atomic: AtomicFail :: String -> AtomicFail
+ Game.LambdaHack.Atomic: SfxSortSlots :: SfxAtomic
+ Game.LambdaHack.Atomic: UpdCoverServer :: ItemId -> AspectRecord -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdDiscoverServer :: ItemId -> AspectRecord -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdLoseItemBag :: Container -> ItemBag -> [(ItemId, Item)] -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdSpotItemBag :: Container -> ItemBag -> [(ItemId, Item)] -> UpdAtomic
+ Game.LambdaHack.Atomic: newtype AtomicFail
+ Game.LambdaHack.Atomic: putState :: MonadStateWrite m => State -> m ()
+ Game.LambdaHack.Atomic.CmdAtomic: SfxSortSlots :: SfxAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdCoverServer :: ItemId -> AspectRecord -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdDiscoverServer :: ItemId -> AspectRecord -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdLoseItemBag :: Container -> ItemBag -> [(ItemId, Item)] -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdSpotItemBag :: Container -> ItemBag -> [(ItemId, Item)] -> UpdAtomic
+ Game.LambdaHack.Atomic.HandleAtomicWrite: ageLevel :: MonadStateWrite m => Delta Time -> LevelId -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: discoverKind :: MonadStateWrite m => ItemKindIx -> Id ItemKind -> m KindMean
+ Game.LambdaHack.Atomic.HandleAtomicWrite: discoverSeed :: MonadStateWrite m => ItemId -> ItemSeed -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updCover :: Container -> ItemId -> Id ItemKind -> ItemSeed -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updCoverKind :: Container -> ItemKindIx -> Id ItemKind -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updCoverSeed :: Container -> ItemId -> ItemSeed -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updCoverServer :: MonadStateWrite m => ItemId -> AspectRecord -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updDiscover :: MonadStateWrite m => Container -> ItemId -> Id ItemKind -> ItemSeed -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updDiscoverKind :: MonadStateWrite m => Container -> ItemKindIx -> Id ItemKind -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updDiscoverSeed :: MonadStateWrite m => Container -> ItemId -> ItemSeed -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updDiscoverServer :: MonadStateWrite m => ItemId -> AspectRecord -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updLoseItemBag :: MonadStateWrite m => Container -> ItemBag -> [(ItemId, Item)] -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updSearchTile :: MonadStateWrite m => ActorId -> Point -> Id TileKind -> m ()
+ Game.LambdaHack.Atomic.HandleAtomicWrite: updSpotItemBag :: MonadStateWrite m => Container -> ItemBag -> [(ItemId, Item)] -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: AtomicFail :: String -> AtomicFail
+ Game.LambdaHack.Atomic.MonadStateWrite: addAis :: MonadStateWrite m => [(ItemId, Item)] -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: addItemToActor :: MonadStateWrite m => ItemId -> Item -> Int -> ActorId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: atomicFail :: String -> a
+ Game.LambdaHack.Atomic.MonadStateWrite: deleteBagContainer :: MonadStateWrite m => ItemBag -> Container -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: deleteItemEmbed :: MonadStateWrite m => ItemId -> ItemQuant -> LevelId -> Point -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: deleteItemEqp :: MonadStateWrite m => ItemId -> ItemQuant -> ActorId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: deleteItemFloor :: MonadStateWrite m => ItemId -> ItemQuant -> LevelId -> Point -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: deleteItemInv :: MonadStateWrite m => ItemId -> ItemQuant -> ActorId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: deleteItemOrgan :: MonadStateWrite m => ItemId -> ItemQuant -> ActorId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: deleteItemSha :: MonadStateWrite m => ItemId -> ItemQuant -> FactionId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertBagContainer :: MonadStateWrite m => ItemBag -> Container -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemEmbed :: MonadStateWrite m => ItemId -> ItemQuant -> LevelId -> Point -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemEqp :: MonadStateWrite m => ItemId -> ItemQuant -> ActorId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemFloor :: MonadStateWrite m => ItemId -> ItemQuant -> LevelId -> Point -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemInv :: MonadStateWrite m => ItemId -> ItemQuant -> ActorId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemOrgan :: MonadStateWrite m => ItemId -> ItemQuant -> ActorId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemSha :: MonadStateWrite m => ItemId -> ItemQuant -> FactionId -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: instance GHC.Exception.Exception Game.LambdaHack.Atomic.MonadStateWrite.AtomicFail
+ Game.LambdaHack.Atomic.MonadStateWrite: instance GHC.Show.Show Game.LambdaHack.Atomic.MonadStateWrite.AtomicFail
+ Game.LambdaHack.Atomic.MonadStateWrite: itemsMatch :: Item -> Item -> Bool
+ Game.LambdaHack.Atomic.MonadStateWrite: newtype AtomicFail
+ Game.LambdaHack.Atomic.MonadStateWrite: resetActorAspect :: MonadStateWrite m => m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: rmFromBag :: ItemQuant -> ItemId -> ItemBag -> ItemBag
+ Game.LambdaHack.Atomic.PosAtomicRead: doubleAid :: MonadStateRead m => ActorId -> ActorId -> m PosAtomic
+ Game.LambdaHack.Atomic.PosAtomicRead: singleAid :: MonadStateRead m => ActorId -> m PosAtomic
+ Game.LambdaHack.Atomic.PosAtomicRead: singleContainer :: MonadStateRead m => Container -> m PosAtomic
+ Game.LambdaHack.Client: ReqAINop :: ReqAI
+ Game.LambdaHack.Client: ReqAITimed :: RequestAnyAbility -> ReqAI
+ Game.LambdaHack.Client: ReqUIAutomate :: ReqUI
+ Game.LambdaHack.Client: ReqUIGameExit :: ReqUI
+ Game.LambdaHack.Client: ReqUIGameRestart :: (GroupName ModeKind) -> Challenge -> ReqUI
+ Game.LambdaHack.Client: ReqUIGameSave :: ReqUI
+ Game.LambdaHack.Client: ReqUINop :: ReqUI
+ Game.LambdaHack.Client: ReqUITactic :: Tactic -> ReqUI
+ Game.LambdaHack.Client: ReqUITimed :: RequestAnyAbility -> ReqUI
+ Game.LambdaHack.Client: RequestAnyAbility :: (RequestTimed a) -> RequestAnyAbility
+ Game.LambdaHack.Client: RespQueryAI :: ActorId -> Response
+ Game.LambdaHack.Client: RespQueryUI :: Response
+ Game.LambdaHack.Client: RespSfxAtomic :: SfxAtomic -> Response
+ Game.LambdaHack.Client: RespUpdAtomic :: State -> UpdAtomic -> Response
+ Game.LambdaHack.Client: RespUpdAtomicNoState :: UpdAtomic -> Response
+ Game.LambdaHack.Client: [ReqAlter] :: Point -> RequestTimed 'AbAlter
+ Game.LambdaHack.Client: [ReqApply] :: ItemId -> CStore -> RequestTimed 'AbApply
+ Game.LambdaHack.Client: [ReqDisplace] :: ActorId -> RequestTimed 'AbDisplace
+ Game.LambdaHack.Client: [ReqMelee] :: ActorId -> ItemId -> CStore -> RequestTimed 'AbMelee
+ Game.LambdaHack.Client: [ReqMoveItems] :: [(ItemId, Int, CStore, CStore)] -> RequestTimed 'AbMoveItem
+ Game.LambdaHack.Client: [ReqMove] :: Vector -> RequestTimed 'AbMove
+ Game.LambdaHack.Client: [ReqProject] :: Point -> Int -> ItemId -> CStore -> RequestTimed 'AbProject
+ Game.LambdaHack.Client: [ReqWait10] :: RequestTimed 'AbWait
+ Game.LambdaHack.Client: [ReqWait] :: RequestTimed 'AbWait
+ Game.LambdaHack.Client: applyUIOptions :: COps -> UIOptions -> ClientOptions -> ClientOptions
+ Game.LambdaHack.Client: data ClientOptions
+ Game.LambdaHack.Client: data ReqAI
+ Game.LambdaHack.Client: data ReqUI
+ Game.LambdaHack.Client: data RequestAnyAbility
+ Game.LambdaHack.Client: data RequestTimed :: Ability -> *
+ Game.LambdaHack.Client: data Response
+ Game.LambdaHack.Client: data UIOptions
+ Game.LambdaHack.Client: defClientOptions :: ClientOptions
+ Game.LambdaHack.Client: mkUIOptions :: COps -> Bool -> IO UIOptions
+ Game.LambdaHack.Client: sbenchmark :: ClientOptions -> Bool
+ Game.LambdaHack.Client: type RequestAI = (ReqAI, Maybe ActorId)
+ Game.LambdaHack.Client: type RequestUI = (ReqUI, Maybe ActorId)
+ Game.LambdaHack.Client: uCmdline :: UIOptions -> [String]
+ Game.LambdaHack.Client.AI: pickActorAndAction :: MonadClient m => Maybe (ActorId, RequestAnyAbility) -> ActorId -> m (ActorId, RequestAnyAbility)
+ Game.LambdaHack.Client.AI.HandleAbilityM: pickAction :: MonadClient m => ActorId -> Bool -> m RequestAnyAbility
+ Game.LambdaHack.Client.AI.PickActorM: setTargetFromTactics :: MonadClient m => ActorId -> m ()
+ Game.LambdaHack.Client.Bfs: abortedKnownBfs :: BfsDistance
+ Game.LambdaHack.Client.Bfs: abortedUnknownBfs :: BfsDistance
+ Game.LambdaHack.Client.ClientOptions: ClientOptions :: Maybe Text -> Maybe Text -> Maybe Int -> Maybe Int -> Maybe Int -> Maybe Bool -> Maybe Int -> Bool -> Maybe Bool -> Bool -> Bool -> Maybe Text -> Maybe FilePath -> String -> Bool -> Bool -> Bool -> Bool -> Maybe Int -> Maybe Int -> ClientOptions
+ Game.LambdaHack.Client.ClientOptions: [sbenchmark] :: ClientOptions -> Bool
+ Game.LambdaHack.Client.ClientOptions: [scolorIsBold] :: ClientOptions -> Maybe Bool
+ Game.LambdaHack.Client.ClientOptions: [sdbgMsgCli] :: ClientOptions -> Bool
+ Game.LambdaHack.Client.ClientOptions: [sdisableAutoYes] :: ClientOptions -> Bool
+ Game.LambdaHack.Client.ClientOptions: [sdlFonSizeAdd] :: ClientOptions -> Maybe Int
+ Game.LambdaHack.Client.ClientOptions: [sdlFontFile] :: ClientOptions -> Maybe Text
+ Game.LambdaHack.Client.ClientOptions: [sdlTtfSizeAdd] :: ClientOptions -> Maybe Int
+ Game.LambdaHack.Client.ClientOptions: [sfontDir] :: ClientOptions -> Maybe FilePath
+ Game.LambdaHack.Client.ClientOptions: [sfontSize] :: ClientOptions -> Maybe Int
+ Game.LambdaHack.Client.ClientOptions: [sfrontendLazy] :: ClientOptions -> Bool
+ Game.LambdaHack.Client.ClientOptions: [sfrontendNull] :: ClientOptions -> Bool
+ Game.LambdaHack.Client.ClientOptions: [sfrontendTeletype] :: ClientOptions -> Bool
+ Game.LambdaHack.Client.ClientOptions: [sgtkFontFamily] :: ClientOptions -> Maybe Text
+ Game.LambdaHack.Client.ClientOptions: [smaxFps] :: ClientOptions -> Maybe Int
+ Game.LambdaHack.Client.ClientOptions: [snewGameCli] :: ClientOptions -> Bool
+ Game.LambdaHack.Client.ClientOptions: [snoAnim] :: ClientOptions -> Maybe Bool
+ Game.LambdaHack.Client.ClientOptions: [ssavePrefixCli] :: ClientOptions -> String
+ Game.LambdaHack.Client.ClientOptions: [sstopAfterFrames] :: ClientOptions -> Maybe Int
+ Game.LambdaHack.Client.ClientOptions: [sstopAfterSeconds] :: ClientOptions -> Maybe Int
+ Game.LambdaHack.Client.ClientOptions: [stitle] :: ClientOptions -> Maybe Text
+ Game.LambdaHack.Client.ClientOptions: data ClientOptions
+ Game.LambdaHack.Client.ClientOptions: defClientOptions :: ClientOptions
+ Game.LambdaHack.Client.ClientOptions: instance Data.Binary.Class.Binary Game.LambdaHack.Client.ClientOptions.ClientOptions
+ Game.LambdaHack.Client.ClientOptions: instance GHC.Classes.Eq Game.LambdaHack.Client.ClientOptions.ClientOptions
+ Game.LambdaHack.Client.ClientOptions: instance GHC.Generics.Generic Game.LambdaHack.Client.ClientOptions.ClientOptions
+ Game.LambdaHack.Client.ClientOptions: instance GHC.Show.Show Game.LambdaHack.Client.ClientOptions.ClientOptions
+ Game.LambdaHack.Client.HandleAtomicM: addItemToDiscoBenefit :: MonadClient m => ItemId -> Item -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: class MonadClient m => MonadClientSetup m
+ Game.LambdaHack.Client.HandleAtomicM: coverKind :: Container -> ItemKindIx -> Id ItemKind -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: coverSeed :: Container -> ItemId -> ItemSeed -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: createActor :: MonadClient m => ActorId -> Actor -> [(ItemId, Item)] -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: destroyActor :: MonadClient m => ActorId -> Actor -> Bool -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: discoverKind :: MonadClient m => Container -> ItemKindIx -> Id ItemKind -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: discoverSeed :: MonadClient m => Container -> ItemId -> ItemSeed -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: killExit :: MonadClient m => m ()
+ Game.LambdaHack.Client.HandleAtomicM: perception :: MonadClient m => LevelId -> Perception -> Perception -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: restartClient :: MonadClientSetup m => m ()
+ Game.LambdaHack.Client.HandleAtomicM: saveClient :: MonadClientSetup m => m ()
+ Game.LambdaHack.Client.HandleAtomicM: tileChangeAffectsBfs :: COps -> Id TileKind -> Id TileKind -> Bool
+ Game.LambdaHack.Client.HandleAtomicM: wipeBfsIfItemAffectsSkills :: MonadClient m => [CStore] -> ActorId -> m ()
+ Game.LambdaHack.Client.HandleResponseM: class MonadClient m => MonadClientAtomic m
+ Game.LambdaHack.Client.HandleResponseM: execPutState :: MonadClientAtomic m => State -> m ()
+ Game.LambdaHack.Client.HandleResponseM: execUpdAtomic :: MonadClientAtomic m => UpdAtomic -> m ()
+ Game.LambdaHack.Client.LoopM: class MonadClient m => MonadClientReadResponse m
+ Game.LambdaHack.Client.LoopM: initAI :: MonadClient m => m ()
+ Game.LambdaHack.Client.LoopM: initUI :: MonadClientUI m => KeyKind -> UIOptions -> m ()
+ Game.LambdaHack.Client.LoopM: receiveResponse :: MonadClientReadResponse m => m Response
+ Game.LambdaHack.Client.Preferences: averageTurnValue :: Double
+ Game.LambdaHack.Client.Preferences: avgItemDelay :: Double
+ Game.LambdaHack.Client.Preferences: avgItemLife :: Double
+ Game.LambdaHack.Client.Preferences: durabilityMult :: Double
+ Game.LambdaHack.Client.Preferences: fakeItem :: ItemKind -> Item
+ Game.LambdaHack.Client.Preferences: recBenefit :: GroupName ItemKind -> COps -> Faction -> (Double, Int)
+ Game.LambdaHack.Client.Request: ReqAINop :: ReqAI
+ Game.LambdaHack.Client.Request: ReqAITimed :: RequestAnyAbility -> ReqAI
+ Game.LambdaHack.Client.Request: ReqUIAutomate :: ReqUI
+ Game.LambdaHack.Client.Request: ReqUIGameExit :: ReqUI
+ Game.LambdaHack.Client.Request: ReqUIGameRestart :: (GroupName ModeKind) -> Challenge -> ReqUI
+ Game.LambdaHack.Client.Request: ReqUIGameSave :: ReqUI
+ Game.LambdaHack.Client.Request: ReqUINop :: ReqUI
+ Game.LambdaHack.Client.Request: ReqUITactic :: Tactic -> ReqUI
+ Game.LambdaHack.Client.Request: ReqUITimed :: RequestAnyAbility -> ReqUI
+ Game.LambdaHack.Client.Request: RequestAnyAbility :: (RequestTimed a) -> RequestAnyAbility
+ Game.LambdaHack.Client.Request: [ReqAlter] :: Point -> RequestTimed 'AbAlter
+ Game.LambdaHack.Client.Request: [ReqApply] :: ItemId -> CStore -> RequestTimed 'AbApply
+ Game.LambdaHack.Client.Request: [ReqDisplace] :: ActorId -> RequestTimed 'AbDisplace
+ Game.LambdaHack.Client.Request: [ReqMelee] :: ActorId -> ItemId -> CStore -> RequestTimed 'AbMelee
+ Game.LambdaHack.Client.Request: [ReqMoveItems] :: [(ItemId, Int, CStore, CStore)] -> RequestTimed 'AbMoveItem
+ Game.LambdaHack.Client.Request: [ReqMove] :: Vector -> RequestTimed 'AbMove
+ Game.LambdaHack.Client.Request: [ReqProject] :: Point -> Int -> ItemId -> CStore -> RequestTimed 'AbProject
+ Game.LambdaHack.Client.Request: [ReqWait10] :: RequestTimed 'AbWait
+ Game.LambdaHack.Client.Request: [ReqWait] :: RequestTimed 'AbWait
+ Game.LambdaHack.Client.Request: data ReqAI
+ Game.LambdaHack.Client.Request: data ReqUI
+ Game.LambdaHack.Client.Request: data RequestAnyAbility
+ Game.LambdaHack.Client.Request: data RequestTimed :: Ability -> *
+ Game.LambdaHack.Client.Request: instance GHC.Show.Show (Game.LambdaHack.Client.Request.RequestTimed a)
+ Game.LambdaHack.Client.Request: instance GHC.Show.Show Game.LambdaHack.Client.Request.ReqAI
+ Game.LambdaHack.Client.Request: instance GHC.Show.Show Game.LambdaHack.Client.Request.ReqUI
+ Game.LambdaHack.Client.Request: instance GHC.Show.Show Game.LambdaHack.Client.Request.RequestAnyAbility
+ Game.LambdaHack.Client.Request: type RequestAI = (ReqAI, Maybe ActorId)
+ Game.LambdaHack.Client.Request: type RequestUI = (ReqUI, Maybe ActorId)
+ Game.LambdaHack.Client.Response: RespQueryAI :: ActorId -> Response
+ Game.LambdaHack.Client.Response: RespQueryUI :: Response
+ Game.LambdaHack.Client.Response: RespSfxAtomic :: SfxAtomic -> Response
+ Game.LambdaHack.Client.Response: RespUpdAtomic :: State -> UpdAtomic -> Response
+ Game.LambdaHack.Client.Response: RespUpdAtomicNoState :: UpdAtomic -> Response
+ Game.LambdaHack.Client.Response: data Response
+ Game.LambdaHack.Client.Response: instance GHC.Show.Show Game.LambdaHack.Client.Response.Response
+ Game.LambdaHack.Client.State: [soptions] :: StateClient -> ClientOptions
+ Game.LambdaHack.Client.State: sleader :: StateClient -> Maybe ActorId
+ Game.LambdaHack.Client.UI: [sUIOptions] :: SessionUI -> UIOptions
+ Game.LambdaHack.Client.UI: applyUIOptions :: COps -> UIOptions -> ClientOptions -> ClientOptions
+ Game.LambdaHack.Client.UI: data UIOptions
+ Game.LambdaHack.Client.UI: mkUIOptions :: COps -> Bool -> IO UIOptions
+ Game.LambdaHack.Client.UI: uCmdline :: UIOptions -> [String]
+ Game.LambdaHack.Client.UI.ActorUI: tryFindActor :: State -> (ActorId -> Actor -> Bool) -> Maybe (ActorId, Actor)
+ Game.LambdaHack.Client.UI.Animation: blank :: Maybe AttrCharW32
+ Game.LambdaHack.Client.UI.Animation: cSym :: Color -> Char -> Maybe AttrCharW32
+ Game.LambdaHack.Client.UI.Animation: mapPosToOffset :: (Point, AttrCharW32) -> (Int, [AttrCharW32])
+ Game.LambdaHack.Client.UI.Animation: mzipPairs :: (Point, Point) -> (Maybe AttrCharW32, Maybe AttrCharW32) -> IntOverlay
+ Game.LambdaHack.Client.UI.Animation: mzipSingleton :: Point -> Maybe AttrCharW32 -> IntOverlay
+ Game.LambdaHack.Client.UI.Content.KeyKind: dropCmd :: HumanCmd
+ Game.LambdaHack.Client.UI.Content.KeyKind: grabCmd :: HumanCmd
+ Game.LambdaHack.Client.UI.Content.KeyKind: projectICmd :: [Trigger] -> HumanCmd
+ Game.LambdaHack.Client.UI.Content.KeyKind: replaceCmd :: HumanCmd -> CmdTriple -> CmdTriple
+ Game.LambdaHack.Client.UI.DisplayAtomicM: actorVerbMU :: MonadClientUI m => ActorId -> ActorUI -> Part -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: aidVerbMU :: MonadClientUI m => ActorId -> Part -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: createActorUI :: MonadClientUI m => Bool -> ActorId -> Actor -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: destroyActorUI :: MonadClientUI m => Bool -> ActorId -> Actor -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: discover :: MonadClientUI m => Container -> ItemId -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: displaceActorUI :: MonadClientUI m => ActorId -> ActorId -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: itemAidVerbMU :: MonadClientUI m => ActorId -> Part -> ItemId -> Either (Maybe Int) Int -> CStore -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: itemVerbMU :: MonadClientUI m => ItemId -> ItemQuant -> Part -> Container -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: lookAtMove :: MonadClientUI m => ActorId -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: markDisplayNeeded :: MonadClientUI m => LevelId -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: moveActor :: MonadClientUI m => ActorId -> Point -> Point -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: moveItemUI :: MonadClientUI m => ItemId -> Int -> ActorId -> CStore -> CStore -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: msgDuplicateScrap :: MonadClientUI m => m Bool
+ Game.LambdaHack.Client.UI.DisplayAtomicM: ppSfxMsg :: MonadClientUI m => SfxMsg -> m Text
+ Game.LambdaHack.Client.UI.DisplayAtomicM: quitFactionUI :: MonadClientUI m => FactionId -> Maybe Status -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: setLastSlot :: MonadClientUI m => ActorId -> ItemId -> CStore -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: spotItem :: MonadClientUI m => Bool -> ItemId -> ItemQuant -> Container -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: strike :: MonadClientUI m => Bool -> ActorId -> ActorId -> ItemId -> CStore -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: updateItemSlot :: MonadClientUI m => CStore -> Maybe ActorId -> ItemId -> m SlotChar
+ Game.LambdaHack.Client.UI.DisplayAtomicM: updateItemSlotSide :: MonadClientUI m => CStore -> ActorId -> ItemId -> m SlotChar
+ Game.LambdaHack.Client.UI.EffectDescription: affixBonus :: Int -> Text
+ Game.LambdaHack.Client.UI.EffectDescription: tmodToSuff :: Text -> ThrowMod -> Text
+ Game.LambdaHack.Client.UI.EffectDescription: wrapInChevrons :: Text -> Text
+ Game.LambdaHack.Client.UI.EffectDescription: wrapInParens :: Text -> Text
+ Game.LambdaHack.Client.UI.Frame: FrameForall :: (forall s. FrameST s) -> FrameForall
+ Game.LambdaHack.Client.UI.Frame: [unFrameForall] :: FrameForall -> forall s. FrameST s
+ Game.LambdaHack.Client.UI.Frame: newtype FrameForall
+ Game.LambdaHack.Client.UI.Frame: truncateAttrLine :: X -> AttrLine -> X -> AttrLine
+ Game.LambdaHack.Client.UI.Frame: type FrameST s = Mutable Vector s Word32 -> ST s ()
+ Game.LambdaHack.Client.UI.Frame: writeLine :: Int -> AttrLine -> FrameForall
+ Game.LambdaHack.Client.UI.FrameM: pushFrame :: MonadClientUI m => m ()
+ Game.LambdaHack.Client.UI.FrameM: renderFrames :: MonadClientUI m => LevelId -> Animation -> m Frames
+ Game.LambdaHack.Client.UI.Frontend: data FrontSetup
+ Game.LambdaHack.Client.UI.Frontend.Sdl: FrontendSession :: Window -> Renderer -> Font -> IORef FontAtlas -> IORef Texture -> IORef SingleFrame -> IORef Bool -> MVar Bool -> FrontendSession
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [satlas] :: FrontendSession -> IORef FontAtlas
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [sdisplayPermitted] :: FrontendSession -> MVar Bool
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [sfont] :: FrontendSession -> Font
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [sforcedShutdown] :: FrontendSession -> IORef Bool
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [spreviousFrame] :: FrontendSession -> IORef SingleFrame
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [srenderer] :: FrontendSession -> Renderer
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [stexture] :: FrontendSession -> IORef Texture
+ Game.LambdaHack.Client.UI.Frontend.Sdl: [swindow] :: FrontendSession -> Window
+ Game.LambdaHack.Client.UI.Frontend.Sdl: colorToRGBA :: Color -> V4 Word8
+ Game.LambdaHack.Client.UI.Frontend.Sdl: data FrontendSession
+ Game.LambdaHack.Client.UI.Frontend.Sdl: display :: ClientOptions -> FrontendSession -> SingleFrame -> IO ()
+ Game.LambdaHack.Client.UI.Frontend.Sdl: displayNoLock :: ClientOptions -> FrontendSession -> SingleFrame -> IO ()
+ Game.LambdaHack.Client.UI.Frontend.Sdl: forceShutdown :: FrontendSession -> IO ()
+ Game.LambdaHack.Client.UI.Frontend.Sdl: frontendName :: String
+ Game.LambdaHack.Client.UI.Frontend.Sdl: keyTranslate :: Bool -> Keycode -> Key
+ Game.LambdaHack.Client.UI.Frontend.Sdl: modTranslate :: KeyModifier -> Modifier
+ Game.LambdaHack.Client.UI.Frontend.Sdl: shutdown :: FrontendSession -> IO ()
+ Game.LambdaHack.Client.UI.Frontend.Sdl: startup :: ClientOptions -> IO RawFrontend
+ Game.LambdaHack.Client.UI.Frontend.Sdl: startupFun :: ClientOptions -> MVar RawFrontend -> IO ()
+ Game.LambdaHack.Client.UI.Frontend.Sdl: type FontAtlas = EnumMap AttrCharW32 Texture
+ Game.LambdaHack.Client.UI.HandleHelperM: lookAt :: MonadClientUI m => Bool -> Text -> Bool -> Point -> ActorId -> Text -> m Text
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: alterTile :: MonadClientUI m => [Trigger] -> Vector -> m (FailOrCmd (RequestTimed 'AbAlter))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: alterTileAtPos :: MonadClientUI m => [Trigger] -> Point -> Text -> m (FailOrCmd (RequestTimed 'AbAlter))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: applyItem :: MonadClientUI m => [Trigger] -> (CStore, (ItemId, ItemFull)) -> m (FailOrCmd (RequestTimed 'AbApply))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: areaToRectangles :: MonadClientUI m => CmdArea -> m [(X, Y, X, Y)]
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: artWithVersion :: MonadClientUI m => m [String]
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: displaceAid :: MonadClientUI m => ActorId -> m (FailOrCmd (RequestTimed 'AbDisplace))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: generateMenu :: MonadClientUI m => (HumanCmd -> m (Either MError ReqUI)) -> [(KM, (Text, HumanCmd))] -> [String] -> String -> m (Either MError ReqUI)
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: goToXhair :: MonadClientUI m => Bool -> Bool -> m (FailOrCmd RequestAnyAbility)
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: guessAlter :: COps -> [Trigger] -> Id TileKind -> Text
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: meleeAid :: MonadClientUI m => ActorId -> m (FailOrCmd (RequestTimed 'AbMelee))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: moveItems :: forall m. MonadClientUI m => [CStore] -> (CStore, [(ItemId, ItemFull)]) -> CStore -> m (FailOrCmd (RequestTimed 'AbMoveItem))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: moveSearchAlter :: MonadClientUI m => Vector -> m (FailOrCmd RequestAnyAbility)
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: multiActorGoTo :: MonadClientUI m => LevelId -> Point -> RunParams -> m (FailOrCmd (Bool, Vector))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: nxtGameMode :: COps -> Int -> ModeKind
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: projectItem :: MonadClientUI m => [Trigger] -> (CStore, (ItemId, ItemFull)) -> m (FailOrCmd (RequestTimed 'AbProject))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: selectItemsToMove :: forall m. MonadClientUI m => [CStore] -> CStore -> Maybe Part -> Bool -> m (FailOrCmd (CStore, [(ItemId, ItemFull)]))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: verifyAlters :: MonadClientUI m => LevelId -> Point -> m (FailOrCmd ())
+ Game.LambdaHack.Client.UI.HandleHumanGlobalM: verifyEscape :: MonadClientUI m => m (FailOrCmd ())
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: doLook :: MonadClientUI m => m ()
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: endAiming :: MonadClientUI m => m ()
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: endAimingMsg :: MonadClientUI m => m ()
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: flashAiming :: MonadClientUI m => m ()
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: permittedProjectClient :: MonadClientUI m => [Char] -> m (ItemFull -> Either ReqFailure Bool)
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: posFromXhair :: MonadClientUI m => m (Either Text Point)
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: projectCheck :: MonadClientUI m => Point -> m (Maybe ReqFailure)
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: selectAid :: MonadClientUI m => ActorId -> m ()
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: xhairLegalEps :: MonadClientUI m => m (Either Text Int)
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: xhairPointerEnemy :: MonadClientUI m => Bool -> m ()
+ Game.LambdaHack.Client.UI.HandleHumanLocalM: xhairPointerFloor :: MonadClientUI m => Bool -> m ()
+ Game.LambdaHack.Client.UI.HandleHumanM: addNoError :: Monad m => m () -> m (Either MError ReqUI)
+ Game.LambdaHack.Client.UI.HandleHumanM: cmdAction :: MonadClientUI m => HumanCmd -> m (Either MError ReqUI)
+ Game.LambdaHack.Client.UI.HandleHumanM: fmapTimedToUI :: Monad m => m (RequestTimed a) -> m ReqUI
+ Game.LambdaHack.Client.UI.HandleHumanM: noRemoteHumanCmd :: HumanCmd -> Bool
+ Game.LambdaHack.Client.UI.ItemDescription: itemDesc :: FactionId -> FactionDict -> Int -> CStore -> Time -> ItemFull -> AttrLine
+ Game.LambdaHack.Client.UI.ItemDescription: partItemN :: FactionId -> FactionDict -> Bool -> Int -> Int -> CStore -> Time -> ItemFull -> (Bool, Bool, Part, Part)
+ Game.LambdaHack.Client.UI.ItemDescription: partItemWsR :: FactionId -> FactionDict -> Bool -> Int -> CStore -> Time -> ItemFull -> Part
+ Game.LambdaHack.Client.UI.ItemDescription: textAllAE :: Int -> Bool -> CStore -> ItemFull -> ([Text], [Text])
+ Game.LambdaHack.Client.UI.ItemSlot: [itemSlots] :: ItemSlots -> EnumMap SlotChar ItemId
+ Game.LambdaHack.Client.UI.ItemSlot: [organSlots] :: ItemSlots -> EnumMap SlotChar ItemId
+ Game.LambdaHack.Client.UI.Key: KMP :: KM -> Point -> KMP
+ Game.LambdaHack.Client.UI.Key: [kmpKeyMod] :: KMP -> KM
+ Game.LambdaHack.Client.UI.Key: [kmpPointer] :: KMP -> Point
+ Game.LambdaHack.Client.UI.Key: data KMP
+ Game.LambdaHack.Client.UI.Key: dirKeypadKey :: [Key]
+ Game.LambdaHack.Client.UI.Key: dirKeypadShiftChar :: [Char]
+ Game.LambdaHack.Client.UI.Key: dirKeypadShiftKey :: [Key]
+ Game.LambdaHack.Client.UI.Key: dirLaptopKey :: [Key]
+ Game.LambdaHack.Client.UI.Key: dirLaptopShiftKey :: [Key]
+ Game.LambdaHack.Client.UI.Key: dirMoveNoModifier :: Bool -> Bool -> [Key]
+ Game.LambdaHack.Client.UI.Key: dirRunControl :: [Key]
+ Game.LambdaHack.Client.UI.Key: dirRunNoModifier :: Bool -> Bool -> [Key]
+ Game.LambdaHack.Client.UI.Key: dirRunShift :: [Key]
+ Game.LambdaHack.Client.UI.Key: dirViChar :: [Char]
+ Game.LambdaHack.Client.UI.Key: dirViKey :: [Key]
+ Game.LambdaHack.Client.UI.Key: dirViShiftKey :: [Key]
+ Game.LambdaHack.Client.UI.MonadClientUI: addPressedKey :: MonadClientUI m => KMP -> m ()
+ Game.LambdaHack.Client.UI.MonadClientUI: connFrontend :: MonadClientUI m => FrontReq a -> m a
+ Game.LambdaHack.Client.UI.MonadClientUI: displayFrame :: MonadClientUI m => Maybe FrameForall -> m ()
+ Game.LambdaHack.Client.UI.Msg: attrLineToU :: AttrLine -> UAttrLine
+ Game.LambdaHack.Client.UI.Msg: renderRepetition :: RepMsgN -> AttrLine
+ Game.LambdaHack.Client.UI.Msg: renderTimeReport :: Time -> Report -> AttrLine
+ Game.LambdaHack.Client.UI.Msg: type UAttrLine = Vector Word32
+ Game.LambdaHack.Client.UI.Msg: uToAttrLine :: UAttrLine -> AttrLine
+ Game.LambdaHack.Client.UI.MsgM: promptMainKeys :: MonadClientUI m => m ()
+ Game.LambdaHack.Client.UI.Overlay: linesAttr :: AttrLine -> Overlay
+ Game.LambdaHack.Client.UI.Overlay: splitAttrPhrase :: X -> AttrLine -> Overlay
+ Game.LambdaHack.Client.UI.Overlay: type IntOverlay = [(Int, AttrLine)]
+ Game.LambdaHack.Client.UI.RunM: checkAndRun :: MonadClient m => ActorId -> Vector -> m (Either Text Vector)
+ Game.LambdaHack.Client.UI.RunM: continueRunDir :: MonadClientUI m => RunParams -> m (Either Text Vector)
+ Game.LambdaHack.Client.UI.RunM: enterableDir :: COps -> Level -> Point -> Vector -> Bool
+ Game.LambdaHack.Client.UI.RunM: tryTurning :: MonadClient m => ActorId -> m (Either Text Vector)
+ Game.LambdaHack.Client.UI.SessionUI: LastRecord :: [KM] -> [KM] -> Int -> LastRecord
+ Game.LambdaHack.Client.UI.SessionUI: [currentKeys] :: LastRecord -> [KM]
+ Game.LambdaHack.Client.UI.SessionUI: [freeSpace] :: LastRecord -> Int
+ Game.LambdaHack.Client.UI.SessionUI: [previousKeys] :: LastRecord -> [KM]
+ Game.LambdaHack.Client.UI.SessionUI: [sUIOptions] :: SessionUI -> UIOptions
+ Game.LambdaHack.Client.UI.SessionUI: data LastRecord
+ Game.LambdaHack.Client.UI.SessionUI: sreport :: SessionUI -> Report
+ Game.LambdaHack.Client.UI.Slideshow: endMsg :: String
+ Game.LambdaHack.Client.UI.Slideshow: keysOKX :: Y -> X -> X -> [KM] -> OKX
+ Game.LambdaHack.Client.UI.Slideshow: moreMsg :: String
+ Game.LambdaHack.Client.UI.UIOptions: UIOptions :: [(KM, CmdTriple)] -> [(Int, (Text, Text))] -> Bool -> Bool -> Text -> Text -> Int -> Int -> Int -> Bool -> Int -> Int -> Bool -> Bool -> [String] -> UIOptions
+ Game.LambdaHack.Client.UI.UIOptions: [uCmdline] :: UIOptions -> [String]
+ Game.LambdaHack.Client.UI.UIOptions: [uColorIsBold] :: UIOptions -> Bool
+ Game.LambdaHack.Client.UI.UIOptions: [uCommands] :: UIOptions -> [(KM, CmdTriple)]
+ Game.LambdaHack.Client.UI.UIOptions: [uFontSize] :: UIOptions -> Int
+ Game.LambdaHack.Client.UI.UIOptions: [uGtkFontFamily] :: UIOptions -> Text
+ Game.LambdaHack.Client.UI.UIOptions: [uHeroNames] :: UIOptions -> [(Int, (Text, Text))]
+ Game.LambdaHack.Client.UI.UIOptions: [uHistoryMax] :: UIOptions -> Int
+ Game.LambdaHack.Client.UI.UIOptions: [uLaptop] :: UIOptions -> Bool
+ Game.LambdaHack.Client.UI.UIOptions: [uMaxFps] :: UIOptions -> Int
+ Game.LambdaHack.Client.UI.UIOptions: [uNoAnim] :: UIOptions -> Bool
+ Game.LambdaHack.Client.UI.UIOptions: [uRunStopMsgs] :: UIOptions -> Bool
+ Game.LambdaHack.Client.UI.UIOptions: [uSdlFonSizeAdd] :: UIOptions -> Int
+ Game.LambdaHack.Client.UI.UIOptions: [uSdlFontFile] :: UIOptions -> Text
+ Game.LambdaHack.Client.UI.UIOptions: [uSdlTtfSizeAdd] :: UIOptions -> Int
+ Game.LambdaHack.Client.UI.UIOptions: [uVi] :: UIOptions -> Bool
+ Game.LambdaHack.Client.UI.UIOptions: applyUIOptions :: COps -> UIOptions -> ClientOptions -> ClientOptions
+ Game.LambdaHack.Client.UI.UIOptions: data UIOptions
+ Game.LambdaHack.Client.UI.UIOptions: instance Control.DeepSeq.NFData Game.LambdaHack.Client.UI.UIOptions.UIOptions
+ Game.LambdaHack.Client.UI.UIOptions: instance Data.Binary.Class.Binary Game.LambdaHack.Client.UI.UIOptions.UIOptions
+ Game.LambdaHack.Client.UI.UIOptions: instance GHC.Generics.Generic Game.LambdaHack.Client.UI.UIOptions.UIOptions
+ Game.LambdaHack.Client.UI.UIOptions: instance GHC.Show.Show Game.LambdaHack.Client.UI.UIOptions.UIOptions
+ Game.LambdaHack.Client.UI.UIOptions: mkUIOptions :: COps -> Bool -> IO UIOptions
+ Game.LambdaHack.Client.UI.UIOptions: parseConfig :: Config -> UIOptions
+ Game.LambdaHack.Common.ActorState: getActorAspect :: ActorId -> State -> AspectRecord
+ Game.LambdaHack.Common.ActorState: getActorAssocsK :: ActorId -> CStore -> State -> [(ItemId, (Item, ItemQuant))]
+ Game.LambdaHack.Common.ActorState: inMelee :: Actor -> State -> Bool
+ Game.LambdaHack.Common.ActorState: itemToFull :: State -> ItemId -> ItemQuant -> ItemFull
+ Game.LambdaHack.Common.ActorState: sharedAllOwned :: FactionId -> State -> ItemBag
+ Game.LambdaHack.Common.ActorState: sharedInv :: FactionId -> State -> ItemBag
+ Game.LambdaHack.Common.Dice: castDice :: forall m. Monad m => ((Int, Int) -> m Int) -> AbsDepth -> AbsDepth -> Dice -> m Int
+ Game.LambdaHack.Common.Dice: minmaxDice :: Dice -> (Int, Int)
+ Game.LambdaHack.Common.Dice: z :: Int -> Int -> Dice
+ Game.LambdaHack.Common.Dice: zl :: Int -> Int -> Dice
+ Game.LambdaHack.Common.Faction: gleader :: Faction -> Maybe ActorId
+ Game.LambdaHack.Common.Flavour: colorToLiquidName :: Color -> Text
+ Game.LambdaHack.Common.Flavour: data FancyName
+ Game.LambdaHack.Common.HSFile: doesFileExist :: FilePath -> IO Bool
+ Game.LambdaHack.Common.HSFile: encodeData :: Binary a => FilePath -> a -> IO ()
+ Game.LambdaHack.Common.HSFile: encodeEOF :: Binary a => FilePath -> a -> IO ()
+ Game.LambdaHack.Common.HSFile: readFile :: FilePath -> IO String
+ Game.LambdaHack.Common.HSFile: renameFile :: FilePath -> FilePath -> IO ()
+ Game.LambdaHack.Common.HSFile: strictDecodeEOF :: Binary a => FilePath -> IO a
+ Game.LambdaHack.Common.HSFile: tryCreateDir :: FilePath -> IO ()
+ Game.LambdaHack.Common.HSFile: tryWriteFile :: FilePath -> String -> IO ()
+ Game.LambdaHack.Common.HighScore: insertPos :: ScoreRecord -> ScoreTable -> (ScoreTable, Int)
+ Game.LambdaHack.Common.HighScore: showNearbyScores :: TimeZone -> Int -> ScoreTable -> Int -> [[Text]]
+ Game.LambdaHack.Common.HighScore: showTable :: TimeZone -> ScoreTable -> Int -> Int -> [Text]
+ Game.LambdaHack.Common.Item: [itemConst] :: ItemDisco -> Bool
+ Game.LambdaHack.Common.Item: [kmConst] :: KindMean -> Bool
+ Game.LambdaHack.Common.Item: addMeanAspect :: AspectRecord -> Aspect -> AspectRecord
+ Game.LambdaHack.Common.Item: castAspect :: AbsDepth -> AbsDepth -> AspectRecord -> Aspect -> Rnd AspectRecord
+ Game.LambdaHack.Common.Item: ceilingMeanDice :: Dice -> Int
+ Game.LambdaHack.Common.Item: itemToFull6 :: COps -> DiscoveryKind -> DiscoveryAspect -> ItemId -> Item -> ItemQuant -> ItemFull
+ Game.LambdaHack.Common.Item: type ItemIxMap = EnumMap ItemKindIx [ItemId]
+ Game.LambdaHack.Common.ItemStrongest: unknownAspect :: (Aspect -> [Dice]) -> ItemFull -> Bool
+ Game.LambdaHack.Common.ItemStrongest: unknownMeleeBonus :: [ItemFull] -> Bool
+ Game.LambdaHack.Common.Level: [lname] :: Level -> Text
+ Game.LambdaHack.Common.Level: assertSparseActors :: ActorMap -> ActorMap
+ Game.LambdaHack.Common.Level: assertSparseItems :: ItemFloor -> ItemFloor
+ Game.LambdaHack.Common.Level: updateActorMap :: (ActorMap -> ActorMap) -> Level -> Level
+ Game.LambdaHack.Common.Level: updateEmbed :: (ItemFloor -> ItemFloor) -> Level -> Level
+ Game.LambdaHack.Common.Level: updateFloor :: (ItemFloor -> ItemFloor) -> Level -> Level
+ Game.LambdaHack.Common.Level: updateSmell :: (SmellMap -> SmellMap) -> Level -> Level
+ Game.LambdaHack.Common.Level: updateTile :: (TileMap -> TileMap) -> Level -> Level
+ Game.LambdaHack.Common.Misc: xD :: Double -> Double
+ Game.LambdaHack.Common.Point: balancedWord :: Int -> Int -> Int -> [Int]
+ Game.LambdaHack.Common.Point: blaXY :: Int -> Point -> Point -> [Point]
+ Game.LambdaHack.Common.Point: maxLevelDim :: Int
+ Game.LambdaHack.Common.PointArray: cnv :: (Enum a, Enum b) => a -> b
+ Game.LambdaHack.Common.Random: rollFreq :: Show a => Frequency a -> StdGen -> (a, StdGen)
+ Game.LambdaHack.Common.ReqFailure: AlterBlockActor :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: AlterBlockItem :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: AlterDistant :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: AlterNothing :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: AlterUnskilled :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: AlterUnwalked :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ApplyCharging :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ApplyNoEffects :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ApplyOutOfReach :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ApplyRead :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ApplyUnskilled :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: DisplaceAccess :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: DisplaceBraced :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: DisplaceDistant :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: DisplaceDying :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: DisplaceImmobile :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: DisplaceProjectiles :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: DisplaceSupported :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: EqpOverfull :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: EqpStackFull :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ItemNotCalm :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ItemNothing :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: MeleeDistant :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: MeleeSelf :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: MoveNothing :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: NoChangeDunLeader :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: NotCalmPrecious :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ProjectAimOnself :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ProjectBlockActor :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ProjectBlockTerrain :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ProjectLobable :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ProjectOutOfReach :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: ProjectUnskilled :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: TriggerNothing :: ReqFailure
+ Game.LambdaHack.Common.ReqFailure: data ReqFailure
+ Game.LambdaHack.Common.ReqFailure: impossibleReqFailure :: ReqFailure -> Bool
+ Game.LambdaHack.Common.ReqFailure: instance Data.Binary.Class.Binary Game.LambdaHack.Common.ReqFailure.ReqFailure
+ Game.LambdaHack.Common.ReqFailure: instance GHC.Classes.Eq Game.LambdaHack.Common.ReqFailure.ReqFailure
+ Game.LambdaHack.Common.ReqFailure: instance GHC.Generics.Generic Game.LambdaHack.Common.ReqFailure.ReqFailure
+ Game.LambdaHack.Common.ReqFailure: instance GHC.Show.Show Game.LambdaHack.Common.ReqFailure.ReqFailure
+ Game.LambdaHack.Common.ReqFailure: permittedApply :: Time -> Int -> Bool -> [Char] -> ItemFull -> Either ReqFailure Bool
+ Game.LambdaHack.Common.ReqFailure: permittedPrecious :: Bool -> Bool -> ItemFull -> Either ReqFailure Bool
+ Game.LambdaHack.Common.ReqFailure: permittedPreciousAI :: Bool -> ItemFull -> Bool
+ Game.LambdaHack.Common.ReqFailure: permittedProject :: Bool -> Int -> Bool -> [Char] -> ItemFull -> Either ReqFailure Bool
+ Game.LambdaHack.Common.ReqFailure: permittedProjectAI :: Int -> Bool -> ItemFull -> Bool
+ Game.LambdaHack.Common.ReqFailure: showReqFailure :: ReqFailure -> Text
+ Game.LambdaHack.Common.Save: delayPrint :: Text -> IO ()
+ Game.LambdaHack.Common.Save: showVersion2 :: (Version, Version) -> Text
+ Game.LambdaHack.Common.Save: vExevLib :: COps -> (Version, Version)
+ Game.LambdaHack.Common.State: actorAspectInDungeon :: State -> ActorAspect
+ Game.LambdaHack.Common.State: aspectRecordFromActor :: Actor -> State -> AspectRecord
+ Game.LambdaHack.Common.State: aspectRecordFromIid :: ItemId -> State -> AspectRecord
+ Game.LambdaHack.Common.State: aspectRecordFromItem :: ItemId -> Item -> State -> AspectRecord
+ Game.LambdaHack.Common.State: getItemBody :: ItemId -> State -> Item
+ Game.LambdaHack.Common.State: sactorAspect :: State -> ActorAspect
+ Game.LambdaHack.Common.State: sdiscoAspect :: State -> DiscoveryAspect
+ Game.LambdaHack.Common.State: sdiscoKind :: State -> DiscoveryKind
+ Game.LambdaHack.Common.State: sitemIxMap :: State -> ItemIxMap
+ Game.LambdaHack.Common.State: unknownLevel :: COps -> AbsDepth -> X -> Y -> Text -> Text -> ([Point], [Point]) -> Int -> [Point] -> Bool -> Level
+ Game.LambdaHack.Common.State: unknownTileMap :: Id TileKind -> Int -> Int -> TileMap
+ Game.LambdaHack.Common.State: updateActorAspect :: (ActorAspect -> ActorAspect) -> State -> State
+ Game.LambdaHack.Common.State: updateDiscoAspect :: (DiscoveryAspect -> DiscoveryAspect) -> State -> State
+ Game.LambdaHack.Common.State: updateDiscoKind :: (DiscoveryKind -> DiscoveryKind) -> State -> State
+ Game.LambdaHack.Common.State: updateItemIxMap :: (ItemIxMap -> ItemIxMap) -> State -> State
+ Game.LambdaHack.Common.Tile: alterMinSkillKind :: Id TileKind -> TileKind -> Word8
+ Game.LambdaHack.Common.Tile: alterMinWalkKind :: Id TileKind -> TileKind -> Word8
+ Game.LambdaHack.Common.Time: _timeTick :: Time
+ Game.LambdaHack.Common.Time: minimalSpeed :: Int64
+ Game.LambdaHack.Common.Time: rangeFromSpeed :: Speed -> Int
+ Game.LambdaHack.Common.Time: sInMs :: Int64
+ Game.LambdaHack.Common.Time: turnsInSecond :: Int64
+ Game.LambdaHack.Common.Vector: _moveTexts :: [Text]
+ Game.LambdaHack.Common.Vector: longMoveTexts :: [Text]
+ Game.LambdaHack.Common.Vector: maxVectorDim :: Int
+ Game.LambdaHack.Common.Vector: normalize :: Double -> Double -> Vector
+ Game.LambdaHack.Common.Vector: normalizeVector :: Vector -> Vector
+ Game.LambdaHack.Content.CaveKind: [cdesc] :: CaveKind -> Text
+ Game.LambdaHack.Content.ItemKind: Composite :: [Effect] -> Effect
+ Game.LambdaHack.Content.ItemKind: boostItemKind :: ItemKind -> ItemKind
+ Game.LambdaHack.Content.ItemKind: boostItemKindList :: StdGen -> [ItemKind] -> [ItemKind]
+ Game.LambdaHack.Content.ItemKind: hardwiredItemGroups :: [GroupName ItemKind]
+ Game.LambdaHack.Content.ItemKind: validateDamage :: [(Int, Dice)] -> [Text]
+ Game.LambdaHack.Content.ItemKind: validateDups :: ItemKind -> Feature -> [Text]
+ Game.LambdaHack.Content.ModeKind: hardwiredModeGroups :: [GroupName ModeKind]
+ Game.LambdaHack.Content.ModeKind: validateSinglePlayer :: Player -> [Text]
+ Game.LambdaHack.Content.ModeKind: validateSingleRoster :: Caves -> Roster -> [Text]
+ Game.LambdaHack.Content.TileKind: hardwiredTileGroups :: [GroupName TileKind]
+ Game.LambdaHack.Content.TileKind: validateDups :: TileKind -> Feature -> [Text]
+ Game.LambdaHack.SampleImplementation.SampleMonadClient: instance Game.LambdaHack.Client.HandleAtomicM.MonadClientSetup Game.LambdaHack.SampleImplementation.SampleMonadClient.CliImplementation
+ Game.LambdaHack.SampleImplementation.SampleMonadClient: instance Game.LambdaHack.Client.HandleResponseM.MonadClientAtomic Game.LambdaHack.SampleImplementation.SampleMonadClient.CliImplementation
+ Game.LambdaHack.SampleImplementation.SampleMonadClient: instance Game.LambdaHack.Client.LoopM.MonadClientReadResponse Game.LambdaHack.SampleImplementation.SampleMonadClient.CliImplementation
+ Game.LambdaHack.SampleImplementation.SampleMonadServer: instance Game.LambdaHack.Server.MonadServer.MonadServerAtomic Game.LambdaHack.SampleImplementation.SampleMonadServer.SerImplementation
+ Game.LambdaHack.Server: ChanServer :: CliSerQueue Response -> CliSerQueue RequestAI -> Maybe (CliSerQueue RequestUI) -> ChanServer
+ Game.LambdaHack.Server: ServerOptions :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe (GroupName ModeKind) -> Bool -> Bool -> Maybe StdGen -> Maybe StdGen -> Bool -> Challenge -> Bool -> String -> Bool -> ClientOptions -> ServerOptions
+ Game.LambdaHack.Server: [requestAIS] :: ChanServer -> CliSerQueue RequestAI
+ Game.LambdaHack.Server: [requestUIS] :: ChanServer -> Maybe (CliSerQueue RequestUI)
+ Game.LambdaHack.Server: [responseS] :: ChanServer -> CliSerQueue Response
+ Game.LambdaHack.Server: [sclientOptions] :: ServerOptions -> ClientOptions
+ Game.LambdaHack.Server: [sniff] :: ServerOptions -> Bool
+ Game.LambdaHack.Server: data ChanServer
+ Game.LambdaHack.Server: data ServerOptions
+ Game.LambdaHack.Server: serverOptionsPI :: ParserInfo ServerOptions
+ Game.LambdaHack.Server.BroadcastAtomic: atomicForget :: FactionId -> LevelId -> Perception -> State -> [UpdAtomic]
+ Game.LambdaHack.Server.BroadcastAtomic: loudSfxAtomic :: MonadStateRead m => Bool -> SfxAtomic -> m (Maybe SfxMsg)
+ Game.LambdaHack.Server.BroadcastAtomic: loudUpdAtomic :: MonadStateRead m => Bool -> UpdAtomic -> m (Maybe SfxMsg)
+ Game.LambdaHack.Server.Commandline: serverOptionsP :: Parser ServerOptions
+ Game.LambdaHack.Server.Commandline: serverOptionsPI :: ParserInfo ServerOptions
+ Game.LambdaHack.Server.CommonM: addProjectile :: MonadServerAtomic m => Point -> [Point] -> ItemId -> ItemQuant -> LevelId -> FactionId -> Time -> Bool -> m ()
+ Game.LambdaHack.Server.CommonM: anyActorsAlive :: MonadServer m => FactionId -> ActorId -> m Bool
+ Game.LambdaHack.Server.CommonM: containerMoveItem :: MonadStateRead m => Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
+ Game.LambdaHack.Server.CommonM: generalMoveItem :: MonadStateRead m => Bool -> ItemId -> Int -> Container -> Container -> m [UpdAtomic]
+ Game.LambdaHack.Server.CommonM: getCacheLucid :: MonadServer m => LevelId -> m FovLucid
+ Game.LambdaHack.Server.CommonM: getCacheTotal :: MonadServer m => FactionId -> LevelId -> m CacheBeforeLucid
+ Game.LambdaHack.Server.CommonM: keepArenaFact :: Faction -> Bool
+ Game.LambdaHack.Server.CommonM: projectBla :: MonadServerAtomic m => ActorId -> Point -> [Point] -> ItemId -> CStore -> Bool -> m ()
+ Game.LambdaHack.Server.CommonM: quitF :: MonadServerAtomic m => Status -> FactionId -> m ()
+ Game.LambdaHack.Server.CommonM: updatePer :: MonadServerAtomic m => FactionId -> LevelId -> m ()
+ Game.LambdaHack.Server.DebugM: DebugAid :: Text -> ActorId -> FactionId -> LevelId -> Int64 -> Time -> Time -> DebugAid
+ Game.LambdaHack.Server.DebugM: [aid] :: DebugAid -> ActorId
+ Game.LambdaHack.Server.DebugM: [bHP] :: DebugAid -> Int64
+ Game.LambdaHack.Server.DebugM: [btime] :: DebugAid -> Time
+ Game.LambdaHack.Server.DebugM: [faction] :: DebugAid -> FactionId
+ Game.LambdaHack.Server.DebugM: [label] :: DebugAid -> Text
+ Game.LambdaHack.Server.DebugM: [lid] :: DebugAid -> LevelId
+ Game.LambdaHack.Server.DebugM: [time] :: DebugAid -> Time
+ Game.LambdaHack.Server.DebugM: data DebugAid
+ Game.LambdaHack.Server.DebugM: debugAid :: MonadServer m => ActorId -> Text -> m Text
+ Game.LambdaHack.Server.DebugM: debugPlain :: MonadServer m => FactionId -> Text -> UpdAtomic -> m ()
+ Game.LambdaHack.Server.DebugM: debugPretty :: MonadServer m => FactionId -> Text -> UpdAtomic -> m ()
+ Game.LambdaHack.Server.DebugM: debugShow :: Show a => a -> Text
+ Game.LambdaHack.Server.DebugM: instance GHC.Show.Show Game.LambdaHack.Server.DebugM.DebugAid
+ Game.LambdaHack.Server.DungeonGen: buildTileMap :: COps -> Cave -> Rnd TileMap
+ Game.LambdaHack.Server.DungeonGen.Area: mkFixed :: (X, Y) -> Area -> Point -> Area
+ Game.LambdaHack.Server.DungeonGen.AreaRnd: borderPlace :: Area -> Fence -> (Area, Area)
+ Game.LambdaHack.Server.DungeonGen.AreaRnd: connectGrid' :: EnumSet Point -> (X, Y) -> EnumSet Point -> EnumSet Point -> [(Point, Point)] -> Rnd [(Point, Point)]
+ Game.LambdaHack.Server.DungeonGen.AreaRnd: mkCorridor :: HV -> Point -> Bool -> Point -> Bool -> Area -> Rnd Corridor
+ Game.LambdaHack.Server.DungeonGen.AreaRnd: sortPoint :: (Point, Point) -> (Point, Point)
+ Game.LambdaHack.Server.DungeonGen.Cave: digCorridors :: Id TileKind -> Corridor -> TileMapEM
+ Game.LambdaHack.Server.DungeonGen.Cave: pickOpening :: COps -> CaveKind -> TileMapEM -> Id TileKind -> Int -> Point -> (Id TileKind, Id TileKind) -> Rnd (Id TileKind)
+ Game.LambdaHack.Server.DungeonGen.Place: buildFence :: Id TileKind -> Area -> TileMapEM
+ Game.LambdaHack.Server.DungeonGen.Place: interiorArea :: PlaceKind -> Area -> Maybe Area
+ Game.LambdaHack.Server.DungeonGen.Place: olegend :: COps -> GroupName TileKind -> Rnd (EnumMap Char (Int, Id TileKind), EnumMap Char (Id TileKind))
+ Game.LambdaHack.Server.DungeonGen.Place: ooverride :: COps -> [(Char, GroupName TileKind)] -> Rnd (EnumMap Char (Int, Id TileKind), EnumMap Char (Id TileKind))
+ Game.LambdaHack.Server.DungeonGen.Place: tilePlace :: Area -> PlaceKind -> Rnd (EnumMap Point Char)
+ Game.LambdaHack.Server.EndM: dropAllItems :: MonadServerAtomic m => ActorId -> Actor -> m ()
+ Game.LambdaHack.Server.EndM: gameExit :: (MonadServerAtomic m, MonadServerReadRequest m) => m ()
+ Game.LambdaHack.Server.EndM: writeSaveAll :: MonadServerAtomic m => Bool -> m ()
+ Game.LambdaHack.Server.Fov: type Matrix = (Int, Int, Int, Int)
+ Game.LambdaHack.Server.HandleAtomicM: invalidateArenas :: MonadServer m => m ()
+ Game.LambdaHack.Server.HandleEffectM: allGroupItems :: MonadServerAtomic m => CStore -> GroupName ItemKind -> ActorId -> m [(ItemId, ItemQuant)]
+ Game.LambdaHack.Server.HandleEffectM: applyMeleeDamage :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: dominateFid :: MonadServerAtomic m => FactionId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectActivateInv :: MonadServerAtomic m => m () -> ActorId -> Char -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectApplyPerfume :: MonadServerAtomic m => m () -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectAscend :: MonadServerAtomic m => (Effect -> m Bool) -> m () -> Bool -> ActorId -> ActorId -> Point -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectBurn :: MonadServerAtomic m => Dice -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectComposite :: MonadServerAtomic m => (Effect -> m Bool) -> [Effect] -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectCreateItem :: MonadServerAtomic m => Maybe FactionId -> Maybe Int -> ActorId -> CStore -> GroupName ItemKind -> TimerDice -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDetect :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDetectActor :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDetectExit :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDetectHidden :: MonadServerAtomic m => m () -> Int -> ActorId -> Point -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDetectItem :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDetectX :: MonadServerAtomic m => (Point -> Bool) -> ([Point] -> m Bool) -> m () -> Int -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDominate :: MonadServerAtomic m => (Effect -> m Bool) -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDropBestWeapon :: MonadServerAtomic m => m () -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectDropItem :: MonadServerAtomic m => m () -> Int -> Int -> CStore -> GroupName ItemKind -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectEscape :: MonadServerAtomic m => ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectExplode :: MonadServerAtomic m => m () -> GroupName ItemKind -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectIdentify :: MonadServerAtomic m => m () -> ItemId -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectImpress :: MonadServerAtomic m => (Effect -> m Bool) -> m () -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectInsertMove :: MonadServerAtomic m => m () -> Dice -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectOneOf :: MonadServerAtomic m => (Effect -> m Bool) -> [Effect] -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectParalyze :: MonadServerAtomic m => m () -> Dice -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectPolyItem :: MonadServerAtomic m => m () -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectRecharging :: MonadServerAtomic m => (Effect -> m Bool) -> Effect -> Bool -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectRefillCalm :: MonadServerAtomic m => m () -> Int -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectRefillHP :: MonadServerAtomic m => Int -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectSem :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> Container -> Bool -> Bool -> Effect -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectSendFlying :: MonadServerAtomic m => m () -> ThrowMod -> ActorId -> ActorId -> Maybe Bool -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectSummon :: MonadServerAtomic m => m () -> GroupName ItemKind -> Dice -> ItemId -> ActorId -> ActorId -> Bool -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectTeleport :: MonadServerAtomic m => m () -> Dice -> ActorId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectTemporary :: MonadServerAtomic m => m () -> ActorId -> ItemId -> Container -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: effectTransformEqp :: forall m. MonadServerAtomic m => m () -> ActorId -> Char -> CStore -> (ItemId -> ItemQuant -> m ()) -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: findStairExit :: MonadStateRead m => FactionId -> Bool -> LevelId -> Point -> m Point
+ Game.LambdaHack.Server.HandleEffectM: identifyIid :: MonadServerAtomic m => ItemId -> Container -> Id ItemKind -> m ()
+ Game.LambdaHack.Server.HandleEffectM: imperishableKit :: [Effect] -> Bool -> ItemTimer -> ItemFull -> (Bool, ItemQuant)
+ Game.LambdaHack.Server.HandleEffectM: itemEffectDisco :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> Container -> Bool -> Bool -> [Effect] -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: refillHP :: MonadServerAtomic m => Bool -> ActorId -> Actor -> Int64 -> m ()
+ Game.LambdaHack.Server.HandleEffectM: sendFlyingVector :: MonadServerAtomic m => ActorId -> ActorId -> Maybe Bool -> m Vector
+ Game.LambdaHack.Server.HandleEffectM: switchLevels1 :: MonadServerAtomic m => (ActorId, Actor) -> m (Maybe ActorId)
+ Game.LambdaHack.Server.HandleEffectM: switchLevels2 :: MonadServerAtomic m => LevelId -> Point -> (ActorId, Actor) -> Time -> Maybe ActorId -> m ()
+ Game.LambdaHack.Server.HandleRequestM: managePerRequest :: MonadServerAtomic m => ActorId -> m ()
+ Game.LambdaHack.Server.ItemM: createLevelItem :: MonadServerAtomic m => Point -> LevelId -> m ()
+ Game.LambdaHack.Server.ItemM: embedItem :: MonadServerAtomic m => LevelId -> Point -> Id TileKind -> m ()
+ Game.LambdaHack.Server.ItemM: onlyRegisterItem :: MonadServerAtomic m => ItemKnown -> ItemSeed -> m ItemId
+ Game.LambdaHack.Server.LoopM: manageCalmAndDomination :: MonadServerAtomic m => ActorId -> Actor -> m ()
+ Game.LambdaHack.Server.MonadServer: chanSaveServer :: MonadServer m => m (ChanSave (State, StateServer))
+ Game.LambdaHack.Server.MonadServer: class MonadServer m => MonadServerAtomic m
+ Game.LambdaHack.Server.MonadServer: execSendPer :: MonadServerAtomic m => FactionId -> LevelId -> Perception -> Perception -> Perception -> m ()
+ Game.LambdaHack.Server.MonadServer: execSfxAtomic :: MonadServerAtomic m => SfxAtomic -> m ()
+ Game.LambdaHack.Server.MonadServer: execUpdAtomic :: MonadServerAtomic m => UpdAtomic -> m ()
+ Game.LambdaHack.Server.MonadServer: execUpdAtomicFid :: MonadServerAtomic m => FactionId -> UpdAtomic -> m ()
+ Game.LambdaHack.Server.MonadServer: execUpdAtomicFidCatch :: MonadServerAtomic m => FactionId -> UpdAtomic -> m Bool
+ Game.LambdaHack.Server.MonadServer: execUpdAtomicSer :: MonadServerAtomic m => UpdAtomic -> m Bool
+ Game.LambdaHack.Server.PeriodicM: rollSpawnPos :: COps -> EnumSet Point -> Bool -> LevelId -> Level -> FactionId -> State -> Rnd Point
+ Game.LambdaHack.Server.ProtocolM: ChanServer :: CliSerQueue Response -> CliSerQueue RequestAI -> Maybe (CliSerQueue RequestUI) -> ChanServer
+ Game.LambdaHack.Server.ProtocolM: [requestAIS] :: ChanServer -> CliSerQueue RequestAI
+ Game.LambdaHack.Server.ProtocolM: [requestUIS] :: ChanServer -> Maybe (CliSerQueue RequestUI)
+ Game.LambdaHack.Server.ProtocolM: [responseS] :: ChanServer -> CliSerQueue Response
+ Game.LambdaHack.Server.ProtocolM: data ChanServer
+ Game.LambdaHack.Server.ProtocolM: newQueue :: IO (CliSerQueue a)
+ Game.LambdaHack.Server.ProtocolM: readQueueAI :: MonadServerReadRequest m => CliSerQueue RequestAI -> m RequestAI
+ Game.LambdaHack.Server.ProtocolM: readQueueUI :: MonadServerReadRequest m => CliSerQueue RequestUI -> m RequestUI
+ Game.LambdaHack.Server.ProtocolM: sendUpdNoState :: MonadServerReadRequest m => FactionId -> UpdAtomic -> m ()
+ Game.LambdaHack.Server.ProtocolM: sendUpdateCheck :: (MonadServerAtomic m, MonadServerReadRequest m) => FactionId -> UpdAtomic -> m ()
+ Game.LambdaHack.Server.ProtocolM: type CliSerQueue = MVar
+ Game.LambdaHack.Server.ProtocolM: writeQueue :: MonadServerReadRequest m => Response -> CliSerQueue Response -> m ()
+ Game.LambdaHack.Server.ServerOptions: RNGs :: Maybe StdGen -> Maybe StdGen -> RNGs
+ Game.LambdaHack.Server.ServerOptions: ServerOptions :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe (GroupName ModeKind) -> Bool -> Bool -> Maybe StdGen -> Maybe StdGen -> Bool -> Challenge -> Bool -> String -> Bool -> ClientOptions -> ServerOptions
+ Game.LambdaHack.Server.ServerOptions: [dungeonRandomGenerator] :: RNGs -> Maybe StdGen
+ Game.LambdaHack.Server.ServerOptions: [sallClear] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sautomateAll] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sboostRandomItem] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sclientOptions] :: ServerOptions -> ClientOptions
+ Game.LambdaHack.Server.ServerOptions: [scurChalSer] :: ServerOptions -> Challenge
+ Game.LambdaHack.Server.ServerOptions: [sdbgMsgSer] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sdumpInitRngs] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sdungeonRng] :: ServerOptions -> Maybe StdGen
+ Game.LambdaHack.Server.ServerOptions: [sgameMode] :: ServerOptions -> Maybe (GroupName ModeKind)
+ Game.LambdaHack.Server.ServerOptions: [skeepAutomated] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sknowEvents] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sknowItems] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sknowMap] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [smainRng] :: ServerOptions -> Maybe StdGen
+ Game.LambdaHack.Server.ServerOptions: [snewGameSer] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [sniff] :: ServerOptions -> Bool
+ Game.LambdaHack.Server.ServerOptions: [ssavePrefixSer] :: ServerOptions -> String
+ Game.LambdaHack.Server.ServerOptions: [startingRandomGenerator] :: RNGs -> Maybe StdGen
+ Game.LambdaHack.Server.ServerOptions: data RNGs
+ Game.LambdaHack.Server.ServerOptions: data ServerOptions
+ Game.LambdaHack.Server.ServerOptions: defServerOptions :: ServerOptions
+ Game.LambdaHack.Server.ServerOptions: instance Data.Binary.Class.Binary Game.LambdaHack.Server.ServerOptions.RNGs
+ Game.LambdaHack.Server.ServerOptions: instance Data.Binary.Class.Binary Game.LambdaHack.Server.ServerOptions.ServerOptions
+ Game.LambdaHack.Server.ServerOptions: instance GHC.Show.Show Game.LambdaHack.Server.ServerOptions.RNGs
+ Game.LambdaHack.Server.ServerOptions: instance GHC.Show.Show Game.LambdaHack.Server.ServerOptions.ServerOptions
+ Game.LambdaHack.Server.StartM: findEntryPoss :: COps -> LevelId -> Level -> Int -> Rnd [Point]
+ Game.LambdaHack.Server.StartM: mapFromFuns :: (Bounded a, Enum a, Ord b) => [a -> b] -> Map b a
+ Game.LambdaHack.Server.StartM: populateDungeon :: MonadServerAtomic m => m ()
+ Game.LambdaHack.Server.StartM: resetFactions :: FactionDict -> Id ModeKind -> Int -> AbsDepth -> Roster -> Rnd FactionDict
+ Game.LambdaHack.Server.State: [sclientStates] :: StateServer -> EnumMap FactionId State
+ Game.LambdaHack.Server.State: [soptionsNxt] :: StateServer -> ServerOptions
+ Game.LambdaHack.Server.State: [soptions] :: StateServer -> ServerOptions
- Game.LambdaHack.Atomic: SfxIdentifyNothing :: CStore -> SfxMsg
+ Game.LambdaHack.Atomic: SfxIdentifyNothing :: SfxMsg
- Game.LambdaHack.Atomic: UpdCoverKind :: Container -> ItemId -> (Id ItemKind) -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdCoverKind :: Container -> ItemKindIx -> (Id ItemKind) -> UpdAtomic
- Game.LambdaHack.Atomic: UpdDiscoverKind :: Container -> ItemId -> (Id ItemKind) -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdDiscoverKind :: Container -> ItemKindIx -> (Id ItemKind) -> UpdAtomic
- Game.LambdaHack.Atomic: UpdRestart :: FactionId -> DiscoveryKind -> PerLid -> State -> Challenge -> DebugModeCli -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdRestart :: FactionId -> PerLid -> State -> Challenge -> ClientOptions -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: SfxIdentifyNothing :: CStore -> SfxMsg
+ Game.LambdaHack.Atomic.CmdAtomic: SfxIdentifyNothing :: SfxMsg
- Game.LambdaHack.Atomic.CmdAtomic: UpdCoverKind :: Container -> ItemId -> (Id ItemKind) -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdCoverKind :: Container -> ItemKindIx -> (Id ItemKind) -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdDiscoverKind :: Container -> ItemId -> (Id ItemKind) -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdDiscoverKind :: Container -> ItemKindIx -> (Id ItemKind) -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdRestart :: FactionId -> DiscoveryKind -> PerLid -> State -> Challenge -> DebugModeCli -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdRestart :: FactionId -> PerLid -> State -> Challenge -> ClientOptions -> UpdAtomic
- Game.LambdaHack.Client: loopCli :: (MonadClientSetup m, MonadClientUI m, MonadAtomic m, MonadClientReadResponse m, MonadClientWriteRequest m) => KeyKind -> Config -> DebugModeCli -> m ()
+ Game.LambdaHack.Client: loopCli :: (MonadClientSetup m, MonadClientUI m, MonadClientAtomic m, MonadClientReadResponse m, MonadClientWriteRequest m) => KeyKind -> UIOptions -> ClientOptions -> m ()
- Game.LambdaHack.Client.AI.ConditionM: condFloorWeaponM :: MonadClient m => ActorId -> m Bool
+ Game.LambdaHack.Client.AI.ConditionM: condFloorWeaponM :: MonadStateRead m => ActorId -> m Bool
- Game.LambdaHack.Client.AI.ConditionM: condNoEqpWeaponM :: MonadClient m => ActorId -> m Bool
+ Game.LambdaHack.Client.AI.ConditionM: condNoEqpWeaponM :: MonadStateRead m => ActorId -> m Bool
- Game.LambdaHack.Client.AI.ConditionM: condShineWouldBetrayM :: MonadClient m => ActorId -> m Bool
+ Game.LambdaHack.Client.AI.ConditionM: condShineWouldBetrayM :: MonadStateRead m => ActorId -> m Bool
- Game.LambdaHack.Client.AI.ConditionM: desirableItem :: Bool -> Maybe Int -> Item -> Bool
+ Game.LambdaHack.Client.AI.ConditionM: desirableItem :: Bool -> Maybe Double -> Item -> Bool
- Game.LambdaHack.Client.AI.ConditionM: meleeThreatDistList :: MonadClient m => ActorId -> m [(Int, (ActorId, Actor))]
+ Game.LambdaHack.Client.AI.ConditionM: meleeThreatDistList :: MonadStateRead m => ActorId -> m [(Int, (ActorId, Actor))]
- Game.LambdaHack.Client.BfsM: embedBenefit :: MonadClient m => FleeViaStairsOrEscape -> ActorId -> [(Point, ItemBag)] -> m [(Int, (Point, ItemBag))]
+ Game.LambdaHack.Client.BfsM: embedBenefit :: MonadClient m => FleeViaStairsOrEscape -> ActorId -> [(Point, ItemBag)] -> m [(Double, (Point, ItemBag))]
- Game.LambdaHack.Client.CommonM: aidTgtToPos :: MonadClient m => ActorId -> LevelId -> Target -> m (Maybe Point)
+ Game.LambdaHack.Client.CommonM: aidTgtToPos :: MonadStateRead m => ActorId -> LevelId -> Target -> m (Maybe Point)
- Game.LambdaHack.Client.CommonM: makeLine :: MonadClient m => Bool -> Actor -> Point -> Int -> m (Maybe Int)
+ Game.LambdaHack.Client.CommonM: makeLine :: MonadStateRead m => Bool -> Actor -> Point -> Int -> m (Maybe Int)
- Game.LambdaHack.Client.HandleAtomicM: cmdAtomicSemCli :: MonadClientSetup m => UpdAtomic -> m ()
+ Game.LambdaHack.Client.HandleAtomicM: cmdAtomicSemCli :: MonadClientSetup m => State -> UpdAtomic -> m ()
- Game.LambdaHack.Client.HandleResponseM: handleResponse :: (MonadClientSetup m, MonadClientUI m, MonadAtomic m, MonadClientWriteRequest m) => Response -> m ()
+ Game.LambdaHack.Client.HandleResponseM: handleResponse :: (MonadClientSetup m, MonadClientUI m, MonadClientAtomic m, MonadClientWriteRequest m) => Response -> m ()
- Game.LambdaHack.Client.LoopM: loopCli :: (MonadClientSetup m, MonadClientUI m, MonadAtomic m, MonadClientReadResponse m, MonadClientWriteRequest m) => KeyKind -> Config -> DebugModeCli -> m ()
+ Game.LambdaHack.Client.LoopM: loopCli :: (MonadClientSetup m, MonadClientUI m, MonadClientAtomic m, MonadClientReadResponse m, MonadClientWriteRequest m) => KeyKind -> UIOptions -> ClientOptions -> m ()
- Game.LambdaHack.Client.Preferences: aspectToBenefit :: Aspect -> Int
+ Game.LambdaHack.Client.Preferences: aspectToBenefit :: Aspect -> Double
- Game.LambdaHack.Client.Preferences: effectToBenefit :: COps -> Faction -> Effect -> (Int, Int)
+ Game.LambdaHack.Client.Preferences: effectToBenefit :: COps -> Faction -> Effect -> (Double, Double)
- Game.LambdaHack.Client.Preferences: organBenefit :: Int -> GroupName ItemKind -> COps -> Faction -> (Int, Int)
+ Game.LambdaHack.Client.Preferences: organBenefit :: Double -> GroupName ItemKind -> COps -> Faction -> (Double, Int)
- Game.LambdaHack.Client.Preferences: recordToBenefit :: AspectRecord -> [Int]
+ Game.LambdaHack.Client.Preferences: recordToBenefit :: AspectRecord -> [Double]
- Game.LambdaHack.Client.State: StateClient :: Int -> EnumMap ActorId TgtAndPath -> EnumSet LevelId -> EnumMap ActorId BfsAndPath -> [CmdAtomic] -> DiscoveryKind -> DiscoveryAspect -> DiscoveryBenefit -> ActorAspect -> PerLid -> AlterLid -> StdGen -> Maybe ActorId -> FactionId -> Bool -> Challenge -> Challenge -> Int -> Int -> EnumMap LevelId (Maybe Bool) -> EnumMap (Id ModeKind) (Map Challenge Int) -> DebugModeCli -> StateClient
+ Game.LambdaHack.Client.State: StateClient :: Int -> EnumMap ActorId TgtAndPath -> EnumSet LevelId -> EnumMap ActorId BfsAndPath -> [CmdAtomic] -> DiscoveryBenefit -> PerLid -> AlterLid -> StdGen -> Maybe ActorId -> FactionId -> Bool -> Challenge -> Challenge -> Int -> Int -> EnumMap LevelId (Maybe Bool) -> EnumMap (Id ModeKind) (Map Challenge Int) -> ClientOptions -> StateClient
- Game.LambdaHack.Client.UI: SessionUI :: Target -> ActorDictUI -> ItemSlots -> SlotChar -> Maybe (CStore, CStore) -> ChanFrontend -> Binding -> Config -> Maybe AimMode -> Bool -> Maybe (CStore, ItemId) -> EnumSet ActorId -> Maybe RunParams -> Report -> History -> Point -> LastRecord -> [KM] -> EnumSet ActorId -> Int -> Bool -> Bool -> Map String Int -> Bool -> KeysHintMode -> POSIXTime -> POSIXTime -> Time -> Int -> Int -> SessionUI
+ Game.LambdaHack.Client.UI: SessionUI :: Target -> ActorDictUI -> ItemSlots -> SlotChar -> Maybe (CStore, CStore) -> ChanFrontend -> Binding -> UIOptions -> Maybe AimMode -> Bool -> Maybe (CStore, ItemId) -> EnumSet ActorId -> Maybe RunParams -> Report -> History -> Point -> LastRecord -> [KM] -> EnumSet ActorId -> Int -> Bool -> Bool -> Map String Int -> Bool -> KeysHintMode -> POSIXTime -> POSIXTime -> Time -> Int -> Int -> SessionUI
- Game.LambdaHack.Client.UI: chanFrontend :: MonadClientUI m => DebugModeCli -> m ChanFrontend
+ Game.LambdaHack.Client.UI: chanFrontend :: MonadClientUI m => ClientOptions -> m ChanFrontend
- Game.LambdaHack.Client.UI: displayRespUpdAtomicUI :: MonadClientUI m => Bool -> StateClient -> UpdAtomic -> m ()
+ Game.LambdaHack.Client.UI: displayRespUpdAtomicUI :: MonadClientUI m => Bool -> UpdAtomic -> m ()
- Game.LambdaHack.Client.UI: stdBinding :: KeyKind -> Config -> Binding
+ Game.LambdaHack.Client.UI: stdBinding :: KeyKind -> UIOptions -> Binding
- Game.LambdaHack.Client.UI: tryRestore :: MonadClientUI m => m (Maybe (State, StateClient, Maybe SessionUI))
+ Game.LambdaHack.Client.UI: tryRestore :: MonadClientUI m => m (Maybe (StateClient, Maybe SessionUI))
- Game.LambdaHack.Client.UI.DisplayAtomicM: displayRespUpdAtomicUI :: MonadClientUI m => Bool -> StateClient -> UpdAtomic -> m ()
+ Game.LambdaHack.Client.UI.DisplayAtomicM: displayRespUpdAtomicUI :: MonadClientUI m => Bool -> UpdAtomic -> m ()
- Game.LambdaHack.Client.UI.Frame: overlayFrame :: Overlay -> FrameForall -> FrameForall
+ Game.LambdaHack.Client.UI.Frame: overlayFrame :: IntOverlay -> FrameForall -> FrameForall
- Game.LambdaHack.Client.UI.Frame: overlayFrameWithLines :: Bool -> [AttrLine] -> FrameForall -> FrameForall
+ Game.LambdaHack.Client.UI.Frame: overlayFrameWithLines :: Bool -> Overlay -> FrameForall -> FrameForall
- Game.LambdaHack.Client.UI.FrameM: drawOverlay :: MonadClientUI m => ColorMode -> Bool -> [AttrLine] -> LevelId -> m FrameForall
+ Game.LambdaHack.Client.UI.FrameM: drawOverlay :: MonadClientUI m => ColorMode -> Bool -> Overlay -> LevelId -> m FrameForall
- Game.LambdaHack.Client.UI.FrameM: promptGetKey :: MonadClientUI m => ColorMode -> [AttrLine] -> Bool -> [KM] -> m KM
+ Game.LambdaHack.Client.UI.FrameM: promptGetKey :: MonadClientUI m => ColorMode -> Overlay -> Bool -> [KM] -> m KM
- Game.LambdaHack.Client.UI.Frontend: chanFrontendIO :: DebugModeCli -> IO ChanFrontend
+ Game.LambdaHack.Client.UI.Frontend: chanFrontendIO :: ClientOptions -> IO ChanFrontend
- Game.LambdaHack.Client.UI.Frontend: fchanFrontend :: DebugModeCli -> FSession -> RawFrontend -> ChanFrontend
+ Game.LambdaHack.Client.UI.Frontend: fchanFrontend :: ClientOptions -> FrontSetup -> RawFrontend -> ChanFrontend
- Game.LambdaHack.Client.UI.Frontend: getKey :: DebugModeCli -> FSession -> RawFrontend -> [KM] -> FrameForall -> IO KMP
+ Game.LambdaHack.Client.UI.Frontend: getKey :: ClientOptions -> FrontSetup -> RawFrontend -> [KM] -> FrameForall -> IO KMP
- Game.LambdaHack.Client.UI.Frontend.Chosen: startup :: DebugModeCli -> IO RawFrontend
+ Game.LambdaHack.Client.UI.Frontend.Chosen: startup :: ClientOptions -> IO RawFrontend
- Game.LambdaHack.Client.UI.Frontend.Teletype: startup :: DebugModeCli -> IO RawFrontend
+ Game.LambdaHack.Client.UI.Frontend.Teletype: startup :: ClientOptions -> IO RawFrontend
- Game.LambdaHack.Client.UI.ItemSlot: ItemSlots :: (EnumMap SlotChar ItemId) -> (EnumMap SlotChar ItemId) -> ItemSlots
+ Game.LambdaHack.Client.UI.ItemSlot: ItemSlots :: EnumMap SlotChar ItemId -> EnumMap SlotChar ItemId -> ItemSlots
- Game.LambdaHack.Client.UI.KeyBindings: stdBinding :: KeyKind -> Config -> Binding
+ Game.LambdaHack.Client.UI.KeyBindings: stdBinding :: KeyKind -> UIOptions -> Binding
- Game.LambdaHack.Client.UI.MonadClientUI: chanFrontend :: MonadClientUI m => DebugModeCli -> m ChanFrontend
+ Game.LambdaHack.Client.UI.MonadClientUI: chanFrontend :: MonadClientUI m => ClientOptions -> m ChanFrontend
- Game.LambdaHack.Client.UI.MonadClientUI: tryRestore :: MonadClientUI m => m (Maybe (State, StateClient, Maybe SessionUI))
+ Game.LambdaHack.Client.UI.MonadClientUI: tryRestore :: MonadClientUI m => m (Maybe (StateClient, Maybe SessionUI))
- Game.LambdaHack.Client.UI.Overlay: glueLines :: [AttrLine] -> [AttrLine] -> [AttrLine]
+ Game.LambdaHack.Client.UI.Overlay: glueLines :: Overlay -> Overlay -> Overlay
- Game.LambdaHack.Client.UI.Overlay: splitAttrLine :: X -> AttrLine -> [AttrLine]
+ Game.LambdaHack.Client.UI.Overlay: splitAttrLine :: X -> AttrLine -> Overlay
- Game.LambdaHack.Client.UI.Overlay: type Overlay = [(Int, AttrLine)]
+ Game.LambdaHack.Client.UI.Overlay: type Overlay = [AttrLine]
- Game.LambdaHack.Client.UI.Overlay: updateLines :: Int -> (AttrLine -> AttrLine) -> [AttrLine] -> [AttrLine]
+ Game.LambdaHack.Client.UI.Overlay: updateLines :: Int -> (AttrLine -> AttrLine) -> Overlay -> Overlay
- Game.LambdaHack.Client.UI.SessionUI: SessionUI :: Target -> ActorDictUI -> ItemSlots -> SlotChar -> Maybe (CStore, CStore) -> ChanFrontend -> Binding -> Config -> Maybe AimMode -> Bool -> Maybe (CStore, ItemId) -> EnumSet ActorId -> Maybe RunParams -> Report -> History -> Point -> LastRecord -> [KM] -> EnumSet ActorId -> Int -> Bool -> Bool -> Map String Int -> Bool -> KeysHintMode -> POSIXTime -> POSIXTime -> Time -> Int -> Int -> SessionUI
+ Game.LambdaHack.Client.UI.SessionUI: SessionUI :: Target -> ActorDictUI -> ItemSlots -> SlotChar -> Maybe (CStore, CStore) -> ChanFrontend -> Binding -> UIOptions -> Maybe AimMode -> Bool -> Maybe (CStore, ItemId) -> EnumSet ActorId -> Maybe RunParams -> Report -> History -> Point -> LastRecord -> [KM] -> EnumSet ActorId -> Int -> Bool -> Bool -> Map String Int -> Bool -> KeysHintMode -> POSIXTime -> POSIXTime -> Time -> Int -> Int -> SessionUI
- Game.LambdaHack.Client.UI.SessionUI: emptySessionUI :: Config -> SessionUI
+ Game.LambdaHack.Client.UI.SessionUI: emptySessionUI :: UIOptions -> SessionUI
- Game.LambdaHack.Client.UI.Slideshow: type OKX = ([AttrLine], [KYX])
+ Game.LambdaHack.Client.UI.Slideshow: type OKX = (Overlay, [KYX])
- Game.LambdaHack.Common.Actor: Actor :: ItemId -> Int64 -> ResDelta -> Int64 -> ResDelta -> Point -> (Maybe Point) -> LevelId -> FactionId -> (Maybe ([Vector], Speed)) -> ItemBag -> ItemBag -> ItemBag -> Int -> Bool -> Bool -> Actor
+ Game.LambdaHack.Common.Actor: Actor :: ItemId -> Int64 -> ResDelta -> Int64 -> ResDelta -> Point -> Maybe Point -> LevelId -> FactionId -> Maybe ([Vector], Speed) -> ItemBag -> ItemBag -> ItemBag -> Int -> Bool -> Bool -> Actor
- Game.LambdaHack.Common.Actor: [boldpos] :: Actor -> (Maybe Point)
+ Game.LambdaHack.Common.Actor: [boldpos] :: Actor -> Maybe Point
- Game.LambdaHack.Common.Actor: [btrajectory] :: Actor -> (Maybe ([Vector], Speed))
+ Game.LambdaHack.Common.Actor: [btrajectory] :: Actor -> Maybe ([Vector], Speed)
- Game.LambdaHack.Common.ActorState: actorSkills :: Maybe ActorId -> ActorId -> AspectRecord -> State -> Skills
+ Game.LambdaHack.Common.ActorState: actorSkills :: Maybe ActorId -> ActorId -> State -> Skills
- Game.LambdaHack.Common.ActorState: armorHurtBonus :: ActorAspect -> ActorId -> ActorId -> State -> Int
+ Game.LambdaHack.Common.ActorState: armorHurtBonus :: ActorId -> ActorId -> State -> Int
- Game.LambdaHack.Common.ActorState: fullAssocs :: COps -> DiscoveryKind -> DiscoveryAspect -> ActorId -> [CStore] -> State -> [(ItemId, ItemFull)]
+ Game.LambdaHack.Common.ActorState: fullAssocs :: ActorId -> [CStore] -> State -> [(ItemId, ItemFull)]
- Game.LambdaHack.Common.Dice: d :: Int -> Dice
+ Game.LambdaHack.Common.Dice: d :: Int -> Int -> Dice
- Game.LambdaHack.Common.Dice: dl :: Int -> Dice
+ Game.LambdaHack.Common.Dice: dl :: Int -> Int -> Dice
- Game.LambdaHack.Common.Dice: meanDice :: Dice -> Int
+ Game.LambdaHack.Common.Dice: meanDice :: Dice -> Double
- Game.LambdaHack.Common.Dice: meanDiceXY :: DiceXY -> (Int, Int)
+ Game.LambdaHack.Common.Dice: meanDiceXY :: DiceXY -> (Double, Double)
- Game.LambdaHack.Common.Item: Benefit :: ~Bool -> ~Int -> ~Int -> ~Int -> ~Int -> Benefit
+ Game.LambdaHack.Common.Item: Benefit :: ~Bool -> ~Double -> ~Double -> ~Double -> ~Double -> Benefit
- Game.LambdaHack.Common.Item: ItemDisco :: Id ItemKind -> ItemKind -> AspectRecord -> Maybe AspectRecord -> ItemDisco
+ Game.LambdaHack.Common.Item: ItemDisco :: Id ItemKind -> ItemKind -> AspectRecord -> Bool -> Maybe AspectRecord -> ItemDisco
- Game.LambdaHack.Common.Item: KindMean :: Id ItemKind -> AspectRecord -> KindMean
+ Game.LambdaHack.Common.Item: KindMean :: Id ItemKind -> AspectRecord -> Bool -> KindMean
- Game.LambdaHack.Common.Item: [benApply] :: Benefit -> ~Int
+ Game.LambdaHack.Common.Item: [benApply] :: Benefit -> ~Double
- Game.LambdaHack.Common.Item: [benFling] :: Benefit -> ~Int
+ Game.LambdaHack.Common.Item: [benFling] :: Benefit -> ~Double
- Game.LambdaHack.Common.Item: [benMelee] :: Benefit -> ~Int
+ Game.LambdaHack.Common.Item: [benMelee] :: Benefit -> ~Double
- Game.LambdaHack.Common.Item: [benPickup] :: Benefit -> ~Int
+ Game.LambdaHack.Common.Item: [benPickup] :: Benefit -> ~Double
- Game.LambdaHack.Common.ItemStrongest: damageUsefulness :: Item -> Int
+ Game.LambdaHack.Common.ItemStrongest: damageUsefulness :: Item -> Double
- Game.LambdaHack.Common.ItemStrongest: strongestMelee :: Maybe DiscoveryBenefit -> Time -> [(ItemId, ItemFull)] -> [(Int, (ItemId, ItemFull))]
+ Game.LambdaHack.Common.ItemStrongest: strongestMelee :: Maybe DiscoveryBenefit -> Time -> [(ItemId, ItemFull)] -> [(Double, (ItemId, ItemFull))]
- Game.LambdaHack.Common.Level: Level :: AbsDepth -> ItemFloor -> ItemFloor -> ActorMap -> TileMap -> X -> Y -> SmellMap -> Text -> ([Point], [Point]) -> Int -> Int -> Time -> Int -> Freqs ItemKind -> Int -> Freqs ItemKind -> [Point] -> Bool -> Level
+ Game.LambdaHack.Common.Level: Level :: AbsDepth -> ItemFloor -> ItemFloor -> ActorMap -> TileMap -> X -> Y -> SmellMap -> ([Point], [Point]) -> Int -> Int -> Time -> Int -> Freqs ItemKind -> Int -> Freqs ItemKind -> [Point] -> Bool -> Text -> Text -> Level
- Game.LambdaHack.Common.MonadStateRead: pickWeaponM :: MonadStateRead m => Maybe DiscoveryBenefit -> [(ItemId, ItemFull)] -> Skills -> ActorAspect -> ActorId -> m [(Int, (ItemId, ItemFull))]
+ Game.LambdaHack.Common.MonadStateRead: pickWeaponM :: MonadStateRead m => Maybe DiscoveryBenefit -> [(ItemId, ItemFull)] -> Skills -> ActorId -> m [(Double, (ItemId, ItemFull))]
- Game.LambdaHack.Common.State: defStateGlobal :: Dungeon -> AbsDepth -> FactionDict -> COps -> ScoreDict -> Id ModeKind -> State
+ Game.LambdaHack.Common.State: defStateGlobal :: Dungeon -> AbsDepth -> FactionDict -> COps -> ScoreDict -> Id ModeKind -> DiscoveryKind -> State
- Game.LambdaHack.Common.Tile: hideAs :: Ops TileKind -> Id TileKind -> Id TileKind
+ Game.LambdaHack.Common.Tile: hideAs :: Ops TileKind -> Id TileKind -> Maybe (Id TileKind)
- Game.LambdaHack.Content.CaveKind: CaveKind :: Char -> Text -> Freqs CaveKind -> X -> Y -> DiceXY -> DiceXY -> DiceXY -> Dice -> Dice -> Rational -> Rational -> Int -> Dice -> Chance -> Chance -> Int -> Int -> Freqs ItemKind -> Dice -> Freqs ItemKind -> Freqs PlaceKind -> Bool -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> Maybe (GroupName PlaceKind) -> Freqs PlaceKind -> CaveKind
+ Game.LambdaHack.Content.CaveKind: CaveKind :: Char -> Text -> Freqs CaveKind -> X -> Y -> DiceXY -> DiceXY -> DiceXY -> Dice -> Dice -> Rational -> Rational -> Int -> Dice -> Chance -> Chance -> Int -> Int -> Freqs ItemKind -> Dice -> Freqs ItemKind -> Freqs PlaceKind -> Bool -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> GroupName TileKind -> Maybe (GroupName PlaceKind) -> Freqs PlaceKind -> Text -> CaveKind
- Game.LambdaHack.SampleImplementation.SampleMonadClient: CliState :: State -> StateClient -> Maybe SessionUI -> ChanServer -> ChanSave (State, StateClient, Maybe SessionUI) -> CliState
+ Game.LambdaHack.SampleImplementation.SampleMonadClient: CliState :: State -> StateClient -> Maybe SessionUI -> ChanServer -> ChanSave (StateClient, Maybe SessionUI) -> CliState
- Game.LambdaHack.SampleImplementation.SampleMonadClient: [cliToSave] :: CliState -> ChanSave (State, StateClient, Maybe SessionUI)
+ Game.LambdaHack.SampleImplementation.SampleMonadClient: [cliToSave] :: CliState -> ChanSave (StateClient, Maybe SessionUI)
- Game.LambdaHack.SampleImplementation.SampleMonadClient: executorCli :: KeyKind -> Config -> DebugModeCli -> COps -> Maybe SessionUI -> FactionId -> ChanServer -> IO ()
+ Game.LambdaHack.SampleImplementation.SampleMonadClient: executorCli :: KeyKind -> UIOptions -> ClientOptions -> COps -> Bool -> FactionId -> ChanServer -> IO ()
- Game.LambdaHack.SampleImplementation.SampleMonadServer: executorSer :: COps -> KeyKind -> DebugModeSer -> IO ()
+ Game.LambdaHack.SampleImplementation.SampleMonadServer: executorSer :: COps -> KeyKind -> ServerOptions -> IO ()
- Game.LambdaHack.Server: [sallClear] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sallClear] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [sautomateAll] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sautomateAll] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [sboostRandomItem] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sboostRandomItem] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [scurChalSer] :: DebugModeSer -> Challenge
+ Game.LambdaHack.Server: [scurChalSer] :: ServerOptions -> Challenge
- Game.LambdaHack.Server: [sdbgMsgSer] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sdbgMsgSer] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [sdumpInitRngs] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sdumpInitRngs] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [sdungeonRng] :: DebugModeSer -> Maybe StdGen
+ Game.LambdaHack.Server: [sdungeonRng] :: ServerOptions -> Maybe StdGen
- Game.LambdaHack.Server: [sgameMode] :: DebugModeSer -> Maybe (GroupName ModeKind)
+ Game.LambdaHack.Server: [sgameMode] :: ServerOptions -> Maybe (GroupName ModeKind)
- Game.LambdaHack.Server: [skeepAutomated] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [skeepAutomated] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [sknowEvents] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sknowEvents] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [sknowItems] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sknowItems] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [sknowMap] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [sknowMap] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [smainRng] :: DebugModeSer -> Maybe StdGen
+ Game.LambdaHack.Server: [smainRng] :: ServerOptions -> Maybe StdGen
- Game.LambdaHack.Server: [snewGameSer] :: DebugModeSer -> Bool
+ Game.LambdaHack.Server: [snewGameSer] :: ServerOptions -> Bool
- Game.LambdaHack.Server: [ssavePrefixSer] :: DebugModeSer -> String
+ Game.LambdaHack.Server: [ssavePrefixSer] :: ServerOptions -> String
- Game.LambdaHack.Server: loopSer :: (MonadAtomic m, MonadServerReadRequest m) => DebugModeSer -> Config -> (Maybe SessionUI -> FactionId -> ChanServer -> IO ()) -> m ()
+ Game.LambdaHack.Server: loopSer :: (MonadServerAtomic m, MonadServerReadRequest m) => ServerOptions -> (Bool -> FactionId -> ChanServer -> IO ()) -> m ()
- Game.LambdaHack.Server.BroadcastAtomic: atomicRemember :: LevelId -> Perception -> State -> [UpdAtomic]
+ Game.LambdaHack.Server.BroadcastAtomic: atomicRemember :: LevelId -> Perception -> State -> State -> [UpdAtomic]
- Game.LambdaHack.Server.BroadcastAtomic: handleAndBroadcast :: (MonadStateWrite m, MonadServerReadRequest m) => CmdAtomic -> m ()
+ Game.LambdaHack.Server.BroadcastAtomic: handleAndBroadcast :: (MonadServerAtomic m, MonadServerReadRequest m) => PosAtomic -> [UpdAtomic] -> CmdAtomic -> m ()
- Game.LambdaHack.Server.BroadcastAtomic: handleCmdAtomicServer :: MonadStateWrite m => PosAtomic -> UpdAtomic -> m ()
+ Game.LambdaHack.Server.BroadcastAtomic: handleCmdAtomicServer :: MonadServerAtomic m => UpdAtomic -> m (PosAtomic, [UpdAtomic], Bool)
- Game.LambdaHack.Server.BroadcastAtomic: sendPer :: MonadServerReadRequest m => FactionId -> LevelId -> Perception -> Perception -> Perception -> m ()
+ Game.LambdaHack.Server.BroadcastAtomic: sendPer :: (MonadServerAtomic m, MonadServerReadRequest m) => FactionId -> LevelId -> Perception -> Perception -> Perception -> m ()
- Game.LambdaHack.Server.CommonM: addActor :: (MonadAtomic m, MonadServer m) => GroupName ItemKind -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)
+ Game.LambdaHack.Server.CommonM: addActor :: MonadServerAtomic m => GroupName ItemKind -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)
- Game.LambdaHack.Server.CommonM: addActorIid :: (MonadAtomic m, MonadServer m) => ItemId -> ItemFull -> Bool -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)
+ Game.LambdaHack.Server.CommonM: addActorIid :: MonadServerAtomic m => ItemId -> ItemFull -> Bool -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)
- Game.LambdaHack.Server.CommonM: deduceKilled :: (MonadAtomic m, MonadServer m) => ActorId -> m ()
+ Game.LambdaHack.Server.CommonM: deduceKilled :: MonadServerAtomic m => ActorId -> m ()
- Game.LambdaHack.Server.CommonM: deduceQuits :: (MonadAtomic m, MonadServer m) => FactionId -> Status -> m ()
+ Game.LambdaHack.Server.CommonM: deduceQuits :: MonadServerAtomic m => FactionId -> Status -> m ()
- Game.LambdaHack.Server.CommonM: discoverIfNoEffects :: (MonadAtomic m, MonadServer m) => Container -> ItemId -> ItemFull -> m ()
+ Game.LambdaHack.Server.CommonM: discoverIfNoEffects :: MonadServerAtomic m => Container -> ItemId -> ItemFull -> m ()
- Game.LambdaHack.Server.CommonM: electLeader :: MonadAtomic m => FactionId -> LevelId -> ActorId -> m ()
+ Game.LambdaHack.Server.CommonM: electLeader :: MonadServerAtomic m => FactionId -> LevelId -> ActorId -> m ()
- Game.LambdaHack.Server.CommonM: execFailure :: (MonadAtomic m, MonadServer m) => ActorId -> RequestTimed a -> ReqFailure -> m ()
+ Game.LambdaHack.Server.CommonM: execFailure :: MonadServerAtomic m => ActorId -> RequestTimed a -> ReqFailure -> m ()
- Game.LambdaHack.Server.CommonM: moveStores :: (MonadAtomic m, MonadServer m) => Bool -> ActorId -> CStore -> CStore -> m ()
+ Game.LambdaHack.Server.CommonM: moveStores :: MonadServerAtomic m => Bool -> ActorId -> CStore -> CStore -> m ()
- Game.LambdaHack.Server.CommonM: projectFail :: (MonadAtomic m, MonadServer m) => ActorId -> Point -> Int -> ItemId -> CStore -> Bool -> m (Maybe ReqFailure)
+ Game.LambdaHack.Server.CommonM: projectFail :: MonadServerAtomic m => ActorId -> Point -> Int -> ItemId -> CStore -> Bool -> m (Maybe ReqFailure)
- Game.LambdaHack.Server.CommonM: registerActor :: (MonadAtomic m, MonadServer m) => ItemKnown -> ItemFull -> ItemDisco -> ItemSeed -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)
+ Game.LambdaHack.Server.CommonM: registerActor :: MonadServerAtomic m => ItemKnown -> ItemFull -> ItemSeed -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)
- Game.LambdaHack.Server.CommonM: revealItems :: (MonadAtomic m, MonadServer m) => Maybe FactionId -> m ()
+ Game.LambdaHack.Server.CommonM: revealItems :: MonadServerAtomic m => Maybe FactionId -> m ()
- Game.LambdaHack.Server.CommonM: supplantLeader :: MonadAtomic m => FactionId -> ActorId -> m ()
+ Game.LambdaHack.Server.CommonM: supplantLeader :: MonadServerAtomic m => FactionId -> ActorId -> m ()
- Game.LambdaHack.Server.DebugM: debugRequestAI :: MonadServer m => ActorId -> RequestAI -> m ()
+ Game.LambdaHack.Server.DebugM: debugRequestAI :: MonadServer m => ActorId -> m ()
- Game.LambdaHack.Server.DebugM: debugRequestUI :: MonadServer m => ActorId -> RequestUI -> m ()
+ Game.LambdaHack.Server.DebugM: debugRequestUI :: MonadServer m => ActorId -> m ()
- Game.LambdaHack.Server.EndM: dieSer :: (MonadAtomic m, MonadServer m) => ActorId -> Actor -> m ()
+ Game.LambdaHack.Server.EndM: dieSer :: MonadServerAtomic m => ActorId -> Actor -> m ()
- Game.LambdaHack.Server.EndM: endOrLoop :: (MonadAtomic m, MonadServer m) => m () -> (Maybe (GroupName ModeKind) -> m ()) -> m () -> m () -> m ()
+ Game.LambdaHack.Server.EndM: endOrLoop :: (MonadServerAtomic m, MonadServerReadRequest m) => m () -> (Maybe (GroupName ModeKind) -> m ()) -> m () -> m ()
- Game.LambdaHack.Server.Fov: lucidFromLevel :: DiscoveryAspect -> ActorAspect -> FovClearLid -> FovLitLid -> State -> LevelId -> Level -> FovLucid
+ Game.LambdaHack.Server.Fov: lucidFromLevel :: FovClearLid -> FovLitLid -> State -> LevelId -> Level -> FovLucid
- Game.LambdaHack.Server.Fov: lucidInDungeon :: DiscoveryAspect -> ActorAspect -> FovClearLid -> FovLitLid -> State -> FovLucidLid
+ Game.LambdaHack.Server.Fov: lucidInDungeon :: FovClearLid -> FovLitLid -> State -> FovLucidLid
- Game.LambdaHack.Server.Fov: perFidInDungeon :: DiscoveryAspect -> State -> (ActorAspect, FovLitLid, FovClearLid, FovLucidLid, PerValidFid, PerCacheFid, PerFid)
+ Game.LambdaHack.Server.Fov: perFidInDungeon :: State -> (FovLitLid, FovClearLid, FovLucidLid, PerValidFid, PerCacheFid, PerFid)
- Game.LambdaHack.Server.Fov: perLidFromFaction :: ActorAspect -> FovLucidLid -> FovClearLid -> FactionId -> State -> (PerLid, PerCacheLid)
+ Game.LambdaHack.Server.Fov: perLidFromFaction :: FovLucidLid -> FovClearLid -> FactionId -> State -> (PerLid, PerCacheLid)
- Game.LambdaHack.Server.Fov: perceptionCacheFromLevel :: ActorAspect -> FovClearLid -> FactionId -> LevelId -> State -> PerceptionCache
+ Game.LambdaHack.Server.Fov: perceptionCacheFromLevel :: FovClearLid -> FactionId -> LevelId -> State -> PerceptionCache
- Game.LambdaHack.Server.Fov: shineFromLevel :: DiscoveryAspect -> ActorAspect -> State -> LevelId -> Level -> FovShine
+ Game.LambdaHack.Server.Fov: shineFromLevel :: State -> LevelId -> Level -> FovShine
- Game.LambdaHack.Server.HandleAtomicM: cmdAtomicSemSer :: MonadServer m => UpdAtomic -> m ()
+ Game.LambdaHack.Server.HandleAtomicM: cmdAtomicSemSer :: MonadServer m => State -> UpdAtomic -> m ()
- Game.LambdaHack.Server.HandleAtomicM: deletePerActor :: MonadServer m => ActorId -> Actor -> m ()
+ Game.LambdaHack.Server.HandleAtomicM: deletePerActor :: MonadServer m => ActorAspect -> ActorId -> Actor -> m ()
- Game.LambdaHack.Server.HandleEffectM: applyItem :: (MonadAtomic m, MonadServer m) => ActorId -> ItemId -> CStore -> m ()
+ Game.LambdaHack.Server.HandleEffectM: applyItem :: MonadServerAtomic m => ActorId -> ItemId -> CStore -> m ()
- Game.LambdaHack.Server.HandleEffectM: cutCalm :: (MonadAtomic m, MonadServer m) => ActorId -> m ()
+ Game.LambdaHack.Server.HandleEffectM: cutCalm :: MonadServerAtomic m => ActorId -> m ()
- Game.LambdaHack.Server.HandleEffectM: dominateFidSfx :: (MonadAtomic m, MonadServer m) => FactionId -> ActorId -> m Bool
+ Game.LambdaHack.Server.HandleEffectM: dominateFidSfx :: MonadServerAtomic m => FactionId -> ActorId -> m Bool
- Game.LambdaHack.Server.HandleEffectM: dropCStoreItem :: (MonadAtomic m, MonadServer m) => Bool -> CStore -> ActorId -> Actor -> Int -> ItemId -> ItemQuant -> m ()
+ Game.LambdaHack.Server.HandleEffectM: dropCStoreItem :: MonadServerAtomic m => Bool -> CStore -> ActorId -> Actor -> Int -> ItemId -> ItemQuant -> m ()
- Game.LambdaHack.Server.HandleEffectM: effectAndDestroy :: (MonadAtomic m, MonadServer m) => Bool -> ActorId -> ActorId -> ItemId -> Container -> Bool -> [Effect] -> ItemFull -> m ()
+ Game.LambdaHack.Server.HandleEffectM: effectAndDestroy :: MonadServerAtomic m => Bool -> ActorId -> ActorId -> ItemId -> Container -> Bool -> [Effect] -> ItemFull -> m ()
- Game.LambdaHack.Server.HandleEffectM: itemEffectEmbedded :: (MonadAtomic m, MonadServer m) => ActorId -> Point -> ItemBag -> m ()
+ Game.LambdaHack.Server.HandleEffectM: itemEffectEmbedded :: MonadServerAtomic m => ActorId -> Point -> ItemBag -> m ()
- Game.LambdaHack.Server.HandleEffectM: meleeEffectAndDestroy :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> ItemId -> Container -> m ()
+ Game.LambdaHack.Server.HandleEffectM: meleeEffectAndDestroy :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> Container -> m ()
- Game.LambdaHack.Server.HandleRequestM: affectSmell :: (MonadAtomic m, MonadServer m) => ActorId -> m ()
+ Game.LambdaHack.Server.HandleRequestM: affectSmell :: MonadServerAtomic m => ActorId -> m ()
- Game.LambdaHack.Server.HandleRequestM: handleRequestAI :: (MonadAtomic m) => ReqAI -> m (Maybe RequestAnyAbility)
+ Game.LambdaHack.Server.HandleRequestM: handleRequestAI :: MonadServerAtomic m => ReqAI -> m (Maybe RequestAnyAbility)
- Game.LambdaHack.Server.HandleRequestM: handleRequestTimed :: (MonadAtomic m, MonadServer m) => FactionId -> ActorId -> RequestTimed a -> m Bool
+ Game.LambdaHack.Server.HandleRequestM: handleRequestTimed :: MonadServerAtomic m => FactionId -> ActorId -> RequestTimed a -> m Bool
- Game.LambdaHack.Server.HandleRequestM: handleRequestTimedCases :: (MonadAtomic m, MonadServer m) => ActorId -> RequestTimed a -> m ()
+ Game.LambdaHack.Server.HandleRequestM: handleRequestTimedCases :: MonadServerAtomic m => ActorId -> RequestTimed a -> m ()
- Game.LambdaHack.Server.HandleRequestM: handleRequestUI :: (MonadAtomic m, MonadServer m) => FactionId -> ActorId -> ReqUI -> m (Maybe RequestAnyAbility)
+ Game.LambdaHack.Server.HandleRequestM: handleRequestUI :: MonadServerAtomic m => FactionId -> ActorId -> ReqUI -> m (Maybe RequestAnyAbility)
- Game.LambdaHack.Server.HandleRequestM: reqAlter :: (MonadAtomic m, MonadServer m) => ActorId -> Point -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqAlter :: MonadServerAtomic m => ActorId -> Point -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqApply :: (MonadAtomic m, MonadServer m) => ActorId -> ItemId -> CStore -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqApply :: MonadServerAtomic m => ActorId -> ItemId -> CStore -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqAutomate :: MonadAtomic m => FactionId -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqAutomate :: MonadServerAtomic m => FactionId -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqDisplace :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqDisplace :: MonadServerAtomic m => ActorId -> ActorId -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqGameExit :: (MonadAtomic m, MonadServer m) => ActorId -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqGameExit :: MonadServerAtomic m => ActorId -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqGameRestart :: (MonadAtomic m, MonadServer m) => ActorId -> GroupName ModeKind -> Challenge -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqGameRestart :: MonadServerAtomic m => ActorId -> GroupName ModeKind -> Challenge -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqMelee :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> ItemId -> CStore -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqMelee :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> CStore -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqMove :: (MonadAtomic m, MonadServer m) => ActorId -> Vector -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqMove :: MonadServerAtomic m => ActorId -> Vector -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqMoveItem :: (MonadAtomic m, MonadServer m) => ActorId -> Bool -> (ItemId, Int, CStore, CStore) -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqMoveItem :: MonadServerAtomic m => ActorId -> Bool -> (ItemId, Int, CStore, CStore) -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqMoveItems :: (MonadAtomic m, MonadServer m) => ActorId -> [(ItemId, Int, CStore, CStore)] -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqMoveItems :: MonadServerAtomic m => ActorId -> [(ItemId, Int, CStore, CStore)] -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqProject :: (MonadAtomic m, MonadServer m) => ActorId -> Point -> Int -> ItemId -> CStore -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqProject :: MonadServerAtomic m => ActorId -> Point -> Int -> ItemId -> CStore -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqTactic :: MonadAtomic m => FactionId -> Tactic -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqTactic :: MonadServerAtomic m => FactionId -> Tactic -> m ()
- Game.LambdaHack.Server.HandleRequestM: reqWait :: MonadAtomic m => ActorId -> m ()
+ Game.LambdaHack.Server.HandleRequestM: reqWait :: MonadServerAtomic m => ActorId -> m ()
- Game.LambdaHack.Server.HandleRequestM: setBWait :: (MonadAtomic m) => RequestTimed a -> ActorId -> m (Maybe Bool)
+ Game.LambdaHack.Server.HandleRequestM: setBWait :: MonadServerAtomic m => RequestTimed a -> ActorId -> Actor -> m (Maybe Bool)
- Game.LambdaHack.Server.HandleRequestM: switchLeader :: (MonadAtomic m, MonadServer m) => FactionId -> ActorId -> m ()
+ Game.LambdaHack.Server.HandleRequestM: switchLeader :: MonadServerAtomic m => FactionId -> ActorId -> m ()
- Game.LambdaHack.Server.ItemM: embedItemsInDungeon :: (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Server.ItemM: embedItemsInDungeon :: MonadServerAtomic m => m ()
- Game.LambdaHack.Server.ItemM: placeItemsInDungeon :: forall m. (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Server.ItemM: placeItemsInDungeon :: forall m. MonadServerAtomic m => m ()
- Game.LambdaHack.Server.ItemM: registerItem :: (MonadAtomic m, MonadServer m) => ItemFull -> ItemKnown -> ItemSeed -> Container -> Bool -> m ItemId
+ Game.LambdaHack.Server.ItemM: registerItem :: MonadServerAtomic m => ItemFull -> ItemKnown -> ItemSeed -> Container -> Bool -> m ItemId
- Game.LambdaHack.Server.ItemM: rollAndRegisterItem :: (MonadAtomic m, MonadServer m) => LevelId -> Freqs ItemKind -> Container -> Bool -> Maybe Int -> m (Maybe (ItemId, (ItemFull, GroupName ItemKind)))
+ Game.LambdaHack.Server.ItemM: rollAndRegisterItem :: MonadServerAtomic m => LevelId -> Freqs ItemKind -> Container -> Bool -> Maybe Int -> m (Maybe (ItemId, (ItemFull, GroupName ItemKind)))
- Game.LambdaHack.Server.ItemM: rollItem :: (MonadAtomic m, MonadServer m) => Int -> LevelId -> Freqs ItemKind -> m (Maybe (ItemKnown, ItemFull, ItemDisco, ItemSeed, GroupName ItemKind))
+ Game.LambdaHack.Server.ItemM: rollItem :: MonadServerAtomic m => Int -> LevelId -> Freqs ItemKind -> m (Maybe (ItemKnown, ItemFull, ItemDisco, ItemSeed, GroupName ItemKind))
- Game.LambdaHack.Server.LoopM: applyPeriodicLevel :: (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Server.LoopM: applyPeriodicLevel :: MonadServerAtomic m => m ()
- Game.LambdaHack.Server.LoopM: endClip :: forall m. (MonadAtomic m, MonadServer m) => (FactionId -> m ()) -> m ()
+ Game.LambdaHack.Server.LoopM: endClip :: forall m. MonadServerAtomic m => (FactionId -> m ()) -> m ()
- Game.LambdaHack.Server.LoopM: hActors :: forall m. (MonadAtomic m, MonadServerReadRequest m) => FactionId -> [(ActorId, Actor)] -> m Bool
+ Game.LambdaHack.Server.LoopM: hActors :: forall m. (MonadServerAtomic m, MonadServerReadRequest m) => FactionId -> [(ActorId, Actor)] -> m Bool
- Game.LambdaHack.Server.LoopM: hTrajectories :: (MonadAtomic m, MonadServer m) => (ActorId, Actor) -> m ()
+ Game.LambdaHack.Server.LoopM: hTrajectories :: MonadServerAtomic m => (ActorId, Actor) -> m ()
- Game.LambdaHack.Server.LoopM: handleActors :: (MonadAtomic m, MonadServerReadRequest m) => LevelId -> FactionId -> m Bool
+ Game.LambdaHack.Server.LoopM: handleActors :: (MonadServerAtomic m, MonadServerReadRequest m) => LevelId -> FactionId -> m Bool
- Game.LambdaHack.Server.LoopM: handleFidUpd :: (MonadAtomic m, MonadServerReadRequest m) => Bool -> (FactionId -> m ()) -> FactionId -> Faction -> m Bool
+ Game.LambdaHack.Server.LoopM: handleFidUpd :: (MonadServerAtomic m, MonadServerReadRequest m) => Bool -> (FactionId -> m ()) -> FactionId -> Faction -> m Bool
- Game.LambdaHack.Server.LoopM: handleTrajectories :: (MonadAtomic m, MonadServer m) => LevelId -> FactionId -> m ()
+ Game.LambdaHack.Server.LoopM: handleTrajectories :: MonadServerAtomic m => LevelId -> FactionId -> m ()
- Game.LambdaHack.Server.LoopM: loopSer :: (MonadAtomic m, MonadServerReadRequest m) => DebugModeSer -> Config -> (Maybe SessionUI -> FactionId -> ChanServer -> IO ()) -> m ()
+ Game.LambdaHack.Server.LoopM: loopSer :: (MonadServerAtomic m, MonadServerReadRequest m) => ServerOptions -> (Bool -> FactionId -> ChanServer -> IO ()) -> m ()
- Game.LambdaHack.Server.LoopM: loopUpd :: forall m. (MonadAtomic m, MonadServerReadRequest m) => m () -> m ()
+ Game.LambdaHack.Server.LoopM: loopUpd :: forall m. (MonadServerAtomic m, MonadServerReadRequest m) => m () -> m ()
- Game.LambdaHack.Server.LoopM: restartGame :: (MonadAtomic m, MonadServer m) => m () -> m () -> Maybe (GroupName ModeKind) -> m ()
+ Game.LambdaHack.Server.LoopM: restartGame :: MonadServerAtomic m => m () -> m () -> Maybe (GroupName ModeKind) -> m ()
- Game.LambdaHack.Server.LoopM: setTrajectory :: (MonadAtomic m, MonadServer m) => ActorId -> m ()
+ Game.LambdaHack.Server.LoopM: setTrajectory :: MonadServerAtomic m => ActorId -> m ()
- Game.LambdaHack.Server.PeriodicM: addAnyActor :: (MonadAtomic m, MonadServer m) => Freqs ItemKind -> LevelId -> Time -> Maybe Point -> m (Maybe ActorId)
+ Game.LambdaHack.Server.PeriodicM: addAnyActor :: MonadServerAtomic m => Freqs ItemKind -> LevelId -> Time -> Maybe Point -> m (Maybe ActorId)
- Game.LambdaHack.Server.PeriodicM: advanceTime :: (MonadAtomic m, MonadServer m) => ActorId -> Int -> m ()
+ Game.LambdaHack.Server.PeriodicM: advanceTime :: MonadServerAtomic m => ActorId -> Int -> m ()
- Game.LambdaHack.Server.PeriodicM: leadLevelSwitch :: (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Server.PeriodicM: leadLevelSwitch :: MonadServerAtomic m => m ()
- Game.LambdaHack.Server.PeriodicM: overheadActorTime :: (MonadAtomic m, MonadServer m) => FactionId -> m ()
+ Game.LambdaHack.Server.PeriodicM: overheadActorTime :: MonadServerAtomic m => FactionId -> LevelId -> m ()
- Game.LambdaHack.Server.PeriodicM: spawnMonster :: (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Server.PeriodicM: spawnMonster :: MonadServerAtomic m => m ()
- Game.LambdaHack.Server.PeriodicM: swapTime :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> m ()
+ Game.LambdaHack.Server.PeriodicM: swapTime :: MonadServerAtomic m => ActorId -> ActorId -> m ()
- Game.LambdaHack.Server.PeriodicM: udpateCalm :: (MonadAtomic m, MonadServer m) => ActorId -> Int64 -> m ()
+ Game.LambdaHack.Server.PeriodicM: udpateCalm :: MonadServerAtomic m => ActorId -> Int64 -> m ()
- Game.LambdaHack.Server.ProtocolM: killAllClients :: (MonadAtomic m, MonadServerReadRequest m) => m ()
+ Game.LambdaHack.Server.ProtocolM: killAllClients :: (MonadServerAtomic m, MonadServerReadRequest m) => m ()
- Game.LambdaHack.Server.ProtocolM: sendQueryUI :: (MonadAtomic m, MonadServerReadRequest m) => FactionId -> ActorId -> m RequestUI
+ Game.LambdaHack.Server.ProtocolM: sendQueryUI :: (MonadServerAtomic m, MonadServerReadRequest m) => FactionId -> ActorId -> m RequestUI
- Game.LambdaHack.Server.ProtocolM: sendUpdate :: MonadServerReadRequest m => FactionId -> UpdAtomic -> m ()
+ Game.LambdaHack.Server.ProtocolM: sendUpdate :: (MonadServerAtomic m, MonadServerReadRequest m) => FactionId -> UpdAtomic -> m ()
- Game.LambdaHack.Server.ProtocolM: tryRestore :: MonadServerReadRequest m => COps -> DebugModeSer -> m (Maybe (State, StateServer))
+ Game.LambdaHack.Server.ProtocolM: tryRestore :: MonadServerReadRequest m => m (Maybe (State, StateServer))
- Game.LambdaHack.Server.ProtocolM: updateConn :: (MonadAtomic m, MonadServerReadRequest m) => Config -> (Maybe SessionUI -> FactionId -> ChanServer -> IO ()) -> m ()
+ Game.LambdaHack.Server.ProtocolM: updateConn :: (MonadServerAtomic m, MonadServerReadRequest m) => (Bool -> FactionId -> ChanServer -> IO ()) -> m ()
- Game.LambdaHack.Server.StartM: gameReset :: MonadServer m => COps -> DebugModeSer -> Maybe (GroupName ModeKind) -> Maybe StdGen -> m State
+ Game.LambdaHack.Server.StartM: gameReset :: MonadServer m => ServerOptions -> Maybe (GroupName ModeKind) -> Maybe StdGen -> m State
- Game.LambdaHack.Server.StartM: reinitGame :: (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Server.StartM: reinitGame :: MonadServerAtomic m => m ()
- Game.LambdaHack.Server.State: StateServer :: ActorTime -> DiscoveryKind -> DiscoveryKindRev -> UniqueSet -> DiscoveryAspect -> ItemSeedDict -> ItemRev -> FlavourMap -> ActorId -> ItemId -> EnumMap LevelId Int -> [CmdAtomic] -> PerFid -> PerValidFid -> PerCacheFid -> ActorAspect -> FovLucidLid -> FovClearLid -> FovLitLid -> [LevelId] -> Bool -> StdGen -> RNGs -> Bool -> Bool -> DebugModeSer -> DebugModeSer -> StateServer
+ Game.LambdaHack.Server.State: StateServer :: ActorTime -> DiscoveryKindRev -> UniqueSet -> ItemSeedDict -> ItemRev -> FlavourMap -> ActorId -> ItemId -> EnumMap LevelId Int -> [CmdAtomic] -> EnumMap FactionId State -> PerFid -> PerValidFid -> PerCacheFid -> FovLucidLid -> FovClearLid -> FovLitLid -> [LevelId] -> Bool -> StdGen -> RNGs -> Bool -> Bool -> ServerOptions -> ServerOptions -> StateServer
Files
- CHANGELOG.md +19/−0
- CREDITS +1/−0
- Game/LambdaHack/Atomic.hs +7/−11
- Game/LambdaHack/Atomic/CmdAtomic.hs +40/−26
- Game/LambdaHack/Atomic/HandleAtomicWrite.hs +299/−138
- Game/LambdaHack/Atomic/MonadAtomic.hs +0/−20
- Game/LambdaHack/Atomic/MonadStateWrite.hs +125/−43
- Game/LambdaHack/Atomic/PosAtomicRead.hs +47/−54
- Game/LambdaHack/Client.hs +16/−3
- Game/LambdaHack/Client/AI.hs +26/−60
- Game/LambdaHack/Client/AI/ConditionM.hs +35/−36
- Game/LambdaHack/Client/AI/HandleAbilityM.hs +88/−73
- Game/LambdaHack/Client/AI/PickActorM.hs +24/−15
- Game/LambdaHack/Client/AI/PickTargetM.hs +54/−48
- Game/LambdaHack/Client/AI/Strategy.hs +13/−6
- Game/LambdaHack/Client/Bfs.hs +7/−6
- Game/LambdaHack/Client/BfsM.hs +34/−38
- Game/LambdaHack/Client/ClientOptions.hs +84/−0
- Game/LambdaHack/Client/CommonM.hs +32/−97
- Game/LambdaHack/Client/HandleAtomicM.hs +127/−348
- Game/LambdaHack/Client/HandleResponseM.hs +36/−17
- Game/LambdaHack/Client/LoopM.hs +57/−32
- Game/LambdaHack/Client/MonadClient.hs +8/−13
- Game/LambdaHack/Client/Preferences.hs +67/−48
- Game/LambdaHack/Client/Request.hs +67/−0
- Game/LambdaHack/Client/Response.hs +33/−0
- Game/LambdaHack/Client/State.hs +51/−66
- Game/LambdaHack/Client/UI.hs +48/−50
- Game/LambdaHack/Client/UI/ActorUI.hs +7/−8
- Game/LambdaHack/Client/UI/Animation.hs +13/−7
- Game/LambdaHack/Client/UI/Config.hs +0/−145
- Game/LambdaHack/Client/UI/Content/KeyKind.hs +13/−10
- Game/LambdaHack/Client/UI/DisplayAtomicM.hs +216/−184
- Game/LambdaHack/Client/UI/DrawM.hs +50/−57
- Game/LambdaHack/Client/UI/EffectDescription.hs +19/−14
- Game/LambdaHack/Client/UI/Frame.hs +44/−16
- Game/LambdaHack/Client/UI/FrameM.hs +46/−26
- Game/LambdaHack/Client/UI/Frontend.hs +48/−46
- Game/LambdaHack/Client/UI/Frontend/Chosen.hs +1/−1
- Game/LambdaHack/Client/UI/Frontend/Common.hs +8/−8
- Game/LambdaHack/Client/UI/Frontend/Curses.hs +10/−10
- Game/LambdaHack/Client/UI/Frontend/Dom.hs +19/−19
- Game/LambdaHack/Client/UI/Frontend/Gtk.hs +20/−24
- Game/LambdaHack/Client/UI/Frontend/Sdl.hs +27/−26
- Game/LambdaHack/Client/UI/Frontend/Teletype.hs +11/−15
- Game/LambdaHack/Client/UI/Frontend/Vty.hs +3/−3
- Game/LambdaHack/Client/UI/HandleHelperM.hs +77/−36
- Game/LambdaHack/Client/UI/HandleHumanGlobalM.hs +84/−84
- Game/LambdaHack/Client/UI/HandleHumanLocalM.hs +81/−88
- Game/LambdaHack/Client/UI/HandleHumanM.hs +39/−13
- Game/LambdaHack/Client/UI/HumanCmd.hs +15/−26
- Game/LambdaHack/Client/UI/InventoryM.hs +31/−36
- Game/LambdaHack/Client/UI/ItemDescription.hs +116/−13
- Game/LambdaHack/Client/UI/ItemSlot.hs +16/−13
- Game/LambdaHack/Client/UI/Key.hs +36/−22
- Game/LambdaHack/Client/UI/KeyBindings.hs +20/−20
- Game/LambdaHack/Client/UI/MonadClientUI.hs +66/−59
- Game/LambdaHack/Client/UI/Msg.hs +14/−8
- Game/LambdaHack/Client/UI/MsgM.hs +26/−8
- Game/LambdaHack/Client/UI/Overlay.hs +29/−149
- Game/LambdaHack/Client/UI/OverlayM.hs +0/−85
- Game/LambdaHack/Client/UI/RunM.hs +20/−16
- Game/LambdaHack/Client/UI/SessionUI.hs +37/−34
- Game/LambdaHack/Client/UI/Slideshow.hs +15/−7
- Game/LambdaHack/Client/UI/SlideshowM.hs +18/−13
- Game/LambdaHack/Client/UI/UIOptions.hs +149/−0
- Game/LambdaHack/Common/Ability.hs +14/−14
- Game/LambdaHack/Common/Actor.hs +58/−61
- Game/LambdaHack/Common/ActorState.hs +60/−42
- Game/LambdaHack/Common/ClientOptions.hs +0/−82
- Game/LambdaHack/Common/Color.hs +73/−70
- Game/LambdaHack/Common/ContentDef.hs +6/−5
- Game/LambdaHack/Common/Dice.hs +164/−165
- Game/LambdaHack/Common/Faction.hs +28/−22
- Game/LambdaHack/Common/File.hs +1/−1
- Game/LambdaHack/Common/Flavour.hs +6/−2
- Game/LambdaHack/Common/Frequency.hs +36/−14
- Game/LambdaHack/Common/HSFile.hs +10/−6
- Game/LambdaHack/Common/HighScore.hs +50/−49
- Game/LambdaHack/Common/Item.hs +215/−193
- Game/LambdaHack/Common/ItemStrongest.hs +71/−67
- Game/LambdaHack/Common/JSFile.hs +7/−8
- Game/LambdaHack/Common/Kind.hs +27/−23
- Game/LambdaHack/Common/Level.hs +41/−17
- Game/LambdaHack/Common/Misc.hs +102/−99
- Game/LambdaHack/Common/MonadStateRead.hs +20/−16
- Game/LambdaHack/Common/Perception.hs +4/−7
- Game/LambdaHack/Common/Point.hs +4/−0
- Game/LambdaHack/Common/PointArray.hs +28/−25
- Game/LambdaHack/Common/Prelude.hs +2/−2
- Game/LambdaHack/Common/Random.hs +8/−12
- Game/LambdaHack/Common/ReqFailure.hs +211/−0
- Game/LambdaHack/Common/Request.hs +0/−259
- Game/LambdaHack/Common/Response.hs +0/−33
- Game/LambdaHack/Common/RingBuffer.hs +3/−3
- Game/LambdaHack/Common/Save.hs +14/−14
- Game/LambdaHack/Common/State.hs +159/−75
- Game/LambdaHack/Common/Tile.hs +111/−105
- Game/LambdaHack/Common/Time.hs +35/−31
- Game/LambdaHack/Common/Vector.hs +23/−18
- Game/LambdaHack/Content/CaveKind.hs +36/−34
- Game/LambdaHack/Content/ItemKind.hs +125/−84
- Game/LambdaHack/Content/ModeKind.hs +33/−28
- Game/LambdaHack/Content/RuleKind.hs +3/−9
- Game/LambdaHack/Content/TileKind.hs +41/−41
- Game/LambdaHack/SampleImplementation/SampleMonadClient.hs +37/−40
- Game/LambdaHack/SampleImplementation/SampleMonadServer.hs +82/−43
- Game/LambdaHack/Server.hs +10/−6
- Game/LambdaHack/Server/BroadcastAtomic.hs +140/−79
- Game/LambdaHack/Server/Commandline.hs +255/−157
- Game/LambdaHack/Server/CommonM.hs +147/−107
- Game/LambdaHack/Server/DebugM.hs +40/−30
- Game/LambdaHack/Server/DungeonGen.hs +26/−25
- Game/LambdaHack/Server/DungeonGen/Area.hs +19/−4
- Game/LambdaHack/Server/DungeonGen/AreaRnd.hs +5/−17
- Game/LambdaHack/Server/DungeonGen/Cave.hs +33/−30
- Game/LambdaHack/Server/DungeonGen/Place.hs +16/−12
- Game/LambdaHack/Server/EndM.hs +71/−11
- Game/LambdaHack/Server/Fov.hs +51/−81
- Game/LambdaHack/Server/FovDigital.hs +1/−1
- Game/LambdaHack/Server/HandleAtomicM.hs +89/−88
- Game/LambdaHack/Server/HandleEffectM.hs +203/−180
- Game/LambdaHack/Server/HandleRequestM.hs +180/−128
- Game/LambdaHack/Server/ItemM.hs +37/−49
- Game/LambdaHack/Server/ItemRev.hs +57/−55
- Game/LambdaHack/Server/LoopM.hs +86/−138
- Game/LambdaHack/Server/MonadServer.hs +56/−33
- Game/LambdaHack/Server/PeriodicM.hs +52/−48
- Game/LambdaHack/Server/ProtocolM.hs +95/−65
- Game/LambdaHack/Server/ServerOptions.hs +126/−0
- Game/LambdaHack/Server/StartM.hs +70/−87
- Game/LambdaHack/Server/State.hs +52/−171
- GameDefinition/Client/UI/Content/KeyKind.hs +216/−218
- GameDefinition/Content/CaveKind.hs +75/−55
- GameDefinition/Content/ItemKind.hs +138/−127
- GameDefinition/Content/ItemKindActor.hs +5/−4
- GameDefinition/Content/ItemKindBlast.hs +17/−17
- GameDefinition/Content/ItemKindEmbed.hs +9/−9
- GameDefinition/Content/ItemKindOrgan.hs +52/−52
- GameDefinition/Content/ItemKindTemporary.hs +4/−4
- GameDefinition/Content/ModeKind.hs +24/−24
- GameDefinition/Content/PlaceKind.hs +2/−3
- GameDefinition/Content/TileKind.hs +11/−7
- GameDefinition/Main.hs +18/−8
- GameDefinition/PLAYING.md +2/−1
- GameDefinition/TieKnot.hs +22/−40
- LambdaHack.cabal +32/−36
- Makefile +24/−21
- README.md +22/−12
- test/test.hs +7/−2
CHANGELOG.md view
@@ -1,3 +1,22 @@+## [v0.7.0.0, aka 'The dice are cast'](https://github.com/LambdaHack/LambdaHack/compare/v0.6.2.0...v0.7.0.0)++- decouple tile searching from tile alteration+- refrain from identifying items that are not randomized+- switch away from incapacitated leader to let others revive him+- make rescue easier by not going into negative HP the first time+- fix crowd of friends on another level slowing even actors that melee+- fix missing report about items underneath an actor when changing levels+- API breakage: change the syntax of dice in content+- API addition: introduce cave descriptions+- keep all client states in the server and optimize communication with clients+- improve item choice for identification and item polymorphing+- reset embedded items when altering tile+- replace atomic command filtering with exception catching+- reimplement dice as symbolic expressions inducing multiple RNG calls+- switch to optparse-applicative and rewrite cli handling+- add stack and cabal new-build project files+- improve haddocks across the codebase+ ## [v0.6.2.0, aka 'Zoom out'](https://github.com/LambdaHack/LambdaHack/compare/v0.6.1.0...v0.6.2.0) - make fireworks slower and so easier to spot
CREDITS view
@@ -4,6 +4,7 @@ Andres Loeh Mikolaj Konarski Tuukka Turto+Veronika Romashkina Fonts 16x16x.fon, 8x8x.fon and 8x8xb.fon are are taken from
Game/LambdaHack/Atomic.hs view
@@ -1,26 +1,22 @@--- | Atomic game state transformations.+-- | Atomic game state transformations, their representation and semantics. -- -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>. module Game.LambdaHack.Atomic- ( -- * Re-exported from "Game.LambdaHack.Atomic.MonadAtomic"- MonadAtomic(..)- -- * Re-exported from "Game.LambdaHack.Atomic.CmdAtomic"- , CmdAtomic(..), UpdAtomic(..), SfxAtomic(..), SfxMsg(..)+ ( -- * Re-exported from "Game.LambdaHack.Atomic.CmdAtomic"+ CmdAtomic(..), UpdAtomic(..), SfxAtomic(..), SfxMsg(..)+ -- * Re-exported from "Game.LambdaHack.Atomic.HandleAtomicWrite"+ , handleUpdAtomic -- * Re-exported from "Game.LambdaHack.Atomic.PosAtomicRead" , PosAtomic(..), posUpdAtomic, posSfxAtomic, breakUpdAtomic- , seenAtomicCli, seenAtomicSer, generalMoveItem- , posProjBody+ , seenAtomicCli, seenAtomicSer -- * Re-exported from "Game.LambdaHack.Atomic.MonadStateWrite"- , MonadStateWrite(..)- -- * Re-exported from "Game.LambdaHack.Atomic.HandleAtomicWrite"- , handleUpdAtomic+ , MonadStateWrite(..), AtomicFail(..), putState ) where import Prelude () import Game.LambdaHack.Atomic.CmdAtomic import Game.LambdaHack.Atomic.HandleAtomicWrite-import Game.LambdaHack.Atomic.MonadAtomic import Game.LambdaHack.Atomic.MonadStateWrite import Game.LambdaHack.Atomic.PosAtomicRead
Game/LambdaHack/Atomic/CmdAtomic.hs view
@@ -8,7 +8,7 @@ -- For example item removal from inventory is not an atomic command, -- but item dropped from the inventory to the ground is. This makes -- it easier to undo the commands. In principle, the commands are the only--- way to affect the basic game state (@State@).+-- way to affect the basic game state ('State'). -- -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>.@@ -25,23 +25,27 @@ import Data.Int (Int64) import GHC.Generics (Generic) -import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ClientOptions+-- Dependence on ClientOptions is an anomaly. Instead, probably the raw+-- remaining commandline should be passed and parsed by the client to extract+-- client and ui options from and singnal an error if anything was left.++import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Common.Actor import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Content.TileKind (TileKind) -- | Abstract syntax of atomic commands, that is, atomic game state -- transformations.@@ -53,9 +57,9 @@ instance Binary CmdAtomic -- | Abstract syntax of atomic updates, that is, atomic commands--- that really change the state. Most of them are an encoding of a game+-- that really change the 'State'. Most of them are an encoding of a game -- state diff, though they also carry some intentional hints--- that help clients determine whether and how to communicate them to players.+-- that help clients determine whether and how to communicate it to players. data UpdAtomic = -- Create/destroy actors and items. UpdCreateActor ActorId Actor [(ItemId, Item)]@@ -66,6 +70,8 @@ | UpdLoseActor ActorId Actor [(ItemId, Item)] | UpdSpotItem Bool ItemId Item ItemQuant Container | UpdLoseItem Bool ItemId Item ItemQuant Container+ | UpdSpotItemBag Container ItemBag [(ItemId, Item)]+ | UpdLoseItemBag Container ItemBag [(ItemId, Item)] -- Move actors and items. | UpdMoveActor ActorId Point Point | UpdWaitActor ActorId Bool@@ -98,24 +104,25 @@ | UpdUnAgeGame [LevelId] | UpdDiscover Container ItemId (Kind.Id ItemKind) ItemSeed | UpdCover Container ItemId (Kind.Id ItemKind) ItemSeed- | UpdDiscoverKind Container ItemId (Kind.Id ItemKind)- | UpdCoverKind Container ItemId (Kind.Id ItemKind)+ | UpdDiscoverKind Container ItemKindIx (Kind.Id ItemKind)+ | UpdCoverKind Container ItemKindIx (Kind.Id ItemKind) | UpdDiscoverSeed Container ItemId ItemSeed | UpdCoverSeed Container ItemId ItemSeed+ | UpdDiscoverServer ItemId AspectRecord+ | UpdCoverServer ItemId AspectRecord | UpdPerception LevelId Perception Perception- | UpdRestart FactionId DiscoveryKind PerLid State Challenge DebugModeCli+ | UpdRestart FactionId PerLid State Challenge ClientOptions | UpdRestartServer State | UpdResume FactionId PerLid | UpdResumeServer State | UpdKillExit FactionId | UpdWriteSave- | UpdMsgAll Text deriving (Show, Eq, Generic) instance Binary UpdAtomic -- | Abstract syntax of atomic special effects, that is, atomic commands--- that only display special effects and don't change the state.+-- that only display special effects and don't change 'State'. data SfxAtomic = SfxStrike ActorId ActorId ItemId CStore | SfxRecoil ActorId ActorId ItemId CStore@@ -129,10 +136,13 @@ | SfxShun ActorId Point | SfxEffect FactionId ActorId IK.Effect Int64 | SfxMsgFid FactionId SfxMsg+ | SfxSortSlots deriving (Show, Eq, Generic) instance Binary SfxAtomic +-- | Symbolic representation of text messages sent by server to clients+-- and shown to players. data SfxMsg = SfxUnexpected ReqFailure | SfxLoudUpd Bool UpdAtomic@@ -147,7 +157,7 @@ | SfxBracedImmune ActorId | SfxEscapeImpossible | SfxTransImpossible- | SfxIdentifyNothing CStore+ | SfxIdentifyNothing | SfxPurposeNothing CStore | SfxPurposeTooFew Int Int | SfxPurposeUnique@@ -167,6 +177,8 @@ UpdLoseActor aid body ais -> Just $ UpdSpotActor aid body ais UpdSpotItem verbose iid item k c -> Just $ UpdLoseItem verbose iid item k c UpdLoseItem verbose iid item k c -> Just $ UpdSpotItem verbose iid item k c+ UpdSpotItemBag c bag ais -> Just $ UpdLoseItemBag c bag ais+ UpdLoseItemBag c bag ais -> Just $ UpdSpotItemBag c bag ais UpdMoveActor aid fromP toP -> Just $ UpdMoveActor aid toP fromP UpdWaitActor aid toWait -> Just $ UpdWaitActor aid (not toWait) UpdDisplaceActor source target -> Just $ UpdDisplaceActor target source@@ -196,10 +208,12 @@ UpdUnAgeGame lids -> Just $ UpdAgeGame lids UpdDiscover c iid ik seed -> Just $ UpdCover c iid ik seed UpdCover c iid ik seed -> Just $ UpdDiscover c iid ik seed- UpdDiscoverKind c iid ik -> Just $ UpdCoverKind c iid ik- UpdCoverKind c iid ik -> Just $ UpdDiscoverKind c iid ik+ UpdDiscoverKind c ix ik -> Just $ UpdCoverKind c ix ik+ UpdCoverKind c ix ik -> Just $ UpdDiscoverKind c ix ik UpdDiscoverSeed c iid seed -> Just $ UpdCoverSeed c iid seed UpdCoverSeed c iid seed -> Just $ UpdDiscoverSeed c iid seed+ UpdDiscoverServer iid aspectRecord -> Just $ UpdCoverServer iid aspectRecord+ UpdCoverServer iid aspectRecord -> Just $ UpdDiscoverServer iid aspectRecord UpdPerception lid outPer inPer -> Just $ UpdPerception lid inPer outPer UpdRestart{} -> Just cmd -- here history ends; change direction UpdRestartServer{} -> Just cmd -- here history ends; change direction@@ -207,7 +221,6 @@ UpdResumeServer{} -> Nothing UpdKillExit{} -> Nothing UpdWriteSave -> Nothing- UpdMsgAll{} -> Nothing -- only generated by @cmdAtomicFilterCli@ or as a hack undoSfxAtomic :: SfxAtomic -> SfxAtomic undoSfxAtomic cmd = case cmd of@@ -223,6 +236,7 @@ SfxShun aid p -> SfxTrigger aid p SfxEffect{} -> cmd -- not ideal? SfxMsgFid{} -> cmd+ SfxSortSlots -> cmd undoCmdAtomic :: CmdAtomic -> Maybe CmdAtomic undoCmdAtomic (UpdAtomic cmd) = UpdAtomic <$> undoUpdAtomic cmd
Game/LambdaHack/Atomic/HandleAtomicWrite.hs view
@@ -1,4 +1,5 @@ -- | Semantics of atomic commands shared by client and server.+-- -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>. module Game.LambdaHack.Atomic.HandleAtomicWrite@@ -6,13 +7,18 @@ #ifdef EXPOSE_INTERNAL -- * Internal operations , updCreateActor, updDestroyActor, updCreateItem, updDestroyItem+ , updSpotItemBag, updLoseItemBag , updMoveActor, updWaitActor, updDisplaceActor, updMoveItem , updRefillHP, updRefillCalm , updTrajectory, updQuitFaction, updLeadFaction , updDiplFaction, updTacticFaction, updAutoFaction, updRecordKill- , updAlterTile, updAlterExplorable, updSpotTile, updLoseTile+ , updAlterTile, updAlterExplorable, updSearchTile, updSpotTile, updLoseTile , updAlterSmell, updSpotSmell, updLoseSmell, updTimeItem- , updAgeGame, updUnAgeGame, updRestart, updRestartServer, updResumeServer+ , updAgeGame, updUnAgeGame, ageLevel, updDiscover, updCover+ , updDiscoverKind, discoverKind, updCoverKind+ , updDiscoverSeed, discoverSeed, updCoverSeed+ , updDiscoverServer, updCoverServer+ , updRestart, updRestartServer, updResumeServer #endif ) where @@ -21,31 +27,44 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import Data.Int (Int64)+import Data.Int (Int64) -import Game.LambdaHack.Atomic.CmdAtomic-import Game.LambdaHack.Atomic.MonadStateWrite-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Atomic.CmdAtomic+import Game.LambdaHack.Atomic.MonadStateWrite+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind (ItemKind)-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind (TileKind, unknownId)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.TileKind (TileKind, unknownId) -- | The game-state semantics of atomic game commands.--- Special effects (@SfxAtomic@) don't modify state.+-- There is no corresponding definition for special effects (`SfxAtomic`),+-- because they don't modify 'State'.+--+-- For each of the commands, we are guaranteed that the client,+-- the command is addressed to, perceives all the positions the command+-- affects (as computed by 'Game.LambdaHack.Atomic.PosAtomicRead.posUpdAtomic').+-- In the code for each semantic function we additonally verify+-- the client is aware of any relevant items and/or actors and we throw+-- the @AtomicFail@ exception if it's not.+-- The server keeps copies of all clients' states and, before sending a command+-- to a client, applies it to the client's state copy.+-- If @AtomicFail@ is signalled, the command is ignored for that client.+-- This enables simpler server code that addresses commands to all clients+-- that can see it, even though not all are able to process it. handleUpdAtomic :: MonadStateWrite m => UpdAtomic -> m () handleUpdAtomic cmd = case cmd of UpdCreateActor aid body ais -> updCreateActor aid body ais@@ -56,6 +75,8 @@ UpdLoseActor aid body ais -> updDestroyActor aid body ais UpdSpotItem _ iid item kit c -> updCreateItem iid item kit c UpdLoseItem _ iid item kit c -> updDestroyItem iid item kit c+ UpdSpotItemBag c bag ais -> updSpotItemBag c bag ais+ UpdLoseItemBag c bag ais -> updLoseItemBag c bag ais UpdMoveActor aid fromP toP -> updMoveActor aid fromP toP UpdWaitActor aid toWait -> updWaitActor aid toWait UpdDisplaceActor source target -> updDisplaceActor source target@@ -72,8 +93,8 @@ UpdRecordKill aid ikind k -> updRecordKill aid ikind k UpdAlterTile lid p fromTile toTile -> updAlterTile lid p fromTile toTile UpdAlterExplorable lid delta -> updAlterExplorable lid delta- UpdSearchTile{} -> return () -- only for clients- UpdHideTile{} -> return () -- only for clients+ UpdSearchTile aid p toTile -> updSearchTile aid p toTile+ UpdHideTile{} -> undefined UpdSpotTile lid ts -> updSpotTile lid ts UpdLoseTile lid ts -> updLoseTile lid ts UpdAlterSmell lid p fromSm toSm -> updAlterSmell lid p fromSm toSm@@ -82,69 +103,59 @@ UpdTimeItem iid c fromIt toIt -> updTimeItem iid c fromIt toIt UpdAgeGame lids -> updAgeGame lids UpdUnAgeGame lids -> updUnAgeGame lids- UpdDiscover{} -> return () -- We can't keep dicovered data in State,- UpdCover{} -> return () -- because server saves all atomic commands- UpdDiscoverKind{} -> return () -- to apply their inverses for undo,- UpdCoverKind{} -> return () -- so they would wipe out server knowledge.- UpdDiscoverSeed{} -> return ()- UpdCoverSeed{} -> return ()+ UpdDiscover c iid ik seed -> updDiscover c iid ik seed+ UpdCover c iid ik seed -> updCover c iid ik seed+ UpdDiscoverKind c ix ik -> updDiscoverKind c ix ik+ UpdCoverKind c ix ik -> updCoverKind c ix ik+ UpdDiscoverSeed c iid seed -> updDiscoverSeed c iid seed+ UpdCoverSeed c iid seed -> updCoverSeed c iid seed+ UpdDiscoverServer iid aspectRecord -> updDiscoverServer iid aspectRecord+ UpdCoverServer iid aspectRecord -> updCoverServer iid aspectRecord UpdPerception _ outPer inPer -> assert (not (nullPer outPer && nullPer inPer)) (return ())- UpdRestart _ _ _ s _ _ -> updRestart s+ UpdRestart _ _ s _ _ -> updRestart s UpdRestartServer s -> updRestartServer s UpdResume{} -> return () UpdResumeServer s -> updResumeServer s UpdKillExit{} -> return () UpdWriteSave -> return ()- UpdMsgAll{} -> return () --- | Creates an actor. Note: after this command, usually a new leader+-- Note: after this command, usually a new leader -- for the party should be elected (in case this actor is the only one alive). updCreateActor :: MonadStateWrite m => ActorId -> Actor -> [(ItemId, Item)] -> m () updCreateActor aid body ais = do -- Add actor to @sactorD@.+ -- The exception is possible, e.g., when we teleport and so see our actor+ -- at the new location, but also the location is part of new perception,+ -- so @UpdSpotActor@ is sent. let f Nothing = Just body- f (Just b) = error $ "actor already added"- `showFailure` (aid, body, b)+ f (Just b) = assert (body == b `blame` (aid, body, b)) $+ atomicFail $ "actor already added" `showFailure` (aid, body, b) modifyState $ updateActorD $ EM.alter f aid -- Add actor to @sprio@. let g Nothing = Just [aid] g (Just l) = #ifdef WITH_EXPENSIVE_ASSERTIONS+ -- Not so much expensive, as doubly impossible. assert (aid `notElem` l `blame` "actor already added" `swith` (aid, body, l)) #endif (Just $ aid : l) updateLevel (blid body) $ updateActorMap (EM.alter g (bpos body))- -- Actor's items may or may not be already present in @sitemD@,- -- regardless if they are already present otherwise in the dungeon.- -- We re-add them all to save time determining which really need it.- forM_ ais $ \(iid, item) -> do- let h item1 item2 =- assert (itemsMatch item1 item2- `blame` "inconsistent created actor items"- `swith` (aid, body, iid, item1, item2))- item2 -- keep the first found level- modifyState $ updateItemD $ EM.insertWith h iid item--itemsMatch :: Item -> Item -> Bool-itemsMatch item1 item2 =- jkindIx item1 == jkindIx item2- -- && aspects and effects are the same, but too much writing;- -- Note that nothing else needs to be the same, since items are merged- -- and clients have different views on dungeon items than the server.+ addAis ais+ aspectRecord <- getsState $ aspectRecordFromActor body+ modifyState $ updateActorAspect $ EM.insert aid aspectRecord --- | Kills an actor.+-- If a leader dies, a new leader should be elected on the server+-- before this command is executed (not checked). updDestroyActor :: MonadStateWrite m => ActorId -> Actor -> [(ItemId, Item)] -> m () updDestroyActor aid body ais = do- -- If a leader dies, a new leader should be elected on the server- -- before this command is executed (not checked).- itemD <- getsState sitemD- let match (iid, item) = itemsMatch (itemD EM.! iid) item -- Assert that actor's items belong to @sitemD@. Do not remove those -- that do not appear anywhere else, for simplicity and speed.+ itemD <- getsState sitemD+ let match (iid, item) = itemsMatch (itemD EM.! iid) item let !_A = assert (allB match ais `blame` "destroyed actor items not found" `swith` (aid, body, ais, itemD)) () -- Remove actor from @sactorD@.@@ -156,31 +167,32 @@ let g Nothing = error $ "actor already removed" `showFailure` (aid, body) g (Just l) = #ifdef WITH_EXPENSIVE_ASSERTIONS+ -- Not so much expensive, as doubly impossible. assert (aid `elem` l `blame` "actor already removed" `swith` (aid, body, l)) #endif (let l2 = delete aid l in if null l2 then Nothing else Just l2) updateLevel (blid body) $ updateActorMap (EM.alter g (bpos body))+ modifyState $ updateActorAspect $ EM.delete aid --- | Create a few copies of an item that is already registered for the dungeon+-- Create a few copies of an item that is already registered for the dungeon -- (in @sitemRev@ field of @StateServer@). updCreateItem :: MonadStateWrite m => ItemId -> Item -> ItemQuant -> Container -> m () updCreateItem iid item kit@(k, _) c = assert (k > 0) $ do- -- The item may or may not be already present in @sitemD@,- -- regardless if it's actually present in the dungeon.- -- If items equivalent, pick the one found on easier level.- let f item1 item2 =- assert (itemsMatch item1 item2)- item2 -- keep the first found level- modifyState $ updateItemD $ EM.insertWith f iid item+ addAis [(iid, item)] insertItemContainer iid kit c+ case c of+ CActor aid store ->+ when (store `elem` [CEqp, COrgan]) $ addItemToActor iid item k aid+ _ -> return () --- | Destroy some copies (possibly not all) of an item.+-- Destroy some copies (possibly not all) of an item. updDestroyItem :: MonadStateWrite m => ItemId -> Item -> ItemQuant -> Container -> m () updDestroyItem iid item kit@(k, _) c = assert (k > 0) $ do+ deleteItemContainer iid kit c -- Do not remove the item from @sitemD@ nor from @sitemRev@, -- It's incredibly costly and not noticeable for the player. -- However, assert the item is registered in @sitemD@.@@ -190,8 +202,43 @@ Just item0 -> itemsMatch item0 item) `blame` "item already removed" `swith` (iid, item, itemD)) ()- deleteItemContainer iid kit c+ case c of+ CActor aid store ->+ when (store `elem` [CEqp, COrgan]) $ addItemToActor iid item (-k) aid+ _ -> return () +updSpotItemBag :: MonadStateWrite m+ => Container -> ItemBag -> [(ItemId, Item)] -> m ()+updSpotItemBag c bag ais = assert (EM.size bag > 0+ && EM.size bag == length ais) $ do+ addAis ais+ insertBagContainer bag c+ case c of+ CActor aid store ->+ when (store `elem` [CEqp, COrgan]) $+ forM_ ais $ \(iid, item) ->+ addItemToActor iid item (fst $ bag EM.! iid) aid+ _ -> return ()++updLoseItemBag :: MonadStateWrite m+ => Container -> ItemBag -> [(ItemId, Item)] -> m ()+updLoseItemBag c bag ais = assert (EM.size bag > 0+ && EM.size bag == length ais) $ do+ deleteBagContainer bag c+ -- Do not remove the items from @sitemD@ nor from @sitemRev@,+ -- It's incredibly costly and not noticeable for the player.+ -- However, assert the items are registered in @sitemD@.+ itemD <- getsState sitemD+ let match (iid, item) = itemsMatch (itemD EM.! iid) item+ let !_A = assert (allB match ais `blame` "items already removed"+ `swith` (c, bag, ais, itemD)) ()+ case c of+ CActor aid store ->+ when (store `elem` [CEqp, COrgan]) $+ forM_ ais $ \(iid, item) ->+ addItemToActor iid item (- (fst $ bag EM.! iid)) aid+ _ -> return ()+ updMoveActor :: MonadStateWrite m => ActorId -> Point -> Point -> m () updMoveActor aid fromP toP = assert (fromP /= toP) $ do body <- getsState $ getActorBody aid@@ -226,30 +273,49 @@ updMoveItem :: MonadStateWrite m => ItemId -> Int -> ActorId -> CStore -> CStore -> m ()-updMoveItem iid k aid c1 c2 = assert (k > 0 && c1 /= c2) $ do+updMoveItem iid k aid s1 s2 = assert (k > 0 && s1 /= s2) $ do b <- getsState $ getActorBody aid- bag <- getsState $ getBodyStoreBag b c1+ bag <- getsState $ getBodyStoreBag b s1 case iid `EM.lookup` bag of- Nothing -> error $ "" `showFailure` (iid, k, aid, c1, c2)+ Nothing -> error $ "" `showFailure` (iid, k, aid, s1, s2) Just (_, it) -> do- deleteItemActor iid (k, take k it) aid c1- insertItemActor iid (k, take k it) aid c2+ deleteItemActor iid (k, take k it) aid s1+ insertItemActor iid (k, take k it) aid s2+ case s1 of+ CEqp -> case s2 of+ COrgan -> return ()+ _ -> do+ itemBase <- getsState $ getItemBody iid+ addItemToActor iid itemBase (-k) aid+ COrgan -> case s2 of+ CEqp -> return ()+ _ -> do+ itemBase <- getsState $ getItemBody iid+ addItemToActor iid itemBase (-k) aid+ _ ->+ when (s2 `elem` [CEqp, COrgan]) $ do+ itemBase <- getsState $ getItemBody iid+ addItemToActor iid itemBase k aid updRefillHP :: MonadStateWrite m => ActorId -> Int64 -> m ()-updRefillHP aid n =+updRefillHP aid nRaw = updateActor aid $ \b ->- b { bhp = bhp b + n- , bhpDelta = let oldD = bhpDelta b- in case compare n 0 of- EQ -> ResDelta { resCurrentTurn = (0, 0)- , resPreviousTurn = resCurrentTurn oldD }- LT -> oldD {resCurrentTurn =- ( fst (resCurrentTurn oldD) + n- , snd (resCurrentTurn oldD) )}- GT -> oldD {resCurrentTurn =- ( fst (resCurrentTurn oldD)- , snd (resCurrentTurn oldD) + n )}- }+ -- Make rescue easier by not going into negative HP the first time.+ let newRawHP = bhp b + nRaw+ newHP = if bhp b <= 0 then newRawHP else max 0 newRawHP+ n = newHP - bhp b+ in b { bhp = newHP+ , bhpDelta = let oldD = bhpDelta b+ in case compare n 0 of+ EQ -> ResDelta { resCurrentTurn = (0, 0)+ , resPreviousTurn = resCurrentTurn oldD }+ LT -> oldD {resCurrentTurn =+ ( fst (resCurrentTurn oldD) + n+ , snd (resCurrentTurn oldD) )}+ GT -> oldD {resCurrentTurn =+ ( fst (resCurrentTurn oldD)+ , snd (resCurrentTurn oldD) + n )}+ } updRefillCalm :: MonadStateWrite m => ActorId -> Int64 -> m () updRefillCalm aid n =@@ -303,7 +369,7 @@ mtb <- getsState $ \s -> flip getActorBody s <$> target let !_A = assert (maybe True (not . bproj) mtb `blame` (fid, source, target, mtb, fact)) ()- let !_A = assert (source == _gleader fact+ let !_A = assert (source == gleader fact `blame` "unexpected actor leader" `swith` (fid, source, target, mtb, fact)) () let adj fa = fa {_gleader = target}@@ -323,12 +389,6 @@ updateFaction fid1 (adj fid2) updateFaction fid2 (adj fid1) -updAutoFaction :: MonadStateWrite m => FactionId -> Bool -> m ()-updAutoFaction fid st =- updateFaction fid (\fact ->- assert (isAIFact fact == not st)- $ fact {gplayer = automatePlayer st (gplayer fact)})- updTacticFaction :: MonadStateWrite m => FactionId -> Tactic -> Tactic -> m () updTacticFaction fid toT fromT = do let adj fact =@@ -337,7 +397,13 @@ $ fact {gplayer = player {ftactic = toT}} updateFaction fid adj --- | Record a given number (usually just 1, or -1 for undo) of actor kills+updAutoFaction :: MonadStateWrite m => FactionId -> Bool -> m ()+updAutoFaction fid st =+ updateFaction fid (\fact ->+ assert (isAIFact fact == not st)+ $ fact {gplayer = automatePlayer st (gplayer fact)})++-- Record a given number (usually just 1, or -1 for undo) of actor kills -- for score calculation. updRecordKill :: MonadStateWrite m => ActorId -> Kind.Id ItemKind -> Int -> m () updRecordKill aid ikind k = do@@ -354,76 +420,74 @@ -- as it could be and there is a higher chance of getting back alive -- the actor, the human player has grown attached to. --- | Alter an attribute (actually, the only, the defining attribute)+-- Alter an attribute (actually, the only, the defining attribute) -- of a visible tile. This is similar to e.g., @UpdTrajectory@. ----- For now, we don't remove embedded items when altering a tile--- and neither do we create fresh ones. It works fine, e.g., for tiles on fire--- that change into burnt out tile and then the fire item can no longer--- be triggered due to @alterMinSkillKind@ excluding items without @Embed@,--- even if the burnt tile has low enough @talter@.+-- Removing and creating embedded items when altering a tile+-- is done separately via @UpdCreateItem@ and @UpdDestroyItem@. updAlterTile :: MonadStateWrite m => LevelId -> Point -> Kind.Id TileKind -> Kind.Id TileKind -> m () updAlterTile lid p fromTile toTile = assert (fromTile /= toTile) $ do- Kind.COps{cotile, coTileSpeedup} <- getsState scops+ Kind.COps{coTileSpeedup} <- getsState scops lvl <- getLevel lid- -- The second alternative below can happen if, e.g., a client remembers,- -- but does not see the tile (so does not notice the SearchTile action),- -- and it suddenly changes into another tile,- -- which at the same time becomes visible (e.g., an open door).- let adj ts = assert (ts PointArray.! p == fromTile- || ts PointArray.! p == Tile.hideAs cotile fromTile- `blame` "unexpected altered tile kind"- `swith` (lid, p, fromTile, toTile, ts PointArray.! p))- $ ts PointArray.// [(p, toTile)]- updateLevel lid $ updateTile adj- case ( Tile.isExplorable coTileSpeedup fromTile- , Tile.isExplorable coTileSpeedup toTile ) of- (False, True) -> updateLevel lid $ \lvl2 -> lvl2 {lseen = lseen lvl + 1}- (True, False) -> updateLevel lid $ \lvl2 -> lvl2 {lseen = lseen lvl - 1}- _ -> return ()+ let t = lvl `at` p+ if t /= fromTile+ then atomicFail "tile to alter is different than assumed"+ else do+ let adj ts = ts PointArray.// [(p, toTile)]+ updateLevel lid $ updateTile adj+ case ( Tile.isExplorable coTileSpeedup fromTile+ , Tile.isExplorable coTileSpeedup toTile ) of+ (False, True) -> updateLevel lid $ \lvl2 -> lvl2 {lseen = lseen lvl + 1}+ (True, False) -> updateLevel lid $ \lvl2 -> lvl2 {lseen = lseen lvl - 1}+ _ -> return () updAlterExplorable :: MonadStateWrite m => LevelId -> Int -> m () updAlterExplorable lid delta = assert (delta /= 0) $ updateLevel lid $ \lvl -> lvl {lexplorable = lexplorable lvl + delta} --- Notice previously invisible tiles. This is similar to @UpdSpotActor@,--- but done in bulk, because it often involves dozens of tiles pers move.--- We don't check that the tiles at the positions in question are unknown--- to save computation, especially for clients that remember tiles--- at previously seen positions. Similarly, when updating the @lseen@--- field we don't assume the tiles were unknown previously.+-- Showing to the client the embedded items, if any, is done elsewhere.+updSearchTile :: MonadStateWrite m+ => ActorId -> Point -> Kind.Id TileKind -> m ()+updSearchTile aid p toTile = do+ Kind.COps{cotile} <- getsState scops+ b <- getsState $ getActorBody aid+ lvl <- getLevel $ blid b+ let t = lvl `at` p+ if t == toTile+ then atomicFail "tile already searched"+ else assert (Just t == Tile.hideAs cotile toTile) $ do+ updLoseTile (blid b) [(p, t)]+ updSpotTile (blid b) [(p, toTile)] -- not the hidden version this one time++-- Notice previously invisible tiles. This is done in bulk,+-- because it often involves dozens of tiles per move.+-- We verify that the old tiles at the positions in question+-- are indeed unknown. updSpotTile :: MonadStateWrite m => LevelId -> [(Point, Kind.Id TileKind)] -> m () updSpotTile lid ts = assert (not $ null ts) $ do Kind.COps{coTileSpeedup} <- getsState scops- Level{ltile} <- getLevel lid- let adj tileMap = tileMap PointArray.// ts+ let unk tileMap (p, _) = tileMap PointArray.! p == unknownId+ adj tileMap = assert (all (unk tileMap) ts) $ tileMap PointArray.// ts updateLevel lid $ updateTile adj- let f (p, t2) = do- let t1 = ltile PointArray.! p- case (Tile.isExplorable coTileSpeedup t1, Tile.isExplorable coTileSpeedup t2) of- (False, True) -> updateLevel lid $ \lvl -> lvl {lseen = lseen lvl+1}- (True, False) -> updateLevel lid $ \lvl -> lvl {lseen = lseen lvl-1}- _ -> return ()+ let f (_, t1) = when (Tile.isExplorable coTileSpeedup t1) $+ updateLevel lid $ \lvl -> lvl {lseen = lseen lvl + 1} mapM_ f ts --- Stop noticing previously visible tiles. Unlike @updSpotActor@, it verifies--- the state of the tiles before changing them.+-- Stop noticing previously visible tiles. It verifies+-- the state of the tiles before wiping them out. updLoseTile :: MonadStateWrite m => LevelId -> [(Point, Kind.Id TileKind)] -> m () updLoseTile lid ts = assert (not $ null ts) $ do Kind.COps{coTileSpeedup} <- getsState scops- let matches _ [] = True- matches tileMap ((p, ov) : rest) =- tileMap PointArray.! p == ov && matches tileMap rest+ let matches tileMap (p, ov) = tileMap PointArray.! p == ov tu = map (second (const unknownId)) ts- adj tileMap = assert (matches tileMap ts) $ tileMap PointArray.// tu+ adj tileMap = assert (all (matches tileMap) ts) $ tileMap PointArray.// tu updateLevel lid $ updateTile adj- let f (_, t1) =- when (Tile.isExplorable coTileSpeedup t1) $- updateLevel lid $ \lvl -> lvl {lseen = lseen lvl - 1}+ let f (_, t1) = when (Tile.isExplorable coTileSpeedup t1) $+ updateLevel lid $ \lvl -> lvl {lseen = lseen lvl - 1} mapM_ f ts updAlterSmell :: MonadStateWrite m => LevelId -> Point -> Time -> Time -> m ()@@ -466,7 +530,6 @@ insertItemContainer iid (k, toIt) c Nothing -> error $ "" `showFailure` (bag, iid, c, fromIt, toIt) --- | Age the game. updAgeGame :: MonadStateWrite m => [LevelId] -> m () updAgeGame lids = do modifyState $ updateTime $ flip timeShift (Delta timeClip)@@ -480,6 +543,104 @@ ageLevel :: MonadStateWrite m => Delta Time -> LevelId -> m () ageLevel delta lid = updateLevel lid $ \lvl -> lvl {ltime = timeShift (ltime lvl) delta}++updDiscover :: MonadStateWrite m+ => Container -> ItemId -> Kind.Id ItemKind -> ItemSeed -> m ()+updDiscover _c iid ik seed = do+ itemD <- getsState sitemD+ case EM.lookup iid itemD of+ Nothing -> atomicFail "discovered item unknown"+ Just item -> do+ discoKind <- getsState sdiscoKind+ case EM.lookup (jkindIx item) discoKind of+ Just KindMean{kmConst} -> do+ discoAspect <- getsState sdiscoAspect+ if kmConst || iid `EM.member` discoAspect+ then atomicFail "item already fully discovered"+ else discoverSeed iid seed+ Nothing -> do+ KindMean{kmConst} <- discoverKind (jkindIx item) ik+ unless kmConst $ discoverSeed iid seed+ resetActorAspect++updCover :: Container -> ItemId -> Kind.Id ItemKind -> ItemSeed -> m ()+updCover _c _iid _ik _seed = undefined++updDiscoverKind :: MonadStateWrite m+ => Container -> ItemKindIx -> Kind.Id ItemKind -> m ()+updDiscoverKind _c ix kmKind = do+ discoKind <- getsState sdiscoKind+ if ix `EM.member` discoKind+ then atomicFail "item kind already discovered"+ else do+ void $ discoverKind ix kmKind+ resetActorAspect++discoverKind :: MonadStateWrite m+ => ItemKindIx -> Kind.Id ItemKind -> m KindMean+discoverKind ix kmKind = do+ Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops+ let kind = okind kmKind+ kmMean = meanAspect kind+ kmConst = not $ aspectsRandom kind+ km = KindMean{..}+ f Nothing = Just km+ f Just{} = error $ "already discovered" `showFailure` (ix, kmKind)+ modifyState $ updateDiscoKind $ \discoKind1 ->+ EM.alter f ix discoKind1+ return km++updCoverKind :: Container -> ItemKindIx -> Kind.Id ItemKind -> m ()+updCoverKind _c _ix _ik = undefined++updDiscoverSeed :: MonadStateWrite m+ => Container -> ItemId -> ItemSeed -> m ()+updDiscoverSeed _c iid seed = do+ itemD <- getsState sitemD+ case EM.lookup iid itemD of+ Nothing -> atomicFail "discovered item unknown"+ Just item -> do+ discoKind <- getsState sdiscoKind+ case EM.lookup (jkindIx item) discoKind of+ Nothing -> error "discovered item kind unknown"+ Just KindMean{kmConst} -> do+ discoAspect <- getsState sdiscoAspect+ if kmConst || iid `EM.member` discoAspect+ then atomicFail "item seed already discovered"+ else do+ discoverSeed iid seed+ resetActorAspect++discoverSeed :: MonadStateWrite m => ItemId -> ItemSeed -> m ()+discoverSeed iid seed = do+ Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops+ item <- getsState $ getItemBody iid+ totalDepth <- getsState stotalDepth+ Level{ldepth} <- getLevel $ jlid item+ discoKind <- getsState sdiscoKind+ let KindMean{..} = fromMaybe (error "discovered item kind unknown")+ (EM.lookup (jkindIx item) discoKind)+ kind = okind kmKind+ aspects = seedToAspect seed kind ldepth totalDepth+ f Nothing = Just aspects+ f Just{} = error $ "already discovered" `showFailure` (iid, seed)+ assert (not kmConst) $+ modifyState $ updateDiscoAspect $ \discoAspect1 ->+ EM.alter f iid discoAspect1++updCoverSeed :: Container -> ItemId -> ItemSeed -> m ()+updCoverSeed _c _iid _seed = undefined++updDiscoverServer :: MonadStateWrite m => ItemId -> AspectRecord -> m ()+updDiscoverServer iid aspectRecord =+ modifyState $ updateDiscoAspect $ \discoAspect1 ->+ EM.insert iid aspectRecord discoAspect1++updCoverServer :: MonadStateWrite m => ItemId -> AspectRecord -> m ()+updCoverServer iid aspectRecord =+ modifyState $ updateDiscoAspect $ \discoAspect1 ->+ assert (discoAspect1 EM.! iid == aspectRecord)+ $ EM.delete iid discoAspect1 updRestart :: MonadStateWrite m => State -> m () updRestart = putState
− Game/LambdaHack/Atomic/MonadAtomic.hs
@@ -1,20 +0,0 @@--- | Atomic monads for handling atomic game state transformations.-module Game.LambdaHack.Atomic.MonadAtomic- ( MonadAtomic(..)- ) where--import Prelude ()--import Game.LambdaHack.Atomic.CmdAtomic-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception---- | The monad for executing atomic game state transformations.-class MonadStateRead m => MonadAtomic m where- -- | Execute an atomic command that really changes the state.- execUpdAtomic :: UpdAtomic -> m ()- -- | Execute an atomic command that only displays special effects.- execSfxAtomic :: SfxAtomic -> m ()- execSendPer :: FactionId -> LevelId- -> Perception -> Perception -> Perception -> m ()
Game/LambdaHack/Atomic/MonadStateWrite.hs view
@@ -1,17 +1,27 @@--- | The monad for writing to the game state and related operations.+-- | The monad for writing to the main game state. module Game.LambdaHack.Atomic.MonadStateWrite- ( MonadStateWrite(..)- , putState, updateLevel, updateActor, updateFaction- , insertItemContainer, insertItemActor, deleteItemContainer, deleteItemActor- , updateFloor, updateActorMap, moveActorMap- , updateTile, updateSmell+ ( MonadStateWrite(..), AtomicFail(..), atomicFail+ , putState, updateLevel, updateActor, updateFaction, moveActorMap+ , insertBagContainer, insertItemContainer, insertItemActor+ , deleteBagContainer, deleteItemContainer, deleteItemActor+ , addAis, itemsMatch, addItemToActor, resetActorAspect+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , insertItemFloor, insertItemEmbed+ , insertItemOrgan, insertItemEqp, insertItemInv, insertItemSha+ , deleteItemFloor, deleteItemEmbed+ , deleteItemOrgan, deleteItemEqp, deleteItemInv, deleteItemSha+ , rmFromBag+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude +import qualified Control.Exception as Ex import qualified Data.EnumMap.Strict as EM+import Data.Key (mapWithKeyM_) import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState@@ -23,23 +33,49 @@ import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.State +-- | The monad for writing to the main game state. Atomic updates ('UpdAtomic')+-- are given semantics in this monad. class MonadStateRead m => MonadStateWrite m where modifyState :: (State -> State) -> m () +-- | Exception signifying that atomic action failed because+-- the information it carries is inconsistent with the client's state,+-- (e.g., because the client knows too little to understand the command+-- or already deduced the state change from earlier commands+-- or is confused, amnesiac or sees illusory actors or tiles).+-- Whenever we know the failure is logically impossible,+-- we don't throw the @AtomicFail@ exception, but insert a normal assertion+-- or @error@ call, which are never caught nor handled.+newtype AtomicFail = AtomicFail String+ deriving Show++instance Ex.Exception AtomicFail++atomicFail :: String -> a+atomicFail = Ex.throw . AtomicFail+ putState :: MonadStateWrite m => State -> m () putState s = modifyState (const s) --- | Update the items on the ground map.-updateFloor :: (ItemFloor -> ItemFloor) -> Level -> Level-updateFloor f lvl = lvl {lfloor = f (lfloor lvl)}+-- INLIning offers no speedup, increases alloc and binary size.+-- EM.alter not necessary, because levels not removed, so little risk+-- of adjusting at absent index.+updateLevel :: MonadStateWrite m => LevelId -> (Level -> Level) -> m ()+updateLevel lid f = modifyState $ updateDungeon $ EM.adjust f lid --- | Update the items embedded in a tile on the level.-updateEmbed :: (ItemFloor -> ItemFloor) -> Level -> Level-updateEmbed f lvl = lvl {lembed = f (lembed lvl)}+-- INLIning doesn't help despite probably canceling the alt indirection.+-- perhaps it's applied automatically due to INLINABLE.+updateActor :: MonadStateWrite m => ActorId -> (Actor -> Actor) -> m ()+updateActor aid f = do+ let alt Nothing = error $ "no body to update" `showFailure` aid+ alt (Just b) = Just $ f b+ modifyState $ updateActorD $ EM.alter alt aid --- | Update the actors on the ground map.-updateActorMap :: (ActorMap -> ActorMap) -> Level -> Level-updateActorMap f lvl = lvl {lactor = f (lactor lvl)}+updateFaction :: MonadStateWrite m => FactionId -> (Faction -> Faction) -> m ()+updateFaction fid f = do+ let alt Nothing = error $ "no faction to update" `showFailure` fid+ alt (Just fact) = Just $ f fact+ modifyState $ updateFactionD $ EM.alter alt fid moveActorMap :: MonadStateWrite m => ActorId -> Actor -> Actor -> m () moveActorMap aid body newBody = do@@ -63,34 +99,23 @@ . EM.alter rmActor (bpos body) updateLevel (blid body) $ updateActorMap updActor --- | Update the tile map.-updateTile :: (TileMap -> TileMap) -> Level -> Level-updateTile f lvl = lvl {ltile = f (ltile lvl)}---- | Update the smell map.-updateSmell :: (SmellMap -> SmellMap) -> Level -> Level-updateSmell f lvl = lvl {lsmell = f (lsmell lvl)}---- INLIning offers no speedup, increases alloc and binary size.--- EM.alter not necessary, because levels not removed, so little risk--- of adjusting at absent index.--- | Update a given level data within state.-updateLevel :: MonadStateWrite m => LevelId -> (Level -> Level) -> m ()-updateLevel lid f = modifyState $ updateDungeon $ EM.adjust f lid---- INLIning doesn't help despite probably canceling the alt indirection.--- perhaps it's applied automatically due to INLINABLE.-updateActor :: MonadStateWrite m => ActorId -> (Actor -> Actor) -> m ()-updateActor aid f = do- let alt Nothing = error $ "no body to update" `showFailure` aid- alt (Just b) = Just $ f b- modifyState $ updateActorD $ EM.alter alt aid--updateFaction :: MonadStateWrite m => FactionId -> (Faction -> Faction) -> m ()-updateFaction fid f = do- let alt Nothing = error $ "no faction to update" `showFailure` fid- alt (Just fact) = Just $ f fact- modifyState $ updateFactionD $ EM.alter alt fid+insertBagContainer :: MonadStateWrite m+ => ItemBag -> Container -> m ()+insertBagContainer bag c = case c of+ CFloor lid pos -> do+ let alt Nothing = Just bag+ alt (Just bag2) = atomicFail $ "floor bag not empty"+ `showFailure` (bag2, lid, pos, bag)+ updateLevel lid $ updateFloor $ EM.alter alt pos+ CEmbed lid pos -> do+ let alt Nothing = Just bag+ alt (Just bag2) = atomicFail $ "embed bag not empty"+ `showFailure` (bag2, lid, pos, bag)+ updateLevel lid $ updateEmbed $ EM.alter alt pos+ CActor aid store ->+ -- Very unlikely case, so we prefer brevity over performance.+ mapWithKeyM_ (\iid kit -> insertItemActor iid kit aid store) bag+ CTrunk{} -> return () insertItemContainer :: MonadStateWrite m => ItemId -> ItemQuant -> Container -> m ()@@ -162,6 +187,24 @@ upd = EM.unionWith mergeItemQuant bag updateFaction fid $ \fact -> fact {gsha = upd (gsha fact)} +deleteBagContainer :: MonadStateWrite m+ => ItemBag -> Container -> m ()+deleteBagContainer bag c = case c of+ CFloor lid pos -> do+ let alt Nothing = atomicFail $ "floor bag already empty"+ `showFailure` (lid, pos, bag)+ alt (Just bag2) = assert (bag == bag2) Nothing+ updateLevel lid $ updateFloor $ EM.alter alt pos+ CEmbed lid pos -> do+ let alt Nothing = atomicFail $ "embed bag already empty"+ `showFailure` (lid, pos, bag)+ alt (Just bag2) = assert (bag == bag2 `blame` (bag, bag2)) Nothing+ updateLevel lid $ updateEmbed $ EM.alter alt pos+ CActor aid store ->+ -- Very unlikely case, so we prefer brevity over performance.+ mapWithKeyM_ (\iid kit -> deleteItemActor iid kit aid store) bag+ CTrunk{} -> error $ "" `showFailure` c+ deleteItemContainer :: MonadStateWrite m => ItemId -> ItemQuant -> Container -> m () deleteItemContainer iid kit c = case c of@@ -239,3 +282,42 @@ `blame` (rmIt, take k it, n, kit, iid, bag)) $ Just (n - k, take (n - k) it) in EM.alter rfb iid bag++-- Actor's items may or may not be already present in @sitemD@,+-- regardless if they are already present otherwise in the dungeon.+-- We re-add them all to save time determining which really need it.+-- If collision occurs, pick the item found on easier level.+addAis :: MonadStateWrite m => [(ItemId, Item)] -> m ()+addAis ais = do+ let h item1 item2 =+ assert (itemsMatch item1 item2+ `blame` "inconsistent added items"+ `swith` (item1, item2, ais))+ item2 -- keep the first found level+ forM_ ais $ \(iid, item) ->+ modifyState+ $ updateItemIxMap (EM.insertWith (++) (jkindIx item) [iid])+ . updateItemD (EM.insertWith h iid item)++itemsMatch :: Item -> Item -> Bool+itemsMatch item1 item2 =+ jkindIx item1 == jkindIx item2+ -- Note that nothing else needs to be the same, since items are merged+ -- and clients have different views on dungeon items than the server.++addItemToActor :: MonadStateWrite m => ItemId -> Item -> Int -> ActorId -> m ()+addItemToActor iid itemBase k aid = do+ arItem <- getsState $ aspectRecordFromItem iid itemBase+ let f arActor = sumAspectRecord [(arActor, 1), (arItem, k)]+ modifyState $ updateActorAspect $ EM.adjust f aid++resetActorAspect :: MonadStateWrite m => m ()+resetActorAspect = do+ -- Each actor's equipment and organs would need to be inspected,+ -- the iid looked up, e.g., if it wasn't in old discoKind, but is in new,+ -- and then aspect record updated, so it's simpler and not much more+ -- expensive to generate new sactorAspect. Optimize only after profiling.+ -- Also note this doesn't get invoked on the server, because it bails out+ -- earlier, upon noticing the item is already fully known.+ actorAspect <- getsState actorAspectInDungeon+ modifyState $ updateActorAspect $ const actorAspect
Game/LambdaHack/Atomic/PosAtomicRead.hs view
@@ -1,23 +1,27 @@--- | Semantics of atomic commands shared by client and server.+-- | Representation and computation of visiblity of atomic commands+-- by clients.+-- -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>. module Game.LambdaHack.Atomic.PosAtomicRead ( PosAtomic(..), posUpdAtomic, posSfxAtomic- , breakUpdAtomic, seenAtomicCli, seenAtomicSer, generalMoveItem, posProjBody+ , breakUpdAtomic, seenAtomicCli, seenAtomicSer+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , posProjBody, singleAid, doubleAid, singleContainer+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import Game.LambdaHack.Atomic.CmdAtomic import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.Level import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.MonadStateRead@@ -35,29 +39,35 @@ | PosFidAndSight [FactionId] LevelId [Point] -- ^ observers and the faction notice | PosSmell LevelId [Point] -- ^ whomever smells all the positions, notices- | PosFid FactionId -- ^ only the faction notices- | PosFidAndSer (Maybe LevelId) FactionId -- ^ faction and server notices+ | PosFid FactionId -- ^ only the faction notices, server doesn't+ | PosFidAndSer (Maybe LevelId) FactionId+ -- ^ faction and server notices | PosSer -- ^ only the server notices | PosAll -- ^ everybody notices | PosNone -- ^ never broadcasted, but sent manually deriving (Show, Eq) --- | Produce the positions where the atomic update takes place.+-- | Produce the positions where the atomic update takes place or, more+-- generally, the conditions under which the update can be noticed by+-- a client. ----- The goal of the mechanics is to ensure the commands don't carry--- significantly more information than their corresponding state diffs would.--- In other words, the atomic commands involving the positions seen by a client--- should convey similar information as the client would get by directly--- observing the changes the commands enact on the visible portion of server--- game state. The client is then free to change its copy of game state--- accordingly or not --- it only partially reflects reality anyway.+-- The goal of this mechanics is to ensure that atomic commands involving+-- some positions visible by a client convey similar information as the client+-- would get by directly observing the changes+-- of the portion of server state limited to the visible positions.+-- Consequently, when the visible commands are later applied+-- to the client's state, the state stays consistent+-- --- in sync with the server state and correctly limited by visiblity.+-- There is some wiggle room both in what "in sync" and+-- "visible" means and how they propagate through time. ----- E.g., @UpdDisplaceActor@ in a black room,--- with one actor carrying a 0-radius light would not be+-- E.g., @UpdDisplaceActor@ in a black room between two enemy actors,+-- with only one actor carrying a 0-radius light would not be -- distinguishable by looking at the state (or the screen) from @UpdMoveActor@ -- of the illuminated actor, hence such @UpdDisplaceActor@ should not be--- observable, but @UpdMoveActor@ should be (or the former should be perceived--- as the latter). However, to simplify, we assign as strict visibility+-- observable, but @UpdMoveActor@ in similar cotext would be+-- (or the former should be perceived as the latter).+-- However, to simplify, we assign as strict visibility -- requirements to @UpdMoveActor@ as to @UpdDisplaceActor@ and fall back -- to @UpdSpotActor@ (which provides minimal information that does not -- contradict state) if the visibility is lower.@@ -71,6 +81,8 @@ UpdLoseActor _ body _ -> return $! posProjBody body UpdSpotItem _ _ _ _ c -> singleContainer c UpdLoseItem _ _ _ _ c -> singleContainer c+ UpdSpotItemBag c _ _ -> singleContainer c+ UpdLoseItemBag c _ _ -> singleContainer c UpdMoveActor aid fromP toP -> do b <- getsState $ getActorBody aid -- Non-projectile actors are never totally isolated from envirnoment;@@ -125,14 +137,15 @@ UpdCoverKind c _ _ -> singleContainer c UpdDiscoverSeed c _ _ -> singleContainer c UpdCoverSeed c _ _ -> singleContainer c+ UpdDiscoverServer{} -> return PosSer+ UpdCoverServer{} -> return PosSer UpdPerception{} -> return PosNone- UpdRestart fid _ _ _ _ _ -> return $! PosFid fid+ UpdRestart fid _ _ _ _ -> return $! PosFid fid UpdRestartServer _ -> return PosSer UpdResume _ _ -> return PosNone UpdResumeServer _ -> return PosSer UpdKillExit fid -> return $! PosFid fid UpdWriteSave -> return PosAll- UpdMsgAll{} -> return PosAll -- | Produce the positions where the atomic special effect takes place. posSfxAtomic :: MonadStateRead m => SfxAtomic -> m PosAtomic@@ -161,6 +174,7 @@ else return $! PosFidAndSight [bfid body] (blid body) [bpos body, p] SfxEffect _ aid _ _ -> singleAid aid -- sometimes we don't see source, OK SfxMsgFid fid _ -> return $! PosFid fid+ SfxSortSlots -> return PosAll posProjBody :: Actor -> PosAtomic posProjBody body =@@ -191,23 +205,25 @@ singleContainer (CTrunk fid lid p) = return $! PosFidAndSight [fid] lid [p] --- | Decompose an atomic action. The decomposed actions give reduced--- information that still modifies client's state to match the server state--- wrt the current FOV and the subset of @posUpdAtomic@ that is visible.--- The original actions give more information not only due to spanning--- potentially more positions than those visible. E.g., @UpdMoveActor@+-- | Decompose an atomic action that is outside a client's visiblity.+-- The decomposed actions give less information that the original command,+-- but some of them may fall within the visibility range of the client.+-- The original action may give more information than even the total sum+-- of all actions it's broken into. E.g., @UpdMoveActor@ -- informs about the continued existence of the actor between--- moves, v.s., popping out of existence and then back in.+-- moves vs popping out of existence and then back in.+--+-- This is computed in server's @State@ from before performing the command. breakUpdAtomic :: MonadStateRead m => UpdAtomic -> m [UpdAtomic] breakUpdAtomic cmd = case cmd of- UpdMoveActor aid _ toP -> do+ UpdMoveActor aid fromP toP -> do -- We assume other factions don't see leaders and we know the actor's -- faction always sees the atomic command, so the leader doesn't -- need to be updated (or the actor is a projectile, hence not a leader). b <- getsState $ getActorBody aid ais <- getsState $ getCarriedAssocs b return [ UpdLoseActor aid b ais- , UpdSpotActor aid b {bpos = toP, boldpos = Just $ bpos b} ais ]+ , UpdSpotActor aid b {bpos = toP, boldpos = Just fromP} ais ] UpdDisplaceActor source target -> do sb <- getsState $ getActorBody source sais <- getsState $ getCarriedAssocs sb@@ -222,7 +238,7 @@ ] _ -> return [] --- | Given the client, it's perception and an atomic command, determine+-- | Given the client, its perception and an atomic command, determine -- if the client notices the command. seenAtomicCli :: Bool -> FactionId -> Perception -> PosAtomic -> Bool seenAtomicCli knowEvents fid per posAtomic =@@ -237,34 +253,11 @@ PosAll -> True PosNone -> error $ "no position possible" `showFailure` fid --- Not needed ATM, but may be a coincidence.+-- | Determine whether the server would see a command that has+-- the given visibilty conditions. seenAtomicSer :: PosAtomic -> Bool seenAtomicSer posAtomic = case posAtomic of PosFid _ -> False PosNone -> error $ "no position possible" `showFailure` posAtomic _ -> True---- | Generate the atomic updates that jointly perform a given item move.-generalMoveItem :: MonadStateRead m- => Bool -> ItemId -> Int -> Container -> Container- -> m [UpdAtomic]-generalMoveItem verbose iid k c1 c2 =- case (c1, c2) of- (CActor aid1 cstore1, CActor aid2 cstore2) | aid1 == aid2- && cstore1 /= CSha- && cstore2 /= CSha ->- return [UpdMoveItem iid k aid1 cstore1 cstore2]- _ -> containerMoveItem verbose iid k c1 c2--containerMoveItem :: MonadStateRead m- => Bool -> ItemId -> Int -> Container -> Container- -> m [UpdAtomic]-containerMoveItem verbose iid k c1 c2 = do- bag <- getsState $ getContainerBag c1- case iid `EM.lookup` bag of- Nothing -> error $ "" `showFailure` (iid, k, c1, c2)- Just (_, it) -> do- item <- getsState $ getItemBody iid- return [ UpdLoseItem verbose iid item (k, take k it) c1- , UpdSpotItem verbose iid item (k, take k it) c2 ]
Game/LambdaHack/Client.hs view
@@ -1,16 +1,29 @@--- | Semantics of responses that are sent to clients.+-- | Semantics of responses that are sent from server to clients,+-- in terms of client state transformations,+-- and semantics of human commands and AI moves, in terms of requests+-- to be sent from the client to the server. -- -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>. module Game.LambdaHack.Client ( -- * Re-exported from "Game.LambdaHack.Client.LoopM" loopCli+ -- * Re-exported from "Game.LambdaHack.Client.Request"+ , RequestAI, ReqAI(..), RequestUI, ReqUI(..)+ , RequestAnyAbility(..), RequestTimed(..)+ -- * Re-exported from "Game.LambdaHack.Client.Response"+ , Response (..)+ -- * Re-exported from "Game.LambdaHack.Client.ClientOptions"+ , ClientOptions, defClientOptions, sbenchmark -- * Re-exported from "Game.LambdaHack.Client.UI"- , KeyKind, SessionUI, emptySessionUI- , Config, applyConfigToDebug, configCmdline, mkConfig+ , KeyKind+ , UIOptions, applyUIOptions, uCmdline, mkUIOptions ) where import Prelude () +import Game.LambdaHack.Client.ClientOptions import Game.LambdaHack.Client.LoopM+import Game.LambdaHack.Client.Request+import Game.LambdaHack.Client.Response import Game.LambdaHack.Client.UI
Game/LambdaHack/Client/AI.hs view
@@ -5,7 +5,7 @@ ( queryAI #ifdef EXPOSE_INTERNAL -- * Internal operations- , pickAI, pickAction, udpdateCondInMelee, condInMeleeM+ , pickActorAndAction, udpdateCondInMelee #endif ) where @@ -17,57 +17,56 @@ import Game.LambdaHack.Client.AI.HandleAbilityM import Game.LambdaHack.Client.AI.PickActorM-import Game.LambdaHack.Client.AI.Strategy import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Request import Game.LambdaHack.Client.State import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Frequency import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Request import Game.LambdaHack.Common.State --- | Handle the move of an actor under AI control (of UI or AI player).+-- | Handle the move of an actor under AI control (regardless if the whole+-- faction is under human or computer control). queryAI :: MonadClient m => ActorId -> m RequestAI queryAI aid = do- -- @_sleader@ may be different from @_gleader@ due to @stopPlayBack@,+ -- @sleader@ may be different from @gleader@ due to @stopPlayBack@, -- but only leaders may change faction leader, so we fix that: side <- getsClient sside- mleader <- getsState $ _gleader . (EM.! side) . sfactionD- mleaderCli <- getsClient _sleader+ mleader <- getsState $ gleader . (EM.! side) . sfactionD+ mleaderCli <- getsClient sleader unless (Just aid == mleader || mleader == mleaderCli) $ -- @aid@ is not the leader, so he can't change leader modifyClient $ \cli -> cli {_sleader = mleader}- -- @condInMelee@ will most proably be needed in this functions,+ -- @condInMelee@ will most proably be needed in the following functions, -- but even if not, it's OK, it's not forced, because wrapped in @Maybe@: udpdateCondInMelee aid- (aidToMove, treq) <- pickAI Nothing aid+ (aidToMove, treq) <- pickActorAndAction Nothing aid (aidToMove2, treq2) <- case treq of RequestAnyAbility ReqWait | mleader == Just aid -> do -- leader waits; a waste; try once to pick a yet different leader modifyClient $ \cli -> cli {_sleader = mleader} -- undo previous choice- pickAI (Just (aidToMove, treq)) aid+ pickActorAndAction (Just (aidToMove, treq)) aid _ -> return (aidToMove, treq) return ( ReqAITimed treq2 , if aidToMove2 /= aid then Just aidToMove2 else Nothing ) -pickAI :: MonadClient m- => Maybe (ActorId, RequestAnyAbility) -> ActorId- -> m (ActorId, RequestAnyAbility)+-- | Pick an actor to move and an action for him to perform, given an optional+-- previous candidate actor and action and the server-proposed actor.+pickActorAndAction :: MonadClient m+ => Maybe (ActorId, RequestAnyAbility) -> ActorId+ -> m (ActorId, RequestAnyAbility) -- This inline speeds up execution by 10% and increases allocation by 15%, -- despite probably bloating executable:-{-# INLINE pickAI #-}-pickAI maid aid = do- mleader <- getsClient _sleader+{-# INLINE pickActorAndAction #-}+pickActorAndAction maid aid = do+ mleader <- getsClient sleader aidToMove <- if mleader == Just aid then pickActorToMove (fst <$> maid) else do- useTactics aid+ setTargetFromTactics aid return aid treq <- case maid of Just (aidOld, treqOld) | aidToMove == aidOld ->@@ -75,26 +74,11 @@ _ -> pickAction aidToMove (isJust maid) return (aidToMove, treq) --- | Pick an action the actor will perform this turn.-pickAction :: MonadClient m => ActorId -> Bool -> m RequestAnyAbility-{-# INLINE pickAction #-}-pickAction aid retry = do- side <- getsClient sside- body <- getsState $ getActorBody aid- let !_A = assert (bfid body == side- `blame` "AI tries to move enemy actor"- `swith` (aid, bfid body, side)) ()- let !_A = assert (isNothing (btrajectory body)- `blame` "AI gets to manually move its projectiles"- `swith` (aid, bfid body, side)) ()- stratAction <- actionStrategy aid retry- let bestAction = bestVariant stratAction- !_A = assert (not (nullFreq bestAction) -- equiv to nullStrategy- `blame` "no AI action for actor"- `swith` (stratAction, aid, body)) ()- -- Run the AI: chose an action from those given by the AI strategy.- rndToAction $ frequency bestAction-+-- | Check if any non-dying foe (projectile or not) is adjacent+-- to any of our normal actors (whether they can melee or just need to flee,+-- in which case alert is needed so that they are not slowed down by others)+-- and record this per-level. This is needed only by AI and computed+-- as lazily as possible before each round of AI deliberations. udpdateCondInMelee :: MonadClient m => ActorId -> m () udpdateCondInMelee aid = do b <- getsState $ getActorBody aid@@ -102,26 +86,8 @@ case condInMelee of Just{} -> return () -- still up to date Nothing -> do- newCond <- condInMeleeM b -- lazy and kept that way due to @Maybe@+ newCond <- getsState $ inMelee b+ -- lazy and kept that way due to @Maybe@ modifyClient $ \cli -> cli {scondInMelee = EM.adjust (const $ Just newCond) (blid b) (scondInMelee cli)}---- | Check if any non-dying foe (projectile or not) is adjacent--- to any of our normal actors (whether they can melee or just need to flee,--- in which case alert is needed so that they are not slowed down by others).-condInMeleeM :: MonadClient m => Actor -> m Bool-condInMeleeM bodyOur = do- fact <- getsState $ (EM.! bfid bodyOur) . sfactionD- let f !b = blid b == blid bodyOur && isAtWar fact (bfid b) && bhp b > 0- -- We assume foes are less numerous, because usually they are heroes,- -- and so we compute them once and use many times.- -- For the same reason @anyFoeAdj@ would not speed up this computation- -- in normal gameplay (as opposed to AI vs AI benchmarks).- allFoes <- getsState $ filter f . EM.elems . sactorD- getsState $ any (\body ->- bfid bodyOur == bfid body- && blid bodyOur == blid body- && not (bproj body)- && bhp body > 0- && any (\b -> adjacent (bpos b) (bpos body)) allFoes) . sactorD
Game/LambdaHack/Client/AI/ConditionM.hs view
@@ -1,23 +1,22 @@--- | Semantics of abilities in terms of actions and the AI procedure--- for picking the best action for an actor.+-- | Assorted conditions used later on in AI logic. module Game.LambdaHack.Client.AI.ConditionM ( condAimEnemyPresentM , condAimEnemyRememberedM , condTgtNonmovingM , condAnyFoeAdjM , condAdjTriggerableM+ , meleeThreatDistList , condBlocksFriendsM , condFloorWeaponM , condNoEqpWeaponM , condCanProjectM , condProjectListM- , condDesirableFloorItemM- , condSupport , benAvailableItems , hinders+ , condDesirableFloorItemM , benGroundItems , desirableItem- , meleeThreatDistList+ , condSupport , condShineWouldBetrayM , fleeList ) where@@ -27,29 +26,29 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import Data.Ord+import Data.Ord -import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.ModeKind -- All conditions are (partially) lazy, because they are not always -- used in the strict monadic computations they are in.@@ -103,9 +102,10 @@ -- so to resolve the stalemate, the opposing AI has to be aggresive -- by ignoring them and then when melee is started, it's usually too late -- to retreat.-meleeThreatDistList :: MonadClient m => ActorId -> m [(Int, (ActorId, Actor))]+meleeThreatDistList :: MonadStateRead m+ => ActorId -> m [(Int, (ActorId, Actor))] meleeThreatDistList aid = do- actorAspect <- getsClient sactorAspect+ actorAspect <- getsState sactorAspect b <- getsState $ getActorBody aid fact <- getsState $ (EM.! bfid b) . sfactionD allAtWar <- getsState $ actorRegularAssocs (isAtWar fact) (blid b)@@ -132,14 +132,14 @@ return $ any blocked ours -- | Require the actor stands over a weapon that would be auto-equipped.-condFloorWeaponM :: MonadClient m => ActorId -> m Bool+condFloorWeaponM :: MonadStateRead m => ActorId -> m Bool condFloorWeaponM aid = do floorAssocs <- getsState $ getActorAssocs aid CGround let lootIsWeapon = any (isMelee . snd) floorAssocs return lootIsWeapon -- | Check whether the actor has no weapon in equipment.-condNoEqpWeaponM :: MonadClient m => ActorId -> m Bool+condNoEqpWeaponM :: MonadStateRead m => ActorId -> m Bool condNoEqpWeaponM aid = do eqpAssocs <- getsState $ getActorAssocs aid CEqp return $ all (not . isMelee . snd) eqpAssocs@@ -159,9 +159,8 @@ b <- getsState $ getActorBody aid condShineWouldBetray <- condShineWouldBetrayM aid condAimEnemyPresent <- condAimEnemyPresentM aid- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect aid+ let calmE = calmEnough b ar condNotCalmEnough = not calmE heavilyDistressed = -- Actor hit by a projectile or similarly distressed. deltaSerious (bcalmDelta b)@@ -186,7 +185,7 @@ => ActorId -> [CStore] -> m [(Maybe Benefit, CStore, ItemId, ItemFull)] benAvailableItems aid cstores = do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull b <- getsState $ getActorBody aid discoBenefit <- getsClient sdiscoBenefit s <- getState@@ -238,7 +237,7 @@ benList <- benAvailableItems aid [CGround] return $ filter isDesirable benList -desirableItem :: Bool -> Maybe Int -> Item -> Bool+desirableItem :: Bool -> Maybe Double -> Item -> Bool desirableItem canEsc mpickupSum item = if canEsc then fromMaybe 10 mpickupSum > 0@@ -253,7 +252,7 @@ condSupport :: MonadClient m => Int -> ActorId -> m Bool condSupport param aid = do- actorAspect <- getsClient sactorAspect+ actorAspect <- getsState sactorAspect b <- getsState $ getActorBody aid btarget <- getsClient $ getTarget aid mtgtPos <- case btarget of@@ -274,14 +273,14 @@ closeAndStrong (aid2, b2) = closeEnough b2 && actorCanMelee actorAspect aid2 b2 closeAndStrongFriends = filter closeAndStrong friends- -- The smaller area scanned for friends, the lower number required.- suport = length closeAndStrongFriends >= param - aAggression ar+ -- The smaller the area scanned for friends, the lower number required.+ suport = length closeAndStrongFriends >= min 2 param - aAggression ar || length friends <= 1 -- solo fighters aggresive return suport -- | Require that the actor stands in the dark and so would be betrayed -- by his own equipped light,-condShineWouldBetrayM :: MonadClient m => ActorId -> m Bool+condShineWouldBetrayM :: MonadStateRead m => ActorId -> m Bool condShineWouldBetrayM aid = do b <- getsState $ getActorBody aid aInAmbient <- getsState $ actorInAmbient b
Game/LambdaHack/Client/AI/HandleAbilityM.hs view
@@ -1,14 +1,13 @@ {-# LANGUAGE DataKinds #-}--- | Semantics of abilities in terms of actions and the AI procedure--- for picking the best action for an actor.+-- | AI procedure for picking the best action for an actor. module Game.LambdaHack.Client.AI.HandleAbilityM- ( actionStrategy+ ( pickAction #ifdef EXPOSE_INTERNAL -- * Internal operations- , ApplyItemGroup+ , actionStrategy , waitBlockNow, pickup, equipItems, toShare, yieldUnneeded, unEquipItems , groupByEqpSlot, bestByEqpSlot, harmful, meleeBlocker, meleeAny- , trigger, projectItem, applyItem, flee+ , trigger, projectItem, ApplyItemGroup, applyItem, flee , displaceFoe, displaceBlocker, displaceTowards , chase, moveTowards, moveOrRunAid #endif@@ -20,44 +19,66 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Function-import Data.Ord-import Data.Ratio+import Data.Function+import Data.Ord+import Data.Ratio -import Game.LambdaHack.Client.AI.ConditionM-import Game.LambdaHack.Client.AI.Strategy-import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Client.BfsM-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Common.Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Frequency-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Client.AI.ConditionM+import Game.LambdaHack.Client.AI.Strategy+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.BfsM+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Request+import Game.LambdaHack.Client.State+import Game.LambdaHack.Common.Ability+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.ModeKind +-- | Pick the most desirable AI ation for the actor.+pickAction :: MonadClient m => ActorId -> Bool -> m RequestAnyAbility+{-# INLINE pickAction #-}+pickAction aid retry = do+ side <- getsClient sside+ body <- getsState $ getActorBody aid+ let !_A = assert (bfid body == side+ `blame` "AI tries to move enemy actor"+ `swith` (aid, bfid body, side)) ()+ let !_A = assert (isNothing (btrajectory body)+ `blame` "AI gets to manually move its projectiles"+ `swith` (aid, bfid body, side)) ()+ stratAction <- actionStrategy aid retry+ let bestAction = bestVariant stratAction+ !_A = assert (not (nullFreq bestAction) -- equiv to nullStrategy+ `blame` "no AI action for actor"+ `swith` (stratAction, aid, body)) ()+ -- Run the AI: chose an action from those given by the AI strategy.+ rndToAction $ frequency bestAction+ type ToAny a = Strategy (RequestTimed a) -> Strategy RequestAnyAbility toAny :: ToAny a toAny strat = RequestAnyAbility <$> strat --- | AI strategy based on actor's sight, smell, etc.+-- AI strategy based on actor's sight, smell, etc. -- Never empty. actionStrategy :: forall m. MonadClient m => ActorId -> Bool -> m (Strategy RequestAnyAbility)@@ -73,7 +94,7 @@ threatDistL <- meleeThreatDistList aid (fleeL, badVic) <- fleeList aid condSupport1 <- condSupport 1 aid- condSupport2 <- condSupport 2 aid+ condSupport3 <- condSupport 3 aid canDeAmbientL <- getsState $ canDeAmbientList body actorSk <- currentSkillsClient aid condCanProject <-@@ -86,8 +107,8 @@ condDesirableFloorItem <- condDesirableFloorItemM aid condTgtNonmoving <- condTgtNonmovingM aid explored <- getsClient sexplored- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)+ actorAspect <- getsState sactorAspect+ let ar = actorAspect EM.! aid lidExplored = ES.member (blid body) explored panicFleeL = fleeL ++ badVic condHpTooLow = hpTooLow body ar@@ -95,7 +116,6 @@ speed1_5 = speedScale (3%2) (bspeed body ar) condCanMelee = actorCanMelee actorAspect aid body condMeleeBad1 = not (condSupport1 && condCanMelee)- condMeleeBad2 = not (condSupport2 && condCanMelee) condThreat n = not $ null $ takeWhile ((<= n) . fst) threatDistL threatAdj = takeWhile ((== 1) . fst) threatDistL condManyThreatAdj = length threatAdj >= 2@@ -134,7 +154,7 @@ -- in the latter case, may return via different stairs later on , condAdjTriggerable && not condAimEnemyPresent && ((condNotCalmEnough || condHpTooLow) -- flee- && condMeleeBad2 && condThreat 1+ && condMeleeBad1 && condThreat 1 || (lidExplored || condEnoughGear) -- explore && not condDesirableFloorItem) ) , ( [AbDisplace]@@ -163,7 +183,7 @@ -- to enemy to get out of his range, most likely, -- and so melee him instead, unless can't melee at all. not condCanMelee- || not condSupport2 && not heavilyDistressed+ || not condSupport3 && not heavilyDistressed | condThreat 5 -> -- Too far to flee from melee, too close from ranged, -- not in ambient, so no point fleeing into dark; advance.@@ -290,7 +310,6 @@ sumFallback <- sumS fallback return $! sumPrefix .| comDistant .| sumSuffix .| sumFallback --- | A strategy to always just wait. waitBlockNow :: MonadClient m => m (Strategy (RequestTimed 'AbWait)) waitBlockNow = return $! returN "wait" ReqWait @@ -306,9 +325,8 @@ -- The calmE is inaccurate also if an item not IDed, but that's intended -- and the server will ignore and warn (and content may avoid that, -- e.g., making all rings identified)- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect aid+ let calmE = calmEnough b ar isWeapon (_, _, _, itemFull) = isMelee $ itemBase itemFull filterWeapon | onlyWeapon = filter isWeapon | otherwise = id@@ -338,12 +356,11 @@ => ActorId -> m (Strategy (RequestTimed 'AbMoveItem)) equipItems aid = do body <- getsState $ getActorBody aid- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)- calmE = calmEnough body ar- eqpAssocs <- fullAssocsClient aid [CEqp]- invAssocs <- fullAssocsClient aid [CInv]- shaAssocs <- fullAssocsClient aid [CSha]+ ar <- getsState $ getActorAspect aid+ let calmE = calmEnough body ar+ eqpAssocs <- getsState $ fullAssocs aid [CEqp]+ invAssocs <- getsState $ fullAssocs aid [CInv]+ shaAssocs <- getsState $ fullAssocs aid [CSha] condShineWouldBetray <- condShineWouldBetrayM aid condAimEnemyPresent <- condAimEnemyPresentM aid discoBenefit <- getsClient sdiscoBenefit@@ -401,10 +418,9 @@ => ActorId -> m (Strategy (RequestTimed 'AbMoveItem)) yieldUnneeded aid = do body <- getsState $ getActorBody aid- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)- calmE = calmEnough body ar- eqpAssocs <- fullAssocsClient aid [CEqp]+ ar <- getsState $ getActorAspect aid+ let calmE = calmEnough body ar+ eqpAssocs <- getsState $ fullAssocs aid [CEqp] condShineWouldBetray <- condShineWouldBetrayM aid condAimEnemyPresent <- condAimEnemyPresentM aid discoBenefit <- getsClient sdiscoBenefit@@ -439,12 +455,11 @@ => ActorId -> m (Strategy (RequestTimed 'AbMoveItem)) unEquipItems aid = do body <- getsState $ getActorBody aid- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)- calmE = calmEnough body ar- eqpAssocs <- fullAssocsClient aid [CEqp]- invAssocs <- fullAssocsClient aid [CInv]- shaAssocs <- fullAssocsClient aid [CSha]+ ar <- getsState $ getActorAspect aid+ let calmE = calmEnough body ar+ eqpAssocs <- getsState $ fullAssocs aid [CEqp]+ invAssocs <- getsState $ fullAssocs aid [CInv]+ shaAssocs <- getsState $ fullAssocs aid [CSha] discoBenefit <- getsClient sdiscoBenefit let improve :: CStore -> ( IK.EqpSlot , ( [(Int, (ItemId, ItemFull))]@@ -533,8 +548,8 @@ meleeBlocker :: MonadClient m => ActorId -> m (Strategy (RequestTimed 'AbMelee)) meleeBlocker aid = do b <- getsState $ getActorBody aid- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)+ actorAspect <- getsState sactorAspect+ let ar = actorAspect EM.! aid fact <- getsState $ (EM.! bfid b) . sfactionD actorSk <- currentSkillsClient aid mtgtMPath <- getsClient $ EM.lookup aid . stargetD@@ -553,8 +568,7 @@ Just aim -> getsState $ posToAssocs aim (blid b) case lBlocker of (aid2, body2) : _ -> do- let ar2 = fromMaybe (error $ "" `showFailure` aid2)- (EM.lookup aid2 actorAspect)+ let ar2 = actorAspect EM.! aid2 -- No problem if there are many projectiles at the spot. We just -- attack the first one. if | actorDying body2@@ -596,7 +610,7 @@ let freq = uniformFreq "melee adjacent" $ catMaybes mels return $! liftFrequency freq --- | The level the actor is on is either explored or the actor already+-- The level the actor is on is either explored or the actor already -- has a weapon equipped, so no need to explore further, he tries to find -- enemies on other levels. -- We don't verify any embedded item is targeted by the actor, but at least@@ -613,7 +627,7 @@ pbags = mapMaybe f $ vicinityUnsafe (bpos b) efeat <- embedBenefit fleeVia aid pbags return $! liftFrequency $ toFreq "trigger"- [ (benefit, ReqAlter pos)+ [ (ceiling benefit, ReqAlter pos) | (benefit, (pos, _)) <- efeat ] projectItem :: MonadClient m@@ -658,7 +672,7 @@ Nothing -> -10 -- experiment if no good options Just Benefit{benFling} -> benFling in if trange >= chessDist (bpos b) fpos && recharged- then Just ( - benR * rangeMult `div` 10+ then Just ( - ceiling (benR * fromIntegral rangeMult / 10) , ReqProject fpos newEps iid cstore ) else Nothing benRanged = mapMaybe fRanged benList@@ -677,9 +691,8 @@ condShineWouldBetray <- condShineWouldBetrayM aid condAimEnemyPresent <- condAimEnemyPresentM aid localTime <- getsState $ getLocalTime (blid b)- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect aid+ let calmE = calmEnough b ar condNotCalmEnough = not calmE heavilyDistressed = -- Actor hit by a projectile or similarly distressed. deltaSerious (bcalmDelta b)@@ -715,7 +728,7 @@ then Just $ toGroupName $ jname item else Nothing) organs itemLegal itemFull =- -- Don't include @Ascend@ not @Teleport@, because can be no foe nearby.+ -- Don't include @Ascend@ nor @Teleport@, because can be no foe nearby. let getP (IK.RefillHP p) | p > 0 = True getP _ = False firstAidItem = case itemDisco itemFull of@@ -736,7 +749,7 @@ -- This assumes the organ dropping is beneficial and so worth -- saving for the future, for otherwise the item would not -- be considered at all, given that it's the only effect.- -- We don't try to intecept a case of many effects.+ -- We don't try to intercept the case of many effects. let dropsGrps = strengthDropOrgan itemFull hasDropOrgan = not $ null dropsGrps f eff = [eff | IK.forApplyEffect eff]@@ -751,7 +764,7 @@ -- experimenting is fun, but it's better to risk -- foes' skin than ours Just Benefit{benApply} ->- benApply+ ceiling benApply * if cstore == CEqp && not durable then 100000 -- must hinder currently else coeff cstore@@ -829,7 +842,7 @@ if boldpos b /= Just target -- avoid trivial loops && Tile.isWalkable coTileSpeedup (lvl `at` target) then do -- DisplaceAccess- mleader <- getsClient _sleader+ mleader <- getsClient sleader mBlocker <- getsState $ posToAssocs target (blid b) case mBlocker of [] -> return reject@@ -870,6 +883,8 @@ mtgtMPath <- getsClient $ EM.lookup aid . stargetD lvl <- getLevel $ blid body let isAmbient pos = Tile.isLit coTileSpeedup (lvl `at` pos)+ && Tile.isWalkable coTileSpeedup (lvl `at` pos)+ -- if solid, will be altered and perhaps darkened str <- case mtgtMPath of Just TgtAndPath{tapPath=AndPath{pathList=q : _, ..}} | pathGoal == bpos body -> return reject -- shortcut and just to be sure@@ -918,7 +933,7 @@ freqs = map (liftFrequency . uniformFreq "moveTowards") groups return $! foldr (.|) reject freqs --- | Actor moves or searches or alters or attacks.+-- Actor moves or searches or alters or attacks. -- This function is very general, even though it's often used in contexts -- when only one or two of the many cases can possibly occur. moveOrRunAid :: MonadClient m
Game/LambdaHack/Client/AI/PickActorM.hs view
@@ -1,6 +1,6 @@--- | Semantics of most 'ResponseAI' client commands.+-- | Picking the AI actor to move and refreshing leader and non-leader targets. module Game.LambdaHack.Client.AI.PickActorM- ( pickActorToMove, useTactics+ ( pickActorToMove, setTargetFromTactics ) where import Prelude ()@@ -8,7 +8,7 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import Data.Ratio+import Data.Ratio import Game.LambdaHack.Client.AI.ConditionM import Game.LambdaHack.Client.AI.PickTargetM@@ -29,13 +29,13 @@ import Game.LambdaHack.Common.Time import Game.LambdaHack.Content.ModeKind --- Pick a new leader from among the actors on the current level.+-- | Pick a new leader from among the actors on the current level. -- Refresh the target of the new leader, even if unchanged. pickActorToMove :: MonadClient m => Maybe ActorId -> m ActorId {-# INLINE pickActorToMove #-} pickActorToMove maidToAvoid = do- actorAspect <- getsClient sactorAspect- mleader <- getsClient _sleader+ actorAspect <- getsState sactorAspect+ mleader <- getsClient sleader let oldAid = fromMaybe (error $ "" `showFailure` maidToAvoid) mleader oldBody <- getsState $ getActorBody oldAid let side = bfid oldBody@@ -89,10 +89,10 @@ Just ((aid, b), tgt) _ -> Nothing oursTgtRaw <- mapM refresh ours+ scondInMelee <- getsClient scondInMelee let oursTgt = mapMaybe goodGeneric oursTgtRaw -- This should be kept in sync with @actionStrategy@. actorVulnerable ((aid, body), _) = do- scondInMelee <- getsClient scondInMelee let condInMelee = fromMaybe (error $ "" `showFailure` condInMelee) (scondInMelee EM.! blid body) ar = fromMaybe (error $ "" `showFailure` aid)@@ -100,7 +100,7 @@ threatDistL <- meleeThreatDistList aid (fleeL, _) <- fleeList aid condSupport1 <- condSupport 1 aid- condSupport2 <- condSupport 2 aid+ condSupport3 <- condSupport 3 aid canDeAmbientL <- getsState $ canDeAmbientList body let condCanFlee = not (null fleeL) speed1_5 = speedScale (3%2) (bspeed body ar)@@ -122,13 +122,14 @@ canFleeFromLight = not $ null $ aCanDeLightL `intersect` map snd fleeL return $!+ -- This is a part of the condition for @flee@ in @HandleAbilityM@. not condFastThreatAdj && if | condThreat 1 -> not condCanMelee || condManyThreatAdj && not condSupport1 | not condInMelee && (condThreat 2 || condThreat 5 && canFleeFromLight) -> not condCanMelee- || not condSupport2 && not heavilyDistressed+ || not condSupport3 && not heavilyDistressed -- not used: | condThreat 5 -> False -- because actor should be picked anyway, to try to melee | otherwise ->@@ -259,21 +260,29 @@ l : _ -> do let freq = toFreq "candidates for AI leader" $ map (positiveOverhead &&& id) l- ((aid, _), _) <- rndToAction $ frequency freq+ ((aid, b), _) <- rndToAction $ frequency freq s <- getState modifyClient $ updateLeader aid s+ -- When you become a leader, stop following old leader, but follow+ -- his target, if still valid, to avoid distraction.+ let condInMelee = fromMaybe (error $ "" `showFailure` condInMelee)+ (scondInMelee EM.! blid b)+ when (ftactic (gplayer fact) `elem` [TFollow, TFollowNoItems]+ && not condInMelee) $+ void $ refreshTarget (aid, b) return aid _ -> return oldAid -useTactics :: MonadClient m => ActorId -> m ()-{-# INLINE useTactics #-}-useTactics oldAid = do+-- | Inspect the tactics of the actor and set his target according to it.+setTargetFromTactics :: MonadClient m => ActorId -> m ()+{-# INLINE setTargetFromTactics #-}+setTargetFromTactics oldAid = do+ mleader <- getsClient sleader+ let !_A = assert (mleader /= Just oldAid) () oldBody <- getsState $ getActorBody oldAid scondInMelee <- getsClient scondInMelee let condInMelee = fromMaybe (error $ "" `showFailure` condInMelee) (scondInMelee EM.! blid oldBody)- mleader <- getsClient _sleader- let !_A = assert (mleader /= Just oldAid) () let side = bfid oldBody arena = blid oldBody fact <- getsState $ (EM.! side) . sfactionD
Game/LambdaHack/Client/AI/PickTargetM.hs view
@@ -15,33 +15,33 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES -import Game.LambdaHack.Client.AI.ConditionM-import Game.LambdaHack.Client.AI.Strategy-import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Client.BfsM-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Common.Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Frequency-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Client.AI.ConditionM+import Game.LambdaHack.Client.AI.Strategy+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.BfsM+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Common.Ability+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind-import Game.LambdaHack.Content.TileKind (isUknownSpace)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Content.TileKind (isUknownSpace) -- | Verify and possibly change the target of an actor. This function both -- updates the target in the client state and returns the new target explicitly.@@ -87,12 +87,12 @@ targetStrategy aid = do Kind.COps{corule, coTileSpeedup} <- getsState scops b <- getsState $ getActorBody aid- mleader <- getsClient _sleader+ mleader <- getsClient sleader scondInMelee <- getsClient scondInMelee salter <- getsClient salter -- We assume the actor eventually becomes a leader (or has the same -- set of abilities as the leader, anyway) and set his target accordingly.- actorAspect <- getsClient sactorAspect+ actorAspect <- getsState sactorAspect let lalter = salter EM.! blid b condInMelee = fromMaybe (error $ "" `showFailure` condInMelee) (scondInMelee EM.! blid b)@@ -145,7 +145,7 @@ -- Needed for now, because AI targets and shoots enemies -- based on the path to them, not LOS to them: || EM.findWithDefault 0 AbProject actorMaxSk > 0- actorMinSk <- getsState $ actorSkills Nothing aid ar+ actorMinSk <- getsState $ actorSkills Nothing aid condCanProject <- condCanProjectM (EM.findWithDefault 0 AbProject actorMaxSk) aid condEnoughGear <- condEnoughGearM aid@@ -316,6 +316,9 @@ pickNewTarget tileAdj :: (Point -> Bool) -> Point -> Bool tileAdj f p = any f $ vicinityUnsafe p+ followingWrong permit =+ permit && (condInMelee -- in melee, stop following+ || mleader == Just aid) -- a leader, never follow updateTgt :: TgtAndPath -> m (Strategy TgtAndPath) updateTgt TgtAndPath{tapPath=NoPath} = pickNewTarget updateTgt tap@TgtAndPath{tapPath=AndPath{..},tapTgt} = case tapTgt of@@ -325,11 +328,9 @@ || not focused && not (null nearbyFoes)) -- prefers closer foes && a `notElem` map fst nearbyFoes -- old one not close enough || blid body /= blid b -- wrong level- || actorDying body -- foe already dying- || permit- && (condInMelee -- in melee, stop following- || mleader == Just aid) -> -- a leader, never follow+ || actorDying body -> -- foe already dying pickNewTarget+ | followingWrong permit -> pickNewTarget | bpos body == pathGoal -> return $! returN "TEnemy" tap -- The enemy didn't move since the target acquired.@@ -356,10 +357,7 @@ pickNewTarget -- prefer close foes to anything else TEnemyPos _ permit -- chase last position even if foe hides | bpos b == pos -> tellOthersNothingHere pos- | permit- && (condInMelee -- in melee, stop following- || mleader == Just aid) -> -- a leader, never follow- pickNewTarget -- melee, stop following+ | followingWrong permit -> pickNewTarget | otherwise -> return $! returN "TEnemyPos" tap -- Below we check the target could not be picked again in -- pickNewTarget (e.g., an item got picked up by our teammate)@@ -395,13 +393,15 @@ -- to enable pickup; if pickup fails, will retarget | otherwise -> return $! returN "TItem" tap TSmell ->- if not canSmell- || let sml = EM.findWithDefault timeZero pos (lsmell lvl)- in sml <= ltime lvl- then pickNewTarget -- others will notice soon enough- else return $! returN "TSmell" tap+ let lvl2 = sdungeon s EM.! lid+ in if not canSmell+ || let sml = EM.findWithDefault timeZero pos (lsmell lvl2)+ in sml <= ltime lvl2+ then pickNewTarget -- others will notice soon enough+ else return $! returN "TSmell" tap TUnknown ->- let t = lvl `at` pos+ let lvl2 = sdungeon s EM.! lid+ t = lvl2 `at` pos in if lidExplored || not (isUknownSpace t) || condEnoughGear && tileAdj (isStairPos lid) pos@@ -410,13 +410,19 @@ -- looks silly then pickNewTarget -- others will notice soon enough else return $! returN "TUnknown" tap- TKnown ->- if bpos b == pos- || isStuck- || alterSkill < fromEnum (lalter PointArray.! pos)- -- tile was searched or altered or skill lowered- then pickNewTarget -- others unconcerned- else return $! returN "TKnown" tap+ TKnown -> -- e.g., staircase or first unknown tile of an area+ let allExplored = ES.size explored == EM.size dungeon+ lvl2 = sdungeon s EM.! lid+ in if bpos b == pos+ || isStuck+ || alterSkill < fromEnum (lalter PointArray.! pos)+ -- tile was searched or altered or skill lowered+ || Tile.isWalkable coTileSpeedup (lvl2 `at` pos)+ && not allExplored -- not patrolling explored dungeon+ -- tile is no longer unwalkable, so was explored+ -- so time to recalculate target+ then pickNewTarget -- others unconcerned+ else return $! returN "TKnown" tap TAny -> pickNewTarget -- reset elsewhere or carried over from UI TVector{} -> if pathLen > 1 then return $! returN "TVector" tap
Game/LambdaHack/Client/AI/Strategy.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DeriveFoldable, DeriveTraversable, TupleSections #-} -- | AI strategies to direct actors not controlled directly by human players.--- No operation in this module involves the 'State' or 'Action' type.+-- No operation in this module involves the 'State' tyep or any of our+-- client/server monads types. module Game.LambdaHack.Client.AI.Strategy ( Strategy, nullStrategy, liftFrequency , (.|), reject, (.=>), only, bestVariant, renameStrategy, returN, mapStrategyM@@ -11,6 +12,7 @@ import Game.LambdaHack.Common.Prelude import Control.Applicative+import Data.Int (Int32) import Game.LambdaHack.Common.Frequency as Frequency @@ -19,12 +21,16 @@ newtype Strategy a = Strategy { runStrategy :: [Frequency a] } deriving (Show, Foldable, Traversable) --- | Strategy is a monad.+_maxBound32 :: Integer+_maxBound32 = toInteger (maxBound :: Int32)+ instance Monad Strategy where- {-# INLINE return #-}- return x = Strategy $ return $! uniformFreq "Strategy_return" [x] m >>= f = normalizeStrategy $ Strategy- [ toFreq name [ (p * q, b)+ [ toFreq name [+#ifdef WITH_EXPENSIVE_ASSERTIONS+ assert (toInteger p * toInteger q <= _maxBound32)+#endif+ (p * q, b) | (p, a) <- runFrequency x , y <- runStrategy (f a) , (q, b) <- runFrequency y@@ -36,7 +42,8 @@ fmap f (Strategy fs) = Strategy (map (fmap f) fs) instance Applicative Strategy where- pure = return+ {-# INLINE pure #-}+ pure x = Strategy $ return $! uniformFreq "Strategy_pure" [x] (<*>) = ap instance MonadPlus Strategy where
Game/LambdaHack/Client/Bfs.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}--- | Breadth first search algorithms.+-- | Breadth first search algorithm. module Game.LambdaHack.Client.Bfs ( BfsDistance, MoveLegal(..), minKnownBfs, apartBfs, fillBfs , AndPath(..), findPathBfs , accessBfs #ifdef EXPOSE_INTERNAL -- * Internal operations+ , abortedKnownBfs, abortedUnknownBfs #endif ) where @@ -13,14 +14,14 @@ import Game.LambdaHack.Common.Prelude -import Control.Monad.ST.Strict-import Data.Binary-import Data.Bits (Bits, complement, (.&.), (.|.))+import Control.Monad.ST.Strict+import Data.Binary+import Data.Bits (Bits, complement, (.&.), (.|.)) import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as VM-import GHC.Generics (Generic)+import GHC.Generics (Generic) -import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray -- | Weighted distance between points along shortest paths.
Game/LambdaHack/Client/BfsM.hs view
@@ -1,16 +1,14 @@ {-# LANGUAGE TupleSections #-}--- | Breadth first search and realted algorithms using the client monad.+-- | Breadth first search and related algorithms using the client monad. module Game.LambdaHack.Client.BfsM ( invalidateBfsAid, invalidateBfsLid, invalidateBfsAll- , createBfs, condBFS, getCacheBfsAndPath, getCacheBfs- , getCachePath, createPath- , unexploredDepth- , closestUnknown, closestSmell, furthestKnown- , FleeViaStairsOrEscape(..), embedBenefit, closestTriggers- , closestItems, closestFoes- , condEnoughGearM+ , createBfs, getCacheBfsAndPath, getCacheBfs+ , getCachePath, createPath, condBFS+ , furthestKnown, closestUnknown, closestSmell+ , FleeViaStairsOrEscape(..)+ , embedBenefit, closestTriggers, condEnoughGearM, closestItems, closestFoes #ifdef EXPOSE_INTERNAL- , updatePathFromBfs+ , unexploredDepth, updatePathFromBfs #endif ) where @@ -20,32 +18,32 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Ord-import Data.Word+import Data.Ord+import Data.Word -import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind (isUknownSpace)+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.TileKind (isUknownSpace) invalidateBfsAid :: MonadClient m => ActorId -> m () invalidateBfsAid aid =@@ -152,9 +150,9 @@ lvl <- getLevel $ blid b let stopAtUnwalkable tapPath@AndPath{..} = let (walkable, rest) =- -- Unknown tiles are not walkable, so path stops before them,- -- which is good, because by the time actor reaches them,- -- they are no longer unknown, so target invalidated.+ -- Unknown tiles are not walkable, so path stops at first such.+ -- which is good, because by the time actor reaches the tile,+ -- it is known and target is recalculated with new info. span (Tile.isWalkable coTileSpeedup . at lvl) pathList in case rest of [] -> TgtAndPath{..}@@ -278,7 +276,7 @@ embedBenefit :: MonadClient m => FleeViaStairsOrEscape -> ActorId -> [(Point, ItemBag)]- -> m [(Int, (Point, ItemBag))]+ -> m [(Double, (Point, ItemBag))] embedBenefit fleeVia aid pbags = do Kind.COps{coitem=Kind.Ops{okind}, coTileSpeedup} <- getsState scops dungeon <- getsState sdungeon@@ -293,7 +291,7 @@ unexploredTrue <- unexploredDepth True (blid b) unexploredFalse <- unexploredDepth False (blid b) condEnoughGear <- condEnoughGearM aid- discoKind <- getsClient sdiscoKind+ discoKind <- getsState sdiscoKind discoBenefit <- getsClient sdiscoBenefit s <- getState let alterMinSkill p = Tile.alterMinSkill coTileSpeedup $ lvl `at` p@@ -340,7 +338,6 @@ _ -> 0 -- don't ascend prematurely _ -> if fleeVia `elem` [ViaNothing, ViaAnything]- then -- Actor uses the embedded item on himself, hence @effApply@. -- Let distance be the deciding factor and also prevent -- overflow on 32-bit machines.@@ -383,9 +380,8 @@ let mix (benefit, ppbag) dist = let maxd = fromEnum (maxBound :: BfsDistance) - fromEnum apartBfs- -- Bewqre of overflowing 32-bit integers here.- v = (maxd * 10) `div` (dist + 1)- in (benefit * v, ppbag)+ v = (fromIntegral maxd * 10) / (fromIntegral dist + 1)+ in (ceiling $ benefit * v, ppbag) in mapMaybe (\bpp@(_, (p, _)) -> mix bpp <$> accessBfs bfs p) vicAll
+ Game/LambdaHack/Client/ClientOptions.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE DeriveGeneric #-}+-- | Options that affect the behaviour of the client.+module Game.LambdaHack.Client.ClientOptions+ ( ClientOptions(..), defClientOptions+ ) where++import Prelude ()++import Game.LambdaHack.Common.Prelude++import Data.Binary+import GHC.Generics (Generic)++-- | Options that affect the behaviour of the client (but not game rules).+data ClientOptions = ClientOptions+ { sgtkFontFamily :: Maybe Text+ -- ^ Font family to use for the GTK main game window.+ , sdlFontFile :: Maybe Text+ -- ^ Font file to use for the SDL2 main game window.+ , sdlTtfSizeAdd :: Maybe Int+ -- ^ Pixels to add to map cells on top of scalable font max glyph height.+ , sdlFonSizeAdd :: Maybe Int+ -- ^ Pixels to add to map cells on top of .fon font max glyph height.+ , sfontSize :: Maybe Int+ -- ^ Font size to use for the main game window.+ , scolorIsBold :: Maybe Bool+ -- ^ Whether to use bold attribute for colorful characters.+ , smaxFps :: Maybe Int+ -- ^ Maximal frames per second.+ -- This is better low and fixed, to avoid jerkiness and delays+ -- that tell the player there are many intelligent enemies on the level.+ -- That's better than scaling AI sofistication down based+ -- on the FPS setting and machine speed.+ , sdisableAutoYes :: Bool+ -- ^ Never auto-answer all prompts, even if under AI control.+ , snoAnim :: Maybe Bool+ -- ^ Don't show any animations.+ , snewGameCli :: Bool+ -- ^ Start a new game, overwriting the save file.+ , sbenchmark :: Bool+ -- ^ Don't create directories and files and show time stats.+ , stitle :: Maybe Text+ , sfontDir :: Maybe FilePath+ , ssavePrefixCli :: String+ -- ^ Prefix of the save game file name.+ , sfrontendTeletype :: Bool+ -- ^ Whether to use the stdout/stdin frontend.+ , sfrontendNull :: Bool+ -- ^ Whether to use null (no input/output) frontend.+ , sfrontendLazy :: Bool+ -- ^ Whether to use lazy (output not even calculated) frontend.+ , sdbgMsgCli :: Bool+ -- ^ Show clients' internal debug messages.+ , sstopAfterSeconds :: Maybe Int+ , sstopAfterFrames :: Maybe Int+ }+ deriving (Show, Eq, Generic)++instance Binary ClientOptions++-- | Default value of client options.+defClientOptions :: ClientOptions+defClientOptions = ClientOptions+ { sgtkFontFamily = Nothing+ , sdlFontFile = Nothing+ , sdlTtfSizeAdd = Nothing+ , sdlFonSizeAdd = Nothing+ , sfontSize = Nothing+ , scolorIsBold = Nothing+ , smaxFps = Nothing+ , sdisableAutoYes = False+ , snoAnim = Nothing+ , snewGameCli = False+ , sbenchmark = False+ , stitle = Nothing+ , sfontDir = Nothing+ , ssavePrefixCli = ""+ , sfrontendTeletype = False+ , sfrontendNull = False+ , sfrontendLazy = False+ , sdbgMsgCli = False+ , sstopAfterSeconds = Nothing+ , sstopAfterFrames = Nothing+ }
Game/LambdaHack/Client/CommonM.hs view
@@ -2,9 +2,8 @@ -- | Common client monad operations. module Game.LambdaHack.Client.CommonM ( getPerFid, aidTgtToPos, makeLine- , maxActorSkillsClient, currentSkillsClient, fullAssocsClient- , itemToFullClient, pickWeaponClient, updateSalter, createSalter- , aspectRecordFromItemClient, aspectRecordFromActorClient, createSactorAspect+ , maxActorSkillsClient, currentSkillsClient, pickWeaponClient+ , updateSalter, createSalter ) where import Prelude ()@@ -13,26 +12,26 @@ import qualified Data.EnumMap.Strict as EM -import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Request+import Game.LambdaHack.Client.State import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.TileKind (TileKind, isUknownSpace)+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.TileKind (TileKind, isUknownSpace) -- | Get the current perception of a client. getPerFid :: MonadClient m => LevelId -> m Perception@@ -43,7 +42,8 @@ return $! EM.findWithDefault assFail lid fper -- | Calculate the position of an actor's target.-aidTgtToPos :: MonadClient m => ActorId -> LevelId -> Target -> m (Maybe Point)+aidTgtToPos :: MonadStateRead m+ => ActorId -> LevelId -> Target -> m (Maybe Point) aidTgtToPos aid lidV tgt = case tgt of TEnemy a _ -> do@@ -65,7 +65,7 @@ -- an actor or obstacle. Starts searching with the given eps and returns -- the first found eps for which the number reaches the distance between -- actor and target position, or Nothing if none can be found.-makeLine :: MonadClient m => Bool -> Actor -> Point -> Int -> m (Maybe Int)+makeLine :: MonadStateRead m => Bool -> Actor -> Point -> Int -> m (Maybe Int) makeLine onlyFirst body fpos epsOld = do Kind.COps{coTileSpeedup} <- getsState scops lvl@Level{lxsize, lysize} <- getLevel (blid body)@@ -73,7 +73,7 @@ let dist = chessDist (bpos body) fpos calcScore eps = case bla lxsize lysize eps (bpos body) fpos of Just bl ->- let blDist = take dist bl+ let blDist = take (dist - 1) bl -- goal not checked; actor well aware noActor p = all (bproj . snd) (posA p) || p == fpos accessibleUnknown tpos = let tt = lvl `at` tpos@@ -101,44 +101,23 @@ | otherwise -> tryLines (epsOld + 1) (Nothing, minBound) -- generate best +-- @MonadStateRead@ would be enough, but the logic is sound only on client. maxActorSkillsClient :: MonadClient m => ActorId -> m Ability.Skills maxActorSkillsClient aid = do- actorAspect <- getsClient sactorAspect- case EM.lookup aid actorAspect of- Just aspectRecord -> return $ aSkills aspectRecord -- keep it lazy- Nothing -> error $ "" `showFailure` aid+ ar <- getsState $ getActorAspect aid+ return $ aSkills ar -- keep it lazy currentSkillsClient :: MonadClient m => ActorId -> m Ability.Skills currentSkillsClient aid = do- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect) body <- getsState $ getActorBody aid side <- getsClient sside- -- Newest Leader in _sleader, not yet in sfactionD.+ -- Newest Leader in sleader, not yet in sfactionD. mleader <- if side == bfid body- then getsClient _sleader+ then getsClient sleader else do fact <- getsState $ (EM.! bfid body) . sfactionD- return $! _gleader fact- getsState $ actorSkills mleader aid ar -- keep it lazy--fullAssocsClient :: MonadClient m- => ActorId -> [CStore] -> m [(ItemId, ItemFull)]-fullAssocsClient aid cstores = do- cops <- getsState scops- discoKind <- getsClient sdiscoKind- discoAspect <- getsClient sdiscoAspect- getsState $ fullAssocs cops discoKind discoAspect aid cstores--itemToFullClient :: MonadClient m => m (ItemId -> ItemQuant -> ItemFull)-itemToFullClient = do- cops <- getsState scops- discoKind <- getsClient sdiscoKind- discoAspect <- getsClient sdiscoAspect- s <- getState- let itemToF iid = itemToFull cops discoKind discoAspect iid- (getItemBody iid s)- return itemToF+ return $! gleader fact+ getsState $ actorSkills mleader aid -- keep it lazy -- Client has to choose the weapon based on its partial knowledge, -- because if server chose it, it would leak item discovery information.@@ -146,15 +125,13 @@ => ActorId -> ActorId -> m (Maybe (RequestTimed 'Ability.AbMelee)) pickWeaponClient source target = do- eqpAssocs <- fullAssocsClient source [CEqp]- bodyAssocs <- fullAssocsClient source [COrgan]+ eqpAssocs <- getsState $ fullAssocs source [CEqp]+ bodyAssocs <- getsState $ fullAssocs source [COrgan] actorSk <- currentSkillsClient source- actorAspect <- getsClient sactorAspect let allAssocsRaw = eqpAssocs ++ bodyAssocs allAssocs = filter (isMelee . itemBase . snd) allAssocsRaw discoBenefit <- getsClient sdiscoBenefit- strongest <- pickWeaponM (Just discoBenefit)- allAssocs actorSk actorAspect source+ strongest <- pickWeaponM (Just discoBenefit) allAssocs actorSk source case strongest of [] -> return Nothing iis@((maxS, _) : _) -> do@@ -177,45 +154,3 @@ f Level{ltile} = PointArray.mapA (toEnum . Tile.alterMinWalk coTileSpeedup) ltile in EM.map f $ sdungeon s--aspectRecordFromItem :: DiscoveryKind -> DiscoveryAspect -> ItemId -> Item- -> AspectRecord-aspectRecordFromItem disco discoAspect iid itemBase =- case EM.lookup iid discoAspect of- Just ar -> ar- Nothing -> case EM.lookup (jkindIx itemBase) disco of- Just KindMean{kmMean} -> kmMean- Nothing -> emptyAspectRecord--aspectRecordFromItemClient :: MonadClient m => ItemId -> Item -> m AspectRecord-aspectRecordFromItemClient iid itemBase = do- disco <- getsClient sdiscoKind- discoAspect <- getsClient sdiscoAspect- return $! aspectRecordFromItem disco discoAspect iid itemBase--aspectRecordFromActorState :: DiscoveryKind -> DiscoveryAspect -> Actor -> State- -> AspectRecord-aspectRecordFromActorState disco discoAspect b s =- let processIid (iid, (k, _)) =- let itemBase = getItemBody iid s- ar = aspectRecordFromItem disco discoAspect iid itemBase- in (ar, k)- processBag ass = sumAspectRecord $ map processIid ass- in processBag $ EM.assocs (borgan b) ++ EM.assocs (beqp b)--aspectRecordFromActorClient :: MonadClient m- => Actor -> [(ItemId, Item)] -> m AspectRecord-aspectRecordFromActorClient b ais = do- disco <- getsClient sdiscoKind- discoAspect <- getsClient sdiscoAspect- s <- getState- let f (iid, itemBase) = EM.insert iid itemBase- sAis = updateItemD (\itemD -> foldr f itemD ais) s- return $! aspectRecordFromActorState disco discoAspect b sAis--createSactorAspect :: MonadClient m => State -> m ActorAspect-createSactorAspect s = do- disco <- getsClient sdiscoKind- discoAspect <- getsClient sdiscoAspect- let f b = aspectRecordFromActorState disco discoAspect b s- return $! EM.map f $ sactorD s
Game/LambdaHack/Client/HandleAtomicM.hs view
@@ -1,6 +1,14 @@ -- | Handle atomic commands received by the client. module Game.LambdaHack.Client.HandleAtomicM- ( cmdAtomicSemCli, cmdAtomicFilterCli+ ( MonadClientSetup(..)+ , cmdAtomicSemCli+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , wipeBfsIfItemAffectsSkills, tileChangeAffectsBfs, createActor, destroyActor+ , addItemToDiscoBenefit, perception+ , discoverKind, coverKind, discoverSeed, coverSeed+ , killExit+#endif ) where import Prelude ()@@ -8,268 +16,75 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import qualified Data.EnumSet as ES import qualified Data.Map.Strict as M-import Data.Ord-import qualified NLP.Miniutter.English as MU+import Data.Ord -import Game.LambdaHack.Atomic-import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Client.BfsM-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.Preferences-import Game.LambdaHack.Client.State-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.BfsM+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Preferences+import Game.LambdaHack.Client.State+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind (ModeKind)-import Game.LambdaHack.Content.TileKind (TileKind)-import qualified Game.LambdaHack.Content.TileKind as TK---- * RespUpdAtomicAI+import Game.LambdaHack.Content.ModeKind (ModeKind)+import Game.LambdaHack.Content.TileKind (TileKind) --- | Clients keep a subset of atomic commands sent by the server--- and add some of their own. The result of this function is the list--- of commands kept for each command received.-cmdAtomicFilterCli :: MonadClient m => UpdAtomic -> m [UpdAtomic]-{-# INLINE cmdAtomicFilterCli #-}-cmdAtomicFilterCli cmd = case cmd of- UpdSpotActor aid _ _ -> do- -- Needed, e.g., when we teleport and so see our actor at the new- -- location, but also the location is part of new perception,- -- so @UpdSpotActor@ is sent.- alreadyAdded <- getsState $ EM.member aid . sactorD- return $! if alreadyAdded then [] else [cmd]- UpdAlterTile lid p fromTile toTile -> do- Kind.COps{cotile=Kind.Ops{okind}} <- getsState scops- lvl <- getLevel lid- let t = lvl `at` p- if t == fromTile- then return [cmd]- else do- -- From @UpdAlterTile@ we know @t == freshClientTile@,- -- which is uncanny, so we produce a message.- -- It happens when a client thinks the tile is @t@,- -- but it's @fromTile@, and @UpdAlterTile@ changes it- -- to @toTile@. See @updAlterTile@.- let subject = "" -- a hack, we we don't handle adverbs well- verb = "turn into"- msg = makeSentence [ "the", MU.Text $ TK.tname $ okind t- , "at position", MU.Text $ tshow p- , "suddenly" -- adverb- , MU.SubjectVerbSg subject verb- , MU.AW $ MU.Text $ TK.tname $ okind toTile ]- return [ cmd -- reveal the tile- , UpdMsgAll msg -- show the message- ]- UpdSearchTile aid p toTile -> do- Kind.COps{cotile} <- getsState scops- b <- getsState $ getActorBody aid- lvl <- getLevel $ blid b- let t = lvl `at` p- fromTile = Tile.hideAs cotile toTile- return $!- if t == fromTile- then -- Fully ignorant. (No intermediate knowledge possible.)- [ cmd -- show the message- , UpdAlterTile (blid b) p fromTile toTile -- reveal tile- ]- else assert (t == toTile `blame` "LoseTile fails to reset memory"- `swith` (aid, p, fromTile, toTile, b, t, cmd))- [cmd] -- Already knows the tile fully, only confirm.- UpdHideTile{} -> return [] -- will be fleshed out when Undo completed- UpdSpotTile lid ts -> do- Kind.COps{cotile} <- getsState scops- lvl <- getLevel lid- -- We ignore the server resending us hidden versions of the tiles- -- (and resending us the same data we already got).- -- If the tiles are changed to other variants of the hidden tile,- -- we can still verify by searching.- let notKnown (p, t) = let tClient = lvl `at` p- in Tile.hideAs cotile tClient /= t- newTs = filter notKnown ts- return $! if null newTs then [] else [UpdSpotTile lid newTs]- UpdDiscover c iid _ seed -> do- itemD <- getsState sitemD- case EM.lookup iid itemD of- Nothing -> return []- Just item -> do- discoKind <- getsClient sdiscoKind- if jkindIx item `EM.member` discoKind- then do- discoAspect <- getsClient sdiscoAspect- if iid `EM.member` discoAspect- then return []- else return [UpdDiscoverSeed c iid seed]- else return [cmd]- UpdCover c iid ik _ -> do- itemD <- getsState sitemD- case EM.lookup iid itemD of- Nothing -> return []- Just item -> do- discoKind <- getsClient sdiscoKind- if jkindIx item `EM.notMember` discoKind- then return []- else do- discoAspect <- getsClient sdiscoAspect- if iid `EM.notMember` discoAspect- then return [cmd]- else return [UpdCoverKind c iid ik]- UpdDiscoverKind _ iid _ -> do- itemD <- getsState sitemD- case EM.lookup iid itemD of- Nothing -> return []- Just item -> do- discoKind <- getsClient sdiscoKind- if jkindIx item `EM.notMember` discoKind- then return []- else return [cmd]- UpdCoverKind _ iid _ -> do- itemD <- getsState sitemD- case EM.lookup iid itemD of- Nothing -> return []- Just item -> do- discoKind <- getsClient sdiscoKind- if jkindIx item `EM.notMember` discoKind- then return []- else return [cmd]- UpdDiscoverSeed _ iid _ -> do- itemD <- getsState sitemD- case EM.lookup iid itemD of- Nothing -> return []- Just item -> do- discoKind <- getsClient sdiscoKind- if jkindIx item `EM.notMember` discoKind- then return []- else do- discoAspect <- getsClient sdiscoAspect- if iid `EM.member` discoAspect- then return []- else return [cmd]- UpdCoverSeed _ iid _ -> do- itemD <- getsState sitemD- case EM.lookup iid itemD of- Nothing -> return []- Just item -> do- discoKind <- getsClient sdiscoKind- if jkindIx item `EM.notMember` discoKind- then return []- else do- discoAspect <- getsClient sdiscoAspect- if iid `EM.notMember` discoAspect- then return []- else return [cmd]- UpdPerception lid outPer inPer -> do- -- Here we cheat by setting a new perception outright instead of- -- in @cmdAtomicSemCli@, to avoid computing perception twice.- perOld <- getPerFid lid- perception lid outPer inPer- perNew <- getPerFid lid- carriedAssocs <- getsState $ flip getCarriedAssocs- fid <- getsClient sside- s <- getState- -- Wipe out actors that just became invisible due to changed FOV.- let seenNew = seenAtomicCli False fid perNew- seenOld = seenAtomicCli False fid perOld- outFov = totalVisible outPer- outPrio = concatMap (\p -> posToAssocs p lid s) $ ES.elems outFov- fActor (aid, b) =- let ps = posProjBody b- -- Verify that we forget only previously seen actors.- !_A = assert (seenOld ps) ()- in -- We forget only currently invisible actors.- if seenNew ps- then Nothing- else Just $ UpdLoseActor aid b $ carriedAssocs b- outActor = mapMaybe fActor outPrio- -- Wipe out remembered items on tiles that now came into view.- lvl <- getLevel lid- let inFov = ES.elems $ totalVisible inPer- pMaybe p = maybe Nothing (\x -> Just (p, x))- inContainer fc itemFloor =- let inItem = mapMaybe (\p -> pMaybe p $ EM.lookup p itemFloor) inFov- fItem p (iid, kit) =- UpdLoseItem True iid (getItemBody iid s) kit (fc lid p)- fBag (p, bag) = map (fItem p) $ EM.assocs bag- in concatMap fBag inItem- inFloor = inContainer CFloor (lfloor lvl)- inEmbed = inContainer CEmbed (lembed lvl)- -- Remembered map tiles not wiped out, due to optimization in @updSpotTile@.- -- Wipe out remembered smell on tiles that now came into smell Fov.- let inSmellFov = totalSmelled inPer- inSm = mapMaybe (\p -> pMaybe p $ EM.lookup p (lsmell lvl))- (ES.elems inSmellFov)- inSmell = if null inSm then [] else [UpdLoseSmell lid inSm]- let inTileSmell = inFloor ++ inEmbed ++ inSmell- psItemSmell <- mapM posUpdAtomic inTileSmell- -- Verify that we forget only previously invisible items and smell.- let !_A = assert (allB (not . seenOld) psItemSmell) ()- -- Verify that we forget only currently seen items and smell.- let !_A = assert (allB seenNew psItemSmell) ()- return $! cmd : outActor ++ inTileSmell- _ -> return [cmd]+-- | Client monad for saving and restarting games.+class MonadClient m => MonadClientSetup m where+ saveClient :: m ()+ restartClient :: m () --- | Effect of atomic actions on client state is calculated--- with the global state from before the command is executed.-cmdAtomicSemCli :: MonadClientSetup m => UpdAtomic -> m ()+-- | Effect of atomic actions on client state. It is calculated+-- with the global state from after the command is executed+-- (except where the supplied @oldState@ is used).+cmdAtomicSemCli :: MonadClientSetup m => State -> UpdAtomic -> m () {-# INLINE cmdAtomicSemCli #-}-cmdAtomicSemCli cmd = case cmd of+cmdAtomicSemCli oldState cmd = case cmd of UpdCreateActor aid b ais -> createActor aid b ais UpdDestroyActor aid b _ -> destroyActor aid b True- UpdCreateItem iid itemBase (k, _) (CActor aid store) -> do+ UpdCreateItem iid itemBase _ (CActor aid store) -> do wipeBfsIfItemAffectsSkills [store] aid- when (store `elem` [CEqp, COrgan]) $ addItemToActor iid itemBase k aid addItemToDiscoBenefit iid itemBase UpdCreateItem iid itemBase _ _ -> addItemToDiscoBenefit iid itemBase- UpdDestroyItem iid itemBase (k, _) (CActor aid store) -> do+ UpdDestroyItem _ _ _ (CActor aid store) -> wipeBfsIfItemAffectsSkills [store] aid- when (store `elem` [CEqp, COrgan]) $ addItemToActor iid itemBase (-k) aid UpdSpotActor aid b ais -> createActor aid b ais UpdLoseActor aid b _ -> destroyActor aid b False- UpdSpotItem _ iid itemBase (k, _) (CActor aid store) -> do+ UpdSpotItem _ iid itemBase _ (CActor aid store) -> do wipeBfsIfItemAffectsSkills [store] aid- when (store `elem` [CEqp, COrgan]) $ addItemToActor iid itemBase k aid addItemToDiscoBenefit iid itemBase UpdSpotItem _ iid itemBase _ _ -> addItemToDiscoBenefit iid itemBase- UpdLoseItem _ iid itemBase (k, _) (CActor aid store) -> do+ UpdLoseItem _ _ _ _ (CActor aid store) -> wipeBfsIfItemAffectsSkills [store] aid- when (store `elem` [CEqp, COrgan]) $ addItemToActor iid itemBase (-k) aid+ UpdSpotItemBag (CActor aid store) _bag ais -> do+ wipeBfsIfItemAffectsSkills [store] aid+ mapM_ (\(iid, item) -> addItemToDiscoBenefit iid item) ais+ UpdSpotItemBag _ _ ais ->+ mapM_ (\(iid, item) -> addItemToDiscoBenefit iid item) ais+ UpdLoseItemBag (CActor aid store) _bag _ais ->+ wipeBfsIfItemAffectsSkills [store] aid UpdMoveActor aid _ _ -> invalidateBfsAid aid UpdDisplaceActor source target -> do invalidateBfsAid source invalidateBfsAid target- UpdMoveItem iid k aid s1 s2 -> do- wipeBfsIfItemAffectsSkills [s1, s2] aid- case s1 of- CEqp -> case s2 of- COrgan -> return ()- _ -> do- itemBase <- getsState $ getItemBody iid- addItemToActor iid itemBase (-k) aid- COrgan -> case s2 of- CEqp -> return ()- _ -> do- itemBase <- getsState $ getItemBody iid- addItemToActor iid itemBase (-k) aid- _ ->- when (s2 `elem` [CEqp, COrgan]) $ do- itemBase <- getsState $ getItemBody iid- addItemToActor iid itemBase k aid+ UpdMoveItem _ _ aid s1 s2 -> wipeBfsIfItemAffectsSkills [s1, s2] aid UpdLeadFaction fid source target -> do side <- getsClient sside when (side == fid) $ do- mleader <- getsClient _sleader+ mleader <- getsClient sleader let !_A = assert (mleader == source -- somebody changed the leader for us || mleader == target@@ -282,7 +97,7 @@ invalidateBfsAll UpdTacticFaction{} -> do -- Clear all targets except the leader's.- mleader <- getsClient _sleader+ mleader <- getsClient sleader mtgt <- case mleader of Nothing -> return Nothing Just leader -> getsClient $ EM.lookup leader . stargetD@@ -290,19 +105,35 @@ cli { stargetD = case (mtgt, mleader) of (Just tgt, Just leader) -> EM.singleton leader tgt _ -> EM.empty }- UpdAlterTile lid pos _fromTile toTile -> do- updateSalter lid [(pos, toTile)]+ UpdAlterTile lid p fromTile toTile -> do+ updateSalter lid [(p, toTile)] cops <- getsState scops- lvl <- getLevel lid- let assumedTile = lvl `at` pos- when (tileChangeAffectsBfs cops assumedTile toTile) $+ let lvl = (EM.! lid) . sdungeon $ oldState+ t = lvl `at` p+ let !_A = assert (t == fromTile) ()+ when (tileChangeAffectsBfs cops fromTile toTile) $ invalidateBfsLid lid+ UpdSearchTile aid p toTile -> do+ Kind.COps{cotile} <- getsState scops+ b <- getsState $ getActorBody aid+ let lid = blid b+ updateSalter lid [(p, toTile)]+ cops <- getsState scops+ let lvl = (EM.! lid) . sdungeon $ oldState+ t = lvl `at` p+ let !_A = assert (Just t == Tile.hideAs cotile toTile) ()+ -- The following check is needed even if we verity in content+ -- that searching doesn't change clarity and light of tiles,+ -- because it modifies skill needed to alter the tile and even+ -- walkability and changeability.+ when (tileChangeAffectsBfs cops t toTile) $+ invalidateBfsLid lid UpdSpotTile lid ts -> do updateSalter lid ts cops <- getsState scops- lvl <- getLevel lid- let affects (pos, toTile) =- let fromTile = lvl `at` pos+ let lvl = (EM.! lid) . sdungeon $ oldState+ affects (p, toTile) =+ let fromTile = lvl `at` p in tileChangeAffectsBfs cops fromTile toTile bs = map affects ts when (or bs) $ invalidateBfsLid lid@@ -315,17 +146,19 @@ let g !em !lid = EM.adjust (const Nothing) lid em in cli {scondInMelee = foldl' g (scondInMelee cli) arenas} UpdDiscover c iid ik seed -> do- discoverKind c iid ik+ item <- getsState $ getItemBody iid+ discoverKind c (jkindIx item) ik discoverSeed c iid seed UpdCover c iid ik seed -> do coverSeed c iid seed- coverKind c iid ik- UpdDiscoverKind c iid ik -> discoverKind c iid ik- UpdCoverKind c iid ik -> coverKind c iid ik+ item <- getsState $ getItemBody iid+ coverKind c (jkindIx item) ik+ UpdDiscoverKind c ix ik -> discoverKind c ix ik+ UpdCoverKind c ix ik -> coverKind c ix ik UpdDiscoverSeed c iid seed -> discoverSeed c iid seed UpdCoverSeed c iid seed -> coverSeed c iid seed- -- UpdPerception lid outPer inPer -> perception lid outPer inPer- UpdRestart side sdiscoKind sfper s scurChal sdebugCli -> do+ UpdPerception lid outPer inPer -> perception lid outPer inPer+ UpdRestart side sfper s scurChal soptions -> do Kind.COps{comode=Kind.Ops{ofoldlGroup'}} <- getsState scops snxtChal <- getsClient snxtChal svictories <- getsClient svictories@@ -337,19 +170,16 @@ Just cm -> fromMaybe 0 (M.lookup snxtChal cm) (snxtScenario, _) = minimumBy (comparing g) modes cli = emptyStateClient side- putClient cli { sdiscoKind- , sfper+ putClient cli { sfper -- , sundo = [UpdAtomic cmd] , scurChal , snxtChal , snxtScenario , scondInMelee = EM.map (const Nothing) (sdungeon s) , svictories- , sdebugCli }- modifyClient $ \cli1 -> cli1 {salter = createSalter s}- -- Currently always void, because no actors yet:- sactorAspect <- createSactorAspect s- modifyClient $ \cli1 -> cli1 {sactorAspect}+ , soptions }+ salter <- getsState createSalter+ modifyClient $ \cli1 -> cli1 {salter} restartClient UpdResume _fid sfperNew -> do #ifdef WITH_EXPENSIVE_ASSERTIONS@@ -357,10 +187,8 @@ let !_A = assert (sfperNew == sfperOld `blame` (sfperNew, sfperOld)) () #endif modifyClient $ \cli -> cli {sfper=sfperNew}- s <- getState- modifyClient $ \cli -> cli {salter = createSalter s}- sactorAspect <- createSactorAspect s- modifyClient $ \cli -> cli {sactorAspect}+ salter <- getsState createSalter+ modifyClient $ \cli -> cli {salter} UpdKillExit _fid -> killExit UpdWriteSave -> saveClient _ -> return ()@@ -386,9 +214,6 @@ TgtAndPath (TEnemy a newPermit) NoPath _ -> tap modifyClient $ \cli -> cli {stargetD = EM.map affect3 (stargetD cli)}- aspectRecord <- aspectRecordFromActorClient b ais- let f = EM.insert aid aspectRecord- modifyClient $ \cli -> cli {sactorAspect = f $ sactorAspect cli} mapM_ (uncurry addItemToDiscoBenefit) ais destroyActor :: MonadClient m => ActorId -> Actor -> Bool -> m ()@@ -411,30 +236,23 @@ _ -> tapPath -- foe slow enough, so old path good in TgtAndPath (affect tapTgt) newMPath modifyClient $ \cli -> cli {stargetD = EM.map affect3 (stargetD cli)}- let f = EM.delete aid- modifyClient $ \cli -> cli {sactorAspect = f $ sactorAspect cli} -addItemToActor :: MonadClient m => ItemId -> Item -> Int -> ActorId -> m ()-addItemToActor iid itemBase k aid = do- arItem <- aspectRecordFromItemClient iid itemBase- let g arActor = sumAspectRecord [(arActor, 1), (arItem, k)]- f = EM.adjust g aid- modifyClient $ \cli -> cli {sactorAspect = f $ sactorAspect cli}- addItemToDiscoBenefit :: MonadClient m => ItemId -> Item -> m () addItemToDiscoBenefit iid item = do cops@Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops discoBenefit <- getsClient sdiscoBenefit case EM.lookup iid discoBenefit of- Just{} -> return () -- already there+ Just{} -> return () -- already there, possibly with real aspects, too Nothing -> do- discoKind <- getsClient sdiscoKind+ discoKind <- getsState sdiscoKind case EM.lookup (jkindIx item) discoKind of Nothing -> return () Just KindMean{..} -> do -- possible, if the kind sent in @UpdRestart@ side <- getsClient sside fact <- getsState $ (EM.! side) . sfactionD let effects = IK.ieffects $ okind kmKind+ -- Mean aspects are used, because the item can't yet have+ -- know real aspects, because it didn't even have kind before. benefit = totalUsefulness cops fact effects kmMean item modifyClient $ \cli -> cli {sdiscoBenefit = EM.insert iid benefit (sdiscoBenefit cli)}@@ -464,90 +282,52 @@ f = EM.alter adj lid modifyClient $ \cli -> cli {sfper = f (sfper cli)} -discoverKind :: MonadClient m => Container -> ItemId -> Kind.Id ItemKind -> m ()-discoverKind c iid kmKind = do+discoverKind :: MonadClient m+ => Container -> ItemKindIx -> Kind.Id ItemKind -> m ()+discoverKind _c ix ik = do cops@Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops -- Wipe out BFS, because the player could potentially learn that his items -- affect his actors' skills relevant to BFS. invalidateBfsAll side <- getsClient sside fact <- getsState $ (EM.! side) . sfactionD- item <- getsState $ getItemBody iid- let kind = okind kmKind- kmMean = meanAspect kind- benefit = totalUsefulness cops fact (IK.ieffects kind) kmMean item- f Nothing = Just KindMean{..}- f Just{} = error $ "already discovered"- `showFailure` (c, iid, kmKind)- -- This adds to @sdiscoBenefit@ only @iid@ and not any other items- -- that share the same @jkindIx@, so this is broken if such items- -- are not fully IDed from the start.- modifyClient $ \cli ->- cli { sdiscoKind = EM.alter f (jkindIx item) (sdiscoKind cli)- , sdiscoBenefit = EM.insert iid benefit (sdiscoBenefit cli) }- -- Each actor's equipment and organs would need to be inspected,- -- the iid looked up, e.g., if it wasn't in old discoKind, but is in new,- -- and then aspect record updated, so it's simpler and not much more- -- expensive to generate new sactorAspect. Optimize only after profiling.- s <- getState- sactorAspect <- createSactorAspect s- modifyClient $ \cli -> cli {sactorAspect}+ discoKind <- getsState sdiscoKind+ itemD <- getsState sitemD+ let effs = IK.ieffects $ okind ik+ KindMean{kmMean} = fromMaybe (error "item kind not found")+ (EM.lookup ix discoKind)+ benefit iid =+ let item = itemD EM.! iid+ in totalUsefulness cops fact effs kmMean item+ itemIxMap <- getsState $ (EM.! ix) . sitemIxMap+ forM_ itemIxMap $ \iid -> modifyClient $ \cli ->+ cli {sdiscoBenefit = EM.insert iid (benefit iid) (sdiscoBenefit cli)} -coverKind :: MonadClient m => Container -> ItemId -> Kind.Id ItemKind -> m ()-coverKind c iid ik = do- item <- getsState $ getItemBody iid- let f Nothing = error $ "already covered" `showFailure` (c, iid, ik)- f (Just KindMean{kmKind}) =- assert (ik == kmKind `blame` "unexpected covered item kind"- `swith` (ik, kmKind)) Nothing- -- For now, undoing @sdiscoBenefit@ is too much work.- modifyClient $ \cli ->- cli {sdiscoKind = EM.alter f (jkindIx item) (sdiscoKind cli)}- s <- getState- sactorAspect <- createSactorAspect s- modifyClient $ \cli -> cli {sactorAspect}+coverKind :: Container -> ItemKindIx -> Kind.Id ItemKind -> m ()+coverKind _c _iid _ik = undefined discoverSeed :: MonadClient m => Container -> ItemId -> ItemSeed -> m ()-discoverSeed c iid seed = do+discoverSeed _c iid seed = do cops@Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops -- Wipe out BFS, because the player could potentially learn that his items -- affect his actors' skills relevant to BFS. invalidateBfsAll side <- getsClient sside fact <- getsState $ (EM.! side) . sfactionD- discoKind <- getsClient sdiscoKind+ discoKind <- getsState sdiscoKind item <- getsState $ getItemBody iid totalDepth <- getsState stotalDepth- case EM.lookup (jkindIx item) discoKind of- Nothing -> error $ "kind not known"- `showFailure` (c, iid, seed)- Just KindMean{kmKind} -> do- Level{ldepth} <- getLevel $ jlid item- let kind = okind kmKind- aspects = seedToAspect seed kind ldepth totalDepth- benefit = totalUsefulness cops fact (IK.ieffects kind) aspects item- f Nothing = Just aspects- f Just{} = error $ "already discovered"- `showFailure` (c, iid, seed)- -- This adds to @sdiscoBenefit@ only @iid@ and not any other items- -- that share the same @jkindIx@, so this is broken if such items- -- are not fully IDed from the start.- modifyClient $ \cli ->- cli { sdiscoAspect = EM.alter f iid (sdiscoAspect cli)- , sdiscoBenefit = EM.insert iid benefit (sdiscoBenefit cli) }- s <- getState- sactorAspect <- createSactorAspect s- modifyClient $ \cli -> cli {sactorAspect}+ Level{ldepth} <- getLevel $ jlid item+ let KindMean{..} = fromMaybe (error "item kind not found")+ (EM.lookup (jkindIx item) discoKind)+ kind = okind kmKind+ aspects = seedToAspect seed kind ldepth totalDepth+ benefit = totalUsefulness cops fact (IK.ieffects kind) aspects item+ modifyClient $ \cli ->+ cli {sdiscoBenefit = EM.insert iid benefit (sdiscoBenefit cli)} -coverSeed :: MonadClient m => Container -> ItemId -> ItemSeed -> m ()-coverSeed c iid seed = do- let f Nothing = error $ "already covered" `showFailure` (c, iid, seed)- f Just{} = Nothing -- checking that old and new agree is too much work- -- For now, undoing @sdiscoBenefit@ is too much work.- modifyClient $ \cli -> cli {sdiscoAspect = EM.alter f iid (sdiscoAspect cli)}- s <- getState- sactorAspect <- createSactorAspect s- modifyClient $ \cli -> cli {sactorAspect}+coverSeed :: Container -> ItemId -> ItemSeed -> m ()+coverSeed _c _iid _seed = undefined killExit :: MonadClient m => m () killExit = do@@ -556,12 +336,11 @@ modifyClient $ \cli -> cli {squit = True} -- Verify that the not saved caches are equal to future reconstructed. -- Otherwise, save/restore would change game state.- sactorAspect <- getsClient sactorAspect+ sactorAspect2 <- getsState sactorAspect salter <- getsClient salter sbfsD <- getsClient sbfsD- s <- getState- let alter = createSalter s- actorAspect <- createSactorAspect s+ alter <- getsState createSalter+ actorAspect <- getsState actorAspectInDungeon let f aid = do (canMove, alterSkill) <- condBFS aid bfsArr <- createBfs canMove alterSkill aid@@ -579,9 +358,9 @@ let !_A1 = assert (salter == alter `blame` "wrong accumulated salter on side" `swith` (side, salter, alter)) ()- !_A2 = assert (sactorAspect == actorAspect+ !_A2 = assert (sactorAspect2 == actorAspect `blame` "wrong accumulated sactorAspect on side"- `swith` (side, sactorAspect, actorAspect)) ()+ `swith` (side, sactorAspect2, actorAspect)) () !_A3 = assert (sbfsD `subBfs` bfsD `blame` "wrong accumulated sbfsD on side" `swith` (side, sbfsD, bfsD)) ()
Game/LambdaHack/Client/HandleResponseM.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE FlexibleContexts #-}--- | Semantics of client commands.+-- | Semantics of responses sent by the server to clients. module Game.LambdaHack.Client.HandleResponseM- ( MonadClientReadResponse(..), MonadClientWriteRequest(..)+ ( MonadClientWriteRequest(..)+ , MonadClientAtomic(..) , handleResponse ) where @@ -9,37 +10,55 @@ import Game.LambdaHack.Common.Prelude -import Game.LambdaHack.Atomic+import Game.LambdaHack.Atomic (UpdAtomic) import Game.LambdaHack.Client.AI import Game.LambdaHack.Client.HandleAtomicM import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Request+import Game.LambdaHack.Client.Response import Game.LambdaHack.Client.UI-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.Response--class MonadClient m => MonadClientReadResponse m where- receiveResponse :: m Response+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.State +-- | Client monad in which one can send requests to the client. class MonadClient m => MonadClientWriteRequest m where sendRequestAI :: RequestAI -> m () sendRequestUI :: RequestUI -> m () clientHasUI :: m Bool +-- | Monad for executing atomic game state transformations on a client.+class MonadClient m => MonadClientAtomic m where+ -- | Execute an atomic update that changes the client's 'State'.+ execUpdAtomic :: UpdAtomic -> m ()+ -- | Put state that is intended to be the result of performing+ -- an atomic update by the server on its copy of the client's 'State'.+ execPutState :: State -> m ()++-- | Handle server responses.+--+-- Note that for clients communicating with the server over the net,+-- @RespUpdAtomicNoState@ should be used, because executing a single command+-- is cheaper than sending the whole state over the net.+-- However, for the standalone exe mode, with clients in the same process+-- as the server, a pointer to the state set with @execPutState@ is cheaper. handleResponse :: ( MonadClientSetup m , MonadClientUI m- , MonadAtomic m+ , MonadClientAtomic m , MonadClientWriteRequest m ) => Response -> m () handleResponse cmd = case cmd of- RespUpdAtomic cmdA -> do+ RespUpdAtomic newState cmdA -> do+ oldState <- getState+ execPutState newState+ cmdAtomicSemCli oldState cmdA hasUI <- clientHasUI- cmds <- cmdAtomicFilterCli cmdA- let handle !c = do- cli <- getClient- cmdAtomicSemCli c- execUpdAtomic c- when hasUI $ displayRespUpdAtomicUI False cli c- mapM_ handle cmds+ when hasUI $ displayRespUpdAtomicUI False cmdA+ RespUpdAtomicNoState cmdA -> do+ oldState <- getState+ execUpdAtomic cmdA+ cmdAtomicSemCli oldState cmdA+ hasUI <- clientHasUI+ when hasUI $ displayRespUpdAtomicUI False cmdA RespQueryAI aid -> do cmdC <- queryAI aid sendRequestAI cmdC
Game/LambdaHack/Client/LoopM.hs view
@@ -2,7 +2,12 @@ -- | The main loop of the client, processing human and computer player -- moves turn by turn. module Game.LambdaHack.Client.LoopM- ( loopCli+ ( MonadClientReadResponse(..)+ , loopCli+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , initAI, initUI+#endif ) where import Prelude ()@@ -10,63 +15,74 @@ import Game.LambdaHack.Common.Prelude import Game.LambdaHack.Atomic+import Game.LambdaHack.Client.ClientOptions+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.Response import Game.LambdaHack.Common.State import Game.LambdaHack.Common.Vector -initAI :: MonadClient m => DebugModeCli -> m ()-initAI sdebugCli = do- modifyClient $ \cli -> cli {sdebugCli}+-- | Client monad in which one can receive responses from the server.+class MonadClient m => MonadClientReadResponse m where+ receiveResponse :: m Response++initAI :: MonadClient m => m ()+initAI = do side <- getsClient sside debugPossiblyPrint $ "AI client" <+> tshow side <+> "initializing." -initUI :: MonadClientUI m => KeyKind -> Config -> DebugModeCli -> m ()-initUI copsClient sconfig sdebugCli = do- modifyClient $ \cli -> cli {sdebugCli}+initUI :: MonadClientUI m => KeyKind -> UIOptions -> m ()+initUI copsClient sUIOptions = do side <- getsClient sside+ soptions <- getsClient soptions debugPossiblyPrint $ "UI client" <+> tshow side <+> "initializing." -- Start the frontend.- schanF <- chanFrontend sdebugCli- let !sbinding = stdBinding copsClient sconfig -- evaluate to check for errors- sess = emptySessionUI sconfig- putSession sess { schanF- , sbinding- , sxhair = TVector $ Vector 1 1 }- -- a step south-east, less alarming+ schanF <- chanFrontend soptions+ let !sbinding = stdBinding copsClient sUIOptions+ -- evaluate to check for errors+ modifySession $ \sess ->+ sess { schanF+ , sbinding+ , sxhair = TVector $ Vector 1 1 }+ -- a step south-east, less alarming --- | The main game loop for an AI or UI client.+-- | The main game loop for an AI or UI client. It receives responses from+-- the server, changes internal client state accordingly, analyzes+-- ensuing human or AI commands and sends resulting requests to the server.+-- Depending on whether it's an AI or UI client, it sends AI or human player+-- requests.+--+-- The loop is started in client state that is empty except for+-- the @sside@ and @seps@ fields, see 'emptyStateClient'. loopCli :: ( MonadClientSetup m , MonadClientUI m- , MonadAtomic m+ , MonadClientAtomic m , MonadClientReadResponse m , MonadClientWriteRequest m )- => KeyKind -> Config -> DebugModeCli -> m ()-loopCli copsClient sconfig sdebugCli = do+ => KeyKind -> UIOptions -> ClientOptions -> m ()+loopCli copsClient sUIOptions soptions = do+ modifyClient $ \cli -> cli {soptions} hasUI <- clientHasUI- if not hasUI then initAI sdebugCli else initUI copsClient sconfig sdebugCli+ if not hasUI then initAI else initUI copsClient sUIOptions -- Warning: state and client state are invalid here, e.g., sdungeon -- and sper are empty. cops <- getsState scops restoredG <- tryRestore restored <- case restoredG of- Just (s, cli, msess) | not $ snewGameCli sdebugCli -> do+ Just (cli, msess) | not $ snewGameCli soptions -> do -- Restore game.- let sCops = updateCOps (const cops) s- handleResponse $ RespUpdAtomic $ UpdResumeServer sCops schanF <- getsSession schanF sbinding <- getsSession sbinding- maybe (return ()) (\sess ->- putSession sess {schanF, sbinding, sconfig}) msess- putClient cli {sdebugCli}+ maybe (return ()) (\sess -> modifySession $ \_ ->+ sess {schanF, sbinding, sUIOptions}) msess+ putClient cli {soptions} return True- Just (_, _, msessR) -> do+ Just (_, msessR) -> do -- Preserve previous history, if any (--newGame). maybe (return ()) (\sessR -> modifySession $ \sess -> sess {shistory = shistory sessR}) msessR@@ -75,13 +91,22 @@ side <- getsClient sside cmd1 <- receiveResponse case (restored, cmd1) of- (True, RespUpdAtomic UpdResume{}) -> return ()- (True, RespUpdAtomic UpdRestart{}) ->+ (True, RespUpdAtomic s UpdResume{}) -> do+ let sCops = updateCOps (const cops) s+ handleResponse $ RespUpdAtomic sCops $ UpdResumeServer sCops+ (True, RespUpdAtomic _ UpdRestart{}) -> when hasUI $ msgAdd "Ignoring an old savefile and starting a new game."- (False, RespUpdAtomic UpdResume{}) ->+ (False, RespUpdAtomic _ UpdResume{}) -> error $ "Savefile of client " ++ show side ++ " not usable." `showFailure` ()- (False, RespUpdAtomic UpdRestart{}) -> return ()+ (False, RespUpdAtomic _ UpdRestart{}) -> return ()+ (True, RespUpdAtomicNoState UpdResume{}) -> undefined+ (True, RespUpdAtomicNoState UpdRestart{}) ->+ when hasUI $ msgAdd "Ignoring an old savefile and starting a new game."+ (False, RespUpdAtomicNoState UpdResume{}) ->+ error $ "Savefile of client " ++ show side ++ " not usable."+ `showFailure` ()+ (False, RespUpdAtomicNoState UpdRestart{}) -> return () _ -> error $ "unexpected command" `showFailure` (side, restored, cmd1) handleResponse cmd1 -- State and client state now valid.
Game/LambdaHack/Client/MonadClient.hs view
@@ -1,12 +1,10 @@ -- | Basic client monad and related operations. module Game.LambdaHack.Client.MonadClient- ( -- * Basic client monad- MonadClient( getsClient, modifyClient+ ( -- * Basic client monads+ MonadClient( getsClient+ , modifyClient , liftIO -- exposed only to be implemented, not used )- , MonadClientSetup( saveClient- , restartClient- ) -- * Assorted primitives , getClient, putClient , debugPossiblyPrint, rndToAction, rndToActionForget@@ -18,14 +16,15 @@ import qualified Control.Monad.Trans.State.Strict as St import qualified Data.Text.IO as T-import System.IO (hFlush, stdout)+import System.IO (hFlush, stdout) import qualified System.Random as R +import Game.LambdaHack.Client.ClientOptions import Game.LambdaHack.Client.State-import Game.LambdaHack.Common.ClientOptions import Game.LambdaHack.Common.MonadStateRead import Game.LambdaHack.Common.Random +-- | Monad for writing to client state. class MonadStateRead m => MonadClient m where getsClient :: (StateClient -> a) -> m a modifyClient :: (StateClient -> StateClient) -> m ()@@ -33,10 +32,6 @@ -- nobody can subvert the action monads by invoking arbitrary IO. liftIO :: IO a -> m a -class MonadClient m => MonadClientSetup m where- saveClient :: m ()- restartClient :: m ()- getClient :: MonadClient m => m StateClient getClient = getsClient id @@ -45,7 +40,7 @@ debugPossiblyPrint :: MonadClient m => Text -> m () debugPossiblyPrint t = do- sdbgMsgCli <- getsClient $ sdbgMsgCli . sdebugCli+ sdbgMsgCli <- getsClient $ sdbgMsgCli . soptions when sdbgMsgCli $ liftIO $ do T.hPutStrLn stdout t hFlush stdout@@ -55,7 +50,7 @@ rndToAction r = do gen <- getsClient srandom let (gen1, gen2) = R.split gen- modifyClient $ \ser -> ser {srandom = gen1}+ modifyClient $ \cli -> cli {srandom = gen1} return $! St.evalState r gen2 -- | Invoke pseudo-random computation, don't change generator kept in state.
Game/LambdaHack/Client/Preferences.hs view
@@ -1,9 +1,11 @@--- | Actor preferences for targets and actions based on actor attributes.+-- | Actor preferences for targets and actions, based on actor attributes. module Game.LambdaHack.Client.Preferences ( totalUsefulness #ifdef EXPOSE_INTERNAL -- * Internal operations- , effectToBenefit, organBenefit, aspectToBenefit, recordToBenefit+ , effectToBenefit+ , averageTurnValue, avgItemDelay, avgItemLife, durabilityMult+ , organBenefit, recBenefit, fakeItem, aspectToBenefit, recordToBenefit #endif ) where @@ -12,17 +14,17 @@ import Game.LambdaHack.Common.Prelude import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Flavour-import Game.LambdaHack.Common.Frequency-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.ModeKind -- | How much AI benefits from applying the effect. -- The first component is benefit when applied to self, the second@@ -36,7 +38,7 @@ -- So there is less than @averageTurnValue@ included in each benefit, -- so in case when turn is not spent, e.g, periodic or temporary effects, -- the difference in value is only slight.-effectToBenefit :: Kind.COps -> Faction -> IK.Effect -> (Int, Int)+effectToBenefit :: Kind.COps -> Faction -> IK.Effect -> (Double, Double) effectToBenefit cops fact eff = let delta x = (x, x) in case eff of@@ -47,12 +49,16 @@ IK.Explode _ -> delta 1 -- depends on explosion, but usually good, -- unless under OnSmash, but they are ignored IK.RefillHP p ->- delta $ if p > 0 then min 2000 (20 * p) else max (-1000) (10 * p)+ delta $ if p > 0+ then min 2000 (20 * fromIntegral p)+ else max (-1000) (10 * fromIntegral p) -- one HP healed is worth a bit more than one HP dealed to enemy, -- because if the actor survives, he may deal damage many times; -- however, AI is mostly for non-heroes that fight in suicidal crowds, -- so the two values are kept close enough to maintain berserk approach- IK.RefillCalm p -> delta $ if p > 0 then min 9 p else max (-9) p+ IK.RefillCalm p -> delta $ if p > 0+ then min 9 (fromIntegral p)+ else max (-9) (fromIntegral p) IK.Dominate -> (0, -300) -- I obtained an actor with, say 10HP, -- worth 200, and enemy lost him, another 100 IK.Impress -> (5, -50) -- usually has no effect on self, hence low value@@ -75,7 +81,7 @@ IK.Paralyze d -> delta $ -20 * Dice.meanDice d -- clips IK.InsertMove d -> delta $ 100 * Dice.meanDice d -- turns IK.Teleport d -> if Dice.meanDice d <= 8- then (1, 0) -- blink to shoot at foes+ then (1, 0) -- blink to shoot at foes else (-9, -1) -- for self, don't derail exploration -- for foes, fight with one less at a time IK.CreateItem COrgan "temporary condition" _ ->@@ -86,8 +92,9 @@ IK.TimerGameTurn n -> Dice.meanDice n IK.TimerActorTurn n -> Dice.meanDice n (total, count) = organBenefit turnTimer grp cops fact- in delta $ total `divUp` count -- the same when created in me and in foe- -- average over all matching grps; simplified: rarities ignored+ in delta $ total / fromIntegral count+ -- the same when created in me and in foe+ -- average over all matching grps; simplified: rarities ignored IK.CreateItem _ "treasure" _ -> (100, 0) -- assumed not temporary IK.CreateItem _ "useful" _ -> (70, 0) IK.CreateItem _ "any scroll" _ -> (50, 0)@@ -96,7 +103,7 @@ IK.CreateItem _ "flask" _ -> (50, 0) IK.CreateItem _ grp _ -> -- assumed not temporary and @grp@ tiny let (total, count) = recBenefit grp cops fact- in (total `divUp` count, 0)+ in (total / fromIntegral count, 0) IK.DropItem _ _ COrgan "temporary condition" -> (1, -1) -- varied, big bunch, but try to nullify it anyway IK.DropItem ngroup kcopy COrgan grp -> -- assumed temporary@@ -110,15 +117,16 @@ (total, count) = organBenefit turnTimer grp cops fact boundBonus n = if n == maxBound then 10 else 0 in delta $ boundBonus ngroup + boundBonus kcopy- - total `divUp` count -- the same when dropped from me and foe+ - total / fromIntegral count+ -- the same when dropped from me and foe IK.DropItem{} -> delta (-10) -- depends a lot on what is dropped IK.PolyItem -> delta 0 -- AI can't estimate item desirability vs average IK.Identify -> delta 0 -- AI doesn't know how to use- IK.Detect radius -> (radius * 2, 0)- IK.DetectActor radius -> (radius, 0)- IK.DetectItem radius -> (radius, 0)- IK.DetectExit radius -> (radius, 0)- IK.DetectHidden radius -> (radius, 0)+ IK.Detect radius -> (fromIntegral radius * 2, 0)+ IK.DetectActor radius -> (fromIntegral radius, 0)+ IK.DetectItem radius -> (fromIntegral radius, 0)+ IK.DetectExit radius -> (fromIntegral radius, 0)+ IK.DetectHidden radius -> (fromIntegral radius, 0) IK.SendFlying _ -> (1, -10) -- very context dependent, but it's better IK.PushActor _ -> (1, -10) -- to be the one that decides and not the one IK.PullActor _ -> (1, -10) -- that is interrupted in the middle of fleeing@@ -130,16 +138,22 @@ let bs = map (effectToBenefit cops fact) efs f (self, foe) (accSelf, accFoe) = (self + accSelf, foe + accFoe) (effSelf, effFoe) = foldr f (0, 0) bs- in (effSelf `divUp` length bs, effFoe `divUp` length bs)+ in (effSelf / fromIntegral (length bs), effFoe / fromIntegral (length bs)) IK.OnSmash _ -> delta 0 -- can be beneficial; we'd need to analyze explosions, range, etc. IK.Recharging _ -> delta 0 -- taken into account separately IK.Temporary _ -> delta 0 -- assumed for created organs only IK.Unique -> delta 0 IK.Periodic -> delta 0 -- considered in totalUsefulness+ IK.Composite [] -> delta 0+ IK.Composite (eff1 : _) -> effectToBenefit cops fact eff1+ -- for simplicity; so in content make sure to place initial animations+ -- among normal effects, not at the start of composite effect+ -- (animations should not fail, after all), and start composite+ -- effect with the main thing -- See the comment for @Paralyze@.-averageTurnValue :: Int+averageTurnValue :: Double averageTurnValue = 10 -- Average delay between desired item uses. Some items are best activated@@ -156,7 +170,7 @@ -- We don't want to undervalue rarely used items with long timeouts -- and we think that most interesting gameplay comes from alternating -- item use, so we arbitrarily set the full value timeout to 3.-avgItemDelay :: Int+avgItemDelay :: Double avgItemDelay = 3 -- The average time between item being found (and enough skill obtained@@ -173,14 +187,14 @@ -- We set the value to 30, assuming if the actor finds an item, then he is -- most likely at an unlooted level, so he will find more loot soon, -- or he is in a battle, so he will die soon (or win even more loot).-avgItemLife :: Int+avgItemLife :: Double avgItemLife = 30 -- The value of durable item is this many times higher than non-durable, -- because the item will on average be activated this many times -- before it stops being used.-durabilityMult :: Int-durabilityMult = avgItemLife `div` avgItemDelay+durabilityMult :: Double+durabilityMult = avgItemLife / avgItemDelay -- We assume the organ is temporary (@Temporary@, @Periodic@, @Timeout 0@) -- and also that it doesn't provide any functionality, e.g., detection@@ -212,19 +226,20 @@ -- are applied and we can't stop/restart them. -- -- We assume, only one of timer and count mechanisms is present at once.-organBenefit :: Int -> GroupName ItemKind -> Kind.COps -> Faction -> (Int, Int)+organBenefit :: Double -> GroupName ItemKind -> Kind.COps -> Faction+ -> (Double, Int) organBenefit turnTimer grp cops@Kind.COps{coitem=Kind.Ops{ofoldlGroup'}} fact = let f (!sacc, !pacc) !p _ !kind =- let paspect asp = p * aspectToBenefit asp- peffect eff = p * fst (effectToBenefit cops fact eff)+ let paspect asp = fromIntegral p * aspectToBenefit asp+ peffect eff = fromIntegral p * fst (effectToBenefit cops fact eff) in ( sacc + Dice.meanDice (IK.icount kind) * (sum (map paspect $ IK.iaspects kind) + sum (map peffect $ stripRecharging $ IK.ieffects kind))- - averageTurnValue `div` turnTimer+ - averageTurnValue / turnTimer , pacc + p ) in ofoldlGroup' grp f (0, 0) -recBenefit :: GroupName ItemKind -> Kind.COps -> Faction -> (Int, Int)+recBenefit :: GroupName ItemKind -> Kind.COps -> Faction -> (Double, Int) recBenefit grp cops@Kind.COps{coitem=Kind.Ops{ofoldlGroup'}} fact = let f (!sacc, !pacc) !p _ !kind = let recPickup = benPickup $@@ -267,15 +282,15 @@ -- Valuation of effects, and more precisely, more the signs than absolute -- values, ensures that both shield and torch get picked up so that -- the (human) actor can nevertheless equip them in very special cases.-aspectToBenefit :: IK.Aspect -> Int+aspectToBenefit :: IK.Aspect -> Double aspectToBenefit asp = case asp of IK.Timeout{} -> 0 IK.AddHurtMelee p -> Dice.meanDice p -- offence favoured- IK.AddArmorMelee p -> Dice.meanDice p `divUp` 4 -- only partial protection- IK.AddArmorRanged p -> Dice.meanDice p `divUp` 8+ IK.AddArmorMelee p -> Dice.meanDice p / 4 -- only partial protection+ IK.AddArmorRanged p -> Dice.meanDice p / 8 IK.AddMaxHP p -> Dice.meanDice p- IK.AddMaxCalm p -> Dice.meanDice p `divUp` 5+ IK.AddMaxCalm p -> Dice.meanDice p / 5 IK.AddSpeed p -> Dice.meanDice p * 25 -- 1 speed ~ 5% melee; times 5 for no caps, escape, pillar-dancing, etc.; -- also, it's 1 extra turn each 20 turns, so 100/20, so 5; figures@@ -286,10 +301,13 @@ IK.AddAggression{} -> 0 IK.AddAbility _ p -> Dice.meanDice p * 5 -recordToBenefit :: AspectRecord -> [Int]+recordToBenefit :: AspectRecord -> [Double] recordToBenefit aspects = map aspectToBenefit $ aspectRecordToList aspects --- Result has non-strict fields, so arguments are forced to avoid leaks.+-- | Compute the whole 'Benefit' structure, containing various facets+-- of AI item preference, for an item with the given effects and aspects.+--+-- Note: result has non-strict fields, so arguments are forced to avoid leaks. -- When AI looks at items (including organs) more often, force the fields. totalUsefulness :: Kind.COps -> Faction -> [IK.Effect] -> AspectRecord -> Item -> Benefit@@ -309,8 +327,9 @@ let scaleChargeBens bens | timeout <= 3 = bens | otherwise = map (\eff ->- if avgItemDelay >= timeout then eff- else eff * avgItemDelay `divUp` timeout) bens+ if avgItemDelay >= fromIntegral timeout+ then eff+ else eff * avgItemDelay / fromIntegral timeout) bens (cself, cfoe) = unzip $ map (effectToBenefit cops fact) (stripRecharging effects) in (scaleChargeBens cself, scaleChargeBens cfoe)@@ -348,12 +367,12 @@ benApply = max 0 $ -- because optional; I don't need to apply (effSelf + effDice -- hits self with dice too, when applying + if periodic then 0 else sum chargeSelf)- `divUp` if durable then 1 else durabilityMult+ / if durable then 1 else durabilityMult -- For melee, we add the foe part. benMelee = min 0 $ (effFoe + effDice -- @AddHurtMelee@ already in @eqpSum@ + if periodic then 0 else sum chargeFoe)- `divUp` if durable then 1 else durabilityMult+ / if durable then 1 else durabilityMult -- The periodic effects, if any, are activated when projectile flies, -- but not when it hits, so they are not added to @benFling@. -- However, if item is not periodic, the recharging effects@@ -361,17 +380,17 @@ benFling = min 0 $ effFoe + benFlingDice -- nothing in @eqpSum@; normally not worn + if periodic then 0 else sum chargeFoe- benFlingDice | jdamage item <= 0 = 0 -- speedup+ benFlingDice | jdamage item == 0 = 0 -- speedup | otherwise = assert (v <= 0) v where hurtMult = 100 + min 99 (max (-99) (aHurtMelee aspects)) -- assumes no enemy armor and no block dmg = Dice.meanDice (jdamage item)- rawDeltaHP = fromIntegral hurtMult * xM dmg `divUp` 100+ rawDeltaHP = ceiling $ fromIntegral hurtMult * xD dmg / 100 -- For simplicity, we ignore range bonus/malus and @Lobable@. IK.ThrowMod{IK.throwVelocity} = strengthToThrow item speed = speedFromWeight (jweight item) throwVelocity- v = fromEnum $ - modifyDamageBySpeed rawDeltaHP speed * 10 `div` oneM+ v = - fromIntegral (modifyDamageBySpeed rawDeltaHP speed) * 10 / xD 1 -- 1 damage valued at 10, just as in @damageUsefulness@ -- For equipment benefit, we take into account only the self -- value of the recharging effects, because they applied to self.
+ Game/LambdaHack/Client/Request.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE DataKinds, GADTs, KindSignatures, StandaloneDeriving #-}+-- | Abstract syntax of requests.+--+-- See+-- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>.+module Game.LambdaHack.Client.Request+ ( RequestAI, ReqAI(..), RequestUI, ReqUI(..)+ , RequestAnyAbility(..), RequestTimed(..)+ ) where++import Prelude ()++import Game.LambdaHack.Common.Prelude++import Game.LambdaHack.Common.Ability+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ModeKind++-- | Requests sent by AI clients to the server. If faction leader is to be+-- changed, it's included as the second component.+type RequestAI = (ReqAI, Maybe ActorId)++-- | Possible forms of requests sent by AI clients.+data ReqAI =+ ReqAINop+ | ReqAITimed RequestAnyAbility+ deriving Show++-- | Requests sent by UI clients to the server. If faction leader is to be+-- changed, it's included as the second component.+type RequestUI = (ReqUI, Maybe ActorId)++-- | Possible forms of requests sent by UI clients.+data ReqUI =+ ReqUINop+ | ReqUITimed RequestAnyAbility+ | ReqUIGameRestart (GroupName ModeKind) Challenge+ | ReqUIGameExit+ | ReqUIGameSave+ | ReqUITactic Tactic+ | ReqUIAutomate+ deriving Show++-- | Basic form of requests, sent by both AI and UI clients to the server.+data RequestAnyAbility = forall a. RequestAnyAbility (RequestTimed a)++deriving instance Show RequestAnyAbility++-- | Requests that take game time, indexed by actor ability+-- that is needed for performing the corresponding actions.+data RequestTimed :: Ability -> * where+ ReqMove :: Vector -> RequestTimed 'AbMove+ ReqMelee :: ActorId -> ItemId -> CStore -> RequestTimed 'AbMelee+ ReqDisplace :: ActorId -> RequestTimed 'AbDisplace+ ReqAlter :: Point -> RequestTimed 'AbAlter+ ReqWait :: RequestTimed 'AbWait+ ReqWait10 :: RequestTimed 'AbWait+ ReqMoveItems :: [(ItemId, Int, CStore, CStore)] -> RequestTimed 'AbMoveItem+ ReqProject :: Point -> Int -> ItemId -> CStore -> RequestTimed 'AbProject+ ReqApply :: ItemId -> CStore -> RequestTimed 'AbApply++deriving instance Show (RequestTimed a)
+ Game/LambdaHack/Client/Response.hs view
@@ -0,0 +1,33 @@+-- | Abstract syntax of responses.+--+-- See+-- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>.+module Game.LambdaHack.Client.Response+ ( Response(..)+ ) where++import Prelude ()++import Game.LambdaHack.Common.Prelude++import Game.LambdaHack.Atomic+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.State++-- | Abstract syntax of responses sent by server to an AI or UI client+-- (or a universal client that can handle both roles, which is why+-- this type is not separated into distinct AI and UI types).+-- A response tells a client how to update game state or what information+-- to send to the server.+data Response =+ RespUpdAtomicNoState UpdAtomic+ -- ^ change @State@ by performing this atomic update+ | RespUpdAtomic State UpdAtomic+ -- ^ put the given @State@, which results from performing the atomic update+ | RespQueryAI ActorId+ -- ^ compute an AI move for the actor and send (the semantics of) it+ | RespSfxAtomic SfxAtomic+ -- ^ perform special effects (animations, messages, etc.)+ | RespQueryUI+ -- ^ prompt the human player for a command and send (the semantics of) it+ deriving Show
Game/LambdaHack/Client/State.hs view
@@ -1,82 +1,72 @@ {-# LANGUAGE DeriveGeneric #-}--- | Server and client game state types and operations.+-- | Client-specific game state components. module Game.LambdaHack.Client.State- ( StateClient(..), emptyStateClient- , AlterLid- , updateTarget, getTarget, updateLeader, sside- , BfsAndPath(..), TgtAndPath(..), cycleMarkSuspect+ ( StateClient(..), AlterLid, BfsAndPath(..), TgtAndPath(..)+ , emptyStateClient, cycleMarkSuspect+ , updateTarget, getTarget, updateLeader, sside, sleader ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.Map.Strict as M-import GHC.Generics (Generic)+import GHC.Generics (Generic) import qualified System.Random as R -import Game.LambdaHack.Atomic-import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.State-import Game.LambdaHack.Content.ModeKind (ModeKind)+import Game.LambdaHack.Common.State+import Game.LambdaHack.Content.ModeKind (ModeKind) -- | Client state, belonging to a single faction.--- Some of the data, e.g, the history, carries over--- from game to game, even across playing sessions.------ When many actors want to fling at the same target, they set--- their personal targets to follow the common xhair.--- When each wants to kill a fleeing enemy they recently meleed,--- they keep the enemies as their personal targets.------ Data invariant: if @_sleader@ is @Nothing@ then so is @srunning@. data StateClient = StateClient- { seps :: Int -- ^ a parameter of the aiming digital line+ { seps :: Int -- ^ a parameter of the aiming digital line , stargetD :: EM.EnumMap ActorId TgtAndPath- -- ^ targets of our actors in the dungeon+ -- ^ targets of our actors in the dungeon , sexplored :: ES.EnumSet LevelId- -- ^ the set of fully explored levels+ -- ^ the set of fully explored levels , sbfsD :: EM.EnumMap ActorId BfsAndPath- -- ^ pathfinding distances for our actors- -- and paths to their targets, if any- , sundo :: [CmdAtomic] -- ^ atomic commands performed to date- , sdiscoKind :: DiscoveryKind -- ^ remembered item discoveries- , sdiscoAspect :: DiscoveryAspect -- ^ remembered aspects of items- , sdiscoBenefit :: DiscoveryBenefit -- ^ remembered AI benefits of items- , sactorAspect :: ActorAspect -- ^ best known actor aspect data- , sfper :: PerLid -- ^ faction perception indexed by levels- , salter :: AlterLid -- ^ cached alter ability data for positions- , srandom :: R.StdGen -- ^ current random generator- , _sleader :: Maybe ActorId- -- ^ candidate new leader of the faction;- -- Faction._gleader is the old leader- , _sside :: FactionId -- ^ faction controlled by the client- , squit :: Bool -- ^ exit the game loop- , scurChal :: Challenge -- ^ current game challenge setup- , snxtChal :: Challenge -- ^ next game challenge setup- , snxtScenario :: Int -- ^ next game scenario number- , smarkSuspect :: Int -- ^ mark suspect features+ -- ^ pathfinding data for our actors+ , sundo :: [CmdAtomic] -- ^ atomic commands performed to date+ , sdiscoBenefit :: DiscoveryBenefit+ -- ^ remembered AI benefits of items+ , sfper :: PerLid -- ^ faction perception indexed by level+ , salter :: AlterLid -- ^ cached alter ability data for positions+ , srandom :: R.StdGen -- ^ current random generator+ , _sleader :: Maybe ActorId -- ^ candidate new leader of the faction;+ -- Faction.gleader is the old leader+ , _sside :: FactionId -- ^ faction controlled by the client+ , squit :: Bool -- ^ exit the game loop+ , scurChal :: Challenge -- ^ current game challenge setup+ , snxtChal :: Challenge -- ^ next game challenge setup+ , snxtScenario :: Int -- ^ next game scenario number+ , smarkSuspect :: Int -- ^ whether to mark suspect features , scondInMelee :: EM.EnumMap LevelId (Maybe Bool)- -- ^ condInMelee value, unless invalidated+ -- ^ last in-melee condition for each level , svictories :: EM.EnumMap (Kind.Id ModeKind) (M.Map Challenge Int)- -- ^ won games at particular difficulty levels- , sdebugCli :: DebugModeCli -- ^ client debugging mode+ -- ^ won games at particular difficulty lvls+ , soptions :: ClientOptions -- ^ client options } deriving Show +type AlterLid = EM.EnumMap LevelId (PointArray.Array Word8)++-- | Pathfinding distances to all reachable positions of an actor+-- and a shortest paths to some of the positions. data BfsAndPath = BfsInvalid | BfsAndPath { bfsArr :: PointArray.Array BfsDistance@@ -84,13 +74,12 @@ } deriving Show +-- | Actor's target and a path to it, if any. data TgtAndPath = TgtAndPath {tapTgt :: Target, tapPath :: AndPath} deriving (Show, Generic) instance Binary TgtAndPath -type AlterLid = EM.EnumMap LevelId (PointArray.Array Word8)- -- | Initial empty game client state. emptyStateClient :: FactionId -> StateClient emptyStateClient _sside =@@ -100,10 +89,7 @@ , sexplored = ES.empty , sbfsD = EM.empty , sundo = []- , sdiscoKind = EM.empty- , sdiscoAspect = EM.empty , sdiscoBenefit = EM.empty- , sactorAspect = EM.empty , sfper = EM.empty , salter = EM.empty , srandom = R.mkStdGen 42 -- will get modified in this and future games@@ -116,9 +102,10 @@ , smarkSuspect = 1 , scondInMelee = EM.empty , svictories = EM.empty- , sdebugCli = defDebugModeCli+ , soptions = defClientOptions } +-- | Cycle the 'smarkSuspect' setting. cycleMarkSuspect :: StateClient -> StateClient cycleMarkSuspect s@StateClient{smarkSuspect} = s {smarkSuspect = (smarkSuspect + 1) `mod` 3}@@ -148,14 +135,15 @@ sside :: StateClient -> FactionId sside = _sside +sleader :: StateClient -> Maybe ActorId+sleader = _sleader+ instance Binary StateClient where put StateClient{..} = do put seps put stargetD put sexplored put sundo- put sdiscoKind- put sdiscoAspect put sdiscoBenefit put (show srandom) put _sleader@@ -166,7 +154,7 @@ put smarkSuspect put scondInMelee put svictories- put sdebugCli+ put soptions #ifdef WITH_EXPENSIVE_ASSERTIONS put sfper #endif@@ -175,8 +163,6 @@ stargetD <- get sexplored <- get sundo <- get- sdiscoKind <- get- sdiscoAspect <- get sdiscoBenefit <- get g <- get _sleader <- get@@ -187,9 +173,8 @@ smarkSuspect <- get scondInMelee <- get svictories <- get- sdebugCli <- get+ soptions <- get let sbfsD = EM.empty- sactorAspect = EM.empty salter = EM.empty srandom = read g squit = False
Game/LambdaHack/Client/UI.hs view
@@ -2,19 +2,17 @@ -- requests, based on the client's view (visualized for the player) -- of the game state. module Game.LambdaHack.Client.UI- ( -- * Client UI monad- MonadClientUI(..)- -- * Assorted UI operations- , putSession, queryUI+ ( -- * Querying the human player+ queryUI+ -- * UI monad and session type+ , MonadClientUI(..), SessionUI(..)+ -- * Updating UI state wrt game state changes , displayRespUpdAtomicUI, displayRespSfxAtomicUI- -- * Startup- , KeyKind, SessionUI(..), emptySessionUI- , Config, applyConfigToDebug, configCmdline, mkConfig- , ChanFrontend, chanFrontend, frontendShutdown- -- * Operations exposed for LoopClient- , ColorMode(..)- , reportToSlideshow, getConfirms, msgAdd, promptAdd, addPressedEsc- , tryRestore, stdBinding+ -- * Startup and initialization+ , KeyKind+ , UIOptions, applyUIOptions, uCmdline, mkUIOptions+ -- * Operations exposed for "Game.LambdaHack.Client.LoopM"+ , ChanFrontend, chanFrontend, msgAdd, tryRestore, stdBinding #ifdef EXPOSE_INTERNAL -- * Internal operations , humanCommand@@ -30,35 +28,34 @@ import qualified Data.Map.Strict as M import qualified Data.Text as T -import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.Content.KeyKind-import Game.LambdaHack.Client.UI.DisplayAtomicM-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 Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Request+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.Content.KeyKind+import Game.LambdaHack.Client.UI.DisplayAtomicM+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.KeyBindings-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.Msg-import Game.LambdaHack.Client.UI.MsgM-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.OverlayM-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.Slideshow-import Game.LambdaHack.Client.UI.SlideshowM-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.Request-import Game.LambdaHack.Common.State-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Client.UI.KeyBindings+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+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.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.State+import Game.LambdaHack.Content.ModeKind --- | Handle the move of a UI player.+-- | Handle the move of a human player. queryUI :: MonadClientUI m => m RequestUI queryUI = do side <- getsClient sside@@ -71,16 +68,16 @@ addPressedEsc -- Regaining control of faction cancels --stopAfter*. modifyClient $ \cli ->- cli {sdebugCli = (sdebugCli cli) { sstopAfterSeconds = Nothing+ cli {soptions = (soptions cli) { sstopAfterSeconds = Nothing , sstopAfterFrames = Nothing }} return (ReqUIAutomate, Nothing) -- stop AI else do -- As long as UI faction is under AI control, check, once per move, -- for benchmark game stop.- stopAfterFrames <- getsClient $ sstopAfterFrames . sdebugCli+ stopAfterFrames <- getsClient $ sstopAfterFrames . soptions case stopAfterFrames of Nothing -> do- stopAfterSeconds <- getsClient $ sstopAfterSeconds . sdebugCli+ stopAfterSeconds <- getsClient $ sstopAfterSeconds . soptions case stopAfterSeconds of Nothing -> return (ReqUINop, Nothing) Just stopS -> do@@ -97,7 +94,7 @@ return (ReqUIGameExit, Nothing) -- ask server to exit else return (ReqUINop, Nothing) else do- let mleader = _gleader fact+ let mleader = gleader fact !_A = assert (isJust mleader) () req <- humanCommand leader2 <- getLeaderUI@@ -106,16 +103,16 @@ -- | Let the human player issue commands until any command takes time. humanCommand :: forall m. MonadClientUI m => m ReqUI humanCommand = do- modifySession $ \sess -> sess {slastLost = ES.empty}- modifySession $ \sess -> sess {skeysHintMode = KeysHintAbsent}+ modifySession $ \sess -> sess { slastLost = ES.empty+ , skeysHintMode = KeysHintAbsent } let loop :: m ReqUI loop = do- report <- getsSession _sreport+ report <- getsSession sreport if nullReport report then do -- Display keys sometimes, alternating with empty screen. keysHintMode <- getsSession skeysHintMode case keysHintMode of- KeysHintPresent -> describeMainKeys >>= promptAdd+ KeysHintPresent -> promptMainKeys KeysHintBlocked -> modifySession $ \sess -> sess {skeysHintMode = KeysHintAbsent} _ -> return ()@@ -134,9 +131,10 @@ void $ getConfirms ColorFull [K.spaceKM, K.escKM] slidesRaw -- Display base frame at the end. return []- (seqCurrent, seqPrevious, k) <- getsSession slastRecord- let slastRecord | k == 0 = ([], seqCurrent, 0)- | otherwise = ([], seqCurrent ++ seqPrevious, k - 1)+ LastRecord seqCurrent seqPrevious k <- getsSession slastRecord+ let slastRecord+ | k == 0 = LastRecord [] seqCurrent 0+ | otherwise = LastRecord [] (seqCurrent ++ seqPrevious) (k - 1) modifySession $ \sess -> sess {slastRecord} lastPlay <- getsSession slastPlay leader <- getLeaderUI
Game/LambdaHack/Client/UI/ActorUI.hs view
@@ -4,24 +4,24 @@ ( ActorUI(..), ActorDictUI , keySelected, partActor, partPronoun , ppContainer, ppCStore, ppCStoreIn, ppCStoreWownW- , ppContainerWownW, verbCStore, tryFindHeroK+ , ppContainerWownW, verbCStore, tryFindActor, tryFindHeroK ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.Char as Char import qualified Data.EnumMap.Strict as EM-import GHC.Generics (Generic)+import GHC.Generics (Generic) import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Actor import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.State data ActorUI = ActorUI { bsymbol :: Char -- ^ individual map symbol@@ -88,7 +88,6 @@ verbCStore CInv = "pack" verbCStore CSha = "stash" --- | Tries to finds an actor body satisfying a predicate on any level. tryFindActor :: State -> (ActorId -> Actor -> Bool) -> Maybe (ActorId, Actor) tryFindActor s p = find (uncurry p) $ EM.assocs $ sactorD s
Game/LambdaHack/Client/UI/Animation.hs view
@@ -2,14 +2,18 @@ module Game.LambdaHack.Client.UI.Animation ( Animation, renderAnim , pushAndDelay, blinkColorActor, twirlSplash, blockHit, blockMiss- , deathBody, shortDeathBody, actorX, swapPlaces, teleport, fadeout+ , deathBody, shortDeathBody, actorX, teleport, swapPlaces, fadeout+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , blank, cSym, mapPosToOffset, mzipSingleton, mzipPairs+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Bits+import Data.Bits import qualified Data.EnumMap.Strict as EM import Game.LambdaHack.Client.UI.Frame@@ -21,15 +25,17 @@ -- | Animation is a list of frame modifications to play one by one, -- where each modification if a map from positions to level map symbols.-newtype Animation = Animation [Overlay]+newtype Animation = Animation [IntOverlay] deriving (Eq, Show) -- | Render animations on top of a screen frame.+--+-- Located in this module to keep @Animation@ abstract. renderAnim :: FrameForall -> Animation -> Frames renderAnim basicFrame (Animation anim) =- let modifyFrame :: Overlay -> FrameForall+ let modifyFrame :: IntOverlay -> FrameForall modifyFrame am = overlayFrame am basicFrame- modifyFrames :: (Overlay, Overlay) -> Maybe FrameForall+ modifyFrames :: (IntOverlay, IntOverlay) -> Maybe FrameForall modifyFrames (am, amPrevious) = if am == amPrevious then Nothing else Just $ modifyFrame am in Just basicFrame : map modifyFrames (zip anim ([] : anim))@@ -45,13 +51,13 @@ let lxsize = fst normalLevelBound + 1 in ((py + 1) * lxsize + px, [attr]) -mzipSingleton :: Point -> Maybe AttrCharW32 -> Overlay+mzipSingleton :: Point -> Maybe AttrCharW32 -> IntOverlay mzipSingleton p1 mattr1 = map mapPosToOffset $ let mzip (pos, mattr) = fmap (\attr -> (pos, attr)) mattr in catMaybes [mzip (p1, mattr1)] mzipPairs :: (Point, Point) -> (Maybe AttrCharW32, Maybe AttrCharW32)- -> Overlay+ -> IntOverlay mzipPairs (p1, p2) (mattr1, mattr2) = map mapPosToOffset $ let mzip (pos, mattr) = fmap (\attr -> (pos, attr)) mattr in catMaybes $ if p1 /= p2
− Game/LambdaHack/Client/UI/Config.hs
@@ -1,145 +0,0 @@-{-# LANGUAGE DeriveGeneric, FlexibleContexts #-}--- | Personal game configuration file type definitions.-module Game.LambdaHack.Client.UI.Config- ( Config(..), mkConfig, applyConfigToDebug- ) where--import Prelude ()--import Game.LambdaHack.Common.Prelude--import Control.DeepSeq-import Data.Binary-import qualified Data.Ini as Ini-import qualified Data.Ini.Reader as Ini-import qualified Data.Ini.Types as Ini-import qualified Data.Map.Strict as M-import Game.LambdaHack.Common.ClientOptions-import GHC.Generics (Generic)-import System.FilePath-import Text.Read--import Game.LambdaHack.Client.UI.HumanCmd-import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.File-import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Content.RuleKind---- | Fully typed contents of the UI config file. This config--- is a part of a game client.-data Config = Config- { -- commands- configCommands :: [(K.KM, CmdTriple)]- -- hero names- , configHeroNames :: [(Int, (Text, Text))]- -- ui- , configVi :: Bool -- ^ the option for Vi keys takes precendence- , configLaptop :: Bool -- ^ because the laptop keys are the default- , configGtkFontFamily :: Text- , configSdlFontFile :: Text- , configSdlTtfSizeAdd :: Int- , configSdlFonSizeAdd :: Int- , configFontSize :: Int- , configColorIsBold :: Bool- , configHistoryMax :: Int- , configMaxFps :: Int- , configNoAnim :: Bool- , configRunStopMsgs :: Bool- , configCmdline :: [String]- }- deriving (Show, Generic)--instance NFData Config--instance Binary Config--parseConfig :: Ini.Config -> Config-parseConfig cfg =- let configCommands =- let mkCommand (ident, keydef) =- case stripPrefix "Cmd_" ident of- Just _ ->- let (key, def) = read keydef- in (K.mkKM key, def :: CmdTriple)- Nothing -> error $ "wrong macro id" `showFailure` ident- section = Ini.allItems "extra_commands" cfg- in map mkCommand section- configHeroNames =- let toNumber (ident, nameAndPronoun) =- case stripPrefix "HeroName_" ident of- Just n -> (read n, read nameAndPronoun)- Nothing -> error $ "wrong hero name id" `showFailure` ident- section = Ini.allItems "hero_names" cfg- in map toNumber section- getOption :: forall a. Read a => String -> a- getOption optionName =- let lookupFail :: forall b. String -> b- lookupFail err =- error $ "config file access failed"- `showFailure` (err, optionName, cfg)- s = fromMaybe (lookupFail "") $ Ini.getOption "ui" optionName cfg- in either lookupFail id $ readEither s- configVi = getOption "movementViKeys_hjklyubn"- -- The option for Vi keys takes precendence,- -- because the laptop keys are the default.- configLaptop = not configVi && getOption "movementLaptopKeys_uk8o79jl"- configGtkFontFamily = getOption "gtkFontFamily"- configSdlFontFile = getOption "sdlFontFile"- configSdlTtfSizeAdd = getOption "sdlTtfSizeAdd"- configSdlFonSizeAdd = getOption "sdlFonSizeAdd"- configFontSize = getOption "fontSize"- configColorIsBold = getOption "colorIsBold"- configHistoryMax = getOption "historyMax"- configMaxFps = max 1 $ getOption "maxFps"- configNoAnim = getOption "noAnim"- configRunStopMsgs = getOption "runStopMsgs"- configCmdline = words $ getOption "overrideCmdline"- in Config{..}---- | Read and parse UI config file.-mkConfig :: Kind.COps -> Bool -> IO Config-mkConfig Kind.COps{corule} benchmark = do- let stdRuleset = Kind.stdRuleset corule- cfgUIName = rcfgUIName stdRuleset- sUIDefault = rcfgUIDefault stdRuleset- cfgUIDefault = either (error . ("" `showFailure`)) id- $ Ini.parse sUIDefault- dataDir <- appDataDir- let userPath = dataDir </> cfgUIName- cfgUser <- if benchmark then return Ini.emptyConfig else do- cpExists <- doesFileExist userPath- if not cpExists- then return Ini.emptyConfig- else do- sUser <- readFile userPath- return $! either (error . ("" `showFailure`)) id $ Ini.parse sUser- let cfgUI = M.unionWith M.union cfgUser cfgUIDefault -- user cfg preferred- conf = parseConfig cfgUI- -- Catch syntax errors in complex expressions ASAP,- return $! deepseq conf conf--applyConfigToDebug :: Kind.COps -> Config -> DebugModeCli -> DebugModeCli-applyConfigToDebug Kind.COps{corule} sconfig sdebugCli =- let stdRuleset = Kind.stdRuleset corule- in (\dbg -> dbg {sgtkFontFamily =- sgtkFontFamily dbg `mplus` Just (configGtkFontFamily sconfig)}) .- (\dbg -> dbg {sdlFontFile =- sdlFontFile dbg `mplus` Just (configSdlFontFile sconfig)}) .- (\dbg -> dbg {sdlTtfSizeAdd =- sdlTtfSizeAdd dbg `mplus` Just (configSdlTtfSizeAdd sconfig)}) .- (\dbg -> dbg {sdlFonSizeAdd =- sdlFonSizeAdd dbg `mplus` Just (configSdlFonSizeAdd sconfig)}) .- (\dbg -> dbg {sfontSize =- sfontSize dbg `mplus` Just (configFontSize sconfig)}) .- (\dbg -> dbg {scolorIsBold =- scolorIsBold dbg `mplus` Just (configColorIsBold sconfig)}) .- (\dbg -> dbg {smaxFps =- smaxFps dbg `mplus` Just (configMaxFps sconfig)}) .- (\dbg -> dbg {snoAnim =- snoAnim dbg `mplus` Just (configNoAnim sconfig)}) .- (\dbg -> dbg {stitle =- stitle dbg `mplus` Just (rtitle stdRuleset)}) .- (\dbg -> dbg {sfontDir =- sfontDir dbg `mplus` Just (rfontDir stdRuleset)})- $ sdebugCli
Game/LambdaHack/Client/UI/Content/KeyKind.hs view
@@ -1,12 +1,17 @@--- | The type of key-command mappings to be used for the UI.+-- | The type of definitions of key-command mappings to be used for the UI+-- and shorthands for specifying command triples in the content files. module Game.LambdaHack.Client.UI.Content.KeyKind ( KeyKind(..), evalKeyDef , addCmdCategory, replaceDesc, moveItemTriple, repeatTriple , mouseLMB, mouseMMB, mouseRMB , goToCmd, runToAllCmd, autoexploreCmd, autoexplore25Cmd- , aimFlingCmd, projectI, projectA, flingTs, applyI, applyIK+ , aimFlingCmd, projectI, projectA, flingTs, applyIK, applyI , grabItems, dropItems, descTs, defaultHeroSelect- ) where+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , replaceCmd, projectICmd, grabCmd, dropCmd+#endif+ ) where import Prelude () @@ -15,15 +20,13 @@ import qualified Data.Char as Char import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.UI.ActorUI (verbCStore)-import Game.LambdaHack.Client.UI.HumanCmd+import Game.LambdaHack.Client.UI.ActorUI (verbCStore)+import Game.LambdaHack.Client.UI.HumanCmd import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Misc --- | Key-command mappings to be used for the UI.-newtype KeyKind = KeyKind- { rhumanCommands :: [(K.KM, CmdTriple)] -- ^ default client UI commands- }+-- | Key-command mappings to be specified in content and used for the UI.+newtype KeyKind = KeyKind [(K.KM, CmdTriple)] -- ^ default client UI commands evalKeyDef :: (String, CmdTriple) -> (K.KM, CmdTriple) evalKeyDef (t, triple@(cats, _, _)) =
Game/LambdaHack/Client/UI/DisplayAtomicM.hs view
@@ -1,6 +1,13 @@ -- | Display atomic commands received by the client. module Game.LambdaHack.Client.UI.DisplayAtomicM ( displayRespUpdAtomicUI, displayRespSfxAtomicUI+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , updateItemSlot, markDisplayNeeded, updateItemSlotSide, lookAtMove+ , actorVerbMU, aidVerbMU, itemVerbMU, itemAidVerbMU, msgDuplicateScrap+ , createActorUI, destroyActorUI, spotItem, moveActor, displaceActorUI+ , moveItemUI, quitFactionUI, discover, ppSfxMsg, setLastSlot, strike+#endif ) where import Prelude ()@@ -10,59 +17,59 @@ import qualified Data.Char as Char import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES+import Data.Key (mapWithKeyM_) import qualified Data.Map.Strict as M-import Data.Tuple-import GHC.Exts (inline)+import Data.Tuple+import GHC.Exts (inline) import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Atomic-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.Animation-import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.FrameM-import Game.LambdaHack.Client.UI.HandleHelperM-import Game.LambdaHack.Client.UI.ItemDescription-import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.Animation+import Game.LambdaHack.Client.UI.FrameM+import Game.LambdaHack.Client.UI.HandleHelperM+import Game.LambdaHack.Client.UI.ItemDescription+import Game.LambdaHack.Client.UI.ItemSlot 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.Overlay-import Game.LambdaHack.Client.UI.OverlayM-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.Slideshow-import Game.LambdaHack.Client.UI.SlideshowM-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+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.Common.Actor+import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Flavour-import Game.LambdaHack.Common.Item+import qualified Game.LambdaHack.Common.Dice as Dice+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile+import Game.LambdaHack.Common.Time import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind import qualified Game.LambdaHack.Content.TileKind as TK -- * RespUpdAtomicUI --- | Visualize atomic actions sent to the client. This is done+-- | Visualize atomic updates sent to the client. This is done -- in the global state after the command is executed and after -- the client state is modified by the command.-displayRespUpdAtomicUI :: MonadClientUI m- => Bool -> StateClient -> UpdAtomic -> m ()+displayRespUpdAtomicUI :: MonadClientUI m => Bool -> UpdAtomic -> m () {-# INLINE displayRespUpdAtomicUI #-}-displayRespUpdAtomicUI verbose oldCli cmd = case cmd of+displayRespUpdAtomicUI verbose cmd = case cmd of -- Create/destroy actors and items. UpdCreateActor aid body _ -> createActorUI True aid body UpdDestroyActor aid body _ -> destroyActorUI True aid body@@ -88,7 +95,7 @@ ownerFun <- partActorLeaderFun let wown = ppContainerWownW ownerFun True c itemVerbMU iid kit (MU.Text $ makePhrase $ "appear" : wown) c- mleader <- getsClient _sleader+ mleader <- getsClient sleader when (Just aid == mleader) $ modifySession $ \sess -> sess {slastSlot} CEmbed lid _ -> markDisplayNeeded lid@@ -106,48 +113,11 @@ markDisplayNeeded lid UpdSpotActor aid body _ -> createActorUI False aid body UpdLoseActor aid body _ -> destroyActorUI False aid body- UpdSpotItem verbose2 iid _ kit c -> do- -- This is due to a move, or similar, which will be displayed,- -- so no extra @markDisplayNeeded@ needed here and in similar places.- ItemSlots itemSlots _ <- getsSession sslots- case lookup iid $ map swap $ EM.assocs itemSlots of- Nothing -> -- never seen or would have a slot- case c of- CActor aid store ->- -- Most probably an actor putting item in or out of shared stash.- void $ updateItemSlotSide store aid iid- CEmbed{} -> return ()- CFloor lid p -> do- void $ updateItemSlot CGround Nothing iid- sxhairOld <- getsSession sxhair- case sxhairOld of- TEnemy{} -> return () -- probably too important to overwrite- TPoint TEnemyPos{} _ _ -> return ()- _ -> do- -- Don't steal xhair if it's only an item on another level.- -- For enemies, OTOH, capture xhair to alarm player.- lidV <- viewedLevelUI- when (lid == lidV) $ do- bag <- getsState $ getFloorBag lid p- modifySession $ \sess ->- sess {sxhair = TPoint (TItem bag) lidV p}- itemVerbMU iid kit "be spotted" c- stopPlayBack- CTrunk{} -> return ()- _ -> return () -- seen already (has a slot assigned)- when verbose2 $ case c of- CActor aid store | store `elem` [CEqp, CInv] -> do- -- Actor fetching an item from shared stash, most probably.- bUI <- getsSession $ getActorUI aid- subject <- partActorLeader aid bUI- let ownW = ppCStoreWownW False store subject- verb = MU.Text $ makePhrase $ "be added to" : ownW- itemVerbMU iid kit verb c- _ -> return ()+ UpdSpotItem verbose2 iid _ kit c -> spotItem verbose2 iid kit c+ {- UpdLoseItem False _ _ _ _ -> return () -- The message is rather cryptic, so let's disable it until it's decided -- if anemy inventories should be displayed, etc.- {- UpdLoseItem True iid _ kit c@(CActor aid store) | store /= CSha -> do -- Actor putting an item into shared stash, most probably. side <- getsClient sside@@ -158,6 +128,9 @@ when (bfid b == side) $ itemVerbMU iid kit verb c -} UpdLoseItem{} -> return ()+ UpdSpotItemBag c bag _ ->+ mapWithKeyM_ (\iid kit -> spotItem True iid kit c) bag+ UpdLoseItemBag{} -> return () -- Move actors and items. UpdMoveActor aid source target -> moveActor aid source target UpdWaitActor aid _ -> when verbose $ aidVerbMU aid "wait"@@ -196,11 +169,9 @@ | otherwise -> do when (n >= bhp b && bhp b > 0) $ actorVerbMU aid bUI "return from the brink of death"- mleader <- getsClient _sleader+ mleader <- getsClient sleader when (Just aid == mleader) $ do- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid)- (EM.lookup aid actorAspect)+ ar <- getsState $ getActorAspect aid -- Regenerating actors never stop gaining HP, so we need to stop -- reporting it after they reach full HP for the first time. when (bhp b >= xM (aMaxHP ar) && bhp b - n < xM (aMaxHP ar)) $ do@@ -226,26 +197,26 @@ -- Change faction attributes. UpdQuitFaction fid _ toSt -> quitFactionUI fid toSt UpdLeadFaction fid (Just source) (Just target) -> do- side <- getsClient sside- when (fid == side) $ do- fact <- getsState $ (EM.! side) . sfactionD- lidV <- viewedLevelUI- when (isAIFact fact) $ markDisplayNeeded lidV- -- This faction can't run with multiple actors, so this is not- -- a leader change while running, but rather server changing- -- their leader, which the player should be alerted to.- when (noRunWithMulti fact) stopPlayBack- actorD <- getsState sactorD- case EM.lookup source actorD of- Just sb | bhp sb <= 0 -> assert (not $ bproj sb) $ do- -- Regardless who the leader is, give proper names here, not 'you'.- sbUI <- getsSession $ getActorUI source- tbUI <- getsSession $ getActorUI target- let subject = partActor tbUI- object = partActor sbUI- msgAdd $ makeSentence [ MU.SubjectVerbSg subject "take command"- , "from", object ]- _ -> return ()+ fact <- getsState $ (EM.! fid) . sfactionD+ lidV <- viewedLevelUI+ when (isAIFact fact) $ markDisplayNeeded lidV+ -- This faction can't run with multiple actors, so this is not+ -- a leader change while running, but rather server changing+ -- their leader, which the player should be alerted to.+ when (noRunWithMulti fact) stopPlayBack+ actorD <- getsState sactorD+ case EM.lookup source actorD of+ Just sb | bhp sb <= 0 -> assert (not $ bproj sb) $ do+ -- Regardless who the leader is, give proper names here, not 'you'.+ sbUI <- getsSession $ getActorUI source+ tbUI <- getsSession $ getActorUI target+ let subject = partActor tbUI+ object = partActor sbUI+ msgAdd $ makeSentence [ MU.SubjectVerbSg subject "take command"+ , "from", object ]+ _ -> return ()+ lookAtMove target+ UpdLeadFaction _ Nothing (Just target) -> lookAtMove target UpdLeadFaction{} -> return () UpdDiplFaction fid1 fid2 _ toDipl -> do name1 <- getsState $ gname . (EM.! fid1) . sfactionD@@ -263,23 +234,41 @@ when (fid == side) $ setFrontAutoYes b UpdRecordKill{} -> return () -- Alter map.- UpdAlterTile lid _ _ _ -> markDisplayNeeded lid+ UpdAlterTile lid p fromTile toTile -> do+ markDisplayNeeded lid+ Kind.COps{cotile=Kind.Ops{okind}} <- getsState scops+ let feats = TK.tfeature $ okind fromTile+ toAlter feat =+ case feat of+ TK.OpenTo tgroup -> Just tgroup+ TK.CloseTo tgroup -> Just tgroup+ TK.ChangeTo tgroup -> Just tgroup+ _ -> Nothing+ groupsToAlterTo = mapMaybe toAlter feats+ freq = map fst $ filter (\(_, q) -> q > 0) $ TK.tfreq $ okind toTile+ when (null $ intersect freq groupsToAlterTo) $ do+ -- Player notices @fromTile can't be altered into @toTIle@,+ -- which is uncanny, so we produce a message.+ -- This happens when the player missed an earlier search of the tile+ -- performed by another faction.+ let subject = "" -- a hack, we we don't handle adverbs well+ verb = "turn into"+ msg = makeSentence [ "the", MU.Text $ TK.tname $ okind fromTile+ , "at position", MU.Text $ tshow p+ , "suddenly" -- adverb+ , MU.SubjectVerbSg subject verb+ , MU.AW $ MU.Text $ TK.tname $ okind toTile ]+ msgAdd msg UpdAlterExplorable{} -> return ()- UpdSearchTile aid p toTile -> do+ UpdSearchTile aid _p toTile -> do Kind.COps{cotile = cotile@Kind.Ops{okind}} <- getsState scops- b <- getsState $ getActorBody aid- lvl <- getLevel $ blid b subject <- partAidLeader aid- let t = lvl `at` p- fromTile = Tile.hideAs cotile toTile- verb | t == toTile = "confirm"- | otherwise = "reveal"+ let fromTile = fromJust $ Tile.hideAs cotile toTile subject2 = MU.Text $ TK.tname $ okind fromTile- verb2 = "be" object = MU.Text $ TK.tname $ okind toTile- let msg = makeSentence [ MU.SubjectVerbSg subject verb+ let msg = makeSentence [ MU.SubjectVerbSg subject "reveal" , "that the"- , MU.SubjectVerbSg subject2 verb2+ , MU.SubjectVerbSg subject2 "be" , MU.AW object ] unless (subject2 == object) $ msgAdd msg UpdHideTile{} -> return ()@@ -292,25 +281,18 @@ UpdTimeItem{} -> return () UpdAgeGame{} -> do sdisplayNeeded <- getsSession sdisplayNeeded- when sdisplayNeeded $ do- -- Push the frame depicting the current level to the frame queue.- -- Only one line of the report is shown, as in animations,- -- because it may not be our turn, so we can't clear the message- -- to see what is underneath.- lidV <- viewedLevelUI- report <- getReportUI- let truncRep = [renderReport report]- frame <- drawOverlay ColorFull False truncRep lidV- displayFrames lidV [Just frame]+ when sdisplayNeeded pushFrame UpdUnAgeGame{} -> return ()- UpdDiscover c iid _ _ -> discover c oldCli iid+ UpdDiscover c iid _ _ -> discover c iid UpdCover{} -> return () -- don't spam when doing undo- UpdDiscoverKind c iid _ -> discover c oldCli iid+ UpdDiscoverKind{} -> return () -- don't spam when server tweaks stuff UpdCoverKind{} -> return () -- don't spam when doing undo- UpdDiscoverSeed c iid _ -> discover c oldCli iid+ UpdDiscoverSeed{} -> return () -- don't spam when server tweaks stuff UpdCoverSeed{} -> return () -- don't spam when doing undo+ UpdDiscoverServer{} -> error "server command leaked to client"+ UpdCoverServer{} -> error "server command leaked to client" UpdPerception{} -> return ()- UpdRestart fid _ _ _ _ _ -> do+ UpdRestart fid _ _ _ _ -> do sstart <- getsSession sstart when (sstart == 0) resetSessionStart history <- getsSession shistory@@ -319,9 +301,11 @@ let title = rtitle $ Kind.stdRuleset corule msgAdd $ "Welcome to" <+> title <> "!" -- Generate initial history. Only for UI clients.- sconfig <- getsSession sconfig- shistory <- defaultHistory $ configHistoryMax sconfig+ sUIOptions <- getsSession sUIOptions+ shistory <- defaultHistory $ uHistoryMax sUIOptions modifySession $ \sess -> sess {shistory}+ lid <- getArenaUI+ lvl <- getLevel lid mode <- getGameMode curChal <- getsClient scurChal fact <- getsState $ (EM.! fid) . sfactionD@@ -329,7 +313,8 @@ [] -> True [(_, 1, _)] -> True _ -> False- msgAdd $ "New game started in" <+> mname mode <+> "mode." <+> mdesc mode+ msgAdd $ "New game started in" <+> mname mode <+> "mode."+ <+> mdesc mode <+> ldesc lvl <+> if cwolf curChal && not loneMode then "Being a lone wolf, you start without companions." else ""@@ -345,8 +330,11 @@ fact <- getsState $ (EM.! fid) . sfactionD setFrontAutoYes $ isAIFact fact unless (isAIFact fact) $ do+ lid <- getArenaUI+ lvl <- getLevel lid mode <- getGameMode- promptAdd $ "Continuing" <+> mname mode <> "." <+> mdesc mode+ promptAdd $ "Continuing" <+> mname mode <> "."+ <+> mdesc mode <+> ldesc lvl <+> "Are you up for the challenge?" slides <- reportToSlideshow [K.spaceKM, K.escKM] km <- getConfirms ColorFull [K.spaceKM, K.escKM] slides@@ -354,10 +342,6 @@ UpdResumeServer{} -> return () UpdKillExit{} -> frontendShutdown UpdWriteSave -> when verbose $ promptAdd "Saving backup."- UpdMsgAll "SortSlots" -> do -- hack- side <- getsClient sside- sortSlots side Nothing- UpdMsgAll msg -> msgAdd msg updateItemSlot :: MonadClientUI m => CStore -> Maybe ActorId -> ItemId -> m SlotChar@@ -391,8 +375,7 @@ markDisplayNeeded :: MonadClientUI m => LevelId -> m () markDisplayNeeded lid = do lidV <- viewedLevelUI- when (lidV == lid) $- modifySession $ \sess -> sess {sdisplayNeeded = True}+ when (lidV == lid) $ modifySession $ \sess -> sess {sdisplayNeeded = True} updateItemSlotSide :: MonadClientUI m => CStore -> ActorId -> ItemId -> m SlotChar@@ -424,7 +407,6 @@ adjOur = filter our adjacentAssocs unless (null adjOur) stopPlayBack --- | Sentences such as \"Dog barks loudly.\". actorVerbMU :: MonadClientUI m => ActorId -> ActorUI -> MU.Part -> m () actorVerbMU aid bUI verb = do subject <- partActorLeader aid bUI@@ -440,7 +422,7 @@ itemVerbMU iid kit@(k, _) verb c = assert (k > 0) $ do lid <- getsState $ lidFromC c localTime <- getsState $ getLocalTime lid- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull side <- getsClient sside factionD <- getsState sfactionD let subject = partItemWs side factionD@@ -464,7 +446,7 @@ case iid `EM.lookup` bag of Nothing -> error $ "" `showFailure` (aid, verb, iid, cstore) Just kit@(k, _) -> do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull let lid = blid body localTime <- getsState $ getLocalTime lid subject <- partAidLeader aid@@ -488,7 +470,7 @@ msgDuplicateScrap :: MonadClientUI m => m Bool msgDuplicateScrap = do- report <- getsSession _sreport+ report <- getsSession sreport history <- getsSession shistory let (lastMsg, repRest) = lastMsgOfReport report repLast = lastReportOfHistory history@@ -508,12 +490,14 @@ createActorUI born aid body = do side <- getsClient sside fact <- getsState $ (EM.! bfid body) . sfactionD+ globalTime <- getsState stime+ localTime <- getsState $ getLocalTime $ blid body mbUI <- getsSession $ EM.lookup aid . sactorUI bUI <- case mbUI of Just bUI -> return bUI Nothing -> do trunk <- getsState $ getItemBody $ btrunk body- Config{configHeroNames} <- getsSession sconfig+ UIOptions{uHeroNames} <- getsSession sUIOptions let isBlast = actorTrunkIsBlast trunk baseColor = flavourToColor $ jflavour trunk basePronoun | not (bproj body) && fhasGender (gplayer fact) = "he"@@ -525,7 +509,7 @@ if gcolor fact /= Color.BrWhite then (nameFromNumber (fname $ gplayer fact) k, "he") else fromMaybe (nameFromNumber (fname $ gplayer fact) k, "he")- $ lookup k configHeroNames+ $ lookup k uHeroNames (n, bsymbol) <- if | bproj body -> return (0, if isBlast then jsymbol trunk else '*') | baseColor /= Color.BrWhite -> return (0, jsymbol trunk)@@ -538,7 +522,6 @@ n = fromJust $ elemIndex False mhs return (n, if 0 < n && n < 10 then Char.intToDigit n else '@') factionD <- getsState sfactionD- localTime <- getsState $ getLocalTime $ blid body let (bname, bpronoun) = if | bproj body -> let adj | length (btrajectory body) < 5 = "falling"@@ -558,10 +541,12 @@ modifySession $ \sess -> sess {sactorUI = EM.insert aid bUI $ sactorUI sess} return bUI- let verb = if born- then MU.Text $ "appear"- <+> if bfid body == side then "" else "suddenly"- else "be spotted"+ let verb = MU.Text $+ if born+ then if globalTime == timeZero+ then "be here"+ else "appear" <+> if bfid body == side then "" else "suddenly"+ else "be spotted" mapM_ (\(iid, store) -> void $ updateItemSlotSide store aid iid) (getCarriedIidCStore body) when (bfid body /= side) $ do@@ -582,7 +567,6 @@ else do actorVerbMU aid bUI verb animate (blid body) $ actorX (bpos body)- lookAtMove aid destroyActorUI :: MonadClientUI m => Bool -> ActorId -> Actor -> m () destroyActorUI destroy aid b = do@@ -613,7 +597,7 @@ modifySession $ \sess -> sess {sselected = upd $ sselected sess} when destroy $ do displayMore ColorBW "Alas!"- mleader <- getsClient _sleader+ mleader <- getsClient sleader when (isJust mleader) -- This is especially handy when the dead actor was a leader -- on a different level than the new one:@@ -621,6 +605,48 @@ -- If pushed, animate spotting again, to draw attention to pushing. markDisplayNeeded (blid b) +spotItem :: MonadClientUI m+ => Bool -> ItemId -> ItemQuant -> Container -> m ()+spotItem verbose iid kit c = do+ -- This is due to a move, or similar, which will be displayed,+ -- so no extra @markDisplayNeeded@ needed here and in similar places.+ ItemSlots itemSlots _ <- getsSession sslots+ case lookup iid $ map swap $ EM.assocs itemSlots of+ Nothing -> -- never seen or would have a slot+ case c of+ CActor aid store ->+ -- Most probably an actor putting item in or out of shared stash.+ void $ updateItemSlotSide store aid iid+ CEmbed{} -> return ()+ CFloor lid p -> do+ void $ updateItemSlot CGround Nothing iid+ sxhairOld <- getsSession sxhair+ case sxhairOld of+ TEnemy{} -> return () -- probably too important to overwrite+ TPoint TEnemyPos{} _ _ -> return ()+ _ -> do+ -- Don't steal xhair if it's only an item on another level.+ -- For enemies, OTOH, capture xhair to alarm player.+ lidV <- viewedLevelUI+ when (lid == lidV) $ do+ bag <- getsState $ getFloorBag lid p+ modifySession $ \sess ->+ sess {sxhair = TPoint (TItem bag) lidV p}+ itemVerbMU iid kit "be spotted" c+ stopPlayBack+ CTrunk{} -> return ()+ _ -> return () -- this item or another with the same @iid@+ -- seen already (has a slot assigned), so old news+ when verbose $ case c of+ CActor aid store | store `elem` [CEqp, CInv, CGround, CSha] -> do+ -- Actor fetching an item from or to shared stash, most probably.+ bUI <- getsSession $ getActorUI aid+ subject <- partActorLeader aid bUI+ let ownW = ppCStoreWownW False store subject+ verb = MU.Text $ makePhrase $ "be added to" : ownW+ itemVerbMU iid kit verb c+ _ -> return ()+ moveActor :: MonadClientUI m => ActorId -> Point -> Point -> m () moveActor aid source target = do -- If source and target tile distant, assume it's a teleportation@@ -661,7 +687,7 @@ b <- getsState $ getActorBody aid fact <- getsState $ (EM.! bfid b) . sfactionD let underAI = isAIFact fact- mleader <- getsClient _sleader+ mleader <- getsClient sleader ItemSlots itemSlots _ <- getsSession sslots case lookup iid $ map swap $ EM.assocs itemSlots of Just slastSlot -> do@@ -752,7 +778,7 @@ sli <- overlayToSlideshow (lysize + 1) [K.spaceKM, K.escKM] io return (bag, sli, tot) localTime <- getsState $ getLocalTime arena- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull ItemSlots lSlots _ <- getsSession sslots let keyOfEKM (Left km) = km keyOfEKM (Right SlotChar{slotChar}) = [K.mkChar slotChar]@@ -806,15 +832,13 @@ fadeOutOrIn True _ -> return () -discover :: MonadClientUI m => Container -> StateClient -> ItemId -> m ()-discover c oldCli iid = do- let StateClient{sdiscoKind=oldDiscoKind, sdiscoAspect=oldDiscoAspect} = oldCli- cstore = storeFromC c+discover :: MonadClientUI m => Container -> ItemId -> m ()+discover c iid = do+ let cstore = storeFromC c lid <- getsState $ lidFromC c- discoKind <- getsClient sdiscoKind- discoAspect <- getsClient sdiscoAspect+ globalTime <- getsState stime localTime <- getsState $ getLocalTime lid- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull bag <- getsState $ getContainerBag c side <- getsClient sside factionD <- getsState sfactionD@@ -839,14 +863,10 @@ namePhrase = MU.Phrase $ [secretName, secretAEText] ++ nameWhere msg = makeSentence ["the", MU.SubjectVerbSg namePhrase "turn out to be", knownName]- jix = jkindIx $ itemBase itemFull- ik = itemKind $ fromJust $ itemDisco itemFull -- Compare descriptions of all aspects and effects to determine -- if the discovery was meaningful to the player.- unless (isOurOrgan- || (EM.member jix discoKind == EM.member jix oldDiscoKind- && (EM.member iid discoAspect == EM.member iid oldDiscoAspect- || not (aspectsRandom ik)))) $+ unless (globalTime == timeZero -- don't spam about initial equipment+ || isOurOrgan) $ -- assume own faction organs known intuitively msgAdd msg -- * RespSfxAtomicUI@@ -949,26 +969,36 @@ msgAdd $ makeSentence [MU.SubjectVerbSg subject verb, MU.Text fidSourceName, "control"] stopPlayBack- IK.Impress -> actorVerbMU aid bUI $ "be awestruck"+ IK.Impress -> actorVerbMU aid bUI "be awestruck" IK.Summon grp p -> do let verb = if bproj b then "lure" else "summon"- object = if p == 1- then MU.Text $ tshow grp- else MU.Ws $ MU.Text $ tshow grp -- avoid "1 + dl 3"+ object = (if p == 1 -- avoid "2 + 4 dl 3"+ then MU.AW+ else MU.Ws) $ MU.Text $ tshow grp actorVerbMU aid bUI $ MU.Phrase [verb, object]- IK.Ascend True -> actorVerbMU aid bUI "find a way upstairs"- IK.Ascend False -> actorVerbMU aid bUI "find a way downstairs"+ IK.Ascend True -> do+ actorVerbMU aid bUI "find a way upstairs"+ (lid, _) <- getsState $ whereTo (blid b) (bpos b) (Just True)+ . sdungeon+ lvl <- getLevel lid+ msgAdd $ ldesc lvl+ IK.Ascend False -> do+ actorVerbMU aid bUI "find a way downstairs"+ (lid, _) <- getsState $ whereTo (blid b) (bpos b) (Just False)+ . sdungeon+ lvl <- getLevel lid+ msgAdd $ ldesc lvl IK.Escape{} -> return () IK.Paralyze{} -> actorVerbMU aid bUI "be paralyzed" IK.InsertMove{} -> actorVerbMU aid bUI "act with extreme speed"- IK.Teleport t | t <= 8 -> actorVerbMU aid bUI "blink"+ IK.Teleport t | Dice.maxDice t <= 9 -> actorVerbMU aid bUI "blink" IK.Teleport{} -> actorVerbMU aid bUI "teleport" IK.CreateItem{} -> return () IK.DropItem _ _ COrgan _ -> return () IK.DropItem{} -> actorVerbMU aid bUI "be stripped" IK.PolyItem -> do localTime <- getsState $ getLocalTime $ blid b- allAssocs <- fullAssocsClient aid [CGround]+ allAssocs <- getsState $ fullAssocs aid [CGround] case allAssocs of [] -> return () -- invisible items? (_, ItemFull{..}) : _ -> do@@ -983,7 +1013,7 @@ [ MU.SubjectVerbSg subject verb , "the", secretName, secretAEText, store ] IK.Identify -> do- allAssocs <- fullAssocsClient aid [CGround]+ allAssocs <- getsState $ fullAssocs aid [CGround] case allAssocs of [] -> return () -- invisible items? (_, ItemFull{..}) : _ -> do@@ -1026,8 +1056,9 @@ IK.Temporary t -> actorVerbMU aid bUI $ MU.Text t IK.Unique -> error $ "" `showFailure` sfx IK.Periodic -> error $ "" `showFailure` sfx+ IK.Composite{} -> error $ "" `showFailure` sfx SfxMsgFid _ sfxMsg -> do- mleader <- getsClient _sleader+ mleader <- getsClient sleader case mleader of Just{} -> return () -- will display stuff when leader moves Nothing -> do@@ -1036,6 +1067,9 @@ recordHistory msg <- ppSfxMsg sfxMsg msgAdd msg+ SfxSortSlots -> do+ side <- getsClient sside+ sortSlots side Nothing ppSfxMsg :: MonadClientUI m => SfxMsg -> m Text ppSfxMsg sfxMsg = case sfxMsg of@@ -1101,8 +1135,7 @@ return $! makeSentence [MU.SubjectVerbSg subject verb] SfxEscapeImpossible -> return "This faction doesn't want to escape outside." SfxTransImpossible -> return "Translocation not possible."- SfxIdentifyNothing store -> return $!- "Nothing to identify" <+> ppCStoreIn store <> "."+ SfxIdentifyNothing -> return "Nothing to identify." SfxPurposeNothing store -> return $! "The purpose of repurpose cannot be availed without an item" <+> ppCStoreIn store <> "."@@ -1117,18 +1150,18 @@ aidPhrase <- partActorLeader aid bUI factionD <- getsState sfactionD localTime <- getsState $ getLocalTime (blid b)- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull let itemFull = itemToF iid (1, []) (_, _, name, stats) = partItem (bfid b) factionD cstore localTime itemFull storeOwn = ppCStoreWownW True cstore aidPhrase- cond = if jsymbol (itemBase itemFull) == '+' then ["condition"] else []+ cond = ["condition" | jsymbol (itemBase itemFull) == '+'] return $! makeSentence $ ["the", name, stats] ++ cond ++ storeOwn ++ ["will now last longer"] setLastSlot :: MonadClientUI m => ActorId -> ItemId -> CStore -> m () setLastSlot aid iid cstore = do- mleader <- getsClient _sleader+ mleader <- getsClient sleader when (Just aid == mleader) $ do ItemSlots itemSlots _ <- getsSession sslots case lookup iid $ map swap $ EM.assocs itemSlots of@@ -1138,14 +1171,13 @@ strike :: MonadClientUI m => Bool -> ActorId -> ActorId -> ItemId -> CStore -> m () strike catch source target iid cstore = assert (source /= target) $ do- actorAspect <- getsClient sactorAspect tb <- getsState $ getActorBody target tbUI <- getsSession $ getActorUI target sourceSeen <- getsState $ memActor source (blid tb) (ps, hurtMult) <- if sourceSeen then do- hurtMult <- getsState $ armorHurtBonus actorAspect source target- itemToF <- itemToFullClient+ hurtMult <- getsState $ armorHurtBonus source target+ itemToF <- getsState itemToFull sb <- getsState $ getActorBody source sbUI <- getsSession $ getActorUI source spart <- partActorLeader source sbUI@@ -1168,7 +1200,7 @@ else partItemShortAW side factionD cstore localTime msg | bhp tb <= 0 -- incapacitated, so doesn't actively block || hurtMult > 90 -- at most minor armor- || jdamage (itemBase itemFull) <= 0 = makeSentence $+ || jdamage (itemBase itemFull) == 0 = makeSentence $ [MU.SubjectVerbSg spart verb, tpart] ++ if bproj sb then []
Game/LambdaHack/Client/UI/DrawM.hs view
@@ -15,65 +15,63 @@ import Game.LambdaHack.Common.Prelude -import Control.Arrow (first)-import Control.Monad.ST.Strict+import Control.Monad.ST.Strict import qualified Data.Char as Char import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Ord+import Data.Ord import qualified Data.Text as T import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as VM-import Data.Word (Word16)-import GHC.Exts (inline)+import Data.Word (Word16)+import GHC.Exts (inline) import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Client.BfsM-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.ItemDescription-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.BfsM+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.ItemDescription+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind import qualified Game.LambdaHack.Common.KindOps as KindOps-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.ModeKind as MK-import Game.LambdaHack.Content.TileKind (TileKind, isUknownSpace)+import Game.LambdaHack.Content.TileKind (TileKind, isUknownSpace) import qualified Game.LambdaHack.Content.TileKind as TK targetDesc :: MonadClientUI m => Maybe Target -> m (Maybe Text, Maybe Text) targetDesc mtarget = do arena <- getArenaUI lidV <- viewedLevelUI- mleader <- getsClient _sleader+ mleader <- getsClient sleader case mtarget of Just (TEnemy aid _) -> do side <- getsClient sside b <- getsState $ getActorBody aid bUI <- getsSession $ getActorUI aid- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid)- (EM.lookup aid actorAspect)- percentage = 100 * bhp b `div` xM (max 5 $ aMaxHP ar)+ ar <- getsState $ getActorAspect aid+ let percentage = 100 * bhp b `div` xM (max 5 $ aMaxHP ar) chs n = "[" <> T.replicate n "*" <> T.replicate (4 - n) "_" <> "]" stars = chs $ fromEnum $ max 0 $ min 4 $ percentage `div` 20@@ -95,7 +93,7 @@ [] -> return $! "exact spot" <+> tshow p [(iid, kit@(k, _))] -> do localTime <- getsState $ getLocalTime lid- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull side <- getsClient sside factionD <- getsState sfactionD let (_, _, name, stats) =@@ -206,7 +204,7 @@ Level{lxsize, lysize, ltile=PointArray.Array{avector}} <- getLevel drawnLevelId totVisible <- totalVisible <$> getPerFid drawnLevelId- mleader <- getsClient _sleader+ mleader <- getsClient sleader xhairPosRaw <- xhairToPos let xhairPos = fromMaybe originPoint xhairPosRaw s <- getState@@ -224,7 +222,7 @@ | pathGoal == xhairPos -> return tapPath _ -> getCachePath aid xhairPos) mleader let lpath = if null bline then []- else maybe [] (\mp -> case mp of+ else maybe [] (\case NoPath -> [] AndPath {pathList} -> pathList) mpath xhairHere = find (\(_, m) -> xhairPos == bpos m)@@ -271,7 +269,7 @@ SessionUI{sselected} <- getSession Level{lxsize, lactor} <- getLevel drawnLevelId side <- getsClient sside- mleader <- getsClient _sleader+ mleader <- getsClient sleader s <- getState sactorUI <- getsSession sactorUI let {-# INLINE viewActor #-}@@ -316,7 +314,7 @@ totVisible <- totalVisible <$> getPerFid drawnLevelId mxhairPos <- xhairToPos mtgtPos <- do- mleader <- getsClient _sleader+ mleader <- getsClient sleader case mleader of Nothing -> return Nothing Just leader -> do@@ -365,7 +363,7 @@ drawFrameStatus :: MonadClientUI m => LevelId -> m AttrLine drawFrameStatus drawnLevelId = do SessionUI{sselected, saimMode, swaitTimes, sitemSel} <- getSession- mleader <- getsClient _sleader+ mleader <- getsClient sleader xhairPos <- xhairToPos tgtPos <- leaderTgtToPos mbfs <- maybe (return Nothing) (\aid -> Just <$> getCacheBfs aid) mleader@@ -443,7 +441,7 @@ Nothing -> return $! tgtBlurb Just kit@(k, _) -> do localTime <- getsState $ getLocalTime (blid b)- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull factionD <- getsState sfactionD let (_, _, name, stats) = partItem (bfid b) factionD@@ -465,9 +463,6 @@ <+:> targetStatus -- | Draw the whole screen: level map and status area.--- Pass at most a single page if overlay of text unchanged--- to the frontends to display separately or overlay over map,--- depending on the frontend. drawBaseFrame :: MonadClientUI m => ColorMode -> LevelId -> m FrameForall drawBaseFrame dm drawnLevelId = do Level{lxsize, lysize} <- getLevel drawnLevelId@@ -492,7 +487,7 @@ -- level descriptions (currently enforced max). drawArenaStatus :: Bool -> Level -> Int -> AttrLine drawArenaStatus explored- Level{ldepth=AbsDepth ld, ldesc, lseen, lexplorable}+ Level{ldepth=AbsDepth ld, lname, lseen, lexplorable} width = let seenN = 100 * lseen `div` max 1 lexplorable seenTxt | explored || seenN >= 100 = "all"@@ -500,20 +495,18 @@ lvlN = T.justifyLeft 2 ' ' (tshow ld) seenStatus = "[" <> seenTxt <+> "seen]" in textToAL $ T.justifyLeft width ' '- $ T.take 29 (lvlN <+> T.justifyLeft 26 ' ' ldesc) <+> seenStatus+ $ T.take 29 (lvlN <+> T.justifyLeft 26 ' ' lname) <+> seenStatus drawLeaderStatus :: MonadClient m => Int -> m AttrLine drawLeaderStatus waitT = do let calmHeaderText = "Calm" hpHeaderText = "HP"- mleader <- getsClient _sleader+ mleader <- getsClient sleader case mleader of Just leader -> do- actorAspect <- getsClient sactorAspect+ ar <- getsState $ getActorAspect leader s <- getState- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- showTrunc :: Show a => a -> String+ let showTrunc :: Show a => a -> String showTrunc = (\t -> if length t > 3 then "***" else t) . show (darkL, bracedL, hpDelta, calmDelta, ahpS, bhpS, acalmS, bcalmS) =@@ -554,22 +547,22 @@ drawLeaderDamage :: MonadClientUI m => Int -> m AttrLine drawLeaderDamage width = do- mleader <- getsClient _sleader+ mleader <- getsClient sleader let addColor = map (Color.attrChar2ToW32 Color.BrCyan) stats <- case mleader of Just leader -> do- allAssocsRaw <- fullAssocsClient leader [CEqp, COrgan]+ allAssocsRaw <- getsState $ fullAssocs leader [CEqp, COrgan] let allAssocs = filter (isMelee . itemBase . snd) allAssocsRaw actorSk <- leaderSkillsClientUI- actorAspect <- getsClient sactorAspect- strongest <- pickWeaponM Nothing allAssocs actorSk actorAspect leader+ actorAspect <- getsState sactorAspect+ strongest <- pickWeaponM Nothing allAssocs actorSk leader let damage = case strongest of [] -> "0" (_, (_, itemFull)) : _ -> let tdice = show $ jdamage $ itemBase itemFull bonusRaw = aHurtMelee $ actorAspect EM.! leader bonus = min 200 $ max (-200) bonusRaw- unknownBonus = unknownMelee $ map snd allAssocs+ unknownBonus = unknownMeleeBonus $ map snd allAssocs tbonus = if bonus == 0 then if unknownBonus then "+?" else "" else (if bonus > 0 then "+" else "")@@ -585,7 +578,7 @@ drawSelected :: MonadClientUI m => LevelId -> Int -> ES.EnumSet ActorId -> m (Int, AttrLine) drawSelected drawnLevelId width selected = do- mleader <- getsClient _sleader+ mleader <- getsClient sleader side <- getsClient sside sactorUI <- getsSession sactorUI ours <- getsState $ filter (not . bproj . snd)
Game/LambdaHack/Client/UI/EffectDescription.hs view
@@ -1,9 +1,12 @@--- | Description of effects. No operation in this module--- involves state or monad types.+-- | Description of effects. module Game.LambdaHack.Client.UI.EffectDescription- ( effectToSuffix, featureToSuff, kindAspectToSuffix, affixDice- , featureToSentence, slotToSentence- , slotToName, slotToDesc, slotToDecorator, statSlots+ ( effectToSuffix+ , slotToSentence, slotToName, slotToDesc, slotToDecorator, statSlots+ , kindAspectToSuffix, featureToSuff, featureToSentence, affixDice+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , tmodToSuff, affixBonus, wrapInParens, wrapInChevrons+#endif ) where import Prelude ()@@ -13,12 +16,12 @@ import qualified Data.Text as T import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Common.Ability-import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Ability+import Game.LambdaHack.Common.Actor import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind -- | Suffix to append to a basic content name if the content causes the effect. --@@ -36,7 +39,7 @@ ELabel _ -> "" -- printed specially EqpSlot{} -> "" -- used in @slotToSentence@ instead Burn d -> wrapInParens (tshow d- <+> if d > 1 then "burns" else "burn")+ <+> if Dice.maxDice d > 1 then "burns" else "burn") Explode t -> "of" <+> tshow t <+> "explosion" RefillHP p | p > 0 -> "of healing" <+> wrapInParens (affixBonus p) RefillHP 0 -> error $ "" `showFailure` effect@@ -68,9 +71,10 @@ Nothing -> tshow dice <+> "moves" Just p -> makePhrase [MU.CarWs p "move"] in "of speed surge for" <+> moves- Teleport dice | dice <= 0 ->+ Teleport dice | Dice.minDice dice <= 0 -> error $ "" `showFailure` effect- Teleport dice | dice <= 9 -> "of blinking" <+> wrapInParens (tshow dice)+ Teleport dice | Dice.maxDice dice <= 9 ->+ "of blinking" <+> wrapInParens (tshow dice) Teleport dice -> "of teleport" <+> wrapInParens (tshow dice) CreateItem COrgan grp tim -> let stime = if tim == TimerNone then "" else "for" <+> tshow tim <> ":"@@ -86,7 +90,7 @@ verb = if store == COrgan then "nullify" else "drop" in "of" <+> verb <+> ntxt <+> tshow grp -- TMI: <+> ppCStore store PolyItem -> "of repurpose on the ground"- Identify -> "of identify on the ground"+ Identify -> "of identify" Detect radius -> "of detection" <+> wrapInParens (tshow radius) DetectActor radius -> "of actor detection" <+> wrapInParens (tshow radius) DetectItem radius -> "of item detection" <+> wrapInParens (tshow radius)@@ -107,6 +111,7 @@ Temporary _ -> "" Unique -> "" -- marked by capital letters in name Periodic -> "" -- printed specially+ Composite effs -> T.intercalate " and then " $ map effectToSuffix effs slotToSentence :: EqpSlot -> Text slotToSentence es = case es of
Game/LambdaHack/Client/UI/Frame.hs view
@@ -1,21 +1,49 @@+{-# LANGUAGE RankNTypes #-} -- | Screen frames. module Game.LambdaHack.Client.UI.Frame- ( SingleFrame(..), Frames+ ( FrameST, FrameForall(..), writeLine+ , SingleFrame(..), Frames , blankSingleFrame, overlayFrame, overlayFrameWithLines+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , truncateAttrLine+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Word (Word32)+import Control.Monad.ST.Strict+import qualified Data.Vector.Generic as G+import qualified Data.Vector.Unboxed as U+import qualified Data.Vector.Unboxed.Mutable as VM+import Data.Word -import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Common.Color-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Client.UI.Overlay+import qualified Game.LambdaHack.Common.Color as Color+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray +type FrameST s = G.Mutable U.Vector s Word32 -> ST s ()++-- | Efficiently composable representation of an operation+-- on a frame, that is, on a mutable vector. When the composite operation+-- is eventually performed, the vector is frozen to become a 'SingleFrame'.+newtype FrameForall = FrameForall {unFrameForall :: forall s. FrameST s}++-- | Representation of an operation of overwriting a frame with a single line+-- at the given row.+writeLine :: Int -> AttrLine -> FrameForall+{-# INLINE writeLine #-}+writeLine offset l = FrameForall $ \v -> do+ let writeAt _ [] = return ()+ writeAt off (ac32 : rest) = do+ VM.write v off (Color.attrCharW32 ac32)+ writeAt (off + 1) rest+ writeAt offset l+ -- | An overlay that fits on the screen (or is meant to be truncated on display) -- and is padded to fill the whole screen -- and is displayed as a single game screen frame.@@ -23,7 +51,7 @@ -- Note that we don't provide a list of color-highlighed positions separately, -- because overlays need to obscure not only map, but the highlights as well. newtype SingleFrame = SingleFrame- {singleFrame :: PointArray.GArray Word32 AttrCharW32}+ {singleFrame :: PointArray.GArray Word32 Color.AttrCharW32} deriving (Eq, Show) -- | Sequences of screen frames, including delays.@@ -33,11 +61,11 @@ blankSingleFrame = let lxsize = fst normalLevelBound + 1 lysize = snd normalLevelBound + 4- in SingleFrame $ PointArray.replicateA lxsize lysize spaceAttrW32+ in SingleFrame $ PointArray.replicateA lxsize lysize Color.spaceAttrW32 -- | Truncate the overlay: for each line, if it's too long, it's truncated -- and if there are too many lines, excess is dropped and warning is appended.-truncateLines :: Bool -> [AttrLine] -> [AttrLine]+truncateLines :: Bool -> Overlay -> Overlay truncateLines onBlank l = let lxsize = fst normalLevelBound + 1 lysize = snd normalLevelBound + 1@@ -57,24 +85,24 @@ truncateAttrLine w xs lenMax = case compare w (length xs) of LT -> let discarded = drop w xs- in if all (== spaceAttrW32) discarded+ in if all (== Color.spaceAttrW32) discarded then take w xs- else take (w - 1) xs ++ [attrChar2ToW32 BrBlack '$']+ else take (w - 1) xs ++ [Color.attrChar2ToW32 Color.BrBlack '$'] EQ -> xs- GT -> let xsSpace = if null xs || last xs == spaceAttrW32+ GT -> let xsSpace = if null xs || last xs == Color.spaceAttrW32 then xs- else xs ++ [spaceAttrW32]+ else xs ++ [Color.spaceAttrW32] whiteN = max (40 - length xsSpace) (1 + lenMax - length xsSpace)- in xsSpace ++ replicate whiteN spaceAttrW32+ in xsSpace ++ replicate whiteN Color.spaceAttrW32 -- | Overlays either the game map only or the whole empty screen frame. -- We assume the lines of the overlay are not too long nor too many.-overlayFrame :: Overlay -> FrameForall -> FrameForall+overlayFrame :: IntOverlay -> FrameForall -> FrameForall overlayFrame ov ff = FrameForall $ \v -> do unFrameForall ff v mapM_ (\(offset, l) -> unFrameForall (writeLine offset l) v) ov -overlayFrameWithLines :: Bool -> [AttrLine] -> FrameForall -> FrameForall+overlayFrameWithLines :: Bool -> Overlay -> FrameForall -> FrameForall overlayFrameWithLines onBlank l msf = let lxsize = fst normalLevelBound + 1 ov = map (\(y, al) -> (y * lxsize, al))
Game/LambdaHack/Client/UI/FrameM.hs view
@@ -1,6 +1,10 @@ -- | A set of Frame monad operations. module Game.LambdaHack.Client.UI.FrameM- ( drawOverlay, promptGetKey, stopPlayBack, animate, fadeOutOrIn+ ( pushFrame, promptGetKey, stopPlayBack, animate, fadeOutOrIn+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , drawOverlay, renderFrames+#endif ) where import Prelude ()@@ -9,38 +13,50 @@ import qualified Data.EnumMap.Strict as EM -import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.Animation-import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.DrawM-import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.Animation+import Game.LambdaHack.Client.UI.DrawM+import Game.LambdaHack.Client.UI.Frame 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.Overlay-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.State+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Client.UI.UIOptions+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.State -- | Draw the current level with the overlay on top. -- If the overlay is too long, it's truncated. -- Similarly, for each line of the overlay, if it's too wide, it's truncated. drawOverlay :: MonadClientUI m- => ColorMode -> Bool -> [AttrLine] -> LevelId -> m FrameForall+ => ColorMode -> Bool -> Overlay -> LevelId -> m FrameForall drawOverlay dm onBlank topTrunc lid = do mbaseFrame <- if onBlank then return $ FrameForall $ \_v -> return () else drawBaseFrame dm lid return $! overlayFrameWithLines onBlank topTrunc mbaseFrame +-- | Push the frame depicting the current level to the frame queue.+-- Only one line of the report is shown, as in animations,+-- because it may not be our turn, so we can't clear the message+-- to see what is underneath.+pushFrame :: MonadClientUI m => m ()+pushFrame = do+ lidV <- viewedLevelUI+ report <- getReportUI+ let truncRep = [renderReport report]+ frame <- drawOverlay ColorFull False truncRep lidV+ displayFrames lidV [Just frame]+ promptGetKey :: MonadClientUI m- => ColorMode -> [AttrLine] -> Bool -> [K.KM] -> m K.KM+ => ColorMode -> Overlay -> Bool -> [K.KM] -> m K.KM promptGetKey dm ov onBlank frontKeyKeys = do lidV <- viewedLevelUI keyPressed <- anyKeyPressed@@ -51,8 +67,8 @@ frontKeyFrame <- drawOverlay dm onBlank ov lidV displayFrames lidV [Just frontKeyFrame] modifySession $ \sess -> sess {slastPlay = kms}- Config{configRunStopMsgs} <- getsSession sconfig- when configRunStopMsgs $ promptAdd $ "Voicing '" <> tshow km <> "'."+ UIOptions{uRunStopMsgs} <- getsSession sUIOptions+ when uRunStopMsgs $ promptAdd $ "Voicing '" <> tshow km <> "'." return km _ : _ -> do -- We can't continue playback, so wipe out old slastPlay, srunning, etc.@@ -62,10 +78,14 @@ frontKeyFrame <- drawOverlay dm onBlank ov2 lidV connFrontendFrontKey frontKeyKeys frontKeyFrame [] -> do+ -- If we ask for a key, then we don't want to run any more+ -- and we want to avoid changing leader back to initial run leader+ -- at the nearest @stopPlayBack@, etc.+ modifySession $ \sess -> sess {srunning = Nothing} frontKeyFrame <- drawOverlay dm onBlank ov lidV connFrontendFrontKey frontKeyKeys frontKeyFrame- (seqCurrent, seqPrevious, k) <- getsSession slastRecord- let slastRecord = (km : seqCurrent, seqPrevious, k)+ LastRecord seqCurrent seqPrevious k <- getsSession slastRecord+ let slastRecord = LastRecord (km : seqCurrent) seqPrevious k modifySession $ \sess -> sess { slastRecord , sdisplayNeeded = False } return km@@ -74,7 +94,7 @@ stopPlayBack = do modifySession $ \sess -> sess { slastPlay = []- , slastRecord = ([], [], 0)+ , slastRecord = LastRecord [] [] 0 -- Needed to cancel macros that contain apostrophes. , swaitTimes = - abs (swaitTimes sess) }@@ -99,7 +119,7 @@ report <- getReportUI let truncRep = [renderReport report] basicFrame <- drawOverlay ColorFull False truncRep arena- snoAnim <- getsClient $ snoAnim . sdebugCli+ snoAnim <- getsClient $ snoAnim . soptions return $! if fromMaybe False snoAnim then [Just basicFrame] else renderAnim basicFrame anim
Game/LambdaHack/Client/UI/Frontend.hs view
@@ -2,15 +2,13 @@ -- | Display game data on the screen and receive user input -- using one of the available raw frontends and derived operations. module Game.LambdaHack.Client.UI.Frontend- ( -- * Connection types- FrontReq(..), ChanFrontend(..), KMP(..)+ ( -- * Connection and initialization+ FrontReq(..), ChanFrontend(..), chanFrontendIO -- * Re-exported part of the raw frontend , frontendName- -- * Derived operations- , chanFrontendIO #ifdef EXPOSE_INTERNAL -- * Internal operations- , FSession, getKey, fchanFrontend, display, defaultMaxFps, microInSec+ , FrontSetup, getKey, fchanFrontend, display, defaultMaxFps, microInSec , frameTimeoutThread, lazyStartup, nullStartup, seqFrame #endif ) where@@ -19,29 +17,30 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent-import Control.Concurrent.Async+import Control.Concurrent+import Control.Concurrent.Async import qualified Control.Concurrent.STM as STM-import Control.Monad.ST.Strict-import Data.IORef+import Control.Monad.ST.Strict+import Data.IORef import qualified Data.Vector.Generic as G import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as VM-import Data.Word+import Data.Word -import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.UI.Frame import qualified Game.LambdaHack.Client.UI.Frontend.Chosen as Chosen-import Game.LambdaHack.Client.UI.Frontend.Common+import Game.LambdaHack.Client.UI.Frontend.Common import qualified Game.LambdaHack.Client.UI.Frontend.Teletype as Teletype+import Game.LambdaHack.Client.UI.Key (KMP (..)) import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray --- | The instructions sent by clients to the raw frontend.+-- | The instructions sent by clients to the raw frontend, indexed+-- by the returned value. data FrontReq :: * -> * where -- | Show a frame. FrontFrame :: {frontFrame :: FrameForall} -> FrontReq ()@@ -52,30 +51,49 @@ , frontKeyFrame :: FrameForall } -> FrontReq KMP -- | Inspect the fkeyPressed MVar. FrontPressed :: FrontReq Bool- -- | discard a key in the queue, if any.+ -- | Discard a key in the queue, if any. FrontDiscard :: FrontReq () -- | Add a key to the queue. FrontAdd :: KMP -> FrontReq ()- -- | set in the frontend that it should auto-answer prompts.+ -- | Set in the frontend that it should auto-answer prompts. FrontAutoYes :: Bool -> FrontReq ()- -- | shut the frontend down.+ -- | Shut the frontend down. FrontShutdown :: FrontReq () -- | Connection channel between a frontend and a client. Frontend acts -- as a server, serving keys, etc., when given frames to display. newtype ChanFrontend = ChanFrontend (forall a. FrontReq a -> IO a) -data FSession = FSession+-- | Machinery allocated for an individual frontend at its startup,+-- unchanged for its lifetime.+data FrontSetup = FrontSetup { fautoYesRef :: IORef Bool , fasyncTimeout :: Async () , fdelay :: MVar Int } --- | Display a prompt, wait for any of the specified keys (for any key,+-- | Initialize the frontend chosen by the player via client options.+chanFrontendIO :: ClientOptions -> IO ChanFrontend+chanFrontendIO soptions = do+ let startup | sfrontendNull soptions = nullStartup+ | sfrontendLazy soptions = lazyStartup+ | sfrontendTeletype soptions = Teletype.startup soptions+ | otherwise = Chosen.startup soptions+ maxFps = fromMaybe defaultMaxFps $ smaxFps soptions+ delta = max 1 $ microInSec `div` maxFps+ rf <- startup+ fautoYesRef <- newIORef $ not $ sdisableAutoYes soptions+ fdelay <- newMVar 0+ fasyncTimeout <- async $ frameTimeoutThread delta fdelay rf+ -- Warning: not linking @fasyncTimeout@, so it'd better not crash.+ let fs = FrontSetup{..}+ return $ fchanFrontend soptions fs rf++-- Display a frame, wait for any of the specified keys (for any key, -- if the list is empty). Repeat if an unexpected key received.-getKey :: DebugModeCli -> FSession -> RawFrontend -> [K.KM] -> FrameForall+getKey :: ClientOptions -> FrontSetup -> RawFrontend -> [K.KM] -> FrameForall -> IO KMP-getKey sdebugCli fs rf@RawFrontend{fchanKey} keys frame = do+getKey soptions fs rf@RawFrontend{fchanKey} keys frame = do autoYes <- readIORef $ fautoYesRef fs if autoYes && (null keys || K.spaceKM `elem` keys) then do display rf frame@@ -86,15 +104,15 @@ kmp <- STM.atomically $ STM.readTQueue fchanKey if null keys || kmpKeyMod kmp `elem` keys then return kmp- else getKey sdebugCli fs rf keys frame+ else getKey soptions fs rf keys frame --- | Read UI requests from the client and send them to the frontend,-fchanFrontend :: DebugModeCli -> FSession -> RawFrontend -> ChanFrontend-fchanFrontend sdebugCli fs@FSession{..} rf =- ChanFrontend $ \req -> case req of+-- Read UI requests from the client and send them to the frontend,+fchanFrontend :: ClientOptions -> FrontSetup -> RawFrontend -> ChanFrontend+fchanFrontend soptions fs@FrontSetup{..} rf =+ ChanFrontend $ \case FrontFrame{..} -> display rf frontFrame FrontDelay k -> modifyMVar_ fdelay $ return . (+ k)- FrontKey{..} -> getKey sdebugCli fs rf frontKeyKeys frontKeyFrame+ FrontKey{..} -> getKey soptions fs rf frontKeyKeys frontKeyFrame FrontPressed -> do noKeysPending <- STM.atomically $ STM.isEmptyTQueue (fchanKey rf) return $! not noKeysPending@@ -174,19 +192,3 @@ `seq` Color.charFromW32 attr == ' ' `seq` () in return $! PointArray.foldlA' seqAttr () singleFrame--chanFrontendIO :: DebugModeCli -> IO ChanFrontend-chanFrontendIO sdebugCli = do- let startup | sfrontendNull sdebugCli = nullStartup- | sfrontendLazy sdebugCli = lazyStartup- | sfrontendTeletype sdebugCli = Teletype.startup sdebugCli- | otherwise = Chosen.startup sdebugCli- maxFps = fromMaybe defaultMaxFps $ smaxFps sdebugCli- delta = max 1 $ microInSec `div` maxFps- rf <- startup- fautoYesRef <- newIORef $ not $ sdisableAutoYes sdebugCli- fdelay <- newMVar 0- fasyncTimeout <- async $ frameTimeoutThread delta fdelay rf- -- Warning: not linking @fasyncTimeout@, so it'd better not crash.- let fs = FSession{..}- return $ fchanFrontend sdebugCli fs rf
Game/LambdaHack/Client/UI/Frontend/Chosen.hs view
@@ -17,5 +17,5 @@ #elif USE_BROWSER import Game.LambdaHack.Client.UI.Frontend.Dom #else-import Game.LambdaHack.Client.UI.Frontend.Gtk+import Game.LambdaHack.Client.UI.Frontend.Sdl #endif
Game/LambdaHack/Client/UI/Frontend/Common.hs view
@@ -1,6 +1,6 @@ -- | Screen frames and animations. module Game.LambdaHack.Client.UI.Frontend.Common- ( RawFrontend(..), KMP(..)+ ( RawFrontend(..) , startupBound, createRawFrontend, resetChanKey, saveKMP , modifierTranslate ) where@@ -9,17 +9,17 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent-import Control.Concurrent.Async+import Control.Concurrent+import Control.Concurrent.Async import qualified Control.Concurrent.STM as STM -import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Key (KMP (..)) import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.Point--data KMP = KMP { kmpKeyMod :: K.KM- , kmpPointer :: Point }+import Game.LambdaHack.Common.Point +-- | Raw frontend definition. The minimal closed set of values that need+-- to depend on the specifics of the chosen frontend. data RawFrontend = RawFrontend { fdisplay :: SingleFrame -> IO () , fshutdown :: IO ()
Game/LambdaHack/Client/UI/Frontend/Curses.hs view
@@ -9,19 +9,19 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent.Async-import Data.Char (chr, ord)+import Control.Concurrent.Async+import Data.Char (chr, ord) import qualified Data.Map.Strict as M import qualified UI.HSCurses.Curses as C import qualified UI.HSCurses.CursesHelper as C -import Game.LambdaHack.Client.UI.Frame-import Game.LambdaHack.Client.UI.Frontend.Common+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Frontend.Common import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray -- | Session data maintained by the frontend.@@ -36,8 +36,8 @@ frontendName = "curses" -- | Starts the main program loop using the frontend input and output.-startup :: DebugModeCli -> IO RawFrontend-startup _sdebugCli = do+startup :: ClientOptions -> IO RawFrontend+startup _soptions = do C.start void $ C.cursSet C.CursorInvisible let s = [ ((fg, bg), C.Style (toFColor fg) (toBColor bg))@@ -46,7 +46,7 @@ , bg <- [Color.Black, Color.Blue, Color.White, Color.BrBlack] ] nr <- C.colorPairs when (nr < length s) $- C.end >> (error $ "terminal has too few color pairs" `showFailure` nr)+ C.end >> error ("terminal has too few color pairs" `showFailure` nr) let (ks, vs) = unzip s ws <- C.convertStyles vs let swin = C.stdScr
Game/LambdaHack/Client/UI/Frontend/Dom.hs view
@@ -1,4 +1,4 @@--- | Text frontend running in Browser.+-- | Text frontend running in a browser. module Game.LambdaHack.Client.UI.Frontend.Dom ( startup, frontendName ) where@@ -7,14 +7,14 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent+import Control.Concurrent import qualified Control.Monad.IO.Class as IO-import Control.Monad.Trans.Reader (ask)+import Control.Monad.Trans.Reader (ask) import qualified Data.Char as Char-import Data.IORef+import Data.IORef import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U-import Data.Word (Word32)+import Data.Word (Word32) import GHCJS.DOM (currentDocument, currentWindow) import GHCJS.DOM.CSSStyleDeclaration (setProperty)@@ -22,7 +22,7 @@ import GHCJS.DOM.Element (Element (Element), setInnerHTML) import GHCJS.DOM.ElementCSSInlineStyle (getStyle) import GHCJS.DOM.EventM (EventM, mouseAltKey, mouseButton, mouseCtrlKey,- mouseMetaKey, mouseShiftKey, on, on, preventDefault,+ mouseMetaKey, mouseShiftKey, on, preventDefault, stopPropagation) import GHCJS.DOM.GlobalEventHandlers (contextMenu, keyDown, mouseUp, wheel) import GHCJS.DOM.HTMLCollection (itemUnsafe)@@ -42,13 +42,13 @@ import GHCJS.DOM.WheelEvent (getDeltaY) import GHCJS.DOM.Window (requestAnimationFrame_) -import Game.LambdaHack.Client.UI.Frame-import Game.LambdaHack.Client.UI.Frontend.Common+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Frontend.Common import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray -- | Session data maintained by the frontend.@@ -66,14 +66,14 @@ frontendName = "browser" -- | Starts the main program loop using the frontend input and output.-startup :: DebugModeCli -> IO RawFrontend-startup sdebugCli = do+startup :: ClientOptions -> IO RawFrontend+startup soptions = do rfMVar <- newEmptyMVar- flip runDOM undefined $ runWeb sdebugCli rfMVar+ flip runDOM undefined $ runWeb soptions rfMVar takeMVar rfMVar -runWeb :: DebugModeCli -> MVar RawFrontend -> DOM ()-runWeb sdebugCli@DebugModeCli{..} rfMVar = do+runWeb :: ClientOptions -> MVar RawFrontend -> DOM ()+runWeb soptions@ClientOptions{..} rfMVar = do -- Init the document. Just doc <- currentDocument Just scurrentWindow <- currentWindow@@ -114,7 +114,7 @@ scharCells <- flattenTable tableElem spreviousFrame <- newIORef blankSingleFrame let sess = FrontendSession{..}- rf <- IO.liftIO $ createRawFrontend (display sdebugCli sess) shutdown+ rf <- IO.liftIO $ createRawFrontend (display soptions sess) shutdown let readMod = do modCtrl <- ask >>= getCtrlKey modShift <- ask >>= getShiftKey@@ -238,11 +238,11 @@ return $! V.fromListN (lxsize * lysize) $ concat lrc -- | Output to the screen via the frontend.-display :: DebugModeCli+display :: ClientOptions -> FrontendSession -- ^ frontend session data -> SingleFrame -- ^ the screen frame to draw -> IO ()-display DebugModeCli{scolorIsBold}+display ClientOptions{scolorIsBold} FrontendSession{..} !curFrame = flip runDOM undefined $ do let setChar :: Int -> Word32 -> Word32 -> DOM ()
Game/LambdaHack/Client/UI/Frontend/Gtk.hs view
@@ -2,32 +2,28 @@ -- | Text frontend based on Gtk. module Game.LambdaHack.Client.UI.Frontend.Gtk ( startup, frontendName-#ifdef EXPOSE_INTERNAL- -- * Internal operations- , startupFun, shutdown, doAttr, extraAttr, display-#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude hiding (Alt) -import Control.Concurrent+import Control.Concurrent import qualified Control.Monad.IO.Class as IO import qualified Data.IntMap.Strict as IM-import Data.IORef+import Data.IORef import qualified Data.Text as T import qualified Game.LambdaHack.Common.PointArray as PointArray-import Graphics.UI.Gtk hiding (Point)-import System.Exit (exitFailure)+import Graphics.UI.Gtk hiding (Point)+import System.Exit (exitFailure) -import Game.LambdaHack.Client.UI.Frame-import Game.LambdaHack.Client.UI.Frontend.Common+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Frontend.Common import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point -- | Session data maintained by the frontend. data FrontendSession = FrontendSession@@ -43,11 +39,11 @@ -- -- Because of Windows, GTK needs to be on a bound thread, -- so we can't avoid the communication overhead of bound threads.-startup :: DebugModeCli -> IO RawFrontend-startup sdebugCli = startupBound $ startupFun sdebugCli+startup :: ClientOptions -> IO RawFrontend+startup soptions = startupBound $ startupFun soptions -startupFun :: DebugModeCli -> MVar RawFrontend -> IO ()-startupFun sdebugCli@DebugModeCli{..} rfMVar = do+startupFun :: ClientOptions -> MVar RawFrontend -> IO ()+startupFun soptions@ClientOptions{..} rfMVar = do -- Init GUI. unsafeInitGUIForThreadedRTS -- Text attributes.@@ -75,7 +71,7 @@ mapM (\ak -> do tt <- textTagNew Nothing textTagTableAdd ttt tt- doAttr sdebugCli tt (emulateBox ak)+ doAttr soptions tt (emulateBox ak) return (fromEnum ak, tt)) [ Color.Attr{fg, bg} | fg <- [minBound..maxBound], bg <- [minBound..maxBound] ]@@ -203,21 +199,21 @@ shutdown :: IO () shutdown = postGUISync mainQuit -doAttr :: DebugModeCli -> TextTag -> (Color.Color, Color.Color) -> IO ()-doAttr sdebugCli tt (fg, bg)+doAttr :: ClientOptions -> TextTag -> (Color.Color, Color.Color) -> IO ()+doAttr soptions tt (fg, bg) | fg == Color.defFG && bg == Color.Black = return () | fg == Color.defFG = set tt [textTagBackground := Color.colorToRGB bg] | bg == Color.Black =- set tt $ extraAttr sdebugCli+ set tt $ extraAttr soptions ++ [textTagForeground := Color.colorToRGB fg] | otherwise =- set tt $ extraAttr sdebugCli+ set tt $ extraAttr soptions ++ [ textTagForeground := Color.colorToRGB fg , textTagBackground := Color.colorToRGB bg ] -extraAttr :: DebugModeCli -> [AttrOp TextTag]-extraAttr DebugModeCli{scolorIsBold} =+extraAttr :: ClientOptions -> [AttrOp TextTag]+extraAttr ClientOptions{scolorIsBold} = [textTagWeight := fromEnum WeightBold | scolorIsBold == Just True] -- , textTagStretch := StretchUltraExpanded
Game/LambdaHack/Client/UI/Frontend/Sdl.hs view
@@ -3,7 +3,8 @@ ( startup, frontendName #ifdef EXPOSE_INTERNAL -- * Internal operations- , startupFun, shutdown, display+ , FontAtlas, FrontendSession(..), startupFun, shutdown, forceShutdown+ , display, displayNoLock, modTranslate, keyTranslate, colorToRGBA #endif ) where @@ -11,31 +12,31 @@ import Game.LambdaHack.Common.Prelude hiding (Alt) -import Control.Concurrent-import Control.Concurrent.Async+import Control.Concurrent+import Control.Concurrent.Async import qualified Data.Char as Char import qualified Data.EnumMap.Strict as EM-import Data.IORef+import Data.IORef import qualified Data.Text as T import qualified Data.Vector.Unboxed as U-import Data.Word (Word32, Word8)-import Foreign.C.Types (CInt)-import System.Directory-import System.Exit (exitSuccess)-import System.FilePath+import Data.Word (Word32, Word8)+import Foreign.C.Types (CInt)+import System.Directory+import System.Exit (exitSuccess)+import System.FilePath import qualified SDL import qualified SDL.Font as TTF-import SDL.Input.Keyboard.Codes+import SDL.Input.Keyboard.Codes import qualified SDL.Vect as Vect -import Game.LambdaHack.Client.UI.Frame-import Game.LambdaHack.Client.UI.Frontend.Common+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Frontend.Common import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray type FontAtlas = EM.EnumMap Color.AttrCharW32 SDL.Texture@@ -61,11 +62,11 @@ -- Apparently some SDL backends are not thread-safe -- (https://wiki.libsdl.org/FAQDevelopment), -- so we stick to main thread.-startup :: DebugModeCli -> IO RawFrontend-startup sdebugCli = startupBound $ startupFun sdebugCli+startup :: ClientOptions -> IO RawFrontend+startup soptions = startupBound $ startupFun soptions -startupFun :: DebugModeCli -> MVar RawFrontend -> IO ()-startupFun sdebugCli@DebugModeCli{..} rfMVar = do+startupFun :: ClientOptions -> MVar RawFrontend -> IO ()+startupFun soptions@ClientOptions{..} rfMVar = do SDL.initialize [SDL.InitVideo, SDL.InitEvents] let title = fromJust stitle fontFileName = T.unpack (fromJust sdlFontFile)@@ -105,7 +106,7 @@ sforcedShutdown <- newIORef False sdisplayPermitted <- newMVar True let sess = FrontendSession{..}- rf <- createRawFrontend (display sdebugCli sess) (shutdown sess)+ rf <- createRawFrontend (display soptions sess) (shutdown sess) putMVar rfMVar rf let pointTranslate :: forall i. (Enum i) => Vect.Point Vect.V2 i -> Point pointTranslate (SDL.P (SDL.V2 x y)) =@@ -123,7 +124,7 @@ writeIORef stexture newTexture prevFrame <- readIORef spreviousFrame writeIORef spreviousFrame blankSingleFrame- displayNoLock sdebugCli sess prevFrame+ displayNoLock soptions sess prevFrame putMVar sdisplayPermitted displayPermitted storeKeys :: IO () storeKeys = do@@ -195,15 +196,15 @@ shutdown sess -- | Add a frame to be drawn.-display :: DebugModeCli+display :: ClientOptions -> FrontendSession -- ^ frontend session data -> SingleFrame -- ^ the screen frame to draw -> IO ()-display sdebugCli sess@FrontendSession{..} curFrame = do+display soptions sess@FrontendSession{..} curFrame = do displayPermitted <- takeMVar sdisplayPermitted if displayPermitted then do -- Apparently some SDL backends are not thread-safe, so keep to main thread:- a <- asyncBound $ displayNoLock sdebugCli sess curFrame+ a <- asyncBound $ displayNoLock soptions sess curFrame wait a putMVar sdisplayPermitted displayPermitted else do@@ -217,11 +218,11 @@ -- to avoid exiting via "thread blocked". threadDelay 50000 -displayNoLock :: DebugModeCli+displayNoLock :: ClientOptions -> FrontendSession -- ^ frontend session data -> SingleFrame -- ^ the screen frame to draw -> IO ()-displayNoLock DebugModeCli{..} FrontendSession{..} curFrame = do+displayNoLock ClientOptions{..} FrontendSession{..} curFrame = do let isFonFile = "fon" `isSuffixOf` T.unpack (fromJust sdlFontFile) sdlSizeAdd = fromJust $ if isFonFile then sdlFonSizeAdd else sdlTtfSizeAdd boxSize <- (+ sdlSizeAdd) <$> TTF.height sfont
Game/LambdaHack/Client/UI/Frontend/Teletype.hs view
@@ -1,29 +1,25 @@--- | Line terimanl text frontend based on stdin/stdout, intended for logging--- tests, but may be used for a teletype terminal, or keyboard and printer.+-- | Line terminal text frontend based on stdin/stdout, intended for logging+-- tests, but may be used on a teletype terminal, or with keyboard and printer. module Game.LambdaHack.Client.UI.Frontend.Teletype ( startup, frontendName-#ifdef EXPOSE_INTERNAL- -- * Internal operations- , shutdown, display-#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.Concurrent.Async-import Data.Char (chr, ord)+import Control.Concurrent.Async+import Data.Char (chr, ord) import qualified Data.Char as Char import qualified System.IO as SIO -import Game.LambdaHack.Client.UI.Frame-import Game.LambdaHack.Client.UI.Frontend.Common+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Frontend.Common import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray -- No session data maintained by this frontend@@ -33,8 +29,8 @@ frontendName = "teletype" -- | Set up the frontend input and output.-startup :: DebugModeCli -> IO RawFrontend-startup _sdebugCli = do+startup :: ClientOptions -> IO RawFrontend+startup _soptions = do rf <- createRawFrontend display shutdown let storeKeys :: IO () storeKeys = do
Game/LambdaHack/Client/UI/Frontend/Vty.hs view
@@ -14,7 +14,7 @@ import Game.LambdaHack.Client.UI.Frame import Game.LambdaHack.Client.UI.Frontend.Common import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Common.ClientOptions+import Game.LambdaHack.Client.ClientOptions import qualified Game.LambdaHack.Common.Color as Color import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Point@@ -30,8 +30,8 @@ frontendName = "vty" -- | Starts the main program loop using the frontend input and output.-startup :: DebugModeCli -> IO RawFrontend-startup _sdebugCli = do+startup :: ClientOptions -> IO RawFrontend+startup _soptions = do svty <- mkVty mempty let sess = FrontendSession{..} rf <- createRawFrontend (display sess) (Vty.shutdown svty)
Game/LambdaHack/Client/UI/HandleHelperM.hs view
@@ -1,10 +1,10 @@ -- | Helper functions for both inventory management and human commands. module Game.LambdaHack.Client.UI.HandleHelperM- ( MError, FailOrCmd, FailError- , showFailError, mergeMError, failWith, failSer, failMsg, weaveJust+ ( FailError, showFailError, MError, mergeMError, FailOrCmd, failWith+ , failSer, failMsg, weaveJust , sortSlots, memberCycle, memberBack, partyAfterLeader , pickLeader, pickLeaderWithPointer- , itemOverlay, statsOverlay, pickNumber+ , itemOverlay, statsOverlay, pickNumber, lookAt ) where import Prelude ()@@ -14,39 +14,40 @@ import qualified Data.Char as Char import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Function-import Data.Ord+import Data.Function+import Data.Ord import qualified Data.Text as T import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.EffectDescription-import Game.LambdaHack.Client.UI.ItemDescription-import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.EffectDescription+import Game.LambdaHack.Client.UI.ItemDescription+import Game.LambdaHack.Client.UI.ItemSlot import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.MsgM-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.OverlayM-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.Slideshow-import Game.LambdaHack.Client.UI.SlideshowM-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Client.UI.Slideshow+import Game.LambdaHack.Client.UI.SlideshowM+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest+import qualified Game.LambdaHack.Common.Kind as Kind+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Content.ItemKind as IK+import qualified Game.LambdaHack.Content.TileKind as TK +-- | Message describing the cause of failure of human command. newtype FailError = FailError {failError :: Text} deriving Show @@ -79,7 +80,7 @@ sortSlots :: MonadClientUI m => FactionId -> Maybe Actor -> m () sortSlots fid mbody = do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull s <- getState let -- If apperance the same, keep the order from before sort. apperance ItemFull{itemBase} =@@ -234,11 +235,10 @@ Nothing -> failMsg "not pointing at an actor" Just (aid, b, _) -> pick (aid, b) --- | Create a list of item names. itemOverlay :: MonadClientUI m => CStore -> LevelId -> ItemBag -> m OKX itemOverlay store lid bag = do localTime <- getsState $ getLocalTime lid- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull ItemSlots itemSlots organSlots <- getsSession sslots side <- getsClient sside factionD <- getsState sfactionD@@ -272,9 +272,8 @@ statsOverlay :: MonadClient m => ActorId -> m OKX statsOverlay aid = do b <- getsState $ getActorBody aid- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` aid) (EM.lookup aid actorAspect)- prSlot :: (Y, SlotChar) -> IK.EqpSlot -> (Text, KYX)+ ar <- getsState $ getActorAspect aid+ let prSlot :: (Y, SlotChar) -> IK.EqpSlot -> (Text, KYX) prSlot (y, c) eqpSlot = let statName = slotToName eqpSlot fullText t =@@ -323,3 +322,45 @@ case res of Right k | k <= 0 -> error $ "" `showFailure` (res, kAll) _ -> return res++-- | Produces a textual description of the terrain and items at an already+-- explored position. Mute for unknown positions.+-- The detailed variant is for use in the aiming mode.+lookAt :: MonadClientUI m+ => Bool -- ^ detailed?+ -> Text -- ^ how to start tile description+ -> Bool -- ^ can be seen right now?+ -> Point -- ^ position to describe+ -> ActorId -- ^ the actor that looks+ -> Text -- ^ an extra sentence to print+ -> m Text+lookAt detailed tilePrefix canSee pos aid msg = do+ Kind.COps{cotile=Kind.Ops{okind}} <- getsState scops+ itemToF <- getsState itemToFull+ b <- getsState $ getActorBody aid+ -- Not using @viewedLevelUI@, because @aid@ may be temporarily not a leader.+ saimMode <- getsSession saimMode+ let lidV = maybe (blid b) aimLevelId saimMode+ lvl <- getLevel lidV+ localTime <- getsState $ getLocalTime lidV+ subject <- partAidLeader aid+ is <- getsState $ getFloorBag lidV pos+ side <- getsClient sside+ factionD <- getsState sfactionD+ let verb = MU.Text $ if | pos == bpos b && lidV == blid b -> "stand on"+ | canSee -> "notice"+ | otherwise -> "remember"+ let nWs (iid, kit@(k, _)) =+ partItemWs side factionD k CGround localTime (itemToF iid kit)+ isd = if EM.size is == 0 then ""+ else makeSentence [ MU.SubjectVerbSg subject verb+ , MU.WWandW $ map nWs $ EM.assocs is]+ tile = lvl `at` pos+ tileText = TK.tname (okind tile)+ tilePart | T.null tilePrefix = MU.Text tileText+ | otherwise = MU.AW $ MU.Text tileText+ tileDesc = [MU.Text tilePrefix, tilePart]+ if | detailed ->+ return $! makeSentence tileDesc <+> msg <+> isd+ | otherwise ->+ return $! msg <+> isd
Game/LambdaHack/Client/UI/HandleHumanGlobalM.hs view
@@ -1,9 +1,10 @@ {-# LANGUAGE DataKinds, GADTs #-}--- | Semantics of 'Command.Cmd' client commands that return server commands.+-- | Semantics of "Game.LambdaHack.Client.UI.HumanCmd"+-- client commands that return server requests. -- A couple of them do not take time, the rest does.--- Here prompts and menus and displayed, but any feedback resulting+-- Here prompts and menus are displayed, but any feedback resulting -- from the commands (e.g., from inventory manipulation) is generated later on,--- for all clients that witness the results of the commands.+-- by the server, for all clients that witness the results of the commands. module Game.LambdaHack.Client.UI.HandleHumanGlobalM ( -- * Meta commands byAreaHuman, byAimModeHuman, byItemModeHuman@@ -17,10 +18,17 @@ , alterDirHuman, alterWithPointerHuman , helpHuman, itemMenuHuman, chooseItemMenuHuman , mainMenuHuman, settingsMenuHuman, challengesMenuHuman- , gameDifficultyIncr, gameWolfToggle, gameFishToggle, gameScenarioIncr+ , gameScenarioIncr, gameDifficultyIncr, gameWolfToggle, gameFishToggle -- * Global commands that never take time , gameRestartHuman, gameExitHuman, gameSaveHuman , tacticHuman, automateHuman+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , areaToRectangles, meleeAid, displaceAid, moveSearchAlter, goToXhair+ , multiActorGoTo, selectItemsToMove, moveItems, projectItem, applyItem+ , alterTile, alterTileAtPos, verifyAlters, verifyEscape, guessAlter+ , artWithVersion, generateMenu, nxtGameMode+#endif ) where import Prelude ()@@ -34,51 +42,53 @@ import qualified Data.EnumSet as ES import qualified Data.Map.Strict as M import qualified Data.Text as T-import Data.Version+import Data.Version import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.Bfs-import Game.LambdaHack.Client.BfsM-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.FrameM-import Game.LambdaHack.Client.UI.Frontend (frontendName)-import Game.LambdaHack.Client.UI.HandleHelperM-import Game.LambdaHack.Client.UI.HandleHumanLocalM-import Game.LambdaHack.Client.UI.HumanCmd (CmdArea (..), Trigger (..))+import Game.LambdaHack.Client.Bfs+import Game.LambdaHack.Client.BfsM+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Request+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.FrameM+import Game.LambdaHack.Client.UI.Frontend (frontendName)+import Game.LambdaHack.Client.UI.HandleHelperM+import Game.LambdaHack.Client.UI.HandleHumanLocalM+import Game.LambdaHack.Client.UI.HumanCmd (CmdArea (..), Trigger (..)) import qualified Game.LambdaHack.Client.UI.HumanCmd as HumanCmd-import Game.LambdaHack.Client.UI.InventoryM+import Game.LambdaHack.Client.UI.InventoryM+import Game.LambdaHack.Client.UI.ItemDescription import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.KeyBindings-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.Msg-import Game.LambdaHack.Client.UI.MsgM-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.RunM-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.Slideshow-import Game.LambdaHack.Client.UI.SlideshowM-import Game.LambdaHack.Common.Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Client.UI.KeyBindings+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.RunM+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.Common.Ability+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind-import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Content.TileKind (TileKind) import qualified Game.LambdaHack.Content.TileKind as TK -- * ByArea@@ -400,14 +410,15 @@ tpos = spos `shift` dir -- target position t = lvl `at` tpos alterMinSkill = Tile.alterMinSkill coTileSpeedup t+ bag <- getsState $ getEmbedBag (blid sb) tpos runStopOrCmd <- -- Movement requires full access. if | Tile.isWalkable coTileSpeedup t -> -- A potential invisible actor is hit. War started without asking. return $ Right $ RequestAnyAbility $ ReqMove dir -- No access, so search and/or alter the tile.- | Tile.isSuspect coTileSpeedup t -- not yet searched- || Tile.isHideAs coTileSpeedup t -- search again, could be swapped+ | not (null bag)+ || Tile.isSuspect coTileSpeedup t -- not yet searched || alterMinSkill < 10 || alterMinSkill >= 10 && alterSkill >= alterMinSkill -> if | alterSkill < alterMinSkill -> failSer AlterUnwalked@@ -441,7 +452,7 @@ side <- getsClient sside fact <- getsState $ (EM.! side) . sfactionD leader <- getLeaderUI- Config{configRunStopMsgs} <- getsSession sconfig+ UIOptions{uRunStopMsgs} <- getsSession sUIOptions keyPressed <- anyKeyPressed srunning <- getsSession srunning -- When running, stop if disturbed. If not running, stop at once.@@ -452,13 +463,13 @@ Just RunParams{runMembers} | noRunWithMulti fact && runMembers /= [leader] -> do stopPlayBack- if configRunStopMsgs+ if uRunStopMsgs then weaveJust <$> failWith "run stop: automatic leader change" else return $ Left Nothing Just _runParams | keyPressed -> do discardPressedKey stopPlayBack- if configRunStopMsgs+ if uRunStopMsgs then weaveJust <$> failWith "run stop: key pressed" else weaveJust <$> failWith "interrupted" Just runParams -> do@@ -467,7 +478,7 @@ case runOutcome of Left stopMsg -> do stopPlayBack- if configRunStopMsgs+ if uRunStopMsgs then weaveJust <$> failWith ("run stop:" <+> stopMsg) else return $ Left Nothing Right runCmd ->@@ -478,8 +489,7 @@ moveOnceToXhairHuman :: MonadClientUI m => m (FailOrCmd RequestAnyAbility) moveOnceToXhairHuman = goToXhair True False -goToXhair :: MonadClientUI m- => Bool -> Bool -> m (FailOrCmd RequestAnyAbility)+goToXhair :: MonadClientUI m => Bool -> Bool -> m (FailOrCmd RequestAnyAbility) goToXhair initialStep run = do aimMode <- getsSession saimMode -- Movement is legal only outside aiming mode.@@ -593,7 +603,7 @@ Nothing -> -- the case of old selection or selection from another actor moveItemHuman cLegalRaw destCStore mverb auto Just (k, it) -> assert (k > 0) $ do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull let eqpFree = eqpFreeN b kToPick | destCStore == CEqp = min eqpFree k | otherwise = k@@ -632,11 +642,9 @@ -- The calmE is inaccurate also if an item not IDed, but that's intended -- and the server will ignore and warn (and content may avoid that, -- e.g., making all rings identified)- actorAspect <- getsClient sactorAspect+ ar <- getsState $ getActorAspect leader lastItemMove <- getsSession slastItemMove- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- calmE = calmEnough b ar+ let calmE = calmEnough b ar cLegalE | calmE = cLegalRaw | destCStore == CSha = [] | otherwise = delete CSha cLegalRaw@@ -674,13 +682,10 @@ moveItems cLegalRaw (fromCStore, l) destCStore = do leader <- getLeaderUI b <- getsState $ getActorBody leader- actorAspect <- getsClient sactorAspect+ ar <- getsState $ getActorAspect leader discoBenefit <- getsClient sdiscoBenefit- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- calmE = calmEnough b ar- ret4 :: MonadClientUI m- => [(ItemId, ItemFull)]+ let calmE = calmEnough b ar+ ret4 :: [(ItemId, ItemFull)] -> Int -> [(ItemId, Int, CStore, CStore)] -> m (FailOrCmd [(ItemId, Int, CStore, CStore)]) ret4 [] _ acc = return $ Right $ reverse acc@@ -742,7 +747,7 @@ case iid `EM.lookup` bag of Nothing -> failWith "no item to fling" Just kit -> do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull let i = (fromCStore, (iid, itemToF iid kit)) projectItem ts i Nothing -> failWith "no item to fling"@@ -753,10 +758,8 @@ projectItem ts (fromCStore, (iid, itemFull)) = do leader <- getLeaderUI b <- getsState $ getActorBody leader- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect leader+ let calmE = calmEnough b ar if not calmE && fromCStore == CSha then failSer ItemNotCalm else do mpsuitReq <- psuitReq ts@@ -789,7 +792,7 @@ case iid `EM.lookup` bag of Nothing -> failWith "no item to apply" Just kit -> do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull let i = (fromCStore, (iid, itemToF iid kit)) applyItem ts i Nothing -> failWith "no item to apply"@@ -800,10 +803,8 @@ applyItem ts (fromCStore, (iid, itemFull)) = do leader <- getLeaderUI b <- getsState $ getActorBody leader- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect leader+ let calmE = calmEnough b ar if not calmE && fromCStore == CSha then failSer ItemNotCalm else do p <- permittedApplyClient $ triggerSymbols ts@@ -817,13 +818,13 @@ alterDirHuman :: MonadClientUI m => [Trigger] -> m (FailOrCmd (RequestTimed 'AbAlter)) alterDirHuman ts = do- Config{configVi, configLaptop} <- getsSession sconfig+ UIOptions{uVi, uLaptop} <- getsSession sUIOptions let verb1 = case ts of [] -> "alter" tr : _ -> verb tr keys = K.escKM : K.leftButtonReleaseKM- : map (K.KM K.NoModifier) (K.dirAllKey configVi configLaptop)+ : map (K.KM K.NoModifier) (K.dirAllKey uVi uLaptop) prompt = makePhrase ["Where to", verb1 <> "? [movement key] [pointer]"] promptAdd prompt@@ -839,7 +840,7 @@ then alterTile ts dir else failWith "never mind" _ ->- case K.handleDir configVi configLaptop km of+ case K.handleDir uVi uLaptop km of Nothing -> failWith "never mind" Just dir -> alterTile ts dir @@ -853,7 +854,7 @@ pText = compassText dir alterTileAtPos ts tpos pText --- | Try to alter a tile using a feature in at the given position.+-- | Try to alter a tile using a feature at the given position. alterTileAtPos :: MonadClientUI m => [Trigger] -> Point -> Text -> m (FailOrCmd (RequestTimed 'AbAlter))@@ -898,7 +899,8 @@ let isE Item{jname} = jname == "escape" if | any isE is -> verifyEscape | null is && not (Tile.isDoor coTileSpeedup t- || Tile.isChangable coTileSpeedup t) ->+ || Tile.isChangable coTileSpeedup t+ || Tile.isSuspect coTileSpeedup t) -> failWith "never mind" | otherwise -> return $ Right () @@ -1000,10 +1002,8 @@ case iid `EM.lookup` bag of Nothing -> weaveJust <$> failWith "no item to open Item Menu for" Just kit -> do- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- itemToF <- itemToFullClient+ ar <- getsState $ getActorAspect leader+ itemToF <- getsState itemToFull lidV <- viewedLevelUI Level{lxsize, lysize} <- getLevel lidV localTime <- getsState $ getLocalTime (blid b)@@ -1187,9 +1187,9 @@ gameMode <- getGameMode snxtScenario <- getsClient snxtScenario let nxtGameName = mname $ nxtGameMode cops snxtScenario- tnextScenario = "new scenario:" <+> nxtGameName+ tnextScenario = "pick next:" <+> nxtGameName -- Key-description-command tuples.- kds = (K.mkKM "s", (tnextScenario, HumanCmd.GameScenarioIncr))+ kds = (K.mkKM "p", (tnextScenario, HumanCmd.GameScenarioIncr)) : [ (km, (desc, cmd)) | (km, ([HumanCmd.CmdMainMenu], desc, cmd)) <- bcmdList ] bindingLen = 30@@ -1269,7 +1269,7 @@ , T.justifyLeft bindingLen ' ' tcurWolf , T.justifyLeft bindingLen ' ' tcurFish , T.justifyLeft bindingLen ' ' ""- , T.justifyLeft bindingLen ' ' "New game challenges:"+ , T.justifyLeft bindingLen ' ' "Next game challenges:" , T.justifyLeft bindingLen ' ' "" ] generateMenu cmdAction kds gameInfo "challenge"
Game/LambdaHack/Client/UI/HandleHumanLocalM.hs view
@@ -1,5 +1,7 @@--- | Semantics of 'HumanCmd' client commands that do not return--- server commands. None of such commands takes game time.+-- | Semantics of "Game.LambdaHack.Client.UI.HumanCmd"+-- client commands that do not return server requests,,+-- but only change internal client state.+-- None of such commands takes game time. module Game.LambdaHack.Client.UI.HandleHumanLocalM ( -- * Meta commands macroHuman@@ -19,70 +21,75 @@ , xhairUnknownHuman, xhairItemHuman, xhairStairHuman , xhairPointerFloorHuman, xhairPointerEnemyHuman , aimPointerFloorHuman, aimPointerEnemyHuman+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , permittedProjectClient, projectCheck, xhairLegalEps, posFromXhair+ , selectAid, endAiming, endAimingMsg, doLook, flashAiming+ , xhairPointerFloor, xhairPointerEnemy+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude --- Cabal- import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.Map.Strict as M-import Data.Ord+import Data.Ord import qualified Data.Text as T import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.BfsM-import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.Animation-import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.DrawM-import Game.LambdaHack.Client.UI.EffectDescription-import Game.LambdaHack.Client.UI.FrameM-import Game.LambdaHack.Client.UI.HandleHelperM-import Game.LambdaHack.Client.UI.HumanCmd (Trigger (..))-import Game.LambdaHack.Client.UI.InventoryM-import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Client.BfsM+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.Animation+import Game.LambdaHack.Client.UI.DrawM+import Game.LambdaHack.Client.UI.EffectDescription+import Game.LambdaHack.Client.UI.FrameM+import Game.LambdaHack.Client.UI.HandleHelperM+import Game.LambdaHack.Client.UI.HumanCmd (Trigger (..))+import Game.LambdaHack.Client.UI.InventoryM+import Game.LambdaHack.Client.UI.ItemDescription+import Game.LambdaHack.Client.UI.ItemSlot 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.Overlay-import Game.LambdaHack.Client.UI.OverlayM-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.SlideshowM-import Game.LambdaHack.Common.Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Client.UI.SlideshowM+import Game.LambdaHack.Client.UI.UIOptions+import Game.LambdaHack.Common.Ability+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.TileKind (isUknownSpace)+import Game.LambdaHack.Content.ModeKind (fhasGender)+import Game.LambdaHack.Content.TileKind (isUknownSpace) -- * Macro macroHuman :: MonadClientUI m => [String] -> m () macroHuman kms = do modifySession $ \sess -> sess {slastPlay = map K.mkKM kms ++ slastPlay sess}- Config{configRunStopMsgs} <- getsSession sconfig- when configRunStopMsgs $+ UIOptions{uRunStopMsgs} <- getsSession sUIOptions+ when uRunStopMsgs $ promptAdd $ "Macro activated:" <+> T.pack (intercalate " " kms) -- * Clear@@ -173,10 +180,8 @@ Level{lxsize, lysize} <- getLevel lidV localTime <- getsState $ getLocalTime (blid b) factionD <- getsState sfactionD- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- attrLine = itemDesc (bfid b) factionD (aHurtMelee ar)+ ar <- getsState $ getActorAspect leader+ let attrLine = itemDesc (bfid b) factionD (aHurtMelee ar) store localTime itemFull ov = splitAttrLine lxsize attrLine slides <-@@ -226,10 +231,8 @@ leader <- getLeaderUI b <- getsState $ getActorBody leader bUI <- getsSession $ getActorUI leader- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- valueText = slotToDecorator eqpSlot b $ prEqpSlot eqpSlot ar+ ar <- getsState $ getActorAspect leader+ let valueText = slotToDecorator eqpSlot b $ prEqpSlot eqpSlot ar prompt2 = makeSentence [ MU.WownW (partActor bUI) (MU.Text $ slotToName eqpSlot) , "is", MU.Text valueText ]@@ -247,9 +250,7 @@ chooseItemProjectHuman ts = do leader <- getLeaderUI b <- getsState $ getActorBody leader- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)+ ar <- getsState $ getActorAspect leader let calmE = calmEnough b ar cLegalRaw = [CGround, CInv, CSha, CEqp] cLegal | calmE = cLegalRaw@@ -279,11 +280,9 @@ permittedProjectClient triggerSyms = do leader <- getLeaderUI b <- getsState $ getActorBody leader+ ar <- getsState $ getActorAspect leader actorSk <- leaderSkillsClientUI let skill = EM.findWithDefault 0 AbProject actorSk- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect) calmE = calmEnough b ar return $ permittedProject False skill calmE triggerSyms @@ -330,13 +329,11 @@ findNewEps onlyFirst pos = do oldEps <- getsClient seps mnewEps <- makeLine onlyFirst b pos oldEps- case mnewEps of- Just newEps -> return $ Right newEps- Nothing ->- return $ Left- $ if onlyFirst- then "aiming blocked at the first step"- else "aiming line to the opponent blocked somewhere"+ return $! case mnewEps of+ Just newEps -> Right newEps+ Nothing -> Left $ if onlyFirst+ then "aiming blocked at the first step"+ else "aiming line blocked somewhere" xhair <- getsSession sxhair case xhair of TEnemy a _ -> do@@ -349,14 +346,14 @@ return $ Left "selected opponent not visible" TPoint _ lid pos -> if lid == lidV- then findNewEps True pos+ then findNewEps False pos else error $ "" `showFailure` (xhair, lidV) TVector v -> do Level{lxsize, lysize} <- getLevel lidV let shifted = shiftBounded lxsize lysize (bpos b) v if shifted == bpos b && v /= Vector 0 0 then return $ Left "selected translation is void"- else findNewEps True shifted+ else findNewEps True shifted -- True, because the goal is vague anyway posFromXhair :: MonadClientUI m => m (Either Text Point) posFromXhair = do@@ -408,10 +405,8 @@ chooseItemApplyHuman ts = do leader <- getLeaderUI b <- getsState $ getActorBody leader- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect leader+ let calmE = calmEnough b ar cLegalRaw = [CGround, CInv, CSha, CEqp] cLegal | calmE = cLegalRaw | otherwise = delete CSha cLegalRaw@@ -437,11 +432,9 @@ permittedApplyClient triggerSyms = do leader <- getLeaderUI b <- getsState $ getActorBody leader+ ar <- getsState $ getActorAspect leader actorSk <- leaderSkillsClientUI let skill = EM.findWithDefault 0 AbApply actorSk- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect) calmE = calmEnough b ar localTime <- getsState $ getLocalTime (blid b) return $ permittedApply localTime skill calmE triggerSyms@@ -463,7 +456,7 @@ mactor = case drop k hs of [] -> Nothing (aid, b, _) : _ -> Just (aid, b)- mchoice = mhero `mplus` mactor+ mchoice = if fhasGender (gplayer fact) then mhero else mactor (autoDun, _) = autoDungeonLevel fact case mchoice of Nothing -> failMsg "no such member of the party"@@ -481,13 +474,13 @@ -- * MemberCycle --- | Switches current member to the next on the viewed level, if any, wrapping.+-- | Switch current member to the next on the viewed level, if any, wrapping. memberCycleHuman :: MonadClientUI m => m MError memberCycleHuman = memberCycle True -- * MemberBack --- | Switches current member to the previous in the whole dungeon, wrapping.+-- | Switch current member to the previous in the whole dungeon, wrapping. memberBackHuman :: MonadClientUI m => m MError memberBackHuman = memberBack True @@ -496,10 +489,10 @@ selectActorHuman :: MonadClientUI m => m () selectActorHuman = do leader <- getLeaderUI- selectAidHuman leader+ selectAid leader -selectAidHuman :: MonadClientUI m => ActorId -> m ()-selectAidHuman leader = do+selectAid :: MonadClientUI m => ActorId -> m ()+selectAid leader = do bodyUI <- getsSession $ getActorUI leader wasMemeber <- getsSession $ ES.member leader . sselected let upd = if wasMemeber@@ -548,11 +541,11 @@ | py == lysize + 2 -> case drop (px - 1) viewed of [] -> failMsg "not pointing at an actor"- (aid, _, _) : _ -> selectAidHuman aid >> return Nothing+ (aid, _, _) : _ -> selectAid aid >> return Nothing | otherwise -> case find (\(_, b) -> bpos b == Point px (py - mapStartY)) ours of Nothing -> failMsg "not pointing at an actor"- Just (aid, _) -> selectAidHuman aid >> return Nothing+ Just (aid, _) -> selectAid aid >> return Nothing -- * Repeat @@ -561,10 +554,10 @@ -- at terrain change or when walking over items. repeatHuman :: MonadClientUI m => Int -> m () repeatHuman n = do- (_, seqPrevious, k) <- getsSession slastRecord+ LastRecord _ seqPrevious k <- getsSession slastRecord let macro = concat $ replicate n $ reverse seqPrevious modifySession $ \sess -> sess {slastPlay = macro ++ slastPlay sess}- let slastRecord = ([], [], if k == 0 then 0 else maxK)+ let slastRecord = LastRecord [] [] (if k == 0 then 0 else maxK) modifySession $ \sess -> sess {slastRecord} maxK :: Int@@ -574,16 +567,16 @@ recordHuman :: MonadClientUI m => m () recordHuman = do- (_seqCurrent, seqPrevious, k) <- getsSession slastRecord+ LastRecord _seqCurrent seqPrevious k <- getsSession slastRecord case k of 0 -> do- let slastRecord = ([], [], maxK)+ let slastRecord = LastRecord [] [] maxK modifySession $ \sess -> sess {slastRecord} promptAdd $ "Macro will be recorded for up to" <+> tshow maxK <+> "actions. Stop recording with the same key." _ -> do- let slastRecord = (seqPrevious, [], 0)+ let slastRecord = LastRecord seqPrevious [] 0 modifySession $ \sess -> sess {slastRecord} promptAdd $ "Macro recording stopped after" <+> tshow (maxK - k - 1) <+> "actions."@@ -739,7 +732,7 @@ map (\(aid2, b2) -> (aid2, b2, sactorUI EM.! aid2)) inhabitants seps <- getsClient seps mnewEps <- makeLine False b p seps- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull factionD <- getsState sfactionD s <- getState let aims = isJust mnewEps
Game/LambdaHack/Client/UI/HandleHumanM.hs view
@@ -1,24 +1,28 @@ -- | Semantics of human player commands. module Game.LambdaHack.Client.UI.HandleHumanM ( cmdHumanSem+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , noRemoteHumanCmd, cmdAction, addNoError, fmapTimedToUI+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude +import Game.LambdaHack.Client.Request import Game.LambdaHack.Client.UI.HandleHelperM import Game.LambdaHack.Client.UI.HandleHumanGlobalM import Game.LambdaHack.Client.UI.HandleHumanLocalM import Game.LambdaHack.Client.UI.HumanCmd import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Common.Request --- | The semantics of human player commands in terms of the @Action@ monad.--- Decides if the action takes time and what action to perform.--- Some time cosuming commands are enabled in aiming mode, but cannot be+-- | The semantics of human player commands in terms of the client monad.+--+-- Some time cosuming commands are enabled even in aiming mode, but cannot be -- invoked in aiming mode on a remote level (level different than--- the level of the leader).+-- the level of the leader), which is caught here. cmdHumanSem :: MonadClientUI m => HumanCmd -> m (Either MError ReqUI) cmdHumanSem cmd = if noRemoteHumanCmd cmd then do@@ -32,7 +36,24 @@ else cmdAction cmd else cmdAction cmd --- | Compute the basic action for a command and mark whether it takes time.+-- | Commands that are forbidden on a remote level, because they+-- would usually take time when invoked on one, but not necessarily do+-- what the player expects. Note that some commands that normally take time+-- are not included, because they don't take time in aiming mode+-- or their individual sanity conditions include a remote level check.+noRemoteHumanCmd :: HumanCmd -> Bool+noRemoteHumanCmd cmd = case cmd of+ Wait -> True+ Wait10 -> True+ MoveItem{} -> True+ Apply{} -> True+ AlterDir{} -> True+ AlterWithPointer{} -> True+ MoveOnceToXhair -> True+ RunOnceToXhair -> True+ ContinueToXhair -> True+ _ -> False+ cmdAction :: MonadClientUI m => HumanCmd -> m (Either MError ReqUI) cmdAction cmd = case cmd of Macro kms -> addNoError $ macroHuman kms@@ -50,8 +71,8 @@ LoopOnNothing cmd1 -> loopOnNothingHuman (cmdAction cmd1) - Wait -> weaveJust <$> Right <$> fmap timedToUI waitHuman- Wait10 -> weaveJust <$> Right <$> fmap timedToUI waitHuman10+ Wait -> weaveJust <$> Right <$> fmapTimedToUI waitHuman+ Wait10 -> weaveJust <$> Right <$> fmapTimedToUI waitHuman10 MoveDir v -> weaveJust <$> (ReqUITimed <$$> moveRunHuman True True False False v) RunDir v -> weaveJust <$> (ReqUITimed <$$> moveRunHuman True True True True v)@@ -60,11 +81,13 @@ RunOnceToXhair -> weaveJust <$> (ReqUITimed <$$> runOnceToXhairHuman) ContinueToXhair -> weaveJust <$> (ReqUITimed <$$> continueToXhairHuman) MoveItem cLegalRaw toCStore mverb auto ->- weaveJust <$> (timedToUI <$$> moveItemHuman cLegalRaw toCStore mverb auto)- Project ts -> weaveJust <$> (timedToUI <$$> projectHuman ts)- Apply ts -> weaveJust <$> (timedToUI <$$> applyHuman ts)- AlterDir ts -> weaveJust <$> (timedToUI <$$> alterDirHuman ts)- AlterWithPointer ts -> weaveJust <$> (timedToUI <$$> alterWithPointerHuman ts)+ weaveJust+ <$> (fmapTimedToUI <$> moveItemHuman cLegalRaw toCStore mverb auto)+ Project ts -> weaveJust <$> (fmapTimedToUI <$> projectHuman ts)+ Apply ts -> weaveJust <$> (fmapTimedToUI <$> applyHuman ts)+ AlterDir ts -> weaveJust <$> (fmapTimedToUI <$> alterDirHuman ts)+ AlterWithPointer ts -> weaveJust+ <$> (fmapTimedToUI <$> alterWithPointerHuman ts) Help -> helpHuman cmdAction ItemMenu -> itemMenuHuman cmdAction ChooseItemMenu dialogMode -> chooseItemMenuHuman cmdAction dialogMode@@ -122,3 +145,6 @@ addNoError :: Monad m => m () -> m (Either MError ReqUI) addNoError cmdCli = cmdCli >> return (Left Nothing)++fmapTimedToUI :: Monad m => m (RequestTimed a) -> m ReqUI+fmapTimedToUI mr = ReqUITimed . RequestAnyAbility <$> mr
Game/LambdaHack/Client/UI/HumanCmd.hs view
@@ -1,9 +1,9 @@ {-# LANGUAGE DeriveGeneric #-}--- | Abstract syntax human player commands.+-- | Abstract syntax of human player commands. module Game.LambdaHack.Client.UI.HumanCmd ( CmdCategory(..), categoryDescription , CmdArea(..), areaDescription- , CmdTriple, HumanCmd(..), noRemoteHumanCmd+ , CmdTriple, HumanCmd(..) , Trigger(..) ) where @@ -11,13 +11,13 @@ import Game.LambdaHack.Common.Prelude -import Control.DeepSeq-import Data.Binary-import GHC.Generics (Generic)+import Control.DeepSeq+import Data.Binary+import GHC.Generics (Generic) import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.TileKind as TK data CmdCategory =@@ -45,6 +45,8 @@ -- The constructors are sorted, roughly, wrt inclusion, then top to bottom, -- the left to right.+-- | Symbolic representation of areas of the screen used to define the meaning+-- of mouse button presses relative to where the mouse points to. data CmdArea = CaMessage | CaMapLeader@@ -80,9 +82,12 @@ CaTargetDesc -> "target info" -- 1234567890123 +-- | This triple of command categories, description and the command term itself+-- defines the meaning of a human command as entered via a keypress,+-- mouse click or chosen from a menu. type CmdTriple = ([CmdCategory], Text, HumanCmd) --- | Abstract syntax of player commands.+-- | Abstract syntax of human player commands. data HumanCmd = -- Meta. Macro [String]@@ -168,24 +173,8 @@ instance Binary HumanCmd --- | Commands that are forbidden on a remote level, because they--- would usually take time when invoked on one, but not necessarily do--- what the player expects. Note that some commands that normally take time--- are not included, because they don't take time in aiming mode--- or their individual sanity conditions include a remote level check.-noRemoteHumanCmd :: HumanCmd -> Bool-noRemoteHumanCmd cmd = case cmd of- Wait -> True- Wait10 -> True- MoveItem{} -> True- Apply{} -> True- AlterDir{} -> True- AlterWithPointer{} -> True- MoveOnceToXhair -> True- RunOnceToXhair -> True- ContinueToXhair -> True- _ -> False-+-- | Description of how item manipulation is triggered and communicated+-- to the player. data Trigger = ApplyItem {verb :: MU.Part, object :: MU.Part, symbol :: Char} | AlterFeature {verb :: MU.Part, object :: MU.Part, feature :: TK.Feature}
Game/LambdaHack/Client/UI/InventoryM.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE DataKinds #-}--- | Inventory management and party cycling.+-- | UI of inventory management. module Game.LambdaHack.Client.UI.InventoryM ( Suitability(..) , getFull, getGroupItem, getStoreItem@@ -14,37 +14,36 @@ import Game.LambdaHack.Common.Prelude import qualified Data.Char as Char-import Data.Either+import Data.Either import qualified Data.EnumMap.Strict as EM import qualified Data.Map.Strict as M import qualified Data.Text as T-import Data.Tuple (swap)+import Data.Tuple (swap) import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.HandleHelperM-import Game.LambdaHack.Client.UI.HumanCmd-import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.HandleHelperM+import Game.LambdaHack.Client.UI.HumanCmd+import Game.LambdaHack.Client.UI.ItemSlot import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.KeyBindings-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.MsgM-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.Slideshow-import Game.LambdaHack.Client.UI.SlideshowM-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Client.UI.KeyBindings+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Client.UI.Slideshow+import Game.LambdaHack.Client.UI.SlideshowM+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State data ItemDialogState = ISuitable | IAll deriving (Show, Eq)@@ -173,7 +172,7 @@ return $ Left $ "no items" <+> ppLegal else return $ Left $ showReqFailure ItemNotCalm haveThis@(headThisActor : _) -> do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull let suitsThisActor store = let bag = getCStoreBag store in any (\(iid, kit) -> psuitFun $ itemToF iid kit) $ EM.assocs bag@@ -216,7 +215,7 @@ allAssocs = concatMap storeAssocs (cCur : cRest) case (cRest, allAssocs) of ([], [(iid, k)]) | not askWhenLone -> do- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull ItemSlots itemSlots organSlots <- getsSession sslots let isOrgan = cCur `elem` [MStore COrgan, MLoreOrgan] lSlots = if isOrgan then organSlots else itemSlots@@ -258,13 +257,11 @@ leader <- getLeaderUI body <- getsState $ getActorBody leader bodyUI <- getsSession $ getActorUI leader- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)+ ar <- getsState $ getActorAspect leader fact <- getsState $ (EM.! bfid body) . sfactionD hs <- partyAfterLeader leader bagAll <- getsState $ \s -> accessModeBag leader s cCur- itemToF <- itemToFullClient+ itemToF <- getsState itemToFull Binding{brevMap} <- getsSession sbinding mpsuit <- psuit -- when throwing, this sets eps and checks xhair validity psuitFun <- case mpsuit of@@ -458,10 +455,8 @@ leader <- getLeaderUI let newLegal = cCur : cRest -- not updated in any way yet b <- getsState $ getActorBody leader- actorAspect <- getsClient sactorAspect- let ar = fromMaybe (error $ "" `showFailure` leader)- (EM.lookup leader actorAspect)- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect leader+ let calmE = calmEnough b ar legalAfterCalm = case newLegal of c1@(MStore CSha) : c2 : rest | not calmE -> (c2, c1 : rest) [MStore CSha] | not calmE -> (MStore CGround, newLegal)
Game/LambdaHack/Client/UI/ItemDescription.hs view
@@ -2,7 +2,11 @@ module Game.LambdaHack.Client.UI.ItemDescription ( partItem, partItemShort, partItemHigh, partItemWs, partItemWsRanged , partItemShortAW, partItemMediumAW, partItemShortWownW- , viewItem, show64With2+ , viewItem, itemDesc+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , show64With2, partItemN, textAllAE, partItemWsR+#endif ) where import Prelude ()@@ -10,19 +14,20 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import Data.Int (Int64)+import Data.Int (Int64) import qualified Data.Text as T import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.UI.EffectDescription+import Game.LambdaHack.Client.UI.EffectDescription+import Game.LambdaHack.Client.UI.Overlay import qualified Game.LambdaHack.Common.Color as Color import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Flavour-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Time import qualified Game.LambdaHack.Content.ItemKind as IK show64With2 :: Int64 -> Text@@ -148,11 +153,11 @@ _ -> [] damage = case find hurtMeleeAspect aspects of Just (IK.AddHurtMelee hurtMelee) ->- (if jdamage itemBase <= 0+ (if jdamage itemBase == 0 then "0d0" else tshow (jdamage itemBase)) <> affixDice hurtMelee <> "%"- _ -> if jdamage itemBase <= 0+ _ -> if jdamage itemBase == 0 then "" else tshow (jdamage itemBase) in elab ++ if fullInfo >= 6 || fullInfo >= 2 && null elab@@ -166,12 +171,12 @@ splitAE (IK.iaspects itemKind) (IK.ieffects itemKind) IK.ThrowMod{IK.throwVelocity} = strengthToThrow itemBase speed = speedFromWeight (jweight itemBase) throwVelocity- meanDmg = Dice.meanDice (jdamage itemBase)+ meanDmg = ceiling $ Dice.meanDice (jdamage itemBase) minDeltaHP = xM meanDmg `divUp` 100 aHurtMeleeOfItem = case itemAspect of Just aspectRecord -> aHurtMelee aspectRecord Nothing -> case find hurtMeleeAspect (IK.iaspects itemKind) of- Just (IK.AddHurtMelee d) -> Dice.meanDice d+ Just (IK.AddHurtMelee d) -> ceiling $ Dice.meanDice d _ -> 0 pmult = 100 + min 99 (max (-99) aHurtMeleeOfItem) prawDeltaHP = fromIntegral pmult * minDeltaHP@@ -244,3 +249,101 @@ {-# INLINE viewItem #-} viewItem item = Color.attrChar2ToW32 (flavourToColor $ jflavour item) (jsymbol item)++itemDesc :: FactionId -> FactionDict -> Int -> CStore -> Time -> ItemFull+ -> AttrLine+itemDesc side factionD aHurtMeleeOfOwner store localTime+ itemFull@ItemFull{itemBase} =+ let (_, unique, name, stats) =+ partItemHigh side factionD store localTime itemFull+ nstats = makePhrase [name, stats]+ IK.ThrowMod{IK.throwVelocity, IK.throwLinger} = strengthToThrow itemBase+ speed = speedFromWeight (jweight itemBase) throwVelocity+ range = rangeFromSpeedAndLinger speed throwLinger+ tspeed | speed < speedLimp = "When thrown, it drops at once."+ | speed < speedWalk = "When thrown, it travels only one meter and drops immediately."+ | otherwise =+ "When thrown, it flies with speed of"+ <+> tshow (fromSpeed speed `div` 10)+ <> if throwLinger /= 100+ then " m/s and range" <+> tshow range <+> "m."+ else " m/s."+ (desc, featureSentences, damageAnalysis) = case itemDisco itemFull of+ Nothing -> ("This item is as unremarkable as can be.", "", tspeed)+ Just ItemDisco{itemKind, itemAspect} ->+ let sentences = mapMaybe featureToSentence (IK.ifeature itemKind)+ hurtMeleeAspect :: IK.Aspect -> Bool+ hurtMeleeAspect IK.AddHurtMelee{} = True+ hurtMeleeAspect _ = False+ aHurtMeleeOfItem = case itemAspect of+ Just aspectRecord -> aHurtMelee aspectRecord+ Nothing -> case find hurtMeleeAspect (IK.iaspects itemKind) of+ Just (IK.AddHurtMelee d) -> ceiling $ Dice.meanDice d+ _ -> 0+ meanDmg = ceiling $ Dice.meanDice (jdamage itemBase)+ dmgAn = if meanDmg <= 0 then "" else+ let multRaw = aHurtMeleeOfOwner+ + if store `elem` [CEqp, COrgan]+ then 0+ else aHurtMeleeOfItem+ mult = 100 + min 99 (max (-99) multRaw)+ minDeltaHP = xM meanDmg `divUp` 100+ rawDeltaHP = fromIntegral mult * minDeltaHP+ pmult = 100 + min 99 (max (-99) aHurtMeleeOfItem)+ prawDeltaHP = fromIntegral pmult * minDeltaHP+ pdeltaHP = modifyDamageBySpeed prawDeltaHP speed+ mDeltaHP = modifyDamageBySpeed minDeltaHP speed+ in "Against defenceless targets you would inflict around"+ -- rounding and non-id items+ <+> tshow meanDmg+ <> "*" <> tshow mult <> "%"+ <> "=" <> show64With2 rawDeltaHP+ <+> "melee damage (min" <+> show64With2 minDeltaHP+ <> ") and"+ <+> tshow meanDmg+ <> "*" <> tshow pmult <> "%"+ <> "*" <> "speed^2"+ <> "/" <> tshow (fromSpeed speedThrust `divUp` 10) <> "^2"+ <> "=" <> show64With2 pdeltaHP+ <+> "ranged damage (min" <+> show64With2 mDeltaHP+ <> ") with it"+ <> if Dice.minDice (jdamage itemBase)+ == Dice.maxDice (jdamage itemBase)+ then "."+ else "on average."+ in (IK.idesc itemKind, T.intercalate " " sentences, tspeed <+> dmgAn)+ eqpSlotSentence = case strengthEqpSlot itemFull of+ Just es -> slotToSentence es+ Nothing -> ""+ weight = jweight itemBase+ (scaledWeight, unitWeight)+ | weight > 1000 =+ (tshow $ fromIntegral weight / (1000 :: Double), "kg")+ | otherwise = (tshow weight, "g")+ onLevel = "on level" <+> tshow (abs $ fromEnum $ jlid itemBase) <> "."+ discoFirst = (if unique then "Discovered" else "First seen")+ <+> onLevel+ whose fid = gname (factionD EM.! fid)+ sourceDesc =+ case jfid itemBase of+ Just fid | jsymbol itemBase `elem` ['+'] ->+ "Caused by" <+> (if fid == side then "us" else whose fid)+ <> ". First observed" <+> onLevel+ Just fid ->+ "Coming from" <+> whose fid+ <> "." <+> discoFirst+ _ -> discoFirst+ colorSymbol = viewItem itemBase+ blurb =+ " "+ <> nstats+ <> ":"+ <+> desc+ <+> (if weight > 0+ then makeSentence ["Weighs", MU.Text scaledWeight <> unitWeight]+ else "")+ <+> featureSentences+ <+> eqpSlotSentence+ <+> sourceDesc+ <+> damageAnalysis+ in colorSymbol : textToAL blurb
Game/LambdaHack/Client/UI/ItemSlot.hs view
@@ -1,6 +1,6 @@ -- | Item slots for UI and AI item collections. module Game.LambdaHack.Client.UI.ItemSlot- ( ItemSlots(..), SlotChar(..)+ ( SlotChar(..), ItemSlots(..) , allSlots, allZeroSlots, intSlots, slotLabel, assignSlot ) where @@ -8,12 +8,12 @@ import Game.LambdaHack.Common.Prelude -import Data.Binary-import Data.Bits (unsafeShiftL, unsafeShiftR)-import Data.Char+import Data.Binary+import Data.Bits (unsafeShiftL, unsafeShiftR)+import Data.Char import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Ord (comparing)+import Data.Ord (comparing) import qualified Data.Text as T import Game.LambdaHack.Common.Actor@@ -23,6 +23,7 @@ import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.State +-- | Slot label. Usually just a character. Sometimes with a numerical prefix. data SlotChar = SlotChar {slotPrefix :: Int, slotChar :: Char} deriving (Show, Eq) @@ -42,8 +43,10 @@ c100 = c0 - if c0 > 150 then 100 else 0 in SlotChar n (chr c100) -data ItemSlots = ItemSlots (EM.EnumMap SlotChar ItemId)- (EM.EnumMap SlotChar ItemId)+-- | Mapping from slot labels to item identifiers.+data ItemSlots = ItemSlots+ { itemSlots :: EM.EnumMap SlotChar ItemId+ , organSlots :: EM.EnumMap SlotChar ItemId } deriving Show instance Binary ItemSlots where@@ -59,6 +62,12 @@ intSlots :: [SlotChar] intSlots = map (flip SlotChar 'a') [0..] +slotLabel :: SlotChar -> Text+slotLabel x =+ T.snoc (if slotPrefix x == 0 then T.empty else tshow $ slotPrefix x)+ (slotChar x)+ <> ")"+ -- | Assigns a slot to an item, for inclusion in the inventory -- of a hero. Tries to to use the requested slot, if any. assignSlot :: CStore -> Item -> FactionId -> Maybe Actor -> ItemSlots@@ -85,9 +94,3 @@ free = filter f candidates g l = l `EM.notMember` lSlots fresh = filter g $ take ((slotPrefix lastSlot + 1) * len0) candidates--slotLabel :: SlotChar -> Text-slotLabel x =- T.snoc (if slotPrefix x == 0 then T.empty else tshow $ slotPrefix x)- (slotChar x)- <> ")"
Game/LambdaHack/Client/UI/Key.hs view
@@ -1,25 +1,34 @@ {-# LANGUAGE DeriveGeneric #-} -- | Frontend-independent keyboard input operations. module Game.LambdaHack.Client.UI.Key- ( Key(..), showKey, handleDir, dirAllKey- , moveBinding, mkKM, mkChar, mkKP, keyTranslate, keyTranslateWeb- , Modifier(..), KM(..), showKM+ ( Key(..), Modifier(..), KM(..), KMP(..)+ , showKey, showKM , escKM, spaceKM, safeSpaceKM, returnKM , pgupKM, pgdnKM, wheelNorthKM, wheelSouthKM , upKM, downKM, leftKM, rightKM , homeKM, endKM, backspaceKM , leftButtonReleaseKM, rightButtonReleaseKM+ , dirAllKey, handleDir, moveBinding, mkKM, mkChar, mkKP+ , keyTranslate, keyTranslateWeb+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , dirKeypadKey, dirKeypadShiftChar, dirKeypadShiftKey+ , dirLaptopKey, dirLaptopShiftKey+ , dirViChar, dirViKey, dirViShiftKey+ , dirMoveNoModifier, dirRunNoModifier, dirRunControl, dirRunShift+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude hiding (Alt, Left, Right) -import Control.DeepSeq-import Data.Binary+import Control.DeepSeq+import Data.Binary import qualified Data.Char as Char-import GHC.Generics (Generic)+import GHC.Generics (Generic) +import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.Vector -- | Frontend-independent datatype to represent keys.@@ -72,6 +81,7 @@ instance NFData Modifier +-- | Key and modifier. data KM = KM { modifier :: Modifier , key :: Key } deriving (Ord, Eq, Generic)@@ -83,7 +93,11 @@ instance Show KM where show = showKM --- Common and terse names for keys.+-- | Key, modifier and position of mouse pointer.+data KMP = KMP { kmpKeyMod :: KM+ , kmpPointer :: Point }++-- | Common and terse names for keys. showKey :: Key -> String showKey Esc = "ESC" showKey Return = "RET"@@ -199,15 +213,15 @@ dirViShiftKey = map (Char . Char.toUpper) dirViChar dirMoveNoModifier :: Bool -> Bool -> [Key]-dirMoveNoModifier configVi configLaptop =- dirKeypadKey ++ if | configVi -> dirViKey- | configLaptop -> dirLaptopKey+dirMoveNoModifier uVi uLaptop =+ dirKeypadKey ++ if | uVi -> dirViKey+ | uLaptop -> dirLaptopKey | otherwise -> [] dirRunNoModifier :: Bool -> Bool -> [Key]-dirRunNoModifier configVi configLaptop =- dirKeypadShiftKey ++ if | configVi -> dirViShiftKey- | configLaptop -> dirLaptopShiftKey+dirRunNoModifier uVi uLaptop =+ dirKeypadShiftKey ++ if | uVi -> dirViShiftKey+ | uLaptop -> dirLaptopShiftKey | otherwise -> [] dirRunControl :: [Key]@@ -219,30 +233,30 @@ dirRunShift = dirRunControl dirAllKey :: Bool -> Bool -> [Key]-dirAllKey configVi configLaptop =- dirMoveNoModifier configVi configLaptop- ++ dirRunNoModifier configVi configLaptop+dirAllKey uVi uLaptop =+ dirMoveNoModifier uVi uLaptop+ ++ dirRunNoModifier uVi uLaptop ++ dirRunControl -- | Configurable event handler for the direction keys. -- Used for directed commands such as close door. handleDir :: Bool -> Bool -> KM -> Maybe Vector-handleDir configVi configLaptop KM{modifier=NoModifier, key} =- let assocs = zip (dirAllKey configVi configLaptop) $ cycle moves+handleDir uVi uLaptop KM{modifier=NoModifier, key} =+ let assocs = zip (dirAllKey uVi uLaptop) $ cycle moves in lookup key assocs handleDir _ _ _ = Nothing --- | Binding of both sets of movement keys.+-- | Binding of both sets of movement keys, vi and laptop. moveBinding :: Bool -> Bool -> (Vector -> a) -> (Vector -> a) -> [(KM, a)]-moveBinding configVi configLaptop move run =+moveBinding uVi uLaptop move run = let assign f (km, dir) = (km, f dir) mapMove modifier keys = map (assign move) (zip (map (KM modifier) keys) $ cycle moves) mapRun modifier keys = map (assign run) (zip (map (KM modifier) keys) $ cycle moves)- in mapMove NoModifier (dirMoveNoModifier configVi configLaptop)- ++ mapRun NoModifier (dirRunNoModifier configVi configLaptop)+ in mapMove NoModifier (dirMoveNoModifier uVi uLaptop)+ ++ mapRun NoModifier (dirRunNoModifier uVi uLaptop) ++ mapRun Control dirRunControl ++ mapRun Shift dirRunShift
Game/LambdaHack/Client/UI/KeyBindings.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE TupleSections #-}--- | Binding of keys to commands.--- No operation in this module involves the 'State' or 'Action' type.+-- | Verifying, aggregating and displaying binding of keys to commands. module Game.LambdaHack.Client.UI.KeyBindings ( Binding(..), stdBinding, keyHelp, okxsN ) where@@ -12,13 +11,13 @@ import qualified Data.Map.Strict as M import qualified Data.Text as T -import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.Content.KeyKind-import Game.LambdaHack.Client.UI.HumanCmd-import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Client.UI.Content.KeyKind+import Game.LambdaHack.Client.UI.HumanCmd+import Game.LambdaHack.Client.UI.ItemSlot import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.Slideshow+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.Slideshow+import Game.LambdaHack.Client.UI.UIOptions import qualified Game.LambdaHack.Common.Color as Color -- | Bindings and other information about human player commands.@@ -29,36 +28,36 @@ , brevMap :: M.Map HumanCmd [K.KM] -- ^ and from commands to their keys } --- | Binding of keys to movement and other standard commands,+-- | Create binding of keys to movement and other standard commands, -- as well as commands defined in the config file.-stdBinding :: KeyKind -- ^ default key bindings from the content- -> Config -- ^ game config- -> Binding -- ^ concrete binding-stdBinding copsClient Config{configCommands, configVi, configLaptop} =+stdBinding :: KeyKind -- ^ default key bindings from the content+ -> UIOptions -- ^ UI client options+ -> Binding -- ^ concrete binding+stdBinding (KeyKind copsClient) UIOptions{uCommands, uVi, uLaptop} = let waitTriple = ([CmdMove], "", Wait) wait10Triple = ([CmdMove], "", Wait10) moveXhairOr n cmd v = ByAimMode { exploration = cmd v , aiming = MoveXhair v n } bcmdList =- (if configVi+ (if uVi then filter (\(k, _) -> k `notElem` [K.mkKM "period", K.mkKM "C-period"])- else id) (rhumanCommands copsClient)- ++ configCommands+ else id) copsClient+ ++ uCommands ++ [ (K.mkKM "KP_Begin", waitTriple) , (K.mkKM "C-KP_Begin", wait10Triple) , (K.mkKM "KP_5", waitTriple) , (K.mkKM "C-KP_5", wait10Triple) ]- ++ (if | configVi ->+ ++ (if | uVi -> [ (K.mkKM "period", waitTriple) , (K.mkKM "C-period", wait10Triple) ]- | configLaptop ->+ | uLaptop -> [ (K.mkKM "i", waitTriple) , (K.mkKM "C-i", wait10Triple) , (K.mkKM "I", waitTriple) ] | otherwise -> [])- ++ K.moveBinding configVi configLaptop+ ++ K.moveBinding uVi uLaptop (\v -> ([CmdMove], "", moveXhairOr 1 MoveDir v)) (\v -> ([CmdMove], "", moveXhairOr 10 RunDir v)) rejectRepetitions t1 t2 = error $ "duplicate key"@@ -77,7 +76,7 @@ ] } --- | Produce a set of help screens from the key bindings.+-- | Produce a set of help/menu screens from the key bindings. keyHelp :: Binding -> Int -> [(Text, OKX)] keyHelp keyb@Binding{..} offset = assert (offset > 0) $ let@@ -242,6 +241,7 @@ [areaCaption] lastHelpEnd ) ] +-- | Turn the specified portion of bindings into a menu. okxsN :: Binding -> Int -> Int -> (HumanCmd -> Bool) -> CmdCategory -> [Text] -> [Text] -> OKX okxsN Binding{..} offset n greyedOut cat header footer =
Game/LambdaHack/Client/UI/MonadClientUI.hs view
@@ -1,20 +1,25 @@ -- | Client monad for interacting with a human through UI. module Game.LambdaHack.Client.UI.MonadClientUI ( -- * Client UI monad- MonadClientUI( getsSession, modifySession+ MonadClientUI( getsSession+ , modifySession , liftIO -- exposed only to be implemented, not used, ) -- * Assorted primitives- , getSession, putSession, clientPrintUI, mapStartY, displayFrames- , setFrontAutoYes, anyKeyPressed, discardPressedKey, addPressedEsc- , connFrontendFrontKey, frontendShutdown, chanFrontend+ , clientPrintUI, mapStartY, getSession, putSession, displayFrames+ , connFrontendFrontKey, setFrontAutoYes, frontendShutdown, chanFrontend+ , anyKeyPressed, discardPressedKey, addPressedEsc , getReportUI, getLeaderUI, getArenaUI, viewedLevelUI , leaderTgtToPos, xhairToPos, clearXhair, clearAimMode , scoreToSlideshow, defaultHistory , tellAllClipPS, tellGameClipPS, elapsedSessionTimeGT , resetSessionStart, resetGameStart- , partAidLeader, partActorLeader, partActorLeaderFun, partPronounLeader+ , partActorLeader, partActorLeaderFun, partPronounLeader, partAidLeader , tryRestore, leaderSkillsClientUI+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , connFrontend, displayFrame, addPressedKey+#endif ) where import Prelude ()@@ -24,42 +29,42 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.Text as T import qualified Data.Text.IO as T-import Data.Time.Clock-import Data.Time.Clock.POSIX-import Data.Time.LocalTime+import Data.Time.Clock+import Data.Time.Clock.POSIX+import Data.Time.LocalTime import qualified NLP.Miniutter.English as MU-import System.FilePath-import System.IO (hFlush, stdout)+import System.FilePath+import System.IO (hFlush, stdout) -import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient hiding (liftIO)-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.Frame-import Game.LambdaHack.Client.UI.Frontend+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.CommonM+import Game.LambdaHack.Client.MonadClient hiding (liftIO)+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.Frame+import Game.LambdaHack.Client.UI.Frontend import qualified Game.LambdaHack.Client.UI.Frontend as Frontend import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.Msg-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.Slideshow+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Client.UI.Slideshow import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.File+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.File import qualified Game.LambdaHack.Common.HighScore as HighScore import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.Save as Save-import Game.LambdaHack.Common.State-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind -- Assumes no interleaving with other clients, because each UI client -- in a different terminal/window/machine.@@ -115,34 +120,35 @@ connFrontendFrontKey :: MonadClientUI m => [K.KM] -> FrameForall -> m K.KM connFrontendFrontKey frontKeyKeys frontKeyFrame = do kmp <- connFrontend FrontKey{..}- modifySession $ \sess -> sess {spointer = kmpPointer kmp}- return $! kmpKeyMod kmp+ modifySession $ \sess -> sess {spointer = K.kmpPointer kmp}+ return $! K.kmpKeyMod kmp setFrontAutoYes :: MonadClientUI m => Bool -> m () setFrontAutoYes b = connFrontend $ FrontAutoYes b +frontendShutdown :: MonadClientUI m => m ()+frontendShutdown = connFrontend FrontShutdown++-- | Initialize the frontend chosen by the player via client options.+chanFrontend :: MonadClientUI m => ClientOptions -> m ChanFrontend+chanFrontend = liftIO . Frontend.chanFrontendIO+ anyKeyPressed :: MonadClientUI m => m Bool anyKeyPressed = connFrontend FrontPressed discardPressedKey :: MonadClientUI m => m () discardPressedKey = connFrontend FrontDiscard -addPressedKey :: MonadClientUI m => KMP -> m ()+addPressedKey :: MonadClientUI m => K.KMP -> m () addPressedKey = connFrontend . FrontAdd addPressedEsc :: MonadClientUI m => m ()-addPressedEsc = addPressedKey KMP { kmpKeyMod = K.escKM- , kmpPointer = originPoint }--frontendShutdown :: MonadClientUI m => m ()-frontendShutdown = connFrontend FrontShutdown--chanFrontend :: MonadClientUI m => DebugModeCli -> m ChanFrontend-chanFrontend = liftIO . Frontend.chanFrontendIO+addPressedEsc = addPressedKey K.KMP { K.kmpKeyMod = K.escKM+ , K.kmpPointer = originPoint } getReportUI :: MonadClientUI m => m Report getReportUI = do- report <- getsSession _sreport+ report <- getsSession sreport side <- getsClient sside fact <- getsState $ (EM.! side) . sfactionD let underAI = isAIFact fact@@ -152,7 +158,7 @@ getLeaderUI :: MonadClientUI m => m ActorId getLeaderUI = do cli <- getClient- case _sleader cli of+ case sleader cli of Nothing -> error $ "leader expected but not found" `showFailure` cli Just leader -> return leader @@ -164,7 +170,7 @@ case gquit fact of Just Status{stDepth} -> return $! toEnum stDepth Nothing -> getEntryArena fact- mleader <- getsClient _sleader+ mleader <- getsClient sleader case mleader of Just leader -> do -- The leader may just be teleporting (e.g., due to displace@@ -184,7 +190,7 @@ leaderTgtToPos :: MonadClientUI m => m (Maybe Point) leaderTgtToPos = do lidV <- viewedLevelUI- mleader <- getsClient _sleader+ mleader <- getsClient sleader case mleader of Nothing -> return Nothing Just aid -> do@@ -196,7 +202,7 @@ xhairToPos :: MonadClientUI m => m (Maybe Point) xhairToPos = do lidV <- viewedLevelUI- mleader <- getsClient _sleader+ mleader <- getsClient sleader sxhair <- getsSession sxhair case mleader of Nothing -> return Nothing -- e.g., when game start and no leader yet@@ -269,18 +275,18 @@ else emptySlideshow defaultHistory :: MonadClientUI m => Int -> m History-defaultHistory configHistoryMax = liftIO $ do+defaultHistory uHistoryMax = liftIO $ do utcTime <- getCurrentTime timezone <- getTimeZone utcTime let curDate = show $ utcToLocalTime timezone utcTime- emptyHist = emptyHistory configHistoryMax+ emptyHist = emptyHistory uHistoryMax return $! addReport emptyHist timeZero $ singletonReport $ toMsg $ stringToAL $ "Human history log started on " ++ curDate ++ "." tellAllClipPS :: MonadClientUI m => m () tellAllClipPS = do- bench <- getsClient $ sbenchmark . sdebugCli+ bench <- getsClient $ sbenchmark . soptions when bench $ do sstartPOSIX <- getsSession sstart curPOSIX <- liftIO getPOSIXTime@@ -300,7 +306,7 @@ tellGameClipPS :: MonadClientUI m => m () tellGameClipPS = do- bench <- getsClient $ sbenchmark . sdebugCli+ bench <- getsClient $ sbenchmark . soptions when bench $ do sgstartPOSIX <- getsSession sgstart curPOSIX <- liftIO getPOSIXTime@@ -344,14 +350,14 @@ -- of the client's faction. The actor may be not present in the dungeon. partActorLeader :: MonadClientUI m => ActorId -> ActorUI -> m MU.Part partActorLeader aid b = do- mleader <- getsClient _sleader+ mleader <- getsClient sleader return $! case mleader of Just leader | aid == leader -> "you" _ -> partActor b partActorLeaderFun :: MonadClientUI m => m (ActorId -> MU.Part) partActorLeaderFun = do- mleader <- getsClient _sleader+ mleader <- getsClient sleader sess <- getSession return $! \aid -> if mleader == Just aid@@ -362,7 +368,7 @@ -- of the client's faction. The actor may be not present in the dungeon. partPronounLeader :: MonadClient m => ActorId -> ActorUI -> m MU.Part partPronounLeader aid b = do- mleader <- getsClient _sleader+ mleader <- getsClient sleader return $! case mleader of Just leader | aid == leader -> "you" _ -> partPronoun b@@ -375,14 +381,15 @@ b <- getsSession $ getActorUI aid partActorLeader aid b -tryRestore :: MonadClientUI m => m (Maybe (State, StateClient, Maybe SessionUI))+-- | Try to read saved client game state from the file system.+tryRestore :: MonadClientUI m => m (Maybe (StateClient, Maybe SessionUI)) tryRestore = do cops@Kind.COps{corule} <- getsState scops- bench <- getsClient $ sbenchmark . sdebugCli+ bench <- getsClient $ sbenchmark . soptions if bench then return Nothing else do side <- getsClient sside- prefix <- getsClient $ ssavePrefixCli . sdebugCli+ prefix <- getsClient $ ssavePrefixCli . soptions let fileName = prefix <> Save.saveNameCli cops side res <- liftIO $ Save.restoreGame cops fileName let stdRuleset = Kind.stdRuleset corule
Game/LambdaHack/Client/UI/Msg.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}--- | Game messages displayed on top of the screen for the player to read.+-- | Game messages displayed on top of the screen for the player to read+-- and then saved to player history. module Game.LambdaHack.Client.UI.Msg ( -- * Msg Msg, toMsg, toPrompt@@ -11,23 +12,28 @@ , History, emptyHistory, addReport, lengthHistory , lastReportOfHistory, replaceLastReportOfHistory , splitReportForHistory, renderHistory+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , UAttrLine, uToAttrLine, attrLineToU+ , renderRepetition, renderTimeReport,+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary-import Data.Vector.Binary ()+import Data.Binary+import Data.Vector.Binary () import qualified Data.Vector.Unboxed as U-import Data.Word (Word32)-import GHC.Generics (Generic)+import Data.Word (Word32)+import GHC.Generics (Generic) -import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.Overlay import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.RingBuffer as RB-import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Time -- * UAttrLine
Game/LambdaHack/Client/UI/MsgM.hs view
@@ -1,6 +1,6 @@--- | Client monad for interacting with a human through UI.+-- | Monadic operations on game messages. module Game.LambdaHack.Client.UI.MsgM- ( msgAdd, promptAdd, promptAddAttr, recordHistory+ ( msgAdd, promptAdd, promptMainKeys, promptAddAttr, recordHistory ) where import Prelude ()@@ -11,30 +11,48 @@ import Game.LambdaHack.Client.UI.Msg import Game.LambdaHack.Client.UI.Overlay import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Client.UI.UIOptions+import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.MonadStateRead import Game.LambdaHack.Common.State -- | Add a message to the current report. msgAdd :: MonadClientUI m => Text -> m () msgAdd msg = modifySession $ \sess ->- sess {_sreport = snocReport (_sreport sess) (toMsg $ textToAL msg)}+ sess {_sreport = snocReport (sreport sess) (toMsg $ textToAL msg)} -- | Add a prompt to the current report. promptAdd :: MonadClientUI m => Text -> m () promptAdd msg = modifySession $ \sess ->- sess {_sreport = snocReport (_sreport sess) (toPrompt $ textToAL msg)}+ sess {_sreport = snocReport (sreport sess) (toPrompt $ textToAL msg)} +-- | Add a prompt with basic keys description.+promptMainKeys :: MonadClientUI m => m ()+promptMainKeys = do+ saimMode <- getsSession saimMode+ UIOptions{uVi, uLaptop} <- getsSession sUIOptions+ xhair <- getsSession sxhair+ let moveKeys | uVi = "keypad or hjklyubn"+ | uLaptop = "keypad or uk8o79jl"+ | otherwise = "keypad"+ keys | isNothing saimMode =+ "Explore with" <+> moveKeys <+> "keys or mouse."+ | otherwise =+ "Aim" <+> tgtKindDescription xhair+ <+> "with" <+> moveKeys <+> "keys or mouse."+ promptAdd keys+ -- | Add a prompt to the current report. promptAddAttr :: MonadClientUI m => AttrLine -> m () promptAddAttr msg = modifySession $ \sess ->- sess {_sreport = snocReport (_sreport sess) (toPrompt msg)}+ sess {_sreport = snocReport (sreport sess) (toPrompt msg)} -- | Store current report in the history and reset report. recordHistory :: MonadClientUI m => m () recordHistory = do time <- getsState stime- SessionUI{_sreport, shistory} <- getSession- unless (nullReport _sreport) $ do- let nhistory = addReport shistory time _sreport+ sessionUI <- getSession+ unless (nullReport $ sreport sessionUI) $ do+ let nhistory = addReport (shistory sessionUI) time (sreport sessionUI) modifySession $ \sess -> sess { _sreport = emptyReport , shistory = nhistory }
Game/LambdaHack/Client/UI/Overlay.hs view
@@ -2,42 +2,30 @@ -- | Screen overlays. module Game.LambdaHack.Client.UI.Overlay ( -- * AttrLine- AttrLine, emptyAttrLine, textToAL, fgToAL, stringToAL- , (<+:>), splitAttrLine, itemDesc, glueLines, updateLines+ AttrLine, emptyAttrLine, textToAL, fgToAL, stringToAL, (<+:>) -- * Overlay- , Overlay+ , Overlay, IntOverlay+ , splitAttrLine, glueLines, updateLines -- * Misc , ColorMode(..)- , FrameST, FrameForall(..), writeLine+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , linesAttr, splitAttrPhrase+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.Monad.ST.Strict-import qualified Data.EnumMap.Strict as EM import qualified Data.Text as T-import qualified Data.Vector.Generic as G-import qualified Data.Vector.Unboxed as U-import qualified Data.Vector.Unboxed.Mutable as VM-import Data.Word-import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Client.UI.EffectDescription-import Game.LambdaHack.Client.UI.ItemDescription import qualified Game.LambdaHack.Common.Color as Color-import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Time-import qualified Game.LambdaHack.Content.ItemKind as IK+import Game.LambdaHack.Common.Point -- * AttrLine +-- | Line of colourful text. type AttrLine = [Color.AttrCharW32] emptyAttrLine :: Int -> AttrLine@@ -49,6 +37,7 @@ in ac : l in T.foldr f [] t +-- | Render line of text in the given foreground colour. fgToAL :: Color.Color -> Text -> AttrLine fgToAL !fg !t = let f c l = let !ac = Color.attrChar2ToW32 fg c@@ -64,20 +53,33 @@ (<+:>) l1 [] = l1 (<+:>) l1 l2 = l1 ++ [Color.spaceAttrW32] ++ l2 +-- * Overlay++-- | A series of screen lines that either fit the width of the screen+-- or are intended for truncation when displayed. The length of overlay+-- may exceed the length of the screen, unlike in @SingleFrame@.+-- An exception is lines generated from animation, which have to fit+-- in either dimension.+type Overlay = [AttrLine]++-- | Sparse screen overlay representation where only the indicated rows+-- are overlayed and the remaining rows are kept unchanged.+type IntOverlay = [(Int, AttrLine)]+ -- | Split a string into lines. Avoids ending the line with a character -- other than whitespace or punctuation. Space characters are removed -- from the start, but never from the end of lines. Newlines are respected.-splitAttrLine :: X -> AttrLine -> [AttrLine]+splitAttrLine :: X -> AttrLine -> Overlay splitAttrLine w l = concatMap (splitAttrPhrase w . dropWhile (== Color.spaceAttrW32)) $ linesAttr l -linesAttr :: AttrLine -> [AttrLine]+linesAttr :: AttrLine -> Overlay linesAttr l | null l = [] | otherwise = h : if null t then [] else linesAttr (tail t) where (h, t) = span (/= Color.retAttrW32) l -splitAttrPhrase :: X -> AttrLine -> [AttrLine]+splitAttrPhrase :: X -> AttrLine -> Overlay splitAttrPhrase w xs | w >= length xs = [xs] -- no problem, everything fits | otherwise =@@ -88,112 +90,14 @@ then pre : splitAttrPhrase w post else reverse ppost : splitAttrPhrase w (reverse ppre ++ post) -itemDesc :: FactionId -> FactionDict -> Int -> CStore -> Time -> ItemFull- -> AttrLine-itemDesc side factionD aHurtMeleeOfOwner store localTime- itemFull@ItemFull{itemBase} =- let (_, unique, name, stats) =- partItemHigh side factionD store localTime itemFull- nstats = makePhrase [name, stats]- IK.ThrowMod{IK.throwVelocity, IK.throwLinger} = strengthToThrow itemBase- speed = speedFromWeight (jweight itemBase) throwVelocity- range = rangeFromSpeedAndLinger speed throwLinger- tspeed | speed < speedLimp = "When thrown, it drops at once."- | speed < speedWalk = "When thrown, it travels only one meter and drops immediately."- | otherwise =- "When thrown, it flies with speed of"- <+> tshow (fromSpeed speed `div` 10)- <> if throwLinger /= 100- then " m/s and range" <+> tshow range <+> "m."- else " m/s."- (desc, featureSentences, damageAnalysis) = case itemDisco itemFull of- Nothing -> ("This item is as unremarkable as can be.", "", tspeed)- Just ItemDisco{itemKind, itemAspect} ->- let sentences = mapMaybe featureToSentence (IK.ifeature itemKind)- hurtMeleeAspect :: IK.Aspect -> Bool- hurtMeleeAspect IK.AddHurtMelee{} = True- hurtMeleeAspect _ = False- aHurtMeleeOfItem = case itemAspect of- Just aspectRecord -> aHurtMelee aspectRecord- Nothing -> case find hurtMeleeAspect (IK.iaspects itemKind) of- Just (IK.AddHurtMelee d) -> Dice.meanDice d- _ -> 0- meanDmg = Dice.meanDice (jdamage itemBase)- dmgAn = if meanDmg <= 0 then "" else- let multRaw = aHurtMeleeOfOwner- + if store `elem` [CEqp, COrgan]- then 0- else aHurtMeleeOfItem- mult = 100 + min 99 (max (-99) multRaw)- minDeltaHP = xM meanDmg `divUp` 100- rawDeltaHP = fromIntegral mult * minDeltaHP- pmult = 100 + min 99 (max (-99) aHurtMeleeOfItem)- prawDeltaHP = fromIntegral pmult * minDeltaHP- pdeltaHP = modifyDamageBySpeed prawDeltaHP speed- mDeltaHP = modifyDamageBySpeed minDeltaHP speed- in "Against defenceless targets you would inflict around"- -- rounding and non-id items- <+> tshow meanDmg- <> "*" <> tshow mult <> "%"- <> "=" <> show64With2 rawDeltaHP- <+> "melee damage (min" <+> show64With2 minDeltaHP- <> ") and"- <+> tshow meanDmg- <> "*" <> tshow pmult <> "%"- <> "*" <> "speed^2"- <> "/" <> tshow (fromSpeed speedThrust `divUp` 10) <> "^2"- <> "=" <> show64With2 pdeltaHP- <+> "ranged damage (min" <+> show64With2 mDeltaHP- <> ") with it"- <> if Dice.minDice (jdamage itemBase)- == Dice.maxDice (jdamage itemBase)- then "."- else "on average."- in (IK.idesc itemKind, T.intercalate " " sentences, tspeed <+> dmgAn)- eqpSlotSentence = case strengthEqpSlot itemFull of- Just es -> slotToSentence es- Nothing -> ""- weight = jweight itemBase- (scaledWeight, unitWeight)- | weight > 1000 =- (tshow $ fromIntegral weight / (1000 :: Double), "kg")- | otherwise = (tshow weight, "g")- onLevel = "on level" <+> tshow (abs $ fromEnum $ jlid itemBase) <> "."- discoFirst = (if unique then "Discovered" else "First seen")- <+> onLevel- whose fid = gname (factionD EM.! fid)- sourceDesc =- case jfid itemBase of- Just fid | jsymbol itemBase `elem` ['+'] ->- "Caused by" <+> (if fid == side then "us" else whose fid)- <> ". First observed" <+> onLevel- Just fid ->- "Coming from" <+> whose fid- <> "." <+> discoFirst- _ -> discoFirst- colorSymbol = viewItem itemBase- blurb =- " "- <> nstats- <> ":"- <+> desc- <+> (if weight > 0- then makeSentence ["Weighs", MU.Text scaledWeight <> unitWeight]- else "")- <+> featureSentences- <+> eqpSlotSentence- <+> sourceDesc- <+> damageAnalysis- in colorSymbol : textToAL blurb--glueLines :: [AttrLine] -> [AttrLine] -> [AttrLine]+glueLines :: Overlay -> Overlay -> Overlay glueLines ov1 ov2 = reverse $ glue (reverse ov1) ov2 where glue [] l = l glue m [] = m glue (mh : mt) (lh : lt) = reverse lt ++ (mh <+:> lh) : mt -- @f@ should not enlarge the line beyond screen width.-updateLines :: Int -> (AttrLine -> AttrLine) -> [AttrLine] -> [AttrLine]+updateLines :: Int -> (AttrLine -> AttrLine) -> Overlay -> Overlay updateLines n f ov = let upd k (l : ls) = if k == 0 then f l : ls@@ -201,34 +105,10 @@ upd _ [] = [] in upd n ov --- blurb about [AttrLine]:--- | A series of screen lines that either fit the width of the screen--- or are intended for truncation when displayed. The length of overlay--- may exceed the length of the screen, unlike in @SingleFrame@.--- An exception is lines generated from animation, which have to fit--- in either dimension.---- * Overlay--type Overlay = [(Int, AttrLine)]- -- * Misc -- | Color mode for the display. data ColorMode = ColorFull -- ^ normal, with full colours- | ColorBW -- ^ black+white only+ | ColorBW -- ^ black and white only deriving Eq--type FrameST s = G.Mutable U.Vector s Word32 -> ST s ()--newtype FrameForall = FrameForall {unFrameForall :: forall s. FrameST s}--writeLine :: Int -> AttrLine -> FrameForall-{-# INLINE writeLine #-}-writeLine offset l = FrameForall $ \v -> do- let writeAt _ [] = return ()- writeAt off (ac32 : rest) = do- VM.write v off (Color.attrCharW32 ac32)- writeAt (off + 1) rest- writeAt offset l
− Game/LambdaHack/Client/UI/OverlayM.hs
@@ -1,85 +0,0 @@--- | A set of Overlay monad operations.-module Game.LambdaHack.Client.UI.OverlayM- ( describeMainKeys, lookAt- ) where--import Prelude ()--import Game.LambdaHack.Common.Prelude--import qualified Data.EnumMap.Strict as EM-import qualified Data.Text as T-import qualified NLP.Miniutter.English as MU--import Game.LambdaHack.Client.CommonM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.ItemDescription-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.State-import qualified Game.LambdaHack.Content.TileKind as TK--describeMainKeys :: MonadClientUI m => m Text-describeMainKeys = do- saimMode <- getsSession saimMode- Config{configVi, configLaptop} <- getsSession sconfig- xhair <- getsSession sxhair- let moveKeys | configVi = "keypad or hjklyubn"- | configLaptop = "keypad or uk8o79jl"- | otherwise = "keypad"- keys | isNothing saimMode =- "Explore with" <+> moveKeys <+> "keys or mouse."- | otherwise =- "Aim" <+> tgtKindDescription xhair- <+> "with" <+> moveKeys <+> "keys or mouse."- return $! keys---- | Produces a textual description of the terrain and items at an already--- explored position. Mute for unknown positions.--- The detailed variant is for use in the aiming mode.-lookAt :: MonadClientUI m- => Bool -- ^ detailed?- -> Text -- ^ how to start tile description- -> Bool -- ^ can be seen right now?- -> Point -- ^ position to describe- -> ActorId -- ^ the actor that looks- -> Text -- ^ an extra sentence to print- -> m Text-lookAt detailed tilePrefix canSee pos aid msg = do- Kind.COps{cotile=Kind.Ops{okind}} <- getsState scops- itemToF <- itemToFullClient- b <- getsState $ getActorBody aid- lidV <- viewedLevelUI- lvl <- getLevel lidV- localTime <- getsState $ getLocalTime lidV- subject <- partAidLeader aid- is <- getsState $ getFloorBag lidV pos- side <- getsClient sside- factionD <- getsState sfactionD- let verb = MU.Text $ if | pos == bpos b -> "stand on"- | canSee -> "notice"- | otherwise -> "remember"- let nWs (iid, kit@(k, _)) =- partItemWs side factionD k CGround localTime (itemToF iid kit)- isd = if EM.size is == 0 then ""- else makeSentence [ MU.SubjectVerbSg subject verb- , MU.WWandW $ map nWs $ EM.assocs is]- tile = lvl `at` pos- tileText = TK.tname (okind tile)- tilePart | T.null tilePrefix = MU.Text tileText- | otherwise = MU.AW $ MU.Text tileText- tileDesc = [MU.Text tilePrefix, tilePart]- if | detailed ->- return $! makeSentence tileDesc <+> msg <+> isd- | otherwise ->- return $! msg <+> isd
Game/LambdaHack/Client/UI/RunM.hs view
@@ -12,6 +12,10 @@ -- heard, solid tiles and actors in the way. module Game.LambdaHack.Client.UI.RunM ( continueRun+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , continueRunDir, enterableDir, tryTurning, checkAndRun+#endif ) where import Prelude ()@@ -19,24 +23,24 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import Data.Function+import Data.Function -import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.Msg-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.Request+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.TileKind as TK -- | Continue running in the given direction.@@ -107,7 +111,7 @@ RunParams{ runLeader , runMembers = aid : _ , runInitial } -> do- report <- getsSession _sreport+ report <- getsSession sreport let boringMsgs = map stringToAL [ "You hear a distant" , "reveals that the"
Game/LambdaHack/Client/UI/SessionUI.hs view
@@ -1,41 +1,40 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | The client UI session state. module Game.LambdaHack.Client.UI.SessionUI- ( SessionUI(..), emptySessionUI- , AimMode(..), RunParams(..), LastRecord, KeysHintMode(..)- , toggleMarkVision, toggleMarkSmell, getActorUI+ ( SessionUI(..), AimMode(..), RunParams(..), LastRecord(..), KeysHintMode(..)+ , sreport, emptySessionUI, toggleMarkVision, toggleMarkSmell, getActorUI ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.Map.Strict as M-import Data.Time.Clock.POSIX+import Data.Time.Clock.POSIX -import Game.LambdaHack.Client.UI.ActorUI-import Game.LambdaHack.Client.UI.Config-import Game.LambdaHack.Client.UI.Frontend-import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Client.UI.ActorUI+import Game.LambdaHack.Client.UI.Frontend+import Game.LambdaHack.Client.UI.ItemSlot import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.KeyBindings-import Game.LambdaHack.Client.UI.Msg-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Client.UI.KeyBindings+import Game.LambdaHack.Client.UI.Msg+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.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector -- | The information that is used across a client playing session, -- including many consecutive games in a single session. -- Some of it is saved, some is reset when a new playing session starts.--- An important component is a frontend session.+-- An important component is the frontend session. data SessionUI = SessionUI { sxhair :: Target -- ^ the common xhair , sactorUI :: ActorDictUI -- ^ assigned actor UI presentations@@ -44,7 +43,7 @@ , slastItemMove :: Maybe (CStore, CStore) -- ^ last item move stores , schanF :: ChanFrontend -- ^ connection with the frontend , sbinding :: Binding -- ^ binding of keys to commands- , sconfig :: Config+ , sUIOptions :: UIOptions -- ^ UI options as set by the player , saimMode :: Maybe AimMode -- ^ aiming mode , sxhairMoused :: Bool -- ^ last mouse aiming not vacuus , sitemSel :: Maybe (CStore, ItemId) -- ^ selected item, if any@@ -64,7 +63,7 @@ , smarkSmell :: Bool -- ^ mark smell, if the leader can smell , smenuIxMap :: M.Map String Int -- ^ indices of last used menu items- , sdisplayNeeded :: Bool -- ^ something to display on current level+ , sdisplayNeeded :: Bool -- ^ current level needs displaying , skeysHintMode :: KeysHintMode -- ^ how to show keys hints when no messages , sstart :: POSIXTime -- ^ this session start time , sgstart :: POSIXTime -- ^ this game start time@@ -88,10 +87,12 @@ } deriving (Show) -type LastRecord = ( [K.KM] -- accumulated keys of the current command- , [K.KM] -- keys of the rest of the recorded command batch- , Int -- commands left to record for this batch- )+-- | State of last recorded and currently being recorded key sequences.+data LastRecord = LastRecord+ { currentKeys :: [K.KM] -- ^ accumulated keys of the current command+ , previousKeys :: [K.KM] -- ^ keys of the rest of the recorded command batch+ , freeSpace :: Int -- ^ space left for commands to record in this batch+ } data KeysHintMode = KeysHintBlocked@@ -99,9 +100,11 @@ | KeysHintPresent deriving (Eq, Enum, Bounded) --- | Initial empty game client state.-emptySessionUI :: Config -> SessionUI-emptySessionUI sconfig =+sreport :: SessionUI -> Report+sreport = _sreport++emptySessionUI :: UIOptions -> SessionUI+emptySessionUI sUIOptions = SessionUI { sxhair = TVector $ Vector 0 0 , sactorUI = EM.empty@@ -111,7 +114,7 @@ , schanF = ChanFrontend $ const $ error $ "emptySessionUI: ChanFrontend" `showFailure` () , sbinding = Binding M.empty [] M.empty- , sconfig+ , sUIOptions , saimMode = Nothing , sxhairMoused = True , sitemSel = Nothing@@ -120,7 +123,7 @@ , _sreport = emptyReport , shistory = emptyHistory 0 , spointer = originPoint- , slastRecord = ([], [], 0)+ , slastRecord = LastRecord [] [] 0 , slastPlay = [] , slastLost = ES.empty , swaitTimes = 0@@ -153,7 +156,7 @@ put sactorUI put sslots put slastSlot- put sconfig+ put sUIOptions put saimMode put sitemSel put sselected@@ -168,7 +171,7 @@ sactorUI <- get sslots <- get slastSlot <- get- sconfig <- get -- is overwritten ASAP, but useful for, e.g., crash debug+ sUIOptions <- get -- is overwritten ASAP, but useful for, e.g., crash debug saimMode <- get sitemSel <- get sselected <- get@@ -184,7 +187,7 @@ sbinding = Binding M.empty [] M.empty sxhairMoused = True spointer = originPoint- slastRecord = ([], [], 0)+ slastRecord = LastRecord [] [] 0 slastPlay = [] slastLost = ES.empty swaitTimes = 0
Game/LambdaHack/Client/UI/Slideshow.hs view
@@ -3,25 +3,33 @@ ( KYX, OKX, Slideshow(slideshow) , emptySlideshow, unsnoc, toSlideshow, menuToSlideshow , wrapOKX, splitOverlay, splitOKX+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , moreMsg, endMsg, keysOKX+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Client.UI.ItemSlot import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.Msg-import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.Msg+import Game.LambdaHack.Client.UI.Overlay import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Point +-- | A key or an item slot label at a given position on the screen. type KYX = (Either [K.KM] SlotChar, (Y, X, X)) -type OKX = ([AttrLine], [KYX])+-- | An Overlay of text with an associated list of keys or slots+-- that activated when the specified screen position is pointed at.+-- The list should be sorted wrt rows and then columns.+type OKX = (Overlay, [KYX]) --- May be empty, but both of each @OKX@ list have to be nonempty.--- Guaranteed by construction.+-- | A list of active screenfulls to be shown one after another.+-- Each screenful has an independent numbering of rows and columns. newtype Slideshow = Slideshow {slideshow :: [OKX]} deriving (Show, Eq)
Game/LambdaHack/Client/UI/SlideshowM.hs view
@@ -1,4 +1,4 @@--- | A set of Slideshow monad operations.+-- | Monadic operations on slideshows and related data. module Game.LambdaHack.Client.UI.SlideshowM ( overlayToSlideshow, reportToSlideshow, reportToSlideshowKeep , displaySpaceEsc, displayMore, displayMoreKeep, displayYesNo, getConfirms@@ -9,18 +9,18 @@ import Game.LambdaHack.Common.Prelude -import Game.LambdaHack.Client.UI.FrameM-import Game.LambdaHack.Client.UI.ItemSlot+import Game.LambdaHack.Client.UI.FrameM+import Game.LambdaHack.Client.UI.ItemSlot import qualified Game.LambdaHack.Client.UI.Key as K-import Game.LambdaHack.Client.UI.MonadClientUI-import Game.LambdaHack.Client.UI.MsgM-import Game.LambdaHack.Client.UI.Overlay-import Game.LambdaHack.Client.UI.SessionUI-import Game.LambdaHack.Client.UI.Slideshow+import Game.LambdaHack.Client.UI.MonadClientUI+import Game.LambdaHack.Client.UI.MsgM+import Game.LambdaHack.Client.UI.Overlay+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Client.UI.Slideshow import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point -- | Add current report to the overlay, split the result and produce, -- possibly, many slides.@@ -89,7 +89,11 @@ (ekm, _) <- displayChoiceScreen dm False 0 slides extraKeys return $! either id (error $ "" `showFailure` ekm) ekm --- This is the only source of menus and so, effectively, UI modes.+-- | Display a, potentially, multi-screen menu and return the chosen+-- key or item slot label (and the index in the whole menu so that the cursor+-- can again be placed at that spot next time menu is displayed).+--+-- This function is the only source of menus and so, effectively, UI modes. displayChoiceScreen :: forall m . MonadClientUI m => ColorMode -> Bool -> Int -> Slideshow -> [K.KM] -> m (Either K.KM SlotChar, Int)@@ -191,7 +195,8 @@ page (max 0 (pointer - ixOnPage - 1)) _ | K.key ikm `elem` [K.PgDn, K.WheelSouth] -> page (min maxIx (pointer + pageLen - ixOnPage))- K.Space -> ignoreKey+ K.Space -> if pointer == maxIx then page 0+ else page maxIx _ -> error $ "unknown key" `showFailure` ikm pkm <- promptGetKey dm ov1 sfBlank legalKeys interpretKey pkm
+ Game/LambdaHack/Client/UI/UIOptions.hs view
@@ -0,0 +1,149 @@+{-# LANGUAGE DeriveGeneric #-}+-- | UI client options.+module Game.LambdaHack.Client.UI.UIOptions+ ( UIOptions(..), mkUIOptions, applyUIOptions+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , parseConfig+#endif+ ) where++import Prelude ()++import Game.LambdaHack.Common.Prelude++import Control.DeepSeq+import Data.Binary+import qualified Data.Ini as Ini+import qualified Data.Ini.Reader as Ini+import qualified Data.Ini.Types as Ini+import qualified Data.Map.Strict as M+import Game.LambdaHack.Client.ClientOptions+import GHC.Generics (Generic)+import System.FilePath+import Text.Read++import Game.LambdaHack.Client.UI.HumanCmd+import qualified Game.LambdaHack.Client.UI.Key as K+import Game.LambdaHack.Common.File+import qualified Game.LambdaHack.Common.Kind as Kind+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Content.RuleKind++-- | Options that affect the UI of the client.+data UIOptions = UIOptions+ { -- commands+ uCommands :: [(K.KM, CmdTriple)]+ -- hero names+ , uHeroNames :: [(Int, (Text, Text))]+ -- ui+ , uVi :: Bool -- ^ the option for Vi keys takes precendence+ , uLaptop :: Bool -- ^ because the laptop keys are the default+ , uGtkFontFamily :: Text+ , uSdlFontFile :: Text+ , uSdlTtfSizeAdd :: Int+ , uSdlFonSizeAdd :: Int+ , uFontSize :: Int+ , uColorIsBold :: Bool+ , uHistoryMax :: Int+ , uMaxFps :: Int+ , uNoAnim :: Bool+ , uRunStopMsgs :: Bool+ , uCmdline :: [String] -- ^ Hardwired commandline arguments to process.+ }+ deriving (Show, Generic)++instance NFData UIOptions++instance Binary UIOptions++parseConfig :: Ini.Config -> UIOptions+parseConfig cfg =+ let uCommands =+ let mkCommand (ident, keydef) =+ case stripPrefix "Cmd_" ident of+ Just _ ->+ let (key, def) = read keydef+ in (K.mkKM key, def :: CmdTriple)+ Nothing -> error $ "wrong macro id" `showFailure` ident+ section = Ini.allItems "extra_commands" cfg+ in map mkCommand section+ uHeroNames =+ let toNumber (ident, nameAndPronoun) =+ case stripPrefix "HeroName_" ident of+ Just n -> (read n, read nameAndPronoun)+ Nothing -> error $ "wrong hero name id" `showFailure` ident+ section = Ini.allItems "hero_names" cfg+ in map toNumber section+ getOption :: forall a. Read a => String -> a+ getOption optionName =+ let lookupFail :: forall b. String -> b+ lookupFail err =+ error $ "config file access failed"+ `showFailure` (err, optionName, cfg)+ s = fromMaybe (lookupFail "") $ Ini.getOption "ui" optionName cfg+ in either lookupFail id $ readEither s+ uVi = getOption "movementViKeys_hjklyubn"+ -- The option for Vi keys takes precendence,+ -- because the laptop keys are the default.+ uLaptop = not uVi && getOption "movementLaptopKeys_uk8o79jl"+ uGtkFontFamily = getOption "gtkFontFamily"+ uSdlFontFile = getOption "sdlFontFile"+ uSdlTtfSizeAdd = getOption "sdlTtfSizeAdd"+ uSdlFonSizeAdd = getOption "sdlFonSizeAdd"+ uFontSize = getOption "fontSize"+ uColorIsBold = getOption "colorIsBold"+ uHistoryMax = getOption "historyMax"+ uMaxFps = max 1 $ getOption "maxFps"+ uNoAnim = getOption "noAnim"+ uRunStopMsgs = getOption "runStopMsgs"+ uCmdline = words $ getOption "overrideCmdline"+ in UIOptions{..}++-- | Read and parse UI config file.+mkUIOptions :: Kind.COps -> Bool -> IO UIOptions+mkUIOptions Kind.COps{corule} benchmark = do+ let stdRuleset = Kind.stdRuleset corule+ cfgUIName = rcfgUIName stdRuleset+ sUIDefault = rcfgUIDefault stdRuleset+ cfgUIDefault = either (error . ("" `showFailure`)) id+ $ Ini.parse sUIDefault+ dataDir <- appDataDir+ let userPath = dataDir </> cfgUIName+ cfgUser <- if benchmark then return Ini.emptyConfig else do+ cpExists <- doesFileExist userPath+ if not cpExists+ then return Ini.emptyConfig+ else do+ sUser <- readFile userPath+ return $! either (error . ("" `showFailure`)) id $ Ini.parse sUser+ let cfgUI = M.unionWith M.union cfgUser cfgUIDefault -- user cfg preferred+ conf = parseConfig cfgUI+ -- Catch syntax errors in complex expressions ASAP,+ return $! deepseq conf conf++-- | Modify client options with UI options.+applyUIOptions :: Kind.COps -> UIOptions -> ClientOptions -> ClientOptions+applyUIOptions Kind.COps{corule} uioptions soptions =+ let stdRuleset = Kind.stdRuleset corule+ in (\opts -> opts {sgtkFontFamily =+ sgtkFontFamily opts `mplus` Just (uGtkFontFamily uioptions)}) .+ (\opts -> opts {sdlFontFile =+ sdlFontFile opts `mplus` Just (uSdlFontFile uioptions)}) .+ (\opts -> opts {sdlTtfSizeAdd =+ sdlTtfSizeAdd opts `mplus` Just (uSdlTtfSizeAdd uioptions)}) .+ (\opts -> opts {sdlFonSizeAdd =+ sdlFonSizeAdd opts `mplus` Just (uSdlFonSizeAdd uioptions)}) .+ (\opts -> opts {sfontSize =+ sfontSize opts `mplus` Just (uFontSize uioptions)}) .+ (\opts -> opts {scolorIsBold =+ scolorIsBold opts `mplus` Just (uColorIsBold uioptions)}) .+ (\opts -> opts {smaxFps =+ smaxFps opts `mplus` Just (uMaxFps uioptions)}) .+ (\opts -> opts {snoAnim =+ snoAnim opts `mplus` Just (uNoAnim uioptions)}) .+ (\opts -> opts {stitle =+ stitle opts `mplus` Just (rtitle stdRuleset)}) .+ (\opts -> opts {sfontDir =+ sfontDir opts `mplus` Just (rfontDir stdRuleset)})+ $ soptions
Game/LambdaHack/Common/Ability.hs view
@@ -10,11 +10,11 @@ import Game.LambdaHack.Common.Prelude -import Control.DeepSeq-import Data.Binary+import Control.DeepSeq+import Data.Binary import qualified Data.EnumMap.Strict as EM-import Data.Hashable (Hashable)-import GHC.Generics (Generic)+import Data.Hashable (Hashable)+import GHC.Generics (Generic) import Game.LambdaHack.Common.Misc @@ -38,16 +38,6 @@ -- It's also easier to code and maintain. type Skills = EM.EnumMap Ability Int -tacticSkills :: Tactic -> Skills-tacticSkills TExplore = zeroSkills-tacticSkills TFollow = zeroSkills-tacticSkills TFollowNoItems = ignoreItems-tacticSkills TMeleeAndRanged = meleeAndRanged-tacticSkills TMeleeAdjacent = meleeAdjacent-tacticSkills TBlock = blockOnly-tacticSkills TRoam = zeroSkills-tacticSkills TPatrol = zeroSkills- zeroSkills :: Skills zeroSkills = EM.empty @@ -59,6 +49,16 @@ scaleSkills :: Int -> Skills -> Skills scaleSkills n = EM.map (n *)++tacticSkills :: Tactic -> Skills+tacticSkills TExplore = zeroSkills+tacticSkills TFollow = zeroSkills+tacticSkills TFollowNoItems = ignoreItems+tacticSkills TMeleeAndRanged = meleeAndRanged+tacticSkills TMeleeAdjacent = meleeAdjacent+tacticSkills TBlock = blockOnly+tacticSkills TRoam = zeroSkills+tacticSkills TPatrol = zeroSkills minusTen, blockOnly, meleeAdjacent, meleeAndRanged, ignoreItems :: Skills
Game/LambdaHack/Common/Actor.hs view
@@ -1,44 +1,44 @@ {-# LANGUAGE DeriveGeneric #-}--- | Actors in the game: heroes, monsters, etc. No operation in this module--- involves the 'State' or 'Action' type.+-- | Actors in the game: heroes, monsters, etc. module Game.LambdaHack.Common.Actor- ( -- * Actor identifiers and related operations- ActorId, monsterGenChance- -- * The@ Acto@r type+ ( -- * Actor identifiers+ ActorId+ -- * The@ Acto@r type, its components and operations on them , Actor(..), ResDelta(..), ActorAspect , deltaSerious, deltaMild, actorCanMelee- , bspeed, actorTemplate, braced, waitedLastTurn, actorDying+ , bspeed, braced, actorTemplate, waitedLastTurn, actorDying , actorTrunkIsBlast, hpTooLow, calmEnough, hpEnough+ , checkAdjacent, eqpOverfull, eqpFreeN -- * Assorted- , ActorDict, smellTimeout, checkAdjacent- , eqpOverfull, eqpFreeN+ , ActorDict, monsterGenChance, smellTimeout ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM-import Data.Int (Int64)-import Data.Ratio-import GHC.Generics (Generic)+import Data.Int (Int64)+import Data.Ratio+import GHC.Generics (Generic) import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector -- | Actor properties that are changing throughout the game.--- If they are dublets of properties from @ActorKind@,--- they are usually modified temporarily, but tend to return--- to the original value from @ActorKind@ over time. E.g., HP.+-- If they appear dublets of properties of actor kinds, e.g. HP,+-- they may be results of casting the dice specified in their respective+-- actor kind and/or may be modified temporarily, but return+-- to the original value from their respective kind over time. data Actor = Actor { -- The trunk of the actor's body (present also in @borgan@ or @beqp@)- btrunk :: ItemId+ btrunk :: ItemId -- ^ the trunk organ of the actor's body -- Resources , bhp :: Int64 -- ^ current hit points * 1M@@ -48,11 +48,10 @@ -- Location , bpos :: Point -- ^ current position- , boldpos :: (Maybe Point)- -- ^ previous position, if any+ , boldpos :: Maybe Point -- ^ previous position, if any , blid :: LevelId -- ^ current level , bfid :: FactionId -- ^ faction the actor currently belongs to- , btrajectory :: (Maybe ([Vector], Speed))+ , btrajectory :: Maybe ([Vector], Speed) -- ^ trajectory the actor must -- travel and his travel speed @@ -82,6 +81,9 @@ type ActorAspect = EM.EnumMap ActorId AspectRecord +-- | All actors on the level, indexed by actor identifier.+type ActorDict = EM.EnumMap ActorId Actor+ deltaSerious :: ResDelta -> Bool deltaSerious ResDelta{..} = fst resCurrentTurn < 0 && fst resCurrentTurn /= minusM@@ -99,25 +101,16 @@ canMelee = EM.findWithDefault 0 Ability.AbMelee actorMaxSk > 0 in condUsableWeapon && canMelee --- | Chance that a new monster is generated. Currently depends on the--- number of monsters already present, and on the level. In the future,--- the strength of the character and the strength of the monsters present--- could further influence the chance, and the chance could also affect--- which monster is generated. How many and which monsters are generated--- will also depend on the cave kind used to build the level.-monsterGenChance :: AbsDepth -> AbsDepth -> Int -> Int -> Rnd Bool-monsterGenChance _ _ _ 0 = return False-monsterGenChance (AbsDepth n) (AbsDepth totalDepth) lvlSpawned actorCoeff =- assert (totalDepth > 0 && n > 0)- -- Mimics @castDice@. On level 5/10, first 6 monsters appear fast.- $ let scaledDepth = n * 10 `div` totalDepth- -- Heroes have to endure two lvl-sized waves of spawners for each level.- numSpawnedCoeff = lvlSpawned `div` 2- in chance $ 1%(fromIntegral- ((actorCoeff * (numSpawnedCoeff - scaledDepth))- `max` 1)) -- monsters up to level depth spawned at once+bspeed :: Actor -> AspectRecord -> Speed+bspeed !b AspectRecord{aSpeed} =+ case btrajectory b of+ Nothing -> toSpeed aSpeed+ Just (_, speed) -> speed --- | A template for a new actor.+-- | Whether an actor is braced for combat this clip.+braced :: Actor -> Bool+braced = bwait+ actorTemplate :: ItemId -> Int64 -> Int64 -> Point -> LevelId -> FactionId -> Actor actorTemplate btrunk bhp bcalm bpos blid bfid =@@ -133,17 +126,6 @@ bproj = False in Actor{..} -bspeed :: Actor -> AspectRecord -> Speed-bspeed !b AspectRecord{aSpeed} =- case btrajectory b of- Nothing -> toSpeed aSpeed- Just (_, speed) -> speed---- | Whether an actor is braced for combat this clip.-braced :: Actor -> Bool-braced = bwait---- | The actor waited last turn. waitedLastTurn :: Actor -> Bool waitedLastTurn = bwait @@ -171,13 +153,6 @@ let hpMax = max 1 aMaxHP in xM hpMax <= 2 * bhp b && bhp b > xM 1 --- | How long until an actor's smell vanishes from a tile.-smellTimeout :: Delta Time-smellTimeout = timeDeltaScale (Delta timeTurn) 100---- | All actors on the level, indexed by actor identifier.-type ActorDict = EM.EnumMap ActorId Actor- checkAdjacent :: Actor -> Actor -> Bool checkAdjacent sb tb = blid sb == blid tb && adjacent (bpos sb) (bpos tb) @@ -190,3 +165,25 @@ eqpFreeN b = let size = sum $ map fst $ EM.elems $ beqp b in assert (size <= 10 `blame` (b, size)) $ 10 - size++-- | Chance that a new monster is generated. Currently depends on the+-- number of monsters already present, and on the level. In the future,+-- the strength of the character and the strength of the monsters present+-- could further influence the chance, and the chance could also affect+-- which monster is generated. How many and which monsters are generated+-- will also depend on the cave kind used to build the level.+monsterGenChance :: AbsDepth -> AbsDepth -> Int -> Int -> Rnd Bool+monsterGenChance _ _ _ 0 = return False+monsterGenChance (AbsDepth n) (AbsDepth totalDepth) lvlSpawned actorCoeff =+ assert (totalDepth > 0 && n > 0)+ -- Mimics @castDice@. On level 5/10, first 6 monsters appear fast.+ $ let scaledDepth = n * 10 `div` totalDepth+ -- Heroes have to endure two lvl-sized waves of spawners for each level.+ numSpawnedCoeff = lvlSpawned `div` 2+ in chance $ 1%fromIntegral+ ((actorCoeff * (numSpawnedCoeff - scaledDepth))+ `max` 1) -- monsters up to level depth spawned at once++-- | How long until an actor's smell vanishes from a tile.+smellTimeout :: Delta Time+smellTimeout = timeDeltaScale (Delta timeTurn) 100
Game/LambdaHack/Common/ActorState.hs view
@@ -1,19 +1,20 @@ {-# LANGUAGE TupleSections #-}--- | Operations on the 'Actor' type that need the 'State' type,+-- | Operations on the 'Actor' type, and related, that need the 'State' type, -- but not our custom monad types. module Game.LambdaHack.Common.ActorState ( fidActorNotProjAssocs, actorAssocs, actorRegularAssocs , warActorRegularList, friendlyActorRegularList, fidActorRegularIds- , bagAssocs, bagAssocsK, calculateTotal- , mergeItemQuant, sharedEqp, sharedAllOwnedFid, findIid+ , bagAssocs, bagAssocsK, posToAidsLvl, posToAids, posToAssocs+ , nearbyFreePoints, calculateTotal, mergeItemQuant+ , sharedInv, sharedEqp, sharedAllOwned, sharedAllOwnedFid, findIid+ , getActorBody, getActorAspect, getCarriedAssocs, getCarriedIidCStore , getContainerBag, getFloorBag, getEmbedBag, getBodyStoreBag- , mapActorItems_, getActorAssocs- , nearbyFreePoints, getCarriedAssocs, getCarriedIidCStore- , posToAidsLvl, posToAids, posToAssocs- , getItemBody, memActor, getActorBody, getLocalTime, regenCalmDelta- , actorInAmbient, canDeAmbientList, actorSkills, dispEnemy, fullAssocs- , storeFromC, lidFromC, posFromC, aidFromC, isEscape, isStair- , anyFoeAdj, actorAdjacentAssocs, armorHurtBonus+ , mapActorItems_, getActorAssocs, getActorAssocsK+ , memActor, getLocalTime, regenCalmDelta+ , actorInAmbient, canDeAmbientList, actorSkills, dispEnemy+ , itemToFull, fullAssocs, storeFromC, aidFromC, lidFromC, posFromC+ , isEscape, isStair, anyFoeAdj, actorAdjacentAssocs+ , armorHurtBonus, inMelee ) where import Prelude ()@@ -21,23 +22,23 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import Data.Int (Int64)-import GHC.Exts (inline)+import Data.Int (Int64)+import GHC.Exts (inline) import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.TileKind (TileKind) fidActorNotProjAssocs :: FactionId -> State -> [(ActorId, Actor)] fidActorNotProjAssocs fid s =@@ -72,11 +73,6 @@ fidActorRegularIds fid lid s = map fst $ actorRegularAssocs (== fid) lid s -getItemBody :: ItemId -> State -> Item-getItemBody iid s =- let assFail = error $ "item body not found" `showFailure` (iid, s)- in EM.findWithDefault assFail iid $ sitemD s- bagAssocs :: State -> ItemBag -> [(ItemId, Item)] bagAssocs s bag = let iidItem iid = (iid, getItemBody iid s)@@ -160,6 +156,10 @@ {-# INLINE getActorBody #-} getActorBody aid s = sactorD s EM.! aid +getActorAspect :: ActorId -> State -> AspectRecord+{-# INLINE getActorAspect #-}+getActorAspect aid s = sactorAspect s EM.! aid+ getCarriedAssocs :: Actor -> State -> [(ItemId, Item)] getCarriedAssocs b s = -- The trunk is important for a case of spotting a caught projectile@@ -204,8 +204,7 @@ CSha -> gsha $ sfactionD s EM.! bfid b mapActorItems_ :: Monad m- => (CStore -> ItemId -> ItemQuant -> m a) -> Actor- -> State+ => (CStore -> ItemId -> ItemQuant -> m a) -> Actor -> State -> m () mapActorItems_ f b s = do let notProcessed = [CGround]@@ -269,10 +268,10 @@ then filter posDeAmbient (vicinityUnsafe $ bpos b) else [] -actorSkills :: Maybe ActorId -> ActorId -> AspectRecord -> State- -> Ability.Skills-actorSkills mleader aid ar s =+actorSkills :: Maybe ActorId -> ActorId -> State -> Ability.Skills+actorSkills mleader aid s = let body = getActorBody aid s+ ar = getActorAspect aid s player = gplayer . (EM.! bfid body) . sfactionD $ s skillsFromTactic = Ability.tacticSkills $ ftactic player factionSkills@@ -301,13 +300,16 @@ || EM.findWithDefault 0 Ability.AbMove actorMaxSk <= 0 || hasSupport sb && hasSupport tb) -- solo actors are flexible -fullAssocs :: Kind.COps -> DiscoveryKind -> DiscoveryAspect- -> ActorId -> [CStore] -> State- -> [(ItemId, ItemFull)]-fullAssocs cops disco discoAspect aid cstores s =+itemToFull :: State -> ItemId -> ItemQuant -> ItemFull+itemToFull s iid =+ itemToFull6 (scops s) (sdiscoKind s) (sdiscoAspect s) iid (getItemBody iid s)++fullAssocs :: ActorId -> [CStore] -> State -> [(ItemId, ItemFull)]+fullAssocs aid cstores s = let allAssocs = concatMap (\cstore -> getActorAssocsK aid cstore s) cstores iToFull (iid, (item, kit)) =- (iid, itemToFull cops disco discoAspect iid item kit)+ (iid, itemToFull6 (scops s) (sdiscoKind s) (sdiscoAspect s)+ iid item kit) in map iToFull allAssocs storeFromC :: Container -> CStore@@ -379,15 +381,31 @@ g !aid = (aid, getActorBody aid s) in map g $ concatMap f moves -armorHurtBonus :: ActorAspect -> ActorId -> ActorId -> State -> Int-armorHurtBonus actorAspect source target s =+armorHurtBonus :: ActorId -> ActorId -> State -> Int+armorHurtBonus source target s = let sb = getActorBody source s tb = getActorBody target s trim200 n = min 200 $ max (-200) n block200 b n = min 200 $ max (-200) $ n + if braced tb then b else 0- sar = actorAspect EM.! source- tar = actorAspect EM.! target+ sar = sactorAspect s EM.! source+ tar = sactorAspect s EM.! target itemBonus = trim200 (aHurtMelee sar) - if bproj sb then block200 25 (aArmorRanged tar) else block200 50 (aArmorMelee tar) in 100 + min 99 (max (-99) itemBonus) -- at least 1% of damage gets through++inMelee :: Actor -> State -> Bool+inMelee bodyOur s =+ let fact = sfactionD s EM.! bfid bodyOur+ f !b = blid b == blid bodyOur && isAtWar fact (bfid b) && bhp b > 0+ -- We assume foes are less numerous, because usually they are heroes,+ -- and so we compute them once and use many times.+ -- For the same reason @anyFoeAdj@ would not speed up this computation+ -- in normal gameplay (as opposed to AI vs AI benchmarks).+ allFoes = filter f $ EM.elems $ sactorD s+ in any (\body ->+ bfid bodyOur == bfid body+ && blid bodyOur == blid body+ && not (bproj body)+ && bhp body > 0+ && any (\b -> adjacent (bpos b) (bpos body)) allFoes) $ sactorD s
− Game/LambdaHack/Common/ClientOptions.hs
@@ -1,82 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--- | Screen frames and animations.-module Game.LambdaHack.Common.ClientOptions- ( DebugModeCli(..), defDebugModeCli- ) where--import Prelude ()--import Game.LambdaHack.Common.Prelude--import Data.Binary-import GHC.Generics (Generic)--data DebugModeCli = DebugModeCli- { sgtkFontFamily :: Maybe Text- -- ^ Font family to use for the GTK main game window.- , sdlFontFile :: Maybe Text- -- ^ Font file to use for the SDL2 main game window.- , sdlTtfSizeAdd :: Maybe Int- -- ^ Pixels to add to map cells on top of scalable font max glyph height.- , sdlFonSizeAdd :: Maybe Int- -- ^ Pixels to add to map cells on top of .fon font max glyph height.- , sfontSize :: Maybe Int- -- ^ Font size to use for the main game window.- , scolorIsBold :: Maybe Bool- -- ^ Whether to use bold attribute for colorful characters.- , smaxFps :: Maybe Int- -- ^ Maximal frames per second.- -- This is better low and fixed, to avoid jerkiness and delays- -- that tell the player there are many intelligent enemies on the level.- -- That's better than scaling AI sofistication down based- -- on the FPS setting and machine speed.- , sdisableAutoYes :: Bool- -- ^ Never auto-answer all prompts, even if under AI control.- , snoAnim :: Maybe Bool- -- ^ Don't show any animations.- , snewGameCli :: Bool- -- ^ Start a new game, overwriting the save file.- , sbenchmark :: Bool- -- ^ Don't create directories and files and show time stats.- , stitle :: Maybe Text- , sfontDir :: Maybe FilePath- , ssavePrefixCli :: String- -- ^ Prefix of the save game file name.- , sfrontendTeletype :: Bool- -- ^ Whether to use the stdout/stdin frontend.- , sfrontendNull :: Bool- -- ^ Whether to use null (no input/output) frontend.- , sfrontendLazy :: Bool- -- ^ Whether to use lazy (output not even calculated) frontend.- , sdbgMsgCli :: Bool- -- ^ Show clients' internal debug messages.- , sstopAfterSeconds :: Maybe Int- , sstopAfterFrames :: Maybe Int- }- deriving (Show, Eq, Generic)--instance Binary DebugModeCli--defDebugModeCli :: DebugModeCli-defDebugModeCli = DebugModeCli- { sgtkFontFamily = Nothing- , sdlFontFile = Nothing- , sdlTtfSizeAdd = Nothing- , sdlFonSizeAdd = Nothing- , sfontSize = Nothing- , scolorIsBold = Nothing- , smaxFps = Nothing- , sdisableAutoYes = False- , snoAnim = Nothing- , snewGameCli = False- , sbenchmark = False- , stitle = Nothing- , sfontDir = Nothing- , ssavePrefixCli = ""- , sfrontendTeletype = False- , sfrontendNull = False- , sfrontendLazy = False- , sdbgMsgCli = False- , sstopAfterSeconds = Nothing- , sstopAfterFrames = Nothing- }
Game/LambdaHack/Common/Color.hs view
@@ -2,32 +2,31 @@ -- | Colours and text attributes. module Game.LambdaHack.Common.Color ( -- * Colours- Color(..), defFG, isBright, darkCol, brightCol, stdCol- , colorToRGB- , Highlight (..)- -- * Text attributes and the screen- , Attr(..), defAttr- , AttrChar(..)- , AttrCharW32(..)+ Color(..)+ , defFG, isBright, darkCol, brightCol, stdCol, colorToRGB+ -- * Complete text attributes+ , Highlight (..), Attr(..)+ , defAttr+ -- * Characters with attributes+ , AttrChar(..), AttrCharW32(..) , attrCharToW32, attrCharFromW32 , fgFromW32, bgFromW32, charFromW32, attrFromW32, attrEnumFromW32- , spaceAttrW32, retAttrW32- , attrChar2ToW32, attrChar1ToW32+ , spaceAttrW32, retAttrW32, attrChar2ToW32, attrChar1ToW32 ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary-import Data.Bits (unsafeShiftL, unsafeShiftR, (.&.))+import Data.Binary+import Data.Bits (unsafeShiftL, unsafeShiftR, (.&.)) import qualified Data.Char as Char-import Data.Hashable (Hashable)-import Data.Word (Word32)-import GHC.Exts (Int (I#))-import GHC.Generics (Generic)-import GHC.Prim (int2Word#)-import GHC.Word (Word32 (W32#))+import Data.Hashable (Hashable)+import Data.Word (Word32)+import GHC.Exts (Int (I#))+import GHC.Generics (Generic)+import GHC.Prim (int2Word#)+import GHC.Word (Word32 (W32#)) -- | Colours supported by the major frontends. data Color =@@ -59,6 +58,59 @@ defFG :: Color defFG = White +-- | A helper for the terminal frontends that display bright via bold.+isBright :: Color -> Bool+isBright c = c >= BrBlack++-- | Colour sets.+darkCol, brightCol, stdCol :: [Color]+darkCol = [Red .. Cyan]+brightCol = [BrRed .. BrCyan] -- BrBlack is not really that bright+stdCol = darkCol ++ brightCol++-- | Translationg to heavily modified Linux console color RGB values.+--+-- Warning: SDL frontend sadly duplicates this code.+colorToRGB :: Color -> Text+colorToRGB Black = "#000000"+colorToRGB Red = "#D50000"+colorToRGB Green = "#00AA00"+colorToRGB Brown = "#CA4A00"+colorToRGB Blue = "#203AF0"+colorToRGB Magenta = "#AA00AA"+colorToRGB Cyan = "#00AAAA"+colorToRGB White = "#C5BCB8"+colorToRGB BrBlack = "#6F5F5F"+colorToRGB BrRed = "#FF5555"+colorToRGB BrGreen = "#75FF45"+colorToRGB BrYellow = "#FFE855"+colorToRGB BrBlue = "#4090FF"+colorToRGB BrMagenta = "#FF77FF"+colorToRGB BrCyan = "#60FFF0"+colorToRGB BrWhite = "#FFFFFF"++-- | For reference, the original Linux console colors.+-- Good old retro feel and more useful than xterm (e.g. brown).+_olorToRGB :: Color -> Text+_olorToRGB Black = "#000000"+_olorToRGB Red = "#AA0000"+_olorToRGB Green = "#00AA00"+_olorToRGB Brown = "#AA5500"+_olorToRGB Blue = "#0000AA"+_olorToRGB Magenta = "#AA00AA"+_olorToRGB Cyan = "#00AAAA"+_olorToRGB White = "#AAAAAA"+_olorToRGB BrBlack = "#555555"+_olorToRGB BrRed = "#FF5555"+_olorToRGB BrGreen = "#55FF55"+_olorToRGB BrYellow = "#FFFF55"+_olorToRGB BrBlue = "#5555FF"+_olorToRGB BrMagenta = "#FF55FF"+_olorToRGB BrCyan = "#55FFFF"+_olorToRGB BrWhite = "#FFFFFF"++-- | Additional map cell highlight, e.g., a colorful square around the cell+-- or a colorful background. data Highlight = HighlightNone | HighlightRed@@ -75,10 +127,10 @@ instance Hashable Highlight --- | Text attributes: foreground and backgroud colors.+-- | Text attributes: foreground color and highlight. data Attr = Attr { fg :: Color -- ^ foreground colour- , bg :: Highlight -- ^ backgroud highlight+ , bg :: Highlight -- ^ highlight } deriving (Show, Eq, Ord) @@ -91,6 +143,7 @@ defAttr :: Attr defAttr = Attr defFG HighlightNone +-- | Character to display, with its attribute. data AttrChar = AttrChar { acAttr :: Attr , acChar :: Char@@ -99,6 +152,7 @@ -- This implementation is faster than @Int@, because some vector updates -- can be done without going to and from @Int@.+-- | Optimized representation of 'AttrChar'. newtype AttrCharW32 = AttrCharW32 {attrCharW32 :: Word32} deriving (Show, Eq, Enum, Binary) @@ -147,57 +201,6 @@ retAttrW32 :: AttrCharW32 retAttrW32 = attrCharToW32 $ AttrChar defAttr '\n'---- | A helper for the terminal frontends that display bright via bold.-isBright :: Color -> Bool-isBright c = c >= BrBlack---- | Colour sets.-darkCol, brightCol, stdCol :: [Color]-darkCol = [Red .. Cyan]-brightCol = [BrRed .. BrCyan] -- BrBlack is not really that bright-stdCol = darkCol ++ brightCol---- | Translationg to heavily modified Linux console color RGB values.------ Warning: SDL frontend sadly duplicates this code.-colorToRGB :: Color -> Text-colorToRGB Black = "#000000"-colorToRGB Red = "#D50000"-colorToRGB Green = "#00AA00"-colorToRGB Brown = "#CA4A00"-colorToRGB Blue = "#203AF0"-colorToRGB Magenta = "#AA00AA"-colorToRGB Cyan = "#00AAAA"-colorToRGB White = "#C5BCB8"-colorToRGB BrBlack = "#6F5F5F"-colorToRGB BrRed = "#FF5555"-colorToRGB BrGreen = "#75FF45"-colorToRGB BrYellow = "#FFE855"-colorToRGB BrBlue = "#4090FF"-colorToRGB BrMagenta = "#FF77FF"-colorToRGB BrCyan = "#60FFF0"-colorToRGB BrWhite = "#FFFFFF"---- | For reference, the original Linux console colors.--- Good old retro feel and more useful than xterm (e.g. brown).-_olorToRGB :: Color -> Text-_olorToRGB Black = "#000000"-_olorToRGB Red = "#AA0000"-_olorToRGB Green = "#00AA00"-_olorToRGB Brown = "#AA5500"-_olorToRGB Blue = "#0000AA"-_olorToRGB Magenta = "#AA00AA"-_olorToRGB Cyan = "#00AAAA"-_olorToRGB White = "#AAAAAA"-_olorToRGB BrBlack = "#555555"-_olorToRGB BrRed = "#FF5555"-_olorToRGB BrGreen = "#55FF55"-_olorToRGB BrYellow = "#FFFF55"-_olorToRGB BrBlue = "#5555FF"-_olorToRGB BrMagenta = "#FF55FF"-_olorToRGB BrCyan = "#55FFFF"-_olorToRGB BrWhite = "#FFFFFF" attrChar2ToW32 :: Color -> Char -> AttrCharW32 {-# INLINE attrChar2ToW32 #-}
Game/LambdaHack/Common/ContentDef.hs view
@@ -2,11 +2,12 @@ -- and game content, defined completely afresh for the particular game. -- The general type of the content is @ContentDef@ and it has instances -- for all content kinds, such as items kinds--- (@Game.LambdaHack.Content.ItemKind@).--- The possible kinds are fixed in the library and all defined in the same--- directory. On the other hand, game content, that is all elements--- of @ContentDef@ instances, are defined in a directory--- of the game code proper, with names corresponding to their kinds.+-- ("Game.LambdaHack.Content.ItemKind").+--+-- The possible kinds are fixed in the library and all defined within+-- the library source code directory. On the other hand, game content,+-- that is the values whose types are @ContentDef@ instances,+-- are defined in the directory hosting the particular game definition. module Game.LambdaHack.Common.ContentDef ( ContentDef(..), contentFromList ) where
Game/LambdaHack/Common/Dice.hs view
@@ -1,122 +1,66 @@ {-# LANGUAGE DeriveGeneric, FlexibleInstances, TypeSynonymInstances #-} {-# OPTIONS_GHC -Wno-orphans #-}--- | Representation of dice for parameters scaled with current level depth.+-- | Representation of dice scaled with current level depth. module Game.LambdaHack.Common.Dice ( -- * Frequency distribution for casting dice scaled with level depth- Dice, diceConst, diceLevel, diceMult, (|*|)- , d, dl, intToDice- , maxDice, minDice, meanDice, reduceDice+ Dice, castDice, d, dl, z, zl, intToDice+ , minmaxDice, maxDice, minDice, meanDice, reduceDice -- * Dice for rolling a pair of integer parameters representing coordinates. , DiceXY(..), maxDiceXY, minDiceXY, meanDiceXY-#ifdef EXPOSE_INTERNAL- -- * Internal operations- , SimpleDice-#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.Applicative import Control.DeepSeq import Data.Binary-import qualified Data.Char as Char import Data.Hashable (Hashable)-import qualified Data.IntMap.Strict as IM-import qualified Data.Text as T-import Data.Tuple+import Game.LambdaHack.Common.Misc import GHC.Generics (Generic) -import Game.LambdaHack.Common.Frequency--type SimpleDice = Frequency Int--normalizeSimple :: SimpleDice -> SimpleDice-normalizeSimple fr = toFreq (nameFrequency fr)- $ map swap $ IM.toAscList $ IM.fromListWith (+)- $ map swap $ runFrequency fr---- Normalized mainly as an optimization, but it also makes many expected--- algebraic laws hold (wrt @Eq@), except for some laws about--- multiplication. We use @liftA2@ instead of @liftM2@, because it's probably--- faster in this case.-instance Num SimpleDice where- fr1 + fr2 = normalizeSimple $ liftA2AdditiveName "+" (+) fr1 fr2- fr1 * fr2 =- let frRes = normalizeSimple $ do- n <- fr1- sum $ replicate n fr2 -- not commutative!- nameRes =- case T.uncons $ nameFrequency fr2 of- _ | nameFrequency fr1 == "0" || nameFrequency fr2 == "0" -> "0"- Just ('d', _) | T.all Char.isDigit $ nameFrequency fr1 ->- nameFrequency fr1 <> nameFrequency fr2- _ -> nameFrequency fr1 <+> "*" <+> nameFrequency fr2- in renameFreq nameRes frRes- fr1 - fr2 = normalizeSimple $ liftA2AdditiveName "-" (-) fr1 fr2- negate = liftAName "-" negate- abs = normalizeSimple . liftAName "abs" abs- signum = normalizeSimple . liftAName "signum" signum- fromInteger n = renameFreq (tshow n) $ pure $ fromInteger n--liftAName :: Text -> (Int -> Int) -> SimpleDice -> SimpleDice-liftAName name f fr =- let frRes = f <$> fr- nameRes = name <> " (" <> nameFrequency fr <> ")"- in renameFreq nameRes frRes--liftA2AdditiveName :: Text- -> (Int -> Int -> Int)- -> SimpleDice -> SimpleDice -> SimpleDice-liftA2AdditiveName name f fra frb =- let frRes = liftA2 f fra frb- nameRes- | nameFrequency fra == "0" =- (if name == "+" then "" else name) <+> nameFrequency frb- | nameFrequency frb == "0" = nameFrequency fra- | otherwise = nameFrequency fra <+> name <+> nameFrequency frb- in renameFreq nameRes frRes--dieSimple :: Int -> SimpleDice-dieSimple n = uniformFreq ("d" <> tshow n) [1..n]--zdieSimple :: Int -> SimpleDice-zdieSimple n = uniformFreq ("z" <> tshow n) [0..n-1]--dieLevelSimple :: Int -> SimpleDice-dieLevelSimple n = uniformFreq ("dl" <> tshow n) [1..n]--zdieLevelSimple :: Int -> SimpleDice-zdieLevelSimple n = uniformFreq ("zl" <> tshow n) [0..n-1]---- | Dice for parameters scaled with current level depth.--- To the result of rolling the first set of dice we add the second,--- scaled in proportion to current depth divided by maximal dungeon depth.--- The result if then multiplied by the scale --- to be used to ensure--- that dice results are multiples of, e.g., 10. The scale is set with @|*|@.+-- | Multiple dice rolls, some scaled with current level depth, in which case+-- the sum of all rolls is scaled in proportion to current depth+-- divided by maximal dungeon depth. ----- Dice like 100d100 lead to enormous lists, so we help a bit--- by keeping simple dice nonstrict below.-data Dice = Dice- { diceConst :: ~SimpleDice- , diceLevel :: ~SimpleDice- , diceMult :: Int- }+-- The simple dice should have positive number of rolls and number of sides.+--+-- The @Num@ instance doesn't have @abs@ nor @signum@ defined,+-- because the functions for computing minimum, maximum and mean dice+-- results would be too costly.+data Dice =+ DiceI Int+ | DiceD Int Int+ | DiceDL Int Int+ | DiceZ Int Int+ | DiceZL Int Int+ | DicePlus Dice Dice+ | DiceTimes Dice Dice+ | DiceNegate Dice deriving (Eq, Ord, Generic) instance Show Dice where- show Dice{..} = T.unpack $- let rawMult = nameFrequency diceLevel- scaled = if rawMult == "0" then "" else rawMult- signAndMult = case T.uncons scaled of- Just ('-', _) -> scaled- _ -> "+" <+> scaled- in (if | nameFrequency diceLevel == "0" -> nameFrequency diceConst- | nameFrequency diceConst == "0" -> scaled- | otherwise -> nameFrequency diceConst <+> signAndMult)- <+> if diceMult == 1 then "" else "|*|" <+> tshow diceMult+ show dice1 = case dice1 of+ DiceI k -> show k+ DiceD n k -> show n ++ "d" ++ show k+ DiceDL n k -> show n ++ "dl" ++ show k+ DiceZ n k -> show n ++ "z" ++ show k+ DiceZL n k -> show n ++ "zl" ++ show k+ DicePlus d1 (DiceNegate d2) | simpleDice d2 -> show d1 ++ "-" ++ show d2+ DicePlus d1 (DiceNegate d2) -> show d1 ++ "-" ++ "(" ++ show d2 ++ ")"+ DicePlus d1 d2 -> show d1 ++ "+" ++ show d2+ DiceTimes d1 d2 -> "(" ++ show d1 ++ ") * (" ++ show d2 ++ ")" -- rare+ DiceNegate (DiceI k) -> "-" ++ show k -- "-2" parses as this, not as DiceI+ DiceNegate d1 -> "- (" ++ show d1 ++ ")" +simpleDice :: Dice -> Bool+simpleDice DiceI{} = True+simpleDice DiceD{} = True+simpleDice DiceDL{} = True+simpleDice DiceZ{} = True+simpleDice DiceZL{} = True+simpleDice _ = False+ instance Hashable Dice instance Binary Dice@@ -124,95 +68,150 @@ instance NFData Dice instance Num Dice where- (Dice dc1 dl1 ds1) + (Dice dc2 dl2 ds2) =- Dice (scaleFreq ds1 dc1 + scaleFreq ds2 dc2)- (scaleFreq ds1 dl1 + scaleFreq ds2 dl2)- (if ds1 == 1 && ds2 == 1 then 1 else- error $ "|*| must be at top level" `showFailure` (ds1, ds2))- (Dice dc1 dl1 ds1) * (Dice dc2 dl2 ds2) =- -- Hacky, but necessary (unless we forgo general multiplication and- -- stick to multiplications by a scalar from the left and from the right).- -- The pseudo-reasoning goes (remember the multiplication- -- is not commutative, so we take all kinds of liberties):- -- (dc1 + dl1 * l) * (dc2 + dl2 * l)- -- = dc1 * dc2 + dc1 * dl2 * l + dl1 * l * dc2 + dl1 * l * dl2 * l- -- = dc1 * dc2 + (dc1 * dl2) * l + (dl1 * dc2) * l + (dl1 * dl2) * l * l- -- Now, we don't have a slot to put the coefficient of l * l into- -- (and we don't know l yet, so we can't eliminate it by division),- -- so we happily ignore it. Done. It works well in the cases that interest- -- us, that is, multiplication by a scalar (a one-element frequency- -- distribution) from any side, unscaled and scaled by level depth- -- (but when we multiply two scaled scalars, we get 0).- Dice (scaleFreq ds1 dc1 * scaleFreq ds2 dc2)- (scaleFreq ds1 dc1 * scaleFreq ds2 dl2- + scaleFreq ds1 dl1 * scaleFreq ds2 dc2)- (if ds1 == 1 && ds2 == 1 then 1 else- error $ "|*| must be at top level" `showFailure` (ds1, ds2))- (Dice dc1 dl1 ds1) - (Dice dc2 dl2 ds2) =- Dice (scaleFreq ds1 dc1 - scaleFreq ds2 dc2)- (scaleFreq ds1 dl1 - scaleFreq ds2 dl2)- (if ds1 == 1 && ds2 == 1 then 1 else- error $ "|*| must be at top level" `showFailure` (ds1, ds2))- negate = affectBothDice negate- abs = affectBothDice abs- signum = affectBothDice signum- fromInteger n = Dice (fromInteger n) 0 1+ d1 + d2 = DicePlus d1 d2+ d1 * d2 = DiceTimes d1 d2+ d1 - d2 = d1 + DiceNegate d2+ negate = DiceNegate+ abs = undefined+ signum = undefined+ fromInteger n = DiceI (fromInteger n) -affectBothDice :: (SimpleDice -> SimpleDice) -> Dice -> Dice-affectBothDice f (Dice dc1 dl1 ds1) = Dice (f dc1) (f dl1) ds1+-- | Cast dice scaled with current level depth.+-- Note that at the first level, the scaled dice are always ignored.+--+-- The implementation calls RNG as many times as there are dice rolls,+-- which is costly, so content should prefer to case fewer dice+-- and then multiply them by a constant. If rounded results are not desired+-- (often they are, to limit the number of distinct item varieties+-- in inventory), another dice may be added to the result.+--+-- A different possible implementation, with dice represented as 'Frequency',+-- makes only one RNG call per dice, but due to lists lengths proportional+-- to the maximal value of the dice, it's is intractable for 1000d1000+-- and problematic already for 100d100.+castDice :: forall m. Monad m+ => ((Int, Int) -> m Int)+ -> AbsDepth -> AbsDepth -> Dice -> m Int+castDice randomR (AbsDepth lvlDepth) (AbsDepth maxDepth) dice = do+ let !_A = assert (lvlDepth >= 0 && lvlDepth <= maxDepth+ `blame` "invalid depth for dice rolls"+ `swith` (lvlDepth, maxDepth)) ()+ castNK n start k = do+ let f !acc 0 = return acc+ f acc count = do+ r <- randomR (start, k)+ f (acc + r) (count - 1)+ f 0 n+ scaleL k = (k * max 0 (lvlDepth - 1)) `div` max 1 (maxDepth - 1)+ castD :: Dice -> m Int+ castD dice1 = case dice1 of+ DiceI k -> return k+ DiceD n k -> castNK n 1 k+ DiceDL n k -> scaleL <$> castNK n 1 k+ DiceZ n k -> castNK n 0 (k - 1)+ DiceZL n k -> scaleL <$> castNK n 0 (k - 1)+ DicePlus d1 d2 -> do+ k1 <- castD d1+ k2 <- castD d2+ return $! k1 + k2+ DiceTimes d1 d2 -> do+ k1 <- castD d1+ k2 <- castD d2+ return $! k1 * k2+ DiceNegate d1 -> do+ k <- castD d1+ return $! negate k+ castD dice --- | A single simple dice.-d :: Int -> Dice-d n = Dice (dieSimple n) 0 1+-- | A die, rolled the given number of times. E.g., @1 `d` 2@ rolls 2-sided+-- die one time.+d :: Int -> Int -> Dice+d n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))+ $ DiceD n k --- | Dice scaled with level.-dl :: Int -> Dice-dl n = Dice 0 (dieLevelSimple n) 1+-- | A die rolled the given number of times, with the result scaled+-- with dungeon level depth.+dl :: Int -> Int -> Dice+dl n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))+ $ DiceDL n k --- Not exposed to save on documentation.-_z :: Int -> Dice-_z n = Dice (zdieSimple n) 0 1+-- | A die, starting from zero, ending at one less than the bound,+-- rolled the given number of times. E.g., @1 `z` 1@ always rolls zero.+z :: Int -> Int -> Dice+z n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))+ $ DiceZ n k -_zl :: Int -> Dice-_zl n = Dice 0 (zdieLevelSimple n) 1+-- | A die, starting from zero, ending at one less than the bound,+-- rolled the given number of times,+-- with the result scaled with dungeon level depth.+zl :: Int -> Int -> Dice+zl n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))+ $ DiceZL n k intToDice :: Int -> Dice-intToDice = fromInteger . fromIntegral+intToDice = DiceI -infixl 5 |*|--- | Multiplying the dice, after all randomness is resolved, by a constant.--- Infix declaration ensures that @1 + 2 |*| 3@ parses as @(1 + 2) |*| 3@.-(|*|) :: Dice -> Int -> Dice-Dice dc1 dl1 ds1 |*| s2 = Dice dc1 dl1 (ds1 * s2)+-- | Minimal and maximal possible value of the dice.+--+-- @divUp@ in the implementation corresponds to @ceiling@,+-- applied to results of @meanDice@ elsewhere in the code,+-- and prevents treating 1d1-power effects (on shallow levels) as null effects.+minmaxDice :: Dice -> (Int, Int)+minmaxDice dice1 = case dice1 of+ DiceI k -> (k, k)+ DiceD n k -> (n, n * k)+ DiceDL n k -> (0, n * k) -- bottom and top level considered+ DiceZ n k -> (0, n * (k - 1))+ DiceZL n k -> (0, n * (k - 1)) -- bottom and top level considered+ DicePlus d1 d2 ->+ let (minD1, maxD1) = minmaxDice d1+ (minD2, maxD2) = minmaxDice d2+ in (minD1 + minD2, maxD1 + maxD2)+ DiceTimes (DiceI k) d2 ->+ let (minD2, maxD2) = minmaxDice d2+ in if k >= 0 then (k * minD2, k * maxD2) else (k * maxD2, k * minD2)+ DiceTimes d1 (DiceI k) ->+ let (minD1, maxD1) = minmaxDice d1+ in if k >= 0 then (minD1 * k, maxD1 * k) else (maxD1 * k, minD1 * k)+ -- Multiplication other than the two cases above is unlikely, but here it is.+ DiceTimes d1 d2 ->+ let (minD1, maxD1) = minmaxDice d1+ (minD2, maxD2) = minmaxDice d2+ options = [minD1 * minD2, minD1 * maxD2, maxD1 * maxD2, maxD1 * minD2]+ in (minimum options, maximum options)+ DiceNegate d1 ->+ let (minD1, maxD1) = minmaxDice d1+ in (negate maxD1, negate minD1) -- | Maximal value of dice. The scaled part taken assuming median level. maxDice :: Dice -> Int-maxDice Dice{..} = (fromMaybe 0 (maxFreq diceConst)- + fromMaybe 0 (maxFreq diceLevel) `div` 2)- * diceMult+maxDice = snd . minmaxDice -- | Minimal value of dice. The scaled part taken assuming median level. minDice :: Dice -> Int-minDice Dice{..} = (fromMaybe 0 (minFreq diceConst)- + fromMaybe 0 (minFreq diceLevel) `div` 2)- * diceMult+minDice = fst . minmaxDice -- | Mean value of dice. The scaled part taken assuming median level.--- Assumes the frequencies are not null.-meanDice :: Dice -> Int-meanDice Dice{..} = (meanFreq diceConst- + meanFreq diceLevel `div` 2)- * diceMult+meanDice :: Dice -> Double+meanDice dice1 = case dice1 of+ DiceI k -> fromIntegral k+ DiceD n k -> fromIntegral (n * (k + 1)) / 2+ DiceDL n k -> fromIntegral (n * (k + 1)) / 4+ DiceZ n k -> fromIntegral (n * k) / 2+ DiceZL n k -> fromIntegral (n * k) / 4+ DicePlus d1 d2 -> meanDice d1 + meanDice d2+ DiceTimes d1 d2 -> meanDice d1 * meanDice d2 -- I hope this is that simple+ DiceNegate d1 -> negate $ meanDice d1 reduceDice :: Dice -> Maybe Int-reduceDice de =- let minD = minDice de- in if minD == maxDice de then Just minD else Nothing+reduceDice d1 =+ let (minD1, maxD1) = minmaxDice d1+ in if minD1 == maxD1 then Just minD1 else Nothing -- | Dice for rolling a pair of integer parameters pertaining to, -- respectively, the X and Y cartesian 2D coordinates. data DiceXY = DiceXY Dice Dice- deriving (Show, Eq, Ord, Generic)+ deriving (Show, Generic) instance Hashable DiceXY @@ -227,5 +226,5 @@ minDiceXY (DiceXY x y) = (minDice x, minDice y) -- | Mean value of DiceXY.-meanDiceXY :: DiceXY -> (Int, Int)+meanDiceXY :: DiceXY -> (Double, Double) meanDiceXY (DiceXY x y) = (meanDice x, meanDice y)
Game/LambdaHack/Common/Faction.hs view
@@ -1,10 +1,10 @@ {-# LANGUAGE DeriveGeneric #-}--- | Factions taking part in the game: e.g., two human players controlling--- the hero faction battling the monster and the animal factions.+-- | Factions taking part in the game, e.g., a hero faction, a monster faction+-- and an animal faction. module Game.LambdaHack.Common.Faction ( FactionId, FactionDict, Faction(..), Diplomacy(..), Status(..)- , Target(..), TGoal(..), Challenge(..), tgtKindDescription- , isHorrorFact, nameOfHorrorFact+ , Target(..), TGoal(..), Challenge(..)+ , gleader, tgtKindDescription, isHorrorFact, nameOfHorrorFact , noRunWithMulti, isAIFact, autoDungeonLevel, automatePlayer , isAtWar, isAllied , difficultyBound, difficultyDefault, difficultyCoeff, difficultyInverse@@ -19,25 +19,26 @@ import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.IntMap.Strict as IM-import GHC.Generics (Generic)+import GHC.Generics (Generic) import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Actor import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind (ItemKind)-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.ModeKind -- | All factions in the game, indexed by faction identifier. type FactionDict = EM.EnumMap FactionId Faction +-- | The faction datatype. data Faction = Faction { gname :: Text -- ^ individual name , gcolor :: Color.Color -- ^ color of actors or their frames@@ -46,14 +47,14 @@ , gdipl :: Dipl -- ^ diplomatic mode , gquit :: Maybe Status -- ^ cause of game end/exit , _gleader :: Maybe ActorId -- ^ the leader of the faction; don't use- -- in place of _sleader on clients+ -- in place of sleader on clients , gsha :: ItemBag -- ^ faction's shared inventory , gvictims :: EM.EnumMap (Kind.Id ItemKind) Int -- ^ members killed , gvictimsD :: EM.EnumMap (Kind.Id ModeKind) (IM.IntMap (EM.EnumMap (Kind.Id ItemKind) Int)) -- ^ members killed in the past, by game mode and difficulty level }- deriving (Show, Eq, Ord, Generic)+ deriving (Show, Eq, Generic) instance Binary Faction @@ -90,17 +91,19 @@ instance Binary Target +-- | The goal of an actor. data TGoal = TEnemyPos ActorId Bool -- ^ last seen position of the targeted actor | TEmbed ItemBag Point- -- ^ in @TPoint (TEmbed bag p) _ q@ usually @bag@ is embbedded in @p@- -- and @q@ is an adjacent open tile- | TItem ItemBag- | TSmell- | TUnknown- | TKnown- | TAny+ -- ^ embedded item that can be triggered;+ -- in @TPoint (TEmbed bag p) _ q@ usually @bag@ is embbedded in @p@+ -- and @q@ is an adjacent open tile+ | TItem ItemBag -- ^ item lying on the ground+ | TSmell -- ^ smell potentially left by enemies+ | TUnknown -- ^ an unknown tile to be explored+ | TKnown -- ^ a known tile to be patrolled+ | TAny -- ^ an unspecified goal deriving (Show, Eq, Ord, Generic) instance Binary TGoal@@ -113,6 +116,9 @@ deriving (Show, Eq, Ord, Generic) instance Binary Challenge++gleader :: Faction -> Maybe ActorId+gleader = _gleader tgtKindDescription :: Target -> Text tgtKindDescription tgt = case tgt of
Game/LambdaHack/Common/File.hs view
@@ -1,4 +1,4 @@--- | Saving/loading with serialization and compression.+-- | Saving/loading to files, with serialization and compression. module Game.LambdaHack.Common.File ( encodeEOF, strictDecodeEOF , tryCreateDir, doesFileExist, tryWriteFile, readFile, renameFile
Game/LambdaHack/Common/Flavour.hs view
@@ -4,11 +4,15 @@ ( -- * The @Flavour@ type Flavour , -- * Constructors- zipPlain, zipFancy, stdFlav, zipLiquid+ zipPlain, zipFancy, zipLiquid, stdFlav , -- * Accessors flavourToColor, flavourToName -- * Assorted- , colorToTeamName, colorToPlainName, colorToFancyName+ , colorToPlainName, colorToFancyName, colorToTeamName+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , FancyName, colorToLiquidName+#endif ) where import Prelude ()
Game/LambdaHack/Common/Frequency.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE DeriveFoldable, DeriveGeneric, DeriveTraversable #-}--- | A list of items with relative frequencies of appearance.+-- | A list of entities with relative frequencies of appearance. module Game.LambdaHack.Common.Frequency ( -- * The @Frequency@ type Frequency@@ -20,6 +20,7 @@ import Control.DeepSeq import Data.Binary import Data.Hashable (Hashable)+import Data.Int (Int32) import Data.Ord (comparing) import GHC.Generics (Generic) @@ -29,31 +30,43 @@ -- -- The @Eq@ instance compares raw representations, not relative, -- normalized frequencies, so operations don't need to preserve--- the expected equalities, unless they do some kind of normalization--- (see 'Dice').+-- the expected equalities. data Frequency a = Frequency { runFrequency :: [(Int, a)] -- ^ give acces to raw frequency values- , nameFrequency :: ~Text -- ^ short description for debug, etc.;- -- keep it lazy, because it's rarely used+ , nameFrequency :: Text -- ^ short description for debug, etc. } deriving (Show, Eq, Ord, Foldable, Traversable, Generic) +_maxBound32 :: Integer+_maxBound32 = toInteger (maxBound :: Int32)+ instance Monad Frequency where- {-# INLINE return #-}- return x = Frequency [(1, x)] "return" Frequency xs name >>= f =- Frequency [ (p * q, y) | (p, x) <- xs- , (q, y) <- runFrequency (f x) ]+ Frequency [+#ifdef WITH_EXPENSIVE_ASSERTIONS+ assert (toInteger p * toInteger q <= _maxBound32)+#endif+ (p * q, y)+ | (p, x) <- xs+ , (q, y) <- runFrequency (f x)+ ] ("bind (" <> name <> ")") instance Functor Frequency where fmap f (Frequency xs name) = Frequency (map (second f) xs) name instance Applicative Frequency where- pure = return+ {-# INLINE pure #-}+ pure x = Frequency [(1, x)] "pure" Frequency fs fname <*> Frequency ys yname =- Frequency [ (p * q, f y) | (p, f) <- fs- , (q, y) <- ys ]+ Frequency [+#ifdef WITH_EXPENSIVE_ASSERTIONS+ assert (toInteger p * toInteger q <= _maxBound32)+#endif+ (p * q, f y)+ | (p, f) <- fs+ , (q, y) <- ys+ ] ("(" <> fname <> ") <*> (" <> yname <> ")") instance MonadPlus Frequency where@@ -83,14 +96,23 @@ -- | Takes a name and a list of frequencies and items -- into the frequency distribution. toFreq :: Text -> [(Int, a)] -> Frequency a-toFreq name l = Frequency (filter ((> 0 ) . fst) l) name+toFreq name l =+#ifdef WITH_EXPENSIVE_ASSERTIONS+ assert (all (\(p, _) -> toInteger p <= _maxBound32) l) $+#endif+ Frequency (filter ((> 0 ) . fst) l) name -- | Scale frequency distribution, multiplying it -- by a positive integer constant. scaleFreq :: Show a => Int -> Frequency a -> Frequency a scaleFreq n (Frequency xs name) = assert (n > 0 `blame` "non-positive frequency scale" `swith` (name, n, xs)) $- Frequency (map (first (* n)) xs) name+ let multN p =+#ifdef WITH_EXPENSIVE_ASSERTIONS+ assert (toInteger p * toInteger n <= _maxBound32) $+#endif+ p * n+ in Frequency (map (first multN) xs) name -- | Change the description of the frequency. renameFreq :: Text -> Frequency a -> Frequency a
Game/LambdaHack/Common/HSFile.hs view
@@ -1,7 +1,11 @@--- | Saving/loading with serialization and compression.+-- | Saving/loading to files, with serialization and compression. module Game.LambdaHack.Common.HSFile ( encodeEOF, strictDecodeEOF , tryCreateDir, doesFileExist, tryWriteFile, readFile, renameFile+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , encodeData+#endif ) where import Prelude ()@@ -10,12 +14,12 @@ import qualified Codec.Compression.Zlib as Z import qualified Control.Exception as Ex-import Data.Binary+import Data.Binary import qualified Data.ByteString.Lazy as LBS-import System.Directory-import System.FilePath-import System.IO (IOMode (..), hClose, openBinaryFile, readFile, withBinaryFile,- writeFile)+import System.Directory+import System.FilePath+import System.IO (IOMode (..), hClose, openBinaryFile, readFile,+ withBinaryFile, writeFile) -- | Serialize, compress and save data. -- Note that LBS.writeFile opens the file in binary mode.
Game/LambdaHack/Common/HighScore.hs view
@@ -1,11 +1,11 @@ {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} -- | High score table operations. module Game.LambdaHack.Common.HighScore- ( ScoreDict, ScoreTable+ ( ScoreTable, ScoreDict , empty, register, showScore, getTable, getRecord, highSlideshow #ifdef EXPOSE_INTERNAL -- * Internal operations- , ScoreRecord+ , ScoreRecord, insertPos, showTable, showNearbyScores #endif ) where @@ -13,21 +13,22 @@ import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.Text as T-import Data.Time.Clock.POSIX-import Data.Time.LocalTime-import GHC.Generics (Generic)+import Data.Time.Clock.POSIX+import Data.Time.LocalTime+import GHC.Generics (Generic) import qualified NLP.Miniutter.English as MU -import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Faction import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind (ItemKind)-import Game.LambdaHack.Content.ModeKind (HiCondPoly, HiIndeterminant (..),- ModeKind, Outcome (..))+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.ModeKind (HiCondPoly,+ HiIndeterminant (..),+ ModeKind, Outcome (..)) -- | A single score record. Records are ordered in the highscore table, -- from the best to the worst, in lexicographic ordering wrt the fields below.@@ -41,7 +42,7 @@ , ourVictims :: EM.EnumMap (Kind.Id ItemKind) Int -- ^ allies lost , theirVictims :: EM.EnumMap (Kind.Id ItemKind) Int -- ^ foes killed }- deriving (Eq, Ord, Show, Generic)+ deriving (Show, Eq, Ord, Generic) instance Binary ScoreRecord @@ -55,42 +56,6 @@ -- | A dictionary from game mode IDs to scores tables. type ScoreDict = EM.EnumMap (Kind.Id ModeKind) ScoreTable --- | Show a single high score, from the given ranking in the high score table.-showScore :: TimeZone -> (Int, ScoreRecord) -> [Text]-showScore tz (pos, score) =- let Status{stOutcome, stDepth} = status score- died = case stOutcome of- Killed -> "perished on level" <+> tshow (abs stDepth)- Defeated -> "got defeated"- Camping -> "set camp"- Conquer -> "slew all opposition"- Escape -> "emerged victorious"- Restart -> "resigned prematurely"- curDate = tshow . utcToLocalTime tz . posixSecondsToUTCTime . date $ score- turns = absoluteTimeNegate (negTime score) `timeFitUp` timeTurn- tpos = T.justifyRight 3 ' ' $ tshow pos- tscore = T.justifyRight 6 ' ' $ tshow $ points score- victims = let nkilled = sum $ EM.elems $ theirVictims score- nlost = sum $ EM.elems $ ourVictims score- in "killed" <+> tshow nkilled <> ", lost" <+> tshow nlost- diff = cdiff $ challenge score- diffText | diff == difficultyDefault = ""- | otherwise = "difficulty" <+> tshow diff <> ", "- tturns = makePhrase [MU.CarWs turns "turn"]- in [ tpos <> "." <+> tscore <+> gplayerName score- <+> died <> "," <+> victims <> ","- , " "- <> diffText <> "after" <+> tturns <+> "on" <+> curDate <> "."- ]--getTable :: Kind.Id ModeKind -> ScoreDict -> ScoreTable-getTable = EM.findWithDefault (ScoreTable [])--getRecord :: Int -> ScoreTable -> ScoreRecord-getRecord pos (ScoreTable table) =- fromMaybe (error $ "" `showFailure` (pos, table))- $ listToMaybe $ drop (pred pos) table- -- | Empty score table empty :: ScoreDict empty = EM.empty@@ -140,6 +105,42 @@ negTime = absoluteTimeNegate time score = ScoreRecord{..} in (points > 0, insertPos score table)++-- | Show a single high score, from the given ranking in the high score table.+showScore :: TimeZone -> (Int, ScoreRecord) -> [Text]+showScore tz (pos, score) =+ let Status{stOutcome, stDepth} = status score+ died = case stOutcome of+ Killed -> "perished on level" <+> tshow (abs stDepth)+ Defeated -> "got defeated"+ Camping -> "set camp"+ Conquer -> "slew all opposition"+ Escape -> "emerged victorious"+ Restart -> "resigned prematurely"+ curDate = tshow . utcToLocalTime tz . posixSecondsToUTCTime . date $ score+ turns = absoluteTimeNegate (negTime score) `timeFitUp` timeTurn+ tpos = T.justifyRight 3 ' ' $ tshow pos+ tscore = T.justifyRight 6 ' ' $ tshow $ points score+ victims = let nkilled = sum $ EM.elems $ theirVictims score+ nlost = sum $ EM.elems $ ourVictims score+ in "killed" <+> tshow nkilled <> ", lost" <+> tshow nlost+ diff = cdiff $ challenge score+ diffText | diff == difficultyDefault = ""+ | otherwise = "difficulty" <+> tshow diff <> ", "+ tturns = makePhrase [MU.CarWs turns "turn"]+ in [ tpos <> "." <+> tscore <+> gplayerName score+ <+> died <> "," <+> victims <> ","+ , " "+ <> diffText <> "after" <+> tturns <+> "on" <+> curDate <> "."+ ]++getTable :: Kind.Id ModeKind -> ScoreDict -> ScoreTable+getTable = EM.findWithDefault (ScoreTable [])++getRecord :: Int -> ScoreTable -> ScoreRecord+getRecord pos (ScoreTable table) =+ fromMaybe (error $ "" `showFailure` (pos, table))+ $ listToMaybe $ drop (pred pos) table -- | Show a screenful of the high scores table. -- Parameter height is the number of (3-line) scores to be shown.
Game/LambdaHack/Common/Item.hs view
@@ -1,20 +1,22 @@ {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} -- | Weapons, treasure and all the other items in the game.--- No operation in this module involves the state or any of our custom monads. module Game.LambdaHack.Common.Item- ( -- * The @Item@ type- ItemId, Item(..), ItemSource(..)- , itemPrice, goesIntoEqp, isMelee, goesIntoInv, goesIntoSha- , seedToAspect, meanAspect, aspectRecordToList- , aspectRecordFull, aspectsRandom- -- * Item discovery types- , ItemKindIx, ItemSeed, KindMean(..), DiscoveryKind- , Benefit(..), DiscoveryBenefit- , AspectRecord(..), emptyAspectRecord, sumAspectRecord, DiscoveryAspect- , ItemFull(..), ItemDisco(..)- , itemNoDisco, itemToFull+ ( -- * The @Item@ type and operations+ ItemId, Item(..)+ , itemPrice, isMelee, goesIntoEqp, goesIntoInv, goesIntoSha+ -- * The @AspectRecord@ type and operations+ , AspectRecord(..), DiscoveryAspect+ , emptyAspectRecord, sumAspectRecord, aspectRecordToList, meanAspect+ -- * Item discovery types and operations+ , ItemKindIx, ItemSeed, ItemDisco(..), ItemFull(..)+ , KindMean(..), DiscoveryKind, ItemIxMap, Benefit(..), DiscoveryBenefit+ , itemNoDisco, itemToFull6, aspectsRandom, seedToAspect, aspectRecordFull -- * Inventory management types , ItemTimer, ItemQuant, ItemBag, ItemDict+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , castAspect, addMeanAspect, ceilingMeanDice+#endif ) where import Prelude ()@@ -22,70 +24,73 @@ import Game.LambdaHack.Common.Prelude import qualified Control.Monad.Trans.State.Strict as St-import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM-import Data.Hashable (Hashable)+import Data.Hashable (Hashable) import qualified Data.Ix as Ix-import GHC.Generics (Generic)-import System.Random (mkStdGen)+import GHC.Generics (Generic)+import System.Random (mkStdGen) import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Dice (intToDice)+import Game.LambdaHack.Common.Dice (intToDice) import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Flavour import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Time import qualified Game.LambdaHack.Content.ItemKind as IK -- | A unique identifier of an item in the dungeon. newtype ItemId = ItemId Int deriving (Show, Eq, Ord, Enum, Binary) --- | An index of the kind id of an item. Clients have partial knowledge--- how these idexes map to kind ids. They gain knowledge by identifying items.-newtype ItemKindIx = ItemKindIx Int- deriving (Show, Eq, Ord, Enum, Ix.Ix, Hashable, Binary)--data KindMean = KindMean- { kmKind :: Kind.Id IK.ItemKind- , kmMean :: AspectRecord+-- | Game items in actor possesion or strewn around the dungeon.+-- The fields @jsymbol@, @jname@ and @jflavour@ make it possible to refer to+-- and draw an unidentified item. Full information about item is available+-- through the @jkindIx@ index as soon as the item is identified.+data Item = Item+ { jkindIx :: ItemKindIx -- ^ index pointing to the kind of the item+ , jlid :: LevelId -- ^ lowest level the item was created at+ , jfid :: Maybe FactionId+ -- ^ the faction that created the item, if any+ , jsymbol :: Char -- ^ map symbol+ , jname :: Text -- ^ generic name+ , jflavour :: Flavour -- ^ flavour+ , jfeature :: [IK.Feature] -- ^ public properties+ , jweight :: Int -- ^ weight in grams, obvious enough+ , jdamage :: Dice.Dice -- ^ impact damage of this particular weapon } deriving (Show, Eq, Generic) -instance Binary KindMean+instance Hashable Item --- | The map of item kind indexes to item kind ids.--- The full map, as known by the server, is 1-1.-type DiscoveryKind = EM.EnumMap ItemKindIx KindMean+instance Binary Item --- | Fields are intentionally kept non-strict, because they are recomputed--- often, but not used every time. The fields are, in order:--- 1. whether the item should be kept in equipment (not in pack nor stash)--- 2. the total benefit from picking the item up (to use or to put in equipment)--- 3. the benefit of applying the item to self--- 4. the (usually negative) benefit of hitting a foe in meleeing with the item--- 5. the (usually negative) benefit of flinging an item at an opponent-data Benefit = Benefit- { benInEqp :: ~Bool- , benPickup :: ~Int- , benApply :: ~Int- , benMelee :: ~Int- , benFling :: ~Int- }- deriving (Show, Generic)+-- | Price an item, taking count into consideration.+itemPrice :: (Item, Int) -> Int+itemPrice (item, jcount) =+ case jsymbol item of+ '$' -> jcount+ '*' -> jcount * 100+ _ -> 0 -instance Binary Benefit+isMelee :: Item -> Bool+isMelee item = IK.Meleeable `elem` jfeature item -type DiscoveryBenefit = EM.EnumMap ItemId Benefit+goesIntoEqp :: Item -> Bool+goesIntoEqp item = IK.Equipable `elem` jfeature item+ || IK.Meleeable `elem` jfeature item --- | A seed for rolling aspects of an item--- Clients have partial knowledge of how item ids map to the seeds.--- They gain knowledge by identifying items.-newtype ItemSeed = ItemSeed Int- deriving (Show, Eq, Ord, Enum, Hashable, Binary)+goesIntoInv :: Item -> Bool+goesIntoInv item = IK.Precious `notElem` jfeature item+ && not (goesIntoEqp item) +goesIntoSha :: Item -> Bool+goesIntoSha item = IK.Precious `elem` jfeature item+ && not (goesIntoEqp item)++-- | Record of sums of aspect values of an item, container, actor, etc. data AspectRecord = AspectRecord { aTimeout :: Int , aHurtMelee :: Int@@ -107,6 +112,10 @@ instance Hashable AspectRecord +-- | The map of item ids to item aspects.+-- The full map is known by the server.+type DiscoveryAspect = EM.EnumMap ItemId AspectRecord+ emptyAspectRecord :: AspectRecord emptyAspectRecord = AspectRecord { aTimeout = 0@@ -144,21 +153,100 @@ mapScale f = map (\(ar, k) -> f ar * k) mapScaleAbility = map (\(ar, k) -> Ability.scaleSkills k $ aSkills ar) --- | The map of item ids to item aspects.--- The full map is known by the server.-type DiscoveryAspect = EM.EnumMap ItemId AspectRecord+aspectRecordToList :: AspectRecord -> [IK.Aspect]+aspectRecordToList AspectRecord{..} =+ [IK.Timeout $ intToDice aTimeout | aTimeout /= 0]+ ++ [IK.AddHurtMelee $ intToDice aHurtMelee | aHurtMelee /= 0]+ ++ [IK.AddArmorMelee $ intToDice aArmorMelee | aArmorMelee /= 0]+ ++ [IK.AddArmorRanged $ intToDice aArmorRanged | aArmorRanged /= 0]+ ++ [IK.AddMaxHP $ intToDice aMaxHP | aMaxHP /= 0]+ ++ [IK.AddMaxCalm $ intToDice aMaxCalm | aMaxCalm /= 0]+ ++ [IK.AddSpeed $ intToDice aSpeed | aSpeed /= 0]+ ++ [IK.AddSight $ intToDice aSight | aSight /= 0]+ ++ [IK.AddSmell $ intToDice aSmell | aSmell /= 0]+ ++ [IK.AddShine $ intToDice aShine | aShine /= 0]+ ++ [IK.AddNocto $ intToDice aNocto | aNocto /= 0]+ ++ [IK.AddAggression $ intToDice aAggression | aAggression /= 0]+ ++ [IK.AddAbility ab $ intToDice n | (ab, n) <- EM.assocs aSkills, n /= 0] +meanAspect :: IK.ItemKind -> AspectRecord+meanAspect kind = foldl' addMeanAspect emptyAspectRecord (IK.iaspects kind)++addMeanAspect :: AspectRecord -> IK.Aspect -> AspectRecord+addMeanAspect !ar !asp =+ case asp of+ IK.Timeout d ->+ let n = ceilingMeanDice d+ in assert (aTimeout ar == 0) $ ar {aTimeout = n}+ IK.AddHurtMelee d ->+ let n = ceilingMeanDice d+ in ar {aHurtMelee = n + aHurtMelee ar}+ IK.AddArmorMelee d ->+ let n = ceilingMeanDice d+ in ar {aArmorMelee = n + aArmorMelee ar}+ IK.AddArmorRanged d ->+ let n = ceilingMeanDice d+ in ar {aArmorRanged = n + aArmorRanged ar}+ IK.AddMaxHP d ->+ let n = ceilingMeanDice d+ in ar {aMaxHP = n + aMaxHP ar}+ IK.AddMaxCalm d ->+ let n = ceilingMeanDice d+ in ar {aMaxCalm = n + aMaxCalm ar}+ IK.AddSpeed d ->+ let n = ceilingMeanDice d+ in ar {aSpeed = n + aSpeed ar}+ IK.AddSight d ->+ let n = ceilingMeanDice d+ in ar {aSight = n + aSight ar}+ IK.AddSmell d ->+ let n = ceilingMeanDice d+ in ar {aSmell = n + aSmell ar}+ IK.AddShine d ->+ let n = ceilingMeanDice d+ in ar {aShine = n + aShine ar}+ IK.AddNocto d ->+ let n = ceilingMeanDice d+ in ar {aNocto = n + aNocto ar}+ IK.AddAggression d ->+ let n = ceilingMeanDice d+ in ar {aAggression = n + aAggression ar}+ IK.AddAbility ab d ->+ let n = ceilingMeanDice d+ in ar {aSkills = Ability.addSkills (EM.singleton ab n)+ (aSkills ar)}++ceilingMeanDice :: Dice.Dice -> Int+ceilingMeanDice d = ceiling $ Dice.meanDice d++-- | An index of the kind identifier of an item. Clients have partial knowledge+-- how these idexes map to kind ids. They gain knowledge by identifying items.+-- The indexes and kind identifiers are 1-1.+newtype ItemKindIx = ItemKindIx Int+ deriving (Show, Eq, Ord, Enum, Ix.Ix, Hashable, Binary)++-- | A seed for rolling aspects of an item+-- Clients have partial knowledge of how item ids map to the seeds.+-- They gain knowledge by identifying items.+newtype ItemSeed = ItemSeed Int+ deriving (Show, Eq, Ord, Enum, Hashable, Binary)+ -- Tiny speedup from making fields non-strict (1%, a bit more GC, less alloc). -- The fields of @KindMean@ also need to be non-strict then, otherwise slowdown.+-- | The secret part of the information about an item. If a faction+-- knows the aspects of the item (the 'itemAspect' field is not empty),+-- this is a complete secret information/. data ItemDisco = ItemDisco { itemKindId :: Kind.Id IK.ItemKind , itemKind :: IK.ItemKind , itemAspectMean :: AspectRecord+ , itemConst :: Bool , itemAspect :: Maybe AspectRecord } deriving Show -- No speedup from making fields non-strict.+-- | Full information about an item. data ItemFull = ItemFull { itemBase :: Item , itemK :: Int@@ -167,92 +255,82 @@ } deriving Show -itemNoDisco :: (Item, Int) -> ItemFull-itemNoDisco (itemBase, itemK) =- ItemFull {itemBase, itemK, itemTimer = [], itemDisco=Nothing}--itemToFull :: Kind.COps -> DiscoveryKind -> DiscoveryAspect -> ItemId -> Item- -> ItemQuant- -> ItemFull-itemToFull Kind.COps{coitem=Kind.Ops{okind}}- disco discoAspect iid itemBase (itemK, itemTimer) =- let itemDisco = case EM.lookup (jkindIx itemBase) disco of- Nothing -> Nothing- Just KindMean{..} -> Just ItemDisco{ itemKindId = kmKind- , itemKind = okind kmKind- , itemAspectMean = kmMean- , itemAspect = EM.lookup iid discoAspect }- in ItemFull {..}---- | Game items in actor possesion or strewn around the dungeon.--- The fields @jsymbol@, @jname@ and @jflavour@ make it possible to refer to--- and draw an unidentified item. Full information about item is available--- through the @jkindIx@ index as soon as the item is identified.-data Item = Item- { jkindIx :: ItemKindIx -- ^ index pointing to the kind of the item- , jlid :: LevelId -- ^ lowest level the item was created at- , jfid :: Maybe FactionId- -- ^ the faction that created the item, if any- , jsymbol :: Char -- ^ map symbol- , jname :: Text -- ^ generic name- , jflavour :: Flavour -- ^ flavour- , jfeature :: [IK.Feature] -- ^ public properties- , jweight :: Int -- ^ weight in grams, obvious enough- , jdamage :: Dice.Dice -- ^ impact damage of this particular weapon+-- | Partial information about item kinds. These are assigned to each+-- 'ItemKindIx'. There is an item kinda, mean aspect value computed+-- (and here cached) for items of that kind and a flag saying whether+-- the item's aspects are constant rather than random or dependent+-- on dungeon level where the item is created.+data KindMean = KindMean+ { kmKind :: Kind.Id IK.ItemKind+ , kmMean :: AspectRecord+ , kmConst :: Bool } deriving (Show, Eq, Generic) -instance Hashable Item--instance Binary Item+instance Binary KindMean -data ItemSource =- ItemSourceLevel LevelId- | ItemSourceFaction FactionId- deriving (Show, Eq, Generic)+-- | The map of item kind indexes to item kind ids.+-- The full map, as known by the server, is 1-1.+type DiscoveryKind = EM.EnumMap ItemKindIx KindMean -instance Hashable ItemSource+-- | The map of item kind indexes to identifiers of items that have that kind.+type ItemIxMap = EM.EnumMap ItemKindIx [ItemId] -instance Binary ItemSource+-- | Fields are intentionally kept non-strict, because they are recomputed+-- often, but not used every time. The fields are, in order:+-- 1. whether the item should be kept in equipment (not in pack nor stash)+-- 2. the total benefit from picking the item up (to use or to put in equipment)+-- 3. the benefit of applying the item to self+-- 4. the (usually negative) benefit of hitting a foe in meleeing with the item+-- 5. the (usually negative) benefit of flinging an item at an opponent+data Benefit = Benefit+ { benInEqp :: ~Bool+ , benPickup :: ~Double+ , benApply :: ~Double+ , benMelee :: ~Double+ , benFling :: ~Double+ }+ deriving (Show, Generic) --- | Price an item, taking count into consideration.-itemPrice :: (Item, Int) -> Int-itemPrice (item, jcount) =- case jsymbol item of- '$' -> jcount- '*' -> jcount * 100- _ -> 0+instance Binary Benefit -goesIntoEqp :: Item -> Bool-goesIntoEqp item = IK.Equipable `elem` jfeature item- || IK.Meleeable `elem` jfeature item+type DiscoveryBenefit = EM.EnumMap ItemId Benefit -isMelee :: Item -> Bool-isMelee item = IK.Meleeable `elem` jfeature item+itemNoDisco :: (Item, Int) -> ItemFull+itemNoDisco (itemBase, itemK) =+ ItemFull {itemBase, itemK, itemTimer = [], itemDisco=Nothing} -goesIntoInv :: Item -> Bool-goesIntoInv item = IK.Precious `notElem` jfeature item- && not (goesIntoEqp item)+itemToFull6 :: Kind.COps -> DiscoveryKind -> DiscoveryAspect -> ItemId -> Item+ -> ItemQuant+ -> ItemFull+itemToFull6 Kind.COps{coitem=Kind.Ops{okind}}+ discoKind discoAspect iid itemBase (itemK, itemTimer) =+ let itemDisco = case EM.lookup (jkindIx itemBase) discoKind of+ Nothing -> Nothing+ Just KindMean{..} ->+ Just ItemDisco{ itemKindId = kmKind+ , itemKind = okind kmKind+ , itemAspectMean = kmMean+ , itemConst = kmConst+ , itemAspect = EM.lookup iid discoAspect }+ in ItemFull {..} -goesIntoSha :: Item -> Bool-goesIntoSha item = IK.Precious `elem` jfeature item- && not (goesIntoEqp item)+-- If @False@, aspects of this kind are most probably fixed, not random+-- nor dependent on dungeon level where the item is created.+aspectsRandom :: IK.ItemKind -> Bool+aspectsRandom kind =+ let rollM depth = foldlM' (castAspect (AbsDepth depth) (AbsDepth 10))+ emptyAspectRecord (IK.iaspects kind)+ gen = mkStdGen 0+ (ar0, gen0) = St.runState (rollM 0) gen+ (ar1, gen1) = St.runState (rollM 10) gen0+ in show gen /= show gen0 || show gen /= show gen1 || ar0 /= ar1 -aspectRecordToList :: AspectRecord -> [IK.Aspect]-aspectRecordToList AspectRecord{..} =- [IK.Timeout $ intToDice aTimeout | aTimeout /= 0]- ++ [IK.AddHurtMelee $ intToDice aHurtMelee | aHurtMelee /= 0]- ++ [IK.AddArmorMelee $ intToDice aArmorMelee | aArmorMelee /= 0]- ++ [IK.AddArmorRanged $ intToDice aArmorRanged | aArmorRanged /= 0]- ++ [IK.AddMaxHP $ intToDice aMaxHP | aMaxHP /= 0]- ++ [IK.AddMaxCalm $ intToDice aMaxCalm | aMaxCalm /= 0]- ++ [IK.AddSpeed $ intToDice aSpeed | aSpeed /= 0]- ++ [IK.AddSight $ intToDice aSight | aSight /= 0]- ++ [IK.AddSmell $ intToDice aSmell | aSmell /= 0]- ++ [IK.AddShine $ intToDice aShine | aShine /= 0]- ++ [IK.AddNocto $ intToDice aNocto | aNocto /= 0]- ++ [IK.AddAggression $ intToDice aAggression | aAggression /= 0]- ++ [IK.AddAbility ab $ intToDice n | (ab, n) <- EM.assocs aSkills, n /= 0]+seedToAspect :: ItemSeed -> IK.ItemKind -> AbsDepth -> AbsDepth -> AspectRecord+seedToAspect (ItemSeed itemSeed) kind ldepth totalDepth =+ let rollM = foldlM' (castAspect ldepth totalDepth) emptyAspectRecord+ (IK.iaspects kind)+ in St.evalState rollM (mkStdGen itemSeed) castAspect :: AbsDepth -> AbsDepth -> AspectRecord -> IK.Aspect -> Rnd AspectRecord@@ -299,67 +377,6 @@ return $! ar {aSkills = Ability.addSkills (EM.singleton ab n) (aSkills ar)} -addMeanAspect :: AspectRecord -> IK.Aspect -> AspectRecord-addMeanAspect !ar !asp =- case asp of- IK.Timeout d ->- let n = Dice.meanDice d- in assert (aTimeout ar == 0) $ ar {aTimeout = n}- IK.AddHurtMelee d ->- let n = Dice.meanDice d- in ar {aHurtMelee = n + aHurtMelee ar}- IK.AddArmorMelee d ->- let n = Dice.meanDice d- in ar {aArmorMelee = n + aArmorMelee ar}- IK.AddArmorRanged d ->- let n = Dice.meanDice d- in ar {aArmorRanged = n + aArmorRanged ar}- IK.AddMaxHP d ->- let n = Dice.meanDice d- in ar {aMaxHP = n + aMaxHP ar}- IK.AddMaxCalm d ->- let n = Dice.meanDice d- in ar {aMaxCalm = n + aMaxCalm ar}- IK.AddSpeed d ->- let n = Dice.meanDice d- in ar {aSpeed = n + aSpeed ar}- IK.AddSight d ->- let n = Dice.meanDice d- in ar {aSight = n + aSight ar}- IK.AddSmell d ->- let n = Dice.meanDice d- in ar {aSmell = n + aSmell ar}- IK.AddShine d ->- let n = Dice.meanDice d- in ar {aShine = n + aShine ar}- IK.AddNocto d ->- let n = Dice.meanDice d- in ar {aNocto = n + aNocto ar}- IK.AddAggression d ->- let n = Dice.meanDice d- in ar {aAggression = n + aAggression ar}- IK.AddAbility ab d ->- let n = Dice.meanDice d- in ar {aSkills = Ability.addSkills (EM.singleton ab n)- (aSkills ar)}--seedToAspect :: ItemSeed -> IK.ItemKind -> AbsDepth -> AbsDepth -> AspectRecord-seedToAspect (ItemSeed itemSeed) kind ldepth totalDepth =- let rollM = foldlM' (castAspect ldepth totalDepth) emptyAspectRecord- (IK.iaspects kind)- in St.evalState rollM (mkStdGen itemSeed)---- If @False@, aspects of this kind are most probably fixed, not random.-aspectsRandom :: IK.ItemKind -> Bool-aspectsRandom kind =- let rollM = foldlM' (castAspect (AbsDepth 10) (AbsDepth 10))- emptyAspectRecord (IK.iaspects kind)- gen = mkStdGen 0- in show gen /= show (St.execState rollM gen)--meanAspect :: IK.ItemKind -> AspectRecord-meanAspect kind = foldl' addMeanAspect emptyAspectRecord (IK.iaspects kind)- aspectRecordFull :: ItemFull -> AspectRecord aspectRecordFull itemFull = case itemDisco itemFull of@@ -369,8 +386,13 @@ type ItemTimer = [Time] +-- | Number of items in a bag, together with recharging timer, in case of+-- items that need recharging, exists only temporarily or auto-activate+-- at regular intervals. type ItemQuant = (Int, ItemTimer) +-- | A bag of items, e.g., one of the stores of an actor or the items+-- on a particular floor position or embedded in a particular map tile. type ItemBag = EM.EnumMap ItemId ItemQuant -- | All items in the dungeon (including in actor inventories),
Game/LambdaHack/Common/ItemStrongest.hs view
@@ -1,13 +1,16 @@ -- | Determining the strongest item wrt some property.--- No operation in this module involves the state or any of our custom monads. module Game.LambdaHack.Common.ItemStrongest ( -- * Strongest items- strengthOnSmash, strengthCreateOrgan, strengthDropOrgan- , strengthEqpSlot, strengthToThrow, strengthEffect, strongestSlot+ strengthEffect, strengthOnSmash, strengthCreateOrgan, strengthDropOrgan+ , strengthEqpSlot, strengthToThrow, strongestSlot -- * Assorted- , totalRange, computeTrajectory, itemTrajectory- , unknownMelee, filterRecharging, stripRecharging, stripOnSmash+ , computeTrajectory, itemTrajectory, totalRange , hasCharge, damageUsefulness, strongestMelee, prEqpSlot+ , unknownMeleeBonus, filterRecharging, stripRecharging, stripOnSmash+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , unknownAspect+#endif ) where import Prelude ()@@ -19,12 +22,12 @@ import qualified Game.LambdaHack.Common.Ability as Ability import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ItemKind strengthEffect :: (Effect -> [b]) -> ItemFull -> [b] strengthEffect f itemFull =@@ -71,6 +74,27 @@ [x] -> x xs -> error $ "" `showFailure` (xs, item) +-- This ignores items that don't go into equipment, as determined in @inEqp@.+-- They are removed from equipment elsewhere via @harmful@.+strongestSlot :: DiscoveryBenefit -> EqpSlot -> [(ItemId, ItemFull)]+ -> [(Int, (ItemId, ItemFull))]+strongestSlot discoBenefit eqpSlot is =+ let f (iid, itemFull) =+ let rawDmg = damageUsefulness $ itemBase itemFull+ (bInEqp, bPickup) = case EM.lookup iid discoBenefit of+ Just Benefit{benInEqp, benPickup} -> (benInEqp, benPickup)+ Nothing -> (goesIntoEqp $ itemBase itemFull, rawDmg)+ in if not bInEqp+ then Nothing+ else Just $+ let ben = if eqpSlot == EqpSlotWeapon+ -- For equipping/unequipping a weapon we take into+ -- account not only melee power, but also aspects, etc.+ then ceiling bPickup+ else prEqpSlot eqpSlot $ aspectRecordFull itemFull+ in (ben, (iid, itemFull))+ in sortBy (flip $ Ord.comparing fst) $ mapMaybe f is+ computeTrajectory :: Int -> Int -> Int -> [Point] -> ([Vector], (Speed, Int)) computeTrajectory weight throwVelocity throwLinger path = let speed = speedFromWeight weight throwVelocity@@ -86,37 +110,6 @@ totalRange :: Item -> Int totalRange item = snd $ snd $ itemTrajectory item [] -prEqpSlot :: EqpSlot -> AspectRecord -> Int-prEqpSlot eqpSlot ar@AspectRecord{..} =- case eqpSlot of- EqpSlotMiscBonus ->- aTimeout -- usually better items have longer timeout- + aMaxCalm + aSmell- + aNocto -- powerful, but hard to boost over aSight- EqpSlotAddHurtMelee -> aHurtMelee- EqpSlotAddArmorMelee -> aArmorMelee- EqpSlotAddArmorRanged -> aArmorRanged- EqpSlotAddMaxHP -> aMaxHP- EqpSlotAddSpeed -> aSpeed- EqpSlotAddSight -> aSight- EqpSlotLightSource -> aShine- EqpSlotWeapon -> error $ "" `showFailure` ar- EqpSlotMiscAbility ->- EM.findWithDefault 0 Ability.AbWait aSkills- + EM.findWithDefault 0 Ability.AbMoveItem aSkills- EqpSlotAbMove -> EM.findWithDefault 0 Ability.AbMove aSkills- EqpSlotAbMelee -> EM.findWithDefault 0 Ability.AbMelee aSkills- EqpSlotAbDisplace -> EM.findWithDefault 0 Ability.AbDisplace aSkills- EqpSlotAbAlter -> EM.findWithDefault 0 Ability.AbAlter aSkills- EqpSlotAbProject -> EM.findWithDefault 0 Ability.AbProject aSkills- EqpSlotAbApply -> EM.findWithDefault 0 Ability.AbApply aSkills- EqpSlotAddMaxCalm -> aMaxCalm- EqpSlotAddSmell -> aSmell- EqpSlotAddNocto -> aNocto- EqpSlotAddAggression -> aAggression- EqpSlotAbWait -> EM.findWithDefault 0 Ability.AbWait aSkills- EqpSlotAbMoveItem -> EM.findWithDefault 0 Ability.AbMoveItem aSkills- hasCharge :: Time -> ItemFull -> Bool hasCharge localTime itemFull@ItemFull{..} = let timeout = aTimeout $ aspectRecordFull itemFull@@ -125,12 +118,12 @@ it1 = filter charging itemTimer in length it1 < itemK -damageUsefulness :: Item -> Int+damageUsefulness :: Item -> Double damageUsefulness item = let v = min 1000 (10 * Dice.meanDice (jdamage item)) in assert (v >= 0) v strongestMelee :: Maybe DiscoveryBenefit -> Time -> [(ItemId, ItemFull)]- -> [(Int, (ItemId, ItemFull))]+ -> [(Double, (ItemId, ItemFull))] strongestMelee _ _ [] = [] strongestMelee mdiscoBenefit localTime is = -- For simplicity we assume, if weapon not recharged, all important effects,@@ -150,37 +143,48 @@ -- e.g., geysers, bees. This is intended and fun. in sortBy (flip $ Ord.comparing fst) $ map f is --- This ignores items that don't go into equipment, as determined in @inEqp@.--- They are removed from equipment elsewhere via @harmful@.-strongestSlot :: DiscoveryBenefit -> EqpSlot -> [(ItemId, ItemFull)]- -> [(Int, (ItemId, ItemFull))]-strongestSlot discoBenefit eqpSlot is =- let f (iid, itemFull) =- let rawDmg = damageUsefulness $ itemBase itemFull- (bInEqp, bPickup) = case EM.lookup iid discoBenefit of- Just Benefit{benInEqp, benPickup} -> (benInEqp, benPickup)- Nothing -> (goesIntoEqp $ itemBase itemFull, rawDmg)- in if not bInEqp- then Nothing- else Just $- let ben = if eqpSlot == EqpSlotWeapon- -- For equipping/unequipping a weapon we take into- -- account not only melee power, but also aspects, etc.- then bPickup- else prEqpSlot eqpSlot $ aspectRecordFull itemFull- in (ben, (iid, itemFull))- in sortBy (flip $ Ord.comparing fst) $ mapMaybe f is+prEqpSlot :: EqpSlot -> AspectRecord -> Int+prEqpSlot eqpSlot ar@AspectRecord{..} =+ case eqpSlot of+ EqpSlotMiscBonus ->+ aTimeout -- usually better items have longer timeout+ + aMaxCalm + aSmell+ + aNocto -- powerful, but hard to boost over aSight+ EqpSlotAddHurtMelee -> aHurtMelee+ EqpSlotAddArmorMelee -> aArmorMelee+ EqpSlotAddArmorRanged -> aArmorRanged+ EqpSlotAddMaxHP -> aMaxHP+ EqpSlotAddSpeed -> aSpeed+ EqpSlotAddSight -> aSight+ EqpSlotLightSource -> aShine+ EqpSlotWeapon -> error $ "" `showFailure` ar+ EqpSlotMiscAbility ->+ EM.findWithDefault 0 Ability.AbWait aSkills+ + EM.findWithDefault 0 Ability.AbMoveItem aSkills+ EqpSlotAbMove -> EM.findWithDefault 0 Ability.AbMove aSkills+ EqpSlotAbMelee -> EM.findWithDefault 0 Ability.AbMelee aSkills+ EqpSlotAbDisplace -> EM.findWithDefault 0 Ability.AbDisplace aSkills+ EqpSlotAbAlter -> EM.findWithDefault 0 Ability.AbAlter aSkills+ EqpSlotAbProject -> EM.findWithDefault 0 Ability.AbProject aSkills+ EqpSlotAbApply -> EM.findWithDefault 0 Ability.AbApply aSkills+ EqpSlotAddMaxCalm -> aMaxCalm+ EqpSlotAddSmell -> aSmell+ EqpSlotAddNocto -> aNocto+ EqpSlotAddAggression -> aAggression+ EqpSlotAbWait -> EM.findWithDefault 0 Ability.AbWait aSkills+ EqpSlotAbMoveItem -> EM.findWithDefault 0 Ability.AbMoveItem aSkills unknownAspect :: (Aspect -> [Dice.Dice]) -> ItemFull -> Bool unknownAspect f itemFull = case itemDisco itemFull of+ Nothing -> True -- not even kind is known, so aspect unknown Just ItemDisco{itemAspect=Nothing, itemKind=ItemKind{iaspects}} -> let unknown x = Dice.minDice x /= Dice.maxDice x in or $ concatMap (map unknown . f) iaspects- _ -> False -- we don't know if it affect the aspect, so we assume 0+ Just{} -> False -- all known -unknownMelee :: [ItemFull] -> Bool-unknownMelee =+unknownMeleeBonus :: [ItemFull] -> Bool+unknownMeleeBonus = let p (AddHurtMelee k) = [k] p _ = [] f itemFull b = b || unknownAspect p itemFull
Game/LambdaHack/Common/JSFile.hs view
@@ -1,4 +1,4 @@--- | Saving/loading with serialization and compression.+-- | Saving/loading to JS storeage, mimicking operations on files. module Game.LambdaHack.Common.JSFile ( encodeEOF, strictDecodeEOF , tryCreateDir, doesFileExist, tryWriteFile, readFile, renameFile@@ -8,14 +8,14 @@ import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.ByteString.Lazy.Char8 as LBS import qualified Data.Text as T-import Data.Text.Encoding (decodeLatin1)-import GHCJS.DOM (currentWindow)-import GHCJS.DOM.Storage (getItem, removeItem, setItem)-import GHCJS.DOM.Types (runDOM)-import GHCJS.DOM.Window (getLocalStorage)+import Data.Text.Encoding (decodeLatin1)+import GHCJS.DOM (currentWindow)+import GHCJS.DOM.Storage (getItem, removeItem, setItem)+import GHCJS.DOM.Types (runDOM)+import GHCJS.DOM.Window (getLocalStorage) -- | Serialize and save data with an EOF marker. In JS, compression -- is probably performed by the browser and we don't have access@@ -54,7 +54,6 @@ let fileExists = isJust (mitem :: Maybe String) return $! fileExists --- | Try to write a file, given content, if the file not already there. tryWriteFile :: FilePath -> String -> IO () tryWriteFile path content = flip runDOM undefined $ do Just win <- currentWindow
Game/LambdaHack/Common/Kind.hs view
@@ -1,6 +1,7 @@+{-# OPTIONS_GHC -fno-expose-all-unfoldings #-} -- | General content types and operations. module Game.LambdaHack.Common.Kind- ( Id, Ops(..), COps(..), createOps, stdRuleset+ ( Id, Ops(..), COps(..), stdRuleset, createOps ) where import Prelude ()@@ -23,10 +24,34 @@ import Game.LambdaHack.Content.RuleKind import Game.LambdaHack.Content.TileKind --- Not specialized, because no speedup, but huge JS code bloat.+-- | Operations for all content types, gathered together.+data COps = COps+ { cocave :: Ops CaveKind -- server only+ , coitem :: Ops ItemKind+ , comode :: Ops ModeKind -- server only+ , coplace :: Ops PlaceKind -- server only, so far+ , corule :: Ops RuleKind+ , cotile :: Ops TileKind+ , coTileSpeedup :: TileSpeedup+ }++instance Show COps where+ show _ = "game content"++instance Eq COps where+ (==) _ _ = True++-- | The standard ruleset used for level operations.+stdRuleset :: Ops RuleKind -> RuleKind+stdRuleset Ops{ouniqGroup, okind} = okind $ ouniqGroup "standard"++-- Not specialized, because no speedup, but big JS code bloat+-- (-fno-expose-all-unfoldings and NOINLINE used to ensure that,+-- in the absence of NOSPECIALIZABLE pragma). -- | Create content operations for type @a@ from definition of content -- of type @a@. createOps :: forall a. Show a => ContentDef a -> Ops a+{-# NOINLINE createOps #-} createOps ContentDef{getName, getFreq, content, validateSingle, validateAll} = assert (V.length content <= fromEnum (maxBound :: Id a)) $ let kindFreq :: M.Map (GroupName a) [(Int, (Id a, a))]@@ -87,24 +112,3 @@ `showFailure` () , olength = V.length content }---- | Operations for all content types, gathered together.-data COps = COps- { cocave :: Ops CaveKind -- server only- , coitem :: Ops ItemKind- , comode :: Ops ModeKind -- server only- , coplace :: Ops PlaceKind -- server only, so far- , corule :: Ops RuleKind- , cotile :: Ops TileKind- , coTileSpeedup :: TileSpeedup- }--instance Show COps where- show _ = "game content"--instance Eq COps where- (==) _ _ = True---- | The standard ruleset used for level operations.-stdRuleset :: Ops RuleKind -> RuleKind-stdRuleset Ops{ouniqGroup, okind} = okind $ ouniqGroup "standard"
Game/LambdaHack/Common/Level.hs view
@@ -5,34 +5,40 @@ LevelId, AbsDepth, Dungeon , ascendInBranch, whereTo -- * The @Level@ type and its components- , Level(..), ItemFloor, ActorMap, TileMap, SmellMap+ , ItemFloor, ActorMap, TileMap, SmellMap, Level(..)+ -- * Component updates+ , updateFloor, updateEmbed, updateActorMap, updateTile, updateSmell -- * Level query , at, findPoint, findPos, findPosTry, findPosTry2+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , assertSparseItems, assertSparseActors+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM -import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind import qualified Game.LambdaHack.Common.KindOps as KindOps-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind (ItemKind)-import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.TileKind (TileKind) --- | The complete dungeon is a map from level names to levels.+-- | The complete dungeon is a map from level identifiers to levels. type Dungeon = EM.EnumMap LevelId Level --- | Levels in the current branch, @k@ levels shallower than the current.+-- | Levels in the current branch, one level up (or down) from the current. ascendInBranch :: Dungeon -> Bool -> LevelId -> [LevelId] ascendInBranch dungeon up lid = -- Currently there is just one branch, so the computation is simple.@@ -92,13 +98,12 @@ data Level = Level { ldepth :: AbsDepth -- ^ absolute depth of the level , lfloor :: ItemFloor -- ^ remembered items lying on the floor- , lembed :: ItemFloor -- ^ items embedded in the tile+ , lembed :: ItemFloor -- ^ remembered items embedded in the tile , lactor :: ActorMap -- ^ seen actors at positions on the level , ltile :: TileMap -- ^ remembered level map , lxsize :: X -- ^ width of the level , lysize :: Y -- ^ height of the level , lsmell :: SmellMap -- ^ remembered smells on the level- , ldesc :: Text -- ^ level description , lstair :: ([Point], [Point]) -- ^ positions of (up, down) stairs , lseen :: Int -- ^ currently remembered clear tiles@@ -111,7 +116,9 @@ , litemFreq :: Freqs ItemKind -- ^ frequency of initial items; [] for clients , lescape :: [Point] -- ^ positions of IK.Escape tiles- , lnight :: Bool+ , lnight :: Bool -- ^ whether the level is covered in darkness+ , lname :: Text -- ^ level name+ , ldesc :: Text -- ^ level description } deriving (Show, Eq) @@ -125,6 +132,21 @@ assert (EM.null (EM.filter null m) `blame` "null actor lists found" `swith` m) m +updateFloor :: (ItemFloor -> ItemFloor) -> Level -> Level+updateFloor f lvl = lvl {lfloor = f (lfloor lvl)}++updateEmbed :: (ItemFloor -> ItemFloor) -> Level -> Level+updateEmbed f lvl = lvl {lembed = f (lembed lvl)}++updateActorMap :: (ActorMap -> ActorMap) -> Level -> Level+updateActorMap f lvl = lvl {lactor = f (lactor lvl)}++updateTile :: (TileMap -> TileMap) -> Level -> Level+updateTile f lvl = lvl {ltile = f (ltile lvl)}++updateSmell :: (SmellMap -> SmellMap) -> Level -> Level+updateSmell f lvl = lvl {lsmell = f (lsmell lvl)}+ -- | Query for tile kinds on the map. at :: Level -> Point -> Kind.Id TileKind {-# INLINE at #-}@@ -203,7 +225,6 @@ put lxsize put lysize put lsmell- put ldesc put lstair put lseen put lexplorable@@ -214,6 +235,8 @@ put litemFreq put lescape put lnight+ put lname+ put ldesc get = do ldepth <- get lfloor <- get@@ -223,7 +246,6 @@ lxsize <- get lysize <- get lsmell <- get- ldesc <- get lstair <- get lseen <- get lexplorable <- get@@ -234,4 +256,6 @@ litemFreq <- get lescape <- get lnight <- get+ lname <- get+ ldesc <- get return $! Level{..}
Game/LambdaHack/Common/Misc.hs view
@@ -3,95 +3,53 @@ -- | Hacks that haven't found their home yet. module Game.LambdaHack.Common.Misc ( -- * Game object identifiers- FactionId, LevelId, AbsDepth(..), ActorId+ FactionId, LevelId, ActorId -- * Item containers , Container(..), CStore(..), ItemDialogMode(..) -- * Assorted- , makePhrase, makeSentence- , normalLevelBound, GroupName, toGroupName, Freqs, breturn- , Rarity, validateRarity- , Tactic(..), describeTactic, appDataDir- , xM, minusM, minusM1, oneM+ , GroupName, Freqs, Rarity, AbsDepth(..), Tactic(..)+ , toGroupName, validateRarity, describeTactic+ , makePhrase, makeSentence, normalLevelBound, breturn+ , appDataDir, xM, xD, minusM, minusM1, oneM ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.DeepSeq-import Data.Binary+import Control.DeepSeq+import Data.Binary import qualified Data.Char as Char import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.Fixed as Fixed-import Data.Function-import Data.Hashable+import Data.Function+import Data.Hashable import qualified Data.HashMap.Strict as HM-import Data.Int (Int64)-import Data.Key-import Data.Ord-import Data.String (IsString (..))+import Data.Int (Int64)+import Data.Key+import Data.Ord+import Data.String (IsString (..)) import qualified Data.Text as T import qualified Data.Time as Time-import GHC.Generics (Generic)+import GHC.Generics (Generic) import qualified NLP.Miniutter.English as MU-import System.Directory (getAppUserDataDirectory)-import System.Environment (getProgName)+import System.Directory (getAppUserDataDirectory)+import System.Environment (getProgName) import Game.LambdaHack.Common.Point --- | Re-exported English phrase creation functions, applied to default--- irregular word sets.-makePhrase, makeSentence :: [MU.Part] -> Text-makePhrase = MU.makePhrase MU.defIrregular-makeSentence = MU.makeSentence MU.defIrregular---- | Level bounds.-normalLevelBound :: (Int, Int)-normalLevelBound = (79, 20)---- If ever needed, we can use a symbol table here, since content--- is never serialized. But we'd need to cover the few cases--- (e.g., @litemFreq@) where @GroupName@ goes into savegame.-newtype GroupName a = GroupName Text- deriving (Read, Eq, Ord, Hashable, Binary, Generic)--instance IsString (GroupName a) where- fromString = GroupName . T.pack--instance Show (GroupName a) where- show (GroupName gn) = T.unpack gn--instance NFData (GroupName a)--toGroupName :: Text -> GroupName a-{-# INLINE toGroupName #-}-toGroupName = GroupName---- | For each group that the kind belongs to, denoted by a @GroupName@--- in the first component of a pair, the second component of a pair shows--- how common the kind is within the group.-type Freqs a = [(GroupName a, Int)]---- | Rarity on given depths.-type Rarity = [(Double, Int)]+-- | A unique identifier of a faction in a game.+newtype FactionId = FactionId Int+ deriving (Show, Eq, Ord, Enum, Hashable, Binary) -validateRarity :: Rarity -> [Text]-validateRarity rarity =- let sortedRarity = sortBy (comparing fst) rarity- in [ "rarity not sorted" | sortedRarity /= rarity ]- ++ [ "rarity depth thresholds not unique"- | nubBy ((==) `on` fst) sortedRarity /= sortedRarity ]- ++ [ "rarity depth not between 0 and 10"- | case (sortedRarity, reverse sortedRarity) of- ((lowest, _) : _, (highest, _) : _) ->- lowest <= 0 || highest > 10- _ -> False ]+-- | Abstract level identifiers.+newtype LevelId = LevelId Int+ deriving (Show, Eq, Ord, Enum, Hashable, Binary) --- | @breturn b a = [a | b]@-breturn :: MonadPlus m => Bool -> a -> m a-breturn True a = return a-breturn False _ = mzero+-- | A unique identifier of an actor in the dungeon.+newtype ActorId = ActorId Int+ deriving (Show, Eq, Ord, Enum, Binary) -- | Item container type. data Container =@@ -103,6 +61,7 @@ instance Binary Container +-- | Actor's item stores. data CStore = CGround | COrgan@@ -124,14 +83,28 @@ instance Binary ItemDialogMode --- | A unique identifier of a faction in a game.-newtype FactionId = FactionId Int- deriving (Show, Eq, Ord, Enum, Hashable, Binary)+-- If ever needed, we can use a symbol table here, since content+-- is never serialized. But we'd need to cover the few cases+-- (e.g., @litemFreq@) where @GroupName@ goes into savegame.+newtype GroupName a = GroupName Text+ deriving (Read, Eq, Ord, Hashable, Binary, Generic) --- | Abstract level identifiers.-newtype LevelId = LevelId Int- deriving (Show, Eq, Ord, Enum, Hashable, Binary)+instance IsString (GroupName a) where+ fromString = GroupName . T.pack +instance Show (GroupName a) where+ show (GroupName gn) = T.unpack gn++instance NFData (GroupName a)++-- | For each group that the kind belongs to, denoted by a @GroupName@+-- in the first component of a pair, the second component of a pair shows+-- how common the kind is within the group.+type Freqs a = [(GroupName a, Int)]++-- | Rarity on given depths.+type Rarity = [(Double, Int)]+ -- | Absolute depth in the dungeon. When used for the maximum depth -- of the whole dungeon, this can be different than dungeon size, -- e.g., when the dungeon is branched, and it can even be different@@ -139,10 +112,6 @@ newtype AbsDepth = AbsDepth Int deriving (Show, Eq, Ord, Hashable, Binary) --- | A unique identifier of an actor in the dungeon.-newtype ActorId = ActorId Int- deriving (Show, Eq, Ord, Enum, Binary)- -- | Tactic of non-leader actors. Apart of determining AI operation, -- each tactic implies a skill modifier, that is added to the non-leader skills -- defined in 'fskillsOther' field of 'Player'.@@ -170,6 +139,26 @@ show TRoam = "roam freely" show TPatrol = "patrol area" +instance Binary Tactic++instance Hashable Tactic++toGroupName :: Text -> GroupName a+{-# INLINE toGroupName #-}+toGroupName = GroupName++validateRarity :: Rarity -> [Text]+validateRarity rarity =+ let sortedRarity = sortBy (comparing fst) rarity+ in [ "rarity not sorted" | sortedRarity /= rarity ]+ ++ [ "rarity depth thresholds not unique"+ | nubBy ((==) `on` fst) sortedRarity /= sortedRarity ]+ ++ [ "rarity depth not between 0 and 10"+ | case (sortedRarity, reverse sortedRarity) of+ ((lowest, _) : _, (highest, _) : _) ->+ lowest <= 0 || highest > 10+ _ -> False ]+ describeTactic :: Tactic -> Text describeTactic TExplore = "investigate unknown positions, chase targets" describeTactic TFollow = "follow leader's target or position, grab items"@@ -182,12 +171,42 @@ describeTactic TRoam = "move freely, chase targets" describeTactic TPatrol = "find and patrol an area (WIP)" -instance Binary Tactic+-- | Re-exported English phrase creation functions, applied to default+-- irregular word sets.+makePhrase, makeSentence :: [MU.Part] -> Text+makePhrase = MU.makePhrase MU.defIrregular+makeSentence = MU.makeSentence MU.defIrregular -instance Hashable Tactic+-- | Level bounds.+normalLevelBound :: (Int, Int)+normalLevelBound = (79, 20) --- Data.Binary+-- | @breturn b a = [a | b]@+breturn :: MonadPlus m => Bool -> a -> m a+breturn True a = return a+breturn False _ = mzero +-- | Personal data directory for the game. Depends on the OS and the game,+-- e.g., for LambdaHack under Linux it's @~\/.LambdaHack\/@.+appDataDir :: IO FilePath+appDataDir = do+ progName <- getProgName+ let name = takeWhile Char.isAlphaNum progName+ getAppUserDataDirectory name++xM :: Int -> Int64+xM k = fromIntegral k * 1000000++xD :: Double -> Double+xD k = k * 1000000++minusM, minusM1, oneM :: Int64+minusM = xM (-1)+minusM1 = xM (-1) - 1+oneM = xM 1++-- Data.Binary orphan instances+ instance (Enum k, Binary k, Binary e) => Binary (EM.EnumMap k e) where put m = put (EM.size m) >> mapM_ put (EM.toAscList m) get = EM.fromDistinctAscList <$> get@@ -204,7 +223,7 @@ get = fmap HM.fromList get put = put . HM.toList --- Data.Key+-- Data.Key orphan instances type instance Key (EM.EnumMap k) = k @@ -240,31 +259,15 @@ {-# INLINE adjust #-} adjust = EM.adjust --- Data.Hashable+-- Data.Hashable orphan instances instance (Enum k, Hashable k, Hashable e) => Hashable (EM.EnumMap k e) where hashWithSalt s x = hashWithSalt s (EM.toAscList x) --- Control.DeepSeq+-- Control.DeepSeq orphan instances instance NFData MU.Part instance NFData MU.Person instance NFData MU.Polarity---- | Personal data directory for the game. Depends on the OS and the game,--- e.g., for LambdaHack under Linux it's @~\/.LambdaHack\/@.-appDataDir :: IO FilePath-appDataDir = do- progName <- getProgName- let name = takeWhile Char.isAlphaNum progName- getAppUserDataDirectory name--xM :: Int -> Int64-xM k = fromIntegral k * 1000000--minusM, minusM1, oneM :: Int64-minusM = xM (-1)-minusM1 = xM (-1) - 1-oneM = xM 1
Game/LambdaHack/Common/MonadStateRead.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE TupleSections #-}--- | Game action monads and basic building blocks for human and computer--- player actions. Has no access to the main action type.+-- | Game state reading monad and basic operations. module Game.LambdaHack.Common.MonadStateRead ( MonadStateRead(..) , getState, getLevel, nUI@@ -14,17 +13,22 @@ import qualified Data.EnumMap.Strict as EM import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State+import Game.LambdaHack.Content.ModeKind +-- | Monad for reading game state. A state monad with state modification+-- disallowed (another constraint is needed to permit that).+-- The basic server and client monads are like that, because server+-- and clients freely modify their internal session data, but don't modify+-- the main game state, except in very restricted and synchronized way. class (Monad m, Functor m, Applicative m) => MonadStateRead m where getsState :: (State -> a) -> m a @@ -63,13 +67,13 @@ pickWeaponM :: MonadStateRead m => Maybe DiscoveryBenefit- -> [(ItemId, ItemFull)] -> Ability.Skills -> ActorAspect -> ActorId- -> m [(Int, (ItemId, ItemFull))]-pickWeaponM mdiscoBenefit allAssocs actorSk actorAspect source = do+ -> [(ItemId, ItemFull)] -> Ability.Skills -> ActorId+ -> m [(Double, (ItemId, ItemFull))]+pickWeaponM mdiscoBenefit allAssocs actorSk source = do sb <- getsState $ getActorBody source localTime <- getsState $ getLocalTime (blid sb)- let ar = actorAspect EM.! source- calmE = calmEnough sb ar+ ar <- getsState $ getActorAspect source+ let calmE = calmEnough sb ar forced = bproj sb permitted = permittedPrecious calmE forced preferredPrecious = either (const False) id . permitted
Game/LambdaHack/Common/Perception.hs view
@@ -4,7 +4,7 @@ -- Visibility works according to KISS. Everything that player sees is real. -- There are no unmarked hidden tiles and only solid tiles can be marked, -- so there are no invisible walls and to pass through an illusory wall,--- you have use a turn bumping into it first. Only tiles marked with Suspect+-- you have to use a turn bumping into it first. Only tiles marked with Suspect -- can turn out to be another tile. (So, if all tiles are marked with -- Suspect, the player knows nothing for sure, but this should be avoided, -- because searching becomes too time-consuming.)@@ -17,8 +17,7 @@ -- the tile, so the player can flee or block. Invisible actors in open -- space can be hit. module Game.LambdaHack.Common.Perception- ( -- * Public perception- PerVisible(..)+ ( PerVisible(..) , PerSmelled(..) , Perception(..) , PerLid@@ -31,16 +30,14 @@ import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import GHC.Generics (Generic)+import GHC.Generics (Generic) import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Level import Game.LambdaHack.Common.Point---- * Public perception -- | Visible positions. newtype PerVisible = PerVisible {pvisible :: ES.EnumSet Point}
Game/LambdaHack/Common/Point.hs view
@@ -4,6 +4,10 @@ ( X, Y, Point(..), maxLevelDimExponent , chessDist, euclidDistSq, adjacent, inside, bla, fromTo , originPoint+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , maxLevelDim, blaXY, balancedWord+#endif ) where import Prelude ()
Game/LambdaHack/Common/PointArray.hs view
@@ -1,22 +1,25 @@ -- | Arrays, based on Data.Vector.Unboxed, indexed by @Point@. module Game.LambdaHack.Common.PointArray ( GArray(..), Array, pindex, punindex- , (!), accessI, (//)+ , (!), accessI, (//), unsafeUpdateA, unsafeWriteA, unsafeWriteManyA , replicateA, replicateMA, generateA, generateMA, unfoldrNA, sizeA , foldrA, foldrA', foldlA', ifoldrA, ifoldrA', ifoldlA', foldMA', ifoldMA'- , mapA, imapA, imapMA_- , safeSetA, unsafeSetA, unsafeUpdateA, unsafeWriteA, unsafeWriteManyA+ , mapA, imapA, imapMA_, safeSetA, unsafeSetA , minIndexA, minLastIndexA, minIndexesA, maxIndexA, maxLastIndexA, forceA , fromListA, toListA+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , cnv+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.Monad.ST.Strict-import Data.Binary-import Data.Vector.Binary ()+import Control.Monad.ST.Strict+import Data.Binary+import Data.Vector.Binary () import qualified Data.Vector.Fusion.Bundle as Bundle import qualified Data.Vector.Generic as G import qualified Data.Vector.Unboxed as U@@ -35,6 +38,17 @@ instance Show (GArray w c) where show a = "PointArray.Array with size " ++ show (sizeA a) +instance (U.Unbox w, Binary w) => Binary (GArray w c) where+ put Array{..} = do+ put axsize+ put aysize+ put avector+ get = do+ axsize <- get+ aysize <- get+ avector <- get+ return $! Array{..}+ -- | Arrays of, effectively, @Word8@, indexed by @Point@. type Array c = GArray Word8 c @@ -204,6 +218,14 @@ let v = U.imap (\n c -> cnv $ f (punindex axsize n) (cnv c)) avector in Array{avector = v, ..} +-- | Map monadically over an array (function applied to each element+-- and its index) and ignore the results.+imapMA_ :: (U.Unbox w, Enum w, Enum c, Monad m)+ => (Point -> c -> m ()) -> GArray w c -> m ()+{-# INLINE imapMA_ #-}+imapMA_ f Array{..} =+ U.imapM_ (\n c -> f (punindex axsize n) (cnv c)) avector+ -- | Set all elements to the given value, in place. unsafeSetA :: (U.Unbox w, Enum w, Enum c) => c -> GArray w c -> GArray w c {-# INLINE unsafeSetA #-}@@ -219,14 +241,6 @@ safeSetA c Array{..} = Array{avector = U.modify (\v -> VM.set v (cnv c)) avector, ..} --- | Map monadically over an array (function applied to each element--- and its index) and ignore the results.-imapMA_ :: (U.Unbox w, Enum w, Enum c, Monad m)- => (Point -> c -> m ()) -> GArray w c -> m ()-{-# INLINE imapMA_ #-}-imapMA_ f Array{..} =- U.imapM_ (\n c -> f (punindex axsize n) (cnv c)) avector- -- | Yield the point coordinates of a minimum element of the array. -- The array may not be empty. minIndexA :: (U.Unbox w, Ord w) => GArray w c -> Point@@ -286,14 +300,3 @@ toListA :: (U.Unbox w, Enum w, Enum c) => GArray w c -> [c] {-# INLINE toListA #-} toListA Array{..} = map cnv $ U.toList avector--instance (U.Unbox w, Binary w) => Binary (GArray w c) where- put Array{..} = do- put axsize- put aysize- put avector- get = do- axsize <- get- aysize <- get- avector <- get- return $! Array{..}
Game/LambdaHack/Common/Prelude.hs view
@@ -1,4 +1,4 @@--- | Client monad for interacting with a human through UI.+-- | Custom Prelude, compatible across many GHC versions. module Game.LambdaHack.Common.Prelude ( module Prelude.Compat @@ -30,7 +30,7 @@ import Data.Text (Text) import qualified Data.Text as T (pack)-import NLP.Miniutter.English ((<+>))+import NLP.Miniutter.English ((<+>)) -- | Show and pack the result. tshow :: Show a => a -> Text
Game/LambdaHack/Common/Random.hs view
@@ -10,6 +10,10 @@ , castDice, chanceDice, castDiceXY -- * Specialized monadic folds , foldrM, foldlM'+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , rollFreq+#endif ) where import Prelude ()@@ -17,15 +21,14 @@ import Game.LambdaHack.Common.Prelude import qualified Control.Monad.Trans.State.Strict as St-import Data.Ratio+import Data.Ratio import qualified System.Random as R import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Frequency-import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Misc -- | The monad of computations with random generator state.--- The lazy state monad is OK here: the state is small and regularly forced. type Rnd a = St.State R.StdGen a -- | Get a random object within a range with a uniform distribution.@@ -83,14 +86,7 @@ -- | Cast dice scaled with current level depth. -- Note that at the first level, the scaled dice are always ignored. castDice :: AbsDepth -> AbsDepth -> Dice.Dice -> Rnd Int-castDice (AbsDepth n) (AbsDepth depth) dice = do- let !_A = assert (n >= 0 && n <= depth- `blame` "invalid depth for dice rolls"- `swith` (n, depth)) ()- dc <- frequency $ Dice.diceConst dice- dl <- frequency $ Dice.diceLevel dice- return $! (dc + (dl * max 0 (n - 1)) `div` max 1 (depth - 1))- * Dice.diceMult dice+castDice = Dice.castDice randomR -- | Cast dice scaled with current level depth and return @True@ -- if the results is greater than 50.
+ Game/LambdaHack/Common/ReqFailure.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE DeriveGeneric #-}+-- | Possible causes of failure of request.+module Game.LambdaHack.Common.ReqFailure+ ( ReqFailure(..)+ , impossibleReqFailure, showReqFailure+ , permittedPrecious, permittedProject, permittedProjectAI, permittedApply+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , permittedPreciousAI+#endif+ ) where++import Prelude ()++import Game.LambdaHack.Common.Prelude++import Data.Binary+import GHC.Generics (Generic)++import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Common.Time+import qualified Game.LambdaHack.Content.ItemKind as IK++-- | Possible causes of failure of request.+data ReqFailure =+ MoveNothing+ | MeleeSelf+ | MeleeDistant+ | DisplaceDistant+ | DisplaceAccess+ | DisplaceProjectiles+ | DisplaceDying+ | DisplaceBraced+ | DisplaceImmobile+ | DisplaceSupported+ | AlterUnskilled+ | AlterUnwalked+ | AlterDistant+ | AlterBlockActor+ | AlterBlockItem+ | AlterNothing+ | EqpOverfull+ | EqpStackFull+ | ApplyUnskilled+ | ApplyRead+ | ApplyOutOfReach+ | ApplyCharging+ | ApplyNoEffects+ | ItemNothing+ | ItemNotCalm+ | NotCalmPrecious+ | ProjectUnskilled+ | ProjectAimOnself+ | ProjectBlockTerrain+ | ProjectBlockActor+ | ProjectLobable+ | ProjectOutOfReach+ | TriggerNothing+ | NoChangeDunLeader+ deriving (Show, Eq, Generic)++instance Binary ReqFailure++impossibleReqFailure :: ReqFailure -> Bool+impossibleReqFailure reqFailure = case reqFailure of+ MoveNothing -> True+ MeleeSelf -> True+ MeleeDistant -> True+ DisplaceDistant -> True+ DisplaceAccess -> True+ DisplaceProjectiles -> True+ DisplaceDying -> True+ DisplaceBraced -> True+ DisplaceImmobile -> False -- unidentified skill items+ DisplaceSupported -> False+ AlterUnskilled -> False -- unidentified skill items+ AlterUnwalked -> False+ AlterDistant -> True+ AlterBlockActor -> True -- adjacent actor always visible+ AlterBlockItem -> True -- adjacent item always visible+ AlterNothing -> True+ EqpOverfull -> True+ EqpStackFull -> True+ ApplyUnskilled -> False -- unidentified skill items+ ApplyRead -> False -- unidentified skill items+ ApplyOutOfReach -> True+ ApplyCharging -> False -- if aspects unknown, charging unknown+ ApplyNoEffects -> False -- if effects unknown, can't prevent it+ ItemNothing -> True+ ItemNotCalm -> False -- unidentified skill items+ NotCalmPrecious -> False -- unidentified skill items+ ProjectUnskilled -> False -- unidentified skill items+ ProjectAimOnself -> True+ ProjectBlockTerrain -> True -- adjacent terrain always visible+ ProjectBlockActor -> True -- adjacent actor always visible+ ProjectLobable -> False -- unidentified skill items+ ProjectOutOfReach -> True+ TriggerNothing -> True -- terrain underneath always visible+ NoChangeDunLeader -> True++showReqFailure :: ReqFailure -> Text+showReqFailure reqFailure = case reqFailure of+ MoveNothing -> "wasting time on moving into obstacle"+ MeleeSelf -> "trying to melee oneself"+ MeleeDistant -> "trying to melee a distant foe"+ DisplaceDistant -> "trying to displace a distant actor"+ DisplaceAccess -> "switching places without access"+ DisplaceProjectiles -> "trying to displace multiple projectiles"+ DisplaceDying -> "trying to displace a dying foe"+ DisplaceBraced -> "trying to displace a braced foe"+ DisplaceImmobile -> "trying to displace an immobile foe"+ DisplaceSupported -> "trying to displace a supported foe"+ AlterUnskilled -> "unskilled actors cannot alter tiles"+ AlterUnwalked -> "unskilled actors cannot enter tiles"+ AlterDistant -> "trying to alter a distant tile"+ AlterBlockActor -> "blocked by an actor"+ AlterBlockItem -> "jammed by an item"+ AlterNothing -> "wasting time on altering nothing"+ EqpOverfull -> "cannot equip any more items"+ EqpStackFull -> "cannot equip the whole item stack"+ ApplyUnskilled -> "unskilled actors cannot apply items"+ ApplyRead -> "activating this kind of items requires skill level 2"+ ApplyOutOfReach -> "cannot apply an item out of reach"+ ApplyCharging -> "cannot apply an item that is still charging"+ ApplyNoEffects -> "cannot apply an item that produces no effects"+ ItemNothing -> "wasting time on void item manipulation"+ ItemNotCalm -> "you are too alarmed to use the shared stash"+ NotCalmPrecious -> "you are too alarmed to handle such an exquisite item"+ ProjectUnskilled -> "unskilled actors cannot aim"+ ProjectAimOnself -> "cannot aim at oneself"+ ProjectBlockTerrain -> "aiming obstructed by terrain"+ ProjectBlockActor -> "aiming blocked by an actor"+ ProjectLobable -> "lobbing an item requires fling skill 3"+ ProjectOutOfReach -> "cannot aim an item out of reach"+ TriggerNothing -> "wasting time on triggering nothing"+ NoChangeDunLeader -> "no manual level change for your team"++-- The item should not be applied nor thrown because it's too delicate+-- to operate when not calm or becuse it's too precious to identify by use.+permittedPrecious :: Bool -> Bool -> ItemFull -> Either ReqFailure Bool+permittedPrecious calmE forced itemFull =+ let isPrecious = IK.Precious `elem` jfeature (itemBase itemFull)+ in if not calmE && not forced && isPrecious then Left NotCalmPrecious+ else Right $ IK.Durable `elem` jfeature (itemBase itemFull)+ || case itemDisco itemFull of+ Just ItemDisco{itemAspect=Just _} -> True+ _ -> not isPrecious++permittedPreciousAI :: Bool -> ItemFull -> Bool+permittedPreciousAI calmE itemFull =+ let isPrecious = IK.Precious `elem` jfeature (itemBase itemFull)+ in if not calmE && isPrecious then False+ else IK.Durable `elem` jfeature (itemBase itemFull)+ || case itemDisco itemFull of+ Just ItemDisco{itemAspect=Just _} -> True+ _ -> not isPrecious++permittedProject :: Bool -> Int -> Bool -> [Char] -> ItemFull+ -> Either ReqFailure Bool+permittedProject forced skill calmE triggerSyms itemFull@ItemFull{itemBase} =+ if | not forced && skill < 1 -> Left ProjectUnskilled+ | not forced+ && IK.Lobable `elem` jfeature itemBase+ && skill < 3 -> Left ProjectLobable+ | otherwise ->+ let legal = permittedPrecious calmE forced itemFull+ in case legal of+ Left{} -> legal+ Right False -> legal+ Right True -> Right $+ if | null triggerSyms -> True+ | ' ' `elem` triggerSyms ->+ case strengthEqpSlot itemFull of+ Just IK.EqpSlotLightSource -> True+ Just _ -> False+ Nothing -> not (goesIntoEqp itemBase)+ | otherwise -> jsymbol itemBase `elem` triggerSyms++-- Speedup.+permittedProjectAI :: Int -> Bool -> ItemFull -> Bool+permittedProjectAI skill calmE itemFull@ItemFull{itemBase} =+ if | skill < 1 -> False+ | IK.Lobable `elem` jfeature itemBase+ && skill < 3 -> False+ | otherwise -> permittedPreciousAI calmE itemFull++permittedApply :: Time -> Int -> Bool-> [Char] -> ItemFull+ -> Either ReqFailure Bool+permittedApply localTime skill calmE triggerSyms itemFull@ItemFull{..} =+ if | skill < 1 -> Left ApplyUnskilled+ | jsymbol itemBase == '?' && skill < 2 -> Left ApplyRead+ -- We assume if the item has a timeout, all or most of interesting+ -- effects are under Recharging, so no point activating if not recharged.+ -- Note that if client doesn't know the timeout, here we leak the fact+ -- that the item is still charging, but the client risks destruction+ -- if the item is, in fact, recharged and is not durable+ -- (very likely in case of jewellery), so it's OK (the message may be+ -- somewhat alarming though).+ | not $ hasCharge localTime itemFull -> Left ApplyCharging+ | otherwise -> case itemDisco of+ Just ItemDisco{itemKind} | null $ IK.ieffects itemKind ->+ Left ApplyNoEffects+ _ -> let legal = permittedPrecious calmE False itemFull+ in case legal of+ Left{} -> legal+ Right False -> legal+ Right True -> Right $+ if ' ' `elem` triggerSyms+ then IK.Applicable `elem` jfeature itemBase+ else jsymbol itemBase `elem` triggerSyms
− Game/LambdaHack/Common/Request.hs
@@ -1,259 +0,0 @@-{-# LANGUAGE DataKinds, DeriveGeneric, GADTs, KindSignatures, StandaloneDeriving- #-}--- | Abstract syntax of server commands.--- See--- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>.-module Game.LambdaHack.Common.Request- ( RequestAI, ReqAI(..), RequestUI, ReqUI(..)- , RequestTimed(..), RequestAnyAbility(..), ReqFailure(..)- , impossibleReqFailure, showReqFailure, timedToUI- , permittedPrecious, permittedProject, permittedProjectAI, permittedApply- ) where--import Prelude ()--import Game.LambdaHack.Common.Prelude--import Data.Binary-import GHC.Generics (Generic)--import Game.LambdaHack.Common.Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector-import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind---- | Client-server requests sent by AI clients.-data ReqAI =- ReqAITimed RequestAnyAbility- | ReqAINop- deriving Show--type RequestAI = (ReqAI, Maybe ActorId)---- | Client-server requests sent by UI clients.-data ReqUI =- ReqUINop- | ReqUITimed RequestAnyAbility- | ReqUIGameRestart (GroupName ModeKind) Challenge- | ReqUIGameExit- | ReqUIGameSave- | ReqUITactic Tactic- | ReqUIAutomate- deriving Show--type RequestUI = (ReqUI, Maybe ActorId)--data RequestAnyAbility = forall a. RequestAnyAbility (RequestTimed a)--deriving instance Show RequestAnyAbility--timedToUI :: RequestTimed a -> ReqUI-timedToUI = ReqUITimed . RequestAnyAbility---- | Client-server requests that take game time. Sent by both AI and UI clients.-data RequestTimed :: Ability -> * where- ReqMove :: Vector -> RequestTimed 'AbMove- ReqMelee :: ActorId -> ItemId -> CStore -> RequestTimed 'AbMelee- ReqDisplace :: ActorId -> RequestTimed 'AbDisplace- ReqAlter :: Point -> RequestTimed 'AbAlter- ReqWait :: RequestTimed 'AbWait- ReqWait10 :: RequestTimed 'AbWait- ReqMoveItems :: [(ItemId, Int, CStore, CStore)] -> RequestTimed 'AbMoveItem- ReqProject :: Point -> Int -> ItemId -> CStore -> RequestTimed 'AbProject- ReqApply :: ItemId -> CStore -> RequestTimed 'AbApply--deriving instance Show (RequestTimed a)--data ReqFailure =- MoveNothing- | MeleeSelf- | MeleeDistant- | DisplaceDistant- | DisplaceAccess- | DisplaceProjectiles- | DisplaceDying- | DisplaceBraced- | DisplaceImmobile- | DisplaceSupported- | AlterUnskilled- | AlterUnwalked- | AlterDistant- | AlterBlockActor- | AlterBlockItem- | AlterNothing- | EqpOverfull- | EqpStackFull- | ApplyUnskilled- | ApplyRead- | ApplyOutOfReach- | ApplyCharging- | ApplyNoEffects- | ItemNothing- | ItemNotCalm- | NotCalmPrecious- | ProjectUnskilled- | ProjectAimOnself- | ProjectBlockTerrain- | ProjectBlockActor- | ProjectLobable- | ProjectOutOfReach- | TriggerNothing- | NoChangeDunLeader- deriving (Show, Eq, Generic)--instance Binary ReqFailure--impossibleReqFailure :: ReqFailure -> Bool-impossibleReqFailure reqFailure = case reqFailure of- MoveNothing -> True- MeleeSelf -> True- MeleeDistant -> True- DisplaceDistant -> True- DisplaceAccess -> True- DisplaceProjectiles -> True- DisplaceDying -> True- DisplaceBraced -> True- DisplaceImmobile -> False -- unidentified skill items- DisplaceSupported -> False- AlterUnskilled -> False -- unidentified skill items- AlterUnwalked -> False- AlterDistant -> True- AlterBlockActor -> True -- adjacent actor always visible- AlterBlockItem -> True -- adjacent item always visible- AlterNothing -> True- EqpOverfull -> True- EqpStackFull -> True- ApplyUnskilled -> False -- unidentified skill items- ApplyRead -> False -- unidentified skill items- ApplyOutOfReach -> True- ApplyCharging -> False -- if aspects unknown, charging unknown- ApplyNoEffects -> False -- if effects unknown, can't prevent it- ItemNothing -> True- ItemNotCalm -> False -- unidentified skill items- NotCalmPrecious -> False -- unidentified skill items- ProjectUnskilled -> False -- unidentified skill items- ProjectAimOnself -> True- ProjectBlockTerrain -> True -- adjacent terrain always visible- ProjectBlockActor -> True -- adjacent actor always visible- ProjectLobable -> False -- unidentified skill items- ProjectOutOfReach -> True- TriggerNothing -> True -- terrain underneath always visible- NoChangeDunLeader -> True--showReqFailure :: ReqFailure -> Text-showReqFailure reqFailure = case reqFailure of- MoveNothing -> "wasting time on moving into obstacle"- MeleeSelf -> "trying to melee oneself"- MeleeDistant -> "trying to melee a distant foe"- DisplaceDistant -> "trying to displace a distant actor"- DisplaceAccess -> "switching places without access"- DisplaceProjectiles -> "trying to displace multiple projectiles"- DisplaceDying -> "trying to displace a dying foe"- DisplaceBraced -> "trying to displace a braced foe"- DisplaceImmobile -> "trying to displace an immobile foe"- DisplaceSupported -> "trying to displace a supported foe"- AlterUnskilled -> "unskilled actors cannot alter tiles"- AlterUnwalked -> "unskilled actors cannot enter tiles"- AlterDistant -> "trying to alter a distant tile"- AlterBlockActor -> "blocked by an actor"- AlterBlockItem -> "jammed by an item"- AlterNothing -> "wasting time on altering nothing"- EqpOverfull -> "cannot equip any more items"- EqpStackFull -> "cannot equip the whole item stack"- ApplyUnskilled -> "unskilled actors cannot apply items"- ApplyRead -> "activating this kind of items requires skill level 2"- ApplyOutOfReach -> "cannot apply an item out of reach"- ApplyCharging -> "cannot apply an item that is still charging"- ApplyNoEffects -> "cannot apply an item that produces no effects"- ItemNothing -> "wasting time on void item manipulation"- ItemNotCalm -> "you are too alarmed to use the shared stash"- NotCalmPrecious -> "you are too alarmed to handle such an exquisite item"- ProjectUnskilled -> "unskilled actors cannot aim"- ProjectAimOnself -> "cannot aim at oneself"- ProjectBlockTerrain -> "aiming obstructed by terrain"- ProjectBlockActor -> "aiming blocked by an actor"- ProjectLobable -> "lobbing an item requires fling skill 3"- ProjectOutOfReach -> "cannot aim an item out of reach"- TriggerNothing -> "wasting time on triggering nothing"- NoChangeDunLeader -> "no manual level change for your team"---- The item should not be applied nor thrown because it's too delicate--- to operate when not calm or becuse it's too precious to identify by use.-permittedPrecious :: Bool -> Bool -> ItemFull -> Either ReqFailure Bool-permittedPrecious calmE forced itemFull =- let isPrecious = IK.Precious `elem` jfeature (itemBase itemFull)- in if not calmE && not forced && isPrecious then Left NotCalmPrecious- else Right $ IK.Durable `elem` jfeature (itemBase itemFull)- || case itemDisco itemFull of- Just ItemDisco{itemAspect=Just _} -> True- _ -> not isPrecious--permittedPreciousAI :: Bool -> ItemFull -> Bool-permittedPreciousAI calmE itemFull =- let isPrecious = IK.Precious `elem` jfeature (itemBase itemFull)- in if not calmE && isPrecious then False- else IK.Durable `elem` jfeature (itemBase itemFull)- || case itemDisco itemFull of- Just ItemDisco{itemAspect=Just _} -> True- _ -> not isPrecious--permittedProject :: Bool -> Int -> Bool -> [Char] -> ItemFull- -> Either ReqFailure Bool-permittedProject forced skill calmE triggerSyms itemFull@ItemFull{itemBase} =- if | not forced && skill < 1 -> Left ProjectUnskilled- | not forced- && IK.Lobable `elem` jfeature itemBase- && skill < 3 -> Left ProjectLobable- | otherwise ->- let legal = permittedPrecious calmE forced itemFull- in case legal of- Left{} -> legal- Right False -> legal- Right True -> Right $- if | null triggerSyms -> True- | ' ' `elem` triggerSyms ->- case strengthEqpSlot itemFull of- Just IK.EqpSlotLightSource -> True- Just _ -> False- Nothing -> not (goesIntoEqp itemBase)- | otherwise -> jsymbol itemBase `elem` triggerSyms---- Speedup.-permittedProjectAI :: Int -> Bool -> ItemFull -> Bool-permittedProjectAI skill calmE itemFull@ItemFull{itemBase} =- if | skill < 1 -> False- | IK.Lobable `elem` jfeature itemBase- && skill < 3 -> False- | otherwise -> permittedPreciousAI calmE itemFull--permittedApply :: Time -> Int -> Bool-> [Char] -> ItemFull- -> Either ReqFailure Bool-permittedApply localTime skill calmE triggerSyms itemFull@ItemFull{..} =- if | skill < 1 -> Left ApplyUnskilled- | jsymbol itemBase == '?' && skill < 2 -> Left ApplyRead- -- We assume if the item has a timeout, all or most of interesting- -- effects are under Recharging, so no point activating if not recharged.- -- Note that if client doesn't know the timeout, here we leak the fact- -- that the item is still charging, but the client risks destruction- -- if the item is, in fact, recharged and is not durable- -- (very likely in case of jewellery), so it's OK (the message may be- -- somewhat alarming though).- | not $ hasCharge localTime itemFull -> Left ApplyCharging- | otherwise -> case itemDisco of- Just ItemDisco{itemKind} | null $ IK.ieffects itemKind ->- Left ApplyNoEffects- _ -> let legal = permittedPrecious calmE False itemFull- in case legal of- Left{} -> legal- Right False -> legal- Right True -> Right $- if ' ' `elem` triggerSyms- then IK.Applicable `elem` jfeature itemBase- else jsymbol itemBase `elem` triggerSyms
− Game/LambdaHack/Common/Response.hs
@@ -1,33 +0,0 @@--- | Abstract syntax of client commands.--- See--- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>.-module Game.LambdaHack.Common.Response- ( Response(..), CliSerQueue, ChanServer(..)- ) where--import Prelude ()--import Game.LambdaHack.Common.Prelude--import Control.Concurrent--import Game.LambdaHack.Atomic-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.Request---- | Abstract syntax of client commands for both AI and UI clients.-data Response =- RespUpdAtomic UpdAtomic- | RespQueryAI ActorId- | RespSfxAtomic SfxAtomic- | RespQueryUI- deriving Show--type CliSerQueue = MVar---- | Connection channel between the server and a single client.-data ChanServer = ChanServer- { responseS :: CliSerQueue Response- , requestAIS :: CliSerQueue RequestAI- , requestUIS :: Maybe (CliSerQueue RequestUI)- }
Game/LambdaHack/Common/RingBuffer.hs view
@@ -9,11 +9,12 @@ import Game.LambdaHack.Common.Prelude hiding (length, uncons) -import Data.Binary+import Data.Binary import qualified Data.Foldable as Foldable import qualified Data.Sequence as Seq-import GHC.Generics (Generic)+import GHC.Generics (Generic) +-- | Ring buffers of a size determined at initialization. data RingBuffer a = RingBuffer { rbCarrier :: Seq.Seq a , rbMaxSize :: Int@@ -30,7 +31,6 @@ let rbMaxSize = max 1 size in RingBuffer (Seq.replicate rbMaxSize dummy) rbMaxSize 0 0 --- | Add element to the front of the buffer. cons :: a -> RingBuffer a -> RingBuffer a cons a RingBuffer{..} = let incNext = (rbNext + 1) `mod` rbMaxSize
Game/LambdaHack/Common/Save.hs view
@@ -1,9 +1,9 @@--- | Saving and restoring server game state.+-- | Saving and restoring game state, used by both server and clients. module Game.LambdaHack.Common.Save ( ChanSave, saveToChan, wrapInSaves, restoreGame, saveNameCli, saveNameSer #ifdef EXPOSE_INTERNAL -- * Internal operations- , loopSave+ , loopSave, vExevLib, showVersion2, delayPrint #endif ) where @@ -14,22 +14,22 @@ -- Cabal import qualified Paths_LambdaHack as Self (version) -import Control.Concurrent-import Control.Concurrent.Async-import qualified Control.Exception as Ex hiding (handle)-import Data.Binary-import Data.Text (Text)+import Control.Concurrent+import Control.Concurrent.Async+import qualified Control.Exception as Ex+import Data.Binary+import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T-import Data.Version-import System.FilePath-import System.IO (hFlush, stdout)+import Data.Version+import System.FilePath+import System.IO (hFlush, stdout) import qualified System.Random as R -import Game.LambdaHack.Common.File+import Game.LambdaHack.Common.File import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc (FactionId, appDataDir)-import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Common.Misc (FactionId, appDataDir)+import Game.LambdaHack.Content.RuleKind type ChanSave a = MVar (Maybe a) @@ -39,7 +39,7 @@ void $ tryTakeMVar toSave putMVar toSave $ Just s --- | Repeatedly save a simple serialized version of the current state.+-- | Repeatedly save serialized snapshots of current state. loopSave :: Binary a => Kind.COps -> (a -> FilePath) -> ChanSave a -> IO () loopSave cops stateToFileName toSave = loop
Game/LambdaHack/Common/State.hs view
@@ -1,56 +1,143 @@--- | Server and client game state types and operations.+-- | The common server and client basic game state type and its operations. module Game.LambdaHack.Common.State ( -- * Basic game state, local or global State -- * State components- , sdungeon, stotalDepth, sactorD, sitemD, sfactionD, stime, scops, shigh, sgameModeId- -- * State operations+ , sdungeon, stotalDepth, sactorD, sitemD, sitemIxMap, sfactionD, stime, scops+ , shigh, sgameModeId, sdiscoKind, sdiscoAspect, sactorAspect+ -- * State construction , defStateGlobal, emptyState, localFromGlobal- , updateDungeon, updateDepth, updateActorD, updateItemD+ -- * State update+ , updateDungeon, updateDepth, updateActorD, updateItemD, updateItemIxMap , updateFactionD, updateTime, updateCOps- ) where+ , updateDiscoKind, updateDiscoAspect, updateActorAspect+ -- * State operations+ , getItemBody, aspectRecordFromItem, aspectRecordFromIid+ , aspectRecordFromActor, actorAspectInDungeon+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , unknownLevel, unknownTileMap+#endif+ ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM -import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Faction import qualified Game.LambdaHack.Common.HighScore as HighScore-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind (TileKind, unknownId)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.TileKind (TileKind, unknownId) --- | View on game state. "Remembered" fields carry a subset of the info--- in the client copies of the state. Clients never directly change--- their @State@, but apply atomic actions sent by the server to do so.+-- | View on the basic game state.+-- The @remembered@ fields, in client copies of the state, carry only+-- a subset of the full information that the server keeps.+-- Clients never directly change their @State@, but apply+-- atomic actions sent by the server to do so (and/or the server applies+-- the actions to each client state in turn). data State = State- { _sdungeon :: Dungeon -- ^ remembered dungeon- , _stotalDepth :: AbsDepth -- ^ absolute dungeon depth, for item creation- , _sactorD :: ActorDict -- ^ remembered actors in the dungeon- , _sitemD :: ItemDict -- ^ remembered items in the dungeon- , _sfactionD :: FactionDict -- ^ remembered sides still in game- , _stime :: Time -- ^ global game time, for UI display only- , _scops :: ~Kind.COps -- ^ remembered content- , _shigh :: HighScore.ScoreDict -- ^ high score table- , _sgameModeId :: Kind.Id ModeKind -- ^ current game mode+ { _sdungeon :: Dungeon -- ^ remembered dungeon+ , _stotalDepth :: AbsDepth -- ^ absolute dungeon depth, for item creation+ , _sactorD :: ActorDict -- ^ remembered actors in the dungeon+ , _sitemD :: ItemDict -- ^ remembered items in the dungeon+ , _sitemIxMap :: ItemIxMap -- ^ spotted items with the same kind index+ , _sfactionD :: FactionDict -- ^ remembered sides still in game+ , _stime :: Time -- ^ global game time, for UI display only+ , _scops :: ~Kind.COps -- ^ remembered content+ , _shigh :: HighScore.ScoreDict -- ^ high score table+ , _sgameModeId :: Kind.Id ModeKind -- ^ current game mode+ , _sdiscoKind :: DiscoveryKind -- ^ item kind discoveries data+ , _sdiscoAspect :: DiscoveryAspect -- ^ item aspect data+ , _sactorAspect :: ActorAspect -- ^ actor aspect data } deriving (Show, Eq) +instance Binary State where+ put State{..} = do+ put _sdungeon+ put _stotalDepth+ put _sactorD+ put _sitemD+ put _sitemIxMap+ put _sfactionD+ put _stime+ put _shigh+ put _sgameModeId+ put _sdiscoKind+ put _sdiscoAspect+ get = do+ _sdungeon <- get+ _stotalDepth <- get+ _sactorD <- get+ _sitemD <- get+ _sitemIxMap <- get+ _sfactionD <- get+ _stime <- get+ _shigh <- get+ _sgameModeId <- get+ _sdiscoKind <- get+ _sdiscoAspect <- get+ let _scops = error $ "overwritten by recreated cops" `showFailure` ()+ sNoActorAspect = State{_sactorAspect = EM.empty, ..}+ _sactorAspect = actorAspectInDungeon sNoActorAspect+ return $! State{..}++sdungeon :: State -> Dungeon+sdungeon = _sdungeon++stotalDepth :: State -> AbsDepth+stotalDepth = _stotalDepth++sactorD :: State -> ActorDict+sactorD = _sactorD++sitemD :: State -> ItemDict+sitemD = _sitemD++sitemIxMap :: State -> ItemIxMap+sitemIxMap = _sitemIxMap++sfactionD :: State -> FactionDict+sfactionD = _sfactionD++stime :: State -> Time+stime = _stime++scops :: State -> Kind.COps+scops = _scops++shigh :: State -> HighScore.ScoreDict+shigh = _shigh++sgameModeId :: State -> Kind.Id ModeKind+sgameModeId = _sgameModeId++sdiscoKind :: State -> DiscoveryKind+sdiscoKind = _sdiscoKind++sdiscoAspect :: State -> DiscoveryAspect+sdiscoAspect = _sdiscoAspect++sactorAspect :: State -> ActorAspect+sactorAspect = _sactorAspect+ unknownLevel :: Kind.COps -> AbsDepth -> X -> Y- -> Text -> ([Point], [Point]) -> Int -> [Point] -> Bool+ -> Text -> Text -> ([Point], [Point]) -> Int -> [Point] -> Bool -> Level unknownLevel Kind.COps{cotile=Kind.Ops{ouniqGroup}}- ldepth lxsize lysize ldesc lstair lexplorable lescape lnight =+ ldepth lxsize lysize lname ldesc+ lstair lexplorable lescape lnight = let outerId = ouniqGroup "basic outer fence" in Level { ldepth , lfloor = EM.empty@@ -60,7 +147,6 @@ , lxsize , lysize , lsmell = EM.empty- , ldesc , lstair , lseen = 0 , lexplorable@@ -71,6 +157,8 @@ , litemFreq = [] , lescape , lnight+ , lname+ , ldesc } unknownTileMap :: Kind.Id TileKind -> Int -> Int -> TileMap@@ -85,13 +173,17 @@ -- | Initial complete global game state. defStateGlobal :: Dungeon -> AbsDepth -> FactionDict -> Kind.COps- -> HighScore.ScoreDict -> Kind.Id ModeKind+ -> HighScore.ScoreDict -> Kind.Id ModeKind -> DiscoveryKind -> State-defStateGlobal _sdungeon _stotalDepth _sfactionD _scops _shigh _sgameModeId =+defStateGlobal _sdungeon _stotalDepth _sfactionD _scops _shigh _sgameModeId+ _sdiscoKind = State { _sactorD = EM.empty , _sitemD = EM.empty+ , _sitemIxMap = EM.empty , _stime = timeZero+ , _sdiscoAspect = EM.empty+ , _sactorAspect = EM.empty , .. } @@ -103,11 +195,15 @@ , _stotalDepth = AbsDepth 0 , _sactorD = EM.empty , _sitemD = EM.empty+ , _sitemIxMap = EM.empty , _sfactionD = EM.empty , _stime = timeZero , _scops , _shigh = HighScore.empty , _sgameModeId = minBound -- the initial value is unused+ , _sdiscoKind = EM.empty+ , _sdiscoAspect = EM.empty+ , _sactorAspect = EM.empty } -- | Local state created by removing secret information from global@@ -117,8 +213,8 @@ State { _sdungeon = EM.map (\Level{..} ->- unknownLevel _scops ldepth lxsize lysize ldesc lstair lexplorable- lescape lnight)+ unknownLevel _scops ldepth lxsize lysize lname ldesc+ lstair lexplorable lescape lnight) _sdungeon , .. }@@ -139,6 +235,10 @@ updateItemD :: (ItemDict -> ItemDict) -> State -> State updateItemD f s = s {_sitemD = f (_sitemD s)} +-- | Update the item kind index map.+updateItemIxMap :: (ItemIxMap -> ItemIxMap) -> State -> State+updateItemIxMap f s = s {_sitemIxMap = f (_sitemIxMap s)}+ -- | Update faction data within state. updateFactionD :: (FactionDict -> FactionDict) -> State -> State updateFactionD f s = s {_sfactionD = f (_sfactionD s)}@@ -151,51 +251,35 @@ updateCOps :: (Kind.COps -> Kind.COps) -> State -> State updateCOps f s = s {_scops = f (_scops s)} -sdungeon :: State -> Dungeon-sdungeon = _sdungeon--stotalDepth :: State -> AbsDepth-stotalDepth = _stotalDepth--sactorD :: State -> ActorDict-sactorD = _sactorD+updateDiscoKind :: (DiscoveryKind -> DiscoveryKind) -> State -> State+updateDiscoKind f s = s {_sdiscoKind = f (_sdiscoKind s)} -sitemD :: State -> ItemDict-sitemD = _sitemD+updateDiscoAspect :: (DiscoveryAspect -> DiscoveryAspect) -> State -> State+updateDiscoAspect f s = s {_sdiscoAspect = f (_sdiscoAspect s)} -sfactionD :: State -> FactionDict-sfactionD = _sfactionD+updateActorAspect :: (ActorAspect -> ActorAspect) -> State -> State+updateActorAspect f s = s {_sactorAspect = f (_sactorAspect s)} -stime :: State -> Time-stime = _stime+getItemBody :: ItemId -> State -> Item+getItemBody iid s = sitemD s EM.! iid -scops :: State -> Kind.COps-scops = _scops+aspectRecordFromItem :: ItemId -> Item -> State -> AspectRecord+aspectRecordFromItem iid item s =+ case EM.lookup iid (sdiscoAspect s) of+ Just ar -> ar+ Nothing -> case EM.lookup (jkindIx item) (sdiscoKind s) of+ Just KindMean{kmMean} -> kmMean+ Nothing -> emptyAspectRecord -shigh :: State -> HighScore.ScoreDict-shigh = _shigh+aspectRecordFromIid :: ItemId -> State -> AspectRecord+aspectRecordFromIid iid s = aspectRecordFromItem iid (getItemBody iid s) s -sgameModeId :: State -> Kind.Id ModeKind-sgameModeId = _sgameModeId+aspectRecordFromActor :: Actor -> State -> AspectRecord+aspectRecordFromActor b s =+ let processIid (iid, (k, _)) = (aspectRecordFromIid iid s, k)+ processBag ass = sumAspectRecord $ map processIid ass+ in processBag $ EM.assocs (borgan b) ++ EM.assocs (beqp b) -instance Binary State where- put State{..} = do- put _sdungeon- put _stotalDepth- put _sactorD- put _sitemD- put _sfactionD- put _stime- put _shigh- put _sgameModeId- get = do- _sdungeon <- get- _stotalDepth <- get- _sactorD <- get- _sitemD <- get- _sfactionD <- get- _stime <- get- _shigh <- get- _sgameModeId <- get- let _scops = error $ "overwritten by recreated cops" `showFailure` ()- return $! State{..}+actorAspectInDungeon :: State -> ActorAspect+actorAspectInDungeon s =+ EM.map (`aspectRecordFromActor` s) $ sactorD s
Game/LambdaHack/Common/Tile.hs view
@@ -14,15 +14,19 @@ -- -- Actors at normal speed (2 m/s) take one turn to move one tile (1 m by 1 m). module Game.LambdaHack.Common.Tile- ( kindHasFeature, hasFeature, isClear, isLit, isWalkable, isDoor, isChangable+ ( -- * Construction of tile property lookup speedup tables+ speedup+ -- * Sped up property lookups+ , isClear, isLit, isWalkable, isDoor, isChangable , isSuspect, isHideAs, consideredByAI, isExplorable , isOftenItem, isOftenActor, isNoItem, isNoActor, isEasyOpen- , speedup, alterMinSkill, alterMinWalk- , openTo, closeTo, embeddedItems, revealAs, obscureAs, hideAs, buildAs- , isEasyOpenKind, isOpenable, isClosable+ , alterMinSkill, alterMinWalk+ -- * Slow property lookups+ , kindHasFeature, hasFeature, openTo, closeTo, embeddedItems, revealAs+ , obscureAs, hideAs, buildAs, isEasyOpenKind, isOpenable, isClosable #ifdef EXPOSE_INTERNAL -- * Internal operations- , createTab, createTabWithKey, accessTab+ , createTab, createTabWithKey, accessTab, alterMinSkillKind, alterMinWalkKind #endif ) where @@ -31,14 +35,14 @@ import Game.LambdaHack.Common.Prelude import qualified Data.Vector.Unboxed as U-import Data.Word (Word8)+import Data.Word (Word8) import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Content.ItemKind (ItemKind)-import Game.LambdaHack.Content.TileKind (TileKind, TileSpeedup (..),- isUknownSpace)+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.TileKind (TileKind, TileSpeedup (..),+ isUknownSpace) import qualified Game.LambdaHack.Content.TileKind as TK createTab :: U.Unbox a => Kind.Ops TileKind -> (TileKind -> a) -> TK.Tab a@@ -59,16 +63,77 @@ {-# INLINE accessTab #-} accessTab (TK.Tab tab) ki = tab `U.unsafeIndex` fromEnum ki --- | Whether a tile kind has the given feature.-kindHasFeature :: TK.Feature -> TileKind -> Bool-{-# INLINE kindHasFeature #-}-kindHasFeature f t = f `elem` TK.tfeature t+speedup :: Bool -> Kind.Ops TileKind -> TileSpeedup+speedup allClear cotile =+ -- Vectors pack bools as Word8 by default. No idea if the extra memory+ -- taken makes random lookups more or less efficient, so not optimizing+ -- further, until I have benchmarks.+ let isClearTab | allClear = createTab cotile+ $ not . (== maxBound) . TK.talter+ | otherwise = createTab cotile+ $ kindHasFeature TK.Clear+ isLitTab = createTab cotile $ not . kindHasFeature TK.Dark+ isWalkableTab = createTab cotile $ kindHasFeature TK.Walkable+ isDoorTab = createTab cotile $ \tk ->+ let getTo TK.OpenTo{} = True+ getTo TK.CloseTo{} = True+ getTo _ = False+ in any getTo $ TK.tfeature tk+ isChangableTab = createTab cotile $ \tk ->+ let getTo TK.ChangeTo{} = True+ getTo _ = False+ in any getTo $ TK.tfeature tk+ isSuspectTab = createTab cotile TK.isSuspectKind+ isHideAsTab = createTab cotile $ \tk ->+ let getTo TK.HideAs{} = True+ getTo _ = False+ in any getTo $ TK.tfeature tk+ consideredByAITab = createTab cotile $ kindHasFeature TK.ConsideredByAI+ isOftenItemTab = createTab cotile $ kindHasFeature TK.OftenItem+ isOftenActorTab = createTab cotile $ kindHasFeature TK.OftenActor+ isNoItemTab = createTab cotile $ kindHasFeature TK.NoItem+ isNoActorTab = createTab cotile $ kindHasFeature TK.NoActor+ isEasyOpenTab = createTab cotile isEasyOpenKind+ alterMinSkillTab = createTabWithKey cotile alterMinSkillKind+ alterMinWalkTab = createTabWithKey cotile alterMinWalkKind+ in TileSpeedup {..} --- | Whether a tile kind (specified by its id) has the given feature.-hasFeature :: Kind.Ops TileKind -> TK.Feature -> Kind.Id TileKind -> Bool-{-# INLINE hasFeature #-}-hasFeature Kind.Ops{okind} f t = kindHasFeature f (okind t)+-- Check that alter can be used, if not, @maxBound@.+-- For now, we assume only items with @Embed@ may have embedded items,+-- whether inserted at dungeon creation or later on.+-- This is used by UI and server to validate (sensibility of) altering.+-- See the comment for @alterMinWalkKind@ regarding @HideAs@.+alterMinSkillKind :: Kind.Id TileKind -> TileKind -> Word8+alterMinSkillKind _k tk =+ let getTo TK.OpenTo{} = True+ getTo TK.CloseTo{} = True+ getTo TK.ChangeTo{} = True+ getTo TK.HideAs{} = True -- in case tile swapped, but server sends hidden+ getTo TK.RevealAs{} = True+ getTo TK.ObscureAs{} = True+ getTo TK.Embed{} = True+ getTo TK.ConsideredByAI = True+ getTo _ = False+ in if any getTo $ TK.tfeature tk then TK.talter tk else maxBound +-- How high alter skill is needed to make it walkable. If already+-- walkable, put @0@, if can't, put @maxBound@. Used only be AI and Bfs+-- We don't include @HideAs@, because it's very unlikely anybody swapped+-- the tile while AI was not looking so AI can assume it's still uninteresting.+-- Pathfinding in UI will also not show such tile as passable, which is OK.+-- If a human player has a suspicion the tile was swapped, he can check+-- it manually, disregarding the displayed path hints.+alterMinWalkKind :: Kind.Id TileKind -> TileKind -> Word8+alterMinWalkKind k tk =+ let getTo TK.OpenTo{} = True+ getTo TK.RevealAs{} = True+ getTo TK.ObscureAs{} = True+ getTo _ = False+ in if | kindHasFeature TK.Walkable tk -> 0+ | isUknownSpace k -> TK.talter tk+ | any getTo $ TK.tfeature tk -> TK.talter tk+ | otherwise -> maxBound+ -- | Whether a tile does not block vision. -- Essential for efficiency of "FOV", hence tabulated. isClear :: TileSpeedup -> Kind.Id TileKind -> Bool@@ -112,11 +177,23 @@ {-# INLINE consideredByAI #-} consideredByAI TileSpeedup{consideredByAITab} = accessTab consideredByAITab +-- | Whether one can easily explore a tile, possibly finding a treasure,+-- either spawned there or dropped there by a (dying from poison) foe.+-- Doors can't be explorable since revealing a secret tile+-- should not change it's (walkable and) explorable status.+-- Door status should not depend on whether they are open or not+-- so that a foe opening a door doesn't force us to backtrack to explore it.+-- Still, a foe that digs through a wall will affect our exploration counter+-- and if content lets walls contain threasure, such backtraking makes sense.+isExplorable :: TileSpeedup -> Kind.Id TileKind -> Bool+isExplorable coTileSpeedup t =+ isWalkable coTileSpeedup t && not (isDoor coTileSpeedup t)+ isOftenItem :: TileSpeedup -> Kind.Id TileKind -> Bool {-# INLINE isOftenItem #-} isOftenItem TileSpeedup{isOftenItemTab} = accessTab isOftenItemTab -isOftenActor:: TileSpeedup -> Kind.Id TileKind -> Bool+isOftenActor :: TileSpeedup -> Kind.Id TileKind -> Bool {-# INLINE isOftenActor #-} isOftenActor TileSpeedup{isOftenActorTab} = accessTab isOftenActorTab @@ -144,88 +221,15 @@ alterMinWalk TileSpeedup{alterMinWalkTab} = fromEnum . accessTab alterMinWalkTab --- | Whether one can easily explore a tile, possibly finding a treasure,--- either spawned there or dropped there by a (dying from poison) foe.--- Doors can't be explorable since revealing a secret tile--- should not change it's (walkable and) explorable status.--- Door status should not depend on whether they are open or not--- so that a foe opening a door doesn't force us to backtrack to explore it.--- Still, a foe that digs through a wall will affect our exploration counter--- and if content lets walls contain threasure, such backtraking makes sense.-isExplorable :: TileSpeedup -> Kind.Id TileKind -> Bool-isExplorable coTileSpeedup t =- isWalkable coTileSpeedup t && not (isDoor coTileSpeedup t)--speedup :: Bool -> Kind.Ops TileKind -> TileSpeedup-speedup allClear cotile =- -- Vectors pack bools as Word8 by default. No idea if the extra memory- -- taken makes random lookups more or less efficient, so not optimizing- -- further, until I have benchmarks.- let isClearTab | allClear = createTab cotile- $ not . (== maxBound) . TK.talter- | otherwise = createTab cotile- $ kindHasFeature TK.Clear- isLitTab = createTab cotile $ not . kindHasFeature TK.Dark- isWalkableTab = createTab cotile $ kindHasFeature TK.Walkable- isDoorTab = createTab cotile $ \tk ->- let getTo TK.OpenTo{} = True- getTo TK.CloseTo{} = True- getTo _ = False- in any getTo $ TK.tfeature tk- isChangableTab = createTab cotile $ \tk ->- let getTo TK.ChangeTo{} = True- getTo _ = False- in any getTo $ TK.tfeature tk- isSuspectTab = createTab cotile TK.isSuspectKind- isHideAsTab = createTab cotile $ \tk ->- let getTo TK.HideAs{} = True- getTo _ = False- in any getTo $ TK.tfeature tk- consideredByAITab = createTab cotile $ kindHasFeature TK.ConsideredByAI- isOftenItemTab = createTab cotile $ kindHasFeature TK.OftenItem- isOftenActorTab = createTab cotile $ kindHasFeature TK.OftenActor- isNoItemTab = createTab cotile $ kindHasFeature TK.NoItem- isNoActorTab = createTab cotile $ kindHasFeature TK.NoActor- isEasyOpenTab = createTab cotile isEasyOpenKind- alterMinSkillTab = createTabWithKey cotile alterMinSkillKind- alterMinWalkTab = createTabWithKey cotile alterMinWalkKind- in TileSpeedup {..}---- Check that alter can be used, if not, @maxBound@.--- For now, we assume only items with @Embed@ may have embedded items,--- whether inserted at dungeon creation or later on.--- This is used by UI and server to validate (sensibility of) altering.--- See the comment for @alterMinWalkKind@ regarding @HideAs@.-alterMinSkillKind :: Kind.Id TileKind -> TileKind -> Word8-alterMinSkillKind _k tk =- let getTo TK.OpenTo{} = True- getTo TK.CloseTo{} = True- getTo TK.ChangeTo{} = True- getTo TK.HideAs{} = True -- in case tile swapped, but server sends hidden- getTo TK.RevealAs{} = True- getTo TK.ObscureAs{} = True- getTo TK.Embed{} = True- getTo TK.ConsideredByAI = True- getTo _ = False- in if any getTo $ TK.tfeature tk then TK.talter tk else maxBound+-- | Whether a tile kind has the given feature.+kindHasFeature :: TK.Feature -> TileKind -> Bool+{-# INLINE kindHasFeature #-}+kindHasFeature f t = f `elem` TK.tfeature t --- How high alter skill is needed to make it walkable. If already--- walkable, put @0@, if can't, put @maxBound@. Used only be AI and Bfs--- We don't include @HideAs@, because it's very unlikely anybody swapped--- the tile while AI was not looking so AI can assume it's still uninteresting.--- Pathfinding in UI will also not show such tile as passable, which is OK.--- If a human player has a suspicion the tile was swapped, he can check--- it manually, disregarding the displayed path hints.-alterMinWalkKind :: Kind.Id TileKind -> TileKind -> Word8-alterMinWalkKind k tk =- let getTo TK.OpenTo{} = True- getTo TK.RevealAs{} = True- getTo TK.ObscureAs{} = True- getTo _ = False- in if | kindHasFeature TK.Walkable tk -> 0- | isUknownSpace k -> TK.talter tk- | any getTo $ TK.tfeature tk -> TK.talter tk- | otherwise -> maxBound+-- | Whether a tile kind (specified by its id) has the given feature.+hasFeature :: Kind.Ops TileKind -> TK.Feature -> Kind.Id TileKind -> Bool+{-# INLINE hasFeature #-}+hasFeature Kind.Ops{okind} f t = kindHasFeature f (okind t) openTo :: Kind.Ops TileKind -> Kind.Id TileKind -> Rnd (Kind.Id TileKind) openTo Kind.Ops{okind, opick} t = do@@ -245,7 +249,7 @@ embeddedItems :: Kind.Ops TileKind -> Kind.Id TileKind -> [GroupName ItemKind] embeddedItems Kind.Ops{okind} t =- let getTo (TK.Embed eff) acc = eff : acc+ let getTo (TK.Embed igrp) acc = igrp : acc getTo _ acc = acc in foldr getTo [] $ TK.tfeature $ okind t @@ -269,13 +273,15 @@ grp <- oneOf groups fromMaybe (error $ "" `showFailure` grp) <$> opick grp (const True) -hideAs :: Kind.Ops TileKind -> Kind.Id TileKind -> Kind.Id TileKind+hideAs :: Kind.Ops TileKind -> Kind.Id TileKind -> Maybe (Kind.Id TileKind) hideAs Kind.Ops{okind, ouniqGroup} t = let getTo TK.HideAs{} = True getTo _ = False in case find getTo $ TK.tfeature $ okind t of- Just (TK.HideAs grp) -> ouniqGroup grp- _ -> t+ Just (TK.HideAs grp) ->+ let tHidden = ouniqGroup grp+ in assert (tHidden /= t) $ Just tHidden+ _ -> Nothing buildAs :: Kind.Ops TileKind -> Kind.Id TileKind -> Kind.Id TileKind buildAs Kind.Ops{okind, ouniqGroup} t =
Game/LambdaHack/Common/Time.hs view
@@ -1,25 +1,29 @@ {-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving #-} -- | Game time and speed. module Game.LambdaHack.Common.Time- ( Time, timeZero, timeClip, timeTurn, timeSecond, timeEpsilon+ ( Time, timeZero, timeEpsilon, timeClip, timeTurn, timeSecond , absoluteTimeAdd, absoluteTimeSubtract, absoluteTimeNegate , timeFit, timeFitUp , Delta(..), timeShift, timeDeltaToFrom , timeDeltaSubtract, timeDeltaReverse, timeDeltaScale, timeDeltaPercent- , timeDeltaToDigit, ticksPerMeter+ , timeDeltaDiv, timeDeltaToDigit , Speed, toSpeed, fromSpeed , speedZero, speedWalk, speedLimp, speedThrust, modifyDamageBySpeed- , speedScale, timeDeltaDiv, speedAdd, speedNegate- , speedFromWeight, rangeFromSpeedAndLinger+ , speedScale, speedAdd, speedNegate+ , ticksPerMeter, speedFromWeight, rangeFromSpeedAndLinger+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , _timeTick, turnsInSecond, sInMs, minimalSpeed, rangeFromSpeed+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.Char as Char-import Data.Int (Int64)+import Data.Int (Int64) -- | Game time in ticks. The time dimension. -- One tick is 1 microsecond (one millionth of a second),@@ -27,17 +31,12 @@ newtype Time = Time {timeTicks :: Int64} deriving (Show, Eq, Ord, Enum, Bounded, Binary) --- | One-dimentional vectors. Introduced to tell apart the 2 uses of Time:--- as an absolute game time and as an increment.-newtype Delta a = Delta a- deriving (Show, Eq, Ord, Enum, Bounded, Binary, Functor)- -- | Start of the game time, or zero lenght time interval. timeZero :: Time timeZero = Time 0 --- | The smallest unit of time. Do not export, because the proportion--- of turn to tick is an implementation detail.+-- | The smallest unit of time. Should not be exported and used elsewhere,+-- because the proportion of turn to tick is an implementation detail. -- The significance of this detail is only that it determines resolution -- of the time dimension. _timeTick :: Time@@ -77,10 +76,11 @@ {-# INLINE absoluteTimeSubtract #-} absoluteTimeSubtract (Time t1) (Time t2) = Time (t1 - t2) --- | Shifting an absolute time by a time vector.-timeShift :: Time -> Delta Time -> Time-{-# INLINE timeShift #-}-timeShift (Time t1) (Delta (Time t2)) = Time (t1 + t2)+-- | Absolute time negation. To be used for reversing time flow,+-- e.g., for comparing absolute times in the reverse order.+absoluteTimeNegate :: Time -> Time+{-# INLINE absoluteTimeNegate #-}+absoluteTimeNegate (Time t) = Time (-t) -- | How many time intervals of the latter kind fits in an interval -- of the former kind.@@ -94,16 +94,15 @@ {-# INLINE timeFitUp #-} timeFitUp (Time t1) (Time t2) = fromEnum $ t1 `divUp` t2 --- | Reverse a time vector.-timeDeltaReverse :: Delta Time -> Delta Time-{-# INLINE timeDeltaReverse #-}-timeDeltaReverse (Delta (Time t)) = Delta (Time (-t))+-- | One-dimentional vectors. Introduced to tell apart the 2 uses of Time:+-- as an absolute game time and as an increment.+newtype Delta a = Delta a+ deriving (Show, Eq, Ord, Enum, Bounded, Binary, Functor) --- | Absolute time negation. To be used for reversing time flow,--- e.g., for comparing absolute times in the reverse order.-absoluteTimeNegate :: Time -> Time-{-# INLINE absoluteTimeNegate #-}-absoluteTimeNegate (Time t) = Time (-t)+-- | Shifting an absolute time by a time vector.+timeShift :: Time -> Delta Time -> Time+{-# INLINE timeShift #-}+timeShift (Time t1) (Delta (Time t2)) = Time (t1 + t2) -- | Time time vector between the second and the first absolute times. -- The arguments are in the same order as in the underlying scalar subtraction.@@ -117,6 +116,11 @@ {-# INLINE timeDeltaSubtract #-} timeDeltaSubtract (Delta (Time t1)) (Delta (Time t2)) = Delta $ Time (t1 - t2) +-- | Reverse a time vector.+timeDeltaReverse :: Delta Time -> Delta Time+{-# INLINE timeDeltaReverse #-}+timeDeltaReverse (Delta (Time t)) = Delta (Time (-t))+ -- | Scale the time vector by an @Int@ scalar value. timeDeltaScale :: Delta Time -> Int -> Delta Time {-# INLINE timeDeltaScale #-}@@ -162,15 +166,15 @@ {-# INLINE toSpeed #-} toSpeed s = Speed $ fromIntegral s * sInMs `div` 10 --- Can't be lower or actors would slow down (via tmp organs and weight),--- boost time with InsertMove, speed up and have lots of free moves.-minimalSpeed :: Int64-minimalSpeed = sInMs `div` 10- -- | Pretty-printing of speed in the format used in content definitions. fromSpeed :: Speed -> Int {-# INLINE fromSpeed #-} fromSpeed (Speed s) = fromEnum $ s * 10 `div` sInMs++-- Can't be lower or actors would slow down (via tmp organs and weight),+-- boost time with InsertMove, speed up and have lots of free moves.+minimalSpeed :: Int64+minimalSpeed = sInMs `div` 10 -- | No movement possible at that speed. speedZero :: Speed
Game/LambdaHack/Common/Vector.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE DeriveGeneric #-}--- | Basic operations on 2D vectors represented in an efficient,--- but not unique, way.+-- | Basic operations on bounded 2D vectors, with an efficient, but not 1-1+-- and not monotonic @Enum@ instance. module Game.LambdaHack.Common.Vector ( Vector(..), isUnit, isDiagonal, neg, chessDistVector, euclidDistSqVector , moves, movesCardinal, movesDiagonal, compassText@@ -9,17 +9,21 @@ , shift, shiftBounded, trajectoryToPath, trajectoryToPathBounded , vectorToFrom, pathToTrajectory , RadianAngle, rotate, towards+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , maxVectorDim, _moveTexts, longMoveTexts, normalize, normalizeVector+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.DeepSeq-import Data.Binary+import Control.DeepSeq+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Int (Int32)+import Data.Int (Int32) import GHC.Generics (Generic) @@ -73,17 +77,17 @@ {-# INLINE neg #-} neg (Vector vx vy) = Vector (-vx) (-vy) --- | Squared euclidean distance between two vectors.-euclidDistSqVector :: Vector -> Vector -> Int-euclidDistSqVector (Vector x0 y0) (Vector x1 y1) =- (x1 - x0) ^ (2 :: Int) + (y1 - y0) ^ (2 :: Int)- -- | The lenght of a vector in the chessboard metric, -- where diagonal moves cost 1. chessDistVector :: Vector -> Int {-# INLINE chessDistVector #-} chessDistVector (Vector x y) = max (abs x) (abs y) +-- | Squared euclidean distance between two vectors.+euclidDistSqVector :: Vector -> Vector -> Int+euclidDistSqVector (Vector x0 y0) (Vector x1 y1) =+ (x1 - x0) ^ (2 :: Int) + (y1 - y0) ^ (2 :: Int)+ -- | Vectors of all unit moves in the chessboard metric, -- clockwise, starting north-west. moves :: [Vector]@@ -91,6 +95,15 @@ map (uncurry Vector) [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)] +-- | Vectors of all cardinal direction unit moves, clockwise, starting north.+movesCardinal :: [Vector]+movesCardinal = map (uncurry Vector) [(0, -1), (1, 0), (0, 1), (-1, 0)]++-- | Vectors of all diagonal direction unit moves, clockwise, starting north.+movesDiagonal :: [Vector]+movesDiagonal = map (uncurry Vector) [(-1, -1), (1, -1), (1, 1), (-1, 1)]++-- | Currently unused. _moveTexts :: [Text] _moveTexts = ["NW", "N", "NE", "E", "SE", "S", "SW", "W"] @@ -102,14 +115,6 @@ compassText v = let m = EM.fromList $ zip moves longMoveTexts assFail = error $ "not a unit vector" `showFailure` v in EM.findWithDefault assFail v m---- | Vectors of all cardinal direction unit moves, clockwise, starting north.-movesCardinal :: [Vector]-movesCardinal = map (uncurry Vector) [(0, -1), (1, 0), (0, 1), (-1, 0)]---- | Vectors of all diagonal direction unit moves, clockwise, starting north.-movesDiagonal :: [Vector]-movesDiagonal = map (uncurry Vector) [(-1, -1), (1, -1), (1, 1), (-1, 1)] -- | All (8 at most) closest neighbours of a point within an area. vicinity :: X -> Y -- ^ limit the search to this area
Game/LambdaHack/Content/CaveKind.hs view
@@ -1,4 +1,4 @@--- | The type of cave layout kinds.+-- | The type of cave kinds. module Game.LambdaHack.Content.CaveKind ( CaveKind(..), validateSingleCaveKind, validateAllCaveKind ) where@@ -10,41 +10,42 @@ import qualified Data.Text as T import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Content.ItemKind (ItemKind)-import Game.LambdaHack.Content.PlaceKind-import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.PlaceKind+import Game.LambdaHack.Content.TileKind (TileKind) -- | Parameters for the generation of dungeon levels. -- Warning: for efficiency, avoid embedded items in any of the common tiles. data CaveKind = CaveKind- { csymbol :: Char -- ^ a symbol- , cname :: Text -- ^ short description- , cfreq :: Freqs CaveKind -- ^ frequency within groups- , cxsize :: X -- ^ X size of the whole cave- , cysize :: Y -- ^ Y size of the whole cave- , cgrid :: Dice.DiceXY -- ^ the dimensions of the grid of places- , cminPlaceSize :: Dice.DiceXY -- ^ minimal size of places; for merging- , cmaxPlaceSize :: Dice.DiceXY -- ^ maximal size of places- , cdarkChance :: Dice.Dice -- ^ the chance a place is dark- , cnightChance :: Dice.Dice -- ^ the chance the cave is dark- , cauxConnects :: Rational -- ^ a proportion of extra connections- , cmaxVoid :: Rational -- ^ at most this proportion of rooms void- , cminStairDist :: Int -- ^ minimal distance between stairs- , cextraStairs :: Dice.Dice -- ^ extra stairs on top of from above- , cdoorChance :: Chance -- ^ the chance of a door in an opening- , copenChance :: Chance -- ^ if there's a door, is it open?- , chidden :: Int -- ^ if not open, hidden one in n times- , cactorCoeff :: Int -- ^ the lower, the more monsters spawn- , cactorFreq :: Freqs ItemKind -- ^ actor groups to consider- , citemNum :: Dice.Dice -- ^ the number of items in the cave- , citemFreq :: Freqs ItemKind- -- ^ item groups to consider- , cplaceFreq :: Freqs PlaceKind- -- ^ place groups to consider- , cpassable :: Bool -- ^ are passable default tiles permitted+ { csymbol :: Char -- ^ a symbol+ , cname :: Text -- ^ short description+ , cfreq :: Freqs CaveKind -- ^ frequency within groups+ , cxsize :: X -- ^ X size of the whole cave+ , cysize :: Y -- ^ Y size of the whole cave+ , cgrid :: Dice.DiceXY+ -- ^ the dimensions of the grid of places+ , cminPlaceSize :: Dice.DiceXY -- ^ minimal size of places; for merging+ , cmaxPlaceSize :: Dice.DiceXY -- ^ maximal size of places+ , cdarkChance :: Dice.Dice -- ^ the chance a place is dark+ , cnightChance :: Dice.Dice -- ^ the chance the cave is dark+ , cauxConnects :: Rational -- ^ a proportion of extra connections+ , cmaxVoid :: Rational+ -- ^ at most this proportion of rooms may be void+ , cminStairDist :: Int -- ^ minimal distance between stairs+ , cextraStairs :: Dice.Dice -- ^ extra stairs on top of from above+ , cdoorChance :: Chance -- ^ the chance of a door in an opening+ , copenChance :: Chance -- ^ if there's a door, is it open?+ , chidden :: Int -- ^ if not open, hidden one in n times+ , cactorCoeff :: Int -- ^ the lower, the more monsters spawn+ , cactorFreq :: Freqs ItemKind -- ^ actor groups to consider+ , citemNum :: Dice.Dice -- ^ the number of items in the cave+ , citemFreq :: Freqs ItemKind -- ^ item groups to consider+ , cplaceFreq :: Freqs PlaceKind -- ^ place groups to consider+ , cpassable :: Bool+ -- ^ are passable default tiles permitted , cdefTile :: GroupName TileKind -- ^ the default cave tile , cdarkCorTile :: GroupName TileKind -- ^ the dark cave corridor tile , clitCorTile :: GroupName TileKind -- ^ the lit cave corridor tile@@ -56,6 +57,7 @@ , cstairFreq :: Freqs PlaceKind -- ^ place groups to consider for stairs; in this case the rarity -- of items in the group does not affect group choice+ , cdesc :: Text -- ^ cave description } deriving Show -- No Eq and Ord to make extending it logically sound @@ -83,10 +85,10 @@ | maxGridX * (maxMinSizeX - 4) + xborder >= cxsize ] ++ [ "cysize too small" | maxGridY * maxMinSizeY + yborder >= cysize ]- ++ [ "cextraStairs < 0" | cextraStairs < 0 ]+ ++ [ "cextraStairs < 0" | Dice.minDice cextraStairs < 0 ] ++ [ "chidden < 0" | chidden < 0 ] ++ [ "cactorCoeff < 0" | cactorCoeff < 0 ]- ++ [ "citemNum < 0" | citemNum < 0 ]+ ++ [ "citemNum < 0" | Dice.minDice citemNum < 0 ] -- | Validate all cave kinds. -- Note that names don't have to be unique: we can have several variants
Game/LambdaHack/Content/ItemKind.hs view
@@ -1,92 +1,106 @@ {-# LANGUAGE DeriveGeneric #-}--- | The type of kinds of weapons, treasure, organs, blasts and actors.+-- | The type of kinds of weapons, treasure, organs, blasts, etc. module Game.LambdaHack.Content.ItemKind ( ItemKind(..) , Effect(..), TimerDice(..) , Aspect(..), ThrowMod(..) , Feature(..), EqpSlot(..)- , forApplyEffect, forIdEffect+ , boostItemKindList, forApplyEffect, forIdEffect , toDmg, toVelocity, toLinger, toOrganGameTurn, toOrganActorTurn, toOrganNone , validateSingleItemKind, validateAllItemKind+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , boostItemKind, validateDups, validateDamage, hardwiredItemGroups+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.DeepSeq-import Data.Binary-import Data.Hashable (Hashable)+import Control.DeepSeq+import Data.Binary+import Data.Hashable (Hashable) import qualified Data.Set as S import qualified Data.Text as T-import GHC.Generics (Generic)+import GHC.Generics (Generic) import qualified NLP.Miniutter.English as MU+import qualified System.Random as R import qualified Game.LambdaHack.Common.Ability as Ability import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Flavour-import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Misc -- | Item properties that are fixed for a given kind of items. data ItemKind = ItemKind- { isymbol :: Char -- ^ map symbol- , iname :: Text -- ^ generic name- , ifreq :: Freqs ItemKind -- ^ frequency within groups- , iflavour :: [Flavour] -- ^ possible flavours- , icount :: Dice.Dice -- ^ created in that quantity- , irarity :: Rarity -- ^ rarity on given depths- , iverbHit :: MU.Part -- ^ the verb&noun for applying and hit- , iweight :: Int -- ^ weight in grams+ { isymbol :: Char -- ^ map symbol+ , iname :: Text -- ^ generic name+ , ifreq :: Freqs ItemKind -- ^ frequency within groups+ , iflavour :: [Flavour] -- ^ possible flavours+ , icount :: Dice.Dice -- ^ created in that quantity+ , irarity :: Rarity -- ^ rarity on given depths+ , iverbHit :: MU.Part -- ^ the verb&noun for applying and hit+ , iweight :: Int -- ^ weight in grams , idamage :: [(Int, Dice.Dice)] -- ^ frequency of basic impact damage- , iaspects :: [Aspect] -- ^ keep the aspect continuously- , ieffects :: [Effect] -- ^ cause the effect when triggered- , ifeature :: [Feature] -- ^ public properties- , idesc :: Text -- ^ description+ , iaspects :: [Aspect] -- ^ keep the aspect continuously+ , ieffects :: [Effect] -- ^ cause the effect when triggered+ , ifeature :: [Feature] -- ^ public properties+ , idesc :: Text -- ^ description , ikit :: [(GroupName ItemKind, CStore)]- -- ^ accompanying organs and items+ -- ^ accompanying organs and items } deriving Show -- No Eq and Ord to make extending it logically sound -- | Effects of items. Can be invoked by the item wielder to affect -- another actor or the wielder himself. Many occurences in the same item--- are possible. Constructors are sorted vs increasing impact/danger.+-- are possible. data Effect =- -- Ordinary effects.- ELabel Text -- ^ secret (learned as effect) label of the item+ ELabel Text -- ^ secret (learned as effect) name of the item | EqpSlot EqpSlot -- ^ AI and UI flag that leaks item properties- | Burn Dice.Dice+ | Burn Dice.Dice -- ^ burn with this damage | Explode (GroupName ItemKind)- -- ^ explode, producing this group of blasts- | RefillHP Int- | RefillCalm Int- | Dominate- | Impress+ -- ^ explode producing this group of blasts+ | RefillHP Int -- ^ modify HP of the actor by this amount+ | RefillCalm Int -- ^ modify Calm of the actor by this amount+ | Dominate -- ^ change actor's allegiance+ | Impress -- ^ make actor susceptible to domination | Summon (GroupName ItemKind) Dice.Dice- | Ascend Bool- | Escape- | Paralyze Dice.Dice -- ^ expressed in game clips- | InsertMove Dice.Dice -- ^ expressed in game turns- | Teleport Dice.Dice+ -- ^ summon the given number of actors of this group+ | Ascend Bool -- ^ ascend to another level of the dungeon+ | Escape -- ^ escape from the dungeon+ | Paralyze Dice.Dice -- ^ paralyze for this many game clips+ | InsertMove Dice.Dice -- ^ give free time to actor of this many game turns+ | Teleport Dice.Dice -- ^ teleport actor across rougly this distance | CreateItem CStore (GroupName ItemKind) TimerDice -- ^ create an item of the group and insert into the store with the given -- random timer | DropItem Int Int CStore (GroupName ItemKind)+ -- ^ make the actor drop items of the given group from the given store;+ -- the first integer says how many item kinds to drop, the second,+ -- how many copie of each kind to drop | PolyItem+ -- ^ find a suitable (i.e., numerous enough) item, starting from+ -- the floor, and polymorph it randomly | Identify- | Detect Int- | DetectActor Int- | DetectItem Int- | DetectExit Int- | DetectHidden Int- | SendFlying ThrowMod- | PushActor ThrowMod- | PullActor ThrowMod- | DropBestWeapon- | ActivateInv Char -- ^ symbol @' '@ means all- | ApplyPerfume- -- Exotic effects follow.- | OneOf [Effect]- | OnSmash Effect -- ^ trigger if item smashed (not applied nor meleed)+ -- ^ find a suitable (i.e., not identified) item, starting from+ -- the floor, and identify it+ | Detect Int -- ^ detect all on the map in the given radius+ | DetectActor Int -- ^ detect actors on the map in the given radius+ | DetectItem Int -- ^ detect items on the map in the given radius+ | DetectExit Int -- ^ detect exits on the map in the given radius+ | DetectHidden Int -- ^ detect hidden tiles on the map in the radius+ | SendFlying ThrowMod -- ^ send an actor flying (push or pull, depending)+ | PushActor ThrowMod -- ^ push an actor+ | PullActor ThrowMod -- ^ pull an actor+ | DropBestWeapon -- ^ make the actor drop its best weapon+ | ActivateInv Char+ -- ^ activate all items with this symbol in inventory; space character+ -- means all symbols+ | ApplyPerfume -- ^ remove all smell on the level+ | OneOf [Effect] -- ^ trigger one of the effects with equal probability+ | OnSmash Effect+ -- ^ trigger the effect when item smashed (not when applied nor meleed) | Recharging Effect -- ^ this effect inactive until timeout passes | Temporary Text -- ^ the item is temporary, vanishes at even void Periodic activation,@@ -94,36 +108,20 @@ -- this verb at last copy activation or at each activation -- unless Durable and Fragile | Unique -- ^ at most one copy can ever be generated- | Periodic -- ^ in eqp, triggered as often as @Timeout@ permits- deriving (Show, Eq, Ord, Generic)+ | Periodic+ -- ^ in equipment, triggered as often as @Timeout@ permits+ | Composite [Effect] -- ^ only fire next effect if previous was triggered+ deriving (Show, Eq, Generic) instance NFData Effect -forApplyEffect :: Effect -> Bool-forApplyEffect eff = case eff of- ELabel{} -> False- EqpSlot{} -> False- OnSmash{} -> False- Temporary{} -> False- Unique -> False- Periodic -> False- _ -> True--forIdEffect :: Effect -> Bool-forIdEffect eff = case eff of- ELabel{} -> False- EqpSlot{} -> False- OnSmash{} -> False- Explode{} -> False -- tentative; needed for rings to auto-identify- Unique -> False- Periodic -> False- _ -> True-+-- | Specification of how to randomly roll a timer at item creation+-- to obtain a fixed timer for the item's lifetime. data TimerDice = TimerNone | TimerGameTurn Dice.Dice | TimerActorTurn Dice.Dice- deriving (Eq, Ord, Generic)+ deriving (Eq, Generic) instance Show TimerDice where show TimerNone = "0"@@ -144,12 +142,12 @@ | AddArmorRanged Dice.Dice -- ^ percentage armor bonus against ranged | AddMaxHP Dice.Dice -- ^ maximal hp | AddMaxCalm Dice.Dice -- ^ maximal calm- | AddSpeed Dice.Dice -- ^ speed in m/10s (not of a projectile)- | AddSight Dice.Dice -- ^ FOV radius, where 1 means a single tile- | AddSmell Dice.Dice -- ^ smell radius, where 1 means a single tile- | AddShine Dice.Dice -- ^ shine radius, where 1 means a single tile- | AddNocto Dice.Dice -- ^ noctovision radius, where 1 is single tile- | AddAggression Dice.Dice -- ^ aggression, especially closing in for melee+ | AddSpeed Dice.Dice -- ^ speed in m/10s (not when pushed or pulled)+ | AddSight Dice.Dice -- ^ FOV radius, where 1 means a single tile FOV+ | AddSmell Dice.Dice -- ^ smell radius+ | AddShine Dice.Dice -- ^ shine radius+ | AddNocto Dice.Dice -- ^ noctovision radius+ | AddAggression Dice.Dice -- ^ aggression, e.g., when closing in for melee | AddAbility Ability.Ability Dice.Dice -- ^ bonus to an ability deriving (Show, Eq, Ord, Generic) @@ -163,8 +161,8 @@ instance NFData ThrowMod --- | Features of item. Affect only the item in question, not the actor,--- and so not additive in any sense.+-- | Features of item. Publicly visible. Affect only the item in question,+-- not the actor, and so not additive in any sense. data Feature = Fragile -- ^ drop and break at target tile, even if no hit | Lobable -- ^ drop at target tile, even if no hit@@ -173,13 +171,14 @@ | Identified -- ^ the item starts identified | Applicable -- ^ AI and UI flag: consider applying | Equipable -- ^ AI and UI flag: consider equipping (independent of- -- ^ 'EqpSlot', e.g., in case of mixed blessings)+ -- 'EqpSlot', e.g., in case of mixed blessings) | Meleeable -- ^ AI and UI flag: consider meleeing with- | Precious -- ^ AI and UI flag: don't risk identifying by use- -- also, can't throw or apply if not calm enough;+ | Precious -- ^ AI and UI flag: don't risk identifying by use;+ -- also, can't throw or apply if not calm enough | Tactic Tactic -- ^ overrides actor's tactic deriving (Show, Eq, Ord, Generic) +-- | AI and UI hints about the role of the item. data EqpSlot = EqpSlotMiscBonus | EqpSlotAddHurtMelee@@ -232,6 +231,48 @@ instance Binary EqpSlot +boostItemKindList :: R.StdGen -> [ItemKind] -> [ItemKind]+boostItemKindList _ [] = []+boostItemKindList initialGen l =+ let (r, _) = R.randomR (0, length l - 1) initialGen+ in case splitAt r l of+ (pre, i : post) -> pre ++ boostItemKind i : post+ _ -> error $ "" `showFailure` l++boostItemKind :: ItemKind -> ItemKind+boostItemKind i =+ let mainlineLabel (label, _) = label `elem` ["useful", "treasure"]+ in if any mainlineLabel (ifreq i)+ then i { ifreq = ("useful", 10000)+ : filter (not . mainlineLabel) (ifreq i)+ , ieffects = delete Unique $ ieffects i+ }+ else i++-- | Whether the effect has a chance of exhibiting any potentially+-- noticeable behaviour.+forApplyEffect :: Effect -> Bool+forApplyEffect eff = case eff of+ ELabel{} -> False+ EqpSlot{} -> False+ OnSmash{} -> False+ Temporary{} -> False+ Unique -> False+ Periodic -> False+ Composite effs -> any forApplyEffect effs+ _ -> True++forIdEffect :: Effect -> Bool+forIdEffect eff = case eff of+ ELabel{} -> False+ EqpSlot{} -> False+ OnSmash{} -> False+ Explode{} -> False -- tentative; needed for rings to auto-identify+ Unique -> False+ Periodic -> False+ Composite (eff1 : _) -> forIdEffect eff1 -- the rest may never fire+ _ -> True+ toDmg :: Dice.Dice -> [(Int, Dice.Dice)] toDmg dmg = [(1, dmg)] @@ -254,7 +295,7 @@ validateSingleItemKind :: ItemKind -> [Text] validateSingleItemKind ik@ItemKind{..} = [ "iname longer than 23" | T.length iname > 23 ]- ++ [ "icount < 0" | icount < 0 ]+ ++ [ "icount < 0" | Dice.minDice icount < 0 ] ++ validateRarity irarity ++ validateDamage idamage -- Reject duplicate Timeout, because it's not additive.
Game/LambdaHack/Content/ModeKind.hs view
@@ -1,36 +1,41 @@ {-# LANGUAGE DeriveGeneric #-} -- | The type of kinds of game modes. module Game.LambdaHack.Content.ModeKind- ( Caves, Roster(..), Player(..), ModeKind(..), LeaderMode(..), AutoLeader(..)- , Outcome(..), HiIndeterminant(..), HiCondPoly, HiSummand, HiPolynomial+ ( ModeKind(..), Caves, Roster(..), Outcome(..)+ , HiCondPoly, HiSummand, HiPolynomial, HiIndeterminant(..)+ , Player(..), LeaderMode(..), AutoLeader(..) , validateSingleModeKind, validateAllModeKind+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , validateSingleRoster, validateSinglePlayer, hardwiredModeGroups+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.IntMap.Strict as IM import qualified Data.Set as S import qualified Data.Text as T-import GHC.Generics (Generic)+import GHC.Generics (Generic) -import Game.LambdaHack.Common.Ability+import Game.LambdaHack.Common.Ability import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Content.CaveKind-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Content.CaveKind+import Game.LambdaHack.Content.ItemKind (ItemKind) -- | Game mode specification. data ModeKind = ModeKind- { msymbol :: Char -- ^ a symbol- , mname :: Text -- ^ short description+ { msymbol :: Char -- ^ a symbol+ , mname :: Text -- ^ short description , mfreq :: Freqs ModeKind -- ^ frequency within groups- , mroster :: Roster -- ^ players taking part in the game- , mcaves :: Caves -- ^ arena of the game- , mdesc :: Text -- ^ description+ , mroster :: Roster -- ^ players taking part in the game+ , mcaves :: Caves -- ^ arena of the game+ , mdesc :: Text -- ^ description } deriving Show @@ -47,7 +52,7 @@ , rosterEnemy :: [(Text, Text)] -- ^ the initial enmity matrix , rosterAlly :: [(Text, Text)] -- ^ the initial aliance matrix }- deriving (Show, Eq)+ deriving Show -- | Outcome of a game. data Outcome =@@ -61,39 +66,39 @@ instance Binary Outcome -data HiIndeterminant = HiConst | HiLoot | HiBlitz | HiSurvival | HiKill | HiLoss- deriving (Show, Eq, Ord, Generic)+-- | Conditional polynomial representing score calculation for this player.+type HiCondPoly = [HiSummand] -instance Binary HiIndeterminant+type HiSummand = (HiPolynomial, [Outcome]) type HiPolynomial = [(HiIndeterminant, Double)] -type HiSummand = (HiPolynomial, [Outcome])+data HiIndeterminant = HiConst | HiLoot | HiBlitz | HiSurvival | HiKill | HiLoss+ deriving (Show, Eq, Ord, Generic) --- | Conditional polynomial representing score calculation for this player.-type HiCondPoly = [HiSummand]+instance Binary HiIndeterminant -- | Properties of a particular player. data Player = Player { fname :: Text -- ^ name of the player , fgroups :: [GroupName ItemKind]- -- ^ names of actor groups that may naturally- -- fall under player's control, e.g., upon- -- spawning or summoning+ -- ^ names of actor groups that may naturally+ -- fall under player's control, e.g., upon+ -- spawning or summoning , fskillsOther :: Skills -- ^ fixed skill modifiers to the non-leader- -- actors; also summed with skills implied- -- by ftactic (which is not fixed)+ -- actors; also summed with skills implied+ -- by ftactic (which is not fixed) , fcanEscape :: Bool -- ^ the player can escape the dungeon , fneverEmpty :: Bool -- ^ the faction declared killed if no actors , fhiCondPoly :: HiCondPoly -- ^ score polynomial for the player , fhasGender :: Bool -- ^ whether actors have gender , ftactic :: Tactic -- ^ non-leaders behave according to this- -- tactic; can be changed during the game+ -- tactic; can be changed during the game , fleaderMode :: LeaderMode -- ^ the mode of switching the leader , fhasUI :: Bool -- ^ does the faction have a UI client- -- (for control or passive observation)+ -- (for control or passive observation) }- deriving (Show, Eq, Ord, Generic)+ deriving (Show, Eq, Generic) instance Binary Player
Game/LambdaHack/Content/RuleKind.hs view
@@ -13,13 +13,8 @@ -- | The type of game rule sets and assorted game data. ----- For now the rules are immutable througout the game, so there is--- no type @Rule@ to hold any changing parameters, just @RuleKind@--- for the fixed set.--- However, in the future, if the rules can get changed during gameplay--- based on data mining of player behaviour, we may add such a type--- and then @RuleKind@ will become just a starting template, analogously--- as for the other content.+-- In principle, it'se possible to have many rule sets+-- and switch between them during a game session or even a single game. data RuleKind = RuleKind { rsymbol :: Char -- ^ a symbol , rname :: Text -- ^ short description@@ -39,8 +34,7 @@ } -- | A dummy instance of the 'Show' class, to satisfy general requirments--- about content. We won't have many rule sets and they contain functions,--- so defining a proper instance is not practical.+-- about content. We won't don't expect to ever print out whole rule sets. instance Show RuleKind where show _ = "The game ruleset specification."
Game/LambdaHack/Content/TileKind.hs view
@@ -1,31 +1,35 @@ {-# LANGUAGE DeriveGeneric #-} -- | The type of kinds of terrain tiles. module Game.LambdaHack.Content.TileKind- ( TileKind(..), Feature(..)+ ( TileKind(..), Feature(..), TileSpeedup(..), Tab(..) , validateSingleTileKind, validateAllTileKind, actionFeatures- , TileSpeedup(..), Tab(..), isUknownSpace, unknownId- , isSuspectKind, isOpenableKind, isClosableKind, talterForStairs, floorSymbol+ , isUknownSpace, unknownId, isSuspectKind, isOpenableKind, isClosableKind+ , talterForStairs, floorSymbol+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , validateDups, hardwiredTileGroups+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.DeepSeq-import Data.Binary+import Control.DeepSeq+import Data.Binary import qualified Data.Char as Char-import Data.Hashable+import Data.Hashable import qualified Data.IntSet as IS import qualified Data.Map.Strict as M import qualified Data.Set as S import qualified Data.Text as T import qualified Data.Vector.Unboxed as U-import GHC.Generics (Generic)+import GHC.Generics (Generic) -import Game.LambdaHack.Common.Color+import Game.LambdaHack.Common.Color import qualified Game.LambdaHack.Common.KindOps as KindOps-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Content.ItemKind (ItemKind) -- | The type of kinds of terrain tiles. See @Tile.hs@ for explanation -- of the absence of a corresponding type @Tile@ that would hold@@ -60,19 +64,15 @@ -- ^ alters tile, but does not change walkability | HideAs (GroupName TileKind) -- ^ when hidden, looks as the unique tile of the group-- -- The following three are only used in dungeon generation. | BuildAs (GroupName TileKind) -- ^ when generating, may be transformed to the unique tile of the group | RevealAs (GroupName TileKind) -- ^ when generating in opening, can be revealed to belong to the group | ObscureAs (GroupName TileKind) -- ^ when generating in solid wall, can be revealed to belong to the group- | Walkable -- ^ actors can walk through | Clear -- ^ actors can see through | Dark -- ^ is not lit with an ambient light- | OftenItem -- ^ initial items often generated there | OftenActor -- ^ initial actors often generated there | NoItem -- ^ no items ever generated there@@ -118,14 +118,6 @@ -- indexing. Also, in JS bool arrays are obviously not packed. newtype Tab a = Tab (U.Vector a) -- morally indexed by @Id a@ -isUknownSpace :: KindOps.Id TileKind -> Bool-{-# INLINE isUknownSpace #-}-isUknownSpace tt = KindOps.Id 0 == tt--unknownId :: KindOps.Id TileKind-{-# INLINE unknownId #-}-unknownId = KindOps.Id 0- -- | Validate a single tile kind. validateSingleTileKind :: TileKind -> [Text] validateSingleTileKind t@TileKind{..} =@@ -171,25 +163,6 @@ let ts = filter (== feat) tfeature in ["more than one" <+> tshow feat <+> "specification" | length ts > 1] -isSuspectKind :: TileKind -> Bool-isSuspectKind t =- let getTo RevealAs{} = True- getTo ObscureAs{} = True- getTo _ = False- in any getTo $ tfeature t--isOpenableKind ::TileKind -> Bool-isOpenableKind t =- let getTo OpenTo{} = True- getTo _ = False- in any getTo $ tfeature t--isClosableKind :: TileKind -> Bool-isClosableKind t =- let getTo CloseTo{} = True- getTo _ = False- in any getTo $ tfeature t- -- | Validate all tile kinds. -- -- If tiles look the same on the map (symbol and color), their substantial@@ -265,6 +238,33 @@ Trail -> Just feat -- doesn't affect tile behaviour, but important Spice -> Nothing in IS.fromList $ map hash $ mapMaybe f $ tfeature t++isUknownSpace :: KindOps.Id TileKind -> Bool+{-# INLINE isUknownSpace #-}+isUknownSpace tt = KindOps.Id 0 == tt++unknownId :: KindOps.Id TileKind+{-# INLINE unknownId #-}+unknownId = KindOps.Id 0++isSuspectKind :: TileKind -> Bool+isSuspectKind t =+ let getTo RevealAs{} = True+ getTo ObscureAs{} = True+ getTo _ = False+ in any getTo $ tfeature t++isOpenableKind ::TileKind -> Bool+isOpenableKind t =+ let getTo OpenTo{} = True+ getTo _ = False+ in any getTo $ tfeature t++isClosableKind :: TileKind -> Bool+isClosableKind t =+ let getTo CloseTo{} = True+ getTo _ = False+ in any getTo $ tfeature t talterForStairs :: Word8 talterForStairs = 3
Game/LambdaHack/SampleImplementation/SampleMonadClient.hs view
@@ -1,8 +1,6 @@-{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}--- | The main game action monad type implementation. Just as any other+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+-- | The implementation of our custom game client monads. Just as any other -- component of the library, this implementation can be substituted.--- This module should not be imported anywhere except in 'Action'--- to expose the executor to any code using the library. module Game.LambdaHack.SampleImplementation.SampleMonadClient ( executorCli #ifdef EXPOSE_INTERNAL@@ -15,34 +13,35 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent+import Control.Concurrent import qualified Control.Monad.IO.Class as IO-import Control.Monad.Trans.State.Strict hiding (State)-import GHC.Generics (Generic)+import Control.Monad.Trans.State.Strict hiding (State) -import Game.LambdaHack.Atomic-import Game.LambdaHack.Client-import Game.LambdaHack.Client.HandleResponseM-import Game.LambdaHack.Client.MonadClient-import Game.LambdaHack.Client.State-import Game.LambdaHack.Client.UI-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Atomic (MonadStateWrite (..), putState)+import Game.LambdaHack.Client+import Game.LambdaHack.Client.ClientOptions+import Game.LambdaHack.Client.HandleAtomicM+import Game.LambdaHack.Client.HandleResponseM+import Game.LambdaHack.Client.LoopM+import Game.LambdaHack.Client.MonadClient+import Game.LambdaHack.Client.State+import Game.LambdaHack.Client.UI+import Game.LambdaHack.Client.UI.SessionUI+import Game.LambdaHack.Common.Faction import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Response+import Game.LambdaHack.Common.MonadStateRead import qualified Game.LambdaHack.Common.Save as Save-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.State+import Game.LambdaHack.Server (ChanServer (..)) data CliState = CliState { cliState :: State -- ^ current global state , cliClient :: StateClient -- ^ current client state , cliSession :: Maybe SessionUI -- ^ UI state, empty for AI clients , cliDict :: ChanServer -- ^ this client connection information- , cliToSave :: Save.ChanSave (State, StateClient, Maybe SessionUI)+ , cliToSave :: Save.ChanSave (StateClient, Maybe SessionUI) -- ^ connection to the save thread }- deriving Generic -- | Client state transformation monad. newtype CliImplementation a = CliImplementation@@ -71,18 +70,17 @@ instance MonadClientSetup CliImplementation where saveClient = CliImplementation $ do toSave <- gets cliToSave- s <- gets cliState cli <- gets cliClient msess <- gets cliSession- IO.liftIO $ Save.saveToChan toSave (s, cli, msess)+ IO.liftIO $ Save.saveToChan toSave (cli, msess) restartClient = CliImplementation $ state $ \cliS -> case cliSession cliS of Just sess ->- let !newSess = (emptySessionUI (sconfig sess))+ let !newSess = (emptySessionUI (sUIOptions sess)) { schanF = schanF sess , sbinding = sbinding sess , shistory = shistory sess- , _sreport = _sreport sess+ , _sreport = sreport sess , sstart = sstart sess , sgstart = sgstart sess , sallTime = sallTime sess@@ -117,27 +115,26 @@ mSession <- gets cliSession return $! isJust mSession --- | The game-state semantics of atomic commands--- as computed on the client.-instance MonadAtomic CliImplementation where+instance MonadClientAtomic CliImplementation where {-# INLINE execUpdAtomic #-}- execUpdAtomic = handleUpdAtomic- {-# INLINE execSfxAtomic #-}- execSfxAtomic _sfx = return ()- {-# INLINE execSendPer #-}- execSendPer _ _ _ _ _ = return ()+ execUpdAtomic _ = return () -- handleUpdAtomic, until needed, save resources+ -- Don't catch anything; assume exceptions impossible.+ {-# INLINE execPutState #-}+ execPutState = putState --- | Init the client, then run an action, with a given session,--- state and history, in the @IO@ monad.-executorCli :: KeyKind -> Config -> DebugModeCli+-- | Run the main client loop, with the given arguments and empty+-- initial states, in the @IO@ monad.+executorCli :: KeyKind -> UIOptions -> ClientOptions -> Kind.COps- -> Maybe SessionUI+ -> Bool -> FactionId -> ChanServer -> IO ()-executorCli copsClient sconfig sdebugMode cops cliSession fid cliDict =- let stateToFileName (_, cli, _) =- ssavePrefixCli (sdebugCli cli) <> Save.saveNameCli cops (sside cli)+executorCli copsClient sUIOptions clientOptions cops isUI fid cliDict =+ let cliSession | isUI = Just $ emptySessionUI sUIOptions+ | otherwise = Nothing+ stateToFileName (cli, _) =+ ssavePrefixCli (soptions cli) <> Save.saveNameCli cops (sside cli) totalState cliToSave = CliState { cliState = emptyState cops , cliClient = emptyStateClient fid@@ -145,6 +142,6 @@ , cliToSave , cliSession }- m = loopCli copsClient sconfig sdebugMode+ m = loopCli copsClient sUIOptions clientOptions exe = evalStateT (runCliImplementation m) . totalState in Save.wrapInSaves cops stateToFileName exe
Game/LambdaHack/SampleImplementation/SampleMonadServer.hs view
@@ -1,8 +1,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}--- | The main game action monad type implementation. Just as any other+-- | The implementation of our custom game server monads. Just as any other -- component of the library, this implementation can be substituted.--- This module should not be imported anywhere except in 'Action'--- to expose the executor to any code using the library. module Game.LambdaHack.SampleImplementation.SampleMonadServer ( executorSer #ifdef EXPOSE_INTERNAL@@ -15,33 +13,35 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent+import Control.Concurrent import qualified Control.Exception as Ex import qualified Control.Monad.IO.Class as IO-import Control.Monad.Trans.State.Strict hiding (State)+import Control.Monad.Trans.State.Strict hiding (State) import qualified Data.EnumMap.Strict as EM import qualified Data.Text.IO as T-import System.Exit (ExitCode (ExitSuccess))-import System.FilePath-import System.IO (hFlush, stdout)+import Options.Applicative (defaultPrefs, execParserPure,+ handleParseResult)+import System.Exit (ExitCode (ExitSuccess))+import System.FilePath+import System.IO (hFlush, stdout) -import Game.LambdaHack.Atomic-import Game.LambdaHack.Client-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.File+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client+import Game.LambdaHack.Common.File import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead import qualified Game.LambdaHack.Common.Save as Save-import Game.LambdaHack.Common.State-import Game.LambdaHack.Common.Thread-import Game.LambdaHack.SampleImplementation.SampleMonadClient (executorCli)-import Game.LambdaHack.Server-import Game.LambdaHack.Server.BroadcastAtomic-import Game.LambdaHack.Server.HandleAtomicM-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.ProtocolM-import Game.LambdaHack.Server.State+import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Thread+import Game.LambdaHack.SampleImplementation.SampleMonadClient (executorCli)+import Game.LambdaHack.Server+import Game.LambdaHack.Server.BroadcastAtomic+import Game.LambdaHack.Server.HandleAtomicM+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.ProtocolM+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State data SerState = SerState { serState :: State -- ^ current global state@@ -73,7 +73,7 @@ modifyServer f = SerImplementation $ state $ \serS -> let !newSerServer = f $ serServer serS in ((), serS {serServer = newSerServer})- saveChanServer = SerImplementation $ gets serToSave+ chanSaveServer = SerImplementation $ gets serToSave liftIO = SerImplementation . IO.liftIO instance MonadServerReadRequest SerImplementation where@@ -85,50 +85,89 @@ in ((), serS {serDict = newSerDict}) liftIO = SerImplementation . IO.liftIO --- | The game-state semantics of atomic commands--- as computed on the server.-instance MonadAtomic SerImplementation where- execUpdAtomic cmd = cmdAtomicSemSer cmd >> handleAndBroadcast (UpdAtomic cmd)- execSfxAtomic sfx = handleAndBroadcast (SfxAtomic sfx)+instance MonadServerAtomic SerImplementation where+ execUpdAtomic cmd = do+ oldState <- getState+ (ps, atomicBroken, executedOnServer) <- handleCmdAtomicServer cmd+ when executedOnServer $ cmdAtomicSemSer oldState cmd+ handleAndBroadcast ps atomicBroken (UpdAtomic cmd)+ execUpdAtomicSer cmd = SerImplementation $ StateT $ \cliS -> do+ cliSNewOrE <- Ex.try+ $ execStateT (runSerImplementation $ handleUpdAtomic cmd)+ cliS+ case cliSNewOrE of+ Left AtomicFail{} -> return (False, cliS)+ Right cliSNew ->+ -- We know @cliSNew@ differs only in @serState@.+ return (True, cliSNew)+ execUpdAtomicFid fid cmd = SerImplementation $ StateT $ \cliS -> do+ -- Don't catch anything; assume exceptions impossible.+ let sFid = sclientStates (serServer cliS) EM.! fid+ cliSNew <- execStateT (runSerImplementation $ handleUpdAtomic cmd)+ cliS {serState = sFid}+ -- We know @cliSNew@ differs only in @serState@.+ let serServerNew = (serServer cliS)+ {sclientStates = EM.insert fid (serState cliSNew)+ $ sclientStates $ serServer cliS}+ return $! ((), cliS {serServer = serServerNew})+ execUpdAtomicFidCatch fid cmd = SerImplementation $ StateT $ \cliS -> do+ let sFid = sclientStates (serServer cliS) EM.! fid+ cliSNewOrE <- Ex.try+ $ execStateT (runSerImplementation $ handleUpdAtomic cmd)+ cliS {serState = sFid}+ case cliSNewOrE of+ Left AtomicFail{} -> return (False, cliS)+ Right cliSNew -> do+ -- We know @cliSNew@ differs only in @serState@.+ let serServerNew = (serServer cliS)+ {sclientStates = EM.insert fid (serState cliSNew)+ $ sclientStates $ serServer cliS}+ return $! (True, cliS {serServer = serServerNew})+ execSfxAtomic sfx = do+ ps <- posSfxAtomic sfx+ handleAndBroadcast ps [] (SfxAtomic sfx) execSendPer = sendPer -- Don't inline this, to keep GHC hard work inside the library -- for easy access of code analysis tools.--- | Run an action in the @IO@ monad, with undefined state.-executorSer :: Kind.COps -> KeyKind -> DebugModeSer -> IO ()-executorSer cops copsClient sdebugNxtCmdline = do+-- | Run the main server loop, with the given arguments and empty+-- initial states, in the @IO@ monad.+executorSer :: Kind.COps -> KeyKind -> ServerOptions -> IO ()+executorSer cops copsClient soptionsNxtCmdline = do -- Parse UI client configuration file. -- It is reparsed at each start of the game executable.- sconfig <- mkConfig cops (sbenchmark $ sdebugCli sdebugNxtCmdline)- sdebugNxt <- case configCmdline sconfig of- [] -> return sdebugNxtCmdline- args -> return $! debugArgs args+ let benchmark = sbenchmark $ sclientOptions soptionsNxtCmdline+ sUIOptions <- mkUIOptions cops benchmark+ soptionsNxt <- case uCmdline sUIOptions of+ [] -> return soptionsNxtCmdline+ args -> handleParseResult $ execParserPure defaultPrefs serverOptionsPI args -- Options for the clients modified with the configuration file. -- The client debug inside server debug only holds the client commandline -- options and is never updated with config options, etc.- let sdebugMode = applyConfigToDebug cops sconfig $ sdebugCli sdebugNxt+ let clientOptions = applyUIOptions cops sUIOptions+ $ sclientOptions soptionsNxt -- Partially applied main loop of the clients.- executorClient = executorCli copsClient sconfig sdebugMode cops+ executorClient = executorCli copsClient sUIOptions clientOptions cops -- Wire together game content, the main loop of game clients -- and the game server loop. let stateToFileName (_, ser) =- ssavePrefixSer (sdebugSer ser) <> Save.saveNameSer cops+ ssavePrefixSer (soptions ser) <> Save.saveNameSer cops totalState serToSave = SerState { serState = emptyState cops , serServer = emptyStateServer , serDict = EM.empty , serToSave }- m = loopSer sdebugNxt sconfig executorClient+ m = loopSer soptionsNxt executorClient exe = evalStateT (runSerImplementation m) . totalState exeWithSaves = Save.wrapInSaves cops stateToFileName exe- defPrefix = ssavePrefixSer defDebugModeSer+ defPrefix = ssavePrefixSer defServerOptions bkpOneSave name = do dataDir <- appDataDir let path bkp = dataDir </> "saves" </> bkp <> name b <- doesFileExist (path "") when b $ renameFile (path "") (path "bkp.")- bkpAllSaves = do+ bkpAllSaves = if benchmark then return () else do T.hPutStrLn stdout "The game crashed, so savefiles are moved aside." bkpOneSave $ defPrefix <> Save.saveNameSer cops forM_ [-99..99] $ \n ->@@ -144,7 +183,7 @@ _ -> do Ex.uninterruptibleMask_ $ threadDelay 1000000 -- let clients report their errors and save- when (ssavePrefixSer sdebugNxt == defPrefix) bkpAllSaves+ when (ssavePrefixSer soptionsNxt == defPrefix) bkpAllSaves hFlush stdout Ex.throwIO ex -- crash eventually, which kills clients )
Game/LambdaHack/Server.hs view
@@ -1,18 +1,22 @@--- | Semantics of requests that are sent to the server.+-- | Semantics of requests that are sent by clients to the server,+-- in terms of game state changes and responses to be sent to the clients. -- -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>. module Game.LambdaHack.Server ( -- * Re-exported from "Game.LambdaHack.Server.LoopM" loopSer+ -- * Re-exported from "Game.LambdaHack.Server.ProtocolM"+ , ChanServer (..) -- * Re-exported from "Game.LambdaHack.Server.Commandline"- , debugArgs- -- * Re-exported from "Game.LambdaHack.Server.State"- , DebugModeSer(..)+ , serverOptionsPI+ -- * Re-exported from "Game.LambdaHack.Server.ServerOptions"+ , ServerOptions(..) ) where import Prelude () -import Game.LambdaHack.Server.Commandline (debugArgs)+import Game.LambdaHack.Server.Commandline (serverOptionsPI) import Game.LambdaHack.Server.LoopM (loopSer)-import Game.LambdaHack.Server.State (DebugModeSer (..))+import Game.LambdaHack.Server.ProtocolM+import Game.LambdaHack.Server.ServerOptions
Game/LambdaHack/Server/BroadcastAtomic.hs view
@@ -1,11 +1,13 @@+{-# LANGUAGE TupleSections #-} -- | Sending atomic commands to clients and executing them on the server.+-- -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>. module Game.LambdaHack.Server.BroadcastAtomic- ( handleAndBroadcast, sendPer+ ( handleAndBroadcast, sendPer, handleCmdAtomicServer #ifdef EXPOSE_INTERNAL -- * Internal operations- , handleCmdAtomicServer, atomicRemember+ , loudUpdAtomic, loudSfxAtomic, atomicForget, atomicRemember #endif ) where @@ -15,43 +17,46 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Key (mapWithKeyM_)+import Data.Key (mapWithKeyM_) -import Game.LambdaHack.Atomic-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Atomic+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Server.ItemM-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.ProtocolM-import Game.LambdaHack.Server.State+import Game.LambdaHack.Content.TileKind (isUknownSpace)+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.ProtocolM+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State --storeUndo :: MonadServer m => CmdAtomic -> m () --storeUndo _atomic = -- maybe skip (\a -> modifyServer $ \ser -> ser {sundo = a : sundo ser}) -- $ Nothing -- undoCmdAtomic atomic -handleCmdAtomicServer :: MonadStateWrite m => PosAtomic -> UpdAtomic -> m ()-{-# INLINE handleCmdAtomicServer #-}-handleCmdAtomicServer posAtomic cmd =- when (seenAtomicSer posAtomic) $--- Not implemented ATM:--- storeUndo atomic- handleUpdAtomic cmd+handleCmdAtomicServer :: MonadServerAtomic m+ => UpdAtomic -> m (PosAtomic, [UpdAtomic], Bool)+handleCmdAtomicServer cmd = do+ ps <- posUpdAtomic cmd+ atomicBroken <- breakUpdAtomic cmd+ executedOnServer <- if seenAtomicSer ps+ then execUpdAtomicSer cmd+ else return False+ return (ps, atomicBroken, executedOnServer) -- | Send an atomic action to all clients that can see it.-handleAndBroadcast :: (MonadStateWrite m, MonadServerReadRequest m)- => CmdAtomic -> m ()-handleAndBroadcast atomic = do+handleAndBroadcast :: (MonadServerAtomic m, MonadServerReadRequest m)+ => PosAtomic -> [UpdAtomic] -> CmdAtomic -> m ()+handleAndBroadcast ps atomicBroken atomic = do -- This is calculated in the server State before action (simulating -- current client State, because action has not been applied -- on the client yet).@@ -59,20 +64,7 @@ -- To get rid of breakUpdAtomic we'd need to send only Spot and Lose -- commands instead of Move and Displace (plus Sfx for Displace). -- So this only makes sense when we switch to sending state diffs.- (ps, atomicBroken, psBroken) <-- case atomic of- UpdAtomic cmd -> do- ps <- posUpdAtomic cmd- atomicBroken <- breakUpdAtomic cmd- psBroken <- mapM posUpdAtomic atomicBroken- -- Perform the action on the server. The only part that requires- -- @MonadStateWrite@ and modifies server State.- handleCmdAtomicServer ps cmd- return (ps, atomicBroken, psBroken)- SfxAtomic sfx -> do- ps <- posSfxAtomic sfx- return (ps, [], [])- knowEvents <- getsServer $ sknowEvents . sdebugSer+ knowEvents <- getsServer $ sknowEvents . soptions sperFidOld <- getsServer sperFid -- Send some actions to the clients, one faction at a time. let sendAtomic fid (UpdAtomic cmd) = sendUpdate fid cmd@@ -80,7 +72,7 @@ breakSend lid fid fact perFidLid = do -- We take the new leader, from after cmd execution. let hear atomic2 = do- local <- case _gleader fact of+ local <- case gleader fact of Nothing -> return True -- give leaderless factions some love Just leader -> do body <- getsState $ getActorBody leader@@ -94,6 +86,7 @@ send2 (cmd2, ps2) = when (seenAtomicCli knowEvents fid perFidLid ps2) $ sendUpdate fid cmd2+ psBroken <- mapM posUpdAtomic atomicBroken case psBroken of _ : _ -> mapM_ send2 $ zip atomicBroken psBroken [] -> hear atomic -- broken commands are never loud@@ -144,11 +137,11 @@ return $! SfxLoudUpd local <$> mcmd -- | Messages for some unseen sfx.-loudSfxAtomic :: MonadServer m => Bool -> SfxAtomic -> m (Maybe SfxMsg)+loudSfxAtomic :: MonadStateRead m => Bool -> SfxAtomic -> m (Maybe SfxMsg) loudSfxAtomic local cmd = case cmd of SfxStrike source _ iid cstore | local -> do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull sb <- getsState $ getActorBody source bag <- getsState $ getBodyStoreBag sb cstore let kit = EM.findWithDefault (1, []) iid bag@@ -161,52 +154,120 @@ return $ Just $ SfxLoudSummon (bproj b) grp p _ -> return Nothing -sendPer :: MonadServerReadRequest m- => FactionId -> LevelId- -> Perception -> Perception -> Perception -> m ()+sendPer :: (MonadServerAtomic m, MonadServerReadRequest m)+ => FactionId -> LevelId -> Perception -> Perception -> Perception+ -> m () {-# INLINE sendPer #-} sendPer fid lid outPer inPer perNew = do- sendUpdate fid $ UpdPerception lid outPer inPer- remember <- getsState $ atomicRemember lid inPer+ sendUpdNoState fid $ UpdPerception lid outPer inPer+ sClient <- getsServer $ (EM.! fid) . sclientStates+ let forget = atomicForget fid lid outPer sClient+ remember <- getsState $ atomicRemember lid inPer sClient let seenNew = seenAtomicCli False fid perNew psRem <- mapM posUpdAtomic remember -- Verify that we remember only currently seen things. let !_A = assert (allB seenNew psRem) ()+ mapM_ (sendUpdateCheck fid) forget mapM_ (sendUpdate fid) remember -atomicRemember :: LevelId -> Perception -> State -> [UpdAtomic]+-- Remembered items, map tiles and smells are not wiped out when they get+-- out of FOV. Clients remember them. Only actors are forgotten.+atomicForget :: FactionId -> LevelId -> Perception -> State+ -> [UpdAtomic]+atomicForget side lid outPer sClient =+ -- Wipe out actors that just became invisible due to changed FOV.+ let outFov = totalVisible outPer+ outPrio = concatMap (\p -> posToAssocs p lid sClient) $ ES.elems outFov+ fActor (aid, b) =+ -- We forget only currently invisible actors. Actors can be outside+ -- perception, but still visible, if they belong to our faction,+ -- e.g., if they teleport to outside of current perception+ -- or if they have disabled senses.+ if not (bproj b) && bfid b == side+ then Nothing+ else Just $ UpdLoseActor aid b $ getCarriedAssocs b sClient+ -- this command always succeeds, the actor can be always removed,+ -- because the actor is taken from the state+ outActor = mapMaybe fActor outPrio+ in outActor++atomicRemember :: LevelId -> Perception -> State -> State -> [UpdAtomic] {-# INLINE atomicRemember #-}-atomicRemember lid inPer s =- -- No @UpdLoseItem@ is sent for items that became out of sight.- -- The client will create these atomic actions based on @outPer@,- -- if required. Any client that remembers out of sight items, OTOH,- -- will create atomic actions that forget remembered items- -- that are revealed not to be there any more (no @UpdSpotItem@ for them).- -- Similarly no @UpdLoseActor@, @UpdLoseTile@ nor @UpdLoseSmell@.- let inFov = ES.elems $ totalVisible inPer+atomicRemember lid inPer sClient s =+ let Kind.COps{cotile, coTileSpeedup} = scops s+ inFov = ES.elems $ totalVisible inPer lvl = sdungeon s EM.! lid- -- Actors.+ -- Wipe out remembered items on tiles that now came into view+ -- and spot items on these tiles. Optimized away, when items match.+ lvlClient = sdungeon sClient EM.! lid+ inContainer allow fc bagEM bagEMClient =+ let f p = case (EM.lookup p bagEM, EM.lookup p bagEMClient) of+ (Nothing, Nothing) -> [] -- most common, no items ever+ (Just bag, Nothing) -> -- common, client unaware+ let ais = map (\iid -> (iid, getItemBody iid s))+ (EM.keys bag)+ in [UpdSpotItemBag (fc lid p) bag ais | allow p]+ (Nothing, Just bagClient) -> -- uncommon, all items vanished+ -- We don't check @allow@, because client sees items there,+ -- so we assume he's aware of the tile enough to notice.+ let aisClient = map (\iid -> (iid, getItemBody iid sClient))+ (EM.keys bagClient)+ in [UpdLoseItemBag (fc lid p) bagClient aisClient]+ (Just bag, Just bagClient) ->+ -- We don't check @allow@, because client sees items there,+ -- so we assume he's aware of the tile enough to see new items.+ if bag == bagClient+ then [] -- common, nothing has changed, so optimized+ else -- uncommon, surprise; because it's rare, we send+ -- whole bags and don't optimize by sending only delta+ let aisClient = map (\iid -> (iid, getItemBody iid sClient))+ (EM.keys bagClient)+ ais = map (\iid -> (iid, getItemBody iid s))+ (EM.keys bag)+ in [ UpdLoseItemBag (fc lid p) bagClient aisClient+ , UpdSpotItemBag (fc lid p) bag ais ]+ in concatMap f inFov+ inFloor = inContainer (const True) CFloor (lfloor lvl) (lfloor lvlClient)+ -- Check that client may be shown embedded items, assuming he's not seeing+ -- any at this position so far. If he's not shown now, the items will be+ -- revealed via searching the tile later on.+ -- This check is essential to prevent embedded items from leaking+ -- tile identity.+ allowEmbed p = not (Tile.isHideAs coTileSpeedup $ lvl `at` p)+ || lvl `at` p == lvlClient `at` p+ inEmbed = inContainer allowEmbed CEmbed (lembed lvl) (lembed lvlClient)+ -- Spot tiles.+ atomicTile =+ -- We ignore the server resending us hidden versions of the tiles+ -- (or resending us the same data we already got).+ -- If the tiles are changed to other variants of the hidden tile,+ -- we can still verify by searching.+ let f p (loses1, spots1) =+ let t = lvl `at` p+ tHidden = fromMaybe t $ Tile.hideAs cotile t+ tClient = lvlClient `at` p+ in if tClient `elem` [t, tHidden]+ then (loses1, spots1)+ else ( if isUknownSpace tClient+ then loses1+ else (p, tClient) : loses1+ , (p, tHidden) : spots1 ) -- send the hidden version+ (loses, spots) = foldr f ([], []) inFov+ in [UpdLoseTile lid loses | not $ null loses]+ ++ [UpdSpotTile lid spots | not $ null spots]+ -- Wipe out remembered smell on tiles that now came into smell Fov.+ -- Smell radius is small, so we can just wipe and send all.+ inSmellFov = ES.elems $ totalSmelled inPer+ inSm = mapMaybe (\p -> (p,) <$> EM.lookup p (lsmell lvlClient)) inSmellFov+ inSmell = if null inSm then [] else [UpdLoseSmell lid inSm]+ -- Spot smells.+ inSm2 = mapMaybe (\p -> (p,) <$> EM.lookup p (lsmell lvl)) inSmellFov+ atomicSmell = if null inSm2 then [] else [UpdSpotSmell lid inSm2]+ -- Actors come last to report the environment they land on. inAssocs = concatMap (\p -> posToAssocs p lid s) inFov+ -- Here, the actor may be already visible, e.g., when teleporting,+ -- so the exception is caught in @sendUpdate@ above. fActor (aid, b) = let ais = getCarriedAssocs b s in UpdSpotActor aid b ais inActor = map fActor inAssocs- -- Items.- pMaybe p = maybe Nothing (\x -> Just (p, x))- inContainer fc itemFloor =- let inItem = mapMaybe (\p -> pMaybe p $ EM.lookup p itemFloor) inFov- fItem p (iid, kit) =- UpdSpotItem True iid (getItemBody iid s) kit (fc lid p)- fBag (p, bag) = map (fItem p) $ EM.assocs bag- in concatMap fBag inItem- inFloor = inContainer CFloor (lfloor lvl)- inEmbed = inContainer CEmbed (lembed lvl)- -- Tiles.- Kind.COps{cotile} = scops s- hideTile p = Tile.hideAs cotile $ lvl `at` p- inTileMap = map (\p -> (p, hideTile p)) inFov- atomicTile = if null inTileMap then [] else [UpdSpotTile lid inTileMap]- -- Smells.- inSmellFov = ES.elems $ totalSmelled inPer- inSm = mapMaybe (\p -> pMaybe p $ EM.lookup p (lsmell lvl)) inSmellFov- atomicSmell = if null inSm then [] else [UpdSpotSmell lid inSm]- in atomicTile ++ inFloor ++ inEmbed ++ atomicSmell ++ inActor+ in atomicTile ++ inFloor ++ inEmbed ++ inSmell ++ atomicSmell ++ inActor
Game/LambdaHack/Server/Commandline.hs view
@@ -1,6 +1,12 @@+{-# LANGUAGE ApplicativeDo #-} -- | Parsing of commandline arguments. module Game.LambdaHack.Server.Commandline- ( debugArgs+ ( serverOptionsPI+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , serverOptionsP+ -- other internal operations too numerous and changing, so not listed+#endif ) where import Prelude ()@@ -8,163 +14,255 @@ import Game.LambdaHack.Common.Prelude import qualified Data.Text as T+import Options.Applicative+import qualified System.Random as R -import Game.LambdaHack.Common.ClientOptions+-- Dependence on ClientOptions is an anomaly. Instead, probably the raw+-- remaining commandline should be passed and parsed by the client to extract+-- client and ui options from and singnal an error if anything was left.++import Game.LambdaHack.Client.ClientOptions import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Server.State+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Server.ServerOptions --- | Parse server debug parameters from commandline arguments.-debugArgs :: [String] -> DebugModeSer-debugArgs =- let breakPar s = let (params, args) = break ("-" `isPrefixOf`) s- in (unwords params, args)- usage =- [ "Configure debug options here, gameplay options in config.rules.ini."- , " --knowMap reveal map for all clients in the next game"- , " --knowEvents show all events in the next game (needs --knowMap)"- , " --knowItems auto-identify all items in the next game (needs --knowEvents)"- , " --sniffIn display all incoming commands on console "- , " --sniffOut display all outgoing commands on console "- , " --allClear let all map tiles be translucent"- , " --boostRandomItem pick a random item and make it very common"- , " --gameMode m start next game in the given mode"- , " --automateAll give control of all UI teams to computer"- , " --keepAutomated keep factions automated after game over"- , " --newGame n start a new game, overwriting the save file,"- , " with difficulty for all UI players set to n"- , " --stopAfterSeconds n exit game session after around n seconds"- , " --stopAfterFrames n exit game session after around n frames"- , " --benchmark restrict file IO, print stats"- , " --setDungeonRng s set dungeon generation RNG seed to string s"- , " --setMainRng s set the main game RNG seed to string s"- , " --dumpInitRngs dump RNG states from the start of the game"- , " --dbgMsgSer let the server emit its internal debug messages"- , " --gtkFontFamily s use the given font family for the main game window in GTK"- , " --sdlFontFile s use the given font file for the main game window in SDL2"- , " --sdlTtfSizeAdd s enlarge map cells over scalable font max height in SDL2"- , " --sdlFonSizeAdd s enlarge map cells on top of .fon font max height in SDL2"- , " --fontSize s use the given font size for the main game window"- , " --noColorIsBold refrain from making some bright color characters bolder"- , " --maxFps n display at most n frames per second"- , " --disableAutoYes never auto-answer all prompts"- , " --noAnim don't show any animations"- , " --savePrefix prepend the text to all savefile names"- , " --frontendTeletype use the line terminal frontend (for tests)"- , " --frontendNull use frontend with no display (for benchmarks)"- , " --frontendLazy use frontend that not even computes frames (for benchmarks)"- , " --dbgMsgCli let clients emit their internal debug messages"- ]- parseArgs [] = defDebugModeSer- parseArgs ("--knowMap" : rest) =- (parseArgs rest) {sknowMap = True}- parseArgs ("--knowEvents" : rest) =- (parseArgs rest) {sknowEvents = True}- parseArgs ("--knowItems" : rest) =- (parseArgs rest) {sknowItems = True}- parseArgs ("--sniffIn" : rest) =- (parseArgs rest) {sniffIn = True}- parseArgs ("--sniffOut" : rest) =- (parseArgs rest) {sniffOut = True}- parseArgs ("--allClear" : rest) =- (parseArgs rest) {sallClear = True}- parseArgs ("--boostRandomItem" : rest) =- (parseArgs rest) {sboostRandomItem = True}- parseArgs ("--gameMode" : rest) =- let (params, args) = breakPar rest- in (parseArgs args) {sgameMode = Just $ toGroupName (T.pack params)}- parseArgs ("--automateAll" : rest) =- (parseArgs rest) {sautomateAll = True}- parseArgs ("--keepAutomated" : rest) =- (parseArgs rest) {skeepAutomated = True}- parseArgs ("--newGame" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- cdiff = read params- in debugSer { scurChalSer = (scurChalSer debugSer) {cdiff}- , snewGameSer = True- , sdebugCli = (sdebugCli debugSer) {snewGameCli = True}}- parseArgs ("--stopAfterSeconds" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer {sdebugCli =- (sdebugCli debugSer) {sstopAfterSeconds = Just $ read params}}- parseArgs ("--stopAfterFrames" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer {sdebugCli =- (sdebugCli debugSer) {sstopAfterFrames = Just $ read params}}- parseArgs ("--benchmark" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli = (sdebugCli debugSer) {sbenchmark = True}}- parseArgs ("--setDungeonRng" : rest) =- let (params, args) = breakPar rest- in (parseArgs args) {sdungeonRng = Just $ read params}- parseArgs ("--setMainRng" : rest) =- let (params, args) = breakPar rest- in (parseArgs args) {smainRng = Just $ read params}- parseArgs ("--dumpInitRngs" : rest) =- (parseArgs rest) {sdumpInitRngs = True}- parseArgs ("--dbgMsgSer" : rest) =- (parseArgs rest) {sdbgMsgSer = True}- parseArgs ("--gtkFontFamily" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer {sdebugCli = (sdebugCli debugSer) {sgtkFontFamily =- Just $ T.pack params}}- parseArgs ("--sdlFontFile" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer {sdebugCli = (sdebugCli debugSer) {sdlFontFile =- Just $ T.pack params}}- parseArgs ("--sdlTtfSizeAdd" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer {sdebugCli = (sdebugCli debugSer) {sdlTtfSizeAdd =- Just $ read params}}- parseArgs ("--sdlFonSizeAdd" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer {sdebugCli = (sdebugCli debugSer) {sdlFonSizeAdd =- Just $ read params}}- parseArgs ("--fontSize" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer {sdebugCli = (sdebugCli debugSer) {sfontSize =- Just $ read params}}- parseArgs ("--noColorIsBold" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli =- (sdebugCli debugSer) {scolorIsBold = Just False}}- parseArgs ("--maxFps" : n : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli =- (sdebugCli debugSer) {smaxFps = Just $ max 1 $ read n}}- parseArgs ("--disableAutoYes" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli = (sdebugCli debugSer) {sdisableAutoYes = True}}- parseArgs ("--noAnim" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli = (sdebugCli debugSer) {snoAnim = Just True}}- parseArgs ("--savePrefix" : rest) =- let (params, args) = breakPar rest- debugSer = parseArgs args- in debugSer { ssavePrefixSer = params- , sdebugCli =- (sdebugCli debugSer) {ssavePrefixCli = params}}- parseArgs ("--frontendTeletype" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli = (sdebugCli debugSer)- {sfrontendTeletype = True}}- parseArgs ("--frontendNull" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli = (sdebugCli debugSer) {sfrontendNull = True}}- parseArgs ("--frontendLazy" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli = (sdebugCli debugSer) {sfrontendLazy = True}}- parseArgs ("--dbgMsgCli" : rest) =- let debugSer = parseArgs rest- in debugSer {sdebugCli = (sdebugCli debugSer) {sdbgMsgCli = True}}- parseArgs (wrong : _rest) =- error $ "Unrecognized: " ++ wrong ++ "\n" ++ unlines usage- in parseArgs+-- | Parser for server options from commandline arguments.+serverOptionsPI :: ParserInfo ServerOptions+serverOptionsPI = info ( helper <*> serverOptionsP )+ $ fullDesc+ <> progDesc "Configure debug options here, gameplay options in configuration file."++serverOptionsP :: Parser ServerOptions+serverOptionsP = do+ ~(snewGameSer, scurChalSer)+ <- serToChallenge <$> newGameP+ knowMap <- knowMapP+ knowEvents <- knowEventsP+ knowItems <- knowItemsP+ sniff <- sniffP+ sallClear <- allClearP+ sboostRandomItem <- boostRandItemP+ sgameMode <- gameModeP+ sautomateAll <- automateAllP+ skeepAutomated <- keepAutomatedP+ sstopAfterSeconds <- stopAfterSecsP+ sstopAfterFrames <- stopAfterFramesP+ sbenchmark <- benchmarkP+ sdungeonRng <- setDungeonRngP+ smainRng <- setMainRngP+ sdumpInitRngs <- dumpInitRngsP+ sdbgMsgSer <- dbgMsgSerP+ sgtkFontFamily <- gtkFontFamilyP+ sdlFontFile <- sdlFontFileP+ sdlTtfSizeAdd <- sdlTtfSizeAddP+ sdlFonSizeAdd <- sdlFonSizeAddP+ sfontSize <- fontSizeP+ sfontDir <- fontDirP+ scolorIsBold <- noColorIsBoldP+ smaxFps <- maxFpsP+ sdisableAutoYes <- disableAutoYesP+ snoAnim <- noAnimP+ ssavePrefixSer <- savePrefixP+ sfrontendTeletype <- frontendTeletypeP+ sfrontendNull <- frontendNullP+ sfrontendLazy <- frontendLazyP+ sdbgMsgCli <- dbgMsgCliP++ pure ServerOptions+ {+ sclientOptions = ClientOptions+ { stitle = Nothing+ , snewGameCli = snewGameSer+ , ssavePrefixCli = ssavePrefixSer+ , ..+ }+ , sknowMap = knowMap || knowEvents || knowItems+ , sknowEvents = knowEvents || knowItems+ , sknowItems = knowItems+ , ..+ }+ where+ serToChallenge :: Maybe Int -> (Bool, Challenge)+ serToChallenge Nothing = (False, defaultChallenge)+ serToChallenge (Just cdiff) = (True, defaultChallenge {cdiff})++knowMapP :: Parser Bool+knowMapP =+ switch ( long "knowMap"+ <> help "Reveal map for all clients in the next game" )++knowEventsP :: Parser Bool+knowEventsP =+ switch ( long "knowEvents"+ <> help "Show all events in the next game (implies --knowMap)" )++knowItemsP :: Parser Bool+knowItemsP =+ switch ( long "knowItems"+ <> help "Auto-identify all items in the next game (implies --knowEvents)" )++sniffP :: Parser Bool+sniffP =+ switch ( long "sniff"+ <> help "Monitor all trafic between server and clients" )++allClearP :: Parser Bool+allClearP =+ switch ( long "allClear"+ <> help "Let all map tiles be translucent" )++boostRandItemP :: Parser Bool+boostRandItemP =+ switch ( long "boostRandomItem"+ <> help "Pick a random item and make it very common" )++gameModeP :: Parser (Maybe (GroupName ModeKind))+gameModeP = optional $ toGameMode <$>+ strOption ( long "gameMode"+ <> metavar "MODE"+ <> help "Start next game in the scenario indicated by MODE" )+ where+ toGameMode :: String -> GroupName ModeKind+ toGameMode = toGroupName . T.pack++automateAllP :: Parser Bool+automateAllP =+ switch ( long "automateAll"+ <> help "Give control of all UI teams to computer" )++keepAutomatedP :: Parser Bool+keepAutomatedP =+ switch ( long "keepAutomated"+ <> help "Keep factions automated after game over" )++newGameP :: Parser (Maybe Int)+newGameP = optional $+ option auto ( long "newGame"+ <> help "Start a new game, overwriting the save file, with difficulty for all UI players set to N"+ <> metavar "N" )++stopAfterSecsP :: Parser (Maybe Int)+stopAfterSecsP = optional $+ option auto ( long "stopAfterSeconds"+ <> help "Exit game session after around N seconds"+ <> metavar "N" )++stopAfterFramesP :: Parser (Maybe Int)+stopAfterFramesP = optional $+ option auto ( long "stopAfterFrames"+ <> help "Exit game session after around N frames"+ <> metavar "N" )++benchmarkP :: Parser Bool+benchmarkP =+ switch ( long "benchmark"+ <> help "Restrict file IO, print timing stats" )++setDungeonRngP :: Parser (Maybe R.StdGen)+setDungeonRngP = optional $+ option auto ( long "setDungeonRng"+ <> metavar "RNG_SEED"+ <> help "Set dungeon generation RNG seed to string RNG_SEED" )++setMainRngP :: Parser (Maybe R.StdGen)+setMainRngP = optional $+ option auto ( long "setMainRng"+ <> metavar "RNG_SEED"+ <> help "Set the main game RNG seed to string RNG_SEED" )++dumpInitRngsP :: Parser Bool+dumpInitRngsP =+ switch ( long "dumpInitRngs"+ <> help "Dump the RNG seeds used to initialize the game" )++dbgMsgSerP :: Parser Bool+dbgMsgSerP =+ switch ( long "dbgMsgSer"+ <> help "Emit extra internal server debug messages" )++gtkFontFamilyP :: Parser (Maybe Text)+gtkFontFamilyP = optional $ T.pack <$>+ strOption ( long "gtkFontFamily"+ <> metavar "FONT_FAMILY"+ <> help "Use FONT_FAMILY for the main game window in GTK frontend" )++sdlFontFileP :: Parser (Maybe Text)+sdlFontFileP = optional $ T.pack <$>+ strOption ( long "sdlFontFile"+ <> metavar "FONT_FILE"+ <> help "Use FONT_FILE for the main game window in SDL2 frontend" )++sdlTtfSizeAddP :: Parser (Maybe Int)+sdlTtfSizeAddP = optional $+ option auto ( long "sdlTtfSizeAdd"+ <> metavar "N"+ <> help "Enlarge map cells by N over scalable font max height in SDL2 frontend" )++sdlFonSizeAddP :: Parser (Maybe Int)+sdlFonSizeAddP = optional $+ option auto ( long "sdlFonSizeAdd"+ <> metavar "N"+ <> help "Enlarge map cells by N on top of .fon font max height in SDL2 frontend" )++fontSizeP :: Parser (Maybe Int)+fontSizeP = optional $+ option auto ( long "fontSize"+ <> metavar "N"+ <> help "Use font size N for the main game window (interpreted differently by different graphical frontends)" )++fontDirP :: Parser (Maybe FilePath)+fontDirP = optional $+ option auto ( long "fontDir"+ <> metavar "FILEPATH"+ <> help "Take font files for the SDL2 frontend from FILEPATH" )++noColorIsBoldP :: Parser (Maybe Bool)+noColorIsBoldP =+ flag Nothing (Just False)+ ( long "noColorIsBold"+ <> help "Refrain from making some bright color characters bolder" )++maxFpsP :: Parser (Maybe Int)+maxFpsP = optional $ max 1 <$>+ option auto ( long "maxFps"+ <> metavar "N"+ <> help "Display at most N frames per second" )++disableAutoYesP :: Parser Bool+disableAutoYesP =+ switch ( long "disableAutoYes"+ <> help "Never auto-answer prompts, not even when UI faction is automated" )++noAnimP :: Parser (Maybe Bool)+noAnimP =+ flag Nothing (Just True)+ ( long "noAnim"+ <> help "Don't show any animations" )++savePrefixP :: Parser String+savePrefixP =+ strOption ( long "savePrefix"+ <> metavar "PREFIX"+ <> value ""+ <> help "Prepend PREFIX to all savefile names" )++frontendTeletypeP :: Parser Bool+frontendTeletypeP =+ switch ( long "frontendTeletype"+ <> help "Use the line terminal frontend (for tests)" )++frontendNullP :: Parser Bool+frontendNullP =+ switch ( long "frontendNull"+ <> help "Use frontend with no display (for benchmarks)" )++frontendLazyP :: Parser Bool+frontendLazyP =+ switch ( long "frontendLazy"+ <> help "Use frontend that not even computes frames (for benchmarks)" )++dbgMsgCliP :: Parser Bool+dbgMsgCliP =+ switch ( long "dbgMsgCli"+ <> help "Emit extra internal client debug messages" )
Game/LambdaHack/Server/CommonM.hs view
@@ -1,12 +1,16 @@ {-# LANGUAGE TupleSections #-} -- | Server operations common to many modules. module Game.LambdaHack.Server.CommonM- ( execFailure, getPerFid- , revealItems, moveStores, deduceQuits, deduceKilled- , electLeader, supplantLeader- , addActor, registerActor, addActorIid, projectFail, discoverIfNoEffects+ ( execFailure, revealItems, moveStores, generalMoveItem+ , deduceQuits, deduceKilled, electLeader, supplantLeader+ , updatePer, recomputeCachePer, projectFail+ , addActor, registerActor, addActorIid, discoverIfNoEffects , pickWeaponServer, currentSkillsServer- , recomputeCachePer+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , containerMoveItem, quitF, keepArenaFact, anyActorsAlive, projectBla+ , addProjectile, getCacheLucid, getCacheTotal+#endif ) where import Prelude ()@@ -17,35 +21,37 @@ import qualified Data.Text as T import qualified Text.Show.Pretty as Show.Pretty -import Game.LambdaHack.Atomic+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind-import Game.LambdaHack.Server.Fov-import Game.LambdaHack.Server.ItemM-import Game.LambdaHack.Server.ItemRev-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.State+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Server.Fov+import Game.LambdaHack.Server.ItemM+import Game.LambdaHack.Server.ItemRev+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State -execFailure :: (MonadAtomic m, MonadServer m)+execFailure :: MonadServerAtomic m => ActorId -> RequestTimed a -> ReqFailure -> m () execFailure aid req failureSer = do -- Clients should rarely do that (only in case of invisible actors)@@ -65,25 +71,18 @@ <> debugShow body <> "\n" <> debugShow req <> "\n" <> debugShow failureSer execSfxAtomic $ SfxMsgFid fid $ SfxUnexpected failureSer -getPerFid :: MonadServer m => FactionId -> LevelId -> m Perception-getPerFid fid lid = do- pers <- getsServer sperFid- let failFact = error $ "no perception for faction" `showFailure` (lid, fid)- fper = EM.findWithDefault failFact fid pers- failLvl = error $ "no perception for level" `showFailure` (lid, fid)- per = EM.findWithDefault failLvl lid fper- return $! per--revealItems :: (MonadAtomic m, MonadServer m) => Maybe FactionId -> m ()+revealItems :: MonadServerAtomic m => Maybe FactionId -> m () revealItems mfid = do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull let discover aid store iid k = let itemFull = itemToF iid k c = CActor aid store in case itemDisco itemFull of- Just ItemDisco{itemKindId} -> do- seed <- getsServer $ (EM.! iid) . sitemSeedD- execUpdAtomic $ UpdDiscover c iid itemKindId seed+ Just ItemDisco{itemKindId, itemKind} -> do+ let isGem = maybe False (> 0) (lookup "gem" $ IK.ifreq itemKind)+ unless isGem $ do -- a hack+ seed <- getsServer $ (EM.! iid) . sitemSeedD+ execUpdAtomic $ UpdDiscover c iid itemKindId seed _ -> error $ "" `showFailure` (mfid, c, iid, itemFull) f aid = do b <- getsState $ getActorBody aid@@ -97,7 +96,7 @@ as <- getsState $ EM.keys . sactorD mapM_ f as -moveStores :: (MonadAtomic m, MonadServer m)+moveStores :: MonadServerAtomic m => Bool -> ActorId -> CStore -> CStore -> m () moveStores verbose aid fromStore toStore = do b <- getsState $ getActorBody aid@@ -107,30 +106,59 @@ mapM_ execUpdAtomic move mapActorCStore_ fromStore g b -quitF :: (MonadAtomic m, MonadServer m) => Status -> FactionId -> m ()+-- | Generate the atomic updates that jointly perform a given item move.+generalMoveItem :: MonadStateRead m+ => Bool -> ItemId -> Int -> Container -> Container+ -> m [UpdAtomic]+generalMoveItem verbose iid k c1 c2 =+ case (c1, c2) of+ (CActor aid1 cstore1, CActor aid2 cstore2) | aid1 == aid2+ && cstore1 /= CSha+ && cstore2 /= CSha ->+ return [UpdMoveItem iid k aid1 cstore1 cstore2]+ _ -> containerMoveItem verbose iid k c1 c2++containerMoveItem :: MonadStateRead m+ => Bool -> ItemId -> Int -> Container -> Container+ -> m [UpdAtomic]+containerMoveItem verbose iid k c1 c2 = do+ bag <- getsState $ getContainerBag c1+ case iid `EM.lookup` bag of+ Nothing -> error $ "" `showFailure` (iid, k, c1, c2)+ Just (_, it) -> do+ item <- getsState $ getItemBody iid+ return [ UpdLoseItem verbose iid item (k, take k it) c1+ , UpdSpotItem verbose iid item (k, take k it) c2 ]++quitF :: MonadServerAtomic m => Status -> FactionId -> m () quitF status fid = do fact <- getsState $ (EM.! fid) . sfactionD let oldSt = gquit fact+ -- Note that it's the _old_ status that we check here. case stOutcome <$> oldSt of Just Killed -> return () -- Do not overwrite in case Just Defeated -> return () -- many things happen in 1 turn. Just Conquer -> return () Just Escape -> return () _ -> do+ -- This runs regardless of the _new_ status. when (fhasUI $ gplayer fact) $ do- keepAutomated <- getsServer $ skeepAutomated . sdebugSer+ keepAutomated <- getsServer $ skeepAutomated . soptions+ -- Try to remove AI control of the UI faction, to show endgame info. when (isAIFact fact && fleaderMode (gplayer fact) /= LeaderNull && not keepAutomated) $ execUpdAtomic $ UpdAutoFaction fid False revealItems (Just fid)+ -- Likely, by this time UI faction is no longer AI-controlled,+ -- so the score will get registered. registerScore status fid execUpdAtomic $ UpdQuitFaction fid oldSt $ Just status modifyServer $ \ser -> ser {squit = True} -- check game over ASAP -- Send any UpdQuitFaction actions that can be deduced from factions' -- current state.-deduceQuits :: (MonadAtomic m, MonadServer m) => FactionId -> Status -> m ()+deduceQuits :: MonadServerAtomic m => FactionId -> Status -> m () deduceQuits fid0 status@Status{stOutcome} | stOutcome `elem` [Defeated, Camping, Restart, Conquer] = error $ "no quitting to deduce" `showFailure` (fid0, status)@@ -192,7 +220,7 @@ -- We assume the actor in the second argument has HP <= 0 or is going to be -- dominated right now. Even if the actor is to be dominated, -- @bfid@ of the actor body is still the old faction.-deduceKilled :: (MonadAtomic m, MonadServer m) => ActorId -> m ()+deduceKilled :: MonadServerAtomic m => ActorId -> m () deduceKilled aid = do Kind.COps{corule} <- getsState scops body <- getsState $ getActorBody aid@@ -208,28 +236,61 @@ as <- getsState $ fidActorNotProjAssocs fid return $! map fst as /= [aid] -electLeader :: MonadAtomic m => FactionId -> LevelId -> ActorId -> m ()+electLeader :: MonadServerAtomic m => FactionId -> LevelId -> ActorId -> m () electLeader fid lid aidDead = do- mleader <- getsState $ _gleader . (EM.! fid) . sfactionD+ mleader <- getsState $ gleader . (EM.! fid) . sfactionD when (mleader == Just aidDead) $ do actorD <- getsState sactorD let ours (_, b) = bfid b == fid && not (bproj b) party = filter ours $ EM.assocs actorD+ -- Prefer actors on level and with positive HP.+ (positive, negative) = partition (\(_, b) -> bhp b > 0) party onLevel <- getsState $ fidActorRegularIds fid lid- let mleaderNew = case filter (/= aidDead) $ onLevel ++ map fst party of+ let mleaderNew = case filter (/= aidDead)+ $ onLevel ++ map fst (positive ++ negative) of [] -> Nothing aid : _ -> Just aid execUpdAtomic $ UpdLeadFaction fid mleader mleaderNew -supplantLeader :: MonadAtomic m => FactionId -> ActorId -> m ()+supplantLeader :: MonadServerAtomic m => FactionId -> ActorId -> m () supplantLeader fid aid = do fact <- getsState $ (EM.! fid) . sfactionD- unless (fleaderMode (gplayer fact) == LeaderNull) $- execUpdAtomic $ UpdLeadFaction fid (_gleader fact) (Just aid)+ unless (fleaderMode (gplayer fact) == LeaderNull) $ do+ -- First update and send Perception so that the new leader+ -- may report his environment.+ b <- getsState $ getActorBody aid+ valid <- getsServer $ (EM.! blid b) . (EM.! fid) . sperValidFid+ unless valid $ updatePer fid (blid b)+ execUpdAtomic $ UpdLeadFaction fid (gleader fact) (Just aid) +updatePer :: MonadServerAtomic m => FactionId -> LevelId -> m ()+{-# INLINE updatePer #-}+updatePer fid lid = do+ modifyServer $ \ser ->+ ser {sperValidFid = EM.adjust (EM.insert lid True) fid $ sperValidFid ser}+ sperFidOld <- getsServer sperFid+ let perOld = sperFidOld EM.! fid EM.! lid+ knowEvents <- getsServer $ sknowEvents . soptions+ -- Performed in the State after action, e.g., with a new actor.+ perNew <- recomputeCachePer fid lid+ let inPer = diffPer perNew perOld+ outPer = diffPer perOld perNew+ unless (nullPer outPer && nullPer inPer) $+ unless knowEvents $ -- inconsistencies would quickly manifest+ execSendPer fid lid outPer inPer perNew++recomputeCachePer :: MonadServer m => FactionId -> LevelId -> m Perception+recomputeCachePer fid lid = do+ total <- getCacheTotal fid lid+ fovLucid <- getCacheLucid lid+ let perNew = perceptionFromPTotal fovLucid total+ fper = EM.adjust (EM.insert lid perNew) fid+ modifyServer $ \ser -> ser {sperFid = fper $ sperFid ser}+ return perNew+ -- The missile item is removed from the store only if the projection -- went into effect (no failure occured).-projectFail :: (MonadAtomic m, MonadServer m)+projectFail :: MonadServerAtomic m => ActorId -- ^ actor projecting the item (is on current lvl) -> Point -- ^ target position of the projectile -> Int -- ^ digital line parameter@@ -252,11 +313,10 @@ case EM.lookup iid bag of Nothing -> return $ Just ProjectOutOfReach Just kit -> do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull actorSk <- currentSkillsServer source- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! source- skill = EM.findWithDefault 0 Ability.AbProject actorSk+ ar <- getsState $ getActorAspect source+ let skill = EM.findWithDefault 0 Ability.AbProject actorSk itemFull@ItemFull{itemBase} = itemToF iid kit forced = isBlast || bproj sb calmE = calmEnough sb ar@@ -287,7 +347,7 @@ projectBla source pos rest iid cstore isBlast return Nothing -projectBla :: (MonadAtomic m, MonadServer m)+projectBla :: MonadServerAtomic m => ActorId -- ^ actor projecting the item (is on current lvl) -> Point -- ^ starting point of the projectile -> [Point] -- ^ rest of the trajectory of the projectile@@ -310,15 +370,12 @@ let c = CActor source cstore execUpdAtomic $ UpdLoseItem False iid item (1, take 1 it) c --- | Create a projectile actor containing the given missile.------ Projectile has no organs except for the trunk.-addProjectile :: (MonadAtomic m, MonadServer m)+addProjectile :: MonadServerAtomic m => Point -> [Point] -> ItemId -> ItemQuant -> LevelId -> FactionId -> Time -> Bool -> m () addProjectile bpos rest iid (_, it) blid bfid btime _isBlast = do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull let itemFull@ItemFull{itemBase} = itemToF iid (1, take 1 it) (trajectory, (speed, _)) = itemTrajectory itemBase (bpos : rest) tweakBody b = b { bhp = oneM@@ -328,7 +385,7 @@ , borgan = EM.empty } -- don't confer bonuses from trunk void $ addActorIid iid itemFull True bfid bpos blid tweakBody btime -addActor :: (MonadAtomic m, MonadServer m)+addActor :: MonadServerAtomic m => GroupName ItemKind -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)@@ -339,30 +396,23 @@ m5 <- rollItem 0 lid trunkFreq case m5 of Nothing -> return Nothing- Just (itemKnownRaw, itemFullRaw, itemDisco, seed, _) ->- registerActor itemKnownRaw itemFullRaw itemDisco seed- bfid pos lid tweakBody time+ Just (itemKnownRaw, itemFullRaw, _, seed, _) ->+ registerActor itemKnownRaw itemFullRaw seed bfid pos lid tweakBody time -registerActor :: (MonadAtomic m, MonadServer m)- => ItemKnown -> ItemFull -> ItemDisco -> ItemSeed+registerActor :: MonadServerAtomic m+ => ItemKnown -> ItemFull -> ItemSeed -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)-registerActor (kindIx, ar, damage, _) itemFullRaw itemDisco seed+registerActor (kindIx, ar, damage, _) itemFullRaw seed bfid pos lid tweakBody time = do let container = CTrunk bfid lid pos- -- Other code adds to @sdiscoBenefit@ only @iid@ and not any other items- -- that share the same @jkindIx@, so this is broken if such items- -- are not fully IDed from the start, so check that before risking a copy- -- of the same item, but with different @jfid@.- jfid = if IK.Identified `elem` IK.ifeature (itemKind itemDisco)- then Just bfid- else Nothing+ jfid = Just bfid itemKnown = (kindIx, ar, damage, jfid) itemFull = itemFullRaw {itemBase = (itemBase itemFullRaw) {jfid}} trunkId <- registerItem itemFull itemKnown seed container False addActorIid trunkId itemFull False bfid pos lid tweakBody time -addActorIid :: (MonadAtomic m, MonadServer m)+addActorIid :: MonadServerAtomic m => ItemId -> ItemFull -> Bool -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Time -> m (Maybe ActorId)@@ -380,7 +430,7 @@ -- Create actor. factionD <- getsState sfactionD let fact = factionD EM.! bfid- curChalSer <- getsServer $ scurChalSer . sdebugSer+ curChalSer <- getsServer $ scurChalSer . soptions nU <- nUI -- If difficulty is below standard, HP is added to the UI factions, -- otherwise HP is added to their enemies.@@ -422,21 +472,23 @@ Just (iid, (itemFull, _)) -> discoverIfNoEffects container iid itemFull return $ Just aid -discoverIfNoEffects :: (MonadAtomic m, MonadServer m)+discoverIfNoEffects :: MonadServerAtomic m => Container -> ItemId -> ItemFull -> m () discoverIfNoEffects c iid itemFull = case itemFull of- ItemFull{itemDisco=Just ItemDisco{itemKind=IK.ItemKind{IK.ieffects}}}- | any IK.forIdEffect ieffects -> return () -- discover by use- _ -> do+ ItemFull{itemDisco=Just ItemDisco{itemKind}}+ | any IK.forIdEffect (IK.ieffects itemKind)+ || maybe False (> 0) (lookup "gem" $ IK.ifreq itemKind) -> -- a hack+ return () -- discover by use, ignore gems+ ItemFull{itemDisco=Just ItemDisco{itemKindId}} -> do seed <- getsServer $ (EM.! iid) . sitemSeedD- execUpdAtomic $ UpdDiscoverSeed c iid seed+ execUpdAtomic $ UpdDiscover c iid itemKindId seed+ _ -> error "server doesn't fully know item" pickWeaponServer :: MonadServer m => ActorId -> m (Maybe (ItemId, CStore)) pickWeaponServer source = do- eqpAssocs <- fullAssocsServer source [CEqp]- bodyAssocs <- fullAssocsServer source [COrgan]+ eqpAssocs <- getsState $ fullAssocs source [CEqp]+ bodyAssocs <- getsState $ fullAssocs source [COrgan] actorSk <- currentSkillsServer source- actorAspect <- getsServer sactorAspect sb <- getsState $ getActorBody source let allAssocsRaw = eqpAssocs ++ bodyAssocs forced = bproj sb@@ -445,7 +497,7 @@ -- Server ignores item effects or it would leak item discovery info. -- In particular, it even uses weapons that would heal opponent, -- and not only in case of projectiles.- strongest <- pickWeaponM Nothing allAssocs actorSk actorAspect source+ strongest <- pickWeaponM Nothing allAssocs actorSk source case strongest of [] -> return Nothing iis@((maxS, _) : _) -> do@@ -454,24 +506,21 @@ let cstore = if isJust (lookup iid bodyAssocs) then COrgan else CEqp return $ Just (iid, cstore) +-- @MonadStateRead@ would be enough, but the logic is sound only on server. currentSkillsServer :: MonadServer m => ActorId -> m Ability.Skills currentSkillsServer aid = do- ar <- getsServer $ (EM.! aid) . sactorAspect body <- getsState $ getActorBody aid fact <- getsState $ (EM.! bfid body) . sfactionD- let mleader = _gleader fact- getsState $ actorSkills mleader aid ar+ let mleader = gleader fact+ getsState $ actorSkills mleader aid getCacheLucid :: MonadServer m => LevelId -> m FovLucid getCacheLucid lid = do- discoAspect <- getsServer sdiscoAspect- actorAspect <- getsServer sactorAspect fovClearLid <- getsServer sfovClearLid fovLitLid <- getsServer sfovLitLid fovLucidLid <- getsServer sfovLucidLid let getNewLucid = getsState $ \s ->- lucidFromLevel discoAspect actorAspect fovClearLid fovLitLid- s lid (sdungeon s EM.! lid)+ lucidFromLevel fovClearLid fovLitLid s lid (sdungeon s EM.! lid) case EM.lookup lid fovLucidLid of Just (FovValid fovLucid) -> return fovLucid _ -> do@@ -488,7 +537,7 @@ case ptotal perCacheOld of FovValid total -> return total FovInvalid -> do- actorAspect <- getsServer sactorAspect+ actorAspect <- getsState sactorAspect fovClearLid <- getsServer sfovClearLid getActorB <- getsState $ flip getActorBody let perActorNew =@@ -502,12 +551,3 @@ fperCache = EM.adjust (EM.insert lid perCache) fid modifyServer $ \ser -> ser {sperCacheFid = fperCache $ sperCacheFid ser} return total--recomputeCachePer :: MonadServer m => FactionId -> LevelId -> m Perception-recomputeCachePer fid lid = do- total <- getCacheTotal fid lid- fovLucid <- getCacheLucid lid- let perNew = perceptionFromPTotal fovLucid total- fper = EM.adjust (EM.insert lid perNew) fid- modifyServer $ \ser -> ser {sperFid = fper $ sperFid ser}- return perNew
Game/LambdaHack/Server/DebugM.hs view
@@ -1,7 +1,11 @@--- | Debug output for requests and responseQs.+-- | Debug output for requests and responses. module Game.LambdaHack.Server.DebugM ( debugResponse , debugRequestAI, debugRequestUI+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , debugShow, debugPretty, debugPlain, DebugAid(..), debugAid+#endif ) where import Prelude ()@@ -9,18 +13,17 @@ import Game.LambdaHack.Common.Prelude import qualified Data.EnumMap.Strict as EM-import Data.Int (Int64)+import Data.Int (Int64) import qualified Data.Text as T import qualified Text.Show.Pretty as Show.Pretty import Game.LambdaHack.Atomic+import Game.LambdaHack.Client import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Level import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.Response import Game.LambdaHack.Common.Time import Game.LambdaHack.Server.MonadServer import Game.LambdaHack.Server.State@@ -34,43 +37,51 @@ debugShow = T.pack . Show.Pretty.ppShow debugResponse :: MonadServer m => FactionId -> Response -> m ()-debugResponse fid cmd = case cmd of- RespUpdAtomic cmdA@UpdPerception{} -> debugPlain fid cmd cmdA- RespUpdAtomic cmdA@UpdResume{} -> debugPlain fid cmd cmdA- RespUpdAtomic cmdA@UpdSpotTile{} -> debugPlain fid cmd cmdA- RespUpdAtomic cmdA -> debugPretty fid cmd cmdA+debugResponse fid resp = case resp of+ RespUpdAtomic _ cmd@UpdPerception{} -> debugPlain fid "RespUpdAtomic" cmd+ RespUpdAtomic _ cmd@UpdResume{} -> debugPlain fid "RespUpdAtomic" cmd+ RespUpdAtomic _ cmd@UpdSpotTile{} -> debugPlain fid "RespUpdAtomic" cmd+ RespUpdAtomic _ cmd -> debugPretty fid "RespUpdAtomic" cmd+ RespUpdAtomicNoState cmd@UpdPerception{} ->+ debugPlain fid "RespUpdAtomicNoState" cmd+ RespUpdAtomicNoState cmd@UpdResume{} ->+ debugPlain fid "RespUpdAtomicNoState" cmd+ RespUpdAtomicNoState cmd@UpdSpotTile{} ->+ debugPlain fid "RespUpdAtomicNoState" cmd+ RespUpdAtomicNoState cmd ->+ debugPretty fid "RespUpdAtomicNoState" cmd RespQueryAI aid -> do- d <- debugAid aid "RespQueryAI" cmd+ d <- debugAid aid "RespQueryAI" serverPrint d RespSfxAtomic sfx -> do ps <- posSfxAtomic sfx- serverPrint $ debugShow (fid, cmd, ps)+ serverPrint $ debugShow (fid, "RespSfxAtomic" :: Text, ps) RespQueryUI -> serverPrint "RespQueryUI" -debugPretty :: (MonadServer m, Show a) => FactionId -> a -> UpdAtomic -> m ()-debugPretty fid cmd cmdA = do- ps <- posUpdAtomic cmdA- serverPrint $ debugShow (fid, cmd, ps)+debugPretty :: MonadServer m => FactionId -> Text -> UpdAtomic -> m ()+debugPretty fid t cmd = do+ ps <- posUpdAtomic cmd+ serverPrint $ debugShow (fid, t, ps, cmd) -debugPlain :: (MonadServer m, Show a) => FactionId -> a -> UpdAtomic -> m ()-debugPlain fid cmd cmdA = do- ps <- posUpdAtomic cmdA- serverPrint $ T.pack $ show (fid, cmd, ps) -- too large for pretty printing+debugPlain :: MonadServer m => FactionId -> Text -> UpdAtomic -> m ()+debugPlain fid t cmd = do+ ps <- posUpdAtomic cmd+ serverPrint $ T.pack $ show (fid, t, ps, cmd)+ -- too large for pretty printing -debugRequestAI :: MonadServer m => ActorId -> RequestAI -> m ()-debugRequestAI aid cmd = do- d <- debugAid aid "AI request" cmd+debugRequestAI :: MonadServer m => ActorId -> m ()+debugRequestAI aid = do+ d <- debugAid aid "AI request" serverPrint d -debugRequestUI :: MonadServer m => ActorId -> RequestUI -> m ()-debugRequestUI aid cmd = do- d <- debugAid aid "UI request" cmd+debugRequestUI :: MonadServer m => ActorId -> m ()+debugRequestUI aid = do+ d <- debugAid aid "UI request" serverPrint d -data DebugAid a = DebugAid+data DebugAid = DebugAid { label :: Text , aid :: ActorId- , cmd :: a , faction :: FactionId , lid :: LevelId , bHP :: Int64@@ -79,14 +90,13 @@ } deriving Show -debugAid :: (MonadServer m, Show a) => ActorId -> Text -> a -> m Text-debugAid aid label cmd = do+debugAid :: MonadServer m => ActorId -> Text -> m Text+debugAid aid label = do b <- getsState $ getActorBody aid time <- getsState $ getLocalTime (blid b) btime <- getsServer $ (EM.! aid) . (EM.! blid b) . (EM.! bfid b) . sactorTime return $! debugShow DebugAid { label , aid- , cmd , faction = bfid b , lid = blid b , bHP = bhp b
Game/LambdaHack/Server/DungeonGen.hs view
@@ -1,9 +1,11 @@--- | The unpopulated dungeon generation routine.+-- | The dungeon generation routine. It creates empty dungeons, without+-- actors and without items, either lying on the floor or embedded inside tiles. module Game.LambdaHack.Server.DungeonGen ( FreshDungeon(..), dungeonGen #ifdef EXPOSE_INTERNAL -- * Internal operations- , convertTileMaps, placeDownStairs, buildLevel, levelFromCaveKind+ , convertTileMaps, buildTileMap, buildLevel, placeDownStairs+ , levelFromCaveKind #endif ) where @@ -14,29 +16,28 @@ import qualified Control.Monad.Trans.State.Strict as St import qualified Data.EnumMap.Strict as EM import qualified Data.IntMap.Strict as IM-import Data.Tuple+import Data.Tuple import qualified System.Random as R -import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Frequency import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Random import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.CaveKind-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.PlaceKind (PlaceKind)-import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.CaveKind+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.PlaceKind (PlaceKind)+import Game.LambdaHack.Content.TileKind (TileKind) import qualified Game.LambdaHack.Content.TileKind as TK-import Game.LambdaHack.Server.DungeonGen.Cave-import Game.LambdaHack.Server.DungeonGen.Place+import Game.LambdaHack.Server.DungeonGen.Cave+import Game.LambdaHack.Server.DungeonGen.Place -convertTileMaps :: Kind.COps -> Bool- -> Rnd (Kind.Id TileKind) -> Maybe (Rnd (Kind.Id TileKind))- -> Int -> Int -> TileMapEM+convertTileMaps :: Kind.COps -> Bool -> Rnd (Kind.Id TileKind)+ -> Maybe (Rnd (Kind.Id TileKind)) -> Int -> Int -> TileMapEM -> Rnd TileMap convertTileMaps Kind.COps{coTileSpeedup} areAllWalkable cdefTile mpickPassable cxsize cysize ltile = do@@ -107,7 +108,7 @@ convertTileMaps cops areAllWalkable pickDefTile mpickPassable cxsize cysize dmap --- | Create a level from a cave.+-- Create a level from a cave. buildLevel :: Kind.COps -> Int -> GroupName CaveKind -> Int -> AbsDepth -> [Point] -> Rnd (Level, [Point])@@ -169,7 +170,7 @@ (dnight cave) return (lvl, lstairsDouble ++ lstairsSingleDown) --- | Places yet another staircase (or escape), taking into account only+-- Places yet another staircase (or escape), taking into account only -- the already existing stairs. placeDownStairs :: CaveKind -> [Point] -> Rnd Point placeDownStairs kc@CaveKind{..} ps = do@@ -195,10 +196,9 @@ in if dist 0 np && distProj np then Just np else Nothing findPoint cxsize cysize f --- | Build rudimentary level from a cave kind.-levelFromCaveKind :: Kind.COps- -> CaveKind -> AbsDepth -> TileMap -> ([Point], [Point])- -> Int -> [Point] -> Bool+-- Build rudimentary level from a cave kind.+levelFromCaveKind :: Kind.COps -> CaveKind -> AbsDepth -> TileMap+ -> ([Point], [Point]) -> Int -> [Point] -> Bool -> Level levelFromCaveKind Kind.COps{coTileSpeedup} CaveKind{ cactorCoeff=lactorCoeff@@ -219,7 +219,6 @@ , lxsize = cxsize , lysize = cysize , lsmell = EM.empty- , ldesc = cname , lstair , lseen = 0 , lexplorable@@ -230,6 +229,8 @@ , litemFreq , lescape , lnight+ , lname = cname+ , ldesc = cdesc } -- | Freshly generated and not yet populated dungeon.
Game/LambdaHack/Server/DungeonGen/Area.hs view
@@ -1,15 +1,14 @@ -- | Rectangular areas of levels and their basic operations. module Game.LambdaHack.Server.DungeonGen.Area- ( Area, toArea, fromArea, trivialArea, isTrivialArea- , grid, shrink, expand, sumAreas- , SpecialArea(..)+ ( Area, toArea, fromArea, trivialArea, isTrivialArea, mkFixed+ , SpecialArea(..), grid, shrink, expand, sumAreas ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.IntSet as IS @@ -35,6 +34,22 @@ isTrivialArea :: Area -> Bool isTrivialArea (Area x0 y0 x1 y1) = x0 == x1 && y0 == y1++-- Doesn't respect minimum sizes, because staircases are specified verbatim,+-- so can't be arbitrarily scaled up.+-- The size may be one more than what maximal size hint requests,+-- but this is safe (limited by area size) and makes up for the rigidity+-- of the fixed room sizes (e.g., that the size is always odd).+mkFixed :: (X, Y) -- ^ maximum size+ -> Area -- ^ the containing area, not the room itself+ -> Point -- ^ the center point+ -> Area+mkFixed (xMax, yMax) area p@Point{..} =+ let (x0, y0, x1, y1) = fromArea area+ xradius = min ((xMax + 1) `div` 2) $ min (px - x0) (x1 - px)+ yradius = min ((yMax + 1) `div` 2) $ min (py - y0) (y1 - py)+ a = (px - xradius, py - yradius, px + xradius, py + yradius)+ in fromMaybe (error $ "" `showFailure` (a, xMax, yMax, area, p)) $ toArea a data SpecialArea = SpecialArea Area
Game/LambdaHack/Server/DungeonGen/AreaRnd.hs view
@@ -1,11 +1,15 @@ -- | Operations on the 'Area' type that involve random numbers. module Game.LambdaHack.Server.DungeonGen.AreaRnd ( -- * Picking points inside areas- xyInArea, mkVoidRoom, mkRoom, mkFixed+ xyInArea, mkVoidRoom, mkRoom -- * Choosing connections , connectGrid, randomConnection -- * Plotting corridors , HV(..), Corridor, connectPlaces+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , connectGrid', sortPoint, mkCorridor, borderPlace+#endif ) where import Prelude ()@@ -56,22 +60,6 @@ let a3 = (rx1, ry1, rx1 + xW - 1, ry1 + yW - 1) area3 = fromMaybe (error $ "" `showFailure` a3) $ toArea a3 return $! area3---- Doesn't respect minimum sizes, because staircases are specified verbatim,--- so can't be arbitrarily scaled up.--- The size may be one more than what maximal size hint requests,--- but this is safe (limited by area size) and makes up for the rigidity--- of the fixed room sizes (e.g., that the size is always odd).-mkFixed :: (X, Y) -- ^ maximum size- -> Area -- ^ the containing area, not the room itself- -> Point -- ^ the center point- -> Area-mkFixed (xM, yM) area p@Point{..} =- let (x0, y0, x1, y1) = fromArea area- xradius = min ((xM + 1) `div` 2) $ min (px - x0) (x1 - px)- yradius = min ((yM + 1) `div` 2) $ min (py - y0) (y1 - py)- a = (px - xradius, py - yradius, px + xradius, py + yradius)- in fromMaybe (error $ "" `showFailure` (a, xM, yM, area, p)) $ toArea a -- Choosing connections between areas in a grid
Game/LambdaHack/Server/DungeonGen/Cave.hs view
@@ -1,6 +1,10 @@ -- | Generation of caves (not yet inhabited dungeon levels) from cave kinds. module Game.LambdaHack.Server.DungeonGen.Cave ( Cave(..), bootFixedCenters, buildCave+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , pickOpening, digCorridors+#endif ) where import Prelude ()@@ -9,21 +13,21 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Key (mapWithKeyM)+import Data.Key (mapWithKeyM) import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.CaveKind-import Game.LambdaHack.Content.PlaceKind-import Game.LambdaHack.Content.TileKind (TileKind)-import Game.LambdaHack.Server.DungeonGen.Area-import Game.LambdaHack.Server.DungeonGen.AreaRnd-import Game.LambdaHack.Server.DungeonGen.Place+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.CaveKind+import Game.LambdaHack.Content.PlaceKind+import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Server.DungeonGen.Area+import Game.LambdaHack.Server.DungeonGen.AreaRnd+import Game.LambdaHack.Server.DungeonGen.Place -- | The type of caves (not yet inhabited dungeon levels). data Cave = Cave@@ -38,29 +42,28 @@ bootFixedCenters :: CaveKind -> [Point] bootFixedCenters CaveKind{..} = [Point 4 3, Point (cxsize - 5) (cysize - 4)] -{--Rogue cave is generated by an algorithm inspired by the original Rogue,-as follows:+{- |+Generate a cave using an algorithm inspired by the original Rogue,+as follows (in gross simplification): - * The available area is divided into a grid, e.g, 3 by 3,- where each of the 9 grid cells has approximately the same size.+* The available area is divided into a grid, e.g, 3 by 3,+ where each of the 9 grid cells has approximately the same size. - * In each of the 9 grid cells one room is placed at a random position- and with a random size, but larger than The minimum size,- e.g, 2 by 2 floor tiles.+* In some of the 9 grid cells a room is placed at a random position+ and with a random size, but larger than the minimum size,+ e.g, 2 by 2 floor tiles. - * Rooms that are on horizontally or vertically adjacent grid cells- may be connected by a corridor. Corridors consist of 3 segments of straight- lines (either "horizontal, vertical, horizontal" or "vertical, horizontal,- vertical"). They end in openings in the walls of the room they connect.- It is possible that one or two of the 3 segments have length 0, such that- the resulting corridor is L-shaped or even a single straight line.+* Rooms that are on horizontally or vertically adjacent grid cells+ may be connected by a corridor. Corridors consist of 3 segments of straight+ lines (either "horizontal, vertical, horizontal" or "vertical, horizontal,+ vertical"). They end in openings in the walls of the room they connect.+ It is possible that one or two of the 3 segments have length 0, such that+ the resulting corridor is L-shaped or even a single straight line. - * Corridors are generated randomly in such a way that at least every room- on the grid is connected, and a few more might be. It is not sufficient- to always connect all adjacent rooms.+* Corridors are generated randomly in such a way that at least every room+ on the grid is connected, and a few more might be. It is not sufficient+ to always connect all adjacent rooms, because not each cell holds a room. -}--- | Cave generation by an algorithm inspired by the original Rogue, buildCave :: Kind.COps -- ^ content definitions -> AbsDepth -- ^ depth of the level to generate -> AbsDepth -- ^ absolute depth
Game/LambdaHack/Server/DungeonGen/Place.hs view
@@ -1,33 +1,37 @@ {-# LANGUAGE RankNTypes #-} -- | Generation of places from place kinds. module Game.LambdaHack.Server.DungeonGen.Place- ( TileMapEM, Place(..), isChancePos, placeCheck, buildFenceRnd, buildPlace+ ( Place(..), TileMapEM, placeCheck, buildPlace, isChancePos, buildFenceRnd+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , interiorArea, olegend, ooverride, buildFence, tilePlace+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.Bits as Bits import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.Text as T -import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Frequency import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.CaveKind-import Game.LambdaHack.Content.PlaceKind-import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Content.CaveKind+import Game.LambdaHack.Content.PlaceKind+import Game.LambdaHack.Content.TileKind (TileKind) import qualified Game.LambdaHack.Content.TileKind as TK-import Game.LambdaHack.Server.DungeonGen.Area+import Game.LambdaHack.Server.DungeonGen.Area --- | The parameters of a place. All are immutable and set+-- | The parameters of a place. All are immutable and rolled and fixed -- at the time when a place is generated. data Place = Place { qkind :: Kind.Id PlaceKind
Game/LambdaHack/Server/EndM.hs view
@@ -1,7 +1,10 @@--- | The main loop of the server, processing human and computer player--- moves turn by turn.+-- | Server operations used when ending game and deciding whether to end. module Game.LambdaHack.Server.EndM- ( endOrLoop, dieSer+ ( endOrLoop, dieSer, writeSaveAll+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , gameExit, dropAllItems+#endif ) where import Prelude ()@@ -11,6 +14,7 @@ import qualified Data.EnumMap.Strict as EM import Game.LambdaHack.Atomic+import Game.LambdaHack.Client import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.Faction@@ -20,16 +24,19 @@ import Game.LambdaHack.Common.State import Game.LambdaHack.Content.ModeKind import Game.LambdaHack.Server.CommonM+import Game.LambdaHack.Server.Fov import Game.LambdaHack.Server.HandleEffectM import Game.LambdaHack.Server.ItemM import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.ProtocolM+import Game.LambdaHack.Server.ServerOptions import Game.LambdaHack.Server.State -- | Continue or exit or restart the game.-endOrLoop :: (MonadAtomic m, MonadServer m)- => m () -> (Maybe (GroupName ModeKind) -> m ()) -> m () -> m ()+endOrLoop :: (MonadServerAtomic m, MonadServerReadRequest m)+ => m () -> (Maybe (GroupName ModeKind) -> m ()) -> m () -> m ()-endOrLoop loop restart gameExit gameSave = do+endOrLoop loop restart gameSave = do factionD <- getsState sfactionD let inGame fact = case gquit fact of Nothing -> True@@ -56,10 +63,55 @@ | not $ null campers -> gameExit -- and @loop@ is not called | otherwise -> loop -- continue current game -dieSer :: (MonadAtomic m, MonadServer m) => ActorId -> Actor -> m ()+gameExit :: (MonadServerAtomic m, MonadServerReadRequest m) => m ()+gameExit = do+ -- Verify that the not saved caches are equal to future reconstructed.+ -- Otherwise, save/restore would change game state.+-- debugPossiblyPrint "Verifying all perceptions."+ sperCacheFid <- getsServer sperCacheFid+ sperValidFid <- getsServer sperValidFid+ sactorAspect2 <- getsState sactorAspect+ sfovLucidLid <- getsServer sfovLucidLid+ sfovClearLid <- getsServer sfovClearLid+ sfovLitLid <- getsServer sfovLitLid+ sperFid <- getsServer sperFid+ actorAspect <- getsState actorAspectInDungeon+ ( fovLitLid, fovClearLid, fovLucidLid+ ,perValidFid, perCacheFid, perFid ) <- getsState perFidInDungeon+ let !_A7 = assert (sfovLitLid == fovLitLid+ `blame` "wrong accumulated sfovLitLid"+ `swith` (sfovLitLid, fovLitLid)) ()+ !_A6 = assert (sfovClearLid == fovClearLid+ `blame` "wrong accumulated sfovClearLid"+ `swith` (sfovClearLid, fovClearLid)) ()+ !_A5 = assert (sactorAspect2 == actorAspect+ `blame` "wrong accumulated sactorAspect"+ `swith` (sactorAspect2, actorAspect)) ()+ !_A4 = assert (sfovLucidLid == fovLucidLid+ `blame` "wrong accumulated sfovLucidLid"+ `swith` (sfovLucidLid, fovLucidLid)) ()+ !_A3 = assert (sperValidFid == perValidFid+ `blame` "wrong accumulated sperValidFid"+ `swith` (sperValidFid, perValidFid)) ()+ !_A2 = assert (sperCacheFid == perCacheFid+ `blame` "wrong accumulated sperCacheFid"+ `swith` (sperCacheFid, perCacheFid)) ()+ !_A1 = assert (sperFid == perFid+ `blame` "wrong accumulated perception"+ `swith` (sperFid, perFid)) ()+ -- Kill all clients, including those that did not take part+ -- in the current game.+ -- Clients exit not now, but after they print all ending screens.+ -- debugPrint "Server kills clients"+-- debugPossiblyPrint "Killing all clients."+ killAllClients+-- debugPossiblyPrint "All clients killed."+ return ()++dieSer :: MonadServerAtomic m => ActorId -> Actor -> m () dieSer aid b = do unless (bproj b) $ do- discoKind <- getsServer sdiscoKind+ discoKind <- getsState sdiscoKind trunk <- getsState $ getItemBody $ btrunk b let KindMean{kmKind} = discoKind EM.! jkindIx trunk execUpdAtomic $ UpdRecordKill aid kmKind 1@@ -71,7 +123,7 @@ -- Projectiles can't drop stash, because they are blind and so the faction -- would not see the actor that drops the stash, leading to a crash. -- But this is OK; projectiles can't be leaders, so stash dropped earlier.- when (isNothing $ _gleader fact) $ moveStores False aid CSha CInv+ when (isNothing $ gleader fact) $ moveStores False aid CSha CInv -- If the actor was a projectile and no effect was triggered by hitting -- an enemy, the item still exists and @OnSmash@ effects will be triggered: dropAllItems aid b@@ -79,8 +131,16 @@ execUpdAtomic $ UpdDestroyActor aid b2 [] -- | Drop all actor's items.-dropAllItems :: (MonadAtomic m, MonadServer m)- => ActorId -> Actor -> m ()+dropAllItems :: MonadServerAtomic m => ActorId -> Actor -> m () dropAllItems aid b = do mapActorCStore_ CInv (dropCStoreItem False CInv aid b maxBound) b mapActorCStore_ CEqp (dropCStoreItem False CEqp aid b maxBound) b++-- | Save game on server and all clients.+writeSaveAll :: MonadServerAtomic m => Bool -> m ()+writeSaveAll uiRequested = do+ bench <- getsServer $ sbenchmark . sclientOptions . soptions+ noConfirmsGame <- isNoConfirmsGame+ when (uiRequested || not bench && not noConfirmsGame) $ do+ execUpdAtomic UpdWriteSave+ saveServer
Game/LambdaHack/Server/Fov.hs view
@@ -1,32 +1,24 @@--- | Field Of View scanning with a variety of algorithms.+-- | Field Of View scanning.+-- -- See <https://github.com/LambdaHack/LambdaHack/wiki/Fov-and-los> -- for discussion. module Game.LambdaHack.Server.Fov ( -- * Perception cache- FovValid(..)- , PerValidFid- , PerReachable(..)- , CacheBeforeLucid(..)- , PerActor- , PerceptionCache(..)- , PerCacheLid- , PerCacheFid+ FovValid(..), PerValidFid+ , PerReachable(..), CacheBeforeLucid(..), PerActor+ , PerceptionCache(..), PerCacheLid, PerCacheFid -- * Data used in FOV computation and cached to speed it up , FovShine(..), FovLucid(..), FovLucidLid , FovClear(..), FovClearLid, FovLit (..), FovLitLid- -- * Update of invalidated Fov data- , perceptionFromPTotal, perActorFromLevel, totalFromPerActor, lucidFromLevel- -- * Computation of initial perception and caches- , perFidInDungeon, aspectRecordFromActorServer, boundSightByCalm+ -- * Operations+ , perceptionFromPTotal, perActorFromLevel, boundSightByCalm+ , totalFromPerActor, lucidFromLevel, perFidInDungeon #ifdef EXPOSE_INTERNAL -- * Internal operations- , cacheBeforeLucidFromActor- , perceptionCacheFromLevel, perLidFromFaction- , clearFromLevel, clearInDungeon- , litFromLevel, litInDungeon, shineFromLevel- , floorLightSources, lucidFromItems, lucidInDungeon- -- * The actual Fov algorithm- , fullscan+ , cacheBeforeLucidFromActor, shineFromLevel, floorLightSources, lucidFromItems+ , litFromLevel, litInDungeon, clearFromLevel, clearInDungeon, lucidInDungeon+ , perLidFromFaction, perceptionCacheFromLevel+ , Matrix, fullscan #endif ) where @@ -36,23 +28,23 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Int (Int64)-import GHC.Exts (inline)+import Data.Int (Int64)+import GHC.Exts (inline) -import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Server.FovDigital+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Server.FovDigital -- * Perception cache types @@ -100,10 +92,7 @@ -- | Map from level positions that currently hold item or actor(s) with shine -- to the maximum of radiuses of the shining lights. ----- Note: @ActorAspect@ and @FovShine@ shoudn't be in @State@,--- because on client they need to be updated every time an item discovery--- is made, unlike on the server, where it's much simpler and cheaper.--- BTW, floor and (many projectile) actors light on a single tile+-- Note that floor and (many projectile) actors light on a single tile -- should be additive for @FovShine@ to be incrementally updated. -- -- @FovShine@ should not even be kept in @StateServer@, because it's cheap@@ -175,7 +164,7 @@ totalFromPerActor :: PerActor -> CacheBeforeLucid totalFromPerActor perActor =- let as = map (\a -> case a of+ let as = map (\case FovValid x -> x FovInvalid -> error $ "" `showFailure` perActor) $ EM.elems perActor@@ -196,25 +185,23 @@ -- but it's rare that cmd changed them, but not the perception -- (e.g., earthquake in an uninhabited corner of the active arena, -- but the we'd probably want some feedback, at least sound).-lucidFromLevel :: DiscoveryAspect -> ActorAspect -> FovClearLid -> FovLitLid- -> State -> LevelId -> Level+lucidFromLevel :: FovClearLid -> FovLitLid -> State -> LevelId -> Level -> FovLucid-lucidFromLevel discoAspect actorAspect fovClearLid fovLitLid s lid lvl =- let shine = shineFromLevel discoAspect actorAspect s lid lvl+lucidFromLevel fovClearLid fovLitLid s lid lvl =+ let shine = shineFromLevel s lid lvl lucids = lucidFromItems (fovClearLid EM.! lid) $ EM.assocs $ fovShine shine litTiles = fovLitLid EM.! lid in FovLucid $ ES.unions $ fovLit litTiles : map fovLucid lucids -shineFromLevel :: DiscoveryAspect -> ActorAspect -> State -> LevelId -> Level- -> FovShine-shineFromLevel discoAspect actorAspect s lid lvl =+shineFromLevel :: State -> LevelId -> Level -> FovShine+shineFromLevel s lid lvl = let actorLights = [ (bpos b, radius) | (aid, b) <- inline actorAssocs (const True) lid s- , let radius = aShine $ actorAspect EM.! aid+ , let radius = aShine $ sactorAspect s EM.! aid , radius > 0 ]- floorLights = floorLightSources discoAspect lvl+ floorLights = floorLightSources (sdiscoAspect s) lvl allLights = floorLights ++ actorLights -- If there is light both on the floor and carried by actor -- (or several projectile actors), its radius is the maximum.@@ -249,32 +236,19 @@ -- * Computation of initial perception and caches -- | Calculate the perception and its caches for the whole dungeon.-perFidInDungeon :: DiscoveryAspect -> State- -> ( ActorAspect, FovLitLid, FovClearLid, FovLucidLid- , PerValidFid, PerCacheFid, PerFid)-perFidInDungeon discoAspect s =- let actorAspect = actorAspectInDungeon discoAspect s- fovLitLid = litInDungeon s+perFidInDungeon :: State -> ( FovLitLid, FovClearLid, FovLucidLid+ , PerValidFid, PerCacheFid, PerFid)+perFidInDungeon s =+ let fovLitLid = litInDungeon s fovClearLid = clearInDungeon s- fovLucidLid =- lucidInDungeon discoAspect actorAspect fovClearLid fovLitLid s+ fovLucidLid = lucidInDungeon fovClearLid fovLitLid s perValidLid = EM.map (const True) (sdungeon s) perValidFid = EM.map (const perValidLid) (sfactionD s)- f fid _ = perLidFromFaction actorAspect fovLucidLid fovClearLid fid s+ f fid _ = perLidFromFaction fovLucidLid fovClearLid fid s em = EM.mapWithKey f $ sfactionD s- in ( actorAspect, fovLitLid, fovClearLid, fovLucidLid+ in ( fovLitLid, fovClearLid, fovLucidLid , perValidFid, EM.map snd em, EM.map fst em) -aspectRecordFromActorServer :: DiscoveryAspect -> Actor -> AspectRecord-aspectRecordFromActorServer discoAspect b =- let processIid (iid, (k, _)) = (discoAspect EM.! iid, k)- processBag ass = sumAspectRecord $ map processIid ass- in processBag $ EM.assocs (borgan b) ++ EM.assocs (beqp b)--actorAspectInDungeon :: DiscoveryAspect -> State -> ActorAspect-actorAspectInDungeon discoAspect s =- EM.map (aspectRecordFromActorServer discoAspect) $ sactorD s- litFromLevel :: Kind.COps -> Level -> FovLit litFromLevel Kind.COps{coTileSpeedup} Level{ltile} = let litSet p t set = if Tile.isLit coTileSpeedup t then p : set else set@@ -290,23 +264,20 @@ clearInDungeon :: State -> FovClearLid clearInDungeon s = EM.map (clearFromLevel (scops s)) $ sdungeon s -lucidInDungeon :: DiscoveryAspect -> ActorAspect -> FovClearLid -> FovLitLid- -> State- -> FovLucidLid-lucidInDungeon discoAspect actorAspect fovClearLid fovLitLid s =+lucidInDungeon :: FovClearLid -> FovLitLid -> State-> FovLucidLid+lucidInDungeon fovClearLid fovLitLid s = EM.mapWithKey (\lid lvl -> FovValid $- lucidFromLevel discoAspect actorAspect fovClearLid fovLitLid s lid lvl)+ lucidFromLevel fovClearLid fovLitLid s lid lvl) $ sdungeon s -- | Calculate perception of a faction.-perLidFromFaction :: ActorAspect -> FovLucidLid -> FovClearLid- -> FactionId -> State+perLidFromFaction :: FovLucidLid -> FovClearLid -> FactionId -> State -> (PerLid, PerCacheLid)-perLidFromFaction actorAspect fovLucidLid fovClearLid fid s =+perLidFromFaction fovLucidLid fovClearLid fid s = let em = EM.mapWithKey (\lid _ ->- perceptionCacheFromLevel actorAspect fovClearLid fid lid s)- (sdungeon s)+ perceptionCacheFromLevel fovClearLid fid lid s)+ (sdungeon s) fovLucid lid = case EM.lookup lid fovLucidLid of Just (FovValid fl) -> fl _ -> error $ "" `showFailure` (lid, fovLucidLid)@@ -316,14 +287,13 @@ perceptionFromPTotal (fovLucid lid) (getValid (ptotal pc))) em , em ) -perceptionCacheFromLevel :: ActorAspect -> FovClearLid- -> FactionId -> LevelId -> State+perceptionCacheFromLevel :: FovClearLid -> FactionId -> LevelId -> State -> PerceptionCache-perceptionCacheFromLevel actorAspect fovClearLid fid lid s =+perceptionCacheFromLevel fovClearLid fid lid s = let fovClear = fovClearLid EM.! lid lvlBodies = inline actorAssocs (== fid) lid s f (aid, b) =- let ar@AspectRecord{..} = actorAspect EM.! aid+ let ar@AspectRecord{..} = sactorAspect s EM.! aid in if aSight <= 0 && aNocto <= 0 && aSmell <= 0 -- dumb missiles then Nothing else Just (aid, FovValid $ cacheBeforeLucidFromActor fovClear b ar)
Game/LambdaHack/Server/FovDigital.hs view
@@ -23,7 +23,7 @@ import qualified Data.EnumSet as ES -import Game.LambdaHack.Common.Point hiding (inside)+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray -- | Distance from the (0, 0) point where FOV originates.
Game/LambdaHack/Server/HandleAtomicM.hs view
@@ -1,10 +1,10 @@--- | Handle atomic commands before they are executed to change State--- and sent to clients.+-- | Handle atomic commands on the server, after they are executed+-- to change server 'State' and before they are sent to clients. module Game.LambdaHack.Server.HandleAtomicM ( cmdAtomicSemSer #ifdef EXPOSE_INTERNAL -- * Internal operations- , addItemToActor, updateSclear, updateSlit+ , invalidateArenas, updateSclear, updateSlit , invalidateLucidLid, invalidateLucidAid , actorHasShine, itemAffectsShineRadius, itemAffectsPerRadius , addPerActor, addPerActorAny, deletePerActor, deletePerActorAny@@ -19,110 +19,126 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES -import Game.LambdaHack.Atomic-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Atomic+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.TileKind (TileKind)-import Game.LambdaHack.Server.Fov-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.State+import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Server.Fov+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.State -- | Effect of atomic actions on server state is calculated--- with the global state from before the command is executed.-cmdAtomicSemSer :: MonadServer m => UpdAtomic -> m ()-cmdAtomicSemSer cmd = case cmd of+-- with the global state from after the command is executed+-- (except where the supplied @oldState@ is used).+cmdAtomicSemSer :: MonadServer m => State -> UpdAtomic -> m ()+cmdAtomicSemSer oldState cmd = case cmd of UpdCreateActor aid b _ -> do- discoAspect <- getsServer sdiscoAspect- let aspectRecord = aspectRecordFromActorServer discoAspect b- f = EM.insert aid aspectRecord- modifyServer $ \ser -> ser {sactorAspect = f $ sactorAspect ser}- actorAspect <- getsServer sactorAspect- -- We don't have the body in the State yet, hence no @invalidateLucidAid@.+ actorAspect <- getsState sactorAspect when (actorHasShine actorAspect aid) $ invalidateLucidLid $ blid b addPerActor aid b UpdDestroyActor aid b _ -> do- deletePerActor aid b- actorAspectOld <- getsServer sactorAspect+ let actorAspectOld = sactorAspect oldState when (actorHasShine actorAspectOld aid) $ invalidateLucidLid $ blid b- modifyServer $ \ser -> ser {sactorAspect = EM.delete aid $ sactorAspect ser}+ deletePerActor actorAspectOld aid b modifyServer $ \ser -> ser {sactorTime = EM.adjust (EM.adjust (EM.delete aid) (blid b)) (bfid b) (sactorTime ser)} UpdCreateItem iid _ _ (CFloor lid _) -> do- discoAspect <- getsServer sdiscoAspect+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid []) $ invalidateLucidLid lid- UpdCreateItem iid _ (k, _) (CActor aid store) -> do- discoAspect <- getsServer sdiscoAspect+ UpdCreateItem iid _ _ (CActor aid store) -> do+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid [store]) $ invalidateLucidAid aid- when (store `elem` [CEqp, COrgan]) $ do- addItemToActor iid k aid+ when (store `elem` [CEqp, COrgan]) $ when (itemAffectsPerRadius discoAspect iid) $ reconsiderPerActor aid UpdDestroyItem iid _ _ (CFloor lid _) -> do- discoAspect <- getsServer sdiscoAspect+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid []) $ invalidateLucidLid lid- UpdDestroyItem iid _ (k, _) (CActor aid store) -> do- discoAspect <- getsServer sdiscoAspect+ UpdDestroyItem iid _ _ (CActor aid store) -> do+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid [store]) $ invalidateLucidAid aid- when (store `elem` [CEqp, COrgan]) $ do- addItemToActor iid (-k) aid+ when (store `elem` [CEqp, COrgan]) $ when (itemAffectsPerRadius discoAspect iid) $ reconsiderPerActor aid UpdSpotActor aid b _ -> do -- On server, it does't affect aspects, but does affect lucid (Ascend).- actorAspect <- getsServer sactorAspect- -- We don't have the body in the State yet, hence no @invalidateLucidAid@.+ actorAspect <- getsState sactorAspect when (actorHasShine actorAspect aid) $ invalidateLucidLid $ blid b addPerActor aid b UpdLoseActor aid b _ -> do -- On server, it does't affect aspects, but does affect lucid (Ascend).- deletePerActor aid b- actorAspectOld <- getsServer sactorAspect+ let actorAspectOld = sactorAspect oldState when (actorHasShine actorAspectOld aid) $ invalidateLucidLid $ blid b+ deletePerActor actorAspectOld aid b modifyServer $ \ser -> ser {sactorTime = EM.adjust (EM.adjust (EM.delete aid) (blid b)) (bfid b) (sactorTime ser)} UpdSpotItem _ iid _ _ (CFloor lid _) -> do- discoAspect <- getsServer sdiscoAspect+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid []) $ invalidateLucidLid lid- UpdSpotItem _ iid _ (k, _) (CActor aid store) -> do- discoAspect <- getsServer sdiscoAspect+ UpdSpotItem _ iid _ _ (CActor aid store) -> do+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid [store]) $ invalidateLucidAid aid- when (store `elem` [CEqp, COrgan]) $ do- addItemToActor iid k aid+ when (store `elem` [CEqp, COrgan]) $ when (itemAffectsPerRadius discoAspect iid) $ reconsiderPerActor aid UpdLoseItem _ iid _ _ (CFloor lid _) -> do- discoAspect <- getsServer sdiscoAspect+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid []) $ invalidateLucidLid lid- UpdLoseItem _ iid _ (k, _) (CActor aid store) -> do- discoAspect <- getsServer sdiscoAspect+ UpdLoseItem _ iid _ _ (CActor aid store) -> do+ discoAspect <- getsState sdiscoAspect when (itemAffectsShineRadius discoAspect iid [store]) $ invalidateLucidAid aid- when (store `elem` [CEqp, COrgan]) $ do- addItemToActor iid (-k) aid+ when (store `elem` [CEqp, COrgan]) $ when (itemAffectsPerRadius discoAspect iid) $ reconsiderPerActor aid+ UpdSpotItemBag (CFloor lid _) bag _ais -> do+ discoAspect <- getsState sdiscoAspect+ let iids = EM.keys bag+ when (any (\iid -> itemAffectsShineRadius discoAspect iid []) iids) $+ invalidateLucidLid lid+ UpdSpotItemBag (CActor aid store) bag _ais -> do+ discoAspect <- getsState sdiscoAspect+ let iids = EM.keys bag+ when (any (\iid -> itemAffectsShineRadius discoAspect iid [store]) iids) $+ invalidateLucidAid aid+ when (store `elem` [CEqp, COrgan]) $+ when (any (itemAffectsPerRadius discoAspect) iids) $+ reconsiderPerActor aid+ UpdLoseItemBag (CFloor lid _) bag _ais -> do+ discoAspect <- getsState sdiscoAspect+ let iids = EM.keys bag+ when (any (\iid -> itemAffectsShineRadius discoAspect iid []) iids) $+ invalidateLucidLid lid+ UpdLoseItemBag (CActor aid store) bag _ais -> do+ discoAspect <- getsState sdiscoAspect+ let iids = EM.keys bag+ when (any (\iid -> itemAffectsShineRadius discoAspect iid [store]) iids) $+ invalidateLucidAid aid+ when (store `elem` [CEqp, COrgan]) $+ when (any (itemAffectsPerRadius discoAspect) iids) $+ reconsiderPerActor aid UpdMoveActor aid _ _ -> do- actorAspect <- getsServer sactorAspect+ actorAspect <- getsState sactorAspect when (actorHasShine actorAspect aid) $ invalidateLucidAid aid invalidatePerActor aid UpdDisplaceActor aid1 aid2 -> do- actorAspect <- getsServer sactorAspect+ actorAspect <- getsState sactorAspect when (actorHasShine actorAspect aid1 || actorHasShine actorAspect aid2) $ invalidateLucidAid aid1 -- the same lid as aid2 invalidatePerActor aid1 invalidatePerActor aid2- UpdMoveItem iid k aid s1 s2 -> do- discoAspect <- getsServer sdiscoAspect+ UpdMoveItem iid _k aid s1 s2 -> do+ discoAspect <- getsState sdiscoAspect let itemAffectsPer = itemAffectsPerRadius discoAspect iid invalidatePer = when itemAffectsPer $ reconsiderPerActor aid itemAffectsShine = itemAffectsShineRadius discoAspect iid [s1, s2]@@ -131,26 +147,22 @@ CEqp -> case s2 of COrgan -> return () _ -> do- addItemToActor iid (-k) aid- invalidatePer invalidateLucid+ invalidatePer COrgan -> case s2 of CEqp -> return () _ -> do- addItemToActor iid (-k) aid- invalidatePer invalidateLucid- _ -> do- when (s2 `elem` [CEqp, COrgan]) $ do- addItemToActor iid k aid invalidatePer+ _ -> do invalidateLucid -- from itemAffects, s2 provides light or s1 is CGround- UpdRefillCalm aid n -> do- actorAspect <- getsServer sactorAspect+ when (s2 `elem` [CEqp, COrgan]) invalidatePer+ UpdRefillCalm aid _ -> do+ AspectRecord{aSight} <- getsState $ getActorAspect aid body <- getsState $ getActorBody aid- let AspectRecord{aSight} = actorAspect EM.! aid- radiusOld = boundSightByCalm aSight (bcalm body)- radiusNew = boundSightByCalm aSight (bcalm body + n)+ let oldBody = getActorBody aid oldState+ radiusOld = boundSightByCalm aSight (bcalm oldBody)+ radiusNew = boundSightByCalm aSight (bcalm body) when (radiusOld /= radiusNew) $ invalidatePerActor aid UpdLeadFaction{} -> invalidateArenas UpdRecordKill{} -> invalidateArenas@@ -164,16 +176,9 @@ invalidateArenas :: MonadServer m => m () invalidateArenas = modifyServer $ \ser -> ser {svalidArenas = False} -addItemToActor :: MonadServer m => ItemId -> Int -> ActorId -> m ()-addItemToActor iid k aid = do- discoAspect <- getsServer sdiscoAspect- let arItem = discoAspect EM.! iid- g arActor = sumAspectRecord [(arActor, 1), (arItem, k)]- f = EM.adjust g aid- modifyServer $ \ser -> ser {sactorAspect = f $ sactorAspect ser}- updateSclear :: MonadServer m- => LevelId -> Point -> Kind.Id TileKind -> Kind.Id TileKind -> m Bool+ => LevelId -> Point -> Kind.Id TileKind -> Kind.Id TileKind+ -> m Bool updateSclear lid pos fromTile toTile = do Kind.COps{coTileSpeedup} <- getsState scops let fromClear = Tile.isClear coTileSpeedup fromTile@@ -203,7 +208,7 @@ ser { sfovLucidLid = EM.insert lid FovInvalid $ sfovLucidLid ser , sperValidFid = EM.map (EM.insert lid False) $ sperValidFid ser } -invalidateLucidAid :: MonadServer m => ActorId -> m ()+invalidateLucidAid :: MonadServer m => ActorId -> m () invalidateLucidAid aid = do lid <- getsState $ blid . getActorBody aid invalidateLucidLid lid@@ -229,8 +234,7 @@ addPerActor :: MonadServer m => ActorId -> Actor -> m () addPerActor aid b = do- actorAspect <- getsServer sactorAspect- let AspectRecord{..} = actorAspect EM.! aid+ AspectRecord{..} <- getsState $ getActorAspect aid unless (aSight <= 0 && aNocto <= 0 && aSmell <= 0) $ addPerActorAny aid b addPerActorAny :: MonadServer m => ActorId -> Actor -> m ()@@ -245,10 +249,9 @@ , sperValidFid = EM.adjust (EM.insert lid False) fid $ sperValidFid ser } -deletePerActor :: MonadServer m => ActorId -> Actor -> m ()-deletePerActor aid b = do- actorAspect <- getsServer sactorAspect- let AspectRecord{..} = actorAspect EM.! aid+deletePerActor :: MonadServer m => ActorAspect -> ActorId -> Actor -> m ()+deletePerActor actorAspectOld aid b = do+ let AspectRecord{..} = actorAspectOld EM.! aid unless (aSight <= 0 && aNocto <= 0 && aSmell <= 0) $ deletePerActorAny aid b deletePerActorAny :: MonadServer m => ActorId -> Actor -> m ()@@ -265,8 +268,7 @@ invalidatePerActor :: MonadServer m => ActorId -> m () invalidatePerActor aid = do- actorAspect <- getsServer sactorAspect- let AspectRecord{..} = actorAspect EM.! aid+ AspectRecord{..} <- getsState $ getActorAspect aid unless (aSight <= 0 && aNocto <= 0 && aSmell <= 0) $ do b <- getsState $ getActorBody aid addPerActorAny aid b@@ -274,8 +276,7 @@ reconsiderPerActor :: MonadServer m => ActorId -> m () reconsiderPerActor aid = do b <- getsState $ getActorBody aid- actorAspect <- getsServer sactorAspect- let AspectRecord{..} = actorAspect EM.! aid+ AspectRecord{..} <- getsState $ getActorAspect aid if aSight <= 0 && aNocto <= 0 && aSmell <= 0 then do perCacheFid <- getsServer sperCacheFid
Game/LambdaHack/Server/HandleEffectM.hs view
@@ -1,71 +1,87 @@ {-# LANGUAGE TupleSections #-}--- | Handle effects (most often caused by requests sent by clients).+-- | Handle effects. They are most often caused by requests sent by clients+-- but sometimes also caused by projectiles or periodically activated items. module Game.LambdaHack.Server.HandleEffectM ( applyItem, meleeEffectAndDestroy, effectAndDestroy, itemEffectEmbedded- , dropCStoreItem, dominateFidSfx, pickDroppable, cutCalm+ , dropCStoreItem, dominateFidSfx, pickDroppable, refillHP, cutCalm+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , applyMeleeDamage, imperishableKit, itemEffectDisco, effectSem+ , effectBurn, effectExplode, effectRefillHP, effectRefillCalm+ , effectDominate, dominateFid, effectImpress, effectSummon+ , effectAscend, findStairExit, switchLevels1, switchLevels2, effectEscape+ , effectParalyze, effectInsertMove, effectTeleport, effectCreateItem+ , effectDropItem, allGroupItems, effectPolyItem, effectIdentify, identifyIid+ , effectDetect, effectDetectX, effectDetectActor, effectDetectItem+ , effectDetectExit, effectDetectHidden+ , effectSendFlying, sendFlyingVector, effectDropBestWeapon+ , effectActivateInv, effectTransformEqp, effectApplyPerfume, effectOneOf+ , effectRecharging, effectTemporary, effectComposite+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Bits (xor)+import Data.Bits (xor) import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.HashMap.Strict as HM-import Data.Key (mapWithKeyM_)+import Data.Int (Int64)+import Data.Key (mapWithKeyM_) -import Game.LambdaHack.Atomic+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.ModeKind import qualified Game.LambdaHack.Content.TileKind as TK-import Game.LambdaHack.Server.CommonM-import Game.LambdaHack.Server.ItemM-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.PeriodicM-import Game.LambdaHack.Server.State+import Game.LambdaHack.Server.CommonM+import Game.LambdaHack.Server.ItemM+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.PeriodicM+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State --- + Semantics of effects+-- * Semantics of effects -applyItem :: (MonadAtomic m, MonadServer m)- => ActorId -> ItemId -> CStore -> m ()+applyItem :: MonadServerAtomic m => ActorId -> ItemId -> CStore -> m () applyItem aid iid cstore = do execSfxAtomic $ SfxApply aid iid cstore let c = CActor aid cstore meleeEffectAndDestroy aid aid iid c -applyMeleeDamage :: (MonadAtomic m, MonadServer m)+applyMeleeDamage :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> m Bool applyMeleeDamage source target iid = do itemBase <- getsState $ getItemBody iid- if jdamage itemBase <= 0 then return False else do -- speedup+ if jdamage itemBase == 0 then return False else do -- speedup sb <- getsState $ getActorBody source tb <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect- hurtMult <- getsState $ armorHurtBonus actorAspect source target+ ar <- getsState $ getActorAspect target+ hurtMult <- getsState $ armorHurtBonus source target dmg <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) $ jdamage itemBase- let ar = actorAspect EM.! target- hpMax = aMaxHP ar+ let hpMax = aMaxHP ar rawDeltaHP = fromIntegral hurtMult * xM dmg `divUp` 100 speedDeltaHP = case btrajectory sb of Just (_, speed) -> - modifyDamageBySpeed rawDeltaHP speed@@ -77,22 +93,40 @@ min speedDeltaHP (xM hpMax - bhp tb) | otherwise = speedDeltaHP if deltaHP < 0 then do -- damage the target, never heal- execUpdAtomic $ UpdRefillHP target deltaHP- when serious $ cutCalm target+ refillHP serious target tb deltaHP return True else return False +refillHP :: MonadServerAtomic m => Bool -> ActorId -> Actor -> Int64 -> m ()+refillHP serious target tbOld deltaHP = do+ execUpdAtomic $ UpdRefillHP target deltaHP+ when serious $ cutCalm target+ -- If leader just lost all HP, change the leader to let players rescue him,+ -- especially if he's slowed by the attackers.+ tb <- getsState $ getActorBody target+ when (bhp tb <= 0 && bhp tbOld > 0) $ do+ mleader <- getsState $ gleader . (EM.! bfid tb) . sfactionD+ when (Just target == mleader) $ do+ actorD <- getsState sactorD+ let ours (_, b) = bfid b == bfid tb && not (bproj b) && bhp b > 0+ -- Only consider actors positive HP.+ positive = filter ours $ EM.assocs actorD+ onLevel <- getsState $ fidActorRegularIds (bfid tb) (blid tb)+ case onLevel ++ map fst positive of+ [] -> return ()+ aid : _ -> execUpdAtomic $ UpdLeadFaction (bfid tb) mleader $ Just aid+ -- Here melee damage is applied. This is necessary so that the same -- AI benefit calculation may be used for flinging and for applying items.-meleeEffectAndDestroy :: (MonadAtomic m, MonadServer m)- => ActorId -> ActorId -> ItemId -> Container -> m ()+meleeEffectAndDestroy :: MonadServerAtomic m+ => ActorId -> ActorId -> ItemId -> Container -> m () meleeEffectAndDestroy source target iid c = do meleePerformed <- applyMeleeDamage source target iid bag <- getsState $ getContainerBag c case iid `EM.lookup` bag of Nothing -> error $ "" `showFailure` (source, target, iid, c) Just kit -> do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull let itemFull = itemToF iid kit case itemDisco itemFull of Just ItemDisco {itemKind=IK.ItemKind{IK.ieffects}} ->@@ -100,7 +134,7 @@ itemFull _ -> error $ "" `showFailure` (source, target, iid, c) -effectAndDestroy :: (MonadAtomic m, MonadServer m)+effectAndDestroy :: MonadServerAtomic m => Bool -> ActorId -> ActorId -> ItemId -> Container -> Bool -> [IK.Effect] -> ItemFull -> m ()@@ -183,7 +217,7 @@ -- One item of each @iid@ is triggered at once. If there are more copies, -- they are left to be triggered next time.-itemEffectEmbedded :: (MonadAtomic m, MonadServer m)+itemEffectEmbedded :: MonadServerAtomic m => ActorId -> Point -> ItemBag -> m () itemEffectEmbedded aid tpos bag = do sb <- getsState $ getActorBody aid@@ -202,12 +236,12 @@ -- Note that if we activate a durable item, e.g., armor, from the ground, -- it will get identified, which is perfectly fine, until we want to add -- sticky armor that can't be easily taken off (and, e.g., has some maluses).-itemEffectDisco :: (MonadAtomic m, MonadServer m)+itemEffectDisco :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> Container -> Bool -> Bool -> [IK.Effect] -> m Bool itemEffectDisco source target iid c recharged periodic effs = do- discoKind <- getsServer sdiscoKind+ discoKind <- getsState sdiscoKind item <- getsState $ getItemBody iid case EM.lookup (jkindIx item) discoKind of Just KindMean{kmKind} -> do@@ -223,7 +257,7 @@ -- The item may or may not still be in the container. -- The boolean result indicates if the effect actually fired up, -- as opposed to fizzled.-effectSem :: (MonadAtomic m, MonadServer m)+effectSem :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> Container -> Bool -> Bool -> IK.Effect -> m Bool@@ -274,24 +308,23 @@ IK.Temporary _ -> effectTemporary execSfx source iid c IK.Unique -> return False IK.Periodic -> return False+ IK.Composite l -> effectComposite recursiveCall l --- + Individual semantic functions for effects+-- * Individual semantic functions for effects -- ** Burn -- Damage from fire. Not affected by armor.-effectBurn :: (MonadAtomic m, MonadServer m)- => Dice.Dice -> ActorId -> ActorId- -> m Bool+effectBurn :: MonadServerAtomic m+ => Dice.Dice -> ActorId -> ActorId -> m Bool effectBurn nDm source target = do tb <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target- hpMax = aMaxHP ar+ ar <- getsState $ getActorAspect target+ let hpMax = aMaxHP ar n <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm let rawDeltaHP = - xM n -- We ignore minor burns.- serious = not (bproj tb) && source /= target && n > 1+ serious = n > 1 && source /= target && not (bproj tb) deltaHP | serious = -- if HP overfull, at least cut back to max HP min rawDeltaHP (xM hpMax - bhp tb) | otherwise = rawDeltaHP@@ -303,13 +336,12 @@ let reportedEffect = IK.Burn $ Dice.intToDice n execSfxAtomic $ SfxEffect (bfid sb) target reportedEffect deltaHP -- Damage the target.- execUpdAtomic $ UpdRefillHP target deltaHP- when serious $ cutCalm target+ refillHP serious target tb deltaHP return True -- ** Explode -effectExplode :: (MonadAtomic m, MonadServer m)+effectExplode :: MonadServerAtomic m => m () -> GroupName ItemKind -> ActorId -> m Bool effectExplode execSfx cgroup target = do execSfx@@ -384,21 +416,19 @@ -- ** RefillHP -- Unaffected by armor.-effectRefillHP :: (MonadAtomic m, MonadServer m)- => Int -> ActorId -> ActorId -> m Bool+effectRefillHP :: MonadServerAtomic m => Int -> ActorId -> ActorId -> m Bool effectRefillHP power source target = do sb <- getsState $ getActorBody source tb <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target- hpMax = aMaxHP ar+ ar <- getsState $ getActorAspect target+ let hpMax = aMaxHP ar -- We ignore light poison and similar -1HP per turn annoyances.- serious = not (bproj tb) && source /= target && abs power > 1- deltaHP | power < 0 && serious = -- if overfull, at least cut back to max- min (xM power) (xM hpMax - bhp tb)+ serious = power < -1 && source /= target && not (bproj tb)+ deltaHP | serious = -- if overfull, at least cut back to max+ min (xM power) (xM hpMax - bhp tb) | otherwise = min (xM power) (max 0 $ xM 999 - bhp tb) -- UI limitation- curChalSer <- getsServer $ scurChalSer . sdebugSer+ curChalSer <- getsServer $ scurChalSer . soptions fact <- getsState $ (EM.! bfid tb) . sfactionD if | cfish curChalSer && power > 0 && fhasUI (gplayer fact) && bfid sb /= bfid tb -> do@@ -407,16 +437,14 @@ | deltaHP == 0 -> return False | otherwise -> do execSfxAtomic $ SfxEffect (bfid sb) target (IK.RefillHP power) deltaHP- execUpdAtomic $ UpdRefillHP target deltaHP- when (deltaHP < 0 && serious) $ cutCalm target+ refillHP serious target tb deltaHP return True -cutCalm :: (MonadAtomic m, MonadServer m) => ActorId -> m ()+cutCalm :: MonadServerAtomic m => ActorId -> m () cutCalm target = do tb <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target- upperBound = if hpTooLow tb ar+ ar <- getsState $ getActorAspect target+ let upperBound = if hpTooLow tb ar then 0 -- to trigger domination, etc. else xM $ aMaxCalm ar deltaCalm = min minusM1 (upperBound - bcalm tb)@@ -426,13 +454,12 @@ -- ** RefillCalm -effectRefillCalm :: (MonadAtomic m, MonadServer m)+effectRefillCalm :: MonadServerAtomic m => m () -> Int -> ActorId -> ActorId -> m Bool effectRefillCalm execSfx power source target = do tb <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target- calmMax = aMaxCalm ar+ ar <- getsState $ getActorAspect target+ let calmMax = aMaxCalm ar serious = not (bproj tb) && source /= target && power > 1 deltaCalm | power < 0 && serious = -- if overfull, at least cut to max min (xM power) (xM calmMax - bcalm tb)@@ -446,10 +473,8 @@ -- ** Dominate -effectDominate :: (MonadAtomic m, MonadServer m)- => (IK.Effect -> m Bool)- -> ActorId -> ActorId- -> m Bool+effectDominate :: MonadServerAtomic m+ => (IK.Effect -> m Bool) -> ActorId -> ActorId -> m Bool effectDominate recursiveCall source target = do sb <- getsState $ getActorBody source tb <- getsState $ getActorBody target@@ -460,22 +485,20 @@ recursiveCall IK.Impress | otherwise -> dominateFidSfx (bfid sb) target -dominateFidSfx :: (MonadAtomic m, MonadServer m)- => FactionId -> ActorId -> m Bool+dominateFidSfx :: MonadServerAtomic m => FactionId -> ActorId -> m Bool dominateFidSfx fid target = do tb <- getsState $ getActorBody target -- Actors that don't move freely can't be dominated, for otherwise, -- when they are the last survivors, they could get stuck -- and the game wouldn't end.- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target- actorMaxSk = aSkills ar+ ar <- getsState $ getActorAspect target+ let actorMaxSk = aSkills ar -- Check that the actor can move, also between levels and through doors. -- Otherwise, it's too awkward for human player to control. canMove = EM.findWithDefault 0 Ability.AbMove actorMaxSk > 0 && EM.findWithDefault 0 Ability.AbAlter actorMaxSk >= fromEnum TK.talterForStairs- if canMove && not (bproj tb) then do+ if canMove && not (bproj tb) && bhp tb > 0 then do let execSfx = execSfxAtomic $ SfxEffect fid target IK.Dominate 0 execSfx -- if actor ours, possibly the last occasion to see him gameOver <- dominateFid fid target@@ -485,7 +508,7 @@ else return False -dominateFid :: (MonadAtomic m, MonadServer m) => FactionId -> ActorId -> m Bool+dominateFid :: MonadServerAtomic m => FactionId -> ActorId -> m Bool dominateFid fid target = do Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops tb0 <- getsState $ getActorBody target@@ -494,14 +517,13 @@ electLeader (bfid tb0) (blid tb0) target fact <- getsState $ (EM.! bfid tb0) . sfactionD -- Prevent the faction's stash from being lost in case they are not spawners.- when (isNothing $ _gleader fact) $ moveStores False target CSha CInv+ when (isNothing $ gleader fact) $ moveStores False target CSha CInv tb <- getsState $ getActorBody target ais <- getsState $ getCarriedAssocs tb- actorAspect <- getsServer sactorAspect+ ar <- getsState $ getActorAspect target getItem <- getsState $ flip getItemBody- discoKind <- getsServer sdiscoKind- let ar = actorAspect EM.! target- isImpression iid = case EM.lookup (jkindIx $ getItem iid) discoKind of+ discoKind <- getsState sdiscoKind+ let isImpression iid = case EM.lookup (jkindIx $ getItem iid) discoKind of Just KindMean{kmKind} -> maybe False (> 0) $ lookup "impressed" $ IK.ifreq (okind kmKind) Nothing -> error $ "" `showFailure` iid@@ -531,7 +553,7 @@ -- Add some nostalgia for the old faction. void $ effectCreateItem (Just $ bfid tb) (Just 10) target COrgan "impressed" IK.TimerNone- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull let discoverIf (iid, cstore) = do let itemFull = itemToF iid (1, []) c = CActor target cstore@@ -544,12 +566,12 @@ -- ** Impress -effectImpress :: (MonadAtomic m, MonadServer m)+effectImpress :: MonadServerAtomic m => (IK.Effect -> m Bool) -> m () -> ActorId -> ActorId -> m Bool effectImpress recursiveCall execSfx source target = do sb <- getsState $ getActorBody source tb <- getsState $ getActorBody target- if | bproj tb -> return False+ if | bproj tb || bhp tb <= 0 -> return False -- avoid spam just before death | bfid tb == bfid sb -> do -- Unimpress wrt others, but only once. res <- recursiveCall $ IK.DropItem 1 1 COrgan "impressed"@@ -563,7 +585,7 @@ -- ** Summon -- Note that the Calm expended doesn't depend on the number of actors summoned.-effectSummon :: (MonadAtomic m, MonadServer m)+effectSummon :: MonadServerAtomic m => m () -> GroupName ItemKind -> Dice.Dice -> ItemId -> ActorId -> ActorId -> Bool -> m Bool@@ -573,7 +595,7 @@ power <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm sb <- getsState $ getActorBody source tb <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect+ actorAspect <- getsState sactorAspect item <- getsState $ getItemBody iid let sar = actorAspect EM.! source tar = actorAspect EM.! target@@ -605,7 +627,7 @@ Nothing -> return False -- not enough space in dungeon? Just aid -> do b <- getsState $ getActorBody aid- mleader <- getsState $ _gleader . (EM.! bfid b) . sfactionD+ mleader <- getsState $ gleader . (EM.! bfid b) . sfactionD when (isNothing mleader) $ supplantLeader (bfid b) aid return True return $! or bs@@ -613,7 +635,7 @@ -- ** Ascend -- Note that projectiles can be teleported, too, for extra fun.-effectAscend :: (MonadAtomic m, MonadServer m)+effectAscend :: MonadServerAtomic m => (IK.Effect -> m Bool) -> m () -> Bool -> ActorId -> ActorId -> Point -> m Bool@@ -693,10 +715,10 @@ [] -> error $ "" `showFailure` ps posRes : _ -> return posRes -switchLevels1 :: MonadAtomic m => (ActorId, Actor) -> m (Maybe ActorId)+switchLevels1 :: MonadServerAtomic m => (ActorId, Actor) -> m (Maybe ActorId) switchLevels1 (aid, bOld) = do let side = bfid bOld- mleader <- getsState $ _gleader . (EM.! side) . sfactionD+ mleader <- getsState $ gleader . (EM.! side) . sfactionD -- Prevent leader pointing to a non-existing actor. mlead <- if not (bproj bOld) && isJust mleader then do@@ -711,7 +733,7 @@ execUpdAtomic $ UpdLoseActor aid bOld ais return mlead -switchLevels2 ::(MonadAtomic m, MonadServer m)+switchLevels2 ::MonadServerAtomic m => LevelId -> Point -> (ActorId, Actor) -> Time -> Maybe ActorId -> m () switchLevels2 lidNew posNew (aid, bOld) btime_bOld mlead = do@@ -771,7 +793,7 @@ -- ** Escape -- | The faction leaves the dungeon.-effectEscape :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> m Bool+effectEscape :: MonadServerAtomic m => ActorId -> ActorId -> m Bool effectEscape source target = do -- Obvious effect, nothing announced. sb <- getsState $ getActorBody source@@ -791,7 +813,7 @@ -- | Advance target actor time by this many time clips. Not by actor moves, -- to hurt fast actors more.-effectParalyze :: (MonadAtomic m, MonadServer m)+effectParalyze :: MonadServerAtomic m => m () -> Dice.Dice -> ActorId -> m Bool effectParalyze execSfx nDm target = do p <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm@@ -809,14 +831,13 @@ -- | Give target actor the given number of extra moves. Don't give -- an absolute amount of time units, to benefit slow actors more.-effectInsertMove :: (MonadAtomic m, MonadServer m)+effectInsertMove :: MonadServerAtomic m => m () -> Dice.Dice -> ActorId -> m Bool effectInsertMove execSfx nDm target = do execSfx p <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm b <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target+ ar <- getsState $ getActorAspect target let actorTurn = ticksPerMeter $ bspeed b ar t = timeDeltaScale actorTurn (-p) modifyServer $ \ser ->@@ -827,7 +848,7 @@ -- | Teleport the target actor. -- Note that projectiles can be teleported, too, for extra fun.-effectTeleport :: (MonadAtomic m, MonadServer m)+effectTeleport :: MonadServerAtomic m => m () -> Dice.Dice -> ActorId -> ActorId -> m Bool effectTeleport execSfx nDm source target = do Kind.COps{coTileSpeedup} <- getsState scops@@ -867,7 +888,7 @@ -- ** CreateItem -effectCreateItem :: (MonadAtomic m, MonadServer m)+effectCreateItem :: MonadServerAtomic m => Maybe FactionId -> Maybe Int -> ActorId -> CStore -> GroupName ItemKind -> IK.TimerDice -> m Bool@@ -882,23 +903,20 @@ IK.TimerActorTurn nDm -> do k <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm let !_A = assert (k >= 0) ()- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target- actorTurn = ticksPerMeter $ bspeed tb ar+ ar <- getsState $ getActorAspect target+ let actorTurn = ticksPerMeter $ bspeed tb ar return $! timeDeltaScale actorTurn k let c = CActor target store bagBefore <- getsState $ getBodyStoreBag tb store let litemFreq = [(grp, 1)] -- Power depth of new items unaffected by number of spawned actors. m5 <- rollItem 0 (blid tb) litemFreq- let (itemKnownRaw, itemFullRaw, itemDisco, seed, _) =+ let (itemKnownRaw, itemFullRaw, _, seed, _) = fromMaybe (error $ "" `showFailure` (blid tb, litemFreq, c)) m5- -- Other code adds to @sdiscoBenefit@ only @iid@ and not any other items- -- that share the same @jkindIx@, so this is broken if such items- -- are not fully IDed from the start, so check that before risking a copy- -- of the same item, but with different @jfid@.+ -- Avoid too many different item identifiers (one for each faction)+ -- for blasts or common item generating tiles. Temporary organs are+ -- also duplicated but they provide really useful info (perpetrator). jfid = if store == COrgan- && IK.Identified `elem` IK.ifeature (itemKind itemDisco) then jfidRaw else Nothing (itemKnown, itemFullFid) =@@ -949,7 +967,7 @@ -- | Make the target actor drop all items in a store from the given group -- (not just a random single item, or cluttering equipment with rubbish -- would be beneficial).-effectDropItem :: (MonadAtomic m, MonadServer m)+effectDropItem :: MonadServerAtomic m => m () -> Int -> Int -> CStore -> GroupName ItemKind -> ActorId -> m Bool effectDropItem execSfx ngroup kcopy store grp target = do@@ -961,12 +979,12 @@ mapM_ (uncurry (dropCStoreItem True store target b kcopy)) $ take ngroup is return True -allGroupItems :: (MonadAtomic m, MonadServer m)+allGroupItems :: MonadServerAtomic m => CStore -> GroupName ItemKind -> ActorId -> m [(ItemId, ItemQuant)] allGroupItems store grp target = do Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops- discoKind <- getsServer sdiscoKind+ discoKind <- getsState sdiscoKind b <- getsState $ getActorBody target let hasGroup (iid, _) = do item <- getsState $ getItemBody iid@@ -982,7 +1000,7 @@ -- at most one explodes to avoid excessive carnage and UI clutter -- (let's say, the multiple explosions interfere with each other or perhaps -- larger quantities of explosives tend to be packaged more safely).-dropCStoreItem :: (MonadAtomic m, MonadServer m)+dropCStoreItem :: MonadServerAtomic m => Bool -> CStore -> ActorId -> Actor -> Int -> ItemId -> ItemQuant -> m ()@@ -994,7 +1012,7 @@ isDestroyed = bproj b && (bhp b <= 0 && not durable || fragile) || fragile && durable -- hack for tmp organs if isDestroyed then do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull let itemFull = itemToF iid kit effs = strengthOnSmash itemFull -- Activate even if effects null, to destroy the item.@@ -1019,12 +1037,11 @@ -- ** PolyItem -effectPolyItem :: (MonadAtomic m, MonadServer m)- => m () -> ActorId -> ActorId -> m Bool+effectPolyItem :: MonadServerAtomic m => m () -> ActorId -> ActorId -> m Bool effectPolyItem execSfx source target = do sb <- getsState $ getActorBody source let cstore = CGround- allAssocs <- fullAssocsServer target [cstore]+ allAssocs <- getsState $ fullAssocs target [cstore] case allAssocs of [] -> do execSfxAtomic $ SfxMsgFid (bfid sb) $ SfxPurposeNothing cstore@@ -1052,38 +1069,41 @@ -- ** Identify -effectIdentify :: (MonadAtomic m, MonadServer m)+effectIdentify :: MonadServerAtomic m => m () -> ItemId -> ActorId -> ActorId -> m Bool effectIdentify execSfx iidId source target = do sb <- getsState $ getActorBody source+ s <- getsServer $ (EM.! bfid sb) . sclientStates let tryFull store as = case as of- [] -> do- execSfxAtomic $ SfxMsgFid (bfid sb) $ SfxIdentifyNothing store- return False+ [] -> return False (iid, _) : rest | iid == iidId -> tryFull store rest -- don't id itself- (iid, ItemFull{itemDisco=Just ItemDisco{..}}) : rest -> do- -- We avoid identifying trivial items, but they may also be right- -- in the middle of bonus ranges, to if no other option, id them;- -- client will ignore them if really trivial.- let ided = IK.Identified `elem` IK.ifeature itemKind- statsObvious = Just itemAspectMean == itemAspect- if ided && statsObvious && not (null rest)- then tryFull store rest- else do- let c = CActor target store- execSfx- identifyIid iid c itemKindId- return True+ (iid, ItemFull{itemBase, itemDisco=Just ItemDisco{..}}) : rest ->+ if iid `EM.member` sdiscoAspect s -- already identified+ || itemConst -- doesn't need further identification+ && jkindIx itemBase `EM.member` sdiscoKind s+ || store == CGround+ && (not $ any IK.forIdEffect $ IK.ieffects itemKind)+ -- will be identified as soon as picked up, so don't bother;+ -- darts included, which prevents wasting the scroll+ || maybe False (> 0) (lookup "gem" $ IK.ifreq itemKind) -- a hack+ then tryFull store rest+ else do+ let c = CActor target store+ execSfx+ identifyIid iid c itemKindId+ return True _ -> error $ "" `showFailure` (store, as) tryStore stores = case stores of- [] -> return False+ [] -> do+ execSfxAtomic $ SfxMsgFid (bfid sb) SfxIdentifyNothing+ return False store : rest -> do- allAssocs <- fullAssocsServer target [store]+ allAssocs <- getsState $ fullAssocs target [store] go <- tryFull store allAssocs if go then return True else tryStore rest- tryStore [CGround]+ tryStore [CGround, CEqp, CInv, CSha] -identifyIid :: (MonadAtomic m, MonadServer m)+identifyIid :: MonadServerAtomic m => ItemId -> Container -> Kind.Id ItemKind -> m () identifyIid iid c itemKindId = do seed <- getsServer $ (EM.! iid) . sitemSeedD@@ -1091,11 +1111,11 @@ -- ** Detect -effectDetect :: (MonadAtomic m, MonadServer m)+effectDetect :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool effectDetect = effectDetectX (const True) (const $ return False) -effectDetectX :: (MonadAtomic m, MonadServer m)+effectDetectX :: MonadServerAtomic m => (Point -> Bool) -> ([Point] -> m Bool) -> m () -> Int -> ActorId -> m Bool effectDetectX predicate action execSfx radius target = do@@ -1133,8 +1153,7 @@ -- ** DetectActor -effectDetectActor :: (MonadAtomic m, MonadServer m)- => m () -> Int -> ActorId -> m Bool+effectDetectActor :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool effectDetectActor execSfx radius target = do b <- getsState $ getActorBody target Level{lactor} <- getLevel $ blid b@@ -1143,8 +1162,7 @@ -- ** DetectItem -effectDetectItem :: (MonadAtomic m, MonadServer m)- => m () -> Int -> ActorId -> m Bool+effectDetectItem :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool effectDetectItem execSfx radius target = do b <- getsState $ getActorBody target Level{lfloor} <- getLevel $ blid b@@ -1153,8 +1171,7 @@ -- ** DetectExit -effectDetectExit :: (MonadAtomic m, MonadServer m)- => m () -> Int -> ActorId -> m Bool+effectDetectExit :: MonadServerAtomic m => m () -> Int -> ActorId -> m Bool effectDetectExit execSfx radius target = do b <- getsState $ getActorBody target Level{lstair=(ls1, ls2), lescape} <- getLevel $ blid b@@ -1163,7 +1180,7 @@ -- ** DetectHidden -effectDetectHidden :: (MonadAtomic m, MonadServer m)+effectDetectHidden :: MonadServerAtomic m => m () -> Int -> ActorId -> Point -> m Bool effectDetectHidden execSfx radius target pos = do Kind.COps{coTileSpeedup} <- getsState scops@@ -1171,8 +1188,10 @@ lvl <- getLevel $ blid b let predicate p = Tile.isHideAs coTileSpeedup $ lvl `at` p action l = do- let f p = when (p /= pos)- $ execUpdAtomic $ UpdSearchTile target p $ lvl `at` p+ let f p = when (p /= pos) $ do+ let t = lvl `at` p+ execUpdAtomic $ UpdSearchTile target p t+ -- This is safe searching; embedded items are not triggered. mapM_ f l return $! not $ null l effectDetectX predicate action execSfx radius target@@ -1184,9 +1203,8 @@ -- the vector is directed outwards, if no, inwards, if it's the same actor, -- boldpos is used, if it can't, a random outward vector of length 10 -- is picked.-effectSendFlying :: (MonadAtomic m, MonadServer m)- => m () -> IK.ThrowMod- -> ActorId -> ActorId -> Maybe Bool+effectSendFlying :: MonadServerAtomic m+ => m () -> IK.ThrowMod -> ActorId -> ActorId -> Maybe Bool -> m Bool effectSendFlying execSfx IK.ThrowMod{..} source target modePush = do v <- sendFlyingVector source target modePush@@ -1208,7 +1226,7 @@ if not $ Tile.isWalkable coTileSpeedup t then return False -- supported by a wall else do- weightAssocs <- fullAssocsServer target [CInv, CEqp, COrgan]+ weightAssocs <- getsState $ fullAssocs target [CInv, CEqp, COrgan] let weight = sum $ map (jweight . itemBase . snd) weightAssocs path = bpos tb : pos : rest (trajectory, (speed, range)) =@@ -1229,7 +1247,7 @@ $ sactorTime ser} return True -sendFlyingVector :: (MonadAtomic m, MonadServer m)+sendFlyingVector :: MonadServerAtomic m => ActorId -> ActorId -> Maybe Bool -> m Vector sendFlyingVector source target modePush = do sb <- getsState $ getActorBody source@@ -1260,12 +1278,11 @@ -- ** DropBestWeapon -- | Make the target actor drop his best weapon (stack).-effectDropBestWeapon :: (MonadAtomic m, MonadServer m)- => m () -> ActorId -> m Bool+effectDropBestWeapon :: MonadServerAtomic m => m () -> ActorId -> m Bool effectDropBestWeapon execSfx target = do tb <- getsState $ getActorBody target localTime <- getsState $ getLocalTime (blid tb)- allAssocsRaw <- fullAssocsServer target [CEqp]+ allAssocsRaw <- getsState $ fullAssocs target [CEqp] let allAssocs = filter (isMelee . itemBase . snd) allAssocsRaw case strongestMelee Nothing localTime allAssocs of (_, (iid, _)) : _ -> do@@ -1282,14 +1299,13 @@ -- in the target actor's equipment (there's no variant that activates -- a random one, to avoid the incentive for carrying garbage). -- Only one item of each stack is activated (and possibly consumed).-effectActivateInv :: (MonadAtomic m, MonadServer m)- => m () -> ActorId -> Char -> m Bool+effectActivateInv :: MonadServerAtomic m => m () -> ActorId -> Char -> m Bool effectActivateInv execSfx target symbol = effectTransformEqp execSfx target symbol CInv $ \iid _ -> do let c = CActor target CInv meleeEffectAndDestroy target target iid c -effectTransformEqp :: forall m. MonadAtomic m+effectTransformEqp :: forall m. MonadServerAtomic m => m () -> ActorId -> Char -> CStore -> (ItemId -> ItemQuant -> m ()) -> m Bool@@ -1311,8 +1327,7 @@ -- ** ApplyPerfume -effectApplyPerfume :: MonadAtomic m- => m () -> ActorId -> m Bool+effectApplyPerfume :: MonadServerAtomic m => m () -> ActorId -> m Bool effectApplyPerfume execSfx target = do execSfx tb <- getsState $ getActorBody target@@ -1324,10 +1339,8 @@ -- ** OneOf -effectOneOf :: (MonadAtomic m, MonadServer m)- => (IK.Effect -> m Bool)- -> [IK.Effect]- -> m Bool+effectOneOf :: MonadServerAtomic m+ => (IK.Effect -> m Bool) -> [IK.Effect] -> m Bool effectOneOf recursiveCall l = do let call1 = do ef <- rndToAction $ oneOf l@@ -1337,13 +1350,12 @@ b <- result if b then return True else callNext foldr f (return False) call99+ -- no @execSfx@, because individual effects sent them -- ** Recharging -effectRecharging :: MonadAtomic m- => (IK.Effect -> m Bool)- -> IK.Effect -> Bool- -> m Bool+effectRecharging :: MonadServerAtomic m+ => (IK.Effect -> m Bool) -> IK.Effect -> Bool -> m Bool effectRecharging recursiveCall e recharged = if recharged then recursiveCall e@@ -1351,7 +1363,7 @@ -- ** Temporary -effectTemporary :: MonadAtomic m+effectTemporary :: MonadServerAtomic m => m () -> ActorId -> ItemId -> Container -> m Bool effectTemporary execSfx source iid c = case c of@@ -1364,3 +1376,14 @@ _ -> do execSfx return False -- just a message++-- ** Composite++effectComposite :: MonadServerAtomic m+ => (IK.Effect -> m Bool) -> [IK.Effect] -> m Bool+effectComposite recursiveCall l = do+ let f eff result = do+ b <- recursiveCall eff+ if b then result >> return True else return False+ foldr f (return False) l -- @True@ if any effect triggered+ -- no @execSfx@, because individual effects sent them
Game/LambdaHack/Server/HandleRequestM.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE GADTs #-}--- | Semantics of request.+-- | Semantics of requests+-- . -- A couple of them do not take time, the rest does. -- Note that since the results are atomic commands, which are executed -- only later (on the server and some of the clients), all condition@@ -8,11 +9,11 @@ -- are already issued by the point an expression is evaluated, they do not -- influence the outcome of the evaluation. module Game.LambdaHack.Server.HandleRequestM- ( handleRequestAI, handleRequestUI, switchLeader, handleRequestTimed+ ( handleRequestAI, handleRequestUI, handleRequestTimed, switchLeader , reqMove, reqDisplace, reqGameExit #ifdef EXPOSE_INTERNAL -- * Internal operations- , setBWait, handleRequestTimedCases+ , setBWait, managePerRequest, handleRequestTimedCases , affectSmell, reqMelee, reqAlter, reqWait , reqMoveItems, reqMoveItem, computeRndTimeout, reqProject, reqApply , reqGameRestart, reqGameSave, reqTactic, reqAutomate@@ -25,44 +26,46 @@ import qualified Data.EnumMap.Strict as EM -import Game.LambdaHack.Atomic+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client import qualified Game.LambdaHack.Common.Ability as Ability-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.ReqFailure+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.ModeKind import qualified Game.LambdaHack.Content.TileKind as TK-import Game.LambdaHack.Server.CommonM-import Game.LambdaHack.Server.HandleEffectM-import Game.LambdaHack.Server.ItemM-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.PeriodicM-import Game.LambdaHack.Server.State+import Game.LambdaHack.Server.CommonM+import Game.LambdaHack.Server.HandleEffectM+import Game.LambdaHack.Server.ItemM+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.PeriodicM+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State -- | The semantics of server commands. -- AI always takes time and so doesn't loop.-handleRequestAI :: (MonadAtomic m)+handleRequestAI :: MonadServerAtomic m => ReqAI -> m (Maybe RequestAnyAbility) handleRequestAI cmd = case cmd of ReqAITimed cmdT -> return $ Just cmdT ReqAINop -> return Nothing --- | The semantics of server commands. Only the first two cases take time.-handleRequestUI :: (MonadAtomic m, MonadServer m)+-- | The semantics of server commands. Only the first two cases affect time.+handleRequestUI :: MonadServerAtomic m => FactionId -> ActorId -> ReqUI -> m (Maybe RequestAnyAbility) handleRequestUI fid aid cmd = case cmd of@@ -77,35 +80,36 @@ -- | This is a shorthand. Instead of setting @bwait@ in @ReqWait@ -- and unsetting in all other requests, we call this once before -- executing a request.-setBWait :: (MonadAtomic m) => RequestTimed a -> ActorId -> m (Maybe Bool)+setBWait :: MonadServerAtomic m+ => RequestTimed a -> ActorId -> Actor -> m (Maybe Bool) {-# INLINE setBWait #-}-setBWait cmd aid = do+setBWait cmd aid b = do let mwait = case cmd of ReqWait -> Just True -- true wait, with bracind, no overhead, etc. ReqWait10 -> Just False -- false wait, only one clip at a time _ -> Nothing- bPre <- getsState $ getActorBody aid- when ((mwait == Just True) /= bwait bPre) $+ when ((mwait == Just True) /= bwait b) $ execUpdAtomic $ UpdWaitActor aid (mwait == Just True) return mwait -handleRequestTimed :: (MonadAtomic m, MonadServer m)+handleRequestTimed :: MonadServerAtomic m => FactionId -> ActorId -> RequestTimed a -> m Bool handleRequestTimed fid aid cmd = do- mwait <- setBWait cmd aid+ b <- getsState $ getActorBody aid+ mwait <- setBWait cmd aid b -- Note that only the ordinary 1-turn wait eliminates overhead. -- The more fine-graned waits don't make actors braced and induce -- overhead, so that they have some drawbacks in addition to the -- benefit of seeing approaching danger up to almost a turn faster. -- It may be too late to block then, but not too late to sidestep or attack.- unless (mwait == Just True) $ overheadActorTime fid+ unless (mwait == Just True) $ overheadActorTime fid (blid b) advanceTime aid (if mwait == Just False then 10 else 100) handleRequestTimedCases aid cmd managePerRequest aid return $! isNothing mwait -- for speed, we report if @cmd@ harmless -- | Clear deltas for Calm and HP for proper UI display and AI hints.-managePerRequest :: MonadAtomic m => ActorId -> m ()+managePerRequest :: MonadServerAtomic m => ActorId -> m () managePerRequest aid = do b <- getsState $ getActorBody aid let clearMark = 0@@ -116,7 +120,7 @@ -- Clear delta for the next player turn. execUpdAtomic $ UpdRefillHP aid clearMark -handleRequestTimedCases :: (MonadAtomic m, MonadServer m)+handleRequestTimedCases :: MonadServerAtomic m => ActorId -> RequestTimed a -> m () handleRequestTimedCases aid cmd = case cmd of ReqMove target -> reqMove aid target@@ -129,13 +133,12 @@ ReqProject p eps iid cstore -> reqProject aid p eps iid cstore ReqApply iid cstore -> reqApply aid iid cstore -switchLeader :: (MonadAtomic m, MonadServer m)- => FactionId -> ActorId -> m ()+switchLeader :: MonadServerAtomic m => FactionId -> ActorId -> m () {-# INLINE switchLeader #-} switchLeader fid aidNew = do fact <- getsState $ (EM.! fid) . sfactionD bPre <- getsState $ getActorBody aidNew- let mleader = _gleader fact+ let mleader = gleader fact !_A1 = assert (Just aidNew /= mleader && not (bproj bPre) `blame` (aidNew, bPre, fid, fact)) ()@@ -174,14 +177,13 @@ -- with gender leave strong and unique enough smell. If smell already there -- and the actor can smell, remove smell. Projectiles are ignored. -- As long as an actor can smell, he doesn't leave any smell ever.-affectSmell :: (MonadAtomic m, MonadServer m) => ActorId -> m ()+affectSmell :: MonadServerAtomic m => ActorId -> m () affectSmell aid = do b <- getsState $ getActorBody aid unless (bproj b) $ do fact <- getsState $ (EM.! bfid b) . sfactionD- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! aid- smellRadius = aSmell ar+ ar <- getsState $ getActorAspect aid+ let smellRadius = aSmell ar when (fhasGender (gplayer fact) || smellRadius > 0) $ do localTime <- getsState $ getLocalTime $ blid b lvl <- getLevel $ blid b@@ -199,7 +201,7 @@ -- Also, only the server is authorized to check if a move is legal -- and it needs full context for that, e.g., the initial actor position -- to check if melee attack does not try to reach to a distant tile.-reqMove :: (MonadAtomic m, MonadServer m) => ActorId -> Vector -> m ()+reqMove :: MonadServerAtomic m => ActorId -> Vector -> m () reqMove source dir = do Kind.COps{coTileSpeedup} <- getsState scops sb <- getsState $ getActorBody source@@ -235,7 +237,7 @@ -- but for melee and not using up the weapon. -- No problem if there are many projectiles at the spot. We just -- attack the one specified.-reqMelee :: (MonadAtomic m, MonadServer m)+reqMelee :: MonadServerAtomic m => ActorId -> ActorId -> ItemId -> CStore -> m () reqMelee source target iid cstore = do sb <- getsState $ getActorBody source@@ -292,7 +294,7 @@ -- or due to a hurled actor colliding with another. -- Don't deduct if no pierce, to prevent spam. when (not (bproj sb2) || bhp sb2 > oneM) $- execUpdAtomic $ UpdRefillHP source minusM+ refillHP False source sb2 minusM when (not (bproj sb2) || bhp sb2 <= oneM) $ -- Non-projectiles can't pierce, so terminate their flight. -- If projectile has too low HP to pierce, ditto.@@ -312,7 +314,7 @@ -- * ReqDisplace -- | Actor tries to swap positions with another.-reqDisplace :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> m ()+reqDisplace :: MonadServerAtomic m => ActorId -> ActorId -> m () reqDisplace source target = do Kind.COps{coTileSpeedup} <- getsState scops sb <- getsState $ getActorBody source@@ -322,8 +324,7 @@ adj = checkAdjacent sb tb atWar = isAtWar tfact (bfid sb) req = ReqDisplace target- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target+ ar <- getsState $ getActorAspect target dEnemy <- getsState $ dispEnemy source target $ aSkills ar if | not adj -> execFailure source req DisplaceDistant | atWar && not dEnemy -> do -- if not at war, can displace always@@ -359,100 +360,153 @@ -- * ReqAlter -- | Search and/or alter the tile.------ Note that if @serverTile /= freshClientTile@, @freshClientTile@--- should not be alterable (but @serverTile@ may be).-reqAlter :: (MonadAtomic m, MonadServer m) => ActorId -> Point -> m ()+reqAlter :: MonadServerAtomic m => ActorId -> Point -> m () reqAlter source tpos = do- Kind.COps{cotile=Kind.Ops{okind, opick}, coTileSpeedup} <- getsState scops+ Kind.COps{ cotile=cotile@Kind.Ops{okind, opick}+ , coTileSpeedup } <- getsState scops sb <- getsState $ getActorBody source+ sClient <- getsServer $ (EM.! bfid sb) . sclientStates actorSk <- currentSkillsServer source let alterSkill = EM.findWithDefault 0 Ability.AbAlter actorSk lid = blid sb- spos = bpos sb req = ReqAlter tpos+ embeds <- getsState $ getEmbedBag lid tpos lvl <- getLevel lid let serverTile = lvl `at` tpos- hidden = Tile.isHideAs coTileSpeedup serverTile- -- Only actors with AbAlter > 1 can search for hidden doors, etc.- if alterSkill <= 1- || not hidden -- no searching needed- && alterSkill < Tile.alterMinSkill coTileSpeedup serverTile- then execFailure source req AlterUnskilled- else if not $ adjacent spos tpos then execFailure source req AlterDistant- else do- let changeTo tgroup = do- -- No @SfxAlter@, because the effect is obvious (e.g., opened door).- let nightCond kt = not (Tile.kindHasFeature TK.Walkable kt- && Tile.kindHasFeature TK.Clear kt)- || (if lnight lvl then id else not)- (Tile.kindHasFeature TK.Dark kt)- -- Sometimes the tile is determined precisely by the ambient light- -- of the source tiles. If not, default to cave day/night condition.- mtoTile <- rndToAction $ opick tgroup nightCond- toTile <- maybe (rndToAction- $ fromMaybe (error $ "" `showFailure` tgroup)- <$> opick tgroup (const True))- return- mtoTile- unless (toTile == serverTile) $ do- execUpdAtomic $ UpdAlterTile lid tpos serverTile toTile- case (Tile.isExplorable coTileSpeedup serverTile,- Tile.isExplorable coTileSpeedup toTile) of- (False, True) -> execUpdAtomic $ UpdAlterExplorable lid 1- (True, False) -> execUpdAtomic $ UpdAlterExplorable lid (-1)- _ -> return ()- feats = TK.tfeature $ okind serverTile- toAlter feat =- case feat of- TK.OpenTo tgroup -> Just tgroup- TK.CloseTo tgroup -> Just tgroup- TK.ChangeTo tgroup -> Just tgroup- _ -> Nothing- groupsToAlterTo = mapMaybe toAlter feats- embeds <- getsState $ getEmbedBag lid tpos- if null groupsToAlterTo && null embeds && not hidden then- -- Neither searching nor altering possible; silly client.- execFailure source req AlterNothing- else- if EM.notMember tpos $ lfloor lvl then- if null (posToAidsLvl tpos lvl) then do- when hidden $- -- Search, in case some actors present (e.g., of other factions)- -- don't know this tile.- execUpdAtomic $ UpdSearchTile source tpos serverTile- when (alterSkill >= Tile.alterMinSkill coTileSpeedup serverTile) $ do+ lvlClient = (EM.! lid) . sdungeon $ sClient+ clientTile = lvlClient `at` tpos+ hiddenTile = Tile.hideAs cotile serverTile+ revealEmbed = unless (null embeds) $ do+ s <- getState+ let ais = map (\iid -> (iid, getItemBody iid s)) (EM.keys embeds)+ execUpdAtomic $ UpdSpotItemBag (CEmbed lid tpos) embeds ais+ if not $ adjacent (bpos sb) tpos then execFailure source req AlterDistant+ else if Just clientTile == hiddenTile then -- searches+ -- Only actors with AbAlter > 1 can search for hidden doors, etc.+ if alterSkill <= 1+ then execFailure source req AlterUnskilled -- don't leak about searching+ else do+ -- Blocking by items nor actors does not prevent searching.+ -- Searching broadcasted, in case actors from other factions are present+ -- so that they can learn the tile and learn our action.+ -- If they already know the tile, they will just consider our action+ -- a waste of time and ignore the command.+ execUpdAtomic $ UpdSearchTile source tpos serverTile+ -- Searching also reveals the embedded items of the tile.+ -- If the items are already seen by the client+ -- (e.g., due to item detection, despite tile being still hidden),+ -- the command is ignored on the client.+ revealEmbed+ -- Seaching triggers the embeds as well, after they are revealed.+ -- The rationale is that the items were all the time present+ -- (just invisible to the client), so they need to be triggered.+ -- The exception is changable tiles, because they are not so easy+ -- to trigger; they need subsequent altering.+ unless (Tile.isDoor coTileSpeedup serverTile+ || Tile.isChangable coTileSpeedup serverTile) $+ itemEffectEmbedded source tpos embeds+ else if clientTile == serverTile then -- alters+ if alterSkill < Tile.alterMinSkill coTileSpeedup serverTile+ then execFailure source req AlterUnskilled -- don't leak about altering+ else do+ let changeTo tgroup = do+ lvl2 <- getLevel lid+ -- No @SfxAlter@, because the effect is obvious (e.g., opened door).+ let nightCond kt = not (Tile.kindHasFeature TK.Walkable kt+ && Tile.kindHasFeature TK.Clear kt)+ || (if lnight lvl2 then id else not)+ (Tile.kindHasFeature TK.Dark kt)+ -- Sometimes the tile is determined precisely by the ambient light+ -- of the source tiles. If not, default to cave day/night condition.+ mtoTile <- rndToAction $ opick tgroup nightCond+ toTile <- maybe (rndToAction+ $ fromMaybe (error $ "" `showFailure` tgroup)+ <$> opick tgroup (const True))+ return+ mtoTile+ unless (toTile == serverTile) $ do+ -- At most one of these two will be accepted on any given client.+ execUpdAtomic $ UpdAlterTile lid tpos serverTile toTile+ -- This case happens when a client does not see a searching+ -- action by another faction, but sees the subsequent altering.+ case hiddenTile of+ Just tHidden ->+ execUpdAtomic $ UpdAlterTile lid tpos tHidden toTile+ Nothing -> return ()+ case (Tile.isExplorable coTileSpeedup serverTile,+ Tile.isExplorable coTileSpeedup toTile) of+ (False, True) -> execUpdAtomic $ UpdAlterExplorable lid 1+ (True, False) -> execUpdAtomic $ UpdAlterExplorable lid (-1)+ _ -> return ()+ -- At the end we replace old embeds (even if partially used up)+ -- with new ones.+ -- If the source tile was hidden, the items could not be visible+ -- on a client, in which case the command would be ignored+ -- on the client, without causing any problems. Otherwise,+ -- if the position is in view, client has accurate info.+ case EM.lookup tpos (lembed lvl2) of+ Just bag -> do+ s <- getState+ let ais = map (\iid -> (iid, getItemBody iid s)) (EM.keys bag)+ execUpdAtomic $ UpdLoseItemBag (CEmbed lid tpos) bag ais+ Nothing -> return ()+ -- Altering always reveals the outcome tile, so it's not hidden+ -- and so its embedded items are always visible.+ embedItem lid tpos toTile+ feats = TK.tfeature $ okind serverTile+ toAlter feat =+ case feat of+ TK.OpenTo tgroup -> Just tgroup+ TK.CloseTo tgroup -> Just tgroup+ TK.ChangeTo tgroup -> Just tgroup+ _ -> Nothing+ groupsToAlterTo = mapMaybe toAlter feats+ if null groupsToAlterTo && null embeds then+ -- No altering possible; silly client.+ execFailure source req AlterNothing+ else+ if EM.notMember tpos $ lfloor lvl then+ if null (posToAidsLvl tpos lvl) then do+ -- The embeds of the initial tile are activated before the tile+ -- is altered. This prevents, e.g., trying to activate items+ -- where none are present any more, or very different to what+ -- the client expected. Surprise only comes through searching above.+ -- The items are first revealed for the sake of clients that+ -- may see the tile as hidden. Note that the tile is not revealed+ -- (unless it's altered later on, in which case the new one is).+ revealEmbed+ itemEffectEmbedded source tpos embeds case groupsToAlterTo of [] -> return () [groupToAlterTo] -> changeTo groupToAlterTo l -> error $ "tile changeable in many ways" `showFailure` l- itemEffectEmbedded source tpos embeds- else execFailure source req AlterBlockActor- else execFailure source req AlterBlockItem+ else execFailure source req AlterBlockActor+ else execFailure source req AlterBlockItem+ else -- client is misguided re tile at that position, so bail out+ execFailure source req AlterNothing -- * ReqWait -- | Do nothing. -- -- Something is sometimes done in 'setBWait'.-reqWait :: MonadAtomic m => ActorId -> m ()+reqWait :: MonadServerAtomic m => ActorId -> m () {-# INLINE reqWait #-} reqWait _ = return () -- * ReqMoveItems -reqMoveItems :: (MonadAtomic m, MonadServer m)+reqMoveItems :: MonadServerAtomic m => ActorId -> [(ItemId, Int, CStore, CStore)] -> m () reqMoveItems aid l = do b <- getsState $ getActorBody aid- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! aid+ ar <- getsState $ getActorAspect aid -- Server accepts item movement based on calm at the start, not end -- or in the middle, to avoid interrupted or partially ignored commands.- calmE = calmEnough b ar+ let calmE = calmEnough b ar mapM_ (reqMoveItem aid calmE) l -reqMoveItem :: (MonadAtomic m, MonadServer m)+reqMoveItem :: MonadServerAtomic m => ActorId -> Bool -> (ItemId, Int, CStore, CStore) -> m () reqMoveItem aid calmE (iid, k, fromCStore, toCStore) = do b <- getsState $ getActorBody aid@@ -469,7 +523,7 @@ | (fromCStore == CSha || toCStore == CSha) && not calmE -> execFailure aid req ItemNotCalm | otherwise -> do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull let itemFull = itemToF iid (k, []) when (fromCStore == CGround) $ discoverIfNoEffects fromC iid itemFull upds <- generalMoveItem True iid k fromC toC@@ -522,7 +576,7 @@ -- * ReqProject -reqProject :: (MonadAtomic m, MonadServer m)+reqProject :: MonadServerAtomic m => ActorId -- ^ actor projecting the item (is on current lvl) -> Point -- ^ target position of the projectile -> Int -- ^ digital line parameter@@ -532,9 +586,8 @@ reqProject source tpxy eps iid cstore = do let req = ReqProject tpxy eps iid cstore b <- getsState $ getActorBody source- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! source- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect source+ let calmE = calmEnough b ar if cstore == CSha && not calmE then execFailure source req ItemNotCalm else do mfail <- projectFail source tpxy eps iid cstore False@@ -542,7 +595,7 @@ -- * ReqApply -reqApply :: (MonadAtomic m, MonadServer m)+reqApply :: MonadServerAtomic m => ActorId -- ^ actor applying the item (is on current level) -> ItemId -- ^ the item to be applied -> CStore -- ^ the location of the item@@ -550,16 +603,15 @@ reqApply aid iid cstore = do let req = ReqApply iid cstore b <- getsState $ getActorBody aid- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! aid- calmE = calmEnough b ar+ ar <- getsState $ getActorAspect aid+ let calmE = calmEnough b ar if cstore == CSha && not calmE then execFailure aid req ItemNotCalm else do bag <- getsState $ getBodyStoreBag b cstore case EM.lookup iid bag of Nothing -> execFailure aid req ApplyOutOfReach Just kit -> do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull actorSk <- currentSkillsServer aid localTime <- getsState $ getLocalTime (blid b) let skill = EM.findWithDefault 0 Ability.AbApply actorSk@@ -571,11 +623,11 @@ -- * ReqGameRestart -reqGameRestart :: (MonadAtomic m, MonadServer m)+reqGameRestart :: MonadServerAtomic m => ActorId -> GroupName ModeKind -> Challenge -> m () reqGameRestart aid groupName scurChalSer = do- modifyServer $ \ser -> ser {sdebugNxt = (sdebugNxt ser) {scurChalSer}}+ modifyServer $ \ser -> ser {soptionsNxt = (soptionsNxt ser) {scurChalSer}} b <- getsState $ getActorBody aid oldSt <- getsState $ gquit . (EM.! bfid b) . sfactionD modifyServer $ \ser ->@@ -590,7 +642,7 @@ -- * ReqGameExit -reqGameExit :: (MonadAtomic m, MonadServer m) => ActorId -> m ()+reqGameExit :: MonadServerAtomic m => ActorId -> m () reqGameExit aid = do b <- getsState $ getActorBody aid oldSt <- getsState $ gquit . (EM.! bfid b) . sfactionD@@ -608,12 +660,12 @@ -- * ReqTactic -reqTactic :: MonadAtomic m => FactionId -> Tactic -> m ()+reqTactic :: MonadServerAtomic m => FactionId -> Tactic -> m () reqTactic fid toT = do fromT <- getsState $ ftactic . gplayer . (EM.! fid) . sfactionD execUpdAtomic $ UpdTacticFaction fid toT fromT -- * ReqAutomate -reqAutomate :: MonadAtomic m => FactionId -> m ()+reqAutomate :: MonadServerAtomic m => FactionId -> m () reqAutomate fid = execUpdAtomic $ UpdAutoFaction fid True
Game/LambdaHack/Server/ItemM.hs view
@@ -1,8 +1,11 @@ -- | Server operations for items. module Game.LambdaHack.Server.ItemM- ( rollItem, rollAndRegisterItem, registerItem- , placeItemsInDungeon, embedItemsInDungeon, fullAssocsServer- , itemToFullServer, mapActorCStore_+ ( registerItem, embedItem, rollItem, rollAndRegisterItem+ , placeItemsInDungeon, embedItemsInDungeon, mapActorCStore_+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , onlyRegisterItem, createLevelItem+#endif ) where import Prelude ()@@ -11,30 +14,31 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Function+import Data.Function import qualified Data.HashMap.Strict as HM-import Data.Ord+import Data.Ord -import Game.LambdaHack.Atomic-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Atomic+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.TileKind (TileKind)-import Game.LambdaHack.Server.ItemRev-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.State+import Game.LambdaHack.Content.TileKind (TileKind)+import Game.LambdaHack.Server.ItemRev+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State -onlyRegisterItem :: (MonadAtomic m, MonadServer m)+onlyRegisterItem :: MonadServerAtomic m => ItemKnown -> ItemSeed -> m ItemId onlyRegisterItem itemKnown@(_, aspectRecord, _, _) seed = do itemRev <- getsServer sitemRev@@ -42,21 +46,23 @@ Just iid -> return iid Nothing -> do icounter <- getsServer sicounter+ executedOnServer <-+ execUpdAtomicSer $ UpdDiscoverServer icounter aspectRecord+ let !_A = assert executedOnServer () modifyServer $ \ser ->- ser { sdiscoAspect = EM.insert icounter aspectRecord (sdiscoAspect ser)- , sitemSeedD = EM.insert icounter seed (sitemSeedD ser)+ ser { sitemSeedD = EM.insert icounter seed (sitemSeedD ser) , sitemRev = HM.insert itemKnown icounter (sitemRev ser) , sicounter = succ icounter } return $! icounter -registerItem :: (MonadAtomic m, MonadServer m)+registerItem :: MonadServerAtomic m => ItemFull -> ItemKnown -> ItemSeed -> Container -> Bool -> m ItemId registerItem ItemFull{..} itemKnown seed container verbose = do iid <- onlyRegisterItem itemKnown seed let cmd = if verbose then UpdCreateItem else UpdSpotItem False execUpdAtomic $ cmd iid itemBase (itemK, itemTimer) container- knowItems <- getsServer $ sknowItems . sdebugSer+ knowItems <- getsServer $ sknowItems . soptions when knowItems $ case container of CTrunk{} -> return () _ -> do@@ -64,13 +70,13 @@ execUpdAtomic $ UpdDiscover container iid itemKindId seed return iid -createLevelItem :: (MonadAtomic m, MonadServer m) => Point -> LevelId -> m ()+createLevelItem :: MonadServerAtomic m => Point -> LevelId -> m () createLevelItem pos lid = do Level{litemFreq} <- getLevel lid let container = CFloor lid pos void $ rollAndRegisterItem lid litemFreq container True Nothing -embedItem :: (MonadAtomic m, MonadServer m)+embedItem :: MonadServerAtomic m => LevelId -> Point -> Kind.Id TileKind -> m () embedItem lid pos tk = do Kind.COps{cotile} <- getsState scops@@ -79,14 +85,14 @@ f grp = rollAndRegisterItem lid [(grp, 1)] container False Nothing mapM_ f embeds -rollItem :: (MonadAtomic m, MonadServer m)+rollItem :: MonadServerAtomic m => Int -> LevelId -> Freqs ItemKind -> m (Maybe ( ItemKnown, ItemFull, ItemDisco , ItemSeed, GroupName ItemKind )) rollItem lvlSpawned lid itemFreq = do cops <- getsState scops flavour <- getsServer sflavour- disco <- getsServer sdiscoKind+ disco <- getsState sdiscoKind discoRev <- getsServer sdiscoKindRev uniqueSet <- getsServer suniqueSet totalDepth <- getsState stotalDepth@@ -101,7 +107,7 @@ _ -> return () return m5 -rollAndRegisterItem :: (MonadAtomic m, MonadServer m)+rollAndRegisterItem :: MonadServerAtomic m => LevelId -> Freqs ItemKind -> Container -> Bool -> Maybe Int -> m (Maybe (ItemId, (ItemFull, GroupName ItemKind)))@@ -115,7 +121,7 @@ iid <- registerItem itemFull itemKnown seed container verbose return $ Just (iid, (itemFull, itemGroup)) -placeItemsInDungeon :: forall m. (MonadAtomic m, MonadServer m) => m ()+placeItemsInDungeon :: forall m. MonadServerAtomic m => m () placeItemsInDungeon = do Kind.COps{coTileSpeedup} <- getsState scops let initialItems (lid, Level{ltile, litemNum, lxsize, lysize}) = do@@ -145,7 +151,7 @@ fromEasyToHard = sortBy (comparing absLid `on` fst) $ EM.assocs dungeon mapM_ initialItems fromEasyToHard -embedItemsInDungeon :: (MonadAtomic m, MonadServer m) => m ()+embedItemsInDungeon :: MonadServerAtomic m => m () embedItemsInDungeon = do let embedItems (lid, Level{ltile}) = PointArray.imapMA_ (embedItem lid) ltile dungeon <- getsState sdungeon@@ -154,24 +160,6 @@ let absLid = abs . fromEnum fromEasyToHard = sortBy (comparing absLid `on` fst) $ EM.assocs dungeon mapM_ embedItems fromEasyToHard--fullAssocsServer :: MonadServer m- => ActorId -> [CStore] -> m [(ItemId, ItemFull)]-fullAssocsServer aid cstores = do- cops <- getsState scops- discoKind <- getsServer sdiscoKind- discoAspect <- getsServer sdiscoAspect- getsState $ fullAssocs cops discoKind discoAspect aid cstores--itemToFullServer :: MonadServer m => m (ItemId -> ItemQuant -> ItemFull)-itemToFullServer = do- cops <- getsState scops- discoKind <- getsServer sdiscoKind- discoAspect <- getsServer sdiscoAspect- s <- getState- let itemToF iid =- itemToFull cops discoKind discoAspect iid (getItemBody iid s)- return itemToF -- | Mapping over actor's items from a give store. mapActorCStore_ :: MonadServer m
Game/LambdaHack/Server/ItemRev.hs view
@@ -1,10 +1,10 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}--- | Server types and operations for items that don't involve server state--- nor our custom monads.+-- | Creation of items on the server. Types and operations that don't involve+-- server state nor our custom monads. module Game.LambdaHack.Server.ItemRev- ( ItemKnown, ItemRev, buildItem, newItem, UniqueSet+ ( ItemKnown, ItemRev, UniqueSet, buildItem, newItem -- * Item discovery types- , DiscoveryKindRev, serverDiscos, ItemSeedDict+ , DiscoveryKindRev, ItemSeedDict, serverDiscos -- * The @FlavourMap@ type , FlavourMap, emptyFlavourMap, dungeonFlavourMap ) where@@ -13,49 +13,41 @@ import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.HashMap.Strict as HM import qualified Data.Set as S import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Flavour-import Game.LambdaHack.Common.Frequency-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK --- | The reverse map to @DiscoveryKind@, needed for item creation.-type DiscoveryKindRev = EM.EnumMap (Kind.Id ItemKind) ItemKindIx+-- | The essential item properties, used for the @ItemRev@ hash table+-- from items to their ids, needed to assign ids to newly generated items.+-- All the other meaningul properties can be derived from them.+-- Note 1: @jlid@ is not meaningful; it gets forgotten if items from+-- different levels roll the same random properties and so are merged.+-- However, the first item generated by the server wins, which is most+-- of the time the lower @jlid@ item, which makes sense for the client.+-- Note 2: @ItemSeed@ instead of @AspectRecord@ is not enough,+-- becaused different seeds may result in the same @AspectRecord@+-- and we don't want such items to be distinct in UI and elsewhere.+type ItemKnown = (ItemKindIx, AspectRecord, Dice.Dice, Maybe FactionId) --- | The map of item ids to item seeds, needed for item creation.-type ItemSeedDict = EM.EnumMap ItemId ItemSeed+-- | Reverse item map, for item creation, to keep items and item identifiers+-- in bijection.+type ItemRev = HM.HashMap ItemKnown ItemId type UniqueSet = ES.EnumSet (Kind.Id ItemKind) -serverDiscos :: Kind.COps -> Rnd (DiscoveryKind, DiscoveryKindRev)-serverDiscos Kind.COps{coitem=Kind.Ops{olength, ofoldlWithKey', okind}} = do- let ixs = [toEnum 0..toEnum (olength-1)]- shuffle :: Eq a => [a] -> Rnd [a]- shuffle [] = return []- shuffle l = do- x <- oneOf l- (x :) <$> shuffle (delete x l)- shuffled <- shuffle ixs- let f (!ikMap, !ikRev, ix : rest) kmKind _ =- let kmMean = meanAspect $ okind kmKind- in (EM.insert ix KindMean{..} ikMap, EM.insert kmKind ix ikRev, rest)- f (ikMap, _, []) ik _ =- error $ "too short ixs" `showFailure` (ik, ikMap)- (discoS, discoRev, _) =- ofoldlWithKey' f (EM.empty, EM.empty, shuffled)- return (discoS, discoRev)- -- | Build an item with the given stats. buildItem :: FlavourMap -> DiscoveryKindRev -> Kind.Id ItemKind -> ItemKind -> LevelId -> Dice.Dice@@ -122,11 +114,11 @@ itemK = max 1 itemN itemTimer = [timeZero | IK.Periodic `elem` IK.ieffects itemKind] -- delay first discharge of single organs- itemAspectMean =- kmMean $ EM.findWithDefault (error $ "" `showFailure` kindIx)- kindIx disco- itemDiscoData = ItemDisco { itemKindId, itemKind, itemAspectMean- , itemAspect = Just aspectRecord }+ km = EM.findWithDefault (error $ "" `showFailure` kindIx) kindIx disco+ itemAspectMean = kmMean km+ itemConst = kmConst km+ itemAspect = Just aspectRecord+ itemDiscoData = ItemDisco {..} itemDisco = Just itemDiscoData -- Bonuses on items/actors unaffected by number of spawned actors. aspectRecord = seedToAspect seed itemKind ldepth totalDepth@@ -137,6 +129,32 @@ , seed , itemGroup ) +-- | The reverse map to @DiscoveryKind@, needed for item creation.+type DiscoveryKindRev = EM.EnumMap (Kind.Id ItemKind) ItemKindIx++-- | The map of item ids to item seeds, needed for item creation.+type ItemSeedDict = EM.EnumMap ItemId ItemSeed++serverDiscos :: Kind.COps -> Rnd (DiscoveryKind, DiscoveryKindRev)+serverDiscos Kind.COps{coitem=Kind.Ops{olength, ofoldlWithKey', okind}} = do+ let ixs = [toEnum 0..toEnum (olength-1)]+ shuffle :: Eq a => [a] -> Rnd [a]+ shuffle [] = return []+ shuffle l = do+ x <- oneOf l+ (x :) <$> shuffle (delete x l)+ shuffled <- shuffle ixs+ let f (!ikMap, !ikRev, ix : rest) kmKind _ =+ let kind = okind kmKind+ kmMean = meanAspect kind+ kmConst = not $ aspectsRandom kind+ in (EM.insert ix KindMean{..} ikMap, EM.insert kmKind ix ikRev, rest)+ f (ikMap, _, []) ik _ =+ error $ "too short ixs" `showFailure` (ik, ikMap)+ (discoS, discoRev, _) =+ ofoldlWithKey' f (EM.empty, EM.empty, shuffled)+ return (discoS, discoRev)+ -- | Flavours assigned by the server to item kinds, in this particular game. newtype FlavourMap = FlavourMap (EM.EnumMap (Kind.Id ItemKind) Flavour) deriving (Show, Binary)@@ -175,19 +193,3 @@ liftM (FlavourMap . fst) $ ofoldlWithKey' (rollFlavourMap (S.fromList stdFlav)) (return (EM.empty, EM.empty))---- | Reverse item map, for item creation, to keep items and item identifiers--- in bijection.-type ItemRev = HM.HashMap ItemKnown ItemId---- | The essential item properties, used for the @ItemRev@ hash table--- from items to their ids, needed to assign ids to newly generated items.--- All the other meaningul properties can be derived from them.--- Note 1: @jlid@ is not meaningful; it gets forgotten if items from--- different levels roll the same random properties and so are merged.--- However, the first item generated by the server wins, which is most--- of the time the lower @jlid@ item, which makes sense for the client.--- Note 2: @ItemSeed@ instead of @AspectRecord@ is not enough,--- becaused different seeds may result in the same @AspectRecord@--- and we don't want such items to be distinct in UI and elsewhere.-type ItemKnown = (ItemKindIx, AspectRecord, Dice.Dice, Maybe FactionId)
Game/LambdaHack/Server/LoopM.hs view
@@ -6,9 +6,9 @@ #ifdef EXPOSE_INTERNAL -- * Internal operations , factionArena, arenasForLoop, handleFidUpd, loopUpd, endClip- , applyPeriodicLevel- , handleTrajectories, hTrajectories, handleActors, hActors- , gameExit, restartGame, writeSaveAll, setTrajectory+ , manageCalmAndDomination, applyPeriodicLevel+ , handleTrajectories, hTrajectories, setTrajectory+ , handleActors, hActors, restartGame #endif ) where @@ -20,73 +20,80 @@ import qualified Data.EnumSet as ES import qualified Data.Ord as Ord -import Game.LambdaHack.Atomic-import Game.LambdaHack.Client (Config, SessionUI)-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemStrongest+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.Response-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Vector import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind-import Game.LambdaHack.Server.EndM-import Game.LambdaHack.Server.Fov-import Game.LambdaHack.Server.HandleEffectM-import Game.LambdaHack.Server.HandleRequestM-import Game.LambdaHack.Server.ItemM-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.PeriodicM-import Game.LambdaHack.Server.ProtocolM-import Game.LambdaHack.Server.StartM-import Game.LambdaHack.Server.State+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Server.CommonM+import Game.LambdaHack.Server.EndM+import Game.LambdaHack.Server.HandleEffectM+import Game.LambdaHack.Server.HandleRequestM+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.PeriodicM+import Game.LambdaHack.Server.ProtocolM+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.StartM+import Game.LambdaHack.Server.State -- | Start a game session, including the clients, and then loop, -- communicating with the clients.-loopSer :: (MonadAtomic m, MonadServerReadRequest m)- => DebugModeSer -- ^ server debug parameters- -> Config- -> (Maybe SessionUI -> FactionId -> ChanServer -> IO ())- -- ^ the code to run for UI clients+--+-- The loop is started in server state that is empty, see 'emptyStateServer'.+loopSer :: (MonadServerAtomic m, MonadServerReadRequest m)+ => ServerOptions+ -- ^ player-supplied server options+ -> (Bool -> FactionId -> ChanServer -> IO ())+ -- ^ function that initializes a client and runs its main loop -> m ()-loopSer sdebug sconfig executorClient = do+loopSer serverOptions executorClient = do -- Recover states and launch clients.+ modifyServer $ \ser -> ser { soptionsNxt = serverOptions+ , soptions = serverOptions } cops <- getsState scops- let updConn = updateConn sconfig executorClient- restored <- tryRestore cops sdebug+ let updConn = updateConn executorClient+ restored <- tryRestore case restored of- Just (sRaw, ser) | not $ snewGameSer sdebug -> do -- a restored game+ Just (sRaw, ser) | not $ snewGameSer serverOptions -> do -- a restored game execUpdAtomic $ UpdResumeServer $ updateCOps (const cops) sRaw- putServer ser- modifyServer $ \ser2 -> ser2 {sdebugNxt = sdebug}+ putServer ser {soptionsNxt = serverOptions} applyDebug+ factionD <- getsState sfactionD+ let f fid = let cmd = UpdResumeServer $ updateCOps (const cops)+ $ sclientStates ser EM.! fid+ in execUpdAtomicFidCatch fid cmd+ mapM_ f $ EM.keys factionD updConn initPer pers <- getsServer sperFid- factionD <- getsState sfactionD mapM_ (\fid -> sendUpdate fid $ UpdResume fid (pers EM.! fid)) (EM.keys factionD)- -- We dump RNG seeds here, in case the game wasn't run- -- with --dumpInitRngs previously and we need the seeds.+ -- We dump RNG seeds here, based on @soptionsNxt@, in case the game+ -- wasn't run with @--dumpInitRngs@ previously, but we need the seeds,+ -- e.g., to diagnose a crash. rngs <- getsServer srngs- when (sdumpInitRngs sdebug) $ dumpRngs rngs+ when (sdumpInitRngs serverOptions) $ dumpRngs rngs _ -> do -- starting new game for this savefile (--newGame or fresh save)- s <- gameReset cops sdebug Nothing Nothing -- get RNG from item boost- -- Set up commandline debug mode.- let debugBarRngs = sdebug {sdungeonRng = Nothing, smainRng = Nothing}- modifyServer $ \ser -> ser { sdebugNxt = debugBarRngs- , sdebugSer = debugBarRngs }+ s <- gameReset serverOptions Nothing Nothing+ -- get RNG from item boost+ -- Set up commandline options.+ let optionsBarRngs =+ serverOptions {sdungeonRng = Nothing, smainRng = Nothing}+ modifyServer $ \ser -> ser { soptionsNxt = optionsBarRngs+ , soptions = optionsBarRngs } execUpdAtomic $ UpdRestartServer s updConn initPer@@ -95,7 +102,7 @@ loopUpd updConn factionArena :: MonadStateRead m => Faction -> m (Maybe LevelId)-factionArena fact = case _gleader fact of+factionArena fact = case gleader fact of -- Even spawners need an active arena for their leader, -- or they start clogging stairs. Just leader -> do@@ -117,7 +124,7 @@ `swith` factionD) () return $! arenas -handleFidUpd :: (MonadAtomic m, MonadServerReadRequest m)+handleFidUpd :: (MonadServerAtomic m, MonadServerReadRequest m) => Bool -> (FactionId -> m ()) -> FactionId -> Faction -> m Bool {-# INLINE handleFidUpd #-} handleFidUpd True _ _ _ = return True@@ -144,10 +151,11 @@ Nothing -> arenas handle myArenas --- | Handle a clip (a part of a turn for which one or more frames--- will be generated). Run the leader and other actors moves.+-- | Handle a clip (the smallest fraction of a game turn for which a frame may+-- potentially be generated). Run the leader and other actors moves. -- Eventually advance the time and repeat.-loopUpd :: forall m. (MonadAtomic m, MonadServerReadRequest m) => m () -> m ()+loopUpd :: forall m. (MonadServerAtomic m, MonadServerReadRequest m)+ => m () -> m () loopUpd updConn = do let updatePerFid :: FactionId -> m () {-# NOINLINE updatePerFid #-}@@ -178,16 +186,15 @@ if quit then do modifyServer $ \ser -> ser {squit = False} endOrLoop loopUpdConn (restartGame updConn loopUpdConn)- gameExit (writeSaveAll True)+ (writeSaveAll True) else loopUpdConn loopUpdConn -- | Handle the end of every clip. Do whatever has to be done--- every fixed number of time units, e.g., monster generation.+-- every fixed number of clips, e.g., monster generation. -- Advance time. Perform periodic saves, if applicable.-endClip :: forall m. (MonadAtomic m, MonadServer m)- => (FactionId -> m ()) -> m ()+endClip :: forall m. MonadServerAtomic m => (FactionId -> m ()) -> m () {-# INLINE endClip #-} endClip updatePerFid = do Kind.COps{corule} <- getsState scops@@ -228,13 +235,12 @@ when (clipN `mod` rwriteSaveClips == 0) $ writeSaveAll False -- | Check if the given actor is dominated and update his calm.-manageCalmAndDomination :: (MonadAtomic m, MonadServer m)- => ActorId -> Actor -> m ()+manageCalmAndDomination :: MonadServerAtomic m => ActorId -> Actor -> m () manageCalmAndDomination aid b = do Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops fact <- getsState $ (EM.! bfid b) . sfactionD getItem <- getsState $ flip getItemBody- discoKind <- getsServer sdiscoKind+ discoKind <- getsState sdiscoKind let isImpression iid = case EM.lookup (jkindIx $ getItem iid) discoKind of Just KindMean{kmKind} -> maybe False (> 0) (lookup "impressed" $ IK.ifreq $ okind kmKind)@@ -253,15 +259,14 @@ Just fid1 -> assert (fid1 /= bfid b) $ dominateFidSfx fid1 aid else return False unless dominated $ do- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! aid+ ar <- getsState $ getActorAspect aid newCalmDelta <- getsState $ regenCalmDelta b ar unless (newCalmDelta == 0) $ -- Update delta for the current player turn. udpateCalm aid newCalmDelta -- | Trigger periodic items for all actors on the given level.-applyPeriodicLevel :: (MonadAtomic m, MonadServer m) => m ()+applyPeriodicLevel :: MonadServerAtomic m => m () applyPeriodicLevel = do arenas <- getsServer sarenas let arenasSet = ES.fromDistinctAscList arenas@@ -273,7 +278,7 @@ case iid `EM.lookup` bag of Nothing -> return () -- item dropped Just kit -> do- itemToF <- itemToFullServer+ itemToF <- getsState itemToFull let itemFull = itemToF iid kit case itemDisco itemFull of Just ItemDisco {itemKind=IK.ItemKind{IK.ieffects}} ->@@ -292,8 +297,7 @@ allActors <- getsState sactorD mapM_ applyPeriodicActor $ EM.assocs allActors -handleTrajectories :: (MonadAtomic m, MonadServer m)- => LevelId -> FactionId -> m ()+handleTrajectories :: MonadServerAtomic m => LevelId -> FactionId -> m () handleTrajectories lid fid = do localTime <- getsState $ getLocalTime lid levelTime <- getsServer $ (EM.! lid) . (EM.! fid) . sactorTime@@ -311,7 +315,7 @@ -- from projectiles or pushed actors doesn't work; too late. -- Even if the actor got teleported to another level by this point, -- we don't care, we set the trajectory, check death, etc.-hTrajectories :: (MonadAtomic m, MonadServer m) => (ActorId, Actor) -> m ()+hTrajectories :: MonadServerAtomic m => (ActorId, Actor) -> m () {-# INLINE hTrajectories #-} hTrajectories (aid, b) = do b2 <- if actorDying b then return b else do@@ -344,7 +348,7 @@ -- Not advancing time forces dead projectiles to be destroyed ASAP. -- Otherwise, with some timings, it can stay on the game map dead, -- blocking path of human-controlled actors and alarming the hapless human.-setTrajectory :: (MonadAtomic m, MonadServer m) => ActorId -> m ()+setTrajectory :: MonadServerAtomic m => ActorId -> m () {-# INLINE setTrajectory #-} setTrajectory aid = do Kind.COps{coTileSpeedup} <- getsState scops@@ -368,14 +372,14 @@ -- Nothing from non-empty trajectories signifies obstacle hit. execUpdAtomic $ UpdTrajectory aid (btrajectory b) Nothing -- Lose HP due to flying into an obstacle.- execUpdAtomic $ UpdRefillHP aid minusM+ refillHP False aid b minusM Just ([], _) -> -- Non-projectile actor stops flying. assert (not $ bproj b) $ execUpdAtomic $ UpdTrajectory aid (btrajectory b) Nothing _ -> error $ "Nothing trajectory" `showFailure` (aid, b) -handleActors :: (MonadAtomic m, MonadServerReadRequest m)+handleActors :: (MonadServerAtomic m, MonadServerReadRequest m) => LevelId -> FactionId -> m Bool handleActors lid fid = do localTime <- getsState $ getLocalTime lid@@ -383,14 +387,14 @@ factionD <- getsState sfactionD s <- getState -- Leader acts first, so that UI leader can save&exit before state changes.- let notLeader (aid, b) = Just aid /= _gleader (factionD EM.! bfid b)+ let notLeader (aid, b) = Just aid /= gleader (factionD EM.! bfid b) l = sortBy (Ord.comparing notLeader) $ filter (\(_, b) -> isNothing (btrajectory b) && bhp b > 0) $ map (\(a, _) -> (a, getActorBody a s)) $ filter (\(_, atime) -> atime <= localTime) $ EM.assocs levelTime hActors fid l -hActors :: forall m. (MonadAtomic m, MonadServerReadRequest m)+hActors :: forall m. (MonadServerAtomic m, MonadServerReadRequest m) => FactionId -> [(ActorId, Actor)] -> m Bool hActors _ [] = return False hActors _fid as@((aid, body) : rest) = do@@ -398,7 +402,7 @@ !_A = assert (side == _fid) () fact <- getsState $ (EM.! side) . sfactionD squit <- getsServer squit- let mleader = _gleader fact+ let mleader = gleader fact aidIsLeader = mleader == Just aid mainUIactor = fhasUI (gplayer fact) && (aidIsLeader@@ -440,74 +444,18 @@ swriteSave <- getsServer swriteSave if swriteSave then return False else hActors side as -gameExit :: (MonadAtomic m, MonadServerReadRequest m) => m ()-gameExit = do- -- Verify that the not saved caches are equal to future reconstructed.- -- Otherwise, save/restore would change game state.--- debugPossiblyPrint "Verifying all perceptions."- sperCacheFid <- getsServer sperCacheFid- sperValidFid <- getsServer sperValidFid- sactorAspect <- getsServer sactorAspect- sfovLucidLid <- getsServer sfovLucidLid- sfovClearLid <- getsServer sfovClearLid- sfovLitLid <- getsServer sfovLitLid- sperFid <- getsServer sperFid- discoAspect <- getsServer sdiscoAspect- ( actorAspect, fovLitLid, fovClearLid, fovLucidLid- ,perValidFid, perCacheFid, perFid )- <- getsState $ perFidInDungeon discoAspect- let !_A7 = assert (sfovLitLid == fovLitLid- `blame` "wrong accumulated sfovLitLid"- `swith` (sfovLitLid, fovLitLid)) ()- !_A6 = assert (sfovClearLid == fovClearLid- `blame` "wrong accumulated sfovClearLid"- `swith` (sfovClearLid, fovClearLid)) ()- !_A5 = assert (sactorAspect == actorAspect- `blame` "wrong accumulated sactorAspect"- `swith` (sactorAspect, actorAspect)) ()- !_A4 = assert (sfovLucidLid == fovLucidLid- `blame` "wrong accumulated sfovLucidLid"- `swith` (sfovLucidLid, fovLucidLid)) ()- !_A3 = assert (sperValidFid == perValidFid- `blame` "wrong accumulated sperValidFid"- `swith` (sperValidFid, perValidFid)) ()- !_A2 = assert (sperCacheFid == perCacheFid- `blame` "wrong accumulated sperCacheFid"- `swith` (sperCacheFid, perCacheFid)) ()- !_A1 = assert (sperFid == perFid- `blame` "wrong accumulated perception"- `swith` (sperFid, perFid)) ()- -- Kill all clients, including those that did not take part- -- in the current game.- -- Clients exit not now, but after they print all ending screens.- -- debugPrint "Server kills clients"--- debugPossiblyPrint "Killing all clients."- killAllClients--- debugPossiblyPrint "All clients killed."- return ()--restartGame :: (MonadAtomic m, MonadServer m)+restartGame :: MonadServerAtomic m => m () -> m () -> Maybe (GroupName ModeKind) -> m () restartGame updConn loop mgameMode = do- cops <- getsState scops- sdebugNxt <- getsServer sdebugNxt+ soptionsNxt <- getsServer soptionsNxt srandom <- getsServer srandom- s <- gameReset cops sdebugNxt mgameMode (Just srandom)- let debugBarRngs = sdebugNxt {sdungeonRng = Nothing, smainRng = Nothing}- modifyServer $ \ser -> ser { sdebugNxt = debugBarRngs- , sdebugSer = debugBarRngs }+ s <- gameReset soptionsNxt mgameMode (Just srandom)+ let optionsBarRngs = soptionsNxt {sdungeonRng = Nothing, smainRng = Nothing}+ modifyServer $ \ser -> ser { soptionsNxt = optionsBarRngs+ , soptions = optionsBarRngs } execUpdAtomic $ UpdRestartServer s updConn initPer reinitGame writeSaveAll False loop---- | Save game on server and all clients.-writeSaveAll :: (MonadAtomic m, MonadServer m) => Bool -> m ()-writeSaveAll uiRequested = do- bench <- getsServer $ sbenchmark . sdebugCli . sdebugSer- noConfirmsGame <- isNoConfirmsGame- when (uiRequested || not bench && not noConfirmsGame) $ do- execUpdAtomic UpdWriteSave- saveServer
Game/LambdaHack/Server/MonadServer.hs view
@@ -1,14 +1,12 @@--- | Game action monads and basic building blocks for human and computer--- player actions. Has no access to the main action type.--- Does not export the @liftIO@ operation nor a few other implementation--- details.+-- | Basic server monads and related operations. module Game.LambdaHack.Server.MonadServer ( -- * The server monad MonadServer( getsServer , modifyServer- , saveChanServer -- exposed only to be implemented, not used+ , chanSaveServer -- exposed only to be implemented, not used , liftIO -- exposed only to be implemented, not used )+ , MonadServerAtomic(..) -- * Assorted primitives , getServer, putServer, debugPossiblyPrint, debugPossiblyPrintAndExit , serverPrint, saveServer, dumpRngs, restoreScore, registerScore@@ -22,41 +20,64 @@ -- Cabal import qualified Paths_LambdaHack as Self (version) -import qualified Control.Exception as Ex hiding (handle)+import qualified Control.Exception as Ex import qualified Control.Monad.Trans.State.Strict as St import qualified Data.EnumMap.Strict as EM import qualified Data.Text as T import qualified Data.Text.IO as T-import Data.Time.Clock.POSIX-import Data.Time.LocalTime-import System.Exit (exitFailure)-import System.FilePath-import System.IO (hFlush, stdout)+import Data.Time.Clock.POSIX+import Data.Time.LocalTime+import System.Exit (exitFailure)+import System.FilePath+import System.IO (hFlush, stdout) import qualified System.Random as R -import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.File+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.File import qualified Game.LambdaHack.Common.HighScore as HighScore import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Random import qualified Game.LambdaHack.Common.Save as Save-import Game.LambdaHack.Common.State-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind-import Game.LambdaHack.Server.State+import Game.LambdaHack.Common.State+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State class MonadStateRead m => MonadServer m where getsServer :: (StateServer -> a) -> m a modifyServer :: (StateServer -> StateServer) -> m ()- saveChanServer :: m (Save.ChanSave (State, StateServer))+ chanSaveServer :: m (Save.ChanSave (State, StateServer)) -- 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 +-- | The monad for executing atomic game state transformations.+class MonadServer m => MonadServerAtomic m where+ -- | Execute an atomic command that changes the state+ -- on the server and on all clients that can notice it.+ execUpdAtomic :: UpdAtomic -> m ()+ -- | Execute an atomic command that changes the state+ -- on the server only.+ execUpdAtomicSer :: UpdAtomic -> m Bool+ -- | Execute an atomic command that changes the state+ -- on the given single client only.+ execUpdAtomicFid :: FactionId -> UpdAtomic -> m ()+ -- | Execute an atomic command that changes the state+ -- on the given single client only.+ -- Catch 'AtomicFail' and indicate if it was in fact raised.+ execUpdAtomicFidCatch :: FactionId -> UpdAtomic -> m Bool+ -- | Execute an atomic command that only displays special effects.+ execSfxAtomic :: SfxAtomic -> m ()+ execSendPer :: FactionId -> LevelId+ -> Perception -> Perception -> Perception -> m ()+ getServer :: MonadServer m => m StateServer getServer = getsServer id @@ -65,14 +86,14 @@ debugPossiblyPrint :: MonadServer m => Text -> m () debugPossiblyPrint t = do- debug <- getsServer $ sdbgMsgSer . sdebugSer+ debug <- getsServer $ sdbgMsgSer . soptions when debug $ liftIO $ do T.hPutStrLn stdout t hFlush stdout debugPossiblyPrintAndExit :: MonadServer m => Text -> m () debugPossiblyPrintAndExit t = do- debug <- getsServer $ sdbgMsgSer . sdebugSer+ debug <- getsServer $ sdbgMsgSer . soptions when debug $ liftIO $ do T.hPutStrLn stdout t hFlush stdout@@ -87,10 +108,10 @@ saveServer = do s <- getState ser <- getServer- toSave <- saveChanServer+ toSave <- chanSaveServer liftIO $ Save.saveToChan toSave (s, ser) --- | Dumps RNG states from the start of the game to stdout.+-- | Dumps to stdout the RNG states from the start of the game. dumpRngs :: MonadServer m => RNGs -> m () dumpRngs rngs = liftIO $ do T.hPutStrLn stdout $ tshow rngs@@ -99,7 +120,7 @@ -- | Read the high scores dictionary. Return the empty table if no file. restoreScore :: forall m. MonadServer m => Kind.COps -> m HighScore.ScoreDict restoreScore Kind.COps{corule} = do- bench <- getsServer $ sbenchmark . sdebugCli . sdebugSer+ bench <- getsServer $ sbenchmark . sclientOptions . soptions mscore <- if bench then return Nothing else do let stdRuleset = Kind.stdRuleset corule scoresFile = rscoresFile stdRuleset@@ -140,13 +161,15 @@ time <- getsState stime date <- liftIO getPOSIXTime tz <- liftIO $ getTimeZone $ posixSecondsToUTCTime date- curChalSer <- getsServer $ scurChalSer . sdebugSer+ curChalSer <- getsServer $ scurChalSer . soptions factionD <- getsState sfactionD- bench <- getsServer $ sbenchmark . sdebugCli . sdebugSer+ bench <- getsServer $ sbenchmark . sclientOptions . soptions+ noConfirmsGame <- isNoConfirmsGame let path = dataDir </> scoresFile outputScore (worthMentioning, (ntable, pos)) =- -- If not human, probably debugging, so dump instead of registering.- if bench || isAIFact fact then+ -- If testing or fooling around, dump instead of registering.+ -- In particular don't register score for the auto-* scenarios.+ if bench || noConfirmsGame || isAIFact fact then debugPossiblyPrint $ T.intercalate "\n" $ HighScore.showScore tz (pos, HighScore.getRecord pos ntable) else@@ -179,7 +202,7 @@ modifyServer $ \ser -> ser {srandom = gen1} return $! St.evalState r gen2 --- | Gets a random generator from the arguments or, if not present,+-- | Gets a random generator from the user-submitted options or, if not present, -- generates one. getSetGen :: MonadServer m => Maybe R.StdGen -> m R.StdGen getSetGen mrng = case mrng of
Game/LambdaHack/Server/PeriodicM.hs view
@@ -2,8 +2,11 @@ -- and related operations. module Game.LambdaHack.Server.PeriodicM ( spawnMonster, addAnyActor- , advanceTime, overheadActorTime, swapTime- , leadLevelSwitch, udpateCalm+ , advanceTime, overheadActorTime, swapTime, udpateCalm, leadLevelSwitch+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , rollSpawnPos+#endif ) where import Prelude ()@@ -12,35 +15,35 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES-import Data.Int (Int64)+import Data.Int (Int64) -import Game.LambdaHack.Atomic-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Frequency-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Atomic+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.ActorState+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Frequency+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind (ItemKind) import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Server.CommonM-import Game.LambdaHack.Server.ItemM-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.State+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Server.CommonM+import Game.LambdaHack.Server.ItemM+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.State -- | Spawn, possibly, a monster according to the level's actor groups. -- We assume heroes are never spawned.-spawnMonster :: (MonadAtomic m, MonadServer m) => m ()+spawnMonster :: MonadServerAtomic m => m () spawnMonster = do arenas <- getsServer sarenas -- Do this on only one of the arenas to prevent micromanagement,@@ -60,10 +63,10 @@ Nothing -> return () Just aid -> do b <- getsState $ getActorBody aid- mleader <- getsState $ _gleader . (EM.! bfid b) . sfactionD+ mleader <- getsState $ gleader . (EM.! bfid b) . sfactionD when (isNothing mleader) $ supplantLeader (bfid b) aid -addAnyActor :: (MonadAtomic m, MonadServer m)+addAnyActor :: MonadServerAtomic m => Freqs ItemKind -> LevelId -> Time -> Maybe Point -> m (Maybe ActorId) addAnyActor actorFreq lid time mpos = do@@ -100,8 +103,7 @@ Nothing -> do rollPos <- getsState $ rollSpawnPos cops allPers mobile lid lvl fid rndToAction rollPos- registerActor itemKnownRaw itemFullRaw itemDisco seed- fid pos lid id time+ registerActor itemKnownRaw itemFullRaw seed fid pos lid id time rollSpawnPos :: Kind.COps -> ES.EnumSet Point -> Bool -> LevelId -> Level -> FactionId -> State@@ -149,48 +151,51 @@ ] -- | Advance the move time for the given actor-advanceTime :: (MonadAtomic m, MonadServer m) => ActorId -> Int -> m ()+advanceTime :: MonadServerAtomic m => ActorId -> Int -> m () advanceTime aid percent = do b <- getsState $ getActorBody aid- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! aid- t = timeDeltaPercent (ticksPerMeter $ bspeed b ar) percent+ ar <- getsState $ getActorAspect aid+ let t = timeDeltaPercent (ticksPerMeter $ bspeed b ar) percent -- @t@ may be negative; that's OK. modifyServer $ \ser -> ser {sactorTime = ageActor (bfid b) (blid b) aid t $ sactorTime ser} -- Add communication overhead time delta to all non-projectile, non-dying--- faction's actors (except the leader). Effectively, this limits moves of--- a faction to 10, regardless of the number of actors and their speeds.--- To discourage micromanagement distributing actors among active arenas,--- overhead applies to all actors in active arenas.+-- faction's actors (except the leader). Effectively, this limits moves+-- of a faction on a level to 10, regardless of the number of actors+-- and their speeds. To avoid animals suddenly acting extremely sluggish+-- whenever monster's leader visits a distant arena that has a crowd+-- of animals, overhead applies only to actors on the same level.+-- Since the number of active arenas is limited, this bounds the total moves+-- per turn of each faction as well. -- -- Leader is immune from overhead and so he is faster than other faction -- members and of equal speed to leaders of other factions (of equal -- base speed) regardless how numerous the faction is. -- Thanks to this, there is no problem with leader of a numerous faction -- having very long UI turns, introducing UI lag.-overheadActorTime :: (MonadAtomic m, MonadServer m) => FactionId -> m ()-overheadActorTime fid = do+overheadActorTime :: MonadServerAtomic m => FactionId -> LevelId -> m ()+overheadActorTime fid lid = do actorTime <- getsServer $ (EM.! fid) . sactorTime s <- getState- mleader <- getsState $ _gleader . (EM.! fid) . sfactionD+ mleader <- getsState $ gleader . (EM.! fid) . sfactionD arenas <- getsServer sarenas- let f !aid !time =+ let f !lid2 !_aid !time | lid2 /= lid = time+ f _ aid time = let body = getActorBody aid s in if isNothing (btrajectory body) && bhp body > 0 && Just aid /= mleader -- leader fast, for UI to be fast then timeShift time (Delta timeClip) else time- g !acc !lid = EM.adjust (EM.mapWithKey f) lid acc+ g !acc !lid2 = EM.adjust (EM.mapWithKey $ f lid2) lid acc actorTimeNew = foldl' g actorTime arenas modifyServer $ \ser -> ser {sactorTime = EM.insert fid actorTimeNew $ sactorTime ser} -- | Swap the relative move times of two actors (e.g., when switching -- a UI leader).-swapTime :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> m ()+swapTime :: MonadServerAtomic m => ActorId -> ActorId -> m () swapTime source target = do sb <- getsState $ getActorBody source tb <- getsState $ getActorBody target@@ -217,12 +222,11 @@ when (tdelta /= Delta timeZero) $ modifyServer $ \ser -> ser {sactorTime = ageActor (bfid tb) (blid tb) target tdelta $ sactorTime ser} -udpateCalm :: (MonadAtomic m, MonadServer m) => ActorId -> Int64 -> m ()+udpateCalm :: MonadServerAtomic m => ActorId -> Int64 -> m () udpateCalm target deltaCalm = do tb <- getsState $ getActorBody target- actorAspect <- getsServer sactorAspect- let ar = actorAspect EM.! target- calmMax64 = xM $ aMaxCalm ar+ ar <- getsState $ getActorAspect target+ let calmMax64 = xM $ aMaxCalm ar execUpdAtomic $ UpdRefillCalm target deltaCalm when (bcalm tb < calmMax64 && bcalm tb + deltaCalm >= calmMax64) $@@ -232,7 +236,7 @@ -- to regenerate Calm. This is unnatural and boring. Better fight -- and hope he gets his Calm again to 0 and then defects back. -leadLevelSwitch :: (MonadAtomic m, MonadServer m) => m ()+leadLevelSwitch :: MonadServerAtomic m => m () leadLevelSwitch = do let canSwitch fact = fst (autoDungeonLevel fact) -- a hack to help AI, until AI client can switch levels@@ -242,7 +246,7 @@ LeaderUI _ -> False flipFaction fact | not $ canSwitch fact = return () flipFaction fact =- case _gleader fact of+ case gleader fact of Nothing -> return () Just leader -> do body <- getsState $ getActorBody leader
Game/LambdaHack/Server/ProtocolM.hs view
@@ -1,7 +1,7 @@ -- | The server definitions for the server-client communication protocol. module Game.LambdaHack.Server.ProtocolM ( -- * The communication channels- ConnServerDict+ CliSerQueue, ConnServerDict, ChanServer(..) -- * The server-client communication monad , MonadServerReadRequest ( getsDict -- exposed only to be implemented, not used@@ -9,11 +9,13 @@ , liftIO -- exposed only to be implemented, not used ) -- * Protocol- , putDict, sendUpdate, sendSfx, sendQueryAI, sendQueryUI+ , putDict, sendUpdate, sendUpdateCheck, sendUpdNoState+ , sendSfx, sendQueryAI, sendQueryUI -- * Assorted , killAllClients, childrenServer, updateConn, tryRestore #ifdef EXPOSE_INTERNAL -- * Internal operations+ , writeQueue, readQueueAI, readQueueUI, newQueue #endif ) where @@ -21,32 +23,30 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent-import Control.Concurrent.Async+import Control.Concurrent+import Control.Concurrent.Async import qualified Data.EnumMap.Strict as EM-import Data.Key (mapWithKeyM, mapWithKeyM_)-import System.FilePath-import System.IO.Unsafe (unsafePerformIO)+import Data.Key (mapWithKeyM, mapWithKeyM_)+import System.FilePath+import System.IO.Unsafe (unsafePerformIO) -import Game.LambdaHack.Atomic-import Game.LambdaHack.Client (Config, SessionUI, emptySessionUI)-import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ClientOptions-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.File+import Game.LambdaHack.Atomic+import Game.LambdaHack.Client+import Game.LambdaHack.Common.Actor+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.File import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Request-import Game.LambdaHack.Common.Response+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead import qualified Game.LambdaHack.Common.Save as Save-import Game.LambdaHack.Common.State-import Game.LambdaHack.Common.Thread-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.RuleKind-import Game.LambdaHack.Server.DebugM-import Game.LambdaHack.Server.MonadServer hiding (liftIO)-import Game.LambdaHack.Server.State+import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Thread+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.RuleKind+import Game.LambdaHack.Server.DebugM+import Game.LambdaHack.Server.MonadServer hiding (liftIO)+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State writeQueue :: MonadServerReadRequest m => Response -> CliSerQueue Response -> m ()@@ -54,40 +54,30 @@ writeQueue cmd responseS = liftIO $ putMVar responseS cmd readQueueAI :: MonadServerReadRequest m- => CliSerQueue RequestAI- -> m RequestAI+ => CliSerQueue RequestAI -> m RequestAI {-# INLINE readQueueAI #-} readQueueAI requestS = liftIO $ takeMVar requestS readQueueUI :: MonadServerReadRequest m- => CliSerQueue RequestUI- -> m RequestUI+ => CliSerQueue RequestUI -> m RequestUI {-# INLINE readQueueUI #-} readQueueUI requestS = liftIO $ takeMVar requestS newQueue :: IO (CliSerQueue a) newQueue = newEmptyMVar -tryRestore :: MonadServerReadRequest m- => Kind.COps -> DebugModeSer- -> m (Maybe (State, StateServer))-tryRestore cops@Kind.COps{corule} sdebugSer = do- let bench = sbenchmark $ sdebugCli sdebugSer- if bench then return Nothing- else do- let prefix = ssavePrefixSer sdebugSer- fileName = prefix <> Save.saveNameSer cops- res <- liftIO $ Save.restoreGame cops fileName- let stdRuleset = Kind.stdRuleset corule- cfgUIName = rcfgUIName stdRuleset- content = rcfgUIDefault stdRuleset- dataDir <- liftIO appDataDir- liftIO $ tryWriteFile (dataDir </> cfgUIName) content- return $! res+type CliSerQueue = MVar -- | Connection information for all factions, indexed by faction identifier. type ConnServerDict = EM.EnumMap FactionId ChanServer +-- | Connection channel between the server and a single client.+data ChanServer = ChanServer+ { responseS :: CliSerQueue Response+ , requestAIS :: CliSerQueue RequestAI+ , requestUIS :: Maybe (CliSerQueue RequestUI)+ }+ -- | The server monad with the ability to communicate with clients. class MonadServer m => MonadServerReadRequest m where getsDict :: (ConnServerDict -> a) -> m a@@ -100,18 +90,43 @@ putDict :: MonadServerReadRequest m => ConnServerDict -> m () putDict s = modifyDict (const s) -sendUpdate :: MonadServerReadRequest m => FactionId -> UpdAtomic -> m ()+-- | If the @AtomicFail@ conditions hold, send a command to client,+-- otherwise do nothing.+sendUpdate :: (MonadServerAtomic m, MonadServerReadRequest m)+ => FactionId -> UpdAtomic -> m () sendUpdate !fid !cmd = do+ succeeded <- execUpdAtomicFidCatch fid cmd+ when succeeded $ sendUpd fid cmd++-- | Send a command to client, crashing if the @AtomicFail@ conditions+-- don't hold when executed on the client's state.+sendUpdateCheck :: (MonadServerAtomic m, MonadServerReadRequest m)+ => FactionId -> UpdAtomic -> m ()+sendUpdateCheck !fid !cmd = do+ execUpdAtomicFid fid cmd+ sendUpd fid cmd++sendUpd :: MonadServerReadRequest m => FactionId -> UpdAtomic -> m ()+sendUpd !fid !cmd = do chan <- getsDict (EM.! fid)- let resp = RespUpdAtomic cmd- debug <- getsServer $ sniffOut . sdebugSer+ s <- getsServer $ (EM.! fid) . sclientStates+ let resp = RespUpdAtomic s cmd+ debug <- getsServer $ sniff . soptions when debug $ debugResponse fid resp writeQueue resp $ responseS chan +sendUpdNoState :: MonadServerReadRequest m => FactionId -> UpdAtomic -> m ()+sendUpdNoState !fid !cmd = do+ chan <- getsDict (EM.! fid)+ let resp = RespUpdAtomicNoState cmd+ debug <- getsServer $ sniff . soptions+ when debug $ debugResponse fid resp+ writeQueue resp $ responseS chan+ sendSfx :: MonadServerReadRequest m => FactionId -> SfxAtomic -> m () sendSfx !fid !sfx = do let resp = RespSfxAtomic sfx- debug <- getsServer $ sniffOut . sdebugSer+ debug <- getsServer $ sniff . soptions when debug $ debugResponse fid resp chan <- getsDict (EM.! fid) case chan of@@ -121,34 +136,34 @@ sendQueryAI :: MonadServerReadRequest m => FactionId -> ActorId -> m RequestAI sendQueryAI fid aid = do let respAI = RespQueryAI aid- debug <- getsServer $ sniffOut . sdebugSer+ debug <- getsServer $ sniff . soptions when debug $ debugResponse fid respAI chan <- getsDict (EM.! fid) req <- do writeQueue respAI $ responseS chan readQueueAI $ requestAIS chan- when debug $ debugRequestAI aid req+ when debug $ debugRequestAI aid return req -sendQueryUI :: (MonadAtomic m, MonadServerReadRequest m)+sendQueryUI :: (MonadServerAtomic m, MonadServerReadRequest m) => FactionId -> ActorId -> m RequestUI sendQueryUI fid _aid = do let respUI = RespQueryUI- debug <- getsServer $ sniffOut . sdebugSer+ debug <- getsServer $ sniff . soptions when debug $ debugResponse fid respUI chan <- getsDict (EM.! fid) req <- do writeQueue respUI $ responseS chan readQueueUI $ fromJust $ requestUIS chan- when debug $ debugRequestUI _aid req+ when debug $ debugRequestUI _aid return req -killAllClients :: (MonadAtomic m, MonadServerReadRequest m) => m ()+killAllClients :: (MonadServerAtomic m, MonadServerReadRequest m) => m () killAllClients = do d <- getDict- let sendKill fid _ =- -- We can't check in sfactionD, because client can be from an old game.- sendUpdate fid $ UpdKillExit fid+ let sendKill fid _ = sendUpdNoState fid $ UpdKillExit fid+ -- We can't interate over sfactionD, because client can be from an old game.+ -- For the same reason we can't look up and send client's state. mapWithKeyM_ sendKill d -- Global variable for all children threads of the server.@@ -159,15 +174,13 @@ -- | Update connections to the new definition of factions. -- Connect to clients in old or newly spawned threads -- that read and write directly to the channels.-updateConn :: (MonadAtomic m, MonadServerReadRequest m)- => Config- -> (Maybe SessionUI -> FactionId -> ChanServer -> IO ())+updateConn :: (MonadServerAtomic m, MonadServerReadRequest m)+ => (Bool -> FactionId -> ChanServer -> IO ()) -> m ()-updateConn sconfig executorClient = do+updateConn executorClient = do -- Prepare connections based on factions. oldD <- getDict- let sess = emptySessionUI sconfig- mkChanServer :: Faction -> IO ChanServer+ let mkChanServer :: Faction -> IO ChanServer mkChanServer fact = do responseS <- newQueue requestAIS <- newQueue@@ -186,9 +199,9 @@ -- Spawn client threads. let toSpawn = newD EM.\\ oldD forkUI fid connS =- forkChild childrenServer $ executorClient (Just sess) fid connS+ forkChild childrenServer $ executorClient True fid connS forkAI fid connS =- forkChild childrenServer $ executorClient Nothing fid connS+ forkChild childrenServer $ executorClient False fid connS forkClient fid conn@ChanServer{requestUIS=Nothing} = -- When a connection is reused, clients are not respawned, -- even if UI usage changes, but it works OK thanks to UI faction@@ -197,3 +210,20 @@ forkClient fid conn = forkUI fid conn liftIO $ mapWithKeyM_ forkClient toSpawn++tryRestore :: MonadServerReadRequest m => m (Maybe (State, StateServer))+tryRestore = do+ cops@Kind.COps{corule} <- getsState scops+ soptions <- getsServer soptions+ let bench = sbenchmark $ sclientOptions soptions+ if bench then return Nothing+ else do+ let prefix = ssavePrefixSer soptions+ fileName = prefix <> Save.saveNameSer cops+ res <- liftIO $ Save.restoreGame cops fileName+ let stdRuleset = Kind.stdRuleset corule+ cfgUIName = rcfgUIName stdRuleset+ content = rcfgUIDefault stdRuleset+ dataDir <- liftIO appDataDir+ liftIO $ tryWriteFile (dataDir </> cfgUIName) content+ return $! res
+ Game/LambdaHack/Server/ServerOptions.hs view
@@ -0,0 +1,126 @@+-- | Server and client game state types and operations.+module Game.LambdaHack.Server.ServerOptions+ ( ServerOptions(..), RNGs(..), defServerOptions+ ) where++import Prelude ()++import Game.LambdaHack.Common.Prelude++import Data.Binary+import qualified System.Random as R++import Game.LambdaHack.Client+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Content.ModeKind++-- | Options that affect the behaviour of the server (including game rules).+data ServerOptions = ServerOptions+ { sknowMap :: Bool+ , sknowEvents :: Bool+ , sknowItems :: Bool+ , sniff :: Bool+ , sallClear :: Bool+ , sboostRandomItem :: Bool+ , sgameMode :: Maybe (GroupName ModeKind)+ , sautomateAll :: Bool+ , skeepAutomated :: Bool+ , sdungeonRng :: Maybe R.StdGen+ , smainRng :: Maybe R.StdGen+ , snewGameSer :: Bool+ , scurChalSer :: Challenge+ , sdumpInitRngs :: Bool+ , ssavePrefixSer :: String+ , sdbgMsgSer :: Bool+ , sclientOptions :: ClientOptions+ -- The client debug inside server debug only holds the client commandline+ -- options and is never updated with config options, etc.+ }+ deriving Show++data RNGs = RNGs+ { dungeonRandomGenerator :: Maybe R.StdGen+ , startingRandomGenerator :: Maybe R.StdGen+ }++instance Show RNGs where+ show RNGs{..} =+ let args = [ maybe "" (\gen -> "--setDungeonRng \"" ++ show gen ++ "\"")+ dungeonRandomGenerator+ , maybe "" (\gen -> "--setMainRng \"" ++ show gen ++ "\"")+ startingRandomGenerator ]+ in unwords args++instance Binary ServerOptions where+ put ServerOptions{..} = do+ put sknowMap+ put sknowEvents+ put sknowItems+ put sniff+ put sallClear+ put sboostRandomItem+ put sgameMode+ put sautomateAll+ put skeepAutomated+ put scurChalSer+ put ssavePrefixSer+ put sdbgMsgSer+ put sclientOptions+ get = do+ sknowMap <- get+ sknowEvents <- get+ sknowItems <- get+ sniff <- get+ sallClear <- get+ sboostRandomItem <- get+ sgameMode <- get+ sautomateAll <- get+ skeepAutomated <- get+ scurChalSer <- get+ ssavePrefixSer <- get+ sdbgMsgSer <- get+ sclientOptions <- get+ let sdungeonRng = Nothing+ smainRng = Nothing+ snewGameSer = False+ sdumpInitRngs = False+ return $! ServerOptions{..}++instance Binary RNGs where+ put RNGs{..} = do+ put (show dungeonRandomGenerator)+ put (show startingRandomGenerator)+ get = do+ dg <- get+ sg <- get+ let dungeonRandomGenerator = read dg+ startingRandomGenerator = read sg+ return $! RNGs{..}++-- | Default value of server options.+defServerOptions :: ServerOptions+defServerOptions = ServerOptions+ { sknowMap = False+ , sknowEvents = False+ , sknowItems = False+ , sniff = False+ , sallClear = False+ , sboostRandomItem = False+ , sgameMode = Nothing+ , sautomateAll = False+ , skeepAutomated = False+ , sdungeonRng = Nothing+ , smainRng = Nothing+ , snewGameSer = False+ , scurChalSer = defaultChallenge+-- for debug; hard to set manually in browser:+#ifdef USE_BROWSER+ , sdumpInitRngs = True+#else+ , sdumpInitRngs = False+#endif+ , ssavePrefixSer = ""+ , sdbgMsgSer = False+ , sclientOptions = defClientOptions+ }
Game/LambdaHack/Server/StartM.hs view
@@ -1,81 +1,84 @@ -- | Operations for starting and restarting the game. module Game.LambdaHack.Server.StartM- ( gameReset, reinitGame, updatePer, initPer, applyDebug+ ( initPer, reinitGame, gameReset, applyDebug+#ifdef EXPOSE_INTERNAL+ -- * Internal operations+ , mapFromFuns, resetFactions, populateDungeon, findEntryPoss+#endif ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Control.Arrow (first) import qualified Control.Monad.Trans.State.Strict as St import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.IntMap.Strict as IM-import Data.Key (mapWithKeyM_)+import Data.Key (mapWithKeyM_) import qualified Data.Map.Strict as M-import Data.Ord+import Data.Ord import qualified Data.Text as T-import Data.Tuple (swap)+import Data.Tuple (swap) import qualified NLP.Miniutter.English as MU import qualified System.Random as R -import Game.LambdaHack.Atomic-import Game.LambdaHack.Common.ActorState-import Game.LambdaHack.Common.ClientOptions+import Game.LambdaHack.Atomic+import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Color as Color-import Game.LambdaHack.Common.Faction-import Game.LambdaHack.Common.Flavour-import qualified Game.LambdaHack.Common.HighScore as HighScore-import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind-import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.MonadStateRead-import Game.LambdaHack.Common.Perception-import Game.LambdaHack.Common.Point-import Game.LambdaHack.Common.Random-import Game.LambdaHack.Common.State+import Game.LambdaHack.Common.Level+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.MonadStateRead+import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Random+import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Common.Time+import Game.LambdaHack.Common.Time import qualified Game.LambdaHack.Content.ItemKind as IK-import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Server.CommonM+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Server.CommonM import qualified Game.LambdaHack.Server.DungeonGen as DungeonGen-import Game.LambdaHack.Server.Fov-import Game.LambdaHack.Server.ItemM-import Game.LambdaHack.Server.ItemRev-import Game.LambdaHack.Server.MonadServer-import Game.LambdaHack.Server.State+import Game.LambdaHack.Server.Fov+import Game.LambdaHack.Server.ItemM+import Game.LambdaHack.Server.ItemRev+import Game.LambdaHack.Server.MonadServer+import Game.LambdaHack.Server.ServerOptions+import Game.LambdaHack.Server.State initPer :: MonadServer m => m () initPer = do- discoAspect <- getsServer sdiscoAspect- ( sactorAspect, sfovLitLid, sfovClearLid, sfovLucidLid- ,sperValidFid, sperCacheFid, sperFid )- <- getsState $ perFidInDungeon discoAspect+ ( sfovLitLid, sfovClearLid, sfovLucidLid+ ,sperValidFid, sperCacheFid, sperFid ) <- getsState perFidInDungeon modifyServer $ \ser ->- ser { sactorAspect, sfovLitLid, sfovClearLid, sfovLucidLid+ ser { sfovLitLid, sfovClearLid, sfovLucidLid , sperValidFid, sperCacheFid, sperFid } -reinitGame :: (MonadAtomic m, MonadServer m) => m ()+reinitGame :: MonadServerAtomic m => m () reinitGame = do Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops pers <- getsServer sperFid- DebugModeSer{scurChalSer, sknowMap, sdebugCli} <- getsServer sdebugSer+ ServerOptions{scurChalSer, sknowMap, sclientOptions} <- getsServer soptions -- This state is quite small, fit for transmition to the client. -- The biggest part is content, which needs to be updated -- at this point to keep clients in sync with server improvements. s <- getState- let defLocal | sknowMap = s- | otherwise = localFromGlobal s- discoS <- getsServer sdiscoKind- let sdiscoKind =+ discoS <- getsState sdiscoKind+ -- Thanks to the following, for any item with feature @Identified@,+ -- the client has its kind from the start.+ let discoKindFiltered = let f KindMean{kmKind} = IK.Identified `elem` IK.ifeature (okind kmKind) in EM.filter f discoS- updRestart fid = UpdRestart fid sdiscoKind (pers EM.! fid) defLocal- scurChalSer sdebugCli+ defL | sknowMap = s+ | otherwise = localFromGlobal s+ defLocal = updateDiscoKind (const discoKindFiltered) defL factionD <- getsState sfactionD+ modifyServer $ \ser -> ser {sclientStates = EM.map (const defLocal) factionD}+ let updRestart fid = UpdRestart fid (pers EM.! fid) defLocal+ scurChalSer sclientOptions mapWithKeyM_ (\fid _ -> execUpdAtomic $ updRestart fid) factionD dungeon <- getsState sdungeon let sactorTime = EM.map (const (EM.map (const EM.empty) dungeon)) factionD@@ -83,23 +86,7 @@ populateDungeon mapM_ (\fid -> mapM_ (updatePer fid) (EM.keys dungeon)) (EM.keys factionD)- execUpdAtomic $ UpdMsgAll "SortSlots" -- hack--updatePer :: (MonadAtomic m, MonadServer m) => FactionId -> LevelId -> m ()-{-# INLINE updatePer #-}-updatePer fid lid = do- modifyServer $ \ser ->- ser {sperValidFid = EM.adjust (EM.insert lid True) fid $ sperValidFid ser}- sperFidOld <- getsServer sperFid- let perOld = sperFidOld EM.! fid EM.! lid- knowEvents <- getsServer $ sknowEvents . sdebugSer- -- Performed in the State after action, e.g., with a new actor.- perNew <- recomputeCachePer fid lid- let inPer = diffPer perNew perOld- outPer = diffPer perOld perNew- unless (nullPer outPer && nullPer inPer) $- unless knowEvents $ -- inconsistencies would quickly manifest- execSendPer fid lid outPer inPer perNew+ execSfxAtomic SfxSortSlots mapFromFuns :: (Bounded a, Enum a, Ord b) => [a -> b] -> M.Map b a mapFromFuns =@@ -173,30 +160,27 @@ return $! warFs gameReset :: MonadServer m- => Kind.COps -> DebugModeSer -> Maybe (GroupName ModeKind)+ => ServerOptions -> Maybe (GroupName ModeKind) -> Maybe R.StdGen -> m State-gameReset cops@Kind.COps{comode=Kind.Ops{opick, okind}}- sdebug mGameMode mrandom = do+gameReset serverOptions mGameMode mrandom = do -- Dungeon seed generation has to come first, to ensure item boosting -- is determined by the dungeon RNG.- dungeonSeed <- getSetGen $ sdungeonRng sdebug `mplus` mrandom- srandom <- getSetGen $ smainRng sdebug `mplus` mrandom+ cops@Kind.COps{comode=Kind.Ops{opick, okind}} <- getsState scops+ dungeonSeed <- getSetGen $ sdungeonRng serverOptions `mplus` mrandom+ srandom <- getSetGen $ smainRng serverOptions `mplus` mrandom let srngs = RNGs (Just dungeonSeed) (Just srandom)- when (sdumpInitRngs sdebug) $ dumpRngs srngs- scoreTable <- if sfrontendNull $ sdebugCli sdebug then- return HighScore.empty- else- restoreScore cops+ when (sdumpInitRngs serverOptions) $ dumpRngs srngs+ scoreTable <- restoreScore cops factionDold <- getsState sfactionD gameModeIdOld <- getsState sgameModeId- curChalSer <- getsServer $ scurChalSer . sdebugSer+ curChalSer <- getsServer $ scurChalSer . soptions #ifdef USE_BROWSER let startingModeGroup = "starting JS" #else let startingModeGroup = "starting" #endif gameMode = fromMaybe startingModeGroup- $ mGameMode `mplus` sgameMode sdebug+ $ mGameMode `mplus` sgameMode serverOptions rnd :: Rnd (FactionDict, FlavourMap, DiscoveryKind, DiscoveryKindRev, DungeonGen.FreshDungeon, Kind.Id ModeKind) rnd = do@@ -205,38 +189,38 @@ let mode = okind modeKindId automatePS ps = ps {rosterList = map (first $ automatePlayer True) $ rosterList ps}- players = if sautomateAll sdebug+ players = if sautomateAll serverOptions then automatePS $ mroster mode else mroster mode sflavour <- dungeonFlavourMap cops- (sdiscoKind, sdiscoKindRev) <- serverDiscos cops+ (discoKind, sdiscoKindRev) <- serverDiscos cops freshDng <- DungeonGen.dungeonGen cops $ mcaves mode factionD <- resetFactions factionDold gameModeIdOld (cdiff curChalSer) (DungeonGen.freshTotalDepth freshDng) players- return ( factionD, sflavour, sdiscoKind+ return ( factionD, sflavour, discoKind , sdiscoKindRev, freshDng, modeKindId )- let ( factionD, sflavour, sdiscoKind+ let ( factionD, sflavour, discoKind ,sdiscoKindRev, DungeonGen.FreshDungeon{..}, modeKindId ) = St.evalState rnd dungeonSeed defState = defStateGlobal freshDungeon freshTotalDepth- factionD cops scoreTable modeKindId+ factionD cops scoreTable modeKindId discoKind defSer = emptyStateServer { srandom , srngs } putServer defSer- modifyServer $ \ser -> ser {sdiscoKind, sdiscoKindRev, sflavour}+ modifyServer $ \ser -> ser {sdiscoKindRev, sflavour} return $! defState -- Spawn initial actors. Clients should notice this, to set their leaders.-populateDungeon :: (MonadAtomic m, MonadServer m) => m ()+populateDungeon :: MonadServerAtomic m => m () populateDungeon = do cops@Kind.COps{coTileSpeedup} <- getsState scops placeItemsInDungeon embedItemsInDungeon dungeon <- getsState sdungeon factionD <- getsState sfactionD- curChalSer <- getsServer $ scurChalSer . sdebugSer+ curChalSer <- getsServer $ scurChalSer . soptions let ginitialWolf fact1 = if cwolf curChalSer && fhasUI (gplayer fact1) then case ginitial fact1 of [] -> []@@ -293,7 +277,7 @@ Nothing -> error $ "can't spawn initial actors" `showFailure` (lid, (fid3, fact3)) Just aid -> do- mleader <- getsState $ _gleader . (EM.! fid3) . sfactionD+ mleader <- getsState $ gleader . (EM.! fid3) . sfactionD when (isNothing mleader) $ supplantLeader fid3 aid return True mapM_ initialActors arenas@@ -335,15 +319,14 @@ found <- tryFind (middlePos : onStairs) nk return $! found ++ onStairs --- | Apply debug options that don't need a new game.+-- | Apply options that don't need a new game. applyDebug :: MonadServer m => m () applyDebug = do- DebugModeSer{..} <- getsServer sdebugNxt+ ServerOptions{..} <- getsServer soptionsNxt modifyServer $ \ser ->- ser {sdebugSer = (sdebugSer ser) { sniffIn- , sniffOut- , sallClear- , sdbgMsgSer- , snewGameSer- , sdumpInitRngs- , sdebugCli }}+ ser {soptions = (soptions ser) { sniff+ , sallClear+ , sdbgMsgSer+ , snewGameSer+ , sdumpInitRngs+ , sclientOptions }}
Game/LambdaHack/Server/State.hs view
@@ -1,16 +1,14 @@ -- | Server and client game state types and operations. module Game.LambdaHack.Server.State- ( StateServer(..), emptyStateServer- , DebugModeSer(..), defDebugModeSer- , RNGs(..)- , ActorTime, updateActorTime, ageActor+ ( StateServer(..), ActorTime+ , emptyStateServer, updateActorTime, ageActor ) where import Prelude () import Game.LambdaHack.Common.Prelude -import Data.Binary+import Data.Binary import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import qualified Data.HashMap.Strict as HM@@ -18,109 +16,60 @@ import Game.LambdaHack.Atomic import Game.LambdaHack.Common.Actor-import Game.LambdaHack.Common.ClientOptions import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.Level-import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Perception+import Game.LambdaHack.Common.State import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ModeKind import Game.LambdaHack.Server.Fov import Game.LambdaHack.Server.ItemRev+import Game.LambdaHack.Server.ServerOptions --- | Global, server state.+-- | State with server-specific data, including a copy of each client's+-- basic game state, but not the server's basic state. data StateServer = StateServer- { sactorTime :: ActorTime -- ^ absolute times of next actions- , sdiscoKind :: DiscoveryKind -- ^ full item kind discoveries data- , sdiscoKindRev :: DiscoveryKindRev -- ^ reverse map, used for item creation- , suniqueSet :: UniqueSet -- ^ already generated unique items- , sdiscoAspect :: DiscoveryAspect -- ^ full item aspect data- , sitemSeedD :: ItemSeedDict -- ^ map from item ids to item seeds- , sitemRev :: ItemRev -- ^ reverse id map, used for item creation- , sflavour :: FlavourMap -- ^ association of flavour to items- , sacounter :: ActorId -- ^ stores next actor index- , sicounter :: ItemId -- ^ stores next item index+ { sactorTime :: ActorTime -- ^ absolute times of next actions+ , sdiscoKindRev :: DiscoveryKindRev+ -- ^ reverse map, used for item creation+ , suniqueSet :: UniqueSet -- ^ already generated unique items+ , sitemSeedD :: ItemSeedDict -- ^ map from item ids to item seeds+ , sitemRev :: ItemRev -- ^ reverse id map, used for item creation+ , sflavour :: FlavourMap -- ^ association of flavour to items+ , sacounter :: ActorId -- ^ stores next actor index+ , sicounter :: ItemId -- ^ stores next item index , snumSpawned :: EM.EnumMap LevelId Int- , sundo :: [CmdAtomic] -- ^ atomic commands performed to date- , sperFid :: PerFid -- ^ perception of all factions- , sperValidFid :: PerValidFid -- ^ perception validity for all factions- , sperCacheFid :: PerCacheFid -- ^ perception cache of all factions- , sactorAspect :: ActorAspect -- ^ full actor aspect data- , sfovLucidLid :: FovLucidLid -- ^ ambient or shining light positions- , sfovClearLid :: FovClearLid -- ^ clear tiles positions- , sfovLitLid :: FovLitLid -- ^ ambient light positions- , sarenas :: [LevelId] -- ^ active arenas- , svalidArenas :: Bool -- ^ whether active arenas valid- , srandom :: R.StdGen -- ^ current random generator- , srngs :: RNGs -- ^ initial random generators- , squit :: Bool -- ^ exit the game loop- , swriteSave :: Bool -- ^ write savegame to a file now- , sdebugSer :: DebugModeSer -- ^ current debugging mode- , sdebugNxt :: DebugModeSer -- ^ debugging mode for the next game+ , sundo :: [CmdAtomic] -- ^ atomic commands performed to date+ , sclientStates :: EM.EnumMap FactionId State+ -- ^ each faction state, as seen by clients+ , sperFid :: PerFid -- ^ perception of all factions+ , sperValidFid :: PerValidFid -- ^ perception validity for all factions+ , sperCacheFid :: PerCacheFid -- ^ perception cache of all factions+ , sfovLucidLid :: FovLucidLid -- ^ ambient or shining light positions+ , sfovClearLid :: FovClearLid -- ^ clear tiles positions+ , sfovLitLid :: FovLitLid -- ^ ambient light positions+ , sarenas :: [LevelId] -- ^ active arenas+ , svalidArenas :: Bool -- ^ whether active arenas valid+ , srandom :: R.StdGen -- ^ current random generator+ , srngs :: RNGs -- ^ initial random generators+ , squit :: Bool -- ^ exit the game loop+ , swriteSave :: Bool -- ^ write savegame to a file now+ , soptions :: ServerOptions -- ^ current commandline options+ , soptionsNxt :: ServerOptions -- ^ options for the next game } deriving (Show) --- | Debug commands. See 'Server.debugArgs' for the descriptions.-data DebugModeSer = DebugModeSer- { sknowMap :: Bool- , sknowEvents :: Bool- , sknowItems :: Bool- , sniffIn :: Bool- , sniffOut :: Bool- , sallClear :: Bool- , sboostRandomItem :: Bool- , sgameMode :: Maybe (GroupName ModeKind)- , sautomateAll :: Bool- , skeepAutomated :: Bool- , sdungeonRng :: Maybe R.StdGen- , smainRng :: Maybe R.StdGen- , snewGameSer :: Bool- , scurChalSer :: Challenge- , sdumpInitRngs :: Bool- , ssavePrefixSer :: String- , sdbgMsgSer :: Bool- , sdebugCli :: DebugModeCli- -- The client debug inside server debug only holds the client commandline- -- options and is never updated with config options, etc.- }- deriving Show--data RNGs = RNGs- { dungeonRandomGenerator :: Maybe R.StdGen- , startingRandomGenerator :: Maybe R.StdGen- }--instance Show RNGs where- show RNGs{..} =- let args = [ maybe "" (\gen -> "--setDungeonRng \"" ++ show gen ++ "\"")- dungeonRandomGenerator- , maybe "" (\gen -> "--setMainRng \"" ++ show gen ++ "\"")- startingRandomGenerator ]- in unwords args-+-- | Position in time for each actor, grouped by level and by faction. type ActorTime = EM.EnumMap FactionId (EM.EnumMap LevelId (EM.EnumMap ActorId Time)) -updateActorTime :: FactionId -> LevelId -> ActorId -> Time -> ActorTime- -> ActorTime-updateActorTime !fid !lid !aid !time =- EM.adjust (EM.adjust (EM.insert aid time) lid) fid--ageActor :: FactionId -> LevelId -> ActorId -> Delta Time -> ActorTime- -> ActorTime-ageActor !fid !lid !aid !delta =- EM.adjust (EM.adjust (EM.adjust (`timeShift` delta) aid) lid) fid- -- | Initial, empty game server state. emptyStateServer :: StateServer emptyStateServer = StateServer { sactorTime = EM.empty- , sdiscoKind = EM.empty , sdiscoKindRev = EM.empty , suniqueSet = ES.empty- , sdiscoAspect = EM.empty , sitemSeedD = EM.empty , sitemRev = HM.empty , sflavour = emptyFlavourMap@@ -128,10 +77,10 @@ , sicounter = toEnum 0 , snumSpawned = EM.empty , sundo = []+ , sclientStates = EM.empty , sperFid = EM.empty , sperValidFid = EM.empty , sperCacheFid = EM.empty- , sactorAspect = EM.empty , sfovLucidLid = EM.empty , sfovClearLid = EM.empty , sfovLitLid = EM.empty@@ -142,74 +91,54 @@ , startingRandomGenerator = Nothing } , squit = False , swriteSave = False- , sdebugSer = defDebugModeSer- , sdebugNxt = defDebugModeSer+ , soptions = defServerOptions+ , soptionsNxt = defServerOptions } -defDebugModeSer :: DebugModeSer-defDebugModeSer = DebugModeSer { sknowMap = False- , sknowEvents = False- , sknowItems = False- , sniffIn = False- , sniffOut = False- , sallClear = False- , sboostRandomItem = False- , sgameMode = Nothing- , sautomateAll = False- , skeepAutomated = False- , sdungeonRng = Nothing- , smainRng = Nothing- , snewGameSer = False- , scurChalSer = defaultChallenge--- for debug; hard to set manually in browser:-#ifdef USE_BROWSER- , sdumpInitRngs = True-#else- , sdumpInitRngs = False-#endif- , ssavePrefixSer = ""- , sdbgMsgSer = False- , sdebugCli = defDebugModeCli- }+updateActorTime :: FactionId -> LevelId -> ActorId -> Time -> ActorTime+ -> ActorTime+updateActorTime !fid !lid !aid !time =+ EM.adjust (EM.adjust (EM.insert aid time) lid) fid +ageActor :: FactionId -> LevelId -> ActorId -> Delta Time -> ActorTime+ -> ActorTime+ageActor !fid !lid !aid !delta =+ EM.adjust (EM.adjust (EM.adjust (`timeShift` delta) aid) lid) fid+ instance Binary StateServer where put StateServer{..} = do put sactorTime- put sdiscoKind put sdiscoKindRev put suniqueSet- put sdiscoAspect put sitemSeedD put sitemRev put sflavour put sacounter put sicounter put snumSpawned- put sundo+ put sclientStates put (show srandom) put srngs- put sdebugSer+ put soptions get = do sactorTime <- get- sdiscoKind <- get sdiscoKindRev <- get suniqueSet <- get- sdiscoAspect <- get sitemSeedD <- get sitemRev <- get sflavour <- get sacounter <- get sicounter <- get snumSpawned <- get- sundo <- get+ sclientStates <- get g <- get srngs <- get- sdebugSer <- get+ soptions <- get let srandom = read g+ sundo = [] sperFid = EM.empty sperValidFid = EM.empty sperCacheFid = EM.empty- sactorAspect = EM.empty sfovLucidLid = EM.empty sfovClearLid = EM.empty sfovLitLid = EM.empty@@ -217,53 +146,5 @@ svalidArenas = False squit = False swriteSave = False- sdebugNxt = defDebugModeSer+ soptionsNxt = defServerOptions return $! StateServer{..}--instance Binary DebugModeSer where- put DebugModeSer{..} = do- put sknowMap- put sknowEvents- put sknowItems- put sniffIn- put sniffOut- put sallClear- put sboostRandomItem- put sgameMode- put sautomateAll- put skeepAutomated- put scurChalSer- put ssavePrefixSer- put sdbgMsgSer- put sdebugCli- get = do- sknowMap <- get- sknowEvents <- get- sknowItems <- get- sniffIn <- get- sniffOut <- get- sallClear <- get- sboostRandomItem <- get- sgameMode <- get- sautomateAll <- get- skeepAutomated <- get- scurChalSer <- get- ssavePrefixSer <- get- sdbgMsgSer <- get- sdebugCli <- get- let sdungeonRng = Nothing- smainRng = Nothing- snewGameSer = False- sdumpInitRngs = False- return $! DebugModeSer{..}--instance Binary RNGs where- put RNGs{..} = do- put (show dungeonRandomGenerator)- put (show startingRandomGenerator)- get = do- dg <- get- sg <- get- let dungeonRandomGenerator = read dg- startingRandomGenerator = read sg- return $! RNGs{..}
GameDefinition/Client/UI/Content/KeyKind.hs view
@@ -8,9 +8,9 @@ import Game.LambdaHack.Common.Prelude -import Game.LambdaHack.Client.UI.Content.KeyKind-import Game.LambdaHack.Client.UI.HumanCmd-import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Client.UI.Content.KeyKind+import Game.LambdaHack.Client.UI.HumanCmd+import Game.LambdaHack.Common.Misc import qualified Game.LambdaHack.Content.TileKind as TK -- | Description of default key-command bindings.@@ -18,230 +18,228 @@ -- In addition to these commands, mouse and keys have a standard meaning -- when navigating various menus. standardKeys :: KeyKind-standardKeys = KeyKind- { rhumanCommands = map evalKeyDef $- -- All commands are defined here, except some movement and leader picking- -- commands. All commands are shown on help screens except debug commands- -- and macros with empty descriptions.- -- The order below determines the order on the help screens.- -- Remember to put commands that show information (e.g., enter aiming- -- mode) first.+standardKeys = KeyKind $ map evalKeyDef $+ -- All commands are defined here, except some movement and leader picking+ -- commands. All commands are shown on help screens except debug commands+ -- and macros with empty descriptions.+ -- The order below determines the order on the help screens.+ -- Remember to put commands that show information (e.g., enter aiming+ -- mode) first. - -- Main Menu- [ ("c", ([CmdMainMenu], "enter challenges menu>", ChallengesMenu))- , ("n", ([CmdMainMenu], "start new game", GameRestart))- , ("x", ([CmdMainMenu], "save and exit", GameExit))- , ("m", ([CmdMainMenu], "enter settings menu>", SettingsMenu))- , ("a", ([CmdMainMenu], "automate faction", Automate))- , ("?", ([CmdMainMenu], "see command Help", Help))- , ("Escape", ([CmdMainMenu], "back to playing", Cancel))+ -- Main Menu+ [ ("e", ([CmdMainMenu], "enter challenges menu>", ChallengesMenu))+ , ("s", ([CmdMainMenu], "start new game", GameRestart))+ , ("x", ([CmdMainMenu], "exit to desktop", GameExit))+ , ("v", ([CmdMainMenu], "visit settings menu>", SettingsMenu))+ , ("a", ([CmdMainMenu], "automate faction", Automate))+ , ("?", ([CmdMainMenu], "see command Help", Help))+ , ("Escape", ([CmdMainMenu], "back to playing", Cancel)) - -- Item use, 1st part- , ("g", addCmdCategory CmdMinimal $ grabItems "grab item(s)")- , ("comma", grabItems "")- , ("d", dropItems "drop item(s)")- , ("period", dropItems "")- , ("f", addCmdCategory CmdItemMenu $ projectA flingTs)- , ("C-f", addCmdCategory CmdItemMenu- $ replaceDesc "fling without aiming" $ projectI flingTs)- , ("a", addCmdCategory CmdItemMenu $ applyI [ApplyItem- { verb = "apply"- , object = "consumable"- , symbol = ' ' }])- , ("C-a", addCmdCategory CmdItemMenu- $ replaceDesc "apply and keep choice" $ applyIK [ApplyItem- { verb = "apply"- , object = "consumable"- , symbol = ' ' }])+ -- Item use, 1st part+ , ("g", addCmdCategory CmdMinimal $ grabItems "grab item(s)")+ , ("comma", grabItems "")+ , ("d", dropItems "drop item(s)")+ , ("period", dropItems "")+ , ("f", addCmdCategory CmdItemMenu $ projectA flingTs)+ , ("C-f", addCmdCategory CmdItemMenu+ $ replaceDesc "fling without aiming" $ projectI flingTs)+ , ("a", addCmdCategory CmdItemMenu $ applyI [ApplyItem+ { verb = "apply"+ , object = "consumable"+ , symbol = ' ' }])+ , ("C-a", addCmdCategory CmdItemMenu+ $ replaceDesc "apply and keep choice" $ applyIK [ApplyItem+ { verb = "apply"+ , object = "consumable"+ , symbol = ' ' }]) - -- Terrain exploration and alteration- , ("semicolon", ( [CmdMove]- , "go to x-hair for 25 steps"- , Macro ["C-semicolon", "C-/", "C-V"] ))- , ("colon", ( [CmdMove]- , "run to x-hair collectively for 25 steps"- , Macro ["C-colon", "C-/", "C-V"] ))- , ("x", ( [CmdMove]- , "explore nearest unknown spot"- , autoexploreCmd ))- , ("X", ( [CmdMove]- , "autoexplore 25 times"- , autoexplore25Cmd ))- , ("R", ([CmdMove], "rest (wait 25 times)", Macro ["KP_5", "C-V"]))- , ("C-R", ( [CmdMove], "lurk (wait 0.1 turns 100 times)"- , Macro ["C-KP_5", "V"] ))- , ("c", ( [CmdMove, CmdMinimal]- , descTs closeDoorTriggers- , AlterDir closeDoorTriggers ))+ -- Terrain exploration and alteration+ , ("semicolon", ( [CmdMove]+ , "go to x-hair for 25 steps"+ , Macro ["C-semicolon", "C-/", "C-V"] ))+ , ("colon", ( [CmdMove]+ , "run to x-hair collectively for 25 steps"+ , Macro ["C-colon", "C-/", "C-V"] ))+ , ("x", ( [CmdMove]+ , "explore nearest unknown spot"+ , autoexploreCmd ))+ , ("X", ( [CmdMove]+ , "autoexplore 25 times"+ , autoexplore25Cmd ))+ , ("R", ([CmdMove], "rest (wait 25 times)", Macro ["KP_5", "C-V"]))+ , ("C-R", ( [CmdMove], "lurk (wait 0.1 turns 100 times)"+ , Macro ["C-KP_5", "V"] ))+ , ("c", ( [CmdMove, CmdMinimal]+ , descTs closeDoorTriggers+ , AlterDir closeDoorTriggers )) - -- Item use, continued- , ("^", ( [CmdItem], "sort items by kind and stats", SortSlots))- , ("p", moveItemTriple [CGround, CEqp, CSha] CInv- "item" False)- , ("e", moveItemTriple [CGround, CInv, CSha] CEqp- "item" False)- , ("s", moveItemTriple [CGround, CInv, CEqp] CSha- "and share item" False)- , ("P", ( [CmdMinimal, CmdItem]- , "manage item pack of the leader"- , ChooseItemMenu (MStore CInv) ))- , ("G", ( [CmdItem]- , "manage items on the ground"- , ChooseItemMenu (MStore CGround) ))- , ("E", ( [CmdItem]- , "manage equipment of the leader"- , ChooseItemMenu (MStore CEqp) ))- , ("S", ( [CmdItem]- , "manage the shared party stash"- , ChooseItemMenu (MStore CSha) ))- , ("A", ( [CmdItem]- , "manage all owned items"- , ChooseItemMenu MOwned ))- , ("@", ( [CmdItem]- , "describe organs of the leader"- , ChooseItemMenu (MStore COrgan) ))- , ("#", ( [CmdItem]- , "show stat summary of the leader"- , ChooseItemMenu MStats ))- , ("~", ( [CmdItem]- , "display known lore"- , ChooseItemMenu MLoreItem ))- , ("q", addCmdCategory CmdItem $ applyI [ApplyItem- { verb = "quaff"- , object = "potion"- , symbol = '!' }])- , ("r", addCmdCategory CmdItem $ applyI [ApplyItem- { verb = "read"- , object = "scroll"- , symbol = '?' }])+ -- Item use, continued+ , ("^", ( [CmdItem], "sort items by kind and stats", SortSlots))+ , ("p", moveItemTriple [CGround, CEqp, CSha] CInv+ "item" False)+ , ("e", moveItemTriple [CGround, CInv, CSha] CEqp+ "item" False)+ , ("s", moveItemTriple [CGround, CInv, CEqp] CSha+ "and share item" False)+ , ("P", ( [CmdMinimal, CmdItem]+ , "manage item pack of the leader"+ , ChooseItemMenu (MStore CInv) ))+ , ("G", ( [CmdItem]+ , "manage items on the ground"+ , ChooseItemMenu (MStore CGround) ))+ , ("E", ( [CmdItem]+ , "manage equipment of the leader"+ , ChooseItemMenu (MStore CEqp) ))+ , ("S", ( [CmdItem]+ , "manage the shared party stash"+ , ChooseItemMenu (MStore CSha) ))+ , ("A", ( [CmdItem]+ , "manage all owned items"+ , ChooseItemMenu MOwned ))+ , ("@", ( [CmdItem]+ , "describe organs of the leader"+ , ChooseItemMenu (MStore COrgan) ))+ , ("#", ( [CmdItem]+ , "show stat summary of the leader"+ , ChooseItemMenu MStats ))+ , ("~", ( [CmdItem]+ , "display known lore"+ , ChooseItemMenu MLoreItem ))+ , ("q", addCmdCategory CmdItem $ applyI [ApplyItem+ { verb = "quaff"+ , object = "potion"+ , symbol = '!' }])+ , ("r", addCmdCategory CmdItem $ applyI [ApplyItem+ { verb = "read"+ , object = "scroll"+ , symbol = '?' }]) - , ("t", addCmdCategory CmdItem $ projectA- [ ApplyItem { verb = "throw"- , object = "missile"- , symbol = '|' } ])--- , ("z", projectA [ApplyItem { verb = "zap"--- , object = "wand"--- , symbol = '/' }])+ , ("t", addCmdCategory CmdItem $ projectA+ [ ApplyItem { verb = "throw"+ , object = "missile"+ , symbol = '|' } ])+-- , ("z", projectA [ApplyItem { verb = "zap"+-- , object = "wand"+-- , symbol = '/' }]) - -- Aiming- , ("KP_Multiply", ( [CmdAim, CmdMinimal]- , "cycle x-hair among enemies", AimEnemy ))- -- not really minimal, because flinging from Item Menu enters aiming- -- mode, first screen mentions aiming mode not in fling context- , ("!", ([CmdAim], "", AimEnemy))- , ("KP_Divide", ([CmdAim], "cycle x-hair among items", AimItem))- , ("/", ([CmdAim], "", AimItem))- , ("\\", ([CmdAim], "cycle aiming modes", AimFloor))- , ("+", ([CmdAim, CmdMinimal], "swerve the aiming line", EpsIncr True))- , ("-", ([CmdAim], "unswerve the aiming line", EpsIncr False))- , ("C-?", ( [CmdAim]- , "set x-hair to nearest unknown spot"- , XhairUnknown ))- , ("C-I", ( [CmdAim]- , "set x-hair to nearest item"- , XhairItem ))- , ("C-{", ( [CmdAim]- , "set x-hair to nearest upstairs"- , XhairStair True ))- , ("C-}", ( [CmdAim]- , "set x-hair to nearest downstairs"- , XhairStair False ))- , ("<", ([CmdAim], "move aiming one level higher" , AimAscend 1))- , ("C-<", ( [CmdNoHelp], "move aiming 10 levels higher"- , AimAscend 10) )- , (">", ([CmdAim], "move aiming one level lower", AimAscend (-1)))- , ("C->", ( [CmdNoHelp], "move aiming 10 levels lower"- , AimAscend (-10)) )- , ("BackSpace" , ( [CmdAim]- , "clear chosen item and target"- , ComposeUnlessError ItemClear TgtClear ))- , ("Escape", ( [CmdAim, CmdMinimal]- , "cancel aiming/open Main Menu"- , ByAimMode {exploration = MainMenu, aiming = Cancel} ))- , ("Return", ( [CmdAim, CmdMinimal]- , "accept target/open Help"- , ByAimMode {exploration = Help, aiming = Accept} ))+ -- Aiming+ , ("KP_Multiply", ( [CmdAim, CmdMinimal]+ , "cycle x-hair among enemies", AimEnemy ))+ -- not really minimal, because flinging from Item Menu enters aiming+ -- mode, first screen mentions aiming mode not in fling context+ , ("!", ([CmdAim], "", AimEnemy))+ , ("KP_Divide", ([CmdAim], "cycle x-hair among items", AimItem))+ , ("/", ([CmdAim], "", AimItem))+ , ("\\", ([CmdAim], "cycle aiming modes", AimFloor))+ , ("+", ([CmdAim, CmdMinimal], "swerve the aiming line", EpsIncr True))+ , ("-", ([CmdAim], "unswerve the aiming line", EpsIncr False))+ , ("C-?", ( [CmdAim]+ , "set x-hair to nearest unknown spot"+ , XhairUnknown ))+ , ("C-I", ( [CmdAim]+ , "set x-hair to nearest item"+ , XhairItem ))+ , ("C-{", ( [CmdAim]+ , "set x-hair to nearest upstairs"+ , XhairStair True ))+ , ("C-}", ( [CmdAim]+ , "set x-hair to nearest downstairs"+ , XhairStair False ))+ , ("<", ([CmdAim], "move aiming one level higher" , AimAscend 1))+ , ("C-<", ( [CmdNoHelp], "move aiming 10 levels higher"+ , AimAscend 10) )+ , (">", ([CmdAim], "move aiming one level lower", AimAscend (-1)))+ , ("C->", ( [CmdNoHelp], "move aiming 10 levels lower"+ , AimAscend (-10)) )+ , ("BackSpace" , ( [CmdAim]+ , "clear chosen item and target"+ , ComposeUnlessError ItemClear TgtClear ))+ , ("Escape", ( [CmdAim, CmdMinimal]+ , "cancel aiming/open Main Menu"+ , ByAimMode {exploration = MainMenu, aiming = Cancel} ))+ , ("Return", ( [CmdAim, CmdMinimal]+ , "accept target/open Help"+ , ByAimMode {exploration = Help, aiming = Accept} )) - -- Assorted- , ("space", ( [CmdMinimal, CmdMeta]- , "clear messages/display history", Clear ))- , ("?", ([CmdMeta], "display Help", Help))- , ("F1", ([CmdMeta], "", Help))- , ("Tab", ( [CmdMeta]- , "cycle among party members on the level"- , MemberCycle ))- , ("BackTab", ( [CmdMeta, CmdMinimal]- , "cycle among all party members"- , MemberBack ))- , ("=", ( [CmdMinimal, CmdMeta]- , "select (or deselect) party member", SelectActor) )- , ("_", ([CmdMeta], "deselect (or select) all on the level", SelectNone))- , ("v", ([CmdMeta], "voice again the recorded commands", Repeat 1))- , ("V", repeatTriple 100)- , ("C-v", repeatTriple 1000)- , ("C-V", repeatTriple 25)- , ("'", ([CmdMeta], "start recording commands", Record))+ -- Assorted+ , ("space", ( [CmdMinimal, CmdMeta]+ , "clear messages/display history", Clear ))+ , ("?", ([CmdMeta], "display Help", Help))+ , ("F1", ([CmdMeta], "", Help))+ , ("Tab", ( [CmdMeta]+ , "cycle among party members on the level"+ , MemberCycle ))+ , ("BackTab", ( [CmdMeta, CmdMinimal]+ , "cycle among all party members"+ , MemberBack ))+ , ("=", ( [CmdMinimal, CmdMeta]+ , "select (or deselect) party member", SelectActor) )+ , ("_", ([CmdMeta], "deselect (or select) all on the level", SelectNone))+ , ("v", ([CmdMeta], "voice again the recorded commands", Repeat 1))+ , ("V", repeatTriple 100)+ , ("C-v", repeatTriple 1000)+ , ("C-V", repeatTriple 25)+ , ("'", ([CmdMeta], "start recording commands", Record)) - -- Mouse- , ("LeftButtonRelease", mouseLMB)- , ("RightButtonRelease", mouseRMB)- , ("C-LeftButtonRelease", replaceDesc "" mouseRMB) -- Mac convention- , ( "C-RightButtonRelease"- , ( [CmdMouse]- , "open or close door"- , AlterWithPointer $ closeDoorTriggers ++ openDoorTriggers ) )- , ("MiddleButtonRelease", mouseMMB)- , ("WheelNorth", ([CmdMouse], "swerve the aiming line", Macro ["+"]))- , ("WheelSouth", ([CmdMouse], "unswerve the aiming line", Macro ["-"]))+ -- Mouse+ , ("LeftButtonRelease", mouseLMB)+ , ("RightButtonRelease", mouseRMB)+ , ("C-LeftButtonRelease", replaceDesc "" mouseRMB) -- Mac convention+ , ( "C-RightButtonRelease"+ , ( [CmdMouse]+ , "open or close door"+ , AlterWithPointer $ closeDoorTriggers ++ openDoorTriggers ) )+ , ("MiddleButtonRelease", mouseMMB)+ , ("WheelNorth", ([CmdMouse], "swerve the aiming line", Macro ["+"]))+ , ("WheelSouth", ([CmdMouse], "unswerve the aiming line", Macro ["-"])) - -- Debug and others not to display in help screens- , ("C-S", ([CmdDebug], "save game", GameSave))- , ("C-semicolon", ( [CmdNoHelp]- , "move one step towards the x-hair"- , MoveOnceToXhair ))- , ("C-colon", ( [CmdNoHelp]- , "run collectively one step towards the x-hair"- , RunOnceToXhair ))- , ("C-/", ( [CmdNoHelp]- , "continue towards the x-hair"- , ContinueToXhair ))- , ("C-comma", ([CmdNoHelp], "run once ahead", RunOnceAhead))- , ("safe1", ( [CmdInternal]- , "go to pointer for 25 steps"- , goToCmd ))- , ("safe2", ( [CmdInternal]- , "run to pointer collectively"- , runToAllCmd ))- , ("safe3", ( [CmdInternal]- , "pick new leader on screen"- , PickLeaderWithPointer ))- , ("safe4", ( [CmdInternal]- , "select party member on screen"- , SelectWithPointer ))- , ("safe5", ( [CmdInternal]- , "set x-hair to enemy"- , AimPointerEnemy ))- , ("safe6", ( [CmdInternal]- , "fling at enemy under pointer"- , aimFlingCmd ))- , ("safe7", ( [CmdInternal]- , "open Main Menu"- , MainMenu ))- , ("safe8", ( [CmdInternal]- , "cancel aiming"- , Cancel ))- , ("safe9", ( [CmdInternal]- , "accept target"- , Accept ))- , ("safe10", ( [CmdInternal]- , "wait a turn, bracing for impact"- , Wait ))- , ("safe11", ( [CmdInternal]- , "wait 0.1 of a turn"- , Wait10 ))- ]- ++ map defaultHeroSelect [0..6]- }+ -- Debug and others not to display in help screens+ , ("C-S", ([CmdDebug], "save game", GameSave))+ , ("C-semicolon", ( [CmdNoHelp]+ , "move one step towards the x-hair"+ , MoveOnceToXhair ))+ , ("C-colon", ( [CmdNoHelp]+ , "run collectively one step towards the x-hair"+ , RunOnceToXhair ))+ , ("C-/", ( [CmdNoHelp]+ , "continue towards the x-hair"+ , ContinueToXhair ))+ , ("C-comma", ([CmdNoHelp], "run once ahead", RunOnceAhead))+ , ("safe1", ( [CmdInternal]+ , "go to pointer for 25 steps"+ , goToCmd ))+ , ("safe2", ( [CmdInternal]+ , "run to pointer collectively"+ , runToAllCmd ))+ , ("safe3", ( [CmdInternal]+ , "pick new leader on screen"+ , PickLeaderWithPointer ))+ , ("safe4", ( [CmdInternal]+ , "select party member on screen"+ , SelectWithPointer ))+ , ("safe5", ( [CmdInternal]+ , "set x-hair to enemy"+ , AimPointerEnemy ))+ , ("safe6", ( [CmdInternal]+ , "fling at enemy under pointer"+ , aimFlingCmd ))+ , ("safe7", ( [CmdInternal]+ , "open Main Menu"+ , MainMenu ))+ , ("safe8", ( [CmdInternal]+ , "cancel aiming"+ , Cancel ))+ , ("safe9", ( [CmdInternal]+ , "accept target"+ , Accept ))+ , ("safe10", ( [CmdInternal]+ , "wait a turn, bracing for impact"+ , Wait ))+ , ("safe11", ( [CmdInternal]+ , "wait 0.1 of a turn"+ , Wait10 ))+ ]+ ++ map defaultHeroSelect [0..6] closeDoorTriggers :: [Trigger] closeDoorTriggers =
GameDefinition/Content/CaveKind.hs view
@@ -1,4 +1,4 @@--- | Cave layouts.+-- | Cave properties. module Content.CaveKind ( cdefs ) where@@ -33,21 +33,21 @@ , ("caveRogue", 1) ] , cxsize = fst normalLevelBound + 1 , cysize = snd normalLevelBound + 1- , cgrid = DiceXY (3 * d 2) 4- , cminPlaceSize = DiceXY (2 * d 2 + 4) 5+ , cgrid = DiceXY (3 `d` 2) 4+ , cminPlaceSize = DiceXY (2 `d` 2 + 4) 5 , cmaxPlaceSize = DiceXY 15 10- , cdarkChance = d 54 + dl 20+ , cdarkChance = 1 `d` 54 + 1 `dl` 20 , cnightChance = 51 -- always night , cauxConnects = 1%2 , cmaxVoid = 1%6 , cminStairDist = 15- , cextraStairs = 1 + d 2+ , cextraStairs = 1 + 1 `d` 2 , cdoorChance = 3%4 , copenChance = 1%5 , chidden = 7 , cactorCoeff = 130 -- the maze requires time to explore , cactorFreq = [("monster", 60), ("animal", 40)]- , citemNum = 6 * d 5+ , citemNum = 6 `d` 5 , citemFreq = [("useful", 40), ("treasure", 60)] , cplaceFreq = [("rogue", 100)] , cpassable = False@@ -60,64 +60,68 @@ , clegendLitTile = "legendLit" , cescapeGroup = Nothing , cstairFreq = [("staircase", 100)]+ , cdesc = "" } -- no lit corridor alternative, because both lit # and . look bad here arena = rogue { csymbol = 'A' , cname = "Dusty underground library" , cfreq = [("default random", 40), ("caveArena", 1)]- , cgrid = DiceXY (2 + d 2) (d 3)- , cminPlaceSize = DiceXY (2 * d 2 + 4) 6+ , cgrid = DiceXY (2 + 1 `d` 2) (1 `d` 3)+ , cminPlaceSize = DiceXY (2 `d` 2 + 4) 6 , cmaxPlaceSize = DiceXY 16 12- , cdarkChance = 49 + d 10 -- almost all rooms dark (1 in 10 lit)+ , cdarkChance = 49 + 1 `d` 10 -- almost all rooms dark (1 in 10 lit) -- Light is not too deadly, because not many obstructions and so -- foes visible from far away and few foes have ranged combat -- at shallow depth. , cnightChance = 0 -- always day , cauxConnects = 1 , cmaxVoid = 1%8- , cextraStairs = d 3+ , cextraStairs = 1 `d` 3 , chidden = 0 , cactorCoeff = 100 , cactorFreq = [("monster", 30), ("animal", 70)]- , citemNum = 5 * d 5 -- few rooms+ , citemNum = 5 `d` 5 -- few rooms , citemFreq = [("useful", 20), ("treasure", 40), ("any scroll", 40)] , cplaceFreq = [("arena", 100)] , cpassable = True , cdefTile = "arenaSetLit" , cdarkCorTile = "trailLit" -- let trails give off light , clitCorTile = "trailLit"+ , cdesc = "" } arena2 = arena { cname = "Smoking rooms" , cfreq = [("deep random", 30)]- , cdarkChance = 41 + d 10 -- almost all rooms lit (1 in 10 dark)+ , cdarkChance = 41 + 1 `d` 10 -- almost all rooms lit (1 in 10 dark) -- Trails provide enough light for fun stealth. , cnightChance = 51 -- always night- , citemNum = 7 * d 5 -- rare, so make it exciting+ , citemNum = 7 `d` 5 -- rare, so make it exciting , citemFreq = [("useful", 20), ("treasure", 40), ("any vial", 40)] , cdefTile = "arenaSetDark"+ , cdesc = "" } laboratory = arena2 { csymbol = 'L' , cname = "Burnt laboratory" , cfreq = [("deep random", 20), ("caveLaboratory", 1)]- , cgrid = DiceXY (2 * d 2 + 7) 3- , cminPlaceSize = DiceXY (3 * d 2 + 4) 5- , cdarkChance = d 54 + dl 20 -- most rooms lit, to compensate for corridors+ , cgrid = DiceXY (2 `d` 2 + 7) 3+ , cminPlaceSize = DiceXY (3 `d` 2 + 4) 5+ , cdarkChance = 1 `d` 54 + 1 `dl` 20 -- most rooms lit, to compensate for corridors , cnightChance = 0 -- always day , cauxConnects = 1%10 , cmaxVoid = 1%10- , cextraStairs = d 2+ , cextraStairs = 1 `d` 2 , cdoorChance = 1 , copenChance = 1%2 , chidden = 7- , citemNum = 7 * d 5 -- reward difficulty+ , citemNum = 7 `d` 5 -- reward difficulty , citemFreq = [("useful", 20), ("treasure", 40), ("any vial", 40)] , cplaceFreq = [("laboratory", 100)] , cpassable = False , cdefTile = "fillerWall" , cdarkCorTile = "labTrailLit" -- let lab smoke give off light always , clitCorTile = "labTrailLit"+ , cdesc = "An experiment (or was it manufacturing?) had gone wrong here." } empty = rogue { csymbol = 'E'@@ -126,16 +130,16 @@ , cgrid = DiceXY 1 1 , cminPlaceSize = DiceXY 12 12 , cmaxPlaceSize = DiceXY 48 32 -- favour large rooms- , cdarkChance = d 100 + dl 100+ , cdarkChance = 1 `d` 100 + 1 `dl` 100 , cnightChance = 0 -- always day , cauxConnects = 3%2 , cminStairDist = 30 , cmaxVoid = 0 -- too few rooms to have void and fog common anyway- , cextraStairs = d 2+ , cextraStairs = 1 `d` 2 , cdoorChance = 0 , copenChance = 0 , chidden = 0- , cactorCoeff = 8+ , cactorCoeff = 7 , cactorFreq = [("animal", 10), ("immobile animal", 90)] -- The healing geysers on lvl 3 act like HP resets. Needed to avoid -- cascading failure, if the particular starting conditions were@@ -143,18 +147,19 @@ -- enough of a continuity. Gyesers on lvl 3 are not OP and can't be -- abused, because they spawn less and less often and also HP doesn't -- effectively accumulate over max.- , citemNum = 4 * d 5 -- few rooms and geysers are the boon+ , citemNum = 4 `d` 5 -- few rooms and geysers are the boon , cplaceFreq = [("empty", 100)] , cpassable = True , cdefTile = "emptySet" , cdarkCorTile = "floorArenaDark" , clitCorTile = "floorArenaLit"+ , cdesc = "" } noise = rogue { csymbol = 'N' , cname = "Leaky burrowed sediment" , cfreq = [("default random", 10), ("caveNoise", 1)]- , cgrid = DiceXY (2 + d 3) 3+ , cgrid = DiceXY (2 + 1 `d` 3) 3 , cminPlaceSize = DiceXY 8 5 , cmaxPlaceSize = DiceXY 20 10 , cdarkChance = 51@@ -163,30 +168,33 @@ , cnightChance = 0 -- harder variant, but looks cheerful , cauxConnects = 1%10 , cmaxVoid = 1%100- , cextraStairs = d 4+ , cextraStairs = 1 `d` 4 , cdoorChance = 1 -- to avoid lit quasi-door tiles , chidden = 0 , cactorCoeff = 160 -- the maze requires time to explore , cactorFreq = [("monster", 80), ("animal", 20)]- , citemNum = 7 * d 5 -- an incentive to explore the labyrinth+ , citemNum = 7 `d` 5 -- an incentive to explore the labyrinth , cpassable = True , cplaceFreq = [("noise", 100)] , cdefTile = "noiseSet" , couterFenceTile = "noise fence" -- ensures no cut-off parts from collapsed , cdarkCorTile = "floorArenaDark" , clitCorTile = "floorArenaLit"+ , cdesc = "" } noise2 = noise { cname = "Frozen derelict mine" , cfreq = [("caveNoise2", 1)] , cnightChance = 51 -- easier variant, but looks sinister- , citemNum = 13 * d 5 -- an incentive to explore the final labyrinth+ , citemNum = 13 `d` 5 -- an incentive to explore the final labyrinth , cplaceFreq = [("noise", 1), ("mine", 99)] , cstairFreq = [("gated staircase", 100)]+ , cdesc = "" } shallow2rogue = rogue { cfreq = [("shallow random 2", 100)] , cextraStairs = 1 -- ensure heroes meet initial monsters and their loot+ , cdesc = "" } shallow1rogue = shallow2rogue { csymbol = 'B'@@ -195,9 +203,10 @@ , cdarkChance = 0 -- all rooms lit, for a gentle start , cextraStairs = 1 , cactorFreq = filter ((/= "monster") . fst) $ cactorFreq rogue- , citemNum = 8 * d 5 -- lure them in with loot+ , citemNum = 8 `d` 5 -- lure them in with loot , citemFreq = filter ((/= "treasure") . fst) $ citemFreq rogue , cescapeGroup = Just "escape up"+ , cdesc = "" } raid = rogue { csymbol = 'T'@@ -207,9 +216,10 @@ , cmaxVoid = 1%10 , cactorCoeff = 500 -- deep level with no kit, so slow spawning , cactorFreq = [("animal", 100)]- , citemNum = 6 * d 8 -- just one level, hard enemies, treasure+ , citemNum = 6 `d` 8 -- just one level, hard enemies, treasure , citemFreq = [("useful", 33), ("gem", 33), ("currency", 33)] , cescapeGroup = Just "escape up"+ , cdesc = "" } brawl = rogue -- many random solid tiles, to break LOS, since it's a day -- and this scenario is not focused on ranged combat;@@ -217,7 +227,7 @@ { csymbol = 'b' , cname = "Sunny woodland" , cfreq = [("caveBrawl", 1)]- , cgrid = DiceXY (2 * d 2 + 2) 3+ , cgrid = DiceXY (2 `d` 2 + 2) 3 , cminPlaceSize = DiceXY 3 3 , cmaxPlaceSize = DiceXY 7 5 , cdarkChance = 51@@ -227,13 +237,14 @@ , cextraStairs = 1 , chidden = 0 , cactorFreq = []- , citemNum = 5 * d 8+ , citemNum = 5 `d` 8 , citemFreq = [("useful", 100)] , cplaceFreq = [("brawl", 60), ("rogue", 40)] , cpassable = True , cdefTile = "brawlSetLit" , cdarkCorTile = "floorArenaLit" , clitCorTile = "floorArenaLit"+ , cdesc = "" } shootout = rogue -- a scenario with strong missiles; -- few solid tiles, but only translucent tiles or walkable@@ -243,7 +254,7 @@ { csymbol = 'S' , cname = "Misty meadow" , cfreq = [("caveShootout", 1)]- , cgrid = DiceXY (d 2 + 7) 3+ , cgrid = DiceXY (1 `d` 2 + 7) 3 , cminPlaceSize = DiceXY 3 3 , cmaxPlaceSize = DiceXY 3 4 , cdarkChance = 51@@ -253,20 +264,21 @@ , cextraStairs = 1 , chidden = 0 , cactorFreq = []- , citemNum = 5 * d 16+ , citemNum = 5 `d` 16 -- less items in inventory, more to be picked up, -- to reward explorer and aggressor and punish camper , citemFreq = [ ("useful", 30) , ("any arrow", 400), ("harpoon", 300) , ("any vial", 60) ] -- Many consumable buffs are needed in symmetric maps- -- so that aggresor prepares them in advance and camper+ -- so that aggressor prepares them in advance and camper -- needs to waste initial turns to buff for the defence. , cplaceFreq = [("shootout", 100)] , cpassable = True , cdefTile = "shootoutSetLit" , cdarkCorTile = "floorArenaLit" , clitCorTile = "floorArenaLit"+ , cdesc = "" } escape = rogue -- a scenario with weak missiles, because heroes don't depend -- on them; dark, so solid obstacles are to hide from missiles,@@ -275,8 +287,8 @@ { csymbol = 'E' , cname = "Metropolitan park at dusk" -- "night" didn't fit , cfreq = [("caveEscape", 1)]- , cgrid = DiceXY -- (2 * d 2 + 3) 4 -- park, so lamps in lines- (2 * d 2 + 6) 3 -- for now, to fit larger places+ , cgrid = DiceXY -- (2 `d` 2 + 3) 4 -- park, so lamps in lines+ (2 `d` 2 + 6) 3 -- for now, to fit larger places , cminPlaceSize = DiceXY 3 3 , cmaxPlaceSize = DiceXY 9 9 -- bias towards larger lamp areas , cdarkChance = 51 -- colonnade rooms should always be dark@@ -286,7 +298,7 @@ , cextraStairs = 1 , chidden = 0 , cactorFreq = []- , citemNum = 6 * d 8+ , citemNum = 6 `d` 8 , citemFreq = [ ("useful", 30), ("treasure", 30), ("gem", 100) , ("weak arrow", 500), ("harpoon", 400) ] , cplaceFreq = [("park", 100)] -- the same rooms as in ambush@@ -295,12 +307,13 @@ , cdarkCorTile = "trailLit" -- let trails give off light , clitCorTile = "trailLit" , cescapeGroup = Just "escape outdoor down"+ , cdesc = "" } zoo = rogue -- few lights and many solids, to help the less numerous heroes { csymbol = 'Z' , cname = "Menagerie in flames" , cfreq = [("caveZoo", 1)]- , cgrid = DiceXY (2 * d 2 + 6) 3+ , cgrid = DiceXY (2 `d` 2 + 6) 3 , cminPlaceSize = DiceXY 4 4 , cmaxPlaceSize = DiceXY 12 12 , cdarkChance = 51 -- always dark rooms@@ -312,13 +325,14 @@ , cextraStairs = 1 , chidden = 0 , cactorFreq = []- , citemNum = 7 * d 8+ , citemNum = 7 `d` 8 , citemFreq = [("useful", 100), ("light source", 1000)] , cplaceFreq = [("zoo", 50)] , cpassable = True , cdefTile = "zooSet" , cdarkCorTile = "trailLit" -- let trails give off light , clitCorTile = "trailLit"+ , cdesc = "" } ambush = rogue -- a scenario with strong missiles; -- dark, so solid obstacles are to hide from missiles,@@ -332,8 +346,8 @@ { csymbol = 'M' , cname = "Burning metropolitan park" , cfreq = [("caveAmbush", 1)]- , cgrid = DiceXY -- (2 * d 2 + 3) 4 -- park, so lamps in lines- (2 * d 2 + 5) 3 -- for now, to fit larger places+ , cgrid = DiceXY -- (2 `d` 2 + 3) 4 -- park, so lamps in lines+ (2 `d` 2 + 5) 3 -- for now, to fit larger places , cminPlaceSize = DiceXY 3 3 , cmaxPlaceSize = DiceXY 9 9 -- bias towards larger lamp areas , cdarkChance = 51 -- colonnade rooms should always be dark@@ -343,19 +357,20 @@ , cextraStairs = 1 , chidden = 0 , cactorFreq = []- , citemNum = 5 * d 8+ , citemNum = 5 `d` 8 , citemFreq = [("useful", 30), ("any arrow", 400), ("harpoon", 300)] , cplaceFreq = [("park", 100)] , cpassable = True , cdefTile = "ambushSet" , cdarkCorTile = "trailLit" -- let trails give off light , clitCorTile = "trailLit"+ , cdesc = "" } battle = rogue -- few lights and many solids, to help the less numerous heroes { csymbol = 'B' , cname = "Old battle ground" , cfreq = [("caveBattle", 1)]- , cgrid = DiceXY (2 * d 2 + 1) 3+ , cgrid = DiceXY (2 `d` 2 + 1) 3 , cminPlaceSize = DiceXY 4 4 , cmaxPlaceSize = DiceXY 9 7 , cdarkChance = 0@@ -367,7 +382,7 @@ , cextraStairs = 1 , chidden = 0 , cactorFreq = []- , citemNum = 5 * d 8+ , citemNum = 5 `d` 8 , citemFreq = [("useful", 100), ("light source", 200)] , cplaceFreq = [("battle", 50), ("rogue", 50)] , cpassable = True@@ -375,20 +390,25 @@ , cdarkCorTile = "trailLit" -- let trails give off light , clitCorTile = "trailLit" , couterFenceTile = "noise fence" -- ensures no cut-off parts from collapsed+ , cdesc = "" } safari1 = brawl- { cname = "Hunam habitat"- , cfreq = [("caveSafari1", 1)]- , cescapeGroup = Nothing- , cstairFreq = [("staircase outdoor", 1)]+ { cname = "Hunam habitat"+ , cfreq = [("caveSafari1", 1)]+ , cescapeGroup = Nothing+ , cstairFreq = [("staircase outdoor", 1)]+ , cdesc = "\"Act 1. Hunams scavenge in a forest in their usual disgusting way.\"" }-safari2 = ambush- { cname = "Hunting grounds"- , cfreq = [("caveSafari2", 1)]- , cstairFreq = [("staircase outdoor", 1)]+safari2 = ambush -- lamps instead of trees, but ok, it's only a simulation+ { cname = "Deep into the jungle"+ , cfreq = [("caveSafari2", 1)]+ , cstairFreq = [("staircase outdoor", 1)]+ , cdesc = "\"Act 2. In the dark pure heart of the jungle noble animals roam freely.\"" }-safari3 = zoo- { cfreq = [("caveSafari3", 1)]- , cescapeGroup = Just "escape outdoor down"- , cstairFreq = [("staircase outdoor", 1)]+safari3 = zoo -- glass rooms, but ok, it's only a simulation+ { cname = "Jungle in flames"+ , cfreq = [("caveSafari3", 1)]+ , cescapeGroup = Just "escape outdoor down"+ , cstairFreq = [("staircase outdoor", 1)]+ , cdesc = "\"Act 3. Jealous hunams set jungle on fire and flee.\"" }
GameDefinition/Content/ItemKind.hs view
@@ -1,4 +1,4 @@--- | Item and treasure definitions.+-- | Item definitions. module Content.ItemKind ( cdefs, items, otherItemContent ) where@@ -27,7 +27,7 @@ , getFreq = ifreq , validateSingle = validateSingleItemKind , validateAll = validateAllItemKind- , content = contentFromList [] -- filled out later on+ , content = contentFromList $ items ++ otherItemContent } otherItemContent :: [ItemKind]@@ -74,12 +74,12 @@ , iname = "sandstone rock" , ifreq = [("sandstone rock", 1), ("weak arrow", 10)] , iflavour = zipPlain [Green]- , icount = 1 * d 2+ , icount = 1 `d` 2 , irarity = [(1, 50), (10, 1)] , iverbHit = "hit" , iweight = 300- , idamage = toDmg $ 1 * d 1- , iaspects = [AddHurtMelee (-16 |*| 5)]+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [AddHurtMelee $ -16 * 5] , ieffects = [] , ifeature = [toVelocity 70, Fragile, Identified] -- not dense, irregular , idesc = "A lump of brittle sandstone rock."@@ -90,12 +90,13 @@ , iname = "dart" , ifreq = [("useful", 100), ("any arrow", 50), ("weak arrow", 50)] , iflavour = zipPlain [BrRed]- , icount = 4 * d 3+ , icount = 4 `d` 3 , irarity = [(1, 20), (10, 10)] , iverbHit = "prick" , iweight = 40- , idamage = toDmg $ 1 * d 1- , iaspects = [AddHurtMelee (-14 + d 2 + dl 4 |*| 5)] -- only leather-piercing+ , idamage = [(98, 1 `d` 1), (2, 2 `d` 1)]+ , iaspects = [AddHurtMelee $ (-14 + 1 `d` 2 + 1 `dl` 4) * 5]+ -- only leather-piercing , ieffects = [] , ifeature = [Identified] , idesc = "A sharp delicate dart with fins."@@ -106,12 +107,13 @@ , iname = "spike" , ifreq = [("useful", 100), ("any arrow", 50), ("weak arrow", 50)] , iflavour = zipPlain [Cyan]- , icount = 4 * d 3+ , icount = 4 `d` 3 , irarity = [(1, 10), (10, 20)] , iverbHit = "nick" , iweight = 150- , idamage = toDmg $ 2 * d 1- , iaspects = [AddHurtMelee (-10 + d 2 + dl 4 |*| 5)] -- heavy vs armor+ , idamage = [(98, 2 `d` 1), (2, 4 `d` 1)]+ , iaspects = [AddHurtMelee $ (-10 + 1 `d` 2 + 1 `dl` 4) * 5]+ -- heavy vs armor , ieffects = [ Explode "single spark" -- when hitting enemy , OnSmash (Explode "single spark") ] -- at wall hit , ifeature = [toVelocity 70, Identified] -- hitting with tip costs speed@@ -123,12 +125,13 @@ , iname = "sling stone" , ifreq = [("useful", 5), ("any arrow", 100)] , iflavour = zipPlain [Blue]- , icount = 3 * d 3+ , icount = 3 `d` 3 , irarity = [(1, 1), (10, 20)] , iverbHit = "hit" , iweight = 200- , idamage = toDmg $ 1 * d 1- , iaspects = [AddHurtMelee (-10 + d 2 + dl 4 |*| 5)] -- heavy vs armor+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [AddHurtMelee $ (-10 + 1 `d` 2 + 1 `dl` 4) * 5]+ -- heavy vs armor , ieffects = [ Explode "single spark" -- when hitting enemy , OnSmash (Explode "single spark") ] -- at wall hit , ifeature = [toVelocity 150, Identified]@@ -140,12 +143,13 @@ , iname = "sling bullet" , ifreq = [("useful", 5), ("any arrow", 100)] , iflavour = zipPlain [BrBlack]- , icount = 6 * d 3+ , icount = 6 `d` 3 , irarity = [(1, 1), (10, 15)] , iverbHit = "hit" , iweight = 28- , idamage = toDmg $ 1 * d 1- , iaspects = [AddHurtMelee (-17 + d 2 + dl 4 |*| 5)] -- not armor-piercing+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [AddHurtMelee $ (-17 + 1 `d` 2 + 1 `dl` 4) * 5]+ -- not armor-piercing , ieffects = [] , ifeature = [toVelocity 200, Identified] , idesc = "Small almond-shaped leaden projectile that weighs more than the sling used to tie the bag. It doesn't drop out of the sling's pouch when swung and doesn't snag when released."@@ -160,12 +164,12 @@ , iname = "bolas set" , ifreq = [("useful", 100)] , iflavour = zipPlain [BrYellow]- , icount = dl 4+ , icount = 1 `dl` 4 , irarity = [(5, 5), (10, 5)] , iverbHit = "entangle" , iweight = 500- , idamage = toDmg $ 1 * d 1- , iaspects = [AddHurtMelee (-14 |*| 5)]+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [AddHurtMelee $ -14 * 5] , ieffects = [Paralyze 15, DropBestWeapon] , ifeature = [Identified] , idesc = "Wood balls tied with hemp rope. The target enemy is tripped and bound to drop the main weapon, while fighting for balance."@@ -176,12 +180,12 @@ , iname = "harpoon" , ifreq = [("useful", 100), ("harpoon", 100)] , iflavour = zipPlain [Brown]- , icount = dl 5+ , icount = 1 `dl` 5 , irarity = [(10, 10)] , iverbHit = "hook" , iweight = 750- , idamage = [(99, 5 * d 1), (1, 10 * d 1)]- , iaspects = [AddHurtMelee (-10 + d 2 + dl 4 |*| 5)]+ , idamage = [(99, 5 `d` 1), (1, 10 `d` 1)]+ , iaspects = [AddHurtMelee $ (-10 + 1 `d` 2 + 1 `dl` 4) * 5] , ieffects = [PullActor (ThrowMod 200 50)] , ifeature = [Identified] , idesc = "The cruel, barbed head lodges in its victim so painfully that the weakest tug of the thin line sends the victim flying."@@ -192,13 +196,13 @@ , iname = "net" , ifreq = [("useful", 100)] , iflavour = zipPlain [White]- , icount = dl 3+ , icount = 1 `dl` 3 , irarity = [(3, 5), (10, 4)] , iverbHit = "entangle" , iweight = 1000- , idamage = toDmg $ 2 * d 1- , iaspects = [AddHurtMelee (-14 |*| 5)]- , ieffects = [ toOrganGameTurn "slowed" (3 + d 3)+ , idamage = toDmg $ 2 `d` 1+ , iaspects = [AddHurtMelee $ -14 * 5]+ , ieffects = [ toOrganGameTurn "slowed" (3 + 1 `d` 3) , DropItem maxBound 1 CEqp "torso armor" ] , ifeature = [Identified] , idesc = "A wide net with weights along the edges. Entangles armor and restricts movement."@@ -212,7 +216,7 @@ , iname = "wooden torch" , ifreq = [("useful", 100), ("light source", 100), ("wooden torch", 1)] , iflavour = zipPlain [Brown]- , icount = d 2+ , icount = 1 `d` 2 , irarity = [(1, 15)] , iverbHit = "scorch" , iweight = 1000@@ -233,7 +237,7 @@ , irarity = [(6, 7)] , iverbHit = "burn" , iweight = 1500- , idamage = toDmg $ 1 * d 1+ , idamage = toDmg $ 1 `d` 1 , iaspects = [AddShine 3, AddSight (-1)] , ieffects = [ Burn 1, Paralyze 6, OnSmash (Explode "burning oil 2") , EqpSlot EqpSlotLightSource ]@@ -250,7 +254,7 @@ , irarity = [(10, 5)] , iverbHit = "burn" , iweight = 3000- , idamage = toDmg $ 4 * d 1+ , idamage = toDmg $ 4 `d` 1 , iaspects = [AddShine 4, AddSight (-1)] , ieffects = [ Burn 1, Paralyze 8, OnSmash (Explode "burning oil 4") , EqpSlot EqpSlotLightSource ]@@ -309,28 +313,28 @@ flask1 = flask { irarity = [(10, 4)] , ieffects = [ ELabel "of strength renewal brew"- , toOrganActorTurn "strengthened" (20 + d 5)+ , toOrganActorTurn "strengthened" (20 + 1 `d` 5) , toOrganNone "regenerating" , OnSmash (Explode "dense shower") ] } flask2 = flask { ieffects = [ ELabel "of weakness brew"- , toOrganGameTurn "weakened" (20 + d 5)+ , toOrganGameTurn "weakened" (20 + 1 `d` 5) , OnSmash (Explode "sparse shower") ] } flask3 = flask { ieffects = [ ELabel "of melee protective balm"- , toOrganActorTurn "protected from melee" (20 + d 5)+ , toOrganActorTurn "protected from melee" (20 + 1 `d` 5) , OnSmash (Explode "melee protective balm") ] } flask4 = flask { ieffects = [ ELabel "of ranged protective balm"- , toOrganActorTurn "protected from ranged" (20 + d 5)+ , toOrganActorTurn "protected from ranged" (20 + 1 `d` 5) , OnSmash (Explode "ranged protective balm") ] } flask5 = flask { ieffects = [ ELabel "of PhD defense questions"- , toOrganGameTurn "defenseless" (20 + d 5)+ , toOrganGameTurn "defenseless" (20 + 1 `d` 5) , Impress , DetectExit 20 , OnSmash (Explode "PhD defense question") ]@@ -338,7 +342,7 @@ flask6 = flask { irarity = [(10, 9)] , ieffects = [ ELabel "of resolution"- , toOrganActorTurn "resolute" (200 + d 50)+ , toOrganActorTurn "resolute" (200 + 1 `d` 50) -- long, for scouting and has to recharge , RefillCalm 60 -- not to make it a drawback, via @calmEnough@ , OnSmash (Explode "resolution dust") ]@@ -346,14 +350,14 @@ flask7 = flask { irarity = [(10, 4)] , ieffects = [ ELabel "of haste brew"- , toOrganActorTurn "hasted" (20 + d 5)+ , toOrganActorTurn "hasted" (20 + 1 `d` 5) , OnSmash (Explode "blast 20") , OnSmash (Explode "haste spray") ] } flask8 = flask { irarity = [(1, 14), (10, 4)] , ieffects = [ ELabel "of lethargy brew"- , toOrganGameTurn "slowed" (20 + d 5)+ , toOrganGameTurn "slowed" (20 + 1 `d` 5) , toOrganNone "regenerating", toOrganNone "regenerating" -- x2 , RefillCalm 5 , OnSmash (Explode "slowness mist") ]@@ -361,32 +365,32 @@ flask9 = flask { irarity = [(10, 4)] , ieffects = [ ELabel "of eye drops"- , toOrganActorTurn "far-sighted" (40 + d 10)+ , toOrganActorTurn "far-sighted" (40 + 1 `d` 10) , OnSmash (Explode "eye drop") ] } flask10 = flask { irarity = [(10, 2)] , ieffects = [ ELabel "of smelly concoction"- , toOrganActorTurn "keen-smelling" (40 + d 10)+ , toOrganActorTurn "keen-smelling" (40 + 1 `d` 10) , DetectActor 10 , OnSmash (Explode "smelly droplet") ] } flask11 = flask { irarity = [(10, 4)] , ieffects = [ ELabel "of cat tears"- , toOrganActorTurn "shiny-eyed" (40 + d 10)+ , toOrganActorTurn "shiny-eyed" (40 + 1 `d` 10) , OnSmash (Explode "eye shine") ] } flask12 = flask { irarity = [(1, 14), (10, 10)] , ieffects = [ ELabel "of whiskey"- , toOrganActorTurn "drunk" (20 + d 5)+ , toOrganActorTurn "drunk" (20 + 1 `d` 5) , Burn 1, RefillHP 3 , OnSmash (Explode "whiskey spray") ] } flask13 = flask { ieffects = [ ELabel "of bait cocktail"- , toOrganActorTurn "drunk" (20 + d 5)+ , toOrganActorTurn "drunk" (20 + 1 `d` 5) , Burn 1, RefillHP 3 , Summon "mobile animal" 1 , OnSmash (Summon "mobile animal" 1)@@ -429,14 +433,14 @@ } flask19 = flask { ieffects = [ ELabel "of blindness"- , toOrganGameTurn "blind" (40 + d 10)+ , toOrganGameTurn "blind" (40 + 1 `d` 10) , OnSmash (Explode "iron filing") ] } flask20 = flask { ieffects = [ ELabel "of calamity" , toOrganNone "poisoned"- , toOrganGameTurn "weakened" (20 + d 5)- , toOrganGameTurn "defenseless" (20 + d 5)+ , toOrganGameTurn "weakened" (20 + 1 `d` 5)+ , toOrganGameTurn "defenseless" (20 + 1 `d` 5) , OnSmash (Explode "poison cloud") ] } @@ -482,7 +486,8 @@ potion5 = potion -- needs to be common to show at least a portion of effects { irarity = [(1, 30), (10, 15)] , ieffects = [ OneOf [ RefillHP 10, RefillHP 5, Burn 5- , toOrganActorTurn "strengthened" (20 + d 5) ]+ , DropItem 1 maxBound COrgan "poisoned"+ , toOrganActorTurn "strengthened" (20 + 1 `d` 5) ] , OnSmash (OneOf [ Explode "dense shower" , Explode "sparse shower" , Explode "melee protective balm"@@ -495,7 +500,8 @@ , ieffects = [ Impress , OneOf [ RefillCalm (-60) , RefillHP 20, RefillHP 10, Burn 10- , toOrganActorTurn "hasted" (20 + d 5) ]+ , DropItem 1 maxBound COrgan "poisoned"+ , toOrganActorTurn "hasted" (20 + 1 `d` 5) ] , OnSmash (OneOf [ Explode "healing mist 2" , Explode "wounding mist" , Explode "distressing odor"@@ -551,11 +557,12 @@ { ifreq = [("treasure", 100)] , irarity = [(5, 9), (10, 9)] -- mixed blessing, so available early , ieffects = [ Unique, ELabel "of Reckless Beacon"- , Summon "hero" 1, Summon "mobile animal" (2 + d 2) ]+ , Summon "hero" 1, Summon "mobile animal" (2 + 1 `d` 2) ]+ , idesc = "The bright flame and sweet-smelling smoke of this heavily infused scroll should attract natural creatures inhabiting the area, including human survivors, if any." } scroll2 = scroll { irarity = [(1, 2)]- , ieffects = [ ELabel "of greed", Teleport 20, DetectItem 20+ , ieffects = [ ELabel "of greed", DetectItem 20, Teleport 20 , RefillCalm (-100) ] } scroll3 = scroll@@ -583,21 +590,25 @@ } scroll8 = scroll { irarity = [(10, 2)]- , ieffects = [InsertMove $ 1 + d 2 + dl 2]+ , ieffects = [InsertMove $ 1 + 1 `d` 2 + 1 `dl` 2] } scroll9 = scroll { irarity = [(1, 30)]- , ieffects = [ELabel "of scientific explanation", Identify]+ , ieffects = [ ELabel "of scientific explanation"+ , Composite [Identify, RefillCalm 10] ]+ -- your most pressing existential concerns are answered scientifitically,+ -- hence the calming effect } scroll10 = scroll { irarity = [(10, 20)] , ieffects = [ ELabel "transfiguration"- , PolyItem, Explode "firecracker 7" ]+ , Composite [PolyItem, Explode "firecracker 7"] ] } scroll11 = scroll { ifreq = [("treasure", 100)] , irarity = [(6, 9), (10, 9)]- , ieffects = [Unique, ELabel "of Prisoner Release", Summon "hero" 1]+ , ieffects = [Unique, ELabel "of Rescue Proclamation", Summon "hero" 1]+ , idesc = "A survivor is found that enjoys, apparently, complete physiological integrity. If we so wish, we can pronounce him rescued and let him join our team." } scroll12 = scroll { irarity = [(1, 9), (10, 4)]@@ -619,7 +630,7 @@ , iverbHit = "prod" , iweight = 10000 , idamage = toDmg 0- , iaspects = [Timeout $ d 2 + 2 - dl 2 |*| 10]+ , iaspects = [Timeout $ (1 `d` 2 + 2 - 1 `dl` 2) * 10] , ieffects = [Recharging (toOrganActorTurn "hasted" 1)] , ifeature = [Durable, Applicable, Identified] , idesc = "Makes you vulnerable at take-off, but then you are free like a bird."@@ -635,7 +646,7 @@ , iverbHit = "smack" , iweight = 400 , idamage = toDmg 0- , iaspects = [AddHurtMelee $ d 10 |*| 3]+ , iaspects = [AddHurtMelee $ (1 `d` 10) * 3] , ieffects = [EqpSlot EqpSlotAddHurtMelee] , ifeature = [Identified, Equipable] , idesc = "A portable sharpening stone that lets you fix your weapons between or even during fights, without the need to set up camp, fish out tools and assemble a proper sharpening workshop."@@ -652,7 +663,7 @@ , iweight = 100 , idamage = toDmg 0 , iaspects = [ AddSight 10, AddMaxCalm 30, AddShine 2- , Timeout $ 1 + d 2 ]+ , Timeout $ 1 + 1 `d` 2 ] , ieffects = [ Periodic , Recharging (toOrganNone "poisoned") , Recharging (Summon "mobile monster" 1) ]@@ -671,7 +682,7 @@ , iweight = 300 , idamage = toDmg 0 , iaspects = [ AddNocto 1- , AddArmorMelee (dl 5 - 10), AddArmorRanged (dl 5 - 10) ]+ , AddArmorMelee (1 `dl` 5 - 10), AddArmorRanged (1 `dl` 5 - 10) ] , ieffects = [EqpSlot EqpSlotMiscBonus] , ifeature = [Identified, Equipable] , idesc = "A silk flag with a bell for detecting sudden draft changes. May indicate a nearby corridor crossing or a fast enemy approaching in the dark. Is also very noisy."@@ -690,9 +701,9 @@ , iverbHit = "whip" , iweight = 30 , idamage = toDmg 0- , iaspects = [ Timeout $ 1 + d 2- , AddArmorMelee $ 2 + d 3- , AddArmorRanged $ d 3 ]+ , iaspects = [ Timeout $ 1 + 1 `d` 2+ , AddArmorMelee $ 2 + 1 `d` 3+ , AddArmorRanged $ 1 `d` 3 ] , ieffects = [ Unique, Periodic , Recharging (RefillCalm 1), EqpSlot EqpSlotMiscBonus ] , ifeature = [Durable, Precious, Identified, Equipable]@@ -718,7 +729,7 @@ } necklace1 = necklace { ifreq = [("treasure", 100)]- , iaspects = [Timeout $ d 3 + 4 - dl 3 |*| 10]+ , iaspects = [Timeout $ (1 `d` 3 + 4 - 1 `dl` 3) * 10] , ieffects = [ Unique, ELabel "of Aromata", EqpSlot EqpSlotMiscBonus , Recharging (RefillHP 1) ] ++ ieffects necklace@@ -728,57 +739,57 @@ necklace2 = necklace { ifreq = [("treasure", 100)] -- just too nasty to call it useful , irarity = [(1, 1)]- , iaspects = [Timeout $ d 3 + 3 - dl 3 |*| 10]- , ieffects = [ Recharging (Summon "mobile animal" $ 1 + dl 2)+ , iaspects = [Timeout $ (1 `d` 3 + 3 - 1 `dl` 3) * 10]+ , ieffects = [ Recharging (Summon "mobile animal" $ 1 + 1 `dl` 2) , Recharging (Explode "waste") , Recharging Impress , Recharging (DropItem 1 maxBound COrgan "temporary condition") ] ++ ieffects necklace } necklace3 = necklace- { iaspects = [Timeout $ d 3 + 4 - dl 3 |*| 5]+ { iaspects = [Timeout $ (1 `d` 3 + 4 - 1 `dl` 3) * 5] , ieffects = [ ELabel "of fearful listening" , Recharging (DetectActor 10) , Recharging (RefillCalm (-20)) ] ++ ieffects necklace } necklace4 = necklace- { iaspects = [Timeout $ d 4 + 4 - dl 4 |*| 2]- , ieffects = [Recharging (Teleport $ d 2 * 3)]+ { iaspects = [Timeout $ (1 `d` 4 + 4 - 1 `dl` 4) * 2]+ , ieffects = [Recharging (Teleport $ 3 `d` 2)] ++ ieffects necklace } necklace5 = necklace- { iaspects = [Timeout $ d 3 + 4 - dl 3 |*| 10]+ { iaspects = [Timeout $ (1 `d` 3 + 4 - 1 `dl` 3) * 10] , ieffects = [ ELabel "of escape"- , Recharging (Teleport $ 14 + d 3 * 3)+ , Recharging (Teleport $ 14 + 3 `d` 3) , Recharging (DetectExit 20) , Recharging (RefillHP (-2)) ] -- prevent micromanagement ++ ieffects necklace } necklace6 = necklace- { iaspects = [Timeout $ d 3 + 1 |*| 2]+ { iaspects = [Timeout $ (1 `d` 3 + 1) * 2] , ieffects = [Recharging (PushActor (ThrowMod 100 50))] ++ ieffects necklace } necklace7 = necklace { ifreq = [("treasure", 100)]- , iaspects = [ AddMaxHP $ 10 + d 10+ , iaspects = [ AddMaxHP $ 10 + 1 `d` 10 , AddArmorMelee 20, AddArmorRanged 10- , Timeout $ d 2 + 5 - dl 3 ]+ , Timeout $ 1 `d` 2 + 5 - 1 `dl` 3 ] , ieffects = [ Unique, ELabel "of Overdrive", EqpSlot EqpSlotAddSpeed- , Recharging (InsertMove $ 1 + d 2)+ , Recharging (InsertMove $ 1 + 1 `d` 2) , Recharging (RefillHP (-1)) , Recharging (RefillCalm (-1)) ] -- fake "hears something" :) ++ ieffects necklace , ifeature = Durable : ifeature necklace } necklace8 = necklace- { iaspects = [Timeout $ d 3 + 3 - dl 3 |*| 5]+ { iaspects = [Timeout $ (1 `d` 3 + 3 - 1 `dl` 3) * 5] , ieffects = [Recharging $ Explode "spark"] ++ ieffects necklace } necklace9 = necklace- { iaspects = [Timeout $ d 3 + 3 - dl 3 |*| 5]+ { iaspects = [Timeout $ (1 `d` 3 + 3 - 1 `dl` 3) * 5] , ieffects = [Recharging $ Explode "fragrance"] ++ ieffects necklace }@@ -795,7 +806,7 @@ , iverbHit = "bang" , iweight = 500 , idamage = toDmg 0- , iaspects = [AddNocto 1, AddSight (-1), AddArmorMelee $ 1 + dl 3 |*| 3]+ , iaspects = [AddNocto 1, AddSight (-1), AddArmorMelee $ (1 + 1 `dl` 3) * 3] , ieffects = [EqpSlot EqpSlotMiscBonus] , ifeature = [Precious, Identified, Durable, Equipable] , idesc = "Contraption of lenses and mirrors on a polished brass headband for capturing and strengthening light in dark environment. Hampers vision in daylight. Stackable."@@ -811,7 +822,7 @@ , iverbHit = "rap" , iweight = 50 , idamage = toDmg 0- , iaspects = [AddSight $ 1 + d 2, AddHurtMelee $ d 2 |*| 3]+ , iaspects = [AddSight $ 1 + 1 `d` 2, AddHurtMelee $ (1 `d` 2) * 3] , ieffects = [EqpSlot EqpSlotAddSight] , ifeature = [Precious, Identified, Durable, Equipable] , idesc = "Let's you better focus your weaker eye."@@ -849,29 +860,30 @@ } ring1 = ring { irarity = [(10, 2)]- , iaspects = [AddSpeed $ 1 + d 2, AddMaxHP $ dl 7 - 7 - d 7]+ , iaspects = [AddSpeed $ 1 + 1 `d` 2, AddMaxHP $ 1 `dl` 7 - 7 - 1 `d` 7] , ieffects = [ Explode "distortion" -- strong magic , EqpSlot EqpSlotAddSpeed ] } ring2 = ring- { irarity = [(10, 5)]- , iaspects = [AddMaxHP $ 10 + dl 10, AddMaxCalm $ dl 5 - 20 - d 5]+ { irarity = [(10, 8)]+ , iaspects = [AddMaxHP $ 10 + 1 `dl` 10, AddMaxCalm $ 1 `dl` 5 - 20 - 1 `d` 5] , ieffects = [Explode "blast 20", EqpSlot EqpSlotAddMaxHP] } ring3 = ring- { irarity = [(10, 5)]- , iaspects = [AddMaxCalm $ 29 + dl 10]+ { irarity = [(10, 3)]+ , iaspects = [AddMaxCalm $ 29 + 1 `dl` 10] , ieffects = [Explode "blast 20", EqpSlot EqpSlotMiscBonus] , idesc = "Cold, solid to the touch, perfectly round, engraved with solemn, strangely comforting, worn out words." } ring4 = ring { irarity = [(3, 3), (10, 5)]- , iaspects = [AddHurtMelee $ d 5 + dl 5 |*| 3, AddMaxHP $ dl 3 - 5 - d 3]+ , iaspects = [ AddHurtMelee $ (1 `d` 5 + 1 `dl` 5) * 3+ , AddMaxHP $ 1 `dl` 3 - 5 - 1 `d` 3 ] , ieffects = [Explode "blast 20", EqpSlot EqpSlotAddHurtMelee] } ring5 = ring -- by the time it's found, probably no space in eqp { irarity = [(5, 0), (10, 2)]- , iaspects = [AddShine $ d 2]+ , iaspects = [AddShine $ 1 `d` 2] , ieffects = [ Explode "distortion" -- strong magic , EqpSlot EqpSlotLightSource ] , idesc = "A sturdy ring with a large, shining stone."@@ -879,8 +891,8 @@ ring6 = ring { ifreq = [("treasure", 100)] , irarity = [(10, 2)]- , iaspects = [ AddSpeed $ 3 + d 4- , AddMaxCalm $ - 20 - d 20, AddMaxHP $ - 20 - d 20 ]+ , iaspects = [ AddSpeed $ 3 + 1 `d` 4+ , AddMaxCalm $ - 20 - 1 `d` 20, AddMaxHP $ - 20 - 1 `d` 20 ] , ieffects = [ Unique, ELabel "of Rush" -- no explosion, because Durable , EqpSlot EqpSlotAddSpeed ] , ifeature = Durable : ifeature ring@@ -915,8 +927,8 @@ , iweight = 7000 , idamage = toDmg 0 , iaspects = [ AddHurtMelee (-2)- , AddArmorMelee $ 1 + d 2 + dl 2 |*| 5- , AddArmorRanged $ dl 3 |*| 3 ]+ , AddArmorMelee $ (1 + 1 `d` 2 + 1 `dl` 2) * 5+ , AddArmorRanged $ (1 `dl` 3) * 3 ] , ieffects = [EqpSlot EqpSlotAddArmorMelee] , ifeature = [Durable, Identified, Equipable] , idesc = "A stiff jacket formed from leather boiled in bee wax, padded linen and horse hair. Protects from anything that is not too sharp. Smells much better than the rest of your garment."@@ -930,8 +942,8 @@ , iweight = 12000 , idamage = toDmg 0 , iaspects = [ AddHurtMelee (-3)- , AddArmorMelee $ 1 + d 2 + dl 2 |*| 5- , AddArmorRanged $ 2 + d 2 + dl 3 |*| 3 ]+ , AddArmorMelee $ (1 + 1 `d` 2 + 1 `dl` 2) * 5+ , AddArmorRanged $ (2 + 1 `d` 2 + 1 `dl` 3) * 3 ] , ieffects = [EqpSlot EqpSlotAddArmorRanged] , ifeature = [Durable, Identified, Equipable] , idesc = "A long shirt woven from iron rings that are hard to pierce through. Discourages foes from attacking your torso, making it harder for them to hit you."@@ -945,9 +957,9 @@ , irarity = [(5, 9), (10, 9)] , iverbHit = "flap" , iweight = 100- , idamage = toDmg $ 1 * d 1- , iaspects = [ AddHurtMelee $ d 2 + dl 7 |*| 3- , AddArmorRanged $ dl 2 |*| 3 ]+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [ AddHurtMelee $ (1 `d` 2 + 1 `dl` 7) * 3+ , AddArmorRanged $ (1 `dl` 2) * 3 ] , ieffects = [EqpSlot EqpSlotAddHurtMelee] , ifeature = [ toVelocity 50 -- flaps and flutters , Durable, Identified, Equipable ]@@ -960,9 +972,9 @@ , iflavour = zipPlain [BrCyan] , irarity = [(1, 9), (10, 3)] , iweight = 300- , idamage = toDmg $ 2 * d 1- , iaspects = [ AddArmorMelee $ 2 + dl 2 |*| 5- , AddArmorRanged $ dl 1 |*| 3 ]+ , idamage = toDmg $ 2 `d` 1+ , iaspects = [ AddArmorMelee $ (2 + 1 `dl` 2) * 5+ , AddArmorRanged $ (1 `dl` 1) * 3 ] , ieffects = [EqpSlot EqpSlotAddArmorMelee] , idesc = "Long leather gauntlet covered in overlapping steel plates." }@@ -972,10 +984,10 @@ , iflavour = zipFancy [BrRed] , irarity = [(1, 3), (10, 3)] , iweight = 1000- , idamage = toDmg $ 3 * d 1- , iaspects = [ AddHurtMelee $ dl 4 - 6 |*| 3- , AddArmorMelee $ 2 + d 2 + dl 2 |*| 5- , AddArmorRanged $ dl 2 |*| 3 ]+ , idamage = toDmg $ 3 `d` 1+ , iaspects = [ AddHurtMelee $ (1 `dl` 4 - 6) * 3+ , AddArmorMelee $ (2 + 1 `d` 2 + 1 `dl` 2) * 5+ , AddArmorRanged $ (1 `dl` 2) * 3 ] , ieffects = [Unique, EqpSlot EqpSlotAddArmorMelee] , idesc = "Rigid, steel, jousting handgear. If only you had a lance. And a horse." }@@ -997,10 +1009,10 @@ , irarity = [(4, 6)] , iverbHit = "bash" , iweight = 2000- , idamage = [(96, 2 * d 1), (3, 4 * d 1), (1, 8 * d 1)]+ , idamage = [(96, 2 `d` 1), (3, 4 `d` 1), (1, 8 `d` 1)] , iaspects = [ AddArmorMelee 40 -- not enough to compensate; won't be in eqp , AddHurtMelee (-30) -- too harmful; won't be wielded as weapon- , Timeout $ d 3 + 3 - dl 3 |*| 2 ]+ , Timeout $ (1 `d` 3 + 3 - 1 `dl` 3) * 2 ] , ieffects = [ Recharging (PushActor (ThrowMod 200 50)) , EqpSlot EqpSlotAddArmorMelee ] , ifeature = [ toVelocity 50 -- unwieldy to throw@@ -1013,10 +1025,10 @@ , irarity = [(8, 3)] , iflavour = zipPlain [Green] , iweight = 3000- , idamage = [(96, 4 * d 1), (3, 8 * d 1), (1, 16 * d 1)]+ , idamage = [(96, 4 `d` 1), (3, 8 `d` 1), (1, 16 `d` 1)] , iaspects = [ AddArmorMelee 80 -- not enough to compensate; won't be in eqp , AddHurtMelee (-70) -- too harmful; won't be wielded as weapon- , Timeout $ d 6 + 6 - dl 6 |*| 2 ]+ , Timeout $ (1 `d` 6 + 6 - 1 `dl` 6) * 2 ] , ieffects = [ Recharging (PushActor (ThrowMod 400 50)) , EqpSlot EqpSlotAddArmorMelee ] , ifeature = [ toVelocity 50 -- unwieldy to throw@@ -1035,9 +1047,9 @@ , irarity = [(1, 50), (3, 1)] , iverbHit = "stab" , iweight = 800- , idamage = toDmg $ 6 * d 1- , iaspects = [ AddHurtMelee $ d 3 + dl 3 |*| 3- , AddArmorMelee $ d 2 |*| 5 ]+ , idamage = toDmg $ 6 `d` 1+ , iaspects = [ AddHurtMelee $ (1 `d` 3 + 1 `dl` 3) * 3+ , AddArmorMelee $ (1 `d` 2) * 5 ] , ieffects = [EqpSlot EqpSlotWeapon] , ifeature = [ toVelocity 40 -- ensuring it hits with the tip costs speed , Durable, Identified, Meleeable ]@@ -1056,7 +1068,7 @@ -- If the effect is very powerful and so the timeout has to be significant, -- let's make it really large, for the effect to occur only once in a fight: -- as soon as the item is equipped, or just on the first strike.- , iaspects = iaspects dagger ++ [Timeout $ d 3 + 4 - dl 3 |*| 2]+ , iaspects = iaspects dagger ++ [Timeout $ (1 `d` 3 + 4 - 1 `dl` 3) * 2] , ieffects = ieffects dagger ++ [ Unique , Recharging DropBestWeapon, Recharging $ RefillCalm (-3) ]@@ -1071,8 +1083,8 @@ , irarity = [(5, 20), (8, 1)] , iverbHit = "club" , iweight = 1600- , idamage = [(96, 8 * d 1), (3, 12 * d 1), (1, 16 * d 1)]- , iaspects = [AddHurtMelee $ d 2 + dl 2 |*| 3]+ , idamage = [(96, 8 `d` 1), (3, 12 `d` 1), (1, 16 `d` 1)]+ , iaspects = [AddHurtMelee $ (1 `d` 2 + 1 `dl` 2) * 3] , ieffects = [EqpSlot EqpSlotWeapon] , ifeature = [ toVelocity 40 -- ensuring it hits with the tip costs speed , Durable, Identified, Meleeable ]@@ -1083,16 +1095,16 @@ { iname = "Concussion Hammer" , ifreq = [("treasure", 20)] , irarity = [(5, 1), (10, 6)]- , idamage = toDmg $ 8 * d 1- , iaspects = iaspects hammer ++ [Timeout $ d 2 + 3 - dl 2 |*| 2]+ , idamage = toDmg $ 8 `d` 1+ , iaspects = iaspects hammer ++ [Timeout $ (1 `d` 2 + 3 - 1 `dl` 2) * 2] , ieffects = ieffects hammer ++ [Unique, Recharging $ Paralyze 10] } hammerSpark = hammer { iname = "Grand Smithhammer" , ifreq = [("treasure", 20)] , irarity = [(5, 1), (10, 6)]- , idamage = toDmg $ 8 * d 1- , iaspects = iaspects hammer ++ [AddShine 3, Timeout $ d 4 + 4 - dl 4 |*| 2]+ , idamage = toDmg $ 12 `d` 1+ , iaspects = iaspects hammer ++ [AddShine 3, Timeout $ (1 `d` 4 + 4 - 1 `dl` 4) * 2] , ieffects = ieffects hammer ++ [Unique, Recharging $ Explode "spark"] } sword = ItemKind@@ -1104,7 +1116,7 @@ , irarity = [(4, 1), (5, 15)] , iverbHit = "slash" , iweight = 2000- , idamage = toDmg $ 10 * d 1+ , idamage = toDmg $ 10 `d` 1 , iaspects = [] , ieffects = [EqpSlot EqpSlotWeapon] , ifeature = [ toVelocity 40 -- ensuring it hits with the tip costs speed@@ -1116,16 +1128,15 @@ { iname = "Master's Sword" , ifreq = [("treasure", 20)] , irarity = [(5, 1), (10, 6)]- , iaspects = [Timeout $ d 4 + 5 - dl 4 |*| 2]- , ieffects = ieffects sword- ++ [Unique, Recharging Impress, Recharging (DetectActor 5)]- , idesc = "A particularly well-balance blade, lending itself to impressive shows of fencing skill. Master sees enemies reflected on its mirror-like surface."+ , iaspects = [Timeout $ (1 `d` 4 + 5 - 1 `dl` 4) * 2]+ , ieffects = ieffects sword ++ [Unique, Recharging Impress]+ , idesc = "A particularly well-balance blade, lending itself to impressive shows of fencing skill." } swordNullify = sword { iname = "Gutting Sword" , ifreq = [("treasure", 20)] , irarity = [(5, 1), (10, 6)]- , iaspects = [Timeout $ d 4 + 5 - dl 4 |*| 2]+ , iaspects = [Timeout $ (1 `d` 4 + 5 - 1 `dl` 4) * 2] , ieffects = ieffects sword ++ [ Unique , Recharging $ DropItem 1 maxBound COrgan@@ -1142,9 +1153,9 @@ , irarity = [(8, 1), (9, 40)] , iverbHit = "impale" , iweight = 3000- , idamage = [(96, 12 * d 1), (3, 18 * d 1), (1, 24 * d 1)]+ , idamage = [(96, 12 `d` 1), (3, 18 `d` 1), (1, 24 `d` 1)] , iaspects = [ AddHurtMelee (-20) -- just benign enough to be used- , AddArmorMelee $ 1 + dl 3 |*| 5 ]+ , AddArmorMelee $ (1 + 1 `dl` 3) * 5 ] , ieffects = [EqpSlot EqpSlotWeapon] , ifeature = [ toVelocity 20 -- not balanced , Durable, Identified, Meleeable ]@@ -1155,8 +1166,8 @@ { iname = "Swiss Halberd" , ifreq = [("treasure", 20)] , irarity = [(8, 1), (9, 20)]- , idamage = toDmg $ 12 * d 1- , iaspects = iaspects halberd ++ [Timeout $ d 5 + 5 - dl 5 |*| 2]+ , idamage = toDmg $ 12 `d` 1+ , iaspects = iaspects halberd ++ [Timeout $ (1 `d` 5 + 5 - 1 `dl` 5) * 2] , ieffects = ieffects halberd ++ [Unique, Recharging (PushActor (ThrowMod 400 25))] , idesc = "A versatile polearm, with great reach and leverage. Foes are held at a distance."@@ -1203,8 +1214,8 @@ , iaspects = [AddShine 1, AddSpeed (-1)] -- reflects strongly, distracts; so it glows in the dark, -- is visible on dark floor, but not too tempting to wear- , ieffects = []- , ifeature = [Precious] -- no @Identified@ and no effects, so never ided+ , ieffects = [RefillCalm (-1)] -- minor effect to ensure no id-on-pickup+ , ifeature = [Precious] -- no @Identified@, so kind not known , idesc = "Useless, and still worth around 100 gold each. Would gems of thought and pearls of artful design be valued that much in our age of Science and Progress!" , ikit = [] }@@ -1235,7 +1246,7 @@ , iname = "gold piece" , ifreq = [("treasure", 100), ("currency", 100)] , iflavour = zipPlain [BrYellow]- , icount = 10 + d 20 + dl 20+ , icount = 10 + 1 `d` 20 + 1 `dl` 20 , irarity = [(1, 25), (10, 10)] , iverbHit = "tap" , iweight = 31
GameDefinition/Content/ItemKindActor.hs view
@@ -296,14 +296,15 @@ , iverbHit = "thud" , iweight = 80000 , idamage = toDmg 0- , iaspects = [ AddMaxHP 20, AddMaxCalm 30, AddSpeed 20, AddNocto 2+ , iaspects = [ AddMaxHP 10, AddMaxCalm 30, AddSpeed 20, AddNocto 2 , AddAbility AbAlter (-2) ] -- can't use stairs nor doors , ieffects = [] , ifeature = [Durable, Identified] , idesc = "" , ikit = [ ("hooked claw", COrgan), ("snout", COrgan)- , ("armored skin", COrgan), ("nostril", COrgan)- , ("eye 3", COrgan), ("animal brain", COrgan) ]+ , ("armored skin", COrgan), ("armored skin", COrgan)+ , ("nostril", COrgan), ("eye 3", COrgan)+ , ("animal brain", COrgan) ] } gilaMonster = ItemKind { isymbol = 'g'@@ -516,7 +517,7 @@ geyserSulfur = ItemKind { isymbol = 'g' , iname = "sulfur geyser"- , ifreq = [("animal", 10), ("immobile animal", 100)]+ , ifreq = [("animal", 10), ("immobile animal", 120)] , iflavour = zipPlain [BrYellow] -- exception, animal with bright color , icount = 1 , irarity = [(1, 10), (5, 10)]
GameDefinition/Content/ItemKindBlast.hs view
@@ -75,13 +75,13 @@ , iname = "firecracker" , ifreq = [(toGroupName $ "firecracker" <+> tshow n, 1)] , iflavour = zipPlain [brightCol !! (n `mod` length brightCol)]- , icount = intToDice (n `div` 6) + d (n `div` 2)+ , icount = intToDice (n `div` 6) + 1 `d` (n `div` 2) , irarity = [(1, 1)] , iverbHit = "crack" , iweight = 1 , idamage = toDmg 0 , iaspects = [AddShine $ intToDice $ n `div` 2]- , ieffects = [ RefillCalm (-1) | n >= 5 ]+ , ieffects = [ RefillCalm (3 - n) | n >= 5 ] ++ [ DropBestWeapon | n >= 5] ++ [ OnSmash $ Explode $ toGroupName $ "firecracker" <+> tshow (n - 1)@@ -224,7 +224,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [Teleport $ 15 + d 10]+ , ieffects = [Teleport $ 15 + 1 `d` 10] , ifeature = [toLinger 10, Fragile, Identified] -- 2 steps, 1 turn , idesc = "" , ikit = []@@ -343,7 +343,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "strengthened" (3 + d 3)]+ , ieffects = [toOrganActorTurn "strengthened" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -359,7 +359,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganGameTurn "weakened" (3 + d 3)]+ , ieffects = [toOrganGameTurn "weakened" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -375,7 +375,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "protected from melee" (3 + d 3)]+ , ieffects = [toOrganActorTurn "protected from melee" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -391,7 +391,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "protected from ranged" (3 + d 3)]+ , ieffects = [toOrganActorTurn "protected from ranged" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -407,7 +407,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganGameTurn "defenseless" (3 + d 3)]+ , ieffects = [toOrganGameTurn "defenseless" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -423,7 +423,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "resolute" (3 + d 3)]+ , ieffects = [toOrganActorTurn "resolute" (3 + 1 `d` 3)] -- short enough duration that @calmEnough@ not a big problem , ifeature = [toLinger 10, Fragile, Identified] , idesc = ""@@ -440,7 +440,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "hasted" (3 + d 3)]+ , ieffects = [toOrganActorTurn "hasted" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -456,7 +456,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganGameTurn "slowed" (3 + d 3)]+ , ieffects = [toOrganGameTurn "slowed" (3 + 1 `d` 3)] , ifeature = [toVelocity 5, Fragile, Identified] -- 1 step, 1 turn, mist , idesc = "" , ikit = []@@ -472,7 +472,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "far-sighted" (3 + d 3)]+ , ieffects = [toOrganActorTurn "far-sighted" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -488,7 +488,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "blind" (10 + d 10)]+ , ieffects = [toOrganActorTurn "blind" (10 + 1 `d` 10)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -504,7 +504,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "keen-smelling" (3 + d 3)]+ , ieffects = [toOrganActorTurn "keen-smelling" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -520,7 +520,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "shiny-eyed" (3 + d 3)]+ , ieffects = [toOrganActorTurn "shiny-eyed" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -539,7 +539,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [toOrganActorTurn "drunk" (3 + d 3)]+ , ieffects = [toOrganActorTurn "drunk" (3 + 1 `d` 3)] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []@@ -555,7 +555,7 @@ , iweight = 1 , idamage = toDmg 0 , iaspects = []- , ieffects = [Burn (-1)]+ , ieffects = [Burn 1] , ifeature = [toLinger 10, Fragile, Identified] , idesc = "" , ikit = []
GameDefinition/Content/ItemKindEmbed.hs view
@@ -180,7 +180,7 @@ staircaseTrapDown = staircaseTrapUp { ifreq = [("staircase trap down", 1)] , ieffects = [ Temporary "tumble down the stairwell"- , toOrganActorTurn "drunk" (20 + d 5) ]+ , toOrganActorTurn "drunk" (20 + 1 `d` 5) ] } doorwayTrap = ItemKind { isymbol = '^'@@ -193,9 +193,9 @@ , iweight = 10000 , idamage = toDmg 0 , iaspects = []- , ieffects = [OneOf [ RefillCalm (-20)- , toOrganActorTurn "slowed" (20 + d 5)- , toOrganActorTurn "weakened" (20 + d 5) ]]+ , ieffects = [OneOf [ toOrganActorTurn "blind" (20 + 1 `d` 5)+ , toOrganActorTurn "slowed" (20 + 1 `d` 5)+ , toOrganActorTurn "weakened" (20 + 1 `d` 5) ]] , ifeature = [Identified] -- not Durable, springs at most once , idesc = "" , ikit = []@@ -214,7 +214,7 @@ , ieffects = [ Temporary "enter destructive rage at the sight of obscene pictograms" , RefillCalm (-20) , Recharging $ OneOf- [ toOrganActorTurn "strengthened" (3 + d 3)+ [ toOrganActorTurn "strengthened" (3 + 1 `d` 3) , CreateItem CInv "sandstone rock" TimerNone ] ] , ifeature = [Identified, Durable] , idesc = ""@@ -233,8 +233,8 @@ , iaspects = [Timeout 7] , ieffects = [ Temporary "feel refreshed by the subtle fresco" , RefillCalm 2- , Recharging $ toOrganActorTurn "far-sighted" (3 + d 3)- , Recharging $ toOrganActorTurn "keen-smelling" (3 + d 3) ]+ , Recharging $ toOrganActorTurn "far-sighted" (3 + 1 `d` 3)+ , Recharging $ toOrganActorTurn "keen-smelling" (3 + 1 `d` 3) ] , ifeature = [Identified, Durable] , idesc = "" , ikit = []@@ -266,8 +266,8 @@ , iweight = 10000 , idamage = toDmg 0 , iaspects = []- , ieffects = [ CreateItem CInv "any scroll" TimerNone- , toOrganGameTurn "defenseless" (20 + d 5)+ , ieffects = [ CreateItem CGround "any scroll" TimerNone+ , toOrganGameTurn "defenseless" (20 + 1 `d` 5) , Explode "PhD defense question" ] , ifeature = [Identified] -- not Durable, springs at most once , idesc = ""
GameDefinition/Content/ItemKindOrgan.hs view
@@ -1,4 +1,4 @@--- | Organ definitions.+-- | Actor organ definitions. module Content.ItemKindOrgan ( organs ) where@@ -37,7 +37,7 @@ , irarity = [(1, 1)] , iverbHit = "punch" , iweight = 2000- , idamage = toDmg $ 4 * d 1+ , idamage = toDmg $ 4 `d` 1 , iaspects = [] , ieffects = [] , ifeature = [Durable, Identified, Meleeable]@@ -48,7 +48,7 @@ { iname = "foot" , ifreq = [("foot", 50)] , iverbHit = "kick"- , idamage = toDmg $ 4 * d 1+ , idamage = toDmg $ 4 `d` 1 , idesc = "" } @@ -59,8 +59,8 @@ , ifreq = [("hooked claw", 50)] , icount = 2 -- even if more, only the fore claws used for fighting , iverbHit = "hook"- , idamage = toDmg $ 2 * d 1- , iaspects = [Timeout $ 4 + d 4]+ , idamage = toDmg $ 2 `d` 1+ , iaspects = [Timeout $ 4 + 1 `d` 4] , ieffects = [Recharging (toOrganGameTurn "slowed" 2)] , idesc = "" }@@ -68,7 +68,7 @@ { iname = "small claw" , ifreq = [("small claw", 50)] , iverbHit = "slash"- , idamage = toDmg $ 2 * d 1+ , idamage = toDmg $ 2 `d` 1 , idesc = "" } snout = fist@@ -76,7 +76,7 @@ , ifreq = [("snout", 10)] , icount = 1 , iverbHit = "bite"- , idamage = toDmg $ 2 * d 1+ , idamage = toDmg $ 2 `d` 1 , idesc = "" } smallJaw = fist@@ -84,7 +84,7 @@ , ifreq = [("small jaw", 20)] , icount = 1 , iverbHit = "rip"- , idamage = toDmg $ 3 * d 1+ , idamage = toDmg $ 3 `d` 1 , idesc = "" } jaw = fist@@ -92,7 +92,7 @@ , ifreq = [("jaw", 20)] , icount = 1 , iverbHit = "rip"- , idamage = toDmg $ 5 * d 1+ , idamage = toDmg $ 5 `d` 1 , idesc = "" } largeJaw = fist@@ -100,7 +100,7 @@ , ifreq = [("large jaw", 100)] , icount = 1 , iverbHit = "crush"- , idamage = toDmg $ 10 * d 1+ , idamage = toDmg $ 10 `d` 1 , idesc = "" } horn = fist@@ -108,7 +108,7 @@ , ifreq = [("horn", 20)] , icount = 2 , iverbHit = "impale"- , idamage = toDmg $ 6 * d 1+ , idamage = toDmg $ 6 `d` 1 , iaspects = [AddHurtMelee 20] , idesc = "" }@@ -120,40 +120,40 @@ , ifreq = [("tentacle", 50)] , icount = 4 , iverbHit = "slap"- , idamage = toDmg $ 4 * d 1+ , idamage = toDmg $ 4 `d` 1 , idesc = "" } thorn = fist { iname = "thorn" , ifreq = [("thorn", 100)]- , icount = 2 + d 3+ , icount = 2 + 1 `d` 3 , iverbHit = "impale"- , idamage = toDmg $ 1 * d 3+ , idamage = toDmg $ 1 `d` 3 , ifeature = [Identified, Meleeable] -- not Durable , idesc = "" } boilingFissure = fist { iname = "fissure" , ifreq = [("boiling fissure", 100)]- , icount = 5 + d 5+ , icount = 5 + 1 `d` 5 , iverbHit = "hiss at"- , idamage = toDmg $ 1 * d 1+ , idamage = toDmg $ 1 `d` 1 , iaspects = [AddHurtMelee 20] -- decreasing as count decreases- , ieffects = [InsertMove $ 1 * d 3]+ , ieffects = [InsertMove $ 1 `d` 3] , ifeature = [Identified, Meleeable] -- not Durable , idesc = "" } arsenicFissure = boilingFissure { iname = "fissure" , ifreq = [("arsenic fissure", 100)]- , icount = 3 + d 3- , idamage = toDmg $ 2 * d 1- , ieffects = [toOrganGameTurn "weakened" (2 + d 2)]+ , icount = 3 + 1 `d` 3+ , idamage = toDmg $ 2 `d` 1+ , ieffects = [toOrganGameTurn "weakened" (2 + 1 `d` 2)] } sulfurFissure = boilingFissure { iname = "fissure" , ifreq = [("sulfur fissure", 100)]- , icount = 2 + d 2+ , icount = 2 + 1 `d` 2 , idamage = toDmg 0 , ieffects = [RefillHP 5] }@@ -173,8 +173,8 @@ , ifreq = [("sting", 100)] , icount = 1 , iverbHit = "sting"- , idamage = toDmg $ 1 * d 1- , iaspects = [Timeout $ 1 + d 5, AddHurtMelee 40]+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [Timeout $ 1 + 1 `d` 5, AddHurtMelee 40] , ieffects = [Recharging (Paralyze 4)] , idesc = "Painful, debilitating and harmful." }@@ -183,9 +183,9 @@ , ifreq = [("venom tooth", 100)] , icount = 2 , iverbHit = "bite"- , idamage = toDmg $ 2 * d 1- , iaspects = [Timeout $ 5 + d 3]- , ieffects = [Recharging (toOrganGameTurn "slowed" (3 + d 3))]+ , idamage = toDmg $ 2 `d` 1+ , iaspects = [Timeout $ 5 + 1 `d` 3]+ , ieffects = [Recharging (toOrganGameTurn "slowed" (3 + 1 `d` 3))] , idesc = "" } venomFang = fist@@ -193,8 +193,8 @@ , ifreq = [("venom fang", 100)] , icount = 2 , iverbHit = "bite"- , idamage = toDmg $ 2 * d 1- , iaspects = [Timeout $ 7 + d 5]+ , idamage = toDmg $ 2 `d` 1+ , iaspects = [Timeout $ 7 + 1 `d` 5] , ieffects = [Recharging (toOrganNone "poisoned")] , idesc = "" }@@ -203,9 +203,9 @@ , ifreq = [("screeching beak", 100)] , icount = 1 , iverbHit = "peck"- , idamage = toDmg $ 2 * d 1- , iaspects = [Timeout $ 5 + d 5]- , ieffects = [Recharging $ Summon "scavenger" $ 1 + dl 2]+ , idamage = toDmg $ 2 `d` 1+ , iaspects = [Timeout $ 5 + 1 `d` 5]+ , ieffects = [Recharging $ Summon "scavenger" $ 1 + 1 `dl` 2] , idesc = "" } largeTail = fist@@ -213,8 +213,8 @@ , ifreq = [("large tail", 50)] , icount = 1 , iverbHit = "knock"- , idamage = toDmg $ 6 * d 1- , iaspects = [Timeout $ 1 + d 3, AddHurtMelee 20]+ , idamage = toDmg $ 6 `d` 1+ , iaspects = [Timeout $ 1 + 1 `d` 3, AddHurtMelee 20] , ieffects = [Recharging (PushActor (ThrowMod 400 25))] , idesc = "" }@@ -290,7 +290,7 @@ { iname = "insect mortality" , ifreq = [("insect mortality", 100)] , iverbHit = "age"- , iaspects = [Timeout $ 40 + d 10]+ , iaspects = [Timeout $ 40 + 1 `d` 10] , ieffects = [Periodic, Recharging (RefillHP (-1))] , idesc = "" }@@ -331,7 +331,7 @@ { iname = "scent gland" , ifreq = [("scent gland", 100)] , iverbHit = "spray at"- , iaspects = [Timeout $ 10 + d 2 |*| 5 ]+ , iaspects = [Timeout $ (10 + 1 `d` 2) * 5 ] , ieffects = [ Periodic, Recharging (Explode "distressing odor") , Recharging ApplyPerfume ] , idesc = ""@@ -341,7 +341,7 @@ , ifreq = [("boiling vent", 100)] , iflavour = zipPlain [Blue] , iverbHit = "menace"- , iaspects = [Timeout $ 2 + d 2 |*| 5]+ , iaspects = [Timeout $ (2 + 1 `d` 2) * 5] , ieffects = [Periodic , Recharging (Explode "boiling water") , Recharging (RefillHP 2) ]@@ -352,7 +352,7 @@ , ifreq = [("arsenic vent", 100)] , iflavour = zipPlain [Cyan] , iverbHit = "menace"- , iaspects = [Timeout $ 2 + d 2 |*| 5]+ , iaspects = [Timeout $ (2 + 1 `d` 2) * 5] , ieffects = [ Periodic , Recharging (Explode "sparse shower") , Recharging (RefillHP 2) ]@@ -363,7 +363,7 @@ , ifreq = [("sulfur vent", 100)] , iflavour = zipPlain [BrYellow] , iverbHit = "menace"- , iaspects = [Timeout $ 2 + d 2 |*| 5]+ , iaspects = [Timeout $ (2 + 1 `d` 2) * 5] , ieffects = [ Periodic , Recharging (Explode "dense shower") , Recharging (RefillHP 2) ]@@ -387,7 +387,7 @@ , ifreq = [("tooth", 20)] , icount = 3 , iverbHit = "nail"- , idamage = toDmg $ 2 * d 1+ , idamage = toDmg $ 2 `d` 1 , idesc = "" } lash = fist@@ -395,7 +395,7 @@ , ifreq = [("lash", 100)] , icount = 1 , iverbHit = "lash"- , idamage = toDmg $ 3 * d 1+ , idamage = toDmg $ 3 `d` 1 , idesc = "" } noseTip = fist@@ -403,7 +403,7 @@ , ifreq = [("nose tip", 50)] , icount = 1 , iverbHit = "poke"- , idamage = toDmg $ 2 * d 1+ , idamage = toDmg $ 2 `d` 1 , idesc = "" } lip = fist@@ -411,9 +411,9 @@ , ifreq = [("lip", 10)] , icount = 1 , iverbHit = "lap"- , idamage = toDmg $ 1 * d 1- , iaspects = [Timeout $ 3 + d 3]- , ieffects = [Recharging (toOrganGameTurn "weakened" (2 + d 2))]+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [Timeout $ 3 + 1 `d` 3]+ , ieffects = [Recharging (toOrganGameTurn "weakened" (2 + 1 `d` 2))] , idesc = "" } torsionRight = fist@@ -421,9 +421,9 @@ , ifreq = [("right torsion", 100)] , icount = 1 , iverbHit = "twist"- , idamage = toDmg $ 13 * d 1- , iaspects = [Timeout $ 5 + d 5, AddHurtMelee 20]- , ieffects = [Recharging (toOrganGameTurn "slowed" (3 + d 3))]+ , idamage = toDmg $ 13 `d` 1+ , iaspects = [Timeout $ 5 + 1 `d` 5, AddHurtMelee 20]+ , ieffects = [Recharging (toOrganGameTurn "slowed" (3 + 1 `d` 3))] , idesc = "" } torsionLeft = fist@@ -431,9 +431,9 @@ , ifreq = [("left torsion", 100)] , icount = 1 , iverbHit = "twist"- , idamage = toDmg $ 13 * d 1- , iaspects = [Timeout $ 5 + d 5, AddHurtMelee 20]- , ieffects = [Recharging (toOrganGameTurn "weakened" (3 + d 3))]+ , idamage = toDmg $ 13 `d` 1+ , iaspects = [Timeout $ 5 + 1 `d` 5, AddHurtMelee 20]+ , ieffects = [Recharging (toOrganGameTurn "weakened" (3 + 1 `d` 3))] , idesc = "" } pupil = fist@@ -441,8 +441,8 @@ , ifreq = [("pupil", 100)] , icount = 1 , iverbHit = "gaze at"- , idamage = toDmg $ 1 * d 1- , iaspects = [AddSight 12, Timeout $ 5 + d 5]+ , idamage = toDmg $ 1 `d` 1+ , iaspects = [AddSight 12, Timeout $ 5 + 1 `d` 5] , ieffects = [ Recharging (DropItem 1 maxBound COrgan "temporary condition") , Recharging $ RefillCalm (-10) ]
GameDefinition/Content/ItemKindTemporary.hs view
@@ -66,22 +66,22 @@ ] tmpRegenerating = let tmp = tmpAs "regenerating" []- in tmp { icount = 4 + d 2+ in tmp { icount = 4 + 1 `d` 2 , ieffects = Recharging (RefillHP 1) : ieffects tmp } tmpPoisoned = let tmp = tmpAs "poisoned" []- in tmp { icount = 4 + d 2+ in tmp { icount = 4 + 1 `d` 2 , ieffects = Recharging (RefillHP (-1)) : ieffects tmp } tmpSlow10Resistant = let tmp = tmpAs "slow resistant" []- in tmp { icount = 8 + d 4+ in tmp { icount = 8 + 1 `d` 4 , ieffects = Recharging (DropItem 1 1 COrgan "slowed") : ieffects tmp } tmpPoisonResistant = let tmp = tmpAs "poison resistant" []- in tmp { icount = 8 + d 4+ in tmp { icount = 8 + 1 `d` 4 , ieffects = Recharging (DropItem 1 maxBound COrgan "poisoned") : ieffects tmp }
GameDefinition/Content/ModeKind.hs view
@@ -22,9 +22,9 @@ , validateSingle = validateSingleModeKind , validateAll = validateAllModeKind , content = contentFromList- [raid, brawl, shootout, escape, zoo, ambush, exploration, explorationSurvival, safari, safariSurvival, battle, battleSurvival, defense, screensaverRaid, screensaverBrawl, screensaverShootout, screensaverEscape, screensaverZoo, screensaverAmbush, screensaverExploration, screensaverSafari]+ [raid, brawl, shootout, escape, zoo, ambush, crawl, crawlSurvival, safari, safariSurvival, battle, battleSurvival, defense, screensaverRaid, screensaverBrawl, screensaverShootout, screensaverEscape, screensaverZoo, screensaverAmbush, screensaverCrawl, screensaverSafari] }-raid, brawl, shootout, escape, zoo, ambush, exploration, explorationSurvival, safari, safariSurvival, battle, battleSurvival, defense, screensaverRaid, screensaverBrawl, screensaverShootout, screensaverEscape, screensaverZoo, screensaverAmbush, screensaverExploration, screensaverSafari :: ModeKind+raid, brawl, shootout, escape, zoo, ambush, crawl, crawlSurvival, safari, safariSurvival, battle, battleSurvival, defense, screensaverRaid, screensaverBrawl, screensaverShootout, screensaverEscape, screensaverZoo, screensaverAmbush, screensaverCrawl, screensaverSafari :: ModeKind -- What other symmetric (two only-one-moves factions) and asymmetric vs crowd -- scenarios make sense (e.g., are good for a tutorial or for standalone@@ -109,13 +109,13 @@ , mdesc = "Prevent hijacking of your ideas at all cost! Be stealthy, be aggressive. Fast execution is what makes or breaks a creative team." } -exploration = ModeKind+crawl = ModeKind { msymbol = 'c' , mname = "crawl (long)"- , mfreq = [("crawl", 1), ("exploration", 1), ("campaign scenario", 1)]- , mroster = rosterExploration- , mcaves = cavesExploration- , mdesc = "Enjoy the peaceful seclusion of these cold austere tunnels, but don't let wanton curiosity, greed and the ever-creeping abstraction madness keep you down there for too long."+ , mfreq = [("crawl", 1), ("campaign scenario", 1)]+ , mroster = rosterCrawl+ , mcaves = cavesCrawl+ , mdesc = "Enjoy the peaceful seclusion of these cold austere tunnels, but don't let wanton curiosity, greed and the ever-creeping abstraction madness keep you down there for too long. If find survivors (whole or perturbed or segmented) of the past scientific missions, exercise extreme caution and engage or ignore at your discretion." } safari = ModeKind -- easter egg available only via screensaver@@ -124,17 +124,17 @@ , mfreq = [("safari", 1)] , mroster = rosterSafari , mcaves = cavesSafari- , mdesc = "\"In this simulation you'll discover the joys of hunting the most exquisite of Earth's flora and fauna, both animal and semi-intelligent. Exit at the bottommost level.\" This is a VR recording recovered from a monster nest debris."+ , mdesc = "\"In this enactment you'll discover the joys of hunting the most exquisite of Earth's flora and fauna, both animal and semi-intelligent. Exit at the bottommost level.\" This is a drama script recovered from a monster nest debris." } -- * Testing modes -explorationSurvival = ModeKind+crawlSurvival = ModeKind { msymbol = 'd' , mname = "crawl survival" , mfreq = [("crawl survival", 1)]- , mroster = rosterExplorationSurvival- , mcaves = cavesExploration+ , mroster = rosterCrawlSurvival+ , mcaves = cavesCrawl , mdesc = "Lure the human intruders deeper and deeper." } @@ -144,7 +144,7 @@ , mfreq = [("safari survival", 1)] , mroster = rosterSafariSurvival , mcaves = cavesSafari- , mdesc = "In this simulation you'll discover the joys of being hunted among the most exquisite of Earth's flora and fauna, both animal and semi-intelligent."+ , mdesc = "In this enactment you'll discover the joys of being hunted among the most exquisite of Earth's flora and fauna, both animal and semi-intelligent." } battle = ModeKind@@ -170,7 +170,7 @@ , mname = "defense" , mfreq = [("defense", 1)] , mroster = rosterDefense- , mcaves = cavesExploration+ , mcaves = cavesCrawl , mdesc = "Don't let human interlopers defile your abstract secrets and flee unpunished!" } @@ -219,10 +219,10 @@ , mroster = screensave (AutoLeader False False) rosterAmbush } -screensaverExploration = exploration+screensaverCrawl = crawl { mname = "auto-crawl (long)" , mfreq = [("no confirms", 1)]- , mroster = screensave (AutoLeader False False) rosterExploration+ , mroster = screensave (AutoLeader False False) rosterCrawl } screensaverSafari = safari@@ -232,7 +232,7 @@ screensave (AutoLeader False True) rosterSafari } -rosterRaid, rosterBrawl, rosterShootout, rosterEscape, rosterZoo, rosterAmbush, rosterExploration, rosterExplorationSurvival, rosterSafari, rosterSafariSurvival, rosterBattle, rosterBattleSurvival, rosterDefense :: Roster+rosterRaid, rosterBrawl, rosterShootout, rosterEscape, rosterZoo, rosterAmbush, rosterCrawl, rosterCrawlSurvival, rosterSafari, rosterSafariSurvival, rosterBattle, rosterBattleSurvival, rosterDefense :: Roster rosterRaid = Roster { rosterList = [ ( playerHero {fhiCondPoly = hiRaid}@@ -319,21 +319,21 @@ , ("Indigo Researcher", "Horror Den") ] , rosterAlly = [] } -rosterExploration = Roster+rosterCrawl = Roster { rosterList = [ ( playerHero , [(-1, 3, "hero")] ) , ( playerMonster , [(-4, 1, "scout monster"), (-4, 3, "monster")] ) , ( playerAnimal , -- Fun from the start to avoid empty initial level:- [ (-1, 1 + d 2, "animal")+ [ (-1, 1 + 1 `d` 2, "animal") -- Huge battle at the end: , (-10, 100, "mobile animal") ] ) ] , rosterEnemy = [ ("Explorer", "Monster Hive") , ("Explorer", "Animal Kingdom") ] , rosterAlly = [("Monster Hive", "Animal Kingdom")] } -rosterExplorationSurvival = rosterExploration+rosterCrawlSurvival = rosterCrawl { rosterList = [ ( playerHero { fleaderMode = LeaderAI $ AutoLeader True False , fhasUI = False }@@ -342,7 +342,7 @@ , [(-4, 1, "scout monster"), (-4, 3, "monster")] ) , ( playerAnimal {fhasUI = True} , -- Fun from the start to avoid empty initial level:- [ (-1, 1 + d 2, "animal")+ [ (-1, 1 + 1 `d` 2, "animal") -- Huge battle at the end: , (-10, 100, "mobile animal") ] ) ] } @@ -407,16 +407,16 @@ , fhasUI = True } , [(-5, 30, "mobile animal")] ) ] } -rosterDefense = rosterExploration+rosterDefense = rosterCrawl { rosterList = [ ( playerAntiHero , [(-1, 3, "hero")] ) , ( playerAntiMonster , [(-4, 1, "scout monster"), (-4, 3, "monster")] ) , ( playerAnimal- , [ (-1, 1 + d 2, "animal")+ , [ (-1, 1 + 1 `d` 2, "animal") , (-10, 100, "mobile animal") ] ) ] } -cavesRaid, cavesBrawl, cavesShootout, cavesEscape, cavesZoo, cavesAmbush, cavesExploration, cavesSafari, cavesBattle :: Caves+cavesRaid, cavesBrawl, cavesShootout, cavesEscape, cavesZoo, cavesAmbush, cavesCrawl, cavesSafari, cavesBattle :: Caves cavesRaid = IM.fromList [(-2, "caveRaid")] @@ -430,7 +430,7 @@ cavesAmbush = IM.fromList [(-9, "caveAmbush")] -cavesExploration = IM.fromList $+cavesCrawl = IM.fromList $ [ (-1, "outermost") , (-2, "shallow random 2") , (-3, "caveEmpty") ]
GameDefinition/Content/PlaceKind.hs view
@@ -32,8 +32,7 @@ rect = PlaceKind -- Valid for any nonempty area, hence low frequency. { psymbol = 'r' , pname = "room"- , pfreq = [ ("rogue", 100), ("arena", 40), ("laboratory", 40)- , ("shootout", 8), ("zoo", 7) ]+ , pfreq = [("rogue", 100), ("arena", 40), ("laboratory", 40), ("zoo", 9)] , prarity = [(1, 10), (10, 8)] , pcover = CStretch , pfence = FNone@@ -59,7 +58,7 @@ glasshouse = PlaceKind { psymbol = 'g' , pname = "glasshouse"- , pfreq = [("arena", 40), ("zoo", 12)]+ , pfreq = [("arena", 40), ("shootout", 8), ("zoo", 9)] , prarity = [(1, 10), (10, 8)] , pcover = CStretch , pfence = FNone
GameDefinition/Content/TileKind.hs view
@@ -115,7 +115,8 @@ , talter = 2 , tfeature = [ RevealAs "trapped vertical door Lit" , ObscureAs "obscured vertical wall Lit"- , Indistinct ]+ , Indistinct+ ] } wallObscured = TileKind { tsymbol = '|'@@ -137,7 +138,8 @@ , tcolor2 = defFG , talter = 100 , tfeature = [ BuildAs "suspect horizontal wall Lit"- , Indistinct ]+ , Indistinct+ ] } wallSuspectH = TileKind -- only on client { tsymbol = '-'@@ -148,7 +150,8 @@ , talter = 2 , tfeature = [ RevealAs "trapped horizontal door Lit" , ObscureAs "obscured horizontal wall Lit"- , Indistinct ]+ , Indistinct+ ] } wallObscuredDefacedH = TileKind { tsymbol = '-'@@ -214,11 +217,12 @@ , tcolor = BrCyan , tcolor2 = Cyan , talter = 5- , tfeature = [ Embed "signboard", Indistinct- , ConsideredByAI -- changes after use, so safe for AI- , RevealAs "signboard" ] -- to display as hidden+ , tfeature = [ ConsideredByAI -- changes after use, so safe for AI+ , RevealAs "signboard" -- to display as hidden+ , Indistinct+ ] }-signboardRead = TileKind -- after first use revealed to be this one+signboardRead = TileKind { tsymbol = 'O' , tname = "signboard" , tfreq = [("signboard", 1), ("zooSet", 2)]
GameDefinition/Main.hs view
@@ -8,24 +8,34 @@ import Game.LambdaHack.Common.Prelude -import Control.Concurrent.Async+import Control.Concurrent.Async import qualified Control.Exception as Ex-import System.Environment (getArgs)-import System.Exit+import qualified GHC.IO.Handle as GHC.IO.Handle+import qualified Options.Applicative as OA+import System.Exit+import qualified System.IO as SIO +import Game.LambdaHack.Server (serverOptionsPI) import TieKnot --- | Tie the LambdaHack engine client, server and frontend code--- with the game-specific content definitions, and run the game.+-- | Parse commandline options, tie the engine, content and clients knot,+-- run the game and handle exit. main :: IO () main = do- args <- getArgs+ -- For the case when the game is started not on a console.+ isTerminal <- SIO.hIsTerminalDevice SIO.stdout+ unless isTerminal $ do+ fstdout <- SIO.openFile "stdout.txt" SIO.WriteMode+ fstderr <- SIO.openFile "stderr.txt" SIO.WriteMode+ GHC.IO.Handle.hDuplicateTo fstdout SIO.stdout+ GHC.IO.Handle.hDuplicateTo fstderr SIO.stderr+ serverOptions <- OA.execParser serverOptionsPI -- Avoid the bound thread that would slow down the communication.- a <- async $ tieKnot args+ a <- async $ tieKnot serverOptions ex <- waitCatch a case ex of Right () -> return () Left e -> case Ex.fromException e of Just ExitSuccess ->- exitSuccess -- we are in the main thread, so it really exits+ exitSuccess -- we are in the main thread, so here it really exits _ -> Ex.throwIO e
GameDefinition/PLAYING.md view
@@ -270,7 +270,8 @@ The stress of combat drains Calm, gradually limiting the use of items and, if Calm reaches zero and the actor is sufficiently impressed by his foes, making him defect and surrender to their domination.-Whenever the monster's or hero's hit points reach zero, the combatant dies.+Whenever the monster's or hero's hit points reach zero,+the combatant is incapacitated and promptly dies. When the last hero dies or is dominated, the scenario ends in defeat.
GameDefinition/TieKnot.hs view
@@ -1,4 +1,4 @@--- | Here the knot of engine code pieces and the game-specific+-- | Here the knot of engine code pieces, frontend and the game-specific -- content definitions is tied, resulting in an executable game. module TieKnot ( tieKnot@@ -10,12 +10,12 @@ import qualified System.Random as R -import Game.LambdaHack.Common.ContentDef+import Game.LambdaHack.Common.ContentDef import qualified Game.LambdaHack.Common.Kind as Kind import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.ItemKind-import Game.LambdaHack.SampleImplementation.SampleMonadServer (executorSer)-import Game.LambdaHack.Server+import qualified Game.LambdaHack.Content.ItemKind as IK+import Game.LambdaHack.SampleImplementation.SampleMonadServer (executorSer)+import Game.LambdaHack.Server import qualified Client.UI.Content.KeyKind as Content.KeyKind import qualified Content.CaveKind@@ -28,47 +28,29 @@ -- | Tie the LambdaHack engine client, server and frontend code -- with the game-specific content definitions, and run the game. ----- The action monad types to be used are determined by the 'executorSer'--- and 'executorCli' calls. If other functions are used in their place--- the types are different and so the whole pattern of computation--- is different. Which of the frontends is run inside the UI client+-- The custom monad types to be used are determined by the 'executorSer'+-- call, which in turn calls 'executorCli'. If other functions are used+-- in their place- the types are different and so the whole pattern+-- of computation differs. Which of the frontends is run inside the UI client -- depends on the flags supplied when compiling the engine library.-tieKnot :: [String] -> IO ()-tieKnot args = do- -- Options for the next game taken from the commandline.- let sdebug@DebugModeSer{sallClear, sboostRandomItem, sdungeonRng} =- debugArgs args+-- Similarly for the choice of native vs JS builds.+tieKnot :: ServerOptions -> IO ()+tieKnot options@ServerOptions{sallClear, sboostRandomItem, sdungeonRng} = do -- This setup ensures the boosting option doesn't affect generating initial -- RNG for dungeon, etc., and also, that setting dungeon RNG on commandline -- equal to what was generated last time, ensures the same item boost. initialGen <- maybe R.getStdGen return sdungeonRng- let sdebugNxt = sdebug {sdungeonRng = Just initialGen}+ let soptionsNxt = options {sdungeonRng = Just initialGen}+ cotile = Kind.createOps Content.TileKind.cdefs+ boostedItems = IK.boostItemKindList initialGen Content.ItemKind.items+ coitem = Kind.createOps $+ if sboostRandomItem+ then Content.ItemKind.cdefs+ {content = contentFromList+ $ boostedItems ++ Content.ItemKind.otherItemContent}+ else Content.ItemKind.cdefs -- Common content operations, created from content definitions. -- Evaluated fully to discover errors ASAP and to free memory.- cotile = Kind.createOps Content.TileKind.cdefs- boostItem :: ItemKind -> ItemKind- boostItem i =- let mainlineLabel (label, _) = label `elem` ["useful", "treasure"]- in if any mainlineLabel (ifreq i)- then i { ifreq = ("useful", 10000)- : filter (not . mainlineLabel) (ifreq i)- , ieffects = delete Unique $ ieffects i- }- else i- boostList :: [ItemKind] -> [ItemKind]- boostList l | not sboostRandomItem = l- boostList [] = []- boostList l =- let (r, _) = R.randomR (0, length l - 1) initialGen- in case splitAt r l of- (pre, i : post) -> pre ++ boostItem i : post- _ -> error $ "" `showFailure` l- boostedItems = boostList Content.ItemKind.items- cdefsItem =- Content.ItemKind.cdefs- {content = contentFromList- $ boostedItems ++ Content.ItemKind.otherItemContent}- coitem = Kind.createOps cdefsItem !cops = Kind.COps { cocave = Kind.createOps Content.CaveKind.cdefs , coitem@@ -83,4 +65,4 @@ !copsClient = Content.KeyKind.standardKeys -- Wire together game content, the main loops of game clients -- and the game server loop.- executorSer cops copsClient sdebugNxt+ executorSer cops copsClient soptionsNxt
LambdaHack.cabal view
@@ -5,7 +5,7 @@ -- PVP summary:+-+------- breaking API changes -- | | +----- minor or non-breaking API additions -- | | | +--- code changes with no API change-version: 0.6.2.0+version: 0.7.0.0 synopsis: A game engine library for roguelike dungeon crawlers description: LambdaHack is a Haskell game engine library for roguelike games of arbitrary theme, size and complexity,@@ -60,7 +60,7 @@ bug-reports: http://github.com/LambdaHack/LambdaHack/issues license: BSD3 license-file: LICENSE-tested-with: GHC >= 8.0 && <= 8.2+tested-with: GHC==8.0.2, GHC==8.2.1, GHC==8.2.2 data-files: GameDefinition/config.ui.default, GameDefinition/fonts/16x16x.fon, GameDefinition/fonts/8x8xb.fon,@@ -124,7 +124,6 @@ exposed-modules: Game.LambdaHack.Atomic Game.LambdaHack.Atomic.CmdAtomic Game.LambdaHack.Atomic.HandleAtomicWrite- Game.LambdaHack.Atomic.MonadAtomic Game.LambdaHack.Atomic.MonadStateWrite Game.LambdaHack.Atomic.PosAtomicRead Game.LambdaHack.Client@@ -136,17 +135,19 @@ Game.LambdaHack.Client.AI.Strategy Game.LambdaHack.Client.Bfs Game.LambdaHack.Client.BfsM+ Game.LambdaHack.Client.ClientOptions Game.LambdaHack.Client.CommonM Game.LambdaHack.Client.HandleAtomicM Game.LambdaHack.Client.HandleResponseM Game.LambdaHack.Client.LoopM Game.LambdaHack.Client.MonadClient Game.LambdaHack.Client.Preferences+ Game.LambdaHack.Client.Request+ Game.LambdaHack.Client.Response Game.LambdaHack.Client.State Game.LambdaHack.Client.UI Game.LambdaHack.Client.UI.ActorUI Game.LambdaHack.Client.UI.Animation- Game.LambdaHack.Client.UI.Config Game.LambdaHack.Client.UI.Content.KeyKind Game.LambdaHack.Client.UI.DrawM Game.LambdaHack.Client.UI.DisplayAtomicM@@ -171,15 +172,14 @@ Game.LambdaHack.Client.UI.Msg Game.LambdaHack.Client.UI.MsgM Game.LambdaHack.Client.UI.Overlay- Game.LambdaHack.Client.UI.OverlayM Game.LambdaHack.Client.UI.RunM Game.LambdaHack.Client.UI.SessionUI Game.LambdaHack.Client.UI.Slideshow Game.LambdaHack.Client.UI.SlideshowM+ Game.LambdaHack.Client.UI.UIOptions Game.LambdaHack.Common.Ability Game.LambdaHack.Common.Actor Game.LambdaHack.Common.ActorState- Game.LambdaHack.Common.ClientOptions Game.LambdaHack.Common.Color Game.LambdaHack.Common.ContentDef Game.LambdaHack.Common.Dice@@ -200,10 +200,9 @@ Game.LambdaHack.Common.Point Game.LambdaHack.Common.Prelude Game.LambdaHack.Common.Random+ Game.LambdaHack.Common.ReqFailure Game.LambdaHack.Common.RingBuffer Game.LambdaHack.Common.Save- Game.LambdaHack.Common.Request- Game.LambdaHack.Common.Response Game.LambdaHack.Common.State Game.LambdaHack.Common.Thread Game.LambdaHack.Common.Tile@@ -239,6 +238,7 @@ Game.LambdaHack.Server.MonadServer Game.LambdaHack.Server.PeriodicM Game.LambdaHack.Server.ProtocolM+ Game.LambdaHack.Server.ServerOptions Game.LambdaHack.Server.StartM Game.LambdaHack.Server.State @@ -260,10 +260,11 @@ hsini >= 0.2, keys >= 3, miniutter >= 0.4.5.0,- time >= 1.4,+ optparse-applicative >= 0.13, pretty-show >= 1.6, random >= 1.1, stm >= 2.4,+ time >= 1.4, text >= 0.11.2.3, transformers >= 0.4, unordered-containers >= 0.2.3,@@ -273,7 +274,7 @@ default-language: Haskell2010 default-extensions: MonoLocalBinds, ScopedTypeVariables, OverloadedStrings BangPatterns, RecordWildCards, NamedFieldPuns, MultiWayIf,- StrictData, CPP+ LambdaCase, StrictData, CPP other-extensions: TemplateHaskell, MultiParamTypeClasses, RankNTypes, TypeFamilies, FlexibleContexts, FlexibleInstances, DeriveFunctor, FunctionalDependencies,@@ -281,44 +282,37 @@ DeriveFoldable, DeriveTraversable, ExistentialQuantification, GADTs, StandaloneDeriving, DataKinds, KindSignatures, DeriveGeneric- ghc-options: -Wall -Wcompat -Worphans -Wincomplete-uni-patterns -Wincomplete-record-updates -Wimplicit-prelude -Wmissing-home-modules -Widentities+ ghc-options: -Wall -Wcompat -Worphans -Wincomplete-uni-patterns -Wincomplete-record-updates -Wimplicit-prelude -Wmissing-home-modules -Widentities -Wredundant-constraints ghc-options: -Wall-missed-specialisations- ghc-options: -fno-ignore-asserts -fexpose-all-unfoldings -fspecialise-aggressively+ ghc-options: -fno-ignore-asserts -fexpose-all-unfoldings -fspecialise-aggressively -fsimpl-tick-factor=200 - if impl(ghcjs) {- other-modules: Game.LambdaHack.Client.UI.Frontend.Dom+ if impl(ghcjs) || flag(jsaddle) {+ exposed-modules: Game.LambdaHack.Client.UI.Frontend.Dom build-depends: ghcjs-dom >= 0.9.1.1- cpp-options: -DUSE_BROWSER -DUSE_JSFILE+ cpp-options: -DUSE_BROWSER } else { if flag(vty) {- other-modules: Game.LambdaHack.Client.UI.Frontend.Vty+ exposed-modules: Game.LambdaHack.Client.UI.Frontend.Vty build-depends: vty >= 5 cpp-options: -DUSE_VTY } else { if flag(curses) {- other-modules: Game.LambdaHack.Client.UI.Frontend.Curses+ exposed-modules: Game.LambdaHack.Client.UI.Frontend.Curses build-depends: hscurses >= 1.4.1 cpp-options: -DUSE_CURSES } else { if flag(gtk) {- other-modules: Game.LambdaHack.Client.UI.Frontend.Gtk+ exposed-modules: Game.LambdaHack.Client.UI.Frontend.Gtk build-depends: gtk3 >= 0.12.1 cpp-options: -DUSE_GTK- } else { if flag(sdl) {- other-modules: Game.LambdaHack.Client.UI.Frontend.Sdl- build-depends: sdl2 >= 2, sdl2-ttf >= 2- cpp-options: -DUSE_SDL- } else { if flag(jsaddle) {- other-modules: Game.LambdaHack.Client.UI.Frontend.Dom- build-depends: ghcjs-dom >= 0.9.1.1- cpp-options: -DUSE_BROWSER } else {- other-modules: Game.LambdaHack.Client.UI.Frontend.Sdl+ exposed-modules: Game.LambdaHack.Client.UI.Frontend.Sdl build-depends: sdl2 >= 2, sdl2-ttf >= 2 cpp-options: -DUSE_SDL- } } } } } }+ } } } } if impl(ghcjs) {- other-modules: Game.LambdaHack.Common.JSFile+ exposed-modules: Game.LambdaHack.Common.JSFile+ cpp-options: -DUSE_JSFILE } else {- other-modules: Game.LambdaHack.Common.HSFile+ exposed-modules: Game.LambdaHack.Common.HSFile build-depends: zlib >= 0.5.3.1 } @@ -365,11 +359,12 @@ hsini >= 0.2, keys >= 3, miniutter >= 0.4.5.0,- time >= 1.4,+ optparse-applicative >= 0.13, pretty-show >= 1.6, random >= 1.1, stm >= 2.4, text >= 0.11.2.3,+ time >= 1.4, transformers >= 0.4, unordered-containers >= 0.2.3, vector >= 0.11,@@ -378,9 +373,9 @@ default-language: Haskell2010 default-extensions: MonoLocalBinds, ScopedTypeVariables, OverloadedStrings BangPatterns, RecordWildCards, NamedFieldPuns, MultiWayIf,- StrictData+ LambdaCase, StrictData other-extensions: TemplateHaskell- ghc-options: -Wall -Wcompat -Worphans -Wincomplete-uni-patterns -Wincomplete-record-updates -Wimplicit-prelude -Wmissing-home-modules -Widentities+ ghc-options: -Wall -Wcompat -Worphans -Wincomplete-uni-patterns -Wincomplete-record-updates -Wimplicit-prelude -Wmissing-home-modules -Widentities -Wredundant-constraints ghc-options: -Wall-missed-specialisations ghc-options: -fno-ignore-asserts -fexpose-all-unfoldings -fspecialise-aggressively ghc-options: -threaded -rtsopts@@ -438,11 +433,12 @@ hsini >= 0.2, keys >= 3, miniutter >= 0.4.5.0,- time >= 1.4,+ optparse-applicative >= 0.13, pretty-show >= 1.6, random >= 1.1, stm >= 2.4, text >= 0.11.2.3,+ time >= 1.4, transformers >= 0.4, unordered-containers >= 0.2.3, vector >= 0.11,@@ -451,9 +447,9 @@ default-language: Haskell2010 default-extensions: MonoLocalBinds, ScopedTypeVariables, OverloadedStrings BangPatterns, RecordWildCards, NamedFieldPuns, MultiWayIf,- StrictData+ LambdaCase, StrictData other-extensions: TemplateHaskell- ghc-options: -Wall -Wcompat -Worphans -Wincomplete-uni-patterns -Wincomplete-record-updates -Wimplicit-prelude -Wmissing-home-modules -Widentities+ ghc-options: -Wall -Wcompat -Worphans -Wincomplete-uni-patterns -Wincomplete-record-updates -Wimplicit-prelude -Wmissing-home-modules -Widentities -Wredundant-constraints ghc-options: -fno-ignore-asserts -fexpose-all-unfoldings -fspecialise-aggressively ghc-options: -threaded -rtsopts
Makefile view
@@ -34,8 +34,8 @@ frontendAmbush: dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 5 --dumpInitRngs --automateAll --gameMode ambush -frontendExploration:- dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 1 --dumpInitRngs --automateAll --gameMode exploration+frontendCrawl:+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 1 --dumpInitRngs --automateAll --gameMode crawl frontendSafari: dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix test --newGame 2 --dumpInitRngs --automateAll --gameMode safari@@ -54,7 +54,7 @@ benchMemoryAnim:- dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --maxFps 100000 --benchmark --stopAfterFrames 33000 --automateAll --keepAutomated --gameMode exploration --setDungeonRng 120 --setMainRng 47 --frontendNull --noAnim +RTS -s -A1M -RTS+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --maxFps 100000 --benchmark --stopAfterFrames 33000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 120 --setMainRng 47 --frontendNull --noAnim +RTS -s -A1M -RTS benchBattle: dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1500 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7@@ -65,31 +65,31 @@ benchFrontendBattle: dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --benchmark --stopAfterFrames 1500 --automateAll --keepAutomated --gameMode battle --setDungeonRng 7 --setMainRng 7 -benchExploration:- dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode exploration --setDungeonRng 0 --setMainRng 0+benchCrawl:+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0 -benchFrontendExploration:- dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --noAnim --maxFps 100000 --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode exploration --setDungeonRng 0 --setMainRng 0+benchFrontendCrawl:+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 1 --noAnim --maxFps 100000 --benchmark --stopAfterFrames 7000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0 -benchNull: benchBattle benchAnimBattle benchExploration+benchNull: benchBattle benchAnimBattle benchCrawl -bench: benchBattle benchAnimBattle benchFrontendBattle benchExploration benchFrontendExploration+bench: benchBattle benchAnimBattle benchFrontendBattle benchCrawl benchFrontendCrawl -nativeBenchExploration:- dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode exploration --setDungeonRng 0 --setMainRng 0+nativeBenchCrawl:+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0 nativeBenchBattle: dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 0 --setMainRng 0 -nativeBench: nativeBenchBattle nativeBenchExploration+nativeBench: nativeBenchBattle nativeBenchCrawl -nodeBenchExploration:- node dist/build/LambdaHack/LambdaHack.jsexe/all.js --dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode exploration --setDungeonRng 0 --setMainRng 0+nodeBenchCrawl:+ node dist/build/LambdaHack/LambdaHack.jsexe/all.js --dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 2000 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 0 --setMainRng 0 nodeBenchBattle: node dist/build/LambdaHack/LambdaHack.jsexe/all.js --dbgMsgSer --newGame 3 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 1000 --automateAll --keepAutomated --gameMode battle --setDungeonRng 0 --setMainRng 0 -nodeBench: nodeBenchBattle nodeBenchExploration+nodeBench: nodeBenchBattle nodeBenchCrawl test-travis-short: test-short@@ -100,7 +100,7 @@ test-short: test-short-new test-short-load -test-medium: testRaid-medium testBrawl-medium testShootout-medium testEscape-medium testZoo-medium testAmbush-medium testExploration-medium testSafari-medium testSafariSurvival-medium testBattle-medium testBattleSurvival-medium+test-medium: testRaid-medium testBrawl-medium testShootout-medium testEscape-medium testZoo-medium testAmbush-medium testCrawl-medium testSafari-medium testSafariSurvival-medium testBattle-medium testBattleSurvival-medium testRaid-medium: dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode raid 2> /tmp/teletypetest.log@@ -120,8 +120,8 @@ testAmbush-medium: dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 20 --dumpInitRngs --automateAll --keepAutomated --gameMode ambush 2> /tmp/teletypetest.log -testExploration-medium:- dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 1 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 200 --dumpInitRngs --automateAll --keepAutomated --gameMode exploration 2> /tmp/teletypetest.log+testCrawl-medium:+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 1 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 200 --dumpInitRngs --automateAll --keepAutomated --gameMode crawl 2> /tmp/teletypetest.log testSafari-medium: dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 2 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 100 --dumpInitRngs --automateAll --keepAutomated --gameMode safari 2> /tmp/teletypetest.log@@ -136,7 +136,7 @@ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 7 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 60 --dumpInitRngs --automateAll --keepAutomated --gameMode "battle survival" 2> /tmp/teletypetest.log testDefense-medium:- dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 9 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 500 --dumpInitRngs --automateAll --keepAutomated --gameMode defense 2> /tmp/teletypetest.log+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 9 --noAnim --maxFps 100000 --frontendTeletype --benchmark --stopAfterSeconds 300 --dumpInitRngs --automateAll --keepAutomated --gameMode defense 2> /tmp/teletypetest.log test-short-new: dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix raid --dumpInitRngs --automateAll --keepAutomated --gameMode raid --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log@@ -145,7 +145,7 @@ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix escape --dumpInitRngs --automateAll --keepAutomated --gameMode escape --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix zoo --dumpInitRngs --automateAll --keepAutomated --gameMode zoo --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix ambush --dumpInitRngs --automateAll --keepAutomated --gameMode ambush --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log- dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix exploration --dumpInitRngs --automateAll --keepAutomated --gameMode exploration --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix crawl --dumpInitRngs --automateAll --keepAutomated --gameMode crawl --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix safari --dumpInitRngs --automateAll --keepAutomated --gameMode safari --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix safariSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --newGame 5 --savePrefix battle --dumpInitRngs --automateAll --keepAutomated --gameMode battle --frontendTeletype --stopAfterSeconds 2 2> /tmp/teletypetest.log@@ -160,7 +160,7 @@ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix escape --dumpInitRngs --automateAll --keepAutomated --gameMode escape --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix zoo --dumpInitRngs --automateAll --keepAutomated --gameMode zoo --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix ambush --dumpInitRngs --automateAll --keepAutomated --gameMode ambush --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log- dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix exploration --dumpInitRngs --automateAll --keepAutomated --gameMode exploration --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log+ dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix crawl --dumpInitRngs --automateAll --keepAutomated --gameMode crawl --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix safari --dumpInitRngs --automateAll --keepAutomated --gameMode safari --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix safariSurvival --dumpInitRngs --automateAll --keepAutomated --gameMode "safari survival" --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log dist/build/LambdaHack/LambdaHack --dbgMsgSer --boostRandomItem --savePrefix battle --dumpInitRngs --automateAll --keepAutomated --gameMode battle --frontendTeletype --stopAfterSeconds 2 --setDungeonRng 0 --setMainRng 0 2> /tmp/teletypetest.log@@ -190,3 +190,6 @@ build-binary: build-binary-common cp LambdaHackTheGameInstall/bin/LambdaHack LambdaHackTheGame tar -czf LambdaHack_x_ubuntu-16.04-amd64.tar.gz LambdaHackTheGame++new-build-dev:+ cabal new-build --datadir=. --disable-optimization -j1
README.md view
@@ -166,15 +166,7 @@ cabal install -fvty -To compile with GTK2 (deprecated but still supported; beware that-the font is not square and special position highlights are annoying),-you need GTK libraries for your OS. On Windows follow the same steps-as for Wine[13]. On OSX, if you encounter problems, you may want to-compile the GTK libraries from sources[14]. Invoke Cabal as follows - cabal install -fgtk gtk2hs-buildtools .-- Testing and debugging --------------------- @@ -187,8 +179,8 @@ with the standard, user-friendly frontend. Run `LambdaHack --help` to see a brief description of all debug options.-Of these, `--sniffIn` and `--sniffOut` are very useful (though verbose-and initially cryptic), for monitoring the traffic between clients+Of these, the `--sniff` option is very useful (though verbose+and initially cryptic), for displaying the traffic between clients and the server. Some options in the config file may prove useful too, though they mostly overlap with commandline options (and will be totally merged at some point).@@ -209,6 +201,26 @@ because HPC requires a clean exit to save data files. +Coding style+------------++Stylish Haskell is used for slight auto-formatting at buffer save; see+[.stylish-haskell.yaml](https://github.com/LambdaHack/LambdaHack/blob/master/.stylish-haskell.yaml).+As defined in the file, indentation is 2 spaces wide and screen is+80-columns wide. Spaces are used, not tabs. Spurious whitespace avoided.+Spaces around arithmetic operators encouraged.+Generally, relax and try to stick to the style apparent in a file+you are editing. Put big formatting changes in separate commits.++Haddocks are provided for all module headers and for all functions and types+from major modules, in particular the modules that are interfaces+for a whole directory of modules. Apart of that only very important+functions and types are distinguished by having a haddock.+If minor ones have comments, they should not be haddocks+and they are permitted to describe implementation details and be out of date.+Prefer assertions in place of comments, unless too verbose.++ Further information ------------------- @@ -232,8 +244,6 @@ [9]: https://github.com/LambdaHack/LambdaHack/wiki/Sample-dungeon-crawler [10]: https://github.com/AllureOfTheStars/Allure [11]: https://github.com/LambdaHack/LambdaHack/releases/latest-[13]: http://www.haskell.org/haskellwiki/GHC_under_Wine#Code_that_uses_gtk2hs-[14]: http://www.edsko.net/2014/04/27/haskell-including-gtk-on-mavericks [15]: https://github.com/ghcjs/ghcjs [16]: https://www.npmjs.com/package/google-closure-compiler [17]: https://ci.appveyor.com/project/Mikolaj/lambdahack-4hh0j/build/artifacts
test/test.hs view
@@ -1,10 +1,15 @@ import Prelude () +import Options.Applicative+ import Game.LambdaHack.Common.Prelude+import Game.LambdaHack.Server import TieKnot main :: IO ()-main =- tieKnot $ words "--dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 100 --automateAll --keepAutomated --gameMode exploration --setDungeonRng 42 --setMainRng 42"+main = do+ let args = words "--dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 100 --automateAll --keepAutomated --gameMode crawl --setDungeonRng 42 --setMainRng 42"+ serverOptions <- handleParseResult $ execParserPure defaultPrefs serverOptionsPI args+ tieKnot serverOptions -- tieKnot $ words "--dbgMsgSer --newGame 2 --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfterFrames 100 --automateAll --keepAutomated --gameMode battle --setDungeonRng 42 --setMainRng 42"